File Coverage

blib/lib/JavaScript/Framework/jQuery.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package JavaScript::Framework::jQuery;
2              
3 11     11   2400803 use 5.008.000;
  11         41  
  11         565  
4 11     11   69 use warnings;
  11         19  
  11         335  
5 11     11   65 use strict;
  11         23  
  11         424  
6 11     11   58 use Carp;
  11         19  
  11         613898  
7              
8 11     11   8157 use Moose;
  0            
  0            
9             use MooseX::Types::Moose qw( Bool CodeRef HashRef Int );
10             use MooseX::Params::Validate;
11             use JavaScript::Framework::jQuery::Subtypes qw( libraryAssets pluginAssets );
12              
13             our $VERSION = '0.07';
14              
15             has 'library' => (
16             is => 'rw',
17             isa => libraryAssets,
18             required => 1,
19             );
20              
21             has 'plugins' => (
22             is => 'rw',
23             isa => pluginAssets,
24             );
25              
26             has 'xhtml' => (
27             is => 'rw',
28             isa => Bool,
29             default => sub { 1 },
30             );
31              
32             has 'transient_plugins' => (
33             is => 'rw',
34             isa => Bool,
35             default => sub { 1 },
36             );
37              
38             has 'rel2abs_uri_callback' => (
39             is => 'rw',
40             isa => CodeRef,
41             default => sub { sub { shift } },
42             );
43              
44             no Moose;
45              
46             =head1 NAME
47              
48             JavaScript::Framework::jQuery - Generate markup and code for jQuery JavaScript framework
49              
50             =head1 VERSION
51              
52             Version 0.01
53              
54             =cut
55              
56             =head1 SYNOPSIS
57              
58             use JavaScript::Framework::jQuery;
59              
60             my $jquery = JavaScript::Framework::jQuery->new(
61             library => {
62             src => [ '/static/js/jquery.min.js' ],
63             css => [
64             { href => 'theme/ui.all.css', media => 'screen' },
65             ],
66             },
67             plugins => [
68             {
69             name => mcDropdown,
70             library => {
71             src => [
72             '/js/jquery.mcdropdown.js',
73             '/js/jquery.bgiframe.js',
74             ],
75             css => [
76             { href => '/css/jquery.mcdropdown.css', media => 'all' },
77             ],
78             },
79             },
80             ],
81             );
82              
83             # alternative to configuring the plugin's asset locations in the
84             # constructor parameters:
85              
86             $jquery->config_plugin(
87             name => 'mcDropdown',
88             library => {
89             src => [
90             '/js/jquery.mcdropdown.js',
91             '/js/jquery.bgiframe.js',
92             ],
93             css => [
94             { href => '/css/jquery.mcdropdown.css', media => 'all' },
95             ],
96             },
97             );
98              
99             # add JavaScript constructor for the plugin
100              
101             $jquery->construct_plugin(
102             name => 'mcDropdown',
103             target_selector => '#category',
104             srl_ul => '#categorymenu',
105             options => # JavaScript object literal, sans curly braces
106             'minRows : 8, # no validation, broken JavaScript will pass unnoticed
107             maxRows : 25,
108             openSpeed : 500'
109             );
110              
111             print $jquery->link_elements;
112             print $jquery->script_src_elements;
113             print $jquery->document_ready;
114              
115             # output
116              
117             <link type="text/css" href="theme/ui.all.css" rel="stylesheet" media="screen" />
118             <link type="text/css" href="/css/jquery.mcdropdown.css" rel="stylesheet" media="all" />
119             <script type="text/javascript" src="/static/js/jquery.min.js" />
120             <script type="text/javascript" src="/js/jquery.mcdropdown.js" />
121             <script type="text/javascript" src="/js/jquery.bgiframe.js" />
122             <script type="text/javascript">
123             <![CDATA[
124             $(document).ready(function (){
125             $("#category").mcDropdown("#categorymenu",{
126             minRows : 8,
127             maxRows : 25,
128             openSpeed : 500
129             });
130             });
131             ]]>
132             </script>
133              
134             =cut
135              
136             =head1 DESCRIPTION
137              
138             Manage composition and insertion of C<link> and C<script> elements and the
139             jQuery C<ready> call into generated HTML.
140              
141             Plugin modules provide support for individual jQuery plugins.
142              
143             Framework plugins verify that the correct number of arguments will be passed to
144             JavaScript plugin constructors but perform no validation beyond
145             that.
146              
147             This module provides four methods for inserting content into an HTML (or XHTML)
148             document:
149              
150             =over
151              
152             =item link_elements( )
153              
154             For the mcDropdown plugin, for example, would print the LINK elements for the
155             jQueryUI and mcDropdown stylesheets. The output from this method should be
156             inserted in the HEAD element of the document:
157              
158             <link type="text/css" href="ui.all.css" rel="stylesheet" media="screen" />
159             <link type="text/css" href="jquery.mcdropdown.css" rel="stylesheet" media="all" />
160              
161             =item script_src_elements( )
162              
163             Prints all the SCRIPT elements with SRC attribute. The output from this method
164             should be inserted in the HEAD element or somewhere before any calls to
165             code in the JavaScript files:
166              
167             <script type="text/javascript" src="jquery.min.js" />
168             <script type="text/javascript" src="jquery.mcdropdown.js" />
169             <script type="text/javascript" src="jquery.bgiframe.js" />
170              
171             =item document_ready( )
172              
173             Prints the jQuery $.ready function call and deletes any plugin objects created
174             in this response cycle from the queue (otherwise they would accumulate with
175             each request).
176              
177             <![CDATA[
178             $(document).ready(function (){
179             $("#inputid").mcDropdown("#ulid");
180             });
181             ]]>
182              
183             Set transient_plugins to 0 if you wish to be able to fetch script and link
184             elements and $.ready function calls more than once.
185              
186             =item constructor_calls( )
187              
188             Returns only the text of the constructor calls for insertion into existing
189             code text. Useful for including the constructor calls in a template.
190              
191             Set transient_plugins to 0 if you wish to be able to fetch script and link
192             elements and $.ready function calls more than once.
193              
194             =back
195              
196             Other accessors:
197              
198             =over
199              
200             =item transient_plugins( )
201              
202             Set or get the value of transient_plugins. Takes 1 or 0.
203              
204             =back
205              
206             The data structure passed to the constructor provides the module with locations
207             for all the script and style assets required to make the jQuery plugins work.
208              
209             The 'src' and 'css' buckets can contain multiple list items.
210              
211             =cut
212              
213             =head1 SUPPORTED PLUGINS
214              
215             The following jQuery plugins are supported in this version:
216              
217             =over
218              
219             =item Superfish
220              
221             L<http://users.tpg.com.au/j_birch/plugins/superfish/>
222              
223             The Supersubs jQuery plugin may be used in conjunction with Superfish to improve
224             rendering of sub menu items.
225              
226             =item FileamentGrpMenu
227              
228             The FileamentGrpMenu framework plugin implements the interface required to
229             generate a jQuery constructor for the Filament Group jQuery menu plugin.
230              
231             L<http://www.filamentgroup.com/lab/jquery_ipod_style_and_flyout_menus/>
232              
233             =item mcDropdown
234              
235             L<http://www.givainc.com/labs/mcdropdown_jquery_plugin.htm>
236              
237             =item funcliteral
238              
239             Add literal text to document_ready method's output.
240              
241             =back
242              
243             Support for other jQuery plugins will be added as the need arises. Contributions
244             are welcome.
245              
246             =cut
247              
248             =head1 METHODS
249              
250             =head2 new( %params )
251              
252             Parameters
253              
254             =over
255              
256             =item library
257              
258             A reference to a hash:
259              
260             {
261             src => [ 'jquery.js' ],
262             css => [
263             { href => 'jquery-ui.css', media => 'all' }
264             ]
265             }
266              
267             This argument specifies the locations of the jQuery source and any stylesheets that
268             should be included in your content.
269              
270             These settings will be used to form script elements with the src attribute for any
271             files included in the 'src' bucket, and link elements with the href attribute
272             for any stylesheets included in the 'css' bucket. The C<script_src_elements> and
273             C<link_elements> methods return the text of these HTML elements.
274              
275             =item plugins
276              
277             A reference to a hash with an element for each jQuery plugin that you want to
278             manage with this module. Each element contains a C<library> type data structure.
279              
280             =item xhtml
281              
282             Default: true
283              
284             A boolean indicating whether markup should try to conform to XHTML or not.
285              
286             =item transient_plugins
287              
288             Default: true
289              
290             If true, calling the C<document_ready> or C<constructor_calls> method clears
291             the list of plugin constructors and assets (JavaScript and CSS files) returned
292             by the C<script_src_elements>, C<link_elements>, C<document_ready> and
293             C<constructor_calls> methods.
294              
295             =item rel2abs_uri_callback
296              
297             A reference to a subroutine that takes a (possibly) relative URI and returns
298             and absolute URI.
299              
300             In a Catalyst application this parameter might be passed with a value like:
301              
302             rel2abs_uri_callback => sub { $c->uri_for(shift) }
303              
304             =back
305              
306             =cut
307              
308             =head2 config_plugin( %params )
309              
310             Params
311              
312             =over
313              
314             =item name
315              
316             Required Str
317              
318             Short name for the plugin module. JavaScript::Framework::jQuery::Plugin::Superfish's
319             short name would be Superfish. This module calls require() against a package name
320             formed by inserting C<name> into the string
321             "JavaScript::Framework::jQuery::Plugin::<name>".
322              
323             =item no_library
324              
325             Optional Bool
326              
327             If true indicates that you are intentionally omitting the C<library> parameter
328             from the call to C<config_plugin>.
329              
330             Passing no_library with a true value and a library param in the same call to
331             C<config_plugin> will cause an exception.
332              
333             The effect of omitting the library data when configuring the plugin is to omit
334             the JavaScript and CSS assets from the html markup returned by the
335             C<link_elements> and C<script_src_elements> methods. The only use case for this
336             is the C<funcliteral> plugin which is used to add to the text output by
337             C<constructor_calls> and C<document_ready> and so has no assets associated with
338             it.
339              
340             =back
341              
342             Set static variables for a particular plugin type.
343              
344             The plugin must be configured with C<config_plugin> before calling C<construct_plugin>
345             or an exception will be raised.
346              
347             =cut
348              
349             sub config_plugin {
350             my $self = shift;
351             my %param = validated_hash(
352             \@_,
353             name => { isa => 'Str' },
354             library => { isa => libraryAssets, optional => 1 },
355             no_library => { isa => Bool, default => 0 },
356             );
357              
358             if ($param{no_library} && $param{library}) {
359             croak("'no_library' is true but a 'library' parameter was passed "
360             . 'with a true value.');
361             }
362             if (!$param{library} && !$param{no_library}) {
363             croak("'library' parameter is required unless 'no_library' parameter is passed "
364             . 'with a true value');
365             }
366              
367             my $plugclass = _mk_plugin_class_name($param{name});
368              
369             eval qq(require $plugclass);
370             if ($@) {
371             (my $pmpath = $plugclass) =~ s!::!/!g;
372             if ($@ =~ qr/Can't locate ${pmpath}\.pm in \@INC/) {
373             croak("Unknown plugin cannot be configured: $plugclass");
374             }
375             else {
376             $@ && croak($@);
377             }
378             }
379              
380             $self->_stash_plugin_config($param{name}, $param{library});
381             $self->_set_plugin_is_configured($param{name});
382             }
383              
384             =head2 construct_plugin( %params )
385              
386             Append a new jQuery plugin wrapper object to the queue.
387              
388             =cut
389              
390             sub construct_plugin {
391             my $self = shift;
392              
393             unless (@_ && 0 == @_ % 2) {
394             croak 'usage: construct_plugin($self, '
395             . 'name => <plugin>, '
396             . 'target_selector => <jQuery selector string> '
397             . '[@plugin_args])';
398             }
399              
400             my %param = @_;
401              
402             my $plugclass = _mk_plugin_class_name($param{name});
403              
404             unless ($self->_plugin_is_configured($param{name})) {
405             croak("attempt to instantiate unconfigured plugin: $param{name} ($plugclass)");
406             }
407              
408             $self->_enqueue_plugin($plugclass->new( %param ));
409              
410             return;
411             }
412              
413             # return requested plugin's class name
414             sub _mk_plugin_class_name {
415             return join('::' => __PACKAGE__, 'Plugin', shift);
416             }
417              
418             =head2 add_func_calls( @funccalls )
419              
420             Add list of literal text containing function calls (technically you can add any
421             text you like here, the text is opaque to this module).
422              
423             =cut
424              
425             sub add_func_calls {
426             my ( $self, @funccalls ) = @_;
427              
428             require JavaScript::Framework::jQuery::Plugin::funcliteral;
429              
430             $self->config_plugin(
431             name => 'funcliteral',
432             no_library => 1,
433             );
434              
435             my $obj =
436             JavaScript::Framework::jQuery::Plugin::funcliteral->new(
437             funccalls => [ @funccalls ],
438             );
439             $self->_enqueue_plugin($obj);
440              
441             return;
442             }
443              
444             =head2 link_elements( )
445              
446             Return markup for HTML LINK elements.
447              
448             =cut
449              
450             sub link_elements {
451             my ( $self ) = @_;
452              
453             my @css;
454              
455             push @css, $self->_library_css;
456              
457             for my $config ($self->_plugin_config_list) {
458             next unless $self->_plugin_used($config->{name});
459             next unless $config->{library};
460             push @css, @{$config->{library}{css}};
461             }
462              
463             my (@text, $end);
464              
465             if ($self->xhtml) {
466             $end = ' />';
467             }
468             else {
469             $end = '>';
470             }
471              
472             my %seen;
473              
474             for (@css) {
475             my $href = $_->{href};
476             next if $seen{$href}++;
477             $href = $self->rel2abs_uri_callback->($href);
478             push @text,
479             qq(<link type="text/css" href="${href}" rel="stylesheet" media="$_->{media}") . $end;
480             }
481              
482             return join("\n" => @text);
483             }
484              
485             =head2 script_src_elements( )
486              
487             Return markup for HTML SCRIPT (with SRC attr) elements.
488              
489             =cut
490              
491             sub script_src_elements {
492             my ( $self ) = @_;
493              
494             my @src;
495              
496             push @src, $self->_library_src;
497              
498             for my $config ($self->_plugin_config_list) {
499             next unless $self->_plugin_used($config->{name});
500             next unless $config->{library};
501             push @src, @{$config->{library}{src}};
502             }
503              
504             my (@text, $end);
505              
506             #if ($self->xhtml) {
507             # $end = ' />';
508             #}
509             #else {
510             # $end = '></script>';
511             #}
512              
513             $end = '></script>';
514              
515             my %seen;
516              
517             for my $src (@src) {
518             next if $seen{$src}++;
519             $src = $self->rel2abs_uri_callback->($src);
520             push @text,
521             qq(<script type="text/javascript" src="${src}") . $end;
522             }
523              
524             return join("\n" => @text);
525             }
526              
527             =head2 document_ready( )
528              
529             Return the jQuery $(document).ready(...) statement.
530              
531             =cut
532              
533             sub document_ready {
534             my ( $self ) = @_;
535              
536             my $docready = $self->constructor_calls;
537             return '' unless defined $docready;
538              
539             $docready = qq|\$(document).ready(function (){
540             $docready
541             });|;
542              
543             if ($self->xhtml) {
544             $docready = qq|//<![CDATA[
545             $docready
546             //]]>|;
547             }
548              
549             # don't forget the script tags
550             $docready = qq|<script type="text/javascript">
551             $docready
552             </script>|;
553              
554             return $docready;
555             }
556              
557             =head2 constructor_calls( )
558              
559             Return the text of the jQuery plugin constructor calls for inclusion in an existing
560             $(document).ready() text.
561              
562             =cut
563              
564             sub constructor_calls {
565             my ( $self ) = @_;
566              
567             my @plugins = $self->_plugin_queue;
568             return unless @plugins;
569              
570             my @cons;
571              
572             for my $obj (@plugins) {
573             push @cons, $obj->cons_statement;
574             }
575              
576             if ($self->transient_plugins) {
577             $self->_dequeue_plugins;
578             }
579              
580             return join("\n" => @cons);
581             }
582              
583             sub _set_plugin_is_configured {
584             my ( $self, $name ) = @_;
585             $self->{configured_plugin}{$name} = 1;
586             return;
587             }
588              
589             sub _plugin_is_configured {
590             my ( $self, $name ) = @_;
591             return $self->{configured_plugin}{$name};
592             }
593              
594             sub _plugin_queue {
595             my ( $self ) = @_;
596              
597             my $objs = $self->{plugin_objects};
598             return unless $objs;
599             return @$objs;
600             }
601              
602             sub _enqueue_plugin {
603             my ( $self, $pluginobj ) = @_;
604              
605             push @{$self->{plugin_objects}}, $pluginobj;
606              
607             $self->_register_used_plugin_name($pluginobj->name);
608              
609             return;
610             }
611              
612             sub _register_used_plugin_name {
613             my ( $self, $name ) = @_;
614              
615             $self->{used_plugin_name}{$name} = 1;
616              
617             return;
618             }
619              
620             sub _deregister_used_plugin_name {
621             my ( $self, $name ) = @_;
622              
623             delete $self->{used_plugin_name}{$name};
624              
625             return;
626             }
627              
628             sub _plugin_used {
629             my ( $self, $name ) = @_;
630              
631             return $self->{used_plugin_name}{$name};
632             }
633              
634             sub _dequeue_plugins {
635             my ( $self, $plugin ) = @_;
636              
637             if ($self->{plugin_objects}) {
638             @{$self->{plugin_objects}} = ();
639             }
640             if ($self->{used_plugin_name}) {
641             %{$self->{used_plugin_name}} = ();
642             }
643              
644             return;
645             }
646              
647             sub _library_css {
648             my ( $self ) = @_;
649             my $css = $self->library->{css};
650             return unless $css;
651             return @$css;
652             }
653              
654             sub _library_src {
655             my ( $self ) = @_;
656             my $src = $self->library->{src};
657             return unless $src;
658             return @$src;
659             }
660              
661             # stash a jQuery plugin's asset locations and HTML element attributes
662             sub _stash_plugin_config {
663             my ( $self, $name, $library ) = @_;
664             $self->{plugin_config}{$name} = $library;
665             push @{ $self->{plugin_config_list} }, { name => $name, library => $library };
666             }
667              
668             # fetch a stashed jQuery plugin configuration (asset locations)
669             sub _plugin_config {
670             my ( $self, $plugclass ) = @_;
671             $self->{plugin_config}{$plugclass};
672             }
673              
674             sub _plugin_config_list {
675             my ( $self, $plugclass ) = @_;
676              
677             my $list = $self->{plugin_config_list};
678             return unless $list;
679             return @$list;
680             }
681              
682             # fetch the list of plugins' css hashes
683             sub _plugin_config_css {
684             my ( $self, $plugclass ) = @_;
685             my $css = $self->_plugin_config($plugclass)->{css};
686             return unless $css;
687              
688             return @$css;
689             }
690              
691             # fetch the list of plugins' src hashes
692             sub _plugin_config_src {
693             my ( $self, $plugclass ) = @_;
694             my $src = $self->_plugin_config($plugclass)->{src};
695             return unless $src;
696              
697             return @$src;
698             }
699              
700             =head2 BUILD( )
701              
702             See L<Moose::Cookbook::Basics::Recipe11>
703              
704             =cut
705              
706             sub BUILD {
707             my $self = shift;
708              
709             my $plugins = $self->plugins;
710             return unless $plugins;
711              
712             for my $pi (@$plugins) {
713             $self->config_plugin(
714             name => $pi->{name},
715             library => $pi->{library},
716             );
717             }
718             }
719              
720              
721             1;
722              
723             __END__
724              
725             =head1 AUTHOR
726              
727             David P.C. Wollmann, C<< <converter42 at gmail.com> >>
728              
729             =head1 BUGS
730              
731             This is ALPHA code. The interface(s) may change or break.
732              
733             Please report any bugs or feature requests to C<bug-javascript-framework-jquery at rt.cpan.org>, or through
734             the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=JavaScript-Framework-jQuery>. I will be notified, and then you'll
735             automatically be notified of progress on your bug as I make changes.
736              
737             =head1 SUPPORT
738              
739             You can find documentation for this module with the perldoc command.
740              
741             perldoc JavaScript::Framework::jQuery
742              
743              
744             You can also look for information at:
745              
746             =over 4
747              
748             =item * RT: CPAN's request tracker
749              
750             L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=JavaScript-Framework-jQuery>
751              
752             =item * AnnoCPAN: Annotated CPAN documentation
753              
754             L<http://annocpan.org/dist/JavaScript-Framework-jQuery>
755              
756             =item * CPAN Ratings
757              
758             L<http://cpanratings.perl.org/d/JavaScript-Framework-jQuery>
759              
760             =item * Search CPAN
761              
762             L<http://search.cpan.org/dist/JavaScript-Framework-jQuery/>
763              
764             =back
765              
766             =head1 COPYRIGHT & LICENSE
767              
768             Copyright 2009 David P.C. Wollmann, all rights reserved.
769              
770             This program is free software; you can redistribute it and/or modify it
771             under the same terms as Perl itself.
772