Chapter 3 – Element Nodes

最后更新于:2022-04-01 04:39:13

## 3.1 HTML*Element object overview Elements in an html document all have a unique nature and as such they all have a unique [JavaScript constructor](http://www.w3.org/TR/2003/REC-DOM-Level-2-HTML-20030109/html.html)that instantiates the element as a node object in a DOM tree. For example an *`<a>`* element is created as a DOM node from the *HTMLAnchorElement()* constructor. Below we verify that an anchor element is created from*HTMLAnchorElement()*. live code: [http://jsfiddle.net/domenlightenment/TgcNu](http://jsfiddle.net/domenlightenment/TgcNu) ~~~ <!DOCTYPE html> <html lang="en"> <body> <a></a> <script> // grab <a> element node from DOM and ask for the name of the constructor that constructed it console.log(document.querySelector('a').constructor); //logs function HTMLAnchorElement() { [native code] } </script> </body> </html> ~~~ The point I am trying to express in the previous code example is that each element in the DOM is constructed from a unique JavaScript intefaces/constructor. The list below (not a [complete list](http://www.whatwg.org/specs/web-apps/current-work/multipage/section-index.html#elements-1)) should give you a good sense of the interfaces/constructors used to create HTML elements. * *HTMLHtmlElement* * *HTMLHeadElement* * *HTMLLinkElement* * *HTMLTitleElement* * *HTMLMetaElement* * *HTMLBaseElement* * *HTMLIsIndexElement* * *HTMLStyleElement* * *HTMLBodyElement* * *HTMLFormElement* * *HTMLSelectElement* * *HTMLOptGroupElement* * *HTMLOptionElement* * *HTMLInputElement* * *HTMLTextAreaElement* * *HTMLButtonElement* * *HTMLLabelElement* * *HTMLFieldSetElement* * *HTMLLegendElement* * *HTMLUListElement* * *HTMLOListElement* * *HTMLDListElement* * *HTMLDirectoryElement* * *HTMLMenuElement* * *HTMLLIElement* * *HTMLDivElement* * *HTMLParagraphElement* * *HTMLHeadingElement* * *HTMLQuoteElement* * *HTMLPreElement* * *HTMLBRElement* * *HTMLBaseFontElement* * *HTMLFontElement* * *HTMLHRElement* * *HTMLModElement* * *HTMLAnchorElement* * *HTMLImageElement* * *HTMLObjectElement* * *HTMLParamElement* * *HTMLAppletElement* * *HTMLMapElement* * *HTMLAreaElement* * *HTMLScriptElement* * *HTMLTableElement* * *HTMLTableCaptionElement* * *HTMLTableColElement* * *HTMLTableSectionElement* * *HTMLTableRowElement* * *HTMLTableCellElement* * *HTMLFrameSetElement* * *HTMLFrameElement* * *HTMLIFrameElement* Keep in mind each *HTML*Element *above inherits properties and methods from *HTMLElement*, *Element*, *Node*, and *Object*. ## 3.2 HTML*Element object properties and methods (including inherited) To get accurate information pertaining to the available properties and methods on an *HTML*Element* node its best to ignore the specification and to ask the browser what is available. Examine the arrays created in the code below detailing the properties and methods available from HTML element nodes. live code: [http://jsfiddle.net/domenlightenment/vZUHw](http://jsfiddle.net/domenlightenment/vZUHw) ~~~ <!DOCTYPE html> <html lang="en"> <body> <a href="#">Hi</a> <script> var anchor = document.querySelector('a'); //element own properties console.log(Object.keys(anchor).sort()); //element own properties & inherited properties var documentPropertiesIncludeInherited = []; for(var p in document){ documentPropertiesIncludeInherited.push(p); } console.log(documentPropertiesIncludeInherited.sort()); //element inherited properties only var documentPropertiesOnlyInherited = []; for(var p in document){ if(!document.hasOwnProperty(p)){ documentPropertiesOnlyInherited.push(p); } } console.log(documentPropertiesOnlyInherited.sort()); </script> </body> </html> ~~~ The available properties are many even if the inherited properties were not considered. Below I've hand pick a list of note worthy properties and methods (inherited as well) for the context of this chapter. * *createElement()* * *tagName* * *children* * *getAttribute()* * *setAttribute()* * *hasAttribute()* * *removeAttribute()* * *classList()* * *dataset* * *attributes* For a complete list check out the MDN documentation which covers the [general properties and methods](https://developer.mozilla.org/en/DOM/element) available to most HTML elements. ## 3.3 Creating Elements *Element* nodes are instantiated for us when a browser interputs an HTML document and a corresponding DOM is built based on the contents of the document. After this fact, its also possible to programaticlly create *Element*nodes using *createElement()*. In the code below I create a  element node and then inject that node into the live DOM tree. live code: [http://jsfiddle.net/domenlightenment/d3Yvv](http://jsfiddle.net/domenlightenment/d3Yvv) ~~~ <!DOCTYPE html> <html lang="en"> <body> <script> var elementNode = document.createElement('textarea'); //HTMLTextAreaElement() constructs <textarea> document.body.appendChild(elementNode); console.log(document.querySelector('textarea')); //verify it's now in the DOM </script> </body> </html> ~~~ The value passed to the *createElement()* method is a string that specifices the type of element (aka *[tagName](http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-104682815)*) to be created. ### Notes This value passed to createElement is changed to a lower-case string before the element is created. ## 3.4 Get the tag name of an element Using the *tagName* property we can access the name of an element. The *tagName* property returns the same value that using *nodeName* would return. Both return the value in uppercase regardless of the case in the source HTML document. Below we get the name of an *`<a>`* element in the DOM. live code: [http://jsfiddle.net/domenlightenment/YJb3W](http://jsfiddle.net/domenlightenment/YJb3W) ~~~ <!DOCTYPE html> <html lang="en"> <body> <a href="#">Hi</a> <script> console.log(document.querySelector('a').tagName); //logs A //the nodeName property returns the same value console.log(document.querySelector('a').nodeName); //logs A </script> </body> </html> ~~~ ## 3.5 Getting a list/collection of element attributes and values Using the *attributes* property (inherited by element nodes from *Node*) we can get a collection of the *[Attr](http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-637646024)*nodes that an element currently has defined. The list returned is a *[NameNodeMap](https://developer.mozilla.org/en/DOM/NamedNodeMap)*. Below I loop over the attributes collection exposing each *Attr* node object contained in the collection. live code: [http://jsfiddle.net/domenlightenment/9gVQf](http://jsfiddle.net/domenlightenment/9gVQf) ~~~ <!DOCTYPE html> <html lang="en"> <body> <a href='#' title="title" data-foo="dataFoo" class="yes" style="margin:0;" foo="boo"></a> <script> var atts = document.querySelector('a').attributes; for(var i=0; i< atts.length; i++){ console.log(atts[i].nodeName +'='+ atts[i].nodeValue); } </script> </body> </html> ~~~ ### Notes The array returned from accessing the attributes property should be consider live. Meaning that its contents can be changed at anytime. The array that is returned inherits from the *NameNodeMap* which provides methods to operate on the array such as*getNamtedItem()*, *setNamedItem()*, and *removeNamedItem()*. Operating on *attributes* with these methods should be secondary to using *getAttribute()*, *setAttribute()*, *hasAttribute()*, *removeAttribute()*. Its this authors opinion that dealing with [Attr](http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-637646024) nodes is messy. The only merit in using the *attributes* is found only in its funcitonaly for returning a list of live attributes. The *attributes* property is an array like collection and has a read only *length* property. Boolean attributres (e.g. *foo*) show up in the *attributes* list but of course have no value unless you provide one (e.g. *foo*). ## 3.6 Getting, Setting, & Removing an element's attribute value The most consistent way to get, set, or remove an elements [attribute](http://www.whatwg.org/specs/web-apps/current-work/#attributes-1) value is to use the *getAttribute(), setAttribute(),* and *removeAttribute()* method. In the code below I demonstrate each of these methods for managing element attributes. live code: [http://jsfiddle.net/domenlightenment/wp7rq](http://jsfiddle.net/domenlightenment/wp7rq) ~~~ <!DOCTYPE html> <html lang="en"> <body> <a href='#' title="title" data-foo="dataFoo" style="margin:0;" class="yes" foo="boo" hidden="hidden">#link</a> <script> var atts = document.querySelector('a'); //remove attributes atts.removeAttribute('href'); atts.removeAttribute('title'); atts.removeAttribute('style'); atts.removeAttribute('data-foo'); atts.removeAttribute('class'); atts.removeAttribute('foo'); //custom attributeatts.removeAttribute('hidden'); //boolean attribute //set (really re-set) attributes atts.setAttribute('href','#'); atts.setAttribute('title','title'); atts.setAttribute('style','margin:0;'); atts.setAttribute('data-foo','dataFoo'); atts.setAttribute('class','yes'); atts.setAttribute('foo','boo'); atts.setAttribute('hidden','hidden'); //boolean attribute requires sending the attribute as the value too //get attributes console.log(atts.getAttribute('href')); console.log(atts.getAttribute('title')); console.log(atts.getAttribute('style')); console.log(atts.getAttribute('data-foo')); console.log(atts.getAttribute('class')); console.log(atts.getAttribute('foo')); console.log(atts.getAttribute('hidden')); </script> </body> </html> ~~~ ### Notes Use *removeAttribute()* instead of setting the attribute value to *null* or *''* using *setAttribute()* Some element attributes are available from element nodes as object properties (i.e. *document.body.id* or*document.body.className*). This author recommends avoiding these properties and using the remove, set, and get attribute methods. ## 3.7 Verifying an element has a specific attribute The best way to determine (i.e. boolean) if an element has an attribute is to use the *hasAttribute()*  method. Below I verify if the *`<a>`* has a *href*, *title*, *style*, *data-foo*, *class*, and *foo* attribute. live code: [http://jsfiddle.net/domenlightenment/hbCCE](http://jsfiddle.net/domenlightenment/hbCCE) ~~~ <!DOCTYPE html> <html lang="en"> <body> <a href='#' title="title" data-foo="dataFoo" style="margin:0;" class="yes" goo></a> <script> var atts = document.querySelector('a'); console.log( atts.hasAttribute('href'), atts.hasAttribute('title'), atts.hasAttribute('style'), atts.hasAttribute('data-foo'), atts.hasAttribute('class'), atts.hasAttribute('goo') //Notice this is true regardless if a value is defined ) </script> </body> </html> ~~~ This method will return *true* if the element contains the attribute even if the attribute has no value. For example using *hasAttribute()* we can get a boolean response for [boolean attributes](http://www.w3.org/TR/html4/intro/sgmltut.html#h-3.3.4.2). In the code example below we check to see if a checkbox is checked. live code: [http://jsfiddle.net/domenlightenment/tb6Ja](http://jsfiddle.net/domenlightenment/tb6Ja) ~~~ <!DOCTYPE html> <html lang="en"> <body> <input type="checkbox" checked></input> <script> var atts = document.querySelector('input'); console.log(atts.hasAttribute('checked')); //logs true </script> </body> </html> ~~~ ## 3.8 Getting a list of class attribute values Using the *classList* property available on element nodes we can access a list (i.e. *[DOMTokenList](http://www.w3.org/TR/dom/#interface-domtokenlist)*) of class attribute values that is much easier to work with than a space-delimited string value returned from the *className*property. In the code below I contrast the use of *classList* with *className*. live code: [http://jsfiddle.net/domenlightenment/DLJEA](http://jsfiddle.net/domenlightenment/DLJEA) ~~~ <!DOCTYPE html> <html lang="en"> <body> <div class="big brown bear"></div> <script> var elm = document.querySelector('div'); console.log(elm.classList); //big brown bear {0="big", 1="brown", 2="bear", length=3, ...} console.log(elm.className); //logs 'big brown bear' </script> </body> </html> ~~~ ### Notes Given the *classList* is an array like collection it has a read only *length* property. *classList* is read-only but can be modifyied using the *add()*,* remove()*, *contains()*, and *toggle()* methods IE9 does not support *classList*. Support will land in [IE10](http://blogs.msdn.com/b/ie/archive/2012/05/31/windows-release-preview-the-sixth-ie10-platform-preview.aspx). [Several](https://github.com/eligrey/classList.js) [polyfills](https://gist.github.com/1381839) are avaliable. ## 3.9 Adding & removing sub-values to a class attribute Using the *classList.add()* and *classList.remove()* methods its extremely simple to edit the value of a class attribute. In the code below I demonstrated adding and removing class values. live code: [http://jsfiddle.net/domenlightenment/YVaUU](http://jsfiddle.net/domenlightenment/YVaUU) ~~~ <!DOCTYPE html> <html lang="en"> <body> <div class="dog"></div>​ <script> var elm = document.querySelector('div'); elm.classList.add('cat'); elm.classList.remove('dog'); console.log(elm.className); //'cat' </script> </body> </html> ~~~ ## 3.10 Toggling a class attribute value Using the *classList.toggle()* method we can toggle a sub-value of the class attribute. This allows us to add a value if its missing or remove a value if its already added. In the code below I toggle the *'visible'* value and the*'grow'* value. Which essentially means I remove *'visible'* and add *'grow'* to the class attribute value. live code: [http://jsfiddle.net/domenlightenment/uFp6J](http://jsfiddle.net/domenlightenment/uFp6J) ~~~ <!DOCTYPE html> <html lang="en"> <body> <div class="visible"></div>​ <script> var elm = document.querySelector('div'); elm.classList.toggle('visible'); elm.classList.toggle('grow'); console.log(elm.className); //'grow' </script> </body> </html> ~~~ ## 3.11 Determining if a class attribute value contains a specific value Using the *classList.contains()* method its possible to determine (boolean) if a class attribute value contains a specific sub-value. In the code below we test weather the *`<div>`* class attribute contains a sub-value of *brown*. live code: [http://jsfiddle.net/domenlightenment/njyaP](http://jsfiddle.net/domenlightenment/njyaP) ~~~ <!DOCTYPE html> <html lang="en"> <body> <div class="big brown bear"></div>​ <script> var elm = document.querySelector('div'); console.log(elm.classList.contains('brown')); //logs true </script> </body> </html> ~~~ ## 3.12 Getting & Setting data-* attributes The *dataset* property of a element node provides an object containing all of the attributes of an element that starts with data-*. Because its a simply a JavaScript object we can manipulate *dataset* and have the element in the DOM reflect those changes live code: [http://jsfiddle.net/domenlightenment/ystgj](http://jsfiddle.net/domenlightenment/ystgj) ~~~ <!DOCTYPE html> <html lang="en"> <body> <div data-foo-foo="foo" data-bar-bar="bar"></div>​ <script> var elm = document.querySelector('div'); //get console.log(elm.dataset.fooFoo); //logs 'foo' console.log(elm.dataset.barBar); //logs 'bar' //set elm.dataset.gooGoo = 'goo'; console.log(elm.dataset); //logs DOMStringMap {fooFoo="foo", barBar="bar", gooGoo="goo"} //what the element looks like in the DOM console.log(elm); //logs <div data-foo-foo="foo" data-bar-bar="bar" data-goo-goo="goo"> </script> </body> </html> ~~~ ### Notes *dataset* contains camel case versions of data attributes. Meaning *data-foo-foo* will be listed as the property *fooFoo* in the dataset *DOMStringMap* object. The*-* is replaced by camel casing. Removing a data-* attribute from the DOM is as simple using the *delete* operator on a property of the *datset* (e.g. *delete dataset.fooFoo*) *dataset* is not supported in IE9\. A [polyfill](https://github.com/remy/polyfills/blob/master/dataset.js) is avaliable. However, you can always just use getAttribute('data-foo'), removeAttribute('data-foo'), setAttribute('data-foo'), hasAttribute('data-foo').
';