Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

A fictitious company, "Meowster, Inc." is used throughout the examples. This tutorial builds on top of the Custom View tutorial (, although you could create a tabular view for your app without the need for the custom view too)choose to only have a table view in your app, if you wished.

Let's get started...

Adding a Tabular View to our App

...

Code Block
$ cd ~/meow/sample

 

Now we can overlay the sample Let's add the table view template files by using the overlaying the uitab archetype: 

Code Block
$ onos-create-app uitab org.meowster.app.sample meowster-sample

...

Note that we already updated the pom.xml file in the previous tutorial, it should be good to go as is. 

New

...

Files in the

...

Project Structure

You should see a number of new files added to the project:

...

Note

If you still have the app installed from the custom view tutorial, you can "reinstall" instead:

 

  $ onos-app localhost reinstall! target/meowster-sample-1.0-SNAPSHOT.oar

...

  • The HTML file defines the structure of the table view, and indicates to Angular where directives (behaviors) need to be injected.
  • The JavaScript file creates the Angular controller for the view, delegates to the TableBuilderService to build the table, and defines a directive for populating the details panel.
  • The CSS file defines custom styling, if required.
  • The server-side Java code receives :
    • registers the view with the GUI framework
    •  receives requests from the client, fetches the data, formats, sorts and sends
    back
    • the information back to the client.

 

Description of Template Files - Server Side

These files are under the directory ~/src/main/java/org/meowster/appThis section describes the additional Java files generated by the uitab archetype.

Note

The exact path depends on the groupId (also used as the Java package) specified when the application was built with onos-create-app.

AppComponent

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

...

To be able to use the archetype overlay mechanism such that we can add the custom, table, and topology-overlay samples incrementally, we actually create three separate UiExtension instances and register them with the UiExtensionService individually. If your ONOS application was indeed creating multiple views, it should define a single UiExtension instance and declare each of the views and message handlers in one place. See UiExtensionManager.createCoreExtension() for an example of how to do this.

 

AppUiTableComponent

This is the base class for UI functionality. See the Custom View tutorial for a description.

...

Things to note:

 

(1) Reference to the UiExtensionService:

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

Provides access to the UI Extension Service, so that we can register our "view".

 

(2) List of application view descriptors, defining which categories the views appear under in the GUI navigation pane, the internal identifiers for the views, and the corresponding display text:

Code Block
languagejava
private static final String VIEW_ID = "sampleTable";
private static final String VIEW_TEXT = "Sample Table";
...
private final List<UiView> uiViews = ImmutableList.of(
        new UiView(UiView.Category.OTHER, VIEW_ID, VIEW_TEXT)
);

(Note that this extension in our application only contributes a single view; see note above.)

 

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

Code Block
languagejava
private final UiMessageHandlerFactory messageHandlerFactory =
        () -> ImmutableList.of(
                new AppUiTableMessageHandler()
        );

Generally, there should be one message handler for each contributed view.

 

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

Code Block
languagejava
protected UiExtension extension =
        new UiExtension.Builder(getClass().getClassLoader(), uiViews)
                .resourcePath(VIEW_ID)
                .messageHandlerFactory(messageHandlerFactory)
                .build();

Note that in this case, (as opposed to the Custom View sample code), we also declare a "resource path" (relative to the ~/src/main/resources directory) using the view ID as the subdirectory name. This tells the extension service that the glue files (see later) are located at ~/src/main/resources/sampleTable/*.html.

 

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

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

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

 

AppUiTableMessageHandler

This class extends UiMessageHandler to implement code that handles events from the (client-side) sample application table view. Salient features of note:

...

Code Block
languagejava
@Override
protected Collection<RequestHandler> createRequestHandlers() {
    return ImmutableSet.of(
            new SampleDataRequestHandlerSampleTableDataRequestHandler(),
            new SampleDetailRequestHandlerSampleTableDetailRequestHandler()
    );
}

 

(2) define SampleDataRequestHandler SampleTableDataRequestHandler class to handle "sampleDataRequestsampleTableDataRequest" events from the client. Note that this class extends TableRequestHandler, which implements most of the functionality required to support the table data model:

Code Block
languagejava
private static final String SAMPLE_TABLE_DATA_REQ = "sampleDataRequestsampleTableDataRequest";
private static final String SAMPLE_TABLE_DATA_RESP = "sampleDataResponsesampleTableDataResponse";
private static final String SAMPLESSAMPLE_TABLES = "samplessampleTables";

... 
 
private final class SampleDataRequestHandlerSampleTableDataRequestHandler extends TableRequestHandler {
    private SampleDataRequestHandlerSampleTableDataRequestHandler() {
        super(SAMPLE_TABLE_DATA_REQ, SAMPLE_TABLE_DATA_RESP, SAMPLESSAMPLE_TABLES);
    }
    ...
}

Note the call to the super-constructor, which takes three arguments:

...

  • For a given table view, the table is identified by a "tag" (in this example, that tag is "samplesampleTable")
    • request event identifier is derived as: <tag> + "DataRequest"

    • response event identifier is derived as: <tag> + "DataResponse"
    • "root" tag is derived as: <tag> + "s"

...

The sample table uses the default column identifier of "id", so can rely on the default implementation and does not need to override the this method.

 

(2b) define column identifiers:

...

Note that the column identifiers defined here must match the identifiers specified in the HTML snippet for the view (see samplesampleTable.html below). 

 

(2c) optionally override createTableModel() to specify custom cell formatters / comparators. 

...

Code Block
languagejava
@Override
protected void populateTable(TableModel tm, ObjectNode payload) {
    // ...
    List<Item> items = getItems();
    for (Item item: items) {
        populateRow(tm.addRow(), item);
    }
}
 
private void populateRow(TableModel.Row row, Item item) {
    row.cell(ID, item.id())
        .cell(LABEL, item.label())
        .cell(CODE, item.code());
}

Note the payload parameter; this allows contextual information to be passed in with the request, if desired.

...

Code Block
languagejava
collapsetrue
@Override
protected void populateTable(TableModel tm, ObjectNode payload) {
    DeviceService ds = get(DeviceService.class);
    MastershipService ms = get(MastershipService.class);
    for (Device dev : ds.getDevices()) {
        populateRow(tm.addRow(), dev, ds, ms);
    }
}

private void populateRow(TableModel.Row row, Device dev,
                         DeviceService ds, MastershipService ms) {
    DeviceId id = dev.id();
    String protocol = dev.annotations().value(PROTOCOL);

    row.cell(ID, id)
        .cell(MFR, dev.manufacturer())
        .cell(HW, dev.hwVersion())
        .cell(SW, dev.swVersion())
        .cell(PROTOCOL, protocol != null ? protocol : "")
        .cell(NUM_PORTS, ds.getPorts(id).size())
        .cell(MASTER_ID, ms.getMasterFor(id));
    }
}

 

(3) define SampleDetailRequestHandlerSampleTableDetailRequestHandler class to handle "sampleDetailRequestsampleTableDetailRequest" events from the client. Note that this class extends the base RequestHandler class:

Code Block
languagejava
private static final String SAMPLE_TABLE_DETAIL_REQ = "sampleDetailsRequestsampleTableDetailsRequest";

...
 
private final class SampleDetailRequestHandlerSampleTableDetailRequestHandler extends RequestHandler {

    private SampleDetailRequestHandlerSampleTableDetailRequestHandler() {
        super(SAMPLE_TABLE_DETAIL_REQ);
    }

    ...
}

 

(3a) implement process(...) to return detail information about the "selected" row:

Code Block
languagejava
private static final String SAMPLE_TABLE_DETAIL_RESP = "sampleDetailsResponsesampleTableDetailsResponse";
private static final String DETAILS = "details";
...
private static final String COMMENT = "comment";
private static final String RESULT = "result";

...
 
@Override
public void process(long sid, ObjectNode payload) {
    String id = string(payload, ID, "(none)");

    // SomeService ss = get(SomeService.class);
    // Item item = ss.getItemDetails(id)

    // fake data for demonstration purposes...
    Item item = getItem(id);

    ObjectNode rootNode = MAPPER.createObjectNodeobjectNode();
    ObjectNode data = MAPPER.createObjectNodeobjectNode();
    rootNode.set(DETAILS, data);

    if (item == null) {
        rootNode.put(RESULT, "Item with id '" + id + "' not found");
        log.warn("attempted to get item detail for id '{}'", id);

    } else {
        rootNode.put(RESULT, "Found item with id '" + id + "'");

        data.put(ID, item.id());
        data.put(LABEL, item.label());
        data.put(CODE, item.code());
        data.put(COMMENT, "Some arbitrary comment");
    }

    sendMessage(SAMPLE_TABLE_DETAIL_RESP, 0, rootNode);
}

...