Ajax.InPlaceEditor
Controls > Ajax.InPlaceEditor
Introduction
The in-place “text edit” testing allows for Flickr-style AJAX-backed “on-the-fly” textfields.
See the documentation on Ajax.InPlaceEditor and Ajax.InPlaceCollectionEditor
Syntax
new Ajax.InPlaceEditor( element, url, {options});
The constructor takes three parameters. The first is the element that should support in-place editing. The second is the url to submit the changed value to. The server should respond with the updated value (the server might have post-processed it or validation might have prevented it from changing). The third is a hash of options.
The server side component gets the new value as the parameter ‘value’ (POST method), and should send the new value as the body of the response.
Options
Name | since | default | Description |
okControl |
V?? | “button” | What type of ok button to use in edit mode, or none at all (button, link, false) |
cancelControl |
V?? | “link” | What type of cancel button to use in edit mode, or none at all (button, link, false) |
okText |
V1.5 | “ok” | The text of the submit button that submits the changed value to the server |
cancelText |
V1.5 | “cancel” | The text of the link that cancels editing |
savingText |
V1.5 | “Saving…” | The text shown while the text is sent to the server |
clickToEditText |
V1.6 | “Click to edit” | The text shown during mouseover the editable text |
formId |
V1.5 | id of the element to edit plus ‘InPlaceForm’ | The id given to the element |
externalControl |
V1.5 | null | ID of an element that acts as an external control used to enter edit mode. The external control will be hidden when entering edit mode and shown again when leaving edit mode. |
externalControlOnly |
V1.5 | false | Whether or not to disable onclick editing so that only an external control can activate editable mode |
rows |
V1.5 | 1 | The row height of the input field (anything greater than 1 uses a multiline textarea for input) |
onComplete |
V1.6 | “function(transport, element) {new Effect.Highlight(element, {startcolor: this.options.highlightColor});}” | Code run if update successful with server. Also if user cancels the form. See bug. |
onFailure |
V1.6 | “function(transport) {alert(“Error communicating with the server: ” + transport.responseText.stripTags());}” | Code run if update failed with server |
cols |
V1.5 | none | The number of columns the text area should span (works for both single line or multi line) |
size |
V1.5 | none | Synonym for ‘cols’ when using single-line (rows=1) input |
highlightColor |
? | Ajax.InPlaceEditor.defaultHighlightColor | The highlight color. Must be six hex digits, not 3. |
highlightEndColor |
? | ”#FFFFFF” | The color which the highlight fades to. Must be six hex digits, not 3. |
savingClassName |
V1.5 | “inplaceeditor-saving” | CSS class added to the element while displaying “Saving…” (removed when server responds) |
formClassName |
V1.5 | “inplaceeditor-form” | CSS class used for the in place edit form |
hoverClassName |
? | ? | ? |
loadTextURL |
V1.5 | null | Will cause the text to be loaded from the server (useful if your text is actually textile and formatted on the server) |
loadingText |
V1.5 | “Loading…” | If the loadText URL option is specified then this text is displayed while the text is being loaded from the server |
callback |
V1.5 | function(form) {Form.serialize(form)} | A function that will get executed just before the request is sent to the server, should return the parameters to be sent in the URL. Will get two parameters, the entire form and the value of the text control. |
submitOnBlur |
V1.6 | “false” | This option if true will submit the in_place_edit form when the input tag loses focus. |
ajaxOptions |
V1.5 | {} | Options specified to all AJAX calls (loading and saving text), these options are passed through to the prototype AJAX classes. |
The server side component gets the new value as the parameter ‘value’ (POST method), and should send the new value as the body of the response.
Character encoding
The form data is sent encoded in UTF-8 regardless of the page encoding. This is as of the prototype function Form.serialize. More info on here.
If this doesn’t work, you can use iconv as outlined here. Link Dead!
Removing the behavior
To disable the InPlaceEditor behavior later on, store it in a variable like:
var editor = new Ajax.InPlaceEditor('product_1',...);
(... do stuff ..)
editor.dispose();
This way, you can enable and disable " In Place Editing ":http://madrobby.github.com/scriptaculous/in-place-editing at will.
You can also arbitrarily force it into edit mode like so:
editor.enterEditMode('click');
Demo1 (Single Line Edit Mode)
note: the demo doesn’t actually save its changes, as demoajaxreturn.html
is not a valid url.
<p id="editme">Click me, click me!</p>
<script type="text/javascript">
new Ajax.InPlaceEditor('editme', '/demoajaxreturn.html');
</script>
Click me, click me!
Demo2 (Multi Line Edit Mode)
<p id="editme2">Click me to edit this nice long text.</p>
<script type="text/javascript">
new Ajax.InPlaceEditor('editme2', '/demoajaxreturn.html', {rows:15,cols:40});
</script>
Click me to edit this nice long text.
How to specify a different name for the parameter sent to the server
Add a callback function which is supposed to return the parameters that is sent to the server. Like this:
new Ajax.InPlaceEditor('id', 'url', {
callback: function(form, value) { return 'myparam=' + encodeURIComponent(value) }
})
The encodeURIComponent()
makes sure values containing special characters like “&” or “=” don’t cause problems. Use encodeURIComponent()
instead of escape()
from previous examples to get UTF-8 encoded data (and not “malformed URI” errors in Firefox). This function can also be used to pass additional parameters, such as what item or field to edit:
new Ajax.InPlaceEditor('id', 'url', {
callback: function(form, value) { return 'item=123&field=description&myparam='+encodeURIComponent(value) }
})
Note: this parameters name may change (to “parameters” or “with”) before the final release of 1.5.
How to execute custom code using the InPlaceEditor’s callbacks.
new Ajax.InPlaceEditor(id, url, {
callback: function(form, value) { return 'item=123&field=description&myparam='+encodeURIComponent(value) },
onEnterEditMode: function(form, value) { // custom code goes here... },
onLeaveEditMode: function(form, value) { // custom code goes here... }
});
You can find all the possible callbacks by looking for lines in the source like this:
this.triggerCallback('onEnterEditMode');
(Should these callback options be documented in the Options list above?)
How to add a spinning in progress indicator
Get an animated .gif that shows progress (the spinning image used on the Macintosh for example). Add a CSS class for ‘inplaceeditor-saving’ with the spinning image as background. The class will be added to the element during communication with the server and removed afterwards.
<style type="text/css" media="screen">
.inplaceeditor-saving { background: url(/images/wait.gif) bottom right no-repeat; }
</style>
<p id="editme3">Click me, click me!</p>
<script type="text/javascript">
new Ajax.InPlaceEditor('editme3', '/demoajaxreturn.html');
</script>
How to customize the look and feel of the form
Use the id or class (‘inplaceeditor-form’) of the form in a CSS selector to apply the desired style. The text field is always named ‘value’, the ok button is an input with type ‘submit’ and the cancel link is a normal anchor (‘a’) element.
form.inplaceeditor-form { /* The form */
}
form.inplaceeditor-form input[type="text"] { /* Input box */
}
form.inplaceeditor-form textarea { /* Textarea, if multiple rows */
}
form.inplaceeditor-form input[type="submit"] { /* The submit button */
margin-left:1em;
}
form.inplaceeditor-form a { /* The cancel link */
margin-left:1em;
}
How to edit server side formatted text (formatted with eg. textile)
Format the text on the server when the initial page is rendered. Add a new server side action that returns the unformatted text in the reponse and specify the load Text URL? option as a URL to this action. The control will load the text from the server instead of using the text on the page. The action that receives the updated text should save it and return the formatted text as an HTML snippet in the reponse.
Example using Ruby on Rails
#controller
def my_action
#get the text to display initially
@fancy_text = “Some fancy bold and emphasized text.”
end
def update_text
#save your text here, and return the saved value
@fancy_text = params[:value]
render :layout => false, :inline => “<%= textilize( @fancy_text ) %>”
end
def return_unformatted_text
#get your text and return it without the formatting
@fancy_text = “Some fancy edited, bold and emphasized text.”
render :layout => false, :inline => “<%= @fancy_text %>”
end
#view ( of my_action.rhtml )
<%= textilize( @fancy_text ) %>
In-Place edits with select lists
In my (java) application, I have a need for in-place editing in the form of select lists. Given a server-side ajax helper that provides the necessary options for the current user, I was able to write the following:
function setupCategoryEditor(el, url) {
var editor=new Ajax.InPlaceEditor(el, url);
Object.extend(editor, {
createEditField: function() {
var text=this.getText();
var field=document.createElement("select");
field.name="value";
this.editField=field;
this.form.appendChild(this.editField);
new Ajax.Request('/url/to/option/list', {
onSuccess: function(req) {
// Get the text from an XML tag.
var getData=function(el, which) {
stuff=el.getElementsByTagName(which);
return stuff[0].firstChild.nodeValue;
};
var cats=req.responseXML.getElementsByTagName("cat");
$A(cats).each( function(cat, idx) {
var op=document.createElement("option");
op.value=getData(cat, "value");
op.text=getData(cat, "key");
if(window.ActiveXObject) {
field.options.add(op);
} else {
field.appendChild(op);
}
// Select the current item
if(op.text == text) {
field.selectedIndex=idx;
}
});
}
});
}
});
}
Obviously, one could write something smaller with JSON or innerHTML if you’re into that kind of thing.
There is another implementation of " In Place Editor “:http://madrobby.github.com/scriptaculous/in-place-editor for select lists called Ajax. ”color:#aaa"> In Place Select? available here.
You can change getText like so:
getText: function() {
return this.element.childNodes[0] ? this.element.childNodes[0].nodeValue : '';
},
to avoid e.g. “Me & Myself” being represented in the input box as “Me & amp; Myself” (use nodeValue because innerText does not work with Safari when the element is hidden).
- – -
I just added inplace editor inside a sortable list array. I can drag and after, the editor appears, but ideally I would not want that to happen and instead activate the editor specifically, not as a result of dragging my list items. What is suggested here?
I tried using a drag handle, which seemed like an obvious solution, except the editor then shows the span class code as well, which is a caveat I suppose.
Unless I can use a drag handle without the method in example, then that won’t be a good solution.
How else can I drag or how else can I activate the editor besides writing redundant for each instance?
-ms
I found a workaround to use draggable and Ajax.InPlaceEditor at same time. I faced also this problem mentioned above (the ‘mousup’ action at the end of the draggable activates the editor). The solution that I found is to modify control.js to activate InPlaceEditor only on dbclick instead of click. The drawback is that you should deal with navigator compatibilty… So It consists in replacing:
byListeners: { click: 'enterEditMode', keydown: 'checkForEscapeOrReturn', mouseover: 'enterHover', mouseout: 'leaveHover' }
Listeners: { dblclick: 'enterEditMode', keydown: 'checkForEscapeOrReturn', mouseover: 'enterHover', mouseout: 'leaveHover' }
-Tonio
- – -
I just spent a good deal of time figuring that out myself so I’ll share my result:
<style>
div.handle {
display: none;
float: left;
width: 25px;
height: 11px;
margin-top: 2px;
padding-right: 3px;
background: url('/images/drag.gif');
background-repeat: no-repeat;
clear: both;
}
</style>
<ul id="field">
<li id="field_1">
<div class="handle"></div><div class="delete"></div>
<span class="data" id="field_name_1">Brand</span></li>
<li id="field_3">
<div class="handle"></div><div class="delete"></div>
<span class="data" id="field_name_3">SKU Number</span></li>
<li id="field_5">
<div class="handle"></div><div class="delete"></div>
<span class="data" id="field_name_5">Color</span></li>
</ul>
<a id="field_sort" href="javascript:;" onclick="makeSortable('field');">Reorder</a>
<a id="field_donesort" href="javascript:;" onclick="doneSorting('field');" style="display:none;">Done Reordering</a>
<script>
function makeSortable(myid,mytag){
Sortable.create(myid,{ tag: mytag || 'li', handle:'handle'});
setDisplayByClass('handle','inline',$(myid));
$(myid+'_sort').style.display = 'none';
$(myid+'_donesort').style.display = '';
$(myid).setAttribute('reordering','yes');
return false;
}
function doneSorting(myid){
new Ajax.Updater('reply',page,{parameters:'a=reorder&table='+myid+'&'+Sortable.serialize(myid)});
Sortable.destroy(myid);
setDisplayByClass('handle','none',$(myid));
$(myid+'_sort').style.display = '';
$(myid+'_donesort').style.display = 'none';
$(myid).setAttribute('reordering','no');
return false;
}
var list = getElementsByClass('data',$('fields'));
var ipeOptionsStd = {
callback: function(form,value){
var id=form.id;
id = id.substring(0,id.indexOf('-inplaceeditor'));
var t = id.substring(0,id.indexOf('_'));
var c = id.substring(0,id.lastIndexOf('_'));
var i = id.substring(id.lastIndexOf('_')+1,id.length);
return 'a=update&table='+t+'&column='+c+'&id='+i+'&value='+encodeURIComponent(value);
}
};
for(var i=0;i<list.length;i++){
new Ajax.InPlaceEditor(list[i], page, ipeOptionsStd );
}
</script>
Hope that helps, that is a much simplified version. I’ve also done this with tables where each cell is an " In Place Editor ":http://madrobby.github.com/scriptaculous/in-place-editor and the rows are Sortable.
- Colin
Using HTML within the In Place Editor
If you are using Markdown or Textile, you can include HTML within your text. And if you follow the instructions above, you will be able to load this text in from your database with loadTextURL
. But the stripTags function is invoked by the In Place Editor, and that strips out all the code before it can be displayed in the editor field.
To get around this, add the following two extensions anywhere after the rest of the library has loaded. I use the extensions.js method noted elsewhere on this site.
Object.extend(Ajax.InPlaceEditor.prototype, {
onLoadedExternalText: function(transport) {
Element.removeClassName(this.form, this.options.loadingClassName);
this.editField.disabled = false;
this.editField.value = transport.responseText;
Field.scrollFreeActivate(this.editField);
}
});
Object.extend(Ajax.InPlaceEditor.prototype, {
getText: function() {
return this.element.childNodes[0] ? this.element.childNodes[0].nodeValue : '';
}
});
If you do this, make sure that your loadTextURL
does not return entity-encoded text (using htmlentities()
for example) or you will end up with double-encoded text.
In-Place edits only by externalControl
i searched for a way to allow editing of certain fields only by clicking the external control field. currently its implemented to allow a user to click either on the element itself or on the external control element.
to only allow editing by clicking on the external control, simply change a few lines:
Event.observe(this.element, 'click', this.onclickListener);
Event.observe(this.element, 'mouseover', this.mouseoverListener);
Event.observe(this.element, 'mouseout', this.mouseoutListener);
if (this.options.externalControl) {
Event.observe(this.options.externalControl, 'click', this.onclickListener);
Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
}
change to:
if (this.options.externalControl) {
Event.observe(this.options.externalControl, 'click', this.onclickListener);
Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
} else {
Event.observe(this.element, 'click', this.onclickListener);
Event.observe(this.element, 'mouseover', this.mouseoverListener);
Event.observe(this.element, 'mouseout', this.mouseoutListener);
}
maybe someone can add a flag like ‘only External Control ? to add this to the trunk ..
Small extension to editor to add a text in case field is empty.
/*
* InPlaceEditor extension that adds a 'click to edit' text when the field is
* empty.
*/
Ajax.InPlaceEditor.prototype.__initialize = Ajax.InPlaceEditor.prototype.initialize;
Ajax.InPlaceEditor.prototype.__getText = Ajax.InPlaceEditor.prototype.getText;
Ajax.InPlaceEditor.prototype.__onComplete = Ajax.InPlaceEditor.prototype.onComplete;
Ajax.InPlaceEditor.prototype = Object.extend(Ajax.InPlaceEditor.prototype, {
initialize: function(element, url, options){
this.__initialize(element,url,options)
this.setOptions(options);
this._checkEmpty();
},
setOptions: function(options){
this.options = Object.extend(Object.extend(this.options,{
emptyText: 'click to edit...',
emptyClassName: 'inplaceeditor-empty'
}),options||{});
},
_checkEmpty: function(){
if( this.element.innerHTML.length == 0 ){
this.element.appendChild(
Builder.node('span',{className:this.options.emptyClassName},this.options.emptyText));
}
},
getText: function(){
document.getElementsByClassName(this.options.emptyClassName,this.element).each(function(child){
this.element.removeChild(child);
}.bind(this));
return this.__getText();
},
onComplete: function(transport){
this._checkEmpty();
this.__onComplete(transport);
}
});
To use, copy this into a extensions.js and load it in your app after the other scriptaculous stuff.
Here is the style I use with this:
/* ---- InPlaceEditor style --------------------------------------------- */
.inplaceeditor-empty {
font-style: italic;
color: #999;
}
- Pedro
This doesn’t seem to work for me in Prototype 1.6, so I wrote an updated version. (Blog Post).
Ajax.InPlaceEditorWithEmptyText = Class.create(Ajax.InPlaceEditor, {
initialize : function($super, element, url, options) {
if (!options.emptyText) options.emptyText = “click to edit…“;
if (!options.emptyClassName) options.emptyClassName = “inplaceeditor-empty“;
$super(element, url, options);
this.checkEmpty();
},
checkEmpty : function() {
if (this.element.innerHTML.length == 0 && this.options.emptyText) {
this.element.appendChild(
new Element(“span“, { className : this.options.emptyClassName }).update(this.options.emptyText)
);
}
},
getText : function($super) {
if (empty_span = this.element.select(“.“ + this.options.emptyClassName).first()) {
empty_span.remove();
}
return $super();
},
onComplete : function($super, transport) {
this.checkEmpty();
return $super(transport);
}
});
- Nik
Create your own Callback
I wanted to have a custom callback that would fire after the InPlaceEditor form was added to the DOM. onEnterEditMode
fires as soon as you click, but the form doesn’t exist yet.
This works by copying an existing method enterEditMode()
, which builds the form and writes it to the page, into a new method name so that I can edit the real one. In mine, the original is called via __enterEditMode()
and then my custom code is run:
Ajax.InPlaceEditor.prototype.__enterEditMode = Ajax.InPlaceEditor.prototype.enterEditMode;
Object.extend(Ajax.InPlaceEditor.prototype, {
enterEditMode:function(e) {
this.__enterEditMode(e);
this.triggerCallback('onFormReady',this._form);
}
});
Now when I create my InPlaceEditor I just add a the onFormReady
callback as an option:
new Ajax.InPlaceEditor('location', '/change_location',
{
onFormReady: function(obj,form) {
form.getInputs().first().value = '';
}
});
In my case I wanted to clear out the value in the text box so that the user had to type something from scratch. If they cancel it will switch back to the original text.
- Rob
Validation.
Information for implementing validation with ajax." In Place Editor ":http://madrobby.github.com/scriptaculous/in-place-editor can be found here. Link Dead!
suggest
If “cancel control” type was ‘button’ then… javascript error occurs.
new Ajax.InPlaceEditor(node, 'items.aspx', {
cancelControl: 'button'
});
So, I suggest the method modification of ‘handleFormCancellation’.
handleFormCancellation: function(e) {
this.wrapUp();
if (e) Event.stop(e);
return false;
},