表单脚本
选择框脚本
选择框是通过<select>和<option>元素创建的。<select>元素是在JS中是HTMLSelectElement 类型,提供了额外的属性和方法:
add(newOption, relOption):向控件中插入新<option>元素,其位置在相关项(relOption)之前。可以通过new Option(text, value)创建HTMLOptionElement 类型的对象,此外用这个函数来调换两个option元素位置也挺好用的(类似于insertBefore)。multiple:布尔值,表示是否允许多项选择;等价于HTML 中的multiple 特性。options:控件中所有<option>元素的HTMLCollection。remove(index):移除给定位置的选项。如果不传参数,则会把整个select移除掉。如果传的不是Number类型的,会通过valueOf或者toString转成Number类型。没有重写的话会返回0?selectedIndex:基于0 的选中项的索引,如果没有选中项,则值为-1。对于支持多选的控件,只保存选中项中第一项的索引。改变这个值也可以设置选中项。size:选择框中可见的行数;等价于HTML 中的size 特性。type:”select-one“或”select-multiple“,取决是是否有multiple 特性。选择框的value属性由选中项决定(一些自定义标签实现了设置value改变选中项的功能,但默认是没有这个功能的):
如果没有选中的项,则选择框的value保存空字符。如果有选中项或多个选中项,取第一个选中项作为参考。如果第一个选中项的value有值,则select的value值将设置与其相同;如果无值,则设置为option的文本值。针对上面的特点,下面是两个例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title
</title>
</head>
<body>
<form action="javascript:alert(1)">
<select>
<option>1
</option>
<option>2
</option>
<option>3
</option>
<option>4
</option>
<option>5
</option>
<option>6
</option>
<option>7
</option>
<option>8
</option>
<option>9
</option>
<option>10
</option>
</select>
</form>
</input>
<script>
var select = document.forms[0].elements[0];
console.log(select.type);
var options = select.options;
console.log(options.length);
select.remove(0);
select.remove({});
select.remove(9);
select.remove(
{
valueOf:function(){
return 7;
}
}
);
select.add(options[1], options[0]);
</script>
</body>
</html>
在做select的value值实验前,我们来了解一下HTMLOptionElement类型。每个option都是该类型,该类型有下列额外的属性:
index:当前选项在options集合中的索引。label:等价于label特性。selected:布尔值,表示当前选项是否被选中。将这个属性设置为true 可以选中当前选项。text:选项的文本。value:选项的值(等价于HTML 中的value 特性)。在未指定value 特性的情况下,IE8 会返回空字符串,而IE9+、Safari、Firefox、Chrome 和Opera 则会返回与text 特性相同的值。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title
</title>
</head>
<body>
<form action="javascript:alert(1)">
<select name="location" id="selLocation">
<option value="Sunnyvale, CA">Sunnyvale
</option>
<option value="Los Angeles, CA">Los Angeles
</option>
<option value="Mountain View, CA">Mountain View
</option>
<option value="">China
</option>
<option>Australia
</option>
</select>
</form>
</input>
<script>
var select = document.forms[0].elements[0];
select.addEventListener("change", function (e) {
var select = e.target;
console.log(select.value);
console.log(select.options[select.selectedIndex].value
+ "-----" + select.options[select.selectedIndex].text);
});
</script>
</body>
</html>
------------------------------
Los Angeles, CA
Los Angeles, CA-----Los Angeles
Sunnyvale, CA
Sunnyvale, CA-----Sunnyvale
Mountain View, CA
Mountain View, CA-----Mountain View
-----China //主动设置为"",将返回""
Australia
Australia-----Australia //不设置value特性,可以理解成设置value=text(但是没有改变HTML)
针对上面最后一个特性,我想知道,这个是不是自动设置了value特性所造成的。于是我用getAttribute去获取option的value特性,结果发现是null。这说明了value属性与text相同,并不是自动设置了value特性。而应该是一种类似呼叫转移(这个词纯属胡诌)的行为。这个结果也给了我们一个启示:也就是前一篇文章开头提到的:对value 属性所做的修改,不一定会反映在DOM中。虽然value值与value特性息息相关,但是value值的变化并不都是反映了DOM中的value特性的变化,也可能是text的变化。所以要获取value值,千万不要通过getAttribute这种方式。通过常规DOM的方式去获取option元素的value与text的效率也会更低。所以我们在得到HTMLOptionElement对象后,要多去使用value和text属性而避免再通过getAttribute和innerHTML去获取value和text特性。
选择选项
选中选项有两种方式,一个是设置select元素的selectedIndex,另外一种则是设置option元素的selected。接下来分单选和多选框进行讨论。如果是单选框,那么selectedIndex的用法是没有疑问的,而selected就有一个问题:如果我设置了多个option的selected都为true,那会如何:
<select name="location" id="selLocation">
<option value="Sunnyvale, CA" selected="true">Sunnyvale
</option>
<option value="Los Angeles, CA"selected="true">Los Angeles
</option>
<option value="Mountain View, CA"selected="true">Mountain View
</option>
<option value="">China
</option>
<option>Australia
</option>
</select>
<script>
var select = document.forms[0].elements[0];
console.log(select.options[0].selected);
console.log(select.options[1].selected);
console.log(select.options[2].selected);
</script>
----------------------
无论在何种浏览器中都是选中了第三个,可见这种行为只认最后一个的设置。
如果我单纯通过脚本去设置:结果一模一样
<script>
var select = document.forms[0].elements[0];
select.options[0].selected = true;
select.options[1].selected = true;
select.options[2].selected = true;
console.log(select.options[0].selected);
console.log(select.options[1].selected);
console.log(select.options[2].selected);
</script>
如果是多选框,那selected可以不用讨论了,可以允许多个被选中,也就可以允许多个为true。但是selectedIndex的行为就很诡异。选中了多个选项,selectedIndex只会返回第一个被选中的选项的索引。如果通过脚本改变selectedIndex,则会只选中那一项,即使之前已经选中了很多项。例子就不举了。
添加选项
可以使用JavaScript 动态创建选项,并将它们添加到选择框中。添加选项的方式有很多,第一种方式就是使用如下所示的DOM 方法。
var newOption = document
.createElement(
"option")
newOption
.appendChild(document
.createTextNode(
"Option text"))
newOption
.setAttribute(
"value",
"Option value")
selectbox
.appendChild(newOption)
第二种就是之前提到过的使用new Option(“Option text”, “Option value”);创建一个HTMLOptionElement对象,然后通过appendChild或者select元素独有的add方法去添加到DOM中。
var newOption =
new Option(
"Option text",
"Option value");
selectbox.appendChild(newOption);
selectbox.add(newOption,
undefined);
移除选项
除了最常规的DOM的removeChild()方法外,还可以使用select元素特有的remove()方法。这个方法传入一个索引即可使用,前面已经试验过了。还有一种遗留机制:
selectbox.options[
0] =
null;
如果想要移除所有选项,可以遍历获取options的长度,然后执行长度次数的remove(0)即可(DOM都是动态的)。
for(var
i=
0, len=
selectbox.options.length;
i < len;
i++)
{
selectbox.remove(0);//千万不要以为是remove(i),否则你会发现只能移除索引为偶数的option
}
移动和重排选项
在DOM 标准出现之前,将一个选择框中的选项移动到另一个选择框中是非常麻烦的。整个过程要涉及从第一个选择框中移除选项,然后以相同的文本和值创建新选项,最后再将新选项添加到第二个选择框中。而使用DOM 的appendChild()方法,就可以将第一个选择框中的选项直接移动到第二个选择框中。我们知道,如果为appendChild()方法(insertBefore也可以)传入一个文档中已有的元素,那么就会先从该元素的父节点中移除它,再把它添加到指定的位置。
var selectbox1 = document
.getElementById(
"selLocations1")
var selectbox2 = document
.getElementById(
"selLocations2")
selectbox2
.appendChild(selectbox1
.options[
0])
而通过之前的add方法我们也可以移动位置
var selectbox1 =
document.getElementById(
"selLocation1");
var selectbox2 =
document.getElementById(
"selLocation2");
selectbox2.add(selectbox1.options[
0],
undefined);
表单序列化
随着Ajax 的出现,表单序列化已经成为一种常见需求(第21 章将讨论Ajax)。在JavaScript 中,可以利用表单字段的type 属性,连同name 和value 属性一起实现对表单的序列化。
对表单字段的名称和值进行URL 编码,使用和号(&)分隔。不发送禁用的表单字段。只发送勾选的复选框和单选按钮。不发送type 为”reset”和”button”的按钮。多选选择框中的每个选中的值单独一个条目。在单击提交按钮提交表单的情况下,也会发送提交按钮;否则,不发送提交按钮。也包括type为”image”的<input>元素。<select>元素的值,就是选中的<option>元素的value 特性的值。如果<option>元素没有value 特性,则是<option>元素的文本值。下面是书中给的序列化的代码:
function serialize(form){
var parts = [],
field =
null,
i,
len,
j,
optLen,
option,
optValue;
for (i=
0, len=form.elements.length; i < len; i++){
field = form.elements[i];
switch(field.type){
case "select-one":
case "select-multiple":
if (field.name.length){
for (j=
0, optLen = field.options.length; j < optLen; j++){
option = field.options[j];
if (option.selected){
optValue =
"";
if (option.hasAttribute){
optValue = (option.hasAttribute(
"value") ? option.value : option.text);
}
else {
optValue = (option.attributes[
"value"].specified ? option.value : option.text);
}
parts.push(
encodeURIComponent(field.name) +
"=" +
encodeURIComponent(optValue));
}
}
}
break;
case undefined:
case "file":
case "submit":
case "reset":
case "button":
break;
case "radio":
case "checkbox":
if (!field.checked){
break;
}
default:
if (field.name.length){
parts.push(
encodeURIComponent(field.name) +
"=" +
encodeURIComponent(field.value));
}
}
}
return parts.join(
"&");
}
富文本编辑
所谓富文本编辑就是我们可以在页面中建立一个文本编辑区,我们可以对其中的文本进行样式的设置,然后可以保持原样的将这段文本放置入页面的其他位置。故富文本编辑,又称为WYSIWYG(What You See Is What You Get,所见即所得)。就像博客下面的回复编辑区一样,我们可以改变字体大小或者颜色,提交后就可以保持原样地显示在回复区。这个功能可以说非常有用,那么该如何实现呢?这一技术的本质,就是在页面中嵌入一个包含空HTML 页面的iframe。通过设置designMode 属性,这个空白的HTML 页面可以被编辑,而编辑对象则是该页面<body>元素的HTML 代码。designMode 属性有两个可能的值:”off”(默认值)和”on”。在设置为”on”时(需在页面加载完成后设置,所以一般写在load事件处理程序内部),整个文档都会变得可以编辑(显示插入符号),然后就可以像使用字处理软件一样,通过键盘将文本内容加粗、变成斜体,等等。此外还可以使用contenteditable属性,可以把contenteditable 属性应用给页面中的任何元素,然后用户立即就可以编辑该元素。还可以在JS脚本中设置contenteditable属性,contenteditable属性有三个可能的值:true”表示打开、“false”表示关闭,“inherit”表示从父元素那里继承(因为可以在contenteditable 元素中创建或删除元素)。但是我实验下来发现这个contenteditable属性好像不能用,所以这里也不再放例子了。
<
div class=
"editable" id=
"richedit" contenteditable></
div>
操作富文本
与富文本编辑器交互的主要方式,就是使用document.execCommand()。这个方法可以对文档执行预定义的命令,而且可以应用大多数格式。可以为document.execCommand()方法传递3 个参数:要执行的命令名称、表示浏览器是否应该为当前命令提供用户界面的一个布尔值(为了兼容,这个参数应该始终为false)和执行命令必须的一个值(如果不需要值,则传递null)。以下是命令表: 下面是书中给的一个简单的富文本编辑例子:
<!DOCTYPE html>
<html>
<head>
<title>Rich Text Editing Example
</title>
</head>
<body>
<form method="post" action="javascript:alert('Form submitted!')">
<div id="divSimple">
<input type="button" value="Bold">
<input type="button" value="Italic">
<input type="button" value="Underline">
<input type="button" value="Indent">
<input type="button" value="Outdent">
<input type="button" value="Copy">
<input type="button" value="Cut">
<input type="button" value="Paste">
</div>
<div id="divComplex">
<input type="button" value="Create Link" id="btnCreateLink">
<input type="button" value="Change Font Size" id="btnChangeFontSize">
<input type="button" value="Highlight Text" id="btnHighlight">
<input type="button" value="Get HTML" id="btnGetHtml">
<input type="button" value="Get Selected Text" id="btnGetSelected">
</div>
<div id="divQuery">Is the current selection:
<input type="button" value="Bold">
<input type="button" value="Italic">
<input type="button" value="Underline">
</div>
<iframe name="richedit" style="height: 500px; width: 1000px" src="blank.htm"></iframe>
<input type="hidden" name="comments" value="">
<input type="submit" value="Submit Form">
</form>
<script type="text/javascript">
(function(){
window.addEventListener("load", function(){
frames["richedit"].document.designMode = "on";
});
var simple = document.getElementById("divSimple");
var complex = document.getElementById("divComplex");
var queryDiv = document.getElementById("divQuery");
document.forms[0].addEventListener("submit", function(event){
event.target.elements["comments"].value = frames["richedit"].document.body.innerHTML;
});
simple.addEventListener("click", function(event){
var target = event.target;
if (target.type == "button"){
frames["richedit"].document.execCommand(target.value.toLowerCase(), false, null);
}
});
complex.addEventListener("click", function(event){
var target = event.target;
switch(target.id){
case "btnGetHtml":
alert(frames["richedit"].document.body.innerHTML);
break;
case "btnCreateLink":
var link = prompt("What link?", "http://www.wrox.com");
if (link){
frames["richedit"].document.execCommand("createlink", false, link);
}
break;
case "btnChangeFontSize":
var size = prompt("What size? (1-7)", "7");
if (size){
frames["richedit"].document.execCommand("fontsize", false, parseInt(size,10));
}
break;
case "btnGetSelected":
if (frames["richedit"].getSelection){
alert(frames["richedit"].getSelection().toString());
} else if (frames["richedit"].document.selection){
alert(frames["richedit"].document.selection.createRange().text);
}
break;
case "btnHighlight":
if (frames["richedit"].getSelection){
var selection = frames["richedit"].getSelection();
var range = selection.getRangeAt(0);
var span = frames["richedit"].document.createElement("span");
span.style.backgroundColor = "yellow";
range.surroundContents(span);
} else if (frames["richedit"].document.selection){
var range = frames["richedit"].document.selection.createRange();
range.pasteHTML("<span style=\"background-color:yellow\">" + range.htmlText + "</span>");
}
break;
}
});
queryDiv.addEventListener("click", function(event){
var target = event.target;
if (target.type == "button"){
alert(frames["richedit"].document.queryCommandState(target.value.toLowerCase(), false, null));
}
});
})();
</script>
</body>
</html>