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