File Coverage

blib/lib/Mojo/DOM/Role/Form.pm
Criterion Covered Total %
statement 26 26 100.0
branch 14 14 100.0
condition 21 21 100.0
subroutine 10 10 100.0
pod 1 1 100.0
total 72 72 100.0


line stmt bran cond sub pod time code
1             package Mojo::DOM::Role::Form;
2              
3 2     2   2345 use Mojo::Base -role;
  2         5  
  2         12  
4              
5             requires qw{ancestors at attr find matches selector tag val};
6              
7             sub target {
8 21     21 1 2733 my ($self, $submit) = (shift, shift);
9 21 100 100     73 return () if ($self->tag // '') ne 'form';
10             return ()
11 20 100 100     360 unless defined($submit = $self->at($submit || _form_default_submit($self)));
12 19 100       14964 return () if $submit->matches('[disabled]');
13             return (
14 17   100     2535 uc($submit->attr('formmethod') || $self->attr('method') || 'GET'),
      100        
      100        
15             $submit->attr('formaction') || $self->attr('action') || '#',
16             $submit->attr('formenctype') || $self->attr('enctype') || 'url-encoded'
17             );
18             }
19              
20             around val => sub {
21             my ($orig, $self, @args) = @_;
22              
23             # "form"
24             return $self->find('button, checkbox, input, radio, select, textarea')->map(
25             sub {
26             my $is_image = !!$_->matches('input[type=image]');
27              
28             # ignore disabled nodes
29             return () if _form_element_disabled($_);
30              
31             # ignore those without name, unless image type
32             return () if !defined(my $name = $_->attr("name")) && !$is_image;
33              
34             # only continue if the clickable element matches (synthesize click)
35             return () if _form_element_submits($_) && !$_->matches($_[1]);
36              
37             # client only buttons ignored
38             return () if _form_element_client_only_button($_);
39              
40             # simply return name => value for all but image types
41             return [$name => $_->val()] unless $is_image;
42              
43             # synthesize image click
44             return _form_image_click($_, $name);
45             },
46             $args[0] || _form_default_submit($self)
47             )->reduce(
48             sub {
49             my ($key, $value) = @$b;
50             $a->{$key}
51             = defined $a->{$key} && defined($value)
52             ? [ref($a->{$key}) ? (@{$a->{$key}}, $value) : ($a->{$key}, $value)]
53             : $value;
54             $a;
55             },
56             {}
57             ) if (my $tag = $self->tag) eq 'form';
58              
59             # "option"
60             return $self->{value} // $self->text if $tag eq 'option';
61              
62             # "input" ("type=checkbox" and "type=radio")
63             my $type = $self->{type} // '';
64             return $self->{value} // 'on'
65             if $tag eq 'input' && ($type eq 'radio' || $type eq 'checkbox');
66              
67             # "textarea", "input" or "button". Give input[type=submit] default value
68             return (
69             $tag eq 'textarea' ? $self->text
70             : ( $self->matches('input[type=submit]') ? ($self->{value} || 'Submit')
71             : $self->{value})
72             ) if $tag ne 'select';
73              
74             # "select"
75             my $v = $self->find('option:checked:not([disabled])')
76             ->grep(sub { !$_->ancestors('optgroup[disabled]')->size })->map('val');
77             return exists $self->{multiple} ? $v->size ? $v->to_array : undef : $v->last;
78             };
79              
80             #
81             # internal
82             #
83              
84             sub _form_default_submit {
85             return shift->find('*')
86              
87             # filter for those submittable nodes
88 454     454   23974 ->grep(sub { !!$_->_form_element_submits; })
89              
90             # only the first continues, save some cycles
91 30     30   743 ->tap(sub { splice @$_, 1; })
92              
93             # get the selector and relativise to form
94             ->map(sub {
95 23     23   341 (my $s = $_->selector) =~ s/^.*form[^>]*>\s//;
96 23         4057 return $s;
97 30   100 30   31218 })->first || '';
98             }
99              
100             sub _form_element_client_only_button {
101 222     222   376 my $s = 'input[type=button], button[type=button], button[type=reset]';
102 222         600 return !!$_[0]->matches($s);
103             }
104              
105             sub _form_element_disabled {
106 441 100   441   76953 return 1 if $_[0]->matches('[disabled]');
107 399 100 100     46057 return 1
108             if $_[0]->ancestors('fieldset[disabled]')->size
109             && !$_[0]->ancestors('fieldset legend:first-child')->size;
110 396         224579 return 0;
111             }
112              
113             sub _form_element_submits {
114 719     719   1181 my $s = join ', ', 'button:not([type=button], [type=reset])', 'button',
115             'input[type=submit]', 'input[type=image]';
116              
117             # submit is the default
118 719 100 100     1527 return 1 if $_[0]->matches($s) && !_form_element_disabled($_[0]);
119 588         304590 return 0;
120             }
121              
122             sub _form_image_click {
123 6     6   12 my ($self, $name) = (shift, shift);
124 6   100     15 my ($x, $y) = map { int(rand($self->attr($_) || 1)) + 1 } qw{width height};
  12         165  
125              
126             # x and y if no name
127 6 100       101 return ([x => $x], [y => $y]) unless $name;
128              
129             # named x and y, with name
130 3         18 return (["$name.x" => $x], ["$name.y" => $y]);
131             }
132              
133             1;
134              
135             =encoding utf8
136              
137             =head1 NAME
138              
139             Mojo::DOM::Role::Form - Form data extraction
140              
141             =head1 SYNOPSIS
142              
143             # description
144             my $obj = Mojo::DOM::Role::Form->new();
145             $obj->target('#submit-id');
146              
147             =head1 DESCRIPTION
148              
149             L based role to compose additional form data extraction methods into
150             L.
151              
152             =head1 METHODS
153              
154             L implements the following methods.
155              
156             =head2 target
157              
158             # result
159             $obj->target
160              
161             Explain what the L does.
162              
163             =head1 AUTHOR
164              
165             =cut