27 February 2011
Ajaxy Edit-In-Place with Autocomplete
I’ve been working on creating a slick UI for WikiGini, a new genealogy project I’m building. As part of that I’m using the “Best In Place” jQuery plugin for in-place editing, and the corresponding gem for Rails 3. I’ve also been using the jQuery Autocomplete plugin to provide an autocomplete drop-down as you type. Today I managed to marry the two, for a rather spectacular result.
Here’s how it goes. First, we add this JavaScript code to our page.
$( function() { /* Initialize a custom form type "autocomplete" for best_in_place */ BestInPlaceEditor.forms.autocomplete = { activateForm : function() { var output = '<form class="form_in_place" action="javascript:void(0)" style="display:inline;">'; output += '<input type="text"'; output += this.inner_class ? ' class="' + this.inner_class + '"' : ''; output += this.options.autocomplete ? ' data-autocomplete="' + this.options.autocomplete + '"' : ''; output += this.options.id_element ? ' data-id_element="#' + this.options.id_element + '"' : ''; output += ' />'; output += this.options.id_element ? '<input type="hidden" id="' + this.options.id_element + '" name="' + this.options.id_element_name + '" />' : ''; output += '</form>'; this.element.html( output ); this.element.find( "input" )[0].select(); autoCompletePerson( null, this.element.find( 'input' )[0] ); this.element.find( "form" ) .bind( 'submit', {editor: this}, BestInPlaceEditor.forms.autocomplete.submitHandler ); this.element.find( "input" ).bind( 'blur', {editor: this}, BestInPlaceEditor.forms.autocomplete.inputBlurHandler ); this.element.find( "input" ).bind( 'keyup', {editor: this}, BestInPlaceEditor.forms.autocomplete.keyupHandler ); this.element.find( "input" ).bind( 'keypress', stopSubmitOnEnter ); }, getValue : function() { return this.sanitizeValue( this.element.find("input")[1].value ); }, inputBlurHandler : function(event) { event.data.editor.abort(); }, submitHandler : function(event) { event.data.editor.update(); }, keyupHandler : function(event) { if ( event.keyCode == 27 ) { event.data.editor.abort(); } } } /* Activate in-place ajaxy editing with best_in_place */ $( ".best_in_place" ).best_in_place() } ); /* Activate autocomplete plugin for specified element */ function autoCompletePerson( index, element ) { $( element ).autocomplete( { minLength: 2, select: function( event, ui ) { var $autoCompleteField = $( this ); $( $autoCompleteField.data( 'id_element' ) ).val( ui.item.id ); $( $autoCompleteField[0].form ).submit(); } } ); } /* prevent form from being submitted on "enter" keypress */ function stopSubmitOnEnter(e) { var eve = e || window.event; var keycode = eve.keyCode || eve.which || eve.charCode; if (keycode == 13) { eve.cancelBubble = true; eve.returnValue = false; if (eve.stopPropagation) { eve.stopPropagation(); eve.preventDefault(); } return false; } }
The first block you see is extending the capability of Best In Place with a custom type of form, “autocomplete”. You can look in the best_in_place.js
file to see how this compares to the implementations of the other form types.
Next, we activate the Best In Place editor with $( ".best_in_place" ).best_in_place()
.
Then we declare a function autoCompletePerson()
that is called from our Best In Place code up above. This function initializes the Autocomplete plugin on the input element created when Best In Place creates the editing form.
Finally, we declare a function stopSubmitOnEnter()
, which is bound to the input element to prevent the form from being submitted when the user presses the enter key. This forces them to choose an element from the drop-down.
Then on the Rails side, we initialize our Best In Place element in the “show.html.erb” file:
<%= best_in_place( @person, :father_id, { :type => :autocomplete, :nil => '<span class="nil">Click to add father</span>', :path => update_father_person_path, :value => @person.father ? @person.father.name : '', :autocomplete => autocomplete_person_name_people_path, :id_element => "person_father_id", :id_element_name => "person[father_id]" } ) %>
Here we’re using the standard Best In Place Rails gem features as described in Bernat Farrero’s blog post, as well as a custom :value
, and several custom elements that will be used by the JavaScript code we discussed above to create the edit-in-place form – :autocomplete
, :id_element
, and :id_element_name
.
And that’s it! The modifications I made to the Best In Place code allow for the custom elements to be specified through the Rails helper, and places them in the this.options
array in the JavaScript, so they can be used in our custom Best In Place “autocomplete” form type.
I had to make some updates to the Best In Place code, which you can find on my Github fork of the project.
The code here has been simplified from the actual code in the application, so leave a comment if something is missing or confusing.
I should also note that the code given has an additional feature, which may or may not be desired by the reader. The Rails model I’m working on here is called Person, and each Person can have an associated “father”, which is a one-to-one relationship with another Person. This autocomplete code doesn’t just autocomplete on the names of People in the database and store the name string. It autocompletes on the names of the people in the database, and when you select the person from the dropdown it actually submits the ID of that person, so that the proper association can be made on the model. This is both awesome, and extra complicated if all you want to do is autocomplete on a string. To do that, you’d just strip out all of the id_element
related code and it should work for you.