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

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

Issue 821353003: Switch to SkyBinder (Closed) Base URL: git@github.com:domokit/mojo.git@master
Patch Set: Created 5 years, 11 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 | « no previous file | sky/framework/sky-element/sky-binder.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
8 <script>
9 Node.prototype.bind = function(name, observable, oneTime) {
10 var self = this;
11
12 if (oneTime) {
13 this[name] = observable;
14 return;
15 }
16
17 this[name] = observable.open(function(value) {
18 self[name] = value;
19 });
20
21 return observable;
22 };
23
24 function sanitizeValue(value) {
25 return value == null ? '' : value;
26 }
27
28 function updateText(node, value) {
29 node.data = sanitizeValue(value);
30 }
31
32 function textBinding(node) {
33 return function(value) {
34 return updateText(node, value);
35 };
36 }
37
38 Text.prototype.bind = function(name, value, oneTime) {
39 if (name !== 'textContent')
40 return Node.prototype.bind.call(this, name, value, oneTime);
41
42 if (oneTime)
43 return updateText(this, value);
44
45 var observable = value;
46 updateText(this, observable.open(textBinding(this)));
47 return observable;
48 }
49
50 function updateAttribute(el, name, value) {
51 el.setAttribute(name, sanitizeValue(value));
52 }
53
54 function attributeBinding(el, name) {
55 return function(value) {
56 updateAttribute(el, name, value);
57 };
58 }
59
60 function bindAsAttribute(el, name) {
61 if (name == 'style' || name == 'class')
62 return true;
63 if (el.tagName == 'a' && name == 'href')
64 return true;
65 }
66
67 Element.prototype.bind = function(name, value, oneTime) {
68 if (!bindAsAttribute(this, name))
69 return Node.prototype.bind.call(this, name, value, oneTime);
70
71 if (oneTime)
72 return updateAttribute(this, name, value);
73
74 var observable = value;
75 updateAttribute(this, name, observable.open(attributeBinding(this, name)));
76 return observable;
77 }
78
79 function getFragmentRoot(node) {
80 var p;
81 while (p = node.parentNode) {
82 node = p;
83 }
84
85 return node;
86 }
87
88 function searchRefId(node, id) {
89 if (!id)
90 return;
91
92 var ref;
93 var selector = '#' + id;
94 while (!ref) {
95 node = getFragmentRoot(node);
96
97 if (node.protoContent_)
98 ref = node.protoContent_.querySelector(selector);
99 else if (node.getElementById)
100 ref = node.getElementById(id);
101
102 if (ref || !node.templateCreator_)
103 break
104
105 node = node.templateCreator_;
106 }
107
108 return ref;
109 }
110
111 function getInstanceRoot(node) {
112 while (node.parentNode) {
113 node = node.parentNode;
114 }
115 return node.templateCreator_ ? node : null;
116 }
117
118 var BIND = 'bind';
119 var REPEAT = 'repeat';
120 var IF = 'if';
121
122 var templateAttributeDirectives = {
123 'template': true,
124 'repeat': true,
125 'bind': true,
126 'ref': true
127 };
128
129 function isTemplate(el) {
130 if (el.isTemplate_ === undefined)
131 el.isTemplate_ = el.tagName == 'template';
132
133 return el.isTemplate_;
134 }
135
136 function mixin(to, from) {
137 Object.getOwnPropertyNames(from).forEach(function(name) {
138 Object.defineProperty(to, name,
139 Object.getOwnPropertyDescriptor(from, name));
140 });
141 }
142
143 function getTemplateStagingDocument(template) {
144 if (!template.stagingDocument_) {
145 var owner = template.ownerDocument;
146 if (!owner.stagingDocument_) {
147 // FIXME(sky): Does this need to create a Document without a registration
148 // context?
149 owner.stagingDocument_ = new Document();
150 owner.stagingDocument_.isStagingDocument = true;
151 owner.stagingDocument_.stagingDocument_ = owner.stagingDocument_;
152 }
153
154 template.stagingDocument_ = owner.stagingDocument_;
155 }
156
157 return template.stagingDocument_;
158 }
159
160 var templateObserver;
161 if (typeof MutationObserver == 'function') {
162 templateObserver = new MutationObserver(function(records) {
163 for (var i = 0; i < records.length; i++) {
164 records[i].target.refChanged_();
165 }
166 });
167 }
168
169 var contentDescriptor = {
170 get: function() {
171 return this.content_;
172 },
173 enumerable: true,
174 configurable: true
175 };
176
177 function ensureSetModelScheduled(template) {
178 if (!template.setModelFn_) {
179 template.setModelFn_ = function() {
180 template.setModelFnScheduled_ = false;
181 var map = getBindings(template,
182 template.delegate_ && template.delegate_.prepareBinding);
183 processBindings(template, map, template.model_);
184 };
185 }
186
187 if (!template.setModelFnScheduled_) {
188 template.setModelFnScheduled_ = true;
189 Observer.runEOM_(template.setModelFn_);
190 }
191 }
192
193 mixin(HTMLTemplateElement.prototype, {
194 bind: function(name, value, oneTime) {
195 if (name != 'ref')
196 return Element.prototype.bind.call(this, name, value, oneTime);
197
198 var self = this;
199 var ref = oneTime ? value : value.open(function(ref) {
200 self.setAttribute('ref', ref);
201 self.refChanged_();
202 });
203
204 this.setAttribute('ref', ref);
205 this.refChanged_();
206 if (oneTime)
207 return;
208
209 if (!this.bindings_) {
210 this.bindings_ = { ref: value };
211 } else {
212 this.bindings_.ref = value;
213 }
214
215 return value;
216 },
217
218 processBindingDirectives_: function(directives) {
219 if (this.iterator_)
220 this.iterator_.closeDeps();
221
222 if (!directives.if && !directives.bind && !directives.repeat) {
223 if (this.iterator_) {
224 this.iterator_.close();
225 this.iterator_ = undefined;
226 }
227
228 return;
229 }
230
231 if (!this.iterator_) {
232 this.iterator_ = new TemplateIterator(this);
233 }
234
235 this.iterator_.updateDependencies(directives, this.model_);
236
237 if (templateObserver) {
238 templateObserver.observe(this, { attributes: true,
239 attributeFilter: ['ref'] });
240 }
241
242 return this.iterator_;
243 },
244
245 createInstance: function(model, bindingDelegate, delegate_) {
246 if (bindingDelegate)
247 delegate_ = this.newDelegate_(bindingDelegate);
248 else if (!delegate_)
249 delegate_ = this.delegate_;
250
251 if (!this.refContent_)
252 this.refContent_ = this.ref_.content;
253 var content = this.refContent_;
254 if (content.firstChild === null)
255 return emptyInstance;
256
257 var map = getInstanceBindingMap(content, delegate_);
258 var stagingDocument = getTemplateStagingDocument(this);
259 var instance = stagingDocument.createDocumentFragment();
260 instance.templateCreator_ = this;
261 instance.protoContent_ = content;
262 instance.bindings_ = [];
263 instance.terminator_ = null;
264 var instanceRecord = instance.templateInstance_ = {
265 firstNode: null,
266 lastNode: null,
267 model: model
268 };
269
270 var i = 0;
271 var collectTerminator = false;
272 for (var child = content.firstChild; child; child = child.nextSibling) {
273 // The terminator of the instance is the clone of the last child of the
274 // content. If the last child is an active template, it may produce
275 // instances as a result of production, so simply collecting the last
276 // child of the instance after it has finished producing may be wrong.
277 if (child.nextSibling === null)
278 collectTerminator = true;
279
280 var clone = cloneAndBindInstance(child, instance, stagingDocument,
281 map.children[i++],
282 model,
283 delegate_,
284 instance.bindings_);
285 clone.templateInstance_ = instanceRecord;
286 if (collectTerminator)
287 instance.terminator_ = clone;
288 }
289
290 instanceRecord.firstNode = instance.firstChild;
291 instanceRecord.lastNode = instance.lastChild;
292 instance.templateCreator_ = undefined;
293 instance.protoContent_ = undefined;
294 return instance;
295 },
296
297 get model() {
298 return this.model_;
299 },
300
301 set model(model) {
302 this.model_ = model;
303 ensureSetModelScheduled(this);
304 },
305
306 get bindingDelegate() {
307 return this.delegate_ && this.delegate_.raw;
308 },
309
310 refChanged_: function() {
311 if (!this.iterator_ || this.refContent_ === this.ref_.content)
312 return;
313
314 this.refContent_ = undefined;
315 this.iterator_.valueChanged();
316 this.iterator_.updateIteratedValue(this.iterator_.getUpdatedValue());
317 },
318
319 clear: function() {
320 this.model_ = undefined;
321 this.delegate_ = undefined;
322 if (this.bindings_ && this.bindings_.ref)
323 this.bindings_.ref.close()
324 this.refContent_ = undefined;
325 if (!this.iterator_)
326 return;
327 this.iterator_.valueChanged();
328 this.iterator_.close()
329 this.iterator_ = undefined;
330 },
331
332 setDelegate_: function(delegate) {
333 this.delegate_ = delegate;
334 this.bindingMap_ = undefined;
335 if (this.iterator_) {
336 this.iterator_.instancePositionChangedFn_ = undefined;
337 this.iterator_.instanceModelFn_ = undefined;
338 }
339 },
340
341 newDelegate_: function(bindingDelegate) {
342 if (!bindingDelegate)
343 return;
344
345 function delegateFn(name) {
346 var fn = bindingDelegate && bindingDelegate[name];
347 if (typeof fn != 'function')
348 return;
349
350 return function() {
351 return fn.apply(bindingDelegate, arguments);
352 };
353 }
354
355 return {
356 bindingMaps: {},
357 raw: bindingDelegate,
358 prepareBinding: delegateFn('prepareBinding'),
359 prepareInstanceModel: delegateFn('prepareInstanceModel'),
360 prepareInstancePositionChanged:
361 delegateFn('prepareInstancePositionChanged')
362 };
363 },
364
365 set bindingDelegate(bindingDelegate) {
366 if (this.delegate_) {
367 throw Error('Template must be cleared before a new bindingDelegate ' +
368 'can be assigned');
369 }
370
371 this.setDelegate_(this.newDelegate_(bindingDelegate));
372 },
373
374 get ref_() {
375 var ref = searchRefId(this, this.getAttribute('ref'));
376 if (!ref)
377 ref = this.instanceRef_;
378
379 if (!ref)
380 return this;
381
382 var nextRef = ref.ref_;
383 return nextRef ? nextRef : ref;
384 }
385 });
386
387 // Returns
388 // a) undefined if there are no mustaches.
389 // b) [TEXT, (ONE_TIME?, PATH, DELEGATE_FN, TEXT)+] if there is at least
390 // one mustache.
391 function parseMustaches(s, name, node, prepareBindingFn) {
392 if (!s || !s.length)
393 return;
394
395 var tokens;
396 var length = s.length;
397 var startIndex = 0, lastIndex = 0, endIndex = 0;
398 var onlyOneTime = true;
399 while (lastIndex < length) {
400 var startIndex = s.indexOf('{{', lastIndex);
401 var oneTimeStart = s.indexOf('[[', lastIndex);
402 var oneTime = false;
403 var terminator = '}}';
404
405 if (oneTimeStart >= 0 &&
406 (startIndex < 0 || oneTimeStart < startIndex)) {
407 startIndex = oneTimeStart;
408 oneTime = true;
409 terminator = ']]';
410 }
411
412 endIndex = startIndex < 0 ? -1 : s.indexOf(terminator, startIndex + 2);
413
414 if (endIndex < 0) {
415 if (!tokens)
416 return;
417
418 tokens.push(s.slice(lastIndex)); // TEXT
419 break;
420 }
421
422 tokens = tokens || [];
423 tokens.push(s.slice(lastIndex, startIndex)); // TEXT
424 var pathString = s.slice(startIndex + 2, endIndex).trim();
425 tokens.push(oneTime); // ONE_TIME?
426 onlyOneTime = onlyOneTime && oneTime;
427 var delegateFn = prepareBindingFn &&
428 prepareBindingFn(pathString, name, node);
429 // Don't try to parse the expression if there's a prepareBinding function
430 if (delegateFn == null) {
431 tokens.push(observe.Path.get(pathString)); // PATH
432 } else {
433 tokens.push(null);
434 }
435 tokens.push(delegateFn); // DELEGATE_FN
436 lastIndex = endIndex + 2;
437 }
438
439 if (lastIndex === length)
440 tokens.push(''); // TEXT
441
442 tokens.hasOnePath = tokens.length === 5;
443 tokens.isSimplePath = tokens.hasOnePath &&
444 tokens[0] == '' &&
445 tokens[4] == '';
446 tokens.onlyOneTime = onlyOneTime;
447
448 tokens.combinator = function(values) {
449 var newValue = tokens[0];
450
451 for (var i = 1; i < tokens.length; i += 4) {
452 var value = tokens.hasOnePath ? values : values[(i - 1) / 4];
453 if (value !== undefined)
454 newValue += value;
455 newValue += tokens[i + 3];
456 }
457
458 return newValue;
459 }
460
461 return tokens;
462 };
463
464 function processOneTimeBinding(name, tokens, node, model) {
465 if (tokens.hasOnePath) {
466 var delegateFn = tokens[3];
467 var value = delegateFn ? delegateFn(model, node, true) :
468 tokens[2].getValueFrom(model);
469 return tokens.isSimplePath ? value : tokens.combinator(value);
470 }
471
472 var values = [];
473 for (var i = 1; i < tokens.length; i += 4) {
474 var delegateFn = tokens[i + 2];
475 values[(i - 1) / 4] = delegateFn ? delegateFn(model, node) :
476 tokens[i + 1].getValueFrom(model);
477 }
478
479 return tokens.combinator(values);
480 }
481
482 function processSinglePathBinding(name, tokens, node, model) {
483 var delegateFn = tokens[3];
484 var observer = delegateFn ? delegateFn(model, node, false) :
485 new observe.PathObserver(model, tokens[2]);
486
487 return tokens.isSimplePath ? observer :
488 new observe.ObserverTransform(observer, tokens.combinator);
489 }
490
491 function processBinding(name, tokens, node, model) {
492 if (tokens.onlyOneTime)
493 return processOneTimeBinding(name, tokens, node, model);
494
495 if (tokens.hasOnePath)
496 return processSinglePathBinding(name, tokens, node, model);
497
498 var observer = new observe.CompoundObserver();
499
500 for (var i = 1; i < tokens.length; i += 4) {
501 var oneTime = tokens[i];
502 var delegateFn = tokens[i + 2];
503
504 if (delegateFn) {
505 var value = delegateFn(model, node, oneTime);
506 if (oneTime)
507 observer.addPath(value)
508 else
509 observer.addObserver(value);
510 continue;
511 }
512
513 var path = tokens[i + 1];
514 if (oneTime)
515 observer.addPath(path.getValueFrom(model))
516 else
517 observer.addPath(model, path);
518 }
519
520 return new observe.ObserverTransform(observer, tokens.combinator);
521 }
522
523 function processBindings(node, bindings, model, instanceBindings) {
524 for (var i = 0; i < bindings.length; i += 2) {
525 var name = bindings[i]
526 var tokens = bindings[i + 1];
527 var value = processBinding(name, tokens, node, model);
528 var binding = node.bind(name, value, tokens.onlyOneTime);
529 if (binding && instanceBindings)
530 instanceBindings.push(binding);
531 }
532
533 if (!bindings.isTemplate)
534 return;
535
536 node.model_ = model;
537 var iter = node.processBindingDirectives_(bindings);
538 if (instanceBindings && iter)
539 instanceBindings.push(iter);
540 }
541
542 function parseWithDefault(el, name, prepareBindingFn) {
543 var v = el.getAttribute(name);
544 return parseMustaches(v == '' ? '{{}}' : v, name, el, prepareBindingFn);
545 }
546
547 function addEventHandler(element, name, method) {
548 element.addEventListener(name, function(event) {
549 var scope = element.ownerScope;
550 var host = scope.host;
551 var handler = host && host[method];
552 if (handler instanceof Function)
553 return handler.call(host, event);
554 });
555 }
556
557 function parseAttributeBindings(element, prepareBindingFn) {
558 var bindings = [];
559 var ifFound = false;
560 var bindFound = false;
561 var attributes = element.getAttributes();
562
563 for (var i = 0; i < attributes.length; i++) {
564 var attr = attributes[i];
565 var name = attr.name;
566 var value = attr.value;
567
568 if (isTemplate(element) &&
569 (name === IF || name === BIND || name === REPEAT)) {
570 continue;
571 }
572
573 if (name.startsWith('on-')) {
574 if (!bindings.eventHandlers)
575 bindings.eventHandlers = new Map();
576 bindings.eventHandlers.set(name.substring(3), value);
577 continue;
578 }
579
580 var tokens = parseMustaches(value, name, element,
581 prepareBindingFn);
582 if (!tokens)
583 continue;
584
585 bindings.push(name, tokens);
586 }
587
588 if (isTemplate(element)) {
589 bindings.isTemplate = true;
590 bindings.if = parseWithDefault(element, IF, prepareBindingFn);
591 bindings.bind = parseWithDefault(element, BIND, prepareBindingFn);
592 bindings.repeat = parseWithDefault(element, REPEAT, prepareBindingFn);
593
594 if (bindings.if && !bindings.bind && !bindings.repeat)
595 bindings.bind = parseMustaches('{{}}', BIND, element, prepareBindingFn);
596 }
597
598 return bindings;
599 }
600
601 function getBindings(node, prepareBindingFn) {
602 if (node instanceof Element) {
603 return parseAttributeBindings(node, prepareBindingFn);
604 }
605
606 if (node instanceof Text) {
607 var tokens = parseMustaches(node.data, 'textContent', node,
608 prepareBindingFn);
609 if (tokens)
610 return ['textContent', tokens];
611 }
612
613 return [];
614 }
615
616 function cloneAndBindInstance(node, parent, stagingDocument, bindings, model,
617 delegate,
618 instanceBindings,
619 instanceRecord) {
620 var clone = parent.appendChild(stagingDocument.importNode(node, false));
621
622 var i = 0;
623 for (var child = node.firstChild; child; child = child.nextSibling) {
624 cloneAndBindInstance(child, clone, stagingDocument,
625 bindings.children[i++],
626 model,
627 delegate,
628 instanceBindings);
629 }
630
631 if (bindings.isTemplate) {
632 clone.instanceRef_ = node;
633
634 if (delegate)
635 clone.setDelegate_(delegate);
636 }
637
638 if (bindings.eventHandlers) {
639 bindings.eventHandlers.forEach(function(handler, eventName) {
640 addEventHandler(clone, eventName, handler);
641 });
642 }
643
644 processBindings(clone, bindings, model, instanceBindings);
645 return clone;
646 }
647
648 function createInstanceBindingMap(node, prepareBindingFn) {
649 var map = getBindings(node, prepareBindingFn);
650 map.children = {};
651 var index = 0;
652 for (var child = node.firstChild; child; child = child.nextSibling) {
653 map.children[index++] = createInstanceBindingMap(child, prepareBindingFn);
654 }
655
656 return map;
657 }
658
659 var contentUidCounter = 1;
660
661 // TODO(rafaelw): Setup a MutationObserver on content which clears the id
662 // so that bindingMaps regenerate when the template.content changes.
663 function getContentUid(content) {
664 var id = content.id_;
665 if (!id)
666 id = content.id_ = contentUidCounter++;
667 return id;
668 }
669
670 // Each delegate is associated with a set of bindingMaps, one for each
671 // content which may be used by a template. The intent is that each binding
672 // delegate gets the opportunity to prepare the instance (via the prepare*
673 // delegate calls) once across all uses.
674 // TODO(rafaelw): Separate out the parse map from the binding map. In the
675 // current implementation, if two delegates need a binding map for the same
676 // content, the second will have to reparse.
677 function getInstanceBindingMap(content, delegate_) {
678 var contentId = getContentUid(content);
679 if (delegate_) {
680 var map = delegate_.bindingMaps[contentId];
681 if (!map) {
682 map = delegate_.bindingMaps[contentId] =
683 createInstanceBindingMap(content, delegate_.prepareBinding) || [];
684 }
685 return map;
686 }
687
688 var map = content.bindingMap_;
689 if (!map) {
690 map = content.bindingMap_ =
691 createInstanceBindingMap(content, undefined) || [];
692 }
693 return map;
694 }
695
696 Object.defineProperty(Node.prototype, 'templateInstance', {
697 get: function() {
698 var instance = this.templateInstance_;
699 return instance ? instance :
700 (this.parentNode ? this.parentNode.templateInstance : undefined);
701 }
702 });
703
704 var emptyInstance = document.createDocumentFragment();
705 emptyInstance.bindings_ = [];
706 emptyInstance.terminator_ = null;
707
708 function TemplateIterator(templateElement) {
709 this.closed = false;
710 this.templateElement_ = templateElement;
711 this.instances = [];
712 this.deps = undefined;
713 this.iteratedValue = [];
714 this.presentValue = undefined;
715 this.arrayObserver = undefined;
716 }
717
718 TemplateIterator.prototype = {
719 closeDeps: function() {
720 var deps = this.deps;
721 if (deps) {
722 if (deps.ifOneTime === false)
723 deps.ifValue.close();
724 if (deps.oneTime === false)
725 deps.value.close();
726 }
727 },
728
729 updateDependencies: function(directives, model) {
730 this.closeDeps();
731
732 var deps = this.deps = {};
733 var template = this.templateElement_;
734
735 var ifValue = true;
736 if (directives.if) {
737 deps.hasIf = true;
738 deps.ifOneTime = directives.if.onlyOneTime;
739 deps.ifValue = processBinding(IF, directives.if, template, model);
740
741 ifValue = deps.ifValue;
742
743 // oneTime if & predicate is false. nothing else to do.
744 if (deps.ifOneTime && !ifValue) {
745 this.valueChanged();
746 return;
747 }
748
749 if (!deps.ifOneTime)
750 ifValue = ifValue.open(this.updateIfValue, this);
751 }
752
753 if (directives.repeat) {
754 deps.repeat = true;
755 deps.oneTime = directives.repeat.onlyOneTime;
756 deps.value = processBinding(REPEAT, directives.repeat, template, model);
757 } else {
758 deps.repeat = false;
759 deps.oneTime = directives.bind.onlyOneTime;
760 deps.value = processBinding(BIND, directives.bind, template, model);
761 }
762
763 var value = deps.value;
764 if (!deps.oneTime)
765 value = value.open(this.updateIteratedValue, this);
766
767 if (!ifValue) {
768 this.valueChanged();
769 return;
770 }
771
772 this.updateValue(value);
773 },
774
775 /**
776 * Gets the updated value of the bind/repeat. This can potentially call
777 * user code (if a bindingDelegate is set up) so we try to avoid it if we
778 * already have the value in hand (from Observer.open).
779 */
780 getUpdatedValue: function() {
781 var value = this.deps.value;
782 if (!this.deps.oneTime)
783 value = value.discardChanges();
784 return value;
785 },
786
787 updateIfValue: function(ifValue) {
788 if (!ifValue) {
789 this.valueChanged();
790 return;
791 }
792
793 this.updateValue(this.getUpdatedValue());
794 },
795
796 updateIteratedValue: function(value) {
797 if (this.deps.hasIf) {
798 var ifValue = this.deps.ifValue;
799 if (!this.deps.ifOneTime)
800 ifValue = ifValue.discardChanges();
801 if (!ifValue) {
802 this.valueChanged();
803 return;
804 }
805 }
806
807 this.updateValue(value);
808 },
809
810 updateValue: function(value) {
811 if (!this.deps.repeat)
812 value = [value];
813 var observe = this.deps.repeat &&
814 !this.deps.oneTime &&
815 Array.isArray(value);
816 this.valueChanged(value, observe);
817 },
818
819 valueChanged: function(value, observeValue) {
820 if (!Array.isArray(value))
821 value = [];
822
823 if (value === this.iteratedValue)
824 return;
825
826 this.unobserve();
827 this.presentValue = value;
828 if (observeValue) {
829 this.arrayObserver = new observe.ArrayObserver(this.presentValue);
830 this.arrayObserver.open(this.handleSplices, this);
831 }
832
833 this.handleSplices(observe.ArrayObserver.calculateSplices(this.presentValue,
834 this.iteratedValue));
835 },
836
837 getLastInstanceNode: function(index) {
838 if (index == -1)
839 return this.templateElement_;
840 var instance = this.instances[index];
841 var terminator = instance.terminator_;
842 if (!terminator)
843 return this.getLastInstanceNode(index - 1);
844
845 if (terminator.nodeType !== Node.ELEMENT_NODE ||
846 this.templateElement_ === terminator) {
847 return terminator;
848 }
849
850 var subtemplateIterator = terminator.iterator_;
851 if (!subtemplateIterator)
852 return terminator;
853
854 return subtemplateIterator.getLastTemplateNode();
855 },
856
857 getLastTemplateNode: function() {
858 return this.getLastInstanceNode(this.instances.length - 1);
859 },
860
861 insertInstanceAt: function(index, fragment) {
862 var previousInstanceLast = this.getLastInstanceNode(index - 1);
863 var parent = this.templateElement_.parentNode;
864 this.instances.splice(index, 0, fragment);
865
866 parent.insertBefore(fragment, previousInstanceLast.nextSibling);
867 },
868
869 extractInstanceAt: function(index) {
870 var previousInstanceLast = this.getLastInstanceNode(index - 1);
871 var lastNode = this.getLastInstanceNode(index);
872 var parent = this.templateElement_.parentNode;
873 var instance = this.instances.splice(index, 1)[0];
874
875 while (lastNode !== previousInstanceLast) {
876 var node = previousInstanceLast.nextSibling;
877 if (node == lastNode)
878 lastNode = previousInstanceLast;
879
880 instance.appendChild(parent.removeChild(node));
881 }
882
883 return instance;
884 },
885
886 getDelegateFn: function(fn) {
887 fn = fn && fn(this.templateElement_);
888 return typeof fn === 'function' ? fn : null;
889 },
890
891 handleSplices: function(splices) {
892 if (this.closed || !splices.length)
893 return;
894
895 var template = this.templateElement_;
896
897 if (!template.parentNode) {
898 this.close();
899 return;
900 }
901
902 observe.ArrayObserver.applySplices(this.iteratedValue, this.presentValue,
903 splices);
904
905 var delegate = template.delegate_;
906 if (this.instanceModelFn_ === undefined) {
907 this.instanceModelFn_ =
908 this.getDelegateFn(delegate && delegate.prepareInstanceModel);
909 }
910
911 if (this.instancePositionChangedFn_ === undefined) {
912 this.instancePositionChangedFn_ =
913 this.getDelegateFn(delegate &&
914 delegate.prepareInstancePositionChanged);
915 }
916
917 // Instance Removals
918 var instanceCache = new Map;
919 var removeDelta = 0;
920 for (var i = 0; i < splices.length; i++) {
921 var splice = splices[i];
922 var removed = splice.removed;
923 for (var j = 0; j < removed.length; j++) {
924 var model = removed[j];
925 var instance = this.extractInstanceAt(splice.index + removeDelta);
926 if (instance !== emptyInstance) {
927 instanceCache.set(model, instance);
928 }
929 }
930
931 removeDelta -= splice.addedCount;
932 }
933
934 // Instance Insertions
935 for (var i = 0; i < splices.length; i++) {
936 var splice = splices[i];
937 var addIndex = splice.index;
938 for (; addIndex < splice.index + splice.addedCount; addIndex++) {
939 var model = this.iteratedValue[addIndex];
940 var instance = instanceCache.get(model);
941 if (instance) {
942 instanceCache.delete(model);
943 } else {
944 if (this.instanceModelFn_) {
945 model = this.instanceModelFn_(model);
946 }
947
948 if (model === undefined) {
949 instance = emptyInstance;
950 } else {
951 instance = template.createInstance(model, undefined, delegate);
952 }
953 }
954
955 this.insertInstanceAt(addIndex, instance);
956 }
957 }
958
959 instanceCache.forEach(function(instance) {
960 this.closeInstanceBindings(instance);
961 }, this);
962
963 if (this.instancePositionChangedFn_)
964 this.reportInstancesMoved(splices);
965 },
966
967 reportInstanceMoved: function(index) {
968 var instance = this.instances[index];
969 if (instance === emptyInstance)
970 return;
971
972 this.instancePositionChangedFn_(instance.templateInstance_, index);
973 },
974
975 reportInstancesMoved: function(splices) {
976 var index = 0;
977 var offset = 0;
978 for (var i = 0; i < splices.length; i++) {
979 var splice = splices[i];
980 if (offset != 0) {
981 while (index < splice.index) {
982 this.reportInstanceMoved(index);
983 index++;
984 }
985 } else {
986 index = splice.index;
987 }
988
989 while (index < splice.index + splice.addedCount) {
990 this.reportInstanceMoved(index);
991 index++;
992 }
993
994 offset += splice.addedCount - splice.removed.length;
995 }
996
997 if (offset == 0)
998 return;
999
1000 var length = this.instances.length;
1001 while (index < length) {
1002 this.reportInstanceMoved(index);
1003 index++;
1004 }
1005 },
1006
1007 closeInstanceBindings: function(instance) {
1008 var bindings = instance.bindings_;
1009 for (var i = 0; i < bindings.length; i++) {
1010 bindings[i].close();
1011 }
1012 },
1013
1014 unobserve: function() {
1015 if (!this.arrayObserver)
1016 return;
1017
1018 this.arrayObserver.close();
1019 this.arrayObserver = undefined;
1020 },
1021
1022 close: function() {
1023 if (this.closed)
1024 return;
1025 this.unobserve();
1026 for (var i = 0; i < this.instances.length; i++) {
1027 this.closeInstanceBindings(this.instances[i]);
1028 }
1029
1030 this.instances.length = 0;
1031 this.closeDeps();
1032 this.templateElement_.iterator_ = undefined;
1033 this.closed = true;
1034 }
1035 };
1036 </script>
OLDNEW
« no previous file with comments | « no previous file | sky/framework/sky-element/sky-binder.sky » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698