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 Copyright 2019 Alexis Puga Ruíz.
 * @file Just js: The core for all your javascript proyects.
 * @version 1.0.0-rc.17
 */
(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 () {

    /* eslint-disable */
	/**
	 * Non-compliant polyfills for common ES6+ functions, written in ES5:
	 * Object.assign, Object.values, DOMTokenList.prototype.replace.
	 */
	(function(){var O=Object;function fp(c,p,f){var o=c[p]=c[p]||f;f===o&&(o.justPolyfill=!0)}function fy(o){return o.prototype}fp(O,'assign',function(o){var a=arguments,i=a.length,k,v;o=Object(o);while(--i>=1){v=a[i];for(k in v){if(({}).hasOwnProperty.call(v,k)){o[k]=v[k]}}}return o});fp(O,'values',function(o){var r=[],k;for(k in o){({}).hasOwnProperty.call(o,k)&&r.push(o[k])}return r});if(typeof window!=='undefined'){fp(fy(DOMTokenList),'replace',function(a,b){var t=this;return b.trim()&&t.contains(a)&&(t.remove(a),t.add(b),!0)})}})();
	/* eslint-enable */

    /* 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);

    /**
     * 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>
     * 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>
     * access({a: 1}, ['a', 'b', 'c']); // throws TypeError.
     * 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 = 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.
     * Object.assign(newObj.prototype, obj.prototype);
     *
     * @example <caption>Modifying the base object</caption>
     * var obj = {a: {b: false}, b: {b: false}, prototype: [...]};
     *
     * 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 = Object.assign({}, {
            'override': true,
            'mutate': false
        }, opts);
        var properties = Array.isArray(path) ? path : [path];
        var initialObject = options.mutate ? object : 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) {

            if (!(currentObject[key] instanceof Object)) {

                if (typeof currentObject[key] !== 'undefined'
                && currentObject[key] !== null
                && !options.override) {

                    throw new TypeError('The value of "' + key +
                    '" is not an object.');

                }

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

            }

            currentObject = currentObject[key];

        });

        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
     * check(null, {}, "null", []); // false. Neither is `null`.
     * check({}, [], {}); // true. {} is {}.
     *
     * @return {boolean} `true` if some other value looks like <var>value</var>.
     */
    function check (value, otherValues) {

        return [].slice.call(arguments, 1).some(function (otherValue, i) {

            if ([value, otherValue].some(
                function (v) { return v === null || v === void 0; }
            )) {

                return otherValue === value;

            }

            return value.constructor === otherValue.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
         * @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 = (!(Object(this) instanceof String) ? (value +
            ' must be like one of the following values: ' +
            args.slice(1).map(function (v) { return v + ''; }).join(', ')
        ) : this);

            if (!check.apply(this, args)) { 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;
     *
     * 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); }

        /** @member {Element} */
        this.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
     * defaults([1, 2], {a: 1}); // {a: 1}
     *
     * @example
     * defaults({}, null); // null: null is not an object literal.
     * defaults([], null, {'checkLooks': false}); // []: null is an object.
     * defaults(null, {}); // {}: null is not an object literal.
     * defaults(null, []); // []: null is not an Array.
     *
     * @example
     * defaults(1, NaN); // 1 (NaN is an instance of a Number)
     *
     * @example
     * defaults({'a': 1, 'b': 2}, {'a': 'some string'}, {'ignoreDefaultKeys': false}); // {'a': 'some string', 'b': 2}
     *
     * @example
     * defaults({'a': 1}, {'b': 2}, {'ignoreDefaultKeys': false}); // {'a': 1, 'b': 2}
     * defaults({'a': 1}, {'b': 2}, {'ignoreDefaultKeys': true}); // {'a': 1}
     *
     * @example
     * defaults(1, null, {'ignoreNull': false}) // null (1 is not an object)
     * defaults(1, null, {'ignoreNull': true}) // 1
     * defaults(undefined, null, {'ignoreNull': true}) // null
     * 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 = Object.assign({}, {
            'ignoreDefaultKeys': false,
            'checkLooks': true,
            'checkDeepLooks': true,
            'ignoreNull': false
        }, opts);

        /* eslint-disable padded-blocks */
        if (options.ignoreNull && defaultValue === null && value !== void 0) {
            return value;
        }
        /* eslint-enable padded-blocks */

        if (options.checkLooks) {

            /* eslint-disable padded-blocks */
            if (!check(value, defaultValue)) {
                return defaultValue;
            }
            /* eslint-enable padded-blocks */

            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 (value, key) {

            defineProperty(object, key, value);

        });

        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.
     *
     * If <var>object</var> contains any other key that's not a valid attribute for a
     * {@link propertyDescriptor|property descriptor} the value WON'T be used
     * as a property descriptor. I.e:
     * <code>
     * defineProperty({}, 'property', {
     *     value: 1,
     *     other: 'value'
     * }).property; // > {value: 1, other: 'value'}
     * </code>
     *
     * Note: Empty objects will be considered values rather than property descriptors.
     *
     * @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}] - A {@link propertyDescriptor} or some value.
     * @return <var>object</var>.
     */
    function defineProperty (object, key, value) {

        var descriptor = Object(value);
        var defaultAttributes = Object.assign(Object.getOwnPropertyDescriptor(
            Object.defineProperty({}, '_', {}), '_'
        ), {
            'get': void 0,
            'set': void 0
        });

        if ((value || {}).constructor !== ({}).constructor || !Object.keys(defaultAttributes).some(
            function (key) { return key in descriptor; }
        ) || !Object.keys(descriptor).every(
            function (key) { return key in defaultAttributes; }
        )) {

            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 = Object.assign({}, {
            'addNonOwned': false
        }, opts);
        var wasInterrupted = false;
        var k;

        /* eslint-disable padded-blocks */
        if (typeof fn !== 'function') {
            throw new TypeError(fn + ' is not a function.');
        }
        /* eslint-enable padded-blocks */

        for (k in properties) {

            if (wasInterrupted) { break; }

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

                wasInterrupted = !!fn.call(thisArg, properties[k], k,
                    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;

        /* eslint-disable padded-blocks */
        if (typeof fn !== 'function') {
            throw new TypeError(fn + ' is not a function.');
        }

        if (!(childNode instanceof Node)) {
            throw new TypeError('The child isn\'t an instance of a Node.');
        }

        if (!(rootContainer instanceof Node)) {
            rootContainer = document.documentElement;
        }
        /* eslint-enable padded-blocks */

        while (currentNode) {

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

                return currentNode;

            }

            /* eslint-disable padded-blocks */
            if (currentNode === rootContainer) {
                return null;
            }
            /* eslint-enable padded-blocks */

            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 !!('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
     * @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) {

        return !!(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} tag - A tag name.
     * @param {!url} url - The url of the file.
     * @param {just.loadElement~handler} [handler=appendToHead]
     *     If it's a function: it will be triggered
     *     (without appending the element),
     *     otherwise: the element will be appended to
     *     {@link just.head|head}.
     * @param  {just.loadElement~listener} [listener] - A function to trigger after
     *     the element is appended.
     *
     * @return {*} The return of the {@link just.loadElement~handler|handler}.
     */
    function loadElement (tag, url, listener, handler) {

        var attribute = loadElement.nonSrcAttributes[tag] || 'src';
        var element = document.createElement(tag);
        var parsedUrl = parseUrl(url);
        var selectors = [
            tag +'[' + attribute + '="' + url + '"]',
            tag + '[' + attribute + '="' + parsedUrl.href + '"]'
        ];
        var elementFound = findElements(selectors.join(','))[0] || null;
        var intercept = typeof handler === 'function' ? handler : function appendToHead (elementFound, url) {

            if (!elementFound) { document.head.appendChild(this); }

            return this;

        };

        if (!url || typeof url !== 'string') { throw new TypeError(url + ' is not a valid url.'); }

        if (tag === 'link') {

            // Default attributes:
            element.rel = 'stylesheet';

        }

        if (parsedUrl.origin !== window.location.origin
        && ['video', 'img', 'script', 'link'].indexOf(tag) >= 0) {

            element.setAttribute('crossorigin', 'anonymous');

        }

        if (typeof listener === 'function') {

            element.onerror = element.onload = function (e) {

                this.onload = this.onerror = null;

                return listener.call(this, e);

            };

        }

        element[attribute] = url;

        return intercept.call(element, elementFound, url);

    }

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

        /**
         * An src-like attribute for an Element.
         *
         * @typedef {string} just.loadElement~srcLikeAttribute
         */

        /**
         * {@link element_tag|Element-tags} that are known
         * for not using 'src' to fetch a url.
         *
         * @type {Object.<
         *     element_tag,
         *     just.loadElement~srcLikeAttribute
         * >}
         */
        'nonSrcAttributes': {
            'link': 'href'
        }

    });

    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) {

        /* eslint-disable padded-blocks */
        if (!(this instanceof LocalStorage)) {
            return new LocalStorage(consent, isExplicit);
        }
        /* eslint-enable padded-blocks */

        defineProperties(this, {
            'consent': !!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;

            }

            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) {

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

        [].slice.call((elements = elements || [])).forEach(function (element) {

            eventNames.forEach(function (eventName) {

                element.addEventListener(eventName, listener, options || false);

            });

        });

        return elements;

    }

    set('addEventListener', addEventListener);

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

    set('on', on);

    /**
     * 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>
     * parseUrl(window.location.href);
     *
     * @example <caption>A relative url:</caption>
     * parseUrl('/?a#c?d'); // "/" is the pathname, "?a" the search and "#c?d" the hash.
     *
     * @example <caption>A blob url:</caption>
     * parseUrl('blob:'); // Same as 'blob:' + `window.location.href`
     *
     * @example <caption>Some good-to-know urls:</caption>
     * parseUrl(); // Same as `window.location`.
     * parseUrl('a'); // Something that doesn't start with "/", "?", or "#" is evaluated as a host.
     * parseUrl('a:b'); // "a:b" is a host, since "b" is not a number.
     * parseUrl('//'); // evals as the current origin.
     * 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 Object.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);

    /**
     * Parses a JSON string into a JSON.
     *
     * @namespace
     * @memberof just
     * @param {*} string - Some string to parse.
     *
     * @example
     * stringToJSON('{"a": 1}'); // returns {a: 1}.
     *
     * @example
     * stringToJSON(1); // returns {}.
     *
     * @return {!object} A JSON-like object.
     */
    function stringToJSON (string) {

        var json;

        if (!/\{.+\}/.test(string)) { return {}; }

        try { json = JSON.parse(string); }
        catch (exception) { return {}; }

        return json;

    }

    set('stringToJSON', stringToJSON);

    /**
     * @mixin just
     * @borrows just.stringToJSON as toJSON
     */
    var toJSON = stringToJSON;

    set('toJSON', toJSON);

    /**
     * Converts <code>[[k0, v0], {k1: v1}]</code> to <code>{k0: v0, k1: v1}</code>.
     *
     * @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 = {};

        /* eslint-disable padded-blocks */
        if (check(array, {}, null)) {
            return Object.assign({}, array);
        }

        if (!check(array, [])) {
            throw new TypeError(array + ' must be either ' +
			'null, an object literal or an Array.');
        }
        /* eslint-enable padded-blocks */

        array.forEach(function (subArray) {

            var key, value;

            if (check(subArray, [])) {

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

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

                Object.assign(this, subArray);

            }
            else {

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

            }

        }, objectLiteral);

        return objectLiteral;

    }

    set('toObjectLiteral', toObjectLiteral);

    /**
     * @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; }

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

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

        });

    }

    set('onDocumentReady', onDocumentReady);

    var Define = (function () {

        'use strict';

        var STATE_NON_CALLED = 0;
        var STATE_CALLING = 1;
        var STATE_CALLED = 2;
        var root = window;
        var definedModules = {};

        /**
         * A module loader: it loads {@link just.Define~file|files} in order (when needed) and
         * execute them when all his 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 aliased first in order to be loaded.</li>
         *
         *         <li><var>require</var>, <var>module</var> and <var>exports</var>
         *             are not present in this loader.</li>
         *
         *         <li>Anonymous modules are not allowed.</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
         * var files = just.Define.findInDocument('data-files');
         * var fileIDs = Object.keys(files);
         *
         * just.Define.load(files);
         * just.Define('some id', fileIDs, function (file1, file2, ...) {
         *     // Loads after all ids have been defined.
         * });
         */
        function Define (id, dependencyIDs, value) {

            var handler;

            /* eslint-disable padded-blocks */
            if (!(this instanceof Define)) {
                return new Define(id, dependencyIDs, value);
            }

            if (typeof id !== 'string') {
                throw new TypeError('The id must be a string');
            }
            /* eslint-enable padded-blocks */

            if ((typeof value === 'undefined' && !check(dependencyIDs, []))
			|| typeof dependencyIDs === 'function') {

                value = arguments[1];
                dependencyIDs = [];

            }

            if (typeof value === 'function') { handler = value; }

            defineProperties(this, {

                'id': id,
                'dependencyIDs': normalizeIDs(dependencyIDs),
                'handler': handler,
                'state': {
                    'value': STATE_NON_CALLED,
                    'writable': true
                },
                'returnedValue': {
                    'value': handler ? void 0 : value,
                    'writable': true
                }

            });

            setModule(id, this);

            updateModules();

        }

        function setModule (id, theModule) { return definedModules[id] = theModule; }
        function getModule (id) { return definedModules[id]; }
        function hasModule (id) { return id in definedModules; }

        function callModule (someModule) {

            var dependencies;

            if (someModule.state === STATE_CALLED) { return true; }

            someModule.state = STATE_CALLING;

            /* istanbul ignore else */
            if (!someModule.dependencies) {

                dependencies = someModule.dependencyIDs.map(
                    function (dependencyID) { return getModule(dependencyID); }
                );

                if (dependencies.some(
                    function (dependency) { return !dependency || dependency.state === STATE_NON_CALLED; }
                )) {

                    return false;

                }

                someModule.dependencies = dependencies;

            }

            if (someModule.handler) {

                someModule.returnedValue = someModule.handler.apply(null,
                    someModule.dependencies.map(
                        function (dependency) { return dependency.returnedValue; }
                    )
                );

            }

            someModule.state = STATE_CALLED;

            return true;

        }

        function updateModules () {

            clearTimeout(updateModules.timeout);

            updateModules.timeout = setTimeout(function () {

                var nonReadyModules = [];
                var somethingNewWasCalled = false;

                eachProperty(definedModules, function (someModule, id) {

                    /* eslint-disable padded-blocks */
                    if (!someModule || someModule.state === STATE_CALLED) {
                        return;
                    }

                    if (callModule(someModule)) {
                        somethingNewWasCalled = true;
                    }
                    else {
                        nonReadyModules.push(someModule);
                    }
                    /* eslint-enable padded-blocks */

                });

                /* eslint-disable padded-blocks */
                if (somethingNewWasCalled) {
                    return updateModules();
                }

                if (nonReadyModules.length) {
                    return false;
                }
                /* eslint-enable padded-blocks */

                return true;

            }, 0);

        }

        function loadModuleByID (moduleID, listener) {

            var urlParts = (Define.files[moduleID] || '').split(/\s+/);
            var eventListener = defaults(listener, function setUrlAsAlias (error, data) {

                var id = data.id;
                var givenUrl = data.url;
                var loadedUrl = this.src;

                /* istanbul ignore else */
                if (!getModule(loadedUrl) && id !== loadedUrl && id !== givenUrl) {

                    new Define(loadedUrl, [id],
                        function (theModule) { return theModule; }
                    );

                }

            });
            var url = urlParts[1];
            var tagName = urlParts[0];

            /* eslint-disable padded-blocks */
            if (!(moduleID in Define.files)) {
                throw new TypeError(moduleID + ' must be added to "files".');
            }
            /* eslint-enable padded-blocks */

            if (!url) {

                url = urlParts[0];
                tagName = 'script';

            }

            loadElement(tagName, url, function (event) {

                var globals = Define.globals;
                var error = (event.type === 'error'
                ? new Error('Error loading the following url: ' + this.src)
                : null
                );

                if (moduleID in globals && !getModule(moduleID)) {

                    new Define(moduleID, defaults(globals[moduleID], function () {

                        return access(root, globals[moduleID].split('.'), null, {
                            'mutate': true
                        });

                    }));

                }

                eventListener.call(this, error, {
                    'event': event,
                    'id': moduleID,
                    'url': url
                });

            });

        }

        function normalizeIDs (ids) {

            if (check(ids, null, void 0)) { ids = []; }

            return defaults(ids, [ids]).map(function (value) {

                var id = check.throwable(value, 'string');

                if (!hasModule(id) && id in Define.files) {

                    loadModuleByID(id);

                }

                return id;

            });

        }

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

            /**
             * Finds {@link just.Define.files|files} within the document, adds them, and
             * if some is called "main", it loads it.
             * <br/>
             * <aside class='note'>
             *     <h3>Note</h3>
             *     <p>This function is called when the file is loaded.</p>
             * </aside>
             *
             * @function
             * @chainable
             */
            'init': function () {

                var files = Define.findInDocument('data-just-Define');

                Define.addFiles(files).load(Object.keys(files));

                return Define;

            },

            /**
             * A function to be called when the {@link just.Define~file|file} load.
             *
             * @typedef {function} just.Define~load_listener
             * @param {!Error} error - An error if the url is not being loaded.
             * @param {!object} data - Some metadata.
             * @param {!Event} data.event - The triggered event: "load" or "error".
             * @param {!just.Define~id} data.moduleID - The id passed to {@link just.Define}.
             * @param {!url} data.url - The loaded url.
             */

            /**
             * A function to load {@link just.Define~file|files} by ids.
             *
             * @function
             * @param {just.Define~file_id|just.Define~file_id[]|Object.<
             *     just.Define~file_id,
             *     just.Define~load_listener
             * >} value - {@link just.Define~file_id|File ids}.
             * @chainable
             */
            'load': function (value) {

                if (check(value, {})) {

                    eachProperty(check.throwable(value, {}), function (listener, id) {

                        loadModuleByID(id, check.throwable(listener, Function, null, void 0));

                    });

                }
                else if (check(value, [], 'string')) {

                    defaults(value, [value]).forEach(
                        function (id) { loadModuleByID(id); }
                    );

                }
                else {

                    check.throwable(value, {}, [], 'string');

                }

                return Define;

            },

            /**
             * An alias for a url.
             *
             * @typedef {string} just.Define~file_id
             */

            /**
             * Any element that references an external source, like an
             * &lt;script&gt; or a &lt;link&gt;.
             *
             * @typedef {string} just.Define~file
             */

            /**
             * A url, or a tag name with a url splitted by an space.
             *
             * By default, "http://..." is the same as "script http://..."
             *
             * @typedef {string} just.Define~files_expression
             *
             * @example <caption>A url</caption>
             * "http://..."
             *
             * @example <caption>A tag name with a url.</caption>
             * "link /index.css"; // Note the space in between.
             */

            /**
             * Aliases for urls.
             *
             * @type {Object.<
             *     just.Define~file_id,
             *     just.Define~files_expression
             * >}
             */
            'files': {
                'value': {},
                'writable': true
            },

            /**
             * A function that returns the module value or a string
             * splitted by '.' that will be {@link just~access|accessed}
             * from <var>window</var>.
             *
             * @typedef {string|function} just.Define~globals_expression
             *
             * @example <caption>A function.</caption>
             * function () { return 1; } // The module value is 1.
             *
             * @example <caption>A string.</caption>
             * "a.b"; // accesses to window, then window.a,
             *        // and then returns window.a.b
             */

            /**
             * Aliases for ids.
             *
             * @type {Object.<
             *     just.Define~id,
             *     just.Define~globals_expression
             * >}
             */
            'globals': {
                'value': {},
                'writable': true
            },

            /**
             * Assigns values to {@link just.Define.globals|the globals property}.
             *
             * @function
             * @chainable
             * @param {just.Define.globals} value - {@link just.Define.globals|Globals}.
             */
            'addGlobals': function (value) {

                Object.assign(Define.globals, value);

                return Define;

            },

            /**
             * Assigns values to {@link just.Define.files|the files property}.
             *
             * @function
             * @chainable
             * @param {just.Define.files} value - {@link just.Define.files|Files}.
             */
            'addFiles': function (value) {

                Object.assign(Define.files, value);

                return Define;

            },

            /**
             * Checks if module has been defined.
             *
             * @function
             * @param {just.Define~id} id - The id passed to {@link just.Define}.
             * @return {boolean} `true` if defined, `false` otherwise.
             */
            'isDefined': hasModule,

            /**
             * Removes all defined modules.
             *
             * @function
             * @chainable
             */
            'clean': function () {

                definedModules = {};

                return Define;

            },

            /**
             * Finds {@link just.Define.files|files} 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-files='{"[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.files|files} to be loaded.
             * @param {Element} [container=document]
             *
             * @example
             * // Considering the following document:
             * < body>
             *     < div id='a' data-files='{"[id]": "link a.css"}'>< /div>
             *     < script src='b.js' data-files='{"b": "script [src]"}'>< /script>
             * < /body>
             *
             * // then, in js:
             * findInDocument('data-files');
             * // Should return {a: 'link a.css', b: 'script b.js'}.
             *
             * @return {!just.Define.files}
             */
            'findInDocument': function (attributeName, container) {

                var files = {};

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

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

                    Object.assign(this, files);

                }, files);

                return files;

            }

        });

        return Define;

    })();

    onDocumentReady(Define.init);

    set('Define', Define);

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

    set('Def', Def);


    set('version', '1.0.0-rc.17'); set('just', just);

    return just;

});