Files
2026-04-04 19:27:45 +08:00

829 lines
28 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(function(global) {
"use strict";
global.DataView = {
ChangeType: {
add: "add",
remove: "remove",
change: "change",
clear: "clear",
move: "move",
sort: "sort",
},
};
var childAnimeDuration = 120;
var parentAnimeDuration = 400;
function showItemAmine(node) {
return Windows.UI.Animation.runAsync(node, [
Windows.UI.Animation.Keyframes.Scale.up,
Windows.UI.Animation.Keyframes.Opacity.visible,
], childAnimeDuration);
}
function hideItemAmine(node) {
return Windows.UI.Animation.runAsync(node, [
Windows.UI.Animation.Keyframes.Scale.down,
Windows.UI.Animation.Keyframes.Opacity.hidden,
], childAnimeDuration);
}
function noAnime(node) {
return Promise.resolve(node);
}
function runShowAnime(node, enable) {
if (enable === void 0) enable = true;
if (!enable) return noAnime(node);
return showItemAmine(node);
}
function runHideAnime(node, enable) {
if (enable === void 0) enable = true;
if (!enable) return noAnime(node);
return hideItemAmine(node);
}
function updateItemAmine(node, updateCallback) {
return Windows.UI.Animation.runAsync(node, [
Windows.UI.Animation.Keyframes.Opacity.hidden,
Windows.UI.Animation.Keyframes.Scale.down
], 120).then(function() {
if (updateCallback && typeof updateCallback === 'function') {
updateCallback(node);
}
return Windows.UI.Animation.runAsync(node, [
Windows.UI.Animation.Keyframes.Opacity.visible,
Windows.UI.Animation.Keyframes.Scale.up
], 120);
}).then(function() {
return node;
});
}
function PMChangeEvent(type, datas, detailOperation) {
this.type = type; // ChangeType
this.datas = datas || []; // 受影响的数据
this.detail = detailOperation || null;
}
function PMDataSource() {
var _list = [];
var _listeners = [];
var _keySelector = null;
var _autoKeySeed = 1;
this.setKeySelector = function(fn) {
_keySelector = (typeof fn === "function") ? fn : null;
};
function getKey(item) {
if (!item) return null;
// 用户提供
if (_keySelector) {
return _keySelector(item);
}
// 自动注入(对象)
if (typeof item === "object") {
if (item.__pm_key !== void 0) {
return item.__pm_key;
}
try {
Object.defineProperty(item, "__pm_key", {
value: "pm_" + (_autoKeySeed++),
enumerable: false
});
} catch (e) {
// IE10 兜底
item.__pm_key = "pm_" + (_autoKeySeed++);
}
return item.__pm_key;
}
// 原始类型兜底
return typeof item + ":" + item;
}
this.subscribe = function(fn) {
if (typeof fn === "function") {
_listeners.push(fn);
}
};
function emit(evt) {
for (var i = 0; i < _listeners.length; i++) {
_listeners[i](evt);
}
}
this.indexOf = function(item) {
var key = getKey(item);
for (var i = 0; i < _list.length; i++) {
if (getKey(_list[i]) === key) {
return i;
}
}
return -1;
};
this.add = function(item) {
_list.push(item);
emit(new PMChangeEvent(
DataView.ChangeType.add, [{ item: item, index: _list.length - 1, key: getKey(item) }]
));
};
this.removeAt = function(index) {
if (index < 0 || index >= _list.length) return;
var item = _list.splice(index, 1)[0];
emit(new PMChangeEvent(
DataView.ChangeType.remove, [{ item: item, index: index, key: getKey(item) }]
));
};
this.remove = function(item) {
var index = this.indexOf(item);
if (index >= 0) {
this.removeAt(index);
}
};
this.changeAt = function(index, newItem) {
if (index < 0 || index >= _list.length) return;
_list[index] = newItem;
emit(new PMChangeEvent(
DataView.ChangeType.change, [{ item: newItem, index: index, key: getKey(newItem) }]
));
};
this.change = function(oldItem, newItem) {
var index = this.indexOf(oldItem);
if (index >= 0) {
this.changeAt(index, newItem);
}
};
this.clear = function() {
_list.length = 0;
emit(new PMChangeEvent(
DataView.ChangeType.clear
));
};
this.move = function(from, to) {
if (from === to ||
from < 0 || to < 0 ||
from >= _list.length || to >= _list.length) {
return;
}
var item = _list.splice(from, 1)[0];
_list.splice(to, 0, item);
emit(new PMChangeEvent(
DataView.ChangeType.move, [item], { from: from, to: to }
));
};
this.sort = function(compareFn) {
_list.sort(compareFn);
emit(new PMChangeEvent(
DataView.ChangeType.sort,
_list.slice(0), { compare: compareFn }
));
};
this.get = function() {
return _list.slice(0);
};
this.addList = function(list, keySelector) {
if (!list || !list.length) return;
var added = [];
var changed = [];
var useKey = keySelector !== void 0;
var getKey;
if (keySelector === null) {
getKey = function(item) {
return item && item.id;
};
} else if (typeof keySelector === "function") {
getKey = keySelector;
}
for (var i = 0; i < list.length; i++) {
var item = list[i];
if (!useKey) {
_list.push(item);
added.push({ item: item, index: _list.length - 1 });
continue;
}
var key = getKey(item);
if (key === void 0) {
_list.push(item);
added.push({ item: item, index: _list.length - 1, key: key });
continue;
}
var found = -1;
for (var j = 0; j < _list.length; j++) {
if (getKey(_list[j]) === key) {
found = j;
break;
}
}
if (found >= 0) {
_list[found] = item;
changed.push({ item: item, index: found, key: key });
} else {
_list.push(item);
added.push({ item: item, index: _list.length - 1, key: key });
}
}
// 统一发出一个事件
if (added.length > 0) {
emit(new PMChangeEvent(DataView.ChangeType.add, added));
}
if (changed.length > 0) {
emit(new PMChangeEvent(DataView.ChangeType.change, changed));
}
};
this.updateList = function(list, fnGetKey) {
if (!list) list = [];
var getKey;
if (fnGetKey === null) {
getKey = function(item) {
return item && item.id;
};
} else if (typeof fnGetKey === "function") {
getKey = fnGetKey;
} else {
// 不提供 key直接整体替换
_list = list.slice(0);
emit(new PMChangeEvent(
DataView.ChangeType.clear
));
emit(new PMChangeEvent(
DataView.ChangeType.add,
list.map(function(item, index) {
return { item: item, index: index };
})
));
return;
}
var oldList = _list;
var newList = list;
var oldKeyIndex = {};
var newKeyIndex = {};
var i;
// 建立旧列表 key → index
for (i = 0; i < oldList.length; i++) {
var ok = getKey(oldList[i]);
if (ok !== void 0) {
oldKeyIndex[ok] = i;
}
}
// 建立新列表 key → index
for (i = 0; i < newList.length; i++) {
var nk = getKey(newList[i]);
if (nk !== void 0) {
newKeyIndex[nk] = i;
}
}
var added = [];
var changed = [];
var removed = [];
for (i = oldList.length - 1; i >= 0; i--) {
var oldItem = oldList[i];
var oldKey = getKey(oldItem);
if (oldKey === void 0 || newKeyIndex[oldKey] === void 0) {
removed.push({
item: oldItem,
index: i,
key: oldKey
});
}
}
for (i = 0; i < newList.length; i++) {
var newItem = newList[i];
var newKey = getKey(newItem);
if (newKey === void 0 || oldKeyIndex[newKey] === void 0) {
added.push({
item: newItem,
index: i,
key: newKey
});
} else {
var oldIndex = oldKeyIndex[newKey];
var oldItem2 = oldList[oldIndex];
if (oldItem2 !== newItem) {
changed.push({
item: newItem,
index: oldIndex,
key: newKey
});
}
}
}
if (removed.length > 0) {
for (i = 0; i < removed.length; i++) {
_list.splice(removed[i].index, 1);
}
emit(new PMChangeEvent(
DataView.ChangeType.remove,
removed
));
}
_list = newList.slice(0);
if (added.length > 0) {
emit(new PMChangeEvent(
DataView.ChangeType.add,
added
));
}
if (changed.length > 0) {
emit(new PMChangeEvent(
DataView.ChangeType.change,
changed
));
}
};
Object.defineProperty(this, "length", {
get: function() {
return _list.length;
},
enumerable: true,
});
this.getDatas = function() { return _list; }
this.forEach = function(callback, args) {
if (typeof callback !== "function") return;
for (var i = 0; i < _list.length; i++) {
callback.apply(this, [_list[i], i].concat(Array.prototype.slice.call(arguments, 1)));
}
};
this._getKey = getKey;
}
var MAX_ANIMATE_COUNT = 100;
function PMDataListView(container, templateFn) {
this.container = container;
this.templateFn = templateFn;
this.listViewControl = this;
this._emptyView = null;
// === 新增 ===
this._filter = null;
this._searchHandler = null;
this._searchText = null;
this._searchSuggestProvider = null;
this.onSearchSuggest = null; // function(text, list)
this._isSearching = false;
this.onsearchstart = null;
this.onsearchend = null;
}
PMDataListView.prototype.bind = function(ds) {
var self = this;
this._ds = ds;
self.container.innerHTML = "";
var items = ds.get();
// 动画队列,保证异步操作不会乱序
var queue = Promise.resolve();
function renderItem(data, index) {
var el = self.templateFn(data, index);
var key = ds && ds._getKey ? ds._getKey(data) : null;
el.__pm_item = data;
el.__pm_key = key;
if (key != null) {
el.setAttribute("data-pm-key", key);
}
el.addEventListener("click", function() {
self._toggleSelect(el);
});
return el;
}
// 初始化渲染
for (var i = 0; i < items.length; i++) {
self.container.appendChild(renderItem(items[i], i));
}
// 初始化 emptyView 状态
self._updateEmptyView();
ds.subscribe(function(evt) {
// 把每次事件放进队列,保证顺序执行
queue = queue.then(function() {
switch (evt.type) {
case DataView.ChangeType.add:
{
// evt.datas = [{item, index}, ...]
var datas = evt.datas;
// 先批量 append 到 DOM顺序必须保持
var nodes = [];
for (var i = 0; i < datas.length; i++) {
var n = renderItem(datas[i].item, datas[i].index);
n.style.display = "none";
nodes.push(n);
self.container.appendChild(n);
}
var enableAnime = datas.length <= MAX_ANIMATE_COUNT;
if (!enableAnime) {
return Promise.resolve();
}
// 如果数量>=20动画串行否则并行
if (datas.length <= 20) {
var promises = [];
for (var j = 0; j < nodes.length; j++) {
promises.push((function(node) {
node.style.display = "";
return showItemAmine(node);
})(nodes[j]));
}
return Promise.all(promises);
} else {
// 串行
var p = Promise.resolve();
var group = [];
for (var k = 0; k < nodes.length; k++) {
group.push((function(node) {
node.style.display = "";
return showItemAmine(node);
})
(nodes[k]));
if (group.length === 20 || k === nodes.length - 1) {
(function(g) {
p = p.then(function() {
return Promise.join(g);
});
})(group);
group = [];
}
}
(function(g) {
p = p.then(function() {
return Promise.join(g);
});
})(group);
return p;
}
}
case DataView.ChangeType.remove:
{
var info = evt.datas[0];
var node = self._findNodeByKey(info.key);
if (!node) return;
return hideItemAmine(node).then(function() {
self.container.removeChild(node);
});
}
case DataView.ChangeType.change:
{
var info = evt.datas[0];
var oldNode = self._findNodeByKey(info.key);
if (!oldNode) return;
return hideItemAmine(oldNode).then(function() {
var newNode = renderItem(info.item);
self.container.replaceChild(newNode, oldNode);
return showItemAmine(newNode);
});
}
case DataView.ChangeType.clear:
self.container.innerHTML = "";
return Promise.resolve();
case DataView.ChangeType.move:
{
var info = evt.datas[0];
var node = self._findNodeByKey(info.key);
if (!node) return;
var ref = self.container.children[evt.detail.to] || null;
self.container.insertBefore(node, ref);
return Promise.resolve();
}
case DataView.ChangeType.sort:
{
self.container.innerHTML = "";
for (var i = 0; i < evt.datas.length; i++) {
self.container.appendChild(renderItem(evt.datas[i], i));
}
return Promise.resolve();
}
}
promises.push(self._refreshVisibility());
return Promise.join(promises);
});
});
};
PMDataListView.prototype._findNodeByKey = function(key) {
if (key == null) return null;
var children = this.container.children;
for (var i = 0; i < children.length; i++) {
if (children[i].__pm_key === key) {
return children[i];
}
}
return null;
};
PMDataListView.prototype._toggleSelect = function(ele) {
// 如果选择模式为 none则不处理
if (this.selectionMode === "none") return;
var isSelected = ele.classList.contains("selected");
if (this.selectionMode === "single") {
// 单选:先取消所有选中
this._clearSelected();
if (!isSelected) {
ele.classList.add("selected");
}
} else if (this.selectionMode === "multiple") {
// 多选:点一次切换状态
if (isSelected) {
ele.classList.remove("selected");
} else {
ele.classList.add("selected");
}
}
};
PMDataListView.prototype._clearSelected = function() {
var selected = this.container.querySelectorAll(".selected");
for (var i = 0; i < selected.length; i++) {
selected[i].classList.remove("selected");
}
};
Object.defineProperty(PMDataListView.prototype, "selectionMode", {
get: function() {
return this._selectionMode || "none";
},
set: function(value) {
var mode = String(value).toLowerCase();
if (mode !== "none" && mode !== "single" && mode !== "multiple") {
mode = "none";
}
this._selectionMode = mode;
// 切换模式时,清空选中状态(可选)
if (mode === "none") {
this._clearSelected();
}
if (mode === "single") {
// 单选模式:如果多选了多个,保留第一个
var selected = this.container.querySelectorAll(".selected");
if (selected.length > 1) {
for (var i = 1; i < selected.length; i++) {
selected[i].classList.remove("selected");
}
}
}
}
});
Object.defineProperty(PMDataListView.prototype, "selectedItems", {
get: function() {
return Array.prototype.slice.call(this.container.querySelectorAll(".selected"));
}
});
PMDataListView.prototype._updateEmptyView = function() {
if (!this._emptyView) return;
var hasItem = this.container.children.length > 0;
var itemVisibleLength = 0;
for (var i = 0; i < this.container.children.length; i++) {
var child = this.container.children[i];
if (!child) continue;
if (child.style.display !== "none" && child.style.display !== "hidden" && child.style.opacity !== 0 && child.style.visibility !== "hidden") {
itemVisibleLength++;
}
}
hasItem = hasItem && itemVisibleLength > 0;
if (hasItem) {
if (this._emptyView.parentNode) {
this._emptyView.style.display = "none";
}
} else {
if (!this._emptyView.parentNode) {
this.container.appendChild(this._emptyView);
}
this._emptyView.style.display = "";
}
};
Object.defineProperty(PMDataListView.prototype, "emptyView", {
get: function() {
return this._emptyView;
},
set: function(value) {
// 只接受 HTMLElement 或 null / undefined
if (value !== null && value !== void 0 && !(value instanceof HTMLElement)) {
return;
}
// 移除旧的
if (this._emptyView && this._emptyView.parentNode) {
this._emptyView.parentNode.removeChild(this._emptyView);
}
this._emptyView = value || null;
// 设置后立刻刷新一次
this._updateEmptyView();
}
});
PMDataListView.prototype._isItemVisible = function(item) {
// 1⃣ filter
if (this._filter) {
if (!this._filter(item)) return false;
}
// 2⃣ search自动启用 / 禁用)
var handler = this._searchHandler;
var text = this._searchText;
if (typeof handler === "function") {
if (text != null) {
text = ("" + text).replace(/^\s+|\s+$/g, "");
}
if (text && text.length > 0) {
if (!handler(text, item)) {
return false;
}
}
}
return true;
};
PMDataListView.prototype._refreshVisibility = function() {
var self = this;
var children = self.container.children;
var animes = [];
for (var i = 0; i < children.length; i++) {
(function(node) {
var item = node.__pm_item;
if (!item) return;
var visible = self._isItemVisible(item);
var enableAnime = animes.length < MAX_ANIMATE_COUNT;
if (visible) {
if (node.style.display === "none") {
node.style.display = "";
animes.push(runShowAnime(node, enableAnime));
}
} else {
if (node.style.display !== "none") {
// 移除选择状态
node.classList.remove("selected");
animes.push(runHideAnime(node, enableAnime).then(function() {
node.style.display = "none";
}));
}
}
})(children[i]);
}
return Promise.join(animes);
};
Object.defineProperty(PMDataListView.prototype, "filter", {
get: function() {
return this._filter;
},
set: function(fn) {
this._filter = (typeof fn === "function") ? fn : null;
this._refreshVisibility();
}
});
Object.defineProperty(PMDataListView.prototype, "searchHandler", {
get: function() {
return this._searchHandler;
},
set: function(fn) {
this._searchHandler = (typeof fn === "function") ? fn : null;
this._refreshVisibility();
}
});
Object.defineProperty(PMDataListView.prototype, "searchText", {
get: function() {
return this._searchText;
},
set: function(text) {
var oldText = this._searchText;
this._searchText = text;
var oldActive = !!(oldText && oldText.trim());
var newActive = !!(text && ("" + text).trim());
//if (!oldActive && newActive) {
this._isSearching = true;
this._emitSearchEvent("searchstart");
//}
var handler = this._searchHandler;
var provider = this._searchSuggestProvider;
var cb = this.onSearchSuggest;
var t = text;
if (t != null) {
t = ("" + t).replace(/^\s+|\s+$/g, "");
}
// 搜索建议
if (
typeof handler === "function" &&
t &&
t.length > 0 &&
typeof provider === "function" &&
typeof cb === "function"
) {
var list = provider(t);
if (list && list.length) {
cb(t, list.slice(0, 10));
}
}
var self = this;
var func = function() {
//if (oldActive && !newActive) {
self._isSearching = false;
self._emitSearchEvent("searchend");
//}
};
this._refreshVisibility().done(func, func);
}
});
Object.defineProperty(PMDataListView.prototype, "searchSuggestProvider", {
get: function() {
return this._searchSuggestProvider;
},
set: function(fn) {
this._searchSuggestProvider = (typeof fn === "function") ? fn : null;
}
});
Object.defineProperty(PMDataListView.prototype, "findItemLength", {
get: function() {
var count = 0;
var children = this.container.children;
for (var i = 0; i < children.length; i++) {
var item = children[i].__pm_item;
if (this._isItemVisible(item)) {
count++;
}
}
return count;
}
});
PMDataListView.prototype._emitSearchEvent = function(type) {
if (typeof this["on" + type] === "function") {
try {
this["on" + type].call(this);
} catch (e) {}
}
try {
var ev = document.createEvent("Event");
ev.initEvent(type, true, true);
this.container.dispatchEvent(ev);
} catch (e) {}
};
PMDataListView.prototype.refresh = function() {
this._refreshVisibility();
this._updateEmptyView();
};
global.DataView.ChangeEvent = PMChangeEvent;
global.DataView.DataSource = PMDataSource;
global.DataView.ListView = PMDataListView;
})(this);