为您推荐:
精华内容
最热下载
问答
  • 5星
    8.82MB weixin_44573410 2021-03-02 23:35:55
  • 1.33MB loushuai 2016-10-29 20:49:39
  • 3星
    22.67MB u011433684 2016-02-20 12:58:01
  • 12.65MB weixin_38744375 2019-09-23 16:52:18
  •  The eventual following stack trace is caused by an error thrown for debugging purposes as well as to attempt to terminate the thread which caused the illegal access, and...

    1. 系统框架加载正常,但是Tomcat启动失败,报错如下:

     

    Log代码   收藏代码
    1. 2013-7-26 17:18:33 org.apache.catalina.core.StandardContext startInternal  
    2. 严重: Error listenerStart  
    3. 2013-7-26 17:18:34 org.apache.catalina.util.SessionIdGenerator createSecureRandom  
    4. 信息: Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [452] milliseconds.  
    5. 2013-7-26 17:18:34 org.apache.catalina.core.StandardContext startInternal  
    6. 严重: Context [] startup failed due to previous errors  
    7. 2013-7-26 17:18:34 org.apache.coyote.AbstractProtocol start  
    8. 信息: Starting ProtocolHandler ["http-apr-8080"]  
    9. 2013-7-26 17:18:34 org.apache.coyote.AbstractProtocol start  
    10. 信息: Starting ProtocolHandler ["ajp-apr-8009"]  
    11. 2013-7-26 17:18:34 org.apache.catalina.startup.Catalina start  
    12. 信息: Server startup in 9576 ms  
    13. diamond client log path : /home/admin/xxx/target/../logs/xx-client.log  
    14. 2013-7-26 17:18:57 org.apache.catalina.loader.WebappClassLoader loadClass  
    15. 信息: Illegal access: this web application instance has been stopped already.  Could not load javax.xml.parsers.ParserConfigurationException.  The eventual following stack trace is caused by an error thrown for debugging purposes as well as to attempt to terminate the thread which caused the illegal access, and has no functional impact.  
    16. java.lang.IllegalStateException  
    17.         at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1587)  
    18.         at com.taobao.tomcat.classloader.TomcatWebAppClassLoader.loadClass(TomcatWebAppClassLoader.java:37)  
    19.         at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1546)  
    20.         at com.taobao.hsf.container.HSFClassLoaderDelegateHook.postFindClass(HSFClassLoaderDelegateHook.java:96)  
    21.         at org.eclipse.osgi.framework.internal.core.BundleLoader.searchHooks(BundleLoader.java:495)  
    22.         at org.eclipse.osgi.framework.internal.core.BundleLoader.findClassInternal(BundleLoader.java:461)  
    23.         at org.eclipse.osgi.framework.internal.core.BundleLoader.findClass(BundleLoader.java:397)  
    24.         at org.eclipse.osgi.framework.internal.core.BundleLoader.findClass(BundleLoader.java:385)  
    25.         at org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader.loadClass(DefaultClassLoader.java:87)  

     

    2. 分析

          查看上述日志,可能会很明显的看到下面很多的exception。都是this web application instance has been stopped already以及Could not load XX Class。但是不要被这些异常迷惑,他们只是结果而不是原因。

    1). 细致一点可以查看到,哪些日志是Tomcat是启动完成之后报错的。Server startup in 9576 ms

    2). Tomcat日志中有:严重: Error listenerStart。

    3). 除了Error listenerStart没有详细日志,但是可以知道是web.xml中listener初始化的时候出错了。

     

     

     

    3. 处理

    1). 在WEB-INF/classes目录下新建logging.properties

     

    Log4j代码   收藏代码
    1. handlers = org.apache.juli.FileHandler, java.util.logging.ConsoleHandler    
    2.     
    3. ############################################################    
    4. # Handler specific properties.    
    5. # Describes specific configuration info for Handlers.    
    6. ############################################################    
    7.     
    8. org.apache.juli.FileHandler.level = FINE    
    9. org.apache.juli.FileHandler.directory = ${应用目录}/logs    
    10. org.apache.juli.FileHandler.prefix = error-debug.    
    11.     
    12. java.util.logging.ConsoleHandler.level = FINE    
    13. java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter    

    2). 重新启动应用,指定目录下会有一个error-debug.2013-07-07.log的错误日志,或者错误日志生成在jboss_stdout.log中。(日志在Error listenerStart 到 Server startup in 9576 ms之间)

     

    4. 结果

     

    Tomcat log代码   收藏代码
    1. 2013-7-29 9:58:11 org.apache.catalina.core.ApplicationContext log  
    2. 信息: Initializing Spring root WebApplicationContext  
    3. 2013-7-29 9:58:16 org.apache.catalina.core.StandardContext listenerStart  
    4. 严重: Exception sending context initialized event to listener instance of class com.alibaba.citrus.webx.context.WebxContextLoaderListener  
    5. org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Failed to import bean definitions from URL location [classpath:web/common/uris.xml]  
    6. Offending resource: ServletContext resource [/WEB-INF/webx.xml]; nested exception is org.springframework.beans.factory.BeanDefinitionStoreException: Unexpected exception parsing XML document from class path resource [web/common/uris.xml]; nested exception is java.lang.NoSuchMethodError: org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseBeanDefinitionAttributes(Lorg/w3c/dom/Element;Ljava/lang/String;Lorg/springframework/beans/factory/config/BeanDefinition;Lorg/springframework/beans/factory/support/AbstractBeanDefinition;)Lorg/springframework/beans/factory/support/AbstractBeanDefinition;  
    7. Caused by: org.springframework.beans.factory.BeanDefinitionStoreException: Unexpected exception parsing XML document from class path resource [web/common/uris.xml]; nested exception is java.lang.NoSuchMethodError: org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseBeanDefinitionAttributes(Lorg/w3c/dom/Element;Ljava/lang/String;Lorg/springframework/beans/factory/config/BeanDefinition;Lorg/springframework/beans/factory/support/AbstractBeanDefinition;)Lorg/springframework/beans/factory/support/AbstractBeanDefinition;  
    8. Caused by: java.lang.NoSuchMethodError: org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseBeanDefinitionAttributes(Lorg/w3c/dom/Element;Ljava/lang/String;Lorg/springframework/beans/factory/config/BeanDefinition;Lorg/springframework/beans/factory/support/AbstractBeanDefinition;)Lorg/springframework/beans/factory/support/AbstractBeanDefinition;  
    9.         at com.alibaba.citrus.springext.util.SpringExtUtil.parseBeanDefinitionAttributes(SpringExtUtil.java:223)  
    10.         at com.alibaba.citrus.service.uribroker.impl.URIBrokerServiceDefinitionParser.doParse(URIBrokerServiceDefinitionParser.java:48)  
    11.         at org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser.parseInternal(AbstractSingleBeanDefinitionParser.java:81)  
    12.         at org.springframework.beans.factory.xml.AbstractBeanDefinitionParser.parse(AbstractBeanDefinitionParser.java:56)  
    13.         at org.springframework.beans.factory.xml.NamespaceHandlerSupport.parse(NamespaceHandlerSupport.java:69)  
    14.         at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1123)  
    15.         at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1113)  
    16.         at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:133)  
    17.         at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.registerBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:90)  
    18.         at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.registerBeanDefinitions(XmlBeanDefinitionReader.java:468)  
    19.         at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:363)  

     

    5. 看到上述日志,问题就很容易解决了,明显是由于spring的类冲突导致,分析之后发现,是由于spring-2.0.7与spring-2.5.6的类冲突导致。

     

    6. 如果上述不行,可以尝试:

    http://www.havenliu.com/web/298.html

    http://grails.1312388.n4.nabble.com/Deployment-problems-td4628710.html

    http://xpenxpen.iteye.com/blog/1545648

    转载于:https://www.cnblogs.com/qiumingcheng/p/7198750.html

    展开全文
    weixin_30436891 2017-07-18 09:31:00
  • 4.9MB weixin_38743602 2019-09-23 23:33:40
  • 37KB weixin_38601446 2021-02-22 21:41:12
  • 39.21MB weixin_43960172 2019-01-08 23:12:29
  • 232KB weixin_38628243 2021-04-22 18:01:39
  • 涌出,喷出 (up out forth)。 vt. 使涌出,使喷出,使流出。 eyes welling tears of joy 涌出欢乐泪水的眼睛。 a fountain welling pure waters 喷射清水的喷泉。 well over 溢满。 adv. ( better best ) 1.好。 2....

    well    adv. ( better best ) 1.好。 2. ...

    cluster    n. 1.丛集;丛;(葡萄等的)串,挂;(花)团;(秧) ...

    a cluster of    一串/丛; 一串,一束,一群; 一群、一组; 一群;一组

    cluster    n. 1.丛集;丛;(葡萄等的)串,挂;(花)团;(秧)蔸;组。 2.(蜂、人等的)丛,群,群集。 3.【物理学】聚集,组件;【化学】类族,基;(原子)团;【天文学】星团。 4.【美军】(表示又一个同等勋章的)金属片。 5.【语音】音丛,音群,义丛,词组。 6.集中建筑群〔在一大片土地上集中兴建住宅,以提供较大的公共休息场所〕。 in a cluster 成串的;成团[群]的。 vi.,vt. (使)成群;(使)群集。 clustered column 【建筑】簇柱。

    as well    不防; 不用于否定句; 常放于句末; 放在句末; 更常用于句末; 记住了 也,还; 就…之限度,在…范围内; 同样,也,还; 同样的; 也,同样,不妨; 也,又; 也..; 也;和; 也,此外; 也,还,又; 也,还有; 也,同样; 也,一样; 也,又,同样,至于; 也,又,同样地; 也;并;和;同; 意思是“亦; 用于口语中; 用在句尾相当于

    as well as    (除...之处)也,即...又; 不但...而且.; 除……外,如同……一样;以及; 除之外; 除…之外(也),和; 和,还有/; 和…一样好; 和,同,既……又……,不仅……而且……; 和也; 和,以及; 及其; 既…又,和…一样(都); 既…又;除…之外; 强调前者; 同,和,也; 同一样; 也, 还, 而且; 也, 又; 也,又,还; 也;和; 也;还;而且; 也,又; 与……同样好

    well    n. 1.井。 2.泉水;源头,来源。 3.坑,穴,凹处;【矿物】矿井,竖坑;【军事】(地雷的)井坑;【建筑】井孔;通风竖井;楼梯井,升降机井道。 4.【机械工程】(插仪表的)插孔,(油轴承中的)室。 5.(渔船的)养鱼舱;(书桌上的)墨水池。 6.(阶梯形教室的)讲坛;(法庭的)律师席。 an artesian well 自流井,喷水井。 an oil well 油井。 a landing gear well 【航空】起落架舱。 vi. 涌出,喷出 (up out forth)。 vt. 使涌出,使喷出,使流出。 eyes welling tears of joy 涌出欢乐泪水的眼睛。 a fountain welling pure waters 喷射清水的喷泉。 well over 溢满。 adv. ( better best ) 1.好。 2.适当,恰当,合适,正好。 3.足够;完全;充分。 4.很,相当。 5.大概;很可能。 6.有理。 7.关心地,优待;赞扬。 That is well said. 说得好。 W- done! 好!做得好! It was well done of you to come. 你来得正好。 live well 生活得优越。 It may well be true. 这大概是真的。 You may well say that. 你那么说也有理。 be treated well 受优待,得到照顾。 as well 又,也;同样 ( He is a scientist, but he is a poet as well. 他是科学家又是诗人)。 as well as (除…之外)又…,既…又…,和…一样;…不用说 ( He gave me clothes as well as food. 他给我食物又给我衣服。 As well be hung for a sheep as a lamb. 一不做二不休,要干索性大干)。 be well on in life 年纪很大。 be well past (forty) 远远超过(四十岁)。 be well up in the list 列在最前面几个当中。 do oneself well 日子过得很好[富裕]。 may as well 还是…的好 ( You may as well go at once. 你还是马上去的好)。 may (as) well 也不坏,也有好处 ( We might well make the experiment. 我们做做实验也不坏)。 might as well 等于,不如 ( You might as well throw your money into the sea as lend it to him. 你借钱给他等于丢在海里)。 stand well with ...中…的意,受…欢迎。 W- met! 幸会,幸会! well off = well to do 经济宽裕,富裕。 well over (a hundred) 远远超过(一百)。 adj. (better best) 1.健康(的),痊愈(的),健全(的)。 2.适当(的),得当(的)。 3.正好(的),好;满意(的)。 a well man 〔美国〕健康的人。 Quite well, thank you. 我很好,谢谢你。 It is well you came along. 你来得正好。 be as well …也好 ( It may be as well to explain. 说明说明也好)。 It is all very well, but ...这好倒是很好,可是…。 very well 非常好;非常健康;好。 Well and good. (既然…,那就)好吧,没法了;那也好。 well enough 相当好。 n. 好,满意。 I wish him well. 我希望他幸福。 Let well alone. 那样很好,别去管它;不要画蛇添足;不要多事。 int. 〔表示吃惊、安心、让步、谈话的重新开始、期待、断念等的意思〕 1.哎呀,唷;怎么! 2.好啦。 3.好,那么,那么说的时候。 4.喔,这个;可是,且说,至于。 5.后来。 6.算啦。 W-, to be sure! = W-, I never! = W- now! 唷,这倒奇怪! W-, here we are at last. 好啦,好容易到了。 W-, do as you please. 那么[好吧],随你的便罢。 W- then, say no more about it. 那么这事情就这样算啦。 W-, who was it 可是,那是谁呢? W- then 后来怎样啦? W-, it cannot be helped. 算了,没办法。 n. -ness 〔美口〕身心健康(a wellness center [program]身心健康中心[计划])。

    展开全文
    weixin_33350741 2021-04-21 21:15:32
  • 5星
    2.65MB chenws1972 2011-04-25 22:01:01
  • 4星
    11MB pxsaner 2018-04-20 11:44:09
  • 19Scripting FormsWHAT’S IN ...➤➤ Understanding form basics➤➤ Text box validation and interaction➤➤ Working with other form controlsWROX.COM DOWNLOADS FOR THIS CHAPTERPlease note that all the...

    19

    Scripting Forms

    WHAT’S IN THIS CHAPTER?

    ➤➤ Understanding form basics

    ➤➤ Text box validation and interaction

    ➤➤ Working with other form controls

    WROX.COM DOWNLOADS FOR THIS CHAPTER

    Please note that all the code examples for this chapter are available as a part of this chapter’s

    code download on the book’s website at www.wrox.com/go/projavascript4e on the Down-

    load Code tab.

    One of the original uses of JavaScript was to offload some form-processing responsibilities onto

    the browser instead of relying on the server to do it all. Although the web and JavaScript have

    evolved since that time, web forms remain more or less unchanged. The failure of web forms to

    provide out-of-the-box solutions for common problems led developers to use JavaScript not just

    for form validation but also to augment the default behavior of standard form controls.

    FORM BASICS

    Web forms are represented by the

    element in HTML and by the HTMLFormElement type

    in JavaScript. The HTMLFormElement type inherits from HTMLElement and therefore has all of

    the same default properties as other HTML elements. However, HTMLFormElement also has the

    following additional properties and methods:

    ➤➤ acceptCharset—The character sets that the server can process; equivalent to the

    HTML accept-charset attribute.

    Professional JavaScript® for Web Developers, Fourth Edition. Matt Frisbie.

    © 2020 John Wiley & Sons, Inc. Published 2020 by John Wiley & Sons, Inc.

    708 ❘ CHAPTER 19  Scripting Forms

    ➤➤ action—The URL to send the request to; equivalent to the HTML action attribute.

    ➤➤ elements—An HTMLCollection of all controls in the form.

    ➤➤ enctype—The encoding type of the request; equivalent to the HTML enctype attribute.

    ➤➤ length—The number of controls in the form.

    ➤➤ method—The type of HTTP request to send, typically "get" or "post"; equivalent to the

    HTML method attribute.

    ➤➤ name—The name of the form; equivalent to the HTML name attribute.

    ➤➤ reset()—Resets all form fields to their default values.

    ➤➤ submit()—Submits the form.

    ➤➤ target—The name of the window to use for sending the request and receiving the response;

    equivalent to the HTML target attribute.

    References to

    elements can be retrieved in a number of different ways. The most c­ ommon

    way is to treat them as any other elements and assign the id attribute, allowing the use of

    g­ etElementById(), as in the following example:

    let form = document.getElementById("form1");

    All forms on the page can also be retrieved from document.forms collection. Each form can be

    accessed in this collection by numeric index and by name, as shown in the following examples:

    // get the first form in the page

    let firstForm = document.forms[0];

    // get the form with a name of "form2"

    let myForm = document.forms["form2"];

    Older browsers, or those with strict backwards compatibility, also add each form with a name as

    a property of the document object. For instance, a form named "form2" could be accessed via

    ­document.form2. This approach is not recommended, because it is error-prone and may be removed

    from browsers in the future.

    Note that forms can have both an id and a name and that these values need not be the same.

    Submitting Forms

    Forms are submitted when a user interacts with a submit button or an image button. Submit b­ uttons

    are defined using either the element or the element with a type attribute of "­ submit",

    and image buttons are defined using the element with a type attribute of "image". All of

    the following, when clicked, will submit a form in which the button resides:

    Submit Form

    Form Basics  ❘  709

    If any one of these types of buttons is within a form that has a submit button, pressing Enter on the

    keyboard while a form control has focus will also submit the form. (The one exception is a textarea,

    within which Enter creates a new line of text.) Note that forms without a submit button will not be

    submitted when Enter is pressed.

    When a form is submitted in this manner, the submit event fires right before the request is sent to the

    server. This gives you the opportunity to validate the form data and decide whether to allow the form

    submission to occur. Preventing the event’s default behavior cancels the form submission. For exam-

    ple, the following prevents a form from being submitted:

    let form = document.getElementById("myForm");

    form.addEventListener("submit", (event) => {

    // prevent form submission

    event.preventDefault();

    });

    The preventDefault() method stops the form from being submitted. Typically, this functionality is

    used when data in the form is invalid and should not be sent to the server.

    It’s possible to submit a form programmatically by calling the submit() method from JavaScript.

    This method can be called at any time to submit a form and does not require a submit button to be

    present in the form to function appropriately. Here’s an example:

    let form = document.getElementById("myForm");

    // submit the form

    form.submit();

    When a form is submitted via submit(), the submit event does not fire, so be sure to do data valida-

    tion before calling the method.

    One of the biggest issues with form submission is the possibility of submitting the form twice. Users

    sometimes get impatient when it seems like nothing is happening and may click a submit button mul-

    tiple times. The results can be annoying (because the server processes duplicate requests) or damag-

    ing (if the user is attempting a purchase and ends up placing multiple orders). There are essentially

    two ways to solve this problem: disable the submit button once the form is submitted, or use the

    ­onsubmit event handler to cancel any further form submissions.

    Resetting Forms

    Forms are reset when the user clicks a reset button. Reset buttons are created using either the

    or the element with a type attribute of "reset", as in these examples:

    Reset Form

    710 ❘ CHAPTER 19  Scripting Forms

    Either of these buttons will reset a form. When a form is reset, all of the form fields are set back to

    the values they had when the page was first rendered. If a field was originally blank, it becomes blank

    again, whereas a field with a default value reverts to that value.

    When a form is reset by the user clicking a reset button, the reset event fires. This event gives you

    the opportunity to cancel the reset if necessary. For example, the following prevents a form from

    being reset:

    let form = document.getElementById("myForm");

    form.addEventListener("reset", (event) => {

    event.preventDefault();

    });

    As with form submission, resetting a form can be accomplished via JavaScript using the reset()

    method, as in this example:

    let form = document.getElementById("myForm");

    // reset the form

    form.reset();

    Unlike the submit() method’s functionality, reset() fires the reset event the same as if a reset

    button were clicked.

    NOTE  Form resetting is typically a frowned-upon approach to web form design.

    It’s often disorienting to the user and, when triggered accidentally, can be quite

    frustrating. There’s almost never a need to reset a form. It’s often enough to pro-

    vide a cancel button that takes the user back to the previous page rather than

    explicitly revert all values in the form.

    Form Fields

    Form elements can be accessed in the same ways as any other elements on the page using native

    DOM methods. Additionally, all form elements are parts of an elements collection that is a property

    of each form. The elements collection is an ordered list of references to all form fields in the form

    and includes all , , , , and elements. Each form

    field appears in the elements collection in the order in which it appears in the markup, indexed by

    both position and name. Here are some examples:

    let form = document.getElementById("form1");

    // get the first field in the form

    let field1 = form.elements[0];

    // get the field named "textbox1"

    let field2 = form.elements["textbox1"];

    // get the number of fields

    let fieldCount = form.elements.length;

    Form Basics  ❘  711

    If a name is in use by multiple form controls, as is the case with radio buttons, then an H­ TMLCollection

    is returned containing all of the elements with the name. For example, consider the following HTML

    snippet:

    • Red
    • Green
    • Blue

    The form in this HTML has three radio controls that have "color" as their name, which ties the

    fields together. When accessing elements["color"], a NodeList is returned, containing all three

    elements; when accessing elements[0], however, only the first element is returned. Consider

    this example:

    let form = document.getElementById("myForm");

    let colorFields = form.elements["color"];

    console.log(colorFields.length); // 3

    let firstColorField = colorFields[0]; // true

    let firstFormField = form.elements[0];

    console.log(firstColorField === firstFormField);

    This code shows that the first form field, accessed via form.elements[0], is the same as the first ele-

    ment contained in form.elements["color"].

    NOTE  It’s possible to access elements as properties of a form as well, such as

    form[0] to get the first form field and form["color"] to get a named field. These

    properties always return the same thing as their equivalent in the elements

    collection. This approach is provided for backwards compatibility with older

    browsers and should be avoided when possible in favor of using elements.

    Common Form-Field Properties

    With the exception of the

    element, all form fields share a common set of properties.

    Because the type represents many form fields, some properties are used only with certain

    field types, whereas others are used regardless of the field type. The common form-field properties

    and methods are as follows:

    ➤➤ disabled—A Boolean indicating if the field is disabled.

    ➤➤ form—A pointer to the form that the field belongs to. This property is read-only.

    ➤➤ name—The name of the field.

    ➤➤ readOnly—A Boolean indicating if the field is read-only.

    ➤➤ tabIndex—Indicates the tab order for the field.

    712 ❘ CHAPTER 19  Scripting Forms

    ➤➤ type—The type of the field: "checkbox", "radio", and so on.

    ➤➤ value—The value of the field that will be submitted to the server. For file-input fields, this

    property is read only and simply contains the file’s path on the computer.

    With the exception of the form property, JavaScript can change all other properties dynamically.

    Consider this example:

    let form = document.getElementById("myForm");

    let field = form.elements[0];

    // change the value

    field.value = "Another value";

    // check the value of form

    console.log(field.form === form); // true

    // set focus to the field

    field.focus();

    // disable the field

    field.disabled = true;

    // change the type of field (not recommended, but possible for )

    field.type = "checkbox";

    The ability to change form-field properties dynamically allows you to change the form at any time

    and in almost any way. For example, a common problem with web forms is users’ tendency to click

    the submit button twice. This is a major problem when credit-card orders are involved because it may

    result in duplicate charges. A very common solution to this problem is to disable the submit button

    once it’s been clicked, which is possible by listening for the submit event and disabling the submit

    button when it occurs. The following code accomplishes this:

    // Code to prevent multiple form submissions

    let form = document.getElementById("myForm");

    form.addEventListener("submit", (event) => {

    let target = event.target;

    // get the submit button

    let btn = target.elements["submit-btn"];

    // disable the submit button

    btn.disabled = true;

    });

    This code attaches an event handler on the form for the submit event. When the event fires, the

    submit button is retrieved and its disabled property is set to true. Note that you cannot attach an

    onclick event handler to the submit button to do this because of a timing issue across browsers:

    some browsers fire the click event before the form’s submit event, some after. For browsers that

    fire click first, the button will be disabled before the submission occurs, meaning that the form will

    never be submitted. Therefore it’s better to disable the submit button using the submit event. This

    approach won’t work if you are submitting the form without using a submit button because, as stated

    before, the submit event is fired only by a submit button.

    Form Basics  ❘  713

    The type property exists for all form fields except

    . For elements, this value is

    equal to the HTML type attribute. For other elements, the value of type is set as described in the

    following table.

    DESCRIPTION SAMPLE HTML VALUE OF T YPE

    Single-select list ... "select-one"

    Multi-select list ... "select-multiple"

    Custom button ... "submit"

    Custom nonsubmit button ... "button"

    Custom reset button ... "reset"

    Custom submit button ... "submit"

    For and elements, the type property can be changed dynamically, whereas the

    element’s type property is read-only.

    Common Form-Field Methods

    Each form field has two methods in common: focus() and blur(). The focus() method sets the

    browser’s focus to the form field, meaning that the field becomes active and will respond to keyboard

    events. For example, a text box that receives focus displays its caret and is ready to accept input. The

    focus() method is most often employed to call the user’s attention to some part of the page. It’s quite

    common, for instance, to have the focus moved to the first field in a form when the page is loaded.

    This can be accomplished by listening for the load event and then calling focus() on the first field,

    as in the following example:

    window.addEventListener("load", (event) => {

    document.forms[0].elements[0].focus();

    });

    Note that this code will cause an error if the first form field is an element with a type of

    "hidden" or if the field is being hidden using the display or visibility CSS property.

    HTML5 introduces an autofocus attribute for form fields that causes supporting browsers to auto-

    matically set the focus to that element without the use of JavaScript. For example:

    In order for the previous code to work correctly with autofocus, you must first detect if it has been

    set. If autofocus is set, you should not call focus():

    window.addEventListener("load", (event) => {

    let element = document.forms[0].elements[0];

    if (element.autofocus !== true) {

    element.focus();

    console.log("JS focus");

    }

    });

    714 ❘ CHAPTER 19  Scripting Forms

    Because autofocus is a Boolean attribute, the value of the autofocus property will be true in

    supporting browsers. (It will be the empty string in browsers without support.) So this code calls

    focus() only if the autofocus property is not equal to true, ensuring forwards compatibility. The

    autofocus property is supported in most modern browsers. It only lacks support in iOS Safari,

    Opera Mini, and Internet Explorer 10 and below.

    NOTE  By default, only form elements can have focus set to them. It’s possible to

    allow any element to have focus by setting its tabIndex property to –1 and then

    calling focus(). The only browser that doesn’t support this technique is Opera.

    The opposite of focus() is blur(), which removes focus from the element. When blur() is called,

    focus isn’t moved to any element in particular; it’s just removed from the field on which it was called.

    This method was used early in web development to create read-only fields before the readonly

    attribute was introduced. There’s rarely a need to call blur(), but it’s available if necessary. Here’s

    an example:

    document.forms[0].elements[0].blur();

    Common Form-Field Events

    All form fields support the following three events in addition to mouse, keyboard, mutation, and

    HTML events:

    ➤➤ blur—Fires when the field loses focus.

    ➤➤ change—Fires when the field loses focus and the value has changed for and

    elements; also fires when the selected option changes for elements.

    ➤➤ focus—Fires when the field gets focus.

    Both the blur and the focus events fire because of users manually changing the field’s focus, as well

    as by calling the blur() and focus() methods, respectively. These two events work the same way for

    all form fields. The change event, however, fires at different times for different controls. For

    and elements, the change event fires when the field loses focus and the value has

    changed since the time the control got focus. For elements, however, the change event fires

    whenever the user changes the selected option; the control need not lose focus for change to fire.

    The focus and blur events are typically used to change the user interface in some way, to provide

    either visual cues or additional functionality (such as showing a drop-down menu of options for a

    text box). The change event is typically used to validate data that was entered into a field. For exam-

    ple, consider a text box that expects only numbers to be entered. The focus event may be used to

    change the background color to more clearly indicate that the field has focus, the blur event can be

    used to remove that background color, and the change event can change the background color to red

    if nonnumeric characters are entered. The following code accomplishes this:

    let textbox = document.forms[0].elements[0];

    Scripting Text Boxes  ❘  715

    textbox.addEventListener("focus", (event) => {

    let target = event.target;

    if (target.style.backgroundColor != "red") {

    target.style.backgroundColor = "yellow";

    }

    });

    textbox.addEventListener("blur", (event) => {

    let target = event.target;

    target.style.backgroundColor = /[^\d]/.test(target.value) ? "red" : "";

    });

    textbox.addEventListener("change", (event) => {

    let target = event.target;

    target.style.backgroundColor = /[^\d]/.test(target.value) ? "red" : "";

    });

    The onfocus event handler simply changes the background color of the text box to yellow, more

    clearly indicating that it’s the active field. The onblur and onchange event handlers turn the back-

    ground color red if any nonnumeric character is found. To test for a nonnumeric character, use

    a simple regular expression against the text box’s value. This functionality has to be in both the

    onblur and onchange event handlers to ensure that the behavior remains consistent regardless of text

    box changes.

    NOTE  The relationship between the blur and the change events is not strictly

    defined. In some browsers, the blur event fires before change; in others, it’s the

    opposite. You can’t depend on the order in which these events fire, so use care

    whenever they are required.

    SCRIPTING TEXT BOXES

    There are two ways to represent text boxes in HTML: a single-line version using the element

    and a multiline version using . These two controls are very similar and behave in similar

    ways most of the time. There are, however, some important differences.

    By default, the element displays a text box, even when the type attribute is omitted (the

    default value is "text"). The size attribute can then be used to specify how wide the text box should

    be in terms of visible characters. The value attribute specifies the initial value of the text box, and

    the maxlength attribute specifies the maximum number of characters allowed in the text box. So to

    create a text box that can display 25 characters at a time but has a maximum length of 50, you can

    use the following code:

    The element always renders a multiline text box. To specify how large the text box

    should be, you can use the rows attribute, which specifies the height of the text box in number of

    characters, and the cols attribute, which specifies the width in number of characters, similar to size

    716 ❘ CHAPTER 19  Scripting Forms

    for an element. Unlike , the initial value of a must be enclosed between

    and , as shown here:

    initial value

    Also unlike the element, a cannot specify the maximum number of characters

    allowed using HTML.

    Despite the differences in markup, both types of text boxes store their contents in the value property.

    The value can be used to read the text box value and to set the text box value, as in this example:

    let textbox = document.forms[0].elements["textbox1"];

    console.log(textbox.value);

    textbox.value = "Some new value";

    You should use the value property to read or write text box values rather than to use standard DOM

    methods. For instance, don’t use setAttribute() to set the value attribute on an element,

    and don’t try to modify the first child node of a element. Changes to the value property

    aren’t always reflected in the DOM either, so it’s best to avoid using DOM methods when dealing

    with text box values.

    Text Selection

    Both types of text boxes support a method called select(), which selects all of the text in a text box.

    Most browsers automatically set focus to the text box when the select() method is called (Opera

    does not). The method accepts no arguments and can be called at any time. Here’s an example:

    let textbox = document.forms[0].elements["textbox1"];

    textbox.select();

    It’s quite common to select all of the text in a text box when it gets focus, especially if the text box

    has a default value. The thinking is that it makes life easier for users when they don’t have to delete

    text separately. This pattern is accomplished with the following code:

    textbox.addEventListener("focus", (event) => {

    event.target.select();

    });

    With this code applied to a text box, all of the text will be selected as soon as the text box gets focus.

    This can greatly aid the usability of forms.

    The select Event

    To accompany the select() method, there is a select event. The select event fires when text is

    selected in the text box. Exactly when the event fires differs from browser to browser. In Internet

    Explorer 9+, Opera, Firefox, Chrome, and Safari, the select event fires once the user has finished

    selecting text, whereas in Internet Explorer 8 and earlier it fires as soon as one letter is selected. The

    select event also fires when the select() method is called. Here’s a simple example:

    let textbox = document.forms[0].elements["textbox1"];

    textbox.addEventListener("select", (event) => {

    console.log('Text selected: ${textbox.value}');

    });

    Scripting Text Boxes  ❘  717

    Retrieving Selected Text

    Although useful for understanding when text is selected, the select event provides no information

    about what text has been selected. HTML5 solved this issue by introducing some extensions to allow

    for better retrieval of selected text. The specification approach adds two properties to text boxes:

    selectionStart and selectionEnd. These properties contain zero-based numbers indicating the

    text-selection boundaries (the offset of the beginning of text selection and the offset of end of text

    selection, respectively). So, to get the selected text in a text box, you can use the following code:

    function getSelectedText(textbox){

    return textbox.value.substring(textbox.selectionStart,

    textbox.selectionEnd);

    }

    Because the substring() method works on string offsets, the values from selectionStart and

    selectionEnd can be passed in directly to retrieve the selected text.

    This solution works for Internet Explorer 9+, Firefox, Safari, Chrome, and Opera. Internet Explorer 8

    and earlier don’t support these properties, so a different approach is necessary.

    Older versions of Internet Explorer have a document.selection object that contains text-selection

    information for the entire document, which means you can’t be sure where the selected text is on the

    page. When used in conjunction with the select event, however, you can be assured that the selec-

    tion is inside the text box that fired the event. To get the selected text, you must first create a range

    and then extract the text from it, as in the following:

    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;

    }

    }

    This function has been modified to determine whether to use the Internet Explorer approach to

    selected text. Note that document.selection doesn’t need the textbox argument at all.

    Partial Text Selection

    HTML5 also specifies an addition to aid in partially selecting text in a text box. The

    s­ etSelectionRange() method, originally implemented by Firefox, is now available on all text

    boxes in addition to the select() method. This method takes two arguments: the index of the first

    character to select and the index at which to stop the selection (the same as the string’s substring()

    method). Here are some examples:

    textbox.value = "Hello world!"

    // select all text

    textbox.setSelectionRange(0, textbox.value.length); // "Hello world!"

    // select first three characters

    textbox.setSelectionRange(0, 3); // "Hel"

    718 ❘ CHAPTER 19  Scripting Forms

    // select characters 4 through 6

    textbox.setSelectionRange(4, 7); // "o w"

    To see the selection, you must set focus to the text box either immediately before or after a call to

    setSelectionRange(). This approach works for Internet Explorer 9, Firefox, Safari, Chrome,

    and Opera.

    Internet Explorer 8 and earlier allow partial text selection through the use of ranges. To select

    part of the text in a text box, you must first create a range and place it in the correct position by

    using the createTextRange() method that Internet Explorer provides on text boxes and using the

    moveStart() and moveEnd() range methods to move the range into position. Before calling these

    methods, however, you need to collapse the range to the start of the text box using collapse(). After

    that, moveStart() moves both the starting and the end points of the range to the same position. You

    can then pass in the total number of characters to select as the argument to moveEnd(). The last step

    is to use the range’s select() method to select the text, as shown in these examples:

    textbox.value = "Hello world!";

    var range = textbox.createTextRange();

    // select all text // "Hello world!"

    range.collapse(true);

    range.moveStart("character", 0);

    range.moveEnd("character", textbox.value.length);

    range.select();

    // select first three characters

    range.collapse(true);

    range.moveStart("character", 0);

    range.moveEnd("character", 3);

    range.select(); // "Hel"

    // select characters 4 through 6

    range.collapse(true);

    range.moveStart("character", 4);

    range.moveEnd("character", 3);

    range.select(); // "o w"

    As with the other browsers, the text box must have focus in order for the selection to be visible.

    Partial text selection is useful for implementing advanced text input boxes such as those that provide

    autocomplete suggestions.

    Input Filtering

    It’s common for text boxes to expect a certain type of data or data format. Perhaps the data needs to

    contain certain characters or must match a particular pattern. Because text boxes don’t offer much

    in the way of validation by default, JavaScript must be used to accomplish such input filtering. Using

    a combination of events and other DOM capabilities, you can turn a regular text box into one that

    understands the data it is dealing with.

    Scripting Text Boxes  ❘  719

    Blocking Characters

    Certain types of input require that specific characters be present or absent. For example, a text box

    for the user’s phone number should not allow non-numeric values to be inserted. The keypress event

    is responsible for inserting characters into a text box. Characters can be blocked by preventing this

    event’s default behavior. For example, the following code blocks all key presses:

    textbox.addEventListener("keypress", (event) => {

    event.preventDefault();

    });

    Running this code causes the text box to effectively become read only, because all key presses are

    blocked. To block only specific characters, you need to inspect the character code for the event and

    determine the correct response. For example, the following code allows only numbers:

    textbox.addEventListener("keypress", (event) => {

    if (!/\d/.test(String.fromCharCode(event.charCode))){

    event.preventDefault();

    }

    });

    In this example, the character code is converted to a string using String.fromCharCode(), and the

    result is tested against the regular expression /\d/, which matches all numeric characters. If that test

    fails, then the event is blocked using preventDefault(). This ensures that the text box ignores non-

    numeric keys.

    Even though the keypress event should be fired only when a character key is pressed, some browsers

    fire it for other keys as well. Firefox and Safari (versions prior to 3.1) fire keypress for keys such as

    up, down, Backspace, and Delete; Safari versions 3.1 and later do not fire keypress events for these

    keys. This means that simply blocking all characters that aren’t numbers isn’t good enough because

    you’ll also be blocking these very useful and necessary keys. Fortunately, you can easily detect when

    one of these keys is pressed. In Firefox, all noncharacter keys that fire the keypress event have a

    character code of 0, whereas Safari versions prior to 3 give them all a character code of 8. To general-

    ize the case, you don’t want to block any character codes lower than 10. The function can then be

    updated as follows:

    textbox.addEventListener("keypress", (event) => {

    if (!/\d/.test(String.fromCharCode(event.charCode)) &&

    event.charCode > 9){

    event.preventDefault();

    }

    });

    The event handler now behaves appropriately in all browsers, blocking nonnumeric characters but

    allowing all basic keys that also fire keypress.

    There is still one more issue to handle: copying, pasting, and any other functions that involve the Ctrl

    key. In all browsers but Internet Explorer, the preceding code disallows the shortcut keystrokes of

    Ctrl+C, Ctrl+V, and any other combinations using the Ctrl key. The last check, therefore, is to make

    sure the Ctrl key is not pressed, as shown in the following example:

    textbox.addEventListener("keypress", (event) => {

    if (!/\d/.test(String.fromCharCode(event.charCode)) &&

    event.charCode > 9 &&

    720 ❘ CHAPTER 19  Scripting Forms

    !event.ctrlKey){

    event.preventDefault();

    }

    });

    This final change ensures that all of the default text box behaviors work. This technique can be cus-

    tomized to allow or disallow any characters in a text box.

    Dealing with the Clipboard

    Internet Explorer was the first browser to support events related to the clipboard and access to

    clipboard data from JavaScript. The Internet Explorer implementation became a de facto standard as

    Safari, Chrome, Opera, and Firefox implemented similar events and clipboard access, and clipboard

    events were later added to HTML5. The following six events are related to the clipboard:

    ➤➤ beforecopy—Fires just before the copy operation takes place.

    ➤➤ copy—Fires when the copy operation takes place.

    ➤➤ beforecut—Fires just before the cut operation takes place.

    ➤➤ cut—Fires when the cut operation takes place.

    ➤➤ beforepaste—Fires just before the paste operation takes place.

    ➤➤ paste—Fires when the paste operation takes place.

    Because this is a fairly new standard governing clipboard access, the behavior of the events and

    related objects differs from browser to browser. In Safari, Chrome, and Firefox, the beforecopy,

    beforecut, and beforepaste events fire only when the context menu for the text box is displayed

    (in anticipation of a clipboard event), but Internet Explorer fires them in that case and immediately

    before firing the copy, cut, and paste events. The copy, cut, and paste events all fire when you

    would expect them to in all browsers, both when the selection is made from a context menu and

    when using keyboard shortcuts.

    The beforecopy, beforecut, and beforepaste events give you the opportunity to change the data

    being sent to or retrieved from the clipboard before the actual event occurs. However, canceling these

    events does not cancel the clipboard operation—you must cancel the copy, cut, or paste event to

    prevent the operation from occurring.

    Clipboard data is accessible via the clipboardData object that exists either on the window object

    (in Internet Explorer) or on the event object (in Firefox, Safari, and Chrome). In Firefox, Safari, and

    Chrome, the clipboardData object is available only during clipboard events to prevent unauthorized

    clipboard access; Internet Explorer exposes the clipboardData object all the time. For cross-browser

    compatibility, it’s best to use this object only during clipboard events.

    There are three methods on the clipboardData object: getData(), setData(), and clearData().

    The getData() method retrieves string data from the clipboard and accepts a single argument, which

    is the format for the data to retrieve. Internet Explorer specifies two options: "text" and "URL". Fire-

    fox, Safari, and Chrome expect a MIME type but will accept "text" as equivalent to "text/plain".

    The setData() method is similar: its first argument is the data type, and its second argument is the

    text to place on the clipboard. Once again, Internet Explorer supports "text" and "URL", whereas

    Scripting Text Boxes  ❘  721

    Safari and Chrome expect a MIME type. Unlike getData(), however, Safari and Chrome won’t

    recognize the "text" type. Only Internet Explorer 8 and earlier allow honors calling setData();

    other browsers simply ignore the call. To even out the differences, you can use the following cross-

    browser methods:

    function getClipboardText(event){

    var clipboardData = (event.clipboardData || window.clipboardData);

    return clipboardData.getData("text");

    }

    function setClipboardText (event, value){

    if (event.clipboardData){

    return event.clipboardData.setData("text/plain", value);

    } else if (window.clipboardData){

    return window.clipboardData.setData("text", value);

    }

    }

    The getClipboardText() function is relatively simple. It needs only to identify the location of the

    clipboardData object and then call getData() with a type of "text". Its companion method,

    s­ etClipboardText(), is slightly more involved. Once the clipboardData object is located,

    ­setData() is called with the appropriate type for each implementation ("text/plain" for Firefox,

    Safari, and Chrome; "text" for Internet Explorer).

    Reading text from the clipboard is helpful when you have a text box that expects only certain

    characters or a certain format of text. For example, if a text box allows only numbers, then pasted

    values must also be inspected to ensure that the value is valid. In the paste event, you can determine

    if the text on the clipboard is invalid and, if so, cancel the default behavior, as shown in the follow-

    ing example:

    textbox.addEventListener("paste", (event) => {

    let text = getClipboardText(event);

    if (!/^\d*$/.test(text)){

    event.preventDefault();

    }

    });

    This onpaste handler ensures that only numeric values can be pasted into the text box. If the clip-

    board value doesn’t match the pattern, then the paste is canceled. Firefox, Safari, and Chrome allow

    access to the getData() method only in an onpaste event handler.

    Because not all browsers support clipboard access, it’s often easier to block one or more of the

    clipboard operations. In browsers that support the copy, cut, and paste events (Internet Explorer,

    Safari, Chrome, and Firefox), it’s easy to prevent the event’s default behavior. For Opera, you need to

    block the keystrokes that cause the events and block the context menu from being displayed.

    Automatic Tab Forward

    JavaScript can be used to increase the usability of form fields in a number of ways. One of the most

    common is to automatically move the focus to the next field when the current field is complete.

    This is frequently done when entering data whose appropriate length is already known, such as for

    722 ❘ CHAPTER 19  Scripting Forms

    telephone numbers. In the United States, telephone numbers are typically split into three parts: the

    area code, the exchange, and then four more digits. It’s quite common for web pages to represent this

    as three text boxes, such as the following:

    To aid in usability and speed up the data-entry process, you can automatically move focus to the next

    element as soon as the maximum number of characters has been entered. So once the user types three

    characters in the first text box, the focus moves to the second, and once the user types three charac-

    ters in the second text box, the focus moves to the third. This "tab forward" behavior can be accom-

    plished using the following code:

    function tabForward(event){

    let target = event.target;

    if (target.value.length == target.maxLength){

    let form = target.form;

    for (let 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;

    }

    }

    }

    }

    let inputIds = ["txtTel1", "txtTel2", "txtTel3"];

    for (let id of inputIds) {

    let textbox = document.getElementById(id);

    textbox.addEventListener("keyup", tabForward);

    }

    let textbox1 = document.getElementById("txtTel1");

    let textbox2 = document.getElementById("txtTel2");

    let textbox3 = document.getElementById("txtTel3");

    The tabForward() function is the key to this functionality. It checks to see if the text box’s maximum

    length has been reached by comparing the value to the maxlength attribute. If they’re equal (because

    the browser enforces the maximum, there’s no way it could be more), then the next form element

    needs to be found by looping through the elements collection until the text box is found and then set-

    ting focus to the element in the next position. This function is then assigned as the onkeyup handler

    for each text box. Since the keyup event fires after a new character has been inserted into the text

    box, this is the ideal time to check the length of the text box contents. When filling out this simple

    form, the user will never have to press the Tab key to move between fields and submit the form.

    Scripting Text Boxes  ❘  723

    Keep in mind that this code is specific to the markup mentioned previously and doesn’t take into

    account possible hidden fields.

    HTML5 Constraint Validation API

    HTML5 introduces the ability for browsers to validate data in forms before submitting to the server.

    This capability enables basic validation even when JavaScript is unavailable or fails to load. The

    browser itself handles performing the validation based on rules in the code and then displays appro-

    priate error messages on its own (without needing additional JavaScript). This functionality works

    only in browsers that support this part of HTML5, which includes all modern browsers (except for

    Safari) and IE 10+.

    Validation is applied to a form field only under certain conditions. You can use HTML markup to

    specify constraints on a particular field that will result in the browser automatically performing form

    validation.

    Required Fields

    The first condition is when a form field has a required attribute, as in this example:

    Any field marked as required must have a value in order for the form to be submitted. This attribute

    applies to , , and fields (Opera through version 11 doesn’t support

    required on ). You can check to see if a form field is required in JavaScript by using the

    corresponding required property on the element:

    let isUsernameRequired = document.forms[0].elements["username"].required;

    You can also test to see if the browser supports the required attribute using this code snippet:

    let isRequiredSupported = "required" in document.createElement("input");

    This code uses simple feature detection to determine if the property required exists on a newly created

    element.

    Keep in mind that different browsers behave differently when a form field is required. Firefox,

    Chrome, IE, and Opera prevent the form from submitting and pop up a help box beneath the field,

    while Safari does nothing and doesn’t prevent the form from submitting.

    Alternate Input Types

    HTML5 specifies several additional values for the type attribute on an element. These type

    attributes not only provide additional information about the type of data expected but also provide

    some default validation. The two new input types that are most widely supported are "email" and

    "url", and each comes with a custom validation that the browser applies. For example:

    The "email" type ensures that the input text matches the pattern for an e-mail address, while the

    "url" type ensures that the input text matches the pattern for a URL. Note that the browsers

    724 ❘ CHAPTER 19  Scripting Forms

    As mentioned earlier in this section all have some issues with proper pattern matching. Most nota-

    bly, the text "-@-" is considered a valid e-mail address. Such issues are still being addressed with

    browser vendors.

    You can detect if a browser supports these new types by creating an element in JavaScript and setting

    the type property to "email" or "url" and then reading the value back. Older browsers automati-

    cally set unknown values back to "text", while supporting browsers echo the correct value back.

    For example:

    let input = document.createElement("input");

    input.type = "email";

    let isEmailSupported = (input.type == "email");

    Keep in mind that an empty field is also considered valid unless the required attribute is applied.

    Also, specifying a special input type doesn’t prevent the user from entering an invalid value; it only

    applies some default validation.

    Numeric Ranges

    In addition to "email" and "url", there are several other new input element types defined in

    HTML5. These are all numeric types that expect some sort of numbers-based input: "number",

    "range", "datetime", "datetime-local", "date", "month", "week", and "time". These types

    are not supported across all major browsers and thus should be used carefully. Browser vendors are

    working toward better cross-compatibility and more logical functionality at this time. Therefore, the

    information in this section is more forward looking rather than explanatory of existing functionality.

    For each of these numeric types, you can specify a min attribute (the smallest possible value), a max

    attribute (the largest possible value), and a step attribute (the difference between individual steps

    along the scale from min to max). For instance, to allow only multiples of 5 between 0 and 100, you

    could use the following:

    Depending on the browser, you may or may not see a spin control (up and down buttons) to auto-

    matically increment or decrement the browser.

    Each of the attributes have corresponding properties on the element that are accessible (and change-

    able) using JavaScript. Additionally, there are two methods: stepUp() and stepDown(). These meth-

    ods each accept an optional argument: the number to either subtract or add from the current value.

    (By default, they increment or decrement by one.) The methods have not yet been implemented by

    browsers but will be usable as in this example:

    input.stepUp(); // increment by 1

    input.stepUp(5); // increment by 5

    input.stepDown(); // decrement by 1

    input.stepDown(10); // decrement by 10

    Input Patterns

    The pattern attribute was introduced for text fields in HTML5. This attribute specifies a regular

    expression with which the input value must match. For example, to allow only numbers in a text

    field, the following code applies this constraint:

    Scripting Text Boxes  ❘  725

    Note that ^ and $ are assumed at the beginning and end of the pattern, respectively. That means the

    input must exactly match the pattern from beginning to end.

    As with the alternate input types, specifying a pattern does not prevent the user from entering

    inv­ alid text. The pattern is applied to the value, and the browser then knows if the value is valid or

    not. You can read the pattern by accessing the pattern property:

    let pattern = document.forms[0].elements["count"].pattern;

    You can also test to see if the browser supports the pattern attribute using this code snippet:

    let isPatternSupported = "pattern" in document.createElement("input");

    Checking Validity

    You can check if any given field on the form is valid by using the checkValidity() method. This

    method is provided on all elements and returns true if the field’s value is valid or false if not.

    Whether or not a field is valid is based on the conditions previously mentioned in this section, so

    a required field without a value is considered invalid, and a field whose value does not match the

    pattern attribute is considered invalid. For example:

    if (document.forms[0].elements[0].checkValidity()){

    // field is valid, proceed

    } else {

    // field is invalid

    }

    To check if the entire form is valid, you can use the checkValidity() method on the form itself. This

    method returns true if all form fields are valid and false if even one is not:

    if(document.forms[0].checkValidity()){

    // form is valid, proceed

    } else {

    // form field is invalid

    }

    While checkValidity() simply tells you if a field is valid or not, the validity property indi-

    cates exactly why the field is valid or invalid. This object has a series of properties that return a

    Boolean value:

    ➤➤ customError—true if setCustomValidity() was set, false if not.

    ➤➤ patternMismatch—true if the value doesn’t match the specified pattern attribute.

    ➤➤ rangeOverflow—true if the value is larger than the max value.

    ➤➤ rangeUnderflow—true if the value is smaller than the min value.

    ➤➤ stepMisMatch—true if the value isn’t correct given the step attribute in combination with

    min and max.

    ➤➤ tooLong—true if the value has more characters than allowed by the maxlength property.

    Some browsers, such as Firefox 4, automatically constrain the character count, and so this

    value may always be false.

    726 ❘ CHAPTER 19  Scripting Forms

    ➤➤ typeMismatch—value is not in the required format of either "email" or "url".

    ➤➤ valid—true if every other property is false. Same value that is required by

    checkValidity().

    ➤➤ valueMissing—true if the field is marked as required and there is no value.

    Therefore, you may wish to check the validity of a form field using validity to get more specific

    information, as in the following code:

    if (input.validity && !input.validity.valid){

    if (input.validity.valueMissing){

    console.log("Please specify a value.")

    } else if (input.validity.typeMismatch){

    console.log("Please enter an email address.");

    } else {

    console.log("Value is invalid.");

    }

    }

    Disabling Validation

    You can instruct a form not to apply any validation to a form by specifying the novalidate

    attribute:

    This value can also be retrieved or set by using the JavaScript property noValidate, which is set to

    true if the attribute is present and false if the attribute is omitted:

    document.forms[0].noValidate = true; //turn off validation

    If there are multiple submit buttons in a form, you can specify that the form not validate when a

    particular submit button is used by adding the formnovalidate attribute to the button itself:

    value="Non-validating Submit">

    In this example, the first submit button will cause the form to validate as usual while the second

    disables validation when submitting. You can also set this property using JavaScript:

    // turn off validation

    document.forms[0].elements["btnNoValidate"].formNoValidate = true;

    SCRIPTING SELECT BOXES

    Select boxes are created using the and elements. To allow for easier interaction

    with the control, the HTMLSelectElement type provides the following properties and methods in

    addition to those that are available on all form fields:

    ➤➤ add(newOption, relOption)—Adds a new element to the control before the

    related option.

    Scripting Select Boxes  ❘  727

    ➤➤ multiple—A Boolean value indicating if multiple selections are allowed; equivalent to the

    HTML multiple attribute.

    ➤➤ options—An HTMLCollection of elements in the control.

    ➤➤ remove(index)—Removes the option in the given position.

    ➤➤ selectedIndex—The zero-based index of the selected option or –1 if no options are

    selected. For select boxes that allow multiple selections, this is always the first option in the

    selection.

    ➤➤ size—The number of rows visible in the select box; equivalent to the HTML size attribute.

    The type property for a select box is either "select-one" or "select-multiple", depending on

    the absence or presence of the multiple attribute. The option that is currently selected determines a

    select box’s value property according to the following rules:

    ➤➤ If there is no option selected, the value of a select box is an empty string.

    ➤➤ If an option is selected and it has a value attribute specified, then the select box’s value

    is the value attribute of the selected option. This is true even if the value attribute is an

    empty string.

    ➤➤ If an option is selected and it doesn’t have a value attribute specified, then the select box’s

    value is the text of the option.

    ➤➤ If multiple options are selected, then the select box’s value is taken from the first selected

    option according to the previous two rules.

    Consider the following select box:

    Sunnyvale

    Los Angeles

    Mountain View

    China

    Australia

    If the first option in this select box is selected, the value of the field is "Sunnyvale, CA". If the option

    with the text "China" is selected, then the field’s value is an empty string because the value attribute

    is empty. If the last option is selected, then the value is "Australia" because there is no value attrib-

    ute specified on the .

    Each element is represented in the DOM by an HTMLOptionElement object. The

    HTMLOptionElement type adds the following properties for easier data access:

    ➤➤ index—The option’s index inside the options collection.

    ➤➤ label—The option’s label; equivalent to the HTML label attribute.

    ➤➤ selected—A Boolean value used to indicate if the option is selected. Set this property to

    true to select an option.

    ➤➤ text—The option’s text.

    ➤➤ value—The option’s value (equivalent to the HTML value attribute).

    728 ❘ CHAPTER 19  Scripting Forms

    Most of the properties are used for faster access to the option data. Normal DOM func-

    tionality can be used to access this information, but it’s quite inefficient, as this example shows:

    let selectbox = document.forms[0].elements["location"];

    // not recommended // option text

    let text = selectbox.options[0].firstChild.nodeValue; // option value

    let value = selectbox.options[0].getAttribute("value");

    This code gets the text and value of the first option in the select box using standard DOM techniques.

    Compare this to using the special option properties:

    let selectbox = document.forms[0].elements["location"];

    // preferred // option text

    let text = selectbox.options[0].text; // option value

    let value = selectbox.options[0].value;

    When dealing with options, it’s best to use the option-specific properties because they are well

    supported across all browsers. The exact interactions of form controls may vary from browser to

    browser when manipulating DOM nodes. It is not recommended to change the text or values of

    elements by using standard DOM techniques.

    As a final note, there is a difference in the way the change event is used for select boxes. As opposed

    to other form fields, which fire the change event after the value has changed and the field loses focus,

    the change event fires on select boxes as soon as an option is selected.

    NOTE  There are differences in what the value property returns across browsers.

    The value property is always equal to the value attribute in all browsers. When

    the value attribute is not specified, Internet Explorer 8 and earlier versions

    return an empty string, whereas Internet Explorer 9+, Safari, Firefox, Chrome,

    and Opera return the same value as text.

    Options Selection

    For a select box that allows only one option to be selected, the easiest way to access the selected

    option is by using the select box’s selectedIndex property to retrieve the option, as shown in the

    following example:

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

    This can be used to display all of the information about the selected option, as in this example:

    let selectedIndex = selectbox.selectedIndex;

    let selectedOption = selectbox.options[selectedIndex];

    console.log('Selected index: $[selectedIndex}\n' +

    'Selected text: ${selectedOption.text}\n' +

    'Selected value: ${selectedOption.value}');

    Here, a log message is displayed showing the selected index along with the text and value of the

    selected option.

    Scripting Select Boxes  ❘  729

    When used in a select box that allows multiple selections, the selectedIndex property acts as if

    only one selection was allowed. Setting selectedIndex removes all selections and selects just the

    single option specified, whereas getting selectedIndex returns only the index of the first option that

    was selected.

    Options can also be selected by getting a reference to the option and setting its selected property to

    true. For example, the following selects the first option in a select box:

    selectbox.options[0].selected = true;

    Unlike selectedIndex, setting the option’s selected property does not remove other selections

    when used in a multiselect select box, allowing you to dynamically select any number of options. If

    an option’s selected property is changed in a single-select select box, then all other selections are

    removed. It’s worth noting that setting the selected property to false has no effect in a single-select

    select box.

    The selected property is helpful in determining which options in a select box are selected. To get

    all of the selected options, you can loop over the options collection and test the selected property.

    Consider this example:

    function getSelectedOptions(selectbox){

    let result = new Array();

    for (let option of selectbox.options) {

    if (option.selected) {

    result.push(option);

    }

    }

    return result;

    }

    This function returns an array of options that are selected in a given select box. First an array to

    contain the results is created. Then a for loop iterates over the options, checking each option’s

    selected property. If the option is selected, it is added to the result array. The last step is to return

    the array of selected options. The getSelectedOptions() function can then be used to get informa-

    tion about the selected options, like this:

    let selectbox = document.getElementById("selLocation");

    let selectedOptions = getSelectedOptions(selectbox);

    let message = "";

    for (let option of selectedOptions) {

    message += 'Selected index: ${option.index}\n' +

    'Selected text: ${option.text}\n' +

    'Selected value: ${option.value}\n'

    }

    console.log(message);

    In this example, the selected options are retrieved from a select box. A for loop is used to construct a

    message containing information about all of the selected options, including each option’s index, text,

    and value. This can be used for select boxes that allow single or multiple selection.

    730 ❘ CHAPTER 19  Scripting Forms

    Adding Options

    There are several ways to create options dynamically and add them to select boxes using JavaScript.

    The first way is to use the DOM as follows:

    let newOption = document.createElement("option");

    newOption.appendChild(document.createTextNode("Option text"));

    newOption.setAttribute("value", "Option value");

    selectbox.appendChild(newOption);

    This code creates a new element, adds some text using a text node, sets its value attribute,

    and then adds it to a select box. The new option shows up immediately after being created.

    New options can also be created using the Option constructor, which is a holdover from pre-DOM

    browsers. The Option constructor accepts two arguments, the text and the value, though the

    second argument is optional. Even though this constructor is used to create an instance of Object,

    DOM-compliant browsers return an element. This means you can still use appendChild()

    to add the option to the select box. Consider the following:

    let newOption = new Option("Option text", "Option value");

    selectbox.appendChild(newOption); // problems in IE <= 8

    This approach works as expected in all browsers except Internet Explorer 8 and earlier. Because of a

    bug, the browser doesn’t correctly set the text of the new option when using this approach.

    Another way to add a new option is to use the select box’s add() method. The DOM specifies that

    this method accepts two arguments: the new option to add and the option before which the new

    option should be inserted. To add an option at the end of the list, the second argument should be

    null. The Internet Explorer 8 and earlier implementation of add() is slightly different in that the

    second argument is optional, and it must be the index of the option before which to insert the new

    option. DOM-compliant browsers require the second argument, so you can’t use just one argument

    for a cross-browser approach (Internet Explorer 9 is DOM-compliant). Instead, passing undefined

    as the second argument ensures that the option is added at the end of the list in all browsers. Here’s

    an example:

    let newOption = new Option("Option text", "Option value");

    selectbox.add(newOption, undefined); // best solution

    This code works appropriately in all versions of Internet Explorer and DOM-compliant browsers. If

    you need to insert a new option into a position other than last, you should use the DOM technique

    and insertBefore().

    NOTE  As in HTML, you are not required to assign a value for an option. The

    Option constructor works with just one argument (the option text).

    Removing Options

    As with adding options, there are multiple ways to remove options. You can use the DOM

    r­ emoveChild() method and pass in the option to remove, as shown here:

    selectbox.removeChild(selectbox.options[0]); // remove first option

    Scripting Select Boxes  ❘  731

    The second way is to use the select box’s remove() method. This method accepts a single argument,

    the index of the option to remove, as shown here:

    selectbox.remove(0); // remove first option

    The last way is to simply set the option equal to null. This is also a holdover from pre-DOM

    browsers. Here’s an example:

    selectbox.options[0] = null; // remove first option

    To clear a select box of all options, you need to iterate over the options and remove each one, as in

    this example:

    function clearSelectbox(selectbox) {

    for (let option of selectbox.options) {

    selectbox.remove(0);

    }

    }

    This function simply removes the first option in a select box repeatedly. Because removing the first

    option automatically moves all of the options up one spot, this removes all options.

    Moving and Reordering Options

    Before the DOM, moving options from one select box to another was a rather arduous process that

    involved removing the option from the first select box, creating a new option with the same name

    and value, and then adding that new option to the second select box. Using DOM methods, it’s

    possible to literally move an option from the first select box into the second select box by using the

    appendChild() method. If you pass an element that is already in the document into this method,

    the element is removed from its parent and put into the position specified. For example, the following

    code moves the first option from one select box into another select box.

    let selectbox1 = document.getElementById("selLocations1");

    let selectbox2 = document.getElementById("selLocations2");

    selectbox2.appendChild(selectbox1.options[0]);

    Moving options is the same as removing them in that the index property of each option is reset.

    Reordering options is very similar, and DOM methods are the best way to accomplish this. To move

    an option to a particular location in the select box, the insertBefore() method is most appropriate,

    though the appendChild() method can be used to move any option to the last position. To move an

    option up one spot in the select box, you can use the following code:

    let optionToMove = selectbox.options[1];

    selectbox.insertBefore(optionToMove,

    selectbox.options[optionToMove.index-1]);

    In this code, an option is selected to move and then inserted before the option that is in the previous

    index. The second line of code is generic enough to work with any option in the select box except the

    first. The following similar code can be used to move an option down one spot:

    let optionToMove = selectbox.options[1];

    selectbox.insertBefore(optionToMove,

    selectbox.options[optionToMove.index+2]);

    This code works for all options in a select box, including the last one.

    732 ❘ CHAPTER 19  Scripting Forms

    FORM SERIALIZATION

    With the emergence of Ajax (discussed further in Chapter 21), form serialization has become a

    common requirement. A form can be serialized in JavaScript using the type property of form fields

    in conjunction with the name and value properties. Before writing the code, you need to understand

    how the browser determines what gets sent to the server during a form submission:

    ➤➤ Field names and values are URL-encoded and delimited using an ampersand.

    ➤➤ Disabled fields aren’t sent at all.

    ➤➤ A check box or radio field is sent only if it is checked.

    ➤➤ Buttons of type "reset" or "button" are never sent.

    ➤➤ Multiselect fields have an entry for each value selected.

    ➤➤ When the form is submitted by clicking a submit button, that submit button is sent;

    otherwise no submit buttons are sent. Any elements with a type of "image" are

    treated the same as submit buttons.

    ➤➤ The value of a element is the value attribute of the selected element.

    If the element doesn’t have a value attribute, then the value is the text of the

    element.

    Form serialization typically doesn’t include any button fields, because the resulting string will most

    likely be submitted in another way. All of the other rules should be followed. The code to accomplish

    form serialization is as follows:

    function serialize(form) {

    let parts = [];

    let optValue;

    for (let field of form.elements) {

    switch(field.type) {

    case "select-one":

    case "select-multiple":

    if (field.name.length) {

    for (let option of field.options) {

    if (option.selected) {

    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));

    }

    }

    }

    Form Serialization  ❘  733

    break;

    case undefined: // fieldset

    case "file": // file input

    case "submit": // submit button

    case "reset": // reset button

    case "button": // custom button

    break;

    case "radio": // radio button

    case "checkbox": // checkbox

    if (!field.checked) {

    break;

    }

    default:

    // don't include form fields without names

    if (field.name.length) {

    parts.push('${encodeURIComponent(field.name)}=' +

    '${encodeURIComponent(field.value)}');

    }

    }

    return parts.join("&");

    }

    The serialize() function begins by defining an array called parts to hold the parts of the string

    that will be created. Next, a for loop iterates over each form field, storing it in the field variable.

    Once a field reference is obtained, its type is checked using a switch statement. The most involved

    field to serialize is the element, in either single-select or multiselect mode. Serialization is

    done by looping over all of the options in the control and adding a value if the option is selected. For

    single-select controls, there will be only one option selected, whereas multiselect controls may have

    zero or more options selected. The same code can be used for both select types, because the restric-

    tion on the number of selections is enforced by the browser. When an option is selected, you need to

    determine which value to use. If the value attribute is not present, the text should be used instead,

    although a value attribute with an empty string is completely valid. To check this, you’ll need to use

    hasAttribute() in DOM-compliant browsers and the attribute’s specified property in Internet

    Explorer 8 and earlier.

    If a

    element is in the form, it appears in the elements collection but has no type prop-

    erty. So if type is undefined, no serialization is necessary. The same is true for all types of buttons

    and file input fields. (File input fields contain the content of the file in form submissions; however,

    these fields can’t be mimicked, so they are typically omitted in serialization.) For radio and check box

    controls, the checked property is inspected and if it is set to false, the switch statement is exited. If

    checked is true, then the code continues executing in the default statement, which encodes the name

    and value of the field and adds it to the parts array. Note that in all cases form fields without names

    are not included as part of the serialization to mimic browser form submission behavior. The last part

    of the function uses join() to format the string correctly with ampersands between fields.

    The serialize() function outputs the string in query string format, though it can easily be adapted

    to serialize the form into another format.

    734 ❘ CHAPTER 19  Scripting Forms

    RICH TEXT EDITING

    One of the most requested features for web applications was the ability to edit rich text on a web

    page (also called what you see is what you get, or WYSIWYG, editing). Though no specification

    covers this, a de facto standard has emerged from functionality originally introduced by Internet

    Explorer and now supported by Opera, Safari, Chrome, and Firefox. The basic technique is to embed

    an iframe containing a blank HTML file in the page. Through the designMode property, this blank

    document can be made editable, at which point you’re editing the HTML of the page’s

    element. The designMode property has two possible values: "off" (the default) and "on". When set

    to "on", an entire document becomes editable (showing a caret), allowing you to edit text as if you

    were using a word processor complete with keystrokes for making text bold, italic, and so forth.

    A very simple, blank HTML page is used as the source of the iframe. Here’s an example:

    Blank Page for Rich Text Editing

    This page is loaded inside an iframe as any other page would be. To allow it to be edited, you

    must set designMode to "on", but this can happen only after the document is fully loaded. In the

    containing page, you’ll need to use the onload event handler to indicate the appropriate time to set

    designMode, as shown in the following example:

    window.addEventListener("load", () => {

    frames["richedit"].document.designMode = "on";

    });

    Once this code is loaded, you’ll see what looks like a text box on the page. The box has the same

    default styling as any web page, though this can be adjusted by applying CSS to the blank page.

    Using contenteditable

    Another way to interact with rich text, also first implemented by Internet Explorer, is through the use

    of a special attribute called contenteditable. The contenteditable attribute can be applied to any

    element on a page and instantly makes that element editable by the user. This approach has gained

    favor because it doesn’t require the overhead of an iframe, blank page, and JavaScript. Instead, you

    can just add the attribute to an element:

    Rich Text Editing  ❘  735

    Any text already contained within the element is automatically made editable by the user, making

    it behave similarly to the element. You can also toggle the editing mode on or off by

    setting the contentEditable property on an element:

    let div = document.getElementById("richedit");

    richedit.contentEditable = "true";

    There are three possible values for contentEditable: "true" to turn on, "false" to turn off, or

    "inherit" to inherit the setting from a parent (required because elements can be created/destroyed

    inside of a contenteditable element). The contentEditable attribute is supported in Internet

    Explorer, Firefox, Chrome, Safari, and Opera, and on all major mobile browsers.

    NOTE  contenteditable is an extremely versatile attribute. For example, you’re

    able to convert your browser window into a notepad by visiting the pseudo-URL

    data:text/html, . This creates an ad-hoc DOM with

    the entire document set to editable.

    Interacting with Rich Text

    The primary method of interacting with a rich text editor is through the use of document

    .execCommand(). This method executes named commands on the document and can be used to

    apply most formatting changes. There are three possible arguments for document.execCommand():

    the name of the command to execute, a Boolean value indicating if the browser should provide a

    user interface for the command, and a value necessary for the command to work (or null if none is

    necessary). The second argument should always be false for cross-browser compatibility, because

    Firefox throws an error when true is passed in.

    Each browser supports a different set of commands. The most commonly supported commands are

    listed in the following table.

    COMMAND VALUE (THIRD ARGUMENT) DESCRIPTION

    backcolor A color string Sets the background color of

    the document.

    bold null

    copy null Toggles bold text for the text selection.

    createlink A URL string Executes a clipboard copy on the text

    selection.

    cut null

    Turns the current text selection into a

    link that goes to the given URL.

    Executes a clipboard cut on the text

    selection.

    continues

    736 ❘ CHAPTER 19  Scripting Forms

    (continued) VALUE (THIRD ARGUMENT) DESCRIPTION

    COMMAND null Deletes the currently selected text.

    The font name

    delete Changes the text selection to use the

    fontname 1 through 7 given font name.

    fontsize A color string Changes the font size for the text

    selection.

    forecolor The HTML tag to

    surround the block with; Changes the text color for the text

    formatblock for example,

    selection.

    null

    indent null Formats the entire text box around the

    inserthorizontalrule selection with a particular HTML tag.

    The image URL

    insertimage null Indents the text.

    insertorderedlist

    null Inserts an


    element at the

    insertparagraph caret location.

    null

    insertunorderedlist Inserts an image at the caret location.

    null

    italic null Inserts an

    1. element at the

    justifycenter caret location.

    null

    justifyleft Inserts a

    element at the

    null caret location.

    outdent null

    paste Inserts a

    • element at the

    null caret location.

    removeformat

    Toggles italic text for the text selection.

    Centers the block of text in which the

    caret is positioned.

    Left-aligns the block of text in which the

    caret is positioned.

    Outdents the text.

    Executes a clipboard paste on the text

    selection.

    Removes block formatting from the

    block in which the caret is positioned.

    This is the opposite of formatblock.

    Rich Text Editing  ❘  737

    COMMAND VALUE (THIRD ARGUMENT) DESCRIPTION

    selectall null Selects all of the text in the document.

    underline null

    Toggles underlined text for the text

    unlink null selection.

    Removes a text link. This is the opposite

    of createlink.

    The clipboard commands are very browser-dependent. Note that even though these commands aren’t

    all available via document.execCommand(), they still work with the appropriate keyboard shortcuts.

    These commands can be used at any time to modify the appearance of the iframe rich text area, as in

    this example:

    // toggle bold text in an iframe

    frames["richedit"].document.execCommand("bold", false, null);

    // toggle italic text in an iframe

    frames["richedit"].document.execCommand("italic", false, null);

    // create link to www.wrox.com in an iframe

    frames["richedit"].document.execCommand("createlink", false,

    "http://www.wrox.com");

    // format as first-level heading in an iframe

    frames["richedit"].document.execCommand("formatblock", false, "

    ");

    You can use the same methods to act on a contenteditable section of the page; just use the

    document object of the current window instead of referencing the iframe:

    // toggle bold text

    document.execCommand("bold", false, null);

    // toggle italic text

    document.execCommand("italic", false, null);

    // create link to www.wrox.com

    document.execCommand("createlink", false, "http://www.wrox.com");

    // format as first-level heading

    document.execCommand("formatblock", false, "

    ");

    Note that even when commands are supported across all browsers, the HTML that the commands

    produce is often very different. For instance, applying the bold command surrounds text with

    in Internet Explorer and Opera, with in Safari and Chrome, and with a in

    Firefox. You cannot rely on consistency in the HTML produced from a rich text editor, because of

    both command implementation and the transformations done by innerHTML.

    738 ❘ CHAPTER 19  Scripting Forms

    There are some other methods related to commands. The first is queryCommandEnabled(), which

    determines if a command can be executed given the current text selection or caret position. This

    method accepts a single argument, the command name to check, and returns true if the command is

    allowed given the state of the editable area or false if not. Consider this example:

    let result = frames["richedit"].document.queryCommandEnabled("bold");

    This code returns true if the "bold" command can be executed on the current selection. It’s worth

    noting that queryCommandEnabled() indicates not if you are allowed to execute the

    command but only if the current selection is appropriate for use with the command. In Firefox,

    queryCommandEnabled("cut") returns true even though it isn’t allowed by default.

    The queryCommandState() method lets you determine if a given command has been applied to the

    current text selection. For example, to determine if the text in the current selection is bold, you can

    use the following:

    let isBold = frames["richedit"].document.queryCommandState("bold");

    If the "bold" command was previously applied to the text selection, then this code returns true.

    This is the method by which full-featured rich text editors are able to update buttons for bold, italic,

    and so on.

    The last method is queryCommandValue(), which is intended to return the value with which a

    command was executed. (The third argument in execCommand is in the earlier example.) For instance,

    a range of text that has the "fontsize" command applied with a value of 7 returns "7" from the

    following:

    let fontSize = frames["richedit"].document.queryCommandValue("fontsize");

    This method can be used to determine how a command was applied to the text selection, allowing

    you to determine whether the next command is appropriate to be executed.

    Rich Text Selections

    You can determine the exact selection in a rich text editor by using the getSelection() method of

    the iframe. This method is available on both the document object and the window object and returns

    a Selection object representing the currently selected text. Each Selection object has the following

    properties:

    ➤➤ anchorNode—The node in which the selection begins.

    ➤➤ anchorOffset—The number of characters within the anchorNode that are skipped before

    the selection begins.

    ➤➤ focusNode—The node in which the selection ends.

    ➤➤ focusOffset—The number of characters within the focusNode that are included in the

    selection.

    ➤➤ isCollapsed—Boolean value indicating if the start and end of the selection are the same.

    ➤➤ rangeCount—The number of DOM ranges in the selection.

    Rich Text Editing  ❘  739

    The properties for a Selection don’t contain a lot of useful information. Fortunately, the following

    methods provide more information and allow manipulation of the selection:

    ➤➤ addRange(range)—Adds the given DOM range to the selection.

    ➤➤ collapse(node, offset)—Collapses the selection to the given text offset within the

    given node.

    ➤➤ collapseToEnd()—Collapses the selection to its end.

    ➤➤ collapseToStart()—Collapses the selection to its start.

    ➤➤ containsNode(node)—Determines if the given node is contained in the selection.

    ➤➤ deleteFromDocument()—Deletes the selection text from the document. This is the same as

    execCommand("delete", false, null).

    ➤➤ extend(node, offset)—Extends the selection by moving the focusNode and focusOffset

    to the values specified.

    ➤➤ getRangeAt(index)—Returns the DOM range at the given index in the selection.

    ➤➤ removeAllRanges()—Removes all DOM ranges from the selection. This effectively removes

    the selection, because there must be at least one range in a selection.

    ➤➤ removeRange(range)—Removes the specified DOM range from the selection.

    ➤➤ selectAllChildren(node)—Clears the selection and then selects all child nodes of the

    given node.

    ➤➤ toString()—Returns the text content of the selection.

    The methods of a Selection object are extremely powerful and make extensive use of DOM ranges

    to manage the selection. Access to DOM ranges allows you to modify the contents of the rich text

    editor in even finer-grain detail than is available using execCommand() because you can directly

    manipulate the DOM of the selected text. Consider the following example:

    let selection = frames["richedit"].getSelection();

    // get selected text

    let selectedText = selection.toString();

    // get the range representing the selection

    let range = selection.getRangeAt(0);

    // highlight the selected text

    let span = frames["richedit"].document.createElement("span");

    span.style.backgroundColor = "yellow";

    range.surroundContents(span);

    This code places a yellow highlight around the selected text in a rich text editor. Using the DOM

    range in the default selection, the surroundContents() method surrounds the selection with a

    element whose background color is yellow.

    740 ❘ CHAPTER 19  Scripting Forms

    The getSelection() method was standardized in HTML5 and is implemented in Internet Explorer 9

    and all modern versions of Firefox, Safari, Chrome, and Opera.

    Internet Explorer 8 and earlier versions don’t support DOM ranges, but they do allow interaction

    with the selected text via the proprietary selection object. The selection object is a property of

    document, as discussed earlier in this chapter. To get the selected text in a rich text editor, you must

    first create a text range and then use the text property as follows:

    let range = frames["richedit"].document.selection.createRange();

    let selectedText = range.text;

    Performing HTML manipulations using Internet Explorer text ranges is not as safe as using DOM

    ranges, but it is possible. To achieve the same highlighting effect as described using DOM ranges, you

    can use a combination of the htmlText property and the pasteHTML() method:

    let range = frames["richedit"].document.selection.createRange();

    range.pasteHTML(

    '${range.htmlText}');

    This code retrieves the HTML of the current selection using htmlText and then surrounds it with a

    and inserts it back into the selection using pasteHTML().

    Rich Text in Forms

    Because rich text editing is implemented using an iframe or a contenteditable element instead

    of a form control, a rich text editor is technically not part of a form. That means the HTML will

    not be submitted to the server unless you extract the HTML manually and submit it yourself. This

    is typically done by having a hidden form field that is updated with the HTML from the iframe or

    the contenteditable element. Just before the form is submitted, the HTML is extracted from the

    iframe or element and inserted into the hidden field. For example, the following may be done in the

    form’s onsubmit event handler when using an iframe:

    form.addEventListener("submit", (event) => {

    let target = event.target;

    target.elements["comments"].value =

    frames["richedit"].document.body.innerHTML;

    });

    Here, the HTML is retrieved from the iframe using the innerHTML property of the document’s

    body and inserted into a form field named "comments". Doing so ensures that the "comments"

    field is filled in just before the form is submitted. If you are submitting the form manually using the

    s­ ubmit() method, take care to perform this operation beforehand. You can perform a similar operation

    with a contenteditable element:

    form.addEventListener("submit", (event) => {

    let target = event.target;

    target.elements["comments"].value =

    document.getElementById("richedit").innerHTML;

    });

    Summary  ❘  741

    SUMMARY

    Even though HTML and web applications have changed dramatically since their inception, web

    forms have remained mostly unchanged. JavaScript can be used to augment existing form fields to

    provide new functionality and usability enhancements. To aid in this, forms and form fields have

    properties, methods, and events for JavaScript usage. Here are some of the concepts introduced in

    this chapter:

    ➤➤ It’s possible to select all of the text in a text box or just part of the text using a variety of

    standard and nonstandard methods.

    ➤➤ All browsers have adopted Firefox’s way of interacting with text selection, making it a

    true standard.

    ➤➤ Text boxes can be changed to allow or disallow certain characters by listening for keyboard

    events and inspecting the characters being inserted.

    All browsers support events for the clipboard, including copy, cut, and paste. Clipboard event

    implementations across the other browsers vary widely between browser vendors.

    Hooking into clipboard events is useful for blocking paste events when the contents of a text box

    must be limited to certain characters.

    Select boxes are also frequently controlled using JavaScript. Thanks to the DOM, manipulating select

    boxes is much easier than it was previously. Options can be added, removed, moved from one select box

    to another, or reordered using standard DOM techniques.

    Rich text editing is handled by using an iframe containing a blank HTML document. By setting

    the document’s designMode property to "on", you make the page editable and it acts like a word

    processor. You can also use an element set as contenteditable. By default, you can toggle font

    styles such as bold and italic and use clipboard actions. JavaScript can access some of this func-

    tionality by using the execCommand() method and can get information about the text selection by

    using the queryCommandEnabled(), queryCommandState(), and queryCommandValue() methods.

    Because building a rich text editor in this manner does not create a form field, it’s necessary to copy

    the HTML from the iframe or contenteditable element into a form field if it is to be submitted to

    the server.

    20

    JavaScript APIs

    WHAT’S IN THIS CHAPTER?

    ➤➤ Atomics and SharedArrayBuffer

    ➤➤ Cross-context messaging

    ➤➤ Encoding API

    ➤➤ File and Blob API

    ➤➤ Drag and drop

    ➤➤ Notifications API

    ➤➤ Page Visibility API

    ➤➤ Streams API

    ➤➤ Timing APIs

    ➤➤ Web components

    ➤➤ Web Cryptography API

    The increasing versatility of web browsers is accompanied by a dizzying increase in complex-

    ity. In many ways, the modern web browser has become a Swiss army knife of different APIs

    detailed in a broad collection of specifications. This browser specification ecosystem is messy

    and volatile. Some specifications like HTML5 are a bundle of APIs and browser features that

    enhance an existing standard. Other specifications define an API for a single feature, such as the

    Web Cryptography API or the Notifications API. Depending on the browser, adoption of these

    newer APIs can sometimes be partial or nonexistent.

    Ultimately, the decision to use newer APIs involves a tradeoff between supporting more brows-

    ers and enabling more modern features. Some APIs can be emulated using a polyfill, but poly-

    fills can often incur a performance hit or bloat your site’s JS payloads.

    Professional JavaScript® for Web Developers, Fourth Edition. Matt Frisbie.

    © 2020 John Wiley & Sons, Inc. Published 2020 by John Wiley & Sons, Inc.

    744 ❘ CHAPTER 20  JavaScript APIs

    NOTE  The number of web APIs is mind-bogglingly huge (https://developer.

    mozilla.org/en-US/docs/Web/API). This chapter’s API coverage is limited to

    APIs that are relevant to most developers, supported by multiple browser ven-

    dors, and not covered elsewhere in this book.

    ATOMICS AND SharedArrayBuffer

    When a SharedArrayBuffer is accessed by multiple contexts, race conditions can occur when opera-

    tions on the buffer are performed simultaneously. The Atomics API allows multiple contexts to safely

    read and write to a single SharedArrayBuffer by forcing buffer operations to occur only one at a

    time. The Atomics API was defined in the ES2017 specification.

    You will notice that the Atomics API in many ways resembles a stripped-down instruction set archi-

    tecture (ISA)—this is no accident. The nature of atomic operations precludes some optimizations

    that the operating system or computer hardware would normally perform automatically (such as

    instruction reordering). Atomic operations also make concurrent memory access impossible, which

    obviously can slow program execution when improperly applied. Therefore, the Atomics API was

    designed to enable sophisticated multithreaded JavaScript programs to be architected out of a mini-

    mal yet robust collection of atomic behaviors.

    SharedArrayBuffer

    A SharedArrayBuffer features an identical API to an ArrayBuffer. The primary difference is that,

    whereas a reference to an ArrayBuffer must be handed off between execution contexts, a reference

    to a SharedArrayBuffer can be used simultaneously by any number of execution contexts.

    Sharing memory between multiple execution contexts means that concurrent thread operations

    become a possibility. Traditional JavaScript operations offer no protection from race conditions

    resulting from concurrent memory access. The following example demonstrates a race condition

    between four dedicated workers accessing the same SharedArrayBuffer:

    const workerScript = `

    self.onmessage = ({data}) => {

    const view = new Uint32Array(data);

    // Perform 1000000 add operations

    for (let i = 0; i < 1E6; ++i) {

    // Thread-unsafe add operation introduces race condition

    view[0] += 1;

    }

    self.postMessage(null);

    };

    `;

    const workerScriptBlobUrl = URL.createObjectURL(new Blob([workerScript]));

    Atomics and  SharedArrayBuffer ❘  745

    // Create worker pool of size 4

    const workers = [];

    for (let i = 0; i < 4; ++i) {

    workers.push(new Worker(workerScriptBlobUrl));

    }

    // Log the final value after the last worker completes

    let responseCount = 0;

    for (const worker of workers) {

    worker.onmessage = () => {

    if (++responseCount == workers.length) {

    console.log(`Final buffer value: ${view[0]}`);

    }

    };

    }

    // Initialize the SharedArrayBuffer

    const sharedArrayBuffer = new SharedArrayBuffer(4);

    const view = new Uint32Array(sharedArrayBuffer);

    view[0] = 1;

    // Send the SharedArrayBuffer to each worker

    for (const worker of workers) {

    worker.postMessage(sharedArrayBuffer);

    }

    // (Expected result is 4000001. Actual output will be something like:)

    // Final buffer value: 2145106

    To address this problem, the Atomics API was introduced to allow for thread-safe JavaScript opera-

    tions on a SharedArrayBuffer.

    NOTE  The SharedArrayBuffer API is identical to the ArrayBuffer API, which

    is covered in the “Collection Reference Types” chapter. For details on how to use

    a SharedArrayBuffer across multiple contexts, refer to the “Workers” chapter.

    Atomics Basics

    The Atomics object exists on all global contexts, and it exposes a suite of static methods for perform-

    ing thread-safe operations. Most of these methods take a TypedArray instance (referencing a Shar-

    edArrayBuffer) as the first argument and the relevant operands as subsequent arguments.

    Atomic Arithmetic and Bitwise Methods

    The Atomics API offers a simple suite of methods for performing an in-place modification. In the

    ECMA specification, these methods are defined as AtomicReadModifyWrite operations. Under the

    hood, each of these methods is performing a read from a location in the SharedArrayBuffer, an

    arithmetic or bitwise operation, and a write to the same location. The atomic nature of these opera-

    tors means that these three operations will be performed in sequence and without interruption by

    another thread.

    746 ❘ CHAPTER 20  JavaScript APIs

    All the arithmetic methods are demonstrated here:

    // Create buffer of size 1

    let sharedArrayBuffer = new SharedArrayBuffer(1);

    // Create Uint8Array from buffer

    let typedArray = new Uint8Array(sharedArrayBuffer);

    // All ArrayBuffers are initialized to 0

    console.log(typedArray); // Uint8Array[0]

    const index = 0;

    const increment = 5;

    // Atomic add 5 to value at index 0

    Atomics.add(typedArray, index, increment);

    console.log(typedArray); // Uint8Array[5]

    // Atomic subtract 5 to value at index 0

    Atomics.sub(typedArray, index, increment);

    console.log(typedArray); // Uint8Array[0]

    All the bitwise methods are demonstrated here:

    // Create buffer of size 1

    let sharedArrayBuffer = new SharedArrayBuffer(1);

    // Create Uint8Array from buffer

    let typedArray = new Uint8Array(sharedArrayBuffer);

    // All ArrayBuffers are initialized to 0

    console.log(typedArray); // Uint8Array[0]

    const index = 0;

    // Atomic or 0b1111 to value at index 0

    Atomics.or(typedArray, index, 0b1111);

    console.log(typedArray); // Uint8Array[15]

    // Atomic and 0b1100 to value at index 0

    Atomics.and(typedArray, index, 0b1100);

    console.log(typedArray); // Uint8Array[12]

    // Atomic xor 0b1111 to value at index 0

    Atomics.xor(typedArray, index, 0b1111);

    console.log(typedArray); // Uint8Array[3]

    The thread-unsafe example from earlier can be corrected as follows:

    const workerScript = `

    self.onmessage = ({data}) => {

    Atomics and  SharedArrayBuffer ❘  747

    const view = new Uint32Array(data);

    // Perform 1000000 add operations

    for (let i = 0; i < 1E6; ++i) {

    // Thread-safe add operation

    Atomics.add(view, 0, 1);

    }

    self.postMessage(null);

    };

    `;

    const workerScriptBlobUrl = URL.createObjectURL(new Blob([workerScript]));

    // Create worker pool of size 4

    const workers = [];

    for (let i = 0; i < 4; ++i) {

    workers.push(new Worker(workerScriptBlobUrl));

    }

    // Log the final value after the last worker completes

    let responseCount = 0;

    for (const worker of workers) {

    worker.onmessage = () => {

    if (++responseCount == workers.length) {

    console.log(`Final buffer value: ${view[0]}`);

    }

    };

    }

    // Initialize the SharedArrayBuffer

    const sharedArrayBuffer = new SharedArrayBuffer(4);

    const view = new Uint32Array(sharedArrayBuffer);

    view[0] = 1;

    // Send the SharedArrayBuffer to each worker

    for (const worker of workers) {

    worker.postMessage(sharedArrayBuffer);

    }

    // (Expected result is 4000001)

    // Final buffer value: 4000001

    Atomic Reads and Writes

    Both the browser’s JavaScript compiler and the CPU architecture itself are given license to reorder

    instructions if they detect it will increase the overall throughput of program execution. Normally, the

    single-threaded nature of JavaScript means this optimization should be welcomed with open arms.

    However, instruction reordering across multiple threads can yield race conditions that are extremely

    difficult to debug.

    748 ❘ CHAPTER 20  JavaScript APIs

    The Atomics API addresses this problem in two primary ways:

    ➤➤ All Atomics instructions are never reordered with respect to one another.

    ➤➤ Using an Atomic read or Atomic write guarantees that all instructions (both Atomic and

    non-Atomic) will never be reordered with respect to that Atomic read/write. This means

    that all instructions before an Atomic read/write will finish before the Atomic read/write

    occurs, and all instructions after the Atomic read/write will not begin until the Atomic

    read/write completes.

    In addition to reading and writing values to a buffer, Atomics.load() and Atomics.store() behave

    as “code fences.” The JavaScript engine guarantees that, although non-Atomic instructions may be

    locally reordered relative to a load() or store(), the reordering will never violate the Atomic read/

    write boundary. The following code annotates this behavior:

    const sharedArrayBuffer = new SharedArrayBuffer(4);

    const view = new Uint32Array(sharedArrayBuffer);

    // Perform non-Atomic write

    view[0] = 1;

    // Non-Atomic write is guaranteed to occur before this read,

    // so this is guaranteed to read 1

    console.log(Atomics.load(view, 0)); // 1

    // Perform Atomic write

    Atomics.store(view, 0, 2);

    // Non-Atomic read is guaranteed to occur after Atomic write,

    // so this is guaranteed to read 2

    console.log(view[0]); // 2

    Atomic Exchanges

    The Atomics API offers two types of methods that guarantee a sequential and uninterrupted

    read-then-write: exchange() and compareExchange(). Atomics.exchange() performs a simple

    swap, guaranteeing that no other threads will interrupt the value swap:

    const sharedArrayBuffer = new SharedArrayBuffer(4);

    const view = new Uint32Array(sharedArrayBuffer);

    // Write 3 to 0-index

    Atomics.store(view, 0, 3);

    // Read value out of 0-index and then write 4 to 0-index

    console.log(Atomics.exchange(view, 0, 4)); // 3

    // Read value at 0-index // 4

    console.log(Atomics.load(view, 0));

    One thread in a multithreaded program might want to perform a write to a shared buffer only if

    another thread has not modified a specific value since it was last read. If the value has not changed,

    Atomics and  SharedArrayBuffer ❘  749

    it can safely write the update value. If the value has changed, performing a write would trample the

    value calculated by another thread. For this task, the Atomics API features the compareExchange()

    method. This method only performs a write if the value at the intended index matches an expected

    value. Consider the following example:

    const sharedArrayBuffer = new SharedArrayBuffer(4);

    const view = new Uint32Array(sharedArrayBuffer);

    // Write 5 to 0-index

    Atomics.store(view, 0, 5);

    // Read the value out of the buffer

    let initial = Atomics.load(view, 0);

    // Perform a non-atomic operation on that value

    let result = initial ** 2;

    // Write that value back into the buffer only if the buffer has not changed

    Atomics.compareExchange(view, 0, initial, result);

    // Check that the write succeeded

    console.log(Atomics.load(view, 0)); // 25

    If the value does not match, the compareExchange() call will simply behave as a passthrough:

    const sharedArrayBuffer = new SharedArrayBuffer(4);

    const view = new Uint32Array(sharedArrayBuffer);

    // Write 5 to 0-index

    Atomics.store(view, 0, 5);

    // Read the value out of the buffer

    let initial = Atomics.load(view, 0);

    // Perform a non-atomic operation on that value

    let result = initial ** 2;

    // Write that value back into the buffer only if the buffer has not changed

    Atomics.compareExchange(view, 0, -1, result);

    // Check that the write failed

    console.log(Atomics.load(view, 0)); // 5

    Atomics Futex Operations and Locks

    Multithreaded programs wouldn’t amount to much without some sort of locking construct. To

    address this need, the Atomics API offers several methods modeled on the Linux futex (a portmanteau

    of fast user-space mutex). The methods are fairly rudimentary, but they are intended to be used as a

    fundamental building block for more elaborate locking constructs.

    NOTE  All Atomics futex operations only work with an Int32Array view. Fur-

    thermore, they can only be used inside workers.

    750 ❘ CHAPTER 20  JavaScript APIs

    Atomics.wait() and Atomics.notify() are best understood by example. The following rudimen-

    tary example spawns four workers to operate on an Int32Array of length 1. The spawned workers

    will take turns obtaining the lock and performing their add operation:

    const workerScript = `

    self.onmessage = ({data}) => {

    const view = new Int32Array(data);

    console.log('Waiting to obtain lock');

    // Halt when encountering the initial value, timeout at 10000ms

    Atomics.wait(view, 0, 0, 1E5);

    console.log('Obtained lock');

    // Add 1 to data index

    Atomics.add(view, 0, 1);

    console.log('Releasing lock');

    // Allow exactly one worker to continue execution

    Atomics.notify(view, 0, 1);

    self.postMessage(null);

    };

    `;

    const workerScriptBlobUrl = URL.createObjectURL(new Blob([workerScript]));

    const workers = [];

    for (let i = 0; i < 4; ++i) {

    workers.push(new Worker(workerScriptBlobUrl));

    }

    // Log the final value after the last worker completes

    let responseCount = 0;

    for (const worker of workers) {

    worker.onmessage = () => {

    if (++responseCount == workers.length) {

    console.log(`Final buffer value: ${view[0]}`);

    }

    };

    }

    // Initialize the SharedArrayBuffer

    const sharedArrayBuffer = new SharedArrayBuffer(8);

    const view = new Int32Array(sharedArrayBuffer);

    // Send the SharedArrayBuffer to each worker

    for (const worker of workers) {

    worker.postMessage(sharedArrayBuffer);

    }

    Cross-Context Messaging  ❘  751

    // Release first lock in 1000ms

    setTimeout(() => Atomics.notify(view, 0, 1), 1000);

    // Waiting to obtain lock

    // Waiting to obtain lock

    // Waiting to obtain lock

    // Waiting to obtain lock

    // Obtained lock

    // Releasing lock

    // Obtained lock

    // Releasing lock

    // Obtained lock

    // Releasing lock

    // Obtained lock

    // Releasing lock

    // Final buffer value: 4

    Because the SharedArrayBuffer is initialized with 0s, each worker will arrive at the Atomics.

    wait() and halt execution. In the halted state, the thread of execution exists inside a wait queue,

    remaining paused until the specified timeout elapses or until Atomics.notify() is invoked for that

    index. After 1000 milliseconds, the top-level execution context will call Atomics.notify() to release

    exactly one of the waiting threads. This thread will finish execution and call Atomics.notify() once

    again, releasing yet another thread. This continues until all the threads have completed execution and

    transmitted their final postMessage().

    The Atomics API also features the Atomics.isLockFree() method. It is almost certain that you will

    never need to use this method, as it is designed for high-performance algorithms to decide whether or

    not obtaining a lock is necessary. The specification offers this description:

    Atomics.isLockFree() is an optimization primitive. The intuition is that if the

    atomic step of an atomic primitive (compareExchange, load, store, add, sub, and,

    or, xor, or exchange) on a datum of size n bytes will be performed without the

    calling agent acquiring a lock outside the n bytes comprising the datum, then Atom-

    ics.isLockFree(n) will return true. High-performance algorithms will use Atom-

    ics.isLockFree to determine whether to use locks or atomic operations in critical

    sections. If an atomic primitive is not lock-free then it is often more efficient for an

    algorithm to provide its own locking.

    Atomics.isLockFree(4) always returns true as that can be supported on

    all known relevant hardware. Being able to assume this will generally sim-

    plify programs.

    CROSS-CONTEXT MESSAGING

    Cross-document messaging, sometimes abbreviated as XDM, is the capability to pass informa-

    tion between different execution contexts, such as web workers or pages from different origins. For

    example, a page on www.wrox.com wants to communicate with a page from p2p.wrox.com that is

    752 ❘ CHAPTER 20  JavaScript APIs

    contained in an iframe. Prior to XDM, achieving this communication in a secure manner took a lot of

    work. XDM formalizes this functionality in a way that is both secure and easy to use.

    NOTE  Cross-context messaging is used for communication between windows

    and communication with workers. This section focuses on using postMessage()

    to communicate with other windows. For coverage on worker messaging, Mes-

    sageChannel, and BroadcastChannel, refer to the “Workers” chapter.

    At the heart of XDM is the postMessage() method. This method name is used in many parts

    of HTML5 in addition to XDM and is always used for the same purpose: to pass data into

    another location.

    The postMessage() method accepts three arguments: a message, a string indicating the intended

    recipient origin, and an optional array of transferable objects (only relevant to web workers). The

    second argument is very important for security reasons and restricts where the browser will deliver

    the message. Consider this example:

    let iframeWindow = document.getElementById("myframe").contentWindow;

    iframeWindow.postMessage("A secret", "http://www.wrox.com");

    The last line attempts to send a message into the iframe and specifies that the origin must be

    "http://www.wrox.com". If the origin matches, then the message will be delivered into the iframe;

    otherwise, postMessage() silently does nothing. This restriction protects your information should

    the location of the window change without your knowledge. It is possible to allow posting to any

    origin by passing in "*" as the second argument to postMessage(), but this is not recommended.

    A message event is fired on window when an XDM message is received. This message is fired asyn-

    chronously so there may be a delay between the time at which the message was sent and the time

    at which the message event is fired in the receiving window. The event object that is passed to an

    onmessage event handler has three important pieces of information:

    ➤➤ data—The string data that was passed as the first argument to postMessage().

    ➤➤ origin—The origin of the document that sent the message, for example, "http://www.

    wrox.com".

    ➤➤ source—A proxy for the window object of the document that sent the message. This

    proxy object is used primarily to execute the postMessage() method on the window that

    sent the last message. If the sending window has the same origin, this may be the actual

    window object.

    It’s very important when receiving a message to verify the origin of the sending window. Just as

    specifying the second argument to postMessage() ensures that data doesn’t get passed unintention-

    ally to an unknown page, checking the origin during onmessage ensures that the data being passed is

    coming from the right place. The basic pattern is as follows:

    window.addEventListener("message", (event) => {

    // ensure the sender is expected

    if (event.origin == "http://www.wrox.com") {

    Encoding API  ❘  753

    // do something with the data

    processMessage(event.data);

    // optional: send a message back to the original window

    event.source.postMessage("Received!", "http://p2p.wrox.com");

    }

    });

    Keep in mind that event.source is a proxy for a window in most cases, not the actual window object,

    so you can’t access all of the window information. It’s best to just use postMessage(), which is

    always present and always callable.

    There are a few quirks with XDM. First, the first argument of postMessage() was initially imple-

    mented as always being a string. The definition of that first argument changed to allow any struc-

    tured data to be passed in; however, not all browsers have implemented this change. For this reason,

    it’s best to always pass a string using postMessage(). If you need to pass structured data, then the

    best approach is to call JSON.stringify() on the data, passing the string to postMessage(), and

    then call JSON.parse() in the onmessage event handler.

    XDM is extremely useful when trying to sandbox content using an iframe to a different domain. This

    approach is frequently used in mashups and social networking applications. The containing page is

    able to keep itself secure against malicious content by only communicating into an embedded iframe

    via XDM. XDM can also be used with pages from the same domain.

    ENCODING API

    The Encoding API allows for converting between strings and typed arrays. The specification intro-

    duces four global classes for performing these conversions: TextEncoder, TextEncoderStream,

    TextDecoder, and TextDecoderStream.

    NOTE  Support for stream encoding/decoding is much narrower than bulk

    encoding/decoding.

    Encoding Text

    The Encoding API affords two ways of converting a string into its typed array binary equivalent:

    a bulk encoding, and a stream encoding. When going from string to typed array, the encoder will

    always use UTF-8.

    Bulk Encoding

    The bulk designation means that the JavaScript engine will synchronously encode the entire string.

    For very long strings, this can be a costly operation. Bulk encoding is accomplished using an instance

    of a TextEncoder:

    const textEncoder = new TextEncoder();

    754 ❘ CHAPTER 20  JavaScript APIs

    This instance exposes an encode() method, which accepts a string and returns each character’s

    UTF-8 encoding inside a freshly created Uint8Array:

    const textEncoder = new TextEncoder();

    const decodedText = 'foo';

    const encodedText = textEncoder.encode(decodedText);

    // f encoded in utf-8 is 0x66 (102 in decimal)

    // o encoded in utf-8 is 0x6F (111 in decimal)

    console.log(encodedText); // Uint8Array(3) [102, 111, 111]

    The encoder is equipped to handle characters, which will take up multiple indices in the eventual

    array, such as emojis:

    const textEncoder = new TextEncoder();

    const decodedText = ' ';

    const encodedText = textEncoder.encode(decodedText);

    // encoded in UTF-8 is 0xF0 0x9F 0x98 0x8A (240, 159, 152, 138 in decimal)

    console.log(encodedText); // Uint8Array(4) [240, 159, 152, 138]

    The instance also exposes an encodeInto() method, which accepts a string and the destination

    Uint8Array. This method returns a dictionary containing read and written properties, indicating

    how many characters were successfully read from the source string and written to the destination

    array, respectively. If the typed array has insufficient space, the encoding will terminate early and the

    dictionary will indicate that result:

    const textEncoder = new TextEncoder();

    const fooArr = new Uint8Array(3);

    const barArr = new Uint8Array(2);

    const fooResult = textEncoder.encodeInto('foo', fooArr);

    const barResult = textEncoder.encodeInto('bar', barArr);

    console.log(fooArr); // Uint8Array(3) [102, 111, 111]

    console.log(fooResult); // { read: 3, written: 3 }

    console.log(barArr); // Uint8Array(2) [98, 97]

    console.log(barResult); // { read: 2, written: 2 }

    encode() must allocate a new Uint8Array, whereas encodeInto() does not. For performance-sensi-

    tive applications, this distinction may have significant implications.

    NOTE  Text encoding will always utilize the UTF-8 format and must write into

    a Uint8Array instance. Attempting to use a different typed array when calling

    encodeInto() will throw an error.

    Stream Encoding

    A TextEncoderStream is merely a TextEncoder in the form of a TransformStream. Piping a

    decoded text stream through the stream encoder will yield a stream of encoded text chunks:

    async function* chars() {

    const decodedText = 'foo';

    Encoding API  ❘  755

    for (let char of decodedText) {

    yield await new Promise((resolve) => setTimeout(resolve, 1000, char));

    }

    }

    const decodedTextStream = new ReadableStream({

    async start(controller) {

    for await (let chunk of chars()) {

    controller.enqueue(chunk);

    }

    controller.close();

    }

    });

    const encodedTextStream = decodedTextStream.pipeThrough(new TextEncoderStream());

    const readableStreamDefaultReader = encodedTextStream.getReader();

    (async function() {

    while(true) {

    const { done, value } = await readableStreamDefaultReader.read();

    if (done) {

    break;

    } else {

    console.log(value);

    }

    }

    })();

    // Uint8Array[102]

    // Uint8Array[111]

    // Uint8Array[111]

    Decoding Text

    The Encoding API affords two ways of converting a typed array into its string equivalent: a bulk

    decoding, and a stream decoding. Unlike the encoder classes, when going from typed array to string,

    the decoder supports a large number of string encodings, listed here: https://encoding.spec

    .whatwg.org/#names-and-labels

    The default character encoding is UTF-8.

    Bulk Decoding

    The bulk designation means that the JavaScript engine will synchronously decode the entire string.

    For very long strings, this can be a costly operation. Bulk decoding is accomplished using an instance

    of a DecoderEncoder:

    const textDecoder = new TextDecoder();

    756 ❘ CHAPTER 20  JavaScript APIs

    This instance exposes a decode() method, which accepts a typed array and returns the

    decoded string:

    const textDecoder = new TextDecoder();

    // f encoded in utf-8 is 0x66 (102 in decimal)

    // o encoded in utf-8 is 0x6F (111 in decimal)

    const encodedText = Uint8Array.of(102, 111, 111);

    const decodedText = textDecoder.decode(encodedText);

    console.log(decodedText); // foo

    The decoder does not care which typed array it is passed, so it will dutifully decode the entire binary

    representation. In this example, 32-bit values only containing 8-bit characters are decoded as UTF-8,

    yielding extra empty characters:

    const textDecoder = new TextDecoder();

    // f encoded in utf-8 is 0x66 (102 in decimal)

    // o encoded in utf-8 is 0x6F (111 in decimal)

    const encodedText = Uint32Array.of(102, 111, 111);

    const decodedText = textDecoder.decode(encodedText);

    console.log(decodedText); // "f o o "

    The decoder is equipped to handle characters that span multiple indices in the typed array, such

    as emojis:

    const textDecoder = new TextDecoder();

    // encoded in UTF-8 is 0xF0 0x9F 0x98 0x8A (240, 159, 152, 138 in decimal)

    const encodedText = Uint8Array.of(240, 159, 152, 138);

    const decodedText = textDecoder.decode(encodedText);

    console.log(decodedText); //

    Unlike TextEncoder, TextDecoder is compatible with a wide number of character encodings.

    Consider the following example, which uses UTF-16 encoding instead of the default UTF-8:

    const textDecoder = new TextDecoder('utf-16');

    // f encoded in utf-8 is 0x0066 (102 in decimal)

    // o encoded in utf-8 is 0x006F (111 in decimal)

    const encodedText = Uint16Array.of(102, 111, 111);

    const decodedText = textDecoder.decode(encodedText);

    console.log(decodedText); // foo

    Stream Decoding

    A TextDecoderStream is merely a TextDecoder in the form of a TransformStream. Piping an

    encoded text stream through the stream decoder will yield a stream of decoded text chunks:

    async function* chars() {

    // Each chunk must exist as a typed array

    const encodedText = [102, 111, 111].map((x) => Uint8Array.of(x));

    展开全文
    weixin_34293588 2021-06-17 12:59:10
  • 5星
    6.36MB weixin_43960172 2019-01-09 00:50:03
  • 153KB nrniu 2017-09-01 21:07:27
  • 4MB weixin_38744207 2019-09-23 15:43:55
  • 6.14MB weixin_38744375 2019-09-23 16:13:14
  • 13.87MB kernelkoder 2018-08-05 16:53:59
  • 5.71MB weixin_38744270 2019-09-23 15:44:03
  • 22.81MB nn123456789 2018-04-27 21:34:06
  • 8.64MB qq_42281988 2018-05-22 23:11:24
  • 9.21MB weixin_38743602 2019-09-23 18:22:35
  • 9.41MB weixin_38744270 2019-09-23 16:02:14
  • 3.08MB sfdg5467 2018-04-25 18:49:13
  • 803KB admy55 2017-08-03 15:18:29
  • 5星
    5.17MB zmjlz 2014-10-30 15:40:01
  • 5星
    57.22MB weixin_38743602 2019-09-23 19:11:05
  • 5.7MB jsntghf 2016-08-16 22:48:31
  • 5星
    27.9MB s276091348 2018-11-01 10:02:33
  • 5星
    6.99MB u010984814 2013-06-07 00:21:10

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 34,457
精华内容 13,782
关键字:

forthwell

友情链接: TimedMovement.rar