MDL-52869 course: use inplace_editable for activity names
This commit is contained in:
@@ -51,6 +51,7 @@ class block_site_main_menu extends block_list {
|
||||
require_once($CFG->dirroot.'/course/lib.php');
|
||||
$context = context_course::instance($course->id);
|
||||
$isediting = $this->page->user_is_editing() && has_capability('moodle/course:manageactivities', $context);
|
||||
$courserenderer = $this->page->get_renderer('core', 'course');
|
||||
|
||||
/// extra fast view mode
|
||||
if (!$isediting) {
|
||||
@@ -69,32 +70,18 @@ class block_site_main_menu extends block_list {
|
||||
}
|
||||
|
||||
if (!empty($cm->url)) {
|
||||
$attrs = array();
|
||||
$attrs['title'] = $cm->modfullname;
|
||||
$attrs['class'] = $cm->extraclasses . ' activity-action';
|
||||
if ($cm->onclick) {
|
||||
// Get on-click attribute value if specified and decode the onclick - it
|
||||
// has already been encoded for display.
|
||||
$attrs['onclick'] = htmlspecialchars_decode($cm->onclick);
|
||||
}
|
||||
if (!$cm->visible) {
|
||||
$attrs['class'] .= ' dimmed';
|
||||
}
|
||||
$icon = '<img src="' . $cm->get_icon_url() . '" class="icon" alt="" />';
|
||||
$content = html_writer::link($cm->url, $icon . $cm->get_formatted_name(), $attrs);
|
||||
$content = html_writer::div($courserenderer->course_section_cm_name($cm), 'activity');
|
||||
} else {
|
||||
$content = $cm->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
|
||||
}
|
||||
|
||||
$this->content->items[] = $indent.html_writer::div($content, 'main-menu-content');
|
||||
$this->content->items[] = $indent . $content;
|
||||
}
|
||||
}
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
// Slow & hacky editing mode.
|
||||
/** @var core_course_renderer $courserenderer */
|
||||
$courserenderer = $this->page->get_renderer('core', 'course');
|
||||
$ismoving = ismoving($course->id);
|
||||
course_create_sections_if_missing($course, 0);
|
||||
$modinfo = get_fast_modinfo($course);
|
||||
@@ -104,11 +91,9 @@ class block_site_main_menu extends block_list {
|
||||
$strmovehere = get_string('movehere');
|
||||
$strmovefull = strip_tags(get_string('movefull', '', "'$USER->activitycopyname'"));
|
||||
$strcancel= get_string('cancel');
|
||||
$stractivityclipboard = $USER->activitycopyname;
|
||||
} else {
|
||||
$strmove = get_string('move');
|
||||
}
|
||||
$editbuttons = '';
|
||||
|
||||
if ($ismoving) {
|
||||
$this->content->icons[] = '<img src="'.$OUTPUT->pix_url('t/move') . '" class="iconsmall" alt="" />';
|
||||
@@ -116,7 +101,6 @@ class block_site_main_menu extends block_list {
|
||||
}
|
||||
|
||||
if (!empty($modinfo->sections[0])) {
|
||||
$options = array('overflowdiv'=>true);
|
||||
foreach ($modinfo->sections[0] as $modnumber) {
|
||||
$mod = $modinfo->cms[$modnumber];
|
||||
if (!$mod->uservisible) {
|
||||
@@ -153,27 +137,12 @@ class block_site_main_menu extends block_list {
|
||||
} else {
|
||||
$indent = '';
|
||||
}
|
||||
$url = $mod->url;
|
||||
if (!$url) {
|
||||
if (!$mod->url) {
|
||||
$content = $mod->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
|
||||
} else {
|
||||
//Accessibility: incidental image - should be empty Alt text
|
||||
$attrs = array();
|
||||
$attrs['title'] = $mod->modfullname;
|
||||
$attrs['class'] = $mod->extraclasses . ' activity-action';
|
||||
if ($mod->onclick) {
|
||||
// Get on-click attribute value if specified and decode the onclick - it
|
||||
// has already been encoded for display.
|
||||
$attrs['onclick'] = htmlspecialchars_decode($mod->onclick);
|
||||
}
|
||||
if (!$mod->visible) {
|
||||
$attrs['class'] .= ' dimmed';
|
||||
}
|
||||
|
||||
$icon = '<img src="' . $mod->get_icon_url() . '" class="icon" alt="" />';
|
||||
$content = html_writer::link($url, $icon . $mod->get_formatted_name(), $attrs);
|
||||
$content = html_writer::div($courserenderer->course_section_cm_name($mod), ' activity');
|
||||
}
|
||||
$this->content->items[] = $indent.html_writer::div($content . $editbuttons, 'main-menu-content');
|
||||
$this->content->items[] = $indent. $content . $editbuttons;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,5 +6,4 @@
|
||||
.block_site_main_menu .footer { margin-top: 1em; }
|
||||
.block_site_main_menu .section_add_menus noscript div { display: inline;}
|
||||
.block_site_main_menu .mod-indent,
|
||||
.block_site_main_menu .main-menu-content { display: table-cell; }
|
||||
.block_site_main_menu .main-menu-content > .activity-action { display: block; }
|
||||
.block_site_main_menu .activity { display: table-cell; }
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
@block @block_main_menu
|
||||
Feature: Edit activities in main menu block
|
||||
In order to use main menu block
|
||||
As an admin
|
||||
I need to add and edit activities there
|
||||
|
||||
@javascript
|
||||
Scenario: Edit name of acitivity in-place in site main menu block
|
||||
Given I log in as "admin"
|
||||
And I am on site homepage
|
||||
And I navigate to "Turn editing on" node in "Front page settings"
|
||||
When I add a "Forum" to section "0" and I fill the form with:
|
||||
| Forum name | My forum name |
|
||||
And I click on "Edit title" "link" in the "//.[contains(@class,'block_site_main_menu')]//li[contains(.,'My forum name')]" "xpath_element"
|
||||
And I set the field "New name for activity My forum name" to "New forum name"
|
||||
And I press key "13" in the field "New name for activity My forum name"
|
||||
Then I should not see "My forum name"
|
||||
And I should see "New forum name"
|
||||
And I follow "New forum name"
|
||||
And I should not see "My forum name"
|
||||
And I should see "New forum name"
|
||||
@@ -48,6 +48,7 @@ class block_social_activities extends block_list {
|
||||
}
|
||||
|
||||
$course = $this->page->course;
|
||||
$courserenderer = $this->page->get_renderer('core', 'course');
|
||||
|
||||
require_once($CFG->dirroot.'/course/lib.php');
|
||||
|
||||
@@ -58,25 +59,18 @@ class block_social_activities extends block_list {
|
||||
/// extra fast view mode
|
||||
if (!$isediting) {
|
||||
if (!empty($modinfo->sections[0])) {
|
||||
$options = array('overflowdiv'=>true);
|
||||
foreach($modinfo->sections[0] as $cmid) {
|
||||
$cm = $modinfo->cms[$cmid];
|
||||
if (!$cm->uservisible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$content = $cm->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
|
||||
$instancename = $cm->get_formatted_name();
|
||||
|
||||
if (!($url = $cm->url)) {
|
||||
if (!$cm->url) {
|
||||
$content = $cm->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
|
||||
$this->content->items[] = $content;
|
||||
$this->content->icons[] = '';
|
||||
} else {
|
||||
$linkcss = $cm->visible ? '' : ' class="dimmed" ';
|
||||
//Accessibility: incidental image - should be empty Alt text
|
||||
$icon = '<img src="' . $cm->get_icon_url() . '" class="icon" alt="" /> ';
|
||||
$this->content->items[] = '<a title="'.$cm->modplural.'" '.$linkcss.' '.$cm->extra.
|
||||
' href="' . $url . '">' . $icon . $instancename . '</a>';
|
||||
$this->content->items[] = html_writer::div($courserenderer->course_section_cm_name($cm), 'activity');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,21 +79,16 @@ class block_social_activities extends block_list {
|
||||
|
||||
|
||||
// Slow & hacky editing mode.
|
||||
/** @var core_course_renderer $courserenderer */
|
||||
$courserenderer = $this->page->get_renderer('core', 'course');
|
||||
$ismoving = ismoving($course->id);
|
||||
$modinfo = get_fast_modinfo($course);
|
||||
$section = $modinfo->get_section_info(0);
|
||||
|
||||
if ($ismoving) {
|
||||
$strmovehere = get_string('movehere');
|
||||
$strmovefull = strip_tags(get_string('movefull', '', "'$USER->activitycopyname'"));
|
||||
$strcancel= get_string('cancel');
|
||||
$stractivityclipboard = $USER->activitycopyname;
|
||||
} else {
|
||||
$strmove = get_string('move');
|
||||
}
|
||||
$editbuttons = '';
|
||||
|
||||
if ($ismoving) {
|
||||
$this->content->icons[] = ' <img align="bottom" src="'.$OUTPUT->pix_url('t/move') . '" class="iconsmall" alt="" />';
|
||||
@@ -107,7 +96,6 @@ class block_social_activities extends block_list {
|
||||
}
|
||||
|
||||
if (!empty($modinfo->sections[0])) {
|
||||
$options = array('overflowdiv'=>true);
|
||||
foreach ($modinfo->sections[0] as $modnumber) {
|
||||
$mod = $modinfo->cms[$modnumber];
|
||||
if (!$mod->uservisible) {
|
||||
@@ -139,19 +127,13 @@ class block_social_activities extends block_list {
|
||||
'<img style="height:16px; width:80px; border:0px" src="'.$OUTPUT->pix_url('movehere') . '" alt="'.$strmovehere.'" /></a>';
|
||||
$this->content->icons[] = '';
|
||||
}
|
||||
$content = $mod->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
|
||||
$instancename = $mod->get_formatted_name();
|
||||
|
||||
$linkcss = $mod->visible ? '' : ' class="dimmed" ';
|
||||
|
||||
if (!($url = $mod->url)) {
|
||||
if (!$mod->url) {
|
||||
$content = $mod->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
|
||||
$this->content->items[] = $content . $editbuttons;
|
||||
$this->content->icons[] = '';
|
||||
} else {
|
||||
//Accessibility: incidental image - should be empty Alt text
|
||||
$icon = '<img src="' . $mod->get_icon_url() . '" class="icon" alt="" /> ';
|
||||
$this->content->items[] = '<a title="' . $mod->modfullname . '" ' . $linkcss . ' ' . $mod->extra .
|
||||
' href="' . $url . '">' . $icon . $instancename . '</a>' . $editbuttons;
|
||||
$this->content->items[] = html_writer::div($courserenderer->course_section_cm_name($mod), 'activity') .
|
||||
$editbuttons;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
@block @block_social_activities
|
||||
Feature: Edit activities in social activities block
|
||||
In order to use social activities block
|
||||
As a teacher
|
||||
I need to add and edit activities there
|
||||
|
||||
@javascript
|
||||
Scenario: Edit name of acitivity in-place in social activities block
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname | format |
|
||||
| Course 1 | C1 | social |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname |
|
||||
| user1 | User | One |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| user1 | C1 | editingteacher |
|
||||
Given I log in as "user1"
|
||||
And I follow "Course 1"
|
||||
And I turn editing mode on
|
||||
And I set the field "Add an activity to section 'section 0'" to "Forum"
|
||||
And I set the field "Forum name" to "My forum name"
|
||||
And I press "Save and return to course"
|
||||
And I click on "Edit title" "link" in the "//.[contains(@class,'block_social_activities')]//li[contains(.,'My forum name')]" "xpath_element"
|
||||
And I set the field "New name for activity My forum name" to "New forum name"
|
||||
And I press key "13" in the field "New name for activity My forum name"
|
||||
Then I should not see "My forum name" in the "Social activities" "block"
|
||||
And I should see "New forum name"
|
||||
And I follow "New forum name"
|
||||
And I should not see "My forum name"
|
||||
And I should see "New forum name"
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Contains class core_tag\output\course_module_name
|
||||
*
|
||||
* @package core_course
|
||||
* @copyright 2016 Marina Glancy
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core_course\output;
|
||||
|
||||
use context_module;
|
||||
use lang_string;
|
||||
use cm_info;
|
||||
|
||||
/**
|
||||
* Class to prepare a course module name for display and in-place editing
|
||||
*
|
||||
* @package core_course
|
||||
* @copyright 2016 Marina Glancy
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class course_module_name extends \core\output\inplace_editable {
|
||||
|
||||
/** @var cm_info */
|
||||
protected $cm;
|
||||
|
||||
/** @var array */
|
||||
protected $displayoptions;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param cm_info $cm
|
||||
* @param bool $editable
|
||||
* @param array $displayoptions
|
||||
*/
|
||||
public function __construct(cm_info $cm, $editable, $displayoptions = array()) {
|
||||
$this->cm = $cm;
|
||||
$this->displayoptions = $displayoptions;
|
||||
$value = $cm->name;
|
||||
$edithint = new lang_string('edittitle');
|
||||
$editlabel = new lang_string('newactivityname', '', $cm->get_formatted_name());
|
||||
$editable = $editable && has_capability('moodle/course:manageactivities',
|
||||
context_module::instance($cm->id));
|
||||
parent::__construct(
|
||||
'core_course', 'activityname', $cm->id, $editable, $value, $value, $edithint, $editlabel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export this data so it can be used as the context for a mustache template (core/inplace_editable).
|
||||
*
|
||||
* @param renderer_base $output typically, the renderer that's calling this function
|
||||
* @return array data context for a mustache template
|
||||
*/
|
||||
public function export_for_template(\renderer_base $output) {
|
||||
global $PAGE;
|
||||
$courserenderer = $PAGE->get_renderer('core', 'course');
|
||||
$this->displayvalue = $courserenderer->course_section_cm_name_title($this->cm, $this->displayoptions);
|
||||
if (strval($this->displayvalue) === '') {
|
||||
$this->editable = false;
|
||||
}
|
||||
return parent::export_for_template($output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates course module name
|
||||
*
|
||||
* @param int $itemid course module id
|
||||
* @param string $newvalue new name
|
||||
* @return static
|
||||
*/
|
||||
public static function update($itemid, $newvalue) {
|
||||
list($course, $cm) = get_course_and_cm_from_cmid($itemid);
|
||||
$context = context_module::instance($cm->id);
|
||||
// Check access.
|
||||
require_login($course, false, $cm, true, true);
|
||||
require_capability('moodle/course:manageactivities', $context);
|
||||
// Update value.
|
||||
set_coursemodule_name($cm->id, $newvalue);
|
||||
// Return instance.
|
||||
$cm = get_fast_modinfo($course)->get_cm($cm->id);
|
||||
return new static($cm, true);
|
||||
}
|
||||
}
|
||||
+58
-47
@@ -1605,6 +1605,50 @@ function set_coursemodule_visible($id, $visible) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the course module name
|
||||
*
|
||||
* @param int $id course module id
|
||||
* @param string $name new value for a name
|
||||
* @return bool whether a change was made
|
||||
*/
|
||||
function set_coursemodule_name($id, $name) {
|
||||
global $CFG, $DB;
|
||||
require_once($CFG->libdir . '/gradelib.php');
|
||||
|
||||
$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($name, PARAM_TEXT);
|
||||
} else {
|
||||
$module->name = clean_param($name, PARAM_CLEANHTML);
|
||||
}
|
||||
if ($module->name === $cm->name || strval($module->name) === '') {
|
||||
return false;
|
||||
}
|
||||
if (\core_text::strlen($module->name) > 255) {
|
||||
throw new \moodle_exception('maximumchars', 'moodle', '', 255);
|
||||
}
|
||||
|
||||
$module->timemodified = time();
|
||||
$DB->update_record($cm->modname, $module);
|
||||
$cm->name = $module->name;
|
||||
\core\event\course_module_updated::create_from_cm($cm)->trigger();
|
||||
rebuild_course_cache($cm->course, true);
|
||||
|
||||
// Attempt to update the grade item if relevant.
|
||||
$grademodule = $DB->get_record($cm->modname, array('id' => $cm->instance));
|
||||
$grademodule->cmidnumber = $cm->idnumber;
|
||||
$grademodule->modname = $cm->modname;
|
||||
grade_update_mod_grades($grademodule);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will handle the whole deletion process of a module. This includes calling
|
||||
* the modules delete_instance function, deleting files, events, grades, conditional data,
|
||||
@@ -2241,53 +2285,6 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the rename action.
|
||||
*
|
||||
* @param cm_info $mod The module to produce editing buttons for
|
||||
* @param int $sr The section to link back to (used for creating the links)
|
||||
* @return The markup for the rename action, or an empty string if not available.
|
||||
*/
|
||||
function course_get_cm_rename_action(cm_info $mod, $sr = null) {
|
||||
global $COURSE, $OUTPUT;
|
||||
|
||||
static $str;
|
||||
static $baseurl;
|
||||
|
||||
$modcontext = context_module::instance($mod->id);
|
||||
$hasmanageactivities = has_capability('moodle/course:manageactivities', $modcontext);
|
||||
|
||||
if (!isset($str)) {
|
||||
$str = get_strings(array('edittitle'));
|
||||
}
|
||||
|
||||
if (!isset($baseurl)) {
|
||||
$baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey()));
|
||||
}
|
||||
|
||||
if ($sr !== null) {
|
||||
$baseurl->param('sr', $sr);
|
||||
}
|
||||
|
||||
// AJAX edit title.
|
||||
if ($mod->has_view() && $hasmanageactivities && course_ajax_enabled($COURSE) &&
|
||||
(($mod->course == $COURSE->id) || ($mod->course == SITEID))) {
|
||||
// we will not display link if we are on some other-course page (where we should not see this module anyway)
|
||||
return html_writer::span(
|
||||
html_writer::link(
|
||||
new moodle_url($baseurl, array('update' => $mod->id)),
|
||||
$OUTPUT->pix_icon('t/editstring', '', 'moodle', array('class' => 'iconsmall visibleifjs', 'title' => '')),
|
||||
array(
|
||||
'class' => 'editing_title',
|
||||
'data-action' => 'edittitle',
|
||||
'title' => $str->edittitle,
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the move action.
|
||||
*
|
||||
@@ -3897,3 +3894,17 @@ function course_get_tagged_courses($tag, $exclusivemode = false, $fromctx = 0, $
|
||||
return new core_tag\output\tagindex($tag, 'core', 'course', $content,
|
||||
$exclusivemode, $fromctx, $ctx, $rec, $page, $totalpages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements callback inplace_editable() allowing to edit values in-place
|
||||
*
|
||||
* @param string $itemtype
|
||||
* @param int $itemid
|
||||
* @param mixed $newvalue
|
||||
* @return \core\output\inplace_editable
|
||||
*/
|
||||
function core_course_inplace_editable($itemtype, $itemid, $newvalue) {
|
||||
if ($itemtype === 'activityname') {
|
||||
return \core_course\output\course_module_name::update($itemid, $newvalue);
|
||||
}
|
||||
}
|
||||
|
||||
+26
-6
@@ -733,10 +733,34 @@ class core_course_renderer extends plugin_renderer_base {
|
||||
* @return string
|
||||
*/
|
||||
public function course_section_cm_name(cm_info $mod, $displayoptions = array()) {
|
||||
global $CFG;
|
||||
if ((!$mod->uservisible && empty($mod->availableinfo)) || !$mod->url) {
|
||||
// Nothing to be displayed to the user.
|
||||
return '';
|
||||
}
|
||||
|
||||
// Render element that allows to edit activity name inline. It calls {@link course_section_cm_name_title()}
|
||||
// to get the display title of the activity.
|
||||
$tmpl = new \core_course\output\course_module_name($mod, $this->page->user_is_editing(), $displayoptions);
|
||||
return $this->output->render_from_template('core/inplace_editable', $tmpl->export_for_template($this->output));
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders html to display a name with the link to the course module on a course page
|
||||
*
|
||||
* If module is unavailable for user but still needs to be displayed
|
||||
* in the list, just the name is returned without a link
|
||||
*
|
||||
* Note, that for course modules that never have separate pages (i.e. labels)
|
||||
* this function return an empty string
|
||||
*
|
||||
* @param cm_info $mod
|
||||
* @param array $displayoptions
|
||||
* @return string
|
||||
*/
|
||||
public function course_section_cm_name_title(cm_info $mod, $displayoptions = array()) {
|
||||
$output = '';
|
||||
if (!$mod->uservisible && empty($mod->availableinfo)) {
|
||||
// nothing to be displayed to the user
|
||||
// Nothing to be displayed to the user.
|
||||
return $output;
|
||||
}
|
||||
$url = $mod->url;
|
||||
@@ -985,10 +1009,6 @@ class core_course_renderer extends plugin_renderer_base {
|
||||
$output .= $cmname;
|
||||
|
||||
|
||||
if ($this->page->user_is_editing()) {
|
||||
$output .= ' ' . course_get_cm_rename_action($mod, $sectionreturn);
|
||||
}
|
||||
|
||||
// Module can put text after the link (e.g. forum unread)
|
||||
$output .= $mod->afterlink;
|
||||
|
||||
|
||||
@@ -150,49 +150,6 @@ switch($requestmethod) {
|
||||
$isvisible = moveto_module($cm, $section, $beforemod);
|
||||
echo json_encode(array('visible' => (bool) $isvisible));
|
||||
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);
|
||||
require_once($CFG->libdir . '/gradelib.php');
|
||||
$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 (strval($module->name) !== '') {
|
||||
$DB->update_record($cm->modname, $module);
|
||||
$cm->name = $module->name;
|
||||
\core\event\course_module_updated::create_from_cm($cm, $modcontext)->trigger();
|
||||
rebuild_course_cache($cm->course);
|
||||
} else {
|
||||
$module->name = $cm->name;
|
||||
}
|
||||
|
||||
// Attempt to update the grade item if relevant
|
||||
$grademodule = $DB->get_record($cm->modname, array('id' => $cm->instance));
|
||||
$grademodule->cmidnumber = $cm->idnumber;
|
||||
$grademodule->modname = $cm->modname;
|
||||
grade_update_mod_grades($grademodule);
|
||||
|
||||
// We need to return strings after they've been through filters for multilang
|
||||
$stringoptions = new stdClass;
|
||||
$stringoptions->context = $coursecontext;
|
||||
echo json_encode(array('instancename' => html_entity_decode(format_string($module->name, true, $stringoptions))));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
@core @core_course
|
||||
Feature: Edit activity name in-place
|
||||
In order to quickly edit activity name
|
||||
As a teacher
|
||||
I need to use inplace editing
|
||||
|
||||
@javascript
|
||||
Scenario: Edit activity name in-place
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@example.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | format |
|
||||
| Course 1 | C1 | topics |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
When I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
And I turn editing mode on
|
||||
And I add a "Forum" to section "1" and I fill the form with:
|
||||
| Forum name | Test forum name |
|
||||
| Description | Test forum description |
|
||||
# Rename activity
|
||||
And I click on "Edit title" "link" in the "//div[contains(@class,'activityinstance') and contains(.,'Test forum name')]" "xpath_element"
|
||||
And I set the field "New name for activity Test forum name" to "Good news"
|
||||
And I press key "13" in the field "New name for activity Test forum name"
|
||||
Then I should not see "Test forum name" in the ".course-content" "css_element"
|
||||
And "New name for activity Test forum name" "field" should not exist
|
||||
And I should see "Good news"
|
||||
And I follow "Course 1"
|
||||
And I should see "Good news"
|
||||
And I should not see "Test forum name"
|
||||
# Cancel renaming
|
||||
And I click on "Edit title" "link" in the "//div[contains(@class,'activityinstance') and contains(.,'Good news')]" "xpath_element"
|
||||
And I set the field "New name for activity Good news" to "Terrible news"
|
||||
And I press key "27" in the field "New name for activity Good news"
|
||||
And "New name for activity Good news" "field" should not exist
|
||||
And I should see "Good news"
|
||||
And I should not see "Terrible news"
|
||||
And I follow "Course 1"
|
||||
And I should see "Good news"
|
||||
And I should not see "Terrible news"
|
||||
And I log out
|
||||
@@ -467,7 +467,7 @@ class behat_course extends behat_base {
|
||||
foreach ($activities as $activity) {
|
||||
// Dimmed.
|
||||
$this->find('xpath', "//div[contains(concat(' ', normalize-space(@class), ' '), ' activityinstance ')]" .
|
||||
"/a[contains(concat(' ', normalize-space(@class), ' '), ' dimmed ')]", $dimmedexception, $activity);
|
||||
"//a[contains(concat(' ', normalize-space(@class), ' '), ' dimmed ')]", $dimmedexception, $activity);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -2779,4 +2779,34 @@ class core_course_courselib_testcase extends advanced_testcase {
|
||||
$this->assertNull($DB->get_field('course_modules', 'availability',
|
||||
array('id' => $label->cmid)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test update_inplace_editable()
|
||||
*/
|
||||
public function test_update_module_name_inplace() {
|
||||
global $CFG, $DB, $PAGE;
|
||||
require_once($CFG->dirroot . '/lib/external/externallib.php');
|
||||
|
||||
$this->setUser($this->getDataGenerator()->create_user());
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$forum = self::getDataGenerator()->create_module('forum', array('course' => $course->id, 'name' => 'forum name'));
|
||||
|
||||
// Call service for core_course component without necessary permissions.
|
||||
try {
|
||||
core_external::update_inplace_editable('core_course', 'activityname', $forum->cmid, 'New forum name');
|
||||
$this->fail('Exception expected');
|
||||
} catch (moodle_exception $e) {
|
||||
$this->assertEquals('Course or activity not accessible. (Not enrolled)',
|
||||
$e->getMessage());
|
||||
}
|
||||
|
||||
// Change to admin user and make sure that cm name can be updated using web service update_inplace_editable().
|
||||
$this->setAdminUser();
|
||||
$res = core_external::update_inplace_editable('core_course', 'activityname', $forum->cmid, 'New forum name');
|
||||
$res = external_api::clean_returnvalue(core_external::update_inplace_editable_returns(), $res);
|
||||
$this->assertEquals('New forum name', $res['value']);
|
||||
$this->assertEquals('New forum name', $DB->get_field('forum', 'name', array('id' => $forum->id)));
|
||||
}
|
||||
}
|
||||
|
||||
+7
-189
@@ -19,31 +19,28 @@ var CSS = {
|
||||
DIMCLASS : 'dimmed',
|
||||
DIMMEDTEXT : 'dimmed_text',
|
||||
EDITINSTRUCTIONS : 'editinstructions',
|
||||
EDITINGTITLE: 'editor_displayed',
|
||||
HIDE : 'hide',
|
||||
MODINDENTCOUNT : 'mod-indent-',
|
||||
MODINDENTHUGE : 'mod-indent-huge',
|
||||
MODULEIDPREFIX : 'module-',
|
||||
SECTIONHIDDENCLASS : 'hidden',
|
||||
SECTIONIDPREFIX : 'section-',
|
||||
SHOW : 'editing_show',
|
||||
TITLEEDITOR : 'titleeditor'
|
||||
SHOW : 'editing_show'
|
||||
},
|
||||
// The CSS selectors we use.
|
||||
SELECTOR = {
|
||||
ACTIONAREA: '.actions',
|
||||
ACTIONLINKTEXT : '.actionlinktext',
|
||||
ACTIVITYACTION : 'a.cm-edit-action[data-action], a.editing_title',
|
||||
ACTIVITYFORM : '.' + CSS.ACTIVITYINSTANCE + ' form',
|
||||
ACTIVITYACTION : 'a.cm-edit-action[data-action]',
|
||||
ACTIVITYICON : 'img.activityicon',
|
||||
ACTIVITYINSTANCE : '.' + CSS.ACTIVITYINSTANCE,
|
||||
ACTIVITYLINK: '.' + CSS.ACTIVITYINSTANCE + ' > a',
|
||||
ACTIVITYLINK: '.' + CSS.ACTIVITYINSTANCE + ' > a, .'+ CSS.ACTIVITYINSTANCE +
|
||||
' > span[data-inplaceeditable] > a:not([data-inplaceeditablelink])',
|
||||
ACTIVITYLI : 'li.activity',
|
||||
ACTIVITYTITLE : 'input[name=title]',
|
||||
COMMANDSPAN : '.commands',
|
||||
CONTENTAFTERLINK : 'div.contentafterlink',
|
||||
CONTENTWITHOUTLINK : 'div.contentwithoutlink',
|
||||
EDITTITLE: 'a.editing_title',
|
||||
GROUPINGLABEL: '.' + CSS.ACTIVITYINSTANCE + ' .groupinglabel',
|
||||
HIDE : 'a.editing_hide',
|
||||
HIGHLIGHT : 'a.editing_highlight',
|
||||
INSTANCENAME : 'span.instancename',
|
||||
@@ -256,17 +253,6 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
|
||||
*/
|
||||
GROUPS_VISIBLE: 2,
|
||||
|
||||
/**
|
||||
* An Array of events added when editing a title.
|
||||
* These should all be detached when editing is complete.
|
||||
*
|
||||
* @property edittitleevents
|
||||
* @protected
|
||||
* @type Array
|
||||
* @protected
|
||||
*/
|
||||
edittitleevents: [],
|
||||
|
||||
/**
|
||||
* Initialize the resource toolbox
|
||||
*
|
||||
@@ -317,10 +303,6 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
|
||||
|
||||
// Switch based upon the action and do the desired thing.
|
||||
switch (action) {
|
||||
case 'edittitle':
|
||||
// The user wishes to edit the title of the event.
|
||||
this.edit_title(ev, node, activity, action);
|
||||
break;
|
||||
case 'moveleft':
|
||||
case 'moveright':
|
||||
// The user changing the indent of the activity.
|
||||
@@ -633,11 +615,13 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
|
||||
dimarea.addClass(toggleclass);
|
||||
// We need to toggle dimming on the description too.
|
||||
activity.all(SELECTOR.CONTENTAFTERLINK).addClass(CSS.DIMMEDTEXT);
|
||||
activity.all(SELECTOR.GROUPINGLABEL).addClass(CSS.DIMMEDTEXT);
|
||||
} else {
|
||||
// Change the UI.
|
||||
dimarea.removeClass(toggleclass);
|
||||
// We need to toggle dimming on the description too.
|
||||
activity.all(SELECTOR.CONTENTAFTERLINK).removeClass(CSS.DIMMEDTEXT);
|
||||
activity.all(SELECTOR.GROUPINGLABEL).removeClass(CSS.DIMMEDTEXT);
|
||||
}
|
||||
}
|
||||
// Toggle availablity info for conditional activities.
|
||||
@@ -714,172 +698,6 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Edit the title for the resource
|
||||
*
|
||||
* @method edit_title
|
||||
* @protected
|
||||
* @param {EventFacade} ev The event that was fired.
|
||||
* @param {Node} button The button that triggered this action.
|
||||
* @param {Node} activity The activity node that this action will be performed on.
|
||||
* @param {String} action The action that has been requested.
|
||||
* @chainable
|
||||
*/
|
||||
edit_title: function(ev, button, activity) {
|
||||
// Get the element we're working on
|
||||
var activityid = Y.Moodle.core_course.util.cm.getId(activity),
|
||||
instancename = activity.one(SELECTOR.INSTANCENAME),
|
||||
instance = activity.one(SELECTOR.ACTIVITYINSTANCE),
|
||||
currenttitle = instancename.get('firstChild'),
|
||||
oldtitle = currenttitle.get('data'),
|
||||
titletext = oldtitle,
|
||||
thisevent,
|
||||
anchor = instancename.ancestor('a'),// Grab the anchor so that we can swap it with the edit form.
|
||||
data = {
|
||||
'class': 'resource',
|
||||
'field': 'gettitle',
|
||||
'id': activityid
|
||||
};
|
||||
|
||||
// Prevent the default actions.
|
||||
ev.preventDefault();
|
||||
|
||||
this.send_request(data, null, function(response) {
|
||||
if (M.core.actionmenu && M.core.actionmenu.instance) {
|
||||
M.core.actionmenu.instance.hideMenu(ev);
|
||||
}
|
||||
|
||||
// Try to retrieve the existing string from the server
|
||||
if (response.instancename) {
|
||||
titletext = response.instancename;
|
||||
}
|
||||
|
||||
// Create the editor and submit button
|
||||
var editform = Y.Node.create('<form action="#" />');
|
||||
var editinstructions = Y.Node.create('<span class="'+CSS.EDITINSTRUCTIONS+'" id="id_editinstructions" />')
|
||||
.set('innerHTML', M.util.get_string('edittitleinstructions', 'moodle'));
|
||||
var editor = Y.Node.create('<input name="title" type="text" class="'+CSS.TITLEEDITOR+'" />').setAttrs({
|
||||
'value': titletext,
|
||||
'autocomplete': 'off',
|
||||
'aria-describedby': 'id_editinstructions',
|
||||
'maxLength': '255'
|
||||
});
|
||||
|
||||
// Clear the existing content and put the editor in
|
||||
editform.appendChild(activity.one(SELECTOR.ACTIVITYICON).cloneNode());
|
||||
editform.appendChild(editor);
|
||||
editform.setData('anchor', anchor);
|
||||
instance.insert(editinstructions, 'before');
|
||||
anchor.replace(editform);
|
||||
|
||||
// Force the editing instruction to match the mod-indent position.
|
||||
var padside = 'left';
|
||||
if (window.right_to_left()) {
|
||||
padside = 'right';
|
||||
}
|
||||
|
||||
// We hide various components whilst editing:
|
||||
activity.addClass(CSS.EDITINGTITLE);
|
||||
|
||||
// Focus and select the editor text
|
||||
editor.focus().select();
|
||||
|
||||
// Cancel the edit if we lose focus or the escape key is pressed.
|
||||
thisevent = editor.on('blur', this.edit_title_cancel, this, activity, false);
|
||||
this.edittitleevents.push(thisevent);
|
||||
thisevent = editor.on('key', this.edit_title_cancel, 'esc', this, activity, true);
|
||||
this.edittitleevents.push(thisevent);
|
||||
|
||||
// Handle form submission.
|
||||
thisevent = editform.on('submit', this.edit_title_submit, this, activity, oldtitle);
|
||||
this.edittitleevents.push(thisevent);
|
||||
});
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the submit event when editing the activity or resources title.
|
||||
*
|
||||
* @method edit_title_submit
|
||||
* @protected
|
||||
* @param {EventFacade} ev The event that triggered this.
|
||||
* @param {Node} activity The activity whose title we are altering.
|
||||
* @param {String} originaltitle The original title the activity or resource had.
|
||||
*/
|
||||
edit_title_submit: function(ev, activity, originaltitle) {
|
||||
// We don't actually want to submit anything
|
||||
ev.preventDefault();
|
||||
|
||||
var newtitle = Y.Lang.trim(activity.one(SELECTOR.ACTIVITYFORM + ' ' + SELECTOR.ACTIVITYTITLE).get('value'));
|
||||
this.edit_title_clear(activity);
|
||||
var spinner = this.add_spinner(activity);
|
||||
if (newtitle !== null && newtitle !== "" && newtitle !== originaltitle) {
|
||||
var data = {
|
||||
'class': 'resource',
|
||||
'field': 'updatetitle',
|
||||
'title': newtitle,
|
||||
'id': Y.Moodle.core_course.util.cm.getId(activity)
|
||||
};
|
||||
this.send_request(data, spinner, function(response) {
|
||||
if (response.instancename) {
|
||||
activity.one(SELECTOR.INSTANCENAME).setContent(response.instancename);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the cancel event when editing the activity or resources title.
|
||||
*
|
||||
* @method edit_title_cancel
|
||||
* @protected
|
||||
* @param {EventFacade} ev The event that triggered this.
|
||||
* @param {Node} activity The activity whose title we are altering.
|
||||
* @param {Boolean} preventdefault If true we should prevent the default action from occuring.
|
||||
*/
|
||||
edit_title_cancel: function(ev, activity, preventdefault) {
|
||||
if (preventdefault) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
this.edit_title_clear(activity);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles clearing the editing UI and returning things to the original state they were in.
|
||||
*
|
||||
* @method edit_title_clear
|
||||
* @protected
|
||||
* @param {Node} activity The activity whose title we were altering.
|
||||
*/
|
||||
edit_title_clear: function(activity) {
|
||||
// Detach all listen events to prevent duplicate triggers
|
||||
new Y.EventHandle(this.edittitleevents).detach();
|
||||
|
||||
var editform = activity.one(SELECTOR.ACTIVITYFORM),
|
||||
instructions = activity.one('#id_editinstructions');
|
||||
if (editform) {
|
||||
editform.replace(editform.getData('anchor'));
|
||||
}
|
||||
if (instructions) {
|
||||
instructions.remove();
|
||||
}
|
||||
|
||||
// Remove the editing class again to revert the display.
|
||||
activity.removeClass(CSS.EDITINGTITLE);
|
||||
|
||||
// Refocus the link which was clicked originally so the user can continue using keyboard nav.
|
||||
Y.later(100, this, function() {
|
||||
activity.one(SELECTOR.EDITTITLE).focus();
|
||||
});
|
||||
|
||||
// TODO MDL-50768 This hack is to keep Behat happy until they release a version of
|
||||
// MinkSelenium2Driver that fixes
|
||||
// https://github.com/Behat/MinkSelenium2Driver/issues/80.
|
||||
if (!Y.one('input[name=title]')) {
|
||||
Y.one('body').append('<input type="text" name="title" style="display: none">');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the visibility of the specified resource to match the visible parameter.
|
||||
*
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -19,31 +19,28 @@ var CSS = {
|
||||
DIMCLASS : 'dimmed',
|
||||
DIMMEDTEXT : 'dimmed_text',
|
||||
EDITINSTRUCTIONS : 'editinstructions',
|
||||
EDITINGTITLE: 'editor_displayed',
|
||||
HIDE : 'hide',
|
||||
MODINDENTCOUNT : 'mod-indent-',
|
||||
MODINDENTHUGE : 'mod-indent-huge',
|
||||
MODULEIDPREFIX : 'module-',
|
||||
SECTIONHIDDENCLASS : 'hidden',
|
||||
SECTIONIDPREFIX : 'section-',
|
||||
SHOW : 'editing_show',
|
||||
TITLEEDITOR : 'titleeditor'
|
||||
SHOW : 'editing_show'
|
||||
},
|
||||
// The CSS selectors we use.
|
||||
SELECTOR = {
|
||||
ACTIONAREA: '.actions',
|
||||
ACTIONLINKTEXT : '.actionlinktext',
|
||||
ACTIVITYACTION : 'a.cm-edit-action[data-action], a.editing_title',
|
||||
ACTIVITYFORM : '.' + CSS.ACTIVITYINSTANCE + ' form',
|
||||
ACTIVITYACTION : 'a.cm-edit-action[data-action]',
|
||||
ACTIVITYICON : 'img.activityicon',
|
||||
ACTIVITYINSTANCE : '.' + CSS.ACTIVITYINSTANCE,
|
||||
ACTIVITYLINK: '.' + CSS.ACTIVITYINSTANCE + ' > a',
|
||||
ACTIVITYLINK: '.' + CSS.ACTIVITYINSTANCE + ' > a, .'+ CSS.ACTIVITYINSTANCE +
|
||||
' > span[data-inplaceeditable] > a:not([data-inplaceeditablelink])',
|
||||
ACTIVITYLI : 'li.activity',
|
||||
ACTIVITYTITLE : 'input[name=title]',
|
||||
COMMANDSPAN : '.commands',
|
||||
CONTENTAFTERLINK : 'div.contentafterlink',
|
||||
CONTENTWITHOUTLINK : 'div.contentwithoutlink',
|
||||
EDITTITLE: 'a.editing_title',
|
||||
GROUPINGLABEL: '.' + CSS.ACTIVITYINSTANCE + ' .groupinglabel',
|
||||
HIDE : 'a.editing_hide',
|
||||
HIGHLIGHT : 'a.editing_highlight',
|
||||
INSTANCENAME : 'span.instancename',
|
||||
@@ -256,17 +253,6 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
|
||||
*/
|
||||
GROUPS_VISIBLE: 2,
|
||||
|
||||
/**
|
||||
* An Array of events added when editing a title.
|
||||
* These should all be detached when editing is complete.
|
||||
*
|
||||
* @property edittitleevents
|
||||
* @protected
|
||||
* @type Array
|
||||
* @protected
|
||||
*/
|
||||
edittitleevents: [],
|
||||
|
||||
/**
|
||||
* Initialize the resource toolbox
|
||||
*
|
||||
@@ -317,10 +303,6 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
|
||||
|
||||
// Switch based upon the action and do the desired thing.
|
||||
switch (action) {
|
||||
case 'edittitle':
|
||||
// The user wishes to edit the title of the event.
|
||||
this.edit_title(ev, node, activity, action);
|
||||
break;
|
||||
case 'moveleft':
|
||||
case 'moveright':
|
||||
// The user changing the indent of the activity.
|
||||
@@ -633,11 +615,13 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
|
||||
dimarea.addClass(toggleclass);
|
||||
// We need to toggle dimming on the description too.
|
||||
activity.all(SELECTOR.CONTENTAFTERLINK).addClass(CSS.DIMMEDTEXT);
|
||||
activity.all(SELECTOR.GROUPINGLABEL).addClass(CSS.DIMMEDTEXT);
|
||||
} else {
|
||||
// Change the UI.
|
||||
dimarea.removeClass(toggleclass);
|
||||
// We need to toggle dimming on the description too.
|
||||
activity.all(SELECTOR.CONTENTAFTERLINK).removeClass(CSS.DIMMEDTEXT);
|
||||
activity.all(SELECTOR.GROUPINGLABEL).removeClass(CSS.DIMMEDTEXT);
|
||||
}
|
||||
}
|
||||
// Toggle availablity info for conditional activities.
|
||||
@@ -714,172 +698,6 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Edit the title for the resource
|
||||
*
|
||||
* @method edit_title
|
||||
* @protected
|
||||
* @param {EventFacade} ev The event that was fired.
|
||||
* @param {Node} button The button that triggered this action.
|
||||
* @param {Node} activity The activity node that this action will be performed on.
|
||||
* @param {String} action The action that has been requested.
|
||||
* @chainable
|
||||
*/
|
||||
edit_title: function(ev, button, activity) {
|
||||
// Get the element we're working on
|
||||
var activityid = Y.Moodle.core_course.util.cm.getId(activity),
|
||||
instancename = activity.one(SELECTOR.INSTANCENAME),
|
||||
instance = activity.one(SELECTOR.ACTIVITYINSTANCE),
|
||||
currenttitle = instancename.get('firstChild'),
|
||||
oldtitle = currenttitle.get('data'),
|
||||
titletext = oldtitle,
|
||||
thisevent,
|
||||
anchor = instancename.ancestor('a'),// Grab the anchor so that we can swap it with the edit form.
|
||||
data = {
|
||||
'class': 'resource',
|
||||
'field': 'gettitle',
|
||||
'id': activityid
|
||||
};
|
||||
|
||||
// Prevent the default actions.
|
||||
ev.preventDefault();
|
||||
|
||||
this.send_request(data, null, function(response) {
|
||||
if (M.core.actionmenu && M.core.actionmenu.instance) {
|
||||
M.core.actionmenu.instance.hideMenu(ev);
|
||||
}
|
||||
|
||||
// Try to retrieve the existing string from the server
|
||||
if (response.instancename) {
|
||||
titletext = response.instancename;
|
||||
}
|
||||
|
||||
// Create the editor and submit button
|
||||
var editform = Y.Node.create('<form action="#" />');
|
||||
var editinstructions = Y.Node.create('<span class="'+CSS.EDITINSTRUCTIONS+'" id="id_editinstructions" />')
|
||||
.set('innerHTML', M.util.get_string('edittitleinstructions', 'moodle'));
|
||||
var editor = Y.Node.create('<input name="title" type="text" class="'+CSS.TITLEEDITOR+'" />').setAttrs({
|
||||
'value': titletext,
|
||||
'autocomplete': 'off',
|
||||
'aria-describedby': 'id_editinstructions',
|
||||
'maxLength': '255'
|
||||
});
|
||||
|
||||
// Clear the existing content and put the editor in
|
||||
editform.appendChild(activity.one(SELECTOR.ACTIVITYICON).cloneNode());
|
||||
editform.appendChild(editor);
|
||||
editform.setData('anchor', anchor);
|
||||
instance.insert(editinstructions, 'before');
|
||||
anchor.replace(editform);
|
||||
|
||||
// Force the editing instruction to match the mod-indent position.
|
||||
var padside = 'left';
|
||||
if (window.right_to_left()) {
|
||||
padside = 'right';
|
||||
}
|
||||
|
||||
// We hide various components whilst editing:
|
||||
activity.addClass(CSS.EDITINGTITLE);
|
||||
|
||||
// Focus and select the editor text
|
||||
editor.focus().select();
|
||||
|
||||
// Cancel the edit if we lose focus or the escape key is pressed.
|
||||
thisevent = editor.on('blur', this.edit_title_cancel, this, activity, false);
|
||||
this.edittitleevents.push(thisevent);
|
||||
thisevent = editor.on('key', this.edit_title_cancel, 'esc', this, activity, true);
|
||||
this.edittitleevents.push(thisevent);
|
||||
|
||||
// Handle form submission.
|
||||
thisevent = editform.on('submit', this.edit_title_submit, this, activity, oldtitle);
|
||||
this.edittitleevents.push(thisevent);
|
||||
});
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the submit event when editing the activity or resources title.
|
||||
*
|
||||
* @method edit_title_submit
|
||||
* @protected
|
||||
* @param {EventFacade} ev The event that triggered this.
|
||||
* @param {Node} activity The activity whose title we are altering.
|
||||
* @param {String} originaltitle The original title the activity or resource had.
|
||||
*/
|
||||
edit_title_submit: function(ev, activity, originaltitle) {
|
||||
// We don't actually want to submit anything
|
||||
ev.preventDefault();
|
||||
|
||||
var newtitle = Y.Lang.trim(activity.one(SELECTOR.ACTIVITYFORM + ' ' + SELECTOR.ACTIVITYTITLE).get('value'));
|
||||
this.edit_title_clear(activity);
|
||||
var spinner = this.add_spinner(activity);
|
||||
if (newtitle !== null && newtitle !== "" && newtitle !== originaltitle) {
|
||||
var data = {
|
||||
'class': 'resource',
|
||||
'field': 'updatetitle',
|
||||
'title': newtitle,
|
||||
'id': Y.Moodle.core_course.util.cm.getId(activity)
|
||||
};
|
||||
this.send_request(data, spinner, function(response) {
|
||||
if (response.instancename) {
|
||||
activity.one(SELECTOR.INSTANCENAME).setContent(response.instancename);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the cancel event when editing the activity or resources title.
|
||||
*
|
||||
* @method edit_title_cancel
|
||||
* @protected
|
||||
* @param {EventFacade} ev The event that triggered this.
|
||||
* @param {Node} activity The activity whose title we are altering.
|
||||
* @param {Boolean} preventdefault If true we should prevent the default action from occuring.
|
||||
*/
|
||||
edit_title_cancel: function(ev, activity, preventdefault) {
|
||||
if (preventdefault) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
this.edit_title_clear(activity);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles clearing the editing UI and returning things to the original state they were in.
|
||||
*
|
||||
* @method edit_title_clear
|
||||
* @protected
|
||||
* @param {Node} activity The activity whose title we were altering.
|
||||
*/
|
||||
edit_title_clear: function(activity) {
|
||||
// Detach all listen events to prevent duplicate triggers
|
||||
new Y.EventHandle(this.edittitleevents).detach();
|
||||
|
||||
var editform = activity.one(SELECTOR.ACTIVITYFORM),
|
||||
instructions = activity.one('#id_editinstructions');
|
||||
if (editform) {
|
||||
editform.replace(editform.getData('anchor'));
|
||||
}
|
||||
if (instructions) {
|
||||
instructions.remove();
|
||||
}
|
||||
|
||||
// Remove the editing class again to revert the display.
|
||||
activity.removeClass(CSS.EDITINGTITLE);
|
||||
|
||||
// Refocus the link which was clicked originally so the user can continue using keyboard nav.
|
||||
Y.later(100, this, function() {
|
||||
activity.one(SELECTOR.EDITTITLE).focus();
|
||||
});
|
||||
|
||||
// TODO MDL-50768 This hack is to keep Behat happy until they release a version of
|
||||
// MinkSelenium2Driver that fixes
|
||||
// https://github.com/Behat/MinkSelenium2Driver/issues/80.
|
||||
if (!Y.one('input[name=title]')) {
|
||||
Y.one('body').append('<input type="text" name="title" style="display: none">');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the visibility of the specified resource to match the visible parameter.
|
||||
*
|
||||
|
||||
+2
-181
@@ -52,17 +52,6 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
|
||||
*/
|
||||
GROUPS_VISIBLE: 2,
|
||||
|
||||
/**
|
||||
* An Array of events added when editing a title.
|
||||
* These should all be detached when editing is complete.
|
||||
*
|
||||
* @property edittitleevents
|
||||
* @protected
|
||||
* @type Array
|
||||
* @protected
|
||||
*/
|
||||
edittitleevents: [],
|
||||
|
||||
/**
|
||||
* Initialize the resource toolbox
|
||||
*
|
||||
@@ -113,10 +102,6 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
|
||||
|
||||
// Switch based upon the action and do the desired thing.
|
||||
switch (action) {
|
||||
case 'edittitle':
|
||||
// The user wishes to edit the title of the event.
|
||||
this.edit_title(ev, node, activity, action);
|
||||
break;
|
||||
case 'moveleft':
|
||||
case 'moveright':
|
||||
// The user changing the indent of the activity.
|
||||
@@ -429,11 +414,13 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
|
||||
dimarea.addClass(toggleclass);
|
||||
// We need to toggle dimming on the description too.
|
||||
activity.all(SELECTOR.CONTENTAFTERLINK).addClass(CSS.DIMMEDTEXT);
|
||||
activity.all(SELECTOR.GROUPINGLABEL).addClass(CSS.DIMMEDTEXT);
|
||||
} else {
|
||||
// Change the UI.
|
||||
dimarea.removeClass(toggleclass);
|
||||
// We need to toggle dimming on the description too.
|
||||
activity.all(SELECTOR.CONTENTAFTERLINK).removeClass(CSS.DIMMEDTEXT);
|
||||
activity.all(SELECTOR.GROUPINGLABEL).removeClass(CSS.DIMMEDTEXT);
|
||||
}
|
||||
}
|
||||
// Toggle availablity info for conditional activities.
|
||||
@@ -510,172 +497,6 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Edit the title for the resource
|
||||
*
|
||||
* @method edit_title
|
||||
* @protected
|
||||
* @param {EventFacade} ev The event that was fired.
|
||||
* @param {Node} button The button that triggered this action.
|
||||
* @param {Node} activity The activity node that this action will be performed on.
|
||||
* @param {String} action The action that has been requested.
|
||||
* @chainable
|
||||
*/
|
||||
edit_title: function(ev, button, activity) {
|
||||
// Get the element we're working on
|
||||
var activityid = Y.Moodle.core_course.util.cm.getId(activity),
|
||||
instancename = activity.one(SELECTOR.INSTANCENAME),
|
||||
instance = activity.one(SELECTOR.ACTIVITYINSTANCE),
|
||||
currenttitle = instancename.get('firstChild'),
|
||||
oldtitle = currenttitle.get('data'),
|
||||
titletext = oldtitle,
|
||||
thisevent,
|
||||
anchor = instancename.ancestor('a'),// Grab the anchor so that we can swap it with the edit form.
|
||||
data = {
|
||||
'class': 'resource',
|
||||
'field': 'gettitle',
|
||||
'id': activityid
|
||||
};
|
||||
|
||||
// Prevent the default actions.
|
||||
ev.preventDefault();
|
||||
|
||||
this.send_request(data, null, function(response) {
|
||||
if (M.core.actionmenu && M.core.actionmenu.instance) {
|
||||
M.core.actionmenu.instance.hideMenu(ev);
|
||||
}
|
||||
|
||||
// Try to retrieve the existing string from the server
|
||||
if (response.instancename) {
|
||||
titletext = response.instancename;
|
||||
}
|
||||
|
||||
// Create the editor and submit button
|
||||
var editform = Y.Node.create('<form action="#" />');
|
||||
var editinstructions = Y.Node.create('<span class="'+CSS.EDITINSTRUCTIONS+'" id="id_editinstructions" />')
|
||||
.set('innerHTML', M.util.get_string('edittitleinstructions', 'moodle'));
|
||||
var editor = Y.Node.create('<input name="title" type="text" class="'+CSS.TITLEEDITOR+'" />').setAttrs({
|
||||
'value': titletext,
|
||||
'autocomplete': 'off',
|
||||
'aria-describedby': 'id_editinstructions',
|
||||
'maxLength': '255'
|
||||
});
|
||||
|
||||
// Clear the existing content and put the editor in
|
||||
editform.appendChild(activity.one(SELECTOR.ACTIVITYICON).cloneNode());
|
||||
editform.appendChild(editor);
|
||||
editform.setData('anchor', anchor);
|
||||
instance.insert(editinstructions, 'before');
|
||||
anchor.replace(editform);
|
||||
|
||||
// Force the editing instruction to match the mod-indent position.
|
||||
var padside = 'left';
|
||||
if (window.right_to_left()) {
|
||||
padside = 'right';
|
||||
}
|
||||
|
||||
// We hide various components whilst editing:
|
||||
activity.addClass(CSS.EDITINGTITLE);
|
||||
|
||||
// Focus and select the editor text
|
||||
editor.focus().select();
|
||||
|
||||
// Cancel the edit if we lose focus or the escape key is pressed.
|
||||
thisevent = editor.on('blur', this.edit_title_cancel, this, activity, false);
|
||||
this.edittitleevents.push(thisevent);
|
||||
thisevent = editor.on('key', this.edit_title_cancel, 'esc', this, activity, true);
|
||||
this.edittitleevents.push(thisevent);
|
||||
|
||||
// Handle form submission.
|
||||
thisevent = editform.on('submit', this.edit_title_submit, this, activity, oldtitle);
|
||||
this.edittitleevents.push(thisevent);
|
||||
});
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the submit event when editing the activity or resources title.
|
||||
*
|
||||
* @method edit_title_submit
|
||||
* @protected
|
||||
* @param {EventFacade} ev The event that triggered this.
|
||||
* @param {Node} activity The activity whose title we are altering.
|
||||
* @param {String} originaltitle The original title the activity or resource had.
|
||||
*/
|
||||
edit_title_submit: function(ev, activity, originaltitle) {
|
||||
// We don't actually want to submit anything
|
||||
ev.preventDefault();
|
||||
|
||||
var newtitle = Y.Lang.trim(activity.one(SELECTOR.ACTIVITYFORM + ' ' + SELECTOR.ACTIVITYTITLE).get('value'));
|
||||
this.edit_title_clear(activity);
|
||||
var spinner = this.add_spinner(activity);
|
||||
if (newtitle !== null && newtitle !== "" && newtitle !== originaltitle) {
|
||||
var data = {
|
||||
'class': 'resource',
|
||||
'field': 'updatetitle',
|
||||
'title': newtitle,
|
||||
'id': Y.Moodle.core_course.util.cm.getId(activity)
|
||||
};
|
||||
this.send_request(data, spinner, function(response) {
|
||||
if (response.instancename) {
|
||||
activity.one(SELECTOR.INSTANCENAME).setContent(response.instancename);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles the cancel event when editing the activity or resources title.
|
||||
*
|
||||
* @method edit_title_cancel
|
||||
* @protected
|
||||
* @param {EventFacade} ev The event that triggered this.
|
||||
* @param {Node} activity The activity whose title we are altering.
|
||||
* @param {Boolean} preventdefault If true we should prevent the default action from occuring.
|
||||
*/
|
||||
edit_title_cancel: function(ev, activity, preventdefault) {
|
||||
if (preventdefault) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
this.edit_title_clear(activity);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles clearing the editing UI and returning things to the original state they were in.
|
||||
*
|
||||
* @method edit_title_clear
|
||||
* @protected
|
||||
* @param {Node} activity The activity whose title we were altering.
|
||||
*/
|
||||
edit_title_clear: function(activity) {
|
||||
// Detach all listen events to prevent duplicate triggers
|
||||
new Y.EventHandle(this.edittitleevents).detach();
|
||||
|
||||
var editform = activity.one(SELECTOR.ACTIVITYFORM),
|
||||
instructions = activity.one('#id_editinstructions');
|
||||
if (editform) {
|
||||
editform.replace(editform.getData('anchor'));
|
||||
}
|
||||
if (instructions) {
|
||||
instructions.remove();
|
||||
}
|
||||
|
||||
// Remove the editing class again to revert the display.
|
||||
activity.removeClass(CSS.EDITINGTITLE);
|
||||
|
||||
// Refocus the link which was clicked originally so the user can continue using keyboard nav.
|
||||
Y.later(100, this, function() {
|
||||
activity.one(SELECTOR.EDITTITLE).focus();
|
||||
});
|
||||
|
||||
// TODO MDL-50768 This hack is to keep Behat happy until they release a version of
|
||||
// MinkSelenium2Driver that fixes
|
||||
// https://github.com/Behat/MinkSelenium2Driver/issues/80.
|
||||
if (!Y.one('input[name=title]')) {
|
||||
Y.one('body').append('<input type="text" name="title" style="display: none">');
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the visibility of the specified resource to match the visible parameter.
|
||||
*
|
||||
|
||||
+5
-8
@@ -17,31 +17,28 @@ var CSS = {
|
||||
DIMCLASS : 'dimmed',
|
||||
DIMMEDTEXT : 'dimmed_text',
|
||||
EDITINSTRUCTIONS : 'editinstructions',
|
||||
EDITINGTITLE: 'editor_displayed',
|
||||
HIDE : 'hide',
|
||||
MODINDENTCOUNT : 'mod-indent-',
|
||||
MODINDENTHUGE : 'mod-indent-huge',
|
||||
MODULEIDPREFIX : 'module-',
|
||||
SECTIONHIDDENCLASS : 'hidden',
|
||||
SECTIONIDPREFIX : 'section-',
|
||||
SHOW : 'editing_show',
|
||||
TITLEEDITOR : 'titleeditor'
|
||||
SHOW : 'editing_show'
|
||||
},
|
||||
// The CSS selectors we use.
|
||||
SELECTOR = {
|
||||
ACTIONAREA: '.actions',
|
||||
ACTIONLINKTEXT : '.actionlinktext',
|
||||
ACTIVITYACTION : 'a.cm-edit-action[data-action], a.editing_title',
|
||||
ACTIVITYFORM : '.' + CSS.ACTIVITYINSTANCE + ' form',
|
||||
ACTIVITYACTION : 'a.cm-edit-action[data-action]',
|
||||
ACTIVITYICON : 'img.activityicon',
|
||||
ACTIVITYINSTANCE : '.' + CSS.ACTIVITYINSTANCE,
|
||||
ACTIVITYLINK: '.' + CSS.ACTIVITYINSTANCE + ' > a',
|
||||
ACTIVITYLINK: '.' + CSS.ACTIVITYINSTANCE + ' > a, .'+ CSS.ACTIVITYINSTANCE +
|
||||
' > span[data-inplaceeditable] > a:not([data-inplaceeditablelink])',
|
||||
ACTIVITYLI : 'li.activity',
|
||||
ACTIVITYTITLE : 'input[name=title]',
|
||||
COMMANDSPAN : '.commands',
|
||||
CONTENTAFTERLINK : 'div.contentafterlink',
|
||||
CONTENTWITHOUTLINK : 'div.contentwithoutlink',
|
||||
EDITTITLE: 'a.editing_title',
|
||||
GROUPINGLABEL: '.' + CSS.ACTIVITYINSTANCE + ' .groupinglabel',
|
||||
HIDE : 'a.editing_hide',
|
||||
HIGHLIGHT : 'a.editing_highlight',
|
||||
INSTANCENAME : 'span.instancename',
|
||||
|
||||
@@ -1228,6 +1228,7 @@ $string['never'] = 'Never';
|
||||
$string['neverdeletelogs'] = 'Never delete logs';
|
||||
$string['new'] = 'New';
|
||||
$string['newaccount'] = 'New account';
|
||||
$string['newactivityname'] = 'New name for activity {$a}';
|
||||
$string['newcourse'] = 'New course';
|
||||
$string['newpassword'] = 'New password';
|
||||
$string['newpassword_help'] = 'Enter a new password or leave blank to keep current password.';
|
||||
|
||||
@@ -4408,3 +4408,54 @@ function get_clam_error_code($returncode) {
|
||||
$antivirus = \core\antivirus\manager::get_antivirus('clamav');
|
||||
return $antivirus->get_clam_error_code($returncode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the rename action.
|
||||
*
|
||||
* @deprecated since 3.1
|
||||
* @param cm_info $mod The module to produce editing buttons for
|
||||
* @param int $sr The section to link back to (used for creating the links)
|
||||
* @return The markup for the rename action, or an empty string if not available.
|
||||
*/
|
||||
function course_get_cm_rename_action(cm_info $mod, $sr = null) {
|
||||
global $COURSE, $OUTPUT;
|
||||
|
||||
static $str;
|
||||
static $baseurl;
|
||||
|
||||
debugging('Function course_get_cm_rename_action() is deprecated. Please use inplace_editable ' .
|
||||
'https://docs.moodle.org/dev/Inplace_editable', DEBUG_DEVELOPER);
|
||||
|
||||
$modcontext = context_module::instance($mod->id);
|
||||
$hasmanageactivities = has_capability('moodle/course:manageactivities', $modcontext);
|
||||
|
||||
if (!isset($str)) {
|
||||
$str = get_strings(array('edittitle'));
|
||||
}
|
||||
|
||||
if (!isset($baseurl)) {
|
||||
$baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey()));
|
||||
}
|
||||
|
||||
if ($sr !== null) {
|
||||
$baseurl->param('sr', $sr);
|
||||
}
|
||||
|
||||
// AJAX edit title.
|
||||
if ($mod->has_view() && $hasmanageactivities && course_ajax_enabled($COURSE) &&
|
||||
(($mod->course == $COURSE->id) || ($mod->course == SITEID))) {
|
||||
// we will not display link if we are on some other-course page (where we should not see this module anyway)
|
||||
return html_writer::span(
|
||||
html_writer::link(
|
||||
new moodle_url($baseurl, array('update' => $mod->id)),
|
||||
$OUTPUT->pix_icon('t/editstring', '', 'moodle', array('class' => 'iconsmall visibleifjs', 'title' => '')),
|
||||
array(
|
||||
'class' => 'editing_title',
|
||||
'data-action' => 'edittitle',
|
||||
'title' => $str->edittitle,
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ information provided here is intended especially for theme designer.
|
||||
* notification_problem.mustache => notification_error.mustache
|
||||
* notification_message => notification_info
|
||||
* notification_redirect => notification_warning
|
||||
* Method core_course_renderer::course_section_cm_name() is split into two methods, custom themes that override
|
||||
this method must be modified or otherwise editing functionality may become broken. This method is now also used by
|
||||
social activities block and site menu block to display activities names.
|
||||
|
||||
=== 3.0 ===
|
||||
|
||||
|
||||
Reference in New Issue
Block a user