Lange überfällige Doku zu Layouts
[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   Menu->new('menu.ini');
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 remnand 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   $::myconfig{css_path} = $css_path; # needed for menunew, FIXME: don't do this here
117
118   return $css_path;
119 }
120
121 sub add_javascripts {
122   &use_javascript
123 }
124
125 sub use_javascript {
126   my $self = shift;
127   push @{ $self->{javascripts} ||= [] }, @_ if @_;
128   @{ $self->{javascripts} ||= [] };
129 }
130
131 sub javascripts {
132   my ($self) = @_;
133
134   return uniq grep { $_ } map { $self->_find_javascript($_)  }
135     map({ $_->javascripts } $self->sub_layouts), $self->use_javascript;
136 }
137
138 sub _find_javascript {
139   my ($self, $javascript) = @_;
140
141   return "js/$javascript"        if -f "js/$javascript";
142   return $javascript             if -f $javascript;
143 }
144
145
146 ############################################
147 # track state of form header
148 ############################################
149
150 sub header_done {
151   $_[0]{_header_done} = 1;
152 }
153
154 sub need_footer {
155   $_[0]{_header_done};
156 }
157
158 sub presenter {
159   SL::Presenter->get;
160 }
161
162 1;
163
164 __END__
165
166 =encoding utf-8
167
168 =head1 NAME
169
170 SL::Layout::Base - Base class for layouts
171
172 =head1 SYNOPSIS
173
174   package SL::Layout::MyLayout;
175
176   use parent qw(SL::Layout::Base);
177
178 =head1 DESCRIPTION
179
180 For a description about the external interface of layouts in general see
181 L<SL::Layout::Dispatcher>.
182
183 This is a base class for layouts in general. It provides the basic interface
184 and some capabilities to extend and cascade layouts.
185
186
187 =head1 IMPLEMENTING LAYOUT CALLBACKS
188
189 There are eight callbacks (C<pre_content>, C<post_content>, C<start_content>,
190 C<end_content>, C<stylesheets>, C<stylesheets_inline>, C<javscripts>,
191 C<javascripts_inline>) which are documented in L<SL::Layout::Dispatcher>. If
192 you are writing a new simple layout, you can just override some of them like
193 this:
194
195   package SL::Layout::MyEvilLayout;
196
197   sub pre_content {
198     '<h1>This is MY page now</h1>'
199   }
200
201   sub post_content {
202     '<p align="right"><small><em>Brought to you by my own layout class</em></small></p>'
203   }
204
205
206 To preserve the sanitizing effects of C<stylesheets> and C<javascripts> you should instead do the following:
207
208   sub stylesheets {
209     $_[0]->add_stylesheets(qw(mystyle1.css mystyle2.css);
210     $_[0]->SUPER::stylesheets;
211   }
212
213 If you want to add something to a different layout, you should write a sub
214 layout and add it to the other layouts.
215
216
217 =head1 SUB LAYOUTS
218
219 Layouts can be aggregated, so that common elements can be used in different
220 layouts. Currently this is used for the L<None|SL::Layout::None> sub layout,
221 which contains a lot of the stylesheets and javascripts necessary. Another
222 example is the L<Top|SL::Layout::Top> layout, which is used to generate a
223 common top bar for all menu types.
224
225 To add a sub layout to your layout just overwrite the sub_layout method:
226
227   package SL::Layout::MyFinalLayout;
228
229   sub init_sub_layout {
230     [
231       SL::Layout::None->new,
232       SL::Layout::MyEvilLayout->new,
233     ]
234   }
235
236 You can also add a sublayout at runtime:
237
238   $layout->add_sub_layout(SL::Layout::SideBar->new);
239
240 The standard implementation for the callbacks will see to it that the contents
241 of all sub layouts will get rendered.
242
243
244 =head1 COMBINING SUB LAYOUTS AND OWN BEHAVIOUR
245
246 This is still somewhat rough, and improvements are welcome.
247
248 For the C<*_content> callbacks this works if you just remember to dispatch to the base method:
249
250   sub post_content {
251     return $_[0]->render_status_bar .
252     $_[0]->SUPER::post_content
253   }
254
255 For the stylesheet and javascript callbacks things are hard, because of the
256 backwards compatibility, and the built-in sanity checks. The best way currently
257 is to just add your content and dispatch to the base method.
258
259   sub stylesheets {
260     $_[0]->add_stylesheets(qw(mystyle1.css mystyle2.css);
261     $_[0]->SUPER::stylesheets;
262   }
263
264 =head1 GORY DETAILS ABOUT JAVASCRIPT AND STYLESHEET OVERLOADING
265
266 The original code used to store one stylehsheet in C<< $form->{stylesheet} >> and
267 allowed/expected authors of potential C<bin/mozilla/> controllers to change
268 that into their own modified stylesheet.
269
270 This was at some point cleaned up into a method C<use stylesheet> which took a
271 string of space separated stylesheets and processed them into the response.
272
273 A lot of controllers are still using this methods so the layout interface
274 supports it to change as few controller code as possible, while providing the
275 more intuitive C<add_stylesheets> method.
276
277 At the same time the following things need to be possible:
278
279 =over 4
280
281 =item 1.
282
283 Runtime additions.
284
285   $layout->add_stylesheets(...)
286
287 Since add_stylesheets adds to C<< $self->{stylesheets} >> there must be a way to read
288 from it. Currently this is the deprecated C<use_stylesheet>.
289
290 =item 2.
291
292 Overriding Callbacks
293
294 A leaf layout should be able to override a callback to return a list.
295
296 =item 3.
297
298 Sanitizing
299
300 C<stylesheets> needs to retain it's sanitizing behaviour.
301
302 =item 4.
303
304 Aggregation
305
306 The standard implementation should be able to collect from sub layouts.
307
308 =item 5.
309
310 Preserving of Inclusion Order
311
312 Since there is currently no standard way of mixing own content and including
313 sub layouts, this has to be done manually. Certain things like jquery get added
314 in L<SL::Layout::None> so that they get rendered first.
315
316 =back
317
318 The current implementation provides no good candidate for overriding in sub
319 classes, which should be changed. The other points work pretty well.
320
321 =head1 BUGS
322
323 None yet, if you don't count the horrible stylesheet/javascript interface.
324
325 =head1 AUTHOR
326
327 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>
328
329 =cut
330