Frederik Vig – ASP.NET developer

Follow me

Posts tagged ‘JavaScript’

CSS and JavaScript compression and bundling

In the last post in Create an EPiServer site from scratch I go through some optimization tips and tricks from YSlow. One of these are CSS and JavaScript compression and bundling of files to create fewer HTTP requests and to send the least amount of data back to the client as possible, removing whitespaces, comments etc, that we don’t need to run our code.

In the past I’ve done this as part of my deployment process, but recently I’ve come across a tool called SquishIt. Which basically is a wrapper for YUI Compressor (you can easily replace it with your compressor of choice). It is dead simple to use, you simply add a reference to SquishIt.Framework.dll in your project, and replace your old CSS and JavaScript links and script tags with code like this.

<%= Bundle.Css()
          .Add("~/Styles/screen.css")
          .Add("~/Styles/print.css")
          .Add("~/Styles/mobile.css")
          .Render("~/Styles/combined-#.css")%>
 
<%= Bundle.JavaScript()
          .Add("~/Scripts/belatedPNG-0.0.8a-min.js")
          .Add("~/Scripts/ie6.js")
          .Render("~/Scripts/ie6combined-#.js") %>

Using a fluent interface makes it easy to add more file references, ending with the Render method that takes a parameter for where to store the combined and minimized file.

It is very easy to toggle SquishIt on and off. Simply set debug=”true” in your web.config file to turn it off and render CSS and JavaScript files as normal, great for when you need to debug your code. Set debug=”false” to enable compression and bundling again.

Best part is that it only took me 2 minutes to setup and add to my current project! Works like a charm so far :) .

Backup plan when loading the jQuery library from CDN

In most of my project I load the jQuery library from a CDN, either Google or Microsoft. This ensures that my page will load faster for my visitors, since the jQuery file will get sent to them from their nearest location, gzipped and compressed. When the visitor visits another site that use the same jQuery version from the same CDN, they don’t need to wait for their browser to download the library since it’s already in their temporary Internet files.

One of the drawbacks to this approach is when the CDN goes offline or becomes unavailable. Fortunately this has not happened to me – we should however have a backup plan! While reading through Rick Strahl’s presentations on jQuery I came across a great little snippet that will use a local copy of the jQuery library if the CDN is down or in some way unavailable.

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript">
if (typeof jQuery == 'undefined')
	document.write(unescape("%3Cscript src='/scripts/jquery-1.4.2.min.js' type='text/javascript'%3E%3C/script%3E"));
</script>

Exactly what we need!

Font resizing and printing with jQuery

You’ve probably seen the three A’s on various websites, especially public sector websites; that you can click on to increase the font size on the web site. I’m personally not a huge fan of those. In my opinion it is much better to explain to the user how they can use the built in browser functionality to do this instead. This will also increase the font size on all websites not just the one you created. You could do this by linking to a page with an explanation and screenshots of how to do this in the most popular browsers.

However sometimes I still need to create those three A’s. Below is a little jQuery plugin that does this.

See the demo

Font resizing plugin

This plugin will create an ordered list with three links to increase the font size. The plugin adds three different classes to the body element, which then can be used to set the base font size.

body.small {
    font-size: 80%;
}
 
body.medium {
    font-size: 120%;
}
 
body.large {
    font-size: 140%;
}

The plugin code.

(function($) {
    $.fn.fontresizing = function(customOptions) {
        var options = $.extend({}, $.fn.fontresizing.defaultOptions, customOptions);
        var bodyClasses = '' + options.smallClass + ' ' + options.mediumClass + ' ' + options.largeClass + '';
        return this.each(function() {
            $(this).append('<ol class="' + options.fontresizingClass + '"><li><a href="" class="' + options.smallClass + '">A</a></li><li><a href="" class="' + options.mediumClass + '">A</a></li><li><a href="" class="' + options.largeClass + '">A</a></li></ol>');
 
            $('ol.' + options.fontresizingClass + ' a').click(function() {
                var cssClass = $(this).attr('class');
                $('body').removeClass(bodyClasses).addClass(cssClass);
                createCookie('fontresizingClass', cssClass, options.cookieDuration);
 
                return false;
            });
 
            var fontresizingClass = readCookie('fontresizingClass');
            if (fontresizingClass == options.smallClass || fontresizingClass == options.mediumClass || fontresizingClass == options.largeClass) {
                $('body').removeClass(bodyClasses).addClass(fontresizingClass);
            }
 
            // cookie functions http://www.quirksmode.org/js/cookies.html
            function createCookie(name, value, days) {
                if (days) {
                    var date = new Date();
                    date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
                    var expires = "; expires=" + date.toGMTString();
                }
                else var expires = "";
                document.cookie = name + "=" + value + expires + "; path=/";
            }
 
            function readCookie(name) {
                var nameEQ = name + "=";
                var ca = document.cookie.split(';');
                for (var i = 0; i < ca.length; i++) {
                    var c = ca[i];
                    while (c.charAt(0) == ' ') c = c.substring(1, c.length);
                    if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
                }
 
                return null;
            }
        });
    };
 
    $.fn.fontresizing.defaultOptions = {
        smallClass: 'small',
        mediumClass: 'medium',
        largeClass: 'large',
        fontresizingClass: 'font-resizing',
        cookieDuration: 365
    };
})(jQuery);

Breaking it down

We start by creating a private scope for our jQuery code. The reason for this is so we don’t have to worry about conflicts with other libraries.

(function($) {
...
})(jQuery);

We then add a new method to the jQuery.fn object

(function($) {
    $.fn.fontresizing = function(customOptions) {
    ...
    };
})(jQuery);

Combine the custom options with the default options.

(function($) {
    $.fn.fontresizing = function(customOptions) {
        var options = $.extend({}, $.fn.fontresizing.defaultOptions, customOptions);
        ...
    };
 
    $.fn.fontresizing.defaultOptions = {
        smallClass: 'small',
        mediumClass: 'medium',
        largeClass: 'large',
        fontresizingClass: 'font-resizing',
        cookieDuration: 365
    };
})(jQuery);

Next we iterate over the jQuery object or jQuery wrapper set, and return “this” (the current jQuery object), so we don’t break the jQuery chaining functionality.

(function($) {
    $.fn.fontresizing = function(customOptions) {
        var options = $.extend({}, $.fn.fontresizing.defaultOptions, customOptions);
        var bodyClasses = '' + options.smallClass + ' ' + options.mediumClass + ' ' + options.largeClass + '';
        return this.each(function() {
        ...
        });
    };
 
    $.fn.fontresizing.defaultOptions = {
        smallClass: 'small',
        mediumClass: 'medium',
        largeClass: 'large',
        fontresizingClass: 'font-resizing',
        cookieDuration: 365
    };
})(jQuery);

Now we come to the actual code for creating the links and attaching click events to them.

(function($) {
    $.fn.fontresizing = function(customOptions) {
        var options = $.extend({}, $.fn.fontresizing.defaultOptions, customOptions);
        var bodyClasses = '' + options.smallClass + ' ' + options.mediumClass + ' ' + options.largeClass + '';
        return this.each(function() {
            $(this).append('<ol class="' + options.fontresizingClass + '"><li><a href="" class="' + options.smallClass + '">A</a></li><li><a href="" class="' + options.mediumClass + '">A</a></li><li><a href="" class="' + options.largeClass + '">A</a></li></ol>');
 
            $('ol.' + options.fontresizingClass + ' a').click(function() {
                var cssClass = $(this).attr('class');
                $('body').removeClass(bodyClasses).addClass(cssClass);
                createCookie('fontresizingClass', cssClass, options.cookieDuration);
 
                return false;
            });
            ...
        });
    };
 
    $.fn.fontresizing.defaultOptions = {
        smallClass: 'small',
        mediumClass: 'medium',
        largeClass: 'large',
        fontresizingClass: 'font-resizing',
        cookieDuration: 365
    };
})(jQuery);

Lastly we have the cookie functions to read and create the cookie to hold the font size choices the user has made.

...
            var fontresizingClass = readCookie('fontresizingClass');
            if (fontresizingClass == options.smallClass || fontresizingClass == options.mediumClass || fontresizingClass == options.largeClass) {
                $('body').removeClass(bodyClasses).addClass(fontresizingClass);
            }
 
            // cookie functions from http://www.quirksmode.org/js/cookies.html
            function createCookie(name, value, days) {
                if (days) {
                    var date = new Date();
                    date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
                    var expires = "; expires=" + date.toGMTString();
                }
                else var expires = "";
                document.cookie = name + "=" + value + expires + "; path=/";
            }
 
            function readCookie(name) {
                var nameEQ = name + "=";
                var ca = document.cookie.split(';');
                for (var i = 0; i < ca.length; i++) {
                    var c = ca[i];
                    while (c.charAt(0) == ' ') c = c.substring(1, c.length);
                    if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
                }
 
                return null;
            }
...

Usage

To use the plugin simply add a reference to the jQuery library file, and copy the code into your page. After you’ve done that you can use it by calling the fontresizing method

// With default options
jQuery(function($) {
    $('#tools').fontresizing();
});
 
// With custom options
jQuery(function($) {
    $('#tools').fontresizing({
        smallClass: 'small',
        mediumClass: 'medium',
        largeClass: 'large',
        fontresizingClass: 'font-resizing',
        cookieDuration: 365
    });
});

Print plugin

Another thing that many clients ask for is a print link. Below is a little jQuery plugin that creates the link and attaches a click event that triggers the browsers print dialog.

(function($) {
    $.fn.print = function(customOptions) {
        var options = $.extend({}, $.fn.print.defaultOptions, customOptions);
 
        return this.each(function() {
            $(this).append('<a href="" class="' + options.printClass + '">' + options.printText + '</a>');
 
            $('a.' + options.printClass + '').click(function() {
                window.print();
                return false;
            });
        });
    };
 
    $.fn.print.defaultOptions = {
        printClass: 'print',
        printText: 'Print'
    };
})(jQuery);

Here we also generate the link with JavaScript, so that users who don’t have JavaScript wont see it – since they cannot use it anyway. When you click on the link you’ll see the print dialog; use this with CSS to create a printer friendly version of your page.

To create CSS rules for print either use the @media

@media print {
  /* CSS rules here */
}

or link to an external CSS file and add the media=”print” attribute.

<link rel="stylesheet" href="styles/print.css" media="print" type="text/css" />

Usage

// With default options
jQuery(function($) {
    $('#tools').print();
});
 
// With custom options
jQuery(function($) {
    $('#tools').print({
        printText: 'Print this page'
    });
});

Download the code or see the demo

ASP.NET Validation and jQuery

Recently I was working on a simple form that used the ASP.NET Validation controls for validation. This worked fine when doing a regular Postback to the server with the data. However I wanted to use Ajax to make it a little faster and the user experience better.

One of the problems I had was that when I hooked into my buttons click event with jQuery I needed to make sure the form validated before sending it to my web method. That’s when I came across this little piece of JavaScript.

if (Page_IsValid) {
}

Which does the same thing as Page.IsValid on the server

Below is some simple sample code.

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
 
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js" type="text/javascript"></script>
 
    <style type="text/css">
        fieldset em { color: Red; }
    </style>
</head>
<body>
    <form id="form1" runat="server">
    <fieldset>
        <p>
            <asp:Label runat="server" AssociatedControlID="txtName">Name: <em title="Required">*</em></asp:Label>
            <asp:TextBox runat="server" ID="txtName" ValidationGroup="Form" />
            <asp:RequiredFieldValidator runat="server" ControlToValidate="txtName" Text="Name is missing" ValidationGroup="Form" />
        </p>
        <p>
            <asp:Button runat="server" ID="btnSend" Text="Submit" ValidationGroup="Form" OnClick="btnSend_Click" />
        </p>
    </fieldset>
    <p id="result">
            <asp:Literal runat="server" ID="ltlResult" />
        </p>
    </form>
 
    <script type="text/javascript">
        $("#<%= btnSend.ClientID %>").click(function() {
            if (Page_IsValid) {
                var name = $("#<%= txtName.ClientID %>").val();
 
                $.ajax({
                    type: "POST",
                    url: "Default.aspx/YourName",
                    data: "{name:'" + name + "'}",
                    contentType: "application/json; charset=utf-8",
                    dataType: "json",
                    success: function(msg) {
                        $("#result").text(msg.d);
                    }
                });
 
                return false;
            }
        });
    </script>
</body>
</html>
using System;
using System.Web;
using System.Web.Services;
using System.Web.UI;
 
public partial class _Default : Page 
{
    protected void btnSend_Click(object sender, EventArgs e)
    {
        if (Page.IsValid)
        {
            ltlResult.Text = string.Format("Your name is: {0}", HttpUtility.HtmlEncode(txtName.Text));
        }
    }
    // Make sure to add the [WebMethod] attribute and make the method static
    [WebMethod]
    public static string YourName(string name)
    {
        return string.Format("Your name is: {0}", HttpUtility.HtmlEncode(name));
    }
}

The reason I still have the btnSend_Click method is in case the user does not have JavaScript enabled.

Measuring JavaScript performance

According to Response Time overview, the response time for JavaScript code to execeute should be no more than 0.1 seconds (100 milliseconds). Especially on JavaScript/Ajax heavy sites this can be a problem. That’s why it is always good thing to check the performance of your code.
To measure your JavaScript you can use the code below:

function somefunction() {
    var start = new Date().getMilliseconds();
    // code here
    var stop = new Date().getMilliseconds();
    var executionTime = stop - start;
    console.log("Execution time " + executionTime +
        " milliseconds"); // or alert("Execution time " + executionTime + " milliseconds");
}

Note that Firebug for Firefox also has a code profiler which does the same thing. To use it install Firebug, enable firebug (f12), activate the console window, press the profile button, refresh the page, click the profile button again (more information: Firebug JavaScript Debugger and Profiler).
Firebug profiler

© Copyright Frederik Vig. Based on Fluid Blue theme