From 449923feae0a26318dc5a9ca5f0e067948383372 Mon Sep 17 00:00:00 2001 From: Deep Tailor Date: Wed, 2 Aug 2017 11:12:09 -0700 Subject: [PATCH] Persist User preference widths for MCTSplitPane Fix to Issue #1646 Persist MCTSplitPane widths to local storage, thus when user reloads, the Panes maintain dimensions Use persisted widths if available otherwise use default Add tests for localStorage and fix failing tests --- .gitignore | 2 + .../commonUI/browse/res/templates/browse.html | 5 +- platform/commonUI/general/bundle.js | 3 +- .../general/src/directives/MCTSplitPane.js | 53 ++++++++++++++----- .../general/src/directives/MCTSplitter.js | 11 ++-- .../test/directives/MCTSplitPaneSpec.js | 37 ++++++++++--- .../test/directives/MCTSplitterSpec.js | 15 +++--- 7 files changed, 89 insertions(+), 37 deletions(-) diff --git a/.gitignore b/.gitignore index b8eea4a5be..3d231f100b 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,5 @@ protractor/logs # npm-debug log npm-debug.log + +package-lock.json diff --git a/platform/commonUI/browse/res/templates/browse.html b/platform/commonUI/browse/res/templates/browse.html index b6d4dd0b92..0855ebcf59 100644 --- a/platform/commonUI/browse/res/templates/browse.html +++ b/platform/commonUI/browse/res/templates/browse.html @@ -26,7 +26,7 @@ ng-controller="PaneController as modelPaneTree" ng-class="modelPaneTree.visible() ? 'pane-tree-showing' : 'pane-tree-hidden'"> + anchor='left' alias="leftSide">
- +
- diff --git a/platform/commonUI/general/bundle.js b/platform/commonUI/general/bundle.js index 78e0e551ba..75f240d8c2 100644 --- a/platform/commonUI/general/bundle.js +++ b/platform/commonUI/general/bundle.js @@ -374,7 +374,8 @@ define([ "depends": [ "$parse", "$log", - "$interval" + "$interval", + "$window" ] }, { diff --git a/platform/commonUI/general/src/directives/MCTSplitPane.js b/platform/commonUI/general/src/directives/MCTSplitPane.js index 35625da413..f52876335c 100644 --- a/platform/commonUI/general/src/directives/MCTSplitPane.js +++ b/platform/commonUI/general/src/directives/MCTSplitPane.js @@ -93,13 +93,21 @@ define( * @memberof platform/commonUI/general * @constructor */ - function MCTSplitPane($parse, $log, $interval) { + function MCTSplitPane($parse, $log, $interval, $window) { function controller($scope, $element, $attrs) { var anchorKey = $attrs.anchor || DEFAULT_ANCHOR, + positionParsed = $parse($attrs.position), anchor, activeInterval, - positionParsed = $parse($attrs.position), - position; // Start undefined, until explicitly set + position, + splitterSize, + + alias = $attrs.alias !== undefined ? + "mctSplitPane-" + $attrs.alias : undefined, + + //convert string to number from localStorage + userWidthPreference = $window.localStorage.getItem(alias) === null ? + undefined : Number($window.localStorage.getItem(alias)); // Get relevant size (height or width) of DOM element function getSize(domElement) { @@ -114,11 +122,11 @@ define( var first = children.eq(anchor.reversed ? 2 : 0), splitter = children.eq(1), last = children.eq(anchor.reversed ? 0 : 2), - splitterSize = getSize(splitter[0]), firstSize; + splitterSize = getSize(splitter[0]); first.css(anchor.edge, "0px"); - first.css(anchor.dimension, (position - splitterSize) + 'px'); + first.css(anchor.dimension, (userWidthPreference || position) + 'px'); // Get actual size (to obey min-width etc.) firstSize = getSize(first[0]); @@ -126,9 +134,8 @@ define( splitter.css(anchor.edge, firstSize + 'px'); splitter.css(anchor.opposite, "auto"); - last.css(anchor.edge, (firstSize + splitterSize) + 'px'); + last.css(anchor.edge, firstSize + splitterSize + 'px'); last.css(anchor.opposite, "0px"); - position = firstSize + splitterSize; } @@ -169,6 +176,17 @@ define( return position; } + function setUserWidthPreference(value) { + userWidthPreference = value - splitterSize; + } + + function persistToLocalStorage(value) { + if (alias) { + userWidthPreference = value - splitterSize; + $window.localStorage.setItem(alias, userWidthPreference); + } + } + // Dynamically apply a CSS class to elements when the user // is actively resizing function toggleClass(classToToggle) { @@ -196,18 +214,31 @@ define( activeInterval = $interval(function () { getSetPosition(getSetPosition()); }, POLLING_INTERVAL, 0, false); - // ...and stop polling when we're destroyed. $scope.$on('$destroy', function () { $interval.cancel(activeInterval); }); + // Interface exposed by controller, for mct-splitter to user return { - position: getSetPosition, - toggleClass: toggleClass, anchor: function () { return anchor; + }, + position: function (value) { + if (arguments.length > 0) { + setUserWidthPreference(value); + return getSetPosition(value); + } else { + return getSetPosition(); + } + }, + startResizing: function () { + toggleClass('resizing'); + }, + endResizing: function (finalPosition) { + persistToLocalStorage(finalPosition); + toggleClass('resizing'); } }; } @@ -219,9 +250,7 @@ define( controller: ['$scope', '$element', '$attrs', controller] }; } - return MCTSplitPane; } ); - diff --git a/platform/commonUI/general/src/directives/MCTSplitter.js b/platform/commonUI/general/src/directives/MCTSplitter.js index 63acb90824..69db2f0a02 100644 --- a/platform/commonUI/general/src/directives/MCTSplitter.js +++ b/platform/commonUI/general/src/directives/MCTSplitter.js @@ -37,15 +37,16 @@ define( */ function MCTSplitter() { function link(scope, element, attrs, mctSplitPane) { - var initialPosition; + var initialPosition, + newPosition; element.addClass("splitter"); scope.splitter = { // Begin moving this splitter startMove: function () { + mctSplitPane.startResizing(); initialPosition = mctSplitPane.position(); - mctSplitPane.toggleClass('resizing'); }, // Handle user changes to splitter position move: function (delta) { @@ -55,12 +56,13 @@ define( (anchor.reversed ? -1 : 1); // Update the position of this splitter - mctSplitPane.position(initialPosition + pixelDelta); + newPosition = initialPosition + pixelDelta; + mctSplitPane.position(newPosition); }, // Grab the event when the user is done moving // the splitter and pass it on endMove: function () { - mctSplitPane.toggleClass('resizing'); + mctSplitPane.endResizing(newPosition); } }; } @@ -83,4 +85,3 @@ define( } ); - diff --git a/platform/commonUI/general/test/directives/MCTSplitPaneSpec.js b/platform/commonUI/general/test/directives/MCTSplitPaneSpec.js index fb36eba68c..abd0935021 100644 --- a/platform/commonUI/general/test/directives/MCTSplitPaneSpec.js +++ b/platform/commonUI/general/test/directives/MCTSplitPaneSpec.js @@ -38,7 +38,8 @@ define( mockLog, mockInterval, mockParsed, - mctSplitPane; + mctSplitPane, + mockWindow = {}; beforeEach(function () { mockParse = jasmine.createSpy('$parse'); @@ -48,13 +49,23 @@ define( mockInterval.cancel = jasmine.createSpy('mockCancel'); mockParsed = jasmine.createSpy('parsed'); mockParsed.assign = jasmine.createSpy('assign'); - mockParse.andReturn(mockParsed); + mockWindow.localStorage = { + store: {}, + setItem: function (key, value) { + this.store[key] = value; + }, + getItem: function (key) { + return this.store[key]; + } + }; + mctSplitPane = new MCTSplitPane( mockParse, mockLog, - mockInterval + mockInterval, + mockWindow ); }); @@ -85,7 +96,7 @@ define( jasmine.createSpyObj('$scope', ['$apply', '$watch', '$on']); mockElement = jasmine.createSpyObj('element', JQLITE_METHODS); - testAttrs = {}; + testAttrs = {alias: 'rightSide'}; mockChildren = jasmine.createSpyObj('children', JQLITE_METHODS); mockFirstPane = @@ -142,10 +153,14 @@ define( }); }); - it("allows classes to be toggled on contained elements", function () { - controller.toggleClass('resizing'); - expect(mockChildren.toggleClass) - .toHaveBeenCalledWith('resizing'); + it("applies resizing class to children when resizing", function () { + controller.startResizing(); + expect(mockChildren.toggleClass).toHaveBeenCalledWith('resizing'); + }); + + it("removes resizing class from children when resizing action ends", function () { + controller.endResizing(0); + expect(mockChildren.toggleClass).toHaveBeenCalledWith('resizing'); }); it("allows positions to be set", function () { @@ -198,6 +213,12 @@ define( fireOn('$destroy'); expect(mockInterval.cancel).toHaveBeenCalled(); }); + + it("saves user preference to localStorage when user is done resizing", function () { + controller.endResizing(100); + expect(Number(mockWindow.localStorage.getItem('mctSplitPane-rightSide'))).toEqual(100 - mockSplitter[0].offsetWidth); + }); + }); }); diff --git a/platform/commonUI/general/test/directives/MCTSplitterSpec.js b/platform/commonUI/general/test/directives/MCTSplitterSpec.js index df6ee256e6..caf0fd468d 100644 --- a/platform/commonUI/general/test/directives/MCTSplitterSpec.js +++ b/platform/commonUI/general/test/directives/MCTSplitterSpec.js @@ -57,7 +57,7 @@ define( testAttrs = {}; mockSplitPane = jasmine.createSpyObj( 'mctSplitPane', - ['position', 'toggleClass', 'anchor'] + ['position', 'startResizing', 'endResizing', 'anchor'] ); mctSplitter.link( @@ -86,9 +86,9 @@ define( mockScope.splitter.startMove(); }); - it("adds a 'resizing' class", function () { - expect(mockSplitPane.toggleClass) - .toHaveBeenCalledWith('resizing'); + it("tell's the splitter when it is resizing", function () { + expect(mockSplitPane.startResizing) + .toHaveBeenCalled(); }); it("repositions during drag", function () { @@ -97,11 +97,10 @@ define( .toHaveBeenCalledWith(testPosition + 10); }); - it("removes the 'resizing' class when finished", function () { - mockSplitPane.toggleClass.reset(); + it("tell's the splitter when it is done resizing", function () { + mockScope.splitter.move([10,0]); mockScope.splitter.endMove(); - expect(mockSplitPane.toggleClass) - .toHaveBeenCalledWith('resizing'); + expect(mockSplitPane.endResizing).toHaveBeenCalledWith(testPosition + 10); }); });