diff options
Diffstat (limited to 'ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts')
5 files changed, 607 insertions, 0 deletions
diff --git a/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/SaveChangesModal.html b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/SaveChangesModal.html new file mode 100644 index 00000000..f9f6f361 --- /dev/null +++ b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/SaveChangesModal.html @@ -0,0 +1,13 @@ +<div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true" ng-click="cancel()">×</button> + <h3>Unsaved Changes to "{{layout.title}}"</h3> +</div> + +<div class="modal-body"> + <p>You have {{layout.dashboard.unsavedChangeCount}} unsaved changes on this dashboard. Would you like to save them?</p> +</div> + +<div class="modal-footer"> + <button type="button" class="btn btn-default" ng-click="cancel()">Don't Save</button> + <button type="button" class="btn btn-primary" ng-click="ok()">Save</button> +</div>
\ No newline at end of file diff --git a/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/SaveChangesModalCtrl.js b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/SaveChangesModalCtrl.js new file mode 100644 index 00000000..252f9df4 --- /dev/null +++ b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/SaveChangesModalCtrl.js @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. + * + * 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. + */ + +'use strict'; + +angular.module('ui.dashboard') + .controller('SaveChangesModalCtrl', ['$scope', '$uibModalInstance', 'layout', function ($scope, $uibModalInstance, layout) { + + // add layout to scope + $scope.layout = layout; + + $scope.ok = function () { + $uibModalInstance.close(); + }; + + $scope.cancel = function () { + $uibModalInstance.dismiss(); + }; + }]);
\ No newline at end of file diff --git a/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/dashboardLayouts.html b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/dashboardLayouts.html new file mode 100644 index 00000000..54aef297 --- /dev/null +++ b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/dashboardLayouts.html @@ -0,0 +1,19 @@ +<ul ui-sortable="sortableOptions" ng-model="layouts" class="nav nav-tabs layout-tabs"> + <li ng-repeat="layout in layouts" ng-class="{ active: layout.active }"> + <a ng-click="makeLayoutActive(layout)"> + <span ng-dblclick="editTitle(layout)" ng-show="!layout.editingTitle">{{layout.title}}</span> + <form action="" class="layout-title" ng-show="layout.editingTitle" ng-submit="saveTitleEdit(layout)"> + <input type="text" ng-model="layout.title" class="form-control" data-layout="{{layout.id}}"> + </form> + <span ng-if="!layout.locked" ng-click="removeLayout(layout)" class="glyphicon glyphicon-remove icon-erase remove-layout-icon"></span> + <!-- <span class="glyphicon glyphicon-pencil"></span> --> + <!-- <span class="glyphicon glyphicon-remove"></span> --> + </a> + </li> + <li> + <a ng-click="createNewLayout()"> + <span class="glyphicon glyphicon-plus"></span> + </a> + </li> +</ul> +<div ng-repeat="layout in layouts | filter:isActive" dashboard="layout.dashboard" template-url="app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboard/dashboard.html"></div>
\ No newline at end of file diff --git a/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/dashboardLayouts.js b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/dashboardLayouts.js new file mode 100644 index 00000000..bbf107a8 --- /dev/null +++ b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/dashboardLayouts.js @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. + * + * 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. + */ + +'use strict'; + +angular.module('ui.dashboard') + .directive('dashboardLayouts', ['LayoutStorage', '$timeout', '$uibModal', + function(LayoutStorage, $timeout, $uibModal) { + return { + scope: true, + templateUrl: function(element, attr) { + return attr.templateUrl ? attr.templateUrl : 'app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/dashboardLayouts.html'; + }, + link: function(scope, element, attrs) { + + scope.options = scope.$eval(attrs.dashboardLayouts); + + var layoutStorage = new LayoutStorage(scope.options); + + scope.layouts = layoutStorage.layouts; + + scope.createNewLayout = function() { + var newLayout = { + title: 'Custom', + defaultWidgets: scope.options.defaultWidgets || [] + }; + layoutStorage.add(newLayout); + scope.makeLayoutActive(newLayout); + layoutStorage.save(); + return newLayout; + }; + + scope.removeLayout = function(layout) { + layoutStorage.remove(layout); + layoutStorage.save(); + }; + + scope.makeLayoutActive = function(layout) { + + var current = layoutStorage.getActiveLayout(); + + if (current && current.dashboard.unsavedChangeCount) { + var modalInstance = $uibModal.open({ + templateUrl: 'template/SaveChangesModal.html', + resolve: { + layout: function() { + return layout; + } + }, + controller: 'SaveChangesModalCtrl' + }); + + // Set resolve and reject callbacks for the result promise + modalInstance.result.then( + function() { + current.dashboard.saveDashboard(); + scope._makeLayoutActive(layout); + }, + function() { + scope._makeLayoutActive(layout); + } + ); + } else { + scope._makeLayoutActive(layout); + } + + }; + + scope._makeLayoutActive = function(layout) { + angular.forEach(scope.layouts, function(l) { + if (l !== layout) { + l.active = false; + } else { + l.active = true; + } + }); + layoutStorage.save(); + }; + + scope.isActive = function(layout) { + return !!layout.active; + }; + + scope.editTitle = function(layout) { + if (layout.locked) { + return; + } + + var input = element.find('input[data-layout="' + layout.id + '"]'); + layout.editingTitle = true; + + $timeout(function() { + input.focus()[0].setSelectionRange(0, 9999); + }); + }; + + // saves whatever is in the title input as the new title + scope.saveTitleEdit = function(layout) { + layout.editingTitle = false; + layoutStorage.save(); + }; + + scope.options.saveLayouts = function() { + layoutStorage.save(true); + }; + scope.options.addWidget = function() { + var layout = layoutStorage.getActiveLayout(); + if (layout) { + layout.dashboard.addWidget.apply(layout.dashboard, arguments); + } + }; + scope.options.loadWidgets = function() { + var layout = layoutStorage.getActiveLayout(); + if (layout) { + layout.dashboard.loadWidgets.apply(layout.dashboard, arguments); + } + }; + scope.options.saveDashboard = function() { + console.log("================= saveDashboard called =================") + var layout = layoutStorage.getActiveLayout(); + console.log("===================== layout ==========================="); + console.log(layout); + if (layout) { + layout.dashboard.saveDashboard.apply(layout.dashboard, arguments); + } + }; + + var sortableDefaults = { + stop: function() { + scope.options.saveLayouts(); + }, + distance: 5 + }; + scope.sortableOptions = angular.extend({}, sortableDefaults, scope.options.sortableOptions || {}); + } + }; + } + ]);
\ No newline at end of file diff --git a/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/dashboardLayouts.spec.js b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/dashboardLayouts.spec.js new file mode 100644 index 00000000..8533a211 --- /dev/null +++ b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/app/fusion/scripts/view-models/reportdashboard-page/src/components/directives/dashboardLayouts/dashboardLayouts.spec.js @@ -0,0 +1,392 @@ +/* + * Copyright (c) 2014 DataTorrent, Inc. ALL Rights Reserved. + * + * 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. + */ + +'use strict'; + +describe('Directive: dashboard-layouts', function () { + + var $rootScope, element, options, childScope, DashboardState, LayoutStorage, $mockModal, $mockTimeout, toFn; + + // mock UI Sortable + beforeEach(function () { + angular.module('ui.sortable', []); + }); + + // load the directive's module + beforeEach(module('ui.dashboard', function($provide) { + $mockModal = { + open: function() {} + }; + $mockTimeout = function(fn, delay) { + toFn = fn; + }; + $provide.value('$uibModal', $mockModal); + $provide.value('$timeout', $mockTimeout); + })); + + beforeEach(inject(function ($compile, _$rootScope_, _DashboardState_, _LayoutStorage_) { + // services + $rootScope = _$rootScope_; + DashboardState = _DashboardState_; + LayoutStorage = _LayoutStorage_; + + // options + var widgetDefinitions = [ + { + name: 'wt-one', + template: '<div class="wt-one-value">{{2 + 2}}</div>' + }, + { + name: 'wt-two', + template: '<span class="wt-two-value">{{value}}</span>' + } + ]; + var defaultWidgets = _.clone(widgetDefinitions); + $rootScope.dashboardOptions = options = { + widgetButtons: true, + widgetDefinitions: widgetDefinitions, + defaultLayouts: [ + { + title: 'first', + active: true, + defaultWidgets: defaultWidgets + }, + { + title: 'second', + active: false, + defaultWidgets: defaultWidgets + } + ], + defaultWidgets: defaultWidgets, + storage: { + setItem: function(key, val) { + + }, + getItem: function(key) { + + }, + removeItem: function(key) { + + } + } + }; + $rootScope.value = 10; + + // element setup + element = $compile('<div dashboard-layouts="dashboardOptions"></div>')($rootScope); + $rootScope.$digest(); + childScope = element.scope(); + })); + + it('should not require storage', inject(function($compile) { + delete $rootScope.dashboardOptions.storage; + expect(function() { + var noStorageEl = $compile('<div dashboard-layouts="dashboardOptions"></div>')($rootScope); + $rootScope.$digest(); + }).not.toThrow(); + + })); + + it('should be able to use a different dashboard-layouts template', inject(function($compile, $templateCache) { + $templateCache.put( + 'myCustomTemplate.html', + '<ul class="my-custom-tabs layout-tabs">' + + '<li ng-repeat="layout in layouts" ng-class="{ active: layout.active }">' + + '<a ng-click="makeLayoutActive(layout)">' + + '<span ng-dblclick="editTitle(layout)" ng-show="!layout.editingTitle">{{layout.title}}</span>' + + '<form action="" class="layout-title" ng-show="layout.editingTitle" ng-submit="saveTitleEdit(layout)">' + + '<input type="text" ng-model="layout.title" class="form-control" data-layout="{{layout.id}}">' + + '</form>' + + '<span ng-click="removeLayout(layout)" class="glyphicon glyphicon-remove icon-erase"></span>' + + '<!-- <span class="glyphicon glyphicon-pencil"></span> -->' + + '<!-- <span class="glyphicon glyphicon-remove"></span> -->' + + '</a>' + + '</li>' + + '<li>' + + '<a ng-click="createNewLayout()">' + + '<span class="glyphicon glyphicon-plus"></span>' + + '</a>' + + '</li>' + + '</ul>' + + '<div ng-repeat="layout in layouts | filter:isActive" dashboard="layout.dashboard" templateUrl="template/dashboard.html"></div>' + ); + var customElement = $compile('<div dashboard-layouts="dashboardOptions" template-url="myCustomTemplate.html"></div>')($rootScope); + $rootScope.$digest(); + expect(customElement.find('ul.my-custom-tabs').length).toEqual(1); + + })); + + it('should set the first dashboard to active if there is not one already active', inject(function($compile) { + options.defaultLayouts[0].active = options.defaultLayouts[1].active = false; + element = $compile('<div dashboard-layouts="dashboardOptions"></div>')($rootScope); + $rootScope.$digest(); + childScope = element.scope(); + + var layouts = childScope.layouts; + var active; + for (var i = 0; i < layouts.length; i++) { + if (layouts[i].active) { + active = layouts[i]; + break; + } + }; + expect(active).not.toBeUndefined(); + })); + + describe('the createNewLayout method', function() { + + it('should call the add and save methods of LayoutStorage', function() { + spyOn(LayoutStorage.prototype, 'add'); + spyOn(LayoutStorage.prototype, 'save'); + + childScope.createNewLayout(); + expect(LayoutStorage.prototype.add).toHaveBeenCalled(); + expect(LayoutStorage.prototype.save).toHaveBeenCalled(); + }); + + it('should return the newly created layout object', function() { + var result = childScope.createNewLayout(); + expect(typeof result).toEqual('object'); + }); + + it('should set active=true on the newly created layout', function() { + var result = childScope.createNewLayout(); + expect(result.active).toEqual(true); + }); + + it('should set defaultWidgets to dashboardOptions.defaultWidgets if it is present', function() { + var result = childScope.createNewLayout(); + expect(result.defaultWidgets === options.defaultWidgets).toEqual(true); + }); + + it('should set defaultWidgets to an empty array if dashboardOptions.defaultWidgets is not present', inject(function($compile) { + delete options.defaultWidgets; + element = $compile('<div dashboard-layouts="dashboardOptions"></div>')($rootScope); + $rootScope.$digest(); + childScope = element.scope(); + var result = childScope.createNewLayout(); + expect(result.defaultWidgets).toEqual([]); + })); + + }); + + describe('the removeLayout method', function() { + + it('should call the remove and save methods of LayoutStorage', function() { + spyOn(LayoutStorage.prototype, 'remove'); + spyOn(LayoutStorage.prototype, 'save'); + + childScope.removeLayout(childScope.layouts[0]); + expect(LayoutStorage.prototype.remove).toHaveBeenCalled(); + expect(LayoutStorage.prototype.save).toHaveBeenCalled(); + }); + + it('should call remove with the layout it was passed', function() { + spyOn(LayoutStorage.prototype, 'remove'); + var layout = childScope.layouts[0]; + childScope.removeLayout(layout); + expect(LayoutStorage.prototype.remove.calls.argsFor(0)[0]).toEqual(layout); + }); + + }); + + describe('the makeLayoutActive method', function() { + + it('should call _makeLayoutActive if there is not a currently active dashboard with unsaved changes', function() { + spyOn(childScope, '_makeLayoutActive'); + var layout = childScope.layouts[1]; + childScope.makeLayoutActive(layout); + expect(childScope._makeLayoutActive).toHaveBeenCalled(); + }); + + describe('when there are unsaved changes on the current dashboard', function() { + + var current, options, successCb, errorCb, layout; + + beforeEach(function() { + current = childScope.layouts[0]; + current.dashboard.unsavedChangeCount = 1; + + spyOn($mockModal, 'open').and.callFake(function(arg) { + options = arg; + return { + result: { + then: function(success, error) { + successCb = success; + errorCb = error; + } + } + } + }); + + layout = childScope.layouts[1]; + childScope.makeLayoutActive(layout); + }); + + it('should create a modal', function() { + expect($mockModal.open).toHaveBeenCalled(); + }); + + it('should resolve layout to the layout to be made active', function() { + expect(options.resolve.layout()).toEqual(layout); + }); + + it('should provide a success callback that saves the current dashboard and then calls _makeLayoutActive', function() { + spyOn(current.dashboard, 'saveDashboard'); + spyOn(childScope, '_makeLayoutActive'); + successCb(); + expect(current.dashboard.saveDashboard).toHaveBeenCalled(); + expect(childScope._makeLayoutActive).toHaveBeenCalled(); + expect(childScope._makeLayoutActive.calls.argsFor(0)[0]).toEqual(layout); + }); + + it('should provide an error callback that only calls _makeLayoutActive', function() { + spyOn(current.dashboard, 'saveDashboard'); + spyOn(childScope, '_makeLayoutActive'); + errorCb(); + expect(current.dashboard.saveDashboard).not.toHaveBeenCalled(); + expect(childScope._makeLayoutActive).toHaveBeenCalled(); + expect(childScope._makeLayoutActive.calls.argsFor(0)[0]).toEqual(layout); + }); + + }); + + }); + + describe('the editTitle method', function() { + + it('should set the editingTitle attribute to true on the layout it is passed', function() { + var layout = { id: '1' }; + childScope.editTitle(layout); + $rootScope.$digest(); + expect(layout.editingTitle).toEqual(true); + toFn(); + }); + + }); + + describe('the saveTitleEdit method', function() { + + it('should set editingTitle to false', function() { + var layout = { id: '1' }; + childScope.saveTitleEdit(layout); + expect(layout.editingTitle).toEqual(false); + }); + + it('should call layoutStorage.save', function() { + var layout = { id: '1' }; + spyOn(LayoutStorage.prototype, 'save').and.callThrough(); + childScope.saveTitleEdit(layout); + expect(LayoutStorage.prototype.save).toHaveBeenCalled(); + }); + + }); + + describe('the saveLayouts method', function() { + + it('should call LayoutStorage.save', function() { + spyOn(LayoutStorage.prototype, 'save').and.callThrough(); + $rootScope.dashboardOptions.saveLayouts(); + expect(LayoutStorage.prototype.save).toHaveBeenCalled(); + }); + + it('should call LayoutStorage.save with true as the first arg', function() { + spyOn(LayoutStorage.prototype, 'save').and.callThrough(); + $rootScope.dashboardOptions.saveLayouts(); + expect(LayoutStorage.prototype.save.calls.argsFor(0)[0]).toEqual(true); + }); + + }); + describe('the proxy methods to active layout', function() { + + var mockDash, galSpy; + + beforeEach(function() { + mockDash = { + active: true, + dashboard: { + addWidget: function() {}, + loadWidgets: function() {}, + saveDashboard: function() {} + } + }; + spyOn(mockDash.dashboard, 'addWidget'); + spyOn(mockDash.dashboard, 'loadWidgets'); + spyOn(mockDash.dashboard, 'saveDashboard'); + galSpy = spyOn(LayoutStorage.prototype, 'getActiveLayout').and; + galSpy.returnValue(mockDash); + }); + + describe('the addWidget method', function() { + + it('should call dashboard.addWidget method of the active layout', function() { + options.addWidget(1,2,3); + expect(mockDash.dashboard.addWidget).toHaveBeenCalled(); + var firstCall = mockDash.dashboard.addWidget.calls.first(); + expect(firstCall.object).toEqual(mockDash.dashboard); + expect(firstCall.args).toEqual([1,2,3]); + }); + + it('should do nothing if there is no active layout', function() { + galSpy.returnValue(null); + expect(function() { + options.addWidget(); + }).not.toThrow(); + }); + + }); + + describe('the loadWidgets method', function() { + + it('should call dashboard.loadWidgets of the current layout', function() { + options.loadWidgets(1,2,3); + expect(mockDash.dashboard.loadWidgets).toHaveBeenCalled(); + var firstCall = mockDash.dashboard.loadWidgets.calls.first(); + expect(firstCall.object).toEqual(mockDash.dashboard); + expect(firstCall.args).toEqual([1,2,3]); + }); + + it('should do nothing if there is no active layout', function() { + galSpy.returnValue(null); + expect(function() { + options.loadWidgets(); + }).not.toThrow(); + }); + + }); + + describe('the saveDashboard method', function() { + + it('should call dashboard.saveDashboard of the current layout', function() { + options.saveDashboard(1,2,3); + expect(mockDash.dashboard.saveDashboard).toHaveBeenCalled(); + var firstCall = mockDash.dashboard.saveDashboard.calls.first(); + expect(firstCall.object).toEqual(mockDash.dashboard); + expect(firstCall.args).toEqual([1,2,3]); + }); + + it('should do nothing if there is no active layout', function() { + galSpy.returnValue(null); + expect(function() { + options.saveDashboard(); + }).not.toThrow(); + }); + + }); + + }); + +});
\ No newline at end of file |