1. Trang chủ
  2. » Công Nghệ Thông Tin

Practical prototype and scipt.aculo.us part 27 potx

6 214 0
Tài liệu đã được kiểm tra trùng lặp

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

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 6
Dung lượng 90,55 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

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.logth

Trang 1

>> "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 WideReceiverclass

defines only those methods and properties that are unique to wide receivers, deferring to

the Playerclass for everything else

Trang 2

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 Playerclass for managing team lineups But these methods also propagate to Player’s two subclasses:Quarterbackand

WideReceiver Now we can use makeActiveand makeReserveon all instances of these

classes—even the instances we’ve already created Remember the narcissistic instance

ofQuarterbackI 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#addMethodsis 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

Trang 3

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?

Trang 4

• 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” eleele-ment Any other parameters can fall back to intelligent defaults, so we’ll place them in an optionsargument at the end

Trang 5

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 optionsargument 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 optionsargument

entirely: it’s akin to saying, “Extend this.optionswith optionsor, failing that, an empty

object.”

Remember that thisrefers 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 Totalerclass as it gets instantiated But

to do the actual adding, we’ll write another method:

Trang 6

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#updateTotalwill 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#selectin 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#updateTotaluses the selector we assigned in the constructor; anything set as

a property of thiscan be read both inside and outside the class It selects all elements

As before, we use Enumerable#injectto add the numbers together:

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