Recommend this
on Google+

Recommend this
on Google+

Wednesday, October 20, 2010

Lack of a string-trimming function in Javascript…

I love using Javascript in my web applications to reduce network traffic as much as possible by minimizing server requests. Today, I noticed a strange and ridiculous flaw in Javascript API. There is no method to remove leading and trailing spaces, as we have in all other standard scripting languages. How could the designers & vendors miss this simple "trim()" functionality?
As a follower of “Bhagavad-Gita”, I realized that simply pointing out mistake with no effort to provide solution is against “Dharma”. Hence, as JavaScript doesn't include a trim method natively I decided to write my own code and add to "string.prototype" for enabling access globally. I've tried to write the code that runs optimally. Intutively, I tried to write a number of functions using various approaches. It helped me learn few interesting facts; which most of us just ignore and write lengthy crude code, performing inappropriately.

  • Using only regular expressions:
    Here we use pure regular expressions to perform trim(). One would wonder why I wrote a conditional statement in between. The first regular expression within “if” works best for short strings. Where as the one in “else” performs faster with long strings — when efficiency matters. The speed is largely due to a number of optimizations internal to JavaScript regex interpreters which the two discrete regexes here trigger. Specifically, the pre-check of required character and start of string anchor optimizations, possibly among others.
    function trim(input_string)
    {
        if (input_string.length < 25)
        {
    /*
                If the input string is not too long. I assumed a string to be
                considered short in length if it contains less than 25  characters.
                I chose 25 just randomly.
          */
             
          return input_string.replace(/^\s*([\S\s]*?)\s*$/, '$1'
    );   
        }
        else
        {
    /*           If the input string is longer. I assumed a string to be
               considered long in length if it contains 25 or more characters.
             */
          return input_string.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
        }
    }
  • No regular expressions

    I wondered how an implementation which used no regular expressions would perform. Here's what I tried. With long strings it blows away the competition, even with first approach described above. Trickily, this doesn’t mean regular expressions are poor performers. Regular expressions start with trimming leading whitespace and continue till the last of string. Hence, they do not offer a direct approach to jump to the last -character. Hence, this approach uses the second loop to directly jump to last, and works backwards until it finds a non-whitespace character.


    function trim(input_string){
        var whitespace = '\n\r\t\f\x0b\xa0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000';
        for (var i = 0; i < input_string.length; i++)
        {
            if (whitespace.indexOf(input_string.charAt(i)) === -1)
            {
                input_string = input_string.substring(i);
                break;
            }
        }
        for (i = input_string.length - 1; i >= 0; i--)
        {
            if (whitespace.indexOf(input_string.charAt(i)) === -1)
            {
                input_string = input_string.substring(0, i + 1);
                break;
            }
        }
        return whitespace.indexOf(input_string.charAt(0)) === -1 ? input_string : '';
    }
  • Mixed approach

    Here, I experimented a mixed-implementation combining the best of both worlds. Knowing that,  Regular-Expressions are efficient at trimming leading whitespace; and second loop of the above method's advantage at removing trailing spaces. One more profit of using this approach is it utilizes least amount of code, and provides best performance.
    function trim(input_string)
    {
        input_string = input_string.replace(/^\s+/, '');
        for (var i = input_string.length - 1; i >= 0; i--)
        {
            if (/\S/.test(input_string.charAt(i)))
            {
                input_string = input_string.substring(0, i + 1);
                break;
            }
        }
        return input_string;
    }
Please let me know in case of any mistakes...
K Chandrasekhar Omkar,
kcomkar@gmail.com




Tuesday, October 19, 2010

Tuesday, September 14, 2010

Talking about HTML5 - Offline Application Caching

 

Offline Web Applications

Introduction:

Offline-Application-Cache (or, simply AppCache) is not a new thing. It’s the same caching we always heard about in web-apps development to facilitate client side data storage. Yet, the way it’s done is new J . All the traditional ways of storing data on client include Cookies, Sessions & HTML headers to instruct browser to cache. Infact, there was no standard to make a web page available offline, so that only those features that require connection can be disabled, keeping the rest still available to the client. Also, those features require connection, can be substituted with suitable content, to notify the user gracefully, instead of some error page saying “Page cannot be displayed”.

What it does?

For example, consider a webpage that displays some various types of content such as, quarterly business reports, stock market share price etc. Suppose, while browsing this application, the user lost his network connection, after which the user clicks on the “Next” to see the other part of the report. Obviously, an error page comes in place of the webpage, to notify user that the connection was lost.

Now, let’s analyze the page. Quarterly reports is something that changes once in 3 months, while Stock ticker changes every second. Hence, it would have been sensible to make the report data available, even when offline as it has nothing to do with the server. Instead the webpage has been replaced with an error message??? It is exactly where AppCache provides a seamless browsing experience very gracefully. However, the stock ticker cannot be made available offline as the data changes constantly. Yet, the user can be notified meaningfully, and enhance his browsing experience by keeping past data available till the connection to the server restores.

This is coherent with the work of GMail, that works even while offline. If you hadn't observed this, try now. Login to your GMail from Safari/Chrome and disconnect from your network. You can continue browsing emails, and prepare drafts, before getting back online. It's because of Google Gears, a technology which google came up with GMail, and gave inspiration for this feature of HTML5. Thanks to Google!

Technically speaking...

Lets explore technical details, and it's implementation now. HTML5, adds new classes to the DOM for making this possible. JavaScript, is used to deal with the API. Don't worry if you didn't understand this jargon like DOM, API...so on.... If you have some experience with HTML & JavaScript, you may ignore this section.

Basically, however different & new web-application-development technologies like ASP.NET, JSP, PHP blah..blah... existed, the sole aim is to produce some HTML to show as output. HTML is the only language of web browsers. HTML uses a set of tags, to instruct the browser, how-&-what to show. Based upon our inputs & interactions, browser submits requests on your behalf for processing. It's the server responsibility to do necessary processing, and respond to the client with suitable output again in the form of HTML.

Imagine a website being used by billions, at once. All the requests, responses make huge amounts of data to flow, contributing network congestion. Hence, it would be ridiculous & costly to send a request to the server for a very minor operation. For example, take an online ticket booking server, which is used for planning trips, booking all tickets. Suppose there is a phone-number, and email ID field that is required at the time of booking tickets, and you forgot to fill it before submitting. It then becomes the server's responsibility to ensure valid data is being processed, apart from various other business transactions. For such a busy server, validating input data would surely become an overhead, and has considerable impact on the performance. Hence, it should be ensured before submitting, that all inputs are fed properly. So, JavaScript comes into the picture here. This script is executed suitably, at the client end to ensure that all data adheres to specifications. Script then submits data after thorough validation. Doesn't it reduce atleast half of the requests being made to the server? Obviously, the amount of traffic in the network gets reduced drastically.

Apart from this, JavaScript also enhances the browsing experience by facilitating stunning user-interfaces, dynamic updates, user alerts etc. For JavaScript to run, the browser must provide necessary infrastructure to understand and access various components on the page. For example, to display a message in the form of alert, window.alert("MY MESSAGE") is used. Here, browser provides a "window" object, with "alert()" functionality which is used through JavaScript.

Similarly, to support new HTML5, browser needs to provide support for all new features. Hence, ensure that you develop HTML5 related stuff to run on most recent versions. I'd suggest to use Safari 4.0.5 that provides excellent support as per the W3C Specs.

It's almost ready.

Please refer to the specs document by W3C reference API, and documentation.

Tools & Examples

I've prepared a template kind of code, that can be used without much effort. Just edit the parts of this code and add your custom logic, to run it.

 

<!DOCTYPE html>

<html manifest="FullFeatured.manifest">

<!--

       Author: K Chandrasekhar Omkar

       E-Mail: kcomkar@gmail.com

       Phone: +91-9538856655

-->

<head>

    <title>HTML5: Application Caching</title>

    <meta http-equiv="Author" content="K Chandrasekhar Omkar" />

    <meta http-equiv="E-Mail" content="kcomkar@gmail.com" />

    <meta http-equiv="Context" content="Test content for HTML5-ApplicationCache" />

    <style type="text/css">

        .style1

        {

            font-family: Consolas;

            font-size: LARGE;

            text-align: CENTER;

            margin-left: 80px;

            text-weight: BOLD;

        }

    </style>

    <script type="text/javascript">

        function onCacheChecking(e)

        /** DOMApplicationCache: CHECKING event's handler

        *   Argument e is the event parameter

        */

        {

            printOutput("CHECKING", "Contents of the manifest are being checked.", 0);

        }

 

        function onCacheCached(e)

        /** DOMApplicationCache: CACHED event's handler

        *   Argument e is the event parameter

        */

        {

            printOutput("<tr><td align='right'><strong>CACHED</strong></td><td>All the resources mentioned in the manifest have been downloaded</td></tr>", 0);

        }

 

        function onCacheNoUpdate(e)

        /** DOMApplicationCache: NOUPDATE event's handler

        *   Argument e is the event parameter

        */

        {

            printOutput("<tr><td align='right'><strong>NOUPDATE</strong></td><td>Manifest file has not been changed. No updates took place.</td></tr>", 0);

        }

 

        function onCacheUpdateReady(e)

        /** DOMApplicationCache: UPDATEREADY event's handler

        *   Argument e is the event parameter

        */

        {

            printOutput("<tr><td align='right'><strong>UPDATEREADY</strong></td><td>Changes have been made to manifest file, and were downloaded.</td></tr>", 0);

        }

 

        function onCacheError(e)

        /** DOMApplicationCache: ERROR event's handler

        *   Argument e is the event parameter

        */

        {

            printOutput("<tr><td align='right'><strong>ERROR</strong></td><td>An error occured while trying to process manifest file.</td></tr>", 0);

        }

 

        function onCacheObselete(e)

        /** DOMApplicationCache: OBSELETE event's handler

        *   Argument e is the event parameter

        */

        {

            printOutput("<tr><td align='right'><strong>OBSOLETE</strong></td><td>Either the manifest file has been deleted or renamed at the source</td></tr>", 0);

        }

 

        function onCacheDownloading(e)

        /** DOMApplicationCache: DOWNLOADING event's handler

        *   Argument e is the event parameter

        */

        {

            printOutput("<tr><td align='right'><strong>DOWNLOADING</strong></td><td>Downloading resources into local cache.</td></tr>", 0);

        }

 

        function onCacheProgress(e)

        /** DOMApplicationCache: PROGRESS event's handler

        *   Argument e is the event parameter

        */

        {

            printOutput("<tr><td align='right'><strong>PROGRESS</strong></td><td>Download in process.</td></tr>", 0);

        }

 

 

        function printOutput(eventName, statusMessages, howToTell)

        /**

        * Outputs information about an event with its description

        * @param {String} eventName Should be one of the event being processed by appCache when this message is sent

        * @param {String} statusMessages The message string to be displayed that describes the event

        * @param {Integer} howToTell Specifies if the output is to be written onto document(0) or alert(1) or both(2)

        */

        {

            try {

                if (howToTell == 2) {

                    document.getElementById("stat").innerHTML += formatComment(eventName, statusMessages);

                    window.alert(statusMessages);

                }

                else if (howToTell == 0) {

                    document.getElementById("stat").innerHTML += formatComment(eventName, statusMessages);

                }

                else if (howToTell == 1) {

                    window.alert(statusMessages);

                   

                }

            }

            catch (IOExceptionOutput) {

                window.alert(IOExceptionOutput);

            }

        }

 

        function formatComment(eventName, statusMessage) {

            statusMessage = "<tr><td align='right'><strong>" + eventName + "</strong></td><td>" + statusMessage + "</td></tr>";

            return statusMessage;

        }

 

        function initiateCaching()

        /** This method initiates all the required variables, events and related members of the document.

        *   It contains the following members:

        *   S.No. VARIABLE NAME     SCOPE      DESCRIPTION

        *   ----- -------------    --------    ----------------------------------------------------

        *     1     ONLY_DOC        GLOBAL     This is a CONSTANT used to specify

        *                                      the mode for printOutput() method.

        *                                      Indicates that output should be printed on HTML page.

        *

        *     2     ONLY_ALERT      GLOBAL     This is a CONSTANT used to specify

        *                                      the mode for printOutput() method.

        *                                      Indicates that output should be displayed in alert

        *                                      dialog window, only.

        *

        *     3     BOTH_DOC_ALERT  GLOBAL     This is a CONSTANT used to specify

        *                                      the mode for printOutput() method.

        *                                      Indicates that output should be printed on HTML page

        *                                      & displayed in alert as well.     

        **/

        {

            var ONLY_DOC = 0;

            var ONLY_ALERT = 1;

            var BOTH_DOC_ALERT = 2;

            try {

                if (window.applicationCache) {

                    var appcache = window.applicationCache;

                    //printOutput("<tr><td>BROWSER COMPATIBILITY</td><td>SUCCESS!! AppCache works on this browser.</td></tr>", 0);

                    appcache.addEventListener('checking', onCacheChecking, false);

                    appcache.addEventListener('cached', onCacheCached, false);

                    appcache.addEventListener('noupdate', onCacheNoUpdate, false);

                    appcache.addEventListener('downloading', onCacheDownloading, false);

                    appcache.addEventListener('progress', onCacheProgress, false);

                    appcache.addEventListener('updateready', onCacheUpdateReady, false);

                    appcache.addEventListener('error', onCacheError, false);

                    appcache.addEventListener('obsolete', onCacheObselete, false);

                }

                else {

                    document.getElementById("stat").innerHTML = "<tr><td colspan=2>Failure! AppCaching is not supported on this browser yet.</td></tr>";

                }

            }

            catch (UnknownError) {

 

                window.alert('Internet Explorer does not support Application Caching yet.\nPlease run me on Safari or Firefox browsers\n\n');

                stat.innerHTML = "<tr><td colspan='2'>Failure! I cant work.</td></tr>";

            }

 

        }

 

    </script>

</head>

<body onload="initiateCaching()">

    <table id="stat" name="stat" style="text-transform: capitalize; color: #000000; font-size: medium;

        font-weight: bold; font-style: normal; font-variant: small-caps; font-family: Consolas;

        table-layout: auto; border: thin dotted #000000;" cellspacing="10">

    </table>

    <hr />

    <a style="text-align: center; font-family: Consolas; font-size: large; font-variant: small-caps;"

        href="index.html">Go to Index page</a>

    <hr />

    <a style="font-family: Consolas; font: Consolas; font-size: large; font-variant: small-caps"

        href="javascript:var sURL = unescape(window.location.pathname);window.location.href=sURL;">

        Reload this page</a>

    <hr />

</body>

</html>