MDL-40308: Fix javascript for lti mod_form
[moodle.git] / mod / lti / mod_form.js
CommitLineData
61eb12d4
CS
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/**
8f45215d 17 * Javascript extensions for the External Tool activity editor.
61eb12d4
CS
18 *
19 * @package mod
20 * @subpackage lti
8f45215d 21 * @copyright Copyright (c) 2011 Moodlerooms Inc. (http://www.moodlerooms.com)
61eb12d4
CS
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
6831c7cd
CS
24(function(){
25 var Y;
e27cb316 26
6831c7cd
CS
27 M.mod_lti = M.mod_lti || {};
28
4c598b13
CS
29 M.mod_lti.LTI_SETTING_NEVER = 0;
30 M.mod_lti.LTI_SETTING_ALWAYS = 1;
31 M.mod_lti.LTI_SETTING_DELEGATE = 2;
32
6831c7cd
CS
33 M.mod_lti.editor = {
34 init: function(yui3, settings){
35 if(yui3){
36 Y = yui3;
996b0fd9 37 }
e27cb316 38
16e8f130 39 var self = this;
6831c7cd
CS
40 this.settings = Y.JSON.parse(settings);
41
42 this.urlCache = {};
038d8e30 43 this.toolTypeCache = {};
6831c7cd
CS
44
45 this.addOptGroups();
46
d8d04121
CS
47 var updateToolMatches = function(){
48 self.updateAutomaticToolMatch(Y.one('#id_toolurl'));
49 self.updateAutomaticToolMatch(Y.one('#id_securetoolurl'));
50 };
51
6831c7cd
CS
52 var typeSelector = Y.one('#id_typeid');
53 typeSelector.on('change', function(e){
d8d04121 54 updateToolMatches();
e27cb316 55
6831c7cd
CS
56 self.toggleEditButtons();
57 });
58
59 this.createTypeEditorButtons();
60
61 this.toggleEditButtons();
e27cb316 62
6831c7cd
CS
63 var textAreas = new Y.NodeList([
64 Y.one('#id_toolurl'),
d8d04121 65 Y.one('#id_securetoolurl'),
6831c7cd
CS
66 Y.one('#id_resourcekey'),
67 Y.one('#id_password')
68 ]);
e27cb316 69
6831c7cd
CS
70 var debounce;
71 textAreas.on('keyup', function(e){
72 clearTimeout(debounce);
73
7715c88d 74 // If no more changes within 2 seconds, look up the matching tool URL
6831c7cd 75 debounce = setTimeout(function(){
d8d04121 76 updateToolMatches();
6831c7cd
CS
77 }, 2000);
78 });
e27cb316 79
d8d04121 80 updateToolMatches();
6831c7cd
CS
81 },
82
16e8f130
CS
83 clearToolCache: function(){
84 this.urlCache = {};
038d8e30 85 this.toolTypeCache = {};
16e8f130
CS
86 },
87
d8d04121 88 updateAutomaticToolMatch: function(field){
16e8f130 89 var self = this;
e27cb316 90
d8d04121 91 var toolurl = field;
606ab1a1 92 var typeSelector = Y.one('#id_typeid');
e27cb316 93
d8d04121
CS
94 var id = field.get('id') + '_lti_automatch_tool';
95 var automatchToolDisplay = Y.one('#' + id);
6831c7cd
CS
96
97 if(!automatchToolDisplay){
98 automatchToolDisplay = Y.Node.create('<span />')
d8d04121 99 .set('id', id)
6831c7cd 100 .setStyle('padding-left', '1em');
e27cb316 101
6831c7cd
CS
102 toolurl.insert(automatchToolDisplay, 'after');
103 }
104
105 var url = toolurl.get('value');
996b0fd9 106
7715c88d 107 // Hide the display if the url box is empty
16e8f130 108 if(!url){
6831c7cd 109 automatchToolDisplay.setStyle('display', 'none');
16e8f130
CS
110 } else {
111 automatchToolDisplay.set('innerHTML', '');
112 automatchToolDisplay.setStyle('display', '');
113 }
114
038d8e30 115 var selectedToolType = parseInt(typeSelector.get('value'));
89b7bbe9 116 var selectedOption = typeSelector.one('option[value="' + selectedToolType + '"]');
16e8f130 117
7715c88d
EL
118 // A specific tool type is selected (not "auto")
119 // We still need to check with the server to get privacy settings
16e8f130 120 if(selectedToolType > 0){
7715c88d 121 // If the entered domain matches the domain of the tool configuration...
16e8f130
CS
122 var domainRegex = /(?:https?:\/\/)?(?:www\.)?([^\/]+)(?:\/|$)/i;
123 var match = domainRegex.exec(url);
124 if(match && match[1] && match[1].toLowerCase() === selectedOption.getAttribute('domain').toLowerCase()){
125 automatchToolDisplay.set('innerHTML', '<img style="vertical-align:text-bottom" src="' + self.settings.green_check_icon_url + '" />' + M.str.lti.using_tool_configuration + selectedOption.get('text'));
126 } else {
7715c88d 127 // The entered URL does not match the domain of the tool configuration
16e8f130
CS
128 automatchToolDisplay.set('innerHTML', '<img style="vertical-align:text-bottom" src="' + self.settings.warning_icon_url + '" />' + M.str.lti.domain_mismatch);
129 }
6831c7cd
CS
130 }
131
132 var key = Y.one('#id_resourcekey');
133 var secret = Y.one('#id_password');
134
7715c88d
EL
135 // Indicate the tool is manually configured
136 // We still check the Launch URL with the server as course/site tools may override privacy settings
6831c7cd 137 if(key.get('value') !== '' && secret.get('value') !== ''){
16e8f130 138 automatchToolDisplay.set('innerHTML', '<img style="vertical-align:text-bottom" src="' + self.settings.green_check_icon_url + '" />' + M.str.lti.custom_config);
7715c88d
EL
139 }
140
4c598b13
CS
141 var continuation = function(toolInfo){
142 self.updatePrivacySettings(toolInfo);
e27cb316 143
4c598b13
CS
144 if(toolInfo.toolname){
145 automatchToolDisplay.set('innerHTML', '<img style="vertical-align:text-bottom" src="' + self.settings.green_check_icon_url + '" />' + M.str.lti.using_tool_configuration + toolInfo.toolname);
038d8e30 146 } else if(!selectedToolType) {
7715c88d 147 // Inform them custom configuration is in use
4c598b13
CS
148 if(key.get('value') === '' || secret.get('value') === ''){
149 automatchToolDisplay.set('innerHTML', '<img style="vertical-align:text-bottom" src="' + self.settings.warning_icon_url + '" />' + M.str.lti.tool_config_not_found);
150 }
151 }
152 };
153
7715c88d
EL
154 // Cache urls which have already been checked to increase performance
155 // Don't use URL cache if tool type manually selected
038d8e30
CS
156 if(selectedToolType && self.toolTypeCache[selectedToolType]){
157 return continuation(self.toolTypeCache[selectedToolType]);
158 } else if(self.urlCache[url] && !selectedToolType){
159 return continuation(self.urlCache[url]);
160 } else if(!selectedToolType && !url) {
7715c88d 161 // No tool type or url set
038d8e30 162 return continuation({});
4c598b13 163 } else {
038d8e30 164 self.findToolByUrl(url, selectedToolType, function(toolInfo){
4c598b13 165 if(toolInfo){
7715c88d 166 // Cache the result based on whether the URL or tool type was used to look up the tool
038d8e30
CS
167 if(!selectedToolType){
168 self.urlCache[url] = toolInfo;
169 } else {
170 self.toolTypeCache[selectedToolType] = toolInfo;
171 }
7715c88d 172
6831c7cd 173 continuation(toolInfo);
4c598b13
CS
174 }
175 });
176 }
177 },
178
179 /**
180 * Updates display of privacy settings to show course / site tool configuration settings.
181 */
182 updatePrivacySettings: function(toolInfo){
038d8e30 183 if(!toolInfo || !toolInfo.toolid){
4c598b13
CS
184 toolInfo = {
185 sendname: M.mod_lti.LTI_SETTING_DELEGATE,
186 sendemailaddr: M.mod_lti.LTI_SETTING_DELEGATE,
59fc5c54 187 acceptgrades: M.mod_lti.LTI_SETTING_DELEGATE
4c598b13
CS
188 }
189 }
7715c88d 190
4c598b13 191 var setting, control;
7715c88d 192
4c598b13 193 var privacyControls = {
392c581c
DW
194 sendname: Y.one('#id_instructorchoicesendname'),
195 sendemailaddr: Y.one('#id_instructorchoicesendemailaddr'),
196 acceptgrades: Y.one('#id_instructorchoiceacceptgrades')
4c598b13 197 };
7715c88d
EL
198
199 // Store a copy of user entered privacy settings as we may overwrite them
4c598b13
CS
200 if(!this.userPrivacySettings){
201 this.userPrivacySettings = {};
202 }
203
204 for(setting in privacyControls){
205 if(privacyControls.hasOwnProperty(setting)){
206 control = privacyControls[setting];
207
7715c88d 208 // Only store the value if it hasn't been forced by the editor
4c598b13
CS
209 if(!control.get('disabled')){
210 this.userPrivacySettings[setting] = control.get('checked');
211 }
212 }
213 }
7715c88d
EL
214
215 // Update UI based on course / site tool configuration
4c598b13
CS
216 for(setting in privacyControls){
217 if(privacyControls.hasOwnProperty(setting)){
218 var settingValue = toolInfo[setting];
219 control = privacyControls[setting];
7715c88d 220
4c598b13
CS
221 if(settingValue == M.mod_lti.LTI_SETTING_NEVER){
222 control.set('disabled', true);
223 control.set('checked', false);
224 control.set('title', M.str.lti.forced_help);
225 } else if(settingValue == M.mod_lti.LTI_SETTING_ALWAYS){
226 control.set('disabled', true);
227 control.set('checked', true);
228 control.set('title', M.str.lti.forced_help);
229 } else if(settingValue == M.mod_lti.LTI_SETTING_DELEGATE){
230 control.set('disabled', false);
7715c88d
EL
231
232 // Get the value out of the stored copy
233 control.set('checked', this.userPrivacySettings[setting]);
4c598b13
CS
234 control.set('title', '');
235 }
6831c7cd
CS
236 }
237 }
238 },
239
240 getSelectedToolTypeOption: function(){
241 var typeSelector = Y.one('#id_typeid');
242
89b7bbe9 243 return typeSelector.one('option[value="' + typeSelector.get('value') + '"]');
6831c7cd
CS
244 },
245
246 /**
247 * Separate tool listing into option groups. Server-side select control
248 * doesn't seem to support this.
249 */
250 addOptGroups: function(){
251 var typeSelector = Y.one('#id_typeid');
252
253 if(typeSelector.one('option[courseTool=1]')){
7715c88d 254 // One ore more course tools exist
6831c7cd
CS
255
256 var globalGroup = Y.Node.create('<optgroup />')
257 .set('id', 'global_tool_group')
258 .set('label', M.str.lti.global_tool_types);
259
260 var courseGroup = Y.Node.create('<optgroup />')
261 .set('id', 'course_tool_group')
262 .set('label', M.str.lti.course_tool_types);
263
606ab1a1 264 var globalOptions = typeSelector.all('option[globalTool=1]').remove().each(function(node){
6831c7cd
CS
265 globalGroup.append(node);
266 });
267
606ab1a1 268 var courseOptions = typeSelector.all('option[courseTool=1]').remove().each(function(node){
6831c7cd 269 courseGroup.append(node);
996b0fd9 270 });
6831c7cd 271
606ab1a1
CS
272 if(globalOptions.size() > 0){
273 typeSelector.append(globalGroup);
274 }
e27cb316 275
606ab1a1
CS
276 if(courseOptions.size() > 0){
277 typeSelector.append(courseGroup);
278 }
996b0fd9 279 }
6831c7cd
CS
280 },
281
282 /**
283 * Adds buttons for creating, editing, and deleting tool types.
284 * Javascript is a requirement to edit course level tools at this point.
285 */
286 createTypeEditorButtons: function(){
16e8f130 287 var self = this;
e27cb316 288
6831c7cd
CS
289 var typeSelector = Y.one('#id_typeid');
290
291 var createIcon = function(id, tooltip, iconUrl){
e27cb316 292 return Y.Node.create('<a />')
6831c7cd
CS
293 .set('id', id)
294 .set('title', tooltip)
295 .setStyle('margin-left', '.5em')
296 .set('href', 'javascript:void(0);')
297 .append(Y.Node.create('<img src="' + iconUrl + '" />'));
298 }
299
300 var addIcon = createIcon('lti_add_tool_type', M.str.lti.addtype, this.settings.add_icon_url);
301 var editIcon = createIcon('lti_edit_tool_type', M.str.lti.edittype, this.settings.edit_icon_url);
302 var deleteIcon = createIcon('lti_delete_tool_type', M.str.lti.deletetype, this.settings.delete_icon_url);
303
304 editIcon.on('click', function(e){
305 var toolTypeId = typeSelector.get('value');
306
307 if(self.getSelectedToolTypeOption().getAttribute('editable')){
308 window.open(self.settings.instructor_tool_type_edit_url + '&action=edit&typeid=' + toolTypeId, 'edit_tool');
309 } else {
310 alert(M.str.lti.cannot_edit);
311 }
312 });
313
314 addIcon.on('click', function(e){
315 window.open(self.settings.instructor_tool_type_edit_url + '&action=add', 'add_tool');
316 });
317
318 deleteIcon.on('click', function(e){
319 var toolTypeId = typeSelector.get('value');
320
321 if(self.getSelectedToolTypeOption().getAttribute('editable')){
322 if(confirm(M.str.lti.delete_confirmation)){
16e8f130 323 self.deleteTool(toolTypeId);
6831c7cd
CS
324 }
325 } else {
326 alert(M.str.lti.cannot_delete);
327 }
328 });
329
330 typeSelector.insert(addIcon, 'after');
331 addIcon.insert(editIcon, 'after');
332 editIcon.insert(deleteIcon, 'after');
333 },
334
335 toggleEditButtons: function(){
336 var lti_edit_tool_type = Y.one('#lti_edit_tool_type');
337 var lti_delete_tool_type = Y.one('#lti_delete_tool_type');
338
7715c88d
EL
339 // Make the edit / delete icons look enabled / disabled.
340 // Does not work in older browsers, but alerts will catch those cases.
6831c7cd
CS
341 if(this.getSelectedToolTypeOption().getAttribute('editable')){
342 lti_edit_tool_type.setStyle('opacity', '1');
343 lti_delete_tool_type.setStyle('opacity', '1');
344 } else {
345 lti_edit_tool_type.setStyle('opacity', '.2');
346 lti_delete_tool_type.setStyle('opacity', '.2');
347 }
348 },
349
16e8f130 350 addToolType: function(toolType){
6831c7cd
CS
351 var typeSelector = Y.one('#id_typeid');
352 var course_tool_group = Y.one('#course_tool_group');
353
354 var option = Y.Node.create('<option />')
16e8f130
CS
355 .set('text', toolType.name)
356 .set('value', toolType.id)
6831c7cd
CS
357 .set('selected', 'selected')
358 .setAttribute('editable', '1')
16e8f130
CS
359 .setAttribute('courseTool', '1')
360 .setAttribute('domain', toolType.tooldomain);
6831c7cd
CS
361
362 if(course_tool_group){
363 course_tool_group.append(option);
364 } else {
365 typeSelector.append(option);
366 }
e27cb316 367
7715c88d 368 // Adding the new tool may affect which tool gets matched automatically
16e8f130 369 this.clearToolCache();
89b7bbe9
CS
370 this.updateAutomaticToolMatch(Y.one('#id_toolurl'));
371 this.updateAutomaticToolMatch(Y.one('#id_securetoolurl'));
6831c7cd
CS
372 },
373
16e8f130 374 updateToolType: function(toolType){
6831c7cd
CS
375 var typeSelector = Y.one('#id_typeid');
376
89b7bbe9 377 var option = typeSelector.one('option[value="' + toolType.id + '"]');
16e8f130
CS
378 option.set('text', toolType.name)
379 .set('domain', toolType.tooldomain);
e27cb316 380
7715c88d 381 // Editing the tool may affect which tool gets matched automatically
16e8f130 382 this.clearToolCache();
89b7bbe9
CS
383 this.updateAutomaticToolMatch(Y.one('#id_toolurl'));
384 this.updateAutomaticToolMatch(Y.one('#id_securetoolurl'));
16e8f130
CS
385 },
386
387 deleteTool: function(toolTypeId){
388 var self = this;
e27cb316 389
16e8f130
CS
390 Y.io(self.settings.instructor_tool_type_edit_url + '&action=delete&typeid=' + toolTypeId, {
391 on: {
392 success: function(){
393 self.getSelectedToolTypeOption().remove();
e27cb316 394
7715c88d 395 // Editing the tool may affect which tool gets matched automatically
16e8f130 396 self.clearToolCache();
89b7bbe9
CS
397 self.updateAutomaticToolMatch(Y.one('#id_toolurl'));
398 self.updateAutomaticToolMatch(Y.one('#id_securetoolurl'));
16e8f130
CS
399 },
400 failure: function(){
401
402 }
403 }
404 });
6831c7cd
CS
405 },
406
038d8e30 407 findToolByUrl: function(url, toolId, callback){
16e8f130 408 var self = this;
7715c88d 409
e27cb316 410 Y.io(self.settings.ajax_url, {
16e8f130 411 data: {action: 'find_tool_config',
6831c7cd 412 course: self.settings.courseId,
038d8e30
CS
413 toolurl: url,
414 toolid: toolId || 0
6831c7cd 415 },
996b0fd9 416
6831c7cd
CS
417 on: {
418 success: function(transactionid, xhr){
419 var response = xhr.response;
e27cb316 420
6831c7cd 421 var toolInfo = Y.JSON.parse(response);
e27cb316 422
6831c7cd
CS
423 callback(toolInfo);
424 },
425 failure: function(){
996b0fd9 426
6831c7cd
CS
427 }
428 }
429 });
430 }
996b0fd9 431
6831c7cd 432 };
61eb12d4 433})();