- A reusable solution that can be applied to occurring problems in software design (JavaScript Applications)
- Can also be thought of as programming templates
- Situations vary significantly
Patterns we will look at
- Module
- Revealing Module Pattern
- Singleton
- Factory
- Observer
- Mediator
- State
Basic structure
// Anonymus function
(function(){
//Declare private variables and functions
return {
// Declare public variables and functions
}
})();
Foundation of Module Pattern
// Standard Module Pattern
const UICtrl = (function() {
// Anything we declare up here is going to be private
let text = 'Hello World';
const changeText = function(){
const element = document.querySelector('h1');
element.textContent = text;
}
return {
callChangeText: function(){
changeText();
console.log(text);
}
}
})();
UICtrl.callChangeText();
Revealing Module Pattern
// REVEALING MODULE PATTERN
const ItemCtrl = (function() {
let data =[];
function add(item){
data.push(item);
console.log('Item added....');
}
function get(id){
return data.find(item => {
return item.id === id;
});
}
return {
add: add,
get: get
}
})();
ItemCtrl.add({id: 1, name: 'John'});
ItemCtrl.add({id: 2, name: 'Mark'});
console.log(ItemCtrl.get(2));
Singleton Patterns
Actually a manifestation of the module pattern. A singleton object is an immediate anonymus function and it can only return one instance of an object at a time.
const Singleton = (function() {
let instance;
function createInstance(){
const object = new Object({name: 'Gabor'});
return object;
}
return{
getInstance: function(){
if(!instance){
instance = createInstance();
}
return instance;
}
}
})();
const instanceA = Singleton.getInstance();
const instanceB = Singleton.getInstance();
console.log(instanceA === instanceB);
Factory Pattern
function MemberFactory(){
this.createMember = function(name, type){
let member;
if (type === 'Simple') {
member = new SimpleMembership(name);
} else if (type === 'Standard'){
member = new StandardMembership(name);
} else if (type === 'Super'){
member = new SuperMembership(name);
}
member.type = type;
member.define = function(){
console.log(`${this.name} - Membership: ${this.type} - Cost: ${this.cost}`);
}
return member;
}
}
const SimpleMembership = function(name) {
this.name = name;
this.cost = '$5';
}
const StandardMembership = function(name) {
this.name = name;
this.cost = '$15';
}
const SuperMembership = function(name) {
this.name = name;
this.cost = '$25';
}
const members = [];
const factory = new MemberFactory();
members.push(factory.createMember('John Doe', 'Simple'));
members.push(factory.createMember('Chris Johnson', 'Super'));
members.push(factory.createMember('Janis Williams', 'Simple'));
members.push(factory.createMember('Sam Smith', 'Standard'));
console.log(members);
members.forEach(function(member) {
member.define();
});
Observer Pattern
It allows us to subscribe and unsubscribe to certain event and certain functionality.
Example
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="#">
<title>Factory Pattern</title>
<style>button{font-size: 18px; padding: 20px; width: 100%;} .buttons{display: grid; grid-template-columns: 1fr 1fr; gap: 30px;}</style>
</head>
<body>
<h1>JavaScript Sandbox: Patterns</h1>
<div class="buttons">
<button class="sub-ms">Subscribe Milliseconds</button>
<button class="unsub-ms">Unsubscribe Milliseconds</button>
</div>
<br><br>
<div class="buttons">
<button class="sub-s">Subscribe Seconds</button>
<button class="unsub-s">Unsubscribe Seconds</button>
</div>
<br><br>
<button class="fire">Fire</button>
<div id="output"></div>
<script src="appes6.js"></script>
</body>
</html>
ES5
function EventObserver() {
this.observers = [];
}
EventObserver.prototype = {
subscribe: function(fn) {
this.observers.push(fn);
console.log(`You are now subscribed to ${fn.name}`);
},
unsubscribe: function(fn) {
// Filter out from the list whatever matches the callback function. If there is no match, the callback gets to stay on the list. The filter returns a new list and reassigns the list of observers.
this.observers = this.observers.filter(function(item){
if(item !== fn) {
return item;
}
});
console.log(`You are now unsubscribed from ${fn.name}`);
},
fire: function() {
this.observers.forEach(function(item) {
item.call();
});
}
}
const click = new EventObserver();
// Event Listeners
document.querySelector('.sub-ms').addEventListener('click', function() {
click.subscribe(getCurMilliseconds);
});
document.querySelector('.unsub-ms').addEventListener('click', function() {
click.unsubscribe(getCurMilliseconds);
});
document.querySelector('.sub-s').addEventListener('click', function() {
click.subscribe(getCurSeconds);
});
document.querySelector('.unsub-s').addEventListener('click', function() {
click.unsubscribe(getCurSeconds);
});
document.querySelector('.fire').addEventListener('click', function() {
click.fire();
});
// Click Handler
const getCurMilliseconds = function() {
console.log(`Current Milliseconds: ${new Date().getMilliseconds()}`);
}
const getCurSeconds = function() {
console.log(`Current Seconds: ${new Date().getSeconds()}`);
}
ES6
class EventObserver {
constructor(){
this.observers = [];
}
subscribe(fn){
this.observers.push(fn);
console.log(`You are now subscribed to ${fn.name}`);
}
unsubscribe(fn){
// Filter out from the list whatever matches the callback function. If there is no match, the callback gets to stay on the list. The filter returns a new list and reassigns the list of observers.
this.observers = this.observers.filter(function(item){
if(item !== fn) {
return item;
}
});
console.log(`You are now unsubscribed from ${fn.name}`);
}
fire(){
this.observers.forEach(function(item) {
item.call();
});
}
}
const click = new EventObserver();
// Event Listeners
document.querySelector('.sub-ms').addEventListener('click', function() {
click.subscribe(getCurMilliseconds);
});
document.querySelector('.unsub-ms').addEventListener('click', function() {
click.unsubscribe(getCurMilliseconds);
});
document.querySelector('.sub-s').addEventListener('click', function() {
click.subscribe(getCurSeconds);
});
document.querySelector('.unsub-s').addEventListener('click', function() {
click.unsubscribe(getCurSeconds);
});
document.querySelector('.fire').addEventListener('click', function() {
click.fire();
});
// Click Handler
const getCurMilliseconds = function() {
console.log(`Current Milliseconds: ${new Date().getMilliseconds()}`);
}
const getCurSeconds = function() {
console.log(`Current Seconds: ${new Date().getSeconds()}`);
}
Mediator Pattern
// Constructor function
const User = function(name){
this.name = name;
this.chatroom = null;
}
User.prototype = {
send: function(message, to){
this.chatroom.send(message, this, to);
},
receive: function(message, from){
console.log(`${from.name} to ${this.name}: ${message}`);
}
}
const Chatroom = function(){
let users = {}; // List of users
return {
register: function(user){
users[user.name] = user;
user.chatroom = this;
},
send: function(message, from, to){
if(to) {
// Single user message
to.receive(message, from);
} else {
// Mass message
for(key in users) {
if(users[key] !== from) {
users[key].receive(message, from);
}
}
}
}
}
}
const brad = new User('Brad');
const gabor = new User('Gabor');
const balazs = new User('Balazs');
const chatroom = new Chatroom();
chatroom.register(brad);
chatroom.register(gabor);
chatroom.register(balazs);
gabor.send('Hello Balázs', balazs);
brad.send('Hello Gábor, You are the best dev ever!', gabor);
brad.send('Hello Everyone!')
State Pattern
const PageState = function(){
let currentState = new homeState(this);
this.init = function(){
this.change(new homeState);
}
this.change = function(state){
currentState = state;
}
};
// Home state
const homeState = function(page){
document.querySelector('#heading').textContent = 'Home';
document.querySelector('#content').innerHTML = `
<p>This is home page</p>
`;
};
// About State
const aboutState = function(page){
document.querySelector('#heading').textContent = 'About us';
document.querySelector('#content').innerHTML = `
<p>This is about page</p>
`;
}
// Contact State
const contactState = function(page){
document.querySelector('#heading').textContent = 'Contact us';
document.querySelector('#content').innerHTML = `
<form>
<div class="form-group">
<label>Name</label>
<input type="text" class="form-control">
</div>
<div class="form-group">
<label>Email address</label>
<input type="email" class="form-control">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
`;
};
// Instantiate pageState
const page = new PageState();
// Init the first state
page.init();
// UI Variables
const home = document.getElementById('home'),
about = document.getElementById('about'),
contact = document.getElementById('contact');
// Add eventlisteners
home.addEventListener('click', (e) => {
page.change(new homeState);
e.preventDefault();
});
about.addEventListener('click', (e) => {
page.change(new aboutState);
e.preventDefault();
});
contact.addEventListener('click', (e) => {
page.change(new contactState);
e.preventDefault();
});