2693 lines
82 KiB
JavaScript
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}])
|