UI-Ref : added example use of chained dialogs in topology overlay, illustrating how to get additional data from the server, and to allow user to make a sequence of selections.
Change-Id: Ice882b45fc9e378e134023fc40b81747a954d19f
diff --git a/uiref/src/main/java/org/onosproject/uiref/UiRefTopoOverlay.java b/uiref/src/main/java/org/onosproject/uiref/UiRefTopoOverlay.java
index 63b65ba..3553824 100644
--- a/uiref/src/main/java/org/onosproject/uiref/UiRefTopoOverlay.java
+++ b/uiref/src/main/java/org/onosproject/uiref/UiRefTopoOverlay.java
@@ -41,8 +41,8 @@
private static final String MY_VERSION = "Beta-1.0.0042";
private static final String MY_DEVICE_TITLE = "I changed the title";
- private static final ButtonId FOO_BUTTON = new ButtonId("foo");
- private static final ButtonId BAR_BUTTON = new ButtonId("bar");
+ private static final ButtonId SIMPLE_D_BUTTON = new ButtonId("simpleDialog");
+ private static final ButtonId CHAIN_D_BUTTON = new ButtonId("chainDialog");
public UiRefTopoOverlay() {
super(OVERLAY_ID);
@@ -51,6 +51,11 @@
@Override
public void modifySummary(PropertyPanel pp) {
+ // NOTE: if you don't want any of the original content you can
+ // use the following convenience methods:
+// pp.removeAllButtons();
+// pp.removeAllProps();
+
pp.title(MY_TITLE)
.typeId(TopoConstants.Glyphs.CROWN)
.removeProps(
@@ -68,8 +73,8 @@
pp.title(MY_DEVICE_TITLE);
pp.removeProps(LATITUDE, LONGITUDE);
- pp.addButton(FOO_BUTTON)
- .addButton(BAR_BUTTON);
+ pp.addButton(SIMPLE_D_BUTTON)
+ .addButton(CHAIN_D_BUTTON);
pp.removeButtons(TopoConstants.CoreButtons.SHOW_PORT_VIEW)
.removeButtons(TopoConstants.CoreButtons.SHOW_GROUP_VIEW);
diff --git a/uiref/src/main/java/org/onosproject/uiref/UiRefTopoOverlayMessageHandler.java b/uiref/src/main/java/org/onosproject/uiref/UiRefTopoOverlayMessageHandler.java
index 18fc4e2..2fe5669 100644
--- a/uiref/src/main/java/org/onosproject/uiref/UiRefTopoOverlayMessageHandler.java
+++ b/uiref/src/main/java/org/onosproject/uiref/UiRefTopoOverlayMessageHandler.java
@@ -16,6 +16,7 @@
package org.onosproject.uiref;
+import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
@@ -25,9 +26,11 @@
import org.onosproject.net.Element;
import org.onosproject.net.HostId;
import org.onosproject.net.Link;
+import org.onosproject.net.Port;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.host.HostService;
import org.onosproject.net.link.LinkService;
+import org.onosproject.ui.JsonUtils;
import org.onosproject.ui.RequestHandler;
import org.onosproject.ui.UiConnection;
import org.onosproject.ui.UiMessageHandler;
@@ -40,6 +43,7 @@
import java.util.Collection;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
@@ -52,10 +56,22 @@
private static final String UI_REF_TOPOV_DISPLAY_START = "uiRefTopovDisplayStart";
private static final String UI_REF_TOPOV_DISPLAY_UPDATE = "uiRefTopovDisplayUpdate";
private static final String UI_REF_TOPOV_DISPLAY_STOP = "uiRefTopovDisplayStop";
+ private static final String UI_REF_TOPOV_DEV_PORTS_REQ = "uiRefTopovDevicePortsReq";
+ private static final String UI_REF_TOPOV_DEV_PORTS_RESP = "uiRefTopovDevicePortsResp";
+ private static final String UI_REF_TOPOV_DEV_PORTS_OP = "uiRefTopovDevicePortFakeOp";
private static final String ID = "id";
+
+ private static final String PORTS = "ports";
+ private static final String SPEED = "speed";
+ private static final String TYPE = "type";
private static final String MODE = "mode";
+ private static final String DEVICE = "device";
+ private static final String PORT = "port";
+ private static final String FOO = "foo";
+ private static final String BAR = "bar";
+
private static final long UPDATE_PERIOD_MS = 1000;
private static final Link[] EMPTY_LINK_SET = new Link[0];
@@ -92,7 +108,9 @@
return ImmutableSet.of(
new DisplayStartHandler(),
new DisplayUpdateHandler(),
- new DisplayStopHandler()
+ new DisplayStopHandler(),
+ new DevicePortHandler(),
+ new PortFakeOpHandler()
);
}
@@ -100,7 +118,7 @@
// === Handler classes
private final class DisplayStartHandler extends RequestHandler {
- public DisplayStartHandler() {
+ DisplayStartHandler() {
super(UI_REF_TOPOV_DISPLAY_START);
}
@@ -135,7 +153,7 @@
}
private final class DisplayUpdateHandler extends RequestHandler {
- public DisplayUpdateHandler() {
+ DisplayUpdateHandler() {
super(UI_REF_TOPOV_DISPLAY_UPDATE);
}
@@ -152,7 +170,7 @@
}
private final class DisplayStopHandler extends RequestHandler {
- public DisplayStopHandler() {
+ DisplayStopHandler() {
super(UI_REF_TOPOV_DISPLAY_STOP);
}
@@ -165,8 +183,79 @@
}
}
+ private final class DevicePortHandler extends RequestHandler {
+ DevicePortHandler() {
+ super(UI_REF_TOPOV_DEV_PORTS_REQ);
+ }
+
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ String id = string(payload, ID);
+ log.debug("Request ports for device [{}]", id);
+ sendPortDataForDevice(id);
+ }
+ }
+
+ private final class PortFakeOpHandler extends RequestHandler {
+ PortFakeOpHandler() {
+ super(UI_REF_TOPOV_DEV_PORTS_OP);
+ }
+
+ @Override
+ public void process(long sid, ObjectNode payload) {
+ String device = string(payload, DEVICE);
+ String port = string(payload, PORT);
+
+// TODO: re-instate boolean results once onos-app-samples -> onos dep. fixed
+// boolean foo = bool(payload, FOO);
+// boolean bar = bool(payload, BAR);
+
+ // NOTE: we won't go any further with this example, but obviously
+ // we could initiate some action on the device and port
+ // selected by the user from the Topology view in the Web UI.
+ log.info("FAKE-op request device {} port {}", device, port);
+// log.info(" options FOO={}, BAR={}", foo, bar);
+ }
+ }
+
// === ------------
+ private void sendPortDataForDevice(String id) {
+ try {
+ DeviceId did = DeviceId.deviceId(id);
+ List<Port> ports = deviceService.getPorts(did);
+ log.debug("Sending port data for device {} (#ports: {})", did, ports.size());
+ ArrayNode portArray = arrayNode();
+ for (Port p : ports) {
+ // don't bother to send logical ports
+ if (p.number().isLogical()) {
+ continue;
+ }
+ // NOTE: we could just add the port numbers (as longs) to the
+ // array and have done, but let's demonstrate how we
+ // might build more complex record types...
+ portArray.add(portData(p));
+ }
+
+ ObjectNode payload = objectNode();
+ payload.set(PORTS, portArray);
+ payload.put(ID, id);
+ sendMessage(JsonUtils.envelope(UI_REF_TOPOV_DEV_PORTS_RESP, payload));
+
+ } catch (Exception e) {
+ log.warn("[port data] Unable to process ID [{}]", id);
+ }
+ }
+
+ private ObjectNode portData(Port p) {
+ ObjectNode node = objectNode();
+ node.put(ID, p.number().toLong());
+ node.put(SPEED, p.portSpeed());
+ node.put(TYPE, p.type().name());
+ return node;
+ }
+
+
private void clearState() {
currentMode = Mode.IDLE;
elementOfNote = null;
@@ -191,7 +280,7 @@
log.debug("device element {}", elementOfNote);
} catch (Exception e2) {
- log.debug("Unable to process ID [{}]", id);
+ log.warn("Unable to process ID [{}]", id);
elementOfNote = null;
}
}
diff --git a/uiref/src/main/resources/app/view/uiRefTopov/uiRefTopovDemo.js b/uiref/src/main/resources/app/view/uiRefTopov/uiRefTopovDemo.js
index 803ec59..839633f 100644
--- a/uiref/src/main/resources/app/view/uiRefTopov/uiRefTopovDemo.js
+++ b/uiref/src/main/resources/app/view/uiRefTopov/uiRefTopovDemo.js
@@ -10,14 +10,136 @@
var $log, fs, flash, wss, tss, tds;
// constants
- var displayStart = 'uiRefTopovDisplayStart',
- displayUpdate = 'uiRefTopovDisplayUpdate',
- displayStop = 'uiRefTopovDisplayStop';
+ var pfx = 'uiRefTopov',
+ displayStart = pfx + 'DisplayStart',
+ displayUpdate = pfx + 'DisplayUpdate',
+ displayStop = pfx + 'DisplayStop',
+ portsReq = pfx + 'DevicePortsReq',
+ portsResp = pfx + 'DevicePortsResp',
+ portsOp = pfx + 'DevicePortFakeOp';
// internal state
- var currentMode = null;
+ var currentMode = null,
+ ctx = { // chained dialog context
+ device: null,
+ port: -1,
+ foo: false,
+ bar: false
+ },
+ handlers = {};
+ // Handle device ports response from server:
+ // This will be invoked in response to a device selected and the
+ // "chain" button pressed on the details dialog, once the response
+ // comes back from the server.
+ // We are going to open a dialog and ask the user to select one
+ // of the ports for the device...
+ handlers[portsResp] = function (data) {
+ $log.debug('hey! Port Data from the server!', data);
+ ctx.device = data.id;
+
+ // invoked when the OK button is pressed on this dialog
+ function dOk() {
+ $log.debug('Dialog OK button pressed');
+ portOptionsDialog();
+ }
+
+ tds.openDialog()
+ .setTitle('Choose Port')
+ .addContent(createPortChoiceContent(data.ports))
+ .addCancel()
+ .addOkChained(dOk) // NOTE: we use the "chained" version here
+ .bindKeys();
+ };
+
+ function createPortChoiceContent(ports) {
+ var content = tds.createDiv('port-list'),
+ form,
+ portSelect;
+
+ content.append('p').text('Select port of device ' + ctx.device);
+ form = content.append('form');
+ form.append('span').text('port number: ');
+
+ // onchange function for selection widget
+ function selectPort() {
+ ctx.port = this.options[this.selectedIndex].value;
+ }
+
+ portSelect = form.append('select').on('change', selectPort);
+ ports.forEach(function (p) {
+ portSelect.append('option')
+ .attr('value', p.id)
+ .text(p.id);
+ });
+
+ ctx.port = -1; // clear state from any previous invocations
+
+ return content;
+ }
+
+
+ // the function that is called if OK is pressed on our ports dialog
+ function portOptionsDialog() {
+
+ // invoked when the OK button is pressed on this dialog
+ function dOk() {
+ $log.debug('Port Options Dialog OK button pressed');
+ $log.debug('Sending event', portsOp, ctx);
+ wss.sendEvent(portsOp, ctx);
+ }
+
+ tds.openDialog()
+ .setTitle('Select Port Options')
+ .addContent(createPortOptionsContent())
+ .addCancel()
+ .addOk(dOk) // NOTE: NOT the "chained" version!
+ .bindKeys();
+ }
+
+ function createPortOptionsContent() {
+ var content = tds.createDiv('port-opts'),
+ form;
+
+ // helper function to add a paragraph
+ function para(text) {
+ content.append('p').text(text);
+ }
+
+ para('Device ' + ctx.device);
+ para('Port ' + ctx.port);
+
+ form = content.append('form');
+
+ // helper function to add a checkbox to the form, which updates the
+ // context when the user toggles the checked state of the box.
+ function cbox(name, val) {
+
+ // onchange function for checkbox widget
+ function onchange() {
+ ctx[val] = this.checked;
+ }
+
+ form.append('input').attr({
+ type: 'checkbox',
+ name: name,
+ value: val
+ }).on('change', onchange);
+
+ ctx[val] = false; // clear state from any previous invocations
+
+ form.append('span').text(name);
+ form.append('br');
+ }
+
+ // add two checkboxes...
+ cbox('Foo', 'foo');
+ cbox('Bar', 'bar');
+
+ return content;
+ }
+
// === ---------------------------
// === Helper functions
@@ -40,6 +162,7 @@
function createDialogContent(devs) {
var content = tds.createDiv('my-content-class'),
items;
+
content.append('p').text('Do something to these devices?');
items = content.append('div');
devs.forEach(function (d) {
@@ -48,25 +171,25 @@
return content;
}
- function dCancel() {
- $log.debug('Dialog CANCEL button pressed');
- }
- function dOk() {
- $log.debug('Dialog OK button pressed');
- }
-
- function createListContent() {
- var content = tds.createDiv('my-list-class'),
- items;
- // TODO: figure out best way to inject selectable list
- content.append('p').text('(Selectable list to show here...)');
+ function createCustomContent() {
+ var content = tds.createDiv('my-div-class');
+ content.append('p').text('(Some content goes here...)');
return content;
}
// === ---------------------------
// === Main API functions
+ function overlayActive(active) {
+ if (active) {
+ wss.bindHandlers(handlers);
+ } else {
+ stopDisplay();
+ wss.unbindHandlers(handlers);
+ }
+ }
+
function startDisplay(mode) {
if (currentMode === mode) {
$log.debug('(in mode', mode, 'already)');
@@ -93,31 +216,59 @@
return false;
}
- // this example dialog invoked from the details panel, when one or more
- // devices have been selected
- function deviceDialog() {
- var ctx = tss.selectionContext();
+ // this example dialog is invoked from the details panel, when one or more
+ // devices have been selected, and the "banner" button is pressed.
+ function simpleDialog() {
+ var sctx = tss.selectionContext();
- $log.debug('device dialog invoked with context:', ctx);
+ $log.debug('SIMPLE: device dialog invoked with context:', sctx);
+
+ function dCancel() {
+ $log.debug('Dialog CANCEL button pressed');
+ }
+
+ function dOk() {
+ $log.debug('Dialog OK button pressed');
+ }
// only if at least one device was selected
- if (ctx.devices.length) {
+ if (sctx.devices.length) {
tds.openDialog()
.setTitle('Process Devices')
- .addContent(createDialogContent(ctx.devices))
+ .addContent(createDialogContent(sctx.devices))
.addCancel(dCancel) // 'esc' key bound to 'Cancel' button
.addOk(dOk) // 'enter' key bound to 'OK' button
.bindKeys();
}
}
+ // this example dialog is invoked from the details panel, when a single
+ // device has been selected and the "chain" button is pressed.
+ function chainedDialogs() {
+ var sctx = tss.selectionContext();
+
+ $log.debug('CHAINED: device dialog invoked with context:', sctx);
+
+ // only if exactly one device was selected...
+ if (sctx.devices.length === 1) {
+ // send a request for port information about the device to server
+ wss.sendEvent(portsReq, {
+ id: sctx.devices[0]
+ });
+ }
+ }
+
// this example dialog invoked from the toolbar
function listDialog() {
$log.debug('list dialog invoked');
+ function dOk() {
+ $log.debug('Dialog Gotcha button pressed');
+ }
+
tds.openDialog()
.setTitle('A list of stuff')
- .addContent(createListContent())
+ .addContent(createCustomContent())
.addOk(dOk, 'Gotcha') // custom text for "OK" button
.bindKeys();
}
@@ -127,24 +278,27 @@
angular.module('ovUiRefTopov', [])
.factory('UiRefTopovDemoService',
- ['$log', 'FnService', 'FlashService', 'WebSocketService',
- 'TopoSelectService', 'TopoDialogService',
+ ['$log', 'FnService', 'FlashService', 'WebSocketService',
+ 'TopoSelectService', 'TopoDialogService',
- function (_$log_, _fs_, _flash_, _wss_, _tss_, _tds_) {
- $log = _$log_;
- fs = _fs_;
- flash = _flash_;
- wss = _wss_;
- tss = _tss_;
- tds = _tds_;
+ function (_$log_, _fs_, _flash_, _wss_, _tss_, _tds_) {
+ $log = _$log_;
+ fs = _fs_;
+ flash = _flash_;
+ wss = _wss_;
+ tss = _tss_;
+ tds = _tds_;
- return {
- startDisplay: startDisplay,
- updateDisplay: updateDisplay,
- stopDisplay: stopDisplay,
+ return {
+ overlayActive: overlayActive,
- deviceDialog: deviceDialog,
- listDialog: listDialog
- };
- }]);
+ startDisplay: startDisplay,
+ updateDisplay: updateDisplay,
+ stopDisplay: stopDisplay,
+
+ chainedDialogs: chainedDialogs,
+ simpleDialog: simpleDialog,
+ listDialog: listDialog
+ };
+ }]);
}());
diff --git a/uiref/src/main/resources/app/view/uiRefTopov/uiRefTopovOverlay.js b/uiref/src/main/resources/app/view/uiRefTopov/uiRefTopovOverlay.js
index bf9e484..3a91674 100644
--- a/uiref/src/main/resources/app/view/uiRefTopov/uiRefTopovOverlay.js
+++ b/uiref/src/main/resources/app/view/uiRefTopov/uiRefTopovOverlay.js
@@ -53,31 +53,31 @@
activate: function () {
$log.debug("UI Ref topology overlay ACTIVATED");
+ demo.overlayActive(true);
},
deactivate: function () {
- demo.stopDisplay();
+ demo.overlayActive(false);
$log.debug("UI Ref topology overlay DEACTIVATED");
},
- // detail panel button definitions
+ // Detail panel button definitions
+ // NOTE: the callbacks needs to be wrapped in anonymous functions
+ // to defer the dereferencing of 'demo' to after injection
+ // of the business logic service API.
buttons: {
- foo: {
- gid: 'chain',
- tt: 'A FOO action',
- cb: function() {
- demo.deviceDialog();
- }
- },
- bar: {
+ simpleDialog: {
gid: '*banner',
- tt: 'A BAR action',
- cb: function (data) {
- $log.debug('BAR action invoked with data:', data);
- }
+ tt: 'Simple dialog example',
+ cb: function() { demo.simpleDialog(); }
+ },
+ chainDialog: {
+ gid: 'chain',
+ tt: 'Chained dialogs example',
+ cb: function () { demo.chainedDialogs(); }
}
},
- // Key bindings for traffic overlay buttons
+ // Key bindings for topology overlay buttons
// NOTE: fully qual. button ID is derived from overlay-id and key-name
keyBindings: {
0: {
@@ -95,20 +95,20 @@
tt: 'Start Link Mode',
gid: 'chain'
},
- G: {
+ A: {
cb: function () { demo.listDialog(); },
- tt: 'Uses the G key',
+ tt: 'List Dialog',
gid: 'crown'
},
+ // defines the order in which the buttons appear on the toolbar
_keyOrder: [
- '0', 'V', 'F', 'G'
+ '0', 'V', 'F', 'A'
]
},
hooks: {
- // hook for handling escape key
- // Must return true to consume ESC, false otherwise.
+ // hook for handling escape key...
escape: function () {
// Must return true to consume ESC, false otherwise.
return demo.stopDisplay();
@@ -116,16 +116,24 @@
// hooks for when the selection changes...
empty: function () {
+ // selection changed to the empty set
selectionCallback('empty');
},
single: function (data) {
+ // selection changed to a single node
selectionCallback('single', data);
+ // NOTE: the detail buttons to show on the dialog are included
+ // in the detail data response from the server
},
multi: function (selectOrder) {
+ // selection changed to more than one node
selectionCallback('multi', selectOrder);
- tov.addDetailButton('foo');
- tov.addDetailButton('bar');
+ // NOTE: we have to manually add detail button(s) for a
+ // multi-selection
+ tov.addDetailButton('simpleDialog');
},
+
+ // hooks for mouse movement over nodes (devices/hosts)...
mouseover: function (m) {
// m has id, class, and type properties
$log.debug('mouseover:', m);
@@ -138,6 +146,8 @@
}
};
+ // just an example callback to log the selection to the console.
+ // usually you would do something more useful.
function selectionCallback(x, d) {
$log.debug('Selection callback', x, d);
}