(function($, window, nodeGlobal) { var global = { jquery: $ }, globalFunc = {}, globalClass = {} ; var navigator = window.navigator; // JUI의 기본 설정 값 (향후 더 추가될 수 있음) var globalOpts = { template: { evaluate : /<\!([\s\S]+?)\!>/g, interpolate : /<\!=([\s\S]+?)\!>/g, escape : /<\!-([\s\S]+?)\!>/g }, logUrl: "tool/debug.html" }; /** * @class QuickSort * * 퀵 정렬 * * @param {Array} array * @param {Boolean} isClone isClone 이 true 이면, 해당 배열을 참조하지 않고 복사해서 처리 * @constructor * @private */ var QuickSort = function(array, isClone) { // var compareFunc = null, array = (isClone) ? array.slice(0) : array; function swap(indexA, indexB) { var temp = array[indexA]; array[indexA] = array[indexB]; array[indexB] = temp; } function partition(pivot, left, right) { var storeIndex = left, pivotValue = array[pivot]; swap(pivot, right); for(var v = left; v < right; v++) { if(compareFunc(array[v], pivotValue) || !compareFunc(pivotValue, array[v]) && v%2 == 1) { swap(v, storeIndex); storeIndex++; } } swap(right, storeIndex); return storeIndex; } this.setCompare = function(func) { compareFunc = func; } this.run = function(left, right) { var pivot = null; if (typeof left !== 'number') { left = 0; } if (typeof right !== 'number') { right = array.length - 1; } if (left < right) { pivot = left + Math.ceil((right - left) * 0.5); newPivot = partition(pivot, left, right); this.run(left, newPivot - 1); this.run(newPivot + 1, right); } return array; } } /** * @class IndexParser * * 0.0.1 형식의 키 문자열을 제어하는 클래스 * * @private * @constructor */ var IndexParser = function() { /** * @method isIndexDepth * * @param {String} index * @return {Boolean} */ this.isIndexDepth = function(index) { if(typeof(index) == "string" && index.indexOf(".") != -1) { return true; } return false; } /** * @method getIndexList * * @param {String} index * @return {Array} */ this.getIndexList = function(index) { // 트리 구조의 모든 키를 배열 형태로 반환 var resIndex = [], strIndex = "" + index; if(strIndex.length == 1) { resIndex[0] = parseInt(index); } else { var keys = strIndex.split("."); for(var i = 0; i < keys.length; i++) { resIndex[i] = parseInt(keys[i]); } } return resIndex; } /** * @method changeIndex * * * @param {String} index * @param {String} targetIndex * @param {String} rootIndex * @return {String} */ this.changeIndex = function(index, targetIndex, rootIndex) { var rootIndexLen = this.getIndexList(rootIndex).length, indexList = this.getIndexList(index), tIndexList = this.getIndexList(targetIndex); for(var i = 0; i < rootIndexLen; i++) { indexList.shift(); } return tIndexList.concat(indexList).join("."); } /** * @method getNextIndex * * @param {String} index * @return {String} */ this.getNextIndex = function(index) { // 현재 인덱스에서 +1 var indexList = this.getIndexList(index), no = indexList.pop() + 1; indexList.push(no); return indexList.join("."); } /** * @method getParentIndex * * * @param {String} index * @returns {*} */ this.getParentIndex = function(index) { if(!this.isIndexDepth(index)) return null; var keys = this.getIndexList(index); if(keys.length == 2) { return "" + keys[0]; } else if(keys.length > 2) { keys.pop(); return keys.join("."); } } } /** * Private Static Classes * */ var Base64 = { // private property _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", // public method for encoding encode : function (input) { var output = ""; var chr1, chr2, chr3, enc1, enc2, enc3, enc4; var i = 0; input = Base64._utf8_encode(input); while (i < input.length) { chr1 = input.charCodeAt(i++); chr2 = input.charCodeAt(i++); chr3 = input.charCodeAt(i++); enc1 = chr1 >> 2; enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); enc4 = chr3 & 63; if (isNaN(chr2)) { enc3 = enc4 = 64; } else if (isNaN(chr3)) { enc4 = 64; } output = output + Base64._keyStr.charAt(enc1) + Base64._keyStr.charAt(enc2) + Base64._keyStr.charAt(enc3) + Base64._keyStr.charAt(enc4); } return output; }, // public method for decoding decode : function (input) { var output = ""; var chr1, chr2, chr3; var enc1, enc2, enc3, enc4; var i = 0; input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); while (i < input.length) { enc1 = Base64._keyStr.indexOf(input.charAt(i++)); enc2 = Base64._keyStr.indexOf(input.charAt(i++)); enc3 = Base64._keyStr.indexOf(input.charAt(i++)); enc4 = Base64._keyStr.indexOf(input.charAt(i++)); chr1 = (enc1 << 2) | (enc2 >> 4); chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); chr3 = ((enc3 & 3) << 6) | enc4; output = output + String.fromCharCode(chr1); if (enc3 != 64) { output = output + String.fromCharCode(chr2); } if (enc4 != 64) { output = output + String.fromCharCode(chr3); } } output = Base64._utf8_decode(output); return output; }, // private method for UTF-8 encoding _utf8_encode : function (string) { string = string.replace(/\r\n/g,"\n"); // BOM 코드 적용 (UTF-8 관련) var utftext = String.fromCharCode(239) + String.fromCharCode(187) + String.fromCharCode(191); for (var n = 0; n < string.length; n++) { var c = string.charCodeAt(n); if (c < 128) { utftext += String.fromCharCode(c); } else if((c > 127) && (c < 2048)) { utftext += String.fromCharCode((c >> 6) | 192); utftext += String.fromCharCode((c & 63) | 128); } else { utftext += String.fromCharCode((c >> 12) | 224); utftext += String.fromCharCode(((c >> 6) & 63) | 128); utftext += String.fromCharCode((c & 63) | 128); } } return utftext; }, // private method for UTF-8 decoding _utf8_decode : function (utftext) { var string = ""; var i = 0; var c = c1 = c2 = 0; while ( i < utftext.length ) { c = utftext.charCodeAt(i); if (c < 128) { string += String.fromCharCode(c); i++; } else if((c > 191) && (c < 224)) { c2 = utftext.charCodeAt(i+1); string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); i += 2; } else { c2 = utftext.charCodeAt(i+1); c3 = utftext.charCodeAt(i+2); string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); i += 3; } } return string; } } /** * Private Functions * */ var template = function(text, data, settings) { var _ = {}, breaker = {}; var ArrayProto = Array.prototype, slice = ArrayProto.slice, nativeForEach = ArrayProto.forEach; var escapes = { '\\' : '\\', "'" : "'", 'r' : '\r', 'n' : '\n', 't' : '\t', 'u2028' : '\u2028', 'u2029' : '\u2029' }; for (var p in escapes) escapes[escapes[p]] = p; var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g, unescaper = /\\(\\|'|r|n|t|u2028|u2029)/g, noMatch = /.^/; var unescape = function(code) { return code.replace(unescaper, function(match, escape) { return escapes[escape]; }); }; var each = _.each = _.forEach = function(obj, iterator, context) { if (obj == null) return; if (nativeForEach && obj.forEach === nativeForEach) { obj.forEach(iterator, context); } else if (obj.length === +obj.length) { for (var i = 0, l = obj.length; i < l; i++) { if ( i in obj && iterator.call(context, obj[i], i, obj) === breaker) return; } } else { for (var key in obj) { if (_.has(obj, key)) { if (iterator.call(context, obj[key], key, obj) === breaker) return; } } } }; _.has = function(obj, key) { return hasOwnProperty.call(obj, key); }; _.defaults = function(obj) { each(slice.call(arguments, 1), function(source) { for (var prop in source) { if (obj[prop] == null) obj[prop] = source[prop]; } }); return obj; }; _.template = function(text, data, settings) { settings = _.defaults(settings || {}, globalOpts.template); var source = "__p+='" + text.replace(escaper, function(match) { return '\\' + escapes[match]; }).replace(settings.escape || noMatch, function(match, code) { return "'+\n_.escape(" + unescape(code) + ")+\n'"; }).replace(settings.interpolate || noMatch, function(match, code) { return "'+\n(" + unescape(code) + ")+\n'"; }).replace(settings.evaluate || noMatch, function(match, code) { return "';\n" + unescape(code) + "\n;__p+='"; }) + "';\n"; if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; source = "var __p='';" + "var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n" + source + "return __p;\n"; var render = new Function(settings.variable || 'obj', '_', source); if (data) return render(data, _); var template = function(data) { return render.call(this, data, _); }; template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; return template; }; return _.template(text, data, settings); } /** * @class util.base * * jui 에서 공통적으로 사용하는 유틸리티 함수 모음 * * ``` * var _ = jui.include("util.base"); * * console.log(_.browser.webkit); * ``` * * @singleton */ var utility = global["util.base"] = { /** * @property browser check browser agent * @property {Boolean} browser.webkit Webkit 브라우저 체크 * @property {Boolean} browser.mozilla Mozilla 브라우저 체크 * @property {Boolean} browser.msie IE 브라우저 체크 */ browser: { webkit: (typeof window.webkitURL != "undefined") ? true : false, mozilla: (typeof window.mozInnerScreenX != "undefined") ? true : false, msie: (navigator.userAgent.indexOf("Trident") != -1) ? true : false }, /** * @property {Boolean} isTouch * check touch device */ isTouch: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent), //-- Functions /** * @method scrollWidth * returns scroll width for body * @return {Number} */ scrollWidth: function() { var isJUI = ($(".jui").size() > 0 && this.browser.webkit) ? true : false; var div = $('
'); $('body').append(div); var w1 = $('div', div).innerWidth(); div.css('overflow-y', 'auto'); var w2 = $('div', div).innerWidth(); $(div).remove(); return (isJUI) ? 10 : (w1 - w2); }, /** * @method inherit * * 프로토타입 기반의 상속 제공 * * @param {Function} ctor base Class * @param {Function} superCtor super Class */ inherit: function(ctor, superCtor) { if(!this.typeCheck("function", ctor) || !this.typeCheck("function", superCtor)) return; ctor.parent = superCtor; ctor.prototype = new superCtor; ctor.prototype.constructor = ctor; ctor.prototype.parent = ctor.prototype; /** * @method super * call parent method * @param {String} method parent method name * @param {Array} args * @returns {Mixed} */ ctor.prototype.super = function(method, args) { return this.constructor.prototype[method].apply(this, args); } }, /** * @method extend * implements object extend * @param origin * @param add * @param skip * @return {Object} */ extend: function(origin, add, skip) { if(!this.typeCheck("object", origin)) origin = {}; if(!this.typeCheck("object", add)) return origin; for(var key in add) { if(skip === true) { if(isRecursive(origin[key])) { this.extend(origin[key], add[key], skip); } else if(this.typeCheck("undefined", origin[key])) { origin[key] = add[key]; } } else { if(isRecursive(origin[key])) { this.extend(origin[key], add[key], skip); } else { origin[key] = add[key]; } } } function isRecursive(value) { return utility.typeCheck("object", value); } return origin; }, /** * convert px to integer * @param {String or Number} px * @return {Number} */ pxToInt: function(px) { if(typeof(px) == "string" && px.indexOf("px") != -1) { return parseInt(px.split("px").join("")); } return px; }, /** * @method clone * implements object clone * @param {Array/Object} obj 복사할 객체 * @return {Array} */ clone: function(obj) { var clone = ($.isArray(obj)) ? [] : {}; for(var i in obj) { if(this.typeCheck("object", obj[i])) clone[i] = this.clone(obj[i]); else clone[i] = obj[i]; } return clone; }, /** * @method deepClone * implements object deep clone * @param obj * @param emit * @return {*} */ deepClone: function(obj, emit) { var value = null; emit = emit || {}; if(this.typeCheck("array", obj )) { value = new Array(obj.length); for(var i = 0, len = obj.length; i < len; i++) { value[i] = this.deepClone(obj[i], emit); } } else if(this.typeCheck("date", obj)) { value = obj; } else if(this.typeCheck("object", obj)) { value = {}; for(var key in obj) { if (emit[key]) { value[key] = obj[key]; } else { value[key] = this.deepClone(obj[key], emit); } } } else { value = obj; } return value ; }, /** * @method sort * use QuickSort * @param {Array} array * @return {QuickSort} */ sort: function(array) { return new QuickSort(array); }, /** * @method runtime * * caculate callback runtime * * @param {String} name * @param {Function} callback */ runtime: function(name, callback) { var nStart = new Date().getTime(); callback(); var nEnd = new Date().getTime(); console.log(name + " : " + (nEnd - nStart) + "ms"); }, /** * @method template * parsing template string * @param html * @param obj */ template: function(html, obj) { if(!obj) return template(html); else return template(html, obj); }, /** * @method resize * add event in window resize event * @param {Function} callback * @param {Number} ms delay time */ resize: function(callback, ms) { var after_resize = (function(){ var timer = 0; return function() { clearTimeout(timer); timer = setTimeout(callback, ms); } })(); $(window).resize(function() { after_resize(); }); }, /** * @method index * * IndexParser 객체 생성 * * @return {IndexParser} */ index: function() { return new IndexParser(); }, /** * @method chunk * split array by length * @param {Array} arr * @param {Number} len * @return {Array} */ chunk: function(arr, len) { var chunks = [], i = 0, n = arr.length; while (i < n) { chunks.push(arr.slice(i, i += len)); } return chunks; }, /** * @method typeCheck * check data type * @param {String} t type string * @param {Object} v value object * @return {Boolean} */ typeCheck: function(t, v) { function check(type, value) { if(typeof(type) != "string") return false; if (type == "string") { return (typeof(value) == "string"); } else if (type == "integer") { return (typeof(value) == "number" && value % 1 == 0); } else if (type == "float") { return (typeof(value) == "number" && value % 1 != 0); } else if (type == "number") { return (typeof(value) == "number"); } else if (type == "boolean") { return (typeof(value) == "boolean"); } else if (type == "undefined") { return (typeof(value) == "undefined"); } else if (type == "null") { return (value === null); } else if (type == "array") { return (value instanceof Array); } else if (type == "date") { return (value instanceof Date); } else if (type == "function") { return (typeof(value) == "function"); } else if (type == "object") { // typeCheck에 정의된 타입일 경우에는 object 체크시 false를 반환 (date, array, null) return ( typeof(value) == "object" && value !== null && !(value instanceof Array) && !(value instanceof Date) && !(value instanceof RegExp) ); } return false; } if(typeof(t) == "object" && t.length) { var typeList = t; for(var i = 0; i < typeList.length; i++) { if(check(typeList[i], v)) return true; } return false; } else { return check(t, v); } }, typeCheckObj: function(uiObj, list) { if(typeof(uiObj) != "object") return; var self = this; for(var key in uiObj) { var func = uiObj[key]; if(typeof(func) == "function") { (function(funcName, funcObj) { uiObj[funcName] = function() { var args = arguments, params = list[funcName]; for(var i = 0; i < args.length; i++) { if(!self.typeCheck(params[i], args[i])) { throw new Error("JUI_CRITICAL_ERR: the " + i + "th parameter is not a " + params[i] + " (" + name + ")"); } } return funcObj.apply(this, args); } })(key, func); } } }, /** * @method dataToCsv * * data 를 csv 로 변환한다. * * @param {Array} keys * @param {Array} dataList * @param {Number} dataSize * @return {String} 변환된 csv 문자열 */ dataToCsv: function(keys, dataList, dataSize) { var csv = "", len = (!dataSize) ? dataList.length : dataSize; for(var i = -1; i < len; i++) { var tmpArr = []; for(var j = 0; j < keys.length; j++) { if(keys[j]) { if(i == -1) { tmpArr.push('"' + keys[j] + '"'); } else { var value = dataList[i][keys[j]]; tmpArr.push(isNaN(value) ? '"' + value + '"' : value); } } } csv += tmpArr.join(",") + "\n"; } return csv; }, /** * @method dataToCsv2 * * @param {Object} options * @return {String} */ dataToCsv2: function(options) { var csv = ""; var opts = $.extend({ fields: null, // required rows: null, // required names: null, count: (this.typeCheck("integer", options.count)) ? options.count : options.rows.length }, options); for(var i = -1; i < opts.count; i++) { var tmpArr = []; for(var j = 0; j < opts.fields.length; j++) { if(opts.fields[j]) { if(i == -1) { if(opts.names && opts.names[j]) { tmpArr.push('"' + opts.names[j] + '"'); } else { tmpArr.push('"' + opts.fields[j] + '"'); } } else { var value = opts.rows[i][opts.fields[j]]; tmpArr.push(isNaN(value) ? '"' + value + '"' : value); } } } csv += tmpArr.join(",") + "\n"; } return csv; }, /** * @method fileToCsv * * file 에서 csv 컨텐츠 로드 * * @param {File} file * @param {Function} callback */ fileToCsv: function(file, callback) { var reader = new FileReader(); reader.onload = function(readerEvt) { if(typeof(callback) == "function") { callback(readerEvt.target.result); } }; reader.readAsText(file); }, /** * @method csvToBase64 * * csv 다운로드 링크로 변환 * * @param {String} csv * @return {String} */ csvToBase64: function(csv) { return "data:application/octet-stream;base64," + Base64.encode(csv); }, /** * @method csvToData * * @param {Array} keys * @param {String} csv * @param {Number} csvNumber * @return {Array} */ csvToData: function(keys, csv, csvNumber) { var dataList = [], tmpRowArr = csv.split("\n") for(var i = 1; i < tmpRowArr.length; i++) { if(tmpRowArr[i] != "") { var tmpArr = tmpRowArr[i].split(","), // TODO: 값 안에 콤마(,)가 있을 경우에 별도로 처리해야 함 data = {}; for(var j = 0; j < keys.length; j++) { data[keys[j]] = tmpArr[j]; // '"' 로 감싸져있는 문자열은 '"' 제거 if(this.startsWith(tmpArr[j], '"') && this.endsWith(tmpArr[j], '"')) { data[keys[j]] = tmpArr[j].split('"').join(''); } else { data[keys[j]] = tmpArr[j]; } if($.inArray(keys[j], csvNumber) != -1) { data[keys[j]] = parseFloat(tmpArr[j]); } } dataList.push(data); } } return dataList; }, /** * @method getCsvFields * * csv 에서 필드 얻어오기 * * @param {Array} fields * @param {Array} csvFields * @return {Array} */ getCsvFields: function(fields, csvFields) { var tmpFields = (this.typeCheck("array", csvFields)) ? csvFields : fields; for(var i = 0; i < tmpFields.length; i++) { if(!isNaN(tmpFields[i])) { tmpFields[i] = fields[tmpFields[i]]; } } return tmpFields; }, /** * @method svgToBase64 * * xml 문자열로 svg datauri 생성 * * @param {String} xml * @return {String} 변환된 data uri 링크 */ svgToBase64: function(xml) { return "data:image/svg+xml;base64," + Base64.encode(xml); }, /** * @method dateFormat * * implements date format function * * yyyy : 4 digits year * yy : 2 digits year * y : 1 digit year * * @param {Date} date * @param {String} format date format string * @param utc * @return {string} */ dateFormat: function(date, format, utc) { var MMMM = ["\x00", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; var MMM = ["\x01", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; var dddd = ["\x02", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; var ddd = ["\x03", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; function ii(i, len) { var s = i + ""; len = len || 2; while (s.length < len) s = "0" + s; return s; } var y = utc ? date.getUTCFullYear() : date.getFullYear(); format = format.replace(/(^|[^\\])yyyy+/g, "$1" + y); format = format.replace(/(^|[^\\])yy/g, "$1" + y.toString().substr(2, 2)); format = format.replace(/(^|[^\\])y/g, "$1" + y); var M = (utc ? date.getUTCMonth() : date.getMonth()) + 1; format = format.replace(/(^|[^\\])MMMM+/g, "$1" + MMMM[0]); format = format.replace(/(^|[^\\])MMM/g, "$1" + MMM[0]); format = format.replace(/(^|[^\\])MM/g, "$1" + ii(M)); format = format.replace(/(^|[^\\])M/g, "$1" + M); var d = utc ? date.getUTCDate() : date.getDate(); format = format.replace(/(^|[^\\])dddd+/g, "$1" + dddd[0]); format = format.replace(/(^|[^\\])ddd/g, "$1" + ddd[0]); format = format.replace(/(^|[^\\])dd/g, "$1" + ii(d)); format = format.replace(/(^|[^\\])d/g, "$1" + d); var H = utc ? date.getUTCHours() : date.getHours(); format = format.replace(/(^|[^\\])HH+/g, "$1" + ii(H)); format = format.replace(/(^|[^\\])H/g, "$1" + H); var h = H > 12 ? H - 12 : H == 0 ? 12 : H; format = format.replace(/(^|[^\\])hh+/g, "$1" + ii(h)); format = format.replace(/(^|[^\\])h/g, "$1" + h); var m = utc ? date.getUTCMinutes() : date.getMinutes(); format = format.replace(/(^|[^\\])mm+/g, "$1" + ii(m)); format = format.replace(/(^|[^\\])m/g, "$1" + m); var s = utc ? date.getUTCSeconds() : date.getSeconds(); format = format.replace(/(^|[^\\])ss+/g, "$1" + ii(s)); format = format.replace(/(^|[^\\])s/g, "$1" + s); var f = utc ? date.getUTCMilliseconds() : date.getMilliseconds(); format = format.replace(/(^|[^\\])fff+/g, "$1" + ii(f, 3)); f = Math.round(f / 10); format = format.replace(/(^|[^\\])ff/g, "$1" + ii(f)); f = Math.round(f / 10); format = format.replace(/(^|[^\\])f/g, "$1" + f); var T = H < 12 ? "AM" : "PM"; format = format.replace(/(^|[^\\])TT+/g, "$1" + T); format = format.replace(/(^|[^\\])T/g, "$1" + T.charAt(0)); var t = T.toLowerCase(); format = format.replace(/(^|[^\\])tt+/g, "$1" + t); format = format.replace(/(^|[^\\])t/g, "$1" + t.charAt(0)); var tz = -date.getTimezoneOffset(); var K = utc || !tz ? "Z" : tz > 0 ? "+" : "-"; if (!utc) { tz = Math.abs(tz); var tzHrs = Math.floor(tz / 60); var tzMin = tz % 60; K += ii(tzHrs) + ":" + ii(tzMin); } format = format.replace(/(^|[^\\])K/g, "$1" + K); var day = (utc ? date.getUTCDay() : date.getDay()) + 1; format = format.replace(new RegExp(dddd[0], "g"), dddd[day]); format = format.replace(new RegExp(ddd[0], "g"), ddd[day]); format = format.replace(new RegExp(MMMM[0], "g"), MMMM[M]); format = format.replace(new RegExp(MMM[0], "g"), MMM[M]); format = format.replace(/\\(.)/g, "$1"); return format; }, /** * @method createId * * 유니크 아이디 생성 * * @param {String} key prefix string * @return {String} 생성된 아이디 문자열 */ createId: function(key) { return [ key || "id", (+new Date), Math.round(Math.random() * 100) % 100 ].join("-"); }, /** * @method btoa * * Base64 인코딩 * * @return {String} */ btoa: Base64.encode, /** * @method atob * * Base64 디코딩 * * @return {String} */ atob: Base64.decode, /** * @method loop * * 최적화된 루프 생성 (5단계로 나눔) * * @param {Number} total * @param {Object} [context=null] * @return {Function} 최적화된 루프 콜백 (index, groupIndex 2가지 파라미터를 받는다.) */ loop : function(total, context) { var start = 0, end = total, unit = Math.ceil(total/5); return function(callback) { var first = start, second = unit * 1, third = unit * 2, fourth = unit * 3, fifth = unit * 4, firstMax = second, secondMax = third, thirdMax = fourth, fourthMax = fifth, fifthMax = end; while(first < firstMax && first < end) { callback.call(context, first, 1); first++; if(second < secondMax && second < end) { callback.call(context, second, 2); second++; } if(third < thirdMax && third < end) { callback.call(context, third, 3); third++; } if(fourth < fourthMax && fourth < end) { callback.call(context, fourth, 4); fourth++; } if(fifth < fifthMax && fifth < end) { callback.call(context, fifth, 5); fifth++; } } }; }, /** * @method loopArray * * 배열을 사용해서 최적화된 루프로 생성한다. * * * @param {Array} data 루프로 생성될 배열 * @param {Object} [context=null] * @return {Function} 최적화된 루프 콜백 (data, index, groupIndex 3가지 파라미터를 받는다.) */ loopArray : function(data, context) { var total = data.length, start = 0, end = total, unit = Math.ceil(total/5); return function(callback) { var first = start, second = unit * 1, third = unit * 2, fourth = unit * 3, fifth = unit * 4, firstMax = second, secondMax = third, thirdMax = fourth, fourthMax = fifth, fifthMax = end; while(first < firstMax && first < end) { callback.call(context, data[first], first, 1); first++; if (second < secondMax && second < end) { callback.call(context, data[second], second, 2); second++; } if (third < thirdMax && third < end) { callback.call(context, data[third], third, 3); third++; } if (fourth < fourthMax && fourth < end) { callback.call(context, data[fourth], fourth, 4); fourth++; } if (fifth < fifthMax && fifth < end) { callback.call(context, data[fifth], fifth, 5); fifth++; } } }; }, /** * @method makeIndex * * 배열의 키 기반 인덱스를 생성한다. * * 개별 값 별로 멀티 인덱스를 생성한다. * * @param {Array} data * @param {String} keyField * @return {Object} 생성된 인덱스 */ makeIndex : function(data, keyField) { var list = {}, func = this.loopArray(data); func(function(d, i) { var value = d[keyField]; if (typeof list[value] == 'undefined') { list[value] = []; } list[value].push(i); }); return list; }, /** * @method startsWith * Check that it matches the starting string search string. * * @param {String} string * @param {String} searchString * @return {Integer} position */ startsWith : function(string, searchString, position) { position = position || 0; return string.lastIndexOf(searchString, position) === position; }, /** * @method endsWith * Check that it matches the end of a string search string. * * @param {String} string * @param {String} searchString * @return {Integer} position */ endsWith : function(string, searchString, position) { var subjectString = string; if(position === undefined || position > subjectString.length) { position = subjectString.length; } position -= searchString.length; var lastIndex = subjectString.indexOf(searchString, position); return lastIndex !== -1 && lastIndex === position; } } /* * Module related functions * */ var getDepends = function(depends) { var args = []; for(var i = 0; i < depends.length; i++) { var module = global[depends[i]]; if(!utility.typeCheck([ "function", "object" ], module)) { var modules = getModules(depends[i]); if(modules == null) { throw new Error("JUI_CRITICAL_ERR: '" + depends[i] + "' is not loaded"); } else { args.push(modules); } } else { args.push(module); } } return args; } var getModules = function(parent) { var modules = null, parent = parent + "."; for(var key in global) { if(key.indexOf(parent) != -1) { if(utility.typeCheck([ "function", "object" ], global[key])) { var child = key.split(parent).join(""); if(child.indexOf(".") == -1) { if(modules == null) { modules = {}; } modules[child] = global[key]; } } } } return modules; } /** * @class jui * * Global Object * * @singleton */ window.jui = nodeGlobal.jui = { /** * @method ready * * ready 타임에 실행될 callback 정의 * * @param {Function} callback */ ready: function() { var args = [], callback = (arguments.length == 2) ? arguments[1] : arguments[0], depends = (arguments.length == 2) ? arguments[0] : null; if(!utility.typeCheck([ "array", "null" ], depends) || !utility.typeCheck("function", callback)) { throw new Error("JUI_CRITICAL_ERR: Invalid parameter type of the function"); } $(function() { if(depends) { args = getDepends(depends); } else { args = [ getModules("ui"), getModules("uix"), utility ]; } callback.apply(null, args); }); }, /** * @method defineUI * * 사용자가 실제로 사용할 수 있는 UI 클래스를 정의 * * @param {String} name 모듈 로드와 상속에 사용될 이름을 정한다. * @param {Array} depends 'define'이나 'defineUI'로 정의된 클래스나 객체를 인자로 받을 수 있다. * @param {Function} callback UI 클래스를 해당 콜백 함수 내에서 클래스 형태로 구현하고 리턴해야 한다. * @param {String} parent 'depends'와 달리 'define'으로 정의된 클래스만 상속받을 수 있다. */ defineUI: function(name, depends, callback, parent) { if(!utility.typeCheck("string", name) || !utility.typeCheck("array", depends) || !utility.typeCheck("function", callback) || !utility.typeCheck([ "string", "undefined" ], parent)) { throw new Error("JUI_CRITICAL_ERR: Invalid parameter type of the function"); } if(utility.typeCheck("function", globalClass[name])) { throw new Error("JUI_CRITICAL_ERR: '" + name + "' is already exist"); } if(utility.typeCheck("undefined", parent)) { // 기본적으로 'core' 클래스를 상속함 parent = "core"; } if(!utility.typeCheck("function", globalClass[parent])) { throw new Error("JUI_CRITICAL_ERR: Parents are the only function"); } else { if(globalFunc[parent] !== true) { throw new Error("JUI_CRITICAL_ERR: UI function can not be inherited"); } } var args = getDepends(depends), uiFunc = callback.apply(null, args); // 상속 utility.inherit(uiFunc, globalClass[parent]); // UI 고유 설정 global[name] = globalClass["core"].init({ type: name, "class": uiFunc }); globalClass[name] = uiFunc; globalFunc[name] = true; // support AMD module if(typeof define == "function" && define.amd) { define(name, function() { return global[name] }); } }, /** * @method define * * UI 클래스에서 사용될 클래스를 정의하고, 자유롭게 상속할 수 있는 클래스를 정의 * * @param {String} name 모듈 로드와 상속에 사용될 이름을 정한다. * @param {Array} depends 'define'이나 'defineUI'로 정의된 클래스나 객체를 인자로 받을 수 있다. * @param {Function} callback UI 클래스를 해당 콜백 함수 내에서 클래스 형태로 구현하고 리턴해야 한다. * @param {String} parent 상속받을 클래스 */ define: function(name, depends, callback, parent) { if(!utility.typeCheck("string", name) || !utility.typeCheck("array", depends) || !utility.typeCheck("function", callback) || !utility.typeCheck([ "string", "undefined" ], parent)) { throw new Error("JUI_CRITICAL_ERR: Invalid parameter type of the function"); } if(utility.typeCheck("function", globalClass[name])) { throw new Error("JUI_CRITICAL_ERR: '" + name + "' is already exist"); } var args = getDepends(depends), uiFunc = callback.apply(null, args); if(utility.typeCheck("function", globalClass[parent])) { if(globalFunc[parent] !== true) { throw new Error("JUI_CRITICAL_ERR: UI function can not be inherited"); } else { utility.inherit(uiFunc, globalClass[parent]); } } // 함수 고유 설정 global[name] = uiFunc; globalClass[name] = uiFunc; // original function globalFunc[name] = true; // support AMD module if(typeof define == "function" && define.amd) { define(name, function() { return global[name] }); } }, /** * @method defineOptions * * 모듈 기본 옵션 정의 * * @param {Object} Module * @param {Object} options * @param {Object} exceptOpts * @return {Object} */ defineOptions: function(Module, options, exceptOpts) { var defOpts = getOptions(Module, {}); var defOptKeys = Object.keys(defOpts), optKeys = Object.keys(options); // 정의되지 않은 옵션 사용 유무 체크 for(var i = 0; i < optKeys.length; i++) { var name = optKeys[i]; if($.inArray(name, defOptKeys) == -1 && $.inArray(name, exceptOpts) == -1) { throw new Error("JUI_CRITICAL_ERR: '" + name + "' is not an option"); } } // 사용자 옵션 + 기본 옵션 utility.extend(options, defOpts, true); // 상위 모듈의 옵션까지 모두 얻어오는 함수 function getOptions(Module, options) { if(utility.typeCheck("function", Module)) { if(utility.typeCheck("function", Module.setup)) { var opts = Module.setup(); for(var key in opts) { if(utility.typeCheck("undefined", options[key])) { options[key] = opts[key]; } } } getOptions(Module.parent, options); } return options; } return options; }, /** * define과 defineUI로 정의된 클래스 또는 객체를 가져온다. * * @param name 가져온 클래스 또는 객체의 이름 * @return {*} */ include: function(name) { if(!utility.typeCheck("string", name)) { throw new Error("JUI_CRITICAL_ERR: Invalid parameter type of the function"); } var module = global[name]; if(utility.typeCheck([ "function", "object" ], module)) { return module; } else { var modules = getModules(name); if(modules == null) { throw new Error("JUI_CRITICAL_ERR: '" + name + "' is not loaded"); } else { return modules; } } }, /** * define과 defineUI로 정의된 모든 클래스와 객체를 가져온다. * * @return {Array} */ includeAll: function() { var result = []; for(var key in global) { result.push(global[key]); } return result; }, /** * 설정된 jui 관리 화면을 윈도우 팝업으로 띄운다. * * @param logUrl * @return {Window} */ log: function(logUrl) { var jui_mng = window.open( logUrl || globalOpts.logUrl, "JUIM", "width=1024, height=768, toolbar=no, menubar=no, resizable=yes" ); jui.debugAll(function (log, str) { jui_mng.log(log, str); }); return jui_mng; }, setup: function(options) { if(utility.typeCheck("object", options)) { globalOpts = utility.extend(globalOpts, options); } return globalOpts; } }; })(jQuery || $, window, (typeof global !== "undefined") ? global : window); jui.define("core", [ "jquery", "util.base" ], function($, _) { /** * @class core.UIManager * @private * @singleton */ var UIManager = new function() { var instances = [], classes = []; /** * @method add * Adds a component object created * * @param {Object} ui UI instance */ this.add = function(uiIns) { instances.push(uiIns); } /** * @method emit * Generates a custom event to an applicable component * * @param {String} key Selector or UI type * @param {String} type Event type * @param {Array} args Event arguments */ this.emit = function(key, type, args) { var targets = []; for(var i = 0; i < instances.length; i++) { var uiSet = instances[i]; if(key == uiSet.selector || key == uiSet.type) { targets.push(uiSet); } } for(var i = 0; i < targets.length; i++) { var uiSet = targets[i]; for(var j = 0; j < uiSet.length; j++) { uiSet[j].emit(type, args); } } } /** * @method get * Gets a component currently created * * @param {Integer/String} key * @returns {Object/Array} UI instance */ this.get = function(key) { if(_.typeCheck("integer", key)) { return instances[key]; } else if(_.typeCheck("string", key)) { // 셀렉터 객체 검색 for(var i = 0; i < instances.length; i++) { var uiSet = instances[i]; if(key == uiSet.selector) { return (uiSet.length == 1) ? uiSet[0] : uiSet; } } // 모듈 객체 검색 var result = []; for(var i = 0; i < instances.length; i++) { var uiSet = instances[i]; if(key == uiSet.type) { result.push(uiSet); } } return result; } } /** * @method getAll * Gets all components currently created * * @return {Array} UI instances */ this.getAll = function() { return instances; } /** * @method remove * Removes a component object in an applicable index from the list * * @param {Integer} index * @return {Object} Removed instance */ this.remove = function(index) { if(_.typeCheck("integer", index)) { // UI 객체 인덱스 return instances.splice(index, 1)[0]; } } /** * @method shift * Removes the last component object from the list * * @return {Object} Removed instance */ this.shift = function() { return instances.shift(); } /** * @method pop * Removes the first component object from the list * * @return {Object} Removed instance */ this.pop = function() { return instances.pop(); } /** * @method size * Gets the number of objects currently created * * @return {Number} */ this.size = function() { return instances.length; } /** * @method debug * * @param {Object} uiObj UI instance * @param {Number} i * @param {Number} j * @param {Function} callback */ this.debug = function(uiObj, i, j, callback) { if(!uiObj.__proto__) return; var exFuncList = [ "emit", "on", "addEvent", "addValid", "callBefore", "callAfter", "callDelay", "setTpl", "setVo", "setOption" ]; for(var key in uiObj) { var func = uiObj[key]; if(typeof(func) == "function" && $.inArray(key, exFuncList) == -1) { (function(funcName, funcObj, funcIndex, funcChildIndex) { uiObj.__proto__[funcName] = function() { var nStart = Date.now(); var resultObj = funcObj.apply(this, arguments); var nEnd = Date.now(); if(typeof(callback) == "function") { callback({ type: jui.get(i).type, name: funcName, c_index: funcIndex, u_index: funcChildIndex, time: nEnd - nStart }, arguments); } else { if(!isNaN(funcIndex) && !isNaN(funcChildIndex)) { console.log( "TYPE(" + jui.get(i).type + "), " + "NAME(" + funcName + "), " + "INDEX(" + funcIndex + ":" + funcChildIndex + "), " + "TIME(" + (nEnd - nStart) + "ms), " + "ARGUMENTS..." ); } else { console.log( "NAME(" + funcName + "), " + "TIME(" + (nEnd - nStart) + "ms), " + "ARGUMENTS..." ); } console.log(arguments); console.log(""); } return resultObj; } })(key, func, i, j); } } } /** * @method debugAll * debugs all component objects currently existing * * @param {Function} callback */ this.debugAll = function(callback) { for(var i = 0; i < instances.length; i++) { var uiList = instances[i]; for(var j = 0; j < uiList.length; j++) { this.debug(uiList[j], i, j, callback); } } } /** * @method addClass * Adds a component class * * @param {Object} uiCls UI Class */ this.addClass = function(uiCls) { classes.push(uiCls); } /** * @method getClass * Gets a component class * * @param {String/Integer} key * @return {Object} */ this.getClass = function(key) { if(_.typeCheck("integer", key)) { return classes[key]; } else if(_.typeCheck("string", key)) { for(var i = 0; i < classes.length; i++) { if(key == classes[i].type) { return classes[i]; } } } return null; } /** * @method getClassAll * Gets all component classes * * @return {Array} */ this.getClassAll = function() { return classes; } /** * @method create * It is possible to create a component dynamically after the ready point * * @param {String} type UI type * @param {String/DOMElement} selector * @param {Object} options * @return {Object} */ this.create = function(type, selector, options) { var cls = UIManager.getClass(type); if(_.typeCheck("null", cls)) { throw new Error("JUI_CRITICAL_ERR: '" + type + "' does not exist"); } return cls["class"](selector, options); } } var UIListener = function() { var list = []; function settingEventAnimation(e) { var pfx = [ "webkit", "moz", "MS", "o", "" ]; for(var p = 0; p < pfx.length; p++) { var type = e.type; if(!pfx[p]) type = type.toLowerCase(); $(e.target).on(pfx[p] + type, e.callback); } list.push(e); } function settingEvent(e) { if(e.callback && !e.children) { $(e.target).on(e.type, e.callback); } else { $(e.target).on(e.type, e.children, e.callback); } list.push(e); } function settingEventTouch(e) { if(e.callback && !e.children) { $(e.target).on(getEventTouchType(e.type), e.callback); } else { $(e.target).on(getEventTouchType(e.type), e.children, e.callback); } list.push(e); } function getEventTouchType(type) { return { "click": "touchstart", "dblclick": "touchend", "mousedown": "touchstart", "mousemove": "touchmove", "mouseup": "touchend" }[type]; } this.add = function(args) { var e = { target: args[0], type: args[1] }; if(_.typeCheck("function", args[2])) { e = $.extend(e, { callback: args[2] }); } else if(_.typeCheck("string", args[2])) { e = $.extend(e, { children: args[2], callback: args[3] }); } // 이벤트 유형을 배열로 변경 var eventTypes = _.typeCheck("array", e.type) ? e.type : [ e.type ]; // 이벤트 유형에 따른 이벤트 설정 for(var i = 0; i < eventTypes.length; i++) { e.type = eventTypes[i] if (e.type.toLowerCase().indexOf("animation") != -1) settingEventAnimation(e); else { if (e.target != "body" && e.target != window) { // body와 window일 경우에만 이벤트 중첩이 가능 $(e.target).off(e.type); } if (_.isTouch) { settingEventTouch(e); } else { settingEvent(e); } } } } this.trigger = function(selector, type) { $(selector).trigger((_.isTouch) ? getEventTouchType(type) : type); } this.get = function(index) { return list[index]; } this.getAll = function() { return list; } this.size = function() { return list.length; } } var UICoreSet = function(type, selector, options, list) { this.type = type; this.selector = selector; this.options = options; this.destroy = function() { for(var i = 0; i < list.length; i++) { list[i].destroy(); } } for(var i = 0; i < list.length; i++) { this.push(list[i]); } } // 배열 클래스 상속 UICoreSet.prototype = Object.create(Array.prototype); /** * @class core * Core classes for all of the components * * @alias UICore */ var UICore = function() { var vo = null; /** * @method find * Get the child element of the root element * * @param {String/HTMLElement} Selector * @returns {*|jQuery} */ this.find = function(selector) { return $(this.root).find(selector); } /** * @method emit * Generates a custom event. The first parameter is the type of a custom event. A function defined as an option or on method is called * * @param {String} type Event type * @param {Function} args Event Arguments * @return {Mixed} */ this.emit = function(type, args) { if(typeof(type) != "string") return; var result; for(var i = 0; i < this.event.length; i++) { var e = this.event[i]; if(e.type == type.toLowerCase()) { var arrArgs = (typeof(args) == "object" && args.length) ? args : [ args ]; result = e.callback.apply(this, arrArgs); } } return result; } /** * @method on * A callback function defined as an on method is run when an emit method is called * * @param {String} type Event type * @param {Function} callback */ this.on = function(type, callback) { if(typeof(type) != "string" || typeof(callback) != "function") return; this.event.push({ type: type.toLowerCase(), callback: callback, unique: false }); } /** * @method off * Removes a custom event of an applicable type or callback handler * * @param {String} type Event type */ this.off = function(type) { var event = []; for(var i = 0; i < this.event.length; i++) { var e = this.event[i]; if ((typeof(type) == "function" && e.callback != type) || (typeof(type) == "string" && e.type != type.toLowerCase())) { event.push(e); } } this.event = event; } /** * @method addEvent * Defines a browser event of a DOM element * * @param {String/HTMLElement} selector * @param {String} type Dom event type * @param {Function} callback */ this.addEvent = function() { this.listen.add(arguments); } /** * @method addTrigger * Generates an applicable event to a DOM element * * @param {String/HTMLElement} Selector * @param {String} Dom event type */ this.addTrigger = function(selector, type) { this.listen.trigger(selector, type); } /** * @method addValid * Check the parameter type of a UI method and generates an alarm when a wrong value is entered * * @param {String} name Method name * @param {Array} params Parameters */ this.addValid = function(name, params) { if(!this.__proto__) return; var ui = this.__proto__[name]; this.__proto__[name] = function() { var args = arguments; for(var i = 0; i < args.length; i++) { if(!_.typeCheck(params[i], args[i])) { throw new Error("JUI_CRITICAL_ERR: the " + i + "th parameter is not a " + params[i] + " (" + name + ")"); } } return ui.apply(this, args); } } /** * @method callBefore * Sets a callback function that is called before a UI method is run * * @param {String} name Method name * @param {Function} callback * @return {Mixed} */ this.callBefore = function(name, callback) { if(!this.__proto__) return; var ui = this.__proto__[name]; this.__proto__[name] = function() { var args = arguments; if(typeof(callback) == "function") { // before 콜백이 false가 이날 경우에만 실행 한다. if(callback.apply(this, args) !== false) { return ui.apply(this, args); } } else { return ui.apply(this, args); } } } /** * @method callAfter * Sets a callback function that is called after a UI method is run * * @param {String} name Method name * @param {Function} callback * @return {Mixed} */ this.callAfter = function(name, callback) { if(!this.__proto__) return; var ui = this.__proto__[name]; this.__proto__[name] = function() { var args = arguments, obj = ui.apply(this, args); // 실행 함수의 리턴 값이 false일 경우에는 after 콜백을 실행하지 않는다. if(typeof(callback) == "function" && obj !== false) { callback.apply(this, args); } return obj; } } /** * @method callDelay * Sets a callback function and the delay time before/after a UI method is run * * @param {String} name Method name * @param {Function} callback */ this.callDelay = function(name, callObj) { // void 형의 메소드에서만 사용할 수 있음 if(!this.__proto__) return; var ui = this.__proto__[name], delay = (!isNaN(callObj.delay)) ? callObj.delay : 0; this.__proto__[name] = function() { var self = this, args = arguments; if(typeof(callObj.before) == "function") { callObj.before.apply(self, args); } if(delay > 0) { setTimeout(function() { callFunc(self, args); }, delay); } else { callFunc(self, args); } } function callFunc(self, args) { var obj = ui.apply(self, args); if(typeof(callObj.after) == "function" && obj !== false) { // callAfter와 동일 callObj.after.apply(self, args); } } } /** * @method setTpl * Dynamically defines the template method of a UI * * @param {String} name Template name * @param {String} html Template markup */ this.setTpl = function(name, html) { this.tpl[name] = _.template(html); } /** * @method setVo * Dynamically defines the template method of a UI * * @deprecated */ this.setVo = function() { // @Deprecated if(!this.options.vo) return; if(vo != null) vo.reload(); vo = $(this.selector).jbinder(); this.bind = vo; } /** * @method setOption * Dynamically defines the options of a UI * * @param {String} key * @param {Mixed} value */ this.setOption = function(key, value) { if(typeof(key) == "object") { for(var k in key) { this.options[k] = key[k]; } } else { this.options[key] = value; } } /** * @method destroy * Removes all events set in a UI obejct and the DOM element * */ this.destroy = function() { if(!this.__proto__) return; for(var i = 0; i < this.listen.size(); i++) { var obj = this.listen.get(i); $(obj.target).off(obj.type); } for(var key in this.__proto__) { delete this.__proto__[key]; } } }; UICore.build = function(UI) { return function(selector, options) { var $root = $(selector || ""); var list = []; $root.each(function(index) { var mainObj = new UI["class"](); // Check Options var opts = jui.defineOptions(UI["class"], options || {}); // Public Properties mainObj.init.prototype = mainObj; /** @property {String/HTMLElement} selector */ mainObj.init.prototype.selector = $root.selector; /** @property {HTMLElement} root */ mainObj.init.prototype.root = this; /** @property {Object} options */ mainObj.init.prototype.options = opts; /** @property {Object} tpl Templates */ mainObj.init.prototype.tpl = {}; /** @property {Array} event Custom events */ mainObj.init.prototype.event = new Array(); // Custom Event /** @property {Object} listen Dom events */ mainObj.init.prototype.listen = new UIListener(); // DOM Event /** @property {Integer} timestamp UI Instance creation time*/ mainObj.init.prototype.timestamp = new Date().getTime(); /** @property {Integer} index Index of UI instance*/ mainObj.init.prototype.index = index; /** @property {Class} module Module class */ mainObj.init.prototype.module = UI; // Template Setting (Markup) $("script").each(function(i) { if(selector == $(this).data("jui") || selector == $(this).data("vo") || selector instanceof HTMLElement) { var tplName = $(this).data("tpl"); if(tplName == "") { throw new Error("JUI_CRITICAL_ERR: 'data-tpl' property is required"); } opts.tpl[tplName] = $(this).html(); } }); // Template Setting (Script) for(var name in opts.tpl) { var tplHtml = opts.tpl[name]; if(_.typeCheck("string", tplHtml) && tplHtml != "") { mainObj.init.prototype.tpl[name] = _.template(tplHtml); } } var uiObj = new mainObj.init(); // Event Setting for(var key in opts.event) { uiObj.on(key, opts.event[key]); } list[index] = uiObj; // 엘리먼트 객체에 jui 속성 추가 this.jui = uiObj; }); // UIManager에 데이터 입력 UIManager.add(new UICoreSet(UI.type, selector, options, list)); // 객체가 없을 경우에는 null을 반환 (기존에는 빈 배열을 반환) if(list.length == 0) { return null; } else if(list.length == 1) { return list[0]; } return list; } } UICore.init = function(UI) { var uiObj = null; if(typeof(UI) === "object") { uiObj = UICore.build(UI); UIManager.addClass({ type: UI.type, "class": uiObj }); } return uiObj; } UICore.setup = function() { return { /** * @cfg {Object} [tpl={}] * Defines a template markup to be used in a UI */ tpl: {}, /** * @cfg {Object} [event={}] * Defines a DOM event to be used in a UI */ event: {}, /** * @cfg {Object} [vo=null] * Configures a binding object of a markup * * @deprecated */ vo: null } } /** * @class jui * * @extends core.UIManager * @singleton */ window.jui = (typeof(jui) == "object") ? $.extend(jui, UIManager) : UIManager; return UICore; }); jui.define("util.math", [], function() { /** * @class util.math * * Math Utility * * @singleton */ var self = { /** * @method rotate * * 2d rotate * * @param {Number} x * @param {Number} y * @param {Number} radian roate 할 radian * @return {Object} * @return {Number} return.x 변환된 x * @return {Number} return.y 변환된 y * */ rotate : function(x, y, radian) { return { x : x * Math.cos(radian) - y * Math.sin(radian), y : x * Math.sin(radian) + y * Math.cos(radian) } }, resize : function(maxWidth, maxHeight, objectWidth, objectHeight) { var ratio = objectHeight / objectWidth; if (objectWidth >= maxWidth && ratio <= 1) { objectWidth = maxWidth; objectHeight = maxHeight * ratio; } else if (objectHeight >= maxHeight) { objectHeight = maxHeight; objectWidth = maxWidth / ratio; } return { width : objectWidth, height : objectHeight}; }, /** * @method radian * * convert degree to radian * * @param {Number} degree * @return {Number} radian */ radian : function(degree) { return degree * Math.PI / 180; }, /** * @method degree * * convert radian to degree * * @param {Number} radian * @return {Number} degree */ degree : function(radian) { return radian * 180 / Math.PI; }, angle : function(x1, y1, x2, y2) { var dx = x2 - x1, dy = y2 - y1; return Math.atan2(dy, dx); }, /** * @method interpolateNumber * * a, b 의 중간값 계산을 위한 callback 함수 만들기 * * @param {Number} a first value * @param {Number} b second value * @return {Function} */ interpolateNumber : function(a, b) { var dist = (b - a); return function(t) { return a + dist * t; } }, // 중간값 round 해서 계산하기 interpolateRound : function(a, b) { var dist = (b - a); return function(t) { return Math.round(a + dist * t); } }, /** * 특정 구간의 값을 자동으로 계산 * * @param {Object} min * @param {Object} max * @param {Object} ticks * @param {Object} isNice */ nice : function(min, max, ticks, isNice) { isNice = isNice || false; if (min > max) { var _max = min; var _min = max; } else { var _min = min; var _max = max; } var _ticks = ticks; var _tickSpacing = 0; var _range = []; var _niceMin; var _niceMax; function niceNum(range, round) { var exponent = Math.floor(Math.log(range) / Math.LN10); var fraction = range / Math.pow(10, exponent); var nickFraction; //console.log(range, exponent, fraction, _ticks); if (round) { if (fraction < 1.5) niceFraction = 1; else if (fraction < 3) niceFraction = 2; else if (fraction < 7) niceFraction = 5; else niceFraction = 10; } else { if (fraction <= 1) niceFraction = 1; else if (fraction <= 2) niceFraction = 2; else if (fraction <= 5) niceFraction = 5; else niceFraction = 10; //console.log(niceFraction) } return niceFraction * Math.pow(10, exponent); } function caculate() { _range = (isNice) ? niceNum(_max - _min, false) : _max - _min; _tickSpacing = (isNice) ? niceNum(_range / _ticks, true) : _range / _ticks; _niceMin = (isNice) ? Math.floor(_min / _tickSpacing) * _tickSpacing : _min; _niceMax = (isNice) ? Math.floor(_max / _tickSpacing) * _tickSpacing : _max; } caculate(); return { min : _niceMin, max : _niceMax, range : _range, spacing : _tickSpacing } } } return self; }); jui.define("util.time", [ "util.base" ], function(_) { /** * time 객체 * */ var self = { // unit years : 0x01, months : 0x02, days : 0x03, hours : 0x04, minutes : 0x05, seconds : 0x06, milliseconds : 0x07, weeks : 0x08, /** * 시간 더하기 * var date = new Date(); * * time.add(date, time.hours, 1); // 현재시간에서 1시간 추가 * time.add(date, time.hours, 1, time.minutes, 2); // 현재시간에서 1시간 2분 추가 * * @param {Object} date */ add : function(date) { if (arguments.length <= 2) { return date; } if (arguments.length > 2) { var d = new Date(+date); for (var i = 1; i < arguments.length; i += 2) { var split = typeof arguments[i] == 'string' ? this[arguments[i]] : arguments[i]; var time = arguments[i + 1]; if (this.years == split) { d.setFullYear(d.getFullYear() + time); } else if (this.months == split) { d.setMonth(d.getMonth() + time); } else if (this.days == split) { d.setDate(d.getDate() + time); } else if (this.hours == split) { d.setHours(d.getHours() + time); } else if (this.minutes == split) { d.setMinutes(d.getMinutes() + time); } else if (this.seconds == split) { d.setSeconds(d.getSeconds() + time); } else if (this.milliseconds == split) { d.setMilliseconds(d.getMilliseconds() + time); } else if (this.weeks == split) { d.setDate(d.getDate() + time * 7); } } return d; } }, /** * jui.util.dateFormat 의 alias * * @param {Object} date * @param {Object} format * @param {Object} utc */ format: function(date, format, utc) { return _.dateFormat(date, format, utc); } } return self; }); jui.define("util.scale", [ "util.math", "util.time" ], function(math, _time) { /** * scale utility * @class util.scale * @singleton */ var self = { /** * 원형 좌표에 대한 scale * */ circle : function() {// 원형 radar var that = this; var _domain = []; var _range = []; var _rangeBand = 0; function func(t) { } func.domain = function(values) { if ( typeof values == 'undefined') { return _domain; } for (var i = 0; i < values.length; i++) { _domain[i] = values[i]; } return this; } func.range = function(values) { if ( typeof values == 'undefined') { return _range; } for (var i = 0; i < values.length; i++) { _range[i] = values[i]; } return this; } func.rangePoints = function(interval, padding) { padding = padding || 0; var step = _domain.length; var unit = (interval[1] - interval[0] - padding) / step; var range = []; for (var i = 0; i < _domain.length; i++) { if (i == 0) { range[i] = interval[0] + padding / 2 + unit / 2; } else { range[i] = range[i - 1] + unit; } } _range = range; _rangeBand = unit; return func; } func.rangeBands = function(interval, padding, outerPadding) { padding = padding || 0; outerPadding = outerPadding || 0; var count = _domain.length; var step = count - 1; var band = (interval[1] - interval[0]) / step; var range = []; for (var i = 0; i < _domain.length; i++) { if (i == 0) { range[i] = interval[0]; } else { range[i] = band + range[i - 1]; } } _rangeBand = band; _range = range; return func; } func.rangeBand = function() { return _rangeBand; } return func; }, /** * * 순서를 가지는 리스트에 대한 scale * */ ordinal : function() {// 순서 var that = this; var _domain = []; var _range = []; var _rangeBand = 0; var _cache = {}; function func(t) { var key = "" + t; if (typeof _cache[key] != 'undefined') { return _cache[key]; } var index = -1; for (var i = 0; i < _domain.length; i++) { if (typeof t == 'string' && _domain[i] === t) { index = i; break; } } if (index > -1) { _cache[key] = _range[index]; return _range[index]; } else { if ( typeof _range[t] != 'undefined') { _domain[t] = t; _cache[key] = _range[t]; return _range[t]; } return null; } } func.domain = function(values) { if ( typeof values == 'undefined') { return _domain; } for (var i = 0; i < values.length; i++) { _domain[i] = values[i]; } return this; } func.range = function(values) { if ( typeof values == 'undefined') { return _range; } for (var i = 0; i < values.length; i++) { _range[i] = values[i]; } return this; } func.rangePoints = function(interval, padding) { padding = padding || 0; var step = _domain.length; var unit = (interval[1] - interval[0] - padding) / step; var range = []; for (var i = 0; i < _domain.length; i++) { if (i == 0) { range[i] = interval[0] + padding / 2 + unit / 2; } else { range[i] = range[i - 1] + unit; } } _range = range; _rangeBand = unit; return func; } func.rangeBands = function(interval, padding, outerPadding) { padding = padding || 0; outerPadding = outerPadding || 0; var count = _domain.length; var step = count - 1; var band = (interval[1] - interval[0]) / step; var range = []; for (var i = 0; i < _domain.length; i++) { if (i == 0) { range[i] = interval[0]; } else { range[i] = band + range[i - 1]; } } _rangeBand = band; _range = range; return func; } func.rangeBand = function() { return _rangeBand; } func.invert = function(x) { return Math.ceil(x / _rangeBand); } return func; }, /** * 시간에 대한 scale * */ time : function() {// 시간 var that = this; var _domain = []; var _range = []; var _rangeBand; var func = self.linear(); var df = func.domain; func.domain = function(domain) { if (!arguments.length) return df.call(func); for (var i = 0; i < domain.length; i++) { _domain[i] = +domain[i]; } return df.call(func, _domain); } func.min = function() { return Math.min(_domain[0], _domain[_domain.length - 1]); } func.max = function() { return Math.max(_domain[0], _domain[_domain.length - 1]); } func.rate = function(value, max) { return func(func.max() * (value / max)); } func.ticks = function(type, step) { var start = _domain[0]; var end = _domain[1]; var times = []; while (start < end) { times.push(new Date(+start)); start = _time.add(start, type, step); } times.push(new Date(+start)); var first = func(times[0]); var second = func(times[1]); _rangeBand = second - first; return times; } func.realTicks = function(type, step) { var start = _domain[0]; var end = _domain[1]; var times = []; var date = new Date(+start) var realStart = null; if (type == _time.years) { realStart = new Date(date.getFullYear(), 0, 1); } else if (type == _time.months) { realStart = new Date(date.getFullYear(), date.getMonth(), 1); } else if (type == _time.days || type == _time.weeks) { realStart = new Date(date.getFullYear(), date.getMonth(), date.getDate()); } else if (type == _time.hours) { realStart = new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), 0, 0, 0); } else if (type == _time.minutes) { realStart = new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), 0, 0); } else if (type == _time.seconds) { realStart = new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), 0); } else if (type == _time.milliseconds) { realStart = new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds()); } realStart = _time.add(realStart, type, step); while (+realStart < +end) { times.push(new Date(+realStart)); realStart = _time.add(realStart, type, step); } var first = func(times[1]); var second = func(times[2]); _rangeBand = second - first; return times; } func.rangeBand = function() { return _rangeBand; } func.invert = function(y) { var f = self.linear().domain(func.range()).range(func.domain()); return new Date(f(y)); } return func; }, /** * log scale * * var log = _.scale.log(10).domain([0, 1000000]).range([0, 300]); * * log(0) == 0 * log.ticks(4) == [0, 100, 10000, 1000000] * * @param base */ log : function(base) { var that = this; var _base = base || 10; var func = self.linear(); var _domain = []; var _domainMax = null; var _domainMin = null; function log(value) { if (value < 0) { return -(Math.log(Math.abs(value)) / Math.log(_base)); } else if (value > 0) { return Math.log(value) / Math.log(_base); } return 0; } function pow(value) { if (value < 0) { return - Math.pow(_base, Math.abs(value)); } else if (value > 0) { return Math.pow(_base, value); } return 0; } function checkMax(value) { return Math.pow(_base, (value+"").length-1) < value; } function getNextMax(value) { return Math.pow(_base, (value+"").length); } var newFunc = function(x) { var value = x; if (x > _domainMax) { value = _domainMax; } else if (x < _domainMin) { value = _domainMin; } return func(log(value)); } $.extend(newFunc, func); newFunc.log = function() { var newDomain = []; for (var i = 0; i < _domain.length; i++) { newDomain[i] = log(_domain[i]); } return newDomain; } newFunc.domain = function(values) { if (!arguments.length) { return _domain; } for (var i = 0; i < values.length; i++) { _domain[i] = values[i]; } _domainMax = Math.max.apply(Math, _domain); _domainMin = Math.min.apply(Math, _domain); if (checkMax(_domainMax)) { _domain[1] = _domainMax = getNextMax(_domainMax); } if (checkMax(Math.abs(_domainMin))) { var value = getNextMax(Math.abs(_domainMin)); _domain[0] = _domainMin = _domainMin < 0 ? -value : value ; } func.domain(newFunc.log()); return newFunc; } newFunc.base = function(base) { func.domain(newFunc.log()); return newFunc; } newFunc.invert = function(y) { return pow(func.invert(y)); } newFunc.ticks = function(count, isNice, intNumber) { var arr = func.ticks(count, isNice, intNumber || 100000000000000000000, true); if (arr[arr.length-1] < func.max()) { arr.push(func.max()); } var newArr = []; for(var i = 0, len = arr.length; i < len; i++) { newArr[i] = pow(arr[i]); } return newArr; } return newFunc; }, /** * 범위에 대한 scale * */ linear : function() {// 선형 var that = this; var _domain = [0, 1]; var _range = [0, 1]; var _isRound = false; var _isClamp = false; var _cache = {}; var roundFunction = null; var numberFunction = null; var domainMin = null; var domainMax = null; var rangeMin = null; var rangeMax = null; var distDomain = null; var distRange = null; var callFunction = null; var _rangeBand = null; function func(x) { if (domainMax < x) { if (_isClamp) { return func(domainMax); } return _range[0] + Math.abs(x - _domain[1]) * distDomain / distRange; } else if (domainMin > x) { if (_isClamp) { return func(domainMin); } return _range[0] - Math.abs(x - _domain[0]) * distDomain / distRange; } else { var pos = (x - _domain[0]) / (distDomain); return callFunction(pos); } } func.cache = function() { return _cache; } func.min = function() { return Math.min.apply(Math, _domain); } func.max = function() { return Math.max.apply(Math, _domain); } func.rangeMin = function() { return Math.min.apply(Math, _range); } func.rangeMax = function() { return Math.max.apply(Math, _range); } func.rate = function(value, max) { return func(func.max() * (value / max)); } func.clamp = function(isClamp) { _isClamp = isClamp || false; } func.domain = function(values) { if (!arguments.length) { return _domain; } for (var i = 0; i < values.length; i++) { _domain[i] = values[i]; } domainMin = func.min(); domainMax = func.max(); distDomain = _domain[1] - _domain[0]; return this; } func.range = function(values) { if (!arguments.length) { return _range; } for (var i = 0; i < values.length; i++) { _range[i] = values[i]; } roundFunction = math.interpolateRound(_range[0], _range[1]); numberFunction = math.interpolateNumber(_range[0], _range[1]); rangeMin = func.rangeMin(); rangeMax = func.rangeMax(); distRange = Math.abs(rangeMax - rangeMin); callFunction = _isRound ? roundFunction : numberFunction; return this; } func.rangeRound = function(values) { _isRound = true; return func.range(values); } func.rangeBand = function() { return _rangeBand; } func.invert = function(y) { var f = self.linear().domain(_range).range(_domain); return f(y); } func.ticks = function(count, isNice, intNumber, reverse) { intNumber = intNumber || 10000; reverse = reverse || false; var max = func.max(); if (_domain[0] == 0 && _domain[1] == 0) { return []; } var obj = math.nice(_domain[0], _domain[1], count || 10, isNice || false); var arr = []; var start = (reverse ? obj.max : obj.min) * intNumber; var end = (reverse ? obj.min : obj.max) * intNumber; while ((reverse ? end <= start : start <= end)) { arr.push(start / intNumber); var unit = obj.spacing * intNumber; if (reverse) { start -= unit; } else { start += unit; } } if (reverse) { if (arr[0] != max) { arr.unshift(max); } for(var i = 0, len = arr.length; i < len; i++) { arr[i] = Math.abs(arr[i] - max); } //arr.reverse(); } else { if (arr[arr.length - 1] * intNumber != end && start > end) { arr.push(end / intNumber); } if (_domain[0] > _domain[1]) { arr.reverse(); } } var first = func(arr[0]); var second = func(arr[1]); _rangeBand = Math.abs(second - first); return arr; } return func; } } return self; }); jui.define("util.color", [], function() { /** * @class util.color * color parser for chart * @singleton */ var self = { regex : /(linear|radial)\((.*)\)(.*)/i, trim : function (str) { return (str || "").replace(/^\s+|\s+$/g, ''); }, /** * @method lighten * * rgb 컬러 밝은 농도로 변환 * * @param {String} color RGB color code * @param {Number} rate 밝은 농도 * @return {String} */ lighten : function(color, rate) { color = color.replace(/[^0-9a-f]/gi, ''); rate = rate || 0; var rgb = [], c, i; for (i = 0; i < 6; i += 2) { c = parseInt(color.substr(i,2), 16); c = Math.round(Math.min(Math.max(0, c + (c * rate)), 255)).toString(16); rgb.push(("00"+c).substr(c.length)); } return "#" + rgb.join(""); }, /** * @method darken * * rgb 컬러 어두운 농도로 변환 * * @param {String} color RGB color code * @param {Number} rate 어두운 농도 * @return {String} */ darken : function(color, rate) { return this.lighten(color, -rate) }, /** * @method parse * * color 파싱 * * @param color * @returns {*} */ parse : function(color) { return this.parseGradient(color); }, /** * @method parseGrident * * gradient parser * * @example * linear(left) #fff,#000 * linear(right) #fff,50 yellow,black * radial(50%,50%,50%,50,50) * * @param {String} color */ parseGradient : function(color) { var matches = color.match(this.regex); if (!matches) return color; var type = this.trim(matches[1]); var attr = this.parseAttr(type, this.trim(matches[2])); var stops = this.parseStop(this.trim(matches[3])); var obj = { type : type + "Gradient", attr : attr, children : stops }; return obj; }, parseStop : function(stop) { var stop_list = stop.split(","); var stops = []; for(var i = 0, len = stop_list.length; i < len; i++) { var stop = stop_list[i]; var arr = stop.split(" "); if (arr.length == 0) continue; if (arr.length == 1) { stops.push({ type : "stop", attr : {"stop-color" : arr[0] } }) } else if (arr.length == 2) { stops.push({ type : "stop", attr : {"offset" : arr[0], "stop-color" : arr[1] } }) } else if (arr.length == 3) { stops.push({ type : "stop", attr : {"offset" : arr[0], "stop-color" : arr[1], "stop-opacity" : arr[2] } }) } } var start = -1; var end = -1; for(var i = 0, len = stops.length; i < len; i++) { var stop = stops[i]; if (i == 0) { if (!stop.offset) stop.offset = 0; } else if (i == len - 1) { if (!stop.offset) stop.offset = 1; } if (start == -1 && typeof stop.offset == 'undefined') { start = i; } else if (end == -1 && typeof stop.offset == 'undefined') { end = i; var count = end - start; var endOffset = stops[end].offset.indexOf("%") > -1 ? parseFloat(stops[end].offset)/100 : stops[end].offset; var startOffset = stops[start].offset.indexOf("%") > -1 ? parseFloat(stops[start].offset)/100 : stops[start].offset; var dist = endOffset - startOffset var value = dist/ count; var offset = startOffset + value; for(var index = start + 1; index < end; index++) { stops[index].offset = offset; offset += value; } start = end; end = -1; } } return stops; }, parseAttr : function(type, str) { if (type == 'linear') { switch(str) { case "": case "left": return { x1 : 0, y1 : 0, x2 : 1, y2 : 0, direction : str || "left" }; case "right": return { x1 : 1, y1 : 0, x2 : 0, y2 : 0, direction : str }; case "top": return { x1 : 0, y1 : 0, x2 : 0, y2 : 1, direction : str }; case "bottom": return { x1 : 0, y1 : 1, x2 : 0, y2 : 0, direction : str }; case "top left": return { x1 : 0, y1 : 0, x2 : 1, y2 : 1, direction : str }; case "top right": return { x1 : 1, y1 : 0, x2 : 0, y2 : 1, direction : str }; case "bottom left": return { x1 : 0, y1 : 1, x2 : 1, y2 : 0, direction : str }; case "bottom right": return { x1 : 1, y1 : 1, x2 : 0, y2 : 0, direction : str }; default : var arr = str.split(","); for(var i = 0, len = arr.length; i < len; i++) { if (arr[i].indexOf("%") == -1) arr[i] = parseFloat(arr[i]); } return { x1 : arr[0], y1 : arr[1],x2 : arr[2], y2 : arr[3] }; } } else { var arr = str.split(","); for(var i = 0, len = arr.length; i < len; i++) { if (arr[i].indexOf("%") == -1) arr[i] = parseFloat(arr[i]); } return { cx : arr[0], cy : arr[1],r : arr[2], fx : arr[3], fy : arr[4] }; } } } return self; }); jui.define("util.svg.element", [], function() { /** * @class util.svg.element * Create SVG Element * @constructor */ var Element = function() { var events = []; /** * 엘리먼트 생성 및 조회 메소드 * */ this.create = function(type, attr) { // 퍼블릭 프로퍼티 this.element = document.createElementNS("http://www.w3.org/2000/svg", type); this.children = []; this.parent = null; this.styles = {}; this.attributes = {}; // 기본 속성 설정 this.attr(attr); }; this.each = function(callback) { if(typeof(callback) != "function") return; for(var i = 0, len = this.children.length; i < len; i++) { var self = this.children[i]; callback.apply(self, [ i, self ]); } return this.children; }; this.get = function(index) { if(this.children[index]) { return this.children[index]; } return null; } this.index = function(obj) { for(var i = 0; i < this.children.length; i++) { if(obj == this.children[i]) { return i; } } return -1; } /** * 엘리먼트 관계 메소드 * */ this.append = function(elem) { if(elem instanceof Element) { if (elem.parent) { elem.remove(); } this.children.push(elem); elem.parent = this; } return this; } this.prepend = function(elem) { return this.insert(0, elem); } this.insert = function(index, elem) { if(elem.parent) { elem.remove(); } this.children.splice(index, 0, elem); elem.parent = this; return this; } this.remove = function() { var index = 0, nChild = [], pChild = this.parent.children; for(var i = 0; i < pChild.length; i++) { if (pChild[i] == this) { index = i; break; } nChild.push(pChild[i]); } this.parent.children = nChild; return this; } /** * 엘리먼트 DOM 조작 메소드 * */ this.attr = function(attr) { if(typeof attr == "undefined" || !attr) return; if(typeof attr == "string") { return this.attributes[attr] || this.element.getAttribute(attr); } for(var k in attr) { this.attributes[k] = attr[k]; if(k.indexOf("xlink:") != -1) { this.element.setAttributeNS("http://www.w3.org/1999/xlink", k, attr[k]); } else { this.element.setAttribute(k, attr[k]); } } return this; } this.css = function(css) { var list = []; for(var k in css) { this.styles[k] = css[k]; } for(var k in css) { list.push(k + ":" + css[k]); } this.attr({ style: list.join(";") }); return this; } this.html = function(html) { this.element.innerHTML = html; return this; } this.text = function(text) { this.element.innerHTML = ""; this.element.appendChild(document.createTextNode(text)); return this; } /** * 엘리먼트 DOM 이벤트 메소드 * */ this.on = function(type, handler) { var callback = function(e) { if(typeof(handler) == "function") { handler.call(this, e); } } this.element.addEventListener(type, callback, false); events.push({ type: type, callback: callback }); return this; } this.off = function(type) { var newEvents = []; for(var i = 0, len = events.length; i < len; i++) { var event = events[i]; if(event.type != type) { newEvents.push(event); } else { this.element.removeEventListener(type, event.callback, false); } } events = newEvents; return this; } this.hover = function(overHandler, outHandler) { var callback1 = function(e) { if(typeof(overHandler) == "function") { overHandler.call(this, e); } } var callback2 = function(e) { if(typeof(outHandler) == "function") { outHandler.call(this, e); } } this.element.addEventListener("mouseover", callback1, false); this.element.addEventListener("mouseout", callback2, false); events.push({ type: "mouseover", callback: callback1 }); events.push({ type: "mouseout", callback: callback2 }); return this; } /** * 그 외 메소드 * */ this.size = function() { var size = { width: 0, height: 0 }, rect = this.element.getBoundingClientRect(); if(!rect || (rect.width == 0 && rect.height == 0)) { var height_list = [ "height", "paddingTop", "paddingBottom", "borderTopWidth", "borderBottomWidth" ], width_list = [ "width", "paddingLeft", "paddingRight", "borderLeftWidth", "borderRightWidth" ]; var computedStyle = window.getComputedStyle(this.element); for (var i = 0; i < height_list.length; i++) { size.height += parseFloat(computedStyle[height_list[i]]); } for (var i = 0; i < width_list.length; i++) { size.width += parseFloat(computedStyle[width_list[i]]); } size.width = size.width || this.element.getAttribute('width'); size.height = size.height || this.element.getAttribute('height'); } else { size.width = rect.width; size.height = rect.height; } if(isNaN(size.width)) size.width = 0; if(isNaN(size.height)) size.height = 0; return size; } this.is = function(moduleId) { return this instanceof jui.include(moduleId); } } return Element; }); jui.define("util.svg.element.transform", [ "util.base" ], function(_) { // polygon, polyline /** * @class util.svg.element.transform * * implement Transform Element * * @extends util.svg.element * @constructor */ var TransElement = function() { var orders = { translate: null, scale: null, rotate: null, skew: null, matrix: null }; function applyOrders(self) { var orderArr = []; for(var key in orders) { if(orders[key]) orderArr.push(orders[key]); } self.attr({ transform: orderArr.join(" ") }); } function getStringArgs(args) { var result = []; for(var i = 0; i < args.length; i++) { result.push(args[i]); } return result.join(","); } this.translate = function() { orders["translate"] = "translate(" + getStringArgs(arguments) + ")"; applyOrders(this); return this; } this.rotate = function(angle, x, y) { if(arguments.length == 1) { var str = angle; } else if(arguments.length == 3) { var str = angle + " " + x + "," + y; } orders["rotate"] = "rotate(" + str + ")"; applyOrders(this); return this; } this.scale = function() { orders["scale"] = "scale(" + getStringArgs(arguments) + ")"; applyOrders(this); return this; } this.skew = function() { orders["skew"] = "skew(" + getStringArgs(arguments) + ")"; applyOrders(this); return this; } this.matrix = function() { orders["matrix"] = "matrix(" + getStringArgs(arguments) + ")"; applyOrders(this); return this; } this.data = function(type) { var text = this.attr("transform"), regex = { translate: /[^translate()]+/g, rotate: /[^rotate()]+/g, scale: /[^scale()]+/g, skew: /[^skew()]+/g, matrix: /[^matrix()]+/g }; if(_.typeCheck("string", text)) { return text.match(regex[type])[0]; } return null; } } return TransElement; }, "util.svg.element"); jui.define("util.svg.element.path", [ "util.base" ], function(_) { // path var PathElement = function() { var orders = []; this.moveTo = function(x, y, type) { orders.push( (type || "m") + x + "," + y ); return this; } this.MoveTo = function(x, y) { return this.moveTo(x, y, "M"); } this.lineTo = function(x, y, type) { orders.push( (type || "l") + x + "," + y ); return this; } this.LineTo = function(x, y) { return this.lineTo(x, y, "L"); } this.hLineTo = function(x, type) { orders.push( (type || "h") + x ); return this; } this.HLineTo = function(x) { return this.hLineTo(x, "H"); } this.vLineTo = function(y, type) { orders.push( (type || "v") + y ); return this; } this.VLineTo = function(y) { return this.vLineTo(y, "V"); } this.curveTo = function(x1, y1, x2, y2, x, y, type) { orders.push( (type || "c") + x1 + "," + y1 + " " + x2 + "," + y2 + " " + x + "," + y ); return this; } this.CurveTo = function(x1, y1, x2, y2, x, y) { return this.curveTo(x1, y1, x2, y2, x, y, "C"); } this.sCurveTo = function(x2, y2, x, y, type) { orders.push( (type || "s") + x2 + "," + y2 + " " + x + "," + y ); return this; } this.SCurveTo = function(x2, y2, x, y) { return this.sCurveTo(x2, y2, x, y, "S"); } this.qCurveTo = function(x1, y1, x, y, type) { orders.push( (type || "q") + x1 + "," + y1 + " " + x + "," + y ); return this; } this.QCurveTo = function(x1, y1, x, y) { return this.qCurveTo(x1, y1, x, y, "Q"); } this.tCurveTo = function(x1, y1, x, y, type) { orders.push( (type || "t") + x1 + "," + y1 + " " + x + "," + y ); return this; } this.TCurveTo = function(x1, y1, x, y) { return this.tCurveTo(x1, y1, x, y, "T"); } this.arc = function(rx, ry, x_axis_rotation, large_arc_flag, sweep_flag, x, y, type) { large_arc_flag = (large_arc_flag) ? 1 : 0; sweep_flag = (sweep_flag) ? 1 : 0; orders.push( (type || "a") + rx + "," + ry + " " + x_axis_rotation + " " + large_arc_flag + "," + sweep_flag + " " + x + "," + y ); return this; } this.Arc = function(rx, ry, x_axis_rotation, large_arc_flag, sweep_flag, x, y) { return this.arc(rx, ry, x_axis_rotation, large_arc_flag, sweep_flag, x, y, "A"); } this.closePath = function(type) { orders.push( (type || "z") ); return this; } this.ClosePath = function() { return this.closePath("Z"); } this.join = function() { if(orders.length > 0) { this.attr({ d: orders.join(" ") }); orders = []; } } this.length = function() { var id = _.createId(), d = orders.join(" "); var svg = document.createElement("svg"), path = document.createElementNS("http://www.w3.org/2000/svg", "path"); path.setAttributeNS(null, "id", id); path.setAttributeNS(null, "d", d); svg.appendChild(path); document.body.appendChild(svg); var length = document.getElementById(id).getTotalLength(); document.body.removeChild(svg); return length; } } return PathElement; }, "util.svg.element.transform"); jui.define("util.svg.element.path.symbol", [ "util.base" ], function(_) { // symbol var PathSymbolElement = function() { var ordersString = ""; /** * 심볼 템플릿 * */ this.template = function(width, height) { var r = width, half_width = half_r = width / 2, half_height = height / 2; var start = "a" + half_r + "," + half_r + " 0 1,1 " + r + ",0", end = "a" + half_r + "," + half_r + " 0 1,1 " + -r + ",0"; var obj = { triangle : ["m0," + -half_height, "l" + (half_width) + "," + height, "l" + (-width) + ",0", "l" + (half_width) + "," + (-height)].join(" "), rect : ["m" + (-half_width) + "," + (-half_height), "l" + (width) + ",0", "l0," + (height) , "l" + (-width) + ',0', "l0," + (-height)].join(" "), cross : ["m" + (-half_width) + ',' + (-half_height), "l" + (width) + "," + (height), "m0," + (-height), "l" + (-width) + "," + (height)].join(" "), circle : ["m" + (-r) + ",0", start, end ].join(" ") } obj.rectangle = obj.rect; return obj; } this.join = function() { if(ordersString.length > 0) { this.attr({ d: ordersString }); ordersString = ""; } } /** * 심볼 추가 하기 (튜닝) */ this.add = function(cx, cy, tpl) { ordersString += " M" + (cx) + "," + (cy) + tpl; } /** * path 내 심볼 생성 * */ this.triangle = function(cx, cy, width, height) { return this.MoveTo(cx, cy).moveTo(0, -height/2).lineTo(width/2,height).lineTo(-width, 0).lineTo(width/2, -height); } this.rect = this.rectangle = function(cx, cy, width, height) { return this.MoveTo(cx, cy).moveTo(-width/2, -height/2).lineTo(width,0).lineTo(0, height).lineTo(-width, 0).lineTo(0, -height); } this.cross = function(cx, cy, width, height) { return this.MoveTo(cx, cy).moveTo(-width/2, -height/2).lineTo(width, height).moveTo(0, -height).lineTo(-width, height); } this.circle = function(cx, cy, r) { return this.MoveTo(cx, cy).moveTo(-r, 0).arc(r/2, r/2, 0, 1, 1, r, 0).arc(r/2, r/2, 0, 1, 1, -r, 0); } } return PathSymbolElement; }, "util.svg.element.path"); jui.define("util.svg.element.path.rect", [ "util.math" ], function(math) { var PathRectElement = function() { this.round = function(width, height, tl, tr, br, bl) { tl = (!tl) ? 0 : tl; tr = (!tr) ? 0 : tr; br = (!br) ? 0 : br; bl = (!bl) ? 0 : bl; this.MoveTo(0, tl) .Arc(tl, tl, 0, 0, 1, tl, 0) .HLineTo(width - tr) .Arc(tr, tr, 0, 0, 1, width, tr) .VLineTo(height - br) .Arc(br, br, 0, 0, 1, width - br, height) .HLineTo(bl) .Arc(bl, bl, 0, 0, 1, 0, height - bl) .ClosePath() .join(); } } return PathRectElement; }, "util.svg.element.path"); jui.define("util.svg.element.poly", [], function() { // polygon, polyline var PolyElement = function() { var orders = []; this.point = function(x, y) { orders.push(x + "," + y); return this; } this.join = function() { if(orders.length > 0) { // Firefox 처리 var start = orders[0]; orders.push(start); // 폴리곤 그리기 this.attr({ points: orders.join(" ") }); orders = []; } } } return PolyElement; }, "util.svg.element.transform"); jui.define("util.svg.base", [ "util.base", "util.math", "util.color", "util.svg.element", "util.svg.element.transform", "util.svg.element.path", "util.svg.element.path.symbol", "util.svg.element.path.rect", "util.svg.element.poly" ], function(_, math, color, Element, TransElement, PathElement, PathSymbolElement, PathRectElement, PolyElement) { var globalObj = null; var SVGBase = function() { this.create = function(obj, type, attr, callback) { obj.create(type, attr); return obj; } this.createChild = function(obj, type, attr, callback) { return this.create(obj, type, attr, callback); } /** * @method custom * * return custom element * * @param {String} name * @param {Object} attr * @param {Function} callback * @return {util.svg.element} */ this.custom = function(name, attr, callback) { return this.create(new Element(), name, attr, callback); } /** * @method defs * * return defs element * * @param {Function} callback * @return {util.svg.element} */ this.defs = function(callback) { return this.create(new Element(), "defs", null, callback); } /** * @method symbol * * return symbol element * * @param {Object} attr * @param {Function} callback * @return {util.svg.element} */ this.symbol = function(attr, callback) { return this.create(new Element(), "symbol", attr, callback); } /** * @method g * * return defs element * * @alias group * @param {Object} attr * @param {Function} callback * @return {util.svg.element.transform} */ this.g = this.group = function(attr, callback) { return this.create(new TransElement(), "g", attr, callback); } /** * @method marker * * return marker element * * @param {Object} attr * @param {Function} callback * @return {util.svg.element} */ this.marker = function(attr, callback) { return this.create(new Element(), "marker", attr, callback); } /** * @method a * * return a element * * @param {Object} attr * @param {Function} callback * @return {util.svg.element.transform} */ this.a = function(attr, callback) { return this.create(new TransElement(), "a", attr, callback); } /** * @method switch * * return switch element * * @param {Object} attr * @param {Function} callback * @return {util.svg.element} */ this.switch = function(attr, callback) { return this.create(new Element(), "switch", attr, callback); } /** * @method use * * return use element * * @param {Object} attr * @return {util.svg.element} */ this.use = function(attr) { return this.create(new Element(), "use", attr); } /** * @method rect * * return rect element * * @param {Object} attr * @param {Function} callback * @return {util.svg.element.transform} */ this.rect = function(attr, callback) { return this.create(new TransElement(), "rect", attr, callback); } /** * @method line * * return line element * * @param {Object} attr * @param {Function} callback * @return {util.svg.element.transform} */ this.line = function(attr, callback) { return this.create(new TransElement(), "line", attr, callback); } this.circle = function(attr, callback) { return this.create(new TransElement(), "circle", attr, callback); } this.text = function(attr, textOrCallback) { if(arguments.length == 2) { if (_.typeCheck("function", textOrCallback)) { return this.create(new TransElement(), "text", attr, textOrCallback); } return this.create(new TransElement(), "text", attr).text(textOrCallback); } return this.create(new TransElement(), "text", attr); } this.textPath = function(attr, text) { if(_.typeCheck("string", text)) { return this.create(new Element(), "textPath", attr).text(text); } return this.create(new Element(), "textPath", attr); } this.tref = function(attr, text) { if(_.typeCheck("string", text)) { return this.create(new Element(), "tref", attr).text(text); } return this.create(new Element(), "tref", attr); } this.tspan = function(attr, text) { if(_.typeCheck("string", text)) { return this.create(new Element(), "tspan", attr).text(text); } return this.create(new Element(), "tspan", attr); } this.ellipse = function(attr, callback) { return this.create(new TransElement(), "ellipse", attr, callback); } this.image = function(attr, callback) { return this.create(new TransElement(), "image", attr, callback); } this.path = function(attr, callback) { return this.create(new PathElement(), "path", attr, callback); } this.pathSymbol = function(attr, callback) { return this.create(new PathSymbolElement(), "path", attr, callback); } this.pathRect = function(attr, callback) { return this.create(new PathRectElement(), "path", attr, callback); } this.polyline = function(attr, callback) { return this.create(new PolyElement(), "polyline", attr, callback); } this.polygon = function(attr, callback) { return this.create(new PolyElement(), "polygon", attr, callback); } this.pattern = function(attr, callback) { return this.create(new Element(), "pattern", attr, callback); } this.mask = function(attr, callback) { return this.create(new Element(), "mask", attr, callback); } this.clipPath = function(attr, callback) { return this.create(new Element(), "clipPath", attr, callback); } this.linearGradient = function(attr, callback) { return this.create(new Element(), "linearGradient", attr, callback); } this.radialGradient = function(attr, callback) { return this.create(new Element(), "radialGradient", attr, callback); } this.filter = function(attr, callback) { return this.create(new Element(), "filter", attr, callback); } /** * 엘리먼트 관련 메소드 (그라데이션) * */ this.stop = function(attr) { return this.createChild(new Element(), "stop", attr); } /** * 엘리먼트 관련 메소드 (애니메이션) * */ this.animate = function(attr) { return this.createChild(new Element(), "animate", attr); } this.animateColor = function(attr) { return this.createChild(new Element(), "animateColor", attr); } this.animateMotion = function(attr) { return this.createChild(new Element(), "animateMotion", attr); } this.animateTransform = function(attr) { return this.createChild(new Element(), "animateTransform", attr); } this.mpath = function(attr) { return this.createChild(new Element(), "mpath", attr); } this.set = function(attr) { return this.createChild(new Element(), "set", attr); } /** * 엘리먼트 관련 메소드 (필터) * */ this.feBlend = function(attr) { return this.createChild(new Element(), "feBlend", attr); } this.feColorMatrix = function(attr) { return this.createChild(new Element(), "feColorMatrix", attr); } this.feComponentTransfer = function(attr) { return this.createChild(new Element(), "feComponentTransfer", attr); } this.feComposite = function(attr) { return this.createChild(new Element(), "feComposite", attr); } this.feConvolveMatrix = function(attr) { return this.createChild(new Element(), "feConvolveMatrix", attr); } this.feDiffuseLighting = function(attr) { return this.createChild(new Element(), "feDiffuseLighting", attr); } this.feDisplacementMap = function(attr) { return this.createChild(new Element(), "feDisplacementMap", attr); } this.feFlood = function(attr) { return this.createChild(new Element(), "feFlood", attr); } this.feGaussianBlur = function(attr) { return this.createChild(new Element(), "feGaussianBlur", attr); } this.feImage = function(attr) { return this.createChild(new Element(), "feImage", attr); } this.feMerge = function(attr, callback) { return this.createChild(new Element(), "feMerge", attr, callback); } this.feMergeNode = function(attr) { return this.createChild(new Element(), "feMergeNode", attr); } this.feMorphology = function(attr) { return this.createChild(new Element(), "feMorphology", attr); } this.feOffset = function(attr) { return this.createChild(new Element(), "feOffset", attr); } this.feSpecularLighting = function(attr) { return this.createChild(new Element(), "feSpecularLighting", attr); } this.feTile = function(attr) { return this.createChild(new Element(), "feTile", attr); } this.feTurbulence = function(attr) { return this.createChild(new Element(), "feTurbulence", attr); } } SVGBase.create = function(name, attr, callback) { if(globalObj == null) { globalObj = new SVGBase(); } return globalObj.custom(name, attr, callback); } return SVGBase; }); jui.define("util.svg.3d", [ "util.base", "util.math", "util.color" ], function(_, math, color) { var SVG3d = function() { // 3D 사각형 그리기 this.rect3d = function(fill, width, height, degree, depth) { var self = this; var radian = math.radian(degree), x1 = 0, y1 = 0, w1 = width, h1 = height; var x2 = Math.cos(radian) * depth, y2 = Math.sin(radian) * depth, w2 = width + x2, h2 = height + y2; var g = self.group({}, function() { self.path({ fill: color.lighten(fill, 0.15), stroke: color.lighten(fill, 0.15) }).MoveTo(x2, x1) .LineTo(w2, y1) .LineTo(w1, y2) .LineTo(x1, y2); self.path({ fill: fill, stroke: fill }).MoveTo(x1, y2) .LineTo(x1, h2) .LineTo(w1, h2) .LineTo(w1, y2); self.path({ fill: color.darken(fill, 0.2), stroke: color.darken(fill, 0.2) }).MoveTo(w1, h2) .LineTo(w2, h1) .LineTo(w2, y1) .LineTo(w1, y2); }); return g; } // 3D 타원 그리기 this.cylinder3d = function(fill, width, height, degree, depth, rate) { var self = this; var radian = math.radian(degree), rate = (rate == undefined) ? 1 : (rate == 0) ? 0.01 : rate, r = width / 2, tr = r * rate, l = (Math.cos(radian) * depth) / 2, d = (Math.sin(radian) * depth) / 2, key = _.createId("cylinder3d"); var g = self.group({}, function() { self.ellipse({ fill: color.darken(fill, 0.05), "fill-opacity": 0.85, stroke: color.darken(fill, 0.05), rx: r, ry: d, cx: r, cy: height }).translate(l, d); self.path({ fill: "url(#" + key + ")", "fill-opacity": 0.85, stroke: fill }).MoveTo(r - tr, d) .LineTo(0, height) .Arc(r, d, 0, 0, 0, width, height) .LineTo(r + tr, d) .Arc(r + tr, d, 0, 0, 1, r - tr, d) .translate(l, d); self.ellipse({ fill: color.lighten(fill, 0.2), "fill-opacity": 0.95, stroke: color.lighten(fill, 0.2), rx: r * rate, ry: d * rate, cx: r, cy: d }).translate(l, d); self.linearGradient({ id: key, x1: "100%", x2: "0%", y1: "0%", y2: "0%" }, function() { self.stop({ offset: "0%", "stop-color": color.lighten(fill, 0.15) }); self.stop({ offset: "33.333333333333336%", "stop-color": color.darken(fill, 0.2) }); self.stop({ offset: "66.66666666666667%", "stop-color": color.darken(fill, 0.2) }); self.stop({ offset: "100%", "stop-color": color.lighten(fill, 0.15) }); }); }); return g; } } return SVG3d; }, "util.svg.base"); jui.define("util.svg", [ "util.base", "util.math", "util.color", "util.svg.element", "util.svg.element.transform", "util.svg.element.path", "util.svg.element.path.symbol", "util.svg.element.path.rect", "util.svg.element.poly" ], function(_, math, color, Element, TransElement, PathElement, PathSymbolElement, PathRectElement, PolyElement) { /** * @class util.svg * * SVG Util * * @param {jQuery/Element} rootElem * @param {Object} rootAttr * @constructor * @alias SVG */ var SVG = function(rootElem, rootAttr) { var self = this, root = null, main = null, sub = null, parent = {}, depth = 0; var isFirst = false; // 첫번째 렌더링 체크 function init() { self.root = root = new Element(); main = new TransElement(); sub = new TransElement(); root.create("svg", rootAttr); main.create("g"); sub.create("g"); main.translate(0.5, 0.5); sub.translate(0.5, 0.5); rootElem.appendChild(root.element); root.append(main); root.append(sub); } function appendAll(target) { var len = target.children.length; for(var i = 0; i < len; i++) { var child = target.children[i]; if(child) { if(child.children.length > 0) { appendAll(child); } // PathElement & PathSymbolElement & PathRectElement & PolyElement auto join if(child instanceof PathElement || child instanceof PolyElement) { child.join(); } if(child.parent == target) { target.element.appendChild(child.element); } } } } this.create = function(obj, type, attr, callback) { obj.create(type, attr); if(depth == 0) { main.append(obj); } else { parent[depth].append(obj); } if(_.typeCheck("function", callback)) { depth++; parent[depth] = obj; callback.call(obj); depth--; } return obj; } this.createChild = function(obj, type, attr, callback) { if(obj.parent == main) { throw new Error("JUI_CRITICAL_ERR: Parents are required elements of the '" + type + "'"); } return this.create(obj, type, attr, callback); } /** * @method size * * if arguments.length is 2, set attribute width, height to root element * if arguments.length is zero, return svg size * * @return {Object} * @return {Integer} width * @return {Integer} height */ this.size = function() { if(arguments.length == 2) { var w = arguments[0], h = arguments[1]; root.attr({ width: w, height: h }); } else { return root.size(); } } /** * @method clear * @param isAll */ this.clear = function(isAll) { main.each(function() { if(this.element.parentNode) { main.element.removeChild(this.element); } }); if(isAll === true) { sub.each(function() { if(this.element.parentNode) { sub.element.removeChild(this.element); } }); } } /** * @method reset * @param isAll */ this.reset = function(isAll) { this.clear(isAll); main.children = []; if(isAll === true) { sub.children = []; } } /** * @method render * @param isAll */ this.render = function(isAll) { this.clear(); if(isFirst === false || isAll === true) { appendAll(root); } else { appendAll(main); } isFirst = true; } /** * @method * implements svg image file download used by canvas * @param name */ this.download = function(name) { if(_.typeCheck("string", name)) { name = name.split(".")[0]; } var a = document.createElement("a"); a.download = (name) ? name + ".svg" : "svg.svg"; a.href = this.toDataURI()//;_.svgToBase64(rootElem.innerHTML); document.body.appendChild(a); a.click(); a.parentNode.removeChild(a); } this.downloadImage = function(name, type) { type = type || "image/png"; var img = new Image(); var size = this.size(); var uri = this.toDataURI() .replace('width="100%"', 'width="' + size.width + '"') .replace('height="100%"', 'height="' + size.height + '"'); img.onload = function(){ var canvas = document.createElement("canvas"); canvas.width = img.width; canvas.height = img.height; var context = canvas.getContext('2d'); context.drawImage(img, 0, 0); var png = canvas.toDataURI(type); if(_.typeCheck("string", name)) { name = name.split(".")[0]; } var a = document.createElement('a'); a.download = (name) ? name + ".png" : "svg.png"; a.href = png; document.body.appendChild(a); a.click(); a.parentNode.removeChild(a); } img.src = uri; } /** * @method exportCanvas * * convert svg image to canvas * * @param {Canvas} canvas */ this.exportCanvas = function(canvas) { var img = new Image(), size = this.size(); var uri = this.toDataURI() .replace('width="100%"', 'width="' + size.width + '"') .replace('height="100%"', 'height="' + size.height + '"'); img.onload = function() { canvas.width = img.width; canvas.height = img.height; var context = canvas.getContext('2d'); context.drawImage(img, 0, 0); } img.src = uri; } /** * @method toXML * * convert xml string * * @return {String} xml */ this.toXML = function() { var text = rootElem.innerHTML; text = text.replace('xmlns="http://www.w3.org/2000/svg"', ''); return [ '', text.replace("