HOWTO Create complex editable fields with JEditable and make it sending all form content.
Problem description
If you are using JEditable, you probably know the nice tutorial and all its very useful comments to create custom editable fields : Create custom input types with JEditable
--
But JEditable has a point that may be blocking in more complex applications: it only returns one input field to the submitted form/request in charge of editing the record.
And some time this simply does not do the trick.
In a lot of cases you will have to use complex widgets:
- To edit complex records, like user information having name, first name, id, emails and others data
- To edit list of records having more than one information by record
- To have sophisticated and user friendly widgets like colopicker, datepicker and others
- And much more cases...
Sometimes submitting all these data in only one variable/input element is just no really possible. More, simply you don't want to write a complex javascript function that have to merge all your widget fields into one unique input type, and then decode it on the server side.
Also, when developing with Plone and using the Zope :records functionality, which need to return more than one input field, JEditable default behaviour does no more do the trick.
Below is a plugin example and a little bit modified version of JEditable source code that allow to create such complex fields and send all their form components (select, input, textarea, ...) at once to the server.
The proposed JEditable modification to satisfy this need could have been avoided, but I think it provides a more flexible way to do things.
Creating the jEditable custom input type
First, we need to create the new JEditable input type in charge of displaying the editable field.
/*
* Creating the custom JEditable input type
*/
$.editable.addInputType('widget_input_type_name', {
/*
* Element is a function that will return
* a dummy input type, as the real one will be created using
* the plugin parameter.
*/
element : function(settings, original) {
var id = original.getAttribute('id');
var widget = '<' + 'input id="' + id + '_edited" type="hidden" value="" ' + '/>';
var input = $(widget);
$(this).append(input);
return(input);
},
The element parameter is said to be dummy in the code comment because form data won't be returned using this field.
We will let JEditable returning it, but our form data won't be saved in it:
- jEditable will use it as the real form element to return.
- The plugin will use it as a marker to append the real widget form fields (understand input fields).
Second, we are now going to use the plugin parameter provided by JEditable to customize your plugin.
Below is an extract of jEditable code when creating the form used to edit the data:
content.apply(form, [input_content, settings, self]);
input.attr('name', settings.name);
/* add buttons to the form */
buttons.apply(form, [settings, self]);
/* add created form to self */
$(self).append(form);
/* attach 3rd party plugin if requested */
plugin.apply(form, [settings, self]);
/* focus to first visible form element */
$(':input:visible:enabled:first', form).focus();
/* highlight input contents when requested */
if (settings.select) {
input.select();
}
We can observe that the plugin function is called once the form is created to allow any kind of customization.
We will use this functionality to append the real widget fields.
Below is the code in charge of this.
plugin : function(settings, original) {
// I always use try/catch to display javascript errors, this ease debugging and
// prevent from forgotten errors that you browser does not prompt
try {
// Save form for future reference
var c_form = this;
// The strip() function is used to remove spaces at the begin and the end of
// the string.
// The type parameter is used in this example to select the widget to use.
// IE: date picker, color picker, other widget.
// The id parameter is also kept for future use.
// So the data to edit is in a span element with 2 classes names and an id
//
var plugin_data = {
type: strip(original.getAttribute('class').replace('editable', '')),
id: original.getAttribute('id')
};
// Update data sent when processing the widget
//
// This is a special behaviour of my plugin. You may not need it.
// I leave it in this tutorial, because it may help you to accomplish the
// similar tasks.
// When editing some fields, I need data from others fields/data in the page,
// for example to limit city list based on previouly selected state or country.
// The updatePluginData is in charge of adding these special filters parameters
// to the widget processing.
plugin_data = updatePluginData(original, plugin_data, settings);
// The the widget content (IE, html elements like textarea, input, select and others) is created
// by querying an ajax method.
$.ajax( {
type : 'GET',
url: '/widget_creation_url',
data: plugin_data,
async: true,
success: function(data){
var id = original.getAttribute('id');
var etype = original.getAttribute('class');
$('#' + id + '_edited').before($(strip(data)));
// Focus
$(':input:visible:enabled:first', c_form).focus();
// ESC and ENTER on SPAN container
$('#' + id).keydown(function(e) {
//e.stopPropagation();
if (e.keyCode == 27) {
e.preventDefault();
var reset = $.editable.types[settings.type].reset || $.editable.types['defaults'].reset;
reset.apply(c_form, [settings, original]);
}
// Do not validate with enter on textarea
// Should test
var areas = $('textarea', '#' + id);
if (e.keyCode == 13 && areas.length == 0) {
e.preventDefault();
c_form.submit();
}
});
}
});
} catch(err) {
alert('plugin: ' + err);
}
}
About the processing of the retieved data that is the HTML code for the plugin edition:
- The HTML code is added before the official input element used by jEditable.
- The Focus action is used to give the focus to the first form field of the widget that is visible.
Some hidden input elements could exist. - As we are using complex widgets, having multiple input, select, textarea and others html elements the ESC and ENTER keys are mapped on the span container.
First, because these new HTML elements just created have not been key-mapped by jEditable.
Second, because jEditable only map the official input element. - ENTER key have a special handling to take care of textarea elements contained in widget.
It is not processed if widget contains some, because this would have prevented from adding a new line.
The complete Javascript code for this
Attaching the widget to the editable data
Third, we need to attach this new input type to our editable data.
var params = {
placeholder: '-----',
tooltip : 'Double click to edit...',
type : 'widget_input_type_name',
event : "dblclick",
width : 120,
onblur : 'submit',
// Here is the second real trick of this tutorial.
// We are using a custom submitdata function to submit all form parameters
submitdata : function(original, settings) {
try {
// Here is the only one line of code that does the job: retrieving all form parameters
// and sending them to jEditable
var data = $(this).formToArray(true);
// If this field depends on other parameters, get these
//
// Like with the previouly seen updatePluginData() function
// I allow here to retrieve other data on page that are not part
// of the currrent form field to submit them with the form.
//
// This is not needed in most cases, but it show how you could do it
// if you need this functionality.
updateSubmitData(this, data);
} catch(err) {
alert('submitdata: ' + err);
}
},
callback : function( result, settings) {
// Do what you want here with the data returned after processing the form data
// You don't need to update the data content, jEditable does it as usual.
return true;
},
indicator : '<' + 'img src="/spinner.gif" alt="loading..." title="loading...">'
};
// Then we associate the editable plugin to the editable elements in page
// Here we suppose these elements have a class attribute with 'editable' value
// value to edit
$('.editable').editable('update_url', params);
The action of submitting all form data to jEditable is done by this line
var data = $(this).formToArray(true);
This is done using the jQuery.form plugin that must be included in you HTML document.
About the callback function:
In my use case I use it to update others data in current page that are depending of current data value.
Typically in a spreadsheet working with DataTables and KeyTable.
For example to update the sum value of a table cell when edited data was a cell which value was part of this sum.
Make JEditable submitting complete form fields
Finally, we have to make jEditable processing these form attributes.
This is done by updating the way jEditable manage the submitdata function.
That's here we have to patch a little jEditable code.
/* check if given target is function */
if ($.isFunction(settings.target)) {
var str = settings.target.apply(self, [input.val(), settings]);
$(self).html(str);
self.editing = false;
callback.apply(self, [self.innerHTML, settings]);
/* TODO: this is not dry */
if (!$.trim($(self).html())) {
$(self).html(settings.placeholder);
}
} else {
/* add edited content and id of edited element to POST */
//
// Here comes the updated management of jEditable submitdata behaviour
//
var submitdata = {};
/* First, add extra data to be POST:ed */
if ($.isFunction(settings.submitdata)) {
submitdata = settings.submitdata.apply(self, [self.revert, settings]);
} else {
submitdata = settings.submitdata;
}
if(!submitdata)
submitdata = {}
// Then, add jEditable usual parameters
// If submitted data are passed as an array of dictionaries (hash table)
// This is what jquery.forms return in our plugin submidata function
// add default jEditable input parameters as dictionaries
if(submitdata.constructor == Array) {
submitdata.push({name: settings.name, value:input.val()});
submitdata.push({name: settings.id, value: self.id});
/* quick and dirty PUT support */
if ('PUT' == settings.method) {
submitdata.push({name:'_method', value: 'put'});
}
submitdata = $.param(submitdata);
}
// The submitdata is supposed to be an hash table (dictionary)
else {
submitdata[settings.name] = input.val();
submitdata[settings.id] = self.id;
/* quick and dirty PUT support */
if ('PUT' == settings.method) {
submitdata['_method'] = 'put';
}
}
The patched code allow to jEditable to manage submitted data as hash tables. This could have been avoided, but it makes easier to send optional parameters.
And, that's all folks. You should now be able to manage complex widgets and retrieve the full form data on the server side.
See it in action
Unfortunatly, I cannot give you access to a real and really great live example.
Nethertheless, below is a simple HTML page that you can test.
| Fichier attaché | Taille |
|---|---|
| jEditableSubmitFullForm.tar_.gz | 52.48 Ko |
Fields still visible after editing
Hi Gaël,
Thanks very much for this that's great
I'm using firefox 3.6 and after editing and clicking out of the controls the fields remains visible
Can you help?
Thanks
Rida
JEditable and multiple fields
Hi,
I'm interested in using this method to generate and submit a form with multiple fields including text inputs and selects. Can you explain a bit how to use your method to do this? Where in the code do I tell JEditable what types of inputs to generate and submit?
Example now available
Hi Buken,
I have added an example in attachement.
It has been tested to work with firefox directly from the filesystem (not tested behind a HTTP server, but it should works too).
The example provides the patched jEditable plugin and the full javascript used to show how to submit the full jeditable form fields.
Of course as it works on the filesystem, returned data is dummy.
Test it by opening the file jeditable-full-form-test.html
Tell me if you have any issue with this example.
Hope it should help.
With kind regards,
Gaël,
Example now available
Hi,
In the jeditable-full-form-test, could it be there is no submit button to actually send the data?
Thanks,
Koen