QDatagrid performance
I need some guidance on using a QDatagrid.
I've created a datagrid, which includes a check box and five text boxes. The check box has an ajax action to call a method, which does a couple of calculations (very simple +/-) and updates three of the five text boxes. This all works very well. However, the performance is incredibly bad. I've done some timings and the called method itself is negligible and remains the same, regardless of how many items are in the datagrid. However, the longer the list in the datagrid gets, the longer it takes to update the form. If there's only 15 or 20 items in the list, it's not too bad, but if that list gets much longer, the performance get really bad. Is QCubed updating the entire datagrid list?
I've thought about using pagination, but the customer won't have it. He wants to have all details listed in the datagrid. I'm wondering if it would perform much better, if I just created my own list, using QPanels, within a parent QPanel, and just manage it in arrays, similar to the way the QSortable plugin works, or is there something I can do to improve QDatagrid performance?

Chances are that yes, you're refreshing the entire data grid on an ajax hit, and that the whole thing is being re-bound and re-sent to the client.
How is your check box built and rendered? It should be possible to isolate those controls and update them via ajax without causing a datagrid refresh.
Vexed, the datagrid is inside a QPanel, which has AutoRenderChildren = true, so there's no template file for that QPanel. The datagrid is created like this:
$this->dtgInvPmts = new QDataGrid($this, 'dtgInvPmts');
$this->dtgInvPmts->CellPadding = 0;
$this->dtgInvPmts->CellSpacing = 0;
// Add Columns to the datagrid
$this->dtgInvPmts->AddColumn(new QDataGridColumn('Apply Full Pmt', '<?= $_FORM->getCheckBox_Render($_ITEM) ?>','HtmlEntities=false'));
$this->dtgInvPmts->AddColumn(new QDataGridColumn('Invoice No', '<?= $_FORM->getInvoiceNo_Render($_ITEM) ?>','HtmlEntities=false'));
$this->dtgInvPmts->AddColumn(new QDataGridColumn('Inv Date', '<?= $_FORM->getInvoiceDate_Render($_ITEM) ?>','HtmlEntities=false'));
$this->dtgInvPmts->AddColumn(new QDataGridColumn('Unpaid Balance', '<?= $_FORM->getUnpaidBalance_Render($_ITEM) ?>','HtmlEntities=false'));
$this->dtgInvPmts->AddColumn(new QDataGridColumn('Payment', '<?= $_FORM->getPayment_Render($_ITEM) ?>','HtmlEntities=false'));
$this->dtgInvPmts->AddColumn(new QDataGridColumn('Discount', '<?= $_FORM->getDiscount_Render($_ITEM) ?>','HtmlEntities=false'));
$this->dtgInvPmts->AddColumn(new QDataGridColumn('Balance Due', '<?= $_FORM->getBalanceDue_Render($_ITEM) ?>','HtmlEntities=false'));
// Specify the Datagrid's Data Binder method
$this->dtgInvPmts->SetDataBinder('dtgInvPmts_Bind');
$objStyle = $this->dtgInvPmts->RowStyle;
$objStyle->FontSize = 12;
$objStyle = $this->dtgInvPmts->AlternateRowStyle;
$objStyle->BackColor = '#eaeaea';
$objStyle = $this->dtgInvPmts->HeaderRowStyle;
$objStyle->ForeColor = 'white';
$objStyle->BackColor = '#0B0B3B';
Here's the method that actually creates the check box:
public function getCheckBox_Render(Arcashrcptsinvpmts $objArcashrcptsinvpmts) {
$strControlId = 'Apply' . $objArcashrcptsinvpmts->Id;
// See if it exists already
$Apply = $this->GetControl($strControlId);
if (!$Apply) {
$Apply = new QCheckBox($this->pnlCashrecptentry->dtgInvPmts, $strControlId);
$Apply->Text = 'Pay Invoice' ;
$Apply->ToolTip = QApplication::Translate("Click to Pay Invoice In Full, if possible.");
$Apply->AddCssClass("pmtpmt");
$Apply->AddAction(new QClickEvent(), new QAjaxAction("ApplyCheckBox_Click"));
if ($objArcashrcptsinvpmts->PmtRecd > 0 || $objArcashrcptsinvpmts->Discount > 0) {
$Apply->Checked = true;
}
// Pass the Id as Action Parameter
$Apply->ActionParameter = $objArcashrcptsinvpmts->Id;
}
return $Apply->Render(false);
}
So, it's very much like in the example site.
After some testing, I discovered it gets even worse.
The QForm includes a control that contains the total amount of the payment, which has not yet been "used" against payment of an invoice, included in the datagrid list. So, when the user clicks the "Apply" check box, the called method, subtracts the amount of the invoice from the total amount. I'm noticing, if I just go down the list, clicking "Apply" check boxes, without waiting for the total amount to be updated, the first and list item clicked are not subtracted from that total amount in the QForm. However, each item clicked is updated properly. Very weird.
What is your ApplyCheckBox_Click function doing? If it causes a refresh of the panel or the datagrid, that would explain the performance hit.
As for the AJAX, the browser probably has time to fire off all the AJAX requests, but having it parse multiple responses at once may not work as expected, with the browser dropping some, dealing with them out of order, or (most likely) using incomplete data to send the next AJAX request (eg: without the previous one's response applied).
ApplyCheckBox_Click updates other controls on the same datagrid line and it updates a control on the QForm, which is the parent of the QPanel, which is the parent of the datagrid. However, only values for controls contained on the same datagrid line as the check box are updated within the QPanel.
I'm beginning to think I'll need to do this in a javascript, rather than using an AjaxAction. What do you think of that idea, or do you have another suggestion that might work for me?
So that's your issue. Any control you refresh will auto-refresh its children. So by updating the parent of the QPanel, it's causing the QPanel and all its children to be refreshed as well.
Vexed, so if I put those controls, which are now being updated in the QForm, into their own QPanel, it will stop the entire datagrid list from being update? That is it will only refresh the modified QPanel and only the modified row of the datagrid?
That's apparently not the cause of the problem. I moved the controls from the QForm into a QPanel and the performance hasn't improved at all. I'm now pursuing the use of javascript, so it all happens client side.
Ok, I think I misunderstood. Updating or refreshing a control auto-refreshes all of that control's children. Sibling controls should not affect each other. Unfortunately, I don't have time to look closely at your code to see what might be causing this. :(
Vexed, thanks for the advice. I'm already working on moving all that code to javascript, which will actually work out better, because it will eliminate all the traffic and nothing will actually get updated, until the user presses the "Save" button. I've already eliminated the datagrid and substituted QPanels, which seems to at least display much faster.
Thanks for all your help!
LaCeja,
I've had a few cases with big pages like yours, and it's my experience that moving as much of your actions into javascript as possible is the shortest path to performance. Another thing I've done for performance reasons is to reduce my controls to minimum by e.g. getting rid of QLabel's and any other controls that are not absolutely necessary in the form (e.g. if the checkbox is only to do some javascript actions, then I won't use QCheckbox, but just render a html checkbox). Of course this is mostly useful if you're using the default state form handler.
However, it should be quite easy to find out what is actually being refreshed with your ajax actions. Both Firebug and a half-decent debugging support in your php IDE can help you there. Do you have these tools setup and working?
-Vartan
Vartan:
Thanks so much for the response. In this case, the problem isn't always that the form is so large. Most of the time it's fairly small. However, there will be times, when a user has a customer, who either has a large volume of invoices to pay, regularly, or simply hasn't made a payment in a long time. So, unfortunately, I need to prepare for the worst case. With 250 invoices in the list, it does take quite awhile for the page to load. However, the javascript that helps the user apply the payment across multiple invoices works just as fast on a page with one invoice as on one with 250. So, the user will just have to suffer the slow loading (and slow saving) page on those occasions.
The upside is, I'm learning some javascript and that's a good thing. Now that I've played with it, I can see where I have other applications for it. Might as well leverage some of the power otherwise wasted on the users end.
Thanks again, I appreciate the help.