Rafał Wrzeszcz - Wrzasq.pl

JavaScript Zend-like url helper

Monday, 25 July 2011, 20:35

The idea

In JavaScript, especially when you work with AJAX (many requests to frontend host) and dynamic UI (many requests for assets like images) you have to resolve a lot of URLs. Problem comes, when you have to keep some base of your URLs reference (like web application base URL), or keep your links switchable easily (static hosts balancing). If you are familiar with Zend_Framework, you must know baseUrl() helper for Zend_View. Isn't that a handful thing? Then why not use it in your client scripts as well :)? Here I propose my solution for easy URLs resolving that behaves much like Zend_View's baseUrl() helper.

Requirements

Here is the list of requirements that I stated for my solution:

  • it must be possible to cast it to string, so it can be treated as a string concatenation argument;
  • it must deal with slashes (the same way that Zend_View's helper does that);
  • it must be a general solution, so creation of multiple helpers at once must be possible (for different base references - for instance one for frontend requests, one for static paths);
  • and finally - it must be possible to change base URL at runtime.

The realisation

/**
 * Zend_Framework-like baseUrl() helper.
 *
 * @author Rafał Wrzeszcz <rafal.wrzeszcz@wrzasq.pl>
 */
var AssetUrl = ( function() {
    // container for methods to avoid multiple instances creation
    var methods = {
        setBaseUrl: function(baseUrl) {
            this.baseUrl = (baseUrl || "").replace(/[\/\\]+$/, "");
            return this;
        },
        toString: function() {
            return this.call(this);
        }
    };

    /**
     * Creates new helper.
     *
     * @class
     * @param {String} [baseUrl=""] Optional default base url.
     * @extends Function
     */
    return function(baseUrl) {
        /**
         * Base part for relative URLs.
         *
         * @name AssetUrl#baseUrl
         * @private
         * @default ""
         * @type String
         */

        /**
         * Calculates absolute URL.
         *
         * @param {String} [url=""] Relative URL part.
         * @return {String} Full URL.
         */
        var helper = function(url) {
            // makes us sure that URL starts with just one slash
            url = (url || "").replace(/^[\/\\]*/, "/");

            return helper.baseUrl + url;
        };

        /**
         * Changes helper's base URL part.
         *
         * @function
         * @param {Stirng} [baseUrl=""] New base URL for helper.
         * @return {AssetUrl} Self instance.
         */
        helper.setBaseUrl = methods.setBaseUrl;
        /**
         * Allows to use helper directly in strings concatenation.
         *
         * @function
         * @return {Stirng} Base URL part only.
         */
        helper.toString = methods.toString;

        // sets startup default base URL.
        return helper.setBaseUrl(baseUrl);
    };
} )();

This is in fact just a helper factory - this allows for multiple helpers to exist simultaneously and be associated with different URLs:

var baseUrl = AssetUrl("/");
var staticUrl = AssetUrl("http://static.example.com/");

Note, that you don't use new operator with AssetUrl(). In fact you can - since the function returns non-scalar value, constructor mechanism will be replaced, but semanticly it's pointless - we just enclose function definition, it has nothing to do with object construction. Capital letter at the beginning denotes way of use, not a class type.

How to use? Simple:

new Ajax.Request( baseUrl("account/login") );
$("loader-image").writeAttribute("src", staticUrl("images/loader.png") );

Since AssetUrl() helper implements toString() method it can be casted to string directly:

baseUrl + "foo/bar"

But it is recommented to use it as a helper function. Why? Helper takes care about slashes. When you use helper as function both helper("/foo") and helper("foo") will produce same result. But using helper as a string makes it unable to it to determinate how to transform the result, hence helper + "/foo" will most likely produce URL that ends with //foo.

You can also change associated base URL with helper.setBaseUrl(newUrl).

ChillDev.JS

I implemented this mechanism in ChillDev.JS as ChillDev.Utils.AssetUrl helper if you want a ready solution.

Tags: Zend, Code, JavaScript, Kod