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   return sprintf('?rand=%d-%d-%d', Time::HiRes::gettimeofday(), int(rand 1000000000000)) if $::lx_office_conf{debug}->{auto_reload_resources};
 
  49   my $git_dir = SL::System::Process::exe_dir() . '/.git';
 
  51   return '' unless -d $git_dir;
 
  53   my $content = eval { scalar(read_file($git_dir . '/HEAD')) };
 
  55   return '' unless ($content // '') =~ m{\Aref: ([^\r\n]+)};
 
  57   $content = eval { scalar(read_file($git_dir . '/' . $1)) };
 
  59   return '' unless ($content // '') =~ m{\A([0-9a-fA-F]+)};
 
  64 ##########################################
 
  65 #  inheritable/overridable
 
  66 ##########################################
 
  69   join '', map { $_->pre_content } $_[0]->sub_layouts;
 
  73   join '', map { $_->start_content } $_[0]->sub_layouts;
 
  77   join '', map { $_->end_content } $_[0]->sub_layouts;
 
  81   join '', map { $_->post_content } $_[0]->sub_layouts;
 
  84 sub stylesheets_inline {
 
  85   uniq ( map { $_->stylesheets_inline } $_[0]->sub_layouts ),
 
  86   @{ $_[0]->{stylesheets_inline} || [] };
 
  89 sub javascripts_inline {
 
  90   uniq ( map { $_->javascripts_inline } $_[0]->sub_layouts ),
 
  91   @{ $_[0]->{javascripts_inline} || [] };
 
  94 sub init_sub_layouts { [] }
 
  96 sub init_sub_layouts_by_name { +{} }
 
  99 #########################################
 
 101 ########################################
 
 103 sub add_stylesheets {
 
 109   push @{ $self->{stylesheets} ||= [] }, @_ if @_;
 
 110   @{ $self->{stylesheets} ||= [] };
 
 115   my $css_path = $self->get_stylesheet_for_user;
 
 117   return uniq grep { $_ } map { $self->_find_stylesheet($_, $css_path)  }
 
 118     $self->use_stylesheet, map { $_->stylesheets } $self->sub_layouts;
 
 121 sub _find_stylesheet {
 
 122   my ($self, $stylesheet, $css_path) = @_;
 
 124   return "$css_path/$stylesheet" if -f "$css_path/$stylesheet";
 
 125   return "css/$stylesheet"       if -f "css/$stylesheet";
 
 126   return $stylesheet             if -f $stylesheet;
 
 129 sub get_stylesheet_for_user {
 
 130   my $css_path = 'css';
 
 131   if (my $user_style = $::myconfig{stylesheet}) {
 
 132     $user_style =~ s/\.css$//; # nuke trailing .css, this is a remnant of pre 2.7.0 stylesheet handling
 
 133     if (-d "$css_path/$user_style" &&
 
 134         -f "$css_path/$user_style/main.css") {
 
 135       $css_path = "$css_path/$user_style";
 
 137       $css_path = "$css_path/kivitendo";
 
 140     $css_path = "$css_path/kivitendo";
 
 146 sub add_javascripts {
 
 152   push @{ $self->{javascripts} ||= [] }, @_ if @_;
 
 153   @{ $self->{javascripts} ||= [] };
 
 159   return uniq grep { $_ } map { $self->_find_javascript($_)  }
 
 160     map({ $_->javascripts } $self->sub_layouts), $self->use_javascript;
 
 163 sub _find_javascript {
 
 164   my ($self, $javascript) = @_;
 
 166   return "js/$javascript"        if -f "js/$javascript";
 
 167   return $javascript             if -f $javascript;
 
 171 ############################################
 
 172 # track state of form header
 
 173 ############################################
 
 176   $_[0]{_header_done} = 1;
 
 195 SL::Layout::Base - Base class for layouts
 
 199   package SL::Layout::MyLayout;
 
 201   use parent qw(SL::Layout::Base);
 
 205 For a description of the external interface of layouts in general see
 
 206 L<SL::Layout::Dispatcher>.
 
 208 This is a base class for layouts in general. It provides the basic interface
 
 209 and some capabilities to extend and cascade layouts.
 
 212 =head1 IMPLEMENTING LAYOUT CALLBACKS
 
 214 There are eight callbacks (C<pre_content>, C<post_content>, C<start_content>,
 
 215 C<end_content>, C<stylesheets>, C<stylesheets_inline>, C<javascripts>,
 
 216 C<javascripts_inline>) which are documented in L<SL::Layout::Dispatcher>. If
 
 217 you are writing a new simple layout, you can just override some of them like
 
 220   package SL::Layout::MyEvilLayout;
 
 223     '<h1>This is MY page now</h1>'
 
 227     '<p align="right"><small><em>Brought to you by my own layout class</em></small></p>'
 
 231 To preserve the sanitizing effects of C<stylesheets> and C<javascripts> you should instead do the following:
 
 234     $_[0]->add_stylesheets(qw(mystyle1.css mystyle2.css);
 
 235     $_[0]->SUPER::stylesheets;
 
 238 If you want to add something to a different layout, you should write a sub
 
 239 layout and add it to the other layouts.
 
 244 Layouts can be aggregated, so that common elements can be used in different
 
 245 layouts. Currently this is used for the L<None|SL::Layout::None> sub layout,
 
 246 which contains a lot of the stylesheets and javascripts necessary. Another
 
 247 example is the L<Top|SL::Layout::Top> layout, which is used to generate a
 
 248 common top bar for all menu types.
 
 250 To add a sub layout to your layout just overwrite the sub_layout method:
 
 252   package SL::Layout::MyFinalLayout;
 
 254   sub init_sub_layout {
 
 256       SL::Layout::None->new,
 
 257       SL::Layout::MyEvilLayout->new,
 
 261 You can also add a sublayout at runtime:
 
 263   $layout->add_sub_layout(SL::Layout::SideBar->new);
 
 265 The standard implementation for the callbacks will see to it that the contents
 
 266 of all sub layouts will get rendered.
 
 269 =head1 COMBINING SUB LAYOUTS AND OWN BEHAVIOUR
 
 271 This is still somewhat rough, and improvements are welcome.
 
 273 For the C<*_content> callbacks this works if you just remember to dispatch to the base method:
 
 276     return $_[0]->render_status_bar .
 
 277     $_[0]->SUPER::post_content
 
 280 For the stylesheet and javascript callbacks things are hard, because of the
 
 281 backwards compatibility, and the built-in sanity checks. The best way currently
 
 282 is to just add your content and dispatch to the base method.
 
 285     $_[0]->add_stylesheets(qw(mystyle1.css mystyle2.css);
 
 286     $_[0]->SUPER::stylesheets;
 
 289 =head1 GORY DETAILS ABOUT JAVASCRIPT AND STYLESHEET OVERLOADING
 
 291 The original code used to store one stylesheet in C<< $form->{stylesheet} >> and
 
 292 allowed/expected authors of potential C<bin/mozilla/> controllers to change
 
 293 that into their own modified stylesheet.
 
 295 This was at some point cleaned up into a method C<use stylesheet> which took a
 
 296 string of space separated stylesheets and processed them into the response.
 
 298 A lot of controllers are still using this method so the layout interface
 
 299 supports it to change as little controller code as possible, while providing the
 
 300 more intuitive C<add_stylesheets> method.
 
 302 At the same time the following things need to be possible:
 
 310   $layout->add_stylesheets(...)
 
 312 Since add_stylesheets adds to C<< $self->{stylesheets} >> there must be a way to read
 
 313 from it. Currently this is the deprecated C<use_stylesheet>.
 
 319 A leaf layout should be able to override a callback to return a list.
 
 325 C<stylesheets> needs to retain its sanitizing behaviour.
 
 331 The standard implementation should be able to collect from sub layouts.
 
 335 Preserving Inclusion Order
 
 337 Since there is currently no standard way of mixing own content and including
 
 338 sub layouts, this has to be done manually. Certain things like jquery get added
 
 339 in L<SL::Layout::None> so that they get rendered first.
 
 343 The current implementation provides no good candidate for overriding in sub
 
 344 classes, which should be changed. The other points work pretty well.
 
 348 * stylesheet/javascript interface is a horrible mess.
 
 350 * It's currently not possible to do compositor layouts without assupmtions
 
 351 about the position of the content. That's because the content will return
 
 352 control to the actual controller, so the layouts need to know where to split
 
 353 pre- and post-content.
 
 357 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>