Ajax in Action phần 8 pdf

68 277 0
Ajax in Action phần 8 pdf

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

Implementing DHTML windows 447 With the portal windows open, we can test the functionalities built into the DHTML library without the Ajax functionality we are going to add next. Here are some of the things we can do: ■ Maximize and minimize the windows by clicking on the button labeled with an O. ■ Hide the window by clicking the button labeled with an X. ■ Open the DHTML window into its own pop-up window by clicking on the w. ■ Drag the window by holding down the left mouse button on the title bar and moving the mouse around. Releasing the mouse button stops the drag operation. ■ Resize the window by clicking on the green box on the lower-right corner and dragging it around the screen. You can see that the windows in figure 11.13 are in different positions than in fig- ure 11.12. Now that we have the ability to position and resize the windows with the library, we need to make our changes to the external .js file. The changes will allow us to call a function that utilizes Ajax to send the new property values to our database. Figure 11.13 Ajax portal showing windows with different positions Licensed to jonathan zheng <yiyisjun@gmail.com> 448 CHAPTER 11 The enhanced Ajax web portal 11.5 Adding Ajax autosave functionality Using Ajax allows us to implement an autosave feature that can be fired by any event without the user knowing that it is happening. Normally, the user would have to click a button to force a postback to the server. In this case, we will be fir- ing the autosave with the onmouseup event, which ends the process of dragging and resizing events. If we were to fire a normal form submission on the onmouseup event, the user would lose all of the functionality of the page, disrupting her workflow. With Ajax, the flow is seamless. 11.5.1 Adapting the library As we mentioned earlier, the code from JavaScript DHTML libraries is normally cross-browser compliant, which frees us from spending time getting cross-browser code to work correctly. If you look at the code in the external JavaScript file, Ajax- Window.js, you’ll see a lot of functionality (which we will not discuss here because of its length). There are functions that monitor the mouse movements, and one function that builds the windows. There are functions that set the position of the windows, and another function that sets the size. Out of all of these functions, we need to adapt only one to have our window save back to the database with Ajax. Adapting the DHTML library for Ajax The DHTML library functions for dragging and resizing windows use many event handlers and DOM methods to overcome the inconsistencies between browsers. The dragging and resizing of the windows is completed when the mouse button is released (“up”). Therefore, we should look for a function that is called with the onmouseup event handler in the AjaxWindow.js file. It contains the following code, which is executed when the mouse button is released: document.onmouseup = function(){ bDrag = false; bResize = false; intLastX = -1; document.body.style.cursor = "default"; elemWin=""; bHasMoved = false; } In this code, a lot of booleans are being set to false to indicate that their actions have been canceled. The cursor is being set back to the default. The line that we need to change is the one where the elemWin reference is being canceled. At this point, we want to take the reference and pass it to another function to initialize our XMLHttpRequest object, in order to transfer the information to the server. Licensed to jonathan zheng <yiyisjun@gmail.com> Adding Ajax autosave functionality 449 Although sometimes when we adapt libraries, it might take a lot of trial and error to adapt them to our needs, in this case, the functionality is pretty straightfor- ward. Just add the following line, shown in bold, to your document’s onmouseup event handler: document.onmouseup = function(){ bDrag = false; bResize = false; intLastX = -1; document.body.style.cursor = "default"; if(elemWin && bHasMoved)SaveWindowProperties(elemWin); bHasMoved = false; } The bold line in the previous code snippet checks to make sure that the object has been moved or resized and that the element still exists. If the user did not per- form either of these actions, then there would be no reason to send the request to the server. If one of these actions was performed, we pass the element reference to the function SaveWindowProperties() , which initiates the request to the server. Obtaining the active element properties After the user has moved or resized an element, we must update the server with the new parameters. The DHTML window library uses CSS to position the ele- ments and to set their width and height. This means that all we have to do is obtain the database ID, the coordinates, and the size of the window. We can obtain the coordinates and size by looking at the CSS parameters assigned to the window that had focus. We then can take these new parameters and send them to the server to be saved in the database with Ajax (listing 11.10). function SaveWindowProperties(){ winProps = "ref=" + elemWin.id; winProps += "&x=" + parseInt(elemWin.style.left); winProps += "&y=" + parseInt(elemWin.style.top); winProps += "&w=" + parseInt(elemWin.style.width); winProps += "&h=" + parseInt(elemWin.style.height); Settings("saveSettings",winProps); elemWin = ""; } Listing 11.10 SaveWindowProperties() function b Obtain window ID c Find window position d Grab window size e Call Settings function f Remove element reference Licensed to jonathan zheng <yiyisjun@gmail.com> 450 CHAPTER 11 The enhanced Ajax web portal As you can see in listing 11.11, we obtain the ID of the window b by referencing the window object. The ID that we obtained was assigned to the window when the library built it. When it assigns an ID, it appends win in front of the number from the database id column; we can see that by looking at the JavaScript code that is building the windows. The x and y positions of the window are obtained c by referencing the left and top properties in the stylesheet. We also use the stylesheet properties to obtain the size d of the window by referencing its width and height properties. After obtaining the information, we can call another function, Settings() e , which we will be creating shortly, to send our request to the server. Once we call the function, we should remove the element object from our global variable elem- Win f . To do this, we assign an empty string to the variable elemWin . Now with the SaveWindowProperties() function complete, we can initiate our silent Ajax request to the server with the JavaScript function Settings() . 11.5.2 Autosaving the information to the database Ajax lets us send information to the server without the user even knowing it is happening. We can see this in action with two projects in this book. We can easily submit requests to the server as a result of both monitoring keystrokes, as we do in the type-ahead suggest (chapter 10), and monitoring mouse movements, as we do in this chapter. This invisible submission is great for developers since we can update the user’s settings without him having to lift a finger. In most cases, reduc- ing steps increases the user’s satisfaction. For this application, the action of the user releasing the mouse button is all we need to initiate the XMLHttpRequest object. Now it’s time to initiate the process to send the request to the server. The client: sending the silent request The XMLHttpRequest process in this case will not require anything sophisticated. The user’s interaction with the form sends all of the form properties to our func- tion. We first need to initialize the XMLHttpRequest object: function Settings(xAction,xParams){ var url = xAction + ".servlet"; var strParams = xParams; var loader1 = new net.ContentLoader(url, BuildSettings, ErrorBuildSettings, "POST", strParams); } Licensed to jonathan zheng <yiyisjun@gmail.com> Adding Ajax autosave functionality 451 For the function Settings() , we are passing the action string that contains all of our window’s properties. We attach the parameters that we’re going to post back to the server. If we get a successful round-trip to the server, the loader will call the function BuildSettings() . If we get an error during the round-trip, we will call the function ErrorBuildSettings() : function BuildSettings(){ strText = this.req.responseText; document.getElementById("divSettings").innerHTML = strText; } function ErrorBuildSettings(){ alert('There was an error trying to connect to the server.'); document.getElementById("divSettings").style.display = "none"; } The function BuildSettings() shown here is quite basic; all we are doing is fin- ishing up our XMLHttpRequest received from the server. We can set a message on the portal status bar to show that we have updated the information on the server. We can add an error message to the status bar if we encounter a problem updating the information on the server. We also generate an alert, which tells the user of the error, but will also disrupt their workflow. We presented produc- tion-ready notification mechanisms in chapter 6, and leave it as an exercise for the reader to integrate those systems into the portal. Now let's see what hap- pens on the server. The server: gathering information from the client All we have left to do is to extract the values from our form submission. The values were sent by our XMLHttpRequest object, which was triggered by the onmouseup event handlers. We need to create our SQL query with this informa- tion and update the record in the database to save the new information. We define an UpdateServlet for this purpose, which is shown in listing 11.11. public class UpdateServlet extends HttpServlet { protected void doPost( HttpServletRequest request, HttpServletResponse response )throws ServletException, IOException{ String windowId= request.getParameter("ref"); HttpSession session=request.getSession(); PortalWindow window=(PortalWindow) (session.getAttribute ("window_"+windowId)); window.setXPos(getIntParam(request,"x")); Listing 11.11 UpdateServlet.java (mapped to 'saveSettings.servlet') b Get unique ID from request c Get Window object from session Licensed to jonathan zheng <yiyisjun@gmail.com> 452 CHAPTER 11 The enhanced Ajax web portal window.setYPos(getIntParam(request,"y")); window.setWidth(getIntParam(request,"w")); window.setHeight(getIntParam(request,"h")); DBUtil.savePortalWindow(window); Writer writer=response.getWriter(); writer.write("Save Complete"); writer.flush(); } private int getIntParam(HttpServletRequest request, String param) { String str=request.getParameter(param); int result=Integer.parseInt(str); return result; } } Given the window ID as a request parameter b , we can extract the PortalWindow from session c and update its geometry based on further request parameters. We then call another method on our DBUtil object to save the portal window set- tings in the database d . Again, the implementation that we’ve provided here in listing 11.12 has been written to be simple and easy to translate to other languages. public static void savePortalWindow(PortalWindow window){ Connection conn=getConnection(); int x=window.getXPos(); int y=window.getYPos(); int w=window.getWidth(); int h=window.getHeight(); int id=window.getId(); String sql="UPDATE portal_windows SET xPos="+x +",yPos="+y +",width="+w +",height="+h +" WHERE id="+id; try{ Statement stmt=conn.createStatement(); stmt.execute(sql); stmt.close(); }catch (SQLException sqlex){ } } The code in listing 11.12 is very straightforward. We read the relevant details from the PortalWindow object and construct a SQL update statement accord- ingly. Rather than returning any JavaScript this time, we issue a simple text acknowledgment. Listing 11.12 savePortalWindows() method d Save changes Return simple text reply Licensed to jonathan zheng <yiyisjun@gmail.com> Refactoring 453 To test the new functionality, log into the portal as our test user. Drag the windows around the screen, and resize them so they are in different positions from their defaults. Close the browser to force an end to the session. When we reopen the browser and log back into the portal as that user, we see the windows in the same position. Move the windows to a new position and look at the data- base table. We are automatically saving the user’s preferences without him even knowing it. We’ve now provided all the basic functionality for a working portal system, including a few things that a classic web application just couldn’t do. There are several other requirements that we could classify as “nice to have,” such as being able to add, remove, and rename windows. Because of limited space, we are not going to discuss them here. The full code for the portal application is available to download and includes the ability to add, delete, rename, and adjust the win- dow’s properties without leaving the single portal page. If you have any questions about the code in this section or need a more thorough understanding, you can always reach us on Manning.com’s Author Online at www.manning.com. Our code so far has been somewhat rough and ready so that we could demon- strate how the individual pieces work. Let’s hand it over to our refactoring team now, to see how to tighten things up and make the system easier to reuse. 11.6 Refactoring The concept of an Ajax-based portal client that interacts with a server-side portal “manager” is, as you’ve seen, a compelling notion. In our refactoring of this chapter’s client-side code, let’s consider our component as an entity that serves as the arbitrator of portal commands sent to the portal manager on the server. Throughout this refactoring discussion, let’s make it our goal to isolate the pieces of code that might change over time and facilitate those changes as easily as pos- sible. Since the portal is a much coarser-grained component and something that will more or less take over the real estate of our page, we won’t be so stringent with the requirement of not interrupting the HTML as we have in the previous two refactoring examples. But, before discussing the client-side semantic, let’s first stop and contemplate the contract with the server. Our previous server-side implementation was written in Java, so we had a servlet filter perform the authentication functionality: one servlet to return the window configurations, and another servlet to save window configurations. Similarly, for adding new windows and deleting the current ones, we would provide further standalone servlets. In a Java web application, the servlets Licensed to jonathan zheng <yiyisjun@gmail.com> 454 CHAPTER 11 The enhanced Ajax web portal can be mapped to URLs in a very flexible fashion, defined in the web.xml file of the web archive (.war) file. For example, our SelectServlet, which returned the script defining the initial windows, was mapped to the URL portalLogin.servlet. One of the strengths of Ajax is the loose coupling between the client and the server. Our portal example uses Java as a back-end, but we don’t want to tie it to Java-specific features such as servlet filters and flexible URL rewriting. An alter- native back-end architecture might use a request dispatch pattern, in which a sin- gle servlet, PHP page, or ASP.NET resource accepts all incoming requests and then reads a GET or POST parameter that specifies what type of action is being undertaken. For example, the URL for logging in to the portal might be por- tal?action=login&userid=user&password=password or, more likely, the equiva- lent using POST parameters. In Java, we might implement a request dispatcher approach by assigning a specific URL prefix, say .portal, to the dispatcher servlet, allowing us to write URLs such as login.portal. In our refactored component, we will generalize our assumptions about the back-end to allow either a request dispatcher architecture or the multiple address option that we used for our Java implementation. We don’t, however, need to introduce complete flexibility, so we’ll predefine a number of commands that the portal back-end will be expected to understand, covering login, showing the user’s portal windows, and adding and deleting windows from the portal. With these changes to the server in mind, let’s return our attention to the client-side implementation. Let’s begin our discussion of the portal refactoring by redefining the usage contract from the perspective of the page’s HTML; then we’ll delve into the implementation. Recall that the hook from our HTML page into the portal script was via the login, specifically through the login button: <input type="button" name="btnSub" value="login" onclick="LoginRequest('login')"> We’ll change the onclick handler to be a call to a function that will use our portal component. Let’s assume that the portal component will be instantiated via a script that executes once the page loads. A representative example of what this should look like is shown in listing 11.13. function createPortal() { myPortal = new Portal( 'portalManager', Listing 11.13 Portal creation and login b Base URL for portal Licensed to jonathan zheng <yiyisjun@gmail.com> Refactoring 455 { messageSpanId: 'spanProcessing', urlSuffix: '.portal' } ); myPortal.loadPage(Portal.LOAD_SETTINGS_ACTION); document.getElementById('username').focus(); } function login() { myPortal.login( document.getElementById('username').value, document.getElementById('password').value ); } In this usage semantic, createPortal() , which should get called once the page loads, creates an instance of the portal component. The first argument is the base URL for the portal’s server-side application b , and the second provides optional parameters used to customize it for a particular context c . In this case, we tell it the ID of the DOM element into which status messages should be written and the name of the request parameter that will denote which action to execute. Once created, an API on the portal named loadPage is called. This loads the page’s por- tal windows if there is already a user login present in the server session d . If nobody is logged in, this server will return an empty script, leaving only the login form on the screen. The login() function is just a utility function in the page that calls the login() method of our portal component, passing the username and password values as arguments. Given this contract, the login button’s onclick handler now calls the page’s login() method, as shown here: <input type="button" name="btnSub" value="login" onclick="login()"> 11.6.1 Defining the constructor Now that you have a basic understanding of how the component will be used from the perspective of the page, let’s implement the logic, starting with the constructor: function Portal( baseUrl, options ) { this.baseUrl = baseUrl; this.options = options; this.initDocumentMouseHandler(); The constructor takes the URL of the Ajax portal management on the server as its first argument and an options object for configuration as the second. In our c Optional parameters d Call to load windows Licensed to jonathan zheng <yiyisjun@gmail.com> 456 CHAPTER 11 The enhanced Ajax web portal earlier development of the script, recall that we had a servlet filter and two serv- lets perform the back-end processing. Throughout the rest of this example, we’ll assume a single servlet or resource, portalManager , which intercepts all requests to the portal back-end, as configured in listing 11.13. If we wanted to configure the portal against a back-end that didn’t use a single request dispatcher, we could simply pass different arguments to the constructor, for example: myPortal = new Portal( 'data', { messageSpanId: 'spanProcessing', urlSuffix: '.php' } ); This will pass a base URL of “data” and, because no actionParam is defined in the options array, append the command to the URL path, with the suffix .php, result- ing in a URL such as data/login.php. We’ve given ourselves all the flexibility we’ll need here. We’ll see how the options are turned into URLs in section 11.6.3. For now, let’s move on to the next task. The final line of the constructor introduces the issue of adapting the AjaxWindows.js library. 11.6.2 Adapting the AjaxWindows.js library Recall that the implementation of this portal used an external library called Ajax- Windows.js for creating the individual portal windows and managing their size and position on the screen. One of the things we had to do was to adapt the library to send Ajax requests to the portal manager for saving the settings on the mouseup event. This was the hook we needed; all move and resize operations are theoretically terminated by a mouseup event. The way we performed the adapta- tion in round one was to make a copy of the AjaxWindows.js library code and change the piece of code that puts a mouseup handler on the document. If we think of the AjaxWindow.js library as a third-party library, the drawback to this approach is evident. We’ve branched a third-party library codebase, that is, modi- fied the source code and behavior of the library in such a way that it’s no longer compatible with the version maintained by its original author. If the library changes, we have to merge in our changes with every new version we upgrade to. We haven’t done a good job of isolating this change point and making it as pain- less as possible. Let’s consider a less-radical approach of adaptation and see if we can rectify the situation. Recall the last line of our constructor: this.initDocumentMouseHandler(); Our initDocumentMouseHandler() method is an on-the-fly adaptation of the Ajax- Windows.js library. It just overrides the document.onmouseup as before, but within Licensed to jonathan zheng <yiyisjun@gmail.com> [...]... the benefit of not duplicating the logic already defined in the AjaxWindows.js library This approach is illustrated in the following code: function ajaxWindowsMouseUpHandler() { // logic here } document.onmouseup = ajaxWindowsMouseUpHandler; ajaxWindowsMouseUpHandler() is a callback defined by the AjaxWindows.js exter- nal library Using it would allow us to save the definition of the method and use... adaptation on the mouseup event in order to store all move and size operations: saveWindowProperties: function(id) { this.issuePortalCommand( Portal.SAVE_SETTINGS _ACTION, "ref=" + id, "x=" + parseInt(elemWin.style.left), "y=" + parseInt(elemWin.style.top), "w=" + parseInt(elemWin.style.width), "h=" + parseInt(elemWin.style.height) ); elemWin = null; }, Adding/deleting windows Although we didn’t fully... Portal.LOAD_SETTINGS _ACTION Portal.SAVE_SETTINGS _ACTION Portal.ADD_WINDOW _ACTION Portal.DELETE_WINDOW _ACTION = = = = = "login"; "PageLoad"; "UpdateDragWindow"; "AddWindow"; "DeleteWindow"; Even though the language doesn’t really support constants, let’s assume that based on the uppercase naming convention, these values are intended to be constant values We could lazily sprinkle these string literals throughout... commands and the formal Command pattern in Ajax, in chapters 3 and 5 Here is another opportunity to put that knowledge to use The commands that we’ve supported up to this point in our portal are logging in, loading settings, and saving settings We’re going to throw in the ability to add Licensed to jonathan zheng Refactoring 459 and delete windows, which we alluded to although we... uses Ajax introduces a new level of performance in a rich user interface In the final section of this chapter, we looked at refactoring the portal code In previous sections, we have focused on creating a reusable component that can be dropped in to an existing page In this case, that isn’t appropriate, as the portal is the shell within which other components will reside Our emphasis in these refactorings... or an image, to indicate that the request processing is happening This eliminates the chance of the user repeatedly clicking the submit button, thinking that nothing has happened, since Ajax is a “silent” process The last step is to call the function LoadXMLXSLTDoc() g, which initiates the process of sending the information to the server The LoadXMLXSLTDoc() function that we will build in section 12.4... we’ll find that the browsers differ on how they combine the two documents Therefore, we first check to see what method the browser supports in order to load and combine our two documents Again we’re using the ContentLoader object, introduced in chapter 3 It is contained in the external JavaScript file, net.js This file does all of the work of determining how to send the information to the server, hiding... Examining a live search with Ajax and XSLT We can improve the functionality of certain search features on a website by turning the search into a live search, which is how some developers are naming the functionality of Ajax searches This search is performed without posting the entire page to the server (as in the traditional search), which means that the current state of the page can be maintained In. .. sure you want to delete this window?"); if(doDelete) this.issuePortalCommand( Portal.DELETE_WINDOW _ACTION, "ref=" + id ); }, This concludes our discussion of the APIs required to support the portal commands Now let’s look at our portal Ajax handling 11.6.4 Performing the Ajax processing As already noted, in this example we’re using an Ajax technique for handling responses in a script-centric way The... refactorings has been on increasing the maintainability of the code by isolating String constants, creating some generic methods, and separating the third-party library from our project code in a cleaner way In this chapter, we’ve generated simple XML responses from the server and decoded them manually using JavaScript In the next chapter, we’ll look at an alternative approach: using XSLT stylesheets on . savePortalWindow(PortalWindow window){ Connection conn=getConnection(); int x=window.getXPos(); int y=window.getYPos(); int w=window.getWidth(); int h=window.getHeight(); int id=window.getId(); . parseInt(elemWin.style.height); Settings("saveSettings",winProps); elemWin = ""; } Listing 11.10 SaveWindowProperties() function b Obtain window ID c Find window position d Grab window size e Call. use. The commands that we’ve supported up to this point in our portal are logging in, loading settings, and saving settings. We’re going to throw in the ability to add c Call library function d Add

Ngày đăng: 09/08/2014, 12:22

Tài liệu cùng người dùng

Tài liệu liên quan