Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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/>.
15
 
16
/**
17
 * Controls all of the behaviour and interaction with a tool type card. These are
18
 * listed on the LTI tool type management page.
19
 *
20
 * See template: mod_lti/tool_card
21
 *
22
 * @module     mod_lti/tool_card_controller
23
 * @copyright  2015 Ryan Wyllie <ryan@moodle.com>
24
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25
 * @since      3.1
26
 */
27
 define(['jquery', 'core/ajax', 'core/notification', 'core/templates', 'core/modal',
28
        'mod_lti/tool_type', 'mod_lti/events', 'mod_lti/keys',
29
        'core/str'],
30
        function($, ajax, notification, templates, Modal, toolType, ltiEvents, KEYS, str) {
31
 
32
    var SELECTORS = {
33
        DELETE_BUTTON: '.delete',
34
        NAME_ELEMENT: '.name',
35
        DESCRIPTION_ELEMENT: '.description',
36
        CAPABILITIES_CONTAINER: '.capabilities-container',
37
        ACTIVATE_BUTTON: '.tool-card-footer a.activate',
38
    };
39
 
40
    // Timeout in seconds.
41
    var ANNOUNCEMENT_TIMEOUT = 2000;
42
 
43
    /**
44
     * Return the delete button element.
45
     *
46
     * @method getDeleteButton
47
     * @private
48
     * @param {JQuery} element jQuery object representing the tool card.
49
     * @return {JQuery} jQuery object
50
     */
51
    var getDeleteButton = function(element) {
52
        return element.find(SELECTORS.DELETE_BUTTON);
53
    };
54
 
55
    /**
56
     * Return the element representing the tool type name.
57
     *
58
     * @method getNameElement
59
     * @private
60
     * @param {JQuery} element jQuery object representing the tool card.
61
     * @return {JQuery} jQuery object
62
     */
63
    var getNameElement = function(element) {
64
        return element.find(SELECTORS.NAME_ELEMENT);
65
    };
66
 
67
    /**
68
     * Return the element representing the tool type description.
69
     *
70
     * @method getDescriptionElement
71
     * @private
72
     * @param {JQuery} element jQuery object representing the tool card.
73
     * @return {JQuery} jQuery object
74
     */
75
    var getDescriptionElement = function(element) {
76
        return element.find(SELECTORS.DESCRIPTION_ELEMENT);
77
    };
78
 
79
    /**
80
     * Return the activate button for the type.
81
     *
82
     * @method getActivateButton
83
     * @private
84
     * @param {Object} element jQuery object representing the tool card.
85
     * @return {Object} jQuery object
86
     */
87
    var getActivateButton = function(element) {
88
        return element.find(SELECTORS.ACTIVATE_BUTTON);
89
    };
90
 
91
    /**
92
     * Checks if the type card has an activate button.
93
     *
94
     * @method hasActivateButton
95
     * @private
96
     * @param {JQuery} element jQuery object representing the tool card.
97
     * @return {Boolean} true if has active buton
98
     */
99
    var hasActivateButton = function(element) {
100
        return getActivateButton(element).length ? true : false;
101
    };
102
 
103
    /**
104
     * Return the element that contains the capabilities approval for
105
     * the user.
106
     *
107
     * @method getCapabilitiesContainer
108
     * @private
109
     * @param {Object} element jQuery object representing the tool card.
110
     * @return {Object} The element
111
     */
112
    var getCapabilitiesContainer = function(element) {
113
        return element.find(SELECTORS.CAPABILITIES_CONTAINER);
114
    };
115
 
116
    /**
117
     * Checks if the tool type has capabilities that need approval. If it
118
     * does then the container will be present.
119
     *
120
     * @method hasCapabilitiesContainer
121
     * @private
122
     * @param {JQuery} element jQuery object representing the tool card.
123
     * @return {Boolean} true if has capbilities.
124
     */
125
    var hasCapabilitiesContainer = function(element) {
126
        return getCapabilitiesContainer(element).length ? true : false;
127
    };
128
 
129
    /**
130
     * Get the type id.
131
     *
132
     * @method getTypeId
133
     * @private
134
     * @param {Object} element jQuery object representing the tool card.
135
     * @return {String} Type ID
136
     */
137
    var getTypeId = function(element) {
138
        return element.attr('data-type-id');
139
    };
140
 
141
    /**
142
     * Stop any announcement currently visible on the card.
143
     *
144
     * @method clearAllAnnouncements
145
     * @private
146
     * @param {JQuery} element jQuery object representing the tool card.
147
     */
148
    var clearAllAnnouncements = function(element) {
149
        element.removeClass('announcement loading success fail capabilities');
150
    };
151
 
152
    /**
153
     * Show the loading announcement.
154
     *
155
     * @method startLoading
156
     * @private
157
     * @param {JQuery} element jQuery object representing the tool card.
158
     */
159
    var startLoading = function(element) {
160
        clearAllAnnouncements(element);
161
        element.addClass('announcement loading');
162
    };
163
 
164
    /**
165
     * Hide the loading announcement.
166
     *
167
     * @method stopLoading
168
     * @private
169
     * @param {JQuery} element jQuery object representing the tool card.
170
     */
171
    var stopLoading = function(element) {
172
        element.removeClass('announcement loading');
173
    };
174
 
175
    /**
176
     * Show the success announcement. The announcement is only
177
     * visible for 2 seconds.
178
     *
179
     * @method announceSuccess
180
     * @private
181
     * @param {JQuery} element jQuery object representing the tool card.
182
     * @return {Promise} jQuery Deferred object
183
     */
184
    var announceSuccess = function(element) {
185
        var promise = $.Deferred();
186
 
187
        clearAllAnnouncements(element);
188
        element.addClass('announcement success');
189
        setTimeout(function() {
190
            element.removeClass('announcement success');
191
            promise.resolve();
192
        }, ANNOUNCEMENT_TIMEOUT);
193
 
194
        return promise;
195
    };
196
 
197
    /**
198
     * Show the failure announcement. The announcement is only
199
     * visible for 2 seconds.
200
     *
201
     * @method announceFailure
202
     * @private
203
     * @param {JQuery} element jQuery object representing the tool card.
204
     * @return {Promise} jQuery Deferred object
205
     */
206
    var announceFailure = function(element) {
207
        var promise = $.Deferred();
208
 
209
        clearAllAnnouncements(element);
210
        element.addClass('announcement fail');
211
        setTimeout(function() {
212
            element.removeClass('announcement fail');
213
            promise.resolve();
214
        }, ANNOUNCEMENT_TIMEOUT);
215
 
216
        return promise;
217
    };
218
 
219
    /**
220
     * Delete the tool type from the Moodle server. Triggers a success
221
     * or failure announcement depending on the result.
222
     *
223
     * @method deleteType
224
     * @private
225
     * @param {JQuery} element jQuery object representing the tool card.
226
     * @return {Promise} jQuery Deferred object
227
     */
228
    var deleteType = function(element) {
229
        var promise = $.Deferred();
230
        var typeId = getTypeId(element);
231
        startLoading(element);
232
 
233
        if (typeId === "") {
234
            return $.Deferred().resolve();
235
        }
236
 
237
        str.get_strings([
238
                {
239
                    key: 'delete',
240
                    component: 'mod_lti'
241
                },
242
                {
243
                    key: 'delete_confirmation',
244
                    component: 'mod_lti'
245
                },
246
                {
247
                    key: 'delete',
248
                    component: 'mod_lti'
249
                },
250
                {
251
                    key: 'cancel',
252
                    component: 'core'
253
                },
254
            ])
255
            .done(function(strs) {
256
                    notification.confirm(strs[0], strs[1], strs[2], strs[3], function() {
257
                            toolType.delete(typeId)
258
                                .done(function() {
259
                                        stopLoading(element);
260
                                        announceSuccess(element)
261
                                            .done(function() {
262
                                                    element.remove();
263
                                                })
264
                                            .fail(notification.exception)
265
                                            .always(function() {
266
                                                    // Always resolve because even if the announcement fails the type was deleted.
267
                                                    promise.resolve();
268
                                                });
269
                                    })
270
                                .fail(function(error) {
271
                                        announceFailure(element);
272
                                        promise.reject(error);
273
                                    });
274
                        }, function() {
275
                                stopLoading(element);
276
                                promise.resolve();
277
                            });
278
                })
279
            .fail(function(error) {
280
                    stopLoading(element);
281
                    notification.exception(error);
282
                    promise.reject(error);
283
                });
284
 
285
        return promise;
286
    };
287
 
288
    /**
289
     * Save a given value in a data attribute on the element.
290
     *
291
     * @method setValueSnapshot
292
     * @private
293
     * @param {JQuery} element jQuery object representing the element.
294
     * @param {String} value to be saved.
295
     */
296
    var setValueSnapshot = function(element, value) {
297
        element.attr('data-val-snapshot', value);
298
    };
299
 
300
    /**
301
     * Return the saved value from the element.
302
     *
303
     * @method getValueSnapshot
304
     * @private
305
     * @param {JQuery} element jQuery object representing the element.
306
     * @return {String} the saved value.
307
     */
308
    var getValueSnapshot = function(element) {
309
        return element.attr('data-val-snapshot');
310
    };
311
 
312
    /**
313
     * Save the current value of the tool description.
314
     *
315
     * @method snapshotDescription
316
     * @private
317
     * @param {JQuery} element jQuery object representing the tool card.
318
     */
319
    var snapshotDescription = function(element) {
320
        var descriptionElement = getDescriptionElement(element);
321
 
322
        if (descriptionElement.hasClass('loading')) {
323
            return;
324
        }
325
 
326
        var description = descriptionElement.text().trim();
327
        setValueSnapshot(descriptionElement, description);
328
    };
329
 
330
    /**
331
     * Send a request to update the description value for this tool
332
     * in the Moodle server.
333
     *
334
     * @method updateDescription
335
     * @private
336
     * @param {JQuery} element jQuery object representing the tool card.
337
     * @return {Promise} jQuery Deferred object
338
     */
339
    var updateDescription = function(element) {
340
        var typeId = getTypeId(element);
341
 
342
        // Return early if we don't have an id because it's
343
        // required to save the changes.
344
        if (typeId === "") {
345
            return $.Deferred().resolve();
346
        }
347
 
348
        var descriptionElement = getDescriptionElement(element);
349
 
350
        // Return early if we're already saving a value.
351
        if (descriptionElement.hasClass('loading')) {
352
            return $.Deferred().resolve();
353
        }
354
 
355
        var description = descriptionElement.text().trim();
356
        var snapshotVal = getValueSnapshot(descriptionElement);
357
 
358
        // If the value hasn't change then don't bother sending the
359
        // update request.
360
        if (snapshotVal == description) {
361
            return $.Deferred().resolve();
362
        }
363
 
364
        descriptionElement.addClass('loading');
365
 
366
        var promise = toolType.update({id: typeId, description: description});
367
 
368
        promise.done(function(type) {
369
            descriptionElement.removeClass('loading');
370
            // Make sure the text is updated with the description from the
371
            // server, just in case the update didn't work.
372
            descriptionElement.text(type.description);
373
        }).fail(notification.exception);
374
 
375
        // Probably need to handle failures better so that we can revert
376
        // the value in the input for the user.
377
        promise.fail(function() {
378
          descriptionElement.removeClass('loading');
379
        });
380
 
381
        return promise;
382
    };
383
 
384
    /**
385
     * Save the current value of the tool name.
386
     *
387
     * @method snapshotName
388
     * @private
389
     * @param {JQuery} element jQuery object representing the tool card.
390
     */
391
    var snapshotName = function(element) {
392
        var nameElement = getNameElement(element);
393
 
394
        if (nameElement.hasClass('loading')) {
395
            return;
396
        }
397
 
398
        var name = nameElement.text().trim();
399
        setValueSnapshot(nameElement, name);
400
    };
401
 
402
    /**
403
     * Send a request to update the name value for this tool
404
     * in the Moodle server.
405
     *
406
     * @method updateName
407
     * @private
408
     * @param {JQuery} element jQuery object representing the tool card.
409
     * @return {Promise} jQuery Deferred object
410
     */
411
    var updateName = function(element) {
412
        var typeId = getTypeId(element);
413
 
414
        // Return if we don't have an id.
415
        if (typeId === "") {
416
            return $.Deferred().resolve();
417
        }
418
 
419
        var nameElement = getNameElement(element);
420
 
421
        // Return if we're already saving.
422
        if (nameElement.hasClass('loading')) {
423
            return $.Deferred().resolve();
424
        }
425
 
426
        var name = nameElement.text().trim();
427
        var snapshotVal = getValueSnapshot(nameElement);
428
 
429
        // If the value hasn't change then don't bother sending the
430
        // update request.
431
        if (snapshotVal == name) {
432
            return $.Deferred().resolve();
433
        }
434
 
435
        nameElement.addClass('loading');
436
        var promise = toolType.update({id: typeId, name: name});
437
 
438
        promise.done(function(type) {
439
            nameElement.removeClass('loading');
440
            // Make sure the text is updated with the name from the
441
            // server, just in case the update didn't work.
442
            nameElement.text(type.name);
443
        });
444
 
445
        // Probably need to handle failures better so that we can revert
446
        // the value in the input for the user.
447
        promise.fail(function() {
448
          nameElement.removeClass('loading');
449
        });
450
 
451
        return promise;
452
    };
453
 
454
    /**
455
     * Send a request to update the state for this tool to be configured (active)
456
     * in the Moodle server. A success or failure announcement is triggered depending
457
     * on the result.
458
     *
459
     * @method setStatusActive
460
     * @private
461
     * @param {JQuery} element jQuery object representing the tool card.
462
     * @return {Promise} jQuery Deferred object
463
     */
464
    var setStatusActive = function(element) {
465
        var id = getTypeId(element);
466
 
467
        // Return if we don't have an id.
468
        if (id === "") {
469
            return $.Deferred().resolve();
470
        }
471
 
472
        startLoading(element);
473
 
474
        var promise = toolType.update({
475
            id: id,
476
            state: toolType.constants.state.configured
477
        });
478
 
479
        promise.then(function(toolTypeData) {
480
            stopLoading(element);
481
            announceSuccess(element);
482
            return toolTypeData;
483
        }).then(function(toolTypeData) {
484
            return templates.render('mod_lti/tool_card', toolTypeData);
485
        }).then(function(html, js) {
486
            templates.replaceNode(element, html, js);
487
            return;
488
        }).catch(function() {
489
            stopLoading(element);
490
            announceFailure(element);
491
        });
492
 
493
        return promise;
494
    };
495
 
496
    /**
497
     * Show the capabilities approval screen to show which groups of data this
498
     * type requires access to in Moodle (if any).
499
     *
500
     * @method displayCapabilitiesApproval
501
     * @private
502
     * @param {JQuery} element jQuery object representing the tool card.
503
     */
504
    var displayCapabilitiesApproval = function(element) {
505
        element.addClass('announcement capabilities');
506
    };
507
 
508
    /**
509
     * Hide the capabilities approval screen.
510
     *
511
     * @method hideCapabilitiesApproval
512
     * @private
513
     * @param {JQuery} element jQuery object representing the tool card.
514
     */
515
    var hideCapabilitiesApproval = function(element) {
516
        element.removeClass('announcement capabilities');
517
    };
518
 
519
    /**
520
     * The user wishes to activate this tool so show them the capabilities that
521
     * they need to agree to or if there are none then set the tool type's state
522
     * to active.
523
     *
524
     * @method activateToolType
525
     * @private
526
     * @param {JQuery} element jQuery object representing the tool card.
527
     */
528
    var activateToolType = function(element) {
529
        if (hasCapabilitiesContainer(element)) {
530
            displayCapabilitiesApproval(element);
531
        } else {
532
            setStatusActive(element);
533
        }
534
    };
535
 
536
    /**
537
     * Sets up the listeners for user interaction on this tool type card.
538
     *
539
     * @method registerEventListeners
540
     * @private
541
     * @param {JQuery} element jQuery object representing the tool card.
542
     */
543
    var registerEventListeners = function(element) {
544
        var deleteButton = getDeleteButton(element);
545
        deleteButton.click(function(e) {
546
            e.preventDefault();
547
            deleteType(element);
548
        });
549
        deleteButton.keypress(function(e) {
550
            if (!e.metaKey && !e.shiftKey && !e.altKey && !e.ctrlKey) {
551
                if (e.keyCode == KEYS.ENTER || e.keyCode == KEYS.SPACE) {
552
                    e.preventDefault();
553
                    deleteButton.click();
554
                }
555
            }
556
        });
557
 
558
        var descriptionElement = getDescriptionElement(element);
559
        descriptionElement.focus(function(e) {
560
            e.preventDefault();
561
            // Save a copy of the current value for the description so that
562
            // we can check if the user has changed it before sending a request to
563
            // the server.
564
            snapshotDescription(element);
565
        });
566
        descriptionElement.blur(function(e) {
567
            e.preventDefault();
568
            updateDescription(element);
569
        });
570
        descriptionElement.keypress(function(e) {
571
            if (!e.metaKey && !e.shiftKey && !e.altKey && !e.ctrlKey) {
572
                if (e.keyCode == KEYS.ENTER) {
573
                    e.preventDefault();
574
                    descriptionElement.blur();
575
                }
576
            }
577
        });
578
 
579
        var nameElement = getNameElement(element);
580
        nameElement.focus(function(e) {
581
            e.preventDefault();
582
            // Save a copy of the current value for the name so that
583
            // we can check if the user has changed it before sending a request to
584
            // the server.
585
            snapshotName(element);
586
        });
587
        nameElement.blur(function(e) {
588
            e.preventDefault();
589
            updateName(element);
590
        });
591
        nameElement.keypress(function(e) {
592
            if (!e.metaKey && !e.shiftKey && !e.altKey && !e.ctrlKey) {
593
                if (e.keyCode == KEYS.ENTER) {
594
                    e.preventDefault();
595
                    nameElement.blur();
596
                }
597
            }
598
        });
599
 
600
        // Only pending tool type cards have an activate button.
601
        if (hasActivateButton(element)) {
602
            var activateButton = getActivateButton(element);
603
            activateButton.click(function(e) {
604
                e.preventDefault();
605
                activateToolType(element);
606
            });
607
            activateButton.keypress(function(e) {
608
                if (!e.metaKey && !e.shiftKey && !e.altKey && !e.ctrlKey) {
609
                    if (e.keyCode == KEYS.ENTER || e.keyCode == KEYS.SPACE) {
610
                        e.preventDefault();
611
                        activateButton.click();
612
                    }
613
                }
614
            });
615
        }
616
 
617
        if (hasCapabilitiesContainer(element)) {
618
            var capabilitiesContainer = getCapabilitiesContainer(element);
619
 
620
            capabilitiesContainer.on(ltiEvents.CAPABILITIES_AGREE, function() {
621
                setStatusActive(element);
622
            });
623
 
624
            capabilitiesContainer.on(ltiEvents.CAPABILITIES_DECLINE, function() {
625
                hideCapabilitiesApproval(element);
626
            });
627
        }
628
    };
629
 
630
    /**
631
     * Sets up the templates for the tool configuration modal on this tool type card.
632
     *
633
     * @method registerModal
634
     * @private
635
     * @param {JQuery} element jQuery object representing the tool card.
636
     */
637
    var registerModal = function(element) {
638
        const configurationLink = element.find('#' + element.data('uniqid') + '-' + element.data('deploymentid'));
639
        if (!configurationLink.length) {
640
            return;
641
        }
642
        const trigger = configurationLink.get(0);
643
        trigger.addEventListener('click', (e) => {
644
            e.preventDefault();
645
            var context = {
646
                'uniqid': element.data('uniqid'),
647
                'platformid': element.data('platformid'),
648
                'clientid': element.data('clientid'),
649
                'deploymentid': element.data('deploymentid'),
650
                'urls': {
651
                    'publickeyset': element.data('publickeyseturl'),
652
                    'accesstoken': element.data('accesstokenurl'),
653
                    'authrequest': element.data('authrequesturl')
654
                }
655
            };
656
            var bodyPromise = templates.render('mod_lti/tool_config_modal_body', context);
657
            var mailTo = 'mailto:?subject=' + encodeURIComponent(element.data('mailtosubject')) +
658
                '&body=' + encodeURIComponent(element.data('platformidstr')) + ':%20' +
659
                encodeURIComponent(element.data('platformid')) + '%0D%0A' +
660
                encodeURIComponent(element.data('clientidstr')) + ':%20' +
661
                encodeURIComponent(element.data('clientid')) + '%0D%0A' +
662
                encodeURIComponent(element.data('deploymentidstr')) + ':%20' +
663
                encodeURIComponent(element.data('deploymentid')) + '%0D%0A' +
664
                encodeURIComponent(element.data('publickeyseturlstr')) + ':%20' +
665
                encodeURIComponent(element.data('publickeyseturl')) + '%0D%0A' +
666
                encodeURIComponent(element.data('accesstokenurlstr')) + ':%20' +
667
                encodeURIComponent(element.data('accesstokenurl')) + '%0D%0A' +
668
                encodeURIComponent(element.data('authrequesturlstr')) + ':%20' +
669
                encodeURIComponent(element.data('authrequesturl')) + '%0D%0A';
670
            context = {
671
                'mailto': mailTo
672
            };
673
            var footerPromise = templates.render('mod_lti/tool_config_modal_footer', context);
674
            Modal.create({
675
                large: true,
676
                title: element.data('modaltitle'),
677
                body: bodyPromise,
678
                footer: footerPromise,
679
                show: true
680
            });
681
        });
682
    };
683
 
684
    return /** @alias module:mod_lti/tool_card_controller */ {
685
 
686
        /**
687
         * Initialise this module.
688
         *
689
         * @param {JQuery} element jQuery object representing the tool card.
690
         */
691
        init: function(element) {
692
            registerEventListeners(element);
693
            registerModal(element);
694
        }
695
    };
696
});