joedawson's avatar

Pagination Help/Logic

Hello all,

I'm unsure of how to tackle pagination for a bunch of photos at the following routes:

/photos
/photos/popular
/profiles/{slug}/photos

Lets say from /photos, I click the first photo. On this page /photos/1 I navigate to the next photo which is now /photos/2. This is fine.

But if I now navigate to /photos/popular and click the first photo, which is potentially /photos/1709 and I click next - my existing code will simply take me to the next photo based in it's id. So potentially /photos/1710.

The same applies for the /profiles/{slug}/photos route, I want to take into account the way a user hits a photo page.

So here's my existing code for fetching the next and previous photos;

/**
 * The previous photo.
 * 
 * @param  integer $id
 * @return mixed
 */
public static function previous($id)
{
    return static::where('id', '<', $id)->first();
}

/**
 * The next photo.
 * 
 * @param  integer $id
 * @return mixed
 */
public static function next($id)
{
    return static::where('id', '>', $id)->first();
}

(Used on my Photo model, Photo::next($id) returns the next photo.)

How would you recommend I amend my existing code above? Sorry if anythings not clear!

Thanks in advance :)

0 likes
16 replies
vitorarjol's avatar

@JoeDawson I would use something like that:

/**
     * Scope a query to only include popular photos.
     *
     * @return \Illuminate\Database\Eloquent\Builder
     */
    public function scopePopular($query)
    {
        return $query->where('likes', '>', 100);
    }

//Then in the controller (or whatever you get the photos :] )
$photos = App\Photos::popular()->paginate(10);

Then I would use the pagination of Laravel to get the hard work for me (You won't have to concern about the Id anymore) http://laravel.com/docs/5.1/pagination

Hope it helps!

1 like
joedawson's avatar

@vitorarjol sorry, this doesn't help me at all. My question was regarding pagination, depending on how the user gets to a photo. Not a scope query.

For example, my show() method looks like this;

public function show($id)
{
    $photo      = Photo::with('views')->findOrFail($id);
    $previous   = Photo::previous($photo->id);
    $next       = Photo::next($photo->id);

    event(new PhotoWasViewed($photo));
    
    return view('photos.show', compact('photo', 'previous', 'next'));
}

On this page, I have a next and previous link. But these next and previous links will change depending on where the user comes from (which is explained above).

For example, when I visit /photos/popular and click a photo - of which may be /photos/139 - the next photo will be the next popular photo. Not the next photo based on the ID.

vitorarjol's avatar

I know that is about pagination. The problem I see here is that you're reusing the method show, but the collection should be different based on the page that the users access.

The scope was just to exemplify that you could get one collection for the /photos/popular link, and instead of passing the next and previous ID's, use the pagination helper to do this work.

BUT, if you're reloading the page everytime that the user click's on a link, this will not work :/.

I'll think more about this second case.

joedawson's avatar

I don't think you're understanding my question. I'm not reusing my show method, anywhere? I think I may be confusing you with by using the term "pagination". Nothing is being reloaded anywhere?

These next and previous methods, simply need to return a link to the photo depending on how they reached that photo.

I have written the different ways a user can reach a photo above.

thomaskim's avatar

@JoeDawson How are you finding the most popular photo? Or better yet, what is your query to find that?

1 like
vitorarjol's avatar

Can you show the method where you get the photos if I navigate to /photos/popular?

vitorarjol's avatar

The reason why I'm insisting on the pagination it's because if you paginate your query it will handle the next and prev photos, and the ID will not be a trouble anymore.

joedawson's avatar

@thomaskim I'm not quite sure why this is relevant but...

$photos = Photo::leftJoin('views','photos.id','=','views.photo_id')->
    selectRaw('photos.*, count(views.photo_id) AS `count`')->
    groupBy('photos.id')->
    orderBy('count','DESC')->
    paginate(18);

The /photos/popular was an example of how to view a single photo. I'm still not sure you guys understand my question.

vitorarjol's avatar

@JoeDawson Great! Now, for that $phothos, how are you handling the prev and next links in the view?? From the pagination helper or are you trying to use the id from a $photo?

joedawson's avatar

Sorry guys, please let me explain further.

There are multiple ways of reaching a photo. Currently, 3 that I can think of. This is how I want it to work.

One (/photos)

  • I visit /photos
  • I click a photo
  • This is possibly /photos/3
  • On this page (/photos/3) there are two buttons to navigate to the next and previous photo
  • If I click next, I am taken to /photos/4
  • This page (/photos/4) still has my two buttons to navigate next and previous
  • I click previous, I am now taken back to /photos/3
  • I click previous again, then I am now on /photos/2

Two (/photos/popular)

  • I visit /photos/popular - These are ordered based on the view count
  • I click on a photo, possibly /photos/1309
  • On this page (/photos/1309) there are two buttons to navigate to the next and previous photo - these need to link to the next and previous popular photos
  • I click next, I am taken to /photos/788 - This is the next most popular photo

As you can see, I want these buttons to navigate between the popular photos because this is where I have come from.

Three (/profiles/{slug}/photos)

  • I am on someones profile, their photos are listed here.
  • I click a photo
  • This is possibly (/photos/33)
  • The same buttons as before (next and previous) are here too
  • If I click next, I want to be taken to the next photo based on this users profile I just come from

I am not duplicating my show() method anywhere as @vitorarjol suggested, it all uses this same method

public function show($id)
{
    $photo      = Photo::with('views')->findOrFail($id);
    $previous   = Photo::previous($photo->id);
    $next       = Photo::next($photo->id);

    event(new PhotoWasViewed($photo));
    
    return view('photos.show', compact('photo', 'previous', 'next'));
}

All I am after, is how to deal with where the user comes from so I can provide the correct link.

joedawson's avatar

The best example I can think of is http://dribbble.com/shots.

  • Click the first photo
  • Click next (right arrow), this will take you to the next popular photo

Now go to here https://dribbble.com/joedawsonnn

  • Click the first photo
  • Click next (right arrow), this will now take you to my next photo on my profile

This is very similar to what I want to achieve.

thomaskim's avatar
Level 41

@JoeDawson I think I understand what you're saying now. If they clicked on a photo while on the popular page, why don't you send them to a different route like photos/popular/123? Dribble does something similar. From there, you can specifically query based on popularity and do something similar to what you did before. You just need to use the having() method. Obviously, you would need to refactor this and you can probably clean it up, but here's quick fix based on your query.

// Get photo based on the id passed in URL
$mainPhoto = Photo::leftJoin('views','photos.id','=','views.photo_id')->
    selectRaw('photos.*, count(views.photo_id) AS `count`')->
    groupBy('photos.id')->
    orderBy('count','DESC')->
    where('photos.id', '=', $id)->
    first();

// nextPhoto is the first photo with less views than the mainPhoto
$nextPhoto = Photo::leftJoin('views','photos.id','=','views.photo_id')->
    selectRaw('photos.*, count(views.photo_id) AS `count`')->
    groupBy('photos.id')->
    orderBy('count','DESC')->
    having('count', '<', $mainPhoto->count)->
    first();

// prevPhoto is the first photo with more views than the mainPhoto
$prevPhoto = Photo::leftJoin('views','photos.id','=','views.photo_id')->
    selectRaw('photos.*, count(views.photo_id) AS `count`')->
    groupBy('photos.id')->
    orderBy('count','DESC')->
    having('count', '>', $mainPhoto->count)->
    first();

Another option is to save the list of photos from your pagination query (18 photos based on your query). You save and paginate through that list. Once you reach the end of that list, you query the pagination for the next page and cycle through that.

1 like

Please or to participate in this conversation.