Files
App-Installer-For-Windows-8…/shared/html/js/datasrc.js
T
2026-04-04 19:27:45 +08:00

829 lines
28 KiB
JavaScript

(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);