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   149 use 5.20.0;
  14         46  
2 14     14   62 use warnings;
  14         27  
  14         783  
3              
4             package Mojolicious::Plugin::BootstrapHelpers::Helpers;
5              
6             # ABSTRACT: Supporting module
7             our $AUTHORITY = 'cpan:CSSON'; # AUTHORITY
8             our $VERSION = '0.0206';
9              
10 14     14   70 use List::Util qw/uniq/;
  14         27  
  14         790  
11 14     14   72 use Mojo::ByteStream;
  14         26  
  14         458  
12 14     14   72 use Mojo::Util 'xml_escape';
  14         27  
  14         601  
13 14     14   74 use Scalar::Util 'blessed';
  14         40  
  14         602  
14 14     14   5098 use String::Trim;
  14         7179  
  14         801  
15 14     14   5494 use String::Random;
  14         37952  
  14         748  
16 14     14   5243 use experimental qw/postderef signatures/;
  14         40221  
  14         74  
17              
18             sub bootstraps_bootstraps {
19 6     6 0 69351 my $c = shift;
20 6         15 my $arg = shift;
21              
22 6         13 my $bs_version = '3.4.1';
23              
24 6         18 my $css = qq{};
25 6         17 my $theme = qq{};
26 6         13 my $js = qq{};
27 6         12 my $jq = q{};
28              
29 6 50       59 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 29537 my($c, $title, $callback, $content, $attr) = parse_call(@_);
42              
43 4         26 $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         97 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 15 my $c = shift;
57 6         12 my $title = shift;
58 6         10 my $body = shift;
59 6         16 my $attr = shift;
60              
61 6 100       18 my $header_attr = exists $attr->{'-header'} ? delete($attr->{'-header'}) : [];
62 6         13 my $header_button_groups = '';
63 6 100       26 if(scalar keys @$header_attr) {
64 1         3 for my $attr (shift @$header_attr) {
65 1 50       5 if($attr eq 'buttongroup') {
66 1         6 $header_button_groups .= bootstrap_buttongroup($c, class => 'pull-right', (shift @$header_attr)->@*);
67             }
68             }
69             }
70              
71 6 100       73 my $tag = qq{
72            
73             } . (defined $title ? qq{
74            
75            

$header_button_groups$title

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

    });
    523             }
    524              
    525             sub get_hamburger {
    526 5     5 0 10 my $toggler_id = shift;
    527              
    528 5         15 my $tag = qq{
    529            
    530            
    531            
    532            
    533            
    534             };
    535 5         10 return $tag;
    536             }
    537              
    538             sub make_nav_meat {
    539 7     7 0 11 my $c = shift;
    540 7         12 my $contents = shift;
    541              
    542 7         14 my $tag = '';
    543             CONTENT:
    544 7         18 foreach my $content ($contents->@*) {
    545 18 50       44 next CONTENT if ref $content ne 'ARRAY';
    546              
    547 18         32 my $text = shift $content->@*;
    548 18         39 my $url = url_for($c, shift $content->@*);
    549 18         40 my $attr = parse_attributes($content->@*);
    550              
    551 18   100     56 my $items = delete $attr->{'items'} || [];
    552             #* No sub menu
    553 18 100       43 if(!scalar $items->@*) {
    554 14         25 my $active = delete $attr->{'__active'};
    555 14         27 my $disabled = delete $attr->{'__disabled'};
    556 14 100       29 my $li_attr = exists $attr->{'-li'} ? parse_attributes(delete($attr->{'-li'})->@*) : {};
    557 14 100       41 $li_attr = add_classes($li_attr, $active ? 'active' : (), $disabled ? 'disabled' : ());
        100          
    558 14         28 my $li_html = htmlify_attrs($li_attr);
    559              
    560 14         28 my $a_attr = htmlify_attrs($attr);
    561              
    562 14         36 my $link_html = qq{$text};
    563 14         43 $tag .= qq{$link_html};
    564 14         42 next CONTENT;
    565             }
    566             else {
    567 4         10 $attr = add_classes($attr, 'dropdown-toggle');
    568 4         11 $attr->{'data-toggle'} = 'dropdown';
    569 4         10 $attr->{'href'} = $url;
    570 4 50       21 my $caret = delete $attr->{'__caret'} ? ' ' : '';
    571 4         10 my $button_arg = htmlify_attrs($attr);
    572 4         17 my $button_html = qq{$text$caret};
    573              
    574 4         8 my $lis = '';
    575             ITEM:
    576 4         11 foreach my $item ($items->@*) {
    577 20 100       43 if(!scalar $item->@*) {
    578 5         9 $lis .= q{
  • };
    579 5         13 next ITEM;
    580             }
    581 15         22 my $text = shift $item->@*;
    582 15         28 my $url = url_for($c, shift $item->@*);
    583 15         30 my $a_attr = parse_attributes($item->@*);
    584 15         39 $a_attr->{'href'} = $url;
    585 15         27 my $a_html = htmlify_attrs($a_attr);
    586 15         42 $lis .= qq{
  • $text
  • };
    587             }
    588 4         19 $tag .= qq{
    589            
    590             $button_html
    591            
    592             $lis
    593            
    594            
    595             };
    596             }
    597             }
    598 7         24 return $tag;
    599             }
    600              
    601             sub bootstrap_nav {
    602 2     2 0 12 my $c = shift;
    603 2         9 my $attr = parse_attributes(@_);
    604 2         5 my $pills = delete $attr->{'pills'};
    605 2         4 my $tabs = delete $attr->{'tabs'};
    606              
    607 2 50       9 my $which = $pills ? 'nav-pills'
        100          
    608             : $tabs ? 'nav-tabs'
    609             : ()
    610             ;
    611 2 100       6 my $justified = delete $attr->{'__justified'} ? 'nav-justified' : ();
    612 2         9 $attr = add_classes($attr, 'nav', $which, $justified);
    613 2         8 my $html = htmlify_attrs($attr);
    614 2   66     9 my $either = $pills || $tabs;
    615              
    616 2         7 my $tag = make_nav_meat($c, $either);
    617 2         14 $tag = qq{$tag};
    618              
    619 2         8 return out($tag);
    620             }
    621              
    622             sub bootstrap_badge {
    623 3     3 0 16447 my $c = shift;
    624 3 50       9 my $content = iscoderef($_[-1]) ? pop : shift;
    625 3         9 my $attr = parse_attributes(@_);
    626              
    627 3         9 $attr = add_classes($attr, 'badge', { direction => 'pull-%s' });
    628 3         33 my $html = htmlify_attrs($attr);
    629              
    630 3 50 33     15 my $badge = defined $content && length $content ? qq{$content} : '';
    631              
    632 3         7 return out($badge);
    633             }
    634              
    635             sub bootstrap_icon {
    636 4     4 0 40896 my $c = shift;
    637 4         10 my $icon = shift;
    638              
    639 4         28 my $icon_class = $c->config->{'Plugin::BootstrapHelpers'}{'icons'}{'class'};
    640 4         126 my $formatter = $c->config->{'Plugin::BootstrapHelpers'}{'icons'}{'formatter'};
    641              
    642 4         69 my $this_icon = sprintf $formatter => $icon;
    643 4         19 my $attr = parse_attributes(@_);
    644 4         16 $attr = add_classes($attr, $icon_class, $this_icon);
    645 4         18 my $html = htmlify_attrs($attr);
    646              
    647 4 50 33     63 return '' if !defined $icon || !length $icon;
    648 4         18 return out(qq{});
    649             }
    650              
    651             sub bootstrap_input {
    652 5     5 0 28140 my $c = shift;
    653 5         21 my $attr = parse_attributes(@_);
    654              
    655 5         17 my $prepend = delete $attr->{'prepend'};
    656 5         13 my $append = delete $attr->{'append'};
    657              
    658 5         23 my(undef, $input_tag) = fix_input($c, (delete $attr->{'input'}) );
    659 5         64 $attr = add_classes($attr, 'input-group', { size => 'input-group-%s' });
    660 5         18 my $html = htmlify_attrs($attr);
    661              
    662 5         14 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       13 if($append) {
    668 3         9 $append_tag = fix_input_ender($c, $append);
    669             }
    670              
    671 5   100     48 my $tag = qq{
          100        
    672            
    673             } . ($prepend_tag || '') . qq{
    674             $input_tag
    675             } . ($append_tag || '') . qq{
    676            
    677             };
    678              
    679 5         14 return out($tag);
    680              
    681             }
    682              
    683             sub fix_input_ender {
    684 6     6 0 12 my $c = shift;
    685 6         28 my $ender = shift;
    686 6         17 my $where = shift;
    687              
    688 6 100       18 if(ref $ender eq '') {
    689 1         3 return qq{$ender};
    690             }
    691              
    692 5         15 my $key = (keys $ender->%*)[0];
    693 5         12 my $tag = undef;
    694 5 100 100     32 if($key eq 'check_box' || $key eq 'radio_button') {
        100          
        50          
    695 2 100       6 my $type = $key eq 'check_box' ? 'checkbox' : 'radio';
    696 2         9 my $extra_input = Mojolicious::Plugin::TagHelpers::_input($c, $ender->{ $key }->@*, type => $type);
    697 2         297 $tag = qq{
    698             $extra_input
    699             };
    700             }
    701             elsif($key eq 'button') {
    702 1         5 my $button = bootstrap_button($c, $ender->{ $key }->@*);
    703 1         10 $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         13 $tag = qq{
    713            
    $button_group
    714             }
    715             }
    716 0         0 else { $tag = $key; }
    717              
    718 5         32 return $tag;
    719             }
    720              
    721             sub iscoderef {
    722 39     39 0 100 return ref shift eq 'CODE';
    723             }
    724             sub url_for {
    725 86     86 0 152 my $c = shift;
    726 86         122 my $url = shift;
    727 86 50       199 return '' if ref $url ne 'ARRAY';
    728 86 100 66     373 return $url->[0] if scalar $url->@* == 1 && substr ($url->[0], 0, 1) eq '#';
    729 43         624 return $c->url_for($url->@*)->to_string;
    730             }
    731              
    732             sub fix_input {
    733 17     17 0 35 my $c = shift;
    734 17         25 my $attr = shift;
    735              
    736 17         48 my $tagname;
    737             my $info;
    738              
    739 17 100       50 if((grep { exists $attr->{"${_}_field"} } qw/date datetime month time week color email number range search tel text url password/)[0]) {
      238 50       454  
    740 16         43 $tagname = (grep { exists $attr->{"${_}_field"} } qw/date datetime month time week color email number range search tel text url password/)[0];
      224         376  
    741 16         35 $info = $attr->{"${tagname}_field"};
    742             }
    743             elsif(exists $attr->{'text_area'}) {
    744 1         2 $tagname = 'textarea';
    745 1         2 $info = $attr->{'text_area'};
    746             }
    747 17         45 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       45 if($info->@* % 2) {
    751 2         7 push $info->@* => (value => shift $info->@*);
    752             }
    753 17         44 my $tag_attr = { $info->@* };
    754              
    755 17         63 my @column_classes = get_column_classes($attr->{'column_information'}, 1);
    756 17         69 $tag_attr = add_classes($tag_attr, 'form-control', { size => 'input-%s' });
    757              
    758 17 50 33     98 $tag_attr->{'id'} = $id if defined $id && !exists $tag_attr->{'id'};
    759 17 50       61 my $name_attr = defined $id ? $id =~ s{-}{_}rg : undef;
    760              
    761 17         46 $tag_attr = cleanup_attrs($tag_attr);
    762              
    763 17 100       50 my $horizontal_before = scalar @column_classes ? qq{
    ' : '';
    764 17 100       75 my $horizontal_after = scalar @column_classes ? '' : '';
    765 17 100       103 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         21425 return ($id => $horizontal_before . $input . $horizontal_after);
    770              
    771             }
    772              
    773             sub fix_label {
    774 8     8 0 17 my $c = shift;
    775 8         14 my $for = shift;
    776 8         13 my $title = shift;
    777 8         13 my $attr = shift;
    778              
    779 8         22 my @column_classes = get_column_classes($attr->{'column_information'}, 0);
    780 8         32 my @args = (class => trim join ' ' => ('control-label', @column_classes));
    781 8 100       171 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         8 my $title = shift;
    789 4 100       18 my $callback = ref $_[-1] eq 'CODE' ? pop : undef;
    790 4 50       16 my $content = scalar @_ % 2 ? pop : '';
    791 4         17 my $attr = parse_attributes(@_);
    792              
    793 4         17 return ($c, $title, $callback, $content, $attr);
    794             }
    795              
    796             sub parse_attributes {
    797 226     226 0 528 my %attr = @_;
    798 226 100 66     586 if($attr{'data'} && ref $attr{'data'} eq 'HASH') {
    799 9         17 while(my($key, $value) = each %{ $attr{'data'} }) {
      18         68  
    800 9         24 $key =~ tr/_/-/;
    801 9         42 $attr{ lc("data-$key") } = $value;
    802             }
    803 9         20 delete $attr{'data'};
    804             }
    805 226         488 return \%attr;
    806             }
    807              
    808             sub get_column_classes {
    809 25     25 0 87 my $attr = shift;
    810 25         41 my $index = shift;
    811              
    812 25         46 my @classes = ();
    813 25         77 foreach my $key (keys $attr->%*) {
    814 6         16 my $correct_name = get_size_for($key);
    815 6 50       18 if(defined $correct_name) {
    816 6         28 push @classes => sprintf "col-%s-%d" => $correct_name, $attr->{ $key }[ $index ];
    817             }
    818             }
    819 25         84 return sort @classes;
    820             }
    821              
    822             sub add_classes {
    823 248     248 0 419 my $attr = shift;
    824 248 100       556 my $formatter = ref $_[-1] eq 'HASH' ? pop : undef;
    825              
    826 14     14   81459 no warnings 'uninitialized';
      14         47  
      14         17019  
    827              
    828 248         576 my @classes = ($attr->{'class'}, @_);
    829              
    830 248 100       554 if($formatter) {
    831 127 100       275 if(exists $formatter->{'size'}) {
    832 95         349 push @classes => sprintfify_class($attr, $formatter->{'size'}, $formatter->{'size_default'}, _sizes());
    833             }
    834 127 100       489 if(exists $formatter->{'button'}) {
    835 51         122 push @classes => sprintfify_class($attr, $formatter->{'button'}, $formatter->{'button_default'}, _button_contexts());
    836             }
    837 127 100       370 if(exists $formatter->{'panel'}) {
    838 6         28 push @classes => sprintfify_class($attr, $formatter->{'panel'}, $formatter->{'panel_default'}, _panel_contexts());
    839             }
    840 127 100       295 if(exists $formatter->{'table'}) {
    841 6         21 push @classes => sprintfify_class($attr, $formatter->{'table'}, $formatter->{'table_default'}, _table_contexts());
    842             }
    843 127 100       267 if(exists $formatter->{'direction'}) {
    844 15         47 push @classes => sprintfify_class($attr, $formatter->{'direction'}, $formatter->{'direction_default'}, _direction_contexts());
    845             }
    846 127 100       291 if(exists $formatter->{'navbar'}) {
    847 5         16 push @classes => sprintfify_class($attr, $formatter->{'navbar'}, $formatter->{'navbar_default'}, _navbar_contexts());
    848             }
    849             }
    850              
    851 248         414 my %uniqs = ();
    852 248         1666 $attr->{'class'} = trim join ' ' => uniq sort @classes;
    853              
    854 248         5564 return $attr;
    855              
    856             }
    857              
    858             sub sprintfify_class {
    859 178     178 0 312 my $attr = shift;
    860 178         245 my $format = shift;
    861 178         244 my $possibilities = pop;
    862 178         314 my $default = shift;
    863              
    864 178         602 my @founds = (grep { exists $attr->{ $_ } } (keys $possibilities->%*));
      2186         3463  
    865              
    866 178 100 100     753 return if !scalar @founds && !defined $default;
    867 85 100       206 push @founds => $default if !scalar @founds;
    868              
    869 85         132 return map { sprintf $format => $possibilities->{ $_ } } @founds;
      91         399  
    870              
    871             }
    872              
    873             sub add_disabled {
    874 87     87 0 142 my $attr = shift;
    875 87         117 my $add_as_class = shift; # if false, add as attribute
    876              
    877 87 100 100     261 if(exists $attr->{'__disabled'} && $attr->{'__disabled'}) {
    878 3 100       10 if($add_as_class) {
    879 2         7 $attr = add_classes($attr, 'disabled');
    880             }
    881             else {
    882 1         4 $attr->{'disabled'} = 'disabled';
    883             }
    884             }
    885 87         141 return $attr;
    886             }
    887              
    888             sub contents {
    889 4     4 0 9 my $callback = shift;
    890 4         10 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 90 my $content = shift;
    897              
    898 51 100       211 return ref $content eq 'CODE' ? $content->() : xml_escape($content);
    899             }
    900              
    901             sub cleanup_attrs {
    902 314     314 0 491 my $hash = shift;
    903              
    904             #* delete all shortcuts (-> __*)
    905 314         694 map { delete $hash->{ $_ } } grep { substr($_, 0, 2) eq '__' } keys $hash->%*;
      98         259  
      616         1360  
    906              
    907             #* delete all keys whose value is not a string
    908 314 50       613 map { delete $hash->{ $_ } } grep { $_ ne 'data' && ref $hash->{ $_ } ne '' } keys $hash->%*;
      15         38  
      518         1737  
    909              
    910 314         562 return $hash;
    911             }
    912              
    913             sub get_size_for {
    914 6     6 0 9 my $input = shift;
    915              
    916 6         11 return _sizes()->{ $input };
    917             }
    918              
    919             sub _sizes {
    920             return {
    921 101     101   841 __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   126 return { map { ("__$_" => $_, $_ => $_) } qw/default primary success info warning danger link/ };
      357         1141  
    930             }
    931             sub _panel_contexts {
    932 6     6   19 return { map { ("__$_" => $_, $_ => $_) } qw/default primary success info warning danger/ };
      36         169  
    933             }
    934             sub _table_contexts {
    935 6     6   13 return { map { ("__$_" => $_, $_ => $_) } qw/striped bordered hover condensed responsive/ };
      30         96  
    936             }
    937             sub _direction_contexts {
    938 15     15   30 return { map { ("__$_" => $_, $_ => $_) } qw/right block vertical justified dropup left/ };
      90         287  
    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   11 return { map { ("__$_" => $_, $_ => $_) } qw/default inverse/ };
      10         44  
    948             }
    949              
    950             sub out {
    951 177     177 0 714 my $tag = shift;
    952 177         615 return Mojo::ByteStream->new($tag);
    953             }
    954              
    955             1;
    956              
    957             __END__