Merge "Adding clone feature to Transfer Table"

This commit is contained in:
Jenkins 2015-08-28 13:05:37 +00:00 committed by Gerrit Code Review
commit 2096671dbe
11 changed files with 538 additions and 464 deletions

View File

@ -1,69 +0,0 @@
<table st-table="trModel.displayedAllocated" st-safe-src="trModel.allocated"
hz-table class="table-striped table-rsp table-detail modern">
<thead>
<tr>
<!-- <th class="reorder"></th> -->
<th class="expander"></th>
<th class="rsp-p1">Name</th>
<th class="rsp-p1">VCPUs</th>
<th class="rsp-p1">RAM</th>
<th class="rsp-p2">Total Disk</th>
<th class="rsp-p2">Root Disk</th>
<th class="rsp-p3">Ephemeral Disk</th>
<th class="rsp-p3">Public</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-if="trModel.allocated.length === 0">
<td colspan="10">
<div class="no-rows-help">
{$ ::trCtrl.helpText.noneAllocText $}
</div>
</td>
</tr>
<tr ng-repeat-start="row in trModel.displayedAllocated track by row.id">
<!-- lr-drag-data="trModel.displayedAllocated" lr-drag-src="reorder"
lr-drop-target="reorder" lr-drop-success="trCtrl.updateAllocated(e, item, collection)"> -->
<!-- <td class="reorder">
<span class="fa fa-sort" title="{$ ::trCtrl.helpText.orderText $}"></span>
{$ $index + 1 $}
</td> -->
<td class="expander">
<span class="fa fa-chevron-right" hz-expand-detail
title="{$ ::trCtrl.helpText.expandDetailsText $}"></span>
</td>
<td class="rsp-p1">{$ row.name $}</td>
<td class="rsp-p1">{$ row.vcpus $}</td>
<td class="rsp-p1">{$ row.ram $}</td>
<td class="rsp-p2">{$ row.totalDisk $}</td>
<td class="rsp-p2">{$ row.rootDisk $}</td>
<td class="rsp-p3">{$ row.ephemeralDisk $}</td>
<td class="rsp-p3">{$ row.isPublic $}</td>
<td class="action-col">
<action-list>
<action action-classes="'btn btn-sm btn-default'"
callback="trCtrl.deallocate" item="row">
<span class="fa fa-minus"></span>
</action>
</action-list>
</td>
</tr>
<tr ng-repeat-end class="detail-row">
<!-- <td></td> -->
<td></td>
<td colspan="7" class="detail">
<ul>
<li>Name: {$ row.name $}</li>
<li>Name: {$ row.vcpus $}</li>
<li>Name: {$ row.ram $}</li>
<li>Name: {$ row.totalDisk $}</li>
<li>Name: {$ row.rootDisk $}</li>
<li>Name: {$ row.ephemeralDisk $}</li>
<li>Name: {$ row.isPublic $}</li>
</ul>
</td>
<td></td>
</tr>
</tbody>
</table>

View File

@ -1,88 +0,0 @@
<table st-table="trModel.displayedAvailable" st-safe-src="trModel.available"
hz-table class="table-striped table-rsp table-detail modern">
<thead>
<tr>
<th class="search-header" colspan="10">
<hz-search-bar group-classes="input-group-sm" icon-classes="fa-search">
</hz-search-bar>
</th>
</tr>
<tr>
<!-- <th class="reorder"></th> -->
<th class="expander"></th>
<th st-sort="name" st-sort-default class="rsp-p1">Name</th>
<th st-sort="vcpus" class="rsp-p1">VCPUs</th>
<th st-sort="ram" class="rsp-p1">RAM</th>
<th st-sort="totalDisk" class="rsp-p2">Total Disk</th>
<th st-sort="rootDisk" class="rsp-p2">Root Disk</th>
<th st-sort="ephemeralDisk" class="rsp-p3">Ephemeral Disk</th>
<th st-sort="isPublic" class="rsp-p3">Public</th>
<th class="action-col"></th>
</tr>
</thead>
<tbody>
<tr ng-if="trCtrl.numDisplayedAvailable() === 0">
<td colspan="10">
<div class="no-rows-help">
{$ ::trCtrl.helpText.noneAvailText $}
</div>
</td>
</tr>
<tr ng-repeat-start="row in trModel.displayedAvailable track by row.id" ng-if="!trCtrl.allocatedIds[row.id]">
<!-- <td class="reorder"></td> -->
<td class="expander">
<span class="fa fa-chevron-right" hz-expand-detail
title="{$ ::trCtrl.helpText.expandDetailsText $}"></span>
</td>
<td class="rsp-p1">{$ row.name $}</td>
<td class="rsp-p1">
<span class="invalid fa fa-exclamation-circle"
ng-show="row.errors.vcpus"
popover="{$ row.errors.vcpus $}"
popover-append-to-body="true"
popover-trigger="mouseenter mouseleave" popover-placement="top"></span>
{$ row.vcpus $}
</td>
<td class="rsp-p1">
<span class="invalid fa fa-exclamation-circle"
ng-show="row.errors.ram"
popover="{$ row.errors.ram $}"
popover-append-to-body="true"
popover-trigger="mouseenter mouseleave" popover-placement="top"></span>
{$ row.ram $}
</td>
<td class="rsp-p2">{$ row.totalDisk $}</td>
<td class="rsp-p2">{$ row.rootDisk $}</td>
<td class="rsp-p3">{$ row.ephemeralDisk $}</td>
<td class="rsp-p3">{$ row.isPublic $}</td>
<td class="action-col">
<action-list button-tooltip="row.warningMessage"
bt-model="trCtrl.tooltipModel" bt-disabled="!row.disabled"
warning-classes="'invalid'">
<notifications>
<span class="fa fa-exclamation-circle invalid" ng-show="row.disabled"></span>
</notifications>
<action action-classes="'btn btn-sm btn-default'"
callback="trCtrl.allocate" item="row" disabled="row.disabled">
<span class="fa fa-plus"></span>
</action>
</action-list>
</td>
</tr>
<tr ng-repeat-end class="detail-row" ng-if="!trCtrl.allocatedIds[row.id]">
<!-- <td class="reorder"></td> -->
<td></td>
<td colspan="8" class="detail">
<ul>
<li>Name: {$ row.name $}</li>
<li>Name: {$ row.vcpus $}</li>
<li>Name: {$ row.ram $}</li>
<li>Name: {$ row.totalDisk $}</li>
<li>Name: {$ row.rootDisk $}</li>
<li>Name: {$ row.ephemeralDisk $}</li>
<li>Name: {$ row.isPublic $}</li>
</ul>
</td>
</tr>
</tbody>
</table>

View File

@ -0,0 +1,41 @@
<transfer-table tr-model="tableData" limits="limits">
<available>
<table st-table="tableData.available" hz-table>
<thead>
<tr>
<th>Animal</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in tableData.available"
ng-if="!trCtrl.allocatedIds[row.id]">
<td>{$ row.animal $}</td>
<td>
<action-list>
<action callback="trCtrl.allocate" item="row">x</action>
</action-list>
</td>
</tr>
</tbody>
</table>
</available>
<allocated>
<table st-table="tableData.allocated" hz-table>
<thead>
<tr>
<th>Animal</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="alRow in tableData.allocated">
<td>{$ alRow.animal $}</td>
<td>
<action-list>
<action callback="trCtrl.deallocate" item="alRow">x</action>
</action-list>
</td>
</tr>
</tbody>
</table>
</allocated>
</transfer-table>

View File

@ -0,0 +1,20 @@
<transfer-table tr-model="tableData" clone-content>
<table st-table="$displayItems" st-safe-src="$sourceItems">
<tr>
<th>ID</th>
<th>Animal</th>
<th>Action</th>
</tr>
<tr ng-repeat="item in $displayedItems"
ng-if="$isAllocatedTable || ($isAvailableTable && !trCtrl.allocatedIds[item.id])">
<td>{$ item.id $}</td>
<td>{$ item.animal $}</td>
<td>
<action-list>
<action ng-if="$isAllocatedTable" callback="trCtrl.deallocate" item="item">-</action>
<action ng-if="$isAvailableTable" callback="trCtrl.allocate" item="item">+</action>
</action-list>
</td>
</tr>
</table>
</transfer-table>

View File

@ -1,5 +1,6 @@
/*
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -22,7 +23,6 @@
.controller('transferTableController', TransferTableController);
TransferTableController.$inject = [
'horizon.framework.widgets.basePath',
'$scope',
'$timeout',
'$parse',
@ -38,60 +38,33 @@
* @description
* The `transferTableController` controller provides functions for allocating
* and deallocating to and from the 'allocated' array, respectively.
* This controller can be accessed through `trCtrl`.
*
* This controller can be accessed through `trCtrl`. See examples below.
*
* Functions and objects available:
*
* allocate - add row to allocated array
* Provide this as callback for the allocate button
* <action-list>
* <action callback="trCtrl.allocate" item="row"></action>
* </action-list>
*
* deallocate - remove row from allocated array
* Provide this as callback for the deallocate button
* <action-list>
* <action callback="trCtrl.deallocate" item="row"></action>
* </action-list>
*
* updateAllocated - update allocated array after re-order
* This is needed if drag and drop re-ordering is enabled in
* the allocated table.
* <table st-table="displayedAllocated" st-safe-src="allocated"
* lr-drag-data="displayedAllocated"
* lr-drag-src="reorder" lr-drop-target="reorder"
* lr-drop-success="trCtrl.updateAllocated(e, item, collection)">
* ... table definition ...
* </table>
*
* tooltipModel - custom warning tooltip model
* Use this with the allocate button (action-list)
* <action-list button-tooltip bt-model="trCtrl.tooltipModel">
* <action>...</action>
* </action-list>
* The data model assumes four arrays: allocated, displayedAllocated,
* available, and displayedAvailable. Smart-Table requires additional
* 'displayed' arrays for sorting and re-ordering. Of these four arrays, only
* allocated is required. The remaining arrays are populated for you if they
* are not present.
*
* @example
* ```
* var availableItems = [
* { id: 'u1', username: 'User 1', disabled: true, warnings: { username: 'Invalid!' } },
* { id: 'u2', username: 'User 2', disabled: true, warningMessage: 'Invalid!' },
* { id: 'u3', username: 'User 3' }
* ];
* $scope.model = { available: availableItems };
* $scope.limits = { maxAllocation: -1 };
* ```
* For usage example, see the transfer-table.example.html file.
*/
function TransferTableController(path, $scope, $timeout, $parse, $attrs, $log, helpText, limits) {
function TransferTableController($scope, $timeout, $parse, $attrs, $log, helpText, limits) {
var trModel = $parse($attrs.trModel)($scope);
var trHelpText = $parse($attrs.helpText)($scope);
var trLimits = $parse($attrs.limits)($scope);
if (!angular.isArray(trModel.allocated)) {
$log.error('Allocated is not an array as required.');
}
var ctrl = this;
ctrl.allocatedIds = {};
ctrl.available = {
sourceItems: trModel.available,
displayedItems: trModel.displayedAvailable
};
ctrl.allocated = {
sourceItems: trModel.allocated,
displayedItems: trModel.displayedAllocated
};
ctrl.allocate = allocate;
ctrl.deallocate = deallocate;
ctrl.toggleView = toggleView;
@ -103,19 +76,40 @@
ctrl.numAvailable = numAvailable;
ctrl.views = { allocated: true, available: true };
init();
init(trModel);
//////////
function init() {
function init(model) {
// populate the allocatedIds if allocated source given
if (!angular.isArray(model.available)) {
$log.error('Available is not an array.');
}
if (model.allocated && !angular.isArray(model.allocated)) {
$log.error('Allocated is not an array.');
}
ctrl.available = {
sourceItems: model.available,
displayedItems: model.displayedAvailable ? model.displayedAvailable : []
};
ctrl.allocated = {
sourceItems: model.allocated ? model.allocated : [],
displayedItems: model.displayedAllocated ? model.displayedAllocated : []
};
ctrl.allocatedIds = {};
angular.forEach(ctrl.allocated.sourceItems, function(item) {
ctrl.allocatedIds[item.id] = true;
});
}
function allocate(item) {
// we currently don't have to check for item uniqueness
// because we are using the ng-repeat track by
// Add to allocated only if limit not reached
if (ctrl.limits.maxAllocation < 0 ||
ctrl.limits.maxAllocation > ctrl.allocated.sourceItems.length) {

View File

@ -0,0 +1,201 @@
/*
* Copyright 2015 IBM Corp.
*
* 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('transfer-table controller', function() {
beforeEach(module('templates'));
beforeEach(module('smart-table'));
beforeEach(module('horizon.framework'));
beforeEach(module('horizon.framework.widgets'));
beforeEach(module('horizon.framework.widgets.table'));
beforeEach(module('horizon.framework.widgets.transfer-table'));
var log, params;
beforeEach(module(function($provide) {
// we will mock scope and timeout in this test
// because we aren't concern with rendering results
var scope = { $apply: angular.noop };
var timeout = function(fn) { fn(); };
// we will mock parse and attrs
// because we want to control the parameters
log = { error: function() {} };
var attrs = angular.noop;
var parse = function(attr) {
return function(scope) {
return attr ? attr : {};
};
};
$provide.value('$scope', scope);
$provide.value('$timeout', timeout);
$provide.value('$parse', parse);
$provide.value('$attrs', attrs);
$provide.value('$log', log);
}));
beforeEach(inject(function($injector) {
params = {
'$scope': $injector.get('$scope'),
'$timeout': $injector.get('$timeout'),
'$parse': $injector.get('$parse'),
'$attrs': $injector.get('$attrs'),
'$log': $injector.get('$log'),
'helpText': {},
'limits': { maxAllocation: 1 }
};
}));
function generateItems(count) {
var itemList = [];
for (var index = 0; index < count; index++) {
itemList[index] = { id: index };
}
return itemList;
}
//////////
describe('initialization code', function() {
var controllerProvider;
beforeEach(inject(function($controller) {
controllerProvider = $controller;
spyOn(log, 'error');
}));
it('should log error on init with bad model', testBadModel);
it('should initialize IDs with good model', testGoodModel);
function testBadModel() {
params.$attrs = { trModel: { available: 'abc', allocated: 123 } };
controllerProvider('transferTableController', params);
expect(log.error).toHaveBeenCalled();
expect(log.error.calls.count()).toEqual(2);
}
function testGoodModel() {
var availableCount = 10;
var allocatedCount = 5;
params.$attrs = {
trModel: {
available: generateItems(availableCount),
allocated: generateItems(allocatedCount)
}
};
var trCtrl = controllerProvider('transferTableController', params);
expect(log.error).not.toHaveBeenCalled();
expect(Object.keys(trCtrl.allocatedIds).length).toEqual(allocatedCount);
expect(trCtrl.allocated.sourceItems.length).toEqual(allocatedCount);
expect(trCtrl.available.sourceItems.length).toEqual(availableCount);
}
});
describe('core functions', function() {
var trCtrl;
beforeEach(inject(function($controller) {
trCtrl = $controller('transferTableController', params);
}));
it('should always allocate if allocation limit is negative', testLimitNegative);
it('should not allocate if allocation limit is reached', testLimitMaxed);
it('should swap out allocated item if allocation limit is one', testLimitOne);
it('should deallocate by moving item from allocated to available list', testDeallocate);
it('should update allocated on reorder', testUpdateAllocated);
it('should toggle the views correctly on request', testToggleView);
//////////
function testToggleView() {
trCtrl.toggleView('allocated');
trCtrl.toggleView('available');
expect(trCtrl.views.allocated).toEqual(false);
expect(trCtrl.views.available).toEqual(false);
trCtrl.toggleView('allocated');
trCtrl.toggleView('available');
expect(trCtrl.views.allocated).toEqual(true);
expect(trCtrl.views.available).toEqual(true);
}
function testLimitNegative() {
var itemCount = 10;
trCtrl.limits.maxAllocation = -1;
trCtrl.available.sourceItems = generateItems(itemCount);
for (var index = 0; index < itemCount; index++) {
trCtrl.allocate(trCtrl.available.sourceItems[index]);
}
expect(Object.keys(trCtrl.allocatedIds).length).toEqual(itemCount);
expect(trCtrl.allocated.sourceItems.length).toEqual(itemCount);
expect(trCtrl.numAllocated()).toEqual(itemCount);
expect(trCtrl.numAvailable()).toEqual(0);
}
function testLimitMaxed() {
var itemCount = 10;
trCtrl.limits.maxAllocation = 5;
trCtrl.available.sourceItems = generateItems(itemCount);
for (var index = 0; index < itemCount; index++) {
trCtrl.allocate(trCtrl.available.sourceItems[index]);
}
expect(Object.keys(trCtrl.allocatedIds).length).toEqual(trCtrl.limits.maxAllocation);
expect(trCtrl.allocated.sourceItems.length).toEqual(trCtrl.limits.maxAllocation);
expect(trCtrl.numAllocated()).toEqual(trCtrl.limits.maxAllocation);
expect(trCtrl.numAvailable()).toEqual(itemCount - trCtrl.limits.maxAllocation);
}
function testLimitOne() {
var itemCount = 10;
trCtrl.limits.maxAllocation = 1;
trCtrl.available.sourceItems = generateItems(itemCount);
for (var index = 0; index < itemCount; index++) {
trCtrl.allocate(trCtrl.available.sourceItems[index]);
}
expect(Object.keys(trCtrl.allocatedIds).length).toEqual(trCtrl.limits.maxAllocation);
expect(trCtrl.allocated.sourceItems.length).toEqual(trCtrl.limits.maxAllocation);
expect(trCtrl.numAllocated()).toEqual(trCtrl.limits.maxAllocation);
expect(trCtrl.numAvailable()).toEqual(itemCount - trCtrl.limits.maxAllocation);
// when limit is one, we swap out items
// the ID of the last item allocated should be present
expect('9' in trCtrl.allocatedIds).toEqual(true);
}
function testDeallocate() {
trCtrl.available.sourceItems = generateItems(1);
var item = trCtrl.available.sourceItems[0];
trCtrl.allocate(item);
trCtrl.deallocate(item);
expect(item.id in trCtrl.allocatedIds).toEqual(false);
expect(trCtrl.allocated.sourceItems.indexOf(item)).toEqual(-1);
expect(trCtrl.numAllocated()).toEqual(0);
expect(trCtrl.numAvailable()).toEqual(1);
}
function testUpdateAllocated() {
var orderedItems = [1,2,3,4];
trCtrl.updateAllocated(null, null, orderedItems);
expect(trCtrl.allocated.sourceItems).toEqual(orderedItems);
expect(trCtrl.numAllocated()).toEqual(orderedItems.length);
}
}); // end of core functions
}); // end of transfer-table controller
})(); // end of IIFE

View File

@ -1,5 +1,6 @@
/*
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
* Copyright 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,111 +21,25 @@
/**
* @ngdoc directive
* @name horizon.framework.widgets.transfer-table.directive:transferTable
* @element
* @restrict E
*
* @param {object} trModel Table data model (required)
* @param {object} helpText Help text (optional)
* @param {object} limits Max allocation (optional, default: 1)
*
* @description
* The `transferTable` directive generates two tables and allows the
* transfer of rows between the two tables. Help text and maximum
* allocation are configurable. The defaults for help text and limits
* are described above (constants: helpContent and limits).
*
* The data model requires 4 arrays: allocated, displayedAllocated,
* available, and displayedAvailable. Smart-Table requires 'displayed'
* arrays for sorting and re-ordering.
* Refer to transfer-table.controller.js for data model description.
* Refer to transfer-table.example.html to see it use as a directive.
*
* Data model:
* ```
* $scope.available = [
* { id: 'u1', username: 'User 1', disabled: true, warnings: { username: 'Invalid!' } },
* { id: 'u2', username: 'User 2', disabled: true, warningMessage: 'Invalid!' },
* { id: 'u3', username: 'User 3' }
* ];
*
* $scope.allocated = [];
*
* $scope.tableData = {
* available: $scope.available,
* displayedAvailable: [].concat($scope.available),
* allocated: $scope.allocated,
* displayedAllocated: [].concat($scope.allocated)
* };
*
* $scope.helpText = {
* availHelpText: 'Select one from the list'
* };
*
* $scope.limits = {
* maxAllocation: -1
* };
* ```
* Optional arguments for each row in table data model:
* disabled - disables the allocate button in available table
* warningMessage - the message to show in warning tooltip
* warnings - show warning text and icon next to value in table cell
*
* @restrict E
*
* @example
* There are 2 examples available as a template: allocated.html.example and
* available.html.example. The `transferTableController` methods are available
* via `trCtrl`. For example, for allocation, use `trCtrl.allocate`.
* ```
* <transfer-table tr-model="tableData" help-text="helpText" limits="limits">
* <allocated>
* <table st-table="tableData.displayedAllocated"
* st-safe-src="tableData.allocated" hz-table>
* <thead>... header definition ...</thead>
* <tbody>
* <tr ng-repeat-start="row in tableData.displayedAllocated">
* <td>{$ row.username $}</td>
* ... more cell definitions
* <td action-col>
* <action-list>
* <action action-classes="'btn btn-sm btn-default'"
* callback="trCtrl.deallocate" item="row">
* <span class="fa fa-minus"></span>
* </action>
* </action-list>
* </td>
* </tr>
* <tr ng-repeat-end class="detail-row">
* <td class="detail">
* ... detail row definition ...
* </td>
* <td></td>
* </tr>
* </tbody>
* </table>
* </allocated>
* <available>
* <table st-table="tableData.displayedAvailable"
* st-safe-src="tableData.available" hz-table>
* <thead>... header definition ...</thead>
* <tbody>
* <tr ng-repeat-start="row in tableData.displayedAvailable">
* <td>{$ row.username $}</td>
* ... more cell definitions
* <td action-col>
* <action-list>
* <action action-classes="'btn btn-sm btn-default'"
* callback="trCtrl.allocate" item="row">
* <span class="fa fa-minus"></span>
* </action>
* </action-list>
* </td>
* </tr>
* <tr ng-repeat-end class="detail-row">
* <td class="detail">
* ... detail row definition ...
* </td>
* </tr>
* </tbody>
* </table>
* </available>
* </transfer-table>
* ```
*/
angular
.module('horizon.framework.widgets.transfer-table')
@ -150,11 +65,28 @@
function link(scope, element, attrs, ctrl, transclude) {
var allocated = element.find('.transfer-allocated');
var available = element.find('.transfer-available');
transclude(scope, function(clone) {
allocated.append(clone.filter('allocated'));
available.append(clone.filter('available'));
});
if ('cloneContent' in attrs) {
var allocatedScope = scope.$new();
allocatedScope.$displayedItems = ctrl.allocated.displayedItems;
allocatedScope.$sourceItems = ctrl.allocated.sourceItems;
allocatedScope.$isAllocatedTable = true;
transclude(allocatedScope, function(clone) {
allocated.append(clone.filter('table'));
});
var availableScope = scope.$new();
availableScope.$displayedItems = ctrl.available.displayedItems;
availableScope.$sourceItems = ctrl.available.sourceItems;
availableScope.$isAvailableTable = true;
transclude(availableScope, function(clone) {
available.append(clone.filter('table'));
});
}
else {
transclude(scope, function(clone) {
allocated.append(clone.filter('allocated'));
available.append(clone.filter('available'));
});
}
}
}
})();

View File

@ -0,0 +1,126 @@
/*
* (c) Copyright 2015 Hewlett-Packard Development Company, L.P.
* Copyright 2015 IBM Corp.
*
* 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('transfer-table directive', function() {
beforeEach(module('templates'));
beforeEach(module('smart-table'));
beforeEach(module('horizon.framework'));
var $templateCache, $compile, $scope, basePath;
var available = [
{ id: '1', animal: 'cat' },
{ id: '2', animal: 'dog' },
{ id: '3', animal: 'fish' }
];
beforeEach(inject(function($injector) {
basePath = $injector.get('horizon.framework.widgets.basePath');
$templateCache = $injector.get('$templateCache');
$compile = $injector.get('$compile');
$scope = $injector.get('$rootScope').$new();
$scope.tableData = {
available: available,
allocated: [],
displayedAvailable: [].concat(available),
displayedAllocated: []
};
}));
describe('render with allocated and available tags', function() {
var $element;
beforeEach(inject(function($injector) {
var path = 'transfer-table/transfer-table.basic.mock.html';
var markup = $templateCache.get(basePath + path);
$element = angular.element(markup);
$compile($element)($scope);
$scope.$apply();
}));
it('should place allocated and available element correctly', testContent);
it('should have 0 allocated rows', testAllocatedRows);
it('should have 3 available rows', testAvailableRows);
///////////
function testContent() {
expect($element.find('table').length).toBe(2);
expect($element.find('.transfer-allocated > allocated').length).toBe(1);
expect($element.find('.transfer-available > available').length).toBe(1);
}
function testAllocatedRows() {
expect($element.find('.transfer-allocated tr[ng-repeat]').length).toBe(0);
}
function testAvailableRows() {
expect($element.find('.transfer-available tr[ng-repeat]').length).toBe(3);
}
});
describe('clone content', function() {
var $element;
beforeEach(inject(function($injector) {
var path = 'transfer-table/transfer-table.clone.mock.html';
var markup = $templateCache.get(basePath + path);
$element = angular.element(markup);
$compile($element)($scope);
$scope.$apply();
}));
it('should contain 2 table elements', testCloneContent);
it('should have 0 allocated rows', testAllocatedRows);
it('should have 3 available rows', testAvailableRows);
it('should create 2 new scopes', testNewScopes);
///////////
function testCloneContent() {
expect($element.find('table').length).toBe(2);
expect($element.find('.transfer-allocated > table').length).toBe(1);
expect($element.find('.transfer-available > table').length).toBe(1);
}
function testAllocatedRows() {
expect($element.find('.transfer-allocated tr[ng-repeat]').length).toBe(0);
}
function testAvailableRows() {
expect($element.find('.transfer-available tr[ng-repeat]').length).toBe(3);
}
function testNewScopes() {
var allocatedScope = $element.find('.transfer-allocated > table').scope();
var availableScope = $element.find('.transfer-available > table').scope();
expect(allocatedScope.$isAllocatedTable).toBe(true);
expect(allocatedScope.$sourceItems).toBe($scope.tableData.allocated);
expect(allocatedScope.$displayedItems).toBe($scope.tableData.displayedAllocated);
expect(availableScope.$isAvailableTable).toBe(true);
expect(availableScope.$sourceItems).toBe($scope.tableData.available);
expect(availableScope.$displayedItems).toBe($scope.tableData.displayedAvailable);
}
});
}); // end of transfer-table directive
})(); // end of IIFE

View File

@ -0,0 +1,50 @@
<transfer-table tr-model="data" clone-content>
<table st-table="$displayedItems" st-safe-src="$sourceItems"
hz-table class="table-striped table-rsp table-detail modern">
<thead>
<!-- show search bar only for available table -->
<tr ng-show="$isAvailableTable">
<th class="search-header" colspan="10">
<hz-search-bar group-classes="input-group-sm" icon-classes="fa-search">
</hz-search-bar>
</th>
</tr>
<tr>
<th st-sort="name" st-sort-default class="rsp-p1" translate>Name</th>
<th st-sort="description" class="rsp-p2" translate>description</th>
<th class="action-col"></th>
</tr>
</thead>
<tbody>
<!-- no item message -->
<tr ng-if="trCtrl.numAvailable() === 0">
<td colspan="10">
<div class="no-rows-help" translate>
No available items
</div>
</td>
</tr>
<tr ng-repeat="item in $displayedItems track by item.id"
ng-if="$isAllocatedTable || ($isAvailableTable && !trCtrl.allocatedIds[item.id])">
<td class="rsp-p1">{$ item.name $}</td>
<th class="rsp-p2">{$ item.description $}</th>
<td class="action-col">
<action-list>
<!-- conditional actions -->
<action ng-if="$isAllocatedTable"
action-classes="'btn btn-sm btn-default'"
callback="trCtrl.deallocate" item="item">
<span class="fa fa-minus"></span>
</action>
<action ng-if="$isAvailableTable"
action-classes="'btn btn-sm btn-default'"
callback="trCtrl.allocate" item="item">
<span class="fa fa-plus"></span>
</action>
</action-list>
</td>
</tr>
</tbody>
</table>
</transfer-table>

View File

@ -0,0 +1,26 @@
/*
* (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('horizon.framework.widgets.transfer-table module', function() {
it('should have been defined', function() {
expect(angular.module('horizon.framework.widgets.transfer-table')).toBeDefined();
});
});
})();

View File

@ -1,159 +0,0 @@
/*
* (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('horizon.framework.widgets.transfer-table module', function() {
it('should have been defined', function() {
expect(angular.module('horizon.framework.widgets.transfer-table')).toBeDefined();
});
});
describe('transfer-table directive', function() {
var $scope, $timeout, $element;
beforeEach(module('templates'));
beforeEach(module('smart-table'));
beforeEach(module('horizon.framework'));
describe('max 1 allocation', function() {
beforeEach(inject(function($injector) {
var $templateCache = $injector.get('$templateCache');
var basePath = $injector.get('horizon.framework.widgets.basePath');
var $compile = $injector.get('$compile');
$scope = $injector.get('$rootScope').$new();
$timeout = $injector.get('$timeout');
var available = [
{ id: '1', animal: 'cat' },
{ id: '2', animal: 'dog' },
{ id: '3', animal: 'fish' }
];
$scope.tableData = {
available: available,
allocated: [],
displayedAvailable: [].concat(available),
displayedAllocated: []
};
var markup = $templateCache.get(basePath + 'transfer-table/transfer-table.max-1.mock.html');
$element = angular.element(markup);
$compile($element)($scope);
$scope.$apply();
}));
it('should have 0 allocated rows', function() {
expect($element.find('.transfer-allocated tr[ng-repeat]').length).toBe(0);
});
it('should have 3 available rows', function() {
expect($element.find('.transfer-available tr[ng-repeat]').length).toBe(3);
});
it('should have 1 allocated row if first available row allocated', function() {
$element.find('.transfer-available tbody tr:first-child button').click();
expect($element.find('.transfer-allocated tr[ng-repeat]').length).toBe(1);
});
it('should swap allocated row if one already exists', function() {
var available = $element.find('.transfer-available tbody tr:first-child button');
available.click();
// After first click, should be one allocated row
var allocated = $element.find('.transfer-allocated tr[ng-repeat]');
expect(allocated.length).toBe(1);
expect(allocated.find('td:nth-child(1)').text().trim()).toBe('cat');
// After second click, previously allocated row swapped out for new one
available = $element.find('.transfer-available tbody tr:first-child button');
available.click();
$timeout.flush();
allocated = $element.find('.transfer-allocated tr[ng-repeat]');
expect(allocated.length).toBe(1);
expect(allocated.find('td:nth-child(1)').text().trim()).toBe('dog');
});
it('should have 0 allocated row if allocated row de-allocated', function() {
$element.find('.transfer-allocated tr[ng-repeat] button').click();
expect($element.find('.transfer-allocated tr[ng-repeat]').length).toBe(0);
expect($element.find('.transfer-available tr[ng-repeat]').length).toBe(3);
});
});
describe('max 2 allocations', function() {
beforeEach(inject(function($injector) {
var $templateCache = $injector.get('$templateCache');
var basePath = $injector.get('horizon.framework.widgets.basePath');
var $compile = $injector.get('$compile');
$scope = $injector.get('$rootScope').$new();
var available = [
{ id: '1', animal: 'cat' },
{ id: '2', animal: 'dog' },
{ id: '3', animal: 'fish' }
];
$scope.tableData = {
available: available,
allocated: [],
displayedAvailable: [].concat(available),
displayedAllocated: []
};
$scope.limits = {
maxAllocation: 2
};
var markup = $templateCache.get(basePath + 'transfer-table/transfer-table.max-2.mock.html');
$element = angular.element(markup);
$compile($element)($scope);
$scope.$apply();
}));
it('should have 0 allocated rows', function() {
expect($element.find('.transfer-allocated tr[ng-repeat]').length).toBe(0);
});
it('should have 3 available rows', function() {
expect($element.find('.transfer-available tr[ng-repeat]').length).toBe(3);
});
it('should allow only 2 allocated rows', function() {
$element.find('.transfer-available tbody tr:first-child button').click();
$element.find('.transfer-available tbody tr:first-child button').click();
var lastRow = $element.find('.transfer-available tbody tr:first-child');
lastRow.find('.action-col .btn').click();
expect($element.find('.transfer-allocated tr[ng-repeat]').length).toBe(2);
expect($element.find('.transfer-available tr[ng-repeat]').length).toBe(1);
// The last row should not have been added
expect(lastRow.find('td:nth-child(1)').text().trim()).toBe('fish');
});
});
});
})();