Przeglądaj źródła

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 13 lat temu
rodzic
commit
4f97176cee

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

@@ -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)
+		{
+			$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
-<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");
+		$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;
 	}

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

@@ -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

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

@@ -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)
         {

+ 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
 		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));
+	}
 }
 
 /**

+ 2 - 0
css/light-grey.css

@@ -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;

+ 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.',
 	'UI:NavigateAwayConfirmationMessage' => 'Any modification will be discarded.',
 	'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.',
 	'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',
 ));
 ?>

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

@@ -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);