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