File Coverage

blib/lib/Weasel/WidgetHandlers.pm
Criterion Covered Total %
statement 28 57 49.1
branch 2 16 12.5
condition 1 8 12.5
subroutine 6 9 66.6
pod 2 2 100.0
total 39 92 42.3


line stmt bran cond sub pod time code
1              
2             =head1 NAME
3              
4             Weasel::WidgetHandlers - Mapping elements to widget handlers
5              
6             =head1 VERSION
7              
8             0.01
9              
10             =head1 SYNOPSIS
11              
12             use Weasel::WidgetHandlers qw( register_widget_handler );
13              
14             register_widget_handler(
15             'Weasel::Widgets::HTML::Radio', # Perl class handler
16             'HTML', # Widget group
17             tag_name => 'input',
18             attributes => {
19             type => 'radio',
20             });
21              
22             register_widget_handler(
23             'Weasel::Widgets::Dojo::FilteringSelect',
24             'Dojo',
25             tag_name => 'span',
26             classes => ['dijitFilteringSelect'],
27             attributes => {
28             role => 'presentation',
29             ...
30             });
31              
32             =cut
33              
34             =head1 DESCRIPTION
35              
36             =cut
37              
38             =head1 DEPENDENCIES
39              
40              
41              
42             =cut
43              
44             package Weasel::WidgetHandlers;
45              
46 2     2   19 use strict;
  2         3  
  2         77  
47 2     2   13 use warnings;
  2         4  
  2         56  
48              
49 2     2   10 use base 'Exporter';
  2         3  
  2         195  
50              
51 2     2   14 use Module::Runtime qw(use_module);
  2         3  
  2         23  
52 2     2   91 use List::Util qw(max);
  2         5  
  2         1306  
53              
54             our @EXPORT_OK = qw| register_widget_handler best_match_handler_class |;
55              
56             =head1 SUBROUTINES/METHODS
57              
58             =over
59              
60             =item register_widget_handler($handler_class_name, $group_name, %conditions)
61              
62             Registers C<$handler_class_name> to be the instantiated widget returned
63             for an element matching C<%conditions> into C<$group_name>.
64              
65             C<Weasel::Session> can select a subset of widgets to be applicable to that
66             session by adding a subset of available groups to that session.
67              
68             =cut
69              
70              
71             # Stores handlers as arrays per group
72             my %widget_handlers;
73              
74             sub register_widget_handler {
75 0     0 1 0 my ($class, $group, %conditions) = @_;
76              
77             # make sure we can use the module by pre-loading it
78 0         0 use_module $class;
79              
80 0         0 return push @{$widget_handlers{$group}}, {
  0         0  
81             class => $class,
82             conditions => \%conditions,
83             };
84             }
85              
86             =item best_match_handler_class($driver, $_id, $groups)
87              
88             Returns the best matching handler's class name, within the groups
89             listed in the arrayref C<$groups>, or C<undef> in case of no match.
90              
91             When C<$groups> is undef, all registered handlers will be searched.
92              
93             When multiple handlers are considered "best match", the one last added
94             to the group last mentioned in C<$groups> is selected.
95              
96             =cut
97              
98             sub _cached_elem_att {
99 0     0   0 my ($cache, $driver, $_id, $att) = @_;
100              
101             return (exists $cache->{$att})
102             ? $cache->{$att}
103 0 0       0 : ($cache->{$att} = $driver->get_attribute($_id, $att));
104             }
105              
106             sub _att_eq {
107 0     0   0 my ($att1, $att2) = @_;
108              
109 0   0     0 return ($att1 // '') eq ($att2 // '');
      0        
110             }
111              
112             sub best_match_handler_class {
113 4     4 1 8 my ($driver, $_id, $groups) = @_;
114              
115 4   50     20 $groups //= [ keys %widget_handlers ]; # undef --> unrestricted
116              
117 4         7 my @matches;
118 4         9 my $elem_att_cache = {};
119 4         5 my $elem_classes;
120              
121 4         15 my $tag = $driver->tag_name($_id);
122 4         19 for my $group (@{$groups}) {
  4         8  
123 0         0 my $handlers = $widget_handlers{$group};
124              
125             HANDLER:
126 0         0 for my $handler (@{$handlers}) {
  0         0  
127 0         0 my $conditions = $handler->{conditions};
128              
129 0 0       0 next unless $tag eq $conditions->{tag_name};
130 0         0 my $match_count = 1;
131              
132 0 0       0 if (exists $conditions->{classes}) {
133 0         0 %{$elem_classes} =
134 0 0 0     0 map { $_ => 1 }
  0         0  
135             split /\s+/x, ($driver->get_attribute($_id, 'class')
136             // '')
137             unless defined $elem_classes;
138              
139 0         0 for my $class (@{$conditions->{classes}}) {
  0         0  
140             next HANDLER
141 0 0       0 unless exists $elem_classes->{$class};
142 0         0 $match_count++;
143             }
144             }
145              
146 0         0 for my $att (keys %{$conditions->{attributes}}) {
  0         0  
147             next HANDLER
148             unless _att_eq(
149 0 0       0 $conditions->{attributes}->{$att},
150             _cached_elem_att(
151             $elem_att_cache, $driver, $_id, $att));
152 0         0 $match_count++;
153             }
154              
155             push @matches, {
156             count => $match_count,
157             class => $handler->{class},
158 0         0 };
159             }
160             }
161 4         15 my $max_count = max map { $_->{count} } @matches;
  0         0  
162 4         7 @matches = grep { $_->{count} == $max_count } @matches;
  0         0  
163              
164 4 50       9 warn "multiple matching handlers for element\n"
165             if scalar(@matches) > 1;
166              
167 4         8 my $best_match = pop @matches;
168 4 50       20 return $best_match ? $best_match->{class} : undef;
169             }
170              
171             =back
172              
173             =cut
174              
175             =head1 AUTHOR
176              
177             Erik Huelsmann
178              
179             =head1 CONTRIBUTORS
180              
181             Erik Huelsmann
182             Yves Lavoie
183              
184             =head1 MAINTAINERS
185              
186             Erik Huelsmann
187              
188             =head1 BUGS AND LIMITATIONS
189              
190             Bugs can be filed in the GitHub issue tracker for the Weasel project:
191             https://github.com/perl-weasel/weasel/issues
192              
193             =head1 SOURCE
194              
195             The source code repository for Weasel is at
196             https://github.com/perl-weasel/weasel
197              
198             =head1 SUPPORT
199              
200             Community support is available through
201             L<perl-weasel@googlegroups.com|mailto:perl-weasel@googlegroups.com>.
202              
203             =head1 LICENSE AND COPYRIGHT
204              
205             (C) 2016-2023 Erik Huelsmann
206              
207             Licensed under the same terms as Perl.
208              
209             =cut
210              
211              
212             1;
213