Bug 336510 - [user] manage-users page doesn't scale - Client side changes for pagination of users list
diff --git a/bundles/org.eclipse.orion.client.users/web/orion/profile/UsersList.js b/bundles/org.eclipse.orion.client.users/web/orion/profile/UsersList.js
index 97507d0..1075e8b 100644
--- a/bundles/org.eclipse.orion.client.users/web/orion/profile/UsersList.js
+++ b/bundles/org.eclipse.orion.client.users/web/orion/profile/UsersList.js
@@ -1,6 +1,6 @@
 /*******************************************************************************
  * @license
- * Copyright (c) 2009, 2011 IBM Corporation and others.
+ * Copyright (c) 2009, 2012 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials are made 
  * available under the terms of the Eclipse Public License v1.0 
  * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
@@ -8,6 +8,9 @@
  * 
  * Contributors: IBM Corporation - initial API and implementation
  ******************************************************************************/
+ 
+ /*jslint browser:true devel:true sub:true*/
+ /*global define window*/
 
 define(['i18n!profile/nls/messages', 'require', 'dojo', 'orion/explorer', 'orion/profile/usersUtil', 'orion/navigationUtils'], function(messages, require, dojo, mExplorer, mUsersUtil, mNavUtils) {
 
@@ -27,11 +30,33 @@
 		this.model = null;
 		this.myTree = null;
 		this.renderer = new eclipse.UsersRenderer({actionScopeId: this.actionScopeId, checkbox: false, cachePrefix: "UsersNavigator"}, this); //$NON-NLS-0$
-	};
+	}
+	
 	UsersList.prototype = new mExplorer.Explorer();
 	
-	UsersList.prototype.loadUsers = function(){
+	UsersList.prototype.queryObject = { start: 0, rows:100, length: 0};
+	
+	UsersList.prototype.calculateQuery = function(locationHash, queryObj) {
+		var startQuery = locationHash.indexOf("?"); //$NON-NLS-0$
+		if (startQuery !== -1) {
+			var queryStr = locationHash.substring(startQuery + 1);
+			var splitQ = queryStr.split("&"); //$NON-NLS-0$
+			for(var i=0; i < splitQ.length; i++){
+				var splitparameters = splitQ[i].split("="); //$NON-NLS-0$
+				if(splitparameters.length === 2){
+					if(splitparameters[0] === "rows"){  //$NON-NLS-0$
+						queryObj.rows = parseInt(splitparameters[1], 10);
+					} else if(splitparameters[0] === "start"){ //$NON-NLS-0$
+						queryObj.start = parseInt(splitparameters[1], 10);
+					}
+				}
+			}
+		}
+	};
+
+	UsersList.prototype.createModel = function() {
 		var parent = dojo.byId(this.parentId);
+		var queryObj = UsersList.prototype.queryObject;
 
 		// Progress indicator
 		var progress = dojo.byId("progress");  //$NON-NLS-0$
@@ -39,20 +64,55 @@
 			progress = dojo.create("div", {id: "progress"}, parent, "only"); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
 		}
 		dojo.empty(progress);
-		b = dojo.create("b"); //$NON-NLS-0$
-		dojo.place(document.createTextNode(messages["Loading users..."]), progress, "last"); //$NON-NLS-1$
-
-		mUsersUtil.updateNavTools(this.registry, this, this.toolbarId, this.selectionToolsId, {});
-					
+		dojo.create("b"); //$NON-NLS-0$
+		dojo.place(document.createTextNode(messages["Loading users..."]), progress, "last"); //$NON-NLS-0$
+		
 		var service = this.registry.getService("orion.core.user"); //$NON-NLS-0$
-		this.createTree(this.parentId, new mExplorer.ExplorerFlatModel("/users", service.getUsersList), {setFocus: true}); //$NON-NLS-0$
+		UsersList.prototype.registry = this.registry;
+		
+		var locationHash = window.location.hash;
+		UsersList.prototype.calculateQuery(locationHash, queryObj);
+		var flatModel = new mExplorer.ExplorerFlatModel("../users", UsersList.prototype.getUsersListSubset.bind(this)); //$NON-NLS-0$
+		flatModel.service = service;
+		flatModel.queryObject = queryObj;
+		this.queryObject = queryObj;
+		this.createTree(this.parentId, flatModel, {setFocus: true}); //$NON-NLS-0$
+		mUsersUtil.updateNavTools(this.registry, this, this.toolbarId, this.selectionToolsId, {});
+	};
+	
+	UsersList.prototype.loadUsers = function(refreshTree){
+		var that = this;
+		var queryObj = UsersList.prototype.queryObject;
+		var locationHash = window.location.hash;
+		UsersList.prototype.calculateQuery(locationHash, queryObj);
+		if (refreshTree) {
+			UsersList.prototype.getUsersListSubset().then( function(newChildren) {
+				that.myTree.refresh("userslist", newChildren); //$NON-NLS-0$
+				mUsersUtil.updateNavTools(that.registry, that, that.toolbarId, that.selectionToolsId, {});
+			}.bind(this));
+		}
 	};
 	
 	UsersList.prototype.reloadUsers = function() {
-		dojo.empty(this.parentId);
-		this.loadUsers();
+		this.loadUsers(true);
 	};
-	
+
+	UsersList.prototype.getUsersListSubset = function(root) {
+	    var aService;
+	    if (this.service) {
+	        aService = this.service;
+	    } else {
+	        aService = this.registry.getService("orion.core.user"); //$NON-NLS-0$
+	    }
+		return aService.getUsersListSubset(this.queryObject.start, this.queryObject.rows).then(
+			function(result) {
+				this.queryObject.start = parseInt(result.users_start, 10);
+				this.queryObject.rows = parseInt(result.users_rows, 10);
+				this.queryObject.length = parseInt(result.users_length, 10);
+				return result.users;
+			}.bind(this));
+	};
+
 	return UsersList;
 }());
 
@@ -68,18 +128,14 @@
 		
 		switch(col_no){
 		case 0: 
-			return dojo.create("th", {innerHTML: "<h2>"+messages["Login"]+"</h2>"}); //$NON-NLS-3$ //$NON-NLS-1$ //$NON-NLS-0$
-			break;
+			return dojo.create("th", {innerHTML: "<h2>"+messages["Login"]+"</h2>"}); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
 		case 1:
-			return dojo.create("th", {innerHTML: "<h2>"+messages["Actions"]+"</h2>"}); //$NON-NLS-3$ //$NON-NLS-1$ //$NON-NLS-0$
-			break;
+			return dojo.create("th", {innerHTML: "<h2>"+messages["Actions"]+"</h2>"}); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
 		case 2:
-			return dojo.create("th", {innerHTML: "<h2>"+messages["Name"]+"</h2>"}); //$NON-NLS-3$ //$NON-NLS-1$ //$NON-NLS-0$
-			break;
+			return dojo.create("th", {innerHTML: "<h2>"+messages["Name"]+"</h2>"}); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
 		case 3:
-			return dojo.create("th", {innerHTML: "<h2>"+messages["Last Login"]+"</h2>"}); //$NON-NLS-3$ //$NON-NLS-1$ //$NON-NLS-0$
-			break;
-		};
+			return dojo.create("th", {innerHTML: "<h2>"+messages["Last Login"]+"</h2>"}); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
+		}
 		
 	};
 	
@@ -96,17 +152,13 @@
 			dojo.place(document.createTextNode(item.login), link, "only");			 //$NON-NLS-0$
 			mNavUtils.addNavGrid(this.explorer.getNavDict(), item, link);
 			return col;
-			break;
 		case 1:
 			return this.getActionsColumn(item, tableRow, null, null, true);
-			break;
 		case 2:
 			return dojo.create("td", {innerHTML: item.Name ? item.Name : "&nbsp;"}); //$NON-NLS-1$ //$NON-NLS-0$
-			break;
 		case 3:
-			return dojo.create("td", {innerHTML: item.LastLogInTimestamp ? dojo.date.locale.format(new Date(parseInt(item.LastLogInTimestamp)), {formatLength: "short"}) : '&nbsp;'}); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
-			break;
-		};
+			return dojo.create("td", {innerHTML: item.LastLogInTimestamp ? dojo.date.locale.format(new Date(parseInt(item.LastLogInTimestamp, 10)), {formatLength: "short"}) : '&nbsp;'}); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
+		}
 		
 	};
 	
@@ -136,19 +188,19 @@
 				var titleRow = dojo.create("tr", {"class": "domCommandBackground"}, thead); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
 
 				dojo.create("td", { //$NON-NLS-0$
-					innerHTML : "<h2>"+messages['Login']+"</h2>", //$NON-NLS-2$ //$NON-NLS-0$
+					innerHTML : "<h2>"+messages['Login']+"</h2>", //$NON-NLS-1$ //$NON-NLS-0$
 					className : "usersTable" //$NON-NLS-0$
 				}, titleRow);
 				dojo.create("td", { //$NON-NLS-0$
-					innerHTML : "<h2>"+messages['Actions']+"</h2>", //$NON-NLS-2$ //$NON-NLS-0$
+					innerHTML : "<h2>"+messages['Actions']+"</h2>", //$NON-NLS-1$ //$NON-NLS-0$
 					className : "usersTable" //$NON-NLS-0$
 				}, titleRow);
 				dojo.create("td", { //$NON-NLS-0$
-					innerHTML : "<h2>"+messages['Name']+"</h2>", //$NON-NLS-2$ //$NON-NLS-0$
+					innerHTML : "<h2>"+messages['Name']+"</h2>", //$NON-NLS-1$ //$NON-NLS-0$
 					className : "usersTable" //$NON-NLS-0$
 				}, titleRow);
 				dojo.create("td", { //$NON-NLS-0$
-					innerHTML : "<h2>"+messages['Last Login']+"</h2>", //$NON-NLS-2$ //$NON-NLS-0$
+					innerHTML : "<h2>"+messages['Last Login']+"</h2>", //$NON-NLS-1$ //$NON-NLS-0$
 					className : "usersTable" //$NON-NLS-0$
 				}, titleRow);
 
@@ -159,7 +211,7 @@
 				var tbody = dojo.create("tbody", null, table); //$NON-NLS-0$
 
 				for ( var i in jsonData.users) {
-					var userRow = dojo.create("tr", {"class": i%2==0 ? "treeTableRow lightTreeTableRow" : "treeTableRow darkTreeTableRow"}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
+					var userRow = dojo.create("tr", {"class": i%2===0 ? "treeTableRow lightTreeTableRow" : "treeTableRow darkTreeTableRow"}); //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
 					dojo.connect(userRow, "onmouseover", dojo.hitch(this, function(i){document.getElementById("usersActions"+i).style.visibility="";}, i)); //$NON-NLS-1$ //$NON-NLS-0$
 					dojo.connect(userRow, "onmouseout", dojo.hitch(this, function(i){document.getElementById("usersActions"+i).style.visibility="hidden";}, i)); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
 					dojo.create("td", { //$NON-NLS-0$
@@ -189,7 +241,7 @@
 						className: "usersTable secondaryColumn" //$NON-NLS-0$
 					}, userRow);
 					dojo.create("td", { //$NON-NLS-0$
-						innerHTML : jsonData.users[i].LastLogInTimestamp ? dojo.date.locale.format(new Date(parseInt(jsonData.users[i].LastLogInTimestamp)), {formatLength: "short"}) : '&nbsp;', //$NON-NLS-1$ //$NON-NLS-0$
+						innerHTML : jsonData.users[i].LastLogInTimestamp ? dojo.date.locale.format(new Date(parseInt(jsonData.users[i].LastLogInTimestamp, 10)), {formatLength: "short"}) : '&nbsp;', //$NON-NLS-1$ //$NON-NLS-0$
 						className: "usersTable secondaryColumn" //$NON-NLS-0$
 					}, userRow);
 					dojo.place(userRow, tbody);
@@ -197,7 +249,7 @@
 			}));
 		},
 		getUserTab : function(userName, userLocation) {
-			return tab = "<a class=\"navlinkonpage\" href=\"" + require.toUrl("profile/user-profile.html") + "#" + userLocation //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
+			return "<a class=\"navlinkonpage\" href=\"" + require.toUrl("profile/user-profile.html") + "#" + userLocation //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
 					+ "\">" + userName + "</a>"; //$NON-NLS-1$ //$NON-NLS-0$
 		},
 		reloadUsers : function() {
diff --git a/bundles/org.eclipse.orion.client.users/web/orion/profile/profile.js b/bundles/org.eclipse.orion.client.users/web/orion/profile/profile.js
index 64f1343..4d5be5b 100644
--- a/bundles/org.eclipse.orion.client.users/web/orion/profile/profile.js
+++ b/bundles/org.eclipse.orion.client.users/web/orion/profile/profile.js
@@ -306,7 +306,7 @@
 				var breadcrumbTarget = 	{};
 				breadcrumbTarget.Parents = [];
 				breadcrumbTarget.Name = profile.lastJSON.Name && profile.lastJSON.Name.replace(/^\s+|\s+$/g,"")!=="" ? profile.lastJSON.Name : profile.lastJSON.login; //$NON-NLS-0$
-				mGlobalCommands.setPageTarget({task: "User Profile", breadcrumbTarget: breadcrumbTarget});
+				mGlobalCommands.setPageTarget({task: "User Profile", breadcrumbTarget: breadcrumbTarget}); //$NON-NLS-0$
 			
 				this.commandService.destroy(this.pageActionsPlaceholder);
 				this.commandService.addCommandGroup(this.pageActionsPlaceholder.id, "eclipse.profileActionsGroup", 100); //$NON-NLS-0$
diff --git a/bundles/org.eclipse.orion.client.users/web/orion/profile/usersClient.js b/bundles/org.eclipse.orion.client.users/web/orion/profile/usersClient.js
index a401447..eff8fb0 100644
--- a/bundles/org.eclipse.orion.client.users/web/orion/profile/usersClient.js
+++ b/bundles/org.eclipse.orion.client.users/web/orion/profile/usersClient.js
@@ -29,6 +29,9 @@
 		getUserInfo: function(userURI, onLoad){
 			return this._doServiceCall("getUserInfo", arguments); //$NON-NLS-0$
 		},
+		getUsersListSubset : function(start, rows, onLoad) {
+			return this._doServiceCall("getUsersListSubset", arguments); //$NON-NLS-0$
+		},
 		getUsersList : function(onLoad) {
 			return this._doServiceCall("getUsersList", arguments); //$NON-NLS-0$
 		},
diff --git a/bundles/org.eclipse.orion.client.users/web/profile/UsersService.js b/bundles/org.eclipse.orion.client.users/web/profile/UsersService.js
index 4d11fdc..dc0099a 100644
--- a/bundles/org.eclipse.orion.client.users/web/profile/UsersService.js
+++ b/bundles/org.eclipse.orion.client.users/web/profile/UsersService.js
@@ -49,6 +49,33 @@
 
 	UsersService.prototype = /** @lends eclipse.FileService.prototype */
 	{
+		getUsersListSubset : function(start, rows, onLoad) {
+			var service = this;
+			var uri = "../users?start=" + start + "&rows=" + rows;
+			return xhr("GET", uri, { //$NON-NLS-1$ 
+				headers : {
+					"Orion-Version" : "1" //$NON-NLS-1$ //$NON-NLS-0$
+				},
+				timeout: 15000
+			}).then(function(result) {
+				var jsonData = getJSON(result.response);
+				if (onLoad){
+					if(typeof onLoad === "function") //$NON-NLS-0$
+						onLoad(jsonData);
+					else
+						service.dispatchEvent(onLoad, jsonData);
+				}
+				return jsonData;
+			}, function(result) {
+				var error = getError(result);
+				if(!service.info) {
+					handleAuthenticationError(error, function(){
+						service.getUsersListSubset(start, rows, onLoad); // retry GET
+					});
+				}
+				return error;
+			});
+		},
 		getUsersList : function(onLoad) {
 			var service = this;
 			return xhr("GET", "../users", { //$NON-NLS-1$ //$NON-NLS-0$
diff --git a/bundles/org.eclipse.orion.client.users/web/profile/nls/root/messages.js b/bundles/org.eclipse.orion.client.users/web/profile/nls/root/messages.js
index 429d43e..fccd6ae 100644
--- a/bundles/org.eclipse.orion.client.users/web/profile/nls/root/messages.js
+++ b/bundles/org.eclipse.orion.client.users/web/profile/nls/root/messages.js
@@ -40,5 +40,9 @@
 	"Are you sure you want to delete user ${0}?": "Are you sure you want to delete user ${0}?",
 	"Change Password": "Change Password",
 	"More": "More",
-	"Passwords do not match!": "Passwords do not match!"
+	"Passwords do not match!": "Passwords do not match!",
+	"< Previous Page": "< Previous Page",
+	"Show previous page of Users names": "Show previous page of Users names",
+	"Next Page >": "Next Page >",
+	"Show next page of User names": "Show next page of User names"
 });
\ No newline at end of file
diff --git a/bundles/org.eclipse.orion.client.users/web/profile/user-list.js b/bundles/org.eclipse.orion.client.users/web/profile/user-list.js
index 642653a..0e0ff9e 100644
--- a/bundles/org.eclipse.orion.client.users/web/profile/user-list.js
+++ b/bundles/org.eclipse.orion.client.users/web/profile/user-list.js
@@ -1,6 +1,6 @@
 /*******************************************************************************
  * @license
- * Copyright (c) 2009, 2011 IBM Corporation and others.
+ * Copyright (c) 2009, 2012 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials are made 
  * available under the terms of the Eclipse Public License v1.0 
  * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
@@ -8,6 +8,9 @@
  * 
  * Contributors: IBM Corporation - initial API and implementation
  ******************************************************************************/
+ 
+ /*jslint browser:true devel:true sub:true */
+ /*global define window orion */
 
 define(['i18n!profile/nls/messages', 'require', 'dojo', 'orion/bootstrap', 'orion/status', 'orion/progress', 'orion/operationsClient', 'orion/commands', 'orion/selection',
 	        'orion/searchClient', 'orion/fileClient', 'orion/globalCommands', 'orion/profile/UsersList', 'orion/profile/usersUtil',
@@ -33,6 +36,37 @@
 
 			mGlobalCommands.generateBanner("orion-userList", serviceRegistry, commandService, preferences, searcher, usersList); //$NON-NLS-0$	
 			
+			var previousPage = new mCommands.Command({
+				name : messages["< Previous Page"],
+				tooltip: messages["Show previous page of Users names"],
+				id : "orion.userlist.prevPage", //$NON-NLS-0$
+				hrefCallback : function() {
+					var start = usersList.queryObject.start - usersList.queryObject.rows;
+					if (start < 0) {
+						start = 0;
+					}
+					return window.location.pathname + "#?start=" + start + "&rows=" + usersList.queryObject.rows; //$NON-NLS-1$ //$NON-NLS-0$
+				},
+				visibleWhen : function(item) {
+					return usersList.queryObject.start > 0;
+				}
+			});
+			commandService.addCommand(previousPage);
+
+			var nextPage = new mCommands.Command({
+				name : messages["Next Page >"],
+				tooltip: messages["Show next page of User names"],
+				id : "orion.userlist.nextPage", //$NON-NLS-0$
+				hrefCallback : function() {
+					return window.location.pathname + "#?start=" + (usersList.queryObject.start + usersList.queryObject.rows) + "&rows=" + usersList.queryObject.rows; //$NON-NLS-1$ //$NON-NLS-0$
+				},
+				visibleWhen : function(item) {
+					return usersList.queryObject.length === 0 ? true : (usersList.queryObject.start + usersList.queryObject.rows) < usersList.queryObject.length;
+				}
+			});
+			commandService.addCommand(nextPage);
+
+
 			var createUserCommand = new mCommands.Command({
 				name: messages["Create User"],
 				id: "eclipse.createUser", //$NON-NLS-0$
@@ -77,9 +111,10 @@
 							var usersProcessed = 0;
 							for(var i=0; i<item.length; i++){
 								userService.deleteUser(item[i].Location).then( dojo.hitch(usersList, function(jsonData) {
-									  usersProcessed++;
-									  if(usersProcessed==item.length)
-										  this.reloadUsers();
+									usersProcessed++;
+									if(usersProcessed===item.length) {
+										this.reloadUsers();
+									}
 								  }));	
 							}
 						}
@@ -119,14 +154,22 @@
 			commandService.addCommandGroup("pageActions", "eclipse.usersGroup", 100); //$NON-NLS-1$ //$NON-NLS-0$
 			commandService.addCommandGroup("selectionTools", "eclipse.selectionGroup", 500, messages["More"]); //$NON-NLS-1$ //$NON-NLS-0$
 			
+			commandService.registerCommandContribution("pageActions", "orion.userlist.prevPage", 2, "eclipse.usersGroup");  //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
+			commandService.registerCommandContribution("pageActions", "orion.userlist.nextPage", 3, "eclipse.usersGroup");  //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
+
 			commandService.registerCommandContribution("pageActions", "eclipse.createUser", 1, "eclipse.usersGroup"); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
 			
 			commandService.registerCommandContribution("userCommands", "eclipse.deleteUser", 1); //$NON-NLS-1$ //$NON-NLS-0$
 			commandService.registerCommandContribution("userCommands", "eclipse.changePassword", 2); //$NON-NLS-1$ //$NON-NLS-0$
 			commandService.registerCommandContribution("selectionTools", "eclipse.deleteUser", 1, "eclipse.selectionGroup"); //$NON-NLS-2$ //$NON-NLS-1$ //$NON-NLS-0$
+
+			//every time the user manually changes the hash, we need to load the user list again
+			dojo.subscribe("/dojo/hashchange", usersList, function() { //$NON-NLS-0$
+				usersList.reloadUsers();
+			});
 			
-			usersList.loadUsers();
-			mUsersUtil.updateNavTools(serviceRegistry, usersList, "pageActions", "selectionTools", {});	 //$NON-NLS-1$ //$NON-NLS-0$
+			usersList.createModel();
+			usersList.loadUsers(true);
 		});
 	});
 });
\ No newline at end of file