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   = "&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