Note:

If you want to create a new page for developers, you should create it on the Moodle Developer Resource site.

Inplace editable: Difference between revisions

From MoodleDocs
No edit summary
(use callbacks)
Line 1: Line 1:
{{Work in progress}}
{{Work in progress}}


inplace_editable is a mini-API being introduced under MDL-51802 for Moodle 3.1. It allows developers easily add in-place editing of a value on any page. The interface looks very similar to activity name editing but implemented as AMD module using JQuery and is re-usable.  
inplace_editable is a mini-API being introduced under MDL-51802 for Moodle 3.1. It allows developers easily add in-place editing of a value on any page. The interface looks very similar to activity name editing but implemented as AMD module using JQuery and is re-usable. Activity names will be converted to use it as well in MDL-52869.


== Implementing inplace_editable in a plugin ==
== Implementing inplace_editable in a plugin ==


The best way is to explain the usage on a simple example. Imagine we have plugin '''tool_mytest''' that needs to implement in-place editing of a field 'name' from db table tool_mytest_mytable. We are going to call this itemtype "mytestname". This plugin only uses one in-place element so we will basically ignore $itemtype argument in the saver function.  
The best way is to explain the usage on a simple example. Imagine we have plugin '''tool_mytest''' that needs to implement in-place editing of a field 'name' from db table tool_mytest_mytable. We are going to call this itemtype "mytestname". Each plugin (or core component) may use as many itemtypes as it needs.


Create a file '''/admin/tool/mytest/classes/inplace_editable_saver.php''' with a class:
Define a callback in '''/admin/tool/mytest/lib.php''' that starts with the plugin name and ends with "''_inplace_editable''":
<code php>
<code php>
namespace tool_mytest;
function tool_mytest_inplace_editable($itemtype, $itemid, $newvalue) {
class inplace_editable_saver implements core\inplace_editable_saver_base {
    if ($itemtype === 'mytestname') {
    public function render_value($itemtype, $record) {
        $value = $record->name;
        $displayvalue = format_string($value); // You can add html link to the displayvalue.
        $editable = has_capability('tool/mytest:update', context_system::instance());
        $edithint = 'Edit mytest name';
        $editlabel = 'New value for ' . format_string($value); // Obviously, use language strings here!
        return new \core\output\inplace_editable('tool_mytest', $itemtype, $record->id, $editable, $displayvalue,
            $value, $edithint, $editlabel);
    }
 
    public function update_value($itemtype, $itemid, $newvalue) {
         global $DB;
         global $DB;
        // Always clean and validate input!
         $record = $DB->get_record('tool_mytest_mytable', array('id' => $itemid), '*', MUST_EXIST);
         $newvalue = clean_param($newvalue, PARAM_NOTAGS);
         // Check permission of the user to update this item. Call require_login($course) if needed.
         // Check permission of the user to update this item. Call require_login($course) if needed.
         require_capability('tool/mytest:update', context_system::instance());
         require_capability('tool/mytest:update', context_system::instance());
         $record = $DB->get_record('tool_mytest_mytable', array('id' => $itemid), '*', MUST_EXIST);
        // Clean input and update the record.
        // Update the record (the best way is to call an existing function for it).
         $newvalue = clean_param($newvalue, PARAM_NOTAGS);
         $DB->update_record('tool_mytest_mytable', array('id' => $itemid, 'name' => $newvalue));
         $DB->update_record('tool_mytest_mytable', array('id' => $itemid, 'name' => $newvalue));
        // Prepare the element for the output:
         $record->name = $newvalue;
         $record->name = $newvalue;
         return $this->render_value($itemtype, $record);
         return new \core\output\inplace_editable('tool_mytest', 'mytestname', $record->id, true,
            format_string($record->value), $record->value, 'Edit mytest name',  'New value for ' . format_string($record->value));
     }
     }
    throw new coding_exception('Unrecognised itemtype');
}
}
</code>
</code>


Please note that function "render_value" is not part of the interface and you can choose to call rendering function differently or not have it at all. Although it is recommended because you need to use the same templateable/renderable element for actually displaying the element on the page. For example:
In your renderer or wherever you actually display the name, use the same inplace_editable template:


<code php>
<code php>
$tmpl = new \tool_mytest\inplace_editable_saver();
$tmpl = new \core\output\inplace_editable('tool_mytest', 'mytestname', $record->id,
echo $OUTPUT->render($tmpl->render_value('mytestname', $record));
    has_capability('tool/mytest:update', context_system::instance()),
    format_string($record->value), $record->value, 'Edit mytest name', 'New value for ' . format_string($record->value));
echo $OUTPUT->render($tmpl);
</code>
</code>
This was a very simplified example, in the real life you will probably want to:
* Create a function (or class extending core\output\inplace_editable) to form the instance of templateable object so you don't need to duplicate code;
* Use language strings from the ''edithint'' and ''editlabel''
* Use an existing function to update a record (which hopefully also validates and triggers events)
* Add unit tests and behat tests
Constructor argument ''displayvalue'' may contain


== How does it work ==
== How does it work ==


inplace_editable consists of
inplace_editable consists of
* Templateable/renderable '''class core/inplace_editable'''
* Templateable/renderable '''class core\output\inplace_editable'''
* Template '''core/inplace_editable'''
* Javascript module '''core/inplace_editable'''
* Javascript module '''core/inplace_editable'''
* Web service '''core_update_inplace_editable''' available from AJAX
* Web service '''core_update_inplace_editable''' available from AJAX
* Interface '''core\inplace_editable_saver_base'''


All four call each other so it's hard to decide where we start explaining this circle of friends but let's start with web service.
All four call each other so it's hard to decide where we start explaining this circle of friends but let's start with web service.


'''Web service''' receives arguments ($component, $itemtype, $itemid, $newvalue) - it searches for the inplace_editable_saver class in the component and makes sure it implements the '''interface''' core\inplace_editable_saver_base. This is a security check that ensures that only allowed classes can be initiated in this web service. Then web service calls method update_value($itemtype, $itemid, $newvalue), this must return templateable element which is sent back to the web service caller.
'''Web service''' receives arguments ($component, $itemtype, $itemid, $newvalue) - it searches for the inplace_editable callback in the component. Then web service calls this callback as {component}_inplace_editable($itemtype, $itemid, $newvalue), this must return templateable element which is sent back to the web service caller.


'''Templateable element''' contains such properties as component, itemtype, itemid, displayvalue, value, editlabel and edithint. It only renders the displayvalue and the edit link (with title=edithint). All other properties are rendered as data-xxx attributes. Template also ensures that javascript module is loaded.  
'''Templateable element''' contains such properties as component, itemtype, itemid, displayvalue, value, editlabel and edithint. When used in a '''template''' It only renders the displayvalue and the edit link (with title=edithint). All other properties are rendered as data-xxx attributes. Template also ensures that javascript module is loaded.  


'''Javascript module''' registers a listener to when the edit link is clicked and then it replaces the displayvalue with the text input box that allows to edit value. When user presses "Enter" the AJAX request is called to the web service and code from the component is executed. If web service throws an exception it is displayed for user as a popup.
'''Javascript module''' registers a listener to when the edit link is clicked and then it replaces the displayvalue with the text input box that allows to edit value. When user presses "Enter" the AJAX request is called to the web service and code from the component is executed. If web service throws an exception it is displayed for user as a popup.

Revision as of 01:22, 5 February 2016

Note: This page is a work-in-progress. Feedback and suggested improvements are welcome. Please join the discussion on moodle.org or use the page comments.


inplace_editable is a mini-API being introduced under MDL-51802 for Moodle 3.1. It allows developers easily add in-place editing of a value on any page. The interface looks very similar to activity name editing but implemented as AMD module using JQuery and is re-usable. Activity names will be converted to use it as well in MDL-52869.

Implementing inplace_editable in a plugin

The best way is to explain the usage on a simple example. Imagine we have plugin tool_mytest that needs to implement in-place editing of a field 'name' from db table tool_mytest_mytable. We are going to call this itemtype "mytestname". Each plugin (or core component) may use as many itemtypes as it needs.

Define a callback in /admin/tool/mytest/lib.php that starts with the plugin name and ends with "_inplace_editable": function tool_mytest_inplace_editable($itemtype, $itemid, $newvalue) {

   if ($itemtype === 'mytestname') {
       global $DB;
       $record = $DB->get_record('tool_mytest_mytable', array('id' => $itemid), '*', MUST_EXIST);
       // Check permission of the user to update this item. Call require_login($course) if needed.
       require_capability('tool/mytest:update', context_system::instance());
       // Clean input and update the record.
       $newvalue = clean_param($newvalue, PARAM_NOTAGS);
       $DB->update_record('tool_mytest_mytable', array('id' => $itemid, 'name' => $newvalue));
       // Prepare the element for the output:
       $record->name = $newvalue;
       return new \core\output\inplace_editable('tool_mytest', 'mytestname', $record->id, true,
           format_string($record->value), $record->value, 'Edit mytest name',  'New value for ' . format_string($record->value));
   }
   throw new coding_exception('Unrecognised itemtype');

}

In your renderer or wherever you actually display the name, use the same inplace_editable template:

$tmpl = new \core\output\inplace_editable('tool_mytest', 'mytestname', $record->id,

   has_capability('tool/mytest:update', context_system::instance()),
   format_string($record->value), $record->value, 'Edit mytest name',  'New value for ' . format_string($record->value));

echo $OUTPUT->render($tmpl);

This was a very simplified example, in the real life you will probably want to:

  • Create a function (or class extending core\output\inplace_editable) to form the instance of templateable object so you don't need to duplicate code;
  • Use language strings from the edithint and editlabel
  • Use an existing function to update a record (which hopefully also validates and triggers events)
  • Add unit tests and behat tests

Constructor argument displayvalue may contain

How does it work

inplace_editable consists of

  • Templateable/renderable class core\output\inplace_editable
  • Template core/inplace_editable
  • Javascript module core/inplace_editable
  • Web service core_update_inplace_editable available from AJAX

All four call each other so it's hard to decide where we start explaining this circle of friends but let's start with web service.

Web service receives arguments ($component, $itemtype, $itemid, $newvalue) - it searches for the inplace_editable callback in the component. Then web service calls this callback as {component}_inplace_editable($itemtype, $itemid, $newvalue), this must return templateable element which is sent back to the web service caller.

Templateable element contains such properties as component, itemtype, itemid, displayvalue, value, editlabel and edithint. When used in a template It only renders the displayvalue and the edit link (with title=edithint). All other properties are rendered as data-xxx attributes. Template also ensures that javascript module is loaded.

Javascript module registers a listener to when the edit link is clicked and then it replaces the displayvalue with the text input box that allows to edit value. When user presses "Enter" the AJAX request is called to the web service and code from the component is executed. If web service throws an exception it is displayed for user as a popup.

See also

[Category:AJAX]