DatasetSupport (TableSupport, PagingSupport, CurrentDataSupport) 추가
parent
e337b0cc7a
commit
6e7649bc16
@ -0,0 +1,387 @@
|
||||
/* Copyright (c) 2020 Emjay Khan. All rights reserved. */
|
||||
|
||||
/**Base class of DatasetSupport.
|
||||
* It has empty implementations of methods called back in response to a Dataset's events.
|
||||
* A DatasetSupport extension implements some of the call-back methods to achieve its purpose.
|
||||
*/
|
||||
class DatasetSupport {
|
||||
/**Creates a DatasetSupport with the given configuration.
|
||||
* @param {object} conf configuration.
|
||||
* <ul><li>css selector for a containing element, optional</li>
|
||||
* <li>css selector for elements the DatasetSupport works with primarily</li>
|
||||
* </ul>
|
||||
*/
|
||||
constructor(conf) {
|
||||
this.selector = conf.selector || null;
|
||||
this.dataset = conf.ctrl.dataset;
|
||||
this._doq = conf.ctrl.doq;
|
||||
}
|
||||
|
||||
get doq() {
|
||||
return this._doq;
|
||||
}
|
||||
|
||||
init() {}
|
||||
|
||||
get keymapped() {
|
||||
return this.dataset ? this.dataset.keymapped : false;
|
||||
}
|
||||
|
||||
getSelector(...selectors) {
|
||||
return this.doq.selector(...selectors);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} selector
|
||||
* @param {boolean} strict
|
||||
*/
|
||||
find(...selector) {
|
||||
return this.doq.find(...selector);
|
||||
}
|
||||
|
||||
findAll(...selector) {
|
||||
return this.doq.findAll(...selector);
|
||||
}
|
||||
}
|
||||
|
||||
class TableSupport extends DatasetSupport {
|
||||
constructor(conf) {
|
||||
super(conf);
|
||||
|
||||
this.selector = conf.table;
|
||||
this.tr = conf.tr;
|
||||
this.notFound = conf.notFound;
|
||||
|
||||
this.formatter = conf.formatter;
|
||||
this.selectionToggler = conf.selectionToggler || "";
|
||||
this.refreshOnModify = conf.refreshOnModify || [];
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.body = this.find(this.selector + " tbody");
|
||||
|
||||
if (this.tr) {
|
||||
let template = this.find(this.tr);
|
||||
this.tr = (template || {}).innerHTML || "";
|
||||
}
|
||||
if (this.notFound) {
|
||||
let template = this.find(this.notFound);
|
||||
this.notFound = (template || {}).innerHTML || "";
|
||||
}
|
||||
|
||||
if (!this.tr && !this.notFound) {
|
||||
let templates = this.findAll(this.selector + " template");
|
||||
if (templates.length < 1)
|
||||
log("WARNING: ", this.selector + " must have a template for a data row");
|
||||
this.tr = (templates[0] || {}).innerHTML;
|
||||
this.notFound = (templates[1] || {}).innerHTML;
|
||||
}
|
||||
|
||||
let sort = evt => {
|
||||
let th = evt.target;
|
||||
this.dataset.sort(th.getAttribute("data-sort"));
|
||||
};
|
||||
this.sortables().forEach(th => th.addEventListener("click", sort));
|
||||
}
|
||||
|
||||
sortables() {
|
||||
return this.findAll(this.selector + " thead th[data-sort]");
|
||||
}
|
||||
|
||||
updateSortables(sorter) {
|
||||
this.sortables().forEach(th => {
|
||||
th.classList.remove(
|
||||
TableSupport.cssClass.sortable,
|
||||
TableSupport.cssClass.asc,
|
||||
TableSupport.cssClass.desc
|
||||
);
|
||||
if (th.getAttribute("data-sort") == sorter.by)
|
||||
th.classList.add(TableSupport.cssClass[sorter.order]);
|
||||
else
|
||||
th.classList.add(TableSupport.cssClass.sortable);
|
||||
});
|
||||
if (sorter.by)
|
||||
this.draw();
|
||||
}
|
||||
|
||||
/**Handler called back on the dataset change event.
|
||||
* @param {Dataset} dataset this Dataset
|
||||
* @param {object} option optional information
|
||||
*/
|
||||
renderList(option = {}) {
|
||||
this.draw(option);
|
||||
|
||||
this.findAll(".enable-onfound").forEach(e => e.disabled = this.dataset.empty);
|
||||
}
|
||||
|
||||
draw(option = {}) {
|
||||
let empty = this.dataset.empty,
|
||||
trs = !empty ? this.dataset.inStrings(this.tr, this.formatter) : [this.notFound];
|
||||
this.body.innerHTML = trs.join("");
|
||||
|
||||
if (!this.selectionToggler) return;
|
||||
|
||||
let toggler = this.find(this.selectionToggler);
|
||||
toggler.checked = false;
|
||||
toggler.disabled = empty;
|
||||
}
|
||||
|
||||
/**Handler called back on the current change event.
|
||||
* @param {DataItem} item current DataItem
|
||||
*/
|
||||
setCurrentRow(item) {
|
||||
if (!item) return;
|
||||
|
||||
let index = item.index;
|
||||
this.findAll(this.selector + " tbody tr").forEach(tr => {
|
||||
let dataIndex = tr.getAttribute("data-index"),
|
||||
current = index == dataIndex;
|
||||
if (current)
|
||||
tr.classList.add(TableSupport.cssClass.current);
|
||||
else
|
||||
tr.classList.remove(TableSupport.cssClass.current);
|
||||
});
|
||||
}
|
||||
|
||||
/**Handler called back on the selection change event
|
||||
* @param {array} selected selected DataItems
|
||||
*/
|
||||
setSelections(selected) {
|
||||
let selectedIndex = selected.map(item => item.index);
|
||||
this.findAll(this.selector + " tbody input[name='data-index']")
|
||||
.forEach(input => {
|
||||
input.checked = selectedIndex.includes(input.value)
|
||||
});
|
||||
|
||||
this.findAll(".enable-onselect")
|
||||
.forEach(e => e.disabled = selected.length < 1);
|
||||
}
|
||||
|
||||
/**Handler called back on the modify event
|
||||
* @param {array} changed names of the changed properties
|
||||
* @param {DataItem} item owner DataItem of the changed properties
|
||||
* @param {boolean} current whether the event is on the current item
|
||||
*/
|
||||
updateModified(changed) {
|
||||
if (this.refreshOnModify.length < 1) return;
|
||||
|
||||
let refresh = false;
|
||||
for (let prop of changed) {
|
||||
refresh = this.refreshOnModify.includes(prop);
|
||||
if (refresh)
|
||||
break;
|
||||
}
|
||||
if (!refresh) return;
|
||||
|
||||
this.draw();
|
||||
this.dataset.setState();
|
||||
}
|
||||
}
|
||||
TableSupport.cssClass = {
|
||||
current: "current-row",
|
||||
sortable: "sortable",
|
||||
asc: "sort-asc",
|
||||
desc: "sort-desc"
|
||||
};
|
||||
|
||||
class CurrentDataSupport extends DatasetSupport {
|
||||
constructor(conf) {
|
||||
super(conf);
|
||||
if (!this.selector)
|
||||
this.selector = "[data-field]";
|
||||
}
|
||||
|
||||
type(input) {
|
||||
return input ? (input.getAttribute("type") || input.tagName).toLowerCase() : "";
|
||||
}
|
||||
|
||||
dataField(input) {
|
||||
return input.getAttribute("data-field");
|
||||
}
|
||||
|
||||
property(input) {
|
||||
return this.dataField(input)
|
||||
|| input.name
|
||||
|| input.id;
|
||||
}
|
||||
|
||||
update(input, value) {
|
||||
switch (this.type(input)) {
|
||||
case "radio":
|
||||
case "checkbox":
|
||||
if (isEmpty(value)) {
|
||||
input.checked = false;
|
||||
} else {
|
||||
switch (typeof value) {
|
||||
case "string":
|
||||
input.checked = input.value == value || value.split(",").includes(input.value);
|
||||
break;
|
||||
case "number":
|
||||
case "boolean":
|
||||
input.checked = input.value == value.toString();
|
||||
break;
|
||||
default:
|
||||
if (value instanceof Array)
|
||||
input.checked = value.includes(input.value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "select":
|
||||
for (let option of input.options) {
|
||||
option.selected = option.value === value;
|
||||
}
|
||||
break;
|
||||
case "img": input.src = value; break;
|
||||
case "button": input.innerHTML = value; break;
|
||||
default:
|
||||
if (input.value !== undefined)
|
||||
input.value = value;
|
||||
else
|
||||
input.innerHTML = value;
|
||||
}
|
||||
}
|
||||
|
||||
setChanged(evt) {
|
||||
let input = evt.target,
|
||||
type = this.type(input),
|
||||
prop = this.property(input),
|
||||
val = input.value;
|
||||
|
||||
switch (type) {
|
||||
case "checkbox":
|
||||
case "radio":
|
||||
let attrs = input.getAttributeNames()
|
||||
.filter(attr => ['data-field', 'name'].includes(attr))
|
||||
.map(attr => "[" + attr + "=\"" + input.getAttribute(attr) + "\"]")
|
||||
.join("");
|
||||
let inputs = this.findAll("[type=\"" + type + "\"]" + attrs);
|
||||
switch (inputs.length) {
|
||||
case 0: return;
|
||||
case 1:
|
||||
let cb = inputs[0];
|
||||
if (cb.checked) break;
|
||||
|
||||
switch (val) {
|
||||
case "true": val = "false"; break;
|
||||
case "y": val = "n"; break;
|
||||
case "Y": val = "N"; break;
|
||||
case "yes": val = "no"; break;
|
||||
default: val = ""; break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
val = inputs.filter(cb => cb.checked).map(cb => cb.value).join(",");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
this.dataset.setValue(prop, val);
|
||||
}
|
||||
|
||||
setCurrent(item) {
|
||||
if (!item) return;
|
||||
|
||||
this.findAll(this.selector).forEach(input => {
|
||||
let prop = this.property(input);
|
||||
if (!prop) return;
|
||||
|
||||
let evt = !["checkbox", "radio"].includes(this.type(input)) ? "change" : "click",
|
||||
handler = (evt) => this.setChanged(evt)
|
||||
|
||||
input.removeEventListener(evt, handler);
|
||||
this.update(input, item.getValue(prop));
|
||||
input.addEventListener(evt, handler);
|
||||
});
|
||||
this.enableOnDirty(item);
|
||||
this.enableOnNew(item);
|
||||
}
|
||||
|
||||
updateModified(changed, item) {
|
||||
if (!item) return;
|
||||
|
||||
this.findAll(this.selector).forEach(input => {
|
||||
let prop = this.property(input);
|
||||
if (!changed.includes(prop)) return;
|
||||
|
||||
this.update(input, item.getValue(prop));
|
||||
});
|
||||
this.enableOnDirty(item);
|
||||
}
|
||||
|
||||
/**Enables the HTML elements of '.enable-ondirtyitem' if the item is dirty.
|
||||
* @param {DataItem} item a DataItem
|
||||
*/
|
||||
enableOnDirty(item) {
|
||||
let dirty = item.dirty;
|
||||
|
||||
this.findAll(".enable-ondirtyitem")
|
||||
.forEach(e => e.disabled = !dirty);
|
||||
}
|
||||
|
||||
/**Enables the HTML elements of '.enable-onnewitem' if the item is new.
|
||||
* @param {DataItem} item a DataItem
|
||||
*/
|
||||
enableOnNew(item) {
|
||||
let isnew = item.isNew();
|
||||
|
||||
this.findAll(".enable-onnewitem")
|
||||
.forEach(e => e.disabled = !isnew);
|
||||
}
|
||||
|
||||
newData(init) {
|
||||
let data = this.findAll(this.selector).reduce((data, input) => {
|
||||
let prop = this.property(input);
|
||||
if (prop)
|
||||
data[prop] = null;
|
||||
return data;
|
||||
}, {});
|
||||
if (init)
|
||||
init(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
getData(item = this.dataset.getCurrent("item")) {
|
||||
return this.findAll(this.selector).reduce((data, input) => {
|
||||
let dataField = this.dataField(input),
|
||||
property = input.getAttribute("name") || input.getAttribute("id"),
|
||||
val = item.data[dataField];
|
||||
if (property && val !== undefined)
|
||||
data[property] = val;
|
||||
return data;
|
||||
}, {});
|
||||
}
|
||||
}
|
||||
|
||||
class PagingSupport extends DatasetSupport {
|
||||
constructor(conf) {
|
||||
super(conf);
|
||||
|
||||
this.prefix = conf.ctrl.prefix;
|
||||
this.sizeOffset = conf.sizeOffset || 0;
|
||||
|
||||
this.linkContainer = conf.linkContainer;
|
||||
this.func = conf.func;
|
||||
|
||||
this.statusContainer = conf.statusContainer;
|
||||
this.statusContent = conf.statusContent;
|
||||
}
|
||||
|
||||
setPaging(option) {
|
||||
let pagination = option ? option.pagination : null;
|
||||
if (!pagination) return;
|
||||
|
||||
pagination.prefix = this.prefix;
|
||||
pagination.dataSize = pagination.dataSize + this.sizeOffset;
|
||||
|
||||
if (this.linkContainer) {
|
||||
pagination.func = this.func;
|
||||
pagination.added = option.added;
|
||||
$(this.doq.selector(this.linkContainer)).setPaging(pagination);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue