Pflichtenheft: Zugriff auf nicht vorhandenes »visible_item« verhindern
[kivitendo-erp.git] / SL / Layout / Base.pm
1 package SL::Layout::Base;
2
3 use strict;
4 use parent qw(Rose::Object);
5
6 use List::MoreUtils qw(uniq);
7 use Time::HiRes qw();
8
9 use Rose::Object::MakeMethods::Generic (
10   'scalar --get_set_init' => [ qw(menu auto_reload_resources_param) ],
11   'scalar'                => qw(focus),
12   'array'                 => [
13     'add_stylesheets_inline' => { interface => 'add', hash_key => 'stylesheets_inline' },
14     'add_javascripts_inline' => { interface => 'add', hash_key => 'javascripts_inline' },
15     'sub_layouts',           => { interface => 'get_set_init' },
16     'add_sub_layouts'        => { interface => 'add', hash_key => 'sub_layouts' },
17   ],
18 );
19
20 use SL::Menu;
21 use SL::Presenter;
22
23 my %menu_cache;
24
25 sub new {
26   my ($class, @slurp) = @_;
27
28   my $self = $class->SUPER::new(@slurp);
29 }
30
31 sub init_menu {
32   SL::Menu->new('user');
33 }
34
35 sub init_auto_reload_resources_param {
36   return '' unless $::lx_office_conf{debug}->{auto_reload_resources};
37   return sprintf('?rand=%d-%d-%d', Time::HiRes::gettimeofday(), int(rand 1000000000000));
38 }
39
40 ##########################################
41 #  inheritable/overridable
42 ##########################################
43
44 sub pre_content {
45   join '', map { $_->pre_content } $_[0]->sub_layouts;
46 }
47
48 sub start_content {
49   join '', map { $_->start_content } $_[0]->sub_layouts;
50 }
51
52 sub end_content {
53   join '', map { $_->end_content } $_[0]->sub_layouts;
54 }
55
56 sub post_content {
57   join '', map { $_->post_content } $_[0]->sub_layouts;
58 }
59
60 sub stylesheets_inline {
61   uniq ( map { $_->stylesheets_inline } $_[0]->sub_layouts ),
62   @{ $_[0]->{stylesheets_inline} || [] };
63 }
64
65 sub javascripts_inline {
66   uniq ( map { $_->javascripts_inline } $_[0]->sub_layouts ),
67   @{ $_[0]->{javascripts_inline} || [] };
68 }
69
70 sub init_sub_layouts { [] }
71
72
73 #########################################
74 # Interface
75 ########################################
76
77 sub add_stylesheets {
78   &use_stylesheet;
79 }
80
81 sub use_stylesheet {
82   my $self = shift;
83   push @{ $self->{stylesheets} ||= [] }, @_ if @_;
84   @{ $self->{stylesheets} ||= [] };
85 }
86
87 sub stylesheets {
88   my ($self) = @_;
89   my $css_path = $self->get_stylesheet_for_user;
90
91   return uniq grep { $_ } map { $self->_find_stylesheet($_, $css_path)  }
92     $self->use_stylesheet, map { $_->stylesheets } $self->sub_layouts;
93 }
94
95 sub _find_stylesheet {
96   my ($self, $stylesheet, $css_path) = @_;
97
98   return "$css_path/$stylesheet" if -f "$css_path/$stylesheet";
99   return "css/$stylesheet"       if -f "css/$stylesheet";
100   return $stylesheet             if -f $stylesheet;
101 }
102
103 sub get_stylesheet_for_user {
104   my $css_path = 'css';
105   if (my $user_style = $::myconfig{stylesheet}) {
106     $user_style =~ s/\.css$//; # nuke trailing .css, this is a remnant of pre 2.7.0 stylesheet handling
107     if (-d "$css_path/$user_style" &&
108         -f "$css_path/$user_style/main.css") {
109       $css_path = "$css_path/$user_style";
110     } else {
111       $css_path = "$css_path/kivitendo";
112     }
113   } else {
114     $css_path = "$css_path/kivitendo";
115   }
116
117   return $css_path;
118 }
119
120 sub add_javascripts {
121   &use_javascript
122 }
123
124 sub use_javascript {
125   my $self = shift;
126   push @{ $self->{javascripts} ||= [] }, @_ if @_;
127   @{ $self->{javascripts} ||= [] };
128 }
129
130 sub javascripts {
131   my ($self) = @_;
132
133   return uniq grep { $_ } map { $self->_find_javascript($_)  }
134     map({ $_->javascripts } $self->sub_layouts), $self->use_javascript;
135 }
136
137 sub _find_javascript {
138   my ($self, $javascript) = @_;
139
140   return "js/$javascript"        if -f "js/$javascript";
141   return $javascript             if -f $javascript;
142 }
143
144
145 ############################################
146 # track state of form header
147 ############################################
148
149 sub header_done {
150   $_[0]{_header_done} = 1;
151 }
152
153 sub need_footer {
154   $_[0]{_header_done};
155 }
156
157 sub presenter {
158   SL::Presenter->get;
159 }
160
161 1;
162
163 __END__
164
165 =encoding utf-8
166
167 =head1 NAME
168
169 SL::Layout::Base - Base class for layouts
170
171 =head1 SYNOPSIS
172
173   package SL::Layout::MyLayout;
174
175   use parent qw(SL::Layout::Base);
176
177 =head1 DESCRIPTION
178
179 For a description of the external interface of layouts in general see
180 L<SL::Layout::Dispatcher>.
181
182 This is a base class for layouts in general. It provides the basic interface
183 and some capabilities to extend and cascade layouts.
184
185
186 =head1 IMPLEMENTING LAYOUT CALLBACKS
187
188 There are eight callbacks (C<pre_content>, C<post_content>, C<start_content>,
189 C<end_content>, C<stylesheets>, C<stylesheets_inline>, C<javascripts>,
190 C<javascripts_inline>) which are documented in L<SL::Layout::Dispatcher>. If
191 you are writing a new simple layout, you can just override some of them like
192 this:
193
194   package SL::Layout::MyEvilLayout;
195
196   sub pre_content {
197     '<h1>This is MY page now</h1>'
198   }
199
200   sub post_content {
201     '<p align="right"><small><em>Brought to you by my own layout class</em></small></p>'
202   }
203
204
205 To preserve the sanitizing effects of C<stylesheets> and C<javascripts> you should instead do the following:
206
207   sub stylesheets {
208     $_[0]->add_stylesheets(qw(mystyle1.css mystyle2.css);
209     $_[0]->SUPER::stylesheets;
210   }
211
212 If you want to add something to a different layout, you should write a sub
213 layout and add it to the other layouts.
214
215
216 =head1 SUB LAYOUTS
217
218 Layouts can be aggregated, so that common elements can be used in different
219 layouts. Currently this is used for the L<None|SL::Layout::None> sub layout,
220 which contains a lot of the stylesheets and javascripts necessary. Another
221 example is the L<Top|SL::Layout::Top> layout, which is used to generate a
222 common top bar for all menu types.
223
224 To add a sub layout to your layout just overwrite the sub_layout method:
225
226   package SL::Layout::MyFinalLayout;
227
228   sub init_sub_layout {
229     [
230       SL::Layout::None->new,
231       SL::Layout::MyEvilLayout->new,
232     ]
233   }
234
235 You can also add a sublayout at runtime:
236
237   $layout->add_sub_layout(SL::Layout::SideBar->new);
238
239 The standard implementation for the callbacks will see to it that the contents
240 of all sub layouts will get rendered.
241
242
243 =head1 COMBINING SUB LAYOUTS AND OWN BEHAVIOUR
244
245 This is still somewhat rough, and improvements are welcome.
246
247 For the C<*_content> callbacks this works if you just remember to dispatch to the base method:
248
249   sub post_content {
250     return $_[0]->render_status_bar .
251     $_[0]->SUPER::post_content
252   }
253
254 For the stylesheet and javascript callbacks things are hard, because of the
255 backwards compatibility, and the built-in sanity checks. The best way currently
256 is to just add your content and dispatch to the base method.
257
258   sub stylesheets {
259     $_[0]->add_stylesheets(qw(mystyle1.css mystyle2.css);
260     $_[0]->SUPER::stylesheets;
261   }
262
263 =head1 GORY DETAILS ABOUT JAVASCRIPT AND STYLESHEET OVERLOADING
264
265 The original code used to store one stylesheet in C<< $form->{stylesheet} >> and
266 allowed/expected authors of potential C<bin/mozilla/> controllers to change
267 that into their own modified stylesheet.
268
269 This was at some point cleaned up into a method C<use stylesheet> which took a
270 string of space separated stylesheets and processed them into the response.
271
272 A lot of controllers are still using this method so the layout interface
273 supports it to change as little controller code as possible, while providing the
274 more intuitive C<add_stylesheets> method.
275
276 At the same time the following things need to be possible:
277
278 =over 4
279
280 =item 1.
281
282 Runtime additions.
283
284   $layout->add_stylesheets(...)
285
286 Since add_stylesheets adds to C<< $self->{stylesheets} >> there must be a way to read
287 from it. Currently this is the deprecated C<use_stylesheet>.
288
289 =item 2.
290
291 Overriding Callbacks
292
293 A leaf layout should be able to override a callback to return a list.
294
295 =item 3.
296
297 Sanitizing
298
299 C<stylesheets> needs to retain its sanitizing behaviour.
300
301 =item 4.
302
303 Aggregation
304
305 The standard implementation should be able to collect from sub layouts.
306
307 =item 5.
308
309 Preserving Inclusion Order
310
311 Since there is currently no standard way of mixing own content and including
312 sub layouts, this has to be done manually. Certain things like jquery get added
313 in L<SL::Layout::None> so that they get rendered first.
314
315 =back
316
317 The current implementation provides no good candidate for overriding in sub
318 classes, which should be changed. The other points work pretty well.
319
320 =head1 BUGS
321
322 None yet, if you don't count the horrible stylesheet/javascript interface.
323
324 =head1 AUTHOR
325
326 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>
327
328 =cut