NAME
Rose::DBx::Object::Renderer - Web UI Rendering for Rose::DB::Object
SYNOPSIS
use CGI;
my $query = new CGI;
print $query->header();
use Rose::DBx::Object::Renderer;
# Load a database called 'company', which has two tables 'employee' and 'position' where employee has a position
load_database('company', {db_username => 'root', db_password => 'root'});
# Render a form to add employees
Company::Employee->render_as_form();
# Load an object
my $e = Company::Employee->new(id => 1);
$e->load;
# Render a google map for the address column
print $e->address_for_view();
# Render a form with using the default template with custom fields
$e->render_as_form(
template => 1,
fields => {'hobby' => {required => 1, options => ['Coding', 'Reading', 'Cooking']}},
);
# Render a table
Company::Employee::Manager->render_as_table();
# Render a table for all the employees who love to code with create, edit, and delete access
Company::Employee::Manager->render_as_table(
get => {query => [hobby => 'Coding']}
order => ['first_name', 'email', 'address', 'phone'], # specify the column order
create => 1,
edit => 1,
delete => 1,
);
# Render a table with search and use a custom template
Company::Employee::Manager->render_as_table(
get => {require_objects => [ 'position' ]},
searchable => ['first_name', 'last_name', 'position.title'],
template => 'custom_template.tt'
);
# Render a menu
my $menu = Company::Employee::Manager->render_as_menu (
order => ['Company::Employee', 'Company::Position'], # the classes
output => 1 # store the rendered menu
);
print $menu->{output};
# Render a pie chart
Company::Employee::Manager->render_as_chart(
type => 'pie', # type of the chart
values => ['Coding', 'Cooking'], # the values to compare
column => 'hobby', # in which column
);
# Render a bar chart
Company::Employee::Manager->render_as_chart(
type => 'bar',
title => 'The Employee Bar Chart', # set the title
description => 'A useful bar chart.', # add some description
columns => ['salary', 'tax'], # the columns to compare
objects => [1, 2, 3] # for which objects
);
DESCRIPTION
Rose::DBx::Object::Renderer generates web UIs for Rose::DB::Object. It encapsulates conventional web behaviours in the generated UIs as defaults. For example, email addresses are by default rendered as mailto links in tables and appropiate validation is enforced automatically in forms. These behaviours are highly configurable and extensible.
Renderer integrates CGI::FormBuilder to generate forms and Plotr to render charts. Template::Toolkit is used for template processing, however, Renderer can generate a default set of UIs without any templates. Moreover, UIs are generated dynamically, in other words, no physical files are created.
RESTRICTIONS
The database table must follow the conventions in
Rose::DB::Object.Support for tables with multiple primary keys is limited.
CONFIGURATION
$Rose::DBx::Object::Renderer::CONFIG is an exported hash which defines many settings in Renderer.
Database Connection
The load_database method by default uses the settings defined in $Rose::DBx::Object::Renderer::CONFIG to connect a database.
# Use the DBD for PostgreSQL (defaulted to 'mysql')
$Rose::DBx::Object::Renderer::CONFIG->{db}->{type} = 'Pg';
$Rose::DBx::Object::Renderer::CONFIG->{db}->{port} = '5543';
$Rose::DBx::Object::Renderer::CONFIG->{db}->{username} = 'admin';
$Rose::DBx::Object::Renderer::CONFIG->{db}->{password} = 'password';
# Change the Rose::DB::Object convention such that database table names are singular
$Rose::DBx::Object::Renderer::CONFIG->{db}->{tables_are_singular} = 1;
Paths
The default TT INCLUDE_PATH is './template', which can be changed in:
$Rose::DBx::Object::Renderer::CONFIG->{template}->{path} = '../templates:../alternative';
Renderer also needs a directory with write access to upload files. The default upload path is './upload'.
# Change the upload directory
$Rose::DBx::Object::Renderer::CONFIG->{upload}->{path} = '../uploads';
# Change the url for the upload directory
$Rose::DBx::Object::Renderer::CONFIG->{upload}->{url} = '../uploads';
Default Settings for Rendering Methods
We can modify the default settings of the rendering methods, for example:
# Change the default form template
$Rose::DBx::Object::Renderer::CONFIG->{form}->{template} = 'custom_form.tt';
# Change the default number of rows per page to 25 in tables
$Rose::DBx::Object::Renderer::CONFIG->{table}->{per_page} = '25';
# Use 'ilike' to perform case-insensitive searches in PostgreSQL
$Rose::DBx::Object::Renderer::CONFIG->{table}->{search_operator} = 'ilike'; # defaulted to 'like'
# Keep old upload files
$Rose::DBx::Object::Renderer::CONFIG->{form}->{keep_old_file} = 1;
Column Definitions
Renderer embraces a built-in list of commonly-used column types in web applications, such as email, address, photo, document, and media. This list is defined in $Rose::DBx::Object::Renderer::CONFIG->{columns}. For those who are familiar with CGI::FormBuilder, it is obvious that most of the values inside $Rose::DBx::Object::Renderer::CONFIG->{columns}->{column_name} are in fact CGI::FormBuilder field definitions, except for format, unsortable, and stringify. We can create new column definitions simply by extending $CONFIG->{columns}.
format-
Renderer encapsulates web-oriented behaviours by injecting the coderefs defined inside the
formathashref as object methods, for example:# Prints the localised DateTime object in 'DD/MM/YYYY' format print $object->date_for_view; # Prints the image column in formatted HTML print $object->image_for_view; # Prints the url of the image print $object->image_url; # Prints the file path of the image print $object->image_path;These injected object objects are used by rendering methods. The
for_editandfor_updatemethods are used byrender_as_form. Thefor_editmethods are triggered to format column values during form rendering, while thefor_updatemethods are triggered to update column values during form submission. Similarly, thefor_view, andfor_searchmethods are used byrender_as_table. Thefor_viewmethods are used to format column values during table rendering, while thefor_searchmethods are triggered during column filtering and searches.We can customise existing formatting methods or define new ones easily. Let's say we would like to use the HTML::Strip module to strip out HTML for the 'description' column:
use HTML::Strip; ... $Rose::DBx::Object::Renderer::CONFIG->{columns}->{description}->{format}->{for_update} = sub{ my ($self, $column, $value) = @_; return unless $value; my $hs = HTML::Strip->new(emit_spaces => 0); my $clean_text = $hs->parse($value); return $self->$column($clean_text); };We can always use the modified method directly:
load_namespace('company'); my $p = Company::Product->new(id => 1); $p->load; $p->description_for_update('<html>The Lightweight UI Generator.</html>'); print $p->description; # which prints 'The Lightweight UI Generator.' $p->save();We can create a custom method for the 'first_name' column so that users can click on a link to search the first name in CPAN:
$Rose::DBx::Object::Renderer::CONFIG->{columns}->{first_name}->{format}->{in_cpan} = sub{ my ($self, $column) = @_; my $value = $self->$column; return qq(<a href="http://search.cpan.org/search?query=$value&mode=all">$value</a>) if $value; }; ... load_namespace('company'); my $e = Company::Employee->new(id => 1); $e->load; print $e->first_name_in_cpan;# or use it directly inside a template: [% e.first_name_in_cpan %]
unsortable-
This parameter defines whether a column is a sortable column in tables. For example, the 'password' column is by default unsortable, i.e.:
$Rose::DBx::Object::Renderer::CONFIG->{columns}->{password}->{unsortable} = 1;Custom columns are always unsortable.
stringify-
This parameter specifies which columns are stringified. This is used by the exported
stringify_meobject method.$Rose::DBx::Object::Renderer::CONFIG->{columns}->{first_name}->{stringify} = 1;
METHODS
load_database
load_database loads database tables into classes using Rose::DB::Object::Loader. In order to eliminate the need for manually mapping column definitions to database table columns, load_database also tries to automatically assign a column definition to each column of the loaded classes by matching the column definition name with the database table column name.
load_database accepts three parameters. The first parameter is the database name, the second parameter is a hashref that gets passed directly to the Rose::DB::Object::Loader constructor, while the last parameter is passed to the make_classes method. load_database always use the title case of the database name as the class_prefix unless it is specified. load_database returns an array of the loaded classes via the make_classes method in Rose::DB::Object::Loader. However, if the Rose::DB::Object base_class for the database already exists, which most than likely happens in a persistent environment, load_database will simply skip the loading process and return nothing.
load_database(
'company',
{db_username => 'admin', db_password => 'password'},
{include_tables => ['employee','position']}
);
Common Parameters in Rendering Methods
template-
A string to define the name of the TT template for rendering the UI. When it is set to 1, it will use the default templates defined in
$CONFIG. For instance, the default template forrender_as_formis defined in$CONFIG->{form}->{template}. prefix-
A string to set a prefix for a UI.
prefixis used to prevent CGI param conflicts between multiple generated UIs on the same page. title-
A string to set the title of the UI.
description-
A string to set the description of the UI.
no_head-
When set to 1, rendering methods will not include the default DOCTYPE and CSS styles defined in
$CONFIG->{misc}->{html_head}. This is useful when rendering multiple UIs in the same page. output-
When set to 1, the rendering methods would return the rendered UI instead of printing it directly. For example:
my $form = Company::Employee->render_as_form(output => 1); print $form->{output}; extra-
A hashref of additional variables passed to the template.
Company::Employee->render_as_form(extra => {hobby => 'basketball'}); template_options-
Optional parameters to be passed to template toolkit. This is not applicable to
render_as_form.
render_as_form
render_as_form renders forms and handles the submission.
# Render a form for creating a new object instance
Company::Employee->render_as_form();
# Render a form for updating an existing object instance
my $e = Company::Employee->new(id => 1);
$e->load;
$e->render_as_form();
order-
render_as_formby default sorts all fields based on the column order of the underlying database table.orderaccepts an arrayref to define the order of the form fields to be shown. fields-
Accepts a hashref to overwrite the CGI::FormBuilder field definitions auto-initialised by
render_as_form. Any custom fields must be included to theorderarrayref in order to be shown.Company::Employee->render_as_form( order => ['username', 'password', 'confirm_password', 'favourite_cuisine'], fields => { password => {required => 1, class=> 'password_css'}, });Please note that 'confirm_password' is also a built-in column inside Renderer. The default validation Javascript will work automatically, unless the password field is not called 'password' or when a prefix is used, in which case, the validation code should be updated accordingly.
queries-
An arrayref of query parameters to be converted as hidden fields.
Company::Employee->render_as_form( queries => { 'rm' => 'edit', 'favourite_cuisine' => ['French', 'Japanese'] });Please note that when a prefix is used, all fields are renamed to '
prefix_fieldname'. controllersandcontroller_order-
Controllers are essentially callbacks. We can add multiple custom controllers to a form. They are rendered as submit buttons.
controller_orderdefines the order of the controllers, in other words, the order of the submit buttons.my $form = Employee::Company->render_as_form( output => 1, controller_order => ['Hello', 'Good Bye'], controllers => { 'Hello' => { create => sub { return if DateTime->now->day_name eq 'Sunday'; return 1; }, callback => sub { my $self = shift; if (ref $self) { return 'Hello ' . $self->first_name; } else { return 'No employee has been created'. } }, 'Good Bye' => \&say_goodbye }); if (exists $form->{controller}) { print $form->{controller}; } else { print $form->{output}; } sub say_goodbye { return 'Good Bye'; }Within the
controllershashref, we can set thecreateparameter to 1 so that the object is always inserted into the database before running the custom callback. We can also pointcreateto a coderef, in which case, the object is inserted into the database only if the coderef returns true.Similarly, when rendering an object instance as a form, we can update the object before running the custom callback:
... $e->render_as_form( controllers => { 'Hello' => { update => 1, callback => sub{...}; } );Another parameter within the
controllershashref ishide_form, which informsrender_as_formnot to render the form after executing the controller. cancel-
render_as_formhas a built-in controller called 'Cancel'.cancelis a string for renaming the default 'Cancel' controller in case it clashes with customcontrollers. form-
Parameters for the CGI::FormBuilder constructor.
validate-
Parameters for the CGI::FormBuilder's
validatemethod. jserror-
When a template is used,
render_as_formsets CGI::FormBuilder'sjserrorfunction name to 'notify_error' so that we can always customise the error alert mechanism within the template (see the included 'form.tt' template). show_id-
Shows the ID column (primary key) of the table as a form field when it is set to 1. This is generally not a very good idea except for debugging purposes.
javascript_code-
A string with javascript code to be added to the template
render_as_form calls CGI::FormBuilder's $form->template() method to process template. Thus, we can access the form object via the [% form %] variable. Additionally, The order of the form fields are defined by [% field_order %]. We can also access the calling object (or class) using the [% self %] variable.
render_as_table
render_as_table renders tables for CRUD operations.
or_filter-
render_as_tableallows columns to be filtered via URL. For example:http://www.yoursite.com/yourscript.pl?first_name=Danny&last_name=Liangreturns the object where 'first_name' is 'Danny' and 'Last_name' is 'liang'. By default, column queries are joined by "AND", unless
or_filteris set to 1. columns-
The
columnsparameter can be used to define custom columns which do not physically exist in the database tableCompany::Employee::Manager->render_as_table( columns => {'custom_column' => label => 'Total', value => { 1 => '100', # the 'Total' is 100 for object ID 1 2 => '50' }, }); order-
orderaccepts an arrayref to define the order of the columns to be shown. Theorderparameter also determines which columns are allowed to be filtered via url. searchable-
searchableenables search for multiple columns.Company::Employee::Manager->render_as_table( searchable => ['first_name', 'hobby'], #search for those fields ); # A search box will be shown in rendered table. In the web browser, we can now do http://www.yoursite.com/yourscript.pl?q=coding get-
getaccepts a hashref to construct database queries.getis directly passed to the get method of the manager class.Company::Employee::Manager->render_as_table( get => { per_page = 5, require_objects => [ 'position' ], query => ['position.title' => 'Manager'], }); controllersandcontroller_order-
The
controllersparameter works very similar torender_as_form.controller_orderdefines the order of the controllers.Company::Employee::Manager->render_as_table( controller_order => ['edit', 'Review', 'approve'], controllers => { 'Review' => sub{my $self = shift; do_something_with($self);} 'approve' => { label => 'Approve', hide_table => 1, queries => {approve => '1'}, callback => sub {my $self = shift; do_something_else_with($self); } } );Within the
controllershashref, thequeriesparameter allows us to define custom query strings for the controller. Thehide_tableparameter informsrender_as_tablenot to render the table after executing the controller. create-
This enables the built-in 'create' controller when set to 1.
Company::Employee::Manager->render_as_table(create => 1);Since
render_as_formis used to render the form, we can also pass a hashref to manipulate the generated form.Company::Employee::Manager->render_as_table( create => {title => 'Add New Employee', fields => {...}} ); edit-
Similar to
create,editenables the built-in 'edit' controller for updating objects. delete-
When set to 1,
deleteenables the built-in 'delete' controller for removing objects. queries-
Similar to the
queriesparameter inrender_as_form,queriesis an arrayref of query parameters, which will be converted to query strings. Please note that when a prefix is used, all query strings are renamed to 'prefix_querystring'. url-
Unless a url is specified in
url,render_as_tablewill resolve the self url using CGI. show_id-
Shows the id column (primary key) of the table when it is set to 1. This can be also achieved using the
orderparameter. javascript_code-
A string with javascript code to be added to the template
ajaxandajax_template-
These two parameters are designed for rendering Ajax-enabled tables. When
ajaxis set to 1,render_as_tablewill use the template defined either via theajax_templateparameter or in$CONFIG->{table}->{ajax_template}.render_as_tablealso passes a variable called 'ajax' to the template and sets it to 1 when a CGI param called 'ajax' is set. We can use this variable in the template to differentiate whether the current request is an ajax request or not.
Within a template, we can loop through objects using the [% table %] variable. Alternatively, we can use the [% objects %] variable.
render_as_menu
render_as_menu generates a menu with the given list of classes and renders a table for the current class. We can have fine-grained control over each table within the menu. For example, we can make the 'date_of_birth' field to be mandatory inside the 'create' form of the 'Company::Employee' table within the menu:
my $menu = Company::Employee::Manager->render_as_menu (
order => ['Company::Employee', 'Company::Position'],
items => {
'Company::Employee' => {
create => {
fields => {date_of_birth => {required => 1}}
}
}
'Company::Position' => {
title => 'Current Positions',
description => 'important positions in the company'
}},
create => 1,
edit => 1,
delete => 1,
);
order-
The
orderparameter defines the list of classes to be shown in the menu as well as their order. The current item of the menu is always the calling class, i.e.Company::Employee::Managerin the example. items-
The
itemsparameter is a hashref of parameters to control each table within the menu. create,edit,delete, andajax-
These parameters are shortcuts which get passed to all the underlying tables rendered by the menu.
The [% items %] variable passed to a template defines the menu item, which order is determined by the [% item_order %] variable.
render_as_chart
render_as_chart renders pie, line, and bar charts using Plotr, a Prototype-based Javascript charting library. By default, render_as_chart assumes that the Prototype and Plotr libraries are located in:
$Rose::DBx::Object::Renderer::CONFIG->{template}->{url}/js/prototype.js
$Rose::DBx::Object::Renderer::CONFIG->{template}->{url}/js/plotr/excanvas.js
$Rose::DBx::Object::Renderer::CONFIG->{template}->{url}/js/plotr/plotr.js
Therefore, please make sure those Javascript libraries are in place when no custom template is used.
type-
This can be either 'pie', 'bar', or 'line'.
columnandvalues-
These two parameters are only applicable to pie charts.
columndefines the column of the table in which the values are compared. Thevaluesparameter is a list of values to be compared in that column, i.e. the slices. columnsandobjects-
These two parameters are only applicable to bar and line charts.
columnsdefines the columns of the object to be compared. Theobjectsparameter is a list of object IDs representing the objects to be compared. dataset-
We can use a pre-populated dataset. In this case,
render_as_chartwould simply convert the pre-populated hash to a JSON string and pass it to the template directly.
[% type %], [% options %], and [% dataset %] are the three main variables passed to a template for rendering charts.
OBJECT METHODS
Apart from the formatting methods injected by load_namespace, there are several other less-used object methods.
delete_with_file
This is a wrapper of the object's delete method to remove any uploaded files associated:
$object->delete_with_file();
stringify_me
The default stringify_me method return a string by joining all the matching columns with the stringify parameter set to true. The default stringify delimiter is comma.
# Change the stringify delimiter to a space
$Rose::DBx::Object::Renderer::CONFIG->{misc}->{stringify_delimiter} = ' ';
...
$object->title('Mr');
$object->first_name('Rose');
...
print $object->stringify_me();
# prints 'Mr Rose';
This method is used internally to stringify foreign objects as form field values.
stringify_package_name
This method stringifies the package name:
print Company::Employee->stringify_package_name(); # Prints 'company_employee'
OTHER CONFIGURATIONS
Javascript
We can also specify the path to contents such as javascript libraries or images used within templates:
$Rose::DBx::Object::Renderer::CONFIG->{template}->{url} = '../docs/';
as well as which js libraries to include to the default template of a particular type of UI:
$Rose::DBx::Object::Renderer::CONFIG->{form}->{js}->{datepicker} = 1;
Such that, in the actual TT template, we can do
[% IF js.datepicker %]
<script type="text/javascript" src="[% template_url %]/js/prototype.js"></script>
<script type="text/javascript" src="[% template_url %]/js/scriptaculous/scriptaculous.js"/></script>
<style type="text/css">@import url([% template_url %]/js/datepicker/datepicker.css);</style>
<script type="text/javascript" src="[% template_url %]/js/datepicker/datepicker.js"/></script>
[% END %]
The address_for_view and media_for_view object methods are also designed to work seamlessly with Lightview (http://www.nickstakenburg.com/projects/lightview/), a Prototype based lightbox effect library. Simply include the appropriate Javascript libraries into your custom templates to enable the lightbox effect.
The default CSS class for the 'address' column is 'disable_editor'. This is for excluding the TinyMCE editor with this setup: editor_deselector : "disable_editor".
Miscellaneous
Other miscellaneous configurations are defined in:
$Rose::DBx::Object::Renderer::CONFIG->{misc}
By default, the 'date', 'phone', and 'mobile' columns are localised for Australia.
Sample Templates
There are four sample templates: form.tt, table.tt, menu.tt, and chart.tt in the 'templates' folder inside the TAR archive of the module.
SEE ALSO
Rose::DB::Object, CGI::FormBuilder, Template::Toolkit, http://solutoire.com/plotr/
AUTHOR
Xufeng (Danny) Liang (danny.glue@gmail.com)
COPYRIGHT & LICENSE
Copyright 2008 Xufeng (Danny) Liang, All Rights Reserved.
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.