[原创] 扩展jquery-treegrid插件, 实现勾选功能和全删按钮.
新上手一个项目, 因而正好想学习下bootstrap, 所以就采用asp.net mvc + bootstrap来做. 因为需要TreeGrid的控件, 本来想用easyUI、LingerUi、DWZ, 但显示效果总觉得不协调. 搜来搜去, 发现了牛人写的 jquery-treegrid, 所以就参考这园子里的两篇文章开始做:
JS组件系列——自己动手封装bootstrap-treegrid组件: http://www.cnblogs.com/landeanfen/p/6776152.html
JS组件系列——封装自己的JS组件,你也可以: http://www.cnblogs.com/landeanfen/p/5124542.html
这次目的是扩展jquery-treegrid插件, 实现checkbox的全选、单选, 也照着上文添加了ajax远程获取数据的功能. 虽然后来发现了Kendo UI, 但本着学习的目的, 还是完成了这次的插件扩展功能.
由于常年没有接触过前端, 所以jquery基本语法都磕磕绊绊更别说扩展插件了, 边摸索边做. 待我改完一版本, 才能理解了jquery-treegrid插件的作者真的牛掰:
1. jquery-treegrid思想: 没有硬编码的html, 全部通过css样式实现. 像树形结点的维护、结点的打开与折叠状态, 都是由写在TR元素上的css来实现. 也倒是jquery本身不熟吧, 我第一次见这么少的html来实现的jQuery控件.
2. 插件的结构: 跟上面的总体结构类似, 都是 method{}方法集合、$.fn.treegrid扩展、default{}默认参数对象. 其通过初始化函数initTree来开始渲染table元素, 通过保存控件容器(TreeContainer) -> 保存全局设置(Settings) -> 递归地初始化结点(initNode) -> 为每个结点添加事件(intExpander、initIdent、initEvents、initState、initChangeEvent、initSettingsEvents)来完成最终功能. 整体上有点面向对象的意味, 跟之前自己写的JS差距甚大.
刚开始我也是照着前面两篇的文章来实现了基本的样子, 当我快做完的时候才理解了jquery-treegrid控件作者的思路. 根据前两篇文章的思路, 就是在初始化方法中, 通过ajax获取数据, 然后在回调方法中拼接HTML表格, 来完成对jquery-treegrid的远程取数据的扩展. 当我基本理解了jQuery-treegrid作者的思想以后, 尝试将上面的代码改成: 在获取远程数据后, render表格(renderHeader、RenderBody、递归的生成List类型的Tr和Tree类型的Tr)、之后初始化Header(初始化checkAll、DelAll、操作列)、再往后初始化结点(initExpander、initIndent、initCheck、initOperation、initEvnets、initState、initChangeEvent、initSettingsEvents). 所有操作(如:全选、全部选、直选部分)等操作, 也都是通过在Tr元素上附加css来实现的.
总结实现过程的砍儿:
1. 理解插件开发思路及手段, 为什么采用如此结构来组织插件.
2. 控件状态的处理其实是使用了cookie, 用jquery-cookie来实现的.
3. 特别要理解This所代表的对象, 可以在调试界面一步步验证this所代表的对象.
4. 关于原jquery-treegrid插件的封装, $(this).原控件('方法名','参数')来封装. 这里需要注意的是: 在我们自己的扩展代码中, 关于状态保存、容器位置、设置保存等内容, 我都是通过对原控件的封装, 将这些数据写在同一个位置, 以便实现我们自己的控件和原控件之间的数据共享.
5. 默认default对象之中的方法的调用: 1. 通过settings.方法名.apply(this, '参数')来调用. 2. 通过我们扩展控件中封装的, $(this).treegridExt('getSetting','方法名').apply(this,'参数')来实现的.
6. 注意参数的问题: 有时用this、有时用$(this)、有时用$($(this).treegridExt('方法名'))、有使用[$(this)], 有时用apply(this)、还有时用apply($(Elem))等等. 决定使用哪种方式的的根本原因就是 this所代表的当前对象. 根据当前对象的不同, 来变换几种参数形式.
最后, 废话不多说, 上代码. 注意: 改代码只实现了全选、全不选、选子项、以及子项变更后对父级选项的调整. 之后没有再深入编码, 因为项目还是比较紧, 考虑一个个控件自己搞还是太费劲了, 准备采用Kendo控件来完成项目, 此Demo仅作为学习jquery和扩展其插件的相关知识为目的. 顺带对老大说声抱歉, 多耽误2,3天(算上端午的话可能是5天).
//jquery.treegrid.js
/* * jQuery treegrid Plugin 0.3.0 * https://github.com/maxazan/jquery-treegrid * * Copyright 2013, Pomazan Max * Licensed under the MIT licenses. */ (function($) { var methods = { /** * Initialize tree * * @param {Object} options * @returns {Object[]} */ initTree: function(options) { var settings = $.extend({}, this.treegrid.defaults, options); return this.each(function() { var $this = $(this); $this.treegrid('setTreeContainer', $(this)); $this.treegrid('setSettings', settings); settings.getRootNodes.apply(this, [$(this)]).treegrid('initNode', settings); $this.treegrid('getRootNodes').treegrid('render'); }); }, /** * Initialize node * * @param {Object} settings * @returns {Object[]} */ initNode: function(settings) { return this.each(function() { var $this = $(this); $this.treegrid('setTreeContainer', settings.getTreeGridContainer.apply(this)); $this.treegrid('getChildNodes').treegrid('initNode', settings); $this.treegrid('initExpander').treegrid('initIndent').treegrid('initEvents').treegrid('initState').treegrid('initChangeEvent').treegrid("initSettingsEvents"); }); }, initChangeEvent: function() { var $this = $(this); //Save state on change $this.on("change", function() { var $this = $(this); $this.treegrid('render'); if ($this.treegrid('getSetting', 'saveState')) { $this.treegrid('saveState'); } }); return $this; }, /** * Initialize node events * * @returns {Node} */ initEvents: function() { var $this = $(this); //Default behavior on collapse $this.on("collapse", function() { var $this = $(this); $this.removeClass('treegrid-expanded'); $this.addClass('treegrid-collapsed'); }); //Default behavior on expand $this.on("expand", function() { var $this = $(this); $this.removeClass('treegrid-collapsed'); $this.addClass('treegrid-expanded'); }); return $this; }, /** * Initialize events from settings * * @returns {Node} */ initSettingsEvents: function() { var $this = $(this); //Save state on change $this.on("change", function() { var $this = $(this); if (typeof($this.treegrid('getSetting', 'onChange')) === "function") { $this.treegrid('getSetting', 'onChange').apply($this); } }); //Default behavior on collapse $this.on("collapse", function() { var $this = $(this); if (typeof($this.treegrid('getSetting', 'onCollapse')) === "function") { $this.treegrid('getSetting', 'onCollapse').apply($this); } }); //Default behavior on expand $this.on("expand", function() { var $this = $(this); if (typeof($this.treegrid('getSetting', 'onExpand')) === "function") { $this.treegrid('getSetting', 'onExpand').apply($this); } }); return $this; }, /** * Initialize expander for node * * @returns {Node} */ initExpander: function() { var $this = $(this); var cell = $this.find('td').get($this.treegrid('getSetting', 'treeColumn')); var tpl = $this.treegrid('getSetting', 'expanderTemplate'); var expander = $this.treegrid('getSetting', 'getExpander').apply(this); if (expander) { expander.remove(); } $(tpl).prependTo(cell).click(function() { $($(this).closest('tr')).treegrid('toggle'); }); return $this; }, /** * Initialize indent for node * * @returns {Node} */ initIndent: function() { var $this = $(this); $this.find('.treegrid-indent').remove(); var tpl = $this.treegrid('getSetting', 'indentTemplate'); var expander = $this.find('.treegrid-expander'); var depth = $this.treegrid('getDepth'); for (var i = 0; i < depth; i++) { $(tpl).insertBefore(expander); } return $this; }, /** * Initialise state of node * * @returns {Node} */ initState: function() { var $this = $(this); if ($this.treegrid('getSetting', 'saveState') && !$this.treegrid('isFirstInit')) { $this.treegrid('restoreState'); } else { if ($this.treegrid('getSetting', 'initialState') === "expanded") { $this.treegrid('expand'); } else { $this.treegrid('collapse'); } } return $this; }, /** * Return true if this tree was never been initialised * * @returns {Boolean} */ isFirstInit: function() { var tree = $(this).treegrid('getTreeContainer'); if (tree.data('first_init') === undefined) { tree.data('first_init', $.cookie(tree.treegrid('getSetting', 'saveStateName')) === undefined); } return tree.data('first_init'); }, /** * Save state of current node * * @returns {Node} */ saveState: function() { var $this = $(this); if ($this.treegrid('getSetting', 'saveStateMethod') === 'cookie') { var stateArrayString = $.cookie($this.treegrid('getSetting', 'saveStateName')) || ''; var stateArray = (stateArrayString === '' ? [] : stateArrayString.split(',')); var nodeId = $this.treegrid('getNodeId'); if ($this.treegrid('isExpanded')) { if ($.inArray(nodeId, stateArray) === -1) { stateArray.push(nodeId); } } else if ($this.treegrid('isCollapsed')) { if ($.inArray(nodeId, stateArray) !== -1) { stateArray.splice($.inArray(nodeId, stateArray), 1); } } $.cookie($this.treegrid('getSetting', 'saveStateName'), stateArray.join(',')); } return $this; }, /** * Restore state of current node. * * @returns {Node} */ restoreState: function() { var $this = $(this); if ($this.treegrid('getSetting', 'saveStateMethod') === 'cookie') { var stateArray = $.cookie($this.treegrid('getSetting', 'saveStateName')).split(','); if ($.inArray($this.treegrid('getNodeId'), stateArray) !== -1) { $this.treegrid('expand'); } else { $this.treegrid('collapse'); } } return $this; }, /** * Method return setting by name * * @param {type} name * @returns {unresolved} */ getSetting: function(name) { if (!$(this).treegrid('getTreeContainer')) { return null; } return $(this).treegrid('getTreeContainer').data('settings')[name]; }, /** * Add new settings * * @param {Object} settings */ setSettings: function(settings) { $(this).treegrid('getTreeContainer').data('settings', settings); }, /** * Return tree container * * @returns {HtmlElement} */ getTreeContainer: function() { return $(this).data('treegrid'); }, /** * Set tree container * * @param {HtmlE;ement} container */ setTreeContainer: function(container) { return $(this).data('treegrid', container); }, /** * Method return all root nodes of tree. * * Start init all child nodes from it. * * @returns {Array} */ getRootNodes: function () { return $(this).treegrid('getSetting', 'getRootNodes').apply(this, [$(this).treegrid('getTreeContainer')]); }, /** * Method return all nodes of tree. * * @returns {Array} */ getAllNodes: function() { return $(this).treegrid('getSetting', 'getAllNodes').apply(this, [$(this).treegrid('getTreeContainer')]); }, /** * Mthod return true if element is Node * * @returns {String} */ isNode: function() { return $(this).treegrid('getNodeId') !== null; }, /** * Mthod return id of node * * @returns {String} */ getNodeId: function() { if ($(this).treegrid('getSetting', 'getNodeId') === null) { return null; } else { return $(this).treegrid('getSetting', 'getNodeId').apply(this); } }, /** * Method return parent id of node or null if root node * * @returns {String} */ getParentNodeId: function() { return $(this).treegrid('getSetting', 'getParentNodeId').apply(this); }, /** * Method return parent node or null if root node * * @returns {Object[]} */ getParentNode: function() { if ($(this).treegrid('getParentNodeId') === null) { return null; } else { return $(this).treegrid('getSetting', 'getNodeById').apply(this, [$(this).treegrid('getParentNodeId'), $(this).treegrid('getTreeContainer')]); } }, /** * Method return array of child nodes or null if node is leaf * * @returns {Object[]} */ getChildNodes: function() { return $(this).treegrid('getSetting', 'getChildNodes').apply(this, [$(this).treegrid('getNodeId'), $(this).treegrid('getTreeContainer')]); }, /** * Method return depth of tree. * * This method is needs for calculate indent * * @returns {Number} */ getDepth: function() { if ($(this).treegrid('getParentNode') === null) { return 0; } return $(this).treegrid('getParentNode').treegrid('getDepth') + 1; }, /** * Method return true if node is root * * @returns {Boolean} */ isRoot: function() { return $(this).treegrid('getDepth') === 0; }, /** * Method return true if node has no child nodes * * @returns {Boolean} */ isLeaf: function() { return $(this).treegrid('getChildNodes').length === 0; }, /** * Method return true if node last in branch * * @returns {Boolean} */ isLast: function() { if ($(this).treegrid('isNode')) { var parentNode = $(this).treegrid('getParentNode'); if (parentNode === null) { if ($(this).treegrid('getNodeId') === $(this).treegrid('getRootNodes').last().treegrid('getNodeId')) { return true; } } else { if ($(this).treegrid('getNodeId') === parentNode.treegrid('getChildNodes').last().treegrid('getNodeId')) { return true; } } } return false; }, /** * Method return true if node first in branch * * @returns {Boolean} */ isFirst: function() { if ($(this).treegrid('isNode')) { var parentNode = $(this).treegrid('getParentNode'); if (parentNode === null) { if ($(this).treegrid('getNodeId') === $(this).treegrid('getRootNodes').first().treegrid('getNodeId')) { return true; } } else { if ($(this).treegrid('getNodeId') === parentNode.treegrid('getChildNodes').first().treegrid('getNodeId')) { return true; } } } return false; }, /** * Return true if node expanded * * @returns {Boolean} */ isExpanded: function() { return $(this).hasClass('treegrid-expanded'); }, /** * Return true if node collapsed * * @returns {Boolean} */ isCollapsed: function() { return $(this).hasClass('treegrid-collapsed'); }, /** * Return true if at least one of parent node is collapsed * * @returns {Boolean} */ isOneOfParentsCollapsed: function() { var $this = $(this); if ($this.treegrid('isRoot')) { return false; } else { if ($this.treegrid('getParentNode').treegrid('isCollapsed')) { return true; } else { return $this.treegrid('getParentNode').treegrid('isOneOfParentsCollapsed'); } } }, /** * Expand node * * @returns {Node} */ expand: function() { if (!this.treegrid('isLeaf') && !this.treegrid("isExpanded")) { this.trigger("expand"); this.trigger("change"); return this; } return this; }, /** * Expand all nodes * * @returns {Node} */ expandAll: function() { var $this = $(this); $this.treegrid('getRootNodes').treegrid('expandRecursive'); return $this; }, /** * Expand current node and all child nodes begin from current * * @returns {Node} */ expandRecursive: function() { return $(this).each(function() { var $this = $(this); $this.treegrid('expand'); if (!$this.treegrid('isLeaf')) { $this.treegrid('getChildNodes').treegrid('expandRecursive'); } }); }, /** * Collapse node * * @returns {Node} */ collapse: function() { return $(this).each(function() { var $this = $(this); if (!$this.treegrid('isLeaf') && !$this.treegrid("isCollapsed")) { $this.trigger("collapse"); $this.trigger("change"); } }); }, /** * Collapse all nodes * * @returns {Node} */ collapseAll: function() { var $this = $(this); $this.treegrid('getRootNodes').treegrid('collapseRecursive'); return $this; }, /** * Collapse current node and all child nodes begin from current * * @returns {Node} */ collapseRecursive: function() { return $(this).each(function() { var $this = $(this); $this.treegrid('collapse'); if (!$this.treegrid('isLeaf')) { $this.treegrid('getChildNodes').treegrid('collapseRecursive'); } }); }, /** * Expand if collapsed, Collapse if expanded * * @returns {Node} */ toggle: function() { var $this = $(this); if ($this.treegrid('isExpanded')) { $this.treegrid('collapse'); } else { $this.treegrid('expand'); } return $this; }, /** * Rendering node * * @returns {Node} */ render: function() { return $(this).each(function() { var $this = $(this); //if parent colapsed we hidden if ($this.treegrid('isOneOfParentsCollapsed')) { $this.hide(); } else { $this.show(); } if (!$this.treegrid('isLeaf')) { $this.treegrid('renderExpander'); $this.treegrid('getChildNodes').treegrid('render'); } }); }, /** * Rendering expander depends on node state * * @returns {Node} */ renderExpander: function() { return $(this).each(function() { var $this = $(this); var expander = $this.treegrid('getSetting', 'getExpander').apply(this); if (expander) { if (!$this.treegrid('isCollapsed')) { expander.removeClass($this.treegrid('getSetting', 'expanderCollapsedClass')); expander.addClass($this.treegrid('getSetting', 'expanderExpandedClass')); } else { expander.removeClass($this.treegrid('getSetting', 'expanderExpandedClass')); expander.addClass($this.treegrid('getSetting', 'expanderCollapsedClass')); } } else { $this.treegrid('initExpander'); $this.treegrid('renderExpander'); } }); } }; $.fn.treegrid = function(method) { if (methods[method]) { return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); } else if (typeof method === 'object' || !method) { return methods.initTree.apply(this, arguments); } else { $.error('Method with name ' + method + ' does not exists for jQuery.treegrid'); } }; /** * Plugin's default options */ $.fn.treegrid.defaults = { initialState: 'expanded', saveState: false, saveStateMethod: 'cookie', saveStateName: 'tree-grid-state', expanderTemplate: '<span class="treegrid-expander"></span>', indentTemplate: '<span class="treegrid-indent"></span>', expanderExpandedClass: 'treegrid-expander-expanded', expanderCollapsedClass: 'treegrid-expander-collapsed', treeColumn: 0, getExpander: function() { return $(this).find('.treegrid-expander'); }, getNodeId: function() { var template = /treegrid-([A-Za-z0-9_-]+)/; if (template.test($(this).attr('class'))) { return template.exec($(this).attr('class'))[1]; } return null; }, getParentNodeId: function() { var template = /treegrid-parent-([A-Za-z0-9_-]+)/; if (template.test($(this).attr('class'))) { return template.exec($(this).attr('class'))[1]; } return null; }, getNodeById: function(id, treegridContainer) { var templateClass = "treegrid-" + id; return treegridContainer.find('tr.' + templateClass); }, getChildNodes: function(id, treegridContainer) { var templateClass = "treegrid-parent-" + id; return treegridContainer.find('tr.' + templateClass); }, getTreeGridContainer: function() { return $(this).closest('table'); }, getRootNodes: function(treegridContainer) { var result = $.grep(treegridContainer.find('tr'), function(element) { var classNames = $(element).attr('class'); var templateClass = /treegrid-([A-Za-z0-9_-]+)/; var templateParentClass = /treegrid-parent-([A-Za-z0-9_-]+)/; return templateClass.test(classNames) && !templateParentClass.test(classNames); }); return $(result); }, getAllNodes: function(treegridContainer) { var result = $.grep(treegridContainer.find('tr'), function(element) { var classNames = $(element).attr('class'); var templateClass = /treegrid-([A-Za-z0-9_-]+)/; return templateClass.test(classNames); }); return $(result); }, //Events onCollapse: null, onExpand: null, onChange: null }; })(jQuery);
//jquery.treegrid.css
.treegrid-indent {width:16px; height: 16px; display: inline-block; position: relative;} .treegrid-expander {width:16px; height: 16px; display: inline-block; position: relative; cursor: pointer;} .treegrid-expander-expanded{background-image: url(../img/collapse.png); } .treegrid-expander-collapsed{background-image: url(../img/expand.png);}