File Coverage

blib/lib/Catalyst/View/Seamstress.pm
Criterion Covered Total %
statement 45 60 75.0
branch 11 24 45.8
condition 4 11 36.3
subroutine 5 5 100.0
pod 1 2 50.0
total 66 102 64.7


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