select节点clone全解析
2009-12-18
在开发ns-log项目中,统计分类有复制的功能。由于之前的统计分类中的数据是通过JS赋值进去的,之后用户可能又进行了修改,发现进行节点克隆时,出现了 无法复制select下拉框值的怪异现象。本文对这个怪异现象进行解析和给出解决办法。
问题现状
使用节点的cloneNode(true/false)进行克隆时,目前是下面的表现情况:
- select为设置初始值或者初始值是第一个option。 表现:各大浏览器都没什么问题。
- select初始值不是在第一个option。 表现:IE下无法克隆,其他内核的浏览器没什么问题。
- select的值被用户或者JS修改。 表现:各个浏览器均无法克隆到真确的值。值结果跟第二条结果相同。
IE的特殊处理
对于上面第二条,初始值不在第一个option时无法克隆的情况,确实是IE的一个Bug,相信不少人都遇到过这样的问题。并且IE下使用cloneNode方法时,还有Event方面的问题,所以差不多可以放弃使用这个方法。
IE下可以使用节点的outerHTML属性解决这个问题,它能够实时的获取节点的内容,哪怕是select的值被用户或者程序改变。下面给出简单的实现。
function clone(node) {
var div = document.createElement('div');
div.innerHTML = node.outerHTML;
return div.childNodes[0];
}
其他内核浏览器的处理
既然IE内核的浏览器可以通过outerHTML属性来解决这个问题,那FF等浏览器可以通过类似的方法来实现吗?虽然FF等浏览器没有outerHTML属性,但是可以通过innerHTML属性实现,如:
function getOuterHTML(node) {
var div = document.createElement('div');
div.appendChild(node);
try {
return div.innerHTML;
} catch (e) {
div = null;
}
}
答案是否定。
为什么会这样呢?难道是FF等浏览器的bug?
下面还是从W3C中对cloneNode方法,select标签,属性的定义进行说明。
下面引用的资料都是来自HTML5草案,HTML4或者XHTML对这些没有太多详细的定义。虽然是HTML5的,但这些节点跟以前没什么变化。
W3C中的cloneNode
具体链接:http://www.w3.org/TR/DOM-Level-2-Core/core.html
里面有2点比较重要:
- 克隆时会拷贝节点的所有属性和对应的值。
- 如果cloneNode方法的参数为true,会通过递归的方法克隆子节点。
W3C中的select节点
具体链接:http://dev.w3.org/html5/spec/forms.html#the-select-element
W3C对于属性的定义有2种,一种是内容性属性(Content attributes),另一种是操作性属性(未给出具体的命名,这里暂时使用这个名字)。
对于select标签,内容性属性主要有:Global attributes,autofocus,disabled,form,multiple,name,size。其中Global attributes包含一些常用的属性(accesskey,class,contenteditable,contextmenu,dir,draggable,hidden,id,itemid, itemprop,itemref,itemscope,itemtype,lang,spellcheck,style,tabindex,title),这些属性是所有标签里都包含的,具体的见http://dev.w3.org/html5/spec/dom.html#global-attributes
而selectedIndex和value都属于操作性属性,这两个属性获取值的方式如下:
- select . selectedIndex[ = value ]
- Returns the index of the first selected item, if any, or −1 if there is no selected item. Can be set, to change the selection.
- select . value [ = value ]
- Returns the value of the first selected item, if any, or the empty string if there is no selected item. Can be set, to change the selection.
内容性属性和操作性属性的区别
给节点添加属性有两种方式,如下面所示:
var div = document.createElement('div')
div.id = 'welefen'; //直接加属性
div.setAttribute('id','welefen'); //通过setAttribute方法添加属性
对于内容性属性,这两种方法是完全相同的。
但对于操作性属性,第一种方式只会将属性添加在操作范围内,当把节点添加到DOM中,属性就失效了。
由于selectedIndex和value都是操作性属性,如果select的值被用户或者程序改变,clone时当前的值是无法带过去的。所以才会出现了无法克隆值的情况。并且也无法使用innerHTML来克隆值,因为innerHTML的原理跟这个是一样的。
innerHTML实现原理请看这里:
FF等浏览器解决方案
目前有2中解决方案,第一种是在select绑定change事件,触发change的时候,改变options里的selected属性,当然这种方法是很不可取的。另一种方案就是在克隆时获取元素的值,然后再赋值到克隆后的对象上去。
总结
当前出现这个情况的时候,以为是FF等浏览器的bug。当后来仔细想想,应该不会这样的情况,后来反复查看W3C相关的文档,终于确认了这一问题。下面给出完整的解决方案:
function cloneSelect(select) {
if (document.all) {
var html = select.outerHTML,
div = document.createElement('div');
div.innerHTML = html;
return div.childNodes[0];
}
var cloneSelect = select.cloneNode(true);
cloneSelect.selectedIndex = select.selectedIndex;
cloneSelect.value = select.value;
return cloneSelect;
}