Forums

Running subtotal over pagination

peter49 24 Aug, 2009
Hi,

in a connection I would like to show records that each contain some value. On each line I also would like to display the subtotal of those values in all records sofar.
No problem without pagination, but with pagination (the list is rather lengthy) the subtotal is reset for every page, and that's not what I want. I tried a solution defining a session variable for the subtotal. It works, no more resets, but - as you might expect - there will be never a reset anymore, until I start a new session. And that's one bridge too far.
Any reset of the session variable (even inside the WHERE clause) brings back the reset-per-page.
Does a solution exist or will this be another requirement for a new pagination system?

Regards,
Peter
GreyHead 25 Aug, 2009
Hi Peter,

In the header box try adding some code that looks for the session variable and runs a new database call to get the subtotal if it isn't set. That's the best suggestion I have for the moment. I don't think that improved pagination code will help.

Bob
peter49 25 Aug, 2009
Hi Bob,

thank you for your answer. Somehow (you never know how these processes go) it led me to a solution I think is correct (at least it works perfectly in my situation!), which is shown in this post, as a kind of tutorial for whoever wants to use it.

When thinking about your answer, I realised, that recalculation has to be part of the solution, but when you only do it when the subtotal isn't set, the result will certainly be incorrect. Not only can the user switch to the next page, but also to the previous one, even to a randomly selected page, including the current one.
Therefore you need to (re)calculate the subtotal whenever the first record of the new page does not immediately follow the last record of the previous page.

Implementing this strategy was a nice programming exercise! I did it by defining two session variables: subtotal and lastrec, maintaining over the session the following relation true: "subtotal is the sum of the values in records 1..lastrec" (a socalled invariant). Note that the record numbers in my application will ever be consecutive (cf_id is the record number) - no records will be deleted. Recalculation is required when the first record of a new page has a number != lastrec+1.

Another problem (at least for me) was your suggestion to do the recalculation in the header box. I couldn't find a simple method to determine the first record number of the new page there. In the body box each row can be accessed via $MyRow, but in the header box someting like $MyRows does not seem to exist (compare connection.php functions evalPart() and getBody()). Probably it can be found somewhere in $pageNav, but I didn't want to start a research project on that.

Instead I found a simple way to determine whether we were handling the first record in the body box by realising that PHP variables defined for the first record keep their values handling subsequent records. So when such a variable does not exist, we're handling the first record! Luckily when starting a next page, the variable is undefined again.

Following is the detailed solution, with lots of explaining inline comment. The field to be added in the subtotal only will contain small integers.

The following code goes into the header box. If the session variables do not exist yet they are defined and the invariant is established, otherwise we trust our abilities to maintain the invariant.

<?php

// We define two session variables: subtotal and lastrec
// We will maintain an invariant during the complete session:
//   subtotal contains the sum of the elementary fields in records 1..lastrec
// We first check whether subtotal exists. If not we establish the invariant.

  $session =& JFactory::getSession();
  if ($session->get('subtotal', -1) == -1) {
    $session->set('subtotal', 0);
    $session->set('lastrec', 0);
  }
?>


The following code comes at the start of the body box.

<?php

// At this place the invariant will hold.
// A new calculation of subtotal is only required for the first record on the
// page, and only when its record number is not lastrec+1
 
// First check whether we are handling the first record on the page.
// This is done by checking for the (non-)existence of a variable $nextrec,
// that is set for every record (after this test!).
// We abuse the fact that such a variable is undefined upon the start of each
// execution of this connection, and maintains its value over the body records.

  $session =& JFactory::getSession();  // Required for all records
  if (!$nextrec) {
    $nextrec = 1;

// Here is the code for handling the first record on the page.
// Now we check whether recalculation is required.

    $firstrec = $MyRow->cf_id;   
    $lastrec = $session->get('lastrec', 0);
    if ($firstrec != $lastrec + 1) {

// Yes, recalculation is required, read all previous records

      $db =& JFactory::getDBO();
      $query = "
        SELECT `cf_id`,`element_value`
        FROM `jos_chronoforms_mytable`
        WHERE `cf_id`<'".$firstrec."' 
        ORDER BY `cf_id`";
      $db->setQuery($query);
      $myrows = $db->loadObjectList();

// Recalculate the subtotal

      $subtotal = 0;
      foreach ($myrows as $myrow) {
        $subtotal += $myrow->element_value;
      }

// and reestablish the invariant

      $session->set('subtotal', $subtotal);
      $session->set('lastrec', $firstrec - 1);
    }
  }

// The following code is executed for all records in the current page
// It calculates the subtotal for each record and maintains the invariant.

  $subtotal = $session->get('subtotal', 0) + $MyRow->element_value;
  $session->set('subtotal', $subtotal);
  $session->set('lastrec', $MyRow->cf_id);
?>


In subsequent HTML code (the usual <tr> and <td> stuff) the value of $subtotal can be used.
Please let me know when you find any shortcomings or improvements.

Regards,
Peter
peter49 26 Aug, 2009
Hi Bob,

after all it was pretty easy to obtain information on the current page in the header box. For all pages $pageNav->limitstart gives the number of the last record before this page. The availability of this information allows a much simpler solution: just calculate the subtotal in all cases. Nomore requirements to keep track of the last record myself.
The following implementation replaces the previous one completely.

Code for the header box:

<?php
// We define a session variable: subtotal
// We have access to $pageNav->limitstart 
//    or alternatively JRequest::getVar('limitstart', 0)
// denoting the last record number before the first record on this page.
// We just recalculate the subtotal in all cases and store the value for
// access in the body box.

  $prevrec = $pageNav->limitstart;
  $subtotal = 0;
  if ($prevrec > 0) {
    $db =& JFactory::getDBO();
    $query = "
        SELECT `cf_id`,`element_value`
        FROM `jos_chronoforms_mytable`
        WHERE `cf_id`<='".$prevrec."' 
        ORDER BY `cf_id`";
    $db->setQuery($query);
    $myrows = $db->loadObjectList();

    foreach ($myrows as $myrow) {
      $subtotal += $myrow->element_value;
    }
  }
  $session->set('subtotal', $subtotal);
?>


The code for the (begin of the) body box:

<?php
// Adapt the subtotal for this record
  $subtotal = $session->get('subtotal', 0) + $MyRow->element_value;
  $session->set('subtotal', $subtotal);
?>

In subsequent HTML code (the usual <tr> and <td> stuff) the value of $subtotal can be used.

Regards,
Peter
GreyHead 05 Sep, 2009
Hi Peter,

Looks good - well done.

Bob
sendas 03 Nov, 2009
I have no Php experince. but I can seem to hack my way around.

How do I display $subtotal in the header or body. Your code examples are already in place. Just not sure howto call the subtotal
GreyHead 03 Nov, 2009
Hi sendas,

<?php echo $subtotal; ?>

Bob
This topic is locked and no more replies can be posted.