GUI -- Continued porting topology behavior over to the new codebase. WIP.
- added FnService.windowSize() function.
- added MastService and mastHeight() function.
- implemented SvgUtilService.createDragBehavior().

Change-Id: I5dae35244ab8220e1b95ddfd55b180e6adcb7a00
diff --git a/web/gui/src/main/webapp/app/fw/mast/mast.js b/web/gui/src/main/webapp/app/fw/mast/mast.js
index 6b08b1c..bdb3e67 100644
--- a/web/gui/src/main/webapp/app/fw/mast/mast.js
+++ b/web/gui/src/main/webapp/app/fw/mast/mast.js
@@ -20,8 +20,12 @@
 (function () {
     'use strict';
 
+    // injected services
     var $log;
 
+    // configuration
+    var mastHeight = 36;
+
     angular.module('onosMast', ['onosNav'])
         .controller('MastCtrl', ['$log', 'NavService', function (_$log_, ns) {
             var self = this;
@@ -37,6 +41,13 @@
             };
 
             $log.log('MastCtrl has been created');
+        }])
+
+        // also define a service to allow lookup of mast height.
+        .factory('MastService', [function () {
+            return {
+                mastHeight: function () { return mastHeight; }
+            }
         }]);
 
 }());
diff --git a/web/gui/src/main/webapp/app/fw/svg/svgUtil.js b/web/gui/src/main/webapp/app/fw/svg/svgUtil.js
index ef6049c..a0e0fff 100644
--- a/web/gui/src/main/webapp/app/fw/svg/svgUtil.js
+++ b/web/gui/src/main/webapp/app/fw/svg/svgUtil.js
@@ -34,9 +34,99 @@
             $log = _$log_;
             fs = _fs_;
 
-            function createDragBehavior() {
-                $log.warn('SvgUtilService: createDragBehavior -- To Be Implemented');
-            }
+            function createDragBehavior(force, selectCb, atDragEnd,
+                                        dragEnabled, clickEnabled) {
+                var draggedThreshold = d3.scale.linear()
+                        .domain([0, 0.1])
+                        .range([5, 20])
+                        .clamp(true),
+                    drag,
+                    fSel = fs.isF(selectCb),
+                    fEnd = fs.isF(atDragEnd),
+                    fDEn = fs.isF(dragEnabled),
+                    fCEn = fs.isF(clickEnabled),
+                    bad = [];
+
+                function naf(what) {
+                    return 'SvgUtilService: createDragBehavior(): ' + what +
+                        ' is not a function';
+                }
+
+                if (!fSel) {
+                    bad.push(naf('selectCb'));
+                }
+                if (!fEnd) {
+                    bad.push(naf('atDragEnd'));
+                }
+                if (!fDEn) {
+                    bad.push(naf('dragEnabled'));
+                }
+                if (!fCEn) {
+                    bad.push(naf('clickEnabled'));
+                }
+
+                if (bad.length) {
+                    $log.error(bad.join('\n'));
+                    return null;
+                }
+
+
+                function dragged(d) {
+                    var threshold = draggedThreshold(force.alpha()),
+                        dx = d.oldX - d.px,
+                        dy = d.oldY - d.py;
+                    if (Math.abs(dx) >= threshold || Math.abs(dy) >= threshold) {
+                        d.dragged = true;
+                    }
+                    return d.dragged;
+                }
+
+                drag = d3.behavior.drag()
+                    .origin(function(d) { return d; })
+                    .on('dragstart', function(d) {
+                        if (clickEnabled() || dragEnabled()) {
+                            d3.event.sourceEvent.stopPropagation();
+
+                            d.oldX = d.x;
+                            d.oldY = d.y;
+                            d.dragged = false;
+                            d.fixed |= 2;
+                            d.dragStarted = true;
+                        }
+                    })
+                    .on('drag', function(d) {
+                        if (dragEnabled()) {
+                            d.px = d3.event.x;
+                            d.py = d3.event.y;
+                            if (dragged(d)) {
+                                if (!force.alpha()) {
+                                    force.alpha(.025);
+                                }
+                            }
+                        }
+                    })
+                    .on('dragend', function(d) {
+                        if (d.dragStarted) {
+                            d.dragStarted = false;
+                            if (!dragged(d)) {
+                                // consider this the same as a 'click'
+                                // (selection of a node)
+                                if (clickEnabled()) {
+                                    selectCb(d, this);
+                                    // TODO: set 'this' context instead of param
+                                }
+                            }
+                            d.fixed &= ~6;
+
+                            // hook at the end of a drag gesture
+                            if (dragEnabled()) {
+                                atDragEnd(d, this);
+                                // TODO: set 'this' context instead of param
+                            }
+                        }
+                    });
+
+                return drag;            }
 
             function loadGlow() {
                 $log.warn('SvgUtilService: loadGlow -- To Be Implemented');
diff --git a/web/gui/src/main/webapp/app/fw/util/fn.js b/web/gui/src/main/webapp/app/fw/util/fn.js
index 10cb6ff..932a7c6 100644
--- a/web/gui/src/main/webapp/app/fw/util/fn.js
+++ b/web/gui/src/main/webapp/app/fw/util/fn.js
@@ -20,6 +20,8 @@
 (function () {
     'use strict';
 
+    var $window;
+
     function isF(f) {
         return typeof f === 'function' ? f : null;
     }
@@ -58,15 +60,29 @@
         return true;
     }
 
+    // Returns width and height of window inner dimensions.
+    // offH, offW : offset width/height are subtracted, if present
+    function windowSize(offH, offW) {
+        var oh = offH || 0,
+            ow = offW || 0;
+        return {
+            height: $window.innerHeight - oh,
+            width: $window.innerWidth - ow
+        };
+    }
+
     angular.module('onosUtil')
-        .factory('FnService', [function () {
+        .factory('FnService', ['$window', function (_$window_) {
+            $window = _$window_;
+
             return {
                 isF: isF,
                 isA: isA,
                 isS: isS,
                 isO: isO,
                 contains: contains,
-                areFunctions: areFunctions
+                areFunctions: areFunctions,
+                windowSize: windowSize
             };
     }]);
 
diff --git a/web/gui/src/main/webapp/app/view/topo/topo.js b/web/gui/src/main/webapp/app/view/topo/topo.js
index 8bff419..a22e7ba 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.js
+++ b/web/gui/src/main/webapp/app/view/topo/topo.js
@@ -28,7 +28,7 @@
     ];
 
     // references to injected services etc.
-    var $log, ks, zs, gs, ms, ps, tes, tfs;
+    var $log, fs, ks, zs, gs, ms, ps, tes, tfs;
 
     // DOM elements
     var ovtopo, svg, defs, zoomLayer, mapG, forceG;
@@ -102,7 +102,8 @@
 
     // callback invoked when the SVG view has been resized..
     function svgResized(w, h) {
-        // not used now, but may be required later...
+        $log.debug('TopoView just resized... ' + w + 'x' + h);
+        tfs.resize(w, h);
     }
 
     // --- Background Map ------------------------------------------------
@@ -133,7 +134,7 @@
 
     function setUpForce() {
         forceG = zoomLayer.append('g').attr('id', 'topo-force');
-        tfs.initForce(forceG);
+        tfs.initForce(forceG, svg.attr('width'), svg.attr('height'));
     }
 
 
@@ -143,13 +144,15 @@
 
         .controller('OvTopoCtrl', [
             '$scope', '$log', '$location', '$timeout',
+            'FnService', 'MastService',
             'KeyService', 'ZoomService', 'GlyphService', 'MapService',
             'PanelService', 'TopoEventService', 'TopoForceService',
 
-        function ($scope, _$log_, $loc, $timeout,
+        function ($scope, _$log_, $loc, $timeout, _fs_, mast,
                   _ks_, _zs_, _gs_, _ms_, _ps_, _tes_, _tfs_) {
             var self = this;
             $log = _$log_;
+            fs = _fs_;
             ks = _ks_;
             zs = _zs_;
             gs = _gs_;
@@ -159,7 +162,7 @@
             tfs = _tfs_;
 
             self.notifyResize = function () {
-                svgResized(svg.style('width'), svg.style('height'));
+                svgResized(svg.attr('width'), svg.attr('height'));
             };
 
             // Cleanup on destroyed scope..
@@ -172,6 +175,8 @@
             // svg layer and initialization of components
             ovtopo = d3.select('#ov-topo');
             svg = ovtopo.select('svg');
+            // set the svg size to match that of the window, less the masthead
+            svg.attr(fs.windowSize(mast.mastHeight()));
 
             // bind to topo event dispatcher..
             evDispatcher = tes.bindDispatcher('TODO: topo-DOM-elements-here');
diff --git a/web/gui/src/main/webapp/app/view/topo/topoForce.js b/web/gui/src/main/webapp/app/view/topo/topoForce.js
index 6c3d501..196188c 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoForce.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoForce.js
@@ -96,8 +96,9 @@
             // forceG is the SVG group to display the force layout in
             // w, h are the initial dimensions of the SVG
             // opts are, well, optional :)
-            function initForce (forceG, w, h, opts) {
-                // TODO: create the force layout and initialize
+            function initForce(forceG, w, h, opts) {
+                $log.debug('initForce().. WxH = ' + w + 'x' + h);
+
                 settings = angular.extend({}, defaultSettings, opts);
 
                 linkG = forceG.append('g').attr('id', 'topo-links');
@@ -109,7 +110,7 @@
                 node = nodeG.selectAll('.node');
 
                 force = d3.layout.force()
-                    .size(w, h)
+                    .size([w, h])
                     .nodes(network.nodes)
                     .links(network.links)
                     .gravity(settings.gravity)
@@ -124,8 +125,9 @@
             }
 
             function resize(w, h) {
-                force.size(w, h);
+                force.size([w, h]);
                 // Review -- do we need to nudge the layout ?
+
             }
 
             return {
diff --git a/web/gui/src/main/webapp/tests/app/fw/mast/mast-spec.js b/web/gui/src/main/webapp/tests/app/fw/mast/mast-spec.js
index 131be1f..67fbfbb 100644
--- a/web/gui/src/main/webapp/tests/app/fw/mast/mast-spec.js
+++ b/web/gui/src/main/webapp/tests/app/fw/mast/mast-spec.js
@@ -21,15 +21,20 @@
     // instantiate the masthead module
     beforeEach(module('onosMast'));
 
-    var $log, ctrl;
+    var $log, ctrl, ms;
 
     // we need an instance of the controller
-    beforeEach(inject(function(_$log_, $controller) {
+    beforeEach(inject(function(_$log_, $controller, MastService) {
         $log = _$log_;
         ctrl = $controller('MastCtrl');
+        ms = MastService;
     }));
 
     it('should start with no radio buttons', function () {
         expect(ctrl.radio).toBeNull();
     });
+
+    it('should declare height to be 36', function () {
+        expect(ms.mastHeight()).toBe(36);
+    })
 });
diff --git a/web/gui/src/main/webapp/tests/app/fw/util/fn-spec.js b/web/gui/src/main/webapp/tests/app/fw/util/fn-spec.js
index 56819ae..e5b7223 100644
--- a/web/gui/src/main/webapp/tests/app/fw/util/fn-spec.js
+++ b/web/gui/src/main/webapp/tests/app/fw/util/fn-spec.js
@@ -18,7 +18,8 @@
  ONOS GUI -- Util -- General Purpose Functions - Unit Tests
  */
 describe('factory: fw/util/fn.js', function() {
-    var fs,
+    var $window,
+        fs,
         someFunction = function () {},
         someArray = [1, 2, 3],
         someObject = { foo: 'bar'},
@@ -29,8 +30,12 @@
 
     beforeEach(module('onosUtil'));
 
-    beforeEach(inject(function (FnService) {
+    beforeEach(inject(function (_$window_, FnService) {
+        $window = _$window_;
         fs = FnService;
+
+        $window.innerWidth = 400;
+        $window.innerHeight = 200;
     }));
 
 
@@ -186,4 +191,28 @@
     });
 
 
+    // === Tests for windowSize()
+    it('windowSize(): noargs', function () {
+        var dim = fs.windowSize();
+        expect(dim.width).toEqual(400);
+        expect(dim.height).toEqual(200);
+    });
+
+    it('windowSize(): adjust height', function () {
+        var dim = fs.windowSize(50);
+        expect(dim.width).toEqual(400);
+        expect(dim.height).toEqual(150);
+    });
+
+    it('windowSize(): adjust width', function () {
+        var dim = fs.windowSize(0, 50);
+        expect(dim.width).toEqual(350);
+        expect(dim.height).toEqual(200);
+    });
+
+    it('windowSize(): adjust width and height', function () {
+        var dim = fs.windowSize(101, 201);
+        expect(dim.width).toEqual(199);
+        expect(dim.height).toEqual(99);
+    });
 });
diff --git a/web/gui/src/main/webapp/tests/app/view/topo/topoForce-spec.js b/web/gui/src/main/webapp/tests/app/view/topo/topoForce-spec.js
index e5f0073..f8b4e66 100644
--- a/web/gui/src/main/webapp/tests/app/view/topo/topoForce-spec.js
+++ b/web/gui/src/main/webapp/tests/app/view/topo/topoForce-spec.js
@@ -34,7 +34,7 @@
 
     it('should define api functions', function () {
         expect(fs.areFunctions(tfs, [
-            'initForce'
+            'initForce', 'resize'
         ])).toBeTruthy();
     });