This page is offered as a service of Bristle Software, Inc. New tips are sent to an associated mailing list when they are posted here. Please send comments, corrections, any tips you'd like to contribute, or requests to be added to the mailing list, to tips@bristle.com.
Original Version: 4/14/2007
Last Updated: 4/30/2007
Applies to: JavaScript 1.0+
JavaScript (more properly called ECMAScript) is not Java. However, for their simpler features, they both use the same syntax as C, C++, Perl and many other languages, all based originally on the syntax of the C language. For many of the basic programming constructs, it is hard to tell which language you are looking at:
// One-line comment
/* Block comment */
if (x==y) {doSomething(); } else { doSomethingElse(); }
for (i=1; i < 10; i++) { doSomething(); }
while (i < 10) { doSomething(); }
However, the similarities end there. Java is a "strongly typed" and "statically typed" language where the compiler catches many simple programmer mistakes by requiring an object to be declared with a data type and to maintain that same data type at all times. It is very good for writing large scale mission-critical systems. JavaScript is a "dynamically typed" scripting language where you don't declare the type of an object, and its type can change on the fly. You can even add new properties and methods to an object on the fly. Very good for writing small scale non-critical bells and whistles to run in a Web page, as part of the user interface to a Java application.
You don't need to download and install a compiler or runtime environment for JavaScript. There is a JavaScript interpreter built into every Web browser. Therefore, JavaScript is commonly used for logic written to run in the browser for a Web application, including the browser-side logic for "Ajax" interactions (more on that later).
For more details, see the JavaScript and ECMAScript links on my links page:
http://bristle.com/~fred/#javascript
--Fred
Last Updated: 4/17/2007
Applies to: JavaScript 1.0+
The simplest way to prompt the user in JavaScript is to pop up a message and wait for the user's response. There are 3 standard JavaScript methods to do so:
| alert (message) | Pop up a message with an OK button. Wait for OK to be clicked. |
| confirm (message) | Pop up a message with OK and Cancel buttons. Wait for a button to be clicked. Return true if OK is clicked; false if Cancel. |
| prompt (message, [default]) | Pop up a message and a text input field with OK and Cancel buttons. If a default value was specified, initialize the input field with it. Otherwise, leave the input field empty. Wait for the user to optionally change the value of the input field, and then click a button. Return the value of the input field if OK is clicked; null if Cancel. |
These are the simplest ways to prompt the user. However, they
have a number of limitations:
These are good for debugging (other than issue #8 above, which can be a problem when trying to track down a bug), and for demos, but not so good for production code, where it is often better to pop up a message that is HTML, asynchronous, non-modal, copyable, able to be dismissed programmatically, scrollable, changeable, and doesn't interfere with other events. In a future tip, I'll describe how.
Meanwhile, for a transient message that does not require dismissal, you may prefer to use the "title" attribute of an HTML element instead. It appears automatically when the user hovers the mouse over the HTML element and disappears automatically when the user moves the mouse or hits a key. Or write JavaScript code to respond to events like onmouseover, onmouseout, onkeyup, etc.
For more info on alert(), confirm(), prompt() and other standard JavaScript functions, see:
http://www.javascriptkit.com/jsref/globalfunctions.shtml
--Fred
Original Version: 4/14/2007
Last Updated: 5/4/2007
Applies to: JavaScript 1.0+
As in most scripting languages, JavaScript variables are dynamically typed. When you declare a variable, you don't give it a static type. Instead, you simply declare it and/or initialize it. You must do one or both of these before referring to its value. Thus:
var x = 1; // Declares x with a numeric value of 1.
alert(x); // Shows: 1
y = "hello"; // Initializes (and implicitly declares) y
// with a string value of "hello".
alert(y); // Shows: hello
var z; // Declares z with a special undefined value.
alert(z); // Shows: undefined
z = y + y; // Assigns z the string value "hellohello".
alert(z); // Shows: hellohello
z = x + x; // Assigns z the numeric value 2.
alert(z); // Shows: 2
x = "a string"; // Assigns x the string value "a string",
// changing its type from numeric to string.
alert(x); // Shows: a string
z = x + x; // Assigns z the string value "a stringa string"
alert(z); // Shows: a stringa string
var z; // Re-declares z, leaving its value intact.
alert(z); // Shows: hellohello
alert(zz); // Throws an exception, since zz does not exist.
--Fred
Last Updated: 4/14/2007
Applies to: JavaScript 1.0+
JavaScript is most commonly used inside HTML pages, where it is wrapped in an HTML <script> element.
The <script> element can contain the actual JavaScript code, as:
<script language='JavaScript'>
<!-- Hide script from older browsers, as HTML comments.
// Global code that is executed as soon as it is found.
var i = 1;
alert("i = " + i);
// Function to do something useful. Not executed until called.
function doSomething(evt, obj)
{
var i = 2;
alert("i = " + i);
return false;
}
//-->
</script>
Note the HTML comment delimiters ("<!--" and "-->") used inside the <script> tag, but outside the JavaScript code. This is for compatibility with really old browsers (1990's-era) that don't recognize the <script> tag. Browsers typically ignore tags they don't recognize, but may still render the contents of the tag, so this prevents the JavaScript code from being shown to the user in a really old browser.
Also, note the JavaScript comment delimiter ("//") before the HTML close comment delimiter ("-->"). This prevents the JavaScript interpreter from complaining about "-->" being invalid JavaScript code.
--Fred
Last Updated: 4/14/2007
Applies to: JavaScript 1.0+
Instead of containing the JavaScript code, a <script> tag can mention the name of a separate file that contains the JavaScript code, as:
<script language='JavaScript' src='js/sourcefile.js'></script>
The file is specified using a pathname relative to the current HTML filename, so the above would look in the "js" subfolder of the folder containing the HTML file.
I don't recommend using the shorter HTML format (ending the start tag with "/>" and using no explicit end tag), because I've had trouble with it in some browsers:
<script language='JavaScript' src='js/sourcefile.js' />
Since the default language is 'JavaScript', you can omit the language attribute, as:
<script src='js/sourcefile.js'></script>
You can specify other scripting languages that are supported by some browsers:
<script language='PerlScript' src='perl/sourcefile.pl'></script>
<script language='VBScript' src='vb/sourcefile.bas'></script>
<script language='JScript' src='js/sourcefile.js'></script>
You can even mention the explicit version of the scripting language, which may be necessary in scripting languages where different versions are not backward compatible, or where you are using the very latest features that only the latest version supports. I've never used it, but I think the syntax is something like:
<script language='JScript 1.0' src='js/sourcefile.js'></script>
<script language='JScript 1.1' src='js/sourcefile.js'></script>
Personally, I always specify the language explicitly, but leave out the version number. I've had no problems yet with incompatibility between different versions of JavaScript. Different versions of the DHTML object model, yes, but not different versions of JavaScript.
Finally, there is a newer syntax, where the "language" attribute is deprecated in favor of a new "type" attribute that takes the standard MIME-type syntax, as:
<script type='text/javascript' src='js/sourcefile.js'></script>
<script type='text/ecmascript' src='js/sourcefile.js'></script>
I haven't moved to that yet because the old syntax works fine in all browsers, but the new syntax doesn't work in old browsers.
--Fred
Last Updated: 4/14/2007
Applies to: JavaScript 1.0+
The problem:
How to avoid name collisions in JavaScript?
When you use only a couple of JavaScript functions and variables in a Web page, it is easy to avoid name collisions. However, as you make more use of JavaScript, you start to accumulate a lot of functions and variables, and need naming conventions. If you have multiple people writing them, conventions become even more important. Then, as you start using JavaScript libraries written by other people, or even other companies or vendors, it becomes important that the naming convention be standardized around the world. Even more so if you write your own JavaScript library and want it to work in a variety of environments.
Other languages solve this problem via "namespaces" or "packages". What to do in JavaScript, which doesn't offer these features?
The solution:
You can create the effect of a "namespace" in JavaScript by taking advantage of its dynamic nature to add all of your functions and variables to JavaScript objects. Furthermore, you can mimic the Java naming convention (based on registered Internet domain names) to avoid collisions between unrelated people and organizations throughout the world. Here's how.
Since I own the "bristle.com" domain, the Java convention says that I should place all of my Java classes in packages with names that start with "com.bristle" like:
com.bristle.javalib.util.StrUtil
com.bristle.javalib.util.GetOpt
com.bristle.javalib.log.Logger
com.bristle.javaapps.scalejpg.ScaleJPG
com.bristle.webapps.slideshow.SendImageServlet
Since no one else in the world owns "bristle.com", there should be no collisions with anyone outside my organization.
I do the same with my JavaScript library. Instead of polluting the global namespace with functions like:
function doSomethingUseful(param1, param2)
{
// Put useful code here.
}
I write:
com.bristle.jslib.Util.doSomethingUseful = function(param1, param2)
{
// Put useful code here.
}
This function can be called as:
com.bristle.jslib.Util.doSomethingUseful(1, 2);
In the JavaScript source file, before I declare the function, I create the nested set of objects that will hold the function, as:
var com = {};
com.bristle = {};
com.bristle.jslib = {};
com.bristle.jslib.Util = {};
OK. So far, so good. But, what if someone else does the same, and also creates the starting "com" variable, as:
var com = {};
com.other = {};
Their "com" object replaces my "com" object, and all of the "com.bristle..." stuff vanishes. Not good. The solution is for all of us to test the existence of the "com" object before creating it. Therefore, I actually write the following:
if (typeof(com) == "undefined") { eval("var com = {};"); }
Furthermore, since my JavaScript library contains lots of different groups of functions, in lots of different files, with names like:
com.bristle.jslib.Util.js
com.bristle.jslib.Event.js
com.bristle.jslib.Validate.js
I start each file with the conditional creation of all of the required nested objects, plus the unconditional creation of the most nested object that will hold all the functions and variables in that file. For example:
if (typeof(com) == "undefined") { eval("var com = {};"); }
if (typeof(com.bristle) == "undefined") { eval("com.bristle = {};"); }
if (typeof(com.bristle.jslib) == "undefined") { eval("com.bristle.jslib = {};"); }
com.bristle.jslib.Util = {};
I suggest you do the same, storing your own JavaScript functions and variables in nested objects named for domains you own.
--Fred
Last Updated: 4/17/2007
Applies to: JavaScript 1.0+
Dynamic HTML (DHTML) is a technique that allows you to change the HTML in a Web page on the fly, after it has been sent from the Web server to the browser. You still write the HTML as before, however, you add "id" attributes to those HTML elements that you want to manipulate dynamically, as:
<table>
<tr>
<td>abc</td>
<td id='tdACellICareAbout'>def</td>
<td>ghi</td>
<td id='tdAnotherCellICareAbout'>jkl</td>
</tr>
</table>
Then you can manipulate the HTML elements from JavaScript code using the ids. For example:
<script language='JavaScript'>
alert(document.all.tdACellICareAbout.innerHTML); // Shows def
alert(document.all.tdAnotherCellICareAbout.innerHTML); // Shows jkl
document.all.tdACellICareAbout.innerHTML = '<b>xyz</b>';// Changes the cell to xyz (bold)
document.all.tdAnotherCellICareAbout.innerHTML = 'pdq'; // Changes the cell to pdq
</script>
Whether or not you use DHTML, browsers create a Document Object Model (DOM) in memory to represent the HTML that was loaded into the page. Browsers do this for every Web page, regardless of whether you specify any id attributes. The DOM for the above example would consist of a "document" object, containing a "body" object, containing a "table" object, containing a "tr" object, containing 4 "td" objects, 2 of which happen to have ids.
The purpose of the id attributes is to make it easier for your JavaScript code to find specific DHTML DOM objects in this potentially huge hierarchy. The syntax "document.all.tdACellICareAbout" tells the browser to find the HTML element with id "tdACellICareAbout" anywhere in the document. The "innerHTML" property is a property of a DOM "td" object. Once you've found the right "td" object, you can set its innerHTML to any string of text, even strings that contain more HTML element tags, and it will update the screen immediately. In the example above, I replace the text of a cell with bolded text, but I could just as easily have replaced the text of the cell with an entire new table nested inside that cell. For more info about HTML elements and attributes, and their corresponding DHTML objects and properties, see:
http://bristle.com/~fred/#html
http://bristle.com/~fred/#dhtml
--Fred
Last Updated: 4/17/2007
Applies to: JavaScript 1.0+
As shown in the above example, the syntax:
document.all.myId
is a way to search for an HTML element with an id value of "myId". This is a convenient shortcut, but not really the official way to do it. It is supported in Microsoft Internet Explorer, Mozilla Firefox, and perhaps other browsers, but not necessarily all browsers. The official syntax is:
document.getElementById("myId")
However, the shortcut is gaining popularity, and will probably soon be supported by all browsers.
Another variation on the shortcut is:
document.all["myId"]
This form and the official syntax have one advantage over the simpler dotted form, which is that the id is specified as a string value, not as an implicit property name of the "all" property. Since it is a regular string, you can manipulate the string, computing an id value, or even reading it from a file or something, before using it to get an HTML element. For example:
var intCounter = 1;
strId = "myId" + intCounter;
alert(document.all[strId].innerHTML);
If you changed the last line to:
alert(document.all.strId.innerHTML);
you'd be trying to access the HTML element with id "strId", not "myId1".
--Fred
Original Version: 4/17/2007
Last Updated: 5/22/2007
Applies to: JavaScript 1.0+
Don't confuse the "name" attribute and the "id" attribute.
The "id" attribute of an HTML element is used in JavaScript code to refer to the element programmatically. It is useful only when manipulating the HTML element as DHTML.
The "name" attribute of an HTML element is different. If an input-type
element (input, textarea, select, etc.) has a name attribute, and is inside a form, then its name/value pair gets sent to the server when
the form is submitted. The name attribute predates the existence of
DHTML and is useful in any HTML data entry page, regardless of whether DHTML and JavaScript are being
used.
These are two very different things. However, lots of people confuse
them, so browsers try to be helpful and accommodate the wrong one being used in some situations, so more people confuse them, etc.
You can get away with using the wrong one sometimes, but it is better to use them
correctly. Add an id to all elements you want to manipulate by id in
JavaScript. Add a name only when the element is supposed to send data
to the server.
When choosing ids and names for your HTML elements, keep in mind the different purposes that they serve. One convention is to use the same value for name and id, but I don't think that is appropriate because it just adds to the confusion between the two.
Instead, I set the id to reflect the type of HTML control and other things known about it by the local JavaScript that manipulates it as an HTML element, and I set the name to reflect the meaning and data type of the value sent to the back end server code. For example, if I have an HTML select that shows descriptive names to the user, but has DB keys as values, as:
<select name='productKey' id='selProduct'>
<option value='123'>Aspirin<option>
<option value='127'>Tylenol<option>
<option value='128'>Advil<option>
</select>
I might use a name of "productKey" to remind the server code that
the request parameter by that name is a numeric database key, and an id of
"selProduct" to remind the JavaScript code that it is an HTML select
element and therefore has select-type properties like selectedIndex.
If I later change the
UI so it is a set of checkboxes. I may change the id to "chkProduct",
but I'd leave the name as "productKey". Such "information hiding" and "separation of concerns" principles
are important to large scale software development.
Also, speaking of checkboxes, sometimes you have to give the same name to several HTML
elements, so they send their data as expected, but you may still want separate ids for them, so the JavaScript can manipulate
them separately.
--Fred
Last Updated: 4/17/2007
Applies to: JavaScript 1.0+
Here is a Bristle Software JavaScript library function to get the current style of an HTML element.
/******************************************************************************
* Get the current style of the specified object.
*
* Note: Can't just use the style property. That is just the inline style
* specified in the HTML. This is much more accurate, including
* default values, values set by linked or cascaded stylesheets, and
* values set dynamically via DHTML.
* Note: Can't just use the currentStyle property. It is IE-specific.
******************************************************************************/
com.bristle.jslib.Util.getStyle =
function(obj)
{
return ((typeof(obj.currentStyle) != "undefined")
? obj.currentStyle
: ((typeof(window.getComputedStyle) != "undefined")
? window.getComputedStyle(obj,"")
: obj.style
)
);
}
--Fred
Last Updated: 4/17/2007
Applies to: JavaScript 1.0+
Here are Bristle Software JavaScript library functions to get and set the visibility of an HTML element.
/******************************************************************************
* Get the visibility of the specified object.
******************************************************************************/
com.bristle.jslib.Util.isVisible =
function(obj)
{
return !(com.bristle.jslib.Util.getStyle(obj).display == "none");
}
/******************************************************************************
* Set the visibility of the specified object.
******************************************************************************/
com.bristle.jslib.Util.setVisible =
function(obj, blnValue)
{
// Note: Set display property to "none" instead of setting visibility
// property to "hidden". Otherwise the display space consumed
// by the element does not get reused. The page does not reflow.
// The element is invisible but still consumes page space, per
// Danny Goodman O'Reilly Dynamic HTML book.
// Note: Set display property to "none" instead of setting visibility
// property to "collapse", which is still not recognized by
// Internet Explorer for Windows version 6.0, per Danny Goodman
// O'Reilly Dynamic HTML book.
obj.style.display = blnValue ? "inline" : "none";
}
--Fred
Last Updated: 4/17/2007
Applies to: JavaScript 1.0+
Here are Bristle Software JavaScript library functions to get and set the disabled property of an HTML element.
/******************************************************************************
* Get the disabled property of the specified object.
******************************************************************************/
com.bristle.jslib.Util.isDisabled =
function(obj)
{
return obj.disabled;
}
/******************************************************************************
* Set the disabled property of the specified object.
******************************************************************************/
com.bristle.jslib.Util.setDisabled =
function(obj, blnValue)
{
obj.disabled = blnValue;
}
--Fred
Last Updated: 9/8/2008
Applies to: JavaScript 1.0+
Goal:
A user interface should be as self-explanatory as possible. Therefore, all controls should tell you what they do. Furthermore, all disabled controls should tell you why they are disabled and what you can do to enable them.
I use the standard "tooltip" or "bubble help" technique for this. When you hover the mouse over the control, it pops up a small explanatory text message. In Web applications, I accomplish this by setting the "title" property of an HTML element, and the hover popups occur automatically.
This is especially useful in very dynamic user interfaces, where
elements are enabled and disabled on the fly for a variety of
reasons. For example, a Delete button may ordinarily be enabled
and have a tooltip that says:
"Delete this ingredient
from the formulation"
However, in a variety of situations, the button may be disabled, and the
tooltip may contain one of the following explanations:
"You are not authorized
to delete this ingredient from this formulation"
"Cannot delete an ingredient that
is constrained to have an amount more than zero"
"Cannot delete before
saving. Click Save to enable this button."
etc.
Problem:
When I disable an HTML element via its standard "disabled" attribute, the hover popups don't happen. So, how can you tell why the control is disabled and what you can do to enable it?
Solution:
My solution is to leave all elements enabled at the HTML level so that the tooltips work properly, and to fake the behavior of enabled/disabled. Here are the Bristle Software JavaScript library functions to get and set the "fake disabled" property of an HTML element.
/******************************************************************************
* Set the "fake disabled" property of the specified HTML control, so that
* it looks and acts disabled, but still supports a title popup that can be
* used to tell the user why it is disabled.
*
* Note: This doesn't actually prevent the events of the control from firing.
* To complete the disabled effect, all event procedures of the control
* should call com.bristle.jslib.Util.isFakeDisabled() to decide whether
* to return without doing anything.
*
*@param ctl The control to enable or disable
*@param blnDisabled True if disabled; false otherwise.
*@param strClassName The CSS class name to assign to the control to change
* its appearance to enabled or disabled.
* Optional. Can be null or omitted, in which case the
* CSS class is left unchanged. The empty string is a
* valid value which can be used to clear the class name.
*@param strTitle The value to set as the "title" property (hover popup)
* of the control, typically used to explain its purpose
* or why it is disabled.
* Optional. Can be null or omitted, in which case the
* title property is left unchanged. The empty string
* is a valid value which can be used to clear the title.
******************************************************************************/
com.bristle.jslib.Util.setFakeDisabled =
function(ctl, blnDisabled, strClassName, strTitle)
{
// Note: In most browsers, any negative number removes the control from
// the tab sequence, and zero restores its default position in the
// sequence. However, in some older browsers, the default is -1
// instead of zero. Therefore, use -2 as the special value, not -1.
ctl.tabIndex = (blnDisabled ? "-2" : "0");
if ((typeof(strClassName) != "undefined") && (strClassName != null))
{
ctl.className = strClassName;
}
if ((typeof(strTitle) != "undefined") && (strTitle != null))
{
ctl.title = strTitle;
}
}
/******************************************************************************
* Get the "fake disabled" property of the specified HTML control.
******************************************************************************/
com.bristle.jslib.Util.isFakeDisabled =
function(ctl)
{
return ("-2" == ctl.tabIndex);
}
--Fred
Last Updated: 4/17/2007
Applies to: JavaScript 1.0+
Here are Bristle Software JavaScript library functions to get and set the bold property of an HTML element.
/******************************************************************************
* Get the boldness of the specified object.
******************************************************************************/
com.bristle.jslib.Util.isBold =
function(obj)
{
return (com.bristle.jslib.Util.getStyle(obj).fontWeight == "bold");
}
/******************************************************************************
* Set the boldness of the specified object.
******************************************************************************/
com.bristle.jslib.Util.setBold =
function(obj, blnValue)
{
obj.style.fontWeight = blnValue ? "bold" : "normal";
}
--Fred
Last Updated: 4/17/2007
Applies to: JavaScript 1.0+
Here are Bristle Software JavaScript library functions to get and set the foreground and background colors of an HTML element.
/******************************************************************************
* Get the color of the specified object.
******************************************************************************/
com.bristle.jslib.Util.getColor =
function(obj)
{
return (com.bristle.jslib.Util.getStyle(obj).color);
}
/******************************************************************************
* Set the color of the specified object.
******************************************************************************/
com.bristle.jslib.Util.setColor =
function(obj, strValue)
{
obj.style.color = strValue;
}
/******************************************************************************
* Get the background color of the specified object.
******************************************************************************/
com.bristle.jslib.Util.getBackgroundColor =
function(obj)
{
return (com.bristle.jslib.Util.getStyle(obj).backgroundColor);
}
/******************************************************************************
* Set the background color of the specified object.
******************************************************************************/
com.bristle.jslib.Util.setBackgroundColor =
function(obj, strValue)
{
obj.style.backgroundColor = strValue;
}
--Fred
Last Updated: 11/1/2007
Applies to: JavaScript 1.0+
Various "events" occur in the Web browser, in response to user interactions, timers, pages loading, etc, and you can assign snippets of JavaScript code to "handle" the events.
Some particularly useful events include:
| Type | Events |
| Web page | load, unload, resize |
| Mouse buttons | click, dblclick, mousedown, mouseup |
| Mouse pointer | mousemove, mouseover, mouseout |
| Keyboard | keypress, keydown, keyup, blur, focus |
| Data entry fields | change, select |
The typical way to assign an event handler is via the HTML "on" event handler attributes of the HTML elements. For example, to cause the JavaScript routine objBody_onLoad() to be called when the Web page finishes loading:
<body id='objBody' onload='return objBody_onLoad(event, this)'>
Similarly:
<input id='txtName'
type='text'
onchange='return txtName_onChange(event, this)'
onkeyup='return txtName_onKeyUp(event, this)'
onmouseup='return txtName_onMouseUp(event, this)'
onmousemove='return txtName_onMouseMove(event, this)'
onmouseover='return txtName_onMouseOver(event, this)'
onmouseout='return txtName_onMouseOut(event, this)'
/>
causes the various specified routines to be called whenever the text field value changes, when a keyboard or mouse key is released in the text box, when the mouse moves within the text box, and when the mouse moves into or out of the text box.
Notes:
Here's an example of such an event handler. It turns the text field red when the mouse moves into it.
function txtName_onMouseOver(evt, obj)
{
obj.style.backgroundColor = "red";
return true;
}
--Fred
Last Updated: 11/1/2007
Applies to: JavaScript 1.0+
Another way to assign an event handler is via the "on" properties of the DOM objects. For example:
txtName.onchange = txtName_onChange;
This DOM property approach is more dynamic than the HTML attribute approach because you can assign a new event handler on the fly. Also, this approach allows you to assign event handlers to objects that don't occur in the HTML, like the document itself.
However, it requires that you specify the name of a JavaScript function, without specifying what parameters to pass. In most browsers, such an event handler is always called with one parameter -- the event. In Internet Explorer, such an event handler is called with no parameters, so you have to use the global window.event object to determine the current event. Once you know the event, you can determine the object to which the event occurred via the event.target property in most browsers. In Internet Explorer, you must use the event.srcElement property.
If the event handler is assigned in this way, it has more work to do:
function txtName_onMouseOver(evt)
{
// Get event, which IE does not pass.
if (typeof(evt) == "undefined")
{
evt = window.event;
}
// Get the object to which the event occurred.
var obj;
if (typeof(evt.target) != "undefined")
{
// Most browsers
obj = evt.target;
}
else if (typeof(evt.srcElement) != "undefined")
{
// Internet Explorer
obj = evt.srcElement;
}
else
{
// Can't tell which object. What to do?
return true;
}
// Finally, set the color to red.
obj.style.backgroundColor = "red";
return true;
}
--Fred
Last Updated: 11/1/2007
Applies to: JavaScript 1.0+
Here is the Bristle Software JavaScript library function to determine which key was pressed in a keyboard event:
/******************************************************************************
* Get the keyCode from the specified event.
*
*@param evt The event containing the keyCode
*@return The keyCode, or null.
*@throws None.
******************************************************************************/
com.bristle.jslib.Event.getEventKeyCode =
function(evt)
{
if (!evt) return null;
if (evt.keyCode && evt.keyCode != 0)
{
return evt.keyCode;
}
else if (evt.charCode) // Netscape uses charCode, not keyCode, for
// some events.
{
return evt.charCode;
}
else
{
return null;
}
}
--Fred
Last Updated: 11/1/2007
Applies to: JavaScript 1.0+
Want a way to prevent the Backspace key from clicking the browser Back button in your Web application?
Problem:
Modern Web applications often have large complex data entry screens. Your user may spend a significant amount of time filling them out before finally submitting them to the Web server. Much of the data is entered via text fields, which means he is likely to hit the Backspace key often. However, the default browser behavior when he hits Backspace outside of a text box is to click the browser's Back button. Once the back button has been clicked, there is no way for you, the JavaScript programmer, to prevent the browser from discarding the current page and returning to the previous page. The best you can do is take some action on the way out, like prompting the user and attempting to save the data if it has no validation errors. This is a common and frustrating user error.
Solution:
The solution is to disable the Backspace key as a shortcut for the Back button, on all of your complex data entry screens, taking care to not disable its main function of deleting typed characters. With the Backspace key disabled, your user can still return to the previous page by hitting Alt-Left Arrow, or by clicking on the Back button with the mouse, which he is less likely to do by accident.
Here is the Bristle Software JavaScript library code to prevent Backspace from causing a browser Back operation. To use it, simply put the following call in your objBody_onLoad() event handler:
com.bristle.jslib.Event.disableBackspaceAsBrowserBackButton();
and paste the following (along with the getEventKeyCode() routine from the previous tip) into the HTML page or an included JavaScript source file:
com.bristle.jslib.Event.intKEYCODE_BACKSPACE = 8;
/******************************************************************************
* Return true if the specified event is the Backspace key.
*
*@param evt The event containing the keyCode
*@return True if the event was the Backspace key.
*@throws None.
******************************************************************************/
com.bristle.jslib.Event.isBackspaceKey =
function(evt)
{
if (com.bristle.jslib.Event.getEventKeyCode(evt) ==
com.bristle.jslib.Event.intKEYCODE_BACKSPACE)
{
return true;
}
return false;
}
/******************************************************************************
* Return false if the specified event is the Backspace key as a shortcut for
* the browser Back button, true if it is being used to delete chars in an
* enabled text box or if it is not a Backspace at all.
*
*@param evt Event to check for backspace key.
******************************************************************************/
com.bristle.jslib.Event.isNotBackspaceAsBrowserBackButton =
function(evt)
{
// Get event which IE does not pass as a param like other browsers do.
if (typeof(evt) == "undefined")
{
evt = window.event;
}
if (!com.bristle.jslib.Event.isBackspaceKey(evt))
{
return true;
}
// Note: None of these are needed. Returning false is sufficient.
// if (typeof (evt.returnValue) != "undefined")
// {
// evt.returnValue = false;
// }
// if (typeof (evt.cancelBubble) != "undefined")
// {
// evt.cancelBubble = true;
// }
// if (typeof (evt.stopPropagation) != "undefined")
// {
// evt.stopPropagation();
// }
// if (typeof (evt.keyCode) != "undefined")
// {
// evt.keyCode = 0;
// }
// Get the HTML element in which Backspace was typed.
var targetNode;
if (typeof(evt.target) != "undefined")
{
// Most browsers
targetNode = evt.target;
}
else if (typeof(evt.srcElement) != "undefined")
{
// Internet Explorer
targetNode = evt.srcElement;
}
else
{
// Can't tell where the Backspace was pressed. Don't cancel it.
return true;
}
// Don't cancel Backspace for these HTML elements except when they
// are readonly. When not readonly, they make good use of Backspace
// (to delete chars), and they prevent it from causing the browser
// Back operation anyhow.
// Cancel Backspace for all other HTML elements.
var strNodeName = targetNode.nodeName;
if ( (strNodeName == "INPUT" || strNodeName == "TEXTAREA")
&& !targetNode.readOnly)
{
return true;
}
else
{
return false;
}
}
/******************************************************************************
* Disable the Backspace key as a shortcut for the browser Back button,
* but allow it to delete chars in enabled text boxes.
******************************************************************************/
com.bristle.jslib.Event.disableBackspaceAsBrowserBackButton =
function()
{
// Note: This must be assigned via the onkeydown property of the document
// object, not as an HTML attribute of the HTML body element.
// Otherwise, it doesn't work in IE 6.0.2800.1106.
document.onkeydown=com.bristle.jslib.Event.isNotBackspaceAsBrowserBackButton;
}
--Fred
Last Updated: 11/8/2007
Applies to: JavaScript 1.0+
Here is the Bristle Software JavaScript library function to display a text message in the statusbar of the browser, temporarily for the specified number of seconds:
/******************************************************************************
* Show a text message in the status bar of the browser's window, optionally
* clearing it after a specified number of seconds.
* Note: In Internet Explorer IE 6.0.2800.1106, the screen is updated to show
* the status immediately. In Firefox 2.0.0.9, the screen is updated
* only after the currently running JavaScript code completes and
* control is returned to the browser. Therefore, in Firefox, this is
* not useful for showing the status of multiple steps in a single
* long-running block of JavaScript code inside a single event handler.
* Anticipated Changes:
* - Does not yet check for apostrophe chars in the message, or any other
* chars that could cause syntax errors in calling setTimeout. For now,
* the caller should double all apostrophes.
*
*@param strMessage Message to show
*@param intSeconds Number of seconds before clearing the message.
* Optional. Default: Don't clear it.
******************************************************************************/
com.bristle.jslib.Util.strStatusBarMessageToBeCleared = "";
com.bristle.jslib.Util.showStatusBarMessage =
function(strMessage, intSeconds)
{
window.status = strMessage;
if (typeof(intSeconds) != "undefined")
{
// Schedule a code snippet after the specified number of seconds
// that will clear the status bar only if the original message is
// still being shown at that time.
// Note: Prefix intSeconds with "0" and use parseInt() to guarantee
// a valid numeric value, defaulting to zero.
com.bristle.jslib.Util.strStatusBarMessageToBeCleared = strMessage;
intSeconds = parseInt("0" + intSeconds, 10);
window.setTimeout
( "if (com.bristle.jslib.Util.strStatusBarMessageToBeCleared"
+ " == '" + strMessage + "') "
+ "{ com.bristle.jslib.Util.showStatusBarMessage(''); }"
,intSeconds * 1000
,"JavaScript");
}
}
--Fred
Original Version: 3/31/2007
Last Updated: 12/10/2007
Applies to: JavaScript 1.0+, IE 5.5+, Firefox 1+, Netscape 7+,
Safari 1+
Ajax is a technique, not a programming language or any other new technology. It requires no additional software.
The Ajax technique can be used in any Web application, regardless of whether the server-side code is written in Java, .NET, Ruby on Rails, PHP, Perl, C/C++, or whatever.
The term
"Ajax" was coined in 2005 to describe a technique that some JavaScript programmers had been using since 1997 or so, and that I personally started using in 2000. It stands for "Asynchronous JavaScript and XML". The dramatic use of this technique by Google Maps in 2005, and the subsequent coining of a catchy name, has catapulted Ajax into the mainstream of Web programming.Ajax allows a Web page to go back to the server for more data without the user being aware of it, and without having to rebuild the whole web page and lose the user's context. For example, Google Maps uses it to allow you to drag a map and have new map regions scroll into view. It is typically implemented via JavaScript code using the XMLHttpRequest object (which is built into all modern browsers) to get data from the server, and then using DHTML (Dynamic HTML, which is also built into all modern browsers) to update the current Web page.
However, the name is somewhat of a misnomer because Ajax:
- Can be synchronous or asynchronous, though asynchronous is most common.
- Does not necessarily involve JavaScript. The client-side code is typically written in JavaScript, but could also be PerlScript, ActiveScript, VBScript, or many other client-side browser scripting languages.
- Does not necessarily use XML. It is commonly used to transmit XML, JSON, plain text, or any other data format.
Furthermore, Ajax is not limited to running in Web browsers. The same technique can be used to access Web services from within any application that is capable of creating and using either the standard XMLHttpRequest object or the Microsoft MSXML ActiveX object to access data from a Web service. Therefore, it can be used from:
- VBA in MS Word, Excel and PowerPoint
- LotusScript in Lotus Notes
- AutoLisp in AutoCAD
- etc.
It can also be used in your own custom client applications, written in Java, C, C++, C#, VB, etc. It can even be used in your server-side applications to access Web services on other servers.
--Fred
Original Version: 3/31/2007
Last Updated: 12/10/2007
This last reason may seem like a geeky reason, because the users don't typically care about such separation. However it really is a business-oriented reason because it allows the Ajax app to create a much better user interface. This is the reason that really causes users to demand Ajax.
There are also some truly geeky reasons to use Ajax. The users don't care about these reasons and may never understand how they contribute to the user experience, but the programmers do.
--Fred
Original Version: 3/31/2007
Last Updated: 12/10/2007
Ajax is not an all-or-nothing proposition. You don't have to abandon all of your old Web software and convert everything to Ajax. It is just one more technique in your programming bag of tricks. Take an existing app, and add a few jazzy effects via Ajax. If that works out well, begin adding more. No major re-write required.
In subsequent tips, I'll be showing details of how to use Ajax to:
--Fred
Original Version: 3/31/2007
Last Updated: 12/10/2007
For more info on Ajax, see the Ajax row of my links page:
http://bristle.com/~fred/#ajax
For a quick intro to Ajax, see my PowerPoint presentation at:
http://bristle.com/~fred/ajaxdemo/AJAXIntro.ppt
For a variety of Ajax demos see:
http://bristle.com/~fred/#ajax_demos
- A very simple demo I wrote myself
- A collection of small demos in a single page by Steve Benfield
- Google Suggest (like regular Google, but pre-fetches results)
- Google Maps (pre-fetches map data)
- Google Finance (dynamically loads stock data)
- Language translation (translate words as you type them)
- Mouse gesture as password (track the mouse motion via Ajax)
- Typing speed as password (measure time between keystrokes)
- Classified ads tied to a map
http://bristle.com/~fred/#mashups
- HousingMaps (Google Maps + CraigsList)
- MashMap (Google Maps + ShowTimes)
- Zillow (Google Maps + Real Estate)
Remember, since it is all JavaScript embedded in the Web page, you can see how they do it via View Source, as explained in my tips:
View
Source
View Source of Referenced JavaScript and CSS Files
Firefox View Live Source
--Fred
Original Version: 12/4/2007
Last Updated: 12/13/2007
Applies to: JavaScript 1.0+
Here is a simple complete example of JavaScript code that uses Ajax:
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = myHandler;
xhr.open("GET", "my_servlet", true);
xhr.send("p1=abc");
...
function myHandler()
{
if (xhr.readyState == 4)
{
doSomethingWith(xhr.responseXML);
}
else if (xhr.readyState == 3)
{
showProgressIndicator();
}
}
In subsequent tips, I'll explain what is going on here, and show a slightly different version of the first line that is required by Microsoft Internet Explorer. But, generally speaking, it really is that simple.
--Fred
Last Updated: 12/5/2007
Applies to: JavaScript 1.0+
These are all of the methods of the XMLHttpRequest object:
open ("method", "URL", [async, username, password])
Assigns destination URL, method, etc.
send (params)
Sends request including postable string or DOM object data
abort ()
Terminates current request
getAllResponseHeaders ()
Returns headers (name/value pairs) as a string
getResponseHeader ("header")
Returns value of a given header
setRequestHeader ("label","value")
Sets Request Headers before sending
--Fred
Original Version: 12/5/2007
Last Updated: 1/11/2008
Applies to: JavaScript 1.0+
These are all of the properties of the XMLHttpRequest object:
onreadystatechange
Event handler (your code) that fires at each state change
readyState
0 = uninitialized
1 = loading
2 = loaded
3 = interactive
status
HTTP
status code returned from server.
200-299 = OK
statusText
Message text to go with HTTP status code
responseText
String version of data returned from server
responseXML
XML DOM document of data returned
--Fred
Original Version: 12/12/2007
Last Updated: 1/10/2008
Applies to: JavaScript 1.0+, IE 5.5+, Firefox 1+, Netscape 7+
Generally speaking, the Ajax object is the same in all browsers.
However, there is one notable exception. Microsoft Internet Explorer does not yet support the standard way of creating the object, and requires browser-specific code. Once created, the Ajax object supports the same properties and methods in all browsers, so only the creation code has to be browser-specific.
Therefore, the Simple Ajax Example is not portable. It works fine in all browsers except Internet Explorer, where the first line must be changed to the Microsoft way of creating the Ajax object.
To simplify things, it makes sense to have a single routine that supports creation in any browser, so you can change the first line of Simple Ajax Example from:
var xhr = new XMLHttpRequest();
to something portable but equally simple like:
var xhr = com.bristle.jslib.Ajax.Util.createAjaxObject();
Here is a Bristle Software JavaScript Library routine that does just that:
/******************************************************************************
* Returns a new XMLHttpRequest object for use with Ajax operations.
*
*@throws com.bristle.jslib.Exception.intEXC_UNABLE_TO_CREATE_AJAX_OBJECT
******************************************************************************/
com.bristle.jslib.Ajax.Util.createAjaxObject =
function()
{
var xhr = null;
if (window.XMLHttpRequest)
{
// Create XMLHttpRequest object in most browsers.
xhr = new XMLHttpRequest();
}
else if (window.ActiveXObject)
{
// Create XMLHttpRequest object in Microsoft browsers.
try
{
// Newer versions of Internet Explorer
xhr = new ActiveXObject("Msxml2.XMLHTTP");
}
catch (e1)
{
try
{
// Older versions of Internet Explorer
xhr = new ActiveXObject("Microsoft.XMLHTTP");
}
catch (e2)
{
throw new com.bristle.jslib.Exception.Exception
(com.bristle.jslib.Exception.intEXC_UNABLE_TO_CREATE_AJAX_OBJECT
,"Unable to create Ajax object"
,"com.bristle.jslib.Ajax.Util.createAjaxObject"
);
}
}
}
else
{
throw new com.bristle.jslib.Exception.Exception
(com.bristle.jslib.Exception.intEXC_UNABLE_TO_CREATE_AJAX_OBJECT
,"Unable to create Ajax object"
,"com.bristle.jslib.Ajax.Util.createAjaxObject"
);
}
return xhr;
}
--Fred
Original Version: 12/12/2007
Last Updated: 12/13/2007
Applies to: JavaScript 1.0+, IE 5.5+, Firefox 1+, Netscape 7+
As shown in Simple Ajax Example, the next step after creating the Ajax object is typically to register the "onreadystatechange" callback function that will be called at various times as the Ajax object changes state from "uninitialized" to "loading" to "loaded" to "interactive" and finally to "complete". This single callback is called multiple times, once at each state change. In all browsers except Microsoft Internet Explorer, it is also called periodically as the request is being processed but remains in the "interactive" state, so that you can use it to maintain a visual progress bar or something.
Unfortunately, there is no built-in support for a timeout (if the server is too slow or never responds to the Ajax request). Also, you typically have to write standard boilerplate code inside the single callback to check the readyState property to determine each time why the callback was called, and to check the HTTP status code to see whether a completed Ajax request succeeded or failed.
It makes sense to have a single routine that supports timeouts, and supports different callbacks for different situations, with simple calls like:
com.bristle.jslib.Ajax.Util.registerAjaxCallbacksAndStartTimer
(xhr
,callbackSuccess
,callbackFailure
,10000 // Timeout in 10 seconds
,callbackTimeout
,callbackInteractive
,callbackLoaded
,callbackLoading
,callbackUninitialized);
Then Simple Ajax Example can be re-written as:
var xhr = com.bristle.jslib.Ajax.Util.createAjaxObject();
com.bristle.jslib.Ajax.Util.registerAjaxCallbacksAndStartTimer
(xhr
,doSomethingWith
,null
,null
,null
,showProgressIndicator);
xhr.open("GET", "my_servlet", true);
xhr.send("p1=abc");
Here is a Bristle Software JavaScript Library routine to do that:
/******************************************************************************
* Registers the specified functions as callbacks for the specified
* XMLHttpRequest object, and starts the timeout timer. The callback functions
* will be called asynchronously in response to the various state changes of
* the XMLHttpRequest as it processes Ajax requests.
*
* Notes:
* - Each callback is optional. Trailing callback params can be omitted
* entirely. When not omitted, each callback can be specified as null,
* or as a JavaScript function with the signature:
* function(XMLDOM, XMLHttpRequest)
* where the 1st parameter is the XML DOM Document returned by the
* responseXML property of the XMLHttpRequest, and the 2nd parameter is
* the XMLHttpRequest object itself. The 2nd parameter is optional,
* which allows callbacks to ignore the XMLHttpRequest object and simply
* operate on the XML DOM Document passed in the 1st parameter, as:
* xmlDOM.getElementsByTagName("name")[0].firstChild.nodeValue
* More advanced callbacks can use the 2nd parameter to access the other
* methods of the XMLHttpRequest object:
* abort()
* getResponseHeader()
* getAllResponseHeaders()
* etc.
* and the other properties:
* readyState
* status
* statusText
* responseText
* etc.
* These can be especially useful if the same callback is registered to
* handle multiple states, or both success and failure statuses of the
* Complete state. Or to handle non-XML responses like HTML, JSON, or plain
* text, by using responseText instead of responseXML. Also, callbacks
* other than callbackSuccess will receive a null XML DOM and may have to
* use the XMLHttpRequest parameter to determine the details of the
* HTTP progress or failure.
*
*@param xhr The XMLHttpRequest object
*@param callbackSuccess Function to be called when the state is Complete,
and the HTTP status indicates success.
*@param callbackFailure Function to be called when the state is Complete,
and the HTTP status indicates failure.
*@param intTimeoutMillisecs Number of seconds before aborting the Ajax call
* and calling callbackTimeout.
* Optional. Default=0 which means wait forever.
*@param callbackTimeout Function to be called if the Ajax call times out.
*@param callbackInteractive Function to be called when the state is Interactive
*@param callbackLoaded Function to be called when the state is Loaded
*@param callbackLoading Function to be called when the state is Loading
*@param callbackUninitialized
* Function to be called when the state is Uninitialized
*@return Handle to setTimeout() used for callbackTimeout so it can be
* canceled by the caller via clearTimeout() if necessary.
******************************************************************************/
com.bristle.jslib.Ajax.Util.AJAX_STATE_COMPLETE = 4;
com.bristle.jslib.Ajax.Util.AJAX_STATE_INTERACTIVE = 3;
com.bristle.jslib.Ajax.Util.AJAX_STATE_LOADED = 2;
com.bristle.jslib.Ajax.Util.AJAX_STATE_LOADING = 1;
com.bristle.jslib.Ajax.Util.AJAX_STATE_UNINITIALIZED = 0;
com.bristle.jslib.Ajax.Util.HTTP_STATUS_SUCCESS = 200;
com.bristle.jslib.Ajax.Util.registerAjaxCallbacksAndStartTimer =
function(xhr
,callbackSuccess
,callbackFailure
,intTimeoutMillisecs
,callbackTimeout
,callbackInteractive
,callbackLoaded
,callbackLoading
,callbackUninitialized)
{
// Schedule a call to callbackTimeout.
// Note: Use a closure (anonymous function) to keep track of which
// XMLHttpRequest to pass to callbackTimeout when there are multiple
// XMLHttpRequests instead of one global singleton.
// Note: Shouldn't really start the timer until we make the Ajax call.
// Not now, when we are just registering the callbacks. Should move
// this code to getAjaxXML(). But, the callbacks need to know what
// timer to cancel when the Ajax completes with failure or success,
// so we need a handle to the timer when creating the callbacks,
// and we don't know how get a handle to a timer without starting
// the timer. Could wrap the timer in another object that creates it
// when told and keeps a handle to it, and could then pass a handle
// to that timer to the callbacks. Maybe later. For now, this is
// sufficient, since most use is via getAjaxXML() which makes the
// Ajax call immediately after registering the callbacks.
var timer = null;
if (intTimeoutMillisecs != null
&& typeof(intTimeoutMillisecs) != "undefined"
&& intTimeoutMillisecs > 0 )
{
timer = window.setTimeout
(function()
{
if (callbackTimeout != null
&& typeof(callbackTimeout) != "undefined")
{
callbackTimeout(null, xhr);
}
xhr.abort();
}
,intTimeoutMillisecs
,"JavaScript"
);
}
// Note: Use a closure (anonymous function) to keep track of which
// XMLHttpRequest to pass to the callbacks when there are multiple
// XMLHttpRequests instead of one global singleton.
var callback = null;
xhr.onreadystatechange =
function()
{
// Determine which callback to call.
if (xhr.readyState == com.bristle.jslib.Ajax.Util.AJAX_STATE_COMPLETE)
{
// Cancel the timeout immediately. The operation has completed.
// Note: The completion may have been caused by the timer's call
// to xhr.abort(), so the timer may already have fired.
// Canceling it after it fires is not a problem.
if (timer != null)
{
window.clearTimeout(timer);
}
// Catch any errors that occur while trying to determine the
// reason for the completion of the operation. Reasons can be:
// 1. Normal completion with successful HTTP status code.
// 2. Normal completion with error HTTP status code.
// 3. Call to xhr.abort()
// Note: We could probably prevent this case, if we ever needed
// to, by changing the value of xhr.onreadystatechange to
// no longer refer to this code before calling xhr.abort().
// 4. HTTP server was already down or inaccessible when Ajax call
// was made.
// 5. HTTP server went down or became inaccessible before Ajax call
// completed normally.
// We test xhr.status to distinguish between cases 1 and 2.
// However, with Firefox 2.0.0.10, testing it in cases 3, 4, and 5
// causes an exception:
// Component returned failure code: 0x80040111
// (NS_ERROR_NOT_AVAILABLE) [nsIXMLHttpRequest.status]
// to be thrown.
try
{
if (xhr.status == com.bristle.jslib.Ajax.Util.HTTP_STATUS_SUCCESS)
{
callback = callbackSuccess;
}
else
{
callback = callbackFailure;
}
}
catch(exception)
{
// Treat exception while checking status as a failure.
// This is to act the same in browsers where such an
// exception occurs as in browsers where it does not.
// Since something has gone wrong that causes an exception
// in some browsers, other browsers would most likely not
// have found an HTTP success code, and would have treated
// this as a failure.
callback = callbackFailure;
}
}
else if (xhr.readyState == com.bristle.jslib.Ajax.Util.AJAX_STATE_INTERACTIVE)
{
callback = callbackInteractive;
}
else if (xhr.readyState == com.bristle.jslib.Ajax.Util.AJAX_STATE_LOADED)
{
callback = callbackLoaded;
}
else if (xhr.readyState == com.bristle.jslib.Ajax.Util.AJAX_STATE_LOADING)
{
callback = callbackLoading;
}
else if (xhr.readyState == com.bristle.jslib.Ajax.Util.AJAX_STATE_UNINITIALIZED)
{
callback = callbackUninitialized;
}
// Call the callback.
if (callback != null && typeof(callback) != "undefined")
{
callback(xhr.responseXML, xhr);
}
}
return timer;
}
--Fred
Original Version: 12/12/2007
Last Updated: 12/13/2007
Applies to: JavaScript 1.0+, IE 5.5+, Firefox 1+, Netscape 7+
The next step after creating the Ajax object and registering the callback function(s) is typically to call open() and send() and then wait for the callbacks to occur.
To simplify things even further, it makes sense to have a single routine that does it all, so Simple Ajax Example becomes a one-liner:
com.bristle.jslib.Ajax.Util.getAjaxXML
("my_servlet?p1=abc"
,doSomethingWith
,null
,null
,null
,showProgressIndicator);
or if you have more callbacks and a timeout:
com.bristle.jslib.Ajax.Util.getAjaxXML (my_servlet?p1=abc" ,reportSuccess ,reportFailure ,10000 // 10 second timeout ,reportTimeout );
or specifying all possible callbacks and a timeout:
com.bristle.jslib.Ajax.Util.getAjaxXML (my_servlet?p1=abc" ,reportSuccess ,reportFailure ,10000 // 10 second timeout ,reportTimeout ,reportInteractive ,reportLoaded ,reportLoading ,reportUninitialized );
Here is a Bristle Software JavaScript Library routine that does it:
/******************************************************************************
* Creates a new XMLHttpRequest object for use with Ajax operations, registers
* the specified functions as callbacks, and queues an asynchronous request to
* get XML from the specified URL. The callback functions will be called
* asynchronously in response to the various state changes of the
* XMLHttpRequest as it processes the Ajax request.
*
* Notes:
* - See registerAjaxCallbacksAndStartTimer for details of the callback
* parameters, which are all optional.
*
*@param strURL The URL to be accessed via Ajax
*@param callbackSuccess Function to be called when the state is Complete,
* and the HTTP status indicates success.
*@param callbackFailure Function to be called when the state is Complete,
* and the HTTP status indicates failure.
*@param intTimeoutMillisecs Number of seconds before aborting the Ajax call
* and calling callbackTimeout.
* Optional. Default=0 which means wait forever.
*@param callbackTimeout Function to be called if the Ajax call times out.
*@param callbackInteractive Function to be called when the state is Interactive
*@param callbackLoaded Function to be called when the state is Loaded
*@param callbackLoading Function to be called when the state is Loading
*@param callbackUninitialized
* Function to be called when the state is Uninitialized
*@throws com.bristle.jslib.Exception.intEXC_UNABLE_TO_CREATE_AJAX_OBJECT
*@throws com.bristle.jslib.Exception.intEXC_ERROR_DURING_AJAX_OPEN
*@throws com.bristle.jslib.Exception.intEXC_ERROR_DURING_AJAX_SEND
******************************************************************************/
com.bristle.jslib.Ajax.Util.getAjaxXML =
function(strURL
,callbackSuccess
,callbackFailure
,intTimeoutMillisecs
,callbackTimeout
,callbackInteractive
,callbackLoaded
,callbackLoading
,callbackUninitialized)
{
var xhr = com.bristle.jslib.Ajax.Util.createAjaxObject();
// Try block to catch all errors so the timer can be cancelled.
var timer = null;
try
{
timer = com.bristle.jslib.Ajax.Util.registerAjaxCallbacksAndStartTimer
(xhr
,callbackSuccess
,callbackFailure
,intTimeoutMillisecs
,callbackTimeout
,callbackInteractive
,callbackLoaded
,callbackLoading
,callbackUninitialized
);
// Queue the asynchronous HTTP request.
try
{
var blnASYNC = true;
xhr.open("GET", strURL, blnASYNC);
}
catch (exception)
{
throw new com.bristle.jslib.Exception.Exception
(com.bristle.jslib.Exception.intEXC_ERROR_DURING_AJAX_OPEN
,"Error during open() of Ajax object"
,"com.bristle.jslib.Ajax.Util.getAjaxXML"
);
}
try
{
xhr.send("");
}
catch (exception)
{
throw new com.bristle.jslib.Exception.Exception
(com.bristle.jslib.Exception.intEXC_ERROR_DURING_AJAX_SEND
,"Error during send() of Ajax object"
,"com.bristle.jslib.Ajax.Util.getAjaxXML"
);
}
}
catch (exception)
{
if (timer != null)
{
window.clearTimeout(timer);
}
throw exception;
}
}
--Fred
Original Version: 6/1/2008
Last Updated: 8/6/2008
Applies to: JavaScript 1.0+, IE 5.5+, Firefox 1+, Netscape 7+
Ajax is most commonly done asynchronously, so that the browser window doesn't freeze while waiting for the server. However, for those times when you prefer simplicity, and can afford to briefly freeze the browser, synchronous Ajax is even easier.
You can retrieve a string from the server, with a one-liner like:
strData = com.bristle.jslib.Ajax.Util.doSynchronousAjax
('my_servlet?p1=abc').responseText;
or to get an XML DOM that you can then manipulate:
xmlDOM = com.bristle.jslib.Ajax.Util.doSynchronousAjax
('my_servlet?p1=abc').responseXML;
or to check the success of the Ajax operation before manipulating the data:
xhr = com.bristle.jslib.Ajax.Util.doSynchronousAjax
('my_servlet?p1=abc');
if (xhr.status >= 200 && xhr.status <= 299)
{
strData = xhr.responseText;
}
else
{
alert("Ajax error: " + xhr.status + ": " + xhr.statusText);
}
Here is a Bristle Software JavaScript Library routine that does it:
/******************************************************************************
* Creates a new XMLHttpRequest object and uses it synchronously to make an
* HTTP request.
*@param strURL The URL to be accessed via Ajax
*@return The XMLHttpRequest object, so the caller can examine its properties:
* status, statusText, responseText, responseXML
*@throws com.bristle.jslib.Exception.intEXC_UNABLE_TO_CREATE_AJAX_OBJECT
*@throws com.bristle.jslib.Exception.intEXC_ERROR_DURING_AJAX_OPEN
*@throws com.bristle.jslib.Exception.intEXC_ERROR_DURING_AJAX_SEND
******************************************************************************/
com.bristle.jslib.Ajax.Util.doSynchronousAjax =
function(strURL)
{
var xhr = com.bristle.jslib.Ajax.Util.createAjaxObject();
try
{
var blnASYNC = true;
xhr.open("GET", strURL, !blnASYNC); //?? Change to POST?
}
catch (exception)
{
throw new com.bristle.jslib.Exception.Exception
(com.bristle.jslib.Exception.intEXC_ERROR_DURING_AJAX_OPEN
,"Error during open() of Ajax object"
,"com.bristle.jslib.Ajax.Util.doSynchronousAjax"
);
}
try
{
xhr.send("");
}
catch (exception)
{
throw new com.bristle.jslib.Exception.Exception
(com.bristle.jslib.Exception.intEXC_ERROR_DURING_AJAX_SEND
,"Error during send() of Ajax object"
,"com.bristle.jslib.Ajax.Util.doSynchronousAjax"
);
}
return xhr;
}
--Fred
Last Updated: 6/3/2008
Applies to: All browsers
Dynamically loading images into a Web page is even easier than loading XML or text. Simply assign a new URL to the SRC property of the IMG tag, as:
document.getElementById("img1").src = "image1.jpg";
or, if you are generating the images with a servlet:
document.getElementById("img1").src = "my_servlet?p1=abc";
This makes sense because browsers were invented in the days of slow Internet connections, where images took a long time to load, and impatient users often clicked ahead on partially loaded pages to go to the next page. Therefore, image loading has always been done in a separate thread from the main page load, and has always been dynamic. The same is true for the SRC property of the IFRAME and SCRIPT tags, so you can do pseudo-Ajax of text via them.
However, there are some "gotchas" to be aware of. See the tips below.
--Fred
Original Version: 6/3/2008
Last Updated: 6/4/2008
Applies to: All browsers
Beware browser caching when image files may change on the server, or when you are dynamically generating images with a servlet. If the new URL is identical to a URL that is already in the browser cache, the browser may reuse the old image. This can happen despite any Cache-Control HTTP header set by the servlet, and despite any non-caching options the user may have set in the browser.
To force the browser to go back to the server for a new image, include a dummy URL parameter with a unique value. Using a simple JavaScript counter may not be sufficient:
var intCounter = 1;
function myFunction()
{
document.getElementById("img1").src = "my_servlet?p1=abc"
+ "&forceReload=" + (intCounter++);
}
Even if intCounter is a global JavaScript variable, it gets reset at each page load, so you may still get duplicates. It is better (and easier) to use a random number as:
document.getElementById("img1").src = "my_servlet?p1=abc"
+ "&forceReload=" + Math.random();
However, there's still the slight chance of getting duplicate random numbers eventually, so you could use the current time in milliseconds as:
document.getElementById("img1").src = "my_servlet?p1=abc"
+ "&forceReload=" + new Date().getTime();
However, there's still the slight chance of generating multiple
URLs in the same millisecond, and JavaScript doesn't yet have an equivalent to
Java's System.nanoTime(). Besides, some day computers will be fast enough that you'd
need picoTime(), or femtoTime(), or attoTime(), or zeptoTime(), or
yoctoTime(), or...
In cases where it really matters, it's probably best to use both,
since you're VERY unlikely to get the same random number twice in the same
millisecond:
document.getElementById("img1").src = "my_servlet?p1=abc"
+ "&forceReload=" + Math.random()
+ "_" + new Date().getTime();
Personally, I wrap it all up and make it self-documenting as:
document.getElementById("img1").src = "my_servlet?p1=abc"
+ com.bristle.jslib.Ajax.Util.getForceReloadParam();
Here is the Bristle Software JavaScript Library routine to generate the random URL param:
/******************************************************************************
* Get a unique URL parameter to force a reload, bypassing any cached pages.
******************************************************************************/
com.bristle.jslib.Ajax.Util.getForceReloadParam =
function()
{
// Generate a unique URL param to force a reload, bypassing the browser
// cache. This is especially useful for loading images via the SRC
// property of the IMG tag because otherwise, depending on the browser
// and the user settings, the previously cached image may be re-used,
// despite any Cache-Control header that may be set.
// Note: Use a timestamp to avoid random duplicates.
// Note: Use a random number in case of multiple calls within the same
// millisecond.
// Note: Use getTime(), not getMillseconds() which returns a value 0-999.
intRandom = com.bristle.jslib.Util.randomInt(1,1000000);
return "&forceReload=" + intRandom + "_" + new Date().getTime();
}
and the Bristle Software JavaScript Library routine that it calls internally:
/******************************************************************************
* Generate a random integer between the specified min and max.
*
*@param intMin Min acceptable random integer (inclusive)
*@param intMax Max acceptable random integer (inclusive)
*@return The random number
******************************************************************************/
com.bristle.jslib.Util.randomInt =
function(intMin, intMax)
{
if ((typeof(intMin) == "undefined") || (intMin == null))
{
intMin = 0;
}
if ((typeof(intMax) == "undefined") || (intMax == null))
{
intMax = Math.pow(2, 16) - 1;
}
// Example with min 6 and max 10:
var intRangeSize = intMax - intMin + 1; // Range size = 5
var intRandom = Math.random(); // Between 0 and 0.999...
intRandom *= intRangeSize; // Between 0 and 4.999...
intRandom = Math.floor(intRandom); // Between 0 and 4
intRandom += intMin; // Between 6 and 10
return intRandom;
}
Thanks to John Ferguson and Matt Brophy for pushing me to bother including the timestamp, not just a random number!
--Fred
Last Updated: 6/3/2008
Applies to: All browsers
To avoid mysterious page re-loads and mysterious execution of server side code, don't use the empty string ("" or '') as the value of SRC. Instead, use the URL of an invisible GIF file, like this one, which you can right-click to download: invisible.gif.
If you are planning to dynamically update the URL of an image immediately, you may not care what value is specified in the static HTML and may be tempted to specify the empty string as:
<img id='img1' src=''>
Bad idea! Use an invisible GIF instead, as:
<img id='img1' src='invisible.gif'>
Similarly, if you want to dynamically clear the image, you may be tempted to specify the empty string as:
document.getElementById("img1").src = "";
Don't do it! Use an invisible GIF instead, as:
document.getElementById("img1").src = "invisible.gif";
or better yet, dynamically set the IMG to invisible, as described in Hiding/showing HTML elements.
Why? 2 reasons:
In standards-compliant browsers like Firefox, the empty string maps to the URL (minus POST params) of the current page. In non-standards-compliant browsers like Internet Explorer, it maps to the directory portion of the URL of the current page, which the server probably maps to a default page or servlet. In either case, there are two problems:
For more details, see Section 13.2 "Including an image:
the IMG element" of the HTML spec:
http://www.w3.org/TR/html401/struct/objects.html#h-13.2
which defines the value of SRC as a standard URI, and
Section 4.4 "Same-Document Reference" of the URI spec:
http://tools.ietf.org/html/rfc3986#section-4.4
which says that an empty string URI refers to the URI of the
enclosing base document.
--Fred
Last Updated: 12/13/2007
Applies to: JavaScript 1.0+, IE 5.5+, Firefox 1+, Netscape 7+
OK. So now, you're writing very sophisticated Web apps, using Ajax to go back to the server as needed, and presenting a very responsive and dynamic user interface. You've put a large chunk of your application into a single Web page that interacts extensively with the user and collects lots of data. But...
What happens if the user enters a bunch of data and then goes to lunch for an hour without saving first? Or spends a long time interacting with the fancy JavaScript parts of your app that collect data but don't need the server and so don't use Ajax? When the user finally clicks Save or OK, it fails because the server session has timed out. You've actually made things worse by taking the load off of the server and doing more locally. How to prevent the timeouts?
You could simply make the timeouts much longer, or disable them entirely. However, the timeouts exist for a good reason. They allow the server to discard the sessions of users who really are done with your app, and have closed the browser or gone to another site. Without the timeouts, your server would waste lots of resources, and eventually crash for lack of memory. Better to leave the timeouts in place.
A better solution is to keep the sessions alive only for users who still have a Web page of your app open. You can do this easily with a timer doing Ajax "keep-alive" operations to the server. Ideally, you could just add a one-liner like the following to the onload event handler of each of your Web pages:
com.bristle.jslib.Ajax.Util.schedulePeriodicKeepAlive
("my_keep_alive_servlet"
,300000 // Every 5 minutes
);
This would invoke your servlet every 5 minutes, ignoring any data it sent back, as well as ignoring whether it succeeded, failed, or timed out. That's all you really need to prevent the session timeout.
If you also want to process the returned data, detect Ajax failures and timeouts, warn the user that the server (or your communications to it) seems to be down temporarily, etc., you need additional callbacks to handle that, and might use:
com.bristle.jslib.Ajax.Util.schedulePeriodicKeepAlive
("my_keep_alive_servlet"
,300000 // Send keep-alive every 5 minutes
,!com.bristle.jslib.Ajax.Util.blnDO_FIRST_KEEP_ALIVE_IMMEDIATELY
,callbackSuccess
,callbackFailure
,30000 // Timeout each keep-alive after 30 seconds
,callbackTimeout
);
Here is a Bristle Software JavaScript Library routine that does it:
/******************************************************************************
* Use Ajax operations to access the specified URL periodically to keep the
* connection to the server from timing out. The specified callback functions
* will be called asynchronously when the Ajax operation succeeds or fails.
*
* Notes:
* - See registerAjaxCallbacksAndStartTimer for details of the callback
* parameters, which are all optional.
*
*@param strURL The URL to be accessed via Ajax
*@param intMillisecs Number of milliseconds to wait between accesses.
*@param blnNow Access the URL now, before the first delay, if true.
*@param callbackSuccess Function to be called when the state is Complete,
* and the HTTP status indicates success.
*@param callbackFailure Function to be called when the state is Complete,
* and the HTTP status indicates failure.
*@param intTimeoutMillisecs
* Number of seconds before aborting the Ajax call
* and calling callbackTimeout.
* Optional. Default=0 which means wait forever.
*@param callbackTimeout Function to be called if the Ajax call times out.
******************************************************************************/
com.bristle.jslib.Ajax.Util.blnDO_FIRST_KEEP_ALIVE_IMMEDIATELY = true;
com.bristle.jslib.Ajax.Util.schedulePeriodicKeepAlive =
function(strURL
,intMillisecs
,blnNow
,callbackSuccess
,callbackFailure
,intTimeoutMillisecs
,callbackTimeout
)
{
// Schedule the next call to this function via setTimeout.
// Note: Can get setTimeout to pass the arguments on the scheduled call,
// without having to stringize them into a call string, by creating
// an anonymous function (closure) that calls the desired function
// passing the arguments, which are known via the closure at the
// time the anonymous function is generated.
var timer = window.setTimeout
(function()
{
com.bristle.jslib.Ajax.Util.schedulePeriodicKeepAlive
(strURL
,intMillisecs
,true
,callbackSuccess
,callbackFailure
,intTimeoutMillisecs
,callbackTimeout
);
}
,intMillisecs
,"JavaScript"
);
if (blnNow)
{
try
{
com.bristle.jslib.Ajax.Util.getAjaxXML
(strURL
,callbackSuccess
,callbackFailure
,intTimeoutMillisecs
,callbackTimeout
);
}
catch(exception)
{
// Report error to the user.
// Note: Seems like a bad idea for this library routine to show an
// error to the user, instead of just throwing it to the
// caller. However, after the first call has scheduled
// further calls here via setTimeout(), there is no caller
// to throw to. Any ideas?
// Factors that make this more acceptable:
// - This will only happen after the user has been idle for
// the full keep-alive timeout period, at which point he
// might well want a warning that he is approaching the
// server timeout.
// - This only happens when we are unable to create the Ajax
// object, or when an error occurs on the call to open() or
// send() to queue an asynchronous HTTP transaction.
// It does not occur if an error occurs during the HTTP
// transaction. That type of error is detected via the
// status field of the Ajax callback. Therefore, this
// error is not caused by the unavailability of the server.
// Only by the existence of support for the Ajax object in
// the current browser, the validity of the URL and its
// params, etc., including attempts to use a URL in a
// different domain than the current web app. Such errors
// should be found and resolved by developers, not end
// users.
// Note: The following specific errors may occur:
// - com.bristle.jslib.Exception.intEXC_UNABLE_TO_CREATE_AJAX_OBJECT
// - When unable to even create the Ajax object, because it
// is not supported by this browser, or because the user
// disallowed it.
// - com.bristle.jslib.Exception.intEXC_ERROR_DURING_AJAX_OPEN
// - When there's a problem with the URL, including the
// cross-domain security error. In that case, Firefox
// always throws the error, and IE only throws it if the
// user answers Yes to the popup:
// "This page is accessing information that is not
// under its control. This poses a security risk.
// Do you want to continue?"
// - com.bristle.jslib.Exception.intEXC_ERROR_DURING_AJAX_SEND
// - When there's a problem with the URL parameters.
com.bristle.jslib.MsgBox.reportError
("An error occurred in the Ajax object used for the keep-alive"
+ " function to URL " + strURL
+ "<br />"
+ "This browser does not support Ajax, or you have set an"
+ " option to disable it. Therefore, nothing"
+ " will prevent the web server from timing out if"
+ " you are idle long enough."
,exception
,com.bristle.jslib.MsgBox.blnMSGBOX_SET_FOCUS
);
// Cancel further keep-alive attempts if we couldn't even create
// the Ajax object, and send the request. This is not a problem
// with the server; it is a problem with doing Ajax at all in this
// browser. Otherwise, errors will keep popping up on each
// attempt.
//?? Also set a page variable that will go back at the next normal
//?? (non-Ajax) submit to set a flag in the server session to not
//?? do any more timeouts, or even no more Ajax, for the rest of
//?? the session.
window.clearTimeout(timer);
}
}
}
--Fred
Last Updated: 12/5/2007
Applies to: JavaScript 1.0+
Coming soon...
--Fred
Last Updated: 12/5/2007
Applies to: JavaScript 1.0+
Coming soon...
--Fred
Last Updated: 12/5/2007
Applies to: JavaScript 1.0+
Coming soon...
--Fred
Last Updated: 12/18/2007
Applies to: JavaScript 1.0+
Coming soon...
--Fred
Original Version:
5/26/2007
Last Updated: 6/29/2007
Applies to: JavaScript 1.0+
One of the simplest tools for understanding and debugging Web pages and Web apps is the "View Source" capability built into all Web browsers. It is usually available from the right click menu and from the View menu of the main menu bar. It shows the HTML, JavaScript and CSS source code for the current page. This is useful in a couple of situations:
To learn HTML, JavaScript and CSS techniques.
Since View Source shows the HTML, JavaScript and CSS source code, you can use it as learning tool. Find a page somewhere on the Web that does what you are trying to do. Use View Source to see how it does it, and use the same technique in your own code. Be careful not to abuse copyright protections.
Be aware that other people all over the world can do this with your Web pages, so be sure to add copyright notices to your HTML, JavaScript and CSS code as appropriate. Also, do not reveal any critical security info, sensitive customer info, trade secrets, etc. in such code and comments. All such info should be wrapped inside the JSP or Java servlet code or comments, or better yet, in the Java beans and business objects accessed by the JSP and servlets.
View Source doesn't show your JSP and servlet code, which executes on the server, but it does show the output of that code.
If you use lots of JSP @include directives (see http://bristle.com/Tips/Java.htm#jsp_directives) or jsp:forward, jsp:include, and jsp:invoke actions (see http://bristle.com/Tips/Java.htm#jsp_and_jstl_actions), View Source is a useful way to see the actual "flattened" output that results from all of the including, invoking, and forwarding. Also, if you have complex JSP or servlet logic to determine what HTML, JavaScript and CSS to generate, View Source is a useful way to see the results of that logic.
Keep in mind that JSP comments and Java servlet comments are not sent to the browser, and so are not visible in View Source, but HTML, JavaScript and CSS comments are. Therefore, don't put information into HTML, JavaScript and CSS comments that you don't want the end user to be able to see by doing a View Source. Do put information into them for temporary debugging purposes (a technique similar to printing to standard output). Also, do put information into them (permanently, even in production code) that you don't want to show on the HTML page, but that might be useful to you for debugging production code, as long as you don't mind the occasional inquisitive user being able to peek at it.
If your latest changes to the JSP, HTML, JavaScript or CSS code have had no effect, view the source to see whether you are still using an old cached version of the file without those changes.
The browser caches files to avoid going to the server for the same page over and over, but you can force it to get the latest file by clicking the Refresh or Reload button, especially if you hold down the Shift key while doing so. You can also explicitly delete the contents of the browser cache. In some browsers, you can change a user preference that controls how often the browser checks for newer versions of files it has cached.
The server may also cache pages, especially pages generated by JSP or servlets. You may need to set the server to check more aggressively for updated JSP source files, recompiling them into Java source files and then into Java class files. Also, to check more aggressively for updated Java class files, reloading them, instead of using the copy already loaded into memory. For tips on how to do this, see:
http://bristle.com/Tips/Tomcat.htm#enable_servlet_reload
http://bristle.com/Tips/Tomcat.htm#enable_default_servlet_reload
http://bristle.com/Tips/Tomcat.htm#servlet_reload_frequency
http://bristle.com/Tips/WebLogic.htm#servlet_reload_frequency
http://bristle.com/Tips/WebLogic.htm#jsp_reload_frequency
You may also need to specify in your JSP and servlet code that the browser should not cache the generated pages. For tips on how to do this, see:
http://www.mnot.net/cache_docs/
In any case, View Source is an easy way to detect caching because you can see the HTML, JavaScript, and CSS source code and notice that your changes are missing.
--Fred
Last Updated: 5/26/2007
Applies to: JavaScript 1.0+
View Source can show you not only the source of the main HTML page, but also the source of any referenced JavaScript and CSS pages.
Embedded in the code shown by View Source, you may see lines like:
<script
language='JavaScript' src='js/com.bristle.jslib.Util.js'></script>
<link rel='stylesheet'
type='text/css' href='css/com_bristle_jslib_css_Styles.css'>
These are references to JavaScript and CSS source files. If you copy the relative pathnames of the files:
js/com.bristle.jslib.Util.js
css/com_bristle_jslib_css_Styles.css
and paste them into the address bar of your browser in place of the main HTML or JSP file name, you can directly access the referenced file and then do a View Source on it. For example, if the main URL shown in the address bar is:
http://bristle.com/blah/blah/file1.htm
Change it to one of:
http://bristle.com/blah/blah/js/com.bristle.jslib.Util.js
http://bristle.com/blah/blah/css/com_bristle_jslib_css_Styles.css
Again, be careful not to abuse copyright protections, and since other people can do this with your Web pages, be sure to protect your own code as appropriate.
--Fred
Original Version: 4/9/2007
Last Updated: 9/13/2009
Applies to: Firefox 1.0+
You may have noticed that View Source in a browser shows only the static HTML source that was sent from the Web server. To see the "live source" that reflects any changes you made via DHTML (Dynamic HTML), in Firefox:
Note that the "live source" represents the HTML DOM managed internally by Firefox, in its canonical form, not necessarily the textual form of the static page with your dynamic changes. This is useful because the canonical form is an accurate depiction of what Firefox is actually rendering, so it is good for understanding the appearance and behavior of the rendered page. However, it may differ from what you expect in the following ways:
Thanks to Erin Mulder for "tipping me off" to "View Selection
Source", and to Tim Stone for pointing out that it is
canonicalized!
BTW, if you're not already using the Firefox web browser, give it a try.
It's a free download from:
http://www.mozilla.org/
For tips on using Firefox, see:
http://bristle.com/Tips/Internet.htm#mozilla_firefox
Thanks to Jeff LoBello for pointing out that you can view the live source in Internet Explorer via the DHTML Spy add-on, available free for non-commercial use at:
--Fred
Last Updated: 3/31/2007
Applies to: JavaScript 1.0+
The following are good sources of info about JavaScript:
--Fred
©Copyright 2007-2009, Bristle Software, Inc. All rights reserved.