Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Javascript

Reverse Decorator in JavaScript, or Chain of Responsibility

0.00/5 (No votes)
20 Jul 2020CPOL3 min read 7.2K  
Implementation of the Chain of Responsibility Pattern in JavaScript
For this simple app, I'll use five functions. They are: Add error handler, get the data from the form, add the data to the table, sort table on due date, show any error to user.

Introduction

This is a continuation of the topics covered in my other two articles here:

  1. Decorator Pattern in VB.NET WinForms
  2. Reverse Decorator in VB.NET WinForms

The code in this article is available on GitHub here.

Implementation of what I call the Reverse Decorator. It is really just a take on the chain of responsibility pattern. It is a modular programming style that I believe follows the SOLID principles. I've used it in a couple of projects, all internal to my organization. I'd like to get some feedback on what others think. I'll stick with a simple To Do application as to focus most on the pattern. I'm going to assume that you have read and understand my other two articles and then this is just an implementation in JavaScript.

Background

I have been playing around with the decorator pattern for some time. I implemented it in VB.NET because that is the language I use most at work. I wrote my first CodeProject article with a decorator pattern, with explanations. I wrote the Reverse Decorator article next, also with explanations. I wanted to do this pattern in JavaScript because the language offers some advantages. The function doesn't have to be written in a class, more flexibility on the dataObj, and we don't need to implement an interface. I'm not going to cover the ground I covered in the other two articles mainly because I just wanted to publish this example.

Starting Point

We are starting with a todo.html file. In the HTML, I've created the basic UI for our To Do app. It references Bootstrap CSS. Nothing too exciting. We will be starting with two JavaScript files to help us organize our project. The "todo.js" file is where we will compose our action and then run it. In the "todoAdd.js", we will define all the functions. I find it easier for maintenance to separate the functions from the composition and running.

Functions First

We'll start with the functions file "todoAdd.js". The basic functions will have this format:

JavaScript
function functionName(anyPramsHere, next){
    return function(dataObj){
        
        //function logic here
        
        if(next){
            next(dataObj);
        }
    };
}

This format will allow us to compose the functions together. One benefit of using JavaScript over VB.NET is we don't have to declare a class. We can just use a function that can return another function. "next" is the following function, dataObj is the class that passes the state from one function to the next.

For this simple app, I'll use five functions. They are:

  1. Add error handler
  2. Get the data from the form
  3. Add the data to the table
  4. Sort table on due date
  5. Show any error to user

None of these functions are particularly earth shattering, so we'll have them done, like this:

JavaScript
//get the data from form
function getDataFromForm(next){
    return function(dataObj){
        if(!dataObj.hasError){
            dataObj.dtAddDueDate = document.getElementById("dtAddDueDate").value;
            dataObj.txtAddTask = document.getElementById("txtAddTask").value;
        }
        
        if(next){
            next(dataObj);
        }
    };
}

//write it to the table
function addToTable(next){
    return function(dataObj){
        if(!dataObj.hasError){
            var newRow = document.createElement("tr");
            var ckCol = document.createElement("td");
            var ck = document.createElement("input");
            ck.type = "checkbox";
            ckCol.appendChild(ck)
            newRow.appendChild(ckCol);

            var dtCol = document.createElement("td");
            dtCol.innerText = dataObj.dtAddDueDate;
            newRow.appendChild(dtCol);

            var taskCol = document.createElement("td");
            taskCol.innerText = dataObj.txtAddTask;
            newRow.appendChild(taskCol);

            var tblBody = document.getElementById("tblBody");
            tblBody.appendChild(newRow);
        }
        
        if(next){
            next(dataObj);
        }
    };
}

//sort the table on Due Date
function sortTableOnDueDate(next){
    return function(dataObj){

        var rows = document.getElementById("tblBody").rows;
        var numSorted;
        do {
            numSorted = 0;
            for (i = 0; i < rows.length; i++) {

                var currEl = rows[i];
                var nextEl = currEl.nextElementSibling;
                if ((nextEl) && 
                   (new Date(currEl.cells[1].innerText) > new Date(nextEl.cells[1].innerText))) {

                    document.getElementById("tblBody").insertBefore(nextEl, currEl);
                    numSorted++;
                }
            }

        } while (numSorted > 0);

        if(next){
            next(dataObj);
        }
    };
}

//add error handler
function addErrHandler(next) {
    return function (dataObj) {
        dataObj.err = {
            hasError: false,
            message: ""
        }
        try {
            if (next) {
                next(dataObj);
            }
        } catch (err) {
            dataObj.err.hasError = true;
            dataObj.err.message = err.message;
            next(dataObj);
        }
    };
}

//show error to user
function showErrorToUser(next){
   
    return function(dataObj){
        if(dataObj.err.hasError){
            alert("ERROR: " + dataObj.err.message);
        }
        if(next){
            next(dataObj);
        }
    };
}

Compose the Functions Together

In the todo.js file, we will put the functions together. We compose them backwards so when they run, they will run forward. We do it like this:

JavaScript
var addToDo = function(){

    // we compose here:
    var runMe = null;
    runMe = showErrorToUser(runMe);
    runMe = sortTableOnDueDate(runMe);
    runMe = addToTable(runMe);
    runMe = getDataFromForm(runMe);
    runMe = addErrHandler(runMe);

    dataObj = {};
    // run here:
    runMe(dataObj);
};

Composing backward and running forward allows us to use async/await patterns. If you are wondering why, see my VB.NET article on the Reverse Decorator.

At the end of the composition, we create the dataObj. It is where we will persist state as we go from function to function.

Run it and it works!

History

  • 1st December, 2018: Initial version
  • 3rd December, 2018: Article updated

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)