OLD | NEW |
(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> |
OLD | NEW |