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 {
 
  47   return grep { $_ } ($_[0]->sub_layouts_by_name->{$_[1]});
 
  50 sub init_auto_reload_resources_param {
 
  51   if ($::lx_office_conf{debug}->{auto_reload_resources}) {
 
  52     return sprintf('?rand=%d-%d-%d', Time::HiRes::gettimeofday(), int(rand 1000000000000));
 
  55   if ($::lx_office_conf{debug}{git_commit_reload_resources}) {
 
  56     my $git_dir = SL::System::Process::exe_dir() . '/.git';
 
  58     return '' unless -d $git_dir;
 
  60     my $content = eval { scalar(read_file($git_dir . '/HEAD')) };
 
  62     return '' unless ($content // '') =~ m{\Aref: ([^\r\n]+)};
 
  64     $content = eval { scalar(read_file($git_dir . '/' . $1)) };
 
  66     return '' unless ($content // '') =~ m{\A([0-9a-fA-F]+)};
 
  74 ##########################################
 
  75 #  inheritable/overridable
 
  76 ##########################################
 
  79   join '', map { $_->pre_content } $_[0]->sub_layouts;
 
  83   join '', map { $_->start_content } $_[0]->sub_layouts;
 
  87   join '', map { $_->end_content } $_[0]->sub_layouts;
 
  91   join '', map { $_->post_content } $_[0]->sub_layouts;
 
  94 sub stylesheets_inline {
 
  95   uniq ( map { $_->stylesheets_inline } $_[0]->sub_layouts ),
 
  96   @{ $_[0]->{stylesheets_inline} || [] };
 
  99 sub javascripts_inline {
 
 100   uniq ( map { $_->javascripts_inline } $_[0]->sub_layouts ),
 
 101   @{ $_[0]->{javascripts_inline} || [] };
 
 104 sub init_sub_layouts { [] }
 
 106 sub init_sub_layouts_by_name { +{} }
 
 109 #########################################
 
 111 ########################################
 
 113 # override in sub layouts
 
 114 sub static_stylesheets {}
 
 116 sub add_stylesheets {
 
 122   push @{ $self->{stylesheets} ||= [] }, @_ if @_;
 
 123     (map { $_->use_stylesheet } $self->sub_layouts), $self->static_stylesheets, @{ $self->{stylesheets} ||= [] };
 
 128   my $css_path = $self->get_stylesheet_for_user;
 
 130   return uniq grep { $_ } map { $self->_find_stylesheet($_, $css_path)  }
 
 131     $self->use_stylesheet;
 
 134 sub _find_stylesheet {
 
 135   my ($self, $stylesheet, $css_path) = @_;
 
 137   return "$css_path/$stylesheet" if -f "$css_path/$stylesheet";
 
 138   return "css/$stylesheet"       if -f "css/$stylesheet";
 
 139   return $stylesheet             if -f $stylesheet;
 
 140   return $stylesheet             if $stylesheet =~ /^http/; # external
 
 143 sub get_stylesheet_for_user {
 
 144   my $css_path = 'css';
 
 145   if (my $user_style = $::myconfig{stylesheet}) {
 
 146     $user_style =~ s/\.css$//; # nuke trailing .css, this is a remnant of pre 2.7.0 stylesheet handling
 
 147     if (-d "$css_path/$user_style" &&
 
 148         -f "$css_path/$user_style/main.css") {
 
 149       $css_path = "$css_path/$user_style";
 
 151       $css_path = "$css_path/kivitendo";
 
 154     $css_path = "$css_path/kivitendo";
 
 160 #########################################
 
 162 ########################################
 
 164 # override in sub layouts
 
 165 sub static_javascripts {}
 
 167 sub add_javascripts {
 
 173   push @{ $self->{javascripts} ||= [] }, @_ if @_;
 
 174   map({ $_->use_javascript } $self->sub_layouts), $self->static_javascripts, @{ $self->{javascripts} ||= [] };
 
 180   return uniq grep { $_ } map { $self->_find_javascript($_)  }
 
 181      $self->use_javascript;
 
 184 sub _find_javascript {
 
 185   my ($self, $javascript) = @_;
 
 187   return "js/$javascript"        if -f "js/$javascript";
 
 188   return $javascript             if -f $javascript;
 
 189   return $javascript             if $javascript =~ /^http/;
 
 193 ############################################
 
 194 # track state of form header
 
 195 ############################################
 
 198   $_[0]{_header_done} = 1;
 
 217 SL::Layout::Base - Base class for layouts
 
 221   package SL::Layout::MyLayout;
 
 223   use parent qw(SL::Layout::Base);
 
 227 For a description of the external interface of layouts in general see
 
 228 L<SL::Layout::Dispatcher>.
 
 230 This is a base class for layouts in general. It provides the basic interface
 
 231 and some capabilities to extend and cascade layouts.
 
 234 =head1 IMPLEMENTING LAYOUT CALLBACKS
 
 236 There are eight callbacks (C<pre_content>, C<post_content>, C<start_content>,
 
 237 C<end_content>, C<stylesheets>, C<stylesheets_inline>, C<javascripts>,
 
 238 C<javascripts_inline>) which are documented in L<SL::Layout::Dispatcher>. If
 
 239 you are writing a new simple layout, you can just override some of them like
 
 242   package SL::Layout::MyEvilLayout;
 
 245     '<h1>This is MY page now</h1>'
 
 249     '<p align="right"><small><em>Brought to you by my own layout class</em></small></p>'
 
 253 To preserve the sanitizing effects of C<stylesheets> and C<javascripts> you should instead do the following:
 
 256     $_[0]->add_stylesheets(qw(mystyle1.css mystyle2.css);
 
 257     $_[0]->SUPER::stylesheets;
 
 260 If you want to add something to a different layout, you should write a sub
 
 261 layout and add it to the other layouts.
 
 266 Layouts can be aggregated, so that common elements can be used in different
 
 267 layouts. Currently this is used for the L<None|SL::Layout::None> sub layout,
 
 268 which contains a lot of the stylesheets and javascripts necessary. Another
 
 269 example is the L<Top|SL::Layout::Top> layout, which is used to generate a
 
 270 common top bar for all menu types.
 
 272 To add a sub layout to your layout just overwrite the sub_layout method:
 
 274   package SL::Layout::MyFinalLayout;
 
 276   sub init_sub_layout {
 
 278       SL::Layout::None->new,
 
 279       SL::Layout::MyEvilLayout->new,
 
 283 You can also add a sublayout at runtime:
 
 285   $layout->add_sub_layout(SL::Layout::SideBar->new);
 
 287 The standard implementation for the callbacks will see to it that the contents
 
 288 of all sub layouts will get rendered.
 
 291 =head1 COMBINING SUB LAYOUTS AND OWN BEHAVIOUR
 
 293 This is still somewhat rough, and improvements are welcome.
 
 295 For the C<*_content> callbacks this works if you just remember to dispatch to the base method:
 
 298     return $_[0]->render_status_bar .
 
 299     $_[0]->SUPER::post_content
 
 303 Stylesheets and Javascripts can be added to every layout and sub-layout at
 
 304 runtime with L<SL::Layout::Dispatcher/add_stylesheets> and
 
 305 L<SL::Layout::Dispatcher/add_javascripts> (C<use_stylesheets> and
 
 306 C<use_javascripts> are aliases for backwards compatibility):
 
 308   $layout->add_stylesheets("custom.css");
 
 309   $layout->add_javascripts("app.js", "widget.js");
 
 311 Or they can be overwritten in sub layouts with the calls
 
 312 L<SL::Layout::Displatcher/static_stylesheets> and
 
 313 L<SL::Layout::Dispatcher/static_javascripts>:
 
 315   sub static_stylesheets {
 
 319   sub static_javascripts {
 
 320     qw(app.css widget.js)
 
 323 Note how these are relative to the base dirs of the currently selected
 
 324 stylesheets. Javascripts are resolved relative to the C<js/> basedir.
 
 326 Setting directly with C<stylesheets> and C<javascripts> is eprecated.
 
 329 =head1 GORY DETAILS ABOUT JAVASCRIPT AND STYLESHEET OVERLOADING
 
 331 The original code used to store one stylesheet in C<< $form->{stylesheet} >> and
 
 332 allowed/expected authors of potential C<bin/mozilla/> controllers to change
 
 333 that into their own modified stylesheet.
 
 335 This was at some point cleaned up into a method C<use stylesheet> which took a
 
 336 string of space separated stylesheets and processed them into the response.
 
 338 A lot of controllers are still using this method so the layout interface
 
 339 supports it to change as little controller code as possible, while providing the
 
 340 more intuitive C<add_stylesheets> method.
 
 342 At the same time the following things need to be possible:
 
 350   $layout->add_stylesheets(...)
 
 352 Since add_stylesheets adds to C<< $self->{stylesheets} >> there must be a way to read
 
 353 from it. Currently this is the deprecated C<use_stylesheet>.
 
 359 A leaf layout should be able to override a callback to return a list.
 
 365 C<stylesheets> needs to retain its sanitizing behaviour.
 
 371 The standard implementation should be able to collect from sub layouts.
 
 375 Preserving Inclusion Order
 
 377 Since there is currently no standard way of mixing own content and including
 
 378 sub layouts, this has to be done manually. Certain things like jquery get added
 
 379 in L<SL::Layout::None> so that they get rendered first.
 
 383 The current implementation provides no good candidate for overriding in sub
 
 384 classes, which should be changed. The other points work pretty well.
 
 388 * stylesheet/javascript interface is a horrible mess.
 
 390 * It's currently not possible to do compositor layouts without assupmtions
 
 391 about the position of the content. That's because the content will return
 
 392 control to the actual controller, so the layouts need to know where to split
 
 393 pre- and post-content.
 
 397 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>