Archive
JS 101 Week 5: Event handling
2011-02-23
Reflection
Why is it better to use either trickling or bubbling of events? Why not just give the event to the node on which it was invoked?
There are two main reasons to use the trickling down or bubbling up of events. The first is performance. If we have 100 different images on a page and we want each one to respond to the hover
event, it is very inefficient to create 100 different event listeners and attach them to each element. We can instead create a single hover event listener on the <div>
which contains all of these pictures, and from within that event handler, determine which of its enclosed <img>
elements was clicked. Additionally, when the event bubbles up the parent chain, the programmer is free to do interesting things like change the style of entire subtrees as a result of an event listener lower down in the hierarchy.
Can you think of one situation where you would want to prevent the browser from submitting a form after a user has clicked on the ‘submit’ button? How would you achieve this?
One example would be if we had some client side validation we wanted to take place before submitting a form to the server. For instance, we might want to validate that all the form fields are filled out, or that the values entered into these fields have a certain format (e.g. mm/dd/yyyy format for dates). When the form submit button is clicked and the validation routine takes place, if there is an error that function can call the preventDefault
method on the corresponding event object.
Homework
12.1 of Eloquent Javascript
Write a function asHTML which, when given a DOM node, produces a string representing the HTML text for that node and its children. You may ignore attributes, just show nodes as . The escapeHTML function from chapter 10 is available to properly escape the content of text nodes.
Hint: Recursion!
function isTextNode(node) { return node.nodeType == 3; } function asHTML(node) { // we’re done recursing if (node.childNodes.length === 0) { if (isTextNode(node)) { return node.nodeValue; // This is unavailable in the jsFiddle environment //return escapeHTML(node.nodeValue); } else { return "<" + node.nodeName + ">"; } } var returnString = "<" + node.nodeName + ">"; for (var i = 0; i < node.childNodes.length; i++) { returnString += asHTML(node.childNodes[i]) + "\n"; } return returnString; } alert(asHTML(document.body));
12.2 of Eloquent Javascript
Write the convenient function removeElement which removes the DOM node it is given as an argument from its parent node.
function removeElement(node) { node.parentNode.removeChild(node); }
13.1 of Eloquent Javascript
Write a function called registerEventHandler to wrap the incompatibilities of these two models. It takes three arguments: first a DOM node that the handler should be attached to, then the name of the event type, such as “click” or “keypress”, and finally the handler function.
To determine which method should be called, look for the methods themselves ― if the DOM node has a method called attachEvent, you may assume that this is the correct method. Note that this is much preferable to directly checking whether the browser is Internet Explorer. If a new browser arrives which uses Internet Explorer’s model, or Internet Explorer suddenly switches to the standard model, the code will still work. Both are rather unlikely, of course, but doing something in a smart way never hurts.
// eventname is the name of the event without the ‘on’ prefix. e.g. // to register for a click event, the eventname value should be "click" function registerEventHandler(node, eventname, handler) { // node has an attachEvent method; use that. This is how ie works if (node.attachEvent) { node.attachEvent("on" + eventname, handler); } // Mozilla model else if (node.addEventListener) { // false - use bubble up rather than trickle down node.addEventListener(eventname, handler, false); } else { node["on" + eventname] = handler; } }
JSFiddle example – when mouse enters element, it becomes bold. After it leaves, it becomes normal
Create an HTML page and some Javascript to allow a user to add n numbers.
First display a simple form with the question “How many numbers do you want to add (max is 10)”. The user should enter a number between 2 to 10 and click on a button in the form. You have to validate the answer. If the user has entered a correct value (between 2 and 10), then dynamically create a form with n text input fields and an “Add” button. Once the form is displayed the user will enter n numbers in the n input fields and when they click on the “Add” button, dynamically create a span element with the result. You will have to perform validation on the values entered in the input fields to make sure that they are numbers. If they are not numbers, display an alert dialogue with an error message.
JSFiddle solution
/*#p2pu-Jan2011-javascript101*/
JS 101 Week 4 – Intro to the DOM
JS 101 week 4 reflection and homework
Questions for reflection (please reflect on your blog and discuss in comments below):
There were two key innovations to the original (fetch-parse-flow-paint) linear workflow that the Mosaic browser used to render web pages. One allowed for a perception of faster rendering, and the other allowed for us to use AJAX. Explain both?
Early browsers had to wait for all the images on a page to be downloaded before they could complete their flow phase, as they needed to know how big the images were before they could lay out the page. The advance of Mosaic was to insert temporary placeholders for the images, layout the page, and display it to the user as soon as all the text was downloaded. As the images finished downloading asynchronously, the placeholder images were each swapped out, and the layout was recalculated and rerendered. While the overall time to finish completely rendering the page was higher (due to the increased number of flow/paint steps), the usability was much improved because people could begin reading the webpages almost immediately.
The second major change was that events could call scripts which in turn could cause the page to be rerendered (i.e. the flow algorithm would be re-run). This is the basis of dynamic HTML pages as we know them. See Blain Armstrong’s excellent blog post for more details.
What are the roles of ‘name’ and ‘id’ attributes in HTML tags? What is an appropriate use of both?
The video noted that ‘name’ and ‘id’ used to be synonymous, and in fact most of the browser implementations used them interchangeably. This has changed in recent years and now there are reasons to use one over the other.
The ‘id’ field must be the unique identifier for a given element, whereas the ‘name’ field is not necessarily unique. Name should be used to identify values in form data. The name you give a text field, for instance, determines the key=value pairing you’ll receive when the form is posted. For instance, if we have a text field whose name
is “firstName”, then when the form is posted, we’ll have firstName=blahblahblah
as one of the parameters.
Which pointers does each node in the DOM tree typically have?
Each node has a parent and an array of child nodes. The parent node is accessed via the parentNode
property, and the array of children can be accessed by childNodes
. Furthermore, there are explicit pointers to the first and last child, accessible via the firstChild
and lastChild
properties. Nodes also have pointers to their next sibling as well (via nextSibling
pointer).
Given a node object from a DOM tree, how do we determine if it is a text node or a regular node?
We can access the nodeName
property; if it’s “#text”, it’s a text node, else it’s a regular node. In code:
function isTextNode(node) { return node.nodeName == "#text"; }
Homework:
Download the source for the web page ‘http://www.useit.com/about/nographics.html’. In the source page itself, write a Javascript function which counts the number of text nodes in the document and shows the count in an alert dialogue. You can choose how you want to trigger the function (through a button click, link click, or any other event).
All of the following answers use the following basic code:
// Performs a depth first tree traversal rooted at the given rootNode. This // node and all of its child nodes will have visitorFunc called on it. function dfs(rootNode, visitorFunc) { var numChildren = rootNode.childNodes.length; visitorFunc(rootNode); for (var i = 0; i < numChildren; i++) { var childNode = rootNode.childNodes[i]; dfs(childNode, visitorFunc); } } var count = 0; function isTextNode(node) { return node.nodeName == "#text"; } function countTextNodes(node) { if (isTextNode(node)) { count+= 1; } } // After this call, count is set to the number of text nodes dfs(document, countTextNodes);
Through onload
With a button launching the count
Change the example above so that instead of displaying the count in an alert dialogue, it is displayed in a span tag in the HTML page itself.
Add a link besides the button, such that when the link is click, it changes the style on the span tag to make it’s contents bold.
/#p2pu-Jan2011-javascript101/
JS 101 – Week 3
In Javascript, functions are first class objects. What does it mean to be a first class object?
A function can be used anywhere an object can be used; in particular, you can pass functions as method arguments. (We did this in last week’s homework by passing the print
function into the iterateAndOperate
method). This is in contrast to a language like Java where functions are not first class objects, and the programmer must use interfaces and anonymous classes to get around this problem. For instance, in Java, to delay the invocation of a function, you might do the following:
SwingUtilities.invokeLater(new Runnable() { @Override public void run() { System.out.println("I was run!"); } })
whereas in Javascript, you could do something like
invokeLater(function() { print("I was run") });
assuming there was an invokeLater
function.
Functions and variables share the same namespace. What does this mean and what important implication does this have?
This means that you must take care in not defining variables in the global namespace that have the same name as a function. For instance,
// global variable x = 5; function x(args) { alert(args); } // The global variable is shadowing the function name; // the function is not called x(5);
Douglas Crockford equates Javascript functions with Lambdas, and also mentions that they are a secure construct. Can you do some research and reflect on what he means by ‘secure construct’?
A lambda basically means that you can pass a function as an argument to another function; this is due to the aforementioned first class nature of functions. They are a secure construct because the scope of the functions is such that private variables in function A cannot be accessed by function B when A is passed into B. In other words,
function outer(func) { // "undefined", since the ‘privateVariable’ // field is scoped only to the inner function. return func.privateVariable; } function inner(a,b,c) { var privateVariable = 25; return a + ", " + b + ", " + c; } alert(outer(inner("cat","dog","bear")));
f
Can you explain the concept of a closure.
A closure means that an inner function continues to have access to variables defined inside of an outer function, even after the outer function has finished. I wrote an example you can view on jsfiddle.
What is the difference between a function and a method?
A method is a function that is bound to an object, and thus it has an implicit “self” reference.
In Javascript there is no implicit type checking of arguments passed to functions. This could lead to bugs if programmers are not careful about what they pass to functions. If you create a function, how would you protect it from well meaning but careless programmers?
You can do defensive checks within the function. For instance, if a method is supposed to take in a number, you could do the following:
function numericalFunction(x) { if (typeof(x) != "number") { throw("Expected numerical value for numericalFunction; got " + x); } // Proceed as normal }
You can also use the arguments
implicit variable in the function to check that the number of arguments the user passed in is equal to the number of arguments that the method expects.
Javascript functions have implicit access to something called this
. this
points to different things depending on how the function was created. Can you explain why we need this
, and what it represents in different type of functions.
If a function is created globally, its this
pointer will be to the DOMObject. If it’s created and bound to an object, the this
pointer points to that object. The following illustrates this:
function f() { return "f's this: " + this; } function nested() { function inner() { return "inner's this:" + this; } return inner(); } // o is an object; add a method do it var o = {}; o.f = f; o.nested = nested; o.newFunc = function() { return "newFunc's this: " + this; }; o.nestedFunc = function() { var inner = function() { return "nestedFunc's inner this: " + this; } return inner(); } print(f()); print(nested()); print(o.f()); print(o.nested()); print(o.newFunc()); print(o.nestedFunc()); // prints // f's this: [object DOMWindow] // inner's this:[object DOMWindow] // f's this: [object Object] // inner's this:[object DOMWindow] // newFunc's this: [object Object] // nestedFunc's inner this: [object DOMWindow] //
Note that only the non nested functions that have been bound to object o
point to that object as opposed to the DOMWindow.
The reason we want the this
pointer is to be able to introspect on the type and contents of the object that is calling the function. For instance, in my last assignment, I used the this
variable to produce a nice representation of an object:
nick = {name:"Nick",age:23}; // This function returns a string representation of whatever // is invoking it function selfStringRep() { var str = ""; for (var v in this) { // This is not an inherited property if (this.hasOwnProperty(v)) { str += "’" + v + "’: " + this[v] +"\n"; } } return str; } Object.prototype.toString = selfStringRep; print(nick.toString()); // prints //’name’: Nick //’age’: 23
4.1
The solution for the cat problem talks about a ‘set’ of names. A set is a collection of values in which no value may occur more than once. If names are strings, can you think of a way to use an object to represent a set of names?
Show how a name can be added to this set, how one can be removed, and how you can check whether a name occurs in it.
var cat_set = {}; // Add a cat cat_set["Tigger"] = true; // remove a cat delete cat_set["Tigger"]; // Check if cat exists cat_set["Frog"] != undefined // Wrap it all up nicely: in an object with functions: var cat_set = {}; cat_set.add_cat = function(cat) { this[cat] = true; } cat_set.remove_cat = function(cat) { delete this[cat]; } cat_set.cat_exists = function(cat) { return this[cat] != undefined; } cat_set.add_cat("Frisky"); // prints "true" show(cat_set.cat_exists("Frisky")) cat_set.remove_cat("Frisky"); // "false" show(cat_set.cat_exists("Frisky"))
4.2
Write a function range that takes one argument, a positive number, and returns an array containing all numbers from 0 up to and including the given number.
An empty array can be created by simply typing []. Also remember that adding properties to an object, and thus also to an array, can be done by assigning them a value with the = operator. The length property is automatically updated when elements are added.
// Assumes n >= 0 function range(n) { var array = []; for (var i = 0; i <= n; i++) { array[i] = i; } return array; }
4.3
split
andjoin
are not precisely each other’s inverse.string.split(x).join(x)
always produces the original value, butarray.join(x).split(x)
does not. Can you give an example of an array where.join(" ").split(" ")
produces a different value?
[" ", " ", " "].join(" ").split(" "); // equals ["", "", "", "", "", ""]
4.4
Write a function called startsWith that takes two arguments, both strings. It returns true when the first argument starts with the characters in the second argument, and false otherwise.
function startsWith(string, pattern) { return string.slice(0,pattern.length) === pattern; }
4.5
Can you write a function
catNames
that takes a paragraph as an argument and returns an array of names?Strings have an
indexOf
method that can be used to find the (first) position of a character or sub-string within that string. Also, whenslice
is given only one argument, it will return the part of the string from the given position all the way to the end.It can be helpful to use the console to ‘explore’ functions. For example, type
"foo: bar".indexOf(":")
and see what you get.
// The sentence is in the form "Blah blah: cat1, cat2, …, catN." function catNames(paragraph) { var colonIndex = paragraph.indexOf(":"); // Skip the colon and the following space var catListString = paragraph.slice(colonIndex + 2); return catString.split(", "); }
/*#p2pu-Jan2011-javascript101*/