Stateful Widgets
Stateful widgets freeze themselves to the session and automatically restore their state in the next request. State here means
- structure – where each widget preserves its children – and
- instance variables – which survive between requests
This can be very helpful when building sophisticated widget trees, like forms and wizards with workflows. However, often stateless widgets will fit your needs and involve less internal complexity.
Example?

Why not use that form.
We have a container widget that keeps the upload fields, one widget instance per field.
When clicking “Attach another file” we trigger an event and add another upload field in the container.
During the whole process, the widget developer doesn’t care about state-housekeeping at all.
class FormController < ApplicationController def index use_widgets do |root| root << widget('form/base', 'uploads') end
We add stateful widgets to our controller with #use_widgets in an action. These #use_widgets blocks are invoked only once per session, once called, stateful widgets will stick to root till the session is cleared.
It is also possible to add stateless widgets here, they won’t survive the next request, though.
Adding widgets dynamically
The Form::Base widget initially adds one upload field and renders.
module Form class Base < Apotomo::StatefulWidget responds_to_event :addField, :with => :add_field # triggered when "Attach another file" is clicked. def display # the initial state. self << widget("form/upload_field", 'upload_1') render end def add_field self << widget("form/upload_field", 'upload_2') new_upload_markup = self['upload_2'].invoke # render the new widget. render :js => "Element.insert('#{widget_id}', { below: '#{new_upload_markup}' });" end
This looks worse than it is!
In #add_field we just add another stateful widget to the container, render that new kid and send back a JavaScript call to insert the new upload field under the first.
Keeping instance variables

A really handy feature with Statefuls is that they restore their instance variables in each request.
Say we wanted to display the elapsed time between the first rendering of our form widget and the time we added a new field. An absolutely useless requirement for a form, isn’t it?
Anyway, we’d change the code like this.
def display # the initial state. @created_at = Time.now # save the time we were rendered the first time. self << widget("form/upload_field", 'upload_1') render end def add_field self << widget("form/upload_field", 'upload_2') new_upload_markup = self['upload_2'].invoke # render the new widget. new_upload_markup += "Added after #{(Time.now - @created_at)} secs" # We still have @created_at. render :js => "Element.insert('#{widget_id}', { below: '#{new_upload_markup}' });" end
Notice how we define @created_at in the initial state. As time and requests go by, there might be a click on "Attach another file" which triggers #add_field.
Each time an upload field is added, we compute the elapsed time. The ivar `@created_at` is around as if there had been no request at all!
Discussion

Although there might be multiple requests between the initial rendering of the form and the click on “Attach another file” the whole widget tree starting at 'uploads' will remain the same. If you add 20 new fields, they will still be there, even after 200 requests.
Stale session while developing statefuls
When developing statefuls you often want to flush the widget tree, as classes, initial structure or content changed.
- Add
?flush_widgets=1to your url to instruct Apotomo to flush the existing widget session at request start. - You might also fire
$ rake db:sessions:clear
Watch out!
We got two limitations when using statefuls.
- Only add them inside a controller action in a
#use_widgetsblock - Don’t try to attach a stateless widget to a stateful, that will crash
- Remember to switch to another session store when using multiple stateful widgets, otherwise you will experience the infamous
CookieOverflowErroras all those frozen widgets burst the session.
Hey- that were three!
[cookie overflow] [benchmarks stateful <-> stateless]
