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