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