Home Manual Reference Source

javascript/ievv_jsbase/http/QueryString.js

/**
 * Query-string creator and parser.
 *
 * @example <caption>Basics - build a querystring</caption>
 * const querystring = new QueryString();
 * querystring.set('name', 'Peter');
 * querystring.setIterable('tags', ['person', 'male']);
 * const encodedQuerystring = querystring.urlencode();
 * // encodedQuerystring === 'name=Peter&tags=person&tags=male'  // order may vary
 *
 * @example <caption>Parse a querystring</caption>
 * const querystring = new QueryString('name=Peter&tags=person&tags=male');
 * const name = querystring.get('name');
 * const tags = querystring.getArray('tags');
 * const firstTag = querystring.get('tags');
 *
 * @example <caption>Parse and modify a querystring</caption>
 * const querystring = new QueryString('name=Peter&tags=person&tags=male');
 * querystring.set('name', 'John');
 * querystring.append('tags', 'important');
 * // querystring.urlencode() === 'name=John&tags=person&tags=male&tags=important'
 * querystring.setIterable('tags', ['male']);
 * // querystring.urlencode() === 'name=John&tags=male'
 */
export default class QueryString {
    /**
     *
     * @param {string} querystring Optional input querystring to parse.
     */
    constructor(querystring) {
        this._queryStringMap = new Map();
        if(typeof querystring !== 'undefined') {
            if(typeof querystring !== 'string') {
                throw new TypeError('The querystring argument must be a string.')
            }
            this._parseQueryString(querystring);
        }
    }

    /**
     * Returns ``true`` if the querystring is empty, otherwise ``false``.
     *
     * @returns {boolean}
     */
    isEmpty() {
        return this._queryStringMap.size === 0;
    }

    /**
     * Remove all keys and values from the QueryString.
     */
    clear() {
        this._queryStringMap.clear();
    }

    _parseQueryStringItem(querystringItem) {
        const splitPair = querystringItem.split('=');
        const key = decodeURIComponent(splitPair[0]);
        const value = decodeURIComponent(splitPair[1]);
        this.append(key, value);
    }

    _parseQueryString(querystring) {
        const splitQueryString = querystring.split('&');
        for(const querystringItem of splitQueryString) {
            this._parseQueryStringItem(querystringItem);
        }
    }

    _addToKey(key, value) {
        if(typeof key !== 'string') {
            throw new TypeError('All keys added to a QueryString must be strings.')
        }
        if(typeof value !== 'string') {
            throw new TypeError('All values added to a QueryString must be strings.')
        }
        this._queryStringMap.get(key).push(value);
    }

    _setKeyToEmptyArray(key) {
        this._queryStringMap.set(key, []);
    }

    /**
     * Set value from an iterable.
     *
     * @param {string} key The key to set.
     * @param iterable Something that can be iterated with a
     *      ``for(const value of iterable)`` loop.
     *      All the values in the iterable must be strings.
     *      If the iterable is empty the key will be removed
     *      from the QueryString.
     *
     * @example
     * const querystring = QueryString();
     * querystring.setIterable('names', ['Peter', 'Jane']);
     */
    setIterable(key, iterable) {
        this._setKeyToEmptyArray(key);
        for(const value of iterable) {
            this._addToKey(key, value);
        }
        if(this._queryStringMap.get(key).length === 0) {
            this.remove(key);
        }
    }

    /**
     * Set a value.
     *
     * @param {string} key The key to store the value as.
     * @param {string} value The value to set.
     *
     * @example
     * const querystring = QueryString();
     * querystring.set('name', 'Peter');
     */
    set(key, value) {
        this.setIterable(key, [value]);
    }

    /**
     * Get a value.
     *
     * @param {string} key The key to get the value for.
     * @param {string} fallback An optional fallback value if the key is
     *      not in the QueryString. Defaults to ``undefined``.
     */
    get(key, fallback) {
        const value = this._queryStringMap.get(key);
        if(typeof value === 'undefined') {
            return fallback;
        } else {
            return value[0];
        }
    }

    /**
     * Append a value to a key.
     *
     * @param {string} key The key to append a value to.
     * @param {string} value The value to append.
     *
     * @example
     * const querystring = QueryString();
     * querystring.append('names', 'Jane');
     * querystring.append('names', 'Joe');
     * // querystring.urlencode() === 'names=Jane&names=Joe'
     */
    append(key, value) {
        if (!this._queryStringMap.has(key)) {
            this._setKeyToEmptyArray(key);
        }
        this._addToKey(key, value);
    }

    /**
     * Get the values for the specified key as an array.
     *
     * Always returns an array, even if the value was set
     * with {@link QueryString#set}.
     *
     * @param {string} key The key to get the values for.
     * @param {Array} fallback An optional fallback value if they
     *      key is not in the QueryString. Defaults to an empty array.
     * @returns {Array}
     */
    getArray(key, fallback) {
        if (this._queryStringMap.has(key)) {
            const valueArray = this._queryStringMap.get(key);
            return Array.from(valueArray);
        }
        if(typeof falback !== 'undefined') {
            return [];
        }
        return fallback;
    }

    /**
     * Remove the specified key from the QueryString.
     *
     * @param {string} key The key to remove.
     */
    remove(key) {
        this._queryStringMap.delete(key);
    }

    /**
     * Check if the QueryString contains the given key.
     *
     * @param {string} key The key to check for.
     * @returns {boolean}
     */
    has(key) {
        return this._queryStringMap.has(key);
    }

    _encodeKeyValue(key, value) {
        return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
    }

    /**
     * Get the QueryString object as a string in query-string format.
     *
     * @example
     * const querystring = QueryString();
     * querystring.set('next', '/a&b/');
     * querystring.set('name', 'john');
     * let urlEncodedQuerystring = querystring.urlencode();
     * // urlEncodedQuerystring === 'name=john&next=%2Fa%26b%2F'  // order may vary
     */
    urlencode() {
        let urlEncodedArray = [];
        for(let [key, valueArray] of this._queryStringMap) {
            for(const value of valueArray) {
                urlEncodedArray.push(this._encodeKeyValue(key, value));
            }
        }
        return urlEncodedArray.join('&');
    }
}