Skip to content

Instantly share code, notes, and snippets.

@ErikOnBike
Last active September 30, 2018 13:40
Show Gist options
  • Save ErikOnBike/1eceb2300e2acad5fd10b7275502cc02 to your computer and use it in GitHub Desktop.
Save ErikOnBike/1eceb2300e2acad5fd10b7275502cc02 to your computer and use it in GitHub Desktop.
Pagination using render filters
license: gpl-3.0
height: 450
scrolling: yes

Be aware: This is a V1 example and will not work with the V2 (or newer) version of d3-template.

This example demonstrates pagination using custom filters in d3-template. The pagination controls look like media-player controls (first, previous, next and last).

This pagination example tries to remove as much of the dependency between (Javascript) code and the HTML as possible. The HTML contains references to other parts of the HTML allowing the code to find related items. The pagination control refers to the list being paginated using the data-list-id attribute. Likewise it refers to the template root using the data-template-id attribute. This allows the click-handler to render the correct page without explicitly binding the template from code.

<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font-family: "Helvetica Neue", Helvetica, sans-serif;
}
#first, #second {
float: left;
margin-left: 3em;
padding: 1em;
border: 1px solid #aaaaaa;
}
ul {
list-style: none;
}
img {
height: 3em;
}
.button {
display: inline-block;
border: 1px solid #aaaaaa;
border-radius: .4em;
width: 4em;
height: 2em;
line-height: 2em;
text-align: center;
cursor: pointer;
/* Prevent selection when clicking repeatedly */
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.page-number {
display: inline-block;
width: 3em;
text-align: center;
}
</style>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/d3-template-plugin@1.3.0/build/d3-template.min.js"></script>
<div id="first">
<h3>Contributors to the D3 repo (top 97)</h3>
<!-- Pagination controls -->
<div class="pagination-controls" data-list-id="contributors-list" data-template-id="first">
<span class="button first">&lt;&lt;</span>
<span class="button previous">&lt;</span>
<span class="page-number">{{.|pageNumber}}</span>
/
<span class="page-number">{{.|pageCount}}</span>
<span class="button next">&gt;</span>
<span class="button last">&gt;&gt;</span>
</div>
<!-- Paginated list -->
<ul id="contributors-list" data-start-index="0" data-page-size="5" data-repeat="{{.|paginate}}">
<li>
<img data-attr-src="{{avatar_url}}" alt="{{login}}">
<span>{{login}}</span>: <span>{{contributions}}</span> contributions
</li>
</ul>
</div>
<div id="second">
<h3>42 odd numbers</h3>
<!-- Pagination controls -->
<div class="pagination-controls" data-list-id="numbers-list" data-template-id="second">
<span class="button first">&lt;&lt;</span>
<span class="button previous">&lt;</span>
<span class="page-number">{{.|pageNumber}}</span>
/
<span class="page-number">{{.|pageCount}}</span>
<span class="button next">&gt;</span>
<span class="button last">&gt;&gt;</span>
</div>
<!-- Paginated list -->
<ul id="numbers-list" data-start-index="0" data-page-size="10" data-repeat="{{.|paginate}}">
<li>{{.}}</li>
</ul>
</div>
<script>
// Prepare pagination (has to be performed before templates are created)
// These preperations are generic for all paginated lists
// Create custom filters
d3.renderFilter("paginate", function(d) {
var listElement = d3.select(this);
var startIndex = +listElement.attr("data-start-index");
var pageSize = +listElement.attr("data-page-size");
return d.slice(startIndex, startIndex + pageSize);
});
d3.renderFilter("pageNumber", function(d) {
var listId = d3.select(this).selectAll(function() { return allParents(this); }).attr("data-list-id");
var listElement = d3.select("#" + listId);
var startIndex = +listElement.attr("data-start-index");
var pageSize = +listElement.attr("data-page-size");
return Math.floor(startIndex / pageSize) + 1;
});
d3.renderFilter("pageCount", function(d) {
var listId = d3.select(this).selectAll(function() { return allParents(this); }).attr("data-list-id");
var listElement = d3.select("#" + listId);
var pageSize = +listElement.attr("data-page-size");
return Math.floor((d.length + pageSize - 1) / pageSize);
});
// Add event handlers for page navigation
d3.selectAll(".pagination-controls .button").on("click", function(d) {
var button = d3.select(this);
var listId = button.selectAll(function() { return allParents(this); }).attr("data-list-id");
var listElement = d3.select("#" + listId);
var startIndex = +listElement.attr("data-start-index");
var pageSize = +listElement.attr("data-page-size");
var listLength = d.length;
// Calculate new start index
var newStartIndex = startIndex;
if(button.classed("first")) {
newStartIndex = 0;
} else if(button.classed("previous")) {
newStartIndex = Math.max(startIndex - pageSize, 0);
} else if(button.classed("next")) {
newStartIndex = Math.min(startIndex + pageSize, listLength);
} else if(button.classed("last")) {
newStartIndex = listLength;
}
newStartIndex = Math.floor(newStartIndex / pageSize) * pageSize; // Start on page boundary
// Render new page content
if(newStartIndex !== startIndex) {
listElement.attr("data-start-index", newStartIndex);
var templateId = button.selectAll(function() { return allParents(this); }).attr("data-template-id");
d3.select("#" + templateId).render(d);
}
});
// Create list of contributors
// See https://developer.github.com/v3/repos/#list-contributors
d3.json("https://api.github.com/repos/d3/d3/contributors?per_page=97", function(error, data) {
if(error) {
throw error;
}
// Create template and render data
d3.select("#first")
.template()
.render(data)
;
});
// Create list of odd numbers
var oddNumbers = new Array(42);
for(var i = 0; i < oddNumbers.length; i++) {
oddNumbers[i] = 1 + 2 * i;
}
d3.select("#second")
.template()
.render(oddNumbers)
;
// Helper function: Select all parent nodes
function allParents(node) {
var parents = [];
while(node) {
node = node.parentNode;
if(node) {
parents.push(node);
}
}
return parents;
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment