// This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . /** * Javascript extensions for the External Tool activity editor. * * @package mod * @subpackage lti * @copyright Copyright (c) 2011 Moodlerooms Inc. (http://www.moodlerooms.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ (function(){ var Y; M.mod_lti = M.mod_lti || {}; M.mod_lti.LTI_SETTING_NEVER = 0; M.mod_lti.LTI_SETTING_ALWAYS = 1; M.mod_lti.LTI_SETTING_DELEGATE = 2; M.mod_lti.editor = { init: function(yui3, settings){ if(yui3){ Y = yui3; } var self = this; this.settings = Y.JSON.parse(settings); this.urlCache = {}; this.toolTypeCache = {}; this.addOptGroups(); var updateToolMatches = function(){ self.updateAutomaticToolMatch(Y.one('#id_toolurl')); self.updateAutomaticToolMatch(Y.one('#id_securetoolurl')); }; var typeSelector = Y.one('#id_typeid'); typeSelector.on('change', function(e){ // Reset configuration fields when another preconfigured tool is selected. self.resetToolFields(); updateToolMatches(); self.toggleEditButtons(); if (self.getSelectedToolTypeOption().getAttribute('toolproxy')){ var allowname = Y.one('#id_instructorchoicesendname'); allowname.set('checked', !self.getSelectedToolTypeOption().getAttribute('noname')); var allowemail = Y.one('#id_instructorchoicesendemailaddr'); allowemail.set('checked', !self.getSelectedToolTypeOption().getAttribute('noemail')); var allowgrades = Y.one('#id_instructorchoiceacceptgrades'); allowgrades.set('checked', !self.getSelectedToolTypeOption().getAttribute('nogrades')); self.toggleGradeSection(); } }); var contentItemButton = Y.one('[name="selectcontent"]'); var contentItemUrl = contentItemButton.getAttribute('data-contentitemurl'); // Handle configure from link button click. contentItemButton.on('click', function() { var contentItemId = self.getContentItemId(); if (contentItemId) { // Get activity name and description values. var title = Y.one('#id_name').get('value').trim(); var text = Y.one('#id_introeditor').get('value').trim(); // Set data to be POSTed. var postData = { id: contentItemId, course: self.settings.courseId, title: title, text: text }; require(['mod_lti/contentitem'], function(contentitem) { contentitem.init(contentItemUrl, postData, function() { M.mod_lti.editor.toggleGradeSection(); }); }); } }); this.createTypeEditorButtons(); this.toggleEditButtons(); var textAreas = new Y.NodeList([ Y.one('#id_toolurl'), Y.one('#id_securetoolurl'), Y.one('#id_resourcekey'), Y.one('#id_password') ]); var debounce; textAreas.on('keyup', function(e){ clearTimeout(debounce); // If no more changes within 2 seconds, look up the matching tool URL debounce = setTimeout(function(){ updateToolMatches(); }, 2000); }); var allowgrades = Y.one('#id_instructorchoiceacceptgrades'); allowgrades.on('change', this.toggleGradeSection, this); updateToolMatches(); }, toggleGradeSection: function(e) { if (e) { e.preventDefault(); } var allowgrades = Y.one('#id_instructorchoiceacceptgrades'); var gradefieldset = Y.one('#id_modstandardgrade'); if (!allowgrades.get('checked')) { gradefieldset.hide(); } else { gradefieldset.show(); } }, clearToolCache: function(){ this.urlCache = {}; this.toolTypeCache = {}; }, updateAutomaticToolMatch: function(field){ var self = this; var toolurl = field; var typeSelector = Y.one('#id_typeid'); var id = field.get('id') + '_lti_automatch_tool'; var automatchToolDisplay = Y.one('#' + id); if(!automatchToolDisplay){ automatchToolDisplay = Y.Node.create('') .set('id', id) .setStyle('padding-left', '1em'); toolurl.insert(automatchToolDisplay, 'after'); } var url = toolurl.get('value'); // Hide the display if the url box is empty if(!url){ automatchToolDisplay.setStyle('display', 'none'); } else { automatchToolDisplay.set('innerHTML', ''); automatchToolDisplay.setStyle('display', ''); } var selectedToolType = parseInt(typeSelector.get('value')); var selectedOption = typeSelector.one('option[value="' + selectedToolType + '"]'); // A specific tool type is selected (not "auto") // We still need to check with the server to get privacy settings if(selectedToolType > 0){ // If the entered domain matches the domain of the tool configuration... var domainRegex = /(?:https?:\/\/)?(?:www\.)?([^\/]+)(?:\/|$)/i; var match = domainRegex.exec(url); if(match && match[1] && match[1].toLowerCase() === selectedOption.getAttribute('domain').toLowerCase()){ automatchToolDisplay.set('innerHTML', '' + M.util.get_string('using_tool_configuration', 'lti') + selectedOption.get('text')); } else { // The entered URL does not match the domain of the tool configuration automatchToolDisplay.set('innerHTML', '' + M.util.get_string('domain_mismatch', 'lti')); } } var key = Y.one('#id_resourcekey'); var secret = Y.one('#id_password'); // Indicate the tool is manually configured // We still check the Launch URL with the server as course/site tools may override privacy settings if(key.get('value') !== '' && secret.get('value') !== ''){ automatchToolDisplay.set('innerHTML', '' + M.util.get_string('custom_config', 'lti')); } var continuation = function(toolInfo, inputfield){ if (inputfield === undefined || (inputfield.get('id') != 'id_securetoolurl' || inputfield.get('value'))) { self.updatePrivacySettings(toolInfo); } if(toolInfo.toolname){ automatchToolDisplay.set('innerHTML', '' + M.util.get_string('using_tool_configuration', 'lti') + toolInfo.toolname); } else if(!selectedToolType) { // Inform them custom configuration is in use if(key.get('value') === '' || secret.get('value') === ''){ automatchToolDisplay.set('innerHTML', '' + M.util.get_string('tool_config_not_found', 'lti')); } } if (toolInfo.cartridge) { automatchToolDisplay.set('innerHTML', '' + M.util.get_string('using_tool_cartridge', 'lti')); } }; // Cache urls which have already been checked to increase performance // Don't use URL cache if tool type manually selected if(selectedToolType && self.toolTypeCache[selectedToolType]){ return continuation(self.toolTypeCache[selectedToolType]); } else if(self.urlCache[url] && !selectedToolType){ return continuation(self.urlCache[url]); } else if(!selectedToolType && !url) { // No tool type or url set return continuation({}, field); } else { self.findToolByUrl(url, selectedToolType, function(toolInfo){ if(toolInfo){ // Cache the result based on whether the URL or tool type was used to look up the tool if(!selectedToolType){ self.urlCache[url] = toolInfo; } else { self.toolTypeCache[selectedToolType] = toolInfo; } Y.one('#id_urlmatchedtypeid').set('value', toolInfo.toolid); continuation(toolInfo); } }); } }, /** * Updates display of privacy settings to show course / site tool configuration settings. */ updatePrivacySettings: function(toolInfo){ if(!toolInfo || !toolInfo.toolid){ toolInfo = { sendname: M.mod_lti.LTI_SETTING_DELEGATE, sendemailaddr: M.mod_lti.LTI_SETTING_DELEGATE, acceptgrades: M.mod_lti.LTI_SETTING_DELEGATE } } var setting, control; var privacyControls = { sendname: Y.one('#id_instructorchoicesendname'), sendemailaddr: Y.one('#id_instructorchoicesendemailaddr'), acceptgrades: Y.one('#id_instructorchoiceacceptgrades') }; // Store a copy of user entered privacy settings as we may overwrite them if(!this.userPrivacySettings){ this.userPrivacySettings = {}; } for(setting in privacyControls){ if(privacyControls.hasOwnProperty(setting)){ control = privacyControls[setting]; // Only store the value if it hasn't been forced by the editor if(!control.get('disabled')){ this.userPrivacySettings[setting] = control.get('checked'); } } } // Update UI based on course / site tool configuration for(setting in privacyControls){ if(privacyControls.hasOwnProperty(setting)){ var settingValue = toolInfo[setting]; control = privacyControls[setting]; if(settingValue == M.mod_lti.LTI_SETTING_NEVER){ control.set('disabled', true); control.set('checked', false); control.set('title', M.util.get_string('forced_help', 'lti')); } else if(settingValue == M.mod_lti.LTI_SETTING_ALWAYS){ control.set('disabled', true); control.set('checked', true); control.set('title', M.util.get_string('forced_help', 'lti')); } else if(settingValue == M.mod_lti.LTI_SETTING_DELEGATE){ control.set('disabled', false); // Get the value out of the stored copy control.set('checked', this.userPrivacySettings[setting]); control.set('title', ''); } } } this.toggleGradeSection(); }, getSelectedToolTypeOption: function(){ var typeSelector = Y.one('#id_typeid'); return typeSelector.one('option[value="' + typeSelector.get('value') + '"]'); }, /** * Separate tool listing into option groups. Server-side select control * doesn't seem to support this. */ addOptGroups: function(){ var typeSelector = Y.one('#id_typeid'); if(typeSelector.one('option[courseTool=1]')){ // One ore more course tools exist var globalGroup = Y.Node.create('') .set('id', 'global_tool_group') .set('label', M.util.get_string('global_tool_types', 'lti')); var courseGroup = Y.Node.create('') .set('id', 'course_tool_group') .set('label', M.util.get_string('course_tool_types', 'lti')); var globalOptions = typeSelector.all('option[globalTool=1]').remove().each(function(node){ globalGroup.append(node); }); var courseOptions = typeSelector.all('option[courseTool=1]').remove().each(function(node){ courseGroup.append(node); }); if(globalOptions.size() > 0){ typeSelector.append(globalGroup); } if(courseOptions.size() > 0){ typeSelector.append(courseGroup); } } }, /** * Adds buttons for creating, editing, and deleting tool types. * Javascript is a requirement to edit course level tools at this point. */ createTypeEditorButtons: function(){ var self = this; var typeSelector = Y.one('#id_typeid'); var createIcon = function(id, tooltip, iconUrl){ return Y.Node.create('') .set('id', id) .set('title', tooltip) .setStyle('margin-left', '.5em') .set('href', 'javascript:void(0);') .append(Y.Node.create('')); } var addIcon = createIcon('lti_add_tool_type', M.util.get_string('addtype', 'lti'), this.settings.add_icon_url); var editIcon = createIcon('lti_edit_tool_type', M.util.get_string('edittype', 'lti'), this.settings.edit_icon_url); var deleteIcon = createIcon('lti_delete_tool_type', M.util.get_string('deletetype', 'lti'), this.settings.delete_icon_url); editIcon.on('click', function(e){ var toolTypeId = typeSelector.get('value'); if(self.getSelectedToolTypeOption().getAttribute('editable')){ window.open(self.settings.instructor_tool_type_edit_url + '&action=edit&typeid=' + toolTypeId, 'edit_tool'); } else { alert(M.util.get_string('cannot_edit', 'lti')); } }); addIcon.on('click', function(e){ window.open(self.settings.instructor_tool_type_edit_url + '&action=add', 'add_tool'); }); deleteIcon.on('click', function(e){ var toolTypeId = typeSelector.get('value'); if(self.getSelectedToolTypeOption().getAttribute('editable')){ if(confirm(M.util.get_string('delete_confirmation', 'lti'))){ self.deleteTool(toolTypeId); } } else { alert(M.util.get_string('cannot_delete', 'lti')); } }); typeSelector.insert(addIcon, 'after'); addIcon.insert(editIcon, 'after'); editIcon.insert(deleteIcon, 'after'); }, toggleEditButtons: function(){ var lti_edit_tool_type = Y.one('#lti_edit_tool_type'); var lti_delete_tool_type = Y.one('#lti_delete_tool_type'); // Make the edit / delete icons look enabled / disabled. // Does not work in older browsers, but alerts will catch those cases. if(this.getSelectedToolTypeOption().getAttribute('editable')){ lti_edit_tool_type.setStyle('opacity', '1'); lti_delete_tool_type.setStyle('opacity', '1'); } else { lti_edit_tool_type.setStyle('opacity', '.2'); lti_delete_tool_type.setStyle('opacity', '.2'); } }, addToolType: function(toolType){ var typeSelector = Y.one('#id_typeid'); var course_tool_group = Y.one('#course_tool_group'); var option = Y.Node.create('