What are patterns?

  • 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();
});
Was this page helpful?