Updated GUI Archetype to implement a simple table view.

Change-Id: I1e4faa457d7407f81b1926949a09e8c4570399e2
diff --git a/tools/package/archetypes/ui/src/main/resources/archetype-resources/src/main/java/AppUiComponent.java b/tools/package/archetypes/ui/src/main/resources/archetype-resources/src/main/java/AppUiComponent.java
index 3571c5f..1d241091 100644
--- a/tools/package/archetypes/ui/src/main/resources/archetype-resources/src/main/java/AppUiComponent.java
+++ b/tools/package/archetypes/ui/src/main/resources/archetype-resources/src/main/java/AppUiComponent.java
@@ -2,7 +2,7 @@
 #set( $symbol_dollar = '$' )
 #set( $symbol_escape = '\' )
 /*
- * Copyright 2014 Open Networking Laboratory
+ * Copyright 2014,2015 Open Networking Laboratory
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -18,25 +18,19 @@
  */
 package ${package};
 
-import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Deactivate;
-import org.apache.felix.scr.annotations.Service;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
-import org.onosproject.ui.RequestHandler;
 import org.onosproject.ui.UiExtension;
 import org.onosproject.ui.UiExtensionService;
-import org.onosproject.ui.UiMessageHandler;
 import org.onosproject.ui.UiMessageHandlerFactory;
 import org.onosproject.ui.UiView;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.Collection;
 import java.util.List;
 
 /**
@@ -62,8 +56,9 @@
             );
 
     // Application UI extension
-    protected UiExtension extension = new UiExtension(uiViews, messageHandlerFactory,
-                                                      getClass().getClassLoader());
+    protected UiExtension extension =
+            new UiExtension(uiViews, messageHandlerFactory,
+                            getClass().getClassLoader());
 
     @Activate
     protected void activate() {
@@ -77,24 +72,4 @@
         log.info("Stopped");
     }
 
-    // Application UI message handler
-    private class AppUiMessageHandler extends UiMessageHandler {
-
-        @Override
-        protected Collection<RequestHandler> createRequestHandlers() {
-            return ImmutableSet.of(new SampleRequest());
-        }
-
-        private class SampleRequest extends RequestHandler {
-            public SampleRequest() {
-                super("sampleRequest");
-            }
-
-            @Override
-            public void process(long sid, ObjectNode objectNode) {
-                log.info("We got a message: {}", objectNode);
-            }
-        }
-    }
-
 }
diff --git a/tools/package/archetypes/ui/src/main/resources/archetype-resources/src/main/java/AppUiMessageHandler.java b/tools/package/archetypes/ui/src/main/resources/archetype-resources/src/main/java/AppUiMessageHandler.java
new file mode 100644
index 0000000..d9d68b5
--- /dev/null
+++ b/tools/package/archetypes/ui/src/main/resources/archetype-resources/src/main/java/AppUiMessageHandler.java
@@ -0,0 +1,189 @@
+#set( $symbol_pound = '#' )
+#set( $symbol_dollar = '$' )
+#set( $symbol_escape = '\' )
+/*
+ * Copyright 2014,2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ${package};
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableSet;
+import org.onosproject.ui.RequestHandler;
+import org.onosproject.ui.UiMessageHandler;
+import org.onosproject.ui.table.TableModel;
+import org.onosproject.ui.table.TableRequestHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.Override;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Skeletal ONOS UI message handler.
+ * <p>
+ * This example specifically supporting a "table" view.
+ */
+public class AppUiMessageHandler extends UiMessageHandler {
+
+    private static final String SAMPLE_DATA_REQ = "sampleDataRequest";
+    private static final String SAMPLE_DATA_RESP = "sampleDataResponse";
+    private static final String SAMPLES = "samples";
+
+    private static final String SAMPLE_DETAIL_REQ = "sampleDetailsRequest";
+    private static final String SAMPLE_DETAIL_RESP = "sampleDetailsResponse";
+    private static final String DETAILS = "details";
+
+    private static final String ID = "id";
+    private static final String LABEL = "label";
+    private static final String CODE = "code";
+    private static final String COMMENT = "comment";
+    private static final String RESULT = "result";
+
+    private static final String[] COLUMN_IDS = { ID, LABEL, CODE };
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+
+    @Override
+    protected Collection<RequestHandler> createRequestHandlers() {
+        return ImmutableSet.of(
+                new SampleDataRequestHandler(),
+                new SampleDetailRequestHandler()
+        );
+    }
+
+    // handler for sample table requests
+    private final class SampleDataRequestHandler extends TableRequestHandler {
+
+        private SampleDataRequestHandler() {
+            super(SAMPLE_DATA_REQ, SAMPLE_DATA_RESP, SAMPLES);
+        }
+
+        // if necessary, override defaultColumnId() -- if it isn't "id"
+
+        @Override
+        protected String[] getColumnIds() {
+            return COLUMN_IDS;
+        }
+
+        @Override
+        protected void populateTable(TableModel tm, ObjectNode payload) {
+            // === set custom column cell formatters/comparators if need be...
+            // tm.setFormatter(CODE, new CodeFormatter());
+            // tm.setComparator(CODE, new CodeComparator());
+
+            // === retrieve table row items from some service...
+            // SomeService ss = get(SomeService.class);
+            // List<Item> items = ss.getItems()
+
+            // fake data for demonstration purposes...
+            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());
+        }
+    }
+
+
+    // handler for sample item details requests
+    private final class SampleDetailRequestHandler extends RequestHandler {
+
+        private SampleDetailRequestHandler() {
+            super(SAMPLE_DETAIL_REQ);
+        }
+
+        @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.createObjectNode();
+            ObjectNode data = MAPPER.createObjectNode();
+            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_DETAIL_RESP, 0, rootNode);
+        }
+    }
+
+
+    // ===================================================================
+    // NOTE: The code below this line is to create fake data for this
+    //       sample code. Normally you would use existing services to
+    //       provide real data.
+
+    // Lookup a single item.
+    private static Item getItem(String id) {
+        // We realize this code is really inefficient, but
+        // it suffices for our purposes of demonstration...
+        for (Item item : getItems()) {
+            if (item.id().equals(id)) {
+                return item;
+            }
+        }
+        return null;
+    }
+
+    // Produce a list of items.
+    private static List<Item> getItems() {
+        List<Item> items = new ArrayList<>();
+        items.add(new Item("item-1", "foo", 42));
+        items.add(new Item("item-2", "bar", 99));
+        items.add(new Item("item-3", "baz", 65));
+        return items;
+    }
+
+    // Simple model class to provide sample data
+    private static class Item {
+        private final String id;
+        private final String label;
+        private final int code;
+
+        Item(String id, String label, int code) {
+            this.id = id;
+            this.label = label;
+            this.code = code;
+        }
+
+        String id() { return id; }
+        String label() { return label; }
+        int code() { return code; }
+    }
+}
\ No newline at end of file
diff --git a/tools/package/archetypes/ui/src/main/resources/archetype-resources/src/main/resources/app/view/sample/sample.html b/tools/package/archetypes/ui/src/main/resources/archetype-resources/src/main/resources/app/view/sample/sample.html
index eec3de9..de67585 100644
--- a/tools/package/archetypes/ui/src/main/resources/archetype-resources/src/main/resources/app/view/sample/sample.html
+++ b/tools/package/archetypes/ui/src/main/resources/archetype-resources/src/main/resources/app/view/sample/sample.html
@@ -1,7 +1,46 @@
-
 <!-- partial HTML -->
 <div id="ov-sample">
-    <h2>Sample App View</h2>
+    <div class="tabular-header">
+        <h2>Items ({{tableData.length}} total)</h2>
+        <div class="ctrl-btns">
+            <div class="refresh" ng-class="{active: autoRefresh}"
+                 icon icon-id="refresh" icon-size="36"
+                 tooltip tt-msg="autoRefreshTip"
+                 ng-click="toggleRefresh()"></div>
+        </div>
+    </div>
 
-    <p> {{ctrl.msg}} </p>
-</div>
\ No newline at end of file
+    <div class="summary-list" onos-table-resize>
+
+        <div class="table-header" onos-sortable-header>
+            <table>
+                <tr>
+                    <td colId="id" sortable> Item ID </td>
+                    <td colId="label" sortable> Label </td>
+                    <td colId="code" sortable> Code </td>
+                </tr>
+            </table>
+        </div>
+
+        <div class="table-body">
+            <table>
+                <tr ng-if="!tableData.length" class="no-data">
+                    <td colspan="3">
+                        No Items found
+                    </td>
+                </tr>
+
+                <tr ng-repeat="item in tableData track by $index"
+                    ng-click="selectCallback($event, item)"
+                    ng-class="{selected: item.id === selId}">
+                    <td>{{item.id}}</td>
+                    <td>{{item.label}}</td>
+                    <td>{{item.code}}</td>
+                </tr>
+            </table>
+        </div>
+
+    </div>
+
+    <item-details-panel></item-details-panel>
+</div>
diff --git a/tools/package/archetypes/ui/src/main/resources/archetype-resources/src/main/resources/app/view/sample/sample.js b/tools/package/archetypes/ui/src/main/resources/archetype-resources/src/main/resources/app/view/sample/sample.js
index 313236e..37b8988 100644
--- a/tools/package/archetypes/ui/src/main/resources/archetype-resources/src/main/resources/app/view/sample/sample.js
+++ b/tools/package/archetypes/ui/src/main/resources/archetype-resources/src/main/resources/app/view/sample/sample.js
@@ -2,14 +2,70 @@
 (function () {
     'use strict';
 
+    // injected refs
+    var $log, $scope, fs, mast, ps, wss;
+
+    // internal state
+    var selRow,
+        detailsPanel,
+        pStartY, pHeight,
+        wSize;
+
+    // constants
+    var topPadding = 20,
+
+        detailsReq = 'sampleDetailsRequest',
+        detailsResp = 'sampleDetailsResponse',
+        pName = 'item-details-panel',
+
+        propOrder = [ 'id', 'label', 'code'],
+        friendlyProps = [ 'Item ID', 'Item Label', 'Special Code' ];
+
+
+    function respDetailsCb(data) {
+        $scope.panelData = data.details;
+        $scope.$apply();
+    }
+
     angular.module('ovSample', [])
         .controller('OvSampleCtrl',
-        ['$log', '$scope',
+        ['$log', '$scope', 'TableBuilderService',
+            'FnService', 'WebSocketService',
 
-            function ($log, $scope) {
-                var self = this;
+            function (_$log_, _$scope_, tbs, _fs_, _wss_) {
+                $log = _$log_;
+                $scope = _$scope_;
+                fs = _fs_;
+                wss = _wss_;
 
-                self.msg = 'A message from our app...';
+                var handlers = {};
+
+                $scope.panelData = [];
+
+                function selCb($event, row) {
+                    selRow = angular.element($event.currentTarget);
+                    if ($scope.selId) {
+                        wss.sendEvent(detailsReq, { id: row.id });
+                    } else {
+                        $log.debug('need to hide details panel');
+                        //detailsPanel.hide()
+                    }
+                    $log.debug('Got a click on:', row);
+                }
+
+                tbs.buildTable({
+                    scope: $scope,
+                    tag: 'sample',
+                    selCb: selCb
+                });
+
+                // details response handler
+                handlers[detailsResp] = respDetailsCb;
+                wss.bindHandlers(handlers);
+
+                $scope.$on('$destroy', function () {
+                    wss.unbindHandlerse(handlers);
+                });
 
                 $log.log('OvSampleCtrl has been created');
             }]);