Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,16 @@

```handlebars
<button {{action 'foo'}}>
<button onblur={{action 'foo'}}>
<button onblur={{action (action 'foo') 'bar'}}>
```

with

```handlebars
<button {{action this 'foo'}}>
<button onblur={{action this 'foo'}}>
<button onblur={{action this (action this 'foo') 'bar'}}>
```

@private
Expand All @@ -25,10 +29,6 @@ export default function TransformActionSyntax() {
this.syntax = null;
}

const TRANSFORMATIONS = {
action: 'action'
};

/**
@private
@method transform
Expand All @@ -39,17 +39,29 @@ TransformActionSyntax.prototype.transform = function TransformActionSyntax_trans

traverse(ast, {
ElementModifierStatement(node) {
if (TRANSFORMATIONS[node.path.original]) {
let thisPath = b.path('this');

// We have to delete the `parts` here because otherwise it will be treated
// as a property look up (i.e. `this.this`) and will result in `undefined`.
thisPath.parts = [];

return b.elementModifier(node.path, [thisPath, ...node.params], node.hash, node.loc);
if (isAction(node)) {
insertThisAsFirstParam(node, b);
}
},
MustacheStatement(node) {
if (isAction(node)) {
insertThisAsFirstParam(node, b);
}
},
SubExpression(node) {
if (isAction(node)) {
insertThisAsFirstParam(node, b);
}
}
});

return ast;
};

function isAction(node) {
return node.path.original === 'action';
}

function insertThisAsFirstParam(node, builders) {
node.params.unshift(builders.path(''));
}
2 changes: 2 additions & 0 deletions packages/ember-glimmer/lib/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
inlineUnless
} from './helpers/if-unless';

import { default as action } from './helpers/action';
import { default as get } from './helpers/get';
import { default as hash } from './helpers/hash';
import { default as loc } from './helpers/loc';
Expand All @@ -35,6 +36,7 @@ const builtInHelpers = {
concat,
if: inlineIf,
unless: inlineUnless,
action,
get,
hash,
loc,
Expand Down
127 changes: 127 additions & 0 deletions packages/ember-glimmer/lib/helpers/action.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { CachedReference } from '../utils/references';
import { NULL_REFERENCE, UNDEFINED_REFERENCE } from 'glimmer-runtime';
import EmberError from 'ember-metal/error';
import run from 'ember-metal/run_loop';
import { get } from 'ember-metal/property_get';
import { flaggedInstrument } from 'ember-metal/instrumentation';

export class ClosureActionReference extends CachedReference {
static create(args) {
// TODO: Const reference optimization.
return new ClosureActionReference(args);
}

constructor(args) {
super();

this.args = args;
this.tag = args.tag;
}

compute() {
let { named, positional } = this.args;
let positionalValues = positional.value();

let target = positionalValues[0];
let rawAction = positionalValues[1];

// The first two argument slots are reserved.
// pos[0] is the context (or `this`)
// pos[1] is the action name or function
// Anything else is an action argument.
let actionArgs = positionalValues.slice(2);

// TODO: Check if rawAction is INVOKE-able or (mut)

// on-change={{action setName}}
// element-space actions look to "controller" then target. Here we only
// look to "target".
let actionType = typeof rawAction;
let action = rawAction;

if (actionType === 'string') {
// on-change={{action 'setName'}}
let actionName = rawAction;

action = null;

if (named.has('target')) {
// on-change={{action 'setName' target=alternativeComponent}}
target = named.get('target').value();
}

if (target['actions']) {
action = target.actions[actionName];
}

if (!action) {
throw new EmberError(`An action named '${actionName}' was not found in ${target}`);
}
} else if (actionType !== 'function') {
throw new EmberError(`An action could not be made for \`${rawAction}\` in ${target}. Please confirm that you are using either a quoted action name (i.e. \`(action '${rawAction}')\`) or a function available in ${target}.`);
}

// TODO: Handle INVOKE explicitly here.

// <button on-keypress={{action (mut name) value="which"}}
// on-keypress is not even an Ember feature yet
let valuePath = named.get('value').value();

return createClosureAction(target, action, valuePath, actionArgs);
}
}

export default {
isInternalHelper: true,

toReference(args) {
let rawActionRef = args.positional.at(1);

if (rawActionRef === UNDEFINED_REFERENCE || rawActionRef === NULL_REFERENCE) {
throw new Error(`Action passed is null or undefined in (action) from ${args.positional.at(0).value()}.`);
}

return ClosureActionReference.create(args);
}
};

export function createClosureAction(target, action, valuePath, actionArgs) {
let closureAction;
let actionArgLength = actionArgs.length;

if (actionArgLength > 0) {
closureAction = function(...passedArguments) {
let args = new Array(actionArgLength + passedArguments.length);

for (let i = 0; i < actionArgLength; i++) {
args[i] = actionArgs[i];
}

for (let i = 0; i < passedArguments.length; i++) {
args[i + actionArgLength] = passedArguments[i];
}

if (valuePath && args.length > 0) {
args[0] = get(args[0], valuePath);
}

let payload = { target, args, label: 'glimmer-closure-action' };
return flaggedInstrument('interaction.ember-action', payload, () => {
return run.join(target, action, ...args);
});
};
} else {
closureAction = function(...args) {
if (valuePath && args.length > 0) {
args[0] = get(args[0], valuePath);
}

let payload = { target, args, label: 'glimmer-closure-action' };
return flaggedInstrument('interaction.ember-action', payload, () => {
return run.join(target, action, ...args);
});
};
}

return closureAction;
}
Loading