Friday, June 29, 2012

Creating a very basic HTML5 form validation polyfill

The latest versions of Google Chrome (16+), Mozilla Firefox (8+), and Internet Explorer (10+) all support HTML5 client-side form validation. You can use several attributes with INPUT elements to perform client-side validation, including the required, pattern, min, max, step, and maxlength attributes.
For example, you use the 'required' attribute to require a user to enter a value for an INPUT element. The following HTML form demonstrates how you can make the 'Name' field required. Notice that the value of the 'title' attribute is used to display the validation error message.
<form>
     Your Name:
     <input required="required" title="Your name is required!" />
     <input type="submit" value="Submit" />
</form>

If you want to perform client-side validation in older browsers, you need a polyfill that falls back on JavaScript. Using a polyfill, browsers that support HTML5 form validation will use it, and other browsers will run the JavaScript to perform the validation.
One of my HTML5 apps runs in Internet Explorer 9 and needs client-side form validation. IE9 does not support this natively so I had a look at the Webshims Lib polyfill. It actually looked very good but has many more features than I needed (I only needed 'required' field validation). So I decided to write my own, very basic, form validation polyfill. Here it is:
var FormValidationPolyfill = (function (window) {
    var document = window.document, supportedValidationAttrs;

    function init() {
        // Determine which of the HTML5 form validation properties
        // are supported by the browser.
        var inputElem = document.createElement('input');
        supportedValidationAttrs = (function (props) {
            for (var n = 0, attrs = {}; n < props.length; n++) {
                attrs[props[n]] = (props[n] in inputElem);
            }
            return attrs;
        })('required pattern'.split(' '));
    }

    function formSubmitHandler(e) {
        var errorMessage, childNodes = this.childNodes, invalidElement,
            inputElements = this.getElementsByTagName('input');

        for (n = 0; n < inputElements.length; n++) {
            var element = inputElements[n];
            if (!invalidElement && !supportedValidationAttrs.required) {
                // The HTML5 'required' input validation attribute
                // is not supported natively by the browser.
                if (element.hasAttribute('required')) {
                    // The input element has a required attribute
                    // and must have a value.
                    if (!element.value) {
                        invalidElement = element;
                        errorMessage = 'Please fill in all the required fields.';
                    }
                }
            }
            if (!invalidElement && !supportedValidationAttrs.pattern) {
                // The HTML5 'pattern' input validation attribute
                // is not supported natively by the browser.
                var pattern = element.getAttribute('pattern');
                // If a regex pattern has been specified, check whether
                // the element's value matches it.
                if (pattern && element.value && !(new RegExp(pattern)).test(element.value)) {
                    invalidElement = element;
                    errorMessage = 'Please match the requested format.';
                }
            }
        }

        if (invalidElement) {
            // One of the input fields is not valid.
            if (invalidElement.hasAttribute('title'))
            {
                // The invalid input element has a 'title' attribute,
                // whose value will be used as the validation error
                // message (the default behavior in HTML5 form
                // validation).
                errorMessage = invalidElement.getAttribute('title');
            }
            // Do not submit the form.
            e.preventDefault();
            // Show the validation error message.
            alert(errorMessage);
        }
    }

    function addForm(id) {
        var form = document.getElementById(id);
        if (form) {
            form.addEventListener('submit', formSubmitHandler, false);
        }
    }

    init();

    return {
        addForm: addForm
    }
})(window);
Include this script in the HEAD section of your page. Then, in the page load event, initialize each form (passing its ID value) that you want to be validated as follows:
FormValidationPolyfill.addForm('my-form-id');
Simply add a 'required' attribute to each required input field, and an optional 'title' attribute for the validation error message, like in the example at the beginning of this post.

Tuesday, June 12, 2012

How to detect when a JavaScript or CSS file has been fully loaded dynamically

In a previous post I have described how to dynamically load JavaScript or CSS files into an HTML page. After we have loaded the files, we obviously want to start using them, i.e. run some loaded script function, or layout an HTML element by assigning a loaded CSS class to it.
But how do we know when a script or CSS file has been fully loaded by the browser and ready to use? The JavaScript case is easy to solve, that is if you control a script's source code. If you don't, you will have to fall back on some browser-dependent event handlers.
If you own the contents of the JavaScript file being loaded, a much simpler approach suffices. Let's assume you always want to call the same callback function when a script is loaded. If that's the case, just put a call to the callback function on the last line of the JavaScript file. After the rest of the file has been loaded, the callback function gets called.

Here is an example of a script file being loaded dynamically:

function initGui()
{
    // ...
    // Lots of code
    // ...
}

// ...
// More function and global variable definitions
// ...

// Notify the loading page that the script has been fully loaded.
// That page can now safely call the 'initGui' function above.
callback();

Now for the CSS case. As far as I know, there is no reliable 'load' indicator for CSS files that are included dynamically. This is confirmed by Stoyan's post. I agree with that post that 'to check for changes in the styling of a specific element' (the fifth of the 'Options for the magic part') is a bad idea. However, the idea inspired me to try a technique that also depends on an element's style becoming available after the CSS file that contains it has been loaded. And thus, we actually know that the file has been loaded!

The technique that I describe below is still in an experimental stage, and can only be used if you have control over the content of the CSS file (which is the case in most of my own projects). I have successfully tested it in the latest versions of Chrome, Firefox and Safari (for the desktop), and hope it will also work in the upcoming Internet Explorer 10. It depends on CSS3 transitions and the event that fires after a transition completes. At the end of the CSS file that is included dynamically in my HTML page, I define a 'helper' element that (for the moment) must contain several browser-specific transition properties:

/*
... Other CSS declarations ...
*/

/* CSS declarations of a helper element that must be added to the HTML page that uses the CSS file */
#css-loader
{
    width: 0px;
    height: 0px;
    visibility: hidden;
    transition: visibility 0.001ms;  /* Standard */
    -o-transition: visibility 0.001ms;  /* Opera */ 
    -ms-transition: visibility 0.001ms;  /* IE */ 
    -moz-transition: visibility 0.001ms;  /* Firefox */
    -webkit-transition: visibility 0.001ms;  /* Chrome and Safari */
}

Note the very small transition duration of 0.001ms. It is required since otherwise there will be no 'transition' at all and no 'transition end' event will be triggered. The css-loader helper element, that is visible by default when it is added to the brower DOM, is being hidden through its CSS declarations. This happens when the CSS file has been loaded, and triggers the 'transition end' event. Thus, when that event fires we know that the file has been loaded.

Here is the JavaScript that dynamically includes the CSS file. I have added comments to explain the code.

// Returns the browser-dependent event name that corresponds
// to the 'transition end' event.
function getTransitionEvent(helperElement) {
    var transitions = {
        'transition': 'transitionEnd',  // Standard
        'OTransition': 'oTransitionEnd',  // Opera
        'MSTransition': 'msTransitionEnd',  // IE
        'MozTransition': 'transitionend',  // Firefox
        'WebkitTransition': 'webkitTransitionEnd'  // Chrome and Safari
    }

    for (var t in transitions) {
        if (helperElement.style[t] !== undefined) {
            return transitions[t];
        }
    }
    // In case the above loop didn't return a result:
    return 'transitionEnd';
}

// Dynamically loads the CSS file that corresponds to the specified URL.
// When the file has been loaded, the specified callback function
// is called.
function loadCss(url, callback) {
    // Create a helper element and append it to the HTML page
    // temporarily.
    var cssLoader = document.createElement('div');
    cssLoader.setAttribute('id', 'css-loader');
    document.body.appendChild(cssLoader);

    var transitionEnd = getTransitionEvent(cssLoader);
    cssLoader.addEventListener(transitionEnd, function () {
        // Clean up: remove the event handler from the helper element.
        this.removeEventListener(transitionEnd, arguments.callee, false);
        // Remove the helper element from the HTML document,
        // we no longer need it.
        document.body.removeChild(cssLoader);
        // Notify the calling function that the CSS file has been loaded.
        if (callback) {
            callback();
        }
    }, false);

    // Create a CSS 'link' element to be added
    // to the page's HEAD section.
    var head = document.head || document.getElementsByTagName('head')[0];
    var link = document.createElement('link');
    link.rel = 'stylesheet';
    link.type = 'text/css';
    link.href = url;

    // Load the CSS file. WebKit browsers (Chrome and Safari) seem to
    // need the setTimeout in order for the 'transition end' event to
    // be fired. I haven't yet been able to find out why.
    setTimeout(function () {
        head.appendChild(link);
    }, 0);
}

// Dynamically include a CSS file after the HTML page has finished
// loading. Specify the URL of the file and a callback function
// that should be called when the CSS file has been loaded completely.
window.addEventListener('load', function () {
    loadCss('test.css', function () { alert('CSS file loaded!'); });
});

Like I have said before, the above code is still in an experimental stage and there may be some major flaw in it that I haven't noticed. If you find one, please let me know by leaving a comment below.

Sunday, May 27, 2012

Some simple but effective JavaScript and CSS tips to make your iPad web app feel native

When I am developing HTML5 games, one of the devices that I like them to be run on is the iPad. I have become a fan of this touchscreen device because children (including my daughter) can learn to use it very quickly. I usually do not try to make my apps look like native iPad or iPhone apps (where others go to great lengths). However, I do like my apps to feel native, which starts with being able to run them full screen. The Mobile Safari browser on the iPad allows users to add a web app or page to their home screen. If I, the developer, add the following meta tag to the HEAD section of my HTML, the app will be run full screen when it is launched from the home screen:
<meta name="apple-mobile-web-app-capable" content="yes" />
Next you may have noticed that Mobile Safari has the interesting ability to make web pages have a feeling of elasticity to them. This 'elastic scrolling' occurs when you drag web content past the bottom or top of the page. The page gets elastically moved away from the URL bar or the button bar, or the top or bottom of the screen if it's in full screen mode. In something that is designed more as a web 'app' than as web 'content', the elasticity can become annoying. Therefore I disable it in my apps by adding the following JavaScript:
document.addEventListener('touchmove',
    function (e) {
        e.preventDefault();
    }, false);

To conclude this post, I will describe two WebKit-specific CSS properties that also make your iPad web app feel less like a web site:
-webkit-tap-highlight-color: rgba(0,0,0,0);
-webkit-touch-callout: none;

When a link is tapped in Mobile Safari, a grey background appears over the link by default. This is pretty ugly, so I add the -webkit-tap-highlight-color property to my CSS to disable it.

The -webkit-touch-callout: none; disables the 'callout popup' that appears when a user taps and holds a hyperlink for a few seconds.

Be sure to check out this blog regularly if you are interested in more tips to make your iPad web app feel more 'native'.

Saturday, May 26, 2012

Localizing an HTML5 application using JavaScript resource files

In my previous post I have been writing about server-side localization in ASP.NET MVC. In one of my HTML5 applications, that is cached for offline use, localization resources are also accessed client-side, from JavaScript. In this post I will propose a simple client-side localization system that I have created for that purpose. The localization resources will be defined in JavaScript (JSON) files, one for each language that we want to support. Depending on a language variable the correct file is dynamically loaded at the application start. To refer to a specific resource from within HTML elements I have borrowed a method from ASP.NET that I have mentioned at the beginning of my previous post. A resource key attribute is added to each HTML element that I want to localize. HTML5 allows for custom element attributes whose names start with the 'data-' prefix, so I have named the resource key attribute 'data-res'. Here is an example of a localized HTML form button:
<input type="submit" value="Submit" title="Send your message"
    data-res="btnSubmit" />
The 'data-res' attribute refers to a key in a JSON resource file. A French resource file may look like this:
var resources = {
    "btnSubmit.value": "Envoyer",
    "btnSubmit.title": "Envoyez votre message",
    "ConfirmText": "Etes vous sûr ?"
}
Using a single key ('btnSubmit' in the example) we can translate multiple properties on a single HTML element, e.g. the value and title of a button. Note the key 'ConfirmText', which is an example resource that does not apply to an HTML element but is used by some JavaScript application code directly. I will illustrate this later.
The resource strings for the default language (English in our case) should already be included in the HTML page. Those are applied by the browser automatically if we do not override them in a resource file. This makes the HTML page much better readable and testable, since no resource file interpreter is in place yet.
The resource file that matches the user's current language is loaded at the application start. We assume that there is some init JavaScript method that is called when the window 'load' event fires. We add the following code to that method:

// Try to get language info from the browser.
var lang = window.navigator.userLanguage || window.navigator.language;
if (lang) {
    if (lang.length > 2) {
        // Convert e.g. 'en-GB' to 'en'. We do not support
        // resources for specific cultures at the moment.
        lang = lang.substring(0, 2);
    }
    // Include the languages that we want to support
    // in the following condition.
    // If we do not support the current navigator language,
    // default to english.
    if (lang != 'en' && lang != 'fr' && lang != 'nl') {
        lang = 'en';
    }
}
else {
    // Default to english if we did not succeed in getting
    // a language from the browser.
    lang = 'en';
}
// Construct a language-specific resource path.
var resourcePath = 'resources/strings.' + lang + '.js';
// Get a reference to the HEAD element of the HTML page.
var head = document.head || document.getElementsByTagName('head')[0];
// Dynamically add the resourcePath to the HEAD element
// to start loading the resources.
var scriptEl = document.createElement('script');
scriptEl.type = 'text/javascript';
scriptEl.src = resourcePath;
head.appendChild(scriptEl);

After this code has been run, a global JavaScript variable resources will exist that our resource interpreter can parse. Here is how that interpreter looks like:

// Get all HTML elements that have a resource key.
var resElms = document.querySelectorAll('[data-res]');
for (var n = 0; n < resElms.length; n++) {
    var resEl = resElms[n];
    // Get the resource key from the element.
    var resKey = resEl.getAttribute('data-res');
    if (resKey) {
        // Get all the resources that start with the key.
        for (var key in resources) {
            if (key.indexOf(resKey) == 0) {
                var resValue = resources[key];
                if (key.indexOf('.') == -1) {
                    // No dot notation in resource key,
                    // assign the resource value to the element's
                    // innerHTML.
                    resEl.innerHTML = resValue;
                }
                else {
                    // Dot notation in resource key, assign the
                    // resource value to the element's property
                    // whose name corresponds to the substring
                    // after the dot.
                    var attrKey = key.substring(key.indexOf('.') + 1);
                    resEl[attrKey] = resValue;
                }
            }
        }
    }
}

The interpreter loops through all HTML elements that must be localized. A resource value is assigned to an element property that is specified after a dot (e.g. 'btnSubmit.value' or 'btnSubmit.title'), or to the element's innerHTML if there is no dot in the key.

The 'ConfirmText' example that I have mentioned earlier may be used as follows:
var confirmText = resources.ConfirmText;
    // or resources['ConfirmText'] if you prefer.
if (confirm(confirmText )) {
    // Do something that needs confirmation.
}
The method described in this post makes localizing an HTML app or page pretty easy. I think the method is limited (for instance, we do not take specific cultures like 'en-GB' into account) but effective.