wb/table/model/Column.js

/**
 * @copyright 2015 Tridium, Inc. All Rights Reserved.
 * @author Logan Byam
 */

/**
 * @module nmodule/webEditors/rc/wb/table/model/Column
 */
define([ 'Promise',
        'nmodule/js/rc/tinyevents/tinyevents',
        'nmodule/webEditors/rc/mixin/DataMixin' ], function (
         Promise,
         tinyevents,
         DataMixin) {

  'use strict';

  function setFlag(col, flag, isSet) {
    var flags = col.$flags;

    col.setFlags(isSet ? flags | flag : flags & ~flag);
  }

  /**
   * API Status: **Development**
   *
   * Column for use in a `TableModel`.
   *
   * As of Niagara 4.8, arbitrary data can be stored on a Column instance. To
   * enable tables and table exporters to use a context object when formatting
   * table cells, add a `context` value.
   *
   * @class
   * @alias module:nmodule/webEditors/rc/wb/table/model/Column
   * @mixes tinyevents
   * @mixes module:nmodule/webEditors/rc/mixin/DataMixin
   * @param {String} [name] column name
   * @param {Object} [params]
   * @param {String} [params.displayName] column display name; `name` will be
   * used if omitted. This should not contain HTML as any HTML will be escaped.
   * @param {Number} [params.flags] column flags
   * @see module:nmodule/webEditors/rc/wb/table/model/TableModel
   * @example
   * <caption>Append context data for formatting use in tables and table
   * exporters.</caption>
   * const myBooleanColumn = new Column('myBoolean');
   * myBooleanColumn.data('context', { trueText: 'Yes!', falseText: 'No!' });
   */
  var Column = function Column(name, params) {
    params = params || {};
    this.$name = name ? String(name) : null;
    this.$displayName = params.displayName;
    this.$flags = params.flags || 0;
    this.$sortable = true;
    this.$hidable = true;
    this.$exportable = true;
    tinyevents(this);
    DataMixin(this);
  };

  /**
   * Available configuration flags for a Column.
   * @type {Object}
   */
  Column.flags = {
    /**
     * This column's value should be shown in an editor.
     */
    EDITABLE: 0x0001,

    /**
     * This column should not be shown by default, but only visible if
     * explicitly shown by the user.
     */
    UNSEEN: 0x0002,

    /**
     * This column is intended to be shown in an editor, but readonly.
     */
    READONLY: 0x0004
  };

  /**
   * Get the column name or `null` if none was given.
   *
   * @returns {String}
   */
  Column.prototype.getName = function () {
    return this.$name;
  };

  /**
   * Resolves a display name for this column.
   *
   * @returns {Promise|*} promise to be resolved when the element's display name
   * has been fully built. It's also acceptable for overrides of this function
   * to complete synchronously without returning a promise, so be sure to wrap
   * calls to this function in `Promise.resolve()` when appropriate.
   */
  Column.prototype.toDisplayName = function () {
    return Promise.resolve(this.$displayName || this.getName());
  };

  /**
   * Builds out the DOM element, typically a `td`, that represents the
   * intersection of this column with a particular row. Often this will simply
   * be a simple `toString()` or similar, but could also do more sophisticated
   * things like build widgets.
   *
   * @param {module:nmodule/webEditors/rc/wb/table/model/Row} row
   * @param {JQuery} dom
   * @returns {Promise|*} promise to be resolved when the element's contents
   * have been fully built. It's also acceptable for overrides of this function
   * to complete synchronously without returning a promise, so be sure to wrap
   * calls to this function in `Promise.resolve()` when appropriate.
   */
  Column.prototype.buildCell = function (row, dom) {
    const value = this.getValueFor(row);
    const context = this.$getContext(row);
    const str = (value === null || value === undefined || !context) ? String(value) : value.toString(context);
    return Promise.resolve(str)
      .then((text) => dom.text(text));
  };

  /**
   * @private
   * @param {module:nmodule/webEditors/rc/wb/table/model/Row} row the row we're building the cell for
   * @returns {*} context object for formatting the value in this row for display. Override for
   * specialized behavior. Return a falsy value to use synchronous, non-localized formatting.
   */
  Column.prototype.$getContext = function (row) {
    return this.data('context');
  };

  /**
   * Called when the table is destroying the DOM element built for a cell in this column. This
   * gives a `Column` implementation the chance to clean up any resources that might have been
   * created during the earlier call to `#buildCell`, perhaps destroying a widget in the cell,
   * for example. As with `#buildCell`, if this completes synchronously and doesn't return a
   * Promise, the caller must wrap this in a call to `Promise.resolve()`.
   *
   * @param {module:nmodule/webEditors/rc/wb/table/model/Row} row
   * @param {JQuery} dom
   * @returns {Promise|*}
   */
  Column.prototype.destroyCell = function (row, dom) {
    return Promise.resolve();
  };

  /**
   * Returns a URI for an icon representing this column. Returns `null` by
   * default; override as needed in subclasses.
   *
   * @returns {String} a URI for an icon to be shown for this column.
   */
  Column.prototype.getColumnIcon = function () {
    return null;
  };

  /**
   * Get the flags set on this column.
   *
   * @returns {Number}
   */
  Column.prototype.getFlags = function () {
    return this.$flags;
  };

  /**
   * Return true if the column has *all* of the given flags.
   *
   * @param {Number} flags flags to check for
   * @returns {Boolean}
   */
  Column.prototype.hasFlags = function (flags) {
    return (this.$flags & flags) === flags;
  };

  /**
   * Set the column's flags.
   *
   * @param {Number} flags
   * @throws {Error} if a non-Number given
   */
  Column.prototype.setFlags = function (flags) {
    if (typeof flags !== 'number') {
      throw new Error('number required');
    }
    this.$flags = flags;
    this.emit('flagsChanged', flags);
  };

  /**
   * Override point; each column should define its own method of retrieving a
   * value from the given table row.
   *
   * @abstract
   * @param {module:nmodule/webEditors/rc/wb/table/model/Row} row
   * @returns {*} the row value. Note that this is synchronous; if the row's
   * subject is not ready to provide a value, it should not be loaded into the
   * table.
   * @throws {Error} if not implemented
   */
  Column.prototype.getValueFor = function (row) {
    throw new Error('#getValueFor() not implemented');
  };

  /**
   * Return true if the column is editable.
   *
   * @returns {Boolean}
   */
  Column.prototype.isEditable = function () {
    return this.hasFlags(Column.flags.EDITABLE);
  };

  /**
   * Set or unset the column's `EDITABLE` flag. Emits a `flagsChanged` event.
   *
   * @param {boolean} editable
   */
  Column.prototype.setEditable = function (editable) {
    setFlag(this, Column.flags.EDITABLE, editable);
  };

  /**
   * Return true if the column is readonly.
   *
   * @returns {Boolean}
   */
  Column.prototype.isReadonly = function () {
    return this.hasFlags(Column.flags.READONLY);
  };

  /**
   * Set or unset the column's `READONLY` flag. Emits a `flagsChanged` event.
   *
   * @param {boolean} readonly
   */
  Column.prototype.setReadonly = function (readonly) {
    setFlag(this, Column.flags.READONLY, readonly);
  };

  /**
   * Return true if the column is unseen.
   *
   * @returns {Boolean}
   */
  Column.prototype.isUnseen = function () {
    return this.hasFlags(Column.flags.UNSEEN);
  };

  /**
   * Set or unset the column's `UNSEEN` flag. Emits a `flagsChanged` event.
   *
   * @param {boolean} unseen
   */
  Column.prototype.setUnseen = function (unseen) {
    setFlag(this, Column.flags.UNSEEN, unseen);
  };

  /**
   * Returns a boolean indicating whether the column should not be sortable via the table headings.
   * Defaults to true.
   *
   * @returns {Boolean}
   */
  Column.prototype.isSortable = function () {
    return this.$sortable;
  };

  /**
   * Set or unset whether the column should be allowed to be sorted by the table heading.
   * @param {boolean} sortable
   */
  Column.prototype.setSortable = function (sortable) {
    this.$sortable = !!sortable;
  };

  /**
   * Return true if the column should available in the table's show/hide context menu.
   * Defaults to true.
   *
   * @returns {Boolean}
   */
  Column.prototype.isHidable = function () {
    return this.$hidable;
  };

  /**
   * Set or unset whether the column should be allowed to be hidden or shown by the table's
   * show/hide context menu.
   *
   * @param {boolean} hidable
   */
  Column.prototype.setHidable = function (hidable) {
    this.$hidable = !!hidable;
  };

  /**
   * Return true if the column should show up in export operations, e.g. to CSV.
   * @returns {Boolean}
   * @since Niagara 4.8
   */
  Column.prototype.isExportable = function () {
    return this.$exportable;
  };

  /**
   * Set or unset whether the column should show up in export operations.
   *
   * @param {boolean} exportable
   * @since Niagara 4.8
   */
  Column.prototype.setExportable = function (exportable) {
    this.$exportable = !!exportable;
  };

  return Column;
});