Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(162)

Side by Side Diff: sky/framework/sky-element/sky-binder.sky

Issue 951673003: Remove outdated examples and framework pieces (Closed) Base URL: git@github.com:domokit/mojo.git@master
Patch Set: Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « sky/framework/sky-element/observe.sky ('k') | sky/framework/sky-element/sky-element.sky » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 <!--
2 // Copyright 2014 The Chromium Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 -->
6 <import src="observe.sky" as="observe" />
7 <import src="element-registry.sky" as="registry" />
8
9 <script>
10 var stagingDocument = new Document();
11
12 class TemplateInstance {
13 constructor() {
14 this.bindings = [];
15 this.terminator = null;
16 this.fragment = stagingDocument.createDocumentFragment();
17 Object.preventExtensions(this);
18 }
19 close() {
20 var bindings = this.bindings;
21 for (var i = 0; i < bindings.length; i++) {
22 bindings[i].close();
23 }
24 }
25 }
26
27 var emptyInstance = new TemplateInstance();
28 var directiveCache = new WeakMap();
29
30 function createInstance(template, model) {
31 var content = template.content;
32 if (!content.firstChild)
33 return emptyInstance;
34
35 var directives = directiveCache.get(content);
36 if (!directives) {
37 directives = new NodeDirectives(content);
38 directiveCache.set(content, directives);
39 }
40
41 var instance = new TemplateInstance();
42
43 var length = directives.children.length;
44 for (var i = 0; i < length; ++i) {
45 var clone = directives.children[i].createBoundClone(instance.fragment,
46 model, instance.bindings);
47
48 // The terminator of the instance is the clone of the last child of the
49 // content. If the last child is an active template, it may produce
50 // instances as a result of production, so simply collecting the last
51 // child of the instance after it has finished producing may be wrong.
52 if (i == length - 1)
53 instance.terminator = clone;
54 }
55
56 return instance;
57 }
58
59 function sanitizeValue(value) {
60 return value == null ? '' : value;
61 }
62
63 function updateText(node, value) {
64 node.data = sanitizeValue(value);
65 }
66
67 function updateAttribute(element, name, value) {
68 element.setAttribute(name, sanitizeValue(value));
69 }
70
71 class BindingExpression {
72 constructor(prefix, path) {
73 this.prefix = prefix;
74 this.path = observe.Path.get(path);
75 Object.preventExtensions(this);
76 }
77 }
78
79 class PropertyDirective {
80 constructor(name) {
81 this.name = name;
82 this.expressions = [];
83 this.suffix = "";
84 Object.preventExtensions(this);
85 }
86 createObserver(model) {
87 var expressions = this.expressions;
88 var suffix = this.suffix;
89
90 if (expressions.length == 1 && expressions[0].prefix == "" && suffix == "")
91 return new observe.PathObserver(model, expressions[0].path);
92
93 var observer = new observe.CompoundObserver();
94
95 for (var i = 0; i < expressions.length; ++i)
96 observer.addPath(model, expressions[i].path);
97
98 return new observe.ObserverTransform(observer, function(values) {
99 var buffer = "";
100 for (var i = 0; i < values.length; ++i) {
101 buffer += expressions[i].prefix;
102 buffer += values[i];
103 }
104 buffer += suffix;
105 return buffer;
106 });
107 }
108 bindProperty(node, model) {
109 var name = this.name;
110 var observable = this.createObserver(model);
111 if (node instanceof Text) {
112 updateText(node, observable.open(function(value) {
113 return updateText(node, value);
114 }));
115 } else if (name == 'style' || name == 'class') {
116 updateAttribute(node, name, observable.open(function(value) {
117 updateAttribute(node, name, value);
118 }));
119 } else {
120 node[name] = observable.open(function(value) {
121 node[name] = value;
122 });
123 }
124 if (typeof node.addPropertyBinding == 'function')
125 node.addPropertyBinding(this.name, observable);
126 return observable;
127 }
128 }
129
130 function parsePropertyDirective(value, property) {
131 if (!value || !value.length)
132 return;
133
134 var result;
135 var offset = 0;
136 var firstIndex = 0;
137 var lastIndex = 0;
138
139 while (offset < value.length) {
140 firstIndex = value.indexOf('{{', offset);
141 if (firstIndex == -1)
142 break;
143 lastIndex = value.indexOf('}}', firstIndex + 2);
144 if (lastIndex == -1)
145 lastIndex = value.length;
146 var prefix = value.substring(offset, firstIndex);
147 var path = value.substring(firstIndex + 2, lastIndex);
148 offset = lastIndex + 2;
149 if (!result)
150 result = new PropertyDirective(property);
151 result.expressions.push(new BindingExpression(prefix, path));
152 }
153
154 if (result && offset < value.length)
155 result.suffix = value.substring(offset);
156
157 return result;
158 }
159
160 function parseAttributeDirectives(element, directives) {
161 var attributes = element.getAttributes();
162 var tagName = element.tagName;
163
164 for (var i = 0; i < attributes.length; i++) {
165 var attr = attributes[i];
166 var name = attr.name;
167 var value = attr.value;
168
169 if (name.startsWith('on-')) {
170 directives.eventHandlers.push(name.substring(3));
171 continue;
172 }
173
174 if (!registry.checkAttribute(tagName, name)) {
175 console.error('Element "'+ tagName +
176 '" has unknown attribute "' + name + '".');
177 }
178
179 var property = parsePropertyDirective(value, name);
180 if (property)
181 directives.properties.push(property);
182 }
183 }
184
185 function createCloneSource(element, properties) {
186 if (!properties.length)
187 return element;
188
189 // Leave attributes alone on template so you can see the if/repeat statements
190 // in the inspector.
191 if (element instanceof HTMLTemplateElement)
192 return element;
193
194 var result = element.cloneNode(false);
195
196 for (var i = 0; i < properties.length; ++i) {
197 result.removeAttribute(properties[i].name);
198 }
199
200 return result;
201 }
202
203 function eventHandlerCallback(event) {
204 var element = event.currentTarget;
205 var method = element.getAttribute('on-' + event.type);
206 var scope = element.ownerScope;
207 var host = scope.host;
208 var handler = host && host[method];
209 if (handler instanceof Function)
210 return handler.call(host, event);
211 }
212
213 class NodeDirectives {
214 constructor(node) {
215 this.eventHandlers = [];
216 this.children = [];
217 this.properties = [];
218 this.node = node;
219 this.cloneSourceNode = node;
220 Object.preventExtensions(this);
221
222 if (node instanceof Element) {
223 parseAttributeDirectives(node, this);
224 this.cloneSourceNode = createCloneSource(node, this.properties);
225 } else if (node instanceof Text) {
226 var property = parsePropertyDirective(node.data, 'textContent');
227 if (property)
228 this.properties.push(property);
229 }
230
231 for (var child = node.firstChild; child; child = child.nextSibling) {
232 this.children.push(new NodeDirectives(child));
233 }
234 }
235 findProperty(name) {
236 for (var i = 0; i < this.properties.length; ++i) {
237 if (this.properties[i].name === name)
238 return this.properties[i];
239 }
240 return null;
241 }
242 createBoundClone(parent, model, bindings) {
243 // TODO(esprehn): In sky instead of needing to use a staging docuemnt per
244 // custom element registry we're going to need to use the current module's
245 // registry.
246 var clone = stagingDocument.importNode(this.cloneSourceNode, false);
247
248 for (var i = 0; i < this.eventHandlers.length; ++i) {
249 clone.addEventListener(this.eventHandlers[i], eventHandlerCallback);
250 }
251
252 for (var i = 0; i < this.properties.length; ++i) {
253 bindings.push(this.properties[i].bindProperty(clone, model));
254 }
255
256 parent.appendChild(clone);
257
258 for (var i = 0; i < this.children.length; ++i) {
259 this.children[i].createBoundClone(clone, model, bindings);
260 }
261
262 if (clone instanceof HTMLTemplateElement) {
263 var iterator = new TemplateIterator(clone);
264 iterator.updateDependencies(this, model);
265 bindings.push(iterator);
266 }
267
268 return clone;
269 }
270 }
271
272 var iterators = new WeakMap();
273
274 class TemplateIterator {
275 constructor(element) {
276 this.closed = false;
277 this.template = element;
278 this.contentTemplate = null;
279 this.instances = [];
280 this.hasRepeat = false;
281 this.ifObserver = null;
282 this.valueObserver = null;
283 this.iteratedValue = [];
284 this.presentValue = null;
285 this.arrayObserver = null;
286 Object.preventExtensions(this);
287 iterators.set(element, this);
288 }
289
290 updateDependencies(directives, model) {
291 this.contentTemplate = directives.node;
292
293 var ifValue = true;
294 var ifProperty = directives.findProperty('if');
295 if (ifProperty) {
296 this.ifObserver = ifProperty.createObserver(model);
297 ifValue = this.ifObserver.open(this.updateIfValue, this);
298 }
299
300 var repeatProperty = directives.findProperty('repeat');
301 if (repeatProperty) {
302 this.hasRepeat = true;
303 this.valueObserver = repeatProperty.createObserver(model);
304 } else {
305 var path = observe.Path.get("");
306 this.valueObserver = new observe.PathObserver(model, path);
307 }
308
309 var value = this.valueObserver.open(this.updateIteratedValue, this);
310 this.updateValue(ifValue ? value : null);
311 }
312
313 getUpdatedValue() {
314 return this.valueObserver.discardChanges();
315 }
316
317 updateIfValue(ifValue) {
318 if (!ifValue) {
319 this.valueChanged();
320 return;
321 }
322
323 this.updateValue(this.getUpdatedValue());
324 }
325
326 updateIteratedValue(value) {
327 if (this.ifObserver) {
328 var ifValue = this.ifObserver.discardChanges();
329 if (!ifValue) {
330 this.valueChanged();
331 return;
332 }
333 }
334
335 this.updateValue(value);
336 }
337
338 updateValue(value) {
339 if (!this.hasRepeat)
340 value = [value];
341 var observe = this.hasRepeat && Array.isArray(value);
342 this.valueChanged(value, observe);
343 }
344
345 valueChanged(value, observeValue) {
346 if (!Array.isArray(value))
347 value = [];
348
349 if (value === this.iteratedValue)
350 return;
351
352 this.unobserve();
353 this.presentValue = value;
354 if (observeValue) {
355 this.arrayObserver = new observe.ArrayObserver(this.presentValue);
356 this.arrayObserver.open(this.handleSplices, this);
357 }
358
359 this.handleSplices(observe.ArrayObserver.calculateSplices(this.presentValue,
360 this.iteratedValue));
361 }
362
363 getLastInstanceNode(index) {
364 if (index == -1)
365 return this.template;
366 var instance = this.instances[index];
367 var terminator = instance.terminator;
368 if (!terminator)
369 return this.getLastInstanceNode(index - 1);
370
371 if (!(terminator instanceof Element) || this.template === terminator) {
372 return terminator;
373 }
374
375 var subtemplateIterator = iterators.get(terminator);
376 if (!subtemplateIterator)
377 return terminator;
378
379 return subtemplateIterator.getLastTemplateNode();
380 }
381
382 getLastTemplateNode() {
383 return this.getLastInstanceNode(this.instances.length - 1);
384 }
385
386 insertInstanceAt(index, instance) {
387 var previousInstanceLast = this.getLastInstanceNode(index - 1);
388 var parent = this.template.parentNode;
389 this.instances.splice(index, 0, instance);
390 parent.insertBefore(instance.fragment, previousInstanceLast.nextSibling);
391 }
392
393 extractInstanceAt(index) {
394 var previousInstanceLast = this.getLastInstanceNode(index - 1);
395 var lastNode = this.getLastInstanceNode(index);
396 var parent = this.template.parentNode;
397 var instance = this.instances.splice(index, 1)[0];
398
399 while (lastNode !== previousInstanceLast) {
400 var node = previousInstanceLast.nextSibling;
401 if (node == lastNode)
402 lastNode = previousInstanceLast;
403
404 instance.fragment.appendChild(parent.removeChild(node));
405 }
406
407 return instance;
408 }
409
410 handleSplices(splices) {
411 if (this.closed || !splices.length)
412 return;
413
414 var template = this.template;
415
416 if (!template.parentNode) {
417 this.close();
418 return;
419 }
420
421 observe.ArrayObserver.applySplices(this.iteratedValue, this.presentValue,
422 splices);
423
424 // Instance Removals
425 var instanceCache = new Map;
426 var removeDelta = 0;
427 for (var i = 0; i < splices.length; i++) {
428 var splice = splices[i];
429 var removed = splice.removed;
430 for (var j = 0; j < removed.length; j++) {
431 var model = removed[j];
432 var instance = this.extractInstanceAt(splice.index + removeDelta);
433 if (instance !== emptyInstance) {
434 instanceCache.set(model, instance);
435 }
436 }
437
438 removeDelta -= splice.addedCount;
439 }
440
441 // Instance Insertions
442 for (var i = 0; i < splices.length; i++) {
443 var splice = splices[i];
444 var addIndex = splice.index;
445 for (; addIndex < splice.index + splice.addedCount; addIndex++) {
446 var model = this.iteratedValue[addIndex];
447 var instance = instanceCache.get(model);
448 if (instance) {
449 instanceCache.delete(model);
450 } else {
451 if (model === undefined || model === null) {
452 instance = emptyInstance;
453 } else {
454 instance = createInstance(this.contentTemplate, model);
455 }
456 }
457
458 this.insertInstanceAt(addIndex, instance);
459 }
460 }
461
462 instanceCache.forEach(function(instance) {
463 instance.close();
464 });
465 }
466
467 unobserve() {
468 if (!this.arrayObserver)
469 return;
470
471 this.arrayObserver.close();
472 this.arrayObserver = null;
473 }
474
475 close() {
476 if (this.closed)
477 return;
478 this.unobserve();
479 for (var i = 0; i < this.instances.length; i++) {
480 this.instances[i].close();
481 }
482
483 this.instances.length = 0;
484
485 if (this.ifObserver)
486 this.ifObserver.close();
487 if (this.valueObserver)
488 this.valueObserver.close();
489
490 iterators.delete(this.template);
491 this.closed = true;
492 }
493 }
494
495 module.exports = {
496 createInstance: createInstance,
497 };
498 </script>
OLDNEW
« no previous file with comments | « sky/framework/sky-element/observe.sky ('k') | sky/framework/sky-element/sky-element.sky » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698