diff --git a/bi/core.js b/bi/core.js index 799522e8c4..1721b3f1fb 100644 --- a/bi/core.js +++ b/bi/core.js @@ -11527,31 +11527,192 @@ BI.Layout = BI.inherit(BI.Widget, { }) }, - update: function (opt) { - var o = this.options; - var items = opt.items || []; - var updated, i, len; - for (i = 0, len = Math.min(o.items.length, items.length); i < len; i++) { - if (!this._compare(o.items[i], items[i])) { - updated = this.updateItemAt(i, items[i]) || updated; + patchItem: function (oldVnode, vnode, index) { + if (!this._compare(oldVnode, vnode)) { + return this.updateItemAt(index, vnode); + } + }, + + updateChildren: function (oldCh, newCh) { + var self = this; + var oldStartIdx = 0, newStartIdx = 0; + var oldEndIdx = oldCh.length - 1; + var oldStartVnode = oldCh[0]; + var oldEndVnode = oldCh[oldEndIdx]; + var newEndIdx = newCh.length - 1; + var newStartVnode = newCh[0]; + var newEndVnode = newCh[newEndIdx]; + var oldKeyToIdx; + var idxInOld; + var elmToMove; + var before; + var updated; + var children = {}; + BI.each(oldCh, function (i, child) { + child = self._getOptions(child); + var key = child.key == null ? i : child.key; + if (BI.isKey(key)) { + children[key] = self._children[self._getChildName(i)]; + } + }); + + while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { + if (BI.isNull(oldStartVnode)) { + oldStartVnode = oldCh[++oldStartIdx]; + } else if (BI.isNull(oldEndVnode)) { + oldEndVnode = oldCh[--oldEndIdx]; + } else if (sameVnode(oldStartVnode, newStartVnode, oldStartIdx, newStartIdx)) { + updated = this.patchItem(oldStartVnode, newStartVnode, oldStartIdx) || updated; + oldStartVnode = oldCh[++oldStartIdx]; + newStartVnode = newCh[++newStartIdx]; + } else if (sameVnode(oldEndVnode, newEndVnode, oldEndIdx, newEndIdx)) { + updated = this.patchItem(oldEndVnode, newEndVnode, oldEndIdx) || updated; + oldEndVnode = oldCh[--oldEndIdx]; + newEndVnode = newCh[--newEndIdx]; + } else if (sameVnode(oldStartVnode, newEndVnode)) { + updated = this.patchItem(oldStartVnode, newEndVnode, oldStartIdx) || updated; + insertBefore(oldStartVnode, oldEndVnode, true); + oldStartVnode = oldCh[++oldStartIdx]; + newEndVnode = newCh[--newEndIdx]; + } else if (sameVnode(oldEndVnode, newStartVnode)) { + updated = this.patchItem(oldEndVnode, newStartVnode, oldEndIdx) || updated; + insertBefore(oldEndVnode, oldStartVnode); + oldEndVnode = oldCh[--oldEndIdx]; + newStartVnode = newCh[++newStartIdx]; + } else { + if (oldKeyToIdx === undefined) { + oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); + } + idxInOld = oldKeyToIdx[newStartVnode.key]; + if (BI.isNull(idxInOld)) { + var node = addNode(newStartVnode); + insertBefore(node, oldStartVnode); + newStartVnode = newCh[++newStartIdx]; + } else { + elmToMove = oldCh[idxInOld]; + var node = addNode(newStartVnode); + insertBefore(node, oldStartVnode); + // if (elmToMove.sel !== newStartVnode.sel) { + // api.insertBefore(parentElm, createElm(newStartVnode), oldStartVnode.elm); + // } else { + // updated = this.patchItem(elmToMove, newStartVnode, idxInOld) || updated; + // oldCh[idxInOld] = undefined; + // api.insertBefore(parentElm, (elmToMove.elm), oldStartVnode.elm); + // } + newStartVnode = newCh[++newStartIdx]; + } } } - if (o.items.length > items.length) { - var deleted = []; - for (i = items.length; i < o.items.length; i++) { - deleted.push(this._children[this._getChildName(i)]); - delete this._children[this._getChildName(i)]; + if (oldStartIdx > oldEndIdx) { + before = BI.isNull(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm; + addVnodes(before, newCh, newStartIdx, newEndIdx); + } else if (newStartIdx > newEndIdx) { + removeVnodes(oldCh, oldStartIdx, oldEndIdx); + } + + this._children = {}; + BI.each(newCh, function (i, child) { + var node = self._getOptions(child); + var key = node.key == null ? i : node.key; + self._children[self._getChildName(i)] = children[key]; + }); + + function sameVnode(vnode1, vnode2, oldIndex, newIndex) { + vnode1 = self._getOptions(vnode1); + vnode2 = self._getOptions(vnode2); + if (BI.isKey(vnode1.key)) { + return vnode1.key === vnode2.key; } - o.items.splice(items.length); - BI.each(deleted, function (i, w) { - w._destroy(); - }) - } else if (items.length > o.items.length) { - for (i = o.items.length; i < items.length; i++) { - this.addItemAt(i, items[i]); + if (oldIndex >= 0) { + return oldIndex === newIndex + } + } + + function addNode(vnode, index) { + var opt = self._getOptions(vnode); + var key = opt.key == null ? i : opt.key; + return children[key] = self._addElement(key, vnode); + } + + function addVnodes(before, vnodes, startIdx, endIdx) { + for (; startIdx <= endIdx; ++startIdx) { + var node = addNode(vnodes[startIdx], startIdx); + insertBefore(node, before); + } + } + + function removeVnodes(vnodes, startIdx, endIdx) { + for (; startIdx <= endIdx; ++startIdx) { + var node = self._getOptions(vnodes[startIdx]); + var key = node.key == null ? startIdx : node.key; + children[key]._destroy(); + } + } + + function insertBefore(insert, before, isNext) { + insert = self._getOptions(insert); + before = before && self._getOptions(before); + if (BI.isKey(insert.key)) { + if (before && children[before.key]) { + var next; + if (isNext) { + next = children[before.key].element.next(); + } else { + next = children[before.key].element; + } + if (next.length > 0) { + next.before(children[insert.key].element); + } else { + self._getWrapper().append(children[insert.key].element); + } + } else { + self._getWrapper().append(children[insert.key].element); + } + } else { + throw "key is not defined"; + } + } + + function createKeyToOldIdx(children, beginIdx, endIdx) { + var i, map = {}, key; + for (i = beginIdx; i <= endIdx; ++i) { + key = children[i].key; + if (key !== undefined) map[key] = i; } + return map; } + + return updated; + }, + + update: function (opt) { + var o = this.options; + var items = opt.items || []; + var updated = this.updateChildren(o.items, items); + this.options.items = items; return updated; + // var updated, i, len; + // for (i = 0, len = Math.min(o.items.length, items.length); i < len; i++) { + // if (!this._compare(o.items[i], items[i])) { + // updated = this.updateItemAt(i, items[i]) || updated; + // } + // } + // if (o.items.length > items.length) { + // var deleted = []; + // for (i = items.length; i < o.items.length; i++) { + // deleted.push(this._children[this._getChildName(i)]); + // delete this._children[this._getChildName(i)]; + // } + // o.items.splice(items.length); + // BI.each(deleted, function (i, w) { + // w._destroy(); + // }) + // } else if (items.length > o.items.length) { + // for (i = o.items.length; i < items.length; i++) { + // this.addItemAt(i, items[i]); + // } + // } + // return updated; }, stroke: function (items) { diff --git a/demo/js/core/abstract/demo.virtual_group.js b/demo/js/core/abstract/demo.virtual_group.js index 47d61f7f2d..00b38da352 100644 --- a/demo/js/core/abstract/demo.virtual_group.js +++ b/demo/js/core/abstract/demo.virtual_group.js @@ -4,19 +4,28 @@ Demo.Func = BI.inherit(BI.Widget, { }, _createItems: function () { - var items = BI.makeArray(1000, { - type: "demo.virtual_group_item" + var items = BI.map(BI.range(1000), function (i) { + return { + type: "demo.virtual_group_item", + key: i + 1 + } }); - items[0].value = BI.UUID(); return items; }, render: function () { var self = this; + var buttonGroupItems = self._createItems(); + var virtualGroupItems = self._createItems(); return { type: "bi.vertical", vgap: 20, items: [{ + type: "bi.label", + cls: "layout-bg5", + height: 50, + text: "共1000个元素,演示button_group和virtual_group每次删除第一个元素" + }, { type: "bi.button_group", width: 500, height: 300, @@ -26,17 +35,14 @@ Demo.Func = BI.inherit(BI.Widget, { chooseType: BI.ButtonGroup.CHOOSE_TYPE_MULTI, layouts: [{ type: "bi.vertical" - }, { - type: "bi.center_adapt", }], items: this._createItems() }, { type: "bi.button", text: "演示button_group的刷新", handler: function () { - var items = self._createItems(); - items.pop(); - self.buttonGroup.populate(items); + buttonGroupItems.shift(); + self.buttonGroup.populate(BI.deepClone(buttonGroupItems)); } }, { type: "bi.virtual_group", @@ -48,17 +54,14 @@ Demo.Func = BI.inherit(BI.Widget, { chooseType: BI.ButtonGroup.CHOOSE_TYPE_MULTI, layouts: [{ type: "bi.vertical" - }, { - type: "bi.center_adapt", }], items: this._createItems() }, { type: "bi.button", text: "演示virtual_group的刷新", handler: function () { - var items = self._createItems(); - items.pop(); - self.virtualGroup.populate(items); + virtualGroupItems.shift(); + self.virtualGroup.populate(BI.deepClone(virtualGroupItems)); } }] @@ -74,14 +77,14 @@ Demo.Item = BI.inherit(BI.Widget, { }, render: function () { - var self = this; + var self = this, o = this.options; return { type: "bi.label", ref: function () { self.label = this; }, height: this.options.height, - text: "这是一个测试项" + BI.UUID() + text: "key:" + o.key + ",随机数" + BI.UUID() } }, diff --git a/dist/bundle.js b/dist/bundle.js index 07f8cee693..a9606b60bc 100644 --- a/dist/bundle.js +++ b/dist/bundle.js @@ -19883,31 +19883,192 @@ BI.Layout = BI.inherit(BI.Widget, { }) }, - update: function (opt) { - var o = this.options; - var items = opt.items || []; - var updated, i, len; - for (i = 0, len = Math.min(o.items.length, items.length); i < len; i++) { - if (!this._compare(o.items[i], items[i])) { - updated = this.updateItemAt(i, items[i]) || updated; + patchItem: function (oldVnode, vnode, index) { + if (!this._compare(oldVnode, vnode)) { + return this.updateItemAt(index, vnode); + } + }, + + updateChildren: function (oldCh, newCh) { + var self = this; + var oldStartIdx = 0, newStartIdx = 0; + var oldEndIdx = oldCh.length - 1; + var oldStartVnode = oldCh[0]; + var oldEndVnode = oldCh[oldEndIdx]; + var newEndIdx = newCh.length - 1; + var newStartVnode = newCh[0]; + var newEndVnode = newCh[newEndIdx]; + var oldKeyToIdx; + var idxInOld; + var elmToMove; + var before; + var updated; + var children = {}; + BI.each(oldCh, function (i, child) { + child = self._getOptions(child); + var key = child.key == null ? i : child.key; + if (BI.isKey(key)) { + children[key] = self._children[self._getChildName(i)]; + } + }); + + while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { + if (BI.isNull(oldStartVnode)) { + oldStartVnode = oldCh[++oldStartIdx]; + } else if (BI.isNull(oldEndVnode)) { + oldEndVnode = oldCh[--oldEndIdx]; + } else if (sameVnode(oldStartVnode, newStartVnode, oldStartIdx, newStartIdx)) { + updated = this.patchItem(oldStartVnode, newStartVnode, oldStartIdx) || updated; + oldStartVnode = oldCh[++oldStartIdx]; + newStartVnode = newCh[++newStartIdx]; + } else if (sameVnode(oldEndVnode, newEndVnode, oldEndIdx, newEndIdx)) { + updated = this.patchItem(oldEndVnode, newEndVnode, oldEndIdx) || updated; + oldEndVnode = oldCh[--oldEndIdx]; + newEndVnode = newCh[--newEndIdx]; + } else if (sameVnode(oldStartVnode, newEndVnode)) { + updated = this.patchItem(oldStartVnode, newEndVnode, oldStartIdx) || updated; + insertBefore(oldStartVnode, oldEndVnode, true); + oldStartVnode = oldCh[++oldStartIdx]; + newEndVnode = newCh[--newEndIdx]; + } else if (sameVnode(oldEndVnode, newStartVnode)) { + updated = this.patchItem(oldEndVnode, newStartVnode, oldEndIdx) || updated; + insertBefore(oldEndVnode, oldStartVnode); + oldEndVnode = oldCh[--oldEndIdx]; + newStartVnode = newCh[++newStartIdx]; + } else { + if (oldKeyToIdx === undefined) { + oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); + } + idxInOld = oldKeyToIdx[newStartVnode.key]; + if (BI.isNull(idxInOld)) { + var node = addNode(newStartVnode); + insertBefore(node, oldStartVnode); + newStartVnode = newCh[++newStartIdx]; + } else { + elmToMove = oldCh[idxInOld]; + var node = addNode(newStartVnode); + insertBefore(node, oldStartVnode); + // if (elmToMove.sel !== newStartVnode.sel) { + // api.insertBefore(parentElm, createElm(newStartVnode), oldStartVnode.elm); + // } else { + // updated = this.patchItem(elmToMove, newStartVnode, idxInOld) || updated; + // oldCh[idxInOld] = undefined; + // api.insertBefore(parentElm, (elmToMove.elm), oldStartVnode.elm); + // } + newStartVnode = newCh[++newStartIdx]; + } + } + } + if (oldStartIdx > oldEndIdx) { + before = BI.isNull(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm; + addVnodes(before, newCh, newStartIdx, newEndIdx); + } else if (newStartIdx > newEndIdx) { + removeVnodes(oldCh, oldStartIdx, oldEndIdx); + } + + this._children = {}; + BI.each(newCh, function (i, child) { + var node = self._getOptions(child); + var key = node.key == null ? i : node.key; + self._children[self._getChildName(i)] = children[key]; + }); + + function sameVnode(vnode1, vnode2, oldIndex, newIndex) { + vnode1 = self._getOptions(vnode1); + vnode2 = self._getOptions(vnode2); + if (BI.isKey(vnode1.key)) { + return vnode1.key === vnode2.key; + } + if (oldIndex >= 0) { + return oldIndex === newIndex } } - if (o.items.length > items.length) { - var deleted = []; - for (i = items.length; i < o.items.length; i++) { - deleted.push(this._children[this._getChildName(i)]); - delete this._children[this._getChildName(i)]; + + function addNode(vnode, index) { + var opt = self._getOptions(vnode); + var key = opt.key == null ? i : opt.key; + return children[key] = self._addElement(key, vnode); + } + + function addVnodes(before, vnodes, startIdx, endIdx) { + for (; startIdx <= endIdx; ++startIdx) { + var node = addNode(vnodes[startIdx], startIdx); + insertBefore(node, before); } - o.items.splice(items.length); - BI.each(deleted, function (i, w) { - w._destroy(); - }) - } else if (items.length > o.items.length) { - for (i = o.items.length; i < items.length; i++) { - this.addItemAt(i, items[i]); + } + + function removeVnodes(vnodes, startIdx, endIdx) { + for (; startIdx <= endIdx; ++startIdx) { + var node = self._getOptions(vnodes[startIdx]); + var key = node.key == null ? startIdx : node.key; + children[key]._destroy(); + } + } + + function insertBefore(insert, before, isNext) { + insert = self._getOptions(insert); + before = before && self._getOptions(before); + if (BI.isKey(insert.key)) { + if (before && children[before.key]) { + var next; + if (isNext) { + next = children[before.key].element.next(); + } else { + next = children[before.key].element; + } + if (next.length > 0) { + next.before(children[insert.key].element); + } else { + self._getWrapper().append(children[insert.key].element); + } + } else { + self._getWrapper().append(children[insert.key].element); + } + } else { + throw "key is not defined"; + } + } + + function createKeyToOldIdx(children, beginIdx, endIdx) { + var i, map = {}, key; + for (i = beginIdx; i <= endIdx; ++i) { + key = children[i].key; + if (key !== undefined) map[key] = i; } + return map; } + + return updated; + }, + + update: function (opt) { + var o = this.options; + var items = opt.items || []; + var updated = this.updateChildren(o.items, items); + this.options.items = items; return updated; + // var updated, i, len; + // for (i = 0, len = Math.min(o.items.length, items.length); i < len; i++) { + // if (!this._compare(o.items[i], items[i])) { + // updated = this.updateItemAt(i, items[i]) || updated; + // } + // } + // if (o.items.length > items.length) { + // var deleted = []; + // for (i = items.length; i < o.items.length; i++) { + // deleted.push(this._children[this._getChildName(i)]); + // delete this._children[this._getChildName(i)]; + // } + // o.items.splice(items.length); + // BI.each(deleted, function (i, w) { + // w._destroy(); + // }) + // } else if (items.length > o.items.length) { + // for (i = o.items.length; i < items.length; i++) { + // this.addItemAt(i, items[i]); + // } + // } + // return updated; }, stroke: function (items) { diff --git a/dist/core.js b/dist/core.js index 4e8ecfca1b..07b47e5683 100644 --- a/dist/core.js +++ b/dist/core.js @@ -19832,31 +19832,192 @@ BI.Layout = BI.inherit(BI.Widget, { }) }, - update: function (opt) { - var o = this.options; - var items = opt.items || []; - var updated, i, len; - for (i = 0, len = Math.min(o.items.length, items.length); i < len; i++) { - if (!this._compare(o.items[i], items[i])) { - updated = this.updateItemAt(i, items[i]) || updated; + patchItem: function (oldVnode, vnode, index) { + if (!this._compare(oldVnode, vnode)) { + return this.updateItemAt(index, vnode); + } + }, + + updateChildren: function (oldCh, newCh) { + var self = this; + var oldStartIdx = 0, newStartIdx = 0; + var oldEndIdx = oldCh.length - 1; + var oldStartVnode = oldCh[0]; + var oldEndVnode = oldCh[oldEndIdx]; + var newEndIdx = newCh.length - 1; + var newStartVnode = newCh[0]; + var newEndVnode = newCh[newEndIdx]; + var oldKeyToIdx; + var idxInOld; + var elmToMove; + var before; + var updated; + var children = {}; + BI.each(oldCh, function (i, child) { + child = self._getOptions(child); + var key = child.key == null ? i : child.key; + if (BI.isKey(key)) { + children[key] = self._children[self._getChildName(i)]; + } + }); + + while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { + if (BI.isNull(oldStartVnode)) { + oldStartVnode = oldCh[++oldStartIdx]; + } else if (BI.isNull(oldEndVnode)) { + oldEndVnode = oldCh[--oldEndIdx]; + } else if (sameVnode(oldStartVnode, newStartVnode, oldStartIdx, newStartIdx)) { + updated = this.patchItem(oldStartVnode, newStartVnode, oldStartIdx) || updated; + oldStartVnode = oldCh[++oldStartIdx]; + newStartVnode = newCh[++newStartIdx]; + } else if (sameVnode(oldEndVnode, newEndVnode, oldEndIdx, newEndIdx)) { + updated = this.patchItem(oldEndVnode, newEndVnode, oldEndIdx) || updated; + oldEndVnode = oldCh[--oldEndIdx]; + newEndVnode = newCh[--newEndIdx]; + } else if (sameVnode(oldStartVnode, newEndVnode)) { + updated = this.patchItem(oldStartVnode, newEndVnode, oldStartIdx) || updated; + insertBefore(oldStartVnode, oldEndVnode, true); + oldStartVnode = oldCh[++oldStartIdx]; + newEndVnode = newCh[--newEndIdx]; + } else if (sameVnode(oldEndVnode, newStartVnode)) { + updated = this.patchItem(oldEndVnode, newStartVnode, oldEndIdx) || updated; + insertBefore(oldEndVnode, oldStartVnode); + oldEndVnode = oldCh[--oldEndIdx]; + newStartVnode = newCh[++newStartIdx]; + } else { + if (oldKeyToIdx === undefined) { + oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); + } + idxInOld = oldKeyToIdx[newStartVnode.key]; + if (BI.isNull(idxInOld)) { + var node = addNode(newStartVnode); + insertBefore(node, oldStartVnode); + newStartVnode = newCh[++newStartIdx]; + } else { + elmToMove = oldCh[idxInOld]; + var node = addNode(newStartVnode); + insertBefore(node, oldStartVnode); + // if (elmToMove.sel !== newStartVnode.sel) { + // api.insertBefore(parentElm, createElm(newStartVnode), oldStartVnode.elm); + // } else { + // updated = this.patchItem(elmToMove, newStartVnode, idxInOld) || updated; + // oldCh[idxInOld] = undefined; + // api.insertBefore(parentElm, (elmToMove.elm), oldStartVnode.elm); + // } + newStartVnode = newCh[++newStartIdx]; + } } } - if (o.items.length > items.length) { - var deleted = []; - for (i = items.length; i < o.items.length; i++) { - deleted.push(this._children[this._getChildName(i)]); - delete this._children[this._getChildName(i)]; + if (oldStartIdx > oldEndIdx) { + before = BI.isNull(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm; + addVnodes(before, newCh, newStartIdx, newEndIdx); + } else if (newStartIdx > newEndIdx) { + removeVnodes(oldCh, oldStartIdx, oldEndIdx); + } + + this._children = {}; + BI.each(newCh, function (i, child) { + var node = self._getOptions(child); + var key = node.key == null ? i : node.key; + self._children[self._getChildName(i)] = children[key]; + }); + + function sameVnode(vnode1, vnode2, oldIndex, newIndex) { + vnode1 = self._getOptions(vnode1); + vnode2 = self._getOptions(vnode2); + if (BI.isKey(vnode1.key)) { + return vnode1.key === vnode2.key; } - o.items.splice(items.length); - BI.each(deleted, function (i, w) { - w._destroy(); - }) - } else if (items.length > o.items.length) { - for (i = o.items.length; i < items.length; i++) { - this.addItemAt(i, items[i]); + if (oldIndex >= 0) { + return oldIndex === newIndex + } + } + + function addNode(vnode, index) { + var opt = self._getOptions(vnode); + var key = opt.key == null ? i : opt.key; + return children[key] = self._addElement(key, vnode); + } + + function addVnodes(before, vnodes, startIdx, endIdx) { + for (; startIdx <= endIdx; ++startIdx) { + var node = addNode(vnodes[startIdx], startIdx); + insertBefore(node, before); + } + } + + function removeVnodes(vnodes, startIdx, endIdx) { + for (; startIdx <= endIdx; ++startIdx) { + var node = self._getOptions(vnodes[startIdx]); + var key = node.key == null ? startIdx : node.key; + children[key]._destroy(); + } + } + + function insertBefore(insert, before, isNext) { + insert = self._getOptions(insert); + before = before && self._getOptions(before); + if (BI.isKey(insert.key)) { + if (before && children[before.key]) { + var next; + if (isNext) { + next = children[before.key].element.next(); + } else { + next = children[before.key].element; + } + if (next.length > 0) { + next.before(children[insert.key].element); + } else { + self._getWrapper().append(children[insert.key].element); + } + } else { + self._getWrapper().append(children[insert.key].element); + } + } else { + throw "key is not defined"; + } + } + + function createKeyToOldIdx(children, beginIdx, endIdx) { + var i, map = {}, key; + for (i = beginIdx; i <= endIdx; ++i) { + key = children[i].key; + if (key !== undefined) map[key] = i; } + return map; } + + return updated; + }, + + update: function (opt) { + var o = this.options; + var items = opt.items || []; + var updated = this.updateChildren(o.items, items); + this.options.items = items; return updated; + // var updated, i, len; + // for (i = 0, len = Math.min(o.items.length, items.length); i < len; i++) { + // if (!this._compare(o.items[i], items[i])) { + // updated = this.updateItemAt(i, items[i]) || updated; + // } + // } + // if (o.items.length > items.length) { + // var deleted = []; + // for (i = items.length; i < o.items.length; i++) { + // deleted.push(this._children[this._getChildName(i)]); + // delete this._children[this._getChildName(i)]; + // } + // o.items.splice(items.length); + // BI.each(deleted, function (i, w) { + // w._destroy(); + // }) + // } else if (items.length > o.items.length) { + // for (i = o.items.length; i < items.length; i++) { + // this.addItemAt(i, items[i]); + // } + // } + // return updated; }, stroke: function (items) { diff --git a/dist/demo.js b/dist/demo.js index 629102cd6e..37fc9005db 100644 --- a/dist/demo.js +++ b/dist/demo.js @@ -6872,19 +6872,28 @@ BI.shortcut("demo.list_view", Demo.Func);Demo.Func = BI.inherit(BI.Widget, { }, _createItems: function () { - var items = BI.makeArray(1000, { - type: "demo.virtual_group_item" + var items = BI.map(BI.range(1000), function (i) { + return { + type: "demo.virtual_group_item", + key: i + 1 + } }); - items[0].value = BI.UUID(); return items; }, render: function () { var self = this; + var buttonGroupItems = self._createItems(); + var virtualGroupItems = self._createItems(); return { type: "bi.vertical", vgap: 20, items: [{ + type: "bi.label", + cls: "layout-bg5", + height: 50, + text: "共1000个元素,演示button_group和virtual_group每次删除第一个元素" + }, { type: "bi.button_group", width: 500, height: 300, @@ -6894,17 +6903,14 @@ BI.shortcut("demo.list_view", Demo.Func);Demo.Func = BI.inherit(BI.Widget, { chooseType: BI.ButtonGroup.CHOOSE_TYPE_MULTI, layouts: [{ type: "bi.vertical" - }, { - type: "bi.center_adapt", }], items: this._createItems() }, { type: "bi.button", text: "演示button_group的刷新", handler: function () { - var items = self._createItems(); - items.pop(); - self.buttonGroup.populate(items); + buttonGroupItems.shift(); + self.buttonGroup.populate(BI.deepClone(buttonGroupItems)); } }, { type: "bi.virtual_group", @@ -6916,17 +6922,14 @@ BI.shortcut("demo.list_view", Demo.Func);Demo.Func = BI.inherit(BI.Widget, { chooseType: BI.ButtonGroup.CHOOSE_TYPE_MULTI, layouts: [{ type: "bi.vertical" - }, { - type: "bi.center_adapt", }], items: this._createItems() }, { type: "bi.button", text: "演示virtual_group的刷新", handler: function () { - var items = self._createItems(); - items.pop(); - self.virtualGroup.populate(items); + virtualGroupItems.shift(); + self.virtualGroup.populate(BI.deepClone(virtualGroupItems)); } }] @@ -6942,14 +6945,14 @@ Demo.Item = BI.inherit(BI.Widget, { }, render: function () { - var self = this; + var self = this, o = this.options; return { type: "bi.label", ref: function () { self.label = this; }, height: this.options.height, - text: "这是一个测试项" + BI.UUID() + text: "key:" + o.key + ",随机数" + BI.UUID() } }, diff --git a/src/core/wrapper/layout.js b/src/core/wrapper/layout.js index d4162a154a..f51e9eb0c1 100644 --- a/src/core/wrapper/layout.js +++ b/src/core/wrapper/layout.js @@ -347,31 +347,192 @@ BI.Layout = BI.inherit(BI.Widget, { }) }, - update: function (opt) { - var o = this.options; - var items = opt.items || []; - var updated, i, len; - for (i = 0, len = Math.min(o.items.length, items.length); i < len; i++) { - if (!this._compare(o.items[i], items[i])) { - updated = this.updateItemAt(i, items[i]) || updated; + patchItem: function (oldVnode, vnode, index) { + if (!this._compare(oldVnode, vnode)) { + return this.updateItemAt(index, vnode); + } + }, + + updateChildren: function (oldCh, newCh) { + var self = this; + var oldStartIdx = 0, newStartIdx = 0; + var oldEndIdx = oldCh.length - 1; + var oldStartVnode = oldCh[0]; + var oldEndVnode = oldCh[oldEndIdx]; + var newEndIdx = newCh.length - 1; + var newStartVnode = newCh[0]; + var newEndVnode = newCh[newEndIdx]; + var oldKeyToIdx; + var idxInOld; + var elmToMove; + var before; + var updated; + var children = {}; + BI.each(oldCh, function (i, child) { + child = self._getOptions(child); + var key = child.key == null ? i : child.key; + if (BI.isKey(key)) { + children[key] = self._children[self._getChildName(i)]; } + }); + + while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { + if (BI.isNull(oldStartVnode)) { + oldStartVnode = oldCh[++oldStartIdx]; + } else if (BI.isNull(oldEndVnode)) { + oldEndVnode = oldCh[--oldEndIdx]; + } else if (sameVnode(oldStartVnode, newStartVnode, oldStartIdx, newStartIdx)) { + updated = this.patchItem(oldStartVnode, newStartVnode, oldStartIdx) || updated; + oldStartVnode = oldCh[++oldStartIdx]; + newStartVnode = newCh[++newStartIdx]; + } else if (sameVnode(oldEndVnode, newEndVnode, oldEndIdx, newEndIdx)) { + updated = this.patchItem(oldEndVnode, newEndVnode, oldEndIdx) || updated; + oldEndVnode = oldCh[--oldEndIdx]; + newEndVnode = newCh[--newEndIdx]; + } else if (sameVnode(oldStartVnode, newEndVnode)) { + updated = this.patchItem(oldStartVnode, newEndVnode, oldStartIdx) || updated; + insertBefore(oldStartVnode, oldEndVnode, true); + oldStartVnode = oldCh[++oldStartIdx]; + newEndVnode = newCh[--newEndIdx]; + } else if (sameVnode(oldEndVnode, newStartVnode)) { + updated = this.patchItem(oldEndVnode, newStartVnode, oldEndIdx) || updated; + insertBefore(oldEndVnode, oldStartVnode); + oldEndVnode = oldCh[--oldEndIdx]; + newStartVnode = newCh[++newStartIdx]; + } else { + if (oldKeyToIdx === undefined) { + oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); + } + idxInOld = oldKeyToIdx[newStartVnode.key]; + if (BI.isNull(idxInOld)) { + var node = addNode(newStartVnode); + insertBefore(node, oldStartVnode); + newStartVnode = newCh[++newStartIdx]; + } else { + elmToMove = oldCh[idxInOld]; + var node = addNode(newStartVnode); + insertBefore(node, oldStartVnode); + // if (elmToMove.sel !== newStartVnode.sel) { + // api.insertBefore(parentElm, createElm(newStartVnode), oldStartVnode.elm); + // } else { + // updated = this.patchItem(elmToMove, newStartVnode, idxInOld) || updated; + // oldCh[idxInOld] = undefined; + // api.insertBefore(parentElm, (elmToMove.elm), oldStartVnode.elm); + // } + newStartVnode = newCh[++newStartIdx]; + } + } + } + if (oldStartIdx > oldEndIdx) { + before = BI.isNull(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm; + addVnodes(before, newCh, newStartIdx, newEndIdx); + } else if (newStartIdx > newEndIdx) { + removeVnodes(oldCh, oldStartIdx, oldEndIdx); } - if (o.items.length > items.length) { - var deleted = []; - for (i = items.length; i < o.items.length; i++) { - deleted.push(this._children[this._getChildName(i)]); - delete this._children[this._getChildName(i)]; + + this._children = {}; + BI.each(newCh, function (i, child) { + var node = self._getOptions(child); + var key = node.key == null ? i : node.key; + self._children[self._getChildName(i)] = children[key]; + }); + + function sameVnode(vnode1, vnode2, oldIndex, newIndex) { + vnode1 = self._getOptions(vnode1); + vnode2 = self._getOptions(vnode2); + if (BI.isKey(vnode1.key)) { + return vnode1.key === vnode2.key; } - o.items.splice(items.length); - BI.each(deleted, function (i, w) { - w._destroy(); - }) - } else if (items.length > o.items.length) { - for (i = o.items.length; i < items.length; i++) { - this.addItemAt(i, items[i]); + if (oldIndex >= 0) { + return oldIndex === newIndex } } + + function addNode(vnode, index) { + var opt = self._getOptions(vnode); + var key = opt.key == null ? i : opt.key; + return children[key] = self._addElement(key, vnode); + } + + function addVnodes(before, vnodes, startIdx, endIdx) { + for (; startIdx <= endIdx; ++startIdx) { + var node = addNode(vnodes[startIdx], startIdx); + insertBefore(node, before); + } + } + + function removeVnodes(vnodes, startIdx, endIdx) { + for (; startIdx <= endIdx; ++startIdx) { + var node = self._getOptions(vnodes[startIdx]); + var key = node.key == null ? startIdx : node.key; + children[key]._destroy(); + } + } + + function insertBefore(insert, before, isNext) { + insert = self._getOptions(insert); + before = before && self._getOptions(before); + if (BI.isKey(insert.key)) { + if (before && children[before.key]) { + var next; + if (isNext) { + next = children[before.key].element.next(); + } else { + next = children[before.key].element; + } + if (next.length > 0) { + next.before(children[insert.key].element); + } else { + self._getWrapper().append(children[insert.key].element); + } + } else { + self._getWrapper().append(children[insert.key].element); + } + } else { + throw "key is not defined"; + } + } + + function createKeyToOldIdx(children, beginIdx, endIdx) { + var i, map = {}, key; + for (i = beginIdx; i <= endIdx; ++i) { + key = children[i].key; + if (key !== undefined) map[key] = i; + } + return map; + } + + return updated; + }, + + update: function (opt) { + var o = this.options; + var items = opt.items || []; + var updated = this.updateChildren(o.items, items); + this.options.items = items; return updated; + // var updated, i, len; + // for (i = 0, len = Math.min(o.items.length, items.length); i < len; i++) { + // if (!this._compare(o.items[i], items[i])) { + // updated = this.updateItemAt(i, items[i]) || updated; + // } + // } + // if (o.items.length > items.length) { + // var deleted = []; + // for (i = items.length; i < o.items.length; i++) { + // deleted.push(this._children[this._getChildName(i)]); + // delete this._children[this._getChildName(i)]; + // } + // o.items.splice(items.length); + // BI.each(deleted, function (i, w) { + // w._destroy(); + // }) + // } else if (items.length > o.items.length) { + // for (i = o.items.length; i < items.length; i++) { + // this.addItemAt(i, items[i]); + // } + // } + // return updated; }, stroke: function (items) {