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

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

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

Powered by Google App Engine
This is Rietveld 408576698