Note: This entry was first published on the company wiki at
IWS to better help Python devs learn Javascript.
It has been here slightly modified to omit or work around IWS-specific customizations, with source code supplied for
fromJSON(),
toJSON(), and
toObject().
You may also want to
download MooTools to try some of the examples here, or follow along in your own
JSFiddle (where MooTools is the included default).
HTML is to JavaScript what the Database is to Python
Just as a guide on the essentials of using Python for a web-based application would be incomplete without at the very least mentioning how it interacts with the database, so too a guide on JavaScript is incomplete without understanding how it interacts with HTML.
To understand JavaScript well, one should have at least a basic understanding of both HTML and CSS, as any web application is made of web pages, and web pages are written in HTML which is itself styled with CSS.
Understanding the power of interactive JavaScript also involves a basic understanding of events: when the user clicks a mouse or enters commands on the keyboard or drags a finger across a screen or track pad, the interface magically responds. The magic between the user action and the result is called an
event, and the way JavaScript understands events is by “listening” to an HTML element: by attaching an “event listener.”
We will discuss all these things, and more, in greater detail.
Brief Glance at HTML: Elements Nested Inside Elements Nested Inside...
Let’s begin with a very brief look at HTML, short for “hyper-text markup language.” A typical web page has a
doctype declaration that explicitly tells the browser how to render the page. Following this declaration, the entire document is surrounded by opening and closing
html tags.
Nested inside these outer tags are two other inner sets of tags: an opening and closing
head tag followed by an opening and closing
body tag. Inside the head tag, things such as the title of page (displayed in the browser history and on the browser tab), any meta data, links to any style sheets, and links to any JavaScript files are specified. The body section that follows contains the actual elements that make up the visible part of the web page.
Here’s a skeleton of a web page using the HTML5
doctype:
<!DOCTYPE html>
<html>
<head>
<title>Sample Page</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" type="text/css" href="css/some-css-file.css" />
<script type="text/javascript" src="js/some-js-file.js"></script>
</head>
<body>
<p>
I am a single-sentence paragraph on the page.
</p>
<p>
I am another paragraph on the page. I contain two
sentences.
</p>
<p>
I am a third paragraph on the page. I contain three
sentences. See?
</p>
<div>
I am a generic “div” element that contains a
<a href="http://www.some-link.com">hyperlink</a>,
otherwise known as an “anchor” tag, hence the
opening and closing “a” brackets.
</div>
<!-- I am a multi-line HTML comment that the browser
will ignore: I exist only to comment out sections of
HTML markup or to be read by human “marker-uppers”
and “code sniffers.” -->
</body>
</html>
Within the opening and closing head tags in the skeleton page above, we define the title, the mime type, then link to an external style sheet, then an external JavaScript file.
Notice that we link to the style sheet
before linking to the JavaScript: it would technically work in any order, but best practices places a priority on the style sheets first so that the visual elements will not be delayed while the JavaScript is loading.
We could technically include several JavaScript files on a page, and the thing to remember is that
order can matter. That is, all JavaScript files have access to the JavaScript in all other JavaScript files loaded in the page (and any inline JavaScript on the page), but if a file gets instantiated immediately and relies on code in another file, the file containing the required code has to be loaded first.
<head>
<script src="js/file-containing-needed-code.js" type="text/javascript"></script>
<script src="js/file-that-needs-above-code.js" type="text/javascript"></script>
</head>
It’s a matter of resolving dependencies: add the file you need to make a second file work first, then add the second file.
The browser is to JavaScript what the server is to Python: One other point to notice is that the HTML page links to the JavaScript file, which is loaded and interpreted on page load. That explains why, unlike with Python, you have to refresh the HTML page before you will see any changes you have made to the JavaScript file. Whereas the server interprets the Python file, it is the browser that interprets the JavaScript file: different run-time environments!
Returning to the skeleton page, our opening and closing body tags contain three paragraph elements, a div element, an anchor element (nested inside the div), and a comment node.
We now have enough pieces in place to look at CSS, short for “cascading style sheets.” We’ll talk about the “cascading” part in just a moment, but our first order of business is understanding how style sheets work in general.
Cascading Style Sheets (CSS): Selecting by Tag Name
Style sheets consist of key-value pairs, where the key is
a predefined CSS property and the value is the setting one wishes to apply for that property. These pairs are assigned on an element-by-element basis.
For example, if we wanted to separate all the paragraph tags by 28 pixels and draw a 1px border around them that was bright orange, we could specify that tag (
p) followed by curly brackets that contained the key and value pairs we wanted:
/* Only C-style, multi-line comments are permissible in CSS.
#f91 is short for #ff9911, the hex value for a vivid
shade of orange. */
p {
margin-bottom: 28px;
border: solid 1px #f91;
}
The above specification could be saved in a text file named “some-css-file” and given a “css” extension; we could then park it inside the “css” directory, and then the
<link> tag in our example page would “pull it into the page” and apply a bottom margin of 28 pixels to all paragraphs drawing an orange box around each one.
CSS: Syntactic Significance of Commas and Spaces
If we wanted, we could specify additional elements as well, separated by commas:
p, div {
margin-bottom: 28px;
border: solid 1px #f91;
}
The example above would apply the same style rules to all paragraph and all div elements on the page.
By contrast, if we used a space instead of a comma, we wouldn’t see any visual difference in our example page from unformatted HTML. Do you know why?
p div {
margin-bottom: 28px;
border: solid 1px #f91;
}
Whereas the comma forms a list of elements, a space says “for all
div elements
inside of paragraph tags, apply these rules.”
This particular example doesn’t make much sense, as div tags would rarely be nested inside paragraph tags anyway (though the reverse might well be true), but in any case, we don’t have any divs inside paragraph tags on the page and thus this rule would simply be ignored.
CSS, then, can use element tag names to target an element. But that’s pretty limited, so there are at least two other very common ways to target an element with CSS. Let’s modify our HTML page slightly to include
id and
class attributes.
<body>
<p class="paragraph">
I am a single-sentence paragraph on the page.
</p>
<p class="paragraph bold large" id="special-paragraph">
I am another paragraph on the page. I contain two
sentences.
</p>
<p class="paragraph">
I am a third paragraph on the page. I contain three
sentences. See?
</p>
<div id="special-div">
I am a generic “div” element that contains a
<a href="http://www.some-link.com">hyperlink</a>,
otherwise known as an “anchor” tag, hence the opening
and closing “a” tags.
</div>
</body>
CSS Classes and Ids
Just like variable names, we can name classes and ids anything we want. The only rule is that ids are mutually exclusive—there can only be one element named a given id on a given page—whereas classes can be used over and over, and, as in our second example, there can be multiple classes separated by spaces for a single element.
Accordingly, our example employs three instances of the class “paragraph,” a single instance of the classes “bold” and “large” each, and two unique ids, one named “special-paragraph” and the other “special-div.”
Like variables, we could just as easily have named them “jack-and-jill” or “YellowSunshine” or anything else in the universe we wanted, so long as they (1) started with a letter and (2) were alpha-numeric (including underscores and dashes).
To specify a style rule for a class in CSS, the class name is signified by a dot or period:
.paragraph {
/* some rules */
}
To target an id, we need to use the pound sign:
#special-div {
/* some rules */
}
As with our tag name examples above, multiple elements can be specified with a comma, separated by a space to target selected child elements inside a parent element, or mixed and matched in some combination thereof:
#my_div .children, h4, h2.some-class-on-h2-elements, p#a-unique-paragraph-id, .catch-all {
/* rules to apply to this assortment of elements */
}
There are
other selector methods used to target elements (see
Mootools: Complex CSS3 Expressions), but a basic understanding of CSS only requires that we know we can use tags (with no preceding punctuation)...
p {
/* some rules */
}
Classes (with a dot)...
.some-class {
/* some rules */
}
And ids (with a pound sign)...
#unique-id {
/* some rules */
}
And that we can target multiple elements at a time with commas or drill down into a parent element using spaces.
To summarize what we’ve covered thus far, style sheets specify properties for one or more elements (using tag names, classes, and ids) by means of
CSS properties (keys) and user-specified values.
Getting Ready to Cascade: External, Internal, Inline
Additionally, there are three ways that styles can be declared. We’ve covered the first one already:
(1) In the
<head> section of the page, we can use a
<link> element to link to an
external style sheet.
(2) In the
<head> section of the page, we can use
internal <style> tags:
<head>
<style type="text/css">
div {
font-size: 14px;
color: #eedeca;
}
.some-class {
font-weight: bold;
font-family: verdana;
background-color: #e2e3e1;
}
<style>
</head>
Side note: You can also write internal JavaScript in the <head> or <body> sections using opening <script type="text/javascript"> and closing </script> tags.
(3) Directly on any element in the
<body> section using an
inline “style” attribute:
<p style="font-style: italic; margin-left: 30px; font-size: 18px; font-family: georgia; color: blue;">
I am an italicized paragraph with a 30-pixel left margin.
My font size is now 18 pixels, my font family is Georgia,
and I’m feeling blue.
</p>
I am an italicized paragraph with a 30-pixel left margin. My font size is now 18 pixels, my font family is Georgia, and I’m feeling blue.
So now we finally come to understand what the “cascading” part of CSS means. A “cascade” is often used to describe water, as in a “cascading waterfall” that “cascades” from top to bottom. Likewise, CSS has a “cascading” order of overwrite importance rule.
If in my
external style sheet, I declare that all paragraph tags should have brown font, but then in the
internal <style> tags in my head turn around and say that all paragraph tags should have green font, the browser will render my HTML page with green paragraph text.
But if on one (or more) paragraphs I include an
inline style that specifies yellow font, the affected paragraphs will be displayed in yellow font (even though in the internal style rules I specified green and in the external style sheet I stipulated brown). Hence “cascading.”
With a basic understanding of HTML and CSS in place, we’re now ready to talk about JavaScript, formally known as
ECMAScript.
ECMAScript a.k.a JavaScript
JavaScript is able to grab and manipulate screen elements; it can also style elements by applying CSS rules directly to them. Both of these are inter-related tasks.
The Crooked Path of the DOM
JavaScript interacts with HTML via the Document Object Model, better known as
the DOM.
The DOM is not HTML and the DOM is not JavaScript: the DOM is a tree-like API that a browser layout engine provides JavaScript for working with HTML.
And, through no fault of JavaScript or modern browser vendors, the DOM has traditionally been a particularly “clunky” and comparatively inefficient way of working with HTML, but it’s the only way we’ve got (and HTML5 is working hard to take some of the sting out of this legacy API).
Now is a good time to mention as well that JavaScript itself depends on browser implementation: a typical browser is written in C and the individual browser vendor (like Microsoft or Mozilla) is responsible for writing the parser/interpreter that compiles JavaScript on page load into C. They do this by coding to an interface—coding to the standard laid out by
ECMA International that says method x, y, and z should exist and behave in such-and-such manner—but the actual implementation is left up to the individual vendor.
That means that JavaScript just got more complicated: with Python, once you get the server set up like you like it, you code once and you’re done. But while server-side JavaScript does exist and is gaining in popularity, client-side JavaScript is utterly at the mercy of the browser vendor.
Though times are thankfully changing, engrave this maxim into your forehead:
not all browser vendors are created equal!
The Crooked Path Made Straight: JavaScript Frameworks To The Rescue
The “clunkiness” of the DOM and the inconsistencies between browsers is where JavaScript frameworks or libraries—like JQuery, Dojo, Prototype, or IWS’s choice of MooTools (short for “My Object-Oriented Tools”)—really shine.
These frameworks are written in pure JavaScript and they smooth out a lot of the inconsistencies between browsers, additionally providing a lot of useful tools that make coding to the browser easier: not quite as smooth as coding to the server perhaps, but definitely a lot closer.
Since we use MooTools at IWS, that will be the framework we’ll look at here.
MooTools makes heavy use of CSS selectors (as the tag, dot, and pound notations we covered above are collectively named) as “handles” to grab hold of elements in the DOM, its selector engine Slick arguably neck-to-neck with JQuery’s well-known Sizzle engine in terms of speed and power. But we don’t care about all that. We just need to know how to use the tools we’ve got to get the job done.
In native JavaScript, if we want to grab an element with a unique id, we drill down from the top-level of the DOM, otherwise known as the
document:
var myElement = document.getElementById('some-element-id-on-the-page');
In the example above, we assign the private
myElement variable (private because of the
var keyword) to the return of
document.getElementById. Notice that the method name begins with
getElement (singular) not
getElements (plural). That is because a CSS id is always supposed to be unique, and we are interested in grabbing just one element.
MooTools gives us a shortcut, a shortcut that happens to be shared by virtually all the major frameworks: MooTools gives us the dollar sign:
var myElement = $('some-element-id-on-the-page');
$() is expecting an id, so we don’t need to pass it the
# selector—we just pass a string that corresponds to an actual id that lives on the page. As with the native
document.getElementById method, the MooTools’
$() returns
null if an element is not found (equivalent to
None in Python).
null notwithstanding, when testing to see if an element is in the DOM, it is generally best/cleanest simply to test for truthy / falsy:
if (myElement) {
// myElement exists: do something with it
}
if (!myElement) {
// it doesn't exist: create it
myElement = new Element('div');
}
The DOM allows native JavaScript to gather together a group of elements in several ways, such as:
var myInputsNamedEmail = document.getElementsByName('email');
var myParagraphElms = document.getElementsByTagName('p');
These methods all begin with
getElements (plural) and, like an
array (analogous to a
list in Python), they have a
length attribute...
myParagraphElms.length;
which tells us how many paragraphs elements we were able to collect in the second example above.
But their return is not a real array! Instead, the return is actually a DOM object—a
collection or formally a
NodeList—not a JavaScript array.
Therefore you can’t
slice() or
push() or
pop() or otherwise use
JavaScript array methods on it; you have to iterate over it using a
for loop and manually pack it into an array before it can be used that way. And once you pack it into an array, it’s no longer a “live” collection of objects such that it dynamically reflects changes made to the DOM (see
A collection is not an array for more).
So what if you want your cake (an array) and you want to eat it to (a “live” collection of nodes)? That’s where MooTools comes to the rescue with the double dollar function:
$$(). In short, the double dollar function is meant for collections of elements, and packs them into a live array. (If it helps keep
$() and
$$() straight, think single dollar equals single element, double dollar equals multiple elements.)
So how do we use the
$$() method? We pass it unlimited CSS selectors:
// get all elements with class name
// “some-class” on the page
$$('.some-class');
// get all elements with any of these three classes (note
// the commas)
$$('.some-class, .some-other-class, .some-other-class-still');
// inside all divs with “div-class” get all paragraphs with
// “p-class” (note the space)
$$('div.div-class p.p-class');
In the last example above, we use a tag and space to drill down; we can also drill down into a single object.
Let’s say we have a wrapper element that has the CSS id “myWrapper.” We want to use this myWrapper element as our starting point to drill down and gather all the elements inside of it that have the class name “remove.”
With MooTools, there are two ways we can do this:
// no CSS selector used with $(), but CSS "." selector for its
// getElements() method
var removeElms = $('myWrapper').getElements('.remove');
// CSS selectors for both id "#" and class "." (note the
// space)
var removeElms = $$('#myWrapper .remove');
MooTools not only enables us to grab elements using CSS selectors (tag names, class dots, or id pounds along with spaces and commas for the most common ones), it also enables us to set styles on an element.
In native JavaScript, if we wanted to set the style
font-weight for an element, we would use:
// native Javascript: fontWeight
myDivElement.style.fontWeight = 'bold';
Notice that while CSS uses hyphens, JavaScript requires that hyphens be translated into camelCase; thus
font-weight equals
fontWeight and won’t work without this translation.
With MooTools, however, we can set an element style easily without having to do any translations:
// MooTools: standard font-weight
myDivElement.setStyle('font-weight', 'bold');
Let’s go back to MooTools’ element arrays, such as the
removeElms collection demonstrated a few examples ago. For most tasks, rather than writing a somewhat cumbersome
for loop, we can iterate over an array objects using
each().
MooTools will automatically return to us both the current element being iterated as well as the index of that particular iteration as the first and second parameters (respectively):
removeElms.each(function(myElm, index) {
if (index === 0) {
// if it's the first remove, hide it
myElm.hide();
} else {
// else draw a red border around it
myElm.setStyle('border', 'solid 1px red');
}
});
Let’s take a quick look at what’s going on here:
Element.each() actually takes just one parameter, and that parameter happens to be a function:
function(myElm, index) {
// blah blah
}
Because we pass it a function, that is why it has the somewhat strange looking syntax ending in
});: the curly bracket is the end of the function and the parenthesis the end of
each().
There are a number of methods that MooTools provides just for working with the
Element object alone, but this guide should give you the very basics of how HTML, CSS, and JavaScript—particularly through the MooTools framework—allows us to interact with elements. For complete documentation on the MooTools
Element methods, see the
MooTools Docs.
We now know a bit about the DOM and how JavaScript can interact with it; perhaps the single most important thing we do with the DOM is collect user-entered data. The database would not have any records in it if it were not for this ability, and it is helpful to understand how form elements map to the database.
While JavaScript or Python either one can rename keys, in a perfect world, the relationship between the DOM and the database would be one-to-one:
<form id="my-form">
Password: <input type="password" name="pass" /><br />
Name: <input type="text" name="name" /><br />
Age: <input type="text" name="age" /><br />
<input type="submit" />
</form>
Above are four input elements in a
<form> element that look like so on an unstyled HTML page:
toObject()
In the IWS
sharedLibrary.js, there are two very useful methods. One is called
fromJSON() and the other is called
toObject() (toObject is actually a wrapper for toJSON).
All we really need is a wrapper element of any sort surrounding a collection of inner inputs to make it work; we often do not even use the traditional
<form> element in BriteCore for this purpose. If we use
toObject(), it will provide us with what the Python recognizes as its
js_dict parameter:
var submitObj = $('my-form').toObject();
The output of the above might be something like this (assuming the values to these object/dictionary keys are the ones the user actually typed in our form):
{
'pass': 'a9(_}c8d7bic',
'name': 'Cindy Sutterfield',
'age': '19'
}
You can see the one-to-one correspondence between the screen elements and what ultimately becomes the
js_dict that gets packed into the database.
fromJSON()
Likewise, when the JavaScript makes an Ajax request, Python assembles the dictionary to return, and
json.dumps() (stringfies) it back to the front, where MooTools automatically converts this JSON string into a real JavaScript object. Using the same form element (
$('my-form')) or other wrapper, we can pre-populate the values using
fromJSON():
var submitObj = {
'foo': 'bar'
};
var myRequest = new Request.JSON({
url: '/urls.py?file=foo&method=bar',
onSuccess: function(responseJSON) {
// the JSON response populates the inputs
// inside the form wrapper
$('my-form').fromJSON(responseJSON);
}
}).send(JSON.encode(submitObj));
The result?
JSON.decode() equals json.loads(); JSON.encode() equals json.dumps()
The last thing to notice is the
send() part of the request: we use the MooTools’ method
JSON.encode() to stringify the JSON. An equivalent IWS translation to the JSON methods for stringifying/de-stringifying includes:
// Javascript
JSON.decode(); // turn JSON string into JavaScript object
JSON.encode(); // stringify JavaScript object into JSON
// Python
import json
json.loads() // turn JSON string into Python dict
json.dumps() // stringify Python dict into JSON
A Magical Event
Last but not least, let’s take a look at JavaScript
events, the magical ability JavaScript has of responding to mouse clicks, keyboard presses, or screen touches.
An
event is quite literally an elaborate
object (
dictionary
) that the browser fires off when a user interaction takes place; it contains a variety of information including the element that the event took place on as well as its x/y coordinates.
As long as JavaScript has a listener in place on an appropriate element for the appropriate type of event, it can respond to the firing of these events and act accordingly.
At IWS, we typically use an approach to events called “event delegation.” Rather than adding, say, 25 listeners to 25 different elements on the page, we instead use one listener on a wrapper element and “delegate” the event using a series of if/else if statements. We can do that because most events “bubble.”
There are a wide range of DOM events (
see here for a complete list), and they all begin with the word “on.” MooTools, however, always leaves off the “on" part, so when you pass the string name of the event to
removeEvents and
addEvent (demonstrated below), remember to leave it off!
But what is event bubbling?
When we looked at HTML earlier, we saw that elements were nested inside elements that were nested inside other elements.
An element that wraps other elements is a “parent” element and any elements that are nested inside of it are its “children.”
When a user clicks on one of these “child” elements, the event first registers on the child, then on that child’s parent, then on that parent’s parent, and so on until it bubbles all the way up to the top of the document.
If we attach an event listener to one of the parents, we can “listen” as its children bubble up, testing to see if a particular child we are interested in has bubbled to the top. If so, we take some course of action: a child with the CSS class “remove,” for example, might be an indication that we need to pop up a user dialog that a record is about to be deleted, requiring the user to confirm that this action was done on purpose.
// MooTools
// MooTools passes us back the event, here as evt
myParent.removeEvents('click').addEvent('click', function(evt) {
var self = this;
// MooTools offers us the element that was clicked via
// “target.” We wrap it in the dollar function for IE
var target = $(evt.target);
// to make sure that anything else we do to the
// element using MooTools’ methods will work on IE
// we now run a series of tests on the element that
// may involve tests for its tag, id, a class it
// contains, or any other property we know is
// true of a given child element we’re interested in
if (target.id == 'save') {
self.save();
} else if (target.hasClass('next')) {
self.goToNextScreen();
} else if (target.hasClass('remove')) {
// perform logic that pops up user confirmation--
// if user OKs, fire off an Ajax call to remove a
// record from the db
}
});
Normally we further divide the logic up with an
attachEvents method in a class that in turn calls a
delegateClick method that contains the if / else if logic inside it, but the idea remains the same: attach one event listener to a parent element and listen for the individual children we’re interested in. Hence, “event delegation.”
Additionally, note that we use
removeEvents before using
addEvent: this keeps an event from doubling up.
If more than one event listener of a given type gets attached to the same element, each one will fire, leading to unpredictable behavior. The user clicks once, but because three instances of a “click” event listener were bound to that element, it is as though the user clicked three times in a row rather than just once.
For more on the
Event object in MooTools, see
Type: Event in the docs.
To attach an event using only native JavaScript, we could use the following. (I do not know how to remove an event this way, but—so far as I know at least—stacking shouldn’t occur, as we’re making an assignment that gets overwritten each time.)
// native Javascript
myParent.onclick = function(evnt) {
// MooTools takes the following pain out of the equation:
// if IE, the event will be bound on the global “window”
// object rather than passed via “evnt”
var evt = (evnt) ? evnt : window.event;
// if IE, the target will be “srcElement”; if anybody
// else, it will be “target”
var target = (evt.srcElement) ? evt.srcElement : evt.target;
if (target.id == 'save') {
self.save();
}
// blah blah
};
Not All DOM Events Bubble!
Not all DOM events bubble. As per the DOM specification, the events
onfocus and
onblur (when the focus leaves an element) ''should not'' bubble, whereas
onchange,
onsubmit and
onreset ''should'' bubble. You can also listen for
onkeyup,
onkeydown or
onkeypress as these events bubble up the DOM on all browsers.
Not All Browsers Bubble Equally!
Now for a really big “gotcha”: remember earlier how we said that not all browser vendors were created equal? Guess what? Just to make life difficult for you,
onchange, onsubmit, and onreset do not bubble up in IE even though they are supposed to! This is true of IE8 and below—I’m not sure what the official status is on IE9 and above.
To complicate matters further, to my knowledge, there is no work-around in MooTools, so if you are listening for these events and you want to support IE, you need to remember
NOT to use event delegation but instead attach an event to each element you want to listen for.
// Ugly $$() hack we use inside RecordSet.Table.LineItemTable.attachEvents() in builder.js
if (Browser.ie) { // change events don't bubble in IE!
$$('.linItm_manualCheckbox, .linItm_deductible, .rISWrap_options, .contentRow_amountInput, .lossExposureGroup, .rIIWrap_input, .singleLineAnswer, .multiLineAnswer, .listAnswer, .ifAddressQuestion_addressLine1, .ifAddressQuestion_addressLine2, input[name=city], select[name=state], input[name=zip], .scheduledItemInput, .contentRow_amountInput, .pb_classification').removeEvents('change').addEvent('change', function(evt) {
if (self.localVars.hasFired !== 1) { // hack to keep IE from firing change event twice in a row
self.localVars.hasFired = 1;
self.delegateChange(evt);
}
});
} else {
$(self.options.tableElements.body).removeEvents('change').addEvent('change', function(evt) {
self.delegateChange(evt);
});
}