rest/ahah: Difference between revisions
(54 intermediate revisions by 22 users not shown) | |||
Line 1: | Line 1: | ||
= AHAH: | = AHAH: Asynchronous HTML and HTTP = | ||
AHAH is a very simple technique for dynamically updating web pages using [http://en.wikipedia.org/wiki/JavaScript JavaScript]. It involves using [http://en.wikipedia.org/wiki/XMLHTTP XMLHTTPRequest] to retrieve [http://en.wikipedia.org/wiki/HTML (X)HTML] fragments which are then inserted directly into the web page, whence they can be styled using [http://en.wikipedia.org/wiki/Cascading_Style_Sheets CSS]. | AHAH is a very simple technique for dynamically updating web pages using [http://en.wikipedia.org/wiki/JavaScript JavaScript]. It involves using [http://en.wikipedia.org/wiki/XMLHTTP XMLHTTPRequest] to retrieve [http://en.wikipedia.org/wiki/HTML (X)HTML] fragments which are then inserted directly into the web page, whence they can be styled using [http://en.wikipedia.org/wiki/Cascading_Style_Sheets CSS]. | ||
Line 7: | Line 7: | ||
* [http://epeus.blogspot.com/ Kevin Marks] | * [http://epeus.blogspot.com/ Kevin Marks] | ||
* [http://www.opendarwin.org/~drernie/ Ernest Prabhakar] | * [http://www.opendarwin.org/~drernie/ Ernest Prabhakar] | ||
== Questions == | |||
* Has anyone considered working with browser vendors to have them support client-side includes? This way, markup like <div src="hello-world.txt" /> would just work. This avoids the need for client side script and ensures the browser can correctly manage connections, caching, authentication, etc. For backwards compatibility, you could create a little javascript utility to scan for all occurrences of tags like this, queue up the requests and let the content flow in after the page was loaded. I have a little csi.js utility that does this and it's pretty handy. || Yes, Div Includes is a good concept (fraught with all the usual security issues), but the real power of includes is when they are used dynamically in response to user interaction. For purely static pages it is much more efficient to have the server create them using something like shtml or php. The ability to create/update a Div include via JS is where this proposed technique would be a fantastic improvement on the mess that we have now. | |||
* Okay, maybe this is a dumb question, but I don't understand why this is called AHAH instead of AJAH. AHAH still uses the same XMLHttpRequest JavaScript functionality as AJAX, so why drop the J? AJAX uses the same HTTP functionality as AHAH, so why add the H? Have I misunderstood what is actually happening? (You are right of course, but Ahah! == Eureka!!! and AJah = ?) | |||
== Relation to AJAX == | == Relation to AJAX == | ||
AHAH is intended to be a much simpler way to do [http://en.wikipedia.org/wiki/Web_development web development] than [http://en.wikipedia.org/wiki/Ajax_%28programming%29 AJAX]: "Asynchronous JavaScript and XML." Strictly speaking, AHAH can be considered a subset of AJAX, since (X)HTML is just a special kind of XML However, it is a subset with some very specific and useful properties: | AHAH is intended to be a much simpler way to do [http://en.wikipedia.org/wiki/Web_development web development] than [http://en.wikipedia.org/wiki/Ajax_%28programming%29 AJAX]: "Asynchronous JavaScript and XML." Strictly speaking, AHAH can be considered a subset of AJAX, since (X)HTML is just a special kind of XML. However, it is a subset with some very specific and useful properties: | ||
# The lack of custom XML schemas dramatically reduces design time | # The lack of custom XML schemas dramatically reduces design time | ||
# AHAH can trivially reuse existing HTML pages, avoiding the need for a custom web service | # AHAH can trivially reuse existing HTML pages, avoiding the need for a custom web service | ||
# All data transport is done via browser- | # All data transport is done via browser-friendly HTML, easing debugging and testing | ||
# The HTML is designed to be directly embedded in the page's DOM, eliminating the need for parsing | # The HTML is designed to be directly embedded in the page's DOM, eliminating the need for parsing | ||
# As HTML, designers can format it using CSS, rather than programmers having to do XSLT transforms | # As HTML, designers can format it using CSS, rather than programmers having to do XSLT transforms | ||
# Processing is all done on the server, so the client-side programming is essentially nil (moving opaque bits) | |||
In fact, for any content that is destined to be viewed by the browser, it is virtually impossible to imagine any advantage to sending it as custom XML rather than structurally-correct HTML (with appropriate CSS-friendly class names, of course). | In fact, for any content that is destined to be viewed by the browser, it is virtually impossible to imagine any advantage to sending it as custom XML rather than structurally-correct HTML (with appropriate CSS-friendly class names, of course). | ||
Line 24: | Line 30: | ||
Unlike the various libraries (e.g., [http://en.wikipedia.org/wiki/JSON JSON], [http://mochikit.com/ MochiKit]) important for AJAX, all of AHAH is contained in a single JavaScript file (also available as [http://www.opendarwin.org/~drernie/src/ahah.js ahah.js] and [http://homepage.mac.com/kevinmarks/jah.js jah.js]). In fact, this is little more than the canonical XMLHttpRequest example, and is simple enough for any modern web designer to embed within their existing web pages. | Unlike the various libraries (e.g., [http://en.wikipedia.org/wiki/JSON JSON], [http://mochikit.com/ MochiKit]) important for AJAX, all of AHAH is contained in a single JavaScript file (also available as [http://www.opendarwin.org/~drernie/src/ahah.js ahah.js] and [http://homepage.mac.com/kevinmarks/jah.js jah.js]). In fact, this is little more than the canonical XMLHttpRequest example, and is simple enough for any modern web designer to embed within their existing web pages. | ||
NOTE: The example ahah.js mentioned here has the unfortunate requirement (if the delay argument is not undefined) that url,target,delay must be global variables (which makes one wonder why they are passed as parameters....). If you don't want to use globals, and want this to actually work, use something like: setTimeout( 'ahah( "' + url + '", "' + target + '", ' + delay + ')', delay ); Fair warning. | |||
=== Send AHAH Request and Deal with the Response === | |||
function ahah(url, targetId, onDone) { | |||
var targetElement = document.getElementById(targetId); | |||
targetElement.innerHTML = "Loading data..."; | |||
var request = window.ActiveXObject ? new ActiveXObject('Microsoft.XMLHTTP') : new XMLHttprequestuest(); | |||
request.onreadystatechange = function() { | |||
if (request.readyState != 4) { //not "OK" status | |||
return; | |||
} | |||
if (request.status != 200 && request.status != 304) { | |||
targetElement.innerHTML = "ahah error:\n" + request.statusText; | |||
return; | |||
} | |||
targetElement.innerHTML = request.responseText; | |||
onDone && onDone(); //exists? then trigger | |||
} | } | ||
request.open("GET", url, true); | |||
request.send(); | |||
} | } | ||
Note the workaround needed for IE's ActiveX implementation. | Note the workaround needed for IE's ActiveX implementation. The current version hard-codes GET; there may be value in adding an extra parameter to allow POST, PUT, and DELETE. The last optional parameter allows you to pass a callback that will be executed after successful request. | ||
Aside from error checking, inner function simply splices response into the current page: targetElement.innerHTML = request.responseText. Everything else (e.g., CSS-styling) is merely inherited from the parent webpage. | |||
=== Executing Javascript === | |||
Since the browser won't execute <script> tags when changing innerHTML, you may want to apply the following function call on document.getElementById(target): | |||
function | var bSaf = (navigator.userAgent.indexOf('Safari') != -1); | ||
var bOpera = (navigator.userAgent.indexOf('Opera') != -1); | |||
var bMoz = (navigator.appName == 'Netscape'); | |||
function execJS(node) { | |||
var st = node.getElementsByTagName('SCRIPT'); | |||
var strExec; | |||
for(var i=0;i<st.length; i++) { | |||
if (bSaf) { | |||
strExec = st[i].innerHTML; | |||
} | |||
else if (bOpera) { | |||
strExec = st[i].text; | |||
} | |||
else if (bMoz) { | |||
strExec = st[i].textContent; | |||
} | |||
else { | |||
strExec = st[i].text; | |||
} | |||
try { | |||
eval(strExec.split("<!--").join("").split("-->").join("")); | |||
} catch(e) { | |||
alert(e); | |||
} | |||
} | |||
} | } | ||
==== JavaScript Notes: ==== | |||
* IE always returns tags in UPPER CASE, so you must search for SCRIPT and not script. Keep your SCRIPT tags in upper case for Firefox compatibility. | |||
* Do not use // comments, use /* */ instead. The eval executes all your javascript as though it was on one line, so you must end each statement with a semicolon (;). | |||
* Any functions declared in your JavaScript will go out of scope once the eval statement completes. It is possible to keep them in scope by attaching them as a method to a function that was defined at the document level. | |||
== Indexing == | |||
Another advantage of AHAH is that the dynamic XHTML content can be easily indexed by search engines; this avoids the need to inline all the dynamic content as hidden divs, which would increase page load times. | |||
The current best practice for doing this is to: | |||
* included <link> tags in <head> of the parent page, to reference the various URLs retrieved by AHAH | |||
* include <redirects> in the outer HTML of the AHAH page, so that search hits go to an appropriate anchor on the parent page | |||
It is possible that some crawlers will automatically index the URLs in the JavaScript calls, if recognized as such (e.g., due to the "html" extension, or if it is an absolute URL), though it is not clear how well this would work. | |||
== Meta tags == | |||
One odd characteristic of <tt>responseText</tt> is that it appears to preserve 'meta' and 'link' tags (though apparently not 'head' itself). This would potentially pollute the browser with illegal HTML, though since they're empty tags it shouldn't affect rendering, and any real-world browsers can be counted on to safely ignore it. One might want to add JavaScript to strip out the extra data, but that would likely be more overhead than it is worth (unless there is sensitive data in there you don't want curious source-viewers to see). | |||
On the flip side, though, the preservation of meta tags allows the sending of additional key-value pairs, a la [http://www.crockford.com/JSON/ JSON]. True, it doesn't support more complex data structures, but in many cases it will suffice, and it avoids the security concerns. For more complex metadata, it is probably better to use [[xoxo]] (perhaps with JSON conventions). | |||
== History == | == History == | ||
Line 76: | Line 117: | ||
== Implementations == | == Implementations == | ||
* Sample [http://www.opendarwin.org/~drernie/src/ahah.js JavaScript code] available from Ernie Prabhakar, extending work by Kevin Marks. | |||
* There is some talk of directly supporting AHAH in [http://www.rubyonrails.com/ Ruby on Rails] using [http://wiki.rubyonrails.com/rails/pages/UnderstandingPartials partials]. | * There is some talk of directly supporting AHAH in [http://www.rubyonrails.com/ Ruby on Rails] using [http://wiki.rubyonrails.com/rails/pages/UnderstandingPartials partials]. | ||
* [http://blog.davidjanes.com/mtarchives/2006_01.html#003498 David Janes] has implemented a [http://www.crockford.com/JSON/ JSON]-powered variant called [http://www.blogmatrix.com/tools/jahah/ JAHAH]. | |||
* [http://www.danielthepope.it/#secondPage/1 Daniel Thepope] has performed AHAH original function | |||
* [http://csharpedge.blogspot.com/ Troels Wittrup] published an article about his AHAH/Hijax framework "OutPost" on October 6, 2005 at [http://www.codeproject.com/Ajax/Outpost.asp CodeProject]. | |||
* [http://groups.google.com/group/XMLHttpRequest/browse_thread/thread/3deab0ff4778fdf7/a8f83be73f135ed5 sax'ntouebbe] has modified the code to support multiple simultaneous outstanding AHAH requests. |
Latest revision as of 10:26, 8 November 2016
AHAH: Asynchronous HTML and HTTP
AHAH is a very simple technique for dynamically updating web pages using JavaScript. It involves using XMLHTTPRequest to retrieve (X)HTML fragments which are then inserted directly into the web page, whence they can be styled using CSS.
Contributors
Questions
- Has anyone considered working with browser vendors to have them support client-side includes? This way, markup like <div src="hello-world.txt" /> would just work. This avoids the need for client side script and ensures the browser can correctly manage connections, caching, authentication, etc. For backwards compatibility, you could create a little javascript utility to scan for all occurrences of tags like this, queue up the requests and let the content flow in after the page was loaded. I have a little csi.js utility that does this and it's pretty handy. || Yes, Div Includes is a good concept (fraught with all the usual security issues), but the real power of includes is when they are used dynamically in response to user interaction. For purely static pages it is much more efficient to have the server create them using something like shtml or php. The ability to create/update a Div include via JS is where this proposed technique would be a fantastic improvement on the mess that we have now.
- Okay, maybe this is a dumb question, but I don't understand why this is called AHAH instead of AJAH. AHAH still uses the same XMLHttpRequest JavaScript functionality as AJAX, so why drop the J? AJAX uses the same HTTP functionality as AHAH, so why add the H? Have I misunderstood what is actually happening? (You are right of course, but Ahah! == Eureka!!! and AJah = ?)
Relation to AJAX
AHAH is intended to be a much simpler way to do web development than AJAX: "Asynchronous JavaScript and XML." Strictly speaking, AHAH can be considered a subset of AJAX, since (X)HTML is just a special kind of XML. However, it is a subset with some very specific and useful properties:
- The lack of custom XML schemas dramatically reduces design time
- AHAH can trivially reuse existing HTML pages, avoiding the need for a custom web service
- All data transport is done via browser-friendly HTML, easing debugging and testing
- The HTML is designed to be directly embedded in the page's DOM, eliminating the need for parsing
- As HTML, designers can format it using CSS, rather than programmers having to do XSLT transforms
- Processing is all done on the server, so the client-side programming is essentially nil (moving opaque bits)
In fact, for any content that is destined to be viewed by the browser, it is virtually impossible to imagine any advantage to sending it as custom XML rather than structurally-correct HTML (with appropriate CSS-friendly class names, of course).
That said, many applications of AJAX are (at least in theory) targeteable at custom JavaScript code or desktop GUIs rather than mere browsers. For those cases, the advantages of HTML over custom XML are somewhat less. However, even here, it may well make sense to encode data using xoxo -- aka XHTML Property Lists -- which can be losslessly converted back and forth from standard data structures (lists and dictionaries) without the need for custom parsers.
Source Code
Unlike the various libraries (e.g., JSON, MochiKit) important for AJAX, all of AHAH is contained in a single JavaScript file (also available as ahah.js and jah.js). In fact, this is little more than the canonical XMLHttpRequest example, and is simple enough for any modern web designer to embed within their existing web pages.
NOTE: The example ahah.js mentioned here has the unfortunate requirement (if the delay argument is not undefined) that url,target,delay must be global variables (which makes one wonder why they are passed as parameters....). If you don't want to use globals, and want this to actually work, use something like: setTimeout( 'ahah( "' + url + '", "' + target + '", ' + delay + ')', delay ); Fair warning.
Send AHAH Request and Deal with the Response
function ahah(url, targetId, onDone) { var targetElement = document.getElementById(targetId); targetElement.innerHTML = "Loading data..."; var request = window.ActiveXObject ? new ActiveXObject('Microsoft.XMLHTTP') : new XMLHttprequestuest(); request.onreadystatechange = function() { if (request.readyState != 4) { //not "OK" status return; } if (request.status != 200 && request.status != 304) { targetElement.innerHTML = "ahah error:\n" + request.statusText; return; } targetElement.innerHTML = request.responseText; onDone && onDone(); //exists? then trigger } request.open("GET", url, true); request.send(); }
Note the workaround needed for IE's ActiveX implementation. The current version hard-codes GET; there may be value in adding an extra parameter to allow POST, PUT, and DELETE. The last optional parameter allows you to pass a callback that will be executed after successful request.
Aside from error checking, inner function simply splices response into the current page: targetElement.innerHTML = request.responseText. Everything else (e.g., CSS-styling) is merely inherited from the parent webpage.
Executing Javascript
Since the browser won't execute <script> tags when changing innerHTML, you may want to apply the following function call on document.getElementById(target):
var bSaf = (navigator.userAgent.indexOf('Safari') != -1); var bOpera = (navigator.userAgent.indexOf('Opera') != -1); var bMoz = (navigator.appName == 'Netscape'); function execJS(node) { var st = node.getElementsByTagName('SCRIPT'); var strExec; for(var i=0;i<st.length; i++) { if (bSaf) { strExec = st[i].innerHTML; } else if (bOpera) { strExec = st[i].text; } else if (bMoz) { strExec = st[i].textContent; } else { strExec = st[i].text; } try { eval(strExec.split("<!--").join("").split("-->").join("")); } catch(e) { alert(e); } } }
JavaScript Notes:
- IE always returns tags in UPPER CASE, so you must search for SCRIPT and not script. Keep your SCRIPT tags in upper case for Firefox compatibility.
- Do not use // comments, use /* */ instead. The eval executes all your javascript as though it was on one line, so you must end each statement with a semicolon (;).
- Any functions declared in your JavaScript will go out of scope once the eval statement completes. It is possible to keep them in scope by attaching them as a method to a function that was defined at the document level.
Indexing
Another advantage of AHAH is that the dynamic XHTML content can be easily indexed by search engines; this avoids the need to inline all the dynamic content as hidden divs, which would increase page load times.
The current best practice for doing this is to:
- included <link> tags in <head> of the parent page, to reference the various URLs retrieved by AHAH
- include <redirects> in the outer HTML of the AHAH page, so that search hits go to an appropriate anchor on the parent page
It is possible that some crawlers will automatically index the URLs in the JavaScript calls, if recognized as such (e.g., due to the "html" extension, or if it is an absolute URL), though it is not clear how well this would work.
Meta tags
One odd characteristic of responseText is that it appears to preserve 'meta' and 'link' tags (though apparently not 'head' itself). This would potentially pollute the browser with illegal HTML, though since they're empty tags it shouldn't affect rendering, and any real-world browsers can be counted on to safely ignore it. One might want to add JavaScript to strip out the extra data, but that would likely be more overhead than it is worth (unless there is sensitive data in there you don't want curious source-viewers to see).
On the flip side, though, the preservation of meta tags allows the sending of additional key-value pairs, a la JSON. True, it doesn't support more complex data structures, but in many cases it will suffice, and it avoids the security concerns. For more complex metadata, it is probably better to use xoxo (perhaps with JSON conventions).
History
"AHAH" as a formal technique appears to have been introduced by Kevin Marks on May 12, 2005 under the name JAH: "Just Asynchronous HTML", where it was also used in a simple example. The term "AHAH" was proposed by Ernest Prabhakar during the 2005 Web 2.0 conference, and later adopted as part of the REST-Enabled XHTML (REX) microformat for web services.
David Hansson had independently discovered the exact same concept, and in fact had already submitted an abstract about it for O'Reilly's 2006 E-Tech conference when he encountered the work done by Marks and Prabhakar. He had not however named the technique, and quickly agreed to adopt the AHAH moniker. The same concept has no doubt been independently discovered by others, but these three appear to be the first to make a sustained attempt to promote it as a formal technique.
Implementations
- Sample JavaScript code available from Ernie Prabhakar, extending work by Kevin Marks.
- There is some talk of directly supporting AHAH in Ruby on Rails using partials.
- David Janes has implemented a JSON-powered variant called JAHAH.
- Daniel Thepope has performed AHAH original function
- Troels Wittrup published an article about his AHAH/Hijax framework "OutPost" on October 6, 2005 at CodeProject.
- sax'ntouebbe has modified the code to support multiple simultaneous outstanding AHAH requests.