github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/themes/wind/static/libs/vue-1.0.24/src/directives/public/for.js (about) 1 import FragmentFactory from '../../fragment/factory' 2 import { FOR } from '../priorities' 3 import { withoutConversion } from '../../observer/index' 4 import { getPath } from '../../parsers/path' 5 import { 6 isObject, 7 warn, 8 createAnchor, 9 replace, 10 before, 11 after, 12 remove, 13 hasOwn, 14 inDoc, 15 defineReactive, 16 def, 17 cancellable, 18 isArray, 19 isPlainObject 20 } from '../../util/index' 21 22 let uid = 0 23 24 const vFor = { 25 26 priority: FOR, 27 terminal: true, 28 29 params: [ 30 'track-by', 31 'stagger', 32 'enter-stagger', 33 'leave-stagger' 34 ], 35 36 bind () { 37 // support "item in/of items" syntax 38 var inMatch = this.expression.match(/(.*) (?:in|of) (.*)/) 39 if (inMatch) { 40 var itMatch = inMatch[1].match(/\((.*),(.*)\)/) 41 if (itMatch) { 42 this.iterator = itMatch[1].trim() 43 this.alias = itMatch[2].trim() 44 } else { 45 this.alias = inMatch[1].trim() 46 } 47 this.expression = inMatch[2] 48 } 49 50 if (!this.alias) { 51 process.env.NODE_ENV !== 'production' && warn( 52 'Invalid v-for expression "' + this.descriptor.raw + '": ' + 53 'alias is required.', 54 this.vm 55 ) 56 return 57 } 58 59 // uid as a cache identifier 60 this.id = '__v-for__' + (++uid) 61 62 // check if this is an option list, 63 // so that we know if we need to update the <select>'s 64 // v-model when the option list has changed. 65 // because v-model has a lower priority than v-for, 66 // the v-model is not bound here yet, so we have to 67 // retrive it in the actual updateModel() function. 68 var tag = this.el.tagName 69 this.isOption = 70 (tag === 'OPTION' || tag === 'OPTGROUP') && 71 this.el.parentNode.tagName === 'SELECT' 72 73 // setup anchor nodes 74 this.start = createAnchor('v-for-start') 75 this.end = createAnchor('v-for-end') 76 replace(this.el, this.end) 77 before(this.start, this.end) 78 79 // cache 80 this.cache = Object.create(null) 81 82 // fragment factory 83 this.factory = new FragmentFactory(this.vm, this.el) 84 }, 85 86 update (data) { 87 this.diff(data) 88 this.updateRef() 89 this.updateModel() 90 }, 91 92 /** 93 * Diff, based on new data and old data, determine the 94 * minimum amount of DOM manipulations needed to make the 95 * DOM reflect the new data Array. 96 * 97 * The algorithm diffs the new data Array by storing a 98 * hidden reference to an owner vm instance on previously 99 * seen data. This allows us to achieve O(n) which is 100 * better than a levenshtein distance based algorithm, 101 * which is O(m * n). 102 * 103 * @param {Array} data 104 */ 105 106 diff (data) { 107 // check if the Array was converted from an Object 108 var item = data[0] 109 var convertedFromObject = this.fromObject = 110 isObject(item) && 111 hasOwn(item, '$key') && 112 hasOwn(item, '$value') 113 114 var trackByKey = this.params.trackBy 115 var oldFrags = this.frags 116 var frags = this.frags = new Array(data.length) 117 var alias = this.alias 118 var iterator = this.iterator 119 var start = this.start 120 var end = this.end 121 var inDocument = inDoc(start) 122 var init = !oldFrags 123 var i, l, frag, key, value, primitive 124 125 // First pass, go through the new Array and fill up 126 // the new frags array. If a piece of data has a cached 127 // instance for it, we reuse it. Otherwise build a new 128 // instance. 129 for (i = 0, l = data.length; i < l; i++) { 130 item = data[i] 131 key = convertedFromObject ? item.$key : null 132 value = convertedFromObject ? item.$value : item 133 primitive = !isObject(value) 134 frag = !init && this.getCachedFrag(value, i, key) 135 if (frag) { // reusable fragment 136 frag.reused = true 137 // update $index 138 frag.scope.$index = i 139 // update $key 140 if (key) { 141 frag.scope.$key = key 142 } 143 // update iterator 144 if (iterator) { 145 frag.scope[iterator] = key !== null ? key : i 146 } 147 // update data for track-by, object repeat & 148 // primitive values. 149 if (trackByKey || convertedFromObject || primitive) { 150 withoutConversion(() => { 151 frag.scope[alias] = value 152 }) 153 } 154 } else { // new isntance 155 frag = this.create(value, alias, i, key) 156 frag.fresh = !init 157 } 158 frags[i] = frag 159 if (init) { 160 frag.before(end) 161 } 162 } 163 164 // we're done for the initial render. 165 if (init) { 166 return 167 } 168 169 // Second pass, go through the old fragments and 170 // destroy those who are not reused (and remove them 171 // from cache) 172 var removalIndex = 0 173 var totalRemoved = oldFrags.length - frags.length 174 // when removing a large number of fragments, watcher removal 175 // turns out to be a perf bottleneck, so we batch the watcher 176 // removals into a single filter call! 177 this.vm._vForRemoving = true 178 for (i = 0, l = oldFrags.length; i < l; i++) { 179 frag = oldFrags[i] 180 if (!frag.reused) { 181 this.deleteCachedFrag(frag) 182 this.remove(frag, removalIndex++, totalRemoved, inDocument) 183 } 184 } 185 this.vm._vForRemoving = false 186 if (removalIndex) { 187 this.vm._watchers = this.vm._watchers.filter(w => w.active) 188 } 189 190 // Final pass, move/insert new fragments into the 191 // right place. 192 var targetPrev, prevEl, currentPrev 193 var insertionIndex = 0 194 for (i = 0, l = frags.length; i < l; i++) { 195 frag = frags[i] 196 // this is the frag that we should be after 197 targetPrev = frags[i - 1] 198 prevEl = targetPrev 199 ? targetPrev.staggerCb 200 ? targetPrev.staggerAnchor 201 : targetPrev.end || targetPrev.node 202 : start 203 if (frag.reused && !frag.staggerCb) { 204 currentPrev = findPrevFrag(frag, start, this.id) 205 if ( 206 currentPrev !== targetPrev && ( 207 !currentPrev || 208 // optimization for moving a single item. 209 // thanks to suggestions by @livoras in #1807 210 findPrevFrag(currentPrev, start, this.id) !== targetPrev 211 ) 212 ) { 213 this.move(frag, prevEl) 214 } 215 } else { 216 // new instance, or still in stagger. 217 // insert with updated stagger index. 218 this.insert(frag, insertionIndex++, prevEl, inDocument) 219 } 220 frag.reused = frag.fresh = false 221 } 222 }, 223 224 /** 225 * Create a new fragment instance. 226 * 227 * @param {*} value 228 * @param {String} alias 229 * @param {Number} index 230 * @param {String} [key] 231 * @return {Fragment} 232 */ 233 234 create (value, alias, index, key) { 235 var host = this._host 236 // create iteration scope 237 var parentScope = this._scope || this.vm 238 var scope = Object.create(parentScope) 239 // ref holder for the scope 240 scope.$refs = Object.create(parentScope.$refs) 241 scope.$els = Object.create(parentScope.$els) 242 // make sure point $parent to parent scope 243 scope.$parent = parentScope 244 // for two-way binding on alias 245 scope.$forContext = this 246 // define scope properties 247 // important: define the scope alias without forced conversion 248 // so that frozen data structures remain non-reactive. 249 withoutConversion(() => { 250 defineReactive(scope, alias, value) 251 }) 252 defineReactive(scope, '$index', index) 253 if (key) { 254 defineReactive(scope, '$key', key) 255 } else if (scope.$key) { 256 // avoid accidental fallback 257 def(scope, '$key', null) 258 } 259 if (this.iterator) { 260 defineReactive(scope, this.iterator, key !== null ? key : index) 261 } 262 var frag = this.factory.create(host, scope, this._frag) 263 frag.forId = this.id 264 this.cacheFrag(value, frag, index, key) 265 return frag 266 }, 267 268 /** 269 * Update the v-ref on owner vm. 270 */ 271 272 updateRef () { 273 var ref = this.descriptor.ref 274 if (!ref) return 275 var hash = (this._scope || this.vm).$refs 276 var refs 277 if (!this.fromObject) { 278 refs = this.frags.map(findVmFromFrag) 279 } else { 280 refs = {} 281 this.frags.forEach(function (frag) { 282 refs[frag.scope.$key] = findVmFromFrag(frag) 283 }) 284 } 285 hash[ref] = refs 286 }, 287 288 /** 289 * For option lists, update the containing v-model on 290 * parent <select>. 291 */ 292 293 updateModel () { 294 if (this.isOption) { 295 var parent = this.start.parentNode 296 var model = parent && parent.__v_model 297 if (model) { 298 model.forceUpdate() 299 } 300 } 301 }, 302 303 /** 304 * Insert a fragment. Handles staggering. 305 * 306 * @param {Fragment} frag 307 * @param {Number} index 308 * @param {Node} prevEl 309 * @param {Boolean} inDocument 310 */ 311 312 insert (frag, index, prevEl, inDocument) { 313 if (frag.staggerCb) { 314 frag.staggerCb.cancel() 315 frag.staggerCb = null 316 } 317 var staggerAmount = this.getStagger(frag, index, null, 'enter') 318 if (inDocument && staggerAmount) { 319 // create an anchor and insert it synchronously, 320 // so that we can resolve the correct order without 321 // worrying about some elements not inserted yet 322 var anchor = frag.staggerAnchor 323 if (!anchor) { 324 anchor = frag.staggerAnchor = createAnchor('stagger-anchor') 325 anchor.__v_frag = frag 326 } 327 after(anchor, prevEl) 328 var op = frag.staggerCb = cancellable(function () { 329 frag.staggerCb = null 330 frag.before(anchor) 331 remove(anchor) 332 }) 333 setTimeout(op, staggerAmount) 334 } else { 335 var target = prevEl.nextSibling 336 /* istanbul ignore if */ 337 if (!target) { 338 // reset end anchor position in case the position was messed up 339 // by an external drag-n-drop library. 340 after(this.end, prevEl) 341 target = this.end 342 } 343 frag.before(target) 344 } 345 }, 346 347 /** 348 * Remove a fragment. Handles staggering. 349 * 350 * @param {Fragment} frag 351 * @param {Number} index 352 * @param {Number} total 353 * @param {Boolean} inDocument 354 */ 355 356 remove (frag, index, total, inDocument) { 357 if (frag.staggerCb) { 358 frag.staggerCb.cancel() 359 frag.staggerCb = null 360 // it's not possible for the same frag to be removed 361 // twice, so if we have a pending stagger callback, 362 // it means this frag is queued for enter but removed 363 // before its transition started. Since it is already 364 // destroyed, we can just leave it in detached state. 365 return 366 } 367 var staggerAmount = this.getStagger(frag, index, total, 'leave') 368 if (inDocument && staggerAmount) { 369 var op = frag.staggerCb = cancellable(function () { 370 frag.staggerCb = null 371 frag.remove() 372 }) 373 setTimeout(op, staggerAmount) 374 } else { 375 frag.remove() 376 } 377 }, 378 379 /** 380 * Move a fragment to a new position. 381 * Force no transition. 382 * 383 * @param {Fragment} frag 384 * @param {Node} prevEl 385 */ 386 387 move (frag, prevEl) { 388 // fix a common issue with Sortable: 389 // if prevEl doesn't have nextSibling, this means it's 390 // been dragged after the end anchor. Just re-position 391 // the end anchor to the end of the container. 392 /* istanbul ignore if */ 393 if (!prevEl.nextSibling) { 394 this.end.parentNode.appendChild(this.end) 395 } 396 frag.before(prevEl.nextSibling, false) 397 }, 398 399 /** 400 * Cache a fragment using track-by or the object key. 401 * 402 * @param {*} value 403 * @param {Fragment} frag 404 * @param {Number} index 405 * @param {String} [key] 406 */ 407 408 cacheFrag (value, frag, index, key) { 409 var trackByKey = this.params.trackBy 410 var cache = this.cache 411 var primitive = !isObject(value) 412 var id 413 if (key || trackByKey || primitive) { 414 id = getTrackByKey(index, key, value, trackByKey) 415 if (!cache[id]) { 416 cache[id] = frag 417 } else if (trackByKey !== '$index') { 418 process.env.NODE_ENV !== 'production' && 419 this.warnDuplicate(value) 420 } 421 } else { 422 id = this.id 423 if (hasOwn(value, id)) { 424 if (value[id] === null) { 425 value[id] = frag 426 } else { 427 process.env.NODE_ENV !== 'production' && 428 this.warnDuplicate(value) 429 } 430 } else if (Object.isExtensible(value)) { 431 def(value, id, frag) 432 } else if (process.env.NODE_ENV !== 'production') { 433 warn( 434 'Frozen v-for objects cannot be automatically tracked, make sure to ' + 435 'provide a track-by key.' 436 ) 437 } 438 } 439 frag.raw = value 440 }, 441 442 /** 443 * Get a cached fragment from the value/index/key 444 * 445 * @param {*} value 446 * @param {Number} index 447 * @param {String} key 448 * @return {Fragment} 449 */ 450 451 getCachedFrag (value, index, key) { 452 var trackByKey = this.params.trackBy 453 var primitive = !isObject(value) 454 var frag 455 if (key || trackByKey || primitive) { 456 var id = getTrackByKey(index, key, value, trackByKey) 457 frag = this.cache[id] 458 } else { 459 frag = value[this.id] 460 } 461 if (frag && (frag.reused || frag.fresh)) { 462 process.env.NODE_ENV !== 'production' && 463 this.warnDuplicate(value) 464 } 465 return frag 466 }, 467 468 /** 469 * Delete a fragment from cache. 470 * 471 * @param {Fragment} frag 472 */ 473 474 deleteCachedFrag (frag) { 475 var value = frag.raw 476 var trackByKey = this.params.trackBy 477 var scope = frag.scope 478 var index = scope.$index 479 // fix #948: avoid accidentally fall through to 480 // a parent repeater which happens to have $key. 481 var key = hasOwn(scope, '$key') && scope.$key 482 var primitive = !isObject(value) 483 if (trackByKey || key || primitive) { 484 var id = getTrackByKey(index, key, value, trackByKey) 485 this.cache[id] = null 486 } else { 487 value[this.id] = null 488 frag.raw = null 489 } 490 }, 491 492 /** 493 * Get the stagger amount for an insertion/removal. 494 * 495 * @param {Fragment} frag 496 * @param {Number} index 497 * @param {Number} total 498 * @param {String} type 499 */ 500 501 getStagger (frag, index, total, type) { 502 type = type + 'Stagger' 503 var trans = frag.node.__v_trans 504 var hooks = trans && trans.hooks 505 var hook = hooks && (hooks[type] || hooks.stagger) 506 return hook 507 ? hook.call(frag, index, total) 508 : index * parseInt(this.params[type] || this.params.stagger, 10) 509 }, 510 511 /** 512 * Pre-process the value before piping it through the 513 * filters. This is passed to and called by the watcher. 514 */ 515 516 _preProcess (value) { 517 // regardless of type, store the un-filtered raw value. 518 this.rawValue = value 519 return value 520 }, 521 522 /** 523 * Post-process the value after it has been piped through 524 * the filters. This is passed to and called by the watcher. 525 * 526 * It is necessary for this to be called during the 527 * wathcer's dependency collection phase because we want 528 * the v-for to update when the source Object is mutated. 529 */ 530 531 _postProcess (value) { 532 if (isArray(value)) { 533 return value 534 } else if (isPlainObject(value)) { 535 // convert plain object to array. 536 var keys = Object.keys(value) 537 var i = keys.length 538 var res = new Array(i) 539 var key 540 while (i--) { 541 key = keys[i] 542 res[i] = { 543 $key: key, 544 $value: value[key] 545 } 546 } 547 return res 548 } else { 549 if (typeof value === 'number' && !isNaN(value)) { 550 value = range(value) 551 } 552 return value || [] 553 } 554 }, 555 556 unbind () { 557 if (this.descriptor.ref) { 558 (this._scope || this.vm).$refs[this.descriptor.ref] = null 559 } 560 if (this.frags) { 561 var i = this.frags.length 562 var frag 563 while (i--) { 564 frag = this.frags[i] 565 this.deleteCachedFrag(frag) 566 frag.destroy() 567 } 568 } 569 } 570 } 571 572 /** 573 * Helper to find the previous element that is a fragment 574 * anchor. This is necessary because a destroyed frag's 575 * element could still be lingering in the DOM before its 576 * leaving transition finishes, but its inserted flag 577 * should have been set to false so we can skip them. 578 * 579 * If this is a block repeat, we want to make sure we only 580 * return frag that is bound to this v-for. (see #929) 581 * 582 * @param {Fragment} frag 583 * @param {Comment|Text} anchor 584 * @param {String} id 585 * @return {Fragment} 586 */ 587 588 function findPrevFrag (frag, anchor, id) { 589 var el = frag.node.previousSibling 590 /* istanbul ignore if */ 591 if (!el) return 592 frag = el.__v_frag 593 while ( 594 (!frag || frag.forId !== id || !frag.inserted) && 595 el !== anchor 596 ) { 597 el = el.previousSibling 598 /* istanbul ignore if */ 599 if (!el) return 600 frag = el.__v_frag 601 } 602 return frag 603 } 604 605 /** 606 * Find a vm from a fragment. 607 * 608 * @param {Fragment} frag 609 * @return {Vue|undefined} 610 */ 611 612 function findVmFromFrag (frag) { 613 let node = frag.node 614 // handle multi-node frag 615 if (frag.end) { 616 while (!node.__vue__ && node !== frag.end && node.nextSibling) { 617 node = node.nextSibling 618 } 619 } 620 return node.__vue__ 621 } 622 623 /** 624 * Create a range array from given number. 625 * 626 * @param {Number} n 627 * @return {Array} 628 */ 629 630 function range (n) { 631 var i = -1 632 var ret = new Array(Math.floor(n)) 633 while (++i < n) { 634 ret[i] = i 635 } 636 return ret 637 } 638 639 /** 640 * Get the track by key for an item. 641 * 642 * @param {Number} index 643 * @param {String} key 644 * @param {*} value 645 * @param {String} [trackByKey] 646 */ 647 648 function getTrackByKey (index, key, value, trackByKey) { 649 return trackByKey 650 ? trackByKey === '$index' 651 ? index 652 : trackByKey.charAt(0).match(/\w/) 653 ? getPath(value, trackByKey) 654 : value[trackByKey] 655 : (key || value) 656 } 657 658 if (process.env.NODE_ENV !== 'production') { 659 vFor.warnDuplicate = function (value) { 660 warn( 661 'Duplicate value found in v-for="' + this.descriptor.raw + '": ' + 662 JSON.stringify(value) + '. Use track-by="$index" if ' + 663 'you are expecting duplicate values.', 664 this.vm 665 ) 666 } 667 } 668 669 export default vFor