widgets/notifications.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
 */

/* jshint -W079 */

import { defineChildWidget } from '../child_widget.js';
import { addClass } from '../utils/dom.js';
import { Container } from './container.js';
import { Button } from './button.js';
import { Icon } from './icon.js';
import { defineRender } from '../renderer.js';

/**
 * Notifications is a {@link Container} displaying {@link Notification}
 *   popups.
 *
 * @class Notifications
 *
 * @extends Container
 *
 * @param {Object} [options={ }] - An object containing initial options.
 *
 * @property {String} [options.stack="end"] - Define the position a new {@link Notification}
 *   is appended to the container, either `end` or `start`.
 */

export class Notifications extends Container {
  static get _options() {
    return {
      stack: 'string',
    };
  }

  static get options() {
    return {
      stack: 'start',
    };
  }

  initialize(options) {
    super.initialize(options);
  }

  draw(O, element) {
    addClass(element, 'aux-notifications');

    super.draw(O, element);
  }

  notify(options) {
    /**
     * Create and show a new notification.
     *
     * @method Notifications#notify
     *
     * @param {Object} [options={ }] - An object containing initial options. - Options for the {@link Notification} to add
     *
     */
    let n;
    if (options instanceof Notification) n = options;
    else n = new Notification(options);
    this.addChild(n);
    if (this.options.stack === 'start')
      this.element.insertBefore(n.element, this.element.firstChild);
    else this.element.appendChild(n.element);
    n.show();
    return n;
  }

  /**
   * Remove a notification instantly.
   *
   * @method Notifications#removeNotification
   *
   * @param {Notification} [notification] - The Notification to remove.
   */
  removeNotification(n) {
    this.removeChild(n);
    return n;
  }

  destroy() {
    if (this.children) {
      this.children.map((widget) => {
        widget.element.remove();
      });
    }
    super.destroy();
  }
}

function closeClicked() {
  /**
   * Is fired when the user clicks on the close button.
   *
   * @event Notification#closeclicked
   */
  this.emit('closeclicked');
  this.parent.remove();
}

function afterHide() {
  Promise.resolve().then(() => {
    if (this.isDestructed()) return;
    this.destroyAndRemove();
  });
}

function timeout() {
  this._timeout = void 0;
  this.remove();
}

/**
 * Notification is a {@link Container} to be used in {@link Notifications}.
 *
 * @class Notification
 *
 * @extends Container
 *
 * @param {Object} [options={ }] - An object containing initial options.
 *
 * @property {Number} [options.timeout=5000] - Time in milliseconds
 *   after the notification disappears automatically.
 *   Set to 0 for permanent notification.
 * @property {String} [options.icon=false] - Show an icon. Set to
 *   <code>false</code> to hide it from the DOM.
 * @property {Boolean} [options.show_close=false] - Show a close button.
 */

export class Notification extends Container {
  static get _options() {
    return {
      timeout: 'number',
      icon: 'string',
      show_close: 'boolean',
    };
  }

  static get options() {
    return {
      timeout: 5000,
      icon: false,
      show_close: false,
      role: 'alert',
    };
  }

  static get renderers() {
    return [
      defineRender('content', function () {
        const { element, icon, close } = this;
        if (icon) element.insertBefore(icon.element, element.firstChild);
        if (close) element.insertBefore(close.element, element.firstChild);
      }),
    ];
  }

  initialize(options) {
    super.initialize(options);
    const O = this.options;
    /**
     * @member {HTMLDivElement} Notification#element - The main DIV container.
     *   Has class <code>.aux-notification</code>.
     */
    this._timeout = void 0;
    this.set('timeout', O.timeout);
  }

  draw(O, element) {
    addClass(element, 'aux-notification');

    super.draw(O, element);
  }

  remove() {
    this.on('hide', afterHide);
    this.hide();
    /**
     * Is fired when the notification was removed from the DOM after the hiding animation.
     *
     * @event Notification#closed
     */
    this.emit('closed');
  }

  destroy() {
    if (this._timeout !== void 0) window.clearTimeout(this._timeout);
    super.destroy();
  }

  set(key, val) {
    super.set(key, val);
    if (key === 'timeout') {
      if (this._timeout !== void 0) window.clearTimeout(this._timeout);
      if (val > 0) this._timeout = window.setTimeout(timeout.bind(this), val);
    }
  }
}

/**
 * @member {Button} Notification#close - The Button for closing the notification.
 */
defineChildWidget(Notification, 'close', {
  create: Button,
  show: false,
  toggle_class: true,
  static_events: {
    click: closeClicked,
  },
  default_options: {
    icon: 'close',
    class: 'aux-close',
  },
});

/**
 * @member {Icon} Notification#icon - The Icon widget.
 */
defineChildWidget(Notification, 'icon', {
  create: Icon,
  show: false,
  toggle_class: true,
  option: 'icon',
  map_options: {
    icon: 'icon',
  },
});