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