From c884081cb50c7e63aade1ebe87c1bc84584f0de4 Mon Sep 17 00:00:00 2001 From: leebeomjun Date: Thu, 4 May 2023 19:27:12 +0900 Subject: [PATCH] =?UTF-8?q?fix=20:=20form=ED=83=9C=EA=B7=B8=20=EB=82=B4?= =?UTF-8?q?=EB=B6=80=20=EA=B0=92=20=EA=B0=80=EC=A0=B8=EC=98=A4=EB=8A=94=20?= =?UTF-8?q?=EB=B6=80=EB=B6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../framework/biz/mng/usr/mngUserMgtPopup.jsp | 7 +- .../jsp/include/fims/tail-framework.jsp | 19 +- src/main/webapp/resources/js/base/base.js | 808 ++++++++++++++++++ .../js/fims/framework/cmm/cmmUtil.js | 48 -- 4 files changed, 823 insertions(+), 59 deletions(-) create mode 100644 src/main/webapp/resources/js/base/base.js diff --git a/src/main/webapp/WEB-INF/jsp/fims/framework/biz/mng/usr/mngUserMgtPopup.jsp b/src/main/webapp/WEB-INF/jsp/fims/framework/biz/mng/usr/mngUserMgtPopup.jsp index 4e54c4f8..bac95a93 100644 --- a/src/main/webapp/WEB-INF/jsp/fims/framework/biz/mng/usr/mngUserMgtPopup.jsp +++ b/src/main/webapp/WEB-INF/jsp/fims/framework/biz/mng/usr/mngUserMgtPopup.jsp @@ -226,10 +226,13 @@ ,modify: () => { if (!fnBiz.validate()) return; - const data = getFormJson(document.getElementById('userInfoVO')); + let formFields = new FormFields("#userInfoVO"); + let data = formFields.get(); + data = JSON.stringify(data); + cmmBizAjax('modify', { url: '' - ,data: JSON.stringify(data) //$("#userInfoVO").serialize() + ,data: data ,contentType: 'application/json; charset=utf-8' }); } diff --git a/src/main/webapp/WEB-INF/jsp/include/fims/tail-framework.jsp b/src/main/webapp/WEB-INF/jsp/include/fims/tail-framework.jsp index e5295770..8b588412 100644 --- a/src/main/webapp/WEB-INF/jsp/include/fims/tail-framework.jsp +++ b/src/main/webapp/WEB-INF/jsp/include/fims/tail-framework.jsp @@ -1,21 +1,22 @@ <%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> - - - + + - - + - + - + - - + + + + + diff --git a/src/main/webapp/resources/js/base/base.js b/src/main/webapp/resources/js/base/base.js new file mode 100644 index 00000000..8b7dfcc8 --- /dev/null +++ b/src/main/webapp/resources/js/base/base.js @@ -0,0 +1,808 @@ +var wctx = { + path:"", + url:function(path) { + return this.path + path; + }, + home:function() { + top.location.href = wctx.url("/"); + }, + current:function() { + let str = top.location.href.replace(top.location.origin + wctx.path, ""); + let pos = str.indexOf("?"); + return pos < 0 ? str : str.substr(0, pos); + }, + trace:true +}; + +function log(msg) { + console.log.apply(console, arguments); +} + +function debug(msg) { + if (wctx && wctx.trace) + console.log.apply(console, arguments); +} + +function trim(s) { + if (null == s || undefined == s) return ""; + if ("string" != typeof(s)) return s; + return s.replace(/^\s+/, "").replace(/\s+$/, ""); +} + +function isEmpty(v) { + if (v == undefined + || v == "undefined" + || v == null + || v == "null" + ) return true; + + switch (typeof(v)) { + case "string": return "" == trim(v); + case "boolean": if (false == v) return false; + case "number": if (0 == v) return false; + default: return false; + } +} + +function ifEmpty(v, nv) { + if (!isEmpty(v)) return v; + if (typeof(nv) == "function") + return nv.apply(); + else + return nv; +} + +function notEmpty(obj, msg) { + if (isEmpty(obj)) + throw msg; + return obj; +} + +function toQuery(map, encode) { + if (isEmpty(map)) return ""; + + var query = []; + for (var key in map) { + var v = map[key]; + if (v != null && v == undefined) continue; + if (v != null) + switch (typeof(v)) { + case "object": + case "function": continue; + } + if (isEmpty(v)) + query.push(key); + else + query.push(key + "=" + (encode != false ? encodeURIComponent(v) : v)); + } + return query.join("&"); +} + +function uuid() { + var hexDigits = "0123456789abcde", + result = []; + + for (var i = 0; i < 36; ++i) + result[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1); + result[14] = "4"; + result[19] = hexDigits.substr((result[19] & 0x3) | 0x8, 1); + result[8] = result[13] = result[18] = result[23] = "-"; + + return result.join(""); +} + +function wait(show) { + if (show == false) + $(".wait").hide(); + else + $(".wait").show(); +} + +var dialog = { + title:"XIT", + template:null, + open:function(conf) { + if (this.template) + this.create(conf); + else { + var self = this; + ajax.get({ + url:wctx.url("/resources/html/dialog.html"), + success:function(resp) { + self.template = resp; + self.create(conf); + } + }); + } + }, + close:function(id) { + $("#close" + id).click(); + }, + create:function(conf) { + conf = conf || {}; + let id = conf.id || "dlg-" + uuid(), + backdropID = id + "-backdrop", + size = conf.size || "", + tmpl = this.template + .replace(/{id}/g, id) + .replace(/{title}/g, conf.title || dialog.title); + if (size) + size = " modal-" + size; + tmpl = tmpl.replace(/{size}/, size); + dlg = $(tmpl).appendTo("body"); + + dlg.find(".modal-body").html(conf.content || "").fadeIn(); + + dlg.on("hidden.bs.modal", function() {// on dialog close + $("#" + id +",#" + backdropID).remove(); // removes the dialog and its backdrop + if (conf.onClose) + conf.onClose(); + }); + + if (conf.onOK) { + let footer = dlg.find(".modal-footer"); + footer.show(); + footer.find("button").unbind("click").click(function() { + if (conf.getData) { + var selected = conf.getData.apply(); + if (!selected) return; + + conf.onOK(selected); + dialog.close(id); + } else { + conf.onOK(); + dialog.close(id); + } + }); + } else { + if (conf.timeout) + setTimeout(function(){dialog.close(id);}, conf.timeout); + } + + dlg.draggable().modal("show"); + $(".modal-backdrop").each(function() { // gives id to its backdrop + let backdrop = $(this); + if (!backdrop.prop("id")) + backdrop.prop("id", backdropID); + }); + + if (conf.init) + conf.init(); + }, + alert:function(conf) { + var container = "
{content}
"; + if ("string" == typeof(conf)) { + conf = { + content:container.replace(/{content}/g, conf) + }; + } else { + conf.content = container.replace(/{content}/g, conf.content); + } + conf.timeout = 2200; + this.open(conf); + } +}; + +function onError(xhr, options, error) { + if (xhr.readyState == 0) + return dialog.alert("서버에 접근할 수 없습니다."); + + var resp = JSON.parse(xhr.responseText); + if (resp.handler) + return eval(resp.handler); + + var msgs = []; + for (key in resp) + msgs.push(resp[key]) + msgs = msgs.join("
"); + if (msgs) + dialog.alert(msgs); +} + +var ajax = { + request:function(options) { + options.beforeSend = function(xhr) { + if (wctx.csrf) + xhr.setRequestHeader(wctx.csrf.header, wctx.csrf.token); + if (!options.silent) + wait(); + } + if (!options.type) { + if (options.data) + options.type = "POST"; + } + + var success = options.success; + options.success = function(resp) { + if ("string" == typeof resp) + resp = trim(resp); + + var stacktrace = resp.stacktrace; + delete resp.stacktrace; + + debug("response", resp); + + if (!resp.failed) + return success(resp); + + dialog.alert({ + content:resp.message, + onClose:"sessionExpired" == resp.status ? wctx.home : undefined + }); + + debug("stacktrace", stacktrace); + }; + var handleError = options.error || onError; + options.error = function(xhr, options, error) { + wait(false); + + debug("error", xhr, options, error); + handleError(xhr, options, error); + } + + var handleComplete = options.complete || function(){}; + options.complete = function() { + wait(false); + handleComplete(); + }; + debug("request", options); + $.ajax(options); + }, + get:function(options) { + options.type = "GET"; + ajax.request(options); + }, + post:function(options) { + options.type = "POST"; + ajax.request(options); + } +}; + +var json = { + with:function(options) { + options.dataType = "json"; + if (typeof(options.data) == "string") { + options.contentType = "application/json; charset=UTF-8"; + } + return options; + }, + get:function(options) { + ajax.get(json.with(options)); + }, + post:function(options) { + ajax.post(json.with(options)); + } +}; + +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) { + return this.each(function(){ + $(this).keypress(function(evt){ + if (!handler || evt.which != 13) return; + handler.apply(); + }); + }); +} + +function labelFor(input) { + var selector = "label[for='{id}']", + key = "string" == typeof(input) ? input + : $(input).attr("id") || $(input).attr("name"); + if (!key) return ""; + + selector = selector.replace(/{id}/gi, key); + return $(selector).text(); +} + +function validationFailureHandler() { + var handler = { + notice:function(msg, input) { + dialog.alert({ + content:msg, + onClose:function(){input.focus();} + }); + }, + valueMissing: function(input) { + handler.notice(labelFor(input) + "를(을) 입력하십시오.", input); + }, + typeMismatch: function(input) { + handler.notice(input.value + "는 " + labelFor(input) + "의 형식에 맞지 않습니다.", input); + }, + tooLong: function(input) { + handler.notice(input.value + " 값이 너무 깁니다.", input); + }, + patternMismatch: function(input) { + handler.notice(input.value + "는 " + labelFor(input) + "의 형식에 맞지 않습니다.", input); + }, + rangeOverflow: function(input) { + handler.notice(labelFor(input) + "의 값은 " + input.max + "보다 작아야 합니다.", input); + }, + rangeUnderflow: function(input) { + handler.notice(labelFor(input) + "의 값은 " + input.min + "보다 커야 합니다.", input); + }, + stepMismatch: function(input) { + handler.notice(labelFor(input) + "의 값은 " + input.step + "씩 증가 또는 감소해야 합니다.", input); + } + }; + return handler; +} + +/**Returns the result of validation on the input object.
+ * The types of validation checks are those defined by input objects' basic validity property, which are + *
  • valueMissing
  • + *
  • typeMismatch
  • + *
  • tooLong
  • + *
  • patternMismatch
  • + *
  • rangeOverflow
  • + *
  • rangeUnderflow
  • + *
  • stepMismatch
  • + *
+ * To be effective, the input object must be set with attributes + *
  • required
  • + *
  • type
  • + *
  • maxLength
  • + *
  • pattern
  • + *
  • max
  • + *
  • min
  • + *
  • step
  • + *
+ * On failure, failureHandler takes over.
+ * The default handler from validationFailureHandler() displays a message + * and puts the focus back to the input object.
+ * For the handler to display a message appropriate for the failure, + * it is recommended to have a label associated with the input object. + * @param input input object + * @param failureHandler object that handles validation failure.
+ * If not provided, the default handler from validationFailureHandler() is used. + * @returns + *
  • true if the value of the input object is valid
  • + *
  • false otherwise
  • + *
+ */ +function validInput(input, failureHandler) { + var validity = input.validity; + if (!validity || validity.valid) return true; + + for (var key in validity) { + if (!validity[key]) continue; + + if (!failureHandler) + failureHandler = validationFailureHandler(); + var handler = failureHandler[key]; + if (handler) + handler(input); + else { + log("Handler not found for validation failure of " + key); + } + break; + } + + return false; +} + +/**Returns the result of input validation on the selected input objects.
+ * The types of validation checks are those defined by input objects' basic validity property, which are + *
  • valueMissing
  • + *
  • typeMismatch
  • + *
  • tooLong
  • + *
  • patternMismatch
  • + *
  • rangeOverflow
  • + *
  • rangeUnderflow
  • + *
  • stepMismatch
  • + *
+ * To be effective, the input objects must be set with attributes + *
  • required
  • + *
  • type
  • + *
  • maxLength
  • + *
  • pattern
  • + *
  • max
  • + *
  • min
  • + *
  • step
  • + *
+ * On failure, failureHandler takes over.
+ * The default handler from validationFailureHandler() displays a message + * and puts the focus back to the offending input object.
+ * For the handler to display a message appropriate for the failure, + * it is recommended to have labels associated with input objects. + * @param failureHandler object that handles validation failure.
+ * If not provided, the default handler from validationFailureHandler() is used. + * @returns + *
  • true if the input values of the selected objects are valid
  • + *
  • false otherwise
  • + *
+ */ +$.fn.validInputs = function(failureHandler) { + var valid = true; + this.each(function(){ + var input = $(this); + if (!(valid = validInput(input[0], failureHandler))) { + return false; + } + }); + return valid; +} + +$.fn.getValues = function(propertyMapper) { + var inputValues = {}; + this.each(function() { + var input = $(this), + key = input.prop("id") || input.prop("name"), + value = input.val(); + if (isEmpty(key) || value === undefined) return; + + var type = input.prop("type"); + if ("file" == type) { + value = input.get(0).files; + } else if (["checkbox", "radio"].indexOf(type) > -1) { + if (!input.is(":checked")) return; + } + + var stored = inputValues[key]; + if (stored === undefined) { + inputValues[key] = value; + return; + } + + if (Array.isArray(stored)) { + if (!Array.isArray(value)) + stored.push(value); + else + stored = stored.concat(value); + } else { + var array = [stored]; + if (!Array.isArray(value)) + array.push(value); + else + array = array.concat(value); + inputValues[key] = array; + } + }); + + if (propertyMapper) { // propertyMapper = {property:valueProvider, ...} + for (var property in propertyMapper) { + var valueProvider = propertyMapper[property], + value = undefined; + switch (typeof(valueProvider)) { + case "string": + value = inputValues[valueProvider]; + break; + case "function": + value = valueProvider(inputValues); + break; + } + if (value === undefined) continue; + + delete inputValues[valueProvider]; + inputValues[property] = value; + } + } + + return inputValues; +} + +/** + * @param config + * {start:0, + * fetchSize:10, + * totalSize:135, + * links:3, + * first:function(index, label){return "..."}, + * previous:function(index, label){return "..."}, + * link:function(index, label){return "..."}, + * current:function(index, label){return "..."}, + * next:function(index, label){return "..."}, + * last:function(index, label){return "..."} + * } + * @returns {String} + */ +function paginate(config) { + var rc = config.totalSize || 0; +// if (!rc) return ""; + + var fetchCount = config.fetchSize || 0; +// if (!fetchCount) return ""; + + var fetch = { + all:0, + none:-1, + count:function(elementCount, size) { + if (!elementCount || size == fetch.all) return 1; + return parseInt((elementCount / size) + ((elementCount % size) == 0 ? 0 : 1)); + }, + end:function(elementCount, size, start) { + if (size < fetch.all) throw "Invalid size: " + size; + if (elementCount < 0) throw "Invalid elementCount: " + elementCount; + var last = elementCount - 1; + if (size == fetch.all) return last; + return Math.min(last, start + size -1); + }, + page:function(current, count) { + return parseInt(count < 1 ? 0 : current / count); + }, + band:function(page, visibleLinks) { + return parseInt(visibleLinks < 1 ? 0 : page / visibleLinks); + } + }; + var lc = fetch.count(rc, fetchCount); + if (lc < 2) return ""; + + var links = ifEmpty(config.links, fetch.all), + page = fetch.page(ifEmpty(config.start, 0), fetchCount), + band = fetch.band(page, links), + tags = { + link:function(tag, index, label) { + return !tag ? "" : tag(index, label); + }, + first:function() { + return band < 2 ? "" : tags.link(config.first, 0, 1); + }, + previous:function() { + if (band < 1) return ""; + var prevBand = band - 1, + prevPage = (prevBand * links) + (links - 1), + fromRec = prevPage * fetchCount; + return tags.link(config.previous, fromRec, prevPage + 1); + }, + visibleLinks:function() { + var s = "", + fromPage = links == fetch.all ? 0 : band * links, + toPage = links == fetch.all ? lc : Math.min(lc, fromPage + links); + for (var i = fromPage; i < toPage; ++i) { + var fromRec = i * fetchCount, + label = i + 1; + s += tags.link(i == page ? config.current : config.link, fromRec, label); + } + return s; + }, + next:function(bandCount) { + bandCount = parseInt(bandCount); + if (bandCount - band < 2) return ""; + + var nextBand = band + 1, + page = nextBand * links, + fromRec = page * fetchCount; + return tags.link(config.next, fromRec, page + 1); + }, + last:function(bandCount) { + bandCount = parseInt(bandCount); + var lastBand = bandCount - 1; + if (lastBand - band < 2) return ""; + + var pages = lastBand * links, + fromRec = pages * fetchCount; + return tags.link(config.last, fromRec, pages + 1); + } + }, + tag = ""; + if (links != fetch.all) { + tag += tags.first(); + tag += tags.previous(); + } + tag += tags.visibleLinks(); + if (links != fetch.all) { + var bandCount = parseInt(lc / links); + bandCount += lc % links == 0 ? 0 : 1; + tag += tags.next(bandCount); + tag += tags.last(bandCount); + } + return tag; +} + +$.fn.paginate = function(config) { + return this.each(function(){ + var tag = paginate(config), + container = $(this); + if (tag) + container.html(tag).show(); + else { + if (config.hideIfEmpty != false) + container.hide(); + } + }); +} + +$.fn.setPaging = function(config) { + config.links = 5; + config.first = function(index, label) {return '
  • '.replace(/{func}/, config.func.replace(/{index}/, label));}; + config.previous = function(index, label) {return ''.replace(/{func}/, config.func.replace(/{index}/, label));}; + config.link = function(index, label) {return '
  • {label}
  • '.replace(/{func}/, config.func.replace(/{index}/, label)).replace(/{label}/, label);}; + config.current = function(index, label) {return '
  • {label}
  • '.replace(/{label}/, label);}; + config.next = function(index, label) {return ''.replace(/{func}/, config.func.replace(/{index}/, label));}; + config.last = function(index, label) {return '
  • '.replace(/{func}/, config.func.replace(/{index}/, label));}; + + return this.each(function(){ + let tag = paginate(config), + container = $(this); + if (tag) + container.html(tag.replace(/{func}/g, config.func)).show(); + else { + if (config.hideIfEmpty != false) + container.hide(); + } + + let list = config.list; + let pagingInfo = list.empty ? "" : + numberFormat.format(config.start + 1) + + " ~ " + + numberFormat.format(config.start + list.length) + + " / " + numberFormat.format(config.totalSize); + $("#"+ config.prefix + "PagingInfo").html(pagingInfo); + }); +} + +$.fn.setCurrentRow = function(val) { + if (!val) return; + + return this.each(function() { + var e = $(this); + e.find("tr").each(function(){ + var tr = $(this), + current = val == tr.attr("data-key"); + if (current) + tr.addClass("current-row"); + else + tr.removeClass("current-row"); + }); + }); +} + +class FormFields { + constructor(selector) { + this.form = document.querySelector(this.selector = selector); + this.children = ["input", "select", "textarea"].map(tag => this.selector + " " + tag).join(","); + this.datamap = []; + } + + set(obj) { + document.querySelectorAll(this.children).forEach(input => { + let prop = input.getAttribute("data-map") + || input.name + || input.id; + if (!prop) return; + + let dataItem = obj instanceof DataItem, + value = dataItem ? obj.getValue(prop) : obj[prop]; + + if ("radio" == input.type) { + if (value && value == input.value) + input.checked = true; + } else { + input.value = value; + } + }); + } + + get() { + let obj = {}; + document.querySelectorAll(this.children).forEach(input => { + let property = input.name || input.id; + let value = input.value; + if ("radio" == input.type) { + if (input.checked) + obj[property] = value; + } else { + obj[property] = value; + } +// log(property, value, "radio" == input.type ? "radio" : "", input.checked ? "checked" : ""); + }); + return obj; + //return Object.fromEntries(new FormData(this.form).entries()); + } +}; + +/* +$.datepicker.setDefaults({ + dateFormat:"yy-mm-dd", + yearSuffix:"년", + monthNames:["1월", "2월", "3월", "4월", "5월", "6월", "7월", "8월", "9월", "10월", "11월", "12월"], + dayNames:["일요일", "월요일", "화요일", "수요일", "목요일", "금요일", "토요일"], + dayNamesMin:["일", "월", "화", "수", "목", "금", "토"], + showMonthAfterYear:true, + showOtherMonths:true, + selectOtherMonths:true, + nextText:"다음 달", + prevText:"이전 달" +}); +*/ +/** + * @param fromSource 시작값을 갖는 input의 selector + * @param toSource 종료값을 갖는 input의 selector + */ +function inputsInRange(fromSource, toSource) { + var from = $(fromSource), + to = $(toSource), + compare = function() { + var fromVal = from.val() || "", + toVal = to.val() || "", + ok = toVal >= fromVal; + if (ok) return; + + if ($(this)[0] == from[0]) + to.val(from.val()) + else + from.val(to.val()); + }; + from.change(compare); + to.change(compare); +} + +function ignore() { + console.log.apply(console, arguments); +} + +function fileInput(conf) { + conf = conf || {}; + var name = conf.name || "upload", + tag = "" + .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; +} diff --git a/src/main/webapp/resources/js/fims/framework/cmm/cmmUtil.js b/src/main/webapp/resources/js/fims/framework/cmm/cmmUtil.js index 83f723be..7347a119 100644 --- a/src/main/webapp/resources/js/fims/framework/cmm/cmmUtil.js +++ b/src/main/webapp/resources/js/fims/framework/cmm/cmmUtil.js @@ -485,54 +485,6 @@ function setDateTimeFmt2(srcDateStr, delimiter = '-') { } } -/** - *
    - * form의 element data를 json data로 return
    - * form 내의 각 element name 필수
    - * 
    - * @param {Object} formObj form Object - * @returns {Object} json Object - */ -function getFormJson(formObj) { - // Getting an FormData - let data = new FormData(formObj); - - // Getting a Serialize Data from FormData - return serialize(data); - - // Log - //console.log(JSON.stringify(serializedFormData)); -} - -/** - *
    - * FormData(new FormData(formObj)) 를 json Object로 return
    - * form 내의 각 element의 name 필수
    - * 
    - * @param formData new FormData(formObj) 형태의 데이타 - * @returns {Object} json Object - */ -function serialize (formData) { - - let rtnData = {}; - for (let [key, value] of formData) { - let sel = document.querySelectorAll("[name=" + key + "]"); - - // Array Values - if (sel.length > 1) { - if (rtnData[key] === undefined) { - rtnData[key] = []; - } - rtnData[key].push(value); - } - // Other - else { - rtnData[key] = value; - } - } - - return rtnData; -} /** * form 에 json data set