Guide tip: How to make section pages without pagination

6 Comments

  • Dave Dyson
    Zendesk Community Manager

    Wow, thanks for posting this tip, Ashleigh!

    0
  • Ashleigh Rentz

    My original post saved for posterity, but you're almost certainly better off using the revised version above.

    ---

    If you haven't deviated from the base Copenhagen theme too much, the 'section_page.hbs' and 'category_page.hbs' files include SVG tags to draw the padlock and star icons. I didn't want to hard-code those into the script, so I removed them from the templates and uploaded them as separate files in the 'assets' folder. Now we can apply them via 'style.css'. I also display the non-paginated list in columns if the user's viewport is wide enough.

    .article-promoted::after, .article-internal::after {
    content: '';
    background-size: 100% 100%;
    display: inline-block;
    height: 0.8em;
    width: 0.8em;
    padding-left: 1em;
    }
    .article-promoted::after {
    background-image: url($assets-promoted-article-svg);
    }
    .article-internal::after {
    background-image: url($assets-internal-article-svg);
    }
    @media (min-width: 768px) {
    .long-article-list {
    columns: 2;
    }
    }
    @media (min-width: 1024px) {
    .long-article-list {
    columns: 3;
    }
    }

    Next, we need a 'div' in 'section_page.hbs' that we can overwrite. Leave the pagination helper in place as a safe fallback.

    <div id="section-page-articles-list">
    {{! If more then 30 articles, JS should overwrite this div }}
    {{#if section.articles}}
    <ul class="article-list">
    {{#each section.articles}}
    <li class="article-list-item
    {{#if promoted}} article-promoted{{/if}}
    {{#if internal}} article-internal{{/if}}">
    <a href="{{url}}" class="article-list-link">{{title}}
    </a></li>
    {{/each}}
    </ul>
    {{else}}
    <i class="section-empty">
    <a href="{{section.url}}">{{t 'empty'}}</a></i>
    {{/if}}
    {{pagination "section.articles"}}
    </div>

    And finally, here's the Javascript:

    // Zendesk Guide section pages without pagination.
    // author: Ashleigh Rentz, koresoftware.com
    // license: https://mit-license.org/
    // Append to script.js

    window.onload = function() {
    unpaginatedSections();
    };

    function unpaginatedSections() {

    // Only execute this if we're on a paginated section page.
    if (document.getElementsByClassName("pagination")[0] == null) return;
    const parsedUrlArray = parseSectionUrl(new URL(window.location.href));
    if (parsedUrlArray == null) return;

    // Define API URLs and parameters.
    const hc_host = parsedUrlArray[0];
    const hc_locale = parsedUrlArray[1];
    const section_id = parsedUrlArray[2];
    const per_page = 50; // Default 30. Higher means fewer XHRs but larger JSON
    // objects; they contain the full body of each article.
    const api_usersegs_url = 'https://' + hc_host + '/api/v2/help_center/user_segments';
    const api_articles_url = 'https://' + hc_host + '/api/v2/help_center/' + hc_locale +
    '/sections/' + section_id + '/articles?per_page=' + per_page;

    // These vars need to be accessed from multiple function calls.
    var long_list = '<ul class="article-list long-article-list">';
    var internalUserSegsArray = [];

    // The 'articles' object doesn't tell us which ones are internal-only, so we
    // need a list of 'staff' user segments to figure it out. Note that if you
    // have over 100 user segments, this won't check all of them.
    fetch(api_usersegs_url)
    .then(function(response) {
    if (response.status !== 200) throw new Error();
    return response.json();
    })
    .then(
    function(responseJson) {
    for (let i=0; i < responseJson.count; i++) {
    if (responseJson.user_segments[i].user_type == 'staff')
    internalUserSegsArray.push(responseJson.user_segments[i].id);
    };
    }
    )
    .then(function() {
    // Got the user segments list, now we can get the articles and
    // create a new list.
    var articles_json = new XMLHttpRequest();
    articles_json.open("GET", api_articles_url);
    articles_json.responseType = 'json';
    articles_json.send();
    articles_json.onload = function() {
    // If request for articles failed, ignore and leave pagination in place.
    if (articles_json.status !== 200) return;
    // Add article titles and URLs to the new section page list.
    long_list = long_list + addArticlesToList(articles_json.response['articles']);
    if (articles_json.response.page < articles_json.response.page_count) {
    // If those weren't all the articles, send another request which
    // will call this same listener function again.
    var next_url = articles_json.response.next_page;
    articles_json.open("GET", next_url);
    articles_json.send();
    }
    else {
    // Close out the list and write it to the page.
    long_list += '</ul>';
    document.getElementById("section-page-articles-list").innerHTML = long_list;
    };
    return;
    };
    })
    .catch(function(e) { // API call for user segments failed;
    }); // ignore and leave pagination in place.


    function parseSectionUrl(sectionUrl) {
    const urlArray = sectionUrl.pathname.toLowerCase().match(/\/hc\/.+?\/sections\/[0-9]+/);
    if (urlArray !== null) {
    // This is a section page.
    const splitString = urlArray[0].split("/");
    const locale = splitString[2];
    const id = splitString[4].split("-")[0]; // Remove the text description
    // after the ID, if present.
    return [sectionUrl.hostname, locale, id];
    } else return null; // This isn't a section page.
    };


    function addArticlesToList(articles_array) {
    var list_markup = '';
    for (let i=0; i < articles_array.length; i++) {
    if (articles_array[i].draft) continue; // Don't add drafts to list.
    list_markup += '<li class="article-list-item';
    // Use these two classes to apply icons (e.g. star and padlock) via CSS.
    if (articles_array[i].promoted) list_markup += ' article-promoted';
    if (internalUserSegsArray.includes(articles_array[i].user_segment_id))
    list_markup += ' article-internal';
    list_markup += '"><a href="' + articles_array[i].html_url +
    '" class="article-list-link">' + articles_array[i].title + '</a></li>';
    };
    return list_markup;
    };
    };

    I'm not a developer by trade so this could surely be improved, but hopefully it's useful to someone out there. Cheers!

    0
  • Bruce Michelsen

    In using this, due to how the code block wrapped lines above, I removed spaces and returns in several lines. For example, I changed this:

    var next_url = document.getElementsByClassName("pagination-next-link")[0]
    .getAttribute('href');

    ... to this:

    var next_url = document.getElementsByClassName("pagination-next-link")[0].getAttribute('href');

    I'm seeing an issue with getting the links in the pagination pages.

    • Using pagination-next-link, I get an error: Uncaught TypeError: Cannot read properties of undefined (reading 'getAttribute')
    • When I inspect the element, I see the class is "pagination-next" rather than "pagination-next-link". (Perhaps this is due to my using an older template.)
    • Using pagination-next for the next_url in the document and in the response, I get a 404 error that next_url is null: https://myhostname/hc/en-us/sections/null

    Is the problem that getElementsByClassName has to look at an element within the link tag?

    When inspecting the page, I see the following where the class is in the li.

    <nav class="pagination">
    <ul>
    <li class="pagination-current">
    <span>1</span>
    </li>
    <li>
    <a href="/hc/en-us/sections/360005465593-Sample?page=2#articles" rel="next nofollow">2</a>
    </li>
    <li class="pagination-next">
    <a href="/hc/en-us/sections/360005465593-Sample?page=2#articles" rel="next nofollow">›</a>
    </li>
    <li class="pagination-last">
    <a href="/hc/en-us/sections/360005465593-Sample?page=2#articles" rel="nofollow">»</a>
    </li>
    </ul>
    </nav>

    If I use the following, then I can get the href out of the a tag within the li with class="pagination-next" for the document:

    var list = document.getElementsByClassName("pagination-next")[0];
    var next_url = list.getElementsByTagName("a")[0].getAttribute('href');

    ... and for the response:

    var nextlink = next_page.response.getElementsByClassName("pagination-next")[0];
    var next_url = nextlink.getElementsByTagName("a")[0].getAttribute('href');
    1
  • Ashleigh Rentz

    Hi Bruce - The line breaks are all intentional, JavaScript allows that and I wanted to keep it as human-readable as possible. But there's no reason not to change it, either.  :)

    I think you're right about this being a versioning issue—it looks like you're still on Templating API v1? I wrote this script based on Copenhagen 2.9.0 and Templating API v2, and I see that the 'pagination' helper was indeed updated between the two.

    When inspecting my page, here's what the nav block looks like:

    <nav class="pagination">
      <ul class="pagination-list">
          <li class="pagination-next">
            <a href="/hc/en-us/sections/123456789012-Sample?page=2#articles" class="pagination-next-link" rel="next nofollow">
              <span class="pagination-next-text">Next</span>
              <span class="pagination-next-icon" aria-hidden="true">›</span>
          </a>
          </li>
          <li class="pagination-last">
            <a href="/hc/en-us/sections/123456789012-Sample?page=3#articles" class="pagination-last-link" rel="nofollow">
              <span class="pagination-last-text">Last</span>
              <span class="pagination-last-icon" aria-hidden="true">»</span>
            </a>
          </li>
      </ul>
    </nav>

    So my script failed on you because your 'pagination' helper doesn't assign a class to the link itself, only the list item. There might be other differences too, but it looks to me like you're on the right path for adapting this to the v1 API. Do you have it working?

    0
  • Bruce Michelsen

    Ashleigh,

    • You're right about the wrapping.
      My editor was complaining unnecessarily or I had made some syntax error that I fixed in tightening this up.
    • Good to see that your script would work if I were using the latest version.
    • I did get this working using my workaround.

    Thanks.

    0
  • Trevor Smith

    Ashleigh,

    Thank you so much for this great workaround! I've been looking for a solution to the 30 article limit for some time. 

    Bruce - are you able to share your complete example? I am also on V1, I was able to get the articles on the second page to show, along with removing the pagination - however, every time it executes, it seems to be overwriting the current article list on the first page. In place of the first-page list, I'm getting '[object HTMLLIElement] ' displayed on the page.

    I could technically assign the 'new' list to a different div, but I'd rather append the original div to create one entire list so I can filter through it. 

    **EDIT - I figured it out - had to change "+=" to just +  in this:

    the_list = next_page.response.getElementsByClassName("section-tree")[0].innerHTML;

    and had to change "=" to "+="  in this:

     // Write the new list to the current page.
      
            document.getElementsByClassName("section-tree")[0].innerHTML += the_list;

     

    Here is the full script that now works for me on V1...

     // Zendesk Guide section pages without pagination (v2).
    // author: Ashleigh Rentz, koresoftware.com
    // license: https://mit-license.org/

    window.onload = function() {
      unpaginatedSections();
    };

    function unpaginatedSections() {
    debugger;
      // Only execute this if we're on a paginated section page.
      if (document.getElementsByClassName("pagination")[0] == null) return;


      // Start with the articles listed on the first page

    var the_list = document.getElementsByClassName("pagination-next")[0];
    var next_url = the_list.getElementsByTagName("a")[0].getAttribute('href'); 




      // Request the next page
      var next_page = new XMLHttpRequest();
      next_page.open("GET", next_url);
      next_page.responseType = 'document';
      next_page.send();
      next_page.onload = function() {
        // If something went wrong, leave pagination in place.
        if (next_page.status !== 200) return;
        // Add the articles from this page of results to our list.
        the_list = next_page.response.getElementsByClassName("section-tree")[0].innerHTML;
        // Check for another page.
        try {
        var nextlink = next_page.response.getElementsByClassName("pagination-next")[0];
        next_url = nextlink.getElementsByTagName("a")[0].getAttribute('href'); 
      
      

        }
        catch (e) {
          // No more pages.
          is_finished = true;
        }
        finally {
          if (is_finished) {
            // Write the new list to the current page.
      
            document.getElementsByClassName("section-tree")[0].innerHTML += the_list;
                  
            // Add a class to allow styling via CSS.

            // Remove the pagination controls.
          
                   document.getElementsByClassName("pagination")[0].innerHTML = '';

      
          };
        };
      };
    };

     

    Best,

    Trevor

     

    0

Please sign in to leave a comment.

Powered by Zendesk