horizon/horizon/static/framework/widgets/transfer-table/transfer-table.controller.js

246 lines
7.7 KiB
JavaScript

/*
* (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';
angular
.module('horizon.framework.widgets.transfer-table')
.controller('transferTableController', TransferTableController);
TransferTableController.$inject = [
'$scope',
'$timeout',
'$parse',
'$attrs',
'$log',
'horizon.framework.widgets.transfer-table.events',
'horizon.framework.widgets.transfer-table.helpText',
'horizon.framework.widgets.transfer-table.limits',
'horizon.framework.util.q.extensions'
];
/**
* @ngdoc controller
* @name horizon.framework.widgets.transfer-table.controller:transferTableController
* @description
* The `transferTableController` controller provides functions for allocating
* and deallocating to and from the 'allocated' array, respectively.
* This controller can be accessed through `trCtrl`.
*
* 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(
$scope,
$timeout,
$parse,
$attrs,
$log,
events,
helpText,
limits,
qExtensions
) {
var trModel = $parse($attrs.trModel)($scope);
var trHelpText = $parse($attrs.helpText)($scope);
var trLimits = $parse($attrs.limits)($scope);
var ctrl = this;
ctrl.allocate = allocate;
ctrl.deallocate = deallocate;
ctrl.itemActions = getItemActions();
ctrl.toggleView = toggleView;
ctrl.updateAllocated = updateAllocated;
ctrl.numAllocated = numAllocated;
ctrl.helpText = angular.extend({}, helpText, trHelpText);
ctrl.limits = angular.extend({}, limits, trLimits);
ctrl.numAvailable = numAvailable;
ctrl.views = { allocated: true, available: true };
// if available transfer table is updated dynamically (e.g. based on a dropdown
// selection like in Launch Instance Boot Source), we need to update our data accordingly
var tablesChangedWatcher = $scope.$on(events.TABLES_CHANGED, onTablesChanged);
$scope.$on('$destroy', function () {
tablesChangedWatcher();
});
init(trModel);
//////////
function getItemActions() {
return [{
template: {
text: '',
actionClasses: 'fa fa-arrow-up'
},
service: {
allowed: function allocationAllowed(item) {
var allocatable = item && !ctrl.allocatedIds[item.id];
return qExtensions.booleanAsPromise(allocatable);
},
perform: function performAllocation(item) {
allocate(item);
return qExtensions.booleanAsPromise(true);
}
}
}, {
template: {
text: '',
actionClasses: 'fa fa-arrow-down'
},
service: {
allowed: function deallocationAllowed(item) {
var deallocatable = item && ctrl.allocatedIds[item.id];
return qExtensions.booleanAsPromise(deallocatable);
},
perform: function performDeallocation(item) {
deallocate(item);
return qExtensions.booleanAsPromise(true);
}
}
}];
}
function init(model) {
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 = {};
markAllocatedItems();
$scope.$watchCollection(getAllocated, markAllocatedItems);
}
function getAllocated() {
return ctrl.allocated.sourceItems;
}
function markAllocatedItems() {
angular.forEach(ctrl.allocated.sourceItems, function flag(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) {
ctrl.allocated.sourceItems.push(item);
ctrl.allocatedIds[item.id] = true;
// Swap out items if only one allocation allowed
} else if (ctrl.limits.maxAllocation === 1) {
var temp = ctrl.allocated.sourceItems.pop();
delete ctrl.allocatedIds[temp.id];
// When swapping out, Smart-Table $watch is
// not detecting change so timeout is used as workaround.
$timeout(function() {
ctrl.allocated.sourceItems.push(item);
ctrl.allocatedIds[item.id] = true;
$scope.$apply();
}, 0, false);
}
}
// move item from allocated to available
function deallocate(item) {
var index = ctrl.allocated.sourceItems.indexOf(item);
if (index >= 0) {
ctrl.allocated.sourceItems.splice(index, 1);
delete ctrl.allocatedIds[item.id];
}
}
// update allocated when users re-order via drag-and-drop
function updateAllocated(event, item, orderedItems) {
ctrl.allocated.sourceItems.splice(0, ctrl.allocated.sourceItems.length);
Array.prototype.push.apply(ctrl.allocated.sourceItems, orderedItems);
}
function onTablesChanged(e, args) {
if (args.data.available) {
ctrl.available.sourceItems = args.data.available;
}
if (args.data.displayedAvailable) {
ctrl.available.displayedItems = args.data.displayedAvailable;
}
if (args.data.allocated) {
ctrl.allocated.sourceItems = args.data.allocated;
ctrl.allocatedIds = {};
ctrl.allocated.sourceItems.forEach(function(item) {
ctrl.allocatedIds[item.id] = true;
});
}
if (args.data.displayedAllocated) {
ctrl.allocated.displayedItems = args.data.displayedAllocated;
}
}
/////////////
function toggleView(view) {
ctrl.views[view] = !ctrl.views[view];
}
function numAllocated() {
return ctrl.allocated.sourceItems.length;
}
function numAvailable() {
return ctrl.available.sourceItems.length - ctrl.allocated.sourceItems.length;
}
}
})();