How to write a widget
Talia uses a system to deploy additional user interface elements, called "widgets". A basic widget consists of two files:
- A ruby (.rb) file, containing the logic for the widget
- A rhtml template file, containing the content that will be rendered
The convention is that if the widget is called "MyCoolWidget", then the ruby file must be called my_cool_widget.rb and the rhtml file must be called _my_cool_widget.rhtml
The ruby file must contain a class called MyCoolWidget which must be a subclass of the Widget class.
File locations and installation
Widgets are stored in RAILS_ROOT/app/views/widgets/<widget_name>', where widget_name` must correspond to the "underscore" version of the widget name.
Creating a new widget
The widget system provides a generator, a new widget is created with
script/generate widget MyCoolWidget
Rendering a widget
A widget can be rendered from any rhtml template by calling the widget(widget, options => {}) helper. The first parameter must be a symbol that corresponds to the widget's name. Optionally, a hash with options can be given - the keys of the hash should be symbols.
The options, if present, will be passed to the widget logic.
<%= widget(:my_cool_widget, :title => "Cool", :text => "widget" %>
The Widget logic
The ruby file of the widget must contain a class that is a subclass of Widget and is named according to the rule above. This class should not overwrite the initialize method.
Instead, the class must provide a method called widget_setup, that takes no parameters. This method will be called immediately before the widget is rendered.
Note: Maybe we should also provide a widget_init method that is called while the widget object is initialized, but before the options are set up.
Passing parameters to the logic
The options that are passed to the #widget helper will be available as instance variables when the widget_setup method is called.
Note: The options will only create instance variables if the variable did not already exists. If the options try to "overwrite" an existing variable, an error will be thrown.
Note: Check if there is a security concern with this.
Example:
class MyCoolWidget < Widget def widget_setup widget_title = @title # Get stuff from the option widget_text = @text ... end end
Passing parameters to the rhtml template
All instance variables set in the widget object will be available to the widget. See the template section on how to access them.
The Widget template
The widget template is a normal rhtml template (Note: Should also support other templating engines). The widget engine will render this template as a partial.
It will also set the variable widget_name to the widget object that contains the logic. The object has the following properties:
- Obviously, you can call any method you defined in the widget object
- All instance variables that existed at the end of the #widget_setup call will be made available as readers.
Thus, a template for the my_cool_widget could look like this
<p><%= my_cool_widget.title %></p> <!-- This reads the title variable --> <p> <%= my_cool_widget.calculate_body %> <!-- Calls a method on the object --> </p>
Note
The mechanism above means that even for a widget with no logic, all the options that are passed in the #widget call will be available in the template.
Note
Regardless of the template, each widget will be wrapped in a <div> tag. The tag will be assigned the css class widget and the id of the widget. (See below for widget ids)
Widget configuration
Sometimes widgets will need extensive configuration, most of which is fairly static (e.g. tabs for tabbed navigation). In order not to put all this into the template, the options can be provided in an .yml file.
The file-base configuration can be used by given the filename in the yml_config option:
widget(:my_cool_widget, :yml_config => "my_cool_config")
This will read the configuration from the file my_cool_config.yml in the my_cool_widget directory
Conflicts: If options are given both in the code and the config file, the options from the code will override the config file options.
Widget state information
Widgets may need to maintain a state (e.g. the current page of a paginated view) across multiple page requests. The widget state information is stored in the current Rails session, and is also stored as a hash:
- Permanent: These things are stored permanently in the session hash, and will live as long as the current session lasts.
- On-page: This state information will only last while the user navigates the current page. If the widget is reloaded "fresh", the on-page state will be empty. This is available for things like pagination, which must have a state, but should still return to an initial state on the next use.
The state is provided within the widget object by the two protected methods permanent_state and on_page_state. Each will return a hash with the state information when called.
on_page_state['current_page'] = 2 permanent_state['current_tab'] = 'hello'
The state will be stored per widget instance. If the user does not access the state hash, nothing will be stored in the Rails session.
Widget instance names
There can be multiple instances of the same widget on the same page. In order for each instance to have it's own state, it must be assigned an unique id. This can be done by passing the id option to the widget.
widget(:my_cool_widget, :id => 'cool1') widget(:my_cool_widget, :id => 'cool2')
The given id will be used to identify the widget instance, and should be unique. If no id is given, default is used as the id. Widgets with the same id are completely equal.
Lifetime of the On-Page state
The on-page state is always deleted when a page is loaded directly. It is only kept around during widget callbacks (see below), so it's really only useful together with the callback mechanism.
Widget Callbacks
The widgets can also be used for user interaction. A widget can call back to the server, and re-render itself. This can be accomplished using the widget_callback helper.
<%= widget_backlink(:add_item, :item => "new item") %>
The system will then work like this:
- The helper place a backlink into the page
- When clicked:
- A widget object is created for the current instance
- The state information is set
- The method given in the backlink ("add_item") is called, and the options are passed to this method
- The widget is re-rendered, including the before_render call before rendering
This mechanism works with AJAX, in which case the result of the widget rendering will replace the widget instance's <div> tag. If the JavaScript? is not active, the whole page will be rerendered, include all other widgets included in the page. The other widgets will also be initialised with their on-page-state still present.
Callback Forms
Note: Not completely specified yet. The callback can also take user input, using a callback form.
<%= widget_back_form(:add_user) %> <!-- form fields --> <%= widget_back_form_end %>
This works exactly like the "normal" backlink, only that the values of the form fiels are also passed to the form method (here: "add_user") as options.
Widget helpers
There is one global helper, which can be used in any template:
- widget(widget_name, options = {}) - Renders a widget with the given options
The rest of the helpers can only be used within widget templates:
- widget_partial(partial_name, options) - Renders a partial located in the widget's directory. The options are passed on to the render call.
- widget_callback(text, method, options = {}) - Creates a callback button that calls the given method on this widget instance, passing the given options
- widget_back_form(method, option = {}) - Starts a form that will call back to the given methods with the given options. The results of the form will also be passed in, overriding the options in the code in case of conflicts
- widget_back_form_end - Ends the form
