dataset ui binding 수정, upload-support 추가

master
mjkhan21 4 months ago
parent 92c3f11c08
commit 8543fe2c5d

@ -287,24 +287,6 @@ var json = {
} }
}; };
function upload(options) {
options.enctype = "multipart/form-data";
options.processData = options.contentType = false;
var data = options.data,
formData = new FormData();
for (var key in data) {
var val = data[key];
if (!Array.isArray(val))
formData.append(key, val);
else {
for (var i = 0; i < val.length; ++i)
formData.append(key, val[i]);
}
}
options.data = formData;
ajax.post(options);
}
$.fn.onEnterPress = function(handler) { $.fn.onEnterPress = function(handler) {
return this.each(function(){ return this.each(function(){
$(this).keypress(function(evt){ $(this).keypress(function(evt){
@ -693,11 +675,13 @@ $.fn.setCurrentRow = function(val) {
class FormFields { class FormFields {
constructor(selector) { constructor(selector) {
this.form = document.querySelector(this.selector = selector); this.form = document.querySelector(this.selector = selector);
this.children = ["input", "select", "textarea"].map(tag => this.selector + " " + tag).join(","); this.children = ["input", "select", "textarea", "img", "span"].map(tag => this.selector + " " + tag).join(",");
this.datamap = []; this.datamap = [];
this.ctrl = null;
} }
set(ctrl, obj) { set(ctrl, obj) {
this.ctrl = ctrl;
let setChanged = evt => { let setChanged = evt => {
let input = evt.target, let input = evt.target,
name = input.getAttribute("data-map"), name = input.getAttribute("data-map"),
@ -724,6 +708,8 @@ class FormFields {
option.selected = option.value == value; option.selected = option.value == value;
} }
break; break;
case "span": input.innerHTML = value; break;
case "img": input.src = value; break;
default: input.value = ifEmpty(value, ""); break; default: input.value = ifEmpty(value, ""); break;
} }
input.addEventListener("change", setChanged); input.addEventListener("change", setChanged);
@ -731,20 +717,30 @@ class FormFields {
} }
get() { get() {
let obj = {}; let obj = {},
current = this.ctrl.getCurrent();
if (!current)
return obj;
document.querySelectorAll(this.children).forEach(input => { document.querySelectorAll(this.children).forEach(input => {
let property = input.name || input.id; let field = input.getAttribute("data-map");
let value = input.value; if (!field) return;
let property = input.name || input.id,
value = input.value;
value = current[field];
obj[property] = value;
return;
if ("radio" == input.type) { if ("radio" == input.type) {
if (input.checked) if (input.checked)
obj[property] = value; obj[property] = value;
} else { } else {
obj[property] = value; obj[property] = value;
} }
// log(property, value, "radio" == input.type ? "radio" : "", input.checked ? "checked" : "");
}); });
return obj; return obj;
//return Object.fromEntries(new FormData(this.form).entries());
} }
}; };
@ -806,63 +802,3 @@ function inputsInRange(fromSource, toSource) {
function ignore() { function ignore() {
console.log.apply(console, arguments); console.log.apply(console, arguments);
} }
function fileInput(conf) {
conf = conf || {};
var name = conf.name || "upload",
tag = "<input type='file' name='{name}'{multiple}{accept} style='display:none;'>"
.replace(/{name}/, name)
.replace(/{multiple}/, conf.multiple ? " multiple" : "")
.replace(/{accept}/, conf.accept ? " accept='" + conf.accept + "'" : ""),
obj = {
name:name,
input:null,
files:[],
select:function(onSelect) {
if (!obj.input) {
var input = obj.input = $(tag);
$("body").append(input);
input.change(function() {
var files = $(this).get(0).files,
length = files.length,
selected = [];
for (var i = 0; i < length; ++i) {
var file = files[i];
file.id = name + (obj.files.length);
selected.push(file);
obj.files.push(file);
}
onSelect(selected);
})
}
obj.input.click();
},
getFiles:function() {
return obj.files;
},
remove:function(id) {
if (!id) return;
var files = obj.files;
for (var i = 0; i < files.length; ++i) {
var file = files[i];
if (id == file.id) {
files.splice(i, 1);
break;
}
}
},
clear:function() {
if (obj.input)
$(obj.input).remove();
obj.input = null;
obj.files.forEach(function(file) {delete file;});
obj.files = [];
}
};
return obj;
}

@ -383,7 +383,12 @@ class Dataset {
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"); notEmpty(conf.keymapper, "keymapper is required but missing");
let keymapper = conf.keymapper;
conf.keymapper = info => {
return keymapper(info) || info._tmpKey;
};
this._formats = new ValueFormat(conf.formats); this._formats = new ValueFormat(conf.formats);
this._dataBinder = dataBinder.create(this, conf.inputs);
if (!conf.trace) if (!conf.trace)
this.log = () => {}; this.log = () => {};
@ -463,6 +468,8 @@ class Dataset {
*/ */
getData(key, option) { getData(key, option) {
let item = this._byKeys["key-" + key]; let item = this._byKeys["key-" + key];
if (!item)
item = this.getTempItem();
if (!item || item.unreachable) if (!item || item.unreachable)
return null; return null;
return "item" == option ? item : item.data; return "item" == option ? item : item.data;
@ -481,7 +488,6 @@ class Dataset {
* @returns {Dataset} the Dataset * @returns {Dataset} the Dataset
*/ */
setData(obj) { setData(obj) {
let state = this.state;
this._byKeys = {}; this._byKeys = {};
this._current = null; this._current = null;
@ -503,8 +509,7 @@ class Dataset {
}); });
*/ */
this.onDatasetChange(obj); this.onDatasetChange(obj);
this.setState(obj.state || state); this.setState(obj.state);
// this.setState(!Array.isArray(obj) ? obj.state : state);
this.onDirtiesChange(this.dirty); this.onDirtiesChange(this.dirty);
return this; return this;
@ -602,6 +607,14 @@ class Dataset {
return "item" == option ? current : current.data; return "item" == option ? current : current.data;
} }
getTempItem(current) {
let found = this._items.filter(item => item.data._tmpKey);
found = found.length > 0 ? found[0] : null;
if (current)
this.setCurrent();
return found;
}
/**Sets the user data as current that is associated with the key. /**Sets the user data as current that is associated with the key.
* @param {string} key key to a user data * @param {string} key key to a user data
* After the data is set, the method * After the data is set, the method
@ -616,9 +629,11 @@ class Dataset {
this._current = item; this._current = item;
if (diff || fire) if (diff || fire) {
this._dataBinder.onCurrentChange(item);
this.onCurrentChange(item); this.onCurrentChange(item);
} }
}
/**Returns the Dataset's current state in an object. /**Returns the Dataset's current state in an object.
* The object has the properties as follows. * The object has the properties as follows.
@ -835,6 +850,11 @@ class Dataset {
append(data) { append(data) {
if (!data) return this; if (!data) return this;
let found = this.getTempItem(true);
if (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 => { array.forEach(e => {
@ -842,6 +862,8 @@ class Dataset {
this._items.push(item); this._items.push(item);
let key = this.getKey(e); let key = this.getKey(e);
if (!key)
e._tmpKey = key = new Date().getTime()
this._byKeys["key-" + key] = item; this._byKeys["key-" + key] = item;
item.state = "added"; item.state = "added";
}); });
@ -907,11 +929,14 @@ class Dataset {
if (changed.length > 0) { if (changed.length > 0) {
if (!item.state) if (!item.state)
item.state = "modified"; item.state = "modified";
this._dataBinder.onModify(changed, item);
this.onModify(changed, item, current); this.onModify(changed, item, current);
if (notDirty) if (notDirty)
this.onDirtiesChange(true); this.onDirtiesChange(true);
} else if (revert) { } else if (revert) {
this.onModify(Object.getOwnPropertyNames(data), item, current); changed = Object.getOwnPropertyNames(data);
this._dataBinder.onModify(changed, item);
this.onModify(changed, item, current);
} }
return this; return this;
@ -948,8 +973,7 @@ class Dataset {
replace(replacement) { replace(replacement) {
if (isEmpty(replacement)) return this; if (isEmpty(replacement)) return this;
let before = this.dirty, let replacements = Array.isArray(replacement) ? replacement : [replacement],
replacements = Array.isArray(replacement) ? replacement : [replacement],
replacing = []; replacing = [];
replacements.forEach(obj => { replacements.forEach(obj => {
let data = obj.data; let data = obj.data;
@ -977,10 +1001,7 @@ class Dataset {
replacing.push(newItem); replacing.push(newItem);
}); });
this.onReplace(replacing); this.onReplace(replacing);
let after = this.dirty; this.setState();
if (before != after)
this.onDirtiesChange(after);
return this; return this;
} }
@ -1109,7 +1130,9 @@ class Dataset {
* @returns {array} array of strings converted from the template using the property values of the user data * @returns {array} array of strings converted from the template using the property values of the user data
*/ */
inStrings(template, formatter) { inStrings(template, formatter) {
return this.getDataset("item") let dataset = this.getDataset("item");
return dataset.filter(item => !item.data._tmpKey)
.map(item => item.inString(template, formatter)); .map(item => item.inString(template, formatter));
} }
@ -1229,21 +1252,23 @@ class DatasetControl {
notEmpty(conf.keymapper, "keymapper"); 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.infoSize = conf.infoSize; this.infoSize = conf.infoSize;
this.appendData = conf.appendData; this.appendData = conf.appendData;
this.query = {}; this.query = {};
conf.onDatasetChange = obj => this.onDatasetChange(obj), conf.onDatasetChange = obj => this.onDatasetChange(obj);
conf.onCurrentChange = item => this.onCurrentChange(item), conf.onCurrentChange = item => this.onCurrentChange(item);
conf.onSelectionChange = selected => this.onSelectionChange(selected), conf.onSelectionChange = selected => this.onSelectionChange(selected);
conf.onAppend = items => this.onAppend(items), conf.onAppend = items => this.onAppend(items);
conf.onModify = (props, modified, current) => { conf.onModify = (props, modified, current) => {
let info = this.dataset.getCurrent("item"); let info = this.dataset.getCurrent("item");
if (!info || "added" == info.state) if (!info || "added" == info.state)
return; return;
this.onModify(props, modified, current); this.onModify(props, modified, current);
} };
conf.onReplace = obj => this.onReplace(obj);
this.dataset = new Dataset(conf); this.dataset = new Dataset(conf);
@ -1269,14 +1294,18 @@ class DatasetControl {
this._load(); this._load();
} }
_load() { _load(state, prev) {
if (!this.query.pageNum) if (!this.query.pageNum)
this.query.pageNum = 1; this.query.pageNum = 1;
if (prev)
this.query.pageNum = Math.max(1, this.query.pageNum - 1);
ajax.get({ ajax.get({
url:this.urls.load, url:this.urls.load,
data:this.query, data:this.query,
success:resp => { success:resp => {
if (!prev)
resp.state = state;
if (!this.appendData || this.query.pageNum == 1) if (!this.appendData || this.query.pageNum == 1)
this.setData(resp); this.setData(resp);
else { else {
@ -1286,6 +1315,10 @@ class DatasetControl {
}); });
} }
reload(prev) {
this._load(this.dataset.state, prev);
}
download(type) { download(type) {
this.query.download = type || "xls"; this.query.download = type || "xls";
let query = toQuery(this.query); let query = toQuery(this.query);
@ -1309,6 +1342,10 @@ class DatasetControl {
return this.dataset.getCurrent(option); return this.dataset.getCurrent(option);
} }
currentToObject() {
return this.dataset._dataBinder.toObject(this.getCurrent("item"));
}
setCurrent(key) { setCurrent(key) {
this.dataset.setCurrent(key); this.dataset.setCurrent(key);
} }
@ -1326,7 +1363,6 @@ class DatasetControl {
} }
getInfo(params) { getInfo(params) {
let info = this.dataset.getCurrent("item");
if (this.urls.getInfo) if (this.urls.getInfo)
ajax.get({ ajax.get({
url:this.urls.getInfo, url:this.urls.getInfo,
@ -1339,12 +1375,16 @@ class DatasetControl {
title: this.prefixName + " 정보", title: this.prefixName + " 정보",
content:resp, content:resp,
size:this.infoSize, size:this.infoSize,
init:() => this.setInfo(info) init:() => {
let current = this.getCurrent("item");
this.dataset._dataBinder.onCurrentChange(current);
this.setInfo(current);
}
}); });
} }
}); });
else else
this.setInfo(info); this.setInfo(this.getCurrent("item"));
} }
setInfo(info) {} setInfo(info) {}
@ -1370,6 +1410,10 @@ class DatasetControl {
debug("on modify", props, "modified", modified, "current", current); debug("on modify", props, "modified", modified, "current", current);
} }
onReplace(replacing) {
debug("on replace", replacing);
}
save(info) { save(info) {
if (!info) return; if (!info) return;
let item = this.getCurrent("item"), let item = this.getCurrent("item"),
@ -1386,7 +1430,7 @@ class DatasetControl {
if (resp.saved) { if (resp.saved) {
dialog.alert("저장됐습니다."); dialog.alert("저장됐습니다.");
dialog.close(this.prefixed("dialog")); dialog.close(this.prefixed("dialog"));
this._load(); this.reload();
} }
} }
@ -1408,41 +1452,108 @@ class DatasetControl {
onRemove(selected, resp) { onRemove(selected, resp) {
if (resp.saved) if (resp.saved)
this._load(); this.reload(selected.length == this.dataset.length);
} }
bindInputs(obj, selector) { _selector(selector) {
let inputs = ["input", "select", "textarea"].map(tag => selector + " " + tag).join(","), let doctx = this._doctx;
setChanged = evt => { return doctx ? "*[data-doctx='" + doctx + "'] " + selector : selector;
let input = evt.target, }
name = input.getAttribute("data-map"),
val = input.value;
this.setValue(name, val);
};
document.querySelectorAll(inputs).forEach(input => { querySelector(selector) {
let prop = input.getAttribute("data-map") return document.querySelector(this._selector(selector));
|| input.name }
|| input.id;
if (!prop) return; querySelectorAll(selector) {
return document.querySelectorAll(this._selector(selector));
}
}
input.removeEventListener("change", setChanged); var dataBinder = {
type: input => {
return input ? (input.getAttribute("type") || input.tagName).toLowerCase() : "";
},
let dataItem = obj instanceof DataItem, dataMap: input => {
value = dataItem ? obj.getValue(prop) : obj[prop], return input.getAttribute("data-map");
inputType = (input.type || input.tagName || "").toLowerCase(); },
switch (inputType) { property: input => {
case "radio": input.checked = value && value == input.value; break; return input.name || input.id;
},
setValue: (input, value) => {
value = value || "";
switch (dataBinder.type(input)) {
case "radio":
case "checkbox": input.checked = value && value == input.value; break; case "checkbox": input.checked = value && value == input.value; break;
case "select": case "select":
for(let option of input.options) { for (let option of input.options) {
option.selected = option.value == value; option.selected = option.value == value;
} }
break; break;
default: input.value = ifEmpty(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;
} }
input.addEventListener("change", setChanged); },
create: (dataset, selector) => {
if (!dataset || isEmpty(selector))
return {
onCurrentChange: item => {},
onModify: (changed, item) => {},
toObject: () => null
};
let obj = {
selector: selector,
setChanged: (evt) => {
let input = evt.target,
prop = dataBinder.dataMap(input),
val = input.value;
dataset.setValue(prop, val);
}
};
obj.onCurrentChange = (current) => {
if (!current) return;
document.querySelectorAll(obj.selector).forEach(input => {
let prop = dataBinder.dataMap(input);
if (!prop) return;
input.removeEventListener("change", obj.setChanged);
dataBinder.setValue(input, current.getValue(prop));
input.addEventListener("change", obj.setChanged);
}); });
};
obj.onModify = (changed, item) => {
document.querySelectorAll(obj.selector).forEach(input => {
let prop = dataBinder.dataMap(input);
if (!changed.includes(prop)) return;
dataBinder.setValue(input, item.getValue(prop));
});
};
obj.toObject = (item) => {
let result = {};
document.querySelectorAll(obj.selector).forEach(input => {
let dataMap = dataBinder.dataMap(input),
property = dataBinder.property(input) || dataMap;
result[property] = item.data[dataMap];
});
return result;
} }
}
return obj;
},
};

@ -255,7 +255,7 @@ class TabControl extends Dataset {
} }
} }
open(url) { open(url, query) {
if (!url if (!url
|| url.startsWith("javascript:void") || url.startsWith("javascript:void")
|| url == this.sticky.url) || url == this.sticky.url)
@ -265,8 +265,9 @@ class TabControl extends Dataset {
if (tab) if (tab)
return this.setCurrent(tab.data.index); return this.setCurrent(tab.data.index);
let str = url + (query ? "?" + query : "");
ajax.get({ ajax.get({
url: wctx.url(url), url: wctx.url(str),
success: resp => { success: resp => {
let menu = this.getMenu(url); let menu = this.getMenu(url);
if (!menu) if (!menu)
@ -280,7 +281,7 @@ class TabControl extends Dataset {
menu.content = resp; menu.content = resp;
this.addData([menu]); this.addData([menu]);
this.open(url); this.open(url, query);
} }
}); });
} }

@ -0,0 +1,110 @@
function upload(options) {
options.enctype = "multipart/form-data";
options.processData = options.contentType = false;
var data = options.data,
formData = new FormData();
for (var key in data) {
var val = data[key];
if (!Array.isArray(val))
formData.append(key, val);
else {
for (var i = 0; i < val.length; ++i)
formData.append(key, val[i]);
}
}
options.data = formData;
ajax.post(options);
}
function fileInput(conf) {
conf = conf || {};
var name = conf.name || "upload",
tag = "<input type='file' name='{name}'{multiple}{accept} style='display:none;'>"
.replace(/{name}/, name)
.replace(/{multiple}/, conf.multiple ? " multiple" : "")
.replace(/{accept}/, conf.accept ? " accept='" + conf.accept + "'" : ""),
obj = {
name:name,
input:null,
files:[],
select:function(onSelect) {
if (!obj.input) {
var input = obj.input = $(tag);
$("body").append(input);
input.change(function() {
var files = $(this).get(0).files,
length = files.length,
selected = [];
for (var i = 0; i < length; ++i) {
var file = files[i];
file.id = name + (obj.files.length);
selected.push(file);
obj.files.push(file);
}
if (onSelect)
onSelect(selected);
})
}
obj.input.click();
},
getFiles:function() {
return obj.files;
},
getURLs:function() {
try {
return obj.files.map(file => (window.URL || window.webkitURL).createObjectURL(file));
} catch (e) {
return [];
}
},
remove:function(id) {
if (!id) return;
var files = obj.files;
for (var i = 0; i < files.length; ++i) {
var file = files[i];
if (id == file.id) {
files.splice(i, 1);
break;
}
}
},
clear:function() {
if (obj.input)
$(obj.input).remove();
obj.input = null;
obj.files.forEach(function(file) {delete file;});
obj.files = [];
}
};
return obj;
}
function uploadSupport(selector) {
let input = $(selector);
if (!input)
throw "Element not found: " + selector;
let fileset = new Dataset({
keymapper: info => info.id
});
input.change(function() {
var files = $(this).get(0).files,
length = files.length,
array = []
for (var i = 0; i < length; ++i) {
var file = files[i];
file.id = "file-" + new Date().getTime() + "-" + i;
file.url = (window.URL || window.webkitURL).createObjectURL(file);
array.push(file);
}
fileset.setData(array);
});
return fileset;
}
Loading…
Cancel
Save