$Id: javascript.txt 1889 2008-08-18 21:46:56Z mjs $ ## TOP: Convert relative URL to absolute URL You may not need to do this; the `href` attribute of `a` elements is converted for you. ## TIP: Convert hash to query string var toQueryString = function(hash) { var s = ""; for (var k in hash) if (hash.hasOwnProperty(k)) { if (s !== "") { s += "&"; } s += encodeURIComponent(k) + "=" + encodeURIComponent(hash[k]); } return s; }; Note: if you already have a prototype dependency, use [`Hash.toQueryString`](http://www.prototypejs.org/api/hash/toQueryString) instead. ## TIP: Editable select element Not completely general, but gives an idea: Element.addMethods('select', { setOptions: function(e, options, selected) { if (!selected) { selected = e.selectedIndex >= 0 ? e.options[e.selectedIndex].text : null; } e.options.length = 0; $A(options).each(function (o) { e.options[e.options.length] = new Option(o, null, false, o == selected); }); }, setSelectedOption: function(e, option) { if (option) { $A(e.options).find(function (o) { return o.text == option; }).selected = true; } }, getOptions: function(e) { return $A(e.options).findAll(function (o) { return !o.value.match(/^__/); }).map(function (o) { return o.text; }); }, // Better/cleaner to use lastSelectedIndex instead of lastValue below? I // think I used lastValue because I figured it's more useful, but it // does complicate the implementation somewhat... (One issue is being // able to find the selected option after options have been added // to the select.) enhance: function(e) { e = $(e); // save the last "real" select e.lastValue = e.options[e.selectedIndex].text; // add divider and insert options e.options[e.options.length] = new Option("----------", "__div"); e.options[e.options.length] = new Option("Add new category ...", "__add"); e.observe("change", function(event) { var e = event.element(); var value = e.value; switch (value) { case "__div": // revert selection of the __div option $A(e.options).find(function (o) { return o.text == e.lastValue; }).selected = true; break; case "__add": // revert selection of the __add option $A(e.options).find(function (o) { return o.text == e.lastValue; }).selected = true; yprompt("yprompt", function(p) { // TODO deal with duplicate categories // IE adds options by index, not element, so try the insert in two ways; see // // http://groups.google.com/group/rubyonrails-spinoffs/msg/ebe5ca75bd132f06 (+ surrounding discussion) // http://support.microsoft.com/kb/276228 Try.these( function() { e.add(new Option(p, null, false, true), e.options[e.options.length - 2]); }, function() { e.add(new Option(p, null, false, true), e.options.length - 2); } ); e.lastValue = p; e.fire("data:insert", e.lastValue); // with callback function(e) { ... }, e.memo holds the selected value }); break; default: // save the last "real" select e.lastValue = e.options[e.selectedIndex].text; e.fire("data:change", e.lastValue); // with callback function(e) { ... }, e.memo holds the selected value break; } }); return e; } }); ## TIP: Proxy object Not really a proxy object, but demonstrates how to create one via JavaScript for duplicating method calls. var Duplicator = Class.create({ initialize: function(listeners) { // Would prefer to trap missing methods dynamically (like PHP's __call), but // this doesn't seem to be possible, so instead create stub methods for all // methods defined by the first listener. var methods = Object.keys(listeners[0]).findAll(function (m) { return m != "initialize" && Object.isFunction(this[m]); }, listeners[0]); methods.each(function (m) { this[m] = function() { var a = arguments; listeners.each(function (obj) { if (Object.isFunction(obj[m])) { obj[m].apply(obj, a); } }); } }, this); } }); var foo = new Foo(); var bar = new Bar(); var dup = new Duplicator([foo, bar]); dup.greet("Michael"); // calls by foo.greet() and bar.greet() ## TIP: Yes/No modal dialog box ## TIP: Single-line modal dialog box (prompt replacement) prompt doesn't really work on IE--it gives a whole bunch of security warnings and errors. The following code simulates prompt with the YUI's Dialog widget: ## TIP: JavaScript Scope 1. "In JavaScript, blocks do not introduce a scope. There is only function-scope." http://www.crockford.com/jslint/lint.html (Note that you can nest functions.) 2. "All variables declared in a function, no matter where they are declared, are defined throughout the function." function f() { alert(i); // i exists, and is undefined for (var i = 0; i < 10; i++) { alert(i); } alert(i); // i exists, and is 9 } ("JavaScript: The Definitive Guide," p. 55.) ## HOWTO: Dynamically manipulated select/option drop-downs via JavaScript To delete/remove existing options: el.options.length = 0; To set new options: el.options[i] = new Option('Michael Stillwell', 'mjs'); See http://www.quirksmode.org/js/options.html http://support.microsoft.com/kb/276228 http://particletree.com/notebook/adding-options-to-a-select-element/ ## GOTCHAS: Things IE doesn't do * Doesn't support the click (onclick) event against option elements. See http://www.quirksmode.org/js/events_compinfo.html ## HOWTO: Read HTTP status code (trap 404) I don't believe this is possible. XMLHttpRequest will return the status code, but that requires another request. (Also, redirects will be processed by the browser, so if you want the redirect chain you're completely out of luck.) See: http://tough-to-find.blogspot.com/2007/09/javascript-http-status-detection.html ## HOWTO: Dynamically load JavaScript/write <script> elements var script = document.createElement('script'); script.src = "http://example.com/js/example.js"; script.type = 'text/javascript'; document.getElementsByTagName('head')[0].appendChild(script); ## HOWTO: Refer to a function from itself e.g. for use in a recursive anonymous function. Use [arguments.callee](http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Objects:Function:arguments:callee) ## HOWTO: Do a news ticker Somewhat inspired by: http://news.bbc.co.uk/nol/ifs_news/hi/front_page/ticker.stm function ticker(args) { var n = 0; // the story we're displaying var i = 0; // position within the story (function() { var story = args.stories[n]; var c = i % 2 ? "-" : "_"; if (i == story.title.length) { c = ""; } args.element.href = story.url; args.element.innerHTML = args.prefix + story.title.substring(0, i) + c; if (i != story.title.length) { i++; delay = args.charDelay; } else { i = 0; n = (n + 1) % args.stories.length; delay = args.storyDelay; } setTimeout(arguments.callee, delay); })(); }; ticker({ element: document.getElementById("tickerAnchor"), stories: [ { url: "http://url1.com/", title: "Story 1" }, { url: "http://url2.com/", title: "Story 2" }, { url: "http://url3.com/", title: "Story 3" } ], charDelay: 50, storyDelay: 2000, prefix: "LATEST: " }); Where tickerAnchor is: ## FAQ: What does "new" do? / How do constructors work in JavaScript? "new", when it precedes a function, invokes the function and arranges for the variable "this" within the function to be a reference to a new, empty object. The value of the expression (i.e. the value returned by "new") is "this", unless the function (i.e. constructor) returns a value in which case this value is returned. (Constructors do not typically return a value.) One implication of this is that anonymous constructors are possible: var user = new function(name) { this.name = name; }; See "Constructors" in JavaScript: The Definitive Guide. ## HOWTO: Create automatically changing drop-downs onchange="if (this.options[selectedIndex].value != '') location.href=this.options[selectedIndex].value" ## FAQ: THE "ONCHANGE" EVENT DOESN'T WORK WITH CHECKBOXES IN IE No, it doesn't--or at least not reliably. You can poll for changes to a form surprisingly efficiently though. A Prototype example: if (Prototype.Browser.IE) { var last_state = $(form).serialize(); var p = new PeriodicalExecuter(function () { var state = $(form).serialize(); if (state != last_state) { last_state = state; // form has changed } }, 0.2); }