MVC & Pagination, Filtering, Ordering, etc.
Moderator: General Moderators
MVC & Pagination, Filtering, Ordering, etc.
(*assuming that I do correct assumptions about MVC pattern, *correct* me if not)
Pagination, Filtering, Ordering, etc. - where these are to be implemented in an MVC powered architecture?!?
Let's take the most common case - Pagination.
So ... without a Pagination we have:
- Model - executes SQL "selectAll" statement into a record collection;
- View - shows collection of records;
- Controller - does almost nothing;
With a Pagination we have (?!?):
- Model - executes SQL "selectAll" statement with a LIMIT clause into a record collection;
- View - shows collection of records;
- Controller - does almost nothing;
OK ... but it's obvious (at least to me) that Pagination should not be implemented into the Model because it's naturally a View requirement (view only N-to-M records). Additionally, in my Views I have a so called "view-context" - that is if I call "/users/get.html" it will be outputed as an HTML content , if I call "/users/get.csv" it will be outputed in a CSV format. And I don't need *any* Pagination applied when outputting as CSV, while I need Pagination applied when ouputting as HTML. The view-context output is controlled by the Front controller, so it has nothing to do with the Model.
Can anybody clear this for me?
What I do for now is to have an "Interceptor" object which behaves as a "plugin" for both Model and View objects. This "plugin" is inserted by the Controller.
Pagination, Filtering, Ordering, etc. - where these are to be implemented in an MVC powered architecture?!?
Let's take the most common case - Pagination.
So ... without a Pagination we have:
- Model - executes SQL "selectAll" statement into a record collection;
- View - shows collection of records;
- Controller - does almost nothing;
With a Pagination we have (?!?):
- Model - executes SQL "selectAll" statement with a LIMIT clause into a record collection;
- View - shows collection of records;
- Controller - does almost nothing;
OK ... but it's obvious (at least to me) that Pagination should not be implemented into the Model because it's naturally a View requirement (view only N-to-M records). Additionally, in my Views I have a so called "view-context" - that is if I call "/users/get.html" it will be outputed as an HTML content , if I call "/users/get.csv" it will be outputed in a CSV format. And I don't need *any* Pagination applied when outputting as CSV, while I need Pagination applied when ouputting as HTML. The view-context output is controlled by the Front controller, so it has nothing to do with the Model.
Can anybody clear this for me?
What I do for now is to have an "Interceptor" object which behaves as a "plugin" for both Model and View objects. This "plugin" is inserted by the Controller.
Last edited by VladSun on Sun Dec 06, 2009 5:47 pm, edited 2 times in total.
There are 10 types of people in this world, those who understand binary and those who don't
Re: MVC & Pagination, Filtering, Ordering, etc.
Concerns span multiple layers, indeed cross cutting concerns.
The controller would be able to take an input from the user that specified how he wanted his results presented (ordering, filtering). So basically the controller provides an interface in the form of GET parameters that can be passed. From there it tells the business logic to do it's work (wether that reside in a model or controller).
For pagination, theres more calculations to do like dividing out the page numbers by the rows per page, multiplying by the current page and subtracting the rows per page. For this there are frameworks (eg. Zend_pagination) which do nothing but the maths for you (if you wish).
Then of course you have the view, for which Zend_Pagination has a decorator system and several display styles. With your sorting you will probably also have some more code in the view (unless you expected your users to use the GET parameters).
As far as the .csv, the way I saw the Magento guys handle this (not that theyre a good example of well done MVC or anything), they just made another action, and simply chained a call that reset the page count, and rendered the csv's view. I wouldn't be too dogmatic about re-using actions, if taken to the extreme you would just have 1 god action. If creating 2 actions bothers you (duplication), extract a private parametrized method, and call that method from each action.
So my overall view on this is that context switching is a useful tool, but not a means to an end in itself. The solution with 1 single action might just take you longer and not read as good
The controller would be able to take an input from the user that specified how he wanted his results presented (ordering, filtering). So basically the controller provides an interface in the form of GET parameters that can be passed. From there it tells the business logic to do it's work (wether that reside in a model or controller).
For pagination, theres more calculations to do like dividing out the page numbers by the rows per page, multiplying by the current page and subtracting the rows per page. For this there are frameworks (eg. Zend_pagination) which do nothing but the maths for you (if you wish).
Then of course you have the view, for which Zend_Pagination has a decorator system and several display styles. With your sorting you will probably also have some more code in the view (unless you expected your users to use the GET parameters).
As far as the .csv, the way I saw the Magento guys handle this (not that theyre a good example of well done MVC or anything), they just made another action, and simply chained a call that reset the page count, and rendered the csv's view. I wouldn't be too dogmatic about re-using actions, if taken to the extreme you would just have 1 god action. If creating 2 actions bothers you (duplication), extract a private parametrized method, and call that method from each action.
So my overall view on this is that context switching is a useful tool, but not a means to an end in itself. The solution with 1 single action might just take you longer and not read as good
Re: MVC & Pagination, Filtering, Ordering, etc.
Hm ... I wonder whether my design is right then...
Depending on the view context (i.e. /address/get.html ) $this->view('response') may render "json/response", or ""html/response", etc.
So, Pagination "decorates" the CRUD_Model by modifying its SQL query before it gets executes, thus adding pagination functionality to the Model. The Model doesn't know it has been "paginated".
But here is the problem - I need Pagination for the HTML view, but I don't need it for the CSV view and my Controller doesn't know how many type of view there are and which types require Pagination and which not.
My Views (probably that's my mistake) works with the Model data through a Response object - an object that always has a "success" property, an "errorMessages" property and a "data" property.
By using this: I aim to modify the data properties of the Response generated by the Model. Every "decorator" may change the data ouputed by the previous "decorator" or Model. This way I successfully added the "totalCount" property to the Response object generated by the Pagination decorator, so it's accessible by the View.
Thoughts? Criticism?
PS: I always use POST method for input data (i.e. I DON'T use URL schemes like this: /user/get/14)
Code: Select all
abstract class CRUD_Controller extends Auth_Controller
{
protected $_model = null;
...
protected function setModel($model)
{
if (!empty($model))
{
$this->_model = $model;
}
}
public function get()
{
$this->getModel()->request('getAll');
foreach ($this->getModel()->get() as $key => $value)
RowResponse::register($key, $value);
$this->view('response');
}Code: Select all
class Address extends CRUD_Controller
{
function __construct()
{
parent::__construct();
$this->setModel(new Pagination(new Address_CRUD_Model()));
}
}But here is the problem - I need Pagination for the HTML view, but I don't need it for the CSV view and my Controller doesn't know how many type of view there are and which types require Pagination and which not.
My Views (probably that's my mistake) works with the Model data through a Response object - an object that always has a "success" property, an "errorMessages" property and a "data" property.
By using this:
Code: Select all
foreach ($this->getModel()->get() as $key => $value)
RowResponse::register($key, $value);Thoughts? Criticism?
PS: I always use POST method for input data (i.e. I DON'T use URL schemes like this: /user/get/14)
There are 10 types of people in this world, those who understand binary and those who don't
Re: MVC & Pagination, Filtering, Ordering, etc.
The way I do it I made a Grid library. (i cant say that any approach is "wrong" per se)
I assign a collection (I guess another name for "crud model, but more clearly shows that it is in fact a collection of models). So my grid interface has something like addModels() that is abstract. I make a grid subclass for each module which specifies/overrides fields (grids can inherit and remove eachother's fields just like Zend_Form)
So I might have
A_Model_Grid // has fields only the owner of the records cares about
A_Model_Grid_Search // has fields users that are searching shold see
A_Model_Grid_Admin // has more fields only the admin should see
etc...
Usually addModels() is implemented only once and adds all fields from the model, if the grid doesn't have a field key registered it silently ignores it So we dont have to duplicate addModels() if we are duplicating grids. Only specify what to add/remove in terms of display fields (columns)
In my crud control I have inherited methods that will return an already paginated collection, and all the integers I need to calculate total pages, etc... I simply tell the grid these numbers (which it tells to it's pagination adapter, currently Zend_Paginator)
Then in the view I simply have
echo $this->grid
Each grid has decorators, 99% of the time the default decorators are used. Each grid also has the concept of filters, which do nothing but send GET values back to the controller. The controller uses the mapper to do the filtering, the grid only depends on collection classes, not on mappers. So for instance if I am making a controller for A_Model but I only want to list where the owner_id = the current user
I override a template method
protected function doGetModels();
or better yet
protected function doFiltering()
In there I simply chain filters that filter models before the grid gets them. I wrote more detail about my controllers here and some code samples I think
viewtopic.php?f=19&t=104244&hilit=+grid
I assign a collection (I guess another name for "crud model, but more clearly shows that it is in fact a collection of models). So my grid interface has something like addModels() that is abstract. I make a grid subclass for each module which specifies/overrides fields (grids can inherit and remove eachother's fields just like Zend_Form)
So I might have
A_Model_Grid // has fields only the owner of the records cares about
A_Model_Grid_Search // has fields users that are searching shold see
A_Model_Grid_Admin // has more fields only the admin should see
etc...
Usually addModels() is implemented only once and adds all fields from the model, if the grid doesn't have a field key registered it silently ignores it So we dont have to duplicate addModels() if we are duplicating grids. Only specify what to add/remove in terms of display fields (columns)
In my crud control I have inherited methods that will return an already paginated collection, and all the integers I need to calculate total pages, etc... I simply tell the grid these numbers (which it tells to it's pagination adapter, currently Zend_Paginator)
Then in the view I simply have
echo $this->grid
Each grid has decorators, 99% of the time the default decorators are used. Each grid also has the concept of filters, which do nothing but send GET values back to the controller. The controller uses the mapper to do the filtering, the grid only depends on collection classes, not on mappers. So for instance if I am making a controller for A_Model but I only want to list where the owner_id = the current user
I override a template method
protected function doGetModels();
or better yet
protected function doFiltering()
In there I simply chain filters that filter models before the grid gets them. I wrote more detail about my controllers here and some code samples I think
viewtopic.php?f=19&t=104244&hilit=+grid
- Christopher
- Site Administrator
- Posts: 13596
- Joined: Wed Aug 25, 2004 7:54 pm
- Location: New York, NY, US
Re: MVC & Pagination, Filtering, Ordering, etc.
I am a little confused about what question you are asking exactly? Essentially, the View is a "decorator" for the Model. Views take data from the Model and format it as specified by the Request (i.e. Controller) and generate a Response.VladSun wrote:I aim to modify the data properties of the Response generated by the Model. Every "decorator" may change the data ouputed by the previous "decorator" or Model. This way I successfully added the "totalCount" property to the Response object generated by the Pagination decorator, so it's accessible by the View.
You question is in the murky View/Controller boundary. Where you put that separation is less important that whether you and your application are happy about where you put that separation. So my question is: What is not working for you?
(#10850)
Re: MVC & Pagination, Filtering, Ordering, etc.
In my project, I don't put the paginator as apart of the MVC model. I imitated Zend in the way that the paginator is a view helper [$this->paginate($numberOfPages,$currentPage)]. So it's neither a view nor a controller, but rather a separate chunk of code that can be moved around. However there is a flaw with this design. What if the designer wants to change the pagination style? They would have to get into the system code instead of being able to just edit a view script.
Re: MVC & Pagination, Filtering, Ordering, etc.
But the View doesn't change the way a Mode behaves - it only shows its data in some way. While my "decorator" objects change the data properties of the Model thus creating a Model from a Model.arborint wrote:Essentially, the View is a "decorator" for the Model. Views take data from the Model and format it as specified by the Request (i.e. Controller) and generate a Response.
For example - multiple mime-type Views. I think It should be handled in the Front controller or in the base Controller class (the way I do it):arborint wrote:So my question is: What is not working for you?
Code: Select all
abstract class View_Context_Controller extends Controller
{
protected $requestedViewMode = null;
protected $defaultViewMode = 'json';
public function __construct()
{
parent::__construct();
}
protected function setDefaultViewMode($viewMode)
{
$this->defaultViewMode = $viewMode;
}
protected function getViewMode($viewMode = null)
{
if ($viewMode)
return $viewMode;
if ($this->requestedViewMode)
return $this->requestedViewMode;
if ($this->defaultViewMode)
return $this->defaultViewMode;
return 'html';
}
public function view($view, $data = null, $viewMode = null)
{
$this->load->view($this->getViewMode($viewMode).'/'.$view, $data);
}
protected function getRemapping($method, $arguments)
{
$mv = explode('.', $method);
if (isset($mv[1]))
$this->requestedViewMode = $mv[1];
$o = new stdClass();
$o->method = $mv[0];
$o->arguments = $arguments;
return $o;
}
}There are 10 types of people in this world, those who understand binary and those who don't
- Christopher
- Site Administrator
- Posts: 13596
- Joined: Wed Aug 25, 2004 7:54 pm
- Location: New York, NY, US
Re: MVC & Pagination, Filtering, Ordering, etc.
I think there are three major parts to any pagination scheme, even it if the code is monolithic. It needs to be able to:Cirdan wrote:the paginator is a view helper
1. get Request values to determine the current page, and do any calculations for start/end/# records, bounds checks, etc.,
2. fetch only the records to be displayed,
3. display the fetched records and links to support paging through all the records.
Typically, the code for #1 is in the Controller, but calculation code can be in the Model and/or View. #2 in the Model and #3 in the View.
(#10850)
- Christopher
- Site Administrator
- Posts: 13596
- Joined: Wed Aug 25, 2004 7:54 pm
- Location: New York, NY, US
Re: MVC & Pagination, Filtering, Ordering, etc.
I don't see any reason why your View can't tell the Model what "context" it wants the data in. It can call the Model directly with any information needed.VladSun wrote:But the View doesn't change the way a Mode behaves - it only shows its data in some way. While my "decorator" objects change the data properties of the Model thus creating a Model from a Model.
It looks like you have View code leaking into your Controller. If you are going to leak, I think it is better to push code out of the controller into the Model and View.VladSun wrote:For example - multiple mime-type Views. I think It should be handled in the Front controller or in the base Controller class (the way I do it):
And in my Controllers I don't need to handle the mime-type requested - I just call $this->view('response') and a view according to the current View context is loaded. But as I've mentioned above - not all of my mime-type views need pagination for example.
(#10850)
Re: MVC & Pagination, Filtering, Ordering, etc.
I am trying to have a clear business logic into the Model - I mean really "business" without a single interference to the View requirements(I believe that (e.g.) Pagination related stuff has nothing to do with "business logic").arborint wrote:I don't see any reason why your View can't tell the Model what "context" it wants the data in. It can call the Model directly with any information needed.
Now I think, that my main problem is that I use intermediate communication layer between the View and the Model - my Response object.
Also my model methods are executed in the Controller and probably if I move their execution into the View (which one is loaded depends on the view-context) I can control the Model behavior by adding "decorators". I'll try this.
hm... you mean like this (suppose we have fetched somehow the $context value):arborint wrote:It looks like you have View code leaking into your Controller. If you are going to leak, I think it is better to push code out of the controller into the Model and View.
Code: Select all
class My_Controller
{
function get()
{
$this->load->model('MyModel');
$this->load->view('myview', $this->model, $this->viewContext);
}
}Code: Select all
class My_Controller extends View_Context_Controller
{
function get()
{
$this->load->model('MyModel');
$this->view('myview', $this->model);
}
}
Last edited by VladSun on Tue Dec 08, 2009 3:09 am, edited 1 time in total.
There are 10 types of people in this world, those who understand binary and those who don't
Re: MVC & Pagination, Filtering, Ordering, etc.
Maybe I'm so confused about these things, because I've used ExtJS for a long time and things like Pagination, sorting filtering may be done locally - after all data is fetched and stored client side. So, I am used to have very "clean" Models.
PS:
- get all members = "clear business" logic
- get members that belong to a given group = "clear business" logic
- get members that have names starting with "Vla" != "clear business" logic
- get first 30 members != "clear business" logic
PS:
For example:I am trying to have a clear business logic into the Model - I mean really "business" without a single interference to the View requirements(I believe that (e.g.) Pagination related stuff has nothing to do with "business logic").
- get all members = "clear business" logic
- get members that belong to a given group = "clear business" logic
- get members that have names starting with "Vla" != "clear business" logic
- get first 30 members != "clear business" logic
There are 10 types of people in this world, those who understand binary and those who don't
- Christopher
- Site Administrator
- Posts: 13596
- Joined: Wed Aug 25, 2004 7:54 pm
- Location: New York, NY, US
Re: MVC & Pagination, Filtering, Ordering, etc.
Well, it is simpler. It gets rid of all the ViewMode stuff in the Controller. And it makes the contract direct on the Model from the View.VladSun wrote:hm... you mean like this (suppose we have fetched somehow the $context value):
- how it's better than ?
(#10850)
- Christopher
- Site Administrator
- Posts: 13596
- Joined: Wed Aug 25, 2004 7:54 pm
- Location: New York, NY, US
Re: MVC & Pagination, Filtering, Ordering, etc.
I think my "clear" you mean "simple." If you added a getCountOfMembers() and getLimitMembers($start, $size) your Model would be no less "clear" and would allow the View to deal with Pagination pretty easily.VladSun wrote:Maybe I'm so confused about these things, because I've used ExtJS for a long time and things like Pagination, sorting filtering may be done locally - after all data is fetched and stored client side. So, I am used to have very "clean" Models.
For example:
- get all members = "clear business" logic
- get members that belong to a given group = "clear business" logic
- get members that have names starting with "Vla" != "clear business" logic
- get first 30 members != "clear business" logic
(#10850)
Re: MVC & Pagination, Filtering, Ordering, etc.
I would not consider any of that to be a part of the business tier, I would consider that a persistence concern. It sounds like you are asking an awful lot of overlapping questions at once though so I'm not sure what the issue is.VladSun wrote: For example:
- get all members = "clear business" logic
- get members that belong to a given group = "clear business" logic
- get members that have names starting with "Vla" != "clear business" logic
- get first 30 members != "clear business" logic
Can you try to sum up in simple english what you're trying to do (without using words like decorator, model, and context?).
In my application collections are just collections, models are just models. and then I have "finders" (data mappers). Wether the data mapper I am using is going to return only a 20 item collection, or all 2,000 items, I need not know (its simply an implementation detail)
$collection = $this->paginator_mapper->findAll( 2, 20 ) ; // might have 20 items
$collection = $this->plain_old_vanilla_mapper->findAll() ; // might have 2,000+ items
There's no need to over complicate it more then that, in my opinion (many people feel its necessary to do lazy loading in the collection, that I have never had a need for)
Re: MVC & Pagination, Filtering, Ordering, etc.
OK, I'll try...
1) Imagine a building full of people. If you want to visit them you just go to that building and there they are.
2) Imagine a building full of people. There are some rooms in it. If you want to visit people in a room you just go to that building, then go to the room and there they are.
3) Imagine a building full of people. There are some levels and some rooms on every level. If you want to visit people in a room you just go to that building, go to the level, then go to the room and there they are.
I think this story can be mapped into MVC as:
1) "building", "level", "room" and "people" are business domain objects. They all exist in the real world. It's all very natural.
2) "visiting" is a "natural" finder method.
3) finally, you will meet them face to face - thats for the "View".
Now ...
You want to visit people having names starting with 'V' ... Apparently, you can't just visit a place (it doesn't exist) where there is a bunch of people with names starting with 'V'.
So, what I'm trying to do is to build another building with rooms for every group of people whose names start with a particular letter. I have to move the people to that building and then I can visit the people in a room and meet them face to face.
I think that while the first building is a "natural" one, the second building is a "surrogate" one. Also, you can't have (in real life) a building that have both types of rooms, because you can't have the people be there at the same time. While I can build many types of "surrogate" buildings I can have a single "natural" building build.

1) Imagine a building full of people. If you want to visit them you just go to that building and there they are.
2) Imagine a building full of people. There are some rooms in it. If you want to visit people in a room you just go to that building, then go to the room and there they are.
3) Imagine a building full of people. There are some levels and some rooms on every level. If you want to visit people in a room you just go to that building, go to the level, then go to the room and there they are.
I think this story can be mapped into MVC as:
1) "building", "level", "room" and "people" are business domain objects. They all exist in the real world. It's all very natural.
2) "visiting" is a "natural" finder method.
3) finally, you will meet them face to face - thats for the "View".
Now ...
You want to visit people having names starting with 'V' ... Apparently, you can't just visit a place (it doesn't exist) where there is a bunch of people with names starting with 'V'.
So, what I'm trying to do is to build another building with rooms for every group of people whose names start with a particular letter. I have to move the people to that building and then I can visit the people in a room and meet them face to face.
I think that while the first building is a "natural" one, the second building is a "surrogate" one. Also, you can't have (in real life) a building that have both types of rooms, because you can't have the people be there at the same time. While I can build many types of "surrogate" buildings I can have a single "natural" building build.
There are 10 types of people in this world, those who understand binary and those who don't