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             version 0.32
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             Widget handlers map HTML elements to Perl class instances based on the
37             HTML tag and its attributes. The Perl class can be used to encapsulate
38             interaction with the HTML element and its children, abstracting HTML DOM
39             interactions into functional behaviours.
40              
41             A widget may itself be composed of one or more child-widget. Eg, a form
42             could be composed of several input fields and a button, all mapped to
43             widgets to handle the interactions with the specific element type.
44              
45             Classes can be developed and handlers registered for widgets which
46             perform the same behaviours as basic HTML widgets, like the C<select>
47             tag; eg., a Dojo Toolkit, Vue or React widgets could be mapped to
48             specific classes which encapsulate the DOM interactions required to make
49             these widgets transparent to the Perl code which is interacting with them.
50             An example of a widget mimicing to be a C<select> tag is
51             L<Weasel::Widgets::Dojo::Select>.
52              
53             =cut
54              
55             =head1 DEPENDENCIES
56              
57              
58             =cut
59              
60             package Weasel::WidgetHandlers 0.32;
61              
62 2     2   15 use strict;
  2         5  
  2         86  
63 2     2   12 use warnings;
  2         3  
  2         135  
64              
65 2     2   12 use base 'Exporter';
  2         4  
  2         250  
66              
67 2     2   39 use Module::Runtime qw(use_module);
  2         5  
  2         15  
68 2     2   124 use List::Util qw(max);
  2         3  
  2         1503  
69              
70             our @EXPORT_OK = qw| register_widget_handler best_match_handler_class |;
71              
72             =head1 SUBROUTINES/METHODS
73              
74             =over
75              
76             =item register_widget_handler($handler_class_name, $group_name, %conditions)
77              
78             Registers C<$handler_class_name> to be the instantiated widget returned
79             for an element matching C<%conditions> into C<$group_name>.
80              
81             C<Weasel::Session> can select a subset of widgets to be applicable to that
82             session by adding a subset of available groups to that session.
83              
84             =cut
85              
86              
87             # Stores handlers as arrays per group
88             my %widget_handlers;
89              
90             sub register_widget_handler {
91 0     0 1 0 my ($class, $group, %conditions) = @_;
92              
93             # make sure we can use the module by pre-loading it
94 0         0 use_module $class;
95              
96 0         0 return push @{$widget_handlers{$group}}, {
  0         0  
97             class => $class,
98             conditions => \%conditions,
99             };
100             }
101              
102             =item best_match_handler_class($driver, $_id, $groups)
103              
104             Returns the best matching handler's class name, within the groups
105             listed in the arrayref C<$groups>, or C<undef> in case of no match.
106              
107             When C<$groups> is undef, all registered handlers will be searched.
108              
109             When multiple handlers are considered "best match", the one last added
110             to the group last mentioned in C<$groups> is selected.
111              
112             =cut
113              
114             sub _cached_elem_att {
115 0     0   0 my ($cache, $driver, $_id, $att) = @_;
116              
117             return (exists $cache->{$att})
118             ? $cache->{$att}
119 0 0       0 : ($cache->{$att} = $driver->get_attribute($_id, $att));
120             }
121              
122             sub _att_eq {
123 0     0   0 my ($att1, $att2) = @_;
124              
125 0   0     0 return ($att1 // '') eq ($att2 // '');
      0        
126             }
127              
128             sub best_match_handler_class {
129 4     4 1 6 my ($driver, $_id, $groups) = @_;
130              
131 4   50     15 $groups //= [ keys %widget_handlers ]; # undef --> unrestricted
132              
133 4         4 my @matches;
134 4         4 my $elem_att_cache = {};
135 4         4 my $elem_classes;
136              
137 4         9 my $tag = $driver->tag_name($_id);
138 4         12 for my $group (@{$groups}) {
  4         7  
139 0         0 my $handlers = $widget_handlers{$group};
140              
141             HANDLER:
142 0         0 for my $handler (@{$handlers}) {
  0         0  
143 0         0 my $conditions = $handler->{conditions};
144              
145 0 0       0 next unless $tag eq $conditions->{tag_name};
146 0         0 my $match_count = 1;
147              
148 0 0       0 if (exists $conditions->{classes}) {
149 0         0 %{$elem_classes} =
150 0 0 0     0 map { $_ => 1 }
  0         0  
151             split /\s+/x, ($driver->get_attribute($_id, 'class')
152             // '')
153             unless defined $elem_classes;
154              
155 0         0 for my $class (@{$conditions->{classes}}) {
  0         0  
156             next HANDLER
157 0 0       0 unless exists $elem_classes->{$class};
158 0         0 $match_count++;
159             }
160             }
161              
162 0         0 for my $att (keys %{$conditions->{attributes}}) {
  0         0  
163             next HANDLER
164             unless _att_eq(
165 0 0       0 $conditions->{attributes}->{$att},
166             _cached_elem_att(
167             $elem_att_cache, $driver, $_id, $att));
168 0         0 $match_count++;
169             }
170              
171             push @matches, {
172             count => $match_count,
173             class => $handler->{class},
174 0         0 };
175             }
176             }
177 4         27 my $max_count = max map { $_->{count} } @matches;
  0         0  
178 4         5 @matches = grep { $_->{count} == $max_count } @matches;
  0         0  
179              
180 4 50       7 warn "multiple matching handlers for element\n"
181             if scalar(@matches) > 1;
182              
183 4         4 my $best_match = pop @matches;
184 4 50       16 return $best_match ? $best_match->{class} : undef;
185             }
186              
187             =back
188              
189             =cut
190              
191             =head1 AUTHOR
192              
193             Erik Huelsmann
194              
195             =head1 CONTRIBUTORS
196              
197             Erik Huelsmann
198             Yves Lavoie
199              
200             =head1 MAINTAINERS
201              
202             Erik Huelsmann
203              
204             =head1 BUGS AND LIMITATIONS
205              
206             Bugs can be filed in the GitHub issue tracker for the Weasel project:
207             https://github.com/perl-weasel/weasel/issues
208              
209             =head1 SOURCE
210              
211             The source code repository for Weasel is at
212             https://github.com/perl-weasel/weasel
213              
214             =head1 SUPPORT
215              
216             Community support is available through
217             L<perl-weasel@googlegroups.com|mailto:perl-weasel@googlegroups.com>.
218              
219             =head1 LICENSE AND COPYRIGHT
220              
221             (C) 2016-2023 Erik Huelsmann
222              
223             Licensed under the same terms as Perl.
224              
225             =cut
226              
227              
228             1;