JavaScript Programming: Difference between revisions

From Elvanör's Technical Wiki
Jump to navigation Jump to search
Line 46: Line 46:


* You can read, write and delete cookies in JavaScript. Regarding security restrictions, the policy is the same as if the cookie was set via an HTTP header.
* You can read, write and delete cookies in JavaScript. Regarding security restrictions, the policy is the same as if the cookie was set via an HTTP header.
== Locale ==
* In JavaScript, you can only read the browser or system language, and not the normal language mecanism that for example Firefox uses (via Accept HTTP headers). In addition, reading the locale in JavaScript does not seem to be a standard W3C feature (navigator.language or navigator.userLanguage in IE).


= Objects =
= Objects =

Revision as of 18:07, 27 August 2009

The best documentation on JavaScript available on the Internet is the one on the Mozilla development center. All the other sources are just not worthy and full of errors.

Syntax and Concepts

  • Using the var keyword means that the declared variable is local to the function (and thus is discarded once the function exits). To bind a variable to the global scope (window), just declare the variable without using var.
  • There are no constants in JS. The const keyword is a Mozilla extension (supported by Opera); this does not allow class constants though. The best for creating a class constant is just to declare a MyClass.MY_CONSTANT property. But the fact that it's a constant is not actually enforced (eg, it is only a convention).
  • The for (property in obj) construct allows you to loop over the properties of a JS object (which is always a Hash). Note that the property variable gets assigned the key name, if you want to access the actual value, write obj[property] in the loop. Also, don't forget that it will loop over all the properties, including the methods! This statement should then only be used when dealing with plain data holding objects.

Scope

  • The equivalent of bind() in Prototype:
function() { myObject.pollElement.apply(myObject); }

Interactions with the browser or server

General

  • You can't access the HTTP headers of the host page in JavaScript. You can access headers of XMLHttp requests though.

Ajax

  • The Same Origin Policy (SOP) prevents the browser from receiving results (from an XMLHttp request for example) that came from another host. This means that you can use "normal" Ajax requests only if they are sent to the same host as the origin page.
  • There are multiple ways to work around this problem though. You can use dynamic scripts (eg, create a script element on the page, that will point to the target host). The target host will create this script file. You can use iframes.
  • Note: although in theory SOP should only prevent the browser from reading the results of a request, in practice it seems the browser does not even send the request.
  • Warning: Opera refuses to read external scripts coming from a different port on a different domain. All other major browsers are more permissive and allow this.

JSON

  • Be careful when outputting text that needs to be parsed as a JavaScript string, but in fact contains JSON data. You will need in that case to double escape each backslash, as the first JS parsing (by the interpreter) will remove this backslash and the special character it contained. For example:
var myJSONString = '{ "data" : "Some text.\nAgain some text."}';
var data = myJSONString.evalJSON(); // This won't work as there is in fact a newline in the string, whereas it should be encoded data.
var myJSONString = '{ "data" : "Some text.\\nAgain some text."}';
var data = myJSONString.evalJSON(); // This works fine.
  • You can send a true Number (Float) with JSON, which will be parsed and allocated as a Number. For example, encodeAsJSON() in Grails produces Numbers from Float instances.

Cookies

  • You can read, write and delete cookies in JavaScript. Regarding security restrictions, the policy is the same as if the cookie was set via an HTTP header.

Locale

  • In JavaScript, you can only read the browser or system language, and not the normal language mecanism that for example Firefox uses (via Accept HTTP headers). In addition, reading the locale in JavaScript does not seem to be a standard W3C feature (navigator.language or navigator.userLanguage in IE).

Objects

Document

  • You can obtain the character set (encoding) of a document in JavaScript via document.characterSet.

Images

  • If you preload images, note that once an image is preloaded, you must attach it to the document somehow. Else it seems Firefox can somehow 'forget' it, and it will be fetched again, thus you lose the benefits of preloading.
  • Sample code for preloading (this uses Prototype):
function preloadNormalImages(url_array)
{
	url_array.each(function(url)
	{
		var normal_image = new Image();
		normal_image.onload = function() { $(image_preload).setAttribute("src", url); };
		normal_image.src = url;
	});
}
  • You can obtain in JS the width and height of an Image. Just create a new Image via new Image(), set the source. Then when the onload event is fired, you can obtain the width and height directly as property of the image.
  • new Image() is equivalent to document.createElement("img");
  • On Internet Explorer, if the image is already on the cache, the load event won't fire *if you set the image source before you register the event*. In order to avoid that, just register the event first. The event will then fire as soon as you set the source of the image.
  • Also note that on IE, when the event fires in this way, the code of the event is executed instantly. The behavior seems to be different from Firefox when the current running script finishes its execution before the event code runs.
  • On Firefox (and IE too I think) you can check if an image was correctly loaded was the naturalWidth property. If it is equal to 0, the image was not found / could not be loaded.

Arrays

  • There is a sort() method that takes a comparison function. Note that sortBy (Prototype) is usually easier, since it does not take a comparison function but only a direct "weight" function.
  • Be careful that many methods from the W3C DOM API return a NodeList and not an array, which can be problematic in many cases. You can (and should) easily convert from one to the other.

Events

General Notes

  • When submit() is called on a form programmatically, it won't be caught by an event set on the form.
  • Listening to key presses events needs to be done on the window object, not the document. Eg, use:
window.addEventListener("keydown", this.doSomething, true); // works
window.document.addEventListener("keydown", this.doSomething, true); // won't work

Note that in Prototype it seems OK to do document.observe("keydown").

  • Within an event, in Firefox (W3C API actually), you can test if the alt key, control key or such is currently pressed (active), via the property:
event.ctrlKey // boolean
  • The mouseout event on the document body is triggered everytime you enter a descendant element of the body (so any element actually). This is because the mouse leaves the body to enter a new element. This would also apply on a div that has descendant elements, so be careful with that. Many JS libraries implement a custom event to allow you to trigger a callback when the mouse actually leaves an element and its descendant as displayed on the screen.

Event Workflow

  • In standard W3C event handling, there are multiple phases. First the target of the event is determined by the browser. Then there is a capturing phase when the event goes down the node hierarchy, and a bubbling phase when it goes up.
  • This W3C reference explains the event model very well.
  • You cannot at all change the event target, unfortunately. This means that an element that appears over another one (like an overlay) blocks the underlying element from receiving any events, even if the overlay is transparent. There is no clean workaround over that - you can just recode manually a basic event handling module with the original coordinates of the event.

Focus / Blur

  • These events happen on form elements (input HTML elements with type = text or file for instance). focus is triggered when an element gains focus, blur is triggered when an element loses focus.
  • These elements do not bubble up (and I am not sure if capturing them is possible too, since their target can only be the form element).
  • There is no way to prevent the default action for a focus / blur event: these events are triggered once the elements actually have gained / lost focus. Note that they happen as soon as the mouse is clicked down (of course it is a separate event from mousedown).

JavaScript Frameworks & Libraries

Standard Library (and crossbrowser methods)

  • Be careful about the replace() method of strings; it will only replace one occurrence at most. Use the gsub() method in Prototype.
  • toFixed() is called on a Number and produces a String.

Third party frameworks

  • Prototype is a fantastic JavaScript framework. It is so good that it's almost impossible to program in JavaScript without it.
  • Scriptaculous is a add-on to Prototype. It provides visual effects, drag and drop support, and more.
  • ExtJS is an advanced JS framework for managing page layouts, etc. It can be used as a good foundation to build a web application similar to a desktop one.

Rich Text Editor

  • FCKeditor: a rich text editor that can be integrated into any web page. Integration is really easy, at least for a PHP environment.
    • Warning: due to a strange encoding of the source files on the package, it seems some UTF BOM characters are present. This causes some weird character output when using PHP integration (at least, untested with other environments). Remove these strange characters from the PHP integration files (fckeditor.php and all).
    • By default, FCKeditor converts the latin characters directly to HTML entities (eg, é is converted to é). To disable that, set IncludeLatinEntities to false in the configuration (the file fckconfig.js).
FCKConfig.ProcessHTMLEntities = true;
FCKConfig.IncludeLatinEntities = false;
FCKConfig.IncludeGreekEntities = false;

Calendar Widget

  • There are a lots of calendar widgets for JS. The ideal calendar library should be:
    • available under an open-source licence;
    • light (under 50Kb if possible, ideally less than 20Kb), and fast;
    • skinnable;
    • offers nice features such as the display of more than one month;
    • and based on Prototype since this is what I mainly use.
  • Below is a list of some interesting libraries I have found.

NoGray Calendar

  • Currently the best all-around that I have found.
  • Advantages:
    • open-source, very well documented;
    • easily skinnable;
    • lots of options.
  • Disadvantages:
    • built on mootools;
    • somewhat big (100K if you count mootools).
  • When passing date objects to the API methods (especially when creating the calendar object), it's better to use new Date().fromString("yesterday") than directly passing the string, as it is not always able to correctly parse it, or it takes a wrong date as a reference.
  • Be careful not to have custom CSS properties set on tables, as it may conflict with the styles used by the calendar. The font also should be Arial for the calendar. The size can be adjusted based on the container (nice idea), you need at least something like 180px though.
  • Apparently the initial page of the calendar is based on the start date or the selected date. Setting the selected date just for that is one of the default of this calendar, however you can set via JS the underlying text field (so that it's empty at first).
  • The syntax used to specify the string pattern for the underlying text field is the same as PHP, so here is the documentation.

Improved jQuery Datepicker

  • Not yet available for production, but the look is nice and the fixed suggestions feature is really, really a good idea.

DateJS

  • Not really a calendar, but the concept is interesting.

YUI calendar

  • Not tested, should work though and supports multiple monthes.


Hints and Tips

  • document.write() should *never* be used. It is an extremely dangerous method. Instead rely on Prototype which has much safer methods.
  • Deleting options in a select element (in a form):
document.my_form.my_select.options[index] = null;

Note that this syntax probably also works for other arrays of objects (untested yet, though).

  • Outputting quickly (for debugging) an array:
alert(my_array);

will display all the elements of the array, separated by commas.

  • Clickable link, calling some JS code:
<a href="page_no_JS.html" onclick="javascript: doSomething(); return false;"> ?

This works very well if you have two versions of your site. This will hide the JavaScript code in the status bar in Firefox, which is much better.

  • There is NO default arguments for JavaScript functions. You can however easily "simulate" default arguments by testing if a given argument is undefined, and setting it to a default value in that case (Prototype uses this technique).
  • In a class, or object related code, you must use the this keyword explicitly all the time. Even when calling methods.
  • To create static methods, properties and constants for a class, don't use Object.extend(); this would return an object (which cause problems in Opera if you don't allocate the object to a variable), and with Prototype Object.extend() is not necessary anymore. Just declare the methods directly on the class:
ShopItem.createNewItem = function(referenceItemId)
{
	// doSomethingHere();	
};