File Coverage

blib/lib/HTML/Widget/Factory.pm
Criterion Covered Total %
statement 60 69 86.9
branch 17 26 65.3
condition 13 25 52.0
subroutine 13 16 81.2
pod 4 5 80.0
total 107 141 75.8


line stmt bran cond sub pod time code
1 15     15   243102 use 5.006;
  15         46  
  15         525  
2 15     15   69 use strict;
  15         23  
  15         543  
3 15     15   61 use warnings;
  15         23  
  15         824  
4             package HTML::Widget::Factory;
5             # ABSTRACT: churn out HTML widgets
6             $HTML::Widget::Factory::VERSION = '0.203';
7 15     15   79 use Carp ();
  15         19  
  15         251  
8 15     15   7714 use Module::Load ();
  15         14211  
  15         289  
9 15     15   7235 use MRO::Compat;
  15         36720  
  15         10532  
10              
11             #pod =head1 SYNOPSIS
12             #pod
13             #pod my $factory = HTML::Widget::Factory->new();
14             #pod
15             #pod my $html = $factory->select({
16             #pod name => 'flavor',
17             #pod options => [
18             #pod [ minty => 'Peppermint', ],
19             #pod [ perky => 'Fresh and Warm', ],
20             #pod [ super => 'Red and Blue', ],
21             #pod ],
22             #pod value => 'minty',
23             #pod });
24             #pod
25             #pod =head1 DESCRIPTION
26             #pod
27             #pod HTML::Widget::Factory provides a simple, pluggable system for constructing HTML
28             #pod form controls.
29             #pod
30             #pod =cut
31              
32             #pod =head1 METHODS
33             #pod
34             #pod Most of the useful methods in an HTML::Widget::Factory object will be provided
35             #pod by its plugins. Consult the documentation for the HTML::Widget::Plugin
36             #pod modules.
37             #pod
38             #pod =head2 new
39             #pod
40             #pod my $factory = HTML::Widget::Factory->new(\%arg);
41             #pod
42             #pod This constructor returns a new widget factory.
43             #pod
44             #pod The only valid arguments are C and C, which provide
45             #pod arrayrefs of plugins to be used. If C is not given, the default
46             #pod plugin list is used, which is those plugins that ship with
47             #pod HTML::Widget::Factory. The plugins in C are loaded in addition
48             #pod to these.
49             #pod
50             #pod Plugins may be provided as class names or as objects.
51             #pod
52             #pod =cut
53              
54             my %default_instance;
55             sub _default_instance {
56 0   0 0   0 $default_instance{ $_[0] } ||= $_[0]->new;
57             }
58              
59             my $LOADED_DEFAULTS;
60             my @DEFAULT_PLUGINS = qw(
61             HTML::Widget::Plugin::Attrs
62             HTML::Widget::Plugin::Button
63             HTML::Widget::Plugin::Checkbox
64             HTML::Widget::Plugin::Image
65             HTML::Widget::Plugin::Input
66             HTML::Widget::Plugin::Link
67             HTML::Widget::Plugin::Multiselect
68             HTML::Widget::Plugin::Password
69             HTML::Widget::Plugin::Radio
70             HTML::Widget::Plugin::Select
71             HTML::Widget::Plugin::Submit
72             HTML::Widget::Plugin::Textarea
73             );
74              
75             sub _default_plugins {
76 16   66 16   112 $LOADED_DEFAULTS ||= do {
77 15         172 Module::Load::load("$_") for @DEFAULT_PLUGINS;
78 15         239 1;
79             };
80 16         96 return @DEFAULT_PLUGINS;
81             }
82              
83             sub new {
84 21     21 1 2856 my ($self, $arg) = @_;
85 21   100     135 $arg ||= {};
86              
87 21   66     107 my $class = ref $self || $self;
88              
89             # XXX: I think we need to use default plugins when new is invoked on the
90             # class, but get the parent object's plugins when it's called on an existing
91             # factory. -- rjbs, 2014-02-21
92 5         12 my @plugins = $arg->{plugins}
93 21 100       116 ? @{ $arg->{plugins} }
94             : $class->_default_plugins;
95              
96 21 100       72 unshift @plugins, @{ $self->{plugins} } if ref $self;
  1         3  
97              
98 21 100 66     120 if ($arg->{plugins} or $arg->{extra_plugins}) {
99 5 50       43 push @plugins, @{ $arg->{extra_plugins} } if $arg->{extra_plugins};
  0         0  
100             }
101              
102             # make sure plugins given as classes become objects
103 21   100     228 ref $_ or $_ = $_->new for @plugins;
104              
105 21         32 my %widget;
106 21         42 for my $plugin (@plugins) {
107 201         508 for my $widget ($plugin->provided_widgets) {
108 220 100       392 my ($method, $name) = ref $widget ? @$widget : ($widget) x 2;
109              
110 220 50       433 Carp::croak "$plugin tried to provide $name, already provided by $widget{$name}{plugin}"
111             if $widget{$name};
112              
113 220 50       627 Carp::croak
114             "$plugin claims to provide widget via ->$method but has no such method"
115             unless $plugin->can($method);
116              
117 220         765 $widget{$name} = { plugin => $plugin, method => $method };
118             }
119             }
120              
121             # for some reason PPI/Perl::Critic think this is multiple statements:
122             bless { ## no critic
123 21         159 plugins => \@plugins,
124             widgets => \%widget,
125             } => $class;
126             }
127              
128             #pod =head2 provides_widget
129             #pod
130             #pod if ($factory->provides_widget($name)) { ... }
131             #pod
132             #pod This method returns true if the given name is a widget provided by the factory.
133             #pod This, and not C should be used to determine whether a factory can provide
134             #pod a given widget.
135             #pod
136             #pod =cut
137              
138             sub provides_widget {
139 10     10 1 2469 my ($self, $name) = @_;
140 10 50       27 $self = $self->_default_instance unless ref $self;
141              
142 10 100       52 return 1 if $self->{widgets}{$name};
143              
144 1         3 return;
145             }
146              
147             #pod =head2 provided_widgets
148             #pod
149             #pod for my $name ($fac->provided_widgets) { ... }
150             #pod
151             #pod This method returns an unordered list of the names of the widgets provided by
152             #pod this factory.
153             #pod
154             #pod =cut
155              
156             sub provided_widgets {
157 0     0 1 0 my ($self) = @_;
158 0 0       0 $self = $self->_default_instance unless ref $self;
159              
160 0         0 return keys %{ $self->{widgets} };
  0         0  
161             }
162              
163             my $ErrorMsg = qq{Can\'t locate object method "%s" via package "%s" }.
164             qq{at %s line %d.\n};
165              
166             sub AUTOLOAD {
167 58     58   109145 my $widget_name = our $AUTOLOAD;
168 58         356 $widget_name =~ s/.*:://;
169              
170 58 50 33     347 return if $widget_name eq 'DESTROY' or $widget_name eq 'CLONE';
171              
172 58         92 my ($self, $given_arg) = @_;
173 58   33     164 my $class = ref $self || $self;
174 58         159 my $howto = $self->{widgets}{$widget_name};
175              
176 58 50       122 unless ($howto) {
177 0         0 my ($callpack, $callfile, $callline) = caller;
178 0         0 die sprintf $ErrorMsg, $widget_name, $class, $callfile, $callline;
179             }
180              
181 58         188 return $self->_build_widget(@$howto{qw(plugin method)}, $given_arg);
182             }
183              
184             sub _build_widget {
185 58     58   98 my ($self, $plugin, $method, $given_arg) = @_;
186              
187 58         215 my $arg = $plugin->rewrite_arg($given_arg, $method);
188              
189 58         195 return $plugin->$method($self, $arg);
190             }
191              
192             sub can {
193 22     22 0 482 my ($self, $method) = @_;
194              
195 0     0   0 return sub { $self->$method(@_) }
196 22 50 33     115 if ref $self and $self->{widgets}{$method};
197              
198 22         220 return $self->SUPER::can($method);
199             }
200              
201             #pod =head2 plugins
202             #pod
203             #pod This returns a list of the plugins loaded by the factory.
204             #pod
205             #pod =cut
206              
207 1     1 1 363 sub plugins { @{ $_[0]->{plugins} } }
  1         8  
208              
209             #pod =head1 TODO
210             #pod
211             #pod =over
212             #pod
213             #pod =item * fixed_args for args that are fixed, like (type => 'checkbox')
214             #pod
215             #pod =item * a simple way to say "only include this output if you haven't before"
216             #pod
217             #pod This will make it easy to do JavaScript inclusions: if you've already made a
218             #pod calendar (or whatever) widget, don't bother including this hunk of JS, for
219             #pod example.
220             #pod
221             #pod =item * giving the constructor a data store
222             #pod
223             #pod Create a factory that has a CGI.pm object and let it default values to the
224             #pod param that matches the passed name.
225             #pod
226             #pod =item * include id attribute where needed
227             #pod
228             #pod =item * optional labels (before or after control, or possibly return a list)
229             #pod
230             #pod =back
231             #pod
232             #pod =head1 SEE ALSO
233             #pod
234             #pod =over
235             #pod
236             #pod =item L
237             #pod
238             #pod =item L
239             #pod
240             #pod =item L
241             #pod
242             #pod =item L
243             #pod
244             #pod =item L
245             #pod
246             #pod =item L
247             #pod
248             #pod =item L
249             #pod
250             #pod =item L
251             #pod
252             #pod =item L
253             #pod
254             #pod =item L
255             #pod
256             #pod =item L
257             #pod
258             #pod =item L
259             #pod
260             #pod =item L
261             #pod
262             #pod =back
263             #pod
264             #pod =cut
265              
266             1;
267              
268             __END__