JavaScript 多级联动select
分类:计算机教程

请到这里看09-08-18更新版本

能够根据自定义的菜单数据和select,自动设置联级的下拉菜单,可定义默认值。

类似的多级浮动菜单网上也很多实例,但大部分都是只针对一种情况或不够灵活,简单说就是做死了的。
所以我就想到做一个能够自定义菜单的,有更多功能的多级浮动菜单。
而其中的关键就是怎么根据自定义的菜单结构来生成新菜单,
关键中的难点就是怎么得到下级菜单结构和容器对象的使用。
理想的做法是每次有下级菜单时,从对象直接取得下级菜单结构,放到容器对象中,
并且容器能重用,而不是每次都重新生成。
但想了很久也想不到适合的做法,直到做了多级联动下拉菜单终于得到了灵感。
放弃了直接取得下级菜单结构,而是每次都从原来的菜单结构中获取当前需要的下级菜单结构。
容器对象也不是自动生成,而是由用户先定义好(后来也做到能自动生成了)。
放下了这些包袱后,后面的开发就顺畅了。

效果:

特点:
1.根据自定义菜单结构生成菜单;
2.多级联动功能;
3.自定义浮动位置(上下左右);
4.自定义延迟效果;
5.js控制编辑菜单;
6.可根据需要自动生产容器对象;

其中参数1是菜单结构:

效果:

图片 1图片 2菜单对象
var menu = [
    {'val': '1', 'txt': 'value'},
    {'val': '2 ->', 'menu': [
        {'val': '2_1'},
        {'val': '2_2'}
    ]},
    {'val': '3 ->', 'menu': [
        {'val': '3_1 ->', 'menu': [
            {'val': '3_1_1'},
            {'val': '3_1_2'}
        ]},
        {'val': '3_2'}
    ]},
    {'val': '4 ->', 'menu': [
        {'val': '4_1 ->', 'menu': [
            {'val': '4_1_1 ->', 'menu': [
                {'val': '4_1_1_1'}
            ]}
        ]}
    ]}
];

程序原理

参数2是select的id集合(按顺序):

程序是根据传统浮动菜单扩展而来,这里说一下几个比较关键或有用的地方:

var sel=["sel1","sel2","sel3","sel4","sel5"]

【延时功能】

可设置默认值(按顺序):

这个很多人都懂,就是设个setTimeout计时器,这里有两个计时器,分别是容器计时器和菜单计时器。
容器计时器的作用是鼠标移到容器外面时隐藏容器,难点是如何判断当前鼠标是不是在容器外面。
一般的方法是设个bool参数,mouseout时设为false,mouseover时设为true(or倒过来),再根据这个参数判断,
但这个方法在这个不行,经过容器里的菜单对象时会触发容器mouseout,
由于事件冒泡,菜单对象的mouseout也会触发容器的mouseout。
例如:

var val=["3 ->", "3_1 ->", "3_1_2"];

图片 3图片 4Code
<div style="height:100px; width:100px; background:#000000;" onmouseout="alert(2)">
<div style="height:50px; width:50px; background:#FF0000;" onmouseout="alert(1)">
</div>
</div>

源码:

这里推荐一个方法,使用contains(ff是compareDocumentPosition)方法。
这个方法是我做图片滑动展示效果时muxrwc教我的:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;
<html xmlns="http://www.w3.org/1999/xhtml"&gt;
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
<title>JavaScript 自定义多级联动下拉菜单</title>
<script type="text/javascript">
var $ = function (id) {
    return "string" == typeof id ? document.getElementById(id) : id;
};

var isIn = false, oT = Event(e).relatedTarget;
Each(oThis.Container, function(o, i){ if(o.contains ? o.contains(oT) || o == oT : o.compareDocumentPosition(oT) & 16){ isIn = true; } });

function addEventHandler(oTarget, sEventType, fnHandler) {
    if (oTarget.addEventListener) {
        oTarget.addEventListener(sEventType, fnHandler, false);
    } else if (oTarget.attachEvent) {
        oTarget.attachEvent("on"   sEventType, fnHandler);
    } else {
        oTarget["on"   sEventType] = fnHandler;
    }
};

 

function Each(arrList, fun){
    for (var i = 0, len = arrList.length; i < len; i ) { fun(arrList[i], i); }
};

详细参考仿LightBox内容显示效果,而菜单计时器就没什么特别,就是用来设置菜单内容。

function GetOption(val, txt) {
    var op = document.createElement("OPTION");
    op.value = val; op.innerHTML = txt;
    return op;
};

【浮动位置】

var Class = {
  create: function() {
    return function() {
      this.initialize.apply(this, arguments);
    }
  }
}

除了母菜单的容器是固定的,子菜单的容器都是绝对定位的,定位的关键就是取得适合的left和top值。
首先要取得上一级菜单的left和top值。
由于母菜单是相对定位的,要取它的绝对left和top值就必须逐层向上取值,并加起来:

Object.extend = function(destination, source) {
    for (var property in source) {
        destination[property] = source[property];
    }
    return destination;
}

while (o.offsetParent) { o = o.offsetParent; iLeft  = o.offsetLeft; iTop  = o.offsetTop; }

var CascadeSelect = Class.create();
CascadeSelect.prototype = {
  //select集合,菜单对象
  initialize: function(arrSelects, arrMenu, options) {
    if(arrSelects.length <= 0 || arrMenu.lenght <= 0) return;//菜单对象
    
    var oThis = this;
    
    this.Selects = [];//select集合
    this.Menu = arrMenu;//菜单对象
    
    this.SetOptions(options);
    
    this.Default = this.options.Default || [];
    
    this.ShowEmpty = !!this.options.ShowEmpty;
    this.EmptyText = this.options.EmptyText.toString();
    
    //设置Selects集合和change事件
    Each(arrSelects, function(o, i){
        addEventHandler((oThis.Selects[i] = $(o)), "change", function(){ oThis.Set(i); });
    });
    
    this.ReSet();
  },
  //设置默认属性
  SetOptions: function(options) {
    this.options = {//默认值
        Default:    [],//默认值(按顺序)
        ShowEmpty:    false,//是否显示空值(位于第一个)
        EmptyText:    "请选择"//空值显示文本(ShowEmpty为true时有效)
    };
    Object.extend(this.options, options || {});
  },
  //初始化select
  ReSet: function() {
  
    this.SetSelect(this.Selects[0], this.Menu, this.Default.shift());
    this.Set(0);
  },
  //全部select设置
  Set: function(index) {
    var menu = this.Menu
    
    //第一个select不需要处理所以从1开始
    for(var i=1, len = this.Selects.length; i < len; i ){
        if(menu.length > 0){
            //获取菜单
            var value = this.Selects[i-1].value;
            if(value!=""){
                Each(menu, function(o){ if(o.val == value){ menu = o.menu || []; } });
            } else { menu = []; }
            
            //设置菜单
            if(i > index){ this.SetSelect(this.Selects[i], menu, this.Default.shift()); }
        } else {
            //没有数据
            this.SetSelect(this.Selects[i], [], "");
        }
    }
    //清空默认值
    this.Default.length = 0;
  },
  //select设置
  SetSelect: function(oSel, menu, value) {
    oSel.options.length = 0; oSel.disabled = false;
    if(this.ShowEmpty){ oSel.appendChild(GetOption("", this.EmptyText)); }
    if(menu.length <= 0){ oSel.disabled = true; return; }
    
    Each(menu, function(o){
        var op = GetOption(o.val, o.txt ? o.txt : o.val); op.selected = (value == op.value);
        oSel.appendChild(op);
    });    
  },
  //添加菜单
  Add: function(menu) {
    this.Menu[this.Menu.length] = menu;
    this.ReSet();
  },
  //删除菜单
  Delete: function(index) {
    if(index < 0 || index >= this.Menu.length) return;
    for(var i = index, len = this.Menu.length - 1; i < len; i ){ this.Menu[i] = this.Menu[i   1]; }
    this.Menu.pop()
    this.ReSet();
  }
};

取得上一级菜单的left和top值后,再进行相应的移位就可以了:

window.onload=function(){
    
    var menu = [
        {'val': '1', 'txt': 'value'},
        {'val': '2 ->', 'menu': [
            {'val': '2_1'},
            {'val': '2_2'}
        ]},
        {'val': '3 ->', 'menu': [
            {'val': '3_1 ->', 'menu': [
                {'val': '3_1_1'},
                {'val': '3_1_2'}
            ]},
            {'val': '3_2'}
        ]},
        {'val': '4 ->', 'menu': [
            {'val': '4_1 ->', 'menu': [
                {'val': '4_1_1 ->', 'menu': [
                    {'val': '4_1_1_1'}
                ]}
            ]}
        ]}
    ];
    
    var sel=["sel1", "sel2", "sel3", "sel4", "sel5"];
    
    var val=["3 ->", "3_1 ->", "3_1_2"];
    
    var cs = new CascadeSelect(sel, menu, { Default: val });
    
    $("btnA").onclick=function(){cs.ShowEmpty=!cs.ShowEmpty;}
    
    $("btnB").onclick=function(){
        cs.Add(
            {'val': '5 ->', 'menu': [
                {'val': '5_1 ->', 'menu': [
                    {'val': '5_1_1 ->', 'menu': [
                        {'val': '5_1_1_1 ->', 'menu': [
                            {'val': '5_1_1_1_1'}
                        ]}
                    ]}
                ]}
            ]}
        )
    }
    
    $("btnC").onclick=function(){
        cs.Delete(3)
    }
}
</script>

图片 5图片 6Code
switch (position.toLowerCase()) {
    case "up" :
        iTop -= oContainer.offsetHeight;
        break;
    case "down" :
        iTop  = o.offsetHeight;
        break;
    case "left" :
        iLeft -= oContainer.offsetWidth;
        break;
    case "right" :
    default :
        iLeft  = o.offsetWidth;
}

<style type="text/css">
.sel select{ width:100px;}
</style>

 

</head>
<body>

这里要注意,如果display为none的话会取不到offset值,
所以为了在隐藏的状态也能定位,就要用visibility来隐藏。
当然如果display可以先显示再定位,但这样会出现瞬间移动的现象,不建议。

<div class="sel">
<select id="sel1"></select>
<select id="sel2"></select>
<select id="sel3"></select>
<select id="sel4"></select>
<select id="sel5"></select>
</div>
<br />
<div>
<input id="btnA" type="button" value="显示/不显示空值" />
<input id="btnB" type="button" value="添加菜单" />
<input id="btnC" type="button" value="减少菜单" />

【自动生成容器对象】

</div>
</body>
</html>

除了第一个容器对象,当发现容器不够时,会根据前一个容器来生成新容器。
开始时我想用cloneNode,但由于对象中有事件所以不能这样用,只能手动建一个。


首先根据前一个容器的tagName创建一个新容器,
为了使用相同的样式,复制cssText(这个也是muxrwc告诉我的)和className到新容器,
然后用IniContainer()函数设置一下就可以了:

图片 7图片 8Code
var oPre = this.Container[i-1], oNew = document.body.appendChild(document.createElement(oPre.tagName));
oNew.style.cssText = oPre.style.cssText; oNew.className = oPre.className;
this.IniContainer(this.Container[i] = oNew, true);

【多级联动】

联动的关键是如何得到子菜单结构和根据这个子菜单结构生成菜单对象。
先说说菜单结构,是类似这样的结构:

图片 9图片 10Code
[
    {'txt': '1' },
    {'txt': '2', 'position': 'down', 'menu': [
        {'txt': '2_1'},
        {'txt': '2_2'}
    ]}
]

 

知道json的应该都知道是什么了,js的一种对象结构:
txt是显示的内容,也可以是html,到时会innerHTML插入;
position是位置,可以是"right"(默认),"down","up","left",浮动位置会根据这个值来设置;
menu是下一级的菜单结构。
可以看出这类似一个n维数组,注意是类似。

那怎么根据这个菜单结构获得当前菜单的子菜单呢?
首先从菜单对象的onmouseover说起,
在菜单a的onmouseover中,要做的是重新设置菜单和重新设置样式(这个稍后再说)。
设置菜单还包括设置一个索引属性index来记录当前容器菜单的索引(容器第几个菜单),
这里有点取巧的是容器菜单的索引跟对应菜单结构中menu的索引是相同的(后面会用到),
而_index是当前容器的索引(第几个容器),同样这里的索引也可以用来指示当前菜单在第几级。
还要设置_onmenu为当前的菜单对象,它在取浮动位置时需要用到。
然后就可以用Set()程序来设置菜单了:

图片 11图片 12Code
oThis._timerMenu = setTimeout(function(){
    oContainer.index = i; oThis._onmenu = oMenu; oThis._index = index   1;  oThis.Set();
}, oThis.Delay);

本文由美洲杯赔率发布于计算机教程,转载请注明出处:JavaScript 多级联动select

上一篇:使用ASP.NET AJAX异步调用Web Service和页面中的类方法 下一篇:没有了
猜你喜欢
热门排行
精彩图文
  • JavaScript 多级联动select
    JavaScript 多级联动select
    请到这里看09-08-18更新版本 能够根据自定义的菜单数据和select,自动设置联级的下拉菜单,可定义默认值。 类似的多级浮动菜单网上也很多实例,但大部分
  • Bootstrap 2.2.2 发布,重要的 Bug 修复版本
    Bootstrap 2.2.2 发布,重要的 Bug 修复版本
    今天我们发布了 Bootstrap 2.2.2, 这是另外一个很大的 bugfix版本,主要是侧重于 CSS 和文档方面的问题修复,也有很小一部分的JS问题,主要包括: Docs: Asset
  • 每天一个linux命令 chgrp命令
    每天一个linux命令 chgrp命令
    [root@localhost test]# ll---xrw-r-- 1 root root 302108 11-13 06:03 log2012.log[root@localhost test]# chgrp -v bin log2012.log“log2012.log” 的所属组已更改为 bin[root@localhost test]# ll---xr
  • 我是如何跨专业零基础学习Python爬虫的(2 爬虫所
    我是如何跨专业零基础学习Python爬虫的(2 爬虫所
    列表是Python中最基本的数据结构,列表是最常用的Python数据类型,列表的数据项不需要具有相同的类型。列表中的每个元素都分配一个数字 2. Python 列表
  • TCP socket如何判断连接断开
    TCP socket如何判断连接断开
    自己做了一个tcp工具,在学习动画的时候踩了坑,需求是根据上线变绿色,离线变灰色,如果连接断开了,则变为灰色 http://blog.csdn.net/zzhongcy/article/detail