aboutsummaryrefslogtreecommitdiff
path: root/files/zh-cn/learn/forms/sending_forms_through_javascript/index.html
blob: 6f3edf8801ed17ee56af72df7e1fd75ede92345a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
---
title: 使用 JavaScript 发送表单
slug: Learn/Forms/Sending_forms_through_JavaScript
tags:
  - HTML
  - HTML表单
  - JavaScript
  - Web 表单
  - 示例
  - 表单
  - 高级
translation_of: Learn/Forms/Sending_forms_through_JavaScript
original_slug: Learn/HTML/Forms/Sending_forms_through_JavaScript
---
<div>{{LearnSidebar}}{{PreviousMenuNext("Learn/HTML/Forms/How_to_build_custom_form_widgets", "Learn/HTML/Forms/HTML_forms_in_legacy_browsers", "Learn/HTML/Forms")}}</div>

<p class="summary">正如在<a href="/en-US/docs/HTML/Forms/Sending_and_retrieving_form_data">前面的文章</a>中讲到的,HTML 表单可以声明式地发送一个 HTTP 请求。 但也可以通过 JavaScript 来为表单准备用于发送的 HTTP 请求。 本文探讨如何做到这一点。</p>

<h2 id="表单不总是表单">表单不总是表单</h2>

<p><a href="/en-US/docs/Open_Web_apps_and_Web_standards">开放式Web应用程序</a>中,使用 <a href="https://developer.mozilla.org/en-US/docs/HTML/Forms">HTML form</a> 而不是文字表单让人们来填写变得越来越普遍了 — 越来越多的开发人员正致力于控制传输数据。</p>

<h3 id="获得整体界面的控制">获得整体界面的控制</h3>

<p>标准的 HTML 表单提交会加载数据要发送到的URL,这意味着浏览器窗口以整页加载进行导航。 可以通过隐藏闪烁和网络滞后来避免整页加载以提供更平滑的体验。</p>

<p>许多现代用户界面只使用HTML表单来收集用户的输入。 当用户尝试发送数据时,应用程序将在后台采取控制并且异步地传输数据,只更新UI中需要更改的部分。</p>

<p>异步地发送任何数据被称为 <a href="/zh-CN/docs/AJAX" title="/zh-CN/docs/AJAX">AJAX</a>,它代表 "Asynchronous JavaScript And XML"。</p>

<h3 id="表单提交和_AJAX_请求之间的区别">表单提交和 AJAX 请求之间的区别?</h3>

<p>AJAX 技术主要依靠 {{domxref("XMLHttpRequest")}} (XHR) DOM 对象。它可以构造 HTTP 请求、发送它们,并获取请求结果。</p>

<div class="note">
<p><strong>注意:</strong> 老旧的 AJAX 技术可能不依赖 {{domxref("XMLHttpRequest")}}。例如 <a href="http://en.wikipedia.org/wiki/JSONP" rel="external">JSONP</a><a href="https://developer.mozilla.org/en-US/docs/Core_JavaScript_1.5_Reference:Global_Functions:eval"><code>eval()</code></a> 函数。这也行得通,但是有严重的安全问题,不推荐使用它。使用它的唯一原因是为了不支持 {{domxref("XMLHttpRequest")}}<a href="https://developer.mozilla.org/en-US/docs/JSON">JSON</a>的过时浏览器;但是那些浏览器实在是太古老了!<strong>避免使用这种技术。</strong></p>
</div>

<p>创建之初, {{domxref("XMLHttpRequest")}} 被设计用来将 <a href="/zh-CN/docs/XML" title="/zh-CN/docs/XML">XML</a> 作为传输数据的格式获取和发送。不过,如今 JSON 已经取代了 XML,而且要常用的多,无论这是不是一件好事。</p>

<p>但是 XML 和 JSON 都不适合对表单数据请求编码。 表单数据(<code>application/x-www-form-urlencoded</code>)由 URL编码的键/值对列表组成。为了传输二进制数据,HTTP请求被重新整合成<code>multipart/form-data</code>形式。</p>

<p>如果您控制前端(在浏览器中执行的代码)和后端(在服务器上执行的代码),则可以发送JSON / XML并根据需要处理它们。</p>

<p>但是,如果你想使用第三方服务,没有那么简单。 有些服务只接受表单数据。 也有使用表单数据更简单的情况。 如果数据是键/值对,或是原始二进制数据,以现有的后端工具不需要额外的代码就可以处理它。</p>

<p>那么如何发送这样的数据呢?</p>

<h2 id="发送表单数据">发送表单数据</h2>

<p>一共有三种方式来发送表单数据:包括两种传统的方法和一种利用 {{domxref("XMLHttpRequest/FormData","formData")}}对象的新方法.让我们仔细看一下:</p>

<h3 id="构建_XMLHttpRequest">构建 XMLHttpRequest</h3>

<p>{{domxref("XMLHttpRequest")}} 是进行 HTTP 请求的最安全和最可靠的方式。 要使用{{domxref("XMLHttpRequest")}}发送表单数据,请通过对其进行URL编码来准备数据,并遵守表单数据请求的具体细节。</p>

<div class="note">
<p><strong>备注:</strong>如果想要了解更多关于 <code>XMLHttpRequest</code> 的知识,你可能会对两篇文章感兴趣:<a href="/zh-CN/docs/AJAX/Getting_Started" title="/zh-CN/docs/AJAX/Getting_Started">An introductory article to AJAX</a> 和更高级点的<a href="/zh-CN/docs/DOM/XMLHttpRequest/Using_XMLHttpRequest" title="/zh-CN/docs/DOM/XMLHttpRequest/Using_XMLHttpRequest">using XMLHttpRequest</a>.</p>
</div>

<p>让我们重建之前的这个例子:</p>

<pre class="brush: html">&lt;button type="button" onclick="sendData({test:'ok'})"&gt;点击我!&lt;/button&gt;</pre>

<p>正如你所看到的,HTML实际上没什么改变。 不过,JavaScript变得截然不同了:</p>

<pre class="brush: js">function sendData(data) {
  var XHR = new XMLHttpRequest();
  var urlEncodedData = "";
  var urlEncodedDataPairs = [];
  var name;

  // 将数据对象转换为URL编码的键/值对数组。
  for(name in data) {
    urlEncodedDataPairs.push(encodeURIComponent(name) + '=' + encodeURIComponent(data[name]));
  }

  // 将配对合并为单个字符串,并将所有%编码的空格替换为
  // “+”字符;匹配浏览器表单提交的行为。
  urlEncodedData = urlEncodedDataPairs.join('&amp;').replace(/%20/g, '+');

  // 定义成功数据提交时发生的情况
  XHR.addEventListener('load', function(event) {
    alert('耶! 已发送数据并加载响应。');
  });

  // 定义错误提示
  XHR.addEventListener('error', function(event) {
    alert('哎呀!出问题了。');
  });

  // 建立我们的请求
  XHR.open('POST', 'https://example.com/cors.php');

  // 为表单数据POST请求添加所需的HTTP头
  XHR.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');

  // 最后,发送我们的数据。
  XHR.send(urlEncodedData);
}</pre>

<p>在线演示:</p>

<p>{{EmbedLiveSample("手动构建XMLHttpRequest", "100%", 50)}}</p>

<div class="note">
<p><strong>注:</strong> 当你想要往第三方网站传输数据时,使用{{domxref("XMLHttpRequest")}}会受到同源策略的影响。如果你需要执行跨域请求,你需要熟悉一下<a href="/zh-CN/docs/HTTP/Access_control_CORS" title="/zh-CN/docs/HTTP/Access_control_CORS">CORS和HTTP访问控制</a>.</p>
</div>

<h3 id="使用_XMLHttpRequest_和_the_FormData_object(表单数据对象)">使用 XMLHttpRequest 和 the FormData object(表单数据对象)</h3>

<p>手动建立一个 HTTP 请求非常困难。 幸运的是,最近的 <a href="http://www.w3.org/TR/XMLHttpRequest/">XMLHttpRequest 规范</a>提供了一种方便简单的方法 — 利用{{domxref("XMLHttpRequest/FormData","FormData")}}对象来处理表单数据请求。</p>

<p>{{domxref("XMLHttpRequest/FormData","FormData")}} 对象可以用来构建用于传输的表单数据,或是获取表单元素中的数据来管理它的发送方式。 请注意,{{domxref("XMLHttpRequest/FormData","FormData")}} 对象是“只写”的,这意味着您可以更改它们,但不能检索其内容。</p>

<p>使用这个对象在<a href="/en-US/docs/DOM/XMLHttpRequest/FormData/Using_FormData_Objects">Using FormData Objects</a>中有详细的介绍,不过这里有两个例子:</p>

<h4 id="使用一个独立的_FormData_对象">使用一个独立的 FormData 对象</h4>

<pre class="brush: html">&lt;button type="button" onclick="sendData({test:'ok'})"&gt;点我!&lt;/button&gt;</pre>

<p>你应该会觉得那个HTML示例很熟悉。</p>

<pre class="brush: js">function sendData(data) {
  var XHR = new XMLHttpRequest();
  var FD  = new FormData();

  // 把我们的数据添加到这个FormData对象中
  for(name in data) {
    FD.append(name, data[name]);
  }

  // 定义数据成功发送并返回后执行的操作
  XHR.addEventListener('load', function(event) {
    alert('Yeah! 已发送数据并加载响应。');
  });

  // 定义发生错误时执行的操作
  XHR.addEventListener('error', function(event) {
    alert('Oops! 出错了。');
  });

  // 设置请求地址和方法
  XHR.open('POST', 'https://example.com/cors.php');

  // 发送这个formData对象,HTTP请求头会自动设置
  XHR.send(FD);
}</pre>

<p>在线演示:</p>

<p>{{EmbedLiveSample("向FormData对象中手动添加数据", "100%", 50)}}</p>

<h4 id="使用绑定到表单元素上的_FormData">使用绑定到表单元素上的 FormData</h4>

<p>你也可以把一个 <code>FormData</code> 对象绑定到一个 {{HTMLElement("form")}} 元素上。这会创建一个代表表单中包含元素的 <code>FormData</code></p>

<p>这段HTML是典型的情况:</p>

<pre class="brush: html">&lt;form id="myForm"&gt;
  &lt;label for="myName"&gt;告诉我你的名字:&lt;/label&gt;
  &lt;input id="myName" name="name" value="John"&gt;
  &lt;input type="submit" value="提交"&gt;
&lt;/form&gt;</pre>

<p>但是 JavaScript 接管了这个表单:</p>

<pre class="brush: js">window.addEventListener("load", function () {
  function sendData() {
    var XHR = new XMLHttpRequest();

    // 我们把这个 FormData 和表单元素绑定在一起。
    var FD  = new FormData(form);

    // 我们定义了数据成功发送时会发生的事。
    XHR.addEventListener("load", function(event) {
      alert(event.target.responseText);
    });

    // 我们定义了失败的情形下会发生的事
    XHR.addEventListener("error", function(event) {
      alert('哎呀!出了一些问题。');
    });

    // 我们设置了我们的请求
    XHR.open("POST", "https://example.com/cors.php");

    // 发送的数据是由用户在表单中提供的
    XHR.send(FD);
  }

  // 我们需要获取表单元素
  var form = document.getElementById("myForm");

  // ...然后接管表单的提交事件
  form.addEventListener("submit", function (event) {
    event.preventDefault();

    sendData();
  });
});</pre>

<p>在线演示:</p>

<p>{{EmbedLiveSample("使用绑定到表单元素上的_FormData", "100%", 50)}}</p>

<p>你甚至可以通过使用表单的{{domxref("HTMLFormElement.elements", "elements")}} 属性来更多的参与此过程,来得到一个包含表单里所有数据元素的列表,并且逐一手动管理他们。想了解更多,请参阅这里的例子:{{SectionOnPage("/en-US/docs/Web/API/HTMLFormElement.elements", "Accessing the element list's contents")}}</p>

<h3 id="在隐藏的iframe中构建DOM">在隐藏的iframe中构建DOM</h3>

<p>最古老的异步发送表单数据方法是用 DOM API 构建表单,然后将其数据发送到隐藏的 {{HTMLElement("iframe")}}。 要获得提交的结果,请获取{{HTMLElement("iframe")}}的内容。</p>

<div class="warning">
<p><strong>警告:</strong><strong>不要使用这项技术。</strong>有第三方服务的安全风险,因为它会使你暴露在 <a href="http://en.wikipedia.org/wiki/Cross-site_scripting" rel="external">脚本注入攻击</a> 中. 如果你使用 HTTPS,它会影响 <a href="https://developer.mozilla.org/en-US/docs/JavaScript/Same_origin_policy_for_JavaScript">同源策略</a>, 这可以使 {{HTMLElement("iframe")}} 内容无法访问。然而,该方法可能是你需要支持很古老的浏览器的唯一选择。</p>
</div>

<p>下面是个简单的例子:</p>

<pre class="brush: html">&lt;button onclick="sendData({test:'ok'})"&gt;点击我!&lt;/button&gt;</pre>

<p>所有操作都在下面这段脚本里:</p>

<pre class="brush: js">// 首先创建一个用来发送数据的iframe.
var iframe = document.createElement("iframe");
iframe.name = "myTarget";

// 然后,将iframe附加到主文档
window.addEventListener("load", function () {
  iframe.style.display = "none";
  document.body.appendChild(iframe);
});

// 下面这个函数是真正用来发送数据的.
// 它只有一个参数,一个由键值对填充的对象.
function sendData(data) {
  var name,
      form = document.createElement("form"),
      node = document.createElement("input");

  // 定义响应时发生的事件
  iframe.addEventListener("load", function () {
    alert("Yeah! Data sent.");
  });

  form.action = "http://www.cs.tut.fi/cgi-bin/run/~jkorpela/echo.cgi";
  form.target = iframe.name;

  for(name in data) {
    node.name  = name;
    node.value = data[name].toString();
    form.appendChild(node.cloneNode());
  }

  // 表单元素需要附加到主文档中,才可以被发送。
  form.style.display = "none";
  document.body.appendChild(form);

  form.submit();

  // 表单提交后,就可以删除这个表单,不影响下次的数据发送。
  document.body.removeChild(form);
}</pre>

<p>在线演示:</p>

<p>{{EmbedLiveSample("在DOM中构建一个隐藏的iframe", "100%", 50)}}</p>

<h2 id="处理二进制数据">处理二进制数据</h2>

<p>如果你使用一个含有 <code>&lt;input type="file"&gt;</code> 组件的表格的 {{domxref("XMLHttpRequest/FormData","FormData")}} 对象,传给代码的数据会被自动处理。但是要手动发送二进制数据的话,还有额外的工作要做。</p>

<p>在现代网络上,二进制数据有很多来源:例如{{domxref("FileReader")}} API、{{domxref("HTMLCanvasElement","Canvas")}} API、<a href="/zh-CN/docs/WebRTC/navigator.getUserMedia" title="/zh-CN/docs/WebRTC/navigator.getUserMedia">WebRTC</a> API,等等。不幸的是,一些过时的浏览器无法访问二进制数据,或是需要非常复杂的工作环境。这些遗留问题已经超出了本文的涵盖范围。如果你想了解更多关于 <code>FileReader</code> API的知识,参阅:<a href="/zh-CN/docs/Using_files_from_web_applications" title="/zh-CN/docs/Using_files_from_web_applications">如何在web应用程序中使用文件</a></p>

<p>{{domxref("XMLHttpRequest/FormData","formData")}} 的帮助下,发送二进制数据非常简单,使用 <code>append()</code> 方法就可以了。如果你必须手动进行,那确实会有一些棘手。</p>

<p>在下面的例子中,我们使用了{{domxref("FileReader")}} API来访问二进制数据,然后手动构建多重表单数据请求:</p>

<pre class="brush: html">&lt;form id="myForm"&gt;
  &lt;p&gt;
    &lt;label for="i1"&gt;文本数据:&lt;/label&gt;
    &lt;input id="i1" name="myText" value="一些文本数据"&gt;
  &lt;/p&gt;
  &lt;p&gt;
    &lt;label for="i2"&gt;文件数据:&lt;/label&gt;
    &lt;input id="i2" name="myFile" type="file"&gt;
  &lt;/p&gt;
  &lt;button&gt;提交!&lt;/button&gt;
&lt;/form&gt;</pre>

<p>如你所见,这个 HTML 只是一个标准的 <code>&lt;form&gt;</code>。没有什么神奇的事情发生。“魔法”都在 JavaScript 里:</p>

<pre class="brush: js">// 因为我们想获取 DOM 节点,
// 我们在页面加载时初始化我们的脚本.
window.addEventListener('load', function () {

  // 这些变量用于存储表单数据
  var text = document.getElementById("i1");
  var file = {
        dom    : document.getElementById("i2"),
        binary : null
      };

  // 使用 FileReader API 获取文件内容
  var reader = new FileReader();

  // 因为 FileReader 是异步的, 会在完成读取文件时存储结果
  reader.addEventListener("load", function () {
    file.binary = reader.result;
  });

  // 页面加载时, 如果一个文件已经被选择, 那么读取该文件.
  if(file.dom.files[0]) {
    reader.readAsBinaryString(file.dom.files[0]);
  }

  // 如果没有被选择,一旦用户选择了它,就读取文件。
  file.dom.addEventListener("change", function () {
    if(reader.readyState === FileReader.LOADING) {
      reader.abort();
    }

    reader.readAsBinaryString(file.dom.files[0]);
  });

  // 发送数据是我们需要的主要功能
  function sendData() {
    // 如果存在被选择的文件,等待它读取完成
    // 如果没有, 延迟函数的执行
    if(!file.binary &amp;&amp; file.dom.files.length &gt; 0) {
      setTimeout(sendData, 10);
      return;
    }

    // 要构建我们的多重表单数据请求,
    // 我们需要一个XMLHttpRequest 实例
    var XHR = new XMLHttpRequest();

    // 我们需要一个分隔符来定义请求的每一部分。
    var boundary = "blob";

    // 将我们的主体请求存储于一个字符串中
    var data = "";

    // 所以,如果用户已经选择了一个文件
    if (file.dom.files[0]) {
      // 在请求体中开始新的一部分
      data += "--" + boundary + "\r\n";

      // 把它描述成表单数据
      data += 'content-disposition: form-data; '
      // 定义表单数据的名称
            + 'name="'         + file.dom.name          + '"; '
      // 提供文件的真实名字
            + 'filename="'     + file.dom.files[0].name + '"\r\n';
      // 和文件的MIME类型
      data += 'Content-Type: ' + file.dom.files[0].type + '\r\n';

      // 元数据和数据之间有一条空行。
      data += '\r\n';

      // 将二进制数据添加到主体请求中
      data += file.binary + '\r\n';
    }

    // 文本数据更简单一些
    // 在主体请求中开始一个新的部分
    data += "--" + boundary + "\r\n";

    // 声明它是表单数据,并命名它
    data += 'content-disposition: form-data; name="' + text.name + '"\r\n';
    // 元数据和数据之间有一条空行。
    data += '\r\n';

    // 添加文本数据到主体请求中
    data += text.value + "\r\n";

    // 一旦完成,“关闭”主体请求
    data += "--" + boundary + "--";

    // 定义成功提交数据执行的语句
    XHR.addEventListener('load', function(event) {
      alert('✌!数据已发送且响应已加载。');
    });

    // 定义发生错误时做的事
    XHR.addEventListener('error', function(event) {
      alert('哎呀!出现了一些问题。');
    });

    // 建立请求
    XHR.open('POST', 'https://example.com/cors.php');

    // 添加需要的HTTP头部来处理多重表单数据POST请求
    XHR.setRequestHeader('Content-Type','multipart/form-data; boundary=' + boundary);

    // 最后,发送数据。
    XHR.send(data);
  }

  // 访问表单…
  var form = document.getElementById("myForm");

  // …接管提交事件
  form.addEventListener('submit', function (event) {
    event.preventDefault();
    sendData();
  });
});</pre>

<p>在线演示:</p>

<p>{{EmbedLiveSample("发送二进制数据", "100%", 150)}}</p>

<h2 id="总结">总结</h2>

<p>取决于不同的浏览器,通过 JavaScript 发送数据可能会很简单,也可能会很困难。{{domxref("XMLHttpRequest/FormData","FormData")}} 对象是通用的答案, 所以请毫不犹豫的在旧浏览器上通过polyfill使用它:</p>

<ul>
 <li><a href="https://gist.github.com/3120320" rel="external"> gist</a> 通过 {{domxref("Using_web_workers","Web Workers")}} polyfill 了 <code>FormData</code></li>
 <li><a href="https://github.com/francois2metz/html5-formdata" rel="external">HTML5-formdata</a> 试图 polyfill <code>FormData</code> 对象, 但是它需要 <a href="http://www.w3.org/TR/FileAPI/" rel="external">File API</a></li>
 <li>这个 <a href="https://github.com/jimmywarting/FormData">polyfill</a> 提供了 FormData 所有的大部分新方法(entries, keys, values, 以及对 <code>for...of</code> 的支持)</li>
</ul>

<p>{{PreviousMenuNext("Learn/HTML/Forms/How_to_build_custom_form_widgets", "Learn/HTML/Forms/HTML_forms_in_legacy_browsers", "Learn/HTML/Forms")}}</p>

<h2 id="In_this_module">In this module</h2>

<ul>
 <li><a href="/zh-CN/docs/Learn/HTML/Forms/Your_first_HTML_form">Your first HTML form</a></li>
 <li><a href="/zh-CN/docs/Learn/HTML/Forms/How_to_structure_an_HTML_form">How to structure an HTML form</a></li>
 <li><a href="/zh-CN/docs/Learn/HTML/Forms/The_native_form_widgets">The native form widgets</a></li>
 <li><a href="/zh-CN/docs/Learn/HTML/Forms/Sending_and_retrieving_form_data">Sending form data</a></li>
 <li><a href="/zh-CN/docs/Learn/HTML/Forms/Form_validation">Form data validation</a></li>
 <li><a href="/zh-CN/docs/Learn/HTML/Forms/How_to_build_custom_form_widgets">How to build custom form widgets</a></li>
 <li><a href="/zh-CN/docs/Learn/HTML/Forms/Sending_forms_through_JavaScript">Sending forms through JavaScript</a></li>
 <li><a href="/zh-CN/docs/Learn/HTML/Forms/HTML_forms_in_legacy_browsers">HTML forms in legacy browsers</a></li>
 <li><a href="/zh-CN/docs/Learn/HTML/Forms/Styling_HTML_forms">Styling HTML forms</a></li>
 <li><a href="/zh-CN/docs/Learn/HTML/Forms/Advanced_styling_for_HTML_forms">Advanced styling for HTML forms</a></li>
 <li><a href="/zh-CN/docs/Learn/HTML/Forms/Property_compatibility_table_for_form_widgets">Property compatibility table for form widgets </a></li>
</ul>