├── 3850.zip ├── 3861.pdf ├── 3862.pdf ├── LICENSE.txt ├── 9781590599082.jpg ├── Source Code ├── Chapter04 │ ├── 4.06 - The clone function.js │ ├── 4.01 - Classical inheritance.js │ ├── 4.07 - Mixin classes.js │ ├── 4.02 - The prototype chain.js │ ├── 4.08 - The augment function.js │ ├── 4.04 - Prototypal inheritance.js │ ├── 4.03 - The extend function.js │ ├── 4.05 - Asymmetrical reading and writing.js │ ├── 4.10 - Edit-in-place example, prototypal.js │ ├── 4.09 - Edit-in-place example, classical.js │ └── 4.11 - Edit-in-place example, mixin.js ├── Chapter03 │ ├── 3.01 - Book example class.js │ ├── 3.04 - Scope, nested functions, and closures.js │ ├── 3.03 - Private methods with underscores.js │ ├── 3.07 - Constants.js │ ├── 3.05 - Private methods with closures.js │ ├── 3.06 - Static members.js │ └── 3.02 - Fully exposed object.js ├── Chapter08 │ ├── 8.02 - Other examples of bridges.js │ ├── 8.03 - Bridging multiple classes together.js │ ├── 8.01 - Event listener example.js │ ├── 8.05 - XHR connection queue example page.html │ └── 8.04 - Building an XHR connection queue.js ├── Chapter06 │ ├── 6.01 - Introduction to chaining.js │ ├── 6.04 - Using callbacks.js │ ├── 6.03 - Building a chainable JavaScript library.js │ └── 6.02 - The structure of the chain.js ├── Chapter02 │ ├── 2.06 - When to use the Interface class.js │ ├── 2.03 - Emulating interfaces with duck typing.js │ ├── 2.04 - The interface implementation for this book.js │ ├── 2.01 - Describing interfaces with comments.js │ ├── 2.02 - Emulating interfaces with attribute checking.js │ ├── 2.07 - An example illustrating the use of the Interface class.js │ └── 2.05 - The Interface class.js ├── Chapter05 │ ├── 5.01 - Basic structure of the singleton.js │ ├── 5.08 - Branching.js │ ├── 5.04 - Private methods with underscores.js │ ├── 5.06 - Comparing the two techniques.js │ ├── 5.02 - Namespacing.js │ ├── 5.05 - Private methods with closures.js │ ├── 5.09 - Creating XHR objects with branching.js │ ├── 5.03 - Wrappers for page specific code.js │ └── 5.07 - Lazy instantiation.js ├── Chapter01 │ ├── 1.02 - Functions as first-class objects.js │ ├── 1.03 - The mutability of objects.js │ └── 1.01 - The flexibility of JavaScript.js └── Chapter07 │ ├── 7.05 - Choosing connection objects at run-time.js │ ├── 7.03 - XHR factory example.js │ ├── 7.02 - The factory pattern.js │ ├── 7.04 - Specialized connection objects.js │ ├── 7.06 - RSS reader example.js │ └── 7.01 - The simple factory.js ├── README.md └── contributing.md /3850.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/pro-javascript-design-patterns/HEAD/3850.zip -------------------------------------------------------------------------------- /3861.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/pro-javascript-design-patterns/HEAD/3861.pdf -------------------------------------------------------------------------------- /3862.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/pro-javascript-design-patterns/HEAD/3862.pdf -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/pro-javascript-design-patterns/HEAD/LICENSE.txt -------------------------------------------------------------------------------- /9781590599082.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/pro-javascript-design-patterns/HEAD/9781590599082.jpg -------------------------------------------------------------------------------- /Source Code/Chapter04/4.06 - The clone function.js: -------------------------------------------------------------------------------- 1 | /* Clone function. */ 2 | 3 | function clone(object) { 4 | function F() {} 5 | F.prototype = object; 6 | return new F; 7 | } 8 | -------------------------------------------------------------------------------- /Source Code/Chapter03/3.01 - Book example class.js: -------------------------------------------------------------------------------- 1 | // Book(isbn, title, author) 2 | var theHobbit = new Book('0-395-07122-4', 'The Hobbit', 'J. R. R. Tolkein'); 3 | theHobbit.display(); // Outputs the data by creating and populating an HTML element. 4 | -------------------------------------------------------------------------------- /Source Code/Chapter08/8.02 - Other examples of bridges.js: -------------------------------------------------------------------------------- 1 | var Public = function() { 2 | var secret = 3; 3 | this.privilegedGetter = function() { 4 | return secret; 5 | }; 6 | }; 7 | 8 | var o = new Public; 9 | var data = o.privilegedGetter(); 10 | -------------------------------------------------------------------------------- /Source Code/Chapter04/4.01 - Classical inheritance.js: -------------------------------------------------------------------------------- 1 | /* Class Person. */ 2 | 3 | function Person(name) { 4 | this.name = name; 5 | } 6 | 7 | Person.prototype.getName = function() { 8 | return this.name; 9 | } 10 | 11 | var reader = new Person('John Smith'); 12 | reader.getName(); 13 | -------------------------------------------------------------------------------- /Source Code/Chapter06/6.01 - Introduction to chaining.js: -------------------------------------------------------------------------------- 1 | // Without chaining: 2 | addEvent($('example'), 'click', function() { 3 | setStyle(this, 'color', 'green'); 4 | show(this); 5 | }); 6 | 7 | // With chaining: 8 | $('example').addEvent('click', function() { 9 | $(this).setStyle('color', 'green').show(); 10 | }); 11 | -------------------------------------------------------------------------------- /Source Code/Chapter08/8.03 - Bridging multiple classes together.js: -------------------------------------------------------------------------------- 1 | var Class1 = function(a, b, c) { 2 | this.a = a; 3 | this.b = b; 4 | this.c = c; 5 | } 6 | var Class2 = function(d) { 7 | this.d = d; 8 | }; 9 | 10 | var BridgeClass = function(a, b, c, d) { 11 | this.one = new Class1(a, b, c); 12 | this.two = new Class2(d); 13 | }; 14 | -------------------------------------------------------------------------------- /Source Code/Chapter02/2.06 - When to use the Interface class.js: -------------------------------------------------------------------------------- 1 | var DynamicMap = new Interface('DynamicMap', ['centerOnPoint', 'zoom', 'draw']); 2 | 3 | function displayRoute(mapInstance) { 4 | Interface.ensureImplements(mapInstace, DynamicMap); 5 | mapInstance.centerOnPoint(12, 34); 6 | mapInstance.zoom(5); 7 | mapInstance.draw(); 8 | ... 9 | } 10 | -------------------------------------------------------------------------------- /Source Code/Chapter05/5.01 - Basic structure of the singleton.js: -------------------------------------------------------------------------------- 1 | /* Basic Singleton. */ 2 | 3 | var Singleton = { 4 | attribute1: true, 5 | attribute2: 10, 6 | 7 | method1: function() { 8 | 9 | }, 10 | method2: function(arg) { 11 | 12 | } 13 | }; 14 | 15 | Singleton.attribute1 = false; 16 | var total = Singleton.attribute2 + 5; 17 | var result = Singleton.method1(); 18 | -------------------------------------------------------------------------------- /Source Code/Chapter04/4.07 - Mixin classes.js: -------------------------------------------------------------------------------- 1 | /* Mixin class. */ 2 | 3 | var Mixin = function() {}; 4 | Mixin.prototype = { 5 | serialize: function() { 6 | var output = []; 7 | for(key in this) { 8 | output.push(key + ': ' + this[key]); 9 | } 10 | return output.join(', '); 11 | } 12 | }; 13 | 14 | augment(Author, Mixin); 15 | 16 | var author = new Author('Ross Harmes', ['JavaScript Design Patterns']); 17 | var serializedString = author.serialize(); 18 | -------------------------------------------------------------------------------- /Source Code/Chapter05/5.08 - Branching.js: -------------------------------------------------------------------------------- 1 | /* Branching Singleton (skeleton). */ 2 | 3 | MyNamespace.Singleton = (function() { 4 | var objectA = { 5 | method1: function() { 6 | ... 7 | }, 8 | method2: function() { 9 | ... 10 | } 11 | }; 12 | var objectB = { 13 | method1: function() { 14 | ... 15 | }, 16 | method2: function() { 17 | ... 18 | } 19 | }; 20 | 21 | return (someCondition) ? objectA : objectB; 22 | })(); 23 | -------------------------------------------------------------------------------- /Source Code/Chapter02/2.03 - Emulating interfaces with duck typing.js: -------------------------------------------------------------------------------- 1 | // Interfaces. 2 | 3 | var Composite = new Interface('Composite', ['add', 'remove', 'getChild']); 4 | var FormItem = new Interface('FormItem', ['save']); 5 | 6 | // CompositeForm class 7 | 8 | var CompositeForm = function(id, method, action) { 9 | ... 10 | }; 11 | 12 | ... 13 | 14 | function addForm(formInstance) { 15 | ensureImplements(formInstance, Composite, FormItem); 16 | // This function will throw an error if a required method is not implemented. 17 | ... 18 | } 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Apress Source Code 2 | 3 | This repository accompanies [*Pro JavaScript Design Patterns*](http://www.apress.com/9781590599082) by Dustin Diaz and Ross Harmes (Apress, 2008). 4 | 5 | ![Cover image](9781590599082.jpg) 6 | 7 | Download the files as a zip using the green button, or clone the repository to your machine using Git. 8 | 9 | ## Releases 10 | 11 | Release v1.0 corresponds to the code in the published book, without corrections or updates. 12 | 13 | ## Contributions 14 | 15 | See the file Contributing.md for more information on how you can contribute to this repository. 16 | -------------------------------------------------------------------------------- /Source Code/Chapter05/5.04 - Private methods with underscores.js: -------------------------------------------------------------------------------- 1 | /* DataParser singleton, converts character delimited strings into arrays. */ 2 | 3 | GiantCorp.DataParser = { 4 | // Private methods. 5 | _stripWhitespace: function(str) { 6 | return str.replace(/\s+/, ''); 7 | }, 8 | _stringSplit: function(str, delimiter) { 9 | return str.split(delimiter); 10 | }, 11 | 12 | // Public method. 13 | stringToArray: function(str, delimiter, stripWS) { 14 | if(stripWS) { 15 | str = this._stripWhitespace(str); 16 | } 17 | var outputArray = this._stringSplit(str, delimiter); 18 | return outputArray; 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /Source Code/Chapter03/3.04 - Scope, nested functions, and closures.js: -------------------------------------------------------------------------------- 1 | function foo() { 2 | var a = 10; 3 | 4 | function bar() { 5 | a *= 2; 6 | } 7 | 8 | bar(); 9 | return a; 10 | } 11 | 12 | 13 | 14 | 15 | function foo() { 16 | var a = 10; 17 | 18 | function bar() { 19 | a *= 2; 20 | return a; 21 | } 22 | 23 | return bar; 24 | } 25 | 26 | var baz = foo(); // baz is now a reference to function bar. 27 | baz(); // returns 20. 28 | baz(); // returns 40. 29 | baz(); // returns 80. 30 | 31 | var blat = foo(); // blat is another reference to bar. 32 | blat(); // returns 20, because a new copy of a is being used. 33 | -------------------------------------------------------------------------------- /Source Code/Chapter02/2.04 - The interface implementation for this book.js: -------------------------------------------------------------------------------- 1 | // Interfaces. 2 | 3 | var Composite = new Interface('Composite', ['add', 'remove', 'getChild']); 4 | var FormItem = new Interface('FormItem', ['save']); 5 | 6 | // CompositeForm class 7 | 8 | var CompositeForm = function(id, method, action) { // implements Composite, FormItem 9 | ... 10 | }; 11 | 12 | ... 13 | 14 | function addForm(formInstance) { 15 | Interface.ensureImplements(formInstance, Composite, FormItem); 16 | // This function will throw an error if a required method is not implemented, 17 | // halting execution of the function. 18 | // All code beneath this line will be executed only if the checks pass. 19 | ... 20 | } 21 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing to Apress Source Code 2 | 3 | Copyright for Apress source code belongs to the author(s). However, under fair use you are encouraged to fork and contribute minor corrections and updates for the benefit of the author(s) and other readers. 4 | 5 | ## How to Contribute 6 | 7 | 1. Make sure you have a GitHub account. 8 | 2. Fork the repository for the relevant book. 9 | 3. Create a new branch on which to make your change, e.g. 10 | `git checkout -b my_code_contribution` 11 | 4. Commit your change. Include a commit message describing the correction. Please note that if your commit message is not clear, the correction will not be accepted. 12 | 5. Submit a pull request. 13 | 14 | Thank you for your contribution! -------------------------------------------------------------------------------- /Source Code/Chapter04/4.02 - The prototype chain.js: -------------------------------------------------------------------------------- 1 | /* Class Author. */ 2 | 3 | function Author(name, books) { 4 | Person.call(this, name); // Call the superclass' constructor in the scope of this. 5 | this.books = books; // Add an attribute to Author. 6 | } 7 | 8 | Author.prototype = new Person(); // Set up the prototype chain. 9 | Author.prototype.constructor = Author; // Set the constructor attribute to Author. 10 | Author.prototype.getBooks = function() { // Add a method to Author. 11 | return this.books; 12 | }; 13 | 14 | var author = []; 15 | author[0] = new Author('Dustin Diaz', ['JavaScript Design Patterns']); 16 | author[1] = new Author('Ross Harmes', ['JavaScript Design Patterns']); 17 | 18 | author[1].getName(); 19 | author[1].getBooks(); 20 | -------------------------------------------------------------------------------- /Source Code/Chapter08/8.01 - Event listener example.js: -------------------------------------------------------------------------------- 1 | addEvent(element, 'click', getBeerById); 2 | function getBeerById(e) { 3 | var id = this.id; 4 | asyncRequest('GET', 'beer.uri?id=' + id, function(resp) { 5 | // Callback response. 6 | console.log('Requested Beer: ' + resp.responseText); 7 | }); 8 | } 9 | 10 | function getBeerById(id, callback) { 11 | // Make request for beer by ID, then return the beer data. 12 | asyncRequest('GET', 'beer.uri?id=' + id, function(resp) { 13 | // callback response 14 | callback(resp.responseText); 15 | }); 16 | } 17 | 18 | addEvent(element, 'click', getBeerByIdBridge); 19 | function getBeerByIdBridge (e) { 20 | getBeerById(this.id, function(beer) { 21 | console.log('Requested Beer: '+beer); 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /Source Code/Chapter02/2.01 - Describing interfaces with comments.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | interface Composite { 4 | function add(child); 5 | function remove(child); 6 | function getChild(index); 7 | } 8 | 9 | interface FormItem { 10 | function save(); 11 | } 12 | 13 | */ 14 | 15 | var CompositeForm = function(id, method, action) { // implements Composite, FormItem 16 | ... 17 | }; 18 | 19 | // Implement the Composite interface. 20 | 21 | CompositeForm.prototype.add = function(child) { 22 | ... 23 | }; 24 | CompositeForm.prototype.remove = function(child) { 25 | ... 26 | }; 27 | CompositeForm.prototype.getChild = function(index) { 28 | ... 29 | }; 30 | 31 | // Implement the FormItem interface. 32 | 33 | CompositeForm.prototype.save = function() { 34 | ... 35 | }; 36 | -------------------------------------------------------------------------------- /Source Code/Chapter01/1.02 - Functions as first-class objects.js: -------------------------------------------------------------------------------- 1 | /* An anonymous function, executed immediately. */ 2 | 3 | (function() { 4 | var foo = 10; 5 | var bar = 2; 6 | alert(foo * bar); 7 | })(); 8 | 9 | 10 | /* An anonymous function with arguments. */ 11 | 12 | (function(foo, bar) { 13 | alert(foo * bar); 14 | })(10, 2); 15 | 16 | 17 | /* An anonymous function that returns a value. */ 18 | 19 | var baz = (function(foo, bar) { 20 | return foo * bar; 21 | })(10, 2); 22 | 23 | // baz will equal 20. 24 | 25 | 26 | /* An anonymous function used as a closure. */ 27 | 28 | var baz; 29 | 30 | (function() { 31 | var foo = 10; 32 | var bar = 2; 33 | baz = function() { 34 | return foo * bar; 35 | }; 36 | })(); 37 | 38 | baz(); // baz can access foo and bar, even though is it executed outside of the 39 | // anonymous function. 40 | -------------------------------------------------------------------------------- /Source Code/Chapter01/1.03 - The mutability of objects.js: -------------------------------------------------------------------------------- 1 | function displayError(message) { 2 | displayError.numTimesExecuted++; 3 | alert(message); 4 | }; 5 | displayError.numTimesExecuted = 0; 6 | 7 | 8 | /* Class Person. */ 9 | 10 | function Person(name, age) { 11 | this.name = name; 12 | this.age = age; 13 | } 14 | Person.prototype = { 15 | getName: function() { 16 | return this.name; 17 | }, 18 | getAge: function() { 19 | return this.age; 20 | } 21 | } 22 | 23 | /* Instantiate the class. */ 24 | 25 | var alice = new Person('Alice', 93); 26 | var bill = new Person('Bill', 30); 27 | 28 | /* Modify the class. */ 29 | 30 | Person.prototype.getGreeting = function() { 31 | return 'Hi ' + this.getName() + '!'; 32 | }; 33 | 34 | /* Modify a specific instance. */ 35 | 36 | alice.displayGreeting = function() { 37 | alert(this.getGreeting()); 38 | } 39 | -------------------------------------------------------------------------------- /Source Code/Chapter03/3.03 - Private methods with underscores.js: -------------------------------------------------------------------------------- 1 | var Book = function(isbn, title, author) { // implements Publication 2 | this.setIsbn(isbn); 3 | this.setTitle(title); 4 | this.setAuthor(author); 5 | } 6 | 7 | Book.prototype = { 8 | _checkIsbn: function(isbn) { 9 | ... 10 | }, 11 | getIsbn: function() { 12 | return this._isbn; 13 | }, 14 | setIsbn: function(isbn) { 15 | if(!this._checkIsbn(isbn)) throw new Error('Book: Invalid ISBN.'); 16 | this._isbn = isbn; 17 | }, 18 | 19 | getTitle: function() { 20 | return this._title; 21 | }, 22 | setTitle: function(title) { 23 | this._title = title || 'No title specified'; 24 | }, 25 | 26 | getAuthor: function() { 27 | return this._author; 28 | }, 29 | setAuthor: function(author) { 30 | this._author = author || 'No author specified'; 31 | }, 32 | 33 | display: function() { 34 | ... 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /Source Code/Chapter04/4.08 - The augment function.js: -------------------------------------------------------------------------------- 1 | /* Augment function. */ 2 | 3 | function augment(receivingClass, givingClass) { 4 | for(methodName in givingClass.prototype) { 5 | if(!receivingClass.prototype[methodName]) { 6 | receivingClass.prototype[methodName] = givingClass.prototype[methodName]; 7 | } 8 | } 9 | } 10 | 11 | /* Augment function, improved. */ 12 | 13 | function augment(receivingClass, givingClass) { 14 | if(arguments[2]) { // Only give certain methods. 15 | for(var i = 2, len = arguments.length; i < len; i++) { 16 | receivingClass.prototype[arguments[i]] = givingClass.prototype[arguments[i]]; 17 | } 18 | } 19 | else { // Give all methods. 20 | for(methodName in givingClass.prototype) { 21 | if(!receivingClass.prototype[methodName]) { 22 | receivingClass.prototype[methodName] = givingClass.prototype[methodName]; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Source Code/Chapter04/4.04 - Prototypal inheritance.js: -------------------------------------------------------------------------------- 1 | /* Person Prototype Object. */ 2 | 3 | var Person = { 4 | name: 'default name', 5 | getName: function() { 6 | return this.name; 7 | } 8 | }; 9 | 10 | var reader = clone(Person); 11 | alert(reader.getName()); // This will output 'default name'. 12 | reader.name = 'John Smith'; 13 | alert(reader.getName()); // This will now output 'John Smith'. 14 | 15 | /* Author Prototype Object. */ 16 | 17 | var Author = clone(Person); 18 | Author.books = []; // Default value. 19 | Author.getBooks = function() { 20 | return this.books; 21 | } 22 | 23 | var author = []; 24 | 25 | author[0] = clone(Author); 26 | author[0].name = 'Dustin Diaz'; 27 | author[0].books = ['JavaScript Design Patterns']; 28 | 29 | author[1] = clone(Author); 30 | author[1].name = 'Ross Harmes'; 31 | author[1].books = ['JavaScript Design Patterns']; 32 | 33 | author[1].getName(); 34 | author[1].getBooks(); 35 | -------------------------------------------------------------------------------- /Source Code/Chapter03/3.07 - Constants.js: -------------------------------------------------------------------------------- 1 | var Class = (function() { 2 | 3 | // Constants (created as private static attributes). 4 | var UPPER_BOUND = 100; 5 | 6 | // Privileged static method. 7 | this.getUPPER_BOUND() { 8 | return UPPER_BOUND; 9 | } 10 | 11 | ... 12 | 13 | // Return the constructor. 14 | return function(constructorArgument) { 15 | ... 16 | } 17 | })(); 18 | 19 | 20 | /* Grouping constants together. */ 21 | 22 | var Class = (function() { 23 | 24 | // Private static attributes. 25 | var constants = { 26 | UPPER_BOUND: 100, 27 | LOWER_BOUND: -100 28 | } 29 | 30 | // Privileged static method. 31 | this.getConstant(name) { 32 | return constants[name]; 33 | } 34 | 35 | ... 36 | 37 | // Return the constructor. 38 | return function(constructorArgument) { 39 | ... 40 | } 41 | })(); 42 | 43 | 44 | /* Usage. */ 45 | 46 | Class.getConstant('UPPER_BOUND'); 47 | -------------------------------------------------------------------------------- /Source Code/Chapter05/5.06 - Comparing the two techniques.js: -------------------------------------------------------------------------------- 1 | /* DataParser singleton, converts character delimited strings into arrays. */ 2 | /* Now using true private methods. */ 3 | 4 | GiantCorp.DataParser = (function() { 5 | // Private attributes. 6 | var whitespaceRegex = /\s+/; 7 | 8 | // Private methods. 9 | function stripWhitespace(str) { 10 | return str.replace(whitespaceRegex, ''); 11 | } 12 | function stringSplit(str, delimiter) { 13 | return str.split(delimiter); 14 | } 15 | 16 | // Everything returned in the object literal is public, but can access the 17 | // members in the closure created above. 18 | return { 19 | // Public method. 20 | stringToArray: function(str, delimiter, stripWS) { 21 | if(stripWS) { 22 | str = stripWhitespace(str); 23 | } 24 | var outputArray = stringSplit(str, delimiter); 25 | return outputArray; 26 | } 27 | }; 28 | })(); // Invoke the function and assign the returned object literal to 29 | // GiantCorp.DataParser. 30 | -------------------------------------------------------------------------------- /Source Code/Chapter05/5.02 - Namespacing.js: -------------------------------------------------------------------------------- 1 | /* Declared globally. */ 2 | 3 | function findProduct(id) { 4 | ... 5 | } 6 | 7 | ... 8 | 9 | // Later in your page, another programmer adds... 10 | var resetProduct = $('reset-product-button'); 11 | var findProduct = $('find-product-button'); // The findProduct function just got 12 | // overwritten. 13 | 14 | 15 | /* Using a namespace. */ 16 | 17 | var MyNamespace = { 18 | findProduct: function(id) { 19 | ... 20 | }, 21 | // Other methods can go here as well. 22 | } 23 | ... 24 | 25 | // Later in your page, another programmer adds... 26 | var resetProduct = $('reset-product-button'); 27 | var findProduct = $('find-product-button'); // Nothing was overwritten. 28 | 29 | /* GiantCorp namespace. */ 30 | var GiantCorp = {}; 31 | 32 | GiantCorp.Common = { 33 | // A singleton with common methods used by all objects and modules. 34 | }; 35 | 36 | GiantCorp.ErrorCodes = { 37 | // An object literal used to store data. 38 | }; 39 | 40 | GiantCorp.PageHandler = { 41 | // A singleton with page specific methods and attributes. 42 | }; 43 | -------------------------------------------------------------------------------- /Source Code/Chapter03/3.05 - Private methods with closures.js: -------------------------------------------------------------------------------- 1 | var Book = function(newIsbn, newTitle, newAuthor) { // implements Publication 2 | 3 | // Private attributes. 4 | var isbn, title, author; 5 | 6 | // Private method. 7 | function checkIsbn(isbn) { 8 | ... 9 | } 10 | 11 | // Privileged methods. 12 | this.getIsbn = function() { 13 | return isbn; 14 | }; 15 | this.setIsbn = function(newIsbn) { 16 | if(!checkIsbn(newIsbn)) throw new Error('Book: Invalid ISBN.'); 17 | isbn = newIsbn; 18 | }; 19 | 20 | this.getTitle = function() { 21 | return title; 22 | }; 23 | this.setTitle = function(newTitle) { 24 | title = newTitle || 'No title specified'; 25 | }; 26 | 27 | this.getAuthor = function() { 28 | return author; 29 | }; 30 | this.setAuthor = function(newAuthor) { 31 | author = newAuthor || 'No author specified'; 32 | }; 33 | 34 | // Constructor code. 35 | this.setIsbn(newIsbn); 36 | this.setTitle(newTitle); 37 | this.setAuthor(newAuthor); 38 | }; 39 | 40 | // Public, non-privileged methods. 41 | Book.prototype = { 42 | display: function() { 43 | ... 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /Source Code/Chapter07/7.05 - Choosing connection objects at run-time.js: -------------------------------------------------------------------------------- 1 | /* XhrManager singleton. */ 2 | 3 | var XhrManager = { 4 | createXhrHandler: function() { 5 | var xhr; 6 | if(this.isOffline()) { 7 | xhr = new OfflineHandler(); 8 | } 9 | else if(this.isHighLatency()) { 10 | xhr = new QueuedHandler(); 11 | } 12 | else { 13 | xhr = new SimpleHandler() 14 | } 15 | 16 | Interface.ensureImplements(xhr, AjaxHandler); 17 | return xhr 18 | }, 19 | isOffline: function() { // Do a quick request with SimpleHandler and see if 20 | ... // it succeeds. 21 | }, 22 | isHighLatency: function() { // Do a series of requests with SimpleHandler and 23 | ... // time the responses. Best done once, as a 24 | // branching function. 25 | } 26 | }; 27 | 28 | 29 | /* Usage. */ 30 | 31 | var myHandler = XhrManager.createXhrHandler(); 32 | var callback = { 33 | success: function(responseText) { alert('Success: ' + responseText); }, 34 | failure: function(statusCode) { alert('Failure: ' + statusCode); } 35 | }; 36 | myHandler.request('GET', 'script.php', callback); 37 | -------------------------------------------------------------------------------- /Source Code/Chapter05/5.05 - Private methods with closures.js: -------------------------------------------------------------------------------- 1 | /* Singleton as an Object Literal. */ 2 | 3 | MyNamespace.Singleton = {}; 4 | 5 | /* Singleton with Private Members, step 1. */ 6 | 7 | MyNamespace.Singleton = (function() { 8 | return {}; 9 | })(); 10 | 11 | /* Singleton with Private Members, step 2. */ 12 | 13 | MyNamespace.Singleton = (function() { 14 | return { // Public members. 15 | publicAttribute1: true, 16 | publicAttribute2: 10, 17 | 18 | publicMethod1: function() { 19 | ... 20 | }, 21 | publicMethod2: function(args) { 22 | ... 23 | } 24 | }; 25 | })(); 26 | 27 | /* Singleton with Private Members, step 3. */ 28 | 29 | MyNamespace.Singleton = (function() { 30 | // Private members. 31 | var privateAttribute1 = false; 32 | var privateAttribute2 = [1, 2, 3]; 33 | 34 | function privateMethod1() { 35 | ... 36 | } 37 | function privateMethod2(args) { 38 | ... 39 | } 40 | 41 | return { // Public members. 42 | publicAttribute1: true, 43 | publicAttribute2: 10, 44 | 45 | publicMethod1: function() { 46 | ... 47 | }, 48 | publicMethod2: function(args) { 49 | ... 50 | } 51 | }; 52 | })(); 53 | -------------------------------------------------------------------------------- /Source Code/Chapter06/6.04 - Using callbacks.js: -------------------------------------------------------------------------------- 1 | // Accessor without function callbacks: returning requested data in accessors. 2 | window.API = window.API || {}; 3 | API.prototype = function() { 4 | var name = 'Hello world'; 5 | // Privileged mutator method. 6 | setName: function(newName) { 7 | name = newName; 8 | return this; 9 | }, 10 | // Privileged accessor method. 11 | getName: function() { 12 | return name; 13 | } 14 | }(); 15 | 16 | // Implementation code. 17 | var o = new API; 18 | console.log(o.getName()); // Displays 'Hello world'. 19 | console.log(o.setName('Meow').getName()); // Displays 'Meow'. 20 | 21 | // Accessor with function callbacks. 22 | window.API2 = window.API2 || {}; 23 | API2.prototype = function() { 24 | var name = 'Hello world'; 25 | // Privileged mutator method. 26 | setName: function(newName) { 27 | name = newName; 28 | return this; 29 | }, 30 | // Privileged accessor method. 31 | getName: function(callback) { 32 | callback.call(this, name); 33 | return this; 34 | } 35 | }(); 36 | 37 | // Implementation code. 38 | var o2 = new API2; 39 | o2.getName(console.log).setName('Meow').getName(console.log); 40 | // Displays 'Hello world' and then 'Meow'. 41 | -------------------------------------------------------------------------------- /Source Code/Chapter02/2.02 - Emulating interfaces with attribute checking.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | interface Composite { 4 | function add(child); 5 | function remove(child); 6 | function getChild(index); 7 | } 8 | 9 | interface FormItem { 10 | function save(); 11 | } 12 | 13 | */ 14 | 15 | var CompositeForm = function(id, method, action) { 16 | this.implementsInterfaces = ['Composite', 'FormItem']; 17 | ... 18 | }; 19 | 20 | ... 21 | 22 | function addForm(formInstance) { 23 | if(!implements(formInstance, 'Composite', 'FormItem')) { 24 | throw new Error("Object does not implement a required interface."); 25 | } 26 | ... 27 | } 28 | 29 | // The implements function, which checks to see if an object declares that it 30 | // implements the required interfaces. 31 | 32 | function implements(object) { 33 | for(var i = 1; i < arguments.length; i++) { // Looping through all arguments 34 | // after the first one. 35 | var interfaceName = arguments[i]; 36 | var interfaceFound = false; 37 | for(var j = 0; j < object.implementsInterfaces.length; j++) { 38 | if(object.implementsInterfaces[j] == interfaceName) { 39 | interfaceFound = true; 40 | break; 41 | } 42 | } 43 | 44 | if(!interfaceFound) { 45 | return false; // An interface was not found. 46 | } 47 | } 48 | return true; // All interfaces were found. 49 | } 50 | -------------------------------------------------------------------------------- /Source Code/Chapter04/4.03 - The extend function.js: -------------------------------------------------------------------------------- 1 | /* Extend function. */ 2 | 3 | function extend(subClass, superClass) { 4 | var F = function() {}; 5 | F.prototype = superClass.prototype; 6 | subClass.prototype = new F(); 7 | subClass.prototype.constructor = subClass; 8 | } 9 | 10 | 11 | /* Class Person. */ 12 | 13 | function Person(name) { 14 | this.name = name; 15 | } 16 | 17 | Person.prototype.getName = function() { 18 | return this.name; 19 | } 20 | 21 | /* Class Author. */ 22 | 23 | function Author(name, books) { 24 | Person.call(this, name); 25 | this.books = books; 26 | } 27 | extend(Author, Person); 28 | 29 | Author.prototype.getBooks = function() { 30 | return this.books; 31 | }; 32 | 33 | 34 | 35 | /* Extend function, improved. */ 36 | 37 | function extend(subClass, superClass) { 38 | var F = function() {}; 39 | F.prototype = superClass.prototype; 40 | subClass.prototype = new F(); 41 | subClass.prototype.constructor = subClass; 42 | 43 | subClass.superclass = superClass.prototype; 44 | if(superClass.prototype.constructor == Object.prototype.constructor) { 45 | superClass.prototype.constructor = superClass; 46 | } 47 | } 48 | 49 | 50 | /* Class Author. */ 51 | 52 | function Author(name, books) { 53 | Author.superclass.constructor.call(this, name); 54 | this.books = books; 55 | } 56 | extend(Author, Person); 57 | 58 | Author.prototype.getBooks = function() { 59 | return this.books; 60 | }; 61 | 62 | Author.prototype.getName = function() { 63 | var name = Author.superclass.getName.call(this); 64 | return name + ', Author of ' + this.getBooks().join(', '); 65 | }; 66 | -------------------------------------------------------------------------------- /Source Code/Chapter02/2.07 - An example illustrating the use of the Interface class.js: -------------------------------------------------------------------------------- 1 | // ResultFormatter class, before we implement interface checking. 2 | 3 | var ResultFormatter = function(resultsObject) { 4 | if(!(resultsObject instanceOf TestResult)) { 5 | throw new Error('ResultsFormatter: constructor requires an instance ' 6 | + 'of TestResult as an argument.'); 7 | } 8 | this.resultsObject = resultsObject; 9 | }; 10 | 11 | ResultFormatter.prototype.renderResults = function() { 12 | var dateOfTest = this.resultsObject.getDate(); 13 | var resultsArray = this.resultsObject.getResults(); 14 | 15 | var resultsContainer = document.createElement('div'); 16 | 17 | var resultsHeader = document.createElement('h3'); 18 | resultsHeader.innerHTML = 'Test Results from ' + dateOfTest.toUTCString(); 19 | resultsContainer.appendChild(resultsHeader); 20 | 21 | var resultsList = document.createElement('ul'); 22 | resultsContainer.appendChild(resultsList); 23 | 24 | for(var i = 0, len = resultsArray.length; i < len; i++) { 25 | var listItem = document.createElement('li'); 26 | listItem.innerHTML = resultsArray[i]; 27 | resultsList.appendChild(listItem); 28 | } 29 | 30 | return resultsContainer; 31 | }; 32 | 33 | 34 | // ResultSet Interface. 35 | 36 | var ResultSet = new Interface('ResultSet', ['getDate', 'getResults']); 37 | 38 | // ResultFormatter class, after adding Interface checking. 39 | 40 | var ResultFormatter = function(resultsObject) { 41 | Interface.ensureImplements(resultsObject, ResultSet); 42 | this.resultsObject = resultsObject; 43 | }; 44 | 45 | ResultFormatter.prototype.renderResults = function() { 46 | ... 47 | }; 48 | -------------------------------------------------------------------------------- /Source Code/Chapter03/3.06 - Static members.js: -------------------------------------------------------------------------------- 1 | var Book = (function() { 2 | 3 | // Private static attributes. 4 | var numOfBooks = 0; 5 | 6 | // Private static method. 7 | function checkIsbn(isbn) { 8 | ... 9 | } 10 | 11 | // Return the constructor. 12 | return function(newIsbn, newTitle, newAuthor) { // implements Publication 13 | 14 | // Private attributes. 15 | var isbn, title, author; 16 | 17 | // Privileged methods. 18 | this.getIsbn = function() { 19 | return isbn; 20 | }; 21 | this.setIsbn = function(newIsbn) { 22 | if(!checkIsbn(newIsbn)) throw new Error('Book: Invalid ISBN.'); 23 | isbn = newIsbn; 24 | }; 25 | 26 | this.getTitle = function() { 27 | return title; 28 | }; 29 | this.setTitle = function(newTitle) { 30 | title = newTitle || 'No title specified'; 31 | }; 32 | 33 | this.getAuthor = function() { 34 | return author; 35 | }; 36 | this.setAuthor = function(newAuthor) { 37 | author = newAuthor || 'No author specified'; 38 | }; 39 | 40 | // Constructor code. 41 | numOfBooks++; // Keep track of how many Books have been instantiated 42 | // with the private static attribute. 43 | if(numOfBooks > 50) throw new Error('Book: Only 50 instances of Book can be ' 44 | + 'created.'); 45 | 46 | this.setIsbn(newIsbn); 47 | this.setTitle(newTitle); 48 | this.setAuthor(newAuthor); 49 | } 50 | })(); 51 | 52 | // Public static method. 53 | Book.convertToTitleCase = function(inputString) { 54 | ... 55 | }; 56 | 57 | // Public, non-privileged methods. 58 | Book.prototype = { 59 | display: function() { 60 | ... 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /Source Code/Chapter01/1.01 - The flexibility of JavaScript.js: -------------------------------------------------------------------------------- 1 | /* Start and stop animations using functions. */ 2 | 3 | function startAnimation() { 4 | ... 5 | } 6 | 7 | function stopAnimation() { 8 | ... 9 | } 10 | 11 | 12 | 13 | /* Anim class. */ 14 | 15 | var Anim = function() { 16 | ... 17 | }; 18 | Anim.prototype.start = function() { 19 | ... 20 | }; 21 | Anim.prototype.stop = function() { 22 | ... 23 | }; 24 | 25 | /* Usage. */ 26 | 27 | var myAnim = new Anim(); 28 | myAnim.start(); 29 | ... 30 | myAnim.stop(); 31 | 32 | 33 | 34 | /* Anim class, with a slightly different syntax for declaring methods. */ 35 | 36 | var Anim = function() { 37 | ... 38 | }; 39 | Anim.prototype = { 40 | start: function() { 41 | ... 42 | }, 43 | stop: function() { 44 | ... 45 | } 46 | }; 47 | 48 | 49 | 50 | /* Add a method to the Function class that can be used to declare methods. */ 51 | 52 | Function.prototype.method = function(name, fn) { 53 | this.prototype[name] = fn; 54 | }; 55 | 56 | /* Anim class, with methods created using a convenience method. */ 57 | 58 | var Anim = function() { 59 | ... 60 | }; 61 | Anim.method('start', function() { 62 | ... 63 | }); 64 | Anim.method('stop', function() { 65 | ... 66 | }); 67 | 68 | 69 | 70 | /* This version allows the calls to be chained. */ 71 | 72 | Function.prototype.method = function(name, fn) { 73 | this.prototype[name] = fn; 74 | return this; 75 | }; 76 | 77 | /* Anim class, with methods created using a convenience method and chaining. */ 78 | 79 | var Anim = function() { 80 | ... 81 | }; 82 | Anim. 83 | method('start', function() { 84 | ... 85 | }). 86 | method('stop', function() { 87 | ... 88 | }); 89 | -------------------------------------------------------------------------------- /Source Code/Chapter02/2.05 - The Interface class.js: -------------------------------------------------------------------------------- 1 | // Constructor. 2 | 3 | var Interface = function(name, methods) { 4 | if(arguments.length != 2) { 5 | throw new Error("Interface constructor called with " + arguments.length 6 | + "arguments, but expected exactly 2."); 7 | } 8 | 9 | this.name = name; 10 | this.methods = []; 11 | for(var i = 0, len = methods.length; i < len; i++) { 12 | if(typeof methods[i] !== 'string') { 13 | throw new Error("Interface constructor expects method names to be " 14 | + "passed in as a string."); 15 | } 16 | this.methods.push(methods[i]); 17 | } 18 | }; 19 | 20 | // Static class method. 21 | 22 | Interface.ensureImplements = function(object) { 23 | if(arguments.length < 2) { 24 | throw new Error("Function Interface.ensureImplements called with " + 25 | arguments.length + "arguments, but expected at least 2."); 26 | } 27 | 28 | for(var i = 1, len = arguments.length; i < len; i++) { 29 | var interface = arguments[i]; 30 | if(interface.constructor !== Interface) { 31 | throw new Error("Function Interface.ensureImplements expects arguments " 32 | + "two and above to be instances of Interface."); 33 | } 34 | 35 | for(var j = 0, methodsLen = interface.methods.length; j < methodsLen; j++) { 36 | var method = interface.methods[j]; 37 | if(!object[method] || typeof object[method] !== 'function') { 38 | throw new Error("Function Interface.ensureImplements: object " 39 | + "does not implement the " + interface.name 40 | + " interface. Method " + method + " was not found."); 41 | } 42 | } 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /Source Code/Chapter05/5.09 - Creating XHR objects with branching.js: -------------------------------------------------------------------------------- 1 | /* SimpleXhrFactory singleton, step 1. */ 2 | 3 | var SimpleXhrFactory = (function() { 4 | 5 | // The three branches. 6 | var standard = { 7 | createXhrObject: function() { 8 | return new XMLHttpRequest(); 9 | } 10 | }; 11 | var activeXNew = { 12 | createXhrObject: function() { 13 | return new ActiveXObject('Msxml2.XMLHTTP'); 14 | } 15 | }; 16 | var activeXOld = { 17 | createXhrObject: function() { 18 | return new ActiveXObject('Microsoft.XMLHTTP'); 19 | } 20 | }; 21 | 22 | })(); 23 | 24 | /* SimpleXhrFactory singleton, step 2. */ 25 | 26 | var SimpleXhrFactory = (function() { 27 | 28 | // The three branches. 29 | var standard = { 30 | createXhrObject: function() { 31 | return new XMLHttpRequest(); 32 | } 33 | }; 34 | var activeXNew = { 35 | createXhrObject: function() { 36 | return new ActiveXObject('Msxml2.XMLHTTP'); 37 | } 38 | }; 39 | var activeXOld = { 40 | createXhrObject: function() { 41 | return new ActiveXObject('Microsoft.XMLHTTP'); 42 | } 43 | }; 44 | 45 | // To assign the branch, try each method; return whatever doesn't fail. 46 | var testObject; 47 | try { 48 | testObject = standard.createXhrObject(); 49 | return standard; // Return this if no error was thrown. 50 | } 51 | catch(e) { 52 | try { 53 | testObject = activeXNew.createXhrObject(); 54 | return activeXNew; // Return this if no error was thrown. 55 | } 56 | catch(e) { 57 | try { 58 | testObject = activeXOld.createXhrObject(); 59 | return activeXOld; // Return this if no error was thrown. 60 | } 61 | catch(e) { 62 | throw new Error('No XHR object found in this environment.'); 63 | } 64 | } 65 | } 66 | 67 | })(); 68 | -------------------------------------------------------------------------------- /Source Code/Chapter07/7.03 - XHR factory example.js: -------------------------------------------------------------------------------- 1 | /* AjaxHandler interface. */ 2 | 3 | var AjaxHandler = new Interface('AjaxHandler', ['request', 'createXhrObject']); 4 | 5 | /* SimpleHandler class. */ 6 | 7 | var SimpleHandler = function() {}; // implements AjaxHandler 8 | SimpleHandler.prototype = { 9 | request: function(method, url, callback, postVars) { 10 | var xhr = this.createXhrObject(); 11 | xhr.onreadystatechange = function() { 12 | if(xhr.readyState !== 4) return; 13 | (xhr.status === 200) ? 14 | callback.success(xhr.responseText, xhr.responseXML) : 15 | callback.failure(xhr.status); 16 | }; 17 | xhr.open(method, url, true); 18 | if(method !== 'POST') postVars = null; 19 | xhr.send(postVars); 20 | }, 21 | createXhrObject: function() { // Factory method. 22 | var methods = [ 23 | function() { return new XMLHttpRequest(); }, 24 | function() { return new ActiveXObject('Msxml2.XMLHTTP'); }, 25 | function() { return new ActiveXObject('Microsoft.XMLHTTP'); } 26 | ]; 27 | 28 | for(var i = 0, len = methods.length; i < len; i++) { 29 | try { 30 | methods[i](); 31 | } 32 | catch(e) { 33 | continue; 34 | } 35 | // If we reach this point, method[i] worked. 36 | this.createXhrObject = methods[i]; // Memoize the method. 37 | return methods[i]; 38 | } 39 | 40 | // If we reach this point, none of the methods worked. 41 | throw new Error('SimpleHandler: Could not create an XHR object.'); 42 | } 43 | }; 44 | 45 | /* Usage. */ 46 | 47 | var myHandler = new SimpleHandler(); 48 | var callback = { 49 | success: function(responseText) { alert('Success: ' + responseText); }, 50 | failure: function(statusCode) { alert('Failure: ' + statusCode); } 51 | }; 52 | myHandler.request('GET', 'script.php', callback); 53 | -------------------------------------------------------------------------------- /Source Code/Chapter05/5.03 - Wrappers for page specific code.js: -------------------------------------------------------------------------------- 1 | /* Generic Page Object. */ 2 | 3 | Namespace.PageName = { 4 | 5 | // Page constants. 6 | CONSTANT_1: true, 7 | CONSTANT_2: 10, 8 | 9 | // Page methods. 10 | method1: function() { 11 | 12 | }, 13 | method2: function() { 14 | 15 | }, 16 | 17 | // Initialization method. 18 | init: function() { 19 | 20 | } 21 | } 22 | 23 | // Invoke the initialization method after the page loads. 24 | addLoadEvent(Namespace.PageName.init); 25 | 26 | 27 | var GiantCorp = window.GiantCorp || {}; 28 | 29 | /* RegPage singleton, page handler object. */ 30 | 31 | GiantCorp.RegPage = { 32 | 33 | // Constants. 34 | FORM_ID: 'reg-form', 35 | OUTPUT_ID: 'reg-results', 36 | 37 | // Form handling methods. 38 | handleSubmit: function(e) { 39 | e.preventDefault(); // Stop the normal form submission. 40 | 41 | var data = {}; 42 | var inputs = GiantCorp.RegPage.formEl.getElementsByTagName('input'); 43 | 44 | // Collect the values of the input fields in the form. 45 | for(var i = 0, len = inputs.length; i < len; i++) { 46 | data[inputs[i].name] = inputs[i].value; 47 | } 48 | 49 | // Send the form values back to the server. 50 | GiantCorp.RegPage.sendRegistration(data); 51 | }, 52 | sendRegistration: function(data) { 53 | // Make an XHR request and call displayResult() when the response is 54 | // received. 55 | ... 56 | }, 57 | displayResult: function(response) { 58 | // Output the response directly into the output element. We are 59 | // assuming the server will send back formatted HTML. 60 | GiantCorp.RegPage.outputEl.innerHTML = response; 61 | }, 62 | 63 | // Initialization method. 64 | init: function() { 65 | // Get the form and output elements. 66 | GiantCorp.RegPage.formEl = $(GiantCorp.RegPage.FORM_ID); 67 | GiantCorp.RegPage.outputEl = $(GiantCorp.RegPage.OUTPUT_ID); 68 | 69 | // Hijack the form submission. 70 | addEvent(GiantCorp.RegPage.formEl, 'submit', GiantCorp.RegPage.handleSubmit); 71 | } 72 | }; 73 | 74 | // Invoke the initialization method after the page loads. 75 | addLoadEvent(GiantCorp.RegPage.init); 76 | 77 | -------------------------------------------------------------------------------- /Source Code/Chapter07/7.02 - The factory pattern.js: -------------------------------------------------------------------------------- 1 | /* BicycleShop class (abstract). */ 2 | 3 | var BicycleShop = function() {}; 4 | BicycleShop.prototype = { 5 | sellBicycle: function(model) { 6 | var bicycle = this.createBicycle(model); 7 | 8 | bicycle.assemble(); 9 | bicycle.wash(); 10 | 11 | return bicycle; 12 | }, 13 | createBicycle: function(model) { 14 | throw new Error('Unsupported operation on an abstract class.'); 15 | } 16 | }; 17 | 18 | /* AcmeBicycleShop class. */ 19 | 20 | var AcmeBicycleShop = function() {}; 21 | extend(AcmeBicycleShop, BicycleShop); 22 | AcmeBicycleShop.prototype.createBicycle = function(model) { 23 | var bicycle; 24 | 25 | switch(model) { 26 | case 'The Speedster': 27 | bicycle = new AcmeSpeedster(); 28 | break; 29 | case 'The Lowrider': 30 | bicycle = new AcmeLowrider(); 31 | break; 32 | case 'The Flatlander': 33 | bicycle = new AcmeFlatlander(); 34 | break; 35 | case 'The Comfort Cruiser': 36 | default: 37 | bicycle = new AcmeComfortCruiser(); 38 | } 39 | 40 | Interface.ensureImplements(bicycle, Bicycle); 41 | return bicycle; 42 | }; 43 | 44 | /* GeneralProductsBicycleShop class. */ 45 | 46 | var GeneralProductsBicycleShop = function() {}; 47 | extend(GeneralProductsBicycleShop, BicycleShop); 48 | GeneralProductsBicycleShop.prototype.createBicycle = function(model) { 49 | var bicycle; 50 | 51 | switch(model) { 52 | case 'The Speedster': 53 | bicycle = new GeneralProductsSpeedster(); 54 | break; 55 | case 'The Lowrider': 56 | bicycle = new GeneralProductsLowrider(); 57 | break; 58 | case 'The Flatlander': 59 | bicycle = new GeneralProductsFlatlander(); 60 | break; 61 | case 'The Comfort Cruiser': 62 | default: 63 | bicycle = new GeneralProductsComfortCruiser(); 64 | } 65 | 66 | Interface.ensureImplements(bicycle, Bicycle); 67 | return bicycle; 68 | }; 69 | 70 | 71 | /* Usage. */ 72 | 73 | var alecsCruisers = new AcmeBicycleShop(); 74 | var yourNewBike = alecsCruisers.sellBicycle('The Lowrider'); 75 | 76 | var bobsCruisers = new GeneralProductsBicycleShop(); 77 | var yourSecondNewBike = bobsCruisers.sellBicycle('The Lowrider'); 78 | -------------------------------------------------------------------------------- /Source Code/Chapter05/5.07 - Lazy instantiation.js: -------------------------------------------------------------------------------- 1 | /* Singleton with Private Members, step 3. */ 2 | 3 | MyNamespace.Singleton = (function() { 4 | // Private members. 5 | var privateAttribute1 = false; 6 | var privateAttribute2 = [1, 2, 3]; 7 | 8 | function privateMethod1() { 9 | ... 10 | } 11 | function privateMethod2(args) { 12 | ... 13 | } 14 | 15 | return { // Public members. 16 | publicAttribute1: true, 17 | publicAttribute2: 10, 18 | 19 | publicMethod1: function() { 20 | ... 21 | }, 22 | publicMethod2: function(args) { 23 | ... 24 | } 25 | }; 26 | })(); 27 | 28 | /* General skeleton for a lazy loading singleton, step 1. */ 29 | 30 | MyNamespace.Singleton = (function() { 31 | 32 | function constructor() { // All of the normal singleton code goes here. 33 | // Private members. 34 | var privateAttribute1 = false; 35 | var privateAttribute2 = [1, 2, 3]; 36 | 37 | function privateMethod1() { 38 | ... 39 | } 40 | function privateMethod2(args) { 41 | ... 42 | } 43 | 44 | return { // Public members. 45 | publicAttribute1: true, 46 | publicAttribute2: 10, 47 | 48 | publicMethod1: function() { 49 | ... 50 | }, 51 | publicMethod2: function(args) { 52 | ... 53 | } 54 | } 55 | } 56 | 57 | })(); 58 | 59 | /* General skeleton for a lazy loading singleton, step 2. */ 60 | 61 | MyNamespace.Singleton = (function() { 62 | 63 | function constructor() { // All of the normal singleton code goes here. 64 | ... 65 | } 66 | 67 | return { 68 | getInstance: function() { 69 | // Control code goes here. 70 | } 71 | } 72 | })(); 73 | 74 | /* General skeleton for a lazy loading singleton, step 3. */ 75 | 76 | MyNamespace.Singleton = (function() { 77 | 78 | var uniqueInstance; // Private attribute that holds the single instance. 79 | 80 | function constructor() { // All of the normal singleton code goes here. 81 | ... 82 | } 83 | 84 | return { 85 | getInstance: function() { 86 | if(!uniqueInstance) { // Instantiate only if the instance doesn't exist. 87 | uniqueInstance = constructor(); 88 | } 89 | return uniqueInstance; 90 | } 91 | } 92 | })(); 93 | -------------------------------------------------------------------------------- /Source Code/Chapter04/4.05 - Asymmetrical reading and writing.js: -------------------------------------------------------------------------------- 1 | var authorClone = clone(Author); 2 | alert(authorClone.name); // Linked to the primative Person.name, which is the 3 | // string 'default name'. 4 | authorClone.name = 'new name'; // A new primative is created and added to the 5 | // authorClone object itself. 6 | alert(authorClone.name); // Now linked to the primative authorClone.name, which 7 | // is the string 'new name'. 8 | 9 | authorClone.books.push('new book'); // authorClone.books is linked to the array 10 | // Author.books. We just modified the 11 | // prototype object's default value, and all 12 | // other objects that link to it will now 13 | // have a new default value there. 14 | authorClone.books = []; // A new array is created and added to the authorClone 15 | // object itself. 16 | authorClone.books.push('new book'); // We are now modifying that new array. 17 | 18 | var CompoundObject = { 19 | string1: 'default value', 20 | childObject: { 21 | bool: true, 22 | num: 10 23 | } 24 | } 25 | 26 | var compoundObjectClone = clone(CompoundObject); 27 | 28 | // Bad! Changes the value of CompoundObject.childObject.num. 29 | compoundObjectClone.childObject.num = 5; 30 | 31 | // Better. Creates a new object, but compoundObject must know the structure 32 | // of that object, and the defaults. This makes CompoundObject and 33 | // compoundObjectClone tightly coupled. 34 | compoundObjectClone.childObject = { 35 | bool: true, 36 | num: 5 37 | }; 38 | 39 | // Best approach. Uses a method to create a new object, with the same structure and 40 | // defaults as the original. 41 | 42 | var CompoundObject = {}; 43 | CompoundObject.string1 = 'default value', 44 | CompoundObject.createChildObject = function() { 45 | return { 46 | bool: true, 47 | num: 10 48 | } 49 | }; 50 | CompoundObject.childObject = CompoundObject.createChildObject(); 51 | 52 | var compoundObjectClone = clone(CompoundObject); 53 | compoundObjectClone.childObject = CompoundObject.createChildObject(); 54 | compoundObjectClone.childObject.num = 5; 55 | -------------------------------------------------------------------------------- /Source Code/Chapter06/6.03 - Building a chainable JavaScript library.js: -------------------------------------------------------------------------------- 1 | // Include syntactic sugar to help the development of our interface. 2 | Function.prototype.method = function(name, fn) { 3 | this.prototype[name] = fn; 4 | return this; 5 | }; 6 | (function() { 7 | function _$(els) { 8 | // ... 9 | } 10 | /* 11 | Events 12 | * addEvent 13 | * getEvent 14 | */ 15 | _$.method('addEvent', function(type, fn) { 16 | // ... 17 | }).method('getEvent', function(e) { 18 | // ... 19 | }). 20 | /* 21 | DOM 22 | * addClass 23 | * removeClass 24 | * replaceClass 25 | * hasClass 26 | * getStyle 27 | * setStyle 28 | */ 29 | method('addClass', function(className) { 30 | // ... 31 | }).method('removeClass', function(className) { 32 | // ... 33 | }).method('replaceClass', function(oldClass, newClass) { 34 | // ... 35 | }).method('hasClass', function(className) { 36 | // ... 37 | }).method('getStyle', function(prop) { 38 | // ... 39 | }).method('setStyle', function(prop, val) { 40 | // ... 41 | }). 42 | /* 43 | AJAX 44 | * load. Fetches an HTML fragment from a URL and inserts it into an element. 45 | */ 46 | method('load', function(uri, method) { 47 | // ... 48 | }); 49 | window.$ = function() { 50 | return new _$(arguments); 51 | }); 52 | })(); 53 | 54 | Function.prototype.method = function(name, fn) { 55 | // ... 56 | }; 57 | (function() { 58 | function _$(els) { 59 | // ... 60 | } 61 | _$.method('addEvent', function(type, fn) { 62 | // ... 63 | }) 64 | // ... 65 | 66 | window.installHelper = function(scope, interface) { 67 | scope[interface] = function() { 68 | return new _$(arguments); 69 | } 70 | }; 71 | })(); 72 | 73 | 74 | /* Usage. */ 75 | 76 | installHelper(window, '$'); 77 | 78 | $('example').show(); 79 | 80 | 81 | /* Another usage example. */ 82 | 83 | // Define a namespace without overwriting it if it already exists. 84 | window.com = window.com || {}; 85 | com.example = com.example || {}; 86 | com.example.util = com.example.util || {}; 87 | 88 | installHelper(com.example.util, 'get'); 89 | 90 | (function() { 91 | var get = com.example.util.get; 92 | get('example').addEvent('click', function(e) { 93 | get(this).addClass('hello'); 94 | }); 95 | })(); 96 | -------------------------------------------------------------------------------- /Source Code/Chapter06/6.02 - The structure of the chain.js: -------------------------------------------------------------------------------- 1 | function $() { 2 | var elements = []; 3 | for (var i = 0, len = arguments.length; i < len; ++i) { 4 | var element = arguments[i]; 5 | if (typeof element == 'string') { 6 | element = document.getElementById(element); 7 | } 8 | if (arguments.length == 1) { 9 | return element; 10 | } 11 | elements.push(element); 12 | } 13 | return elements; 14 | } 15 | 16 | 17 | 18 | (function() { 19 | // Use a private class. 20 | function _$(els) { 21 | this.elements = []; 22 | for (var i = 0, len = els.length; i < len; ++i) { 23 | var element = els[i]; 24 | if (typeof element == 'string') { 25 | element = document.getElementById(element); 26 | } 27 | this.elements.push(element); 28 | } 29 | } 30 | // The public interface remains the same. 31 | window.$ = function() { 32 | return new _$(arguments); 33 | }; 34 | })(); 35 | 36 | 37 | 38 | (function() { 39 | function _$(els) { 40 | // ... 41 | } 42 | _$.prototype = { 43 | each: function(fn) { 44 | for ( var i = 0, len = this.elements.length; i < len; ++i ) { 45 | fn.call(this, this.elements[i]); 46 | } 47 | return this; 48 | }, 49 | setStyle: function(prop, val) { 50 | this.each(function(el) { 51 | el.style[prop] = val; 52 | }); 53 | return this; 54 | }, 55 | show: function() { 56 | var that = this; 57 | this.each(function(el) { 58 | that.setStyle('display', 'block'); 59 | }); 60 | return this; 61 | }, 62 | addEvent: function(type, fn) { 63 | var add = function(el) { 64 | if (window.addEventListener) { 65 | el.addEventListener(type, fn, false); 66 | } 67 | else if (window.attachEvent) { 68 | el.attachEvent('on'+type, fn); 69 | } 70 | }; 71 | this.each(function(el) { 72 | add(el); 73 | }); 74 | return this; 75 | } 76 | }; 77 | window.$ = function() { 78 | return new _$(arguments); 79 | }; 80 | })(); 81 | 82 | 83 | /* Usage. */ 84 | 85 | $(window).addEvent('load', function() { 86 | $('test-1', 'test-2').show(). 87 | setStyle('color', 'red'). 88 | addEvent('click', function(e) { 89 | $(this).setStyle('color', 'green'); 90 | }); 91 | }); 92 | -------------------------------------------------------------------------------- /Source Code/Chapter07/7.04 - Specialized connection objects.js: -------------------------------------------------------------------------------- 1 | /* QueuedHandler class. */ 2 | 3 | var QueuedHandler = function() { // implements AjaxHandler 4 | this.queue = []; 5 | this.requestInProgress = false; 6 | this.retryDelay = 5; // In seconds. 7 | }; 8 | extend(QueuedHandler, SimpleHandler); 9 | QueuedHandler.prototype.request = function(method, url, callback, postVars, 10 | override) { 11 | if(this.requestInProgress && !override) { 12 | this.queue.push({ 13 | method: method, 14 | url: url, 15 | callback: callback, 16 | postVars: postVars 17 | }); 18 | } 19 | else { 20 | this.requestInProgress = true; 21 | var xhr = this.createXhrObject(); 22 | var that = this; 23 | xhr.onreadystatechange = function() { 24 | if(xhr.readyState !== 4) return; 25 | if(xhr.status === 200) { 26 | callback.success(xhr.responseText, xhr.responseXML); 27 | that.advanceQueue(); 28 | } 29 | else { 30 | callback.failure(xhr.status); 31 | setTimeout(function() { that.request(method, url, callback, postVars); }, 32 | that.retryDelay * 1000); 33 | } 34 | }; 35 | xhr.open(method, url, true); 36 | if(method !== 'POST') postVars = null; 37 | xhr.send(postVars); 38 | } 39 | }; 40 | QueuedHandler.prototype.advanceQueue = function() { 41 | if(this.queue.length === 0) { 42 | this.requestInProgress = false; 43 | return; 44 | } 45 | var req = this.queue.shift(); 46 | this.request(req.method, req.url, req.callback, req.postVars, true); 47 | }; 48 | 49 | 50 | /* OfflineHandler class. */ 51 | 52 | var OfflineHandler = function() { // implements AjaxHandler 53 | this.storedRequests = []; 54 | }; 55 | extend(OfflineHandler, SimpleHandler); 56 | OfflineHandler.prototype.request = function(method, url, callback, postVars) { 57 | if(XhrManager.isOffline()) { // Store the requests until we are online. 58 | this.storedRequests.push({ 59 | method: method, 60 | url: url, 61 | callback: callback, 62 | postVars: postVars 63 | }); 64 | } 65 | else { // Call SimpleHandler's request method if we are online. 66 | this.flushStoredRequests(); 67 | OfflineHandler.superclass.request(method, url, callback, postVars); 68 | } 69 | }; 70 | OfflineHandler.prototype.flushStoredRequests = function() { 71 | for(var i = 0, len = storedRequests.length; i < len; i++) { 72 | var req = storedRequests[i]; 73 | OfflineHandler.superclass.request(req.method, req.url, req.callback, 74 | req.postVars); 75 | } 76 | }; 77 | -------------------------------------------------------------------------------- /Source Code/Chapter07/7.06 - RSS reader example.js: -------------------------------------------------------------------------------- 1 | /* DisplayModule interface. */ 2 | 3 | var DisplayModule = new Interface('DisplayModule', ['append', 'remove', 'clear']); 4 | 5 | /* ListDisplay class. */ 6 | 7 | var ListDisplay = function(id, parent) { // implements DisplayModule 8 | this.list = document.createElement('ul'); 9 | this.list.id = id; 10 | parent.appendChild(this.list); 11 | }; 12 | ListDisplay.prototype = { 13 | append: function(text) { 14 | var newEl = document.createElement('li'); 15 | this.list.appendChild(newEl); 16 | newEl.innerHTML = text; 17 | return newEl; 18 | }, 19 | remove: function(el) { 20 | this.list.removeChild(el); 21 | }, 22 | clear: function() { 23 | this.list.innerHTML = ''; 24 | } 25 | }; 26 | 27 | /* Configuration object. */ 28 | 29 | var conf = { 30 | id: 'cnn-top-stories', 31 | feedUrl: 'http://rss.cnn.com/rss/cnn_topstories.rss', 32 | updateInterval: 60, // In seconds. 33 | parent: $('feed-readers') 34 | }; 35 | 36 | /* FeedReader class. */ 37 | 38 | var FeedReader = function(display, xhrHandler, conf) { 39 | this.display = display; 40 | this.xhrHandler = xhrHandler; 41 | this.conf = conf; 42 | 43 | this.startUpdates(); 44 | }; 45 | FeedReader.prototype = { 46 | fetchFeed: function() { 47 | var that = this; 48 | var callback = { 49 | success: function(text, xml) { that.parseFeed(text, xml); }, 50 | failure: function(status) { that.showError(status); } 51 | }; 52 | this.xhrHandler.request('GET', 'feedProxy.php?feed=' + this.conf.feedUrl, 53 | callback); 54 | }, 55 | parseFeed: function(responseText, responseXML) { 56 | this.display.clear(); 57 | var items = responseXML.getElementsByTagName('item'); 58 | for(var i = 0, len = items.length; i < len; i++) { 59 | var title = items[i].getElementsByTagName('title')[0]; 60 | var link = items[i].getElementsByTagName('link')[0]; 61 | this.display.append('' + 62 | title.firstChild.data + ''); 63 | } 64 | }, 65 | showError: function(statusCode) { 66 | this.display.clear(); 67 | this.display.append('Error fetching feed.'); 68 | }, 69 | stopUpdates: function() { 70 | clearInterval(this.interval); 71 | }, 72 | startUpdates: function() { 73 | this.fetchFeed(); 74 | var that = this; 75 | this.interval = setInterval(function() { that.fetchFeed(); }, 76 | this.conf.updateInterval * 1000); 77 | } 78 | }; 79 | 80 | /* FeedManager namespace. */ 81 | 82 | var FeedManager = { 83 | createFeedReader: function(conf) { 84 | var displayModule = new ListDisplay(conf.id + '-display', conf.parent); 85 | Interface.ensureImplements(displayModule, DisplayModule); 86 | 87 | var xhrHandler = XhrManager.createXhrHandler(); 88 | Interface.ensureImplements(xhrHandler, AjaxHandler); 89 | 90 | return new FeedReader(displayModule, xhrHandler, conf); 91 | } 92 | }; 93 | -------------------------------------------------------------------------------- /Source Code/Chapter07/7.01 - The simple factory.js: -------------------------------------------------------------------------------- 1 | /* BicycleShop class. */ 2 | 3 | var BicycleShop = function() {}; 4 | BicycleShop.prototype = { 5 | sellBicycle: function(model) { 6 | var bicycle; 7 | 8 | switch(model) { 9 | case 'The Speedster': 10 | bicycle = new Speedster(); 11 | break; 12 | case 'The Lowrider': 13 | bicycle = new Lowrider(); 14 | break; 15 | case 'The Comfort Cruiser': 16 | default: 17 | bicycle = new ComfortCruiser(); 18 | } 19 | Interface.ensureImplements(bicycle, Bicycle); 20 | 21 | bicycle.assemble(); 22 | bicycle.wash(); 23 | 24 | return bicycle; 25 | } 26 | }; 27 | 28 | 29 | /* The Bicycle interface. */ 30 | 31 | var Bicycle = new Interface('Bicycle', ['assemble', 'wash', 'ride', 'repair']); 32 | 33 | /* Speedster class. */ 34 | 35 | var Speedster = function() { // implements Bicycle 36 | ... 37 | }; 38 | Speedster.prototype = { 39 | assemble: function() { 40 | ... 41 | }, 42 | wash: function() { 43 | ... 44 | }, 45 | ride: function() { 46 | ... 47 | }, 48 | repair: function() { 49 | ... 50 | } 51 | }; 52 | 53 | 54 | /* Usage. */ 55 | 56 | var californiaCruisers = new BicycleShop(); 57 | var yourNewBike = californiaCruisers.sellBicycle('The Speedster'); 58 | 59 | 60 | 61 | /* BicycleFactory namespace. */ 62 | 63 | var BicycleFactory = { 64 | createBicycle: function(model) { 65 | var bicycle; 66 | 67 | switch(model) { 68 | case 'The Speedster': 69 | bicycle = new Speedster(); 70 | break; 71 | case 'The Lowrider': 72 | bicycle = new Lowrider(); 73 | break; 74 | case 'The Comfort Cruiser': 75 | default: 76 | bicycle = new ComfortCruiser(); 77 | } 78 | 79 | Interface.ensureImplements(bicycle, Bicycle); 80 | return bicycle; 81 | } 82 | }; 83 | 84 | /* BicycleShop class, improved. */ 85 | 86 | var BicycleShop = function() {}; 87 | BicycleShop.prototype = { 88 | sellBicycle: function(model) { 89 | var bicycle = BicycleFactory.createBicycle(model); 90 | 91 | bicycle.assemble(); 92 | bicycle.wash(); 93 | 94 | return bicycle; 95 | } 96 | }; 97 | 98 | /* BicycleFactory namespace, with more models. */ 99 | 100 | var BicycleFactory = { 101 | createBicycle: function(model) { 102 | var bicycle; 103 | 104 | switch(model) { 105 | case 'The Speedster': 106 | bicycle = new Speedster(); 107 | break; 108 | case 'The Lowrider': 109 | bicycle = new Lowrider(); 110 | break; 111 | case 'The Flatlander': 112 | bicycle = new Flatlander(); 113 | break; 114 | case 'The Comfort Cruiser': 115 | default: 116 | bicycle = new ComfortCruiser(); 117 | } 118 | 119 | Interface.ensureImplements(bicycle, Bicycle); 120 | return bicycle; 121 | } 122 | }; 123 | -------------------------------------------------------------------------------- /Source Code/Chapter03/3.02 - Fully exposed object.js: -------------------------------------------------------------------------------- 1 | var Book = function(isbn, title, author) { 2 | if(isbn == undefined) throw new Error('Book constructor requires an isbn.'); 3 | this.isbn = isbn; 4 | this.title = title || 'No title specified'; 5 | this.author = author || 'No author specified'; 6 | } 7 | 8 | Book.prototype.display = function() { 9 | ... 10 | }; 11 | 12 | 13 | /* With ISBN check. */ 14 | 15 | var Book = function(isbn, title, author) { 16 | if(!this.checkIsbn(isbn)) throw new Error('Book: Invalid ISBN.'); 17 | this.isbn = isbn; 18 | this.title = title || 'No title specified'; 19 | this.author = author || 'No author specified'; 20 | } 21 | 22 | Book.prototype = { 23 | checkIsbn: function(isbn) { 24 | if(isbn == undefined || typeof isbn != 'string') { 25 | return false; 26 | } 27 | 28 | isbn = isbn.replace(/-/. ''); // Remove dashes. 29 | if(isbn.length != 10 && isbn.length != 13) { 30 | return false; 31 | } 32 | 33 | var sum = 0; 34 | if(isbn.length === 10) { // 10 digit ISBN. 35 | If(!isbn.match(\^\d{9}\)) { // Ensure characters 1 through 9 are digits. 36 | return false; 37 | } 38 | 39 | for(var i = 0; i < 9; i++) { 40 | sum += isbn.charAt(i) * (10 - i); 41 | } 42 | var checksum = sum % 11; 43 | if(checksum === 10) checksum = 'X'; 44 | if(isbn.charAt(9) != checksum) { 45 | return false; 46 | } 47 | } 48 | else { // 13 digit ISBN. 49 | if(!isbn.match(\^\d{12}\)) { // Ensure characters 1 through 12 are digits. 50 | return false; 51 | } 52 | 53 | for(var i = 0; i < 12; i++) { 54 | sum += isbn.charAt(i) * ((i % 2 === 0) ? 1 : 3); 55 | } 56 | var checksum = sum % 10; 57 | if(isbn.charAt(12) != checksum) { 58 | return false; 59 | } 60 | } 61 | 62 | return true; // All tests passed. 63 | }, 64 | 65 | display: function() { 66 | ... 67 | } 68 | }; 69 | 70 | 71 | /* Publication interface. */ 72 | 73 | var Publication = new Interface('Publication', ['getIsbn', 'setIsbn', 'getTitle', 74 | 'setTitle', 'getAuthor', 'setAuthor', 'display']); 75 | 76 | 77 | /* With mutators and accessors. */ 78 | 79 | var Book = function(isbn, title, author) { // implements Publication 80 | this.setIsbn(isbn); 81 | this.setTitle(title); 82 | this.setAuthor(author); 83 | } 84 | 85 | Book.prototype = { 86 | checkIsbn: function(isbn) { 87 | ... 88 | }, 89 | getIsbn: function() { 90 | return this.isbn; 91 | }, 92 | setIsbn: function(isbn) { 93 | if(!this.checkIsbn(isbn)) throw new Error('Book: Invalid ISBN.'); 94 | this.isbn = isbn; 95 | }, 96 | 97 | getTitle: function() { 98 | return this.title; 99 | }, 100 | setTitle: function(title) { 101 | this.title = title || 'No title specified'; 102 | }, 103 | 104 | getAuthor: function() { 105 | return this.author; 106 | }, 107 | setAuthor: function(author) { 108 | this.author = author || 'No author specified'; 109 | }, 110 | 111 | display: function() { 112 | ... 113 | } 114 | }; 115 | -------------------------------------------------------------------------------- /Source Code/Chapter08/8.05 - XHR connection queue example page.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | Ajax Connection Queue 7 | 8 | 9 | 94 | 105 | 106 | 107 |
108 |

Ajax Connection Queue

109 |
110 |
111 |

Add Requests to Queue

112 | 117 |
118 |

Other Queue Actions

119 | 125 |
126 |

Results:

127 |
128 |
129 |
130 | 131 | 132 | -------------------------------------------------------------------------------- /Source Code/Chapter04/4.10 - Edit-in-place example, prototypal.js: -------------------------------------------------------------------------------- 1 | /* EditInPlaceField object. */ 2 | 3 | var EditInPlaceField = { 4 | configure: function(id, parent, value) { 5 | this.id = id; 6 | this.value = value || 'default value'; 7 | this.parentElement = parent; 8 | 9 | this.createElements(this.id); 10 | this.attachEvents(); 11 | }, 12 | createElements: function(id) { 13 | this.containerElement = document.createElement('div'); 14 | this.parentElement.appendChild(this.containerElement); 15 | 16 | this.staticElement = document.createElement('span'); 17 | this.containerElement.appendChild(this.staticElement); 18 | this.staticElement.innerHTML = this.value; 19 | 20 | this.fieldElement = document.createElement('input'); 21 | this.fieldElement.type = 'text'; 22 | this.fieldElement.value = this.value; 23 | this.containerElement.appendChild(this.fieldElement); 24 | 25 | this.saveButton = document.createElement('input'); 26 | this.saveButton.type = 'button'; 27 | this.saveButton.value = 'Save'; 28 | this.containerElement.appendChild(this.saveButton); 29 | 30 | this.cancelButton = document.createElement('input'); 31 | this.cancelButton.type = 'button'; 32 | this.cancelButton.value = 'Cancel'; 33 | this.containerElement.appendChild(this.cancelButton); 34 | 35 | this.convertToText(); 36 | }, 37 | attachEvents: function() { 38 | var that = this; 39 | addEvent(this.staticElement, 'click', function() { that.convertToEditable(); }); 40 | addEvent(this.saveButton, 'click', function() { that.save(); }); 41 | addEvent(this.cancelButton, 'click', function() { that.cancel(); }); 42 | }, 43 | 44 | convertToEditable: function() { 45 | this.staticElement.style.display = 'none'; 46 | this.fieldElement.style.display = 'inline'; 47 | this.saveButton.style.display = 'inline'; 48 | this.cancelButton.style.display = 'inline'; 49 | 50 | this.setValue(this.value); 51 | }, 52 | save: function() { 53 | this.value = this.getValue(); 54 | var that = this; 55 | var callback = { 56 | success: function() { that.convertToText(); }, 57 | failure: function() { alert('Error saving value.'); } 58 | }; 59 | ajaxRequest('GET', 'save.php?id=' + this.id + '&value=' + this.value, callback); 60 | }, 61 | cancel: function() { 62 | this.convertToText(); 63 | }, 64 | convertToText: function() { 65 | this.fieldElement.style.display = 'none'; 66 | this.saveButton.style.display = 'none'; 67 | this.cancelButton.style.display = 'none'; 68 | this.staticElement.style.display = 'inline'; 69 | 70 | this.setValue(this.value); 71 | }, 72 | 73 | setValue: function(value) { 74 | this.fieldElement.value = value; 75 | this.staticElement.innerHTML = value; 76 | }, 77 | getValue: function() { 78 | return this.fieldElement.value; 79 | } 80 | }; 81 | 82 | var titlePrototypal = clone(EditInPlaceField); 83 | titlePrototypal.configure(' titlePrototypal ', $('doc'), 'Title Here'); 84 | var currentTitleText = titlePrototypal.getValue(); 85 | 86 | /* EditInPlaceArea object. */ 87 | 88 | var EditInPlaceArea = clone(EditInPlaceField); 89 | 90 | // Override certain methods. 91 | 92 | EditInPlaceArea.createElements = function(id) { 93 | this.containerElement = document.createElement('div'); 94 | this.parentElement.appendChild(this.containerElement); 95 | 96 | this.staticElement = document.createElement('p'); 97 | this.containerElement.appendChild(this.staticElement); 98 | this.staticElement.innerHTML = this.value; 99 | 100 | this.fieldElement = document.createElement('textarea'); 101 | this.fieldElement.value = this.value; 102 | this.containerElement.appendChild(this.fieldElement); 103 | 104 | this.saveButton = document.createElement('input'); 105 | this.saveButton.type = 'button'; 106 | this.saveButton.value = 'Save'; 107 | this.containerElement.appendChild(this.saveButton); 108 | 109 | this.cancelButton = document.createElement('input'); 110 | this.cancelButton.type = 'button'; 111 | this.cancelButton.value = 'Cancel'; 112 | this.containerElement.appendChild(this.cancelButton); 113 | 114 | this.convertToText(); 115 | }; 116 | EditInPlaceArea.convertToEditable = function() { 117 | this.staticElement.style.display = 'none'; 118 | this.fieldElement.style.display = 'block'; 119 | this.saveButton.style.display = 'inline'; 120 | this.cancelButton.style.display = 'inline'; 121 | 122 | this.setValue(this.value); 123 | }; 124 | EditInPlaceArea.convertToText = function() { 125 | this.fieldElement.style.display = 'none'; 126 | this.saveButton.style.display = 'none'; 127 | this.cancelButton.style.display = 'none'; 128 | this.staticElement.style.display = 'block'; 129 | 130 | this.setValue(this.value); 131 | }; 132 | -------------------------------------------------------------------------------- /Source Code/Chapter04/4.09 - Edit-in-place example, classical.js: -------------------------------------------------------------------------------- 1 | /* EditInPlaceField class. */ 2 | 3 | function EditInPlaceField(id, parent, value) { 4 | this.id = id; 5 | this.value = value || 'default value'; 6 | this.parentElement = parent; 7 | 8 | this.createElements(this.id); 9 | this.attachEvents(); 10 | }; 11 | 12 | EditInPlaceField.prototype = { 13 | createElements: function(id) { 14 | this.containerElement = document.createElement('div'); 15 | this.parentElement.appendChild(this.containerElement); 16 | 17 | this.staticElement = document.createElement('span'); 18 | this.containerElement.appendChild(this.staticElement); 19 | this.staticElement.innerHTML = this.value; 20 | 21 | this.fieldElement = document.createElement('input'); 22 | this.fieldElement.type = 'text'; 23 | this.fieldElement.value = this.value; 24 | this.containerElement.appendChild(this.fieldElement); 25 | 26 | this.saveButton = document.createElement('input'); 27 | this.saveButton.type = 'button'; 28 | this.saveButton.value = 'Save'; 29 | this.containerElement.appendChild(this.saveButton); 30 | 31 | this.cancelButton = document.createElement('input'); 32 | this.cancelButton.type = 'button'; 33 | this.cancelButton.value = 'Cancel'; 34 | this.containerElement.appendChild(this.cancelButton); 35 | 36 | this.convertToText(); 37 | }, 38 | attachEvents: function() { 39 | var that = this; 40 | addEvent(this.staticElement, 'click', function() { that.convertToEditable(); }); 41 | addEvent(this.saveButton, 'click', function() { that.save(); }); 42 | addEvent(this.cancelButton, 'click', function() { that.cancel(); }); 43 | }, 44 | 45 | convertToEditable: function() { 46 | this.staticElement.style.display = 'none'; 47 | this.fieldElement.style.display = 'inline'; 48 | this.saveButton.style.display = 'inline'; 49 | this.cancelButton.style.display = 'inline'; 50 | 51 | this.setValue(this.value); 52 | }, 53 | save: function() { 54 | this.value = this.getValue(); 55 | var that = this; 56 | var callback = { 57 | success: function() { that.convertToText(); }, 58 | failure: function() { alert('Error saving value.'); } 59 | }; 60 | ajaxRequest('GET', 'save.php?id=' + this.id + '&value=' + this.value, callback); 61 | }, 62 | cancel: function() { 63 | this.convertToText(); 64 | }, 65 | convertToText: function() { 66 | this.fieldElement.style.display = 'none'; 67 | this.saveButton.style.display = 'none'; 68 | this.cancelButton.style.display = 'none'; 69 | this.staticElement.style.display = 'inline'; 70 | 71 | this.setValue(this.value); 72 | }, 73 | 74 | setValue: function(value) { 75 | this.fieldElement.value = value; 76 | this.staticElement.innerHTML = value; 77 | }, 78 | getValue: function() { 79 | return this.fieldElement.value; 80 | } 81 | }; 82 | To create a field, instantiate the class: 83 | var titleClassical = new EditInPlaceField('titleClassical', $('doc'), 'Title Here'); 84 | var currentTitleText = titleClassical.getValue(); 85 | 86 | /* EditInPlaceArea class. */ 87 | 88 | function EditInPlaceArea(id, parent, value) { 89 | EditInPlaceArea.superclass.constructor.call(this, id, parent, value); 90 | }; 91 | extend(EditInPlaceArea, EditInPlaceField); 92 | 93 | // Override certain methods. 94 | 95 | EditInPlaceArea.prototype.createElements = function(id) { 96 | this.containerElement = document.createElement('div'); 97 | this.parentElement.appendChild(this.containerElement); 98 | 99 | this.staticElement = document.createElement('p'); 100 | this.containerElement.appendChild(this.staticElement); 101 | this.staticElement.innerHTML = this.value; 102 | 103 | this.fieldElement = document.createElement('textarea'); 104 | this.fieldElement.value = this.value; 105 | this.containerElement.appendChild(this.fieldElement); 106 | 107 | this.saveButton = document.createElement('input'); 108 | this.saveButton.type = 'button'; 109 | this.saveButton.value = 'Save'; 110 | this.containerElement.appendChild(this.saveButton); 111 | 112 | this.cancelButton = document.createElement('input'); 113 | this.cancelButton.type = 'button'; 114 | this.cancelButton.value = 'Cancel'; 115 | this.containerElement.appendChild(this.cancelButton); 116 | 117 | this.convertToText(); 118 | }; 119 | EditInPlaceArea.prototype.convertToEditable = function() { 120 | this.staticElement.style.display = 'none'; 121 | this.fieldElement.style.display = 'block'; 122 | this.saveButton.style.display = 'inline'; 123 | this.cancelButton.style.display = 'inline'; 124 | 125 | this.setValue(this.value); 126 | }; 127 | EditInPlaceArea.prototype.convertToText = function() { 128 | this.fieldElement.style.display = 'none'; 129 | this.saveButton.style.display = 'none'; 130 | this.cancelButton.style.display = 'none'; 131 | this.staticElement.style.display = 'block'; 132 | 133 | this.setValue(this.value); 134 | }; 135 | -------------------------------------------------------------------------------- /Source Code/Chapter04/4.11 - Edit-in-place example, mixin.js: -------------------------------------------------------------------------------- 1 | /* Mixin class for the edit-in-place methods. */ 2 | 3 | var EditInPlaceMixin = function() {}; 4 | EditInPlaceMixin.prototype = { 5 | createElements: function(id) { 6 | this.containerElement = document.createElement('div'); 7 | this.parentElement.appendChild(this.containerElement); 8 | 9 | this.staticElement = document.createElement('span'); 10 | this.containerElement.appendChild(this.staticElement); 11 | this.staticElement.innerHTML = this.value; 12 | 13 | this.fieldElement = document.createElement('input'); 14 | this.fieldElement.type = 'text'; 15 | this.fieldElement.value = this.value; 16 | this.containerElement.appendChild(this.fieldElement); 17 | 18 | this.saveButton = document.createElement('input'); 19 | this.saveButton.type = 'button'; 20 | this.saveButton.value = 'Save'; 21 | this.containerElement.appendChild(this.saveButton); 22 | 23 | this.cancelButton = document.createElement('input'); 24 | this.cancelButton.type = 'button'; 25 | this.cancelButton.value = 'Cancel'; 26 | this.containerElement.appendChild(this.cancelButton); 27 | 28 | this.convertToText(); 29 | }, 30 | attachEvents: function() { 31 | var that = this; 32 | addEvent(this.staticElement, 'click', function() { that.convertToEditable(); }); 33 | addEvent(this.saveButton, 'click', function() { that.save(); }); 34 | addEvent(this.cancelButton, 'click', function() { that.cancel(); }); 35 | }, 36 | 37 | convertToEditable: function() { 38 | this.staticElement.style.display = 'none'; 39 | this.fieldElement.style.display = 'inline'; 40 | this.saveButton.style.display = 'inline'; 41 | this.cancelButton.style.display = 'inline'; 42 | 43 | this.setValue(this.value); 44 | }, 45 | save: function() { 46 | this.value = this.getValue(); 47 | var that = this; 48 | var callback = { 49 | success: function() { that.convertToText(); }, 50 | failure: function() { alert('Error saving value.'); } 51 | }; 52 | ajaxRequest('GET', 'save.php?id=' + this.id + '&value=' + this.value, callback); 53 | }, 54 | cancel: function() { 55 | this.convertToText(); 56 | }, 57 | convertToText: function() { 58 | this.fieldElement.style.display = 'none'; 59 | this.saveButton.style.display = 'none'; 60 | this.cancelButton.style.display = 'none'; 61 | this.staticElement.style.display = 'inline'; 62 | 63 | this.setValue(this.value); 64 | }, 65 | 66 | setValue: function(value) { 67 | this.fieldElement.value = value; 68 | this.staticElement.innerHTML = value; 69 | }, 70 | getValue: function() { 71 | return this.fieldElement.value; 72 | } 73 | }; 74 | 75 | /* EditInPlaceField class. */ 76 | 77 | function EditInPlaceField(id, parent, value) { 78 | this.id = id; 79 | this.value = value || 'default value'; 80 | this.parentElement = parent; 81 | 82 | this.createElements(this.id); 83 | this.attachEvents(); 84 | }; 85 | augment(EditInPlaceField, EditInPlaceMixin); 86 | 87 | /* EditInPlaceArea class. */ 88 | 89 | function EditInPlaceArea(id, parent, value) { 90 | this.id = id; 91 | this.value = value || 'default value'; 92 | this.parentElement = parent; 93 | 94 | this.createElements(this.id); 95 | this.attachEvents(); 96 | }; 97 | 98 | // Add certain methods so that augment won't include them. 99 | 100 | EditInPlaceArea.prototype.createElements = function(id) { 101 | this.containerElement = document.createElement('div'); 102 | this.parentElement.appendChild(this.containerElement); 103 | 104 | this.staticElement = document.createElement('p'); 105 | this.containerElement.appendChild(this.staticElement); 106 | this.staticElement.innerHTML = this.value; 107 | 108 | this.fieldElement = document.createElement('textarea'); 109 | this.fieldElement.value = this.value; 110 | this.containerElement.appendChild(this.fieldElement); 111 | 112 | this.saveButton = document.createElement('input'); 113 | this.saveButton.type = 'button'; 114 | this.saveButton.value = 'Save'; 115 | this.containerElement.appendChild(this.saveButton); 116 | 117 | this.cancelButton = document.createElement('input'); 118 | this.cancelButton.type = 'button'; 119 | this.cancelButton.value = 'Cancel'; 120 | this.containerElement.appendChild(this.cancelButton); 121 | 122 | this.convertToText(); 123 | }; 124 | EditInPlaceArea.prototype.convertToEditable = function() { 125 | this.staticElement.style.display = 'none'; 126 | this.fieldElement.style.display = 'block'; 127 | this.saveButton.style.display = 'inline'; 128 | this.cancelButton.style.display = 'inline'; 129 | 130 | this.setValue(this.value); 131 | }; 132 | EditInPlaceArea.prototype.convertToText = function() { 133 | this.fieldElement.style.display = 'none'; 134 | this.saveButton.style.display = 'none'; 135 | this.cancelButton.style.display = 'none'; 136 | this.staticElement.style.display = 'block'; 137 | 138 | this.setValue(this.value); 139 | }; 140 | 141 | augment(EditInPlaceArea, EditInPlaceMixin); 142 | -------------------------------------------------------------------------------- /Source Code/Chapter08/8.04 - Building an XHR connection queue.js: -------------------------------------------------------------------------------- 1 | var asyncRequest = (function() { 2 | function handleReadyState(o, callback) { 3 | var poll = window.setInterval( 4 | function() { 5 | if (o && o.readyState == 4) { 6 | window.clearInterval(poll); 7 | if (callback) { 8 | callback(o); 9 | } 10 | } 11 | }, 12 | 50 13 | ); 14 | } 15 | var getXHR = function() { 16 | var http; 17 | try { 18 | http = new XMLHttpRequest; 19 | getXHR = function() { 20 | return new XMLHttpRequest; 21 | }; 22 | } 23 | catch(e) { 24 | var msxml = [ 25 | 'MSXML2.XMLHTTP.3.0', 26 | 'MSXML2.XMLHTTP', 27 | 'Microsoft.XMLHTTP' 28 | ]; 29 | for (var I = 0, len = msxml.length; i < len; ++i) { 30 | try { 31 | http = new ActiveXObject(msxml[i]); 32 | getXHR = function() { 33 | return new ActiveXObject(msxml[i]); 34 | }; 35 | break; 36 | } 37 | catch(e) {} 38 | } 39 | } 40 | return http; 41 | }; 42 | return function(method, uri, callback, postData) { 43 | var http = getXHR(); 44 | http.open(method, uri, true); 45 | handleReadyState(http, callback); 46 | http.send(postData || null); 47 | return http; 48 | }; 49 | })(); 50 | 51 | 52 | Function.prototype.method = function(name, fn) { 53 | this.prototype[name] = fn; 54 | return this; 55 | }; 56 | 57 | 58 | // From the Mozilla Developer Center website at http://developer.mozilla.org/en/docs/New_in_JavaScript_1.6#Array_extras. 59 | 60 | if ( !Array.prototype.forEach ) { 61 | Array.method('forEach', function(fn, thisObj) { 62 | var scope = thisObj || window; 63 | for ( var i = 0, len = this.length; i < len; ++i ) { 64 | fn.call(scope, this[i], i, this); 65 | } 66 | }); 67 | } 68 | 69 | if ( !Array.prototype.filter ) { 70 | Array.method('filter', function(fn, thisObj) { 71 | var scope = thisObj || window; 72 | var a = []; 73 | for ( var i = 0, len = this.length; i < len; ++i ) { 74 | if ( !fn.call(scope, this[i], i, this) ) { 75 | continue; 76 | } 77 | a.push(this[i]); 78 | } 79 | return a; 80 | }); 81 | } 82 | 83 | 84 | window.DED = window.DED || {}; 85 | DED.util = DED.util || {}; 86 | DED.util.Observer = function() { 87 | this.fns = []; 88 | } 89 | DED.util.Observer.prototype = { 90 | subscribe: function(fn) { 91 | this.fns.push(fn); 92 | }, 93 | unsubscribe: function(fn) { 94 | this.fns = this.fns.filter( 95 | function(el) { 96 | if ( el !== fn ) { 97 | return el; 98 | } 99 | } 100 | ); 101 | }, 102 | fire: function(o) { 103 | this.fns.forEach( 104 | function(el) { 105 | el(o); 106 | } 107 | ); 108 | } 109 | }; 110 | 111 | 112 | DED.Queue = function() { 113 | // Queued requests. 114 | this.queue = []; 115 | 116 | // Observable Objects that can notify the client of interesting moments 117 | // on each DED.Queue instance. 118 | this.onComplete = new DED.util.Observer; 119 | this.onFailure = new DED.util.Observer; 120 | this.onFlush = new DED.util.Observer; 121 | 122 | // Core properties that set up a frontend queueing system. 123 | this.retryCount = 3; 124 | this.currentRetry = 0; 125 | this.paused = false; 126 | this.timeout = 5000; 127 | this.conn = {}; 128 | this.timer = {}; 129 | }; 130 | 131 | DED.Queue. 132 | method('flush', function() { 133 | if (!this.queue.length > 0) { 134 | return; 135 | } 136 | if (this.paused) { 137 | this.paused = false; 138 | return; 139 | } 140 | var that = this; 141 | this.currentRetry++; 142 | var abort = function() { 143 | that.conn.abort(); 144 | if (that.currentRetry == that.retryCount) { 145 | that.onFailure.fire(); 146 | that.currentRetry = 0; 147 | } else { 148 | that.flush(); 149 | } 150 | }; 151 | this.timer = window.setTimeout(abort, this.timeout); 152 | var callback = function(o) { 153 | window.clearTimeout(that.timer); 154 | that.currentRetry = 0; 155 | that.queue.shift(); 156 | that.onFlush.fire(o.responseText); 157 | if (that.queue.length == 0) { 158 | that.onComplete.fire(); 159 | return; 160 | } 161 | // recursive call to flush 162 | that.flush(); 163 | }; 164 | this.conn = asyncRequest( 165 | this.queue[0]['method'], 166 | this.queue[0]['uri'], 167 | callback, 168 | this.queue[0]['params'] 169 | ); 170 | }). 171 | method('setRetryCount', function(count) { 172 | this.retryCount = count; 173 | }). 174 | method('setTimeout', function(time) { 175 | this.timeout = time; 176 | }). 177 | method('add', function(o) { 178 | this.queue.push(o); 179 | }). 180 | method('pause', function() { 181 | this.paused = true; 182 | }). 183 | method('dequeue', function() { 184 | this.queue.pop(); 185 | }). 186 | method('clear', function() { 187 | this.queue = []; 188 | }); 189 | 190 | 191 | /* Usage. */ 192 | 193 | var q = new DED.Queue; 194 | // Reset our retry count to be higher for slow connections. 195 | q.setRetryCount(5); 196 | // Decrease timeout limit because we still want fast connections to benefit. 197 | q.setTimeout(1000); 198 | // Add two slots. 199 | q.add({ 200 | method: 'GET', 201 | uri: '/path/to/file.php?ajax=true' 202 | }); 203 | q.add({ 204 | method: 'GET', 205 | uri: '/path/to/file.php?ajax=true&woe=me' 206 | }); 207 | // Flush the queue. 208 | q.flush(); 209 | // Pause the queue, retaining the requests. 210 | q.pause(); 211 | // Clear our queue and start fresh. 212 | q.clear(); 213 | // Add two requests. 214 | q.add({ 215 | method: 'GET', 216 | uri: '/path/to/file.php?ajax=true' 217 | }); 218 | q.add({ 219 | method: 'GET', 220 | uri: '/path/to/file.php?ajax=true&woe=me' 221 | }); 222 | // Remove the last request from the queue. 223 | q.dequeue(); 224 | // Flush the queue again. 225 | q.flush(); 226 | --------------------------------------------------------------------------------