1use strict;
2
3package HTML::FormFu::Role::Element::Layout;
4# ABSTRACT: layout role
5$HTML::FormFu::Role::Element::Layout::VERSION = '2.07';
6use Moose::Role;
7use MooseX::Attribute::Chained;
8
9use Carp qw( carp croak );
10use List::MoreUtils qw( first_index );
11use Scalar::Util qw( reftype );
12
13use HTML::FormFu::Util qw( process_attrs );
14
15has layout_errors_filename =>
16    ( is => 'rw', traits => ['Chained'], default => 'field_layout_errors' );
17has layout_label_filename =>
18    ( is => 'rw', traits => ['Chained'], default => 'field_layout_label' );
19has layout_field_filename =>
20    ( is => 'rw', traits => ['Chained'], default => 'field_layout_field' );
21has layout_comment_filename =>
22    ( is => 'rw', traits => ['Chained'], default => 'field_layout_comment' );
23has layout_javascript_filename =>
24    ( is => 'rw', traits => ['Chained'], default => 'field_layout_javascript' );
25has layout_label_text_filename =>
26    ( is => 'rw', traits => ['Chained'], default => 'field_layout_label_text' );
27has layout_block_filename =>
28    ( is => 'rw', traits => ['Chained'], default => 'field_layout_block' );
29
30has layout_parser_filename =>
31    ( is => 'rw', traits => ['Chained'], default => 'field_layout_parser' );
32
33has _layout => (
34    is      => 'rw',
35    default => sub {
36        return [ 'errors', 'label', 'field', 'comment', 'javascript', ];
37    },
38);
39
40# if we ever remove the reverse_single() method, we can make layout()
41# a standard Moose attribute
42
43sub layout {
44    my $self = shift;
45
46    if (@_) {
47        $self->_layout(@_);
48        return $self;
49    }
50
51    my $value = $self->_layout;
52
53    if ( defined $value && $self->reverse_single ) {
54
55        # if it's an array-ref,
56        # and 'label' and 'field' are consecutive values (in any order)
57        # then just swap them around
58        # otherwise warn that reverse_single() is deprecated
59
60        my ( $ok, $field_index, $label_index );
61
62        if ( ref $value && 'ARRAY' eq reftype($value) ) {
63            $field_index = first_index { 'field' eq $_ } @$value;
64            $label_index = first_index { 'label' eq $_ } @$value;
65
66            if (   defined $field_index
67                && defined $label_index
68                && 1 == abs( $field_index - $label_index ) )
69            {
70                $ok = 1;
71            }
72        }
73
74        if ($ok) {
75
76            # create new arrayref so we don't change the stored value
77            $value = [@$value];
78
79            @$value[$field_index] = 'label';
80            @$value[$label_index] = 'field';
81        }
82        else {
83            carp "reverse_single() is deprecated, and is having no affect.";
84        }
85    }
86
87    return $value;
88}
89
90has _multi_layout => (
91    is      => 'rw',
92    default => sub {
93        return [ 'label', 'field', ];
94    },
95);
96
97# if we ever remove the reverse_multi() method, we can make multi_layout()
98# a standard Moose attribute
99
100sub multi_layout {
101    my $self = shift;
102
103    if (@_) {
104        $self->_multi_layout(@_);
105        return $self;
106    }
107
108    my $value = $self->_multi_layout;
109
110    if ( defined $value && $self->reverse_multi ) {
111
112        # if it's an array-ref,
113        # and 'label' and 'field' are consecutive values (in any order)
114        # then just swap them around
115        # otherwise warn that reverse_multi() is deprecated
116
117        my ( $ok, $field_index, $label_index );
118
119        if ( ref $value && 'ARRAY' eq reftype($value) ) {
120            $field_index = first_index { 'field' eq $_ } @$value;
121            $label_index = first_index { 'label' eq $_ } @$value;
122
123            if (   defined $field_index
124                && defined $label_index
125                && 1 == abs( $field_index - $label_index ) )
126            {
127                $ok = 1;
128            }
129        }
130
131        if ($ok) {
132
133            # create new arrayref so we don't change the stored value
134            $value = [@$value];
135
136            @$value[$field_index] = 'label';
137            @$value[$label_index] = 'field';
138        }
139        else {
140            carp "reverse_multi() is deprecated, and is having no affect.";
141        }
142    }
143
144    return $value;
145}
146
147after BUILD => sub {
148    my $self = shift;
149
150    $self->filename('field_layout');
151
152    return;
153};
154
155around render_data_non_recursive => sub {
156    my ( $orig, $self, $args ) = @_;
157
158    my $render = $self->$orig(
159        {   layout                     => $self->layout,
160            multi_layout               => $self->multi_layout,
161            layout_errors_filename     => $self->layout_errors_filename,
162            layout_label_filename      => $self->layout_label_filename,
163            layout_field_filename      => $self->layout_field_filename,
164            layout_comment_filename    => $self->layout_comment_filename,
165            layout_javascript_filename => $self->layout_javascript_filename,
166            layout_label_text_filename => $self->layout_label_text_filename,
167            layout_block_filename      => $self->layout_block_filename,
168            layout_parser_filename     => $self->layout_parser_filename,
169            $args ? %$args : (),
170        } );
171
172    return $render;
173};
174
175sub string {
176    my ( $self, $args ) = @_;
177
178    $args ||= {};
179
180    my $render
181        = exists $args->{render_data}
182        ? $args->{render_data}
183        : $self->render_data;
184
185    my $layout
186        = exists $args->{layout}
187        ? $args->{layout}
188        : $self->layout;
189
190    my $html = "";
191
192    if ( defined $render->{container_tag} ) {
193        $html .= sprintf "<%s%s>\n",
194            $render->{container_tag},
195            process_attrs( $render->{container_attributes} );
196    }
197
198    $html .= $self->_parse_layout( $render, $layout );
199
200    if ( defined $render->{container_tag} ) {
201        $html .= sprintf "\n</%s>", $render->{container_tag},;
202    }
203
204    return $html;
205}
206
207sub _parse_layout {
208    my ( $self, $render, $layout ) = @_;
209
210    croak "undefined 'layout'" if !defined $layout;
211
212    my $html = "";
213
214    if ( ref $layout && 'ARRAY' eq ref $layout ) {
215        my @item_html;
216        for my $item (@$layout) {
217            push @item_html, $self->_parse_layout( $render, $item );
218        }
219        $html .=
220            join "\n",
221            grep { defined && length } @item_html;
222    }
223    elsif ( ref $layout && 'HASH' eq ref $layout ) {
224        my ( $key, $value ) = %$layout;
225
226        if ( my $method = $self->can("_parse_layout_$key") ) {
227            $html .= $self->$method( $render, $key, $value );
228        }
229        else {
230            $html .= $self->_parse_layout_block( $render, $key, $value );
231        }
232    }
233    elsif ( my $method = $self->can("_parse_layout_$layout") ) {
234        $html .= $self->$method($render);
235    }
236    else {
237        croak "Unknown layout() option: '$layout'";
238    }
239
240    return $html;
241}
242
243sub _parse_layout_errors {
244    my ( $self, $render ) = @_;
245
246    return $self->_string_errors($render);
247}
248
249sub _parse_layout_label {
250    my $self   = shift;
251    my $render = shift;
252
253    return ""
254        unless exists $render->{label}
255        && defined $render->{label}
256        && length $render->{label};
257
258    if (@_) {
259        my ( $tag, @content ) = @_;
260
261        return $self->_parse_layout_block(
262            $render, $tag,
263            {   attributes => $render->{label_attributes},
264                content    => \@content,
265            },
266        );
267    }
268    else {
269        return $self->_string_label($render);
270    }
271}
272
273sub _parse_layout_field {
274    my ( $self, $render ) = @_;
275
276    return $self->_string_field($render);
277}
278
279sub _parse_layout_comment {
280    my ( $self, $render ) = @_;
281
282    return "" if !defined $render->{comment};
283
284    my $html = sprintf "<%s%s>\n%s\n</%s>",
285        'span',
286        process_attrs( $render->{comment_attributes} ),
287        $render->{comment},
288        'span';
289
290    return $html;
291}
292
293sub _parse_layout_javascript {
294    my ( $self, $render ) = @_;
295
296    return "" if !defined $render->{javascript};
297
298    my $html = sprintf qq{<script type="text/javascript">\n%s\n</script>},
299        $render->{javascript};
300
301    return $html;
302}
303
304sub _parse_layout_label_text {
305    my ( $self, $render ) = @_;
306
307    return "" unless exists $render->{label} && length $render->{label};
308
309    return $render->{label};
310}
311
312sub _parse_layout_block {
313    my ( $self, $render, $tag, $opts ) = @_;
314
315    $opts ||= {};
316
317    my $html = "<$tag";
318
319    if ( $opts->{attributes} ) {
320        $html .= process_attrs( $opts->{attributes} );
321    }
322
323    $html .= ">\n";
324
325    if ( $opts->{content} ) {
326        $html .= $self->_parse_layout( $render, $opts->{content} );
327    }
328
329    $html .= "\n</$tag>";
330
331    return $html;
332}
333
3341;
335
336__END__
337
338=pod
339
340=encoding UTF-8
341
342=head1 NAME
343
344HTML::FormFu::Role::Element::Layout - layout role
345
346=head1 VERSION
347
348version 2.07
349
350=head1 AUTHOR
351
352Carl Franks <cpan@fireartist.com>
353
354=head1 COPYRIGHT AND LICENSE
355
356This software is copyright (c) 2018 by Carl Franks.
357
358This is free software; you can redistribute it and/or modify it under
359the same terms as the Perl 5 programming language system itself.
360
361=cut
362