JS 表单
[1] 表单对象
JavaScript最初的一个应用就是分担服务器处理表单的责任,打破处处依赖服务器的局面。尽管目前的web和JS已经有了长足的发展,但web表单的变化并不明显。由于web表单没有为许多常见任务提供现成的解决方法,很多开发人员不仅会在验证表单时使用javascript,而且还增强了一些标准表单控件的默认行为。
01. 表单属性
在HTML中,表单由form元素表示,而在javascript中,表单对应的则是HTMLFormElement类型,HTMLFormElement继承了HTMLElement,但也有自己独有的属性和方法。
acceptCharset 服务器能够处理的字符集;等价于HTML中的accept-charset特性。
action 接受请求的URL;等价于HTML中的action特性。
enctype 请求的编码类型;等价于HTML中的enctype特性。
<form name="form" action="#"></form> <script> var form = document.form; console.log(form.acceptCharset);//'' console.log(form.action);//"file:///C:/Users/Administrator/Desktop/iframe.html#" console.log(form.enctype);//application/x-www-form-urlencoded </script>
elements 表单中所有控件的集合(HTMLCollection)。
length 表单中控件的数量。
<form name="form" action="#">
<input type="text">
<textarea></textarea>
</form>
<script>
var form = document.form;
console.log(form.elements)//[input,textarea]
console.log(form.length)//2
</script>
method 要发送的HTTP请求类型,通常是"get"或"post";等价于HTML的method特性。
name 表单的名称;等价于HTML的name特性。
target 用于发送请求和接收响应的窗口名称;等价于HTML的target特性。
<form name="form" action="#"></form> <script> var form = document.form; console.log(form.method);//get console.log(form.name);//form console.log(form.target);//'' </script>
02. 表单事件
reset事件 将所有表单域重置为默认值
submit事件 提交表单
<form name="form" action="#">
<input name="test" value="1">
<input type="reset">
<input type="submit">
</form>
<script>
var form = document.form;
form.onreset = function(){
form.test.value = "2";
//若不使用return false阻止默认事件,那么reset将会把form.test的value重新置成1
return false;
}
form.onsubmit = function(){
form.test.value = "3";
}
</script>
03. 表单方法
submit()方法
在JavaScript中,以编程方式调用submit()方法也可以提交表单。而且,这种方式无需表单包含提交按钮,任何时候都可以正常提交表单。
以调用submit()方法提交表单时,不会触发submit事件。
reset()方法
在用户单击重置按钮时,表单会被重置。使用type特性值为"reset"的<input>或<button>都可以创建重置按钮。
[注意] 元素重置时,不再触发元素上的change和input事件。
<input type="reset" value="Reset Form"> <button type="reset">Reset Form</button>
与调用submit()方法不同,调用reset()方法会像单击重置按钮一样触发reset事件。
点击外部提交按钮后,浏览器URL变成file:///C:/inetpub/wwwroot/test.html?test=1#,且没有触发onreset事件,input的value值没有变化。
点击外部重置按钮后,触发reset事件,input的value值变成2。
<form name="form" action="#">
<input name="test" value="1">
</form>
<button id="btn1">外部提交</button>
<button id="btn2">外部重置</button>
<script>
var form = document.form;
form.onreset = function(){
form.test.value = "2";
return false;
}
form.onsubmit = function(){form.test.value = "3";}
btn1.onclick = function(){form.submit();}
btn2.onclick = function(){form.reset();}
</script>
[2] 表单字段
表单字段又叫表单元素,表示表单所包含控件,如<input>、<select>等。
01. 访问
每个表单都有elements属性,该属性是表单中所有元素的集合。这个elements集合是一个有序列表,其中包含着表单中的所有字段,如<input>、<textarea>、<button>和<fieldset>。
[注意] 不包括图片按钮<input type="image">
每个表单字段在elements集合中的顺序,与它们出现在标记中的顺序相同,可以按照位置和name特性来访问它们。
<form action="#">
<input type="text" name="a">
<textarea name="b"></textarea>
<button name="c"></button>
<fieldset name="d"></fieldset>
</form>
<script>
var elements = document.forms[0].elements;
// [input, textarea, button, fieldset, a: input, b: textarea, c: button, d: fieldset]
console.log(elements);
//<input type="text" name="a">
console.log(elements.a);
// <textarea name="b"></textarea>
console.log(elements[1])
console.log(elements[3] === elements.d)//true
</script>
form[name]
除了使用elements元素集合外,还可以使用form[name]来获取表单内的元素控件。
<form name="form" id="myForm"> <input type="text" id="a1" name="a" > </form> <script> var myInput1 = myForm.a; console.log(myInput1.id); // 'a1' </script>
02. 属性
除了<fieldset>元素之外,所有表单字段都拥有相同的一组属性。
disabled 布尔值,表示当前字段是否被禁用 form 指向当前字段所属表单的指针;只读 name 当前字段的名称 readOnly 布尔值,表示当前字段是否只读 tabIndex 表示当前字段的切换(tab)序号 type 当前字段的类型,如"checkbox"、"radio"等等 value 当前字段将被提交给服务器的值 defaultValue 表示该表单元素的初始值
除了form属性之外,可以通过javascript动态修改其他任何属性。
<form action="#">
<input value="123">
</form>
<script>
var input = document.forms[0].elements[0];
console.log(input.disabled);//false
console.log(input.form);//<form action="#"></form>
console.log(input.name);//''
console.log(input.readOnly);//false
console.log(input.tabIndex);//0
console.log(input.type);//text
console.log(input.value);//123
console.log(input.defaultValue);//123
</script>
<form action="#">
<input value="123">
</form>
<button id="btn1">禁用(启用)</button>
<button id="btn2">只读(读写)</button>
<script>
var input = document.forms[0].elements[0];
btn1.onclick = function(){
input.disabled = !input.disabled;
}
btn2.onclick = function(){
input.readOnly = !input.readOnly;
}
</script>
03. 方法
每个表单字段都有两个方法:focus()和blur()。
focus()方法
focus()方法用于将浏览器的焦点设置到表单字段,即激活表单字段,使其可以响应键盘事件。
[注意] 非表单元素设置tabIndex=-1,并设置focus()方法后,也可以获得焦点。
blur()方法
与focus()方法相对的是blur()方法,它的作用是从元素中移走焦点。在调用blur()方法时,并不会把焦点转移到某个特定的元素上;仅仅是将焦点从调用这个方法的元素上面移走而已。
<input id="test" type="text" value="123">
<button id="btn1">input元素获得焦点</button>
<button id="btn2">input元素失去焦点</button>
<script>
btn1.onclick = function(){test.focus();}
btn2.onclick = function(){test.blur();}
</script>
04. 事件
除了支持鼠标、键盘、更改和HTML事件之外,所有表单字段都支持下列3个事件:
focus
当前字段获得焦点时触发。
blur
当前字段失去焦点时触发。
change
对于<input>和<textarea>元素,在它们失去焦点且value值改变时触发;对于<select>元素,在其选项改变时触发。当然,也支持focusin和focusout事件。
<input id="test" type="text" value="123">
<script>
test.onchange = function(){
this.style.backgroundColor = 'pink';
}
</script>
顺序
当一个input元素的值改变并且失去焦点时,blur和change事件的顺序是怎么样的呢?
所有的浏览器的触发顺序都是先change事件,再blur事件。
<input id="test" type="text" value="123">
<script>
test.onblur=test.onchange = function(e){
e = e || event;
this.value += e.type + ';';
}
</script>
[3] 选择文本
表单是最早用来与用户交互的工具,具有丰富的控件和属性。基本上,它们通过各种控件和属性就可以解决大部分问题。但还有一些问题还是需要表单脚本来实现的,如选择文本。
01. select()
select()方法用于选择文本框(指type为text的input元素和textarea元素)中的所有文本,该方法不接受参数,且无返回值。
<input id="text" value="123">
<button id="btn">选择文本</button>
<script>
btn.onclick = function(){
text.select();
}
</script>
02. select事件
与select()方法对应的,是一个select事件。在选择了文本框中的文本时,就会触发select事件。
不过关于什么时间触发select事件有分歧。IE8-浏览器中,只要用鼠标选择了一个字符,未释放鼠标前,就可以触发select事件;而其他浏览器中,要释放鼠标才会触发。
<input id="text" value="123">
<script>
text.onselect = function(){
this.style.backgroundColor = 'pink';
}
</script>
03. 取得选择文本
虽然通过select事件可以知道用户什么时候选择了文本,但仍然不知道用户选择了什么文本。HTML5通过一些扩展方案解决了这个问题,以便更顺利地取得选择的文本。
该规范采取的办法是添加两个属性:selectionStart和selectionEnd。这两个属性中保存的是基于0的数值,表示所选择文本的范围(即文本选区开头和结尾的偏移量)。
function getSelectedText(textbox){
return textbox.value.substring(textbox.selectionStart,textbox.selectionEnd);
}
因为substring()方法基于字符串的偏移量执行操作,所以将selectionStart和selectionEnd直接传给它就可以取得选中的文本。
[注意] IE8-浏览器不支持。
IE8-浏览器有一个document.selection对象,其中保存着用户在整个文档范围内选择的文本信息;也就是说,无法确定用户选择的是页面中哪个部位的文本。不过,在与select事件一起使用的时候,可以假定是用户选择了文本框中的文本,因而触发了该事件。要取得选择的文本,首先必须创建一个范围,然后再将文本从其中提取出来。
function getSelectedText(textbox){
if (typeof textbox.selectionStart == "number"){
return textbox.value.substring(textbox.selectionStart,textbox.selectionEnd);
}else if(document.selection){
return document.selection.createRange().text;
}
}
04. secSelectionRange()
setSelectionRange()方法用于选择文本框中的部分文本,这个方法接收两个参数:要选择的第一个字符的索引和要选择的最后一个字符之后的字符的索引(类似于substring()方法的两个参数)。
firefox浏览器使用该方法,可以选择文本,但却没有获取焦点;而其他浏览器使用该方法可以自动获取焦点。
[注意] IE8-浏览器不支持。
<input id="text" value="1234567890">
<button id="btn">选择前3个字符</button>
<script>
btn.onclick = function(){
text.focus();
text.setSelectionRange(0,3)
}
</script>
IE8-浏览器支持使用范围选择部分文本。要选择文本框中的部分文本,必须首先使用createTextRange()方法创建一个范围,并将其放在恰当的位置上。然后,再使用moveStart()和moveEnd()这两个范围方法将范围移动到位。
不过,在调用这两个方法以前,还必须使用collapse()将范围折叠到文本框的开始位置。此时,moveStart()将范围的起点和终点移动到了相同的位置,只要再给moveEnd()传入要选择的字符总数即可。最后一步,就是使用范围的select()方法选择文本。
<input id="text" value="1234567890">
<button id="btn">选择前3个字符</button>
<script>
btn.onclick = function(){
var range = text.createTextRange();
range.collapse(true);
range.moveStart('character',0);
range.moveEnd('character',3);
range.select();
}
</script>
兼容
function selectText(textbox, startIndex, stopIndex){
if(textbox.setSelectionRange){
textbox.setSelectionRange(startIndex, stopIndex);
} else if(textbox.createTextRange){
var range = textbox.createTextRange();
range.collapse(true);
range.moveStart('character',startIndex);
range.moveEnd('character',stopIndex);
range.select();
}
textbox.focus();
}
[4] 选择框脚本
选择框是通过<select>和<option>元素创建的,又称为下拉列表框。为了方便与这个控件交互,除了所有表单字段共有的属性和方法外,JavaScript还提供了一些属性和方法。
01. select
select元素用来定义一个下拉列表,包含任意数量的option和optgroup元素。select元素包含以下javascript属性:
autofocus 是否在页面加载后文本区域自动获得焦点(IE9-浏览器不支持)
disabled 是否禁用该下拉列表
form 表示文本区域所属的一个或多个表单
multiple 是否可选择多个选项
name 表示下拉列表的名称
size 表示下拉列表中可见选项的数目
options 表示所包含的options的数组
selectedOptions 表示所选择的options的数组(IE浏览器不支持)
selectedIndex 表示所选择的第一个option的索引值
<form id="myForm">
<select name="select" id="mySelect">
<option value="1" >1</option>
<option value="2" selected>2</option>
</select>
</form>
<script>
var mySelect = document.getElementById('mySelect');
console.log(mySelect.form);//<form id="myForm">
console.log(mySelect.name);//'select'
console.log(mySelect.value);//2
console.log(mySelect.options);//[option, option, selectedIndex: 1]
console.log(mySelect.selectedOptions);//[option]
console.log(mySelect.selectedIndex);//1
console.log(mySelect.multiple);//false
console.log(mySelect.size);//0
console.log(mySelect.autofocus);//false
console.log(mySelect.disabled);//false
</script>
以下为重要属性详解:
multiple:表示是否允许多项选择。
<select name="test" id="test">
<option>1</option>
<option>2</option>
<option>3</option>
</select>
<button id="btn">是否多选</button>
<script>
btn.onclick = function(){
test.multiple = !test.multiple;
}
</script>
type
选择框的type属性有两种,一种是'select-one',表示单选;另一种是'select-multiple',表示多选。
<select name="test" id="test">
<option>1</option>
<option>2</option>
<option>3</option>
</select>
<button id="btn">是否多选</button>
<div id="result"></div>
<script>
btn.onclick = function(){
test.multiple = !test.multiple;
result.innerHTML = test.type;
}
</script>
value
选择框的value属性由当前选中项决定。
1、如果没有选中的项,则选择框的value属性保存空字符串。
2、如果有一个选中项,而且该项的value特性已经在HTML中指定,则选择框的value属性等于选中项的value特性。即使value特性的值是空字符串,也同样遵循此条规则。
3、如果有一个选中项,但该项的value特性在HTML中未指定,则选择框的value属性等于该项的文本。
4、如果有多个选中项,则选择框的value属性将依据前两条规则取得第一个选中项的值。
[注意] IE8-浏览器只支持value属性的值,不支持选择的文本值
<select name="test" id="test">
<option value="a">1</option>
<option value="b">2</option>
<option>3</option>
</select>
<button id="btn1">是否多选</button>
<button id="btn2">获取value值</button>
<div id="result"></div>
<script>
btn1.onclick = function(){
test.multiple = !test.multiple;
}
btn2.onclick = function(){
result.innerHTML = test.value;
}
</script>
selectedIndex
selectedIndex属性返回基于0的选中项的索引,如果没有选中项,则值为-1。对于支持多选的控件,只保存选中项中第一项的索引。
<select name="test" id="test">
<option value="a">1</option>
<option value="b">2</option>
<option>3</option>
</select>
<button id="btn1">是否多选</button>
<button id="btn2">获取索引</button>
<div id="result"></div>
<script>
btn1.onclick = function(){
test.multiple = !test.multiple;
}
btn2.onclick = function(){
result.innerHTML = test.selectedIndex;
}
</script>
size:表示选择框的可见行数。
<select name="test" id="test">
<option value="a">1</option>
<option value="b">2</option>
<option>3</option>
</select>
<button id="btn1">可见1行</button>
<button id="btn2">可见2行</button>
<button id="btn3">可见3行</button>
<div id="result"></div>
<script>
btn1.onclick = function(){
test.size = 1;
}
btn2.onclick = function(){
test.size = 2;
}
btn3.onclick = function(){
test.size = 3;
}
</script>
option:表示控件中所有的<option>元素。
<select name="test" id="test">
<option value="a">1</option>
<option value="b">2</option>
<option>3</option>
</select>
<script>
// [option, option, option, selectedIndex: 0]
console.log(test.options)
</script>
02. option
在DOM中,每个<option>元素都有一个HTMLOptionElement对象表示。为便于访问数据, HTMLOptionElement对象也定义了一些属性。
[注意] IE浏览器不支持为<option>元素设置display:none。
index
index属性表示当前选项在options集合中的索引。
label
label属性表示当前选项的标签。
[注意] IE9-浏览器不支持。
selected
selected属性表示当前选项是否被选中。将这个属性设置为true可以选中当前选项。
text
text属性表示选项的文本。
value
value属性表示选项的值。
[注意] 在未指定value特性的情况下,IE8会返回空字符串;而其他浏览器返回text属性的值。
<select name="test" id="test">
<option value="a" selected>1</option>
<option value="b">2</option>
<option>3</option>
</select>
<script>
var option = test.options[0];
console.log(option.index);//0
console.log(option.label);//1,IE9-浏览器返回空字符串''
console.log(option.selected);//true
console.log(option.text);//1
console.log(option.value);//a
</script>
03. 添加选项
a. 添加选项可以使用DOM的appendChild()或insertBefore()方法。
<select name="test" id="test">
<option>1</option>
<option>3</option>
</select>
<button id="btn">增加选项2</button>
<script>
btn.onclick = function(){
var newOption = document.createElement('option');
newOption.innerHTML = 2;
test.insertBefore(newOption,test.options[1]);
}
</script>
b. 可以使用选择框的add()方法,add(newoption,reloption)方法向控件中插入新<option>元素,其位置在相关项(reloption)之前。使用Option构造函数来创建新选项,接受两个参数:文本(text)和值(value),第二个参数可选。
<select name="test" id="test">
<option>1</option>
<option>3</option>
</select>
<button id="btn">增加选项2</button>
<script>
btn.onclick = function(){
var newOption = new Option('2');
test.add(newOption,1);
}
</script>
04. 移除选项
与添加选项类似,移除选项的方式也有很多种。
a. 使用DOM的removeChild()方法
<select name="test" id="test">
<option>1</option>
<option>2</option>
<option>3</option>
</select>
<button id="btn">移除选项2</button>
<script>
btn.onclick = function(){
test.removeChild(test.options[1]);
}
</script>
b. 使用选择框的remove()方法。这个方法接受一个参数,即要移除选项的索引。
[注意] 使用该方法的好处是,若不存在被移除选项的索引,不会报错,只是静默失败。
<select name="test" id="test">
<option>1</option>
<option>2</option>
<option>3</option>
</select>
<button id="btn">移除选项2</button>
<script>
btn.onclick = function(){
test.remove(1);
}
</script>
c. 将相应选项设置为null
[注意] 该方法同样不会报错。
<select name="test" id="test">
<option>1</option>
<option>2</option>
<option>3</option>
</select>
<button id="btn">移除选项2</button>
<script>
btn.onclick = function(){
test.options[1] = null;
}
</script>
[5] 富文本编辑
一说起富文本,人们第一印象就是像使用word一样,在网页上操作文档。实际上差不多就是这样。富文本编辑,又称为WYSIWYG (What You See Is What You Get所见即所得),指在网页中编辑富文本内容。
01. 方式
有两种编辑富文本的方式,一种是使用iframe元素,另一种是使用contenteditable属性。
a. iframe
在页面中嵌入一个包含空HTML页面的iframe。通过设置designMode属性,这个空白的HTML页面可以被编辑,而编辑对象则是该页面<body>元素的HTML代码。
designMode属性有两个可能的值:"off"(默认值)和"on"。在设置为"on"时,整个文档都会变得可以编辑。
只有在页面完全加载之后才能设置designMode属性。因此,在包含页面中,需要使用onload事件处理程序。
[注意] 此方法必须在服务器端才能执行,否则会提示跨域安全提示。
<iframe name="wysiwyg" src="wysiwyg.html" style="height: 100px;width: 100px;"></iframe>
<script>
window.onload = function(){
frames['wysiwyg'].document.designMode = 'on';
}
</script>
b. contenteditable
把contenteditable属性应用给页面中的任何元素,然后用户立即就可以编辑该元素。
设置document.designMode='on'时,页面的任意位置都可以编辑;使用contenteditable='true'则只对具体元素和其包含的元素起作用。
[注意] 一定要区分contenteditable和contentEditable。contenteditable是元素特性,而contentEditable是对象属性。
<div id="wysiwyg" style="height: 100px;width: 100px;border:1px solid black"></div>
<button id="btn1">打开富文本编辑</button>
<button id="btn2">关闭富文本编辑</button>
<script>
btn1.onclick = function(){wysiwyg.contentEditable = true;}
btn2.onclick = function(){wysiwyg.contentEditable = false;}
</script>
02. 命令
与富文本编辑器交互的主要方式,就是使用document.execCommand()。这个方法可以对文档执行预定义的命令,而且可以应用大多数格式。
document.execCommand(String aCommandName, Boolean aShowDefaultUI, String aValueArgument)方法需要传递3个参数。
aCommandName表示要执行的命令名称,不可省略。
aShowDefaultUI表示是否展示用户界面,默认为false,可省略。
aValueArgument表示额外参数值,默认为null,可省略。
[注意]为了确保浏览器兼容性,第二个参数应始终设置为false,因为firefox在该参数为true时抛出错误。
a. 段落格式
居中 document.execCommand('justifyCenter');
左对齐 document.execCommand('justifyLeft');
右对齐 document.execCommand('justifyRight');
添加缩进 document.execCommand('indent');
去掉缩进 document.execCommand('outdent');
<div id="wysiwyg" style="height: 100px;width: 300px;border:1px solid black" contenteditable>测试内容</div>
<button data-name="justifyCenter">居中</button>
<button data-name="justifyLeft">左对齐</button>
<button data-name="justifyRight">右对齐</button>
<button data-name="indent">添加缩进</button>
<button data-name="outdent">去掉缩进</button>
<script>
var btns = document.getElementsByTagName('button');
for(var i = 0; i < btns.length; i++){
btns[i].onclick = function(){
document.execCommand(this.getAttribute('data-name'));
}
}
</script>
b. 文本格式
字体类型 document.execCommand('fontname',false,sFontName)
字体大小 document.execCommand('fontsize',false,sFontSize)
字体颜色 document.execCommand('forecolor',false,sFontColor)
背景色 document.execCommand('backColor',false,sBackColor)
加粗 document.execCommand('bold');
斜体 document.execCommand('italic');
下划线 document.execCommand('underline');
<div id="wysiwyg" style="height: 100px;width: 300px;border:1px solid black" contenteditable>测试内容</div>
<button data-name="fontname" data-value="宋体">宋体</button>
<button data-name="fontsize" data-value="5">大字体</button>
<button data-name="forecolor" data-value="red">红色字体</button>
<button data-name="backColor" data-value="lightgreen">浅绿背景</button>
<button data-name="bold">加粗</button>
<button data-name="italic">斜体</button>
<button data-name="underline">下划线</button>
<script>
var btns = document.getElementsByTagName('button');
for(var i = 0; i < btns.length; i++){
btns[i].onclick = function(){
document.execCommand(this.getAttribute('data-name'),false,this.getAttribute('data-value'));
}
}
</script>
c. 编辑
复制 document.execCommand('copy');
剪切 document.execCommand('cut');
粘贴 document.execCommand('paste');(经测试无效)
全选 document.execCommand('selectAll');
删除 document.execCommand('delete');
后删除 document.execCommand('forwarddelete');
清空格式 document.execCommand('removeFormat');
前进一步 document.execCommand('redo');
后退一步 document.execCommand('undo');
打印 document.execCommand('print');(对firefox无效)
<div id="wysiwyg" style="height: 100px;width: 300px;border:1px solid black" contenteditable>测试内容</div>
<button data-name="copy">复制</button>
<button data-name="cut">剪切</button>
<button data-name="paste">粘贴</button>
<button data-name="selectAll">全选</button>
<button data-name="delete">删除</button>
<button data-name="forwarddelete">后删除</button>
<button data-name="removeFormat">清空格式</button>
<button data-name="redo">前进一步</button>
<button data-name="undo">后退一步</button>
<button data-name="print">打印</button>
<script>
var btns = document.getElementsByTagName('button');
for(var i = 0; i < btns.length; i++){
btns[i].onclick = function(){
document.execCommand(this.getAttribute('data-name'));
}
}
</script>
d. 插入
插入标签 document.execCommand('formatblock',false,elementName);
插入<hr> document.execCommand('inserthorizontalrule');
插入<ol> document.execCommand('insertorderedlist');
插入<ul> document.execCommand('insertunorderedlist');
插入<p> document.execCommand('insertparagraph');
插入图像 document.execCommand('insertimage',false,URL);
增加链接 document.execCommand('createlink',false,URL);
删除链接 document.execCommand('unlink');
<div id="wysiwyg" style="height: 100px;width: 300px;border:1px solid black;overflow:auto" contenteditable>测试内容</div>
<button data-name="formatblock" data-value="div">插入div</button>
<button data-name="inserthorizontalrule">插入hr</button>
<button data-name="insertorderedlist">插入ol</button>
<button data-name="insertunorderedlist">插入ul</button>
<button data-name="insertparagraph">插入p</button>
<button data-name="insertimage" data-value="http://files.cnblogs.com/files/xiaohuochai/zan.gif">插入图像</button>
<button data-name="createlink" data-value="www.cnblogs.com/xiaohuochai">增加链接</button>
<button data-name="unlink">删除链接</button>
<script>
var btns = document.getElementsByTagName('button');
for(var i = 0; i < btns.length; i++){
btns[i].onclick = function(){
document.execCommand(this.getAttribute('data-name'),false,this.getAttribute('data-value'));
}
}
</script>
03. 选区
getSelection()方法
在富文本编辑器中,使用getSelection()方法,可以确定实际选择的文本。这个方法是window对象和document对象的属性,调用它会返回一个表示当前选择文本的Selection对象。每个Selection对象都有下列属性:
[注意] IE8-浏览器不支持。
anchorNode:选区起点所在的节点 anchorOffset:在到达选区起点位置之前跳过的anchorNode中的字符数量 focusNode:选区终点所在的节点 focusOffset:focusNode中包含在选区之内的字符数量 isCollapsed:布尔值,表示选区的起点和终点是否重合 rangeCount:选区中包含的DOM范围的数量
Selection对象的这些属性并没有包含多少有用的信息。好在,该对象的下列方法提供了更多信息,并且支持对选区的操作。
addRange(range):将指定的DOM范围添加到选区中
collapse(node,offset):将选区折叠到指定节点中的相应的文本偏移位置
collapseToEnd():将选区折叠到终点位置
collapseToStart():将选区折叠到起点位置
containsNode(node):确定指定的节点是否包含在选区中
deleteFromDocument():从文档中删除选区中的文本,与document.execCommand("delete",false,null)命令的结果相同
extend(node,offset):通过将focusNode和focusOffset移动到指定的值来扩展选区
getRangeAt(index):返回索引对应的选区中的DOM范围
removeAllRanges():从选区中移除所有DOM范围。实际上,这样会移除选区,因为选区中至少要有一个范围
reomveRange(range):从选区中移除指定的DOM范围
selectAllChildren(node):清除选区并选择指定节点的所有子节点
toString():返回选区所包含的文本内容
Selection对象的这些方法都极为实用,它们利用了DOM范围来管理选区。由于可以直接操作选择文本的DOM表现,因此访问DOM范围与使用execCommand()相比,能够对富文本编辑器进行更加细化的控制。如:
var selection = document.getSelection();
//取得选择的文本
var selectionText = selection.toString();
//取得代表选区的范围
var range = selection.getRangeAt(0);
//突出显示选择的文本
var span = document.createElement("span");
span.style.backgroundColor = "yellow";
range.surroundContents(span);
以上代码会为富文本编辑器中被选择的文本添加黄色的背景。这里使用了默认选区中的DOM范围,通过surroundContents()方法将选区添加到了带有黄色背景的<span>元素中。
HTML5将getSelection()方法纳入了标准,IE8-浏览器不支持DOM范围,但可以通过它支持的selection对象操作选择的文本。IE中的selection对象是document的属性,要取得富文本编辑器中选择的文本,首先必须创建一个文本范围,然后再像下面这样访问其text属性。
var range = document.selection.createRange(); var selectedText = range.text;
虽然使用IE的文本范围来执行HTML操作并不像使用DOM范围那么可靠,但也不失为一种有效的途径。要像前面使用DOM范围那样实现相同的文本高亮效果,可以组合使用htmlText属性和pasteHTML()方法。
var range = document.selection.createRange();
range.pasteHTML("<span style=\"background-color:yellow\">" + range.htmlText+"</span>");
以上代码通过htmlText取得了当前选区中的HTML,然后将其放在了一对<span>标签中,最后又使用pasteHTML()将结果重新插入到了选区中。
04. 表单提交
因为富文本编辑不是使用表单控件实现的,因此富文本编辑器中的HTML不会被自动提交给服务器,而需要手工来提取并提交HTML。为此,通常可以添加一个隐藏的表单字段,让它的值等于从iframe或使用contenteditable属性的元素中提取出的HTML。具体来说,就是在提交表单之前提取出HTML,并将其插入到隐藏的字段中。下面就是通过表单的onsubmit事件处理程序实现上述操作的代码。
form.onsubmit = function(e){
e = e || event;
var target = e.target || e.srcElement;
target.elements["comments"].value = frames["richedit"].document.body.innerHTML;
}
在此,通过文档主体的innerHTML属性取得了iframe中的HTML,然后将其插入到了名为"comments"的表单字段中。这样可以确保恰好在提交表单之前填充"comments"字段。如果在代码中通过submit()来手工提交表单,那么一定不要忘记事先执行上面的操作。对于contenteditable元素,也可以执行类似操作。
form.onsubmit = function(e){
e = e || event;
var target = e.target || e.srcElement;
target.elements["comments"].value = document.getElementById('wysiwyg').innerHTML;
}
// 推荐
实现一个富文本编辑器,看似容易,但实际上是一个大工程。
常见在线富文本编辑器:
表单操作
表单为页面的主要组成部分,其中包含许多的表单控件。用户通过控件提供数据并提交给服务器,服务器则做出相应的处理。而编写一个正常工作的表单需要三个部分:
- 构建表单
- 服务器处理(提供接受数据接口)
- 配置表单
[1] 构建表单
<form>
<p><label>姓名:<input></label></p>
<p><label>电话:<input type="tel"></label></p>
<p><label>邮箱:<input type="email"></label></p>
<fieldset>
<legend> 披萨大小 </legend>
<label><input type="radio" name="size"> 小 </label>
<label><input type="radio" name="size"> 中 </label>
<label><input type="radio" name="size"> 大 </label>
</fieldset>
<fieldset>
<legend> 披萨配料 </legend>
<label><input type="checkbox"> 熏肉 </input></label>
<label><input type="checkbox"> 奶酪 </input></label>
<label><input type="checkbox"> 洋葱 </input></label>
<label><input type="checkbox"> 蘑菇 </input></label>
</fieldset>
<p><label>配送时间:<input type="time" min="11:00" max="2100" step="900"></label></p>
<p><button>提交订单</button></p>
</form>
[2] 服务器处理
提供接口地址(例如,https://pizza.example.com/order,数据格式(application/x-www-form-urlencoded),还是接受的参数信息(custname、custtel、custemail、size、topping、delivery)。
数据命名需在表单控件中注明。
[3] 配置表单
<form action="https://pizza.example.com/order" method="post" enctype="application/x-www-form-urlencoded">
<p><label>姓名:<input name="custname"></label></p>
<p><label>电话:<input type="tel" name="custtel"></label></p>
<p><label>邮箱:<input type="email" name="custemail"></label></p>
<fieldset>
<legend> 披萨大小 </legend>
<label><input type="radio" name="size" value="small"> 小 </label>
<label><input type="radio" name="size" value="medium"> 中 </label>
<label><input type="radio" name="size" value="large"> 大 </label>
</fieldset>
<fieldset>
<legend> 披萨配料 </legend>
<label><input type="checkbox"> 熏肉 </input></label>
<label><input type="checkbox"> 奶酪 </input></label>
<label><input type="checkbox"> 洋葱 </input></label>
<label><input type="checkbox"> 蘑菇 </input></label>
</fieldset>
<p><label>配送时间:<input type="time" min="11:00" max="2100" step="900"></label></p>
<p><button>提交订单</button></p>
</form>
用户所有提交的信息需在提交服务器前对其进行验证从而提高用户体验。
NOTE:表单验证 使用 require 来强制用户填写相应的信息。
内容
元素
form 元素
form 元素为构建表单中最重要的元素。
<form novalidate name="pizza" target="abc" method="post" autocomplete="off" accept-charset="utf-8" action="http://pizza.example.com/order" enctype="application/x-www-form-urlencoded">
其对应的信息则可以视为
| 字段 | 值 |
|---|---|
| noValidate | true |
| target | abc |
| method | post |
| acceptCharset | utf-8 |
| action | http://pizza.example.com/order |
| enctype | application/x-www-form-urlencoded |
| name | pizza |
| autocomplete | off |
NOTE:前六项为表单提交相关的信息。
属性
name属性:可以用于获取表单节点元素。
var pizzaForm = document.forms.pizza;
autocomplete属性:有两个值on与off,在设置为on时,可以自动对输入框进行补全(之前提交过的输入值,下图左)。
NOTE:在已经设置 autocomplete="off" 时依然出现提示框,大多数情况为浏览器设置的自动补全(可以强制关闭,需要时请搜索对应的解决方案)。
-
elements属性:为一个动态节点集合(更具 DOM 的变化进行变化),其用于归结该表单的子孙表单控件(除图标按钮外<input type="image>"):- button
- fieldset
- input
- keygen
- object
- output
- select
- textarea
此外还有归属于该表单的空间(依旧图片按键除外)代码如下所示。
<form id="a">
</form>
<label><input name="null" form="a"></label>
length属性:等价于elements.length来用于描述表单内节点集合的个数。
选取表单空间元素
<form name="test">
<input name="a">
<input name="b">
</form>
选取 name="a" 的控件可以使用下面的方法:
testForm.elements[0];
testForm.elements['a'];
// 操作 Form 表单的属性
testForm[0];
testForm['a'];
-
form[name]通过名称作为索引时有如下特点:- 返回
id或者name为指定名称的表单空间(图标按键除外) - 如果结果为空,则返回
id为指定名称的img元素(入下面代码所示) - 如果有多个同名元素,则返回的元素为动态节点集合
- 一旦用指定名称取过改元素,之后则不论该元素的
id或者name如何变化,只有节点存在则均可使用原名称来继续获取改节点。
- 返回
无指定名称索引范例
<form name="test">
<img id="a" src="sample.png">
</form>
testForm['a']; // 取得的便是 id 为 a 的图片元素
更新名称,依然可以获取节点范例
<form name="test">
<input name="a">
</form>
// 第一步
testForm['a'];
// 或者
testForm.elements['a'];
// 第二步
testForm['a'].name = 'b';
form 接口
form 元素也提供了一些接口便于对其进行操作 reset() submit() checkValidity()。
可以重置(reset)的元素有下面的几种:
- input
- keygen
- output
- select
- textarea
当触发表单 reset 事件时可使用阻止该事件的默认行为来取消重置。而且元素重置时将不会再次触发元素上的 change 与 input 事件。
label 元素
<label for="textId" form="formId">
| 字段 | 值 |
|---|---|
| htmlFor | textId |
| control | HTMLElement#textId |
| form | HTMLFormElement#formId |
-
htmlFor属性:用于关联表单控件的激活行为(可使点击label与点击表单控件的行为一致),可关联的元素有下列(hidden除外):- button
- input
- keygen
- meter
- output
- progress
- select
- textarea
自定义文件提交控件样式
control属性:如果指定了for属性则指定该for属性对于id的可关联元素。如果没有指定for属性则为第一个可关联的子孙元素。
可关联的元素 (只读属性不可在程序中直接赋值修改)
- button
- fieldset
- input
- keygen
- label
- object
- output
- select
- textarea
- form 属性:修改关联元素所归属的表单则可以修改元素的 form 属性为带关联表单Id(元素中对于的for属性也应该做对应的修改)。//这里有一点小问题,更改form属性之后label并不能自动绑定到新表单对应的元素上
label.setAttribute('form', 'newFormId');
input 元素
<input type="text">
type属性:可用于控制控件的外观以及数据类型(默认为text),在不同的浏览器不同数据类型有不同的展示效果。
本地图片预览示例
所需技术点(HTMLInputElement属性)
- onchange
- accept
- multiple
- files
<input type="file" accept="image/*" multiple>
file.addEventListener(
'change', function(event){
var files = Array.prototype.slice.call(
event.target.files, 0
);
files.forEach(function(item){
files2dataurl(item,function(url){
var image = new Image();
parent.appendChild(image);
image.src = url;
});
});
}
);
function file2dataurl(file, callback) {
if (!window.FileReader) {
throw 'Browser not support File API !';
}
var reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = function(event) {
callback(event.target.result);
};
}
NOTE:accept 所支持的格式有 audio/* video/* image/* 以及不带;的 MINE Type 类型和 . 开头的文件名后缀的文件。多个文件类型可以使用,分隔。
select 元素
指定选项列表中选择需要的选项。
主要的三个子标签 select、optgroup(用于选项分组)、option。
-
select具有的属性和方法如下:- name
- value
- multiple
- options(动态节点集合)
- selectedOptions(动态节点集合)
- selectedIndex
- add(element[, before])(无指定参照物则添加至最末端)
- remove([index])
-
optgroup所具有的属性和方法:- disabled (分组选项不可选)
- label(分组说明)
-
option所具有的属性和方法:- disabled
- label(描述信息)
- value(提交表单时的数据信息)
- text(用户看到的文字)
- index
- selected
- defaultSelected
选项操作
创建选项
document.createElement('option')
// 或者
new Option([text[, value[, defaultSelected[, selected]]]])
添加选项
var option = new Option('sample');
opt.insertAdjacentElement(option, '参照元素');
// 或者
select.add(option, '参照元素')
删除选项
opt.parentNode.removeChild(option);
// 或者使用它的索引将其删除
select.remove(2);
级联下列选择器
所需知识点:
- onchange
- remove
- add
<form name="course">
<select name="chapter">
<option>Select0</option>
</select>
<select name="section">
<option>Select1</option>
</select>
</form>
var chapters = {
{text: 1, value: 1},
{text: 2, value: 2}
};
var sections = {
1: [{
text:1.1, value: 1.1
}, {
text:1.2, value: 1.2
}],
2:[{
text:2.1, value:2.1;
}]
};
function fillSelect(select, list) {
for(var i = select.length; i > 0; i--) {
select.remove(i);
}
list.forEach(function(data){
var option = new Option(data.text, data.value);
select.add(option);
})
}
fileSelect(chapterSelect, chapters);
chapterSelect.addEventListener(
'change', function(event) {
var value = event.target.value,
list = sections[value] || [];
fillSelect(sectionSelect, list);
}
);
textarea 元素
textarea 具有的属性和方法如下:
- name
- value (用户输入信息)
- select() (全选当前输入的内容)
- selectionStart (选中的内容的起始位置,无选中时返回当前光标所在位置)
- selectionEnd (选中内容结束位置,无选中时返回光标位置)
- selectionDirection (选取方向
forwardbackward) - setSelectionRange(start, end[, direction]) (使用程序选中内容)
- setRangeText(replacement[, start, end, [mode]]) (设置内容范围)
selection
表示选择区域,对于 input 元素同样有效。
selectionDirection 主要是用于在使用 SHIFT 键与方向键组合选取时的选取方向。设置为 forward 时选取移动的方向为 selectionEnd 设置为 backward 时移动方向为 selectionStart。
@输入提示示例
所需知识点:
- oninput
- selectionStart
- setRangeText
textarea.addEventListener(
'input', function(event) {
var target = event.target,
cursor = target.selectionStart;
if(target.value.charAt(cursor-1) === '@') {
doShowAtList(functi=on(name){
var end = cursor + name.length;
target.setRangeText(
name, cursor, end, 'end'
);
});
}
}
);
其他元素
- fieldset
- button
- keygen
- output
- progress
- meter
验证
可以被验证的元素如下所示:】
- button
- input
- select
- textarea
以下情况不可以做验证
- input 元素在类型是 hidden, reset, button 时
- button 元素在类型为 reset, button 时
- input 与 textarea 当属性为 readonly 时
- 当元素为 datalist 的子孙节点时
- 当元素被禁用时 disabled 的状态
属性
验证涉及到以下的以下属性,在每一个可以验证的元素上均可以调用对于的属性或通过接口进行操作:
- willValidate (表明此元素在表单提交时是否会被验证)
- checkValidity() (用于验证元素,返回 true 当验证通过,或者触发 invalid 事件)
- validity (存储验证结果)
- validationMessage (显示验证异常信息)
- setCustomValidity(message) (自定义验证错误信息)
自定义异常范例
涉及到的知识点:
- oninvalid
- setCustomValidity
<form action="./api" method="post">
<label>Name: <input name="username" required></label>
<button>submit</button>
</form>
input.addEventListener(
'invalid', function(event){
var target = event.target;
if (target.validity.valueMissing) {
target.setCustomValidity('Name is missing');
}
}
)
禁止验证范例
使用 form 中 novalidate 属性来禁止表单提交的验证。
<form action="./api" method="post" novalidate>
<label>Mobile: <input name="mobile" type="number"></label>
<button>submit</button>
</form>
提交
隐式提交
在操作过程中通过控件的操作来提交表单(敲击回车来提交表单),其需要满足以下的条件:
- 表单有非禁用的提交按键
- 没有提交按键时,不超过一个类型为
textsearchurlemailpassworddatetimenumber的input元素
提交过程细节
提交过程分为两个阶段,第一个阶段是更具表单 enctype 指定的值构建要提交的数据,第二个阶段是使用指定的方法(method)发送数据到 action 指定的目标。
构建提交数据,从可提交元素中提取数据组成指定数据结构过程(可提交元素有 button input keygen object select textarea)
编码方式(enctype)所支持的形式:
- application/x-www-form-urlencoded (默认,数据格式为
&分隔的键值对) - multipart/form-data (IFC 2388 字节流形式,例如文件上传所使用的数据编码形式)
- text/plain (回车换行符分隔的键值对)
特殊案例一
当一个表单元素 name="isindex" 并且 type="text" 而且满足如下要求时:
- 编码格式为 application/x-www-form-urlencoded
- 作为表单的第一个元素
则提交时只发送 value 值,不包含 name。
<form action="./api" method="post">
<input name="isindex">
<input name="a">
<button>submit</button>
</form>
特殊案例二
当 name="_charset_" 并且类型为 hidden 时,而且满足如下要求时:
- 没有设置
value值
则提交时 value 自动使用当前提交的字符集填充。
submit 接口
form.submit() 可以通过调用接口submit()直接提交表单,在提交表单时均会触发一个 onsubmit 表单提交事件,在这个事件中 women 可以做下面的事件:
- 提交前的数据验证
- 阻止事件的默认行为来取消表单的提交(例如当验证失败时)
form.addEventListener(
'submit', function(event) {
var notValid = false;
var elements = event.target.elements;
// 自定义验证
if (notValid) {
// 取消提交
event.preventDefault();
}
}
)
无刷新表单提交范例
常用的方式是通过 AJAX 进行实现,这里我们使用 iframe 来做中介代理实现。
所需知识点:
- form
- target
- iframe
<iframe name="targetFrame" class="f-hidden" style="display:none" id="result">
<form action="./api" method="post" target="targetFrame">
<input name="isindex">
<input name="a">
<button>submit</button>
</form>
var frame = document.getElementById('result');
frame.addEventListener(
'load', function(event) {
try {
var result = JSON.parse(
frame.contentWindow.document.body.textContent
);
// 还原登陆按钮状态
disabledSubmit(false);
// 识别登陆结果
if (result.code === 200) {
showMessage('j-suc', 'success');
form.reset();
}
} catch(ex) {
// 忽略操作
}
}
)
表单应用
首先需要知道服务器端登陆接口的相关信息,如下所示:
| 描述 | 数据信息 |
|---|---|
| 请求地址 | /api/login |
| 请求参数 | telephone 手机号码; password 密码 MD5 加密 |
| 返回结果 | code 请求状态; result 请求数据结果 |
var form = document.forms.loginForm;
var message = document.getElementById('message');
// 通用逻辑封装
function showMessage(class, message) {
if(!class) {
message.innerHTML = "";
message.classList.remove('j-suc');
message.classList.remove('j-err');
} else {
message.innerHTML = message;
message.classList.add(class);
}
}
function invalidInput (node, message) {
showMessage('j-err', message);
node.classList.add('j-err');
node.focus();
}
function clearInvalid(node){
showMessage();
node.classList.remove('j-err');
}
function disabledSubmit(disabled) {
form.loginBtn.disabled = !!disabled;
var method = !disabled ? 'remove' : 'add';
form.loginBtn.classList[method]('j-disabled');
}
// 验证手机号码(系统自带方法)
form.telephone.addEventListener(
'invalid', function(event) {
event.preventDefault();
invalidInput(form.telephone, 'invalid mobile number');
}
);
// 验证密码
form.addEventListener(
'submit', function(event) {
var input = form.password;
var password = input.value;
errorMessage = '';
if (password.length < 6) {
errorMessage = 'password less than 6 char';
} else if (!/\d./test(password) || !/[a-z]/i.test(password)) {
errorMessage = 'password must contains number and letter'
}
if (!!errorMessage) {
event.preventDefault();
invalidInput(input, errorMessage);
return;
}
// 提交表单代码
// ...
}
);
// 提交表单
form.addEventListener(
'submit', function(event){
input.value = md5(password);
disabledSubmit(true);
}
);
// 状态恢复
form.addEventListener(
'focus', function(event) {
// 错误还原
clearInvalid(event.target);
// 还原登陆按钮状态
disabledSubmit(false);
}
)