/* * Copyright (c) 2009-2011 Lockheed Martin Corporation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.eurekastreams.web.client.ui.pages.profile; import java.util.HashSet; import org.eurekastreams.commons.client.ActionProcessor; import org.eurekastreams.server.action.request.profile.GetFollowersFollowingRequest; import org.eurekastreams.server.action.request.profile.GetRequestForGroupMembershipRequest; import org.eurekastreams.server.domain.EntityType; import org.eurekastreams.server.domain.Page; import org.eurekastreams.server.domain.stream.StreamScope; import org.eurekastreams.server.domain.stream.StreamScope.ScopeType; import org.eurekastreams.server.search.modelview.DomainGroupModelView; import org.eurekastreams.server.search.modelview.PersonModelView; import org.eurekastreams.web.client.events.ChangeActivityModeEvent; import org.eurekastreams.web.client.events.EventBus; import org.eurekastreams.web.client.events.Observer; import org.eurekastreams.web.client.events.SetBannerEvent; import org.eurekastreams.web.client.events.ShowNotificationEvent; import org.eurekastreams.web.client.events.StreamRequestEvent; import org.eurekastreams.web.client.events.SwitchToFilterOnPagedFilterPanelEvent; import org.eurekastreams.web.client.events.data.AuthorizeUpdateGroupResponseEvent; import org.eurekastreams.web.client.events.data.BaseDataResponseEvent; import org.eurekastreams.web.client.events.data.DeletedGroupMemberResponseEvent; import org.eurekastreams.web.client.events.data.DeletedRequestForGroupMembershipResponseEvent; import org.eurekastreams.web.client.events.data.GotGroupCoordinatorsResponseEvent; import org.eurekastreams.web.client.events.data.GotGroupMembersResponseEvent; import org.eurekastreams.web.client.events.data.GotGroupModelViewInformationResponseEvent; import org.eurekastreams.web.client.events.data.GotRequestForGroupMembershipResponseEvent; import org.eurekastreams.web.client.events.data.InsertedGroupMemberResponseEvent; import org.eurekastreams.web.client.events.data.InsertedRequestForGroupMembershipResponseEvent; import org.eurekastreams.web.client.history.CreateUrlRequest; import org.eurekastreams.web.client.model.AllPopularHashTagsModel; import org.eurekastreams.web.client.model.GroupCoordinatorsModel; import org.eurekastreams.web.client.model.GroupMembersModel; import org.eurekastreams.web.client.model.GroupMembershipRequestModel; import org.eurekastreams.web.client.model.GroupModel; import org.eurekastreams.web.client.model.OrganizationModel; import org.eurekastreams.web.client.model.requests.GetGroupCoordinatorsRequest; import org.eurekastreams.web.client.ui.Session; import org.eurekastreams.web.client.ui.common.LeftBarPanel; import org.eurekastreams.web.client.ui.common.SpinnerLabelButton; import org.eurekastreams.web.client.ui.common.notifier.Notification; import org.eurekastreams.web.client.ui.common.pagedlist.ItemRenderer; import org.eurekastreams.web.client.ui.common.pagedlist.PagedListPanel; import org.eurekastreams.web.client.ui.common.pagedlist.PersonRenderer; import org.eurekastreams.web.client.ui.common.pagedlist.PersonRequestingGroupMembershipRenderer; import org.eurekastreams.web.client.ui.common.pagedlist.RemovableGroupMemberPersonRenderer; import org.eurekastreams.web.client.ui.common.pagedlist.SingleColumnPagedListRenderer; import org.eurekastreams.web.client.ui.common.stream.StreamJsonRequestFactory; import org.eurekastreams.web.client.ui.common.stream.StreamPanel; import org.eurekastreams.web.client.ui.common.stream.renderers.ShowRecipient; import org.eurekastreams.web.client.ui.common.tabs.SimpleTab; import org.eurekastreams.web.client.ui.common.tabs.TabContainerPanel; import org.eurekastreams.web.client.ui.pages.master.StaticResourceBundle; import org.eurekastreams.web.client.ui.pages.profile.tabs.GroupProfileAboutTabPanel; import org.eurekastreams.web.client.ui.pages.profile.widgets.BreadcrumbPanel; import org.eurekastreams.web.client.ui.pages.profile.widgets.ChecklistProgressBarPanel; import org.eurekastreams.web.client.ui.pages.profile.widgets.ConnectionsPanel; import org.eurekastreams.web.client.ui.pages.profile.widgets.GroupAboutPanel; import org.eurekastreams.web.client.ui.pages.profile.widgets.PeopleListPanel; import org.eurekastreams.web.client.ui.pages.profile.widgets.PopularHashtagsPanel; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.Hyperlink; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Panel; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.Widget; /** * Displays a summary of a group's profile. */ public class GroupProfilePanel extends FlowPanel { /** * Spacing panel -- we use floats in on the ProfilePage, so make sure nothing below it intrudes. */ SimplePanel clearPanel = new SimplePanel(); /** * About box containing the logo. */ private final GroupAboutPanel about; /** * Holds the PortalPage section of the profile display. */ private TabContainerPanel portalPage = null; /** * Panel that shows the bread crumb navigation. */ private final BreadcrumbPanel breadCrumbPanel; /** * Link to go to the profile settings page. */ private final Hyperlink profileSettingsLink; /** * Panel that holds the tabbed portion of the profile display. */ private final FlowPanel portalPageContainer = new FlowPanel(); /** * Left bar container. */ private final FlowPanel leftBarContainer = new FlowPanel(); /** * panel that holds the profile summary. */ private final LeftBarPanel leftBarPanel = new LeftBarPanel(); /** * The group whose profile we're looking at. */ private DomainGroupModelView group; /** * The group entity for this panel. */ private DomainGroupModelView groupEntity; /** * The panel that shows the checklist. */ private ChecklistProgressBarPanel checklistPanel; /** * The divider separating the checklistPanel from the rest of the left-bar content. */ private final FlowPanel checklistDivider = new FlowPanel(); /** * Connections Panel Holds the Small boxes with the connect counts. */ private ConnectionsPanel connectionsPanel; /** * Followers. */ private int members; /** Number of membership requests (for use on the admin tab label). */ private int membershipRequestsCount = 0; /** * Constructor. * * @param accountId * the account id. */ public GroupProfilePanel(final String accountId) { RootPanel.get().addStyleName(StaticResourceBundle.INSTANCE.coreCss().profile()); RootPanel.get().addStyleName(StaticResourceBundle.INSTANCE.coreCss().group()); profileSettingsLink = new Hyperlink("Configure", Session.getInstance().generateUrl( new CreateUrlRequest(Page.GROUP_SETTINGS, accountId))); ActionProcessor inProcessor = Session.getInstance().getActionProcessor(); about = new GroupAboutPanel(accountId); portalPageContainer.addStyleName(StaticResourceBundle.INSTANCE.coreCss().profilePageContainer()); profileSettingsLink.addStyleName(StaticResourceBundle.INSTANCE.coreCss().configureTab()); profileSettingsLink.addStyleName(StaticResourceBundle.INSTANCE.coreCss().hidden()); leftBarContainer.addStyleName(StaticResourceBundle.INSTANCE.coreCss().leftBarContainer()); breadCrumbPanel = new BreadcrumbPanel(inProcessor); this.add(breadCrumbPanel); this.add(profileSettingsLink); leftBarContainer.add(leftBarPanel); this.add(leftBarContainer); this.add(portalPageContainer); this.add(clearPanel); this.addStyleName(StaticResourceBundle.INSTANCE.coreCss().profilePage()); EventBus.getInstance().addObserver(GotGroupModelViewInformationResponseEvent.class, new Observer<GotGroupModelViewInformationResponseEvent>() { public void update(final GotGroupModelViewInformationResponseEvent event) { setEntity(event.getResponse()); } }); EventBus.getInstance().addObserver(ChangeActivityModeEvent.class, new Observer<ChangeActivityModeEvent>() { public void update(final ChangeActivityModeEvent event) { if (groupEntity != null) { breadCrumbPanel.setGroup(groupEntity, event.isSingleMode()); } } }); inProcessor.setQueueRequests(true); GroupModel.getInstance().fetch(accountId, false); AllPopularHashTagsModel.getInstance().fetch(null, true); inProcessor.fireQueuedRequests(); inProcessor.setQueueRequests(false); } /** * We have the Group, so set up the Profile summary. * * @param inGroup * the group whose profile is being displayed */ private void setEntity(final DomainGroupModelView inGroup) { groupEntity = inGroup; ActionProcessor inProcessor = Session.getInstance().getActionProcessor(); inProcessor.setQueueRequests(true); if (inGroup == null) { showInvalidGroupMessage(); inProcessor.fireQueuedRequests(); inProcessor.setQueueRequests(false); return; } // create the subset of UI items which are shown even if user does not have access // Set the banner. final EventBus eventBus = Session.getInstance().getEventBus(); eventBus.notifyObservers(new SetBannerEvent(inGroup)); breadCrumbPanel.setGroup(inGroup, false); if (inGroup.isRestricted()) { showRestrictedGroupMessage(inGroup); inProcessor.fireQueuedRequests(); inProcessor.setQueueRequests(false); return; } group = inGroup; if (group.isPending()) { showPendingGroupMessage(); inProcessor.fireQueuedRequests(); inProcessor.setQueueRequests(false); return; } checklistPanel = new ChecklistProgressBarPanel("Group Profile Checklist", "Completing the group profile is easy, upload a group avatar, " + "enter a short description of the group stream " + "and an overview of the group " + "and the members you are looking to participate in the stream. " + "Groups that fill out their profile are more likely " + "to be found by others across your organization.", new CreateUrlRequest(Page.GROUP_SETTINGS, group.getShortName())); leftBarPanel.clear(); portalPageContainer.clear(); members = group.getFollowersCount(); // Update the Profile summary about.setGroup(inGroup.getName(), inGroup.getId(), inGroup.getAvatarId(), inGroup.getUrl(), inGroup .getDescription()); connectionsPanel = new ConnectionsPanel(); connectionsPanel.addConnection("Members", null, group.getFollowersCount()); eventBus.addObserver(InsertedGroupMemberResponseEvent.class, new Observer<InsertedGroupMemberResponseEvent>() { public void update(final InsertedGroupMemberResponseEvent event) { members++; connectionsPanel.updateCount("Members", members); } }); eventBus.addObserver(DeletedGroupMemberResponseEvent.class, new Observer<DeletedGroupMemberResponseEvent>() { public void update(final DeletedGroupMemberResponseEvent event) { members--; connectionsPanel.updateCount("Members", members); } }); PeopleListPanel coordinatorPanel = new PeopleListPanel(new HashSet<PersonModelView>(inGroup.getCoordinators()), "Coordinators", 2, new CreateUrlRequest("tab", "Connections", true), new SwitchToFilterOnPagedFilterPanelEvent("connections", "Coordinators"), null); leftBarPanel.addChildWidget(about); leftBarPanel.addChildWidget(new PopularHashtagsPanel(ScopeType.GROUP, group.getShortName())); leftBarPanel.addChildWidget(connectionsPanel); leftBarPanel.addChildWidget(coordinatorPanel); final StreamPanel streamContent = new StreamPanel(ShowRecipient.FOREIGN_ONLY); StreamScope groupStreamScope = new StreamScope(group.getName(), ScopeType.GROUP, group.getShortName(), group .getStreamId()); streamContent.setStreamScope(groupStreamScope, group.isStreamPostable()); String jsonRequest = StreamJsonRequestFactory.addRecipient(EntityType.GROUP, group.getShortName(), StreamJsonRequestFactory.getEmptyRequest()).toString(); EventBus.getInstance().notifyObservers(new StreamRequestEvent(group.getName(), jsonRequest)); if (group.isStreamPostable()) { streamContent.setStreamScope(new StreamScope(ScopeType.GROUP, inGroup.getShortName()), true); } eventBus.addObserver(AuthorizeUpdateGroupResponseEvent.class, new Observer<AuthorizeUpdateGroupResponseEvent>() { public void update(final AuthorizeUpdateGroupResponseEvent event) { if (event.getResponse()) { profileSettingsLink.removeStyleName(StaticResourceBundle.INSTANCE.coreCss().hidden()); RootPanel.get().addStyleName(StaticResourceBundle.INSTANCE.coreCss().authenticated()); setUpChecklist(); if (!group.isPublic()) { final SimpleTab adminTab = buildAdminTab(); portalPage.addTab(adminTab); if ("Admin".equals(Session.getInstance().getParameterValue("tab"))) { portalPage.switchToTab("Admin"); } } // if posting is disabled for group, re-enable it since // user has org/group coord permissions. if (!group.isStreamPostable()) { streamContent.setStreamScope(new StreamScope(ScopeType.GROUP, inGroup.getShortName()), true); } } } }); portalPage = new TabContainerPanel(); portalPage.addTab(new SimpleTab("Stream", streamContent)); portalPage.addTab(new SimpleTab("Connections", buildConnectionsTabContent())); portalPage.addTab(new SimpleTab("About", new GroupProfileAboutTabPanel(group))); portalPage.init(); portalPage.setStyleName(StaticResourceBundle.INSTANCE.coreCss().profileGadgetsContainer()); portalPageContainer.add(portalPage); GroupModel.getInstance().authorize(inGroup.getShortName(), false); inProcessor.setQueueRequests(false); inProcessor.fireQueuedRequests(); } /** * Builds the connections tab content. * * @return The tab. */ private Widget buildConnectionsTabContent() { final EventBus eventBus = Session.getInstance().getEventBus(); // Make the Connections Tab final PagedListPanel connectionTabContent = new PagedListPanel("connections"); eventBus.addObserver(GotGroupMembersResponseEvent.class, new Observer<GotGroupMembersResponseEvent>() { public void update(final GotGroupMembersResponseEvent event) { connectionTabContent.render(event.getResponse(), "No one is following this group"); } }); eventBus.addObserver(GotGroupCoordinatorsResponseEvent.class, new Observer<GotGroupCoordinatorsResponseEvent>() { public void update(final GotGroupCoordinatorsResponseEvent event) { connectionTabContent.render(event.getResponse(), "This group has no coordinators"); } }); // update the list of members after joining/leaving the group eventBus.addObservers(new Observer<BaseDataResponseEvent<Integer>>() { public void update(final BaseDataResponseEvent<Integer> ev) { if ("Members".equals(connectionTabContent.getCurrentFilter())) { connectionTabContent.refreshData(); } } }, InsertedGroupMemberResponseEvent.class, DeletedGroupMemberResponseEvent.class); ItemRenderer<PersonModelView> memberRenderer = group.isPublic() ? new PersonRenderer(false) : new RemovableGroupMemberPersonRenderer(group.getUniqueId()); connectionTabContent.addSet("Members", GroupMembersModel.getInstance(), memberRenderer, new GetFollowersFollowingRequest(EntityType.GROUP, group.getShortName(), 0, 0)); connectionTabContent.addSet("Coordinators", GroupCoordinatorsModel.getInstance(), new PersonRenderer(false), new GetGroupCoordinatorsRequest(group.getShortName(), new HashSet<PersonModelView>(group .getCoordinators()))); return connectionTabContent; } /** * Set up the checklist. */ private void setUpChecklist() { checklistDivider.addStyleName(StaticResourceBundle.INSTANCE.coreCss().leftBarChildDivider()); leftBarPanel.add(checklistDivider); leftBarPanel.add(checklistPanel); checklistPanel.addTask(new Task("Basic Information", "Upload your avatar and provide a short description of the group stream."), (group.getAvatarId() != null && group.getDescription() != null)); checklistPanel.addTask(new Task("Overview", "Provide an overview of the group and the members you are looking to participate in the stream."), (group.getOverview() != null)); checklistPanel.addTask(new Task("Keywords", "List some keywords that describe the activity that will be posted to the stream."), (group .getCapabilities() != null && group.getCapabilities().size() > 0)); } /** * Creates a new error report box and centers it on the page. * * @return The error report box, ready to have content added to it. */ private Panel addNewCenteredErrorBox() { // create panel Panel errorReport = new FlowPanel(); errorReport.addStyleName(StaticResourceBundle.INSTANCE.coreCss().warningReport()); // center on page FlowPanel centeringPanel = new FlowPanel(); centeringPanel.addStyleName(StaticResourceBundle.INSTANCE.coreCss().warningReportContainer()); centeringPanel.add(errorReport); add(centeringPanel); return errorReport; } /** * Tell the user that the group does not exist. */ private void showInvalidGroupMessage() { OrganizationModel.getInstance().fetch("", true); clear(); Panel errorReport = addNewCenteredErrorBox(); FlowPanel msgPanel = new FlowPanel(); Label msgHeader = new Label("Profile not found"); msgHeader.addStyleName(StaticResourceBundle.INSTANCE.coreCss().warningMessage()); Hyperlink directoryLink = new Hyperlink("profiles", Session.getInstance().generateUrl( new CreateUrlRequest(Page.ORGANIZATIONS, ""))); Label msgText1 = new Label("The group profile you were looking for could not be found. Try browsing the "); Label msgText2 = new Label( " or searching the profiles by entering the name in the \"search profiles\" box above."); FlowPanel msgText = new FlowPanel(); msgText.add(msgText1); msgText.add(directoryLink); msgText.add(msgText2); msgText.addStyleName(StaticResourceBundle.INSTANCE.coreCss().errorMessageText()); msgPanel.add(msgHeader); msgPanel.add(msgText); errorReport.add(msgPanel); } /** * Tell the user that this group is restricted. * * @param inGroup * the restricted access group */ private void showRestrictedGroupMessage(final DomainGroupModelView inGroup) { Panel errorReport = addNewCenteredErrorBox(); errorReport.addStyleName(StaticResourceBundle.INSTANCE.coreCss().groupErrorMsgPanel()); errorReport.addStyleName(StaticResourceBundle.INSTANCE.coreCss().privateGroup()); Label title = new Label("Private Group"); title.addStyleName(StaticResourceBundle.INSTANCE.coreCss().groupErrorMsgPanelTitle()); errorReport.add(title); Label message = new Label("To access this group you must request access from the group's coordinator. " + "The group coordinator will respond to your request via email."); message.addStyleName(StaticResourceBundle.INSTANCE.coreCss().groupErrorMsgPanelText()); errorReport.add(message); final SpinnerLabelButton button = new SpinnerLabelButton(new ClickHandler() { public void onClick(final ClickEvent inArg0) { GroupMembershipRequestModel.getInstance().insert(inGroup.getShortName()); } }); button.addStyleName(StaticResourceBundle.INSTANCE.coreCss().requestAccessButton()); errorReport.add(button); final EventBus eventBus = Session.getInstance().getEventBus(); eventBus.addObserver(InsertedRequestForGroupMembershipResponseEvent.class, new Observer<InsertedRequestForGroupMembershipResponseEvent>() { public void update(final InsertedRequestForGroupMembershipResponseEvent inArg1) { button.disable(); eventBus.notifyObservers(new ShowNotificationEvent(new Notification( "Your request for access has been sent"))); } }); } /** * Tell the user that this group is pending approval. * */ private void showPendingGroupMessage() { Panel errorReport = addNewCenteredErrorBox(); errorReport.addStyleName(StaticResourceBundle.INSTANCE.coreCss().groupErrorMsgPanel()); errorReport.addStyleName(StaticResourceBundle.INSTANCE.coreCss().pendingGroup()); Label title = new Label("Pending Group"); title.addStyleName(StaticResourceBundle.INSTANCE.coreCss().groupErrorMsgPanelTitle()); errorReport.add(title); Label message = new Label( "This group is awaiting an organization coordinator to approve it. Please try again later."); message.addStyleName(StaticResourceBundle.INSTANCE.coreCss().groupErrorMsgPanelText()); errorReport.add(message); } /** * Builds the admin tab. * * @return The tab. */ @SuppressWarnings("unchecked") private SimpleTab buildAdminTab() { final EventBus eventBus = Session.getInstance().getEventBus(); final String membershipRequestsFilterName = "Membership Requests"; // set up the tab itself final PagedListPanel adminTabContent = new PagedListPanel("admin", new SingleColumnPagedListRenderer()); final SimpleTab adminTab = new SimpleTab("Admin", adminTabContent); // wire up the data retrieval events eventBus.addObserver(GotRequestForGroupMembershipResponseEvent.class, new Observer<GotRequestForGroupMembershipResponseEvent>() { public void update(final GotRequestForGroupMembershipResponseEvent event) { if (membershipRequestsFilterName.equals(adminTabContent.getCurrentFilter())) { adminTabContent.render(event.getResponse(), "No membership requests."); } membershipRequestsCount = event.getResponse().getTotal(); adminTabContent.setFilterTitle(membershipRequestsFilterName, membershipRequestsFilterName + " (" + membershipRequestsCount + ")"); adminTab.setTitle("Admin (" + (membershipRequestsCount) + ")"); } }); // request the non-active StaticResourceBundle.INSTANCE.coreCss().filters() to get the counts // Note: if there was more than one list on this tab with counts, we would need to trigger all of the lists to // be loaded so that we could get their counts. per example here: // PendingGroupsModel.getInstance().fetch(new GetPendingGroupsRequest(org.getShortName(), 0, 1), false); // reload list when an item is removed (this handles pulling from next page and pulling in new requests) eventBus.addObservers(new Observer() { public void update(final Object inArg1) { adminTabContent.reload(); } }, DeletedRequestForGroupMembershipResponseEvent.class, InsertedGroupMemberResponseEvent.class); // prepare the StaticResourceBundle.INSTANCE.coreCss().filters() // membership request StaticResourceBundle.INSTANCE.coreCss().filter() adminTabContent.addSet(membershipRequestsFilterName, GroupMembershipRequestModel.getInstance(), new PersonRequestingGroupMembershipRenderer(group.getId(), group.getShortName()), new GetRequestForGroupMembershipRequest(group.getId(), 0, 0)); return adminTab; } }