| 1 | efrain | 1 | // This file is part of Moodle - http://moodle.org/
 | 
        
           |  |  | 2 | //
 | 
        
           |  |  | 3 | // Moodle is free software: you can redistribute it and/or modify
 | 
        
           |  |  | 4 | // it under the terms of the GNU General Public License as published by
 | 
        
           |  |  | 5 | // the Free Software Foundation, either version 3 of the License, or
 | 
        
           |  |  | 6 | // (at your option) any later version.
 | 
        
           |  |  | 7 | //
 | 
        
           |  |  | 8 | // Moodle is distributed in the hope that it will be useful,
 | 
        
           |  |  | 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
        
           |  |  | 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
        
           |  |  | 11 | // GNU General Public License for more details.
 | 
        
           |  |  | 12 | //
 | 
        
           |  |  | 13 | // You should have received a copy of the GNU General Public License
 | 
        
           |  |  | 14 | // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 | 
        
           |  |  | 15 |   | 
        
           |  |  | 16 | /**
 | 
        
           |  |  | 17 |  * AJAX helper for the inline editing a value.
 | 
        
           |  |  | 18 |  *
 | 
        
           |  |  | 19 |  * This script is automatically included from template core/inplace_editable
 | 
        
           |  |  | 20 |  * It registers a click-listener on [data-inplaceeditablelink] link (the "inplace edit" icon),
 | 
        
           |  |  | 21 |  * then replaces the displayed value with an input field. On "Enter" it sends a request
 | 
        
           |  |  | 22 |  * to web service core_update_inplace_editable, which invokes the specified callback.
 | 
        
           |  |  | 23 |  * Any exception thrown by the web service (or callback) is displayed as an error popup.
 | 
        
           |  |  | 24 |  *
 | 
        
           |  |  | 25 |  * @module     core/inplace_editable
 | 
        
           |  |  | 26 |  * @copyright  2016 Marina Glancy
 | 
        
           |  |  | 27 |  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
        
           |  |  | 28 |  * @since      3.1
 | 
        
           |  |  | 29 |  */
 | 
        
           |  |  | 30 | define(
 | 
        
           |  |  | 31 |     ['jquery',
 | 
        
           |  |  | 32 |         'core/ajax',
 | 
        
           |  |  | 33 |         'core/templates',
 | 
        
           |  |  | 34 |         'core/notification',
 | 
        
           |  |  | 35 |         'core/str',
 | 
        
           |  |  | 36 |         'core/config',
 | 
        
           |  |  | 37 |         'core/url',
 | 
        
           |  |  | 38 |         'core/form-autocomplete',
 | 
        
           | 1441 | ariadna | 39 |         'core/loadingicon',
 | 
        
           | 1 | efrain | 40 |         'core/pending',
 | 
        
           |  |  | 41 |         'core/local/inplace_editable/events',
 | 
        
           |  |  | 42 |     ],
 | 
        
           | 1441 | ariadna | 43 |     function($, ajax, templates, notification, str, cfg, url, autocomplete, LoadingIcon, Pending, Events) {
 | 
        
           | 1 | efrain | 44 |   | 
        
           |  |  | 45 |         const removeSpinner = function(element) {
 | 
        
           | 1441 | ariadna | 46 |             element.find('.loading-icon').hide();
 | 
        
           | 1 | efrain | 47 |         };
 | 
        
           |  |  | 48 |   | 
        
           |  |  | 49 |         /**
 | 
        
           |  |  | 50 |          * Update an inplace editable value.
 | 
        
           |  |  | 51 |          *
 | 
        
           |  |  | 52 |          * @param {Jquery} mainelement the element to update
 | 
        
           |  |  | 53 |          * @param {string} value the new value
 | 
        
           |  |  | 54 |          * @param {bool} silent if true the change won't alter the current page focus
 | 
        
           |  |  | 55 |          * @fires event:core/inplace_editable:updated
 | 
        
           |  |  | 56 |          * @fires event:core/inplace_editable:updateFailed
 | 
        
           |  |  | 57 |          */
 | 
        
           |  |  | 58 |         const updateValue = function(mainelement, value, silent) {
 | 
        
           |  |  | 59 |             var pendingId = [
 | 
        
           |  |  | 60 |                 mainelement.attr('data-itemid'),
 | 
        
           |  |  | 61 |                 mainelement.attr('data-component'),
 | 
        
           |  |  | 62 |                 mainelement.attr('data-itemtype'),
 | 
        
           |  |  | 63 |             ].join('-');
 | 
        
           | 1441 | ariadna | 64 |   | 
        
           | 1 | efrain | 65 |             var pendingPromise = new Pending(pendingId);
 | 
        
           | 1441 | ariadna | 66 |             LoadingIcon.addIconToContainerRemoveOnCompletion(mainelement, pendingPromise);
 | 
        
           | 1 | efrain | 67 |   | 
        
           |  |  | 68 |             ajax.call([{
 | 
        
           |  |  | 69 |                 methodname: 'core_update_inplace_editable',
 | 
        
           |  |  | 70 |                 args: {
 | 
        
           |  |  | 71 |                     itemid: mainelement.attr('data-itemid'),
 | 
        
           |  |  | 72 |                     component: mainelement.attr('data-component'),
 | 
        
           |  |  | 73 |                     itemtype: mainelement.attr('data-itemtype'),
 | 
        
           |  |  | 74 |                     value: value,
 | 
        
           |  |  | 75 |                 },
 | 
        
           |  |  | 76 |             }])[0]
 | 
        
           |  |  | 77 |                 .then(function(data) {
 | 
        
           |  |  | 78 |                     return templates.render('core/inplace_editable', data)
 | 
        
           |  |  | 79 |                         .then(function(html, js) {
 | 
        
           |  |  | 80 |                             var oldvalue = mainelement.attr('data-value');
 | 
        
           |  |  | 81 |                             var newelement = $(html);
 | 
        
           |  |  | 82 |                             templates.replaceNode(mainelement, newelement, js);
 | 
        
           |  |  | 83 |                             if (!silent) {
 | 
        
           |  |  | 84 |                                 newelement.find('[data-inplaceeditablelink]').focus();
 | 
        
           |  |  | 85 |                             }
 | 
        
           |  |  | 86 |   | 
        
           |  |  | 87 |                             // Trigger updated event on the DOM element.
 | 
        
           |  |  | 88 |                             Events.notifyElementUpdated(newelement.get(0), data, oldvalue);
 | 
        
           |  |  | 89 |   | 
        
           |  |  | 90 |                             return;
 | 
        
           |  |  | 91 |                         });
 | 
        
           |  |  | 92 |                 })
 | 
        
           |  |  | 93 |                 .then(function() {
 | 
        
           |  |  | 94 |                     return pendingPromise.resolve();
 | 
        
           |  |  | 95 |                 })
 | 
        
           |  |  | 96 |                 .fail(function(ex) {
 | 
        
           |  |  | 97 |                     removeSpinner(mainelement);
 | 
        
           |  |  | 98 |                     M.util.js_complete(pendingId);
 | 
        
           |  |  | 99 |   | 
        
           |  |  | 100 |                     // Trigger update failed event on the DOM element.
 | 
        
           |  |  | 101 |                     let updateFailedEvent = Events.notifyElementUpdateFailed(mainelement.get(0), ex, value);
 | 
        
           |  |  | 102 |                     if (!updateFailedEvent.defaultPrevented) {
 | 
        
           |  |  | 103 |                         notification.exception(ex);
 | 
        
           |  |  | 104 |                     }
 | 
        
           |  |  | 105 |                 });
 | 
        
           |  |  | 106 |         };
 | 
        
           |  |  | 107 |   | 
        
           |  |  | 108 |         $('body').on('click keypress', '[data-inplaceeditable] [data-inplaceeditablelink]', function(e) {
 | 
        
           |  |  | 109 |             if (e.type === 'keypress' && e.keyCode !== 13) {
 | 
        
           |  |  | 110 |                 return;
 | 
        
           |  |  | 111 |             }
 | 
        
           |  |  | 112 |             var editingEnabledPromise = new Pending('autocomplete-start-editing');
 | 
        
           |  |  | 113 |             e.stopImmediatePropagation();
 | 
        
           |  |  | 114 |             e.preventDefault();
 | 
        
           |  |  | 115 |             var target = $(this),
 | 
        
           |  |  | 116 |                 mainelement = target.closest('[data-inplaceeditable]');
 | 
        
           |  |  | 117 |   | 
        
           |  |  | 118 |             var turnEditingOff = function(el) {
 | 
        
           |  |  | 119 |                 el.find('input').off();
 | 
        
           |  |  | 120 |                 el.find('select').off();
 | 
        
           |  |  | 121 |                 el.html(el.attr('data-oldcontent'));
 | 
        
           |  |  | 122 |                 el.removeAttr('data-oldcontent');
 | 
        
           |  |  | 123 |                 el.removeClass('inplaceeditingon');
 | 
        
           |  |  | 124 |                 el.find('[data-inplaceeditablelink]').focus();
 | 
        
           |  |  | 125 |   | 
        
           |  |  | 126 |                 // Re-enable any parent draggable attribute.
 | 
        
           |  |  | 127 |                 el.parents(`[data-inplace-in-draggable="true"]`)
 | 
        
           |  |  | 128 |                     .attr('draggable', true)
 | 
        
           |  |  | 129 |                     .attr('data-inplace-in-draggable', false);
 | 
        
           |  |  | 130 |             };
 | 
        
           |  |  | 131 |   | 
        
           |  |  | 132 |             var turnEditingOffEverywhere = function() {
 | 
        
           |  |  | 133 |                 // Re-enable any disabled draggable attribute.
 | 
        
           |  |  | 134 |                 $(`[data-inplace-in-draggable="true"]`)
 | 
        
           |  |  | 135 |                     .attr('draggable', true)
 | 
        
           |  |  | 136 |                     .attr('data-inplace-in-draggable', false);
 | 
        
           |  |  | 137 |   | 
        
           |  |  | 138 |                 $('span.inplaceeditable.inplaceeditingon').each(function() {
 | 
        
           |  |  | 139 |                     turnEditingOff($(this));
 | 
        
           |  |  | 140 |                 });
 | 
        
           |  |  | 141 |             };
 | 
        
           |  |  | 142 |   | 
        
           |  |  | 143 |             var uniqueId = function(prefix, idlength) {
 | 
        
           |  |  | 144 |                 var uniqid = prefix,
 | 
        
           |  |  | 145 |                     i;
 | 
        
           |  |  | 146 |                 for (i = 0; i < idlength; i++) {
 | 
        
           |  |  | 147 |                     uniqid += String(Math.floor(Math.random() * 10));
 | 
        
           |  |  | 148 |                 }
 | 
        
           |  |  | 149 |                 // Make sure this ID is not already taken by an existing element.
 | 
        
           |  |  | 150 |                 if ($("#" + uniqid).length === 0) {
 | 
        
           |  |  | 151 |                     return uniqid;
 | 
        
           |  |  | 152 |                 }
 | 
        
           |  |  | 153 |                 return uniqueId(prefix, idlength);
 | 
        
           |  |  | 154 |             };
 | 
        
           |  |  | 155 |   | 
        
           |  |  | 156 |             var turnEditingOnText = function(el) {
 | 
        
           |  |  | 157 |                 str.get_string('edittitleinstructions').done(function(s) {
 | 
        
           |  |  | 158 |                     var instr = $('<span class="editinstructions">' + s + '</span>').
 | 
        
           |  |  | 159 |                         attr('id', uniqueId('id_editinstructions_', 20)),
 | 
        
           |  |  | 160 |                         inputelement = $('<input type="text"/>').
 | 
        
           |  |  | 161 |                             attr('id', uniqueId('id_inplacevalue_', 20)).
 | 
        
           |  |  | 162 |                             attr('value', el.attr('data-value')).
 | 
        
           |  |  | 163 |                             attr('aria-describedby', instr.attr('id')).
 | 
        
           |  |  | 164 |                             addClass('ignoredirty').
 | 
        
           |  |  | 165 |                             addClass('form-control'),
 | 
        
           |  |  | 166 |                         lbl = $('<label class="accesshide">' + mainelement.attr('data-editlabel') + '</label>').
 | 
        
           |  |  | 167 |                             attr('for', inputelement.attr('id'));
 | 
        
           |  |  | 168 |                     el.html('').append(instr).append(lbl).append(inputelement);
 | 
        
           |  |  | 169 |   | 
        
           |  |  | 170 |                     inputelement.focus();
 | 
        
           |  |  | 171 |                     inputelement.select();
 | 
        
           |  |  | 172 |                     inputelement.on('keyup keypress focusout', function(e) {
 | 
        
           |  |  | 173 |                         if (cfg.behatsiterunning && e.type === 'focusout') {
 | 
        
           |  |  | 174 |                             // Behat triggers focusout too often.
 | 
        
           |  |  | 175 |                             return;
 | 
        
           |  |  | 176 |                         }
 | 
        
           |  |  | 177 |                         if (e.type === 'keypress' && e.keyCode === 13) {
 | 
        
           |  |  | 178 |                             // We need 'keypress' event for Enter because keyup/keydown would catch Enter that was
 | 
        
           |  |  | 179 |                             // pressed in other fields.
 | 
        
           |  |  | 180 |                             var val = inputelement.val();
 | 
        
           |  |  | 181 |                             turnEditingOff(el);
 | 
        
           |  |  | 182 |                             updateValue(el, val);
 | 
        
           |  |  | 183 |                         }
 | 
        
           |  |  | 184 |                         if ((e.type === 'keyup' && e.keyCode === 27) || e.type === 'focusout') {
 | 
        
           |  |  | 185 |                             // We need 'keyup' event for Escape because keypress does not work with Escape.
 | 
        
           |  |  | 186 |                             turnEditingOff(el);
 | 
        
           |  |  | 187 |                         }
 | 
        
           |  |  | 188 |                     });
 | 
        
           |  |  | 189 |                 });
 | 
        
           |  |  | 190 |             };
 | 
        
           |  |  | 191 |   | 
        
           |  |  | 192 |             var turnEditingOnToggle = function(el, newvalue) {
 | 
        
           |  |  | 193 |                 turnEditingOff(el);
 | 
        
           |  |  | 194 |                 updateValue(el, newvalue);
 | 
        
           |  |  | 195 |             };
 | 
        
           |  |  | 196 |   | 
        
           |  |  | 197 |             var turnEditingOnSelect = function(el, options) {
 | 
        
           |  |  | 198 |                 var i,
 | 
        
           |  |  | 199 |                     inputelement = $('<select></select>').
 | 
        
           |  |  | 200 |                         attr('id', uniqueId('id_inplacevalue_', 20)).
 | 
        
           | 1441 | ariadna | 201 |                         addClass('form-select'),
 | 
        
           | 1 | efrain | 202 |                     lbl = $('<label class="accesshide">' + mainelement.attr('data-editlabel') + '</label>')
 | 
        
           |  |  | 203 |                         .attr('for', inputelement.attr('id'));
 | 
        
           |  |  | 204 |                 for (i in options) {
 | 
        
           |  |  | 205 |                     inputelement
 | 
        
           |  |  | 206 |                         .append($('<option>')
 | 
        
           |  |  | 207 |                             .attr('value', options[i].key)
 | 
        
           |  |  | 208 |                             .html(options[i].value));
 | 
        
           |  |  | 209 |                 }
 | 
        
           |  |  | 210 |                 inputelement.val(el.attr('data-value'));
 | 
        
           |  |  | 211 |   | 
        
           |  |  | 212 |                 el.html('')
 | 
        
           |  |  | 213 |                     .append(lbl)
 | 
        
           |  |  | 214 |                     .append(inputelement);
 | 
        
           |  |  | 215 |   | 
        
           |  |  | 216 |                 inputelement.focus();
 | 
        
           |  |  | 217 |                 inputelement.select();
 | 
        
           |  |  | 218 |                 inputelement.on('keyup change focusout', function(e) {
 | 
        
           |  |  | 219 |                     if (cfg.behatsiterunning && e.type === 'focusout') {
 | 
        
           |  |  | 220 |                         // Behat triggers focusout too often.
 | 
        
           |  |  | 221 |                         return;
 | 
        
           |  |  | 222 |                     }
 | 
        
           |  |  | 223 |                     if (e.type === 'change') {
 | 
        
           |  |  | 224 |                         var val = inputelement.val();
 | 
        
           |  |  | 225 |                         turnEditingOff(el);
 | 
        
           |  |  | 226 |                         updateValue(el, val);
 | 
        
           |  |  | 227 |                     }
 | 
        
           |  |  | 228 |                     if ((e.type === 'keyup' && e.keyCode === 27) || e.type === 'focusout') {
 | 
        
           |  |  | 229 |                         // We need 'keyup' event for Escape because keypress does not work with Escape.
 | 
        
           |  |  | 230 |                         turnEditingOff(el);
 | 
        
           |  |  | 231 |                     }
 | 
        
           |  |  | 232 |                 });
 | 
        
           |  |  | 233 |             };
 | 
        
           |  |  | 234 |   | 
        
           |  |  | 235 |             var turnEditingOnAutocomplete = function(el, args) {
 | 
        
           |  |  | 236 |                 var i,
 | 
        
           |  |  | 237 |                     inputelement = $('<select></select>').
 | 
        
           |  |  | 238 |                         attr('id', uniqueId('id_inplacevalue_', 20)).
 | 
        
           |  |  | 239 |                         addClass('form-autocomplete-original-select').
 | 
        
           | 1441 | ariadna | 240 |                         addClass('form-select'),
 | 
        
           | 1 | efrain | 241 |                     lbl = $('<label class="accesshide">' + mainelement.attr('data-editlabel') + '</label>')
 | 
        
           |  |  | 242 |                         .attr('for', inputelement.attr('id')),
 | 
        
           |  |  | 243 |                     options = args.options,
 | 
        
           |  |  | 244 |                     attributes = args.attributes,
 | 
        
           |  |  | 245 |                     saveelement = $('<a href="#"></a>'),
 | 
        
           |  |  | 246 |                     cancelelement = $('<a href="#"></a>');
 | 
        
           |  |  | 247 |   | 
        
           |  |  | 248 |                 for (i in options) {
 | 
        
           |  |  | 249 |                     inputelement
 | 
        
           |  |  | 250 |                         .append($('<option>')
 | 
        
           |  |  | 251 |                             .attr('value', options[i].key)
 | 
        
           |  |  | 252 |                             .html(options[i].value));
 | 
        
           |  |  | 253 |                 }
 | 
        
           |  |  | 254 |                 if (attributes.multiple) {
 | 
        
           |  |  | 255 |                     inputelement.attr('multiple', 'true');
 | 
        
           |  |  | 256 |                 }
 | 
        
           |  |  | 257 |                 inputelement.val(JSON.parse(el.attr('data-value')));
 | 
        
           |  |  | 258 |   | 
        
           |  |  | 259 |                 str.get_string('savechanges', 'core').then(function(s) {
 | 
        
           |  |  | 260 |                     return templates.renderPix('e/save', 'core', s);
 | 
        
           |  |  | 261 |                 }).then(function(html) {
 | 
        
           |  |  | 262 |                     saveelement.append(html);
 | 
        
           |  |  | 263 |                     return;
 | 
        
           |  |  | 264 |                 }).fail(notification.exception);
 | 
        
           |  |  | 265 |   | 
        
           |  |  | 266 |                 str.get_string('cancel', 'core').then(function(s) {
 | 
        
           |  |  | 267 |                     return templates.renderPix('e/cancel', 'core', s);
 | 
        
           |  |  | 268 |                 }).then(function(html) {
 | 
        
           |  |  | 269 |                     cancelelement.append(html);
 | 
        
           |  |  | 270 |                     return;
 | 
        
           |  |  | 271 |                 }).fail(notification.exception);
 | 
        
           |  |  | 272 |   | 
        
           |  |  | 273 |                 el.html('')
 | 
        
           |  |  | 274 |                     .append(lbl)
 | 
        
           |  |  | 275 |                     .append(inputelement)
 | 
        
           |  |  | 276 |                     .append(saveelement)
 | 
        
           |  |  | 277 |                     .append(cancelelement);
 | 
        
           |  |  | 278 |   | 
        
           |  |  | 279 |                 inputelement.focus();
 | 
        
           |  |  | 280 |                 inputelement.select();
 | 
        
           |  |  | 281 |                 autocomplete.enhance(inputelement,
 | 
        
           |  |  | 282 |                     attributes.tags,
 | 
        
           |  |  | 283 |                     attributes.ajax,
 | 
        
           |  |  | 284 |                     attributes.placeholder,
 | 
        
           |  |  | 285 |                     attributes.caseSensitive,
 | 
        
           |  |  | 286 |                     attributes.showSuggestions,
 | 
        
           |  |  | 287 |                     attributes.noSelectionString)
 | 
        
           |  |  | 288 |                     .then(function() {
 | 
        
           |  |  | 289 |                         // Focus on the enhanced combobox.
 | 
        
           |  |  | 290 |                         el.find('[role=combobox]').focus();
 | 
        
           |  |  | 291 |                         // Stop eslint nagging.
 | 
        
           |  |  | 292 |                         return;
 | 
        
           |  |  | 293 |                     }).fail(notification.exception);
 | 
        
           |  |  | 294 |   | 
        
           |  |  | 295 |                 inputelement.on('keyup', function(e) {
 | 
        
           |  |  | 296 |                     if ((e.type === 'keyup' && e.keyCode === 27) || e.type === 'focusout') {
 | 
        
           |  |  | 297 |                         // We need 'keyup' event for Escape because keypress does not work with Escape.
 | 
        
           |  |  | 298 |                         turnEditingOff(el);
 | 
        
           |  |  | 299 |                     }
 | 
        
           |  |  | 300 |                 });
 | 
        
           |  |  | 301 |                 saveelement.on('click', function(e) {
 | 
        
           |  |  | 302 |                     var val = JSON.stringify(inputelement.val());
 | 
        
           |  |  | 303 |                     // We need to empty the node to destroy all event handlers etc.
 | 
        
           |  |  | 304 |                     inputelement.empty();
 | 
        
           |  |  | 305 |                     turnEditingOff(el);
 | 
        
           |  |  | 306 |                     updateValue(el, val);
 | 
        
           |  |  | 307 |                     e.preventDefault();
 | 
        
           |  |  | 308 |                 });
 | 
        
           |  |  | 309 |                 cancelelement.on('click', function(e) {
 | 
        
           |  |  | 310 |                     // We need to empty the node to destroy all event handlers etc.
 | 
        
           |  |  | 311 |                     inputelement.empty();
 | 
        
           |  |  | 312 |                     turnEditingOff(el);
 | 
        
           |  |  | 313 |                     e.preventDefault();
 | 
        
           |  |  | 314 |                 });
 | 
        
           |  |  | 315 |             };
 | 
        
           |  |  | 316 |   | 
        
           |  |  | 317 |             var turnEditingOn = function(el) {
 | 
        
           |  |  | 318 |                 el.addClass('inplaceeditingon');
 | 
        
           |  |  | 319 |                 el.attr('data-oldcontent', el.html());
 | 
        
           |  |  | 320 |   | 
        
           |  |  | 321 |                 var type = el.attr('data-type');
 | 
        
           |  |  | 322 |                 var options = el.attr('data-options');
 | 
        
           |  |  | 323 |   | 
        
           |  |  | 324 |                 // Input text inside draggable elements disable text selection in some browsers.
 | 
        
           |  |  | 325 |                 // To prevent this we temporally disable any parent draggables.
 | 
        
           |  |  | 326 |                 el.parents('[draggable="true"]')
 | 
        
           |  |  | 327 |                     .attr('data-inplace-in-draggable', true)
 | 
        
           |  |  | 328 |                     .attr('draggable', false);
 | 
        
           |  |  | 329 |   | 
        
           |  |  | 330 |                 if (type === 'toggle') {
 | 
        
           |  |  | 331 |                     turnEditingOnToggle(el, options);
 | 
        
           |  |  | 332 |                 } else if (type === 'select') {
 | 
        
           |  |  | 333 |                     turnEditingOnSelect(el, $.parseJSON(options));
 | 
        
           |  |  | 334 |                 } else if (type === 'autocomplete') {
 | 
        
           |  |  | 335 |                     turnEditingOnAutocomplete(el, $.parseJSON(options));
 | 
        
           |  |  | 336 |                 } else {
 | 
        
           |  |  | 337 |                     turnEditingOnText(el);
 | 
        
           |  |  | 338 |                 }
 | 
        
           |  |  | 339 |             };
 | 
        
           |  |  | 340 |   | 
        
           |  |  | 341 |             // Turn editing on for the current element and register handler for Enter/Esc keys.
 | 
        
           |  |  | 342 |             turnEditingOffEverywhere();
 | 
        
           |  |  | 343 |             turnEditingOn(mainelement);
 | 
        
           |  |  | 344 |             editingEnabledPromise.resolve();
 | 
        
           |  |  | 345 |   | 
        
           |  |  | 346 |         });
 | 
        
           |  |  | 347 |   | 
        
           |  |  | 348 |   | 
        
           |  |  | 349 |         return {
 | 
        
           |  |  | 350 |             /**
 | 
        
           |  |  | 351 |              * Return an object to interact with the current inplace editables at a frontend level.
 | 
        
           |  |  | 352 |              *
 | 
        
           |  |  | 353 |              * @param {Element} parent the parent element containing a inplace editable
 | 
        
           |  |  | 354 |              * @returns {Object|undefined} an object to interact with the inplace element, or undefined
 | 
        
           |  |  | 355 |              *                             if no inplace editable is found.
 | 
        
           |  |  | 356 |              */
 | 
        
           |  |  | 357 |             getInplaceEditable: function(parent) {
 | 
        
           |  |  | 358 |                 const element = parent.querySelector(`[data-inplaceeditable]`);
 | 
        
           |  |  | 359 |                 if (!element) {
 | 
        
           |  |  | 360 |                     return undefined;
 | 
        
           |  |  | 361 |                 }
 | 
        
           |  |  | 362 |                 // Return an object to interact with the inplace editable.
 | 
        
           |  |  | 363 |                 return {
 | 
        
           |  |  | 364 |                     element,
 | 
        
           |  |  | 365 |                     /**
 | 
        
           |  |  | 366 |                      * Get the value from the inplace editable.
 | 
        
           |  |  | 367 |                      *
 | 
        
           |  |  | 368 |                      * @returns {string} the current inplace value
 | 
        
           |  |  | 369 |                      */
 | 
        
           |  |  | 370 |                     getValue: function() {
 | 
        
           |  |  | 371 |                         return this.element.dataset.value;
 | 
        
           |  |  | 372 |                     },
 | 
        
           |  |  | 373 |                     /**
 | 
        
           |  |  | 374 |                      * Force a value change.
 | 
        
           |  |  | 375 |                      *
 | 
        
           |  |  | 376 |                      * @param {string} newvalue the new value
 | 
        
           |  |  | 377 |                      * @fires event:core/inplace_editable:updated
 | 
        
           |  |  | 378 |                      * @fires event:core/inplace_editable:updateFailed
 | 
        
           |  |  | 379 |                      */
 | 
        
           |  |  | 380 |                     setValue: function(newvalue) {
 | 
        
           |  |  | 381 |                         updateValue($(this.element), newvalue, true);
 | 
        
           |  |  | 382 |                     },
 | 
        
           |  |  | 383 |                     /**
 | 
        
           |  |  | 384 |                      * Return the inplace editable itemid.
 | 
        
           |  |  | 385 |                      *
 | 
        
           |  |  | 386 |                      * @returns {string} the current itemid
 | 
        
           |  |  | 387 |                      */
 | 
        
           |  |  | 388 |                     getItemId: function() {
 | 
        
           |  |  | 389 |                         return this.element.dataset.itemid;
 | 
        
           |  |  | 390 |                     },
 | 
        
           |  |  | 391 |                 };
 | 
        
           |  |  | 392 |             }
 | 
        
           |  |  | 393 |         };
 | 
        
           |  |  | 394 |     });
 |