JS高程:读书摘要(十二)表单

一、基础

JavaScript 中,表单有它自己独有的属性和方法。

  • acceptCharset:服务器能够处理的字符集;
  • action:接受请求的URL;
  • elements:表单中所有控件的集合(HTMLCollection)。
  • enctype:请求的编码类型;
  • length:表单中控件的数量。
  • method:要发送的 HTTP 请求类型,通常是"get""post";
  • name:表单的名称;。
  • reset():将所有表单域重置为默认值。
  • submit():提交表单。
  • target:用于发送请求和接收响应的窗口名称

通过document.forms 可以取得页面中所有的表单。在这个集合中,可以通过数值索引或name值来取得特定的表单,也可以通过id、类名来获取。

提交表单
<!-- 通用提交按钮 -->
<input type="submit" value="Submit Form">
<!-- 自定义提交按钮 -->
<button type="submit">Submit Form</button>
<!-- 图像按钮 --> 
<input type="image" src="graphic.gif">

只要表单中存在上面列出的任何一种按钮,那么在相应表单控件拥有焦点的情况下,按回车键就可以提交该表单(textarea除外,在文本区中回车是换行),如果没有提交按钮,按回车键则不会提交表单。以这种方式提交表单时,浏览器会在将请求发送给服务器之前触发onsubmit事件。这样,我们就有机会验证表单数据,并据以决定是否允许表单提交。调用prevetnDefault()方法阻止表单提交。一般来说,在表单数据无效而不能发送给服务器时,可以使用这一技术。

JavaScript 中,以编程方式调用submit()方法也可以提交表单。form.submit();,在以调用submit()方法的形式提交表单时,不会触发onsubmit 事件,因此要记得在调用此方法之前先验证表单数据。

为避免用户多次点击按钮重复提交表单,可以在第一次提交表单后就禁用提交按钮,或者利用onsubmit 事件处理程序取消后续的表单提交操作。

重置表单
<!-- 通用重置按钮 -->
<input type="reset" value="Reset Form">
<!-- 自定义重置按钮 -->
<button type="reset">Reset Form</button>

在重置表单时,所有表单字段都会恢复到页面刚加载完毕时的初始值,用户单击重置按钮重置表单时,会触发onreset事件。也可以使用编程方式来重置表单form.reset(),与调用submit()方法不同,调用reset()方法会像单击重置按钮一样触发onreset事件。

表单字段

每个表单都有elements 属性,该属性是表单中所有表单元素(字段)的集合,例如<input><textarea><button><fieldset>,是一个有序列表(不包含非表单控件)。可以按照位置和name特性来访问它们。

var form = document.getElementById("form1");
//取得表单中的第一个字段
var field1 = form.elements[0];
//取得名为"textbox1"的字段
var field2 = form.elements["textbox1"];
//取得表单中包含的字段的数量
var fieldCount = form.elements.length;

如果有多个表单控件都在使用一个name(如单选按钮),那么就会返回以该name 命名的一个NodeList

  • 表单字段属性

    • disabled:布尔值,表示当前字段是否被禁用。
    • form:指向当前字段所属表单的指针;只读。
    • name:当前字段的名称。
    • readOnly:布尔值,表示当前字段是否只读。
    • tabIndex:表示当前字段的切换(tab)序号。
    • type:当前字段的类型,如"checkbox""radio",等等。
    • value:当前字段将被提交给服务器的值。对文件字段来说,这个属性是只读的,包含着文件在计算机中的路径。

可以通过JavaScrip 动态修改属性。

var form = document.getElementById("myForm");
var field = form.elements[0];
//修改value 属性
field.value = "Another value";
//检查form 属性的值
alert(field.form === form); //true
//把焦点设置到当前字段
field.focus();
//禁用当前字段
field.disabled = true;
//修改type 属性(不推荐,但对<input>来说是可行的)
field.type = "checkbox";
  • 表单字段方法

    • focus():用于将浏览器的焦点设置到表单字段,即激活表单字段,使其可以响应键盘事件。HTML5 为表单字段新增了一个autofocus 属性。在支持这个属性的浏览器中,只要设置这个属性,不用JavaScript 就能自动把焦点移动到相应字段。
    • blur():是从元素中移走焦点。在调用blur()方法时,并不会把焦点转移到某个特定的元素上;仅仅是将焦点从调用这个方法的元素上面移走而已。
  • 表单字段事件

    • onfocus:当前字段获得焦点时触发。
    • onblur:当前字段失去焦点时触发。
    • onchange:对于<input><textarea>元素,在它们失去焦点且value值改变时触发;对于<select>元素,在其选项改变时触发。

关于blurchange 事件的关系,并没有严格的规定。在某些浏览器中,blur事件会先于change事件发生;而在其他浏览器中,则恰好相反。为此,不能假定这两个事件总会以某种顺序依次触发,这一点要特别注意。

二、文本框脚本

HTML 中,有两种方式来表现文本框:一种是使用<input>元素的单行文本框(type 特性设置为"text"),另一种是使用<textarea>的多行文本框。

<input>:通过设置size 特性,可以指定文本框中能够显示的字符数。通过value 特性,可以设置文本框的初始值,而maxlength 特性则用于指定文本框可以接受的最大字符数。

<textarea>:要指定文本框的大小,可以使用rowscols 特性。其中,rows 特性指定的是文本框的字符行数,而cols 特性指定的是文本框的字符列数。

建议使用xxx.value 属性读取或设置文本框的值,不要使用setAttribute()设置<input>元素的value 特性,也不要去修改<textarea>元素的第一个子节点(文本节点)。原因很简单:对value属性所作的修改,不一定会反映在DOM中。因此,在处理文本框的值时,最好不要使用DOM方法。

选择文本

两种文本框都支持select()方法,这个方法用于选择文本框中的所有文本。在调用select()方法时formElement.select();,大多数浏览器(Opera 除外)都会将焦点设置到文本框中。这个方法不接受参数,可以在任何时候被调用。(一般用于当文本框获得焦点,就会选择其中所有的文本)

onselect事件:在选择了文本框中的文本时,就会触发onselect事件。现代浏览器只有用户选择了文本(而且要释放鼠标),才会触发onselect 事件。

HTML5 通过一些扩展方案解决了这个问题,以便更顺利地取得选择的文本。该规范采取的办法是添加两个属性:selectionStartselectionEnd。这两个属性中保存的是基于0的数值,表示所选择文本的范围(即文本选区开头和结尾的偏移量)。因此,要取得用户在文本框中选择的文本,可以使用如下代码。

function getSelectedText(textbox){
  // if(typeof textbox.selectionStart == 'number')
  return textbox.value.substring(textbox.selectionStart, textbox.selectionEnd);
  // ie8之前版本不支持 但是支持document.selection
  // if (document.selection){
  //     return document.selection.createRange().text;
  //  }
}
  • 选择部分文本

所有文本框都有一个setSelectionRange()方法。这个方法接收两个参数:要选择的第一个字符的索引和要选择的最后一个字符之后的字符的索引。(包头不包尾)

IE8 及更早版本支持使用范围选择部分文本:

1、首先使用IE在所有文本框上提供的createTextRange()方法创建一个范围,并将其放在恰当的位置上。
2、使用collapse()将范围折叠到文本框的开始位置。
3、再使用moveStart()moveEnd()这两个范围方法将范围移动到位。
4、最后一步,就是使用范围的select()方法选择文本。

textbox.value = "Hello world!";
var range = textbox.createTextRange();

//选择所有文本
range.collapse(true);
range.moveStart("character", 0);
range.moveEnd("character", textbox.value.length); //"Hello world!"
range.select();

//选择第4 到第6 个字符
range.collapse(true);
range.moveStart("character", 4);
range.moveEnd("character", 6);
range.select(); //"o w"
过滤输入
  • 屏蔽字符

响应向文本框中插入字符操作的是keypress事件。因此,可以通过阻止这个事件的默认行为来屏蔽此类字符。比如,下列代码只允许用户输入数值。

textbox.addEventListener("keypress", function(e){
    var target = e.target
    var charCode = e.charCode
    // charCode 属性,这个属性只有在发生keypress 事件时才包含值
    // 而且这个值是按下的那个键所代表字符的ASCII 编码
    // String.fromCharCode()接收一或多个字符编码,然后将它们转换成一个字符串。
    if (!/\d/.test(String.fromCharCode(charCode)) && charCode > 9){
        e.preventDefault();
    }
});

虽然理论上只应该在用户按下字符键时才触发keypress 事件,但有些浏览器也会对其他键触发此事件。FirefoxSafari3.1 版本以前)会对向上键、向下键、退格键和删除键触发keypress 事件;Safari 3.1 及更新版本则不会对这些键触发keypress 事件。所以加上charCode > 9这个条件,因为在Firefox 中,所有由非字符键触发的keypress 事件对应的字符编码为0,而在Safari 3以前的版本中,对应的字符编码全部为8。也可以直接使用e.key(不再使用e.charCode)来获得用户键入的字符(无需多加判断)。

  • 操作剪贴板

Opera外,所有浏览器都支持通过JavaScript 访问剪贴板。HTML 5 后来也把剪贴板事件纳入了规范。下列就是6 个剪贴板事件。

  • beforecopy:在发生复制操作前触发。
  • copy:在发生复制操作时触发。
  • beforecut:在发生剪切操作前触发。
  • cut:在发生剪切操作时触发。
  • beforepaste:在发生粘贴操作前触发。
  • paste:在发生粘贴操作时触发。

由于没有针对剪贴板操作的标准,这些事件及相关对象会因浏览器而异。在SafariChromeFirefox中,beforecopybeforecutbeforepaste 事件只会在显示针对文本框的上下文菜单(预期将发生剪贴板事件)的情况下触发。但是,IE 则会在触发copycutpaste 事件之前先行触发这些事件。至于copycutpaste 事件,只要是在上下文菜单中选择了相应选项,或者使用了相应的键盘组合键,所有浏览器都会触发它们。取消"before事件"并不会取消对剪贴板的操作——只有取消copycutpaste事件,才能阻止相应操作发生。

要访问剪贴板中的数据,可以使用clipboardData对象:在IE 中,这个对象是window 对象的属性,可以随时访问clipboardData对象;而在Firefox 4+SafariChrome 中,这个对象是相应event对象的属性,并且只有在处理剪贴板事件期间clipboardData 对象才有效。

clipboardData 对象有三个方法:getData()setData()clearData()

getData()用于从剪贴板中取得数据,它接受一个参数,即要取得的数据的格式。在IE中,有两种数据格式:"text""URL"。在FirefoxSafariChrome 中,这个参数是一种MIME 类型(多用途互联网邮箱扩展类型);不过,可以用"text"代表"text/plain"FirefoxSafariChrome 只允许在onpaste 事件处理程序中访问getData()方法。

setData()方法的第一个参数也是数据类型,第二个参数是要放在剪贴板中的文本。对于第一个参数,IE 照样支持"text""URL",而SafariChrome 仍然只支持MIME类型。但是,与getData()方法不同的是,SafariChromesetData()方法不能识别"text"类型。这两个浏览器在成功将文本放到剪贴板中后,都会返回true;否则,返回false

var EventUtil = {
    getClipboardText: function(event){
        var clipboardData = (event.clipboardData || window.clipboardData);
        return clipboardData.getData("text");
    },
    setClipboardText: function(event, value){
        if (event.clipboardData){ // safari \ chrome
            return event.clipboardData.setData("text/plain", value);
        } else if (window.clipboardData){ // IE
          return window.clipboardData.setData("text", value);
        }
    },
};

在需要确保粘贴到文本框中的文本中包含某些字符,或者符合某种格式要求时,能够访问剪贴板是非常有用的。例如,如果一个文本框只接受数值,那么就必须检测粘贴过来的值,以确保有效。在paste事件中,可以确定剪贴板中的值是否有效,如果无效,则取消默认的行为。

自动切换焦点

通过比较用户输入的值与文本框的maxlength 特性,可以确定是否已经达到最大长度。如果这两个值相等(因为浏览器最终会强制它们相等,因此用户绝不会多输入字符),则需要查找表单字段集合,直至找到下一个文本框。找到下一个文本框之后,则将焦点切换到该文本框。然后,我们把这个函数指定为每个文本框的onkeyup事件处理程序。由于keyup 事件会在用户输入了新字符之后触发,所以此时是检测文本框中内容长度的最佳时机。

function tabForward(e){
    var target = e.target
    if (target.value.length == target.maxLength){
        var form = target.form;
        for (var i=0, len=form.elements.length; i < len; i++) {
            if (form.elements[i] == target) {
                if (form.elements[i+1]){
                    form.elements[i+1].focus();
                }
                return;
            }
        }
    }
}
HTML5 约束验证API

为了在将表单提交到服务器之前验证数据,HTML5 新增了一些功能。有了这些功能,即便JavaScript被禁用或者由于种种原因未能加载,也可以确保基本的验证。浏览器自己会根据标记中的规则执行验证,然后自己显示适当的错误消息(完全不用JavaScript插手)只有在支持HTML5这部分内容的浏览器中才有效,这些浏览器有Firefox 4+Safari 5+ChromeOpera 10+

就是要在HTML标记中为特定的字段指定一些约束,然后浏览器才会自动执行表单验证。

  • 必填字段required属性:<input type="text" name="username" required>

这个属性适用于<input><textarea><select>字段。在JavaScript中,通过对应的required属性,可以检查某个表单字段是否为必填字段。

var isUsernameRequired = document.forms[0].elements["username"].required; // boolean
// 或测试浏览器是否支持 required属性 检查创建的input元素是否存在required属性
var isRequiredSupported = "required" in document.createElement("input");

对于空着的必填字段,不同浏览器有不同的处理方式。Firefox 4Opera 11 会阻止表单提交并在相应字段下方弹出帮助框,而Safari(5 之前)Chrome(9 之前)则什么也不做,而且也不阻止表单提交。

  • 其他输入类型
<input type="email" name ="email">
<input type="url" name="homepage">

HTML5 新增的inputtype属性,"email"类型要求输入的文本必须符合电子邮件地址的模式,而"url"类型要求输入的文本必须符合URL 的模式。

var input = document.createElement("input");
input.type = "email"; // 先设置 后检查是否设置成功
var isEmailSupported = (input.type == "email"); // 不支持的浏览器 会自动将未知的值设置为"text"

要注意的是,如果不设置required 属性,那么空文本框也会验证通过。另一方面,设置特定的输入类型并不能阻止用户输入无效的值,只是应用某些默认的验证而已。

  • 数值范围

HTML5 新增的inputtype属性关于数值范围的有:
"number""range""datetime""datetimelocal""date""month""week""time"

对所有这些数值类型的输入元素,可以指定min属性(最小的可能值)、max 属性(最大的可能值)和step 属性(从minmax的两个刻度间的差值)。

// 想让用户只能输入0 到100 的值,而且这个值必须是5 的倍数
<input type="number" min="0" max="100" step="5" name="count">

以上这些属性在JavaScript中都能通过对应的元素访问(或修改)。此外,还有两个方法:stepUp()stepDown(),都接收一个可选的参数:要在当前值基础上加上或减去的数值。(默认是加或减1。)

input.stepUp(); //加1
input.stepUp(5); //加5
input.stepDown(); //减1
input.stepDown(10); //减10
  • 输入模式

HTML5 为文本字段新增了pattern属性。这个属性的值是一个正则表达式,用于匹配文本框中的值。

// 只允许输入数值
<input type="text" pattern="\d+" name="count">
// JavaScript 中可以通过pattern 属性访问
var pattern = document.forms[0].elements["count"].pattern;
// 检测浏览器是否支持pattern 属性
var isPatternSupported = "pattern" in document.createElement("input");
  • 检测有效性

使用checkValidity()方法可以检测表单中的某个字段是否有效。所有表单字段都有个方法,如果字段的值有效,这个方法返回true,否则返回false。字段的值是否有效的判断依据是本节前面介绍过的那些约束。要检测整个表单是否有效,可以在表单自身调用checkValidity()方法。如果所有表单字段都有效,这个方法返回true;即使有一个字段无效,这个方法也会返回false

checkValidity()方法简单地告诉你字段是否有效相比,validity 属性则会告诉你为什么字段有效或无效。这个对象中包含一系列属性,每个属性会返回一个布尔值。

  • customError :如果设置了setCustomValidity(),则为true,否则返回false
  • patternMismatch:如果值与指定的pattern 属性不匹配,返回true
  • rangeOverflow:如果值比max 值大,返回true
  • rangeUnderflow:如果值比min 值小,返回true
  • stepMisMatch:如果minmax之间的步长值不合理,返回true
  • tooLong:如果值的长度超过了maxlength 属性指定的长度,返回true。有的浏览器(如Firefox 4)会自动约束字符数量,因此这个值可能永远都返回false
  • typeMismatch:如果值不是"email""url"要求的格式,返回true
  • valid:如果这里的其他属性都是false,返回truecheckValidity()也要求相同的值。
  • valueMissing:如果标注为required 的字段中没有值,返回true
if (input.validity && !input.validity.valid){
    if (input.validity.valueMissing){
        alert("Please specify a value.")
    } else if (input.validity.typeMismatch){
        alert("Please enter an email address.");
    } else {
        alert("Value is invalid.");
    }
}
  • 禁用验证

通过设置novalidate 属性,可以告诉表单不进行验证。

<form method="post" action="signup.php" novalidate>
    <!--这里插入表单元素-->
</form>
// 在JavaScript 中使用noValidate 属性可以取得或设置这个值
// 如果这个属性存在,值为true,如果不存在,值为false。
document.forms[0].noValidate = true; //禁用验证

如果一个表单中有多个提交按钮,为了指定点击某个提交按钮不必验证表单,可以在相应的按钮上 添加 formnovalidate 属性。也可以使用JavaScript取得或设置这个属性。

三、选择框脚本

选择框是通过<select><option>元素创建的。为了方便与这个控件交互,除了所有表单字段共 有的属性和方法外,HTMLSelectElement 类型还提供了下列属性和方法。

  • add(newOption, relOption):向控件中插入新<option>元素,其位置在相关项(relOption) 之前。
  • multiple:布尔值,表示是否允许多项选择;
  • options:控件中所有<option>元素的 HTMLCollection
  • remove(index):移除给定位置的选项。
  • selectedIndex:基于 0 的选中项的索引,如果没有选中项,则值为-1。对于支持多选的控件,只保存选中项中第一项的索引。
  • size:选择框中可见的行数;

选择框的 type属性不是"select-one",就是"select-multiple",这取决于 HTML 代码中有没有 multiple 特性。

<select>value:如果没有选中的options,则value为空字符串,如果有选中的options,则value的值为被选中的optionsvalue属性,如果被选中的options没有value属性,则为该options的文本值。可以通过 option.attributes.value && option.attributes.value.specified 来判断option元素是否定义了 value属性。

每个<option>元素都有以下属性。

  • index:当前选项在 options 集合中的索引。
  • label:当前选项的标签;
  • selected:布尔值,表示当前选项是否被选中。将这个属性设置为 true 可以选中当前选项。
  • text:选项的文本。
  • value:选项的值。

建议使用属性访问,不建议使用DOM方法访问;其他表单字段的 change 事件是在值被修改且焦点离开当前字段时触发,而选择框的 change 事件只要选中了选项就会触发。在未指定 value特性的情况下,IE8 会返 回空字符串,而 IE9+SafariFirefoxChromeOpera 则会返回与 text 特性相同 的值。

选择选项

访问选中项的最简单方式,就是使用选择框的selectedIndex 属性,如下面的例子所示:

var selectedOption = selectbox.options[selectbox.selectedIndex];

对于可以选择多项的选择框,设置selectedIndex会导致取消以前的所有选项并选择指定的那一项,而读取selectedIndex则只会返回选中项中第一项的索引值。另一种选择选项的方式,就是取得对某一项的引用,然后将其selected属性设置为true。多选的情况下,可以将多个选项的selected值设置为true;而单选框,修改某个选项的selected属性为true,则会取消对其他选项的选择。

添加选项
  • DOM方法
var newOption = document.createElement("option");
newOption.appendChild(document.createTextNode("Option text"));
newOption.setAttribute("value", "Option value");
selectbox.appendChild(newOption);
  • 使用Option 构造函数
// 这一步在兼容DOM的浏览器中会返回一个元素
var newOption = new Option("Option text", "Option value"); 
selectbox.appendChild(newOption); //在IE8 及之前版本中有问题
selectbox.add(newOption, undefined); //最佳方案
// add接受两个参数(要添加的新选项,相对选项),会将新选项添加到相对选项的前面
// 传值为null或是undefined则将插入到最后
// IE中第二个参数可选,兼容DOM的浏览器要求第二个参数必填。
移除选项
//  `removeChild()`
selectbox.removeChild(selectbox.options[0]); //移除第一个选项
// selectElement的`remove()`方法
selectbox.remove(0);
// 这个函数每次只移除选择框中的第一个选项。
// 移除第一个选项后,所有后续选项都会自动向上移动一个位置
// 因此重复移除第一个选项就可以移除所有选项了。
移动和重排选项

如果为appendChild()方法传入一个文档中已有的元素,那么就会先从该元素的父节点中移除它,再把它添加到指定的位置,这样就可以将第一个选择框中的选项直接移动到第二个选择框中。移动选项与移除选项有一个共同之处,即会重置每一个选项的index属性。

重排选项次序的过程也十分类似,最好的方式仍然是使用DOM方法。要将选择框中的某一项移动到特定位置,最合适的DOM 方法就是insertBefore()

// 在选择框中向前移动一个选项的位置
var optionToMove = selectbox.options[1];
selectbox.insertBefore(optionToMove, selectbox.options[optionToMove.index-1]);
// 将选择框中的选项向后移动一个位置。
var optionToMove = selectbox.options[1];
selectbox.insertBefore(optionToMove, selectbox.options[optionToMove.index+2]);
// 如果只加1 相当于没移动

表单序列化

序列化即url?key1=value1&key2=value2。在JavaScript中,可以利用表单字段的type属性,连同namevalue属性一起实现表单序列化。

首先需要搞清楚在表单提交期间,浏览器是怎样将数据发送给服务器的。

  • 对表单字段的名称和值进行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){
                            // 判断options是否设置了value特性
                            if (option.hasAttribute){ // DOM兼容的浏览器
                                optValue = (option.hasAttribute("value") ?
                                option.value : option.text);
                            } else { // IE
                                optValue = (option.attributes["value"].specified ?
                                option.value : option.text);
                            }
                            parts.push(encodeURIComponent(field.name) + "=" +
                            encodeURIComponent(optValue));
                            // encodeURIComponent主要用来编码search值 
                            // encodeURI(只转换空格)主要用来编码整个url
                        }
                    }
                }
                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("&");
}

富文本编辑

富文本编辑,又称为WYSIWYGWhat You See Is What You Get,所见即所得)。包含空HTML页面的iframe。通过设置designMode属性,这个空白的HTML页面可以被编辑,而编辑对象则是该页面<body>元素的HTML代码。

designMode 属性有两个可能的值:"off"(默认值)和"on"。在设置为"on"时,整个文档都会变得可以编辑(显示插入符号),然后就可以像使用字处理软件一样,通过键盘将文本内容加粗、变成斜体,等等。

<iframe name="richedit" style="height:200px;width:200px;" src="./frame.htm"></iframe>

<!-- 也可以把contenteditable 属性应用给页面中的任何元素,然后用户立即就可以编辑该元素 -->
<div class="editable" id="richedit1" contenteditable>1111</div>
<script type="text/javascript">
    window.onload= function () {
        // 不要在chrome的file环境下尝试 会形成跨域问题。
        frames["richedit"].document.designMode = "on";
        console.log(frames["richedit"].document.designMode)
    }
    // iframe_node.contentDocument.designMode = "on";

    var div = document.getElementById("richedit1");
    div.contentEditable = "true";
    // "true"表示打开、"false"表示关闭,"inherit"表示从父元素那里继承
</script>
操作富文本
  • document.execCommand()

该方法接受三个参数:

1、要执行的命令名称。
2、表示浏览器是否应该为当前命令提供用户界面的一个布尔值(为兼容浏览器,一般统一传false
3、执行命令必须的一个值(如果不需要值,则传递null)。

命令 值(第三个参数) 说明
backcolor 颜色字符串 设置文档的背景颜色
bold null 将选择的文本转换为粗体
createlink URL字符串 将选择的文本转换成一个链接,指向指定的URL
cut null 将选择的文本剪切到剪贴板
delete null 删除选择的文本
fontname 字体名称 将选择的文本修改为指定字体
fontsize 1~7 将选择的文本修改为指定字体大小
forecolor 颜色字符串 将选择的文本修改为指定的颜色
formatblock 要包围当前文本块的HTML标签;如<h1> 使用指定的HTML标签来格式化选择的文本块
indent null 缩进文本
inserthorizontalrule null 在插入字符处插入一个<hr>元素
insertimage 图像的URL 在插入字符处插入一个图像
insertorderedlist null 在插入字符处插入一个<ol>元素
insertunorderedlist null 在插入字符处插入一个<ul>元素
insertparagraph null 在插入字符处插入一个<p>元素
italic null 将选择的文本转换成斜体
justifycenter null 将插入光标所在文本块居中对齐
justifyleft null 将插入光标所在文本块左对齐
outdent null 凸排文本(减少缩进)
paste null 将剪贴板中的文本粘贴到选择的文本
removeformat null 移除插入光标所在文本块的块级格式。这是撤销formatblock命令的操作
selectall null 选择文档中的所有文本
underline null 为选择的文本添加下划线
unlink null 移除文本的链接。这是撤销createlink命令的操作

可通过源码来查看具体使用场景。源码下载,14章,兼容浏览器的js在13章

// 选择文本之后 触发该js语句执行
// 转换粗体文本
frames["richedit"].document.execCommand("bold", false, null);
//转换斜体文本
frames["richedit"].document.execCommand("italic", false, null);
//创建指向www.wrox.com 的链接
frames["richedit"].document.execCommand("createlink", false,
"http://www.wrox.com");
//格式化为1 级标题
frames["richedit"].document.execCommand("formatblock", false, "<h1>");
// ... ...

// 同样的方法也适用于页面中contenteditable 属性为"true"的区块
// 只要把对框架的引用替换成当前窗口的document 对象即可。

//转换斜体文本
document.execCommand("italic", false, null);
  • queryCommandEnabled()

可以用它来检测是否可以针对当前选择的文本,或者当前插入字符所在位置执行某个命令。这个方法接收一个参数,即要检测的命令。如果当前编辑区域允许执行传入的命令,这个方法返回true,否则返回false

var result = frames["richedit"].document.queryCommandEnabled("bold");
// 如果能够对当前选择的文本执行"bold"命令,以上代码会返回true。
// 需要注意的是返回true,并不意味着实际上就可以执行相应命令,
// 而只能说明对当前选择的文本执行相应命令是否合适。
// 例如,Firefox 在默认情况下会禁用剪切操作,但执行queryCommandEnabled("cut")也可能会返回true
  • queryCommandState()

用于确定是否已将指定命令应用到了选择的文本。

var isBold = frames["richedit"].document.queryCommandState("bold");
// 如果此前已经对选择的文本执行了"bold"命令,那么上面的代码会返回true。
// 一些功能全面的富文本编辑器,正是利用这个方法来更新粗体、斜体等按钮的状态的。
  • queryCommandValue()

用于取得执行命令时传入的值(即前面例子中传给document.execCommand()的第三个参数)。

var fontSize = frames["richedit"].document.queryCommandValue("fontsize");
// 在对一段文本应用"fontsize"命令时如果传入了7,那么这段代码就会返回"7"
//  通过这个方法可以确定某个命令是怎样应用到选择的文本的,可以据以确定再对其应用后续命令是否合适。
富文本选区

在富文本编辑器中,使用框架(iframe)的getSelection()方法,可以确定实际选择的文本。这个方法是window对象和document对象的属性,调用它会返回一个表示当前选择文本的Selection对象。

  • Selection对象属性

    • anchorNode:选区起点所在的节点。
    • anchorOffset:在到达选区起点位置之前跳过的anchorNode中的字符数量。
    • focusNode:选区终点所在的节点。
    • focusOffsetfocusNode中包含在选区之内的字符数量。
    • isCollapsed:布尔值,表示选区的起点和终点是否重合。
    • rangeCount:选区中包含的DOM范围的数量。
  • Selection对象方法

    • addRange(range):将指定的DOM范围添加到选区中。
    • collapse(node, offset):将选区折叠到指定节点中的相应的文本偏移位置。
    • collapseToEnd():将选区折叠到终点位置。
    • collapseToStart():将选区折叠到起点位置。
    • containsNode(node):确定指定的节点是否包含在选区中。
    • deleteFromDocument():从文档中删除选区中的文本,与document.execCommand("delete",false, null)命令的结果相同。
    • extend(node, offset):通过将focusNodefocusOffset移动到指定的值来扩展选区。
    • getRangeAt(index):返回索引对应的选区中的DOM范围。
    • removeAllRanges():从选区中移除所有DOM范围。实际上,这样会移除选区,因为选区中至少要有一个范围。
    • reomveRange(range):从选区中移除指定的DOM范围。
    • selectAllChildren(node):清除选区并选择指定节点的所有子节点。
    • toString():返回选区所包含的文本内容。
var selection = frames["richedit"].getSelection();
//取得选择的文本
var selectedText = selection.toString();
//取得代表选区的范围
var range = selection.getRangeAt(0);
//突出显示选择的文本
var span = frames["richedit"].document.createElement("span"); span.style.backgroundColor = "yellow"; range.surroundContents(span);

// IE不支持 DOM 范围 但我们可以通过它支持的 selection 对象操作选择的文本(range.text / range.htmlText)。
// IE 中的 selection 对象是 document 的属性
// 要取得富文本编辑器中选择的文本,首先必须创建一个文本范围

var range = frames["richedit"].document.selection.createRange();
range.pasteHTML("<span style=\"background-color:yellow\"> " + range.htmlText +"</span>");

以上代码会为富文本编辑器中被选择的文本添加黄色的背景。这里使用了默认选区中的 DOM范围, 通过 surroundContents()方法将选区添加到了带有黄色背景的<span>元素中。IE的代码通过htmlText 取得了当前选区中的 HTML,然后将其放在了一对<span>标签中,最后又使用 pasteHTML()将结果重新插入到了选区中。

表单与富文本

由于富文本编辑是使用iframe 而非表单控件实现的,因此从技术上说,富文本编辑器并不属于表单。富文本编辑器中的HTML 不会被自动提交给服务器,而需要我们手工来提取并提交 HTML。通常可以添加一个隐藏的表单字段,让它的值等于从 iframe中提取出的 HTML,来说,在提交表单之前,从 iframe 中提取出 HTML,并将其插入到隐藏的字段中。下面就是通过表单onsubmit事件处理程序实现上述操作的代码。

form.addEventListener('submit',function(e){
    e.target.element["comments"].value = frames["richedit"].document.body.innerHTML;
})

取得了 iframe 中的 HTML,然后将其插入到了名为 "comments"的表单字段中。这样可以确保恰好在提交表单之前填充"comments"字段。对于contenteditable元素可以使用document.getElementById("rechedit").innerHTML来获取到HTML

富文本编辑功能是通过一个包含空 HTML 文档的iframe 元素来实现的。通过将空文档的 designMode 属性设置为"on",就可以将该页面转换为可编辑状态,此时其表现如同字处理软件。另外, 也可以将某个元素设置为 contenteditable。在默认情况下,可以将字体加粗或者将文本转换为斜体, 还可以使用剪贴板。avaScript 通过使用 execCommand()方法也可以实现相同的一些功能。另外,使用queryCommandEnabled()queryCommandState()queryCommandValue()方法则可以取得有关 文本选区的信息。由于以这种方式构建的富文本编辑器并不是一个表单字段,因此在将其内容提交给服务器之前,必须将iframecontenteditable 元素中的 HTML(富文本编辑中的html必然是一段文本字符串) 复制到一个表单字段中。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容