I'm looking for some guidance on how to implement a Wufoo-like builder in a Rails 3 application with JQuery on the client side. Basically allow a user to build something (a form in the case of Wufoo) in "offline" mode then save all user edits to the server in one batch by clicking a Save button or similar (e.g. could be an auto-save triggered by the browser every 30s or so).
I'm leaning towards using the HTML5 local storage at this point. The "builder" would essentially store user edits locally in the browser local storage in JSON format. A click on the Save button would then HTTP Post the content of the local storage to the Rails app, again in JSON format. Does this sound about right? Any advice, suggestions?
Some additional considerations/questions:
Note: The following question addresses WYSIWYG form builder specifically and doesn't really provide any good solution. Creating WYSIWYG form builder (á la Wufoo) in Rails
Thanks!
I have been playing around with this a bit at work and I think it could give you a head start on a jquery form builder that knows how to serialize and deserialize itself. I think it serializes the form to a string rather than JSON, but it's a start. http://www.botsko.net/blog/2009/04/07/jquery-form-builder-plugin/
A google search found me sites.google.com/site/daveschindler/jquery-html5-storage-plugin which says that it stores things in HTML 5 storage with a fallback to cookies if the browser does not support it.
Another thought: If the goal in using local storage is for users not to lose work that they don't yet want to publish, another option could be to implement seperate "save" and "publish" buttons so you still save the user's work on the server side but let them retain "drafts" till they're ready to publish, and this way it wouldn't matter which browser or PC they use.
Hope this helps.
Here's the design I ended up implementing. I'm far from having a complete solution but I think it's a good start.
Data Model
In my case users need to be able to build a list of tasks where tasks can have different types and therefore attributes. Tasks can also embed additional objects. Similar to a form builder in a sense although I'm dealing with a deeper hierarchy of nested objects. The key here is to make sure that your back-end application only exposes objects that pertain to your application domain (in the sense of Domain Driven Design) so that your client-side code doesn't spend time refactoring data after deserializing it from a server call and before serializing it in preparation for a Save. To that extent I had to make a few changes to my server-side presentation layer but as a result I think my client-side code is cleaner and more focused on processing actual user events.
Data Serialization
I chose JSON as the data-interchange format. On the client-side I have two functions that handle the data serialization and deserialization. The implementation is pretty straight forward (in part thanks to some of the changes I made to expose domain model objects). I pasted a simplified version below. Only challenge was that the _method parameter used by Rails to handle PUT requests does not seem to work with a JSON Content-Type. See Using HTTP PUT to send JSON with Jquery and Rails 3
var todoList = {};
$.getJSON("/users/123/todolists/456.json", function(data) {
loadTodoList(data);
...
});
function loadTodoList(data) {
todoList = data.todoList;
}
function saveTodoList() {
$.ajax({
type: 'POST',
url: "/users/123/todolists/456",
data: JSON.stringify({ todoList: todoList }),
contentType: 'application/json',
dataType: 'script', // could be "json", "html" too
beforeSend: function(xhr){
xhr.setRequestHeader("X-Http-Method-Override", "put");
}
});
}
On the server-side, Rails makes it easy to handle JSON too (serialization and deserialization of JSON is performed automatically and transparently by the framework). I just overrode the to_json() method on my TodoList model to avoid passing back and forth useless data (e.g. create_at, modified_at attributes). Also had to make sure to include all nested objects when fetching my top-level object (i.e. TodoList).
# TodoListsController
def show
@todolist = TodoList.find_by_id(params[:id], :include => [:tasks, ...])
respond_to do |format|
format.json do
render :json => @todolist.to_json
end
end
end
# TodoList model
def to_json
super(:only => :name,
:include => { :tasks => { :only => [:name, :description, ...],
:include => ... }})
end
Client-side persistence
The goal here is to avoid accidentally losing user edits that haven't been saved. So far I'm directly using the HTML5 local storage (localStorage variable) but ultimately will be looking for a jQuery plugin that automatically handles falling back on cookie storage if HTML5 is not supported.
Dynamic HTML Generation
I'm relying on jQuery Template to generate HTML. The primary function of the builder is to dynamically generate HTML so this plugin comes in very handy. I've defined templates for all building blocks of my todo list model (e.g. tasks, notes, ...). The templates are invoked whenever new instances of these objects are created and need to be rendered.
I think this lays out most of the foundation. The rest is mostly hardcore Javascript to handle all user interactions with the form/todoList builder.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With