429 lines
18 KiB
JavaScript
429 lines
18 KiB
JavaScript
'use strict';
|
|
|
|
app
|
|
.factory('skillsManager', ['$q', '$http', 'dataSources', function ($q, $http, dataSources) {
|
|
var _skillsTree = null;
|
|
var MaxIterations = 50; // maximum iterations limit to avoid infinite loop
|
|
|
|
function generateNewId(collection) {
|
|
var newId, oldId, iterations=0;
|
|
do {
|
|
oldId = newId;
|
|
newId = Math.uuid();
|
|
if (oldId == newId) {
|
|
console.error('Math.uuid() generated duplicate identities: ' + newId);
|
|
break;
|
|
}
|
|
} while (++iterations < MaxIterations && collection[newId] != null);
|
|
return newId;
|
|
};
|
|
// BLL, used to load/manage/store skills data
|
|
var serviceContainer = {
|
|
getSkills: function() {
|
|
return _skillsTree;
|
|
},
|
|
backupSkills: function () {
|
|
return angular.copy(_skillsTree);
|
|
},
|
|
setSkills: function (data) {
|
|
_skillsTree = data;
|
|
},
|
|
loadSkills: function (openerType, openerId, forceLoadFromServer) {
|
|
// return once loaded data instead of get from server
|
|
if (!forceLoadFromServer && _skillsTree) {
|
|
var deferrer = $q.defer();
|
|
deferrer.resolve(_skillsTree);
|
|
return deferrer.promise;
|
|
}
|
|
// prepare and return promise
|
|
var filterObj = null;
|
|
|
|
if (openerType && openerId) {
|
|
// SA. ENV-1328. OpenerType (enum SkillsMatrixQueryModel.Opener) corresponds to the Filter Mode
|
|
// (enum SkillsMaxtrixFilterModel.FilterMode). So, we set specified openerType to Filter Mode
|
|
filterObj = {
|
|
Entities: [openerId],
|
|
Mode: openerType
|
|
}
|
|
}
|
|
|
|
return dataSources.getSkills(openerType, openerId, filterObj).then(function (data) {
|
|
_skillsTree = data;
|
|
return _skillsTree;
|
|
});
|
|
},
|
|
saveSkills: function () {
|
|
return dataSources.saveSkills(_skillsTree).then(function (data) {
|
|
_skillsTree = data;
|
|
return _skillsTree;
|
|
});
|
|
},
|
|
getSkillById: function (id, isGroup) {
|
|
var keys = Object.keys(_skillsTree);
|
|
for (var i = 0; i < keys.length; i++) {
|
|
var group = _skillsTree[keys[i]];
|
|
if (group.Id == id) {
|
|
return group;
|
|
}
|
|
if (!isGroup && group.Children) // if not defined or isGroup=false then search in children
|
|
{
|
|
var childKeys = Object.keys(group.Children);
|
|
for (var j = 0; j < childKeys.length; j++) {
|
|
var skill = group.Children[childKeys[j]];
|
|
if (skill.Id == id) {
|
|
return skill;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
},
|
|
updateSkill: function (id, newName) {
|
|
var obj = this.getSkillById(id);
|
|
if (obj == null)
|
|
return;
|
|
obj.Name = newName;
|
|
obj.IsChanged = true;
|
|
},
|
|
updateSkillGroupCopyDataUponDelete: function(id, bCopyGroupData) {
|
|
var obj = this.getSkillById(id);
|
|
if (obj == null)
|
|
return;
|
|
obj.CopyDataUponDelete = bCopyGroupData;
|
|
},
|
|
removeSkill: function (skill, isGroup) {
|
|
var skillGroup = this.getSkillById(skill.ParentId || skill.Id);
|
|
if (!isGroup) // remove skill from group
|
|
{
|
|
// mark skill as deleted
|
|
var skill = skillGroup.Children[skill.Id];
|
|
|
|
skill.IsDeleted = true;
|
|
// find a number of non-deleted children in current skill group
|
|
var numActive = 0;
|
|
angular.forEach(skillGroup.Children, function (item) {
|
|
if (!item.IsDeleted)
|
|
numActive++;
|
|
});
|
|
// toggle hasChildren field if necessary
|
|
if (skillGroup.HasChildren && numActive <= 0) {
|
|
skillGroup.HasChildren = false;
|
|
skillGroup.IsChanged = true;
|
|
}
|
|
} else { // remove entire skill group
|
|
skillGroup.IsDeleted = true;
|
|
}
|
|
},
|
|
addSkillGroup: function (newValue) {
|
|
// generate a new id and make sure it does not exist yet
|
|
var newId = generateNewId(_skillsTree);
|
|
_skillsTree[newId] = {
|
|
Id: newId,
|
|
Name: newValue,
|
|
HasChildren: false,
|
|
HasS2RData: false,
|
|
CopyDataUponDelete: false,
|
|
Children: {},
|
|
IsNew: true
|
|
};
|
|
return newId;
|
|
},
|
|
addSkill: function (skillGroup) {
|
|
// generate a new id and make sure it does not exist yet
|
|
var newId = generateNewId(_skillsTree);
|
|
var obj = _skillsTree[skillGroup.Id];
|
|
obj.HasChildren = true;
|
|
obj.IsChanged = obj.IsNew ? false : true;
|
|
obj.Children[newId] = {
|
|
Id: newId,
|
|
Name: 'Enter Skill Name here',
|
|
HasChildren: false,
|
|
Children: {},
|
|
IsNew: true,
|
|
ParentId: skillGroup.Id
|
|
};
|
|
return newId;
|
|
}
|
|
};
|
|
return serviceContainer;
|
|
}])
|
|
.controller('skillsEditController', ['$scope', '$rootScope', '$http', '$filter', '$element', '$timeout', '$document', 'skillsManager', function ($scope, $rootScope, $http, $filter, $element, $timeout, $document, skillsManager) {
|
|
// contains all controller specific data
|
|
$scope.data = {
|
|
ViewModel: {}, // visual representation of the model (View Model)
|
|
GroupsWithS2RAndChildren: {},
|
|
SavedState: { // storage for View Model properties which should be retained between rebuilding (like Collapsed)
|
|
Collapsed: {}
|
|
},
|
|
AddItemName: null, // input text field for new skill's name
|
|
OpenerId: null,
|
|
OpenerType: 0
|
|
};
|
|
var ENTER_SKILL_NAME_HERE = "Enter Skill Group Name here";
|
|
|
|
$scope.init = function (initData) {
|
|
$scope.data.OpenerType = initData.model.OpenerType;
|
|
$scope.data.OpenerId = initData.model.OpenerId;
|
|
};
|
|
// gets data source, rebuilds View Model and opens a modal form
|
|
$scope.openEditForm = function () {
|
|
blockUI();
|
|
|
|
skillsManager.loadSkills($scope.data.OpenerType, $scope.data.OpenerId, true).then(function (data) {
|
|
// build UI Model
|
|
$scope.buildViewModel();
|
|
// notify document that it should open modal form
|
|
$document.trigger('skills.open-edit-skills-modal');
|
|
}).then(false, function (ex) {// fail callback, raised if any error occurred in the chain
|
|
showErrorModal('Oops!', 'An error occurred while loading skills. Please, try again later.');
|
|
}).finally(function () {
|
|
unblockUI();
|
|
});
|
|
};
|
|
// rebuilds View Model from DAL datasource
|
|
$scope.buildViewModel = function () {
|
|
$scope.data.ViewModel = [];
|
|
$scope.data.GroupsWithS2RAndChildren = [];
|
|
$.each(skillsManager.getSkills(), function (i, group) {
|
|
if (group.IsDeleted)
|
|
return;
|
|
var state = $scope.data.SavedState.Collapsed[group.Id] || {
|
|
Collapsed: true,
|
|
CollapsedClass: 'fa-plus-square'
|
|
};
|
|
var groupItem = {
|
|
Id: group.Id,
|
|
Name: group.Name,
|
|
HasChildren: group.HasChildren,
|
|
Children: [],
|
|
HasS2RData: group.HasS2RData,
|
|
CopyDataUponDelete: group.CopyDataUponDelete,
|
|
Collapsed: state.Collapsed,
|
|
CollapsedClass: state.CollapsedClass
|
|
};
|
|
// enhance skill group objects to store expanded states between actions
|
|
if (group.Children && Object.keys(group.Children).length > 0) {
|
|
$.each(group.Children, function (skillIndex, skill) {
|
|
if (skill.IsDeleted)
|
|
return;
|
|
groupItem.Children.push({
|
|
Id: skill.Id,
|
|
Name: skill.Name,
|
|
ParentId: group.Id
|
|
});
|
|
});
|
|
}
|
|
$scope.data.ViewModel.push(groupItem);
|
|
if (groupItem.HasS2RData && groupItem.HasChildren)
|
|
$scope.data.GroupsWithS2RAndChildren.push(groupItem);
|
|
$scope.data.GroupsWithS2RAndChildren.sort(sortGroupByName);
|
|
|
|
});
|
|
};
|
|
function sortGroupByName(a, b) {
|
|
var aName = a.Name.toLowerCase();
|
|
var bName = b.Name.toLowerCase();
|
|
return ((aName < bName) ? -1 : ((aName > bName) ? 1 : 0));
|
|
};
|
|
//======================== x-editable event handlers ==============================//
|
|
$scope.onShow = function (obj) {
|
|
obj.$form.$show([obj.$editable]);
|
|
};
|
|
$scope.onTxtBlur = function (txt) {
|
|
var newValue = txt.$data;
|
|
if (!newValue || newValue.length == 0 || $.trim(newValue).length == 0)
|
|
{
|
|
txt.$form.$valid = true;
|
|
txt.$form.$cancel();
|
|
}
|
|
else
|
|
txt.$form.$submit([txt.$editable]);
|
|
};
|
|
$scope.watchKeyInput = function (t) {
|
|
$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) {
|
|
$scope.$apply(function () {
|
|
if (e.which == 13) { //when enter key is pressed
|
|
var parentRow = t.$editable.elem.closest('tr.skillRow').prevAll('tr:has("a.addSkill"):last');
|
|
var isGroup = t.$editable.elem.data('isgroup');
|
|
var newGroupId;
|
|
e.preventDefault();
|
|
t.$form.$submit([t.$editable]);
|
|
if (isGroup) {
|
|
newGroupId = $scope.addSkillGroup(true);
|
|
}
|
|
else {
|
|
$timeout(function () {
|
|
$(parentRow).find('a.addSkill:first').click();
|
|
});
|
|
}
|
|
$timeout(function () {
|
|
var tab2Cell;
|
|
if (isGroup) {
|
|
tab2Cell = $('table#edit-skills-table a[editable-text]').filter(function (index) {
|
|
return $(this).text().trim() == ENTER_SKILL_NAME_HERE;
|
|
}).first();// $(this).parentsUntil('table#edit-skills-table').nextAll(":has(.editable:visible):last").find(".editable:visible:last");
|
|
tab2Cell.click();
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|
|
};
|
|
$scope.checkValue = function (newValue, skill, skillIndex) {
|
|
if (!newValue || newValue.length == 0 || $.trim(newValue).length == 0) {
|
|
return 'Name required';
|
|
}
|
|
|
|
var datasourceBackup = skillsManager.backupSkills();
|
|
try {
|
|
skillsManager.updateSkill(skill.Id, newValue);
|
|
} catch (ex) {
|
|
// rollback entire operation if any error occurred
|
|
skillsManager.setSkills(datasourceBackup);
|
|
// handle exception
|
|
showErrorModal('Oops!', 'An error occurred while updating skill. Please, try again later.');
|
|
console.error(ex);
|
|
} finally {
|
|
$scope.buildViewModel();
|
|
}
|
|
|
|
//required to be false by xeditable grid
|
|
return false;
|
|
};
|
|
//======================== end x-editable event handlers ==============================//
|
|
$scope.removeSkill = function (skill, isGroup) {
|
|
if (!skill)
|
|
return;
|
|
// create a data backup to be able to rollback entire operation if any error occurred
|
|
var datasourceBackup = skillsManager.backupSkills();
|
|
try {
|
|
skillsManager.removeSkill(skill, isGroup);
|
|
} catch (ex) {
|
|
// rollback entire operation if any error occurred
|
|
skillsManager.setSkills(datasourceBackup);
|
|
// handle exception
|
|
showErrorModal('Oops!', 'An error occurred while removing skill. Please, try again later.');
|
|
console.error(ex);
|
|
} finally {
|
|
$scope.buildViewModel();
|
|
}
|
|
};
|
|
$scope.addSkillGroup = function (emptyVal) {
|
|
var newValue = $scope.data.AddItemName;
|
|
if (!emptyVal && (!newValue || newValue.length == 0)) {
|
|
clearAddSkillinput(false);
|
|
return false;
|
|
}
|
|
if (emptyVal)
|
|
{
|
|
newValue = ENTER_SKILL_NAME_HERE;
|
|
}
|
|
if (!$(frmAddSkill).valid()) {
|
|
console.error('Form validation failed and was not handled');
|
|
}
|
|
|
|
var newId = skillsManager.addSkillGroup(newValue);
|
|
clearAddSkillinput(true);
|
|
$document.trigger('skills.Changed');
|
|
return newId;
|
|
};
|
|
$scope.addSkill = function (skillGroup) {
|
|
if (!skillGroup) {
|
|
return false;
|
|
}
|
|
var datasourceBackup = skillsManager.backupSkills();
|
|
try {
|
|
var newId = skillsManager.addSkill(skillGroup);
|
|
toggleCollapsed(skillGroup, false)
|
|
$document.trigger('skills.Changed');
|
|
$timeout(function () {
|
|
$("#" + newId).click();
|
|
});
|
|
} catch (ex) {
|
|
// rollback entire operation if any error occurred
|
|
skillsManager.setSkills(datasourceBackup);
|
|
// handle exception
|
|
showErrorModal('Oops!', 'An error occurred while adding skill. Please, try again later.');
|
|
console.error(ex);
|
|
} finally {
|
|
$scope.buildViewModel();
|
|
}
|
|
};
|
|
$scope.cancelAddSkillGroup = function () {
|
|
clearAddSkillinput(false);
|
|
};
|
|
$scope.onGroupClick = function (group, e) {
|
|
toggleCollapsed(group);
|
|
};
|
|
$scope.onCopyDataUponDeleteClick = function (group) {
|
|
if(group.Id)
|
|
{
|
|
skillsManager.updateSkillGroupCopyDataUponDelete(group.Id, group.CopyDataUponDelete);
|
|
}
|
|
};
|
|
function clearAddSkillinput(isRebuildView) {
|
|
$scope.data.AddItemName = null;
|
|
if (isRebuildView)
|
|
$scope.buildViewModel();
|
|
}
|
|
function toggleCollapsed(obj, value) {
|
|
if (obj.Id)
|
|
{
|
|
var state = $scope.data.SavedState.Collapsed[obj.Id];
|
|
if (!state) {
|
|
state = {
|
|
Collapsed: obj.Collapsed,
|
|
CollapsedClass: obj.CollapsedClass
|
|
};
|
|
$scope.data.SavedState.Collapsed[obj.Id] = state;
|
|
}
|
|
}
|
|
if (value != null)
|
|
obj.Collapsed = value;
|
|
else
|
|
obj.Collapsed = !obj.Collapsed;
|
|
obj.CollapsedClass = obj.Collapsed ? 'fa-plus-square' : 'fa-minus-square';
|
|
|
|
state.Collapsed = obj.Collapsed;
|
|
state.CollapsedClass = obj.CollapsedClass;
|
|
return obj;
|
|
}
|
|
$scope.saveChanges = function () {
|
|
blockUI();
|
|
var datasourceBackup = skillsManager.backupSkills();
|
|
skillsManager.saveSkills().then(function (skills) {
|
|
$scope.buildViewModel();
|
|
if (typeof resetEditSkillsChanged === 'function') {
|
|
resetEditSkillsChanged();
|
|
}
|
|
$rootScope.$broadcast('skillsSaved', skills, function () {
|
|
$document.trigger('skills.close-edit-skills-modal');
|
|
unblockUI();
|
|
});
|
|
}).then(false, function (ex) {// fail callback, raised if any error occurred in the chain
|
|
// rollback entire operation if any error occurred
|
|
skillsManager.setSkills(datasourceBackup);
|
|
// handle exception
|
|
showErrorModal('Oops!', 'An error occurred while saving skills. Please, try again later.');
|
|
unblockUI();
|
|
});
|
|
};
|
|
$scope.cancelForm = function () {
|
|
// we need timeout to wait for cancel event on input field if it is empty
|
|
$timeout(function () {
|
|
//$scope.data.ViewModel = [];
|
|
$document.trigger('skills.close-edit-skills-modal');
|
|
});
|
|
};
|
|
//======================== event handlers ==============================//
|
|
$scope.$on('openEditSkillsModal', function () {
|
|
$scope.openEditForm();
|
|
});
|
|
}]); |