DatasetSupport (TableSupport, PagingSupport, CurrentDataSupport) 추가

master
mjkhan21 3 months ago
parent e337b0cc7a
commit 6e7649bc16

@ -120,6 +120,23 @@ ul.nav-tabs > li.nav-item {
display: inline-block; display: inline-block;
} }
.sortable, .sort-asc, .sort-desc {
cursor: pointer;
}
.sortable::after, .sort-asc::after, .sort-desc::after {
color: #b2babb;
float: inline-end;
}
.sortable::after {
content: '\002B27'; /* #11047, 2B27 */
}
.sort-asc::after {
content: '\002BC5'; /* #11205, 2BC5 */
}
.sort-desc::after {
content: '\002BC6'; /* #11206, 2BC6 */
}
.avatar { .avatar {
width: auto; width: auto;
} }

@ -632,9 +632,9 @@ $.fn.setPaging = function(config) {
return this.each(function(){ return this.each(function(){
let length = config.list ? config.list.length : config.dataLength, let length = config.list ? config.list.length : config.dataSize || config.dataLength,
empty = length < 1, empty = length < 1,
start = empty ? 0 : config.start + 1, start = empty ? 0 : !config.added ? config.start + 1 : 1,
end = empty ? 0 : config.start + length, end = empty ? 0 : config.start + length,
pagingInfo = empty ? "" : start + " ~ " + numberFormat.format(end) + " / " + numberFormat.format(config.totalSize), pagingInfo = empty ? "" : start + " ~ " + numberFormat.format(end) + " / " + numberFormat.format(config.totalSize),
selector = "#"+ config.prefix + "PagingInfo,[name='" + config.prefix + "PagingInfo']"; selector = "#"+ config.prefix + "PagingInfo,[name='" + config.prefix + "PagingInfo']";
@ -673,7 +673,7 @@ $.fn.setCurrentRow = function(val) {
var e = $(this); var e = $(this);
e.find("tr").each(function(){ e.find("tr").each(function(){
var tr = $(this), var tr = $(this),
current = val == tr.attr("data-key"); current = val == (tr.attr("data-key") || tr.attr("data-index"));
if (current) if (current)
tr.addClass("current-row"); tr.addClass("current-row");
else else
@ -800,3 +800,83 @@ function inputsInRange(fromSource, toSource) {
function ignore() { function ignore() {
console.log.apply(console, arguments); console.log.apply(console, arguments);
} }
/**Utility to simplify the document object query.
*/
class DomQuery {
/**Sets the selectors to the containers.
* @param {string} containers selectors(comma-separated) to the containers
* @returns {DomQuery} this DomQuery
*/
setContainers(containers = "") {
this.containers = containers.split(",").filter(str => !isEmpty(str));
return this;
}
/**Returns a selector prepended with the selectors to the containers.
* @param {...string} selectors
* To get a selector prepended with the selectors to the containers
* <pre><code>let doq = new DomQuery().setContainers("div#divID,form#formID"),
* selector = doq.selector("input[type='text']");</code></pre>
* To get a selector with the given attribute value prepended with the selectors to the containers.
* <pre><code>selector = doq.selector("attribute-name", "attribute-value");</code></pre>
* @returns {string} selector prepended with the selectors to the containers
*/
selector(...selectors) {
let result = null;
if (!this.containers)
this.containers = [];
let selector = "";
switch (selectors.length) {
case 1: selector = selectors[0]; break;
case 2: selector = "[" + selectors[0] + "='" + selectors[1] + "']"; break;
default: break;
}
if (this.containers.length < 1)
result = selector;
if (!result && !selector.includes(","))
result = this.containers
.map(container => selector ? container + " " + selector : container)
.join(",");
if (!result && selector.includes(",")) {
selector = selector.split(",");
result = this.containers.reduce((acc, x) => [...acc, ...selector.map(y => x + " " + y)], []).join(",");
}
if (this.trace)
log("selector:", result);
return result;
}
/**Selects an element matching the given selector.
* @param {...string} args
* To get an element matching the given selector
* <pre><code>let doq = new DomQuery().setContainers("div#divID,form#formID"),
* found = doq.find("selector to the desired element");</code></pre>
* To get an element with the given attribute value.
* <pre><code>found = doq.find("attribute-name", "attribute-value");</code></pre>
* @returns {object} element matching the given selector
*/
find(...args) {
return document.querySelector(this.selector(...args));
}
/**Selects elements matching the given selector.
* @param {...string} args
* To get elements matching the given selector
* <pre><code>let doq = new DomQuery().setContainers("div#divID,form#formID"),
* found = doq.findAll("selector to the desired elements");</code></pre>
* To get elements with the given attribute value.
* <pre><code>found = doq.findAll("attribute-name", "attribute-value");</code></pre>
* @returns {array} elements matching the given selector
*/
findAll(...args) {
return Array.from(document.querySelectorAll(this.selector(...args)));
}
}

@ -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);
}
}
}

@ -187,20 +187,13 @@ ValueFormat.InvalidValue = "^invalid^value^";
/**Wraps a user data and traces the manipulation performed on it and consequent status. /**Wraps a user data and traces the manipulation performed on it and consequent status.
*/ */
class DataItem { class DataItem {
/** user data */
data;
/** value formatters */
_formats;
/** whether the user data is selected or not */
selected;
/** state of the user data */
state;
/**Creates a new DataItem. /**Creates a new DataItem.
* @param {any} data user data * @param {any} data user data
* @param {object} formats value formatters of the user data's property * @param {object} formats value formatters of the user data's property
*/ */
constructor(data, formats) { constructor(data, formats) {
this.index = null;
this.no = null;
this.data = data; this.data = data;
this._formats = formats; this._formats = formats;
this.selected = false; this.selected = false;
@ -292,6 +285,16 @@ class DataItem {
return parsed; return parsed;
} }
/**Replaces the current data with the new data and resets the state.
* @param {any} data new data
* @returns the DataItem
*/
replace(data) {
this.data = data;
this.state = null;
return this;
}
/**Returns a string converted from the template using the property values of the user data. /**Returns a string converted from the template using the property values of the user data.
* In the template, placeholder for the properties of the user data is specified like {property name}. * In the template, placeholder for the properties of the user data is specified like {property name}.
* @param {string} template template string * @param {string} template template string
@ -303,10 +306,18 @@ class DataItem {
if (formatter) { if (formatter) {
str = formatter(str, this); str = formatter(str, this);
} }
for (let p in this.data) { let empty = Object.entries(this.data).length < 1
let regexp = this._formats.regexp(p); if (!empty)
str = str.replace(regexp, this.getValue(p)); for (let p in this.data) {
} let regexp = this._formats.regexp(p);
str = str.replace(regexp, this.getValue(p));
}
str = str.replace(/{data-index}/gi, this.index)
.replace(/{data-no}/gi, this.no);
if (empty)
str = str.replace(/{([^}]+)}/g, "");
return str; return str;
} }
} }
@ -357,15 +368,6 @@ class DataItem {
* </p> * </p>
*/ */
class Dataset { class Dataset {
_items;
_byKeys;
_current;
/**Dataset configuration
*/
conf;
_formats;
/**Creates a new Dataset with a configuration. /**Creates a new Dataset with a configuration.
* The configuration is an object with which you specify * The configuration is an object with which you specify
* <ul> <li>keymapper - function that returns a key of a user data. Used to identify user data in the Dataset. Mandatory.</li> * <ul> <li>keymapper - function that returns a key of a user data. Used to identify user data in the Dataset. Mandatory.</li>
@ -386,17 +388,20 @@ class Dataset {
* </ul> * </ul>
* @param conf {object} configuration * @param conf {object} configuration
*/ */
constructor(conf) { constructor(conf = {}) {
this._items = []; this._items = [];
this.keys = conf.keys || [];
this._byKeys = {}; this._byKeys = {};
this._current = null; this._current = null;
this.conf = notEmpty(conf, "conf is required but missing"); this.conf = notEmpty(conf, "conf is required but missing");
notEmpty(conf.keymapper, "keymapper is required but missing");
let keymapper = conf.keymapper; let keymapper = conf.keymapper;
conf.keymapper = info => { if (keymapper)
return keymapper(info) || info._tmpKey; conf.keymapper = info => {
}; return keymapper(info) || info._tmpKey;
};
this.keymapped = !isEmpty(this.conf.keymapper);
this._formats = new ValueFormat(conf.formats); this._formats = new ValueFormat(conf.formats);
this._sorter = {by: ""}; this._sorter = {by: ""};
this.dataBinder = dataBinder.create(this, conf.doctx); this.dataBinder = dataBinder.create(this, conf.doctx);
@ -404,6 +409,7 @@ class Dataset {
if (!conf.trace) if (!conf.trace)
this.log = () => {}; this.log = () => {};
[ "onDatasetChange", [ "onDatasetChange",
"onCurrentChange", "onCurrentChange",
"onSelectionChange", "onSelectionChange",
@ -428,12 +434,20 @@ class Dataset {
} }
/**Returns the key of a user data. /**Returns the key of a user data.
* @param {any|DataItem} info user data or {@link DataItem dataItem} of a user data * @param {any|DataItem} item user data or {@link DataItem dataItem} of a user data
* @returns {string} key of a user data * @returns {string} key of a user data
*/ */
getKey(info) { getKey(item) {
let data = info ? info.data || info : null; if (!item)
return data ? this.conf.keymapper(data) : null; return null;
let dataItem = item instanceof DataItem,
info = dataItem ? item.data : item;
if (this.keymapped)
return this.conf.keymapper(info);
else if (dataItem)
return item.index;
throw "Unable to determine the key of " + info;
} }
/**Returns keys of the Dataset's user data. /**Returns keys of the Dataset's user data.
@ -457,7 +471,7 @@ class Dataset {
* let removed = dirties.removed; * let removed = dirties.removed;
*/ */
getKeys(status){ getKeys(status){
let dataset = this.getDataset(status); let dataset = this.getDataset(status, "item");
if ("dirty" != status) if ("dirty" != status)
return dataset.map(e => this.getKey(e)); return dataset.map(e => this.getKey(e));
@ -479,9 +493,20 @@ class Dataset {
* let dataItem = dataset.getData("key-0", "item"); * let dataItem = dataset.getData("key-0", "item");
*/ */
getData(key, option) { getData(key, option) {
let item = this._byKeys["key-" + key]; if (this.empty)
if (!item) return null;
item = this.getTempItem();
let item = null;
if (this.keymapped) {
item = this._byKeys["key-" + key];
if (!item)
item = this.getTempItem();
} else {
let index = key,
found = this._items.filter(item => index == item.index);
item = found.length > 0 ? found[0] : null;
}
if (!item || item.unreachable) if (!item || item.unreachable)
return null; return null;
return "item" == option ? item : item.data; return "item" == option ? item : item.data;
@ -500,28 +525,17 @@ class Dataset {
* @param {object} obj optional information * @param {object} obj optional information
* @returns {Dataset} the Dataset * @returns {Dataset} the Dataset
*/ */
setData(obj, option) { setData(obj, option = {}) {
this._byKeys = {}; this._byKeys = {};
this._current = null; this._current = null;
obj = obj || {}; obj = obj || {};
let data = this._getDataItems(obj); let data = this._getDataItems(obj, option);
this._items = data.items; this._items = data.items;
this._byKeys = data.byKeys; this._byKeys = data.byKeys;
this._sorter = {by: ""}; this._sorter = {by: ""};
/*
obj = obj || {};
let array = Array.isArray(obj) ? obj : this.conf.dataGetter(obj) || [];
if (!Array.isArray(array))
throw new Error("The data must be an array");
this._items = array.map(e => new DataItem(e, this._formats));
this._items.forEach(item => {
let key = "key-" + this.getKey(item.data);
this._byKeys[key] = item;
});
*/
this.onDatasetChange(obj, option); this.onDatasetChange(obj, option);
this.setState(obj.state); this.setState(obj.state);
this.onDirtiesChange(this.dirty); this.onDirtiesChange(this.dirty);
@ -529,17 +543,34 @@ class Dataset {
return this; return this;
} }
_getDataItems(obj) { _getDataItems(obj, option) {
obj = obj || {}; obj = obj || {};
let array = Array.isArray(obj) ? obj : this.conf.dataGetter(obj) || []; let array = Array.isArray(obj) ? obj : this.conf.dataGetter(obj) || [];
if (!Array.isArray(array)) if (!Array.isArray(array))
throw new Error("The data must be an array"); throw new Error("The data must be an array");
let _items = array.map(e => new DataItem(e, this._formats)), let prefix = "ndx-" + new Date().getTime(),
_byKeys = {}; _items = array.map(e => new DataItem(e, this._formats)),
_items.forEach(item => { _byKeys = {},
let key = "key-" + this.getKey(item.data); length = this._items.length,
_byKeys[key] = item; noStart = 0;
if (option.pagination)
noStart = option.pagination.start || 0;
else {
if (length > 0) {
let last = this._items[length - 1];
noStart = last.no;
}
}
_items.forEach((item, index) => {
if (this.keymapped) {
let key = "key-" + this.getKey(item.data);
_byKeys[key] = item;
}
item.index = prefix + index;
item.no = ++noStart;
}); });
return { return {
@ -561,12 +592,13 @@ class Dataset {
* @param {object} obj optional information * @param {object} obj optional information
* @returns {Dataset} the Dataset * @returns {Dataset} the Dataset
*/ */
addData(obj, option) { addData(obj, option = {}) {
if (this.empty) if (this.empty)
return this.setData(obj); return this.setData(obj, option);
let state = this.state; let state = this.state;
let data = this._getDataItems(obj); option.added = true;
let data = this._getDataItems(obj, option);
this._items = this._items.concat(data.items); this._items = this._items.concat(data.items);
this._byKeys = { this._byKeys = {
...this._byKeys, ...this._byKeys,
@ -614,10 +646,10 @@ class Dataset {
this._items.sort((item0, item1) => { this._items.sort((item0, item1) => {
let val0 = (item0.data || {})[by], let val0 = (item0.data || {})[by],
val1 = (item1.data || {})[by]; val1 = (item1.data || {})[by];
if (val0 === undefined || val1 === undefined) if (val0 === undefined || val1 === undefined) return 0;
throw "Property not found: " + by;
if (isEmpty(val0) && isEmpty(val1)) return 0; if (isEmpty(val0) && isEmpty(val1)) return 0;
if (!this._sorter.asc) if (!this._sorter.asc)
[val0, val1] = [val1, val0]; [val0, val1] = [val1, val0];
@ -689,10 +721,18 @@ class Dataset {
} }
getTempItem(current) { getTempItem(current) {
let found = this._items.filter(item => item.data._tmpKey); let found = this._items.filter(item => item.data._tmpKey || item.isNew());
found = found.length > 0 ? found[0] : null; found = found.length > 0 ? found[0] : null;
if (current) if (this.keymapped) {
this.setCurrent(); if (current)
this.setCurrent();
} else {
if (found && current) {
this._current = found;
this.dataBinder.onCurrentChange(found);
this.onCurrentChange(found);
}
}
return found; return found;
} }
@ -726,8 +766,9 @@ class Dataset {
get state() { get state() {
let empty = this.empty, let empty = this.empty,
self = this; self = this;
return { return {
currentKey:!empty ? self.getKey(self.getCurrent()) : null, currentKey:!empty ? self.getKey(self.getCurrent("item")) : null,
selectedKeys:!empty ? self.getKeys("selected") : [] selectedKeys:!empty ? self.getKeys("selected") : []
}; };
} }
@ -753,8 +794,9 @@ class Dataset {
this.onDirtiesChange(false); this.onDirtiesChange(false);
} else { } else {
state = state || this.state; state = state || this.state;
let current = this.getData(state.currentKey) || this.getDataset()[0], let current = this.getData(state.currentKey, "item") || this.getDataset("item")[0],
currentKey = this.getKey(current); currentKey = this.getKey(current);
this.onSort(this.sorter); this.onSort(this.sorter);
this.setCurrent(currentKey, true); this.setCurrent(currentKey, true);
this.select(state.selectedKeys || [], true, true); this.select(state.selectedKeys || [], true, true);
@ -896,7 +938,8 @@ class Dataset {
fire = args[2]; fire = args[2];
} }
if (dirty || fire) { if (dirty || fire) {
this.onSelectionChange(this.getDataset("selected")); let selected = this.getDataset("selected", this.keymapped ? undefined : "item");
this.onSelectionChange(selected);
} }
return dirty; return dirty;
} }
@ -914,8 +957,10 @@ class Dataset {
*/ */
toggle(key) { toggle(key) {
let item = this.getData(key, "item"), let item = this.getData(key, "item"),
status = item ? item.toggle() : false; status = item ? item.toggle() : false,
this.onSelectionChange(this.getDataset("selected")); selected = this.getDataset("selected", this.keymapped ? undefined : "item");
this.onSelectionChange(selected)
return status; return status;
} }
@ -934,26 +979,41 @@ class Dataset {
if (!data) return this; if (!data) return this;
let found = this.getTempItem(true); let found = this.getTempItem(true);
if (found) { if (found)
return found; return found;
}
let notDirty = !this.dirty, let notDirty = !this.dirty,
array = Array.isArray(data) ? data : [data]; array = Array.isArray(data) ? data : [data],
array.forEach(e => { now = new Date().getTime(),
length = this._items.length,
noStart = 0,
added = [];
if (length > 0) {
let last = this._items[length - 1];
noStart = last.no;
}
array.forEach((e, index) => {
let item = new DataItem(e, this._formats); let item = new DataItem(e, this._formats);
this._items.push(item); this._items.push(item);
let key = this.getKey(e); if (this.keymapped) {
if (!key) let key = this.getKey(e);
e._tmpKey = key = new Date().getTime() if (!key)
this._byKeys["key-" + key] = item; e._tmpKey = key = now + index;
this._byKeys["key-" + key] = item;
} else {
item.index = ("ndx-" + now) + index;
item.no = ++noStart;
}
item.state = "added"; item.state = "added";
added.push(item);
}); });
let state = this.state; let state = this.state;
this.onAppend(array); this.onAppend(array);
state.currentKey = this.getKey(array[array.length - 1]); let last = added[added.length - 1];
state.currentKey = this.keymapped ? this.getKey(last.data) : last.index;
this.setState(state); this.setState(state);
if (notDirty) if (notDirty)
@ -1057,31 +1117,44 @@ class Dataset {
if (isEmpty(replacement)) return this; if (isEmpty(replacement)) return this;
let replacements = Array.isArray(replacement) ? replacement : [replacement], let replacements = Array.isArray(replacement) ? replacement : [replacement],
replacing = []; replacing = [],
getKey = obj => {
return this.keymapped ?
(obj.key || this.getKey(obj.data)) :
this.keys.reduce((acc, cur) => {
acc[cur] = obj.data[cur];
return acc;
}, {}
);
},
getItem = key => {
if (this.keymapped)
return this.getData(key, "item");
let entries = Object.entries(key),
found = this._items.filter(item => {
for (let e of entries) {
let k = e[0],
v = e[1];
if (v != item.data[k])
return false;
}
return true;
});
return found.length < 1 ? null : found[0];
};
replacements.forEach(obj => { replacements.forEach(obj => {
let data = obj.data; let data = obj.data;
if (!data) return; if (!data) return;
let key = obj.key || this.getKey(data); let key = getKey(obj);
if (!key) return; if (!key) return;
let oldItem = this.getData(key, "item"), let item = getItem(key);
newItem = new DataItem(data, this._formats), if (!item) return;
pos = oldItem ? this._items.indexOf(oldItem) : -1;
newItem.selected = oldItem && oldItem.selected;
if (pos > -1)
this._items[pos] = newItem;
else
this._items.push(newItem);
delete this._byKeys["key-" + key];
this._byKeys["key-" + this.getKey(data)] = newItem;
if (this._current == oldItem) item.replace(data);
this._current = newItem; replacing.push(item);
replacing.push(newItem);
}); });
this.onReplace(replacing); this.onReplace(replacing);
this.setState(); this.setState();
@ -1108,7 +1181,7 @@ class Dataset {
let before = this.dirty, let before = this.dirty,
keys = Array.isArray(key) ? key : [key], keys = Array.isArray(key) ? key : [key],
removed = this._items.filter(item => { removed = this._items.filter(item => {
let k = this.getKey(item.data), let k = this.getKey(this.keymapped ? item.data : item),
remove = keys.includes(k); remove = keys.includes(k);
if (remove) { if (remove) {
item.state = "added" == item.state ? "ignore" : "removed"; item.state = "added" == item.state ? "ignore" : "removed";
@ -1167,7 +1240,7 @@ class Dataset {
let before = this.dirty, let before = this.dirty,
keys = Array.isArray(key) ? key : [key], keys = Array.isArray(key) ? key : [key],
erased = this._items.filter(item => { erased = this._items.filter(item => {
let k = this.getKey(item.data), let k = this.getKey(this.keymapped ? item.data : item),
erase = keys.indexOf(k) > -1; erase = keys.indexOf(k) > -1;
if (erase) { if (erase) {
delete this._byKeys["key-" + k]; delete this._byKeys["key-" + k];
@ -1216,7 +1289,7 @@ class Dataset {
let dataset = this.getDataset("item"); let dataset = this.getDataset("item");
return dataset.filter(item => !item.data._tmpKey) return dataset.filter(item => !item.data._tmpKey)
.map(item => item.inString(template, formatter)); .map((item, index) => item.inString(template, formatter));
} }
/**Returns a property value of user data. /**Returns a property value of user data.
@ -1231,20 +1304,24 @@ class Dataset {
*/ */
getValue(...args) { getValue(...args) {
let key = null, let key = null,
property = null; property = null,
item = null;
switch (args.length) { switch (args.length) {
case 1: case 1:
key = this.getKey(this.getCurrent()); //key = this.getKey(this.getCurrent());
property = args[0]; property = args[0];
item = this.getCurrent("item");
break; break;
case 2: case 2:
key = args[0]; key = args[0];
property = args[1]; property = args[1];
item = this.getData(key, "item");
break; break;
default: return null; default: return null;
} }
let item = this.getData(key, "item"); // let item = this.getData(key, "item");
return item ? item.getValue(property) : undefined; return item ? item.getValue(property) : undefined;
} }
@ -1268,7 +1345,7 @@ class Dataset {
value = null; value = null;
switch (args.length) { switch (args.length) {
case 2: case 2:
key = this.getKey(this.getCurrent()); key = this.getKey(this.getCurrent(this.keymapped ? undefined : "item"));
property = args[0]; property = args[0];
value = args[1]; value = args[1];
break; break;
@ -1279,6 +1356,7 @@ class Dataset {
break; break;
default: return this; default: return this;
} }
return this.modify(key, function(item){ return this.modify(key, function(item){
return item.setValue(property, value); return item.setValue(property, value);
}); });
@ -1338,10 +1416,15 @@ class Dataset {
class DatasetControl { class DatasetControl {
constructor(conf) { constructor(conf) {
notEmpty(conf.keymapper, "keymapper");
this.prefix = conf.prefix; this.prefix = conf.prefix;
this.prefixName = conf.prefixName; this.prefixName = conf.prefixName;
this.doctx = conf.doctx || ""; // this.doctx = conf.doctx || "";
this.doq = new DomQuery().setContainers(conf.doctx);
if (conf.addOns) {
conf.addOns.forEach(addOn => addOn.doq = this.doq);
}
this.infoSize = conf.infoSize; this.infoSize = conf.infoSize;
this.appendData = conf.appendData; this.appendData = conf.appendData;
@ -1360,6 +1443,7 @@ class DatasetControl {
*/ */
this.onModify(props, modified, current); this.onModify(props, modified, current);
}; };
conf.onDirtiesChange = dirty => this.onDirtiesChange(dirty);
conf.onReplace = obj => this.onReplace(obj); conf.onReplace = obj => this.onReplace(obj);
conf.onSort = status => this.onSort(status); conf.onSort = status => this.onSort(status);
@ -1374,6 +1458,10 @@ class DatasetControl {
}; };
} }
get addOns() {
return this.dataset.addOns;
}
prefixed(str) { prefixed(str) {
return (this.prefix || "") + str; return (this.prefix || "") + str;
} }
@ -1387,8 +1475,7 @@ class DatasetControl {
this._load(); this._load();
} }
_load(option) { _load(option = {}) {
option = option || {};
if (!this.query.pageNum) if (!this.query.pageNum)
this.query.pageNum = 1; this.query.pageNum = 1;
@ -1443,11 +1530,22 @@ class DatasetControl {
}); });
} }
setData(obj, option) { setData(obj, option = {}) {
this.setPaging(obj, option);
this.dataset.setData(obj, option); this.dataset.setData(obj, option);
} }
addData(obj, option) { setPaging(obj, option) {
let prefix = this.prefix || obj.prefix;
if (!prefix || !obj[prefix + "Paging"]) return;
option.pagination = obj[prefix + "Paging"];
option.pagination.prefix = prefix;
delete obj[prefix + "Paging"];
}
addData(obj, option = {}) {
this.setPaging(obj, option);
this.dataset.addData(obj, option); this.dataset.addData(obj, option);
} }
@ -1463,8 +1561,11 @@ class DatasetControl {
return this.dataset.getCurrent(option); return this.dataset.getCurrent(option);
} }
toObject(item) { toObject(item = this.getCurrent("item")) {
return this.dataBinder().toObject(item || this.getCurrent("item")); if (this.dataset.keymapped)
return this.dataBinder().toObject(item);
if (this.addOns.currentData)
return this.addOns.currentData.getData(item);
} }
dataBinder() { dataBinder() {
@ -1515,7 +1616,8 @@ class DatasetControl {
setInfo(info) {} setInfo(info) {}
newInfo(obj) { newInfo(obj) {
this.dataset.append(obj || {}); if (this.dataset.keymapped)
this.dataset.append(obj || {});
this.getInfo(); this.getInfo();
} }
@ -1535,6 +1637,10 @@ class DatasetControl {
debug("on modify", props, "modified", modified, "current", current); debug("on modify", props, "modified", modified, "current", current);
} }
onDirtiesChange(dirty) {
debug("on dirties change", dirty);
}
onReplace(replacing) { onReplace(replacing) {
debug("on replace", replacing); debug("on replace", replacing);
} }
@ -1585,23 +1691,15 @@ class DatasetControl {
} }
selector(selector) { selector(selector) {
return this.dataBinder().selector(selector); return this.doq.selector(selector);
} }
querySelector(selector) { find(...args) {
return this.dataBinder().querySelector(selector); return this.doq.find(...args);
} }
querySelectorAll(selector) { findAll(...args) {
return this.dataBinder().querySelectorAll(selector); return this.doq.findAll(...args);
}
find(name) {
return this.querySelector(!name.startsWith("#") ? "[name='" + name + "']" : name);
}
findAll(name) {
return this.querySelectorAll("[name='" + name + "']");
} }
} }
@ -1644,7 +1742,7 @@ var dataBinder = {
}, },
create: (dataset, doctx) => { create: (dataset, doctx) => {
if (!dataset || isEmpty(doctx)) if (!dataset || isEmpty(doctx) || !dataset.keymapped)
return { return {
onCurrentChange: item => {}, onCurrentChange: item => {},
onModify: (changed, item) => {}, onModify: (changed, item) => {},
@ -1708,6 +1806,7 @@ var dataBinder = {
}); });
}; };
obj.onModify = (changed, item) => { obj.onModify = (changed, item) => {
return;
obj.inputs().forEach(input => { obj.inputs().forEach(input => {
let prop = dataBinder.dataMap(input) || dataBinder.property(input); let prop = dataBinder.dataMap(input) || dataBinder.property(input);
if (!changed.includes(prop)) return; if (!changed.includes(prop)) return;
@ -1739,26 +1838,26 @@ var dataBinder = {
*/ */
function tableSorter(ctrl, selector) { function tableSorter(ctrl, selector) {
let obj = { let obj = {
asc: "&#11205", asc: "sort-asc",
desc: "&#11206", desc: "sort-desc",
sortable: "sortable",
ctrl: ctrl, ctrl: ctrl,
headers: () => ctrl.querySelectorAll(selector), headers: () => ctrl.findAll(selector),
sort: (evt) => { sort: (evt) => {
let th = evt.target; let th = evt.target;
ctrl.sort(th.getAttribute("data-field")); ctrl.sort(th.getAttribute("data-sort"));
} }
}; };
obj.setHeaders = (sorter) => { obj.setHeaders = (sorter) => {
obj.headers().forEach(th => { obj.headers().forEach(th => {
let inner = th.innerHTML; th.classList.remove(obj.sortable, obj.asc, obj.desc);
if (th.getAttribute("data-sort") == sorter.by)
inner = inner.replace(/ ⯅/gi, "").replace(/ ⯆/gi, ""); th.classList.add(obj[sorter.order]);
if (th.getAttribute("data-field") == sorter.by) else
inner += " " + obj[sorter.order] || ""; th.classList.add(obj.sortable);
th.innerHTML = inner;
}); });
}; };
ctrl.onSort = (sorter) => { ctrl.onSort = (sorter) => {
@ -1768,23 +1867,8 @@ function tableSorter(ctrl, selector) {
}; };
obj.headers().forEach(th => { obj.headers().forEach(th => {
th.style["cursor"] = "pointer";
th.removeEventListener("click", obj.sort); th.removeEventListener("click", obj.sort);
th.addEventListener("click", obj.sort); th.addEventListener("click", obj.sort);
}); });
return obj; return obj;
/*
asc: {style: "sorting_asc", attr: "ascending"},
desc: {style: "sorting_desc", attr: "descending"},
setHeaders: (headers, sorter) => {
headers.forEach(th => {
th.classList.remove("sorting_asc", "sorting_desc");
th.removeAttribute("aria-sort");
if (th.getAttribute("data-field") != sorter.by) return;
th.classList.add(sorting[sorter.order].style);
th.setAttribute("aria-sort", sorting[sorter.order].attr);
});
}
*/
} }
Loading…
Cancel
Save