vendor/assets/javascripts/bower/vue/src/directives/repeat.js in fluentd-ui-0.3.8 vs vendor/assets/javascripts/bower/vue/src/directives/repeat.js in fluentd-ui-0.3.9
- old
+ new
@@ -1,246 +1,497 @@
-var utils = require('../utils'),
- config = require('../config')
+var _ = require('../util')
+var isObject = _.isObject
+var textParser = require('../parsers/text')
+var expParser = require('../parsers/expression')
+var templateParser = require('../parsers/template')
+var compile = require('../compiler/compile')
+var transclude = require('../compiler/transclude')
+var mergeOptions = require('../util/merge-option')
+var uid = 0
-/**
- * Binding that manages VMs based on an Array
- */
module.exports = {
- bind: function () {
+ /**
+ * Setup.
+ */
- this.identifier = '$r' + this.id
+ bind: function () {
+ // uid as a cache identifier
+ this.id = '__v_repeat_' + (++uid)
+ // we need to insert the objToArray converter
+ // as the first read filter.
+ if (!this.filters) {
+ this.filters = {}
+ }
+ // add the object -> array convert filter
+ var objectConverter = _.bind(objToArray, this)
+ if (!this.filters.read) {
+ this.filters.read = [objectConverter]
+ } else {
+ this.filters.read.unshift(objectConverter)
+ }
+ // setup ref node
+ this.ref = document.createComment('v-repeat')
+ _.replace(this.el, this.ref)
+ // check if this is a block repeat
+ this.template = this.el.tagName === 'TEMPLATE'
+ ? templateParser.parse(this.el, true)
+ : this.el
+ // check other directives that need to be handled
+ // at v-repeat level
+ this.checkIf()
+ this.checkRef()
+ this.checkComponent()
+ // check for trackby param
+ this.idKey =
+ this._checkParam('track-by') ||
+ this._checkParam('trackby') // 0.11.0 compat
+ // cache for primitive value instances
+ this.cache = Object.create(null)
+ },
- // a hash to cache the same expressions on repeated instances
- // so they don't have to be compiled for every single instance
- this.expCache = utils.hash()
+ /**
+ * Warn against v-if usage.
+ */
- var el = this.el,
- ctn = this.container = el.parentNode
+ checkIf: function () {
+ if (_.attr(this.el, 'if') !== null) {
+ _.warn(
+ 'Don\'t use v-if with v-repeat. ' +
+ 'Use v-show or the "filterBy" filter instead.'
+ )
+ }
+ },
- // extract child Id, if any
- this.childId = this.compiler.eval(utils.attr(el, 'ref'))
+ /**
+ * Check if v-ref/ v-el is also present.
+ */
- // create a comment node as a reference node for DOM insertions
- this.ref = document.createComment(config.prefix + '-repeat-' + this.key)
- ctn.insertBefore(this.ref, el)
- ctn.removeChild(el)
+ checkRef: function () {
+ var childId = _.attr(this.el, 'ref')
+ this.childId = childId
+ ? this.vm.$interpolate(childId)
+ : null
+ var elId = _.attr(this.el, 'el')
+ this.elId = elId
+ ? this.vm.$interpolate(elId)
+ : null
+ },
- this.collection = null
- this.vms = null
+ /**
+ * Check the component constructor to use for repeated
+ * instances. If static we resolve it now, otherwise it
+ * needs to be resolved at build time with actual data.
+ */
- },
-
- update: function (collection) {
-
- if (!Array.isArray(collection)) {
- if (utils.isObject(collection)) {
- collection = utils.objectToArray(collection)
- } else {
- utils.warn('v-repeat only accepts Array or Object values.')
- }
+ checkComponent: function () {
+ var id = _.attr(this.el, 'component')
+ var options = this.vm.$options
+ if (!id) {
+ this.Ctor = _.Vue // default constructor
+ this.inherit = true // inline repeats should inherit
+ // important: transclude with no options, just
+ // to ensure block start and block end
+ this.template = transclude(this.template)
+ this._linker = compile(this.template, options)
+ } else {
+ var tokens = textParser.parse(id)
+ if (!tokens) { // static component
+ var Ctor = this.Ctor = options.components[id]
+ _.assertAsset(Ctor, 'component', id)
+ if (Ctor) {
+ // merge an empty object with owner vm as parent
+ // so child vms can access parent assets.
+ var merged = mergeOptions(
+ Ctor.options,
+ {},
+ { $parent: this.vm }
+ )
+ this.template = transclude(this.template, merged)
+ this._linker = compile(this.template, merged)
}
+ } else {
+ // to be resolved later
+ var ctorExp = textParser.tokensToExp(tokens)
+ this.ctorGetter = expParser.parse(ctorExp).get
+ }
+ }
+ },
- // keep reference of old data and VMs
- // so we can reuse them if possible
- this.oldVMs = this.vms
- this.oldCollection = this.collection
- collection = this.collection = collection || []
+ /**
+ * Update.
+ * This is called whenever the Array mutates.
+ *
+ * @param {Array} data
+ */
- var isObject = collection[0] && utils.isObject(collection[0])
- this.vms = this.oldCollection
- ? this.diff(collection, isObject)
- : this.init(collection, isObject)
+ update: function (data) {
+ if (typeof data === 'number') {
+ data = range(data)
+ }
+ this.vms = this.diff(data || [], this.vms)
+ // update v-ref
+ if (this.childId) {
+ this.vm.$[this.childId] = this.vms
+ }
+ if (this.elId) {
+ this.vm.$$[this.elId] = this.vms.map(function (vm) {
+ return vm.$el
+ })
+ }
+ },
- if (this.childId) {
- this.vm.$[this.childId] = this.vms
- }
+ /**
+ * 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
+ * @param {Array} oldVms
+ * @return {Array}
+ */
- },
-
- init: function (collection, isObject) {
- var vm, vms = []
- for (var i = 0, l = collection.length; i < l; i++) {
- vm = this.build(collection[i], i, isObject)
- vms.push(vm)
- if (this.compiler.init) {
- this.container.insertBefore(vm.$el, this.ref)
- } else {
- vm.$before(this.ref)
- }
+ diff: function (data, oldVms) {
+ var idKey = this.idKey
+ var converted = this.converted
+ var ref = this.ref
+ var alias = this.arg
+ var init = !oldVms
+ var vms = new Array(data.length)
+ var obj, raw, vm, i, l
+ // First pass, go through the new Array and fill up
+ // the new vms 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++) {
+ obj = data[i]
+ raw = converted ? obj.value : obj
+ vm = !init && this.getVm(raw)
+ if (vm) { // reusable instance
+ vm._reused = true
+ vm.$index = i // update $index
+ if (converted) {
+ vm.$key = obj.key // update $key
}
- return vms
- },
+ if (idKey) { // swap track by id data
+ if (alias) {
+ vm[alias] = raw
+ } else {
+ vm._setData(raw)
+ }
+ }
+ } else { // new instance
+ vm = this.build(obj, i)
+ vm._new = true
+ }
+ vms[i] = vm
+ // insert if this is first run
+ if (init) {
+ vm.$before(ref)
+ }
+ }
+ // if this is the first run, we're done.
+ if (init) {
+ return vms
+ }
+ // Second pass, go through the old vm instances and
+ // destroy those who are not reused (and remove them
+ // from cache)
+ for (i = 0, l = oldVms.length; i < l; i++) {
+ vm = oldVms[i]
+ if (!vm._reused) {
+ this.uncacheVm(vm)
+ vm.$destroy(true)
+ }
+ }
+ // final pass, move/insert new instances into the
+ // right place. We're going in reverse here because
+ // insertBefore relies on the next sibling to be
+ // resolved.
+ var targetNext, currentNext
+ i = vms.length
+ while (i--) {
+ vm = vms[i]
+ // this is the vm that we should be in front of
+ targetNext = vms[i + 1]
+ if (!targetNext) {
+ // This is the last item. If it's reused then
+ // everything else will eventually be in the right
+ // place, so no need to touch it. Otherwise, insert
+ // it.
+ if (!vm._reused) {
+ vm.$before(ref)
+ }
+ } else {
+ if (vm._reused) {
+ // this is the vm we are actually in front of
+ currentNext = findNextVm(vm, ref)
+ // we only need to move if we are not in the right
+ // place already.
+ if (currentNext !== targetNext) {
+ vm.$before(targetNext.$el, null, false)
+ }
+ } else {
+ // new instance, insert to existing next
+ vm.$before(targetNext.$el)
+ }
+ }
+ vm._new = false
+ vm._reused = false
+ }
+ return vms
+ },
- /**
- * Diff the new array with the old
- * and determine the minimum amount of DOM manipulations.
- */
- diff: function (newCollection, isObject) {
+ /**
+ * Build a new instance and cache it.
+ *
+ * @param {Object} data
+ * @param {Number} index
+ */
- var i, l, item, vm,
- oldIndex,
- targetNext,
- currentNext,
- nextEl,
- ctn = this.container,
- oldVMs = this.oldVMs,
- vms = []
+ build: function (data, index) {
+ var original = data
+ var meta = { $index: index }
+ if (this.converted) {
+ meta.$key = original.key
+ }
+ var raw = this.converted ? data.value : data
+ var alias = this.arg
+ var hasAlias = !isObject(raw) || alias
+ // wrap the raw data with alias
+ data = hasAlias ? {} : raw
+ if (alias) {
+ data[alias] = raw
+ } else if (hasAlias) {
+ meta.$value = raw
+ }
+ // resolve constructor
+ var Ctor = this.Ctor || this.resolveCtor(data, meta)
+ var vm = this.vm.$addChild({
+ el: templateParser.clone(this.template),
+ _linker: this._linker,
+ _meta: meta,
+ data: data,
+ inherit: this.inherit
+ }, Ctor)
+ // cache instance
+ this.cacheVm(raw, vm)
+ return vm
+ },
- vms.length = newCollection.length
+ /**
+ * Resolve a contructor to use for an instance.
+ * The tricky part here is that there could be dynamic
+ * components depending on instance data.
+ *
+ * @param {Object} data
+ * @param {Object} meta
+ * @return {Function}
+ */
- // first pass, collect new reused and new created
- for (i = 0, l = newCollection.length; i < l; i++) {
- item = newCollection[i]
- if (isObject) {
- item.$index = i
- if (item.__emitter__ && item.__emitter__[this.identifier]) {
- // this piece of data is being reused.
- // record its final position in reused vms
- item.$reused = true
- } else {
- vms[i] = this.build(item, i, isObject)
- }
- } else {
- // we can't attach an identifier to primitive values
- // so have to do an indexOf...
- oldIndex = indexOf(oldVMs, item)
- if (oldIndex > -1) {
- // record the position on the existing vm
- oldVMs[oldIndex].$reused = true
- oldVMs[oldIndex].$data.$index = i
- } else {
- vms[i] = this.build(item, i, isObject)
- }
- }
- }
+ resolveCtor: function (data, meta) {
+ // create a temporary context object and copy data
+ // and meta properties onto it.
+ // use _.define to avoid accidentally overwriting scope
+ // properties.
+ var context = Object.create(this.vm)
+ var key
+ for (key in data) {
+ _.define(context, key, data[key])
+ }
+ for (key in meta) {
+ _.define(context, key, meta[key])
+ }
+ var id = this.ctorGetter.call(context, context)
+ var Ctor = this.vm.$options.components[id]
+ _.assertAsset(Ctor, 'component', id)
+ return Ctor
+ },
- // second pass, collect old reused and destroy unused
- for (i = 0, l = oldVMs.length; i < l; i++) {
- vm = oldVMs[i]
- item = this.arg
- ? vm.$data[this.arg]
- : vm.$data
- if (item.$reused) {
- vm.$reused = true
- delete item.$reused
- }
- if (vm.$reused) {
- // update the index to latest
- vm.$index = item.$index
- // the item could have had a new key
- if (item.$key && item.$key !== vm.$key) {
- vm.$key = item.$key
- }
- vms[vm.$index] = vm
- } else {
- // this one can be destroyed.
- if (item.__emitter__) {
- delete item.__emitter__[this.identifier]
- }
- vm.$destroy()
- }
- }
+ /**
+ * Unbind, teardown everything
+ */
- // final pass, move/insert DOM elements
- i = vms.length
- while (i--) {
- vm = vms[i]
- item = vm.$data
- targetNext = vms[i + 1]
- if (vm.$reused) {
- nextEl = vm.$el.nextSibling
- // destroyed VMs' element might still be in the DOM
- // due to transitions
- while (!nextEl.vue_vm && nextEl !== this.ref) {
- nextEl = nextEl.nextSibling
- }
- currentNext = nextEl.vue_vm
- if (currentNext !== targetNext) {
- if (!targetNext) {
- ctn.insertBefore(vm.$el, this.ref)
- } else {
- nextEl = targetNext.$el
- // new VMs' element might not be in the DOM yet
- // due to transitions
- while (!nextEl.parentNode) {
- targetNext = vms[nextEl.vue_vm.$index + 1]
- nextEl = targetNext
- ? targetNext.$el
- : this.ref
- }
- ctn.insertBefore(vm.$el, nextEl)
- }
- }
- delete vm.$reused
- delete item.$index
- delete item.$key
- } else { // a new vm
- vm.$before(targetNext ? targetNext.$el : this.ref)
- }
- }
+ unbind: function () {
+ if (this.childId) {
+ delete this.vm.$[this.childId]
+ }
+ if (this.vms) {
+ var i = this.vms.length
+ var vm
+ while (i--) {
+ vm = this.vms[i]
+ this.uncacheVm(vm)
+ vm.$destroy()
+ }
+ }
+ },
- return vms
- },
+ /**
+ * Cache a vm instance based on its data.
+ *
+ * If the data is an object, we save the vm's reference on
+ * the data object as a hidden property. Otherwise we
+ * cache them in an object and for each primitive value
+ * there is an array in case there are duplicates.
+ *
+ * @param {Object} data
+ * @param {Vue} vm
+ */
- build: function (data, index, isObject) {
-
- // wrap non-object values
- var raw, alias,
- wrap = !isObject || this.arg
- if (wrap) {
- raw = data
- alias = this.arg || '$value'
- data = {}
- data[alias] = raw
+ cacheVm: function (data, vm) {
+ var idKey = this.idKey
+ var cache = this.cache
+ var id
+ if (idKey) {
+ id = data[idKey]
+ if (!cache[id]) {
+ cache[id] = vm
+ } else {
+ _.warn('Duplicate ID in v-repeat: ' + id)
+ }
+ } else if (isObject(data)) {
+ id = this.id
+ if (data.hasOwnProperty(id)) {
+ if (data[id] === null) {
+ data[id] = vm
+ } else {
+ _.warn(
+ 'Duplicate objects are not supported in v-repeat.'
+ )
}
- data.$index = index
+ } else {
+ _.define(data, this.id, vm)
+ }
+ } else {
+ if (!cache[data]) {
+ cache[data] = [vm]
+ } else {
+ cache[data].push(vm)
+ }
+ }
+ vm._raw = data
+ },
- var el = this.el.cloneNode(true),
- Ctor = this.compiler.resolveComponent(el, data),
- vm = new Ctor({
- el: el,
- data: data,
- parent: this.vm,
- compilerOptions: {
- repeat: true,
- expCache: this.expCache
- }
- })
+ /**
+ * Try to get a cached instance from a piece of data.
+ *
+ * @param {Object} data
+ * @return {Vue|undefined}
+ */
- if (isObject) {
- // attach an ienumerable identifier to the raw data
- (raw || data).__emitter__[this.identifier] = true
+ getVm: function (data) {
+ if (this.idKey) {
+ return this.cache[data[this.idKey]]
+ } else if (isObject(data)) {
+ return data[this.id]
+ } else {
+ var cached = this.cache[data]
+ if (cached) {
+ var i = 0
+ var vm = cached[i]
+ // since duplicated vm instances might be a reused
+ // one OR a newly created one, we need to return the
+ // first instance that is neither of these.
+ while (vm && (vm._reused || vm._new)) {
+ vm = cached[++i]
}
-
return vm
+ }
+ }
+ },
- },
+ /**
+ * Delete a cached vm instance.
+ *
+ * @param {Vue} vm
+ */
- unbind: function () {
- if (this.childId) {
- delete this.vm.$[this.childId]
- }
- if (this.vms) {
- var i = this.vms.length
- while (i--) {
- this.vms[i].$destroy()
- }
- }
+ uncacheVm: function (vm) {
+ var data = vm._raw
+ if (this.idKey) {
+ this.cache[data[this.idKey]] = null
+ } else if (isObject(data)) {
+ data[this.id] = null
+ vm._raw = null
+ } else {
+ this.cache[data].pop()
}
+ }
+
}
-// Helpers --------------------------------------------------------------------
+/**
+ * Helper to find the next element that is an instance
+ * root node. This is necessary because a destroyed vm's
+ * element could still be lingering in the DOM before its
+ * leaving transition finishes, but its __vue__ reference
+ * should have been removed so we can skip them.
+ *
+ * @param {Vue} vm
+ * @param {CommentNode} ref
+ * @return {Vue}
+ */
+function findNextVm (vm, ref) {
+ var el = (vm._blockEnd || vm.$el).nextSibling
+ while (!el.__vue__ && el !== ref) {
+ el = el.nextSibling
+ }
+ return el.__vue__
+}
+
/**
- * Find an object or a wrapped data object
- * from an Array
+ * Attempt to convert non-Array objects to array.
+ * This is the default filter installed to every v-repeat
+ * directive.
+ *
+ * It will be called with **the directive** as `this`
+ * context so that we can mark the repeat array as converted
+ * from an object.
+ *
+ * @param {*} obj
+ * @return {Array}
+ * @private
*/
-function indexOf (vms, obj) {
- for (var vm, i = 0, l = vms.length; i < l; i++) {
- vm = vms[i]
- if (!vm.$reused && vm.$value === obj) {
- return i
- }
+
+function objToArray (obj) {
+ if (!_.isPlainObject(obj)) {
+ return obj
+ }
+ var keys = Object.keys(obj)
+ var i = keys.length
+ var res = new Array(i)
+ var key
+ while (i--) {
+ key = keys[i]
+ res[i] = {
+ key: key,
+ value: obj[key]
}
- return -1
+ }
+ // `this` points to the repeat directive instance
+ this.converted = true
+ return res
+}
+
+/**
+ * Create a range array from given number.
+ *
+ * @param {Number} n
+ * @return {Array}
+ */
+
+function range (n) {
+ var i = -1
+ var ret = new Array(n)
+ while (++i < n) {
+ ret[i] = i
+ }
+ return ret
}
\ No newline at end of file