jui.define("ui.tree.node", [ "jquery" ], function($) { /** * @class ui.tree.node * implements Tree's Node * @alias TreeNode * @requires jquery */ var Node = function(data, tplFunc) { var self = this; /** @property {Array} [data=null] Data of a specifiednode */ this.data = data; /** @property {HTMLElement} [element=null] LI element of a specified node */ this.element = null; /** @property {Integer} [index=null] Index of a specified node */ this.index = null; /** @property {Integer} [nodenum=null] Unique number of a specifiede node at the current depth */ this.nodenum = null; /** @property {ui.tree.node} [parent=null] Variable that refers to the parent of the current node */ this.parent = null; /** @property {Array} [children=null] List of child nodes of a specified node */ this.children = []; /** @property {Integer} [depth=0] Depth of a specified node */ this.depth = 0; /** @property {String} [type='open'] State value that indicates whether a child node is shown or hidden */ this.type = "open"; function setIndex(nodenum) { self.nodenum = (!isNaN(nodenum)) ? nodenum : self.nodenum; if(self.parent) { if(self.parent.index == null) self.index = "" + self.nodenum; else self.index = self.parent.index + "." + self.nodenum; } // 뎁스 체크 if(self.parent && typeof(self.index) == "string") { self.depth = self.index.split(".").length; } // 자식 인덱스 체크 if(self.children.length > 0) { setIndexChild(self); } } function setIndexChild(node) { var clist = node.children; for(var i = 0; i < clist.length; i++) { clist[i].reload(i); if(clist[i].children.length > 0) { setIndexChild(clist[i]); } } } function getElement() { if(!tplFunc) return self.element; try { var element = $(tplFunc( $.extend({ node: { index: self.index, data: self.data, depth: self.depth } }, self.data)) ).get(0); } catch(e) { console.log(e); } return element; } function removeChildAll(node) { $(node.element).remove(); for(var i = 0; i < node.children.length; i++) { var cNode = node.children[i]; if(cNode.children.length > 0) { removeChildAll(cNode); } else { $(cNode.element).remove(); } } } function reloadChildAll(node) { for(var i = 0; i < node.children.length; i++) { var cNode = node.children[i]; cNode.reload(i); if(cNode.children.length > 0) { reloadChildAll(cNode); } } } this.reload = function(nodenum, isUpdate) { setIndex(nodenum); // 노드 인덱스 설정 if(this.element != null) { var newElem = getElement(); if(!isUpdate) { $(this.parent.element).children("ul").append(newElem); } else { $(newElem).insertAfter(this.element); } $(this.element).remove(); this.element = newElem; } else { this.element = getElement(); } } this.reloadChildrens = function() { reloadChildAll(this); } this.destroy = function() { if(this.parent != null) { // 부모가 있을 경우, 연결관계 끊기 this.parent.removeChild(this.index); } else { removeChildAll(this); $(this.element).remove(); } } this.isLeaf = function() { return (this.children.length == 0) ? true : false; } this.fold = function() { $(this.element).children("ul").hide(); this.type = "fold"; } this.open = function() { $(this.element).children("ul").show(); this.type = "open"; } this.appendChild = function(node) { $(this.element).children("ul").append(node.element); this.children.push(node); } this.insertChild = function(nodenum, node) { if(nodenum == 0) { if(this.children.length == 0) { $(this.element).children("ul").append(node.element); } else { $(node.element).insertBefore(this.children[0].element); } } else { $(node.element).insertAfter(this.children[nodenum - 1].element); } var preNodes = this.children.splice(0, nodenum); preNodes.push(node); this.children = preNodes.concat(this.children); reloadChildAll(this); } this.removeChild = function(index) { for(var i = 0; i < this.children.length; i++) { var node = this.children[i]; if(node.index == index) { this.children.splice(i, 1); // 배열에서 제거 removeChildAll(node); } } reloadChildAll(this); } this.lastChild = function() { if(this.children.length > 0) return this.children[this.children.length - 1]; return null; } this.lastChildLeaf = function(lastRow) { var row = (!lastRow) ? this.lastChild() : lastRow; if(row.isLeaf()) return row; else { return this.lastChildLeaf(row.lastChild()); } } } return Node; }); jui.define("ui.tree.base", [ "jquery", "util.base", "ui.tree.node" ], function($, _, Node) { var Base = function(handler) { var self = this, root = null; var $obj = handler.$obj, $tpl = handler.$tpl; var iParser = _.index(); function createNode(data, no, pNode) { var node = new Node(data, $tpl.node); node.parent = (pNode) ? pNode : null; node.reload(no); return node; } function setNodeChildAll(dataList, node) { var c_nodes = node.children; if(c_nodes.length > 0) { for(var i = 0; i < c_nodes.length; i++) { dataList.push(c_nodes[i]); if(c_nodes[i].children.length > 0) { setNodeChildAll(dataList, c_nodes[i]); } } } } function getNodeChildLeaf(keys, node) { if(!node) return null; var tmpKey = keys.shift(); if(tmpKey == undefined) { return node; } else { return getNodeChildLeaf(keys, node.children[tmpKey]); } } function insertNodeDataChild(index, data) { var keys = iParser.getIndexList(index); var pNode = self.getNodeParent(index), nodenum = keys[keys.length - 1], node = createNode(data, nodenum, pNode); // 데이터 갱신 pNode.insertChild(nodenum, node); return node; } function appendNodeData(data) { if(root == null) { root = createNode(data);; $obj.tree.append(root.element); } else { var node = createNode(data, root.children.length, root); root.appendChild(node); } return node; } function appendNodeDataChild(index, data) { var pNode = self.getNode(index), cNode = createNode(data, pNode.children.length, pNode); pNode.appendChild(cNode); return cNode; } function isRelative(node, targetNode) { var nodeList = []; while(true) { var tNode = targetNode.parent; if(tNode) { nodeList.push(tNode); targetNode = tNode; } else { break; } } for(var i = 0; i < nodeList.length; i++) { if(node == nodeList[i]) { return true; } } return false; } this.appendNode = function() { var index = arguments[0], data = arguments[1]; if(!data) { return appendNodeData(index); } else { return appendNodeDataChild(index, data); } } this.insertNode = function(index, data) { if(root.children.length == 0 && parseInt(index) == 0) { return this.appendNode(data); } else { return insertNodeDataChild(index, data); } } this.updateNode = function(index, data) { var node = this.getNode(index); for(var key in data) { node.data[key] = data[key]; } node.reload(node.nodenum, true); node.reloadChildrens(); return node; } this.removeNode = function(index) { this.getNode(index).destroy(); } this.removeNodes = function() { var nodes = root.children; if(nodes.length > 0) { var node = nodes.pop(); node.parent = null; node.destroy(); this.removeNodes(); } } this.openNode = function(index) { if(index == null) this.getRoot().open(); else this.getNode(index).open(); } this.foldNode = function(index) { if(index == null) this.getRoot().fold(); else this.getNode(index).fold(); } this.openNodeAll = function(index) { var nodeList = this.getNodeAll(index); for(var i = 0; i < nodeList.length; i++) { nodeList[i].open(); } if(index == null) this.getRoot().open(); } this.foldNodeAll = function(index) { var nodeList = this.getNodeAll(index); for(var i = 0; i < nodeList.length; i++) { nodeList[i].fold(); } if(index == null) this.getRoot().fold(); } function isFamily(index, targetIndex) { var parentIndex = iParser.getParentIndex(targetIndex); if(parentIndex == null) { return false; } if(index == parentIndex) { return true; } return isFamily(index, parentIndex); } this.moveNode = function(index, targetIndex) { if(index == targetIndex) return; if(isFamily(index, targetIndex)) return; var node = this.getNode(index), tpNode = this.getNodeParent(targetIndex); var indexList = iParser.getIndexList(targetIndex), tNo = indexList[indexList.length - 1]; if(!isRelative(node, tpNode)) { // 기존의 데이터 node.parent.children.splice(node.nodenum, 1); node.parent.reloadChildrens(); node.parent = tpNode; // 이동 대상 데이터 처리 var preNodes = tpNode.children.splice(0, tNo); preNodes.push(node); tpNode.children = preNodes.concat(tpNode.children); tpNode.reloadChildrens(); } } this.getNode = function(index) { if(index == null) return root.children; else { var nodes = root.children; if(iParser.isIndexDepth(index)) { var keys = iParser.getIndexList(index); return getNodeChildLeaf(keys, nodes[keys.shift()]); } else { return (nodes[index]) ? nodes[index] : null; } } } this.getNodeAll = function(index) { var dataList = [], tmpNodes = (index == null) ? root.children : [ this.getNode(index) ]; for(var i = 0; i < tmpNodes.length; i++) { if(tmpNodes[i]) { dataList.push(tmpNodes[i]); if(tmpNodes[i].children.length > 0) { setNodeChildAll(dataList, tmpNodes[i]); } } } return dataList; } this.getNodeParent = function(index) { // 해당 인덱스의 부모 노드를 가져옴 (단, 해당 인덱스의 노드가 없을 경우) var keys = iParser.getIndexList(index); if(keys.length == 1) { return root; } else if(keys.length == 2) { return this.getNode(keys[0]); } else if(keys.length > 2) { keys.pop(); return this.getNode(keys.join(".")); } } this.getRoot = function() { return root; } } return Base; }); jui.defineUI("ui.tree", [ "util.base", "ui.tree.base" ], function(_, Base) { /** * @class ui.tree * implements Tree Component * @extends core * @alias Tree * @requires util.base * @requires ui.tree.base * */ var UI = function() { var dragIndex = { start: null, end: null, clone: null }, nodeIndex = null, iParser = _.index(); function setNodeStatus(self, nodeList) { for(var i = 0; i < nodeList.length; i++) { var node = nodeList[i]; $(node.element).removeClass("open fold leaf last"); if(node.parent && node.isLeaf()) { $(node.element).addClass("leaf"); } else { if(node.type == "open") { $(node.element).addClass("open"); node.open(); } else { $(node.element).addClass("fold"); node.fold(); } } if(!node.parent) { $(node.element).addClass("root"); } else { if(node.parent.lastChild() == node) { $(node.element).addClass("last"); } } $(node.element).children("i:first-child").remove(); $(node.element).prepend($("<i></i>")); } } function toggleNode(self, index, callback) { if(index == null) { if(self.options.rootHide) { var childs = self.uit.getRoot().children; for(var i = 0; i < childs.length; i++) { callback(childs[i].index); } reloadUI(self, false); } else { callback(index); reloadUI(self, true); } } else { callback(index); reloadUI(self, false); } } function setEventNodes(self, nodeList) { for(var i = 0; i < nodeList.length; i++) { (function(node) { var $elem = $(node.element); self.addEvent($elem.children("i:first-child"), "click", function(e) { if(node.type == "open") { self.fold(node.index, e); } else { self.open(node.index, e); } e.stopPropagation(); }); self.addEvent($elem.children("a,span,div")[0], "click", function(e) { if($elem.hasClass("disabled") || $elem.attr("disabled")) return; self.emit("select", [ node, e ]); // 차후 제거 요망 self.emit("change", [ node, e ]); e.stopPropagation(); }); })(nodeList[i]); } } function resetEventDragNodeData(self, onlyStyle) { if(!self.options.drag) return; var root = self.uit.getRoot(); if(!onlyStyle) { dragIndex.start = null; dragIndex.end = null; dragIndex.clone = null; } $(root.element).find("li.hover").removeClass("hover"); } function setEventDragNodes(self, nodeList) { if(!self.options.drag) return; var root = self.uit.getRoot(); for(var i = 0; i < nodeList.length; i++) { (function(node) { $(node.element).off("mousedown").off("mouseup"); self.addEvent(node.element, "mousedown", function(e) { if(e.target.tagName == "I") return; if(dragIndex.start == null) { if(self.emit("dragstart", [ node, e ]) !== false) { dragIndex.start = node.index; /*/ dragIndex.clone = $(node.element).find(":feq(1)").clone(); dragIndex.clone.css({ position: "absolute", left: e.offsetX + "px", top: e.offsetY + "px", opacity: 0.3 }); $(self.root).append(dragIndex.clone); /**/ } } return false; }); self.addEvent(node.element, "mouseup", function(e) { if(e.target.tagName == "I") return; if(self.options.dragChild !== false) { if(dragIndex.start && dragIndex.start != node.index) { var cNode = node.lastChild(), endIndex = (cNode) ? iParser.getNextIndex(cNode.index) : node.index + ".0"; // 특정 부모 노드에 추가할 경우 if(self.emit("dragend", [ self.get(node.index), e ]) !== false) { self.move(dragIndex.start, endIndex); } } } resetEventDragNodeData(self); return false; }); // 드래그시 마우스 오버 효과 self.addEvent(node.element, "mouseover", function(e) { if(e.target.tagName == "I") return; if(self.options.dragChild !== false) { if(dragIndex.start && dragIndex.start != node.index) { if(self.emit("dragover", [ node, e ]) !== false) { resetEventDragNodeData(self, true); $(node.element).addClass("hover"); } } } return false; }); self.addEvent(root.element, "mouseup", function(e) { if(e.target.tagName == "I") return; if(self.options.dragChild !== false) { if(dragIndex.start) { var endIndex = "" + root.children.length; self.move(dragIndex.start, endIndex); self.emit("dragend", [ self.get(endIndex), e ]); } } resetEventDragNodeData(self); return false; }); })(nodeList[i]); } self.addEvent("body", "mouseup", function(e) { if(dragIndex.start && dragIndex.end) { self.move(dragIndex.start, dragIndex.end); self.emit("dragend", [ self.get(dragIndex.end), e ]); } resetEventDragNodeData(self); return false; }); } function setDragNodes(self) { if(!self.options.drag) return; $(self.root).find(".drag").remove(); var nodeList = self.listAll(); for(var i = 0; i < nodeList.length; i++) { var node = nodeList[i], pos = $(node.element).position(); if(pos.top > 0) { // top이 0이면, hide된 상태로 간주 addDragElement(self, node, pos); } } } function setDragLastNodes(self) { if(!self.options.drag) return; var nodeList = self.listAll(); for(var i = 0; i < nodeList.length; i++) { var node = nodeList[i], pos = $(node.element).position(); if(pos.top > 0 && node.parent) { // top이 0이면, hide된 상태로 간주 if(node.parent.lastChild() == node) { pos.top = pos.top + $(node.element).outerHeight(); addDragElement(self, node, pos, true); } } } } function addDragElement(self, node, pos, isLast) { if(!self.options.drag) return; var index = (isLast) ? iParser.getNextIndex(node.index) : node.index; var $drag = $("<div class='drag'></div>") .attr("data-index", index) .css(pos) .outerWidth($(node.element).outerWidth()); $(self.root).append($drag); self.addEvent($drag, "mouseover", function(e) { if(dragIndex.start) { dragIndex.end = index; $drag.addClass("on"); resetEventDragNodeData(self, true); } }); self.addEvent($drag, "mouseout", function(e) { if(dragIndex.start) { $drag.removeClass("on"); } }); } function reloadUI(self, isRoot) { var nodeList = self.listAll(); setNodeStatus(self, nodeList); setEventNodes(self, nodeList); setEventDragNodes(self, nodeList); setDragNodes(self); // 차후에 개선 setDragLastNodes(self); if(isRoot) { setNodeStatus(self, [ self.uit.getRoot() ]); setEventNodes(self, [ self.uit.getRoot() ]); } } this.init = function() { var opts = this.options; // UITable 객체 생성 this.uit = new Base({ $obj: { tree: $(this.root) }, $tpl: this.tpl }); // 신규 테이블 클래스 사용 // 루트 데이터 처리 if(opts.root) { this.uit.appendNode(opts.root); reloadUI(this, true); } else { throw new Error("JUI_CRITICAL_ERROR: root data is required"); } // 루트 숨기기 if(opts.rootHide) { var root = this.uit.getRoot(); $(root.element).css("padding-left", "0px"); $(root.element).children("*:not(ul)").hide(); } // 루트 접기 if(opts.rootFold) { this.fold(); } } /** * @method update * Changes to the node at a specified index. * * @param {Integer} index * @param {Array} data */ this.update = function(index, data) { var dataList = (arguments.length == 1) ? arguments[0] : arguments[1], index = (arguments.length == 2) ? arguments[0] : null; if(index != null) { this.uit.updateNode(index, dataList); } else { var iParser = _.index(); // 전체 로우 제거 this.uit.removeNodes(); // 트리 로우 추가 for(var i = 0; i < dataList.length; i++) { var pIndex = iParser.getParentIndex(dataList[i].index); if(pIndex == null) { this.uit.appendNode(dataList[i].data); } else { this.uit.appendNode(pIndex, dataList[i].data); } } } reloadUI(this); } /** * @method append * Adds to a child node at a specified index. * * @param {Array/String} param1 index or data * @param {Array} param2 null or data */ this.append = function() { var dataList = (arguments.length == 1) ? arguments[0] : arguments[1], index = (arguments.length == 2) ? arguments[0] : null; dataList = (dataList.length == undefined) ? [ dataList ] : dataList; for(var i = 0; i < dataList.length; i++) { if(index != null) this.uit.appendNode(index, dataList[i]); else this.uit.appendNode(dataList[i]); } reloadUI(this); // 차후에 개선 } /** * @method insert * Adds a node at a specified index. * * @param {String} index * @param {Array} data */ this.insert = function(index, data) { var dataList = (data.length == undefined) ? [ data ] : data; for(var i = 0; i < dataList.length; i++) { this.uit.insertNode(index, dataList[i]); } reloadUI(this); // 차후에 개선 } /** * @method select * Adds a node at a specified index. * * @param {String} index * @return {NodeObject} node */ this.select = function(index) { var node = (index == null) ? this.uit.getRoot() : this.get(index); $(this.root).find("li").removeClass("active"); $(node.element).addClass("active"); nodeIndex = index; return node; } /** * @method unselect * Removes the 'active' class from a selected node and gets an instance of the specified node. */ this.unselect = function() { if(nodeIndex == null) return; var node = this.get(nodeIndex); $(node.element).removeClass("active"); nodeIndex = null; return node; } /** * @method remove * Deletes a node at a specified index. * * @param {String} index */ this.remove = function(index) { this.uit.removeNode(index); reloadUI(this); // 차후에 개선 } /** * @method reset * Deletes all child nodes except for a root. */ this.reset = function() { this.uit.removeNodes(); reloadUI(this); // 차후에 개선 } /** * @method move * Moves a node at a specified index to the target index. * * @param {String} index * @param {String} targetIndex */ this.move = function(index, targetIndex) { this.uit.moveNode(index, targetIndex); reloadUI(this); // 차후에 개선 } /** * @method open * Shows a child node at a specified index. * * @param {String} index */ this.open = function(index, e) { // 로트 제외, 하위 모든 노드 대상 if(index == null && this.options.rootHide) return; var isRoot = (index == null); this.uit.openNode(index); reloadUI(this, isRoot); // 차후에 개선 this.emit("open", [ (isRoot) ? this.uit.getRoot() : this.get(index), e ]); } /** * @method fold * Folds up a child node at a specified index. * * @param {String} index */ this.fold = function(index, e) { if(index == null && this.options.rootHide) return; var isRoot = (index == null); this.uit.foldNode(index); reloadUI(this, isRoot); // 차후에 개선 this.emit("fold", [ (isRoot) ? this.uit.getRoot() : this.get(index), e ]); } /** * @method openAll * Shows all child nodes at a specified index. * * @param {String} index */ this.openAll = function(index) { // 로트 포함, 하위 모든 노드 대상 var self = this, isRoot = (index == null); toggleNode(this, index, function(i) { self.uit.openNodeAll(i); }); this.emit("openall", [ (isRoot) ? this.uit.getRoot() : this.get(index) ]); } /** * @method foldAll * Folds up all child nodes at a specified index. * * @param {String} index */ this.foldAll = function(index) { var self = this, isRoot = (index == null); toggleNode(this, index, function(i) { self.uit.foldNodeAll(i); }); this.emit("foldall", [ (isRoot) ? this.uit.getRoot() : this.get(index) ]); } /** * @method list * Return all nodes of the root. * * @return {Array} nodes */ this.list = function() { return this.uit.getNode(); } /** * @method listAll * Returns all child nodes. * * @return {Array} nodes */ this.listAll = function() { return this.uit.getNodeAll(); } /** * @method listParent * Returns all parent nodes at a specified index. * * @param {String} index * @return {Array} nodes */ this.listParents = function(index) { var node = this.get(index), parents = []; if(node.parent) { addParent(node.parent); } function addParent(node) { if(node.index != null) { parents.push(node); if(node.parent != null) { addParent(node.parent); } } } return parents.reverse(); } /** * @method get * Gets a node at a specified index * * @param {String} index * @return {NodeObject} node */ this.get = function(index) { if(index == null) return null; return this.uit.getNode(index); } /** * @method getAll * Gets all nodes at a specified index including child nodes. * * @param {String} index * @return {Array} nodes */ this.getAll = function(index) { if(index == null) return null; return this.uit.getNodeAll(index); } /** * @method activeIndex * Gets the index of a node that is activated in an active state. * * @return {Integer} index */ this.activeIndex = function() { return nodeIndex; } } UI.setup = function() { return { /** * @cfg {NodeObject} [root=null] * Adds a root node (required). */ root: null, /** * @cfg {Boolean} [rootHide=false] * Hides a root node. */ rootHide: false, /** * @cfg {Boolean} [rootFold=false] * Folds up a root node. */ rootFold: false, /** * @cfg {Boolean} [drag=false] * It is possible to drag the movement of a node. */ drag: false, /** * @cfg {Boolean} [dragChild=true] * It is possible to drag the node movement but the node is not changed to a child node of the target node. */ dragChild: true } } /** * @event select * Event that occurs when a node is selected * * @param {NodeObject) node * @param {EventObject} e The event object */ /** * @event open * Event that occurs when a node is shown * * @param {NodeObject) node * @param {EventObject} e The event object */ /** * @event fold * Event that occurs when a node is hidden * * @param {NodeObject) node * @param {EventObject} e The event object */ /** * @event dragstart * Event that occurs when a node starts to move * * @param {Integer) index Node's index * @param {EventObject} e The event object */ /** * @event dragend * Event that occurs when the movement of a node is completed * * @param {Integer) index Node's index * @param {EventObject} e The event object */ return UI; });