Sorting of lists: sort is now always executed server-side via the mechanism used by paginated lists. This fixes Trac #411, #421, #520

SVN:trunk[1931]
This commit is contained in:
Denis Flaven
2012-03-27 15:27:15 +00:00
parent 144b5ae39f
commit 529b5d53fc
8 changed files with 252 additions and 186 deletions

View File

@@ -757,19 +757,17 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
}
foreach($aList as $sAttCode)
{
$aAttribs[$sAttCode] = array('label' => MetaModel::GetLabel($sClassName, $sAttCode), 'description' => MetaModel::GetDescription($sClassName, $sAttCode));
$oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode);
$aAttribs[$sAttCode] = array('label' => MetaModel::GetLabel($sClassName, $sAttCode), 'description' => $oAttDef->GetOrderByHint());
}
$aValues = array();
$bDisplayLimit = isset($aExtraParams['display_limit']) ? $aExtraParams['display_limit'] : true;
$iMaxObjects = -1;
//if ($bDisplayLimit && $bTruncated)
//{
if ($bDisplayLimit && ($oSet->Count() > MetaModel::GetConfig()->GetMaxDisplayLimit()))
{
$iMaxObjects = MetaModel::GetConfig()->GetMinDisplayLimit();
$oSet->SetLimit($iMaxObjects);
}
//}
if ($bDisplayLimit && ($oSet->Count() > MetaModel::GetConfig()->GetMaxDisplayLimit()))
{
$iMaxObjects = MetaModel::GetConfig()->GetMinDisplayLimit();
$oSet->SetLimit($iMaxObjects);
}
$oSet->Seek(0);
while (($oObj = $oSet->Fetch()) && ($iMaxObjects != 0))
{
@@ -811,14 +809,7 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
}
$sHtml .= '<table class="listContainer">';
$sColspan = '';
// if (isset($aExtraParams['block_id']))
// {
// $divId = $aExtraParams['block_id'];
// }
// else
// {
// $divId = 'missingblockid';
// }
$sFilter = $oSet->GetFilter()->serialize();
$iMinDisplayLimit = MetaModel::GetConfig()->GetMinDisplayLimit();
$sCollapsedLabel = Dict::Format('UI:TruncatedResults', $iMinDisplayLimit, $oSet->Count());
@@ -854,30 +845,62 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
{
$sHeader = Dict::Format('UI:Pagination:HeaderNoSelection', '<span id="total">'.$iCount.'</span>');
}
if ($bDisplayLimit && ($oSet->Count() > MetaModel::GetConfig()->GetMaxDisplayLimit()))
// All lists are now paginated in order to benefit from the SQL sort order
if (!$bDisplayLimit)
{
$sCombo = '<select class="pagesize">';
for($iPage = 1; $iPage < 5; $iPage++)
$sPagerStyle = 'style="display:none"'; // no limit: display the full table, so hide the "pager" UI
$iPageSize = -1; // Display all
}
else
{
$sPagerStyle = '';
$iPageSize = MetaModel::GetConfig()->GetMinDisplayLimit();
}
$iDefaultPageSize = MetaModel::GetConfig()->GetMinDisplayLimit();
$sCombo = '<select class="pagesize">';
for($iPage = 1; $iPage < 5; $iPage++)
{
$sSelected = ($iPage == $iPageSize) ? 'selected="selected"' : '';
$iNbItems = $iPage * $iDefaultPageSize;
$sCombo .= "<option $sSelected value=\"$iNbItems\">$iNbItems</option>";
}
$sSelected = (-1 == $iPageSize) ? 'selected="selected"' : '';
$sCombo .= "<option $sSelected value=\"-1\">".Dict::S('UI:Pagination:All')."</option>";
$sCombo .= '</select>';
$sPages = Dict::S('UI:Pagination:PagesLabel');
$sPageSizeCombo = Dict::Format('UI:Pagination:PageSize', $sCombo);
$iNbPages = ($iPageSize == -1) ? 1 : ceil($iCount / $iPageSize);
$aPagesToDisplay = array();
for($idx = 0; $idx <= min(4, $iNbPages-1); $idx++)
{
if ($idx == 0)
{
$sSelected = '';
if ($iPage == 1)
{
$sSelected = 'selected="selected"';
}
$iNbItems = $iPage * MetaModel::GetConfig()->GetMinDisplayLimit();
$sCombo .= "<option $sSelected value=\"$iNbItems\">$iNbItems</option>";
$aPagesToDisplay[$idx] = '<span page="0" class="curr_page">1</span>';
}
$sCombo .= "<option $sSelected value=\"-1\">".Dict::S('UI:Pagination:All')."</option>";
$sCombo .= '</select>';
$sPages = Dict::S('UI:Pagination:PagesLabel');
$sPageSizeCombo = Dict::Format('UI:Pagination:PageSize', $sCombo);
$sHtml =
else
{
$aPagesToDisplay[$idx] = "<span id=\"gotopage_$idx\" class=\"gotopage\" page=\"$idx\">".(1+$idx)."</span>";
}
}
$iLastPageIdx = $iNbPages - 1;
if (!isset($aPagesToDisplay[$iLastPageIdx]))
{
unset($aPagesToDisplay[$idx - 1]); // remove the last page added to make room for the very last page
$aPagesToDisplay[$iLastPageIdx] = "<span id=\"gotopage_$iLastPageIdx\" class=\"gotopage\" page=\"$iLastPageIdx\">... $iNbPages</span>";
}
$sPagesLinks = implode('', $aPagesToDisplay);
$sPagesList = '['.implode(',', array_keys($aPagesToDisplay)).']';
$sHtml =
<<<EOF
<div id="pager{$iListId}" class="pager">
<div id="pager{$iListId}" class="pager" $sPagerStyle>
<p>$sHeader</p>
<p><table class="pagination"><tr><td>$sPages</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><span id="index">$sPagesLinks</span></td>
<td><img src="../images/next.png" class="next"/></td>
<td><img src="../images/last.png" class="last"/></td>
<td>$sPageSizeCombo</td>
@@ -889,63 +912,62 @@ $sHtml =
</div>
EOF
.$sHtml;
$aArgs = $oSet->GetArgs();
$sExtraParams = addslashes(str_replace('"', "'", json_encode(array_merge($aExtraParams, $aArgs)))); // 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']}'" : '';
$iPageSize = MetaModel::GetConfig()->GetMinDisplayLimit();
$oSet->ApplyParameters();
$sOQL = addslashes($oSet->GetFilter()->serialize());
$oPage->add_ready_script("$('#{$iListId} table.listResults').tablesorter( { $sHeaders widgets: ['myZebra', 'truncatedList']} ).tablesorterPager({container: $('#pager{$iListId}'), totalRows:$iCount, size: $iPageSize, filter: '$sOQL', extra_params: '$sExtraParams', select_mode: '$sSelectMode', displayKey: $sDisplayKey, displayList: $sDisplayList $sCssCount});\n");
}
else
$aArgs = $oSet->GetArgs();
$sExtraParams = addslashes(str_replace('"', "'", json_encode(array_merge($aExtraParams, $aArgs)))); // JSON encode, change the style of the quotes and escape them
$sSelectMode = '';
$sHeaders = '';
if ($bSelectMode)
{
$sHtml =
<<<EOF
<div id="pager{$iListId}" class="pager">
<p>$sHeader</p>
</div>
EOF
.$sHtml;
$sHeaders = '';
if ($bSelectMode)
$sSelectMode = $bSingleSelectMode ? 'single' : 'multiple';
$sHeaders = 'headers: { 0: {sorter: false}},';
}
$sDisplayKey = ($bViewLink) ? 'true' : 'false';
// Protect against duplicate elements in the Zlist
$aUniqueOrderedList = array();
foreach($aList as $sAttCode)
{
$aUniqueOrderedList[$sAttCode] = true;
}
$aUniqueOrderedList = array_keys($aUniqueOrderedList);
$sDisplayList = json_encode($aUniqueOrderedList);
$sCssCount = isset($aExtraParams['cssCount']) ? ", cssCount: '{$aExtraParams['cssCount']}'" : '';
$oSet->ApplyParameters();
// Display the actual sort order of the table
$aRealSortOrder = $oSet->GetRealSortOrder();
$aDefaultSort = array();
$iColOffset = 0;
if ($bSelectMode)
{
$iColOffset += 1;
}
if ($bViewLink)
{
$iColOffset += 1;
}
foreach($aRealSortOrder as $sColCode => $bAscending)
{
$iPos = array_search($sColCode, $aUniqueOrderedList);
if ($iPos !== false)
{
$sHeaders = 'headers: { 0: {sorter: false}},';
$aDefaultSort[] = "[".($iColOffset+$iPos).",".($bAscending ? '0' : '1')."]";
}
$oPage->add_ready_script("$('#{$iListId} table.listResults').tableHover().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']))
else if($sColCode == 'friendlyname' && $bViewLink)
{
$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);
$('#{$iListId} .selectedCount').text(v);
c.trigger('change');
});
EOF
);
$aDefaultSort[] = "[".($iColOffset-1).",".($bAscending ? '0' : '1')."]";
}
}
$sSortList = '';
if (count($aDefaultSort) > 0)
{
$sSortList = ', sortList: ['.implode(',', $aDefaultSort).']';
}
$sOQL = addslashes($oSet->GetFilter()->serialize());
$oPage->add_ready_script(
<<<EOF
var oTable = $('#{$iListId} table.listResults');
oTable.tablesorter( { $sHeaders widgets: ['myZebra', 'truncatedList'] $sSortList} ).tablesorterPager({container: $('#pager{$iListId}'), totalRows:$iCount, size: $iPageSize, filter: '$sOQL', extra_params: '$sExtraParams', select_mode: '$sSelectMode', displayKey: $sDisplayKey, displayList: $sDisplayList $sCssCount});
EOF
);
return $sHtml;
}

View File

@@ -51,29 +51,23 @@ class iTopWebPage extends NiceWebPage
$this->m_sMenu = "";
$this->m_sMessage = '';
$this->SetRootUrl(utils::GetAbsoluteUrlAppRoot());
$oAppContext = new ApplicationContext();
$sExtraParams = $oAppContext->GetForLink();
$sAppContext = addslashes($sExtraParams);
$this->add_header("Content-type: text/html; charset=utf-8");
$this->add_header("Cache-control: no-cache");
$this->add_linked_stylesheet("../css/jquery.treeview.css");
$this->add_linked_stylesheet("../css/jquery.autocomplete.css");
$this->add_linked_script('../js/jquery.layout.min.js');
$this->add_linked_script('../js/jquery.ba-bbq.min.js');
$this->add_linked_script("../js/jquery.tablehover.js");
$this->add_linked_script("../js/jquery.treeview.js");
$this->add_linked_script("../js/jquery.autocomplete.js");
$this->add_linked_script("../js/jquery.positionBy.js");
$this->add_linked_script("../js/jquery.popupmenu.js");
$this->add_linked_script("../js/date.js");
$this->add_linked_script("../js/jquery.tablesorter.min.js");
$this->add_linked_script("../js/jquery.blockUI.js");
$this->add_linked_script("../js/utils.js");
$this->add_linked_script("../js/swfobject.js");
$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
@@ -162,46 +156,6 @@ EOF
;
$this->add_ready_script(
<<< EOF
//add new widget called TruncatedList to properly display truncated lists when they are sorted
$.tablesorter.addWidget({
// give the widget a id
id: "truncatedList",
// format is called when the on init and when a sorting has finished
format: function(table)
{
// Check if there is a "truncated" line
this.truncatedList = false;
if ($("tr td.truncated",table).length > 0)
{
this.truncatedList = true;
}
if (this.truncatedList)
{
$("tr td",table).removeClass('truncated');
$("tr:last td",table).addClass('truncated');
}
}
});
$.tablesorter.addWidget({
// give the widget a id
id: "myZebra",
// format is called when the on init and when a sorting has finished
format: function(table)
{
// Replace the 'red even' lines by 'red_even' since most browser do not support 2 classes selector in CSS, etc..
$("tbody tr:even",table).addClass('even');
$("tbody tr.red:even",table).removeClass('red').removeClass('even').addClass('red_even');
$("tbody tr.orange:even",table).removeClass('orange').removeClass('even').addClass('orange_even');
$("tbody tr.green:even",table).removeClass('green').removeClass('even').addClass('green_even');
// In case we sort again the table, we need to remove the added 'even' classes on odd rows
$("tbody tr:odd",table).removeClass('even');
$("tbody tr.red_even:odd",table).removeClass('even').removeClass('red_even').addClass('red');
$("tbody tr.orange_even:odd",table).removeClass('even').removeClass('orange_even').addClass('orange');
$("tbody tr.green_even:odd",table).removeClass('even').removeClass('green_even').addClass('green');
}
});
// Adjust initial size
$('.v-resizable').each( function()
@@ -307,7 +261,7 @@ EOF
});
// End of Tabs handling
$("table.listResults").tableHover(); // hover tables
$(".date-pick").datepicker({
showOn: 'button',
buttonImage: '../images/calendar.png',
@@ -408,21 +362,7 @@ EOF
if ($('#rawOutput > div').html() != '')
{
$('#rawOutput').dialog( {autoOpen: true, modal:false});
}
}
function AddAppContext(sURL)
{
var sContext = '$sAppContext';
if (sContext.length > 0)
{
if (sURL.indexOf('?') == -1)
{
return sURL+'?'+sContext;
}
return sURL+'&'+sContext;
}
return sURL;
}
var oUserPreferences = $sUserPrefs;
@@ -535,19 +475,7 @@ EOF
public function output()
{
$sAbsURLAppRoot = addslashes($this->m_sRootUrl);
$sAbsURLModulesRoot = addslashes(utils::GetAbsoluteUrlModulesRoot());
$this->add_script(
<<<EOF
function GetAbsoluteUrlAppRoot()
{
return '$sAbsURLAppRoot';
}
function GetAbsoluteUrlModulesRoot()
{
return '$sAbsURLModulesRoot';
}
EOF
);
$this->set_base($this->m_sRootUrl.'pages/');
$sForm = $this->GetSiloSelectionForm();
$this->DisplayMenu(); // Compute the menu

View File

@@ -38,12 +38,92 @@ class NiceWebPage extends WebPage
$this->m_aReadyScripts = array();
$this->add_linked_script("../js/jquery-1.4.2.min.js");
$this->add_linked_stylesheet('../css/ui-lightness/jquery-ui-1.8.2.custom.css');
$this->add_style('body { overflow: auto; }');
$this->add_linked_script('../js/jquery-ui-1.8.2.custom.min.js');
$this->add_linked_script("../js/hovertip.js");
// table sorting
$this->add_linked_script("../js/jquery.tablesorter.min.js");
$this->add_linked_script("../js/jquery.tablesorter.pager.js");
$this->add_linked_script("../js/jquery.tablehover.js");
$this->add_ready_script(
<<< EOF
//add new widget called TruncatedList to properly display truncated lists when they are sorted
$.tablesorter.addWidget({
// give the widget a id
id: "truncatedList",
// format is called when the on init and when a sorting has finished
format: function(table)
{
// Check if there is a "truncated" line
this.truncatedList = false;
if ($("tr td.truncated",table).length > 0)
{
this.truncatedList = true;
}
if (this.truncatedList)
{
$("tr td",table).removeClass('truncated');
$("tr:last td",table).addClass('truncated');
}
}
});
$.tablesorter.addWidget({
// give the widget a id
id: "myZebra",
// format is called when the on init and when a sorting has finished
format: function(table)
{
// Replace the 'red even' lines by 'red_even' since most browser do not support 2 classes selector in CSS, etc..
$("tbody tr:even",table).addClass('even');
$("tbody tr.red:even",table).removeClass('red').removeClass('even').addClass('red_even');
$("tbody tr.orange:even",table).removeClass('orange').removeClass('even').addClass('orange_even');
$("tbody tr.green:even",table).removeClass('green').removeClass('even').addClass('green_even');
// In case we sort again the table, we need to remove the added 'even' classes on odd rows
$("tbody tr:odd",table).removeClass('even');
$("tbody tr.red_even:odd",table).removeClass('even').removeClass('red_even').addClass('red');
$("tbody tr.orange_even:odd",table).removeClass('even').removeClass('orange_even').addClass('orange');
$("tbody tr.green_even:odd",table).removeClass('even').removeClass('green_even').addClass('green');
}
});
$("table.listResults").tableHover(); // hover tables
EOF
);
$this->add_linked_stylesheet("../css/light-grey.css");
$this->m_sRootUrl = '../';
}
$this->m_sRootUrl = utils::GetAbsoluteUrlAppRoot();
$sAbsURLAppRoot = addslashes($this->m_sRootUrl);
$sAbsURLModulesRoot = addslashes(utils::GetAbsoluteUrlModulesRoot());
$oAppContext = new ApplicationContext();
$sExtraParams = $oAppContext->GetForLink();
$sAppContext = addslashes($sExtraParams);
$this->add_script(
<<<EOF
function GetAbsoluteUrlAppRoot()
{
return '$sAbsURLAppRoot';
}
function GetAbsoluteUrlModulesRoot()
{
return '$sAbsURLModulesRoot';
}
function AddAppContext(sURL)
{
var sContext = '$sAppContext';
if (sContext.length > 0)
{
if (sURL.indexOf('?') == -1)
{
return sURL+'?'+sContext;
}
return sURL+'&'+sContext;
}
return sURL;
}
EOF
);
}
public function SetRootUrl($sRootUrl)
{
@@ -100,15 +180,6 @@ class NiceWebPage extends WebPage
*/
public function output()
{
$sAbsURLAppRoot = addslashes($this->m_sRootUrl);
$this->add_script(
<<<EOF
function GetAbsoluteUrlAppRoot()
{
return '$sAbsURLAppRoot';
}
EOF
);
$this->set_base($this->m_sRootUrl.'pages/');
if (count($this->m_aReadyScripts)>0)
{

View File

@@ -323,6 +323,11 @@ abstract class AttributeDefinition
// Note: This is the responsibility of this function to place backticks around column aliases
return array('`'.$sClassAlias.$this->GetCode().'`');
}
public function GetOrderByHint()
{
return '';
}
// Import - differs slightly from SQL input, but identical in most cases
//
@@ -2120,6 +2125,13 @@ class AttributeEnum extends AttributeString
if ($proposedValue == '') return null;
return parent::MakeRealValue($proposedValue, $oHostObj);
}
public function GetOrderByHint()
{
$aValues = $this->GetAllowedValues();
return Dict::Format('UI:OrderByHint_Values', implode(', ', $aValues));
}
}
/**

View File

@@ -1070,6 +1070,8 @@ table.pagination tr td {
}
.pager td span {
min-width: 20px;
padding-left: 2px;
padding-right: 2px;
display:inline-block;
text-align: center;
cursor: pointer;

View File

@@ -956,5 +956,6 @@ When associated with a trigger, each action is given an "order" number, specifyi
'Note that this is not a security setting, objects from any organization are still visible and can be accessed by selecting "All Organizations" in the drop-down list.',
'UI:NavigateAwayConfirmationMessage' => 'Any modification will be discarded.',
'UI:Create_Class_InState' => 'Create the %1$s in state: ',
'UI:OrderByHint_Values' => 'Sort order: %1$s',
));
?>

View File

@@ -800,5 +800,6 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
'Ceci n\'est pas un réglage de sécurité. Les objets de toutes les organisations sont toujours visibles en choisissant "Toutes les Organisations" dans le menu.',
'UI:NavigateAwayConfirmationMessage' => 'Toute modification sera perdue.',
'UI:Create_Class_InState' => 'Créer l\'objet %1$s dans l\'état: ',
'UI:OrderByHint_Values' => 'Ordre de tri: %1$s',
));
?>

View File

@@ -288,40 +288,62 @@ function sprintf(format, etc) {
function renderPager(table, pager)
{
var c = table.config;
var s = c.page - 2;
var aPages = [0]; // first page
var s = c.page - 1;
var nb = Math.ceil(c.totalRows / c.size);
if (s < 0)
if (s < 1)
{
s = 0;
s = 1;
}
var e = s +5;
if (e > nb)
var e = s +3;
if (e >= nb)
{
e = nb;
s = e - 5;
if (s < 0) s = 0;
if ((e - 4) > 1)
{
s = e - 4;
}
}
txt = '';
for(var i=s; i<e; i++)
{
var page = 1+i;
var link = ' '+page+' ';
if (i != c.page)
aPages.push(i);
}
if ((nb > 1) && (nb > i))
{
aPages.push(nb - 1); // very last page
}
txt = '';
for(i=0; i<aPages.length; i++)
{
var page = 1+aPages[i];
var link = '';
var sDotsAfter = '';
var sDotsBefore = '';
if ((i == 0) && (aPages.length > 1) && (aPages[i+1] != aPages[i]+1))
{
link = ' <span page="'+i+'" id="gotopage_'+i+'">'+page+'</span> ';
sDotsAfter = '...'; // Gap between the last 2 page numbers
}
if ((i == aPages.length-1) && (aPages.length > 1) && (aPages[i-1] != aPages[i]-1))
{
sDotsBefore = '...'; // Gap between the first 2 page numbers
}
if (aPages[i] != c.page)
{
link = ' <span page="'+aPages[i]+'" id="gotopage_'+aPages[i]+'">'+sDotsBefore+page+sDotsAfter+'</span> ';
}
else
{
link = ' <span class="curr_page" page="'+i+'">'+page+'</span> ';
link = ' <span class="curr_page" page="'+aPages[i]+'">'+sDotsBefore+page+sDotsAfter+'</span> ';
}
txt += link;
}
txt += '';
$('#total', pager).text(c.totalRows);
$('#index', pager).html(txt);
for(var j=s; j<e; j++)
for(i=0; i<aPages.length; i++)
{
$('#gotopage_'+j, pager).click(function(){
$('#gotopage_'+aPages[i], pager).click(function(){
var idx = $(this).attr('page');
table.config.page = idx;
moveToPage(table);
@@ -426,7 +448,14 @@ function sprintf(format, etc) {
setPageSize(table,config.selectedSize, false);
restoreParams(table, config);
$(this).trigger("appendCache"); // Load the data
//$(this).trigger("appendCache"); // Load the data
//console.log($.tablesorterPager);
$('.gotopage',pager).click(function() {
var idx = $(this).attr('page');
table.config.page = idx;
moveToPage(table);
});
$(config.cssFirst,pager).click(function() {
moveToFirstPage(table);