| CatalystX-Usul documentation | Contained in the CatalystX-Usul distribution. |
CatalystX::Usul::Plugin::Model::StashHelper - Convenience methods for stuffing the stash
0.3.$Revision: 576 $
package CatalystX::Usul; use parent qw(Catalyst::Component CatalystX::Usul::Base); package CatalystX::Usul::Model; use parent qw(CatalystX::Usul CatalystX::Usul::StashHelper); package YourApp::Model::YourModel; use parent qw(CatalystX::Usul::Model);
Many convenience methods for stuffing/resetting the stash. The form widget definitions will be replaced later by the form building method which is called from the HTML view
Stuff some content into the stash so that it will appear in the append div in the template. The content is a hash ref which will be interpreted as a widget definition by the form builder which is invoked by the HTML view. Multiple calls push the content onto a stack which is rendered in the order in which it was stacked
Generates the data for the popup chooser window which allows a data value to be selected from a list produced by some query. It is intended as a replacement for a popup menu widget where the list of values would be prohibitively long
Stringifies the passed error object, localises the text, logs it as an error and calls add_result to display it at the top of the sdata div
Localises the message text, creates a new error object and calls add_error
Create a widget definition for a form field
Stuffs the stash with the data for the page header
Adds the result of forwarding to an an action. This is the result div in the template
Localises the message text and calls add_result
Adds the sequence of links used in search page results; first page, previous page, list of pages around the current one, next page, and last page
$model->check_field_wrapper;
Extract parameters from the query and call check_field. Stash the result
Clears the stash of the widget data used by the region appended to the main data store
Groups the methods that clear the stash of data not used in a minority of pages
Initialises the sdata div contents. Called by /stash_content on
first use
Clears the stash of the quick links navigation data
Clears the stash of messages from the output of actions
Stashes the data used by HTML::FormWidgets to throw fieldset around a group of fields
Returns a content hash ref that renders as a clickable image anchor. The link returns to the web servers default page
Returns the Javascript fragment that will open a new window in the web browser
Create a KinoSearch results page
Creates a "simple" page from information stored in the configuration files
Pushes the content (usually a widget definition) onto the specified stack
Calls stash_content specifying the sdata stack
Adds some meta data to the response for an Ajax call
None
None
There are no known incompatibilities in this module.
There are no known bugs in this module. Please report problems to the address below. Patches are welcome
Peter Flanigan, <Support at RoxSoft.co.uk>
Copyright (c) 2008 Peter Flanigan. All rights reserved
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. See perlartistic
This program is distributed in the hope that it will be useful, but WITHOUT WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE
| CatalystX-Usul documentation | Contained in the CatalystX-Usul distribution. |
# @(#)$Id: StashHelper.pm 576 2009-06-09 23:23:46Z pjf $ package CatalystX::Usul::Plugin::Model::StashHelper; use strict; use warnings; use version; our $VERSION = qv( sprintf '0.3.%d', q$Rev: 576 $ =~ /\d+/gmx ); use parent qw(CatalystX::Usul); use Data::Pageset; use Lingua::Flags; use Time::Elapsed qw(elapsed); my $DOTS = chr 8230; my $NUL = q(); my $SEP = q(/); my $SPC = q( ); my $TTS = q( ~ ); # Core stash helper methods sub stash_content { # Push the content onto the item list for the given stash id my ($self, $content, $id, $clear) = @_; return unless ($content and $id); my $s = $self->context->stash; eval { $self->$clear() } if ($clear and not defined $s->{ $id }); my $item = { content => $content, id => $id }; if (ref $content eq q(HASH) and $content->{class}) { $item->{class} = $content->{class}; } push @{ $s->{ $id }->{items} }, $item; $s->{ $id }->{count} = @{ $s->{ $id }->{items} }; return; } sub stash_meta { # Set attribute value pairs for the given stash id my ($self, $data, $id, $clear) = @_; $id ||= q(sdata); $clear ||= q(clear_form); my $s = $self->context->stash; eval { $self->$clear() } unless (defined $s->{ $id }); while (my ($attr, $value) = each %{ $data }) { $s->{ $id }->{ $attr } = $value unless ($attr eq q(items)); } return; } # Stash content methods sub add_append { # Add a widget definition to the append div my ($self, $content) = @_; return unless ($content and ref $content eq q(HASH)); $content->{widget} = 1; $self->stash_content( $content, q(append), q(clear_append) ); return; } sub add_button { # Add a button widget definition to the button bar div my ($self, $args) = @_; my $s = $self->context->stash; my $content; return unless ($args and ref $args eq q(HASH)); my $label = $args->{label } || q(Unknown); my $button = $s->{buttons}->{ $s->{form}->{name}.q(.).(lc $label) }; my $help = $args->{help } || $button ? $button->{help } : $NUL; my $prompt = $args->{prompt } || $button ? $button->{prompt} : $NUL; my $onclick = $args->{onclick} || $prompt ? "return window.confirm( '${prompt}' )" : $NUL; my $type = $args->{type } || q(image); $label = (split $SPC, $label.q( X))[0]; my $file = $label.q(.png); my $path = $self->catfile( $s->{skindir}, $s->{skin}, $file ); unless (-f $path) { $file = $label.q(.gif); $path = $self->catfile( $s->{skindir}, $s->{skin}, $file ); } if ($type eq q(image) and -f $path) { $content = { alt => $label, src => $s->{assets}.$file }; } $content->{class } = $args->{class} || q(button); $content->{name } = $label; $content->{onclick} = $onclick if ($onclick); $content->{tip } = ($args->{title} || $DOTS).$TTS.$help if ($help); $content->{type } = q(button); $content->{widget } = 1; $self->stash_content( $content, q(bbar), q(clear_buttons) ); return; } sub add_footer { my $self = shift; my $c = $self->context; my $cfg = $c->config; my $req = $c->req; my $s = $c->stash; my ($content, $text); $self->stash_content( $self->_footer_line, q(footer), q(clear_footer) ); $content = { text => $s->{user}.q(@).$s->{host_port}, tip => $self->loc( q(yourIdentity) ), tiptype => q(plain), type => q(label), widget => 1 }; $self->stash_content( $content, q(footer) ); if ($s->{debug}) { # Useful numbers and such if ($cfg->{version}) { $content = { text => q( ).$cfg->{version}, tip => $self->loc( q(moduleVersion) ), tiptype => q(plain), type => q(label), widget => 1 }; $self->stash_content( $content, q(footer) ); } if (defined $s->{version}) { $content = { text => q( ).$s->{version}, tip => $self->loc( q(levelVersion) ), tiptype => q(plain), type => q(label), widget => 1 }; $self->stash_content( $content, q(footer) ); } ($text = $self->stamp) =~ tr?/:?..?; $content = { text => $text, tip => $self->loc( q(pageGenerated) ), tiptype => q(plain), type => q(label), widget => 1 }; $self->stash_content( $content, q(footer) ); if ($s->{elapsed}) { $content = { text => q( ).elapsed( $s->{elapsed} ), tip => $self->loc( q(elapsedTime) ), tiptype => q(plain), type => q(label), widget => 1 }; $self->stash_content( $content, q(footer) ); } # TODO: Replace this with a language selector my %lang2country_map = ( 'de' => q(DE), 'en' => q(GB) ); my $country = $lang2country_map{ $s->{lang} }; my $flag = as_html_img( $country ); $flag =~ s{ \s* / > }{>}mx if ($s->{content_type} eq q(text/html)); $content = { text => $flag, type => q(label), widget => 1 }; $self->stash_content( $content, q(footer) ); } return; } sub add_header { my $self = shift; my $s = $self->context->stash; $self->stash_content( $self->_logo_link, q(header) ); $self->stash_content( $self->_company_link, q(header) ); $self->stash_meta ( { title => $s->{title} }, q(header) ); return; } sub add_hidden { # Add a hidden input field to the form my ($self, $name, $values) = @_; return unless ($name && defined $values); $values = [ $values ] unless (ref $values eq q(ARRAY)); for my $value (@{ $values }) { my $content = { default => $value, name => $name, type => q(hidden), widget => 1 }; $self->stash_content( $content, q(hidden), q(clear_hidden) ); } return; } sub add_result { # Add some content the the result div my ($self, $content) = @_; return unless ($content); chomp $content; my $s = $self->context->stash; if ($s->{result} and $s->{result}->{items}->[ 0 ]) { $s->{result}->{items}->[-1]->{content} .= "\n"; } $self->stash_content( $content, q(result), q(clear_result) ); return; } sub add_sidebar_panel { # Add an Ajax call to the side bar accordion widget my ($self, $args) = @_; my ($content, $count, $jscript); my $s = $self->context->stash; unless ($count = $s->{sidebar}->{count} || 0) { $self->_clear_by_id( q(sidebar) ); $s->{sidebar}->{tip} = $self->loc( q(sidebarTip) ); } if ($args->{name} eq q(default)) { $content = { content => { class => q(sidebarContent), id => q(default), text => $self->loc( q(sidebarBlankContent) ) }, contentClass => q(sidebarPanel), contentId => q(panel).$count.q(Content), header => { content => $self->loc( q(sidebarBlankHeader)) }, headerClass => q(sidebarHeader sidebarHeaderFirst), headerId => q(glassHeader), panelId => q(glassPanel) }; } else { $jscript = "behaviour.loadMore.request('".$args->{name}."', '"; $jscript .= $args->{name}."', '".($args->{value} || $NUL)."')"; $args->{heading} = ucfirst $args->{name} unless ($args->{heading}); $content = { content => { class => q(sidebarContent), id => $args->{name}, text => q( ) }, contentClass => q(sidebarPanel), contentId => q(panel).$count.q(Content), header => { onclick => $jscript, content => $args->{heading} }, headerClass => q(sidebarHeader), headerId => $args->{name}.q(Header), panelId => $args->{name}.q(Panel) }; } $self->stash_content( $content, q(sidebar), q(clear_sidebar) ); return $s->{sidebar}->{count} - 1; } sub stash_form { my ($self, $content) = @_; $self->stash_content( $content, q(sdata), q(clear_form) ); return; } # Clear content methods. Called by the stash content methods on first use sub clear_append { my ($self, $args) = @_; $self->_clear_by_id( q(append), $args ); return; } sub clear_buttons { my ($self, $args) = @_; $self->_clear_by_id( q(bbar), $args ); return; } sub clear_footer { my ($self, $args) = @_; $self->_clear_by_id( q(footer), $args ); return; } sub clear_form { # Clear the stash of all form content my ($self, $args) = @_; my $s = $self->context->stash; my $id = q(sdata); return if (exists $s->{ $id }); $self->_clear_by_id( $id, $args ); if (exists $args->{title}) { $s->{title} = $args->{title}; $s->{header}->{title} = $args->{title}; } $s->{firstfld} = $args->{firstfld} || $NUL; return; } sub clear_hidden { my ($self, $args) = @_; $self->_clear_by_id( q(hidden), $args ); return; } sub clear_menus { my $self = shift; $self->_clear_by_id( q(menus) ); return; } sub clear_quick_links { my $self = shift; $self->_clear_by_id( q(quick_links) ); return; } sub clear_result { my ($self, $args) = @_; my $s = $self->context->stash; $self->_clear_by_id( q(result), $args ); $s->{result}->{class} = q(centre); $s->{result}->{text } = 'Results'; return; } sub clear_sidebar { my $self = shift; $self->context->stash( sidebar => 0 ); return; } # Curried stash content methods sub add_buttons { my ($self, @buttons) = @_; my $title = $self->loc( q(buttonTitle) ); for (0 .. $#buttons) { $self->add_button( { label => $buttons[ $_ ], title => $title } ); } return; } sub add_chooser { my ($self, $args) = @_; my ($jscript, $param); my $attr = $args->{attr}; my $field = $args->{field}; my $form = $args->{form}; my $method = $args->{method}; my $val = $args->{value}; my $w_fld = $args->{where_fld}; my $w_val = $args->{where_val}; $param->{ $w_fld } = $w_val if ($w_fld); $param->{ $field } = { like => $val ? $val : q(%) }; my @items = $self->$method( $param ); unless ($items[0]) { $self->add_field( { text => $self->loc( 'Nothing selected' ), type => q(label) } ); return; } for my $item (@items) { $jscript = "behaviour.submit.returnValue('"; $jscript .= "${form}', '${field}', '".$item->$attr()."') "; $self->add_field( { class => $args->{class}, clear => q(left), href => '#top', onclick => $jscript, text => $item->$attr(), tip => $self->loc( 'Click to select' ), type => q(anchor) } ); } my $s = $self->context->stash; $s->{is_popup} = q(true); # Stop JS from caching window size $s->{header }->{title} = $self->loc( 'Select Item' ); delete $s->{token}; return; } sub add_error { # Handle $self->catch error thrown by a call to the model my ($self, $e, $verbosity, $offset) = @_; unless (defined $verbosity) { $verbosity = $self->context->stash->{debug} ? 3 : 2; } $offset = 1 unless (defined $offset); my $err = $self->loc( $e->as_string( $verbosity, $offset ), @{ $e->args } ); $self->log_error ( (ref $self).$SPC.(split m{ \n }mx, $err)[0] ); $self->add_result( $err ); return; } sub add_error_msg { my ($self, @rest) = @_; $self->add_error( $self->exception_class->new( $self->loc( @rest ) ) ); return; } sub add_field { # Add a field widget definition to the inner frame div my ($self, $content) = @_; my $s = $self->context->stash; return unless ($content and ref $content eq q(HASH)); if (exists $content->{subtype} && $content->{subtype} eq q(html)) { $s->{content}->{style} = q(overflow: hidden; padding: 0px;); } $content->{widget} = 1; $self->stash_form( $content ); return; } sub add_result_msg { my ($self, @rest) = @_; $self->add_result( $self->loc( @rest ) ); return; } sub add_search_links { my ($self, $page_info, $attrs) = @_; my ($key, $name, $page, $ref); $attrs ||= {}; my $s = $self->context->stash; my $expr = $attrs->{expression}; my $hits_per = $attrs->{hits_per}; my $href = $s->{form}->{action}.$SEP.$expr.$SEP; my $anchor_class = $attrs->{anchor_class} || q(searchFade smaller); my $clear = 1; for $page (qw(first_page previous_page pages_in_set next_page last_page)) { if ($page eq q(pages_in_set)) { for (@{ $page_info->pages_in_set }) { if ($_ == $page_info->current_page) { $ref = { container => 1, text => q(…), type => q(label) }; } else { $ref = { class => $anchor_class, href => $href.$hits_per.$SEP.$_, name => q(page).$_, pwidth => 0, text => $_, type => q(anchor) }; } $self->add_field( $ref ); } } elsif ($key = $page_info->$page) { $name = (split m{ _ }mx, $page)[0]; $ref = { class => $anchor_class, href => $href.$hits_per.$SEP.$key, name => $name, pwidth => 0, text => $self->loc( $page.q(_anchor) ), type => q(anchor) }; if ($clear) { $ref->{class } .= q( clearLeft); $ref->{prompt} = $self->loc( q(page_prompt) ); } $self->add_field( $ref ); $clear = 0; } } return; } sub group_fields { # Enclose a group of form fields in a field set definition my ($self, $args) = @_; my $text; my $nitems = $args->{nitems} || $args->{nItems}; my $class = exists $args->{class} ? $args->{class} : q(fullWidth); return if (!$nitems || $nitems <= 0); $text = $args->{text} || $self->loc( $args->{id} || q(duh) ); $self->stash_form( { class => $class, group => 1, nitems => $nitems, text => $text } ); return; } sub search_page { my ($self, $args) = @_; my ($e, $hit, @hits, $link_num, $page_info, $text); my $cnt = 0; my $expr = $args->{expression}; my $excerpts = $args->{excerpts}; my $hits_per = $args->{hits_per}; my $key = $args->{key}; my $model = $args->{data_model}; my $offset = $args->{offset}; my $s = $self->context->stash; my $form = $s->{form}->{name}; my $ref = eval { $model->search_for( $expr, $hits_per, $offset ) }; return $self->add_error( $e ) if ($e = $self->catch); while ($hit = $ref->fetch_hit_hashref) { push @hits, $hit; $cnt++ } $ref = { current_page => $offset + 1, entries_per_page => $hits_per, mode => q(slide), total_entries => $ref->total_hits }; $page_info = Data::Pageset->new( $ref ); $link_num = 1 + $hits_per * $offset; $text = $self->loc( $key, $expr ); $self->add_field( { id => $form.q(.).$key, text => $text } ); $text = $self->loc( q(search_results), $offset +1, $page_info->last_page, $cnt, $ref->{total_entries} ); $self->add_field( { id => $form.q(.search_results), text => $text } ); $self->add_search_links( $page_info, { expression => $expr, hits_per => $hits_per } ); for $hit (@hits) { $self->add_field( { href => $s->{url}.$hit->{url}, id => $form.q(.title), stepno => $link_num++, text => $hit->{title} } ); $self->add_field( { id => $form.q(.excerpt), text => $hit->{excerpts}->{ $excerpts } } ); $self->add_field( { id => $form.q(.score), pwidth => 0, text => sprintf '%0.3f', $hit->{score} } ); $self->add_field( { id => $form.q(.file), pwidth => 0, text => $hit->{file} } ); $self->add_field( { id => $form.q(.key), pwidth => 0, text => $hit->{key} } ); } $self->add_search_links( $page_info, { expression => $expr, hits_per => $hits_per } ); return; } sub simple_page { # Knock up a page of simple content from the XML config files my ($self, $name, $n_cols) = @_; my ($page, $ref, $subh, $text); my $s = $self->context->stash; return $self->add_error_msg( 'No page specified' ) unless ($name); unless ($page = $s->{pages}->{ $name }) { return $self->add_error_msg( 'Page [_1] unknown', $name ); } unless (exists $s->{sdata}) { delete $s->{token}; # Do not need a CSRF token on a simple page $self->clear_form ( { heading => $page->{heading} || $s->{title}, subHeading => { content => $page->{subHeading} || q( ) }, title => $page->{title} || $s->{title} } ); } my $idx = 0; my $data = { values => [] }; my $para = $page->{vals}->{ q(para).$idx }; while ($text = $para->{text}) { my $drop = $para->{dropcap} || 0; my $mark = $para->{markdown} || 0; $ref = { class => q(), text => { dropcap => $drop, markdown => $mark, text => $text, type => q(label) } }; if ($subh = $page->{vals}->{ q(subHeading).$idx }->{text}) { $ref->{heading} = { text => $subh, type => q(label) }; } push @{ $data->{values} }, $ref; $para = $page->{vals}->{ q(para).++$idx }; } my $columns = $n_cols || $page->{columns}; my $col_class = ($columns > 1 ? 'multi' : 'one').'Column '.$page->{class}; $self->add_field( { class => q(fullWidth), column_class => $col_class, columns => $columns, container => 1, container_class => q(paragraphs centre), data => $data, hclass => q(subheading), type => q(paragraphs) } ); return 1; } # Stash meta methods sub check_field_wrapper { # Process Ajax calls to validate form field values my $self = shift; my $s = $self->context->stash; my $id = $self->query_value( q(id) ); my $val = $self->query_value( q(val) ); my $e; delete $s->{token}; $self->stash_meta( { id => $id.q(_checkField), result => q(hidden) } ); eval { $self->check_field( $id, $val ) }; return unless ($e = $self->catch); if ($s->{debug}) { $self->log_debug( $self->loc( $e->as_string( 1 ), $id, $val ) ); } $self->stash_meta( { result => q(error) } ); return; } # Supporting cast sub clear_controls { # Clear contents of multiple divs my $self = shift; $self->clear_footer; $self->clear_menus; $self->clear_quick_links; $self->clear_sidebar; return; } sub open_window { my ($self, @rest) = @_; my ($jscript, $text); my $args = $self->arg_list( @rest ); return unless ($args->{key} and $args->{href}); $text = 'dependent=no, width='.($args->{width} || 800); $text .= ', height='.($args->{height} || 600).', resizable=yes, '; $text .= 'screenX=0, screenY=0, titlebar=no, scrollbars=yes'; $jscript = "behaviour.window.openWindow('".$args->{href}."', '"; $jscript .= $args->{key}."', '${text}')"; return $jscript; } # Private methods sub _clear_by_id { my ($self, $id, $args) = @_; return unless ($id); my $s = $self->context->stash; $s->{ $id } ||= {}; $args ||= {}; $s->{ $id }->{count } = 0; $s->{ $id }->{heading } = $args->{heading} || $NUL; $s->{ $id }->{items } = []; $s->{ $id }->{subHeading} = $args->{subHeading} if (exists $args->{subHeading}); return; } sub _company_link { my $self = shift; my $s = $self->context->stash; my $href = $self->uri_for( q(root).$SEP.q(company), $s->{lang} ); my $tip = $DOTS.$TTS.$self->loc( q(aboutCompanyTip) ); my $content = { class => q(headerFade), container_class => $s->{class}, container_id => q(headerSubTitle), href => '#top', onclick => $self->open_window( key => q(company), href => $href ), sep => q(), text => $s->{company}, tip => $tip, type => q(anchor), widget => 1 }; return $content; } sub _footer_line { my $self = shift; my ($content, $item, $jscript, $tip); my $s = $self->context->stash; # Cut on the dotted line toggle the footer visibilty $item = 1 + ($s->{is_administrator} ? 1 : 0); $jscript = "behaviour.state.toggleSwapText('tools0item${item}"; $jscript .= "', 'footer', '".$self->loc( q(footerOffText) )."', '"; $jscript .= $self->loc( q(footerOnText) )."')"; $tip = $self->loc( q(footerToggleTip) ); $content = { alt => 'Close Footer', class => q(footer), href => '#top', imgclass => q(footer), onclick => $jscript, text => $s->{assets}.'footerCut.gif', tip => $tip, type => q(rule), widget => 1 }; return $content; } sub _logo_link { my $self = shift; my $s = $self->context->stash; my $href = $s->{server_home} || 'http://'.$s->{domain}; my $content = { class => q(logo), container_class => $s->{class}, container_id => q(companyLogo), fhelp => q(Company Logo), hint_title => $href, href => $href, imgclass => q(logo), sep => $NUL, text => $s->{assets}.($s->{logo} || q(logo.png)), tip => $self->loc( q(logoTip) ), type => q(anchor), widget => 1 }; return $content; } 1; __END__
# Local Variables: # mode: perl # tab-width: 3 # End: