ember-legacy-class-transform
Version:
The default blueprint for ember-cli addons.
265 lines (239 loc) • 7.42 kB
JavaScript
import { DEBUG } from '@glimmer/env';
import Ember from 'ember';
import macroComputed from 'ember-macro-helpers/computed';
import extractValue from '../utils/extract-value';
import {
decorator,
decoratorWithParams
} from '../utils/decorator-wrappers';
import { decoratorWithRequiredParams } from '../utils/decorator-macros';
import { assert } from '@ember/debug';
import {
HAS_UNDERSCORE_ACTIONS,
SUPPORTS_NEW_COMPUTED
} from 'ember-compatibility-helpers';
const { computed: emberComputed } = Ember;
/**
* Decorator that turns the target function into an Action
*
* Adds an `actions` object to the target object and creates a passthrough
* function that calls the original. This means the function still exists
* on the original object, and can be used directly.
*
* ```js
* import Component from '@ember/component';
* import { action } from 'ember-decorators/object';
*
* export default class ActionDemoComponent extends Component {
* @action
* foo() {
* // do something
* }
* }
* ```
*
* ```hbs
* <button onclick={{action "foo"}}>Execute foo action</button>
* ```
*
* @function
*/
export const action = decorator(function(target, key, desc) {
let value = extractValue(desc);
assert('The @action decorator must be applied to functions', typeof value === 'function');
// We must collapse the superclass prototype to make sure that the `actions`
// object will exist. Since collapsing doesn't generally happen until a class is
// instantiated, we have to do it manually.
let superClass = Object.getPrototypeOf(target.constructor);
if (superClass.hasOwnProperty('proto') && typeof superClass.proto === 'function') {
superClass.proto();
}
if (HAS_UNDERSCORE_ACTIONS) {
if (!target.hasOwnProperty('_actions')) {
let parentActions = target._actions;
target._actions = parentActions ? Object.create(parentActions) : {};
}
target._actions[key] = value;
} else {
if (!target.hasOwnProperty('actions')) {
let parentActions = target.actions;
target.actions = parentActions ? Object.create(parentActions) : {};
}
target.actions[key] = value;
}
return value;
});
/**
* Decorator that turns a function into a computed property.
*
* #### With Dependent Keys
*
* The values of the dependent properties are passed as arguments to the
* function. You can also use
* [property brace expansion](https://www.emberjs.com/blog/2014/02/12/ember-1-4-0-and-ember-1-5-0-beta-released.html#toc_property-brace-expansion).
*
* ```javascript
* import EmberObject from '@ember/object';
* import computed from 'ember-decorators/object';
*
* export default class User extends EmberObject {
* someKey = 'foo';
* otherKey = 'bar';
*
* person = {
* firstName: 'John',
* lastName: 'Smith'
* };
*
* @computed('someKey', 'otherKey')
* foo(someKey, otherKey) {
* return `${someKey} - ${otherKey}`; // => 'foo - bar'
* }
*
* @computed('person.{firstName,lastName}')
* fullName(firstName, lastName) {
* return `${firstName} ${lastName}`; // => 'John Smith'
* }
* }
* ```
*
* #### Without Dependent Keys
*
* Computed properties without dependent keys are cached for the whole life span
* of the object. You can only force a recomputation by calling
* [`notifyPropertyChange`](https://www.emberjs.com/api/ember/2.14/classes/Ember.Observable/methods/notifyPropertyChange?anchor=notifyPropertyChange)
* on the computed property.
*
* ```javascript
* import EmberObject from '@ember/object';
* import computed from 'ember-decorators/object';
*
* export default class FooBar extends EmberObject {
* @computed
* foo() {
* // calculate stuff
* return stuff;
* }
* }
* ```
*
* #### Getter and Setter
*
* ```javascript
* import Component from '@ember/component';
* import { setProperties } from ''@ember/object';
* import computed from 'ember-decorators/object';
*
* export default class UserProfileComponent extends Component {
* first = 'John';
* last = 'Smith';
*
* @computed('first', 'last')
* name = {
* get(first, last) {
* return `${first} ${last}`; // => 'John Smith'
* },
*
* set(value, first, last) {
* if (typeof value !== 'string' || !value.test(/^[a-z]+ [a-z]+$/i)) {
* throw new TypeError('Invalid name');
* }
*
* const [first, last] = value.split(' ');
* setProperties(this, { first, last });
*
* return value;
* }
* };
* }
* ```
*
* @function
* @param {...String} propertyNames - List of property keys this computed is dependent on
*/
export const computed = decoratorWithParams(function(target, key, desc, params) {
assert(`ES6 property getters/setters only need to be decorated once, '${key}' was decorated on both the getter and the setter`, !(desc.value instanceof Ember.ComputedProperty));
if (desc.writable === undefined) {
let { get, set } = desc;
assert(`Using @computed for only a setter does not make sense. Add a getter for '${key}' as well or remove the @computed decorator.`, typeof get === 'function');
// Unset the getter and setter so the descriptor just has a plain value
desc.get = undefined;
desc.set = undefined;
let setter;
if (typeof set === 'function') {
setter = function(key, value) {
let ret = set.call(this, value);
return typeof ret === 'undefined' ? get.call(this) : ret;
};
} else if (DEBUG) {
setter = function() {
assert(`You must provide a setter in order to set '${key}' as a computed property.`, false);
};
}
// Use a standard ember computed since getter/setter arrity is restricted,
// meaning ember-macro-helpers doesn't provide any benefit
if (SUPPORTS_NEW_COMPUTED) {
return emberComputed(...params, { get, set: setter });
} else {
let callback;
if (typeof setter === 'function') {
callback = function (key, value) {
if (arguments.length > 1) {
return setter.call(this, key, value);
}
return get.call(this);
}
} else {
callback = get;
}
return emberComputed(...params, callback);
}
} else {
return macroComputed(...params, extractValue(desc));
}
});
/**
* Decorator that wraps [Ember.observer](https://emberjs.com/api/#method_observer)
*
* Triggers the target function when the dependent properties have changed
*
* ```javascript
* import Component from '@ember/component';
* import { observes } from 'ember-decorators/object';
*
* export default class extends Component {
* @observes('foo')
* bar() {
* //...
* }
* }
* ```
*
* @function
* @param {...String} eventNames - Names of the events that trigger the function
*/
export const observes = decoratorWithRequiredParams(Ember.observer);
/**
* Decorator that modifies a computed property to be read only.
*
* Usage:
*
* ```javascript
* import Component from '@ember/component';
* import { computed, readOnly } from 'ember-decorators/object';
*
* export default class extends Component {
* @readOnly
* @computed('first', 'last')
* name(first, last) {
* return `${first} ${last}`;
* }
* }
* ```
*
* @function
*/
export const readOnly = decorator(function(target, name, desc) {
var value = extractValue(desc);
return value.readOnly();
});