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