Explorar el Código

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

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@1931 a333f486-631f-4898-b8df-5754b55c2be0
dflaven hace 13 años
padre
commit
4f97176cee

+ 106 - 84
application/cmdbabstract.class.inc.php

@@ -757,19 +757,17 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
 		}
 		}
 		foreach($aList as $sAttCode)
 		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();
 		$aValues = array();
 		$bDisplayLimit = isset($aExtraParams['display_limit']) ? $aExtraParams['display_limit'] : true;
 		$bDisplayLimit = isset($aExtraParams['display_limit']) ? $aExtraParams['display_limit'] : true;
 		$iMaxObjects = -1;
 		$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);
 		$oSet->Seek(0);
 		while (($oObj = $oSet->Fetch()) && ($iMaxObjects != 0))
 		while (($oObj = $oSet->Fetch()) && ($iMaxObjects != 0))
 		{
 		{
@@ -811,14 +809,7 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
 		}
 		}
 		$sHtml .= '<table class="listContainer">';
 		$sHtml .= '<table class="listContainer">';
 		$sColspan = '';
 		$sColspan = '';
-//		if (isset($aExtraParams['block_id']))
-//		{
-//			$divId = $aExtraParams['block_id'];
-//		}
-//		else
-//		{
-//			$divId = 'missingblockid';
-//		}
+
 		$sFilter = $oSet->GetFilter()->serialize();
 		$sFilter = $oSet->GetFilter()->serialize();
 		$iMinDisplayLimit = MetaModel::GetConfig()->GetMinDisplayLimit();
 		$iMinDisplayLimit = MetaModel::GetConfig()->GetMinDisplayLimit();
 		$sCollapsedLabel = Dict::Format('UI:TruncatedResults', $iMinDisplayLimit, $oSet->Count());
 		$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>');
 			$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)
+		{
+			$sPagerStyle = 'style="display:none"'; // no limit: display the full table, so hide the "pager" UI
+			$iPageSize = -1; // Display all
+		}
+		else
 		{
 		{
-			$sCombo = '<select class="pagesize">';
-			for($iPage = 1; $iPage < 5; $iPage++)
+			$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>';
+			}
+			else
+			{
+				$aPagesToDisplay[$idx] = "<span id=\"gotopage_$idx\" class=\"gotopage\" page=\"$idx\">".(1+$idx)."</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 =
+		}
+		$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
 <<<EOF
-<div id="pager{$iListId}" class="pager">
+<div id="pager{$iListId}" class="pager" $sPagerStyle>
 		<p>$sHeader</p>
 		<p>$sHeader</p>
 		<p><table class="pagination"><tr><td>$sPages</td><td><img src="../images/first.png" class="first"/></td>
 		<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><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/next.png" class="next"/></td>
 		<td><img src="../images/last.png" class="last"/></td>
 		<td><img src="../images/last.png" class="last"/></td>
 		<td>$sPageSizeCombo</td>
 		<td>$sPageSizeCombo</td>
@@ -889,63 +912,62 @@ $sHtml =
 </div>
 </div>
 EOF
 EOF
 .$sHtml;
 .$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");
+		$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}},';
 		}
 		}
-		else
+		$sDisplayKey = ($bViewLink) ? 'true' : 'false';
+		// Protect against duplicate elements in the Zlist
+		$aUniqueOrderedList = array();
+		foreach($aList as $sAttCode)
 		{
 		{
-$sHtml =
-<<<EOF
-<div id="pager{$iListId}" class="pager">
-		<p>$sHeader</p>
-</div>
-EOF
-.$sHtml;
-			$sHeaders = '';
-			if ($bSelectMode)
+			$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;
 		return $sHtml;
 	}
 	}

+ 2 - 74
application/itopwebpage.class.inc.php

@@ -51,29 +51,23 @@ class iTopWebPage extends NiceWebPage
 		$this->m_sMenu = "";
 		$this->m_sMenu = "";
 		$this->m_sMessage = '';
 		$this->m_sMessage = '';
 		$this->SetRootUrl(utils::GetAbsoluteUrlAppRoot());
 		$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("Content-type: text/html; charset=utf-8");
 		$this->add_header("Cache-control: no-cache");
 		$this->add_header("Cache-control: no-cache");
 		$this->add_linked_stylesheet("../css/jquery.treeview.css");
 		$this->add_linked_stylesheet("../css/jquery.treeview.css");
 		$this->add_linked_stylesheet("../css/jquery.autocomplete.css");
 		$this->add_linked_stylesheet("../css/jquery.autocomplete.css");
 		$this->add_linked_script('../js/jquery.layout.min.js');
 		$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.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.treeview.js");
 		$this->add_linked_script("../js/jquery.autocomplete.js");
 		$this->add_linked_script("../js/jquery.autocomplete.js");
 		$this->add_linked_script("../js/jquery.positionBy.js");
 		$this->add_linked_script("../js/jquery.positionBy.js");
 		$this->add_linked_script("../js/jquery.popupmenu.js");
 		$this->add_linked_script("../js/jquery.popupmenu.js");
 		$this->add_linked_script("../js/date.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/jquery.blockUI.js");
 		$this->add_linked_script("../js/utils.js");
 		$this->add_linked_script("../js/utils.js");
 		$this->add_linked_script("../js/swfobject.js");
 		$this->add_linked_script("../js/swfobject.js");
 		$this->add_linked_script("../js/ckeditor/ckeditor.js");
 		$this->add_linked_script("../js/ckeditor/ckeditor.js");
 		$this->add_linked_script("../js/ckeditor/adapters/jquery.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.qtip-1.0.min.js");
-		$this->add_linked_script("../js/jquery.tablesorter.pager.js");
 		$this->m_sInitScript =
 		$this->m_sInitScript =
 <<< EOF
 <<< EOF
 	try
 	try
@@ -162,46 +156,6 @@ EOF
 		;
 		;
 		$this->add_ready_script(
 		$this->add_ready_script(
 <<< EOF
 <<< 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
 	// Adjust initial size
 	$('.v-resizable').each( function()
 	$('.v-resizable').each( function()
@@ -307,7 +261,7 @@ EOF
 	});
 	});
 
 
 	// End of Tabs handling
 	// End of Tabs handling
-	$("table.listResults").tableHover(); // hover tables
+
 	$(".date-pick").datepicker({
 	$(".date-pick").datepicker({
 			showOn: 'button',
 			showOn: 'button',
 			buttonImage: '../images/calendar.png',
 			buttonImage: '../images/calendar.png',
@@ -408,21 +362,7 @@ EOF
 			if ($('#rawOutput > div').html() != '')
 			if ($('#rawOutput > div').html() != '')
 			{
 			{
 				$('#rawOutput').dialog( {autoOpen: true, modal:false});
 				$('#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;
 		var oUserPreferences = $sUserPrefs;
@@ -535,19 +475,7 @@ EOF
 	public function output()
 	public function output()
 	{
 	{
 		$sAbsURLAppRoot = addslashes($this->m_sRootUrl);
 		$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/');
 		$this->set_base($this->m_sRootUrl.'pages/');
 		$sForm = $this->GetSiloSelectionForm();
 		$sForm = $this->GetSiloSelectionForm();
 		$this->DisplayMenu(); // Compute the menu
 		$this->DisplayMenu(); // Compute the menu

+ 82 - 11
application/nicewebpage.class.inc.php

@@ -38,12 +38,92 @@ class NiceWebPage extends WebPage
 		$this->m_aReadyScripts = array();
 		$this->m_aReadyScripts = array();
 		$this->add_linked_script("../js/jquery-1.4.2.min.js");
 		$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_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/jquery-ui-1.8.2.custom.min.js');
 		$this->add_linked_script("../js/hovertip.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->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)
     public function SetRootUrl($sRootUrl)
     {
     {
@@ -100,15 +180,6 @@ class NiceWebPage extends WebPage
 	 */
 	 */
     public function output()
     public function output()
     {
     {
-    	$sAbsURLAppRoot = addslashes($this->m_sRootUrl);
-		$this->add_script(
-<<<EOF
-function GetAbsoluteUrlAppRoot()
-{
-	return '$sAbsURLAppRoot';
-}
-EOF
-		);
 		$this->set_base($this->m_sRootUrl.'pages/');
 		$this->set_base($this->m_sRootUrl.'pages/');
         if (count($this->m_aReadyScripts)>0)
         if (count($this->m_aReadyScripts)>0)
         {
         {

+ 12 - 0
core/attributedef.class.inc.php

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

+ 2 - 0
css/light-grey.css

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

+ 1 - 0
dictionaries/dictionary.itop.ui.php

@@ -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.',
 								   '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:NavigateAwayConfirmationMessage' => 'Any modification will be discarded.',
 	'UI:Create_Class_InState' => 'Create the %1$s in state: ',
 	'UI:Create_Class_InState' => 'Create the %1$s in state: ',
+	'UI:OrderByHint_Values' => 'Sort order: %1$s',
 ));
 ));
 ?>
 ?>

+ 1 - 0
dictionaries/fr.dictionary.itop.ui.php

@@ -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.',
 								   '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:NavigateAwayConfirmationMessage' => 'Toute modification sera perdue.',
 	'UI:Create_Class_InState' => 'Créer l\'objet %1$s dans l\'état: ',
 	'UI:Create_Class_InState' => 'Créer l\'objet %1$s dans l\'état: ',
+	'UI:OrderByHint_Values' => 'Ordre de tri: %1$s',
 ));
 ));
 ?>
 ?>

+ 45 - 16
js/jquery.tablesorter.pager.js

@@ -288,40 +288,62 @@ function sprintf(format, etc) {
 			function renderPager(table, pager)
 			function renderPager(table, pager)
 			{
 			{
 				var c = table.config;
 				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);
 				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;
 					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++)
 				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
 					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 += link;
 				}
 				}
 				txt += '';
 				txt += '';
 				$('#total', pager).text(c.totalRows);
 				$('#total', pager).text(c.totalRows);
 				$('#index', pager).html(txt);
 				$('#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');
 						var idx = $(this).attr('page');
 						table.config.page = idx;
 						table.config.page = idx;
 						moveToPage(table);
 						moveToPage(table);
@@ -426,7 +448,14 @@ function sprintf(format, etc) {
 					setPageSize(table,config.selectedSize, false);
 					setPageSize(table,config.selectedSize, false);
 					restoreParams(table, config);
 					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() {
 					$(config.cssFirst,pager).click(function() {
 						moveToFirstPage(table);
 						moveToFirstPage(table);