1#============================================================= -*-Perl-*- 2# 3# Template::View 4# 5# DESCRIPTION 6# A custom view of a template processing context. Can be used to 7# implement custom "skins". 8# 9# AUTHOR 10# Andy Wardley <abw@kfs.org> 11# 12# COPYRIGHT 13# Copyright (C) 2000 Andy Wardley. All Rights Reserved. 14# 15# This module is free software; you can redistribute it and/or 16# modify it under the same terms as Perl itself. 17# 18# TODO 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, { }) %] 23# 24#============================================================================ 25 26package Template::View; 27 28use strict; 29use warnings; 30use base 'Template::Base'; 31 32our $VERSION = 2.91; 33our $DEBUG = 0 unless defined $DEBUG; 34our @BASEARGS = qw( context ); 35our $AUTOLOAD; 36our $MAP = { 37 HASH => 'hash', 38 ARRAY => 'list', 39 TEXT => 'text', 40 default => '', 41}; 42 43 44#------------------------------------------------------------------------ 45# _init(\%config) 46# 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#------------------------------------------------------------------------ 52 53sub _init { 54 my ($self, $config) = @_; 55 56 # move 'context' somewhere more private 57 $self->{ _CONTEXT } = $self->{ context }; 58 delete $self->{ context }; 59 60 # generate table mapping object types to templates 61 my $map = $config->{ map } || { }; 62 $map->{ default } = $config->{ default } unless defined $map->{ default }; 63 $self->{ map } = { 64 %$MAP, 65 %$map, 66 }; 67 68 # local BLOCKs definition table 69 $self->{ _BLOCKS } = $config->{ blocks } || { }; 70 71 # name of presentation method which printed objects might provide 72 $self->{ method } = defined $config->{ method } 73 ? $config->{ method } : 'present'; 74 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; 81 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 } || ''; 85 } 86 87 # check that any base specified is defined 88 return $self->error('Invalid base specified for view') 89 if exists $config->{ base } && ! $self->{ base }; 90 91 # name of data item used by view() 92 $self->{ item } = $config->{ item } || 'item'; 93 94 # map methods of form ${include_prefix}_foobar() to include('foobar')? 95 $self->{ include_prefix } = $config->{ include_prefix } || 'include_'; 96 # what about mapping foobar() to include('foobar')? 97 $self->{ include_naked } = defined $config->{ include_naked } 98 ? $config->{ include_naked } : 1; 99 100 # map methods of form ${view_prefix}_foobar() to include('foobar')? 101 $self->{ view_prefix } = $config->{ view_prefix } || 'view_'; 102 # what about mapping foobar() to view('foobar')? 103 $self->{ view_naked } = $config->{ view_naked } || 0; 104 105 # the view is initially unsealed, allowing directives in the initial 106 # view template to create data items via the AUTOLOAD; once sealed via 107 # call to seal(), the AUTOLOAD will not update any internal items. 108 delete @$config{ qw( base method map default prefix suffix notfound item 109 include_prefix include_naked silent sealed 110 view_prefix view_naked blocks ) }; 111 $config = { %{ $self->{ base }->{ data } }, %$config } 112 if $self->{ base }; 113 $self->{ data } = $config; 114 $self->{ SEALED } = 0; 115 116 return $self; 117} 118 119 120#------------------------------------------------------------------------ 121# seal() 122# unseal() 123# 124# Seal or unseal the view to allow/prevent new datat items from being 125# automatically created by the AUTOLOAD method. 126#------------------------------------------------------------------------ 127 128sub seal { 129 my $self = shift; 130 $self->{ SEALED } = $self->{ sealed }; 131} 132 133sub unseal { 134 my $self = shift; 135 $self->{ SEALED } = 0; 136} 137 138 139#------------------------------------------------------------------------ 140# clone(\%config) 141# 142# Cloning method which takes a copy of $self and then applies to it any 143# modifications specified in the $config hash passed as an argument. 144# Configuration items may also be specified as a list of "name => $value" 145# arguments. Returns a reference to the cloned Template::View object. 146# 147# NOTE: may need to copy BLOCKS??? 148#------------------------------------------------------------------------ 149 150sub clone { 151 my $self = shift; 152 my $clone = bless { %$self }, ref $self; 153 my $config = ref $_[0] eq 'HASH' ? shift : { @_ }; 154 155 # merge maps 156 $clone->{ map } = { 157 %{ $self->{ map } }, 158 %{ $config->{ map } || { } }, 159 }; 160 161 # "map => { default=>'xxx' }" can be specified as "default => 'xxx'" 162 $clone->{ map }->{ default } = $config->{ default } 163 if defined $config->{ default }; 164 165 # update any remaining config items 166 my @args = qw( base prefix suffix notfound item method include_prefix 167 include_naked view_prefix view_naked ); 168 foreach my $arg (@args) { 169 $clone->{ $arg } = $config->{ $arg } if defined $config->{ $arg }; 170 } 171 push(@args, qw( default map )); 172 delete @$config{ @args }; 173 174 # anything left is data 175 my $data = $clone->{ data } = { %{ $self->{ data } } }; 176 @$data{ keys %$config } = values %$config; 177 178 return $clone; 179} 180 181 182#------------------------------------------------------------------------ 183# print(@items, ..., \%config) 184# 185# Prints @items in turn by mapping each to an approriate template using 186# the internal 'map' hash. If an entry isn't found and the item is an 187# object that implements the method named in the internal 'method' item, 188# (default: 'present'), then the method will be called passing a reference 189# to $self, against which the presenter method may make callbacks (e.g. 190# to view_item()). If the presenter method isn't implemented, then the 191# 'default' map entry is consulted and used if defined. The final argument 192# may be a reference to a hash array providing local overrides to the internal 193# defaults for various items (prefix, suffix, etc). In the presence 194# of this parameter, a clone of the current object is first made, applying 195# any configuration updates, and control is then delegated to it. 196#------------------------------------------------------------------------ 197 198sub print { 199 my $self = shift; 200 201 # if final config hash is specified then create a clone and delegate to it 202 # NOTE: potential problem when called print(\%data_hash1, \%data_hash2); 203 if ((scalar @_ > 1) && (ref $_[-1] eq 'HASH')) { 204 my $cfg = pop @_; 205 my $clone = $self->clone($cfg) 206 || return; 207 return $clone->print(@_) 208 || $self->error($clone->error()); 209 } 210 my ($item, $type, $template, $present); 211 my $method = $self->{ method }; 212 my $map = $self->{ map }; 213 my $output = ''; 214 215 # print each argument 216 foreach $item (@_) { 217 my $newtype; 218 219 if (! ($type = ref $item)) { 220 # non-references are TEXT 221 $type = 'TEXT'; 222 $template = $map->{ $type }; 223 } 224 elsif (! defined ($template = $map->{ $type })) { 225 # no specific map entry for object, maybe it implements a 226 # 'present' (or other) method? 227 if ( $method && UNIVERSAL::can($item, $method) ) { 228 $present = $item->$method($self); ## call item method 229 # undef returned indicates error, note that we expect 230 # $item to have called error() on the view 231 return unless defined $present; 232 $output .= $present; 233 next; ## NEXT 234 } 235 elsif ( ref($item) eq 'HASH' 236 && defined($newtype = $item->{$method}) 237 && defined($template = $map->{"$method=>$newtype"})) { 238 } 239 elsif ( defined($newtype) 240 && defined($template = $map->{"$method=>*"}) ) { 241 $template =~ s/\*/$newtype/; 242 } 243 elsif (! ($template = $map->{ default }) ) { 244 # default not defined, so construct template name from type 245 ($template = $type) =~ s/\W+/_/g; 246 } 247 } 248# else { 249# $self->DEBUG("defined map type for $type: $template\n"); 250# } 251 $self->DEBUG("printing view '", $template || '', "', $item\n") if $DEBUG; 252 $output .= $self->view($template, $item) 253 if $template; 254 } 255 return $output; 256} 257 258 259#------------------------------------------------------------------------ 260# view($template, $item, \%vars) 261# 262# Wrapper around include() which expects a template name, $template, 263# followed by a data item, $item, and optionally, a further hash array 264# of template variables. The $item is added as an entry to the $vars 265# hash (which is created empty if not passed as an argument) under the 266# name specified by the internal 'item' member, which is appropriately 267# 'item' by default. Thus an external object present() method can 268# callback against this object method, simply passing a data item to 269# be displayed. The external object doesn't have to know what the 270# view expects the item to be called in the $vars hash. 271#------------------------------------------------------------------------ 272 273sub view { 274 my ($self, $template, $item) = splice(@_, 0, 3); 275 my $vars = ref $_[0] eq 'HASH' ? shift : { @_ }; 276 $vars->{ $self->{ item } } = $item if defined $item; 277 $self->include($template, $vars); 278} 279 280 281#------------------------------------------------------------------------ 282# include($template, \%vars) 283# 284# INCLUDE a template, $template, mapped according to the current prefix, 285# suffix, default, etc., where $vars is an optional hash reference 286# containing template variable definitions. If the template isn't found 287# then the method will default to any 'notfound' template, if defined 288# as an internal item. 289#------------------------------------------------------------------------ 290 291sub include { 292 my ($self, $template, $vars) = @_; 293 my $context = $self->{ _CONTEXT }; 294 295 $template = $self->template($template); 296 297 $vars = { } unless ref $vars eq 'HASH'; 298 $vars->{ view } ||= $self; 299 300 $context->include( $template, $vars ); 301 302# DEBUGGING 303# my $out = $context->include( $template, $vars ); 304# print STDERR "VIEW return [$out]\n"; 305# return $out; 306} 307 308 309#------------------------------------------------------------------------ 310# template($template) 311# 312# Returns a compiled template for the specified template name, according 313# to the current configuration parameters. 314#------------------------------------------------------------------------ 315 316sub template { 317 my ($self, $name) = @_; 318 my $context = $self->{ _CONTEXT }; 319 return $context->throw(Template::Constants::ERROR_VIEW, 320 "no view template specified") 321 unless $name; 322 323 my $notfound = $self->{ notfound }; 324 my $base = $self->{ base }; 325 my ($template, $block, $error); 326 327 return $block 328 if ($block = $self->{ _BLOCKS }->{ $name }); 329 330 # try the named template 331 $template = $self->template_name($name); 332 $self->DEBUG("looking for $template\n") if $DEBUG; 333 eval { $template = $context->template($template) }; 334 335 # try asking the base view if not found 336 if (($error = $@) && $base) { 337 $self->DEBUG("asking base for $name\n") if $DEBUG; 338 eval { $template = $base->template($name) }; 339 } 340 341 # try the 'notfound' template (if defined) if that failed 342 if (($error = $@) && $notfound) { 343 unless ($template = $self->{ _BLOCKS }->{ $notfound }) { 344 $notfound = $self->template_name($notfound); 345 $self->DEBUG("not found, looking for $notfound\n") if $DEBUG; 346 eval { $template = $context->template($notfound) }; 347 348 return $context->throw(Template::Constants::ERROR_VIEW, $error) 349 if $@; # return first error 350 } 351 } 352 elsif ($error) { 353 $self->DEBUG("no 'notfound'\n") 354 if $DEBUG; 355 return $context->throw(Template::Constants::ERROR_VIEW, $error); 356 } 357 return $template; 358} 359 360 361#------------------------------------------------------------------------ 362# template_name($template) 363# 364# Returns the name of the specified template with any appropriate prefix 365# and/or suffix added. 366#------------------------------------------------------------------------ 367 368sub template_name { 369 my ($self, $template) = @_; 370 $template = $self->{ prefix } . $template . $self->{ suffix } 371 if $template; 372 373 $self->DEBUG("template name: $template\n") if $DEBUG; 374 return $template; 375} 376 377 378#------------------------------------------------------------------------ 379# default($val) 380# 381# Special case accessor to retrieve/update 'default' as an alias for 382# '$map->{ default }'. 383#------------------------------------------------------------------------ 384 385sub default { 386 my $self = shift; 387 return @_ ? ($self->{ map }->{ default } = shift) 388 : $self->{ map }->{ default }; 389} 390 391 392#------------------------------------------------------------------------ 393# AUTOLOAD 394# 395 396# Returns/updates public internal data items (i.e. not prefixed '_' or 397# '.') or presents a view if the method matches the view_prefix item, 398# e.g. view_foo(...) => view('foo', ...). Similarly, the 399# include_prefix is used, if defined, to map include_foo(...) to 400# include('foo', ...). If that fails then the entire method name will 401# be used as the name of a template to include iff the include_named 402# parameter is set (default: 1). Last attempt is to match the entire 403# method name to a view() call, iff view_naked is set. Otherwise, a 404# 'view' exception is raised reporting the error "no such view member: 405# $method". 406#------------------------------------------------------------------------ 407 408sub AUTOLOAD { 409 my $self = shift; 410 my $item = $AUTOLOAD; 411 $item =~ s/.*:://; 412 return if $item eq 'DESTROY'; 413 414 if ($item =~ /^[\._]/) { 415 return $self->{ _CONTEXT }->throw(Template::Constants::ERROR_VIEW, 416 "attempt to view private member: $item"); 417 } 418 elsif (exists $self->{ $item }) { 419 # update existing config item (e.g. 'prefix') if unsealed 420 return $self->{ _CONTEXT }->throw(Template::Constants::ERROR_VIEW, 421 "cannot update config item in sealed view: $item") 422 if @_ && $self->{ SEALED }; 423 $self->DEBUG("accessing item: $item\n") if $DEBUG; 424 return @_ ? ($self->{ $item } = shift) : $self->{ $item }; 425 } 426 elsif (exists $self->{ data }->{ $item }) { 427 # get/update existing data item (must be unsealed to update) 428 if (@_ && $self->{ SEALED }) { 429 return $self->{ _CONTEXT }->throw(Template::Constants::ERROR_VIEW, 430 "cannot update item in sealed view: $item") 431 unless $self->{ silent }; 432 # ignore args if silent 433 @_ = (); 434 } 435 $self->DEBUG(@_ ? "updating data item: $item <= $_[0]\n" 436 : "returning data item: $item\n") if $DEBUG; 437 return @_ ? ($self->{ data }->{ $item } = shift) 438 : $self->{ data }->{ $item }; 439 } 440 elsif (@_ && ! $self->{ SEALED }) { 441 # set data item if unsealed 442 $self->DEBUG("setting unsealed data: $item => @_\n") if $DEBUG; 443 $self->{ data }->{ $item } = shift; 444 } 445 elsif ($item =~ s/^$self->{ view_prefix }//) { 446 $self->DEBUG("returning view($item)\n") if $DEBUG; 447 return $self->view($item, @_); 448 } 449 elsif ($item =~ s/^$self->{ include_prefix }//) { 450 $self->DEBUG("returning include($item)\n") if $DEBUG; 451 return $self->include($item, @_); 452 } 453 elsif ($self->{ include_naked }) { 454 $self->DEBUG("returning naked include($item)\n") if $DEBUG; 455 return $self->include($item, @_); 456 } 457 elsif ($self->{ view_naked }) { 458 $self->DEBUG("returning naked view($item)\n") if $DEBUG; 459 return $self->view($item, @_); 460 } 461 else { 462 return $self->{ _CONTEXT }->throw(Template::Constants::ERROR_VIEW, 463 "no such view member: $item"); 464 } 465} 466 467 4681; 469 470 471__END__ 472 473=head1 NAME 474 475Template::View - customised view of a template processing context 476 477=head1 SYNOPSIS 478 479 # define a view 480 [% VIEW view 481 # some standard args 482 prefix => 'my_', 483 suffix => '.tt2', 484 notfound => 'no_such_file' 485 ... 486 487 # any other data 488 title => 'My View title' 489 other_item => 'Joe Random Data' 490 ... 491 %] 492 # add new data definitions, via 'my' self reference 493 [% my.author = "$abw.name <$abw.email>" %] 494 [% my.copy = "© Copyright 2000 $my.author" %] 495 496 # define a local block 497 [% BLOCK header %] 498 This is the header block, title: [% title or my.title %] 499 [% END %] 500 501 [% END %] 502 503 # access data items for view 504 [% view.title %] 505 [% view.other_item %] 506 507 # access blocks directly ('include_naked' option, set by default) 508 [% view.header %] 509 [% view.header(title => 'New Title') %] 510 511 # non-local templates have prefix/suffix attached 512 [% view.footer %] # => [% INCLUDE my_footer.tt2 %] 513 514 # more verbose form of block access 515 [% view.include( 'header', title => 'The Header Title' ) %] 516 [% view.include_header( title => 'The Header Title' ) %] 517 518 # very short form of above ('include_naked' option, set by default) 519 [% view.header( title => 'The Header Title' ) %] 520 521 # non-local templates have prefix/suffix attached 522 [% view.footer %] # => [% INCLUDE my_footer.tt2 %] 523 524 # fallback on the 'notfound' template ('my_no_such_file.tt2') 525 # if template not found 526 [% view.include('missing') %] 527 [% view.include_missing %] 528 [% view.missing %] 529 530 # print() includes a template relevant to argument type 531 [% view.print("some text") %] # type=TEXT, template='text' 532 533 [% BLOCK my_text.tt2 %] # 'text' with prefix/suffix 534 Text: [% item %] 535 [% END %] 536 537 # now print() a hash ref, mapped to 'hash' template 538 [% view.print(some_hash_ref) %] # type=HASH, template='hash' 539 540 [% BLOCK my_hash.tt2 %] # 'hash' with prefix/suffix 541 hash keys: [% item.keys.sort.join(', ') 542 [% END %] 543 544 # now print() a list ref, mapped to 'list' template 545 [% view.print(my_list_ref) %] # type=ARRAY, template='list' 546 547 [% BLOCK my_list.tt2 %] # 'list' with prefix/suffix 548 list: [% item.join(', ') %] 549 [% END %] 550 551 # print() maps 'My::Object' to 'My_Object' 552 [% view.print(myobj) %] 553 554 [% BLOCK my_My_Object.tt2 %] 555 [% item.this %], [% item.that %] 556 [% END %] 557 558 # update mapping table 559 [% view.map.ARRAY = 'my_list_template' %] 560 [% view.map.TEXT = 'my_text_block' %] 561 562 563 # change prefix, suffix, item name, etc. 564 [% view.prefix = 'your_' %] 565 [% view.default = 'anyobj' %] 566 ... 567 568=head1 DESCRIPTION 569 570TODO 571 572=head1 METHODS 573 574=head2 new($context, \%config) 575 576Creates a new Template::View presenting a custom view of the specified 577$context object. 578 579A reference to a hash array of configuration options may be passed as the 580second argument. 581 582=over 4 583 584=item prefix 585 586Prefix added to all template names. 587 588 [% USE view(prefix => 'my_') %] 589 [% view.view('foo', a => 20) %] # => my_foo 590 591=item suffix 592 593Suffix added to all template names. 594 595 [% USE view(suffix => '.tt2') %] 596 [% view.view('foo', a => 20) %] # => foo.tt2 597 598=item map 599 600Hash array mapping reference types to template names. The print() 601method uses this to determine which template to use to present any 602particular item. The TEXT, HASH and ARRAY items default to 'test', 603'hash' and 'list' appropriately. 604 605 [% USE view(map => { ARRAY => 'my_list', 606 HASH => 'your_hash', 607 My::Foo => 'my_foo', } ) %] 608 609 [% view.print(some_text) %] # => text 610 [% view.print(a_list) %] # => my_list 611 [% view.print(a_hash) %] # => your_hash 612 [% view.print(a_foo) %] # => my_foo 613 614 [% BLOCK text %] 615 Text: [% item %] 616 [% END %] 617 618 [% BLOCK my_list %] 619 list: [% item.join(', ') %] 620 [% END %] 621 622 [% BLOCK your_hash %] 623 hash keys: [% item.keys.sort.join(', ') 624 [% END %] 625 626 [% BLOCK my_foo %] 627 Foo: [% item.this %], [% item.that %] 628 [% END %] 629 630=item method 631 632Name of a method which objects passed to print() may provide for presenting 633themselves to the view. If a specific map entry can't be found for an 634object reference and it supports the method (default: 'present') then 635the method will be called, passing the view as an argument. The object 636can then make callbacks against the view to present itself. 637 638 package Foo; 639 640 sub present { 641 my ($self, $view) = @_; 642 return "a regular view of a Foo\n"; 643 } 644 645 sub debug { 646 my ($self, $view) = @_; 647 return "a debug view of a Foo\n"; 648 } 649 650In a template: 651 652 [% USE view %] 653 [% view.print(my_foo_object) %] # a regular view of a Foo 654 655 [% USE view(method => 'debug') %] 656 [% view.print(my_foo_object) %] # a debug view of a Foo 657 658=item default 659 660Default template to use if no specific map entry is found for an item. 661 662 [% USE view(default => 'my_object') %] 663 664 [% view.print(objref) %] # => my_object 665 666If no map entry or default is provided then the view will attempt to 667construct a template name from the object class, substituting any 668sequence of non-word characters to single underscores, e.g. 669 670 # 'fubar' is an object of class Foo::Bar 671 [% view.print(fubar) %] # => Foo_Bar 672 673Any current prefix and suffix will be added to both the default template 674name and any name constructed from the object class. 675 676=item notfound 677 678Fallback template to use if any other isn't found. 679 680=item item 681 682Name of the template variable to which the print() method assigns the current 683item. Defaults to 'item'. 684 685 [% USE view %] 686 [% BLOCK list %] 687 [% item.join(', ') %] 688 [% END %] 689 [% view.print(a_list) %] 690 691 [% USE view(item => 'thing') %] 692 [% BLOCK list %] 693 [% thing.join(', ') %] 694 [% END %] 695 [% view.print(a_list) %] 696 697=item view_prefix 698 699Prefix of methods which should be mapped to view() by AUTOLOAD. Defaults 700to 'view_'. 701 702 [% USE view %] 703 [% view.view_header() %] # => view('header') 704 705 [% USE view(view_prefix => 'show_me_the_' %] 706 [% view.show_me_the_header() %] # => view('header') 707 708=item view_naked 709 710Flag to indcate if any attempt should be made to map method names to 711template names where they don't match the view_prefix. Defaults to 0. 712 713 [% USE view(view_naked => 1) %] 714 715 [% view.header() %] # => view('header') 716 717=back 718 719=head2 print( $obj1, $obj2, ... \%config) 720 721TODO 722 723=head2 view( $template, \%vars, \%config ); 724 725TODO 726 727=head1 AUTHOR 728 729Andy Wardley E<lt>abw@wardley.orgE<gt> L<http://wardley.org/> 730 731=head1 COPYRIGHT 732 733Copyright (C) 2000-2007 Andy Wardley. All Rights Reserved. 734 735This module is free software; you can redistribute it and/or 736modify it under the same terms as Perl itself. 737 738=head1 SEE ALSO 739 740L<Template::Plugin> 741 742=cut 743 744 745 746 747 748