Rose-DBx-Object-Renderer
view release on metacpan or search on metacpan
lib/Rose/DBx/Object/Renderer.pm view on Meta::CPAN
package Rose::DBx::Object::Renderer;
use strict;
use warnings;
no warnings 'uninitialized';
use Exporter 'import';
use base qw(Rose::Object);
our @EXPORT = qw(config load);
our @EXPORT_OK = qw(config load render_as_form render_as_table render_as_menu render_as_chart stringify_me stringify_class delete_with_file prepare_renderer);
our %EXPORT_TAGS = (object => [qw(render_as_form stringify_me stringify_class delete_with_file prepare_renderer)], manager => [qw(render_as_table render_as_menu render_as_chart)]);
use Lingua::EN::Inflect ();
use DateTime;
use Rose::DB::Object::Loader;
use Rose::DB::Object::Helpers ();
use CGI;
use CGI::FormBuilder;
use Template;
use File::Path;
use File::Copy;
use File::Copy::Recursive ();
use File::Spec;
use Digest::MD5 ();
use Scalar::Util ();
use Clone qw(clone);
our $VERSION = 0.77;
# 264.65
sub _config {
my $config = {
db => {name => undef, type => 'mysql', host => '127.0.0.1', port => undef, username => 'root', password => 'root', tables_are_singular => undef, table_prefix => undef, new_or_cached => 1, check_class => undef},
template => {path => 'templates', url => 'templates', options => undef},
upload => {path => 'uploads', url => 'uploads', keep_old_files => undef},
form => {download_message => 'View', remove_message => 'Remove', remove_files => undef, cancel => 'Cancel', delimiter => ',', action => undef},
table => {search_result_title => 'Search Results for "[% q %]"', empty_message => 'No Record Found.', no_pagination => undef, per_page => 15, pages => 9, or_filter => undef, like_filter => undef, delimiter => ', ', keyword_delimiter => ',', , like...
menu => {cascade => ['create', 'edit', 'copy', 'delete', 'ajax', 'prepared', 'searchable', 'template_url', 'template_path', 'template_options', 'query', 'renderer_config']},
misc => {time_zone => 'Australia/Sydney', stringify_delimiter => ' ', doctype => '<!DOCTYPE HTML>', html_head => '<style type="text/css">body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td{margin:0;paddi...
columns => {
'integer' => {validate => 'INT', sortopts => 'NUM'},
'numeric' => {validate => 'NUM', sortopts => 'NUM'},
'float' => {validate => 'FLOAT', sortopts => 'NUM'},
'text' => {type => 'textarea', cols => '55', rows => '10'},
'postcode' => {sortopts => 'NUM', validate => '/^\d{3,4}$/', maxlength => 4},
'address' => {format => {for_view => sub {_view_address(@_);}}},
'date' => {class => 'date', validate => '/^(0?[1-9]|[1-2][0-9]|3[0-1])\/(0?[1-9]|1[0-2])\/[0-9]{4}|([0-9]{4}\-0?[1-9]|1[0-2])\-(0?[1-9]|[1-2][0-9]|3[0-1])$/', format => {for_edit => sub {_edit_date(@_);}, for_update => sub {_update_date(@_);}, for...
'datetime' => {validate => '/^(0?[1-9]|[1-2][0-9]|3[0-1])\/(0?[1-9]|1[0-2])\/[0-9]{4}|([0-9]{4}\-0?[1-9]|1[0-2])\-(0?[1-9]|[1-2][0-9]|3[0-1])\s+[0-9]{1,2}:[0-9]{2}$/', format => {for_edit => sub{_edit_datetime(@_);}, for_view => sub{_view_datetime...
'timestamp' => {readonly => "readonly", disabled => 1, format => {for_view => sub {_view_timestamp(@_);}, for_create => sub {_create_timestamp(@_);}, for_edit => sub {_create_timestamp(@_);}, for_update => sub {_update_timestamp(@_);}, for_search ...
'description' => {sortopts => 'LABELNAME', type => 'textarea', cols => '55', rows => '10'},
'time' => {validate => '/^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/', maxlength => 5, format => {for_update => sub {_update_time(@_)}, for_edit => sub{_edit_time(@_);}, for_view => sub{_view_time(@_);}}},
'length' => {validate => 'NUM', sortopts => 'NUM', format => {for_view => sub {my ($self, $column) = @_;my $value = $self->$column;return unless $value;return $value.' cm';}}},
'weight' => {validate => 'NUM', sortopts => 'NUM', format => {for_view => sub {my ($self, $column) = @_;my $value = $self->$column;return unless $value;return $value.' kg';}}},
'volume' => {validate => 'NUM', sortopts => 'NUM', format => {for_view => sub {my ($self, $column) = @_;my $value = $self->$column;return unless $value;return $value.' cm<sup>3</sup>';}}},
'gender' => {options => ['Male', 'Female']},
'name' => {sortopts => 'LABELNAME', required => 1, stringify => 1},
'first_name' => {validate => 'FNAME', sortopts => 'LABELNAME', required => 1, stringify => 1},
'last_name' => {validate => 'LNAME', sortopts => 'LABELNAME', required => 1, stringify => 1},
'email' => {required => 1, validate => 'EMAIL', sortopts => 'LABELNAME', format => {for_view => sub {my ($self, $column) = @_;my $value = $self->$column;return unless $value;return qq(<a href="mailto:$value">$value</a>);}}},
'url' => {sortopts => 'LABELNAME', format => {for_view => sub {my ($self, $column) = @_;my $value = $self->$column;return unless $value;return qq(<a href="$value">$value</a>);}}},
'phone' => {validate => '/^[\+\d\s\-\(\)]+$/'},
'username' => {validate => '/^[a-zA-Z0-9]{4,}$/', sortopts => 'LABELNAME', required => 1},
'password' => {validate => '/^[\w.!?@#$%&*]{5,}$/', type => 'password', format => {for_view => sub {return '****';}, for_edit => sub {return;}, for_update => sub {my ($self, $column, $value) = @_;return $self->$column(Digest::MD5::md5_hex($value))...
'confirm_password' => {required => 1, type => 'password', validate => {javascript => "!= form.elements['password'].value"}},
'abn' => {label => 'ABN', validate => '/^(\d{2}\s*\d{3}\s*\d{3}\s*\d{3})$/', comment => 'e.g. 12 234 456 678'},
'money' => {validate => '/^\-?\d{1,11}(\.\d{2})?$/', sortopts => 'NUM', format => {for_view => sub {my ($self, $column) = @_;return unless defined $self->$column;return sprintf ('$%.02f', $self->$column);}, for_edit => sub {my ($self, $column) = @...
'percentage' => {validate => 'NUM', sortopts => 'NUM', comment => 'e.g.: 99.8', format => {for_view => sub {my ($self, $column, $value) = @_;$value = $self->$column;return unless $value;my $p = $value*100;return "$p%";}, for_edit => sub {my ($self...
'document' => {validate => '/^[\w\s.!?@#$\(\)\'\_\-:%&*\/\\\\\[\]]+$/', format => {remove => sub {_remove_file(@_);}, path => sub {_get_file_path(@_);}, url => sub {_get_file_url(@_);}, for_update => sub {_update_file(@_);}, for_view => sub {_view...
'image' => {validate => '/^[\w\s.!?@#$\(\)\'\_\-:%&*\/\\\\\[\]]+\.(gif|jpg|jpeg|png|GIF|JPG|JPEG|PNG)$/', format => {remove => sub {_remove_file(@_);}, path => sub {_get_file_path(@_);}, url => sub {_get_file_url(@_);}, for_view => sub {_view_imag...
'media' => {validate => '/^[\w\s.!?@#$\(\)\'\_\-:%&*\/\\\\\[\]]+$/', format => {remove => sub {_remove_file(@_);}, path => sub {_get_file_path(@_);}, url => sub {_get_file_url(@_);}, for_view => sub {_view_media(@_);}, for_update => sub {_update_f...
'video' => {validate => '/^[\w\s.!?@#$\(\)\'\_\-:%&*\/\\\\\[\]]+$/', format => {remove => sub {_remove_file(@_);}, path => sub {_get_file_path(@_);}, url => sub {_get_file_url(@_);}, for_view => sub {_view_video(@_);}, for_update => sub {_update_f...
'audio' => {validate => '/^[\w\s.!?@#$\(\)\'\_\-:%&*\/\\\\\[\]]+$/', format => {remove => sub {_remove_file(@_);}, path => sub {_get_file_path(@_);}, url => sub {_get_file_url(@_);}, for_view => sub {_view_audio(@_);}, for_update => sub {_update_f...
'ipv4' => {validate => 'IPV4'},
'boolean' => {validate => '/^[0-1]$/', sortopts => 'LABELNAME', options => {1 => 'Yes', 0 => 'No'}, format => {for_create => sub {my ($self, $column) = @_;my $default = $self->meta->{columns}->{$column}->{default};return unless length($default);re...
}
};
$config->{columns}->{'doubleprecision'} = $config->{columns}->{'numeric'};
$config->{columns}->{'decimal'} = $config->{columns}->{'numeric'};
$config->{columns}->{'bigint'} = $config->{columns}->{'integer'};
$config->{columns}->{'serial'} = $config->{columns}->{'integer'};
$config->{columns}->{'bigserial'} = $config->{columns}->{'integer'};
$config->{columns}->{'quantity'} = $config->{columns}->{'integer'};
$config->{columns}->{'height'} = $config->{columns}->{'length'};
$config->{columns}->{'width'} = $config->{columns}->{'length'};
$config->{columns}->{'depth'} = $config->{columns}->{'length'};
$config->{columns}->{'title'} = $config->{columns}->{'name'};
$config->{columns}->{'birth'} = $config->{columns}->{'date'};
$config->{columns}->{'mobile'} = $config->{columns}->{'phone'};
$config->{columns}->{'fax'} = $config->{columns}->{'phone'};
$config->{columns}->{'cost'} = $config->{columns}->{'money'};
$config->{columns}->{'price'} = $config->{columns}->{'money'};
$config->{columns}->{'blob'} = $config->{columns}->{'text'};
$config->{columns}->{'comment'} = $config->{columns}->{'text'};
$config->{columns}->{'file'} = $config->{columns}->{'document'};
$config->{columns}->{'report'} = $config->{columns}->{'document'};
$config->{columns}->{'photo'} = $config->{columns}->{'image'};
$config->{columns}->{'logo'} = $config->{columns}->{'image'};
lib/Rose/DBx/Object/Renderer.pm view on Meta::CPAN
if ($table_config->{like_filter}) {
my $formatted_values_like = [map {'%' . $_ . '%'} @{$formatted_values}];
if ($class && $class->meta->db->driver eq 'pg' && exists $class->meta->{columns}->{$column} && ! $class->meta->{columns}->{$column}->isa('Rose::DB::Object::Metadata::Column::Character')) {
my $filter_column_text = 'text(t1.' . $column . ') ' . $like_operator . ' ?';
push @{$filtered_columns}, \$filter_column_text => $formatted_values_like;
}
elsif ($class && $class->meta->db->driver eq 'sqlite' && exists $class->meta->{columns}->{$column} && ! $class->meta->{columns}->{$column}->isa('Rose::DB::Object::Metadata::Column::Character')) {
my $filter_column_text = 'cast(t1.' . $column . ' AS TEXT) ' . $like_operator . ' ?';
push @{$filtered_columns}, \$filter_column_text => $formatted_values_like;
}
else {
push @{$filtered_columns}, $column => {$like_operator => $formatted_values_like};
}
}
else {
push @{$filtered_columns}, $column => $formatted_values;
}
$args{queries}->{$cgi_column} = \@cgi_column_values unless exists $args{queries}->{$cgi_column};
}
}
}
}
if ($filtered_columns) {
if($table_config->{or_filter}) {
push @{$args{get}->{query}}, 'or' => $filtered_columns;
}
else {
foreach my $filtered_column (@{$filtered_columns}) {
push @{$args{get}->{query}}, $filtered_column;
}
}
}
unless (exists $args{get} && (exists $args{get}->{limit} || exists $args{get}->{offset})) {
my $query_param_per_page = $query->param($param_list->{'per_page'});
$args{get}->{per_page} ||= $query_param_per_page || $table_config->{per_page};
$args{queries}->{$param_list->{per_page}} ||= $query_param_per_page if $query_param_per_page;
$args{get}->{page} ||= $query->param($param_list->{'page'}) || 1;
}
$objects = $self->get_objects(%{$args{get}});
$output->{objects} = $objects;
## Handle Submission
my $reload_object;
if ($query->param($param_list->{action})) {
my $valid_form_actions = {create => undef, edit => undef, copy => undef};
my $action = $query->param($param_list->{action});
if (exists $valid_form_actions->{$action} && $args{$action}) {
$args{$action} = {} if $args{$action} == 1;
$args{$action}->{output} = 1;
$args{$action}->{no_head} = $args{no_head} if exists $args{no_head} && ! exists $args{$action}->{no_head};
$args{$action}->{prepared} = $args{prepared} if exists $args{prepared} && ! exists $args{$action}->{prepared};
_cascade($table_config->{cascade}, \%args, $args{$action});
foreach my $option (@{$table_config->{form_options}}) {
_inherit_form_option($option, $action, \%args);
}
$args{$action}->{order} ||= Clone::clone($args{order}) if $args{order};
$args{$action}->{template} ||= _template($args{template}, 'form', 1) if $args{template};
@{$args{$action}->{queries}}{keys %{$args{queries}}} = values %{$args{queries}};
$args{$action}->{queries}->{$param_list->{action}} = $action;
$args{$action}->{queries}->{$param_list->{sort_by}} = $query->param($param_list->{sort_by}) if $query->param($param_list->{sort_by});
$args{$action}->{queries}->{$param_list->{page}} = $query->param($param_list->{page}) if $query->param($param_list->{page});
$args{$action}->{prefix} ||= $table_id.'_form';
my $form;
if ($action eq 'create') {
$form = $class->render_as_form(%{$args{$action}});
}
elsif ($query->param($param_list->{object})) {
$args{$action}->{queries}->{$param_list->{object}} = $query->param($param_list->{object});
$args{$action}->{copy} = 1 if $action eq 'copy';
foreach my $object (@{$objects}) {
if ($object->$primary_key eq $query->param($param_list->{object})) {
$form = $object->render_as_form(%{$args{$action}});
$output->{form} = $form;
last;
}
}
}
$form->{validate}?$reload_object = 1:$output->{output} = $form->{output};
}
elsif ($query->param($param_list->{object})) {
$reload_object = 1;
my @object_ids = $query->param($param_list->{object});
my (%valid_object_ids, @action_objects);
@valid_object_ids{@object_ids} = ();
foreach my $object (@{$objects}) {
push @action_objects, $object if exists $valid_object_ids{$object->$primary_key};
}
if ($query->param($param_list->{action}) eq 'delete' && $args{delete}) {
foreach my $action_object (@action_objects) {
$action_object->delete_with_file;
}
}
elsif (exists $args{controllers} && exists $args{controllers}->{$query->param($param_list->{action})}) {
no strict 'refs';
foreach my $action_object (@action_objects) {
if (ref $args{controllers}->{$query->param($param_list->{action})} eq 'HASH') {
$output->{controller} = $args{controllers}->{$query->param($param_list->{action})}->{callback}->($action_object) if ref $args{controllers}->{$query->param($param_list->{action})}->{callback} eq 'CODE';
$args{hide_table} = 1 if exists $args{controllers}->{$query->param($param_list->{action})}->{hide_table};
}
else {
$output->{controller} = $args{controllers}->{$query->param($param_list->{action})}->($action_object) if ref $args{controllers}->{$query->param($param_list->{action})} eq 'CODE';
}
}
lib/Rose/DBx/Object/Renderer.pm view on Meta::CPAN
}
my $query = $args{query} || CGI->new;
my $url = $args{url} || $query->url(-absolute => 1);
my $query_string = join ('&', map {"$_=$args{queries}->{$_}"} keys %{$args{queries}});
$query_string .= '&' if $query_string;
$template = _template($args{template}, $ui_type) if $args{template};
$current = $query->param($current_param);
unless ($current) {
if ($args{current}) {
$current = $args{current}->meta->table;
}
else {
$current = $class->meta->table;
}
}
$item_order = $args{order} || [$class];
$args{template_data} ||= {};
foreach my $item (@{$item_order}) {
my $table = $item->meta->table;
$items->{$item}->{table} = $table;
if (defined $args{items} && defined $args{items}->{$item} && defined $args{items}->{$item}->{title}) {
$items->{$item}->{label} = $args{items}->{$item}->{title};
}
else {
$items->{$item}->{label} = _label(_pluralise_table(_title($table, $renderer_config->{db}->{table_prefix}), $renderer_config->{db}->{tables_are_singular}));
}
$items->{$item}->{link} = qq($url?$query_string$current_param=$table);
if ($table eq $current) {
my $options;
$options = $args{items}->{$item} if exists $args{items} && exists $args{items}->{$item};
$options->{output} = 1;
@{$options->{queries}}{keys %{$args{queries}}} = values %{$args{queries}};
$options->{queries}->{$current_param} = $table;
$options->{prefix} ||= $menu_id.'_table';
$options->{url} ||= $url;
$options->{template_data} = $args{template_data} unless exists $options->{template_data};
if ($args{ajax}) {
my $valid_form_actions = {create => undef, edit => undef, copy => undef};
$args{hide_menu} = 1 if $query->param($options->{prefix}.'_ajax') && ! exists $valid_form_actions->{$query->param($options->{prefix}.'_action')};
}
if ($args{template} && ! exists $options->{template}) {
if (ref $args{template} eq 'HASH') {
$options->{template} = $args{template};
}
else {
$options->{template} = 1;
}
}
_cascade($menu_config->{cascade}, \%args, $options);
foreach my $shortcut (@{$menu_config->{shortcuts}}) {
$options->{$shortcut} = 1 if $args{$shortcut} && ! exists $options->{$shortcut};
}
$options->{no_head} = 1;
$output->{table} = "$item\::Manager"->render_as_table(%{$options});
$menu_title ||= $items->{$item}->{label};
}
}
$args{hide_menu} = 1 if $query->param($hide_menu_param);
my $html_head = _html_head(\%args, $renderer_config);
if ($args{template}) {
my $template_options = $args{template_options} || $renderer_config->{template}->{options};
$menu = _render_template(
options => $template_options,
template_path => $template_path,
file => $template,
output => 1,
data => {
menu_id => $menu_id,
no_head => $args{no_head},
doctype => $renderer_config->{misc}->{doctype},
html_head => $html_head,
template_url => $template_url,
items => $items,
item_order => $item_order,
current => $current,
title => $menu_title,
description => $args{'description'},
content => $output->{table}->{output},
hide => $args{hide_menu},
extra => $args{extra},
%{$args{template_data}}
}
);
}
else {
unless ($args{hide_menu}) {
$menu = '<div><div class="menu"><ul>';
foreach my $item (@{$item_order}) {
$menu .= '<li><a ';
$menu .= 'class="current" ' if $items->{$item}->{table} eq $current;
$menu .= 'href="'.$items->{$item}->{link}.'">'.$items->{$item}->{label}.'</a></li>';
}
$menu .= '</ul></div>';
$menu .= qq(<p>$args{description}</p>) if defined $args{description};
$menu .= '</div>';
}
$menu .= $output->{table}->{output};
$menu = qq($renderer_config->{misc}->{doctype}<html><head><title>$menu_title</title>$html_head</head><body>$menu</body></html>) unless $args{no_head};
}
$args{output}?$output->{output} = $menu:print $menu;
return $output;
}
lib/Rose/DBx/Object/Renderer.pm view on Meta::CPAN
$template = $ui_type . '.tt';
}
else {
$template = $args{template};
}
$args{template_data} ||= {};
my $template_options = $args{template_options} || $renderer_config->{template}->{options};
$chart = _render_template(
options => $template_options,
template_path => $template_path,
file => $template,
output => 1,
data => {
template_url => $template_url,
chart => $chart_url,
options => $args{'options'},
chart_id => $chart_id,
title => $title ,
description => $args{'description'},
no_head => $args{no_head},
doctype => $renderer_config->{misc}->{doctype},
html_head => $html_head,
extra => $args{extra},
%{$args{template_data}}
}
);
}
else {
$chart = qq(<div><h1>$title</h1>);
$chart .= qq(<p>$args{description}</p>) if defined $args{description};
$chart .= qq(<img src="$chart_url" alt="$title"/></div>);
$chart = qq($renderer_config->{misc}->{doctype}<html><head><title>$title</title>$html_head</head><body>$chart</body></html>) unless $args{no_head};
}
}
$args{output}?$output->{output} = $chart:print $chart;
return $output;
}
sub _render_template {
my %args = @_;
if ($args{file} && $args{data} && $args{template_path}) {
my $options = $args{options};
$options->{INCLUDE_PATH} ||= $args{template_path};
my $template = Template->new(%{$options});
if($args{output}) {
my $output = '';
$template->process($args{file},$args{data}, \$output) || die $template->error(), "\n";
return $output;
}
else {
return $template->process($args{file},$args{data});
}
}
}
# util
sub _cascade {
my ($cascade, $args, $options) = @_;
foreach my $option (@{$cascade}) {
$options->{$option} = $args->{$option} if defined $args->{$option} && ! defined $options->{$option};
}
return;
}
sub _ui_config {
my ($ui_type, $renderer_config, $args) = @_;
my $ui_config;
foreach my $option (keys %{$renderer_config->{$ui_type}}) {
if (defined $args->{$option}) {
$ui_config->{$option} = $args->{$option};
}
else {
$ui_config->{$option} = $renderer_config->{$ui_type}->{$option};
}
}
return $ui_config;
}
sub _prepare {
my ($class, $config, $prepared) = @_;
return $class->prepare_renderer($config) unless $prepared || $class->can('renderer_config');
return $config || $class->renderer_config();
}
sub _get_renderer_config {
my $self = shift;
return $self->renderer_config() if $self->can('renderer_config');
return _config();
}
sub _pagination {
my ($self, $class, $get) = @_;
my $total = $self->get_objects_count(%{$get});
return (1, 1, 1, $total) unless $get->{per_page} && $get->{page};
my ($last_page, $next_page, $previous_page);
if ($total < $get->{per_page}) {
$last_page = 1;
}
else {
my $pages = $total / $get->{per_page};
if ($pages == int $pages) {
$last_page = $pages;
}
else {
$last_page = 1 + int($pages);
}
}
if ($get->{page} == $last_page) {
$next_page = $last_page;
}
else {
$next_page = $get->{page} + 1;
}
if ($get->{page} == 1) {
$previous_page = 1;
}
else {
lib/Rose/DBx/Object/Renderer.pm view on Meta::CPAN
my $primary_key = $self->meta->primary_key_column_names->[0];
foreach my $field (@{$field_order}) {
my $column = $field;
$column =~ s/$form_id\_//x if $prefix;
my $field_value;
my @values = $form->field($field);
my $values_size = scalar @values;
if($values_size > 1) {
$field_value = join _get_renderer_config($self)->{form}->{delimiter}, @values;
}
else {
$field_value = $form->field($field); # if this line is removed, $form->field function will still think it should return an array, which will fail for file upload
}
if (exists $relationships->{$column}) {
my $foreign_class = $relationships->{$column}->{class};
my $foreign_class_foreign_keys = _get_foreign_keys($foreign_class);
my $foreign_key;
foreach my $fk (keys %{$foreign_class_foreign_keys}) {
if ($foreign_class_foreign_keys->{$fk}->{class} eq $class) {
$foreign_key = $fk;
last;
}
}
my $default = undef;
$default = $relationships->{$column}->{class}->meta->{columns}->{$table.'_id'}->{default} if defined $relationships->{$column}->{class}->meta->{columns}->{$table.'_id'}->{default};
# $form->field($field) won't work
if(length($form->cgi_param($field))) {
my ($new_foreign_object_id, $old_foreign_object_id, $value_hash, $new_foreign_object_id_hash);
my $foreign_class_primary_key = $relationships->{$column}->{class}->meta->primary_key_column_names->[0];
foreach my $id (@values) {
push @{$new_foreign_object_id}, $foreign_class_primary_key => $id;
$value_hash->{$id} = undef;
push @{$new_foreign_object_id_hash}, {$foreign_class_primary_key => $id};
}
foreach my $id (keys %{$relationship_object->{$column}}) {
push @{$old_foreign_object_id}, $foreign_class_primary_key => $id unless exists $value_hash->{$id};
}
if ($relationships->{$column}->{type} eq 'one to many') {
Rose::DB::Object::Manager->update_objects(object_class => $foreign_class, set => {$foreign_key => $default}, where => [or => $old_foreign_object_id]) if $old_foreign_object_id;
Rose::DB::Object::Manager->update_objects(object_class => $foreign_class, set => {$foreign_key => $self->$primary_key}, where => [or => $new_foreign_object_id]) if $new_foreign_object_id;
}
else {
# many to many
$self->$column(@{$new_foreign_object_id_hash});
}
}
else {
if ($relationships->{$column}->{type} eq 'one to many') {
Rose::DB::Object::Manager->update_objects(object_class => $foreign_class, set => {$foreign_key => $default}, where => [$foreign_key => $self->$primary_key]);
}
else {
# many to many
$self->$column([]); # cascade deletes foreign objects
}
}
}
else {
my $update_method;
if ($class->can($column . '_for_update')) {
$update_method = $column . '_for_update';
}
elsif ($class->can($column)) {
$update_method = $column;
}
if ($update_method) {
if (length($form->cgi_param($field))) {
$self->$update_method($field_value);
}
else {
$self->$update_method(undef);
}
}
}
}
_remove_column_files($self, $files_to_remove) if $files_to_remove && @{$files_to_remove};
$self->save;
return $self;
}
sub _create_object {
my ($self, $class, $table, $field_order, $form, $form_id, $prefix, $relationships, $relationship_object, $files_to_remove) = @_;
my $custom_field_value;
$self = $self->new();
foreach my $field (@{$field_order}) {
if(defined $form->cgi_param($field) && length($form->cgi_param($field))) {
my $column = $field;
$column =~ s/$form_id\_//x if $prefix;
my @values = $form->field($field);
# one to many or many to many
if (exists $relationships->{$column}) {
my $new_foreign_object_id_hash;
my $foreign_class_primary_key = $relationships->{$column}->{class}->meta->primary_key_column_names->[0];
foreach my $id (@values) {
push @{$new_foreign_object_id_hash}, {$foreign_class_primary_key => $id};
}
$self->$column(@{$new_foreign_object_id_hash});
}
else {
my $field_value;
my $values_size = scalar @values;
if($values_size > 1) {
$field_value = join _get_renderer_config($self)->{form}->{delimiter}, @values;
}
else {
$field_value = $form->field($field); # if this line is removed, $form->field function will still think it should return an array, which will fail for file upload
}
lib/Rose/DBx/Object/Renderer.pm view on Meta::CPAN
The C<template> option specifies the template toolkit C<INCLUDE_PATH> and the base URL for static contents, such as javascript libraries or images:
$renderer->config({
...
template => {
path => '../templates:../alternative', # TT INCLUDE_PATH, defaulted to 'templates'
url => '../images', # defaulted to 'templates'
options => {ABSOLUTE => 1, RELATIVE => 1, POST_CHOMP => 1} # defaulted to undef
},
});
=head3 C<upload>
Renderer needs a directory with write access to upload files. The C<upload> option defines file upload related settings:
$renderer->config({
...
upload => {
path => '../files', # the upload directory path, defaulted to 'uploads'
url => '../files', # the corresponding URL path, defaulted to 'uploads'
keep_old_files => 1, # defaulted to undef
},
});
=head3 C<form>
The C<form> option defines the global default behaviours of C<render_as_form>:
$renderer->config({
...
form => {
download_message => 'View File', # the name of the link for uploaded files, defaulted to 'View'
remove_files => 1, # allow users to remove uploaded files, default to undef
remove_message => 'Remove File', # the label of the checkbox for removing files, defaulted to 'Remove'
cancel => 'Back', # the name of the built-in 'Cancel' controller, defaulted to 'Cancel'
delimiter => ' ' # the delimiter for handling column with muliple values, defaulted to ','
action => '/app' # set form action, defaulted to undef
},
});
These options can be also passed to C<render_as_form> directly to affect only the particular instance.
=head3 C<table>
The C<table> option defines the global default behaviours of C<render_as_table>:
$renderer->config({
...
table => {
search_result_title => 'Looking for "[% q %]"?',
empty_message => 'No matching records.',
per_page => 25, # number of records per table, defaulted to 15
pages => 5, # the amount of page numbers in the table pagination, defaulted to 9
no_pagination => 1, # do not display pagination, defaulted to undef
or_filter => 1, # column filtering is joined by 'OR', defaulted to undef
delimiter => '/', # the delimiter for joining foreign objects in relationship columns, defaulted to ', '
keyword_delimiter => '\s+', # the delimiter for search keywords, defaulted to ','
like_operator => 'like', # only applicable to Postgres, defaulted to undef, i.e. render_as_table() uses 'ilike' for Postgres by default
form_options => ['order', 'template'], # options to be shared by other forms, defaulted to ['before', 'order', 'fields', 'template']
cascade => ['template_data', 'extra'], # options to be cascaded into all forms, defaulted to ['template_url', 'template_path', 'template_options', 'query', 'renderer_config', 'prepared']
},
});
These options can be also passed to C<render_as_table> directly to affect only the particular instance.
=head3 C<menu>
The C<menu> option defines the global default behaviours of C<render_as_menu>:
$renderer->config({
...
menu => {
cascade => ['template_data', 'extra'], # options to be cascaded into all tables, defaulted to ['create', 'edit', 'copy', 'delete', 'ajax', 'prepared', 'searchable', 'template_url', 'template_path', 'template_options', 'query', 'renderer_config'...
},
});
These options can be also passed to C<render_as_menu> directly to affect only the particular instance.
=head3 C<columns>
Renderer has a built-in list of column definitions that encapsulate web conventions and behaviours. A column definition is a collection of column options. Column definitions are used by the rendering methods to generate web UIs. The built-in column d...
my $config = $renderer->config();
print join (', ', keys %{$config->{columns}});
For example, the column definition for 'email' would be:
...
'email' => {
required => 1,
validate => 'EMAIL',
sortopts => 'LABELNAME',
comment => 'e.g. your.name@work.com',
format => {
for_view => sub {
my ($self, $column) = @_;
my $value = $self->$column;
return unless $value;
return qq(<a href="mailto:$value">$value</a>);}
}
},
...
We can also define new column definitions:
$renderer->config({
...
columns => {
hobby => {
label => 'Your Favourite Hobby',
sortopts => 'LABELNAME',
required => 1,
options => ['Reading', 'Coding', 'Shopping']
}
},
});
All options in each column definition are C<CGI::FormBuilder> field definitions, i.e. they are passed to L<CGI::FormBuilder> directly, except for:
=over
=item C<format>
The C<format> option is a hash of coderefs which get injected as object methods by C<load>. For instance, based on the 'email' column definition, we can print a 'mailto' link for the email address by calling:
print $object->email_for_view;
Similarly, based on other column definitions, we can:
# Print the date in 'DD/MM/YYYY' format
print $object->date_for_view;
# Store a password in MD5 hash
( run in 3.512 seconds using v1.01-cache-2.11-cpan-5735350b133 )