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

Enabling progressive loading of index page content #3485

Merged
merged 15 commits into from Aug 21, 2019
1 change: 1 addition & 0 deletions network-api/networkapi/settings.py
Expand Up @@ -156,6 +156,7 @@
'wagtail.core',
'wagtail.contrib.forms',
'wagtail.contrib.redirects',
'wagtail.contrib.routable_page',
'wagtail.contrib.styleguide' if DEBUG else None,
'wagtail.contrib.modeladmin',
'experiments',
Expand Down
@@ -0,0 +1,18 @@
# Generated by Django 2.2.3 on 2019-08-07 18:59

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('wagtailpages', '0071_indexpage'),
]

operations = [
migrations.AddField(
model_name='indexpage',
name='page_size',
field=models.IntegerField(choices=[(4, '4'), (8, '8'), (12, '12'), (24, '24')], default=12, help_text='The number of entries to show by default, and per incremental load'),
),
]
70 changes: 58 additions & 12 deletions network-api/networkapi/wagtailpages/models.py
Expand Up @@ -2,10 +2,13 @@

from django.db import models
from django.conf import settings
from django.http import HttpResponseRedirect
from django.http import HttpResponseRedirect, JsonResponse
from django.template import loader

from taggit.models import Tag

from . import customblocks
from wagtail.contrib.routable_page.models import RoutablePageMixin, route
from wagtail.core import blocks
from wagtail.core.models import Page
from wagtail.core.fields import StreamField, RichTextField
Expand Down Expand Up @@ -532,7 +535,7 @@ def get_context(self, request):
return get_page_tree_information(self, context)


class IndexPage(FoundationMetadataPageMixin, Page):
class IndexPage(FoundationMetadataPageMixin, RoutablePageMixin, Page):
"""
This is a page type for creating "index" pages that
can show cards for all their child content.
Expand All @@ -551,18 +554,67 @@ class IndexPage(FoundationMetadataPageMixin, Page):
help_text='Intro paragraph to show in hero cutout box'
)

DEFAULT_PAGE_SIZE = 12

PAGE_SIZES = (
(4, '4'),
(8, '8'),
(DEFAULT_PAGE_SIZE, str(DEFAULT_PAGE_SIZE)),
(24, '24'),
)

page_size = models.IntegerField(
choices=PAGE_SIZES,
default=DEFAULT_PAGE_SIZE,
help_text='The number of entries to show by default, and per incremental load'
)

content_panels = Page.content_panels + [
FieldPanel('header'),
FieldPanel('intro'),
FieldPanel('page_size'),
]

def get_entries(self):
return self.get_children().live().order_by('-first_published_at')

def get_context(self, request):
context = super().get_context(request)
context = set_main_site_nav_information(self, context, 'Homepage')
context = get_page_tree_information(self, context)
context['entries'] = self.get_children().live().order_by('-first_published_at')
context['entries'] = self.get_entries()[0:self.page_size]
return context

@route('entries')
def generate_entries_set_html(self, request, *args, **kwargs):
"""
Get a set of entries, as rendered HTML
"""

page = 1

if 'page' in request.GET:
page = int(request.GET['page'])

page_size = self.page_size

if 'page_size' in request.GET:
page_size = int(request.GET['page_size'])

start = page * page_size
end = start + page_size
entries = self.get_entries()

return JsonResponse({
'entries_html': loader.render_to_string(
'wagtailpages/fragments/entry_cards.html',
{
'entries': entries[start:end]
}
),
'has_next': end < len(entries)
})


class NewsPage(PrimaryPage):
parent_page_types = ['Homepage']
Expand All @@ -574,7 +626,6 @@ class BlogPageTag(TaggedItemBase):


class BlogPage(FoundationMetadataPageMixin, Page):
template = 'wagtailpages/blog_page.html'
Pomax marked this conversation as resolved.
Show resolved Hide resolved

author = models.CharField(
verbose_name='Author',
Expand All @@ -600,24 +651,19 @@ class BlogPage(FoundationMetadataPageMixin, Page):
('quote', customblocks.QuoteBlock()),
])

# Editor panels configuration
tags = ClusterTaggableManager(through=BlogPageTag, blank=True)

zen_nav = True

content_panels = Page.content_panels + [
FieldPanel('author'),
StreamFieldPanel('body'),
]

# Promote panels configuration
tags = ClusterTaggableManager(through=BlogPageTag, blank=True)

promote_panels = FoundationMetadataPageMixin.promote_panels + [
FieldPanel('tags'),
]

# Database fields

zen_nav = True

def get_context(self, request):
context = super().get_context(request)
context['related_posts'] = get_content_related_by_tag(self)
Expand Down
@@ -0,0 +1,9 @@
{% for entry in entries %}
{% with type=entry.specific_class.get_verbose_name|lower %}
{% if type == "blog page" %}
{% include "./blog-card.html" with page=entry %}
{% else %}
{% include "./generic-card.html" with page=entry %}
{% endif %}
{% endwith %}
{% endfor %}
Expand Up @@ -20,20 +20,20 @@ <h1 class="h1-heading mb-0 mt-1 pt-2">
</div>
</div>

<div class="row">
<div class="index-entries row">
{% block subcontent %}

{% for entry in entries %}
{% with type=entry.specific_class.get_verbose_name|lower %}
{% if type == "blog page" %}
{% include "./fragments/blog-card.html" with page=entry %}
{% else %}
{% include "./fragments/generic-card.html" with page=entry %}
{% endif %}
{% endwith %}
{% endfor %}

{% include "./fragments/entry_cards.html" %}
{% endblock %}
</div>

<div class="row">
<div class="col-12 text-center mb-5">
<button class="btn btn-primary load-more-index-entries" data-page-size="{{ page.specific.page_size }}" >
Load more results
</button>

{# See main.js for the javascript that hooks into this button #}
</div>
</div>
</div>
{% endblock %}
43 changes: 43 additions & 0 deletions source/js/main.js
Expand Up @@ -520,6 +520,49 @@ let main = {

injectDonateModal(donationModal, modalOptions);
}

// Enable the "load more results" button on index pages
let loadMoreButton = document.querySelector(`.load-more-index-entries`);
if (loadMoreButton) {
const entries = document.querySelector(`.index-entries`);

// Get the page size from the document, which the IndexPage should
// have templated into its button as a data-page-size attribute.
const pageSize = parseInt(loadMoreButton.dataset.pageSize) || 12;

// Start at page 1, as page 0 is the same sat as the initial page set.
let page = 1;

const loadMoreResults = evt => {
// Construct our API call as a relative URL:
let url = `./entries/?page=${page++}&page_size=${pageSize}`;

// And then fetch the results and render them into the page.
fetch(url)
mmmavis marked this conversation as resolved.
Show resolved Hide resolved
.then(result => result.json())
.then(data => {
if (!data.has_next) {
loadMoreButton.removeEventListener(`click`, loadMoreResults)
loadMoreButton.parentNode.removeChild(loadMoreButton);
}
return data.entries_html;
})
.then(entries_html => {
const div = document.createElement(`div`);
div.innerHTML = entries_html;

Array.from(div.children).forEach(child =>
entries.appendChild(child)
);
})
Pomax marked this conversation as resolved.
Show resolved Hide resolved
.catch(err => {
// TODO: what do we want to do in this case?
console.error(err);
});
}

loadMoreButton.addEventListener(`click`, loadMoreResults);
}
}
};

Expand Down