Concepts

Injection

The user interface works with ‘injections’ to combine the js viewmodel, the html view and where to show the ui element for each widget on the page. The html is injected into a div in the page and bound through knockout with the correct viewmodel. This makes it possible to create separate widgets. The html templates are kept small. This improves the reusability, reduces complexity and coupling.

Page segments

It is possible to define a “page segment”, which injects a view and viewmodel into the page.

How it works

As an example will inject a simple message into the page

The definePageSegment function uses the following parameters:

  • selector where to inject the message
  • view which html file to use as template
  • viewModel the message viewmodel
  • visible(optional) a ko observable that defines when the message should be shown

First, we have our page. The page is defined with a <message/> tag where our message will be injected. This tag will be replaced with a div with the id “message” wherein the view will be injected.

<!-- page -->
...
<div class="row-fluid" style="margin: 10px">
  <div class="span12">
    <message/><!-- Message injected here -->
  </div>
</div>
...

Next, we define a viewModel and use the page-segment library to inject our view and viewModel into the page.

Note that the value selector corresponds to the name of the <message/> tag

// load page-component library
var Page = require("nsx/injectors/page-segment");
//...
var vm = {
  message: "Hello World!"
}

Page.definePageSegment({
  selector: "message",
  view: "demoComp/message",
  viewModel: vm
}, {
  visible: true
});

Note that the visible parameter is actually not required

We also define an html template file for our view:

<!-- demoComp/message.html -->
<div data-bind="text: message"></div>

This results into the following page:

<!-- page -->
...
<div class="row-fluid" style="margin: 10px">
  <div class="span12">
    <div id="message">
      <div data-bind="stopBindings">
        <div class="nsx-wrapper" data-bind="visible: isVisible, with: viewModel">
          <div data-bind="text: message">Hello World!</div>
        </div>
      </div>
    </div>
  </div>
</div>
...

Recursive Injection

The definePageSegment also returns an injector. This object can be used to recursively inject page-segments into the injected html. For example, every expanded page defines a page page-segment in which most content will be placed.

var page = Page.definePageSegment({
   selector: "page",
   view: template,
   viewModel: viewModel
});

The template will normally define a number of tags which can consequently be used to inject more widgets into.

<!-- classic.html -->
<div class="span12">
    <div class="row-fluid">
        <errors/>
    </div>
    <div class="row-fluid" style="margin-top: 10px">
        <main/>
    </div>
    <div class="nsx-filler-row"></div>
    <div class="row-fluid">
        <widgets/>
    </div>
</div>

The <errors/> tag is used to show error messages. The <main/> tag is used for the main element of the page, like a table. The <widgets/> tag can be used to inject custom widgets.

As an example, let us create a message that shows up only when a row is selected.

// anchor:custom-page-after:start
/* use the selection exported by the table */
var selection = table.selection;
var message = "An item has been selected"

/* the page reference has methods to define a recursive page-segment */
page.definePageSegment({
  selector: "widgets",
  view: "demoComp/message",
  viewModel: {
    message: message
  }
}, {
  visible: isDataRefDefined(selection)
})
// anchor:custom-page-after:end

This example can implemented on any <element>-page-model.js, where <element> is the dataElement.

It is also possible to define a popup with the same view and viewmodel.

e.g. Show the same message in a popup

  • trigger trigger which triggers the popup
  • view which html file to use as template
  • viewModel the message viewmodel
  • title the title for the popup
// load popup-component library
var Popup = require("nsx/popup-component");
//...
var vm = {
  message: "Hello World!"
}

Popup.definePopup({
  trigger: someButton,
  view: "demoComp/message",
  viewModel: vm,
  title: "Greeting"
});
<!-- demoComp/message.html -->
<div data-bind="text: message"></div>

Page Layout

The page layout, how the different elements of the page are positioned to one another, is defined by the page template. The page template is a html file with a number of tags that will be used to inject widgets into.

The default page is defined so that all components are displayed under each other:

<!-- page-default.html -->
<div class="span12">
    <div class="row-fluid">
        <errors/>
    </div>
    <div class="row-fluid" style="margin-top: 10px">
        <main/>
    </div>
    <div class="nsx-filler-row"></div>
    <div class="row-fluid">
        <widgets/>
    </div>
</div>

classic layout

It is possible to define a different template in order to place the table and widgets next each other:

<!-- side-by-side.html -->
<div class="span12">
    <div class="row-fluid">
        <errors/>
    </div>
    <div class="row-fluid">
        <div class="span6">
            <main/>
        </div>
        <div class="span6">
            <widgets/>
        </div>
    </div>
</div>

side-by-side layout

Or add a new position for e.g. a list with shortcuts;

<!-- page-with-shortcuts.html -->
<div class="span1">
  <div class="row-fluid">
      <shortcuts/>
  </div>
</div>
<div class="span11">
    <div class="row-fluid">
        <errors/>
    </div>
    <div class="row-fluid" style="margin-top: 10px">
        <main/>
    </div>
    <div class="nsx-filler-row"></div>
    <div class="row-fluid">
        <widgets/>
    </div>
</div>

layout with shortcuts

Triggers

Triggers provide mechanisms to push updates, events etc. between widgets. A click on a button or a state change can be represented by triggers.

Triggers can be subscribed to with a function. When the trigger is triggered, all subscribed functions will be called. Most triggers call the subscribed functions without any arguments, but it is possible to create a selection-trigger that passes the current value of some variable when triggered.

Simple trigger

Used for buttons and simple events. Exports 2 functions:

  • subscribe register a function that should be called when the trigger is triggered
  • trigger trigger all functions subscribed to the trigger
// load trigger-util library, this combines all different available triggers
var Trigger = require('nsx/triggers');
//...
var trigger = Trigger.defineTrigger();

function doStuff() {/*...*/}
trigger.subscribe(doStuff);
//...
trigger.trigger(); // doStuff function is called

Selection trigger

Passes the current value of an observable to the triggered functions. This is useful for example when you need to know what item is selected when a button is pressed.

var Trigger = require('nsx/triggers');
//...
var selection = ko.observable();
var trigger = Trigger.defineSelectionTrigger({
  source: selection // trigger listens to the selection observable
});

function doStuff(item) {/*...*/}
trigger.subscribe(doStuff);
//...
selection("item 1"); // value of selection is set
//...
trigger.trigger(); // trigger is triggered to 'confirm' the selection
// doStuff function is now called with "item 1" as parameter

Event trigger

Used to link triggers in a loosely coupled way. When defining a trigger, a key is passed. Triggering an event-trigger will trigger all functions subscribed to event-triggers with the same key.

In this way, other widgets can be notified of certain changes without needing a hard link.

var Trigger = require('nsx/triggers');
//...
var trigger = Trigger.defineEventTrigger({
  key: "DEMO_EVENT"
});
//...
trigger.trigger();
// Other file
var Trigger = require('nsx/triggers');
//...
var trigger = Trigger.defineEventTrigger({
  key: "DEMO_EVENT"
});
trigger.subscribe(doStuff);

Bundling triggers

Triggers can be combined so that an action can be performed when 1 of the triggers activates.

var Trigger = require('nsx/triggers');
//...
var trigger = Trigger.defineBundledTrigger();
trigger.addTrigger(simpleTrigger);
trigger.addTrigger(eventTrigger);
trigger.subscribe(doStuff);

Syntax helper

A useful ‘when’ function has been added to make it easier to use triggers.

Some examples:

var Trigger = require('nsx/triggers');
//...
// Perform a function
Trigger.when(simpleTrigger)
  .thenDo(function() { /*...*/ })

// Trigger another trigger
Trigger.when(simpleTrigger)
  .thenTrigger(eventTrigger)

// Update an observable
Trigger.when(selectionTrigger)
  .thenUpdate(observable)

// Combine triggers
Trigger.when(simpleTrigger)
  .or(eventTrigger)
  .thenDo(function() { /*...*/ })

2.4 Widgets

The view is split into widgets. Each widget is contained a different file and follows the same principle.

input, options -> widget -> output

function defineList(input, options) {
  options = options || {}; // options can be left out
  //...
  return {/*...*/}
}
  • input required arguments
  • options extra arguments

Work with Observables

Each widget is defined once, which should not be redefined when the input is updated. Observables make it possible to update the widget if the input is changed.

function defineWidget(input, options) {
  var selection = input.selection; //ko observable

  function update(selected) {
    //...
  }

  selection.subscribe(update);
  //...
}

Don’t enter callbacks as input, use triggers!

function defineAction(input, options) {
  var success = Trigger.makeSimpleTrigger();
  //...
    success.trigger();
  //...
  return {
    success: success
  }
}
var action = Action.defineAction({/*...*/});
action.success.subscribe(function () {/*...*/});