Advertisement
Udgin

bootstrap-listTree

Apr 20th, 2013
656
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*
  2.  * Copyright 2012 Clay Walker
  3.  * Licensed under GPLv2 ONLY
  4.  */
  5. (function ($) {
  6.     var template = '\
  7.        <ul>\
  8.            <% _.each(context, function(parent, index) { %>\
  9.            <li>\
  10.                <span><input value="<%= parent.key %>" id="<%= parent.id%>"/> <a class="icon-plus"></a> <a class="icon-minus"></a> <% if (index > 0) { %><a class="icon-arrow-up"></a> <% } if (index < context.length-1) {%><a class="icon-arrow-down"></a><%}%><i class="icon-chevron-up icon-black"></i></span>\
  11.                <% if (options.startCollapsed && !options.visibleParents[parent.id]) { %>\
  12.                <ul style="display: none;">\
  13.                <% } else { %>\
  14.                <ul >\
  15.                <% } %>\
  16.                    <% _.each(parent.values, function(child, index) { %>\
  17.                    <li>\
  18.                        <span><input value="<%= child.key %>" id="<%= child.id %>"/> <a class="icon-minus"></a> <% if (index > 0) { %> <a class="icon-arrow-up"></a> <% } if (index < parent.values.length-1) {%><a class="icon-arrow-down"></a><%}%></span>\
  19.                    </li>\
  20.                    <% }); %>\
  21.                </ul>\
  22.            </li>\
  23.            <% }); %>\
  24.        </ul>';
  25.  
  26.     var templateParent = '<li>\
  27.                <span><input value="<%= key %>" id="<%= id %>"/> <a class="icon-plus"></a> <a class="icon-minus"></a><i class="icon-chevron-up icon-black"></i></span>\
  28.                <ul style="display: none;">\
  29.            </li>';
  30.  
  31.     var templateChild = '<li>\
  32.                <span><input value="<%= key %>" id="<%= id %>"/> <a class="icon-minus"></a></span>\
  33.            </li>';
  34.  
  35.     /** Check all child checkboxes.
  36.      * @param jQElement The parent <li>.
  37.      */
  38.  
  39.     function _selectAllChildren(jQElement) {
  40.         jQElement.find('ul > li > span > input[type="checkbox"]')
  41.             .each(function () {
  42.                 $(this).prop('checked', true);
  43.             });
  44.     }
  45.  
  46.     /** Uncheck all child checkboxes.
  47.      * @param jQElement The parent <li>.
  48.      */
  49.  
  50.     function _deselectAllChildren(jQElement) {
  51.         jQElement.find('ul > li > span > input[type="checkbox"]')
  52.             .each(function () {
  53.                 $(this).prop('checked', false);
  54.             });
  55.     }
  56.  
  57.     /** Toggle all checkboxes.
  58.      * @param[in] jQElement The root <ul> of the list.
  59.      */
  60.  
  61.     function _toggleAllChildren(jQElement) {
  62.         if (jQElement.children('span').children('input[type="checkbox"]').prop('checked')) {
  63.             _selectAllChildren(jQElement);
  64.         } else {
  65.             _deselectAllChildren(jQElement);
  66.         }
  67.     }
  68.  
  69.     /** Toggle the collapse icon based on the current state.
  70.      * @param[in] jQElement The <li> of the header to toggle.
  71.      */
  72.  
  73.     function _toggleIcon(jQElement) {
  74.         // Change the icon.
  75.         if (jQElement.children('ul').is(':visible')) {
  76.             // The user wants to collapse the child list.
  77.             jQElement.children('span').children('i')
  78.                 .removeClass('icon-chevron-down')
  79.                 .addClass('icon-chevron-up');
  80.         } else {
  81.             // The user wants to expand the child list.
  82.             jQElement.children('span').children('i')
  83.                 .removeClass('icon-chevron-up')
  84.                 .addClass('icon-chevron-down');
  85.         }
  86.     }
  87.  
  88.     /** Make sure there isn't any bogus default selections.
  89.      * @param[in] selected The default selection object.
  90.      * @return The filtered selection object.
  91.      */
  92.  
  93.     function _validateDefaultSelectionValues(selected) {
  94.         return _.filter(selected, function (elem) {
  95.             return (!_.isEmpty(elem.values) && !_.isUndefined(elem.values));
  96.         });
  97.     }
  98.  
  99.     /** If a parent has at least one child node selected, check the parent.
  100.      *  Conversely, if a parent has no child nodes selected, uncheck the parent.
  101.      * @param[in] jQElement The parent <li>.
  102.      */
  103.  
  104.     function _handleChildParentRelationship(jQElement) {
  105.         // If the selected node is a child:
  106.         if (_.isEmpty(_.toArray(jQElement.children('ul')))) {
  107.             var childrenStatuses = _.uniq(
  108.                 _.map(jQElement.parent().find('input[type="checkbox"]'), function (elem) {
  109.                     return $(elem).prop('checked');
  110.                 })
  111.             );
  112.  
  113.             // Check to see if any children are checked.
  114.             if (_.indexOf(childrenStatuses, true) !== -1) {
  115.                 // Check the parent node.
  116.                 jQElement.parent().parent().children('span').children('input[type="checkbox"]').prop('checked', true);
  117.             } else {
  118.                 // Uncheck the parent node.
  119.                 jQElement.parent().parent().children('span').children('input[type="checkbox"]').prop('checked', false);
  120.             }
  121.         }
  122.     }
  123.  
  124.     /** Updates the internal object of selected nodes.
  125.      */
  126.  
  127.     function _updateSelectedObject() {
  128.         var data = $('.listTree').data('listTree');
  129.  
  130.         // Filter the context to the selected parents.
  131.         var selected = _.filter($.extend(true, {}, data.context), function (parent) {
  132.             return $('.listTree > ul > li > span > input[value="' + parent.key + '"]').prop('checked')
  133.         });
  134.  
  135.         // For each parent in the working context...
  136.         _.each(selected, function (parent) {
  137.  
  138.             // Filter the children to the selected children.
  139.             parent.values = _.filter(parent.values, function (child) {
  140.                 return $('.listTree > ul > li > ul > li > span > input[value="' + child.key + '"]').prop('checked');
  141.             });
  142.         });
  143.  
  144.         // Update the plugin's selected object.
  145.         $('.listTree').data('listTree', {
  146.             "target": data.target,
  147.             "context": data.context,
  148.             "options": data.options,
  149.             "selected": selected
  150.         });
  151.     }
  152.  
  153.     function _updateStateOfParent(options, node, parentId) {
  154.         if (node.children('span').children('i').hasClass('icon-chevron-up')) {
  155.             options.visibleParents[parentId] = false;
  156.         } else {
  157.             options.visibleParents[parentId] = true;
  158.         }
  159.     }
  160.  
  161.     function _findIndexInMassiveById(massive, id) {
  162.         var ind = -1;
  163.         $.each(massive, function (i) {
  164.             if (massive[i].id == id) {
  165.                 ind = i;
  166.             }
  167.         });
  168.         return ind;
  169.     }
  170.  
  171.     function _bindDataToContext(nodeListTree, context) {
  172.         $.each(nodeListTree.children('ul').children('li'), function (i) {
  173.             var parentInput = $(this).children('span').find('input');
  174.             var parentInContext = _.find(context, function (item) { return item.id == parentInput.attr('id'); });
  175.             parentInContext.key = parentInput.val();
  176.             $.each($(this).children('ul').children('li'), function() {
  177.                 var childrenInput = $(this).children('span').find('input');
  178.                 var childrenInContext = _.find(parentInContext.values, function (item) { return item.id == childrenInput.attr('id'); });
  179.                 childrenInContext.key = childrenInput.val();
  180.             });
  181.         });
  182.     }
  183.  
  184.  
  185.     var methods = {
  186.         init: function (context, options) {
  187.             // Default options
  188.             var defaults = {
  189.                 "startCollapsed": false,
  190.                 "selected": context
  191.             };
  192.             options = $.extend(defaults, options);
  193.  
  194.             // Validate the user entered default selections.
  195.             options.selected = _validateDefaultSelectionValues(options.selected);
  196.  
  197.             options.visibleParents = {};
  198.  
  199.             return this.each(function () {
  200.                 var $this = $(this),
  201.                     data = $this.data('listTree');
  202.  
  203.                 // If the plugin hasn't been initialized yet...
  204.                 if (!data) {
  205.  
  206.                     $(this).data('listTree', {
  207.                         "target": $this,
  208.                         "context": context,
  209.                         "options": options
  210.                     });
  211.  
  212.                     // Register checkbox handlers.
  213.                     $(document).on('change', '.listTree input[type="checkbox"]', function (e) {
  214.                         var node = $(e.target).parent().parent();
  215.  
  216.                         // Toggle all children.
  217.                         _toggleAllChildren(node);
  218.  
  219.                         // Handle parent checkbox if all children are (un)checked.
  220.                         _handleChildParentRelationship(node);
  221.  
  222.                         // Filter context to selection and store in data.selected.
  223.                         _updateSelectedObject(node);
  224.                     })
  225.  
  226.                     // Register collapse handlers on parents.
  227.                     .on('click', '.listTree > ul > li > span', function (e) {
  228.                         var node = $(e.target).parent();
  229.  
  230.                         // Change the icon.
  231.                         _toggleIcon(node);
  232.  
  233.                         // Toggle the child list.
  234.                         node.children('ul').slideToggle('fast');
  235.  
  236.                         var id = $(e.target).closest('span').find('input').attr('id');
  237.  
  238.                         _updateStateOfParent(options, node, id);
  239.  
  240.                         e.stopImmediatePropagation();
  241.                     })
  242.  
  243.                     .on('click', '.listTree > ul > li > span > a.icon-plus', function (e) {
  244.                         var id = $(e.target).closest('span').find('input').attr('id');
  245.                         var parent = _.find(context, function (item) { return item.id == id; });
  246.  
  247.                         var newChildId = 0;
  248.                         while (true) {
  249.                             var isExisting = _.find(parent.values, function (item) { return item.id == "nc" + newChildId; });
  250.                             if (isExisting) {
  251.                                 newChildId++;
  252.                             } else {
  253.                                 break;
  254.                             }
  255.                         }
  256.  
  257.                         parent.values.push({ key: "", id: "nc" + newChildId });
  258.  
  259.                         _bindDataToContext($this, context);
  260.                         options.visibleParents[id] = true;
  261.                        
  262.                         $this.html(_.template(template, { "context": context, "options": options }));
  263.                         e.stopImmediatePropagation();
  264.                     })
  265.      
  266.                     .on('click', '.listTree > ul > li > ul > li > span > a.icon-minus', function (e) {
  267.                         var id = $(e.target).closest('span').find('input').attr('id');
  268.                         var parentId = $(e.target).closest('ul').prev('span').find('input').attr('id');
  269.                         var parent = _.find(context, function (item) { return item.id == parentId; });
  270.                         var ind = _findIndexInMassiveById(parent.values, id);
  271.  
  272.                         if (ind > -1) {
  273.                             parent.values.splice(ind, 1);
  274.                             $(e.target).closest('li').remove();
  275.                             _bindDataToContext($this, context);
  276.                             $this.html(_.template(template, { "context": context, "options": options }));
  277.                         }
  278.                         e.stopImmediatePropagation();
  279.                     })
  280.  
  281.                     .on('click', '.listTree > ul > li > span > a.icon-minus', function (e) {
  282.                         var id = $(e.target).closest('span').find('input').attr('id');
  283.                         var ind = _findIndexInMassiveById(context, id);
  284.  
  285.                         if (ind > -1) {
  286.                             context.splice(ind, 1);
  287.                             $(e.target).closest('li').remove();
  288.  
  289.  
  290.                             _bindDataToContext($this, context);
  291.                             $this.html(_.template(template, { "context": context, "options": options }));
  292.                         }
  293.                         e.stopImmediatePropagation();
  294.                     })
  295.  
  296.                     .on('click', '.listTree > ul > li > span > a.icon-arrow-up', function (e) {
  297.                         var id = $(e.target).closest('li').find('input').attr('id');
  298.                         var ind = _findIndexInMassiveById(context, id);
  299.  
  300.                         if (ind > -1) {
  301.                             var temp = context[ind];
  302.                             context[ind] = context[ind - 1];
  303.                             context[ind - 1] = temp;
  304.                             _bindDataToContext($this, context);
  305.  
  306.                             $this.html(_.template(template, { "context": context, "options": options }));
  307.                         }
  308.                        
  309.                         e.stopImmediatePropagation();
  310.                     })
  311.  
  312.                     .on('click', '.listTree > ul > li > span > a.icon-arrow-down', function (e) {
  313.                         var id = $(e.target).closest('li').find('input').attr('id');
  314.                         var ind = _findIndexInMassiveById(context, id);
  315.  
  316.                         if (ind > -1) {
  317.                             var temp = context[ind];
  318.                             context[ind] = context[ind + 1];
  319.                             context[ind + 1] = temp;
  320.                             _bindDataToContext($this, context);
  321.                             $this.html(_.template(template, { "context": context, "options": options }));
  322.                         }
  323.                        
  324.                         e.stopImmediatePropagation();
  325.                     })
  326.  
  327.                     .on('click', '.listTree > ul > li > ul > li > span > a.icon-arrow-up', function (e) {
  328.                         var id = $(e.target).closest('li').find('input').attr('id');
  329.                         var parentId = $(e.target).closest('ul').prev('span').find('input').attr('id');
  330.                         var parent = _.find(context, function (item) { return item.id == parentId; });
  331.  
  332.                         options.visibleParents[parentId] = true;
  333.  
  334.                         var ind = _findIndexInMassiveById(parent.values, id);
  335.  
  336.                         if (ind > -1) {
  337.                             var temp = parent.values[ind];
  338.                             parent.values[ind] = parent.values[ind - 1];
  339.                             parent.values[ind - 1] = temp;
  340.                             _bindDataToContext($this, context);
  341.  
  342.                             $this.html(_.template(template, { "context": context, "options": options }));
  343.                         }
  344.  
  345.                         e.stopImmediatePropagation();
  346.  
  347.                     })
  348.  
  349.                     .on('click', '.listTree > ul > li > ul > li > span > a.icon-arrow-down', function (e) {
  350.                         var id = $(e.target).closest('li').find('input').attr('id');
  351.                         var parentId = $(e.target).closest('ul').prev('span').find('input').attr('id');
  352.                         var parent = _.find(context, function (item) { return item.id == parentId; });
  353.  
  354.                         options.visibleParents[parentId] = true;
  355.  
  356.                         var ind = _findIndexInMassiveById(parent.values, id);
  357.  
  358.                         if (ind > -1) {
  359.                             var temp = parent.values[ind];
  360.                             parent.values[ind] = parent.values[ind + 1];
  361.                             parent.values[ind + 1] = temp;
  362.                             _bindDataToContext($this, context);
  363.  
  364.                             $this.html(_.template(template, { "context": context, "options": options }));
  365.                         }
  366.  
  367.                         e.stopImmediatePropagation();
  368.                     });
  369.  
  370.                     // Generate the list tree.
  371.                     $this.html(_.template(template, { "context": context, "options": options }));
  372.                 }
  373.             });
  374.         },
  375.  
  376.         destroy: function () {
  377.             return this.each(function () {
  378.  
  379.                 var $this = $(this),
  380.                     data = $this.data('listTree');
  381.  
  382.                 $(window).unbind('.listTree');
  383.                 $this.removeData('listTree');
  384.             });
  385.         },
  386.  
  387.         selectAll: function () {
  388.             // For each listTree...
  389.             return this.each(function () {
  390.                 // Select each parent checkbox.
  391.                 _selectAllChildren($(this));
  392.  
  393.                 // For each listTree parent...
  394.                 $(this).children('ul > li:first-child').each(function () {
  395.                     // Select each child checkbox.
  396.                     _selectAllChildren($(this));
  397.                 });
  398.  
  399.                 _updateSelectedObject($(this));
  400.             });
  401.         },
  402.  
  403.         deselectAll: function () {
  404.             // For each listTree...
  405.             return this.each(function () {
  406.                 // Deselect each parent checkbox.
  407.                 _deselectAllChildren($(this));
  408.  
  409.                 // For each listTree parent...
  410.                 $(this).children('ul > li:first-child').each(function () {
  411.                     // Deselect each child checkbox.
  412.                     _deselectAllChildren($(this));
  413.                 });
  414.  
  415.                 _updateSelectedObject($(this));
  416.             });
  417.         },
  418.  
  419.         expandAll: function () {
  420.             // For each listTree...
  421.             return this.each(function () {
  422.                 var node = $(this).children('ul').children('li');
  423.  
  424.                 // Change the icon.
  425.                 _toggleIcon(node);
  426.  
  427.                 // Show the child list.
  428.                 node.children('ul').slideDown('fast');
  429.             });
  430.         },
  431.  
  432.         collapseAll: function () {
  433.             // For each listTree...
  434.             return this.each(function () {
  435.                 var node = $(this).children('ul').children('li');
  436.  
  437.                 // Change the icon.
  438.                 _toggleIcon(node);
  439.  
  440.                 // Hide the child list.
  441.                 node.children('ul').slideUp('fast');
  442.             });
  443.         },
  444.  
  445.         update: function (context, options) {
  446.             // Default options
  447.             var defaults = {
  448.                 "startCollapsed": false,
  449.                 "selected": context
  450.             };
  451.             options = $.extend(defaults, options);
  452.  
  453.             // Validate the user entered default selections.
  454.             options.selected = _validateDefaultSelectionValues(options.selected);
  455.  
  456.             return this.each(function () {
  457.                 var $this = $(this),
  458.                     data = $this.data('listTree');
  459.  
  460.                 // Ensure the plugin has been initialized...
  461.                 if (data) {
  462.                     // Update the context.
  463.                     $(this).data('listTree', {
  464.                         "target": $this,
  465.                         "context": context,
  466.                         "options": options,
  467.                         "selected": options.selected
  468.                     });
  469.  
  470.                     // Generate the list tree.
  471.                     $this.html(_.template(template, { "context": context, "options": options }));
  472.                 }
  473.             });
  474.         },
  475.  
  476.         addParent: function () {
  477.             var context = $(this).data('listTree').context;
  478.             var options = $(this).data('listTree').options;
  479.             var newParentId = 0;
  480.  
  481.             while (true) {
  482.                 var child = _.find(context, function (item) { return item.id == "np" + newParentId; });
  483.                 if (child) {
  484.                     newParentId++;
  485.                 } else {
  486.                     break;
  487.                 }
  488.             }
  489.             $.each(context, function (i) {
  490.                 if (context[i].id == "np" + newParentId) {
  491.                     newParentId++;
  492.                 }
  493.             });
  494.  
  495.             context.push({ key: "", id: "np" + newParentId, values: [] });
  496.             _bindDataToContext($(this), context);
  497.             $(this).html(_.template(template, { "context": context, "options": options }));
  498.         },
  499.        
  500.         getContext: function() {
  501.             var context = $(this).data('listTree').context;
  502.             _bindDataToContext($(this), context);
  503.             return context;
  504.         }
  505.     };
  506.  
  507.     $.fn.listTree = function (method) {
  508.  
  509.         if (methods[method]) {
  510.             return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
  511.         } else if (typeof method === 'object' || !method) {
  512.             return methods.init.apply(this, arguments);
  513.         } else {
  514.             $.error('Method ' + method + ' does not exist on jQuery.listTree');
  515.         }
  516.  
  517.     };
  518. })(jQuery);
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement