开发背景:
每次都很啰嗦,要先陈述一下开发背景。不过,这样做的目的一来是让自己能够快速回忆起自己的实战背景,二来是类似引子,能够阐述这样处理的实际用途,不仅仅是用于理论学习。
今天记录下的是前几天开发的一个下拉框组件,该组件内容是一棵菜单树。本来系统开发的时候使用的是BUI,BUI本身也有下拉树组件。只是因为我们使用的BUI版本比较早,今年的版本和去年的在某些方面不兼容,如果一次性替换,会引起很多js报错。又因为近期使用过就是jstree,测试过几万条节点数据的加载性能,相比BUI,渲染速度有优秀很多(BUI并不能胜任这个数量级)。所以,干脆自己封装一个组件。具体要求,我总结了一下:
自定义下拉框,该下拉框内容为菜单树。下拉框操作规则同其他BUI下拉框组件,但是封装方式并非使用BUI封装方式,而是自定义封装。所以需要测试如下功能
①菜单树节点被选中,会将值显示在输入框,满足多选功能,用逗号分隔
②菜单树初始化会将存储的节点名称显示在输入框,并且菜单树相应节点会被触发选中
③提供清空功能,点击清空按钮,下拉框所有选中节点都会清空,并且输入框值也清空;清空按钮在菜单树被选中的情况下显示,在没有选中的情况下隐藏
④下拉框在单击输入框和下拉标签的情况下会显示出来,在单击下拉框区域外的所有位置都会自动收缩
⑤查询界面的下拉框的清空功能和“重置”按钮联动
实战结果:
这里,有使用到很多封装细节,因为时间问题,初稿不做太多细节表述,重点在代码中有做注释。直接上代码:
首先前端html:
- <%@ page language="java" contentType="text/html; charset=UTF-8"
- pageEncoding="UTF-8"%>
- <%-- 显示输入框 --%>
- <div class="rmp-span">
- <label class="rmp-control-label">部门归属</label>
- <div class="rmp-control-text">
- <div id="department" class="bui-cncselectfilter bui-suggest bui-combox bui-select" aria-disabled="false" tabindex="0" hidefocus="true" aria-pressed="false">
- <input type="text" class="bui-select-input" /><span class="icon-delelte-input"><i class="icon-remove-sign-select"></i></span><span class="x-icon x-icon-normal"><span class="x-caret x-caret-down"></span></span>
- </div>
- </div>
- </div>
- <%-- 隐藏的存值输入框 --%>
- <input name="qo.department" id="rdepartment" type="hidden"/>
- <%-- 隐藏的下拉框 --%>
- <div id='jstree_around' class="hide">
- <div class="apm-treeselect-header">
- <div class="apm-treeselect-filter">
- <input id="search_input" class="filter"/>
- </div>
- </div>
- <div id="jstree_div"></div>
- </div>
- <link rel="stylesheet" href="${uics}/jstree/themes/default/style.css" />
- <script type="text/javascript" src="${uics}/jstree/jstree.js"></script>
- <script type="text/javascript">
- // AJAX异步拉取数据
- var treeData = null;
- $.ajax({
- url : ctx + "/common/organization-tree.action",
- type : "post", async: false,
- success : function(data) {
- treeData = data.tree;
- }
- });
- $(function(){
- //树主体初始化
- $('#jstree_div').jstree({
- "core" : {
- "multiple" : true, // 允许多选
- 'animation' : false,
- 'data' : treeData,
- },
- 'expand_selected_onload' : true, //选中项蓝色底显示
- 'checkbox' : {
- // 禁用级联选中
- 'three_state' : false,
- 'cascade' : 'undetermined' //有三个选项,up, down, undetermined; 使用前需要先禁用three_state
- },
- 'plugins' : ['checkbox', 'search'] //如果使用checkbox效率会降低, 'wholerow'会把线隐藏掉
- });
- //绑定到自定义的组件上
- ApmJstreeUtil.bindJstree({
- render : 'jstree_div',
- showField : 'department',
- saveField : 'rdepartment',
- picker : 'jstree_around',
- searchField : 'search_input',
- width : 250,
- height : 300
- });
- })
- </script>
js封装方式:
- var ApmJstreeUtil = {
- /**
- * @param render-渲染id,saveFiled-存储的input id,showField-展示的input id,
- * picker-下拉div的id
- */
- bindJstree : function(obj) { //输入对象
- //初始化下拉隐藏域的弹出位置和宽高
- var picker = $('#' + obj['picker']);
- picker.addClass('apm-tree-picker');
- picker.css('width', obj['width']);
- picker.css('height', obj['height']);
- var inputDiv = $('#' + obj['showField']);
- var top = inputDiv.offset().top + inputDiv.outerHeight(); //获取偏移位置
- var left = inputDiv.offset().left;
- picker.css('top', top); //设置绝对位置
- picker.css('left', left);
-
- var treeObj = $('#' + obj['render']);
- treeObj.css("text-align", "left");
-
- var deleteIcon = inputDiv.find("i"); //通过find查找子元素
- var inputShow = inputDiv.find("input");
- var saveInput = $('#' + obj['saveField']);
-
- //默认查询框,可以外部自定义,从而覆盖该触发方式
- if (obj['searchField'] && $('#' + obj['searchField'])) {
- var searchFieldObj = $('#' + obj['searchField']);
- var to = false;
- searchFieldObj.keyup(function() { //绑定按键事件,也可以绑定特定按键
- if (to) {
- clearTimeout(to);
- }
- to = setTimeout(function() {
- var v = searchFieldObj.val();
- treeObj.jstree(true).search(v);
- }, 250);
- });
- }
-
- //将选择值显示在输入input和隐藏input
- treeObj.on("changed.jstree", function(e, data) {
- if (data && data.selected.length > 0) {
- $('#' + obj['saveField']).val(data.selected.join(","));
- var i, j, r = [];
- for(i = 0, j = data.selected.length; i < j; i++) {
- r.push(data.instance.get_node(data.selected[i]).text);
- }
- deleteIcon.show();
- inputShow.val(r.join(","));
- } else {
- deleteIcon.hide();
- inputShow.val("");
- saveInput.val("");
- }
- });
-
- // 绑定load时间,初始化数据显示
- treeObj.on("loaded.jstree", function(e, data) {
- treeObj.jstree("open_all"); //展开全部
- var saveValue = $('#' + obj['saveField']).val();
- var checkNodeIds = saveValue.split(",");
- if (!saveValue || !checkNodeIds || checkNodeIds.length === 0) {
- deleteIcon.hide();
- return ;
- }
- var r = [];
- treeObj.find("li").each(function() {
- for (var i = 0; i < checkNodeIds.length; i++) {
- if ($(this).attr("id") == checkNodeIds[i]) { //如果节点的ID等于checkNodeIds[i],表示要选中
- r.push(data.instance.get_node(checkNodeIds[i]).text);
- treeObj.jstree("select_node", $(this)); //选中的节点,不是check_node
- //$(this).children("a").addClass("jstree-clicked");
- break;
- }
- }
- })
- deleteIcon.show();
- inputShow.val(r.join(","));
- });
-
- //隐藏和展示绑定
- inputDiv.on('click', function() {
- picker.show();
- });
- $('body').click(function(evt) {
- if ($(evt.target).parents('#' + obj['showField']).length == 0
- && $(evt.target).parents('#' + obj['picker']).length == 0 //判断鼠标点击的上层是否是#jstree_div
- && evt.target.id != obj['showField']
- && evt.target.id != obj['picker']
- && evt.target.className.indexOf("jstree") == -1) { //防止点击展开节点前面值为true
- picker.hide();
- }
- });
- //清空按钮
- deleteIcon.on('click', function() {
- ApmJstreeUtil.deselectJstree(obj);
- deleteIcon.hide();
- });
- },
-
- /**
- * 清楚被选中的项
- * @param render-渲染id,saveFiled-存储的input id,showField-展示的input id,
- * picker-下拉div的id
- */
- deselectJstree : function(obj) {
- var treeObj = $('#' + obj['render']);
- var saveField = $('#' + obj['saveField']);
- var checkNodeIds = saveField.val().split(",");
- if (!checkNodeIds || checkNodeIds.length === 0) {
- return ;
- }
- treeObj.find("li").each(function() {
- for (var i = 0; i < checkNodeIds.length; i++) {
- if ($(this).attr("id") == checkNodeIds[i]) {
- treeObj.jstree("deselect_node", $(this)); //取消选中的节点
- break;
- }
- }
- })
- saveInput.val("");
- }
- };<strong>
- </strong>
效果展示:
单击输入框,弹出下拉框,使用jstree生成的树 选中数据后,会显示叉形删除标示,可以清空数据,包括清空隐藏域
待改进
可以发现,前端html引用输入框和隐藏下拉框代码太长,不便他人二次使用。应该考虑进一步封装到ApmJstreeUtils中。
小结:
这应该可以算是我第一次比较大的js封装实战。以前对于事件的绑定、DOM树的细节操作都不是很清楚,实战后对这些有了进一步的理解。js是个非常灵活的弱语言,开发方式非常多,从而对于初学者而言,有弊有利——弊是别人封装的很多代码不是那么好理解,除非一步步深究,还有就是很多细节用得不是很合理,(详见《JavaScript语言精粹》),容易留下鲁棒性不足的隐患;利,最明显的就是,不管你是不是菜鸟,了解一点js,都能搞点可以显摆给外行人看的交互界面。