LAB-5573: Leveraging JavaScript Toolkits for End-to-End Connectivity in Web Applications

Expected Duration: 100 minutes
Contacts: Troy Giunipero

Exercise 1: Connecting a Script.aculo.us Autocomplete Field to a MySQL Database (40 minutes)

 

This exercise provides you with a hand-coded AJAX autocomplete example that returns names from a HashMap which match the characters typed into a text field.

You begin by sampling the NetBeans JavaScript debugger on the application. Then, you create a MySQL database, add data to it, and port it to the application. Finally, you add the bundled Script.aculo.us toolkit to the project in order to apply the Ajax Autocompleter.

In order to complete this exercise, perform the following activities:

  1. Experimenting with the JavaScript debugger.
  2. Porting data to a MySQL database.
  3. Applying Script.aculo.us' Ajax Autocompleter.

Background Information

 

The NetBeans IDE provides a database viewer from its Services window (Ctrl-5; ⌘-5 on Mac). If you have a MySQL database installed on your computer, you can interface with it from this window.

The IDE provides you with version 1.8.1 of the Script.aculo.us toolkit.



Steps to Follow

 

Step 1: Experimenting with the JavaScript Debugger

  1. In the Projects window, right-click the Ex1_Scriptaculous_Start project node and choose Set as Main Project.
  2. Click the Run Main Project ( Run Main Project button ) button in the IDE's toolbar to run the project.

    In the browser, experiment with the autocomplete field to see how it works.
    Ajax autocomplete dropdown
    This auto-completion is performed using an asynchronous call to the server-side. The JavaScript used in the application prepares and sends an XMLHttpRequest object. The server processes the request and returns data in XML format, which the client then uses to update the DOM (Document Object Model) on the fly. The following diagram illustrates the process flow of the communication that takes place between the client and server:
    Ajax autocomplete dropdown
  3. In the Projects window, right-click the project node and choose Properties. Then, in the left column of the Project Properties window, select Debug.

    We are going to run the JavaScript debugger on the application to get a sense of how communication takes place. First we need to activate the JavaScript debugger and deactivate the Java debugger for the project.
  4. Deselect the Java debugger option and select the JavaScript debugger option. (If you are not using Internet Explorer, the Firefox option is selected by default).
    Project Properties Debug window
  5. Click OK to save changes and exit the Project Properties window.
  6. Run the debugger by pressing the Debug Main Project button ( Debug main project button ) in the IDE's toolbar.

    If you are running the IDE's JavaScript debugger for the first time, the IDE detects whether you have the Firebug add-on and NetBeans Firefox extension installed in Firefox. If these have not been installed, the IDE attempts to install them.
    Dialog box
    Warning: If you see this dialog, shut down Firefox, then click OK. If not, the browser cannot load with the add-on and extension activated, and you will need to restart the browser manually, then rerun the debugger.

    A debugging session is initiated in the IDE, and the autocomplete example opens in the browser.

    Tip: Note that in the IDE, the index.jsp page opens in the editor in read-only ([r/o]) mode. The debugger retrieves all files from the browser cache, and as such it is not possible to edit these files directly. To do so, you would need to open these files from the Projects window, edit and save changes, then rerun the debugger.
  7. In the IDE, make sure you have the following windows open:
    • Local Variables
    • Sessions
    • Watches
    • Call Stack
    • Http Client Monitor
    Many of these windows open by default when you run the debugger. If you need to open any of these, choose Window > Debugging from the main menu and select the window.
  8. Examine the index.jsp file in the editor. Note that the doCompletion() function is called when a user types in the text input field:
    <td><strong>Composer Name:</strong></td>
    <td>
        <input type="text"
               size="30"
               id="completeField"
               onkeyup="doCompletion()">
    </td>
  9. Open the javascript.js file in the editor (double-click the file from the Projects window). This JavaScript file contains all of the client-side functionality required to produce the autocomplete dropdown list.
  10. Set a breakpoint on line 15 by clicking the line number in the left margin of the editor. This will prompt the debugger to suspend on this line the next time the doCompletion() function is called.

    Tip: If line numbers are not displayed, right-click in the left margin and choose Show Line Numbers.
    Editor displaying breakpoint
    At this point, you may want to position the IDE side-by-side with your browser. That way, it is possible to see any updates that take place in the IDE when you trigger actions in the browser.
  11. In the browser, type in the letter "a" in the text field. Note that the debugger suspends on line 16, where you previously set the breakpoint.
    Editor displaying debugger suspended on breakpoint
  12. Examine the Sessions window. The window lists debugging sessions that currently exist in the IDE. Here you can see that the JavaScript debugging session has started, and verify the current state it is in if it is active.
    Debugger Sessions window
    Tip: It is possible to run multiple sessions simultaneously (e.g., Java and JavaScript). If this were the case, you could toggle the session you wanted to use by clicking the entry listed in the Sessions window. (The " Current session icon " icon signifies an active session.)
  13. Examine the HTTP Client Monitor. The HTTP Client Monitor provides an interface that displays all messages being sent to and from the client.
    HTTP Client Monitor
    Tip: Double-click the HTTP Client Monitor title bar to maximize it in the IDE.
  14. Return focus to javascript.js. The doCompletion() function contains the code necessary to prepare and send an XMLHttpObject to the server.
    function doCompletion() {
        var url = "doComposers?input=" + escape(completeField.value);
        req = initRequest();
        req.open("GET", url, true);
        req.onreadystatechange = callback;
        req.send(null);
    }
  15. Hover your mouse over "value" on line 15. You can see the value that the expression "completeField.value" currently holds.
    Expression value tooltip displayed in editor
    Generally speaking, if the debugger session maintains a variable or expression value, you can hover over, or highlight, the variable or expression in the editor to determine the value.
  16. Click the Step Into button ( Step Into button ) in the debugger toolbar. This steps the debugger to the next line (i.e., debugging frame).

    Line 16 calls the initRequest() function, and as you can see by examining the function (lines 22 - 32), it returns an ActiveXObject if browser recognizes ActiveXObjects, or it returns an XMLHttpRequest object if it understands XMLHttpRequests.
  17. Hover the mouse over the req variable in line 16. Notice that the variable currently equates to void.
    Tooltip equates to void in editor
    The debugger still needs to step through the initRequest() function and attach a value to req.
  18. Click the Step Into button ( Step Into button ) to step to line 16, and hover the mouse over the req variable again. Note that although the debugger is suspended on line 16, the req variable's value is still void. This is because the line on which the debugger is suspended represents the code that the debugger is about to process.
  19. Click the Step Over button ( Step Over button ) to skip stepping through the initRequest() function.
  20. Now hover the mouse over the req variable in line 17. Notice that the variable now equates to the value returned from initRequest() - an XMLHttpRequest object.
    Tooltip equates to XMLHttpObject in editor
  21. On line 17, position your cursor after the "n" in req.open, then press Ctrl-Space to invoke code completion and documentation. Scroll down to view the open() function signature that uses 3 arguments:
    Code completion and documentation windows
    The XMLHttpRequest object requires these three arguments to be set before it can be sent. These are:
    • the URL, which contains set parameters and user input (i.e., the character "a"),
    • the method of HTTP interaction (i.e., GET or POST), and
    • a boolean value to signify whether communication is asynchronous or not.
    If asynchronous communication is specified (if the third parameter is set to true), a callback function must also be specified to indicate how the browser is to proceed once it retrieves the requested data. This is essentially done in line 18, which prompts the callback() function to handle the event when the response is complete:
    req.onreadystatechange = callback;
  22. Step the debugger to line 19. The send() function is what initiates the request to the server.
  23. Return focus to the Http Client Monitor, then click the Step Into button ( Step Into button ) to step the debugger beyond line 19. Two events take place:
    • The debugger jumps to line 35, the first line within the callback() function, and
    • The Http Client Monitor lists a new record.
  24. In the Http Client Monitor, click the new record to select it, then under HTTP Request, click the Parameters tab and under HTTP Response, click the Body tag.
    Http Client Monitor displaying new record
    Note that the parameters sent with the request match those contained in the URL of the request, and that the response includes XML content.

    If you examine the XML content contained in the HTTP Response, you basically see 4 <composer> entries:
    <composers>
        <composer>
            <id>12</id>
            <firstName>Antonin</firstName>
            <lastName>Dvorak</lastName>
        </composer>
        <composer>
            <id>45</id>
            <firstName>Aaron</firstName>
            <lastName>Copland</lastName>
        </composer>
        <composer>
            <id>7</id>
            <firstName>Antonio</firstName>
            <lastName>Vivaldi</lastName>
        </composer>
        <composer>
            <id>2</id>
            <firstName>Arcangelo</firstName>
            <lastName>Corelli</lastName>
        </composer>
    </composers>
  25. Click the Continue button ( Continue button ) in the debugger toolbar, then return to the browser to examine the result. You should find that the returned names match the XML <composer> entries found in the HTTP Response.
    Autocomplete dropdown displaying composer names
  26. Click the Finish Debugger Session ( Finish Debugger Session button ) button to terminate the session.

Step 2: Porting Data to a MySQL Database

This step demonstrates how it is possible to create a database instance and database tables, then add data to tables from the IDE. It also describes how to hook the newly-created database up to the sample application.

In order to complete this step, there are 3 tasks that you'll need to accomplish:

  1. Create a database and populate it with data,
  2. Create a data source and connection pool,
  3. Modify the application to use a data access object.

Create a database and populate it with data

  1. In the Services window (Ctrl-5; ⌘-5 on Mac), right-click the MySQL Server node and choose Create Database.
  2. In the window that displays, type in composer to name the new database, then click OK. (The "Grant Full Access To" option will allow any user from the localhost domain access to the database.)
    Create MySQL Database dialog
    Upon clicking OK, the new composer database displays in the Services window.
    MySQL composer database node in Services window
    Also note that a connection to the composer database is automatically established and displays lower down in the Services window.
    Database connection node in Services window
    Note: If for any reason the IDE displays a broken connection ( Broken Connection icon ) node, right-click the node and choose Connect.
  3. Expand the composer connection node, then right-click the Tables folder ( Tables node ) node and choose Create Table. The Create Table dialog displays.

    Now, we want to use the Create Table dialog to define the database table that will maintain composer records for our application. So far, the application maintains these records in a simple HashMap which resides in the ComposerData class.
  4. In the Projects window, expand the Source Packages > com.ajax node, then double-click ComposerData to open the class in the editor. The composers HashMap is defined as follows:
    private HashMap composers = new HashMap();
    
    public ComposerData() {
    
        composers.put("1", new Composer("1", "Johann Sebastian", "Bach", "Baroque"));
        composers.put("2", new Composer("2", "Arcangelo", "Corelli", "Baroque"));
        composers.put("3", new Composer("3", "George Frideric", "Handel", "Baroque"));
        composers.put("4", new Composer("4", "Henry", "Purcell", "Baroque"));
        composers.put("5", new Composer("5", "Jean-Philippe", "Rameau", "Baroque"));
        composers.put("6", new Composer("6", "Domenico", "Scarlatti", "Baroque"));
        composers.put("7", new Composer("7", "Antonio", "Vivaldi", "Baroque"));
    
        ...
    As you can see, each Composer object contains 4 pieces of information: an ID, first name, last name, and the period to which each belongs. To mirror this when we create the database table, we'll apply the id as a primary key, and add 3 other columns of type VARCHAR to capture the rest of the information.
  5. Enter the following data into the Create Table dialog. (Click the Add column button to create a new row in the dialog.)
    Create Table dialog
  6. Click OK. Notice that a new composer table now displays in the Services window. Expand the composer table node, and you'll see the columns that we just defined.
  7. Right-click the composer table node and choose View Data.

    The SQL editor displays with the query, "select * from composer.composer". Beneath the query, a visual representation of the table data is displayed.
  8. Let's add some data to the table. We effectively want to fill the table with all of the data contained in the HashMap from the ComposerData class.

    Click the Insert Records icon ( Insert Records icon ) located in the upper left corner of the table that displays the data, then in the Insert Records dialog, copy over the details from the HashMap. For example:
    Insert Records dialog
    Tip: Click the Show SQL button to view to SQL code that will be applied.
  9. Click the Add Row button to add space for new rows. Note that while you certainly do not have to add all 50 composer records to the table, in order to demonstrate a "filter effect" later when typing characters into the autocomplete field, you'll naturally need enough names for this to seem effective.

    Note: This lab includes an SQL script containing all 50 records, which you can run on your connection to the composer database. If you'd like to run the script, open composer.sql in the IDE (Choose File > Open File from the IDE's main menu, and select the script). When the script contents display in the editor, select the composer connection from the Connection drop-down list above the editor, then click the Run SQL ( Run SQL button ) button.

    Click OK when you are finished. The table updates to display all the records you just added.
    Composers table containing sample data
    At this stage, we've successfully managed to create a new database and database table that contains the data needed for the sample application. The following steps outline what is necessary to link the database table to the application. Specifically, we need to create a data source and connection pool on the server, reference the data source from the application's web.xml deployment descriptor, and create a data access object within the application that is capable of performing queries on the database.

Create a data source and connection pool

Using Java EE 5 technology, the accepted way to implement communication between a relational database and application server is to create a data source (i.e., a DataSource object), and initiate connection pooling on the server. Creating a new, physical connection for each client request can be resource-expensive, especially for applications that continuously receive a large number of requests. To remedy this, a group of reusable connections are created and maintained in a connection pool. When the application requires access to the data layer, it uses its DataSource object to obtain a connection from the connection pool (using the getConnection() method). Likewise, when the application eventually closes the connection (using the close() method on the connection object), the connection is returned to the connection pool.

  1. Add definitions to the application for creating a new data source and connection pool. When using GlassFish, you can do this by adding definitions to a sun-resources.xml file. This will instruct the server to create the artifacts when the project gets deployed. The IDE enables you to do this with the help of a wizard.

    Tip: You could also create the data source and connection pool directly within the GlassFish Admin Console. Exercise 3 explores this method in more detail.

    Click the New File icon ( New File icon ).
  2. In the New File wizard, select the GlassFish category, then select JDBC Resource and click Next.
  3. In Step 2 - General Attributes, select the Create New JDBC Connection Pool option. Doing so has the effect of adding 3 new steps to the wizard.

    For JNDI Name, type in jdbc/composers. This will be the name used by the data source. Optionally, add a description for the data source.
    New JDBC Resource wizard - Step 2
  4. Click Next, and click Next again to move onto Step 4 - Choose Database Connection.

    Type in composerPool for JDBC Connection Pool Name. Then, under the Extract from Existing Connection option, select the database connection that we created previously. This is: jdbc:mysql://localhost:3306/composer.
    New JDBC Resource wizard - Step 4
  5. Click Next, and in Step 5 - Add Connection Pool Properties, accept all default settings. Note that required properties include a valid username and password (i.e., root and nbuser) to enable the data source access the database.
    New JDBC Resource wizard - Step 5
  6. Click Finish. A sun-resources.xml file is added to your project containing the data source and connection pool declarations you specified in the wizard.

    Expand the Server Resources node and double-click the sun-resources.xml file to view its contents in the editor. You'll see the two definitions we just specified in the wizard (formatted differently for readability).
    <resources>
    
      <jdbc-resource enabled="true"
                     jndi-name="jdbc/composers"
                     object-type="user"
                     pool-name="composerPool">
            <description>data source used to connect to the composer database</description>
      </jdbc-resource>
    
      <jdbc-connection-pool allow-non-component-callers="false"
                            associate-with-thread="false"
                            connection-creation-retry-attempts="0"
                            connection-creation-retry-interval-in-seconds="10"
                            connection-leak-reclaim="false"
                            connection-leak-timeout-in-seconds="0"
                            connection-validation-method="auto-commit"
                            datasource-classname="com.mysql.jdbc.jdbc2.optional.MysqlDataSource"
                            fail-all-connections="false"
                            idle-timeout-in-seconds="300"
                            is-connection-validation-required="false"
                            is-isolation-level-guaranteed="true"
                            lazy-connection-association="false"
                            lazy-connection-enlistment="false"
                            match-connections="false"
                            max-connection-usage-count="0"
                            max-pool-size="32"
                            max-wait-time-in-millis="60000"
                            name="composerPool"
                            non-transactional-connections="false"
                            pool-resize-quantity="2"
                            res-type="javax.sql.DataSource"
                            statement-timeout-in-seconds="-1"
                            steady-pool-size="8"
                            validate-atmost-once-period-in-seconds="0"
                            wrap-jdbc-objects="false">
            <property name="URL" value="jdbc:mysql://localhost:3306/composer"/>
            <property name="User" value="root"/>
            <property name="Password" value="nbuser"/>
      </jdbc-connection-pool>
      
    </resources>
    Creating definitions for the data source and connection pool in the sun-resources.xml file is a way to create these resources without needing to interact directly with the server - it is a handy approach that ensures these resources will exist whenever the application is deployed.

    However, just because you've created references in a file that exists within your project does not mean that the application will know about these resources when it is running. To make the application aware of the data source that will exist on the server, we need to add a reference to its web.xml deployment descriptor.
  7. Double-click the WEB-INF > web.xml file to open it in the editor.
  8. Click the References tab along the top of the editor. Expand the Resource References heading if it is not already expanded, then click the Add button.
  9. Enter the details for the jdbc/composers data source which you specified earlier.
    Add Resource Reference dialog
  10. Click OK, then click the XML tab. Notice that the following reference has been added to web.xml.
    <resource-ref>
        <description>data source used to connect to the composer database</description>
        <res-ref-name>jdbc/composers</res-ref-name>
        <res-type>javax.sql.DataSource</res-type>
        <res-auth>Container</res-auth>
        <res-sharing-scope>Shareable</res-sharing-scope>
    </resource-ref>

Modify the application to use a data access object

The only remaining tasks are to code in a data access object that performs specific queries for the application, then swap the data access object for the ComposerData class in the servlet.

The data access object - ComposerDBAO - has already been prepared for you and can be found in the com.ajax package. Open it in the editor and examine its contents. Specifically, you'll see that it contains two data access methods that perform an SQL query on the composer database. These are:

  • getComposers(String userInput): Returns a list of Composer objects whose names begin with userInput
  • getComposerDetails(String id): Returns a Composer object whose id matches the id argument

Perform the following steps:

  1. Replace the ComposerDBAO object for the ComposerData object in the ComposerServlet. To do this, we are going to take advantage of the IDE's Diff Viewer.

    You already have a modified version of ComposerServlet that uses the ComposerDBAO class. It can be found in the Ex1_Scriptaculous_MySQL project, which you should have opened in the Projects window. If not, you should use the IDE's Open Project wizard now to open it in the IDE. (Instructions on how to do this were provided in Exercise 0).

    In the Projects window, click the ComposerServlet class in the Ex1_Scriptaculous_MySQL project, then Ctrl-click (⌘-click on Mac) the ComposerServlet class in the Ex1_Scriptaculous_Start project. When both nodes are highlighted, right-click the latter node and choose Tools > Diff.
    Tool option displayed from right-click menu of Projects window nodes

    A Diff viewer displays in the editor, comparing the two files side-by-side.
    Diff Viewer
  2. Scroll through the two files and note the changes required to make use of the ComposerDBAO object instead of the ComposerData object.

    Note: Make sure you have the Ex1_Scriptaculous_Start version of the class on the right side of the Diff Viewer. If not, click the Swap button located in the toolbar above the Diff. Any changes you commit in the Diff Viewer are made from the file on the left to the file on the right, so in the next step you will be overwriting the Ex1_Scriptaculous_Start version with the Ex1_Scriptaculous_MySQL version.
  3. When you are finished examining the two files, click the arrow ( Diff Viewer arrow ) located between the two files at the top of the viewer. All differences found between the two versions are eliminated, and your Ex1_Scriptaculous_Start version now appears identical to the Ex1_Scriptaculous_MySQL version.
    Diff Viewer
    Note: Now that you have successfully replaced references to the ComposerData class for those of the ComposerDBAO class, your project no longer needs ComposerData. At this point, you can safely delete it. To do so, right-click the ComposerData node in the Projects window and choose Delete.
  4. Click the Run Main Project ( Run Main Project button ) button in the IDE's toolbar to run the project.

    When you type in characters in the autocomplete field, the application now queries the database for matches.

    Note: The contents of your project should now be identical to those of the Ex1_Scriptaculous_MySQL project. This exercise continues by asking you to set Ex1_Scriptaculous_MySQL as your main project. If for any reason you have not gotten your project to run properly, you can use the IDE's Diff Viewer as you did above to compare individual files and locate the problem.

Troubleshooting:

  • You receive the error:
    No object bound to name java:comp/env/jdbc/composers
    StandardWrapperValve[ComposerServlet]: PWC1406:
    Servlet.service() for servlet ComposerServlet threw exception java.lang.NullPointerException


    This can be caused if you did not set a password for root access to the MySQL database. If this is the case, see instructions for setting the root password for the MySQL database server.
  • You receive a "No suitable driver" error. While it is true that this exercise has not discussed adding the MySQL Connector/J driver's JAR file to the project's classpath, the IDE's interoperability with GlassFish provides a feature that detects at deployment whether the driver JAR file has been added - and if not, it does so automatically. Make sure this feature is activated; Choose Tools > Servers, and in the Servers window, select GlassFish V2 in the left panel. Then, in the right panel, select the Options tab and ensure that the "JDBC Driver(s) Deployment Enabled" option is selected. If not, select it, then restart the server.

Step 3: Applying Script.aculo.us' Ajax Autocompleter.

As Script.aculo.us' official documentation wiki states, the Ajax Autocompleter allows for server-powered autocompleting text fields. It makes use of Prototype's Ajax core options to enable asynchronous communication between client and server.

This step explores how it is possible to apply the widget to the project in place of the hand-coded autocomplete functionality that currently exists. This is not simply a matter of "swapping" one for the other however, since the server currently responds with composer elements in XML format while the Ajax Autocompleter expects data in the form of an HTML list. We'll therefore need to modify the servlet to accomodate these requirements.

In order to integrate the widget into the project, you need to perform the following tasks:

  1. Add the Script.aculo.us toolkit to your project
  2. Link to the toolkit's resources from a web page
  3. Add and configure the widget
  4. Modify server-side resources to output data in the appropriate format

Note: If you were unable to get the project to run properly after completing the previous step, you should work from the Ex1_Scriptaculous_MySQL project from this point onward. If you want to use the Ex1_Scriptaculous_MySQL project, right-click its node in the Projects window and choose Set as Main Project.


Add the Script.aculo.us toolkit to your project

  1. In the Projects window, right-click the Ex1_Scriptaculous_MySQL project node and choose Set as Main Project.
  2. Right-click your project node and choose Properties.
  3. In the Project Properties window, select the JavaScript Libraries category, then click the Add button.

    The Add JavaScript Libraries dialog displays, listing all JavaScript libraries registered with the IDE (excluding any libraries already added to the project).
  4. Select the Script.aculo.us library and note that the path where the library will be extracted into your project is displayed.
    Add JavaScript Libraries dialog
    Note: By default, the IDE extracts JavaScript libraries to a web/resources folder within your project. You could modify the path by clicking the ellipsis button ( ellipsis button ) in the above dialog, then navigating to a new path.
  5. Click OK. When you do so, the library is extracted to the specified location in your project.
  6. Click OK to exit the Project Properties window. In your Projects window, note that a scriptaculous_1.8.1 node is now listed under Web Pages > resources. If you drill down into the node, you can see the Script.aculo.us core libraries, as well as the Prototype framework script, which the Script.aculo.us toolkit is based on.
    Projects window
    The Script.aculo.us library is now added to your project.

Link to the toolkit's resources from a web page

  1. Open the index.jsp file in the editor (Use Ctrl-Tab if the file is already opened, otherwise double-click the node from the Projects window).
  2. Add the following <script> tags between the page's <head> tags:
    <script src="resources/scriptaculous_js_1.8.1/lib/prototype.js" type="text/javascript"></script>
    <script src="resources/scriptaculous_js_1.8.1/src/scriptaculous.js?load=effects,controls"
            type="text/javascript"></script>
    The references to scriptaculous.js and prototype.js are generally required whenever you plan to apply Script.aculo.us widgets and functionality to a page. The scriptaculous.js file is a loader script which by default loads all other javascript files from the library. Here, we are limiting the files that are loaded by specifying load=effects,controls.

Add and configure the widget

  1. Start by modifying the HTML markup. Make the following changes between the page's <body> tags (changes in red):
    <body onload="init()">
        <h1>Auto-Completion using AJAX</h1>
    
        <h3>Script.aculo.us Autocompleter with a MySQL Database</h3>
    
        <p>This example shows how you can perform real time auto-completion using the
            <a href="http://wiki.github.com/madrobby/scriptaculous/ajax-autocompleter">Script.aculo.us
            Ajax Autocompleter</a>.</p>
    
        <p>In the form below enter a name. Possible names that will be completed are displayed
            below the form. For example, try typing in "Bach," "Mozart," or
            "Stravinsky," then click on one of the selections to see composer details.</p>
    
        <form action="doComposers">
            <strong>Composer Name:</strong> 
    
            <input type="text" id="completeField" size="30" name="input">
    
            <div id="composerMatches" class="composerList"></div>
    
            <input type="hidden" name="action" value="getComposer">
        </form>
    </body>
    Note that you need to delete the call to init() within the opening <body> tag (i.e., onload="init()").

    There are some fundamental differences between the two versions. Namely, because you are relying on Script.aculo.us' functionality, you will no longer need the javascript.js file (which implements the hand-coded autocomplete field). Previously, the javascript.js file provided a way to differentiate between requests for composer name matches and those for composer details by programmatically attaching a lookup parameter and corresponding composer id to the hyperlink reference of each composer name from the autocomplete list (e.g., Ex1_Scriptaculous_Start/doComposers?action=lookup&id=7 ). Because we are no longer relying on javascript.js, we need some other way to differentiate between the two possible requests coming from the index.jsp page. Here, we have accomplished this by adding a hidden field, which passes an action=getComposer parameter when the form is submitted.
    <input type="hidden" name="action" value="getComposer">
    Later, on the server-side, you will see how the servlet responds when an incoming request contains the action=getComposer attribute.
  2. Add a function call for the Ajax.Autocompleter widget. You can do so by typing in the following code between <script> tags (which you can also add between the page's <head> tags). While you're at it, also modify the page's title (changes in red):
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Script.aculo.us Autocompleter with a MySQL Database</title>
    
        <link rel="stylesheet" href="css/demo.css" type="text/css" media="screen">
        <link rel="stylesheet" href="css/stylesheet.css" type="text/css">
    
        <script src="resources/scriptaculous_js_1.8.1/lib/prototype.js" type="text/javascript"></script>
        <script src="resources/scriptaculous_js_1.8.1/src/scriptaculous.js?load=effects,controls"
                type="text/javascript"></script>
    
        <script type="text/javascript" src="javascript.js"></script>
    
        <script type="text/javascript">
            window.onload = function() {
                new Ajax.Autocompleter("completeField", "composerMatches", "doComposers");
            }
        </script>
    </head>
    Tip: To reformat your code, right-click within the editor and choose Format.

    Note that the Ajax.Autocompleter constructor contains 3 arguments. By examining the documentation wiki, you can see that the constructor requires the following syntax:
    new Ajax.Autocompleter(id_of_text_field, id_of_div_to_populate, url, options);
    Looking at the markup, it is relatively easy to make the following associations:
    • completeField is the id of the autocomplete text field,
    • composerMatches is the id of the <div> element that will be populated with composer matches, and
    • doComposers is the relative url that will be sent to the server.
  3. Finally, delete the reference to the javascript.js file in your index.jsp page, and delete the file from your project.

    Specifically, remove:
    <script type="text/javascript" src="javascript.js"></script>
    from between the page's <head> tags, then in the Projects window, right-click the javascript.js file and choose Delete.

Modify server-side resources to output data in the appropriate format

Based on changes incorporated on the client-side, our servlet specification now requires the following:

  1. HTTP Response in HTML
  2. Composer names returned in the form of an unordered list (using <ul> tags)
  3. For composer details requests, perform searches based on composer name, not id

To deal with these changes, modify the servlet and data access object as follows:

  1. Open the ComposerDBAO class in the editor. In order to perform searches based on composer name instead of id, make the following changes to the getComposerDetails() method (changes in red):
    public Composer getComposerDetails(String fullName) throws SQLException {
    
        // create SQL query
        String query = "SELECT * FROM composer WHERE '" + fullName +
                "' = CONCAT(composer.first_name,' ',composer.last_name)";
    
        ...
  2. Also, delete the call to the setString() method:
    // prepare query
    PreparedStatement ps = con.prepareStatement(query);
    ps.setString(1, id);
    The SQL query now uses MySQL's CONCAT() String function to concatenate the composer table's first_name and last_name columns, and compare them with the fullName argument.
  3. Open the ComposerServlet class in the editor. Scroll down to the processRequest() method and make the following changes (changes in red):
    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
    
        PrintWriter out;
    
        // declare variables for incoming parameters
        String userInput = request.getParameter("input");
        String userAction = request.getParameter("action");
    
        // check if userInput parameter exists
        if (userInput != null) {
            userInput = userInput.trim().toLowerCase();
        } else {
            // flag an error if userInput == null
            context.getRequestDispatcher("/error.jsp").forward(request, response);
        }
    
        // if user submits form, get composer and forward request
        if (userAction != null && userAction.equals("getComposer")) {
    
            if ((userInput != null)) {
    
                ComposerDBAO dbao = new ComposerDBAO();
                try {
                    // get target composer from data store
                    Composer c = dbao.getComposerDetails(userInput);
    
                    // put the target composer in the request scope
                    request.setAttribute("composer", c);
                } catch (SQLException ex) {
                    Logger.getLogger(ComposerServlet.class.getName()).log(Level.SEVERE, null, ex);
                }
    
                context.getRequestDispatcher("/composer.jsp").forward(request, response);
            }
        }
    
        // check if user sent empty string
        if (userAction == null && !userInput.equals("")) {
    
            out = response.getWriter();
    
            try {
                ComposerDBAO dbao = new ComposerDBAO();
    
                List<Composer> composers = dbao.getComposers(userInput);
    
                if (!composers.isEmpty()) {
    
                    out.println("<ul>");
    
                    for (Composer c : composers) {
    
                        Composer composer = c;
    
                        out.println("<li>" + composer.getFirstName() + " " + composer.getLastName() + "</li>");
                    }
                    out.println("</ul>");
    
                }
            } catch (SQLException ex) {
                Logger.getLogger(ComposerServlet.class.getName()).log(Level.SEVERE, null, ex);
            } finally {
                out.close();
            }
        }
    }
    By calling getComposerDetails(), the servlet is now able to retrieve the Composer object whose name matches fullName, and place it into the request scope so it can be accessed by the composer.jsp page.

    When dealing with composer name requests (i.e., for the autocomplete functionality), composer names are now returned in the form of an unordered list:
    List<Composer> composers = dbao.getComposers(userInput);
    
    if (!composers.isEmpty()) {
    
        out.println("<ul>");
    
        for (Composer c : composers) {
    
            Composer composer = c;
    
            out.println("<li>" + composer.getFirstName() + " " + composer.getLastName() + "</li>");
        }
        out.println("</ul>");
    
    }
    And finally, because we have removed the line
    response.setContentType("text/xml");
    from our previous implementation, the HTTP Response will employ the text/html content type by default.
  4. Click the Run Main Project ( Run Main Project button ) button in the IDE's toolbar to run the project.

    When you type in characters in the autocomplete field, Script.aculo.us' Ajax.Autocompleter performs by making asynchronous calls to the server, in a manner similar to the previous hand-coded implementation.
    Projects window


Summary

 

This exercise demonstrated how it is possible to experiment with client-server connectivity in a simple web application. Specifically, you ported data from a Java HashMap into a newly created MySQL database, then reconnected the application's front-end to the database by configuring a data source and connection pool for the server, and by modifying the servlet so that it utilized a data access object.

You then proceeded by integrating a Script.aculo.us Ajax.Autocompleter into the application, and modified the back-end to accommodate client-side changes.

You also had a chance to explore some of the tools available to you when working in the NetBeans IDE, such as the JavaScript debugger and HTTP Client Monitor, as well as the IDE's database support.

 

Back to top
Next exercise