1 | "use strict";
|
2 | var ObservationRecorder = require('can-observation-recorder');
|
3 | var dev = require('can-log/dev/dev');
|
4 | var canSymbol = require("can-symbol");
|
5 | var canReflect = require("can-reflect");
|
6 | var canReflectPromise = require("can-reflect-promise");
|
7 |
|
8 | var getValueSymbol = canSymbol.for("can.getValue");
|
9 | var setValueSymbol = canSymbol.for("can.setValue");
|
10 |
|
11 | var isValueLikeSymbol = canSymbol.for("can.isValueLike");
|
12 | var peek = ObservationRecorder.ignore(canReflect.getKeyValue.bind(canReflect));
|
13 | var observeReader;
|
14 | var isPromiseLike = ObservationRecorder.ignore(function isPromiseLike(value){
|
15 | return typeof value === "object" && value && typeof value.then === "function";
|
16 | });
|
17 |
|
18 | var bindName = Function.prototype.bind;
|
19 |
|
20 | if (process.env.NODE_ENV !== 'production') {
|
21 | bindName = function(source){
|
22 | var fn = Function.prototype.bind.call(this, source);
|
23 | Object.defineProperty(fn, "name", {
|
24 | value: canReflect.getName(source) + "."+canReflect.getName(this)
|
25 | });
|
26 | return fn;
|
27 | };
|
28 | }
|
29 |
|
30 |
|
31 | var isAt = function(index, reads) {
|
32 | var prevRead = reads[index-1];
|
33 | return prevRead && prevRead.at;
|
34 | };
|
35 |
|
36 | var readValue = function(value, index, reads, options, state, prev){
|
37 |
|
38 | var usedValueReader;
|
39 | do {
|
40 |
|
41 | usedValueReader = false;
|
42 | for(var i =0, len = observeReader.valueReaders.length; i < len; i++){
|
43 | if( observeReader.valueReaders[i].test(value, index, reads, options) ) {
|
44 | value = observeReader.valueReaders[i].read(value, index, reads, options, state, prev);
|
45 |
|
46 | }
|
47 | }
|
48 | } while(usedValueReader);
|
49 |
|
50 | return value;
|
51 | };
|
52 |
|
53 | var specialRead = {index: true, key: true, event: true, element: true, viewModel: true};
|
54 |
|
55 | var checkForObservableAndNotify = function(options, state, getObserves, value, index){
|
56 | if(options.foundObservable && !state.foundObservable) {
|
57 | if(ObservationRecorder.trapsCount()) {
|
58 | ObservationRecorder.addMany( getObserves() );
|
59 | options.foundObservable(value, index);
|
60 | state.foundObservable = true;
|
61 | }
|
62 | }
|
63 | };
|
64 |
|
65 | var objHasKeyAtIndex = function(obj, reads, index) {
|
66 | return !!(
|
67 | reads && reads.length &&
|
68 | canReflect.hasKey(obj, reads[index].key)
|
69 | );
|
70 | };
|
71 |
|
72 | observeReader = {
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 | read: function (parent, reads, options) {
|
87 | options = options || {};
|
88 | var state = {
|
89 | foundObservable: false
|
90 | };
|
91 | var getObserves;
|
92 | if(options.foundObservable) {
|
93 | getObserves = ObservationRecorder.trap();
|
94 | }
|
95 |
|
96 |
|
97 | var cur = readValue(parent, 0, reads, options, state),
|
98 | type,
|
99 |
|
100 | prev,
|
101 |
|
102 | readLength = reads.length,
|
103 | i = 0,
|
104 | last,
|
105 | parentHasKey;
|
106 |
|
107 | checkForObservableAndNotify(options, state, getObserves, parent, 0);
|
108 |
|
109 | while( i < readLength ) {
|
110 | prev = cur;
|
111 |
|
112 | for(var r=0, readersLength = observeReader.propertyReaders.length; r < readersLength; r++) {
|
113 | var reader = observeReader.propertyReaders[r];
|
114 | if(reader.test(cur)) {
|
115 | cur = reader.read(cur, reads[i], i, options, state);
|
116 | break;
|
117 | }
|
118 | }
|
119 | checkForObservableAndNotify(options, state, getObserves, prev, i);
|
120 | last = cur;
|
121 | i = i+1;
|
122 |
|
123 | cur = readValue(cur, i, reads, options, state, prev);
|
124 |
|
125 | checkForObservableAndNotify(options, state, getObserves, prev, i-1);
|
126 |
|
127 | type = typeof cur;
|
128 |
|
129 | if (i < reads.length && (cur === null || cur === undefined )) {
|
130 | parentHasKey = objHasKeyAtIndex(prev, reads, i - 1);
|
131 | if (options.earlyExit && !parentHasKey) {
|
132 | options.earlyExit(prev, i - 1, cur);
|
133 | }
|
134 |
|
135 | return {
|
136 | value: undefined,
|
137 | parent: prev,
|
138 | parentHasKey: parentHasKey,
|
139 | foundLastParent: false
|
140 | };
|
141 | }
|
142 |
|
143 | }
|
144 |
|
145 | parentHasKey = objHasKeyAtIndex(prev, reads, reads.length - 1);
|
146 |
|
147 | if (cur === undefined && !parentHasKey) {
|
148 | if (options.earlyExit) {
|
149 | options.earlyExit(prev, i - 1);
|
150 | }
|
151 | }
|
152 | return {
|
153 | value: cur,
|
154 | parent: prev,
|
155 | parentHasKey: parentHasKey,
|
156 | foundLastParent: true
|
157 | };
|
158 | },
|
159 | get: function(parent, reads, options){
|
160 | return observeReader.read(parent, observeReader.reads(reads), options || {}).value;
|
161 | },
|
162 | valueReadersMap: {},
|
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 | valueReaders: [
|
169 | {
|
170 | name: "function",
|
171 |
|
172 | test: function(value){
|
173 | return value && canReflect.isFunctionLike(value) && !canReflect.isConstructorLike(value);
|
174 | },
|
175 | read: function(value, i, reads, options, state, prev){
|
176 | if(options.callMethodsOnObservables && canReflect.isObservableLike(prev) && canReflect.isMapLike(prev)) {
|
177 | dev.warn("can-stache-key: read() called with `callMethodsOnObservables: true`.");
|
178 |
|
179 | return value.apply(prev, options.args || []);
|
180 | }
|
181 |
|
182 | return options.proxyMethods !== false ? bindName.call(value, prev) : value;
|
183 | }
|
184 | },
|
185 | {
|
186 | name: "isValueLike",
|
187 |
|
188 | test: function(value, i, reads, options) {
|
189 | return value && value[getValueSymbol] && value[isValueLikeSymbol] !== false && (options.foundAt || !isAt(i, reads) );
|
190 | },
|
191 | read: function(value, i, reads, options){
|
192 | if(options.readCompute === false && i === reads.length ) {
|
193 | return value;
|
194 | }
|
195 | return canReflect.getValue(value);
|
196 | },
|
197 | write: function(base, newVal){
|
198 | if(base[setValueSymbol]) {
|
199 | base[setValueSymbol](newVal);
|
200 | } else if(base.set) {
|
201 | base.set(newVal);
|
202 | } else {
|
203 | base(newVal);
|
204 | }
|
205 | }
|
206 | }],
|
207 | propertyReadersMap: {},
|
208 |
|
209 | propertyReaders: [
|
210 | {
|
211 | name: "map",
|
212 | test: function(value){
|
213 |
|
214 |
|
215 | if(canReflect.isPromise(value) ||
|
216 | isPromiseLike(value)) {
|
217 | canReflectPromise(value);
|
218 | }
|
219 |
|
220 | return canReflect.isObservableLike(value) && canReflect.isMapLike(value);
|
221 | },
|
222 | read: function(value, prop){
|
223 | var res = canReflect.getKeyValue(value, prop.key);
|
224 | if(res !== undefined) {
|
225 | return res;
|
226 | } else {
|
227 | return value[prop.key];
|
228 | }
|
229 | },
|
230 | write: canReflect.setKeyValue
|
231 | },
|
232 |
|
233 |
|
234 | {
|
235 | name: "object",
|
236 |
|
237 | test: function(){return true;},
|
238 | read: function(value, prop, i, options){
|
239 | if(value == null) {
|
240 | return undefined;
|
241 | } else {
|
242 | if(typeof value === "object") {
|
243 | if(prop.key in value) {
|
244 | return value[prop.key];
|
245 | }
|
246 |
|
247 |
|
248 | if (process.env.NODE_ENV !== 'production') {
|
249 | if( prop.at && specialRead[prop.key] && ( ("@"+prop.key) in value)) {
|
250 | options.foundAt = true;
|
251 | dev.warn("Use %"+prop.key+" in place of @"+prop.key+".");
|
252 | return undefined;
|
253 | }
|
254 | }
|
255 |
|
256 | } else {
|
257 | return value[prop.key];
|
258 | }
|
259 | }
|
260 | },
|
261 | write: function(base, prop, newVal){
|
262 | var propValue = base[prop];
|
263 |
|
264 | if(newVal != null && typeof newVal === "object" && canReflect.isMapLike(propValue) ) {
|
265 | dev.warn("can-stache-key: Merging data into \"" + prop + "\" because its parent is non-observable");
|
266 | canReflect.update(propValue, newVal);
|
267 | } else if(propValue != null && propValue[setValueSymbol] !== undefined){
|
268 | canReflect.setValue(propValue, newVal);
|
269 | } else {
|
270 | base[prop] = newVal;
|
271 | }
|
272 | }
|
273 | }
|
274 | ],
|
275 | reads: function(keyArg) {
|
276 | var key = ""+keyArg;
|
277 | var keys = [];
|
278 | var last = 0;
|
279 | var at = false;
|
280 | if( key.charAt(0) === "@" ) {
|
281 | last = 1;
|
282 | at = true;
|
283 | }
|
284 | var keyToAdd = "";
|
285 | for(var i = last; i < key.length; i++) {
|
286 | var character = key.charAt(i);
|
287 | if(character === "." || character === "@") {
|
288 | if( key.charAt(i -1) !== "\\" ) {
|
289 | keys.push({
|
290 | key: keyToAdd,
|
291 | at: at
|
292 | });
|
293 | at = character === "@";
|
294 | keyToAdd = "";
|
295 | } else {
|
296 | keyToAdd = keyToAdd.substr(0,keyToAdd.length - 1) + ".";
|
297 | }
|
298 | } else {
|
299 | keyToAdd += character;
|
300 | }
|
301 | }
|
302 | keys.push({
|
303 | key: keyToAdd,
|
304 | at: at
|
305 | });
|
306 |
|
307 | return keys;
|
308 | },
|
309 |
|
310 | write: function(parent, key, value, options) {
|
311 | var keys = typeof key === "string" ? observeReader.reads(key) : key;
|
312 | var last;
|
313 |
|
314 | options = options || {};
|
315 | if(keys.length > 1) {
|
316 | last = keys.pop();
|
317 | parent = observeReader.read(parent, keys, options).value;
|
318 | keys.push(last);
|
319 | } else {
|
320 | last = keys[0];
|
321 | }
|
322 | if(!parent) {
|
323 | return;
|
324 | }
|
325 | var keyValue = peek(parent, last.key);
|
326 |
|
327 |
|
328 |
|
329 | if( observeReader.valueReadersMap.isValueLike.test(keyValue, keys.length - 1, keys, options) ) {
|
330 | observeReader.valueReadersMap.isValueLike.write(keyValue, value, options);
|
331 | } else {
|
332 | if(observeReader.valueReadersMap.isValueLike.test(parent, keys.length - 1, keys, options) ) {
|
333 | parent = parent[getValueSymbol]();
|
334 | }
|
335 | if(observeReader.propertyReadersMap.map.test(parent)) {
|
336 | observeReader.propertyReadersMap.map.write(parent, last.key, value, options);
|
337 | }
|
338 | else if(observeReader.propertyReadersMap.object.test(parent)) {
|
339 | observeReader.propertyReadersMap.object.write(parent, last.key, value, options);
|
340 | if(options.observation) {
|
341 | options.observation.update();
|
342 | }
|
343 | }
|
344 | }
|
345 | }
|
346 | };
|
347 | observeReader.propertyReaders.forEach(function(reader){
|
348 | observeReader.propertyReadersMap[reader.name] = reader;
|
349 | });
|
350 | observeReader.valueReaders.forEach(function(reader){
|
351 | observeReader.valueReadersMap[reader.name] = reader;
|
352 | });
|
353 | observeReader.set = observeReader.write;
|
354 |
|
355 | module.exports = observeReader;
|