File Coverage

blib/lib/Catalyst/View/Seamstress.pm
Criterion Covered Total %
statement 9 60 15.0
branch 0 24 0.0
condition 0 11 0.0
subroutine 3 5 60.0
pod 0 2 0.0
total 12 102 11.7


line stmt bran cond sub pod time code
1             package Catalyst::View::Seamstress;
2              
3 1     1   22781 use strict;
  1         3  
  1         39  
4             # [10:05:57] <@andyg> Catalyst::View is the correct base class
5 1     1   5 use base qw/Catalyst::View/;
  1         2  
  1         580  
6              
7 1     1   2721 use Data::Dumper;
  1         18859  
  1         675  
8              
9             our $VERSION = '2.2';
10              
11              
12             =head1 NAME
13              
14             Catalyst::View::Seamstress - HTML::Seamstress View Class for Catalyst
15              
16             =head1 SYNOPSIS
17              
18             # use the helper to create MyApp::View::Seamstress
19             # where comp_root and skeleton are optional
20              
21             myapp_create.pl view Seamstress Seamstress /path/to/html html::skeleton
22             ^-modulenm ^-helpernm ^-comp_root ^-skeleton
23              
24             # optionally edit the skeleton and meat_pack routines
25             # in lib/MyApp/View/Seamstress.pm
26              
27             # create your seamstress template packaged with spkg.pl
28             # see HTML::Seamstress.. This will give you a .pm file to go with your html,
29             # so something like html::helloworld
30              
31             # render view from lib/MyApp.pm or lib/MyApp::C::SomeController.pm
32              
33             sub message : Global {
34             my ( $self, $c ) = @_;
35              
36             # LOOM points to our template class made with spkg.pl or
37             # manually:
38             $c->stash->{LOOM} = 'html::hello_world';
39             $c->stash->{name} = 'Mister GreenJeans';
40             $c->stash->{date} = 'Today';
41              
42             # the DefaultEnd plugin would mean no need for this line
43             $c->forward('MyApp::View::Seamstress');
44             }
45              
46             # and in your html::helloworld you can do something like:
47              
48             sub process{
49             my( $tree, $c, $stash ) = @_;
50            
51             $tree->look_down( id => 'name' )->replace_content( $stash->{name} );
52             }
53              
54              
55             =head1 DESCRIPTION
56              
57              
58             This is the Catalyst view class for L<HTML::Seamstress|HTML::Seamstress>. It allows
59             templating with proper seperation between code and HTML. This means you can get a
60             designer/friend/client/stooge to make your templates for you without having to
61             teach them a mini-language!
62              
63             Your application should define a view class which is a subclass of
64             this module. The easiest way to achieve this is using the
65             F<myapp_create.pl> script (where F<myapp> should be replaced with
66             whatever your application is called). This script is created as part
67             of the Catalyst setup.
68              
69             $ script/myapp_create.pl view Seamstress Seamstress
70              
71             This creates a MyApp::View::Seamstress.pm module in the
72             F<lib> directory (again, replacing C<MyApp> with the name of your
73             application).
74              
75              
76             Now you can modify your action handlers in the main application and/or
77             controllers to forward to your view class. You might choose to do this
78             in the end() method, for example, to automatically forward all actions
79             to the Seamstress view class.
80              
81             # In MyApp or MyApp::Controller::SomeController
82              
83             sub end : Private {
84             my( $self, $c ) = @_;
85             $c->forward('MyApp::View::Seamstress');
86             }
87              
88             Or you might like to use
89             L<Catalyst::Plugin::DefaultEnd|Catalyst::Plugin::DefaultEnd>
90              
91             ..or even
92             L<Catalyst::Action::RenderView|Catalyst::Action::RenderView>
93              
94              
95             =head1 CONFIGURATION
96              
97             The helper app automatically puts the per-application
98             configuration info in C<MyApp::View::Seamstress>. You configure the
99             per-request information (e.g. C<< $c->stash->{LOOM} >> and
100             variables for this template) in your controller.
101              
102             The two main options which control how View::Seamtress renders HTML are the
103             LOOM (which is taken from the stash) and optionally the skeleton, which is
104             stored in the app config.
105              
106             If you just configure a LOOM then you are most likely using the "plain meat" method described below. If you also configure a skeleton in your config as well then you're using the "meat and skeleton" method. See below for a more detailed discussion of this!
107              
108             =over
109              
110             =item * C<< $c->stash->{LOOM} >>
111              
112             The Seamstress view plugin MUST have a LOOM
113             to work on or it
114             will balk with an error:
115              
116             sub message : Global {
117             my ( $self, $c ) = @_;
118             $c->stash->{LOOM} = 'html::hello_world';
119             $c->stash->{name} = 'Billy Bob';
120             $c->stash->{date} = 'medjool sahara';
121             $c->forward('MyApp::View::Seamstress');
122             }
123              
124              
125             =item * C<< MyApp::View::Seamstress->config->{skeleton} >>
126              
127             By default this is not set and the HTML output is simply the result of
128             taking C<< $c->stash->{LOOM} >>, calling C<new()> to create
129             an HTML tree and then passing this to C<process()> so that it can rework
130             the tree.
131              
132             However, if C<< MyApp::View::Seamstress->config->{skeleton} >> is
133             set, then both its value and the values of
134             C<< MyApp::View::Seamstress->config->{meat_pack} >>
135             and C<< $stash->{LOOM}->fixup() >>
136             come into effect
137             as described in L<HTML::Seamstress/"The_meat-skeleton_paradigm">.
138              
139             Let's take that a little slower: C<< $stash->{LOOM}->fixup() >>
140             means: given a Seamstress-style Perl class, whose name is
141             C<< $stash->{LOOM} >>, call the method C<fixup()> in that
142             class so that it can do a final fixup of the entire HTML that is about
143             to be shipped back to the client.
144              
145             =back
146              
147             The output generated from the LOOM
148             (and possibly its interaction with a skeleton)
149             is stored in
150             C<< $c->response->body >>.
151              
152              
153             =head2 Other Config Options
154              
155             =over
156              
157             =item config->{fixup}
158              
159             Set this to a coderef to allow the view to change the tree after the main
160             processing phase.
161              
162             =item config->{use_xhtml}
163              
164             By default the view will generate html 4 style html by calling as_HTML on the
165             tree object. If you set this to a true value it will generate XHTML style HTML
166             by calling as_XML on the tree object. See L<HTML::Element> for details for
167             these methods.
168              
169             Also note that this won't apply proper HTML doctypes and what-have-you unless
170             you have them in your original HTML.
171              
172             =item config->{meat_pack}
173              
174             This is the subref which is called to pack meat into the skeleton for the meat
175             skeleton method. Tinker with this to have more creative LOOMS. See "Funny
176             LOOMs" and the meat/skeleton discussions.
177              
178             =back
179              
180             =head2 Funny LOOMs
181              
182             In the examples so far the LOOM has always been a class name.
183              
184             If instead LOOM is an object then we'll assume that is a useful HTML::Element style
185             object and just use that instead of calling C<new> on the LOOM. In this case we'll also not ->delete it at the end of the request so you'll have to do that yourself!
186              
187             If the LOOM is in fact an ARRAY reference filled with class names we'll send the meat_pack a hash of class names mapped to objects.
188              
189             =cut
190              
191              
192             # process()
193              
194             # C<< eval-requires >> the module specified in C<< $c->stash->{LOOM} >>.
195             # Gets the
196             # C<HTML::Tree> representation of the file via C<new> and then calls
197             # C<< $self->process($c, $c->stash) >> to rewrite the tree.
198              
199             sub page2tree {
200 0     0 0   my ($self, $c, $page_class, $process_method) = @_;
201              
202 0 0         $c->log->debug(qq/Rendering template "$page_class"/) if $c->debug;
203              
204 0   0       $process_method ||= 'process';
205              
206 0           my $page_object;
207              
208             # IF we've been passed a page class, build an object:
209 0 0         if (not ref $page_class) {
210              
211             # pull in the page class:
212 0           eval "require $page_class";
213              
214             # emit errors if there were problems with the page_class:
215 0 0         if ($@) {
216 0           my $error = qq/Couldn't load $page_class -- "$@"/;
217 0           $c->log->error($error);
218 0           $c->error($error);
219 0           return 0;
220             }
221              
222 0           $page_object = $page_class->new($c); # e.g html::hello_world->new
223             }
224             # IF we've been passed a page object, just use it:
225             else {
226 0           $page_object = $page_class;
227             }
228              
229             # Run the process hook:
230 0           my $tree; eval { $tree = $page_object->$process_method($c, $c->stash) } ;
  0            
  0            
231              
232 0 0         if ( my $error = $@ ) {
233              
234 0           chomp $error;
235 0           $error = qq/process() failed in "$page_class". Error: "$error"/;
236 0           $c->log->error($error);
237 0           $c->error($error);
238 0           return undef;
239              
240             } else {
241              
242 0           return $tree;
243              
244             }
245              
246             }
247              
248             # Main view process hook:
249             sub process {
250 0     0 0   my ( $self, $c ) = @_;
251              
252 0           my $body_is_skeleton = 0;
253              
254 0           my ($skeleton, $meat, $body) ;
255              
256 0           my $loom = $c->stash->{LOOM};
257              
258             # check we actually got a loom to work with:
259 0 0         unless ($loom) {
260 0 0         $c->log->debug('No LOOM specified for rendering') if $c->debug;
261 0           return 0;
262             }
263              
264 0 0         unless ( $c->response->content_type ) {
265 0           $c->response->content_type('text/html; charset=utf-8');
266             }
267              
268              
269 0 0         if (ref($loom) eq 'ARRAY') {
270 0           map {
271 0           $meat->{$_} = $self->page2tree($c, $_);
272             } @$loom;
273             } else {
274 0           $meat = $body = $self->page2tree($c, $loom);
275             }
276              
277             #
278             # render and pack MyApp::View::Seamstress->config->{skeleton}
279             # if defined
280             #
281              
282              
283 0 0         if ($skeleton = $self->config->{skeleton}) {
284 0           $skeleton = $self->page2tree($c, $skeleton);
285              
286 0           $self->config->{meat_pack}->(
287             $self, $c, $c->stash, $meat, $skeleton
288             );
289              
290 0           $body_is_skeleton = 1;
291 0           $body = $skeleton ;
292             }
293              
294             # give the main view config an opportunity to twiddle the tree a bit:
295 0 0         if ( ref $self->config->{fixup} ) {
296 0           $self->config->{fixup}->($body, $c);
297             }
298              
299              
300             # take the the body and make some REAL html out of it!
301 0           my $response_body;
302 0 0         if ( $c->config->{use_xhtml} ) {
303 0           $response_body = $body->as_XML( undef, ' ' );
304             }
305             else {
306 0           $response_body = $body->as_HTML(undef, ' ')
307             }
308              
309             # stuff the response_body in the response body!
310 0           $c->response->body( $response_body );
311              
312              
313             # we delete the body unless our loom ( or skeleton if we have one) is a reference
314             # which we take as a sign that the user is doing something more elaborate caching or something..
315 0 0 0       unless( (! $body_is_skeleton && ref $loom) || ( $body_is_skeleton && ref $self->config->{skeleton} ) ) {
      0        
      0        
316 0           $body->delete;
317             }
318              
319 0           return 1;
320             }
321              
322             ;1;
323             __END__
324              
325             =head1 The meat-skeleton paradigm
326              
327             Generally Catalyst::View::Seamstress operates in one of 2 ways: a plain meat
328             way or a meat-skeleton way.
329              
330             Plain meat is simple: the View takes C<$c->stash->{LOOM} > and calls
331             C<new()> and C<process()> on it and stores the result in C<$c->response->body>.
332              
333             Meat-skeleton is designed to facilitate the way that most web sites are
334             typically designed:
335              
336             HTML pages typically have meat and a skeleton. The meat varies from page
337             to page while the skeleton is fairly (though not completely)
338             static. For example, the skeleton of a webpage is usually a header, a
339             footer, and a navbar. The meat is what shows up when you click on a
340             link on the page somewhere. While the meat will change with each
341             click, the skeleton is rather static.
342              
343              
344             Mason accomodates the meat-skeleton paradigm via
345             an C<autohandler> and C<< $m->call_next() >>. Template
346             accomodates it via its C<WRAPPER> directive.
347              
348             And Seamstress? Well, here's what you _can_ do:
349              
350             =over
351              
352             =item 1 generate the meat, C<$meat>
353              
354             This is typically what you see in the C<body> part of an HTML page
355              
356             =item 2 generate the skeleton, C<$skeleton>
357              
358             This is typically the html, head, and maybe some body
359              
360             =item 3 put the meat in the skeleton
361              
362             =back
363              
364             So, nothing about this is forced. This is just how I typically do
365             things and that is why
366             L<Catalyst::View::Seamstress|Catalyst::View::Seamstress> has support
367             for this.
368              
369             =head1 Tips to View Writers
370              
371             =head2 The order of use base is VERY significant
372              
373             When your helper module creates C<MyApp::View::Seamstress> it is B<very>
374             important for the C<use base> to look this way:
375              
376             use base qw(Catalyst::View::Seamstress HTML::Seamstress );
377              
378             and not this way:
379              
380             use base qw(HTML::Seamstress Catalyst::View::Seamstress );
381              
382             so that certain calls (probably new) get handled properly.
383              
384             =head2 Getting config information from MyApp and MyApp::View::*
385              
386             assuming C<Catalyst::View::Seamstress::new()> starts off
387             like this:
388              
389             sub new {
390             my $self = shift;
391             my $c = shift;
392              
393             C<< $self->config >> contains things set in C<MyApp::View::*>.
394             C<< $c->config >> contains things set in C<MyApp>
395              
396             assuming C<Catalyst::View::Seamstress::process()> starts off
397             similarly:
398              
399             sub process {
400             my ( $self, $c ) = @_;
401              
402             C<< $self->config >> contains things set in C<MyApp::View::*>.
403             C<< $c->config >> contains things set in C<MyApp>.
404              
405             There is no automatic merging of the two sources of configuration: you
406             have to do that yourself if you want to do it.
407              
408              
409             =head2
410              
411              
412             =head1 SEE ALSO
413              
414             L<Catalyst>,
415             L<Catalyst::View>,
416             L<Catalyst::Helper::View::Seamstress>,
417             L<HTML::Seamstress>
418              
419             =head2 A working sample app
420              
421             The best way to see a fully working Seamstress-style Perl class is to
422             pull down the working sample app from sourceforge.
423              
424             A working sample app, which does both simple and
425             meat-skeleton rendering is available from github:
426              
427             git clone git://github.com/draxil/catalyst--view--seamstress-sample-app.git
428              
429             =head1 SUPPORT
430              
431             Email the author or ping him on C<#catalyst> on C<irc.perl.org>
432              
433             =head1 AUTHORS
434              
435             Terrence Brannon <metaperl@gmail.com>
436              
437             With some additional hacking by:
438              
439             Joe Higton <draxil@cpan.org>
440              
441             =head1 COPYRIGHT
442              
443             This program is free software, you can redistribute it and/or modify it
444             under the same terms as Perl itself.
445              
446             =cut
447              
448