14 表单脚本

本章内容

  • 理解表单
  • 文本框验证与交互
  • 使用其他表单控制

14.1 表单的基础知识

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

var firstForm = document.forms[0];
var myForm = document.forms["form2"];

14.1.1 提交表单

var form = document.getElementById("myForm");
EventUtil.addHandler(form, "submit", function(event) {
  //取得事件对象
  event = EventUtil.getEvent(event);
  //阻止默认事件
  EventUtil.preventDefault(event);
});

14.1.2 重置表单

var form = document.getElementById("myForm");
EventUtil.addHandler(form, "reset", function(event) {
  //取得事件对象
  event = EventUtil.getEvent(event);
  //阻止表单重置
  EventUtil.preventDefault(event);
});

14.1.3 表单字段

可以像访问页面中的其他元素一样,使用原生 DOM 方法访问表单元素。

  1. 共有的表单字段属性
  2. 共有的表单字段方法
  3. 共有的表单字段事件

14.2 文本框脚本

在 HTML 中,有两种方式来表现文本框:一种是使用<input>元素的单行文本框,另一种是使用<textarea>的多行文本框。

14.2.1 选择文本

上述两种文本都支持select()方法,这个方法用于选择文本框中的所有文本。

  1. 选择(select)事件
  2. 取得选择的文本
  3. 选择部分文本

14.2.2 过滤输入

  1. 屏蔽字符
    响应向文本框中插入字符操作的是keypress事件。因此,可以通过阻止这个事件的默认行为来屏蔽此类字符。
EventUtil.addHandler(textbox, "keypress", function(event) {
  event = EventUtil.getEvent(event);
  EventUtil.preventDefault(event);
});

运行以上代码后,由于所有按键操作都将被屏蔽。如果只想屏蔽特定的字符,则需要检测keypress事件对应的字符编码,然后再决定如何响应。例如,下列代码只允许用户输入数值。

EventUtil.addHandler(textbox, "keypress", function (event) {
  event = EventUtil.getTarget(event);
  var target = EventUtil.getTarget(event);
  var charCode = EventUtil.getCharCode(event);
  
  if(!/\d/.test(String.fromCharCode(charCode))) {
    EventUtil.preventDefault(event);
  }
});

虽然理论上只应该在用户按下字符键时才触发keypress事件,但有些浏览器也会对其他键触发此事件。为了让代码更通用,只要不屏蔽那些字符编码小于 10 的键即可。将上面的函数重写如下。

EventUtil.addHandler(textbox, "keypress", function (event) {
  event = EventUtil.getEvent(event);
  var target = EventUtil.getTarget(event);
  var charCode = EventUtil.getCharCode(event);

  if (!/\d/.test(String.fromCharCode(charCode)) && charCode > 9) {
    EventUtil.preventDefault(event);
  }
});

复制、粘贴及其他操作还要用到 Ctrl 键。在除 IE 之外的所有浏览器中,前面的代码也会屏蔽 Ctrl+C、Ctrl+V,以及其他使用 Ctrl 的组合键。因此,最后还要添加一个检测条件。

EventUtil.addHandler(textbox, "keypress", function(event) {
  event = EventUtil.getEvent(event);
  var target = EventUtil.getTarget(event);
  var charCode = EventUtil.getCharCode(event);

  if(!/\d/.test(String.fromCharCode(charCode) && charCode > 9 && !event.ctrlKey) {
    EventUtil.preventDefault(event);
  });
});
  1. 操作剪贴板
var EventUtil = {
  getClipboardText: function(event) {
    var clipboardData = (event.clipboardData || window.clipboardData);
    return clipboardData.getData("text");
  },
  setClipboardText: function(event, value) {
    if (event.clipboardData) {
      return event.clipboardData.setData("text/plain", value);
    } else if (window.clipboardData) {
      return window.clipboardData.setData("text", value);
    }
  },
};

paste事件中,可以确定剪贴板中的值是否有效。

EventUtil.addHandler(textbox, "paste", function (event) {
  event = EventUtil.getEvent(event);
  var text = EventUtil.getClipboardText(event);
  
  if (!/^\d*$/.test(text)) {
    EventUtil.preventDefault(event);
  }
});

14.2.3 自动切换焦点

(function () {
  function tabForward(event) {
    event = EventUtil.getEvent(event);
    var target = EventUtil.getTarget(event);
    
    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;
        }
      }
    }
  }
var textbox1 = document.getElementById("txtTel1");
var textbox2 = document.getElementById("txtTel2");
var textbox3 = document.getElementById("txtTel3");

EventUtil.addHandler(textbox, "keyup", tabForward);
EventUtil.addHandler(textbox2, "keyup", tabForward);
EventUtil.addHandler(textbox3, "keyup", tabForward);
})();

14.2.4 HTML5 约束验证 API

  1. 必填字段
    第一种情况是在表单字段中指定了required属性
  2. 其他输入类型
  3. 数值范围
  4. 输入模式
    HTML5 为文本字段新增了pattern属性。这个属性的值是一个正则表达式,用于匹配文本框中的值。
    与其他输入类型相似,不能阻止用户输入无效的文本。
  5. 检测有效性
    使用checkValidity()方法可以检测表单中的某个字段是否有效。
if (document.forms[0].elements[0].checkValidity()) {
  //字段有效,继续
} else {
  //字段无效
}

要检测整个表单是否有效,可以在表单自身调用checkValidity()方法。

if (document.forms[0].checkValidity()) {
  //表单有效,继续
} else {
  //表单无效
}

validity属性则会告诉你为什么字段有效或无效。这个对象包含一系列属性,每个属性返回一个布尔值。

  1. 禁用验证
    通过设置novalidate属性,可以告诉表单不进行验证。
    如果一个表单中有多个提交按钮,为了指定点击某个提交按钮不必验证表单,可以在相应的按钮上添加formnovalidate属性。

14.3 选择框脚本

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

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

在 DOM 中,每个<option>元素都有一个HTMLOptionElement对象表示。为便于访问数据,HTMLOptionElement对象添加了下列属性:

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

14.3.1 选择选项

对于只允许选择一项的选择框,访问选中项的最简单方式,就是使用选择框的selectedIndex属性。

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

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

selectbox.options[0].selected = true;

多选框可以通过设置selected属性选中任意多个项。在单选框中,修改某个选项的selected属性则会取消对其他选项的选择。需要注意的是,将selected属性设置为false对单选框没有影响。
要取得所有选中的项,可以循环遍历选项集合,然后测试每个选项的selected属性。如下。

function getSelectedOption(selectbox) {
  var result = new Array();
  var option = null;
  for (var i=0,len=selectbox.options.length; i < len; i++) {
    option = selectbox.options[i];
    if (option.selected) {
      result.push(option);
    }
  }
  return result;
}

14.3.2 添加选项

第一种方式使用 DOM 方法。第二种方式是使用Option构造函数。
第三种添加新选项的方式是使用选择框的add()方法。兼容 DOM 的浏览器要求必须指定第二个参数。

var newOption = new Option("Option text", "Option value");
selectbox.add(newOption, undefined);

如果你想将新选项添加到其他位置(不是最后一个),就应该使用标准的 DOM 技术和insertBefore()方法。

14.3.3 移除选项

首先,可以使用 DOM 的removeChild()方法,为其传入要移除的选项。如下所示:

selectbox.removeChild(selectbox.options[0]);

其次,可以使用选择框的remove()方法。这个方法可以接受一个参数,即要移除选项的索引,如下所示:

selectbox.remove(0);

最后一种方式,就是将相应选项设置为null。这种方式也是 DOM 出现之前浏览器的遗留机制。如:

selectbox.options[0] = null;

要清除选择框中所有的项,需要迭代所有选项并逐个移除它们。如下所示:

function clearSelectbox(selectbox) {
  for (var i=0, len=selectbox.options.length; i < len; i++) {
    selectbox.remove(i?0);
  }
}

这个函数每次只移除选择框中的第一个选项。由于移除第一个选项后,所有后续选项都会自动向上移动一个位置,因此重复移除第一个选项就可以移除所有选项了。

14.3.4 移动和重排选项

移动使用 DOM 的appendChild()方法。
重排最合适的 DOM 的方法就是insertBefore()

14.4 表单序列化

随着 Ajax 的出现,表单序列化已经成为一种常见需求。可以利用表单字段的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) {
            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("&");
}

14.5 富文本编辑

富文本编辑,又称为 WYSIWYG(What You See Is What You Get,所见即所得)。这一技术的本质,就是在页面中嵌入一个包含空 HTML 页面的 iframe。通过设置designMode属性,这个空白的HTML 页面可以被编辑,而编辑对象则是该页面<body>元素的 HTML 代码。designMode属性由两个可能的值:“off”(默认值)和“on”。在设置为“on”时,整个文档都会变得可以编辑(显示插入符号),然后就可以像使用字处理软件一样,通过键盘将文本内容加粗、变成斜体,等等。
可以给iframe指定一个非常简单的 HTML 页面作为其内容来源。例如:

<!DOCTYPE html>
<html>
  <head>
    <title>Blank Page for Rich Text Editing</title>
  </head>
  <body>
  </body>
</html>

只有在页面完全加载之后才能设置designMode属性。因此,在包含页面中,需要使用onload事件处理程序来在恰当的时刻设置designMode,如下所示:

<iframe name="richedit" style="height:100px;width:100px;" src="blank.htm"></iframe>
<script type="text/javascript">
EventUtil.addHandler(window, "load", function() {
  frames["richedit"].document.designMode = "on";
});
</script>

等到以上代码执行之后,你就会在页面中看到一个类似文本框的可编辑区字段。这个区字段具有与其他网页相同的默认样式;不过,通过为空白页面应用 CSS 样式,可以修改可编辑区字段的外观。

14.5.1 使用 contenteditable 属性

另一种编辑富文本内容的方式是使用名为contenteditable的特殊属性,这个属性也是由 IE 最早实现的。可以把contenteditable属性应用给页面中的任何元素,然后用户立即就可以编辑该元素。不需要iframe、空白页和 JavaScript。

<div class="editable" id="richedit" contenteditable></div>

通过在这个元素上设置contenteditable属性,也能打开或关闭编辑模式。

var div = document.getElementById("richedit");
richedit.contentEditable = "true";

contenteditable 属性有三个可能的值:“true”表示打开、“false”、表示关闭、“inherit”表示从父元素那里继承(因为可以在contenteditable元素中创建或删除元素)。兼容性很强。

14.5.2 操作富文本

与富文本编辑器交互的主要方式,就是使用document.execCommand()。这个方法可以对文档执行预定义的命令,而且可以应用大多数格式。可以为document.execCommand()方法传递 3 个参数:要执行的命令名称、表示浏览器是否应该为当前命令提供用户界面的一个布尔值和执行命令必须的一个值(如果不需要值,则传递null)。为了确保跨浏览器的兼容性,第二个参数应该始终设置为false
不同浏览器支持的预定义命令也不一样。
其中,与剪贴板有关的命令在不同浏览器中的差异极大。Opera 根本没有实现任何剪贴板命令,而 Firefox 在默认情况下会禁用它们。Safari 和 Chrome 实现了cutcopy,但没有实现paste。不过,即使不能通过document.execCommand()来执行这些命令,但却可以通过相应的快捷键来实现同样的操作。
可以在任何时候使用这些命令来修改富文本区域的外观,如下例。

//转换粗体文本
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对象即可。
需要注意的是,虽然所有浏览器都支持这些命令,但这些命令所产生的 HTML 不一定相同。
可以使用queryCommandEnabled()来检测是否可以针对当前选择的文本,或者当前插入字符所在位置执行某个命令。
另外,queryCommandState()方法用于确定是否已将指定命令应用到了选择的文本。
最后,queryCommandValue(),用于取得执行命令时传入的值(即前面例子中传给document.execCommand()的第三个参数)。

14.5.3 富文本选区

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

  • anchorNode:选区起点所在的节点。
  • anchorOffset:在到达选区起点位置之前跳过的anchorNode中的字符数量。
  • focusNode:选区终点所在的节点。
  • focusOffsetfocusNode中包含在选区之内的字符数量。
  • isCollapsed:布尔值,表示选区的起点和终点是否重合。
  • rangeCount:选区中包含的 DOM 范围的数量。
    Selection对象的这些属性并没有包含多少有用的信息。好在,该对象的方法提供了更多信息。
    IE 8 及更早的版本不支持 DOM 范围,但我们可以通过它支持的selection对象操作选择的文本。
var range = frame["richedit"].document.selection.createRange();
var selectedText = range.text;

14.5.4 表单与富文本

从技术上说,富文本编辑器并不属于表单。换句话说,富文本编辑器中 HTML 不会被自动提交给服务器,而需要我们手工来提取并提交 HTML。为此,通常可以添加一个隐藏的表单字段,让它的值等于从iframe中提取出的 HTML。

14.6 小结

使用 JavaScript 可以增强已有的表单字段,从而创造出新的功能,或者提升表单的易用性。

  • 可以使用一些标准或非标准的方法选择文本框的全部或部分文本。
  • 大多数浏览器都采用了 Firefox 操作选择文本的方式,但 IE 仍然坚持自己的实现。
  • 在文本框的内容变化时,可以通过侦听键盘事件以及检测插入的字符,来允许或禁止用户输入某些字符。

在文本框内容必须限制为某些特定字符的情况下,就可以利用剪贴板事件来屏蔽通过粘贴向文本框中插入内容的操作。
选择框也是经常要通过 JavaScript 来控制的一个表单字段。
富文本编辑功能是通过一个包含空 HTML 文档的iframe元素来实现的。

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

推荐阅读更多精彩内容

  • 1. 表单基础知识: 在HTML中,表单由 表示;在JS中,表单由HTMLFormElement类型表示,它继承了...
    xiaoguo16阅读 432评论 0 0
  • 表单基础知识 在HTML中,表单是由 元素来表示的,而在JS中,表单对应的则是HTMLFormElement类型。...
    oWSQo阅读 903评论 0 1
  • 14.1 表单的基础知识 表单由 元素来表示,继承自HTMLElement类型,除具有HTML元素相同的默认属性外...
    Elevens_regret阅读 361评论 0 0
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • 不知道怎么就来到了这里。 这里是一个没有朋友知道我的地方。 这里的文字只有推荐给朋友他们才会知道。 一直在寻找能够...
    月下孤者阅读 164评论 0 1