mirror of
https://github.com/moodle/moodle.git
synced 2025-08-10 03:16:42 +02:00

Fixes a bug with YUI, resulting in the selected option not being set properly again after the options have been reorganised into optgroups.
578 lines
24 KiB
JavaScript
578 lines
24 KiB
JavaScript
// 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 <http://www.gnu.org/licenses/>.
|
|
|
|
/**
|
|
* 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 = {};
|
|
|
|
var updateToolMatches = function(){
|
|
self.updateAutomaticToolMatch(Y.one('#id_toolurl'));
|
|
self.updateAutomaticToolMatch(Y.one('#id_securetoolurl'));
|
|
};
|
|
|
|
var typeSelector = Y.one('#id_typeid');
|
|
if (typeSelector) {
|
|
this.addOptGroups();
|
|
|
|
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();
|
|
}
|
|
});
|
|
|
|
this.createTypeEditorButtons();
|
|
|
|
this.toggleEditButtons();
|
|
}
|
|
|
|
var contentItemButton = Y.one('[name="selectcontent"]');
|
|
if (contentItemButton) {
|
|
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(returnData) {
|
|
if (!returnData.multiple) {
|
|
M.mod_lti.editor.toggleGradeSection();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
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);
|
|
|
|
if (typeSelector) {
|
|
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){
|
|
if (!field) {
|
|
return;
|
|
}
|
|
|
|
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('<span />')
|
|
.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', '<img style="vertical-align:text-bottom" src="' + self.settings.green_check_icon_url + '" />' + 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', '<img style="vertical-align:text-bottom" src="' + self.settings.warning_icon_url + '" />' + 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', '<img style="vertical-align:text-bottom" src="' + self.settings.green_check_icon_url + '" />' + 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', '<img style="vertical-align:text-bottom" src="' + self.settings.green_check_icon_url + '" />' + 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', '<img style="vertical-align:text-bottom" src="' + self.settings.warning_icon_url + '" />' + M.util.get_string('tool_config_not_found', 'lti'));
|
|
}
|
|
}
|
|
if (toolInfo.cartridge) {
|
|
automatchToolDisplay.set('innerHTML', '<img style="vertical-align:text-bottom" src="' + self.settings.green_check_icon_url +
|
|
'" />' + 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('<optgroup />')
|
|
.set('id', 'global_tool_group')
|
|
.set('label', M.util.get_string('global_tool_types', 'lti'));
|
|
|
|
var courseGroup = Y.Node.create('<optgroup />')
|
|
.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);
|
|
}
|
|
|
|
// Fixes what is presumably a bug in YUI, in which the selected option is not properly set after reorganising the
|
|
// options into optgroups.
|
|
var selectedOption = typeSelector.one('[selected]');
|
|
if (selectedOption) {
|
|
selectedOption.set('selected', true);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 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('<a />')
|
|
.set('id', id)
|
|
.set('title', tooltip)
|
|
.setStyle('margin-left', '.5em')
|
|
.set('href', 'javascript:void(0);')
|
|
.append(Y.Node.create('<img src="' + iconUrl + '" />'));
|
|
}
|
|
|
|
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('<option />')
|
|
.set('text', toolType.name)
|
|
.set('value', toolType.id)
|
|
.set('selected', 'selected')
|
|
.setAttribute('editable', '1')
|
|
.setAttribute('courseTool', '1')
|
|
.setAttribute('domain', toolType.tooldomain);
|
|
|
|
if(course_tool_group){
|
|
course_tool_group.append(option);
|
|
} else {
|
|
typeSelector.append(option);
|
|
}
|
|
|
|
// Adding the new tool may affect which tool gets matched automatically
|
|
this.clearToolCache();
|
|
this.updateAutomaticToolMatch(Y.one('#id_toolurl'));
|
|
this.updateAutomaticToolMatch(Y.one('#id_securetoolurl'));
|
|
this.toggleEditButtons();
|
|
|
|
require(["core/notification"], function (notification) {
|
|
notification.addNotification({
|
|
message: M.util.get_string('tooltypeadded', 'lti'),
|
|
type: "success"
|
|
});
|
|
});
|
|
},
|
|
|
|
updateToolType: function(toolType){
|
|
var typeSelector = Y.one('#id_typeid');
|
|
|
|
var option = typeSelector.one('option[value="' + toolType.id + '"]');
|
|
option.set('text', toolType.name)
|
|
.set('domain', toolType.tooldomain);
|
|
|
|
// Editing the tool may affect which tool gets matched automatically
|
|
this.clearToolCache();
|
|
this.updateAutomaticToolMatch(Y.one('#id_toolurl'));
|
|
this.updateAutomaticToolMatch(Y.one('#id_securetoolurl'));
|
|
|
|
require(["core/notification"], function (notification) {
|
|
notification.addNotification({
|
|
message: M.util.get_string('tooltypeupdated', 'lti'),
|
|
type: "success"
|
|
});
|
|
});
|
|
},
|
|
|
|
deleteTool: function(toolTypeId){
|
|
var self = this;
|
|
|
|
Y.io(self.settings.instructor_tool_type_edit_url + '&action=delete&typeid=' + toolTypeId, {
|
|
on: {
|
|
success: function(){
|
|
self.getSelectedToolTypeOption().remove();
|
|
|
|
// Editing the tool may affect which tool gets matched automatically
|
|
self.clearToolCache();
|
|
self.updateAutomaticToolMatch(Y.one('#id_toolurl'));
|
|
self.updateAutomaticToolMatch(Y.one('#id_securetoolurl'));
|
|
|
|
require(["core/notification"], function (notification) {
|
|
notification.addNotification({
|
|
message: M.util.get_string('tooltypedeleted', 'lti'),
|
|
type: "success"
|
|
});
|
|
});
|
|
},
|
|
failure: function(){
|
|
require(["core/notification"], function (notification) {
|
|
notification.addNotification({
|
|
message: M.util.get_string('tooltypenotdeleted', 'lti'),
|
|
type: "problem"
|
|
});
|
|
});
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
findToolByUrl: function(url, toolId, callback){
|
|
var self = this;
|
|
|
|
Y.io(self.settings.ajax_url, {
|
|
data: {action: 'find_tool_config',
|
|
course: self.settings.courseId,
|
|
toolurl: url,
|
|
toolid: toolId || 0
|
|
},
|
|
|
|
on: {
|
|
success: function(transactionid, xhr){
|
|
var response = xhr.response;
|
|
|
|
var toolInfo = Y.JSON.parse(response);
|
|
|
|
callback(toolInfo);
|
|
},
|
|
failure: function(){
|
|
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
/**
|
|
* Gets the tool type ID of the selected tool that supports Content-Item selection.
|
|
*
|
|
* @returns {number|boolean} The ID of the tool type if it supports Content-Item selection. False, otherwise.
|
|
*/
|
|
getContentItemId: function() {
|
|
try {
|
|
var selected = this.getSelectedToolTypeOption();
|
|
if (selected.getAttribute('data-contentitem')) {
|
|
return selected.getAttribute('data-id');
|
|
}
|
|
return false;
|
|
} catch (err) {
|
|
// Tool selector not available - check for hidden fields instead.
|
|
var content = Y.one('input[name="contentitem"]');
|
|
if (!content || !content.get('value')) {
|
|
return false;
|
|
}
|
|
return Y.one('input[name="typeid"]').get('value');
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Resets the values of fields related to the LTI tool settings.
|
|
*/
|
|
resetToolFields: function() {
|
|
// Reset values for all text fields.
|
|
var fields = Y.all('#id_toolurl, #id_securetoolurl, #id_instructorcustomparameters, #id_icon, #id_secureicon');
|
|
fields.set('value', null);
|
|
|
|
// Reset value for launch container select box.
|
|
Y.one('#id_launchcontainer').set('value', 1);
|
|
}
|
|
};
|
|
})();
|