TeleFlowRelay AJAX Tutorial

TeleFlowRelay AJAX Tutorial

From TeleFlow

(Difference between revisions)
Jump to: navigation, search
Line 1: Line 1:
-
This page provides an overview and instructions on how to build an AJAX application that will add addition components (such as buttons, or other clickable objects) to the web page, and have them operate without the need to refresh the page. This tutorial references the [[TeleFlowRelay]] web foundation, which includes an application that incorporates jQuery to provide cloning.
+
This page provides an overview and instructions on how to build an AJAX application that will add additional components (such as buttons, or other clickable objects) to the web page, and have them operate without the need to refresh. This tutorial references the [[TeleFlowRelay]] web foundation, which includes an application that incorporates jQuery to provide cloning.
* To help us improve this tutorial, and support for the TeleFlowRelay Web Foundation, please visit our [http://teleflow.org/phpbb/viewforum.php?f=25 Forum]
* To help us improve this tutorial, and support for the TeleFlowRelay Web Foundation, please visit our [http://teleflow.org/phpbb/viewforum.php?f=25 Forum]

Revision as of 19:20, 22 April 2010

This page provides an overview and instructions on how to build an AJAX application that will add additional components (such as buttons, or other clickable objects) to the web page, and have them operate without the need to refresh. This tutorial references the TeleFlowRelay web foundation, which includes an application that incorporates jQuery to provide cloning.

  • To help us improve this tutorial, and support for the TeleFlowRelay Web Foundation, please visit our Forum
  • This AJAX tutorial references a Bar Code graphics creation page. To see what you will be working with before you get started, take a look at a screen shot first. All this code is included in the download found in the Forum.

submit to reddit

Contents

How to Develop and AJAX / XML Web Application

This entry describes the step by step process you will need to work through to develop an AJAX (Asynchronous JavaScript and XML) / XML web page. Essentially, there are three parts to an AJAX web application:

  • HTML web page client with JavaScript - Note: PHP will create and combine the standard page layout at the server, and then provide it to the brower client
  • JavaScript to communicate between the web client and a server PHP application (For our demo set of files, they are found in directories named as '*_xml.php')
  • A PHP XML processing application (mentioned above) which accepts XML from JavaScript running in the browser client, communicates to the database server, and returns XML for processing by JavaScript on the client.

In effect, and using software such as jQuery JavaScript library, pages can be dynamically modified based on XML data returning from PHP code. Warning: This is not easy to code! If you haven't done this before, expect to spend a week just learning how it all fits together before getting very far. However, once you have a feel for it, and have mastered knowing when to reference helper Apps like FireBug, you will be moving forward much sooner.

Please note that this document relates to files created using the "engenic_app_root" files. We call it that, because the web applications build by engenic are all found at this root directory.

What Is AJAX

AJAX is a term applied to a style of development for web applications. AJAX stands for Asynchronous JavaScript and XML, and is the cornerstone of Web 2.0 development. The core concept is that any web page that uses Web 2.0 capabilities can dynamically build content potentially running on several threads.

AJAX is essentially provided by having the browser (or client) running JavaScript being constantly in contact with web server, which is processing with PHP. This client/server relationship then uses XML to express information and requests for it back and forth.

Standard Configuration

Although there are several variations on deploying AJAX applications, a general approach and the one used for this document is:

  • JavaScript and FireFox (with FireBug for debugging) and all browsers supported at end
  • PHP for deploying HTML (which includes links to JavaScript and CSS) to the browser
  • PHP accepting and processing XML for AJAX (JavaScript communications)

The files are being served by Apache running PHP, but could just as easily be deployed in IIS running PHP. The database used is MySQL. If you are not familiar with installations of web services, you might want to use XAMPP (Windows or Linux) available from

  • XAMPP: www.apachefriends.org/en/xampp.html

The site uses examples from a simple Lab Results input screen from our MedicalRelay product. There is also the Barcodes generator website included in the TeleFlowRelay download which includes a set of example files you can deploy on your web server to modify to see how it all works.

Tools You Need

Libraries

  • engenic platform ("engenic_app_root"): This is the software that manages most of engenic's web developments, as well as other company's applications including MedicalRelay, Cheap UPC Barcodes, and others.
  • jQuery: This is embedded in the "engenic_app_root" code, however, if you are building a stand alone system, you might want to include this library directly in your code.
  • jQuery Addons: jQuery.TimeEntry, jQuery.Timer, jQuery.MaskedInput, jQuery.DatePicker and others. Note that these are included in the engenic_root_app.

Please note that TeleFlowRelay Web Foundation installs all these components. For information on how to install this environment, refer to TeleFlowRelay.

When it comes to JavaScript libraries, ensure that they are visible to the browser. By that, you will need to place these files in a location on the website above the "htdocs" folder in order for the website to serve them by request. PHP libraries on the other hand, because they are only accessed by the server and not the browser, may be located anywhere on the drive. It can be helpful to address security concerns by having key files located below the reach of the browser.

Open Source Helper Applications

We recommend that you have these programs installed on both your development computer. Each runs in Windows perfectly, and very well in Linux using Wine.

  • TFHTTPXMLPost.exe - find this at http://teleflow.org, and put it in the *_xml directory for the application you are building in. This tool was created by engenic to quickly test XML document posts, and is a lifesaver.
  • NotePad++: This is a great editor, but anything that can express and/or highlight tag pairs will work.
  • FireFox with FireBug: Essential. Without this product, you will not get far along at any rate. FireBug is what makes it possible to debug AJAX.
  • HeidiSQL: A great MySQL database interface. Highly recommended.
  • Documentation tool: Something you can use to make notes which will then be entered into a wiki, or other documentation formation.

As mentioned, if you do not have a local (that is, on your computer) installation of Apache/PHP, you might want to consider downloading and installing:

  • XAMPP

It's best to have the GUI applications running as you will be jumping back and forth between them frequently.

Formulating in XML and Its Advantages

When developing the code for sending a Request that will receive a Response, make sure that the Responses are designed to be as identical as possible! This effort includes, and where possible, providing the full set of fields, and the names of the elements. This way, you can reuse, via procedure calls, good portions of JavaScript where you pass XML structures back to from jQuery AJAX calls.

XML was used in AJAX because it provides a standard means of expressing data which can then be readily broken down and processed by the environment receiving it. Advantages of using XML include:

  • It replaces direct including SQL statements to databases from within JavaScript which provides a more secure environment if done properly. For additional information on a famous security breach of older websites, look up "SQL Injection Attack".
  • Ease of manipulation of data for processing against a database or other information source at the server end, and then also for reporting back on the JavaScript / browser end. Using XML helps bridge the two programming environments to a degree.
  • Re-usability comes with a general standard for accessing information. For example, the XML Request and Response structures can be readily used by other non-web environments including TeleFlow Speech Server applications, or compiled code applications build in C++/C#,Delphi,VisualBasic and other environments.
  • Layered development using XML to access data also means that should your data source need to shift from MySQL to SQL Server, you will only need to changes the data structures and means of access in the "*_xml.php" files. This provides your application with a level of future proofing.
  • A single web page for your entire application can be achieved using XML to command and guide changes by. Because XML has become a strong standard, you will be able to readily adopt data from other XML information sources at the server end, and then combine it for re-display in JavaScript. Building rich websites becomes easier when thinking in XML.

JSON Option

XML is not dead simple. In fact, it's awkward to work in. For that reason, a simplified version of it has been developed, and its called JSON. With it, you can potentially save some manipulation effort. It's something worth mentioning, but not essential. XML, after working with it for a few days does become easier as time goes on.


Example Applications

Lab Results

Lab Results is a simple edit entry screen which allows you to add entries manually. It is useful because it also include a calendar and review. It relies on the following files

  • eng_medicalrelay/mr_labresults-entry.php - The entry screen
  • eng_medicalrelay_xml/xml_medicalcmd.php - The program that handles the PHP

For the Bar Code web pages, the equivalent files are

  • bc_infoweb/bc_index.php
  • bc_infoweb/bc_ajax_barcodes_commands.php

When you first get started, use the TFHTTPXMLPost.exe program to test that the "hello" function is working. Copy this command:

<REQUEST action="Hello" seconds="2"></REQUEST>

into the program with the location of the file for your development environment

http://localhost/engenic_app_root/eng_medicalrelay_xml/xml_medicalcmd.php

or

http://localhost/engenic_app_root/bc_infoweb/bc_ajax_barcodes_commands.php

On pressing the "Post" button, you should see the following result:

<?xml version="1.0" encoding="ISO-8859-1"?><RESPONSE action="Hello"><SUCCESS action="Hello" result="SUCCESS" type="DB">Hi 
back: Database is up. We are here to serve</SUCCESS></RESPONSE>

This should be your starting point. In fact, you can test for this when you start your program running to make certain that the environment is even available. See the lab results entry program for an example.


Barcode Generator

The Barcode Generator is a class provided by:

  • Jean-Sebastien Goupil

It can be downloaded from barcodephp.com, however we have also included the class in the TFR installation in order to run the examples which are be provided in this tutorial.

Configuration

If you are using the engenic_root_app code set, and before you start running tests, and creating routines to start plugging data into databases, ensure that your database connections are set correctly.

  • LocalSettings.php - check the ($cGlobalMode = MODE_TEST; and $cGlobalDebug = TRUE;) near the top, and compare in:
  • LocalSettings_Database.php

Useful Variables

If you are using the engenic_app_root's security mechanism, then you might want to gather security information for use in your code to limit access for the session. This useful for XML commands etc.

$ClientID = $_SESSION['userclientid'];
$UserID   = $_SESSION['userclientid']; 
$UserName = $_SESSION['username'];


Relative Files Names

For the purposes of this document, the following files relate to the names.

  • Input screen:
    • mr_labresults-entry.php
    • bc_index.php (barcode.php)
  • AJAX manager or processing application:
    • mr_ajax_labresults_commands.php
    • bc_ajax_barcodes_commands.php


Layout

Your system will comprise of reading writing data, and then building and extracting data to and from XML, in both PHP and JavaScript. Most likely, this data will be derived from a database, but it could come from other sources including additional XML request and response applications. Once you have built the database, you will need to display and edit using HTML. Because of this, you will need skills in:

  • PHP
  • SQL for MySQL (or other database)
  • JavaScript
  • jQuery, an extension build on JavaScript
  • XML (and/or JSON)
  • HTML and Web design
  • CSS

In fact, you should probably not be creating much HTML initially, just the minimum to display the layout, and data. Much of your HTML experience should be limited, and you following using classes so that you can change the look and feel in CSS. This is all incredibly annoying stuff, and may even be hazardous to your health and the general well being of those around you.

If you are looking for a great resource to understand some of the above concepts better, check out:

Website Design

Before getting too far, try and determine how you are going to layout the DIVS, or sections on the page.

  • Draw on paper, the names of your DIVs, or boxes that will hold functionality
  • Name their SPANs, so that you can refer to them in code
    • labresults_reportdate_box
    • labresults_report_box
    • labresults_dataentry_box
    • labresults_search_box

The barcode named DIVS in the barcode example are apparent.

Cloning Introduction

This is a brief description of cloning ahead of the section where it is applied provided to give you a sense of its need.

When you load a page in a browser, the JavaScript also loaded is made aware of all the interactive objects (buttons, fields, check boxes etc) just once at the end of the load. This means that the screen need to have all the objects defined in order for events to be recognized for each of them.

If you do not know how many objects you are going to display, then you have to create x number for a "hopefully" worst case scenario. This is plainly messy.

Immediately, you might think, that you can just add more HTML to the browser at any point after the page load. For example, if you wanted to add a button to get information on each client (id: 8487 in example) displayed, you might add this code to a row or DIV:

<input id="ClientButton_8487" class="infoButton" value="Client Info" />

However, by doing this, JavaScript will not be able to recognise events for it. Clicking on the button will not, in this case, fire the "onClick" event so that jQuery can act on the request. The way to get around this is to create a button, or object with the events assigned to it at load time, set it as hidden, and then clone and display it each time it is required.

To approach cloning, you may need to create clone DIVs and/or tables "rows" which are used to dynamically create new content to be inserted into your DIV blocks. Clone DIVs and/or tables "rows" are used so that jQuery can interact with them to populate new data to the browser. Nice effects can be applied such as "fadeIn" which is available in jQuery.

  • labresults_report_entry
    • Note: You will clone these to labresults_report_entry_clone. That way, you will not accidentally remove the original.

Once you have completed the clone DIVs, you will need to define the containers in which they will be created.

  • labresults_report_container
    • eg: <table id="labresults_report_container"...

Ensure any SPANS are properly nested in the DIVs. This is really an issue of aesthetics. Keep in mind that DIV in DIVs with a common class name will enable you to change the look en-mass, but that you are going to want to class each block with different names so that you can move them around the webpage at the final part of the development effort.

See examples in Barcode Generate where both types of cloning are applied.

Development Effort

As you are going to need to underpinning all your display with XML requests, you may as well define those first, and then build them. This is probably the hardest part, as it requires planning, should be well described and then very carefully developed. Having said that, once you build one XML Request and Responses, the rest becomes much easier. Just be mindful before you start out that you are covering every data read, write, and error possibility for your page. It's best not try and get away with writing just enough to get started, as you will be wasting time later having to go back again and again to build new sections. Having said that, your first project should only have one or two reads and a write, so that you get somewhere fast and see results.

Planning and building the XML request and response structure has many benefits:

  • You determine your field naming conventions early, so problems are quickly found.
  • Create the database tables, so that naming conventions are succinct.
  • You build code relatively quickly once you get started.
  • You have the underpinnings to test data flow with once completed.
  • XML statements can immediately also be used by TeleFlow server, or other environments.
  • You are starting the technical documentation (if you follow the example described here)

Make certain when you develop these routines that:

  • You make the Primary Key (PK) available everywhere. Please see "database normalization" for more information.
  • Ensure that client rules are enforced so that one client doesn't accidentally kill another's data. By client, we mean the users of your system. This oversight is common.
  • Also, highly recommended, is a mechanism to create all your tables and indexes within the XML processor if they do not already exists. This helps for quick deployment of applications to other servers, and keeps the database documentation close by.
    • Note: Review xml_medicalcmd_functions.php for an example of how to create tables.

XML Commands

Start your development effort by creating all the commands. For example, Lab Results use the following commands

CreateDatabase
<REQUEST action="CreateDatabase"></REQUEST>

Returns:

<RESPONSE action="CreateDatabase"><SUCCESS action="CreateDatabase" 
result="SUCCESS" type="DB">Tables exist or have been created</SUCCESS></RESPONSE>

Using the Lab Results example, the following requests where developed ahead of time:

ListLabResultsForDate
<REQUEST action="ListLabResultsForDate"
client_id="376"
entry_date="2009/01/19"></REQUEST>

Returns:

<RESPONSE action="ListLabResultsForDate" result="SUCCESS" type="DB"
client_id="1234" entry_date="2009/01/19"
labresultstotal="2">
<LABRESULT count="1" labresult_id="4" pat_id="" pat_id_human="N/A" 
pat_first="Bob" pat_last="Best"  pat_tele="6045550310" pat_tele_human="(604) 555-0310" 
phy_first="Bill" phy_last="Simpson" lab_result="NORMAL" 
lab_call_status="ADD">NORMAL</LABRESULT>
<LABRESULT count="2" labresult_id="5" pat_id="" pat_id_human="N/A" 
pat_first="Sally" pat_last="Mann" pat_tele="6045551110" pat_tele_human="(604) 555-1110" 
phy_first="Bill" phy_last="Simpson" lab_result="NORMAL" 
lab_call_status="ADD">NORMAL</LABRESULT>
</RESPONSE>
AddLabResult
<REQUEST action="AddLabResult"
client_id="1234"
pat_id="991"
pat_first="Janis"
pat_last="Zalcron"
pat_tele="6045551234"
phy_first="Paul"
phy_last="Simmons"
lab_result="NORMAL"></REQUEST>

Returns:

<RESPONSE action="AddLabResult" result="SUCCESS" type="DB">
<LABRESULT 
labresult_id="17" pat_id="991" 
pat_first="Janis" pat_last="Zalcron" 
pat_tele="6045551234" pat_tele_human="(604) 555-1234" phy_first="" 
phy_last="Simmons" lab_result="NORMAL" lab_call_status="ADD">
</LABRESULT></RESPONSE>


UpdateLabResult
<REQUEST action="UpdateLabResult"
client_id="1234"
labresult_id="1633"
pat_id="991"
pat_first="Janis"
pat_last="Zalcron"
pat_tele="6045559999"
phy_first="Paul"
phy_last="Simmons"
lab_result="NORMAL | CONTACT"></REQUEST>

Returns:

<RESPONSE action="UpdateLabResult" result="SUCCESS" type="DB"><SUCCESS 
labresult_id="16"></SUCCESS></RESPONSE>

or

<RESPONSE action="UpdateLabResult" result="ERROR" type="DB"><ERROR 
labresult_id="1633"></ERROR>Record not found</RESPONSE>

or

<RESPONSE action="UpdateLabResult" result="ERROR" type="CMD">Mandetory parameters 
are missing</RESPONSE>


DeleteLabResult
<REQUEST action="DeleteLabResult"
client_id="376"
labresult_id="888"></REQUEST>

Returns:

Returns:

<RESPONSE action="DeleteLabResult" result="SUCCESS" type="DB"><SUCCESS 
labresult_id="5"></SUCCESS></RESPONSE>

or

<RESPONSE action="DeleteLabResult" result="ERROR" type="DB"><ERROR 
labresult_id="888"></ERROR>Record not found</RESPONSE>


HTML & JavaScript Development

This is a bit of an open ended statement, but at this point, the web layout and JavaScript development to read and effect change on the web page need to be implemented. Keep the following in mind when writing the application:

  • Use class names for everything so that the CSS can be worked on independently or at a later date when you are wearing your artistic hat, rather than the engineering hat you need for JavaScript.
  • Minimize the use of HTML. If you are trying to do things like bolding text, adding colors, or changing font sizes, then stop immediately, because you are doing it wrong. The screen should only have input elements, basic text, and possibly tables just because its important to see the screen doing something while you are working on it. At this point, you should not be overly worried about layout. That should come once you get to the CSS part near the end.
  • Wrap DIV tags around blocks of input or edits. DIV tags enable you to move around sections by defining them in CSS later.


Implementing AJAX

This is the tough part to follow initially, but will be easier to follow once the concept settles in. Essentially, this is what needs to happen:

  • User types in or clicks on something creating a jQuery event.
  • The event is handled in JavaScript, and Requests data from a PHP appliciaton on the server
  • A POST request is created with parameters within JavaScript using the jQuery AJAX command, and posted to a PHP server page
  • The server page reads all the post items, and formulates an XML document which is then processed later in the server page
    • Note: This has been done to ensure that all requests are posted in XML. There are many reasons for this including extensibilty for use by other services.
  • The server page does work with the database or other sources, and then compiles an XML response.
  • That response is then returned to the JavaScript jQuery AJAX command
  • The XML document is then parsed, and new information is displayed in a DIV, cloned DIV, or cloned table row on the screen. This becomes the dynamic data.

This is a lot of movement of data. For that reason, you will make a tonne of mistakes. The best thing to do is the keep the TFHTTPPost tester program available, and make sure you have a command that is returning data from the server pages that do precisely that.

When you get to the point where you have jQuery AJAX sending requests, you will need to intercept the POST command in the AJAX commands processor, and recreate the XML. You have save it to a file or print it to the screen to compare that you have something which can then be tested via the TFHTTPPost tester again. If it returns results, you are a good way there.

This is where FireBug becomes essential. When running AJAX commands, watch the Console. Here you will see Post events with the Post contents, and most importantly, the Responses. Once you see the responses being returned, you will be able to use JavaScript to break up the XML document and then display it how you wish.

This is where cloning becomes the challenge, as a lot of putting together of data, breaking a part of data, reassigning to new variables and SQL statements and then back out again, etc. It can be overwhelming at worst, and laborious at least.


Why Cloning

Cloning is the only way that you can provide new data with event management. For example, if you created a new input field without cloning, and you typed something in that field, no events would fire. This is because you need to let JavaScript know about the events before the page is loaded. The only way around this is to clone a section of HTML that have the events defined. This then tricks JavaScript into processing the event of the template to be cloned, and returns the results accordingly.

This is very clever, but obnoxious to work with.

Cloning using jQuery

jQuery is magic and saves the day. The best way to express how to do this is to look at the example code. Essentially, you need to create a DIV with all the events predefined. The DIV can then be cloned, and the events will be duplicated. If you were just to dynamically create new elements, JavaScript is unaware of the events. Cloning: the only way to create new elements that jQuery will be able to function for.

Here is an example of cloning you might see in the JavaScript

// this will grab the last table row.
var clonedRow = $("table tr :last).clone(); 

// use the selectors to
// manipulate any element in the clonedRow object.
$("#formField", clonedRow).attr("id", "newID"); 

//add the row back to the table end of the table
$("table").append(clonedRow); 

At this point for example, you might dynamically populate the attributes with information gathered from XML received from "*_xml.php".

jQuery and IE

jQuery is excellent, and should be learned well. It generally works well against all browsers, however some versions if IE do not work correctly with the following:

  • IE does not understand the "change" event. Make sure you use "click" instead and always.

Testing Commands

It is essential that your XML posts return the data they are expected to, and handle bad information correctly. The Test command is:

http://localhost/engenic_app_root/eng_medicalrelay_xml/xml_medicalcmd.php

In the barcode example:

http://localhost/engenic_app_root/bc_infoweb/bc_ajax_barcodes_commands.php

This command is a full, non-HTML application that accepts XML posts, works with the database or other information sources, and returns the processed results in XML. When building this command, be sure to:

  • Check for minimum mandatory parameters
  • Check for invalid data, and ensure appropriate response
  • Respond with a command Success or Failure method.

And most importantly,

  • Prevent SQL injection attacks

The last points helps make the JavaScript side easy to read and re-usable, and secure. SQL injection is only a concern if you are continuing data into a SQL statement. Attacks are easily prevented by running the data through this function found in library/CalcWorkUnit.php:


// Make SQL safe from SQL injects
function GetSQLSafeValues($theValue)
{
	// Ensure no SQL injections attacks
	return strtr($theValue, array(
	  "\x00" => '\x00',
	  "\n" => '\n', 
	  "\r" => '\r', 
	  '\\' => '\\\\',
	  "'" => "\'", 
	  '"' => '\"', 
	  "\x1a" => '\x1a'
	));
}

Even if your code does not integrate with a database, its a good measure to add this wrapper function to protect future use of the application.

Debug Mode

When adding the Debug flag to the end of the XML request command, you can get details about the SQL statement if it has been coded in

http://localhost/engenic_app_root/eng_medicalrelay_xml/xml_medicalcmd.php?DEBUG=TRUE

Improvements and Shortcuts

JavaScript

JavaScript is an horrendous, nasty, brutal language. Its hard to read, constructed in a strange way, it's hard to spot errors, and it seems to break even if you just look at it funny. Yet, it's actually pretty good, and happens to be the only way to build AJAX applications for the standard browser deployment. Again, the wonderful jQuery library takes a lot of misery out of it... once you become familiar with the incredibly odd manner in which it functions.

Manipulation of strings and date objects are handled differently than other languages, and takes some getting used to. If you are looking for something more conventional, check out a project by Kevin VanZonneveld. He has done something interesting, as he has written many of the PHP functions in JavaScript.

http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_wordwrap/

If you are new to JavaScript, it *might* be worth checking out. If nothing else, it reduces the amount of learning you need to do if you are already familiar with PHP.

Running for the First Time

By the "first time", we mean when you start testing you AJAX application.

DOM Errors

If nothing seems to be happening with the AJAX operations (check using FireBug to see if any results are coming back), and the contents of the response shows this:

"domdocument::domdocument() expects at least 1 parameter, 0 given in ..."

Then in the php.ini needs to be changed on your server environment. You will need to un-comment the following code from this:

; extension php_domxml.dll

to this:

extension=php_domxml.dll

Once done, then restart restart Apache. From a command line:

net stop apache2
net start apache2


CSS

This is probably the easiest and most enjoyable part unless you don't know CSS very well, in which case; prepare to be annoyed! In your CSS, start defining the classes which will define how you want all the elements to look. At this point, colors, font sizes, screen layouts, background graphics and all other beautification should be done now. DIVs should be defined and adjusted to maximize the screen utility.

Helpers

If you don't have it, get:

  • FileZilla Color Picker

This little tool will help you gather and paste colors into your CSS quickly and easily.


Final Touches

Testing Browsers

Before you get too far into any effort updating HTML and CSS, be sure to test in all the common browsers. You will be surprisingly annoyed at how much IE will not reflect what you have built in FireFox. For hints on what to stay away from, search for the words "Broken Box" and "margin" for cross browser development.

Usage
  • for Input Fields, prevent drop down suggest for input fields with: HTML: autocomplete="OFF"

Author

  • Vern Baker (vbaker at various email addresses)
  • engenic
  • Originally written April 2009