Merge "Adding clone feature to Transfer Table"
This commit is contained in:
commit
2096671dbe
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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) {
|
||||
|
@ -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
|
@ -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'));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
@ -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
|
@ -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>
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
})();
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
Loading…
Reference in New Issue
Block a user