AJAX In Action [199]
We’ve decided to put the broker of the events into a dedicated controller class. There’s nothing new or revolutionary about it, but it’s definitely a helpful way to separate class responsibilities. In reality, the design could be further separated by creating explicit classes for the model and view roles to provide a full MVC pattern. This exercise is left to the user, but we will break down the Licensed to jonathan zheng 408 CHAPTER 10 Type-ahead suggest architecture of the RSS reader in chapter 13 with a set of classes that satisfies a traditional MVC pattern. The controller is constructed in the same way as our main class—using Class.create() and an initialize() method. The constructor is shown in listing 10.26. Listing 10.26 The TextSuggestKeyHandler constructor TextSuggestKeyHandler = Class.create(); TextSuggestKeyHandler.prototype = { initialize: function( textSuggest ) { this.textSuggest = textSuggest; Reference to TextSuggest this.input = this.textSuggest.textInput; this.addKeyHandling(); }, // rest of API }, Upon construction, the controller holds a reference to the suggest component along with the native HTML form input field. It then adds the handlers onto the input field via this.addKeyHandling(). The addKeyHandling() method is shown in listing 10.27. Listing 10.27 The keyboard handler addKeyHandling: function() { this.input.onkeyup = this.keyupHandler.bindAsEventListener(this); this.input.onkeydown = this.keydownHandler.bindAsEventListener(this); this.input.onblur = this.onblurHandler.bindAsEventListener(this); if ( this.isOpera ) this.input.onkeypress = this.keyupHandler.bindAsEventListener(this); }, All the relevant events that we need to listen to along with the Opera-specific hack mentioned in the first round of our script development are set up in this method. You will recall that the bindAsEventListener() method is a closure mechanism provided courtesy of the Prototype library. This mechanism allows Licensed to jonathan zheng Refactoring 409 our handlers to call first-class methods on the controller and normalizes the IE and W3C event models. Very nice, indeed. keyupHandler(), keydownHandler(), onblurHandler(), and their helper methods are mostly a repackaging of what’s already been covered with a few changes. We’ll show the full range of methods next and point out differences from the original script along the way. We’ll start by discussing keydownHandler() and its manipulation of the selection. The keydownHandler() method is shown in listing 10.28. Listing 10.28 keydownHandler() method keydownHandler: function(e) { var upArrow = 38; var downArrow = 40; if ( e.keyCode == upArrow ) { this.textSuggest.moveSelectionUp(); setTimeout( this.moveCaretToEnd.bind(this), 1 ); } else if ( e.keyCode == downArrow ) { this.textSuggest.moveSelectionDown(); } }, The most significant difference from the original script in terms of functionality is in the handling of the arrow keys. The arrow keys in our TextSuggest component handle the movement of the selection based on the onkeydown event rather than the onkeyup event. This is done solely as a usability improvement. It’s somewhat disconcerting to see the selection remain where it is when you press one of the arrow keys, only to see it move once you release the key. keydownHandler() therefore handles the movement of the selection. Note that the selection manipulation methods are methods of the TextSuggest component. The controller, because it saved a reference to the component at construction time, can call these methods through the saved object reference this.textSuggest. The selection manipulation methods of TextSuggest are shown in listing 10.29 for the sake of completeness. Listing 10.29 TextSuggest selection manipulation methods moveSelectionUp: function() { if ( this.selectedIndex > 0 ) { this.updateSelection(this.selectedIndex - 1); } }, Licensed to jonathan zheng 410 CHAPTER 10 Type-ahead suggest