-
+
+
+
- + + + + +
diff --git a/book/snippets/01-JavaScript-Design-Patterns/0-design-pattern-categorization.es2015.js b/book/snippets/01-JavaScript-Design-Patterns/0-design-pattern-categorization.es2015.js new file mode 100644 index 00000000..da37c71e --- /dev/null +++ b/book/snippets/01-JavaScript-Design-Patterns/0-design-pattern-categorization.es2015.js @@ -0,0 +1,40 @@ +//*******************************************************// +// A brief note on classes +//*******************************************************// + +// Section contains description of ES2015, but not use it. +// I suggest remove the description and put the new examples. + +//********************** Snippet 1 **********************// + +// A car "class" + +// [ES2015+] Below we used new class declaration, using keyword class +// [ES2015+] We used new constructor method and method declaration +// [ES2015+] Classes are syntactic sugar over JavaScript's prototype-based inheritance +// [ES2015+] We used new template literals for string interpolation +class Car { + constructor(model) { + this.model = model; + this.color = 'silver'; + this.year = '2012'; + } + + getInfo() { + return `${this.model} ${this.year}`; + } +} + +//********************** Snippet 2 **********************// + +// Usage: + +// [ES2015+] We used new keyword const for immutable constant declaration +const myCar = new Car('ford'); + +myCar.year = '2010'; + +console.log(myCar.getInfo()); + +// Here the link on Stoyan Stefanov's post, it's a good post. +// But more modern data can be obtained here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes diff --git a/book/snippets/01-JavaScript-Design-Patterns/0-design-pattern-categorization.es5.js b/book/snippets/01-JavaScript-Design-Patterns/0-design-pattern-categorization.es5.js new file mode 100644 index 00000000..23827f74 --- /dev/null +++ b/book/snippets/01-JavaScript-Design-Patterns/0-design-pattern-categorization.es5.js @@ -0,0 +1,26 @@ +//*******************************************************// +// A brief note on classes +//*******************************************************// + +//********************** Snippet 1 **********************// + +// A car "class" +function Car( model ) { + + this.model = model; + this.color = "silver"; + this.year = "2012"; + + this.getInfo = function () { + return this.model + " " + this.year; + }; + +} + +//********************** Snippet 2 **********************// + +var myCar = new Car("ford"); + +myCar.year = "2010"; + +console.log( myCar.getInfo() ); \ No newline at end of file diff --git a/book/snippets/01-JavaScript-Design-Patterns/1-the-constructor-pattern.es2015.js b/book/snippets/01-JavaScript-Design-Patterns/1-the-constructor-pattern.es2015.js new file mode 100644 index 00000000..8e8bf7d3 --- /dev/null +++ b/book/snippets/01-JavaScript-Design-Patterns/1-the-constructor-pattern.es2015.js @@ -0,0 +1,179 @@ +//*******************************************************// +// Object Creation +//*******************************************************// + +//********************** Snippet 1 **********************// + +// [ES2015+] We used new keyword const for immutable constant declaration +// Each of the following options will create a new empty object: + +const newObject = {}; + +// or +const newObject = Object.create(Object.prototype); + +// or +const newObject = new Object(); + +//********************** Snippet 2 **********************// + +// ECMAScript 3 compatible approaches + +// 1. Dot syntax + +// Set properties +newObject.someKey = 'Hello World'; + +// Get properties +// [ES2015+] We used new keyword const for immutable constant declaration +const value = newObject.someKey; + +// 2. Square bracket syntax + +// Set properties +newObject['Some Key'] = 'Hello World'; + +// Get properties +// [ES2015+] We used new keyword const for immutable constant declaration +const value = newObject['Some Key']; + +// ECMAScript 5 only compatible approaches +// For more information see: http://kangax.github.com/es5-compat-table/ + +// 3. Object.defineProperty + +// Set properties +Object.defineProperty(newObject, 'someKey', { + value: "for more control of the property's behavior", + writable: true, + enumerable: true, + configurable: true, +}); + +// If the above feels a little difficult to read, a short-hand could +// be written as follows: +// [ES2015+] We used new keyword const for immutable constant declaration +// [ES2015+] We used new arrow function expression syntax +const defineProp = (obj, key, value) => { + const config = { + value: value, + writable: true, + enumerable: true, + configurable: true, + }; + Object.defineProperty(obj, key, config); +}; + +// To use, we then create a new empty "person" object +// [ES2015+] We used new keyword const for immutable constant declaration +const person = Object.create(Object.prototype); + +// Populate the object with properties +defineProp(person, 'car', 'Delorean'); +defineProp(person, 'dateOfBirth', '1981'); +defineProp(person, 'hasBeard', false); + +console.log(person); +// Outputs: Object {car: "Delorean", dateOfBirth: "1981", hasBeard: false} + +// 4. Object.defineProperties + +// Set properties +Object.defineProperties(newObject, { + someKey: { + value: 'Hello World', + writable: true, + }, + + anotherKey: { + value: 'Foo bar', + writable: false, + }, +}); + +// Getting properties for 3. and 4. can be done using any of the +// options in 1. and 2. + +//********************** Snippet 3 **********************// + +// Usage: + +// Create a race car driver that inherits from the person object +// [ES2015+] We used new keyword const for immutable constant declaration +const driver = Object.create(person); + +// Set some properties for the driver +defineProp(driver, 'topSpeed', '100mph'); + +// Get an inherited property (1981) +console.log(driver.dateOfBirth); + +// Get the property we set (100mph) +console.log(driver.topSpeed); + +//*******************************************************// +// Basic Constructors +//*******************************************************// + +//********************** Snippet 1 **********************// + +// [ES2015+] Below we used new class declaration, using keyword class +// [ES2015+] We used new constructor method and method declaration +// [ES2015+] Classes are syntactic sugar over JavaScript's prototype-based inheritance +// [ES2015+] We used new template literals for string interpolation +class Car { + constructor(model, year, miles) { + this.model = model; + this.year = year; + this.miles = miles; + } + + toString() { + return `${this.model} has done ${this.miles} miles`; + } +} + +// Usage: + +// We can create new instances of the car +// [ES2015+] We used new keyword const for immutable constant declaration +const civic = new Car('Honda Civic', 2009, 20000); +const mondeo = new Car('Ford Mondeo', 2010, 5000); + +// and then open our browser console to view the +// output of the toString() method being called on +// these objects +console.log(civic.toString()); +console.log(mondeo.toString()); + +//*******************************************************// +// Constructors With Prototypes +//*******************************************************// + +//********************** Snippet 1 **********************// +// [ES2015+] Classes are syntactic sugar over JavaScript's prototype-based inheritance +// [ES2015+] We used new constructor method and method declaration +// [ES2015+] All of it new syntax sugar above old function structures +class Car { + constructor(model, year, miles) { + this.model = model; + this.year = year; + this.miles = miles; + } +} + +// Note here that we are using Object.prototype.newMethod rather than +// Object.prototype so as to avoid redefining the prototype object +// [ES2015+] We still could use Object.prototype for adding new methods, because internally we use the same structure +// [ES2015+] We used new template literals for string interpolation +Car.prototype.toString = function() { + return `${this.model} has done ${this.miles} miles`; +}; + +// Usage: +// [ES2015+] We used new keyword const for immutable constant declaration +const civic = new Car('Honda Civic', 2009, 20000); +const mondeo = new Car('Ford Mondeo', 2010, 5000); + +console.log(civic.toString()); +console.log(mondeo.toString()); diff --git a/book/snippets/01-JavaScript-Design-Patterns/1-the-constructor-pattern.es5.js b/book/snippets/01-JavaScript-Design-Patterns/1-the-constructor-pattern.es5.js new file mode 100644 index 00000000..5794b25d --- /dev/null +++ b/book/snippets/01-JavaScript-Design-Patterns/1-the-constructor-pattern.es5.js @@ -0,0 +1,174 @@ +//*******************************************************// +// Object Creation +//*******************************************************// + +//********************** Snippet 1 **********************// + +// Each of the following options will create a new empty object: + +var newObject = {}; + +// or +var newObject = Object.create( Object.prototype ); + +// or +var newObject = new Object(); + + +//********************** Snippet 2 **********************// + +// ECMAScript 3 compatible approaches + +// 1. Dot syntax + +// Set properties +newObject.someKey = "Hello World"; + +// Get properties +var value = newObject.someKey; + + + +// 2. Square bracket syntax + +// Set properties +newObject["someKey"] = "Hello World"; + +// Get properties +var value = newObject["someKey"]; + + + +// ECMAScript 5 only compatible approaches +// For more information see: http://kangax.github.com/es5-compat-table/ + +// 3. Object.defineProperty + +// Set properties +Object.defineProperty( newObject, "someKey", { + value: "for more control of the property's behavior", + writable: true, + enumerable: true, + configurable: true +}); + +// If the above feels a little difficult to read, a short-hand could +// be written as follows: + +var defineProp = function ( obj, key, value ){ + var config = { + value: value, + writable: true, + enumerable: true, + configurable: true + }; + Object.defineProperty( obj, key, config ); +}; + +// To use, we then create a new empty "person" object +var person = Object.create( Object.prototype ); + +// Populate the object with properties +defineProp( person, "car", "Delorean" ); +defineProp( person, "dateOfBirth", "1981" ); +defineProp( person, "hasBeard", false ); + +console.log(person); +// Outputs: Object {car: "Delorean", dateOfBirth: "1981", hasBeard: false} + + +// 4. Object.defineProperties + +// Set properties +Object.defineProperties( newObject, { + + "someKey": { + value: "Hello World", + writable: true + }, + + "anotherKey": { + value: "Foo bar", + writable: false + } + +}); + +// Getting properties for 3. and 4. can be done using any of the +// options in 1. and 2. + + +//********************** Snippet 3 **********************// + +// Usage: + +// Create a race car driver that inherits from the person object +var driver = Object.create( person ); + +// Set some properties for the driver +defineProp(driver, "topSpeed", "100mph"); + +// Get an inherited property (1981) +console.log( driver.dateOfBirth ); + +// Get the property we set (100mph) +console.log( driver.topSpeed ); + + +//*******************************************************// +// Basic Constructors +//*******************************************************// + +//********************** Snippet 1 **********************// + +function Car( model, year, miles ) { + + this.model = model; + this.year = year; + this.miles = miles; + + this.toString = function () { + return this.model + " has done " + this.miles + " miles"; + }; +} + +// Usage: + +// We can create new instances of the car +var civic = new Car( "Honda Civic", 2009, 20000 ); +var mondeo = new Car( "Ford Mondeo", 2010, 5000 ); + +// and then open our browser console to view the +// output of the toString() method being called on +// these objects +console.log( civic.toString() ); +console.log( mondeo.toString() ); + +//*******************************************************// +// Constructors With Prototypes +//*******************************************************// + +//********************** Snippet 1 **********************// + +function Car( model, year, miles ) { + + this.model = model; + this.year = year; + this.miles = miles; + +} + + +// Note here that we are using Object.prototype.newMethod rather than +// Object.prototype so as to avoid redefining the prototype object +Car.prototype.toString = function () { + return this.model + " has done " + this.miles + " miles"; +}; + +// Usage: + +var civic = new Car( "Honda Civic", 2009, 20000 ); +var mondeo = new Car( "Ford Mondeo", 2010, 5000 ); + +console.log( civic.toString() ); +console.log( mondeo.toString() ); \ No newline at end of file diff --git a/book/snippets/01-JavaScript-Design-Patterns/10-the-factory-pattern.es2015.js b/book/snippets/01-JavaScript-Design-Patterns/10-the-factory-pattern.es2015.js new file mode 100644 index 00000000..c4bc5c1b --- /dev/null +++ b/book/snippets/01-JavaScript-Design-Patterns/10-the-factory-pattern.es2015.js @@ -0,0 +1,168 @@ +//********************** Snippet 1 **********************// +// [ES2015+] Below we used new class declaration, using keyword class +// [ES2015+] We used new constructor method and method declaration +// [ES2015+] Classes are syntactic sugar over JavaScript's prototype-based inheritance +// [ES2015+] We used new keyword const for immutable constant declaration +// [ES2015+] We used the destructuring assignment syntax that makes it possible to unpack values from data structures into distinct variables. + +// Types.js - Constructors used behind the scenes + +// A constructor for defining new cars +class Car { + constructor({ doors, state, color }) { + // some defaults + this.doors = doors || 4; + this.state = state || 'brand new'; + this.color = color || 'silver'; + } +} +// A constructor for defining new trucks +class Truck { + constructor({ state, wheelSize, color }) { + this.state = state || 'used'; + this.wheelSize = wheelSize || 'large'; + this.color = color || 'blue'; + } +} + +// FactoryExample.js + +// Define a vehicle factory +class VehicleFactory { + // Define the prototypes and utilities for this factory + + // Our default vehicleClass is Car + constructor() { + this.vehicleClass = Car; + } + // Our Factory method for creating new Vehicle instances + createVehicle(options) { + switch (options.vehicleType) { + case 'car': + this.vehicleClass = Car; + break; + case 'truck': + this.vehicleClass = Truck; + break; + //defaults to VehicleFactory.prototype.vehicleClass (Car) + } + + return new this.vehicleClass(options); + } +} + +// Create an instance of our factory that makes cars +const carFactory = new VehicleFactory(); +const car = carFactory.createVehicle({ + vehicleType: 'car', + color: 'yellow', + doors: 6, +}); + +// Test to confirm our car was created using the vehicleClass/prototype Car + +// Outputs: true +console.log(car instanceof Car); + +// Outputs: Car object of color "yellow", doors: 6 in a "brand new" state +console.log(car); + +//********************** Snippet 2 **********************// +// [ES2015+] We used new keyword const for immutable constant declaration + +const movingTruck = carFactory.createVehicle({ + vehicleType: 'truck', + state: 'like new', + color: 'red', + wheelSize: 'small', +}); + +// Test to confirm our truck was created with the vehicleClass/prototype Truck + +// Outputs: true +console.log(movingTruck instanceof Truck); + +// Outputs: Truck object of color "red", a "like new" state +// and a "small" wheelSize +console.log(movingTruck); + +//********************** Snippet 3 **********************// +// [ES2015+] Below we used new class declaration, using keyword class +// [ES2015+] The extends keyword is used to create a class which is a child of another class. +// [ES2015+] We have new pattern implementation with new inheritance +// [ES2015+] We used new keyword const for immutable constant declaration +// [ES2015+] A constructor can use the super keyword to call the constructor of the super class. +class TruckFactory extends VehicleFactory { + constructor() { + super(); + this.vehicleClass = Truck; + } +} +const truckFactory = new TruckFactory(); +const myBigTruck = truckFactory.createVehicle({ + state: 'omg..so bad.', + color: 'pink', + wheelSize: 'so big', +}); + +// Confirms that myBigTruck was created with the prototype Truck +// Outputs: true +console.log(myBigTruck instanceof Truck); + +// Outputs: Truck object with the color "pink", wheelSize "so big" +// and state "omg. so bad" +console.log(myBigTruck); + +//*******************************************************// +// Abstract Factories +//*******************************************************// + +//********************** Snippet 1 **********************// +// [ES2015+] Below we used new class declaration, using keyword class +// [ES2015+] We used new constructor method and method declaration +// [ES2015+] Classes are syntactic sugar over JavaScript's prototype-based inheritance +// [ES2015+] The static keyword defines a static method for a class. +// [ES2015+] Static methods are called without instantiating their class and cannot be called through a class instance. +// [ES2015+] Static methods are often used to create utility functions for an application. +// [ES2015+] We used new keyword const for immutable constant declaration + +class AbstractVehicleFactory { + constructor() { + // Storage for our vehicle types + this.types = {}; + } + + static getVehicle(type, customizations) { + const Vehicle = this.types[type]; + + return Vehicle ? new Vehicle(customizations) : null; + } + + static registerVehicle(type, Vehicle) { + const proto = Vehicle.prototype; + + // only register classes that fulfill the vehicle contract + if (proto.drive && proto.breakDown) { + this.types[type] = Vehicle; + } + + return abstractVehicleFactory; + } +} + +// Usage: + +abstractVehicleFactory.registerVehicle('car', Car); +abstractVehicleFactory.registerVehicle('truck', Truck); + +// Instantiate a new car based on the abstract vehicle type +const car = abstractVehicleFactory.getVehicle('car', { + color: 'lime green', + state: 'like new', +}); + +// Instantiate a new truck in a similar manner +const truck = abstractVehicleFactory.getVehicle('truck', { + wheelSize: 'medium', + color: 'neon yellow', +}); diff --git a/book/snippets/01-JavaScript-Design-Patterns/10-the-factory-pattern.es5.js b/book/snippets/01-JavaScript-Design-Patterns/10-the-factory-pattern.es5.js new file mode 100644 index 00000000..76349026 --- /dev/null +++ b/book/snippets/01-JavaScript-Design-Patterns/10-the-factory-pattern.es5.js @@ -0,0 +1,148 @@ +//********************** Snippet 1 **********************// + +// Types.js - Constructors used behind the scenes + +// A constructor for defining new cars +function Car( options ) { + + // some defaults + this.doors = options.doors || 4; + this.state = options.state || "brand new"; + this.color = options.color || "silver"; + + } + + // A constructor for defining new trucks + function Truck( options){ + + this.state = options.state || "used"; + this.wheelSize = options.wheelSize || "large"; + this.color = options.color || "blue"; + } + + + // FactoryExample.js + + // Define a skeleton vehicle factory + function VehicleFactory() {} + + // Define the prototypes and utilities for this factory + + // Our default vehicleClass is Car + VehicleFactory.prototype.vehicleClass = Car; + + // Our Factory method for creating new Vehicle instances + VehicleFactory.prototype.createVehicle = function ( options ) { + + switch(options.vehicleType){ + case "car": + this.vehicleClass = Car; + break; + case "truck": + this.vehicleClass = Truck; + break; + //defaults to VehicleFactory.prototype.vehicleClass (Car) + } + + return new this.vehicleClass( options ); + + }; + + // Create an instance of our factory that makes cars + var carFactory = new VehicleFactory(); + var car = carFactory.createVehicle( { + vehicleType: "car", + color: "yellow", + doors: 6 } ); + + // Test to confirm our car was created using the vehicleClass/prototype Car + + // Outputs: true + console.log( car instanceof Car ); + + // Outputs: Car object of color "yellow", doors: 6 in a "brand new" state + console.log( car ); + + //********************** Snippet 2 **********************// + + var movingTruck = carFactory.createVehicle( { + vehicleType: "truck", + state: "like new", + color: "red", + wheelSize: "small" } ); + +// Test to confirm our truck was created with the vehicleClass/prototype Truck + +// Outputs: true +console.log( movingTruck instanceof Truck ); + +// Outputs: Truck object of color "red", a "like new" state +// and a "small" wheelSize +console.log( movingTruck ); + +//********************** Snippet 3 **********************// + +function TruckFactory () {} +TruckFactory.prototype = new VehicleFactory(); +TruckFactory.prototype.vehicleClass = Truck; + +var truckFactory = new TruckFactory(); +var myBigTruck = truckFactory.createVehicle( { + state: "omg..so bad.", + color: "pink", + wheelSize: "so big" } ); + +// Confirms that myBigTruck was created with the prototype Truck +// Outputs: true +console.log( myBigTruck instanceof Truck ); + +// Outputs: Truck object with the color "pink", wheelSize "so big" +// and state "omg. so bad" +console.log( myBigTruck ); + +//*******************************************************// +// Abstract Factories +//*******************************************************// + +//********************** Snippet 1 **********************// + +var abstractVehicleFactory = (function () { + + // Storage for our vehicle types + var types = {}; + + return { + getVehicle: function ( type, customizations ) { + var Vehicle = types[type]; + + return (Vehicle ? new Vehicle(customizations) : null); + }, + + registerVehicle: function ( type, Vehicle ) { + var proto = Vehicle.prototype; + + // only register classes that fulfill the vehicle contract + if ( proto.drive && proto.breakDown ) { + types[type] = Vehicle; + } + + return abstractVehicleFactory; + } + }; + })(); + + + // Usage: + + abstractVehicleFactory.registerVehicle( "car", Car ); + abstractVehicleFactory.registerVehicle( "truck", Truck ); + + // Instantiate a new car based on the abstract vehicle type + var car = abstractVehicleFactory.getVehicle( "car", { + color: "lime green", + state: "like new" } ); + + // Instantiate a new truck in a similar manner + var truck = abstractVehicleFactory.getVehicle( "truck", { + wheelSize: "medium", + color: "neon yellow" } ); \ No newline at end of file diff --git a/book/snippets/01-JavaScript-Design-Patterns/11-the-mixin-pattern.es2015-2.js b/book/snippets/01-JavaScript-Design-Patterns/11-the-mixin-pattern.es2015-2.js new file mode 100644 index 00000000..31bf8ae7 --- /dev/null +++ b/book/snippets/01-JavaScript-Design-Patterns/11-the-mixin-pattern.es2015-2.js @@ -0,0 +1,115 @@ +//*******************************************************// +// Mixins +//*******************************************************// + +//********************** Snippet 1 **********************// +// [ES2015+] We used new keyword const for immutable constant declaration +// [ES2015+] We used new method declaration +// [ES2015+] We used new arrow function syntax +// [ES2015+] Below we used new class declaration, using keyword class +// [ES2015+] Class can be used as an expression as well as a statement. As an expression it returns a new class each time it's evaluated. +// [ES2015+] The extends keyword is used to create a class which is a child of another class. +// [ES2015+] The extends clause accepts arbitrary expressions that return classes or constructors +// [ES2015+] All we need to define a mixin is a function that accepts a superclass and creates a new subclass from it, like this: + +const MyMixins = superclass => + class extends superclass { + moveUp() { + console.log('move up'); + } + moveDown() { + console.log('move down'); + } + stop() { + console.log('stop! in the name of love!'); + } + }; + +//********************** Snippet 2 **********************// +// [ES2015+] Below we used new class declaration, using keyword class +// [ES2015+] We used new constructor method and method declaration +// [ES2015+] We used new arrow function syntax +// [ES2015+] We have new pattern implementation with new inheritance + +// A skeleton carAnimator constructor +class CarAnimator { + moveLeft() { + console.log('move left'); + } +} +// A skeleton personAnimator constructor +class PersonAnimator { + moveRandomly() { + /*..*/ + } +} + +// [ES2015+] Then we can use it in an extends clause like this: +class MyAnimator extends MyMixins(CarAnimator) {} + +// Create a new instance of carAnimator +const myAnimator = new MyAnimator(); +myAnimator.moveLeft(); +myAnimator.moveDown(); +myAnimator.stop(); + +// Outputs: +// move left +// move down +// stop! in the name of love! + +//********************** Snippet 3 **********************// +// [ES2015+] Below we used new class declaration, using keyword class +// [ES2015+] We used new constructor method and method declaration +// [ES2015+] Classes are syntactic sugar over JavaScript's prototype-based inheritance +// [ES2015+] The extends keyword is used to create a class which is a child of another class. +// [ES2015+] We used new keyword const for immutable constant declaration +// [ES2015+] We used new arrow function syntax + +// Define a simple Car constructor +class Car { + constructor({ model, color }) { + this.model = model || 'no model provided'; + this.color = color || 'no colour provided'; + } +} + +// Mixin +const Mixin = superclass => + class extends superclass { + driveForward() { + console.log('drive forward'); + } + driveBackward() { + console.log('drive backward'); + } + driveSideways() { + console.log('drive sideways'); + } + }; + +class MyCar extends Mixin(Car) {} + +// Create a new Car +const myCar = new MyCar({ + model: 'Ford Escort', + color: 'blue', +}); + +// Test to make sure we now have access to the methods +myCar.driveForward(); +myCar.driveBackward(); + +// Outputs: +// drive forward +// drive backward + +const mySportCar = new MyCar({ + model: 'Porsche', + color: 'red', +}); + +mySportsCar.driveSideways(); + +// Outputs: +// drive sideways diff --git a/book/snippets/01-JavaScript-Design-Patterns/11-the-mixin-pattern.es2015.js b/book/snippets/01-JavaScript-Design-Patterns/11-the-mixin-pattern.es2015.js new file mode 100644 index 00000000..94b1cd2d --- /dev/null +++ b/book/snippets/01-JavaScript-Design-Patterns/11-the-mixin-pattern.es2015.js @@ -0,0 +1,186 @@ +//*******************************************************// +// Sub-classing +//*******************************************************// + +//********************** Snippet 1 **********************// +// [ES2015+] Below we used new class declaration, using keyword class +// [ES2015+] We used new constructor method + +class Person { + constructor(firstName, lastName) { + this.firstName = firstName; + this.lastName = lastName; + this.gender = 'male'; + } +} + +//********************** Snippet 2 **********************// +// [ES2015+] We used new keyword const for immutable constant declaration +// [ES2015+] Below we used new class declaration, using keyword class +// [ES2015+] We used new constructor method +// [ES2015+] We have new pattern implementation with new inheritance + +// a new instance of Person can then easily be created as follows: +const clark = new Person('Clark', 'Kent'); + +// Define a subclass constructor for for "Superhero": +// [ES2015+] A constructor can use the super keyword to call the constructor of the super class. +class Superhero extends Person { + constructor(firstName, lastName, powers) { + // Invoke the superclass constructor on the new object + // then use .call() to invoke the constructor as a method of + // the object to be initialized. + + super(firstName, lastName); + + // Finally, store their powers, a new array of traits not found in a normal "Person" + this.powers = powers; + } +} + +const superman = new Superhero('Clark', 'Kent', ['flight', 'heat-vision']); +console.log(superman); + +// Outputs Person attributes as well as powers + +//*******************************************************// +// Mixins +//*******************************************************// + +//********************** Snippet 1 **********************// +// [ES2015+] We used new keyword const for immutable constant declaration +// [ES2015+] We used new method declaration + +const myMixins = { + moveUp() { + console.log('move up'); + }, + + moveDown() { + console.log('move down'); + }, + + stop() { + console.log('stop! in the name of love!'); + }, +}; + +//********************** Snippet 2 **********************// +// [ES2015+] Below we used new class declaration, using keyword class +// [ES2015+] We used new constructor method and method declaration +// [ES2015+] We used new arrow function syntax +// [ES2015+] We have new pattern implementation with new inheritance + +// A skeleton carAnimator constructor +class CarAnimator { + moveLeft() { + console.log('move left'); + } +} +// A skeleton personAnimator constructor +class PersonAnimator { + moveRandomly() { + /*..*/ + } +} +// [ES2015+] New Object.assign() method copies enumerable and own properties from a source object (second argument) to a target object (first argument). +Object.assign(CarAnimator.prototype, myMixins); +Object.assign(PersonAnimator.prototype, myMixins); + +// Create a new instance of carAnimator +const myAnimator = new CarAnimator(); +myAnimator.moveLeft(); +myAnimator.moveDown(); +myAnimator.stop(); + +// Outputs: +// move left +// move down +// stop! in the name of love! + +//********************** Snippet 3 **********************// +// [ES2015+] Below we used new class declaration, using keyword class +// [ES2015+] We used new constructor method and method declaration +// [ES2015+] Classes are syntactic sugar over JavaScript's prototype-based inheritance +// [ES2015+] We still could use Object.prototype for adding new methods, because internally we use the same structure +// [ES2015+] We used the destructuring assignment syntax that makes it possible to unpack values from data structures into distinct variables. +// [ES2015+] We used new keyword const for immutable constant declaration +// [ES2015+] We used new arrow function syntax + +// Define a simple Car constructor +class Car { + constructor({ model, color }) { + this.model = model || 'no model provided'; + this.color = color || 'no colour provided'; + } +} + +// Mixin +class Mixin { + driveForward() { + console.log('drive forward'); + } + + driveBackward() { + console.log('drive backward'); + } + + driveSideways() { + console.log('drive sideways'); + } +} + +// Extend an existing object with a method from another +// [ES2015+] The rest parameter syntax allows us to represent an indefinite number of arguments as an array. +const augment = (receivingClass, givingClass, ...methodsNames) => { + // only provide certain methods + if (methodsNames.length !== 0) { + // [ES2015+] New function map calls a provided callback function once for each element in an array, in order. + methodsNames.map(methodName => { + receivingClass.prototype[methodName] = givingClass.prototype[methodName]; + }); + + // provide all methods + } else { + // [ES2015+] New method Object.getOwnPropertyNames() returns an array of all properties (including non-enumerable properties) + Object.getOwnPropertyNames(givingClass.prototype).map(methodName => { + // check to make sure the receiving class doesn't + // have a method of the same name as the one currently + // being processed + if (!Object.hasOwnProperty.call(receivingClass.prototype, methodName)) { + receivingClass.prototype[methodName] = givingClass.prototype[methodName]; + } + }); + } +}; + +// Augment the Car constructor to include "driveForward" and "driveBackward" +augment(Car, Mixin, 'driveForward', 'driveBackward'); + +// Create a new Car +const myCar = new Car({ + model: 'Ford Escort', + color: 'blue', +}); + +// Test to make sure we now have access to the methods +myCar.driveForward(); +myCar.driveBackward(); + +// Outputs: +// drive forward +// drive backward + +// We can also augment Car to include all functions from our mixin +// by not explicitly listing a selection of them +augment(Car, Mixin); + +const mySportsCar = new Car({ + model: 'Porsche', + color: 'red', +}); + +mySportsCar.driveSideways(); + +// Outputs: +// drive sideways diff --git a/book/snippets/01-JavaScript-Design-Patterns/11-the-mixin-pattern.es5.js b/book/snippets/01-JavaScript-Design-Patterns/11-the-mixin-pattern.es5.js new file mode 100644 index 00000000..baa66274 --- /dev/null +++ b/book/snippets/01-JavaScript-Design-Patterns/11-the-mixin-pattern.es5.js @@ -0,0 +1,178 @@ +//*******************************************************// +// Sub-classing +//*******************************************************// + +//********************** Snippet 1 **********************// + +var Person = function( firstName, lastName ){ + + this.firstName = firstName; + this.lastName = lastName; + this.gender = "male"; + + }; + +//********************** Snippet 2 **********************// + +// a new instance of Person can then easily be created as follows: +var clark = new Person( "Clark", "Kent" ); + +// Define a subclass constructor for for "Superhero": +var Superhero = function( firstName, lastName, powers ){ + + // Invoke the superclass constructor on the new object + // then use .call() to invoke the constructor as a method of + // the object to be initialized. + + Person.call( this, firstName, lastName ); + + // Finally, store their powers, a new array of traits not found in a normal "Person" + this.powers = powers; +}; + +Superhero.prototype = Object.create( Person.prototype ); +var superman = new Superhero( "Clark", "Kent", ["flight","heat-vision"] ); +console.log( superman ); + +// Outputs Person attributes as well as powers + +//*******************************************************// +// Mixins +//*******************************************************// + +//********************** Snippet 1 **********************// + +var myMixins = { + + moveUp: function(){ + console.log( "move up" ); + }, + + moveDown: function(){ + console.log( "move down" ); + }, + + stop: function(){ + console.log( "stop! in the name of love!" ); + } + + }; + +//********************** Snippet 2 **********************// + +// A skeleton carAnimator constructor +function CarAnimator(){ + this.moveLeft = function(){ + console.log( "move left" ); + }; + } + + // A skeleton personAnimator constructor + function PersonAnimator(){ + this.moveRandomly = function(){ /*..*/ }; + } + + // Extend both constructors with our Mixin + _.extend( CarAnimator.prototype, myMixins ); + _.extend( PersonAnimator.prototype, myMixins ); + + // Create a new instance of carAnimator + var myAnimator = new CarAnimator(); + myAnimator.moveLeft(); + myAnimator.moveDown(); + myAnimator.stop(); + + // Outputs: + // move left + // move down + // stop! in the name of love! + +//********************** Snippet 3 **********************// + + // Define a simple Car constructor +var Car = function ( settings ) { + + this.model = settings.model || "no model provided"; + this.color = settings.color || "no colour provided"; + +}; + +// Mixin +var Mixin = function () {}; + +Mixin.prototype = { + + driveForward: function () { + console.log( "drive forward" ); + }, + + driveBackward: function () { + console.log( "drive backward" ); + }, + + driveSideways: function () { + console.log( "drive sideways" ); + } + +}; + + +// Extend an existing object with a method from another +function augment( receivingClass, givingClass ) { + + // only provide certain methods + if ( arguments[2] ) { + for ( var i = 2, len = arguments.length; i < len; i++ ) { + receivingClass.prototype[arguments[i]] = givingClass.prototype[arguments[i]]; + } + } + // provide all methods + else { + for ( var methodName in givingClass.prototype ) { + + // check to make sure the receiving class doesn't + // have a method of the same name as the one currently + // being processed + if ( !Object.hasOwnProperty.call(receivingClass.prototype, methodName) ) { + receivingClass.prototype[methodName] = givingClass.prototype[methodName]; + } + + // Alternatively (check prototype chain as well): + // if ( !receivingClass.prototype[methodName] ) { + // receivingClass.prototype[methodName] = givingClass.prototype[methodName]; + // } + } + } +} + + +// Augment the Car constructor to include "driveForward" and "driveBackward" +augment( Car, Mixin, "driveForward", "driveBackward" ); + +// Create a new Car +var myCar = new Car({ + model: "Ford Escort", + color: "blue" +}); + +// Test to make sure we now have access to the methods +myCar.driveForward(); +myCar.driveBackward(); + +// Outputs: +// drive forward +// drive backward + +// We can also augment Car to include all functions from our mixin +// by not explicitly listing a selection of them +augment( Car, Mixin ); + +var mySportsCar = new Car({ + model: "Porsche", + color: "red" +}); + +mySportsCar.driveSideways(); + +// Outputs: +// drive sideways \ No newline at end of file diff --git a/book/snippets/01-JavaScript-Design-Patterns/12-the-decoration-pattern.es2015.js b/book/snippets/01-JavaScript-Design-Patterns/12-the-decoration-pattern.es2015.js new file mode 100644 index 00000000..dbfe976a --- /dev/null +++ b/book/snippets/01-JavaScript-Design-Patterns/12-the-decoration-pattern.es2015.js @@ -0,0 +1,379 @@ +//********************** Snippet 1 **********************// +// [ES2015+] Below we used new class declaration, using keyword class +// [ES2015+] We used new constructor method +// [ES2015+] We used new keyword const for immutable constant declaration + +// A vehicle constructor +class Vehicle { + constructor(vehicleType) { + // some sane defaults + this.vehicleType = vehicleType || 'car'; + this.model = 'default'; + this.license = '00000-000'; + } +} + +// Test instance for a basic vehicle +const testInstance = new Vehicle('car'); +console.log(testInstance); + +// Outputs: +// vehicle: car, model:default, license: 00000-000 + +// Lets create a new instance of vehicle, to be decorated +const truck = new Vehicle('truck'); + +// New functionality we're decorating vehicle with +truck.setModel = function(modelName) { + this.model = modelName; +}; + +truck.setColor = function(color) { + this.color = color; +}; + +// Test the value setters and value assignment works correctly +truck.setModel('CAT'); +truck.setColor('blue'); + +console.log(truck); + +// Outputs: +// vehicle:truck, model:CAT, color: blue + +// Demonstrate "vehicle" is still unaltered +const secondInstance = new Vehicle('car'); +console.log(secondInstance); + +// Outputs: +// vehicle: car, model:default, license: 00000-000 + +//********************** Snippet 2 **********************// +// [ES2015+] Below we used new class declaration, using keyword class +// [ES2015+] Classes are syntactic sugar over JavaScript's prototype-based inheritance +// [ES2015+] We have new pattern implementation with new inheritance +// [ES2015+] We used new constructor method +// [ES2015+] We used new keyword const for immutable constant declaration +// [SE2015+] We used new keyword let, which declares a block scope local variable + +// The constructor to decorate +class MacBook { + constructor() { + this.cost = 997; + this.screenSize = 11.6; + } + getCost() { + return this.cost; + } + getScreenSize() { + return this.screenSize; + } +} + +// Decorator 1 +// [ES2015+] The extends keyword is used to create a class which is a child of another class. +// [ES2015+] A constructor can use the super keyword to call the constructor of the super class. +class Memory extends MacBook { + constructor(macBook) { + super(); + this.macBook = macBook; + } + + getCost() { + return this.macBook.getCost() + 75; + } +} + +// Decorator 2 +class Engraving extends MacBook { + constructor(macBook) { + super(); + this.macBook = macBook; + } + + getCost() { + return this.macBook.getCost() + 200; + } +} + +// Decorator 3 +class Insurance extends MacBook { + constructor(macBook) { + super(); + this.macBook = macBook; + } + + getCost() { + return this.macBook.getCost() + 250; + } +} + +// init main object +let mb = new MacBook(); + +// init decorators +mb = new Memory(mb); +mb = new Engraving(mb); +mb = new Insurance(mb); + +// Outputs: 1522 +console.log(mb.getCost()); + +// Outputs: 11.6 +console.log(mb.getScreenSize()); + +//*******************************************************// +// Pseudo-classical Decorators +//*******************************************************// + +//********************** Snippet 1 **********************// +// [ES2015+] Below we used new class declaration, using keyword class +// [ES2015+] We used new constructor method +// [ES2015+] We used new keyword const for immutable constant declaration +// [ES2015+] We used the destructuring assignment syntax that makes it possible to unpack values from data structures into distinct variables. + +// Create interfaces using a pre-defined Interface +// constructor that accepts an interface name and +// skeleton methods to expose. + +// In our reminder example summary() and placeOrder() +// represent functionality the interface should +// support +const reminder = new Interface('List', ['summary', 'placeOrder']); + +const properties = { + name: 'Remember to buy the milk', + date: '05/06/2016', + actions: { + summary() { + return 'Remember to buy the milk, we are almost out!'; + }, + placeOrder() { + return 'Ordering milk from your local grocery store'; + }, + }, +}; + +// Now create a constructor implementing the above properties +// and methods + +class Todo { + constructor({ actions, name }) { + // State the methods we expect to be supported + // as well as the Interface instance being checked + // against + + Interface.ensureImplements(actions, reminder); + + this.name = name; + this.methods = actions; + } +} + +// Create a new instance of our Todo constructor + +const todoItem = new Todo(properties); + +// Finally test to make sure these function correctly + +console.log(todoItem.methods.summary()); +console.log(todoItem.methods.placeOrder()); + +// Outputs: +// Remember to buy the milk, we are almost out! +// Ordering milk from your local grocery store + +//********************** Snippet 2 **********************// +// [ES2015+] We used new keyword const for immutable constant declaration +// [ES2015+] Below we used new class expression, using keyword class + +const Macbook = class { + //... +}; + +const MacbookWith4GBRam = class {}; +const MacbookWith8GBRam = class {}; +const MacbookWith4GBRamAndEngraving = class {}; +const MacbookWith8GBRamAndEngraving = class {}; +const MacbookWith8GBRamAndParallels = class {}; +const MacbookWith4GBRamAndParallels = class {}; +const MacbookWith8GBRamAndParallelsAndCase = class {}; +const MacbookWith4GBRamAndParallelsAndCase = class {}; +const MacbookWith8GBRamAndParallelsAndCaseAndInsurance = class {}; +const MacbookWith4GBRamAndParallelsAndCaseAndInsurance = class {}; + +//********************** Snippet 3 **********************// +// [ES2015+] We used new keyword const for immutable constant declaration +// [ES2015+] Below we used new class declaration, using keyword class + +const Macbook = new Interface('Macbook', [ + 'addEngraving', + 'addParallels', + 'add4GBRam', + 'add8GBRam', + 'addCase', +]); + +// A Macbook Pro might thus be represented as follows: +class MacbookPro { + // implements Macbook +} + +// [ES2015+] We still could use Object.prototype for adding new methods, because internally we use the same structure +MacbookPro.prototype = { + addEngraving() {}, + addParallels() {}, + add4GBRam() {}, + add8GBRam() {}, + addCase() {}, + getPrice() { + // Base price + return 900.0; + }, +}; + +//********************** Snippet 4 **********************// +// [ES2015+] Below we used new class declaration, using keyword class +// [ES2015+] We used new constructor method and method declaration + +// Macbook decorator abstract decorator class + +class MacbookDecorator { + constructor(macbook) { + Interface.ensureImplements(macbook, Macbook); + this.macbook = macbook; + } + + addEngraving() { + return this.macbook.addEngraving(); + } + + addParallels() { + return this.macbook.addParallels(); + } + + add4GBRam() { + return this.macbook.add4GBRam(); + } + + add8GBRam() { + return this.macbook.add8GBRam(); + } + + addCase() { + return this.macbook.addCase(); + } + + getPrice() { + return this.macbook.getPrice(); + } +} + +//********************** Snippet 5 **********************// +// [ES2015+] Below we used new class declaration, using keyword class +// [ES2015+] We used new constructor method and method declaration +// [ES2015+] We have new pattern implementation with new inheritance +// [ES2015+] We used new keyword const for immutable constant declaration +// [ES2015+] We used new arrow function syntax +// [ES2015+] We used new template literals for string interpolation + +// Let's now extend (decorate) the CaseDecorator +// with a MacbookDecorator +// [ES2015+] The extends keyword is used to create a class which is a child of another class. +// [ES2015+] A constructor can use the super keyword to call the constructor of the super class. +class CaseDecorator extends MacbookDecorator { + constructor(macbook) { + super(macbook); + } + + addCase() { + return `${this.macbook.addCase()}Adding case to macbook`; + } + + getPrice() { + return this.macbook.getPrice() + 45.0; + } +} + +//********************** Snippet 6 **********************// +// [ES2015+] We used new keyword const for immutable constant declaration + +// Instantiation of the macbook +const myMacbookPro = new MacbookPro(); + +// Outputs: 900.00 +console.log(myMacbookPro.getPrice()); + +// Decorate the macbook +const decoratedMacbookPro = new CaseDecorator(myMacbookPro); + +// This will return 945.00 +console.log(decoratedMacbookPro.getPrice()); + +//*******************************************************// +// Decorators With jQuery +//*******************************************************// + +//********************** Snippet 1 **********************// +// [ES2015+] We used new keyword const for immutable constant declaration +// [ES2015+] We used new keyword let for mutable variable declaration +// [ES2015+] We used new arrow function syntax +// [ES2015+] We used new template literals for string interpolation + +let decoratorApp = decoratorApp || {}; + +// define the objects we're going to use +decoratorApp = { + defaults: { + validate: false, + limit: 5, + name: 'foo', + welcome() { + console.log('welcome!'); + }, + }, + + options: { + validate: true, + name: 'bar', + helloWorld() { + console.log('hello world'); + }, + }, + + settings: {}, + + printObj(obj) { + const arr = []; + let next; + $.each(obj, (key, val) => { + next = `${key}: `; + next += $.isPlainObject(val) ? printObj(val) : val; + arr.push(next); + }); + + return `{ ${arr.join(', ')} }`; + }, +}; + +// merge defaults and options, without modifying defaults explicitly +decoratorApp.settings = $.extend( + {}, + decoratorApp.defaults, + decoratorApp.options +); + +// what we have done here is decorated defaults in a way that provides +// access to the properties and functionality it has to offer (as well as +// that of the decorator "options"). defaults itself is left unchanged + +$('#log').append( + decoratorApp.printObj(decoratorApp.settings) + + +decoratorApp.printObj(decoratorApp.options) + + +decoratorApp.printObj(decoratorApp.defaults) +); + +// settings -- { validate: true, limit: 5, name: bar, welcome: function (){ console.log( "welcome!" ); }, +// helloWorld: function (){ console.log( "hello world" ); } } +// options -- { validate: true, name: bar, helloWorld: function (){ console.log( "hello world" ); } } +// defaults -- { validate: false, limit: 5, name: foo, welcome: function (){ console.log("welcome!"); } } diff --git a/book/snippets/01-JavaScript-Design-Patterns/12-the-decoration-pattern.es5.js b/book/snippets/01-JavaScript-Design-Patterns/12-the-decoration-pattern.es5.js new file mode 100644 index 00000000..2a3aa1e4 --- /dev/null +++ b/book/snippets/01-JavaScript-Design-Patterns/12-the-decoration-pattern.es5.js @@ -0,0 +1,339 @@ +//********************** Snippet 1 **********************// + +// A vehicle constructor +function Vehicle( vehicleType ){ + + // some sane defaults + this.vehicleType = vehicleType || "car"; + this.model = "default"; + this.license = "00000-000"; + +} + +// Test instance for a basic vehicle +var testInstance = new Vehicle( "car" ); +console.log( testInstance ); + +// Outputs: +// vehicle: car, model:default, license: 00000-000 + +// Lets create a new instance of vehicle, to be decorated +var truck = new Vehicle( "truck" ); + +// New functionality we're decorating vehicle with +truck.setModel = function( modelName ){ + this.model = modelName; +}; + +truck.setColor = function( color ){ + this.color = color; +}; + +// Test the value setters and value assignment works correctly +truck.setModel( "CAT" ); +truck.setColor( "blue" ); + +console.log( truck ); + +// Outputs: +// vehicle:truck, model:CAT, color: blue + +// Demonstrate "vehicle" is still unaltered +var secondInstance = new Vehicle( "car" ); +console.log( secondInstance ); + +// Outputs: +// vehicle: car, model:default, license: 00000-000 + +//********************** Snippet 2 **********************// + +// The constructor to decorate +function MacBook() { + + this.cost = function () { return 997; }; + this.screenSize = function () { return 11.6; }; + + } + + // Decorator 1 + function memory( macbook ) { + + var v = macbook.cost(); + macbook.cost = function() { + return v + 75; + }; + + } + + // Decorator 2 + function engraving( macbook ){ + + var v = macbook.cost(); + macbook.cost = function(){ + return v + 200; + }; + + } + + // Decorator 3 + function insurance( macbook ){ + + var v = macbook.cost(); + macbook.cost = function(){ + return v + 250; + }; + + } + + var mb = new MacBook(); + memory( mb ); + engraving( mb ); + insurance( mb ); + + // Outputs: 1522 + console.log( mb.cost() ); + + // Outputs: 11.6 + console.log( mb.screenSize() ); + + +//*******************************************************// +// Pseudo-classical Decorators +//*******************************************************// + +//********************** Snippet 1 **********************// + +// Create interfaces using a pre-defined Interface +// constructor that accepts an interface name and +// skeleton methods to expose. + +// In our reminder example summary() and placeOrder() +// represent functionality the interface should +// support +var reminder = new Interface( "List", ["summary", "placeOrder"] ); + +var properties = { + name: "Remember to buy the milk", + date: "05/06/2016", + actions:{ + summary: function (){ + return "Remember to buy the milk, we are almost out!"; + }, + placeOrder: function (){ + return "Ordering milk from your local grocery store"; + } + } +}; + +// Now create a constructor implementing the above properties +// and methods + +function Todo( config ){ + + // State the methods we expect to be supported + // as well as the Interface instance being checked + // against + + Interface.ensureImplements( config.actions, reminder ); + + this.name = config.name; + this.methods = config.actions; + +} + +// Create a new instance of our Todo constructor + +var todoItem = new Todo( properties ); + +// Finally test to make sure these function correctly + +console.log( todoItem.methods.summary() ); +console.log( todoItem.methods.placeOrder() ); + +// Outputs: +// Remember to buy the milk, we are almost out! +// Ordering milk from your local grocery store + +//********************** Snippet 2 **********************// + +var Macbook = function(){ + //... +}; + +var MacbookWith4GBRam = function(){}, + MacbookWith8GBRam = function(){}, + MacbookWith4GBRamAndEngraving = function(){}, + MacbookWith8GBRamAndEngraving = function(){}, + MacbookWith8GBRamAndParallels = function(){}, + MacbookWith4GBRamAndParallels = function(){}, + MacbookWith8GBRamAndParallelsAndCase = function(){}, + MacbookWith4GBRamAndParallelsAndCase = function(){}, + MacbookWith8GBRamAndParallelsAndCaseAndInsurance = function(){}, + MacbookWith4GBRamAndParallelsAndCaseAndInsurance = function(){}; + + +//********************** Snippet 3 **********************// + +var Macbook = new Interface( "Macbook", + ["addEngraving", + "addParallels", + "add4GBRam", + "add8GBRam", + "addCase"]); + +// A Macbook Pro might thus be represented as follows: +var MacbookPro = function(){ + // implements Macbook +}; + +MacbookPro.prototype = { + addEngraving: function(){ + }, + addParallels: function(){ + }, + add4GBRam: function(){ + }, + add8GBRam:function(){ + }, + addCase: function(){ + }, + getPrice: function(){ + // Base price + return 900.00; + } +}; + +//********************** Snippet 4 **********************// +// Macbook decorator abstract decorator class + +var MacbookDecorator = function( macbook ){ + + Interface.ensureImplements( macbook, Macbook ); + this.macbook = macbook; + +}; + +MacbookDecorator.prototype = { + addEngraving: function(){ + return this.macbook.addEngraving(); + }, + addParallels: function(){ + return this.macbook.addParallels(); + }, + add4GBRam: function(){ + return this.macbook.add4GBRam(); + }, + add8GBRam:function(){ + return this.macbook.add8GBRam(); + }, + addCase: function(){ + return this.macbook.addCase(); + }, + getPrice: function(){ + return this.macbook.getPrice(); + } +}; + +//********************** Snippet 5 **********************// + +// First, define a way to extend an object a +// with the properties in object b. We'll use +// this shortly! +function extend( a, b ){ + for( var key in b ) + if( b.hasOwnProperty(key) ) + a[key] = b[key]; + return a; +} + +var CaseDecorator = function( macbook ){ + this.macbook = macbook; +}; + +// Let's now extend (decorate) the CaseDecorator +// with a MacbookDecorator +extend( CaseDecorator, MacbookDecorator ); + +CaseDecorator.prototype.addCase = function(){ + return this.macbook.addCase() + "Adding case to macbook"; +}; + +CaseDecorator.prototype.getPrice = function(){ + return this.macbook.getPrice() + 45.00; +}; + +//********************** Snippet 6 **********************// + +// Instantiation of the macbook +var myMacbookPro = new MacbookPro(); + +// Outputs: 900.00 +console.log( myMacbookPro.getPrice() ); + +// Decorate the macbook +var decoratedMacbookPro = new CaseDecorator( myMacbookPro ); + +// This will return 945.00 +console.log( decoratedMacbookPro.getPrice() ); + + +//*******************************************************// +// Decorators With jQuery +//*******************************************************// + +//********************** Snippet 1 **********************// + +var decoratorApp = decoratorApp || {}; + +// define the objects we're going to use +decoratorApp = { + + defaults: { + validate: false, + limit: 5, + name: "foo", + welcome: function () { + console.log( "welcome!" ); + } + }, + + options: { + validate: true, + name: "bar", + helloWorld: function () { + console.log( "hello world" ); + } + }, + + settings: {}, + + printObj: function ( obj ) { + var arr = [], + next; + $.each( obj, function ( key, val ) { + next = key + ": "; + next += $.isPlainObject(val) ? printObj( val ) : val; + arr.push( next ); + } ); + + return "{ " + arr.join(", ") + " }"; + } + +}; + +// merge defaults and options, without modifying defaults explicitly +decoratorApp.settings = $.extend({}, decoratorApp.defaults, decoratorApp.options); + +// what we have done here is decorated defaults in a way that provides +// access to the properties and functionality it has to offer (as well as +// that of the decorator "options"). defaults itself is left unchanged + +$("#log") + .append( decoratorApp.printObj(decoratorApp.settings) + + + decoratorApp.printObj(decoratorApp.options) + + + decoratorApp.printObj(decoratorApp.defaults)); + +// settings -- { validate: true, limit: 5, name: bar, welcome: function (){ console.log( "welcome!" ); }, +// helloWorld: function (){ console.log( "hello world" ); } } +// options -- { validate: true, name: bar, helloWorld: function (){ console.log( "hello world" ); } } +// defaults -- { validate: false, limit: 5, name: foo, welcome: function (){ console.log("welcome!"); } } diff --git a/book/snippets/01-JavaScript-Design-Patterns/13-flyweight-pattern.es2015.js b/book/snippets/01-JavaScript-Design-Patterns/13-flyweight-pattern.es2015.js new file mode 100644 index 00000000..08730cb6 --- /dev/null +++ b/book/snippets/01-JavaScript-Design-Patterns/13-flyweight-pattern.es2015.js @@ -0,0 +1,382 @@ +//*******************************************************// +// Implementing Classical Flyweights +//*******************************************************// +//********************** Snippet 1 **********************// +// Simulate pure virtual inheritance/"implement" keyword for JS +Function.prototype.implementsFor = function(parentClassOrObject) { + if (parentClassOrObject.constructor === Function) { + // Normal Inheritance + this.prototype = new parentClassOrObject(); + this.prototype.constructor = this; + this.prototype.parent = parentClassOrObject.prototype; + } else { + // Pure Virtual Inheritance + this.prototype = parentClassOrObject; + this.prototype.constructor = this; + this.prototype.parent = parentClassOrObject; + } + return this; +}; + + +//********************** Snippet 2 **********************// +// [ES2015+] We used new template literals for string interpolation +// [ES2015+] We used new keyword const for immutable constant declaration +// [SE2015+] We used new keyword let, which declares a block scope local variable +// [ES2015+] We used new arrow function syntax +// [ES2015+] Parentheses are optional when there is only one parameter + +// Flyweight object +const CoffeeOrder = { + // Interfaces + serveCoffee(context) {}, + getFlavor() {}, +}; + +// ConcreteFlyweight object that creates ConcreteFlyweight +// Implements CoffeeOrder +function CoffeeFlavor(newFlavor) { + const flavor = newFlavor; + + // If an interface has been defined for a feature + // implement the feature + if (typeof this.getFlavor === 'function') { + this.getFlavor = () => flavor; + } + + if (typeof this.serveCoffee === 'function') { + this.serveCoffee = context => { + console.log( + `Serving Coffee flavor ${flavor} to table number ${context.getTable()}` + ); + }; + } +} + +// Implement interface for CoffeeOrder +CoffeeFlavor.implementsFor(CoffeeOrder); + +// Handle table numbers for a coffee order +const CoffeeOrderContext = tableNumber => { + return { + getTable() { + return tableNumber; + }, + }; +}; + +const CoffeeFlavorFactory = () => { + const flavors = {}; + let length = 0; + + return { + getCoffeeFlavor(flavorName) { + let flavor = flavors[flavorName]; + if (typeof flavor === 'undefined') { + flavor = new CoffeeFlavor(flavorName); + flavors[flavorName] = flavor; + length++; + } + return flavor; + }, + + getTotalCoffeeFlavorsMade() { + return length; + }, + }; +}; + +// Sample usage: +// testFlyweight() + +const testFlyweight = () => { + // The flavors ordered. + const flavors = []; + + // The tables for the orders. + const tables = []; + + // Number of orders made + let ordersMade = 0; + + // The CoffeeFlavorFactory instance + const flavorFactory = new CoffeeFlavorFactory(); + + function takeOrders(flavorIn, table) { + flavors.push(flavorFactory.getCoffeeFlavor(flavorIn)); + tables.push(new CoffeeOrderContext(table)); + ordersMade++; + } + + takeOrders('Cappuccino', 2); + takeOrders('Cappuccino', 2); + takeOrders('Frappe', 1); + takeOrders('Frappe', 1); + takeOrders('Xpresso', 1); + takeOrders('Frappe', 897); + takeOrders('Cappuccino', 97); + takeOrders('Cappuccino', 97); + takeOrders('Frappe', 3); + takeOrders('Xpresso', 3); + takeOrders('Cappuccino', 3); + takeOrders('Xpresso', 96); + takeOrders('Frappe', 552); + takeOrders('Cappuccino', 121); + takeOrders('Xpresso', 121); + + for (let i = 0; i < ordersMade; ++i) { + flavors[i].serveCoffee(tables[i]); + } + console.log(' '); + console.log( + `total CoffeeFlavor objects made: ${flavorFactory.getTotalCoffeeFlavorsMade()}` + ); +}; + +//*******************************************************// +// Converting code to use the Flyweight pattern +//*******************************************************// + +//********************** Snippet 1 **********************// +// [ES2015+] Below we used new class declaration, using keyword class +// [ES2015+] We used new constructor method and method declaration +// [ES2015+] We used new keyword const for immutable constant declaration + +class Book { + constructor( + id, + title, + author, + genre, + pageCount, + publisherID, + ISBN, + checkoutDate, + checkoutMember, + dueReturnDate, + availability + ) { + this.id = id; + this.title = title; + this.author = author; + this.genre = genre; + this.pageCount = pageCount; + this.publisherID = publisherID; + this.ISBN = ISBN; + this.checkoutDate = checkoutDate; + this.checkoutMember = checkoutMember; + this.dueReturnDate = dueReturnDate; + this.availability = availability; + } + + getTitle() { + return this.title; + } + + getAuthor() { + return this.author; + } + + getISBN() { + return this.ISBN; + } + + // For brevity, other getters are not shown + updateCheckoutStatus( + bookID, + newStatus, + checkoutDate, + checkoutMember, + newReturnDate + ) { + this.id = bookID; + this.availability = newStatus; + this.checkoutDate = checkoutDate; + this.checkoutMember = checkoutMember; + this.dueReturnDate = newReturnDate; + } + + extendCheckoutPeriod(bookID, newReturnDate) { + this.id = bookID; + this.dueReturnDate = newReturnDate; + } + + isPastDue(bookID) { + const currentDate = new Date(); + return currentDate.getTime() > Date.parse(this.dueReturnDate); + } +} + +//********************** Snippet 2 **********************// +// [ES2015+] We used new keyword const for immutable constant declaration + +// Flyweight optimized version +const Book = function(title, author, genre, pageCount, publisherID, ISBN) { + this.title = title; + this.author = author; + this.genre = genre; + this.pageCount = pageCount; + this.publisherID = publisherID; + this.ISBN = ISBN; +}; + +//*******************************************************// +// Converting code to use the Flyweight pattern +//*******************************************************// + +//********************** Snippet 1 **********************// +// [ES2015+] We used new keyword const for immutable constant declaration +// [SE2015+] We used new keyword let, which declares a block scope local variable +// [ES2015+] We used new arrow function syntax + +// Book Factory singleton +const existingBooks = {}; + +class BookFactory { + constructor(title, author, genre, pageCount, publisherID, ISBN) { + + // Find out if a particular book meta-data combination has been created before + // !! or (bang bang) forces a boolean to be returned + this.existingBook = existingBooks[ISBN]; + if (!!this.existingBook) { + return this.existingBook; + } else { + + // if not, let's create a new instance of the book and store it + const book = new Book(title, author, genre, pageCount, publisherID, ISBN); + existingBooks[ISBN] = book; + return book; + + } + } +} + + +//*******************************************************// +// Managing the extrinsic states +//*******************************************************// + +//********************** Snippet 1 **********************// +// [ES2015+] Below we used new class declaration, using keyword class +// [ES2015+] We used new constructor method and method declaration +// [ES2015+] We used new keyword const for immutable constant declaration + +// BookRecordManager singleton +const bookRecordDatabase = {}; + +class BookRecordManager { + // add a new book into the library system + constructor(id, title, author, genre, pageCount, publisherID, ISBN, checkoutDate, checkoutMember, dueReturnDate, availability) { + this.book = new BookFactory(title, author, genre, pageCount, publisherID, ISBN); + + bookRecordDatabase[id] = { + checkoutMember, + checkoutDate, + dueReturnDate, + availability, + book: this.book, + }; + } + + updateCheckoutStatus(bookID, newStatus, checkoutDate, checkoutMember, newReturnDate) { + + const record = bookRecordDatabase[bookID]; + record.availability = newStatus; + record.checkoutDate = checkoutDate; + record.checkoutMember = checkoutMember; + record.dueReturnDate = newReturnDate; + } + + extendCheckoutPeriod(bookID, newReturnDate) { + bookRecordDatabase[bookID].dueReturnDate = newReturnDate; + } + + isPastDue(bookID) { + const currentDate = new Date(); + return currentDate.getTime() > Date.parse(bookRecordDatabase[bookID].dueReturnDate); + } +} + +//*******************************************************// +// The Flyweight pattern and the DOM +//*******************************************************// +//********************** Snippet 1 **********************// + +
+ + +
++ + +
++ + + +
++ + +
+Searched for:${tags}
`); + }); + + // Subscribe to the new results topic + $.subscribe('/search/resultSet', (e, results) => { + $('#searchResults') + .empty() + .append(resultTemplate(results)); + }); + + // Submit a search query and publish tags on the /search/tags topic + $('#flickrSearch').submit(function(e) { + e.preventDefault(); + const tags = $(this) + .find('#query') + .val(); + + if (!tags) { + return; + } + + $.publish('/search/tags', [$.trim(tags)]); + }); + + // Subscribe to new tags being published and perform + // a search query using them. Once data has returned + // publish this data for the rest of the application + // to consume + // [ES2015+] We used the destructuring assignment syntax that makes it possible to unpack values from data structures into distinct variables. + $.subscribe('/search/tags', (e, tags) => { + $.getJSON( + 'http://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?', + { + tags, + tagmode: 'any', + format: 'json', + }, + // [ES2015+] The destructuring assignment as function parameter + ({ items }) => { + if (!items.length) { + return; + } + // [ES2015+] New shorthand property names in object creation, if variable name equal to object key + $.publish('/search/resultSet', { items }); + } + ); + }); + })(jQuery); + \ No newline at end of file diff --git a/book/snippets/01-JavaScript-Design-Patterns/5-the-observer-pattern.es5.js b/book/snippets/01-JavaScript-Design-Patterns/5-the-observer-pattern.es5.js new file mode 100644 index 00000000..957cda44 --- /dev/null +++ b/book/snippets/01-JavaScript-Design-Patterns/5-the-observer-pattern.es5.js @@ -0,0 +1,572 @@ +//********************** Snippet 1 **********************// + +function ObserverList() { + this.observerList = []; +} + +ObserverList.prototype.add = function (obj) { + return this.observerList.push(obj); +}; + +ObserverList.prototype.count = function () { + return this.observerList.length; +}; + +ObserverList.prototype.get = function (index) { + if (index > -1 && index < this.observerList.length) { + return this.observerList[index]; + } +}; + +ObserverList.prototype.indexOf = function (obj, startIndex) { + var i = startIndex; + + while (i < this.observerList.length) { + if (this.observerList[i] === obj) { + return i; + } + i++; + } + + return -1; +}; + +ObserverList.prototype.removeAt = function (index) { + this.observerList.splice(index, 1); +}; + +//********************** Snippet 2 **********************// + +function Subject() { + this.observers = new ObserverList(); +} + +Subject.prototype.addObserver = function (observer) { + this.observers.add(observer); +}; + +Subject.prototype.removeObserver = function (observer) { + this.observers.removeAt(this.observers.indexOf(observer, 0)); +}; + +Subject.prototype.notify = function (context) { + var observerCount = this.observers.count(); + for (var i = 0; i < observerCount; i++) { + this.observers.get(i).update(context); + } +}; + +//********************** Snippet 3 **********************// + +// The Observer +function Observer() { + this.update = function () { + // ... + }; +} + +//********************** Snippet 4 **********************// + + + + + +//********************** Snippet 5 **********************// + +// Extend an object with an extension +function extend( obj, extension ){ + for ( var key in extension ){ + obj[key] = extension[key]; + } + } + + // References to our DOM elements + + var controlCheckbox = document.getElementById( "mainCheckbox" ), + addBtn = document.getElementById( "addNewObserver" ), + container = document.getElementById( "observersContainer" ); + + + // Concrete Subject + + // Extend the controlling checkbox with the Subject class + extend( controlCheckbox, new Subject() ); + + // Clicking the checkbox will trigger notifications to its observers + controlCheckbox.onclick = function(){ + controlCheckbox.notify( controlCheckbox.checked ); + }; + + addBtn.onclick = addNewObserver; + + // Concrete Observer + + function addNewObserver(){ + + // Create a new checkbox to be added + var check = document.createElement( "input" ); + check.type = "checkbox"; + + // Extend the checkbox with the Observer class + extend( check, new Observer() ); + + // Override with custom update behaviour + check.update = function( value ){ + this.checked = value; + }; + + // Add the new observer to our list of observers + // for our main subject + controlCheckbox.addObserver( check ); + + // Append the item to the container + container.appendChild( check ); + } + +//*******************************************************// +// Differences Between The Observer And Publish/Subscribe Pattern +//*******************************************************// + +//********************** Snippet 1 **********************// + +// A very simple new mail handler + +// A count of the number of messages received +var mailCounter = 0; + +// Initialize subscribers that will listen out for a topic +// with the name "inbox/newMessage". + +// Render a preview of new messages +var subscriber1 = subscribe( "inbox/newMessage", function( topic, data ) { + + // Log the topic for debugging purposes + console.log( "A new message was received: ", topic ); + + // Use the data that was passed from our subject + // to display a message preview to the user + $( ".messageSender" ).html( data.sender ); + $( ".messagePreview" ).html( data.body ); + +}); + +// Here's another subscriber using the same data to perform +// a different task. + +// Update the counter displaying the number of new +// messages received via the publisher + +var subscriber2 = subscribe( "inbox/newMessage", function( topic, data ) { + + $('.newMessageCounter').html( ++mailCounter ); + +}); + +publish( "inbox/newMessage", [{ + sender: "hello@google.com", + body: "Hey there! How are you doing today?" +}]); + +// We could then at a later point unsubscribe our subscribers +// from receiving any new topic notifications as follows: +// unsubscribe( subscriber1 ); +// unsubscribe( subscriber2 ); + + +//*******************************************************// +// Publish/Subscribe Implementations +//*******************************************************// + +//********************** Snippet 1 **********************// + +// Publish + +// jQuery: $(obj).trigger("channel", [arg1, arg2, arg3]); +$( el ).trigger( "/login", [{username:"test", userData:"test"}] ); + +// Dojo: dojo.publish("channel", [arg1, arg2, arg3] ); +dojo.publish( "/login", [{username:"test", userData:"test"}] ); + +// YUI: el.publish("channel", [arg1, arg2, arg3]); +el.publish( "/login", {username:"test", userData:"test"} ); + + +// Subscribe + +// jQuery: $(obj).on( "channel", [data], fn ); +$( el ).on( "/login", function( event ){...} ); + +// Dojo: dojo.subscribe( "channel", fn); +var handle = dojo.subscribe( "/login", function(data){..} ); + +// YUI: el.on("channel", handler); +el.on( "/login", function( data ){...} ); + + +// Unsubscribe + +// jQuery: $(obj).off( "channel" ); +$( el ).off( "/login" ); + +// Dojo: dojo.unsubscribe( handle ); +dojo.unsubscribe( handle ); + +// YUI: el.detach("channel"); +el.detach( "/login" ); + +//********************** Snippet 2 **********************// + +var pubsub = {}; + +(function(myObject) { + + // Storage for topics that can be broadcast + // or listened to + var topics = {}; + + // A topic identifier + var subUid = -1; + + // Publish or broadcast events of interest + // with a specific topic name and arguments + // such as the data to pass along + myObject.publish = function( topic, args ) { + + if ( !topics[topic] ) { + return false; + } + + var subscribers = topics[topic], + len = subscribers ? subscribers.length : 0; + + while (len--) { + subscribers[len].func( topic, args ); + } + + return this; + }; + + // Subscribe to events of interest + // with a specific topic name and a + // callback function, to be executed + // when the topic/event is observed + myObject.subscribe = function( topic, func ) { + + if (!topics[topic]) { + topics[topic] = []; + } + + var token = ( ++subUid ).toString(); + topics[topic].push({ + token: token, + func: func + }); + return token; + }; + + // Unsubscribe from a specific + // topic, based on a tokenized reference + // to the subscription + myObject.unsubscribe = function( token ) { + for ( var m in topics ) { + if ( topics[m] ) { + for ( var i = 0, j = topics[m].length; i < j; i++ ) { + if ( topics[m][i].token === token ) { + topics[m].splice( i, 1 ); + return token; + } + } + } + } + return this; + }; +}( pubsub )); + +//********************** Snippet 3 **********************// + +// Another simple message handler + +// A simple message logger that logs any topics and data received through our +// subscriber +var messageLogger = function ( topics, data ) { + console.log( "Logging: " + topics + ": " + data ); +}; + +// Subscribers listen for topics they have subscribed to and +// invoke a callback function (e.g messageLogger) once a new +// notification is broadcast on that topic +var subscription = pubsub.subscribe( "inbox/newMessage", messageLogger ); + +// Publishers are in charge of publishing topics or notifications of +// interest to the application. e.g: + +pubsub.publish( "inbox/newMessage", "hello world!" ); + +// or +pubsub.publish( "inbox/newMessage", ["test", "a", "b", "c"] ); + +// or +pubsub.publish( "inbox/newMessage", { + sender: "hello@google.com", + body: "Hey again!" +}); + +// We can also unsubscribe if we no longer wish for our subscribers +// to be notified +pubsub.unsubscribe( subscription ); + +// Once unsubscribed, this for example won't result in our +// messageLogger being executed as the subscriber is +// no longer listening +pubsub.publish( "inbox/newMessage", "Hello! are you still there?" ); + +//********************** Snippet 4 **********************// + +// Return the current local time to be used in our UI later +getCurrentTime = function (){ + + var date = new Date(), + m = date.getMonth() + 1, + d = date.getDate(), + y = date.getFullYear(), + t = date.toLocaleTimeString().toLowerCase(); + + return (m + "/" + d + "/" + y + " " + t); + }; + + // Add a new row of data to our fictional grid component + function addGridRow( data ) { + + // ui.grid.addRow( data ); + console.log( "updated grid component with:" + data ); + + } + + // Update our fictional grid to show the time it was last + // updated + function updateCounter( data ) { + + // ui.grid.updateLastChanged( getCurrentTime() ); + console.log( "data last updated at: " + getCurrentTime() + " with " + data); + + } + + // Update the grid using the data passed to our subscribers + gridUpdate = function( topic, data ){ + + if ( data !== undefined ) { + addGridRow( data ); + updateCounter( data ); + } + + }; + + // Create a subscription to the newDataAvailable topic + var subscriber = pubsub.subscribe( "newDataAvailable", gridUpdate ); + + // The following represents updates to our data layer. This could be + // powered by ajax requests which broadcast that new data is available + // to the rest of the application. + + // Publish changes to the gridUpdated topic representing new entries + pubsub.publish( "newDataAvailable", { + summary: "Apple made $5 billion", + identifier: "APPL", + stockPrice: 570.91 + }); + + pubsub.publish( "newDataAvailable", { + summary: "Microsoft made $20 million", + identifier: "MSFT", + stockPrice: 30.85 + }); + +//********************** Snippet 5 **********************// + + + + + + ++ + +
++ + +
++ + + +
++ + +
+Searched for:" + tags + "
"); + }); + + // Subscribe to the new results topic + $.subscribe( "/search/resultSet", function( e, results ){ + + $( "#searchResults" ).empty().append(resultTemplate( results )); + + }); + + // Submit a search query and publish tags on the /search/tags topic + $( "#flickrSearch" ).submit( function( e ) { + + e.preventDefault(); + var tags = $(this).find( "#query").val(); + + if ( !tags ){ + return; + } + + $.publish( "/search/tags", [ $.trim(tags) ]); + + }); + + + // Subscribe to new tags being published and perform + // a search query using them. Once data has returned + // publish this data for the rest of the application + // to consume + + $.subscribe("/search/tags", function( e, tags ) { + + $.getJSON( "http://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?", { + tags: tags, + tagmode: "any", + format: "json" + }, + + function( data ){ + + if( !data.items.length ) { + return; + } + + $.publish( "/search/resultSet", { items: data.items } ); + }); + + }); + + + })( jQuery ); \ No newline at end of file diff --git a/book/snippets/01-JavaScript-Design-Patterns/6-the-mediator-pattern.es2015.js b/book/snippets/01-JavaScript-Design-Patterns/6-the-mediator-pattern.es2015.js new file mode 100644 index 00000000..91bb9187 --- /dev/null +++ b/book/snippets/01-JavaScript-Design-Patterns/6-the-mediator-pattern.es2015.js @@ -0,0 +1,67 @@ +//********************** Snippet 1 **********************// +// [ES2015+] We used new keyword const for immutable constant declaration + +const mediator = {}; + +//********************** Snippet 2 **********************// +// [ES2015+] We used new keyword const for immutable constant declaration +// [ES2015+] We used new arrow function syntax + +const orgChart = { + addNewEmployee() { + // getEmployeeDetail provides a view that users interact with + const employeeDetail = this.getEmployeeDetail(); + + // when the employee detail is complete, the mediator (the 'orgchart' object) + // decides what should happen next + // [ES2015+] Parentheses are optional when there is only one parameter + employeeDetail.on('complete', employee => { + // set up additional objects that have additional events, which are used + // by the mediator to do additional things + // [ES2015+] Parentheses are optional when there is only one parameter + const managerSelector = this.selectManager(employee); + managerSelector.on('save', employee => { + employee.save(); + }); + }); + }, + + // ... +}; + +//********************** Snippet 3 **********************// +// [ES2015+] We used new keyword const for immutable constant declaration +// [ES2015+] Below we used new class declaration, using keyword class +// [ES2015+] We used new constructor method and method declaration +// [ES2015+] Classes are syntactic sugar over JavaScript's prototype-based inheritance +// [ES2015+] We used new template literals for string interpolation + +const MenuItem = MyFrameworkView.extend({ + events: { + 'click .thatThing': 'clickedIt', + }, + + clickedIt(e) { + e.preventDefault(); + + // assume this triggers "menu:click:foo" + MyFramework.trigger(`menu:click:${this.model.get('name')}`); + }, +}); + +// ... somewhere else in the app + +class MyWorkflow { + constructor() { + MyFramework.on('menu:click:foo', this.doStuff, this); + } + + // [ES2015+] The static keyword defines a static method for a class. + // [ES2015+] Static methods are called without instantiating their class and cannot be called through a class instance. + // [ES2015+] Static methods are often used to create utility functions for an application. + static doStuff() { + // instantiate multiple objects here. + // set up event handlers for those objects. + // coordinate all of the objects into a meaningful workflow. + } +} diff --git a/book/snippets/01-JavaScript-Design-Patterns/6-the-mediator-pattern.es5.js b/book/snippets/01-JavaScript-Design-Patterns/6-the-mediator-pattern.es5.js new file mode 100644 index 00000000..4b7aa1b5 --- /dev/null +++ b/book/snippets/01-JavaScript-Design-Patterns/6-the-mediator-pattern.es5.js @@ -0,0 +1,60 @@ +//********************** Snippet 1 **********************// + +var mediator = {}; + +//********************** Snippet 2 **********************// + +var orgChart = { + + addNewEmployee: function(){ + + // getEmployeeDetail provides a view that users interact with + var employeeDetail = this.getEmployeeDetail(); + + // when the employee detail is complete, the mediator (the 'orgchart' object) + // decides what should happen next + employeeDetail.on("complete", function(employee){ + + // set up additional objects that have additional events, which are used + // by the mediator to do additional things + var managerSelector = this.selectManager(employee); + managerSelector.on("save", function(employee){ + employee.save(); + }); + + }); + }, + + // ... + } + +//********************** Snippet 3 **********************// + +var MenuItem = MyFrameworkView.extend({ + + events: { + "click .thatThing": "clickedIt" + }, + + clickedIt: function(e){ + e.preventDefault(); + + // assume this triggers "menu:click:foo" + MyFramework.trigger("menu:click:" + this.model.get("name")); + } + + }); + + // ... somewhere else in the app + + var MyWorkflow = function(){ + MyFramework.on("menu:click:foo", this.doStuff, this); + }; + + MyWorkflow.prototype.doStuff = function(){ + // instantiate multiple objects here. + // set up event handlers for those objects. + // coordinate all of the objects into a meaningful workflow. + }; + + \ No newline at end of file diff --git a/book/snippets/01-JavaScript-Design-Patterns/7-the-prototype-pattern.es2015.js b/book/snippets/01-JavaScript-Design-Patterns/7-the-prototype-pattern.es2015.js new file mode 100644 index 00000000..163e85d7 --- /dev/null +++ b/book/snippets/01-JavaScript-Design-Patterns/7-the-prototype-pattern.es2015.js @@ -0,0 +1,94 @@ +//********************** Snippet 1 **********************// +// [ES2015+] We used new keyword const for immutable constant declaration + +const myCar = { + name: 'Ford Escort', + + drive() { + console.log("Weeee. I'm driving!"); + }, + + panic() { + console.log('Wait. How do you stop this thing?'); + }, +}; + +// Use Object.create to instantiate a new car +const yourCar = Object.create(myCar); + +// Now we can see that one is a prototype of the other +console.log(yourCar.name); + +//********************** Snippet 2 **********************// +// [ES2015+] We used new keyword const for immutable constant declaration +// [ES2015+] We used new template literals for string interpolation + +const vehicle = { + getModel() { + console.log(`The model of this vehicle is..${this.model}`); + }, +}; + +const car = Object.create(vehicle, { + id: { + value: MY_GLOBAL.nextId(), + // writable:false, configurable:false by default + enumerable: true, + }, + + model: { + value: 'Ford', + enumerable: true, + }, +}); + +//********************** Snippet 3 **********************// +// [ES2015+] Below we used new class declaration, using keyword class +// [ES2015+] We used new constructor method and method declaration +// [ES2015+] We have new pattern implementation with new inheritance +// [ES2015+] Classes are syntactic sugar over JavaScript's prototype-based inheritance +// [ES2015+] We used new keyword const for immutable constant declaration + +class VehiclePrototype { + constructor(model) { + this.model = model; + } + + getModel() { + console.log('The model of this vehicle is..' + this.model); + } + + Clone() {} +} +// [ES2015+] The extends keyword is used to create a class which is a child of another class. +// [ES2015+] A constructor can use the super keyword to call the constructor of the super class. +class Vehicle extends VehiclePrototype { + constructor(model) { + super(model); + } + Clone() { + return new Vehicle(this.model); + } +} + +const car = new Vehicle('Ford Escort'); +const car2 = car.Clone(); +car2.getModel(); + +//********************** Snippet 4 **********************// +// [ES2015+] We used new arrow function syntax +// [ES2015+] We used new keyword const for immutable constant declaration +// [ES2015+] Below we used new class declaration, using keyword class +// [ES2015+] We used new constructor method +// [ES2015+] We still could use Object.prototype for adding new methods, because internally we use the same structure + +const beget = (() => { + class F { + constructor() {} + } + + return proto => { + F.prototype = proto; + return new F(); + }; +})(); diff --git a/book/snippets/01-JavaScript-Design-Patterns/7-the-prototype-pattern.es5.js b/book/snippets/01-JavaScript-Design-Patterns/7-the-prototype-pattern.es5.js new file mode 100644 index 00000000..98c25443 --- /dev/null +++ b/book/snippets/01-JavaScript-Design-Patterns/7-the-prototype-pattern.es5.js @@ -0,0 +1,85 @@ +//********************** Snippet 1 **********************// + +var myCar = { + + name: "Ford Escort", + + drive: function () { + console.log( "Weeee. I'm driving!" ); + }, + + panic: function () { + console.log( "Wait. How do you stop this thing?" ); + } + +}; + +// Use Object.create to instantiate a new car +var yourCar = Object.create( myCar ); + +// Now we can see that one is a prototype of the other +console.log( yourCar.name ); + +//********************** Snippet 2 **********************// + +var vehicle = { + getModel: function () { + console.log( "The model of this vehicle is.." + this.model ); + } +}; + +var car = Object.create(vehicle, { + + "id": { + value: MY_GLOBAL.nextId(), + // writable:false, configurable:false by default + enumerable: true + }, + + "model": { + value: "Ford", + enumerable: true + } + +}); + +//********************** Snippet 3 **********************// + +var vehiclePrototype = { + + init: function ( carModel ) { + this.model = carModel; + }, + + getModel: function () { + console.log( "The model of this vehicle is.." + this.model); + } +}; + + +function vehicle( model ) { + + function F() {}; + F.prototype = vehiclePrototype; + + var f = new F(); + + f.init( model ); + return f; + +} + +var car = vehicle( "Ford Escort" ); +car.getModel(); + +//********************** Snippet 4 **********************// + +var beget = (function () { + + function F() {} + + return function ( proto ) { + F.prototype = proto; + return new F(); + }; +})(); diff --git a/book/snippets/01-JavaScript-Design-Patterns/8-the-command-pattern.es2015.js b/book/snippets/01-JavaScript-Design-Patterns/8-the-command-pattern.es2015.js new file mode 100644 index 00000000..46a4244a --- /dev/null +++ b/book/snippets/01-JavaScript-Design-Patterns/8-the-command-pattern.es2015.js @@ -0,0 +1,43 @@ +//********************** Snippet 1 **********************// +// [ES2015+] We used new keyword const for immutable constant declaration +// [ES2015+] We used new template literals for string interpolation +// [ES2015+] We used new arrow function syntax + +(() => { + const carManager = { + // request information + requestInfo(model, id) { + return `The information for ${model} with ID ${id} is foobar`; + }, + + // purchase the car + buyVehicle(model, id) { + return `You have successfully purchased Item ${id}, a ${model}`; + }, + + // arrange a viewing + arrangeViewing(model, id) { + return `You have successfully booked a viewing of ${model} ( ${id} ) `; + }, + }; +})(); + +//********************** Snippet 2 **********************// + +carManager.execute('buyVehicle', 'Ford Escort', '453543'); + +//********************** Snippet 3 **********************// + +carManager.execute = function(name) { + return ( + carManager[name] && + carManager[name].apply(carManager, [].slice.call(arguments, 1)) + ); +}; + +//********************** Snippet 4 **********************// + +carManager.execute('arrangeViewing', 'Ferrari', '14523'); +carManager.execute('requestInfo', 'Ford Mondeo', '54323'); +carManager.execute('requestInfo', 'Ford Escort', '34232'); +carManager.execute('buyVehicle', 'Ford Escort', '34232'); diff --git a/book/snippets/01-JavaScript-Design-Patterns/8-the-command-pattern.es5.js b/book/snippets/01-JavaScript-Design-Patterns/8-the-command-pattern.es5.js new file mode 100644 index 00000000..c6726361 --- /dev/null +++ b/book/snippets/01-JavaScript-Design-Patterns/8-the-command-pattern.es5.js @@ -0,0 +1,41 @@ +//********************** Snippet 1 **********************// + +(function(){ + + var carManager = { + + // request information + requestInfo: function( model, id ){ + return "The information for " + model + " with ID " + id + " is foobar"; + }, + + // purchase the car + buyVehicle: function( model, id ){ + return "You have successfully purchased Item " + id + ", a " + model; + }, + + // arrange a viewing + arrangeViewing: function( model, id ){ + return "You have successfully booked a viewing of " + model + " ( " + id + " ) "; + } + + }; + +})(); + +//********************** Snippet 2 **********************// + +carManager.execute( "buyVehicle", "Ford Escort", "453543" ); + +//********************** Snippet 3 **********************// + +carManager.execute = function ( name ) { + return carManager[name] && carManager[name].apply( carManager, [].slice.call(arguments, 1) ); +}; + +//********************** Snippet 4 **********************// + +carManager.execute( "arrangeViewing", "Ferrari", "14523" ); +carManager.execute( "requestInfo", "Ford Mondeo", "54323" ); +carManager.execute( "requestInfo", "Ford Escort", "34232" ); +carManager.execute( "buyVehicle", "Ford Escort", "34232" ); \ No newline at end of file diff --git a/book/snippets/01-JavaScript-Design-Patterns/9-the-facade-pattern.es2015.js b/book/snippets/01-JavaScript-Design-Patterns/9-the-facade-pattern.es2015.js new file mode 100644 index 00000000..74036f3f --- /dev/null +++ b/book/snippets/01-JavaScript-Design-Patterns/9-the-facade-pattern.es2015.js @@ -0,0 +1,75 @@ +//********************** Snippet 1 **********************// +// [ES2015+] We used new template literals for string interpolation +// [ES2015+] We used new keyword const for immutable constant declaration +// [ES2015+] We used new arrow function syntax + +const addMyEvent = (el, ev, fn) => { + if (el.addEventListener) { + el.addEventListener(ev, fn, false); + } else if (el.attachEvent) { + el.attachEvent(`on${ev}`, fn); + } else { + el[`on${ev}`] = fn; + } + }; + +//********************** Snippet 2 **********************// + +bindReady() { + ... + if (document.addEventListener) { + // Use the handy event callback + document.addEventListener('DOMContentLoaded', DOMContentLoaded, false); + + // A fallback to window.onload, that will always work + window.addEventListener('load', jQuery.ready, false); + + // If IE event model is used + } else if (document.attachEvent) { + + document.attachEvent('onreadystatechange', DOMContentLoaded); + + // A fallback to window.onload, that will always work + window.attachEvent('onload', jQuery.ready); + ... + + +//********************** Snippet 3 **********************// +// [ES2015+] We have new pattern implementation with new keywords import and export + +const _private = { + i: 5, + get() { + console.log(`current value:${this.i}`); + }, + set(val) { + this.i = val; + }, + run() { + console.log('running'); + }, + jump() { + console.log('jumping'); + }, + }; + + // [ES2015+] We used the destructuring assignment syntax that makes it possible to unpack values from data structures into distinct variables. + const module = { + facade({ val, run }) { + _private.set(val); + _private.get(); + if (run) { + _private.run(); + } + }, + }; + // [ES2015+] Default export module, without name + export default module; + + // [ES2015+] The import statement is used to import bindings which are exported by another module. + import module from './module'; + // Outputs: "current value: 10" and "running" + module.facade({ + run: true, + val: 10, + }); diff --git a/book/snippets/01-JavaScript-Design-Patterns/9-the-facade-pattern.es5.js b/book/snippets/01-JavaScript-Design-Patterns/9-the-facade-pattern.es5.js new file mode 100644 index 00000000..caa196d2 --- /dev/null +++ b/book/snippets/01-JavaScript-Design-Patterns/9-the-facade-pattern.es5.js @@ -0,0 +1,70 @@ +//********************** Snippet 1 **********************// + +var addMyEvent = function( el,ev,fn ){ + + if( el.addEventListener ){ + el.addEventListener( ev,fn, false ); + }else if(el.attachEvent){ + el.attachEvent( "on" + ev, fn ); + } else{ + el["on" + ev] = fn; + } + +}; + + +//********************** Snippet 2 **********************// +bindReady: function() { + ... + if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", jQuery.ready, false ); + + // If IE event model is used + } else if ( document.attachEvent ) { + + document.attachEvent( "onreadystatechange", DOMContentLoaded ); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", jQuery.ready ); + ... + + + +//********************** Snippet 3 **********************// +var module = (function() { + + var _private = { + i: 5, + get: function() { + console.log( "current value:" + this.i); + }, + set: function( val ) { + this.i = val; + }, + run: function() { + console.log( "running" ); + }, + jump: function(){ + console.log( "jumping" ); + } + }; + + return { + + facade: function( args ) { + _private.set(args.val); + _private.get(); + if ( args.run ) { + _private.run(); + } + } + }; +}()); + + +// Outputs: "current value: 10" and "running" +module.facade( {run: true, val: 10} ); \ No newline at end of file diff --git a/book/snippets/02-JavaScript-MV*-Patterns/14-MVC.es2015.js b/book/snippets/02-JavaScript-MV*-Patterns/14-MVC.es2015.js new file mode 100644 index 00000000..7eb0a832 --- /dev/null +++ b/book/snippets/02-JavaScript-MV*-Patterns/14-MVC.es2015.js @@ -0,0 +1,137 @@ +//*******************************************************// +// Models +//*******************************************************// +// [ES2015+] We used new keyword const for immutable constant declaration + +//********************** Snippet 1 **********************// +const Photo = Backbone.Model.extend({ + // Default attributes for the photo + defaults: { + src: 'placeholder.jpg', + caption: 'A default image', + viewed: false, + }, + + // Ensure that each photo created has an `src`. + initialize: function() { + this.set({ src: this.defaults.src }); + }, +}); + +//********************** Snippet 2 **********************// +const PhotoGallery = Backbone.Collection.extend({ + // Reference to this collection's model. + model: Photo, + + // Filter down the list of all photos + // that have been viewed + viewed: function() { + return this.filter(function(photo) { + return photo.get('viewed'); + }); + }, + + // Filter down the list to only photos that + // have not yet been viewed + unviewed: function() { + return this.without.apply(this, this.viewed()); + }, +}); + +//*******************************************************// +// Models +//*******************************************************// + +//********************** Snippet 1 **********************// +// [ES2015+] We used new arrow function syntax + +const buildPhotoView = (photoModel, photoController) => { + const base = document.createElement('div'); + const photoEl = document.createElement('div'); + + base.appendChild(photoEl); + + const render = () => { + // We use a templating library such as Underscore + // templating which generates the HTML for our + // photo entry + photoEl.innerHTML = _.template('#photoTemplate', { + src: photoModel.getSrc(), + }); + }; + + photoModel.addSubscriber(render); + + photoEl.addEventListener('click', () => { + photoController.handleEvent('click', photoModel); + }); + + const show = () => { + photoEl.style.display = ''; + }; + + const hide = () => { + photoEl.style.display = 'none'; + }; + + return { + showView: show, + hideView: hide, + }; +}; + + +//********************** Snippet 2 **********************// +Contact name:
+ +//********************** Snippet 3 **********************// + + + + +//*******************************************************// +// ViewModel +//*******************************************************// + +//********************** Snippet 1 **********************// +// our main ViewModel + var ViewModel = function ( todos ) { + var self = this; + + // map array of passed in todos to an observableArray of Todo objects + self.todos = ko.observableArray( + ko.utils.arrayMap( todos, function ( todo ) { + return new Todo( todo.content, todo.done ); + })); + + // store the new todo value being entered + self.current = ko.observable(); + + // add a new todo, when enter key is pressed + self.add = function ( data, event ) { + var newTodo, current = self.current().trim(); + if ( current ) { + newTodo = new Todo( current ); + self.todos.push( newTodo ); + self.current(""); + } + }; + + // remove a single todo + self.remove = function ( todo ) { + self.todos.remove( todo ); + }; + + // remove all completed todos + self.removeCompleted = function () { + self.todos.remove(function (todo) { + return todo.done(); + }); + }; + + // writeable computed observable to handle marking all complete/incomplete + self.allCompleted = ko.computed({ + + // always return true/false based on the done flag of all todos + read:function () { + return !self.remainingCount(); + }, + + // set all todos to the written value (true/false) + write:function ( newValue ) { + ko.utils.arrayForEach( self.todos(), function ( todo ) { + //set even if value is the same, as subscribers are not notified in that case + todo.done( newValue ); + }); + } + }); + + // edit an item + self.editItem = function( item ) { + item.editing( true ); + }; +.. + +//********************** Snippet 2 **********************// +// Define an initially an empty array +var myObservableArray = ko.observableArray(); + +// Add a value to the array and notify our observers +myObservableArray.push( 'A new todo item' ); diff --git a/book/snippets/02-JavaScript-MV*-Patterns/17-MVVM-with-looser-data-bindings.es5-no-changes.js b/book/snippets/02-JavaScript-MV*-Patterns/17-MVVM-with-looser-data-bindings.es5-no-changes.js new file mode 100644 index 00000000..75e466d8 --- /dev/null +++ b/book/snippets/02-JavaScript-MV*-Patterns/17-MVVM-with-looser-data-bindings.es5-no-changes.js @@ -0,0 +1,192 @@ +//********************** Snippet 1 **********************// + + +//********************** Snippet 2 **********************// +var ourBindingProvider = { + nodeHasBindings: function( node ) { + // returns true/false + }, + + getBindings: function( node, bindingContext ) { + // returns a binding object + } +}; + +//********************** Snippet 3 **********************// +// does an element have any bindings? +function nodeHasBindings( node ) { + return node.getAttribute ? node.getAttribute("data-class") : false; +}; + +//********************** Snippet 4 **********************// +var viewModel = new ViewModel( todos || [] ), + bindings = { + + newTodo: { + value: viewModel.current, + valueUpdate: "afterkeydown", + enterKey: viewModel.add + }, + + taskTooltip: { + visible: viewModel.showTooltip + }, + checkAllContainer: { + visible: viewModel.todos().length + }, + checkAll: { + checked: viewModel.allCompleted + }, + + todos: { + foreach: viewModel.todos + }, + todoListItem: function() { + return { + css: { + editing: this.editing + } + }; + }, + todoListItemWrapper: function() { + return { + css: { + done: this.done + } + }; + }, + todoCheckBox: function() { + return { + checked: this.done + }; + }, + todoContent: function() { + return { + text: this.content, + event: { + dblclick: this.edit + } + }; + }, + todoDestroy: function() { + return { + click: viewModel.remove + }; + }, + + todoEdit: function() { + return { + value: this.content, + valueUpdate: "afterkeydown", + enterKey: this.stopEditing, + event: { + blur: this.stopEditing + } + }; + }, + + todoCount: { + visible: viewModel.remainingCount + }, + remainingCount: { + text: viewModel.remainingCount + }, + remainingCountWord: function() { + return { + text: viewModel.getLabel(viewModel.remainingCount) + }; + }, + todoClear: { + visible: viewModel.completedCount + }, + todoClearAll: { + click: viewModel.removeCompleted + }, + completedCount: { + text: viewModel.completedCount + }, + completedCountWord: function() { + return { + text: viewModel.getLabel(viewModel.completedCount) + }; + }, + todoInstructions: { + visible: viewModel.todos().length + } + }; + + .... + +//********************** Snippet 5 **********************// + // We can now create a bindingProvider that uses + // something different than data-bind attributes + ko.customBindingProvider = function( bindingObject ) { + this.bindingObject = bindingObject; + + // determine if an element has any bindings + this.nodeHasBindings = function( node ) { + return node.getAttribute ? node.getAttribute( "data-class" ) : false; + }; + }; + + // return the bindings given a node and the bindingContext + this.getBindings = function( node, bindingContext ) { + + var result = {}, + classes = node.getAttribute( "data-class" ); + + if ( classes ) { + classes = classes.split( "" ); + + //evaluate each class, build a single object to return + for ( var i = 0, j = classes.length; i < j; i++ ) { + + var bindingAccessor = this.bindingObject[classes[i]]; + if ( bindingAccessor ) { + var binding = typeof bindingAccessor === "function" ? bindingAccessor.call(bindingContext.$data) : bindingAccessor; + ko.utils.extend(result, binding); + } + + } + } + + return result; + }; +}; + + +//********************** Snippet 6 **********************// +// set ko's current bindingProvider equal to our new binding provider +ko.bindingProvider.instance = new ko.customBindingProvider( bindings ); + +// bind a new instance of our ViewModel to the page +ko.applyBindings( viewModel ); + +})(); + +//********************** Snippet 7 **********************// +foo bar
').appendTo('body'); + +const newParagraph = $('').text('Hello world'); + +$('') + .attr({ type: 'text', id: 'sample' }) + .appendTo('#container'); + +//********************** Snippet 1 **********************// + +// HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + doc = ( context ? context.ownerDocument || context : document ); + + // If a single string is passed in and it's a single tag + // just do a createElement and skip the rest + ret = rsingleTag.exec( selector ); + + if ( ret ) { + if ( jQuery.isPlainObject( context ) ) { + selector = [ document.createElement( ret[1] ) ]; + jQuery.fn.attr.call( selector, context, true ); + + } else { + selector = [ doc.createElement( ret[1] ) ]; + } + + } else { + ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); + selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes; + } + + return jQuery.merge( this, selector ); diff --git a/book/snippets/04-Design-Patterns-In-jQuery/29-the-builder-pattern.es5.js b/book/snippets/04-Design-Patterns-In-jQuery/29-the-builder-pattern.es5.js new file mode 100644 index 00000000..05994d8b --- /dev/null +++ b/book/snippets/04-Design-Patterns-In-jQuery/29-the-builder-pattern.es5.js @@ -0,0 +1,37 @@ +//********************** Snippet 1 **********************// +$( 'foo bar
').appendTo("body"); + +var newParagraph = $( "" ).text( "Hello world" ); + +$( "" ) + .attr({ "type": "text", "id":"sample"}) + .appendTo("#container"); + +//********************** Snippet 1 **********************// + +// HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + doc = ( context ? context.ownerDocument || context : document ); + + // If a single string is passed in and it's a single tag + // just do a createElement and skip the rest + ret = rsingleTag.exec( selector ); + + if ( ret ) { + if ( jQuery.isPlainObject( context ) ) { + selector = [ document.createElement( ret[1] ) ]; + jQuery.fn.attr.call( selector, context, true ); + + } else { + selector = [ doc.createElement( ret[1] ) ]; + } + + } else { + ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); + selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes; + } + + return jQuery.merge( this, selector ); diff --git a/book/snippets/04-Design-Patterns-In-jQuery/30-jQuery-plugin-design-patterns.es2015.js b/book/snippets/04-Design-Patterns-In-jQuery/30-jQuery-plugin-design-patterns.es2015.js new file mode 100644 index 00000000..a7b757c3 --- /dev/null +++ b/book/snippets/04-Design-Patterns-In-jQuery/30-jQuery-plugin-design-patterns.es2015.js @@ -0,0 +1,907 @@ +//*******************************************************// +// Patterns +//*******************************************************// +// [ES2015+] We used new arrow function syntax + +//********************** Snippet 1 **********************// +$.fn.myPluginName = () => { + // our plugin logic + }; + + //********************** Snippet 2 **********************// + ($ => { + fn.myPluginName = () => { + // our plugin logic + }; + })(jQuery); + + //********************** Snippet 3 **********************// + ($ => { + $.extend($.fn, { + myplugin() { + // your plugin logic + }, + }); + })(jQuery); + + +//*******************************************************// +// 'A Lightweight Start' Pattern +//*******************************************************// +// [ES2015+] We used new arrow function syntax +// [ES2015+] We used new keyword const for immutable constant declaration +// [ES2015+] Below we used new class declaration, using keyword class +// [ES2015+] We used new constructor method and method declaration +// [ES2015+] Classes are syntactic sugar over JavaScript's prototype-based inheritance + +//********************** Snippet 1 **********************// +/*! + * jQuery lightweight plugin boilerplate + * Original author: @ajpiano + * Further changes, comments: @addyosmani + * Licensed under the MIT license + */ + +// the semi-colon before the function invocation is a safety +// net against concatenated scripts and/or other plugins +// that are not closed properly. +;(($, window, document, undefined) => { + // undefined is used here as the undefined global + // variable in ECMAScript 3 and is mutable (i.e. it can + // be changed by someone else). undefined isn't really + // being passed in so we can ensure that its value is + // truly undefined. In ES5, undefined can no longer be + // modified. + + // window and document are passed through as local + // variables rather than as globals, because this (slightly) + // quickens the resolution process and can be more + // efficiently minified (especially when both are + // regularly referenced in our plugin). + + // Create the defaults once + const pluginName = 'defaultPluginName'; + + const defaults = { + propertyName: 'value', + }; + + // The actual plugin constructor + class Plugin { + constructor(element, options) { + this.element = element; + + // jQuery has an extend method that merges the + // contents of two or more objects, storing the + // result in the first object. The first object + // is generally empty because we don't want to alter + // the default options for future instances of the plugin + this.options = $.extend({}, defaults, options); + + this._defaults = defaults; + this._name = pluginName; + + this.init(); + } + + init() { + // Place initialization logic here + // We already have access to the DOM element and + // the options via the instance, e.g. this.element + // and this.options + } + } + + // A really lightweight plugin wrapper around the constructor, + // preventing against multiple instantiations + $.fn[pluginName] = function(options) { + return this.each(function() { + if (!$.data(this, `plugin_${pluginName}`)) { + $.data(this, `plugin_${pluginName}`, new Plugin(this, options)); + } + }); + }; + })(jQuery, window, document); + +//********************** Snippet 2 **********************// +$('#elem').defaultPluginName({ + propertyName: 'a custom value', +}); + + +//*******************************************************// +// “Complete” Widget Factory Pattern +//*******************************************************// +// [ES2015+] We used new arrow function syntax +// [ES2015+] We used new object method declaration + +//********************** Snippet 1 **********************// +/*! + * jQuery UI Widget-factory plugin boilerplate (for 1.8/9+) + * Author: @addyosmani + * Further changes: @peolanha + * Licensed under the MIT license + */ + +;(($, window, document, undefined) => { + // define our widget under a namespace of your choice + // with additional parameters e.g. + // $.widget( "namespace.widgetname", (optional) - an + // existing widget prototype to inherit from, an object + // literal to become the widget's prototype ); + + $.widget('namespace.widgetname', { + //Options to be used as defaults + options: { + someValue: null, + }, + + //Setup widget (e.g. element creation, apply theming + //, bind events etc.) + _create() { + // _create will automatically run the first time + // this widget is called. Put the initial widget + // setup code here, then we can access the element + // on which the widget was called via this.element. + // The options defined above can be accessed + // via this.options this.element.addStuff(); + }, + + // Destroy an instantiated plugin and clean up + // modifications the widget has made to the DOM + destroy() { + // this.element.removeStuff(); + // For UI 1.8, destroy must be invoked from the + // base widget + $.Widget.prototype.destroy.call(this); + // For UI 1.9, define _destroy instead and don't + // worry about + // calling the base widget + }, + + methodB(event) { + //_trigger dispatches callbacks the plugin user + // can subscribe to + // signature: _trigger( "callbackName", [eventObject], + // [uiObject] ) + // e.g. this._trigger( "hover", e /*where e.type == + // "mouseenter"*/, { hovered: $(e.target)}); + this._trigger('methodA', event, { + key: value, + }); + }, + + methodA(event) { + this._trigger('dataChanged', event, { + key: value, + }); + }, + + // Respond to any changes the user makes to the + // option method + _setOption(key, value) { + switch (key) { + case 'someValue': + // this.options.someValue = doSomethingWith( value ); + break; + default: + // this.options[ key ] = value; + break; + } + + // For UI 1.8, _setOption must be manually invoked + // from the base widget + $.Widget.prototype._setOption.apply(this, arguments); + // For UI 1.9 the _super method can be used instead + // this._super( "_setOption", key, value ); + }, + }); + })(jQuery, window, document); + +//********************** Snippet 2 **********************// +const collection = $('#elem').widgetName({ + foo: false, +}); + +collection.widgetName('methodB'); + +//*******************************************************// +// Nested Namespacing Plugin Pattern +//*******************************************************// +// [ES2015+] We used new arrow function syntax +// [ES2015+] We used new keyword const for immutable constant declaration + +//********************** Snippet 1 **********************// +/*! + * jQuery namespaced "Starter" plugin boilerplate + * Author: @dougneiner + * Further changes: @addyosmani + * Licensed under the MIT license + */ + +;($ => { + if (!$.myNamespace) { + $.myNamespace = {}; + } + + $.myNamespace.myPluginName = function(el, myFunctionParam, options) { + // To avoid scope issues, use "base" instead of "this" + // to reference this class from internal events and functions. + const base = this; + + // Access to jQuery and DOM versions of element + base.$el = $(el); + base.el = el; + + // Add a reverse reference to the DOM object + base.$el.data('myNamespace.myPluginName', base); + + base.init = () => { + base.myFunctionParam = myFunctionParam; + + base.options = $.extend( + {}, + $.myNamespace.myPluginName.defaultOptions, + options + ); + + // Put our initialization code here + }; + + // Sample Function, Uncomment to use + // base.functionName = function( parameters ){ + // + // }; + // Run initializer + base.init(); + }; + + $.myNamespace.myPluginName.defaultOptions = { + myDefaultValue: '', + }; + + $.fn.mynamespace_myPluginName = function(myFunctionParam, options) { + return this.each(function() { + new $.myNamespace.myPluginName(this, myFunctionParam, options); + }); + }; + })(jQuery); + +//********************** Snippet 2 **********************// +$('#elem').mynamespace_myPluginName({ + myDefaultValue: 'foobar', +}); + + +//*******************************************************// +// Custom Events Plugin Pattern (With The Widget factory) +//*******************************************************// +// [ES2015+] We used new arrow function syntax +// [ES2015+] We used new keyword const for immutable constant declaration +// [ES2015+] We used new object method declaration + +//********************** Snippet 1 **********************// +/*! + * jQuery custom-events plugin boilerplate + * Author: DevPatch + * Further changes: @addyosmani + * Licensed under the MIT license + */ + +// In this pattern, we use jQuery's custom events to add +// pub/sub (publish/subscribe) capabilities to widgets. +// Each widget would publish certain events and subscribe +// to others. This approach effectively helps to decouple +// the widgets and enables them to function independently. + +;(($, window, document, undefined) => { + $.widget('ao.eventStatus', { + options: {}, + + _create() { + const self = this; + + //self.element.addClass( "my-widget" ); + + //subscribe to "myEventStart" + self.element.on('myEventStart', e => { + console.log('event start'); + }); + + //subscribe to "myEventEnd" + self.element.on('myEventEnd', e => { + console.log('event end'); + }); + + //unsubscribe to "myEventStart" + //self.element.off( "myEventStart", function(e){ + ///console.log( "unsubscribed to this event" ); + //}); + }, + + // [ES2015+] The rest parameter syntax allows us to represent an indefinite number of arguments as an array. + destroy(...args) { + $.Widget.prototype.destroy.apply(this, args); + }, + }); + })(jQuery, window, document); + + // Publishing event notifications + // $( ".my-widget" ).trigger( "myEventStart"); + // $( ".my-widget" ).trigger( "myEventEnd" ); + +//********************** Snippet 2 **********************// +const el = $('#elem'); +el.eventStatus(); +el.eventStatus().trigger('myEventStart'); + + +//*******************************************************// +// Prototypal Inheritance With The DOM-To-Object Bridge Pattern +//*******************************************************// +// [ES2015+] We used new keyword const for immutable constant declaration +// [ES2015+] We used new object method declaration + +//********************** Snippet 1 **********************// +/*! + * jQuery prototypal inheritance plugin boilerplate + * Author: Alex Sexton, Scott Gonzalez + * Further changes: @addyosmani + * Licensed under the MIT license + */ + +// myObject - an object representing a concept we wish to model +// (e.g. a car) +const myObject = { + init(options, elem) { + // Mix in the passed-in options with the default options + this.options = $.extend({}, this.options, options); + + // Save the element reference, both as a jQuery + // reference and a normal reference + this.elem = elem; + this.$elem = $(elem); + + // Build the DOM's initial structure + this._build(); + + // return this so that we can chain and use the bridge with less code. + return this; + }, + options: { + name: 'No name', + }, + _build() { + //this.$elem.html( ""+msg+"
" ); + }, + }; + + // Object.create support test, and fallback for browsers without it + if (typeof Object.create !== 'function') { + // [ES2015+] Parentheses are optional when there is only one parameter + Object.create = o => { + function F() {} + F.prototype = o; + return new F(); + }; + } + + // Create a plugin based on a defined object + $.plugin = (name, object) => { + $.fn[name] = function(options) { + return this.each(function() { + if (!$.data(this, name)) { + $.data(this, name, Object.create(object).init(options, this)); + } + }); + }; + }; + +//********************** Snippet 2 **********************// +$.plugin('myobj', myObject); + +$('#elem').myobj({ name: 'John' }); + +const collection = $('#elem').data('myobj'); +collection.myMethod('I am a method'); + + + +//*******************************************************// +// jQuery UI Widget Factory Bridge Pattern +//*******************************************************// +// [ES2015+] Below we used new class declaration, using keyword class +// [ES2015+] We used new constructor method and method declaration +// [ES2015+] Classes are syntactic sugar over JavaScript's prototype-based inheritance + +//********************** Snippet 1 **********************// +/*! + * jQuery UI Widget factory "bridge" plugin boilerplate + * Author: @erichynds + * Further changes, additional comments: @addyosmani + * Licensed under the MIT license + */ + +// a "WidgetName" object constructor +// required: this must accept two arguments, +// options: an object of configuration options +// element: the DOM element the instance was created on +class WidgetName { + constructor(options, element) { + this.name = 'myWidgetName'; + this.options = options; + this.element = element; + this._init(); + } + + // the "WidgetName" prototype + // _create will automatically run the first time this + // widget is called + _create() { + // creation code + } + + // required: initialization logic for the plugin goes into _init + // This fires when our instance is first created and when + // attempting to initialize the widget again (by the bridge) + // after it has already been initialized. + _init() { + // init code + } + + // required: objects to be used with the bridge must contain an + // "option". Post-initialization, the logic for changing options + // goes here. + option(key, value) { + // optional: get/change options post initialization + // ignore if you don't require them. + + // signature: $("#foo").bar({ cool:false }); + if ($.isPlainObject(key)) { + this.options = $.extend(true, this.options, key); + + // signature: $( "#foo" ).option( "cool" ); - getter + } else if (key && typeof value === 'undefined') { + return this.options[key]; + + // signature: $( "#foo" ).bar("option", "baz", false ); + } else { + this.options[key] = value; + } + + // required: option must return the current instance. + // When re-initializing an instance on elements, option + // is called first and is then chained to the _init method. + return this; + } + + // notice no underscore is used for public methods + publicFunction() { + console.log('public function'); + } + + // underscores are used for private methods + _privateFunction() { + console.log('private function'); + } + } + + //********************** Snippet 2 **********************// + // [ES2015+] We used new keyword const for immutable constant declaration + // connect the widget obj to jQuery's API under the "foo" namespace + $.widget.bridge('foo', WidgetName); + + // create an instance of the widget for use + const instance = $('#foo').foo({ + baz: true, + }); + + // our widget instance exists in the elem's data + // Outputs: #elem + console.log(instance.data('foo').element); + + // bridge allows us to call public methods + // Outputs: "public method" + instance.foo('publicFunction'); + + // bridge prevents calls to internal methods + instance.foo('_privateFunction'); + + +//*******************************************************// +// jQuery Mobile Widgets With The Widget factory +//*******************************************************// +// [ES2015+] We used new arrow function syntax +// [ES2015+] We used new object method declaration + +//********************** Snippet 1 **********************// +/*! + * (jQuery mobile) jQuery UI Widget-factory plugin boilerplate (for 1.8/9+) + * Author: @scottjehl + * Further changes: @addyosmani + * Licensed under the MIT license + */ + +;(($, window, document, undefined) => { + // define a widget under a namespace of our choice + // here "mobile" has been used in the first argument + $.widget('mobile.widgetName', $.mobile.widget, { + // Options to be used as defaults + options: { + foo: true, + bar: false, + }, + + _create() { + // _create will automatically run the first time this + // widget is called. Put the initial widget set-up code + // here, then we can access the element on which + // the widget was called via this.element + // The options defined above can be accessed via + // this.options + // var m = this.element, + // p = m.parents( ":jqmData(role="page")" ), + // c = p.find( ":jqmData(role="content")" ) + }, + + // Private methods/props start with underscores + _dosomething() { ... }, + + // Public methods like these below can can be called + // externally: + // $("#myelem").foo( "enable", arguments ); + + enable() { ... }, + + // Destroy an instantiated plugin and clean up modifications + // the widget has made to the DOM + destroy() { + // this.element.removeStuff(); + // For UI 1.8, destroy must be invoked from the + // base widget + $.Widget.prototype.destroy.call(this); + // For UI 1.9, define _destroy instead and don't + // worry about calling the base widget + }, + + methodB(event) { + //_trigger dispatches callbacks the plugin user can + // subscribe to + // signature: _trigger( "callbackName", [eventObject], + // [uiObject] ) + // e.g. this._trigger( "hover", e /*where e.type == + // "mouseenter"*/, { hovered: $(e.target)}); + this._trigger('methodA', event, { + key: value, + }); + }, + + methodA(event) { + this._trigger('dataChanged', event, { + key: value, + }); + }, + + // Respond to any changes the user makes to the option method + _setOption(key, value) { + switch (key) { + case 'someValue': + // this.options.someValue = doSomethingWith( value ); + break; + default: + // this.options[ key ] = value; + break; + } + + // For UI 1.8, _setOption must be manually invoked from + // the base widget + $.Widget.prototype._setOption.apply(this, arguments); + // For UI 1.9 the _super method can be used instead + // this._super( "_setOption", key, value ); + }, + }); + })(jQuery, window, document); + + //********************** Snippet 2 **********************// + // [ES2015+] We used new keyword const for immutable constant declaration + + const instance = $('#foo').widgetName({ + foo: false, + }); + + instance.widgetName('methodB'); + +//********************** Snippet 3 **********************// +// [ES2015+] We used the destructuring assignment syntax that makes it possible to unpack values from data structures into distinct variables. + +$(document).on('pagecreate', ({ target }) => { + // In here, e.target refers to the page that was created + // (it's the target of the pagecreate event) + // So, we can simply find elements on this page that match a + // selector of our choosing, and call our plugin on them. + // Here's how we'd call our "foo" plugin on any element with a + // data-role attribute of "foo": + $(target).find('[data-role="foo"]').foo(options); + + // Or, better yet, let's write the selector accounting for the configurable + // data-attribute namespace + $(target).find(':jqmData(role="foo")').foo(options); + }); + + +//*******************************************************// +// RequireJS And The jQuery UI Widget Factory +//*******************************************************// + +//********************** Snippet 1 **********************// +// [ES2015+] We used new arrow function syntax +// [ES2015+] We used new object method declaration + +/*! + * jQuery UI Widget + RequireJS module boilerplate (for 1.8/9+) + * Authors: @jrburke, @addyosmani + * Licensed under the MIT license + */ + +// Note from James: +// +// This assumes we are using the RequireJS+jQuery file, and +// that the following files are all in the same directory: +// +// - require-jquery.js +// - jquery-ui.custom.min.js (custom jQuery UI build with widget factory) +// - templates/ +// - asset.html +// - ao.myWidget.js + +// Then we can construct the widget as follows: + +// ao.myWidget.js file: +define( + 'ao.myWidget', + ['jquery', 'text!templates/asset.html', 'underscore', 'jquery-ui.custom.min'], + ($, assetHtml, _) => { + // define our widget under a namespace of our choice + // "ao" is used here as a demonstration + $.widget('ao.myWidget', { + // Options to be used as defaults + options: {}, + + // Set up widget (e.g. create element, apply theming, + // bind events, etc.) + _create() { + // _create will automatically run the first time + // this widget is called. Put the initial widget + // set-up code here, then we can access the element + // on which the widget was called via this.element. + // The options defined above can be accessed via + // this.options + // this.element.addStuff(); + // this.element.addStuff(); + // We can then use Underscore templating with + // with the assetHtml that has been pulled in + // var template = _.template( assetHtml ); + // this.content.append( template({}) ); + }, + + // Destroy an instantiated plugin and clean up modifications + // that the widget has made to the DOM + destroy() { + // this.element.removeStuff(); + // For UI 1.8, destroy must be invoked from the base + // widget + $.Widget.prototype.destroy.call(this); + // For UI 1.9, define _destroy instead and don't worry + // about calling the base widget + }, + + methodB(event) { + // _trigger dispatches callbacks the plugin user can + // subscribe to + // signature: _trigger( "callbackName", [eventObject], + // [uiObject] ) + this._trigger('methodA', event, { + key: value, + }); + }, + + methodA(event) { + this._trigger('dataChanged', event, { + key: value, + }); + }, + + // Respond to any changes the user makes to the option method + _setOption(key, value) { + switch (key) { + case 'someValue': + // this.options.someValue = doSomethingWith( value ); + break; + default: + // this.options[ key ] = value; + break; + } + + // For UI 1.8, _setOption must be manually invoked from + // the base widget + $.Widget.prototype._setOption.apply(this, arguments); + // For UI 1.9 the _super method can be used instead + // this._super( "_setOption", key, value ); + }, + }); + } + ); + + +//********************** Snippet 2 **********************// + + +//********************** Snippet 3 **********************// +// [ES2015+] We used new arrow function syntax +// [ES2015+] We used new keyword const for immutable constant declaration + +require({ + paths: { + jquery: 'https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min', + jqueryui: 'https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/jquery-ui.min', + boilerplate: '../patterns/jquery.widget-factory.requirejs.boilerplate', + }, + }, ['require', 'jquery', 'jqueryui', 'boilerplate'], (req, $) => { + $(() => { + const instance = $('#elem').myWidget(); + instance.myWidget('methodB'); + }); + }); + + +//*******************************************************// +// Globally And Per-Call Overridable Options (Best Options Pattern) +//*******************************************************// +// [ES2015+] We used new arrow function syntax +// [ES2015+] We used new keyword const for immutable constant declaration +// [ES2015+] We used new object method declaration + +//********************** Snippet 1 **********************// +/*! + * jQuery "best options" plugin boilerplate + * Author: @cowboy + * Further changes: @addyosmani + * Licensed under the MIT license + */ + +;(($, window, document, undefined) => { + $.fn.pluginName = function(options) { + // Here's a best practice for overriding "defaults" + // with specified options. Note how, rather than a + // regular defaults object being passed as the second + // parameter, we instead refer to $.fn.pluginName.options + // explicitly, merging it with the options passed directly + // to the plugin. This allows us to override options both + // globally and on a per-call level. + + options = $.extend({}, $.fn.pluginName.options, options); + + return this.each(function() { + const elem = $(this); + }); + }; + + // Globally overriding options + // Here are our publicly accessible default plugin options + // that are available in case the user doesn't pass in all + // of the values expected. The user is given a default + // experience but can also override the values as necessary. + // e.g. $fn.pluginName.key ="otherval"; + + $.fn.pluginName.options = { + key: 'value', + myMethod(elem, param) {}, + }; + })(jQuery, window, document); + + //********************** Snippet 2 **********************// + $('#elem').pluginName({ + key: 'foobar', + }); + + +//*******************************************************// +// A Highly Configurable And Mutable Plugin Pattern +//*******************************************************// + +//********************** Snippet 1 **********************// +$('.item-a').draggable({ defaultPosition: 'top-left' }); +$('.item-b').draggable({ defaultPosition: 'bottom-right' }); +$('.item-c').draggable({ defaultPosition: 'bottom-left' }); +//etc + +//********************** Snippet 2 **********************// +$('.items').draggable(); + + +//********************** Snippet 3 **********************// +html +"+msg+"
" ); + } +}; + + +// Object.create support test, and fallback for browsers without it +if ( typeof Object.create !== "function" ) { + Object.create = function (o) { + function F() {} + F.prototype = o; + return new F(); + }; +} + + +// Create a plugin based on a defined object +$.plugin = function( name, object ) { + $.fn[name] = function( options ) { + return this.each(function() { + if ( ! $.data( this, name ) ) { + $.data( this, name, Object.create( object ).init( + options, this ) ); + } + }); + }; +}; + +//********************** Snippet 2 **********************// +$.plugin( "myobj", myObject ); + +$("#elem").myobj( {name: "John"} ); + +var collection = $( "#elem" ).data( "myobj" ); +collection.myMethod( "I am a method"); + + +//*******************************************************// +// jQuery UI Widget Factory Bridge Pattern +//*******************************************************// + +//********************** Snippet 1 **********************// +/*! + * jQuery UI Widget factory "bridge" plugin boilerplate + * Author: @erichynds + * Further changes, additional comments: @addyosmani + * Licensed under the MIT license + */ + + +// a "widgetName" object constructor +// required: this must accept two arguments, +// options: an object of configuration options +// element: the DOM element the instance was created on +var widgetName = function( options, element ){ + this.name = "myWidgetName"; + this.options = options; + this.element = element; + this._init(); +} + +// the "widgetName" prototype +widgetName.prototype = { + + // _create will automatically run the first time this + // widget is called + _create: function(){ + // creation code + }, + + // required: initialization logic for the plugin goes into _init + // This fires when our instance is first created and when + // attempting to initialize the widget again (by the bridge) + // after it has already been initialized. + _init: function(){ + // init code + }, + + // required: objects to be used with the bridge must contain an + // "option". Post-initialization, the logic for changing options + // goes here. + option: function( key, value ){ + + // optional: get/change options post initialization + // ignore if you don't require them. + + // signature: $("#foo").bar({ cool:false }); + if( $.isPlainObject( key ) ){ + this.options = $.extend( true, this.options, key ); + + // signature: $( "#foo" ).option( "cool" ); - getter + } else if ( key && typeof value === "undefined" ){ + return this.options[ key ]; + + // signature: $( "#foo" ).bar("option", "baz", false ); + } else { + this.options[ key ] = value; + } + + // required: option must return the current instance. + // When re-initializing an instance on elements, option + // is called first and is then chained to the _init method. + return this; + }, + + // notice no underscore is used for public methods + publicFunction: function(){ + console.log( "public function" ); + }, + + // underscores are used for private methods + _privateFunction: function(){ + console.log( "private function" ); + } +}; + +//********************** Snippet 2 **********************// +// connect the widget obj to jQuery's API under the "foo" namespace +$.widget.bridge( "foo", widgetName ); + +// create an instance of the widget for use +var instance = $( "#foo" ).foo({ + baz: true +}); + +// our widget instance exists in the elem's data +// Outputs: #elem +console.log(instance.data( "foo" ).element); + +// bridge allows us to call public methods +// Outputs: "public method" +instance.foo("publicFunction"); + +// bridge prevents calls to internal methods +instance.foo("_privateFunction"); + + +//*******************************************************// +// jQuery Mobile Widgets With The Widget factory +//*******************************************************// + +//********************** Snippet 1 **********************// +/*! + * (jQuery mobile) jQuery UI Widget-factory plugin boilerplate (for 1.8/9+) + * Author: @scottjehl + * Further changes: @addyosmani + * Licensed under the MIT license + */ + +;(function ( $, window, document, undefined ) { + + // define a widget under a namespace of our choice + // here "mobile" has been used in the first argument + $.widget( "mobile.widgetName", $.mobile.widget, { + + // Options to be used as defaults + options: { + foo: true, + bar: false + }, + + _create: function() { + // _create will automatically run the first time this + // widget is called. Put the initial widget set-up code + // here, then we can access the element on which + // the widget was called via this.element + // The options defined above can be accessed via + // this.options + + // var m = this.element, + // p = m.parents( ":jqmData(role="page")" ), + // c = p.find( ":jqmData(role="content")" ) + }, + + // Private methods/props start with underscores + _dosomething: function(){ ... }, + + // Public methods like these below can can be called + // externally: + // $("#myelem").foo( "enable", arguments ); + + enable: function() { ... }, + + // Destroy an instantiated plugin and clean up modifications + // the widget has made to the DOM + destroy: function () { + // this.element.removeStuff(); + // For UI 1.8, destroy must be invoked from the + // base widget + $.Widget.prototype.destroy.call( this ); + // For UI 1.9, define _destroy instead and don't + // worry about calling the base widget + }, + + methodB: function ( event ) { + //_trigger dispatches callbacks the plugin user can + // subscribe to + // signature: _trigger( "callbackName", [eventObject], + // [uiObject] ) + // e.g. this._trigger( "hover", e /*where e.type == + // "mouseenter"*/, { hovered: $(e.target)}); + this._trigger( "methodA", event, { + key: value + }); + }, + + methodA: function ( event ) { + this._trigger( "dataChanged", event, { + key: value + }); + }, + + // Respond to any changes the user makes to the option method + _setOption: function ( key, value ) { + switch ( key ) { + case "someValue": + // this.options.someValue = doSomethingWith( value ); + break; + default: + // this.options[ key ] = value; + break; + } + + // For UI 1.8, _setOption must be manually invoked from + // the base widget + $.Widget.prototype._setOption.apply(this, arguments); + // For UI 1.9 the _super method can be used instead + // this._super( "_setOption", key, value ); + } + }); + +})( jQuery, window, document ); + +//********************** Snippet 2 **********************// +var instance = $( "#foo" ).widgetName({ + foo: false +}); + +instance.widgetName( "methodB" ); + +//********************** Snippet 3 **********************// +$(document).on("pagecreate", function ( e ) { + // In here, e.target refers to the page that was created + // (it's the target of the pagecreate event) + // So, we can simply find elements on this page that match a + // selector of our choosing, and call our plugin on them. + // Here's how we'd call our "foo" plugin on any element with a + // data-role attribute of "foo": + $(e.target).find( "[data-role="foo"]" ).foo( options ); + + // Or, better yet, let's write the selector accounting for the configurable + // data-attribute namespace + $( e.target ).find( ":jqmData(role="foo")" ).foo( options ); +}); + + +//*******************************************************// +// RequireJS And The jQuery UI Widget Factory +//*******************************************************// + +//********************** Snippet 1 **********************// +/*! + * jQuery UI Widget + RequireJS module boilerplate (for 1.8/9+) + * Authors: @jrburke, @addyosmani + * Licensed under the MIT license + */ + +// Note from James: +// +// This assumes we are using the RequireJS+jQuery file, and +// that the following files are all in the same directory: +// +// - require-jquery.js +// - jquery-ui.custom.min.js (custom jQuery UI build with widget factory) +// - templates/ +// - asset.html +// - ao.myWidget.js + +// Then we can construct the widget as follows: + +// ao.myWidget.js file: +define( "ao.myWidget", ["jquery", "text!templates/asset.html", "underscore", "jquery-ui.custom.min"], function ( $, assetHtml, _ ) { + + // define our widget under a namespace of our choice + // "ao" is used here as a demonstration + $.widget( "ao.myWidget", { + + // Options to be used as defaults + options: {}, + + // Set up widget (e.g. create element, apply theming, + // bind events, etc.) + _create: function () { + + // _create will automatically run the first time + // this widget is called. Put the initial widget + // set-up code here, then we can access the element + // on which the widget was called via this.element. + // The options defined above can be accessed via + // this.options + + // this.element.addStuff(); + // this.element.addStuff(); + + // We can then use Underscore templating with + // with the assetHtml that has been pulled in + // var template = _.template( assetHtml ); + // this.content.append( template({}) ); + }, + + // Destroy an instantiated plugin and clean up modifications + // that the widget has made to the DOM + destroy: function () { + // this.element.removeStuff(); + // For UI 1.8, destroy must be invoked from the base + // widget + $.Widget.prototype.destroy.call( this ); + // For UI 1.9, define _destroy instead and don't worry + // about calling the base widget + }, + + methodB: function ( event ) { + // _trigger dispatches callbacks the plugin user can + // subscribe to + // signature: _trigger( "callbackName", [eventObject], + // [uiObject] ) + this._trigger( "methodA", event, { + key: value + }); + }, + + methodA: function ( event ) { + this._trigger("dataChanged", event, { + key: value + }); + }, + + // Respond to any changes the user makes to the option method + _setOption: function ( key, value ) { + switch (key) { + case "someValue": + // this.options.someValue = doSomethingWith( value ); + break; + default: + // this.options[ key ] = value; + break; + } + + // For UI 1.8, _setOption must be manually invoked from + // the base widget + $.Widget.prototype._setOption.apply( this, arguments ); + // For UI 1.9 the _super method can be used instead + // this._super( "_setOption", key, value ); + } + + }); +}); + +//********************** Snippet 2 **********************// + + +//********************** Snippet 3 **********************// +require({ + + paths: { + "jquery": "https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min", + "jqueryui": "https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/jquery-ui.min", + "boilerplate": "../patterns/jquery.widget-factory.requirejs.boilerplate" + } +}, ["require", "jquery", "jqueryui", "boilerplate"], +function (req, $) { + + $(function () { + + var instance = $("#elem").myWidget(); + instance.myWidget("methodB"); + + }); +}); + + +//*******************************************************// +// Globally And Per-Call Overridable Options (Best Options Pattern) +//*******************************************************// + +//********************** Snippet 1 **********************// +/*! + * jQuery "best options" plugin boilerplate + * Author: @cowboy + * Further changes: @addyosmani + * Licensed under the MIT license + */ + +;(function ( $, window, document, undefined ) { + + $.fn.pluginName = function ( options ) { + + // Here's a best practice for overriding "defaults" + // with specified options. Note how, rather than a + // regular defaults object being passed as the second + // parameter, we instead refer to $.fn.pluginName.options + // explicitly, merging it with the options passed directly + // to the plugin. This allows us to override options both + // globally and on a per-call level. + + options = $.extend( {}, $.fn.pluginName.options, options ); + + return this.each(function () { + + var elem = $(this); + + }); + }; + + // Globally overriding options + // Here are our publicly accessible default plugin options + // that are available in case the user doesn't pass in all + // of the values expected. The user is given a default + // experience but can also override the values as necessary. + // e.g. $fn.pluginName.key ="otherval"; + + $.fn.pluginName.options = { + + key: "value", + myMethod: function ( elem, param ) { + + } + }; + +})( jQuery, window, document ); + +//********************** Snippet 2 **********************// +$("#elem").pluginName({ + key: "foobar" +}); + + +//*******************************************************// +// A Highly Configurable And Mutable Plugin Pattern +//*******************************************************// + +//********************** Snippet 1 **********************// +$( ".item-a" ).draggable( {"defaultPosition":"top-left"} ); +$( ".item-b" ).draggable( {"defaultPosition":"bottom-right"} ); +$( ".item-c" ).draggable( {"defaultPosition":"bottom-left"} ); +//etc + +//********************** Snippet 2 **********************// +$( ".items" ).draggable(); + +//********************** Snippet 3 **********************// +html +