TexasJetter
.NET Development Examined

A Toast to JavaScript


Tuesday, September 1, 2015

Wow, it's been a while since I've had time to follow up on the last post. This year has truly flown by. We've got lot's to cover building our JavaScript Framework, so let's get going on the next step.

In Building-JavaScript-Framework we began our JavaScript Framework by defining our module, added a couple of variables and a method to generate a GUID. That's a great start, but we want our framework to do much more.

A common requirement of all applications is to convey a message to the user. This could be a warning that the something went wrong, or confirmation that an action was successful. It could also be just passing information along to the user. Additionally we may need to display more than one message to the user simultaneously. In this article we will look at creating a 'toast' type notification system that meets these requirements.

Typically I develop in incremental steps. By this I mean that I get the base part working, then continue to refine it based on the requirements. With this in mind let's start by writing some JavaScript to get something to show up on our view. We will start with our framework developed in the Building-JavaScript-Framework article and add a new function called "notification".

if (typeof ts === "undefined") {
    ts = {};
}
(function(undefined) {
	...
	ts.notification = function(message){
		alert(message);
	};
})();

Running the above code we can call the function ts.notification("Hello World") from anywhere in our application and have our message displayed to the user. This works, but it is definitely not what we want. Aside from the fact that we can already call the JavaScript 'alert' method anywhere without a framework method, the format of the message is not under our control. We can't control the style (color, font, size) or location of the resulting message. This is definitely not what we want in our custom application notification system.

So let's replace the 'alert' with some custom code. To have our notification behave as part of our application we need to inject our message into the Document Object Model (DOM) of our view. There many ways to accomplish this, but since we are already using jQuery (you are using jQuery aren't you?) we can use the .append method.

ts.notification = function(message){
	$("body").append("<div id='notification-container'>" + message + "</div>");
};

Looking at the code above we see the we are appending a new 'div' element to the 'body' of the DOM. We insert our message inside our new 'div' tag. Running this code will most likely result in an unimpressive text block showing up at the end of your view. Arguably that is worse than the 'alert' method because the message could be shown below the fold of the browser view and the user would never see it. To over come this we will use some CSS styles to start defining how where our notification is placed and how it will look.

It is very common for frameworks to have both JavaScript and CSS components. In fact the often heard phrase 'HTML5' often means HTML5 elements, JavaScript, and CSS/CSS3 components.

Start a new CSS file and call it ts-framework.css and ensure that it is referenced in your view. In this file we will place all the CSS that is required to support our framework.

#notification-container {
    position: fixed;
    top: 10px;
    right: 0;
    width:30%
    z-index: 200;
}

Let's examine the style defined here. The 'position: fixed' means that the element layout is taken out of the normal rendering layout and it will always show at the same location, regardless of the browser width and height. The 'top' and 'right' rules determine that the element will be show 10 pixels from the top of the browser container, and shown all the way to the right of the screen. Setting the width dictates that it will have a set width (if this were not set the message would have different widths based on the length of the message). Finally the 'z-index' ensures that it will be shown above other elements (relative to the z-axis). Reloading the page and running the same JavaScript will now result in our text showing at the top right corner of the screen.

We're gaining ground, but there is still a lot to consider. As it stands each time we run the ts.notification() method we will get a notification, but the text is still not formatted, and there can only be one. Each new notification is overlaid on top of the previous one.

To correct this we need a way to track each notification. We'll use a simple counter to accomplish this.

ts.notificationCntr = 0;
ts.notification = function(message){
	if(ts.notificationCntr === 0){
		$("body").append("<div id='notification-container'></div>");
	}
	var html = "<div id='notification" + ts.notificationCntr + "'>" + message + "</div>";
	$("#notification-container").append(html);
	
	ts.notificationCntr = ts.notificationCntr + 1;
};

Here we have added our counter, and only on the first call of ts.notification() do we add our 'notification-container' div. All of our notifications are added to the single container and have a unique 'id' which is incremented by the counter. This means as we repeatedly call ts.notification() it will stack the notifications below each other (along the y-axis). That's a lot better, but we sill need to format each notification to fall in line with our application styling.

ts.notificationCntr = 0;
ts.notification = function(message){
    ...
    var html = "<div id='notification" + ts.notificationCntr + "' " +
               "style='display:none' class='alert alert-info alert-dismissible'>" +
               "<button type='button' class='close' data-dismiss='alert' aria-label='Close'>" +
               "<span aria-hidden='true'>×</span></button>" +
               options.message + "</div>";
    $("#notification-container").append(html);
    $("#notification" + ts.notificationCntr).slideDown("slow");
    
	ts.notificationCntr = ts.notificationCntr + 1;
};

Ok, I'll admit that I'm cheating a bit here. Because we are using the BootStrap framework we already have some predefined alert styles. If you are not using the BootStrap framework you are free to style the notification element to suit your application. But if you will notice our 'div' has it's display style set to 'none'. This means that the notification is not shown until we get to the line with the jQuery animation .slideDown. Running ts.notification() now results in our message nicely formatted and stacked one above the other. Additionally we have a nice animation sliding the message down into view.

Animations can make a tremendous difference in the user experience. Things that 'just appear' tend to confuse a user. Fortunately in our case our notification doesn't re-flow existing text, but having a notification slide in is much more pleasing. However animations should be used carefully. If a 10 second animation is triggered each time a user clicks a button, and the user has to click that button repeatedly the user will quickly become frustrated. It's cool the firs time, but every time after that it's just irritating. Keeping your animations to less than 2-3 seconds will help.

Looking at the BootStrap Alerts we see that they have conveniently defined four different type of alerts; success, info, warning, and danger. That falls in line with our criteria of being able to indicate to the user not only a message, but give a stylistic hint as to the nature of the message. However the above code results in every message being styled the same. Let's allow the user to control the message style in our notification system.

ts.notificationCntr = 0;
ts.notification = function(message, level){
    ...
    var html = "<div id='notification" + ts.notificationCntr + "' " +
               "style='display:none' class='alert alert-" + level + " alert-dismissible'>" +
               "<button type='button' class='close' data-dismiss='alert' aria-label='Close'>" +
               "<span aria-hidden='true'>×</span></button>" +
               options.message + "</div>";
    $("#notification-container").append(html);
	$("#notification" + ts.notificationCntr).slideDown("slow");
	
    ts.notificationCntr = ts.notificationCntr + 1;
};

Well that was easy! Here we have added a 'level' parameter, and used it to control the message style. Now we can call our notification sending two parameters, ts.notification("Hello World", "danger"). But we have another problem now. Each time we issue a notification it stacks below the previous one. After a while we could end up with the entire right side of our application cluttered with messages. We need a why to allow the user to remove a notification. Sharp eyes may have notices that we are using the Bootstrap style of 'alert-dismissible', which places a 'x' to close the message. That was very thoughtful of the Bootstrap folks, so we can check off that item. But requiring the user to remove each notification could be tiresome, so let's look at a way to automatically remove a message after a period of time.

ts.notificationCntr = 0;
ts.notification = function(message, level, delay){
    ...
    $("#notification-container").append(html);
    $("#notification" + ts.notificationCntr).slideDown("slow");
    
    if (delay > 0) {
        $("#notification" + ts.notificationCntr).delay(delay).slideUp("slow", function() {
            $(this).parent().remove();
        });
    }
    ts.notificationCntr = ts.notificationCntr + 1;
};

The above code shows how we have added a new 'delay' parameter to our function. When the developer now calls the ts.notification("Hellow World", "info", 3000) it will result in our message being shown for 3 seconds, then removing it's self from view.

$("#notification" + ts.notificationCntr).delay(delay).slideUp("slow", function() {
    $(this).parent().remove();
});

The key line is shown here. At the time the notification is issued, we find the unique message container, then use the jQuery .delay function to wait x milliseconds, after the delay is complete we run the jQuery .slideUp function using the jQuery pre-defined 'slow' duration. When the .slideUp is complete it returns to the optional callback function, where we clean up the DOM by removing the message element entirely.

Here is the final result:

ts.notification = function (notification, level, delay) {
	if (ts.notificationOptions.cntr === 0) {
		$("body").append("<div id='notification-container'></div>");
	}
    var html = "<div id='notification" + ts.notificationOptions.cntr + 
               "' style='display:none' class='alert alert-" + level + "  alert-dismissible'>" +
               "<button type='button' class='close' data-dismiss='alert' aria-label='Close'>
                    <span aria-hidden='true'>×</span></button>" + notification + "</div>";

	$("#notification-container").append(html);
	$("#notification" + ts.notificationOptions.cntr).slideDown("slow");
	if (delay > 0) {
		$("#notification" + ts.notificationOptions.cntr).delay(delay).slideUp("slow");
	}
	
	ts.notificationOptions.cntr = ts.notificationOptions.cntr + 1;
};

You can test it out now. If you press F12 (to open your Browsers developer tools) and type in ts.notification("Hello World", "info", 3000) you will get a notification message displayed. So it is working pretty good now, but there is one problem. If a developer issues a notification without passing all the parameters, or passing an invalid parameter the system doesn't gracefully deal with it. So if a developer issues the function ts.notification("Hellow World") it will display an unformatted notification that will be displayed until the user finds the 'x' (which depending on your site styling could be very hard to see). In the next article we'll look at making our notification system more bullet proof.


Comments

Add Comment