cfaf7e9ba932b3b10eeaf018d000ff480adc1e0e
[moodle.git] / mod / lti / mod_form.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  * Javascript extensions for the External Tool activity editor.
18  *
19  * @package    mod
20  * @subpackage lti
21  * @copyright  Copyright (c) 2011 Moodlerooms Inc. (http://www.moodlerooms.com)
22  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
24 (function(){
25     var Y;
27     M.mod_lti = M.mod_lti || {};
29     M.mod_lti.editor = {
30         init: function(yui3, settings){
31             if(yui3){
32                 Y = yui3;
33             }
35             var self = this;
36             this.settings = Y.JSON.parse(settings);
38             this.urlCache = {};
40             this.addOptGroups();
42             var updateToolMatches = function(){
43                 self.updateAutomaticToolMatch(Y.one('#id_toolurl'));
44                 self.updateAutomaticToolMatch(Y.one('#id_securetoolurl'));
45             };
47             var typeSelector = Y.one('#id_typeid');
48             typeSelector.on('change', function(e){
49                 updateToolMatches();
51                 self.toggleEditButtons();
52             });
54             this.createTypeEditorButtons();
56             this.toggleEditButtons();
58             var textAreas = new Y.NodeList([
59                 Y.one('#id_toolurl'),
60                 Y.one('#id_securetoolurl'),
61                 Y.one('#id_resourcekey'),
62                 Y.one('#id_password')
63             ]);
65             var debounce;
66             textAreas.on('keyup', function(e){
67                 clearTimeout(debounce);
69                 //If no more changes within 2 seconds, look up the matching tool URL
70                 debounce = setTimeout(function(){
71                     updateToolMatches();
72                 }, 2000);
73             });
75             updateToolMatches();
76         },
78         clearToolCache: function(){
79             this.urlCache = {};
80         },
82         updateAutomaticToolMatch: function(field){
83             var self = this;
85             var toolurl = field;
86             var typeSelector = Y.one('#id_typeid');
88             var id = field.get('id') + '_lti_automatch_tool';
89             var automatchToolDisplay = Y.one('#' + id);
91             if(!automatchToolDisplay){
92                 automatchToolDisplay = Y.Node.create('<span />')
93                                         .set('id', id)
94                                         .setStyle('padding-left', '1em');
96                 toolurl.insert(automatchToolDisplay, 'after');
97             }
99             var url = toolurl.get('value');
101             //Hide the display if the url box is empty
102             if(!url){
103                 automatchToolDisplay.setStyle('display', 'none');
104             } else {
105                 automatchToolDisplay.set('innerHTML', '');
106                 automatchToolDisplay.setStyle('display', '');
107             }
109             var selectedToolType = typeSelector.get('value');
110             var selectedOption = typeSelector.one('option[value="' + selectedToolType + '"]');
112             //A specific tool type is selected (not "auto")
113             if(selectedToolType > 0){
114                 //If the entered domain matches the domain of the tool configuration...
115                 var domainRegex = /(?:https?:\/\/)?(?:www\.)?([^\/]+)(?:\/|$)/i;
116                 var match = domainRegex.exec(url);
117                 if(match && match[1] && match[1].toLowerCase() === selectedOption.getAttribute('domain').toLowerCase()){
118                     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'));
119                 } else {
120                     //The entered URL does not match the domain of the tool configuration
121                     automatchToolDisplay.set('innerHTML', '<img style="vertical-align:text-bottom" src="' + self.settings.warning_icon_url + '" />' + M.str.lti.domain_mismatch);
122                 }
124                 return;
125             }
127             var key = Y.one('#id_resourcekey');
128             var secret = Y.one('#id_password');
130             //We don't care what tool type this tool is associated with if it's manually configured'
131             if(key.get('value') !== '' && secret.get('value') !== ''){
132                 automatchToolDisplay.set('innerHTML',  '<img style="vertical-align:text-bottom" src="' + self.settings.green_check_icon_url + '" />' + M.str.lti.custom_config);
133             } else {
134                 var continuation = function(toolInfo){
135                     if(toolInfo.toolname){
136                         automatchToolDisplay.set('innerHTML',  '<img style="vertical-align:text-bottom" src="' + self.settings.green_check_icon_url + '" />' + M.str.lti.using_tool_configuration + toolInfo.toolname);
137                     } else {
138                         //Inform them custom configuration is in use
139                         if(key.get('value') === '' || secret.get('value') === ''){
140                             automatchToolDisplay.set('innerHTML', '<img style="vertical-align:text-bottom" src="' + self.settings.warning_icon_url + '" />' + M.str.lti.tool_config_not_found);
141                         }
142                     }
143                 };
145                 //Cache urls which have already been checked to increaes performance
146                 if(self.urlCache[url]){
147                     continuation(self.urlCache[url]);
148                 } else {
149                     self.findToolByUrl(url, function(toolInfo){
150                         self.urlCache[url] = toolInfo;
152                         continuation(toolInfo);
153                     });
154                 }
155             }
156         },
158         getSelectedToolTypeOption: function(){
159             var typeSelector = Y.one('#id_typeid');
161             return typeSelector.one('option[value="' + typeSelector.get('value') + '"]');
162         },
164         /**
165          * Separate tool listing into option groups. Server-side select control
166          * doesn't seem to support this.
167          */
168         addOptGroups: function(){
169             var typeSelector = Y.one('#id_typeid');
171             if(typeSelector.one('option[courseTool=1]')){
172                 //One ore more course tools exist
174                 var globalGroup = Y.Node.create('<optgroup />')
175                                     .set('id', 'global_tool_group')
176                                     .set('label', M.str.lti.global_tool_types);
178                 var courseGroup = Y.Node.create('<optgroup />')
179                                     .set('id', 'course_tool_group')
180                                     .set('label', M.str.lti.course_tool_types);
182                 var globalOptions = typeSelector.all('option[globalTool=1]').remove().each(function(node){
183                     globalGroup.append(node);
184                 });
186                 var courseOptions = typeSelector.all('option[courseTool=1]').remove().each(function(node){
187                     courseGroup.append(node);
188                 });
190                 if(globalOptions.size() > 0){
191                     typeSelector.append(globalGroup);
192                 }
194                 if(courseOptions.size() > 0){
195                     typeSelector.append(courseGroup);
196                 }
197             }
198         },
200         /**
201          * Adds buttons for creating, editing, and deleting tool types.
202          * Javascript is a requirement to edit course level tools at this point.
203          */
204         createTypeEditorButtons: function(){
205             var self = this;
207             var typeSelector = Y.one('#id_typeid');
209             var createIcon = function(id, tooltip, iconUrl){
210                 return Y.Node.create('<a />')
211                         .set('id', id)
212                         .set('title', tooltip)
213                         .setStyle('margin-left', '.5em')
214                         .set('href', 'javascript:void(0);')
215                         .append(Y.Node.create('<img src="' + iconUrl + '" />'));
216             }
218             var addIcon = createIcon('lti_add_tool_type', M.str.lti.addtype, this.settings.add_icon_url);
219             var editIcon = createIcon('lti_edit_tool_type', M.str.lti.edittype, this.settings.edit_icon_url);
220             var deleteIcon  = createIcon('lti_delete_tool_type', M.str.lti.deletetype, this.settings.delete_icon_url);
222             editIcon.on('click', function(e){
223                 var toolTypeId = typeSelector.get('value');
225                 if(self.getSelectedToolTypeOption().getAttribute('editable')){
226                     window.open(self.settings.instructor_tool_type_edit_url + '&action=edit&typeid=' + toolTypeId, 'edit_tool');
227                 } else {
228                     alert(M.str.lti.cannot_edit);
229                 }
230             });
232             addIcon.on('click', function(e){
233                 window.open(self.settings.instructor_tool_type_edit_url + '&action=add', 'add_tool');
234             });
236             deleteIcon.on('click', function(e){
237                 var toolTypeId = typeSelector.get('value');
239                 if(self.getSelectedToolTypeOption().getAttribute('editable')){
240                     if(confirm(M.str.lti.delete_confirmation)){
241                         self.deleteTool(toolTypeId);
242                     }
243                 } else {
244                     alert(M.str.lti.cannot_delete);
245                 }
246             });
248             typeSelector.insert(addIcon, 'after');
249             addIcon.insert(editIcon, 'after');
250             editIcon.insert(deleteIcon, 'after');
251         },
253         toggleEditButtons: function(){
254             var lti_edit_tool_type = Y.one('#lti_edit_tool_type');
255             var lti_delete_tool_type = Y.one('#lti_delete_tool_type');
257             //Make the edit / delete icons look enabled / disabled.
258             //Does not work in older browsers, but alerts will catch those cases.
259             if(this.getSelectedToolTypeOption().getAttribute('editable')){
260                 lti_edit_tool_type.setStyle('opacity', '1');
261                 lti_delete_tool_type.setStyle('opacity', '1');
262             } else {
263                 lti_edit_tool_type.setStyle('opacity', '.2');
264                 lti_delete_tool_type.setStyle('opacity', '.2');
265             }
266         },
268         addToolType: function(toolType){
269             var typeSelector = Y.one('#id_typeid');
270             var course_tool_group = Y.one('#course_tool_group');
272             var option = Y.Node.create('<option />')
273                             .set('text', toolType.name)
274                             .set('value', toolType.id)
275                             .set('selected', 'selected')
276                             .setAttribute('editable', '1')
277                             .setAttribute('courseTool', '1')
278                             .setAttribute('domain', toolType.tooldomain);
280             if(course_tool_group){
281                 course_tool_group.append(option);
282             } else {
283                 typeSelector.append(option);
284             }
286             //Adding the new tool may affect which tool gets matched automatically
287             this.clearToolCache();
288             this.updateAutomaticToolMatch(Y.one('#id_toolurl'));
289             this.updateAutomaticToolMatch(Y.one('#id_securetoolurl'));
290         },
292         updateToolType: function(toolType){
293             var typeSelector = Y.one('#id_typeid');
295             var option = typeSelector.one('option[value="' + toolType.id + '"]');
296             option.set('text', toolType.name)
297                   .set('domain', toolType.tooldomain);
299             //Editing the tool may affect which tool gets matched automatically
300             this.clearToolCache();
301             this.updateAutomaticToolMatch(Y.one('#id_toolurl'));
302             this.updateAutomaticToolMatch(Y.one('#id_securetoolurl'));
303         },
305         deleteTool: function(toolTypeId){
306             var self = this;
308             Y.io(self.settings.instructor_tool_type_edit_url + '&action=delete&typeid=' + toolTypeId, {
309                 on: {
310                     success: function(){
311                         self.getSelectedToolTypeOption().remove();
313                         //Editing the tool may affect which tool gets matched automatically
314                         self.clearToolCache();
315                         self.updateAutomaticToolMatch(Y.one('#id_toolurl'));
316                         self.updateAutomaticToolMatch(Y.one('#id_securetoolurl'));
317                     },
318                     failure: function(){
320                     }
321                 }
322             });
323         },
325         findToolByUrl: function(url, callback){
326             var self = this;
328             Y.io(self.settings.ajax_url, {
329                 data: {action: 'find_tool_config',
330                         course: self.settings.courseId,
331                         toolurl: url
332                 },
334                 on: {
335                     success: function(transactionid, xhr){
336                         var response = xhr.response;
338                         var toolInfo = Y.JSON.parse(response);
340                         callback(toolInfo);
341                     },
342                     failure: function(){
344                     }
345                 }
346             });
347         }
349     };
350 })();