Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pagination #103

Closed
jimafisk opened this issue Dec 24, 2020 · 8 comments · Fixed by #105
Closed

Pagination #103

jimafisk opened this issue Dec 24, 2020 · 8 comments · Fixed by #105
Labels
enhancement New feature or request

Comments

@jimafisk
Copy link
Member

There are some Svelte pagination packages you could pull into your project, but swyx makes a good point in the first comment about the issues of loading all pages clientside: https://www.reddit.com/r/sveltejs/comments/did1fr/sveltepaginate_paginate_your_data_in_no_time/. This brings up good points about how we're bundling all page content in the first place, but that's an optimization for a different thread.

Another issue is that if you simply looped through allContent (and possibly filtered it) you would not get static html fallbacks for pages beyond the first landing page. Plenti at the moment doesn't know how to SSR those subsequent sections of a list to display additional items. Hugo has a custom implementation for something like this https://gohugo.io/templates/pagination/

I'd like to keep the scaffolding minimal and give folks the ability to do their own custom pagination. I'm thinking it would be best to extend the routing overrides in plenti.json to allow something like this:

{
  "types": {             
    "blog": "/blog/:paginate(content.type == 'blog', 10)"
  }
}

Where the first argument is the filter and the second is the number of items per page (e.g. if there are 50 total items and you pass 10 as the number of items per page, then there will be 5 total pages). The filters should be able to be combine like content.type == 'blog' && content.fields.publish == true. That would allow me write some logic behind the scenes in cmd/build/data_source.go to create new public/spa/ejected/content.js entries for the same content source but with an incremented path, like:

  • /blog
  • /blog/1
  • /blog/2
  • /blog/3
  • /blog/4
  • /blog/5

The remaining task would be setting the current page value in the svelte template. You could get this from the URL, but we would want it during SSR as well in order to set up each HTML fallback page with the correct values. We could extend the API above ^ to include an argument for the variable name that's used to represent the current page from the Svelte template... it does seem like we're getting awfully close to forcing the user to duplicate their pagination logic for the server and the browser though. Maybe there's a simpler / more elegant way to do this?

@jimafisk
Copy link
Member Author

jimafisk commented Dec 25, 2020

With the API above, behind the scenes in cmd/build/data_source.go we could write additional entries in public/spa/ejected/content.js. Just copy the original entry but adjust the path and add a key for pager (the rest of the fields and values would remain the same):

{
  "path": "/blog/3",
  "pager": 3,
  "type": "blog",
  "filename": "blog-post-3.json",
  "fields": {
    "title": "Post 3",
    "body": "Lorem ipsum..",
    "author": "Jim Fisk",
    "date": "12/24/2020"
  }
}

Then in the Svelte templates you would just initialize your currentPage variable with the pager key from the content source for SSR, which would look something like this:

<script>
  export let content, allContent; // these are magic helper variables from Plenti.
  let currentPage = content.pager; // get the new "pager" key from the content source.
  let postsPerPage = 10;
  let allPosts = allContent.filter(content => content.type == "blog");
  let totalPosts = allPosts.length;
  let totalPages = Math.ceil(totalPosts / postsPerPage);
  $: postRangeHigh = currentPage * postsPerPage;
  $: postRangeLow = postRangeHigh - postsPerPage;
  const setCurrentPage = newPage => {
    currentPage = newPage;
  }
</script>

{#each allPosts as post, i}
  {#if i >= postRangeLow && i < postRangeHigh}
    <h3>{post.title}</h3>
    <div>{post.body}</div>
  {/if}
{/each}

<ul>
  {#if currentPage > 1}
    <li><a href="/blog" on:click={() => setCurrentPage(1)}>first</a></li>
    <li><a href="/blog/{currentPage - 1}" on:click={() => setCurrentPage(currentPage - 1)}>previous</a></li>
  {/if}
  {#each [3,2,1] as i}
    {#if currentPage - i > 0}
      <li><a href="/blog/{currentPage - i}" on:click={() => setCurrentPage(currentPage - i)}>{currentPage - i}</a></li>
    {/if}
  {/each}
  <li><span>{currentPage}</span></li>
  {#each Array(3) as _, i}
    {#if currentPage + (i+1) <= totalPages}
      <li><a href="/blog/{currentPage + (i+1)}" on:click={() => setCurrentPage(currentPage + (i+1))}>{currentPage + (i+1)}</a></li>
    {/if}
  {/each}
  {#if currentPage < totalPages}
    <li><a href="/blog/{currentPage + 1}" on:click={() => setCurrentPage(currentPage + 1)}>next</a></li>
    <li><a href="/blog/{totalPages}" on:click={() => setCurrentPage(totalPages)}>last</li>
  {/if}
</ul>

Here's a Svelte REPL to show what this looks like in action: https://svelte.dev/repl/877286c4f73e419fba262538a727a5ad?version=3.31.0 (I had to use on:click|preventDefault because there's no client router on the REPL)

@jimafisk jimafisk added the enhancement New feature or request label Dec 27, 2020
@jimafisk
Copy link
Member Author

I would like to simplify the route override api to simply reference the variable name from the svelte template that holds the value for the total number of pages instead of recalculating it. That way we're not duplicating this logic in the plenti.config file and the svelte template. I'm not sure if it would actually be feasible to pull this value out of the v8go ctx, but if it's possible the example above would be rewritten like this:

{
  "types": {             
    "blog": "/blog/:paginate(totalPages)"
  }
}

@jimafisk
Copy link
Member Author

jimafisk commented Dec 28, 2020

I wonder if we also need to account for multiple pagers on the same page? I don't think it's a common use case but I see a scenario where someone might want this. For example, if you had two tables of information on the same page, you could be on the 2nd paged output for one, and the 5th for the other. Let's pretend we have a "cats" content type and a "dogs" content type each with 100 content files. If we had an "animals" page with a cats table that displays 10 entries at a time and a dogs table that does the same, we would probably want server fallbacks for each combination (10^2 or 100 unique pages for the "animals" landing page). The route override in plenti.json could look like:

{
  "types": {             
    "blog": "/animals/cats/:paginate(totalCats)/dogs/:paginate(totalDogs)"
  }
}

Which would produce URLs that could look like /animals/cats/2/dogs/5 for the second page in the cats table and the fifth page in the dogs table.

We would have to update the /public/spa/ejected/content.js values that are written behind the scenes so the pager appears like "pager": [2, 5], (that particular static HTML page is on the 2nd page for the cats table and the 5th page for the dogs table) and you would get values out of it in your template like let currentCatPage = content.pager[0]; let currentDogPage = content.pager[1];. We could allow named pagers, but that's just one more thing you would need to worry about setting in the api. I'm leaning towards it just being easier to have the first value in the pager array corresponding to the first replacement pattern in the routes override (2nd value in pager is for the 2nd :paginate() replacement, and so forth).

@jimafisk
Copy link
Member Author

I'm thinking the paginated output can be omitted from allContentStr in cmd/build/data_source.go which is ultimately put into the allContent magic prop used in the templates. If we included it, you'd essentially be getting the same base page information a bunch of times unless you were pulling relationships from the included items on a paginated basis, but that would be pretty complex and not very common imo.

@jimafisk
Copy link
Member Author

SSR Output after making global var
var plenti_global_pager_totalPages;
/* generated by Svelte v3.29.4 */
/*import { create_ssr_component, each, escape } from "svelte/internal";*/

let postsPerPage = 2;

var layout_content_pager_svelte = create_ssr_component(($$result, $$props, $$bindings, slots) => {
	let allContent = ["hi", "bye", "lie", "tie", "fry", "cry", "pie", "my", "guy"];
	let currentPage = 3; // Update this to simulate page change.
	let allPosts = allContent;
	let totalPosts = allPosts.length;
	let totalPages = Math.ceil(totalPosts / postsPerPage);
plenti_global_pager_totalPages = totalPages;

	const setCurrentPage = newPage => {
		currentPage = newPage;
	};

	let postRangeHigh;
	let postRangeLow;
	postRangeHigh = currentPage * postsPerPage;
	postRangeLow = postRangeHigh - postsPerPage;

	return `${each(allPosts, (post, i) => `${i >= postRangeLow && i < postRangeHigh
	? `<h3>${escape(post)}</h3>`
	: ``}`)}

<ul>${currentPage > 1
	? `<li><a href="${"/blog"}">first</a></li>
	  <li><a href="${"/blog/" + escape(currentPage - 1)}">previous</a></li>`
	: ``}
  ${each([3, 2, 1], i => `${currentPage - i > 0
	? `<li><a href="${"/blog/" + escape(currentPage - i)}">${escape(currentPage - i)}</a></li>`
	: ``}`)}
  <li><span>${escape(currentPage)}</span></li>
  ${each(Array(3), (_, i) => `${currentPage + (i + 1) <= totalPages
	? `<li><a href="${"/blog/" + escape(currentPage + (i + 1))}">${escape(currentPage + (i + 1))}</a></li>`
	: ``}`)}
  ${currentPage < totalPages
	? `<li><a href="${"/blog/" + escape(currentPage + 1)}">next</a></li>
    <li><a href="${"/blog/" + escape(totalPages)}">last</a></li>`
	: ``}</ul>`;
});

/*export default Component;*/

@jimafisk
Copy link
Member Author

jimafisk commented Dec 31, 2020

Here's a working example of pagination with static fallbacks in plenti: https://github.com/jimafisk/plenti_pager_example. It's better to work from this than the code snippets I wrote in this ticket which may have errors.

See gif

output

@jimafisk
Copy link
Member Author

jimafisk commented Jan 5, 2021

Visualization of incrementing multiple pagers recursively

IMG_20210105_143058576

@jimafisk
Copy link
Member Author

jimafisk commented Jan 5, 2021

Example with multiple pagers on the same node: https://github.com/jimafisk/plenti_multi-pager_example

See gif

output

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant