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 ('&amp;', map {"$_=$args{queries}->{$_}"} keys %{$args{queries}});
	$query_string .= '&amp;' 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 )