diff --git a/.project b/.project
new file mode 100644
index 0000000..f17759c
--- /dev/null
+++ b/.project
@@ -0,0 +1,12 @@
+
+
+ Source Code
+
+
+
+
+
+
+ com.aptana.projects.webnature
+
+
diff --git a/Chapter01/1.01 - The flexibility of JavaScript.js b/Chapter01/1.01 - The flexibility of JavaScript.js
new file mode 100644
index 0000000..e9eaaaf
--- /dev/null
+++ b/Chapter01/1.01 - The flexibility of JavaScript.js
@@ -0,0 +1,89 @@
+/* below is five method to do the same thing */
+/* a. Start and stop animations using functions. */
+
+function startAnimation() {
+ ...
+}
+
+function stopAnimation() {
+ ...
+}
+
+
+
+/* b. Anim class. */
+
+var Anim = function() {
+ ...
+};
+Anim.prototype.start = function() {
+ ...
+};
+Anim.prototype.stop = function() {
+ ...
+};
+
+/* Usage. */
+
+var myAnim = new Anim();
+myAnim.start();
+...
+myAnim.stop();
+
+
+
+/* c. Anim class, with a slightly different syntax for declaring methods. */
+
+var Anim = function() {
+ ...
+};
+Anim.prototype = {
+ start: function() {
+ ...
+ },
+ stop: function() {
+ ...
+ }
+};
+
+
+
+/* d. Add a method to the Function class that can be used to declare methods. */
+
+Function.prototype.method = function(name, fn) {
+ this.prototype[name] = fn;
+};
+
+/* Anim class, with methods created using a convenience method. */
+
+var Anim = function() {
+ ...
+};
+Anim.method('start', function() {
+ ...
+});
+Anim.method('stop', function() {
+ ...
+});
+
+
+
+/* e. This version allows the calls to be chained. */
+
+Function.prototype.method = function(name, fn) {
+ this.prototype[name] = fn;
+ return this;
+};
+
+/* Anim class, with methods created using a convenience method and chaining. */
+
+var Anim = function() {
+ ...
+};
+Anim.
+ method('start', function() {
+ ...
+ }).
+ method('stop', function() {
+ ...
+ });
diff --git a/Chapter01/1.02 - Functions as first-class objects.js b/Chapter01/1.02 - Functions as first-class objects.js
new file mode 100644
index 0000000..be8ee58
--- /dev/null
+++ b/Chapter01/1.02 - Functions as first-class objects.js
@@ -0,0 +1,39 @@
+/* An anonymous function, executed immediately. */
+
+(function() {
+ var foo = 10;
+ var bar = 2;
+ alert(foo * bar);
+})();
+
+
+/* An anonymous function with arguments. */
+
+(function(foo, bar) {
+ alert(foo * bar);
+})(10, 2);
+
+
+/* An anonymous function that returns a value. */
+
+var baz = (function(foo, bar) {
+ return foo * bar;
+})(10, 2);
+
+// baz will equal 20.
+
+
+/* An anonymous function used as a closure. */
+
+var baz;
+
+(function() {
+ var foo = 10;
+ var bar = 2;
+ baz = function() {
+ return foo * bar;
+ };
+})();
+
+baz(); // baz can access foo and bar, even though is it executed outside of the
+ // anonymous function.
diff --git a/Chapter01/1.03 - The mutability of objects.js b/Chapter01/1.03 - The mutability of objects.js
new file mode 100644
index 0000000..0c08807
--- /dev/null
+++ b/Chapter01/1.03 - The mutability of objects.js
@@ -0,0 +1,38 @@
+function displayError(message) {
+ displayError.numTimesExecuted++;
+ alert(message);
+};
+displayError.numTimesExecuted = 0;
+
+
+/* Class Person. */
+
+function Person(name, age) {
+ this.name = name;
+ this.age = age;
+}
+Person.prototype = {
+ getName: function() {
+ return this.name;
+ },
+ getAge: function() {
+ return this.age;
+ }
+}
+
+/* Instantiate the class. */
+
+var alice = new Person('Alice', 93);
+var bill = new Person('Bill', 30);
+
+/* Modify the class. */
+
+Person.prototype.getGreeting = function() {
+ return 'Hi ' + this.getName() + '!';
+};
+
+/* Modify a specific instance. */
+
+alice.displayGreeting = function() {
+ alert(this.getGreeting());
+}
diff --git a/Chapter02/2.01 - Describing interfaces with comments.js b/Chapter02/2.01 - Describing interfaces with comments.js
new file mode 100644
index 0000000..c02546d
--- /dev/null
+++ b/Chapter02/2.01 - Describing interfaces with comments.js
@@ -0,0 +1,35 @@
+/*
+
+interface Composite {
+ function add(child);
+ function remove(child);
+ function getChild(index);
+}
+
+interface FormItem {
+ function save();
+}
+
+*/
+
+var CompositeForm = function(id, method, action) { // implements Composite, FormItem
+ ...
+};
+
+// Implement the Composite interface.
+
+CompositeForm.prototype.add = function(child) {
+ ...
+};
+CompositeForm.prototype.remove = function(child) {
+ ...
+};
+CompositeForm.prototype.getChild = function(index) {
+ ...
+};
+
+// Implement the FormItem interface.
+
+CompositeForm.prototype.save = function() {
+ ...
+};
diff --git a/Chapter02/2.02 - Emulating interfaces with attribute checking.js b/Chapter02/2.02 - Emulating interfaces with attribute checking.js
new file mode 100644
index 0000000..1725c12
--- /dev/null
+++ b/Chapter02/2.02 - Emulating interfaces with attribute checking.js
@@ -0,0 +1,49 @@
+/*
+
+interface Composite {
+ function add(child);
+ function remove(child);
+ function getChild(index);
+}
+
+interface FormItem {
+ function save();
+}
+
+*/
+
+var CompositeForm = function(id, method, action) {
+ this.implementsInterfaces = ['Composite', 'FormItem'];
+ ...
+};
+
+...
+
+function addForm(formInstance) {
+ if(!implements(formInstance, 'Composite', 'FormItem')) {
+ throw new Error("Object does not implement a required interface.");
+ }
+ ...
+}
+
+// The implements function, which checks to see if an object declares that it
+// implements the required interfaces.
+
+function implements(object) {
+ for(var i = 1; i < arguments.length; i++) { // Looping through all arguments
+ // after the first one.
+ var interfaceName = arguments[i];
+ var interfaceFound = false;
+ for(var j = 0; j < object.implementsInterfaces.length; j++) {
+ if(object.implementsInterfaces[j] == interfaceName) {
+ interfaceFound = true;
+ break;
+ }
+ }
+
+ if(!interfaceFound) {
+ return false; // An interface was not found.
+ }
+ }
+ return true; // All interfaces were found.
+}
diff --git a/Chapter02/2.03 - Emulating interfaces with duck typing.js b/Chapter02/2.03 - Emulating interfaces with duck typing.js
new file mode 100644
index 0000000..28db04a
--- /dev/null
+++ b/Chapter02/2.03 - Emulating interfaces with duck typing.js
@@ -0,0 +1,18 @@
+// Interfaces.
+
+var Composite = new Interface('Composite', ['add', 'remove', 'getChild']);
+var FormItem = new Interface('FormItem', ['save']);
+
+// CompositeForm class
+
+var CompositeForm = function(id, method, action) {
+ ...
+};
+
+...
+
+function addForm(formInstance) {
+ ensureImplements(formInstance, Composite, FormItem);
+ // This function will throw an error if a required method is not implemented.
+ ...
+}
diff --git a/Chapter02/2.04 - The interface implementation for this book.js b/Chapter02/2.04 - The interface implementation for this book.js
new file mode 100644
index 0000000..9df3439
--- /dev/null
+++ b/Chapter02/2.04 - The interface implementation for this book.js
@@ -0,0 +1,20 @@
+// Interfaces.
+
+var Composite = new Interface('Composite', ['add', 'remove', 'getChild']);
+var FormItem = new Interface('FormItem', ['save']);
+
+// CompositeForm class
+
+var CompositeForm = function(id, method, action) { // implements Composite, FormItem
+ ...
+};
+
+...
+
+function addForm(formInstance) {
+ Interface.ensureImplements(formInstance, Composite, FormItem);
+ // This function will throw an error if a required method is not implemented,
+ // halting execution of the function.
+ // All code beneath this line will be executed only if the checks pass.
+ ...
+}
diff --git a/Chapter02/2.05 - The Interface class.js b/Chapter02/2.05 - The Interface class.js
new file mode 100644
index 0000000..145c786
--- /dev/null
+++ b/Chapter02/2.05 - The Interface class.js
@@ -0,0 +1,44 @@
+// Constructor.
+
+var Interface = function(name, methods) {
+ if(arguments.length != 2) {
+ throw new Error("Interface constructor called with " + arguments.length
+ + "arguments, but expected exactly 2.");
+ }
+
+ this.name = name;
+ this.methods = [];
+ for(var i = 0, len = methods.length; i < len; i++) {
+ if(typeof methods[i] !== 'string') {
+ throw new Error("Interface constructor expects method names to be "
+ + "passed in as a string.");
+ }
+ this.methods.push(methods[i]);
+ }
+};
+
+// Static class method.
+
+Interface.ensureImplements = function(object) {
+ if(arguments.length < 2) {
+ throw new Error("Function Interface.ensureImplements called with " +
+ arguments.length + "arguments, but expected at least 2.");
+ }
+
+ for(var i = 1, len = arguments.length; i < len; i++) {
+ var interface = arguments[i];
+ if(interface.constructor !== Interface) {
+ throw new Error("Function Interface.ensureImplements expects arguments "
+ + "two and above to be instances of Interface.");
+ }
+
+ for(var j = 0, methodsLen = interface.methods.length; j < methodsLen; j++) {
+ var method = interface.methods[j];
+ if(!object[method] || typeof object[method] !== 'function') {
+ throw new Error("Function Interface.ensureImplements: object "
+ + "does not implement the " + interface.name
+ + " interface. Method " + method + " was not found.");
+ }
+ }
+ }
+};
diff --git a/Chapter02/2.06 - When to use the Interface class.js b/Chapter02/2.06 - When to use the Interface class.js
new file mode 100644
index 0000000..5f741bb
--- /dev/null
+++ b/Chapter02/2.06 - When to use the Interface class.js
@@ -0,0 +1,9 @@
+var DynamicMap = new Interface('DynamicMap', ['centerOnPoint', 'zoom', 'draw']);
+
+function displayRoute(mapInstance) {
+ Interface.ensureImplements(mapInstace, DynamicMap);
+ mapInstance.centerOnPoint(12, 34);
+ mapInstance.zoom(5);
+ mapInstance.draw();
+ ...
+}
diff --git a/Chapter02/2.07 - An example illustrating the use of the Interface class.js b/Chapter02/2.07 - An example illustrating the use of the Interface class.js
new file mode 100644
index 0000000..3ec0247
--- /dev/null
+++ b/Chapter02/2.07 - An example illustrating the use of the Interface class.js
@@ -0,0 +1,47 @@
+// ResultFormatter class, before we implement interface checking.
+
+var ResultFormatter = function(resultsObject) {
+ if(!(resultsObject instanceOf TestResult)) {
+ throw new Error('ResultsFormatter: constructor requires an instance '
+ + 'of TestResult as an argument.');
+ }
+ this.resultsObject = resultsObject;
+};
+
+ResultFormatter.prototype.renderResults = function() {
+ var dateOfTest = this.resultsObject.getDate();
+ var resultsArray = this.resultsObject.getResults();
+
+ var resultsContainer = document.createElement('div');
+
+ var resultsHeader = document.createElement('h3');
+ resultsHeader.innerHTML = 'Test Results from ' + dateOfTest.toUTCString();
+ resultsContainer.appendChild(resultsHeader);
+
+ var resultsList = document.createElement('ul');
+ resultsContainer.appendChild(resultsList);
+
+ for(var i = 0, len = resultsArray.length; i < len; i++) {
+ var listItem = document.createElement('li');
+ listItem.innerHTML = resultsArray[i];
+ resultsList.appendChild(listItem);
+ }
+
+ return resultsContainer;
+};
+
+
+// ResultSet Interface.
+
+var ResultSet = new Interface('ResultSet', ['getDate', 'getResults']);
+
+// ResultFormatter class, after adding Interface checking.
+
+var ResultFormatter = function(resultsObject) {
+ Interface.ensureImplements(resultsObject, ResultSet);
+ this.resultsObject = resultsObject;
+};
+
+ResultFormatter.prototype.renderResults = function() {
+ ...
+};
diff --git a/Chapter03/3.01 - Book example class.js b/Chapter03/3.01 - Book example class.js
new file mode 100644
index 0000000..44d8c1e
--- /dev/null
+++ b/Chapter03/3.01 - Book example class.js
@@ -0,0 +1,3 @@
+// Book(isbn, title, author)
+var theHobbit = new Book('0-395-07122-4', 'The Hobbit', 'J. R. R. Tolkein');
+theHobbit.display(); // Outputs the data by creating and populating an HTML element.
diff --git a/Chapter03/3.02 - Fully exposed object.js b/Chapter03/3.02 - Fully exposed object.js
new file mode 100644
index 0000000..229e24f
--- /dev/null
+++ b/Chapter03/3.02 - Fully exposed object.js
@@ -0,0 +1,114 @@
+var Book = function(isbn, title, author) {
+ if(isbn == undefined) throw new Error('Book constructor requires an isbn.');
+ this.isbn = isbn;
+ this.title = title || 'No title specified';
+ this.author = author || 'No author specified';
+}
+
+Book.prototype.display = function() {
+ ...
+};
+
+
+/* With ISBN check. */
+
+var Book = function(isbn, title, author) {
+ if(!this.checkIsbn(isbn)) throw new Error('Book: Invalid ISBN.');
+ this.isbn = isbn;
+ this.title = title || 'No title specified';
+ this.author = author || 'No author specified';
+}
+
+Book.prototype = {
+ checkIsbn: function(isbn) {
+ if(isbn == undefined || typeof isbn != 'string') {
+ return false;
+ }
+
+ isbn = isbn.replace(/-/. ''); // Remove dashes.
+ if(isbn.length != 10 && isbn.length != 13) {
+ return false;
+ }
+
+ var sum = 0;
+ if(isbn.length === 10) { // 10 digit ISBN.
+ If(!isbn.match(\^\d{9}\)) { // Ensure characters 1 through 9 are digits.
+ return false;
+ }
+
+ for(var i = 0; i < 9; i++) {
+ sum += isbn.charAt(i) * (10 - i);
+ }
+ var checksum = sum % 11;
+ if(checksum === 10) checksum = 'X';
+ if(isbn.charAt(9) != checksum) {
+ return false;
+ }
+ }
+ else { // 13 digit ISBN.
+ if(!isbn.match(\^\d{12}\)) { // Ensure characters 1 through 12 are digits.
+ return false;
+ }
+
+ for(var i = 0; i < 12; i++) {
+ sum += isbn.charAt(i) * ((i % 2 === 0) ? 1 : 3);
+ }
+ var checksum = sum % 10;
+ if(isbn.charAt(12) != checksum) {
+ return false;
+ }
+ }
+
+ return true; // All tests passed.
+ },
+
+ display: function() {
+ ...
+ }
+};
+
+
+/* Publication interface. */
+
+var Publication = new Interface('Publication', ['getIsbn', 'setIsbn', 'getTitle',
+ 'setTitle', 'getAuthor', 'setAuthor', 'display']);
+
+
+/* With mutators and accessors. */
+
+var Book = function(isbn, title, author) { // implements Publication
+ this.setIsbn(isbn);
+ this.setTitle(title);
+ this.setAuthor(author);
+}
+
+Book.prototype = {
+ checkIsbn: function(isbn) {
+ ...
+ },
+ getIsbn: function() {
+ return this.isbn;
+ },
+ setIsbn: function(isbn) {
+ if(!this.checkIsbn(isbn)) throw new Error('Book: Invalid ISBN.');
+ this.isbn = isbn;
+ },
+
+ getTitle: function() {
+ return this.title;
+ },
+ setTitle: function(title) {
+ this.title = title || 'No title specified';
+ },
+
+ getAuthor: function() {
+ return this.author;
+ },
+ setAuthor: function(author) {
+ this.author = author || 'No author specified';
+ },
+
+ display: function() {
+ ...
+ }
+};
diff --git a/Chapter03/3.03 - Private methods with underscores.js b/Chapter03/3.03 - Private methods with underscores.js
new file mode 100644
index 0000000..db4403e
--- /dev/null
+++ b/Chapter03/3.03 - Private methods with underscores.js
@@ -0,0 +1,36 @@
+var Book = function(isbn, title, author) { // implements Publication
+ this.setIsbn(isbn);
+ this.setTitle(title);
+ this.setAuthor(author);
+}
+
+Book.prototype = {
+ _checkIsbn: function(isbn) {
+ ...
+ },
+ getIsbn: function() {
+ return this._isbn;
+ },
+ setIsbn: function(isbn) {
+ if(!this._checkIsbn(isbn)) throw new Error('Book: Invalid ISBN.');
+ this._isbn = isbn;
+ },
+
+ getTitle: function() {
+ return this._title;
+ },
+ setTitle: function(title) {
+ this._title = title || 'No title specified';
+ },
+
+ getAuthor: function() {
+ return this._author;
+ },
+ setAuthor: function(author) {
+ this._author = author || 'No author specified';
+ },
+
+ display: function() {
+ ...
+ }
+};
diff --git a/Chapter03/3.04 - Scope, nested functions, and closures.js b/Chapter03/3.04 - Scope, nested functions, and closures.js
new file mode 100644
index 0000000..5f9a3a5
--- /dev/null
+++ b/Chapter03/3.04 - Scope, nested functions, and closures.js
@@ -0,0 +1,32 @@
+function foo() {
+ var a = 10;
+
+ function bar() {
+ a *= 2;
+ }
+
+ bar();
+ return a;
+}
+
+
+
+
+function foo() {
+ var a = 10;
+
+ function bar() {
+ a *= 2;
+ return a;
+ }
+
+ return bar;
+}
+
+var baz = foo(); // baz is now a reference to function bar.
+baz(); // returns 20.
+baz(); // returns 40.
+baz(); // returns 80.
+
+var blat = foo(); // blat is another reference to bar.
+blat(); // returns 20, because a new copy of a is being used.
diff --git a/Chapter03/3.05 - Private methods with closures.js b/Chapter03/3.05 - Private methods with closures.js
new file mode 100644
index 0000000..b7586c5
--- /dev/null
+++ b/Chapter03/3.05 - Private methods with closures.js
@@ -0,0 +1,45 @@
+var Book = function(newIsbn, newTitle, newAuthor) { // implements Publication
+
+ // Private attributes.
+ var isbn, title, author;
+
+ // Private method.
+ function checkIsbn(isbn) {
+ ...
+ }
+
+ // Privileged methods.
+ this.getIsbn = function() {
+ return isbn;
+ };
+ this.setIsbn = function(newIsbn) {
+ if(!checkIsbn(newIsbn)) throw new Error('Book: Invalid ISBN.');
+ isbn = newIsbn;
+ };
+
+ this.getTitle = function() {
+ return title;
+ };
+ this.setTitle = function(newTitle) {
+ title = newTitle || 'No title specified';
+ };
+
+ this.getAuthor = function() {
+ return author;
+ };
+ this.setAuthor = function(newAuthor) {
+ author = newAuthor || 'No author specified';
+ };
+
+ // Constructor code.
+ this.setIsbn(newIsbn);
+ this.setTitle(newTitle);
+ this.setAuthor(newAuthor);
+};
+
+// Public, non-privileged methods.
+Book.prototype = {
+ display: function() {
+ ...
+ }
+};
diff --git a/Chapter03/3.06 - Static members.js b/Chapter03/3.06 - Static members.js
new file mode 100644
index 0000000..3487329
--- /dev/null
+++ b/Chapter03/3.06 - Static members.js
@@ -0,0 +1,62 @@
+var Book = (function() {
+
+ // Private static attributes.
+ var numOfBooks = 0;
+
+ // Private static method.
+ function checkIsbn(isbn) {
+ ...
+ }
+
+ // Return the constructor.
+ return function(newIsbn, newTitle, newAuthor) { // implements Publication
+
+ // Private attributes.
+ var isbn, title, author;
+
+ // Privileged methods.
+ this.getIsbn = function() {
+ return isbn;
+ };
+ this.setIsbn = function(newIsbn) {
+ if(!checkIsbn(newIsbn)) throw new Error('Book: Invalid ISBN.');
+ isbn = newIsbn;
+ };
+
+ this.getTitle = function() {
+ return title;
+ };
+ this.setTitle = function(newTitle) {
+ title = newTitle || 'No title specified';
+ };
+
+ this.getAuthor = function() {
+ return author;
+ };
+ this.setAuthor = function(newAuthor) {
+ author = newAuthor || 'No author specified';
+ };
+
+ // Constructor code.
+ numOfBooks++; // Keep track of how many Books have been instantiated
+ // with the private static attribute.
+ if(numOfBooks > 50) throw new Error('Book: Only 50 instances of Book can be '
+ + 'created.');
+
+ this.setIsbn(newIsbn);
+ this.setTitle(newTitle);
+ this.setAuthor(newAuthor);
+ }
+})();
+
+// Public static method.
+Book.convertToTitleCase = function(inputString) {
+ ...
+};
+
+// Public, non-privileged methods.
+Book.prototype = {
+ display: function() {
+ ...
+ }
+};
diff --git a/Chapter03/3.07 - Constants.js b/Chapter03/3.07 - Constants.js
new file mode 100644
index 0000000..53a1732
--- /dev/null
+++ b/Chapter03/3.07 - Constants.js
@@ -0,0 +1,46 @@
+var Class = (function() {
+
+ // Constants (created as private static attributes).
+ var UPPER_BOUND = 100;
+
+ // Privileged static method.
+ this.getUPPER_BOUND() {
+ return UPPER_BOUND;
+ }
+
+ ...
+
+ // Return the constructor.
+ return function(constructorArgument) {
+ ...
+ }
+})();
+
+
+/* Grouping constants together. */
+
+var Class = (function() {
+
+ // Private static attributes.
+ var constants = {
+ UPPER_BOUND: 100,
+ LOWER_BOUND: -100
+ }
+
+ // Privileged static method.
+ this.getConstant(name) {
+ return constants[name];
+ }
+
+ ...
+
+ // Return the constructor.
+ return function(constructorArgument) {
+ ...
+ }
+})();
+
+
+/* Usage. */
+
+Class.getConstant('UPPER_BOUND');
diff --git a/Chapter04/4.01 - Classical inheritance.js b/Chapter04/4.01 - Classical inheritance.js
new file mode 100644
index 0000000..2a52c92
--- /dev/null
+++ b/Chapter04/4.01 - Classical inheritance.js
@@ -0,0 +1,12 @@
+/* Class Person. */
+
+function Person(name) {
+ this.name = name;
+}
+
+Person.prototype.getName = function() {
+ return this.name;
+}
+
+var reader = new Person('John Smith');
+reader.getName();
diff --git a/Chapter04/4.02 - The prototype chain.js b/Chapter04/4.02 - The prototype chain.js
new file mode 100644
index 0000000..f061fd4
--- /dev/null
+++ b/Chapter04/4.02 - The prototype chain.js
@@ -0,0 +1,19 @@
+/* Class Author. */
+
+function Author(name, books) {
+ Person.call(this, name); // Call the superclass' constructor in the scope of this.
+ this.books = books; // Add an attribute to Author.
+}
+
+Author.prototype = new Person(); // Set up the prototype chain.
+Author.prototype.constructor = Author; // Set the constructor attribute to Author.
+Author.prototype.getBooks = function() { // Add a method to Author.
+ return this.books;
+};
+
+var author = [];
+author[0] = new Author('Dustin Diaz', ['JavaScript Design Patterns']);
+author[1] = new Author('Ross Harmes', ['JavaScript Design Patterns']);
+
+author[1].getName();
+author[1].getBooks();
diff --git a/Chapter04/4.03 - The extend function.js b/Chapter04/4.03 - The extend function.js
new file mode 100644
index 0000000..417c946
--- /dev/null
+++ b/Chapter04/4.03 - The extend function.js
@@ -0,0 +1,65 @@
+/* Extend function. */
+
+function extend(subClass, superClass) {
+ var F = function() {};
+ F.prototype = superClass.prototype;
+ subClass.prototype = new F();
+ subClass.prototype.constructor = subClass;
+}
+
+
+/* Class Person. */
+
+function Person(name) {
+ this.name = name;
+}
+
+Person.prototype.getName = function() {
+ return this.name;
+}
+
+/* Class Author. */
+
+function Author(name, books) {
+ Person.call(this, name);
+ this.books = books;
+}
+extend(Author, Person);
+
+Author.prototype.getBooks = function() {
+ return this.books;
+};
+
+
+
+/* Extend function, improved. */
+
+function extend(subClass, superClass) {
+ var F = function() {};
+ F.prototype = superClass.prototype;
+ subClass.prototype = new F();
+ subClass.prototype.constructor = subClass;
+
+ subClass.superclass = superClass.prototype;
+ if(superClass.prototype.constructor == Object.prototype.constructor) {
+ superClass.prototype.constructor = superClass;
+ }
+}
+
+
+/* Class Author. */
+
+function Author(name, books) {
+ Author.superclass.constructor.call(this, name);
+ this.books = books;
+}
+extend(Author, Person);
+
+Author.prototype.getBooks = function() {
+ return this.books;
+};
+
+Author.prototype.getName = function() {
+ var name = Author.superclass.getName.call(this);
+ return name + ', Author of ' + this.getBooks().join(', ');
+};
diff --git a/Chapter04/4.04 - Prototypal inheritance.js b/Chapter04/4.04 - Prototypal inheritance.js
new file mode 100644
index 0000000..0eb4e4f
--- /dev/null
+++ b/Chapter04/4.04 - Prototypal inheritance.js
@@ -0,0 +1,34 @@
+/* Person Prototype Object. */
+
+var Person = {
+ name: 'default name',
+ getName: function() {
+ return this.name;
+ }
+};
+
+var reader = clone(Person);
+alert(reader.getName()); // This will output 'default name'.
+reader.name = 'John Smith';
+alert(reader.getName()); // This will now output 'John Smith'.
+
+/* Author Prototype Object. */
+
+var Author = clone(Person);
+Author.books = []; // Default value.
+Author.getBooks = function() {
+ return this.books;
+}
+
+var author = [];
+
+author[0] = clone(Author);
+author[0].name = 'Dustin Diaz';
+author[0].books = ['JavaScript Design Patterns'];
+
+author[1] = clone(Author);
+author[1].name = 'Ross Harmes';
+author[1].books = ['JavaScript Design Patterns'];
+
+author[1].getName();
+author[1].getBooks();
diff --git a/Chapter04/4.05 - Asymmetrical reading and writing.js b/Chapter04/4.05 - Asymmetrical reading and writing.js
new file mode 100644
index 0000000..11a95dd
--- /dev/null
+++ b/Chapter04/4.05 - Asymmetrical reading and writing.js
@@ -0,0 +1,54 @@
+var authorClone = clone(Author);
+alert(authorClone.name); // Linked to the primative Person.name, which is the
+ // string 'default name'.
+authorClone.name = 'new name'; // A new primative is created and added to the
+ // authorClone object itself.
+alert(authorClone.name); // Now linked to the primative authorClone.name, which
+ // is the string 'new name'.
+
+authorClone.books.push('new book'); // authorClone.books is linked to the array
+ // Author.books. We just modified the
+ // prototype object's default value, and all
+ // other objects that link to it will now
+ // have a new default value there.
+authorClone.books = []; // A new array is created and added to the authorClone
+ // object itself.
+authorClone.books.push('new book'); // We are now modifying that new array.
+
+var CompoundObject = {
+ string1: 'default value',
+ childObject: {
+ bool: true,
+ num: 10
+ }
+}
+
+var compoundObjectClone = clone(CompoundObject);
+
+// Bad! Changes the value of CompoundObject.childObject.num.
+compoundObjectClone.childObject.num = 5;
+
+// Better. Creates a new object, but compoundObject must know the structure
+// of that object, and the defaults. This makes CompoundObject and
+// compoundObjectClone tightly coupled.
+compoundObjectClone.childObject = {
+ bool: true,
+ num: 5
+};
+
+// Best approach. Uses a method to create a new object, with the same structure and
+// defaults as the original.
+
+var CompoundObject = {};
+CompoundObject.string1 = 'default value',
+CompoundObject.createChildObject = function() {
+ return {
+ bool: true,
+ num: 10
+ }
+};
+CompoundObject.childObject = CompoundObject.createChildObject();
+
+var compoundObjectClone = clone(CompoundObject);
+compoundObjectClone.childObject = CompoundObject.createChildObject();
+compoundObjectClone.childObject.num = 5;
diff --git a/Chapter04/4.06 - The clone function.js b/Chapter04/4.06 - The clone function.js
new file mode 100644
index 0000000..867c65c
--- /dev/null
+++ b/Chapter04/4.06 - The clone function.js
@@ -0,0 +1,7 @@
+/* Clone function. */
+
+function clone(object) {
+ function F() {}
+ F.prototype = object;
+ return new F;
+}
diff --git a/Chapter04/4.07 - Mixin classes.js b/Chapter04/4.07 - Mixin classes.js
new file mode 100644
index 0000000..35814ac
--- /dev/null
+++ b/Chapter04/4.07 - Mixin classes.js
@@ -0,0 +1,17 @@
+/* Mixin class. */
+
+var Mixin = function() {};
+Mixin.prototype = {
+ serialize: function() {
+ var output = [];
+ for(key in this) {
+ output.push(key + ': ' + this[key]);
+ }
+ return output.join(', ');
+ }
+};
+
+augment(Author, Mixin);
+
+var author = new Author('Ross Harmes', ['JavaScript Design Patterns']);
+var serializedString = author.serialize();
diff --git a/Chapter04/4.08 - The augment function.js b/Chapter04/4.08 - The augment function.js
new file mode 100644
index 0000000..4d5948b
--- /dev/null
+++ b/Chapter04/4.08 - The augment function.js
@@ -0,0 +1,26 @@
+/* Augment function. */
+
+function augment(receivingClass, givingClass) {
+ for(methodName in givingClass.prototype) {
+ if(!receivingClass.prototype[methodName]) {
+ receivingClass.prototype[methodName] = givingClass.prototype[methodName];
+ }
+ }
+}
+
+/* Augment function, improved. */
+
+function augment(receivingClass, givingClass) {
+ if(arguments[2]) { // Only give certain methods.
+ for(var i = 2, len = arguments.length; i < len; i++) {
+ receivingClass.prototype[arguments[i]] = givingClass.prototype[arguments[i]];
+ }
+ }
+ else { // Give all methods.
+ for(methodName in givingClass.prototype) {
+ if(!receivingClass.prototype[methodName]) {
+ receivingClass.prototype[methodName] = givingClass.prototype[methodName];
+ }
+ }
+ }
+}
diff --git a/Chapter04/4.09 - Edit-in-place example, classical.js b/Chapter04/4.09 - Edit-in-place example, classical.js
new file mode 100644
index 0000000..c0c47b1
--- /dev/null
+++ b/Chapter04/4.09 - Edit-in-place example, classical.js
@@ -0,0 +1,134 @@
+/* EditInPlaceField class. */
+
+function EditInPlaceField(id, parent, value) {
+ this.id = id;
+ this.value = value || 'default value';
+ this.parentElement = parent;
+
+ this.createElements(this.id);
+ this.attachEvents();
+};
+
+EditInPlaceField.prototype = {
+ createElements: function(id) {
+ this.containerElement = document.createElement('div');
+ this.parentElement.appendChild(this.containerElement);
+
+ this.staticElement = document.createElement('span');
+ this.containerElement.appendChild(this.staticElement);
+ this.staticElement.innerHTML = this.value;
+
+ this.fieldElement = document.createElement('input');
+ this.fieldElement.type = 'text';
+ this.fieldElement.value = this.value;
+ this.containerElement.appendChild(this.fieldElement);
+
+ this.saveButton = document.createElement('input');
+ this.saveButton.type = 'button';
+ this.saveButton.value = 'Save';
+ this.containerElement.appendChild(this.saveButton);
+
+ this.cancelButton = document.createElement('input');
+ this.cancelButton.type = 'button';
+ this.cancelButton.value = 'Cancel';
+ this.containerElement.appendChild(this.cancelButton);
+
+ this.convertToText();
+ },
+ attachEvents: function() {
+ var that = this;
+ addEvent(this.staticElement, 'click', function() { that.convertToEditable(); });
+ addEvent(this.saveButton, 'click', function() { that.save(); });
+ addEvent(this.cancelButton, 'click', function() { that.cancel(); });
+ },
+
+ convertToEditable: function() {
+ this.staticElement.style.display = 'none';
+ this.fieldElement.style.display = 'inline';
+ this.saveButton.style.display = 'inline';
+ this.cancelButton.style.display = 'inline';
+
+ this.setValue(this.value);
+ },
+ save: function() {
+ this.value = this.getValue();
+ var that = this;
+ var callback = {
+ success: function() { that.convertToText(); },
+ failure: function() { alert('Error saving value.'); }
+ };
+ ajaxRequest('GET', 'save.php?id=' + this.id + '&value=' + this.value, callback);
+ },
+ cancel: function() {
+ this.convertToText();
+ },
+ convertToText: function() {
+ this.fieldElement.style.display = 'none';
+ this.saveButton.style.display = 'none';
+ this.cancelButton.style.display = 'none';
+ this.staticElement.style.display = 'inline';
+
+ this.setValue(this.value);
+ },
+
+ setValue: function(value) {
+ this.fieldElement.value = value;
+ this.staticElement.innerHTML = value;
+ },
+ getValue: function() {
+ return this.fieldElement.value;
+ }
+};
+To create a field, instantiate the class:
+var titleClassical = new EditInPlaceField('titleClassical', $('doc'), 'Title Here');
+var currentTitleText = titleClassical.getValue();
+
+/* EditInPlaceArea class. */
+
+function EditInPlaceArea(id, parent, value) {
+ EditInPlaceArea.superclass.constructor.call(this, id, parent, value);
+};
+extend(EditInPlaceArea, EditInPlaceField);
+
+// Override certain methods.
+
+EditInPlaceArea.prototype.createElements = function(id) {
+ this.containerElement = document.createElement('div');
+ this.parentElement.appendChild(this.containerElement);
+
+ this.staticElement = document.createElement('p');
+ this.containerElement.appendChild(this.staticElement);
+ this.staticElement.innerHTML = this.value;
+
+ this.fieldElement = document.createElement('textarea');
+ this.fieldElement.value = this.value;
+ this.containerElement.appendChild(this.fieldElement);
+
+ this.saveButton = document.createElement('input');
+ this.saveButton.type = 'button';
+ this.saveButton.value = 'Save';
+ this.containerElement.appendChild(this.saveButton);
+
+ this.cancelButton = document.createElement('input');
+ this.cancelButton.type = 'button';
+ this.cancelButton.value = 'Cancel';
+ this.containerElement.appendChild(this.cancelButton);
+
+ this.convertToText();
+};
+EditInPlaceArea.prototype.convertToEditable = function() {
+ this.staticElement.style.display = 'none';
+ this.fieldElement.style.display = 'block';
+ this.saveButton.style.display = 'inline';
+ this.cancelButton.style.display = 'inline';
+
+ this.setValue(this.value);
+};
+EditInPlaceArea.prototype.convertToText = function() {
+ this.fieldElement.style.display = 'none';
+ this.saveButton.style.display = 'none';
+ this.cancelButton.style.display = 'none';
+ this.staticElement.style.display = 'block';
+
+ this.setValue(this.value);
+};
diff --git a/Chapter04/4.10 - Edit-in-place example, prototypal.js b/Chapter04/4.10 - Edit-in-place example, prototypal.js
new file mode 100644
index 0000000..f4bd2c8
--- /dev/null
+++ b/Chapter04/4.10 - Edit-in-place example, prototypal.js
@@ -0,0 +1,131 @@
+/* EditInPlaceField object. */
+
+var EditInPlaceField = {
+ configure: function(id, parent, value) {
+ this.id = id;
+ this.value = value || 'default value';
+ this.parentElement = parent;
+
+ this.createElements(this.id);
+ this.attachEvents();
+ },
+ createElements: function(id) {
+ this.containerElement = document.createElement('div');
+ this.parentElement.appendChild(this.containerElement);
+
+ this.staticElement = document.createElement('span');
+ this.containerElement.appendChild(this.staticElement);
+ this.staticElement.innerHTML = this.value;
+
+ this.fieldElement = document.createElement('input');
+ this.fieldElement.type = 'text';
+ this.fieldElement.value = this.value;
+ this.containerElement.appendChild(this.fieldElement);
+
+ this.saveButton = document.createElement('input');
+ this.saveButton.type = 'button';
+ this.saveButton.value = 'Save';
+ this.containerElement.appendChild(this.saveButton);
+
+ this.cancelButton = document.createElement('input');
+ this.cancelButton.type = 'button';
+ this.cancelButton.value = 'Cancel';
+ this.containerElement.appendChild(this.cancelButton);
+
+ this.convertToText();
+ },
+ attachEvents: function() {
+ var that = this;
+ addEvent(this.staticElement, 'click', function() { that.convertToEditable(); });
+ addEvent(this.saveButton, 'click', function() { that.save(); });
+ addEvent(this.cancelButton, 'click', function() { that.cancel(); });
+ },
+
+ convertToEditable: function() {
+ this.staticElement.style.display = 'none';
+ this.fieldElement.style.display = 'inline';
+ this.saveButton.style.display = 'inline';
+ this.cancelButton.style.display = 'inline';
+
+ this.setValue(this.value);
+ },
+ save: function() {
+ this.value = this.getValue();
+ var that = this;
+ var callback = {
+ success: function() { that.convertToText(); },
+ failure: function() { alert('Error saving value.'); }
+ };
+ ajaxRequest('GET', 'save.php?id=' + this.id + '&value=' + this.value, callback);
+ },
+ cancel: function() {
+ this.convertToText();
+ },
+ convertToText: function() {
+ this.fieldElement.style.display = 'none';
+ this.saveButton.style.display = 'none';
+ this.cancelButton.style.display = 'none';
+ this.staticElement.style.display = 'inline';
+
+ this.setValue(this.value);
+ },
+
+ setValue: function(value) {
+ this.fieldElement.value = value;
+ this.staticElement.innerHTML = value;
+ },
+ getValue: function() {
+ return this.fieldElement.value;
+ }
+};
+
+var titlePrototypal = clone(EditInPlaceField);
+titlePrototypal.configure(' titlePrototypal ', $('doc'), 'Title Here');
+var currentTitleText = titlePrototypal.getValue();
+
+/* EditInPlaceArea object. */
+
+var EditInPlaceArea = clone(EditInPlaceField);
+
+// Override certain methods.
+
+EditInPlaceArea.createElements = function(id) {
+ this.containerElement = document.createElement('div');
+ this.parentElement.appendChild(this.containerElement);
+
+ this.staticElement = document.createElement('p');
+ this.containerElement.appendChild(this.staticElement);
+ this.staticElement.innerHTML = this.value;
+
+ this.fieldElement = document.createElement('textarea');
+ this.fieldElement.value = this.value;
+ this.containerElement.appendChild(this.fieldElement);
+
+ this.saveButton = document.createElement('input');
+ this.saveButton.type = 'button';
+ this.saveButton.value = 'Save';
+ this.containerElement.appendChild(this.saveButton);
+
+ this.cancelButton = document.createElement('input');
+ this.cancelButton.type = 'button';
+ this.cancelButton.value = 'Cancel';
+ this.containerElement.appendChild(this.cancelButton);
+
+ this.convertToText();
+};
+EditInPlaceArea.convertToEditable = function() {
+ this.staticElement.style.display = 'none';
+ this.fieldElement.style.display = 'block';
+ this.saveButton.style.display = 'inline';
+ this.cancelButton.style.display = 'inline';
+
+ this.setValue(this.value);
+};
+EditInPlaceArea.convertToText = function() {
+ this.fieldElement.style.display = 'none';
+ this.saveButton.style.display = 'none';
+ this.cancelButton.style.display = 'none';
+ this.staticElement.style.display = 'block';
+
+ this.setValue(this.value);
+};
diff --git a/Chapter04/4.11 - Edit-in-place example, mixin.js b/Chapter04/4.11 - Edit-in-place example, mixin.js
new file mode 100644
index 0000000..2b5642f
--- /dev/null
+++ b/Chapter04/4.11 - Edit-in-place example, mixin.js
@@ -0,0 +1,141 @@
+/* Mixin class for the edit-in-place methods. */
+
+var EditInPlaceMixin = function() {};
+EditInPlaceMixin.prototype = {
+ createElements: function(id) {
+ this.containerElement = document.createElement('div');
+ this.parentElement.appendChild(this.containerElement);
+
+ this.staticElement = document.createElement('span');
+ this.containerElement.appendChild(this.staticElement);
+ this.staticElement.innerHTML = this.value;
+
+ this.fieldElement = document.createElement('input');
+ this.fieldElement.type = 'text';
+ this.fieldElement.value = this.value;
+ this.containerElement.appendChild(this.fieldElement);
+
+ this.saveButton = document.createElement('input');
+ this.saveButton.type = 'button';
+ this.saveButton.value = 'Save';
+ this.containerElement.appendChild(this.saveButton);
+
+ this.cancelButton = document.createElement('input');
+ this.cancelButton.type = 'button';
+ this.cancelButton.value = 'Cancel';
+ this.containerElement.appendChild(this.cancelButton);
+
+ this.convertToText();
+ },
+ attachEvents: function() {
+ var that = this;
+ addEvent(this.staticElement, 'click', function() { that.convertToEditable(); });
+ addEvent(this.saveButton, 'click', function() { that.save(); });
+ addEvent(this.cancelButton, 'click', function() { that.cancel(); });
+ },
+
+ convertToEditable: function() {
+ this.staticElement.style.display = 'none';
+ this.fieldElement.style.display = 'inline';
+ this.saveButton.style.display = 'inline';
+ this.cancelButton.style.display = 'inline';
+
+ this.setValue(this.value);
+ },
+ save: function() {
+ this.value = this.getValue();
+ var that = this;
+ var callback = {
+ success: function() { that.convertToText(); },
+ failure: function() { alert('Error saving value.'); }
+ };
+ ajaxRequest('GET', 'save.php?id=' + this.id + '&value=' + this.value, callback);
+ },
+ cancel: function() {
+ this.convertToText();
+ },
+ convertToText: function() {
+ this.fieldElement.style.display = 'none';
+ this.saveButton.style.display = 'none';
+ this.cancelButton.style.display = 'none';
+ this.staticElement.style.display = 'inline';
+
+ this.setValue(this.value);
+ },
+
+ setValue: function(value) {
+ this.fieldElement.value = value;
+ this.staticElement.innerHTML = value;
+ },
+ getValue: function() {
+ return this.fieldElement.value;
+ }
+};
+
+/* EditInPlaceField class. */
+
+function EditInPlaceField(id, parent, value) {
+ this.id = id;
+ this.value = value || 'default value';
+ this.parentElement = parent;
+
+ this.createElements(this.id);
+ this.attachEvents();
+};
+augment(EditInPlaceField, EditInPlaceMixin);
+
+/* EditInPlaceArea class. */
+
+function EditInPlaceArea(id, parent, value) {
+ this.id = id;
+ this.value = value || 'default value';
+ this.parentElement = parent;
+
+ this.createElements(this.id);
+ this.attachEvents();
+};
+
+// Add certain methods so that augment won't include them.
+
+EditInPlaceArea.prototype.createElements = function(id) {
+ this.containerElement = document.createElement('div');
+ this.parentElement.appendChild(this.containerElement);
+
+ this.staticElement = document.createElement('p');
+ this.containerElement.appendChild(this.staticElement);
+ this.staticElement.innerHTML = this.value;
+
+ this.fieldElement = document.createElement('textarea');
+ this.fieldElement.value = this.value;
+ this.containerElement.appendChild(this.fieldElement);
+
+ this.saveButton = document.createElement('input');
+ this.saveButton.type = 'button';
+ this.saveButton.value = 'Save';
+ this.containerElement.appendChild(this.saveButton);
+
+ this.cancelButton = document.createElement('input');
+ this.cancelButton.type = 'button';
+ this.cancelButton.value = 'Cancel';
+ this.containerElement.appendChild(this.cancelButton);
+
+ this.convertToText();
+};
+EditInPlaceArea.prototype.convertToEditable = function() {
+ this.staticElement.style.display = 'none';
+ this.fieldElement.style.display = 'block';
+ this.saveButton.style.display = 'inline';
+ this.cancelButton.style.display = 'inline';
+
+ this.setValue(this.value);
+};
+EditInPlaceArea.prototype.convertToText = function() {
+ this.fieldElement.style.display = 'none';
+ this.saveButton.style.display = 'none';
+ this.cancelButton.style.display = 'none';
+ this.staticElement.style.display = 'block';
+
+ this.setValue(this.value);
+};
+
+augment(EditInPlaceArea, EditInPlaceMixin);
diff --git a/Chapter05/5.01 - Basic structure of the singleton.js b/Chapter05/5.01 - Basic structure of the singleton.js
new file mode 100644
index 0000000..99300fc
--- /dev/null
+++ b/Chapter05/5.01 - Basic structure of the singleton.js
@@ -0,0 +1,17 @@
+/* Basic Singleton. */
+
+var Singleton = {
+ attribute1: true,
+ attribute2: 10,
+
+ method1: function() {
+
+ },
+ method2: function(arg) {
+
+ }
+};
+
+Singleton.attribute1 = false;
+var total = Singleton.attribute2 + 5;
+var result = Singleton.method1();
diff --git a/Chapter05/5.02 - Namespacing.js b/Chapter05/5.02 - Namespacing.js
new file mode 100644
index 0000000..9528873
--- /dev/null
+++ b/Chapter05/5.02 - Namespacing.js
@@ -0,0 +1,42 @@
+/* Declared globally. */
+
+function findProduct(id) {
+ ...
+}
+
+...
+
+// Later in your page, another programmer adds...
+var resetProduct = $('reset-product-button');
+var findProduct = $('find-product-button'); // The findProduct function just got
+ // overwritten.
+
+
+/* Using a namespace. */
+
+var MyNamespace = {
+ findProduct: function(id) {
+ ...
+ },
+ // Other methods can go here as well.
+}
+...
+
+// Later in your page, another programmer adds...
+var resetProduct = $('reset-product-button');
+var findProduct = $('find-product-button'); // Nothing was overwritten.
+
+/* GiantCorp namespace. */
+var GiantCorp = {};
+
+GiantCorp.Common = {
+ // A singleton with common methods used by all objects and modules.
+};
+
+GiantCorp.ErrorCodes = {
+ // An object literal used to store data.
+};
+
+GiantCorp.PageHandler = {
+ // A singleton with page specific methods and attributes.
+};
diff --git a/Chapter05/5.03 - Wrappers for page specific code.js b/Chapter05/5.03 - Wrappers for page specific code.js
new file mode 100644
index 0000000..e9141c2
--- /dev/null
+++ b/Chapter05/5.03 - Wrappers for page specific code.js
@@ -0,0 +1,76 @@
+/* Generic Page Object. */
+
+Namespace.PageName = {
+
+ // Page constants.
+ CONSTANT_1: true,
+ CONSTANT_2: 10,
+
+ // Page methods.
+ method1: function() {
+
+ },
+ method2: function() {
+
+ },
+
+ // Initialization method.
+ init: function() {
+
+ }
+}
+
+// Invoke the initialization method after the page loads.
+addLoadEvent(Namespace.PageName.init);
+
+
+var GiantCorp = window.GiantCorp || {};
+
+/* RegPage singleton, page handler object. */
+
+GiantCorp.RegPage = {
+
+ // Constants.
+ FORM_ID: 'reg-form',
+ OUTPUT_ID: 'reg-results',
+
+ // Form handling methods.
+ handleSubmit: function(e) {
+ e.preventDefault(); // Stop the normal form submission.
+
+ var data = {};
+ var inputs = GiantCorp.RegPage.formEl.getElementsByTagName('input');
+
+ // Collect the values of the input fields in the form.
+ for(var i = 0, len = inputs.length; i < len; i++) {
+ data[inputs[i].name] = inputs[i].value;
+ }
+
+ // Send the form values back to the server.
+ GiantCorp.RegPage.sendRegistration(data);
+ },
+ sendRegistration: function(data) {
+ // Make an XHR request and call displayResult() when the response is
+ // received.
+ ...
+ },
+ displayResult: function(response) {
+ // Output the response directly into the output element. We are
+ // assuming the server will send back formatted HTML.
+ GiantCorp.RegPage.outputEl.innerHTML = response;
+ },
+
+ // Initialization method.
+ init: function() {
+ // Get the form and output elements.
+ GiantCorp.RegPage.formEl = $(GiantCorp.RegPage.FORM_ID);
+ GiantCorp.RegPage.outputEl = $(GiantCorp.RegPage.OUTPUT_ID);
+
+ // Hijack the form submission.
+ addEvent(GiantCorp.RegPage.formEl, 'submit', GiantCorp.RegPage.handleSubmit);
+ }
+};
+
+// Invoke the initialization method after the page loads.
+addLoadEvent(GiantCorp.RegPage.init);
+
diff --git a/Chapter05/5.04 - Private methods with underscores.js b/Chapter05/5.04 - Private methods with underscores.js
new file mode 100644
index 0000000..ed64082
--- /dev/null
+++ b/Chapter05/5.04 - Private methods with underscores.js
@@ -0,0 +1,20 @@
+/* DataParser singleton, converts character delimited strings into arrays. */
+
+GiantCorp.DataParser = {
+ // Private methods.
+ _stripWhitespace: function(str) {
+ return str.replace(/\s+/, '');
+ },
+ _stringSplit: function(str, delimiter) {
+ return str.split(delimiter);
+ },
+
+ // Public method.
+ stringToArray: function(str, delimiter, stripWS) {
+ if(stripWS) {
+ str = this._stripWhitespace(str);
+ }
+ var outputArray = this._stringSplit(str, delimiter);
+ return outputArray;
+ }
+};
diff --git a/Chapter05/5.05 - Private methods with closures.js b/Chapter05/5.05 - Private methods with closures.js
new file mode 100644
index 0000000..e62bd29
--- /dev/null
+++ b/Chapter05/5.05 - Private methods with closures.js
@@ -0,0 +1,52 @@
+/* Singleton as an Object Literal. */
+
+MyNamespace.Singleton = {};
+
+/* Singleton with Private Members, step 1. */
+
+MyNamespace.Singleton = (function() {
+ return {};
+})();
+
+/* Singleton with Private Members, step 2. */
+
+MyNamespace.Singleton = (function() {
+ return { // Public members.
+ publicAttribute1: true,
+ publicAttribute2: 10,
+
+ publicMethod1: function() {
+ ...
+ },
+ publicMethod2: function(args) {
+ ...
+ }
+ };
+})();
+
+/* Singleton with Private Members, step 3. */
+
+MyNamespace.Singleton = (function() {
+ // Private members.
+ var privateAttribute1 = false;
+ var privateAttribute2 = [1, 2, 3];
+
+ function privateMethod1() {
+ ...
+ }
+ function privateMethod2(args) {
+ ...
+ }
+
+ return { // Public members.
+ publicAttribute1: true,
+ publicAttribute2: 10,
+
+ publicMethod1: function() {
+ ...
+ },
+ publicMethod2: function(args) {
+ ...
+ }
+ };
+})();
diff --git a/Chapter05/5.06 - Comparing the two techniques.js b/Chapter05/5.06 - Comparing the two techniques.js
new file mode 100644
index 0000000..bd9b901
--- /dev/null
+++ b/Chapter05/5.06 - Comparing the two techniques.js
@@ -0,0 +1,29 @@
+/* DataParser singleton, converts character delimited strings into arrays. */
+/* Now using true private methods. */
+
+GiantCorp.DataParser = (function() {
+ // Private attributes.
+ var whitespaceRegex = /\s+/;
+
+ // Private methods.
+ function stripWhitespace(str) {
+ return str.replace(whitespaceRegex, '');
+ }
+ function stringSplit(str, delimiter) {
+ return str.split(delimiter);
+ }
+
+ // Everything returned in the object literal is public, but can access the
+ // members in the closure created above.
+ return {
+ // Public method.
+ stringToArray: function(str, delimiter, stripWS) {
+ if(stripWS) {
+ str = stripWhitespace(str);
+ }
+ var outputArray = stringSplit(str, delimiter);
+ return outputArray;
+ }
+ };
+})(); // Invoke the function and assign the returned object literal to
+ // GiantCorp.DataParser.
diff --git a/Chapter05/5.07 - Lazy instantiation.js b/Chapter05/5.07 - Lazy instantiation.js
new file mode 100644
index 0000000..a20bbcf
--- /dev/null
+++ b/Chapter05/5.07 - Lazy instantiation.js
@@ -0,0 +1,92 @@
+/* Singleton with Private Members, step 3. */
+
+MyNamespace.Singleton = (function() {
+ // Private members.
+ var privateAttribute1 = false;
+ var privateAttribute2 = [1, 2, 3];
+
+ function privateMethod1() {
+ ...
+ }
+ function privateMethod2(args) {
+ ...
+ }
+
+ return { // Public members.
+ publicAttribute1: true,
+ publicAttribute2: 10,
+
+ publicMethod1: function() {
+ ...
+ },
+ publicMethod2: function(args) {
+ ...
+ }
+ };
+})();
+
+/* General skeleton for a lazy loading singleton, step 1. */
+
+MyNamespace.Singleton = (function() {
+
+ function constructor() { // All of the normal singleton code goes here.
+ // Private members.
+ var privateAttribute1 = false;
+ var privateAttribute2 = [1, 2, 3];
+
+ function privateMethod1() {
+ ...
+ }
+ function privateMethod2(args) {
+ ...
+ }
+
+ return { // Public members.
+ publicAttribute1: true,
+ publicAttribute2: 10,
+
+ publicMethod1: function() {
+ ...
+ },
+ publicMethod2: function(args) {
+ ...
+ }
+ }
+ }
+
+})();
+
+/* General skeleton for a lazy loading singleton, step 2. */
+
+MyNamespace.Singleton = (function() {
+
+ function constructor() { // All of the normal singleton code goes here.
+ ...
+ }
+
+ return {
+ getInstance: function() {
+ // Control code goes here.
+ }
+ }
+})();
+
+/* General skeleton for a lazy loading singleton, step 3. */
+
+MyNamespace.Singleton = (function() {
+
+ var uniqueInstance; // Private attribute that holds the single instance.
+
+ function constructor() { // All of the normal singleton code goes here.
+ ...
+ }
+
+ return {
+ getInstance: function() {
+ if(!uniqueInstance) { // Instantiate only if the instance doesn't exist.
+ uniqueInstance = constructor();
+ }
+ return uniqueInstance;
+ }
+ }
+})();
diff --git a/Chapter05/5.08 - Branching.js b/Chapter05/5.08 - Branching.js
new file mode 100644
index 0000000..e525aa1
--- /dev/null
+++ b/Chapter05/5.08 - Branching.js
@@ -0,0 +1,22 @@
+/* Branching Singleton (skeleton). */
+
+MyNamespace.Singleton = (function() {
+ var objectA = {
+ method1: function() {
+ ...
+ },
+ method2: function() {
+ ...
+ }
+ };
+ var objectB = {
+ method1: function() {
+ ...
+ },
+ method2: function() {
+ ...
+ }
+ };
+
+ return (someCondition) ? objectA : objectB;
+})();
diff --git a/Chapter05/5.09 - Creating XHR objects with branching.js b/Chapter05/5.09 - Creating XHR objects with branching.js
new file mode 100644
index 0000000..3a73153
--- /dev/null
+++ b/Chapter05/5.09 - Creating XHR objects with branching.js
@@ -0,0 +1,67 @@
+/* SimpleXhrFactory singleton, step 1. */
+
+var SimpleXhrFactory = (function() {
+
+ // The three branches.
+ var standard = {
+ createXhrObject: function() {
+ return new XMLHttpRequest();
+ }
+ };
+ var activeXNew = {
+ createXhrObject: function() {
+ return new ActiveXObject('Msxml2.XMLHTTP');
+ }
+ };
+ var activeXOld = {
+ createXhrObject: function() {
+ return new ActiveXObject('Microsoft.XMLHTTP');
+ }
+ };
+
+})();
+
+/* SimpleXhrFactory singleton, step 2. */
+
+var SimpleXhrFactory = (function() {
+
+ // The three branches.
+ var standard = {
+ createXhrObject: function() {
+ return new XMLHttpRequest();
+ }
+ };
+ var activeXNew = {
+ createXhrObject: function() {
+ return new ActiveXObject('Msxml2.XMLHTTP');
+ }
+ };
+ var activeXOld = {
+ createXhrObject: function() {
+ return new ActiveXObject('Microsoft.XMLHTTP');
+ }
+ };
+
+ // To assign the branch, try each method; return whatever doesn't fail.
+ var testObject;
+ try {
+ testObject = standard.createXhrObject();
+ return standard; // Return this if no error was thrown.
+ }
+ catch(e) {
+ try {
+ testObject = activeXNew.createXhrObject();
+ return activeXNew; // Return this if no error was thrown.
+ }
+ catch(e) {
+ try {
+ testObject = activeXOld.createXhrObject();
+ return activeXOld; // Return this if no error was thrown.
+ }
+ catch(e) {
+ throw new Error('No XHR object found in this environment.');
+ }
+ }
+ }
+
+})();
diff --git a/Chapter06/6.01 - Introduction to chaining.js b/Chapter06/6.01 - Introduction to chaining.js
new file mode 100644
index 0000000..aa35cd0
--- /dev/null
+++ b/Chapter06/6.01 - Introduction to chaining.js
@@ -0,0 +1,10 @@
+// Without chaining:
+addEvent($('example'), 'click', function() {
+ setStyle(this, 'color', 'green');
+ show(this);
+});
+
+// With chaining:
+$('example').addEvent('click', function() {
+ $(this).setStyle('color', 'green').show();
+});
diff --git a/Chapter06/6.02 - The structure of the chain.js b/Chapter06/6.02 - The structure of the chain.js
new file mode 100644
index 0000000..5e16bc3
--- /dev/null
+++ b/Chapter06/6.02 - The structure of the chain.js
@@ -0,0 +1,91 @@
+function $() {
+ var elements = [];
+ for (var i = 0, len = arguments.length; i < len; ++i) {
+ var element = arguments[i];
+ if (typeof element == 'string') {
+ element = document.getElementById(element);
+ }
+ if (arguments.length == 1) {
+ return element;
+ }
+ elements.push(element);
+ }
+ return elements;
+}
+
+
+
+(function() {
+ // Use a private class.
+ function _$(els) {
+ this.elements = [];
+ for (var i = 0, len = els.length; i < len; ++i) {
+ var element = els[i];
+ if (typeof element == 'string') {
+ element = document.getElementById(element);
+ }
+ this.elements.push(element);
+ }
+ }
+ // The public interface remains the same.
+ window.$ = function() {
+ return new _$(arguments);
+ };
+})();
+
+
+
+(function() {
+ function _$(els) {
+ // ...
+ }
+ _$.prototype = {
+ each: function(fn) {
+ for ( var i = 0, len = this.elements.length; i < len; ++i ) {
+ fn.call(this, this.elements[i]);
+ }
+ return this;
+ },
+ setStyle: function(prop, val) {
+ this.each(function(el) {
+ el.style[prop] = val;
+ });
+ return this;
+ },
+ show: function() {
+ var that = this;
+ this.each(function(el) {
+ that.setStyle('display', 'block');
+ });
+ return this;
+ },
+ addEvent: function(type, fn) {
+ var add = function(el) {
+ if (window.addEventListener) {
+ el.addEventListener(type, fn, false);
+ }
+ else if (window.attachEvent) {
+ el.attachEvent('on'+type, fn);
+ }
+ };
+ this.each(function(el) {
+ add(el);
+ });
+ return this;
+ }
+ };
+ window.$ = function() {
+ return new _$(arguments);
+ };
+})();
+
+
+/* Usage. */
+
+$(window).addEvent('load', function() {
+ $('test-1', 'test-2').show().
+ setStyle('color', 'red').
+ addEvent('click', function(e) {
+ $(this).setStyle('color', 'green');
+ });
+});
diff --git a/Chapter06/6.03 - Building a chainable JavaScript library.js b/Chapter06/6.03 - Building a chainable JavaScript library.js
new file mode 100644
index 0000000..7c2e2f2
--- /dev/null
+++ b/Chapter06/6.03 - Building a chainable JavaScript library.js
@@ -0,0 +1,95 @@
+// Include syntactic sugar to help the development of our interface.
+Function.prototype.method = function(name, fn) {
+ this.prototype[name] = fn;
+ return this;
+};
+(function() {
+ function _$(els) {
+ // ...
+ }
+ /*
+ Events
+ * addEvent
+ * getEvent
+ */
+ _$.method('addEvent', function(type, fn) {
+ // ...
+ }).method('getEvent', function(e) {
+ // ...
+ }).
+ /*
+ DOM
+ * addClass
+ * removeClass
+ * replaceClass
+ * hasClass
+ * getStyle
+ * setStyle
+ */
+ method('addClass', function(className) {
+ // ...
+ }).method('removeClass', function(className) {
+ // ...
+ }).method('replaceClass', function(oldClass, newClass) {
+ // ...
+ }).method('hasClass', function(className) {
+ // ...
+ }).method('getStyle', function(prop) {
+ // ...
+ }).method('setStyle', function(prop, val) {
+ // ...
+ }).
+ /*
+ AJAX
+ * load. Fetches an HTML fragment from a URL and inserts it into an element.
+ */
+ method('load', function(uri, method) {
+ // ...
+ });
+ window.$ = function() {
+ return new _$(arguments);
+ });
+})();
+
+Function.prototype.method = function(name, fn) {
+ // ...
+};
+(function() {
+ function _$(els) {
+ // ...
+ }
+ _$.method('addEvent', function(type, fn) {
+ // ...
+ })
+ // ...
+
+ window.installHelper = function(scope, interface) {
+ scope[interface] = function() {
+ return new _$(arguments);
+ }
+ };
+})();
+
+
+/* Usage. */
+
+installHelper(window, '$');
+
+$('example').show();
+
+
+/* Another usage example. */
+
+// Define a namespace without overwriting it if it already exists.
+window.com = window.com || {};
+com.example = com.example || {};
+com.example.util = com.example.util || {};
+
+installHelper(com.example.util, 'get');
+
+(function() {
+ var get = com.example.util.get;
+ get('example').addEvent('click', function(e) {
+ get(this).addClass('hello');
+ });
+})();
diff --git a/Chapter06/6.04 - Using callbacks.js b/Chapter06/6.04 - Using callbacks.js
new file mode 100644
index 0000000..d2cd40e
--- /dev/null
+++ b/Chapter06/6.04 - Using callbacks.js
@@ -0,0 +1,40 @@
+// Accessor without function callbacks: returning requested data in accessors.
+window.API = window.API || {};
+API.prototype = function() {
+ var name = 'Hello world';
+ // Privileged mutator method.
+ setName: function(newName) {
+ name = newName;
+ return this;
+ },
+ // Privileged accessor method.
+ getName: function() {
+ return name;
+ }
+}();
+
+// Implementation code.
+var o = new API;
+console.log(o.getName()); // Displays 'Hello world'.
+console.log(o.setName('Meow').getName()); // Displays 'Meow'.
+
+// Accessor with function callbacks.
+window.API2 = window.API2 || {};
+API2.prototype = function() {
+ var name = 'Hello world';
+ // Privileged mutator method.
+ setName: function(newName) {
+ name = newName;
+ return this;
+ },
+ // Privileged accessor method.
+ getName: function(callback) {
+ callback.call(this, name);
+ return this;
+ }
+}();
+
+// Implementation code.
+var o2 = new API2;
+o2.getName(console.log).setName('Meow').getName(console.log);
+// Displays 'Hello world' and then 'Meow'.
diff --git a/Chapter07/7.01 - The simple factory.js b/Chapter07/7.01 - The simple factory.js
new file mode 100644
index 0000000..57ffebc
--- /dev/null
+++ b/Chapter07/7.01 - The simple factory.js
@@ -0,0 +1,122 @@
+/* BicycleShop class. */
+
+var BicycleShop = function() {};
+BicycleShop.prototype = {
+ sellBicycle: function(model) {
+ var bicycle;
+
+ switch(model) {
+ case 'The Speedster':
+ bicycle = new Speedster();
+ break;
+ case 'The Lowrider':
+ bicycle = new Lowrider();
+ break;
+ case 'The Comfort Cruiser':
+ default:
+ bicycle = new ComfortCruiser();
+ }
+ Interface.ensureImplements(bicycle, Bicycle);
+
+ bicycle.assemble();
+ bicycle.wash();
+
+ return bicycle;
+ }
+};
+
+
+/* The Bicycle interface. */
+
+var Bicycle = new Interface('Bicycle', ['assemble', 'wash', 'ride', 'repair']);
+
+/* Speedster class. */
+
+var Speedster = function() { // implements Bicycle
+ ...
+};
+Speedster.prototype = {
+ assemble: function() {
+ ...
+ },
+ wash: function() {
+ ...
+ },
+ ride: function() {
+ ...
+ },
+ repair: function() {
+ ...
+ }
+};
+
+
+/* Usage. */
+
+var californiaCruisers = new BicycleShop();
+var yourNewBike = californiaCruisers.sellBicycle('The Speedster');
+
+
+
+/* BicycleFactory namespace. */
+
+var BicycleFactory = {
+ createBicycle: function(model) {
+ var bicycle;
+
+ switch(model) {
+ case 'The Speedster':
+ bicycle = new Speedster();
+ break;
+ case 'The Lowrider':
+ bicycle = new Lowrider();
+ break;
+ case 'The Comfort Cruiser':
+ default:
+ bicycle = new ComfortCruiser();
+ }
+
+ Interface.ensureImplements(bicycle, Bicycle);
+ return bicycle;
+ }
+};
+
+/* BicycleShop class, improved. */
+
+var BicycleShop = function() {};
+BicycleShop.prototype = {
+ sellBicycle: function(model) {
+ var bicycle = BicycleFactory.createBicycle(model);
+
+ bicycle.assemble();
+ bicycle.wash();
+
+ return bicycle;
+ }
+};
+
+/* BicycleFactory namespace, with more models. */
+
+var BicycleFactory = {
+ createBicycle: function(model) {
+ var bicycle;
+
+ switch(model) {
+ case 'The Speedster':
+ bicycle = new Speedster();
+ break;
+ case 'The Lowrider':
+ bicycle = new Lowrider();
+ break;
+ case 'The Flatlander':
+ bicycle = new Flatlander();
+ break;
+ case 'The Comfort Cruiser':
+ default:
+ bicycle = new ComfortCruiser();
+ }
+
+ Interface.ensureImplements(bicycle, Bicycle);
+ return bicycle;
+ }
+};
diff --git a/Chapter07/7.02 - The factory pattern.js b/Chapter07/7.02 - The factory pattern.js
new file mode 100644
index 0000000..38e9f82
--- /dev/null
+++ b/Chapter07/7.02 - The factory pattern.js
@@ -0,0 +1,77 @@
+/* BicycleShop class (abstract). */
+
+var BicycleShop = function() {};
+BicycleShop.prototype = {
+ sellBicycle: function(model) {
+ var bicycle = this.createBicycle(model);
+
+ bicycle.assemble();
+ bicycle.wash();
+
+ return bicycle;
+ },
+ createBicycle: function(model) {
+ throw new Error('Unsupported operation on an abstract class.');
+ }
+};
+
+/* AcmeBicycleShop class. */
+
+var AcmeBicycleShop = function() {};
+extend(AcmeBicycleShop, BicycleShop);
+AcmeBicycleShop.prototype.createBicycle = function(model) {
+ var bicycle;
+
+ switch(model) {
+ case 'The Speedster':
+ bicycle = new AcmeSpeedster();
+ break;
+ case 'The Lowrider':
+ bicycle = new AcmeLowrider();
+ break;
+ case 'The Flatlander':
+ bicycle = new AcmeFlatlander();
+ break;
+ case 'The Comfort Cruiser':
+ default:
+ bicycle = new AcmeComfortCruiser();
+ }
+
+ Interface.ensureImplements(bicycle, Bicycle);
+ return bicycle;
+};
+
+/* GeneralProductsBicycleShop class. */
+
+var GeneralProductsBicycleShop = function() {};
+extend(GeneralProductsBicycleShop, BicycleShop);
+GeneralProductsBicycleShop.prototype.createBicycle = function(model) {
+ var bicycle;
+
+ switch(model) {
+ case 'The Speedster':
+ bicycle = new GeneralProductsSpeedster();
+ break;
+ case 'The Lowrider':
+ bicycle = new GeneralProductsLowrider();
+ break;
+ case 'The Flatlander':
+ bicycle = new GeneralProductsFlatlander();
+ break;
+ case 'The Comfort Cruiser':
+ default:
+ bicycle = new GeneralProductsComfortCruiser();
+ }
+
+ Interface.ensureImplements(bicycle, Bicycle);
+ return bicycle;
+};
+
+
+/* Usage. */
+
+var alecsCruisers = new AcmeBicycleShop();
+var yourNewBike = alecsCruisers.sellBicycle('The Lowrider');
+
+var bobsCruisers = new GeneralProductsBicycleShop();
+var yourSecondNewBike = bobsCruisers.sellBicycle('The Lowrider');
diff --git a/Chapter07/7.03 - XHR factory example.js b/Chapter07/7.03 - XHR factory example.js
new file mode 100644
index 0000000..a1b1a7b
--- /dev/null
+++ b/Chapter07/7.03 - XHR factory example.js
@@ -0,0 +1,52 @@
+/* AjaxHandler interface. */
+
+var AjaxHandler = new Interface('AjaxHandler', ['request', 'createXhrObject']);
+
+/* SimpleHandler class. */
+
+var SimpleHandler = function() {}; // implements AjaxHandler
+SimpleHandler.prototype = {
+ request: function(method, url, callback, postVars) {
+ var xhr = this.createXhrObject();
+ xhr.onreadystatechange = function() {
+ if(xhr.readyState !== 4) return;
+ (xhr.status === 200) ?
+ callback.success(xhr.responseText, xhr.responseXML) :
+ callback.failure(xhr.status);
+ };
+ xhr.open(method, url, true);
+ if(method !== 'POST') postVars = null;
+ xhr.send(postVars);
+ },
+ createXhrObject: function() { // Factory method.
+ var methods = [
+ function() { return new XMLHttpRequest(); },
+ function() { return new ActiveXObject('Msxml2.XMLHTTP'); },
+ function() { return new ActiveXObject('Microsoft.XMLHTTP'); }
+ ];
+
+ for(var i = 0, len = methods.length; i < len; i++) {
+ try {
+ methods[i]();
+ }
+ catch(e) {
+ continue;
+ }
+ // If we reach this point, method[i] worked.
+ this.createXhrObject = methods[i]; // Memoize the method.
+ return methods[i];
+ }
+
+ // If we reach this point, none of the methods worked.
+ throw new Error('SimpleHandler: Could not create an XHR object.');
+ }
+};
+
+/* Usage. */
+
+var myHandler = new SimpleHandler();
+var callback = {
+ success: function(responseText) { alert('Success: ' + responseText); },
+ failure: function(statusCode) { alert('Failure: ' + statusCode); }
+};
+myHandler.request('GET', 'script.php', callback);
diff --git a/Chapter07/7.04 - Specialized connection objects.js b/Chapter07/7.04 - Specialized connection objects.js
new file mode 100644
index 0000000..2f69700
--- /dev/null
+++ b/Chapter07/7.04 - Specialized connection objects.js
@@ -0,0 +1,76 @@
+/* QueuedHandler class. */
+
+var QueuedHandler = function() { // implements AjaxHandler
+ this.queue = [];
+ this.requestInProgress = false;
+ this.retryDelay = 5; // In seconds.
+};
+extend(QueuedHandler, SimpleHandler);
+QueuedHandler.prototype.request = function(method, url, callback, postVars,
+ override) {
+ if(this.requestInProgress && !override) {
+ this.queue.push({
+ method: method,
+ url: url,
+ callback: callback,
+ postVars: postVars
+ });
+ }
+ else {
+ this.requestInProgress = true;
+ var xhr = this.createXhrObject();
+ var that = this;
+ xhr.onreadystatechange = function() {
+ if(xhr.readyState !== 4) return;
+ if(xhr.status === 200) {
+ callback.success(xhr.responseText, xhr.responseXML);
+ that.advanceQueue();
+ }
+ else {
+ callback.failure(xhr.status);
+ setTimeout(function() { that.request(method, url, callback, postVars); },
+ that.retryDelay * 1000);
+ }
+ };
+ xhr.open(method, url, true);
+ if(method !== 'POST') postVars = null;
+ xhr.send(postVars);
+ }
+};
+QueuedHandler.prototype.advanceQueue = function() {
+ if(this.queue.length === 0) {
+ this.requestInProgress = false;
+ return;
+ }
+ var req = this.queue.shift();
+ this.request(req.method, req.url, req.callback, req.postVars, true);
+};
+
+
+/* OfflineHandler class. */
+
+var OfflineHandler = function() { // implements AjaxHandler
+ this.storedRequests = [];
+};
+extend(OfflineHandler, SimpleHandler);
+OfflineHandler.prototype.request = function(method, url, callback, postVars) {
+ if(XhrManager.isOffline()) { // Store the requests until we are online.
+ this.storedRequests.push({
+ method: method,
+ url: url,
+ callback: callback,
+ postVars: postVars
+ });
+ }
+ else { // Call SimpleHandler's request method if we are online.
+ this.flushStoredRequests();
+ OfflineHandler.superclass.request(method, url, callback, postVars);
+ }
+};
+OfflineHandler.prototype.flushStoredRequests = function() {
+ for(var i = 0, len = storedRequests.length; i < len; i++) {
+ var req = storedRequests[i];
+ OfflineHandler.superclass.request(req.method, req.url, req.callback,
+ req.postVars);
+ }
+};
diff --git a/Chapter07/7.05 - Choosing connection objects at run-time.js b/Chapter07/7.05 - Choosing connection objects at run-time.js
new file mode 100644
index 0000000..031d275
--- /dev/null
+++ b/Chapter07/7.05 - Choosing connection objects at run-time.js
@@ -0,0 +1,36 @@
+/* XhrManager singleton. */
+
+var XhrManager = {
+ createXhrHandler: function() {
+ var xhr;
+ if(this.isOffline()) {
+ xhr = new OfflineHandler();
+ }
+ else if(this.isHighLatency()) {
+ xhr = new QueuedHandler();
+ }
+ else {
+ xhr = new SimpleHandler()
+ }
+
+ Interface.ensureImplements(xhr, AjaxHandler);
+ return xhr
+ },
+ isOffline: function() { // Do a quick request with SimpleHandler and see if
+ ... // it succeeds.
+ },
+ isHighLatency: function() { // Do a series of requests with SimpleHandler and
+ ... // time the responses. Best done once, as a
+ // branching function.
+ }
+};
+
+
+/* Usage. */
+
+var myHandler = XhrManager.createXhrHandler();
+var callback = {
+ success: function(responseText) { alert('Success: ' + responseText); },
+ failure: function(statusCode) { alert('Failure: ' + statusCode); }
+};
+myHandler.request('GET', 'script.php', callback);
diff --git a/Chapter07/7.06 - RSS reader example.js b/Chapter07/7.06 - RSS reader example.js
new file mode 100644
index 0000000..2cd5408
--- /dev/null
+++ b/Chapter07/7.06 - RSS reader example.js
@@ -0,0 +1,92 @@
+/* DisplayModule interface. */
+
+var DisplayModule = new Interface('DisplayModule', ['append', 'remove', 'clear']);
+
+/* ListDisplay class. */
+
+var ListDisplay = function(id, parent) { // implements DisplayModule
+ this.list = document.createElement('ul');
+ this.list.id = id;
+ parent.appendChild(this.list);
+};
+ListDisplay.prototype = {
+ append: function(text) {
+ var newEl = document.createElement('li');
+ this.list.appendChild(newEl);
+ newEl.innerHTML = text;
+ return newEl;
+ },
+ remove: function(el) {
+ this.list.removeChild(el);
+ },
+ clear: function() {
+ this.list.innerHTML = '';
+ }
+};
+
+/* Configuration object. */
+
+var conf = {
+ id: 'cnn-top-stories',
+ feedUrl: 'http://rss.cnn.com/rss/cnn_topstories.rss',
+ updateInterval: 60, // In seconds.
+ parent: $('feed-readers')
+};
+
+/* FeedReader class. */
+
+var FeedReader = function(display, xhrHandler, conf) {
+ this.display = display;
+ this.xhrHandler = xhrHandler;
+ this.conf = conf;
+
+ this.startUpdates();
+};
+FeedReader.prototype = {
+ fetchFeed: function() {
+ var that = this;
+ var callback = {
+ success: function(text, xml) { that.parseFeed(text, xml); },
+ failure: function(status) { that.showError(status); }
+ };
+ this.xhrHandler.request('GET', 'feedProxy.php?feed=' + this.conf.feedUrl,
+ callback);
+ },
+ parseFeed: function(responseText, responseXML) {
+ this.display.clear();
+ var items = responseXML.getElementsByTagName('item');
+ for(var i = 0, len = items.length; i < len; i++) {
+ var title = items[i].getElementsByTagName('title')[0];
+ var link = items[i].getElementsByTagName('link')[0];
+ this.display.append('' +
+ title.firstChild.data + '');
+ }
+ },
+ showError: function(statusCode) {
+ this.display.clear();
+ this.display.append('Error fetching feed.');
+ },
+ stopUpdates: function() {
+ clearInterval(this.interval);
+ },
+ startUpdates: function() {
+ this.fetchFeed();
+ var that = this;
+ this.interval = setInterval(function() { that.fetchFeed(); },
+ this.conf.updateInterval * 1000);
+ }
+};
+
+/* FeedManager namespace. */
+
+var FeedManager = {
+ createFeedReader: function(conf) {
+ var displayModule = new ListDisplay(conf.id + '-display', conf.parent);
+ Interface.ensureImplements(displayModule, DisplayModule);
+
+ var xhrHandler = XhrManager.createXhrHandler();
+ Interface.ensureImplements(xhrHandler, AjaxHandler);
+
+ return new FeedReader(displayModule, xhrHandler, conf);
+ }
+};
diff --git a/Chapter08/8.01 - Event listener example.js b/Chapter08/8.01 - Event listener example.js
new file mode 100644
index 0000000..1345986
--- /dev/null
+++ b/Chapter08/8.01 - Event listener example.js
@@ -0,0 +1,23 @@
+addEvent(element, 'click', getBeerById);
+function getBeerById(e) {
+ var id = this.id;
+ asyncRequest('GET', 'beer.uri?id=' + id, function(resp) {
+ // Callback response.
+ console.log('Requested Beer: ' + resp.responseText);
+ });
+}
+
+function getBeerById(id, callback) {
+ // Make request for beer by ID, then return the beer data.
+ asyncRequest('GET', 'beer.uri?id=' + id, function(resp) {
+ // callback response
+ callback(resp.responseText);
+ });
+}
+
+addEvent(element, 'click', getBeerByIdBridge);
+function getBeerByIdBridge (e) {
+ getBeerById(this.id, function(beer) {
+ console.log('Requested Beer: '+beer);
+ });
+}
diff --git a/Chapter08/8.02 - Other examples of bridges.js b/Chapter08/8.02 - Other examples of bridges.js
new file mode 100644
index 0000000..7cfafe0
--- /dev/null
+++ b/Chapter08/8.02 - Other examples of bridges.js
@@ -0,0 +1,9 @@
+var Public = function() {
+ var secret = 3;
+ this.privilegedGetter = function() {
+ return secret;
+ };
+};
+
+var o = new Public;
+var data = o.privilegedGetter();
diff --git a/Chapter08/8.03 - Bridging multiple classes together.js b/Chapter08/8.03 - Bridging multiple classes together.js
new file mode 100644
index 0000000..1cb288c
--- /dev/null
+++ b/Chapter08/8.03 - Bridging multiple classes together.js
@@ -0,0 +1,13 @@
+var Class1 = function(a, b, c) {
+ this.a = a;
+ this.b = b;
+ this.c = c;
+}
+var Class2 = function(d) {
+ this.d = d;
+};
+
+var BridgeClass = function(a, b, c, d) {
+ this.one = new Class1(a, b, c);
+ this.two = new Class2(d);
+};
diff --git a/Chapter08/8.04 - Building an XHR connection queue.js b/Chapter08/8.04 - Building an XHR connection queue.js
new file mode 100644
index 0000000..4436cc0
--- /dev/null
+++ b/Chapter08/8.04 - Building an XHR connection queue.js
@@ -0,0 +1,225 @@
+var asyncRequest = (function() {
+ function handleReadyState(o, callback) {
+ var poll = window.setInterval(
+ function() {
+ if (o && o.readyState == 4) {
+ window.clearInterval(poll);
+ if (callback) {
+ callback(o);
+ }
+ }
+ },
+ 50
+ );
+ }
+ var getXHR = function() {
+ var http;
+ try {
+ http = new XMLHttpRequest;
+ getXHR = function() {
+ return new XMLHttpRequest;
+ };
+ }
+ catch(e) {
+ var msxml = [
+ 'MSXML2.XMLHTTP.3.0',
+ 'MSXML2.XMLHTTP',
+ 'Microsoft.XMLHTTP'
+ ];
+ for (var I = 0, len = msxml.length; i < len; ++i) {
+ try {
+ http = new ActiveXObject(msxml[i]);
+ getXHR = function() {
+ return new ActiveXObject(msxml[i]);
+ };
+ break;
+ }
+ catch(e) {}
+ }
+ }
+ return http;
+ };
+ return function(method, uri, callback, postData) {
+ var http = getXHR();
+ http.open(method, uri, true);
+ handleReadyState(http, callback);
+ http.send(postData || null);
+ return http;
+ };
+})();
+
+
+Function.prototype.method = function(name, fn) {
+ this.prototype[name] = fn;
+ return this;
+};
+
+
+// From the Mozilla Developer Center website at http://developer.mozilla.org/en/docs/New_in_JavaScript_1.6#Array_extras.
+
+if ( !Array.prototype.forEach ) {
+ Array.method('forEach', function(fn, thisObj) {
+ var scope = thisObj || window;
+ for ( var i = 0, len = this.length; i < len; ++i ) {
+ fn.call(scope, this[i], i, this);
+ }
+ });
+}
+
+if ( !Array.prototype.filter ) {
+ Array.method('filter', function(fn, thisObj) {
+ var scope = thisObj || window;
+ var a = [];
+ for ( var i = 0, len = this.length; i < len; ++i ) {
+ if ( !fn.call(scope, this[i], i, this) ) {
+ continue;
+ }
+ a.push(this[i]);
+ }
+ return a;
+ });
+}
+
+
+window.DED = window.DED || {};
+DED.util = DED.util || {};
+DED.util.Observer = function() {
+ this.fns = [];
+}
+DED.util.Observer.prototype = {
+ subscribe: function(fn) {
+ this.fns.push(fn);
+ },
+ unsubscribe: function(fn) {
+ this.fns = this.fns.filter(
+ function(el) {
+ if ( el !== fn ) {
+ return el;
+ }
+ }
+ );
+ },
+ fire: function(o) {
+ this.fns.forEach(
+ function(el) {
+ el(o);
+ }
+ );
+ }
+};
+
+
+DED.Queue = function() {
+ // Queued requests.
+ this.queue = [];
+
+ // Observable Objects that can notify the client of interesting moments
+ // on each DED.Queue instance.
+ this.onComplete = new DED.util.Observer;
+ this.onFailure = new DED.util.Observer;
+ this.onFlush = new DED.util.Observer;
+
+ // Core properties that set up a frontend queueing system.
+ this.retryCount = 3;
+ this.currentRetry = 0;
+ this.paused = false;
+ this.timeout = 5000;
+ this.conn = {};
+ this.timer = {};
+};
+
+DED.Queue.
+ method('flush', function() {
+ if (!this.queue.length > 0) {
+ return;
+ }
+ if (this.paused) {
+ this.paused = false;
+ return;
+ }
+ var that = this;
+ this.currentRetry++;
+ var abort = function() {
+ that.conn.abort();
+ if (that.currentRetry == that.retryCount) {
+ that.onFailure.fire();
+ that.currentRetry = 0;
+ } else {
+ that.flush();
+ }
+ };
+ this.timer = window.setTimeout(abort, this.timeout);
+ var callback = function(o) {
+ window.clearTimeout(that.timer);
+ that.currentRetry = 0;
+ that.queue.shift();
+ that.onFlush.fire(o.responseText);
+ if (that.queue.length == 0) {
+ that.onComplete.fire();
+ return;
+ }
+ // recursive call to flush
+ that.flush();
+ };
+ this.conn = asyncRequest(
+ this.queue[0]['method'],
+ this.queue[0]['uri'],
+ callback,
+ this.queue[0]['params']
+ );
+ }).
+ method('setRetryCount', function(count) {
+ this.retryCount = count;
+ }).
+ method('setTimeout', function(time) {
+ this.timeout = time;
+ }).
+ method('add', function(o) {
+ this.queue.push(o);
+ }).
+ method('pause', function() {
+ this.paused = true;
+ }).
+ method('dequeue', function() {
+ this.queue.pop();
+ }).
+ method('clear', function() {
+ this.queue = [];
+ });
+
+
+/* Usage. */
+
+var q = new DED.Queue;
+// Reset our retry count to be higher for slow connections.
+q.setRetryCount(5);
+// Decrease timeout limit because we still want fast connections to benefit.
+q.setTimeout(1000);
+// Add two slots.
+q.add({
+ method: 'GET',
+ uri: '/path/to/file.php?ajax=true'
+});
+q.add({
+ method: 'GET',
+ uri: '/path/to/file.php?ajax=true&woe=me'
+});
+// Flush the queue.
+q.flush();
+// Pause the queue, retaining the requests.
+q.pause();
+// Clear our queue and start fresh.
+q.clear();
+// Add two requests.
+q.add({
+ method: 'GET',
+ uri: '/path/to/file.php?ajax=true'
+});
+q.add({
+ method: 'GET',
+ uri: '/path/to/file.php?ajax=true&woe=me'
+});
+// Remove the last request from the queue.
+q.dequeue();
+// Flush the queue again.
+q.flush();
diff --git a/Chapter08/8.05 - XHR connection queue example page.html b/Chapter08/8.05 - XHR connection queue example page.html
new file mode 100644
index 0000000..0bc1766
--- /dev/null
+++ b/Chapter08/8.05 - XHR connection queue example page.html
@@ -0,0 +1,131 @@
+
+
+
+
+ Ajax Connection Queue
+
+
+
+
+
+
+
+
Ajax Connection Queue
+
+
+
Add Requests to Queue
+
+
+
Other Queue Actions
+
+
+
+
+
diff --git a/Chapter08/8.06 - Where have bridges been used_.js b/Chapter08/8.06 - Where have bridges been used_.js
new file mode 100644
index 0000000..af8613e
--- /dev/null
+++ b/Chapter08/8.06 - Where have bridges been used_.js
@@ -0,0 +1,18 @@
+// Original function.
+
+var addRequest = function(request) {
+ var data = request.split('-')[1];
+ // etc...
+};
+
+// Function de-coupled.
+
+var addRequest = function(data) {
+ // etc...
+};
+
+// Bridge
+
+var addRequestFromClick = function(request) {
+ addRequest(request.split(‘-‘)[0]);
+};
diff --git a/Chapter09/9.01 - Form validation.js b/Chapter09/9.01 - Form validation.js
new file mode 100644
index 0000000..bcae735
--- /dev/null
+++ b/Chapter09/9.01 - Form validation.js
@@ -0,0 +1,153 @@
+/* Interfaces. */
+
+var Composite = new Interface('Composite', ['add', 'remove', 'getChild']);
+var FormItem = new Interface('FormItem', ['save']);
+
+/* CompositeForm class. */
+
+var CompositeForm = function(id, method, action) { // implements Composite, FormItem
+ this.formComponents = [];
+
+ this.element = document.createElement('form');
+ this.element.id = id;
+ this.element.method = method || 'POST';
+ this.element.action = action || '#';
+};
+
+CompositeForm.prototype.add = function(child) {
+ Interface.ensureImplements(child, Composite, FormItem);
+ this.formComponents.push(child);
+ this.element.appendChild(child.getElement());
+};
+
+CompositeForm.prototype.remove = function(child) {
+ for(var i = 0, len = this.formComponents.length; i < len; i++) {
+ if(this.formComponents[i] === child) {
+ this.formComponents.splice(i, 1); // Remove one element from the array at
+ // position i.
+ break;
+ }
+ }
+};
+
+CompositeForm.prototype.getChild = function(i) {
+ return this.formComponents[i];
+};
+
+CompositeForm.prototype.save = function() {
+ for(var i = 0, len = this.formComponents.length; i < len; i++) {
+ this.formComponents[i].save();
+ }
+};
+
+CompositeForm.prototype.getElement = function() {
+ return this.element;
+};
+
+/* Field class, abstract. */
+
+var Field = function(id) { // implements Composite, FormItem
+ this.id = id;
+ this.element;
+};
+
+Field.prototype.add = function() {};
+Field.prototype.remove = function() {};
+Field.prototype.getChild = function() {};
+
+Field.prototype.save = function() {
+ setCookie(this.id, this.getValue);
+};
+
+Field.prototype.getElement = function() {
+ return this.element;
+};
+
+Field.prototype.getValue = function() {
+ throw new Error('Unsupported operation on the class Field.');
+};
+
+/* InputField class. */
+
+var InputField = function(id, label) { // implements Composite, FormItem
+ Field.call(this, id);
+
+ this.input = document.createElement('input');
+ this.input.id = id;
+
+ this.label = document.createElement('label');
+ var labelTextNode = document.createTextNode(label);
+ this.label.appendChild(labelTextNode);
+
+ this.element = document.createElement('div');
+ this.element.className = 'input-field';
+ this.element.appendChild(this.label);
+ this.element.appendChild(this.input);
+};
+extend(InputField, Field); // Inherit from Field.
+
+InputField.prototype.getValue = function() {
+ return this.input.value;
+};
+
+/* TextareaField class. */
+
+var TextareaField = function(id, label) { // implements Composite, FormItem
+ Field.call(this, id);
+
+ this.textarea = document.createElement('textarea');
+ this.textarea.id = id;
+
+ this.label = document.createElement('label');
+ var labelTextNode = document.createTextNode(label);
+ this.label.appendChild(labelTextNode);
+
+ this.element = document.createElement('div');
+ this.element.className = 'input-field';
+ this.element.appendChild(this.label);
+ this.element.appendChild(this.textarea);
+};
+extend(TextareaField, Field); // Inherit from Field.
+
+TextareaField.prototype.getValue = function() {
+ return this.textarea.value;
+};
+
+/* SelectField class. */
+
+var SelectField = function(id, label) { // implements Composite, FormItem
+ Field.call(this, id);
+
+ this.select = document.createElement('select');
+ this.select.id = id;
+
+ this.label = document.createElement('label');
+ var labelTextNode = document.createTextNode(label);
+ this.label.appendChild(labelTextNode);
+
+ this.element = document.createElement('div');
+ this.element.className = 'input-field';
+ this.element.appendChild(this.label);
+ this.element.appendChild(this.select);
+};
+extend(SelectField, Field); // Inherit from Field.
+
+SelectField.prototype.getValue = function() {
+ return this.select.options[this.select.selectedIndex].value;
+};
+
+
+/* Usage. */
+
+var contactForm = new CompositeForm('contact-form', 'POST', 'contact.php');
+
+contactForm.add(new InputField('first-name', 'First Name'));
+contactForm.add(new InputField('last-name', 'Last Name'));
+contactForm.add(new InputField('address', 'Address'));
+contactForm.add(new InputField('city', 'City'));
+contactForm.add(new SelectField('state', 'State', stateArray)); // var stateArray =
+ [{'al', 'Alabama'}, ...]
+contactForm.add(new InputField('zip', 'Zip'));
+contactForm.add(new TextareaField('comments', 'Comments'));
+
+addEvent(window, 'unload', contactForm.save);
diff --git a/Chapter09/9.02 - Adding operations to FormItem.js b/Chapter09/9.02 - Adding operations to FormItem.js
new file mode 100644
index 0000000..119c4d6
--- /dev/null
+++ b/Chapter09/9.02 - Adding operations to FormItem.js
@@ -0,0 +1,13 @@
+var FormItem = new Interface('FormItem', ['save', 'restore']);
+
+Field.prototype.restore = function() {
+ this.element.value = getCookie(this.id);
+};
+
+CompositeForm.prototype.restore = function() {
+ for(var i = 0, len = this.formComponents.length; i < len; i++) {
+ this.formComponents[i].restore();
+ }
+};
+
+addEvent(window, 'load', contactForm.restore);
diff --git a/Chapter09/9.03 - Adding classes to the hierarchy.js b/Chapter09/9.03 - Adding classes to the hierarchy.js
new file mode 100644
index 0000000..4f7525f
--- /dev/null
+++ b/Chapter09/9.03 - Adding classes to the hierarchy.js
@@ -0,0 +1,79 @@
+/* CompositeFieldset class. */
+
+var CompositeFieldset = function(id, legendText) { // implements Composite, FormItem
+ this.components = {};
+
+ this.element = document.createElement('fieldset');
+ this.element.id = id;
+
+ if(legendText) { // Create a legend if the optional second
+ // argument is set.
+ this.legend = document.createElement('legend');
+ this.legend.appendChild(document.createTextNode(legendText);
+ this.element.appendChild(this.legend);
+ }
+};
+
+CompositeFieldset.prototype.add = function(child) {
+ Interface.ensureImplements(child, Composite, FormItem);
+ this.components[child.getElement().id] = child;
+ this.element.appendChild(child.getElement());
+};
+
+CompositeFieldset.prototype.remove = function(child) {
+ delete this.components[child.getElement().id];
+};
+
+CompositeFieldset.prototype.getChild = function(id) {
+ if(this.components[id] != undefined) {
+ return this.components[id];
+ }
+ else {
+ return null;
+ }
+};
+
+CompositeFieldset.prototype.save = function() {
+ for(var id in this.components) {
+ if(!this.components.hasOwnProperty(id)) continue;
+ this.components[id].save();
+ }
+};
+
+CompositeFieldset.prototype.restore = function() {
+ for(var id in this.components) {
+ if(!this.components.hasOwnProperty(id)) continue;
+ this.components[id].restore();
+ }
+};
+
+CompositeFieldset.prototype.getElement = function() {
+ return this.element;
+};
+
+
+/* Usage. */
+
+var contactForm = new CompositeForm('contact-form', 'POST', 'contact.php');
+
+var nameFieldset = new CompositeFieldset('name-fieldset');
+nameFieldset.add(new InputField('first-name', 'First Name'));
+nameFieldset.add(new InputField('last-name', 'Last Name'));
+contactForm.add(nameFieldset);
+
+var addressFieldset = new CompositeFieldset('address-fieldset');
+addressFieldset.add(new InputField('address', 'Address'));
+addressFieldset.add(new InputField('city', 'City'));
+addressFieldset.add(new SelectField('state', 'State', stateArray));
+addressFieldset.add(new InputField('zip', 'Zip'));
+contactForm.add(addressFieldset);
+
+contactForm.add(new TextareaField('comments', 'Comments'));
+
+body.appendChild(contactForm.getElement());
+
+addEvent(window, 'unload', contactForm.save);
+addEvent(window, 'load', contactForm.restore);
+
+addEvent('save-button', 'click', nameFieldset.save);
+addEvent('restore-button', 'click', nameFieldset.restore);
diff --git a/Chapter09/9.04 - Image gallery example.js b/Chapter09/9.04 - Image gallery example.js
new file mode 100644
index 0000000..6431154
--- /dev/null
+++ b/Chapter09/9.04 - Image gallery example.js
@@ -0,0 +1,110 @@
+// Interfaces.
+
+var Composite = new Interface('Composite', ['add', 'remove', 'getChild']);
+var GalleryItem = new Interface('GalleryItem', ['hide', 'show']);
+
+
+// DynamicGallery class.
+
+var DynamicGallery = function(id) { // implements Composite, GalleryItem
+ this.children = [];
+
+ this.element = document.createElement('div');
+ this.element.id = id;
+ this.element.className = 'dynamic-gallery';
+}
+
+DynamicGallery.prototype = {
+
+ // Implement the Composite interface.
+
+ add: function(child) {
+ Interface.ensureImplements(child, Composite, GalleryItem);
+ this.children.push(child);
+ this.element.appendChild(child.getElement());
+ },
+ remove: function(child) {
+ for(var node, i = 0; node = this.getChild(i); i++) {
+ if(node == child) {
+ this.formComponents[i].splice(i, 1);
+ break;
+ }
+ }
+ this.element.removeChild(child.getElement());
+ },
+ getChild: function(i) {
+ return this.children[i];
+ },
+
+ // Implement the GalleryItem interface.
+
+ hide: function() {
+ for(var node, i = 0; node = this.getChild(i); i++) {
+ node.hide();
+ }
+ this.element.style.display = 'none';
+ },
+ show: function() {
+ this.element.style.display = 'block';
+ for(var node, i = 0; node = this.getChild(i); i++) {
+ node.show();
+ }
+ },
+
+ // Helper methods.
+
+ getElement: function() {
+ return this.element;
+ }
+};
+
+// GalleryImage class.
+
+var GalleryImage = function(src) { // implements Composite, GalleryItem
+ this.element = document.createElement('img');
+ this.element.className = 'gallery-image';
+ this.element.src = src;
+}
+
+GalleryImage.prototype = {
+
+ // Implement the Composite interface.
+
+ add: function() {}, // This is a leaf node, so we don't
+ remove: function() {}, // implement these methods, we just
+ getChild: function() {}, // define them.
+
+ // Implement the GalleryItem interface.
+
+ hide: function() {
+ this.element.style.display = 'none';
+ },
+ show: function() {
+ this.element.style.display = ''; // Restore the display attribute to its
+ // previous setting.
+ },
+
+ // Helper methods.
+
+ getElement: function() {
+ return this.element;
+ }
+};
+
+// Usage.
+
+var topGallery = new DynamicGallery('top-gallery');
+
+topGallery.add(new GalleryImage('/img/image-1.jpg'));
+topGallery.add(new GalleryImage('/img/image-2.jpg'));
+topGallery.add(new GalleryImage('/img/image-3.jpg'));
+
+var vacationPhotos = new DynamicGallery('vacation-photos');
+
+for(var i = 0; i < 30; i++) {
+ vacationPhotos.add(new GalleryImage('/img/vac/image-' + i + '.jpg'));
+}
+
+topGallery.add(vacationPhotos);
+topGallery.show(); // Show the main gallery,
+vacationPhotos.hide(); // but hide the vacation gallery.
diff --git a/Chapter10/10.01 - Some facades you probably already know about.js b/Chapter10/10.01 - Some facades you probably already know about.js
new file mode 100644
index 0000000..e5f6197
--- /dev/null
+++ b/Chapter10/10.01 - Some facades you probably already know about.js
@@ -0,0 +1,11 @@
+function addEvent(el, type, fn) {
+ if (window.addEventListener) {
+ el.addEventListener(type, fn, false);
+ }
+ else if (window.attachEvent) {
+ el.attachEvent('on' + type, fn);
+ }
+ else {
+ el['on' + type] = fn;
+ }
+}
diff --git a/Chapter10/10.02 - Facades as convenience methods.js b/Chapter10/10.02 - Facades as convenience methods.js
new file mode 100644
index 0000000..fdef3be
--- /dev/null
+++ b/Chapter10/10.02 - Facades as convenience methods.js
@@ -0,0 +1,41 @@
+function a(x) {
+ // do stuff here...
+}
+function b(y) {
+ // do stuff here...
+}
+function ab(x, y) {
+ a(x);
+ b(y);
+}
+
+
+
+var DED = window.DED || {};
+DED.util = {
+ stopPropagation: function(e) {
+ if (ev.stopPropagation) {
+ // W3 interface
+ e.stopPropagation();
+ }
+ else {
+ // IE's interface
+ e.cancelBubble = true;
+ }
+ },
+ preventDefault: function(e) {
+ if (e.preventDefault) {
+ // W3 interface
+ e.preventDefault();
+ }
+ else {
+ // IE's interface
+ e.returnValue = false;
+ }
+ },
+ /* our convenience method */
+ stopEvent: function(e) {
+ DED.util.stopPropagation(e);
+ DED.util.preventDefault(e);
+ }
+};
diff --git a/Chapter10/10.03 - Setting styles on HTML elements.js b/Chapter10/10.03 - Setting styles on HTML elements.js
new file mode 100644
index 0000000..923d3e5
--- /dev/null
+++ b/Chapter10/10.03 - Setting styles on HTML elements.js
@@ -0,0 +1,48 @@
+var element = document.getElementById('content');
+element.style.color = 'red';
+
+element.style.fontSize = '16px';
+
+var element1 = document.getElementById('foo');
+element1.style.color = 'red';
+
+var element2 = document.getElementById('bar');
+element2.style.color = 'red';
+
+var element3 = document.getElementById('baz');
+element3.style.color = 'red';
+
+
+
+
+setStyle(['foo', 'bar', 'baz'], 'color', 'red');
+
+function setStyle(elements, prop, val) {
+ for (var i = 0, len = elements.length-1; I < len; ++i) {
+ document.getElementById(elements[i]).style[prop] = val;
+ }
+}
+
+setStyle(['foo'], 'position', 'absolute');
+setStyle(['foo'], 'top', '50px');
+setStyle(['foo'], 'left', '300px');
+
+setCSS(['foo'], {
+ position: 'absolute',
+ top: '50px',
+ left: '300px'
+});
+
+function setCSS(el, styles) {
+ for ( var prop in styles ) {
+ if (!styles.hasOwnProperty(prop)) continue;
+ setStyle(el, prop, styles[prop]);
+ }
+}
+
+setCSS(['foo', 'bar', 'baz'], {
+ color: 'white',
+ background: 'black',
+ fontSize: '16px',
+ fontFamily: 'georgia, times, serif'
+});
diff --git a/Chapter10/10.04 - Creating an event utility.js b/Chapter10/10.04 - Creating an event utility.js
new file mode 100644
index 0000000..204efc6
--- /dev/null
+++ b/Chapter10/10.04 - Creating an event utility.js
@@ -0,0 +1,35 @@
+DED.util.Event = {
+ getEvent: function(e) {
+ return e || window.event;
+ },
+ getTarget: function(e) {
+ return e.target || e.srcElement;
+ },
+ stopPropagation: function(e) {
+ if (e.stopPropagation) {
+ e.stopPropagation();
+ }
+ else {
+ e.cancelBubble = true;
+ }
+ },
+ preventDefault: function(e) {
+ if (e.preventDefault) {
+ e.preventDefault();
+ }
+ else {
+ e.returnValue = false;
+ }
+ },
+ stopEvent: function(e) {
+ this.stopPropagation(e);
+ this.preventDefault(e);
+ }
+};
+
+addEvent($('example'), 'click', function(e) {
+ // Who clicked me.
+ console.log(DED.util.Event.getTarget(e));
+ // Stop propgating and prevent the default action.
+ DED.util.Event.stopEvent(e);
+});
diff --git a/Chapter11/11.01 - Characteristics of an adapter.js b/Chapter11/11.01 - Characteristics of an adapter.js
new file mode 100644
index 0000000..e3326af
--- /dev/null
+++ b/Chapter11/11.01 - Characteristics of an adapter.js
@@ -0,0 +1,16 @@
+var clientObject = {
+ string1: 'foo',
+ string2: 'bar',
+ string3: 'baz'
+};
+function interfaceMethod(str1, str2, str3) {
+ ...
+}
+
+function clientToInterfaceAdapter(o) {
+ interfaceMethod(o.string1, o.string2, o.string3);
+}
+
+/* Usage. */
+
+clientToInterfaceAdapter(clientObject);
diff --git a/Chapter11/11.02 - Adapting one library to another.js b/Chapter11/11.02 - Adapting one library to another.js
new file mode 100644
index 0000000..5974970
--- /dev/null
+++ b/Chapter11/11.02 - Adapting one library to another.js
@@ -0,0 +1,42 @@
+// Prototype $ function.
+function $() {
+ var elements = new Array();
+ for(var i = 0; i < arguments.length; i++) {
+ var element = arguments[i];
+ if(typeof element == 'string')
+ element = document.getElementById(element);
+ if(arguments.length == 1)
+ return element;
+ elements.push(element);
+ }
+ return elements;
+}
+
+/* YUI get method. */
+YAHOO.util.Dom.get = function(el) {
+ if(YAHOO.lang.isString(el)) {
+ return document.getElementById(el);
+ }
+ if(YAHOO.lang.isArray(el)) {
+ var c = [];
+ for(var i = 0, len = el.length; i < len; ++i) {
+ c[c.length] = Y.Dom.get(el[i]);
+ }
+ return c;
+ }
+ if(el) {
+ return el;
+ }
+ return null;
+};
+
+function PrototypeToYUIAdapter() {
+ return YAHOO.util.Dom.get(arguments);
+}
+function YUIToPrototypeAdapter(el) {
+ return $.apply(window, el);
+}
+
+$ = PrototypeToYUIAdapter;
+or vice-versa, for those who are migrating from YUI to Prototype:
+YAHOO.util.Dom.get = YUIToPrototypeAdapter;
diff --git a/Chapter11/11.03 - Adapting an email API.html b/Chapter11/11.03 - Adapting an email API.html
new file mode 100644
index 0000000..eb7ccab
--- /dev/null
+++ b/Chapter11/11.03 - Adapting an email API.html
@@ -0,0 +1,170 @@
+
+
+
+ Mail API Demonstration
+
+
+
+
+
+
+
+
Email Application Interface
+
+
+
+
+
diff --git a/Chapter11/11.04 - More on adapting an email API.js b/Chapter11/11.04 - More on adapting an email API.js
new file mode 100644
index 0000000..950214e
--- /dev/null
+++ b/Chapter11/11.04 - More on adapting an email API.js
@@ -0,0 +1,30 @@
+var substitutionObject = {
+ name: "world"
+ place: "Google"
+};
+var text = 'Hello {name}, welcome to {place}';
+var replacedText = DED.util.substitute(text, substitutionObject);
+console.log(replacedText);
+// produces "Hello world, welcome to Google"
+
+
+fooMail.getMail(function(text) {
+ $('message-pane').innerHTML = text;
+});
+
+var dedMailtoFooMailAdapter = {};
+dedMailtoFooMailAdapter.getMail = function(id, callback) {
+ dedMail.getMail(id, function(resp) {
+ var resp = eval('('+resp+')');
+ var details = 'From: {from}
';
+ details += 'Sent: {date}
';
+ details += 'Message:
';
+ details += '{message}
';
+ callback(DED.util.substitute(details, resp));
+ });
+};
+// Other methods needed to adapt dedMail to the fooMail interface.
+...
+
+// Assign the adapter to the fooMail variable.
+fooMail = dedMailtoFooMailAdapter;
diff --git a/Chapter12/12.01 - Structure of the decorator.js b/Chapter12/12.01 - Structure of the decorator.js
new file mode 100644
index 0000000..eea26e3
--- /dev/null
+++ b/Chapter12/12.01 - Structure of the decorator.js
@@ -0,0 +1,92 @@
+/* The Bicycle interface. */
+
+var Bicycle = new Interface('Bicycle', ['assemble', 'wash', 'ride', 'repair',
+ 'getPrice']);
+
+/* The AcmeComfortCruiser class. */
+
+var AcmeComfortCruiser = function() { // implements Bicycle
+ ...
+};
+AcmeComfortCruiser.prototype = {
+ assemble: function() {
+ ...
+ },
+ wash: function() {
+ ...
+ },
+ ride: function() {
+ ...
+ },
+ repair: function() {
+ ...
+ },
+ getPrice: function() {
+ return 399.00;
+ }
+};
+
+/* The BicycleDecorator abstract decorator class. */
+
+var BicycleDecorator = function(bicycle) { // implements Bicycle
+ Interface.ensureImplements(bicycle, Bicycle);
+ this.bicycle = bicycle;
+}
+BicycleDecorator.prototype = {
+ assemble: function() {
+ return this.bicycle.assemble();
+ },
+ wash: function() {
+ return this.bicycle.wash();
+ },
+ ride: function() {
+ return this.bicycle.ride();
+ },
+ repair: function() {
+ return this.bicycle.repair();
+ },
+ getPrice: function() {
+ return this.bicycle.getPrice();
+ }
+};
+
+/* HeadlightDecorator class. */
+
+var HeadlightDecorator = function(bicycle) { // implements Bicycle
+ this.superclass.constructor(bicycle); // Call the superclass's constructor.
+}
+extend(HeadlightDecorator, BicycleDecorator); // Extend the superclass.
+HeadlightDecorator.prototype.assemble = function() {
+ return this.bicycle.assemble() + ' Attach headlight to handlebars.';
+};
+HeadlightDecorator.prototype.getPrice = function() {
+ return this.bicycle.getPrice() + 15.00;
+};
+
+
+/* TaillightDecorator class. */
+
+var TaillightDecorator = function(bicycle) { // implements Bicycle
+ this.superclass.constructor(bicycle); // Call the superclass's constructor.
+}
+extend(TaillightDecorator, BicycleDecorator); // Extend the superclass.
+TaillightDecorator.prototype.assemble = function() {
+ return this.bicycle.assemble() + ' Attach taillight to the seat post.';
+};
+TaillightDecorator.prototype.getPrice = function() {
+ return this.bicycle.getPrice() + 9.00;
+};
+
+
+/* Usage. */
+
+var myBicycle = new AcmeComfortCruiser(); // Instantiate the bicycle.
+alert(myBicycle.getPrice()); // Returns 399.00
+
+myBicycle = new TaillightDecorator(myBicycle); // Decorate the bicycle object
+ // with a taillight.
+alert(myBicycle.getPrice()); // Now returns 408.00
+
+myBicycle = new HeadlightDecorator(myBicycle); // Decorate the bicycle object
+ // again, now with a headlight.
+alert(myBicycle.getPrice()); // Now returns 423.00
diff --git a/Chapter12/12.02 - In what ways can a decorator modify its component.js b/Chapter12/12.02 - In what ways can a decorator modify its component.js
new file mode 100644
index 0000000..2b13636
--- /dev/null
+++ b/Chapter12/12.02 - In what ways can a decorator modify its component.js
@@ -0,0 +1,170 @@
+HeadlightDecorator.prototype.getPrice = function() {
+ return this.bicycle.getPrice() + 15.00;
+};
+
+var myBicycle = new AcmeComfortCruiser(); // Instantiate the bicycle.
+alert(myBicycle.getPrice()); // Returns 399.00
+
+myBicycle = new HeadlightDecorator(myBicycle); // Decorate the bicycle object
+ // with the first headlight.
+myBicycle = new HeadlightDecorator(myBicycle); // Decorate the bicycle object
+ // with the second headlight.
+myBicycle = new TaillightDecorator(myBicycle); // Decorate the bicycle object
+ // with a taillight.
+alert(myBicycle.getPrice()); // Now returns 438.00
+
+
+/* FrameColorDecorator class. */
+
+var FrameColorDecorator = function(bicycle, frameColor) { // implements Bicycle
+ this.superclass.constructor(bicycle); // Call the superclass's constructor.
+ this.frameColor = frameColor;
+}
+extend(FrameColorDecorator, BicycleDecorator); // Extend the superclass.
+FrameColorDecorator.prototype.assemble = function() {
+ return 'Paint the frame ' + this.frameColor + ' and allow it to dry. ' +
+ this.bicycle.assemble();
+};
+FrameColorDecorator.prototype.getPrice = function() {
+ return this.bicycle.getPrice() + 30.00;
+};
+
+var myBicycle = new AcmeComfortCruiser(); // Instantiate the bicycle.
+myBicycle = new FrameColorDecorator(myBicycle, 'red'); // Decorate the bicycle
+ // object with the frame color.
+myBicycle = new HeadlightDecorator(myBicycle); // Decorate the bicycle object
+ // with the first headlight.
+myBicycle = new HeadlightDecorator(myBicycle); // Decorate the bicycle object
+ // with the second headlight.
+myBicycle = new TaillightDecorator(myBicycle); // Decorate the bicycle object
+ // with a taillight.
+alert(myBicycle.assemble());
+/* Returns:
+ "Paint the frame red and allow it to dry. (Full instructions for assembling
+ the bike itself go here) Attach headlight to handlebars. Attach headlight
+ to handlebars. Attach taillight to the seat post."
+*/
+
+
+
+
+/* LifetimeWarrantyDecorator class. */
+
+var LifetimeWarrantyDecorator = function(bicycle) { // implements Bicycle
+ this.superclass.constructor(bicycle); // Call the superclass's constructor.
+}
+extend(LifetimeWarrantyDecorator, BicycleDecorator); // Extend the superclass.
+LifetimeWarrantyDecorator.prototype.repair = function() {
+ return 'This bicycle is covered by a lifetime warranty. Please take it to ' +
+ 'an authorized Acme Repair Center.';
+};
+LifetimeWarrantyDecorator.prototype.getPrice = function() {
+ return this.bicycle.getPrice() + 199.00;
+};
+
+/* TimedWarrantyDecorator class. */
+
+var TimedWarrantyDecorator = function(bicycle, coverageLengthInYears) {
+ // implements Bicycle
+ this.superclass.constructor(bicycle); // Call the superclass's constructor.
+ this.coverageLength = coverageLengthInYears;
+ this.expDate = new Date();
+ var coverageLengthInMs = this.coverageLength * 365 * 24 * 60 * 60 * 1000;
+ expDate.setTime(expDate.getTime() + coverageLengthInMs);
+}
+extend(TimedWarrantyDecorator, BicycleDecorator); // Extend the superclass.
+TimedWarrantyDecorator.prototype.repair = function() {
+ var repairInstructions;
+ var currentDate = new Date();
+ if(currentDate < expDate) {
+ repairInstructions = 'This bicycle is currently covered by a warranty. ' +
+ 'Please take it to an authorized Acme Repair Center.';
+ }
+ else {
+ repairInstructions = this.bicycle.repair();
+ }
+ return repairInstructions;
+};
+TimedWarrantyDecorator.prototype.getPrice = function() {
+ return this.bicycle.getPrice() + (40.00 * this.coverageLength);
+};
+
+
+
+/* BellDecorator class. */
+
+var BellDecorator = function(bicycle) { // implements Bicycle
+ this.superclass.constructor(bicycle); // Call the superclass's constrcutor.
+}
+extend(BellDecorator, BicycleDecorator); // Extend the superclass.
+BellDecorator.prototype.assemble = function() {
+ return this.bicycle.assemble() + ' Attach bell to handlebars.';
+};
+BellDecorator.prototype.getPrice = function() {
+ return this.bicycle.getPrice() + 6.00;
+};
+BellDecorator.prototype.ringBell = function() {
+ return 'Bell rung.';
+};
+
+var myBicycle = new AcmeComfortCruiser(); // Instantiate the bicycle.
+myBicycle = new BellDecorator(myBicycle); // Decorate the bicycle object
+ // with a bell.
+alert(myBicycle.ringBell()); // Returns 'Bell rung.'
+
+var myBicycle = new AcmeComfortCruiser(); // Instantiate the bicycle.
+myBicycle = new BellDecorator(myBicycle); // Decorate the bicycle object
+ // with a bell.
+myBicycle = new HeadlightDecorator(myBicycle); // Decorate the bicycle object
+ // with a headlight.
+alert(myBicycle.ringBell()); // Method not found.
+
+
+
+/* The BicycleDecorator abstract decorator class, improved. */
+
+var BicycleDecorator = function(bicycle) { // implements Bicycle
+ this.bicycle = bicycle;
+ this.interface = Bicycle;
+
+ // Loop through all of the attributes of this.bicycle and create pass-through
+ // methods for any methods that aren't currently implemented.
+ outerloop: for(var key in this.bicycle) {
+ // Ensure that the property is a function.
+ if(typeof this.bicycle[key] !== 'function') {
+ continue outerloop;
+ }
+
+ // Ensure that the method isn't in the interface.
+ for(var i = 0, len = this.interface.methods.length; i < len; i++) {
+ if(key === this.interface.methods[i]) {
+ continue outerloop;
+ }
+ }
+
+ // Add the new method.
+ var that = this;
+ (function(methodName) {
+ that[methodName] = function() {
+ return that.bicycle[methodName]();
+ };
+ })(key);
+ }
+}
+BicycleDecorator.prototype = {
+ assemble: function() {
+ return this.bicycle.assemble();
+ },
+ wash: function() {
+ return this.bicycle.wash();
+ },
+ ride: function() {
+ return this.bicycle.ride();
+ },
+ repair: function() {
+ return this.bicycle.repair();
+ },
+ getPrice: function() {
+ return this.bicycle.getPrice();
+ }
+};
diff --git a/Chapter12/12.03 - The role of the factory.js b/Chapter12/12.03 - The role of the factory.js
new file mode 100644
index 0000000..2640fde
--- /dev/null
+++ b/Chapter12/12.03 - The role of the factory.js
@@ -0,0 +1,81 @@
+/* Original AcmeBicycleShop factory class. */
+
+var AcmeBicycleShop = function() {};
+extend(AcmeBicycleShop, BicycleShop);
+AcmeBicycleShop.prototype.createBicycle = function(model) {
+ var bicycle;
+
+ switch(model) {
+ case 'The Speedster':
+ bicycle = new AcmeSpeedster();
+ break;
+ case 'The Lowrider':
+ bicycle = new AcmeLowrider();
+ break;
+ case 'The Flatlander':
+ bicycle = new AcmeFlatlander();
+ break;
+ case 'The Comfort Cruiser':
+ default:
+ bicycle = new AcmeComfortCruiser();
+ }
+
+ Interface.ensureImplements(bicycle, Bicycle);
+ return bicycle;
+};
+
+/* AcmeBicycleShop factory class, with decorators. */
+
+var AcmeBicycleShop = function() {};
+extend(AcmeBicycleShop, BicycleShop);
+AcmeBicycleShop.prototype.createBicycle = function(model, options) {
+ // Instantiate the bicycle object.
+ var bicycle = new AcmeBicycleShop.models[model]();
+
+ // Iterate through the options and instantiate decorators.
+ for(var i = 0, len = options.length; i < len; i++) {
+ var decorator = AcmeBicycleShop.options[options[i].name];
+ if(typeof decorator !== 'function') {
+ throw new Error('Decorator ' + options[i].name + ' not found.');
+ }
+ var argument = options[i].arg;
+ bicycle = new decorator(bicycle, argument);
+ }
+
+ // Check the interface and return the finished object.
+ Interface.ensureImplements(bicycle, Bicycle);
+ return bicycle;
+};
+
+// Model name to class name mapping.
+AcmeBicycleShop.models = {
+ 'The Speedster': AcmeSpeedster,
+ 'The Lowrider': AcmeLowrider,
+ 'The Flatlander': AcmeFlatlander,
+ 'The Comfort Cruiser': AcmeComfortCruiser
+};
+
+// Option name to decorator class name mapping.
+AcmeBicycleShop.options = {
+ 'headlight': HeadlightDecorator,
+ 'taillight': TaillightDecorator,
+ 'bell': BellDecorator,
+ 'basket': BasketDecorator,
+ 'color': FrameColorDecorator,
+ 'lifetime warranty': LifetimeWarrantyDecorator,
+ 'timed warranty': TimedWarrantyDecorator
+};
+
+var myBicycle = new AcmeSpeedster();
+myBicycle = new FrameColorDecorator(myBicycle, 'blue');
+myBicycle = new HeadlightDecorator(myBicycle);
+myBicycle = new TaillightDecorator(myBicycle);
+myBicycle = new TimedWarrantyDecorator(myBicycle, 2);
+
+var alecsCruisers = new AcmeBicycleShop();
+var myBicycle = alecsCruisers.createBicycle('The Speedster', [
+ { name: 'color', arg: 'blue' },
+ { name: 'headlight' },
+ { name: 'taillight' },
+ { name: 'timed warranty', arg: 2 }
+]);
diff --git a/Chapter12/12.04 - Function decorators.js b/Chapter12/12.04 - Function decorators.js
new file mode 100644
index 0000000..e57aa46
--- /dev/null
+++ b/Chapter12/12.04 - Function decorators.js
@@ -0,0 +1,22 @@
+function upperCaseDecorator(func) {
+ return function() {
+ return func.apply(this, arguments).toUpperCase();
+ }
+}
+
+function getDate() {
+ return (new Date()).toString();
+}
+getDateCaps = upperCaseDecorator(getDate);
+
+alert(getDate()); // Returns Wed Sep 26 2007 20:11:02 GMT-0700 (PDT)
+alert(getDateCaps()); // Returns WED SEP 26 2007 20:11:02 GMT-0700 (PDT)
+
+BellDecorator.prototype.ringBellLoudly =
+ upperCaseDecorator(BellDecorator.prototype.ringBell);
+
+var myBicycle = new AcmeComfortCruiser();
+myBicycle = new BellDecorator(myBicycle);
+
+alert(myBicycle.ringBell()); // Returns 'Bell rung.'
+alert(myBicycle.ringBellLoudly()); // Returns 'BELL RUNG.'
diff --git a/Chapter12/12.05 - Method profiler.js b/Chapter12/12.05 - Method profiler.js
new file mode 100644
index 0000000..a7f3cea
--- /dev/null
+++ b/Chapter12/12.05 - Method profiler.js
@@ -0,0 +1,84 @@
+/* ListBuilder class. */
+
+var ListBuilder = function(parent, listLength) {
+ this.parentEl = $(parent);
+ this.listLength = listLength;
+};
+ListBuilder.prototype = {
+ buildList: function() {
+ var list = document.createElement('ol');
+ this.parentEl.appendChild(list);
+
+ for(var i = 0; i < this.listLength; i++) {
+ var item = document.createElement('li');
+ list.appendChild(item);
+ }
+ }
+};
+
+/* SimpleProfiler class. */
+
+var SimpleProfiler = function(component) {
+ this.component = component;
+};
+SimpleProfiler.prototype = {
+ buildList: function() {
+ var startTime = new Date();
+ this.component.buildList();
+ var elapsedTime = (new Date()).getTime() - startTime.getTime();
+ console.log('buildList: ' + elapsedTime + ' ms');
+ }
+};
+
+/* Usage. */
+
+var list = new ListBuilder('list-container', 5000); // Instantiate the object.
+list = new SimpleProfiler(list); // Wrap the object in the decorator.
+list.buildList(); // Creates the list and displays "buildList: 298 ms".
+
+
+
+/* MethodProfiler class. */
+
+var MethodProfiler = function(component) {
+ this.component = component;
+ this.timers = {};
+
+ for(var key in this.component) {
+ // Ensure that the property is a function.
+ if(typeof this.component[key] !== 'function') {
+ continue;
+ }
+
+ // Add the method.
+ var that = this;
+ (function(methodName) {
+ that[methodName] = function() {
+ that.startTimer(methodName);
+ var returnValue = that.component[methodName].apply(that.component,
+ arguments);
+ that.displayTime(methodName, that.getElapsedTime(methodName));
+ return returnValue;
+ };
+ })(key); }
+};
+MethodProfiler.prototype = {
+ startTimer: function(methodName) {
+ this.timers[methodName] = (new Date()).getTime();
+ },
+ getElapsedTime: function(methodName) {
+ return (new Date()).getTime() - this.timers[methodName];
+ },
+ displayTime: function(methodName, time) {
+ console.log(methodName + ': ' + time + ' ms');
+ }
+};
+
+/* Usage. */
+
+var list = new ListBuilder('list-container', 5000);
+list = new MethodProfiler(list);
+list.buildList('ol'); // Displays "buildList: 301 ms".
+list.buildList('ul'); // Displays "buildList: 287 ms".
+list.removeLists('ul'); // Displays "removeLists: 10 ms".
+list.removeLists('ol'); // Displays "removeLists: 12 ms".
diff --git a/Chapter13/13.01 - Car registration example.js b/Chapter13/13.01 - Car registration example.js
new file mode 100644
index 0000000..a1b240e
--- /dev/null
+++ b/Chapter13/13.01 - Car registration example.js
@@ -0,0 +1,109 @@
+/* Car class, un-optimized. */
+
+var Car = function(make, model, year, owner, tag, renewDate) {
+ this.make = make;
+ this.model = model;
+ this.year = year;
+ this.owner = owner;
+ this.tag = tag;
+ this.renewDate = renewDate;
+};
+Car.prototype = {
+ getMake: function() {
+ return this.make;
+ },
+ getModel: function() {
+ return this.model;
+ },
+ getYear: function() {
+ return this.year;
+ },
+
+ transferOwnership: function(newOwner, newTag, newRenewDate) {
+ this.owner = newOwner;
+ this.tag = newTag;
+ this.renewDate = newRenewDate;
+ },
+ renewRegistration: function(newRenewDate) {
+ this.renewDate = newRenewDate;
+ },
+ isRegistrationCurrent: function() {
+ var today = new Date();
+ return today.getTime() < Date.parse(this.renewDate);
+ }
+};
+
+ /* Car class, optimized as a flyweight. */
+
+var Car = function(make, model, year) {
+ this.make = make;
+ this.model = model;
+ this.year = year;
+};
+Car.prototype = {
+ getMake: function() {
+ return this.make;
+ },
+ getModel: function() {
+ return this.model;
+ },
+ getYear: function() {
+ return this.year;
+ }
+};
+
+/* CarFactory singleton. */
+
+var CarFactory = (function() {
+
+ var createdCars = {};
+
+ return {
+ createCar: function(make, model, year) {
+ // Check to see if this particular combination has been created before.
+ if(createdCars[make + '-' + model + '-' + year]) {
+ return createdCars[make + '-' + model + '-' + year];
+ }
+ // Otherwise create a new instance and save it.
+ else {
+ var car = new Car(make, model, year);
+ createdCars[make + '-' + model + '-' + year] = car;
+ return car;
+ }
+ }
+ };
+})();
+
+/* CarRecordManager singleton. */
+
+var CarRecordManager = (function() {
+
+ var carRecordDatabase = {};
+
+ return {
+ // Add a new car record into the city's system.
+ addCarRecord: function(make, model, year, owner, tag, renewDate) {
+ var car = CarFactory.createCar(make, model, year);
+ carRecordDatabase[tag] = {
+ owner: owner,
+ renewDate: renewDate,
+ car: car
+ };
+ },
+
+ // Methods previously contained in the Car class.
+ transferOwnership: function(tag, newOwner, newTag, newRenewDate) {
+ var record = carRecordDatabase[tag];
+ record.owner = newOwner;
+ record.tag = newTag;
+ record.renewDate = newRenewDate;
+ },
+ renewRegistration: function(tag, newRenewDate) {
+ carRecordDatabase[tag].renewDate = newRenewDate;
+ },
+ isRegistrationCurrent: function(tag) {
+ var today = new Date();
+ return today.getTime() < Date.parse(carRecordDatabase[tag].renewDate);
+ }
+ };
+})();
diff --git a/Chapter13/13.02 - Web calendar example.js b/Chapter13/13.02 - Web calendar example.js
new file mode 100644
index 0000000..17e65db
--- /dev/null
+++ b/Chapter13/13.02 - Web calendar example.js
@@ -0,0 +1,103 @@
+/* CalendarItem interface. */
+
+var CalendarItem = new Interface('CalendarItem', ['display']);
+
+/* CalendarYear class, a composite. */
+
+var CalendarYear = function(year, parent) { // implements CalendarItem
+ this.year = year;
+ this.element = document.createElement('div');
+ this.element.style.display = 'none';
+ parent.appendChild(this.element);
+
+ function isLeapYear(y) {
+ return (y > 0) && !(y % 4) && ((y % 100) || !(y % 400));
+ }
+
+ this.months = [];
+ // The number of days in each month.
+ this.numDays = [31, isLeapYear(this.year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30,
+ 31, 30, 31];
+ for(var i = 0, len = 12; i < len; i++) {
+ this.months[i] = new CalendarMonth(i, this.numDays[i], this.element);
+ }
+);
+CalendarYear.prototype = {
+ display: function() {
+ for(var i = 0, len = this.months.length; i < len; i++) {
+ this.months[i].display(); // Pass the call down to the next level.
+ }
+ this.element.style.display = 'block';
+ }
+};
+
+/* CalendarMonth class, a composite. */
+
+var CalendarMonth = function(monthNum, numDays, parent) { // implements CalendarItem
+ this.monthNum = monthNum;
+ this.element = document.createElement('div');
+ this.element.style.display = 'none';
+ parent.appendChild(this.element);
+
+ this.days = [];
+ for(var i = 0, len = numDays; i < len; i++) {
+ this.days[i] = new CalendarDay(i, this.element);
+ }
+);
+CalendarMonth.prototype = {
+ display: function() {
+ for(var i = 0, len = this.days.length; i < len; i++) {
+ this.days[i].display(); // Pass the call down to the next level.
+ }
+ this.element.style.display = 'block';
+ }
+};
+
+/* CalendarDay class, a leaf (unoptimized). */
+
+var CalendarDay = function(date, parent) { // implements CalendarItem
+ this.date = date;
+ this.element = document.createElement('div');
+ this.element.style.display = 'none';
+ parent.appendChild(this.element);
+};
+CalendarDay.prototype = {
+ display: function() {
+ this.element.style.display = 'block';
+ this.element.innerHTML = this.date;
+ }
+};
+
+
+/* CalendarDay class, a flyweight leaf (optimized). */
+
+var CalendarDay = function() {}; // implements CalendarItem
+CalendarDay.prototype = {
+ display: function(date, parent) {
+ var element = document.createElement('div');
+ parent.appendChild(element);
+ element.innerHTML = date;
+ }
+};
+
+/* CalendarMonth class, a composite (optimized). */
+
+var CalendarMonth = function(monthNum, numDays, parent) { // implements CalendarItem
+ this.monthNum = monthNum;
+ this.element = document.createElement('div');
+ this.element.style.display = 'none';
+ parent.appendChild(this.element);
+
+ this.days = [];
+ for(var i = 0, len = numDays; i < len; i++) {
+ this.days[i] = calendarDay;
+ }
+);
+CalendarMonth.prototype = {
+ display: function() {
+ for(var i = 0, len = this.days.length; i < len; i++) {
+ this.days[i].display(i, this.element);
+ }
+ this.element.style.display = 'block';
+ }
+};
diff --git a/Chapter13/13.03 - Tooltip example.js b/Chapter13/13.03 - Tooltip example.js
new file mode 100644
index 0000000..5cd0c24
--- /dev/null
+++ b/Chapter13/13.03 - Tooltip example.js
@@ -0,0 +1,127 @@
+/* Tooltip class, un-optimized. */
+
+var Tooltip = function(targetElement, text) {
+ this.target = targetElement;
+ this.text = text;
+ this.delayTimeout = null;
+ this.delay = 1500; // in milliseconds.
+
+ // Create the HTML.
+ this.element = document.createElement('div');
+ this.element.style.display = 'none';
+ this.element.style.position = 'absolute';
+ this.element.className = 'tooltip';
+ document.getElementsByTagName('body')[0].appendChild(this.element);
+ this.element.innerHTML = this.text;
+
+ // Attach the events.
+ var that = this; // Correcting the scope.
+ addEvent(this.target, 'mouseover', function(e) { that.startDelay(e); });
+ addEvent(this.target, 'mouseout', function(e) { that.hide(); });
+};
+Tooltip.prototype = {
+ startDelay: function(e) {
+ if(this.delayTimeout == null) {
+ var that = this;
+ var x = e.clientX;
+ var y = e.clientY;
+ this.delayTimeout = setTimeout(function() {
+ that.show(x, y);
+ }, this.delay);
+ }
+ },
+ show: function(x, y) {
+ clearTimeout(this.delayTimeout);
+ this.delayTimeout = null;
+ this.element.style.left = (x) + 'px';
+ this.element.style.top = (y + 20) + 'px';
+ this.element.style.display = 'block';
+ },
+ hide: function() {
+ clearTimeout(this.delayTimeout);
+ this.delayTimeout = null;
+ this.element.style.display = 'none';
+ }
+};
+
+/* Tooltip usage. */
+
+var linkElement = $('link-id');
+var tt = new Tooltip(linkElement, 'Lorem ipsum...');
+
+
+
+/* Tooltip class, as a flyweight. */
+
+var Tooltip = function() {
+ this.delayTimeout = null;
+ this.delay = 1500; // in milliseconds.
+
+ // Create the HTML.
+ this.element = document.createElement('div');
+ this.element.style.display = 'none';
+ this.element.style.position = 'absolute';
+ this.element.className = 'tooltip';
+ document.getElementsByTagName('body')[0].appendChild(this.element);
+};
+Tooltip.prototype = {
+ startDelay: function(e, text) {
+ if(this.delayTimeout == null) {
+ var that = this;
+ var x = e.clientX;
+ var y = e.clientY;
+ this.delayTimeout = setTimeout(function() {
+ that.show(x, y, text);
+ }, this.delay);
+ }
+ },
+ show: function(x, y, text) {
+ clearTimeout(this.delayTimeout);
+ this.delayTimeout = null;
+ this.element.innerHTML = text;
+ this.element.style.left = (x) + 'px';
+ this.element.style.top = (y + 20) + 'px';
+ this.element.style.display = 'block';
+ },
+ hide: function() {
+ clearTimeout(this.delayTimeout);
+ this.delayTimeout = null;
+ this.element.style.display = 'none';
+ }
+};
+
+/* TooltipManager singleton, a flyweight factory and manager. */
+
+var TooltipManager = (function() {
+ var storedInstance = null;
+
+ /* Tooltip class, as a flyweight. */
+
+ var Tooltip = function() {
+ ...
+ };
+ Tooltip.prototype = {
+ ...
+ };
+
+ return {
+ addTooltip: function(targetElement, text) {
+ // Get the tooltip object.
+ var tt = this.getTooltip();
+
+ // Attach the events.
+ addEvent(targetElement, 'mouseover', function(e) { tt.startDelay(e, text); });
+ addEvent(targetElement, 'mouseout', function(e) { tt.hide(); });
+ },
+ getTooltip: function() {
+ if(storedInstance == null) {
+ storedInstance = new Tooltip();
+ }
+ return storedInstance;
+ }
+ };
+})();
+
+/* Tooltip usage. */
+
+TooltipManager.addTooltip($('link-id'), 'Lorem ipsum...');
diff --git a/Chapter13/13.04 - Storing instances for later reuse.js b/Chapter13/13.04 - Storing instances for later reuse.js
new file mode 100644
index 0000000..927007c
--- /dev/null
+++ b/Chapter13/13.04 - Storing instances for later reuse.js
@@ -0,0 +1,49 @@
+/* DisplayModule interface. */
+
+var DisplayModule = new Interface('DisplayModule', ['show', 'hide', 'state']);
+
+/* DialogBox class. */
+
+var DialogBox = function() { // implements DisplayModule
+ ...
+};
+DialogBox.prototype = {
+ show: function(header, body, footer) { // Sets the content and shows the
+ ... // dialog box.
+ },
+ hide: function() { // Hides the dialog box.
+ ...
+ },
+ state: function() { // Returns 'visible' or 'hidden';
+ ...
+ }
+};
+
+/* DialogBoxManager singleton. */
+
+var DialogBoxManager = (function() {
+ var created = []; // Stores created instances.
+
+ return {
+ displayDialogBox: function(header, body, footer) {
+ var inUse = this.numberInUse(); // Find the number currently in use.
+ if(inUse > created.length) {
+ created.push(this.createDialogBox()); // Augment it if need be.
+ }
+ created[inUse].show(header, body, footer); // Show the dialog box.
+ },
+ createDialogBox: function() { // Factory method.
+ var db = new DialogBox();
+ return db;
+ },
+ numberInUse: function() {
+ var inUse = 0;
+ for(var i = 0, len = created.length; i < len; i++) {
+ if(created[i].state() === 'visible') {
+ inUse++;
+ }
+ }
+ return inUse;
+ }
+ };
+})();
diff --git a/Chapter14/14.01 - PublicLibrary class from Chapter 3.js b/Chapter14/14.01 - PublicLibrary class from Chapter 3.js
new file mode 100644
index 0000000..86cdcdf
--- /dev/null
+++ b/Chapter14/14.01 - PublicLibrary class from Chapter 3.js
@@ -0,0 +1,56 @@
+/* From chapter 3. */
+
+var Publication = new Interface('Publication', ['getIsbn', 'setIsbn', 'getTitle',
+ 'setTitle', 'getAuthor', 'setAuthor', 'display']);
+var Book = function(isbn, title, author) { ... } // implements Publication
+
+/* Library interface. */
+
+var Library = new Interface('Library', ['findBooks', 'checkoutBook', 'returnBook']);
+
+/* PublicLibrary class. */
+
+var PublicLibrary = function(books) { // implements Library
+ this.catalog = {};
+ for(var i = 0, len = books.length; i < len; i++) {
+ this.catalog[books[i].getIsbn()] = { book: books[i], available: true };
+ }
+};
+PublicLibrary.prototype = {
+ findBooks: function(searchString) {
+ var results = [];
+ for(var isbn in this.catalog) {
+ if(!this.catalog.hasOwnProperty(isbn)) continue;
+ if(searchString.match(this.catalog[isbn].getTitle()) ||
+ searchString.match(this.catalog[isbn].getAuthor())) {
+ results.push(this.catalog[isbn]);
+ }
+ }
+ return results;
+ },
+ checkoutBook: function(book) {
+ var isbn = book.getIsbn();
+ if(this.catalog[isbn]) {
+ if(this.catalog[isbn].available) {
+ this.catalog[isbn].available = false;
+ return this.catalog[isbn];
+ }
+ else {
+ throw new Error('PublicLibrary: book ' + book.getTitle() +
+ ' is not currently available.');
+ }
+ }
+ else {
+ throw new Error('PublicLibrary: book ' + book.getTitle() + ' not found.');
+ }
+ },
+ returnBook: function(book) {
+ var isbn = book.getIsbn();
+ if(this.catalog[isbn]) {
+ this.catalog[isbn].available = true;
+ }
+ else {
+ throw new Error('PublicLibrary: book ' + book.getTitle() + ' not found.');
+ }
+ }
+};
diff --git a/Chapter14/14.02 - PublicLibraryProxy class.js b/Chapter14/14.02 - PublicLibraryProxy class.js
new file mode 100644
index 0000000..827dabb
--- /dev/null
+++ b/Chapter14/14.02 - PublicLibraryProxy class.js
@@ -0,0 +1,16 @@
+/* PublicLibraryProxy class, a useless proxy. */
+
+var PublicLibraryProxy = function(catalog) { // implements Library
+ this.library = new PublicLibrary(catalog);
+};
+PublicLibraryProxy.prototype = {
+ findBooks: function(searchString) {
+ return this.library.findBooks(searchString);
+ },
+ checkoutBook: function(book) {
+ return this.library.checkoutBook(book);
+ },
+ returnBook: function(book) {
+ return this.library.returnBook(book);
+ }
+};
diff --git a/Chapter14/14.03 - PublicLibraryVirtualProxy class.js b/Chapter14/14.03 - PublicLibraryVirtualProxy class.js
new file mode 100644
index 0000000..03e35d3
--- /dev/null
+++ b/Chapter14/14.03 - PublicLibraryVirtualProxy class.js
@@ -0,0 +1,25 @@
+/* PublicLibraryVirtualProxy class. */
+
+var PublicLibraryVirtualProxy = function(catalog) { // implements Library
+ this.library = null;
+ this.catalog = catalog; // Store the argument to the constructor.
+};
+PublicLibraryVirtualProxy.prototype = {
+ _initializeLibrary: function() {
+ if(this.library === null) {
+ this.library = new PublicLibrary(this.catalog);
+ }
+ },
+ findBooks: function(searchString) {
+ this._initializeLibrary();
+ return this.library.findBooks(searchString);
+ },
+ checkoutBook: function(book) {
+ this._initializeLibrary();
+ return this.library.checkoutBook(book);
+ },
+ returnBook: function(book) {
+ this._initializeLibrary();
+ return this.library.returnBook(book);
+ }
+};
diff --git a/Chapter14/14.04 - Page statistics example.js b/Chapter14/14.04 - Page statistics example.js
new file mode 100644
index 0000000..aaa563f
--- /dev/null
+++ b/Chapter14/14.04 - Page statistics example.js
@@ -0,0 +1,107 @@
+/* Manually making the calls. */
+
+var xhrHandler = XhrManager.createXhrHandler();
+
+/* Get the pageview statistics. */
+
+var callback = {
+ success: function(responseText) {
+ var stats = eval('(' + responseText + ')'); // Parse the JSON data.
+ displayPageviews(stats); // Display the stats on the page.
+ },
+ failure: function(statusCode) {
+ throw new Error('Asynchronous request for stats failed.');
+ }
+};
+xhrHandler.request('GET', '/stats/getPageviews/?page=index.html', callback);
+
+/* Get the browser statistics. */
+
+var callback = {
+ success: function(responseText) {
+ var stats = eval('(' + responseText + ')'); // Parse the JSON data.
+ displayBrowserShare(stats); // Display the stats on the page.
+ },
+ failure: function(statusCode) {
+ throw new Error('Asynchronous request for stats failed.');
+ }
+};
+xhrHandler.request('GET', '/stats/getBrowserShare/?page=index.html', callback);
+
+
+
+/* Using a remote proxy. */
+
+/* PageStats interface. */
+
+var PageStats = new Interface('PageStats', ['getPageviews', 'getUniques',
+ 'getBrowserShare', 'getTopSearchTerms', 'getMostVisitedPages']);
+
+/* StatsProxy singleton. */
+
+var StatsProxy = function() { // implements PageStats
+
+ /* Private attributes. */
+
+ var xhrHandler = XhrManager.createXhrHandler();
+ var urls = {
+ pageviews: '/stats/getPageviews/',
+ uniques: '/stats/getUniques/',
+ browserShare: '/stats/getBrowserShare/',
+ topSearchTerms: '/stats/getTopSearchTerms/',
+ mostVisitedPages: '/stats/getMostVisitedPages/'
+ };
+
+ /* Private methods. */
+
+ function xhrFailure() {
+ throw new Error('StatsProxy: Asynchronous request for stats failed.');
+ }
+
+ function fetchData(url, dataCallback, startDate, endDate, page) {
+ var callback = {
+ success: function(responseText) {
+ var stats = eval('(' + responseText + ')');
+ dataCallback(stats);
+ },
+ failure: xhrFailure
+ };
+
+ var getVars = [];
+ if(startDate != undefined) {
+ getVars.push('startDate=' + encodeURI(startDate));
+ }
+ if(endDate != undefined) {
+ getVars.push('endDate=' + encodeURI(endDate));
+ }
+ if(page != undefined) {
+ getVars.push('page=' + page);
+ }
+
+ if(getVars.length > 0) {
+ url = url + '?' + getVars.join('&');
+ }
+
+ xhrHandler.request('GET', url, callback);
+ }
+
+ /* Public methods. */
+
+ return {
+ getPageviews: function(callback, startDate, endDate, page) {
+ fetchData(urls.pageviews, callback, startDate, endDate, page);
+ },
+ getUniques: function(callback, startDate, endDate, page) {
+ fetchData(urls.uniques, callback, startDate, endDate, page);
+ },
+ getBrowserShare: function(callback, startDate, endDate, page) {
+ fetchData(urls.browserShare, callback, startDate, endDate, page);
+ },
+ getTopSearchTerms: function(callback, startDate, endDate, page) {
+ fetchData(urls.topSearchTerms, callback, startDate, endDate, page);
+ },
+ getMostVisitedPages: function(callback, startDate, endDate) {
+ fetchData(urls.mostVisitedPages, callback, startDate, endDate);
+ }
+ };
+}();
diff --git a/Chapter14/14.05 - General pattern for wrapping a web service.js b/Chapter14/14.05 - General pattern for wrapping a web service.js
new file mode 100644
index 0000000..9d9a7f0
--- /dev/null
+++ b/Chapter14/14.05 - General pattern for wrapping a web service.js
@@ -0,0 +1,77 @@
+/* WebserviceProxy class */
+
+var WebserviceProxy = function() {
+ this.xhrHandler = XhrManager.createXhrHandler();
+};
+WebserviceProxy.prototype = {
+ _xhrFailure: function(statusCode) {
+ throw new Error('StatsProxy: Asynchronous request for stats failed.');
+ },
+ _fetchData: function(url, dataCallback, getVars) {
+ var that = this;
+ var callback = {
+ success: function(responseText) {
+ var obj = eval('(' + responseText + ')');
+ dataCallback(obj);
+ },
+ failure: that._xhrFailure
+ };
+
+ var getVarArray = [];
+ for(varName in getVars) {
+ getVarArray.push(varName + '=' + getVars[varName]);
+ }
+ if(getVarArray.length > 0) {
+ url = url + '?' + getVarArray.join('&');
+ }
+
+ xhrHandler.request('GET', url, callback);
+ }
+};
+
+/* StatsProxy class, using WebserviceProxy. */
+
+var StatsProxy = function() {}; // implements PageStats
+extend(StatsProxy, WebserviceProxy);
+
+/* Implement the needed methods. */
+
+StatsProxy.prototype.getPageviews = function(callback, startDate, endDate,
+ page) {
+ this._fetchData('/stats/getPageviews/', callback, {
+ 'startDate': startDate,
+ 'endDate': endDate,
+ 'page': page
+ });
+};
+StatsProxy.prototype.getUniques = function(callback, startDate, endDate,
+ page) {
+ this._fetchData('/stats/getUniques/', callback, {
+ 'startDate': startDate,
+ 'endDate': endDate,
+ 'page': page
+ });
+};
+StatsProxy.prototype.getBrowserShare = function(callback, startDate, endDate,
+ page) {
+ this._fetchData('/stats/getBrowserShare/', callback, {
+ 'startDate': startDate,
+ 'endDate': endDate,
+ 'page': page
+ });
+};
+StatsProxy.prototype.getTopSearchTerms = function(callback, startDate,
+ endDate, page) {
+ this._fetchData('/stats/getTopSearchTerms/', callback, {
+ 'startDate': startDate,
+ 'endDate': endDate,
+ 'page': page
+ });
+};
+StatsProxy.prototype.getMostVisitedPages = function(callback, startDate,
+ endDate) {
+ this._fetchData('/stats/getMostVisitedPages/', callback, {
+ 'startDate': startDate,
+ 'endDate': endDate
+ });
+};
diff --git a/Chapter14/14.06 - Directory lookup example.js b/Chapter14/14.06 - Directory lookup example.js
new file mode 100644
index 0000000..562b817
--- /dev/null
+++ b/Chapter14/14.06 - Directory lookup example.js
@@ -0,0 +1,109 @@
+/* Directory interface. */
+
+var Directory = new Interface('Directory', ['showPage']);
+
+/* PersonnelDirectory class, the Real Subject */
+
+var PersonnelDirectory = function(parent) { // implements Directory
+ this.xhrHandler = XhrManager.createXhrHandler();
+ this.parent = parent;
+ this.data = null;
+ this.currentPage = null;
+
+ var that = this;
+ var callback = {
+ success: that._configure,
+ failure: function() {
+ throw new Error('PersonnelDirectory: failure in data retrieval.');
+ }
+ }
+ xhrHandler.request('GET', 'directoryData.php', callback);
+};
+PersonnelDirectory.prototype = {
+ _configure: function(responseText) {
+ this.data = eval('(' + reponseText + ')');
+ ...
+ this.currentPage = 'a';
+ },
+ showPage: function(page) {
+ $('page-' + this.currentPage).style.display = 'none';
+ $('page-' + page).style.display = 'block';
+ this.currentPage = page;
+ }
+};
+
+
+/* DirectoryProxy class, just the outline. */
+
+var DirectoryProxy = function(parent) { // implements Directory
+
+};
+DirectoryProxy.prototype = {
+ showPage: function(page) {
+
+ }
+};
+
+/* DirectoryProxy class, as a useless proxy. */
+
+var DirectoryProxy = function(parent) { // implements Directory
+ this.directory = new PersonnelDirectory(parent);
+};
+DirectoryProxy.prototype = {
+ showPage: function(page) {
+ return this.directory.showPage(page);
+ }
+};
+
+/* DirectoryProxy class, as a virtual proxy. */
+
+var DirectoryProxy = function(parent) { // implements Directory
+ this.parent = parent;
+ this.directory = null;
+ var that = this;
+ addEvent(parent, 'mouseover', that._initialize); // Initialization trigger.
+};
+DirectoryProxy.prototype = {
+ _initialize: function() {
+ this.directory = new PersonnelDirectory(this.parent);
+ },
+ showPage: function(page) {
+ return this.directory.showPage(page);
+ }
+};
+
+/* DirectoryProxy class, with loading message. */
+
+var DirectoryProxy = function(parent) { // implements Directory
+ this.parent = parent;
+ this.directory = null;
+ this.warning = null;
+ this.interval = null;
+ this.initialized = false;
+ var that = this;
+ addEvent(parent, 'mouseover', that._initialize); // Initialization trigger.
+};
+DirectoryProxy.prototype = {
+ _initialize: function() {
+ this.warning = document.createElement('div');
+ this.parent.appendChild(this.warning);
+ this.warning.innerHTML = 'The company directory is loading...';
+
+ this.directory = new PersonnelDirectory(this.parent);
+ var that = this;
+ this.interval = setInterval(that._checkInitialization, 100);
+ },
+ _checkInitialization: function() {
+ if(this.directory.currentPage != null) {
+ clearInterval(this.interval);
+ this.initialized = true;
+ this.parent.removeChild(this.warning);
+ }
+ },
+ showPage: function(page) {
+ if(!this.initialized) {
+ return;
+ }
+ return this.directory.showPage(page);
+ }
+};
diff --git a/Chapter14/14.07 - General pattern for creating a virtual proxy.js b/Chapter14/14.07 - General pattern for creating a virtual proxy.js
new file mode 100644
index 0000000..d1558f9
--- /dev/null
+++ b/Chapter14/14.07 - General pattern for creating a virtual proxy.js
@@ -0,0 +1,89 @@
+/* DynamicProxy abstract class, incomplete. */
+
+var DynamicProxy = function() {
+ this.args = arguments;
+ this.initialized = false;
+};
+DynamicProxy.prototype = {
+ _initialize: function() {
+ this.subject = {}; // Instantiate the class.
+ this.class.apply(this.subject, this.args);
+ this.subject.__proto__ = this.class.prototype;
+
+ var that = this;
+ this.interval = setInterval(function() { that._checkInitialization(); }, 100);
+ },
+ _checkInitialization: function() {
+ if(this._isInitialized()) {
+ clearInterval(this.interval);
+ this.initialized = true;
+ }
+ },
+ _isInitialized: function() { // Must be implemented in the subclass.
+ throw new Error('Unsupported operation on an abstract class.');
+ }
+};
+
+/* DynamicProxy abstract class, complete. */
+
+var DynamicProxy = function() {
+ this.args = arguments;
+ this.initialized = false;
+
+ if(typeof this.class != 'function') {
+ throw new Error('DynamicProxy: the class attribute must be set before ' +
+ 'calling the super-class constructor.');
+ }
+
+ // Create the methods needed to implement the same interface.
+ for(var key in this.class.prototype) {
+ // Ensure that the property is a function.
+ if(typeof this.class.prototype[key] !== 'function') {
+ continue;
+ }
+
+ // Add the method.
+ var that = this;
+ (function(methodName) {
+ that[methodName] = function() {
+ if(!that.initialized) {
+ return
+ }
+ return that.subject[methodName].apply(that.subject, arguments);
+ };
+ })(key);
+ }
+};
+DynamicProxy.prototype = {
+ _initialize: function() {
+ this.subject = {}; // Instantiate the class.
+ this.class.apply(this.subject, this.args);
+ this.subject.__proto__ = this.class.prototype;
+
+ var that = this;
+ this.interval = setInterval(function() { that._checkInitialization(); }, 100);
+ },
+ _checkInitialization: function() {
+ if(this._isInitialized()) {
+ clearInterval(this.interval);
+ this.initialized = true;
+ }
+ },
+ _isInitialized: function() { // Must be implemented in the subclass.
+ throw new Error('Unsupported operation on an abstract class.');
+ }
+};
+
+/* TestProxy class. */
+
+var TestProxy = function() {
+ this.class = TestClass;
+ var that = this;
+ addEvent($('test-link'), 'click', function() { that._initialize(); });
+ // Initialization trigger.
+ TestProxy.superclass.constructor.apply(this, arguments);
+};
+extend(TestProxy, DynamicProxy);
+TestProxy.prototype._isInitialized = function() {
+ ... // Initialization condition goes here.
+};
diff --git a/Chapter15/15.01 - Sellsian approach.js b/Chapter15/15.01 - Sellsian approach.js
new file mode 100644
index 0000000..0cdce8f
--- /dev/null
+++ b/Chapter15/15.01 - Sellsian approach.js
@@ -0,0 +1,27 @@
+/* From http://pluralsight.com/blogs/dbox/archive/2007/01/24/45864.aspx */
+
+/*
+ * Publishers are in charge of "publishing" i.e. creating the event.
+ * They're also in charge of "notifying" (firing the event).
+*/
+var Publisher = new Observable;
+
+/*
+ * Subscribers basically... "subscribe" (or listen).
+ * Once they've been "notified" their callback functions are invoked.
+*/
+var Subscriber = function(news) {
+ // news delivered directly to my front porch
+};
+Publisher.subscribeCustomer(Subscriber);
+
+/*
+ * Deliver a paper:
+ * sends out the news to all subscribers.
+*/
+Publisher.deliver('extre, extre, read all about it');
+
+/*
+ * That customer forgot to pay his bill.
+*/
+Publisher.unSubscribeCustomer(Subscriber);
diff --git a/Chapter15/15.02 - Newspapers and subscribers.js b/Chapter15/15.02 - Newspapers and subscribers.js
new file mode 100644
index 0000000..a83f421
--- /dev/null
+++ b/Chapter15/15.02 - Newspapers and subscribers.js
@@ -0,0 +1,60 @@
+/*
+ * Newspaper Vendors
+ * setup as new Publisher objects
+*/
+var NewYorkTimes = new Publisher;
+var AustinHerald = new Publisher;
+var SfChronicle = new Publisher;
+
+
+/*
+ * People who like to read
+ * (Subscribers)
+ *
+ * Each subscriber is set up as a callback method.
+ * They all inherit from the Function prototype Object.
+*/
+var Joe = function(from) {
+ console.log('Delivery from '+from+' to Joe');
+};
+var Lindsay = function(from) {
+ console.log('Delivery from '+from+' to Lindsay');
+};
+var Quadaras = function(from) {
+ console.log('Delivery from '+from+' to Quadaras ');
+};
+
+/*
+ * Here we allow them to subscribe to newspapers
+ * which are the Publisher objects.
+ * In this case Joe subscribes to the NY Times and
+ * the Chronicle. Lindsay subscribes to NY Times
+ * Austin Herald and Chronicle. And the Quadaras
+ * respectfully subscribe to the Herald and the Chronicle
+*/
+Joe.
+ subscribe(NewYorkTimes).
+ subscribe(SfChronicle);
+
+Lindsay.
+ subscribe(AustinHerald).
+ subscribe(SfChronicle).
+ subscribe(NewYorkTimes);
+
+Quadaras.
+ subscribe(AustinHerald).
+ subscribe(SfChronicle);
+
+/*
+ * Then at any given time in our application, our publishers can send
+ * off data for the subscribers to consume and react to.
+*/
+NewYorkTimes.
+ deliver('Here is your paper! Direct from the Big apple');
+AustinHerald.
+ deliver('News').
+ deliver('Reviews').
+ deliver('Coupons');
+SfChronicle.
+ deliver('The weather is still chilly').
+ deliver('Hi Mom! I\'m writing a book');
diff --git a/Chapter15/15.03 - Building an observer API.js b/Chapter15/15.03 - Building an observer API.js
new file mode 100644
index 0000000..8f28415
--- /dev/null
+++ b/Chapter15/15.03 - Building an observer API.js
@@ -0,0 +1,50 @@
+function Publisher() {
+ this.subscribers = [];
+}
+
+Publisher.prototype.deliver = function(data) {
+ this.subscribers.forEach(
+ function(fn) {
+ fn(data);
+ }
+ );
+ return this;
+};
+
+Function.prototype.subscribe = function(publisher) {
+ var that = this;
+ var alreadyExists = publisher.subscribers.some(
+ function(el) {
+ if ( el === that ) {
+ return;
+ }
+ }
+ );
+ if ( !alreadyExists ) {
+ publisher.subscribers.push(this);
+ }
+ return this;
+};
+
+Function.prototype.unsubscribe = function(publisher) {
+ var that = this;
+ publisher.subscribers = publisher.subscribers.filter(
+ function(el) {
+ if ( el !== that ) {
+ return el;
+ }
+ }
+ );
+ return this;
+};
+
+var publisherObject = new Publisher;
+
+var observerObject = function(data) {
+ // process data
+ console.log(data);
+ // unsubscribe from this publisher
+ arguments.callee.unsubscribe(publisherObject);
+};
+
+observerObject.subscribe(publisherObject);
diff --git a/Chapter15/15.04 - Animation example.js b/Chapter15/15.04 - Animation example.js
new file mode 100644
index 0000000..6f40826
--- /dev/null
+++ b/Chapter15/15.04 - Animation example.js
@@ -0,0 +1,35 @@
+// Publisher API
+var Animation = function(o) {
+ this.onStart = new Publisher,
+ this.onComplete = new Publisher,
+ this.onTween = new Publisher;
+};
+Animation.
+ method('fly', function() {
+ // begin animation
+ this.onStart.deliver();
+ for ( ... ) { // loop through frames
+ // deliver frame number
+ this.onTween.deliver(i);
+ }
+ // end animation
+ this.onComplete.deliver();
+ });
+
+// setup an account with the animation manager
+var Superman = new Animation({...config properties...});
+
+// Begin implementing subscribers
+var putOnCape = function(i) { };
+var takeOffCape = function(i) { };
+
+putOnCape.subscribe(Superman.onStart);
+takeOffCape.subscribe(Superman.onComplete);
+
+
+// fly can be called anywhere
+Superman.fly();
+// for instance:
+addEvent(element, 'click', function() {
+ Superman.fly();
+});
diff --git a/Chapter15/15.05 - Event listeners are also observers.js b/Chapter15/15.05 - Event listeners are also observers.js
new file mode 100644
index 0000000..2673e5d
--- /dev/null
+++ b/Chapter15/15.05 - Event listeners are also observers.js
@@ -0,0 +1,25 @@
+// example using listeners
+var element = document.getElementById(‘a’);
+var fn1 = function(e) {
+ // handle click
+};
+var fn2 = function(e) {
+ // do other stuff with click
+};
+
+addEvent(element, ‘click’, fn1);
+addEvent(element, ‘click’, fn2);
+
+
+
+// example using handlers
+var element = document.getElementById(‘b’);
+var fn1 = function(e) {
+ // handle click
+};
+var fn2 = function(e) {
+ // do other stuff with click
+};
+
+element.onclick = fn1;
+element.onclick = fn2;
diff --git a/Chapter16/16.01 - StopAd and StartAd classes.js b/Chapter16/16.01 - StopAd and StartAd classes.js
new file mode 100644
index 0000000..7fb6cd6
--- /dev/null
+++ b/Chapter16/16.01 - StopAd and StartAd classes.js
@@ -0,0 +1,35 @@
+/* AdCommand interface. */
+
+var AdCommand = new Interface('AdCommand', ['execute']);
+
+/* StopAd command class. */
+
+var StopAd = function(adObject) { // implements AdCommand
+ this.ad = adObject;
+};
+StopAd.prototype.execute = function() {
+ this.ad.stop();
+};
+
+/* StartAd command class. */
+
+var StartAd = function(adObject) { // implements AdCommand
+ this.ad = adObject;
+};
+StartAd.prototype.execute = function() {
+ this.ad.start();
+};
+
+
+/* Implementation code. */
+
+var ads = getAds();
+for(var i = 0, len = ads.length; i < len; i++) {
+ // Create command objects for starting and stopping the ad.
+ var startCommand = new StartAd(ads[i]);
+ var stopCommand = new StopAd(ads[i]);
+
+ // Create the UI elements that will execute the command on click.
+ new UiButton('Start ' + ads[i].name, startCommand);
+ new UiButton('Stop ' + ads[i].name, stopCommand);
+}
diff --git a/Chapter16/16.02 - Commands using closures.js b/Chapter16/16.02 - Commands using closures.js
new file mode 100644
index 0000000..8f6d6ec
--- /dev/null
+++ b/Chapter16/16.02 - Commands using closures.js
@@ -0,0 +1,20 @@
+/* Commands using closures. */
+
+function makeStart(adObject) {
+ return function() {
+ adObject.start();
+ };
+}
+function makeStop(adObject) {
+ return function() {
+ adObject.stop();
+ };
+}
+
+/* Implementation code. */
+
+var startCommand = makeStart(ads[i]);
+var stopCommand = makeStop(ads[i]);
+
+startCommand(); // Execute the functions directly instead of calling a method.
+stopCommand();
diff --git a/Chapter16/16.03 - Using interfaces with the command pattern.js b/Chapter16/16.03 - Using interfaces with the command pattern.js
new file mode 100644
index 0000000..81df463
--- /dev/null
+++ b/Chapter16/16.03 - Using interfaces with the command pattern.js
@@ -0,0 +1,19 @@
+/* Command interface. */
+
+var Command = new Interface('Command', ['execute']);
+
+/* Checking the interface of a command object. */
+
+// Ensure that the execute operation is defined. If not, a descriptive exception
+// will be thrown.
+Interface.ensureImplements(someCommand, Command);
+
+// If no exception is thrown, you can safely invoke the execute operation.
+someCommand.execute();
+
+
+/* Checking command functions. */
+
+if(typeof someCommand != 'function') {
+ throw new Error('Command isn't a function');
+}
diff --git a/Chapter16/16.04 - Types of commands.js b/Chapter16/16.04 - Types of commands.js
new file mode 100644
index 0000000..d9801e5
--- /dev/null
+++ b/Chapter16/16.04 - Types of commands.js
@@ -0,0 +1,47 @@
+/* SimpleCommand, a loosely coupled, simple command class. */
+
+var SimpleCommand = function(receiver) { // implements Command
+ this.receiver = receiver;
+};
+SimpleCommand.prototype.execute = function() {
+ this.receiver.action();
+};
+
+/* ComplexCommand, a tightly coupled, complex command class. */
+
+var ComplexCommand = function() { // implements Command
+ this.logger = new Logger();
+ this.xhrHandler = XhrManager.createXhrHandler();
+ this.parameters = {};
+};
+ComplexCommand.prototype = {
+ setParameter: function(key, value) {
+ this.parameters[key] = value;
+ },
+ execute: function() {
+ this.logger.log('Executing command');
+ var postArray = [];
+ for(var key in this.parameters) {
+ postArray.push(key + '=' + this.parameters[key]);
+ }
+ var postString = postArray.join('&');
+ this.xhrHandler.request(
+ 'POST',
+ 'script.php',
+ function() {},
+ postString
+ );
+ }
+};
+
+/* GreyAreaCommand, somewhere between simple and complex. */
+
+var GreyAreaCommand = function(recevier) { // implements Command
+ this.logger = new Logger();
+ this.receiver = receiver;
+};
+GreyAreaCommand.prototype.execute = function() {
+ this.logger.log('Executing command');
+ this.receiver.prepareAction();
+ this.receiver.action();
+};
diff --git a/Chapter16/16.05 - Menu commands.js b/Chapter16/16.05 - Menu commands.js
new file mode 100644
index 0000000..e5ec383
--- /dev/null
+++ b/Chapter16/16.05 - Menu commands.js
@@ -0,0 +1,181 @@
+/* Command, Composite and MenuObject interfaces. */
+
+var Command = new Interface('Command', ['execute']);
+var Composite = new Interface('Composite', ['add', 'remove', 'getChild',
+ 'getElement']);
+var MenuObject = new Interface('MenuObject', ['show']);
+
+/* MenuBar class, a composite. */
+
+var MenuBar = function() { // implements Composite, MenuObject
+ this.menus = {};
+ this.element = document.createElement('ul');
+ this.element.style.display = 'none';
+};
+MenuBar.prototype = {
+ add: function(menuObject) {
+ Interface.ensureImplements(menuObject, Composite, MenuObject);
+ this.menus[menuObject.name] = menuObject;
+ this.element.appendChild(this.menus[menuObject.name].getElement());
+ },
+ remove: function(name) {
+ delete this.menus[name];
+ },
+ getChild: function(name) {
+ return this.menus[name];
+ },
+ getElement: function() {
+ return this.element;
+ },
+
+ show: function() {
+ this.element.style.display = 'block';
+ for(name in this.menus) { // Pass the call down the composite.
+ this.menus[name].show();
+ }
+ }
+};
+
+/* Menu class, a composite. */
+
+var Menu = function(name) { // implements Composite, MenuObject
+ this.name = name;
+ this.items = {};
+ this.element = document.createElement('li');
+ this.element.innerHTML = this.name;
+ this.element.style.display = 'none';
+ this.container = document.createElement('ul');
+ this.element.appendChild(this.container);
+};
+Menu.prototype = {
+ add: function(menuItemObject) {
+ Interface.ensureImplements(menuItemObject, Composite, MenuObject);
+ this.items[menuItemObject.name] = menuItemObject;
+ this.container.appendChild(this.items[menuItemObject.name].getElement());
+ },
+ remove: function(name) {
+ delete this.items[name];
+ },
+ getChild: function(name) {
+ return this.items[name];
+ },
+ getElement: function() {
+ return this.element;
+ },
+
+ show: function() {
+ this.element.style.display = 'block';
+ for(name in this.items) { // Pass the call down the composite.
+ this.items[name].show();
+ }
+ }
+};
+
+/* MenuItem class, a leaf. */
+
+var MenuItem = function(name, command) { // implements Composite, MenuObject
+ Interface.ensureImplements(command, Command);
+ this.name = name;
+ this.element = document.createElement('li');
+ this.element.style.display = 'none';
+ this.anchor = document.createElement('a');
+ this.anchor.href = '#'; // To make it clickable.
+ this.element.appendChild(this.anchor);
+ this.anchor.innerHTML = this.name;
+
+ addEvent(this.anchor, 'click', function(e) { // Invoke the command on click.
+ e.preventDefault();
+ command.execute();
+ });
+};
+MenuItem.prototype = {
+ add: function() {},
+ remove: function() {},
+ getChild: function() {},
+ getElement: function() {
+ return this.element;
+ },
+
+ show: function() {
+ this.element.style.display = 'block';
+ }
+};
+
+
+/* MenuCommand class, a command object. */
+
+var MenuCommand = function(action) { // implements Command
+ this.action = action;
+};
+MenuCommand.prototype.execute = function() {
+ this.action();
+};
+
+
+/* Implementation code. */
+
+/* Receiver objects, instantiated from existing classes. */
+var fileActions = new FileActions();
+var editActions = new EditActions();
+var insertActions = new InsertActions();
+var helpActions = new HelpActions();
+
+/* Create the menu bar. */
+var appMenuBar = new MenuBar();
+
+/* The File menu. */
+var fileMenu = new Menu('File');
+
+var openCommand = new MenuCommand(fileActions.open);
+var closeCommand = new MenuCommand(fileActions.close);
+var saveCommand = new MenuCommand(fileActions.save);
+var saveAsCommand = new MenuCommand(fileActions.saveAs);
+
+fileMenu.add(new MenuItem('Open', openCommand));
+fileMenu.add(new MenuItem('Close', closeCommand));
+fileMenu.add(new MenuItem('Save', saveCommand));
+fileMenu.add(new MenuItem('Save As...', saveAsCommand));
+
+appMenuBar.add(fileMenu);
+
+/* The Edit menu. */
+var editMenu = new Menu('Edit');
+
+var cutCommand = new MenuCommand(editActions.cut);
+var copyCommand = new MenuCommand(editActions.copy);
+var pasteCommand = new MenuCommand(editActions.paste);
+var deleteCommand = new MenuCommand(editActions.delete);
+
+editMenu.add(new MenuItem('Cut', cutCommand));
+editMenu.add(new MenuItem('Copy', copyCommand));
+editMenu.add(new MenuItem('Paste', pasteCommand));
+editMenu.add(new MenuItem('Delete', deleteCommand));
+
+appMenuBar.add(editMenu);
+
+/* The Insert menu. */
+var insertMenu = new Menu('Insert');
+
+var textBlockCommand = new MenuCommand(insertActions.textBlock);
+insertMenu.add(new MenuItem('Text Block', textBlockCommand));
+
+appMenuBar.add(insertMenu);
+
+/* The Help menu. */
+var helpMenu = new Menu('Help');
+
+var showHelpCommand = new MenuCommand(helpActions.showHelp);
+helpMenu.add(new MenuItem('Show Help', showHelpCommand));
+
+appMenuBar.add(helpMenu);
+
+/* Build the menu bar. */
+document.getElementsByTagName('body')[0].appendChild(appMenuBar.getElement());
+appMenuBar.show();
+
+
+/* Adding more menu items later on. */
+
+var imageCommand = new MenuCommand(insertActions.image);
+insertMenu.add(new MenuItem('Image', imageCommand));
+
diff --git a/Chapter16/16.06 - Undo with reversible commands.js b/Chapter16/16.06 - Undo with reversible commands.js
new file mode 100644
index 0000000..0e8bf92
--- /dev/null
+++ b/Chapter16/16.06 - Undo with reversible commands.js
@@ -0,0 +1,137 @@
+/* ReversibleCommand interface. */
+
+var ReversibleCommand = new Interface('ReversibleCommand', ['execute', 'undo']);
+
+/* Movement commands. */
+
+var MoveUp = function(cursor) { // implements ReversibleCommand
+ this.cursor = cursor;
+};
+MoveUp.prototype = {
+ execute: function() {
+ cursor.move(0, -10);
+ },
+ undo: function() {
+ cursor.move(0, 10);
+ }
+};
+
+var MoveDown = function(cursor) { // implements ReversibleCommand
+ this.cursor = cursor;
+};
+MoveDown.prototype = {
+ execute: function() {
+ cursor.move(0, 10);
+ },
+ undo: function() {
+ cursor.move(0, -10);
+ }
+};
+
+var MoveLeft = function(cursor) { // implements ReversibleCommand
+ this.cursor = cursor;
+};
+MoveLeft.prototype = {
+ execute: function() {
+ cursor.move(-10, 0);
+ },
+ undo: function() {
+ cursor.move(10, 0);
+ }
+};
+
+var MoveRight = function(cursor) { // implements ReversibleCommand
+ this.cursor = cursor;
+};
+MoveRight.prototype = {
+ execute: function() {
+ cursor.move(10, 0);
+ },
+ undo: function() {
+ cursor.move(-10, 0);
+ }
+};
+
+/* Cursor class. */
+
+var Cursor = function(width, height, parent) {
+ this.width = width;
+ this.height = height;
+ this.position = { x: width / 2, y: height / 2 };
+
+ this.canvas = document.createElement('canvas');
+ this.canvas.width = this.width;
+ this.canvas.height = this.height;
+ parent.appendChild(this.canvas);
+
+ this.ctx = this.canvas.getContext('2d');
+ this.ctx.fillStyle = '#cc0000';
+ this.move(0, 0);
+};
+Cursor.prototype.move = function(x, y) {
+ this.position.x += x;
+ this.position.y += y;
+
+ this.ctx.clearRect(0, 0, this.width, this.height);
+ this.ctx.fillRect(this.position.x, this.position.y, 3, 3);
+};
+
+/* UndoDecorator class. */
+
+var UndoDecorator = function(command, undoStack) { // implements ReversibleCommand
+ this.command = command;
+ this.undoStack = undoStack;
+};
+UndoDecorator.prototype = {
+ execute: function() {
+ this.undoStack.push(this.command);
+ this.command.execute();
+ },
+ undo: function() {
+ this.command.undo();
+ }
+};
+
+/* CommandButton class. */
+
+var CommandButton = function(label, command, parent) {
+ Interface.ensureImplements(command, ReversibleCommand);
+ this.element = document.createElement('button');
+ this.element.innerHTML = label;
+ parent.appendChild(this.element);
+
+ addEvent(this.element, 'click', function() {
+ command.execute();
+ });
+};
+
+/* UndoButton class. */
+
+var UndoButton = function(label, parent, undoStack) {
+ this.element = document.createElement('button');
+ this.element.innerHTML = label;
+ parent.appendChild(this.element);
+
+ addEvent(this.element, 'click', function() {
+ if(undoStack.length === 0) return;
+ var lastCommand = undoStack.pop();
+ lastCommand.undo();
+ });
+};
+
+/* Implementation code. */
+
+var body = document.getElementsByTagName('body')[0];
+var cursor = new Cursor(400, 400, body);
+var undoStack = [];
+
+var upCommand = new UndoDecorator(new MoveUp(cursor), undoStack);
+var downCommand = new UndoDecorator(new MoveDown(cursor), undoStack);
+var leftCommand = new UndoDecorator(new MoveLeft(cursor), undoStack);
+var rightCommand = new UndoDecorator(new MoveRight(cursor), undoStack);
+
+var upButton = new CommandButton('Up', upCommand, body);
+var downButton = new CommandButton('Down', downCommand, body);
+var leftButton = new CommandButton('Left', leftCommand, body);
+var rightButton = new CommandButton('Right', rightCommand, body);
+var undoButton = new UndoButton('Undo', body, undoStack);
diff --git a/Chapter16/16.07 - Undo with command logging.js b/Chapter16/16.07 - Undo with command logging.js
new file mode 100644
index 0000000..b27b4d4
--- /dev/null
+++ b/Chapter16/16.07 - Undo with command logging.js
@@ -0,0 +1,81 @@
+/* Movement commands. */
+
+var MoveUp = function(cursor) { // implements Command
+ this.cursor = cursor;
+};
+MoveUp.prototype = {
+ execute: function() {
+ cursor.move(0, -10);
+ }
+};
+
+/* Cursor class, with an internal command stack. */
+
+var Cursor = function(width, height, parent) {
+ this.width = width;
+ this.height = height;
+ this.commandStack = [];
+
+ this.canvas = document.createElement('canvas');
+ this.canvas.width = this.width;
+ this.canvas.height = this.height;
+ parent.appendChild(this.canvas);
+
+ this.ctx = this.canvas.getContext('2d');
+ this.ctx.strokeStyle = '#cc0000';
+ this.move(0, 0);
+};
+Cursor.prototype = {
+ move: function(x, y) {
+ var that = this;
+ this.commandStack.push(function() { that.lineTo(x, y); });
+ this.executeCommands();
+ },
+ lineTo: function(x, y) {
+ this.position.x += x;
+ this.position.y += y;
+ this.ctx.lineTo(this.position.x, this.position.y);
+ },
+ executeCommands: function() {
+ this.position = { x: this.width / 2, y: this.height / 2 };
+ this.ctx.clearRect(0, 0, this.width, this.height); // Clear the canvas.
+ this.ctx.beginPath();
+ this.ctx.moveTo(this.position.x, this.position.y);
+ for(var i = 0, len = this.commandStack.length; i < len; i++) {
+ this.commandStack[i]();
+ }
+ this.ctx.stroke();
+ },
+ undo: function() {
+ this.commandStack.pop();
+ this.executeCommands();
+ }
+};
+
+/* UndoButton class. */
+
+var UndoButton = function(label, parent, cursor) {
+ this.element = document.createElement('button');
+ this.element.innerHTML = label;
+ parent.appendChild(this.element);
+
+ addEvent(this.element, 'click', function() {
+ cursor.undo();
+ });
+};
+
+/* Implementation code. */
+
+var body = document.getElementsByTagName('body')[0];
+var cursor = new Cursor(400, 400, body);
+
+var upCommand = new MoveUp(cursor);
+var downCommand = new MoveDown(cursor);
+var leftCommand = new MoveLeft(cursor);
+var rightCommand = new MoveRight(cursor);
+
+var upButton = new CommandButton('Up', upCommand, body);
+var downButton = new CommandButton('Down', downCommand, body);
+var leftButton = new CommandButton('Left', leftCommand, body);
+var rightButton = new CommandButton('Right', rightCommand, body);
+var undoButton = new UndoButton('Undo', body, cursor);
diff --git a/Chapter17/17.01 - PublicLibrary class.js b/Chapter17/17.01 - PublicLibrary class.js
new file mode 100644
index 0000000..2c0e05f
--- /dev/null
+++ b/Chapter17/17.01 - PublicLibrary class.js
@@ -0,0 +1,65 @@
+/* Interfaces. */
+
+var Publication = new Interface('Publication', ['getIsbn', 'setIsbn', 'getTitle',
+ 'setTitle', 'getAuthor', 'setAuthor', 'getGenres', 'setGenres', 'display']);
+var Library = new Interface('Library', [‘addBook’, 'findBooks', 'checkoutBook',
+ 'returnBook']);
+var Catalog = new Interface('Catalog', ['handleFilingRequest', 'findBooks',
+ 'setSuccessor']);
+
+/* Book class. */
+
+var Book = function(isbn, title, author, genres) { // implements Publication
+ ...
+}
+
+
+/* PublicLibrary class. */
+
+var PublicLibrary = function(books) { // implements Library
+ this.catalog = {};
+ for(var i = 0, len = books.length; i < len; i++) {
+ this.addBook(books[i]);
+ }
+};
+PublicLibrary.prototype = {
+ findBooks: function(searchString) {
+ var results = [];
+ for(var isbn in this.catalog) {
+ if(!this.catalog.hasOwnProperty(isbn)) continue;
+ if(this.catalog[isbn].getTitle().match(searchString) ||
+ this.catalog[isbn].getAuthor().match(searchString)) {
+ results.push(this.catalog[isbn]);
+ }
+ }
+ return results;
+ },
+ checkoutBook: function(book) {
+ var isbn = book.getIsbn();
+ if(this.catalog[isbn]) {
+ if(this.catalog[isbn].available) {
+ this.catalog[isbn].available = false;
+ return this.catalog[isbn];
+ }
+ else {
+ throw new Error('PublicLibrary: book ' + book.getTitle() +
+ ' is not currently available.');
+ }
+ }
+ else {
+ throw new Error('PublicLibrary: book ' + book.getTitle() + ' not found.');
+ }
+ },
+ returnBook: function(book) {
+ var isbn = book.getIsbn();
+ if(this.catalog[isbn]) {
+ this.catalog[isbn].available = true;
+ }
+ else {
+ throw new Error('PublicLibrary: book ' + book.getTitle() + ' not found.');
+ }
+ },
+ addBook: function(newBook) {
+ this.catalog[newBook.getIsbn()] = { book: newBook, available: true };
+ }
+};
diff --git a/Chapter17/17.02 - PublicLibrary class with hard-coded catalogs.js b/Chapter17/17.02 - PublicLibrary class with hard-coded catalogs.js
new file mode 100644
index 0000000..8d9fd4b
--- /dev/null
+++ b/Chapter17/17.02 - PublicLibrary class with hard-coded catalogs.js
@@ -0,0 +1,30 @@
+/* PublicLibrary class, with hard-coded catalogs for genre. */
+
+var PublicLibrary = function(books) { // implements Library
+ this.catalog = {};
+ this.biographyCatalog = new BiographyCatalog();
+ this.fantasyCatalog = new FantasyCatalog();
+ this.mysteryCatalog = new MysteryCatalog();
+ this.nonFictionCatalog = new NonFictionCatalog();
+ this.sciFiCatalog = new SciFiCatalog();
+
+ for(var i = 0, len = books.length; i < len; i++) {
+ this.addBook(books[i]);
+ }
+};
+PublicLibrary.prototype = {
+ findBooks: function(searchString) { ... },
+ checkoutBook: function(book) { ... },
+ returnBook: function(book) { ... },
+ addBook: function(newBook) {
+ // Always add the book to the main catalog.
+ this.catalog[newBook.getIsbn()] = { book: newBook, available: true };
+
+ // Try to add the book to each genre catalog.
+ this.biographyCatalog.handleFilingRequest(newBook);
+ this.fantasyCatalog.handleFilingRequest(newBook);
+ this.mysteryCatalog.handleFilingRequest(newBook);
+ this.nonFictionCatalog.handleFilingRequest(newBook);
+ this.sciFiCatalog.handleFilingRequest(newBook);
+ }
+};
diff --git a/Chapter17/17.03 - PublicLibrary class with chain of responsibility catalogs.js b/Chapter17/17.03 - PublicLibrary class with chain of responsibility catalogs.js
new file mode 100644
index 0000000..9e692d8
--- /dev/null
+++ b/Chapter17/17.03 - PublicLibrary class with chain of responsibility catalogs.js
@@ -0,0 +1,47 @@
+/* PublicLibrary class, with genre catalogs in a chain of responsibility. */
+
+var PublicLibrary = function(books, firstGenreCatalog) { // implements Library
+ this.catalog = {};
+ this.firstGenreCatalog = firstGenreCatalog;
+
+ for(var i = 0, len = books.length; i < len; i++) {
+ this.addBook(books[i]);
+ }
+};
+PublicLibrary.prototype = {
+ findBooks: function(searchString) { ... },
+ checkoutBook: function(book) { ... },
+ returnBook: function(book) { ... },
+ addBook: function(newBook) {
+ // Always add the book to the main catalog.
+ this.catalog[newBook.getIsbn()] = { book: newBook, available: true };
+
+ // Try to add the book to each genre catalog.
+ this.firstGenreCatalog.handleFilingRequest(newBook);
+ }
+};
+
+
+// -----------------------------------------------------------------------------
+// Usage example.
+// -----------------------------------------------------------------------------
+
+ // Instantiate the catalogs.
+var biographyCatalog = new BiographyCatalog();
+var fantasyCatalog = new FantasyCatalog();
+var mysteryCatalog = new MysteryCatalog();
+var nonFictionCatalog = new NonFictionCatalog();
+var sciFiCatalog = new SciFiCatalog();
+
+// Set the links in the chain.
+biographyCatalog.setSuccessor(fantasyCatalog);
+fantasyCatalog.setSuccessor(mysteryCatalog);
+mysteryCatalog.setSuccessor(nonFictionCatalog);
+nonFictionCatalog.setSuccessor(sciFiCatalog);
+
+// Give the first link in the chain as an argument to the constructor.
+var myLibrary = new PublicLibrary(books, biographyCatalog);
+
+// You can add links to the chain whenever you like.
+var historyCatalog = new HistoryCatalog();
+sciFiCatalog.setSuccessor(historyCatalog);
diff --git a/Chapter17/17.04 - GenreCatalog and SciFiCatalog classes.js b/Chapter17/17.04 - GenreCatalog and SciFiCatalog classes.js
new file mode 100644
index 0000000..9cea14f
--- /dev/null
+++ b/Chapter17/17.04 - GenreCatalog and SciFiCatalog classes.js
@@ -0,0 +1,51 @@
+/* GenreCatalog class, used as a superclass for specific catalog classes. */
+
+var GenreCatalog = function() { // implements Catalog
+ this.successor = null;
+ this.catalog = [];
+};
+GenreCatalog.prototype = {
+ _bookMatchesCriteria: function(book) {
+ return false; // Default implementation; this method will be overriden in
+ // the subclasses.
+ }
+ handleFilingRequest: function(book) {
+ // Check to see if the book belongs in this catagory.
+ if(this._bookMatchesCriteria(book)) {
+ this.catalog.push(book);
+ }
+ // Pass the request on to the next link.
+ if(this.successor) {
+ this.successor.handleFilingRequest(book);
+ }
+ },
+ findBooks: function(request) {
+ if(this.successor) {
+ return this.successor.findBooks(request);
+ }
+ },
+ setSuccessor: function(successor) {
+ if(Interface.ensureImplements(successor, Catalog) {
+ this.successor = successor;
+ }
+ }
+};
+
+
+/* SciFiCatalog class. */
+
+var SciFiCatalog = function() {}; // implements Catalog
+extend(SciFiCatalog, GenreCatalog);
+SciFiCatalog.prototype._bookMatchesCriteria = function(book) {
+ var genres = book.getGenres();
+ if(book.getTitle().match(/space/i)) {
+ return true;
+ }
+ for(var i = 0, len = genres.length; i < len; i++) {
+ var genre = genres[i].toLowerCase();
+ if(genres === 'sci-fi' || genres === 'scifi' || genres === 'science fiction') {
+ return true;
+ }
+ }
+ return false;
+};
diff --git a/Chapter17/17.05 - The findBooks method.js b/Chapter17/17.05 - The findBooks method.js
new file mode 100644
index 0000000..7182ad1
--- /dev/null
+++ b/Chapter17/17.05 - The findBooks method.js
@@ -0,0 +1,96 @@
+/* PublicLibrary class. */
+
+var PublicLibrary = function(books) { // implements Library
+ ...
+};
+PublicLibrary.prototype = {
+ findBooks: function(searchString, genres) {
+ // If the optional genres argument is given, search for books only in
+ // those genres. Use the chain of responsibility to perform the search.
+ if(typeof genres === 'array' && genres.length > 0) {
+ var requestObject = {
+ searchString: searchString,
+ genres: genres,
+ results: []
+ };
+ var responseObject = this.firstGenreCatalog.findBooks(requestObject);
+ return responseObject.results;
+ }
+ // Otherwise, search through all books.
+ else {
+ var results = [];
+ for(var isbn in this.catalog) {
+ if(!this.catalog.hasOwnProperty(isbn)) continue;
+ if(this.catalog[isbn].getTitle().match(searchString) ||
+ this.catalog[isbn].getAuthor().match(searchString)) {
+ results.push(this.catalog[isbn]);
+ }
+ }
+ return results;
+ }
+ },
+ checkoutBook: function(book) { ... },
+ returnBook: function(book) { ... },
+ addBook: function(newBook) { ... }
+};
+
+/* GenreCatalog class, used as a superclass for specific catalog classes. */
+
+var GenreCatalog = function() { // implements Catalog
+ this.successor = null;
+ this.catalog = [];
+ this.genreNames = [];
+};
+GenreCatalog.prototype = {
+ _bookMatchesCriteria: function(book) { ... }
+ handleFilingRequest: function(book) { ... },
+ findBooks: function(request) {
+ var found = false;
+ for(var i = 0, len = request.genres.length; i < len; i++) {
+ for(var j = 0, nameLen = this.genreNames.length; j < nameLen; j++) {
+ if(this.genreNames[j] === request.genres[i]) {
+ found = true; // This link in the chain should handle
+ // the request.
+ break;
+ }
+ }
+ }
+
+ if(found) { // Search through this catalog for books that match the search
+ // string and aren't already in the results.
+ outerloop: for(var i = 0, len = this.catalog.length; i < len; i++) {
+ var book = this.catalog[i];
+ if(book.getTitle().match(searchString) ||
+ book.getAuthor().match(searchString)) {
+ for(var j = 0, requestLen = request.results.length; j < requestLen; j++) {
+ if(request.results[j].getIsbn() === book.getIsbn()) {
+ continue outerloop; // The book is already in the results; skip it.
+ }
+ }
+ request.results.push(book); // The book matches and doesn't already
+ // appear in the results. Add it.
+ }
+ }
+ }
+
+ // Continue to pass the request down the chain if the successor is set.
+ if(this.successor) {
+ return this.successor.findBooks(request);
+ }
+ // Otherwise, we have reached the end of the chain. Return the request
+ // object back up the chain.
+ else {
+ return request;
+ }
+ },
+ setSuccessor: function(successor) { ... }
+};
+
+
+/* SciFiCatalog class. */
+
+var SciFiCatalog = function() { // implements Catalog
+ this.genreNames = ['sci-fi', 'scifi', 'science fiction'];
+};
+extend(SciFiCatalog, GenreCatalog);
+SciFiCatalog.prototype._bookMatchesCriteria = function(book) { ... };
diff --git a/Chapter17/17.06 - DynamicGallery class from Chapter 9.js b/Chapter17/17.06 - DynamicGallery class from Chapter 9.js
new file mode 100644
index 0000000..1bad7b7
--- /dev/null
+++ b/Chapter17/17.06 - DynamicGallery class from Chapter 9.js
@@ -0,0 +1,73 @@
+/* Interfaces. */
+
+var Composite = new Interface('Composite', ['add', 'remove', 'getChild']);
+var GalleryItem = new Interface('GalleryItem', ['hide', 'show']);
+
+/* DynamicGallery class. */
+
+var DynamicGallery = function(id) { // implements Composite, GalleryItem
+ this.children = [];
+ this.element = document.createElement('div');
+ this.element.id = id;
+ this.element.className = 'dynamic-gallery';
+}
+DynamicGallery.prototype = {
+ add: function(child) {
+ Interface.ensureImplements(child, Composite, GalleryItem);
+ this.children.push(child);
+ this.element.appendChild(child.getElement());
+ },
+ remove: function(child) {
+ for(var node, i = 0; node = this.getChild(i); i++) {
+ if(node == child) {
+ this.formComponents[i].splice(i, 1);
+ break;
+ }
+ }
+ this.element.removeChild(child.getElement());
+ },
+ getChild: function(i) {
+ return this.children[i];
+ },
+
+ hide: function() {
+ for(var node, i = 0; node = this.getChild(i); i++) {
+ node.hide();
+ }
+ this.element.style.display = 'none';
+ },
+ show: function() {
+ this.element.style.display = '';
+ for(var node, i = 0; node = this.getChild(i); i++) {
+ node.show();
+ }
+ },
+
+ getElement: function() {
+ return this.element;
+ }
+};
+
+/* GalleryImage class. */
+
+var GalleryImage = function(src) { // implements Composite, GalleryItem
+ this.element = document.createElement('img');
+ this.element.className = 'gallery-image';
+ this.element.src = src;
+}
+GalleryImage.prototype = {
+ add: function() {}, // This is a leaf node, so we don't
+ remove: function() {}, // implement these methods, we just
+ getChild: function() {}, // define them.
+
+ hide: function() {
+ this.element.style.display = 'none';
+ },
+ show: function() {
+ this.element.style.display = '';
+ },
+
+ getElement: function() {
+ return this.element;
+ }
+};
diff --git a/Chapter17/17.07 - DynamicGallery class with optimization.js b/Chapter17/17.07 - DynamicGallery class with optimization.js
new file mode 100644
index 0000000..9868009
--- /dev/null
+++ b/Chapter17/17.07 - DynamicGallery class with optimization.js
@@ -0,0 +1,15 @@
+/* DynamicGallery class. */
+
+var DynamicGallery = function(id) { // implements Composite, GalleryItem
+ ...
+}
+DynamicGallery.prototype = {
+ add: function(child) { ... },
+ remove: function(child) { ... },
+ getChild: function(i) { ... },
+ hide: function() {
+ this.element.style.display = 'none';
+ },
+ show: function() { ... },
+ getElement: function() { ... }
+};
diff --git a/Chapter17/17.08 - DynamicGallery class with tags.js b/Chapter17/17.08 - DynamicGallery class with tags.js
new file mode 100644
index 0000000..f7d7223
--- /dev/null
+++ b/Chapter17/17.08 - DynamicGallery class with tags.js
@@ -0,0 +1,76 @@
+/* Interfaces. */
+
+var Composite = new Interface('Composite', ['add', 'remove', 'getChild',
+ 'getAllLeaves']);
+var GalleryItem = new Interface('GalleryItem', ['hide', 'show', 'addTag',
+ 'getPhotosWithTag']);
+
+/* DynamicGallery class. */
+
+var DynamicGallery = function(id) { // implements Composite, GalleryItem
+ this.children = [];
+ this.tags = [];
+ this.element = document.createElement('div');
+ this.element.id = id;
+ this.element.className = 'dynamic-gallery';
+}
+DynamicGallery.prototype = {
+ ...
+ addTag: function(tag) {
+ this.tags.push(tag);
+ for(var node, i = 0; node = this.getChild(i); i++) {
+ node.addTag(tag);
+ }
+ },
+ getAllLeaves: function() {
+ var leaves = [];
+ for(var node, i = 0; node = this.getChild(i); i++) {
+ leaves.concat(node.getAllLeaves());
+ }
+ return leaves;
+ },
+ getPhotosWithTag: function(tag) {
+ // First search in this object's tags; if the tag is found here, we can stop
+ // the search and just return all the leaf nodes.
+ for(var i = 0, len = this.tags.length; i < len; i++) {
+ if(this.tags[i] === tag) {
+ return this.getAllLeaves();
+ }
+ }
+
+ // If the tag isn't found in this object's tags, pass the request down
+ // the hierarchy.
+ for(var results = [], node, i = 0; node = this.getChild(i); i++) {
+ results.concat(node.getPhotosWithTag(tag));
+ }
+ return results;
+ },
+ ...
+};
+
+/* GalleryImage class. */
+
+var GalleryImage = function(src) { // implements Composite, GalleryItem
+ this.element = document.createElement('img');
+ this.element.className = 'gallery-image';
+ this.element.src = src;
+ this.tags = [];
+}
+GalleryImage.prototype = {
+ ...
+ addTag: function(tag) {
+ this.tags.push(tag);
+ },
+ getAllLeaves: function() { // Just return this.
+ return [this];
+ },
+ getPhotosWithTag: function(tag) {
+ for(var i = 0, len = this.tags.length; i < len; i++) {
+ if(this.tags[i] === tag) {
+ return [this];
+ }
+ }
+ return []; // Return an empty array if no matches were found.
+ },
+ ...
+};
diff --git a/Introduction/Interface.js b/Introduction/Interface.js
new file mode 100644
index 0000000..aabef9e
--- /dev/null
+++ b/Introduction/Interface.js
@@ -0,0 +1,72 @@
+// Constructor.
+
+var Interface = function(name, methods) {
+ if(arguments.length != 2) {
+ throw new Error("Interface constructor called with " + arguments.length
+ + "arguments, but expected exactly 2.");
+ }
+
+ this.name = name;
+ this.methods = [];
+ for(var i = 0, len = methods.length; i < len; i++) {
+ if(typeof methods[i] !== 'string') {
+ throw new Error("Interface constructor expects method names to be "
+ + "passed in as a string.");
+ }
+ this.methods.push(methods[i]);
+ }
+};
+
+// Static class method.
+
+Interface.ensureImplements = function(object) {
+ if(arguments.length < 2) {
+ throw new Error("Function Interface.ensureImplements called with " +
+ arguments.length + "arguments, but expected at least 2.");
+ }
+
+ for(var i = 1, len = arguments.length; i < len; i++) {
+ var interface = arguments[i];
+ if(interface.constructor !== Interface) {
+ throw new Error("Function Interface.ensureImplements expects arguments "
+ + "two and above to be instances of Interface.");
+ }
+
+ for(var j = 0, methodsLen = interface.methods.length; j < methodsLen; j++) {
+ var method = interface.methods[j];
+ if(!object[method] || typeof object[method] !== 'function') {
+ throw new Error("Function Interface.ensureImplements: object "
+ + "does not implement the " + interface.name
+ + " interface. Method " + method + " was not found.");
+ }
+ }
+ }
+};
+
+
+/*
+
+
+// Example usage:
+
+// Interfaces.
+
+var Composite = new Interface('Composite', ['add', 'remove', 'getChild']);
+var FormItem = new Interface('FormItem', ['save']);
+
+// CompositeForm class
+
+var CompositeForm = function(id, method, action) { // implements Composite, FormItem
+ ...
+};
+
+...
+
+function addForm(formInstance) {
+ Interface.ensureImplements(formInstance, Composite, FormItem);
+ // This function will throw an error if a required method is not implemented, halting execution.
+ // All code beneath this line will be executed only if the checks pass.
+ ...
+}
+
+*/
diff --git a/Introduction/Library.js b/Introduction/Library.js
new file mode 100644
index 0000000..c3a26ac
--- /dev/null
+++ b/Introduction/Library.js
@@ -0,0 +1,167 @@
+/* Reference Article: http://www.dustindiaz.com/top-ten-javascript/ */
+
+/* addEvent: simplified event attachment */
+function addEvent( obj, type, fn ) {
+ if (obj.addEventListener) {
+ obj.addEventListener( type, fn, false );
+ EventCache.add(obj, type, fn);
+ }
+ else if (obj.attachEvent) {
+ obj["e"+type+fn] = fn;
+ obj[type+fn] = function() { obj["e"+type+fn]( window.event ); }
+ obj.attachEvent( "on"+type, obj[type+fn] );
+ EventCache.add(obj, type, fn);
+ }
+ else {
+ obj["on"+type] = obj["e"+type+fn];
+ }
+}
+
+var EventCache = function(){
+ var listEvents = [];
+ return {
+ listEvents : listEvents,
+ add : function(node, sEventName, fHandler){
+ listEvents.push(arguments);
+ },
+ flush : function(){
+ var i, item;
+ for(i = listEvents.length - 1; i >= 0; i = i - 1){
+ item = listEvents[i];
+ if(item[0].removeEventListener){
+ item[0].removeEventListener(item[1], item[2], item[3]);
+ };
+ if(item[1].substring(0, 2) != "on"){
+ item[1] = "on" + item[1];
+ };
+ if(item[0].detachEvent){
+ item[0].detachEvent(item[1], item[2]);
+ };
+ item[0][item[1]] = null;
+ };
+ }
+ };
+}();
+addEvent(window,'unload',EventCache.flush);
+
+/* window 'load' attachment */
+function addLoadEvent(func) {
+ var oldonload = window.onload;
+ if (typeof window.onload != 'function') {
+ window.onload = func;
+ }
+ else {
+ window.onload = function() {
+ oldonload();
+ func();
+ }
+ }
+}
+
+/* grab Elements from the DOM by className */
+function getElementsByClass(searchClass,node,tag) {
+ var classElements = new Array();
+ if ( node == null )
+ node = document;
+ if ( tag == null )
+ tag = '*';
+ var els = node.getElementsByTagName(tag);
+ var elsLen = els.length;
+ var pattern = new RegExp("(^|\\s)"+searchClass+"(\\s|$)");
+ for (i = 0, j = 0; i < elsLen; i++) {
+ if ( pattern.test(els[i].className) ) {
+ classElements[j] = els[i];
+ j++;
+ }
+ }
+ return classElements;
+}
+
+
+/* insert an element after a particular node */
+function insertAfter(parent, node, referenceNode) {
+ parent.insertBefore(node, referenceNode.nextSibling);
+}
+
+
+/* get, set, and delete cookies */
+function getCookie( name ) {
+ var start = document.cookie.indexOf( name + "=" );
+ var len = start + name.length + 1;
+ if ( ( !start ) && ( name != document.cookie.substring( 0, name.length ) ) ) {
+ return null;
+ }
+ if ( start == -1 ) return null;
+ var end = document.cookie.indexOf( ";", len );
+ if ( end == -1 ) end = document.cookie.length;
+ return unescape( document.cookie.substring( len, end ) );
+}
+
+function setCookie( name, value, expires, path, domain, secure ) {
+ var today = new Date();
+ today.setTime( today.getTime() );
+ if ( expires ) {
+ expires = expires * 1000 * 60 * 60 * 24;
+ }
+ var expires_date = new Date( today.getTime() + (expires) );
+ document.cookie = name+"="+escape( value ) +
+ ( ( expires ) ? ";expires="+expires_date.toGMTString() : "" ) +
+ ( ( path ) ? ";path=" + path : "" ) +
+ ( ( domain ) ? ";domain=" + domain : "" ) +
+ ( ( secure ) ? ";secure" : "" );
+}
+
+function deleteCookie( name, path, domain ) {
+ if ( getCookie( name ) ) document.cookie = name + "=" +
+ ( ( path ) ? ";path=" + path : "") +
+ ( ( domain ) ? ";domain=" + domain : "" ) +
+ ";expires=Thu, 01-Jan-1970 00:00:01 GMT";
+}
+
+/* quick getElement reference */
+function $() {
+ var elements = new Array();
+ for (var i = 0; i < arguments.length; i++) {
+ var element = arguments[i];
+ if (typeof element == 'string')
+ element = document.getElementById(element);
+ if (arguments.length == 1)
+ return element;
+ elements.push(element);
+ }
+ return elements;
+}
+
+/* Object-oriented Helper functions. */
+function clone(object) {
+ function F() {}
+ F.prototype = object;
+ return new F;
+}
+
+function extend(subClass, superClass) {
+ var F = function() {};
+ F.prototype = superClass.prototype;
+ subClass.prototype = new F();
+ subClass.prototype.constructor = subClass;
+
+ subClass.superclass = superClass.prototype;
+ if(superClass.prototype.constructor == Object.prototype.constructor) {
+ superClass.prototype.constructor = superClass;
+ }
+}
+
+function augment(receivingClass, givingClass) {
+ if(arguments[2]) { // Only give certain methods.
+ for(var i = 2, len = arguments.length; i < len; i++) {
+ receivingClass.prototype[arguments[i]] = givingClass.prototype[arguments[i]];
+ }
+ }
+ else { // Give all methods.
+ for(methodName in givingClass.prototype) {
+ if(!receivingClass.prototype[methodName]) {
+ receivingClass.prototype[methodName] = givingClass.prototype[methodName];
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..eaa0d12
--- /dev/null
+++ b/README.txt
@@ -0,0 +1,17 @@
+--------------------------------------------------------------------------------
+Version 1.0.0
+--------------------------------------------------------------------------------
+
+This folder contains the code examples from the book "Pro JavaScript Design
+Patterns" by Ross Harmes and Dustin Diaz.
+
+The latest version of this code can always be downloaded at:
+
+http://jsdesignpatterns.com/code.zip
+
+and at the Apress website:
+
+http://apress.com/book/view/159059908x
+
+Questions and corrections can be sent to ross@jsdesignpatterns.com and
+dustin@jsdesignpatterns.com.
\ No newline at end of file