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.
- Carousel
- DataTable
- Search
- Slideshow
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.
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 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.
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.
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
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
viewTypecan be created withviewas a config, orviewcan contain any module that has arender()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
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
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.
@apipkin, this seems like a good start. Here are some things I noticed:
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.
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.