Making your Widget Dashboardable

This tutorial follows from Saving Modifications to
Station
.

We're going to modify MyFirstWidget to make it
dashboardable.

Outline

+- myFirstModule
   +- myFirstModule-ux
      +- src
      |  +- com
      |  |  +- companyname
      |  |     +- myFirstModule
      |  |        +- ux
      |  |           +- BMyFirstWidget.java
      |  +- rc
      |     +- templates
      |     |  +- MyFirstWidget.hbs ----------------------------------*MODIFIED*
      |     +- myFirstModule.css
      |     +- MyFirstWidget.js --------------------------------------*MODIFIED*
      +- module.lexicon ----------------------------------------------*MODIFIED*
      +- module-include.xml
      +- myFirstModule-ux.gradle

Drag and Drop

One of the most useful features of dashboards is to allow users to drag
components from the Nav tree and drop them onto a Widget, instantly loading
the new component so the user can interact with it without navigating away from
that page. On top of that, users can save the dashboard so that they always see
their preferred component in that View.

The added lines of code related to this functionality have been marked as *DRAG & DROP* in the modified MyFirstWidget.js file.

Dashboard-only Features

Sometimes a user may want to show a feature of their Widget only when it is
contained within a dashboard. We've added a private note box at the bottom of
the Widget to demonstrate this.

The added lines of code related to this functionality have been marked as
*DASHBOARD-ONLY NOTE* in the modified MyFirstWidget.js file.

MyFirstWidget.js - modified

/**
 * A module defining `MyFirstWidget`.
 *
 * @module nmodule/myFirstModule/rc/MyFirstWidget
 */
define(['baja!',
        'bajaux/dragdrop/dragDropUtils', //----------------------- *DRAG & DROP*
        'bajaux/mixin/subscriberMixIn',
        'bajaux/util/SaveCommand',
        'bajaux/Widget',
        'css!nmodule/myFirstModule/rc/myFirstModule',
        'hbs!nmodule/myFirstModule/rc/templates/MyFirstWidget',
        'jquery',
        'lex!myFirstModule',
        'Promise'], function (
        baja,
        dragDropUtils,
        subscriberMixIn,
        SaveCommand,
        Widget,
        css,
        template,
        $,
        lexs,
        Promise) {

  'use strict';

  var myFirstModuleLex = lexs[0],
      overrideOrdPropName = 'overrideOrd', //--------------------- *DRAG & DROP*
      privateNoteTextPropName = 'privateNote'; //--------- *DASHBOARD-ONLY NOTE*

  function getAmpDom(dom) {
    return dom.find('.my-first-widget-amplitude');
  }

  function getEnabledDom(dom) {
    return dom.find('.my-first-widget-enabled');
  }

  function getValueDom(dom) {
    return dom.find('.my-first-widget-value');
  }

  function getNoteDom(dom) { //--------------------------- *DASHBOARD-ONLY NOTE*
    return dom.find('.my-first-widget-note');
  }

  function getNoteTextDom(dom) { //----------------------- *DASHBOARD-ONLY NOTE*
    return dom.find('.my-first-widget-noteText');
  }

  /**
   * An editor for working with `kitControl:Ramp` instances.
   *
   * This editor allows a user to drag and drop a new Ramp component onto it to
   * be viewed or modified instantly. This `Widget` is also dashboardable, which
   * allows a user to save the dropped override Ramp as the default one loaded.
   * Dashboarding also provides a private note text box, which only that user
   * can see and modify.
   *
   * @class
   * @extends module:bajaux/Widget
   * @alias module:nmodule/myFirstModule/rc/MyFirstWidget
   */
  var MyFirstWidget = function () {
    var widget = this;

    Widget.apply(widget, arguments);

    // When the dashboard is saved, this property information will be saved away
    // to the user's station, and is accessible by that user alone. Also note
    // that this property is not attached to the `Widget's` value, but the
    // `Widget` itself!

    // This is where we will save the override ORD.
    widget.properties().add({ //---------------------------------- *DRAG & DROP*
      name: overrideOrdPropName,
      value: '',
      dashboard: true,

      // Use hidden and readonly to hide this property from the user in an
      // editor.
      hidden: true,
      readonly: true
    });

    // This is where we will save the contents of the private note unique to
    // each user.
    widget.properties().add({ //-------------------------- *DASHBOARD-ONLY NOTE*
      name: privateNoteTextPropName,
      value: '',
      dashboard: true,
      hidden: true,
      readonly: true
    });

    // This is a special optional property that can be added to see if a
    // `Widget` is currently running on a dashboard. You do not need it to make
    // your `Widget` dashboardable. This is how we will decide whether or not to
    // show the private note textbox.
    widget.properties().add({ //-------------------------- *DASHBOARD-ONLY NOTE*
      name: 'dashboard',
      value: false,
      hidden: true,
      readonly: true,
      trans: true
    });

    subscriberMixIn(widget);
    widget.getCommandGroup().add(new SaveCommand());
  };
  MyFirstWidget.prototype = Object.create(Widget.prototype);
  MyFirstWidget.prototype.constructor = MyFirstWidget;

  //////////////////////////////////////////////////////////////////////////////
  // Override ORD
  //////////////////////////////////////////////////////////////////////////////

  /**
   * Called when a new data value is dragged and dropped onto the `Widget`.
   * Resolves a promise once the drag and drop operation has completed.
   *
   * @param  widget - The `Widget` instance to update with the new value.
   * @param  dataTransfer - The data.
   * @returns {Promise}
   */
  function updateFromDrop(widget, dataTransfer) { //-------------- *DRAG & DROP*
    return dragDropUtils.fromClipboard(dataTransfer)
      .then(function (envelope) {
        if (envelope.getMimeType() === 'niagara/navnodes') {
          return envelope.toJson()
            .then(function (json) {

              // Although multiple items could have been dragged and dropped
              // on, we're just going to use the first item in the list.
              var obj = json && json[0],
                  ord = obj && obj.ord;

              if (ord) {
                if (obj.typeSpec === 'kitControl:Ramp') {
                  return setOverrideOrd(widget, ord);
                }
                else {
                  return Promise.reject(
                    new Error('Override component must be a `kitControl:Ramp`.')
                  );
                }
              }
            });
        }
      });
  }

  /**
   * Sets up a new override ORD and unsubscribes the old one.
   *
   * @param widget - The `Widget` instance.
   * @param newOverrideOrd - The new override ORD.
   * @returns {Promise}
   */
  function setOverrideOrd(widget, newOverrideOrd){ //------------- *DRAG & DROP*

    // Record any old ORD value so we can unsubscribe it.
    var oldOverrideOrd = widget.properties().getValue(overrideOrdPropName);

    // There is no need to manually mark the `Widget` as modified here. When the
    // widget is in a dashboard, a change to widget.properties() automatically
    // does this.
    widget.properties().setValue(overrideOrdPropName, newOverrideOrd);

    return Promise.all([
      resolveOverrideOrd(widget),
      oldOverrideOrd && baja.Ord.make(oldOverrideOrd).get()
        .then(function (comp) {
          if (comp) {
            return widget.getSubscriber().unsubscribe(comp);
          }
        })
    ]);
  }

  /**
   * Resolves any override ORD.
   *
   * @param widget - The `Widget` instance.
   * @returns {Promise}
   */
  function resolveOverrideOrd(widget) { //------------------------ *DRAG & DROP*
    var ord = widget.properties().getValue(overrideOrdPropName);

    if (ord) {
      return baja.Ord.make(ord).get({ subscriber: widget.getSubscriber() })
        .then(function(value) {
          widget.$overrideVal = value;
          updateDom(widget, value);
        });
    }
    else {
      delete widget.$overrideVal;
    }
  }

  /**
   * Updates the DOM to the values of the passed `kitControl:Ramp`.
   *
   * @param widget - The `Widget` instance.
   * @param ramp - The `Ramp` to update DOM values with.
   */
  function updateDom(widget, ramp) {
    var dom = widget.jq(),
        valueDom = getValueDom(dom),
        ampDom = getAmpDom(dom),
        enabledDom = getEnabledDom(dom);

    valueDom.val(ramp.getOutDisplay());
    if (!widget.isModified()) {
      if (!ampDom.is(':focus')) {
        ampDom.val(ramp.getAmplitudeDisplay());
      }
      enabledDom.prop('checked', ramp.getEnabled());
    }
  }

  //////////////////////////////////////////////////////////////////////////////
  // Widget
  //////////////////////////////////////////////////////////////////////////////

  /**
   * Initialize the `MyFirstWidget`.
   *
   * @param {jQuery} dom - The DOM element into which to load this `Widget`
   */
  MyFirstWidget.prototype.doInitialize = function (dom) {
    var widget = this;

    dom.html(template({
      value: myFirstModuleLex.get({
        key: 'MyFirstWidget.value',
        def: 'Value'
      }),
      changeValues: myFirstModuleLex.get({
        key: 'MyFirstWidget.changeValues',
        def: 'Changing these inputs modifies the Widget, not the ' +
        'component. You must Save to push the changes to the station.'
      }),
      amplitude: myFirstModuleLex.get({
        key: 'MyFirstWidget.amplitude',
        def: 'Amplitude'
      }),
      enabled: myFirstModuleLex.get({
        key: 'MyFirstWidget.enabled',
        def: 'Enabled'
      }),
      loading: myFirstModuleLex.get({
        key: 'MyFirstWidget.loading',
        def: 'Loading...'
      }),
      note: myFirstModuleLex.get({ //--------------------- *DASHBOARD-ONLY NOTE*
        key: 'MyFirstWidget.note',
        def: 'Private note:'
      }),
      placeholder: myFirstModuleLex.get({ //-------------- *DASHBOARD-ONLY NOTE*
        key: 'MyFirstWidget.placeholder',
        def: "Thanks to the dashboard, no other users will see " +
             "this text when looking at this View."
      })
    }));

    dom.on('input', '.my-first-widget-amplitude', function () {
      widget.setModified(true);
    });
    dom.on('change', '.my-first-widget-enabled', function () {
      widget.setModified(true);
    });

    // Copy private note text to `Widget` when modified. - *DASHBOARD-ONLY NOTE*
    dom.on('input', '.my-first-widget-noteText', function () {
      widget.properties()
        .setValue(privateNoteTextPropName, getNoteTextDom(dom).val());
    });

    // Hide private note if widget is not dashboarded. --- *DASHBOARD-ONLY NOTE*
    getNoteDom(dom).toggle(widget.properties().getValue('dashboard'));

    // Set up drag and drop. --------------------------------- *DRAG & DROP ORD*
    dom.on('dragover', function(e) {
      e.preventDefault();
    });
    dom.on('drop', function(e) {
      updateFromDrop(widget, e.originalEvent.dataTransfer);
      e.preventDefault();
      e.stopPropagation();
    });

    // Set up the override ORD if it exists. ----------------- *DRAG & DROP ORD*
    resolveOverrideOrd(widget);
  };

  /**
   * Load a `kitControl:Ramp`.
   *
   * @param {baja.Component} ramp - an instance of `kitControl:Ramp`
   */
  MyFirstWidget.prototype.doLoad = function (ramp) {
    var widget = this,
        dom = widget.jq();

    function update() {
      // Use override value if available. -------------------- *DRAG & DROP ORD*
      var rampToUpdate = widget.$overrideVal || ramp;
      updateDom(widget, rampToUpdate);
    }

    widget.getSubscriber().attach('changed', update);
    update();

    // Load private note text from `Widget` properties. -- *DASHBOARD-ONLY NOTE*
    getNoteTextDom(dom).text(
      widget.properties().getValue(privateNoteTextPropName)
    );
  };

  /**
   * Read a new object with the current state of `enabled` and `amplitude`
   * being extracted from the dom.
   *
   * @returns {Object}
   */
  MyFirstWidget.prototype.doRead = function () {
    var dom = this.jq();

    return {
      enabled: getEnabledDom(dom).is(':checked'),
      amplitude: parseFloat(getAmpDom(dom).val())
    };
  };

  /**
   * Save the user-entered changes to the loaded `kitControl:Ramp`.
   *
   * @returns {Promise}
   */
  MyFirstWidget.prototype.doSave = function (readValue) {
    var widget = this,
        dom = widget.jq(),

        // Use override value if available. ------------------ *DRAG & DROP ORD*
        ramp = widget.$overrideVal || widget.value(),

        batch = new baja.comm.Batch(),
        promises = [
          ramp.set({
            slot: 'enabled',
            value: readValue.enabled,
            batch: batch
          }),
          ramp.set({
            slot: 'amplitude',
            value: readValue.amplitude,
            batch: batch
          })
        ];

    batch.commit();
    return Promise.all(promises);
  };

  return MyFirstWidget;
});

MyFirstWidget.hbs - modified

<div class='my-first-widget'>
  <div>
    <label>{{value}}: <label>
    <input class='my-first-widget-value' type='text' value='{{loading}}' readonly='readonly'/>
  </div>
  <div>
    {{changeValues}}
  </div>
  <div>
    <label>{{amplitude}}: <label>
    <input class='my-first-widget-amplitude' type='text' value='{{loading}}' />
  </div>
  <div>
    <label>{{enabled}}: <label>
    <input class='my-first-widget-enabled' type='checkbox' value='Enabled' />
  </div>
  <div class='my-first-widget-note' >
    <div>
      {{note}}
    </div>
    <div>
      <textarea class='my-first-widget-noteText' rows='4' cols='50' placeholder='{{placeholder}}' />
    </div>
  </div>
</div>

module.lexicon - modified

#
# Lexicon for the my first module ux.
#

MyFirstWidget.value=Value
MyFirstWidget.changeValues=Changing these inputs modifies the widget, not the component. You must Save to push the changes to the station.
MyFirstWidget.amplitude=Amplitude
MyFirstWidget.enabled=Enabled
MyFirstWidget.loading=Loading...
MyFirstWidget.note=Private note:
MyFirstWidget.placeholder=Thanks to the dashboard, no other users will see this text when looking at this View.

Next

There's a lot more to learn about. To explore, we're going to switch from
creating a Niagara Module that embeds our JavaScript Widget to using some
JavaScript Playground Components. These are not something we would distribute to
customers for production, but offer another way to experiment with bajaux.

In the playground examples, you can learn about...

  • Using Commands.
  • Using Properties.
  • DOM event handling.
  • Hyperlinking.

  • Ensure you have the BoxService from the box palette added to your Station's
    Service Container.

  • Open the docDeveloper palette and add the BajauxExamples folder to the
    root of your Station.
  • Make sure you're logged on as user with admin read, write and invoke
    privileges.
  • Using Workbench or a browser, click on the first exercise to get started.