File Coverage

blib/lib/Mojolicious/Plugin/BootstrapHelpers/Helpers.pm
Criterion Covered Total %
statement 503 511 98.4
branch 204 238 85.7
condition 36 53 67.9
subroutine 61 63 96.8
pod 0 44 0.0
total 804 909 88.4


line stmt bran cond sub pod time code
1 14     14   107 use 5.20.0;
  14         32  
2 14     14   44 use warnings;
  14         17  
  14         737  
3              
4             package Mojolicious::Plugin::BootstrapHelpers::Helpers;
5              
6             # ABSTRACT: Supporting module
7             our $AUTHORITY = 'cpan:CSSON'; # AUTHORITY
8             our $VERSION = '0.0204';
9              
10 14     14   51 use List::Util qw/uniq/;
  14         15  
  14         1021  
11 14     14   60 use Mojo::ByteStream;
  14         16  
  14         406  
12 14     14   42 use Mojo::Util 'xml_escape';
  14         18  
  14         479  
13 14     14   48 use Scalar::Util 'blessed';
  14         13  
  14         550  
14 14     14   5700 use String::Trim;
  14         6749  
  14         567  
15 14     14   5556 use String::Random;
  14         37804  
  14         709  
16 14     14   5781 use experimental qw/postderef signatures/;
  14         32871  
  14         64  
17              
18             sub bootstraps_bootstraps {
19 6     6 0 45578 my $c = shift;
20 6         6 my $arg = shift;
21              
22 6         6 my $css = q{};
23 6         7 my $theme = q{};
24 6         3 my $js = q{};
25 6         5 my $jq = q{};
26              
27 6 50       42 return out(
    100          
    100          
    100          
    100          
    100          
28             !defined $arg ? $css
29             : $arg eq 'css' ? join "\n" => $css, $theme
30             : $arg eq 'js' ? join "\n" => $js
31             : $arg eq 'jsq' ? join "\n" => $jq, $js
32             : $arg eq 'all' ? join "\n" => $css, $theme, $js
33             : $arg eq 'allq' ? join "\n" => $css, $theme, $jq, $js
34             : ''
35             );
36             }
37              
38             sub bootstrap_panel {
39 4     4 0 20567 my($c, $title, $callback, $content, $attr) = parse_call(@_);
40              
41 4         20 $attr = add_classes($attr, 'panel', { panel => 'panel-%s', panel_default => 'default'});
42              
43 4         11 my $body = qq{
44            
45             } . contents($callback, $content) . qq{
46            
47             };
48              
49 4         66 return create_panel($c, $title, $body, $attr);
50              
51             }
52             #-header => [buttongroup => ['Scan', ['#'], data => { bind => 'click: scanDatabases'}]]
53             sub create_panel {
54 6     6 0 7 my $c = shift;
55 6         9 my $title = shift;
56 6         6 my $body = shift;
57 6         8 my $attr = shift;
58              
59 6 100       17 my $header_attr = exists $attr->{'-header'} ? delete($attr->{'-header'}) : [];
60 6         10 my $header_button_groups = '';
61 6 100       20 if(scalar keys @$header_attr) {
62 1         3 for my $attr (shift @$header_attr) {
63 1 50       5 if($attr eq 'buttongroup') {
64 1         4 $header_button_groups .= bootstrap_buttongroup($c, class => 'pull-right', (shift @$header_attr)->@*);
65             }
66             }
67             }
68              
69 6 100       48 my $tag = qq{
70            
71             } . (defined $title ? qq{
72            
73            

$header_button_groups$title

74            
75             } : '') . qq{
76             $body
77            
78             };
79              
80 6         11 return out($tag);
81             }
82              
83             sub bootstrap_table {
84 6     6 0 23564 my $c = shift;
85 6 50       17 my $callback = ref $_[-1] eq 'CODE' ? pop : undef;
86 6 100       14 my $title = scalar @_ % 2 ? shift : undef;
87 6         14 my $attr = parse_attributes(@_);
88              
89 6         22 $attr = add_classes($attr, 'table', { table => 'table-%s' });
90 6         16 my $html = htmlify_attrs($attr);
91              
92 6         27 my $table = qq{
93            
94             } . $callback->() . qq{
95            
96             };
97              
98 6 100       144 if(defined $title) {
99 2         9 $attr->{'panel'} = add_classes($attr->{'panel'}, 'panel', { panel => 'panel-%s', panel_default => 'default'});
100             }
101              
102 6 100       21 return defined $title ? create_panel($c, $title, $table, $attr->{'panel'}) : out($table);
103             }
104              
105             sub htmlify_attrs {
106 231     231 0 207 my $attr = shift;
107 231 50       341 return '' if !defined $attr;
108 231         590 $attr = cleanup_attrs({$attr->%*}); #* Make a copy
109              
110 231         417 my $html = join ' ' => map { qq{$_="$attr->{ $_ }"} } grep { length $attr->{ $_ } } sort keys $attr->%*;
  331         611  
  340         347  
111 231 50       673 return ' ' . $html if defined $html;
112 0         0 return '';
113             }
114              
115             sub bootstrap_formgroup {
116 12     12 0 48494 my $c = shift;
117 12 100       39 my $title = ref $_[-1] eq 'CODE' ? pop
    100          
118             : scalar @_ % 2 ? shift
119             : undef;
120 12         23 my $attr = parse_attributes(@_);
121              
122 12 100       25 $attr->{'column_information'} = delete $attr->{'cols'} if ref $attr->{'cols'} eq 'HASH';
123              
124 12         20 my($id, $input) = fix_input($c, $attr);
125 12 100       101 my $label = defined $title ? fix_label($c, $id, $title, $attr) : '';
126              
127 12         505 $attr = add_classes($attr, 'form-group', { size => 'form-group-%s'});
128 12         24 $attr = cleanup_attrs($attr);
129              
130              
131 12         37 my $tag = qq{
132            
133             $label
134             $input
135            
136             };
137              
138 12         43 return out($tag);
139             }
140              
141             sub bootstrap_button {
142 51     51 0 21183 my $c = shift;
143 51 100       105 my $content = ref $_[-1] eq 'CODE' ? pop : shift;
144 51 100       99 $content = '' if !defined $content;
145              
146 51 100       112 my @url = shift->@* if ref $_[0] eq 'ARRAY';
147 51         76 my $attr = parse_attributes(@_);
148              
149 51 100 66     158 my $caret = exists $attr->{'__caret'} && $attr->{'__caret'} ? (length $content ? ' ' : '') . q{} : '';
    100          
150              
151 51 100 66     154 $attr->{'type'} = 'button' if !scalar @url && !exists $attr->{'type'};
152 51         173 $attr = add_classes($attr, 'btn', { size => 'btn-%s', button => 'btn-%s', button_default => 'default' });
153 51 100       126 $attr = add_classes($attr, 'active') if $attr->{'__active'};
154 51 100       105 $attr = add_classes($attr, 'block') if $attr->{'__block'};
155 51         77 $attr = add_disabled($attr, scalar @url);
156 51         74 $attr = cleanup_attrs($attr);
157              
158             # We have an url
159 51 100       76 if(scalar @url) {
160 12         37 $attr->{'href'} = url_for($c, \@url);
161              
162 12         1191 my $html = htmlify_attrs($attr);
163 12         33 return out(qq{} . content_single($content) . qq{$caret});
164             }
165             else {
166 39         59 my $html = htmlify_attrs($attr);
167 39         111 return out(qq{} . content_single($content) . qq{$caret});
168             }
169              
170             }
171              
172             sub bootstrap_submit {
173 6     6 0 7037 push @_ => (type => 'submit');
174 6         12 return bootstrap_button(@_);
175             }
176              
177             sub bootstrap_context_menu {
178 1     1 0 12616 my $c = shift;
179 1         3 my %args = @_;
180 1         2 my $items = delete $args{'items'};
181 1         2 my $list = make_dropdown_list($c, \%args, $items);
182              
183 1         9 return out($list);
184             }
185              
186             sub bootstrap_dropdown {
187 2     2 0 16 my $meat = make_dropdown_meat(shift, shift->@*);
188              
189 2         18 my $dropdown = qq{
190            
191             $meat
192            
193             };
194 2         10 return out($dropdown);
195             }
196              
197             sub make_dropdown_meat {
198 11     11 0 11 my $c = shift;
199              
200 11         13 my $button_text = shift;
201 11 100       28 my @url = ref $_[0] eq 'ARRAY' ? shift->@* : ();
202 11         26 my $attr = parse_attributes(@_);
203              
204 11   50     27 my $items = delete $attr->{'items'} || [];
205 11 100       31 my $ulattr = { __right => exists $attr->{'__right'} ? delete $attr->{'__right'} : 0 };
206 11         24 my $listhtml = make_dropdown_list($c, $ulattr, $items);
207              
208 11         57 $attr = add_classes($attr, 'dropdown-toggle');
209 11         21 $attr->{'data-toggle'} = 'dropdown';
210 11         18 $attr->{'type'} = 'button';
211 11         40 my $button = bootstrap_button($c, $button_text, @url, $attr->%*);
212              
213 11         80 my $out = qq{
214             $button
215             $listhtml
216             };
217 11         75 return out($out);
218             }
219              
220             sub make_dropdown_list {
221 12     12 0 13 my $c = shift;
222 12         15 my $attr = shift;
223 12         9 my $items = shift;
224              
225 12         22 $attr = add_classes($attr, 'dropdown-menu');
226 12 100       44 $attr = add_classes($attr, 'dropdown-menu-right') if $attr->{'__right'};
227 12         26 my $attrhtml = htmlify_attrs($attr);
228 12         19 my $menuitems = '';
229              
230             ITEM:
231 12         22 foreach my $item ($items->@*) {
232 49 100       348 if(ref $item eq '') {
233 1         3 $menuitems .= qq{};
234             }
235 49 100       77 next ITEM if ref $item ne 'ARRAY';
236 48 100       76 if(!scalar $item->@*) {
237 12         20 $menuitems .= q{
  • } ;
    238             }
    239             else {
    240 36         58 $menuitems .= create_dropdown_menuitem($c, $item->@*);
    241             }
    242              
    243             }
    244 12         127 my $out = qq{
    245            
    246             $menuitems
    247            
    248             };
    249 12         21 return out($out);
    250             }
    251              
    252             sub create_dropdown_menuitem {
    253 36     36 0 31 my $c = shift;
    254 36 50       71 my $item_text = iscoderef($_[-1]) ? pop : shift;
    255 36         59 my @url = shift->@*;
    256              
    257 36         48 my $attr = parse_attributes(@_);
    258 36         49 $attr = add_classes($attr, 'menuitem');
    259 36 100       76 my $liattr = { __disabled => exists $attr->{'__disabled'} ? delete $attr->{'__disabled'} : 0 };
    260 36         60 $liattr = add_disabled($liattr, 1);
    261 36   100     152 $attr->{'tabindex'} ||= -1;
    262 36         53 $attr->{'href'} = url_for($c, \@url);
    263              
    264 36         8433 my $html = htmlify_attrs($attr);
    265 36         50 my $lihtml = htmlify_attrs($liattr);
    266              
    267 36         66 my $tag = qq{$item_text};
    268              
    269 36         52 return out($tag);
    270             }
    271              
    272             sub bootstrap_buttongroup {
    273 11     11 0 13134 my $c = shift;
    274              
    275             #* Shortcut for one button menus
    276 11 100       30 if(ref $_[0] eq 'ARRAY') {
    277 2         4 my $meat = make_dropdown_meat($c, $_[0]->@*);
    278 2         13 my $menu = qq{
    $meat
    };
    279 2         9 return out($menu);
    280             }
    281 9         19 my($buttons, $html) = make_buttongroup_meat($c, @_);
    282              
    283 9         47 my $tag = qq{
    284            
    285             $buttons
    286             };
    287              
    288 9         12 return out($tag);
    289             }
    290              
    291             sub make_buttongroup_meat {
    292 10     10 0 12 my $c = shift;
    293              
    294 10         19 my $attr = parse_attributes(@_);
    295 10         19 my $buttons_info = delete $attr->{'buttons'};
    296              
    297 10 100       29 my $button_group_class = delete $attr->{'__vertical'} ? 'btn-group-vertical' : 'btn-group';
    298 10 100       22 my $justified_class = delete $attr->{'__justified'} ? 'btn-group-justified' : ();
    299              
    300 10         26 $attr = add_classes($attr, { size => 'btn-group-%s' });
    301              
    302             #* For the possible inner btn-group, use the same base classes (except justified/vertical/pull-right).
    303             #* We add possible .dropup in the loop
    304 10         34 my $inner_attr = add_classes({ class => $attr->{'class'} }, 'btn-group');
    305 10         18 $inner_attr->{'class'} =~ s{\bpull-right\b}{};
    306              
    307             #* These must come after the inner button group has been given the classes.
    308 10         19 $attr = add_classes($attr, $button_group_class, $justified_class);
    309 10         22 my $html = htmlify_attrs($attr);
    310              
    311              
    312 10         16 my $buttons = '';
    313              
    314             BUTTON:
    315 10         20 foreach my $button ($buttons_info->@*) {
    316 29 50       78 next BUTTON if ref $button ne 'ARRAY';
    317              
    318 29         32 my $button_text = shift $button->@*;
    319 29 100       65 my @url = ref $button->[0] eq 'ARRAY' ? shift $button->@* : ();
    320 29         41 my $button_attr = parse_attributes($button->@*);
    321 29   100     105 my $items = delete $button_attr->{'items'} || [];
    322              
    323 29 100       44 if(!scalar $items->@*) {
    324 23         51 my $bootstrap_button = bootstrap_button($c, $button_text, @url, $button_attr->%*);
    325              
    326             #* Justified + No url -> button -> must nest
    327 23 100 100     168 if(length $justified_class && !scalar @url) {
    328 3         6 $buttons .= qq{
    $bootstrap_button
    };
    329             }
    330             else {
    331 20         93 $buttons .= $bootstrap_button;
    332             }
    333 23         150 next BUTTON;
    334             }
    335              
    336 6 100       15 my $dropup_class = delete $button_attr->{'__dropup'} ? 'dropup' : ();
    337 6         11 $inner_attr = add_classes($inner_attr, $dropup_class);
    338 6         11 my $inner_html = htmlify_attrs($inner_attr);
    339 6 100       34 my $meat = make_dropdown_meat($c, $button_text, (scalar @url ? \@url : ()), $button_attr->%*, items => $items);
    340 6         44 $buttons .= qq{
    341            
    342             $meat
    343            
    344             };
    345              
    346             }
    347 10         49 return ($buttons, $html);
    348             }
    349              
    350             sub bootstrap_toolbar {
    351 1     1 0 5 my $c = shift;
    352              
    353 1         3 my $attr = parse_attributes(@_);
    354 1         2 my $groups = delete $attr->{'groups'};
    355              
    356 1         3 $attr = add_classes($attr, 'btn-toolbar');
    357 1         3 my $html = htmlify_attrs($attr);
    358              
    359 1         2 my $toolbar = '';
    360 1         2 foreach my $group ($groups->@*) {
    361 2         16 $toolbar .= bootstrap_buttongroup($c, $group->%*);
    362             }
    363              
    364 1         9 my $tag = qq{
    365            
    366             $toolbar
    367            
    368             };
    369              
    370 1         1 return out($tag);
    371             }
    372              
    373             sub bootstrap_navbar {
    374 5     5 0 20 my $c = shift;
    375              
    376 5         7 my($possible_toggler_id, $navbar_header) = ();
    377              
    378 5         6 my $content_html = '';
    379 5         5 my $has_inverse = 0;
    380 5         5 my $container = 'fluid';
    381              
    382 5         14 while(scalar @_) {
    383 20         14 my $key = shift;
    384 20         23 my @arguments = ($c, shift);
    385              
    386 20 100       67 $content_html .= $key eq 'nav' ? make_navbar_nav(@arguments)
        100          
        100          
        100          
    387             : $key eq 'form' ? make_navbar_form(@arguments)
    388             : $key eq 'button' ? make_navbar_button(@arguments)
    389             : $key eq 'p' ? make_navbar_p(@arguments)
    390             : ''
    391             ;
    392 20 100       94 if($key eq 'header') {
    393 5         11 ($possible_toggler_id, $navbar_header) = make_navbar_header(@arguments);
    394             }
    395 20 100       30 if($key eq '__inverse') {
    396 2         3 $has_inverse = 1;
    397             }
    398 20 100       49 if($key eq 'container') {
    399 1         2 $container = $arguments[1];
    400             }
    401              
    402             }
    403 5 100       11 $container = $container eq 'normal' ? 'container' : 'container-fluid';
    404              
    405 5 100       8 my $attr = $has_inverse ? { __inverse => 1 } : {};
    406 5         13 $attr = add_classes($attr, 'navbar', { navbar => 'navbar-%s', navbar_default => 'default' });
    407 5         10 my $html = htmlify_attrs($attr);
    408 5 100       9 if(length $content_html) {
    409 4         17 $content_html = qq{
    410            
    411             $content_html
    412            
    413             };
    414             }
    415              
    416 5         17 my $tag = qq{
    417            
    418            
    419             $navbar_header
    420             $content_html
    421            
    422            
    423             };
    424              
    425 5         6 return out($tag);
    426              
    427             }
    428             sub make_navbar_header {
    429 5     5 0 4 my($c, $header, $html_header) = @_;
    430              
    431 5 50       7 return (undef, $html_header) if $html_header;
    432              
    433 5         9 my $brand = shift $header->@*;
    434 5   50     15 my $url = url_for($c, shift $header->@* || []);
    435 5         11 my $header_attr = parse_attributes($header->@*);
    436 5   33     14 my $toggler_id = delete $header_attr->{'toggler'} || 'collapsable-' . String::Random->new->randregex('[a-z]{20}');
    437 5         6 my $has_hamburger = delete $header_attr->{'__hamburger'};
    438 5 50       11 my $hamburger = $has_hamburger ? get_hamburger($toggler_id) : '';
    439 5         9 $header_attr = add_classes($header_attr, 'navbar-brand');
    440              
    441 5 50       16 my $brand_html = defined $brand ? qq{$brand} : '';
    442              
    443 5         8 my $navbar_header = qq{
    444            
    445             $hamburger
    446             $brand_html
    447            
    448             };
    449              
    450 5         10 return ($toggler_id, $navbar_header);
    451              
    452             }
    453              
    454             sub make_navbar_nav {
    455 5     5 0 4 my $c = shift;
    456 5         4 my $nav = shift;
    457              
    458 5         10 my $attr = parse_attributes($nav->@*);
    459 5         7 my $items = delete $attr->{'items'};
    460 5         12 $attr = add_classes($attr, 'nav', 'navbar-nav', { direction => 'navbar-%s' });
    461 5         11 my $html = htmlify_attrs($attr);
    462              
    463 5         31 my $tag = "";
    464 5         11 $tag .= make_nav_meat($c, $items);
    465              
    466 5         6 $tag .= '';
    467 5         10 return $tag;
    468             }
    469              
    470             sub make_navbar_form {
    471 3     3 0 3 my $c = shift;
    472 3         5 my $form = shift;
    473 3         3 my $args = shift $form->@*;
    474 3         4 my $contents = shift $form->@*;
    475 3         3 my $url = shift $args->@*;
    476              
    477 3         4 my $attr = parse_attributes($args->@*);
    478              
    479 3         7 $attr = add_classes($attr, 'navbar-form', { direction => 'navbar-%s', direction_default => 'left' });
    480 3         7 $attr = cleanup_attrs($attr);
    481              
    482 3         4 my $tag = '';
    483 3         8 for (my $index = 0; $index < scalar $contents->@*; $index += 2) {
    484 6         36 my $key = $contents->[$index];
    485 6         14 my @arguments = ($c, $contents->[$index + 1]->@*);
    486              
    487 6 100       13 if($key eq 'formgroup') {
        50          
        0          
        0          
    488 3         5 $tag .= bootstrap_formgroup(@arguments);
    489             }
    490             elsif($key eq 'submit_button') {
    491 3         6 $tag .= bootstrap_submit(@arguments);
    492             }
    493             elsif($key eq 'button') {
    494 0         0 $tag .= bootstrap_button(@arguments)
    495             }
    496             elsif($key eq 'input') {
    497 0         0 $tag .= bootstrap_input(@arguments);
    498             }
    499             }
    500              
    501 3     3   51 $tag = Mojolicious::Plugin::TagHelpers::_form_for($c, $url->@*, $attr->%*, sub { $tag });
      3         617  
    502 3         681 return out($tag);
    503              
    504             }
    505              
    506 2 50   2 0 5 sub make_navbar_button($c, $arg) {
      2 50       5  
      2         1  
      2         2  
      2         2  
    507 2         2 my $text = shift $arg->@*;
    508 2         2 my $url = shift $arg->@*;
    509 2         5 my $attr = parse_attributes($arg->@*);
    510 2         5 $attr = add_classes($attr, 'navbar-btn', { direction => 'navbar-%s' });
    511 2         6 return bootstrap_button($c, $text, $url, $attr->%*);
    512             }
    513              
    514 2 50   2 0 5 sub make_navbar_p($c, $arg) {
      2 50       5  
      2         1  
      2         2  
      2         3  
    515 2         2 my $text = shift $arg->@*;
    516 2         3 my $attr = parse_attributes($arg->@*);
    517 2         5 $attr = add_classes($attr, 'navbar-text', { direction => 'navbar-%s' });
    518 2         5 my $html = htmlify_attrs($attr);
    519              
    520 2         6 return out(qq{$text

    });
    521             }
    522              
    523             sub get_hamburger {
    524 5     5 0 5 my $toggler_id = shift;
    525              
    526 5         9 my $tag = qq{
    527            
    528            
    529            
    530            
    531            
    532             };
    533 5         6 return $tag;
    534             }
    535              
    536             sub make_nav_meat {
    537 7     7 0 8 my $c = shift;
    538 7         6 my $contents = shift;
    539              
    540 7         7 my $tag = '';
    541             CONTENT:
    542 7         11 foreach my $content ($contents->@*) {
    543 18 50       37 next CONTENT if ref $content ne 'ARRAY';
    544              
    545 18         18 my $text = shift $content->@*;
    546 18         25 my $url = url_for($c, shift $content->@*);
    547 18         26 my $attr = parse_attributes($content->@*);
    548              
    549 18   100     50 my $items = delete $attr->{'items'} || [];
    550             #* No sub menu
    551 18 100       36 if(!scalar $items->@*) {
    552 14         16 my $active = delete $attr->{'__active'};
    553 14         22 my $disabled = delete $attr->{'__disabled'};
    554 14 100       22 my $li_attr = exists $attr->{'-li'} ? parse_attributes(delete($attr->{'-li'})->@*) : {};
    555 14 100       33 $li_attr = add_classes($li_attr, $active ? 'active' : (), $disabled ? 'disabled' : ());
        100          
    556 14         17 my $li_html = htmlify_attrs($li_attr);
    557              
    558 14         19 my $a_attr = htmlify_attrs($attr);
    559              
    560 14         21 my $link_html = qq{$text};
    561 14         20 $tag .= qq{$link_html};
    562 14         61 next CONTENT;
    563             }
    564             else {
    565 4         6 $attr = add_classes($attr, 'dropdown-toggle');
    566 4         8 $attr->{'data-toggle'} = 'dropdown';
    567 4         6 $attr->{'href'} = $url;
    568 4 50       10 my $caret = delete $attr->{'__caret'} ? ' ' : '';
    569 4         6 my $button_arg = htmlify_attrs($attr);
    570 4         11 my $button_html = qq{$text$caret};
    571              
    572 4         6 my $lis = '';
    573             ITEM:
    574 4         6 foreach my $item ($items->@*) {
    575 20 100       30 if(!scalar $item->@*) {
    576 5         6 $lis .= q{
  • };
    577 5         6 next ITEM;
    578             }
    579 15         14 my $text = shift $item->@*;
    580 15         20 my $url = url_for($c, shift $item->@*);
    581 15         19 my $a_attr = parse_attributes($item->@*);
    582 15         18 $a_attr->{'href'} = $url;
    583 15         16 my $a_html = htmlify_attrs($a_attr);
    584 15         30 $lis .= qq{
  • $text
  • };
    585             }
    586 4         15 $tag .= qq{
    587            
    588             $button_html
    589            
    590             $lis
    591            
    592            
    593             };
    594             }
    595             }
    596 7         12 return $tag;
    597             }
    598              
    599             sub bootstrap_nav {
    600 2     2 0 6 my $c = shift;
    601 2         5 my $attr = parse_attributes(@_);
    602 2         3 my $pills = delete $attr->{'pills'};
    603 2         2 my $tabs = delete $attr->{'tabs'};
    604              
    605 2 50       6 my $which = $pills ? 'nav-pills'
        100          
    606             : $tabs ? 'nav-tabs'
    607             : ()
    608             ;
    609 2 100       4 my $justified = delete $attr->{'__justified'} ? 'nav-justified' : ();
    610 2         4 $attr = add_classes($attr, 'nav', $which, $justified);
    611 2         5 my $html = htmlify_attrs($attr);
    612 2   66     7 my $either = $pills || $tabs;
    613              
    614 2         3 my $tag = make_nav_meat($c, $either);
    615 2         5 $tag = qq{$tag};
    616              
    617 2         3 return out($tag);
    618             }
    619              
    620             sub bootstrap_badge {
    621 3     3 0 12131 my $c = shift;
    622 3 50       8 my $content = iscoderef($_[-1]) ? pop : shift;
    623 3         8 my $attr = parse_attributes(@_);
    624              
    625 3         8 $attr = add_classes($attr, 'badge', { direction => 'pull-%s' });
    626 3         8 my $html = htmlify_attrs($attr);
    627              
    628 3 50 33     12 my $badge = defined $content && length $content ? qq{$content} : '';
    629              
    630 3         5 return out($badge);
    631             }
    632              
    633             sub bootstrap_icon {
    634 4     4 0 27597 my $c = shift;
    635 4         4 my $icon = shift;
    636              
    637 4         24 my $icon_class = $c->config->{'Plugin::BootstrapHelpers'}{'icons'}{'class'};
    638 4         168 my $formatter = $c->config->{'Plugin::BootstrapHelpers'}{'icons'}{'formatter'};
    639              
    640 4         99 my $this_icon = sprintf $formatter => $icon;
    641 4         8 my $attr = parse_attributes(@_);
    642 4         8 $attr = add_classes($attr, $icon_class, $this_icon);
    643 4         9 my $html = htmlify_attrs($attr);
    644              
    645 4 50 33     15 return '' if !defined $icon || !length $icon;
    646 4         10 return out(qq{});
    647             }
    648              
    649             sub bootstrap_input {
    650 5     5 0 22968 my $c = shift;
    651 5         22 my $attr = parse_attributes(@_);
    652              
    653 5         12 my $prepend = delete $attr->{'prepend'};
    654 5         7 my $append = delete $attr->{'append'};
    655              
    656 5         19 my(undef, $input_tag) = fix_input($c, (delete $attr->{'input'}) );
    657 5         61 $attr = add_classes($attr, 'input-group', { size => 'input-group-%s' });
    658 5         22 my $html = htmlify_attrs($attr);
    659              
    660 5         9 my($prepend_tag, $append_tag) = undef;
    661              
    662 5 100       11 if($prepend) {
    663 3         13 $prepend_tag = fix_input_ender($c, $prepend);
    664             }
    665 5 100       13 if($append) {
    666 3         11 $append_tag = fix_input_ender($c, $append);
    667             }
    668              
    669 5   100     39 my $tag = qq{
          100        
    670            
    671             } . ($prepend_tag || '') . qq{
    672             $input_tag
    673             } . ($append_tag || '') . qq{
    674            
    675             };
    676              
    677 5         9 return out($tag);
    678              
    679             }
    680              
    681             sub fix_input_ender {
    682 6     6 0 9 my $c = shift;
    683 6         13 my $ender = shift;
    684 6         9 my $where = shift;
    685              
    686 6 100       15 if(ref $ender eq '') {
    687 1         3 return qq{$ender};
    688             }
    689              
    690 5         16 my $key = (keys $ender->%*)[0];
    691 5         4 my $tag = undef;
    692 5 100 100     46 if($key eq 'check_box' || $key eq 'radio_button') {
        100          
        50          
    693 2 100       4 my $type = $key eq 'check_box' ? 'checkbox' : 'radio';
    694 2         8 my $extra_input = Mojolicious::Plugin::TagHelpers::_input($c, $ender->{ $key }->@*, type => $type);
    695 2         186 $tag = qq{
    696             $extra_input
    697             };
    698             }
    699             elsif($key eq 'button') {
    700 1         6 my $button = bootstrap_button($c, $ender->{ $key }->@*);
    701 1         8 $tag = qq{
    702             $button
    703             };
    704             }
    705             elsif($key eq 'buttongroup') {
    706 2 50       7 return '' if ref $ender->{ $key } ne 'ARRAY';
    707             my($button_group) = scalar $ender->{ $key }->@* == 1 ? make_dropdown_meat($c, $ender->{ $key }[0]->@*)
    708 2 100       12 : make_buttongroup_meat($c, $ender->{ $key }->@*)
    709             ;
    710 2         21 $tag = qq{
    711            
    $button_group
    712             }
    713             }
    714 0         0 else { $tag = $key; }
    715              
    716 5         24 return $tag;
    717             }
    718              
    719             sub iscoderef {
    720 39     39 0 89 return ref shift eq 'CODE';
    721             }
    722             sub url_for {
    723 86     86 0 73 my $c = shift;
    724 86         62 my $url = shift;
    725 86 50       148 return '' if ref $url ne 'ARRAY';
    726 86 100 66     384 return $url->[0] if scalar $url->@* == 1 && substr ($url->[0], 0, 1) eq '#';
    727 43         392 return $c->url_for($url->@*)->to_string;
    728             }
    729              
    730             sub fix_input {
    731 17     17 0 16 my $c = shift;
    732 17         19 my $attr = shift;
    733              
    734 17         19 my $tagname;
    735             my $info;
    736              
    737 17 100       26 if((grep { exists $attr->{"${_}_field"} } qw/date datetime month time week color email number range search tel text url password/)[0]) {
      238 50       280  
    738 16         24 $tagname = (grep { exists $attr->{"${_}_field"} } qw/date datetime month time week color email number range search tel text url password/)[0];
      224         218  
    739 16         25 $info = $attr->{"${tagname}_field"};
    740             }
    741             elsif(exists $attr->{'text_area'}) {
    742 1         2 $tagname = 'textarea';
    743 1         1 $info = $attr->{'text_area'};
    744             }
    745 17         49 my $id = shift $info->@*;
    746              
    747             # if odd number of elements, the first one is the value (shortcut to avoid having to: value => 'value')
    748 17 100       37 if($info->@* % 2) {
    749 2         4 push $info->@* => (value => shift $info->@*);
    750             }
    751 17         25 my $tag_attr = { $info->@* };
    752              
    753 17         48 my @column_classes = get_column_classes($attr->{'column_information'}, 1);
    754 17         54 $tag_attr = add_classes($tag_attr, 'form-control', { size => 'input-%s' });
    755              
    756 17 50 33     100 $tag_attr->{'id'} = $id if defined $id && !exists $tag_attr->{'id'};
    757 17 50       50 my $name_attr = defined $id ? $id =~ s{-}{_}rg : undef;
    758              
    759 17         29 $tag_attr = cleanup_attrs($tag_attr);
    760              
    761 17 100       31 my $horizontal_before = scalar @column_classes ? qq{
    ' : '';
    762 17 100       53 my $horizontal_after = scalar @column_classes ? '' : '';
    763 17 100       76 my $input = exists $attr->{'text_area'} ? Mojolicious::Plugin::TagHelpers::_text_area($c, $name_attr, delete $tag_attr->{'value'}, $tag_attr->%*)
    764             : Mojolicious::Plugin::TagHelpers::_input($c, $name_attr, $tag_attr->%*, type => $tagname)
    765             ;
    766              
    767 17         6604 return ($id => $horizontal_before . $input . $horizontal_after);
    768              
    769             }
    770              
    771             sub fix_label {
    772 8     8 0 6 my $c = shift;
    773 8         7 my $for = shift;
    774 8         5 my $title = shift;
    775 8         7 my $attr = shift;
    776              
    777 8         17 my @column_classes = get_column_classes($attr->{'column_information'}, 0);
    778 8         23 my @args = (class => trim join ' ' => ('control-label', @column_classes));
    779 8 100       137 ref $title eq 'CODE' ? push @args => $title : unshift @args => $title;
    780              
    781 8         18 return Mojolicious::Plugin::TagHelpers::_label_for($c, $for, @args);
    782             }
    783              
    784             sub parse_call {
    785 4     4 0 7 my $c = shift;
    786 4         5 my $title = shift;
    787 4 100       12 my $callback = ref $_[-1] eq 'CODE' ? pop : undef;
    788 4 50       11 my $content = scalar @_ % 2 ? pop : '';
    789 4         12 my $attr = parse_attributes(@_);
    790              
    791 4         14 return ($c, $title, $callback, $content, $attr);
    792             }
    793              
    794             sub parse_attributes {
    795 226     226 0 377 my %attr = @_;
    796 226 100 66     495 if($attr{'data'} && ref $attr{'data'} eq 'HASH') {
    797 9         9 while(my($key, $value) = each %{ $attr{'data'} }) {
      18         59  
    798 9         36 $key =~ tr/_/-/;
    799 9         23 $attr{ lc("data-$key") } = $value;
    800             }
    801 9         14 delete $attr{'data'};
    802             }
    803 226         281 return \%attr;
    804             }
    805              
    806             sub get_column_classes {
    807 25     25 0 41 my $attr = shift;
    808 25         22 my $index = shift;
    809              
    810 25         31 my @classes = ();
    811 25         54 foreach my $key (keys $attr->%*) {
    812 6         8 my $correct_name = get_size_for($key);
    813 6 50       17 if(defined $correct_name) {
    814 6         20 push @classes => sprintf "col-%s-%d" => $correct_name, $attr->{ $key }[ $index ];
    815             }
    816             }
    817 25         61 return sort @classes;
    818             }
    819              
    820             sub add_classes {
    821 248     248 0 214 my $attr = shift;
    822 248 100       410 my $formatter = ref $_[-1] eq 'HASH' ? pop : undef;
    823              
    824 14     14   67357 no warnings 'uninitialized';
      14         25  
      14         14109  
    825              
    826 248         391 my @classes = ($attr->{'class'}, @_);
    827              
    828 248 100       396 if($formatter) {
    829 127 100       207 if(exists $formatter->{'size'}) {
    830 95         237 push @classes => sprintfify_class($attr, $formatter->{'size'}, $formatter->{'size_default'}, _sizes());
    831             }
    832 127 100       393 if(exists $formatter->{'button'}) {
    833 51         96 push @classes => sprintfify_class($attr, $formatter->{'button'}, $formatter->{'button_default'}, _button_contexts());
    834             }
    835 127 100       317 if(exists $formatter->{'panel'}) {
    836 6         21 push @classes => sprintfify_class($attr, $formatter->{'panel'}, $formatter->{'panel_default'}, _panel_contexts());
    837             }
    838 127 100       245 if(exists $formatter->{'table'}) {
    839 6         18 push @classes => sprintfify_class($attr, $formatter->{'table'}, $formatter->{'table_default'}, _table_contexts());
    840             }
    841 127 100       222 if(exists $formatter->{'direction'}) {
    842 15         33 push @classes => sprintfify_class($attr, $formatter->{'direction'}, $formatter->{'direction_default'}, _direction_contexts());
    843             }
    844 127 100       234 if(exists $formatter->{'navbar'}) {
    845 5         9 push @classes => sprintfify_class($attr, $formatter->{'navbar'}, $formatter->{'navbar_default'}, _navbar_contexts());
    846             }
    847             }
    848              
    849 248         264 my %uniqs = ();
    850 248         1499 $attr->{'class'} = trim join ' ' => uniq sort @classes;
    851              
    852 248         4778 return $attr;
    853              
    854             }
    855              
    856             sub sprintfify_class {
    857 178     178 0 150 my $attr = shift;
    858 178         178 my $format = shift;
    859 178         122 my $possibilities = pop;
    860 178         203 my $default = shift;
    861              
    862 178         416 my @founds = (grep { exists $attr->{ $_ } } (keys $possibilities->%*));
      2186         1880  
    863              
    864 178 100 66     671 return if !scalar @founds && !defined $default;
    865 85 100       157 push @founds => $default if !scalar @founds;
    866              
    867 85         85 return map { sprintf $format => $possibilities->{ $_ } } @founds;
      91         357  
    868              
    869             }
    870              
    871             sub add_disabled {
    872 87     87 0 74 my $attr = shift;
    873 87         69 my $add_as_class = shift; # if false, add as attribute
    874              
    875 87 100 66     196 if(exists $attr->{'__disabled'} && $attr->{'__disabled'}) {
    876 3 100       10 if($add_as_class) {
    877 2         6 $attr = add_classes($attr, 'disabled');
    878             }
    879             else {
    880 1         3 $attr->{'disabled'} = 'disabled';
    881             }
    882             }
    883 87         92 return $attr;
    884             }
    885              
    886             sub contents {
    887 4     4 0 6 my $callback = shift;
    888 4         4 my $content = shift;
    889              
    890 4 100       13 return defined $callback ? $callback->() : xml_escape($content);
    891             }
    892              
    893             sub content_single {
    894 51     51 0 46 my $content = shift;
    895              
    896 51 100       148 return ref $content eq 'CODE' ? $content->() : xml_escape($content);
    897             }
    898              
    899             sub cleanup_attrs {
    900 314     314 0 220 my $hash = shift;
    901              
    902             #* delete all shortcuts (-> __*)
    903 314         457 map { delete $hash->{ $_ } } grep { substr($_, 0, 2) eq '__' } keys $hash->%*;
      98         163  
      616         864  
    904              
    905             #* delete all keys whose value is not a string
    906 314 50       366 map { delete $hash->{ $_ } } grep { $_ ne 'data' && ref $hash->{ $_ } ne '' } keys $hash->%*;
      15         24  
      518         1496  
    907              
    908 314         345 return $hash;
    909             }
    910              
    911             sub get_size_for {
    912 6     6 0 5 my $input = shift;
    913              
    914 6         8 return _sizes()->{ $input };
    915             }
    916              
    917             sub _sizes {
    918             return {
    919 101     101   654 __xsmall => 'xs', xsmall => 'xs', xs => 'xs',
    920             __small => 'sm', small => 'sm', sm => 'sm',
    921             __medium => 'md', medium => 'md', md => 'md',
    922             __large => 'lg', large => 'lg', lg => 'lg',
    923             }
    924             }
    925              
    926             sub _button_contexts {
    927 51     51   59 return { map { ("__$_" => $_, $_ => $_) } qw/default primary success info warning danger link/ };
      357         717  
    928             }
    929             sub _panel_contexts {
    930 6     6   12 return { map { ("__$_" => $_, $_ => $_) } qw/default primary success info warning danger/ };
      36         107  
    931             }
    932             sub _table_contexts {
    933 6     6   7 return { map { ("__$_" => $_, $_ => $_) } qw/striped bordered hover condensed responsive/ };
      30         81  
    934             }
    935             sub _direction_contexts {
    936 15     15   18 return { map { ("__$_" => $_, $_ => $_) } qw/right block vertical justified dropup left/ };
      90         185  
    937             }
    938             sub _menu_contexts {
    939 0     0   0 return { map { ("__$_" => undef, $_ => undef) } qw/caret hamburger/ };
      0         0  
    940             }
    941             sub _misc_contexts {
    942 0     0   0 return { map { ("__$_" => $_, $_ => $_) } qw/active disabled/ };
      0         0  
    943             }
    944             sub _navbar_contexts {
    945 5     5   5 return { map { ("__$_" => $_, $_ => $_) } qw/default inverse/ };
      10         36  
    946             }
    947              
    948             sub out {
    949 177     177 0 454 my $tag = shift;
    950 177         495 return Mojo::ByteStream->new($tag);
    951             }
    952              
    953             1;
    954              
    955             __END__