Source: core.js

/**
 * @license
 * BSD 3-Clause License
 *
 * Copyright (c) 2019, Alexis Puga Ruíz
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * @preserve © 2019-2022 {@link https://github.com/AlexisPuga|Alexis Puga} and {@link https://github.com/justjs/just/contributors|contributors}. {@link https://github.com/justjs/just|JustJs} is released under the {@link https://github.com/justjs/just/blob/master/LICENSE|BSD-3-Clause License}.
 * @file Core utilities for browser environments.
 * @version 1.2.0
 */
(function (fn) {

    if (typeof define === 'function' && define.amd) { define('just', [], fn); }
    else if (typeof module === 'object' && module.exports) { module.exports = fn(); }
    else { this.just = fn(); }

}).call(this, function () {

    'use strict';

    /* eslint-disable no-unused-vars */

    /**
     * An absolute, relative or blob url.
     *
     * @typedef {string} url
     * @global
     */

    /**
     * The full parts of an url.
     *
     * @typedef {Object} url_parts
     * @property {string} protocol - A protocol (including ":", like "ftp:") or ":".
     * @property {string} href - An absolute url (like "ftp://username:password@www.example.com:80/a?b=1#c").
     * @property {string} host - The host (like "www.example.com:80") or an empty string.
     * @property {string} hostname - A hostname (like "www.example.com").
     * @property {string} port - The GIVEN port as a number (like "80") or an empty string.
     * @property {string} pathname - A pathname (like "/a").
     * @property {string} origin - The origin (like "ftp://www.example.com").
     * @property {string} search - The query arguments (including "?", like "?b=1") or an empty string.
     * @property {string} hash - The hash (including '#', like "#c") or an empty string.
     * @property {string} username - The given username or an empty string.
     * @property {string} password - The given password or an empty string.
     */

    /**
     * Same as the second param for <var>Object.defineProperties</var>
     *
     * @typedef {!object} propertiesDescriptor
     * @global
     */

    /**
     * Same as the third param for <var>Object.defineProperty</var>
     *
     * @typedef {!object} propertyDescriptor
     * @global
     */

    /**
     * A tagName of an Element (such as "link").
     *
     * @typedef {string} element_tag
     */

    /**
     * @mixin just
     * @global
     */
    var just = {};
    /**
     * Register non-writable, non-enumerable and non-configurable members on Just.
     * @package
     */
    var set = function registerMember (name, value) { Object.defineProperty(just, name, {'value': value}); };

    set('register', set);

    /**
     * Same as <var>Array.prototype.reduce</var>, but for
     * the own enumerable properties of <var>object</var>,
     * and with an extra param (<var>thisArg</var>).<br>
     *
     * <aside class='note'>
     *   <h3>A few things to consider:</h3>
     *   <p>Don't trust the order of the properties.</p>
     *   <p>Instead of using an index for the 3rd argument, we use the current <var>key</var>.</p>
     * </aside>
     *
     * @namespace
     * @memberof just
     * @param {object} object - The target object.
     * @param {function} fn - The transform function. It's called with the same arguments as the <var>Array.prototype.reduce</var> function.
     * @param {*} accumulator - The initial value for the accumulator.
     * @param {*} thisArg - <var>this</var> argument for <var>fn</var>.
     * @returns <var>accumulator</var>
     * @example <caption>Get object keys (when Object.keys is unavailable).</caption>
     * just.reduce({'a': 1}, function (keys, value, key, object) { return keys.concat(key); }, []);
     * // > ['a']
     */
    function reduce (object, fn, accumulator, thisArg) {

        var hasOwnProperty = Object.prototype.hasOwnProperty;
        var target = Object(object);
        var key, value;

        for (key in target) {

            if (hasOwnProperty.call(target, key)) {

                value = target[key];
                accumulator = fn.call(thisArg, accumulator, value, key, target);

            }

        }

        return accumulator;

    }

    set('reduce', reduce);

    var hasOwnProperty = Object.prototype.hasOwnProperty;

    /**
     * Same as <var>Object.assign</var>, but for ES5 environments.<br>
     *
     * <aside class='note'>
     *   <h3>A few things to consider</h3>
     *   <p>This is most likely to be removed in the future, if we
     *   decide to transpile our code and use the spread sintax instead.</p>
     *   <p>This will be used internally, instead of <var>Object.assign</var>,
     *   to prevent users from loading a polyfill. So, it's most likely
     *   that it will be included in your final bundle.</p>
     * </aside>
     *
     * @namespace
     * @memberof just
     * @param {object} target - What to apply the sources' properties to.
     * @param {?object} [...sources] - Objects containing the properties you want to apply.
     * @throws if <var>target</var> is null or not an object.
     * @returns {object} <var>target</var>.
     */
    function assign (target/*, ...sources */) {

        'use strict'; // Make non writable properties throwable.

        var to, i, f, from, key;

        if (target === null || target === void 0) {

            throw new TypeError('can\'t convert ' + target + ' to object');

        }

        to = Object(target);

        for (i = 1, f = arguments.length; i < f; i++) {

            from = Object(arguments[i]);

            for (key in from) {

                if (hasOwnProperty.call(from, key)) {

                    to[key] = from[key];

                }


            }

        }

        return to;

    }

    set('assign', assign);

    /**
     * Show a warning in the console, or throw an Error if called.
     *
     * @since 1.0.0-rc.23
     * @namespace
     * @memberof just
     * @param {string} member - The member's name. E.g.: ".deprecate()"
     * @param {string} type - "warning" or "error". Otherwise it logs the error using console.error().
     * @param {?object} options
     * @param {?string} options.since - Some string.
     * @param {?string} options.message - A message to append to the default message.
     * @throws {Error} If <var>type</var> is set to "error".
     */
    function deprecate (member, type, options) {

        var opts = assign({}, options || {});
        var message = ('just' + member + ' is deprecated' +
		(opts.since ? ' since ' + opts.since : '') + '. ' +
		(opts.message || '')
        ).trim();

        if (type === 'warning') { console.warn(message); }
        else if (type === 'error') { throw new Error(message); }
        else { console.error(message); }

    }

    set('deprecate', deprecate);

    /**
     * The given object (if <var>mutate</var> evals to `true`)
     * or a copy of each own property of the given object.
     *
     * @typedef {!object} just.access~handler_this
     */

    /**
     * A function to call when {@link just.access} reaches the deep property of an object.
     *
     * @typedef {function} just.access~handler
     * @this just.access~handler_this
     * @param {!object} lastObject - The object containing the <var>lastKey</var>.
     * @param {string} lastKey - The last value given in <var>path</var>.
     * @param {boolean} hasProperty - `false` if some key of <var>path</var>
     *     was created, `true` otherwise.
     * @param {string[]} path - The given keys.
     * @return {*} The return value for {@link just.access|the main function}.
     */

    /**
     * Accesses to a deep property in a new <var>object</var>
     * (or <var>object</var> if <var>mutate</var> evals to `true`).
     *
     * @namespace
     * @memberof just
     * @param {!object} object - The base object.
     * @param {string[]} [path=[path]] - The ordered keys.
     * @param {just.access~handler} [handler=returnValue] - A custom function.
     * @param {object} opts
     * @param {boolean} [opts.mutate=false] - If `true`, it will use
     *     the given object as the base object, otherwise it will
     *     copy all the owned properties to a new object.
     * @param {boolean} [opts.override=true] - If `true`, and the
     *     current value is different to `null` or `undefined`,
     *     the function will throw a TypeError.
     *     If `false`, the current value will be overriden by
     *     an empty object if it's not an object nor `undefined`.
     *
     * @throws {TypeError} If some property causes access problems.
     *
     * @example <caption>Accessing to some existent property</caption>
     * just.access({a: {b: {c: {d: 4}}}}, ['a', 'b', 'c', 'd'], function (currentObject, currentKey, hasProperty, path) {
     *     return hasProperty ? currentObject[currentKey] : null;
     * }); // returns 4.
     *
     * @example <caption>Accessing to some property with a non-JSON-like-object as a value</caption>
     * just.access({a: 1}, ['a', 'b', 'c']); // throws TypeError.
     * just.access({a: 1}, ['a', 'b', 'c'], null, {
     *     'override': true
     * }); // returns undefined.
     * // Doesn't throw because it replaces `1` with an empty object
     * // and keeps accessing to the next properties.
     *
     * @example <caption>Accessing to some non-existent property</caption>
     * var obj = {z: 1, prototype: [...]};
     * var newObj = just.access(obj, 'a.b.c'.split('.'), function (currentObject, currentKey, hasProperty, path) {
     *
     *     if (!hasProperty) {
     *         currentObject[currentKey] = path.length;
     *     }
     *
     *     // At this point:
     *     //     `obj` is {z: 1},
     *     //     `currentObject` has a value in `currentKey`,
     *     //     and `this` has all the added keys (even the ones modified in `currentObject`).
     *     return this;
     *
     * }); // returns {z: 1, a: {b: {c: 3}}}
     *
     * // if you want the prototype chain of obj, just copy it.
     * just.assign(newObj.prototype, obj.prototype);
     *
     * @example <caption>Modifying the base object</caption>
     * var obj = {a: {b: false}, b: {b: false}, prototype: [...]};
     *
     * just.access(obj, 'a.b'.split('.'), function (currentObject, currentKey, hasProperty, path) {
     *     currentObject[currentKey] = 2;
     * }, true);
     *
     * // now `obj` is {a: {a: true}, b: {b: true}, prototype: [...]}.
     *
     * @return {*} If <var>handler</var> is given: the returned value of that function,
     *         otherwise: the last value of <var>path</var> in the copied object.
     */
    function access (object, path, handler, opts) {

        var options = assign({
            'override': true,
            'mutate': false
        }, opts);
        var properties = (Array.isArray(path)
        ? path
        : [path]
        );
        var initialObject = (options.mutate
        ? object
        : assign({}, object)
        );
        var currentObject = initialObject;
        var isNewProperty = false;
        var lastKey = properties[properties.length - 1];
        var hasProperty;

        properties.slice(0, -1).forEach(function (key, i) {

            var value = currentObject[key];

            if (!(value instanceof Object)) {

                if (typeof value !== 'undefined' && value !== null && !options.override) {

                    throw new TypeError(key + ' is not an object.');

                }

                isNewProperty = true;
                currentObject[key] = value = {};

            }

            currentObject = value;

        });

        hasProperty = lastKey in currentObject && !isNewProperty;

        return (handler
        ? handler.call(initialObject, currentObject, lastKey, hasProperty, properties)
        : currentObject[lastKey]
        );

    }

    set('access', access);

    /**
     * @mixin just
     * @borrows just.access as prop
     */
    var prop = access;

    set('prop', prop);

    /**
     * Checks if <var>value</var> looks like the other values.
     *
     * @namespace
     * @memberof just
     * @param {*} value - Comparison value.
     * @param {...*} [otherValues] - Values to check against.
     *
     * @example
     * just.check(null, {}, "null", []); // false. Neither is `null`.
     * just.check({}, [], {}); // true. {} is {}.
     *
     * @return {boolean} `true` if some other value looks like <var>value</var>.
     */
    function check (value, otherValues) {

        var exceptFirstArg = [].slice.call(arguments, 1);

        return exceptFirstArg.some(function checkAgainst (arg) {

            return ([arg, value].some(function hasNoProps (v) { return v === null || v === void 0; })
            ? arg === value
            : arg.constructor === value.constructor
            );

        });

    }

    defineProperties(check, /** @lends just.check */{

        /**
         * A custom message to throw.
         *
         * @typedef {string} just.check~throwable_message
         */

        /**
         * A function that {@link just.check|checks} a value against others and
         * throws if the result is `false`.
         *
         * @function
         * @deprecated Since 1.0.0-rc.24
         * @this just.check~throwable_message
         * @param {*} value - Comparison value.
         * @param {...*} [otherValues] - Values to check against.
         *
         * @throws {TypeError} If <var>check</var> returns `false`.
         * @returns {value} <var>value</var> if <var>check</var> returns `true`.
         */
        'throwable': function (value, otherValues) {

            var args = [].slice.call(arguments);
            var throwableMessage = this;

            deprecate('.check.throwable()', 'warning', {
                'since': '1.0.0-rc.24'
            });

            if (!check.apply(this, args)) {

                if (!(throwableMessage instanceof String)) {

                    throwableMessage = (value
                    + ' must be like one of the following values: '
                    + args.slice(1).map(function (v) { return v + ''; }).join(', ')
                    );

                }

                throw new TypeError(throwableMessage);

            }

            return value;

        }

    });

    set('check', check);

    /**
     * @mixin just
     * @borrows just.check as is
     */
    var is = check;

    set('is', is);

    /**
     * Chainable methods for the classList property.
     *
     * @namespace
     * @memberof just
     *
     * @constructor
     * @param {Element} element - The target.
     *
     * @example
     * let force;
     *
     * just.ClassList(button)
     *     .add('a', 'b', 'c')
     *     .remove('b')
     *     .toggle('c', (force = true))
     *     .replace('a', 'z')
     *     .contains('b'); // false
     */
    function ClassList (element) {

        if (!(this instanceof ClassList)) { return new ClassList(element); }

        defineProperties(this, /** @lends just.ClassList# */{
        /** @member {!Element} */
            'element': element
        });

    }

    defineProperties(ClassList, /** @lends just.ClassList */{

        /**
         * Simulate Element.classList.prototype.method.apply(element, args)
         * since it's not possible to call a classList-method that way.
         *
         * @param {Element} element - The target.
         * @param {string} methodName - The name of the classList method to call.
         * @param {array[]|*} [methodArgs=[methodArgs]] - Arguments for the classList method.
         * @return Whatever the method returns.
         *
         * @example
         * ClassList.apply(this, 'add', ['x', 'b']); // > undefined
         * ClassList.apply(this, 'remove', 'c'); // > undefined
         * ClassList.apply(this, 'toggle', ['a', true]); // > true
         */
        'apply': function (element, methodName, methodArgs) {

            var args = (typeof methodArgs === 'number'
            ? [methodArgs]
            : [].slice.call(methodArgs)
            );
            var classList = element.classList;

            if (/(?:add|remove)/.test(methodName)) {

                args.forEach(function (arg) { classList[methodName](arg); });

                /** These methods return undefined. */
                return void 0;

            }

            /*
             * Passing undefined arguments instead of manually
             * adding more conditionals to call the method with
             * the correct amount shouldn't be a problem.
             *
             * I.e:
             * classList.contains('a', undefined);
             * classList.contains('a', 'some other value');
             *
             * Should be the same as calling...
             * classList.contains('a');
             */
            return classList[methodName](args[0], args[1]);

        }

    });

    defineProperties(ClassList.prototype, /** @lends just.ClassList.prototype */{

        /**
         * @alias Element.classList.add
         * @chainable
         */
        'add': function () {

            ClassList.apply(this.element, 'add', arguments);

            return this;

        },
        /**
         * @alias Element.classList.remove
         * @chainable
         */
        'remove': function () {

            ClassList.apply(this.element, 'remove', arguments);

            return this;

        },
        /**
         * @alias Element.classList.toggle
         * @chainable
         */
        'toggle': function () {

            ClassList.apply(this.element, 'toggle', arguments);

            return this;

        },
        /**
         * @alias Element.classList.replace
         * @chainable
         */
        'replace': function () {

            ClassList.apply(this.element, 'replace', arguments);

            return this;

        },
        /**
         * @alias Element.classList.contains
         * @return {boolean}
         */
        'contains': function () { return ClassList.apply(this.element, 'contains', arguments); },
        /**
         * @alias Element.classList.item
         * @return {?string}
         */
        'item': function () { return ClassList.apply(this.element, 'item', arguments); }

    });

    set('ClassList', ClassList);

    /**
     * Defaults to <var>defaultValue</var> if <var>value</var> looks like
     * <var>defaultValue</var>.
     *
     * @namespace
     * @memberof just
     * @param {*} value - Any value.
     * @param {*} [defaultValue] - A value with a desired type for <var>value</var>.
     *     If an object literal is given, all the keys of <var>value</var> will <var>default</var>
     *     to his corresponding key in this object.
     * @param {object} opts - Some options.
     * @param {boolean} [opts.ignoreDefaultKeys=false] - If `false` and <var>defaultValue</var>
     *     is an object literal, the default keys will be added to <var>value</var>
     *     or checked against this function for each repeated key.
     * @param {boolean} [opts.checkLooks=true]
     *     If `true`:
     *         `[]` will match ONLY with another Array.
     *         `{}` will match ONLY with another object literal.
     *     If `false`
     *         `[]` and `{}` will match with any other object.
     * @param {boolean} [opts.checkDeepLooks=true]
     *     Same as <var>checkLooks</var> but it works with the inner values
     *     of the objects.
     * @param {boolean} [opts.ignoreNull=false]
     *     If `true`, <var>defaultValue</var>s with null as a value won't be checked
     *     and any <var>value</var> (except `undefined`) will be allowed.
     *
     * @example
     * just.defaults([1, 2], {a: 1}); // {a: 1}
     *
     * @example
     * just.defaults({}, null); // null: null is not an object literal.
     * just.defaults([], null, {'checkLooks': false}); // []: null is an object.
     * just.defaults(null, {}); // {}: null is not an object literal.
     * just.defaults(null, []); // []: null is not an Array.
     *
     * @example
     * just.defaults(1, NaN); // 1 (NaN is an instance of a Number)
     *
     * @example
     * just.defaults({'a': 1, 'b': 2}, {'a': 'some string'}, {'ignoreDefaultKeys': false}); // {'a': 'some string', 'b': 2}
     *
     * @example
     * just.defaults({'a': 1}, {'b': 2}, {'ignoreDefaultKeys': false}); // {'a': 1, 'b': 2}
     * just.defaults({'a': 1}, {'b': 2}, {'ignoreDefaultKeys': true}); // {'a': 1}
     *
     * @example
     * just.defaults(1, null, {'ignoreNull': false}) // null (1 is not an object)
     * just.defaults(1, null, {'ignoreNull': true}) // 1
     * just.defaults(undefined, null, {'ignoreNull': true}) // null
     * just.defaults({a: 1}, {a: null}, {'ignoreNull': true}) // {a: 1}
     *
     * @returns {value} <var>value</var> if it looks like <var>defaultValue</var> or <var>defaultValue</var> otherwise.
     */
    function defaults (value, defaultValue, opts) {

        var options = assign({
            'ignoreDefaultKeys': false,
            'checkLooks': true,
            'checkDeepLooks': true,
            'ignoreNull': false
        }, opts);

        if (options.ignoreNull && defaultValue === null && value !== void 0) { return value; }

        if (options.checkLooks) {

            if (!check(value, defaultValue)) { return defaultValue; }

            if (check(value, {}) && options.checkDeepLooks) {

                eachProperty(defaultValue, function (v, k) {

                    if (typeof value[k] !== 'undefined') {

                        value[k] = defaults(value[k], v, options);

                    }
                    else if (!options.ignoreDefaultKeys) {

                        value[k] = v;

                    }

                });

            }

            return value;

        }

        return (typeof value === typeof defaultValue
        ? value
        : defaultValue
        );

    }

    set('defaults', defaults);

    /**
     * @mixin just
     * @borrows just.defaults as from
     */
    var from = defaults;

    set('from', from);

    /**
     * Alternative to <var>Object.defineProperties</var>.
     *
     * @see {@link defineProperty} for more details.
     * @namespace
     * @memberof just
     * @param {!object} object
     * @param {!object.<key, propertyDescriptor>|!object.<key, value>} properties
     * @return <var>object</var>
     */
    function defineProperties (object, properties) {

        eachProperty(properties, function define (value, key) {

            defineProperty(this, key, value);

        }, object);

        return object;

    }

    set('defineProperties', defineProperties);

    /**
     * @mixin just
     * @borrows just.defineProperties as defProps
     */
    var defProps = defineProperties;

    set('defProps', defProps);

    /**
     * Alternative to <var>Object.defineProperty</var> with more enhancements.
     *
     * <br>
     * <aside class='note'>
     *     <h3>A few things to consider (see examples):</h3>
     *     <ul>
     *         <li>If <var>object</var> contains invalid {@link propertyDescriptor|property descriptor attributes},
     *             the value WON'T be used as a property descriptor.</li>
     *         <li>Empty objects will be considered values.</li>
     *     </ul>
     * </aside>
     *
     * @namespace
     * @memberof just
     * @throws <var>Object.defineProperty</var> exceptions.
     * @param {!object} object - The target.
     * @param {string} key - Key for the property.
     * @param {!object} [value={'value': value}] - A {@link propertyDescriptor|property descriptor} or some value.
     * @example <caption>Define a property using a value.</caption>
     * just.defineProperty({}, 'a', 1); // Same as Object.defineProperty({}, 'a', {'value': 1})
     *
     * @example <caption>Define a property using a {@link propertyDescriptor|property descriptor}.</caption>
     * just.defineProperty({}, 'a', {'writable': true}); // Same as Object.defineProperty({}, 'a', {'writable': true})
     *
     * @example <caption>Define a property with an empty object.</caption>
     * just.defineProperty({}, 'a', {}); // Same as Object.defineProperty({}, 'a', {'value': {}});
     *
     * @return <var>object</var>.
     */
    function defineProperty (object, key, value) {

        var descriptor = Object(value);
        var defaultDescriptors = ['value', 'writable', 'get', 'set', 'configurable', 'enumerable'];
        var descriptorKeys = reduce(descriptor, function (keys, value, key) { return keys.concat(key); }, []);
        var isEmptyObject = !descriptorKeys.length;
        var notADescriptor = isEmptyObject || descriptorKeys.some(
            function notInDescriptors (key) { return this.indexOf(key) === -1; },
            defaultDescriptors
        );

        if (notADescriptor) {

            descriptor = {
                'value': value
            };

        }

        Object.defineProperty(object, key, descriptor);

        return object;

    }

    set('defineProperty', defineProperty);

    /**
     * @mixin just
     * @borrows just.defineProperty as defProp
     */
    var defProp = defineProperty;

    set('defProp', defProp);

    /**
     * @typedef {function} just.eachProperty~fn
     *
     * @this {@link just.eachProperty|<var>thisArg</var> from the main function}.
     *
     * @param {*} value - The current value.
     * @param {*} key - The current key.
     * @param {!object} object - The current object being iterated.
     *
     * @return {boolean} If `true`, the current loop will stop.
     */

    /**
     * Converts <var>object</var> to an Object, iterates over it,
     * calls a function on each iteration, and if a truthy value
     * is returned from that function, the loop will stop.
     *
     * @namespace
     * @memberof just
     * @param {*} object - Some value.
     * @param {just.eachProperty~fn} fn - The function that will be
     *     called on each iteration.
     * @param {*} [thisArg] - <var>this</var> for <var>fn</var>.
     * @param {object} opts - Some options.
     * @param {boolean} [opts.addNonOwned=false] - Include non-owned properties.
     *     `false`: iterate only the owned properties.
     *     `true`: iterate the (enumerable) inherited properties too.
     *
     * @throws {TypeError} If <var>fn</var> is not a function.
     * @return {boolean} `true` if the function was interrupted, `false` otherwise.
     */
    function eachProperty (object, fn, thisArg, opts) {

        var properties = Object(object);
        var options = assign({
            'addNonOwned': false
        }, opts);
        var wasInterrupted = false;
        var key;

        if (typeof fn !== 'function') { throw new TypeError(fn + ' is not a function.'); }

        for (key in properties) {

            if (wasInterrupted) { break; }

            if (options.addNonOwned || ({}).hasOwnProperty.call(properties, key)) {

                wasInterrupted = Boolean(fn.call(thisArg, properties[key], key, properties));

            }

        }

        return wasInterrupted;

    }

    set('eachProperty', eachProperty);

    /**
     * @mixin just
     * @borrows just.eachProperty as eachProp
     */
    var eachProp = eachProperty;

    set('eachProp', eachProp);

    /**
     * Gets DOM Elements by a CSS Selector and always
     * returns an array.
     *
     * @namespace
     * @memberof just
     * @param {DOMString} selector - A CSS selector.
     * @param {Node} [parent=document] - The parent node.
     *
     * @return {!Array} The found elements.
     */
    function findElements (selector, parent) {

        return [].slice.call((parent || document).querySelectorAll(selector));

    }

    set('findElements', findElements);

    /**
     * @mixin just
     * @borrows just.findElements as el
     */
    var el = findElements;

    set('el', el);

    /**
     * A function that checks if <var>this</var> is the Node that you're looking for.
     *
     * @typedef {function} just.getRemoteParent~fn
     *
     * @this Node
     * @param {!Number} deepLevel - A counter that indicates how many elements have checked.
     * @param {Node} rootContainer - The root container.
     *
     * @return {boolean}
     */

    /**
     * Goes up through the <var>childNode</var> parents, until <var>fn</var> returns `true`
     * or a non-Node is found.
     *
     * @namespace
     * @memberof just
     * @param {Node} childNode - Some child.
     * @param {just.getRemoteParent~fn} fn - Some custom handler.
     * @param {Node} [rootContainer=html] - The farthest parent.
     * @param {boolean} [includeChild=false] - If `true`, it calls <var>fn</var> with <var>childNode</var> too.
     *
     * @example
     * just.getRemoteParent(just.body, function () {
     *     return this.tagName === 'HTML';
     * }); // returns the <html> Element.
     *
     * @return {Node|null} The current Node when <var>fn</var> returns `true`.
     */
    function getRemoteParent (childNode, fn, rootContainer, includeChild) {

        var currentNode = childNode;
        var deepLevel = 0;

        if (!(childNode instanceof Node)) { throw new TypeError(childNode + ' is not a Node.'); }
        if (!(rootContainer instanceof Node)) { rootContainer = document.documentElement; }

        while (currentNode) {

            if ((deepLevel > 0 || includeChild)
			&& fn.call(currentNode, deepLevel, rootContainer)) {

                return currentNode;

            }

            if (currentNode === rootContainer) { return null; }

            currentNode = currentNode.parentNode;
            deepLevel++;

        }

        return null;

    }

    set('getRemoteParent', getRemoteParent);

    /**
     * @mixin just
     * @borrows just.access as parent
     */
    var parent = getRemoteParent;

    set('parent', parent);

    /**
     * Checks if an object has no direct keys.
     *
     * @namespace
     * @memberof just
     * @param {*} [object=Object(object)] - Some object.
     * @return {boolean} `true` if the object doesn't contain owned properties,
     *     `false` otherwise.
     */
    function isEmptyObject (object) {

        return !eachProperty(object, function () { return true; });

    }

    set('isEmptyObject', isEmptyObject);

    /**
     * @mixin just
     * @borrows just.isEmptyObject as emptyObj
     */
    var emptyObj = isEmptyObject;

    set('emptyObj', emptyObj);

    /* global DocumentTouch */
    /**
     * Checks if the screen <em>supports</em> touch.
     *
     * @namespace
     * @memberof just
     * @return {boolean}
     */
    function isTouchSupported () {

        return Boolean('ontouchstart' in document.body
		|| window.navigator.maxTouchPoints > 0
		|| window.navigator.msMaxTouchPoints > 0
		|| 'DocumentTouch' in window
		&& document instanceof DocumentTouch
        );

    }

    set('isTouchSupported', isTouchSupported);

    /**
     * @mixin just
     * @borrows just.isTouchSupported as supportsTouch
     */
    var supportsTouch = isTouchSupported;

    set('supportsTouch', supportsTouch);

    /**
     * Checks if an object is the window global by checking <var>window</var> or
     * some common properties of <var>window</var>.
     *
     * @namespace
     * @memberof just
     * @deprecated Since 1.0.0-rc.24
     * @param {*} object - Some object.
     * @return {boolean} `true` if <var>object</var> is <var>window</var> or contains the common properties,
     *     `false` otherwise.
     */
    function isWindow (object) {

        deprecate('.isWindow()', 'warning', {
            'since': '1.0.0-rc.24'
        });

        return Boolean(object === window
        || object instanceof Object
        && object.document
        && object.setInterval
        );

    }

    set('isWindow', isWindow);

    /**
     * A listener for the "onload" or "onerror" events.
     *
     * @typedef {function} just.loadElement~listener
     *
     * @this Element
     * @param {!Event} event - The triggered event.
     * @return {*}
     */

    /**
     * A custom function to append the created element.
     *
     * @typedef {function} just.loadElement~handler
     * @this {!Element} - The element that loads the url.
     * @param {?Element} loadedElement - An identical element that has been loaded previously.
     * @param {url} url - The given url to load.
     *
     * @return {*} Some value.
     */

    /**
     * Loads an external file if no other similar element is
     * found.
     *
     * @namespace
     * @memberof just
     * @throws document.createElement exception or TypeError if <var>url</var> is missing.
     * @param {!element_tag} tagName - A tag name.
     * @param {?url|?object} [properties] - The url of the file or the properties for the new element.
     * @param {?Node|just.loadElement~handler} [container=document.head]
     *     A custom function to append the element by yourself or a Node
     *     to append the created element to it.
     * @param  {just.loadElement~listener} [listener] - A function to trigger on element load/error.
     *
     * @return {Element|*} The created element, a similar element, or the returned value of
     *     {@link just.loadElement~handler|handler}.
     */
    function loadElement (tagName, properties, listener, container) {

        var props = Object(typeof properties === 'object' ? properties : null);
        var urlProperty = /link/i.test(tagName) ? 'href' : 'src';
        var url = typeof arguments[1] === 'string' ? (props[urlProperty] = arguments[1]) : props[urlProperty];
        var isCrossOriginRequest = parseUrl(url).origin !== window.location.origin;
        var needsCrossOriginProperty = !('crossOrigin' in props) && ['video', 'img', 'script', 'link'].indexOf(tagName) !== -1;
        var isLinkElement = /link/i.test(tagName);
        var needsRelProperty = !('rel' in props);
        var similarElementSelector = tagName + '[' + urlProperty + '="' + (url || '') + '"]';
        var similarElement = findElements(similarElementSelector)[0] || null;
        var element = createElement(tagName, props);
        var listenerWrapper = function listenerWrapper (e) {

            removeEventListener(this, ['load', 'error'], listenerWrapper);

            return listener.call(this, e);

        };
        var invalidUrl = typeof url !== 'string' || url.trim() === '';

        if (invalidUrl) { throw new TypeError(url + ' is not valid url.'); }
        if (listener) { addEventListener(element, ['load', 'error'], listenerWrapper); }
        if (isCrossOriginRequest && needsCrossOriginProperty) { element.crossOrigin = 'anonymous'; }
        if (isLinkElement && needsRelProperty) { element.rel = 'stylesheet'; }

        if (typeof container === 'function') { return container.call(element, similarElement, url); }
        /*else */if (similarElement) { return similarElement; }
        /*else */if (!container) { container = document.head; }

        container.appendChild(element);

        return element;

    }

    set('loadElement', loadElement);

    /**
     * @mixin just
     * @borrows just.loadElement as load
     */
    var load = loadElement;

    set('load', load);

    /**
     * A mixin of properties that access to some kind of storage
     * in the browser.
     *
     * @class
     * @memberof just
     * @param {boolean} [consent=false] - A boolean indicating that
     *     the user allowed the access to some kind of local storage.
     * @param {boolean} [isExplicit=typeof consent !== 'undefined'] -
     *     A value to indicate if the given consent was specified by the
     *     user.
     */
    function LocalStorage (consent, isExplicit) {

        if (!(this instanceof LocalStorage)) { return new LocalStorage(consent, isExplicit); }

        defineProperties(this, {
            'consent': Boolean(consent),
            'isExplicit': defaults(isExplicit, typeof consent !== 'undefined')
        });

    }

    defineProperties(LocalStorage, /** @lends just.LocalStorage */{

        /**
         * The DoNotTrack header formatted as `true`, `false` or `undefined`
         * (for "unspecified").
         *
         * @static
         * @type {boolean|undefined}
         */
        'DNT': {
            'get': function DNT () {

                var dnt = [navigator.doNotTrack, navigator.msDoNotTrack, window.doNotTrack];
                var consent = ',' + dnt + ',';

                return (/,(yes|1),/i.test(consent)
                ? true
                : /,(no|0),/i.test(consent)
                ? false
                : void 0
                );

            }
        },

        /**
         * Checks if <var>cookie</var> is in <var>document.cookie</var>.
         *
         * @function
         * @static
         * @param {string} cookie - The name of the cookie or the cookie itself.
         *
         * @example
         * document.cookie += 'a=b; c=d;';
         * cookieExists('a'); // true
         * cookieExists('b'); // false
         * cookieExists('a=b'); // true
         * cookieExists('a=d'); // false
         *
         * @return {boolean} `true` if it exists, `false` otherwise.
         * @readOnly
         */
        'cookieExists': function cookieExists (cookie) {

            return new RegExp('; ' + cookie + '(=|;)').test('; ' + document.cookie + ';');

        },

        /**
         * Returns a cookie from <var>document.cookie</var>.
         *
         * @function
         * @static
         * @param {string} name - The cookie name.
         *
         * @return {string|null} The cookie if it exists or null.
         * @readOnly
         */
        'getCookie': function getCookie (name) {

            return (!/=/.test(name) && LocalStorage.cookieExists(name)
            ? ('; ' + document.cookie).split('; ' + name + '=').pop().split(';')[0]
            : null
            );

        }
    });

    defineProperties(LocalStorage.prototype, /** @lends just.LocalStorage.prototype */{

        /**
         * Concatenates a value to <var>document.cookie</var>.
         *
         * @function
         * @param {string} name - The name of the cookie.
         * @param {string} value - The value of the cookie.
         * @param {!object} [opts] - Cookie options.
         * @param {string} [opts.secure=location.protocol === 'https:'] - "secure" flag for the cookie.
         *
         * @return {boolean} `true` if was set, `false` otherwise.
         * @readOnly
         */
        'setCookie': function setCookie (name, value, opts) {

            var cookie = '';
            var set = function (k, v) { cookie += k + (typeof v !== 'undefined' ? '=' + v : '') + '; '; };
            var options = defaults(opts, {
                'secure': location.protocol === 'https:'
            });

            if (!this.consent) { return false; }

            if (options.secure) { set('secure'); }
            delete options.secure;

            if (options.expires) { options.expires = new Date(options.expires).toGMTString(); }

            set(name, value);
            eachProperty(options, function (v, k) { set(k, v); });

            document.cookie = cookie.trim();

            return true;

        },

        /**
         * Overrides a cookie by setting an empty value and expiring it.
         *
         * @function
         * @param {string} name - The name of the cookie.
         * @param {object} [opts] - Some extra options.
         * @param {Date} [opts.expires=new Date(0)] - A date in the past.
         *
         * @return {boolean} `true` if was overriden or the cookie
         *     does not exist, `false` otherwise.
         */
        'removeCookie': function removeCookie (name, opts) {

            var options = defaults(opts, {
                'expires': new Date(0)
            });

            if (!LocalStorage.cookieExists(name)) { return true; }

            return this.setCookie(name, '', options);

        },

        /**
         * Any of "cookie", "localStorage", "sessionStorage"...
         *
         * @typedef {string} just.LocalStorage~isStorageAvailable_type
         */

        /**
         * Tests if the specified storage does not throw.
         *
         * @function
         * @param {just.LocalStorage~isStorageAvailable_type} type
         *     A type of storage.
         * @param {string} [tempKey='_'] - Storage will save this key with <var>tempValue</var> as a value.
         * @param {string} [tempValue='_'] - Storage will save this value with <var>tempKey</var> as a key.
         *
         * @return {boolean} `true` if the function does not throw
         *     and is allowed by the user, `false` otherwise.
         */
        'isStorageAvailable': function isStorageAvailable (type, tempKey, tempValue) {

            var k = defaults(tempKey, '_');
            var v = defaults(tempValue, '_');
            var storage;

            if (!this.consent) { return false; }

            if (/cookie/i.test(type)) {

                return this.setCookie(k, v)
                && LocalStorage.getCookie(k) === v
                && this.removeCookie(k);

            }

            try {

                storage = window[type];
                storage.setItem(k, v);
                storage.removeItem(k);

            }
            catch (exception) {

                try { storage.removeItem(k); }
                catch (wrongImplementation) { return false; }

                return false;

            }

            /* istanbul ignore next */
            return true;

        }
    });

    set('LocalStorage', LocalStorage);

    /**
     * Add an event listener to multiple elements.
     *
     * @namespace
     * @memberof just
     * @param {string|Element[]} elements - The targets.
     * @param {string|string[]} eventNames - The event types.
     * @param {function} listener - The handler for the event.
     * @param {object|boolean} [options=false] - Options for addEventListener
     * @return {Element[]} elements
     */
    function addEventListener (elements, eventNames, listener, options) {

        var opts = options || false;
        // Prefer results.push() over elements.filter().map() to avoid increasing complexity.
        var results = [];

        if (typeof elements === 'string') { elements = findElements(elements); }
        if (!Array.isArray(eventNames)) { eventNames = [eventNames]; }
        if (!Array.isArray(elements)) { elements = [elements]; }

        elements.forEach(function (element) {

            if (!('addEventListener' in Object(element))) { return; }

            eventNames.forEach(function (eventType) {

                this.addEventListener(eventType, listener, opts);

            }, element);

            this.push(element);

        }, results);

        return results;

    }

    set('addEventListener', addEventListener);

    /**
     * @mixin just
     * @borrows just.addEventListener as on
     */
    var on = addEventListener;

    set('on', on);

    /**
     * Remove multiple events from multiple targets using
     * EventTarget#removeEventListener().
     *
     * @namespace
     * @memberof just
     * @since 1.0.0-rc.24
     * @param {Element|Element[]} targets - The targets of the attached events.
     * @param {string|string[]} eventTypes - Name of the attached events, like "click", "focus", ...
     * @param {function} listener - The same listener passed to EventTarget#addEventListener().
     * @param {*} [options=false] - The same options passed to EventTarget#addEventListener().
     */
    function removeEventListener (targets, eventTypes, listener, options) {

        var opts = options || false;

        if (!Array.isArray(targets)) { targets = [targets]; }
        if (!Array.isArray(eventTypes)) { eventTypes = [eventTypes]; }

        targets.forEach(function (target) {

            eventTypes.forEach(function (eventType) {

                this.removeEventListener(eventType, listener, opts);

            }, target);

        });

    }

    set('removeEventListener', removeEventListener);

    /**
     * @mixin just
     * @since 1.0.0-rc.24
     * @borrows just.removeEventListener as off
     */
    var off = removeEventListener;

    set('off', off);

    /**
     * Parses <var>url</var> without checking if it's a valid url.
     *
     * Note that this function uses <var>window.location</var> to make relative urls, so
     * weird values in there will give weird results.
     *
     * @namespace
     * @memberof just
     * @param {url} [url=window.location.href] - A relative, an absolute or a blob url.
     *
     * @example <caption>An absolute url:</caption>
     * just.parseUrl(window.location.href);
     *
     * @example <caption>A relative url:</caption>
     * just.parseUrl('/?a#c?d'); // "/" is the pathname, "?a" the search and "#c?d" the hash.
     *
     * @example <caption>A blob url:</caption>
     * just.parseUrl('blob:'); // Same as 'blob:' + `window.location.href`
     *
     * @example <caption>Some good-to-know urls:</caption>
     * just.parseUrl(); // Same as `window.location`.
     * just.parseUrl('a'); // Something that doesn't start with "/", "?", or "#" is evaluated as a host.
     * just.parseUrl('a:b'); // "a:b" is a host, since "b" is not a number.
     * just.parseUrl('//'); // evals as the current origin.
     * just.parseUrl('blob://'); // Same as 'blob:' + `window.location.origin`.
     * // [...]
     *
     * @return {url_parts}
     */
    function parseUrl (url) {

        var parts = {};
        var loc = window.location;
        var optionalParts, hrefParts, id, uriParts, domainParts, hostParts,
            userParts, passwordParts;
        var blob;

        url = url || loc.href;

        if (/^blob:/i.test(url)) {

            blob = parseUrl(url.replace(/^blob:/i, ''));

            return assign(blob, {
                'protocol': 'blob:',
                'href': 'blob:' + blob.href,
                'host': '',
                'hostname': '',
                'port': '',
                'pathname': blob.origin + blob.pathname
            });

        }

        if (/^(:)?\/\//.test(url)) {

            url = ((url = url.replace(/^:/, '')) === '//'
            ? loc.origin
            : loc.protocol + url
            );

        }
        else if (/^(\?|#|\/)/.test(url)) {

            url = loc.origin + url;

        }
        else if (!/:\/\//.test(url)) {

            url = loc.protocol + '//' + url;

        }

        hrefParts = (url || '').split(/(\?.*#?|#.*\??).*/);
        optionalParts = (hrefParts[1] || '').split('#');
        id = optionalParts[1] || '';

        parts.search = optionalParts[0] || '';
        parts.hash = (id ? '#' + id : id);

        uriParts = (hrefParts[0] || '').split('://');

        hostParts = (uriParts[1] || '').split(/(\/.*)/);

        parts.username = '';
        parts.password = '';

        if (/@/.test(hostParts[0])) {

            userParts = hostParts[0].split('@');
            passwordParts = userParts[0].split(':');
            parts.username = passwordParts[0] || '';
            parts.password = passwordParts[1] || '';
            hostParts[0] = userParts[1];

        }

        parts.host = hostParts[0] || '';
        parts.pathname = hostParts[1] || '';

        domainParts = parts.host.split(/:([0-9]+)/);

        parts.hostname = domainParts[0] || '';
        parts.port = (typeof domainParts[1] !== 'undefined'
        ? domainParts[1]
        : ''
        );

        parts.protocol = uriParts[0] + ':';
        parts.origin = parts.protocol + '//' + parts.host;

        parts.href = (userParts
        ? parts.protocol + '//' + parts.username + ':' + parts.password + '@' + parts.host
        : parts.origin
        ) + parts.pathname + parts.search + parts.hash;

        return parts;

    }

    set('parseUrl', parseUrl);

    /**
     * If <var>value</var> is an object, return <var>value</var>,
     * otherwise parse <var>value</var> using JSON.parse().
     * Return null if an exception occurs.
     *
     * @since 1.0.0-rc.23
     * @namespace
     * @memberof just
     * @param {*} value - Some value.
     *
     * @example
     * just.parseJSON('{"a": 1}'); // > {a: 1}
     *
     * @example
     * just.parseJSON('[]'); // > []
     *
     * @example
     * just.parseJSON(''); // > null
     *
     * @example
     * just.parseJSON({}); // > null
     *
     * @example
     * just.parseJSON(1); // > 1
     *
     * @return {*} The given value (if it's an object), the parsed value or null.
     */
    function parseJSON (value) {

        var parsedValue;

        if (typeof value === 'object') { return value; }

        try { parsedValue = JSON.parse(value); }
        catch (exception) { parsedValue = null; }

        return parsedValue;

    }

    set('parseJSON', parseJSON);

    /**
     * Parses an stringified JSON (<code>'{"a": 1}'</code>)
     * into an object literal (<code>{a: 1}</code>).
     * If you need to parse any other value, use {@link just.parseJSON} instead.
     *
     * @namespace
     * @memberof just
     * @param {*} string - Some string to parse.
     *
     * @example
     * just.stringToJSON('{"a": 1}'); // returns {a: 1}.
     *
     * @example
     * just.stringToJSON(1); // returns {}.
     *
     * @return {!object} An object literal.
     */
    function stringToJSON (string) {

        var json;

        if (!/(?:^\{|\}$)/.test(String(string).trim())) { return {}; }

        json = parseJSON(string) || {};

        return json;

    }

    set('stringToJSON', stringToJSON);

    /**
     * @version 1.0.0-rc.23
     * @mixin just
     * @borrows just.parseJSON as toJSON
     */
    var toJSON = parseJSON;

    set('toJSON', toJSON);

    /**
     * Converts <code>[[k0, v0], {k1: v1}]</code> to <code>{k0: v0, k1: v1}</code>.
     *
     * @deprecated since 1.0.0-rc.22
     * @namespace
     * @memberof just
     * @param {!object[]|!object} array - An array containing sub-arrays
     *     with object literal pairs, or object literals: <code>[[k, v], {k: v}]</code>.
     *
     * @return {!object} An object literal.
     */
    function toObjectLiteral (array) {

        var objectLiteral = {};

        deprecate('.toObjectLiteral()', 'warning', {
            'since': '1.0.0-rc.22'
        });

        if (check(array, {}, null)) {

            return assign({}, array);

        }

        if (!check(array, [])) {

            throw new TypeError(array + ' must be either null, an object literal or an Array.');

        }

        array.forEach(function (subArray) {

            var key, value;

            if (check(subArray, [])) {

                key = subArray[0];
                value = subArray[1];
                this[key] = value;

            }
            else if (check(subArray, {})) {

                assign(this, subArray);

            }
            else {

                throw new TypeError(subArray + ' must be either ' +
				'an object literal or an Array.');

            }

        }, objectLiteral);

        return objectLiteral;

    }

    set('toObjectLiteral', toObjectLiteral);

    /**
     * @deprecated since 1.0.0-rc.22
     * @mixin just
     * @borrows just.toObjectLiteral as toObj
     */
    var toObj = toObjectLiteral;

    set('toObj', toObj);

    /**
     * Call a function when the HTML document has been loaded.
     * Source: http://youmightnotneedjquery.com/#ready
     *
     * @param {function} fn - The callback.
     */
    function onDocumentReady (fn) {

        if (document.readyState !== 'loading') { return setTimeout(fn), void 0; }

        addEventListener(document, 'DOMContentLoaded', function onDocumentReady () {

            removeEventListener(document, 'DOMContentLoaded', onDocumentReady);
            fn();

        });

    }

    set('onDocumentReady', onDocumentReady);

    /**
     * Create an element with the given properties.
     *
     * @namespace
     * @memberof just
     * @since 1.0.0-rc.23
     * @param {string} tag - The tag name for the element.
     * @param {?object} properties - Properties for the created element.
     * @return {Element} The created element.
     */
    function createElement (tag, properties) {

        var element = document.createElement(tag);

        assign(element, properties);

        return element;

    }

    set('createElement', createElement);

    /**
     * @mixin just
     * @since 1.0.0-rc.23
     * @borrows just.createElement as create
     */
    var create = createElement;

    set('create', create);

    /**
     * A function to intercept and send the request.
     *
     * @this {XMLHttpRequest}
     * @param {*} data - The data ready to be sent.
     * @param {!object} options - The options for the request.
     * @typedef {function} just.request~send
     */
    /**
     * A function to call on "load"/"error" event.
     *
     * @this {XMLHttpRequest}
     * @param {?Error} error - Bad status error or <code>null</code>.
     * @param {*} response - Either #response or #responseText property.
     *            On JSON request, the property parsed as a JSON.
     * @typedef {function} just.request~fn
     */
    /**
     * Default request headers.
     *
     * @property {string} [X-Requested-With="XMLHttpRequest"]
     * @property {string} [Content-Type="application/json"] - Only on JSON requests.
     * @typedef {!object} just.request~defaultHeaders
     */
    /**
     * Default request properties.
     *
     * @property {string} [responseType="json"] - Only on JSON requests.
     * @typedef {object} just.request~defaultProps
     */
    /**
     * Make a request using XMLHttpRequest.
     *
     * @namespace
     * @memberof just
     * @since 1.0.0-rc.23
     * @param {!url} url - Some url.
     * @param {just.request~fn} [fn] - Hook for onreadystatechange listener.
     * @param {object} [options]
     * @param {string} [options.method="GET"] - An HTTP Request Method: GET, POST, HEAD, ...
     * @param {boolean} [options.json=/.json$/.test(url)] - If <code>true</code>,
     *       <code>"Content-Type"</code> will be set to <code>"application/json"</code>,
     *       #responseType to <code>"json"</code>, and the #response/#responseText
     *       will be parsed to a JSON.
     * @param {*} [options.data=null] - Data to send.
     * @param {function} [options.send={@link just.request~send}] - A custom function to intercept and send the request.
     * @param {boolean} [options.async=true] - "async" param for XMLHttpRequest#open().
     * @param {string} [options.user=null] - User name to use for authentication purposes.
     * @param {string} [options.pwd=null] - Password to use for authentication purposes.
     * @param {object} [options.props={@link just.request~defaultProps}] - Properties for the xhr instance.
     * @param {object} [options.headers={@link just.request~defaultHeaders}] - Custom headers for the request.
     * @returns {*} The retuned value of {@link just.request~send}.
     */
    function request (url, fn, options) {

        var isJSON = ('json' in Object(options)
        ? options.json
        : /\.json$/i.test(url)
        );
        var opts = defaults(options, {
            'json': isJSON,
            'data': null,
            'method': 'GET',
            'async': true,
            'user': null,
            'pwd': null,
            'headers': assign({
                'X-Requested-With': 'XMLHttpRequest'
            }, (isJSON ? {
                'Content-Type': 'application/json'
            } : null)),
            'props': assign({}, (isJSON ? {
                'responseType': 'json'
            } : null)),
            'send': function send (data) { return this.send(data); }
        }, {'ignoreNull': true});
        var data = opts.data;
        var method = opts.method;
        var async = opts.async;
        var user = opts.user;
        var password = opts.pwd;
        var props = opts.props;
        var headers = opts.headers;
        var xhr = new XMLHttpRequest();

        if (/GET/i.test(method) && data) {

            url = request.appendData(url, data);
            data = null;

        }

        xhr.open(method, url, async, user, password);

        eachProperty(headers, function setHeaders (value, key) { this.setRequestHeader(key, value); }, xhr);
        assign(xhr, props);

        xhr.onreadystatechange = function onReadyStateChange (e) {

            var status, response, error;

            if (this.readyState === XMLHttpRequest.DONE) {

                this.onreadystatechange = null;
                status = this.status;
                response = ('response' in this
                ? this.response
                : this.responseText
                );
                error = ((status < 200 || status >= 400) && status !== 0
                ? new Error('Bad status: ' + status)
                : null
                );

                if (isJSON && typeof response !== 'object') { response = parseJSON(response); }
                if (fn) { fn.call(this, error, response); }

            }

        };

        return opts.send.call(xhr, data, options);

    }

    defineProperties(request, /** @lends just.request */{

        /**
         * Append data to the search params of the given url.
         *
         * @function
         * @param {string} url - Some url.
         * @param {?object} data - An object to be appended.
         * @example
         * appendData('/some', {'data': 1}); // > '/some?data=1'
         * appendData('/some?url', {'data': 1}); // > '/some?url&data=1'
         *
         * @returns {string}
         */
        'appendData': function appendDataToUrl (url, data) {

            var parsedUrl = parseUrl(url);
            var searchParams = request.dataToUrl(data);
            var search = ((/\?.+/.test(parsedUrl.search)
            ? parsedUrl.search + '&'
            : '?'
            ) + searchParams).replace(/[?&]$/, '');

            return [
                parsedUrl.origin,
                parsedUrl.pathname,
                search,
                parsedUrl.hash
            ].join('');

        },
        /**
         * Convert data into search params.
         *
         * @function
         * @param {?object} data - Expects an object literal.
         * @throws {TypeError} If <var>data</var> is not an object.
         * @example
         * dataToUrl({'a': '&a', 'b': 2}); // > 'a=%26&b=2'
         *
         * @returns {string}
         */
        'dataToUrl': function convertDataIntoUrl (data) {

            var dataObject = Object(data);

            if (typeof data !== 'object') { throw new TypeError(data + ' is not an object.'); }

            return reduce(dataObject, function (params, value, key) {

                var param = encodeURIComponent(key) + '=' + encodeURIComponent(value);

                return params.concat(param);

            }, []).join('&');

        }

    });

    set('request', request);

    /**
     * @mixin just
     * @since 1.0.0-rc.23
     * @borrows just.request as ajax
     */
    var ajax = request;

    set('ajax', ajax);

    var Define = (function () {

        var modules = {};
        var defaultErrorHandler = function (exception) { throw exception; };
        var timeout;

        function defineKnownValue (id, context, alias) {

            var value = context[id];

            if (!(id in context) || isModuleDefined(id)) { return false; }

            new Define(id, [], value);

            return true;

        }

        function defineAlias (id, alias) {

            if (isModuleDefined(alias)) { return false; }

            new Define(alias, [id], function (value) { return value; });

            return true;

        }

        function defineGlobal (id) { return defineKnownValue(id, Define.globals); }
        function defineNonScript (id) { return defineKnownValue(id, Define.nonScripts); }
        function defineKnownModule (id, isLoaded) {

            if (isModuleDefined(id)) { return false; }

            if (id in Define.urls && !isLoaded) { return loadModule(id); }
            else if (id in Define.nonScripts) { return defineNonScript(id); }
            else if (id in Define.globals) { return defineGlobal(id); }

            return false;

        }

        function loadModule (id, onLoad) {

            var urlDetails = Define.urls[id];
            var urlDetailsObject = Object(urlDetails);
            var url = (typeof urlDetails === 'object'
            ? urlDetailsObject.src || urlDetailsObject.href
            : typeof urlDetails === 'string'
            ? urlDetails
            : id
            );
            var URL = parseUrl(url);
            var urlExtension = (URL.pathname.match(/\.(.+)$/) || ['js'])[0];
            var type = ('tagName' in urlDetailsObject
            ? urlDetailsObject.tagName
            : 'src' in urlDetailsObject
            ? 'script'
            : 'href' in urlDetailsObject
            ? 'link'
            : (/css$/i.test(urlExtension) ? 'link' : 'script')
            );
            var properties = (typeof urlDetails === 'object'
            ? (delete urlDetails.tagName, urlDetails)
            : null
            );

            function listenerWrapper (e) {

                var isError = Object(e).type === 'error';

                removeEventListener(this, ['load', 'error'], listenerWrapper);

                if (!isError) { defineKnownModule(id, true); }
                if (typeof onLoad === 'function') { return onLoad.call(this, e); }
                if (isError) { Define.handleError.call(null, new Error('Error loading ' + url)); }

            }

            if (!(id in Define.urls)) { Define.urls[id] = url; }
            if (url !== id) { defineAlias(id, url); }

            return loadElement(type, properties || url, listenerWrapper, function (similarScript) {

                if (type !== 'script' && !(id in Define.nonScripts)) { Define.nonScripts[id] = this; }
                if (similarScript) {

                    addEventListener(similarScript, ['load', 'error'], listenerWrapper);

                    return false;

                }

                document.head.appendChild(this);

                return true;

            });

        }

        function getModule (id) {

            defineKnownModule(id);

            return modules[id] || null;

        }

        function getModules (ids) {

            return ids.map(function (id) { return getModule(id); });

        }

        function wasCalled (module) {

            return Object(module).state === Define.STATE_CALLED;

        }

        function wereCalled (modules) {

            return modules.every(function (module) { return wasCalled(module); });

        }

        function hasID (id, module) {

            return Object(module).id === id;

        }

        function hasCircularDependencies (module) {

            return module && module.dependencies.some(
                hasID.bind(null, module.id)
            );

        }

        function hasRecursiveDependencies (module) {

            return module && module.dependencies.some(function (d) {

                return d && d.dependencies.some(
                    hasID.bind(null, this.id)
                );

            }, module);

        }

        function callModule (module) {

            var handler = module.handler;
            var dependencies = module.dependencies;
            var errorHandlerResult;
            var isEveryDependencyWaiting;
            var args;

            if (wasCalled(module)) { return false; }

            module.state = Define.STATE_NON_CALLED;
            isEveryDependencyWaiting = dependencies.every(
                function (d) { return d && d.state !== Define.STATE_DEFINED; }
            );

            if (wereCalled(dependencies) || isEveryDependencyWaiting && (
                hasCircularDependencies(module)
            || hasRecursiveDependencies(module)
            )) {

                args = dependencies.map(function (d) { return d.exports; });
                module.state = Define.STATE_CALLING;

                try { module.exports = handler.apply(module, args); }
                catch (exception) { errorHandlerResult = Define.handleError.call(module, exception); }

                module.state = Define.STATE_CALLED;

                return (typeof errorHandlerResult === 'boolean'
                ? errorHandlerResult
                : true
                );

            }

            return false;

        }

        function isValidID (id) {

            return typeof id === 'string' && id.trim() !== '';

        }

        function isModuleDefined (id) {

            return id in modules;

        }

        function isNullOrUndefined (value) {

            return value === null || typeof value === 'undefined';

        }

        /**
         * Define a value after all dependencies became available.
         *
         * <br/>
         * <aside class='note'>
         *     <h3>A few things to consider: </h3>
         *     <ul>
         *         <li>This is not intended to be AMD compliant.</li>
         *
         *         <li>This does not check file contents, so it won't check if the
         *             file defines an specific id.</li>
         *
         *         <li>Urls passed as dependencies are considered ids, so they must
         *             be defined in {@link just.Define.urls} in order to be loaded first.</li>
         *
         *         <li><var>require</var>, <var>module</var> and <var>exports</var>
         *             are not present in this loader, but you can emulate them.</li>
         *
         *         <li>Recursive and circular dependencies pass a recursive module
         *            as argument within another recursive module (instead of the returned value).
         *            Please, avoid using them or use them carefully.</li>
         *     </ul>
         * </aside>
         *
         * @class
         * @memberof just
         * @param {?string} id - The module id.
         * @param {string[]|string} dependencyIDs - Required module ids.
         * @param {*} value - The module value.
         *
         * @example
         * // https://some.cdn/js/just.js
         * window.just = {'Define': function () {}};
         *
         * // index.html
         * < !DOCTYPE html>
         * < html data-just-Define='{"main": "/js/main.js"}'>
         * < head>
         *    < title>Test</title>
         *    < script src='https://some.cdn/js/just.js' async></script>
         * < /head>
         * < body>
         * < /body>
         * < /html>
         *
         * // /js/main.js
         * just.Define.configure({
         *    'globals': {
         *        // Set justJs to window.just
         *        'justJs': function () { return just; }
         *    },
         *    'urls': {
         *        // Load "/css/index.css" when "index.css" is required.
         *        'index.css': '/css/index.css'
         *    },
         *    'nonScripts': {
         *        // Call when "main.html" is required.
         *        'main.html': function () { return '<main></main>'; }
         *    }
         * });
         *
         * // Load when document, justJs and index.css are ready:
         * just.Define('main', ['justJs', 'index.css'], function (j) {
         *
         *    if (j.supportsTouch()) {
         *        j.Define('mobile', 'https://example/m');
         *        return;
         *    }
         *
         *    j.Define('non-mobile', ['main.html']);
         *
         * });
         *
         * // Call only if j.supportsTouch()
         * just.Define(['mobile'], function (url) {
         *    window.redirect = url;
         * });
         *
         * // Call when main.html is ready.
         * just.Define(['non-mobile'], function (html) {
         *    document.body.innerHTML = html;
         * });
         */
        function Define (id, dependencyIDs, value) {

            if (!arguments.length || typeof value !== 'function' && [id, dependencyIDs].every(
                function (value) { return isNullOrUndefined(value); }
            )) {

                throw new TypeError('Not enough arguments.');

            }

            if (arguments.length === 2) {

                value = arguments[1];

                if (Array.isArray(arguments[0])) {

                    dependencyIDs = arguments[0];
                    id = void 0;

                }
                else {

                    dependencyIDs = void 0;

                }

            }
            else if (arguments.length === 1) {

                if (typeof arguments[0] === 'function') {

                    value = arguments[0];
                    id = void 0;
                    dependencyIDs = void 0;

                }

            }

            if (!isValidID(id)) {

                if (isNullOrUndefined(id)) { id = Math.random().toString(); }
                else { throw new TypeError('The given id (' + id + ') is invalid.'); }

            }

            if (isNullOrUndefined(dependencyIDs)) { dependencyIDs = []; }
            else if (!Array.isArray(dependencyIDs)) { dependencyIDs = [dependencyIDs]; }

            if (dependencyIDs.some(
                function (id) { return !isValidID(id); }
            )) { throw new TypeError('If present, the ids for the dependencies must be valid ids.'); }

            if (!(this instanceof Define)) { return new Define(id, dependencyIDs, value); }

            modules[id] = this;

            defineProperties(this, {
                'id': id,
                'handler': (typeof value === 'function'
                ? value
                : function () { return value; }
                ),
                'dependencyIDs': dependencyIDs,
                'dependencies': {
                    'get': function () { return getModules(this.dependencyIDs); }
                },
                'state': {
                    'value': Define.STATE_DEFINED,
                    'writable': true
                },
                'exports': {
                    'value': (typeof value === 'function' ? this : value),
                    'writable': true
                }
            });

            clearTimeout(timeout);

            timeout = setTimeout(function updateModules () {

                eachProperty(modules, function (module) {

                    if (callModule(module)) { return updateModules(), true; }

                });

            });

        }

        defineProperties(Define, /** @lends just.Define */{
        /**
         * The initial value for all defined modules.
         *
         * @type {number}
         * @readOnly
         */
            'STATE_DEFINED': -1,
            /**
             * The value for all modules that had been queued
             * prior to be called.
             *
             * @type {number}
             * @readOnly
             */
            'STATE_NON_CALLED': 0,
            /**
             * The value for all modules that are being called.
             *
             * @type {number}
             * @readOnly
             */
            'STATE_CALLING': 1,
            /**
             * The value for all modules that were called.
             *
             * @type {number}
             * @readOnly
             */
            'STATE_CALLED': 2,
            /**
             * A list of urls that will be used (instead of ids) to load
             * files before defining globals or non-script values.<br/>
             *
             * <aside class='note'>
             *     <h3>Note:</h3>
             *     <p>If you need to load files when you require some id,
             *        you need to specify those urls here. If you do so, you
             *        must {@link just.Define|Define} that id/url within that file.</p>
             *     <p>Starting from v1.0.0-rc.23, you can pass an object to specify
             *        the attributes for the loaded element.</p>
             * </aside>
             *
             * @example
             * // js/b.js
             * just.Define('b', 1); // or: just.Define('js/b.js', 1);
             *
             * // index.js
             * just.Define.urls['b'] = 'js/b.js';
             * just.Define('a', ['b'], function (b) {
             *     // b === 1; > true
             * });
             *
             * @example <caption>Using multiple ids with the same url</caption>
             * // js/index.js
             * just.Define('foo', 1);
             * just.Define('bar', 1);
             *
             * // index.js
             * just.assign(just.Define.urls, {
             *     'foo': 'js/index.js',
             *     'bar': 'js/index.js'
             * });
             *
             * just.Define('foo-bar', ['foo', 'bar'], function () {
             *     // Will load js/index.js once.
             * });
             *
             * @example <caption>Since v1.0.0-rc.23: Adding custom attributes to the loaded element.</caption>
             * just.assign(just.Define.urls, {
             *     'id': {
             *         'src': 'https://some.cdn.com',
             *         'integrity': 'sha512-...',
             *         'crossorigin': '',
             *         'data-some-other': 'attribute'
             *     }
             * });
             *
             * just.Define(['id'], function () {
             *     // Will load a <script> with the given attributes ("integrity", "crossorigin", ...).
             * });
             *
             * @example <caption>Since v1.0.0-rc.23: Load a custom element.</caption>
             * just.assign(just.Define.urls, {
             *     'id': {
             *         'tagName': 'iframe',
             *         'src': 'https://example.org'
             *     }
             * });
             *
             * just.Define(['id'], function () {
             *     // Will load an <iframe> with the given attributes.
             * });
             *
             * @type {!object.<just.Define~id, url>|!object.<just.Define~id, object.<elementAttributes>>}
             */
            'urls': {
                'value': {},
                'writable': true
            },
            /**
             * A writable object literal that contains values for non script
             * resources, like css. Since {@link just.Define|Define} won't
             * check for file contents when loads a new file, you must add
             * the value here.</br>
             *
             * <aside class='note'>
             *     <h3>Note:</h3>
             *     <p>If a module is defined with the same id, the module will take
             *     precedence.</p>
             * </aside>
             *
             * @example
             * just.Define.nonScripts['/css/index.css'] = function () {};
             * just.Define('load css', ['/css/index.css'], function (css) {
             *     // by default, `css` is an HTMLElement (the link element that loaded the file).
             *     // but for now, `css` is a function since the id wasn't defined in Define.urls
             * });
             *
             * @type {!object.<just.Define~id, *>}
             */
            'nonScripts': {
                'value': {},
                'writable': true
            },
            /**
             * A writable object literal that contains all the values that
             * will be defined when required.<br/>
             *
             * <aside class='note'>
             *     <h3>Notes:</h3>
             *     <ul>
             *         <li><strong>Deprecated since 1.0.0-rc.24. It raises a security error over a CDN.</strong>
             *             If the value for the global is a string, the property
             *             will be accessed from window. I.e.:
             *             <var>'some.property'</var> will access to <var>window.some.property</var>.
             *         </li>
             *         <li>If a module is defined with the same id, the module will take
             *             precedence.</li>
             *         <li>If a non-script is defined with the same id, a non-script value
             *          will take precedence.</li>
             *     </ul>
             * </aside>
             *
             * @example
             * // index.js
             * just.Define.globals['just'] = 1;
             * just.Define('index', ['just'], function (just) {
             *     // just === 1; > true
             * });
             *
             * @example <caption>Defining a global on file load.</caption>
             * // https://some.cdn/js/just.js
             * window.just = {Define: 1};
             *
             * // main.js
             * just.Define.globals['JustJs'] = function () { return just; };
             * just.Define.urls['JustJs'] = 'https://some.cdn/js/just.js';
             * just.Define('main', ['JustJs'], function (just) {
             *     // just === {Define: 1};
             * });
             *
             * @example <caption>Defining a global after a file has loaded already.</caption>
             * // https://some.cdn/js/just.js
             * window.just = {Define: 1};
             *
             * // index.html
             * <script src='https://some.cdn/js/just.js'
             *   data-just-Define='{"JustJs": "[src]"}' async></script>
             *
             * // main.js
             * if ('just' in window) { just.Define('JustJs', just); }
             * else { just.Define.globals['JustJs'] = function () { return just; }; }
             *
             * just.Define(['JustJs'], function (just) {
             *     // just === {Define: 1};
             * });
             *
             * @type {!object.<just.Define~id, *>}
             */
            'globals': {
                'value': {},
                'writable': true
            },
            /**
             * Check if a module is defined.
             *
             * @function
             * @return {boolean}
             */
            'isDefined': isModuleDefined,
            /**
             * Load a module explicitly.
             *
             * @function
             * @param {url|just.Define~id} id - Some url or an alias defined in {@link just.Define.urls}.
             * @param {?function} onLoad - Some listener to call when the function loads.
             */
            'load': loadModule,
            /**
             * Configure any writable option in {@link just.Define} using an object.
             *
             * @example
             * just.Define.configure({
             *     'urls': {}, // Same as Define.urls = {}
             *     'handleError': function () {}, // Same as Define.handleError = function () {}
             *     'load': 1 // Same as Define.load = 1 > throws Define.load is read-only.
             * })('id', [], function () {}); // Define afterwards.
             *
             * @function
             * @param {!object} properties - Writable properties from {@link just.Define}.
             * @chainable
             */
            'configure': function (properties) {

                assign(Define, properties);

                return Define;

            },
            /**
             * Empty all internal variables and writable properties.
             *
             * @function
             * @chainable
             */
            'clear': function () {

                Define.globals = {};
                Define.nonScripts = {};
                Define.urls = {};
                Define.handleError = defaultErrorHandler;
                Define.clearModules();

                return Define;

            },
            /**
             * Remove all modules.
             *
             * @function
             * @chainable
             */
            'clearModules': function () { return (modules = {}), this; },
            /**
             * Remove some module.
             *
             * @function
             * @param {just.Define~id} id - The id for the module.
             * @chainable
             */
            'clearModule': function (id) { return (delete modules[id]), this; },
            /**
             * A function to be called when an async error occur.
             *
             * @function
             * @param {*} exception - Some throwable exception.
             * @this just.Define
             * @return {boolean} <var>true</var> if you want to keep updating modules.
             */
            'handleError': {
                'value': defaultErrorHandler,
                'writable': true
            },
            /**
             * Finds {@link just.Define.urls|urls} within the document, adds them, and
             * loads them.<br/>
             *
             * <aside class='note'>
             *     <h3>Note</h3>
             *     <p>This function is called when the file is loaded.</p>
             * </aside>
             *
             * @function
             * @chainable
             */
            'init': function loadUrlsFromDocument () {

                Define.configure({
                    'urls': Define.findUrlsInDocument('data-just-Define')
                });

                eachProperty(Define.urls, function (url, id) { Define.load(id); });

                return Define;

            },

            /**
             * Finds {@link just.Define.urls|urls} within the document
             * by selecting all the elements that contain an specific
             * attribute and parsing that attribute as a JSON.
             * <br/>
             * <aside class='note'>
             *     <h3>Note</h3>
             *     <p>Values within brackets will be replaced with
             *     actual attributes for that element.</p>
             *     <p>I.e.: &lt;span a='123' data-urls='{"[a]456": "[a]456"}'&gt;&lt;/span&gt;
             *     will become: {123456: '123456'}</p>
             * </aside>
             *
             * @function
             * @param {string} attributeName - The attribute which defines the
             *     {@link just.Define.urls|urls} to be loaded.
             * @param {Element} [container=document]
             *
             * @example
             * // Considering the following document:
             * < body>
             *     < div id='a' data-urls='{"[id]": "link a.css"}'>< /div>
             *     < script src='b.js' data-urls='{"b": "script [src]"}'>< /script>
             * < /body>
             *
             * // then, in js:
             * findUrlsInDocument('data-urls');
             * // Should return {a: 'link a.css', b: 'script b.js'}.
             *
             * @return {!just.Define.urls}
             */
            'findUrlsInDocument': function (attributeName, container) {

                var urls = {};

                findElements('*[' + attributeName + ']', container).forEach(function (element) {

                    var attribute = element.getAttribute(attributeName) + '';
                    var urls = stringToJSON(attribute.replace(/\[([^\]]+)\]/ig,
                        function (_, key) { return element.getAttribute(key); }
                    ));

                    assign(this, urls);

                }, urls);

                return urls;

            }
        });

        defineProperties(Define.prototype, /** @lends just.Define# */{
        /**
         * Same as {@link Define.load}, but chainable.
         *
         * @function
         * @chainable
         */
            'load': function () {

                loadModule.apply(null, [].slice.call(arguments));

                return this;

            }
        });

        return Define;

    })();

    onDocumentReady(Define.init);

    set('Define', Define);

    /**
     * @mixin just
     * @borrows just.Define as Def
     */
    var Def = Define;

    set('Def', Def);


    set('version', '1.2.0'); set('just', just);

    return just;

});