Practical prototype and scipt.aculo.us part 27 potx

6 212 0
Practical prototype and scipt.aculo.us part 27 potx

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

Thông tin tài liệu

andrew.throwPass(23); >> "Andrew Dupont throws for 23yds." andrew.passingYards; //-> 23 andrew.throwTouchdown(39); >> "Andrew Dupont throws for 39yds." >> "TOUCHDOWN!" andrew.passingYards; //-> 62 andrew.points; //-> 6 Everything works as expected. So let’s try another position. A wide receiver plays on offense and catches passes thrown by the quarterback. var WideReceiver = Class.create(Player, { initialize: function(firstName, lastName) { // call Player's initialize method $super(firstName, lastName); // define properties for receivers this.receivingYards = 0; }, catchPass: function(yards) { console.log(this + ' catches a pass for ' + yards + 'yds'); this.receivingYards += yards; }, catchTouchdown: function(yards) { this.catchPass(yards); console.log('TOUCHDOWN!'); this.scorePoints(6); } }); Notice again that we’re not writing copy-and-paste code. Our WideReceiver class defines only those methods and properties that are unique to wide receivers, deferring to the Player class for everything else. CHAPTER 7 ■ ADVANCED JAVASCRIPT: FUNCTIONAL PROGRAMMING AND CLASS-BASED OOP 147 Monkeypatching Most OOP-like languages treat classes as static—once they’re defined, they’re immutable. In JavaScript, however, nothing is immutable, and it would be silly to pretend otherwise. Instead, Prototype borrows from Ruby once again. In Ruby, all classes are mutable and can be changed at any point. This practice is referred to as “monkeypatching” by those who deride it; I’ll refer to it that way simply because I like words that contain monkey. But there’s no negative connotation for me. Each class object comes with a method, addMethods, that lets us add instance meth- ods to the class later on: Player.addMethods({ makeActive: function() { this.isActive = true; console.log(this + " will be a starter for Sunday's game."); }, makeReserve: function() { this.isActive = false; console.log(this + " will spend Sunday on the bench."); } }); So now we’ve got two new methods on the Player class for managing team lineups. But these methods also propagate to Player’s two subclasses: Quarterback and WideReceiver. Now we can use makeActive and makeReserve on all instances of these classes—even the instances we’ve already created. Remember the narcissistic instance of Quarterback I created? andrew.makeReserve(); >> "Andrew Dupont will spend Sunday on the bench." andrew.isActive; //-> false Don’t take this freedom as license to code in a style that is stupid and pointless. Most of the time you won’t need to monkeypatch your own code. But Class#addMethods is quite useful when dealing with code that isn’t yours—a script.aculo.us class, for example, or another class defined by a Prototype add-on. Usage: DOM Behavior Pattern Prototype’s advanced OOP model is the perfect tonic for the headache of managing a complex behavior layer. For lack of a better term, I’m going to refer to this as the behavior CHAPTER 7 ■ ADVANCED JAVASCRIPT: FUNCTIONAL PROGRAMMING AND CLASS-BASED OOP148 pattern—a class describes some abstract behaviors, and instances of that class apply the behavior to individual elements. I’ll rephrase that in plain English. In Chapter 6, we wrote some JavaScript to manage computing totals in a data table: given a table of Texas cities alongside their populations, our code added together all the population numbers, and then inserted the total in a new row at the bottom of the table. The function we wrote got the job done, but was written with specifics in mind. In the interest of reusing code, let’s refactor this function. We’ll make the logic more generic and place it into a class that follows the behavior pattern. function computeTotalForTable(table) { // Accept a DOM node or a string. table = $(table); // Grab all the cells with population numbers in them. var populationCells = table.select('td.number'); // Add the rows together. // (Remember the Enumerable methods?) var totalPopulation = populationCells.inject(0, function(memo, cell) { var total = cell.innerHTML; // To add, we'll need to convert the string to a number. return memo + Number(total); }); // We've got the total, so let's build a row to put it in. var tr = new Element('tr', { 'class': 'total' }); tr.insert( new Element('th').update('Total') ); tr.insert( new Element('td', { 'class': 'number' } ).update(totalPopulation) ); // Insert a cell for the airport code, but leave it empty. tr.insert( new Element('td', { 'class': 'code' }) ); table.down('tbody').insert(tr); } What can we improve upon? How can we make this code more generic and versatile? CHAPTER 7 ■ ADVANCED JAVASCRIPT: FUNCTIONAL PROGRAMMING AND CLASS-BASED OOP 149 • We should not make assumptions about the layout of the table, nor the order of its columns, nor the way numeric cells are marked. The function assumes it ought to add all cells with a class of number, but what if that selector is too simplistic for what we need? • To display the total, we build a whole table row in JavaScript, complete with cells, and then insert it at the bottom of the table. This approach isn’t very DRY. What happens when we add a column to this table in the HTML? There’s a good chance we’ll forget to make the same change in the JavaScript. Above all, we should rewrite this code to be more lightweight. Simple things are reusable; complex things are often context specific. We can add more complexity later if need be, but the best foundation is a simple one. Refactoring What are the simplest possible solutions to the preceding issues? • We should be able to specify a CSS selector that describes which elements (within the context of a certain container) we want totaled. It can have a default (like .number) for convenience. • Instead of building our own DOM structure to place the total into, let’s take our cue from the HTML. In other words, the class should accept an existing element on the page for displaying the total. This limits the responsibility of the class, simplifying the code. So let’s write a class called Totaler, starting off with the bare skeleton: var Totaler = Class.create({ initialize: function() { } }); How many arguments should it take? We need at least two things: the context ele- ment and the “total container” element. Any other parameters can fall back to intelligent defaults, so we’ll place them in an options argument at the end. CHAPTER 7 ■ ADVANCED JAVASCRIPT: FUNCTIONAL PROGRAMMING AND CLASS-BASED OOP150 var Totaler = Class.create({ initialize: function(element, totalElement, options){ this.element = $(element); this.totalElement = $(totalElement); } }); So what are these “options” I speak of? First of all, we should be able to tell Totaler which elements to pull numbers from. So one of them we’ll call selector, and by default it will have a value of ".number". var Totaler = Class.create({ initialize: function(element, totalElement, options) { this.element = $(element); this.totalElement = $(totalElement); this.options = { selector: ".number" }; } }); Now we’ve got a default value for selector, but we also want the user to be able to override this. So let’s copy the options argument over this.options, letting all user- specified options trump the defaults: var Totaler = Class.create({ initialize: function(element, totalElement, options) { this.element = $(element); this.totalElement = $(totalElement); this.options = Object.extend({ selector: ".number" }, options || {}); } }); We type options || {} because the user is allowed to omit the options argument entirely: it’s akin to saying, “Extend this.options with options or, failing that, an empty object.” Remember that this refers to the class instance itself. So we’ve defined three proper- ties on the instance, corresponding to the three arguments in our constructor. These properties will be attached to each instance of the Totaler class as it gets instantiated. But to do the actual adding, we’ll write another method: CHAPTER 7 ■ ADVANCED JAVASCRIPT: FUNCTIONAL PROGRAMMING AND CLASS-BASED OOP 151 var Totaler = Class.create({ initialize: function(element, totalElement, options) { this.element = $(element); this.totalElement = $(totalElement); this.options = Object.extend({ selector: ".number" }, options || {}); }, updateTotal: function() { } }); Totaler#updateTotal will select the proper elements, extract a number out of each, and then add them all together, much the same way as before. It needn’t take any argu- ments; all the information it needs is already stored within the class. First, it selects the elements by calling Element#select in the context of the container element. var Totaler = Class.create({ initialize: function(element, totalElement, options) { this.element = $(element); this.totalElement = $(totalElement); this.options = Object.extend({ selector: ".number" }, options || {}); }, updateTotal: function() { var numberElements = this.element.select(this.options.selector); } }); Totaler#updateTotal uses the selector we assigned in the constructor; anything set as a property of this can be read both inside and outside the class. It selects all elements within element (the container) that match the given selector. As before, we use Enumerable#inject to add the numbers together: CHAPTER 7 ■ ADVANCED JAVASCRIPT: FUNCTIONAL PROGRAMMING AND CLASS-BASED OOP152 . Class#addMethods is quite useful when dealing with code that isn’t yours—a script .aculo. us class, for example, or another class defined by a Prototype add-on. Usage: DOM Behavior Pattern Prototype s advanced. andrew.throwPass(23); >> "Andrew Dupont throws for 23yds." andrew.passingYards; //-> 23 andrew.throwTouchdown(39); >> "Andrew Dupont throws. created? andrew.makeReserve(); >> "Andrew Dupont will spend Sunday on the bench." andrew.isActive; //-> false Don’t take this freedom as license to code in a style that is stupid and

Ngày đăng: 03/07/2014, 01:20

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

  • Đang cập nhật ...

Tài liệu liên quan