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