Have questions? Stuck? Please check our FAQ for some common questions and answers.

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 25 Next »

Overview

An application can create their own custom views and have them integrated into the ONOS web GUI. This tutorial walks you through the process of developing such a view. A fictitious company, "Meowster, Inc." is used throughout the examples. This tutorial assumes you have created a top level directory and changed into it:

$ mkdir meow
$ cd meow

 

Application Set Up

The quickest way to get started is to use the maven archetypes to create the application source directory structure and fill it with template code. You can get more details here, but to summarize:

(0) Create a working directory

$ mkdir custom
$ cd custom

 

(1) Create the main application

$ onos-create-app app org.meowster.app.custom meowster-custom

When asked for the version, accept the suggested default: 1.0-SNAPSHOT, and press enter.

When asked to confirm the properties configuration, press enter.

groupId: org.meowster.app.custom
artifactId: meowster-custom
version: 1.0-SNAPSHOT
package: org.meowster.app.custom

 

(2) Overlay the UI additional components

$ onos-create-app ui org.meowster.app.custom meowster-custom

When asked for the version, accept the suggested default: 1.0-SNAPSHOT, and press enter.

When asked to confirm the properties configuration, press enter.


(3) Modify the pom.xml file to mark the module as an ONOS app:

$ cd meowster-custom
$ vi pom.xml

(3a) Change the description:

<description>Meowster Sample ONOS Custom-View App</description>

(3b) In the <properties> section, change the app name and origin:

<onos.app.name>org.meowster.app.custom</onos.app.name>
<onos.app.origin>Meowster, Inc.</onos.app.origin>

Everything else in the pom.xml file should be good to go.

Import into IntelliJ, if you wish

You can import the application source into IntelliJ as a new project.

Start by selecting File --> New --> Project from Existing Sources...

Then navigate to the top level pom.xml file:

On the next screen, you might wish to checkmark the following two items:

  • Search for projects recursively
  • Import Maven projects automatically

...since they are not selected by default.

IntelliJ should find the 1.0-SNAPSHOT:

If presented with a choice of Java versions, select 1.8:

Finally, finish with the name of the project:

Once the project loads, you should finally see a directory structure like this:

Building and Installing the App

From the top level project directory (the one with the pom.xml file) build the project:

$ mvn clean install

Note that your application is bundled as an .oar (ONOS Application ARchive) file in the target directory, and installed into your local maven repository:

...
[INFO] Installing /Users/simonh/dev/meow/custom/meowster-custom/target/meowster-custom-1.0-SNAPSHOT.oar to 
       /Users/simonh/.m2/repository/org/meowster/app/custom/meowster-custom/1.0-SNAPSHOT/meowster-custom-1.0-SNAPSHOT.oar
...

 

Assuming that you have ONOS running on your local machine, you can install the app from the command line:

$ onos-app localhost install! target/meowster-custom-1.0-SNAPSHOT.oar

You should see some JSON output that looks something like this:

{"name":"org.meowster.app.custom","id":39,"version":"1.0.SNAPSHOT",
"description":"Meowster Sample ONOS Custom-View App",
"origin":"Meowster, Inc.","permissions":"[]",
"featuresRepo":"mvn:org.meowster.app.custom/meowster-custom/1.0-SNAPSHOT/xml/features",
"features":"[meowster-custom]","state":"ACTIVE"}

 

After refreshing the GUI in your web browser, the navigation menu should have an additional entry:

 

Clicking on this item should navigate to the injected sample custom view:

Clicking on the Fetch Data button with the mouse, or pressing the spacebar will update the view with new data.

Note that pressing slash (/) will display the "Quick Help" panel:

As you will see in the code below, we created a key-binding with the space bar (to fetch data); it is listed in the quick help panel.

Also of note, pressing the T key will toggle the "theme" between light and dark:

Custom Views in Brief

To create a custom view requires both client-side and server-side resources; the client-side consists of HTML, JavaScript, and CSS files, and the server-side consists of Java classes.

  • The HTML file defines the structure of the custom view, and indicates to Angular where directives (behaviors) need to be injected.
  • The JavaScript file creates the Angular controller for the view, and defines code that drives the view.
  • The CSS file defines custom styling.
  • The server-side Java code receives requests from the client, fetches (and formats) data, and sends the information back to the client.

 

Description of Template Files - Server Side

This section gives a brief introduction to the generated files. 

AppComponent

This is the base Application class and may be used for non-UI related functionality (not addressed in this tutorial). 

AppUiComponent

This the the base class for UI functionality. The salient features to note are introduced briefly below.

 

(1) Reference to the UiExtensionService:

@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected UiExtensionService uiExtensionService;

 

(2) List of application view descriptors, defining which category the view appears under in the GUI navigation pane (if not a hidden view), the internal identifier for the view, and the display text for the link:

 // List of application views
private final List<UiView> uiViews = ImmutableList.of(
        new UiView(UiView.Category.OTHER, "sampleCustom", "Sample Custom")
);

 

(3) Declaration of a UiMessageHandlerFactory to generate message handlers on demand. The example factory generates a single handler each time, AppUiMessageHandler, described below:

// Factory for UI message handlers
private final UiMessageHandlerFactory messageHandlerFactory =
        () -> ImmutableList.of(
                new AppUiMessageHandler()
        );

 

(4) Declaration of a UiExtension, configured with the previously declared UI view descriptors and message handler factory:

// Application UI extension
protected UiExtension extension =
        new UiExtension.Builder(getClass().getClassLoader(), uiViews)
                .messageHandlerFactory(messageHandlerFactory)
                .build();

 

(5) Activation and deactivation callbacks that register and unregister the UI extension at the appropriate times:

@Activate
protected void activate() {
    uiExtensionService.register(extension);
    log.info("Started");
}

@Deactivate
protected void deactivate() {
    uiExtensionService.unregister(extension);
    log.info("Stopped");
}

AppUiMessageHandler

This class extends UiMessageHandler to implement code to handle events from the (client-side) sample application view.

 

Salient features to note:

(1) implement createRequestHandlers() to provide request handler implementations for specific event types from our view: 

@Override
protected Collection<RequestHandler> createRequestHandlers() {
    return ImmutableSet.of(
            new SampleCustomDataRequestHandler()
    );
}

In this simple example, we only have to deal with a single event type, but this is where others would be declared if our view generated additional events.

 

(2) define SampleCustomDataRequestHandler class to handle "sampleCustomDataRequest" events from the client. 

private static final String SAMPLE_CUSTOM_DATA_REQ = "sampleCustomDataRequest";
...
private final class SampleCustomDataRequestHandler extends RequestHandler {
    private SampleCustomDataRequestHandler() {
        super(SAMPLE_CUSTOM_DATA_REQ);
    }
    ...
}

Note that this class extends RequestHandler, which defines an abstract process() method to be implemented by subclasses.

 

(3) implement process() method:

private static final String SAMPLE_CUSTOM_DATA_RESP = "sampleCustomDataResponse";

private static final String NUMBER = "number";
private static final String SQUARE = "square";
private static final String CUBE = "cube";
private static final String MESSAGE = "message";
private static final String MSG_FORMAT = "Next incrememt is %d units";

private long someNumber = 1;
private long someIncrement = 1;
...
 
@Override
public void process(long sid, ObjectNode payload) {
    someIncrement++;
    someNumber += someIncrement;
    log.debug("Computing data for {}...", someNumber);

    ObjectNode result = objectNode();
    result.put(NUMBER, someNumber);
    result.put(SQUARE, someNumber * someNumber);
    result.put(CUBE, someNumber * someNumber * someNumber);
    result.put(MESSAGE, String.format(MSG_FORMAT, someIncrement + 1));
    sendMessage(SAMPLE_CUSTOM_DATA_RESP, 0, result);
}

The process method is invoked every time a "sampleCustomDataRequest" event is received from the client. 

The sid parameter (sequence ID) is deprecated.

The payload parameter provides the payload of the event, but we are not using it in this sample code.

The method creates and populates an ObjectNode instance with data to be transformed into JSON and shipped off back to the client.

Description of Template Files - Client Side

Note that the directory naming convention must be observed for the files to be placed in the correct location when the archive is built. Since our view has the unique identifier "sampleCustom", its client source files should be placed under the directory ~/src/main/resources/app/view/sampleCustom.

~/src/main/resources/app/view/sampleCustom/
client filesclient files for UI viewsclient files for "sampleCustom" view

There are three files here:

  • sampleCustom.html
  • sampleCustom.js
  • sampleCustom.css

Note the convention to name these files using the identifier for the view; in this case "sampleCustom".

sampleCustom.html

This is an HTML snippet for the sample custom view, providing the view's structure. Note that this HTML markup is injected into the Web UI by Angular, to make the view "visible" when the user navigates to it.

Let's describe the different parts of the file in sections...

Outer Wrapper

<!-- partial HTML -->
<div id="ov-sample-custom">
    ...
</div>

The outer <div> element defines the contents of our custom "view". It should use a dash-delimited "id" of the internal identifier for the view, prefixed with "ov". So, in our example, "sampleCustom" becomes "ov-sample-custom". ("ov" stands for "ONOS view").

Button Panel

We have defined a <div> to create a "button panel" at the top of the view:

<div class="button-panel">
    <div class="my-button" ng-click="getData()">
        Fetch Data
    </div>
</div>

Note that we have used Angular's ng-click attribute in the button <div> to instruct Angular to invoke the getData() function defined on our "scope" (see later), whenever the user clicks the <div>.

Data Panel

We have defined a <div> to create a "data panel" below:

<div class="data-panel">
    <table>
        <tr>
            <td> Number </td>
            <td class="number"> {{data.number}} </td>
        </tr>
        <tr>
            <td> Square </td>
            <td class="number"> {{data.square}} </td>
        </tr>
        <tr>
            <td> Cube </td>
            <td class="number"> {{data.cube}} </td>
        </tr>
    </table>

    <p>
        A message from our sponsors:
    </p>
    <p>
        <span class="quote">{{data.message}} </span>
    </p>
</div>

Note the use of Angular's double-braces {{ }} for variable substitution. For example, the expression {{data.number}} is replaced by the current value of the number property of the data object stored on our "scope" (see later).

sampleCustom.js

This file defines the view controller, and provides callback functions to drive the view.

Again, we will examine the different parts of the code in sections:

Outer Wrapper

An anonymous function invocation is used to wrap the contents of the file, to provide private scope (keeping our variables and functions out of the global scope):

// js for sample app custom view
(function () {
    'use strict';
 
    ...
 
}()); 

Variables

Variables are declared to hold injected references (console logger, scope, services...), and configuration "constants":

// injected refs
var $log, $scope, wss, ks;

// constants
var dataReq = 'sampleCustomDataRequest',
    dataResp = 'sampleCustomDataResponse';

Still WIP, sorry....

 

sampleCustom.css

Glue Files

css.html

js.html

 

  • No labels