Jump to content

Code for next/previous record in Display template


Meddysong

Recommended Posts

Posted

I revisit Pages categories in my site every now and again. It's a great way to relive old holidays. The user experience isn't perfect though; once I've finished an article, the only link at the bottom of the record is to go back to the category. What I'd like to do is go to the next article.

Here's the code which generates the links at the end of the record template in the Display template set:

<div class="ipsGrid_span6 ipsType_left ipsPager_prev">
  {{if $record::database()->use_categories}}
  <a href="{$record->container()->url()}" title="{lang="cms_back_to_category_with_title" sprintf="$record->container()->_title"}" rel="up">
    <span class="ipsPager_type">{lang="cms_back_to_category" sprintf="$record::database()->recordWord( 2 )"}</span>
    <span class="ipsPager_title ipsType_light ipsTruncate ipsTruncate_line">{lang="$record->container()->_title"}</span>
  </a>
  {{else}}
  {{$page = \IPS\cms\Pages\Page::$currentPage;}}
  <a href="{$page->url()}" title="{lang="cms_back_to_category_with_title" sprintf="$page->_title"}" rel="up">
    <span class="ipsPager_type">{lang="cms_back_to_category" sprintf="$record::database()->recordWord( 2 )"}</span>
    <span class="ipsPager_title ipsType_light ipsTruncate ipsTruncate_line">{$page->_title}</span>
  </a>
  {{endif}}
</div>

What I'd like to do is add a link to the next article (if there is one). Given that categoryTable of the Listing template identifies records in the category ($rows) and then sends them to be outputted in a foreach loop, I figured this should be possible. The problem is that the parameter $rows is automatically included with categoryTable, so I can't just copy and paste how to generate it into the record Template and work from there.

Can anybody think how to tackle this? It should be possible, shouldn't it? Define $category as this $record's container, then generate a list of articles in this $category (ordering either coded or being inherited from the database), and if this is record (x > n) of n, generate the conditional text with a link to record x + 1. I think I can visualise this conceptually but I'm a novice at PHP. I'd be delighted if anybody could kindly help me crack it.

Posted

The necessary data isn’t available in the record display template. It needs custom MySQL queries to check what the next/previous record (according to the database sorting options) are. 

Posted

You know, I've never tried writing a query in IPS. I learn best by having a goal in mind rather than by abstract examples from a book, so this might be an interesting little project for me to try :)

Posted

Just use:

	{{$prev = $record->prevItem();}}
	{{$next  = $record->nextItem();}}

fVJGSqf.png

You will need to create the lang bits.

The whole script is:

	{{$prev = $record->prevItem();}}
	{{$next  = $record->nextItem();}}

	{{if $prev || $next}}
		<div class='ipsGrid ipsGrid_collapsePhone ipsPager ipsSpacer_top'>
			{{if $prev !== NULL}}
				<div class="ipsGrid_span6 ipsType_left ipsPager_prev">
					<a href="{$prev->url()}" title="{lang="prev_link"}" rel="prev">
						<span class="ipsPager_type">{lang="prev_link"}</span>
						<span class="ipsPager_title ipsType_light ipsType_break">{wordbreak="$prev->mapped('title')"}</span>
					</a>
				</div>
			{{else}}
				<div class="ipsGrid_span6 ipsType_left ipsPager_prev">
					<a href="{$record->container()->url()}" title="{lang="link_go_to_category" sprintf="$record->container()->_title"}" rel="up">
						<span class="ipsPager_type">{lang="link_back_to_category"}</span>
						<span class="ipsPager_title ipsType_light ipsType_break">{lang="$record->container()->_title" wordbreak="true"}</span>
					</a>
				</div>
			{{endif}}
			{{if $next !== NULL}}
				<div class="ipsGrid_span6 ipsType_right ipsPager_next">
					<a href="{$next->url()}" title="{lang="next_link"}" rel="next">
						<span class="ipsPager_type">{lang="next_link"}</span>
						<span class="ipsPager_title ipsType_light ipsType_break">{wordbreak="$next->mapped('title')"}</span>
					</a>
				</div>
			{{else}}
				<div class="ipsGrid_span6 ipsType_right ipsPager_next">
					<a href="{$record->container()->url()}" title="{lang="link_go_to_category" sprintf="$record->container()->_title"}" rel="up">
						<span class="ipsPager_type">{lang="link_back_to_category"}</span>
						<span class="ipsPager_title ipsType_light ipsType_break">{lang="$record->container()->_title" wordbreak="true"}</span>
					</a>
				</div>
			{{endif}}
		</div>
		<hr class='ipsHr'>
	{{endif}}

 

Posted

That's an elegant solution - thanks!

I can't implement it yet, unfortunately; there's some sort of bug preventing me from saving anything to any record (and comments) templates. But soon I'll be making use of it :)

Posted
1 hour ago, Heosforo said:

mod_security?

It was that, yep. I've disabled it and it's working. Got to work out what to do next ...

Thanks for your help, @Adriano Faria - I have a very happy wife. (It's one fewer thing for her to say was "better on Joomla"!)

Posted

Ah, no, I spoke too soon. I've just added a test comment to an article and now that record has become the last one in the list, giving $next == NULL. It was the second of nine articles so this isn't the desired result. It seems to be ordering by record_last_comment whereas I need either record_publish_date or primary_id_field. Is it back to the drawing board (ie work out a query), Adriano?

Posted

This is an existing method in the Content Item class. It wasn’t made by me; it’s part of the framework. I just linked you to it.

Anyway, it is ordered by the column in your DB, which is set default to When a new comment is made only. Go to yoyr DB settings and change to see if it changes something. Take a look also in Fields Options (sort and order).

Posted

As I said before: The information that is actually needed here is not available from the record template. The Prev/Next variables don’t honor the database ordering settings, so they will usually give the wrong results unfortunately. If I have an alphabetic index for example, I neither want the next record by record ID, nor by “update/comment time”. 

Posted

Yeah, I don't think this approach is going to work, unfortunately. There's nothing wrong with the Field Options; they're set to sort by Publish Date. But the method is using the record_last_comment DB field for sorting and, unfortunately, there are only three options to choose from:

  • When the item is edited or a new comment is made
  • When the item is edited only
  • When a new comment is made only

It just takes one little edit to throw everything out of sequence and, unfortunately, I've got sporadic edits in my articles.

I've had a look below the surface (I'm not a coder so this is quite intriguing!) and think I've seen what the challenge is. The function prevItem() is this:

public function prevItem()
{
try
{
$column = $this->getDateColumn();
$idColumn = static::$databaseColumnId;

$item	= NULL;
foreach( static::getItemsWithPermission( array(
array( static::$databaseTable . '.' . static::$databasePrefix . $column . '<?', $this->$column ),
				array( static::$databaseTable . '.' . static::$databasePrefix . static::$databaseColumnMap['container'] . '=?', $this->container()->_id ),
				array( static::$databaseTable . '.' . static::$databasePrefix . $idColumn . '!=?', $this->$idColumn )
			), static::$databasePrefix . $column . ' DESC', 1 ) AS $item )
			{
				break;
			}

			return $item;
		}
		catch( \Exception $e ) { }
	}

which is dependent on this:

/**
* Get date column for next/prev item
* Does not use last comment / last review as these will often be 0 and is not how items are generally ordered
*
* @return	string
*/
protected function getDateColumn()
{
if( isset( static::$databaseColumnMap['updated'] ) )
{
$column	= is_array( static::$databaseColumnMap['updated'] ) ? static::$databaseColumnMap['updated'][0] : static::$databaseColumnMap['updated'];
}
else if( isset( static::$databaseColumnMap['date'] ) )
{
$column	= is_array( static::$databaseColumnMap['date'] ) ? static::$databaseColumnMap['date'][0] : static::$databaseColumnMap['date'];
}

return $column;
}

That looks for a parameter 'updated', which is:

'updated'				=> 'record_last_comment',

and, unfortunately for me, that's limited to the three options I showed above.

My brain is telling me "all you've got to do is create a new function prevItem2(), which calls a function getDateColumn2(), which references $databaseColumnMap['date'] (which already exists and uses DB field record_publish_date)." And my brain is also telling me that making changes to these core templates would be a really bad idea!

This is a job for a plugin, isn't it? I'd like to learn but don't have a clue how to do it. If I paid you to explain how to do it in PM, Adriano, would you be interested in tutoring me? 

Posted
9 minutes ago, opentype said:

As I said before: The information that is actually needed here is not available from the record template. The Prev/Next variables don’t honor the database ordering settings, so they will usually give the wrong results unfortunately. If I have an alphabetic index for example, I neither want the next record by record ID, nor by “update/comment time”. 

\IPS\Content\Item:

	/**
	 * Get Next Item
	 *
	 * @return	static|NULL
	 */
	public function nextItem()
	{
		try
		{
			$column = $this->getDateColumn();
			$idColumn = static::$databaseColumnId;

			$item	= NULL;

			foreach( static::getItemsWithPermission( array(
				array( static::$databaseTable . '.' . static::$databasePrefix . $column . '>?', $this->$column ),
				array( static::$databaseTable . '.' . static::$databasePrefix . static::$databaseColumnMap['container'] . '=?', $this->container()->_id ),
				array( static::$databaseTable . '.' . static::$databasePrefix . $idColumn . '!=?', $this->$idColumn )
			), static::$databasePrefix . $column . ' ASC', 1 ) AS $item )
			{
				break;
			}

			return $item;
		}
		catch( \Exception $e ) { }
	}
	
	/**
	 * Get Previous Item
	 *
	 * @return	static|NULL
	 */
	public function prevItem()
	{
		try
		{
			$column = $this->getDateColumn();
			$idColumn = static::$databaseColumnId;

			$item	= NULL;
			foreach( static::getItemsWithPermission( array(
				array( static::$databaseTable . '.' . static::$databasePrefix . $column . '<?', $this->$column ),
				array( static::$databaseTable . '.' . static::$databasePrefix . static::$databaseColumnMap['container'] . '=?', $this->container()->_id ),
				array( static::$databaseTable . '.' . static::$databasePrefix . $idColumn . '!=?', $this->$idColumn )
			), static::$databasePrefix . $column . ' DESC', 1 ) AS $item )
			{
				break;
			}
			
			return $item;
		}
		catch( \Exception $e ) { }
	}

It uses the date column to sort: submitted or updated.

	/**
	 * Get date column for next/prev item
	 * Does not use last comment / last review as these will often be 0 and is not how items are generally ordered
	 *
	 * @return	string
	 */
	protected function getDateColumn()
	{
		if( isset( static::$databaseColumnMap['updated'] ) )
		{
			$column	= is_array( static::$databaseColumnMap['updated'] ) ? static::$databaseColumnMap['updated'][0] : static::$databaseColumnMap['updated'];
		}
		else if( isset( static::$databaseColumnMap['date'] ) )
		{
			$column	= is_array( static::$databaseColumnMap['date'] ) ? static::$databaseColumnMap['date'][0] : static::$databaseColumnMap['date'];
		}

		return $column;
	}

That's how it works.

Your best way to do it is create a plugin to add new methods to check the field you're sorting in DB, not only dates.

Archived

This topic is now archived and is closed to further replies.

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...