Showing posts with label dom. Show all posts
Showing posts with label dom. Show all posts

March 28, 2011

HTMLCollections & NodeLists

Most of us believed, at least for some time, that in our DOM Scripting, we always dealt with arrays in our JavaScript:
var my_links = document.getElementsByTagName('a'); // we have three links
alert(my_links.length) // outputs "3"

We later found out that the things we thought were arrays, were instead array-like objects. But how exactly are they like arrays? Those "array-like" objects/elements/things, most of the time, are either HTMLCollections or NodeLists, not native JavaScript array objects. Take a look at what the specification says of them, the keyword is live:

"An HTMLCollection is a list of nodes. Collections in the HTML DOM are assumed to be live meaning that they are automatically updated when the underlying document is changed."
- DOM Level 1

"The NodeList interface provides the abstraction of an ordered collection of nodes, without defining or constraining how this collection is implemented. NodeList objects in the DOM are live."
- DOM Level 3

But what does that mean? It means that live collections, if modified, are updated as the program runs. For example, this is an infinte loop:

var i, j,
    my_links = document.getElementsByTagName('a'); // we have three links

for (i = 0, j = my_links.length; i < j; i += 1 ) {
    document.body.appendChild(document.createElement('a'));
}

We're getting our collection (of three links) and then for each link that we have, we're going to append another link to the body. So why is this infinite? Because the collection is live, which means that not only will i increment, j also will, so naturally the loop will keep going.

So why are they called array-like objects? If it looks like an array and acts like an array, then it must be an array, right? Wrong, DOM collections look like arrays because:
  • They have an associated index to each value in the container. But that's something an object can have too:
    var my_obj = {
        0: 'zero',
        1: 'one',
        2: 'two',
        3: 'three'
    };
    
    alert( my_obj[0] ); // 'zero'
    alert( my_obj[3] ); // 'three'
    
    The alert statements might look like they want the elements with index 0 and 3 but you're really getting the value from the property named 0 and 3.
  • They have a length property. This is deceptive because arrays have this same property, but so do HTMLCollections and NodeLists. But, because these are not true arrays they do not have push, concat, splice or any of the other array methods.

Be prepared, these are some of the DOM methods (that I know of) that return an HTMLCollection or NodeList:

// DOM Level 1/HTML 4.0
// ---------------------------
// Return an HTMLCollection
document.anchors
document.applets
document.forms 
document.images 
document.links

document.getElementsByName

formElement.elements
selectElement.options
tableElement.rows
tableElement.tBodies
tableRowElement.cells

// Not part of any standard
document.embeds
document.plugins

// DOM Level 2
// ------------------
// Return a NodeList
Node.childNodes

document.getElementsByName
document.getElementsByTagName
document.getElementsByTagNameNS
document.getElementsByName

element.getElementsByTagName
element.getElementsByTagNameNS

// WHATWG Web Applications 1.0
// ---------------------------
// Return a NodeList
document.getElementsByClassName
element.getElementsByClassName

Throughout this post I've been talking about NodeLists as such and not as live NodeLists because they are inherintely live. There's an exception to this, there are static NodeLists that act as snapshots and do not update when the document is modified:

// Selectors API Level 1
// ---------------------
// Return a static NodeList
document.querySelectorAll
element.querySelectorAll

In conclusion, I think it's important to know the differences between a live DOM collection a true JavaScript array, it's also an important thing to be aware of because you'll eventually interact with these.

I know so far I've been talking mostly about the DOM (sorry, this wasn't the exception) but you cannot say that this was a boring topic, or was it?

Thanks for reading and let me know your comments.

Sources:
Why is getElementsByTagName() faster than querySelectAll()?
Speed Up Your JavaScript (video)
HTMLCollection - MDN Doc Center, NodeList - MDN Doc Center
DOM Level 1 Specification, DOM Level 2 Specification, DOM Level 3 Specification

March 3, 2011

Using innerHTML

innerHTML is a read/write property of a DOM element that gets/sets the HTML contained in the element.

It's fast
This might vary between browsers but, it's almost a fact that creating and inserting elements using innerHTML instead of DOM methods is faster, not only at execution time, it'll also make the script size lighter because less code is needed.

It's clean & readable
Although the name, innerHTML, might seem confusing at first, it comes as the better choice in terms of code readability because DOM methods are very verbose and can consume a lot of lines of code.

It's supported
It was first introduced by Microsoft as proprietary to IE and there's no spec that defines the behavior of innerHTML but it has been adopted by all major browsers because of its usefulness and it pretty much works the same in all of them.

Creating and inserting using DOM methods:
var newDiv = document.createElement('div');
newDiv.setAttribute('id', 'new-div');
newDiv.setAttribute('class', 'big-div');
var text = document.createTextNode('Some text here');
newDiv.appendChild(text);

document.body.appendChild(newDiv); // div is inserted in the tree

Creating and inserting using innerHTML:
var newDiv = '<div id=\'new-div\' class=\'big-div\'>Some text here</div>';
document.body.innerHTML = newDiv; // div is inserted in the tree

Reasons not to use it

  • Not standard. Although it's fast and it works, the bottom line is that it is not part of any W3C or DOM standard. However, there are plans of adding it to the HTML5 specification.
  • XSS unsafe. You have to know when to use it, otherwise you are exposing your application to XSS attacks, choose DOM methods until you're familiar with the subject.
  • Not implemented everywhere. There are some table related elements, in IE, that can't be modified with it. The implementation and behavior might vary from browser to browser.
  • Destroys the children. Setting a value to innerHTML will destroy every descendant to that element, if any of those descendants had event handlers, that could potentially create a memory leak in some browsers.
Thanks for reading and let me know your comments.

Sources:
innerHTML (Mozilla Developer Network)
innerHTML (Microsoft Developer Network)

February 28, 2011

Manipulating DOM elements and styles

Manipulating elements
Once you have access to an element, you can manipulate it using assignment of object properties or DOM methods. Let's take a look at the examples:

HTML
<img src="small-dog.png" id="img-dog">
JavaScript
var myImage = document.getElementById('img-dog');

// Object property
myImage.src = 'big-dog.png';

// DOM method
myImage.setAttribute('src', 'big-dog.png');

In both examples we're changing the src of our image element. It's recommended to use the first example, it's easier, shorter, faster and works across browsers. If, for some reason, you can't use it, use the second one. Here's a link to quirksmode.org with more information.



Style
You can also change an element's style dynamically . You can do so by changing the className property or applying a style directly into the style object.

HTML
<span id="my-span">Some text here</span>
JavaScript
var mySpan = document.getElementById('my-span');

mySpan.className = 'big'; // the CSS class 'big' makes the text big
mySpan.style.textAlign = 'center'; // centers the text

Both, className and style, are read/write. But, the style object will only reflect styles that are directly applied to the element (i.e. in the markup), not the ones from the cascade. To get those you need go a little bit further:

HTML
<span id="my-span">Some text here</span>
CSS
#my-span { font-weight: bold; }
JavaScript
var mySpan = document.getElementById('my-span');

mySpan.style.fontWeight; // returns an empty string
mySpan.currentStyle['fontWeight']; // returns 'bold', works only in IE
document.defaultView.
        getComputedStyle(mySpan, null).
        getPropertyValue('font-weight'); // returns 'bold', doesn't work in IE
As you can see:

  • The CSS property on the style object didn't have anything and returned an empty string.
  • IE's proprietary method is very nice and readable (you could also use the dot notation, e.g. mySpan.currentStyle.fontWeight).
  • The standard way is very verbose and complicated but works on all standards compliant browsers.
If you need to get styles individually I suggest creating a utility function to deal with the cross browser issues (an example from quirksmode.org).
But in general, I recommend using CSS classes for dynamic styling. It is way easier and faster to add/modify/delete CSS classes. It's a style agnostic practice, in case you don't have control over the styles, and also encourages layer separation.

Thanks for reading and let me know your comments.

Source: Douglas Crockford: "Theory of the Dom" (2 of 3)

February 7, 2011

The script tag and the DOM

Today I'm going to talk about the DOM. Things you might not know, tricks, nuances and some best practices (I think). So, let's get to it.

The <script> tag
It first appeared in Netscape Navigator 2 as a way to put programs in HTML documents and it then became part of the HTML standard in version 4.
<script type="text/javascript">...</script>



<!-- // -->
The <!-- // --> hack was a workaround to prevent HTML documents from displaying code on the page by older browsers like Mosaic. You can still see this pattern in pages but it's no longer necessary as today's browsers understand both the script tag and JavaScript.
<script type="text/javascript">
<!--
  code here
// -->
</script>



The language attribute
Microsoft came up with it as a way to select what kind of language (e.g. VBScript) you wanted to use for your program. The W3C deprecated this in favor of the type attribute. Avoid the language attribute.
<script language="javascript" type="text/javascript">...</script>



The src attribute
It allows you to load a script from an external file. Great, because you don't want to put JavaScript in your HTML, for a large number of reasons: it makes the HTML file heavier, it becomes less maintainable, it becomes less scalable, it's not cachable, it becomes hard to minify or inspect, etc. It is highly recommended that you do not write JavaScript in your HTML and instead use the src attribute.
<script type="text/javascript" scr="js/script.js"></script>



The type attribute
The W3C added the type attribute to replace the language one. It takes a MIME type instead of the name of the language. The official MIME type for JavaScript is application/javascript or application/ecmascript and the most up to date browsers understand this, but not IE. So, if you want cross-browser support (and you should) you have to use type="text/javascript", at least for now. This is a required attribute for HTML validation, but in practice you can just leave this out, the default language on all major browsers is JavaScript and besides if you're using src, it ignores it.
<script type="text/javascript">...</script>
<script>...</script>
<script type="text/javascript" src="js/script.js">...</script>
<script src="js/script.js">...</script>



Loading time
Because of the way the browser works, loading of a page's assets is incremental and single-threaded, it is recommended that the script tags be placed as low in the body as possible. It is also recommended to place your CSS i.e. link, as high in the head as possible, this will greatly improve the user experience.
You should also minify, gzip and join your script files into one. These will reduce file size and the number of HTTP requests and thus, the time it takes for the browser to load the page.
<html>
  <head>
    <link rel="stylesheet" type="text/css" href="css/styles.css">
    <!-- head contents here -->
  </head>
  <body>
    <!-- body contents here -->
    <script type="text/javascript" src="js/script.js"></script>
  </body>
</html>



document.write
If you call this before the onload event triggers it will insert stuff into your document, but if you do it after it, it destroys your document and replace it with new stuff. You should avoid using document.write, there are better alternatives now.



Collections
  • document.anchors
  • document.applets
  • document.embeds
  • document.forms
  • document.frames
  • document.images
  • document.plugins
  • document.scripts
  • document.stylesheets
Netscape provided these as a way to get and modify elements easier. These are still available but obsolete and you should avoid using them as there are better alternatives now.



The name and id attributes
If an element didn't have an id but it had a name with the same value, you could still get access to it via DOM methods. They used to be interchangeable but they are not anymore.

The HTML:
<input type="text" name="firstname" value="my value">
The JavaScript:
var input = document.getElementById('firstname');
input.value = 'another value'; // sets a value to "firstname" field



document.all
Microsoft came up with this as a type of super collection that contained any tag that had a name or id. Since this is propriety thing and not cross-browser, it is best to not use it.



Some of the topics that I did not cover in this post were browser history, how a browser works and document tree structure. If you're interested, click the link below to watch the video.

Thanks for reading and let me know your comments.

Source: Douglas Crockford: "Theory of the DOM" (1 of 3)