File Coverage

blib/lib/Mojolicious/Plugin/BootstrapHelpers/Helpers.pm
Criterion Covered Total %
statement 502 510 98.4
branch 200 230 86.9
condition 39 53 73.5
subroutine 61 63 96.8
pod 0 44 0.0
total 802 900 89.1


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

$header_button_groups$title

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

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