diff options
10 files changed, 207 insertions, 16 deletions
diff --git a/horizon/static/framework/conf/resource-type-registry.service.js b/horizon/static/framework/conf/resource-type-registry.service.js index eafecacc6..00080fc61 100644 --- a/horizon/static/framework/conf/resource-type-registry.service.js +++ b/horizon/static/framework/conf/resource-type-registry.service.js @@ -110,6 +110,8 @@ self.list = defaultListFunction; self.setListFunction = setListFunction; self.isListFunctionSet = isListFunctionSet; + self.itemInTransitionFunction = defaultItemInTransitionFunction; + self.setItemInTransitionFunction = setItemInTransitionFunction; self.itemName = itemName; self.setItemNameFunction = setItemNameFunction; self.setPathParser = setPathParser; @@ -253,6 +255,40 @@ /** * @ngdoc function + * @name defaultItemInTransitionFunction + * @description + * A default implementation for the "itemInTransitionFunction function-pointer" which + * returns false every time. + * @returns {boolean} + */ + function defaultItemInTransitionFunction() { + return false; + } + + /** + * Set a function that detects if an instance of this resource type is in a + * "transition" state, such as an image with a "queued" status, or an instance + * with an "in-progress" status. For example, this might be used to highlight + * a particular item in a list, or to set a progress indicator when viewing that + * items details. + * + * By default, a call to itemInTransitionFunction(item) will return false unless this + * function is registered for the resource type; + * + * @ngdoc function + * @param func - The callback-function to be used for determining if this + * resource is in a transitional state. This callback-function will be passed + * an object that is an instance of this resource (e.g. an image) and should + * return a boolean. "true" indicates the item is in a "transition" state. + * @returns {ResourceType} - returning self to facilitate call-chaining. + */ + function setItemInTransitionFunction(func) { + self.itemInTransitionFunction = func; + return self; + } + + /** + * @ngdoc function * @name getTableColumns * @description * Provides the table columns for this type and generates a 'title' diff --git a/horizon/static/framework/conf/resource-type-registry.service.spec.js b/horizon/static/framework/conf/resource-type-registry.service.spec.js index 5cb473621..b204bd4d6 100644 --- a/horizon/static/framework/conf/resource-type-registry.service.spec.js +++ b/horizon/static/framework/conf/resource-type-registry.service.spec.js @@ -195,6 +195,18 @@ expect(type.list()).toBe('this would be a promise'); }); + it("has a default isInTransition function that returns false", function() { + expect(type.itemInTransitionFunction()).toBe(false); + }); + + it("allows setting an isInTransition function", function() { + function isInTransitionTest() { + return "would return a boolean"; + } + type.setItemInTransitionFunction(isInTransitionTest); + expect(type.itemInTransitionFunction()).toBe("would return a boolean"); + }); + it("allows setting of a summary template URL", function() { type.setSummaryTemplateUrl('/my/path.html'); expect(type.summaryTemplateUrl).toBe('/my/path.html'); diff --git a/horizon/static/framework/widgets/table/hz-cell.directive.js b/horizon/static/framework/widgets/table/hz-cell.directive.js index efd94d2c4..a4454dfe8 100644 --- a/horizon/static/framework/widgets/table/hz-cell.directive.js +++ b/horizon/static/framework/widgets/table/hz-cell.directive.js @@ -44,6 +44,10 @@ * It should ideally be used within the context of the `hz-dynamic-table` directive. * 'table' can be referenced in a template if you want to pass in an outside scope. * + * If the column has a itemInTransitionFunction property, that function will be + * called with the row's item. If the function returns true, a progress bar will + * be included in the cell. + * * @restrict E * * @scope @@ -64,7 +68,8 @@ * 'a': 'apple', * 'j': 'jacks' * } - * } + * }, + * {id: 'f', title: 'Status', itemInTransitionFunction: myInTransitionFunc}, * ] * }; * @@ -98,13 +103,24 @@ function link(scope, element) { var column = scope.column; var html; + var progressBarHtml = ''; if (column && column.template) { // if template provided, render, and place into cell html = $compile(column.template)(scope); } else { // NOTE: 'table' is not passed to hz-field as hz-field is intentionally // not cognizant of a 'table' context as hz-cell is. - html = $compile('<hz-field config="column" item="item"></hz-field>')(scope); + if (column.itemInTransitionFunction && column.itemInTransitionFunction(scope.item)) { + // NOTE(woodnt): It'd be nice to split this out into a template file, + // but since we're inside a link function, that's complicated. + progressBarHtml = '<div class="progress-text horizon-loading-bar">' + + '<div class="progress progress-striped active">' + + '<div class="progress-bar"></div>' + + '</div>' + + '</div>'; + } + html = $compile(progressBarHtml + + '<hz-field config="column" item="item"></hz-field>')(scope); } element.append(html); } diff --git a/horizon/static/framework/widgets/table/hz-dynamic-table.directive.js b/horizon/static/framework/widgets/table/hz-dynamic-table.directive.js index 757d418a3..1846255c2 100644 --- a/horizon/static/framework/widgets/table/hz-dynamic-table.directive.js +++ b/horizon/static/framework/widgets/table/hz-dynamic-table.directive.js @@ -39,6 +39,9 @@ * searching. Filter will not be shown if this is not supplied (optional) * @param {function=} resultHandler function that is called with return value * from a clicked actions perform function passed into `actions` directive (optional) + * @param {function=} itemInTransitionFunction function that is called with each item in + * the table. If it returns true, the row is given the class "status_unknown" which by + * default highlights the row with a warning color. * * @description * The `hzDynamicTable` directive generates all the HTML content for a table. @@ -121,7 +124,8 @@ batchActions: '=?', itemActions: '=?', filterFacets: '=?', - resultHandler: '=?' + resultHandler: '=?', + itemInTransitionFunction: '=?' }, controller: 'horizon.framework.widgets.table.HzDynamicTableController', templateUrl: basePath + 'table/hz-dynamic-table.html' diff --git a/horizon/static/framework/widgets/table/hz-dynamic-table.directive.spec.js b/horizon/static/framework/widgets/table/hz-dynamic-table.directive.spec.js new file mode 100644 index 000000000..cbc278177 --- /dev/null +++ b/horizon/static/framework/widgets/table/hz-dynamic-table.directive.spec.js @@ -0,0 +1,82 @@ +/* + * (c) Copyright 2015 Hewlett-Packard Development Company, L.P. + * + * 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. + */ + +(function() { + 'use strict'; + + describe('hz-dynamic-table directive', function() { + var $compile, $rootScope, $scope; + + beforeEach(module('templates')); + beforeEach(module('smart-table')); + beforeEach(module('horizon.framework')); + beforeEach(module('horizon.framework.util')); + beforeEach(module('horizon.framework.conf')); + beforeEach(module('horizon.framework.widgets')); + beforeEach(module('horizon.framework.widgets.magic-search')); + beforeEach(module('horizon.framework.widgets.table')); + + beforeEach(inject(function(_$compile_, _$rootScope_) { + $compile = _$compile_; + $rootScope = _$rootScope_; + $scope = $rootScope.$new(); + })); + + it("sets class when item in transition", function() { + var config = { + selectAll: false, + expand: false, + trackId: 'id', + columns: [ + {id: 'a', title: 'A', priority: 1}, + {id: 'b', title: 'B', priority: 2} + ] + }; + $scope.config = config; + $scope.tableConfig = config; + $scope.items = [ + { + id: 1, + a: "a", + b: "b" + }, + { + id: 2, + a: "a", + b: "b" + }, + { + id: 3, + a: "a", + b: "b" + } + ]; + $scope.itemInTransitionFunc = function(item) { + return item.id === 1; + }; + var element = $compile( + "<hz-dynamic-table" + + " config='tableConfig'" + + " items='items'" + + " item-in-transition-function='itemInTransitionFunc'>" + + "</hz-dynamic-table>")($scope); + $scope.$digest(); + // 3 items in table, only one with ID that will return true from itemInTransitionFunc + expect(element.find("tr.status_unknown").length).toBe(1); + }); + }); + +})(); diff --git a/horizon/static/framework/widgets/table/hz-dynamic-table.html b/horizon/static/framework/widgets/table/hz-dynamic-table.html index a90b8a715..a6c8999e7 100644 --- a/horizon/static/framework/widgets/table/hz-dynamic-table.html +++ b/horizon/static/framework/widgets/table/hz-dynamic-table.html @@ -52,7 +52,8 @@ classes rsp-p1 rsp-p2 are responsive priority as user resizes window. --> <tr ng-repeat-start="item in items track by item[config.trackId]" - ng-class="{'st-selected': checked[item[config.trackId]]}"> + ng-class="{'st-selected': checked[item[config.trackId]], + 'status_unknown': itemInTransitionFunction(item)}"> <td ng-show="config.selectAll" class="multi_select_column"> <div class="themable-checkbox"> diff --git a/horizon/static/framework/widgets/table/hz-resource-table.controller.js b/horizon/static/framework/widgets/table/hz-resource-table.controller.js index 136466920..863eca9e2 100644 --- a/horizon/static/framework/widgets/table/hz-resource-table.controller.js +++ b/horizon/static/framework/widgets/table/hz-resource-table.controller.js @@ -40,6 +40,7 @@ ctrl.batchActions = []; ctrl.items = []; ctrl.itemsSrc = []; + ctrl.itemInTransitionFunction = itemInTransitionFunction; // Watch for changes to search bar $scope.$on(events.SERVER_SEARCH_UPDATED, handleServerSearch); @@ -187,6 +188,10 @@ }).length === 0; } } + + function itemInTransitionFunction(item) { + return ctrl.resourceType.itemInTransitionFunction(item); + } } })(); diff --git a/horizon/static/framework/widgets/table/hz-resource-table.controller.spec.js b/horizon/static/framework/widgets/table/hz-resource-table.controller.spec.js index 51da3b530..cfaff5db4 100644 --- a/horizon/static/framework/widgets/table/hz-resource-table.controller.spec.js +++ b/horizon/static/framework/widgets/table/hz-resource-table.controller.spec.js @@ -31,7 +31,8 @@ getTableColumns: angular.noop, list: angular.noop, globalActions: [], - batchActions: [] + batchActions: [], + itemInTransitionFunction: angular.noop }; beforeEach(inject(function($rootScope, $controller, $q) { @@ -207,6 +208,14 @@ }); }); + describe('item in transition function', function() { + it('it calls resource type itemInTransitionFunction', function() { + spyOn(resourceType, "itemInTransitionFunction"); + ctrl.itemInTransitionFunction(); + expect(resourceType.itemInTransitionFunction.calls.count()).toBe(1); + }); + }); + }); })(); diff --git a/horizon/static/framework/widgets/table/hz-resource-table.html b/horizon/static/framework/widgets/table/hz-resource-table.html index 2ca4e71e7..3ff5384b2 100644 --- a/horizon/static/framework/widgets/table/hz-resource-table.html +++ b/horizon/static/framework/widgets/table/hz-resource-table.html @@ -7,4 +7,5 @@ batch-actions="ctrl.batchActions" filter-facets="ctrl.searchFacets" result-handler="ctrl.actionResultHandler" + item-in-transition-function="ctrl.itemInTransitionFunction" ></hz-dynamic-table> diff --git a/horizon/static/framework/widgets/table/table.scss b/horizon/static/framework/widgets/table/table.scss index 1af84e6a3..5539fdc22 100644 --- a/horizon/static/framework/widgets/table/table.scss +++ b/horizon/static/framework/widgets/table/table.scss @@ -87,8 +87,11 @@ $em-per-priority: floor($table-col-avg-width / $font-size-base) * 3; opacity: 0; } - &:not(.st-sort-ascent):hover:after, &:not(.st-sort-descent):hover:after { - opacity: 1; + &:not(.st-sort-ascent), + &:not(.st-sort-descent) { + &:hover:after { + opacity: 1; + } } } @@ -149,30 +152,52 @@ $em-per-priority: floor($table-col-avg-width / $font-size-base) * 3; &.table-striped { tbody { tr { - &:nth-child(2n+1) > td, &:nth-child(2n+1) + .detail-row > td { - background-color: $table-bg-accent; + &:nth-child(2n+1) { + + > td, + + .detail-row > td { + background-color: $table-bg-accent; + } + + &.status_unknown td { + background-color: lighten($brand-warning, 10%); + } } &, - &.spacer-row > td, &.spacer-row:nth-child(6n+3) + tr + tr.detail-row td, - &.detail-row:nth-child(4n+2) + tr:not(.spacer-row) td, - &.detail-row:nth-child(4n+2) + tr:not(.spacer-row) + tr.detail-row td { + &.spacer-row > td, + &.spacer-row:nth-child(6n+3) + tr + tr.detail-row td { background-color: transparent; } + &.detail-row:nth-child(4n+2) + tr:not(.spacer-row) { + td, + + tr.detail-row td { + background-color: transparent; + } + + &.status_unknown td { + background-color: lighten($brand-warning, 6%); + + } + } } } } } @media only all { - .rsp-p1, .rsp-p2, - .rsp-p3, .rsp-p4 { + .rsp-p1, + .rsp-p2, + .rsp-p3, + .rsp-p4 { display: none; } th,td { - &.rsp-alt-p1, &.rsp-alt-p2, - &.rsp-alt-p3, &.rsp-alt-p4 { + &.rsp-alt-p1, + &.rsp-alt-p2, + &.rsp-alt-p3, + &.rsp-alt-p4 { display: inline-block; } } |