This is the second part of the series Creating your first JavaScript Library. See, Creating your First JavaScript Library: Part 1

Earlier in the part 1 of this series we discussed that a good library should be able to be scaled easily with making least possible edits or not edits at all. Currently our library has basic feature of displaying notification with a logo and a message.

But what if we feel the need of adding new features to make our notification even more powerful? Suppose we want our notification to also have action buttons. Let’s approach this addition in a way that we make as few edits as possible

We want to be able to add option of action button as follows

Notifier.notify("warning", {
    logo: ".../warning.jpg",
    msg: "Do you want to delete your file ?",
    actions: [{title: "Yes", action: deleteFile},
      {title: "No", action: doSomethingElse}]
});

This time while evoking notify, we extended second argument with actions array of objects containing info about the action button(s). We want as many number of action buttons in our notification as the number of objects present in this array(actions.length). Let’s update our notification element with a wrapper to contain our buttons. For that we need to go to createAndAppendNotification function.

createAndAppendNotification() {
    //create a wrapper
    btnContainer= document.createElement("section");

    //add class
    btnContainer.classList.add("btn-container");

    //append
    notification.appendChild(btnContainer);

    //dont't forget to return btnContainer too or 'elements' object won't have it
    return {
        btnContainer: btnContainer
    }
}

Now let’s create a function which uses options argument of notify method to collect the info of the action buttons and append them to btnContainer

function addActionButtons(options) {
    var actBtnLength= options.actions.length;

    for(var i=0; i < actBtnLength; i++) {
        if(typeof actions[i] === "object) {
            //create button
            var btn= document.createElement("button");

            //add class
            btn.classList.add("btn-action");

            //set text
            btn.textContent= options.actions[i].title;

            //get index
            var idx= i;

            //add click event
            btn.addEventListener("click", function() {
              closeNotification();
              options.actions[idx].action();
            });

            //append button
            elements.btnContainer.appendChild(btn);
        } else {
            throw new Error("notify's second argument's third key should contain an array of only objects");
        }
    }
}

The question now is that when notify is called where and how do we call this addActionButtons() function? One quick solution is following

return {
    notify: function(state, options) {
        if(typeof options === "object") {
            addActionButtons(options);
            setNotification(options.logo || "", options.msg);
        } else {
            throw new Error("Second arguments should be an object having keys as 'options' (e.g.- logo, msg)");
        }
        
        //rest of the code
    }
}

But this will add new button(s) on every notify call. So we remove the old ones before appending new ones by using removeButtons method inside setNotification

function removeActionButtons() {
    var buttons= elements.btnContainer.children;
    for (var i=0; i< buttons.length; i++) {
      buttons[0].remove();
    }
}

function setNotification() {
    //if btnContainer has buttons, remove them
    if(elements.btnContainer.children[0]) {
      removeActionButtons();
    }
    
    //rest of the code
}

Now it works fine but don’t you think, our code has become a bit dry? Think over addActionButtons and setNotification for a moment. Don’t these seem related?

The setNotification does all the work of preparing notification elements like setting src attribute to logo image, setting notification text etc. Don’t you think addActionButtons should have been called inside setNotification function just as removeActionButtons has been?

Yes, it’s more appropriate to call it there. But Then we’ll have to also change setNotification parameters from two to just one so that while calling it inside notify method, we will be able to pass options object to it and use that to fetch all the data needed (logo image’s ‘src’ value, message and action button details).

Also do you think addActionButtons and removeActionButtons functions will be needed somewhere else?

Well, for now, answer is No. So we not only call but also place these functions in setNotification.

function setNotification(options) {
    elements.logo.setAttribute("src", options.logo || "");
    elements.msg.textContent= options.msg;

    function removeActionButtons() {
        var buttons= elements.btnContainer.children;
        for (var i=0; i< buttons.length; i++) {
          buttons[0].remove();
        }
    }
    
    // if elements.btnContainer has any action button-
    if(elements.btnContainer.children[0]) {
        removeActionButtons();
    }

    function addActionButtons(options) {
        var actBtnLength= options.actions.length;
        
        for(var i=0; i < actBtnLength; i++) {
          if(typeof options.actions[i] === "object) {
            let btn= document.createElement("button");
            btn.classList.add("btn-action");
            btn.textContent= options.actions[i].title;
            var idx= i;
            
            btn.addEventListener("click", function() {
              closeNotification();
              options.actions[idx].action();
            });
      
            elements.btnContainer.appendChild(btn);
          } else {
            throw new Error("notify's second argument's third key should contain an array of only objects");
        }
    }
}

//if options.actions is available
if(options.actions) {
    //if options.actions is an array
    if(Object.getPrototypeOf(options.actions) === Array.prototype) {
        addActionButtons(options);
    } else {
        throw new Error("notifiy's second argument's third key should contain an array");
    }
}

We’ll also need to make changes in return statement of our Notifier function (wrapper).

return {
    notify: function(state, options) {
      if(typeof options=== "object") {
        setNotification(options); //look! instead of two arguments we have to pass just one
      } else {
          throw new Error("Second arguments should be an object having keys as 'options' (e.g.- logo)");
      }

      if(typeof state=="string") {
        notFirstTime= state;
        showNotification(state);
        stateClass= state;
      } else {
          throw new Error("First argument should be a string representing state of notificaion");
      }
    }
}

Summing Up

Have a recap of this whole section (Extending our library) and observe the amount of code edited.

After clean observation, you’ll find that nothing expect the no. of arguments of setNotification (and a bit of its internal code which uses arguments) was edited. In fact, our code becomes much stronger than before. Now we can pass endless detail in just one object(options) and use it (in setNotification) without having to add new arguments to setNotification each time we want it to have new information passed by the user. This will make easy for us to add new features in future.

Pro Tip: It’s always better to use the object as the parameter of the function instead of separate arguments.

That’s all Devs! Now the previously designed API pattern works awesomely. Switch between the tabs for the complete JavaScript Library code.

See the Pen A Notification Library by Devstreak (@devstreak) on CodePen.

Further Reading

Javascript Modules: A Beginner’s Guide – Medium

Mastering Module Pattern – Todd Motto

How do You Structure JavaScript- The Module Pattern Edition – CSS Tricks

Conclusion

So you have created and extended your first JavaScript Library. Now go ahead and try to implement your newly built library to create something cool out of it.

So which idea are you going to turn into your next library? Do share with us and ask any questions in the comments below.

Share on Facebook Tweet on Twitter Post on Google+ Buffer