diff --git a/course/lib.php b/course/lib.php index 88de1064299..c83265e3d67 100644 --- a/course/lib.php +++ b/course/lib.php @@ -3155,7 +3155,7 @@ function moveto_module($mod, $section, $beforemod=NULL) { * @return string XHTML for the editing buttons */ function make_editing_buttons(stdClass $mod, $absolute_ignored = true, $moveselect = true, $indent=-1, $section=-1) { - global $CFG, $OUTPUT; + global $CFG, $OUTPUT, $COURSE; static $str; @@ -3191,6 +3191,7 @@ function make_editing_buttons(stdClass $mod, $absolute_ignored = true, $movesele $str->forcedgroupsnone = get_string('forcedmodeinbrackets', 'moodle', get_string("groupsnone")); $str->forcedgroupsseparate = get_string('forcedmodeinbrackets', 'moodle', get_string("groupsseparate")); $str->forcedgroupsvisible = get_string('forcedmodeinbrackets', 'moodle', get_string("groupsvisible")); + $str->edittitle = get_string('edittitle', 'moodle'); } $baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey())); @@ -3200,6 +3201,16 @@ function make_editing_buttons(stdClass $mod, $absolute_ignored = true, $movesele } $actions = array(); + // AJAX edit title + if ($mod->modname !== 'label' && $hasmanageactivities && course_ajax_enabled($COURSE)) { + $actions[] = new action_link( + new moodle_url($baseurl, array('update' => $mod->id)), + new pix_icon('t/editstring', $str->edittitle, 'moodle', array('class' => 'iconsmall visibleifjs')), + null, + array('class' => 'editing_title', 'title' => $str->edittitle) + ); + } + // leftright if ($hasmanageactivities) { if (right_to_left()) { // Exchange arrows on RTL @@ -4523,6 +4534,8 @@ function include_course_ajax($course, $modules = array(), $config = null) { 'moveleft', 'deletechecktype', 'deletechecktypename', + 'edittitle', + 'edittitleinstructions', 'show', 'hide', 'groupsnone', diff --git a/course/rest.php b/course/rest.php index 8ec14e6b467..5152563ad92 100644 --- a/course/rest.php +++ b/course/rest.php @@ -43,6 +43,7 @@ $summary = optional_param('summary', '', PARAM_RAW); $sequence = optional_param('sequence', '', PARAM_SEQUENCE); $visible = optional_param('visible', 0, PARAM_INT); $pageaction = optional_param('action', '', PARAM_ALPHA); // Used to simulate a DELETE command +$title = optional_param('title', '', PARAM_TEXT); $PAGE->set_url('/course/rest.php', array('courseId'=>$courseid,'class'=>$class)); @@ -56,8 +57,8 @@ if (in_array($class, array('resource'))) { $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id); } else { require_login($course); - $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id); } +$coursecontext = get_context_instance(CONTEXT_COURSE, $course->id); require_sesskey(); echo $OUTPUT->header(); // send headers @@ -128,6 +129,39 @@ switch($requestmethod) { moveto_module($cm, $section, $beforemod); break; + case 'gettitle': + require_capability('moodle/course:manageactivities', $modcontext); + $cm = get_coursemodule_from_id('', $id, 0, false, MUST_EXIST); + $module = new stdClass(); + $module->id = $cm->instance; + + // Don't pass edit strings through multilang filters - we need the entire string + echo json_encode(array('instancename' => $cm->name)); + break; + case 'updatetitle': + require_capability('moodle/course:manageactivities', $modcontext); + $cm = get_coursemodule_from_id('', $id, 0, false, MUST_EXIST); + $module = new stdClass(); + $module->id = $cm->instance; + + // Escape strings as they would be by mform + if (!empty($CFG->formatstringstriptags)) { + $module->name = clean_param($title, PARAM_TEXT); + } else { + $module->name = clean_param($title, PARAM_CLEANHTML); + } + + if (!empty($module->name)) { + $DB->update_record($cm->modname, $module); + } else { + $module->name = $cm->name; + } + + // We need to return strings after they've been through filters for multilang + $stringoptions = new stdClass; + $stringoptions->context = $coursecontext; + echo json_encode(array('instancename' => format_string($module->name, true, $stringoptions))); + break; } rebuild_course_cache($course->id); break; diff --git a/course/yui/toolboxes/toolboxes.js b/course/yui/toolboxes/toolboxes.js index 562307ace28..0c216484bb6 100644 --- a/course/yui/toolboxes/toolboxes.js +++ b/course/yui/toolboxes/toolboxes.js @@ -9,6 +9,7 @@ YUI.add('moodle-course-toolboxes', function(Y) { DELETE : 'a.editing_delete', DIMCLASS : 'dimmed', DIMMEDTEXT : 'dimmed_text', + EDITTITLE : 'a.editing_title', EDITTITLECLASS : 'edittitle', GENERICICONCLASS : 'iconsmall', GROUPSNONE : 'a.editing_groupsnone', @@ -257,6 +258,9 @@ YUI.add('moodle-course-toolboxes', function(Y) { Y.all(baseselector).each(this._setup_for_resource, this); }, _setup_for_resource : function(toolboxtarget) { + // Edit Title + this.replace_button(toolboxtarget, CSS.COMMANDSPAN + ' ' + CSS.EDITTITLE, this.edit_resource_title); + // Move left and right this.replace_button(toolboxtarget, CSS.COMMANDSPAN + ' ' + CSS.MOVELEFT, this.move_left); this.replace_button(toolboxtarget, CSS.COMMANDSPAN + ' ' + CSS.MOVERIGHT, this.move_right); @@ -482,6 +486,114 @@ YUI.add('moodle-course-toolboxes', function(Y) { anchor.appendChild(newicon); anchor.on('click', this.move_left, this); moveright.insert(anchor, 'before'); + }, + /** + * Edit the title for the resource + */ + edit_resource_title : function(e) { + // Get the element we're working on + var element = e.target.ancestor(CSS.ACTIVITYLI); + var instancename = element.one(CSS.INSTANCENAME); + var currenttitle = instancename.get('firstChild'); + var oldtitle = currenttitle.get('data'); + var titletext = oldtitle; + var editbutton = element.one('a.' + CSS.EDITTITLECLASS + ' img'); + + // Disable the current href to prevent redirections when editing + var anchor = instancename.ancestor('a'); + anchor.setAttribute('oldhref', anchor.getAttribute('href')); + anchor.removeAttribute('href'); + + var data = { + 'class' : 'resource', + 'field' : 'gettitle', + 'id' : this.get_element_id(element) + }; + + // Try to retrieve the existing string from the server + var response = this.send_request(data, editbutton); + if (response.instancename) { + titletext = response.instancename; + } + + // Create the editor and submit button + var editor = Y.Node.create('') + .setAttrs({ + 'name' : 'title', + 'value' : titletext, + 'autocomplete' : 'off' + }) + .addClass('titleeditor'); + var editform = Y.Node.create('
') + .setStyle('padding', '0') + .setStyle('display', 'inline') + .setAttribute('action', '#'); + + var editinstructions = Y.Node.create('') + .addClass('editinstructions') + .set('innerHTML', M.util.get_string('edittitleinstructions', 'moodle')); + + // Clear the existing content and put the editor in + currenttitle.set('data', ''); + editform.appendChild(editor); + instancename.appendChild(editform); + element.appendChild(editinstructions); + e.preventDefault(); + + // Focus and select the editor text + editor.focus().select(); + + // Handle cancellation of the editor + editor.on('blur', function(e) { + // Detach the blur event before removing as some actions trigger multiple blurs in + // some browser + editor.detach('blur'); + editform.remove(); + editinstructions.remove(); + + // Set the title and anchor back to their previous settings + currenttitle.set('data', oldtitle); + anchor.setAttribute('href', anchor.getAttribute('oldhref')); + anchor.removeAttribute('oldhref'); + }); + + // Handle form submission + editform.on('submit', function(e) { + // We don't actually want to submit anything + e.preventDefault(); + + // Detach the handlers to prevent multiple submissions + editform.detach('submit'); + editor.detach('blur'); + + // We only accept strings which have valid content + var newtitle = Y.Lang.trim(editor.get('value')); + if (newtitle != null && newtitle != "" && newtitle != titletext) { + var data = { + 'class' : 'resource', + 'field' : 'updatetitle', + 'title' : newtitle, + 'id' : this.get_element_id(element) + }; + var response = this.send_request(data, editbutton); + if (response.instancename) { + currenttitle.set('data', response.instancename); + } + } else { + // Invalid content. Set the title back to it's original contents + currenttitle.set('data', oldtitle); + } + + editform.remove(); + editinstructions.remove(); + + // We need a timeout here otherwise hitting return to save in some browsers triggers + // the anchor + setTimeout(function(e) { + anchor.setAttribute('href', anchor.getAttribute('oldhref')); + anchor.removeAttribute('oldhref'); + }, 500); + }, this); } }, { NAME : 'course-resource-toolbox', diff --git a/lang/en/moodle.php b/lang/en/moodle.php index 9e6736ad94d..6f4e8f66ff2 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -508,6 +508,8 @@ $string['editorsettings'] = 'Editor settings'; $string['editorshortcutkeys'] = 'Editor shortcut keys'; $string['editsettings'] = 'Edit settings'; $string['editsummary'] = 'Edit summary'; +$string['edittitle'] = 'Edit title'; +$string['edittitleinstructions'] = 'Escape to cancel, Enter when finished'; $string['editthisactivity'] = 'Edit this activity'; $string['editthiscategory'] = 'Edit this category'; $string['edituser'] = 'Edit user accounts'; diff --git a/pix/t/editstring.png b/pix/t/editstring.png new file mode 100644 index 00000000000..08bd5e475a9 Binary files /dev/null and b/pix/t/editstring.png differ diff --git a/theme/base/style/course.css b/theme/base/style/course.css index 5a50d6fb40d..32d12b461be 100644 --- a/theme/base/style/course.css +++ b/theme/base/style/course.css @@ -133,3 +133,20 @@ table.category_subcategories {margin-bottom:1em;} table.category_subcategories td {white-space: nowrap;} + +span.editinstructions { + position: relative; + top: 5px; + left: 19px; + width: 329px; + padding: 3px; + background-color: #dddddd; + color: #000000; + text-decoration: none; + z-index: 100; + border: 1px solid black; +} + +input.titleeditor { + width: 330px; +}