EnVisageOnline/Main/Source/EnVisage/Scripts/Angular/Controllers/SkillsControllers/skillsMatrixController.js

2693 lines
82 KiB
JavaScript

'use strict';
var C_SKILLS_MATRIX_MAX_SKILL_LEVEL = 4;
var C_SKILLS_MATRIX_EDITOR_AVAILABLE_CHARS = ['i', 'I'];
var C_SKILLS_MATRIX_EDITOR_AVAILABLE_KEYSCODES = [
8, 9, 13 // Backspace, Tab, Enter
];
app
.directive('skillsmultilevel', ['$compile', function ($compile) {
return {
link: function (scope, element, attrs, ctrl, transcludeFn) {
function rebuildOptions(options) {
element.html('');
var optionsHtml = "";
for (var index = 0; index < options.length; index++) {
var item = options[index];
if (item.Group && (item.Group.Name !== undefined) && (item.Group.Disabled !== undefined) &&
$.isNumeric(item.Group.Name)) {
// Item is Skills Group
//var hasSkills = Boolean(item.Group.Disabled);
optionsHtml += '<option value="' + item.Value + '" class="select2-result-with-children">' + item.Text + '</option>';
}
else {
// Item is Skill
optionsHtml += '<option value="' + item.Value + '" class="ddl-level-item pad-left" ng-if="FilterOptions.Skills[' + index + '].Visible">' + item.Text + '</option>';
}
}
element.append($compile(optionsHtml)(scope));
}
scope.$watch(attrs.dataoptions, function (newOptions, oldOptions, childScope) {
if (newOptions)
rebuildOptions(newOptions);
});
}
}
}])
.directive('selectedDataChanged', ['$timeout', function ($timeout) {
return {
link: function ($scope, element, attrs) {
$scope.$on('selectedSkillsChanged', function () {
$timeout(function () { // You might need this timeout to be sure its run after DOM render.
element.trigger("change");
}, 0, false);
})
}
};
}])
.directive('skillLevelValidation', function () {
return {
require: 'ngModel',
link: function (scope, element, attrs, modelCtrl) {
modelCtrl.$parsers.push(function (inputValue) {
//todo: imrove regex to allow only one digit and/or one 'i' letter
var transformedInput = inputValue.replace(/[^0-4iI]/g, '');
if (transformedInput != inputValue) {
modelCtrl.$setViewValue(transformedInput);
modelCtrl.$render();
}
return transformedInput;
});
}
};
})
.factory('skillsMatrixManager', ['$injector', '$q', '$rootScope', '$http', 'dataSources', function ($injector, $q, $rootScope, $http, dataSources) {
function serviceContainer() {
};
serviceContainer.prototype = {
matrixData: null,
backup: null,
isMatrixLoaded: function () {
return this.matrixData != null;
},
getMatrix: function () {
if (this.matrixData) {
return this.matrixData;
}
throw "Matrix is not loaded";
},
createRestorePoint: function () {
this.backup = null;
if (this.matrixData) {
this.backup = angular.copy(this.matrixData);
}
},
rollbackChanges: function () {
this.matrixData = this.backup;
this.backup = null;
},
commitChanges: function () {
this.backup = null;
},
loadMatrix: function (openerType, openerId, dataType, readOnly, filter) {
if (!$.isNumeric(openerType))
throw "Opener Type is not set";
// Convert page filtering struct to server model filtering struct
var filterObj = this.convertPageFilterToServerModel(filter);
if (openerId && (openerId.length > 1)) {
// Add to filter page initial open parameters
filterObj = this.createFilterByOpener(openerType, openerId, filterObj)
}
this.createRestorePoint();
var serviceItem = this;
return dataSources.getSkillsMatrix(dataType, readOnly, filterObj).then(function (data) {
serviceItem.matrixData = data;
serviceItem.commitChanges();
return serviceItem.matrixData;
}).then(false, function (e) {
serviceItem.rollbackChanges();
throw e;
});
},
saveMatrix: function () {
// Create lite version of data struct
var mData = angular.copy(this.matrixData);
if (mData.Resources)
delete mData.Resources;
if (mData.SkillGroups)
delete mData.SkillGroups;
if (mData.Teams)
delete mData.Teams;
if (mData.Values) {
var valueKeys = Object.keys(mData.Values)
for (var keyIndex = 0; keyIndex < valueKeys.length; keyIndex++) {
var currentKey = valueKeys[keyIndex];
var currentItem = mData.Values[currentKey];
if (!currentItem.LevelChanged && !currentItem.InterestChanged) {
delete mData.Values[currentKey];
}
}
}
var serviceItem = this;
return dataSources.saveSkillsMatrix(mData).then(function (result) {
if (result) {
serviceItem.resetChangeMarker();
var scope = angular.element($('.skills-matrix-div')).scope();
scope.rebuildViewModel(true);
}
else {
throw "Error happend during skills matrix saving operation";
}
return result;
});
},
updateValue: function (resourceId, skillId, level, interested) {
if (!resourceId || !skillId)
return;
var newLevel = (level !== undefined) && $.isNumeric(level) ? Number(level) : undefined;
var newInterested = interested;
var levelChanged = false;
var interestChanged = false;
var dataKey = resourceId + "#" + skillId;
var dataItem = this.matrixData.Values[dataKey];
if (dataItem) {
var oldLevel = (dataItem.Level !== undefined) && $.isNumeric(dataItem.Level) ? Number(dataItem.Level) : undefined;
levelChanged = (newLevel !== oldLevel);
if (dataItem.Interested !== undefined) {
var oldInterested = dataItem.Interested;
interestChanged = (newInterested != oldInterested);
}
else {
interestChanged = true;
}
}
else {
// Data item not found in the storage - the cell should be created
levelChanged = !((newLevel === undefined) && !newInterested);
interestChanged = !((newLevel === undefined) && !newInterested);
}
if (levelChanged || interestChanged) {
this.createRestorePoint();
try {
dataItem = this.matrixData.Values[dataKey];
if (!dataItem) {
// Data is new. Create it and push to the storage
dataItem = {
SkillId: skillId,
ResourceId: resourceId,
}
this.matrixData.Values[dataKey] = dataItem;
}
dataItem.Level = newLevel;
dataItem.Interested = newInterested;
dataItem.LevelChanged = levelChanged;
dataItem.InterestChanged = interestChanged;
this.commitChanges();
}
catch (ex) {
this.rollbackChanges();
}
}
return (levelChanged || interestChanged);
},
// Adds Skill to matrix as child for specified Skills Group. Checks skill for existance.
// Doesn't perform matrix data backup
addSkillToMatrix: function (skillsGroupId, skillId, skillName, isVirtual) {
if (!skillsGroupId) {
console.log("Can't add Skill to matrix: parent Skills Group Id is empty");
return;
}
if (!skillId) {
console.log("Can't add Skill to matrix: Skill Id is empty");
return;
}
if (!isVirtual && (!skillName || (skillName.length < 1))) {
console.log("Can't add Skills to matrix: Skill Name is empty");
return;
}
if (!this.matrixData || !this.matrixData.SkillGroups) {
console.log("Can't add Skills to matrix: matrix data not loaded or matrix has no Skills Groups");
return result;
}
// Looking for parent Skills Group
var parentSkillsGroup = null;
for (var index = 0; index < this.matrixData.SkillGroups.length; index++) {
var skillsGroup = this.matrixData.SkillGroups[index];
if (skillsGroup.Id == skillsGroupId) {
parentSkillsGroup = skillsGroup;
break;
}
}
if (!parentSkillsGroup) {
console.log("Can't add Skill to matrix: parent Skills Group not found in matrix");
return;
}
// Looking for Skill existance
var foundSkill = null;
if (parentSkillsGroup.Skills) {
for (var index = 0; index < parentSkillsGroup.Skills.length; index++) {
var skillItem = parentSkillsGroup.Skills[index];
if (skillItem.Id == skillId) {
foundSkill = skillItem;
break;
}
}
}
else
parentSkillsGroup.Skills = [];
if (foundSkill)
// Skill already exists in matrix
return;
// Perform Skill adding
var newSkillItem = {
Id: skillId,
Name: skillName,
IsVirtual: isVirtual,
SkillGroupId: skillsGroupId
};
parentSkillsGroup.Skills.push(newSkillItem);
},
// Adds Skills Group to matrix. Checks for Group existance. Doesn't perform matrix data backup
addSkillsGroupToMatrix: function (skillsGroupId, skillGroupName, withVirtualSkill) {
if (!skillsGroupId) {
console.log("Can't add Skills Group to matrix: Skills Group Id is empty");
return;
}
if (this.isSkillGroupExists(skillsGroupId))
// Skills Group already exists in the matrix
return;
if (!skillGroupName || (skillGroupName.length < 1)) {
console.log("Can't add Skills Group to matrix: Skills Group Name is empty");
return;
}
if (!this.matrixData)
this.matrixData = {}
if (!this.matrixData.SkillGroups)
this.matrixData.SkillGroups = [];
var newSkillsGroup = {
Id: skillsGroupId,
Name: skillGroupName,
BarChartData: null,
PiePanelData: null
};
this.matrixData.SkillGroups.push(newSkillsGroup);
if (withVirtualSkill) {
// Perform virtual skill adding
this.addSkillToMatrix(skillsGroupId, skillsGroupId, "", true);
}
},
isSkillGroupExists: function (skillsGroupId) {
var result = false;
if (!skillsGroupId || !this.matrixData || !this.matrixData.SkillGroups)
return result;
for (var index = 0; index < this.matrixData.SkillGroups.length; index++) {
var skillsGroup = this.matrixData.SkillGroups[index];
result = (skillsGroup.Id == skillsGroupId);
if (result)
break;
}
return result;
},
// Drops change marker for all matrix data value items
resetChangeMarker: function () {
var mData = this.matrixData;
if (mData.Values) {
var dataKeys = Object.keys(mData.Values);
for (var index = 0; index < dataKeys.length; index++) {
var key = dataKeys[index];
var dataItem = mData.Values[key];
if (dataItem) {
dataItem.LevelChanged = false;
dataItem.InterestChanged = false;
}
}
}
},
createFilterByOpener: function (openerType, openerId, filter) {
if (!$.isNumeric(openerType))
throw "Filter processing: Opener type is not set";
var filterConverted = filter ? filter : {};
switch (openerType) {
case 1: // Main dashboard (according to ApplicationDashboards enum)
break;
case 2: // Teamboard (according to ApplicationDashboards enum)
if (!openerId) {
throw "Filter processing: TeamId for Teamboard is not specified";
}
if (!filterConverted.Teams) {
filterConverted.Teams = [];
}
if (filterConverted.Teams.indexOf(openerId) < 0) {
filterConverted.Teams.push(openerId);
}
break;
case 3: // Viewboard (according to ApplicationDashboards enum)
if (!openerId) {
throw "Filter processing: ViewId for Viewboard is not specified";
}
if (!filterConverted.Views) {
filterConverted.Views = [];
}
if (filterConverted.Views.indexOf(openerId) < 0) {
filterConverted.Views.push(openerId);
}
break;
case 4: // Resourceboard (according to ApplicationDashboards enum)
if (!openerId) {
throw "Filter processing: ResourceId for Resourceboard is not specified";
}
if (!filterConverted.Resources) {
filterConverted.Resources = [];
}
if (filterConverted.Resources.indexOf(openerId) < 0) {
filterConverted.Resources.push(openerId);
}
break;
}
return filterConverted;
},
convertPageFilterToServerModel: function (viewFilter) {
if (!viewFilter)
return null;
var model = {};
if (viewFilter.CompaniesMode && viewFilter.Companies && (viewFilter.Companies.length > 0)) {
model.Companies = angular.copy(viewFilter.Companies);
}
if (viewFilter.ViewsMode && viewFilter.Views && (viewFilter.Views.length > 0)) {
model.Views = angular.copy(viewFilter.Views);
}
if (viewFilter.TeamsMode && viewFilter.Teams && (viewFilter.Teams.length > 0)) {
model.Teams = angular.copy(viewFilter.Teams);
}
if (viewFilter.Skills && (viewFilter.Skills.length > 0)) {
model.Skills = angular.copy(viewFilter.Skills);
}
if (viewFilter.Resources && (viewFilter.Resources.length > 0)) {
model.Resources = angular.copy(viewFilter.Resources);
}
if (viewFilter.SkillLevels && (viewFilter.SkillLevels.length > 0)) {
model.SkillLevels = angular.copy(viewFilter.SkillLevels);
}
if (viewFilter.SkillInterest && $.isNumeric(viewFilter.SkillInterest)) {
model.IncludeInterested = Number(viewFilter.SkillInterest) > 0;
}
if (viewFilter.GroupMode !== undefined)
model.GroupMode = viewFilter.GroupMode;
if (viewFilter.SkillsWithDataOnly !== undefined)
model.SkillsWithDataOnly = viewFilter.SkillsWithDataOnly;
return model;
}
}
return {
getModel: function () {
return $injector.instantiate(serviceContainer);
}
}
}])
.controller('skillsMatrixController', ['$scope', '$rootScope', '$http', '$filter', '$element', '$timeout', '$document', 'dataSources', 'skillsMatrixManager', function ($scope, $rootScope, $http, $filter, $element, $timeout, $document, dataSources, skillsMatrixManager) {
var editorAvailableChars = C_SKILLS_MATRIX_EDITOR_AVAILABLE_CHARS;
var _barGraphContainer = $element.find('#graph-container');
var _pieChartContainer = $element.find('#piePanel');
$scope.Settings = {
OpenerId: null,
OpenerType: 0, // Main Skills Matrix Page (enum ApplicationDashboards)
DataType: 1, // Actual data (enum SkillMatrixDataType)
GroupByTeams: true, // Groupping resources by teams
ReadOnly: false, // Read-only grid mode
ActivityCalendarUrl: ""
};
$scope.View = {
Header: {},
Rows: {}
};
$scope.State = {
MatrixId: "",
DataChanged: false,
DataAvailable: true,
DataLoaded: false
};
$scope.Widgets = {
BarGraphCollapsed: false,
PieChartCollapsed: false,
};
var model = null;
$scope.init = function (initData) {
if (initData && initData.model) {
if ($.isNumeric(initData.model.OpenerType))
$scope.Settings.OpenerType = Number(initData.model.OpenerType);
if (initData.model.OpenerId && (initData.model.OpenerId.length > 1))
$scope.Settings.OpenerId = initData.model.OpenerId;
if ($.isNumeric(initData.model.DataType))
$scope.Settings.DataType = initData.model.DataType;
if (initData.model.ReadOnly)
$scope.Settings.ReadOnly = initData.model.ReadOnly;
if (initData.model.ActivityCalendarUrl)
$scope.Settings.ActivityCalendarUrl = initData.model.ActivityCalendarUrl;
initFilters(initData);
}
editorAvailableChars = angular.copy(C_SKILLS_MATRIX_EDITOR_AVAILABLE_CHARS);
for (var index = 0; index <= C_SKILLS_MATRIX_MAX_SKILL_LEVEL; index++)
editorAvailableChars.push(String(index));
geneateMatrixId();
setDataChanged(false);
$scope.State.DataAvailable = true;
$scope.State.DataLoaded = false;
// Acquire source data model
model = skillsMatrixManager.getModel();
};
$scope.rebuildViewModel = function (forceReload) {
if (forceReload) {
blockUI();
try {
// Store user preferences
savePreferences();
model.loadMatrix($scope.Settings.OpenerType,
$scope.Settings.OpenerId, $scope.Settings.DataType, $scope.Settings.ReadOnly, $scope.Filter)
.then(function (data) {
// Create indexed structs for quick resources access
createResourceHash(data);
rebuildViewModelInternal(data);
$scope.State.DataLoaded = true;
setDataChanged(false);
unblockUI();
$timeout(function () {
$scope.initBarGraph();
$scope.initPieChart();
});
})
.then(false, function (ex) {// fail callback, raised if any error occurred in the chain
// handle exception
unblockUI();
showErrorModal('Oops!', 'An error occurred while loading skills matrix. Please, try again later.');
});
}
catch (exception) {
unblockUI();
console.log("Error loading Skills Matrix data: " + exception);
showErrorModal('Oops!', 'An error occurred while loading skills matrix. Please, try again later.');
}
}
else {
var data = model.getMatrix();
rebuildViewModelInternal(data);
$scope.initBarGraph();
$scope.initPieChart();
}
};
function rebuildViewModelInternal(data) {
$scope.View.Header = {
Groups: [],
Skills: []
};
$scope.View.Rows = [];
if (data) {
var usedSkills = [];
// Fill header structs
if (data.SkillGroups && (data.SkillGroups.length > 0)) {
for (var gIndex = 0; gIndex < data.SkillGroups.length; gIndex++) {
var groupContract = data.SkillGroups[gIndex];
if (groupContract.Skills && (groupContract.Skills.length > 0)) {
var groupViewItem = {
Name: groupContract.Name,
SkillsCount: groupContract.Skills.length
}
$scope.View.Header.Groups.push(groupViewItem);
for (var sIndex = 0; sIndex < groupContract.Skills.length; sIndex++) {
var skillContract = groupContract.Skills[sIndex];
var skillViewItem = {
Id: skillContract.Id,
Name: skillContract.Name
}
$scope.View.Header.Skills.push(skillViewItem);
usedSkills.push(skillContract.Id);
}
}
}
}
// Fill Teams and resources Names
var matrixSkillsCount = usedSkills.length;
if (matrixSkillsCount > 0) {
if ($scope.Settings.GroupByTeams) {
// Groupping by teams
if (data.Teams && (data.Teams.length > 0)) {
for (var tIndex = 0; tIndex < data.Teams.length; tIndex++) {
var teamContract = data.Teams[tIndex];
if (teamContract.Resources && (teamContract.Resources.length > 0)) {
var teamViewItem = {
Type: 1,
Name: teamContract.Name,
ColSpan: matrixSkillsCount + 1
};
$scope.View.Rows.push(teamViewItem);
for (var rIndex = 0; rIndex < teamContract.Resources.length; rIndex++) {
var resourceId = teamContract.Resources[rIndex];
var resourceContract = getResourceById(resourceId, data);
if (resourceContract) {
var resourceViewItem = {
Type: 2,
Name: resourceContract.FullName,
Cells: new Array(matrixSkillsCount),
Id: resourceId
};
$scope.View.Rows.push(resourceViewItem);
}
else {
console.log('skillsMatrixManager.getMatrix: Resource ' + resourceId + ' not found in the data package');
}
}
}
}
}
}
else {
// Plain resource list
if (data.Resources && (data.Resources.length > 0)) {
for (var rIndex = 0; rIndex < data.Resources.length; rIndex++) {
var resourceContract = data.Resources[rIndex];
var resourceViewItem = {
Type: 2,
Name: resourceContract.FullName,
Cells: new Array(matrixSkillsCount),
Id: resourceContract.Id
};
$scope.View.Rows.push(resourceViewItem);
}
}
}
// Fill matrix with values
for (rIndex = 0; rIndex < $scope.View.Rows.length; rIndex++) {
var currentRow = $scope.View.Rows[rIndex];
if (currentRow && (currentRow.Type == 2)) {
var resourceId = currentRow.Id;
for (sIndex = 0; sIndex < usedSkills.length; sIndex++) {
var cellValue = getCellValue(resourceId, usedSkills[sIndex], data);
currentRow.Cells[sIndex] = cellValue;
}
}
}
}
$scope.State.DataAvailable = ($scope.View.Rows.length > 0) && (matrixSkillsCount > 0);
}
};
$scope.startEditCell = function (cellViewItem, rowIndex, colIndex, t) {
if (!cellViewItem || !t)
return;
$scope.watchKeyInput(t, rowIndex, colIndex);
};
$scope.watchKeyInput = function (t, rowIndex, colIndex) {
$timeout(function () {
if (t.$editable.inputEl.select)
t.$editable.inputEl.select();
else if (t.$editable.inputEl.setSelectionRange)
t.$editable.inputEl.setSelectionRange(0, t.$editable.inputEl.val().length);
}, 3);
t.$editable.inputEl.on('keydown', function (e) {
if (e.char && (e.char.length > 0) && (editorAvailableChars.indexOf(e.char) < 0) &&
e.which && (C_SKILLS_MATRIX_EDITOR_AVAILABLE_KEYSCODES.indexOf(e.which) < 0)) {
e.preventDefault();
return;
}
if (e.which == 9) { //when tab key is pressed
e.preventDefault();
t.$form.$submit();
var nextCellId = undefined;
if (e.shiftKey) { // On shift + tab we look for prev cell to move to
nextCellId = getPrevCellId(rowIndex, colIndex);
}
else {
// Look for next cell ID we should move to
nextCellId = getNextCellId(rowIndex, colIndex);
}
if (nextCellId) {
var nextCellItem = $($element).find('td#' + nextCellId);
if (nextCellItem && (nextCellItem.length == 1)) {
$timeout(function () {
nextCellItem.click();
}, 0);
}
}
$scope.$apply(function () {
// SA: Leave this block to force update submitted cell view by angular
});
}
});
};
$scope.checkEditorValue = function (newValue, rowIndex, colIndex) {
var errorMessage = "Invalid value";
var levelToSave = undefined;
var interestedToSave = false;
if (!$.isNumeric(rowIndex) || !$.isNumeric(colIndex)) {
console.log("checkEditorValue: Matrix cell coordinates are not specified");
return "Internal error";
}
if (newValue && (newValue.length > 0)) {
var trimmedValue = $.trim(newValue).toLowerCase();
interestedToSave = (trimmedValue.indexOf("i") >= 0);
var filteredValue = trimmedValue.replace(new RegExp('i', 'g'), "");
if (trimmedValue.length > 2) {
// Value can't be longer, than 2 letters
return errorMessage;
}
if ($.isNumeric(filteredValue))
filteredValue = Number(filteredValue);
else {
if (filteredValue.length > 0) {
// Value is not a number. May contain letters
return errorMessage;
}
else {
// No numeric component in the user specified value.
// If interestedToSave = true, we assume new skill level is 0.
filteredValue = interestedToSave ? 0 : undefined;
}
}
if ((filteredValue !== undefined) && ((filteredValue < 0) || (filteredValue > C_SKILLS_MATRIX_MAX_SKILL_LEVEL))) {
// Value is out the valid range
return errorMessage;
}
levelToSave = filteredValue;
interestedToSave = (trimmedValue.indexOf("i") >= 0);
}
var resourceId = $scope.View.Rows[rowIndex].Id;
var skillId = $scope.View.Header.Skills[colIndex].Id;
// iterate through all rows and update all rows for the specified resource (in case there are multiple teams assignment for this resource)
for (var iRow = 0; iRow < $scope.View.Rows.length; iRow++) {
if ($scope.View.Rows[iRow].Id == resourceId) {
var cellViewItem = $scope.View.Rows[iRow].Cells[colIndex];
cellViewItem.Level = levelToSave;
cellViewItem.Interested = interestedToSave;
updateCellEditorValue(cellViewItem);
}
}
var dataChanged = model.updateValue(resourceId, skillId, levelToSave, interestedToSave);
setDataChanged(dataChanged || $scope.State.DataChanged);
//required to be false by xeditable grid
return false;
};
$scope.onTxtBlur = function (cellViewItem, txt) {
txt.$form.$submit();
};
$scope.saveChanges = function () {
if ($scope.Settings.ReadOnly || !$scope.State.DataLoaded)
return;
blockUI();
model.saveMatrix().then(function () {
setDataChanged(false);
var data = model.getMatrix();
$rootScope.$broadcast('skillsMatrixSaved', data);
unblockUI();
bootbox.alert('Skills matrix has been saved');
}).then(false, function (ex) {// fail callback, raised if any error occurred in the chain
// handle exception
showErrorModal('Oops!', 'An error occurred while saving skills matrix. Please, try again later.');
unblockUI();
});
};
$scope.openActivityCalendar = function () {
if (!$scope.Settings.ActivityCalendarUrl || ($scope.Settings.ActivityCalendarUrl.length < 1)) {
console.log("Capacity Management url was not specified");
return;
}
var url = $scope.Settings.ActivityCalendarUrl;
var itemId = undefined;
if ($scope.Filter.CompaniesMode && $scope.Filter.Companies && ($scope.Filter.Companies.length > 0))
itemId = $scope.Filter.Companies[0];
if ($scope.Filter.ViewsMode && $scope.Filter.Views && ($scope.Filter.Views.length > 0))
itemId = $scope.Filter.Views[0];
if ($scope.Filter.TeamsMode && $scope.Filter.Teams && ($scope.Filter.Teams.length > 0))
itemId = $scope.Filter.Teams[0];
if (itemId) {
url += '?id=' + itemId;
}
window.open(url);
}
function setDataChanged(value) {
$scope.State.DataChanged = value;
if (value)
addPageLeaveHandler();
else
removePageLeaveHandler();
}
function addPageLeaveHandler() {
window.onbeforeunload = function (e) {
var message = "Skills Matrix contains unsaved changes. Are you want to leave the page and discard changes?",
e = e || window.event;
// For IE and Firefox
if (e) {
e.returnValue = message;
}
// For Safari
return message;
};
}
function removePageLeaveHandler() {
window.onbeforeunload = null;
}
function getNextCellId(rowIndex, colIndex) {
var lastColIndex = $scope.View.Header.Skills.length - 1;
var lastRowIndex = $scope.View.Rows.length - 1;
var nextRow = rowIndex;
var nextCol = colIndex;
if (colIndex < lastColIndex) {
nextCol++;
}
else {
nextRow = rowIndex < lastRowIndex ? rowIndex + 1 : 0;
nextCol = 0;
if (nextRow != rowIndex) {
var startedAtRow = rowIndex;
var matrixLastRowPassed = false;
while (($scope.View.Rows[nextRow].Type != 2) && ((nextRow != startedAtRow) || !matrixLastRowPassed)) {
nextRow++;
if (nextRow > lastRowIndex) {
nextRow = 0;
matrixLastRowPassed = true;
};
}
var nextCellFound = ($scope.View.Rows[nextRow].Type == 2) && (nextRow != startedAtRow);
}
else
// One cell matrix
nextCellFound = false;
if (!nextCellFound) {
// Next cell found
return;
}
}
// Next cell found. Return it's ID in html
return $scope.State.MatrixId + '_' + nextRow + '_' + nextCol;
}
function getPrevCellId(rowIndex, colIndex) {
var lastColIndex = $scope.View.Header.Skills.length - 1;
var lastRowIndex = $scope.View.Rows.length - 1;
var nextRow = rowIndex;
var nextCol = colIndex;
if (colIndex > 0) {
nextCol--;
}
else {
nextRow = rowIndex > 0 ? rowIndex - 1 : lastRowIndex;
nextCol = lastColIndex;
if (nextRow != rowIndex) {
var startedAtRow = rowIndex;
var matrixFirstRowPassed = false;
while (($scope.View.Rows[nextRow].Type != 2) && ((nextRow != startedAtRow) || !matrixFirstRowPassed)) {
nextRow--;
if (nextRow < 0) {
nextRow = lastRowIndex;
matrixFirstRowPassed = true;
};
}
var nextCellFound = ($scope.View.Rows[nextRow].Type == 2) && (nextRow != startedAtRow);
}
else
// One cell matrix
nextCellFound = false;
if (!nextCellFound) {
// Next cell found
return;
}
}
// Next cell found. Return it's ID in html
return $scope.State.MatrixId + '_' + nextRow + '_' + nextCol;
}
// ======================== Helper internal functions ============================ //
var resourceHash = {};
function createResourceHash(data) {
destroyResourceHash();
if (!data || !data.Resources || (data.Resources.length < 1))
return;
for (var rIndex = 0; rIndex < data.Resources.length; rIndex++) {
var resourceContract = data.Resources[rIndex];
var resourceId = resourceContract.Id;
if (resourceId && (resourceId.length > 0)) {
resourceHash[resourceId] = rIndex;
}
}
}
function destroyResourceHash() {
resourceHash = {};
}
function getResourceById(id, data) {
if (!resourceHash || !data || !data.Resources || (data.Resources.length < 1))
return;
var rIndex = resourceHash[id];
if ((rIndex != undefined) && !isNaN(rIndex) && $.isNumeric(rIndex)) {
return data.Resources[rIndex];
}
};
function getCellValue(resourceId, skillId, data) {
if (!resourceId || !skillId || !data || !data.Values)
return;
var cellViewItem = {
CssClass: "",
EditorValue: "",
Interested: false,
}
var key = resourceId + "#" + skillId;
var cellValueContract = data.Values[key];
if (cellValueContract) {
cellViewItem.Interested = cellValueContract.Interested;
cellViewItem.Level = cellValueContract.Level;
}
updateCellEditorValue(cellViewItem);
return cellViewItem;
}
function updateCellEditorValue(cellViewItem) {
if (!cellViewItem)
return;
cellViewItem.CssClass = "skill-cell-level";
cellViewItem.EditorValue = "";
if ($.isNumeric(cellViewItem.Level)) {
cellViewItem.EditorValue += String(cellViewItem.Level);
cellViewItem.CssClass += String(cellViewItem.Level);
}
if (cellViewItem.Interested) {
cellViewItem.EditorValue += "i";
}
}
function geneateMatrixId() {
var result = "";
var variants = "abcdefghijklmnopqrstuvwxyz0123456789";
for (var i = 0; i < 10; i++)
result += variants.charAt(Math.floor(Math.random() * variants.length));
$scope.State.MatrixId = "skm_" + result;
}
// ======================== Filter management ============================ //
$scope.FilterOptions = {};
$scope.Filter = {};
$scope.FilterOptionsHash = {}; // Hash for quick access to filter options
function initFilters(initData) {
// Filtering options
$scope.FilterOptions = {
Companies: [],
Views: [],
Teams: [],
Skills: [],
SkillLevels: [],
SkillInterests: []
};
$scope.Filter = {
CompaniesMode: false,
ViewsMode: false,
TeamsMode: false,
Companies: [],
Views: [],
Teams: [],
Resources: [],
Skills: [],
SkillLevels: [],
SkillInterest: null,
GroupMode: false,
SkillsWithDataOnly: false
};
if (initData.model.FilterOptions) {
if (initData.model.FilterOptions.Companies) {
$scope.FilterOptions.Companies = initData.model.FilterOptions.Companies;
}
if (initData.model.FilterOptions.Views) {
$scope.FilterOptions.Views = initData.model.FilterOptions.Views;
}
if (initData.model.FilterOptions.Teams) {
$scope.FilterOptions.Teams = initData.model.FilterOptions.Teams;
}
if (initData.model.FilterOptions.Skills) {
$scope.FilterOptions.Skills = initData.model.FilterOptions.Skills;
for (var index = 0; index < $scope.FilterOptions.Skills.length; index++) {
$scope.FilterOptions.Skills[index]["Visible"] = true;
}
}
if (initData.model.FilterOptions.SkillLevels) {
$scope.FilterOptions.SkillLevels = initData.model.FilterOptions.SkillLevels;
}
if (initData.model.FilterOptions.SkillInterests) {
$scope.FilterOptions.SkillInterests = initData.model.FilterOptions.SkillInterests;
}
}
$timeout(function () {
$scope.switchCompaniesFilterMode();
// Set custom css-styles for Skill Levels combo options
var options = $($element).find('select[name=select2FilterSkillLevels] option');
angular.forEach(options, function (option, index) {
var optionIndex = $(option).prop("index");
if ($.isNumeric(optionIndex) && (optionIndex >= 0)) {
var className = 'skill-cell-level' + String(Number(optionIndex));
$(option).addClass(className);
}
});
// Set custom css-styles for Companies combo options
options = $($element).find('select[name=select2FilterCompanies] option');
angular.forEach(options, function (option, index) {
var optionIndex = $(option).prop("index");
var className;
if ($.isNumeric(optionIndex) && (optionIndex >= 0)) {
var srcItem = $scope.FilterOptions.Companies[optionIndex];
if (srcItem && srcItem.Group) {
if (srcItem.Group.Name && (srcItem.Group.Name.length > 0))
className = 'ddl-level-item pad-left';
else
className = 'ddl-level-item';
$(option).addClass(className);
}
}
});
$($element).find('[name=select2FilterSkills]').select2()
.on("change", function (e) {
$scope.$apply(function () {
var newSelection = setSkillOptionsVisibility(e.val);
$scope.Filter.Skills = newSelection;
setSelect2MultiSelectionInternal($(e.currentTarget), newSelection);
});
});
$($element).find('[name=select2FilterElement]').select2();
$($element).find('[name=select2FilterSkillLevels]').select2();
$($element).find('[name=select2FilterCompanies]').select2();
$($element).find('[name=select2FilterSkillInterests]').select2({
allowClear: true,
minimumResultsForSearch: -1
});
restorePreferences(initData.prefs);
// Create Group By Teams switcher
$('[name=groupMode]').switcher({
on_state_content: 'Teams',
off_state_content: 'None'
});
$('[name=groupMode]').parent().css("width", "93px");
if ($scope.filterIsValid())
$scope.rebuildViewModel(true);
});
}
$scope.filterIsValid = function () {
var result = $scope.Filter && (
($scope.Filter.CompaniesMode && $scope.Filter.Companies && ($scope.Filter.Companies.length > 0)) ||
($scope.Filter.ViewsMode && $scope.Filter.Views && ($scope.Filter.Views.length > 0)) ||
($scope.Filter.TeamsMode && $scope.Filter.Teams && ($scope.Filter.Teams.length > 0))
);
return result;
};
$scope.switchCompaniesFilterMode = function () {
$scope.Filter.ViewsMode = false;
$scope.Filter.TeamsMode = false;
if (!$scope.Filter.CompaniesMode)
$scope.Filter.CompaniesMode = true;
};
$scope.switchViewsFilterMode = function () {
$scope.Filter.CompaniesMode = false;
$scope.Filter.TeamsMode = false;
if (!$scope.Filter.ViewsMode)
$scope.Filter.ViewsMode = true;
};
$scope.switchTeamsFilterMode = function () {
$scope.Filter.CompaniesMode = false;
$scope.Filter.ViewsMode = false;
if (!$scope.Filter.TeamsMode)
$scope.Filter.TeamsMode = true;
};
$scope.toggleBarGraph = function (value) {
if (value === undefined)
$scope.Widgets.BarGraphCollapsed = !$scope.Widgets.BarGraphCollapsed;
else
$scope.Widgets.BarGraphCollapsed = value;
if ($scope.Widgets.BarGraphCollapsed) {
_barGraphContainer.slideUp();
$('#vt').hide();
}
else
_barGraphContainer.slideDown();
savePreferences();
if (!$scope.Widgets.BarGraphCollapsed && model.isMatrixLoaded())
$scope.initBarGraph();
};
$scope.togglePieChart = function (value) {
if (value === undefined)
$scope.Widgets.PieChartCollapsed = !$scope.Widgets.PieChartCollapsed;
else
$scope.Widgets.PieChartCollapsed = value;
if ($scope.Widgets.PieChartCollapsed) {
$('#PCheading').css('padding', '11px 13% 9px');
$('#pc').removeClass("col-lg-6").addClass("col-lg-1");
$('#bg').removeClass("col-lg-6").addClass("col-lg-11");
$('#pci').hide();
if (!$scope.Widgets.BarGraphCollapsed) {
$('#vt').show();
$('#vt').height(290);
}
$('#piePanel').hide();
$scope.initBarGraph();
}
else {
$('#PCheading').css('padding', '11px 20px 9px');
$('#pc').removeClass("col-lg-1").addClass("col-lg-6");
$('#bg').removeClass("col-lg-11").addClass("col-lg-6");
$('#pci').show();
$('#vt').hide();
$('#piePanel').show();
$scope.initBarGraph();
}
savePreferences();
if (!$scope.Widgets.PieChartCollapsed && model.isMatrixLoaded())
$scope.initPieChart();
};
$scope.switchTeamGroupingMode = function () {
$scope.$apply(function () {
$scope.Settings.GroupByTeams = !$scope.Settings.GroupByTeams;
if ($scope.State.DataLoaded && $scope.filterIsValid())
$scope.rebuildViewModel(false);
});
}
function setSkillOptionsVisibility(selection) {
var selectedItems = selection && (selection.length > 0) ? selection : [];
var newSelection = angular.copy(selectedItems);
var hideSkills = false;
// Reset visibility status for options
for (var index = 0; index < $scope.FilterOptions.Skills.length; index++) {
var currentItem = $scope.FilterOptions.Skills[index];
if (currentItem.Group && (currentItem.Group.Name !== undefined) && $.isNumeric(currentItem.Group.Name)) {
// Item is Skill Group
var groupId = currentItem.Value;
hideSkills = false;
if (selectedItems.indexOf(groupId) >= 0) {
// Start hiding skills, next to this Skill Group, as the options list is plain
hideSkills = true;
}
}
else {
// Item is skill
$scope.FilterOptions.Skills[index].Visible = !hideSkills;
if (hideSkills) {
var indexInSelection = newSelection.indexOf(currentItem.Value);
if (indexInSelection >= 0)
newSelection.splice(indexInSelection, 1);
}
}
}
return newSelection;
}
//========================= User Preferences ===============================//
function savePreferences() {
var dataSection = getDataSection($element);
if (dataSection) {
var preferences = [];
if ($scope.Filter.CompaniesMode) {
preferences.push({
Key: 'companiesFilterMode',
Value: true
});
preferences.push({
Key: 'companiesFilterList',
Value: angular.copy($scope.Filter.Companies)
});
}
if ($scope.Filter.ViewsMode) {
preferences.push({
Key: 'viewsFilterMode',
Value: true
});
preferences.push({
Key: 'viewsFilterList',
Value: angular.copy($scope.Filter.Views)
});
}
if ($scope.Filter.TeamsMode) {
preferences.push({
Key: 'teamsFilterMode',
Value: true
});
preferences.push({
Key: 'teamsFilterList',
Value: angular.copy($scope.Filter.Teams)
});
}
preferences.push({
Key: 'skillsFilterList',
Value: angular.copy($scope.Filter.Skills)
});
if ($scope.Filter.SkillLevels && ($scope.Filter.SkillLevels.length > 0)) {
preferences.push({
Key: 'skillLevelsFilterList',
Value: angular.copy($scope.Filter.SkillLevels)
});
}
if ($scope.Filter.SkillInterest) {
preferences.push({
Key: 'skillInterestsFilterValue',
Value: $scope.Filter.SkillInterest
});
}
if (($scope.Settings.GroupByTeams !== undefined) && ($scope.Settings.GroupByTeams !== null)) {
preferences.push({
Key: 'teamGroupMode',
Value: $scope.Settings.GroupByTeams
});
}
preferences.push({
Key: 'barGraphCollapsed',
Value: $scope.Widgets.BarGraphCollapsed
});
preferences.push({
Key: 'pieChartCollapsed',
Value: $scope.Widgets.PieChartCollapsed
});
saveUserPagePreferences(preferences, dataSection);
}
else
console.log("Skills Matrix user preferences not saved: data section not found");
};
function restorePreferences(data) {
if (!data || (data.length < 1))
return;
var incomingPrefs = JSON.parse(data);
var dataSectionName = getDataSection($element);
if (!dataSectionName) {
console.log("Data section element name for Skills Matrix not found. Preferences restore failed");
return;
}
var dataSectionElem = getDataSectionElement(dataSectionName);
if (!dataSectionElem) {
console.log("Data section element for Skills Matrix not found. Preferences restore failed");
return;
}
var prefExecutor = {
companiesFilterMode: $scope.switchCompaniesFilterMode,
viewsFilterMode: $scope.switchViewsFilterMode,
teamsFilterMode: $scope.switchTeamsFilterMode,
barGraphCollapsed: function (value) {
$scope.toggleBarGraph(value);
},
pieChartCollapsed: function (value) {
$scope.togglePieChart(value);
},
teamGroupMode: function (value) {
$scope.Settings.GroupByTeams = value;
setCheckboxValue(dataSectionElem, 'teamGroupMode', value);
},
companiesFilterList: function (items) {
$scope.Filter.Companies = getSelectionForFilter($scope.FilterOptions.Companies, items);
setSelect2MultiSelection(dataSectionElem, 'companiesFilterList', $scope.Filter.Companies, true);
},
viewsFilterList: function (items) {
$scope.Filter.Views = getSelectionForFilter($scope.FilterOptions.Views, items);
setSelect2MultiSelection(dataSectionElem, 'viewsFilterList', $scope.Filter.Views, true);
},
teamsFilterList: function (items) {
$scope.Filter.Teams = getSelectionForFilter($scope.FilterOptions.Teams, items);
setSelect2MultiSelection(dataSectionElem, 'teamsFilterList', $scope.Filter.Teams, true);
},
skillsFilterList: function (items) {
$scope.Filter.Skills = getSelectionForFilter($scope.FilterOptions.Skills, items);
setSelect2MultiSelection(dataSectionElem, 'skillsFilterList', $scope.Filter.Skills, false);
},
skillLevelsFilterList: function (items) {
$scope.Filter.SkillLevels = getSelectionForFilter($scope.FilterOptions.SkillLevels, items);
setSelect2MultiSelection(dataSectionElem, 'skillLevelsFilterList', $scope.Filter.SkillLevels, true);
},
skillInterestsFilterValue: function (item) {
$scope.Filter.SkillInterest = item;
setSelect2SingleSelection(dataSectionElem, 'skillInterestsFilterValue', $scope.Filter.SkillInterest);
}
};
var knownPrefKeys = Object.keys(prefExecutor);
for (var index = 0; index < incomingPrefs.length; index++) {
var key = incomingPrefs[index].Key;
var value = incomingPrefs[index].Value;
if (knownPrefKeys.indexOf(key) >= 0) {
prefExecutor[key](value);
}
}
setSkillOptionsVisibility($scope.Filter.Skills);
};
// searches in the "filterOptions" array for all items with "Value" property equal to one of values in "itemsToSelect" array
// returns an array of matched values ("Value" property value) from "filterOptions" array
function getSelectionForFilter(filterOptions, itemsToSelect) {
var result = [];
if (filterOptions && itemsToSelect && (itemsToSelect.length > 0)) {
for (var index = 0; index < itemsToSelect.length; index++) {
var foundItems = $.grep(filterOptions, function (item) {
return item.Value == itemsToSelect[index];
});
if (foundItems && (foundItems.length > 0))
result.push(foundItems[0].Value);
}
}
return result;
};
function getDataSectionElement(dataSection) {
var sections = $($element).parents("*[data-section]");
var foundElem;
if (sections && (sections.length > 0))
foundElem = $(sections).first();
return foundElem;
};
function setSelect2MultiSelection(dataSectionElem, controlDataKey, itemsToSelect, addStringText) {
if (itemsToSelect) {
var arrayCopy = angular.copy(itemsToSelect);
if (addStringText) {
for (var index = 0; index < arrayCopy.length; index++) {
arrayCopy[index] = "string:" + arrayCopy[index];
}
}
var targetControl = $(dataSectionElem).find("select[data-key=" + controlDataKey + "]");
setSelect2MultiSelectionInternal(targetControl, arrayCopy);
}
};
function setSelect2MultiSelectionInternal(control, itemsToSelect) {
control.select2('destroy');
control.select2();
control.select2('val', itemsToSelect);
};
function setSelect2SingleSelection(dataSectionElem, controlDataKey, itemToSelect) {
if ($.isNumeric(itemToSelect)) {
var targetControl = $(dataSectionElem).find("select[data-key=" + controlDataKey + "]");
var selectionString = "string:" + itemToSelect;
targetControl.select2('val', selectionString);
}
};
function setCheckboxValue(dataSectionElem, controlDataKey, value) {
if ((value === true) || (value === false)) {
var targetControl = $(dataSectionElem).find("input[data-key=" + controlDataKey + "]");
targetControl.prop('checked', value);
}
};
$scope.switchBarGraphGroupingMode = function () {
$scope.$apply(function () {
$scope.Filter.GroupMode = !$scope.Filter.GroupMode;
if ($scope.State.DataLoaded && $scope.filterIsValid()) {
$scope.initBarGraph();
$scope.initPieChart();
}
});
}
//======================== event handlers ==============================//
$scope.$on('skillsSaved', function (event, newSkills, fnCallback) {
var newSkillOptions = [];
var newGroupsSorted = [];
var newSkillsSorted = [];
angular.forEach(newSkills, function (item, key) {
newGroupsSorted.push(item);
});
newGroupsSorted.sort(function (a, b) {
var aName = a.Name.toLowerCase();
var bName = b.Name.toLowerCase();
return ((aName < bName) ? -1 : ((aName > bName) ? 1 : 0));
});
for (var gIndex = 0; gIndex < newGroupsSorted.length; gIndex++) {
var gItem = newGroupsSorted[gIndex];
var groupOption = {
Disabled: false,
Group: {
Disabled: gItem.HasChildren,
Name: gItem.Children ? Object.keys(gItem.Children).length : 0
},
Selected: false,
Text: gItem.Name,
Value: gItem.Id
};
newSkillOptions.push(groupOption);
if (gItem.HasChildren) {
var newSkillsSorted = [];
angular.forEach(gItem.Children, function (item, key) {
newSkillsSorted.push(item);
});
newSkillsSorted.sort(function (a, b) {
var aName = a.Name.toLowerCase();
var bName = b.Name.toLowerCase();
return ((aName < bName) ? -1 : ((aName > bName) ? 1 : 0));
});
for (var sIndex = 0; sIndex < newSkillsSorted.length; sIndex++) {
var sItem = newSkillsSorted[sIndex];
var option = {
Disabled: false,
Group: {
Disabled: false,
Name: gItem.Id
},
Selected: false,
Text: sItem.Name,
Visible: true,
Value: sItem.Id
};
newSkillOptions.push(option);
}
}
}
// remove deleted items from selected properties
var newSkillsSelection = filterArray($scope.Filter.Skills, newSkillOptions);
addToFilter(newSkillsSelection, $scope.FilterOptions.Skills, newSkillOptions);
$scope.FilterOptions.Skills = newSkillOptions;
$scope.Filter.Skills = newSkillsSelection;
$timeout(function () {
var dataSectionName = getDataSection($element);
if (dataSectionName) {
var dataSectionElem = getDataSectionElement(dataSectionName);
if (dataSectionElem) {
var newSkillsSelection = setSkillOptionsVisibility($scope.Filter.Skills);
$scope.Filter.Skills = newSkillsSelection;
setSelect2MultiSelection(dataSectionElem, 'skillsFilterList', newSkillsSelection, false);
if ($scope.filterIsValid())
$scope.rebuildViewModel(true);
}
}
if (typeof (fnCallback) === 'function')
fnCallback();
}, 200);
});
// removes items from items2Search array which does not exist in items2Remain array
// method compares items2Search[i] with items2Remain[j].Value
// returns an "items2Search" array without filtered items
function filterArray(items2Search, items2Remain) {
var selectedValues = angular.copy(items2Search);
for (var i = selectedValues.length - 1; i >= 0; i--) {
var selected = selectedValues[i];
var found = false;
for (var j = 0; j < items2Remain.length; j++) {
if (selected == items2Remain[j].Value) {
found = true;
break;
}
}
if (!found)
selectedValues.splice(i, 1);
}
return selectedValues;
}
// Adds to filter added skills or groups if nesessary
function addToFilter(filterSet, oldSet, newSet) {
if (filterSet.length == 0)
return;
for (var i = newSet.length - 1; i >= 0; i--) {
var found = false;
for (var j = 0; j < oldSet.length; j++) {
if (newSet[i].Value == oldSet[j].Value) {
found = true;
break;
}
}
if (!found)
filterSet.push(newSet[i].Value);
}
return;
}
$scope.setSkillsFilterFromPie = function (skillIds) {
var dataSectionName = getDataSection($element);
if (dataSectionName) {
var dataSectionElem = getDataSectionElement(dataSectionName);
var newSelection = setSkillOptionsVisibility(skillIds);
$scope.Filter.Skills = newSelection;
$timeout(function () {
setSelect2MultiSelection(dataSectionElem, 'skillsFilterList', newSelection, false);
}, 200);
}
}
$scope.setSkillGroupsFilterFromPie = function (groupIds) {
var dataSectionName = getDataSection($element);
if (dataSectionName) {
var dataSectionElem = getDataSectionElement(dataSectionName);
var newSelection = setSkillOptionsVisibility(groupIds);
$scope.Filter.Skills = newSelection;
$timeout(function () {
setSelect2MultiSelection(dataSectionElem, 'skillsFilterList', newSelection, false);
}, 200);
}
}
//======================== BAR GRAPH ==============================//
$scope.initBarGraph = function () {
if ($scope.Widgets.BarGraphCollapsed || !model.isMatrixLoaded())
return;
// Clear graph placeholder
var matrix = model.getMatrix();
var barData = matrix.BarGraphData;
$('#skillBarGraphContainer').html('<div id="skillBarGraph"></div>');
if ($scope.Filter.GroupMode) {
var tickStep = Math.floor(barData.GMaxVal / 3);
// Init Chart
$('#skillBarGraph').pixelPlot(barData.GroupData, {
series: {
bars: {
show: true,
fill: 1,
barWidth: .5,
}
},
xaxis: {
min: 0.5,
max: barData.GTitles.length + 0.5,
mode: null,
tickLength: 0,
tickSize: 1,
ticks: barData.GTitles,
labelWidth: 10,
},
yaxis: {
tickSize: tickStep,
tickDecimals: 0,
},
grid: {
hoverable: true,
backgroundColor: { colors: ["#878787", "#444444"] },
labelMargin: 5,
},
tooltip: {
show: true,
content: "%y Resouces"
}
}, {
height: 205,
tooltipText: ''
});
}
else {
var tickStep = Math.floor(barData.MaxVal / 3);
// Init Chart
$('#skillBarGraph').pixelPlot(barData.Data, {
series: {
bars: {
show: true,
barWidth: .5,
}
},
xaxis: {
min: 0.5,
max: barData.Titles.length + 0.5,
mode: null,
tickLength: 0,
tickSize: 1,
ticks: barData.Titles,
labelWidth: 10,
},
yaxis: {
tickSize: tickStep,
tickDecimals: 0,
},
grid: {
hoverable: true,
backgroundColor: { colors: ["#878787", "#444444"] },
labelMargin: 5,
},
tooltip: {
show: true,
content: "%y Resouces"
}
}, {
height: 205,
tooltipText: ''
});
}
};
//======================== PIE CHART ==============================//
$scope.initPieChart = function () {
if ($scope.Widgets.PieChartCollapsed || !model.isMatrixLoaded())
return;
// Clear graph placeholder
var matrix = model.getMatrix();
var prPieData = $scope.Filter.GroupMode ? matrix.PieGraphData.PresentGroupedData : matrix.PieGraphData.PresentData;
var psPieData = $scope.Filter.GroupMode ? matrix.PieGraphData.PastGroupedData : matrix.PieGraphData.PastData;
var ftPieData = $scope.Filter.GroupMode ? matrix.PieGraphData.FutureGroupedData : matrix.PieGraphData.FutureData;
$('#skillPieContainerPs').html('<div id="skillPiePs" style="height:205px"></div>');
$('#skillPieContainerPr').html('<div id="skillPiePr" style="height:205px"></div>');
$('#skillPieContainerFt').html('<div id="skillPieFt" style="height:205px"></div>');
if (!psPieData || psPieData.length == 0) {
$('#psCont').hide();
$('#psHead').hide();
} else {
$('#psCont').show();
$('#psHead').show();
}
if (!prPieData || prPieData.length == 0) {
$('#prCont').hide();
$('#prHead').hide();
} else {
$('#prCont').show();
$('#prHead').show();
}
if (!ftPieData || ftPieData.length == 0) {
$('#ftCont').hide();
$('#ftHead').hide();
} else {
$('#ftCont').show();
$('#ftHead').show();
}
$('#headCont').show();
var psdata = new Array();
var prdata = new Array();
var ftdata = new Array();
var prSum = 0, psSum = 0, ftSum = 0;
var i;
var title;
var palette = PixelAdmin.settings.consts.COLORS;
var chartColors = [];
for (i = 0; i < prPieData.length; i++) {
if (prPieData[i].PresetColor) {
prdata.push({ label: prPieData[i].Label, value: prPieData[i].Value, color: prPieData[i].PresetColor, idx: i });
chartColors.push(prPieData[i].PresetColor);
}
else {
var c = getColorFromPalette(palette, i);
prdata.push({ label: prPieData[i].Label, value: prPieData[i].Value, color: c, idx: i });
chartColors.push(c);
}
prSum += prPieData[i].Value;
}
for (i = 0; i < ftPieData.length; i++) {
if (ftPieData[i].PresetColor) {
ftdata.push({ label: ftPieData[i].Label, value: ftPieData[i].Value, color: ftPieData[i].PresetColor, idx: i });
chartColors.push(ftPieData[i].PresetColor);
}
else {
var c = getColorFromPalette(palette, i);
ftdata.push({ label: ftPieData[i].Label, value: ftPieData[i].Value, color: c, idx: i });
chartColors.push(c);
}
ftSum += ftPieData[i].Value;
}
for (i = 0; i < psPieData.length; i++) {
if (psPieData[i].PresetColor) {
psdata.push({ label: psPieData[i].Label, value: psPieData[i].Value, color: psPieData[i].PresetColor, idx: i });
chartColors.push(pieData[i].PresetColor);
}
else {
var c = getColorFromPalette(palette, i);
psdata.push({ label: psPieData[i].Label, value: psPieData[i].Value, color: c, idx: i });
chartColors.push(c);
}
psSum += psPieData[i].Value;
}
// Init Chart
if (prdata.length > 0)
Morris.Donut({
element: 'skillPiePr',
data: prdata,
colors: chartColors,
resize: true,
labelColor: '#888',
formatter: function (y) { return Math.round(y * 100 / (prSum != 0 ? prSum : 1)) + "%" }
}).on('click', function (i, row) {
if (row.idx == null || prdata.length <= row.idx)// || row.label != "Other")
return;
var index;
for (var i = 0; i < prPieData.length; i++) {
if (prPieData[i].Label == row.label) {
index = i;
break;
}
}
if ($scope.Filter.GroupMode) {
$scope.setSkillGroupsFilterFromPie(prPieData[index].TypeId);
}
else
$scope.setSkillsFilterFromPie(prPieData[index].TypeId);
if ($scope.State.DataLoaded && $scope.filterIsValid())
$scope.rebuildViewModel(true);
});
if (psdata.length > 0)
Morris.Donut({
element: 'skillPiePs',
data: psdata,
colors: chartColors,
resize: true,
labelColor: '#888',
formatter: function (y) { return Math.round(y * 100 / (psSum != 0 ? psSum : 1)) + "%" }
}).on('click', function (i, row) {
if (row.idx == null || psdata.length <= row.idx)// || row.label != "Other")
return;
var index;
for (var i = 0; i < psPieData.length; i++) {
if (psPieData[i].Label == row.label) {
index = i;
break;
}
}
if ($scope.Filter.GroupMode) {
$scope.setSkillGroupsFilterFromPie(psPieData[index].TypeId);
}
else
$scope.setSkillsFilterFromPie(psPieData[index].TypeId);
if ($scope.State.DataLoaded && $scope.filterIsValid())
$scope.rebuildViewModel(true);
});
if (ftdata.length > 0)
Morris.Donut({
element: 'skillPieFt',
data: ftdata,
colors: chartColors,
resize: true,
labelColor: '#888',
formatter: function (y) { return Math.round(y * 100 / (ftSum != 0 ? ftSum : 1)) + "%" }
}).on('click', function (i, row) {
if (row.idx == null || ftdata.length <= row.idx)// || row.label != "Other")
return;
var index;
for (var i = 0; i < ftPieData.length; i++) {
if (ftPieData[i].Label == row.label) {
index = i;
break;
}
}
if ($scope.Filter.GroupMode) {
$scope.setSkillGroupsFilterFromPie(ftPieData[index].TypeId);
}
else
$scope.setSkillsFilterFromPie(ftPieData[index].TypeId);
if ($scope.State.DataLoaded && $scope.filterIsValid())
$scope.rebuildViewModel(true);
});
};
function getColorFromPalette(palette, idx) {
if (palette.length < 1)
return null;
if (palette.length == 1)
return palette[0];
return palette[idx % (palette.length - 1)];
}
}])
.controller('personalSkillsMatrixController', ['$scope', '$rootScope', '$http', '$filter', '$element', '$timeout', '$document', 'dataSources', 'skillsMatrixManager', function ($scope, $rootScope, $http, $filter, $element, $timeout, $document, dataSources, skillsMatrixManager) {
var editorAvailableChars = C_SKILLS_MATRIX_EDITOR_AVAILABLE_CHARS;
$scope.Settings = {
OpenerId: null,
OpenerType: 4, // Resource Details Page (enum ApplicationDashboards)
DataType: 1, // Actual data (enum SkillMatrixDataType)
ReadOnly: false
};
$scope.View = {
Header: {},
Rows: {}
};
$scope.State = {
MatrixId: "",
DataChanged: false,
DataAvailable: true,
DataLoaded: false
};
var model = null;
$scope.init = function (initData) {
if (initData && initData.model) {
if ($.isNumeric(initData.model.OpenerType))
$scope.Settings.OpenerType = Number(initData.model.OpenerType);
if (initData.model.OpenerId && (initData.model.OpenerId.length > 1))
$scope.Settings.OpenerId = initData.model.OpenerId;
if ($.isNumeric(initData.model.DataType))
$scope.Settings.DataType = initData.model.DataType;
if (initData.model.ReadOnly)
$scope.Settings.ReadOnly = initData.model.ReadOnly;
initFilters(initData);
}
editorAvailableChars = angular.copy(C_SKILLS_MATRIX_EDITOR_AVAILABLE_CHARS);
for (var index = 0; index <= C_SKILLS_MATRIX_MAX_SKILL_LEVEL; index++)
editorAvailableChars.push(String(index));
geneateMatrixId();
setDataChanged(false);
$scope.State.DataAvailable = true;
$scope.State.DataLoaded = false;
// Acquire source data model
model = skillsMatrixManager.getModel();
if ($scope.filterIsValid())
$scope.rebuildViewModel(true);
};
$scope.rebuildViewModel = function (forceReload) {
if (forceReload) {
blockUI();
try {
model.loadMatrix($scope.Settings.OpenerType,
$scope.Settings.OpenerId, $scope.Settings.DataType, $scope.Settings.ReadOnly, $scope.Filter)
.then(function (data) {
// Create indexed structs for quick resources access
createResourceHash(data);
rebuildViewModelInternal(data);
$scope.State.DataLoaded = true;
setDataChanged(false);
unblockUI();
})
.then(false, function (ex) {// fail callback, raised if any error occurred in the chain
// handle exception
unblockUI();
showErrorModal('Oops!', 'An error occurred while loading skills matrix. Please, try again later.');
});
}
catch (exception) {
unblockUI();
console.log("Error loading Skills Matrix data: " + exception);
showErrorModal('Oops!', 'An error occurred while loading skills matrix. Please, try again later.');
}
}
else {
var data = model.getMatrix();
rebuildViewModelInternal(data);
}
};
function rebuildViewModelInternal(data) {
$scope.View.Header = {
Groups: [],
Skills: []
};
$scope.View.Rows = [];
if (data) {
var usedSkills = [];
// Fill header structs
if (data.SkillGroups && (data.SkillGroups.length > 0)) {
for (var gIndex = 0; gIndex < data.SkillGroups.length; gIndex++) {
var groupContract = data.SkillGroups[gIndex];
if (groupContract.Skills && (groupContract.Skills.length > 0)) {
var groupViewItem = {
Name: groupContract.Name,
SkillsCount: groupContract.Skills.length
}
$scope.View.Header.Groups.push(groupViewItem);
for (var sIndex = 0; sIndex < groupContract.Skills.length; sIndex++) {
var skillContract = groupContract.Skills[sIndex];
var skillViewItem = {
Id: skillContract.Id,
Name: skillContract.Name
}
$scope.View.Header.Skills.push(skillViewItem);
usedSkills.push(skillContract.Id);
}
}
}
}
// Fill Teams and resources Names
var matrixSkillsCount = usedSkills.length;
if (matrixSkillsCount > 0) {
// Plain resource list
if (data.Resources && (data.Resources.length > 0)) {
for (var rIndex = 0; rIndex < data.Resources.length; rIndex++) {
var resourceContract = data.Resources[rIndex];
var resourceViewItem = {
Type: 2,
Name: resourceContract.FullName,
Cells: new Array(matrixSkillsCount),
Id: resourceContract.Id
};
$scope.View.Rows.push(resourceViewItem);
}
}
// Fill matrix with values
for (rIndex = 0; rIndex < $scope.View.Rows.length; rIndex++) {
var currentRow = $scope.View.Rows[rIndex];
if (currentRow && (currentRow.Type == 2)) {
var resourceId = currentRow.Id;
for (sIndex = 0; sIndex < usedSkills.length; sIndex++) {
var cellValue = getCellValue(resourceId, usedSkills[sIndex], data);
currentRow.Cells[sIndex] = cellValue;
}
}
}
// Set visibility for Skill list options
setSkillOptionsVisibility();
}
$scope.State.DataAvailable = ($scope.View.Rows.length > 0) && (matrixSkillsCount > 0);
}
};
$scope.startEditCell = function (cellViewItem, rowIndex, colIndex, t) {
if (!cellViewItem || !t)
return;
$scope.watchKeyInput(t, rowIndex, colIndex);
};
$scope.watchKeyInput = function (t, rowIndex, colIndex) {
$timeout(function () {
if (t.$editable.inputEl.select)
t.$editable.inputEl.select();
else if (t.$editable.inputEl.setSelectionRange)
t.$editable.inputEl.setSelectionRange(0, t.$editable.inputEl.val().length);
}, 3);
t.$editable.inputEl.on('keydown', function (e) {
if (e.char && (e.char.length > 0) && (editorAvailableChars.indexOf(e.char) < 0) &&
e.which && (C_SKILLS_MATRIX_EDITOR_AVAILABLE_KEYSCODES.indexOf(e.which) < 0)) {
e.preventDefault();
return;
}
if (e.which == 9) { //when tab key is pressed
e.preventDefault();
t.$form.$submit();
var nextCellId = undefined;
if (e.shiftKey) { // On shift + tab we look for prev cell to move to
nextCellId = getPrevCellId(rowIndex, colIndex);
}
else {
// Look for next cell ID we should move to
nextCellId = getNextCellId(rowIndex, colIndex);
}
if (nextCellId) {
var nextCellItem = $($element).find('td#' + nextCellId);
if (nextCellItem && (nextCellItem.length == 1)) {
$timeout(function () {
nextCellItem.click();
}, 0);
}
}
$scope.$apply(function () {
// SA: Leave this block to force update submitted cell view by angular
});
}
});
};
$scope.checkEditorValue = function (newValue, rowIndex, colIndex) {
var errorMessage = "Invalid value";
var levelToSave = undefined;
var interestedToSave = false;
if (!$.isNumeric(rowIndex) || !$.isNumeric(colIndex)) {
console.log("checkEditorValue: Matrix cell coordinates are not specified");
return "Internal error";
}
if (newValue && (newValue.length > 0)) {
var trimmedValue = $.trim(newValue).toLowerCase();
interestedToSave = (trimmedValue.indexOf("i") >= 0);
var filteredValue = trimmedValue.replace(new RegExp('i', 'g'), "");
if (trimmedValue.length > 2) {
// Value can't be longer, than 2 letters
return errorMessage;
}
if ($.isNumeric(filteredValue))
filteredValue = Number(filteredValue);
else {
if (filteredValue.length > 0) {
// Value is not a number. May contain letters
return errorMessage;
}
else {
// No numeric component in the user specified value.
// If interestedToSave = true, we assume new skill level is 0.
filteredValue = interestedToSave ? 0 : undefined;
}
}
if ((filteredValue !== undefined) && ((filteredValue < 0) || (filteredValue > C_SKILLS_MATRIX_MAX_SKILL_LEVEL))) {
// Value is out the valid range
return errorMessage;
}
levelToSave = filteredValue;
interestedToSave = (trimmedValue.indexOf("i") >= 0);
}
var resourceId = $scope.View.Rows[rowIndex].Id;
var skillId = $scope.View.Header.Skills[colIndex].Id;
// iterate through all rows and update all rows for the specified resource (in case there are multiple teams assignment for this resource)
for (var iRow = 0; iRow < $scope.View.Rows.length; iRow++) {
if ($scope.View.Rows[iRow].Id == resourceId) {
var cellViewItem = $scope.View.Rows[iRow].Cells[colIndex];
cellViewItem.Level = levelToSave;
cellViewItem.Interested = interestedToSave;
updateCellEditorValue(cellViewItem);
}
}
var dataChanged = model.updateValue(resourceId, skillId, levelToSave, interestedToSave);
setDataChanged(dataChanged || $scope.State.DataChanged);
//required to be false by xeditable grid
return false;
};
$scope.onTxtBlur = function (cellViewItem, txt) {
txt.$form.$submit();
};
$scope.saveChanges = function () {
if ($scope.Settings.ReadOnly || !$scope.State.DataLoaded)
return;
blockUI();
model.saveMatrix().then(function () {
setDataChanged(false);
var data = model.getMatrix();
$rootScope.$broadcast('skillsMatrixSaved', data);
unblockUI();
bootbox.alert('Skills matrix has been saved');
}).then(false, function (ex) {// fail callback, raised if any error occurred in the chain
// handle exception
showErrorModal('Oops!', 'An error occurred while saving skills matrix. Please, try again later.');
unblockUI();
});
};
function setDataChanged(value) {
$scope.State.DataChanged = value;
if (value)
addPageLeaveHandler();
else
removePageLeaveHandler();
}
function addPageLeaveHandler() {
window.onbeforeunload = function (e) {
var message = "Skills Matrix contains unsaved changes. Are you want to leave the page and discard changes?",
e = e || window.event;
// For IE and Firefox
if (e) {
e.returnValue = message;
}
// For Safari
return message;
};
}
function removePageLeaveHandler() {
window.onbeforeunload = null;
}
function getNextCellId(rowIndex, colIndex) {
var lastColIndex = $scope.View.Header.Skills.length - 1;
var lastRowIndex = $scope.View.Rows.length - 1;
var nextRow = rowIndex;
var nextCol = colIndex;
if (colIndex < lastColIndex) {
nextCol++;
}
else {
nextRow = rowIndex < lastRowIndex ? rowIndex + 1 : 0;
nextCol = 0;
if (nextRow != rowIndex) {
var startedAtRow = rowIndex;
var matrixLastRowPassed = false;
while (($scope.View.Rows[nextRow].Type != 2) && ((nextRow != startedAtRow) || !matrixLastRowPassed)) {
nextRow++;
if (nextRow > lastRowIndex) {
nextRow = 0;
matrixLastRowPassed = true;
};
}
var nextCellFound = ($scope.View.Rows[nextRow].Type == 2) && (nextRow != startedAtRow);
}
else
// One cell matrix
nextCellFound = false;
if (!nextCellFound) {
// Next cell found
return;
}
}
// Next cell found. Return it's ID in html
return $scope.State.MatrixId + '_' + nextRow + '_' + nextCol;
}
function getPrevCellId(rowIndex, colIndex) {
var lastColIndex = $scope.View.Header.Skills.length - 1;
var lastRowIndex = $scope.View.Rows.length - 1;
var nextRow = rowIndex;
var nextCol = colIndex;
if (colIndex > 0) {
nextCol--;
}
else {
nextRow = rowIndex > 0 ? rowIndex - 1 : lastRowIndex;
nextCol = lastColIndex;
if (nextRow != rowIndex) {
var startedAtRow = rowIndex;
var matrixFirstRowPassed = false;
while (($scope.View.Rows[nextRow].Type != 2) && ((nextRow != startedAtRow) || !matrixFirstRowPassed)) {
nextRow--;
if (nextRow < 0) {
nextRow = lastRowIndex;
matrixFirstRowPassed = true;
};
}
var nextCellFound = ($scope.View.Rows[nextRow].Type == 2) && (nextRow != startedAtRow);
}
else
// One cell matrix
nextCellFound = false;
if (!nextCellFound) {
// Next cell found
return;
}
}
// Next cell found. Return it's ID in html
return $scope.State.MatrixId + '_' + nextRow + '_' + nextCol;
}
// ======================== Helper internal functions ============================ //
var resourceHash = {};
function createResourceHash(data) {
destroyResourceHash();
if (!data || !data.Resources || (data.Resources.length < 1))
return;
for (var rIndex = 0; rIndex < data.Resources.length; rIndex++) {
var resourceContract = data.Resources[rIndex];
var resourceId = resourceContract.Id;
if (resourceId && (resourceId.length > 0)) {
resourceHash[resourceId] = rIndex;
}
}
}
function destroyResourceHash() {
resourceHash = {};
}
function getResourceById(id, data) {
if (!resourceHash || !data || !data.Resources || (data.Resources.length < 1))
return;
var rIndex = resourceHash[id];
if ((rIndex != undefined) && !isNaN(rIndex) && $.isNumeric(rIndex)) {
return data.Resources[rIndex];
}
};
function getCellValue(resourceId, skillId, data) {
if (!resourceId || !skillId || !data || !data.Values)
return;
var cellViewItem = {
CssClass: "",
EditorValue: "",
Interested: false,
}
var key = resourceId + "#" + skillId;
var cellValueContract = data.Values[key];
if (cellValueContract) {
cellViewItem.Interested = cellValueContract.Interested;
cellViewItem.Level = cellValueContract.Level;
}
updateCellEditorValue(cellViewItem);
return cellViewItem;
}
function updateCellEditorValue(cellViewItem) {
if (!cellViewItem)
return;
cellViewItem.CssClass = "skill-cell-level";
cellViewItem.EditorValue = "";
if ($.isNumeric(cellViewItem.Level)) {
cellViewItem.EditorValue += String(cellViewItem.Level);
cellViewItem.CssClass += String(cellViewItem.Level);
}
if (cellViewItem.Interested) {
cellViewItem.EditorValue += "i";
}
}
function geneateMatrixId() {
var result = "";
var variants = "abcdefghijklmnopqrstuvwxyz0123456789";
for (var i = 0; i < 10; i++)
result += variants.charAt(Math.floor(Math.random() * variants.length));
$scope.State.MatrixId = "skmpr_" + result;
}
// ======================== Filter management ============================ //
$scope.FilterOptions = {};
$scope.FilterOptionsIndex = {}; // Index for quick access to filter options
$scope.Filter = {
Skills: []
};
function initFilters(initData) {
$scope.Filter = {
SkillsWithDataOnly: true
};
// Filtering options
$scope.FilterOptions = {
Skills: []
};
if (initData.model.FilterOptions) {
if (initData.model.FilterOptions.Skills) {
$scope.FilterOptions.Skills = initData.model.FilterOptions.Skills;
var lastSkillGroupId = null;
for (var index = 0; index < $scope.FilterOptions.Skills.length; index++) {
$scope.FilterOptions.Skills[index].Visible = true;
var item = $scope.FilterOptions.Skills[index];
if (item.Group && (item.Group.Name !== undefined) && (item.Group.Disabled !== undefined) &&
$.isNumeric(item.Group.Name)) {
// Is Skills Group
$scope.FilterOptions.Skills[index].Class = "select2-group-option";
$scope.FilterOptions.Skills[index].SkillGroupId = null;
lastSkillGroupId = item.Value;
}
else {
// Is Skill
$scope.FilterOptions.Skills[index].Class = "ddl-level-item pad-left";
$scope.FilterOptions.Skills[index].SkillGroupId = lastSkillGroupId;
}
}
// Create Skill Filter Option Index for quick access
recreateSkillsFilterOptionsIndex();
}
}
$timeout(function () {
var control = createSkillsSelector();
control.on("change", function (e) {
var newSelection = [];
$scope.$apply(function () {
var newSelection = setSkillOptionsVisibility();
setSelect2MultiSelectionInternal($(e.currentTarget), newSelection);
});
});
});
}
function getSkillFilterOptionById(id) {
var foundItem = null;
if ($scope.FilterOptionsIndex && $scope.FilterOptionsIndex.Skills)
foundItem = $scope.FilterOptionsIndex.Skills[id]
return foundItem;
}
function getSkillFilterOptionByGroupId(id) {
var foundItems = [];
angular.forEach($scope.FilterOptions.Skills, function (item, index) {
if (item.SkillGroupId && item.SkillGroupId == id) {
foundItems.push(item.Value);
}
});
return foundItems;
}
// Creates Indexed associative array for Skills Filter Options quick access
function recreateSkillsFilterOptionsIndex() {
$scope.FilterOptionsIndex.Skills = {};
if (!$scope.FilterOptions || !$scope.FilterOptions.Skills)
return;
for (var index = 0; index < $scope.FilterOptions.Skills.length; index++) {
var skillId = $scope.FilterOptions.Skills[index].Value;
$scope.FilterOptionsIndex.Skills[skillId] = $scope.FilterOptions.Skills[index];
}
}
function setSelect2MultiSelectionInternal(control, itemsToSelect) {
control.select2('destroy');
createSkillsSelector();
if (itemsToSelect) {
if (itemsToSelect.length > 0) {
var selection = angular.copy(itemsToSelect);
for (var index = 0; index < selection.length; index++) {
selection[index] = "string:" + selection[index];
}
}
control.select2('val', selection);
}
}
function createSkillsSelector() {
var control = $($element).find('[name=select2FilterSkills]').select2({
formatResult: function (item, container, query) {
var itemId = item.id.replace("string:", "");
var item = getSkillFilterOptionById(itemId);
var result = $("<span>" + item.Text + "</span>")
if (item) {
$(result).attr("class", item.Class);
}
return result;
}
})
return control;
}
$scope.filterIsValid = function () {
return true;
};
function setSkillOptionsVisibility() {
if (!$scope.View.Header || !$scope.View.Header.Skills)
return;
// Get selected skills
var matrixItems = [];
var selectedItems = [];
var newSelection = angular.copy($scope.Filter.Skills);
for (var index = 0; index < $scope.View.Header.Skills.length; index++)
matrixItems.push($scope.View.Header.Skills[index].Id);
if ($scope.Filter.Skills) {
for (var index = 0; index < $scope.Filter.Skills.length; index++)
selectedItems.push($scope.Filter.Skills[index]);
}
var lastSkillGroupItem = null;
var visibleSkillsExistInGroup = false;
// Reset visibility status for options
for (var index = 0; index < $scope.FilterOptions.Skills.length; index++) {
var currentItem = $scope.FilterOptions.Skills[index];
if (currentItem.Group && (currentItem.Group.Name !== undefined) && $.isNumeric(currentItem.Group.Name)) {
// Item is Skill Group
if (lastSkillGroupItem) {
// Set visibility for previous Skill Group Item (hide it, if all its skills are hidden)
lastSkillGroupItem.Visible =
visibleSkillsExistInGroup || (selectedItems.indexOf(lastSkillGroupItem.Value) >= 0);
}
lastSkillGroupItem = currentItem;
visibleSkillsExistInGroup = false;
}
else {
// Item is skill
var skillId = currentItem.Value;
var skillInMatrix = matrixItems.indexOf(skillId) >= 0;
var skillInSelection = selectedItems.indexOf(skillId) >= 0;
var skillGroupInSelection = selectedItems.indexOf(currentItem.SkillGroupId) >= 0;
var skillIsVisible = !skillInMatrix &&
((skillInSelection && !skillGroupInSelection) || (!skillInSelection && !skillGroupInSelection));
$scope.FilterOptions.Skills[index].Visible = skillIsVisible;
visibleSkillsExistInGroup = visibleSkillsExistInGroup || skillIsVisible;
if (newSelection && skillInSelection && skillGroupInSelection) {
var skillPos = newSelection.indexOf(skillId);
if (skillPos >= 0) {
newSelection.splice(skillPos, 1);
}
}
}
}
if (lastSkillGroupItem) {
// Set visibility for previous Skill Group Item (hide it, if all its skills are hidden)
lastSkillGroupItem.Visible =
visibleSkillsExistInGroup || (selectedItems.indexOf(lastSkillGroupItem.Value) >= 0);
}
return newSelection;
}
$scope.cancelChanges = function () {
$scope.rebuildViewModel(true);
setDataChanged(false);
}
$scope.addSkillsToMatrix = function () {
var queueToAdd = getSkillsAndGroupsToAddToMatrix();
if (queueToAdd && (queueToAdd.length > 0)) {
// Add Skill Groups and Skills to matrix
model.createRestorePoint();
try {
addSkillGroupsToMatrixInternal(queueToAdd);
addSkillsToMatrixInternal(queueToAdd);
model.commitChanges();
}
catch (e) {
model.rollbackChanges();
}
}
$scope.rebuildViewModel(false);
// Clear selection and update options in select2
var skillsSelectorControl = $($element).find('[name=select2FilterSkills]');
$scope.Filter.Skills = [];
setSelect2MultiSelectionInternal(skillsSelectorControl, []);
setSkillOptionsVisibility();
$timeout(function () {
setDataChanged(true);
});
}
function getSkillsAndGroupsToAddToMatrix() {
if (!$scope.Filter || !$scope.Filter.Skills || !$scope.FilterOptionsIndex.Skills ||
($scope.Filter.Skills.length < 1))
return;
var skillOptionKeys = Object.keys($scope.FilterOptionsIndex.Skills);
var itemsToAdd = [];
var itemsToAddIndex = {};
var currentSkillItem = null;
var currentGroupItem = null;
var currentOptionItem = null;
for (var index = 0; index < $scope.Filter.Skills.length; index++) {
var currentItemId = $scope.Filter.Skills[index];
if (skillOptionKeys.indexOf(currentItemId) >= 0) {
currentOptionItem = $scope.FilterOptionsIndex.Skills[currentItemId];
if (currentOptionItem.Group && !currentOptionItem.SkillGroupId) {
// Item is Skill Group
if (!itemsToAddIndex[currentOptionItem.Value]) {
// Not exist in the queue. Perform adding
currentGroupItem = {
Id: currentOptionItem.Value,
Skills: null,
FromSelection: true
};
itemsToAdd.push(currentGroupItem);
itemsToAddIndex[currentGroupItem.Id] = currentGroupItem;
}
else {
}
}
else {
// Item is Skill item
if (skillOptionKeys.indexOf(currentOptionItem.SkillGroupId) >= 0) {
if (!itemsToAddIndex[currentOptionItem.SkillGroupId]) {
// Add Skill Group to queue
currentGroupItem = {
Id: currentOptionItem.SkillGroupId,
Skills: null,
FromSelection: false
};
itemsToAdd.push(currentGroupItem);
itemsToAddIndex[currentGroupItem.Id] = currentGroupItem;
}
else
// Skill Group exists in queue. Get it
currentGroupItem = itemsToAddIndex[currentOptionItem.SkillGroupId];
if (!currentGroupItem.Skills)
currentGroupItem.Skills = [];
if (!itemsToAddIndex[currentOptionItem.Value]) {
// Add Skill to queue as child for its Skill Group
currentSkillItem = {
Id: currentOptionItem.Value,
FromSelection: true
};
currentGroupItem.Skills.push(currentSkillItem);
itemsToAddIndex[currentSkillItem.Id] = currentSkillItem;
}
}
}
}
}
// Go through Skill Groups in queue and add skills to ones, that are market as FromSelection
for (var gindex = 0; gindex < itemsToAdd.length; gindex++) {
currentGroupItem = itemsToAdd[gindex];
if (currentGroupItem.FromSelection) {
// Group was selected in select2. Append all its skills to the adding queue
var skillsInGroup = getSkillFilterOptionByGroupId(currentGroupItem.Id);
if (skillsInGroup && (skillsInGroup.length > 0)) {
for (var sIndex = 0; sIndex < skillsInGroup.length; sIndex++) {
var currentSkillItemId = skillsInGroup[sIndex];
if (!itemsToAddIndex[currentSkillItemId]) {
// New skill for group. Add to queue
if (!currentGroupItem.Skills)
currentGroupItem.Skills = [];
currentSkillItem = {
Id: currentSkillItemId,
FromSelection: false
};
currentGroupItem.Skills.push(currentSkillItem);
itemsToAddIndex[currentSkillItem.Id] = currentSkillItem;
}
}
}
}
}
return itemsToAdd;
}
function addSkillGroupsToMatrixInternal(items2Add) {
for (var index = 0; index < items2Add.length; index++) {
var currentGroupItem = items2Add[index];
if (!model.isSkillGroupExists(currentGroupItem.Id)) {
var hasChildSkills = (currentGroupItem.Skills && (currentGroupItem.Skills.length > 0))
var groupName = $scope.FilterOptionsIndex.Skills[currentGroupItem.Id].Text;
model.addSkillsGroupToMatrix(currentGroupItem.Id, groupName, !hasChildSkills);
}
}
}
function addSkillsToMatrixInternal(items2Add) {
for (var gindex = 0; gindex < items2Add.length; gindex++) {
var currentGroupItem = items2Add[gindex];
if (currentGroupItem.Skills && (currentGroupItem.Skills.length > 0)) {
for (var sIndex = 0; sIndex < currentGroupItem.Skills.length; sIndex++) {
var currentSkillItem = currentGroupItem.Skills[sIndex];
var skillName = $scope.FilterOptionsIndex.Skills[currentSkillItem.Id].Text;
model.addSkillToMatrix(currentGroupItem.Id, currentSkillItem.Id, skillName, false);
}
}
}
}
}])