| 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 | 25 | use utf8; | |||
| 7 | 9 | ||||||
| 7 | 34 | ||||||
| 2 | package Mojolicious::Plugin::DevexpressHelpers::Helpers; | ||||||
| 3 | $Mojolicious::Plugin::DevexpressHelpers::Helpers::VERSION = '0.163571'; | ||||||
| 4 | #ABSTRACT: Helpers for Devexpress controls are defined here | ||||||
| 5 | 7 | 7 | 299 | use Modern::Perl; | |||
| 7 | 8 | ||||||
| 7 | 23 | ||||||
| 6 | 7 | 7 | 897 | use Mojo::ByteStream; | |||
| 7 | 44640 | ||||||
| 7 | 239 | ||||||
| 7 | 7 | 7 | 2789 | use MojoX::AlmostJSON qw(encode_json); | |||
| 7 | 14604 | ||||||
| 7 | 383 | ||||||
| 8 | 7 | 7 | 32 | use constant DEBUG => 0; | |||
| 7 | 7 | ||||||
| 7 | 9606 | ||||||
| 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 | 24 | my $tag = shift; | ||
| 71 | 20 | 79 | my $bytes = Mojo::ByteStream->new($tag); | ||||
| 72 | 20 | 50 | 143 | return $bytes->decode($OUT_DECODE) if defined $OUT_DECODE; | |||
| 73 | 0 | 0 | return $bytes; | ||||
| 74 | } | ||||||
| 75 | |||||||
| 76 | sub new{ | ||||||
| 77 | 12 | 12 | 1 | 20 | my $class = shift; | ||
| 78 | 12 | 65 | my $self = bless { | ||||
| 79 | next_id => 1, | ||||||
| 80 | bindings => '', | ||||||
| 81 | before_bindings => '', | ||||||
| 82 | after_bindings => '', | ||||||
| 83 | }, $class; | ||||||
| 84 | 12 | 53 | 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 | 80 | my $self = shift; | ||
| 94 | 10 | 36 | $self->{bindings} .= join "\n", @_; | ||||
| 95 | } | ||||||
| 96 | |||||||
| 97 | sub next_id{ | ||||||
| 98 | 3 | 3 | 1 | 30 | my $self = shift; | ||
| 99 | 3 | 30 | 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 | 11 | $c->stash('dxHelper')->next_id; | ||||
| 106 | } | ||||||
| 107 | |||||||
| 108 | sub dxbind{ | ||||||
| 109 | 10 | 10 | 1 | 17 | my ($c, $control, $id, $attrs, $extensions, $befores, $afters) = @_; | ||
| 110 | #should return html code to be associated to the control | ||||||
| 111 | 10 | 100 | 37 | $befores //=[]; | |||
| 112 | 10 | 100 | 32 | $afters //=[]; | |||
| 113 | #http://stackoverflow.com/questions/9930577/jquery-dot-in-id-selector | ||||||
| 114 | 10 | 12 | my $jquery_id = $id; | ||||
| 115 | 10 | 21 | $jquery_id =~ s{\.}{\\\\.}g; | ||||
| 116 | 10 | 66 | 38 | my $prepend = ref $attrs eq 'HASH' && delete $attrs->{prependTo}; | |||
| 117 | 10 | 66 | 46 | my $append = ref $attrs eq 'HASH' && delete $attrs->{appendTo}; | |||
| 118 | 10 | 8 | my $binding = ''; | ||||
| 119 | 10 | 100 | 100 | 46 | if($prepend || $append){ | ||
| 120 | 2 | 5 | $binding = '$(\' \').'.$control.'('; |
||||
| 121 | } | ||||||
| 122 | else{ | ||||||
| 123 | 8 | 18 | $binding = '$("#'.$jquery_id.'").'.$control.'('; | ||||
| 124 | } | ||||||
| 125 | 10 | 18 | my @options; | ||||
| 126 | |||||||
| 127 | 10 | 100 | 25 | if (ref($attrs) eq 'HASH') { | |||
| 128 | 9 | 10 | $binding .= '{'; | ||||
| 129 | 9 | 50 | 21 | $binding .= "\n " if $INDENT_BINDING; | |||
| 130 | 9 | 35 | for my $k ( sort keys %$attrs){ | ||||
| 131 | 19 | 50 | 39 | my $v = $attrs->{$k} // ''; | |||
| 132 | 19 | 100 | 61 | if(ref($v) eq 'SCALAR'){ | |||
| 50 | |||||||
| 133 | #unref protected scalar | ||||||
| 134 | 2 | 3 | $v = $$v; | ||||
| 135 | } | ||||||
| 136 | elsif ($v!~/^\s*(?:function\s*\()/) { | ||||||
| 137 | 17 | 43 | $v = encode_json $v; | ||||
| 138 | } | ||||||
| 139 | 19 | 527 | push @options, "$k: $v"; | ||||
| 140 | } | ||||||
| 141 | } | ||||||
| 142 | else{ | ||||||
| 143 | 1 | 2 | push @options, $attrs; | ||||
| 144 | } | ||||||
| 145 | 10 | 50 | 38 | $binding .= join ",\n".($INDENT_BINDING?' ':''), @options; | |||
| 146 | 10 | 100 | 28 | $binding .= '}' if ref($attrs) eq 'HASH'; | |||
| 147 | 10 | 11 | $binding .= ')'; | ||||
| 148 | 10 | 100 | 21 | $binding .= '.prependTo("'.$prepend.'")' if $prepend; | |||
| 149 | 10 | 100 | 17 | $binding .= '.appendTo("'.$append.'")' if $append; | |||
| 150 | 10 | 50 | 25 | $binding .= ';' . ($INDENT_BINDING?"\n":""); | |||
| 151 | #append some extensions (eg: dxdatagrid) | ||||||
| 152 | 10 | 50 | 20 | $binding .= join ";\n".($INDENT_BINDING?' ':''), @$extensions if defined $extensions; | |||
| 100 | |||||||
| 153 | 10 | 28 | $c->stash('dxHelper')->add_binding($binding); | ||||
| 154 | 10 | 17 | my $html_code = ""; | ||||
| 155 | 10 | 100 | 100 | 38 | if($prepend || $append){ | ||
| 156 | 2 | 2 | $html_code = ''; | ||||
| 157 | } | ||||||
| 158 | 10 | 33 | out join('',@$befores, $html_code ,@$afters); | ||||
| 159 | } | ||||||
| 160 | |||||||
| 161 | |||||||
| 162 | sub parse_attributs{ | ||||||
| 163 | 10 | 10 | 1 | 10 | my $c = shift; | ||
| 164 | 10 | 10 | my @implicit_args = @{shift()}; | ||||
| 10 | 22 | ||||||
| 165 | 10 | 12 | my %attrs; | ||||
| 166 | IMPLICIT_ARGUMENT: | ||||||
| 167 | 10 | 100 | 62 | while(@_ and @implicit_args){ | |||
| 168 | 27 | 28 | my $ref = ref($_[0]); | ||||
| 169 | 27 | 50 | 51 | my $implicit = shift @implicit_args || ''; | |||
| 170 | 27 | 50 | 33 | 113 | 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 | 34 | $implicit =~ s/^[\\\*\%\@]//; | ||||
| 176 | 25 | 98 | $attrs{ $implicit } = shift @_; | ||||
| 177 | } | ||||||
| 178 | 10 | 100 | 23 | if(my $args = shift){ | |||
| 179 | 7 | 50 | 20 | if(ref($args) eq 'HASH'){ | |||
| 180 | NAMED_ARGUMENT: | ||||||
| 181 | 7 | 28 | while(my($k,$v)=each %$args){ | ||||
| 182 | 8 | 47 | $attrs{$k} = $v; | ||||
| 183 | } | ||||||
| 184 | } | ||||||
| 185 | } | ||||||
| 186 | 10 | 22 | 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 | 9187 | my $c = shift; | ||
| 207 | 3 | 14 | my $attrs = parse_attributs( $c, [qw(id text onClick type)], @_ ); | ||||
| 208 | 3 | 66 | 12 | my $id = delete($attrs->{id}) // new_id( $c, $attrs ); | |||
| 209 | 3 | 7 | dxbind( $c, 'dxButton' => $id => $attrs); | ||||
| 210 | } | ||||||
| 211 | |||||||
| 212 | |||||||
| 213 | sub dxdatagrid{ | ||||||
| 214 | 3 | 3 | 1 | 9087 | my $c = shift; | ||
| 215 | 3 | 11 | my $attrs = parse_attributs( $c, [qw(id dataSource)], @_ ); | ||||
| 216 | 3 | 66 | 10 | 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 | 17 | if ($attrs->{dataSource} && ref($attrs->{dataSource}) eq '') { | ||
| 220 | 1 | 4 | 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 | 5 | $attrs->{dataSource} = \"{store:{type:'odata',url:'$dataSource'}}"; | ||||
| 226 | } | ||||||
| 227 | 3 | 100 | 22 | if (exists $attrs->{options}) { | |||
| 228 | 1 | 2 | $attrs = $attrs->{options}; | ||||
| 229 | } | ||||||
| 230 | |||||||
| 231 | 3 | 7 | 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 | 5337 | my $c = shift; | ||
| 246 | 1 | 3 | my $attrs = parse_attributs( $c, [qw(id title contentTemplate)], @_ ); | ||||
| 247 | 1 | 33 | 4 | my $id = delete($attrs->{id}) // new_id( $c, $attrs ); | |||
| 248 | |||||||
| 249 | 1 | 2 | dxbind( $c, 'dxPopup' => $id => $attrs ); | ||||
| 250 | } | ||||||
| 251 | |||||||
| 252 | |||||||
| 253 | |||||||
| 254 | sub mk_dxcontrol{ | ||||||
| 255 | 258 | 258 | 1 | 201 | my $dxControl = shift; | ||
| 256 | my $generic = sub{ | ||||||
| 257 | 3 | 3 | 12245 | my $c = shift; | |||
| 258 | 3 | 12 | my $attrs = parse_attributs( $c, [qw(id value label)], @_ ); | ||||
| 259 | 3 | 7 | my $id = delete($attrs->{id}); | ||||
| 260 | 3 | 50 | 7 | if (my $name = $id) { | |||
| 261 | 3 | 5 | $attrs->{name}=$name; | ||||
| 262 | } | ||||||
| 263 | |||||||
| 264 | 3 | 33 | 7 | $id //= new_id( $c, $attrs ); | |||
| 265 | |||||||
| 266 | 3 | 4 | my (@before, @after); | ||||
| 267 | 3 | 50 | 7 | if(my $label = delete($attrs->{label})){ | |||
| 268 | 3 | 4 | push @before, ' '; |
||||
| 269 | 3 | 8 | push @before, ' '.$label.' '; |
||||
| 270 | 3 | 3 | push @before, ' '; |
||||
| 271 | 3 | 4 | push @after, ''; | ||||
| 272 | 3 | 4 | push @after, ''; | ||||
| 273 | } | ||||||
| 274 | |||||||
| 275 | 3 | 9 | dxbind( $c, $dxControl => $id => $attrs, undef, \@before, \@after ); | ||||
| 276 | 258 | 552 | }; | ||||
| 277 | |||||||
| 278 | { | ||||||
| 279 | 7 | 7 | 33 | no strict 'refs'; | |||
| 7 | 6 | ||||||
| 7 | 4691 | ||||||
| 258 | 164 | ||||||
| 280 | 258 | 153 | *{__PACKAGE__.'::'.lc $dxControl} = $generic; | ||||
| 258 | 1085 | ||||||
| 281 | } | ||||||
| 282 | } | ||||||
| 283 | |||||||
| 284 | |||||||
| 285 | sub dxbuild { | ||||||
| 286 | 10 | 10 | 1 | 7040 | my $c = shift; | ||
| 287 | 10 | 19 | my %opts = @_; | ||||
| 288 | 10 | 50 | 24 | my $dxhelper = $c->stash('dxHelper') or return; | |||
| 289 | 10 | 50 | 93 | if($dxhelper->{bindings}){ | |||
| 290 | out ''; | ||||||
| 50 | |||||||
| 299 | } | ||||||
| 300 | } | ||||||
| 301 | |||||||
| 302 | |||||||
| 303 | sub require_asset{ | ||||||
| 304 | 3 | 3 | 1 | 7660 | my $c = shift; | ||
| 305 | 3 | 50 | 6 | my $dxhelper = $c->stash('dxHelper') or return; | |||
| 306 | |||||||
| 307 | 3 | 29 | push @{ $dxhelper->{required_assets} }, $_ for @_; | ||||
| 3 | 8 | ||||||
| 308 | |||||||
| 309 | 3 | 4 | return $c; | ||||
| 310 | } | ||||||
| 311 | |||||||
| 312 | |||||||
| 313 | sub required_assets{ | ||||||
| 314 | 2 | 2 | 1 | 1304 | my $c = shift; | ||
| 315 | 2 | 50 | 7 | my $dxhelper = $c->stash('dxHelper') or return; | |||
| 316 | 2 | 50 | 24 | my $required_assets = $dxhelper->{required_assets} // []; | |||
| 317 | 2 | 21 | my $results = Mojo::ByteStream->new(); | ||||
| 318 | ASSET: | ||||||
| 319 | 2 | 18 | 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 | 794 | $$results .= ${ $c->asset($asset) }; | ||||
| 3 | 28 | ||||||
| 323 | } | ||||||
| 324 | 2 | 2097 | 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 | 7 | my ( $self, $app, $args ) = @_; | ||
| 348 | 6 | 9 | my $tp = $args->{tag_prefix}; | ||||
| 349 | |||||||
| 350 | #build generic dx-controls | ||||||
| 351 | 6 | 23 | mk_dxcontrol( "dx$_" ) for @generic_controls; | ||||
| 352 | |||||||
| 353 | SUB_NO_PREFIX: | ||||||
| 354 | 6 | 11 | for my $subname ( @without_prefix ){ | ||||
| 355 | 36 | 358 | my $lc_name = lc $subname; | ||||
| 356 | 36 | 106 | my $sub = __PACKAGE__->can( $lc_name ); | ||||
| 357 | 36 | 50 | 65 | 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 | 78 | $app->helper( $lc_name => $sub ); | ||||
| 362 | } | ||||||
| 363 | |||||||
| 364 | SUB_WITH_PREFIX: | ||||||
| 365 | 6 | 52 | for my $subname ( @with_prefix ){ | ||||
| 366 | 300 | 2447 | my $lc_name = lc $subname; | ||||
| 367 | 300 | 782 | my $sub = __PACKAGE__->can( 'dx' . $lc_name ); | ||||
| 368 | 300 | 50 | 438 | 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 | 157 | say STDERR "## adding helper '$tp$lc_name'" if DEBUG; | ||||
| 373 | 300 | 484 | $app->helper( $tp . $lc_name => $sub ); | ||||
| 374 | 300 | 2268 | say STDERR "## adding helper '$tp$subname'" if DEBUG and $args->{tag_camelcase}; | ||||
| 375 | 300 | 50 | 690 | $app->helper( $tp . $subname => $sub ) if $args->{tag_camelcase}; | |||
| 376 | } | ||||||
| 377 | |||||||
| 378 | } | ||||||
| 379 | |||||||
| 380 | 1; | ||||||
| 381 | |||||||
| 382 | __END__ |