39efd98b0bf21a93117de3fe593d2d2747cce2ce
[moodle.git] / admin / tool / lp / amd / src / planactions.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  * Plan actions via ajax.
18  *
19  * @module     tool_lp/planactions
20  * @package    tool_lp
21  * @copyright  2015 David Monllao
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
24 define(['jquery',
25         'core/templates',
26         'core/ajax',
27         'core/notification',
28         'core/str',
29         'tool_lp/menubar',
30         'tool_lp/dialogue'],
31         function($, templates, ajax, notification, str, Menubar, Dialogue) {
33     /**
34      * PlanActions class.
35      *
36      * Note that presently this cannot be instantiated more than once per page.
37      *
38      * @param {String} type The type of page we're in.
39      */
40     var PlanActions = function(type) {
41         this._type = type;
43         if (type === 'plan') {
44             // This is the page to view one plan.
45             this._region = '[data-region="plan-page"]';
46             this._planNode = '[data-region="plan-page"]';
47             this._template = 'tool_lp/plan_page';
48             this._contextMethod = 'tool_lp_data_for_plan_page';
50         } else if (type === 'plans') {
51             // This is the page to view a list of plans.
52             this._region = '[data-region="plans"]';
53             this._planNode = '[data-region="plan-node"]';
54             this._template = 'tool_lp/plans_page';
55             this._contextMethod = 'tool_lp_data_for_plans_page';
57         } else {
58             throw new TypeError('Unexpected type.');
59         }
60     };
62     /** @type {String} Ajax method to fetch the page data from. */
63     PlanActions.prototype._contextMethod = null;
64     /** @type {String} Selector to find the node describing the plan. */
65     PlanActions.prototype._planNode = null;
66     /** @type {String} Selector mapping to the region to update. Usually similar to wrapper. */
67     PlanActions.prototype._region = null;
68     /** @type {String} Name of the template used to render the region. */
69     PlanActions.prototype._template = null;
70     /** @type {String} Type of page/region we're in. */
71     PlanActions.prototype._type = null;
73     /**
74      * Resolve the arguments to refresh the region.
75      *
76      * @param  {Object} planData Plan data from plan node.
77      * @return {Object} List of arguments.
78      */
79     PlanActions.prototype._getContextArgs = function(planData) {
80         var self = this,
81             args = {};
83         if (self._type === 'plan') {
84             args = {
85                 planid: planData.id
86             };
88         } else if (self._type === 'plans') {
89             args = {
90                 userid: planData.userid
91             };
92         }
94         return args;
95     };
97     /**
98      * Refresh the plan view.
99      *
100      * This is useful when you only want to refresh the view.
101      *
102      * @param  {String} selector The node to search the plan data from.
103      */
104     PlanActions.prototype.refresh = function(selector) {
105         var planData = this._findPlanData($(selector));
106         this._callAndRefresh([], planData);
107     };
109     /**
110      * Callback to render the region template.
111      *
112      * @param {Object} context The context for the template.
113      */
114     PlanActions.prototype._renderView = function(context) {
115         var self = this;
116         templates.render(self._template, context)
117             .done(function(newhtml, newjs) {
118                 $(self._region).replaceWith(newhtml);
119                 templates.runTemplateJS(newjs);
120             }.bind(self))
121             .fail(notification.exception);
122     };
124     /**
125      * Call multiple ajax methods, and refresh.
126      *
127      * @param  {Array}  calls    List of Ajax calls.
128      * @param  {Object} planData Plan data from plan node.
129      * @return {Promise}
130      */
131     PlanActions.prototype._callAndRefresh = function(calls, planData) {
132         var self = this;
134         calls.push({
135             methodname: self._contextMethod,
136             args: self._getContextArgs(planData)
137         });
139         // Apply all the promises, and refresh when the last one is resolved.
140         return $.when.apply($.when, ajax.call(calls))
141             .then(function() {
142                 self._renderView.call(self, arguments[arguments.length - 1]);
143             })
144             .fail(notification.exception);
145     };
147     /**
148      * Delete a plan and reload the region.
149      *
150      * @param  {Object} planData Plan data from plan node.
151      */
152     PlanActions.prototype._doDelete = function(planData) {
153         var self = this,
154             calls = [{
155                 methodname: 'core_competency_delete_plan',
156                 args: { id: planData.id }
157             }];
158         self._callAndRefresh(calls, planData);
159     };
161     /**
162      * Delete a plan.
163      *
164      * @param  {Object} planData Plan data from plan node.
165      */
166     PlanActions.prototype.deletePlan = function(planData) {
167         var self = this,
168             requests;
170         requests = ajax.call([{
171             methodname: 'core_competency_read_plan',
172             args: { id: planData.id }
173         }]);
175         requests[0].done(function(plan) {
176             str.get_strings([
177                 { key: 'confirm', component: 'moodle' },
178                 { key: 'deleteplan', component: 'tool_lp', param: plan.name },
179                 { key: 'delete', component: 'moodle' },
180                 { key: 'cancel', component: 'moodle' }
181             ]).done(function(strings) {
182                 notification.confirm(
183                     strings[0], // Confirm.
184                     strings[1], // Delete plan X?
185                     strings[2], // Delete.
186                     strings[3], // Cancel.
187                     function() {
188                         self._doDelete(planData);
189                     }.bind(self)
190                 );
191             }).fail(notification.exception);
192         }).fail(notification.exception);
194     };
196     /**
197      * Reopen plan and reload the region.
198      *
199      * @param  {Object} planData Plan data from plan node.
200      */
201     PlanActions.prototype._doReopenPlan = function(planData) {
202         var self = this,
203             calls = [{
204                 methodname: 'core_competency_reopen_plan',
205                 args: { planid: planData.id}
206             }];
207         self._callAndRefresh(calls, planData);
208     };
210     /**
211      * Reopen a plan.
212      *
213      * @param  {Object} planData Plan data from plan node.
214      */
215     PlanActions.prototype.reopenPlan = function(planData) {
216         var self = this,
217             requests = ajax.call([{
218                 methodname: 'core_competency_read_plan',
219                 args: { id: planData.id }
220             }]);
222         requests[0].done(function(plan) {
223             str.get_strings([
224                 { key: 'confirm', component: 'moodle' },
225                 { key: 'reopenplanconfirm', component: 'tool_lp', param: plan.name },
226                 { key: 'reopenplan', component: 'tool_lp' },
227                 { key: 'cancel', component: 'moodle' }
228             ]).done(function(strings) {
229                 notification.confirm(
230                     strings[0], // Confirm.
231                     strings[1], // Reopen plan X?
232                     strings[2], // reopen.
233                     strings[3], // Cancel.
234                     function() {
235                         self._doReopenPlan(planData);
236                     }.bind(self)
237                 );
238             }).fail(notification.exception);
239         }).fail(notification.exception);
241     };
243     /**
244      * Complete plan and reload the region.
245      *
246      * @param  {Object} planData Plan data from plan node.
247      */
248     PlanActions.prototype._doCompletePlan = function(planData) {
249         var self = this,
250             calls = [{
251                 methodname: 'core_competency_complete_plan',
252                 args: { planid: planData.id}
253             }];
254         self._callAndRefresh(calls, planData);
255     };
257     /**
258      * Complete a plan process.
259      *
260      * @param  {Object} planData Plan data from plan node.
261      */
262     PlanActions.prototype.completePlan = function(planData) {
263         var self = this,
264             requests = ajax.call([{
265                 methodname: 'core_competency_read_plan',
266                 args: { id: planData.id }
267             }]);
269         requests[0].done(function(plan) {
270             str.get_strings([
271                 { key: 'confirm', component: 'moodle' },
272                 { key: 'completeplanconfirm', component: 'tool_lp', param: plan.name },
273                 { key: 'completeplan', component: 'tool_lp' },
274                 { key: 'cancel', component: 'moodle' }
275             ]).done(function(strings) {
276                 notification.confirm(
277                     strings[0], // Confirm.
278                     strings[1], // Complete plan X?
279                     strings[2], // Complete.
280                     strings[3], // Cancel.
281                     function() {
282                         self._doCompletePlan(planData);
283                     }.bind(self)
284                 );
285             }).fail(notification.exception);
286         }).fail(notification.exception);
287     };
289     /**
290      * Unlink plan and reload the region.
291      *
292      * @param  {Object} planData Plan data from plan node.
293      */
294     PlanActions.prototype._doUnlinkPlan = function(planData) {
295         var self = this,
296             calls = [{
297                 methodname: 'core_competency_unlink_plan_from_template',
298                 args: { planid: planData.id}
299             }];
300         self._callAndRefresh(calls, planData);
301     };
303     /**
304      * Unlink a plan process.
305      *
306      * @param  {Object} planData Plan data from plan node.
307      */
308     PlanActions.prototype.unlinkPlan = function(planData) {
309         var self = this,
310             requests = ajax.call([{
311                 methodname: 'core_competency_read_plan',
312                 args: { id: planData.id }
313             }]);
315         requests[0].done(function(plan) {
316             str.get_strings([
317                 { key: 'confirm', component: 'moodle' },
318                 { key: 'unlinkplantemplateconfirm', component: 'tool_lp', param: plan.name },
319                 { key: 'unlinkplantemplate', component: 'tool_lp' },
320                 { key: 'cancel', component: 'moodle' }
321             ]).done(function(strings) {
322                 notification.confirm(
323                     strings[0], // Confirm.
324                     strings[1], // Unlink plan X?
325                     strings[2], // Unlink.
326                     strings[3], // Cancel.
327                     function() {
328                         self._doUnlinkPlan(planData);
329                     }.bind(self)
330                 );
331             }).fail(notification.exception);
332         }).fail(notification.exception);
333     };
335     /**
336      * Request review of a plan.
337      *
338      * @param  {Object} planData Plan data from plan node.
339      * @method _doRequestReview
340      */
341     PlanActions.prototype._doRequestReview = function(planData) {
342         var calls = [{
343             methodname: 'core_competency_plan_request_review',
344             args: {
345                 id: planData.id
346             }
347         }];
348         this._callAndRefresh(calls, planData);
349     };
351     /**
352      * Request review of a plan.
353      *
354      * @param  {Object} planData Plan data from plan node.
355      * @method requestReview
356      */
357     PlanActions.prototype.requestReview = function(planData) {
358         this._doRequestReview(planData);
359     };
361     /**
362      * Cancel review request of a plan.
363      *
364      * @param  {Object} planData Plan data from plan node.
365      * @method _doCancelReviewRequest
366      */
367     PlanActions.prototype._doCancelReviewRequest = function(planData) {
368         var calls = [{
369             methodname: 'core_competency_plan_cancel_review_request',
370             args: {
371                 id: planData.id
372             }
373         }];
374         this._callAndRefresh(calls, planData);
375     };
377     /**
378      * Cancel review request of a plan.
379      *
380      * @param  {Object} planData Plan data from plan node.
381      * @method cancelReviewRequest
382      */
383     PlanActions.prototype.cancelReviewRequest = function(planData) {
384         this._doCancelReviewRequest(planData);
385     };
387     /**
388      * Start review of a plan.
389      *
390      * @param  {Object} planData Plan data from plan node.
391      * @method _doStartReview
392      */
393     PlanActions.prototype._doStartReview = function(planData) {
394         var calls = [{
395             methodname: 'core_competency_plan_start_review',
396             args: {
397                 id: planData.id
398             }
399         }];
400         this._callAndRefresh(calls, planData);
401     };
403     /**
404      * Start review of a plan.
405      *
406      * @param  {Object} planData Plan data from plan node.
407      * @method startReview
408      */
409     PlanActions.prototype.startReview = function(planData) {
410         this._doStartReview(planData);
411     };
413     /**
414      * Stop review of a plan.
415      *
416      * @param  {Object} planData Plan data from plan node.
417      * @method _doStopReview
418      */
419     PlanActions.prototype._doStopReview = function(planData) {
420         var calls = [{
421             methodname: 'core_competency_plan_stop_review',
422             args: {
423                 id: planData.id
424             }
425         }];
426         this._callAndRefresh(calls, planData);
427     };
429     /**
430      * Stop review of a plan.
431      *
432      * @param  {Object} planData Plan data from plan node.
433      * @method stopReview
434      */
435     PlanActions.prototype.stopReview = function(planData) {
436         this._doStopReview(planData);
437     };
439     /**
440      * Approve a plan.
441      *
442      * @param  {Object} planData Plan data from plan node.
443      * @method _doApprove
444      */
445     PlanActions.prototype._doApprove = function(planData) {
446         var calls = [{
447             methodname: 'core_competency_approve_plan',
448             args: {
449                 id: planData.id
450             }
451         }];
452         this._callAndRefresh(calls, planData);
453     };
455     /**
456      * Approve a plan.
457      *
458      * @param  {Object} planData Plan data from plan node.
459      * @method approve
460      */
461     PlanActions.prototype.approve = function(planData) {
462         this._doApprove(planData);
463     };
465     /**
466      * Unapprove a plan.
467      *
468      * @param  {Object} planData Plan data from plan node.
469      * @method _doUnapprove
470      */
471     PlanActions.prototype._doUnapprove = function(planData) {
472         var calls = [{
473             methodname: 'core_competency_unapprove_plan',
474             args: {
475                 id: planData.id
476             }
477         }];
478         this._callAndRefresh(calls, planData);
479     };
481     /**
482      * Unapprove a plan.
483      *
484      * @param  {Object} planData Plan data from plan node.
485      * @method unapprove
486      */
487     PlanActions.prototype.unapprove = function(planData) {
488         this._doUnapprove(planData);
489     };
491     /**
492      * Display list of linked courses on a modal dialogue.
493      *
494      * @param  {Event} e The event.
495      */
496     PlanActions.prototype._showLinkedCoursesHandler = function(e) {
497         e.preventDefault();
499         var competencyid = $(e.target).data('id');
500         var requests = ajax.call([{
501             methodname: 'tool_lp_list_courses_using_competency',
502             args: { id: competencyid }
503         }]);
505         requests[0].done(function(courses) {
506             var context = {
507                 courses: courses
508             };
509             templates.render('tool_lp/linked_courses_summary', context).done(function(html) {
510                 str.get_string('linkedcourses', 'tool_lp').done(function(linkedcourses) {
511                     new Dialogue(
512                         linkedcourses, // Title.
513                         html // The linked courses.
514                     );
515                 }).fail(notification.exception);
516             }).fail(notification.exception);
517         }).fail(notification.exception);
518     };
520     /**
521      * Plan event handler.
522      *
523      * @param  {String} method The method to call.
524      * @param  {Event} e The event.
525      * @method _eventHandler
526      */
527     PlanActions.prototype._eventHandler = function(method, e) {
528         e.preventDefault();
529         var data = this._findPlanData($(e.target));
530         this[method](data);
531     };
533     /**
534      * Find the plan data from the plan node.
535      *
536      * @param  {Node} node The node to search from.
537      * @return {Object} Plan data.
538      */
539     PlanActions.prototype._findPlanData = function(node) {
540         var parent = node.parentsUntil($(this._region).parent(), this._planNode),
541             data;
543         if (parent.length != 1) {
544             throw new Error('The plan node was not located.');
545         }
547         data = parent.data();
548         if (typeof data === 'undefined' || typeof data.id === 'undefined') {
549             throw new Error('Plan data could not be found.');
550         }
552         return data;
553     };
555     /**
556      * Enhance a menu bar.
557      *
558      * @param  {String} selector Menubar selector.
559      */
560     PlanActions.prototype.enhanceMenubar = function(selector) {
561         Menubar.enhance(selector, {
562             '[data-action="plan-delete"]': this._eventHandler.bind(this, 'deletePlan'),
563             '[data-action="plan-complete"]': this._eventHandler.bind(this, 'completePlan'),
564             '[data-action="plan-reopen"]': this._eventHandler.bind(this, 'reopenPlan'),
565             '[data-action="plan-unlink"]': this._eventHandler.bind(this, 'unlinkPlan'),
566             '[data-action="plan-request-review"]': this._eventHandler.bind(this, 'requestReview'),
567             '[data-action="plan-cancel-review-request"]': this._eventHandler.bind(this, 'cancelReviewRequest'),
568             '[data-action="plan-start-review"]': this._eventHandler.bind(this, 'startReview'),
569             '[data-action="plan-stop-review"]': this._eventHandler.bind(this, 'stopReview'),
570             '[data-action="plan-approve"]': this._eventHandler.bind(this, 'approve'),
571             '[data-action="plan-unapprove"]': this._eventHandler.bind(this, 'unapprove'),
572         });
573     };
575     /**
576      * Register the events in the region.
577      *
578      * At this stage this cannot be used with enhanceMenubar or multiple handlers
579      * will be added to the same node.
580      */
581     PlanActions.prototype.registerEvents = function() {
582         var wrapper = $(this._region);
584         wrapper.find('[data-action="plan-delete"]').click(this._eventHandler.bind(this, 'deletePlan'));
585         wrapper.find('[data-action="plan-complete"]').click(this._eventHandler.bind(this, 'completePlan'));
586         wrapper.find('[data-action="plan-reopen"]').click(this._eventHandler.bind(this, 'reopenPlan'));
587         wrapper.find('[data-action="plan-unlink"]').click(this._eventHandler.bind(this, 'unlinkPlan'));
589         wrapper.find('[data-action="plan-request-review"]').click(this._eventHandler.bind(this, 'requestReview'));
590         wrapper.find('[data-action="plan-cancel-review-request"]').click(this._eventHandler.bind(this, 'cancelReviewRequest'));
591         wrapper.find('[data-action="plan-start-review"]').click(this._eventHandler.bind(this, 'startReview'));
592         wrapper.find('[data-action="plan-stop-review"]').click(this._eventHandler.bind(this, 'stopReview'));
593         wrapper.find('[data-action="plan-approve"]').click(this._eventHandler.bind(this, 'approve'));
594         wrapper.find('[data-action="plan-unapprove"]').click(this._eventHandler.bind(this, 'unapprove'));
596         wrapper.find('[data-action="find-courses-link"]').click(this._showLinkedCoursesHandler.bind(this));
597     };
599     return PlanActions;
600 });