• 5星
8.82MB weixin_44573410 2021-03-02 23:35:55
• ## On Writing Well 2006 (30th Anniversary Edition) Writing

1.33MB loushuai 2016-10-29 20:49:39
• ## Networking All-in-One For Dummies 6th 2016第6版 无水印pdf 0分 Networking

3星
22.67MB u011433684 2016-02-20 12:58:01
• ## 英文原版-Meditation For Dummies 4th Edition 英文

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代码

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代码

handlers = org.apache.juli.FileHandler, java.util.logging.ConsoleHandler        ############################################################    # Handler specific properties.    # Describes specific configuration info for Handlers.    ############################################################        org.apache.juli.FileHandler.level = FINE    org.apache.juli.FileHandler.directory = ${应用目录}/logs org.apache.juli.FileHandler.prefix = error-debug. java.util.logging.ConsoleHandler.level = FINE 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代码 2013-7-29 9:58:11 org.apache.catalina.core.ApplicationContext log 信息: Initializing Spring root WebApplicationContext 2013-7-29 9:58:16 org.apache.catalina.core.StandardContext listenerStart 严重: Exception sending context initialized event to listener instance of class com.alibaba.citrus.webx.context.WebxContextLoaderListener org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: Failed to import bean definitions from URL location [classpath:web/common/uris.xml] 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; 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; 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; at com.alibaba.citrus.springext.util.SpringExtUtil.parseBeanDefinitionAttributes(SpringExtUtil.java:223) at com.alibaba.citrus.service.uribroker.impl.URIBrokerServiceDefinitionParser.doParse(URIBrokerServiceDefinitionParser.java:48) at org.springframework.beans.factory.xml.AbstractSingleBeanDefinitionParser.parseInternal(AbstractSingleBeanDefinitionParser.java:81) at org.springframework.beans.factory.xml.AbstractBeanDefinitionParser.parse(AbstractBeanDefinitionParser.java:56) at org.springframework.beans.factory.xml.NamespaceHandlerSupport.parse(NamespaceHandlerSupport.java:69) at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1123) at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1113) at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:133) at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.registerBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:90) at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.registerBeanDefinitions(XmlBeanDefinitionReader.java:468) 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 • ## 英文原版-Treatment Resource Manual for Speech Language Pathology 4th Edition 英文 4.9MB weixin_38743602 2019-09-23 23:33:40 • 37KB weixin_38601446 2021-02-22 21:41:12 • ## AutoCAD 2019: A Power Guide for Beginners and Intermediate Users 4th Edition AutoCAD 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 • ## Essential_MATLAB_for_Engineers_and_Scientists-4th_edition MATLAB 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: RedGreenBlue 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:
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:
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:
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:
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:
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){
} else if (input.validity.typeMismatch){
} 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:
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
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");
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 "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:
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
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
Removes a text link. This is the opposite
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
"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);
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
➤➤ 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
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
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);
for (let i = 0; i < 1E6; ++i) {
}
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:
// 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
}
});
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));
}
}
async start(controller) {
for await (let chunk of chars()) {
controller.enqueue(chunk);
}
controller.close();
}
});
const encodedTextStream = decodedTextStream.pipeThrough(new TextEncoderStream());
(async function() {
while(true) {
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
• ## Problem Solving with C++, 10th Global Edition C++

5星
6.36MB weixin_43960172 2019-01-09 00:50:03
• ## Learning Word Vectors for Sentiment Analysis 情感分析

153KB nrniu 2017-09-01 21:07:27
• ## 英文原版-Android Recipes 4th Edition 英文

4MB weixin_38744207 2019-09-23 15:43:55
• ## 英文原版-Linux in a Nutshell 6th Edition 英文

6.14MB weixin_38744375 2019-09-23 16:13:14
• 13.87MB kernelkoder 2018-08-05 16:53:59
• ## 英文原版-C Primer Plus 6th Edition 英文

5.71MB weixin_38744270 2019-09-23 15:44:03
• 22.81MB nn123456789 2018-04-27 21:34:06
• ## computer Networking - A top down approach 6th networking

8.64MB qq_42281988 2018-05-22 23:11:24
• ## 英文原版-PHP and MySQL Web Development 4th Edition 英文

9.21MB weixin_38743602 2019-09-23 18:22:35
• ## 英文原版-The ABSITE Review 4th Edition 英文

9.41MB weixin_38744270 2019-09-23 16:02:14
• 3.08MB sfdg5467 2018-04-25 18:49:13
• 5星
5.17MB zmjlz 2014-10-30 15:40:01
• ## 英文原版-Fundamentals of Computer Graphics 4th Edition（解压密码share.weimo.info） 英文

5星
57.22MB weixin_38743602 2019-09-23 19:11:05
• ## Android Recipes, 4th Edition android

5.7MB jsntghf 2016-08-16 22:48:31
• ## Windows Internals (6th Edition) windows

5星
27.9MB s276091348 2018-11-01 10:02:33
• 5星
6.99MB u010984814 2013-06-07 00:21:10

...