1 #============================================================= -*-Perl-*-
6 # A custom view of a template processing context. Can be used to
7 # implement custom "skins".
10 # Andy Wardley <abw@kfs.org>
13 # Copyright (C) 2000 Andy Wardley. All Rights Reserved.
15 # This module is free software; you can redistribute it and/or
16 # modify it under the same terms as Perl itself.
19 # * allowing print to have a hash ref as final args will cause problems
20 # if you do this: [% view.print(hash1, hash2, hash3) %]. Current
21 # work-around is to do [% view.print(hash1); view.print(hash2);
22 # view.print(hash3) %] or [% view.print(hash1, hash2, hash3, { }) %]
24 #============================================================================
26 package Template::View;
30 use base 'Template::Base';
33 our $DEBUG = 0 unless defined $DEBUG;
34 our @BASEARGS = qw( context );
44 #------------------------------------------------------------------------
47 # Initialisation method called by the Template::Base class new()
48 # constructor. $self->{ context } has already been set, by virtue of
49 # being named in @BASEARGS. Remaining config arguments are presented
50 # as a hash reference.
51 #------------------------------------------------------------------------
54 my ($self, $config) = @_;
56 # move 'context' somewhere more private
57 $self->{ _CONTEXT } = $self->{ context };
58 delete $self->{ context };
60 # generate table mapping object types to templates
61 my $map = $config->{ map } || { };
62 $map->{ default } = $config->{ default } unless defined $map->{ default };
68 # local BLOCKs definition table
69 $self->{ _BLOCKS } = $config->{ blocks } || { };
71 # name of presentation method which printed objects might provide
72 $self->{ method } = defined $config->{ method }
73 ? $config->{ method } : 'present';
75 # view is sealed by default preventing variable update after
76 # definition, however we don't actually seal a view until the
77 # END of the view definition
78 my $sealed = $config->{ sealed };
79 $sealed = 1 unless defined $sealed;
80 $self->{ sealed } = $sealed ? 1 : 0;
82 # copy remaining config items from $config or set defaults
83 foreach my $arg (qw( base prefix suffix notfound silent )) {
84 $self->{ $arg } = $config->{ $arg } || '';
87 # name of data item used by view()
88 $self->{ item } = $config->{ item } || 'item';
90 # map methods of form ${include_prefix}_foobar() to include('foobar')?
91 $self->{ include_prefix } = $config->{ include_prefix } || 'include_';
92 # what about mapping foobar() to include('foobar')?
93 $self->{ include_naked } = defined $config->{ include_naked }
94 ? $config->{ include_naked } : 1;
96 # map methods of form ${view_prefix}_foobar() to include('foobar')?
97 $self->{ view_prefix } = $config->{ view_prefix } || 'view_';
98 # what about mapping foobar() to view('foobar')?
99 $self->{ view_naked } = $config->{ view_naked } || 0;
101 # the view is initially unsealed, allowing directives in the initial
102 # view template to create data items via the AUTOLOAD; once sealed via
103 # call to seal(), the AUTOLOAD will not update any internal items.
104 delete @$config{ qw( base method map default prefix suffix notfound item
105 include_prefix include_naked silent sealed
106 view_prefix view_naked blocks ) };
107 $config = { %{ $self->{ base }->{ data } }, %$config }
109 $self->{ data } = $config;
110 $self->{ SEALED } = 0;
116 #------------------------------------------------------------------------
120 # Seal or unseal the view to allow/prevent new datat items from being
121 # automatically created by the AUTOLOAD method.
122 #------------------------------------------------------------------------
126 $self->{ SEALED } = $self->{ sealed };
131 $self->{ SEALED } = 0;
135 #------------------------------------------------------------------------
138 # Cloning method which takes a copy of $self and then applies to it any
139 # modifications specified in the $config hash passed as an argument.
140 # Configuration items may also be specified as a list of "name => $value"
141 # arguments. Returns a reference to the cloned Template::View object.
143 # NOTE: may need to copy BLOCKS???
144 #------------------------------------------------------------------------
148 my $clone = bless { %$self }, ref $self;
149 my $config = ref $_[0] eq 'HASH' ? shift : { @_ };
154 %{ $config->{ map } || { } },
157 # "map => { default=>'xxx' }" can be specified as "default => 'xxx'"
158 $clone->{ map }->{ default } = $config->{ default }
159 if defined $config->{ default };
161 # update any remaining config items
162 my @args = qw( base prefix suffix notfound item method include_prefix
163 include_naked view_prefix view_naked );
164 foreach my $arg (@args) {
165 $clone->{ $arg } = $config->{ $arg } if defined $config->{ $arg };
167 push(@args, qw( default map ));
168 delete @$config{ @args };
170 # anything left is data
171 my $data = $clone->{ data } = { %{ $self->{ data } } };
172 @$data{ keys %$config } = values %$config;
178 #------------------------------------------------------------------------
179 # print(@items, ..., \%config)
181 # Prints @items in turn by mapping each to an approriate template using
182 # the internal 'map' hash. If an entry isn't found and the item is an
183 # object that implements the method named in the internal 'method' item,
184 # (default: 'present'), then the method will be called passing a reference
185 # to $self, against which the presenter method may make callbacks (e.g.
186 # to view_item()). If the presenter method isn't implemented, then the
187 # 'default' map entry is consulted and used if defined. The final argument
188 # may be a reference to a hash array providing local overrides to the internal
189 # defaults for various items (prefix, suffix, etc). In the presence
190 # of this parameter, a clone of the current object is first made, applying
191 # any configuration updates, and control is then delegated to it.
192 #------------------------------------------------------------------------
197 # if final config hash is specified then create a clone and delegate to it
198 # NOTE: potential problem when called print(\%data_hash1, \%data_hash2);
199 if ((scalar @_ > 1) && (ref $_[-1] eq 'HASH')) {
201 my $clone = $self->clone($cfg)
203 return $clone->print(@_)
204 || $self->error($clone->error());
206 my ($item, $type, $template, $present);
207 my $method = $self->{ method };
208 my $map = $self->{ map };
211 # print each argument
215 if (! ($type = ref $item)) {
216 # non-references are TEXT
218 $template = $map->{ $type };
220 elsif (! defined ($template = $map->{ $type })) {
221 # no specific map entry for object, maybe it implements a
222 # 'present' (or other) method?
223 if ( $method && UNIVERSAL::can($item, $method) ) {
224 $present = $item->$method($self); ## call item method
225 # undef returned indicates error, note that we expect
226 # $item to have called error() on the view
227 return unless defined $present;
231 elsif ( ref($item) eq 'HASH'
232 && defined($newtype = $item->{$method})
233 && defined($template = $map->{"$method=>$newtype"})) {
235 elsif ( defined($newtype)
236 && defined($template = $map->{"$method=>*"}) ) {
237 $template =~ s/\*/$newtype/;
239 elsif (! ($template = $map->{ default }) ) {
240 # default not defined, so construct template name from type
241 ($template = $type) =~ s/\W+/_/g;
245 # $self->DEBUG("defined map type for $type: $template\n");
247 $self->DEBUG("printing view '", $template || '', "', $item\n") if $DEBUG;
248 $output .= $self->view($template, $item)
255 #------------------------------------------------------------------------
256 # view($template, $item, \%vars)
258 # Wrapper around include() which expects a template name, $template,
259 # followed by a data item, $item, and optionally, a further hash array
260 # of template variables. The $item is added as an entry to the $vars
261 # hash (which is created empty if not passed as an argument) under the
262 # name specified by the internal 'item' member, which is appropriately
263 # 'item' by default. Thus an external object present() method can
264 # callback against this object method, simply passing a data item to
265 # be displayed. The external object doesn't have to know what the
266 # view expects the item to be called in the $vars hash.
267 #------------------------------------------------------------------------
270 my ($self, $template, $item) = splice(@_, 0, 3);
271 my $vars = ref $_[0] eq 'HASH' ? shift : { @_ };
272 $vars->{ $self->{ item } } = $item if defined $item;
273 $self->include($template, $vars);
277 #------------------------------------------------------------------------
278 # include($template, \%vars)
280 # INCLUDE a template, $template, mapped according to the current prefix,
281 # suffix, default, etc., where $vars is an optional hash reference
282 # containing template variable definitions. If the template isn't found
283 # then the method will default to any 'notfound' template, if defined
284 # as an internal item.
285 #------------------------------------------------------------------------
288 my ($self, $template, $vars) = @_;
289 my $context = $self->{ _CONTEXT };
291 $template = $self->template($template);
293 $vars = { } unless ref $vars eq 'HASH';
294 $vars->{ view } ||= $self;
296 $context->include( $template, $vars );
299 # my $out = $context->include( $template, $vars );
300 # print STDERR "VIEW return [$out]\n";
305 #------------------------------------------------------------------------
306 # template($template)
308 # Returns a compiled template for the specified template name, according
309 # to the current configuration parameters.
310 #------------------------------------------------------------------------
313 my ($self, $name) = @_;
314 my $context = $self->{ _CONTEXT };
315 return $context->throw(Template::Constants::ERROR_VIEW,
316 "no view template specified")
319 my $notfound = $self->{ notfound };
320 my $base = $self->{ base };
321 my ($template, $block, $error);
324 if ($block = $self->{ _BLOCKS }->{ $name });
326 # try the named template
327 $template = $self->template_name($name);
328 $self->DEBUG("looking for $template\n") if $DEBUG;
329 eval { $template = $context->template($template) };
331 # try asking the base view if not found
332 if (($error = $@) && $base) {
333 $self->DEBUG("asking base for $name\n") if $DEBUG;
334 eval { $template = $base->template($name) };
337 # try the 'notfound' template (if defined) if that failed
338 if (($error = $@) && $notfound) {
339 unless ($template = $self->{ _BLOCKS }->{ $notfound }) {
340 $notfound = $self->template_name($notfound);
341 $self->DEBUG("not found, looking for $notfound\n") if $DEBUG;
342 eval { $template = $context->template($notfound) };
344 return $context->throw(Template::Constants::ERROR_VIEW, $error)
345 if $@; # return first error
349 $self->DEBUG("no 'notfound'\n")
351 return $context->throw(Template::Constants::ERROR_VIEW, $error);
357 #------------------------------------------------------------------------
358 # template_name($template)
360 # Returns the name of the specified template with any appropriate prefix
361 # and/or suffix added.
362 #------------------------------------------------------------------------
365 my ($self, $template) = @_;
366 $template = $self->{ prefix } . $template . $self->{ suffix }
369 $self->DEBUG("template name: $template\n") if $DEBUG;
374 #------------------------------------------------------------------------
377 # Special case accessor to retrieve/update 'default' as an alias for
378 # '$map->{ default }'.
379 #------------------------------------------------------------------------
383 return @_ ? ($self->{ map }->{ default } = shift)
384 : $self->{ map }->{ default };
388 #------------------------------------------------------------------------
392 # Returns/updates public internal data items (i.e. not prefixed '_' or
393 # '.') or presents a view if the method matches the view_prefix item,
394 # e.g. view_foo(...) => view('foo', ...). Similarly, the
395 # include_prefix is used, if defined, to map include_foo(...) to
396 # include('foo', ...). If that fails then the entire method name will
397 # be used as the name of a template to include iff the include_named
398 # parameter is set (default: 1). Last attempt is to match the entire
399 # method name to a view() call, iff view_naked is set. Otherwise, a
400 # 'view' exception is raised reporting the error "no such view member:
402 #------------------------------------------------------------------------
406 my $item = $AUTOLOAD;
408 return if $item eq 'DESTROY';
410 if ($item =~ /^[\._]/) {
411 return $self->{ _CONTEXT }->throw(Template::Constants::ERROR_VIEW,
412 "attempt to view private member: $item");
414 elsif (exists $self->{ $item }) {
415 # update existing config item (e.g. 'prefix') if unsealed
416 return $self->{ _CONTEXT }->throw(Template::Constants::ERROR_VIEW,
417 "cannot update config item in sealed view: $item")
418 if @_ && $self->{ SEALED };
419 $self->DEBUG("accessing item: $item\n") if $DEBUG;
420 return @_ ? ($self->{ $item } = shift) : $self->{ $item };
422 elsif (exists $self->{ data }->{ $item }) {
423 # get/update existing data item (must be unsealed to update)
424 if (@_ && $self->{ SEALED }) {
425 return $self->{ _CONTEXT }->throw(Template::Constants::ERROR_VIEW,
426 "cannot update item in sealed view: $item")
427 unless $self->{ silent };
428 # ignore args if silent
431 $self->DEBUG(@_ ? "updating data item: $item <= $_[0]\n"
432 : "returning data item: $item\n") if $DEBUG;
433 return @_ ? ($self->{ data }->{ $item } = shift)
434 : $self->{ data }->{ $item };
436 elsif (@_ && ! $self->{ SEALED }) {
437 # set data item if unsealed
438 $self->DEBUG("setting unsealed data: $item => @_\n") if $DEBUG;
439 $self->{ data }->{ $item } = shift;
441 elsif ($item =~ s/^$self->{ view_prefix }//) {
442 $self->DEBUG("returning view($item)\n") if $DEBUG;
443 return $self->view($item, @_);
445 elsif ($item =~ s/^$self->{ include_prefix }//) {
446 $self->DEBUG("returning include($item)\n") if $DEBUG;
447 return $self->include($item, @_);
449 elsif ($self->{ include_naked }) {
450 $self->DEBUG("returning naked include($item)\n") if $DEBUG;
451 return $self->include($item, @_);
453 elsif ($self->{ view_naked }) {
454 $self->DEBUG("returning naked view($item)\n") if $DEBUG;
455 return $self->view($item, @_);
458 return $self->{ _CONTEXT }->throw(Template::Constants::ERROR_VIEW,
459 "no such view member: $item");
471 Template::View - customised view of a template processing context
480 notfound => 'no_such_file'
484 title => 'My View title'
485 other_item => 'Joe Random Data'
488 # add new data definitions, via 'my' self reference
489 [% my.author = "$abw.name <$abw.email>" %]
490 [% my.copy = "© Copyright 2000 $my.author" %]
492 # define a local block
494 This is the header block, title: [% title or my.title %]
499 # access data items for view
501 [% view.other_item %]
503 # access blocks directly ('include_naked' option, set by default)
505 [% view.header(title => 'New Title') %]
507 # non-local templates have prefix/suffix attached
508 [% view.footer %] # => [% INCLUDE my_footer.tt2 %]
510 # more verbose form of block access
511 [% view.include( 'header', title => 'The Header Title' ) %]
512 [% view.include_header( title => 'The Header Title' ) %]
514 # very short form of above ('include_naked' option, set by default)
515 [% view.header( title => 'The Header Title' ) %]
517 # non-local templates have prefix/suffix attached
518 [% view.footer %] # => [% INCLUDE my_footer.tt2 %]
520 # fallback on the 'notfound' template ('my_no_such_file.tt2')
521 # if template not found
522 [% view.include('missing') %]
523 [% view.include_missing %]
526 # print() includes a template relevant to argument type
527 [% view.print("some text") %] # type=TEXT, template='text'
529 [% BLOCK my_text.tt2 %] # 'text' with prefix/suffix
533 # now print() a hash ref, mapped to 'hash' template
534 [% view.print(some_hash_ref) %] # type=HASH, template='hash'
536 [% BLOCK my_hash.tt2 %] # 'hash' with prefix/suffix
537 hash keys: [% item.keys.sort.join(', ')
540 # now print() a list ref, mapped to 'list' template
541 [% view.print(my_list_ref) %] # type=ARRAY, template='list'
543 [% BLOCK my_list.tt2 %] # 'list' with prefix/suffix
544 list: [% item.join(', ') %]
547 # print() maps 'My::Object' to 'My_Object'
548 [% view.print(myobj) %]
550 [% BLOCK my_My_Object.tt2 %]
551 [% item.this %], [% item.that %]
554 # update mapping table
555 [% view.map.ARRAY = 'my_list_template' %]
556 [% view.map.TEXT = 'my_text_block' %]
559 # change prefix, suffix, item name, etc.
560 [% view.prefix = 'your_' %]
561 [% view.default = 'anyobj' %]
570 =head2 new($context, \%config)
572 Creates a new Template::View presenting a custom view of the specified
575 A reference to a hash array of configuration options may be passed as the
582 Prefix added to all template names.
584 [% USE view(prefix => 'my_') %]
585 [% view.view('foo', a => 20) %] # => my_foo
589 Suffix added to all template names.
591 [% USE view(suffix => '.tt2') %]
592 [% view.view('foo', a => 20) %] # => foo.tt2
596 Hash array mapping reference types to template names. The print()
597 method uses this to determine which template to use to present any
598 particular item. The TEXT, HASH and ARRAY items default to 'test',
599 'hash' and 'list' appropriately.
601 [% USE view(map => { ARRAY => 'my_list',
603 My::Foo => 'my_foo', } ) %]
605 [% view.print(some_text) %] # => text
606 [% view.print(a_list) %] # => my_list
607 [% view.print(a_hash) %] # => your_hash
608 [% view.print(a_foo) %] # => my_foo
615 list: [% item.join(', ') %]
618 [% BLOCK your_hash %]
619 hash keys: [% item.keys.sort.join(', ')
623 Foo: [% item.this %], [% item.that %]
628 Name of a method which objects passed to print() may provide for presenting
629 themselves to the view. If a specific map entry can't be found for an
630 object reference and it supports the method (default: 'present') then
631 the method will be called, passing the view as an argument. The object
632 can then make callbacks against the view to present itself.
637 my ($self, $view) = @_;
638 return "a regular view of a Foo\n";
642 my ($self, $view) = @_;
643 return "a debug view of a Foo\n";
649 [% view.print(my_foo_object) %] # a regular view of a Foo
651 [% USE view(method => 'debug') %]
652 [% view.print(my_foo_object) %] # a debug view of a Foo
656 Default template to use if no specific map entry is found for an item.
658 [% USE view(default => 'my_object') %]
660 [% view.print(objref) %] # => my_object
662 If no map entry or default is provided then the view will attempt to
663 construct a template name from the object class, substituting any
664 sequence of non-word characters to single underscores, e.g.
666 # 'fubar' is an object of class Foo::Bar
667 [% view.print(fubar) %] # => Foo_Bar
669 Any current prefix and suffix will be added to both the default template
670 name and any name constructed from the object class.
674 Fallback template to use if any other isn't found.
678 Name of the template variable to which the print() method assigns the current
679 item. Defaults to 'item'.
683 [% item.join(', ') %]
685 [% view.print(a_list) %]
687 [% USE view(item => 'thing') %]
689 [% thing.join(', ') %]
691 [% view.print(a_list) %]
695 Prefix of methods which should be mapped to view() by AUTOLOAD. Defaults
699 [% view.view_header() %] # => view('header')
701 [% USE view(view_prefix => 'show_me_the_' %]
702 [% view.show_me_the_header() %] # => view('header')
706 Flag to indcate if any attempt should be made to map method names to
707 template names where they don't match the view_prefix. Defaults to 0.
709 [% USE view(view_naked => 1) %]
711 [% view.header() %] # => view('header')
715 =head2 print( $obj1, $obj2, ... \%config)
719 =head2 view( $template, \%vars, \%config );
725 Andy Wardley E<lt>abw@wardley.orgE<gt> L<http://wardley.org/>
729 Copyright (C) 2000-2007 Andy Wardley. All Rights Reserved.
731 This module is free software; you can redistribute it and/or
732 modify it under the same terms as Perl itself.