PHP Classes

File: public/js/lib/vue/src/directives/public/for.js

Recommend this page to a friend!
  Classes of Sergey Beskorovayniy   Silex MVC Blog   public/js/lib/vue/src/directives/public/for.js   Download  
File: public/js/lib/vue/src/directives/public/for.js
Role: Auxiliary data
Content type: text/plain
Description: Auxiliary data
Class: Silex MVC Blog
MVC based blog using on the Silex micro-framework
Author: By
Last change:
Date: 8 years ago
Size: 17,583 bytes
 

Contents

Class file image Download
import FragmentFactory from '../../fragment/factory' import { FOR } from '../priorities' import { withoutConversion } from '../../observer/index' import { getPath } from '../../parsers/path' import { isObject, warn, createAnchor, replace, before, after, remove, hasOwn, inDoc, defineReactive, def, cancellable, isArray, isPlainObject } from '../../util/index' let uid = 0 const vFor = { priority: FOR, terminal: true, params: [ 'track-by', 'stagger', 'enter-stagger', 'leave-stagger' ], bind () { // support "item in/of items" syntax var inMatch = this.expression.match(/(.*) (?:in|of) (.*)/) if (inMatch) { var itMatch = inMatch[1].match(/\((.*),(.*)\)/) if (itMatch) { this.iterator = itMatch[1].trim() this.alias = itMatch[2].trim() } else { this.alias = inMatch[1].trim() } this.expression = inMatch[2] } if (!this.alias) { process.env.NODE_ENV !== 'production' && warn( 'Invalid v-for expression "' + this.descriptor.raw + '": ' + 'alias is required.', this.vm ) return } // uid as a cache identifier this.id = '__v-for__' + (++uid) // check if this is an option list, // so that we know if we need to update the <select>'s // v-model when the option list has changed. // because v-model has a lower priority than v-for, // the v-model is not bound here yet, so we have to // retrive it in the actual updateModel() function. var tag = this.el.tagName this.isOption = (tag === 'OPTION' || tag === 'OPTGROUP') && this.el.parentNode.tagName === 'SELECT' // setup anchor nodes this.start = createAnchor('v-for-start') this.end = createAnchor('v-for-end') replace(this.el, this.end) before(this.start, this.end) // cache this.cache = Object.create(null) // fragment factory this.factory = new FragmentFactory(this.vm, this.el) }, update (data) { this.diff(data) this.updateRef() this.updateModel() }, /** * Diff, based on new data and old data, determine the * minimum amount of DOM manipulations needed to make the * DOM reflect the new data Array. * * The algorithm diffs the new data Array by storing a * hidden reference to an owner vm instance on previously * seen data. This allows us to achieve O(n) which is * better than a levenshtein distance based algorithm, * which is O(m * n). * * @param {Array} data */ diff (data) { // check if the Array was converted from an Object var item = data[0] var convertedFromObject = this.fromObject = isObject(item) && hasOwn(item, '$key') && hasOwn(item, '$value') var trackByKey = this.params.trackBy var oldFrags = this.frags var frags = this.frags = new Array(data.length) var alias = this.alias var iterator = this.iterator var start = this.start var end = this.end var inDocument = inDoc(start) var init = !oldFrags var i, l, frag, key, value, primitive // First pass, go through the new Array and fill up // the new frags array. If a piece of data has a cached // instance for it, we reuse it. Otherwise build a new // instance. for (i = 0, l = data.length; i < l; i++) { item = data[i] key = convertedFromObject ? item.$key : null value = convertedFromObject ? item.$value : item primitive = !isObject(value) frag = !init && this.getCachedFrag(value, i, key) if (frag) { // reusable fragment frag.reused = true // update $index frag.scope.$index = i // update $key if (key) { frag.scope.$key = key } // update iterator if (iterator) { frag.scope[iterator] = key !== null ? key : i } // update data for track-by, object repeat & // primitive values. if (trackByKey || convertedFromObject || primitive) { withoutConversion(() => { frag.scope[alias] = value }) } } else { // new isntance frag = this.create(value, alias, i, key) frag.fresh = !init } frags[i] = frag if (init) { frag.before(end) } } // we're done for the initial render. if (init) { return } // Second pass, go through the old fragments and // destroy those who are not reused (and remove them // from cache) var removalIndex = 0 var totalRemoved = oldFrags.length - frags.length // when removing a large number of fragments, watcher removal // turns out to be a perf bottleneck, so we batch the watcher // removals into a single filter call! this.vm._vForRemoving = true for (i = 0, l = oldFrags.length; i < l; i++) { frag = oldFrags[i] if (!frag.reused) { this.deleteCachedFrag(frag) this.remove(frag, removalIndex++, totalRemoved, inDocument) } } this.vm._vForRemoving = false if (removalIndex) { this.vm._watchers = this.vm._watchers.filter(w => w.active) } // Final pass, move/insert new fragments into the // right place. var targetPrev, prevEl, currentPrev var insertionIndex = 0 for (i = 0, l = frags.length; i < l; i++) { frag = frags[i] // this is the frag that we should be after targetPrev = frags[i - 1] prevEl = targetPrev ? targetPrev.staggerCb ? targetPrev.staggerAnchor : targetPrev.end || targetPrev.node : start if (frag.reused && !frag.staggerCb) { currentPrev = findPrevFrag(frag, start, this.id) if ( currentPrev !== targetPrev && ( !currentPrev || // optimization for moving a single item. // thanks to suggestions by @livoras in #1807 findPrevFrag(currentPrev, start, this.id) !== targetPrev ) ) { this.move(frag, prevEl) } } else { // new instance, or still in stagger. // insert with updated stagger index. this.insert(frag, insertionIndex++, prevEl, inDocument) } frag.reused = frag.fresh = false } }, /** * Create a new fragment instance. * * @param {*} value * @param {String} alias * @param {Number} index * @param {String} [key] * @return {Fragment} */ create (value, alias, index, key) { var host = this._host // create iteration scope var parentScope = this._scope || this.vm var scope = Object.create(parentScope) // ref holder for the scope scope.$refs = Object.create(parentScope.$refs) scope.$els = Object.create(parentScope.$els) // make sure point $parent to parent scope scope.$parent = parentScope // for two-way binding on alias scope.$forContext = this // define scope properties // important: define the scope alias without forced conversion // so that frozen data structures remain non-reactive. withoutConversion(() => { defineReactive(scope, alias, value) }) defineReactive(scope, '$index', index) if (key) { defineReactive(scope, '$key', key) } else if (scope.$key) { // avoid accidental fallback def(scope, '$key', null) } if (this.iterator) { defineReactive(scope, this.iterator, key !== null ? key : index) } var frag = this.factory.create(host, scope, this._frag) frag.forId = this.id this.cacheFrag(value, frag, index, key) return frag }, /** * Update the v-ref on owner vm. */ updateRef () { var ref = this.descriptor.ref if (!ref) return var hash = (this._scope || this.vm).$refs var refs if (!this.fromObject) { refs = this.frags.map(findVmFromFrag) } else { refs = {} this.frags.forEach(function (frag) { refs[frag.scope.$key] = findVmFromFrag(frag) }) } hash[ref] = refs }, /** * For option lists, update the containing v-model on * parent <select>. */ updateModel () { if (this.isOption) { var parent = this.start.parentNode var model = parent && parent.__v_model if (model) { model.forceUpdate() } } }, /** * Insert a fragment. Handles staggering. * * @param {Fragment} frag * @param {Number} index * @param {Node} prevEl * @param {Boolean} inDocument */ insert (frag, index, prevEl, inDocument) { if (frag.staggerCb) { frag.staggerCb.cancel() frag.staggerCb = null } var staggerAmount = this.getStagger(frag, index, null, 'enter') if (inDocument && staggerAmount) { // create an anchor and insert it synchronously, // so that we can resolve the correct order without // worrying about some elements not inserted yet var anchor = frag.staggerAnchor if (!anchor) { anchor = frag.staggerAnchor = createAnchor('stagger-anchor') anchor.__v_frag = frag } after(anchor, prevEl) var op = frag.staggerCb = cancellable(function () { frag.staggerCb = null frag.before(anchor) remove(anchor) }) setTimeout(op, staggerAmount) } else { var target = prevEl.nextSibling /* istanbul ignore if */ if (!target) { // reset end anchor position in case the position was messed up // by an external drag-n-drop library. after(this.end, prevEl) target = this.end } frag.before(target) } }, /** * Remove a fragment. Handles staggering. * * @param {Fragment} frag * @param {Number} index * @param {Number} total * @param {Boolean} inDocument */ remove (frag, index, total, inDocument) { if (frag.staggerCb) { frag.staggerCb.cancel() frag.staggerCb = null // it's not possible for the same frag to be removed // twice, so if we have a pending stagger callback, // it means this frag is queued for enter but removed // before its transition started. Since it is already // destroyed, we can just leave it in detached state. return } var staggerAmount = this.getStagger(frag, index, total, 'leave') if (inDocument && staggerAmount) { var op = frag.staggerCb = cancellable(function () { frag.staggerCb = null frag.remove() }) setTimeout(op, staggerAmount) } else { frag.remove() } }, /** * Move a fragment to a new position. * Force no transition. * * @param {Fragment} frag * @param {Node} prevEl */ move (frag, prevEl) { // fix a common issue with Sortable: // if prevEl doesn't have nextSibling, this means it's // been dragged after the end anchor. Just re-position // the end anchor to the end of the container. /* istanbul ignore if */ if (!prevEl.nextSibling) { this.end.parentNode.appendChild(this.end) } frag.before(prevEl.nextSibling, false) }, /** * Cache a fragment using track-by or the object key. * * @param {*} value * @param {Fragment} frag * @param {Number} index * @param {String} [key] */ cacheFrag (value, frag, index, key) { var trackByKey = this.params.trackBy var cache = this.cache var primitive = !isObject(value) var id if (key || trackByKey || primitive) { id = getTrackByKey(index, key, value, trackByKey) if (!cache[id]) { cache[id] = frag } else if (trackByKey !== '$index') { process.env.NODE_ENV !== 'production' && this.warnDuplicate(value) } } else { id = this.id if (hasOwn(value, id)) { if (value[id] === null) { value[id] = frag } else { process.env.NODE_ENV !== 'production' && this.warnDuplicate(value) } } else if (Object.isExtensible(value)) { def(value, id, frag) } else if (process.env.NODE_ENV !== 'production') { warn( 'Frozen v-for objects cannot be automatically tracked, make sure to ' + 'provide a track-by key.' ) } } frag.raw = value }, /** * Get a cached fragment from the value/index/key * * @param {*} value * @param {Number} index * @param {String} key * @return {Fragment} */ getCachedFrag (value, index, key) { var trackByKey = this.params.trackBy var primitive = !isObject(value) var frag if (key || trackByKey || primitive) { var id = getTrackByKey(index, key, value, trackByKey) frag = this.cache[id] } else { frag = value[this.id] } if (frag && (frag.reused || frag.fresh)) { process.env.NODE_ENV !== 'production' && this.warnDuplicate(value) } return frag }, /** * Delete a fragment from cache. * * @param {Fragment} frag */ deleteCachedFrag (frag) { var value = frag.raw var trackByKey = this.params.trackBy var scope = frag.scope var index = scope.$index // fix #948: avoid accidentally fall through to // a parent repeater which happens to have $key. var key = hasOwn(scope, '$key') && scope.$key var primitive = !isObject(value) if (trackByKey || key || primitive) { var id = getTrackByKey(index, key, value, trackByKey) this.cache[id] = null } else { value[this.id] = null frag.raw = null } }, /** * Get the stagger amount for an insertion/removal. * * @param {Fragment} frag * @param {Number} index * @param {Number} total * @param {String} type */ getStagger (frag, index, total, type) { type = type + 'Stagger' var trans = frag.node.__v_trans var hooks = trans && trans.hooks var hook = hooks && (hooks[type] || hooks.stagger) return hook ? hook.call(frag, index, total) : index * parseInt(this.params[type] || this.params.stagger, 10) }, /** * Pre-process the value before piping it through the * filters. This is passed to and called by the watcher. */ _preProcess (value) { // regardless of type, store the un-filtered raw value. this.rawValue = value return value }, /** * Post-process the value after it has been piped through * the filters. This is passed to and called by the watcher. * * It is necessary for this to be called during the * watcher's dependency collection phase because we want * the v-for to update when the source Object is mutated. */ _postProcess (value) { if (isArray(value)) { return value } else if (isPlainObject(value)) { // convert plain object to array. var keys = Object.keys(value) var i = keys.length var res = new Array(i) var key while (i--) { key = keys[i] res[i] = { $key: key, $value: value[key] } } return res } else { if (typeof value === 'number' && !isNaN(value)) { value = range(value) } return value || [] } }, unbind () { if (this.descriptor.ref) { (this._scope || this.vm).$refs[this.descriptor.ref] = null } if (this.frags) { var i = this.frags.length var frag while (i--) { frag = this.frags[i] this.deleteCachedFrag(frag) frag.destroy() } } } } /** * Helper to find the previous element that is a fragment * anchor. This is necessary because a destroyed frag's * element could still be lingering in the DOM before its * leaving transition finishes, but its inserted flag * should have been set to false so we can skip them. * * If this is a block repeat, we want to make sure we only * return frag that is bound to this v-for. (see #929) * * @param {Fragment} frag * @param {Comment|Text} anchor * @param {String} id * @return {Fragment} */ function findPrevFrag (frag, anchor, id) { var el = frag.node.previousSibling /* istanbul ignore if */ if (!el) return frag = el.__v_frag while ( (!frag || frag.forId !== id || !frag.inserted) && el !== anchor ) { el = el.previousSibling /* istanbul ignore if */ if (!el) return frag = el.__v_frag } return frag } /** * Find a vm from a fragment. * * @param {Fragment} frag * @return {Vue|undefined} */ function findVmFromFrag (frag) { let node = frag.node // handle multi-node frag if (frag.end) { while (!node.__vue__ && node !== frag.end && node.nextSibling) { node = node.nextSibling } } return node.__vue__ } /** * Create a range array from given number. * * @param {Number} n * @return {Array} */ function range (n) { var i = -1 var ret = new Array(Math.floor(n)) while (++i < n) { ret[i] = i } return ret } /** * Get the track by key for an item. * * @param {Number} index * @param {String} key * @param {*} value * @param {String} [trackByKey] */ function getTrackByKey (index, key, value, trackByKey) { return trackByKey ? trackByKey === '$index' ? index : trackByKey.charAt(0).match(/\w/) ? getPath(value, trackByKey) : value[trackByKey] : (key || value) } if (process.env.NODE_ENV !== 'production') { vFor.warnDuplicate = function (value) { warn( 'Duplicate value found in v-for="' + this.descriptor.raw + '": ' + JSON.stringify(value) + '. Use track-by="$index" if ' + 'you are expecting duplicate values.', this.vm ) } } export default vFor