AutorÃa | Ultima modificación | Ver Log |
// This file is part of Moodle - http://moodle.org///// Moodle is free software: you can redistribute it and/or modify// it under the terms of the GNU General Public License as published by// the Free Software Foundation, either version 3 of the License, or// (at your option) any later version.//// Moodle is distributed in the hope that it will be useful,// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the// GNU General Public License for more details.//// You should have received a copy of the GNU General Public License// along with Moodle. If not, see <http://www.gnu.org/licenses/>./*** Handle selection changes and actions on the competency tree.** @module tool_lp/competencyactions* @copyright 2015 Damyon Wiese <damyon@moodle.com>* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/define(['jquery','core/url','core/templates','core/notification','core/str','core/ajax','tool_lp/dragdrop-reorder','tool_lp/tree','tool_lp/dialogue','tool_lp/menubar','tool_lp/competencypicker','tool_lp/competency_outcomes','tool_lp/competencyruleconfig','core/pending',],function($, url, templates, notification, str, ajax, dragdrop, Ariatree, Dialogue, menubar, Picker, Outcomes, RuleConfig, Pending) {// Private variables and functions./** @var {Object} treeModel - This is an object representing the nodes in the tree. */var treeModel = null;/** @var {Node} moveSource - The start of a drag operation */var moveSource = null;/** @var {Node} moveTarget - The end of a drag operation */var moveTarget = null;/** @var {Number} pageContextId The page context ID. */var pageContextId;/** @var {Object} Picker instance. */var pickerInstance;/** @var {Object} Rule config instance. */var ruleConfigInstance;/** @var {Object} The competency we're picking a relation to. */var relatedTarget;/** @var {Object} Taxonomy constants indexed per level. */var taxonomiesConstants;/** @var {Array} The rules modules. Values are object containing type, namd and amd. */var rulesModules;/** @var {Number} the selected competency ID. */var selectedCompetencyId = null;/*** Respond to choosing the "Add" menu item for the selected node in the tree.* @method addHandler*/var addHandler = function() {var parent = $('[data-region="competencyactions"]').data('competency');var params = {competencyframeworkid: treeModel.getCompetencyFrameworkId(),pagecontextid: pageContextId};if (parent !== null) {// We are adding at a sub node.params.parentid = parent.id;}var relocate = function() {var queryparams = $.param(params);window.location = url.relativeUrl('/admin/tool/lp/editcompetency.php?' + queryparams);};if (parent !== null && treeModel.hasRule(parent.id)) {str.get_strings([{key: 'confirm', component: 'moodle'},{key: 'addingcompetencywillresetparentrule', component: 'tool_lp', param: parent.shortname},{key: 'yes', component: 'core'},{key: 'no', component: 'core'}]).done(function(strings) {notification.confirm(strings[0],strings[1],strings[2],strings[3],relocate);}).fail(notification.exception);} else {relocate();}};/*** A source and destination has been chosen - so time to complete a move.* @method doMove*/var doMove = function() {var frameworkid = $('[data-region="filtercompetencies"]').data('frameworkid');var requests = ajax.call([{methodname: 'core_competency_set_parent_competency',args: {competencyid: moveSource, parentid: moveTarget}}, {methodname: 'tool_lp_data_for_competencies_manage_page',args: {competencyframeworkid: frameworkid,search: $('[data-region="filtercompetencies"] input').val()}}]);requests[1].done(reloadPage).fail(notification.exception);};/*** Confirms a competency move.** @method confirmMove*/var confirmMove = function() {moveTarget = typeof moveTarget === "undefined" ? 0 : moveTarget;if (moveTarget == moveSource) {// No move to do.return;}var targetComp = treeModel.getCompetency(moveTarget) || {},sourceComp = treeModel.getCompetency(moveSource) || {},confirmMessage = 'movecompetencywillresetrules',showConfirm = false;// We shouldn't be moving the competency to the same parent.if (sourceComp.parentid == moveTarget) {return;}// If we are moving to a child of self.if (targetComp.path && targetComp.path.indexOf('/' + sourceComp.id + '/') >= 0) {confirmMessage = 'movecompetencytochildofselfwillresetrules';// Show a confirmation if self has rules, as they'll disappear.showConfirm = showConfirm || treeModel.hasRule(sourceComp.id);}// Show a confirmation if the current parent, or the destination have rules.showConfirm = showConfirm || (treeModel.hasRule(targetComp.id) || treeModel.hasRule(sourceComp.parentid));// Show confirm, and/or do the things.if (showConfirm) {str.get_strings([{key: 'confirm', component: 'moodle'},{key: confirmMessage, component: 'tool_lp'},{key: 'yes', component: 'moodle'},{key: 'no', component: 'moodle'}]).done(function(strings) {notification.confirm(strings[0], // Confirm.strings[1], // Delete competency X?strings[2], // Delete.strings[3], // Cancel.doMove);}).fail(notification.exception);} else {doMove();}};/*** A move competency popup was opened - initialise the aria tree in it.* @method initMovePopup* @param {dialogue} popup The tool_lp/dialogue that was created.*/var initMovePopup = function(popup) {var body = $(popup.getContent());var treeRoot = body.find('[data-enhance=movetree]');var tree = new Ariatree(treeRoot, false);tree.on('selectionchanged', function(evt, params) {var target = params.selected;moveTarget = $(target).data('id');});treeRoot.show();body.on('click', '[data-action="move"]', function() {popup.close();confirmMove();});body.on('click', '[data-action="cancel"]', function() {popup.close();});};/*** Turn a flat list of competencies into a tree structure (recursive).* @method addCompetencyChildren* @param {Object} parent The current parent node in the tree* @param {Object[]} competencies The flat list of competencies*/var addCompetencyChildren = function(parent, competencies) {var i;for (i = 0; i < competencies.length; i++) {if (competencies[i].parentid == parent.id) {parent.haschildren = true;competencies[i].children = [];competencies[i].haschildren = false;parent.children[parent.children.length] = competencies[i];addCompetencyChildren(competencies[i], competencies);}}};/*** A node was chosen and "Move" was selected from the menu. Open a popup to select the target.* @param {Event} e* @method moveHandler*/var moveHandler = function(e) {e.preventDefault();var competency = $('[data-region="competencyactions"]').data('competency');// Remember what we are moving.moveSource = competency.id;// Load data for the template.var requests = ajax.call([{methodname: 'core_competency_search_competencies',args: {competencyframeworkid: competency.competencyframeworkid,searchtext: ''}}, {methodname: 'core_competency_read_competency_framework',args: {id: competency.competencyframeworkid}}]);// When all data has arrived, continue.$.when.apply(null, requests).done(function(competencies, framework) {// Expand the list of competencies into a tree.var i;var competenciestree = [];for (i = 0; i < competencies.length; i++) {var onecompetency = competencies[i];if (onecompetency.parentid == "0") {onecompetency.children = [];onecompetency.haschildren = 0;competenciestree[competenciestree.length] = onecompetency;addCompetencyChildren(onecompetency, competencies);}}str.get_strings([{key: 'movecompetency', component: 'tool_lp', param: competency.shortname},{key: 'move', component: 'tool_lp'},{key: 'cancel', component: 'moodle'}]).done(function(strings) {var context = {framework: framework,competencies: competenciestree};templates.render('tool_lp/competencies_move_tree', context).done(function(tree) {new Dialogue(strings[0], // Move competency x.tree, // The move tree.initMovePopup);}).fail(notification.exception);}).fail(notification.exception);}).fail(notification.exception);};/*** Edit the selected competency.* @method editHandler*/var editHandler = function() {var competency = $('[data-region="competencyactions"]').data('competency');var params = {competencyframeworkid: treeModel.getCompetencyFrameworkId(),id: competency.id,parentid: competency.parentid,pagecontextid: pageContextId};var queryparams = $.param(params);window.location = url.relativeUrl('/admin/tool/lp/editcompetency.php?' + queryparams);};/*** Re-render the page with the latest data.* @param {Object} context* @method reloadPage*/var reloadPage = function(context) {templates.render('tool_lp/manage_competencies_page', context).done(function(newhtml, newjs) {$('[data-region="managecompetencies"]').replaceWith(newhtml);templates.runTemplateJS(newjs);}).fail(notification.exception);};/*** Perform a search and render the page with the new search results.* @param {Event} e* @method updateSearchHandler*/var updateSearchHandler = function(e) {e.preventDefault();var frameworkid = $('[data-region="filtercompetencies"]').data('frameworkid');var requests = ajax.call([{methodname: 'tool_lp_data_for_competencies_manage_page',args: {competencyframeworkid: frameworkid,search: $('[data-region="filtercompetencies"] input').val()}}]);requests[0].done(reloadPage).fail(notification.exception);};/*** Move a competency "up". This only affects the sort order within the same branch of the tree.* @method moveUpHandler*/var moveUpHandler = function() {// We are chaining ajax requests here.var competency = $('[data-region="competencyactions"]').data('competency');var requests = ajax.call([{methodname: 'core_competency_move_up_competency',args: {id: competency.id}}, {methodname: 'tool_lp_data_for_competencies_manage_page',args: {competencyframeworkid: competency.competencyframeworkid,search: $('[data-region="filtercompetencies"] input').val()}}]);requests[1].done(reloadPage).fail(notification.exception);};/*** Move a competency "down". This only affects the sort order within the same branch of the tree.* @method moveDownHandler*/var moveDownHandler = function() {// We are chaining ajax requests here.var competency = $('[data-region="competencyactions"]').data('competency');var requests = ajax.call([{methodname: 'core_competency_move_down_competency',args: {id: competency.id}}, {methodname: 'tool_lp_data_for_competencies_manage_page',args: {competencyframeworkid: competency.competencyframeworkid,search: $('[data-region="filtercompetencies"] input').val()}}]);requests[1].done(reloadPage).fail(notification.exception);};/*** Open a dialogue to show all the courses using the selected competency.* @method seeCoursesHandler*/var seeCoursesHandler = function() {var competency = $('[data-region="competencyactions"]').data('competency');var requests = ajax.call([{methodname: 'tool_lp_list_courses_using_competency',args: {id: competency.id}}]);requests[0].done(function(courses) {var context = {courses: courses};templates.render('tool_lp/linked_courses_summary', context).done(function(html) {str.get_string('linkedcourses', 'tool_lp').done(function(linkedcourses) {new Dialogue(linkedcourses, // Title.html, // The linked courses.initMovePopup);}).fail(notification.exception);}).fail(notification.exception);}).fail(notification.exception);};/*** Open a competencies popup to relate competencies.** @method relateCompetenciesHandler*/var relateCompetenciesHandler = function() {relatedTarget = $('[data-region="competencyactions"]').data('competency');if (!pickerInstance) {pickerInstance = new Picker(pageContextId, relatedTarget.competencyframeworkid);pickerInstance.on('save', function(e, data) {var pendingPromise = new Pending();var compIds = data.competencyIds;var calls = [];$.each(compIds, function(index, value) {calls.push({methodname: 'core_competency_add_related_competency',args: {competencyid: value, relatedcompetencyid: relatedTarget.id}});});calls.push({methodname: 'tool_lp_data_for_related_competencies_section',args: {competencyid: relatedTarget.id}});var promises = ajax.call(calls);promises[calls.length - 1].then(function(context) {return templates.render('tool_lp/related_competencies', context);}).then(function(html, js) {$('[data-region="relatedcompetencies"]').replaceWith(html);templates.runTemplateJS(js);updatedRelatedCompetencies();return;}).then(pendingPromise.resolve).catch(notification.exception);});}pickerInstance.setDisallowedCompetencyIDs([relatedTarget.id]);pickerInstance.display();};var ruleConfigHandler = function(e) {e.preventDefault();relatedTarget = $('[data-region="competencyactions"]').data('competency');ruleConfigInstance.setTargetCompetencyId(relatedTarget.id);ruleConfigInstance.display();};var ruleConfigSaveHandler = function(e, config) {var update = {id: relatedTarget.id,shortname: relatedTarget.shortname,idnumber: relatedTarget.idnumber,description: relatedTarget.description,descriptionformat: relatedTarget.descriptionformat,ruletype: config.ruletype,ruleoutcome: config.ruleoutcome,ruleconfig: config.ruleconfig};var promise = ajax.call([{methodname: 'core_competency_update_competency',args: {competency: update}}]);promise[0].then(function(result) {if (result) {relatedTarget.ruletype = config.ruletype;relatedTarget.ruleoutcome = config.ruleoutcome;relatedTarget.ruleconfig = config.ruleconfig;renderCompetencySummary(relatedTarget);}return;}).catch(notification.exception);};/*** Delete a competency.* @method doDelete*/var doDelete = function() {// We are chaining ajax requests here.var competency = $('[data-region="competencyactions"]').data('competency');var requests = ajax.call([{methodname: 'core_competency_delete_competency',args: {id: competency.id}}, {methodname: 'tool_lp_data_for_competencies_manage_page',args: {competencyframeworkid: competency.competencyframeworkid,search: $('[data-region="filtercompetencies"] input').val()}}]);requests[0].done(function(success) {if (success === false) {str.get_strings([{key: 'competencycannotbedeleted', component: 'tool_lp', param: competency.shortname},{key: 'cancel', component: 'moodle'}]).done(function(strings) {notification.alert(null,strings[0]);}).fail(notification.exception);}}).fail(notification.exception);requests[1].done(reloadPage).fail(notification.exception);};/*** Show a confirm dialogue before deleting a competency.* @method deleteCompetencyHandler*/var deleteCompetencyHandler = function() {var competency = $('[data-region="competencyactions"]').data('competency'),confirmMessage = 'deletecompetency';if (treeModel.hasRule(competency.parentid)) {confirmMessage = 'deletecompetencyparenthasrule';}str.get_strings([{key: 'confirm', component: 'moodle'},{key: confirmMessage, component: 'tool_lp', param: competency.shortname},{key: 'delete', component: 'moodle'},{key: 'cancel', component: 'moodle'}]).done(function(strings) {notification.confirm(strings[0], // Confirm.strings[1], // Delete competency X?strings[2], // Delete.strings[3], // Cancel.doDelete);}).fail(notification.exception);};/*** HTML5 implementation of drag/drop (there is an accesible alternative in the menus).* @method dragStart* @param {Event} e*/var dragStart = function(e) {e.originalEvent.dataTransfer.setData('text', $(e.target).parent().data('id'));};/*** HTML5 implementation of drag/drop (there is an accesible alternative in the menus).* @method allowDrop* @param {Event} e*/var allowDrop = function(e) {e.originalEvent.dataTransfer.dropEffect = 'move';e.preventDefault();};/*** HTML5 implementation of drag/drop (there is an accesible alternative in the menus).* @method dragEnter* @param {Event} e*/var dragEnter = function(e) {e.preventDefault();$(this).addClass('currentdragtarget');};/*** HTML5 implementation of drag/drop (there is an accesible alternative in the menus).* @method dragLeave* @param {Event} e*/var dragLeave = function(e) {e.preventDefault();$(this).removeClass('currentdragtarget');};/*** HTML5 implementation of drag/drop (there is an accesible alternative in the menus).* @method dropOver* @param {Event} e*/var dropOver = function(e) {e.preventDefault();moveSource = e.originalEvent.dataTransfer.getData('text');moveTarget = $(e.target).parent().data('id');$(this).removeClass('currentdragtarget');confirmMove();};/*** Deletes a related competency without confirmation.** @param {Event} e The event that triggered the action.* @method deleteRelatedHandler*/var deleteRelatedHandler = function(e) {e.preventDefault();var relatedid = this.id.substr(11);var competency = $('[data-region="competencyactions"]').data('competency');var removeRelated = ajax.call([{methodname: 'core_competency_remove_related_competency',args: {relatedcompetencyid: relatedid, competencyid: competency.id}},{methodname: 'tool_lp_data_for_related_competencies_section',args: {competencyid: competency.id}}]);removeRelated[1].done(function(context) {templates.render('tool_lp/related_competencies', context).done(function(html) {$('[data-region="relatedcompetencies"]').replaceWith(html);updatedRelatedCompetencies();}).fail(notification.exception);}).fail(notification.exception);};/*** Updates the competencies list (with relations) and add listeners.** @method updatedRelatedCompetencies*/var updatedRelatedCompetencies = function() {// Listeners to newly loaded related competencies.$('[data-action="deleterelation"]').on('click', deleteRelatedHandler);};/*** Log the competency viewed event.** @param {Object} competency The competency.* @method triggerCompetencyViewedEvent*/var triggerCompetencyViewedEvent = function(competency) {if (competency.id !== selectedCompetencyId) {// Set the selected competency id.selectedCompetencyId = competency.id;ajax.call([{methodname: 'core_competency_competency_viewed',args: {id: competency.id}}]);}};/*** Return the taxonomy constant for a level.** @param {Number} level The level.* @return {String}* @function getTaxonomyAtLevel*/var getTaxonomyAtLevel = function(level) {var constant = taxonomiesConstants[level];if (!constant) {constant = 'competency';}return constant;};/*** Render the competency summary.** @param {Object} competency The competency.*/var renderCompetencySummary = function(competency) {var promise = $.Deferred().resolve().promise(),context = {};context.competency = competency;context.showdeleterelatedaction = true;context.showrelatedcompetencies = true;context.showrule = false;context.pluginbaseurl = url.relativeUrl('/admin/tool/lp');if (competency.ruleoutcome != Outcomes.NONE) {// Get the outcome and rule name.promise = Outcomes.getString(competency.ruleoutcome).then(function(str) {var name;$.each(rulesModules, function(index, modInfo) {if (modInfo.type == competency.ruletype) {name = modInfo.name;}});return [str, name];});}promise.then(function(strs) {if (typeof strs !== 'undefined') {context.showrule = true;context.rule = {outcome: strs[0],type: strs[1]};}return context;}).then(function(context) {return templates.render('tool_lp/competency_summary', context);}).then(function(html) {$('[data-region="competencyinfo"]').html(html);$('[data-action="deleterelation"]').on('click', deleteRelatedHandler);return templates.render('tool_lp/loading', {});}).then(function(html, js) {templates.replaceNodeContents('[data-region="relatedcompetencies"]', html, js);return ajax.call([{methodname: 'tool_lp_data_for_related_competencies_section',args: {competencyid: competency.id}}])[0];}).then(function(context) {return templates.render('tool_lp/related_competencies', context);}).then(function(html, js) {$('[data-region="relatedcompetencies"]').replaceWith(html);templates.runTemplateJS(js);updatedRelatedCompetencies();return;}).catch(notification.exception);};/*** Return the string "Add <taxonomy>".** @param {Number} level The level.* @return {String}* @function strAddTaxonomy*/var strAddTaxonomy = function(level) {return str.get_string('taxonomy_add_' + getTaxonomyAtLevel(level), 'tool_lp');};/*** Return the string "Selected <taxonomy>".** @param {Number} level The level.* @return {String}* @function strSelectedTaxonomy*/var strSelectedTaxonomy = function(level) {return str.get_string('taxonomy_selected_' + getTaxonomyAtLevel(level), 'tool_lp');};/*** Handler when a node in the aria tree is selected.* @method selectionChanged* @param {Event} evt The event that triggered the selection change.* @param {Object} params The parameters for the event. Contains a list of selected nodes.* @return {Boolean}*/var selectionChanged = function(evt, params) {var node = params.selected,id = $(node).data('id'),btn = $('[data-region="competencyactions"] [data-action="add"]'),actionMenu = $('[data-region="competencyactionsmenu"]'),selectedTitle = $('[data-region="selected-competency"]'),level = 0,sublevel = 1;menubar.closeAll();if (typeof id === "undefined") {// Assume this is the root of the tree.// Here we are only getting the text from the top of the tree, to do it we clone the tree,// remove all children and then call text on the result.$('[data-region="competencyinfo"]').html(node.clone().children().remove().end().text());$('[data-region="competencyactions"]').data('competency', null);actionMenu.hide();} else {var competency = treeModel.getCompetency(id);level = treeModel.getCompetencyLevel(id);sublevel = level + 1;actionMenu.show();$('[data-region="competencyactions"]').data('competency', competency);renderCompetencySummary(competency);// Log Competency viewed event.triggerCompetencyViewedEvent(competency);}strSelectedTaxonomy(level).then(function(str) {selectedTitle.text(str);return;}).catch(notification.exception);strAddTaxonomy(sublevel).then(function(str) {btn.show().find('[data-region="term"]').text(str);return;}).catch(notification.exception);// We handled this event so consume it.evt.preventDefault();return false;};/*** Return the string "Selected <taxonomy>".** @function parseTaxonomies* @param {String} taxonomiesstr Comma separated list of taxonomies.* @return {Array} of level => taxonomystr*/var parseTaxonomies = function(taxonomiesstr) {var all = taxonomiesstr.split(',');all.unshift("");delete all[0];// Note we don't need to fill holes, because other functions check for empty anyway.return all;};return {/*** Initialise this page (attach event handlers etc).** @method init* @param {Object} model The tree model provides some useful functions for loading and searching competencies.* @param {Number} pagectxid The page context ID.* @param {Object} taxonomies Constants indexed by level.* @param {Object} rulesMods The modules of the rules.*/init: function(model, pagectxid, taxonomies, rulesMods) {treeModel = model;pageContextId = pagectxid;taxonomiesConstants = parseTaxonomies(taxonomies);rulesModules = rulesMods;$('[data-region="competencyactions"] [data-action="add"]').on('click', addHandler);menubar.enhance('.competencyactionsmenu', {'[data-action="edit"]': editHandler,'[data-action="delete"]': deleteCompetencyHandler,'[data-action="move"]': moveHandler,'[data-action="moveup"]': moveUpHandler,'[data-action="movedown"]': moveDownHandler,'[data-action="linkedcourses"]': seeCoursesHandler,'[data-action="relatedcompetencies"]': relateCompetenciesHandler.bind(this),'[data-action="competencyrules"]': ruleConfigHandler.bind(this)});$('[data-region="competencyactionsmenu"]').hide();$('[data-region="competencyactions"] [data-action="add"]').hide();$('[data-region="filtercompetencies"]').on('submit', updateSearchHandler);// Simple html5 drag drop because we already added an accessible alternative.var top = $('[data-region="managecompetencies"] [data-enhance="tree"]');top.on('dragstart', 'li>span', dragStart).on('dragover', 'li>span', allowDrop).on('dragenter', 'li>span', dragEnter).on('dragleave', 'li>span', dragLeave).on('drop', 'li>span', dropOver);model.on('selectionchanged', selectionChanged);// Prepare the configuration tool.ruleConfigInstance = new RuleConfig(treeModel, rulesModules);ruleConfigInstance.on('save', ruleConfigSaveHandler.bind(this));}};});