树形菜单

(function() {

    var tree = {

        init: function() {
            /* Configurations */
            this.jsonFilePath     = 'data/tree-data.json';
            this.root_icon        = "glyphicon-folder-open";
            this.node_minus_icon  = "glyphicon-minus";
            this.node_plus_icon   = "glyphicon-plus";
            this.node_leaf_icon   = "glyphicon-leaf";
            this.jsonModelDefault = {
                name: "",
                parent: "",
                value: 10,
                color: "",
                type: ""
            };

            /* Global variables */
            this.newNodeDefaultValue = '新增節點';
            this.map = {};
            this.targetNode = null;
            this.html = "";
        },


        getIcon: function(node) {
            var icon = this.node_minus_icon;
            if (node.root)
                icon = this.root_icon;
            else if (!node.children)
                icon = this.node_leaf_icon;
            return icon;
        },
    };

    var dataOp = {

        init: function(data) {
            var rootNode = this.import.buildTree(data);
            this.import.buildHTML(rootNode);
        },

        import: {

            /*
             * Recursively tranverse the children array of each node and turn them into HTML
             */
            buildNode: function _buildNode(node, dept) {

                tree.html += ('<span class="node node-' + dept + '" data-toggle="context"><i class="glyphicon ' + tree.getIcon(node) + '"></i>' + node.name + '</span> ');

                dept = dept + 1;
                tree.html += '<ul data-dept=' + dept + ' >';

                if (node.children) {
                    $.each(node.children, function(index, child) {
                        tree.html += '<li>';
                        _buildNode(tree.map[child], dept);
                        tree.html += '</li>';
                    });
                }

                tree.html += '</ul>';

            },

            buildTree: function(data) {

                var rootNode = "";

                // Transform the input data into a map, whose key is the name of the node 
                $.each(data, function(key, node) {
                    tree.map[node.name] = node;
                });

                // Build `children` array for each node
                $.each(data, function(index, node) {

                    var parentNode = tree.map[node.parent];

                    if (parentNode) {

                        // If parentNode still doesn't have children, initialize it as empty array and push in a new one.
                        (parentNode.children || (parentNode.children = [])).push(node.name);

                    } else {
                        // If a node does not have parent, it is the root.
                        node.root = true;
                        rootNode = node.name;
                    }
                });


                return rootNode;
            },

            buildHTML: function(rootNode) {

                tree.html += "<ul><li>";
                this.buildNode(tree.map[rootNode], 0);
                tree.html += "</li></ul>";

                // Apply the collected HTML into our page
                $(".tree").append(tree.html);

            },


            loadJSON: function (jsonFilePath) {
                
                $.getJSON(jsonFilePath, function(data) {

                    // Before transforming our data, we shall initialize the necessary utililies
                    dataOp.init(data);
                    plugin.init();

                }).fail(function() {
                    console.log( jsonFilePath + " is not found! " );
                });
            }
        },

        export: {

            /*
             * Add new node into our map
             */
            add: function(you, yourParent) {
                var newNode = {};

                // Assign default value to the new node
                $.extend(newNode, tree.jsonModelDefault);

                newNode.name = you.text();
                newNode.parent = yourParent.text();

                // Add it into our map ( which will be exported )
                tree.map[you.text()] = newNode;
            },

            /*
             * remove node from our map
             */
            remove: function _remove (you) {

                if( you && tree.map[ you ].children ){
                    // Recursively remove your children
                    $.each(tree.map[ you ].children, function(index, child) {
                            _remove(child);
                    });
                }
                delete tree.map[you];
            },

            /*
             * Transform the values of data map into plain array 
             */
            transformIntoArray: function () {
                return $.map(tree.map, function(value, key) {
                    // We don't we children in our JSON
                    delete value.children;
                    return [value];
                });
            },

            /*
             * Export the JSON and throw it into the export textarea
             */
            prepareJSON: function () {
                var that = this;
                $( "#export" ).on( "click", function() {
                    $('#textarea-JSON-export').val( JSON.stringify( that.transformIntoArray() ) );
                });
            }
        }

    };


    var plugin = {

        init: function() {
            this.collapsible();
            this.contextMenu();
            this.modal.process();
        },

        /*
         * A node is collapsible if it is not a leaf
         * When user click on a collapsible node, we toggle opening/closing the node
         */
        collapsible: function() {

            nodeOp.makeCollapsible($('.tree li').has('li'));
            $('.tree').on('click', ' li.collapsible > span', function(e) {
                var children = $(this).parent('li.collapsible').find(' > ul > li');
                if (children.is(":visible")) {
                    children.hide('fast');
                    $(this).attr('title', '開啟').find(' > i').addClass(tree.node_plus_icon).removeClass(tree.node_minus_icon);
                } else {
                    children.show('fast');
                    $(this).attr('title', '關閉').find(' > i').addClass(tree.node_minus_icon).removeClass(tree.node_plus_icon);
                }
                e.stopPropagation();
            });
        },

        /*
         * context menu is activated when user right click on a node
         */
        contextMenu: function() {

            // When user activate the contextmenu, we update the targetNode here
            // Most of the operations apply changes to targetNode
            $('.node').on('contextmenu', function() {
                /* Update targetNode ( the one on which user is right clicking ) */
                tree.targetNode = this;
            });

            var $modal = this.modal;

            $('.node').contextmenu({

                parent: this,

                target: '#context-menu',

                
                /*
                 * This function is activated when user click an option on the context menu.
                 */
                onItem: function(context, e) {

                    var operation = e.target.id;

                    if (operation == "delete"){

                        // remove the node from tree
                        nodeOp.remove($(tree.targetNode).parent());

                        // remove the node from our data ( which will be exported )
                        dataOp.export.remove(nodeOp.getContent().text())

                    }
                    else {

                        if (operation == "add") {

                            nodeOp.add();
                            $modal.build('新增', '知識節點......');

                        } else {

                            // Cache the content so that if user hit 'Cancel', we can recover the original content
                            nodeOp.contentCache = $(tree.targetNode).text();
                            $modal.build('修改', $(tree.targetNode).text());

                        }

                        // Show modal so that user can edit a node
                        $('#modal').modal('show');
                    }

                }
            })
        },

        modal: {

            build: function(title, defaultValue) {
                $('#modal-title').html(title);
                $('#modal-input')
                    .val(defaultValue)
                    .on('click', function() {
                        $(this).select();
                    });
            },

            process: function() {
                /* Update the value of new field constantly */
                $('#modal-input').keyup(function() {
                    var input = $('#modal-input').val();
                    nodeOp.getContent().replaceWith(input);
                });

                $('#modal-submit').click(function() {
                    // If user does not modify the default value when submitting, purge the newly created node.
                    if (!$(tree.targetNode).text() || $(tree.targetNode).text() == this.newNodeDefaultValue) {
                        nodeOp.remove($(tree.targetNode).closest('li'));
                    } else {
                        dataOp.export.add(nodeOp.getContent(), nodeOp.getParentContent())
                    }

                });

                $('#modal-cancel').click(function() {
                    // If there is content cache, the user is giving up an edit.  
                    // So we recover the content of node he is editing.
                    if( nodeOp.contentCache ){
                        nodeOp.getContent().replaceWith(nodeOp.contentCache);
                        nodeOp.contentCache = "";
                    }
                    // The user is giving up creating a new node,
                    // so we simply remove the newly created node.
                    else{
                        nodeOp.remove($(tree.targetNode).closest('li'));
                    }
                });
            }
        },
    };

    var nodeOp = {

        init: function() {
            this.newNode = "<li><span class='node' data-toggle='context' >" + tree.newNodeDefaultValue + "</span><ul></ul></li> ";
            this.contentCache = "";
        },

        add: function() {

            // If parent was a leaf, turn it into a normal node
            this.removeLeafIcon();

            // Make the parent collapsible
            this.makeCollapsible($(tree.targetNode).parent('li'));

            // Get current dept
            var dept = $(tree.targetNode).siblings('ul').attr('data-dept');

            // Append to ul sibling to the target span
            tree.targetNode = $(this.newNode).appendTo($(tree.targetNode).siblings('ul'));

            // Set next dept
            $(tree.targetNode).children('ul').attr('data-dept', parseInt(dept) + 1);

            // Add icon
            tree.targetNode = tree.targetNode.children('.node')
                .addClass('node-' + dept)
                .prepend('<i class="glyphicon ' + tree.getIcon({}) + '"></i>'); // Prepend leaf glyph

            plugin.contextMenu();
        },

        remove: function($node) {
            // Remove node with animation
            $node.hide('slow', function() {
                $node.remove();
            });
            this.makeUncollapsible($node);
        },

        getContent: function() {
            return $(tree.targetNode).contents().last();
        },

        getParentContent: function() {
            return $(tree.targetNode).closest('ul').siblings('.node').contents().last();
        },

        removeLeafIcon: function() {
            $(tree.targetNode).children("." + tree.node_leaf_icon).removeClass(tree.node_leaf_icon).addClass(tree.node_minus_icon);
        },

        makeCollapsible: function($node) {
            $node.addClass('collapsible').find(' > span').attr('title', '關閉');
        },

        makeUncollapsible: function($node) {
            $node.has('li').removeClass('collapsible').find(' > span').attr('title', '葉節點');
        }
    };

    var UI = {

        export: function () {
            /*
             * Export JSON
             */
            $('#export').on('click', function() {
                dataOp.export.prepareJSON();
                $('#modal-JSON-export').modal('show');
            });
        }
    };

    // Executed on document ready
    $(function() {
        
        /* Initializing global configurations */
        tree.init();

        /* Initializing node operations */
        nodeOp.init();

        /* Listening for export command */
        UI.export();

        /* Load default JSON file */
        dataOp.import.loadJSON(tree.jsonFilePath);

    });

}());


你可能感兴趣的:(树形菜单)