web/src/main/webapp/my-app/layout/controllers.js
/*
* Licensed to Apereo under one or more contributor license
* agreements. See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* Apereo licenses this file to you 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 the following location:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
'use strict';
define(['angular', 'jquery'], function(angular, $) {
return angular.module('my-app.layout.controllers', [])
/**
* Controller for default view (my-app/layout/partials/default-view.html)
*/
.controller('DefaultViewController',
['$scope', '$location', '$mdMedia', '$localStorage', 'APP_FLAGS',
function($scope, $location, $mdMedia, $localStorage, APP_FLAGS) {
$scope.loading = [];
if (!APP_FLAGS[$localStorage.layoutMode]) {
// Layout mode set weird, reset to default
$localStorage.layoutMode = ($mdMedia('xs') && APP_FLAGS.compact) ?
'compact' : APP_FLAGS.defaultView;
}
$location.path('/' + $localStorage.layoutMode);
}])
.controller('RemoveWidgetController', ['$sessionStorage', '$scope', '$filter', 'layoutService', 'APP_FLAGS', 'marketplaceService',
function($sessionStorage, $scope, $filter, layoutService, APP_FLAGS, marketplaceService) {
var vm = this;
/**
* Capture information about removed widget, then
* pass it up the chain to the layout scope in
* WidgetController
* @param widget {Object} The widget being removed
*/
if (APP_FLAGS.useNewLayout) {
vm.removeWidget = function(widget) {
var fname = APP_FLAGS.useNewLayout ? widget : widget.fname;
// Match layout entry with fname
var result = $filter('filter')($scope.$parent.layout, fname);
var index = $scope.$parent.layout.indexOf(result[0]);
var title;
marketplaceService.getPortlets().then(function(data) {
var marketplaceEntries = $filter('filter')(
$sessionStorage.marketplace, fname
);
if (marketplaceEntries.length > 0) {
// Remove the flag
title = marketplaceEntries[0].title;
} else {
title = fname;
}
var data = {
removedIndex: index,
removedWidget: fname,
title: title
};
$scope.$emit('REMOVE_WIDGET', data);
}).catch(function() {
$log.warn('Could not getPortlets');
});
};
}
if (APP_FLAGS.useOldLayout) {
vm.removeWidget = function(widget) {
// Match layout entry with fname
var result = $filter('filter')($scope.$parent.layout, widget.fname);
var index = $scope.$parent.layout.indexOf(result[0]);
var data = {
removedIndex: index,
removedWidget: widget.fname,
title: widget.title
};
$scope.$emit('REMOVE_WIDGET', data);
};
}
}])
/**
* Widget initialization and sorting for expanded mode widget layout
* (/widget/partials/home-widget-view.html and
* /widget/partials/widget-card.html)
*/
.controller('WidgetController',
['$controller', '$log', '$scope', '$rootScope', '$mdToast',
'$sessionStorage', '$filter', '$mdColors', 'layoutService', 'APP_FLAGS',
function($controller, $log, $scope, $rootScope, $mdToast,
$sessionStorage, $filter, $mdColors, layoutService, APP_FLAGS) {
var vm = this;
$scope.selectedNodeId = '';
$scope.widgetsToRemove = [];
/**
* Set the selected widget in scope to track focus
* @param nodeId {string} The id of the selected widget
*/
$scope.selectNode = function(nodeId) {
$scope.selectedNodeId = nodeId;
};
// default to unauthenticated experience
$scope.guestMode = true;
/**
* Log whenever a widget is moved
* @param eventType {String} Kind of interaction that moved the widget
* @param fname {String} The moved widget's fname
* @param dropIndex {Number} Index where the widget landed
* @param startIndex {Number} (optional) Index before moving the widget
* @return
*/
$scope.logMoveEvent = function(eventType, fname, dropIndex, startIndex) {
switch (eventType) {
case 'dragEnd':
$log.info('Dragged ' + fname + ' to index ' + dropIndex);
break;
case 'keyboardMove':
$log.info('Moved ' + fname + ' from index ' + startIndex +
' to index ' + dropIndex);
break;
default:
return;
}
};
// Listen for removal event
$scope.$on('REMOVE_WIDGET',
/**
* Listen for widget removal event, then show undo toast
* if one is not already showing.
* @param event {Object} The angularjs event object
* @param data {Object} Data about the widget being removed
*/
function(event, data) {
// Remove the widget from layout in scope
$scope.layout.splice(data.removedIndex, 1);
// Track the widget fname for removal upon
// toast timeout
$scope.widgetsToRemove.push(data.removedWidget);
// Dismiss any open toasts (success), then show new one
// eslint-disable-next-line promise/always-return
$mdToast.hide().then(function() {
showConfirmationToast(data);
}).catch(function(error) {
$log.error(error);
});
});
/**
* Show toast message allowing user to confirm or undo
* the removal of a widget from his/her layout.
* @param data {Object} Information about the removed widget
*/
var showConfirmationToast = function(data) {
var widgetTitle = data.title;
var accentColor = '';
if ($sessionStorage.portal.theme) {
// theme already in session, use accent color from it
accentColor =
$mdColors.getThemeColor($sessionStorage.portal.theme.name
+ '-accent');
} else {
// theme not yet in session, use accent color from first theme
accentColor =
$mdColors.getThemeColor($rootScope.THEMES[0].name
+ '-accent');
}
// Configure and show the toast message
// Pass in widget title for toast text display
$mdToast.show({
hideDelay: 4000,
parent: angular.element(document).find('.wrapper__frame-page')[0],
position: 'bottom right',
locals: {
color: accentColor,
removedTitle: widgetTitle
},
bindToController: true,
templateUrl:
require.toUrl('my-app/layout/partials/toast-widget-removal.html'),
controller: function RemoveToastController($scope, $mdToast,
color, removedTitle) {
$scope.accentColorRgb = color;
$scope.removedTitle = removedTitle;
/**
* Resolve show() promise with 'undo'
* Note: a successful timeout or hide() without argument
* resolves with undefined)
*/
$scope.undoRemoveWidget = function() {
// Hide toast message
$mdToast.hide('undo');
};
},
})
// If user clicked undo, do not proceed
.then(function(response) {
if (response === 'undo') {
// Add the removed widget back to the layout
$scope.layout.splice(data.removedIndex, 0, data.removedWidget);
// Delete the last fname added to the removal array
$scope.widgetsToRemove.pop();
} else {
// Save deletion of any widgets in the tracking array
for (var i = 0; i < $scope.widgetsToRemove.length; i++) {
var fname = $scope.widgetsToRemove[i];
saveLayoutRemoval(fname);
// Remove saved deletions from tracking array
$scope.widgetsToRemove.splice(i, 1);
}
}
return response;
})
.catch(function(error) {
$log.error(error);
});
};
if (APP_FLAGS.useNewLayout) {
/**
* Respond to arrow key-presses when focusing a movable list element
* @param widget {Object} The widget trying to move
* @param event {Object} The event object
*/
$scope.moveWithKeyboard = function(widget, event) {
var result = $filter('filter')($scope.$parent.layout, widget);
var currentIndex = $scope.$parent.layout.indexOf(result[0]);
var previousIndex = currentIndex - 1;
var nextIndex = currentIndex + 1;
// left or up
if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {
// stop element from losing focus
event.preventDefault();
// if currentIndex is already 0, do nothing
if (currentIndex !== 0) {
// remove item from the list
$scope.layout.splice(currentIndex, 1);
// reinsert at new index
$scope.layout.splice(previousIndex, 0, widget);
// save new layout order
saveLayoutOrder(previousIndex,
$scope.layout.length,
widget);
// log change
$scope.logMoveEvent( 'keyboardMove',
widget,
previousIndex,
currentIndex);
}
}
// right or down
if (event.key === 'ArrowRight' || event.key === 'ArrowDown') {
// stop screen from scrolling
event.preventDefault();
// if currentIndex is end of the list, do nothing
if (currentIndex !== $scope.layout.length - 1) {
// remove item from the list
$scope.layout.splice(currentIndex, 1);
// reinsert at desired index
$scope.layout.splice(nextIndex, 0, widget);
// save new layout order
saveLayoutOrder(nextIndex, $scope.layout.length, widget);
// log change
$scope.logMoveEvent('keyboardMove',
widget,
nextIndex,
currentIndex);
}
}
};
/**
* Call layout service to save the removal of the widget from the user's
* home layout.
* @param fname {String} The fname of the removed widget
*/
var saveLayoutRemoval = function(fname) {
// Call layout service to persist change
layoutService.removeFromHome(fname).then(function(result) {
if ($sessionStorage.marketplace != null) {
// Filter for fname match in marketplace
var marketplaceEntries = $filter('filter')(
$sessionStorage.marketplace, fname
);
if (marketplaceEntries.length > 0) {
// Remove the flag
marketplaceEntries[0].hasInLayout = false;
}
}
return result;
}).catch(function() {
$log.warn('Problem deleting ' + fname
+ 'from home screen. Try again later.');
});
};
/**
* Move a widget with a drag and drop action
* @param {Object} widget the widget being moved
* @param {Number} dropIndex index of the new location
* @return {Boolean} true if widget moved, false if otherwise
*/
$scope.moveWithDrag = function(widget, dropIndex) {
var result = $filter('filter')($scope.$parent.layout, widget);
var sourceIndex = $scope.$parent.layout.indexOf(result[0]);
$log.info('index:'+sourceIndex+' dropIndex:'+dropIndex);
if (sourceIndex != dropIndex) {
$scope.layout.splice(sourceIndex, 1);
if (dropIndex > sourceIndex) {
dropIndex--;
}
$scope.layout.splice(dropIndex, 0, widget);
saveLayoutOrder(dropIndex, $scope.layout.length, widget);
$scope.logMoveEvent('dragEnd', widget, dropIndex);
return true;
} else {
return false;
}
};
/**
* After a widget is moved, save the new layout using
* the given information
* @param dropIndex {Number} Where the widget ended up
* @param length {Number} Length of the layout array
* @param nodeId {String} ID of the moved widget
*/
var saveLayoutOrder = function(dropIndex, length, fname) {
$scope.selectedNodeId
// identify previous and next widgets
var previousNodeId =
dropIndex !== 0 ? $scope.layout[dropIndex - 1].fname : '';
var nextNodeId =
dropIndex !== length - 1 ? $scope.layout[dropIndex + 1].fname : '';
// call layout service to save
layoutService.moveStuff(dropIndex,
length, fname, previousNodeId, nextNodeId);
};
}
if (APP_FLAGS.useOldLayout) {
/**
* Respond to arrow key-presses when focusing a movable list element
* @param widget {Object} The widget trying to move
* @param event {Object} The event object
*/
$scope.moveWithKeyboard = function(widget, event) {
// Get index independent of ng-repeat to avoid filter bugs
var currentIndex =
findLayoutIndex($scope.layout, 'nodeId', widget.nodeId);
var previousIndex = currentIndex - 1;
var nextIndex = currentIndex + 1;
// left or up
if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {
// stop element from losing focus
event.preventDefault();
// if currentIndex is already 0, do nothing
if (currentIndex !== 0) {
// remove item from the list
$scope.layout.splice(currentIndex, 1);
// reinsert at new index
$scope.layout.splice(previousIndex, 0, widget);
// save new layout order
saveLayoutOrder(previousIndex,
$scope.layout.length,
widget.nodeId);
// log change
$scope.logMoveEvent( 'keyboardMove',
widget.fname,
previousIndex,
currentIndex);
}
}
// right or down
if (event.key === 'ArrowRight' || event.key === 'ArrowDown') {
// stop screen from scrolling
event.preventDefault();
// if currentIndex is end of the list, do nothing
if (currentIndex !== $scope.layout.length - 1) {
// remove item from the list
$scope.layout.splice(currentIndex, 1);
// reinsert at desired index
$scope.layout.splice(nextIndex, 0, widget);
// save new layout order
saveLayoutOrder(nextIndex, $scope.layout.length, widget.nodeId);
// log change
$scope.logMoveEvent('keyboardMove',
widget.fname,
nextIndex,
currentIndex);
}
}
};
/**
* Call layout service to save the removal of the widget from the user's
* home layout.
* @param fname {String} The fname of the removed widget
*/
var saveLayoutRemoval = function(fname) {
// Call layout service to persist change
layoutService.removeFromHome(fname)
.success(function() {
// Clear marketplace flag
if ($sessionStorage.marketplace != null) {
// Filter for fname match in marketplace
var marketplaceEntries = $filter('filter')(
$sessionStorage.marketplace, fname
);
if (marketplaceEntries.length > 0) {
// Remove the flag
marketplaceEntries[0].hasInLayout = false;
}
}
})
.error(function(error) {
$log.debug('Problem deleting ' + fname
+ 'from home screen. Try again later.');
$log.debug(error);
});
};
/**
* Move a widget with a drag and drop action
* @param {Object} widget the widget being moved
* @param {Number} dropIndex index of the new location
* @return {Boolean} true if widget moved, false if otherwise
*/
$scope.moveWithDrag = function(widget, dropIndex) {
var sourceIndex =
findLayoutIndex($scope.layout, 'nodeId', widget.nodeId);
$log.info('index:'+sourceIndex+' dropIndex:'+dropIndex);
if (sourceIndex != dropIndex) {
$scope.layout.splice(sourceIndex, 1);
if (dropIndex > sourceIndex) {
dropIndex--;
}
$scope.layout.splice(dropIndex, 0, widget);
saveLayoutOrder(dropIndex, $scope.layout.length, widget.nodeId);
$scope.logMoveEvent('dragEnd', widget.fname, dropIndex);
return true;
} else {
return false;
}
};
/**
* After a widget is moved, save the new layout using
* the given information
* @param dropIndex {Number} Where the widget ended up
* @param length {Number} Length of the layout array
* @param nodeId {String} ID of the moved widget
*/
var saveLayoutOrder = function(dropIndex, length, nodeId) {
// identify previous and next widgets
var previousNodeId =
dropIndex !== 0 ? $scope.layout[dropIndex - 1].nodeId : '';
var nextNodeId =
dropIndex !== length - 1 ? $scope.layout[dropIndex + 1].nodeId : '';
// call layout service to save
layoutService.moveStuff(dropIndex,
length, nodeId, previousNodeId, nextNodeId);
};
}
/**
* Find an array object with the given attribute/value pair
* @param array {Array} The array to iterate on
* @param attribute {String} The name of the attribute
* @param value {String} The value to match on
* @return {number} Index of the desired item or -1
*/
var findLayoutIndex = function(array, attribute, value) {
for (var i = 0; i < array.length; i+= 1) {
if (array[i][attribute] === value) {
return i;
}
}
return -1;
};
/**
*
* @param fname
*/
$rootScope.addPortletToHome = function(fname) {
layoutService.addToLayoutByFname(fname).success(function() {
layoutService.getUncachedLayout().then(function(data) {
$scope.$apply($scope.layout.unshift(data.layout[0]));
return true;
}).catch(function() {
$log.warn('Could not getLayout while adding to home');
return false;
});
});
};
/**
* Add widget to home layout
*/
vm.addPortlet = function addPortletFunction(fname) {
$rootScope.addToLayoutByFname(fname).success(function() {
$scope.$apply(function() {
$sessionStorage.layout = $scope.layout;
});
}).error(
function() {
$sessionStorage.layout = layoutService.getLayout();
});
};
var updateGuestMode = function() {
layoutService.getGuestMode().then(function(result) {
$scope.guestMode = result;
return result;
})
.catch(function() {
$log.warn('could not retrieve guest mode');
});
};
/**
* Initialize expanded mode widget layout
*/
vm.init = function() {
if (angular.isUndefined($rootScope.layout) ||
$rootScope.layout == null) {
$rootScope.layout = [];
$scope.layoutEmpty = false;
// Get user's home layout
layoutService.getLayout().then(function(data) {
$rootScope.layout = data.layout;
$scope.layout = data.layout;
if (data.layout && data.layout.length == 0) {
$scope.layoutEmpty = true;
}
return data;
}).catch(function() {
$log.warn('Could not getLayout');
});
}
updateGuestMode();
};
vm.init();
}]);
});