Merge branch 'wip-MDL-53172-master' of https://github.com/marinaglancy/moodle

This commit is contained in:
Eloy Lafuente (stronk7) 2016-03-01 01:59:31 +01:00
commit d9dfc410ad
9 changed files with 301 additions and 77 deletions

View File

@ -1 +1 @@
define(["jquery","core/ajax","core/templates","core/notification","core/str","core/config"],function(a,b,c,d,e,f){return a("body").on("click keypress","[data-inplaceeditable] [data-inplaceeditablelink]",function(g){if("keypress"!==g.type||13===g.keyCode){g.stopImmediatePropagation(),g.preventDefault();var h=a(this),i=h.closest("[data-inplaceeditable]"),j=function(e,f){var g=b.call([{methodname:"core_update_inplace_editable",args:{itemid:e.attr("data-itemid"),component:e.attr("data-component"),itemtype:e.attr("data-itemtype"),value:f}}],!0);a.when.apply(a,g).done(function(a){var b=e.attr("data-value");c.render("core/inplace_editable",a).done(function(a,b){c.replaceNode(e,a,b),e.find("[data-inplaceeditablelink]").focus()}),e.trigger({type:"updated",ajaxreturn:a,oldvalue:b})}).fail(function(b){var c=a.Event("updatefailed",{exception:b,newvalue:f});e.trigger(c),c.isDefaultPrevented()||d.exception(b)})},k=function(a){var b=a.find("input");b.off(),a.html(a.attr("data-oldcontent")),a.removeAttr("data-oldcontent"),a.removeClass("inplaceeditingon")},l=function(){a("span.inplaceeditable.inplaceeditingon").each(function(){k(a(this))})},m=function(b,c){for(var d=b,e=0;c>e;e++)d+=String(Math.floor(10*Math.random()));return 0===a("#"+d).length?d:m(b,c)},n=function(b){b.addClass("inplaceeditingon"),b.attr("data-oldcontent",b.html()),e.get_string("edittitleinstructions").done(function(c){var d=a('<span class="editinstructions">'+c+"</span>").attr("id",m("id_editinstructions_",20)),e=a('<input type="text"/>').attr("id",m("id_inplacevalue_",20)).attr("value",b.attr("data-value")).attr("aria-describedby",d.attr("id")),g=a('<label class="accesshide">'+i.attr("data-editlabel")+"</label>").attr("for",e.attr("id"));b.html("").append(d).append(g).append(e),e.focus(),e.select(),e.on("keyup keypress focusout",function(a){f.behatsiterunning&&"focusout"===a.type||("keypress"===a.type&&13===a.keyCode&&(j(b,e.val()),k(b)),("keyup"===a.type&&27===a.keyCode||"focusout"===a.type)&&k(b))})})};l(),n(i)}}),{}});
define(["jquery","core/ajax","core/templates","core/notification","core/str","core/config","core/url"],function(a,b,c,d,e,f,g){return a("body").on("click keypress","[data-inplaceeditable] [data-inplaceeditablelink]",function(h){if("keypress"!==h.type||13===h.keyCode){h.stopImmediatePropagation(),h.preventDefault();var i=a(this),j=i.closest("[data-inplaceeditable]"),k=function(b){b.addClass("updating");var c=b.find("img.spinner");c.length?c.show():(c=a("<img/>").attr("src",g.imageUrl("i/loading_small")).addClass("spinner").addClass("smallicon"),b.append(c))},l=function(a){a.removeClass("updating"),a.find("img.spinner").hide()},m=function(e,f){k(e),b.call([{methodname:"core_update_inplace_editable",args:{itemid:e.attr("data-itemid"),component:e.attr("data-component"),itemtype:e.attr("data-itemtype"),value:f},done:function(b){var d=e.attr("data-value");c.render("core/inplace_editable",b).done(function(f,g){var h=a(f);c.replaceNode(e,h,g),h.find("[data-inplaceeditablelink]").focus(),h.trigger({type:"updated",ajaxreturn:b,oldvalue:d})})},fail:function(b){var c=a.Event("updatefailed",{exception:b,newvalue:f});l(e),e.trigger(c),c.isDefaultPrevented()||d.exception(b)}}],!0)},n=function(a){a.find("input").off(),a.find("select").off(),a.html(a.attr("data-oldcontent")),a.removeAttr("data-oldcontent"),a.removeClass("inplaceeditingon"),a.find("[data-inplaceeditablelink]").focus()},o=function(){a("span.inplaceeditable.inplaceeditingon").each(function(){n(a(this))})},p=function(b,c){var d,e=b;for(d=0;c>d;d++)e+=String(Math.floor(10*Math.random()));return 0===a("#"+e).length?e:p(b,c)},q=function(b){e.get_string("edittitleinstructions").done(function(c){var d=a('<span class="editinstructions">'+c+"</span>").attr("id",p("id_editinstructions_",20)),e=a('<input type="text"/>').attr("id",p("id_inplacevalue_",20)).attr("value",b.attr("data-value")).attr("aria-describedby",d.attr("id")),g=a('<label class="accesshide">'+j.attr("data-editlabel")+"</label>").attr("for",e.attr("id"));b.html("").append(d).append(g).append(e),e.focus(),e.select(),e.on("keyup keypress focusout",function(a){if(!f.behatsiterunning||"focusout"!==a.type){if("keypress"===a.type&&13===a.keyCode){var c=e.val();n(b),m(b,c)}("keyup"===a.type&&27===a.keyCode||"focusout"===a.type)&&n(b)}})})},r=function(a,b){n(a),m(a,b)},s=function(b,c){var d,e=a("<select></select>").attr("id",p("id_inplacevalue_",20)),g=a('<label class="accesshide">'+j.attr("data-editlabel")+"</label>").attr("for",e.attr("id"));for(d in c)e.append(a("<option>").attr("value",d).html(c[d]));e.val(b.attr("data-value")),b.html("").append(g).append(e),e.focus(),e.select(),e.on("keyup change focusout",function(a){if(!f.behatsiterunning||"focusout"!==a.type){if("change"===a.type){var c=e.val();n(b),m(b,c)}("keyup"===a.type&&27===a.keyCode||"focusout"===a.type)&&n(b)}})},t=function(b){b.addClass("inplaceeditingon"),b.attr("data-oldcontent",b.html());var c=b.attr("data-type"),d=b.attr("data-options");"toggle"===c?r(b,d):"select"===c?s(b,a.parseJSON(d)):q(b)};o(),t(j)}}),{}});

View File

@ -28,8 +28,8 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.1
*/
define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str', 'core/config'],
function($, ajax, templates, notification, str, cfg) {
define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str', 'core/config', 'core/url'],
function($, ajax, templates, notification, str, cfg, url) {
$('body').on('click keypress', '[data-inplaceeditable] [data-inplaceeditablelink]', function(e) {
if (e.type === 'keypress' && e.keyCode !== 13) {
@ -40,67 +40,93 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
var target = $(this),
mainelement = target.closest('[data-inplaceeditable]');
var update_value = function(mainelement, value) {
var promises = ajax.call([{
methodname: 'core_update_inplace_editable',
args: { itemid : mainelement.attr('data-itemid'),
component : mainelement.attr('data-component') ,
itemtype : mainelement.attr('data-itemtype') ,
value : value }
}], true);
$.when.apply($, promises)
.done( function(data) {
var oldvalue = mainelement.attr('data-value');
templates.render('core/inplace_editable', data).done(function(html, js) {
templates.replaceNode(mainelement, html, js);
mainelement.find('[data-inplaceeditablelink]').focus();
});
mainelement.trigger({type: 'updated', ajaxreturn: data, oldvalue: oldvalue});
}).fail(function(ex) {
var e = $.Event('updatefailed', { exception: ex, newvalue: value });
mainelement.trigger(e);
if (!e.isDefaultPrevented()) {
notification.exception(ex);
}
});
var addSpinner = function(element) {
element.addClass('updating');
var spinner = element.find('img.spinner');
if (spinner.length) {
spinner.show();
} else {
spinner = $('<img/>')
.attr('src', url.imageUrl('i/loading_small'))
.addClass('spinner').addClass('smallicon')
;
element.append(spinner);
}
};
var turn_editing_off = function(el) {
var input = el.find('input');
input.off();
var removeSpinner = function(element) {
element.removeClass('updating');
element.find('img.spinner').hide();
};
var updateValue = function(mainelement, value) {
addSpinner(mainelement);
ajax
.call([{
methodname: 'core_update_inplace_editable',
args: {
itemid: mainelement.attr('data-itemid'),
component: mainelement.attr('data-component'),
itemtype: mainelement.attr('data-itemtype'),
value: value
},
done: function(data) {
var oldvalue = mainelement.attr('data-value');
templates.render('core/inplace_editable', data).done(function(html, js) {
var newelement = $(html);
templates.replaceNode(mainelement, newelement, js);
newelement.find('[data-inplaceeditablelink]').focus();
newelement.trigger({type: 'updated', ajaxreturn: data, oldvalue: oldvalue});
});
},
fail: function(ex) {
var e = $.Event('updatefailed', {
exception: ex,
newvalue: value
});
removeSpinner(mainelement);
mainelement.trigger(e);
if (!e.isDefaultPrevented()) {
notification.exception(ex);
}
}
}], true);
};
var turnEditingOff = function(el) {
el.find('input').off();
el.find('select').off();
el.html(el.attr('data-oldcontent'));
el.removeAttr('data-oldcontent');
el.removeClass('inplaceeditingon');
el.find('[data-inplaceeditablelink]').focus();
};
var turn_editing_off_everywhere = function() {
var turnEditingOffEverywhere = function() {
$('span.inplaceeditable.inplaceeditingon').each(function() {
turn_editing_off($( this));
turnEditingOff($(this));
});
};
var unique_id = function(prefix, idlength) {
var uniqid = prefix;
for (var i = 0; i < idlength; i++) {
var uniqueId = function(prefix, idlength) {
var uniqid = prefix,
i;
for (i = 0; i < idlength; i++) {
uniqid += String(Math.floor(Math.random() * 10));
}
// Make sure this ID is not already taken by an existing element.
if ($("#" + uniqid).length === 0) {
return uniqid;
}
return unique_id(prefix, idlength);
return uniqueId(prefix, idlength);
};
var turn_editing_on = function(el) {
el.addClass('inplaceeditingon');
el.attr('data-oldcontent', el.html());
var turnEditingOnText = function(el) {
str.get_string('edittitleinstructions').done(function(s) {
var instr = $('<span class="editinstructions">' + s + '</span>').
attr('id', unique_id('id_editinstructions_', 20)),
attr('id', uniqueId('id_editinstructions_', 20)),
inputelement = $('<input type="text"/>').
attr('id', unique_id('id_inplacevalue_', 20)).
attr('id', uniqueId('id_inplacevalue_', 20)).
attr('value', el.attr('data-value')).
attr('aria-describedby', instr.attr('id')),
lbl = $('<label class="accesshide">' + mainelement.attr('data-editlabel') + '</label>').
@ -117,22 +143,80 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
if (e.type === 'keypress' && e.keyCode === 13) {
// We need 'keypress' event for Enter because keyup/keydown would catch Enter that was
// pressed in other fields.
update_value(el, inputelement.val());
turn_editing_off(el);
var val = inputelement.val();
turnEditingOff(el);
updateValue(el, val);
}
if ((e.type === 'keyup' && e.keyCode === 27) || e.type === 'focusout') {
// We need 'keyup' event for Escape because keypress does not work with Escape.
turn_editing_off(el);
turnEditingOff(el);
}
});
});
};
var turnEditingOnToggle = function(el, newvalue) {
turnEditingOff(el);
updateValue(el, newvalue);
};
var turnEditingOnSelect = function(el, options) {
var i,
inputelement = $('<select></select>')
.attr('id', uniqueId('id_inplacevalue_', 20)),
lbl = $('<label class="accesshide">' + mainelement.attr('data-editlabel') + '</label>')
.attr('for', inputelement.attr('id'));
for (i in options) {
inputelement
.append($('<option>')
.attr('value', i)
.html(options[i]));
}
inputelement.val(el.attr('data-value'));
el.html('')
.append(lbl)
.append(inputelement);
inputelement.focus();
inputelement.select();
inputelement.on('keyup change focusout', function(e) {
if (cfg.behatsiterunning && e.type === 'focusout') {
// Behat triggers focusout too often.
return;
}
if (e.type === 'change') {
var val = inputelement.val();
turnEditingOff(el);
updateValue(el, val);
}
if ((e.type === 'keyup' && e.keyCode === 27) || e.type === 'focusout') {
// We need 'keyup' event for Escape because keypress does not work with Escape.
turnEditingOff(el);
}
});
};
var turnEditingOn = function(el) {
el.addClass('inplaceeditingon');
el.attr('data-oldcontent', el.html());
var type = el.attr('data-type');
var options = el.attr('data-options');
if (type === 'toggle') {
turnEditingOnToggle(el, options);
} else if (type === 'select') {
turnEditingOnSelect(el, $.parseJSON(options));
} else {
turnEditingOnText(el);
}
};
// Turn editing on for the current element and register handler for Enter/Esc keys.
turn_editing_off_everywhere();
turn_editing_on(mainelement);
turnEditingOffEverywhere();
turnEditingOn(mainelement);
});
return {};
});
});

View File

@ -94,6 +94,16 @@ class inplace_editable implements templatable, renderable {
*/
protected $editable = false;
/**
* @var string type of the element - text, toggle or select
*/
protected $type = 'text';
/**
* @var string options for the element, for example new value for the toggle or json-encoded list of options for select
*/
protected $options = '';
/**
* Constructor.
*
@ -120,6 +130,76 @@ class inplace_editable implements templatable, renderable {
$this->editlabel = $editlabel;
}
/**
* Sets the element type to be a toggle
*
* For toggle element $editlabel is not used.
* $displayvalue must be specified, it can have text or icons but can not contain html links.
*
* Toggle element can have two or more options.
*
* @param array $options toggle options as simple, non-associative array; defaults to array(0,1)
* @return self
*/
public function set_type_toggle($options = null) {
if ($options === null) {
$options = array(0, 1);
}
$options = array_values($options);
$idx = array_search($this->value, $options, true);
if ($idx === false) {
throw new \coding_exception('Specified value must be one of the toggle options');
}
$nextvalue = ($idx < count($options) - 1) ? $idx + 1 : 0;
$this->type = 'toggle';
$this->options = (string)$nextvalue;
return $this;
}
/**
* Sets the element type to be a dropdown
*
* For select element specifying $displayvalue is optional, if null it will
* be assumed that $displayvalue = $options[$value].
* However displayvalue can still be specified if it needs icons and/or
* html links.
*
* If only one option specified, the element will not be editable.
*
* @param array $options associative array with dropdown options
* @return self
*/
public function set_type_select($options) {
if (!array_key_exists($this->value, $options)) {
throw new \coding_exception('Options for select element must contain an option for the specified value');
}
if (count($options) < 2) {
$this->editable = false;
}
$this->type = 'select';
$this->options = json_encode($options);
if ($this->displayvalue === null) {
$this->displayvalue = $options[$this->value];
}
return $this;
}
/**
* Whether the link should contain all of the content or not.
*/
protected function get_linkeverything() {
if ($this->type === 'toggle') {
return true;
}
if (preg_match('#<a .*>.*</a>#', $this->displayvalue) === 1) {
return false;
}
return true;
}
/**
* Export this data so it can be used as the context for a mustache template (core/inplace_editable).
*
@ -141,6 +221,9 @@ class inplace_editable implements templatable, renderable {
'value' => (string)$this->value,
'edithint' => (string)$this->edithint,
'editlabel' => (string)$this->editlabel,
'type' => $this->type,
'options' => $this->options,
'linkeverything' => $this->get_linkeverything() ? 1 : 0,
);
}
}
}

View File

@ -260,6 +260,7 @@ $definitions = array(
'tags' => array(
'mode' => cache_store::MODE_REQUEST,
'simplekeys' => true,
'staticacceleration' => true,
),
// Caches search results.

View File

@ -385,6 +385,7 @@ class core_external extends external_api {
if (!$tmpl || !($tmpl instanceof \core\output\inplace_editable)) {
throw new \moodle_exception('inplaceeditableerror');
}
$PAGE->set_context(null); // To prevent warning if context was not set in the callback.
return $tmpl->export_for_template($PAGE->get_renderer('core'));
}
@ -404,6 +405,9 @@ class core_external extends external_api {
'itemid' => new external_value(PARAM_RAW, 'identifier of the updated item', VALUE_OPTIONAL),
'edithint' => new external_value(PARAM_NOTAGS, 'hint for editing element', VALUE_OPTIONAL),
'editlabel' => new external_value(PARAM_NOTAGS, 'label for editing element', VALUE_OPTIONAL),
'type' => new external_value(PARAM_ALPHA, 'type of the element (text, toggle, select)', VALUE_OPTIONAL),
'options' => new external_value(PARAM_RAW, 'options of the element, format depends on type', VALUE_OPTIONAL),
'linkeverything' => new external_value(PARAM_INT, 'Should everything be wrapped in the edit link or link displayed separately', VALUE_OPTIONAL),
)
);
}

View File

@ -30,6 +30,8 @@
* data-itemid
* data-value
* data-editlabel
* data-type
* data-options
Context variables required for this template:
* none
@ -42,15 +44,19 @@
"component" : "core_unknown",
"itemtype" : "unknown",
"edithint" : "Edit this",
"editlabel" : "New name for this"
"editlabel" : "New name for this",
"type" : "text",
"options" : "",
"linkeverything": 0
}
}}
{{#component}}
<span class="inplaceeditable" data-inplaceeditable="1" data-component="{{component}}" data-itemtype="{{itemtype}}" data-itemid="{{itemid}}"
data-value="{{value}}" data-editlabel="{{editlabel}}">
{{{displayvalue}}}
<a href="#" class="quickeditlink visibleifjs" data-inplaceeditablelink="1" title="{{edithint}}">
{{#pix}}t/editstring,core,{{edithint}}{{/pix}}
<span class="inplaceeditable inplaceeditable-{{type}}" data-inplaceeditable="1" data-component="{{component}}" data-itemtype="{{itemtype}}" data-itemid="{{itemid}}"
data-value="{{value}}" data-editlabel="{{editlabel}}" data-type="{{type}}" data-options="{{options}}">
{{^ linkeverything }}{{{displayvalue}}}{{/ linkeverything }}
<a href="#" class="quickeditlink" data-inplaceeditablelink="1" title="{{edithint}}">
{{# linkeverything }}{{{displayvalue}}}{{/ linkeverything }}
<span class="quickediticon visibleifjs">{{#pix}}t/editstring,core,{{edithint}}{{/pix}}</span>
</a>
</span>
{{#js}}

View File

@ -751,7 +751,7 @@ span.flagged-tag a,
tr.flagged-tag a {color:#FF0000;}
.tag-management-table td,
.tag-management-table th {vertical-align: middle;padding: 4px;}
.tag-management-table span.inplaceeditable.inplaceeditingon input {width: 150px;}
.tag-management-table .inplaceeditable.inplaceeditingon input {width: 150px;}
.tag_feed .media, .tag_feed .media-body {overflow: hidden;}
.tag_feed.media-list .media .itemimage {float: left;}
.dir-rtl .tag_feed.media-list .media .itemimage {float: right;}
@ -2332,27 +2332,34 @@ body.lockscroll {
display: inline-block;
}
span.inplaceeditable.inplaceeditingon {
.inplaceeditable.inplaceeditingon {
position: relative;
}
span.inplaceeditable.inplaceeditingon span.editinstructions {
.inplaceeditable.inplaceeditingon .editinstructions {
margin-top: -30px;
font-weight: normal;
margin-right: -300px;
margin-left: 0;
}
.dir-rtl span.inplaceeditable.inplaceeditingon span.editinstructions {
.dir-rtl .inplaceeditable.inplaceeditingon .editinstructions {
margin-left: -300px;
margin-right: 0;
}
span.inplaceeditable .quickeditlink img {
.inplaceeditable .quickeditlink .quickediticon img {
opacity: 0.2;
}
span.inplaceeditable:hover .quickeditlink img,
span.inplaceeditable .quickeditlink:focus img {
.inplaceeditable a.quickeditlink {
color: inherit;
text-decoration: inherit;
}
.inplaceeditable:hover .quickeditlink .quickediticon img,
.inplaceeditable .quickeditlink:focus .quickediticon img {
opacity: 1;
}
span.inplaceeditable.inplaceeditingon input {
.inplaceeditable.inplaceeditable-toggle .quickediticon {
display: none;
}
.inplaceeditable.inplaceeditingon input {
width: 330px;
height: 16px;
vertical-align: text-bottom;

View File

@ -826,7 +826,7 @@ tr.flagged-tag a {
vertical-align: middle;
padding: 4px;
}
.tag-management-table span.inplaceeditable.inplaceeditingon input {
.tag-management-table .inplaceeditable.inplaceeditingon input {
width: 150px;
}
.path-tag .tag-relatedtags {
@ -2452,19 +2452,58 @@ span.inplaceeditable.inplaceeditingon span.editinstructions {
margin-left: -300px;
margin-right: 0;
}
span.inplaceeditable .quickeditlink img {
opacity: 0.2;
.inplaceeditable {
&.inplaceeditingon {
position: relative;
.editinstructions {
margin-top: -30px;
font-weight: normal;
margin-right: -300px;
margin-left: 0;
}
input {
width: 330px;
height: 16px;
vertical-align: text-bottom;
margin-bottom: 0;
}
select {
margin-bottom: 0;
}
}
.quickediticon img {
opacity: 0.2;
}
.quickeditlink {
color: inherit;
text-decoration: inherit;
}
&:hover .quickeditlink .quickediticon img,
.quickeditlink:focus .quickediticon img {
opacity: 1;
}
&.inplaceeditable-toggle .quickediticon {
display: none;
}
}
span.inplaceeditable:hover .quickeditlink img,
span.inplaceeditable .quickeditlink:focus img {
opacity: 1;
.dir-rtl .inplaceeditable {
&.inplaceeditingon {
.editinstructions {
margin-left: -300px;
margin-right: 0;
}
}
}
span.inplaceeditable.inplaceeditingon input {
width: 330px;
height: 16px;
vertical-align: text-bottom;
margin-bottom: 0;
}
h3.sectionname span.inplaceeditable.inplaceeditingon span.editinstructions {
h3.sectionname .inplaceeditable.inplaceeditingon .editinstructions {
margin-top: -20px;
}

File diff suppressed because one or more lines are too long