NodeBB/NodeBB

View on GitHub
public/src/modules/settings/array.js

Summary

Maintainability
A
45 mins
Test Coverage
'use strict';

define('settings/array', function () {
    let helper = null;

    /**
     Creates a new button that removes itself and the given elements on click.
     Calls {@link Settings.helper.destructElement} for each given field.
     @param elements The elements to remove on click.
     @returns JQuery The created remove-button.
     */
    function createRemoveButton(elements) {
        const rm = $(helper.createElement('button', {
            class: 'btn btn-sm btn-primary remove',
            title: 'Remove Item',
        }, '-'));
        rm.click(function (event) {
            event.preventDefault();
            elements.remove();
            rm.remove();
            elements.each(function (i, element) {
                element = $(element);
                if (element.is('[data-key]')) {
                    helper.destructElement(element);
                }
            });
        });
        return rm;
    }

    /**
     Creates a new child-element of given field with given data and calls given callback with elements to add.
     @param field Any wrapper that contains all fields of the array.
     @param key The key of the array.
     @param attributes The attributes to call {@link Settings.helper.createElementOfType} with or to add as
     element-attributes.
     @param value The value to call {@link Settings.helper.fillField} with.
     @param separator The separator to use.
     @param insertCb The callback to insert the elements.
     */
    function addArrayChildElement(field, key, attributes, value, separator, insertCb) {
        attributes = helper.deepClone(attributes);
        const type = attributes['data-type'] || attributes.type || 'text';
        const element = $(helper.createElementOfType(type, attributes.tagName, attributes));
        element.attr('data-parent', '_' + key);
        delete attributes['data-type'];
        delete attributes.tagName;
        for (const name in attributes) {
            if (attributes.hasOwnProperty(name)) {
                const val = attributes[name];
                if (name.search('data-') === 0) {
                    element.data(name.substring(5), val);
                } else if (name.search('prop-') === 0) {
                    element.prop(name.substring(5), val);
                } else {
                    element.attr(name, val);
                }
            }
        }
        helper.fillField(element, value);
        if ($('[data-parent="_' + key + '"]', field).length) {
            insertCb(separator);
        }
        insertCb(element);
        insertCb(createRemoveButton(element.add(separator)));
    }

    /**
     Adds a new button that adds a new child-element to given element on click.
     @param element The element to insert the button.
     @param key The key to forward to {@link addArrayChildElement}.
     @param attributes The attributes to forward to {@link addArrayChildElement}.
     @param separator The separator to forward to {@link addArrayChildElement}.
     */
    function addAddButton(element, key, attributes, separator) {
        const addSpace = $(document.createTextNode(' '));
        const newValue = element.data('new') || '';
        const add = $(helper.createElement('button', {
            class: 'btn btn-sm btn-primary add',
            title: 'Expand Array',
        }, '+'));
        add.click(function (event) {
            event.preventDefault();
            addArrayChildElement(element, key, attributes, newValue, separator.clone(), function (el) {
                addSpace.before(el);
            });
        });
        element.append(addSpace);
        element.append(add);
    }


    const SettingsArray = {
        types: ['array', 'div'],
        use: function () {
            helper = this.helper;
        },
        create: function (ignored, tagName) {
            return helper.createElement(tagName || 'div');
        },
        set: function (element, value) {
            let attributes = element.data('attributes');
            const key = element.data('key') || element.data('parent');
            let separator = element.data('split') || ', ';
            separator = (function () {
                try {
                    return $(separator);
                } catch (_error) {
                    return $(document.createTextNode(separator));
                }
            }());
            if (typeof attributes !== 'object') {
                attributes = {};
            }
            element.empty();
            if (!(value instanceof Array)) {
                value = [];
            }
            for (let i = 0; i < value.length; i += 1) {
                addArrayChildElement(element, key, attributes, value[i], separator.clone(), function (el) {
                    element.append(el);
                });
            }
            addAddButton(element, key, attributes, separator);
        },
        get: function (element, trim, empty) {
            const key = element.data('key') || element.data('parent');
            const children = $('[data-parent="_' + key + '"]', element);
            const values = [];
            children.each(function (i, child) {
                child = $(child);
                const val = helper.readValue(child);
                const empty = helper.isTrue(child.data('empty'));
                if (empty || (val !== undefined && (val == null || val.length !== 0))) {
                    return values.push(val);
                }
            });
            if (empty || values.length) {
                return values;
            }
        },
    };

    return SettingsArray;
});