'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(); }); }]);