From f502c0db1a8050d125cccf0c6e31e7ef1ac24592 Mon Sep 17 00:00:00 2001 From: mjkhan21 Date: Thu, 22 Aug 2024 14:18:26 +0900 Subject: [PATCH] =?UTF-8?q?tableSorter,=20dataBinder=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/webapp/resources/css/styles.css | 4 + .../resources/js/base/dataset-support.js | 83 ++++-- src/main/webapp/resources/js/base/dataset.js | 258 +++++------------- 3 files changed, 128 insertions(+), 217 deletions(-) diff --git a/src/main/webapp/resources/css/styles.css b/src/main/webapp/resources/css/styles.css index bcc479e..0b272c7 100644 --- a/src/main/webapp/resources/css/styles.css +++ b/src/main/webapp/resources/css/styles.css @@ -65,6 +65,10 @@ .current-row {background-color: rgba(105, 108, 255, 0.16);} .current-row td:not(:last-child) {border-right: 1px solid white;} +.table thead { + background-color: #fcfdfd; +} + .table th {font-size:1rem;} .col-form-label {font-size:0.9375rem;} diff --git a/src/main/webapp/resources/js/base/dataset-support.js b/src/main/webapp/resources/js/base/dataset-support.js index 7c4dfb1..64fb926 100644 --- a/src/main/webapp/resources/js/base/dataset-support.js +++ b/src/main/webapp/resources/js/base/dataset-support.js @@ -13,7 +13,8 @@ class DatasetSupport { */ constructor(conf) { this.selector = conf.selector || null; - this.dataset = conf.ctrl.dataset; + this.ctrl = conf.ctrl; + this.dataset = this.ctrl.dataset; this._doq = conf.ctrl.doq; } @@ -51,6 +52,8 @@ class TableSupport extends DatasetSupport { this.selector = conf.table; this.tr = conf.tr; this.notFound = conf.notFound; + this.parent = null; + this.next = false; this.formatter = conf.formatter; this.selectionToggler = conf.selectionToggler || ""; @@ -58,10 +61,10 @@ class TableSupport extends DatasetSupport { this.init(); } - + init() { this.body = this.find(this.selector + " tbody"); - + if (this.tr) { let template = this.find(this.tr); this.tr = (template || {}).innerHTML || ""; @@ -70,7 +73,7 @@ class TableSupport extends DatasetSupport { 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) @@ -78,14 +81,33 @@ class TableSupport extends DatasetSupport { this.tr = (templates[0] || {}).innerHTML; this.notFound = (templates[1] || {}).innerHTML; } - + + this.setLoadNext(); + let sort = evt => { let th = evt.target; this.dataset.sort(th.getAttribute("data-sort")); }; this.sortables().forEach(th => th.addEventListener("click", sort)); } - + + setLoadNext() { + if (!this.ctrl.appendData) return; + + let table = this.find(this.selector), + parent = table ? table.parentElement : null; + if (!parent) return; + + parent.style["overflow-y"] = "auto"; + this.parent = parent; + this.parent.addEventListener("scrollend", (evt) => { + if (!this.next) return; + + let pageNum = this.ctrl.query.pageNum || 0; + this.ctrl.load(pageNum + 1); + }); + } + sortables() { return this.findAll(this.selector + " thead th[data-sort]"); } @@ -116,13 +138,26 @@ class TableSupport extends DatasetSupport { this.findAll(".enable-onfound").forEach(e => e.disabled = this.dataset.empty); } + getTop(option) { + return this.ctrl.appendData && option.reloaded && this.parent ? this.parent.scrollTop : 0; + } + draw(option = {}) { - let empty = this.dataset.empty, + if (!option.reloaded && option.pagination) { + this.next = option.pagination.next; + } + + let prevTop = this.getTop(option), + empty = this.dataset.empty, trs = !empty ? this.dataset.inStrings(this.tr, this.formatter) : [this.notFound]; this.body.innerHTML = trs.join(""); - + + let curTop = this.getTop(option); + if (prevTop != curTop) + this.parent.scrollTo(curTop, this.parent.scrollLeft); + if (!this.selectionToggler) return; - + let toggler = this.find(this.selectionToggler); toggler.checked = false; toggler.disabled = empty; @@ -133,7 +168,7 @@ class TableSupport extends DatasetSupport { */ setCurrentRow(item) { if (!item) return; - + let index = item.index; this.findAll(this.selector + " tbody tr").forEach(tr => { let dataIndex = tr.getAttribute("data-index"), @@ -144,7 +179,7 @@ class TableSupport extends DatasetSupport { tr.classList.remove(TableSupport.cssClass.current); }); } - + /**Handler called back on the selection change event * @param {array} selected selected DataItems */ @@ -154,11 +189,11 @@ class TableSupport extends DatasetSupport { .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 @@ -174,7 +209,7 @@ class TableSupport extends DatasetSupport { break; } if (!refresh) return; - + this.draw(); this.dataset.setState(); } @@ -206,7 +241,7 @@ class CurrentDataSupport extends DatasetSupport { || input.name || input.id; } - + update(input, value) { switch (this.type(input)) { case "radio": @@ -263,7 +298,7 @@ class CurrentDataSupport extends DatasetSupport { case 1: let cb = inputs[0]; if (cb.checked) break; - + switch (val) { case "true": val = "false"; break; case "y": val = "n"; break; @@ -285,14 +320,14 @@ class CurrentDataSupport extends DatasetSupport { 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); @@ -307,7 +342,7 @@ class CurrentDataSupport extends DatasetSupport { 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); @@ -318,7 +353,7 @@ class CurrentDataSupport extends DatasetSupport { */ enableOnDirty(item) { let dirty = item.dirty; - + this.findAll(".enable-ondirtyitem") .forEach(e => e.disabled = !dirty); } @@ -328,11 +363,11 @@ class CurrentDataSupport extends DatasetSupport { */ 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); @@ -344,7 +379,7 @@ class CurrentDataSupport extends DatasetSupport { init(data); return data; } - + getData(item = this.dataset.getCurrent("item")) { return this.findAll(this.selector).reduce((data, input) => { let dataField = this.dataField(input), diff --git a/src/main/webapp/resources/js/base/dataset.js b/src/main/webapp/resources/js/base/dataset.js index 79e3558..cf729a2 100644 --- a/src/main/webapp/resources/js/base/dataset.js +++ b/src/main/webapp/resources/js/base/dataset.js @@ -404,7 +404,6 @@ class Dataset { this._formats = new ValueFormat(conf.formats); this._sorter = {by: ""}; - this.dataBinder = dataBinder.create(this, conf.doctx); if (!conf.trace) this.log = () => {}; @@ -729,7 +728,6 @@ class Dataset { } else { if (found && current) { this._current = found; - this.dataBinder.onCurrentChange(found); this.onCurrentChange(found); } } @@ -751,7 +749,6 @@ class Dataset { this._current = item; if (diff || fire) { - this.dataBinder.onCurrentChange(item); this.onCurrentChange(item); } } @@ -794,12 +791,21 @@ class Dataset { this.onDirtiesChange(false); } else { state = state || this.state; - let current = this.getData(state.currentKey, "item") || this.getDataset("item")[0], - currentKey = this.getKey(current); - - this.onSort(this.sorter); - this.setCurrent(currentKey, true); - this.select(state.selectedKeys || [], true, true); + if (!state.byKeyValue) { + let current = this.getData(state.currentKey, "item") || this.getDataset("item")[0], + currentKey = this.getKey(current); + + this.onSort(this.sorter); + this.setCurrent(currentKey, true); + this.select(state.selectedKeys || [], true, true); + } else { + let currentKey = this.indexOf(state.currentKey)[0], + selectedKeys = this.indexOf(...state.selectedKeys); + + this.onSort(this.sorter); + this.setCurrent(currentKey, true); + this.select(selectedKeys, true, true); + } } return this; } @@ -1072,13 +1078,11 @@ class Dataset { if (changed.length > 0) { if (!item.state) item.state = "modified"; - this.dataBinder.onModify(changed, item); this.onModify(changed, item, current); if (notDirty) this.onDirtiesChange(true); } else if (revert) { changed = Object.getOwnPropertyNames(data); - this.dataBinder.onModify(changed, item); this.onModify(changed, item, current); } @@ -1361,6 +1365,39 @@ class Dataset { return item.setValue(property, value); }); } + + keyValues(...args) { + return args.map(arg => + this.keys.reduce((obj, k) => { + obj[k] = arg[k]; + return obj; + }, {}) + ); + } + + indexOf(...keyValues) { + let ofKeys = item => { + let data = item.data; + + for (let kv of keyValues) { + let equal = true; + for (let entry of Object.entries(kv)) { + let k = entry[0], + v = entry[1]; + equal = equal && data[k] == v; + if (!equal) + break; + } + if (equal) + return equal; + } + + return false; + }; + return this._items + .filter(item => ofKeys(item)) + .map(item => item.index); + } /**Called back when user data are set. * @param {object|array} obj object that has user data or an array of user data @@ -1498,14 +1535,29 @@ class DatasetControl { let all = option.all, prev = option.prev, - state = this.dataset.state; + state = this.dataset.keymapped ? this.dataset.state : null; + if (!state) { + state = this.empty ? {currentKey: null, selectedKeys: []} : null; + if (!state) { + state = {}; + + let current = this.getCurrent(), + selected = this.getDataset("selected"); + current = this.dataset.keyValues(current); + selected = this.dataset.keyValues(...selected); + + state.currentKey = current.length > 0 ? current[0] : null; + state.selectedKeys = selected; + state.byKeyValue = true; + } + } option.state = state; if (!all) { this._load(option); } else { let query = Object.assign({}, this.query); - this.query.fetchSize = (query.pageNum || 1) * query.fetchSize; + this.query.fetchSize = this.dataset.length; //(query.pageNum || 1) * query.fetchSize; this.query.pageNum = 1; option.callback = () => {this.query = query;}; this._load(option); @@ -1556,15 +1608,6 @@ class DatasetControl { getCurrent(option) { return this.dataset.getCurrent(option); } - - toObject(item = this.getCurrent("item")) { - if (this.dataset.keymapped) - return this.dataBinder().toObject(item); - } - - dataBinder() { - return this.dataset.dataBinder; - } setCurrent(key) { this.dataset.setCurrent(key); @@ -1597,7 +1640,6 @@ class DatasetControl { size:this.infoSize, init:() => { let current = this.getCurrent("item"); - this.dataBinder().onCurrentChange(current); this.setInfo(current); } }); @@ -1695,174 +1737,4 @@ class DatasetControl { findAll(...args) { return this.doq.findAll(...args); } -} - -var dataBinder = { - type: input => input ? (input.getAttribute("type") || input.tagName).toLowerCase() : "", - dataMap: input => input.getAttribute("data-map"), - property: input => input.name || input.id, - - toArray(val) { - if (isEmpty(val)) - return []; - if (Array.isArray(val)) - return val; - if ("string" == typeof val) - return val.split(","); - return [val]; - }, - - setValue: (input, value) => { - value = value || ""; - switch (dataBinder.type(input)) { - case "radio": - case "checkbox": - input.checked = dataBinder.toArray(value).includes(input.value); - break; - case "select": - for (let option of input.options) { - option.selected = option.value == value; - } - break; - case "img": - if (!(input.src || "").endsWith(value)) - input.src = value; break; - default: - if (input.value !== undefined) - input.value = value; - else - input.innerHTML = value; - } - }, - - create: (dataset, doctx) => { - if (!dataset || isEmpty(doctx) || !dataset.keymapped) - return { - onCurrentChange: item => {}, - onModify: (changed, item) => {}, - toObject: () => null - }; - - let obj = { - selector: (selector) => doctx ? doctx.split(",").map(str => str + " " + selector).join(",") : selector, - querySelector: (selector) => document.querySelector(obj.selector(selector)), - querySelectorAll: (selector) => Array.from(document.querySelectorAll(obj.selector(selector))), - inputs: () => obj.querySelectorAll("[data-map]"), - - inputValue: (input) => { - let val = input.value; - switch (dataBinder.type(input)) { - case "radio": - return input.checked ? val : undefined; - case "checkbox": - let checks = obj.querySelectorAll("[name='" + input.name + "']"); - switch (checks.length) { - case 0: return undefined; - case 1: - if (input.checked) - return val; - if ("Y" == val) - return "N"; - if ('true' == val) - return 'false'; - return undefined; - default: - return checks - .filter(input => input.checked) - .map(input => input.value) - .join(","); - } - case "img": return input.src; - default: return val !== undefined ? val : input.innerHTML; - } - }, - setChanged: (evt) => { - let input = evt.target, - prop = dataBinder.dataMap(input) || dataBinder.property(input), - inputVal = obj.inputValue(input); - if (undefined === inputVal) return; - - dataset.setValue(prop, inputVal); - } - }; - - obj.onCurrentChange = (current) => { - if (!current) return; - - obj.inputs().forEach(input => { - let prop = dataBinder.dataMap(input) || dataBinder.property(input); - if (!prop) return; - - let evt = !["checkbox", "radio"].includes(dataBinder.type(input)) ? "change" : "click"; - input.removeEventListener(evt, obj.setChanged); - dataBinder.setValue(input, current.getValue(prop)); - input.addEventListener(evt, obj.setChanged); - }); - }; - obj.onModify = (changed, item) => { - return; - obj.inputs().forEach(input => { - let prop = dataBinder.dataMap(input) || dataBinder.property(input); - if (!changed.includes(prop)) return; - - dataBinder.setValue(input, item.getValue(prop)); - }); - }; - obj.toObject = (item) => { - let result = {}; - obj.inputs().forEach(input => { - let dataMap = dataBinder.dataMap(input), - property = dataBinder.property(input) || dataMap, - value = item.data[dataMap || property]; - - if (value !== undefined) - result[property] = value; - }); - return result; - } - - return obj; - }, -}; - - -/**로 표시되는 데이터셋의 데이터를 정렬하고 UI에 반영되도록 설정한다. - * @param ctrl {DatasetControl} DatasetControl - * @param selector 테이블 헤더에 대한 selector - */ -function tableSorter(ctrl, selector) { - let obj = { - asc: "sort-asc", - desc: "sort-desc", - sortable: "sortable", - - ctrl: ctrl, - - headers: () => ctrl.findAll(selector), - sort: (evt) => { - let th = evt.target; - ctrl.sort(th.getAttribute("data-sort")); - } - }; - - obj.setHeaders = (sorter) => { - obj.headers().forEach(th => { - th.classList.remove(obj.sortable, obj.asc, obj.desc); - if (th.getAttribute("data-sort") == sorter.by) - th.classList.add(obj[sorter.order]); - else - th.classList.add(obj.sortable); - }); - }; - ctrl.onSort = (sorter) => { - if (ctrl.renderList) - ctrl.renderList(); - obj.setHeaders(sorter); - }; - - obj.headers().forEach(th => { - th.removeEventListener("click", obj.sort); - th.addEventListener("click", obj.sort); - }); - return obj; } \ No newline at end of file