Source: helper/generator.js

/**
 * A generator for standard elements
 * @namespace Generator
 */

/* global documentExportFormats */
/* global getTranslation */
/* global setInnerText */

var generator = {

    /**
     * Add a data-description attribute to an element
     * 
     * @param    {object}  element the object to update
     * @param    {string}  label   the label of the description
     * @param    {boolean} isDescription   if we want a description of the label which is predefined in i18n
     * @memberof Generator
     */
    addDescription: function (element, label, isDescription) {
        if ( isDescription ) { label += '.description'; }
        if (getTranslation(label, 'empty') != 'empty') {
            element.setAttribute('data-description', getTranslation(label));
        } else {
            element.removeAttribute('data-description');
        }
    },

    /**
     * Generate a label entry and return it.
     * 
     * @param    {string} label the string label
     * @returns  {object} the label element
     * @memberof Generator
     */
    getEntryLabel: function (label) {
        var entry = document.createElement('label');
        var element = document.createElement("span");
        element.addClassName( "label" );
        setInnerText( element, getTranslation(label) + ':');
        entry.appendChild(element);
        generator.addDescription(entry, label, true);

        return entry;
    },
    
    /**
     * Evaluate a value if it is a function and return the response.
     * If it is not a function the contenxt will be returned.
     * 
     * @param    {object} value function or object
     * @returns  {object} the result of the evaluation
     * @memberof Generator
     */
    evalValue: function (value) {
        return typeof value == 'function' ? value() : value;
    },

    /*******************************************************************************
     * A Combobox that has a dropdown
     *
     * @param  {object}  parentElement  the element where the submenu will be added
     * @param  {boolean} replaceElement if the original parentElement should be replaced
     ******************************************************************************/
    submenuContainer: function (parentElement, replaceParent) {
        
        /** @class  Generator~submenuContainer */        

        var self = this;
        this.parentElement = parentElement;
        this.replaceParent = replaceParent || false;
        this.submenu = null;
        this.menuContainer = null;
        this.items = [];
        this.offsetWidth = null;
        this.openMenuContainer = null;

        /**
         * Event to finish closing the submenu
         * 
         * @memberof Generator~submenuContainer
         */
        this.finishClose = function () {
            self.menuContainer.removeClassName('closeNow');
            self.submenu.removeEvent('mouseout', self.finishClose);
        };

        /**
         * Trigger closing the submenu
         * 
         * @memberof Generator~submenuContainer
         */
        this.triggerClose = function () {
            self.menuContainer.addClassName('closeNow');
            self.submenu.addEvent('mouseout', self.finishClose);
        };

        /**
         * Add an entry to the submenu
         * 
         * @param    {(object|string)} value      the entry to add
         * @param    {function}        action     function to execute when selected
         * @param    {boolean}         addInFront if the element shuld be added in front of all others
         * @param    {boolean}         clickable  if the element triggers a close of the container
         * @param    {boolean}         hoverable  if the element can be highlighted
         * @returns  {object}          the entry that has been created
         * @memberof Generator~submenuContainer
         */
        this.addEntry = function (value, action, addInFront, clickable, hoverable) {
            if (addInFront == null) {
                addInFront = false;
            }

            var item = document.createElement('li');
            item.object = value;

            if (hoverable) {
                item.addClassName("__over");
            }

            if (typeof value == 'string') {
                var textValue = value;
                value = document.createElement("span");
                setInnerText( value, textValue);
            }

            item.appendChild(value);

            if (typeof action == 'function') {
                item.addEvent('click', function (event) {

                    action.call(this, event);
                    if (clickable == null || clickable !== false) {
                        // Close popUp.
                        self.triggerClose();
                    }
                });
            }

            if (addInFront && this.items.length > 0) {
                this.menuContainer.insertBefore(item, this.items[0]);
                this.items.unshift(item);
            } else {
                this.menuContainer.appendChild(item);
                this.items.push(item);
            }

            return item;
        };

        /**
         * Remove an entry by its position
         * @param    {object|number} entryNr either the elment number or the object
         * @returns  {object|null}   the object if it has been removed
         * @memberof Generator~submenuContainer
         */
        this.removeEntry = function (entryNr) {
            if (isNaN(entryNr)) {
                entryNr = this.items.indexOf(entryNr);
            }

            if (this.items.length > entryNr && entryNr >= 0) {
                this.items[entryNr].parentNode.removeChild(this.items[entryNr]);
                var object = this.items[entryNr].object;
                this.items.splice(entryNr, 1);
                return object;
            }

            return null;
        };

        /**
         * Create a submenu selector. Requires a SubmenuContainer as entry point.
         * 
         * <p>The staticContent object has the following structure:
         *
         * <pre>
         * {
         *     type:         {documentExportFormats}   // type of the entry. Should be SUBMENU
         *     optionName:   {string|function},        // name of the option, function has to return string
         *     defaultValue: {string},                 // default value from `list`
         *     list:         {array},                  // array of objects
         *     action:       {function}                // the action to execute on click of an entry
         * }
         * </pre>
         *                               
         * the `list` contains objects in the following form:
         *                               
         * <pre>
         * {
         *     name:         {string},                 // the unique name
         *     value:        {string}                  // an optional custom value
         * }
         * </pre>
         * 
         * @param    {string} label         the label
         * @param    {object} staticContent the content object
         * @returns  {object} the Entry
         * @memberof Generator~submenuContainer
         */
        this.addSubmenuSelector = function (label, staticContent) {

            var entry = generator.getEntryLabel(label);
            var combobox = null;
            var action = function () {

                if (!entry.isEnabled()) {
                    return;
                }

                var isOpen = combobox.menuContainer.hasClassName('open');

                if (!isOpen && self.openMenuContainer != null) {
                    self.openMenuContainer.removeClassName('open');
                } else {
                    self.openMenuContainer = null;
                }

                combobox.menuContainer.addRemoveClass('open', !isOpen);
                self.menuContainer.addRemoveClass('subMenuOpen', !isOpen);
                self.openMenuContainer = combobox.menuContainer;
            };
            var item = self.addEntry(entry, action, null, false, ((staticContent && staticContent.list && staticContent.list.length) > 1 || !staticContent));

            combobox = new generator.submenuContainer(entry, true);
            combobox.submenu.style['float'] = 'none';
            combobox.submenu.addClassName('right __menuGroup');
            combobox.menuContainer.addClassName('__over');

            var currentValueHolder = document.createElement('span');
            entry.appendChild(currentValueHolder);
            entry.currentValueHolder = currentValueHolder;
            entry.submenu = combobox;

            if (staticContent && staticContent.list) {

                currentValueHolder.setAttribute("name", generator.evalValue(staticContent.optionName) || '');

                var setValue = function (value) {

                    if (typeof staticContent.action == 'function') {
                        staticContent.action(value, item);
                    }

                    // This should be saved later on
                    entry.currentValueHolder.fullValue = value;
                    $.jStorage.set(label, value);

                    if (typeof value == 'string') {
                        setInnerText(entry.currentValueHolder, value);
                        generator.addDescription(entry.currentValueHolder, value, true);
                    } else if (typeof value == 'object' && value.name) {
                        setInnerText(entry.currentValueHolder, getTranslation(value.name));
                        generator.addDescription(entry.currentValueHolder, value.name, true);
                        value = value.value; // Value to be send
                    }

                    // This is what will be send to server
                    entry.currentValueHolder.value = value;
                };

                setValue($.jStorage.get(label, staticContent.list[0]));
                if (staticContent.list.length > 1) {
                    staticContent.list.forEach(function (value) {
                        var item = combobox.addEntry(getTranslation(value.name) || getTranslation(value), function() {
                            setValue(value);
                        }, null, false);
                        generator.addDescription(item, value.name, true);
                    });
                } else {
                    entry.setEnabled(false);
                }
            }

            return entry;
        };

        /**
         * Create a submenu element with a label and an input field
         *                         
         * <p>The element has the following structure:
         *                         
         * <pre>
         * {
         *     type:         {documentExportFormats}   // type of the entry. Should be INPUT, PASSWORD or COLOR
         *     optionName:   {string|function},        // name of the option, function has to return string
         *     defaultValue: {string},                 // default value from `list`
         * }
         * </pre>
         * 
         * @param    {string} label   the label of the element
         * @param    {object} element the input field type
         * @returns  {object} the created entry
         * @memberof Generator~submenuContainer
         */
        this.addInputValueElement = function (label, element) {

            var entry = generator.getEntryLabel(label);
            entry.addClassName('right');

            var group = document.createElement('div');
            group.addClassName("__comboBox __menuDropDown");
            group.style['float'] = 'none';

            entry.appendChild(group);
            var item = self.addEntry(entry, null, null, false, false);

            var currentValueHolder = document.createElement('input');
            currentValueHolder.setAttribute("name", typeof element.optionName == 'function' ? element.optionName(item) : element.optionName || '');
            currentValueHolder.value = $.jStorage.get(label, element.defaultValue || '');
            currentValueHolder.style['float'] = 'none';
            group.appendChild(currentValueHolder);
            entry.currentValueHolder = currentValueHolder;
            try {
                currentValueHolder.type = element.type == documentExportFormats.COLOR ? 'color' : 'text';
            } catch (e) {
                // IE does not accept color. Really. What???
                currentValueHolder.type = 'text';
            }

            currentValueHolder.className = "__comboInput";

            currentValueHolder.addEvent("blur", function () {
                $.jStorage.set(label, currentValueHolder.value);
            });

            return entry;
        };

        /**
         * clears the whole list
         * 
         * @memberof Generator~submenuContainer
         */
        this.clear = function () {
            this.items = [];
            this.menuContainer.innerHTML = '';
        };

        /**
         * returns an entry by its number
         * 
         * @param    {number} entryNr the entry number
         * @returns  {object} the element
         * @memberof Generator~submenuContainer
         */
        this.getEntry = function (entryNr) {
            if (this.items.length > entryNr) {
                return this.items[entryNr];
            }

            return null;
        };

        /**
         * Finds the index of an entry
         * 
         * @param    {object} entry the object to look for
         * @returns  {number} the index
         * @memberof Generator~submenuContainer
         */
        this.getIndex = function (entry) {
            var i = -1;
            this.items.forEach(function (item, index) {
                if (item === entry) {
                    i = index;
                    return 'break';
                }
            });

            return i >= this.items.length ? -1 : i;
        };

        /**
         * the amount of entries in the list
         * 
         * @returns  {number} the amount of entries
         * @memberof Generator~submenuContainer
         */
        this.numberOfEntries = function () {
            return this.items.length;
        };

        /**
         * Open the submenu
         * 
         * @param    {boolean} visible true to show the subemnu
         * @memberof Generator~submenuContainer
         */
        this.show = function( visible ) {
            this.submenu.addRemoveClass('open', typeof visible == 'undefined' || visible);
        };

        /**
         * Create the submenu structure
         * 
         * @memberof Generator~submenuContainer
         * @inner
         */
        this.init = function () {

            this.submenu = document.createElement('div');
            // this.submenu.style.display = 'none'; // Hide at first. - class visible
            this.submenu.addClassName('__submenuContainer');
            this.submenu.addClassName('visible');

            this.offsetWidth = this.parentElement.offsetWidth;

            this.menuContainer = document.createElement('ul');
            this.menuContainer.addClassName('__submenu');
            this.submenu.appendChild(this.menuContainer);

            this.submenu.addClassName(this.parentElement.id);

			// this.submenu.setAttribute( 'data-left', this.parentElement.absoluteOffsetLeft() );
			// this.submenu.setAttribute( 'data-top', this.parentElement.absoluteOffsetTop() );

            if (this.replaceParent) {
                this.parentElement.parentNode.insertBefore(this.submenu, this.parentElement);
            }

            this.parentElement.parentNode.removeChild(this.parentElement);
            this.submenu.appendChild(this.parentElement);
        };

        this.init();
    },
    
    progressCircle: function( element ) {

        var self = this;

        this.radius = 35;
        this.margin = 5;
        this.element = element;

        this.svgElement = function( element ) {
            return document.createElementNS('http://www.w3.org/2000/svg', element);
        };

        this.progressContainer = this.svgElement('svg');
        this.incomplete = this.svgElement('circle');
        this.complete = this.svgElement('circle');

        this.init = function() {

            this.element && this.element.appendChild( this.progressContainer );
            this.progressContainer.appendChild( this.incomplete );
            this.progressContainer.appendChild( this.complete );

            this.progressContainer.setAttribute('class', 'progress');
            this.incomplete.setAttribute('class', 'incomplete');
            this.complete.setAttribute('class', 'complete');

            var boxSize = (this.radius + this.margin) * 2;
            this.progressContainer.setAttribute('viewBox', [0, 0, boxSize, boxSize ].join(' '));

            this.incomplete.setAttribute('cx', this.radius + this.margin);
            this.incomplete.setAttribute('cy', this.radius + this.margin);
            this.incomplete.setAttribute('r', this.radius);

            this.complete.setAttribute('cx', this.radius + this.margin);
            this.complete.setAttribute('cy', this.radius + this.margin);
            this.complete.setAttribute('r', this.radius);

            this.setProgress( 0 );
        };

        this.setProgress = function( progress ) {
            var circumference = 2 * Math.PI * this.radius;
            var dashOffset = circumference - ((progress * circumference) / 100);
            this.complete.setAttribute('style', 'stroke-dashoffset: ' + dashOffset);
        };

        this.dissolve = function() {
            window.setTimeout( function() {
                self.progressContainer && self.progressContainer.setAttribute('class', 'progress dissolve');
                window.setTimeout( function() {
                    self.element && self.element.parentElement.removeChild( self.element );
                    self = null;
                }, 1000);
            }, 2000);
        };

        this.init();
    }
};