Skip to content

Instantly share code, notes, and snippets.

@zspencer
Created June 23, 2012 17:17
Show Gist options
  • Save zspencer/2979078 to your computer and use it in GitHub Desktop.
Save zspencer/2979078 to your computer and use it in GitHub Desktop.
A step by step refactoring where I separate a jquery plugin into pieces with reduced responsibilities.
/*
This first iteration only paginated an initial set of pages. Adding or removing pages without
reinitializing the plugin would stay with the initial set.
Everything was encapsulated within a single jQuery plugin which knew everything about pagination:
* buttons/etc for moving around pages
* jumping to a page
* animating moving to a new page
* where in the dom the page numbers were stored
* updating the ui based upon which page you scrolled to
*/
$.fn.extend({
paginate: function() {
var $self = $(this);
var currentPage;
var pages = function() {
return $self.find('.page');
}
var init = function() {
currentPage = pages().first().data('page');
pages().waypoint(function() {
currentPage = $(this).data('page');
$('#jump-pages').val(currentPage);
});
$("#jump-pages").html('');
_.each(pages(), function(page) {
var p = $(page).data('page');
$('#jump-pages').append('<option value="' + p + '">' + p + '</option>');
});
}
var scrollToPage = function(pageCount) {
var ps = _.map(pages(), function(p) { return $(p).data('page') });
currentPage = ps[_.indexOf(ps, currentPage) + pageCount] || currentPage;
var offset = pageCount < 0 ? -10 : 10;
$('html, body').animate({ scrollTop: $('.page[data-page="'+ currentPage + '"]').offset().top + offset });
}
$('#next-page').on('click', function() {
scrollToPage(+1);
});
$('#jump-pages').on('change', function() {
scrollToPage($(this).val() - currentPage);
});
$('#previous-page').on('click', function() {
scrollToPage(-1);
});
init();
return { refresh: init }
}
});
$.fn.extend({
rendersPages: function(pages) {
var paginator = Paginator({
pageNumber: $('#jump-pages'),
previous: $('#previous-page'),
next: $('#next-page'),
}, $(this));
var pageTemplate = {
page: _.template($('#page-template').html())
}
pages.bind('pageLoaded', function(p) {
var newPage = $(pageTemplate.page(p));
$this.append(newPage);
paginator.paginate();
});
}
});
(function() {
window.Paginator = function(controls, $pagesContainer) {
var pages = function() {
return $pagesContainer.find('.page');
}
var currentPage = pages().first().data('page');
controls.next.on('click', function() {
scrollToPage(+1);
});
controls.pageNumber.on('change', function() {
scrollToPage($(this).val() - currentPage);
});
controls.previous.on('click', function() {
scrollToPage(-1);
});
var paginate = function() {
pages().waypoint(function() {
currentPage = $(this).data('page');
controls.pageNumber.val(currentPage);
});
controls.pageNumber.html('');
_.each(pages(), function(page) {
var p = $(page).data('page');
controls.pageNumber.append('<option value="' + p + '">' + p + '</option>');
});
}
var scrollToPage = function(pageCount) {
var ps = _.map(pages(), function(p) { return $(p).data('page') });
var nextPage = ps[_.indexOf(ps, currentPage) + pageCount] || currentPage;
$('html, body').animate({ scrollTop: $('.page[data-page="'+nextPage + '"]').offset().top + pageCount });
}
return { paginate: paginate }
}
})();
$.fn.extend({
rendersPages: function(pages) {
$pages = $(this);
var paginator = $pages.paginate();
pages.bind('pageLoaded', function(p) {
var newPage = $(pageTemplate.page(p));
$pages.append(newPage);
paginator.refresh();
});
}
});
$.fn.extend({
paginate: function() {
var $pages = (function($this) { return function() {
return $this.find('.page');
} })($(this))
var convertDomPagesToArray = function() {
return _.map($pages(), function(p) { return '' + $(p).data('page'); });
}
var paginator = Paginator($('#jump-pages').val(), convertDomPagesToArray());
paginator.refresh = function() {
paginator.paginate($('#jump-pages').val(), convertDomPagesToArray());
$("#jump-pages").html('');
_.each(pages, function(p) {
$('#jump-pages').append('<option value="' + p + '">' + p + '</option>');
});
$pages().waypoint(function() {
var currentPage = $(this).data('page');
paginator.updateCurrentPage(currentPage);
$('#jump-pages').val(currentPage)
});
}
$('#previous-page').on('click', paginator.previous);
$('#next-page').on('click', paginator.next);
$('#jump-pages').on('change', function() {
paginator.jumpTo($(this).val());
});
paginator.on('pageChanged', function(p, difference) {
var offset = difference < 0 ? -1 : 1
$('html, body').animate({ scrollTop: $('#page-' + p).offset().top + offset});
})
return paginator;
}
});
(function(exports) {
exports.Paginator = function(currentPage, pages) {
var paginator = {};
asEvented.call(paginator);
paginator.previous = function() {
scrollToPage(-1);
};
paginator.next = function() {
scrollToPage(+1);
}
paginator.jumpTo = function(newPage) {
scrollToPage(_.indexOf(pages, newPage) - _.indexOf(pages, currentPage));
}
var paginate = paginator.paginate = function(newCurrentPage, newPageSet) {
pages = newPageSet;
updateCurrentPage(newCurrentPage);
}
var updateCurrentPage = paginator.updateCurrentPage = function(newCurrentPage) {
currentPage = newCurrentPage;
}
var scrollToPage = function(pageCount) {
updateCurrentPage(pages[_.indexOf(pages, currentPage) + pageCount] || currentPage);
paginator.trigger('pageChanged', currentPage, pageCount);
}
return paginator
}
})(window);
@benjaminws
Copy link

When scanning the first example, it's easier for me to devise what's going on. Also, I like the idea of having all the knowledge required to paginate encapsulated in the single plugin. That, in itself, is a form of separating responsibility by keeping that knowledge out of the presentation layer.

On the last example, the first thought I had was; Why would you need to reuse the Paginator object itself outside the context of the plugin? You've named the dom attributes required to bootstrap the paginator generic enough that the plugin itself is quite reusable. I know now why you did it (SoC), but I don't think it's worth the extra code and I think exposing another interface is confusing.

I'm tending to agree with your final assertion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment