Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Initial commit of Paginator #19

Closed
wants to merge 3 commits into from

2 participants

@tivac

Contains Paginator-Base, a UI-less paginator that provides the core logic & Paginator, a currently mostly worthless UI layer on top of Paginator-Base. Assuming these pass muster we'll see about making Paginator more useful.

Would love to get any and all feedback on API decisions, source decisions, hairstyle decisions, you get the idea.

tivac added some commits
@tivac tivac Initial commit of paginator source
Contains Paginator-Base, a UI-less paginator that provides the core logic & Paginator, a currently mostly worthless UI layer on top of Paginator-Base. Assuming these pass muster we'll see about making Paginator more useful.
3025c5c
@tivac tivac Updates to reflect ls_n's feedback
Paginator Base -> Paginator Core
Initial pass at Infinity support
Stubbed out .first & .last
Updated tests to ensure more consistent output
Various other clean-up bits
bf705ac
@tivac tivac Support for .first & .last
New private method, _max()
Handle Infinity case more succinctly throughout
79754d1
@lsmith lsmith closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jan 6, 2012
  1. @tivac

    Initial commit of paginator source

    tivac authored
    Contains Paginator-Base, a UI-less paginator that provides the core logic & Paginator, a currently mostly worthless UI layer on top of Paginator-Base. Assuming these pass muster we'll see about making Paginator more useful.
Commits on Jan 13, 2012
  1. @tivac

    Updates to reflect ls_n's feedback

    tivac authored
    Paginator Base -> Paginator Core
    Initial pass at Infinity support
    Stubbed out .first & .last
    Updated tests to ensure more consistent output
    Various other clean-up bits
Commits on Jan 27, 2012
  1. @tivac

    Support for .first & .last

    tivac authored
    New private method, _max()
    Handle Infinity case more succinctly throughout
This page is out of date. Refresh to see the latest.
View
13 src/paginator/build.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- YUI 3 paginator Build File -->
+
+<project name="Paginator" default="local">
+ <description>Paginator Build File</description>
+
+ <target name="all">
+ <ant antfile="paginator-core.xml" target="all" />
+ <ant antfile="paginator.xml" target="all" />
+ </target>
+
+</project>
View
168 src/paginator/js/paginator-core.js
@@ -0,0 +1,168 @@
+ //aliases
+var L = Y.Lang,
+ INVALID_VALUE = Y.Attribute.INVALID_VALUE,
+ YAindexOf = Y.Array.indexOf,
+ ceil = Math.ceil,
+ floor = Math.floor,
+
+ //helpers
+ parse = function(value) {
+ var v = (value === Infinity) ? value : parseInt(value, 10);
+
+ return !isNaN(v) ? v : null;
+ },
+
+ //placeholders
+ Core;
+
+Core = function() {};
+
+Core.ATTRS = {
+ total : {
+ value : 0,
+ setter : "_setTotal"
+ },
+
+ per : {
+ value : 1,
+ setter : "_setPer"
+ },
+
+ offset : {
+ value : 0,
+ setter : "_setOffset"
+ },
+
+ page : {
+ setter : "_setPage"
+ },
+
+ details : {
+ value : {}
+ }
+};
+
+Y.mix(Core.prototype, {
+
+ //lifetime
+ initializer : function() {
+ this._handlers = this.after([
+ "perChange",
+ "totalChange",
+ "offsetChange",
+ "pageChange"
+ ], this._update, this);
+
+ this._update();
+ },
+
+ destructor : function() {
+ this._handlers.detach();
+ this._handlers = null;
+ },
+
+ //Public API
+ increment : function(step) {
+ var s = parse(step) || 1;
+
+ this.set("page", this.get("page") + s);
+ },
+
+ decrement : function(step) {
+ var s = parse(step) || 1;
+
+ this.set("page", this.get("page") - s);
+ },
+
+ first : function() {
+ this.set("page", 1);
+ },
+
+ last : function() {
+ this.set("page", this._max());
+ },
+
+ //Private
+ _update : function(e) {
+ //short-circuit internal updates
+ if(e && e.internal) {
+ return;
+ }
+
+ var o = this.getAttrs([ "per", "offset", "page" ]),
+ max = this._max(),
+ source = { internal : true },
+ offset, first, prev, current, next, last;
+
+ //sanity checks
+ isNaN(max) && (max = 0);
+
+ if(e && e.attrName === "offset") {
+ offset = o.offset
+ } else {
+ offset = o.page < 2 ? 0 : (o.page - 1 || 0) * o.per;
+ }
+ current = Math.floor((offset / o.per) + 1 || 1);
+
+ next = current + 1;
+ (next > max) && (next = false);
+
+ prev = current - 1;
+ (prev < 1) && (prev = false);
+
+ first = prev && 1;
+ last = next && max;
+
+ //sanity check current as a final step
+ (current * o.per > max) && (current = o.page);
+
+ //update attributes w/ what we calculated
+ this.set("details", {
+ page : current,
+ offset : offset,
+
+ first : first,
+ prev : prev,
+ next : next,
+ last : last
+ });
+
+ (current !== o.page) && this.set("page", current, source);
+ (offset !== o.offset) && this.set("offset", offset, source);
+ },
+
+ _max : function() {
+ var o = this.getAttrs([ "per", "total" ]);
+
+ //don't have to check for Infinity here, because Infinity divided by
+ //anything is still Infinity. Yes, even 0.
+ return ceil(o.total / o.per);
+ },
+
+ //Attribute-related fns
+ _setPer : function(value) {
+ var per = parse(value);
+
+ return per > 0 ? per : INVALID_VALUE;
+ },
+
+ _setTotal : function(value) {
+ var total = parse(value);
+
+ return total >= 0 ? total : INVALID_VALUE;
+ },
+
+ _setPage : function(value) {
+ var page = parse(value);
+
+ return (page > 0 && (page <= this._max())) ? page : INVALID_VALUE;
+ },
+
+ _setOffset : function(value) {
+ var offset = parse(value);
+
+ return (offset >= 0 && (offset <= this.get("total"))) ? offset : INVALID_VALUE;
+ }
+});
+
+Y.namespace("Paginator").Core = Core;
View
159 src/paginator/js/paginator.js
@@ -0,0 +1,159 @@
+var L = Y.Lang;
+
+Y.Paginator = Y.Base.create("paginator", Y.Widget, [ Y.PaginatorBase ],
+ {
+ TEMPLATES : {
+ //buttons
+ BTN_BASE : "<button class='yui3-u {css}' {attrs}>{label}</button>",
+
+ PREV : [ "data-dir='prev'", "&lt;" ],
+ NEXT : [ "data-dir='next'", "&gt;" ],
+ FIRST : [ "data-page='first'", "&lsaquo;&lsaquo;" ],
+ LAST : [ "data-page='last'", "&rsaquo;&rsaquo;" ],
+ NAV : [ "data-page='{page}'", "{page}" ],
+
+ //general markup
+ MAIN : "<div class='yui3-g {css}' role='toolbar'>{first}{prev}{pages}{next}{last}</div>",
+ PAGES : "<ul class='yui3-g yui3-u {css}' role='presentation'>{pages}</ul>",
+ PAGE : "<li class='yui3-u {css}' role='presentation'>{button}</li>"
+ },
+
+ CLASSES : [
+ "first",
+ "prev",
+ "next",
+ "last",
+ "navigator",
+ "page",
+ "current",
+ "disabled"
+ ],
+
+ initializer : function(config) {
+ config || (config = {});
+
+ this._templates = L.isObject(config.templates) ?
+ Y.merge(this.TEMPLATES, config.templates) :
+ this.TEMPLATES;
+
+ this._classes = {};
+
+ Y.Array.each(this.CLASSES, function(css) {
+ this._classes[css.toUpperCase()] = this.getClassName(css);
+ }, this);
+ },
+
+ destructor : function() {
+ console.log("widget destructor"); //TODO: REMOVE DEBUGGING
+
+ this._templates = this._classes = null;
+ },
+
+ renderUI : function() {
+ var cb = this.get("contentBox"),
+ attrs = this.getAttrs([ "first", "prev", "next", "last" ]),
+ templates = this._templates,
+ classes = this._classes,
+ html;
+
+ html = L.sub(templates.MAIN, {
+ css : classes.NAVIGATOR,
+ first : attrs.first ? this._renderButton(templates.FIRST, { css : classes.FIRST }) : "",
+ prev : attrs.prev ? this._renderButton(templates.PREV, { css : classes.PREV }) : "",
+ pages : this._renderPages(),
+ next : attrs.next ? this._renderButton(templates.NEXT, { css : classes.NEXT }) : "",
+ last : attrs.last ? this._renderButton(templates.LAST, { css : classes.LAST }) : ""
+ });
+
+ cb.setContent(html);
+ },
+
+ bindUI : function() {
+ console.log("widget bindUI"); //TODO: REMOVE DEBUGGING
+ var bb = this.get("boundingBox");
+
+ //direction arrow clicks
+ bb.delegate("click", function(e) {
+ e.preventDefault();
+
+ console.log(e); //TODO: REMOVE DEBUGGING
+
+ }, "button[data-dir]");
+
+ //page arrow clicks
+ bb.delegate("click", function(e) {
+ e.preventDefault();
+
+ console.log(e); //TODO: REMOVE DEBUGGING
+
+ }, "button[data-page]");
+ },
+
+ syncUI : function() {
+ console.log("widget syncUI"); //TODO: REMOVE DEBUGGING
+ },
+
+ _renderButton : function(template, config) {
+ config || (config = {});
+
+ return L.sub(this._templates.BTN_BASE, {
+ css : config.css || "",
+ attrs : L.isObject(config.attrs) ? L.sub(template[0], config.attrs) : template[0],
+ label : L.isObject(config.attrs) ? L.sub(template[1], config.attrs) : template[1]
+ });
+ },
+
+ _renderPages : function() {
+ //TODO: render some pages
+ var attrs = this.getAttrs([ "details" ]),
+ html = [];
+
+ Y.Array.each(attrs.details.pages, function(page) {
+ var css = this._classes.PAGE;
+ //TODO: add active page class to the css
+
+ html[html.length] = this._renderButton(this._templates.NAV, {
+ css : this._classes.PAGE,
+ attrs : {
+ page : page
+ }
+ });
+ }, this);
+
+ return html.join("");
+ }
+ }, {
+ ATTRS : {
+ //show first page link
+ first : {
+ value : true,
+ validator : L.isBoolean
+ },
+
+ //show last page link
+ last : {
+ value : true,
+ validator : L.isBoolean
+ },
+
+ //show prev page link
+ prev : {
+ value : true,
+ validator : L.isBoolean
+ },
+
+ //show next page link
+ next : {
+ value : true,
+ validator : L.isBoolean
+ },
+
+ //determine how many page numbers are shown around the current
+ //0 shows all
+ visible : {
+ value : 0,
+ validator : L.isNumber
+ }
+ }
+ }
+);
View
6 src/paginator/paginator-core.properties
@@ -0,0 +1,6 @@
+builddir=../../../builder/componentbuild
+srcdir=../..
+
+component=paginator-core
+component.jsfiles=paginator-core.js
+component.requires=
View
9 src/paginator/paginator-core.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="Paginator Core" default="local">
+ <property environment="env" />
+
+ <property file="paginator-core.properties" />
+
+ <import file="${builddir}/3.x/bootstrap.xml"
+ description="Default Build Properties and Targets" />
+</project>
View
6 src/paginator/paginator.properties
@@ -0,0 +1,6 @@
+builddir=../../../builder/componentbuild
+srcdir=../..
+
+component=paginator
+component.jsfiles=paginator.js
+component.requires=cssreset,cssfonts,cssgrids,widget,base-build,paginator-core
View
9 src/paginator/paginator.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="Paginator" default="local">
+ <property environment="env" />
+
+ <property file="paginator.properties" />
+
+ <import file="${builddir}/3.x/bootstrap.xml"
+ description="Default Build Properties and Targets" />
+</project>
View
42 src/paginator/tests/paginator.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Paginator Tests</title>
+ <meta charset="utf-8">
+</head>
+<body class="yui3-skin-sam">
+
+<div id="log"></div>
+
+<script src="../../../build/yui/yui.js"></script>
+<script src="../../../build/paginator-core/paginator-core.js"></script>
+<script src="../../../build/paginator/paginator.js"></script>
+<script src="tests-core.js"></script>
+<script src="tests-widget.js"></script>
+<script>
+var Y = YUI({
+ combine : false,
+ allowRollup: false,
+ filter: (window.location.search.match(/[?&]filter=([^&]+)/) || [])[1] || 'min'
+ }).use(
+ "event",
+ "paginator-core-tests",
+ /*"paginator-widget-tests",*/
+ "test-console",
+ function(Y) {
+ new Y.Test.Console().render("#log");
+
+ //add all the tests
+ Y.Object.each(Y.Tests, function(value, key) {
+ Y.Test.Runner.add(value);
+ });
+
+ Y.Test.Runner.on("fail", function(e) { console.error(e.error); });
+
+ Y.on("domready", function() {
+ Y.Test.Runner.run();
+ });
+ });
+</script>
+</body>
+</html>
View
279 src/paginator/tests/tests-core.js
@@ -0,0 +1,279 @@
+YUI.add("paginator-core-tests", function(Y) {
+ var Assert = Y.Assert,
+ ObjectAssert = Y.ObjectAssert,
+ Paginator = Y.Base.create("paginator", Y.Base, [ Y.Paginator.Core ]);
+
+ Y.namespace("Tests").Core = new Y.Test.Case({
+ name : "Y.Core",
+
+ //*
+ "Core should be instantiable" : function() {
+ Assert.isInstanceOf(Paginator, new Paginator());
+ },
+
+ "Core should refuse invalid page attribute" : function() {
+ var p = new Paginator();
+
+ Assert.isUndefined(p.get("page"));
+
+ p.set("page", -1);
+
+ Assert.isUndefined(p.get("page"));
+
+ p.set("page", 0);
+
+ Assert.isUndefined(p.get("page"));
+
+ p.set("page", 2);
+
+ Assert.isUndefined(p.get("page"));
+ },
+
+ "Core should accept page attributes > 1 when total is Infinity" : function() {
+ var pi = new Paginator({ total : Infinity });
+
+ Assert.areEqual(1, pi.get("page"));
+
+ pi.set("page", -1);
+
+ Assert.areEqual(1, pi.get("page"));
+
+ pi.set("page", 0);
+
+ Assert.areEqual(1, pi.get("page"));
+
+ pi.set("page", 200);
+
+ Assert.areEqual(200, pi.get("page"));
+ },
+
+ "Core should validate offset attribute" : function() {
+ var p = new Paginator();
+
+ p.set("offset", -1);
+
+ Assert.areEqual(0, p.get("offset"));
+
+ p.set("offset", 1);
+
+ Assert.areEqual(0, p.get("offset"));
+ },
+
+ "Core should calculate details on instantiation" : function() {
+ new Paginator({
+ after : {
+ detailsChange : function(e) {
+ var o = e.newVal;
+
+ Assert.isObject(o);
+ }
+ }
+ });
+ },
+
+ "Core should calculate sane defaults on basic instantiation" : function() {
+ new Paginator({
+ after : {
+ detailsChange : function(e) {
+ var o = e.newVal;
+
+ Assert.isUndefined(o.page);
+ Assert.areEqual(0, o.offset);
+
+ Assert.isFalse(o.first);
+ Assert.isFalse(o.prev);
+ Assert.isFalse(o.next);
+ Assert.isFalse(o.last);
+ }
+ }
+ });
+ },
+
+ "Core should calculate sane details on normal instantiation" : function() {
+ new Paginator({
+ total : 5,
+ after : {
+ detailsChange : function(e) {
+ var o = e.newVal;
+
+ Assert.areEqual(1, o.page);
+ Assert.areEqual(0, o.offset);
+
+ Assert.isFalse(o.first);
+ Assert.isFalse(o.prev);
+
+ Assert.areEqual(2, o.next);
+ Assert.areEqual(5, o.last);
+ }
+ }
+ });
+ },
+
+ "Core should calculate sane details with page specified" : function() {
+ new Paginator({
+ total : 5,
+ page : 3,
+ after : {
+ detailsChange : function(e) {
+ var o = e.newVal;
+
+ console.log(o); //TODO: REMOVE DEBUGGING
+
+ Assert.areEqual(3, o.page);
+ Assert.areEqual(2, o.offset);
+
+ Assert.areEqual(1, o.first);
+ Assert.areEqual(2, o.prev);
+
+ Assert.areEqual(4, o.next);
+ Assert.areEqual(5, o.last);
+ }
+ }
+ });
+ },
+
+ "Core should calculate sane details with even per" : function() {
+ new Paginator({
+ total : 5,
+ per : 2,
+ after : {
+ detailsChange : function(e) {
+ var o = e.newVal;
+
+ Assert.areEqual(1, o.page);
+ Assert.areEqual(0, o.offset);
+
+ Assert.isFalse(o.first);
+ Assert.isFalse(o.prev);
+
+ Assert.areEqual(2, o.next);
+ Assert.areEqual(3, o.last);
+ }
+ }
+ });
+ },
+
+ "Core.increment should increment page" : function() {
+ var p = new Paginator({
+ total : 5
+ });
+
+ p.increment();
+
+ Assert.areEqual(2, p.get("page"));
+ },
+
+ "Core.increment should increment page a custom distance" : function() {
+ var p = new Paginator({
+ total : 5
+ });
+
+ p.increment(2);
+
+ Assert.areEqual(3, p.get("page"));
+ },
+
+ "Core.increment should not go past the end" : function() {
+ var p = new Paginator({
+ total : 5,
+ page : 5
+ });
+
+ p.increment();
+
+ Assert.areEqual(5, p.get("page"));
+ },
+
+ "Core.decrement should decrement page" : function() {
+ var p = new Paginator({
+ total : 5,
+ page : 5
+ });
+
+ p.decrement();
+
+ Assert.areEqual(4, p.get("page"));
+ },
+
+ "Core.decrement should decrement page a custom distance" : function() {
+ var p = new Paginator({
+ total : 5,
+ page : 5
+ });
+
+ p.decrement(2);
+
+ Assert.areEqual(3, p.get("page"));
+ },
+
+ "Core.decrement should not go past the beginning" : function() {
+ var p = new Paginator({
+ total : 5
+ });
+
+ Assert.areEqual(1, p.get("page"));
+
+ p.decrement();
+
+ Assert.areEqual(1, p.get("page"));
+ },
+
+
+ "Core.first should update page & offset correctly" : function() {
+ var p = new Paginator({
+ total : 10
+ }),
+ p2 = new Paginator();
+
+ p.first();
+
+ Assert.areEqual(1, p.get("page"));
+ Assert.areEqual(0, p.get("offset"));
+
+ p2.first();
+
+ Assert.isUndefined(p2.get("page"));
+ Assert.areEqual(0, p2.get("offset"));
+ },
+ //*/
+
+ "Core.last should update page & offset correctly" : function() {
+ var p = new Paginator({
+ total : 10
+ }),
+ p2 = new Paginator({
+ total : 12,
+ per : 3
+ }),
+ p3 = new Paginator({
+ total : Infinity
+ }),
+ p4 = new Paginator();
+
+ p.last();
+
+ Assert.areEqual(10, p.get("page"));
+ Assert.areEqual(9, p.get("offset"));
+
+ p2.last();
+
+ Assert.areEqual(4, p2.get("page"));
+ Assert.areEqual(9, p.get("offset"));
+
+ p3.last();
+
+ Assert.areEqual(Infinity, p3.get("page"));
+ Assert.areEqual(Infinity, p3.get("offset"));
+
+ p4.last();
+
+ Assert.isUndefined(p4.get("page"));
+ Assert.areEqual(0, p4.get("offset"));
+ }
+ });
+
+}, "@VERSION@", { requires : [
+ "test",
+ "base-build",
+ "paginator-core"
+]});
View
18 src/paginator/tests/tests-widget.js
@@ -0,0 +1,18 @@
+YUI.add("paginator-widget-tests", function(Y) {
+
+ Y.namespace("Tests").Paginator = new Y.Test.Case({
+ name : "Y.Paginator",
+
+ "this test should run" : function() {
+ var p = new Y.Paginator({
+ total : 5,
+ render : true
+ });
+ }
+ });
+
+}, "@VERSION@", { requires : [
+ "test",
+ "base-build",
+ "paginator"
+]});
Something went wrong with that request. Please try again.