Source: utils-1.0.2.js

/*!
 * JavaScript Movie Picker v1.0.1
 * http://wjbryant.com/projects/movie-picker/
 *
 * Copyright (c) 2013 Bill Bryant
 * Licensed under the MIT license
 * http://opensource.org/licenses/mit
 */

/**
 * @fileOverview Utilities library for the Movie Picker project
 * @author       Bill Bryant
 * @version      1.0.2
 */

/*jslint browser: true */
/*global ActiveXObject */

// some methods are based on methods of the WJBRYANT JavaScript Library

/**
 * The UTILS "namespace" / global variable.
 *
 * @namespace
 */
var UTILS = {};

(function (window, document, UTILS) {
    'use strict';

    // requestAnimationFrame polyfill by Erik M´┐Żller
    // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
    // fixes from Paul Irish and Tino Zijdel
    // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
    // https://gist.github.com/1579671
    var i,
        vendors = ['ms', 'moz', 'webkit', 'o'],
        requestAnimationFrame = window.requestAnimationFrame,
        cancelAnimationFrame = window.cancelAnimationFrame,
        rAFLastTime = 0;

    for (i = 0; i < vendors.length && !requestAnimationFrame; i += 1) {
        requestAnimationFrame = window[vendors[i] + 'RequestAnimationFrame'];
        cancelAnimationFrame = window[vendors[i] + 'CancelAnimationFrame'] ||
            window[vendors[i] + 'CancelRequestAnimationFrame'];
    }

    if (!requestAnimationFrame) {
        requestAnimationFrame = function (callback) {
            var currTime = new Date().getTime(),
                timeToCall = Math.max(0, 16 - (currTime - rAFLastTime));

            rAFLastTime = currTime + timeToCall;

            return setTimeout(function () {
                callback(currTime + timeToCall);
            }, timeToCall);
        };
    }

    if (!cancelAnimationFrame) {
        cancelAnimationFrame = function (id) {
            clearTimeout(id);
        };
    }

    /**
     * Removes the leading and trailing whitespace from a string.
     * {@link http://blog.stevenlevithan.com/archives/faster-trim-javascript}
     *
     * @param  {string} str  the string to trim
     * @return {string}      the trimmed string
     *
     * @method
     */
    UTILS.trim = String.prototype.trim ?
            function (str) {
                return str.trim();
            } :
            function (str) {
                return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
            };

    /**
     * Gets the specified cookie's value.
     *
     * @param  {string} name  the name of the cookie to get
     * @return {string}       the cookie value
     */
    UTILS.getCookie = function (name) {
        var start = document.cookie.indexOf(name + '='),
            len = start + name.length + 1,
            end = document.cookie.indexOf(';', len);
        if ((!start && name !== document.cookie.slice(0, name.length)) || start === -1) {
            return null;
        }
        if (end === -1) {
            end = document.cookie.length;
        }
        return decodeURIComponent(document.cookie.slice(len, end));
    };

    /**
     * Sets a cookie to the specified value. If no cookie exists, then it is
     * created.
     *
     * @param {string}  name       the name of the cookie
     * @param {string}  value      the value of the cookie
     * @param {number}  [expires]  the number of days in which the cookie will
     *                             expire
     * @param {string}  [path]     the cookie path
     * @param {string}  [domain]   the cookie domain
     * @param {boolean} [secure]   whether the cookie should only be sent over
     *                             a secure connection
     */
    UTILS.setCookie = function (name, value, expires, path, domain, secure) {
        document.cookie = name + '=' + encodeURIComponent(value) +
            (expires ? ';max-age=' + (expires * 24 * 60 * 60) : '') +
            (path ? ';path=' + path : '') +
            (domain ? ';domain=' + domain : '') +
            (secure ? ';secure' : '');
    };

    /**
     * Sends an Ajax request using the specified options. Ajax requests are
     * sent with a special 'X-Requested-With' header that can be used to
     * identify the requests on the server.
     *
     * @param {Object} [options]  an object containing members specifying the
     *                            options of the request<br />
     *                            <br />
     *                            Valid members of this object are:
     * <pre>
     * {string}        [url=location.href]  the url to send the request to
     *
     * {string}        [method='GET']       the request method ('GET' or 'POST')
     *
     * {string|Object} [data=null]          the data to send with the request
     *
     * {number}        [timeout]            the amount of time in milliseconds
     *                                      to wait for a response before
     *                                      canceling the request. The onfailure
     *                                      and oncomplete functions will be called.
     *
     * {Function}      [onsuccess]          the function to be executed when
     *                                      the response is received successfully.
     *                                      This function is passed the
     *                                      XMLHttpRequest object as a parameter.
     *
     * {Function}      [onfailure]          the function to be executed when the
     *                                      response is received and the status
     *                                      is not 200 or 304 (an error occurred).
     *                                      This function is passed the
     *                                      XMLHttpRequest object as a parameter.
     *
     * {Function}      [oncomplete]         the function to be executed when the
     *                                      response is received whether or not
     *                                      it was successful. This function will
     *                                      always be executed after the onsuccess
     *                                      or onfailure function. This function
     *                                      is passed the XMLHttpRequest object
     *                                      as a parameter.
     * </pre>
     * @return {boolean}  whether or not the request was made successfully
     */
    UTILS.ajaxRequest = function (options) {

        options = options || {};

        var req = false,
            url = options.url || location.href,
            method = options.method ? options.method.toUpperCase() : 'GET',
            data = options.data || null,
            to,
            prop,
            dataPairs = [],
            space = /%20/g;

        // get the request object
        if (window.XMLHttpRequest) {
            req = new XMLHttpRequest();
        } else if (window.ActiveXObject) {
            try {
                req = new ActiveXObject('Msxml2.XMLHTTP.6.0');
            } catch (e1) {
                try {
                    req = new ActiveXObject('Microsoft.XMLHTTP');
                } catch (e2) {
                    req = false;
                }
            }
        }

        // if no request object could be obtained,
        // exit the method and return false
        if (!req) {
            return false;
        }

        if (typeof data === 'object' && data !== null) {
            for (prop in data) {
                if (data.hasOwnProperty(prop)) { // just to be extra safe
                    dataPairs[dataPairs.length] = encodeURIComponent(prop).replace(space, '+') +
                        '=' + encodeURIComponent(data[prop].toString()).replace(space, '+');
                }
            }
            data = dataPairs.join('&');
        }

        // set up the request
        if (method === 'POST') {
            req.open(method, url, true);
            req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
        } else {
            if (data) {
                url += '?' + data;
                data = null;
            }
            req.open(method, url, true);
        }

        // setting onreadystatechange after the call to open allows the same
        // XMLHttpRequest object to be reused by IE without a call to abort
        // (this code currently doesn't support that, but maybe in the future)

        /**
         * Called every time the readyState changes. If the readyState is 4
         * and the status is 200 (OK) or 304 (not modified), the
         * options.onsuccess function is called, otherwise the
         * options.onfailure function is called. Lastly, the options.oncomplete
         * function is called.
         *
         * @ignore
         */
        req.onreadystatechange = function () {
            if (req.readyState === 4) {
                if (to) {
                    clearTimeout(to);
                }
                if (req.status === 200 || req.status === 304) {
                    if (typeof options.onsuccess === 'function') {
                        options.onsuccess(req);
                    }
                } else if (typeof options.onfailure === 'function') {
                    options.onfailure(req);
                }
                if (typeof options.oncomplete === 'function') {
                    options.oncomplete(req);
                }
            }
        };

        // this special header is used to identify Ajax requests on the server
        req.setRequestHeader('X-Requested-With', 'XMLHttpRequest');

        req.send(data);

        if (typeof options.timeout === 'number' && options.timeout > 0) {
            to = setTimeout(function () {
                // prevent Firefox from calling onreadystatechange after abort
                // IE with throw an error if it's set to null,
                // so we set it to an empty function instead
                req.onreadystatechange = function () {};

                req.abort();

                if (typeof options.onfailure === 'function') {
                    options.onfailure(req);
                }
                if (typeof options.oncomplete === 'function') {
                    options.oncomplete(req);
                }
            }, options.timeout);
        }

        return true;
    };

    /**
     * Moves an element from one position to another.
     *
     * @param {Element} elem       the element to be moved
     * @param {Object}  [options]  an object containing members specifying the
     *                             animation options<br />
     *                             <br />
     *                             Valid members of this object are:
     * <pre>
     * {Array}    [from=[0,0]]  the starting position in [x, y]. Defaults to
     *                          the element's current position or [0, 0].
     *
     * {Array}    [to=[0,0]]    the ending position in [x, y]
     *
     * {number}   [dur=400]     the duration of the animation in milliseconds
     *                          (must be > 0)
     *
     * {Function} [callback]    the function to be executed when the animation
     *                          is finished. This function is executed in the
     *                          context of the element.
     * </pre>
     * @throws {Error}  if elem argument is not an Element
     */
    UTILS.move = function (elem, options) {
        if (!elem || elem.nodeType !== 1) {
            throw new Error('Expected elem to be an Element');
        }

        options = options || {};

        var from = options.from,
            fromX = 0,
            fromY = 0,
            to = options.to,
            toX = 0,
            toY = 0,
            dur = options.dur,
            callback = options.callback,
            distX,
            distY,
            elemStyle = elem.style,
            startTime,

            /**
             * Determines if an object is an Array.
             *
             * @param  {Object}  obj the object to test
             * @return {boolean}     whether the object is an array
             *
             * @function
             * @private
             */
            isArray = Array.isArray ||
                function (obj) {
                    return Object.prototype.toString.call(obj) === '[object Array]';
                };

        if (!from) {
            // check for elemStyle.left and elemStyle.top to use as defaults for 'from'
            if (elemStyle.left && elemStyle.top) {
                fromX = parseInt(elemStyle.left, 10);
                fromY = parseInt(elemStyle.top, 10);
            }
        } else if (isArray(from) && typeof from[0] === 'number' && typeof from[1] === 'number') {
            // if 'from' is an array that contains numbers, use it
            fromX = from[0];
            fromY = from[1];
        }

        // if 'to' is an array that contains numbers, use it
        if (isArray(to) && typeof to[0] === 'number' && typeof to[1] === 'number') {
            toX = to[0];
            toY = to[1];
        }

        // check that dur is a positive number
        if (typeof dur !== 'number' || dur < 1) {
            dur = 400;
        }

        // the element cannot be animated without its position being set
        if ((window.getComputedStyle && window.getComputedStyle(elem, null).position === 'static') ||
                (elem.currentStyle && elem.currentStyle.position === 'static')) {

            elemStyle.position = 'relative';
        }

        distX = toX - fromX;
        distY = toY - fromY;

        /**
         * Moves the element the appropriate distance for the next frame of animation.
         *
         * @param {number} scheduledTime  the time the next frame is scheduled to be painted
         *
         * @private
         */
        function next(scheduledTime) {
            var elapsedTime = scheduledTime - startTime,
                fractionComplete = elapsedTime / dur;

            if (fractionComplete < 1) {
                elemStyle.left = (fractionComplete * distX + fromX) + 'px';
                elemStyle.top = (fractionComplete * distY + fromY) + 'px';
                requestAnimationFrame(next);
            } else {
                elemStyle.left = toX + 'px';
                elemStyle.top = toY + 'px';
                if (typeof callback === 'function') {
                    callback.call(elem);
                }
            }
        }

        startTime = new Date().getTime();
        requestAnimationFrame(function (scheduledTime) {
            // correct startTime for browsers that use the precision specification
            startTime = scheduledTime - (new Date().getTime() - startTime);
            next(scheduledTime);
        });
    };
}(window, document, UTILS));