/
property_set.js
148 lines (125 loc) · 4.37 KB
/
property_set.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
import { toString } from 'ember-utils';
import { assert, Error as EmberError } from 'ember-debug';
import { _getPath as getPath } from './property_get';
import {
propertyWillChange,
propertyDidChange
} from './property_events';
import {
isPath
} from './path_cache';
import {
isDescriptor,
isDescriptorTrap,
peekMeta,
DESCRIPTOR,
descriptorFor
} from './meta';
import { DESCRIPTOR_TRAP, EMBER_METAL_ES5_GETTERS, MANDATORY_SETTER } from 'ember/features';
/**
@module @ember/object
*/
/**
Sets the value of a property on an object, respecting computed properties
and notifying observers and other listeners of the change. If the
property is not defined but the object implements the `setUnknownProperty`
method then that will be invoked as well.
```javascript
import { set } from '@ember/object';
set(obj, "name", value);
```
@method set
@static
@for @ember/object
@param {Object} obj The object to modify.
@param {String} keyName The property key to set
@param {Object} value The value to set
@return {Object} the passed value.
@public
*/
export function set(obj, keyName, value, tolerant) {
assert(
`Set must be called with three or four arguments; an object, a property key, a value and tolerant true/false`,
arguments.length === 3 || arguments.length === 4
);
assert(`Cannot call set with '${keyName}' on an undefined object.`, obj && typeof obj === 'object' || typeof obj === 'function');
assert(`The key provided to set must be a string or number, you passed ${keyName}`, typeof keyName === 'string' || (typeof keyName === 'number' && !isNaN(keyName)));
assert(`'this' in paths is not supported`, typeof keyName !== 'string' || keyName.lastIndexOf('this.', 0) !== 0);
assert(`calling set on destroyed object: ${toString(obj)}.${keyName} = ${toString(value)}`, !obj.isDestroyed);
if (isPath(keyName)) {
return setPath(obj, keyName, value, tolerant);
}
if (EMBER_METAL_ES5_GETTERS) {
let possibleDesc = descriptorFor(obj, keyName);
if (possibleDesc !== undefined) { /* computed property */
possibleDesc.set(obj, keyName, value);
return value;
}
}
let currentValue = obj[keyName];
if (DESCRIPTOR_TRAP && isDescriptorTrap(currentValue)) {
currentValue = currentValue[DESCRIPTOR];
}
if (isDescriptor(currentValue)) { /* computed property */
currentValue.set(obj, keyName, value);
} else if (currentValue === undefined && 'object' === typeof obj && !(keyName in obj) &&
typeof obj.setUnknownProperty === 'function') { /* unknown property */
obj.setUnknownProperty(keyName, value);
} else if (currentValue === value) { /* no change */
} else {
let meta = peekMeta(obj);
propertyWillChange(obj, keyName, meta);
if (MANDATORY_SETTER) {
setWithMandatorySetter(meta, obj, keyName, value);
} else {
obj[keyName] = value;
}
propertyDidChange(obj, keyName, meta);
}
return value;
}
if (MANDATORY_SETTER) {
var setWithMandatorySetter = (meta, obj, keyName, value) => {
if (meta !== undefined && meta.peekWatching(keyName) > 0) {
makeEnumerable(obj, keyName);
meta.writeValue(obj, keyName, value);
} else {
obj[keyName] = value;
}
};
var makeEnumerable = (obj, key) => {
let desc = Object.getOwnPropertyDescriptor(obj, key);
if (desc && desc.set && desc.set.isMandatorySetter) {
desc.enumerable = true;
Object.defineProperty(obj, key, desc);
}
};
}
function setPath(root, path, value, tolerant) {
let parts = path.split('.');
let keyName = parts.pop();
assert('Property set failed: You passed an empty path', keyName.trim().length > 0);
let newPath = parts.join('.');
let newRoot = getPath(root, newPath);
if (newRoot) {
return set(newRoot, keyName, value);
} else if (!tolerant) {
throw new EmberError(`Property set failed: object in path "${newPath}" could not be found or was destroyed.`);
}
}
/**
Error-tolerant form of `set`. Will not blow up if any part of the
chain is `undefined`, `null`, or destroyed.
This is primarily used when syncing bindings, which may try to update after
an object has been destroyed.
@method trySet
@static
@for @ember/object
@param {Object} root The object to modify.
@param {String} path The property path to set
@param {Object} value The value to set
@public
*/
export function trySet(root, path, value) {
return set(root, path, value, true);
}