This is a neat trick I picked up reading some of Tom’s code.
This is useful when you’ve got a form that renders differently depending on the state of some of the values in the form. For example, when the user selects “United States” as the country, you may wish to add a “state” field, rename “Postal Code” to “Zip Code”, and modify the list of shipping options in a select box.
You could use lifecycles to make this a two stage form, but this is a neat trick you can use if you want to keep it all in a single form.
Write your view normally using one of the many different options Hobo gives you. In this example, I’m going to extend the form:
<extend tag="form" for="Order">
<do part="order-part">
<old-form>
<field-list: fields="salutation, first_name, last_name, suffix, email_address, phone_number, country, shipping_method">
<shipping-method-view:>
<select-one include-none="&false" options="&(this_parent.country.nil? ? [] : ShippingMethod.find_all_by_country_id(this_parent.country.id))"/>
</shipping-method-view:>
</field-list:>
</old-form>
</do>
</extend>
In this case, I’ve created an order that varies the options available in the shipping-method drop down depending on the country. You’ll also see that I’ve wrapped the whole form in a part.
In your controller (replace Order with your model):
def edit
self.this = Order.new(params[:order]) if params[:order]
hobo_show do
hobo_ajax_response if request.xhr?
end
end
def new
hobo_create(Order.new(params[:order])) do
hobo_ajax_response if request.xhr?
end
end
In your application.js (replace order-part with your part and order_country with your select’s class):
Event.addBehavior({
"select.order_country:change": function(ev) {
Hobo.ajaxRequest(window.location.href, ['order-part'], {
params: Form.serialize(this.up('form')),
method: 'get',
message: 'Please wait...'
});
}
});
There you go: fancy custom AJAX, without really writing any AJAX code.
How it works:
Normally, when you hit submit on a new item, it POSTs to a URL like /orders. We’ve added a lowpro javascript watcher that triggers on the CSS selector “select.order_country:change” that submits the form as a ajax request to the current location (ie, /orders/new).
In the edit controller action, instead of looking up the current value in the database, we create it from the parameters passed in. We don’t save it, though! Then we invoke the standard hobo ajax mechanism that renders our part. Most standard hobo actions will do this automatically for you, but the standard hobo_show and hobo_\new do not, so we add it in ourselves.
User contributed notes
-
On August 04, 2009 Bryan Larsen said:
OK, here's a working controller:
def edit
self.this = User.new(params[:order]) if params[:order]
hobo_show do
@order = this
hobo_ajax_response if request.xhr?
end
end
And I had to modify the view:
<form with="&@order" method="put">
This mostly makes sense, except the method="put" is an ugly hack. -
On August 18, 2009 Bryan Larsen said:
Note that the reason why the above edit works is because the context does not have an id. If the context does have an ID, you trigger Hobo's magic "set up the context for a part" code. -
On June 18, 2010 cbenedict said:
After much digging through the Hobo framework, I created a patch that makes the edit view work with the original code in this recipe. This patch was coded against Hobo 1.0.1. Place "require 'template_environment_patch'" in your environment.rb file. Then add the following into a template_environment_patch.rb file located in lib:
module Hobo::Dryml
TemplateEnvironment.class_eval do
def typed_id_use_this_for_edit_action(object=nil, attribute=nil)
if object.nil?
# nothing passed -- use context
if this_parent && this_field && !this_parent.respond_to?(:member_class)
object, attribute = this_parent, this_field
else
object = this
end
end
id = if (typed_id = object.try.typed_id)
typed_id
elsif object == @this
"this"
end
# For edit actions, use the current context
id = "this" if self.params[:action] == "edit"
attribute ? "#{id}:#{attribute}" : id
end
alias_method :typed_id, :typed_id_use_this_for_edit_action
end
end
On August 04, 2009 Bryan Larsen said: