Updated fn-spec to include classNames
Removed Classnames file and added code to fn.js
Fixed typo dimentions to dimensions
Moved Device/Link logic from Topo2D3 into the model
Model now calls onChange when any property is changed via the set Method

WIP - Added d3 force layout for devices and lines

Change-Id: I4d1afd3cd4cecf2f719e27f4be5d1e874bd9e342
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Device.js b/web/gui/src/main/webapp/app/view/topo2/topo2Device.js
index ae04111..88bf086 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Device.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Device.js
@@ -22,16 +22,37 @@
 (function () {
     'use strict';
 
-    var Collection, Model;
+    var Collection, Model, is, sus, ts, t2vs;
+
+    var remappedDeviceTypes = {
+        virtual: 'cord'
+    };
+
+    // configuration
+    var devIconDim = 36,
+        labelPad = 10,
+        hostRadius = 14,
+        badgeConfig = {
+            radius: 12,
+            yoff: 5,
+            gdelta: 10
+        },
+        halfDevIcon = devIconDim / 2,
+        devBadgeOff = { dx: -halfDevIcon, dy: -halfDevIcon },
+        hostBadgeOff = { dx: -hostRadius, dy: -hostRadius },
+        status = {
+            i: 'badgeInfo',
+            w: 'badgeWarn',
+            e: 'badgeError'
+        },
+        deviceLabelIndex = 0;
 
     function createDeviceCollection(data, region) {
 
         var DeviceCollection = Collection.extend({
             model: Model,
-            get: function () {},
             comparator: function(a, b) {
-
-                var order = region.layerOrder;
+                var order = region.get('layerOrder');
                 return order.indexOf(a.get('layer')) - order.indexOf(b.get('layer'));
             }
         });
@@ -49,14 +70,106 @@
         return deviceCollection;
     }
 
+    function mapDeviceTypeToGlyph(type) {
+        return remappedDeviceTypes[type] || type || 'unknown';
+    }
+
+    function deviceLabel(d) {
+        //TODO: Device Json is missing labels array
+        return "";
+        var labels = this.get('labels'),
+            idx = (deviceLabelIndex < labels.length) ? deviceLabelIndex : 0;
+        return labels[idx];
+    }
+
+    function trimLabel(label) {
+        return (label && label.trim()) || '';
+    }
+
+    function computeLabelWidth() {
+        var text = this.select('text'),
+        box = text.node().getBBox();
+        return box.width + labelPad * 2;
+    }
+
+    function iconBox(dim, labelWidth) {
+        return {
+            x: -dim / 2,
+            y: -dim / 2,
+            width: dim + labelWidth,
+            height: dim
+        }
+    }
+
+    function deviceGlyphColor(d) {
+
+        var o = this.node.online,
+            id = "127.0.0.1", // TODO: This should be from node.master
+            otag = o ? 'online' : 'offline';
+        return o ? sus.cat7().getColor(id, 0, ts.theme())
+                 : dColTheme[ts.theme()][otag];
+    }
+
+    function setDeviceColor() {
+        this.el.select('use')
+            .style('fill', this.deviceGlyphColor());
+    }
+
     angular.module('ovTopo2')
     .factory('Topo2DeviceService',
-        ['Topo2Collection', 'Topo2Model',
+        ['Topo2Collection', 'Topo2NodeModel', 'IconService', 'SvgUtilService',
+        'ThemeService', 'Topo2ViewService',
 
-            function (_Collection_, _Model_) {
+            function (_Collection_, _NodeModel_, _is_, _sus_, _ts_, classnames, _t2vs_) {
 
+                t2vs = _t2vs_;
+                is = _is_;
+                sus = _sus_;
+                ts = _ts_;
                 Collection = _Collection_;
-                Model = _Model_.extend({});
+
+                Model = _NodeModel_.extend({
+                    initialize: function () {
+                        this.set('weight', 0);
+                        this.constructor.__super__.initialize.apply(this, arguments);
+                    },
+                    nodeType: 'device',
+                    deviceLabel: deviceLabel,
+                    deviceGlyphColor: deviceGlyphColor,
+                    mapDeviceTypeToGlyph: mapDeviceTypeToGlyph,
+                    trimLabel: trimLabel,
+                    setDeviceColor: setDeviceColor,
+                    onEnter: function (el) {
+
+                        var node = d3.select(el),
+                            glyphId = mapDeviceTypeToGlyph(this.get('type')),
+                            label = trimLabel(this.deviceLabel()),
+                            rect, text, glyph, labelWidth;
+
+                        this.el = node;
+
+                        rect = node.append('rect');
+
+                        text = node.append('text').text(label)
+                            .attr('text-anchor', 'left')
+                            .attr('y', '0.3em')
+                            .attr('x', halfDevIcon + labelPad);
+
+                        glyph = is.addDeviceIcon(node, glyphId, devIconDim);
+
+                        labelWidth = label ? computeLabelWidth(node) : 0;
+
+                        rect.attr(iconBox(devIconDim, labelWidth));
+                        glyph.attr(iconBox(devIconDim, 0));
+
+                        node.attr('transform', sus.translate(-halfDevIcon, -halfDevIcon));
+                        this.render();
+                    },
+                    onExit: function () {},
+                    render: function () {
+                        this.setDeviceColor();
+                    }
+                });
 
                 return {
                     createDeviceCollection: createDeviceCollection