Skip to content
Closed
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
25 changes: 13 additions & 12 deletions packages/ember-glimmer/lib/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import { get } from 'ember-metal/property_get';
import { PROPERTY_DID_CHANGE } from 'ember-metal/property_events';
import { UPDATE } from './utils/references';
import { DirtyableTag } from 'glimmer-reference';
import { deprecate } from 'ember-metal/debug';
import { NAME_KEY } from 'ember-metal/mixin';
import { deprecate, assert, runInDebug } from 'ember-metal/debug';

export const DIRTY_TAG = symbol('DIRTY_TAG');
export const ARGS = symbol('ARGS');
Expand Down Expand Up @@ -70,18 +70,19 @@ const Component = CoreView.extend(
let args, reference;

if ((args = this[ARGS]) && (reference = args[key])) {
if (reference[UPDATE]) {
reference[UPDATE](get(this, key));
} else {
let value = get(this, key);

runInDebug(() => {
let name = this._debugContainerKey.split(':')[1];
let value = get(this, key);
throw new Error(strip`
Cannot set the \`${key}\` property (on component ${name}) to
\`${value}\`. The \`${key}\` property came from an immutable
binding in the template, such as {{${name} ${key}="string"}}
or {{${name} ${key}=(if theTruth "truth" "false")}}.
`);
}
assert(strip`
Cannot set the \`${key}\` property (on component ${name}) to
\`${value}\`. The \`${key}\` property came from an immutable
binding in the template, such as {{${name} ${key}="string"}}
or {{${name} ${key}=(if theTruth "truth" "false")}}.
`, reference[UPDATE]);
});

reference[UPDATE](value);
}
}
}
Expand Down
10 changes: 6 additions & 4 deletions packages/ember-glimmer/lib/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,18 @@ import {
ClassBasedHelperReference
} from './utils/references';

import { default as concat } from './helpers/concat';
import {
inlineIf,
inlineUnless
} from './helpers/if-unless';

import { default as action } from './helpers/action';
import { default as concat } from './helpers/concat';
import { default as get } from './helpers/get';
import { default as hash } from './helpers/hash';
import { default as loc } from './helpers/loc';
import { default as log } from './helpers/log';
import { default as mut } from './helpers/mut';
import { default as readonly } from './helpers/readonly';
import { default as unbound } from './helpers/unbound';
import { default as classHelper } from './helpers/-class';
Expand All @@ -40,17 +41,18 @@ const builtInComponents = {
};

const builtInHelpers = {
concat,
if: inlineIf,
unless: inlineUnless,
action,
concat,
get,
hash,
loc,
log,
mut,
'query-params': queryParams,
readonly,
unbound,
'query-params': queryParams,
unless: inlineUnless,
'-class': classHelper,
'-each-in': eachIn
};
Expand Down
31 changes: 16 additions & 15 deletions packages/ember-glimmer/lib/helpers/action.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
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';
import isNone from 'ember-metal/is_none';
import { INVOKE, ACTION } from 'ember-htmlbars/keywords/closure-action';

export class ClosureActionReference extends CachedReference {
static create(args) {
Expand All @@ -14,6 +15,7 @@ export class ClosureActionReference extends CachedReference {
constructor(args) {
super();

this[ACTION] = true;
this.args = args;
this.tag = args.tag;
}
Expand All @@ -23,6 +25,7 @@ export class ClosureActionReference extends CachedReference {
let positionalValues = positional.value();

let target = positionalValues[0];
let rawActionRef = positional.at(1);
let rawAction = positionalValues[1];

// The first two argument slots are reserved.
Expand All @@ -31,15 +34,18 @@ export class ClosureActionReference extends CachedReference {
// 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') {
if (isNone(rawAction)) {
throw new EmberError(`Action passed is null or undefined in (action) from ${target}.`);
} else if (rawActionRef[INVOKE]) {
target = rawActionRef;
action = rawActionRef[INVOKE];
} else if (actionType === 'string') {
// on-change={{action 'setName'}}
let actionName = rawAction;

Expand All @@ -57,14 +63,15 @@ export class ClosureActionReference extends CachedReference {
if (!action) {
throw new EmberError(`An action named '${actionName}' was not found in ${target}`);
}
} else if (action && typeof action[INVOKE] === 'function') {
target = action;
action = action[INVOKE];
} 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: Is there a better way of doing this?
let rawActionLabel = rawActionRef._propertyKey || rawAction;
throw new EmberError(`An action could not be made for \`${rawActionLabel}\` in ${target}. Please confirm that you are using either a quoted action name (i.e. \`(action '${rawActionLabel}')\`) 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);
Expand All @@ -75,12 +82,6 @@ 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);
}
};
Expand Down
39 changes: 39 additions & 0 deletions packages/ember-glimmer/lib/helpers/mut.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { assert } from 'ember-metal/debug';
import { InternalHelperReference, UPDATE } from '../utils/references';
import { INVOKE } from 'ember-htmlbars/keywords/closure-action';
import { MUTABLE_REFERENCE } from 'ember-htmlbars/keywords/mut';
import { isConst } from 'glimmer-reference';

export default {
isInternalHelper: true,
toReference(args) {
let ref = args.positional.at(0);

assert('You can only pass a path to mut', !isConst(ref));

if (!ref[MUTABLE_REFERENCE]) {
return new MutHelperReference(mut, args);
}
return ref;
}
};

function mut({ positional }) {
return positional.at(0).value();
}

class MutHelperReference extends InternalHelperReference {
constructor() {
super(...arguments);
this[MUTABLE_REFERENCE] = true;
}

[UPDATE](val) {
let argRef = this.args.positional.at(0);
argRef[UPDATE](val);
}

[INVOKE](val) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like an ember-internal thing and as far as I can tell no internal package really calls INVOKE, should we remove this? It's a symbol anyway so it can't be called externally.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, it is used (as private API) by ember-concurrency and ember-route-action-helper addons.

this[UPDATE](val);
}
}
47 changes: 42 additions & 5 deletions packages/ember-glimmer/lib/helpers/readonly.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,44 @@
import { helper } from '../helper';
import { CachedReference, UnboundReference, UPDATE, READONLY } from '../utils/references';
import { CURRENT_TAG, DirtyableTag, combine, isConst } from 'glimmer-reference';

function readonly(args) {
return args[0];
}
export default {
isInternalHelper: true,
toReference(args) {
return ReadOnlyReference.create(args.positional.at(0));
}
};

export class ReadOnlyReference extends CachedReference {
static create(arg) {
if (isConst(arg)) {
return new UnboundReference(arg);
} else {
return new ReadOnlyReference(arg);
}
}

constructor(arg) {
super();
this.arg = arg;
this.unboundTag = new DirtyableTag(CURRENT_TAG.value());
this.tag = combine([arg.tag, this.unboundTag]);
this.unboundValue = undefined;
this[READONLY] = true;
}

export default helper(readonly);
[UPDATE](val) {
this.unboundValue = val;
this.unboundTag.dirty();
}

compute() {
let { tag, unboundTag, unboundValue, arg } = this;
let unboundTagValue = unboundTag.value();

if (tag.validate(unboundTagValue) && !arg.tag.validate(unboundTagValue)) {
return unboundValue;
} else {
return arg.value();
}
}
}
23 changes: 22 additions & 1 deletion packages/ember-glimmer/lib/utils/process-args.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { CONSTANT_TAG } from 'glimmer-reference';
import { assert } from 'ember-metal/debug';
import EmptyObject from 'ember-metal/empty_object';
import { ARGS } from '../component';
import { MUTABLE_CELL } from 'ember-views/compat/attrs-proxy';
import { MUTABLE_REFERENCE } from 'ember-htmlbars/keywords/mut';
import { ACTION } from 'ember-htmlbars/keywords/closure-action';
import { UPDATE } from 'ember-glimmer/utils/references';

export default function processArgs(args, positionalParamsDefinition) {
if (!positionalParamsDefinition || positionalParamsDefinition.length === 0 || args.positional.length === 0) {
Expand Down Expand Up @@ -48,15 +52,32 @@ class SimpleArgs {
for (let i = 0, l = keys.length; i < l; i++) {
let name = keys[i];
let value = attrs[name];
let ref = namedArgs.get(name);

args[name] = namedArgs.get(name);
if (ref[MUTABLE_REFERENCE] && !ref[ACTION]) {
attrs[name] = new MutableCell(ref, value);
}

args[name] = ref;
props[name] = value;
}

return { attrs, props };
}
}

class MutableCell {
constructor(ref, value) {
this._ref = ref;
this[MUTABLE_CELL] = true;
this.value = value;
}

update(val) {
this._ref[UPDATE](val);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't the value need to be reflected on both sides?

Copy link
Contributor Author

@Joelkang Joelkang May 25, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I added tests to verify the value of component.attrs.myAttr.value and component.get('myAttr')and they all seem to be passing after the update() without any code changes. Is that what you meant?

}
}

class RestArgs {
static create(args, restArgName) {
assert(`You cannot specify positional parameters and the hash argument \`${restArgName}\`.`, !args.named.has(restArgName));
Expand Down
17 changes: 17 additions & 0 deletions packages/ember-glimmer/lib/utils/references.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import { dasherize } from 'ember-runtime/system/string';
import { meta as metaFor } from 'ember-metal/meta';
import { watchKey } from 'ember-metal/watch_key';
import isEnabled from 'ember-metal/features';
import { ARGS } from '../component';
import { MUTABLE_REFERENCE } from 'ember-htmlbars/keywords/mut';

export const UPDATE = symbol('UPDATE');
export const READONLY = symbol('READONLY');

// FIXME: fix tests that uses a "fake" proxy (i.e. a POJOs that "happen" to
// have an `isTruthy` property on them). This is not actually supported –
Expand Down Expand Up @@ -72,6 +76,13 @@ export class CachedReference extends EmberPathReference {
// @implements PathReference
export class RootReference extends ConstReference {
get(propertyKey) {
let args, ref;

if ((args = this.value()[ARGS]) &&
(ref = args[propertyKey]) &&
(ref[MUTABLE_REFERENCE] || ref[READONLY])) {
return ref;
}
return new PropertyReference(this, propertyKey);
}
}
Expand Down Expand Up @@ -413,5 +424,11 @@ export class UnboundReference {
return new UnboundReference(this, key);
}

[UPDATE](val) {
let { key, sourceRef } = this;
let sourceVal = sourceRef.value();
this.cache = key ? set(sourceVal, key, val) : val;
}

destroy() {}
}
Loading