芝麻web文件管理V1.00
编辑当前文件:/home/digitalh/public_html/node_modules/alpinejs/src/directives/x-for.js
import { addScopeToNode, refreshScope } from '../scope' import { evaluateLater } from '../evaluator' import { directive } from '../directives' import { reactive } from '../reactivity' import { initTree } from '../lifecycle' import { mutateDom } from '../mutation' import { flushJobs } from '../scheduler' import { warn } from '../utils/warn' import { dequeueJob } from '../scheduler' directive('for', (el, { expression }, { effect, cleanup }) => { let iteratorNames = parseForExpression(expression) let evaluateItems = evaluateLater(el, iteratorNames.items) let evaluateKey = evaluateLater(el, // the x-bind:key expression is stored for our use instead of evaluated. el._x_keyExpression || 'index' ) el._x_prevKeys = [] el._x_lookup = {} effect(() => loop(el, iteratorNames, evaluateItems, evaluateKey)) cleanup(() => { Object.values(el._x_lookup).forEach(el => el.remove()) delete el._x_prevKeys delete el._x_lookup }) }) let shouldFastRender = true function loop(el, iteratorNames, evaluateItems, evaluateKey) { let isObject = i => typeof i === 'object' && ! Array.isArray(i) let templateEl = el evaluateItems(items => { // Prepare yourself. There's a lot going on here. Take heart, // every bit of complexity in this function was added for // the purpose of making Alpine fast with large datas. // Support number literals. Ex: x-for="i in 100" if (isNumeric(items) && items >= 0) { items = Array.from(Array(items).keys(), i => i + 1) } if (items === undefined) items = [] let lookup = el._x_lookup let prevKeys = el._x_prevKeys let scopes = [] let keys = [] // In order to preserve DOM elements (move instead of replace) // we need to generate all the keys for every iteration up // front. These will be our source of truth for diffing. if (isObject(items)) { items = Object.entries(items).map(([key, value]) => { let scope = getIterationScopeVariables(iteratorNames, value, key, items) evaluateKey(value => keys.push(value), { scope: { index: key, ...scope} }) scopes.push(scope) }) } else { for (let i = 0; i < items.length; i++) { let scope = getIterationScopeVariables(iteratorNames, items[i], i, items) evaluateKey(value => keys.push(value), { scope: { index: i, ...scope} }) scopes.push(scope) } } // Rather than making DOM manipulations inside one large loop, we'll // instead track which mutations need to be made in the following // arrays. After we're finished, we can batch them at the end. let adds = [] let moves = [] let removes = [] let sames = [] // First, we track elements that will need to be removed. for (let i = 0; i < prevKeys.length; i++) { let key = prevKeys[i] if (keys.indexOf(key) === -1) removes.push(key) } // Notice we're mutating prevKeys as we go. This makes it // so that we can efficiently make incremental comparisons. prevKeys = prevKeys.filter(key => ! removes.includes(key)) let lastKey = 'template' // This is the important part of the diffing algo. Identifying // which keys (future DOM elements) are new, which ones have // or haven't moved (noting where they moved to / from). for (let i = 0; i < keys.length; i++) { let key = keys[i] let prevIndex = prevKeys.indexOf(key) if (prevIndex === -1) { // New key found. prevKeys.splice(i, 0, key) adds.push([lastKey, i]) } else if (prevIndex !== i) { // A key has moved. let keyInSpot = prevKeys.splice(i, 1)[0] let keyForSpot = prevKeys.splice(prevIndex - 1, 1)[0] prevKeys.splice(i, 0, keyForSpot) prevKeys.splice(prevIndex, 0, keyInSpot) moves.push([keyInSpot, keyForSpot]) } else { // This key hasn't moved, but we'll still keep track // so that we can refresh it later on. sames.push(key) } lastKey = key } // Now that we've done the diffing work, we can apply the mutations // in batches for both separating types work and optimizing // for browser performance. // We'll remove all the nodes that need to be removed, // letting the mutation observer pick them up and // clean up any side effects they had. for (let i = 0; i < removes.length; i++) { let key = removes[i] // Remove any queued effects that might run after the DOM node has been removed. if (!! lookup[key]._x_effects) { lookup[key]._x_effects.forEach(dequeueJob) } lookup[key].remove() lookup[key] = null delete lookup[key] } // Here we'll move elements around, skipping // mutation observer triggers by using "mutateDom". for (let i = 0; i < moves.length; i++) { let [keyInSpot, keyForSpot] = moves[i] let elInSpot = lookup[keyInSpot] let elForSpot = lookup[keyForSpot] let marker = document.createElement('div') mutateDom(() => { elForSpot.after(marker) elInSpot.after(elForSpot) elForSpot._x_currentIfEl && elForSpot.after(elForSpot._x_currentIfEl) marker.before(elInSpot) elInSpot._x_currentIfEl && elInSpot.after(elInSpot._x_currentIfEl) marker.remove() }) refreshScope(elForSpot, scopes[keys.indexOf(keyForSpot)]) } // We can now create and add new elements. for (let i = 0; i < adds.length; i++) { let [lastKey, index] = adds[i] let lastEl = (lastKey === 'template') ? templateEl : lookup[lastKey] // If the element is a x-if template evaluated to true, // point lastEl to the if-generated node if (lastEl._x_currentIfEl) lastEl = lastEl._x_currentIfEl let scope = scopes[index] let key = keys[index] let clone = document.importNode(templateEl.content, true).firstElementChild addScopeToNode(clone, reactive(scope), templateEl) mutateDom(() => { lastEl.after(clone) initTree(clone) }) if (typeof key === 'object') { warn('x-for key cannot be an object, it must be a string or an integer', templateEl) } lookup[key] = clone } // If an element hasn't changed, we still want to "refresh" the // data it depends on in case the data has changed in an // "unobservable" way. for (let i = 0; i < sames.length; i++) { refreshScope(lookup[sames[i]], scopes[keys.indexOf(sames[i])]) } // Now we'll log the keys (and the order they're in) for comparing // against next time. templateEl._x_prevKeys = keys }) } // This was taken from VueJS 2.* core. Thanks Vue! function parseForExpression(expression) { let forIteratorRE = /,([^,\}\]]*)(?:,([^,\}\]]*))?$/ let stripParensRE = /^\s*\(|\)\s*$/g let forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/ let inMatch = expression.match(forAliasRE) if (! inMatch) return let res = {} res.items = inMatch[2].trim() let item = inMatch[1].replace(stripParensRE, '').trim() let iteratorMatch = item.match(forIteratorRE) if (iteratorMatch) { res.item = item.replace(forIteratorRE, '').trim() res.index = iteratorMatch[1].trim() if (iteratorMatch[2]) { res.collection = iteratorMatch[2].trim() } } else { res.item = item } return res } function getIterationScopeVariables(iteratorNames, item, index, items) { // We must create a new object, so each iteration has a new scope let scopeVariables = {} // Support array destructuring ([foo, bar]). if (/^\[.*\]$/.test(iteratorNames.item) && Array.isArray(item)) { let names = iteratorNames.item.replace('[', '').replace(']', '').split(',').map(i => i.trim()) names.forEach((name, i) => { scopeVariables[name] = item[i] }) // Support object destructuring ({ foo: 'oof', bar: 'rab' }). } else if (/^\{.*\}$/.test(iteratorNames.item) && ! Array.isArray(item) && typeof item === 'object') { let names = iteratorNames.item.replace('{', '').replace('}', '').split(',').map(i => i.trim()) names.forEach(name => { scopeVariables[name] = item[name] }) } else { scopeVariables[iteratorNames.item] = item } if (iteratorNames.index) scopeVariables[iteratorNames.index] = index if (iteratorNames.collection) scopeVariables[iteratorNames.collection] = items return scopeVariables } function isNumeric(subject){ return ! Array.isArray(subject) && ! isNaN(subject) }