New implementation for displaying long lists: pagination

SVN:trunk[1297]
This commit is contained in:
Denis Flaven
2011-06-23 11:01:03 +00:00
parent 46edbbbd2b
commit 43315f50ea
6 changed files with 168 additions and 102 deletions

View File

@@ -560,8 +560,15 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
$sClassName = $oSet->GetFilter()->GetClass();
$aAttribs = array();
$sZListName = isset($aExtraParams['zlist']) ? ($aExtraParams['zlist']) : 'list';
$aList = self::FlattenZList(MetaModel::GetZListItems($sClassName, $sZListName));
$aList = array_merge($aList, $aExtraFields);
if ($sZListName !== false)
{
$aList = self::FlattenZList(MetaModel::GetZListItems($sClassName, $sZListName));
$aList = array_merge($aList, $aExtraFields);
}
else
{
$aList = $aExtraFields;
}
// Filter the list to removed linked set since we are not able to display them here
foreach($aList as $index => $sAttCode)
@@ -616,7 +623,7 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
{
if (!$bSingleSelectMode)
{
$aAttribs['form::select'] = array('label' => "<input type=\"checkbox\" onClick=\"CheckAll('.selectList{$iListId}:not(:disabled)', this.checked);\"></input>", 'description' => Dict::S('UI:SelectAllToggle+'));
$aAttribs['form::select'] = array('label' => "<input type=\"checkbox\" onClick=\"CheckAll('.selectList{$iListId}:not(:disabled)', this.checked);\" class=\"checkAll\"></input>", 'description' => Dict::S('UI:SelectAllToggle+'));
}
else
{
@@ -634,14 +641,14 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
$aValues = array();
$bDisplayLimit = isset($aExtraParams['display_limit']) ? $aExtraParams['display_limit'] : true;
$iMaxObjects = -1;
if ($bDisplayLimit && $bTruncated)
{
//if ($bDisplayLimit && $bTruncated)
//{
if ($oSet->Count() > MetaModel::GetConfig()->GetMaxDisplayLimit())
{
$iMaxObjects = MetaModel::GetConfig()->GetMinDisplayLimit();
$oSet->SetLimit($iMaxObjects);
}
}
//}
$oSet->Seek(0);
while (($oObj = $oSet->Fetch()) && ($iMaxObjects != 0))
{
@@ -699,58 +706,7 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
{
$aExtraParams['query_params'][$sName] = $sValue;
}
if ($bDisplayLimit && $bTruncated && ($oSet->Count() > MetaModel::GetConfig()->GetMaxDisplayLimit()))
{
// list truncated
$aExtraParams['display_limit'] = true;
$sHtml .= '<tr class="containerHeader"><td><span id="lbl_'.$iListId.'">'.$sCollapsedLabel.'</span>&nbsp;&nbsp;<a class="truncated" id="trc_'.$iListId.'">'.$sLinkLabel.'</a></td><td>';
$oPage->add_ready_script(
<<<EOF
$('#$iListId table.listResults').addClass('truncated');
$('#$iListId table.listResults tr:last td').addClass('truncated');
EOF
);
}
else if ($bDisplayLimit && !$bTruncated && ($oSet->Count() > MetaModel::GetConfig()->GetMaxDisplayLimit()))
{
// Collapsible list
$aExtraParams['display_limit'] = true;
$sHtml .= '<tr class="containerHeader"><td><span id="lbl_'.$iListId.'">'.Dict::Format('UI:CountOfResults', $oSet->Count()).'</span><a class="truncated" id="trc_'.$iListId.'">'.Dict::S('UI:CollapseList').'</a></td><td>';
}
$aExtraParams['truncated'] = false; // To expand the full list when clicked
$sExtraParamsExpand = addslashes(str_replace('"', "'", json_encode($aExtraParams))); // JSON encode, change the style of the quotes and escape them
$oPage->add_ready_script(
<<<EOF
// Handle truncated lists
$('#trc_$iListId').click(function()
{
var state = {};
var currentState = $.bbq.getState( this.id, true ) || 'close';
// Toggle the state!
if (currentState == 'close')
{
state[ this.id ] = 'open';
}
else
{
state[ this.id ] = 'close';
}
$.bbq.pushState( state );
$(this).trigger(state[this.id]);
});
$('#trc_$iListId').unbind('open');
$('#trc_$iListId').bind('open', function()
{
ReloadTruncatedList('$iListId', '$sFilter', '$sExtraParamsExpand');
});
$('#trc_$iListId').unbind('close');
$('#trc_$iListId').bind('close', function()
{
TruncateList('$iListId', $iMinDisplayLimit, '$sCollapsedLabel', '$sLinkLabel');
});
EOF
);
if ($bDisplayMenu)
{
$oMenuBlock = new MenuBlock($oSet->GetFilter());
@@ -768,6 +724,80 @@ EOF
$sHtml .= $oPage->GetTable($aAttribs, $aValues);
$sHtml .= '</td></tr>';
$sHtml .= '</table>';
if ($oSet->Count() > MetaModel::GetConfig()->GetMaxDisplayLimit())
{
$iCount = $oSet->Count();
$sHtml =
<<<EOF
<div id="pager{$iListId}" class="pager">
</p><span id="total">0</span> items.&nbsp;&nbsp;<span class="selectedCount"></span> item(s) selected.</p>
<p><table class="pagination"><tr><td>Pages:</td><td><img src="../images/first.png" class="first"/></td>
<td><img src="../images/prev.png" class="prev"/></td>
<td><span id="index"></span></td>
<td><img src="../images/next.png" class="next"/></td>
<td><img src="../images/last.png" class="last"/></td>
<td><select class="pagesize">
<option selected="selected" value="10">10</option>
<option value="20">20</option>
<option value="30">30</option>
<option value="40">40</option>
</select>
items per page.</td>
<td><span id="loading">&nbsp;</span></td>
</tr>
</table>
<input type="hidden" name="selectionMode" value="positive"></input>
</div>
EOF
.$sHtml;
//$oP->add_ready_script("table.tablesorter( { headers: { 0: {sorter: false}}, widgets: ['myZebra', 'truncatedList']} ).tablesorterPager({container: $('#pager')});\n");
$sExtraParams = addslashes(str_replace('"', "'", json_encode($aExtraParams))); // JSON encode, change the style of the quotes and escape them
$sSelectMode = '';
$sHeaders = '';
if ($bSelectMode)
{
$sSelectMode = $bSingleSelectMode ? 'single' : 'multiple';
$sHeaders = 'headers: { 0: {sorter: false}},';
}
$sDisplayKey = ($bViewLink) ? 'true' : 'false';
$sDisplayList = json_encode($aList);
$sCssCount = isset($aExtraParams['cssCount']) ? ", cssCount: '{$aExtraParams['cssCount']}'" : '';
$oPage->add_ready_script("$('#{$iListId} table.listResults').tablesorter( { $sHeaders widgets: ['myZebra', 'truncatedList']} ).tablesorterPager({container: $('#pager{$iListId}'), totalRows:$iCount, filter: '$sFilter', extra_params: '$sExtraParams', select_mode: '$sSelectMode', displayKey: $sDisplayKey, displayList: $sDisplayList $sCssCount});\n");
}
else
{
$sHeaders = '';
if ($bSelectMode)
{
$sHeaders = 'headers: { 0: {sorter: false}},';
}
$oPage->add_ready_script("$('#{$iListId} table.listResults').tablesorter( { $sHeaders widgets: ['myZebra', 'truncatedList']} );\n");
// Manage how we update the 'Ok/Add' buttons that depend on the number of selected items
if (isset($aExtraParams['cssCount']))
{
$sCssCount = $aExtraParams['cssCount'];
if ($bSingleSelectMode)
{
$sSelectSelector = ":radio[name^=selectObj]";
}
else
{
$sSelectSelector = ":checkbox[name^=selectObj]";
}
$oPage->add_ready_script(
<<<EOF
$('#{$iListId} table.listResults $sSelectSelector').change(function() {
var c = $('{$sCssCount}');
var v = $('#{$iListId} table.listResults $sSelectSelector:checked').length;
c.val(v);
c.trigger('change');
});
EOF
);
}
}
return $sHtml;
}
@@ -1898,7 +1928,7 @@ EOF
return $aDetails;
}
protected static function FlattenZList($aList)
static function FlattenZList($aList)
{
$aResult = array();
foreach($aList as $value)

View File

@@ -72,6 +72,7 @@ class iTopWebPage extends NiceWebPage
$this->add_linked_script("../js/ckeditor/ckeditor.js");
$this->add_linked_script("../js/ckeditor/adapters/jquery.js");
$this->add_linked_script("../js/jquery.qtip-1.0.min.js");
$this->add_linked_script("../js/jquery.tablesorter.pager.js");
$this->m_sInitScript =
<<< EOF
try
@@ -289,23 +290,6 @@ EOF
// End of Tabs handling
$("table.listResults").tableHover(); // hover tables
// Check each 'listResults' table for a checkbox in the first column and make the first column sortable only if it does not contain a checkbox in the header
$(".listResults").each( function()
{
var table = $(this);
var id = $(this).parent();
var checkbox = (table.find('th:first :checkbox').length > 0);
if (checkbox)
{
// There is a checkbox in the first column, don't make it sortable
table.tablesorter( { headers: { 0: {sorter: false}}, widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables
}
else
{
// There is NO checkbox in the first column, all columns are considered sortable
table.tablesorter( { widgets: ['myZebra', 'truncatedList']} ); // sortable and zebra tables
}
});
$(".date-pick").datepicker({
showOn: 'button',
buttonImage: '../images/calendar.png',
@@ -368,10 +352,6 @@ EOF
// }
// }
function formatItem(row) {
return row[0];
}
function goBack()
{
window.history.back();

View File

@@ -233,6 +233,7 @@ EOF
$sHTML .= "</div>\n";
$sHTML .= "<input type=\"button\" id=\"btn_cancel_{$this->iId}\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#ac_dlg_{$this->iId}').dialog('close');\">&nbsp;&nbsp;";
$sHTML .= "<input type=\"button\" id=\"btn_ok_{$this->iId}\" value=\"".Dict::S('UI:Button:Ok')."\" onClick=\"oACWidget_{$this->iId}.DoOk();\">";
$sHTML .= "<input type=\"hidden\" id=\"count_{$this->iId}\" value=\"0\">";
$sHTML .= "</form>\n";
$sHTML .= '</div></div>';
@@ -263,7 +264,7 @@ EOF
{
$oFilter = DBObjectSearch::FromOQL($sFilter);
$oBlock = new DisplayBlock($oFilter, 'list', false);
$oBlock->Display($oP, $this->iId, array('this' => $oObj, 'menu' => false, 'selection_mode' => true, 'selection_type' => 'single', 'display_limit' => false)); // Don't display the 'Actions' menu on the results
$oBlock->Display($oP, $this->iId.'_results', array('this' => $oObj, 'cssCount'=> '#count_'.$this->iId, 'menu' => false, 'selection_mode' => true, 'selection_type' => 'single', 'display_limit' => false)); // Don't display the 'Actions' menu on the results
}
catch(MissingQueryArgument $e)
{
@@ -272,7 +273,7 @@ EOF
$sOQL = 'SELECT '.$sRemoteClass;
$oFilter = DBObjectSearch::FromOQL($sOQL);
$oBlock = new DisplayBlock($oFilter, 'list', false);
$oBlock->Display($oP, $this->iId, array('menu' => false, 'selection_mode' => true, 'selection_type' => 'single', 'display_limit' => false)); // Don't display the 'Actions' menu on the results
$oBlock->Display($oP, $this->iId.'_results', array('cssCount'=> '#count_'.$this->iId, 'menu' => false, 'selection_mode' => true, 'selection_type' => 'single', 'display_limit' => false)); // Don't display the 'Actions' menu on the results
}
}

View File

@@ -270,7 +270,8 @@ EOF
$sHtml .= "<div id=\"SearchResultsToAdd_{$this->m_sAttCode}{$this->m_sNameSuffix}\" style=\"vertical-align:top;background: #fff;height:100%;overflow:auto;padding:0;border:0;\">\n";
$sHtml .= "<div style=\"background: #fff; border:0; text-align:center; vertical-align:middle;\"><p>".Dict::S('UI:Message:EmptyList:UseSearchForm')."</p></div>\n";
$sHtml .= "</div>\n";
$sHtml .= "<input type=\"button\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog('close');\">&nbsp;&nbsp;<input type=\"submit\" value=\"".Dict::S('UI:Button:Add')."\">";
$sHtml .= "<input type=\"hidden\" id=\"count_{$this->m_sAttCode}{$this->m_sNameSuffix}\" value=\"0\"/>";
$sHtml .= "<input type=\"button\" value=\"".Dict::S('UI:Button:Cancel')."\" onClick=\"$('#dlg_{$this->m_sAttCode}{$this->m_sNameSuffix}').dialog('close');\">&nbsp;&nbsp;<input id=\"btn_ok_{$this->m_sAttCode}{$this->m_sNameSuffix}\" type=\"submit\" value=\"".Dict::S('UI:Button:Add')."\">";
$sHtml .= "</div>\n";
$sHtml .= "</form>\n";
$sHtml .= "</div>\n";
@@ -332,12 +333,23 @@ EOF
}
$oSet = new CMDBObjectSet($oFilter);
$oBlock = new DisplayBlock($oFilter, 'list', false);
$oBlock->Display($oP, 'ResultsToAdd', array('menu' => false, 'selection_mode' => true, 'display_limit' => false)); // Don't display the 'Actions' menu on the results
$oBlock->Display($oP, "ResultsToAdd_{$this->m_sAttCode}", array('menu' => false, 'cssCount'=> '#count_'.$this->m_sAttCode.$this->m_sNameSuffix , 'selection_mode' => true, 'display_limit' => false)); // Don't display the 'Actions' menu on the results
}
public function DoAddObjects(WebPage $oP, $aLinkedObjectIds = array())
public function DoAddObjects(WebPage $oP, $sRemoteClass)
{
$aTable = array();
if ($sRemoteClass != '')
{
// assert(MetaModel::IsParentClass($this->m_sRemoteClass, $sRemoteClass));
$oFullSetFilter = new DBObjectSearch($sRemoteClass);
}
else
{
// No remote class specified use the one defined in the linkedset
$oFullSetFilter = new DBObjectSearch($this->m_sRemoteClass);
}
$aLinkedObjectIds = utils::ReadMultipleSelection($oFullSetFilter);
foreach($aLinkedObjectIds as $iObjectId)
{
$oLinkedObj = MetaModel::GetObject($this->m_sRemoteClass, $iObjectId);

View File

@@ -217,6 +217,43 @@ class utils
return $oDocument;
}
/**
* Interprets the results posted by a normal or paginated list (in multiple selection mode)
* @param $oFullSetFilter DBObjectSearch The criteria defining the whole sets of objects being selected
* @return Array An arry of object IDs corresponding to the objects selected in the set
*/
public static function ReadMultipleSelection($oFullSetFilter)
{
$aSelectedObj = utils::ReadParam('selectObject', array());
$sSelectionMode = utils::ReadParam('selectionMode', '');
if ($sSelectionMode != '')
{
// Paginated selection
$aExceptions = utils::ReadParam('storedSelection', array());
if ($sSelectionMode == 'positive')
{
// Only the explicitely listed items are selected
$aSelectedObj = $aExceptions;
}
else
{
// All items of the set are selected, except the one explicitely listed
$aSelectedObj = array();
$oFullSet = new DBObjectSet($oFullSetFilter);
$sClassAlias = $oFullSetFilter->GetClassAlias();
$oFullSet->OptimizeColumnLoad(array($sClassAlias => array('friendlyname'))); // We really need only the IDs but it does not work since id is not a real field
while($oObj = $oFullSet->Fetch())
{
if (!in_array($oObj->GetKey(), $aExceptions))
{
$aSelectedObj[] = $oObj->GetKey();
}
}
}
}
return $aSelectedObj;
}
public static function GetNewTransactionId()
{
return privUITransaction::GetNewTransactionId();

View File

@@ -154,27 +154,33 @@ class WebPage
$sHtml .= "<tbody>\n";
foreach($aData as $aRow)
{
if (isset($aRow['@class'])) // Row specific class, for hilighting certain rows
{
$sHtml .= "<tr class=\"{$aRow['@class']}\">\n";
}
else
{
$sHtml .= "<tr>\n";
}
foreach($aConfig as $sName=>$aAttribs)
{
$aMatches = array();
$sClass = isset($aAttribs['class']) ? 'class="'.$aAttribs['class'].'"' : '';
$sValue = ($aRow[$sName] === '') ? '&nbsp;' : $aRow[$sName];
$sHtml .= "<td $sClass>$sValue</td>\n";
}
$sHtml .= "</tr>\n";
$sHtml .= $this->GetTableRow($aRow, $aConfig);
}
$sHtml .= "</tbody>\n";
$sHtml .= "</table>\n";
return $sHtml;
}
public function GetTableRow($aRow, $aConfig)
{
$sHtml = '';
if (isset($aRow['@class'])) // Row specific class, for hilighting certain rows
{
$sHtml .= "<tr class=\"{$aRow['@class']}\">";
}
else
{
$sHtml .= "<tr>";
}
foreach($aConfig as $sName=>$aAttribs)
{
$sClass = isset($aAttribs['class']) ? 'class="'.$aAttribs['class'].'"' : '';
$sValue = ($aRow[$sName] === '') ? '&nbsp;' : $aRow[$sName];
$sHtml .= "<td $sClass>$sValue</td>";
}
$sHtml .= "</tr>";
return $sHtml;
}
/**
* Add some Javascript to the header of the page