widget_helpers.js

/*
 * This file is part of AUX.
 *
 * AUX is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * AUX is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General
 * Public License along with this program; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA  02110-1301  USA
 */

import { toggleClass, element } from './utils/dom.js';

export function addEvent(to, event, fun) {
  const tmp = to[event];

  if (!tmp) {
    to[event] = fun;
  } else if (Array.isArray(tmp)) {
    to[event] = tmp.concat([fun]);
  } else {
    to[event] = [tmp, fun];
  }
}

export function removeEvent(from, event, fun) {
  const tmp = from[event];
  if (!tmp) return;
  if (Array.isArray(tmp)) {
    from[event] = tmp.filter((f) => f !== fun);
    if (tmp.length === 1) from[event] = tmp[0];
    else if (tmp.length === 0) delete from[event];
  } else if (tmp === fun) {
    delete from[event];
  }
}

export function addStaticEvent(w, event, fun) {
  w.addStaticEvent(event, fun);
}

export function defineChildElement(widget, name, config) {
  /**
   * @function defineChildElement
   *
   * @description Creates a HTMLElement as a child for a widget. Is used to simplify
   * widget definitions. E.g. the tiny marker used to display the back-end
   * value is a simple DIV added using child element. The generic element
   * is a DIV added to Widget.element with the class
   * <code>.aux-[name]</code>. Default creating and adding can be
   * overwritten with custom callback functions.
   *
   * @param {Widget} widget - The {@link Widget} to add the child element to.
   * @param {string} name - The identifier of the element. It will be prefixed
   *     by an underscore <code>Widget["_" + config.name]</code>.
   * @param {object} config - The configuration of the child element.
   *
   * @param {boolean} [config.show=false] - Show/hide the child element on initialization.
   * @param {string} [config.option="show_"+config.name] - A custom option of the parent widget
   *     to determine the visibility of the child element. If this is
   *     <code>null</code>, <code>Widget.options["show_"+  config.name]</code>
   *     is used to toggle its visibility. The child element is visible, if
   *     this options is <code>!== false</code>.
   * @param {function} [config.display_check] - A function overriding the
   *     generic <code>show_option</code> behavior. If set, this function
   *     is called with the value of <code>show_option</code> as argument
   *     as soon as it gets set and is supposed to return a boolean
   *     defining the visibility of the element.
   * @param {function} [config.append] - A function overriding the generic
   *     append mechanism. If not <code>null</code>, this function is
   *     supposed to take care of adding the child element to the parent
   *     widget's DOM.
   * @param {function} [config.create] - A function overriding the generic
   *     creation mechanism. If not <code>null</code>, this function is
   *     supposed to create and return a DOM element to be added to the
   *     parent widget.
   * @param {boolean} [config.toggle_class=false] - Defines if the parent widget
   *     receives the class <code>.aux-has-[name]</code> as soon as
   *     the child element is shown.
   * @param {array} [config.draw_options] - A list of options of the parent
   *     widget which are supposed to trigger a check if the element has to
   *     be added or removed.
   * @param {function} [config.draw] - A function to be called on redraw.
   *
   */
  const show_option = config.option || 'show_' + name;
  const index = '_' + name;

  const display_check = config.display_check;

  /* This is done to make sure that the object property is created
   * inside of the constructor. Otherwise, if we add the widget later
   * might be turned into a generic mapping.
   */
  widget.addStaticEvent('initialize', function () {
    this[index] = null;
  });

  /* trigger child element creation after initialization */
  widget.addStaticEvent('initialize_children', function () {
    this.set(show_option, this.options[show_option]);
  });

  /* clean up on destroy */
  widget.addStaticEvent('destroy', function () {
    if (this[index]) {
      this[index].remove();
      this[index] = null;
    }
  });

  let append = config.append;
  let create = config.create;

  if (create === void 0)
    create = function () {
      return element('div', 'aux-' + name);
    };
  if (append === void 0)
    append = function () {
      this.element.appendChild(this[index]);
    };

  widget.addStaticEvent('set_' + show_option, function (value) {
    let C = this[index];
    const show = display_check ? display_check(value) : value !== false;
    if (show === !!C) return;
    if (show && !C) {
      C = create.call(this);
      this[index] = C;
      append.call(this, this.options);
    } else if (C && !show) {
      this[index] = null;
      C.remove();
    }
    if (config.toggle_class) toggleClass(this.element, 'aux-has-' + name, show);
    this.triggerResize();
  });

  if (config.draw) {
    let m = config.draw_options;

    if (!m) m = [show_option];
    else m.push(show_option);

    for (let i = 0; i < m.length; i++) {
      widget.addStaticEvent('set_' + m[i], function () {
        const value = this.options[show_option];
        const show = display_check ? display_check(value) : value !== false;

        if (show) this.drawOnce(config.draw);
      });
    }
  }

  if (!widget.hasOption(show_option)) {
    widget.defineOption(show_option, 'boolean', !!config.show);
  }
}