“Brief” Guide to JavaScript – Part 8
Finally, another part. If you haven’t already, check out the previous parts of this series:
Advanced Topics
Finally, I can stop pushing off things I want to talk about.
Here’s where I’ll talk about them.
Apply This Call (ordered for pun)
Now we’ll demystify the this variable. This is to represent the object that "owns" the function being called. This is easily shown by an event handler example:
// yes, the "bad" way, we'll talk why later. document.getElementById('mylink').onclick = function(){ alert(this.innerHTML); };
Here we get the innerHTML of the element. For events, this is target/srcElement. This is preferred for its simplicity. Using this, we can also do something like:
function myhandler(){ this.style.color = 'red'; } for(var el : document.getElementsByTagName('p')){ el.addEventListener('click', myhandler, false); }
This will make the text of any p element red when the user click on it. This saves us time from having to write functions for each element.
Of course there's a caveat, IE's attachEvent sets this to the window object, making the above code useless in IE (even if you converted addEventListener appropriately). Your wrapper for adding events can change this with the next two handy functions: apply and call.
Apply and call executes a function with a provided this object and set of parameters. The only difference between the two is that apply accepts an array as the parameter list instead of call, which you pass parameters in directly.
function customFn(param1){ alert('this = ' + this); aert('param1 = ' + param1); } // all three calls are identical customFn('hello'); // always implies this = window object customFn.call(window, 'hello'); customFn.apply(window, ['hello']);
This is handy when you need to assign a bind a different value to this for a function. In a way, this allows you to provide custom scoping to a particular function. Class-styled JavaScript frameworks use this to refer to the class (for methods).
Using the above myhandler function, we can pass a custom this, and the function wouldn't care that it wasn't called via an event:
myhandler.call(document.getElementById('my_element'));
Optional parameters
Undefined Parameters
This isn't quite new, but due to the nature of || and && (returning the truthy statement), we can use it as a rudimentary optional parameter feature:
function myfn(optional){ var param = optional || 'my default'; // use param (hopefully more useful than this) return param; } // examples myfn(); // optional variable is undefined; returns 'mydefault' myfn('hello'); // optional variable is 'hello'; returns 'hello' myfn(false); // optional variable is false; returns 'mydefault'
The reason why this works is because any parameters not filled are automatically set to undefined, which is falsy to ||. The caveat about this method if we expect a boolean value. A false value will fill a default, so we have to check for undefined explicitly. This is only necessary if we want to accept boolean or integer values.
function myfn(optional){ var param; if(optional === undefined){ param = 'default'; } else { param = optional; } // commented line below is same as above code; // var param = (optional === undefined) ? 'default' : optional; return param; } // examples myfn(); // returns 'default' myfn('hello'); // returns 'hello' myfn(false); // returns false
Variable Parameter Length
Sometimes we want to have a variable number of parameters, we can build a rudimentary clone of sprintf, or python's % operator on strings (changed to format in python 3). We'll only accept %s to represent a variable to replace with and a variable number of parameters which represents the values to replace it with.
To do this, we can use the special arguments array variable provided to functions which provides meta information about the specific function call. Each element in the array represents a parameter.
function sprintf(){ var str = arguments[0]; // our string for(var i=1; i<arguments.length; i++){ // replaces first occurrence of %s to the given parameter str = str.replace('%s', arguments[i]); } return str; } // usage: sprintf("hello %s", "john"); // returns "hello john" // returns "the sum of 1 and 2 is 3" sprintf("the sum of %s and %s is %s", 1, 2, 1+2);
Besides parameters, we've used arguments.callee to refer to the current function being executed, which is handy for anonymous function recursion.
// recursive anonymous function that displays a predefined countdown using alerts. var num = 3; (function(){ // count down alert(num); num—; if(num < 1){ return; } setTimeout(arguments.callee, 0); })();
Object-based Optional Parameters
Both optional parameter methods we listed so far have their drawbacks if you have multiple optional parameters. Say we have this theoretical function:
function createRobot(unique_id, name, greeting_message, goodbye_message){ // unique_id is required, rest are optional parameters var name = name || 'unknown robot'; var greeting_message = greeting_message || 'hello, human!'; var goodbye_message = goodbye_message || 'destroy all humans!'; }
It's nice, but what if we wanted to only have a custom goodbye message? We would have fill undefined for the other parameters. If this function changes (say, another optional parameter is inserted between name and greeting_message), we have to change all our calls too. A better way would be to accept an optional object at the end.
function createRobot(unique_id, options){ var name = options.name; // options.name and options['name'] are identical var greeting_message = options.greeting_message; var goodbye_message = options.goodbye_message; }
The main problem of the implementation above is handling if options parameter didn’t have a specific key we're looking for. So we need to internally hold an object with default values. Then we iterate over the passed options object and replace any default values with the new one like so:
function createRobot(unique_id, options){ // default values var opt = { name: 'unknown robot', greeting_message: 'hello, human!', goodbye_message: 'destroy all humans!' }; // set options to {} if no value was provided options = options || {}; for (var key in options){ opt[key] = options[key]; } var name = opt.name; var greeting_message = opt.greeting_message; var goodbye_message = opt.goodbye_message; // do robot stuff. } // examples: createRobot('ufo-117'); createRobot('healer-93', {goodbye_message: 'Have a good day!'}); createRobot('sn-131', {name: 'skynet', greeting_message: 'Useless human!'});
As you can see, this provides us flexibility for how many and which specific optional parameters we want to fill. Most javascript frameworks extract this functionality out for simplier use, we can do the same:
function defaultOptions(defaulted, provided){ defaulted = defaulted || {}; provided = provided || {}; for (var key in provided){ defaulted[key] = provided[key]; } return defaulted; } // usage function createRobot(id, options){ var opt = { name: 'unknown robot', greeting_message: 'hello, human!', goodbye_message: 'destroy all humans!' }; opt = defaultOptions(opt, options); // we can access the options in the opt object. }
Our implementation doesn't handle deep copying to ensure the returned object isn't inadvertently changed, which most JS frameworks handle, but for this use case, it's not a deal-breaker. In JavaScript, this is the preferred way to handle multiple optional parameters.
And that wraps up this week. Next will be Try/Catch, DOM Ready, and Namespaces.
“Brief” Guide to JavaScript – Part 7
If you haven’t already, check out the previous parts of this series:
Utility Functions
Various functions that are useful when you start doing more than simple JavaScript/DOM manipulation code.
Typeof
Sometimes you'll want to determine what type of data a variable contains, you can use the typeof keyword which returns a string of the type (parenthesizes are optional, but recommended):
typeof('hello world') // => 'string' typeof 123 // => 'number' typeof(true) // => 'boolean' typeof(function(){}) // => 'function'
It's worth mentioning a quirk in typeof with arrays and objects:
typeof([1 2 3]) // => 'object' typeof({hello: 'world'}) // => 'object'
We'll talk about why when we cover objects in depth, but arrays are actually objects.
Math
As the name suggests, the Math object contains mathematical functions and constants. For convenience, I've copied over the documentation listed at W3Schools. I wish there is more to say about these functions but they're mostly self-explanatory. They come in handy every now and then.
Properties:
- Math.E - Euler's Constant (~2.718)
- Math.LN2 - Value of the natural log of 2 (~0.693)
- Math.LN10 - Value of the natural log of 10 (~2.302)
- Math.LOG2E - Value of the base-2 log of E (~1.442)
- Math.LOG10E - Value of the base-2 log of E (~0.434)
- Math.PI - Circular Constant 22/7 (~3.141)
- Math.SQRT1_2 - Value of square root of 1/2 (~0.707)
- Math.SQRT2 - Value of square root of 2 (~1.414)
Methods:
- Math.abs(x) - Absolute value of x. Converts negative numbers to positive equivalents.
- Math.acos(x) - ArcCosine (cos-1) of x.
- Math.atan(x) - ArcTangent (tan-1) of x. Value returned is between -PI/2 and PI/2 radians.
- Math.atan2(y, x) - Returns angle theta of an (x, y) as a numeric value between -PI and PI radians.
- Math.ceil(x) - Returns the value of x rounded upwards to the nearest integer.
- Math.cos(x) - Returns Cosine of x.
- Math.exp(x) - Returns Ex.
- Math.floor(x) - Returns the value of x rounded downwards to the nearest integer.
- Math.log(x) - Returns the natural logarithm (base E) of x.
- Math.max(x, y) - Returns the highest value of x and y.
- Math.min(x, y) - Returns the lowest value of x and y.
- Math.pow(x, y) - Returns xy.
- Math.random() - Returns a random decimal between 0 and 1.
- Math.round(x) - Rounds x to the nearest integer.
- Math.sin(x) - Returns Sine of x.
- Math.sqrt(x) - Returns square root of x.
- Math.tan(x) - Returns tangent of an angle x.
Strings
As with everything else in JavaScript strings are objects. There are some functions you may find useful when operating with Strings.
- str.anchor(anchorid) - A shortcut for creating an a-anchor tag. "My Title".anchor('mytitle') produces <a name="mytitle">My Title</a>.
- str.link(url) - A shortcut for creating an a-href tag. "Google".link("http://google.com") produces <a href="http://google.com">Google</a>.
- str.charAt(index) - Gets the character at the specific 0-base index location. Identical to doing str[index].
- str.toUpperCase() - Returns an uppercase variant of the string. "aBc".toUpperCase() becomes ABC.
- str.toLowerCase() - Returns a lowercase variant of the string. "aBc".toLowerCase() become abc.
- str.indexOf(searchStr[, startIndex]) - Returns the index where searchStr appears in str or -1 if not found.
- str.replace(searchStr, replaceStr) - Returns a new string of str that replaces the matches of the regular expression of searchStr with replaceStr. You must use regular expressions to replace multiple occurrences of a string. "abccba".replace('a', 'b') produces "bbcba" while "abccba".replace(/a/g, b) produces "bbcbb".
- str.split(sepStr[, max]) - Returns an array of strings split by sepStr. If an empty string it used, an array of every character of the string is returned. If max is provided, the remaining string will the maximum times the string will be split. "may,june,july".split(",") produces ["may", "june", 'july"].
- str.match(rexp) - Returns an array of matches found via the regular expression, or null if no matches.
You can view more methods at W3Schools.
Timeouts & Intervals
Timers provide an easy way to execute a function at a specified delay. This is done using setTimeout, clearTimeout, setInterval, and clearInterval. setTimeout accepts the function to call and the delay time in milliseconds and returns a timer object which can be passed to clearTimeout to prevent the function from being called. Intervals are like timeouts except that intervals persist, calling the given function every time after a given delay (where as timeouts die after the function is called).
Besides having delays, timeouts and intervals are vital for asynchronous activities as this is the only method, in JavaScript, to do async operations--including animations.
For example, we can fade an element like so:
// quick way to set the property (for IE differences)function opacity(element, value){ // CSS method of opacity. element.style.opacity = value; // 0-1 if(element.filters){ // IE method of opacity element.filters.alpha.opacity = value * 100; // 0-100 } }// animate our opacity effect. function fade(el, start, end, time){ var curr = start, interv = 13, t = 0; // how much we should change per interval var amt = (end - start) / (time / speed); opacity(el, curr); // initial opacity var timer = setInterval(function(){ curr += amt; // new opacity value t += interv; // increase time tracker opacity(el, curr); // change opacity // stop animation when we reached out target opacity if(t >= time) clearInterval(timer); // according to john resig, 13 is the min time most browsers will use. // Lower number is "smoother" }, interv); } // usage: fade(document.getElementById('myel'), 0, 1, 1000);// fades in element in 1 second
Animations in JavaScript frameworks are done similar to this method.
Regular Expressions
Another useful feature for any language are regular expressions. Like the DOM tree, regular expressions could have its very own guide. But, unlike the DOM tree, regular expressions are pretty common is higher-level languages, so I'll do a brief overview. After all, this isn't a guide solely for regular expressions.
What are Regular Expressions?
Regular expressions is a way of quickly parsing through a string to find matches, replacements, or just gathering data. For someone who never seen regular expressions, they may appear cryptic. A good reference for getting started with regular expressions is here. Otherwise, prepare for the intense crash course.
Like regular strings, you can match a specific set of letters.
abcdef
Which would only match the string 'abcdef'. By default, regular expressions are case-sensitive, so 'aBcdef' would not be accepted by the expression.
Maybe we want to match a letter from the alphabet, we can do this:
[a-zA-Z]
The square brackets indicates the character must match one of the characters listed inside it. a-z is shorthand for listing all the alphabetical characters where A-Z includes all captial letters.
If we want to match at least one of a particular character, we can use the plus sign.
a+
Which accepts any number of a's (other than zero): 'aaa', 'a', and 'aaaaaaaaaa' all would be accepted.
Star is identical, except it also matches zero of that character:
a*
Accepts everything a+ accepts and no characters (empty string).
Combining what we know, this regular expression is to match only alphabetical characters, which can be done as follows:
[a-zA-Z]+
The + sign indicates that the expression must match one or more of the previous sub-expression (the square brackets, which indicates any alphabetical character).
Having alphanumeric isn't much different.
[a-zA-Z0-9]+
As you can guess, 0-9 represents numbers from 0 to 9.
Shorthand for [0-9], we can use \d to match single digit numbers. So
\d+
Matches any number.
If you just want to match any character known to man, use the dot.
.ell.
This would match 'hello' and 'mello'. But not 'ellot' (no character infront).
Curly braces are used to indicate a more specific value than + or * would provide.
a{3}
Matches only 3 a's. Likewise:
a{3,}
Matches 3 or more a's.
a{,3}
Matches 3 or less a's.
a{3,9}
Matches 3 to 9 a's.
A question mark after a character match indicates an optional match:
abcd?
Matches 'abc' and 'abcd'. If the question mark is used after a + or *, a minimum number of matches is used. This is used to minimize the match range:
a.*c
Would match 'abcabcabc' in the string 'abcabcabc'. Where as:
a.*?c
Would match 'abc' in the string 'abcabcabc'.
Parenthesises are used to group a set of characters as one. Not too significant unless you use other operators
(abc)+
Matches 'abc', 'abcabc', 'abcabcabc', etc.
abc(def)?
Matches 'abc' and 'abcdef'.
So far, most the expressions I've given can match any string that has a substring that matches the expression. So:
[a-z]+
Would accept 'my name is bob', provided matches for 'my', 'name', 'is', 'bob'.
We can provide restrictions than an expression must be at the start of a string or end of a string using ^ and $.
^[a-z]+$
Matches only if the entire string is alphabetical letters. 'mynameisbob' would work, not, 'my name is bob'.
If you want to escape any regular expression special character, use a backslash:
\[hello\]
matches '[hello]'.
That's as much as I'll cover. There are plenty of general-purpose resources on regular expressions you can find via Google.
Now on to the JavaScript specifics of regular expressions.
Constructors
Creating a new regular expression object can be done in two ways. The easiest is using slashes which surround the expression:
var re = /[a-zA-Z]+/;
Alternatively, the new keyword can be used:
var re = new RegExp('[a-zA-Z]+');
The advantage of the latter is being able to use variables, since the constructor accepts a string as the regular expression. In addition, both accept methods can accept additional regular expression flags. The three most common flags are:
- g - performs a global match. This is used with replace() to replace all occurrences of the expression in a string instead of just the first one.
- i - performs a case-insensitive match as opposed to the default case-sensitive match.
- m - performs a multiline match. This makes ^ and $ match the start and end of a line respectively.
Flags are added simply by appending them together:
var rexp1 = /[a-z]+/gi; var rexp2 = new RegExp('[a-z]*', 'gim');
Using Regular Expressions
The most common uses for regular expressions are used in conjunction with string methods: search, match, replace, and split. Such as replace:
"normal stuff /* comment */".replace(/\/\*.*?\*\//g, '');
This removes your typical multiline comments from strings.
Regular expressions are most commonly used for form validation. An example to check if the user entered a valid email address:
function submitHandler(evt){ // we're assuming #email_input is the text input where the user entered the email var email = document.getElementById('email_input').value; if(!email.match(/^[a-zA-Z0-9+._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z]{2,4}(\.[a-zA-Z]{2,4})?$/)){ alert('Please enter a valid email address!'); if(evt && evt.preventDefault) evt.preventDefault(); else window.event.returnValue = false; } } // attach submitHandler to the form submit event.
We've covered most of the things here. We have a function that gets the value of the #email_input element (presumably an input field), check if it matches our regular expression, and stop the form from submitting if it doesn't.
Let's breakdown the regular expression monstrosity:
^[a-zA-Z0-9+._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z]{2,4}(\.[a-zA-Z]{2,4})?$
^ indicates that the expression has to match from the very beginning of the string.
[a-zA-Z0-9+._-] allows any alphanumeric, hypen, period, plus, or underscore character.
+ requires at least one character (that matches the above requirements) but can be more than that. In conjunction with [a-zA-Z0-9+._-], we match the username portion of the email address.
@ is simply the text we're looking for: the @ symbol of the email.
[a-zA-Z0-9._-] allows any alphanumeric, hypen, period or underscore character.
+ again, matches the previous expression at least one time.
\. escapes the normal regular expression behavior of the period. Here we just match a period character.
[a-zA-Z] We match an alphabetical character.
{2,4} two to four times. So 'a' would not work, but 'aa' would. This helps match a domain name extension for us (eg - .com, .net, etc.)
(\.[a-zA-Z]{2,4}) Here we do that again, because there are domains with two dots (eg - .co.uk) and we need to check for those too. In this case, we're creating it as a group because...
? We make the matching requirement optional. Allowing the traditional domain name extensions to still work.
$ Indicates we should be at the end of the string now.
Admittedly, some invalid emails will get through, but the point of validation is to help filter the invalid data from entering our system while not hindering possible valid entries.
Using Regular Expressions (from the Perl/Ruby Camps)
If you're used to a programming language like Perl and Ruby, which have more convenient ways of doing loops, use RegExp.exec(), which returns the text if a match is found and null if otherwise.
But the main advantage of exec is it automatically fills the special variables:
RegExp.$1 RegExp.$2
... all the way to ...
RegExp.$9
These special variables are filled with groups. Groups are either the entire regular expression matching or subexpressions in parenthesizes (), which allow us to "capture" text in certain locations in an expression.
As usual, examples always speak better than documentation:
var re = /^(http:\/\/)?(([^ .]+)\.)?([^ .]+)\.([^ .]{2,4})(\/[^ ]*)?$/; var input = prompt('Enter some URL:'); if(input){ if(re.exec(input)){ alert('Subdomain: '+RegExp.$3 + '\n' + 'Domain: ' + RegExp.$4 + '.' + RegExp.$5); } else { alert('Invalid URL'); } } else { alert('No Input'); } //
There are also some other new things. First the regular expressions:
[^ .]
The carrot at the beginning makes the matching exclusive. So this would match any character that isn't a space or period.
prompt(message[, default])
Prompt displays a browser-provided dialog input with your message and a space for the user to enter input. An optional default parameter can be filled to provide default input. I don't usually recommend using this function in production for usability issues. The function returns the text the user entered.
We then call our exec function on the given input string to parse. If there was no match, we execute the else statement.
After calling exec, The special RegExp variables are filled. To figure out which grouping you want, count the number of parenthesizes.
$1 is for the http:// or blank if not existant
$2 is the subdomain, including the period at the end
$3 is the subdomain without the period
$4 is the domain name
$5 is the domain name extension
$6 is everything after the domain name part of the URL.
Remember that this numbering only goes up to $9, so try not to have too many groupings in your expression.
As anything, experimentation will solidify your understanding more than me yapping away.
That wraps it up. Next, we’ll talk about some more advanced topics that are lesser used if programming isn’t your normal cup-of-tea. Read part 8.
“Brief” Guide to JavaScript – Part 6
Events & Handling
In conjunction with the DOM tree, events are an important aspect of the browser-provided functionality. Think of events as a "reaction" to a particular situation. For example, when the user clicks, you may want to do something. Events provide this capability.
Some events browsers provide:
- load: When the page is finished loaded. Applies only to the window object.
- unload: When the user is attempting to leave the page. Applies only to the window object.
- click: When the object is clicked on by the user.
- dblclick: When the object receives a double click by the user.
- keyup: When the user releases a key. Applies only to window, body, input and textarea.
- keydown: When the user presses down on a key. Applies only to window, body, input and textarea.
- mouseup: When the user releases a mouse button down on the object.
- mousedown: When the user presses a mouse button on the object.
- change: When the input data changed (after the user no longer has the element focused). Applies only to input and textareas.
- focus: When the user provides input focus. Applies only to input, select, and textareas.
- blur: When the user leaves the input focus. Applies only to input, select and textareas.
- resize: When the user resizes the browser window that contains your page. Applies only to window object.
- submit: When the user submits a form. Applies only to form, not submit button.
The load event is useful to access elements not available to your script (html code after your script tag), but there's a flaw which we'll cover in a bit. Normally you would use load if you script is in the head of your document.
Attaching Code to Events
The simplest way to add your own functionality when an event occurs is to set the property with on<eventname>:
// window is prefixed for clarity--we're attaching an event // to something versus just setting some random variable. window.onload = function(){ alert('page loaded!'); };
So when the page finishes loading, an alert is displayed. Quite similar to those attributes you've seen in html documents with on<eventname>="<javascript>". Like <a href="http://google.com" onclick="alert('you\'re leaving my site!')">Google</a>, although embedding JavaScript isn't part of HTML’s philosophy—to represent only data.
The methods above are not ideal. Mostly because only one function can execute per assignment (every element and its events).
window.onload = function(){ alert('I never get executed'); }; window.onload = function(){ alert('hello world!'); };
Now if you only intend to have one function call for a particular element's event, that maybe fine for you, but as you write more JavaScript, or write your own framework, this isn't ideal. The solution is to attach functions to events with browser-provided functions. There's the w3c-compliant way, and the IE way.
- element.addEventListener(type, function, capturing) [w3c-compliant]: Calls function whenever the given event type (as a string) occurs. Capturing should always be false to utilize event bubbling (which is the only kind of event propagation IE supports.)
- element.attachEvent(type, function) [IE-specific]: Calls function whenever the passed event type (as a string) occurs. Type should be prefixed with 'on'. Only supports event bubbling.
Capturing & Bubbling
There are two kinds of event propagation styles since parent elements also receive the event. This is the preferred method most of the time, especially since IE doesn't support event capturing. But for those curious, I'll briefly explain the difference. The first is event capturing:
Here we're assuming that we caused an event to occur on Element 2 (eg - we clicked on Element 2). The event handlers would fire be called in Element 1, then Element 2.
Then there's event bubbling:
Here the event handlers are fired in Element 2 before Element 1. To reiterate again, IE only supports this event propagation technique.
Event Object
The functions you use for event has a special event object it can access. As usual, there's the "all non-IE browsers" method and the IE method to get to this event object.
The normal method is the event object is passed to the function as the first parameter, while IE stores the event object in the global variable. Unfortunately, IE's event object is a bit different from other browsers.
For most browsers, the functions defined for events are as follows:
function clickHandler(evt){ // 'this' is a special variable that contains the HTML element that fired it. // This only works when using addEventListener or attachEvent. // We'll talk about 'this' in a future advanced topic. alert(this.innerHTML); // alternatively, you can do this: alert(evt.target.innerHTML); }
Where as IE would be something like:
function clickHandler(evt){ // contains the targeted element, we want to default to document // in-case IE wasn't called by through an element. var target = evt.srcElement || document; alert(target.innerHTML); }
You may have noticed we utilizes the OR expression oddity to our advantage. This ensures we are acting on an element (srcElement or document if srcElement does not exist).
The event object also provides some addition information, such as which keys were pressed and mouse position, but may not be available for all events:
- evt.keyCode, evt.which (older browsers): Provides the keyCode which the key was pressed.
- evt.pageX, evt.pageY (non-IE) and evt.clientX, evt.clientY (IE): mouse x & y positions of the mouse.
There are more, but those are the most common.
Browser Specificity
As you can see, we need to write code specific to various browser differences. We can easily do this by checking if the given function is defined. Any browser that doesn't support that function will simply have undefined (since all functions we want to access are usually through objects), so we can use a simple if statement block:
var elem = document.getElementById('mylink'); function clickHandler(){ alert("You've clicked me"); } if(elem.addEventListener){ // check for the standard event handler elem.addEventListener('click', clickHandler, false); } else { // do the IE method elem.attachEvent('onclick', clickHandler, false); }
Usually you'll want to abstract this into a function:
function addEvent(elem, type, func){ if(elem.addEventListener) elem.addEventListener(type, func, false); else elem.attachEvent('on'+type, func); } // example: addEvent(document.getElementById('mylink'), 'click', function(){ alert('hello');});
Of course we can do that for anything else like the different properties of events, but you get the idea.
Note: Using the "feature-detection" is recommended instead of detecting the browser to determine what to do.
Stopping Event Propagation
The browser has default behaviors for certain elements, such as going to new url when a user clicks on a link, or a submit input button passing form information to the given action page. Sometimes we want to stop that from happening.
One way is to stop event propagation.
As per usual, this varies by browser *cough* IE *cough*. For most browsers, you call the stopPropagation method for the event object:
function myEventHandler(evt){
evt.stopPropagation();
}
For IE, set the cancelBubble property of the event object to true.
function myEventHandler(){ window.event.cancelBubble = true; }
If we attached this function as a click handler to a link, the user would no longer be transferred to a new url when clicked on. Pretty handy for having links that do web app functionality or stopping a form from submitting when there are errors (eg - Empty required field).
If you want event propagation to still occur, but stop the browser default, you can utilize preventDefault (and returnValue property for IE):
function myEventHandler(evt){ var evt = evt || window.event || document; if(evt.preventDefault) evt.preventDefault(); evt.returnValue = false; }
Removing Attached Events
Another benefit of using addEventListener and attachEvent is the possibility of removing previously attached functionality to events. This is utilized by removeEventListener and detachEvent which accept the same kind of parameters as their attaching counterparts.
The only difference is that the function passed should be the exact same function that was used to attached it.
// for simplicity, this code only works for w3c-compliant browsers: function stopOnce(evt){ evt.stopPropagation(); // can't do whatever evt.target.removeEventListener('click', stopOnce, false); } document.getElementById('mylink').addEventListener('click', stopOnce, false);
For the given ID-ed link, the user will have to click twice to get to the href location the link refers to. A nice way to annoy your users.
Alternatively, if you like anonymous functions, you can use the special arguments variable:
document.getElementById('mylink').addEventListener('click', function(evt){ evt.stopPropagation(); evt.target.removeEventListener('click', arguments.callee, false); }, false);
I hate sounding like a broken record, but arguments is an advanced topic we'll cover.
Next time, we’ll cover some utility functions and regular expressions. Read part 7.