JavaScript
From WhyNotWiki
This is almost exclusively about using JavaScript for web sites. I suppose I should probably separate out the more general stuff about JavaScript (the language, syntax, etc.)...
[edit] The language
[edit]
http://www.howtocreate.co.uk/tutorials/javascript/variables JavaScript tutorial - Variables
http://javascript.crockford.com/remedial.html Remedial JavaScript
[edit] Problem: typeof() often just says "object" and doesn't tell you what kind of object...
Most of the "solutions" I've found are only able to identify built-in objects, like Array and String.
Planet PDF - Testing for Object Types in JavaScript (http://www.planetpdf.com/developer/article.asp?ContentID=testing_for_object_types_in_ja).
What I came up with relies on the fact that every object in JavaScript has a constructor function, which you can access explicitly by means of the constructor property. As an experiment, I suggest you try:app.alert( (new Array).constructor );[...] You'll get a dialog that says
function Array() { [native code] }The trick, then (are you thinking what I'm thinking?), perhaps, is to cast the object's constructor to a String and test the result for the presence of the substring 'Array' (if we're testing for arrayness). In other words:
// Return a boolean value telling whether // the first argument is an Array object. function isArray() { if (typeof arguments[0] == 'object') { var criterion = arguments[0].constructor.toString().match(/array/i); return (criterion != null); } return false; }
This is what I'm talking about: magnetiq.com » Blog Archive » Finding Out Class Names of JavaScript Objects (http://magnetiq.com/2006/07/10/finding-out-class-names-of-javascript-objects/).
The constructor property of a JavaScript object (except for intrinsic objects such as window and document) points to the object’s constructor, which has the same name as the class of the object. The name of the constructor can be obtained by parsing out the function name part from the string representation of the contructor. The following function makes use of this idea to implement a utility function for getting the exact class names of extrinsic objects. It returns undefined for intrinsic objects:/* Returns the class name of the argument or undefined if it's not a valid JavaScript object. */ function getObjectClass(obj) { if (obj && obj.constructor && obj.constructor.toString) { var arr = obj.constructor.toString().match( /function\s*(\w+)/); if (arr && arr.length == 2) { return arr[1]; } } return undefined; }
[edit] Enumerating objects
Dean Edwards: Enumerating JavaScript Objects (http://dean.edwards.name/weblog/2006/07/enum/).
Basically, by calling the forEach method on a function you can enumerate an object and compare the keys with that function’s prototype. That means that you will only enumerate custom properties of the object. An example is required:// create a class function Person(name, age) { this.name = name || ""; this.age = age || 0; }; Person.prototype = new Person; // instantiate the class var fred = new Person("Fred", 38); // add some custom properties fred.language = "English"; fred.wife = "Wilma";Enumerate using the standard forEach method:
forEach (fred, print); // => name: Fred // => age: 38 // => language: English // => wife: WilmaEnumerate using the Person.forEach method:
Person.forEach (fred, print); // => language: English // => wife: WilmaNote that the properties defined on the prototype are not enumerated in the second example.
[edit] Lambda notation (sort of) (>=1.8)
John Resig - JavaScript 1.8 Progress (http://ejohn.org/blog/javascript-18-progress/).
I think, probably, my favorite use for this shorthand will be in binding event listeners, like so:document.addEventListener("click", function() false, true);Or combining this notation with some of the array functions from JavaScript 1.6:
elems.some(function(elem) elem.type == "text");This will give you some JS/DOM code that looks downright elegant.
[edit] Iterators / "reduce" / etc.
John Resig - JavaScript 1.8 Progress (http://ejohn.org/blog/javascript-18-progress/).
// Add an iterator to all numbers Number.prototype.__iterator__ = function() { for ( let i = 0; i < this; i++ ) yield i; }; // Spit out three alerts for ( let i in 3 ) alert( i ); // Create a 100-unit array, filled with zeros [ 0 for ( i in 100 ) ] // Create a 10-by-10 identity matrix [[ i == j ? 1 : 0 for ( i in 10 ) ] for ( j in 10 )]...
Thus, if you wanted to sum up the numbers 0-99, you could do it like so (using JavaScript 1.8, and the number iterator from above):
[x for ( x in 100 )].reduce(function(a,b) a+b);Pretty slick!
You could also use the reduce function to do things like "merge sets of DOM nodes into a single array", like so (thanks Andrew!):
nodes.reduce(function(a,b) a.concat(b.childNodes), []);...
Prototype uses “inject” (our version of reduce) internally for things like DOM node accumulation. Imagine something like getting all of a collection’s child nodes:
function getChildren(nodes) { return nodes.inject([], function(total, node) total.concat(node.childNodes)); }
[edit] Implementations for use in pre-JavaScript-1.6 browsers
Sugar Arrays: Porting over JavaScript 1.6 Array methods (http://www.dustindiaz.com/sugar-arrays/).
As of Firefox 1.5, there has been a new wide array of Array helpers that were included in JavaScript 1.6. That’s all fine and great, however there isn’t a single other browser on the market (as of this writing) that supports any of the new array methods (let alone JavaScript 1.6). What’s a developer to do? [...] If you’re developing for todays grade A browsers, then you can count on being able to extend Array through it’s prototype. But, since I have a natural love toward things that are sweet, I’ve already done it for you with sugar and spice.
Sugar Arrays: JavaScript 1.6 Array Methods (http://www.dustindiaz.com/basement/sugar-arrays.html).
Array.prototype.forEach = function(fn, thisObj) { var scope = thisObj || window; for ( var i=0, j=this.length; i < j; ++i ) { fn.call(scope, this[i], i, this); } }; ... Array.prototype.map = function(fn, thisObj) { var scope = thisObj || window; var a = []; for ( var i=0, j=this.length; i < j; ++i ) { a.push(fn.call(scope, this[i], i, this)); } return a; };
[edit] Destructuring assignment (>= 1.7)
John Resig - JavaScript 1.8 Progress (http://ejohn.org/blog/javascript-18-progress/).
[x,y] = z for ([x,y] in z) [x for ([x,y] in z)]
[edit] Array comprehension (>= 1.7)
[edit] Generator expressions (>= 1.8)
John Resig - JavaScript 1.8 Progress (http://ejohn.org/blog/javascript-18-progress/).
Because generator expressions yield an easy way to generate lazy evaluation, and lazy evaluation is cool.
[edit] Optional parameters / default values for functions
I was surprised to find out that you can't specify default values for parameters in the formal parameter list:
function f(a, b = 'default') { ... }
Fortunately, that's not a big problem. Even if a parameter doesn't have a default value (it can't), you can still omit it when you call the method. In other words, no arguments are required in JavaScript; you can omit as many of the final arguments as you want and it won't throw an error.
However, inside of the function, the arguments will not be initialized. They will be of type 'undefined.
Workaround: How to specify default values: Method 1:
function f(a, b = 'default')
if (typeof(b) == 'undefined') { b = 'default' };
...
}
Workaround: How to specify default values: Method 2: "fatbrain" (2006-06-16). Default Arguments in JavaScript Functions (http://parentnode.org/javascript/default-arguments-in-javascript-functions/).
I often run into people who need (for reasons that are beyond me) to be able to give arguments in a javascript function default values. Kinda like what you have in C/C++:void foo(int a, int b = 42) { ... }I felt an urge to do it, so I sat down for a few minutes, trying to conjure something nifty that would be intuitive enough for even me to use.
The solution I came up with, and that hopefully will put an end to the I-need-default-arguments-in-javascript rant for good.
The solution is quite simple, and very intuitive. It may have some shortcomings but keep in mind I didn't explicitly write it for you. You could probably modify my solution to fit your needs anyway.
I've seen alot of developers (including myself, long time ago) using this pattern as a workaround for the lack of built-in support for default-arguments.
function foo(a, b) { a = typeof(a) != 'undefined' ? a : 42; b = typeof(b) != 'undefined' ? b : 'default_b'; ... }Which most developers probably find sufficient; I don't.
The solution may look scary at a quick first glance, but bear with me. I'll start by writing the framework-code. The code that will be required for this thing to work.
Function.prototype.defaults = function() { var _f = this; var _a = Array(_f.length-arguments.length).concat( Array.prototype.slice.apply(arguments)); return function() { return _f.apply(_f, Array.prototype.slice.apply(arguments).concat( _a.slice(arguments.length, _a.length))); } }See that wasn't so scary :).
In order for this to work you have to declare you functions in a special way. There are basicly two ways of declaring a function.
function foo(a, b) { ... }Is identical to (apart from the lexical difference, and some other minor things)
var foo = function(a, b) { ... }In order for this solution to work you have to declare all functions on which you wish to have default-arguments the latter way.
Usage
var foo = function(a, b) { ... }.defaults(42, 'default_b');Is identical to the first code-block but without adding any code in the function-body.
Example
var bar = function(a, b) { }.defaults('default_b'); bar(); // a = undefined, b = 'default_b' bar(1); // a = 1, b = 'default_b' bar(1, 'some_value'); // a = 1, b = 'some_value'
[edit] Prototype
[edit] [Enumerator (category)] [methods]
AlternateIdea: Prototype Meets Ruby: A Look at Enumerable, Array and Hash (http://encytemedia.com/blog/articles/2005/12/07/prototype-meets-ruby-a-look-at-enumerable-array-and-hash) (2005-12-07).
...$H(F.Products[0]).each(function(product) { logger.info(product.key + ": " + product.value); });...
AlternateIdea: Inject and Pluck: The Secret Sauce Behind Prototype's Enumerable (http://encytemedia.com/blog/articles/2006/12/28/prototype-enumerable-pluck-and-inject).
...// WRONG grades: function() { return this.results.map(function(result) { return result.grade; }); }While the above code works, and works well, Prototype has a tool for this type of operation: pluck. Pluck accepts one parameter, the name of the key in which you wish to extract a value from. We have an array of objects, and we want to get the grades by plucking the grade value from each student. Here it is:
// CORRECT grades: function() { return this.results.pluck('grade'); }When we invoke pluck on an array of objects, it will iterate over those objects grabbing the value in each one corresponding to the key you gave it. Simple, one-liner! [...]
In Ruby, you would do that like this:
def grades results.map(&:grade) end
AlternateIdea: Inject and Pluck: The Secret Sauce Behind Prototype's Enumerable (http://encytemedia.com/blog/articles/2006/12/28/prototype-enumerable-pluck-and-inject).
Inject is also a great way to build arrays and objects. What if we wanted to get the names of the students who correctly answered the bonus question? You might be tempted to write something like this:// WRONG gotBonus: function() { var students = [] this.results.each(function(student) { if(student.bonus) students.push(student.name); }); return students; }Again, the code above works, but it’s about the nastiest way to write it. It can be done in far fewer lines of code using inject:
// CORRECT gotBonus: function() { return this.results.inject([], function(array, student) { return student.bonus ? array.concat([student.name]) : array; });Inject takes care of creating and building our anonymous array and we just return the results of inject as our final outcome. In the case of our students, we’d get back the array [“Sally”, “Joe”]. We pass an array as the memo (lump of snow) and if the student got the bonus question right, we pack on a little more snow to our snowball, and if they got it wrong, we just leave our snowball as is and pass it back.
[edit] Libraries/frameworks
[edit] JQuery
http://michael.futreal.com/blog/0000052 Javascript Library Smackdown: JQuery v. Prototype
[edit] Fork
[edit] Prototype
[edit] MooTools
http://addictedtonew.com/archives/194/mootools-looks-cool/ Mootools Looks Cool
[edit] Yahoo! UI library
http://peter.michaux.ca/article/340 Fork JavaScript library launch
I like the Yahoo! UI library. Of the JavaScript libraries I've used it has the best API. The YUI library has many valuable nuggets of information about browser bugs and workarounds. The code approach of YUI suits browser scripting well. However there are more than a few places in the code where I'm left scratching my head and thinking "why did they do that?" Maybe that is how every developer looks at another developers code. The YUI API is the starting point for much of the Fork API.
[edit] Ext JS
http://extjs.com/ Ext JS - JavaScript Library
[edit] Javascript for the web (effects, etc.)
[edit] Learning/Reference
http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference Core JavaScript 1.5 Reference - MDC
http://www.quirksmode.org/js/contents.html
[edit] onLoad
function init() {
//...
}
window.onload = init;
http://www.quirksmode.org/js/events_tradmod.html:
var old = (element.onclick) ? element.onclick : function () {};
element.onclick = function () {old(); spyOnUser()};
[edit] childNodes
http://developer.mozilla.org/en/docs/DOM:element.childNodes
This is not of type Array, but of type NodeList.
Which means, for example, that I can't use map, and can't do nice iterator stuff like this:
$('feedback_updated').innerHTML = el.childNodes.map( function(value, i, arr) {
return value.innerHTML;
} ).join(', ');
Instead, I have to do all this:
array = [];
for (var node = el.firstChild; node != null; node = node.nextSibling) {
array.push(node.innerHTML);
}
$('feedback_updated').innerHTML = array.join(', ');
[edit] Neat libraries / code
[edit] cssQuery
cssQuery() (http://dean.edwards.name/my/cssQuery/).
cssQuery is a powerful cross-browser JavaScript function that enables querying of a DOM document using CSS selectors.Example:
// find all anchor elements with "href" equal to "#" var anchors = cssQuery("a[href='#']"); // find all images contained by the above anchors var images = cssQuery("img", anchors);
[edit] IE7 -- makes IE behave like a standards-compliant browser
/IE7/ (http://dean.edwards.name/IE7/).
IE7 is a JavaScript library to make IE behave like a standards-compliant browser. It fixes many CSS issues and makes transparent PNG work correctly under IE5 and IE6 (learn more).
[edit] JavaScript Fading Tooltips
http://www.dustindiaz.com/sweet-titles/ JavaScript Fading Tooltips
[edit] sorttable: Make all your tables sortable
http://www.kryogenix.org/code/browser/sorttable/
[edit] NiftyCube
http://www.html.it/articoli/niftycube/index.html : Adds rounded corners to your body layout
[edit] File uploads: Automatically choose good target filename
Source: MediaWiki
function fillDestFilename() {
if (!document.getElementById)
return;
var path = document.getElementById('wpUploadFile').value;
// Find trailing part
var slash = path.lastIndexOf('/');
var backslash = path.lastIndexOf('\\');
var fname;
if (slash == -1 && backslash == -1) {
fname = path;
} else if (slash > backslash) {
fname = path.substring(slash+1, 10000);
} else {
fname = path.substring(backslash+1, 10000);
}
// Capitalise first letter and replace spaces by underscores
fname = fname.charAt(0).toUpperCase().concat(fname.substring(1,10000)).replace(/ /g, '_');
// Output result
var destFile = document.getElementById('wpDestFile');
if (destFile)
destFile.value = fname;
}
[edit] Tips
[edit] window.onload ... throws "Not implemented" error in IE
http://p2p.wrox.com/topic.asp?TOPIC_ID=2439
Apparently you have to use an anonymous function, like this:
window.onload = function() {say("Hello!");};
rather than like this:
window.onload = say("Hello!");
[edit] Code analyzers/checkers/translators
http://www.crockford.com/javascript/jsmin.html JSMIN, The JavaScript Minifier
[JSLint http://www.jslint.com/] checks for problems in your JavaScript
[edit] Menus
[edit] Menus with a delay after mouseover before display
http://www.pjhyett.com/posts/206-timeout-your-mouseovers Timeout your Mouseovers
This is a fun effect I built for Chowhound that does one better for mouseovers. The problem with most menu systems is that they’re really touchy whether you’re too fast or slow with the mouse.
The trick is to use a timeout with the effect, so it will wait a fraction of a second to pop-up, and a fraction of a second to go away…just enough to make the effect feel solid and not finicky.
<script type="text/javascript"> var RollIt = { timeout : null, showPopup : function(){ clearTimeout(this.timeout); if($('rollit').style.display == 'none'){ this.timeout = setTimeout(function(){new Effect.BlindDown('rollit', {duration:.3, fps:40})},400); } }, hidePopup : function(){ if($('rollit').style.display == 'none'){ clearTimeout(this.timeout); }else{ this.timeout = setTimeout(function(){new Effect.BlindUp('rollit', {duration:.3, fps:40})},300); } } } </script> <div style="padding: 10px; background: #fff999;"> <a href="#" onclick="RollIt.hidePopup('rollit')" onmouseout="RollIt.hidePopup('rollit')" onmouseover="RollIt.showPopup('rollit')">Roll over Me</a> </div> <div id="rollit" style="display:none; background: #ff9999;" onmouseout="RollIt.hidePopup('rollit')" onmouseover="RollIt.showPopup('rollit')"> <h3 style="margin: 0;">Roll It</h3> By keeping your mouse hovering over this div or over the link that made it appear, it will stay open. <br/><br/> But, if you roll away from this div, it will roll back up. </div> <div style="background: #fff999; padding: 10px;"> Here's a bunch of other content </div>
[edit] How to add HTML elements dynamically
http://www.dustindiaz.com/add-and-remove-html-elements-dynamically-with-javascript/
http://www.dustindiaz.com/add-remove-elements-reprise/
http://www.matts411.com/webdev/creating_form_elements_with_javascript
It seems that you can add and remove elements (fields) from a form without problem -- unless the form is around a row of a table.
I wrote an example (not uploaded yet) to illustrate the (weird) problems I had with that. : adding_elements_to_a_form_in_a_table_row.html
[edit] How to make static paragraphs (etc.) turn into editable forms by clicking on them
http://www.quirksmode.org/dom/cms.html -- A nice tutorial/demo, focusing on how such a technique could be useful in a CMS.
[edit] Improving (Decreasing) JavaScript load time
http://betterexplained.com/articles/speed-up-your-javascript-load-time/ Speed Up Your Javascript Load Time | BetterExplained
http://dean.edwards.name/weblog/2007/03/google-it/
I was looking for a way to speed up the delivery of this site by moving all static content to code.google.com.http://dean.edwards.name/my/google-js.html
<script type="text/javascript" src="http://deanedwards.googlecode.com/svn/trunk/test.js"></script>
[edit] Misc/Articles
What ASP.NET Developers Should Know About JavaScript (http://www.odetocode.com/Articles/473.aspx) (May 08, 2007).
- ...
