From 88951879460802675ec26082ca0d73379be23772 Mon Sep 17 00:00:00 2001
From: gloas <gloas@afi-sa.fr>
Date: Wed, 15 Sep 2021 13:41:38 +0200
Subject: [PATCH] hotline MT #134907 fix list with tools

---
 VERSIONS_HOTLINE/134907                       |   1 +
 .../Assets/js/truncate_list_tools.js          |  51 ++++++++
 .../templates/Intonation/Library/Settings.php |   8 ++
 .../View/RenderAjaxPaginatedList.php          |  16 +--
 .../Intonation/View/RenderTruncateList.php    | 121 ++++++++----------
 .../View/RenderTruncateList/Tools.php         |  44 +++++++
 .../Polygone/Assets/js/scroll_search.js       |   3 +
 public/opac/java/search_input/search_input.js |   3 +
 .../TemplatesAbonneSelectionsTest.php         |   6 +
 .../Templates/TemplatesAbonneTest.php         |  52 ++++++--
 .../Templates/TemplatesWidgetTest.php         |   7 +
 11 files changed, 226 insertions(+), 86 deletions(-)
 create mode 100644 VERSIONS_HOTLINE/134907
 create mode 100644 library/templates/Intonation/Assets/js/truncate_list_tools.js
 create mode 100644 library/templates/Intonation/View/RenderTruncateList/Tools.php

diff --git a/VERSIONS_HOTLINE/134907 b/VERSIONS_HOTLINE/134907
new file mode 100644
index 00000000000..c3ac6076cbc
--- /dev/null
+++ b/VERSIONS_HOTLINE/134907
@@ -0,0 +1 @@
+ - ticket #134907 : Magasin de thèmes : Correction du composant liste avec interaction. Le mot élément est accordé en fonction du nombre d'éléments. La barre d'outils en bas de la liste est maintenant fonctionnelle.
\ No newline at end of file
diff --git a/library/templates/Intonation/Assets/js/truncate_list_tools.js b/library/templates/Intonation/Assets/js/truncate_list_tools.js
new file mode 100644
index 00000000000..4ea25b177f9
--- /dev/null
+++ b/library/templates/Intonation/Assets/js/truncate_list_tools.js
@@ -0,0 +1,51 @@
+(function ( $ ) {
+  $.fn.truncate_list_tools = function (params) {
+    var widget = $(this);
+    var container = widget.find('.truncate_list_wrapper');
+
+    var page_size = params['page_size'];
+    container.children().slice(0, page_size).show();
+
+    widget.find('select.select_size_tool')
+      .change(function()
+	      {
+		container.children().hide();
+		var size = $(this).val();
+		widget.find('select.select_size_tool').not(this).val(size);
+		container.children().not('.search_input_not_found').slice(0, size).show();
+	      });
+
+    widget.find('input.input_search_tool')
+      .removeAttr('onkeypress')
+      .keypress(function(event)
+		{
+		  return event.which != 13;
+		})
+      .keyup(function()
+	     {
+	       widget.find('select.select_size_tool').val(10000);
+
+	       var html_size = widget.find('.truncate_list_size').first().html();
+
+	       var single = params['single'] + '$';
+	       var single_reg_exp = new RegExp(single, 'gi');
+	       var html_size_plural = html_size.replace(single_reg_exp, params['plural']);
+
+	       var plural = params['plural'] + '$';
+	       var plural_reg_exp = new RegExp(plural, 'gi');
+	       var html_size_single = html_size.replace(plural_reg_exp, params['single']);
+
+	       var list_size = container.children(':visible').length;
+
+	       widget.find('.truncate_list_size').html(1 >= list_size ? html_size_single : html_size_plural);
+	       widget.find('.truncate_list_size_number').text(list_size);
+
+	       var searchText = $(this).val();
+
+	       if (!searchText || searchText == '' || searchText == '*') 
+		 widget.find('select.select_size_tool').val(params['page_size']).change();
+
+	       widget.find('input.input_search_tool').not(this).val(searchText);
+	     });
+  };
+} (jQuery));
diff --git a/library/templates/Intonation/Library/Settings.php b/library/templates/Intonation/Library/Settings.php
index e9e4ee3d2b3..a24a301b81c 100644
--- a/library/templates/Intonation/Library/Settings.php
+++ b/library/templates/Intonation/Library/Settings.php
@@ -312,6 +312,14 @@ class Intonation_Library_Settings extends Intonation_System_Abstract {
                                                   'a class account-disconnect' => 'btn btn-sm btn-secondary',
                                                   'span class user_notifications' => 'btn btn-sm list-group-item-danger',
                                                   'div class babeltheque_reviews' => 'mt-3 mb-3',
+                                                  'span class truncate_list_size' => 'btn btn-sm btn-info',
+                                                  'div class truncate_list_size_wrapper' => 'col-12 col-sm-3',
+                                                  'div class truncate_list_form_wrapper' => 'col-12 col-sm-9',
+                                                  'div class truncate_list_container' => 'container my-2',
+                                                  'div class truncate_list_row' => 'justify-content-start',
+                                                  'div class truncate_list_wrapper' => 'col-12 list-group bg-transparent no_border',
+                                                  'div class truncate_list_top_tools' => 'col-12',
+                                                  'div class truncate_list_bottom_tools' => 'col-12',
                           ],
 
                           'icons_map_doc_types' => [],
diff --git a/library/templates/Intonation/View/RenderAjaxPaginatedList.php b/library/templates/Intonation/View/RenderAjaxPaginatedList.php
index a4aaef34739..8e86884d5ce 100644
--- a/library/templates/Intonation/View/RenderAjaxPaginatedList.php
+++ b/library/templates/Intonation/View/RenderAjaxPaginatedList.php
@@ -161,17 +161,9 @@ class Intonation_View_RenderAjaxPaginatedList extends ZendAfi_View_Helper_BaseHe
                    'submit_' . $this->_id,
                    ['class' => 'd-none']);
 
-    $html = [$this->_tag('div',
-                         $this->_tag('span' ,
-                                     $this->_('%s éléments', $this->_tag('span',
-                                                                         $helper->getSize())),
-                                     ['class' => 'btn btn-sm btn-info']),
-                         ['class' => 'col-12 col-sm-3']),
-
-             $this->_tag('div',
-                         $this->view->renderInlineForm($form),
-                         ['class' => 'ajax_paginated_form col-12 col-sm-9'])];
-
-    return $this->view->grid($html, ['class' => 'container my-2'], ['class' => 'justify-content-start']);
+    return
+      $this->view->RenderTruncateList_Tools($form,
+                                            $helper->getSize(),
+                                            'ajax_paginated_form');
   }
 }
diff --git a/library/templates/Intonation/View/RenderTruncateList.php b/library/templates/Intonation/View/RenderTruncateList.php
index bda4d5e242f..68a195ae590 100644
--- a/library/templates/Intonation/View/RenderTruncateList.php
+++ b/library/templates/Intonation/View/RenderTruncateList.php
@@ -27,64 +27,64 @@ class Intonation_View_RenderTruncateList extends ZendAfi_View_Helper_BaseHelper
     $_page_size = 3,
     $_ajax_size = 20,
     $_container_id,
-    $_input_id,
+    $_widget_id,
+    $_top_tools_id,
+    $_bottom_tools_id,
     $_carousel_context;
 
 
   public function renderTruncateList($collection, $callback, $page_size = 3) {
-    $this->_container_id = $id = uniqid();
-    $this->_input_id = 'input_' . $this->_container_id;
-
     if ($collection->isEmpty())
       return '';
 
-    $this->_page_size = $page_size;
-
     $size = $collection->count();
 
     if ($this->_ajax_size < $size)
       return $this->_ajaxifyList($collection);
 
-    if ($this->_page_size >= $size && $this->_page_size <= $this->_min_size_for_tools)
-      return $this->view->renderList($collection, $callback);
+    $this->_page_size = $page_size;
 
-    $html = $collection
-      ->injectInto('', function($html, $element) use ($callback)
-    {
-      return $html . $this->_tag('div',
-                                 $callback($element->inJsSearch()),
-                                 ['style' => 'display: none',
-                                  'class' => 'list-group-item bg-transparent px-0']);
-    });
-
-    $tools = $this->_renderTools($size);
-
-    return
-      $tools
-      . $this->_tag('div',
-                    $html,
-                    ['id' => $this->_container_id,
-                     'class' => 'list-group bg-transparent no_border'])
-      . $tools;
+    return ($this->_page_size >= $size && $this->_page_size <= $this->_min_size_for_tools)
+      ? $this->view->renderList($collection, $callback)
+      : $this->_truncateListWithTools($collection, $callback, $size);
   }
 
 
-  protected function _renderTools($size) {
-    $size_id = uniqid();
-    $id = uniqid();
+  protected function _truncateListWithTools($collection, $callback, $size) {
+    $this->_container_id = uniqid();
+    $this->_widget_id = 'truncate_list_widget_id_' . $this->_container_id;
+    $this->_top_tools_id = 'top_tools_' . $this->_container_id;
+    $this->_bottom_tools_id = 'bottom_tools_' . $this->_container_id;
+
+    $html = $collection
+      ->injectInto('', function($html, $element) use ($callback)
+                   {
+                     return $html . $this->_tag('div',
+                                                $callback($element->inJsSearch()),
+                                                ['style' => 'display: none',
+                                                 'class' => 'list-group-item bg-transparent px-0']);
+                   });
 
     $this->renderHeadScriptsOn(Class_ScriptLoader::getInstance());
 
-    $onchange =
-      "var container = $('#" . $this->_container_id . "');"
-      . "container.children().hide();"
-      . "var value=$('#" . $id . "').val();"
-      . "container.children().not('.search_input_not_found').slice(0, value).show();";
+    $top_tools = $this->_div(['class' => 'truncate_list_top_tools'],
+                             $this->_renderTools($size, $this->_top_tools_id));
 
-    $input_keyup =
-      "$('#" . $id . "').val(10000).change();"
-      . "setTimeout(function() { $('#" . $size_id . "').text($('#" . $this->_container_id . "').children(':visible').length); var searchText = $('#" . $this->_input_id . "').val(); if (!searchText || searchText == '' || searchText == '*') $('#" . $id . "').val(" . $this->_page_size . ").change();}, 10);";
+    $list_html = $this->_tag('div',
+                             $html,
+                             ['id' => $this->_container_id,
+                              'class' => 'truncate_list_wrapper']);
 
+    $bottom_tools = $this->_div(['class' => 'truncate_list_bottom_tools'],
+                                $this->_renderTools($size, $this->_bottom_tools_id));
+
+    return $this->_div(['class' => 'truncate_list_widget',
+                        'id' => $this->_widget_id],
+                       $this->view->grid($top_tools . $list_html . $bottom_tools));
+  }
+
+
+  protected function _renderTools($size, $tools_id) {
     $multi_options = $this->_getTruncateOptions($size);
 
     $form = new ZendAfi_Form;
@@ -92,57 +92,46 @@ class Intonation_View_RenderTruncateList extends ZendAfi_View_Helper_BaseHelper
     $form
       ->setAction('GET')
       ->addElement('select',
-                   $id,
+                   'select_' . $tools_id,
                    ['label' => $this->_('Afficher'),
                     'title' => $this->_('Limiter le nombre d\'éléments à afficher'),
-                    'onchange' => $onchange,
-                    'multiOptions' => $multi_options])
+                    'multiOptions' => $multi_options,
+                    'class' => 'select_size_tool'])
 
       ->addElement('text',
-                   $this->_input_id,
+                   'input_' . $tools_id,
                    ['label' => $this->_('Filtrer'),
                     'placeholder' => $this->_('Filtrer avec un nom, un mot, une date'),
                     'title' => $this->_('Filtrer la liste avec des noms, des mots, des dates'),
-                    'onkeyup' => $input_keyup])
+                    'class' => 'input_search_tool'])
 
       ->addElement('submit',
-                   md5($id),
+                   'submit_' . $tools_id,
                    ['class' => 'd-none']);
 
-    return $this->view->grid($this->_tag('div',
-                                         $this->_tag('span' ,
-                                                     $this->_tag('span',
-                                                                 $size,
-                                                                 ['id' => $size_id])
-                                                     . $this->_(' éléments'),
-                                                     ['class' => 'btn btn-sm btn-info']),
-                                         ['class' => 'col-12 col-sm-3'])
-
-                             . $this->_tag('div',
-                                           $this->view->renderInlineForm($form),
-                                           ['class' => 'col-12 col-sm-9']),
-                             ['class' => 'container my-2'],
-                             ['class' => 'justify-content-start']);
+    return $this->view->RenderTruncateList_Tools($form, $size, 'paginated_form');
   }
 
 
   protected function _getTruncateOptions($size) {
-      return [$this->_page_size => $this->_page_size,
-              $this->_page_size * 2 => $this->_page_size * 2,
-              $this->_page_size * 5 => $this->_page_size * 5,
-              '10000' => $this->_('Tout')];
+    return [$this->_page_size => $this->_page_size,
+            $this->_page_size * 2 => $this->_page_size * 2,
+            $this->_page_size * 5 => $this->_page_size * 5,
+            '10000' => $this->_('Tout')];
   }
 
 
   public function renderHeadScriptsOn($script_loader) {
     $script_loader
       ->addSearchInputToContainer('#' . $this->_container_id,
-                                  '#' . $this->_input_id,
+                                  '#input_' . $this->_top_tools_id . ', #input_' . $this->_bottom_tools_id,
                                   '#' . $this->_container_id . ' div.card *, .dropdown, .dropdown-menu')
-
-      ->addJQueryReady("var container = $('#" . $this->_container_id . "');"
-                       . "setTimeout(function() {container.children().slice(0, " . $this->_page_size . ").show();"
-                       . "$('#" . $this->_input_id . "').attr('onkeypress', 'return event.keyCode != 13;');}, 10);");
+      ->addScripts([Class_Url::absolute('/library/templates/Intonation/Assets/js/truncate_list_tools.js')])
+      ->addJQueryReady(sprintf('$("#%s").truncate_list_tools({page_size: "%s", single: "%s", plural: "%s"});',
+                               $this->_widget_id,
+                               $this->_page_size,
+                               $this->_('élément'),
+                               $this->_('éléments')));
 
     return $this;
   }
diff --git a/library/templates/Intonation/View/RenderTruncateList/Tools.php b/library/templates/Intonation/View/RenderTruncateList/Tools.php
new file mode 100644
index 00000000000..37596b95e04
--- /dev/null
+++ b/library/templates/Intonation/View/RenderTruncateList/Tools.php
@@ -0,0 +1,44 @@
+<?php
+/**
+ * Copyright (c) 2012-2021, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class Intonation_View_RenderTruncateList_Tools extends ZendAfi_View_Helper_BaseHelper {
+  public function RenderTruncateList_Tools($form, $size, $form_class) {
+    $size_html = $this->_div(['class' => 'truncate_list_size_wrapper'],
+                             $this->_tag('span' ,
+                                         $this->_plural($size,
+                                                        'Pas d\'élément',
+                                                        '%s élément',
+                                                        '%s éléments',
+                                                        $this->_tag('span',
+                                                                    $size,
+                                                                    ['class' => 'truncate_list_size_number'])),
+                                         ['class' => 'truncate_list_size']));
+
+    $form_html = $this->_div(['class' => 'truncate_list_form_wrapper ' . $form_class],
+                             $this->view->renderInlineForm($form));
+
+    return
+      $this->view->grid( $size_html . $form_html,
+                        ['class' => 'truncate_list_container'],
+                        ['class' => 'justify-content-start']);
+  }
+}
diff --git a/library/templates/Polygone/Assets/js/scroll_search.js b/library/templates/Polygone/Assets/js/scroll_search.js
index 648afb2dc90..5ad5ef6943d 100644
--- a/library/templates/Polygone/Assets/js/scroll_search.js
+++ b/library/templates/Polygone/Assets/js/scroll_search.js
@@ -23,6 +23,9 @@
     var search_widget = $(this);
 
     var visible = function(element) {
+      if ( ! element)
+	return;
+      
       var rect = element.getBoundingClientRect(),
     	  top = rect.top,
     	  height = rect.height;
diff --git a/public/opac/java/search_input/search_input.js b/public/opac/java/search_input/search_input.js
index 9dc4cf2ddd8..c9fcc3407d2 100644
--- a/public/opac/java/search_input/search_input.js
+++ b/public/opac/java/search_input/search_input.js
@@ -110,6 +110,9 @@
     var checkRegex = function (reg_exps, value) {
       var check = true;
 
+      if ( ! reg_exps)
+	return check;
+
       reg_exps.forEach(function (regexp) {
         check &= regexp.test(accentsTidy(value));
       });
diff --git a/tests/scenarios/Templates/TemplatesAbonneSelectionsTest.php b/tests/scenarios/Templates/TemplatesAbonneSelectionsTest.php
index 5e702dbf00b..981b59de19a 100644
--- a/tests/scenarios/Templates/TemplatesAbonneSelectionsTest.php
+++ b/tests/scenarios/Templates/TemplatesAbonneSelectionsTest.php
@@ -181,6 +181,12 @@ class TemplatesAbonneSelectionsAsAbonneTest extends TemplatesAbonneSelectionsTes
   public function linkToFollowASearchShouldBePresent() {
     $this->assertXPathContentContains('//div[@class = "collection_action alone_in_the_list btn btn-sm btn-success col mr-3 col-3"]//a', 'Suivre');
   }
+
+
+  /** @test */
+  public function scriptTruncateListToolsShouldBeLoaded() {
+    $this->assertXPath('//script[contains(@src, "/library/templates/Intonation/Assets/js/truncate_list_tools.js")]');
+  }
 }
 
 
diff --git a/tests/scenarios/Templates/TemplatesAbonneTest.php b/tests/scenarios/Templates/TemplatesAbonneTest.php
index ad0acdd389c..49db660f4f8 100644
--- a/tests/scenarios/Templates/TemplatesAbonneTest.php
+++ b/tests/scenarios/Templates/TemplatesAbonneTest.php
@@ -397,6 +397,36 @@ class TemplatesDispatchAbonneLoansPaginatedTest extends TemplatesIntonationAccou
     $this->assertXPathContentContains('//div//a[contains(@href, "/bib-numerique/return-pnb-loan/id/38bcd646a69f4da7fce8921d4cf762ad/page/1/id_profil/72/search/house/size/10/loan_id/666_3")]', 'Retour anticip');
     $this->assertXPathContentContains('//div//a[contains(@href, "/bib-numerique/extends-pnb-loan/id/38bcd646a69f4da7fce8921d4cf762ad/page/1/id_profil/72/search/house/size/10/loan_id/666_3")]', 'Prolonger');
   }
+
+
+  /** @test */
+  public function loansListShouldCountShouldBeNoElement() {
+    Storm_Cache::beVolatile();
+
+    $cards = new Class_User_Cards(Class_Users::getIdentity());
+
+    $loans = $cards->getPNBLoans([]);
+
+    $loans = array_map(function($loan)
+                       {
+                         return (new Intonation_Library_View_Wrapper_PNBLoan)
+                           ->setModel($loan)
+                           ->setView($this->view);
+                       }, $loans->getArrayCopy());
+
+    $collection = new Storm_Collection($loans);
+
+    $helper = (new Intonation_Library_AjaxPaginatedListHelper)
+      ->setCollection($collection)
+      ->setRendering('cardifyHorizontal');
+
+
+    $id = $helper->getId();
+
+    $this->dispatch('/opac/index/ajax-paginated-list/id/' . $id . '/page/1/id_profil/72/search/no+data/size/1');
+    $this->assertXPathContentContains('//div[@class="truncate_list_size_wrapper col-12 col-sm-3"]//span[@class="truncate_list_size btn btn-sm btn-info"]',
+                                      utf8_encode('Pas d\'élément'));
+  }
 }
 
 
@@ -678,12 +708,6 @@ class TemplatesDispatchAbonneLargeNumberOfHoldsTest extends TemplatesIntonationA
   }
 
 
-  /** @test */
-  public function selectOnChangeShouldBePresent() {
-    $this->assertXPath('//select[contains(@onchange,"var container = $(")][contains(@onchange, "container.children().hide();var value=$(")][contains(@onchange, "val();container.children().not(\'.search_input_not_found\').slice(0, value).show()")]');
-  }
-
-
   /** @test */
   public function holdsShouldBeDisplaydInListGroup() {
     $this->assertXPath('//div[contains(@class, "list-group")]//div[contains(@class, "list-group-item")]//div[contains(@class, "card")]');
@@ -697,8 +721,20 @@ class TemplatesDispatchAbonneLargeNumberOfHoldsTest extends TemplatesIntonationA
 
 
   /** @test */
-  public function scriptShouldContainsChildrenSliceShow() {
-    $this->assertXPathContentContains('//script', 'setTimeout(function() {container.children().slice(0, 20).show();');
+  public function truncateListSizeShouldBe20Elements() {
+    $this->assertXPathContentContains('//div[@class="truncate_list_size_wrapper col-12 col-sm-3"]//span[@class="truncate_list_size btn btn-sm btn-info"]', '20 éléments');
+  }
+
+
+  /** @test */
+  public function scriptTruncateListToolsShouldBeLoaded() {
+    $this->assertXPath('//script[contains(@src, "/library/templates/Intonation/Assets/js/truncate_list_tools.js")]');
+  }
+
+
+  /** @test */
+  public function pageShouldBeHTML5Valid() {
+    $this->assertHTML5();
   }
 }
 
diff --git a/tests/scenarios/Templates/TemplatesWidgetTest.php b/tests/scenarios/Templates/TemplatesWidgetTest.php
index 6e1c8964cc7..8adc46b325f 100644
--- a/tests/scenarios/Templates/TemplatesWidgetTest.php
+++ b/tests/scenarios/Templates/TemplatesWidgetTest.php
@@ -1547,6 +1547,13 @@ class TemplatesWidgetWithPaginatedListTest extends AbstractControllerTestCase {
   public function nextButtonShouldNotHaveDataDisabled() {
     $this->assertXPath('//a[contains(@class, "btn btn-sm btn-secondary ajax_anchor next_ajax_anchor")][not(@data-disabled)]');
   }
+
+
+  /** @test */
+  public function truncateListSizeShouldBe10Elements() {
+    $this->assertXPathContentContains('//div[@class="truncate_list_size_wrapper col-12 col-sm-3"]//span[@class="truncate_list_size btn btn-sm btn-info"]',
+                                      utf8_encode('10 éléments'));
+  }
 }
 
 
-- 
GitLab