About the Authors Przemek Piotrowski has been following Oracle 10g Express Edition (Oracle XE) development since its early beta stages back in 2005 and can easily distinguish the new features in this first database from Oracle that’s free to develop, deploy, and distribute. Oracle XE’s small footprint doesn’t stop him from taking advantage of many of its advanced features and plugging them into existing Web architectures. Mark Townsend is vice-president of database product management in Oracle’s Server Technology Division. His responsibilities include requirement analysis, release planning, coordinating database product management activities, communicating with analysts and the press on database topics, and the development and delivery of field technical training. He was product boss for Oracle XE. Mark has been with Oracle since 1991 and has specialized in the Oracle database for over 15 years. Cutout: Build dynamic AJAX Web pages with transparent, asynchronous requests sent directly to Oracle Database 10g Express Edition. Serve dynamic content that doesn’t require a full-page refresh on the client side by leveraging the full potential of JavaScript’s Document Object Model, Oracle XML DB, and Embedded PL/SQL Gateway. Oracle XE + AJAX: Asynchronous XML in Action Building a real application by Przemek Piotrowski and Mark Townsend This article covers the process of building a real working AJAX application on top of Oracle’s free database software. The demonstration takes advantage of features integrated directly into Oracle Database 10g Express Edition (Oracle XE), the Embedded PL/SQL Gateway and Oracle XML DB, which let you serve XML content derived from SQL queries directly over HTTP protocol. You’ve probably run up against one of the numerous frameworks for AJAX that were probably Java- or .NET-based. Although they fit large- scale environments perfectly, smaller projects may not require such overwhelming resources as a J2EE stack. Besides, understanding the technology behind XMLHttpRequest may help you use asynchronous requests better under just about any Web development environment. The concept is surprisingly simple to make Web applications more responsive so that they behave like regular desktop software. Waiting for a page to reload just to see a change in a single place seems like a complete waste of both time and bandwidth. Having said that, using AJAX to create Web applications is good not only for the end user, but for the Web server as well. Applying this relatively simple technique boosts your Web applications’ usability many times. The potential of AJAX is so big that Web applications now have a real chance of replacing desktop programs without degrading the user experience. Prerequisites All you need is access to an Oracle XE database instance and its WebDAV folders. Oracle XE is much more than just a database server: Embedded PL/SQL Gateway makes it a regular Web server, while the built-in WebDAV and FTP listeners let you connect and store files served over HTTP directly through one of these protocols. This tutorial assumes that you have Oracle XE installed on your local computer. So that URLs refer to localhost, you may haveto adjust it to fit your network's configuration. I’ll be using the sample HR schema that ships with Oracle XE. By default it remains unlocked after installation so you need to unlock it first using the SYSTEM account through SQL*Plus (this command will also set a new password for HR): SQL> alter user hr identified by hr account unlock; User altered. On Windows accessing Oracle XE’s storage area is trivial with the Web Folder Access feature of Microsoft Internet Explorer; just select File > Open and type http://localhost:8080 in the address field and the Open as Web Folder checkbox. Then you’ll be prompted for a login and password. Supply hr twice here. If working under Linux or Windows you may choose to leverage the FTP server built into the XDB component of Oracle Database 10g. You activate it by connecting via SQL*Plus as SYSTEM and issuing the following commands: Connected to: Oracle Database 10g Express Edition Release 10.2.0.1.0 - Production SQL> exec dbms_xdb.setftpport('21'); PL/SQL procedure successfully completed. SQL> alter system register; System altered. The first command enables the FTP listener in the database, while the latter registers the just-activated listener on PMON (process monitor) without actually waiting for its 60-second refresh cycle. Now you can connect to the server with any FTP client or directly from the command line (ftp localhost). Now we can upload static files into the database storage area. What about dynamic content? The Embedded PL/SQL Gateway – introduced for the first time within Oracle XE – enables you to access stored procedures directly from a Web location. Using the http://SERVER_ADDRESS:PORT/apex/SCHEMA_NAME.PROCED URE_NAME syntax one can use PL/SQL stored procedures to trigger database events and generate content. For security reasons this feature has been disabled by default in Oracle XE so you have to unlock it first. Since this is a component of Application Express you have to modify the WWV_FLOW_EPG_INCLUDE_MOD_LOCAL function inside the FLOWS_020100 schema. As shown in Listing 1 below, you have to make two changes: comment out the return false statement just after begin and add the name of a stored procedure you’d like to grant external access to (in this tutorial we’ll be running HR.AJAXE stored procedure listed later). This strict policy on running stored procedures prevents one from executing arbitrary PL/SQL code on the server side, which would be a major security risk. Using allowed procedure names makes it convenient to limit execution. To finalize this step, grant execute privileges on the AJAXE procedure to PUBLIC (in SQL*Plus while logged in as SYSTEM or HR): SQL> grant execute on hr.ajaxe to public; Grant succeeded. Outputting XML from SQL Queries You can generate XML directly from SQL queries using the PL/SQL package DBMS_XMLGEN, which doesn’t depend on Java and sois available on XE. The query process takes four basic steps: 1. Get the context by supplying a valid SQL query 2. Set options on the newly created context using the DBMS_XMLGEN procedures (optional) 3. Get the XML result by using getXML() to get CLOB or getXMLType() to obtain XMLType. At this point you can also limit the numbers of rows returned by the XML engine using the setMaxRows() procedure on the context 4. Reset the query to perform step 3 again or run closeContext() to free up allocated resources. Unfortunately DBMS_XMLGEN has one flaw that you’ll run up against almost immediately when working with remote XML – HTP.PRN, whichwill be used to print the output accepts strings of a VARCHAR2 type that can be 4,000 bytes long. This isn’t much for XML to deal with. The AJAX_XMLHTP procedure was created, which takes a context as a parameter and using the DBMS_LOB PL/SQL package outputs XML in 4,000-char chunks, leveraging the possibilities standing behind LOBs. See Listing 2. This procedure will handle all the processing work for us by setting the content type to text/xml (required by the XmlHttpRequest object for DOM processing), setting null handling to output empty tags for null values, and disabling pretty printing for smaller response size. Also, when something goes wrong at the query level, e.g., empty result set or invalid query, the procedure manually outputs the hard-coded "No data found" message to indicate its state. The AJAXE procedure will be a core dispatcher of responses. It will accept two parameters: q – the type of action requested, w – the extra parameter required for certain types of action (optional). See Listing 3. The functionality of the sample application will be divided into three basic use cases: Filling the SELECT list with elements obtained through the AJAX request and hooking the event handler to it Obtaining the results set and rendering it into a HTML table dynamically Using simple auto-complete on the HTML input field Note that both these procedures have to be created in the HR schema to work. At this point you can test whether the procedure is working for you by accessing http://localhost:8080/apex/hr.ajaxe?q=count (remember to adjust the URL to reflect your system’s configuration). A valid XML document should be outputted at this address. You’re halfway through. Hands-on Document Object Model (DOM) AJAX extensively relies on a set of JavaScript programming techniques once referred to as DHTML. But now they’re much more powerful thanks to the unification of the DOM implementation among Web browser vendors. Back in the days of Netscape Navigator 4 and Microsoft Internet Explorer 4 one had to use a different syntax for each browser to manipulate page content dynamically. No need to do that anymore. The Document Object Model is a tree-like hierarchical representation of nodes/HTML elements. The root node of an HTML document is the <html> tag that usually has two children: <head> and <body> – the manipulations happen in the second. Since DOM is a vast subject, we’ll focus only on the most useful properties and methods of JavaScript XML objects. For the complete DOM specification, see the W3C Web site at http://w3.org. Querying There are two core methods of querying an existing DOM tree DOM that can be used both on document object and nodes themselves: document_or_node.getElementById(id) Query context for a node with an id attribute equal to the given ID. Returns a single node or null if not found. document_or_node.getElementsByTagName(tag_name) Query context for all nodes with the given tag name. Always returns a list of nodes (HTMLCollection object) even if it means that it’s empty (no nodes with the given tag name were found). Creating Element is the basic node type that represents a tag element on nodes a Web page. To create a new node of the Element type call the createElement method on the document object: document.createElement(name) Note, however, that this element is initially empty. You can append other elements or a text to it with the document.createTextNode(text) method. Removin To remove a node from the DOM tree you have to find its g nodes parent and use the following removal function: some_node.removeChild(old_child) This function removes the old_child node from the document in place and returns it. Note that this method can be slow when used to remove trees recursively from the main DOM tree. It can drastically decrease page performance so use the well-known trick of setting node’s innerHTML attribute to an empty string. Manipula There are several methods for manipulating the DOM tree, ting some of them are: nodes existing_node.appendChild(new_node) This method appends the new_node at the end of the existing_node’s children list. To be able to insert a node before some node one needs to make use of the insertBefore method. setAttribute(name, value) HTML requires many elements to have certain attributes set. For example, this method could be used to set the href attribute of <a> element. Again, remember that there are a number of other JavaScript methods for working with the DOM object. For purposes of this article, an ajaxTable function was created: it builds up the HTML table from the XML response obtained through the AJAX request. By default DBMS_XMLGEN returns a XML tree of the following structure: <?xml version="1.0"?> <ROWSET> <ROW> <COLUMN1></COLUMN1> <COLUMN2></COLUMN2> ... </ROW> ... </ROWSET> The code in Listing 4represents the body of the ajaxTable function. This function appends the DOM tree in Listing 5 to the DIV element of ID="q" (the TBODY element is required here to render properly under Internet Explorer). AJAX = Asynchronous JavaScript and XML Technologically, AJAX is nothing new. It owes its recent popularity to the unification of the JavaScript standards that allow access to the XmlHttpRequest object. XML isn’t new either. It now plays a key role in data and information interchange and is universally adopted by both the enterprise and the open source community. The biggest problem with the Web was that applications needed a full-page refresh after each user action on the page. It didn’t matter whether it was just sorting table elements or downloading a whole new page. XmlHttpRequest object methods and properties. abort() Aborts the current request getAllResponseHeaders() Returns all headers from the HTTP response as a string getResponseHeader(label) Returns the header value specified by the label open(method, URL) Overloaded open() methods assign open(method, URL, async) parameters to the request object open(method, URL, async, username) open(method, URL, async, The method is one of "GET" or "POST" (use the latter when the data is sent with a username, password) request that exceeds 512 bytes) send(content) setRequestHeader(label, value) onreadystatechange readyState URL might be relative or absolute The async flag is the heart of AJAX, setting Sends theor request together with the the it to true false you control whether specified contentfor (can be null) in the script is waiting a response background or halts execution until the Sets a label/value pair to be included in the response is received requests header The event handler watching the requests You can also supply username and state password for direct HTTP authorization Statusthat of this the request given integer: note is not safe, as by thethe script will 0 uninitial state always remainBasic visible to the user ized 1 open open() method has been successfully called 2 sent The request has been sent 3 receivin After receiving any of the g response headers and just before receiving the body 4 loaded All data transferred responseText responseXML Plain text content of the response XML content of the response status HTTP status of the URL called as a number (eg. 200, 403, 404) HTTP status of the URL called as a string (e.g., OK, Authorization Required, Not Found) statusText There are a number of AJAX frameworks on the market, including the Dojo Toolkit, Prototype, or Microsoft Atlas to name a few. However, writing basic handlers alone helps enable a deeper understanding of what’s happening under AJAX’s hood. First, we need an XmlHttpRequest object that unfortunately still depends on the browser – Microsoft Internet Explorer continues handling it using an ActiveX control so the appropriate requests object creation code is presented in Listing 6. This is the only part that requires browser-compatibility code. Everything else here is crossbrowser. As for the handler, a dedicated function to handle all the asynchronous operations was created. It takes a request method, a URL, and callback functions as parameters. After the response is obtained the callback is immediately called with the fetched XML passed as an argument. function async(method, url, callback) { request.open(method, url, true); request.onreadystatechange = function() { if (request.readyState == 4 && request.status == 200) { callback(request.responseXML); } } request.send(null); } The callback function will do all the DOM processing based on the XML content from the response. The callback works directly on the XMLDocument JavaScript object. Putting It All Together Now that we have all the building blocks of the solution, let’s take a look at the logic behind it. The diagram below separates client side and server side into three separate blocks: Oracle XE, Web page, and browser client. Notice that the Web page has been separated here because of its state-dependent nature – thanks to AJAX it now incorporates the logic to handle user actions and transform them into HTTP requests, the standard Web model assumes that it’s the browser that dispatches HTTP requests to the Web server. That is the key concept in the solution. Take a look at the diagram below to see the whole interaction process. 1. The browser client sends a direct HTTP request to the Web server (here: the Embedded PL/SQL Gateway of Oracle XE), the URL of the request has been supplied in the location bar or through a link. 2. Oracle XE serves a static HTML page that includes AJAX scripts. This static page is then rendered by the browser and now all the requests and interaction happen indirectly, as AJAX requests. 3. When the user triggers an event on the Web page by interacting with page elements such as forms and links, an event handler catches it and sends the appropriate HTTP request through the AJAX engine. 4. The XMLHttpRequest object requests a remote resource from the Embedded PL/SQL gateway. At this point, the user doesn’t have to wait for the script to finish fetching responses from the server, it’s handled in an asynchronous manner. The script takes as much time as needed to fetch the response while the user can further interact with the Web page. 5. The script performs DOM modifications in the background while the user can trigger the next events on the Web page. You can now upload the attached HTML file (hr-ajax-demo.html) into your XE instance through FTP or WebDAV, which contains the complete code for the sample application. After putting it into the /public/ folder of XE, open http://localhost:8080/public/hr-ajax-demo.html with your Web browser. After carefully following all the steps of this tutorial the demo page should pre-load the list of departments and respond to user interaction almost immediately. Advantages and Potential Problems Although programming AJAX isn’t necessarily difficult, the question is whether asynchronous querying techniques are safe and productionready. AJAX isn’t always a sure choice over standard development methodologies. There are at least several viewpoints to consider: Advantages: Better user experience Greater responsiveness Smaller server load, increased performance Network applications can be updated on-the-fly without redistributing updates Separation of content from presentation (easier maintenance) Potential problems: Cross-browser incompatibilities Client-side security concerns The Real World Today, AJAX applications are gaining a lot of steam. With Google leading this trend together AND Yahoo! and Microsoft trailing, the desktop experience is delivered to users through increasingly dynamic Web pages. Since the early adoption of AJAX it has matured to be a fullblown solution often chosen by enterprises for commercial solutions. After the unification of DOM implementations and the adoption of World Wide Web Consortium (W3C) standards among developers of Web browsers, it’s now possible to create cross-platform Web applications more easily than ever before. With a wide range of available AJAX toolkits, developers can now upgrade Web applications to an asynchronous architecture at a low cost. AJAX is flexible enough to be plugged into existing solutions and greatly enhances usability. The movement of desktop applications to the Web is a matter of time and represents the next step in software evolution. Listing 1 Listing 1 create or replace function wwv_flow_epg_include_mod_local(procedure_name in varchar2) return boolean is begin -- return false; -- remove this statement when you modify this function --- Administrator note: the procedure_name input parameter may be in the format: --- procedure -- schema.procedure -- package.procedure -- schema.package.procedure --- If the expected input parameter is a procedure name only, the IN list code shown below -- can be modified to itemize the expected procedure names. Otherwise you must parse the -- procedure_name parameter and replace the simple code below with code that will evaluate -- all of the cases listed above. -if upper(procedure_name) in ( 'HR.AJAXE') then return TRUE; else return FALSE; end if; end wwv_flow_epg_include_mod_local; Listing 2 create or replace procedure ajax_xmlhtp(ctxt in number) as xml clob; chnk number := 4000; begin owa_util.mime_header('text/xml'); dbms_xmlgen.setnullhandling(ctxt, dbms_xmlgen.empty_tag); dbms_xmlgen.setprettyprinting(ctxt, false); xml := dbms_xmlgen.getxml(ctxt); for i in 1..ceil(length(xml)/chnk) loop htp.prn(dbms_lob.substr(xml, chnk+1, i+(i-1)*chnk)); end loop; dbms_xmlgen.closecontext(ctxt); exception when others then htp.print('<?xml version="1.0"?>'); htp.print('<ROWSET><ROW><INFO>No data found.</INFO></ROW></ROWSET>'); end; / Listing 3 create or replace procedure ajaxe(q varchar2, w varchar2 default '') as ctxt number; i number; begin if q='count' then ajax_xmlhtp(dbms_xmlgen.newcontext('select count(*) "EMPLOYEES" from employees')); elsif q='search' then i := length(w); ctxt := dbms_xmlgen.newcontext('select * from employees where upper(substr(last_name, 0, :LEN))=upper(:PREFIX) order by last_name'); dbms_xmlgen.setbindvalue(ctxt, 'LEN', i); dbms_xmlgen.setbindvalue(ctxt, 'PREFIX', w); ajax_xmlhtp(ctxt); elsif q='fetch' then ajax_xmlhtp(dbms_xmlgen.newcontext('select * from employees')); elsif q='select' then ajax_xmlhtp(dbms_xmlgen.newcontext('select department_id, department_name from departments order by 2')); elsif q='dept' then ctxt := dbms_xmlgen.newcontext('select * from employees where department_id=:DEPT'); dbms_xmlgen.setbindvalue(ctxt, 'DEPT', w); ajax_xmlhtp(ctxt); end if; end; / Listing 4 function ajaxTable(xml) { document.getElementById('q').innerHTML = ''; var rows = xml.documentElement.getElementsByTagName('ROW'); var table = document.createElement('table'); var tbody = document.createElement('tbody'); table.setAttribute('border', '1'); var tr = document.createElement('tr'); for (var h=0; h<rows[0].childNodes.length; h++) { if (rows[0].childNodes[h].nodeType!=1) { continue; } var th = document.createElement('th'); th.appendChild(document.createTextNode(rows[0].childNodes[h].ta gName)); tr.appendChild(th); } tbody.appendChild(tr); for (var i=0; i<rows.length; i++) { var tr = document.createElement('tr'); for (var j=0; j<rows[i].childNodes.length; j++) { if (rows[i].childNodes[j].nodeType!=1) { continue; } var td = document.createElement('td'); if (rows[i].childNodes[j].childNodes.length!=0) { td.appendChild(document.createTextNode(rows[i].childNodes[j].fir stChild.nodeValue)); } else { td.appendChild(document.createTextNode('-')); } tr.appendChild(td); } tbody.appendChild(tr); } table.appendChild(tbody); document.getElementById('q').appendChild(table); } Listing 5 <table border="1"> <tbody> <tr> <th>HEADING1</th> <th>HEADING2</th> <th>...</th> </tr> <tr> <td>ROW1, COLUMN1</td> <td>ROW1, COLUMN2</td> <td>ROW1, ...</td> </tr> ... </tbody> </table> Listing 6 var request = false; try { request = new XMLHttpRequest(); } catch(e) { try { request = new ActiveXObject("Msxml2.XMLHTTP"); } catch (e2) { request = new ActiveXObject("MSXML.XMLHTTP"); } }