1 package SL::Layout::Base;
4 use parent qw(Rose::Object);
8 use File::Slurp qw(read_file);
9 use List::MoreUtils qw(uniq);
12 use Rose::Object::MakeMethods::Generic (
13 'scalar --get_set_init' => [ qw(menu auto_reload_resources_param sub_layouts_by_name) ],
14 'scalar' => qw(focus),
16 'add_stylesheets_inline' => { interface => 'add', hash_key => 'stylesheets_inline' },
17 'add_javascripts_inline' => { interface => 'add', hash_key => 'javascripts_inline' },
18 'sub_layouts', => { interface => 'get_set_init' },
19 'add_sub_layouts' => { interface => 'add', hash_key => 'sub_layouts' },
25 use SL::System::Process;
30 my ($class, @slurp) = @_;
32 my $self = $class->SUPER::new(@slurp);
36 SL::Menu->new('user');
39 sub init_sublayouts_by_name {
47 sub webpages_fallback_path {
54 sub allow_stylesheet_fallback {
60 return grep { $_ } ($_[0]->sub_layouts_by_name->{$_[1]});
63 sub _current_git_ref {
64 my $git_dir = SL::System::Process::exe_dir() . '/.git';
66 return unless -d $git_dir;
68 my $content = eval { scalar(read_file($git_dir . '/HEAD')) };
70 return unless ($content // '') =~ m{\Aref: ([^\r\n]+)};
72 $content = eval { scalar(read_file($git_dir . '/' . $1)) };
74 return unless ($content // '') =~ m{\A([0-9a-fA-F]+)};
79 sub init_auto_reload_resources_param {
82 $value = sprintf('%d-%d-%d', Time::HiRes::gettimeofday(), int(rand 1000000000000)) if $::lx_office_conf{debug}->{auto_reload_resources};
83 $value ||= _current_git_ref();
84 $value ||= SL::Version->get_version;
86 return $value ? "?rand=${value}" : '';
89 ##########################################
90 # inheritable/overridable
91 ##########################################
94 join '', map { $_->pre_content } $_[0]->sub_layouts;
98 join '', map { $_->start_content } $_[0]->sub_layouts;
102 join '', map { $_->end_content } $_[0]->sub_layouts;
106 join '', map { $_->post_content } $_[0]->sub_layouts;
109 sub stylesheets_inline {
110 uniq ( map { $_->stylesheets_inline } $_[0]->sub_layouts ),
111 @{ $_[0]->{stylesheets_inline} || [] };
114 sub javascripts_inline {
115 uniq ( map { $_->javascripts_inline } $_[0]->sub_layouts ),
116 @{ $_[0]->{javascripts_inline} || [] };
119 sub init_sub_layouts { [] }
121 sub init_sub_layouts_by_name { +{} }
124 #########################################
126 ########################################
128 # override in sub layouts
129 sub static_stylesheets {}
131 sub add_stylesheets {
137 push @{ $self->{stylesheets} ||= [] }, @_ if @_;
138 (map { $_->use_stylesheet } $self->sub_layouts), $self->static_stylesheets, @{ $self->{stylesheets} ||= [] };
143 my $css_path = $self->get_stylesheet_for_user;
145 return uniq grep { $_ } map { $self->_find_stylesheet($_, $css_path) }
146 $self->use_stylesheet;
149 sub _find_stylesheet {
150 my ($self, $stylesheet, $css_path) = @_;
152 return "$css_path/$stylesheet" if -f "$css_path/$stylesheet";
153 return "css/$stylesheet" if -f "css/$stylesheet" && $self->allow_stylesheet_fallback;
154 return $stylesheet if -f $stylesheet;
155 return $stylesheet if $stylesheet =~ /^http/; # external
158 sub get_stylesheet_for_user {
159 my $css_path = 'css';
160 if (my $user_style = $::myconfig{stylesheet}) {
161 $user_style =~ s/\.css$//; # nuke trailing .css, this is a remnant of pre 2.7.0 stylesheet handling
162 if (-d "$css_path/$user_style" &&
163 -f "$css_path/$user_style/main.css") {
164 $css_path = "$css_path/$user_style";
166 $css_path = "$css_path/kivitendo";
169 $css_path = "$css_path/kivitendo";
175 #########################################
177 ########################################
179 # override in sub layouts
180 sub static_javascripts {}
182 sub add_javascripts {
188 push @{ $self->{javascripts} ||= [] }, @_ if @_;
189 map({ $_->use_javascript } $self->sub_layouts), $self->static_javascripts, @{ $self->{javascripts} ||= [] };
195 return uniq grep { $_ } map { $self->_find_javascript($_) }
196 $self->use_javascript;
199 sub _find_javascript {
200 my ($self, $javascript) = @_;
202 return "js/$javascript" if -f "js/$javascript";
203 return $javascript if -f $javascript;
204 return $javascript if $javascript =~ /^http/;
208 ############################################
209 # track state of form header
210 ############################################
213 $_[0]{_header_done} = 1;
232 SL::Layout::Base - Base class for layouts
236 package SL::Layout::MyLayout;
238 use parent qw(SL::Layout::Base);
242 For a description of the external interface of layouts in general see
243 L<SL::Layout::Dispatcher>.
245 This is a base class for layouts in general. It provides the basic interface
246 and some capabilities to extend and cascade layouts.
249 =head1 IMPLEMENTING LAYOUT CALLBACKS
251 There are eight callbacks (C<pre_content>, C<post_content>, C<start_content>,
252 C<end_content>, C<stylesheets>, C<stylesheets_inline>, C<javascripts>,
253 C<javascripts_inline>) which are documented in L<SL::Layout::Dispatcher>. If
254 you are writing a new simple layout, you can just override some of them like
257 package SL::Layout::MyEvilLayout;
260 '<h1>This is MY page now</h1>'
264 '<p align="right"><small><em>Brought to you by my own layout class</em></small></p>'
268 To preserve the sanitizing effects of C<stylesheets> and C<javascripts> you should instead do the following:
271 $_[0]->add_stylesheets(qw(mystyle1.css mystyle2.css);
272 $_[0]->SUPER::stylesheets;
275 If you want to add something to a different layout, you should write a sub
276 layout and add it to the other layouts.
281 Layouts can be aggregated, so that common elements can be used in different
282 layouts. Currently this is used for the L<None|SL::Layout::None> sub layout,
283 which contains a lot of the stylesheets and javascripts necessary. Another
284 example is the L<Top|SL::Layout::Top> layout, which is used to generate a
285 common top bar for all menu types.
287 To add a sub layout to your layout just overwrite the sub_layout method:
289 package SL::Layout::MyFinalLayout;
291 sub init_sub_layout {
293 SL::Layout::None->new,
294 SL::Layout::MyEvilLayout->new,
298 You can also add a sublayout at runtime:
300 $layout->add_sub_layout(SL::Layout::SideBar->new);
302 The standard implementation for the callbacks will see to it that the contents
303 of all sub layouts will get rendered.
306 =head1 COMBINING SUB LAYOUTS AND OWN BEHAVIOUR
308 This is still somewhat rough, and improvements are welcome.
310 For the C<*_content> callbacks this works if you just remember to dispatch to the base method:
313 return $_[0]->render_status_bar .
314 $_[0]->SUPER::post_content
318 Stylesheets and Javascripts can be added to every layout and sub-layout at
319 runtime with L<SL::Layout::Dispatcher/add_stylesheets> and
320 L<SL::Layout::Dispatcher/add_javascripts> (C<use_stylesheets> and
321 C<use_javascripts> are aliases for backwards compatibility):
323 $layout->add_stylesheets("custom.css");
324 $layout->add_javascripts("app.js", "widget.js");
326 Or they can be overwritten in sub layouts with the calls
327 L<SL::Layout::Displatcher/static_stylesheets> and
328 L<SL::Layout::Dispatcher/static_javascripts>:
330 sub static_stylesheets {
334 sub static_javascripts {
335 qw(app.css widget.js)
338 Note how these are relative to the base dirs of the currently selected
339 stylesheets. Javascripts are resolved relative to the C<js/> basedir.
341 Setting directly with C<stylesheets> and C<javascripts> is eprecated.
344 =head1 BEHAVIOUR SWITCHES FOR SUB LAYOUTS
346 Certain methods have been added to adjust behaviour in sub layouts. Most of these are single case uses.
350 =item * sublayouts_by_name
352 Contains a map that holds named sublayouts. If a sublayout needs to targeted
353 directly, the compositing layout needs to add it here. Initially introduced for
356 =item * webpages_path
358 Overrides the default webpages path "templates/webpages". Used for mobile and design40 styles.
360 Note that this does not have fallback behaviour by default. It is intended for
361 stylesheets where the templates are so incompatible that a complete fork of the
362 templates dir is sensible.
364 =item * webpages_fallback_path
366 Allows partial template sets to fallback to other paths in case a template
367 wasn't found. Intended to be used in conjunction with L</webpages_path>.
369 Note: in case a template can't be found at all, generic/error.html will be
370 rendered, and the fallback doesn't work in this case.
373 =item * allow_stylesheet_fallback
375 Defaults to true. The default behaviour is that stylesheets not found in the
376 stylesheet path of the user will fallback to files found in css/. This is
377 usually desirable for shared stuff like common.css, which contains behaviour
378 styling. If a stylesheet comes from a separate generator, this can be used to
379 turn falllback off. Files can still be included with the complete path though.
380 A request for "common.css" would not find "css/common.css", but a request for
381 "css/common.css" would be found.
383 Also see the next section L</GORY DETAILS ABOUT JAVASCRIPT AND STYLESHEET OVERLOADING>
387 Default 'transitional'. Controls the html dialect that the header will
388 generate. Used in combination with template overriding for html5.
390 See also L<SL::Form/header>
396 =head1 GORY DETAILS ABOUT JAVASCRIPT AND STYLESHEET OVERLOADING
398 The original code used to store one stylesheet in C<< $form->{stylesheet} >> and
399 allowed/expected authors of potential C<bin/mozilla/> controllers to change
400 that into their own modified stylesheet.
402 This was at some point cleaned up into a method C<use stylesheet> which took a
403 string of space separated stylesheets and processed them into the response.
405 A lot of controllers are still using this method so the layout interface
406 supports it to change as little controller code as possible, while providing the
407 more intuitive C<add_stylesheets> method.
409 At the same time the following things need to be possible:
417 $layout->add_stylesheets(...)
419 Since add_stylesheets adds to C<< $self->{stylesheets} >> there must be a way to read
420 from it. Currently this is the deprecated C<use_stylesheet>.
426 A leaf layout should be able to override a callback to return a list.
432 C<stylesheets> needs to retain its sanitizing behaviour.
438 The standard implementation should be able to collect from sub layouts.
442 Preserving Inclusion Order
444 Since there is currently no standard way of mixing own content and including
445 sub layouts, this has to be done manually. Certain things like jquery get added
446 in L<SL::Layout::None> so that they get rendered first.
450 The current implementation provides no good candidate for overriding in sub
451 classes, which should be changed. The other points work pretty well.
455 * stylesheet/javascript interface is a horrible mess.
457 * It's currently not possible to do compositor layouts without assupmtions
458 about the position of the content. That's because the content will return
459 control to the actual controller, so the layouts need to know where to split
460 pre- and post-content.
464 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>