You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
710 lines
20 KiB
JavaScript
710 lines
20 KiB
JavaScript
function isTruthy(prop) {
|
|||
if (prop === undefined) return false;
|
|||
if (prop === "" ) return true;
|
|||
if (prop === "false" ) return false;
|
|||
return true;
|
|||
}
|
|||
|
|||
export class VList extends Element {
|
|||
props;
|
|||
|
|||
this(props) {
|
|||
const {renderItem, renderList, ...rest} = props;
|
|||
super.this?.(rest);
|
|||
this.props = rest;
|
|||
this.renderItem = renderItem ?? this.renderItem;
|
|||
this.renderList = renderList ?? this.renderList;
|
|||
}
|
|||
|
|||
init(itemHeight = 22) {
|
|||
this.vlist.slidingWindowSize = Math.round(2 * view.screenBox("frame", "height") / itemHeight);
|
|||
}
|
|||
|
|||
itemAt(index) {
|
|||
return null;
|
|||
}
|
|||
|
|||
totalItems() {
|
|||
return 0;
|
|||
}
|
|||
|
|||
indexOf(item) {
|
|||
return -1;
|
|||
}
|
|||
|
|||
renderList(items) {}
|
|||
|
|||
renderItem(item) {}
|
|||
|
|||
render() {
|
|||
const list = [];
|
|||
if (!this.vlist) return this.renderList(list);
|
|||
|
|||
const totalItems = this.totalItems();
|
|||
const firstIndex = this.vlist.firstBufferIndex;
|
|||
let lastIndex = this.vlist.lastBufferIndex;
|
|||
if (this.vlist.itemsTotal != totalItems) {
|
|||
const firstVisibleIndex = firstIndex + this.vlist.firstVisibleItem?.index || 0;
|
|||
const lastVisibleIndex = firstIndex + this.vlist.lastVisibleItem?.index;
|
|||
|
|||
if (firstVisibleIndex == 0) {
|
|||
this.post(() => this.vlist.navigate("start"));
|
|||
return this.renderList([]);
|
|||
}
|
|||
|
|||
if (lastVisibleIndex >= totalItems) {
|
|||
this.post(() => this.vlist.navigate("end"));
|
|||
return this.renderList([]);
|
|||
}
|
|||
|
|||
lastIndex = Math.min(totalItems, firstIndex + this.vlist.slidingWindowSize) - 1;
|
|||
this.post(() => {
|
|||
this.vlist.itemsAfter = totalItems - this.vlist.itemsBefore - this.children.length;
|
|||
});
|
|||
}
|
|||
|
|||
if (this.itemsAt) {
|
|||
let items = this.itemsAt(firstIndex, lastIndex - firstIndex + 1);
|
|||
for (let item of items)
|
|||
this.renderElement(list, item);
|
|||
} else {
|
|||
for (let index = firstIndex; index <= lastIndex; ++index)
|
|||
this.renderElement(list, this.itemAt(index));
|
|||
}
|
|||
|
|||
return this.renderList(list);
|
|||
}
|
|||
|
|||
renderElement(elements, item) {
|
|||
if (item) elements.push(this.renderItem(item));
|
|||
}
|
|||
|
|||
appendElements(index, n) {
|
|||
if (index === undefined) index = 0;
|
|||
const totalItems = this.totalItems();
|
|||
const elements = [];
|
|||
if (this.itemsAt) {
|
|||
let items = this.itemsAt(index, n);
|
|||
for (let item of items)
|
|||
this.renderElement(elements, item);
|
|||
index += n;
|
|||
if (index >= totalItems) index = totalItems;
|
|||
} else {
|
|||
for (let i = 0; i < n; ++i, ++index) {
|
|||
if (index >= totalItems) break;
|
|||
this.renderElement(elements, this.itemAt(index));
|
|||
}
|
|||
}
|
|||
|
|||
this.append(elements);
|
|||
return { moreafter: (totalItems - index) };
|
|||
}
|
|||
|
|||
prependElements(index, n) {
|
|||
if (index === undefined) index = this.totalItems() - 1;
|
|||
|
|||
const elements = [];
|
|||
if (this.itemsAt) {
|
|||
let items = this.itemsAt(index - n + 1, n);
|
|||
for (let item of items)
|
|||
this.renderElement(elements, item);
|
|||
index -= n;
|
|||
if (index < 0) index = -1;
|
|||
} else {
|
|||
for (let i = 0; i < n; ++i, --index) {
|
|||
if (index < 0) break;
|
|||
this.renderElement(elements, this.itemAt(index));
|
|||
}
|
|||
elements.reverse();
|
|||
}
|
|||
|
|||
this.prepend(elements);
|
|||
return { morebefore: (index < 0 ? 0 : index + 1) };
|
|||
}
|
|||
|
|||
replaceElements(index, n) {
|
|||
const totalItems = this.totalItems();
|
|||
const elements = [];
|
|||
const start = index;
|
|||
if (this.itemsAt) {
|
|||
let items = this.itemsAt(index, n);
|
|||
for (let item of items)
|
|||
this.renderElement(elements, item);
|
|||
index += n;
|
|||
if (index >= totalItems) index = totalItems - 1;
|
|||
} else {
|
|||
for (let i = 0; i < n; ++i, ++index) {
|
|||
if (index >= totalItems) break;
|
|||
this.renderElement(elements, this.itemAt(index));
|
|||
}
|
|||
}
|
|||
|
|||
this.patch(elements);
|
|||
return {
|
|||
morebefore: start <= 0 ? 0 : start,
|
|||
moreafter: totalItems - index,
|
|||
};
|
|||
}
|
|||
|
|||
oncontentrequired(e) {
|
|||
let {length, start, where} = e.data;
|
|||
if (where > 0) e.data = this.appendElements(start, length);
|
|||
else if (where < 0) e.data = this.prependElements(start, length);
|
|||
else e.data = this.replaceElements(start, length);
|
|||
return true;
|
|||
}
|
|||
}
|
|||
|
|||
export class VSelect extends VList {
|
|||
multiselect = false;
|
|||
rightButtonSelect = true;
|
|||
currentItem = null;
|
|||
anchorIndex = null;
|
|||
selectedIndexes = {};
|
|||
checkedItems = {};
|
|||
recordset = [];
|
|||
|
|||
this(props) {
|
|||
const {recordset, rightButtonSelect, multiselect, multicheck, ...rest} = props;
|
|||
super.this?.(rest);
|
|||
this.recordset = recordset ?? [];
|
|||
this.rightButtonSelect = rightButtonSelect ?? this.rightButtonSelect;
|
|||
this.multiselect = multiselect ?? this.multiselect;
|
|||
this.multicheck = multicheck ?? this.multicheck;
|
|||
console.assert(Array.isArray(this.recordset));
|
|||
}
|
|||
|
|||
itemAt(index) {
|
|||
return this.recordset?.[index];
|
|||
}
|
|||
|
|||
totalItems() {
|
|||
return this.recordset?.length ?? 0;
|
|||
}
|
|||
|
|||
indexOf(item) {
|
|||
return this.recordset?.indexOf(item);
|
|||
}
|
|||
|
|||
itemsAreEqual(item1, item2) {
|
|||
return item1 === item2;
|
|||
}
|
|||
|
|||
uniqueKey() {
|
|||
return "key";
|
|||
}
|
|||
|
|||
renderElement(elements, item) {
|
|||
if (!item) return;
|
|||
const {currentItem, selectedIndexes, checkedItems} = this;
|
|||
elements.push(this.renderItem(item, this.itemsAreEqual(item, currentItem), selectedIndexes[this.indexOf(item)] !== undefined, checkedItems[item[this.uniqueKey()]] !== undefined));
|
|||
}
|
|||
|
|||
notifyCurrentChange() {
|
|||
this.postEvent(new Event("currentchange", { bubbles: true }));
|
|||
}
|
|||
|
|||
componentUpdate(props, kids) {
|
|||
if (props && props.currentItem)
|
|||
if (!this.itemsAreEqual(this.currentItem, props.currentItem)) this.notifyCurrentChange();
|
|||
super.componentUpdate(props, kids);
|
|||
}
|
|||
componentUpdateThrottled = this.componentUpdate.throttle(8ms);
|
|||
|
|||
render(props) {
|
|||
if ((props?.recordset && (this.recordset !== props.recordset)) || !this.vlist) {
|
|||
this.recordset = props?.recordset ?? [];
|
|||
this.post(() => this.vlist.navigate("start"));
|
|||
return this.renderList([], props);
|
|||
}
|
|||
return super.render(props);
|
|||
}
|
|||
|
|||
itemOfElement(element) {
|
|||
return this.itemAt(element.index + this.vlist.firstBufferIndex);
|
|||
}
|
|||
|
|||
advanceCentered(index, animated = false) {
|
|||
if (index < 0 || index > this.totalItems() - 1) return false;
|
|||
this.selectSingleIndex(index);
|
|||
this.componentUpdate({ currentItem: this.itemAt(index) });
|
|||
if (animated) {
|
|||
this.vlist.advanceTo(index);
|
|||
} else {
|
|||
this.vlist.navigateTo(index);
|
|||
let halfPage = this.getPageSize(true);
|
|||
if (halfPage > 0) this.vlist.navigateTo(index - halfPage);
|
|||
}
|
|||
return true;
|
|||
}
|
|||
|
|||
advanceNext(shift) {
|
|||
if (!this.currentItem) {
|
|||
this.currentItem = this.itemOfElement(this.vlist.firstVisibleItem);
|
|||
} else {
|
|||
let index = this.indexOf(this.currentItem);
|
|||
if (++index < this.totalItems()) {
|
|||
this.currentItem = this.itemAt(index);
|
|||
this.vlist.advanceTo(index);
|
|||
} else return true;
|
|||
}
|
|||
if (this.multiselect && shift)
|
|||
this.selectFromAnchorToCurrent();
|
|||
else
|
|||
this.selectSingleIndex(this.indexOf(this.currentItem));
|
|||
this.componentUpdate();
|
|||
this.notifyCurrentChange();
|
|||
return true;
|
|||
}
|
|||
|
|||
advancePrevious(shift) {
|
|||
if (!this.currentItem) {
|
|||
this.currentItem = this.itemOfElement(this.vlist.lastVisibleItem);
|
|||
} else {
|
|||
let index = this.indexOf(this.currentItem);
|
|||
if (--index >= 0) {
|
|||
this.currentItem = this.itemAt(index);
|
|||
this.vlist.advanceTo(index);
|
|||
} else return true;
|
|||
}
|
|||
if (this.multiselect && shift)
|
|||
this.selectFromAnchorToCurrent();
|
|||
else
|
|||
this.selectSingleIndex(this.indexOf(this.currentItem));
|
|||
this.componentUpdate();
|
|||
this.notifyCurrentChange();
|
|||
return true;
|
|||
}
|
|||
|
|||
getPageSize(half = false) {
|
|||
let first = this.indexOf(this.itemOfElement(this.vlist.firstVisibleItem));
|
|||
let last = this.indexOf(this.itemOfElement(this.vlist.lastVisibleItem));
|
|||
let num = last - first + 1;
|
|||
return half ? Math.max(0, Math.round(num / 2 - 1)) : num;
|
|||
}
|
|||
|
|||
advancePageNext(shift) {
|
|||
if (!this.currentItem) {
|
|||
this.currentItem = this.itemOfElement(this.vlist.lastVisibleItem);
|
|||
} else {
|
|||
let index = this.indexOf(this.currentItem);
|
|||
if (index == this.totalItems() - 1) return true;
|
|||
index = Math.min(index + this.getPageSize(), this.totalItems() - 1);
|
|||
this.currentItem = this.itemAt(index);
|
|||
this.vlist.advanceTo(index);
|
|||
}
|
|||
if (this.multiselect && shift)
|
|||
this.selectFromAnchorToCurrent();
|
|||
else
|
|||
this.selectSingleIndex(this.indexOf(this.currentItem));
|
|||
this.componentUpdate();
|
|||
this.notifyCurrentChange();
|
|||
return true;
|
|||
}
|
|||
|
|||
advancePagePrevious(shift) {
|
|||
if (!this.currentItem) {
|
|||
this.currentItem = this.itemOfElement(this.vlist.firstVisibleItem);
|
|||
} else {
|
|||
let index = this.indexOf(this.currentItem);
|
|||
if (index == 0) return true;
|
|||
index = Math.max(index - this.getPageSize(), 0);
|
|||
this.currentItem = this.itemAt(index);
|
|||
this.vlist.advanceTo(index);
|
|||
}
|
|||
if (this.multiselect && shift)
|
|||
this.selectFromAnchorToCurrent();
|
|||
else
|
|||
this.selectSingleIndex(this.indexOf(this.currentItem));
|
|||
this.componentUpdate();
|
|||
this.notifyCurrentChange();
|
|||
return true;
|
|||
}
|
|||
|
|||
advanceFirst(shift) {
|
|||
if (this.currentItem && this.indexOf(this.currentItem) == 0) return true;
|
|||
this.currentItem = this.itemAt(0);
|
|||
if (this.multiselect && shift)
|
|||
this.selectFromAnchorToCurrent();
|
|||
else
|
|||
this.selectSingleIndex(0);
|
|||
this.vlist.navigateTo("start");
|
|||
this.notifyCurrentChange();
|
|||
return true;
|
|||
}
|
|||
|
|||
advanceLast(shift) {
|
|||
var lastIndex = this.totalItems() - 1;
|
|||
if (this.currentItem && this.indexOf(this.currentItem) == lastIndex) return true;
|
|||
this.currentItem = this.itemAt(lastIndex);
|
|||
if (this.multiselect && shift)
|
|||
this.selectFromAnchorToCurrent();
|
|||
else
|
|||
this.selectSingleIndex(lastIndex);
|
|||
this.vlist.navigateTo("end");
|
|||
this.notifyCurrentChange();
|
|||
return true;
|
|||
}
|
|||
|
|||
selectSingleIndex(index) {
|
|||
this.anchorIndex = index ?? null;
|
|||
this.selectedIndexes = {};
|
|||
if (this.anchorIndex !== null) this.selectedIndexes[this.anchorIndex] = true;
|
|||
}
|
|||
|
|||
selectFromAnchorToCurrent() {
|
|||
if (this.anchorIndex === null || !this.currentItem) return;
|
|||
var currentIndex = this.indexOf(this.currentItem);
|
|||
var start = currentIndex > this.anchorIndex ? this.anchorIndex : currentIndex;
|
|||
var end = currentIndex > this.anchorIndex ? currentIndex : this.anchorIndex;
|
|||
this.selectedIndexes = {};
|
|||
for (let index = start; index <= end; index++)
|
|||
this.selectedIndexes[index] = true;
|
|||
}
|
|||
|
|||
selectAll() {
|
|||
if (!this.multiselect) return false;
|
|||
var totalItems = this.totalItems();
|
|||
if (totalItems == 0) return false;
|
|||
this.anchorIndex = 0;
|
|||
this.currentItem = this.itemAt(totalItems - 1);
|
|||
this.selectFromAnchorToCurrent();
|
|||
this.notifyCurrentChange();
|
|||
this.componentUpdate();
|
|||
return true;
|
|||
}
|
|||
|
|||
onkeydown(e) {
|
|||
switch (e.code) {
|
|||
case "ArrowDown": return this.advanceNext(e.shiftKey);
|
|||
case "ArrowUp": return this.advancePrevious(e.shiftKey);
|
|||
case "PageDown": return this.advancePageNext(e.shiftKey);
|
|||
case "PageUp": return this.advancePagePrevious(e.shiftKey);
|
|||
case "End": return this.advanceLast(e.shiftKey);
|
|||
case "Home": return this.advanceFirst(e.shiftKey);
|
|||
case "KeyA": if (e.ctrlKey) return this.selectAll();
|
|||
default: return false;
|
|||
}
|
|||
this.postEvent(new Event("input", { bubbles: true }));
|
|||
return true;
|
|||
}
|
|||
|
|||
// ["on ~mousedown"](e) {
|
|||
// this.state.capture(true);
|
|||
// }
|
|||
|
|||
// ["on ~mouseup"](e) {
|
|||
// this.state.capture(false);
|
|||
// }
|
|||
|
|||
// ["on ~mousetick"](e) {
|
|||
// let height = this.state.box("height");
|
|||
// if (e.y < 0)
|
|||
// this.vlist.navigate("itemprior");
|
|||
// else if(e.y > height)
|
|||
// this.vlist.navigate("itemnext");
|
|||
// console.log(e.y, height);
|
|||
// }
|
|||
|
|||
handleSelection(option, multiSingle, multiRange, mousemove = false) {
|
|||
if (!option) return;
|
|||
|
|||
var currentChanged = this.currentItem === null || (this.currentItem && this.currentItem[this.uniqueKey()] != option.attributes["key"]);
|
|||
if (currentChanged) this.currentItem = this.itemOfElement(option);
|
|||
|
|||
if (!this.currentItem) return;
|
|||
|
|||
if (this.multiselect) {
|
|||
var currentIndex = this.indexOf(this.currentItem);
|
|||
if (multiSingle) {
|
|||
if (this.selectedIndexes[currentIndex] !== undefined)
|
|||
delete this.selectedIndexes[currentIndex];
|
|||
else
|
|||
this.selectedIndexes[currentIndex] = true;
|
|||
} else if (currentChanged) {
|
|||
if (multiRange && this.anchorIndex !== null) {
|
|||
this.selectFromAnchorToCurrent();
|
|||
} else {
|
|||
this.anchorIndex = this.indexOf(this.currentItem);
|
|||
this.selectedIndexes = {};
|
|||
this.selectedIndexes[currentIndex] = true;
|
|||
}
|
|||
} else this.selectSingleIndex(this.indexOf(this.currentItem));
|
|||
} else this.selectSingleIndex(this.indexOf(this.currentItem));
|
|||
|
|||
if (currentChanged || this.multiselect) {
|
|||
if (mousemove)
|
|||
this.componentUpdateThrottled();
|
|||
else
|
|||
this.componentUpdate();
|
|||
}
|
|||
if (currentChanged) this.notifyCurrentChange();
|
|||
}
|
|||
|
|||
isMouseDown = false;
|
|||
|
|||
["on mousedown at :root > *"](e, el) {
|
|||
if (!this.rightButtonSelect && e.button == 2) return false;
|
|||
if (this.multicheck && (e.ctrlKey || e.isOnIcon)) {
|
|||
var cr = this.itemOfElement(el);
|
|||
this.toggleCheck(cr);
|
|||
this.componentUpdate({ currentItem: cr });
|
|||
} else if (e.button == 1 || e.button == 2) {
|
|||
this.isMouseDown = true;
|
|||
this.handleSelection(el, e.ctrlKey, e.shiftKey);
|
|||
}
|
|||
}
|
|||
|
|||
["on mouseup at :root > *"](e) {
|
|||
this.isMouseDown = false;
|
|||
}
|
|||
|
|||
["on mouseenter"](e) {
|
|||
if (e.button == 0) this.isMouseDown = false;
|
|||
}
|
|||
|
|||
["on mousemove at :root > *"](e, el) {
|
|||
//if (!this.rightButtonSelect && e.button == 2) return false;
|
|||
//if (this.isMouseDown && (e.button == 1 || e.button == 2)) this.handleSelection(el, e.ctrlKey, e.shiftKey, true);
|
|||
}
|
|||
|
|||
get value() {
|
|||
return this.currentItem;
|
|||
}
|
|||
|
|||
get currentIndex() {
|
|||
return this.indexOf(this.currentItem);
|
|||
}
|
|||
|
|||
isSelected(index) {
|
|||
return this.selectedIndexes[index] !== undefined;
|
|||
}
|
|||
|
|||
isChecked(key) {
|
|||
return this.checkedItems[key] !== undefined;
|
|||
}
|
|||
|
|||
checkCurrent() {
|
|||
if (this.multicheck && this.currentItem !== null) {
|
|||
this.toggleCheck(this.currentItem);
|
|||
this.componentUpdate();
|
|||
}
|
|||
}
|
|||
|
|||
checkAll() {
|
|||
if (!this.multicheck) return;
|
|||
|
|||
for (let group of this.recordset) {
|
|||
this.checkedItems[group[this.uniqueKey()]] = group;
|
|||
if (group.items)
|
|||
for (let rec of group.items)
|
|||
this.checkedItems[rec[this.uniqueKey()]] = rec;
|
|||
}
|
|||
|
|||
this.componentUpdate();
|
|||
}
|
|||
|
|||
uncheckAll() {
|
|||
if (!this.multicheck) return;
|
|||
|
|||
for (let group of this.recordset) {
|
|||
delete this.checkedItems[group[this.uniqueKey()]];
|
|||
if (group.items)
|
|||
for (let rec of group.items)
|
|||
delete this.checkedItems[rec[this.uniqueKey()]];
|
|||
}
|
|||
|
|||
this.componentUpdate();
|
|||
}
|
|||
|
|||
toggleCheck(cr) {
|
|||
var checked = this.checkedItems[cr[this.uniqueKey()]] !== undefined;
|
|||
if (checked)
|
|||
delete this.checkedItems[cr[this.uniqueKey()]];
|
|||
else
|
|||
this.checkedItems[cr[this.uniqueKey()]] = cr;
|
|||
|
|||
if (cr.kind == 1) {
|
|||
let allChecked = true;
|
|||
let parentGroup = this.recordset.find((record) => record.kind == 0 && record.groupId == cr.groupId);
|
|||
if (parentGroup) {
|
|||
if (parentGroup.items)
|
|||
for (let rec of parentGroup.items)
|
|||
if (rec.kind == 1 && rec.groupId == cr.groupId && this.checkedItems[rec[this.uniqueKey()]] === undefined) {
|
|||
allChecked = false;
|
|||
break;
|
|||
}
|
|||
if (allChecked) {
|
|||
if (this.checkedItems[parentGroup[this.uniqueKey()]] === undefined)
|
|||
this.checkedItems[parentGroup[this.uniqueKey()]] = parentGroup;
|
|||
} else {
|
|||
if (this.checkedItems[parentGroup[this.uniqueKey()]] !== undefined)
|
|||
delete this.checkedItems[parentGroup[this.uniqueKey()]];
|
|||
}
|
|||
}
|
|||
} else if (cr.kind == 0)
|
|||
for (let group of this.recordset)
|
|||
if (group.items)
|
|||
for (let rec of group.items)
|
|||
if (rec.kind == 1 && rec.groupId == cr.groupId)
|
|||
if (checked)
|
|||
delete this.checkedItems[rec[this.uniqueKey()]];
|
|||
else
|
|||
this.checkedItems[rec[this.uniqueKey()]] = rec;
|
|||
|
|||
this.componentUpdate();
|
|||
}
|
|||
|
|||
focusList() {
|
|||
this.post(() => this.state.focus = true);
|
|||
}
|
|||
}
|
|||
|
|||
class VTableBody extends VSelect {
|
|||
this(props, kids) {
|
|||
const {recordview, uniquekey, ...rest} = props;
|
|||
super.this?.(rest);
|
|||
this.recordview = recordview;
|
|||
this.uniquekey = uniquekey;
|
|||
}
|
|||
|
|||
uniqueKey() {
|
|||
return this.uniquekey ?? super.uniqueKey();
|
|||
}
|
|||
|
|||
renderList(items) {
|
|||
return <tbody {this.props}>{items}</tbody>
|
|||
}
|
|||
|
|||
renderItem(item, isCurrent, isSelected, isChecked) {
|
|||
return this.recordview(item, isCurrent, isSelected, isChecked);
|
|||
}
|
|||
}
|
|||
|
|||
export class VTable extends Element {
|
|||
vtbody;
|
|||
sortField = undefined;
|
|||
sortOrder = undefined; // true - ascending, false - descending
|
|||
sortType = "string";
|
|||
|
|||
this(props, kids) {
|
|||
super.this?.(props, kids);
|
|||
|
|||
console.assert(kids[0][0] == "columns"); // expect |
|||
this.columnHeaders = kids[0][2]; // array of |
|||
this.recordset = props.recordset ?? [];
|
|||
this.recordview = props.recordview;
|
|||
this.uniquekey = props.uniquekey ?? "key";
|
|||
this.multicheck = isTruthy(props.multicheck);
|
|||
this.multiselect = isTruthy(props.multiselect);
|
|||
this.sortable = isTruthy(props.sortable);
|
|||
this.unanimated = isTruthy(props.unanimated);
|
|||
console.assert(typeof this.recordview == "function");
|
|||
|
|||
this.recordsForView();
|
|||
}
|
|||
|
|||
render() {
|
|||
var atts = {};
|
|||
if (this.multicheck) atts.multicheck = true;
|
|||
return <table {atts} styleset="vlist.css#vtable">
|
|||
<thead><tr>{this.columnHeaders}</tr></thead>
|
|||
<VTableBody
|
|||
recordset={this.sortedset ?? this.recordset}
|
|||
recordview={this.recordview}
|
|||
uniquekey={this.uniquekey}
|
|||
multicheck={this.multicheck}
|
|||
multiselect={this.multiselect}
|
|||
unanimated={this.unanimated} />
|
|||
</table>;
|
|||
}
|
|||
|
|||
init(itemHeight = 22) {
|
|||
this.vtbody = this.$("tbody");
|
|||
this.vtbody.init(itemHeight);
|
|||
}
|
|||
|
|||
componentDidMount() {
|
|||
this.vtbody = this.$("tbody");
|
|||
}
|
|||
|
|||
transformColumnHeaders() {
|
|||
var field = this.sortField;
|
|||
for (let header of this.columnHeaders)
|
|||
if (header[1].field == field && this.sortOrder !== undefined)
|
|||
header[1].order = this.sortOrder.toString();
|
|||
else
|
|||
header[1].order = "none";
|
|||
}
|
|||
|
|||
recordsForView() {
|
|||
var rs2view = this.recordset;
|
|||
this.sortedset = null;
|
|||
if (this.sortField === undefined || this.sortOrder === undefined || !rs2view.length) return rs2view;
|
|||
if (!this.sortable) return rs2view;
|
|||
this.sortedset = rs2view = [ ...rs2view ];
|
|||
var field = this.sortField;
|
|||
var order = this.sortOrder;
|
|||
var comparator;
|
|||
switch (this.sortType) {
|
|||
case "string":
|
|||
comparator = order == "asc" ? (a,b) => a[field].toLowerCase().localeCompare(b[field].toLowerCase(), true)
|
|||
: (a,b) => b[field].toLowerCase().localeCompare(a[field].toLowerCase(), true);
|
|||
break;
|
|||
case "date":
|
|||
comparator = order == "asc" ? (a,b) => (a[field] ? a[field].valueOf() : Number.MAX_VALUE) - (b[field] ? b[field].valueOf() : Number.MAX_VALUE)
|
|||
: (a,b) => (b[field] ? b[field].valueOf() : -Number.MAX_VALUE) - (a[field] ? a[field].valueOf() : -Number.MAX_VALUE);
|
|||
break;
|
|||
case "boolean":
|
|||
comparator = order == "asc" ? (a,b) => !a[field] && b[field] ? -1 : 0
|
|||
: (a,b) => !b[field] && a[field] ? -1 : 0;
|
|||
break;
|
|||
case "float": case "integer":
|
|||
comparator = order == "asc" ? (a,b) => {
|
|||
if (isNaN(a[field]) || isNaN(b[field]))
|
|||
return a[field].toString().toLowerCase().localeCompare(b[field].toString().toLowerCase(), true);
|
|||
else
|
|||
return (a[field] ? parseFloat(a[field]) : Number.MAX_VALUE) - (b[field] ? parseFloat(b[field]) : Number.MAX_VALUE);
|
|||
} : (a,b) => {
|
|||
if (isNaN(a[field]) || isNaN(b[field]))
|
|||
return b[field].toString().toLowerCase().localeCompare(a[field].toString().toLowerCase(), true);
|
|||
else
|
|||
return (b[field] ? parseFloat(b[field]) : -Number.MAX_VALUE) - (a[field] ? parseFloat(a[field]) : -Number.MAX_VALUE);
|
|||
}
|
|||
break;
|
|||
default:
|
|||
return rs2view;
|
|||
}
|
|||
rs2view.sort(comparator);
|
|||
return rs2view;
|
|||
}
|
|||
|
|||
focusList() {
|
|||
if (this.vtbody) this.vtbody.focusList();
|
|||
}
|
|||
|
|||
["on click at th[field]"](e, th) {
|
|||
if (!this.sortable) return false;
|
|||
|
|||
var field = th.attr["field"];
|
|||
this.sortType = th.attr["as"] ?? "string";
|
|||
if (!field) return false;
|
|||
if (this.sortField != field) this.sortOrder = "asc"; else
|
|||
switch (this.sortOrder) {
|
|||
case "asc": this.sortOrder = "desc"; break;
|
|||
case "desc" : this.sortOrder = undefined; break;
|
|||
default: this.sortOrder = "asc";
|
|||
}
|
|||
this.sortField = field;
|
|||
this.transformColumnHeaders();
|
|||
this.recordsForView();
|
|||
this.componentUpdate();
|
|||
}
|
|||
|
|||
get checkedItems() {
|
|||
return this.vtbody?.checkedItems ?? {};
|
|||
}
|
|||
set checkedItems(v) {
|
|||
this.vtbody?.componentUpdate({ checkedItems: v });
|
|||
}
|
|||
} |