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