Permalink
Browse files

reduce data loads in admin tab

Previously, the user search api was queried for a list of every user in the system
every time the admin opened the Add New Members popup in the class edit tab. Now,
the list of users is loaded once, the first time the popup is opened, and is
only reloaded when a user is added or deleted.
  • Loading branch information...
1 parent a3c1386 commit f37bef4a02c86d9bf675ae5160da41157665671f shlurbee committed Mar 7, 2012
@@ -6,7 +6,9 @@ the different pages can refresh their display.
XXXDataChangedEvents are fired by a presenter when it creates, edits, or deletes an XXX item.
UserInfoUpdatedEvent is fired by the MainApp when it refetches the userInfo object to let all the
-presenters know they need to refresh any part of their display that was depending on userInfo.
+presenters know they need to refresh any part of their display that was depending on userInfo.
+(Note that UserInfoUpdatedEvent applies only to data about the currently logged in user, whereas
+UserDataChangedEvent is fired for changes made to any user.)
So, for example, when an admin creates a new class, the AdminClassEditPresenter fires a ClassDataChangedEvent
to notify any pages that depend on the class data that they need to reload it. The userInfo obj that is
@@ -0,0 +1,30 @@
+package edu.ucla.cens.mobilize.client.event;
+
+import com.google.gwt.event.shared.GwtEvent;
+
+/**
+ * Fired when a user is created or deleted. Mainly useful in the admin tab, where info pertaining
+ * to all users (e.g., list of all usernames) will need to be refreshed.
+ *
+ * Note this event differs from UserInfoUpdatedEvent:
+ * - UserDataChangedEvent is fired for any user. UserInfoUpdatedEvent is fired only for the currently
+ * logged in user.
+ * - UserDataChangedEvent is fired for events that affect whether a username should show up in
+ * a list of all users - i.e., if a user is created or deleted. UserInfoUpdatedEvent is fired
+ * when any info about the currently logged in user changes.
+ */
+public class UserDataChangedEvent extends GwtEvent<UserDataChangedEventHandler> {
+
+ public static Type<UserDataChangedEventHandler> TYPE = new Type<UserDataChangedEventHandler>();
+
+ @Override
+ public com.google.gwt.event.shared.GwtEvent.Type<UserDataChangedEventHandler> getAssociatedType() {
+ return TYPE;
+ }
+
+ @Override
+ protected void dispatch(UserDataChangedEventHandler handler) {
+ handler.onUserDataChanged(this);
+ }
+
+}
@@ -0,0 +1,7 @@
+package edu.ucla.cens.mobilize.client.event;
+
+import com.google.gwt.event.shared.EventHandler;
+
+public interface UserDataChangedEventHandler extends EventHandler {
+ void onUserDataChanged(UserDataChangedEvent event);
+}
@@ -4,6 +4,12 @@
import edu.ucla.cens.mobilize.client.model.UserInfo;
+/**
+ * Fired when info about the currently logged in user changes.
+ *
+ * Note this event applies only to the currently logged in user. In this way, it differs from
+ * UserDataChangedEvent which is fired when any user changes/
+ */
public class UserInfoUpdatedEvent extends GwtEvent<UserInfoUpdatedEventHandler> {
public static Type<UserInfoUpdatedEventHandler> TYPE = new Type<UserInfoUpdatedEventHandler>();
@@ -22,15 +22,15 @@
import edu.ucla.cens.mobilize.client.dataaccess.requestparams.ClassUpdateParams;
import edu.ucla.cens.mobilize.client.dataaccess.requestparams.UserSearchParams;
import edu.ucla.cens.mobilize.client.event.ClassDataChangedEvent;
+import edu.ucla.cens.mobilize.client.event.UserDataChangedEvent;
+import edu.ucla.cens.mobilize.client.event.UserDataChangedEventHandler;
import edu.ucla.cens.mobilize.client.model.ClassInfo;
import edu.ucla.cens.mobilize.client.model.UserInfo;
import edu.ucla.cens.mobilize.client.model.UserSearchInfo;
import edu.ucla.cens.mobilize.client.ui.ConfirmDeleteDialog;
import edu.ucla.cens.mobilize.client.ui.ErrorDialog;
-import edu.ucla.cens.mobilize.client.ui.WaitIndicator;
import edu.ucla.cens.mobilize.client.utils.AwErrorUtils;
import edu.ucla.cens.mobilize.client.utils.CollectionUtils;
-import edu.ucla.cens.mobilize.client.utils.StopWatch;
import edu.ucla.cens.mobilize.client.view.AdminClassEditView;
public class AdminClassEditPresenter implements Presenter {
@@ -39,13 +39,25 @@
private EventBus eventBus;
private AdminClassEditView view;
+ private List<String> allUsernames; // store all usernames to avoid excesssive data loads
private Map<String, RoleClass> currentMemberList; // used to identify deleted users
private static Logger _logger = Logger.getLogger(AdminClassEditPresenter.class.getName());
-
+
public AdminClassEditPresenter(UserInfo userInfo, DataService dataService, EventBus eventBus) {
this.userInfo = userInfo;
this.dataService = dataService;
this.eventBus = eventBus;
+ bind();
+ }
+
+ private void bind() {
+ // If user was added or deleted, clear user list so it will be reloaded on next use.
+ eventBus.addHandler(UserDataChangedEvent.TYPE, new UserDataChangedEventHandler() {
+ @Override
+ public void onUserDataChanged(UserDataChangedEvent event) {
+ allUsernames = null;
+ }
+ });
}
public void setView(AdminClassEditView view) {
@@ -65,7 +77,13 @@ public void onClick(ClickEvent event) {
view.getAddMembersButton().addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
- fetchUsersAndShowAddMembersPopup();
+ if (allUsernames != null) {
+ List<String> usernames = getUsernamesMinusMemberList();
+ view.setAddMembersPopupUserList(usernames);
+ view.showAddMembersPopup();
+ } else {
+ fetchUsersAndShowAddMembersPopup();
+ }
}
});
@@ -193,41 +211,55 @@ public void onSuccess(ClassInfo result) {
}
private void fetchUsersAndShowAddMembersPopup() {
- WaitIndicator.show();
- // TODO: FIXME: should only load users once when admin first goes to page, then re-use them everywhere
+ view.clearAddMembersPopup();
+ view.showAddMembersPopup();
+ view.showAddMembersPopupWaitIndicator();
dataService.fetchUserSearchResults(new UserSearchParams(), new AsyncCallback<List<UserSearchInfo>>() {
@Override
public void onFailure(Throwable caught) {
- WaitIndicator.hide();
+ view.hideAddMembersPopupWaitIndicator();
+ view.hideAddMembersPopup();
AwErrorUtils.logoutIfAuthException(caught);
ErrorDialog.show("There was a problem fetching the user list.", caught.getMessage());
}
@Override
public void onSuccess(List<UserSearchInfo> result) {
- WaitIndicator.hide();
- Map<String, RoleClass> currentMembers = view.getMembersAndRoles();
- List<String> usernames = new ArrayList<String>();
-
- StopWatch.start("search_and_sort");
- for (UserSearchInfo user : result) {
- String username = user.getUsername();
- // popup only shows users that are not already in the class
- if (!currentMembers.containsKey(username)) {
- usernames.add(username);
- }
+ // clear previous data
+ if (allUsernames != null) {
+ allUsernames.clear();
+ } else {
+ allUsernames = new ArrayList<String>();
}
- Collections.sort(usernames);
- StopWatch.stop("search_and_sort");
- StopWatch.start("render_user_list");
- view.showAddMembersPopup(usernames);
- StopWatch.stop("render_user_list");
- _logger.finest(StopWatch.getTotalsString());
- StopWatch.resetAll();
+
+ // build a list of usernames and save it so it won't have to be reloaded
+ for (UserSearchInfo user: result) {
+ allUsernames.add(user.getUsername());
+ }
+
+ List<String> usernames = getUsernamesMinusMemberList();
+ view.setAddMembersPopupUserList(usernames);
+ view.hideAddMembersPopupWaitIndicator();
}
});
}
-
+
+ private List<String> getUsernamesMinusMemberList() {
+ // NOTE: current members are fetched from the view and not this.currentMembers
+ // because this.currentMembers contains list of members associated with this
+ // class in the db, but we want to compare against members in the display,
+ // which may have been edited since the class data was loaded
+ Map<String, RoleClass> currentMembers = view.getMembersAndRoles();
+ List<String> usernames = new ArrayList<String>();
+ for (String username : this.allUsernames) {
+ if (!currentMembers.containsKey(username)) {
+ usernames.add(username);
+ }
+ }
+ Collections.sort(usernames);
+ return usernames;
+ }
+
private boolean validateClassInfo() {
view.clearValidationErrors(); // clear previous errors, if any
boolean allFieldsAreValid = true;
@@ -11,6 +11,7 @@
import edu.ucla.cens.mobilize.client.common.HistoryTokens;
import edu.ucla.cens.mobilize.client.dataaccess.DataService;
import edu.ucla.cens.mobilize.client.dataaccess.requestparams.UserCreateParams;
+import edu.ucla.cens.mobilize.client.event.UserDataChangedEvent;
import edu.ucla.cens.mobilize.client.model.UserInfo;
import edu.ucla.cens.mobilize.client.utils.AwErrorUtils;
import edu.ucla.cens.mobilize.client.view.AdminUserCreateView;
@@ -85,6 +86,7 @@ public void onFailure(Throwable caught) {
@Override
public void onSuccess(String result) {
+ eventBus.fireEvent(new UserDataChangedEvent());
view.resetForm();
History.newItem(HistoryTokens.adminUserDetail(username));
}
@@ -13,9 +13,9 @@
import edu.ucla.cens.mobilize.client.common.HistoryTokens;
import edu.ucla.cens.mobilize.client.dataaccess.DataService;
import edu.ucla.cens.mobilize.client.dataaccess.requestparams.UserUpdateParams;
+import edu.ucla.cens.mobilize.client.event.UserDataChangedEvent;
import edu.ucla.cens.mobilize.client.model.UserInfo;
import edu.ucla.cens.mobilize.client.model.UserSearchInfo;
-import edu.ucla.cens.mobilize.client.model.UserShortInfo;
import edu.ucla.cens.mobilize.client.ui.ErrorDialog;
import edu.ucla.cens.mobilize.client.utils.AwErrorUtils;
import edu.ucla.cens.mobilize.client.view.AdminUserEditView;
@@ -126,6 +126,7 @@ public void onFailure(Throwable caught) {
@Override
public void onSuccess(String result) {
+ eventBus.fireEvent(new UserDataChangedEvent());
HistoryTokens.adminUserList();
}
});
@@ -199,6 +200,8 @@ public void onFailure(Throwable caught) {
@Override
public void onSuccess(String result) {
+ // NOTE: UserDataChangedEvent is not fired here b/c (as of Mar 2012)
+ // only create/delete require refreshing parts of the app.
view.resetForm();
History.newItem(HistoryTokens.adminUserDetail(username));
}
@@ -22,6 +22,7 @@
import edu.ucla.cens.mobilize.client.dataaccess.DataService;
import edu.ucla.cens.mobilize.client.dataaccess.requestparams.ClassUpdateParams;
import edu.ucla.cens.mobilize.client.dataaccess.requestparams.UserSearchParams;
+import edu.ucla.cens.mobilize.client.event.UserDataChangedEvent;
import edu.ucla.cens.mobilize.client.model.UserInfo;
import edu.ucla.cens.mobilize.client.model.UserSearchData;
import edu.ucla.cens.mobilize.client.model.UserSearchInfo;
@@ -387,6 +388,7 @@ public void onFailure(Throwable caught) {
@Override
public void onSuccess(String result) {
+ eventBus.fireEvent(new UserDataChangedEvent());
refreshUserList();
}
});
@@ -19,14 +19,17 @@
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Grid;
+import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.HTMLTable.Cell;
import com.google.gwt.user.client.ui.RadioButton;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;
import edu.ucla.cens.mobilize.client.common.RoleClass;
+
public class AdminClassAddUserPopup extends Composite {
private static AdminClassAddUserPopupUiBinder uiBinder = GWT
@@ -38,6 +41,7 @@
interface AdminClassAddUserPopupStyles extends CssResource {
String selected();
+ String waiting();
}
private static class Columns {
@@ -49,6 +53,7 @@
}
@UiField AdminClassAddUserPopupStyles style;
+ @UiField HTMLPanel userListContainer;
@UiField TextBox usernameTextBox;
@UiField Button usernameSearchButton;
@UiField Anchor selectAllLink;
@@ -58,9 +63,12 @@
@UiField Button cancelButton;
private List<String> allUsernames = new ArrayList<String>();
+ private FlowPanel waitIndicator;
public AdminClassAddUserPopup() {
initWidget(uiBinder.createAndBindUi(this));
+ waitIndicator = new FlowPanel();
+ waitIndicator.setStyleName(style.waiting());
bind();
}
@@ -158,7 +166,7 @@ private void unselectRow(int rowIndex) {
}
public void setUserList(List<String> usernames) {
- if (usernames == null) return;
+ if (usernames == null) usernames = new ArrayList<String>(); // show empty list on null
this.allUsernames = new ArrayList<String>(usernames);
updateUserListGrid(usernames);
}
@@ -217,4 +225,11 @@ public void clearSearchString() {
return usernameToRoleMap;
}
+ public void showWaitIndicator() {
+ this.userListContainer.add(waitIndicator);
+ }
+
+ public void hideWaitIndicator() {
+ this.userListContainer.remove(waitIndicator);
+ }
}
@@ -31,6 +31,14 @@
.userListContainer td {
white-space: nowrap;
}
+ .waiting {
+ background: 50% 50% no-repeat url('images/loading_text.gif');
+ height: 100px;
+ position: absolute;
+ left: 0;
+ top: 100px;
+ width: 100%;
+ }
</ui:style>
<g:HTMLPanel addStyleNames='{style.container}'>
<g:HTMLPanel addStyleNames='{style.filterContainer}'>
@@ -50,7 +58,7 @@
</g:HTMLPanel>
</g:HorizontalPanel>
</g:HTMLPanel>
- <g:HTMLPanel addStyleNames='{style.userListContainer}'>
+ <g:HTMLPanel addStyleNames='{style.userListContainer}' ui:field='userListContainer'>
<g:Grid ui:field='userListGrid' />
</g:HTMLPanel>
<g:HTMLPanel addStyleNames='{style.buttonContainer}'>
Oops, something went wrong.

0 comments on commit f37bef4

Please sign in to comment.