Ruby's Louvre

每天学习一点点算法

导航

表单元素之下拉系

这里特指下拉框,select。但select有两种形态,由multiple属性决定。在多选形态下,用户按住shift键就能实现多选,但用得不多,主要是占空间。那我们着重说说单选形态及其结构。

下拉框是由多种元素组成,通常我们见过select套着option元素,这中间还能夹一层,optgroup就是对option元素进行分组。option元素里面不能放置其他元素节点,option元素间除了空白或注释节点,也不能放其他东西。

optgroup只是装饰用,对提交数据没有影响,当我们选中某个option元素时,它的selected属性就变成true,之前被选中的元素的selected属性变成false,select元素中selectedIndex的值会变成被选元素的序号(它在所有option元素的位置 )。此外,还有一个鲜为人知的属性selectedOptions,它是对应一个数组,装着被选中的元素,那么它就换成被选中元素。因此DOM操作是一种非常复杂与高消耗的行为,这导致基于虚拟DOM的react库的诞生。减少不必要的DOM操作,就能大幅提高性能。

select的值就是被选中的option元素的值,如果用户定义value属性,那么这值就是option.value,否则就是option的innerHTML,也就是option.text。这当中存在兼容问题,比如有的浏览器会对innerHTML进行两端空白trim操作,有的不会,建议统一使用trim操作。


option.value的提取方法如下:

//by 司徒正美
var roption = /^<option(?:\s+\w+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+))?)*\s+value[\s=]/i
var valHooks = {
    "option:get": IEVersion ? function (node) {
        //在IE11及W3C,如果没有指定value,那么node.value默认为node.text(存在trim作),但IE9-10则是取innerHTML(没trim操作)
        //specified并不可靠,因此通过分析outerHTML判定用户有没有显示定义value
        return roption.test(node.outerHTML) ? node.value : node.text.trim()
    } : function (node) {
        return node.value
    }
}

我们在看看如何动态添加option元素。这有两种方式,1是使用W3C的createElement与appendChild,2是使用new Option及options.add方法。

1. 直接使用select.innerHTML

//by 司徒正美
    select.innerHTML = '';

运行发现标准浏览器如chrome, firefox运行正常,DOM树为

IE(678)全家都呵呵了:

原因在于IE使用innerHTML给select赋值时会根据/^<\w\d['" ]>&/(尖括号中间的字母、数字,引号,空格)匹配的字符都干掉,无力吐槽。

2. 使用new Option创建select的options,这是比较推荐的方法。

我们先来看看Option构造器是怎么用的

//by 司徒正美

new option(text,value,defaultSelected,selected)
/*
text:字符串,指定option对象的text属性(即之间的文字)

value:字符串,指定option对象的value属性

defaultSelected:布尔值,指定option对象的defaultSelected属性   返回下面selected的默认值

selected:布尔值,指定option对象的selected属性  ture/false  是否选择

http://www.cnblogs.com/SpringSmallGrass/archive/2013/04/14/3019837.html
*/

除了第一个参数,其他都是可选的,相当于

//by 司徒正美
   var option =  document.createElement("option")
  option.text = "xxxx"
  option.value = "aaa"
  option.selected = true

再看options.add方法。options是select元素的一个数组属性,里面装着所有option元素。add是其上面的一个方法( IE中它也能出现在select元素上),此方法存在兼容问题。

var objSelect = document.getElementById('mySelect'); 
//添加一个选项 
objSelect.add(new Option("文本","值")); //这个只能在IE中有效 
objSelect.options.add(new Option("text","value")); //这个兼容IE与firefox 

options.add有两种传参方式,第一种要来传入两个元素,第一个是新option元素,第二个是已有的option元素,新元素会插入到旧元素之前。问题出现在第二个参数缺省的情况下:

objSelect.add(new Option("Label", "Value"), null);

请注意,在IE6及IE7下请使用不带null参数的add方法,在FF下请使用带null参数的方法,IE8下带不带都可以。 很奇怪为什么一定要加null,我猜测add方法里一定使用了'=== null'来判断第二的参数或者没有对参数数组的长度做验证。

还有一种传参方法,第一个是新option元素,第二个是插入位置,不写默认插入到末尾。

objSelect.add( new Option(txt, val)); //加在末尾
objSelect.add( new Option(txt, val),0);//加在开头 

早期IE是不支持传入两个元素,只支持传入元素与插入位置的方式。IE8是两种方式都支持,对于普通浏览器,如果传入的是索引数值,它不会认为是出错,还是会添加在最后。

<!DOCTYPE HTML>
<html>
    <head>
        <meta charset="UTF-8">
        <script type="text/javascript">
            //try{先执行的代码} catch(err){出错时执行的代码}
            //select的add方法,第一个参数是需要被添加的option元素,第二个参数决定了被添加的位置
            //普通浏览器中,第二个参数是一个既有的option对象,添加的option元素被插入到它之前.
            //ie7浏览器中,第二个参数是一个索引,表示第n个既有的option对象.
            //ie8以上版本,传入两种对象它都能识别.
            //如果没有第二个参数,则插入在最后.
            //对于普通浏览器,如果传入的是索引数值,它不会认为是出错,还是会添加在最后,所以,使用try{}catch{}方法解决这个问题,必须把普通浏览器的适用方法放在try里面.
            window.onload = function () {
                var select = document.getElementById("select")
                var btn = document.getElementById("btn")
                btn.onclick = function () {
                    //通过下标可以把jquery对象转换为javascript对象
                    try {    //普通浏览器和ie8以上版本执行以下代码
                        select.add(new Option('2.5'), select.options[2])
                    } catch (err) {   //ie7执行以下代码
                        select.add(new Option('2.5'), 2)
                    }
                }
            }
        </script>
    </head>

    <body>
        <select id="select" multiple="multiple" size="5">
            <option value="1">One</option>
            <option value="2">Two</option>
            <option value="3">Three</option>
            <option value="4">Four</option>
        </select>
        <div>
            <button type="button" id="btn">Click me!</button>
        </div>
    </body>
</html>

3. 使用document.createElement与appendChild。

这是标准DOM API,基本上无所不能。 在以前的IE4中, document只能创建img, area, option三种元素,到了IE5,一般可以编程创建几乎所以元素, 除了frame和iframe。 而且这些新的创建的元素的属性都是可读写的,并且可以编程随意访问。但是你必须得先把他们先回到他们相应的集合中或者当前文档中你才能使用, 否则会报错。

看下一个课题,如何找到可以提交的option元素。因为决定一个option能否提交,除了selected属性外,还有disabled属性,由于disabled属性可能出现在select或optgroup元素上,这问题就复杂了。此外selectedOptions数组属性并不可靠,不是所有浏览器都支持。jQuery在处理这里也花了不少代码。

var valHooks = {
    "option:get":  function (node) {
        // 见上
    },
    "select:get": function (node, value) {
        var option, options = node.options,
                index = node.selectedIndex,
                getter = valHooks["option:get"],
                one = node.type === "select-one" || index < 0,
                values = one ? null : [],
                max = one ? index + 1 : options.length,
                i = index < 0 ? max : one ? index : 0
        for (; i < max; i++) {
            option = options[i]
            //IE6-9在reset后不会改变selected,需要改用i === index判定
            //我们过滤所有disabled的option元素,但在safari5下,
            //如果设置optgroup为disable,那么其所有孩子都disable
            //因此当一个元素为disable,需要检测其是否显式设置了disable及其父节点的disable情况
            if ((option.selected || i === index) && !option.disabled
                    && (!option.parentNode.disabled || option.parentNode.tagName !== "OPTGROUP")
                    ) {
                value = getter(option)
                if (one) {
                    return value
                }
                //收集所有selected值组成数组返回
                values.push(value)
            }
        }
        return values
    }
}

option还有两个重要的属性,index是返回当前option元素在此select下所有option元素的位置。label是显示其文本,行为有点像text,优化级比text高,但有点兼容性问题。


通常情况下,IE,opera,safari是显示Label1与Label2,而chrome, firefox(即使是4.01的版本)是显示TextContent1与TextContent2,这个古老的bug(见这里) 至今没修复。

posted on 2016-01-19 14:36  司徒正美  阅读(1885)  评论(2编辑  收藏  举报