MDL-54944 js: Fix spacing of objects coding style
[moodle.git] / admin / tool / lp / amd / src / competencyactions.js
1 // This file is part of Moodle - http://moodle.org/
2 //
3 // Moodle is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, either version 3 of the License, or
6 // (at your option) any later version.
7 //
8 // Moodle is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16 /**
17  * Handle selection changes and actions on the competency tree.
18  *
19  * @module     tool_lp/competencyactions
20  * @package    tool_lp
21  * @copyright  2015 Damyon Wiese <damyon@moodle.com>
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
24 define(['jquery',
25         'core/url',
26         'core/templates',
27         'core/notification',
28         'core/str',
29         'core/ajax',
30         'tool_lp/dragdrop-reorder',
31         'tool_lp/tree',
32         'tool_lp/dialogue',
33         'tool_lp/menubar',
34         'tool_lp/competencypicker',
35         'tool_lp/competency_outcomes',
36         'tool_lp/competencyruleconfig'],
37        function($, url, templates, notification, str, ajax, dragdrop, Ariatree, Dialogue, menubar, Picker, Outcomes, RuleConfig) {
39     // Private variables and functions.
40     /** @var {Object} treeModel - This is an object representing the nodes in the tree. */
41     var treeModel = null;
42     /** @var {Node} moveSource - The start of a drag operation */
43     var moveSource = null;
44     /** @var {Node} moveTarget - The end of a drag operation */
45     var moveTarget = null;
46     /** @var {Number} pageContextId The page context ID. */
47     var pageContextId;
48     /** @type {Object} Picker instance. */
49     var pickerInstance;
50     /** @type {Object} Rule config instance. */
51     var ruleConfigInstance;
52     /** @type {Object} The competency we're picking a relation to. */
53     var relatedTarget;
54     /** @type {Object} Taxonomy constants indexed per level. */
55     var taxonomiesConstants;
56     /** @type {Array} The rules modules. Values are object containing type, namd and amd. */
57     var rulesModules;
58     /** @type {Number} the selected competency ID. */
59     var selectedCompetencyId = null;
61     /**
62      * Respond to choosing the "Add" menu item for the selected node in the tree.
63      * @method addHandler
64      */
65     var addHandler = function() {
66         var parent = $('[data-region="competencyactions"]').data('competency');
68         var params = {
69             competencyframeworkid: treeModel.getCompetencyFrameworkId(),
70             pagecontextid: pageContextId
71         };
73         if (parent !== null) {
74             // We are adding at a sub node.
75             params.parentid = parent.id;
76         }
78         var relocate = function() {
79             var queryparams = $.param(params);
80             window.location = url.relativeUrl('/admin/tool/lp/editcompetency.php?' + queryparams);
81         };
83         if (parent !== null && treeModel.hasRule(parent.id)) {
84             str.get_strings([
85                 {key: 'confirm', component: 'moodle'},
86                 {key: 'addingcompetencywillresetparentrule', component: 'tool_lp', param: parent.shortname},
87                 {key: 'yes', component: 'core'},
88                 {key: 'no', component: 'core'}
89             ]).done(function(strings) {
90                 notification.confirm(
91                     strings[0],
92                     strings[1],
93                     strings[2],
94                     strings[3],
95                     relocate
96                 );
97             }).fail(notification.exception);
98         } else {
99             relocate();
100         }
101     };
103     /**
104      * A source and destination has been chosen - so time to complete a move.
105      * @method doMove
106      */
107     var doMove = function() {
108         var frameworkid = $('[data-region="filtercompetencies"]').data('frameworkid');
109         var requests = ajax.call([{
110             methodname: 'core_competency_set_parent_competency',
111             args: {competencyid: moveSource, parentid: moveTarget}
112         }, {
113             methodname: 'tool_lp_data_for_competencies_manage_page',
114             args: {competencyframeworkid: frameworkid,
115                     search: $('[data-region="filtercompetencies"] input').val()}
116         }]);
117         requests[1].done(reloadPage).fail(notification.exception);
118     };
120     /**
121      * Confirms a competency move.
122      *
123      * @method confirmMove
124      */
125     var confirmMove = function() {
126         moveTarget = typeof moveTarget === "undefined" ? 0 : moveTarget;
127         if (moveTarget == moveSource) {
128             // No move to do.
129             return;
130         }
132         var targetComp = treeModel.getCompetency(moveTarget) || {},
133             sourceComp = treeModel.getCompetency(moveSource) || {},
134             confirmMessage = 'movecompetencywillresetrules',
135             showConfirm = false;
137         // We shouldn't be moving the competency to the same parent.
138         if (sourceComp.parentid == moveTarget) {
139             return;
140         }
142         // If we are moving to a child of self.
143         if (targetComp.path && targetComp.path.indexOf('/' + sourceComp.id + '/') >= 0) {
144             confirmMessage = 'movecompetencytochildofselfwillresetrules';
146             // Show a confirmation if self has rules, as they'll disappear.
147             showConfirm = showConfirm || treeModel.hasRule(sourceComp.id);
148         }
150         // Show a confirmation if the current parent, or the destination have rules.
151         showConfirm = showConfirm || (treeModel.hasRule(targetComp.id) || treeModel.hasRule(sourceComp.parentid));
153         // Show confirm, and/or do the things.
154         if (showConfirm) {
155             str.get_strings([
156                 {key: 'confirm', component: 'moodle'},
157                 {key: confirmMessage, component: 'tool_lp'},
158                 {key: 'yes', component: 'moodle'},
159                 {key: 'no', component: 'moodle'}
160             ]).done(function(strings) {
161                 notification.confirm(
162                     strings[0], // Confirm.
163                     strings[1], // Delete competency X?
164                     strings[2], // Delete.
165                     strings[3], // Cancel.
166                     doMove
167                 );
168             }).fail(notification.exception);
170         } else {
171             doMove();
172         }
173     };
175     /**
176      * A move competency popup was opened - initialise the aria tree in it.
177      * @method initMovePopup
178      * @param {dialogue} popup The tool_lp/dialogue that was created.
179      */
180     var initMovePopup = function(popup) {
181         var body = $(popup.getContent());
182         var treeRoot = body.find('[data-enhance=movetree]');
183         var tree = new Ariatree(treeRoot, false);
184         tree.on('selectionchanged', function(evt, params) {
185             var target = params.selected;
186             moveTarget = $(target).data('id');
187         });
188         treeRoot.show();
190         body.on('click', '[data-action="move"]', function() {
191           popup.close();
192           confirmMove();
193         });
194         body.on('click', '[data-action="cancel"]', function() {
195           popup.close();
196         });
197     };
199     /**
200      * Turn a flat list of competencies into a tree structure (recursive).
201      * @method addCompetencyChildren
202      * @param {Object} parent The current parent node in the tree
203      * @param {Object[]} competencies The flat list of competencies
204      */
205     var addCompetencyChildren = function(parent, competencies) {
206         var i;
208         for (i = 0; i < competencies.length; i++) {
209             if (competencies[i].parentid == parent.id) {
210                 parent.haschildren = true;
211                 competencies[i].children = [];
212                 competencies[i].haschildren = false;
213                 parent.children[parent.children.length] = competencies[i];
214                 addCompetencyChildren(competencies[i], competencies);
215             }
216         }
217     };
219     /**
220      * A node was chosen and "Move" was selected from the menu. Open a popup to select the target.
221      * @param {Event} e
222      * @method moveHandler
223      */
224     var moveHandler = function(e) {
225         e.preventDefault();
226         var competency = $('[data-region="competencyactions"]').data('competency');
228         // Remember what we are moving.
229         moveSource = competency.id;
231         // Load data for the template.
232         var requests = ajax.call([
233             {
234                 methodname: 'core_competency_search_competencies',
235                 args: {
236                     competencyframeworkid: competency.competencyframeworkid,
237                     searchtext: ''
238                 }
239             }, {
240                 methodname: 'core_competency_read_competency_framework',
241                 args: {
242                     id: competency.competencyframeworkid
243                 }
244             }
245         ]);
247         // When all data has arrived, continue.
248         $.when.apply(null, requests).done(function(competencies, framework) {
250             // Expand the list of competencies into a tree.
251             var i, competenciestree = [];
252             for (i = 0; i < competencies.length; i++) {
253                 var onecompetency = competencies[i];
254                 if (onecompetency.parentid == "0") {
255                     onecompetency.children = [];
256                     onecompetency.haschildren = 0;
257                     competenciestree[competenciestree.length] = onecompetency;
258                     addCompetencyChildren(onecompetency, competencies);
259                 }
260             }
262             str.get_strings([
263                 {key: 'movecompetency', component: 'tool_lp', param: competency.shortname},
264                 {key: 'move', component: 'tool_lp'},
265                 {key: 'cancel', component: 'moodle'}
266             ]).done(function(strings) {
268                 var context = {
269                     framework: framework,
270                     competencies: competenciestree
271                 };
273                 templates.render('tool_lp/competencies_move_tree', context)
274                    .done(function(tree) {
275                        new Dialogue(
276                            strings[0], // Move competency x.
277                            tree, // The move tree.
278                            initMovePopup
279                        );
281                    }).fail(notification.exception);
283            }).fail(notification.exception);
285         }).fail(notification.exception);
287     };
289     /**
290      * Edit the selected competency.
291      * @method editHandler
292      */
293     var editHandler = function() {
294         var competency = $('[data-region="competencyactions"]').data('competency');
296         var params = {
297             competencyframeworkid: treeModel.getCompetencyFrameworkId(),
298             id: competency.id,
299             parentid: competency.parentid,
300             pagecontextid: pageContextId
301         };
303         var queryparams = $.param(params);
304         window.location = url.relativeUrl('/admin/tool/lp/editcompetency.php?' + queryparams);
305     };
307     /**
308      * Re-render the page with the latest data.
309      * @param {Object} context
310      * @method reloadPage
311      */
312     var reloadPage = function(context) {
313         templates.render('tool_lp/manage_competencies_page', context)
314             .done(function(newhtml, newjs) {
315                 $('[data-region="managecompetencies"]').replaceWith(newhtml);
316                 templates.runTemplateJS(newjs);
317             })
318            .fail(notification.exception);
319     };
321     /**
322      * Perform a search and render the page with the new search results.
323      * @param {Event} e
324      * @method updateSearchHandler
325      */
326     var updateSearchHandler = function(e) {
327         e.preventDefault();
329         var frameworkid = $('[data-region="filtercompetencies"]').data('frameworkid');
331         var requests = ajax.call([{
332             methodname: 'tool_lp_data_for_competencies_manage_page',
333             args: {competencyframeworkid: frameworkid,
334                     search: $('[data-region="filtercompetencies"] input').val()}
335         }]);
336         requests[0].done(reloadPage).fail(notification.exception);
337     };
339     /**
340      * Move a competency "up". This only affects the sort order within the same branch of the tree.
341      * @method moveUpHandler
342      */
343     var moveUpHandler = function() {
344         // We are chaining ajax requests here.
345         var competency = $('[data-region="competencyactions"]').data('competency');
346         var requests = ajax.call([{
347             methodname: 'core_competency_move_up_competency',
348             args: {id: competency.id}
349         }, {
350             methodname: 'tool_lp_data_for_competencies_manage_page',
351             args: {competencyframeworkid: competency.competencyframeworkid,
352                     search: $('[data-region="filtercompetencies"] input').val()}
353         }]);
354         requests[1].done(reloadPage).fail(notification.exception);
355     };
357     /**
358      * Move a competency "down". This only affects the sort order within the same branch of the tree.
359      * @method moveDownHandler
360      */
361     var moveDownHandler = function() {
362         // We are chaining ajax requests here.
363         var competency = $('[data-region="competencyactions"]').data('competency');
364         var requests = ajax.call([{
365             methodname: 'core_competency_move_down_competency',
366             args: {id: competency.id}
367         }, {
368             methodname: 'tool_lp_data_for_competencies_manage_page',
369             args: {competencyframeworkid: competency.competencyframeworkid,
370                     search: $('[data-region="filtercompetencies"] input').val()}
371         }]);
372         requests[1].done(reloadPage).fail(notification.exception);
373     };
375     /**
376      * Open a dialogue to show all the courses using the selected competency.
377      * @method seeCoursesHandler
378      */
379     var seeCoursesHandler = function() {
380         var competency = $('[data-region="competencyactions"]').data('competency');
382         var requests = ajax.call([{
383             methodname: 'tool_lp_list_courses_using_competency',
384             args: {id: competency.id}
385         }]);
387         requests[0].done(function(courses) {
388             var context = {
389                 courses: courses
390             };
391             templates.render('tool_lp/linked_courses_summary', context).done(function(html) {
392                 str.get_string('linkedcourses', 'tool_lp').done(function(linkedcourses) {
393                     new Dialogue(
394                         linkedcourses, // Title.
395                         html, // The linked courses.
396                         initMovePopup
397                     );
398                 }).fail(notification.exception);
399             }).fail(notification.exception);
400         }).fail(notification.exception);
401     };
403     /**
404      * Open a competencies popup to relate competencies.
405      *
406      * @method relateCompetenciesHandler
407      */
408     var relateCompetenciesHandler = function() {
409         relatedTarget = $('[data-region="competencyactions"]').data('competency');
411         if (!pickerInstance) {
412             pickerInstance = new Picker(pageContextId, relatedTarget.competencyframeworkid);
413             pickerInstance.on('save', function(e, data) {
414                 var compIds = data.competencyIds;
416                 var calls = [];
417                 $.each(compIds, function(index, value) {
418                     calls.push({
419                         methodname: 'core_competency_add_related_competency',
420                         args: {competencyid: value, relatedcompetencyid: relatedTarget.id}
421                     });
422                 });
424                 calls.push({
425                     methodname: 'tool_lp_data_for_related_competencies_section',
426                     args: {competencyid: relatedTarget.id}
427                 });
429                 var promises = ajax.call(calls);
431                 promises[calls.length - 1].then(function(context) {
432                     return templates.render('tool_lp/related_competencies', context).done(function(html, js) {
433                         $('[data-region="relatedcompetencies"]').replaceWith(html);
434                         templates.runTemplateJS(js);
435                         updatedRelatedCompetencies();
436                     });
437                 }, notification.exception);
438             });
439         }
441         pickerInstance.setDisallowedCompetencyIDs([relatedTarget.id]);
442         pickerInstance.display();
443     };
445     var ruleConfigHandler = function(e) {
446         e.preventDefault();
447         relatedTarget = $('[data-region="competencyactions"]').data('competency');
448         ruleConfigInstance.setTargetCompetencyId(relatedTarget.id);
449         ruleConfigInstance.display();
450     };
452     var ruleConfigSaveHandler = function(e, config) {
453         var update = {
454             id: relatedTarget.id,
455             shortname: relatedTarget.shortname,
456             idnumber: relatedTarget.idnumber,
457             description: relatedTarget.description,
458             descriptionformat: relatedTarget.descriptionformat,
459             ruletype: config.ruletype,
460             ruleoutcome: config.ruleoutcome,
461             ruleconfig: config.ruleconfig
462         };
463         var promise = ajax.call([{
464             methodname: 'core_competency_update_competency',
465             args: {competency: update}
466         }]);
467         promise[0].then(function(result) {
468             if (result) {
469                 relatedTarget.ruletype = config.ruletype;
470                 relatedTarget.ruleoutcome = config.ruleoutcome;
471                 relatedTarget.ruleconfig = config.ruleconfig;
472                 renderCompetencySummary(relatedTarget);
473             }
474         }, notification.exception);
475     };
477     /**
478      * Delete a competency.
479      * @method doDelete
480      */
481     var doDelete = function() {
482         // We are chaining ajax requests here.
483         var competency = $('[data-region="competencyactions"]').data('competency');
484         var requests = ajax.call([{
485             methodname: 'core_competency_delete_competency',
486             args: {id: competency.id}
487         }, {
488             methodname: 'tool_lp_data_for_competencies_manage_page',
489             args: {competencyframeworkid: competency.competencyframeworkid,
490                     search: $('[data-region="filtercompetencies"] input').val()}
491         }]);
492         requests[0].done(function(success) {
493             if (success === false) {
494                 str.get_strings([
495                 {key: 'competencycannotbedeleted', component: 'tool_lp', param: competency.shortname},
496                 {key: 'cancel', component: 'moodle'}
497                 ]).done(function(strings) {
498                     notification.alert(
499                         null,
500                         strings[0]
501                     );
502                 }).fail(notification.exception);
503             }
504         }).fail(notification.exception);
505         requests[1].done(reloadPage).fail(notification.exception);
506     };
508     /**
509      * Show a confirm dialogue before deleting a competency.
510      * @method deleteCompetencyHandler
511      */
512     var deleteCompetencyHandler = function() {
513         var competency = $('[data-region="competencyactions"]').data('competency'),
514             confirmMessage = 'deletecompetency';
516         if (treeModel.hasRule(competency.parentid)) {
517             confirmMessage = 'deletecompetencyparenthasrule';
518         }
520         str.get_strings([
521             {key: 'confirm', component: 'moodle'},
522             {key: confirmMessage, component: 'tool_lp', param: competency.shortname},
523             {key: 'delete', component: 'moodle'},
524             {key: 'cancel', component: 'moodle'}
525         ]).done(function(strings) {
526             notification.confirm(
527                 strings[0], // Confirm.
528                 strings[1], // Delete competency X?
529                 strings[2], // Delete.
530                 strings[3], // Cancel.
531                 doDelete
532             );
533         }).fail(notification.exception);
534     };
536     /**
537      * HTML5 implementation of drag/drop (there is an accesible alternative in the menus).
538      * @method dragStart
539      * @param {Event} e
540      */
541     var dragStart = function(e) {
542         e.originalEvent.dataTransfer.setData('text', $(e.target).parent().data('id'));
543     };
545     /**
546      * HTML5 implementation of drag/drop (there is an accesible alternative in the menus).
547      * @method allowDrop
548      * @param {Event} e
549      */
550     var allowDrop = function(e) {
551         e.originalEvent.dataTransfer.dropEffect = 'move';
552         e.preventDefault();
553     };
555     /**
556      * HTML5 implementation of drag/drop (there is an accesible alternative in the menus).
557      * @method dragEnter
558      * @param {Event} e
559      */
560     var dragEnter = function(e) {
561         e.preventDefault();
562         $(this).addClass('currentdragtarget');
563     };
565     /**
566      * HTML5 implementation of drag/drop (there is an accesible alternative in the menus).
567      * @method dragLeave
568      * @param {Event} e
569      */
570     var dragLeave = function(e) {
571         e.preventDefault();
572         $(this).removeClass('currentdragtarget');
573     };
575     /**
576      * HTML5 implementation of drag/drop (there is an accesible alternative in the menus).
577      * @method dropOver
578      * @param {Event} e
579      */
580     var dropOver = function(e) {
581         e.preventDefault();
582         moveSource = e.originalEvent.dataTransfer.getData('text');
583         moveTarget = $(e.target).parent().data('id');
584         $(this).removeClass('currentdragtarget');
586         confirmMove();
587     };
589     /**
590      * Deletes a related competency without confirmation.
591      *
592      * @param {Event} e The event that triggered the action.
593      * @method deleteRelatedHandler
594      */
595     var deleteRelatedHandler = function(e) {
596         e.preventDefault();
598         var relatedid = this.id.substr(11);
599         var competency = $('[data-region="competencyactions"]').data('competency');
600         var removeRelated = ajax.call([
601             {methodname: 'core_competency_remove_related_competency',
602               args: {relatedcompetencyid: relatedid, competencyid: competency.id}},
603             {methodname: 'tool_lp_data_for_related_competencies_section',
604               args: {competencyid: competency.id}}
605         ]);
607         removeRelated[1].done(function(context) {
608             templates.render('tool_lp/related_competencies', context).done(function(html) {
609                 $('[data-region="relatedcompetencies"]').replaceWith(html);
610                 updatedRelatedCompetencies();
611             }.bind(this)).fail(notification.exception);
612         }.bind(this)).fail(notification.exception);
613     };
615     /**
616      * Updates the competencies list (with relations) and add listeners.
617      *
618      * @method updatedRelatedCompetencies
619      */
620     var updatedRelatedCompetencies = function() {
622         // Listeners to newly loaded related competencies.
623         $('[data-action="deleterelation"]').on('click', deleteRelatedHandler);
625     };
627     /**
628      * Log the competency viewed event.
629      *
630      * @param  {Object} competency The competency.
631      * @method triggerCompetencyViewedEvent
632      */
633     var triggerCompetencyViewedEvent = function(competency) {
634         if (competency.id !== selectedCompetencyId) {
635             // Set the selected competency id.
636             selectedCompetencyId = competency.id;
637             ajax.call([{
638                     methodname: 'core_competency_competency_viewed',
639                     args: {id: competency.id}
640             }]);
641         }
642     };
644     /**
645      * Return the taxonomy constant for a level.
646      *
647      * @param  {Number} level The level.
648      * @return {String}
649      * @function getTaxonomyAtLevel
650      */
651     var getTaxonomyAtLevel = function(level) {
652         var constant = taxonomiesConstants[level];
653         if (!constant) {
654             constant = 'competency';
655         }
656         return constant;
657     };
659     /**
660      * Render the competency summary.
661      *
662      * @param  {Object} competency The competency.
663      */
664     var renderCompetencySummary = function(competency) {
665         var promise = $.Deferred().resolve().promise(),
666             context = {};
668         context.competency = competency;
669         context.showdeleterelatedaction = true;
670         context.showrelatedcompetencies = true;
671         context.showrule = false;
673         if (competency.ruleoutcome != Outcomes.NONE) {
674             // Get the outcome and rule name.
675             promise = Outcomes.getString(competency.ruleoutcome).then(function(str) {
676                 var name;
677                 $.each(rulesModules, function(index, modInfo) {
678                     if (modInfo.type == competency.ruletype) {
679                         name = modInfo.name;
680                     }
681                 });
682                 return [str, name];
683             });
684         }
686         promise.then(function(strs) {
687             if (typeof strs !== 'undefined') {
688                 context.showrule = true;
689                 context.rule = {
690                     outcome: strs[0],
691                     type: strs[1]
692                 };
693             }
694         }).then(function() {
695             return templates.render('tool_lp/competency_summary', context).then(function(html) {
696                 $('[data-region="competencyinfo"]').html(html);
697                 $('[data-action="deleterelation"]').on('click', deleteRelatedHandler);
698             });
699         }).then(function() {
700             return templates.render('tool_lp/loading', {});
701         }).then(function(html, js) {
702             templates.replaceNodeContents('[data-region="relatedcompetencies"]', html, js);
703         }).done(function() {
704             ajax.call([{
705                 methodname: 'tool_lp_data_for_related_competencies_section',
706                 args: {competencyid: competency.id},
707                 done: function(context) {
708                     return templates.render('tool_lp/related_competencies', context).done(function(html, js) {
709                         $('[data-region="relatedcompetencies"]').replaceWith(html);
710                         templates.runTemplateJS(js);
711                         updatedRelatedCompetencies();
712                     });
713                 }
714             }]);
715         }).fail(notification.exception);
716     };
718     /**
719      * Return the string "Add <taxonomy>".
720      *
721      * @param  {Number} level The level.
722      * @return {String}
723      * @function strAddTaxonomy
724      */
725     var strAddTaxonomy = function(level) {
726         return str.get_string('taxonomy_add_' + getTaxonomyAtLevel(level), 'tool_lp');
727     };
729     /**
730      * Return the string "Selected <taxonomy>".
731      *
732      * @param  {Number} level The level.
733      * @return {String}
734      * @function strSelectedTaxonomy
735      */
736     var strSelectedTaxonomy = function(level) {
737         return str.get_string('taxonomy_selected_' + getTaxonomyAtLevel(level), 'tool_lp');
738     };
740     /**
741      * Handler when a node in the aria tree is selected.
742      * @method selectionChanged
743      * @param {Event} evt The event that triggered the selection change.
744      * @param {Object} params The parameters for the event. Contains a list of selected nodes.
745      * @return {Boolean}
746      */
747     var selectionChanged = function(evt, params) {
748         var node = params.selected,
749             id = $(node).data('id'),
750             btn = $('[data-region="competencyactions"] [data-action="add"]'),
751             actionMenu = $('[data-region="competencyactionsmenu"]'),
752             selectedTitle = $('[data-region="selected-competency"]'),
753             level = 0,
754             sublevel = 1;
756         menubar.closeAll();
758         if (typeof id === "undefined") {
759             // Assume this is the root of the tree.
760             // Here we are only getting the text from the top of the tree, to do it we clone the tree,
761             // remove all children and then call text on the result.
762             $('[data-region="competencyinfo"]').html(node.clone().children().remove().end().text());
763             $('[data-region="competencyactions"]').data('competency', null);
764             actionMenu.hide();
766         } else {
767             var competency = treeModel.getCompetency(id);
769             level = treeModel.getCompetencyLevel(id);
770             sublevel = level + 1;
772             actionMenu.show();
773             $('[data-region="competencyactions"]').data('competency', competency);
774             renderCompetencySummary(competency);
775             // Log Competency viewed event.
776             triggerCompetencyViewedEvent(competency);
777         }
779         strSelectedTaxonomy(level).then(function(str) {
780             selectedTitle.text(str);
781         });
783         strAddTaxonomy(sublevel).then(function(str) {
784             btn.show()
785                 .find('[data-region="term"]')
786                 .text(str);
787         });
789         // We handled this event so consume it.
790         evt.preventDefault();
791         return false;
792     };
794     /**
795      * Return the string "Selected <taxonomy>".
796      *
797      * @function parseTaxonomies
798      * @param  {String} taxonomiesstr Comma separated list of taxonomies.
799      * @return {Array} of level => taxonomystr
800      */
801     var parseTaxonomies = function(taxonomiesstr) {
802         var all = taxonomiesstr.split(',');
803         all.unshift("");
804         delete all[0];
806         // Note we don't need to fill holes, because other functions check for empty anyway.
807         return all;
808     };
810     return {
811         /**
812          * Initialise this page (attach event handlers etc).
813          *
814          * @method init
815          * @param {Object} model The tree model provides some useful functions for loading and searching competencies.
816          * @param {Number} pagectxid The page context ID.
817          * @param {Object} taxonomies Constants indexed by level.
818          * @param {Object} rulesMods The modules of the rules.
819          */
820         init: function(model, pagectxid, taxonomies, rulesMods) {
821             treeModel = model;
822             pageContextId = pagectxid;
823             taxonomiesConstants = parseTaxonomies(taxonomies);
824             rulesModules = rulesMods;
826             $('[data-region="competencyactions"] [data-action="add"]').on('click', addHandler);
828             menubar.enhance('.competencyactionsmenu', {
829                 '[data-action="edit"]': editHandler,
830                 '[data-action="delete"]': deleteCompetencyHandler,
831                 '[data-action="move"]': moveHandler,
832                 '[data-action="moveup"]': moveUpHandler,
833                 '[data-action="movedown"]': moveDownHandler,
834                 '[data-action="linkedcourses"]': seeCoursesHandler,
835                 '[data-action="relatedcompetencies"]': relateCompetenciesHandler.bind(this),
836                 '[data-action="competencyrules"]': ruleConfigHandler.bind(this)
837             });
838             $('[data-region="competencyactionsmenu"]').hide();
839             $('[data-region="competencyactions"] [data-action="add"]').hide();
841             $('[data-region="filtercompetencies"]').on('submit', updateSearchHandler);
842             // Simple html5 drag drop because we already added an accessible alternative.
843             var top = $('[data-region="managecompetencies"] [data-enhance="tree"]');
844             top.on('dragstart', 'li>span', dragStart)
845                 .on('dragover', 'li>span', allowDrop)
846                 .on('dragenter', 'li>span', dragEnter)
847                 .on('dragleave', 'li>span', dragLeave)
848                 .on('drop', 'li>span', dropOver);
850             model.on('selectionchanged', selectionChanged);
852             // Prepare the configuration tool.
853             ruleConfigInstance = new RuleConfig(treeModel, rulesModules);
854             ruleConfigInstance.on('save', ruleConfigSaveHandler.bind(this));
855         }
856     };
857 });