Skip to content

Instantly share code, notes, and snippets.

@apipkin
Last active December 11, 2015 11:48
Show Gist options
  • Select an option

  • Save apipkin/4596384 to your computer and use it in GitHub Desktop.

Select an option

Save apipkin/4596384 to your computer and use it in GitHub Desktop.

Paginator

Objective

Provide a module with core paginator methods (next, prev, first, last, page) as well as the ability to calculate the total number of pages given the total number of items and the items to display per page.

Provide a module with the ability to interchange views to fit the UI needs of the paginator instance.

Usage to keep in mind

  • Carousel
  • DataTable
  • Search
  • Slideshow

Highlevel Design

Render Design

UI renderers, or Views, should remain abstracted from the business logic of the module. The UI should have the ability to be built from templates or direct input of markup. Regardless, the Paginator module should not care how the UI is built.

Events should be delegated to bounding box of the paginator so they can remain unbiased to the structure of the UI.

Logic Design

Data storage, state management, and data manipulation methods should be stored here. Can be standalone method used to mix into a Base-based object for paginator functionality or could be part of a Base-based object.

Changes to data values will generally trigger a change in the UI.

Coupling

Coupling logic between the UI and Data structure should be a matter of listening to events from the renderer and updating the state accordingly. Then updating the renderer according to what's updated in the state data.

Paginator Design Considerations

Looking closer at pagingator, the main business logic and state logic consist of the total number of items and the page you are currently viewing. Where a page represents the current offset of the page number times the number of items per page through the number of items per page.

The items being updated simply interact with the state changes of the paginator. For instance, the rows in a DataTable will update to display rows 51 through 60 if on page 5 display 10 items per page. This means Paginator doesn't need to know what's being displayed or how it's getting there, just simply know what page index it should represent.

This could also mean that the total number of items being displayed is completely irrelevant. Since Paginator doesn't need that data for any functional purposes. However, for maintaining the state, this data could be provided optionally and thus create the ability to display the total number of pages.

Paginator's UI should be completely decoupled from the data storage because the UI for a paginator can be extremely diverse.

This leads to a few architech decisions:

Widget

  • (a) Extend Widget. Each View extends the base paginator.
  • (b) Extend Widget. Each View is added in at instantiation.

Model-View

  • (c) Model contains busines logic. View contains view logic. Controller binds View events to Model. Module consists of three Base-based objects working together.

a) Extending from a widget

This practice would consist of creating a Paginator.Base object that would extend from Y.Widget. A default renderer would be extended from Paginator.Base. Then each custom view for each various usage would extend from that point if it needed the base renderer methods.

Y.Paginator.Base = Y.Base.create('paginator', Y.Widget, []);
Y.Paginator.Renderer = Y.Base.create('paginator-renderer', Y.Paginator.Base, []);
Y.Paginator.List = Y.Base.create('paginator-list', Y.Paginator.Renderer, []);

// instantiation
var paginator = new Y.Paginator.List({
    items: 10000,
    displayRange: 10
});

Pros:

  • Multiple extension points for a customized launching point.
  • Familiar widget API

Cons:

  • Increasingly complex stack
  • May end up piece milling objects together for more customization
  • The more complex the views get, the more Base-based objects there are

b) Adding a custom render to widget

Since the main focus would be to have a widget that has enterchainable templates and template functionality, it may make the most sense to build a base widget in such a way that a renderable template can be added along with UI event binding.

This would look similar to a) with the exception that the renderer is a separate module that is used by the renderUI method of Paginator.Base

Y.Paginator.Core = function() {};

Y.Paginator = Y.Base.create('paginator', Y.Widget, [Y.Paginator.Core], {
    // renderUI of Y.Widget would just call the "view"s render() method
    renderUI: function() {
        return this.get('view').render();
    }
}, {
    ATTRS: {
        view: {}
    }
});

Y.Paginator.List = Y.Base.create('paginator-list', Y.View, []);

// instantiation
var paginator = new Y.Paginator({
    items: 1000,
    view: {
        displayRange: 10
    },
    viewType: 'Paginator.List'
});

Pros:

  • View is abstracted from the business logic
  • viewType can be created with view as a config, or view can contain any module that has a render() method
  • Core logic is placed in a single module. This means it can be mixed into a Y.Model is the need ever arises.

Cons:

  • Widget may not make the most logical controller for what is essentially a model+view
  • Tighter coupling of the model logic

c) Individual MVVM

To extend beyond b) would be to live in a true Y.Model + Y.View space. This again would require a third module to be used in the coupling, along the lines of a ViewModel or Presenter pattern.

Y.Pagingator.Model = Y.Base.create('paginator-model', Y.Model, []);
Y.Paginator.View = Y.Base.create('paginator-view', Y.View, []);
Y.Pagiantor = Y.Base.create('paginator', Y.View, []);

// instantiation
var paginator = new Y.Paginator({
    model: {
        items: 1000
    },
    modelType: 'Paginator.Model',
    view: {
        displayRange: 10
    },
    viewType: 'Paginator.View'
});

Pros:

  • Completely abstracted MVC design.
  • View can be easily changed
  • Model and base logic can be easily changed
  • Can be used as a sub-view in another View-based module

Cons:

  • Possibly unnecessaryly heavy due to additional Base-based objects
  • No familiar Widget interface, methods, or ATTRS

Templating

In either case, a base template object would be available for pagination controller templates that can be used in various cases. A collection of classnames will be avialable to use in templates off of the rendering object. These classnames can be modified if needed, or added upon for custom templates and views, but any modified classnames will need to be added to the css accompanying the view as they are not normalized.

Default templates will use Y.Template.Micro to take advantage of page link and select box building from a range of options. The default templates will also consist of controls that are common to most paginator use cases to try and cover the 80/20 rule of development.

@ericf
Copy link

ericf commented Feb 11, 2013

@apipkin, this seems like a good start. Here are some things I noticed:

  1. The designs described here either use Widget, or they don't, but I don't feel that's mutually exclusive.
  2. I don't understand the first design: Widget <- Paginator.Base <- Paginator.Renderer <- Paginator.List. Why such a deep hierarchy? I thought the "flat" approach would use class extensions instead…

Events should be delegated to bounding box of the paginator so they can remain unbiased to the structure of the UI.

This is missing a value able responsibility of the view. I think the view should listen to the DOM events and translate user interactions into higher-level, custom, paginator events. This then sets up the contract between the view code and the controller code.

c) Individual MVVM […] No familiar Widget interface, methods, or ATTRS

The presenter can be a Widget, this is the approach I wrote up in my JS Bin.


The templates should rely on Y.Template and use an engine instance so they can be easily swapped with Handlebars.

@apipkin
Copy link
Author

apipkin commented Feb 11, 2013

The designs listed follow a "traditional" Y.Widget extension model (a), a Y.Model + Y.View approach (c), and a hybrid of Y.Widget and an interchangeable Y.View (b).

(b) is closely related to the JS Bin you provided, with the exception that the Y.Model in your JS Bin is being replaced with Y.Paginator.Core and mixed into the module, as to not have an additional Base-based module required. Having the core logic abstracted from the base module allows for further customization where you wish to use a Y.View instead of a Y.Widget to extend from. It also helps to show the MVC abstraction intended.

I do agree with you that Y.Paginator should be listening for certain events to be fired from the view and the view can dispatch those events based on UI interactions since that logic is more tightly coupled with the UI.

I agree that Y.Template should be used in the templates to swap out the engine needed.

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