Layout: Javascript Menü refactoring
authorSven Schöling <s.schoeling@googlemail.com>
Fri, 14 May 2021 11:47:46 +0000 (13:47 +0200)
committerSven Schöling <s.schoeling@googlemail.com>
Fri, 25 Jun 2021 13:51:32 +0000 (15:51 +0200)
Das Javascript Menü war noch in einem Zustand aus der Anfangszeit von
SL::Layout. Diese Änderungen teilen das in ein Main Layout (Javascript)
und ein sub layout für das DHTMLMenu.

Das DHTMLMenu Layout ist außerdem ein wenig optimiert. Es benutzt jetzt
nicht mehr ein template sondern baut das DOM direkt zusammen (spart im
hot path einen template aufruf und ist um faktor 5 schneller), und
ausserdem werden die -f checks auf die icon Dateien jetzt mit
SL::System::ResourceCache gecacht, so dass nicht für jeden Request ein
paar Duzend stat() gemacht werden müssen.

SL/Layout/DHTMLMenu.pm [new file with mode: 0644]
SL/Layout/Javascript.pm
SL/Presenter/JavascriptMenu.pm [new file with mode: 0644]
SL/System/ResourceCache.pm [new file with mode: 0644]
templates/webpages/menu/menunew.html [deleted file]

diff --git a/SL/Layout/DHTMLMenu.pm b/SL/Layout/DHTMLMenu.pm
new file mode 100644 (file)
index 0000000..43e83c1
--- /dev/null
@@ -0,0 +1,39 @@
+package SL::Layout::DHTMLMenu;
+
+use strict;
+use parent qw(SL::Layout::Base);
+
+use SL::Presenter::JavascriptMenu qw(render_menu);
+
+sub static_javascripts {
+  qw(dhtmlsuite/menu-for-applications.js),
+}
+
+sub javascripts_inline {
+<<'EOJS',
+  DHTMLSuite.createStandardObjects();
+  DHTMLSuite.configObj.setImagePath('image/dhtmlsuite/');
+  var menu_model = new DHTMLSuite.menuModel();
+  menu_model.addItemsFromMarkup('main_menu_model');
+  menu_model.init();
+  var menu_bar = new DHTMLSuite.menuBar();
+  menu_bar.addMenuItems(menu_model);
+  menu_bar.setTarget('main_menu_div');
+  menu_bar.init();
+EOJS
+}
+
+sub pre_content {
+  render_menu($_[0]->menu),
+}
+
+sub static_stylesheets {
+  qw(
+    dhtmlsuite/menu-item.css
+    dhtmlsuite/menu-bar.css
+    icons16.css
+    menu.css
+  );
+}
+
+1;
index 85454c4..de3c7f3 100644 (file)
@@ -4,6 +4,7 @@ use strict;
 use parent qw(SL::Layout::Base);
 
 use SL::Layout::None;
+use SL::Layout::DHTMLMenu;
 use SL::Layout::Top;
 use SL::Layout::ActionBar;
 use SL::Layout::Content;
@@ -17,64 +18,10 @@ sub init_sub_layouts {
   [
     SL::Layout::None->new,
     SL::Layout::Top->new,
+    SL::Layout::DHTMLMenu->new,
+    $_[0]->sub_layouts_by_name->{actionbar},
     SL::Layout::Content->new,
   ]
 }
 
-sub javascripts {
-  my ($self) = @_;
-
-  return uniq grep { $_ } map { $self->_find_javascript($_)  }
-    map({ $_->javascripts } $self->sub_layouts),
-    qw(dhtmlsuite/menu-for-applications.js),
-    $_[0]->sub_layouts_by_name->{actionbar}->javascripts,
-    $self->use_javascript;
-}
-
-sub javascripts_inline {
-  $_[0]->SUPER::javascripts_inline,
-<<'EOJS',
-  DHTMLSuite.createStandardObjects();
-  DHTMLSuite.configObj.setImagePath('image/dhtmlsuite/');
-  var menu_model = new DHTMLSuite.menuModel();
-  menu_model.addItemsFromMarkup('main_menu_model');
-  menu_model.init();
-  var menu_bar = new DHTMLSuite.menuBar();
-  menu_bar.addMenuItems(menu_model);
-  menu_bar.setTarget('main_menu_div');
-  menu_bar.init();
-EOJS
-  $_[0]->sub_layouts_by_name->{actionbar}->javascripts_inline,
-}
-
-sub pre_content {
-  $_[0]->SUPER::pre_content .
-  $_[0]->presenter->render("menu/menunew",
-    force_ul_width  => 1,
-    menu            => $_[0]->menu,
-    icon_path       => sub { my $simg = "image/icons/svg/$_[0].svg";  my $pimg="image/icons/16x16/$_[0].png"; -f $simg ? $simg : ( -f $pimg ? $pimg : ()) },
-    max_width       => sub { 10 * max map { length $::locale->text($_->{name}) } @{ $_[0]{children} || [] } },
-  ) .
-  ($_[0]->sub_layouts_by_name->{actionbar}->pre_content // '');
-}
-
-sub stylesheets {
-  my ($self) = @_;
-  my $css_path = $self->get_stylesheet_for_user;
-
-  return
-    uniq
-    grep { $_ }
-    map { $self->_find_stylesheet($_, $css_path)  }
-    qw(
-      dhtmlsuite/menu-item.css
-      dhtmlsuite/menu-bar.css
-      icons16.css
-      menu.css
-    ),
-    ( map { $_->stylesheets } $_[0]->sub_layouts ),
-    $_[0]->sub_layouts_by_name->{actionbar}->stylesheets,
-    $_[0]->use_stylesheet;
-}
-
 1;
diff --git a/SL/Presenter/JavascriptMenu.pm b/SL/Presenter/JavascriptMenu.pm
new file mode 100644 (file)
index 0000000..24999b2
--- /dev/null
@@ -0,0 +1,70 @@
+package SL::Presenter::JavascriptMenu;
+
+use strict;
+use SL::Presenter::EscapedText qw(escape is_escaped);
+use SL::Presenter::Tag qw( html_tag link_tag);
+use SL::Locale::String qw(t8);
+use SL::System::ResourceCache;
+
+use List::Util qw(max);
+
+use Exporter qw(import);
+our @EXPORT_OK = qw(render_menu);
+
+sub render_menu {
+  my ($menu) = @_;
+
+  html_tag('div', '', id => 'main_menu_div') .
+  html_tag('ul', render_children($menu, 100, $menu->{tree}),
+    id    => "main_menu_model",
+    style => 'display:none',
+  );
+}
+
+sub render_node {
+  my ($menu, $node, $id) = @_;
+  return '' if !$node->{visible};
+
+  my $icon = get_icon($node->{icon});
+  my $link = $menu->href_for_node($node) || '#';
+  my $name = $menu->name_for_node($node);
+
+  html_tag('li',
+      link_tag($link, $name, target => $node->{target})
+    . html_tag('ul', render_children($menu, $id * 100, $node->{children} // []),
+        width => max_width($node)
+      ),
+    id        => $id,
+    (itemIcon => $icon)x!!$icon,
+  )
+}
+
+sub render_children {
+  my ($menu, $id, $children) = @_;
+  my $sub_id = 1;
+
+  join '', map {
+    render_node($menu, $_, 100 * $id + $sub_id++)
+  } @$children
+}
+
+sub max_width {
+  11 * ( max( map { length $::locale->text($_->{name}) } @{ $_[0]{children} || [] } ) // 1 )
+}
+
+sub get_icon {
+  my $name = $_[0];
+
+  return undef if !defined $name;
+
+  my $simg = "image/icons/svg/$name.svg";
+  my $pimg = "image/icons/16x16/$name.png";
+
+    SL::System::ResourceCache->get($simg) ? $simg
+  : SL::System::ResourceCache->get($pimg) ? $pimg
+  :                                         ();
+}
+
+1;
+
+
diff --git a/SL/System/ResourceCache.pm b/SL/System/ResourceCache.pm
new file mode 100644 (file)
index 0000000..be7a1d4
--- /dev/null
@@ -0,0 +1,79 @@
+package SL::System::ResourceCache;
+
+use strict;
+use File::stat;
+use File::Find;
+
+our @paths = qw(image css);
+our $cache;
+
+sub generate_data {
+  return if $cache;
+
+  $cache = {};
+
+  File::Find::find(sub {
+    $cache->{ $File::Find::name =~ s{^\./}{}r } = stat($_);
+  }, @paths);
+}
+
+sub get {
+  my ($class, $file) = @_;
+  no warnings 'once';
+
+  return stat($file) if ($::dispatcher // { interface => 'cgi' })->{interface} eq 'cgi';
+
+  $class->generate_data;
+  $cache->{$file};
+}
+
+1;
+
+
+__END__
+
+=encoding utf-8
+
+=head1 NAME
+
+SL::System::ResourceCache - provides access to resource files without having to access the filesystem all the time
+
+=head1 SYNOPSIS
+
+  use SL::System::ResourceCache;
+
+  SL::System::ResourceCache->get($filename);
+
+=head1 DESCRIPTION
+
+This will stat() all files in the configured paths at startup once, so that
+subsequent calls can use the cached values. Particularly useful for icons in
+the menu, which would otherwise generate a few hundred file sytem accesses per
+request.
+
+The caching will not happen in CGI and script environments.
+
+=head1 FUNCTIONS
+
+=over 4
+
+=item * C<get FILENAME>
+
+If the file exists, returns a L<File::stat> object. If it doesn't exists, returns undef.
+
+=back
+
+=head1 BUGS
+
+None yet :)
+
+=head1 TODO
+
+Make into instance cache and keep it as system wide object
+
+=head1 AUTHOR
+
+Sven Schöling E<lt>sven.schoeling@googlemail.comE<gt>
+
+=cut
+
diff --git a/templates/webpages/menu/menunew.html b/templates/webpages/menu/menunew.html
deleted file mode 100644 (file)
index df3ed7b..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-[%- USE T8 %]
-[%- USE L %]
-[%- USE HTML %]
-[%- USE LxERP -%]
- <div id="main_menu_div"></div>
- [%- SET main_id = '100' %]
- <ul id="main_menu_model"  style='display:none'>
- [%- FOREACH node = menu.tree %]
-  [%- NEXT UNLESS node.visible %]
-  [%- SET main_id = main_id + 1 %]
-  <li id="[% main_id %]"[% IF icon_path(node.icon) %] itemIcon="[% icon_path(node.icon) %]"[% END %]>
-   [% L.link(menu.href_for_node(node) || '#', menu.name_for_node(node), target=node.target) %]
-   [%- IF node.children %]
-    <ul width="[% max_width(node) %]">
-     [%- SET sub1_id = main_id * 100 %]
-     [%- FOREACH sub1node = node.children %]
-      [%- NEXT UNLESS sub1node.visible %]
-      [%- SET sub1_id = sub1_id + 1 %]
-      <li id="[% sub1_id %]"[% IF icon_path(sub1node.icon) %] itemIcon="[% icon_path(sub1node.icon) %]"[% END %]>
-       [% L.link(menu.href_for_node(sub1node) || '#', menu.name_for_node(sub1node), target=sub1node.target) %]
-       [%- IF sub1node.children %]
-        <ul width="[% max_width(sub1node) %]">
-         [%- SET sub2_id = sub1_id * 100 %]
-         [%- FOREACH sub2node = sub1node.children %]
-          [%- NEXT UNLESS sub2node.visible %]
-          [%- SET sub2_id = sub2_id + 1 %]
-          <li id="[% sub2_id %]"[% IF icon_path(sub2node.icon) %] itemIcon="[% icon_path(sub2node.icon) %]"[% END %]>
-            [% L.link(menu.href_for_node(sub2node) || '#', menu.name_for_node(sub2node), target=sub2node.target) %]
-          </li>
-         [%- END %]
-        </ul>
-       [%- END %]
-      </li>
-     [%- END %]
-    </ul>
-   [%- END %]
-  </li>
- [%- END %]
- </ul>