1. 程式人生 > >js基本樹形外掛實現

js基本樹形外掛實現

var Mock = [
        {element:'輪播',id:'2',pid:'1'},
        {element:'輪播小圖',id:'3',pid:'2'},
        {element:'首頁',id:'1',pid:'0'},
        {element:'輪播大圖',id:'4',pid:'2'},
        {element:'產品',id:'5',pid:'0'},
        {element:'蔬菜',id:'6',pid:'5'},
        {element:'有機蔬菜',id:'7',pid:'6'},
        {element:'無機蔬菜',id:'8',pid:'6'},
        {element:'水果',id:'9',pid:'5'},
        {element:'有機水果',id:'10',pid:'9'},
        {element:'有機大水果',id:'11',pid:'10'},
        {element:'有機小水果',id:'12',pid:'10'},
        {element:'無機水果',id:'13',pid:'9'},
    ];

    /**
     * @Author    Toney
     * @Explain   [樹形外掛]
     * @DateTime  2017-01-28
     * @copyright [datacvg]
     * @param     {[type]}      root_id [description]
     */
    var PTree = function(root_id){

        var _this = this;

        //存放根據pid找到的節點
        this.stack = [];
        //原始json資料
        this.json = null;
        //存放json轉化後的資料模型
        this.nodes = [];

        this.root = document.getElementById(root_id);
        //欄位
        _this.keys = [];

        //快取dom
        this.dom = {};


        //獲取最大json  id
        this.getMaxId = function(){
            var json = this.json.concat();
            json.sort(function(a,b){
                return b.id - a.id;
            });
            return json[0]['id'];
        }
        /**
         * @Author    Toney
         * @Explain   [獲取包含自身的子元素]
         * @DateTime  2017-01-29
         * @copyright [datacvg]
         * @param     {[type]}      parent [description]
         * @return    {[type]}             [description]
         */
        this.outChildrenNode = function(parent){
            var result = [parent];
            var childs = parent.getElementsByTagName('i');
            for(var i = 0; i < childs.length; i++){
                var curNode = childs[i];
                if(curNode){
                    result.push(curNode);
                }
            }
            return result;
        }   
        /**
         * @Author    Toney
         * @Explain   [按照json資料的key初始化一條空的json資料]
         * @DateTime  2017-01-29
         * @copyright [datacvg]
         * @return    {[type]}      [description]
         */
        this.initItem = function(){
            var o = {};
            while(_this.keys.length){
                o[_this.keys.pop()] = null;
            }
            return o;
        }
        /**
         * @Author    Toney
         * @Explain   [檢視轉化為model]
         * @DateTime  2017-01-29
         * @copyright [datacvg]
         * @param     {[type]}      nodes [description]
         * @return    {[type]}            [description]
         */
        this.viewToModel = function(nodes){
            var json = [];
            var temp = this.initItem();
            for(var i = 0; i < nodes.length; i++){
                var node = nodes[i];
                var item = (function(){
                    var result = {};
                    for(key in temp){
                        result[key] = temp[key];
                    }
                    return result;
                })();
                for(var key in item){
                    item[key] = node.getAttribute(key);
                }     
                json.push(item);    
            }
            return json;
        }

        /**
         * @Author    Toney
         * @Explain   [更新model]
         * @DateTime  2017-01-28
         * @copyright [datacvg]
         * @param     {[string]}      id    [id主鍵]
         * @param     {[string]}      field [待更新的欄位]
         * @param     {[string]}      val   [修改後的值]
         */
        this.updateModel = function(id,field,val){
            for(var i = 0 ; i < this.json.length ; i++){
                var item = this.json[i];
                if(item['id'] == id){
                    this.json[i][field] = val;
                }
            }
            console.log(this.json);
        }
        this.addModel = function(item){
            this.json.push(item);
        }   
        this.removeModel = function(id){
            for(var i = 0; i < _this.json.length;i++){
                var item = _this.json[i];
                if(item['id'] == id){
                    _this.json.splice(i,1);
                }
            }
        }
        //重新整理檢視
        this.reRender = function(){
            this.init();
            this.removeAllNode();
            this.initTree(this.json);
        }
        //初始化所有初始變數
        this.init = function(){
            //存放根據pid找到的節點
            this.stack = [];
         
            //存放json轉化後的資料模型
            this.nodes = [];

            //快取dom
            this.dom = {};
        }
        //清空所有節點
        this.removeAllNode = function(){
            var childs=this.root.childNodes;    
            for(var i=childs.length-1;i>=0;i--){    
                this.root.removeChild(childs.item(i));
            }
        }
        //右鍵選單
        this.panelMenu = {
            dom:{
                panel:null,
            },
            pos:{
                x:null,
                y:null
            },
            list:[
                {name:'新增',cmd:'add'},
                {name:'刪除',cmd:'remove'},
                {name:'重新命名',cmd:'rename'},
                {name:'複製',cmd:'copy'},
                {name:'貼上',cmd:'paste'}
                ],
            strategy:{
                dom:{
                    target:null
                },
                json:{
                    copy:null
                },
                add:function(item){
                    console.log('this is add');
                    //初始化一條資料
                    var _item = item || _this.initItem();
                    _item.id = _item.id || (parseInt(_this.getMaxId()) + 1).toString();
                    _item.pid = _item.pid || this.dom.target.getAttribute('id');
                    _item.element = _item.element || '新的節點';
                    _this.addModel(_item);
                    _this.reRender();
                    this.dom.target = _this.findNode(_item.id);
                    !item?this.rename():null;

                },
                remove:function(){
                    console.log('this is remove');
                    _this.removeModel(this.dom.target.getAttribute('id'));
                    _this.reRender();
                },
                rename:function(){
                    console.log('this is rename');
                    var input = {
                        dom:{
                            input:null
                        },
                        initInput:function(){
                            var input = document.createElement('input');
                            input.setAttribute('type','text');
                            input.setAttribute('size','10');
                            input.setAttribute('style','position: absolute;left: 0;top:0;height: 12px;');
                            this.dom.input = this.dom.input || input;
                            this.listen({
                                onBlur:function(me,e){
                                    var target = _this.panelMenu.strategy.dom.target;
                                    var rename = me.value;
                                    if(rename){
                                        _this.updateModel(target.getAttribute('id'),'element',rename);
                                        _this.reRender();
                                    }
                                    input.remove();
                                    
                                    console.log('after rename',_this.json);
                                }
                            });
                        },
                        listen:function(config){
                            this.dom.input.addEventListener('blur',function(e){
                                e?e.stopPropagation():window.event.cancelBubble = true;
                                config.onBlur(this,e);
                            });
                            this.dom.input.addEventListener('click',function(e){
                                e?e.stopPropagation():window.event.cancelBubble = true;
                            });
                        },
                        appendTo:function(parent){
                            _this.add(this.dom.input,parent);
                        },
                        remove:function(){
                            if(this.dom.input && this.dom.input.parentNode){
                                this.dom.input.parentNode.removeChild(this.dom.input);
                            }
                        },
                        focus:function(){
                            this.dom.input.focus();
                        }  
                    };
                    input.initInput();
                    input.appendTo(this.dom.target);
                    input.focus();
                },
                copy:function(){
                    console.log('this is copy');
                    this.dom.copy = this.dom.copy || this.dom.target;
                    var outChildren = _this.outChildrenNode(this.dom.target);
                    var model = _this.viewToModel(outChildren);
                    this.json.copy = model;
                },
                paste:function(){
                    console.log('this is paste');
                    var origin_id = this.json.copy[0]['id'];
                    var max_id = parseInt(_this.getMaxId()) + 1;
                    var minus = max_id - parseInt(origin_id);
                    for(var i = 0; i < this.json.copy.length; i++){
                        this.json.copy[i]['id'] = parseInt(this.json.copy[i]['id']) + minus;
                        this.json.copy[i]['pid'] = parseInt(this.json.copy[i]['pid']) + minus;
                    }
                    this.json.copy[0]['pid'] = this.dom.target.getAttribute('id');
                    _this.json = _this.json.concat(this.json.copy);
                    _this.reRender();
                }
            },
            initPanel:function(){
                if(!this.dom.panel){
                    var __this = this;
                    this.dom.panel = (function(){
                        var panel = document.createElement('div');
                        var style = "background:black;opacity:0.9;color:white;cursor:pointer;text-align:center;background:gray;width:200px;height:auto;position:fixed;left:10px;top:10px";
                        panel.setAttribute('style',style);
                        //選單項
                        (function(){
                            for(var i = 0; i < __this.list.length;i++){
                                var item = __this.list[i];
                                var p = document.createElement('p');
                                p.innerHTML = item['name'];
                                panel.appendChild(p);
                                (function(p,item){
                                    p.addEventListener('click',function(e){
                                        __this.strategy[item['cmd']]();
                                    });
                                })(p,item);
                            }
                        })();
                        return panel;
                    })();
                }
            },   
            open:function(e){
                //初始化一個菜單面板
                this.initPanel();
                //設定初始位置為游標位置
                this.dom.panel.style.left = e.clientX+'px';
                this.dom.panel.style.top = e.clientY+'px';
            },
            close:function(){

                if(this.dom.panel && this.dom.panel.parentNode){
                    this.dom.panel.parentNode.removeChild(this.dom.panel);
                }
            }
        };
        /**
         * @Author    Toney
         * @Explain   [根據一項json資料建立一個節點,key->value設定為節點的屬性]
         * @DateTime  2017-01-24
         * @copyright [datacvg]
         * @param     {[type]}      item [description]
         * @return    {[type]}           [description]
         */
        this.createNode = function(item){
            var node = document.createElement('i');
            node.innerHTML = item['element'];
            for(var key in item){
                node.setAttribute(key,item[key]);
            }
            //其他屬性
            node.setAttribute('open','yes');
            node.setAttribute('class','icon-minus');
            return node;
        }
        /**
         * @Author    Toney
         * @Explain   [description]
         * @DateTime  2017-01-24
         * @copyright [datacvg]
         * @param     {[type]}      child  [description]
         * @param     {[type]}      parent [description]
         */
        this.add = function(child,parent){
            parent.appendChild(child);
        }
        /**
         * @Author    Toney
         * @Explain   [入棧根據pid找到的節點]
         * @DateTime  2017-01-24
         * @copyright [datacvg]
         * @param     {[type]}      pid [description]
         * @return    {[type]}          [description]
         */
        this.pushFoundChild = function(pid){
            for(var i = 0; i < this.nodes.length; i++){
                var node = this.nodes[i];
                if(node.getAttribute('pid') == pid){
                    this.stack.push(node);
                }
            }
            return this.stack.length;
        }
        /**
         * @Author    Toney
         * @Explain   [獲取當前節點的父節點]
         * @DateTime  2017-01-24
         * @copyright [datacvg]
         * @param     {[type]}      pid [description]
         * @return    {[type]}          [description]
         */
        this.findParent = function(pid){
            if(pid == 0){
                return this.root;
            }
            for(var i = 0; i < this.nodes.length; i++){
                var node = this.nodes[i];
                if(node.getAttribute('id') == pid){
                    return node;
                }
            }
            return null;
        }
        /**
         * @Author    Toney
         * @Explain   [獲取某個節點]
         * @DateTime  2017-01-29
         * @copyright [datacvg]
         * @param     {[type]}      id [description]
         * @return    {[type]}         [description]
         */
        this.findNode = function(id){
            for(var i = 0; i < this.nodes.length; i++){
                var node = this.nodes[i];
                if(node.getAttribute('id') == id){
                    return node;
                }
            }
            return null;
        }
        /**
         * @Author    Toney
         * @Explain   [把json資料轉化為一個個節點]
         * @DateTime  2017-01-24
         * @copyright [datacvg]
         * @param     {[type]}      json [description]
         * @return    {[type]}           [description]
         */
        this.initNodes = function(json){
            if(json.length > 0){
                for(var i = 0 ; i < json.length ; i++){
                    var item = json[i];     
                    var node = this.createNode(item);  
                    this.nodes.push(node);
                }
            }else{
                throw '傳入的Json串不合法';
            }
        };
        /**
         * @Author    Toney
         * @Explain   [監聽所有I節點]
         * @DateTime  2017-01-24
         * @copyright [datacvg]
         * @param     {[type]}      config [description]
         * @return    {[type]}             [description]
         */
        this.listenI = function(config){
     
            var cfg = {
                onclick:function(){},
                onMouseDown:function(){}
            };
            //深度繼承
            for(var key in config){
                cfg[key] = config[key];
            }
            this.dom.i = this.dom.i || this.root.getElementsByTagName('i');
            for(var i = 0; i < this.dom.i.length; i++){
                var item = this.dom.i[i];
                (function(item){
                    item.addEventListener('click',function(e){
                        e?e.stopPropagation():window.event.cancelBubble = true;
                        cfg.onclick(this,e);
                    });
                    item.addEventListener('mousedown',function(e){
                        e?e.stopPropagation():window.event.cancelBubble = true;
                        cfg.onMouseDown(this,e);
                    });
                })(item);
            }
       
        }

        this.openMenu = function(e){
            this.panelMenu.open(e);
            this.root.appendChild(this.panelMenu.dom.panel);
        },
        /**
         * @Author    Toney
         * @Explain   [判斷某個節點是否為展開狀態]
         * @DateTime  2017-01-24
         * @copyright [datacvg]
         * @param     {[type]}      node [description]
         * @return    {Boolean}          [description]
         */
        this.isOpen = function(node){
            if(node.getAttribute('open') == 'yes'){
                return true;
            }else{
                return false;
            }
        }
        /**
         * @Author    Toney
         * @Explain   [展開一個節點]
         * @DateTime  2017-01-24
         * @copyright [datacvg]
         * @return    {[type]}      [description]
         */
        this.openNode = function(node){
            //設定狀態為展開
            node.setAttribute('open','yes');
            //設定圖示為減號
            node.setAttribute('class','icon-minus');
            var children = node.getElementsByTagName('i');            
            for(var i = 0; i < children.length;i++){
                var item = children[i];
                item.style.display = 'block';
            }   
        }
        /**
         * @Author    Toney
         * @Explain   [摺疊一個節點]
         * @DateTime  2017-01-24
         * @copyright [datacvg]
         * @return    {[type]}      [description]
         */
        this.closeNode = function(node){
            //設定狀態為關閉
            node.setAttribute('open','no');
            //設定圖示為加號
            node.setAttribute('class','icon-add');
            var children = node.getElementsByTagName('i');
            for(var i = 0; i < children.length;i++){
                var item = children[i];
                item.style.display = 'none';
            }   
        }
        this.listenRoot = function(){
            this.root.addEventListener('click',function(e){
                var target = e.target;
                if(target != 'p'){
                    _this.panelMenu.close();
                }

            });
        }
        this.initListen = function(){
            this.listenI({
                onclick:function(me){
                    if(_this.isOpen(me)){
                        _this.closeNode(me);
                    }else{
                        _this.openNode(me);
                    }
                },
                onMouseDown:function(me,e){ 
                    _this.panelMenu.strategy.dom.target = me;
                    switch(e.button){
                        case 2:
                            _this.openMenu(e);
                            break;
                    }
                }
            });
            this.listenRoot();
        }
        this.initKeys = function(json){
            for(var key in json[0]){
                this.keys.push(key);
            }
        }
        this.testMock = function(num){
            var start = new Date().getTime();//起始時間
 
         
            for(var i = 0; i < num; i++){
                var item = {
                    pid:0,
                    id:null,
                    element:'dskljfkdsl'
                };
                this.json.push(item);
            }
            this.reRender();
            var end = new Date().getTime();//接受時間
             
            return (end - start)+"ms";//返回函式執行需要時間
        }
        //禁止瀏覽器預設滑鼠右鍵
        this.forbidBrowserMouseRightKey = function(){
            window.oncontextmenu = function(){
                window.event.returnValue=false;
                return false;
            }
        }
    };
    /**
     * @Author    Toney
     * @Explain   [把節點拼成樹]
     * @DateTime  2017-01-24
     * @copyright [datacvg]
     * @return    {[type]}      [description]
     */
    PTree.prototype.initTree = function(json){
        this.json = json;
        this.initNodes(json);
        this.initKeys(json);
        var pid = 0,
        curNode = null,
        parent = null;
        console.log(this.nodes);
        while(this.pushFoundChild(pid)){
            curNode = this.stack.pop();
            parent = this.findParent(curNode.getAttribute('pid'));     
            this.add(curNode,parent);
            pid = curNode.getAttribute('id');
        }
        this.initListen();
        this.forbidBrowserMouseRightKey();
    }

    var pTree = new PTree('panel-tree');
    pTree.initTree(Mock);
.p-tree {
	width: 200px;
	height: 300px;
	background: #ddd;
	resize: both;
	overflow: auto;
	padding: 20px;
}

.p-tree i {
	display: block;
	font-style: normal;
	cursor: pointer;
	color: black;
	position: relative;
	margin-left: 20px;
}

.p-tree i.selected {
	color: red;
}

.p-tree i.icon-add:before {
	content: '+';
}

.p-tree i.icon-minus:before {
	content: '-';
}