blib/lib/Mojolicious/Plugin/DevexpressHelpers/Helpers.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 151 | 180 | 83.8 |
branch | 39 | 64 | 60.9 |
condition | 40 | 76 | 52.6 |
subroutine | 22 | 28 | 78.5 |
pod | 21 | 21 | 100.0 |
total | 273 | 369 | 73.9 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | 7 | 7 | 30 | use utf8; | |||
7 | 8 | ||||||
7 | 41 | ||||||
2 | package Mojolicious::Plugin::DevexpressHelpers::Helpers; | ||||||
3 | $Mojolicious::Plugin::DevexpressHelpers::Helpers::VERSION = '0.163572'; | ||||||
4 | #ABSTRACT: Helpers for Devexpress controls are defined here | ||||||
5 | 7 | 7 | 319 | use Modern::Perl; | |||
7 | 9 | ||||||
7 | 29 | ||||||
6 | 7 | 7 | 971 | use Mojo::ByteStream; | |||
7 | 45826 | ||||||
7 | 302 | ||||||
7 | 7 | 7 | 3068 | use MojoX::AlmostJSON qw(encode_json); | |||
7 | 16221 | ||||||
7 | 441 | ||||||
8 | 7 | 7 | 40 | use constant DEBUG => 0; | |||
7 | 7 | ||||||
7 | 11463 | ||||||
9 | |||||||
10 | #Not sure why C |
||||||
11 | #but it make my day! | ||||||
12 | our $OUT_DECODE = 'UTF-8'; | ||||||
13 | our $INDENT_BINDING = 0; | ||||||
14 | |||||||
15 | my @generic_controls = qw( | ||||||
16 | Accordion | ||||||
17 | ActionSheet | ||||||
18 | Autocomplete | ||||||
19 | Box | ||||||
20 | CheckBox | ||||||
21 | Calendar | ||||||
22 | ColorBox | ||||||
23 | ContextMenu | ||||||
24 | DateBox | ||||||
25 | DeferRendering | ||||||
26 | FileUploader | ||||||
27 | Gallery | ||||||
28 | List | ||||||
29 | LoadIndicator | ||||||
30 | Lookup | ||||||
31 | Map | ||||||
32 | MultiView | ||||||
33 | NavBar | ||||||
34 | NumberBox | ||||||
35 | Panorama | ||||||
36 | Pivot | ||||||
37 | PivotGrid | ||||||
38 | PivotGridFieldChooser | ||||||
39 | Popover | ||||||
40 | ProgressBar | ||||||
41 | RadioGroup | ||||||
42 | RangeSlider | ||||||
43 | Resizable | ||||||
44 | ResponsiveBox | ||||||
45 | Scheduler | ||||||
46 | ScrollView | ||||||
47 | SelectBox | ||||||
48 | Slider | ||||||
49 | Switch | ||||||
50 | TabPanel | ||||||
51 | Tabs | ||||||
52 | TagBox | ||||||
53 | TextArea | ||||||
54 | TextBox | ||||||
55 | TitleView | ||||||
56 | Toast | ||||||
57 | Toolbar | ||||||
58 | TreeView | ||||||
59 | ); | ||||||
60 | |||||||
61 | #Helper method to export without prepending a prefix | ||||||
62 | my @without_prefix = qw( dxbuild required_assets require_asset indent_binding append_js prepend_js ); | ||||||
63 | |||||||
64 | #Helper method to export with prepending a prefix | ||||||
65 | my @with_prefix = (qw( Button DataGrid Form Popup Menu LoadPanel Lookup ), | ||||||
66 | @generic_controls); | ||||||
67 | |||||||
68 | |||||||
69 | sub out{ | ||||||
70 | 20 | 20 | 1 | 25 | my $tag = shift; | ||
71 | 20 | 106 | my $bytes = Mojo::ByteStream->new($tag); | ||||
72 | 20 | 50 | 189 | return $bytes->decode($OUT_DECODE) if defined $OUT_DECODE; | |||
73 | 0 | 0 | return $bytes; | ||||
74 | } | ||||||
75 | |||||||
76 | sub new{ | ||||||
77 | 12 | 12 | 1 | 23 | my $class = shift; | ||
78 | 12 | 76 | my $self = bless { | ||||
79 | next_id => 1, | ||||||
80 | bindings => '', | ||||||
81 | before_bindings => '', | ||||||
82 | after_bindings => '', | ||||||
83 | }, $class; | ||||||
84 | 12 | 70 | return $self; | ||||
85 | } | ||||||
86 | |||||||
87 | sub indent_binding{ | ||||||
88 | 0 | 0 | 1 | 0 | my $self = shift; | ||
89 | 0 | 0 | $INDENT_BINDING = shift; | ||||
90 | } | ||||||
91 | |||||||
92 | sub add_binding{ | ||||||
93 | 10 | 10 | 1 | 106 | my $self = shift; | ||
94 | 10 | 52 | $self->{bindings} .= join "\n", @_; | ||||
95 | } | ||||||
96 | |||||||
97 | sub next_id{ | ||||||
98 | 3 | 3 | 1 | 32 | my $self = shift; | ||
99 | 3 | 34 | return "dxctl".($self->{next_id}++); | ||||
100 | } | ||||||
101 | |||||||
102 | sub new_id{ | ||||||
103 | 3 | 3 | 1 | 6 | my ($c, $attrs) = @_; | ||
104 | #should compute a new uniq id | ||||||
105 | 3 | 9 | $c->stash('dxHelper')->next_id; | ||||
106 | } | ||||||
107 | |||||||
108 | sub dxbind{ | ||||||
109 | 10 | 10 | 1 | 20 | my ($c, $control, $id, $attrs, $extensions, $befores, $afters) = @_; | ||
110 | #should return html code to be associated to the control | ||||||
111 | 10 | 100 | 41 | $befores //=[]; | |||
112 | 10 | 100 | 39 | $afters //=[]; | |||
113 | #http://stackoverflow.com/questions/9930577/jquery-dot-in-id-selector | ||||||
114 | 10 | 17 | my $jquery_id = $id; | ||||
115 | 10 | 25 | $jquery_id =~ s{\.}{\\\\.}g; | ||||
116 | 10 | 66 | 48 | my $prepend = ref $attrs eq 'HASH' && delete $attrs->{prependTo}; | |||
117 | 10 | 66 | 56 | my $append = ref $attrs eq 'HASH' && delete $attrs->{appendTo}; | |||
118 | 10 | 14 | my $binding = ''; | ||||
119 | 10 | 100 | 100 | 56 | if($prepend || $append){ | ||
120 | 2 | 5 | $binding = '$(\' \').'.$control.'('; |
||||
121 | } | ||||||
122 | else{ | ||||||
123 | 8 | 25 | $binding = '$("#'.$jquery_id.'").'.$control.'('; | ||||
124 | } | ||||||
125 | 10 | 14 | my @options; | ||||
126 | |||||||
127 | 10 | 100 | 37 | if (ref($attrs) eq 'HASH') { | |||
128 | 9 | 19 | $binding .= '{'; | ||||
129 | 9 | 50 | 24 | $binding .= "\n " if $INDENT_BINDING; | |||
130 | 9 | 47 | for my $k ( sort keys %$attrs){ | ||||
131 | 19 | 50 | 46 | my $v = $attrs->{$k} // ''; | |||
132 | 19 | 100 | 72 | if(ref($v) eq 'SCALAR'){ | |||
50 | |||||||
133 | #unref protected scalar | ||||||
134 | 2 | 3 | $v = $$v; | ||||
135 | } | ||||||
136 | elsif ($v!~/^\s*(?:function\s*\()/) { | ||||||
137 | 17 | 58 | $v = encode_json $v; | ||||
138 | } | ||||||
139 | 19 | 634 | push @options, "$k: $v"; | ||||
140 | } | ||||||
141 | } | ||||||
142 | else{ | ||||||
143 | 1 | 2 | push @options, $attrs; | ||||
144 | } | ||||||
145 | 10 | 50 | 47 | $binding .= join ",\n".($INDENT_BINDING?' ':''), @options; | |||
146 | 10 | 100 | 33 | $binding .= '}' if ref($attrs) eq 'HASH'; | |||
147 | 10 | 18 | $binding .= ')'; | ||||
148 | 10 | 100 | 28 | $binding .= '.prependTo("'.$prepend.'")' if $prepend; | |||
149 | 10 | 100 | 24 | $binding .= '.appendTo("'.$append.'")' if $append; | |||
150 | 10 | 50 | 32 | $binding .= ';' . ($INDENT_BINDING?"\n":""); | |||
151 | #append some extensions (eg: dxdatagrid) | ||||||
152 | 10 | 50 | 29 | $binding .= join ";\n".($INDENT_BINDING?' ':''), @$extensions if defined $extensions; | |||
100 | |||||||
153 | 10 | 34 | $c->stash('dxHelper')->add_binding($binding); | ||||
154 | 10 | 22 | my $html_code = ""; | ||||
155 | 10 | 100 | 100 | 54 | if($prepend || $append){ | ||
156 | 2 | 2 | $html_code = ''; | ||||
157 | } | ||||||
158 | 10 | 42 | out join('',@$befores, $html_code ,@$afters); | ||||
159 | } | ||||||
160 | |||||||
161 | |||||||
162 | sub parse_attributs{ | ||||||
163 | 10 | 10 | 1 | 15 | my $c = shift; | ||
164 | 10 | 19 | my @implicit_args = @{shift()}; | ||||
10 | 26 | ||||||
165 | 10 | 14 | my %attrs; | ||||
166 | IMPLICIT_ARGUMENT: | ||||||
167 | 10 | 100 | 83 | while(@_ and @implicit_args){ | |||
168 | 27 | 40 | my $ref = ref($_[0]); | ||||
169 | 27 | 50 | 52 | my $implicit = shift @implicit_args || ''; | |||
170 | 27 | 50 | 33 | 130 | last unless $ref =~ /^(?:|SCALAR)$/ | ||
66 | |||||||
33 | |||||||
33 | |||||||
33 | |||||||
33 | |||||||
33 | |||||||
171 | or (substr($implicit,0,1) eq '@' and $ref eq 'ARRAY') | ||||||
172 | or (substr($implicit,0,1) eq '%' and $ref eq 'HASH') | ||||||
173 | or (substr($implicit,0,1) eq '\\' and $ref eq 'REF') | ||||||
174 | or (substr($implicit,0,1) eq '*'); | ||||||
175 | 25 | 39 | $implicit =~ s/^[\\\*\%\@]//; | ||||
176 | 25 | 107 | $attrs{ $implicit } = shift @_; | ||||
177 | } | ||||||
178 | 10 | 100 | 34 | if(my $args = shift){ | |||
179 | 7 | 50 | 25 | if(ref($args) eq 'HASH'){ | |||
180 | NAMED_ARGUMENT: | ||||||
181 | 7 | 35 | while(my($k,$v)=each %$args){ | ||||
182 | 8 | 26 | $attrs{$k} = $v; | ||||
183 | } | ||||||
184 | } | ||||||
185 | } | ||||||
186 | 10 | 26 | return \%attrs; | ||||
187 | } | ||||||
188 | |||||||
189 | sub dxmenu { | ||||||
190 | 0 | 0 | 1 | 0 | my $c = shift; | ||
191 | 0 | 0 | my $attrs = parse_attributs( $c, [qw(id @items onItemClick)], @_ ); | ||||
192 | 0 | 0 | 0 | my $id = delete($attrs->{id}) // new_id( $c, $attrs ); | |||
193 | 0 | 0 | dxbind( $c, 'dxMenu' => $id => $attrs); | ||||
194 | } | ||||||
195 | |||||||
196 | |||||||
197 | sub dxloadpanel { | ||||||
198 | 0 | 0 | 1 | 0 | my $c = shift; | ||
199 | 0 | 0 | my $attrs = parse_attributs( $c, [qw(id message)], @_ ); | ||||
200 | 0 | 0 | 0 | my $id = delete($attrs->{id}) // new_id( $c, $attrs ); | |||
201 | 0 | 0 | dxbind( $c, 'dxLoadPanel' => $id => $attrs); | ||||
202 | } | ||||||
203 | |||||||
204 | |||||||
205 | sub dxbutton { | ||||||
206 | 3 | 3 | 1 | 10225 | my $c = shift; | ||
207 | 3 | 19 | my $attrs = parse_attributs( $c, [qw(id text onClick type)], @_ ); | ||||
208 | 3 | 66 | 14 | my $id = delete($attrs->{id}) // new_id( $c, $attrs ); | |||
209 | 3 | 9 | dxbind( $c, 'dxButton' => $id => $attrs); | ||||
210 | } | ||||||
211 | |||||||
212 | |||||||
213 | sub dxdatagrid{ | ||||||
214 | 3 | 3 | 1 | 12000 | my $c = shift; | ||
215 | 3 | 15 | my $attrs = parse_attributs( $c, [qw(id dataSource)], @_ ); | ||||
216 | 3 | 66 | 13 | my $id = delete($attrs->{id}) // new_id( $c, $attrs ); | |||
217 | 3 | 3 | my @extensions; | ||||
218 | #dxbind( $c, 'dxDataGrid' => $id => $attrs, [ $dataSource ]); | ||||||
219 | 3 | 100 | 100 | 18 | if ($attrs->{dataSource} && ref($attrs->{dataSource}) eq '') { | ||
220 | 1 | 2 | my $dataSource = delete $attrs->{dataSource}; | ||||
221 | #push @extensions, '$.getJSON("' . $dataSource . '",function(data){$("#'.$id.'").dxDataGrid({ dataSource: data });});'; | ||||||
222 | #$attrs->{dataSource} = \'[]'; #protect string to be "stringified" within dxbind | ||||||
223 | |||||||
224 | #\"" is to protect string to be "stringified" within dxbind | ||||||
225 | 1 | 4 | $attrs->{dataSource} = \"{store:{type:'odata',url:'$dataSource'}}"; | ||||
226 | } | ||||||
227 | 3 | 100 | 13 | if (exists $attrs->{options}) { | |||
228 | 1 | 2 | $attrs = $attrs->{options}; | ||||
229 | } | ||||||
230 | |||||||
231 | 3 | 19 | dxbind( $c, 'dxDataGrid' => $id => $attrs, \@extensions); | ||||
232 | } | ||||||
233 | |||||||
234 | |||||||
235 | sub dxform{ | ||||||
236 | 0 | 0 | 1 | 0 | my $c = shift; | ||
237 | 0 | 0 | my $attrs = parse_attributs( $c, [qw(id %formData @items)], @_ ); | ||||
238 | 0 | 0 | 0 | my $id = delete($attrs->{id}) // new_id( $c, $attrs ); | |||
239 | |||||||
240 | 0 | 0 | dxbind( $c, 'dxForm' => $id => $attrs ); | ||||
241 | } | ||||||
242 | |||||||
243 | |||||||
244 | sub dxpopup{ | ||||||
245 | 1 | 1 | 1 | 5119 | my $c = shift; | ||
246 | 1 | 5 | my $attrs = parse_attributs( $c, [qw(id title contentTemplate)], @_ ); | ||||
247 | 1 | 33 | 7 | my $id = delete($attrs->{id}) // new_id( $c, $attrs ); | |||
248 | |||||||
249 | 1 | 3 | dxbind( $c, 'dxPopup' => $id => $attrs ); | ||||
250 | } | ||||||
251 | |||||||
252 | |||||||
253 | |||||||
254 | sub mk_dxcontrol{ | ||||||
255 | 258 | 258 | 1 | 225 | my $dxControl = shift; | ||
256 | my $generic = sub{ | ||||||
257 | 3 | 3 | 16041 | my $c = shift; | |||
258 | 3 | 18 | my $attrs = parse_attributs( $c, [qw(id value label)], @_ ); | ||||
259 | 3 | 10 | my $id = delete($attrs->{id}); | ||||
260 | 3 | 50 | 9 | if (my $name = $id) { | |||
261 | 3 | 8 | $attrs->{name}=$name; | ||||
262 | } | ||||||
263 | |||||||
264 | 3 | 33 | 8 | $id //= new_id( $c, $attrs ); | |||
265 | |||||||
266 | 3 | 4 | my (@before, @after); | ||||
267 | 3 | 50 | 10 | if(my $label = delete($attrs->{label})){ | |||
268 | 3 | 5 | push @before, ' '; |
||||
269 | 3 | 8 | push @before, ' '.$label.' '; |
||||
270 | 3 | 7 | push @before, ' '; |
||||
271 | 3 | 5 | push @after, ''; | ||||
272 | 3 | 7 | push @after, ''; | ||||
273 | } | ||||||
274 | |||||||
275 | 3 | 13 | dxbind( $c, $dxControl => $id => $attrs, undef, \@before, \@after ); | ||||
276 | 258 | 699 | }; | ||||
277 | |||||||
278 | { | ||||||
279 | 7 | 7 | 39 | no strict 'refs'; | |||
7 | 12 | ||||||
7 | 5690 | ||||||
258 | 186 | ||||||
280 | 258 | 175 | *{__PACKAGE__.'::'.lc $dxControl} = $generic; | ||||
258 | 1375 | ||||||
281 | } | ||||||
282 | } | ||||||
283 | |||||||
284 | |||||||
285 | sub dxbuild { | ||||||
286 | 10 | 10 | 1 | 7934 | my $c = shift; | ||
287 | 10 | 23 | my %opts = @_; | ||||
288 | 10 | 50 | 27 | my $dxhelper = $c->stash('dxHelper') or return; | |||
289 | 10 | 50 | 115 | if($dxhelper->{bindings}){ | |||
290 | out ''; | ||||||
50 | |||||||
299 | } | ||||||
300 | } | ||||||
301 | |||||||
302 | |||||||
303 | sub require_asset{ | ||||||
304 | 3 | 3 | 1 | 8691 | my $c = shift; | ||
305 | 3 | 50 | 9 | my $dxhelper = $c->stash('dxHelper') or return; | |||
306 | |||||||
307 | 3 | 32 | push @{ $dxhelper->{required_assets} }, $_ for @_; | ||||
3 | 11 | ||||||
308 | |||||||
309 | 3 | 5 | return $c; | ||||
310 | } | ||||||
311 | |||||||
312 | |||||||
313 | sub required_assets{ | ||||||
314 | 2 | 2 | 1 | 1333 | my $c = shift; | ||
315 | 2 | 50 | 7 | my $dxhelper = $c->stash('dxHelper') or return; | |||
316 | 2 | 50 | 26 | my $required_assets = $dxhelper->{required_assets} // []; | |||
317 | 2 | 20 | my $results = Mojo::ByteStream->new(); | ||||
318 | ASSET: | ||||||
319 | 2 | 17 | for my $asset (@$required_assets){ | ||||
320 | #not sure about how to simulate " %= asset 'resource' " that we can use in template rendering, | ||||||
321 | #nor how to output multiple Mojo::ByteStream objets at a time (is returning required ?) | ||||||
322 | 3 | 1034 | $$results .= ${ $c->asset($asset) }; | ||||
3 | 22 | ||||||
323 | } | ||||||
324 | 2 | 1947 | return $results; | ||||
325 | } | ||||||
326 | |||||||
327 | sub prepend_js{ | ||||||
328 | 0 | 0 | 1 | 0 | my ($c, @js) = @_; | ||
329 | 0 | 0 | 0 | my $dxhelper = $c->stash('dxHelper') or return; | |||
330 | 0 | 0 | for(@js){ | ||||
331 | 0 | 0 | 0 | $dxhelper->{before_bindings} .= "\n" if $INDENT_BINDING; | |||
332 | 0 | 0 | $dxhelper->{before_bindings} .= $_; | ||||
333 | } | ||||||
334 | } | ||||||
335 | |||||||
336 | |||||||
337 | sub append_js{ | ||||||
338 | 0 | 0 | 1 | 0 | my ($c, @js) = @_; | ||
339 | 0 | 0 | 0 | my $dxhelper = $c->stash('dxHelper') or return; | |||
340 | 0 | 0 | for(@js){ | ||||
341 | 0 | 0 | 0 | $dxhelper->{after_bindings} .= "\n" if $INDENT_BINDING; | |||
342 | 0 | 0 | $dxhelper->{after_bindings} .= $_; | ||||
343 | } | ||||||
344 | } | ||||||
345 | |||||||
346 | sub register { | ||||||
347 | 6 | 6 | 1 | 10 | my ( $self, $app, $args ) = @_; | ||
348 | 6 | 10 | my $tp = $args->{tag_prefix}; | ||||
349 | |||||||
350 | #build generic dx-controls | ||||||
351 | 6 | 27 | mk_dxcontrol( "dx$_" ) for @generic_controls; | ||||
352 | |||||||
353 | SUB_NO_PREFIX: | ||||||
354 | 6 | 13 | for my $subname ( @without_prefix ){ | ||||
355 | 36 | 497 | my $lc_name = lc $subname; | ||||
356 | 36 | 146 | my $sub = __PACKAGE__->can( $lc_name ); | ||||
357 | 36 | 50 | 79 | unless($sub){ | |||
358 | 0 | 0 | $app->log->debug(__PACKAGE__." helper '$lc_name' does not exists!"); | ||||
359 | 0 | 0 | next SUB_NO_PREFIX; | ||||
360 | } | ||||||
361 | 36 | 110 | $app->helper( $lc_name => $sub ); | ||||
362 | } | ||||||
363 | |||||||
364 | SUB_WITH_PREFIX: | ||||||
365 | 6 | 68 | for my $subname ( @with_prefix ){ | ||||
366 | 300 | 2991 | my $lc_name = lc $subname; | ||||
367 | 300 | 1002 | my $sub = __PACKAGE__->can( 'dx' . $lc_name ); | ||||
368 | 300 | 50 | 470 | unless($sub){ | |||
369 | 0 | 0 | $app->log->debug(__PACKAGE__." helper 'dx$lc_name' does not exists!"); | ||||
370 | 0 | 0 | next SUB_WITH_PREFIX; | ||||
371 | } | ||||||
372 | 300 | 190 | say STDERR "## adding helper '$tp$lc_name'" if DEBUG; | ||||
373 | 300 | 586 | $app->helper( $tp . $lc_name => $sub ); | ||||
374 | 300 | 2740 | say STDERR "## adding helper '$tp$subname'" if DEBUG and $args->{tag_camelcase}; | ||||
375 | 300 | 50 | 814 | $app->helper( $tp . $subname => $sub ) if $args->{tag_camelcase}; | |||
376 | } | ||||||
377 | |||||||
378 | } | ||||||
379 | |||||||
380 | 1; | ||||||
381 | |||||||
382 | __END__ |