commit 5dea830a42262859daed3b692969e6e702fa7e3d
Author: Jan Šedivý <honza.seeda@gmail.com>
Date:   Wed Apr 24 19:16:19 2019 +0200

    Re #7328 výsledky vyhledávání přesunuty do komponenty, stránkování výsledků

diff --git a/app/FrontModule/component/Transliteration/TransliterationSearchResultList.latte b/app/FrontModule/component/Transliteration/TransliterationSearchResultList.latte
new file mode 100644
index 0000000..2fc0b5d
--- /dev/null
+++ b/app/FrontModule/component/Transliteration/TransliterationSearchResultList.latte
@@ -0,0 +1,61 @@
+{snippet resultList}
+    <div class="row h-100 pagination-header">
+        <div class="col my-auto">
+            {if $paginator->getPageCount() > 1}
+            <nav aria-label="Page navigation example">
+                <ul class="pagination">
+                    <li class="page-item">
+                        <a class="page-link" href="{link changePage!, $paginator->first()->getPageNumber(), $paginator->first()->getPageSize()}">
+                            <span aria-hidden="true">&laquo;</span>
+                            <span class="sr-only">First</span>
+                        </a>
+                    </li>
+                    <li class="page-item">
+                        <a class="page-link" href="{link changePage!, $paginator->previous()->getPageNumber(), $paginator->previous()->getPageSize()}">
+                            <span aria-hidden="true">&lsaquo;</span>
+                            <span class="sr-only">Previous</span>
+                        </a>
+                    </li>
+                    {for $i = $paginator->getStartingPage(); $i <= $paginator->getEndingPage(); $i++}
+                        <li class="page-item{if $paginator->getPageNumber() == $i} active{/if}">
+                            <a class="page-link" href="{link changePage!, $i, $paginator->getPageSize()}">
+                                {$i}
+                            </a>
+                        </li>
+                    {/for}
+                    <li class="page-item">
+                        <a class="page-link" href="{link changePage!, $paginator->next()->getPageNumber(), $paginator->next()->getPageSize()}">
+                            <span aria-hidden="true">&rsaquo;</span>
+                            <span class="sr-only">Next</span>
+                        </a>
+                    </li>
+                    <li class="page-item">
+                        <a class="page-link" href="{link changePage!, $paginator->last()->getPageNumber(), $paginator->last()->getPageSize()}">
+                            <span aria-hidden="true">&raquo;</span>
+                            <span class="sr-only">Last</span>
+                        </a>
+                    </li>
+                </ul>
+            </nav>
+            {/if}
+        </div>
+        <div class="col text-right my-auto">
+            <span class="text-muted align-middle">
+                Results found: {$paginator->getItemCount()} ({$paginator->getPageSize()} per page).
+            </span>
+        </div>
+    </div>
+
+    {foreach $resultRows as $result}
+        <hr>
+
+        <a href="{plink Transliteration:view $result->id}">
+            {$result->book_abrev}, {$result->chapter}
+        </a>
+        <div>
+            <span class="lineno linefound">{$result->line_number}.</span>
+            <span class="line">{$result->transliteration}</span>
+        </div>
+
+    {/foreach}
+{/snippet}
\ No newline at end of file
diff --git a/app/FrontModule/component/Transliteration/TransliterationSearchResultList.php b/app/FrontModule/component/Transliteration/TransliterationSearchResultList.php
new file mode 100644
index 0000000..7d83b47
--- /dev/null
+++ b/app/FrontModule/component/Transliteration/TransliterationSearchResultList.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace App\FrontModule\Components;
+
+
+use App\Model\Repository\TransliterationRepository;
+use App\Model\TransliterationSearchModel;
+use App\Utils\Paginator;
+use Nette\Application\UI\Control;
+use Tracy\Debugger;
+
+class TransliterationSearchResultList extends Control
+{
+    /** @var TransliterationSearchModel */
+    private $transliterationSearchModel;
+
+    /** @var TransliterationRepository */
+    private $transliterationRepository;
+
+    /** @var PaginatorControl */
+    private $paginator;
+
+    private $searchTerms;
+    private $resultRows;
+    private $totalCount;
+
+    /**
+     * TransliterationSearchResultList constructor.
+     * @param TransliterationSearchModel $transliterationSearchModel
+     * @param TransliterationRepository $transliterationRepository
+     * @throws \Nette\Application\AbortException
+     */
+    public function __construct(TransliterationSearchModel $transliterationSearchModel, TransliterationRepository $transliterationRepository)
+    {
+        parent::__construct();
+        $this->transliterationSearchModel = $transliterationSearchModel;
+        $this->transliterationRepository = $transliterationRepository;
+        $this->paginator = new Paginator(1, 10);
+
+        $this->searchTerms = $this->transliterationSearchModel->getSearchTerms();
+        $this->totalCount = $this->transliterationRepository->getTransliterationsFulltextSearchTotalCount($this->searchTerms);
+
+        if (!$this->searchTerms)
+        {
+            $this->presenter->redirect('Transliteration:search');
+        }
+
+        if(!$this->searchTerms['word1'])
+        {
+            $this->presenter->redirect('Transliteration:search');
+        }
+    }
+
+
+    public function render()
+    {
+        $this->template->setFile(__DIR__ . '/TransliterationSearchResultList.latte');
+
+        $this->resultRows = $this->transliterationRepository->transliterationsFulltextSearch($this->searchTerms, $this->paginator->getOffset(), $this->paginator->getPageSize());
+        $this->paginator->setPageCount($this->totalCount);
+
+        Debugger::barDump($this->paginator->getOffset(), 'offset');
+        Debugger::barDump($this->paginator->getPageSize(), 'limit');
+        $this->template->resultRows = $this->resultRows;
+        $this->template->paginator = $this->paginator;
+        $this->template->render();
+    }
+
+    public function handleChangePage($page, $limit)
+    {
+        $this->paginator = new Paginator($page, $limit);
+        $this->redrawControl('resultList');
+    }
+}
+
+interface ITransliterationSearchResultListFactory
+{
+    /**
+     * @return TransliterationSearchResultList
+     */
+    public function create();
+}
\ No newline at end of file
diff --git a/app/FrontModule/presenters/TransliterationPresenter.php b/app/FrontModule/presenters/TransliterationPresenter.php
index ab9a98f..d4d73d9 100644
--- a/app/FrontModule/presenters/TransliterationPresenter.php
+++ b/app/FrontModule/presenters/TransliterationPresenter.php
@@ -5,6 +5,7 @@ namespace App\FrontModule\Presenters;
 
 
 use App\FrontModule\Components\ITransliterationSearchFormFactory;
+use App\FrontModule\Components\ITransliterationSearchResultListFactory;
 use App\Model\Repository\LineRepository;
 use App\Model\Repository\LitReferenceRepository;
 use App\Model\Repository\RevHistoryRepository;
@@ -32,13 +33,17 @@ class TransliterationPresenter extends Presenter
     /** @var LineRepository */
     private $lineRepository;
 
+    /** @var ITransliterationSearchResultListFactory */
+    private $transliterationSearchResultListFactory;
+
     public function __construct(
         ITransliterationSearchFormFactory $transliterationSearchFormFactory,
         TransliterationRepository $transliterationRepository,
         TransliterationSearchModel $transliterationSearchModel,
         LitReferenceRepository $litReferenceRepository,
         RevHistoryRepository $revHistoryRepository,
-        LineRepository $lineRepository
+        LineRepository $lineRepository,
+        ITransliterationSearchResultListFactory $transliterationSearchResultListFactory
     )
     {
         parent::__construct();
@@ -49,6 +54,7 @@ class TransliterationPresenter extends Presenter
         $this->litReferenceRepository = $litReferenceRepository;
         $this->revHistoryRepository = $revHistoryRepository;
         $this->lineRepository = $lineRepository;
+        $this->transliterationSearchResultListFactory = $transliterationSearchResultListFactory;
     }
 
 
@@ -59,21 +65,7 @@ class TransliterationPresenter extends Presenter
 
     public function actionSearchResult()
     {
-        $searchTerms = $this->transliterationSearchModel->getSearchTerms();
 
-        if (!$searchTerms)
-        {
-            $this->redirect('Transliteration:search');
-        }
-
-        if(!$searchTerms['word1'])
-        {
-            $this->redirect('Transliteration:search');
-        }
-
-        $resultRows = $this->transliterationRepository->transliterationsFulltextSearch($searchTerms);
-
-        $this->template->resultRows = $resultRows;
     }
 
     public function actionView($id)
@@ -101,4 +93,9 @@ class TransliterationPresenter extends Presenter
     {
         return $this->transliterationSearchFormFactory->create();
     }
+
+    public function createComponentTransliterationSearchResultList()
+    {
+        return $this->transliterationSearchResultListFactory->create();
+    }
 }
\ No newline at end of file
diff --git a/app/FrontModule/templates/Transliteration/searchResult.latte b/app/FrontModule/templates/Transliteration/searchResult.latte
index bdd6e98..b3b4d9a 100644
--- a/app/FrontModule/templates/Transliteration/searchResult.latte
+++ b/app/FrontModule/templates/Transliteration/searchResult.latte
@@ -1,43 +1,5 @@
 {block content}
     <div class="display-5">Search in texts - Results</div>
 
-    <div class="row h-100 pagination-header">
-        <div class="col my-auto">
-            <nav aria-label="Page navigation example">
-                <ul class="pagination">
-                    <li class="page-item"><a class="page-link" href="#"><span aria-hidden="true">&laquo;</span>
-                            <span class="sr-only">Previous</span></a></li>
-                    <li class="page-item"><a class="page-link" href="#"><span aria-hidden="true">&lsaquo;</span>
-                            <span class="sr-only">Previous</span></a></li>
-                    <li class="page-item active"><a class="page-link" href="#">1</a></li>
-                    <li class="page-item"><a class="page-link" href="#">2</a></li>
-                    <li class="page-item"><a class="page-link" href="#">3</a></li>
-                    <li class="page-item"><a class="page-link" href="#">4</a></li>
-                    <li class="page-item"><a class="page-link" href="#">5</a></li>
-                    <li class="page-item"><a class="page-link" href="#"><span aria-hidden="true">&rsaquo;</span>
-                            <span class="sr-only">Next</span></a></li>
-                    <li class="page-item"><a class="page-link" href="#"><span aria-hidden="true">&raquo;</span>
-                            <span class="sr-only">Next</span></a></li>
-                </ul>
-            </nav>
-        </div>
-        <div class="col text-right my-auto">
-            <span class="text-muted align-middle">
-                Total count: 23655. Showing page 1 of 4731 (5 results per site).
-            </span>
-        </div>
-    </div>
-
-    {foreach $resultRows as $result}
-        <hr>
-
-        <a n:href="Transliteration:view $result->id">
-            {$result->book_abrev}, {$result->chapter}
-        </a>
-        <div>
-            <span class="lineno linefound">{$result->line_number}.</span>
-            <span class="line">{$result->transliteration}</span>
-        </div>
-
-    {/foreach}
+    {control transliterationSearchResultList}
 {/block}
\ No newline at end of file
diff --git a/app/config/components.neon b/app/config/components.neon
index 29e36e6..b28b8ea 100644
--- a/app/config/components.neon
+++ b/app/config/components.neon
@@ -18,4 +18,6 @@ services:
     - App\AdminModule\Components\ISurfaceTypeEditFormFactory
     - App\AdminModule\Components\IMuseumEditFormFactory
     - App\AdminModule\Components\IBookTypeEditFormFactory
-    - App\AdminModule\Components\IBookEditFormFactory
\ No newline at end of file
+    - App\AdminModule\Components\IBookEditFormFactory
+
+    - App\FrontModule\Components\ITransliterationSearchResultListFactory
diff --git a/app/model/repository/TransliterationRepository.php b/app/model/repository/TransliterationRepository.php
index 47ebeff..ca7c61d 100644
--- a/app/model/repository/TransliterationRepository.php
+++ b/app/model/repository/TransliterationRepository.php
@@ -4,6 +4,7 @@ namespace App\Model\Repository;
 
 use App\Enum\ESearchFormOperators;
 use Nette\Utils\ArrayHash;
+use Tracy\Debugger;
 
 /**
  * Repository pro práci s tabulkou `transliteration`
@@ -32,9 +33,11 @@ class TransliterationRepository extends Repository
     /**
      * Vyhledává texty podle slov v řádcích textu
      * @param ArrayHash $queryParams objekt s podmínkami pro hledaný text
+     * @param $offset null|int
+     * @param $limit null|int
      * @return \Nette\Database\ResultSet
      */
-    public function transliterationsFulltextSearch(ArrayHash $queryParams)
+    public function transliterationsFulltextSearch(ArrayHash $queryParams, $offset = null, $limit = null)
     {
         $where = '';
         $whereArgs = [];
@@ -126,9 +129,26 @@ class TransliterationRepository extends Repository
                   LEFT JOIN book b ON t.id_book = b.id_book
                   WHERE " . $where;
 
+        if($offset != null && $limit != null)
+        {
+            Debugger::barDump('tu');
+            $query .= ' LIMIT ?, ?';
+            $whereArgs[] = (int) $offset;
+            $whereArgs[] = (int) $limit;
+        }
+
+        Debugger::barDump($offset);
+        Debugger::barDump($limit);
+        Debugger::barDump($query);
+
         return $this->context->queryArgs($query, $whereArgs);
     }
 
+    public function getTransliterationsFulltextSearchTotalCount($queryParams)
+    {
+        return $this->transliterationsFulltextSearch($queryParams, null, null)->getRowCount();
+    }
+
     /**
      * Vytváří regulární výraz z hledaného slova, escapuje regexp znaky
      * @param $word string hledané slovo
diff --git a/app/utils/Paginator.php b/app/utils/Paginator.php
new file mode 100644
index 0000000..353dffc
--- /dev/null
+++ b/app/utils/Paginator.php
@@ -0,0 +1,170 @@
+<?php
+
+namespace App\Utils;
+
+
+class Paginator
+{
+
+    private $limit;
+    private $page;
+
+    private $pageCount;
+    private $itemCount;
+
+    private $startingPage;
+    private $endingPage;
+
+    const ALLOWED_LIMITS = [10, 25, 50, 100];
+
+    /**
+     * Implementation of spring Data Pageable to paginate and sort JPA queries
+     * @param $page int query offset
+     * @param $limit int query limit
+     */
+    public function __construct($page, $limit)
+    {
+        if ($page < 1)
+        {
+            $page = 1;
+        }
+        $this->page = $page;
+
+        foreach (self::ALLOWED_LIMITS as $l)
+        {
+            if ($l == $limit)
+            {
+                $this->limit = $limit;
+            }
+        }
+        if ($this->limit == 0)
+        {
+            $this->limit = self::ALLOWED_LIMITS[0];
+        }
+    }
+
+    /**
+     * @return int
+     */
+    public function getPageNumber()
+    {
+        return $this->page;
+    }
+
+    /**
+     * @return int
+     */
+    public function getPageSize()
+    {
+        return $this->limit;
+    }
+
+    public function getOffset()
+    {
+        return ($this->page - 1) * $this->limit;
+    }
+
+    /**
+     * @return Paginator
+     */
+    public function next()
+    {
+        return $this->hasNext() ? new Paginator($this->getPageNumber() + 1, $this->getPageSize()) : $this;
+    }
+
+    /**
+     * @return Paginator
+     */
+    public function previous()
+    {
+        return $this->hasPrevious() ? new Paginator($this->getPageNumber() - 1, $this->getPageSize()) : $this;
+    }
+
+    public function previousOrFirst()
+    {
+        return $this->hasPrevious() ? $this->previous() : $this->first();
+    }
+
+    public function first()
+    {
+        return new Paginator(1, $this->getPageSize());
+    }
+
+    /**
+     * @return bool
+     */
+    public function hasPrevious()
+    {
+        return $this->getOffset() >= $this->limit;
+    }
+
+    /**
+     * @return bool
+     */
+    public function hasNext()
+    {
+        return $this->getPageNumber() < $this->getPageCount();
+    }
+
+    /**
+     * @return Paginator
+     */
+    public function last()
+    {
+        return new Paginator($this->getPageCount(), $this->getPageSize());
+    }
+
+    public function getPageCount()
+    {
+        return $this->pageCount;
+    }
+
+    public function getItemCount()
+    {
+        return $this->itemCount;
+    }
+
+    /**
+     * Sets maximum possible page to be displayed and total number of items
+     * @param $itemCount int total count
+     */
+    public function setPageCount($itemCount)
+    {
+        $this->itemCount = $itemCount;
+        $this->pageCount = ceil($itemCount / $this->getPageSize());
+        $this->setPagesList();
+    }
+
+    public function getAllowedLimits()
+    {
+        return self::ALLOWED_LIMITS;
+    }
+
+    /**
+     * Sets starting and ending page number to be displayed in template
+     */
+    private function setPagesList()
+    {
+        $offset = 2;
+
+        if ($this->getPageNumber() + $offset >= $this->getPageCount())
+        {
+            $this->startingPage = max(1, $this->getPageCount() - 2 * $offset);
+            $this->endingPage = $this->getPageCount();
+        } else
+        {
+            $this->startingPage = max(1, $this->getPageNumber() - $offset);
+            $this->endingPage = min($this->startingPage + 2 * $offset, $this->getPageCount());
+        }
+    }
+
+    public function getStartingPage()
+    {
+        return $this->startingPage;
+    }
+
+    public function getEndingPage()
+    {
+        return $this->endingPage;
+    }
+}
\ No newline at end of file
