SitePoint Sponsor

User Tag List

Page 1 of 2 12 LastLast
Results 1 to 25 of 49
  1. #1
    Spirit Coder allspiritseve's Avatar
    Join Date
    Dec 2002
    Location
    Ann Arbor, MI (USA)
    Posts
    648
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Skeleton Framework: Pagination Component - Please Review

    Arborint and I have been hard at work on a pagination component that can be used standalone or as part of the Skeleton framework. We're looking for feedback from other developers, so take a look and let us know what you think.

    Executive Summary
    We made these classes first and foremost to make it drop-dead easy to paginate data. Our goal was not to make a one size fits all solution, but rather to provide a layered range of solutions in which developers can pick and choose to tailor the classes to their needs.

    Specific Goals

    • Account for as many use cases as possible
    • Stay flexible for extendability
    • Take care of the messy details of pagination: url generation, maintaining state, dealing with the request.

    Basics
    The base object in this pagination system is a Value Object that holds the values for the list and does the necessary calculations. This class can be used alone, but you are required to initialize it.
    PHP Code:
    $pager = new A_Pagination_Core($datasource);
    $pager->setCurrentPage(intval($_GET['mypagevar']));
    $pager->setNumItems(intval($_GET['mycountvar'])); 
    This component also provides a class that extends the Core class and initializes itself from the request so you don't have to. This class adds a process() method that will initialize the object for you:
    PHP Code:
    $pager = new A_Pagination_Request($datasource);
    $pager->process(); 
    This Core object does the necessary calculation and will return page numbers for the current first, last, next and previous pages, plus a range of page numbers around the current page. These are intended to provide the basic functionality on which to build classes to render paginated output.

    This class takes a Datasource object passed to the constructor. These Datasources comply to a simple Adapter interface. This component will provide Adapters for arrays, PDO, MySQL, etc. A getItems() method is provided to retrieve the records for the currently displayed page.

    The basic methods in its interface are:
    PHP Code:
    class A_Pagination_Core {
         function 
    __construct($datasource)
         function 
    isPage($number)
         function 
    getPage($number)
         function 
    getCurrentPage()
         function 
    getFirstPage()
         function 
    getLastPage()
         function 
    getPageRange()
         function 
    getNumItems()
         function 
    getItems()

    This component is not monolithic. There are several levels of classes that use a Core object and provide support for rendering output. The lowest level of output support uses page numbers from a Core object and builds URLs. Using this class you can have complete control of your template:
    HTML Code:
    $url = new A_Pagination_Helper_Url($pager);
    $out .= '<a href="' . $url->previous() . '">Prev</> ";
    $out .= '<a href="' . $url->next('Next') . '">Next</> ";
    The next level of output support uses page numbers from a Core object and builds complete links (<a> tags). You an pass the name of a CSS class to this object to control link style:
    HTML Code:
    $link = new A_Pagination_Helper_Link($pager);
    $out .= $link->previous('Prev', 'mylinkclass');
    $out .= $link->next('Next', 'mylinkclass');
    Use Case Example
    Here is an example of combining the classes to do a basic paginated list with links:
    PHP Code:
    // create a data object that has the interface needed by the Pager object
    $datasource = new Datasource();
     
    // create a request processor to set pager from GET parameters
    $pager = new A_Pagination_Request($datasource);
     
    // initialize using values from $_GET
    $pager->process();
     
    // create a "standard" view object to create pagination links
    include 'A/Pagination/View/Standard.php';
    $view = new A_Pagination_View_Standard($pager);
     
    // display the data
    echo '<table border="1">';
    foreach (
    $pager->getItems() as $row) {
        echo 
    '<tr>';
        echo 
    '<td>' $row['id'] . '</td><td>' $row['name'] . '</td>';
        echo 
    '</tr>';
    }
    echo 
    '</table>';
     
    // display the pagination links
    echo $view->render(); 
    Additional Features

    • There are a number of examples that show different ways the classes can be used -- from do-it-yourself to standalone.
    • There is support for ORDER BY functionality to sort the list. There are methods to generate column heading links to sort any column ascending/descending. Sort order is maintained while paging.
    • The total number of items in the datasource is persisted RESTfully so, for example, COUNT() is only called one for database datasources.
    • Request parameter names can be changed to remove conflicts and allow multiple pagers on one page.
    • Pagination state values can be saved in the session if you want to be able to resume in the list where you left off -- when editing records in CRUD for example.

    Feedback Wanted

    • Do these classes provide everything you need for pagination?
    • What modifications would you make before using this in a project?
    • What can we do to improve the code?

    DataGrid
    For the most automated use case, we will be providing a DataGrid that takes care of all HTML once the core classes have been finalized.
    Attached Files Attached Files
    Last edited by allspiritseve; May 30, 2009 at 11:02.

  2. #2
    SitePoint Evangelist ghurtado's Avatar
    Join Date
    Sep 2003
    Location
    Wixom, Michigan
    Posts
    591
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I think you have a typo in:

    PHP Code:
    $url = new A_Pagination_Helper_Link($pager);
    $out .= $link->previous('Prev''mylinkclass');
    $out .= $link->next('Next''mylinkclass'); 
    Garcia

  3. #3
    Spirit Coder allspiritseve's Avatar
    Join Date
    Dec 2002
    Location
    Ann Arbor, MI (USA)
    Posts
    648
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by ghurtado View Post
    I think you have a typo in:

    PHP Code:
    $url = new A_Pagination_Helper_Link($pager);
    $out .= $link->previous('Prev''mylinkclass');
    $out .= $link->next('Next''mylinkclass'); 
    You're right... arborint did that part

  4. #4
    SitePoint Enthusiast
    Join Date
    Apr 2009
    Location
    Porto,Portugal
    Posts
    76
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I was looking for something like this.
    It looks great.

  5. #5
    Spirit Coder allspiritseve's Avatar
    Join Date
    Dec 2002
    Location
    Ann Arbor, MI (USA)
    Posts
    648
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Popovich88 View Post
    I was looking for something like this.
    It looks great.
    Great! Please try it out and let us know what you think.

  6. #6
    SitePoint Guru
    Join Date
    Nov 2004
    Location
    Plano
    Posts
    643
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by allspiritseve View Post
    PHP Code:
    $pager = new A_Pagination_Core($datasource);
    $pager->setCurrentPage(intval($_GET['mypagevar']));
    $pager->setNumItems(intval($_GET['mycountvar'])); 
    here your currentpage and numItems relies on the global GET variable. i don't like this because it's harder to unit test, and you're forced to use the get variables...what if you want to load the page number from a source other than the GET in the URL? like the database or cookies.

    personally i might do something like this instead from your controller:
    PHP Code:
    // initialize using values from $_GET
    $pager->process($_GET['mypagevar'],$_GET['mycountvar']); 
    or maybe even:

    PHP Code:
    $pager = new A_Pagination_Request($datasource,$_GET['mypagevar'],$_GET['mycountvar']);
    $pager->process(); 

  7. #7
    SitePoint Evangelist
    Join Date
    Aug 2005
    Location
    Winnipeg
    Posts
    498
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    here your currentpage and numItems relies on the global GET variable. i don't like this because it's harder to unit test, and you're forced to use the get variables...what if you want to load the page number from a source other than the GET in the URL? like the database or cookies.
    For starters...I think I'd question your design if you stored the page number in a DB. :P

    All kidding aside...I think that is for demo purposes only...you could probably use SESSION, GPC, ENV or whatever your fancy.
    The only constant in software is change itself

  8. #8
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,055
    Mentioned
    16 Post(s)
    Tagged
    3 Thread(s)
    Something I may be misunderstanding that it appears that the data source is paginated. However, what if I would only like to query for the the items on the page that are needed based on the total, number of pages and number per page?

    For example, I use a system which calculates everything based on a total. Then supplies a offset and limit to embed in a query.

    PHP Code:
    /*
    * returns integer representing total blogs
    */
    $count BlogEntry::count();

    /*
    * 1.) total per page
    * 2.) page
    * 3.) total entities
    */
    $pagination = new Pagination(10,2,$count);

    /*
    * get offset and limit
    */
    $offset $pagination->getOffset();
    $limit $pagination->getCount();

    /*
    * embed in query to only return needed items
    */
    $sql 'SELECT x,y FROM blogs LIMIT '.$offset.','.$limit.';'
    Is something like this possible in the system your developing?

    If so is it straightforward to use or does it require that separate DataSource class(s) be created?

  9. #9
    SitePoint Wizard
    Join Date
    Aug 2004
    Location
    California
    Posts
    1,672
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by oddz View Post
    Something I may be misunderstanding that it appears that the data source is paginated. However, what if I would only like to query for the the items on the page that are needed based on the total, number of pages and number per page?


    Is something like this possible in the system your developing?

    If so is it straightforward to use or does it require that separate DataSource class(s) be created?
    This is an rewrite of an existing component. The previous version had Adapters for mysql, postgres, PDO, ADODB and array. So there will probably be those, plus whatever else is requested. I know a directory listing Adapter has been mentioned.

    The interface currently is just:
    Code PHP:
    interface A_Pagination_Adapter_Interface	{
     
    	public function getItems ($offset, $length);
    	public function getNumItems();
    	public function setOrderBy ($field, $descending=false);
     
    }
    One feature is that the Adapters will do the COUNT(*) for you, and it is only on the first request. After that the 'num_items' value is passed in URLs.

    Using one of the database adapters would look like this:
    Code PHP:
    $datasource = new A_Pagination_Adapter_Mysql("SELECT user.id,user.name,group.title FROM user JOIN group on user.group_id=group.id WHERE user.active='Y' ");
     
    $pager = new A_Pagination_Core($datasource);
     
    $view = new A_Pagination_View_Standard($pager);
    foreach ($pager->getItems() as $row) {     // will return only the rows for current page
         echo "<div>{$row['id']}. {$row['name']} - {$row['title']}</div>";
    }
    echo $view->render();
    Last edited by arborint; May 29, 2009 at 19:40.
    Christopher

  10. #10
    SitePoint Wizard
    Join Date
    Aug 2004
    Location
    California
    Posts
    1,672
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by XtrEM3 View Post
    personally i might do something like this instead from your controller:
    PHP Code:
    // initialize using values from $_GET
    $pager->process($_GET['mypagevar'],$_GET['mycountvar']); 
    or maybe even:

    PHP Code:
    $pager = new A_Pagination_Request($datasource,$_GET['mypagevar'],$_GET['mycountvar']);
    $pager->process(); 
    Yeah, I think it started as parameters to the constructor. But there are a couple other setters, so it ended up that individual methods was cleaner than a bunch of parameters in the constructor. There are defaults for everything so you only need to call the setters where you want to change the defaults. Also with process() you can pass an alternate to $_GET if you want.

    I think the point of the example is that you can set the values from any source you want. The goal is to give you complete control.
    Code PHP:
    $pager = new A_Pagination_Core($datasource);
    $pager->setCurrentPage(intval($_POST['mypagevar']));
    $pager->setNumItems(intval($_COOKIE['mycountvar']));
    Or if you don't care then you can just let the component deal with the details for you:
    Code PHP:
    $pager = new A_Pagination_Request($datasource);
    $pager->process();
    Christopher

  11. #11
    Spirit Coder allspiritseve's Avatar
    Join Date
    Dec 2002
    Location
    Ann Arbor, MI (USA)
    Posts
    648
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by XtrEM3 View Post
    here your currentpage and numItems relies on the global GET variable. i don't like this because it's harder to unit test, and you're forced to use the get variables...what if you want to load the page number from a source other than the GET in the URL? like the database or cookies.
    We actually have methods on A_Pagination_Request so you can set a request object or a session object with a get ($field) method to retrieve any value.
    PHP Code:
        public function setRequest ($request)    {
            
    $this->request $request;
        }

        public function 
    setSession ($session)    {
            
    $this->session $session;
        } 
    If you are using some other form of persistence (db, cookies) you're probably going to want to set up the core directly, as arborint showed.

    Quote Originally Posted by oddz
    Something I may be misunderstanding that it appears that the data source is paginated. However, what if I would only like to query for the the items on the page that are needed based on the total, number of pages and number per page?
    It may not look like it, but that's exactly what's happening in the core. Arborint already showed the interface for the adapters, so I'll post how the core works with them:

    PHP Code:
        public function getItems()    {
            return 
    $this->datasource->getItems($this->getFirstItem(), $this->pageSize);
        }

        public function 
    getNumItems()    {
            if (
    $this->numItems === false) {
                
    $this->numItems $this->datasource->getNumItems();
            }
            return 
    $this->numItems;
        } 
    In your example, you have to manually retrieve the count, the offset, and the limit before you can paginate. With our system, as long as you use a valid adapter that implements our interface, it will take care of all that for you.

    If you really want to do the query yourself, you could always just use the core as you've been using your pagination class, and it will calculate everything for you. Sure, you already have a class that does that... but our class works with any datasource

  12. #12
    SitePoint Guru
    Join Date
    Jun 2006
    Posts
    638
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    It looks way to complicated to me... from the usage point of view.

    I would do something like this:
    PHP Code:
    // Default
    UtilPaginator::render ();

    // Custom parameters
    UtilPaginator::render ( array (
        
    'class' => 'customClass'
        
    'page' => 1
        
    'itemsPerPage' => 24
        
    'totalItems' => 100
        
    'links' => '22'
        
    'btnNext' => '>>'
        
    'btnPrev' => '<<'
        
    'params' => array (
            
    'var1' => 5
            
    'var2' => 
        

    ) );

    // Wrapper for that custom call
    UtilPaginator::renderType1 (); 
    The idea is this:
    The class will draw an UL with page numbers.

    Every time you call 'render', the class will get all GET/POST data, and create the pagination links for it.

    If you want some custom links, as in not the standard ones, you pass in parameters to that function.

    If you find that your new project always draws the links in the same way, but not the "standard way", you just extend the class (add a function to it or extend it).

    This was a rough description of how some of my "pagination" blocks work. Always the same HTML, and different CSS for the look, and almost always used in a one liner.

  13. #13
    Spirit Coder allspiritseve's Avatar
    Join Date
    Dec 2002
    Location
    Ann Arbor, MI (USA)
    Posts
    648
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Vali
    It looks way to complicated to me... from the usage point of view.
    It might look complicated from your use case, but the idea behind our classes is to split up the functionality so you can use what you want, and not what you don't. With your class, everybody would be stuck with what you have, even if they're replacing some of your functionality. Some examples of benefits of this construction:

    • You can have the current page, total pages, items per page, and number of links set up automatically from the request - if you want.
    • You can set up different parameters for persisting vars in the url, in case there are name clashes.
    • You can pass an adapter that retrieves the total number of pages and the actual items from a datasource - if you want. (Currently your code would have to do that manually.)
    • The most common example is a db query. Our A_Pagination_Adapter_Db will take a query, automatically run a COUNT(*) query, and then execute the query with the proper offset and limit. All you have to do is call getItems().
    • You can generate order links that maintain the current state, but chang the sorting on the datasource.
    • You can create your own view - for example, to render pagination using a .tpl template file. If you do this, you won't need to use any of our HTML generation code. We do provide a URL helper that will generate links for you, including persisting state unrelated to pagination.

    That's just a start... but you get the idea.

    Quote Originally Posted by Vali
    This was a rough description of how some of my "pagination" blocks work. Always the same HTML, and different CSS for the look, and almost always used in a one liner.
    I don't know about a one liner... you still need to get the actual data from somewhere, and potentially calculate the total size of that data. Also, unless all the settings you showed are defaults, each of those items has an equavalent method somewhere in our classes. If you're using the defaults, of course things will be short, but everybody has a different idea of what's default and sometimes that takes a couple of lines to set up.

  14. #14
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,055
    Mentioned
    16 Post(s)
    Tagged
    3 Thread(s)
    I understand the purpose behind the interface but the system should provide a simple means of achieving common tasks without needing to write separate classes. The commonality seems to be that the component can do many things… if you write it yourself. I might as well write my own in that case. Please don't hesitate to correct me if I'm incorrect about this though. I tend to keep pagination simple stupid. The pagination class just pedals numbers and provides a quick way to build a drop down and list with appropriate links. What is done with the numbers could be anything.

  15. #15
    Spirit Coder allspiritseve's Avatar
    Join Date
    Dec 2002
    Location
    Ann Arbor, MI (USA)
    Posts
    648
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by oddz View Post
    Well… it seems like you have made the entire system to loose in my opinion. The last thing I want to do is be writing implementation – that is what the library is for.
    Sorry, can you elaborate? What are you forced to write? You have the option of replacing anything in the system-- but there are also very easy ways for it all to work together without you doing anything.

  16. #16
    SitePoint Guru
    Join Date
    Jun 2006
    Posts
    638
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by allspiritseve View Post
    [*]You can have the current page, total pages, items per page, and number of links set up automatically from the request - if you want.
    Basically, that's what mine does. Takes the passed in parameters, and creates a pagination block for them.

    Quote Originally Posted by allspiritseve View Post
    [*]You can set up different parameters for persisting vars in the url, in case there are name clashes.
    This should not happen, if it does, you have the same "variable names" that mean different things in your system.

    Quote Originally Posted by allspiritseve View Post
    [*]You can pass an adapter that retrieves the total number of pages and the actual items from a datasource - if you want. (Currently your code would have to do that manually.)[*]The most common example is a db query. Our A_Pagination_Adapter_Db will take a query, automatically run a COUNT(*) query, and then execute the query with the proper offset and limit. All you have to do is call getItems().
    Usually when you need a pagination for something, you have that something, so you know how many results you have the pagination for. Why would you want to write extra code to get the number you already have?
    Also, you don't want to do that query on every render / page load. You want to show a list of items, and keep the same list throughout the pagination.
    Example:
    - List the "new users" in your site.
    - Page 1, you see "bob" as the last result. (user takes 1 min to view the page and clicks next)
    - If you do another 'SELECT' here, users that made accounts in the last minute will have pushed "bob" on page 2. And you will see him again.
    So, you want to do the 'SELECT' once, cache it, and reuse it for all the pages. (might have to actually select the data of the records to display on this page)

    Quote Originally Posted by allspiritseve View Post
    [*]You can generate order links that maintain the current state, but chang the sorting on the datasource.
    Why would you want to see page 3 of your results ordered by 'Age' ASC and then go to page 3 of your results ordered by 'Age' DESC?
    From the user's point of view this makes no sense...

    Quote Originally Posted by allspiritseve View Post
    [*]You can create your own view - for example, to render pagination using a .tpl template file. If you do this, you won't need to use any of our HTML generation code. We do provide a URL helper that will generate links for you, including persisting state unrelated to pagination.
    If your making this in PHP, chances are you will only use it for webpages, and this is all you need:
    HTML Code:
    <ul class="pagination">
      <li>prev</li>
      <li>1</li>
      <li class="selected">2</li>
      <li>3</li>
      <li>4</li>
      <li class="last">5</li>
      <li>next</li>
    </ul>
    Then using CSS you can make it look any way you like.
    You will never have to change the HTML for your pagination.
    (Might want to add a "go to first" or "go to last")

    Quote Originally Posted by allspiritseve View Post
    I don't know about a one liner... you still need to get the actual data from somewhere, and potentially calculate the total size of that data. Also, unless all the settings you showed are defaults, each of those items has an equavalent method somewhere in our classes. If you're using the defaults, of course things will be short, but everybody has a different idea of what's default and sometimes that takes a couple of lines to set up.
    You already have the data, since that's what your paginating.
    Since everyone has their own defaults, they can just write a small wrapper for the class, see my previous post for an example.


    The idea is this:
    - Your class might look like it can do allot of things, but it doesn't have to do most if any of those things.
    - Every time you paginate something, you already have the items that need to be paginated.
    - To style the pagination block, you should use CSS, not change the HTML.
    - You want your block to be used easily, without the front end programmer/integrator having to know all the ins and outs of your block.
    - When you code these things, you want to get the most out of your time, so spending 1 week to do your system that will save 15 min every time you need to add a pagination block, might not be worth it. So keep it simple.

  17. #17
    Spirit Coder allspiritseve's Avatar
    Join Date
    Dec 2002
    Location
    Ann Arbor, MI (USA)
    Posts
    648
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Vali View Post
    Basically, that's what mine does. Takes the passed in parameters, and creates a pagination block for them.
    You're not following. If the core is being set up from the request, you don't need to pass in any parameters (except for non-default parameters that will never change). Page numbers, item total, current sort, all that can be set up automatically.

    Quote Originally Posted by Vali View Post
    This should not happen, if it does, you have the same "variable names" that mean different things in your system.
    Of course it can happen. Any other component on the page could use a variable called 'page' in the url, for example. If this happened, you can call setParamName ('page', 'curr_page') or whatever, and the new value will be used consistently throughout our classes.

    Quote Originally Posted by Vali View Post
    Usually when you need a pagination for something, you have that something, so you know how many results you have the pagination for. Why would you want to write extra code to get the number you already have?
    The most common use case for pagination is a database query. Most people consider it a best practice to do a COUNT(*) query and a LIMIT query, rather than pulling every single item from a table at once. In that scenario you don't 'already have' the total results.

    Quote Originally Posted by Vali View Post
    Also, you don't want to do that query on every render / page load. You want to show a list of items, and keep the same list throughout the pagination.
    Exactly, that's why we cache the total number of items, so expensive database queries are not called more often than is necesary.

    Quote Originally Posted by Vali View Post
    Why would you want to see page 3 of your results ordered by 'Age' ASC and then go to page 3 of your results ordered by 'Age' DESC?
    From the user's point of view this makes no sense...
    No, it doesn't, but there's other state that needs to be maintained. Total number of items, number of items per page, number of items in range, any other client state that is unrelated to pagination... our url helper can handle it all.

    Quote Originally Posted by Vali View Post
    If your making this in PHP, chances are you will only use it for webpages, and this is all you need: Then using CSS you can make it look any way you like. You will never have to change the HTML for your pagination.
    That's your opinion. Not everyone will be ok with that HTML. We allow some common changes, such as the label for first, next, previous, and last, changing the separator ('</li><li>') and adding a class for specific items. If people need more control over that (say, a designer who wants full control over the HTML) then a simple view can be constructed that uses a tpl file. Again, if that is done the code that renders HTML doesn't even need to be used.

    Quote Originally Posted by Vali View Post
    You already have the data, since that's what your paginating. Since everyone has their own defaults, they can just write a small wrapper for the class, see my previous post for an example. Every time you paginate something, you already have the items that need to be paginated.
    As I said before, the most common use case is a database query in which you don't already have the data. Also, most people don't want to have to extend code they're not going to use.

    Quote Originally Posted by Vali View Post
    Your class might look like it can do allot of things, but it doesn't have to do most if any of those things.
    In your opinion. That's why we offer simple wrappers with defaults for people with small requirements. Pagination can get a lot more complex than you think, when catering to a variety of use cases other than your own.

    Quote Originally Posted by Vali View Post
    To style the pagination block, you should use CSS, not change the HTML.
    Again, some people need control over some/all aspects of the HTML. Personally I'm not going to be using our HTML rendering classes, though I will be using our url helper. That's the level of customizability I need.

    Quote Originally Posted by Vali View Post
    You want your block to be used easily, without the front end programmer/integrator having to know all the ins and outs of your block.
    It can be as easy as your system: who would know what can be passed in your settings array to your class without reading some documentation? In the simplest use case, you could pass an array to our system, and it would paginate with 10 items per page, a range of 5 links, and maintain state across the request. And it just works.

    Quote Originally Posted by Vali View Post
    When you code these things, you want to get the most out of your time, so spending 1 week to do your system that will save 15 min every time you need to add a pagination block, might not be worth it. So keep it simple.
    It is simple. If you look at the classes, they're really not long or complex. And there's not altogether that many of them. But we've catered to a variety of use cases, many of which need more customizability than you seem to need. If you want to see how simple it can be, even with all of our classes, show me an example from start to finish using your class. That is, show how you retreive the items to paginate, first of all, and how they are displayed. I would be happy to show you an equivalent example with our class that will be of comparable length.

  18. #18
    SitePoint Guru
    Join Date
    Jun 2006
    Posts
    638
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Well, from your post I can see that you still need quite a few sites under your belt before you can see the differences between our two approaches.

    Example, my pagination code (all of it, classes, html, css and everything) does everything a pagination block needs to do, and it's under 400 lines of code (including comments).

    The usage for it is 20 lines long (1 php, +css), and I used it in 50+ sites of all types without needing to ever open up the class code / edit anything.

    What I'm trying to get across, is that I think it's worth it for you to take a step back, reanalyze your requirements (your project requirements, not pagination requirements), and then code only what you need, not what would be cool to have.

    Ps:
    If your using MySQL, COUNT(*) / LIMIT only works with smallish tables that do not change.
    Ex:
    Code:
    SELECT id,x,y FROM table WHERE 1 ORDER BY id ASC LIMIT 1000000,24;
    SELECT COUNT(*) FROM table WHERE 1;
    Is very slow compared to:
    Code:
    SELECT SQL_CALC_FOUND_ROWS id,x,y FROM table WHERE 1 ORDER BY id ASC LIMIT 1000000,24;
    SELECT FOUND_ROWS();
    Is very slow compared to:
    Code:
    SELECT id,x,y FROM table WHERE 1 AND id > 'LAST_ID' ORDER BY id ASC LIMIT 24;
    SELECT COUNT(*) FROM table WHERE 1;
    Which is very slow (in real world applications) compared to:
    Code:
    // First query that gets cached
    SELECT id FROM table WHERE 1 AND id > 'LAST_BATCH_ID' ORDER BY id ASC LIMIT 2400;
    SELECT COUNT(*) FROM table WHERE 1;
    
    // Load records for the page, from cache or DB
    // BATCH_IDS = 24_IDs_TO_DISPLAY minus CACHED_IDs
    SELECT id,x,y FROM table WHERE id IN ('BATCH_IDS') ORDER BY id ASC;
    // Cache the resulting objects
    Depending on your traffic and database size, even if it looks like you have more queries in this version, overall, you will end up with 90% less queries than the first 3 versions.

  19. #19
    Spirit Coder allspiritseve's Avatar
    Join Date
    Dec 2002
    Location
    Ann Arbor, MI (USA)
    Posts
    648
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Vali View Post
    Well, from your post I can see that you still need quite a few sites under your belt before you can see the differences between our two approaches.
    Look, this isn't a competition. I can do without you making comments about my supposed level of expertise. Arborint and I are looking for constructive criticism, and you telling me my opinion doesn't matter because of how many sites you think I've built in no way helps us improve our code.

    Quote Originally Posted by Vali View Post
    Example, my pagination code (all of it, classes, html, css and everything) does everything a pagination block needs to do, and it's under 400 lines of code (including comments).

    The usage for it is 20 lines long (1 php, +css), and I used it in 50+ sites of all types without needing to ever open up the class code / edit anything.
    That is your opinion. Just because it works for your 50 sites doesn't mean it's the perfect pagination solution. If you have specific comments on what we can improve, great. Otherwise, it's obvious you already have a solution that works for you.

  20. #20
    SitePoint Wizard
    Join Date
    Aug 2004
    Location
    California
    Posts
    1,672
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by oddz View Post
    I understand the purpose behind the interface but the system should provide a simple means of achieving common tasks without needing to write separate classes. The commonality seems to be that the component can do many things… if you write it yourself. I might as well write my own in that case. Please don't hesitate to correct me if I'm incorrect about this though. I tend to keep pagination simple stupid. The pagination class just pedals numbers and provides a quick way to build a drop down and list with appropriate links. What is done with the numbers could be anything.
    I think you make an excellent point. One of the things about pagination is that at its core it is a very simple thing. You can make simple pagination links in your view with a couple of if()s and it works great. But then if you look at a DataGrid solution you are then amazed at the mountain of code needed to provide an integrated solution. And since those solutions are monolithic, they are an all-or-nothing-at-all proposition.

    We are trying to build a layered solution that allows people to pick the level they want to deal with. That is the reason why this component is build with a bunch of small, single purpose classes (besides that being a good practice ). So the levels are:

    Page Numbers
    Code PHP:
    $pager = new A_Pagination_Core($datasource);
    echo $pager->getNextPage();
    Output: 8

    URLs
    Code PHP:
    $pager = new A_Pagination_Core($datasource);
    $url = new A_Pagination_Helper_Url($pager);
    echo $url->next();
    Output: http://mysite.com/mypage.php?page=8

    Links
    Code PHP:
    $pager = new A_Pagination_Core($datasource);
    $link = new A_Pagination_Helper_Link($pager);
    echo $link->next();
    Output: 8 (Note this is a link (<a>) to the URL above

    Standard View Helper
    Code PHP:
    $pager = new A_Pagination_Core($datasource);
    $view = new A_Pagination_View_Standard($pager);
    echo $view->render();
    Output: First Prev 5 6 7 8 9 Next Last

    All-in-one
    Code PHP:
    $view = new A_Pagination_Standalone($datasource);
    echo $view->render();
    Output: First Prev 5 6 7 8 9 Next Last

    We have also discussed possibly implementing different View Helpers to allow you to do pagination links like Google, phpBB, Flikr, etc. (e.g. A_Pagination_View_Google). All of the core functionality to build those is in the lower layers.

    It is also our intention to provide a simple DataGrid. (And we may do a Ajax-ified, edit-in-place version as well)

    Data Grid
    Code PHP:
    $pager = new A_Pagination_Request($datasource);
    $view = new A_Pagination_View_Datagrid($pager);
    echo $view->render();
    Output (obviously it would be in columns in a table):
    ID Title Category
    11. Foo One edit
    12. Bar Two edit
    13. Baz Two edit
    14. Faz One edit
    15. Boo Two edit
    First Prev 5 6 7 8 9 Next Last


    (Note that there are options to control how/when Prev/Next are displayed. Note also that ORDER BY link generation and datasource sorting functionality is also build-in to the core classes. I don't know that was mentioned above.)

    I hope that clarifies that this is not just a solution for you or Vali or me. It is a component that supports a number of levels and styles of solutions. It both takes the tedium out of wiring together a pagination solution, and makes it easy to provide features that you might not normally implement given time constraints. Hopefully one of these levels above supports how you like to do pagination. Also hopefully there are other levels here that you may find helpful in specific situations. So think of this as both a toolkit from which you can build custom pagination solutions, and also a set of pre-written solutions.
    Last edited by arborint; May 30, 2009 at 15:58.
    Christopher

  21. #21
    SitePoint Wizard
    Join Date
    Aug 2004
    Location
    California
    Posts
    1,672
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Vali View Post
    Well, from your post I can see that you still need quite a few sites under your belt before you can see the differences between our two approaches.

    Example, my pagination code (all of it, classes, html, css and everything) does everything a pagination block needs to do, and it's under 400 lines of code (including comments).

    The usage for it is 20 lines long (1 php, +css), and I used it in 50+ sites of all types without needing to ever open up the class code / edit anything.

    What I'm trying to get across, is that I think it's worth it for you to take a step back, reanalyze your requirements (your project requirements, not pagination requirements), and then code only what you need, not what would be cool to have.
    That seems a little rude. But if your system is as excellent as you say it is then please post it so we can learn from it. We are not presenting this code with the assumption that it needs no improvement. We presented it because we want critique of the code so we can improve it.

    I should also clarify that this component is not a solution just for you or me. It could be much smaller and simpler if it was. The goal is to provide a toolkit to solve pagination problems, plus some canned solutions. If there are other designs that provide a similar solution is a better way then that is of interest.

    Quote Originally Posted by Vali View Post
    Ps:
    If your using MySQL, COUNT(*) / LIMIT only works with smallish tables that do not change.
    Ex:

    Depending on your traffic and database size, even if it looks like you have more queries in this version, overall, you will end up with 90% less queries than the first 3 versions.
    It is not my experience that "COUNT(*) / LIMIT only works with smallish tables that do not change." I have used both COUNT() and FOUND_ROWS() on tables with millions of records where records are continuously being added -- without noticing a performance problem.

    The current Adapter example does a COUNT() with ORDER BY or LIMIT on the first query and fetches rows with ORDER BY or LIMIT on succeeding queries. That is a simple and straightfoward solution.

    If you think that your scheme of using the query cache, caching keys and using IN() is vastly superior then we could easily provide an Adapter or option in the Adapters to use that instead of COUNT(). The Adapters are very simple. Your input would be appreciated. I would be interested in doing some benchmarks on different styles. Our goal is to provide the best solution we can.
    Christopher

  22. #22
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,055
    Mentioned
    16 Post(s)
    Tagged
    3 Thread(s)
    Much better explanation.

    I took a look at some of the classes and was wondering how the implementation of A_Pagination_View_DataGrid is going to be addressed to account for different display circumstances.

    One recommendation I have is getter and setters rather then having the properties be directly available to the public interface. The one class I noticed with public properties was A_Pagination_Adapter_Db.

  23. #23
    SitePoint Wizard
    Join Date
    Aug 2004
    Location
    California
    Posts
    1,672
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by oddz View Post
    Much better explanation.
    Good. I think my initial introduction was not very clear. Your questions have helped clarify what people will want to know. If we release this component this stuff needs to be clear in the documentation. Thanks.

    Quote Originally Posted by oddz View Post
    I took a look at some of the classes and was wondering how the implementation of A_Pagination_View_DataGrid is going to be addressed to account for different display circumstances.
    That is a big question and I don't know the answers. That is why we have not implemented the DataGrid. We wanted to get the support classes in reasonable shape before we tackled that. I know we have talked about templated solutions, and other ways to deal with the display circumstances. Perhaps a decoratable solution like Zend Form implements. I don't know if there is one solution, so there may be several data grids. Any ideas you have or things you have found to work would be appreciated.

    Quote Originally Posted by oddz View Post
    One recommendation I have is getter and setters rather then having the properties be directly available to the public interface. The one class I noticed with public properties was A_Pagination_Adapter_Db.
    Good catch. Thanks. Those should not be public. The constructor and setOrderBy() method should be sufficient. Do you think we should also have setters for the constructor parameters, so setDb() and setSql() ? Do you find it helpful to be able to set any constructor arg later if you want to?
    Christopher

  24. #24
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,055
    Mentioned
    16 Post(s)
    Tagged
    3 Thread(s)
    Quote Originally Posted by arborint
    Any ideas you have or things you have found to work would be appreciated.
    Ironically I just began a few days ago thinking of ideas for my own "DataGrid" module for my ActiveRecord library. So I was hoping to get some different ideas or inspiration from the work you've been doing perhaps.

    Quote Originally Posted by arborint
    Do you think we should also have setters for the constructor parameters, so setDb() and setSql()?Do you find it helpful to be able to set any constructor arg later if you want to?
    Yes, it just makes the system that much more flexible.

  25. #25
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,055
    Mentioned
    16 Post(s)
    Tagged
    3 Thread(s)
    Some basic ideas that were running through by mind was to use a setter for the columns of the grid. The first parameter would represent the column name and the second the property that relates. However, this would only work for one level and doesn't provide a easy way to transform raw data.

    PHP Code:
    $albums MusicAlbum::find();

    $grid = new ActiveRecordDataGrid($albums); 

    $grid->setCol('Band','aritst');
    $grid->setCol('Title','title');
    $grid->setCol('Release Date','release_date');

    $grid->render(); 
    For example, if artist was factored out into a separate table and albums was dependent on artist then their wouldn't be a way to display the artist. This is because the artist would be a object not a primitive data type.


    PHP Code:
    $albums MusicAlbum::find(array('include'=>'music_artist'));

    $grid = new ActiveRecordDataGrid($albums); 

    $grid->setCol('Band','music_artst','name'); // perhaps?
    $grid->setCol('Title','title');
    $grid->setCol('Release Date','release_date');

    $grid->render(); 
    Those are just some ideas I've been working on. Nothing concrete yet though.

    You may not have this problem because the result set isn't multidimensional. So the previous method may work best.


Tags for this Thread

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •