Jump to:

5540 Posts in 1738 Topics by 1224 members

Customising the CMS

SilverStripe Forums » Customising the CMS » Page Controls: Next/Previous Page Link?

Moderators: martimiz, Sean, biapar, Willr, Ingo, swaiba, simon_w

Page: 1 2
Go to End
Author Topic: 6004 Views
  • Anatol
    Avatar
    126 Posts

    Page Controls: Next/Previous Page Link? Link to this post

    Hi,

    Is there any built-in page control that creates a link to the next or previous page in the site tree? i.e. wherever I am and regardless of the hierarchy level such a page control would create a link to the next/previous page. It would be very useful to have this kind of page control for websites that are set up like a book (to flip from page to page).

    Maybe this control already exists but I couldn't find anything that worked in the built-in page control documentation or in the API. But maybe I just didn't find it.

    It's probably not too hard to extend the PageController though ...

    Cheers!
    Anatol

  • Anatol
    Avatar
    126 Posts

    Re: Page Controls: Next/Previous Page Link? Link to this post

    Hi,

    here is a solution that seems to work to create previous/next links. I have to admit that it's not a very elegant, though.

    First of all, if you only need a previous/next link within a section of a page use the Previous or Next Recipe. It's much nicer code.

    However, if you want to flip from page to page regardless of the hierarchy level you can use the method below (or come up with a better one; I know there must be a nicer way).

    Add this to the Page_Controller class in /mysite/code/Page.php

    function PreviousNext(){
       // set $max_depth to the maximum level of your site hierarchy (/home/ would be level 0, /home/subpage/ would be level 1, etc...)
       $max_depth = 4;
       
       // constructing a SQL query (see the query below *)
       $sqlQuery = new SQLQuery();
       
       // the fields to select
       for($i=0; $i<=$max_depth; $i++){
          $select[] = 't'.$i.'.ID AS ID'.$i;
          $select[] = 't'.$i.'.URLSegment AS URLSegment'.$i;
          $select[] = 't'.$i.'.Title AS Title'.$i;
          $select[] = 't'.$i.'.MenuTitle AS MenuTitle'.$i;
       }
       $sqlQuery->select = $select;
       
       // we select from the SiteTree table and join the SiteTree table a few times to itself
       $from[] = 'SiteTree as t0 ';
       for($i=1; $i<=$max_depth; $i++){
             $from[] = 'LEFT JOIN SiteTree AS t'.$i.' ON t'.$i.'.ParentID = t'.($i-1).'.ID ';
       }
       $sqlQuery->from = $from;
       
       // a few WHERE clauses to only return the pages that show in the menus
       $where[] = 't0.ParentID = 0 ';
       for($i=0; $i<=$max_depth; $i++){
          $where[] = '( t'.$i.'.ShowInMenus = 1 OR t'.$i.'.ShowInMenus is null ) ';
       }
       $sqlQuery->where = $where;
       
       // defining the sort order
       $comma = '';
       $orderby = '';
       for($i=0; $i<=$max_depth; $i++){
          $orderby .= $comma.'t'.$i.'.Sort ASC ';
          $comma = ', ';
       }
       $sqlQuery->orderby = $orderby;

       // finally execute the query ...
       $result = $sqlQuery->execute();

       // and create a more useful array from it
       $page = array();
       foreach($result as $row) {
          for($i=0; $i<=$max_depth; $i++){
             if(!array_key_exists('URLSegment'.$i, $page) && !is_null($row['URLSegment'.$i])) {
                $page[$row['ID'.$i]]['URLSegment'] = $row['URLSegment'.$i];
                $page[$row['ID'.$i]]['Title'] = $row['Title'.$i];
                $page[$row['ID'.$i]]['MenuTitle'] = $row['MenuTitle'.$i];
             }
          }
       }

       // set the array pointer to the current page
       while (key($page) != $this->ID) {
          if (next($page) === false) return '';
       }

       // set the array pointer to the previous page; if there is no previous page it means we are on the home page - in this case set the array pointer to the last page   
       if (!$previousPage = prev($page)) {
          $previousPage = end($page);
          reset($page);
       } else {
          // this is a prep step for the next page (setting the array pointer back to the current page)
          next($page);
       }

       // if there is no next page it means we reached the last page; in this case set the array pointer to the first page
       if (!$nextPage = next($page)) {
          $nextPage = reset($page);
       }
       
       // return two links for the previous and next page
       $output = '<a href="'.$previousPage['URLSegment'].'" title="Go to the previous page: '.$previousPage['Title'].'">previous page</a> <a href="'.$nextPage['URLSegment'].'" title="Go to the next page: '.$nextPage['Title'].'">next page</a>';
          
       return $output;
    }

    Then simply add this to the template wherever you need it:

    $PreviousNext

    Quite messy but it seems to work. I especially would like to get a DataObject as a result to have more control in the template without touching the PHP code, but I didn't have enough time to look that up.

    * FYI: the generated SQL query of the code above (for max 4 hierarchy levels) is:

    SELECT
       t0.Title,
       t1.Title,
       t2.Title,
       t3.Title,
       t4.Title
    FROM
       SiteTree as t0
       LEFT JOIN SiteTree AS t1 ON t1.ParentID = t0.ID
       LEFT JOIN SiteTree AS t2 ON t2.ParentID = t1.ID
       LEFT JOIN SiteTree AS t3 ON t3.ParentID = t2.ID
       LEFT JOIN SiteTree AS t4 ON t4.ParentID = t3.ID
    WHERE
       (
          t0.ParentID = 0
       ) AND (
          t0.ShowInMenus = 1 OR t0.ShowInMenus is null
       ) AND (
          t1.ShowInMenus = 1 OR t1.ShowInMenus is null
       ) AND (
          t2.ShowInMenus = 1 OR t2.ShowInMenus is null
       ) AND (
          t3.ShowInMenus = 1 OR t3.ShowInMenus is null
       ) AND (
          t4.ShowInMenus = 1 OR t4.ShowInMenus is null
       )
    ORDER BY
       t0.Sort ASC,
       t1.Sort ASC,
       t2.Sort ASC,
       t3.Sort ASC,
       t4.Sort ASC

    You can run this query to see the result. The foreach($result as $row) loop then creates something useful from the result.

    If you have a better and cleaner way for the whole thing please post it.

    Cheers!
    Anatol

  • DeklinKelly
    Avatar
    Community Member
    197 Posts

    Re: Page Controls: Next/Previous Page Link? Link to this post

    Here is a solution by Aram Balakjian:
    http://ssbits.com/creating-previous-and-next-buttons-on-a-page/

    function for Page_Controller class:

    public function PrevNextPage($Mode = 'next') {

    if($Mode == 'next'){
       $Where = "ParentID = ($this->ParentID) AND Sort > ($this->Sort)";
    $Sort = "Sort ASC";
    }
    elseif($Mode == 'prev'){
       $Where = "ParentID = ($this->ParentID) AND Sort < ($this->Sort)";
    $Sort = "Sort DESC";
    }
    else{
       return false;
    }

    return DataObject::get("SiteTree", $Where, $Sort, null, 1);
       
    }

    Place in .ss template:

    <% control PrevNextPage(prev) %>
        <a href="$Link" title="Go to $Title">previous page</a>   
    <% end_control %>
    <% control PrevNextPage(next) %>            
        <a href="$Link" title="Go to $Title">next page</a>
    <% end_control %>

  • Anatol
    Avatar
    126 Posts

    Re: Page Controls: Next/Previous Page Link? Link to this post

    Thanks for the reply. The problem is that the solution on SS Bits only works within a section because it relies on the WHERE clause with ParentID and Sort, i.e. you can only create a previous/next button within a set of subpages under one parent page. What I was looking for was a way to go through a complete site structure page by page regardless of the hierarchical structure. I have a site with more than 200 pages on different levels like this:

    - Section I
    - - Part A
    - - - Sub Page 1
    - - - Sub Page 2
    - - - - Sub Page 2a
    - - Part B
    - - Part C
    - Section II
    - - etc

    If you had a Next button on "Part A" the next page should be "Sub Page 1", but the SSBits solution would jump to "Part B" which is the next page in this section of the same level. (Sub page 1 has a different parent ID.)

    I eventually used a solution slightly different from the one in my earlier post to fix some bugs:

    function PreviousNext(){
          // set $max_depth to the maximum level of your site hierarchy (/home/ would be level 0, /home/subpage/ would be level 1, etc...)
          $max_depth = 4;
          
          // constructing a SQL query
          $sqlQuery = new SQLQuery();

          // the fields to select
          for($i=0; $i<=$max_depth; $i++){
             $select[] = 't'.$i.'.ID AS ID'.$i;
             $select[] = 't'.$i.'.URLSegment AS URLSegment'.$i;
             $select[] = 't'.$i.'.Title AS Title'.$i;
             $select[] = 't'.$i.'.MenuTitle AS MenuTitle'.$i;
             $select[] = 't'.$i.'.ShowInMenus AS ShowInMenus'.$i;
          }
          $sqlQuery->select = $select;

          // we select from the SiteTree table and join the SiteTree table a few times to itself
          $from[] = 'SiteTree as t0 ';
          for($i=1; $i<=$max_depth; $i++){
                $from[] = 'LEFT JOIN SiteTree AS t'.$i.' ON t'.$i.'.ParentID = t'.($i-1).'.ID ';
          }
          $sqlQuery->from = $from;

          // the WHERE clause
          $where[] = 't0.ParentID = 0 ';
          $sqlQuery->where = $where;

          // the sort order
          $comma = '';
          $orderby = '';
          for($i=0; $i<=$max_depth; $i++){
             $orderby .= $comma.'t'.$i.'.Sort ASC ';
             $comma = ', ';
          }
          $sqlQuery->orderby = $orderby;

          // execute the query
          $result = $sqlQuery->execute();
          $page = array();

          // create a more useful array from the result
          foreach($result as $row) {
             for($i=0; $i<=$max_depth; $i++){
                if(!array_key_exists('URLSegment'.$i, $page) && !is_null($row['URLSegment'.$i]) && $row['ShowInMenus'.$i]) {
                   $page[$row['ID'.$i]]['URLSegment'] = $row['URLSegment'.$i];
                   $page[$row['ID'.$i]]['Title'] = $row['Title'.$i];
                   $page[$row['ID'.$i]]['MenuTitle'] = $row['MenuTitle'.$i];
                }
             }
          }

          // set the array pointer to the current page
          while (key($page) != $this->ID) {
             if (next($page) === false) return '';
          }

          // set the array pointer to the previous page; if there is no previous page it means we are on the home page - in this case set the array pointer to the last page
          if (!$previousPage = prev($page)) {
             $previousPage = end($page);
             reset($page);
          } else {
          // this is a prep step for the next page (setting the array pointer back to the current page)
             next($page);
          }

          // if there is no next page it means we reached the last page; in this case set the array pointer to the first page
          if (!$nextPage = next($page)) {
             $nextPage = reset($page);
          }

          $output = '<ul class="previous_next"><li class="previous"><a href="'.$previousPage['URLSegment'].'" title="Go to the previous page: '.$previousPage['Title'].'">&nbsp;<span>previous page</span>&nbsp;</a></li>';
          $output .= '<li class="next"><a href="'.$nextPage['URLSegment'].'" title="Go to the next page: '.$nextPage['Title'].'">&nbsp;<span>next page</span>&nbsp;</a></li></ul>';

          // return two links for the previous and next page
          return $output;
       }

    This solution works with only one SQL query. If your site structure is less complex you could simply go through all pages and all child pages (and child pages of child pages) and construct an array in that way but with a complex site and many pages you'll end up with heaps of SQL queries.

    Still, it looks too complicated and I wish there was some easier way than all the code above. I think if Silverstripe had a ParentID, Sort AND a Level field (what level in the site hierarchy) in the SiteTree table it would be easier to get a proper result with one query. But there is no level field

    Any better solutions (or even a built-in Silverstripe previous() / next() function) would be very appreciated.

    Cheers!
    Anatol

  • DeklinKelly
    Avatar
    Community Member
    197 Posts

    Re: Page Controls: Next/Previous Page Link? Link to this post

    You can sort only by Sort. Not a perfect solution but it could be improved.

       public function PrevNextPage($Mode = 'next') {
           if($Mode == 'next'){
           $Where = " Sort > ($this->Sort)";
           $Sort = "Sort ASC";
           }
           elseif($Mode == 'prev'){
           $Where = " Sort < ($this->Sort)";
           $Sort = "Sort DESC";
           }
           else{
              return false;
           }
           return DataObject::get("SiteTree", $Where, $Sort, null, 1);
       }

  • Anatol
    Avatar
    126 Posts

    Re: Page Controls: Next/Previous Page Link? Link to this post

    Hm, yes. I thought about that, but unfortunately it would return the wrong results for most pages.

    Take again the example:

    - Section I (Sort: 1)
    - - Part A (Sort: 1)
    - - - Sub Page 1 (Sort: 1)
    - - - Sub Page 2 (Sort: 2)
    - - - - Sub Page 2a (Sort: 1)
    - - Part B (Sort: 2)
    - - Part C (Sort: 3)
    - Section II (Sort: 2)
    - - etc

    If you only use Sort in the WHERE clause and you had a Next button on the page "Part A" it would link to the first result with Sort = 2, i.e. "Sub Page 2" or even "Part B", not "Sub Page 1" which would be the desired link.

  • DeklinKelly
    Avatar
    Community Member
    197 Posts

    Re: Page Controls: Next/Previous Page Link? Link to this post

    I see the problem. Does anybody else have an elegant solution? If so, please let us know. Thanks!

  • cbolt
    Avatar
    Community Member
    6 Posts

    Re: Page Controls: Next/Previous Page Link? Link to this post

    I recently coded this solution, I know this thread is getting old, but I'm just posting in-case someone else finds this useful:

    For your controller:

       function NextPage($id=null) {
          if ($id===null) {
             // if page has a child go to the first child page
             $children = $this->AllChildren();
             foreach ($children as $child) {
                return $child;   
             }
             $parent = $this->ParentID;
             $sort = $this->Sort;
          } else {
             $page = DataObject::get_by_id("SiteTree", $id);
             $parent = $page->ParentID;
             $sort = $page->Sort;
          }
          $where = "ParentID = $parent AND `SiteTree`.ID != 4 AND Sort > $sort";
          $pages = DataObject::get("SiteTree", $where, "Sort", "", 1);
          if($pages) {
             foreach($pages as $page) {
                return $page;
             }
          }
          // nothing returned so we recurse up through parents
          if ($parent > 0) return $this->NextPage($parent);
       }
       function PreviousPage() {
          $where = "ParentID = {$this->ParentID} AND Sort < {$this->Sort}";
          $pages = DataObject::get("SiteTree", $where, "Sort DESC", "", 1);
          if($pages) {
             foreach($pages as $page) {
                // if page has a child go to the last child page
                $children = $page->AllChildren();
                if ($children) {
                   foreach ($children as $child) {
                      continue;
                   }
                   return $child;
                }            
                return $page;
             }
          }
          // nothing returned so we jump to the parent page
          if ($this->ParentID > 0) return $this->getParent();
       }

    For your template

    <% control PreviousPage %>
    <a href="$Link" title="Go to the previous page">Prev</a>
    <% end_control %>
    <% control NextPage %>
    <a href="$Link" title="Go to the next page">Next</a>
    <% end_control %>

    6004 Views
Page: 1 2
Go to Top

Want to know more about the company that brought you SilverStripe? Then check out SilverStripe.com

Comments on this website? Please give feedback.