1 package SL::Layout::Base;
4 use parent qw(Rose::Object);
6 use File::Slurp qw(read_file);
7 use List::MoreUtils qw(uniq);
10 use Rose::Object::MakeMethods::Generic (
11 'scalar --get_set_init' => [ qw(menu auto_reload_resources_param sub_layouts_by_name) ],
12 'scalar' => qw(focus),
14 'add_stylesheets_inline' => { interface => 'add', hash_key => 'stylesheets_inline' },
15 'add_javascripts_inline' => { interface => 'add', hash_key => 'javascripts_inline' },
16 'sub_layouts', => { interface => 'get_set_init' },
17 'add_sub_layouts' => { interface => 'add', hash_key => 'sub_layouts' },
23 use SL::System::Process;
28 my ($class, @slurp) = @_;
30 my $self = $class->SUPER::new(@slurp);
34 SL::Menu->new('user');
37 sub init_sublayouts_by_name {
43 return grep { $_ } ($_[0]->sub_layouts_by_name->{$_[1]});
46 sub init_auto_reload_resources_param {
47 if ($::lx_office_conf{debug}->{auto_reload_resources}) {
48 return sprintf('?rand=%d-%d-%d', Time::HiRes::gettimeofday(), int(rand 1000000000000));
51 if ($::lx_office_conf{debug}{git_commit_reload_resources}) {
52 my $git_dir = SL::System::Process::exe_dir() . '/.git';
54 return '' unless -d $git_dir;
56 my $content = eval { scalar(read_file($git_dir . '/HEAD')) };
58 return '' unless ($content // '') =~ m{\Aref: ([^\r\n]+)};
60 $content = eval { scalar(read_file($git_dir . '/' . $1)) };
62 return '' unless ($content // '') =~ m{\A([0-9a-fA-F]+)};
70 ##########################################
71 # inheritable/overridable
72 ##########################################
75 join '', map { $_->pre_content } $_[0]->sub_layouts;
79 join '', map { $_->start_content } $_[0]->sub_layouts;
83 join '', map { $_->end_content } $_[0]->sub_layouts;
87 join '', map { $_->post_content } $_[0]->sub_layouts;
90 sub stylesheets_inline {
91 uniq ( map { $_->stylesheets_inline } $_[0]->sub_layouts ),
92 @{ $_[0]->{stylesheets_inline} || [] };
95 sub javascripts_inline {
96 uniq ( map { $_->javascripts_inline } $_[0]->sub_layouts ),
97 @{ $_[0]->{javascripts_inline} || [] };
100 sub init_sub_layouts { [] }
102 sub init_sub_layouts_by_name { +{} }
105 #########################################
107 ########################################
109 sub add_stylesheets {
115 push @{ $self->{stylesheets} ||= [] }, @_ if @_;
116 @{ $self->{stylesheets} ||= [] };
121 my $css_path = $self->get_stylesheet_for_user;
123 return uniq grep { $_ } map { $self->_find_stylesheet($_, $css_path) }
124 $self->use_stylesheet, map { $_->stylesheets } $self->sub_layouts;
127 sub _find_stylesheet {
128 my ($self, $stylesheet, $css_path) = @_;
130 return "$css_path/$stylesheet" if -f "$css_path/$stylesheet";
131 return "css/$stylesheet" if -f "css/$stylesheet";
132 return $stylesheet if -f $stylesheet;
133 return $stylesheet if $stylesheet =~ /^http/; # external
136 sub get_stylesheet_for_user {
137 my $css_path = 'css';
138 if (my $user_style = $::myconfig{stylesheet}) {
139 $user_style =~ s/\.css$//; # nuke trailing .css, this is a remnant of pre 2.7.0 stylesheet handling
140 if (-d "$css_path/$user_style" &&
141 -f "$css_path/$user_style/main.css") {
142 $css_path = "$css_path/$user_style";
144 $css_path = "$css_path/kivitendo";
147 $css_path = "$css_path/kivitendo";
153 sub add_javascripts {
159 push @{ $self->{javascripts} ||= [] }, @_ if @_;
160 @{ $self->{javascripts} ||= [] };
166 return uniq grep { $_ } map { $self->_find_javascript($_) }
167 map({ $_->javascripts } $self->sub_layouts), $self->use_javascript;
170 sub _find_javascript {
171 my ($self, $javascript) = @_;
173 return "js/$javascript" if -f "js/$javascript";
174 return $javascript if -f $javascript;
175 return $javascript if $javascript =~ /^http/;
179 ############################################
180 # track state of form header
181 ############################################
184 $_[0]{_header_done} = 1;
203 SL::Layout::Base - Base class for layouts
207 package SL::Layout::MyLayout;
209 use parent qw(SL::Layout::Base);
213 For a description of the external interface of layouts in general see
214 L<SL::Layout::Dispatcher>.
216 This is a base class for layouts in general. It provides the basic interface
217 and some capabilities to extend and cascade layouts.
220 =head1 IMPLEMENTING LAYOUT CALLBACKS
222 There are eight callbacks (C<pre_content>, C<post_content>, C<start_content>,
223 C<end_content>, C<stylesheets>, C<stylesheets_inline>, C<javascripts>,
224 C<javascripts_inline>) which are documented in L<SL::Layout::Dispatcher>. If
225 you are writing a new simple layout, you can just override some of them like
228 package SL::Layout::MyEvilLayout;
231 '<h1>This is MY page now</h1>'
235 '<p align="right"><small><em>Brought to you by my own layout class</em></small></p>'
239 To preserve the sanitizing effects of C<stylesheets> and C<javascripts> you should instead do the following:
242 $_[0]->add_stylesheets(qw(mystyle1.css mystyle2.css);
243 $_[0]->SUPER::stylesheets;
246 If you want to add something to a different layout, you should write a sub
247 layout and add it to the other layouts.
252 Layouts can be aggregated, so that common elements can be used in different
253 layouts. Currently this is used for the L<None|SL::Layout::None> sub layout,
254 which contains a lot of the stylesheets and javascripts necessary. Another
255 example is the L<Top|SL::Layout::Top> layout, which is used to generate a
256 common top bar for all menu types.
258 To add a sub layout to your layout just overwrite the sub_layout method:
260 package SL::Layout::MyFinalLayout;
262 sub init_sub_layout {
264 SL::Layout::None->new,
265 SL::Layout::MyEvilLayout->new,
269 You can also add a sublayout at runtime:
271 $layout->add_sub_layout(SL::Layout::SideBar->new);
273 The standard implementation for the callbacks will see to it that the contents
274 of all sub layouts will get rendered.
277 =head1 COMBINING SUB LAYOUTS AND OWN BEHAVIOUR
279 This is still somewhat rough, and improvements are welcome.
281 For the C<*_content> callbacks this works if you just remember to dispatch to the base method:
284 return $_[0]->render_status_bar .
285 $_[0]->SUPER::post_content
288 For the stylesheet and javascript callbacks things are hard, because of the
289 backwards compatibility, and the built-in sanity checks. The best way currently
290 is to just add your content and dispatch to the base method.
293 $_[0]->add_stylesheets(qw(mystyle1.css mystyle2.css);
294 $_[0]->SUPER::stylesheets;
297 =head1 GORY DETAILS ABOUT JAVASCRIPT AND STYLESHEET OVERLOADING
299 The original code used to store one stylesheet in C<< $form->{stylesheet} >> and
300 allowed/expected authors of potential C<bin/mozilla/> controllers to change
301 that into their own modified stylesheet.
303 This was at some point cleaned up into a method C<use stylesheet> which took a
304 string of space separated stylesheets and processed them into the response.
306 A lot of controllers are still using this method so the layout interface
307 supports it to change as little controller code as possible, while providing the
308 more intuitive C<add_stylesheets> method.
310 At the same time the following things need to be possible:
318 $layout->add_stylesheets(...)
320 Since add_stylesheets adds to C<< $self->{stylesheets} >> there must be a way to read
321 from it. Currently this is the deprecated C<use_stylesheet>.
327 A leaf layout should be able to override a callback to return a list.
333 C<stylesheets> needs to retain its sanitizing behaviour.
339 The standard implementation should be able to collect from sub layouts.
343 Preserving Inclusion Order
345 Since there is currently no standard way of mixing own content and including
346 sub layouts, this has to be done manually. Certain things like jquery get added
347 in L<SL::Layout::None> so that they get rendered first.
351 The current implementation provides no good candidate for overriding in sub
352 classes, which should be changed. The other points work pretty well.
356 * stylesheet/javascript interface is a horrible mess.
358 * It's currently not possible to do compositor layouts without assupmtions
359 about the position of the content. That's because the content will return
360 control to the actual controller, so the layouts need to know where to split
361 pre- and post-content.
365 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>