org.eurekastreams.web.client.ui.pages.activity.ActivityContent.java Source code

Java tutorial

Introduction

Here is the source code for org.eurekastreams.web.client.ui.pages.activity.ActivityContent.java

Source

/*
 * Copyright (c) 2011-2012 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.activity;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eurekastreams.server.domain.AvatarUrlGenerator;
import org.eurekastreams.server.domain.EntityType;
import org.eurekastreams.server.domain.Follower.FollowerStatus;
import org.eurekastreams.server.domain.Identifiable;
import org.eurekastreams.server.domain.Page;
import org.eurekastreams.server.domain.PagedSet;
import org.eurekastreams.server.domain.stream.ActivityDTO;
import org.eurekastreams.server.domain.stream.Stream;
import org.eurekastreams.server.domain.stream.StreamFilter;
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.server.search.modelview.PersonModelView.Role;
import org.eurekastreams.web.client.events.CustomStreamCreatedEvent;
import org.eurekastreams.web.client.events.CustomStreamDeletedEvent;
import org.eurekastreams.web.client.events.CustomStreamUpdatedEvent;
import org.eurekastreams.web.client.events.EventBus;
import org.eurekastreams.web.client.events.HistoryViewsChangedEvent;
import org.eurekastreams.web.client.events.MessageStreamAppendEvent;
import org.eurekastreams.web.client.events.Observer;
import org.eurekastreams.web.client.events.ShowNotificationEvent;
import org.eurekastreams.web.client.events.StreamReinitializeRequestEvent;
import org.eurekastreams.web.client.events.StreamSearchBeginEvent;
import org.eurekastreams.web.client.events.UpdateHistoryEvent;
import org.eurekastreams.web.client.events.UpdatedHistoryParametersEvent;
import org.eurekastreams.web.client.events.data.AddedFeaturedStreamResponseEvent;
import org.eurekastreams.web.client.events.data.GotActivityResponseEvent;
import org.eurekastreams.web.client.events.data.GotCurrentUserCustomStreamsResponseEvent;
import org.eurekastreams.web.client.events.data.GotCurrentUserStreamBookmarks;
import org.eurekastreams.web.client.events.data.GotGroupModelViewInformationResponseEvent;
import org.eurekastreams.web.client.events.data.GotPersonFollowerStatusResponseEvent;
import org.eurekastreams.web.client.events.data.GotPersonalInformationResponseEvent;
import org.eurekastreams.web.client.events.data.GotStreamActivitySubscriptionResponseEvent;
import org.eurekastreams.web.client.events.data.GotStreamResponseEvent;
import org.eurekastreams.web.client.events.data.InsertedGroupMemberResponseEvent;
import org.eurekastreams.web.client.events.data.InsertedPersonFollowerResponseEvent;
import org.eurekastreams.web.client.events.data.InsertedRequestForGroupMembershipResponseEvent;
import org.eurekastreams.web.client.events.data.PostableStreamScopeChangeEvent;
import org.eurekastreams.web.client.events.data.StreamActivitySubscriptionChangedEvent;
import org.eurekastreams.web.client.events.data.UpdatedGroupStickyActivityEvent;
import org.eurekastreams.web.client.history.CreateUrlRequest;
import org.eurekastreams.web.client.jsni.EffectsFacade;
import org.eurekastreams.web.client.jsni.WidgetJSNIFacadeImpl;
import org.eurekastreams.web.client.model.ActivityModel;
import org.eurekastreams.web.client.model.CustomStreamModel;
import org.eurekastreams.web.client.model.Deletable;
import org.eurekastreams.web.client.model.GadgetModel;
import org.eurekastreams.web.client.model.GroupActivitySubscriptionModel;
import org.eurekastreams.web.client.model.GroupMembershipRequestModel;
import org.eurekastreams.web.client.model.GroupModel;
import org.eurekastreams.web.client.model.PersonActivitySubscriptionModel;
import org.eurekastreams.web.client.model.PersonalInformationModel;
import org.eurekastreams.web.client.model.StreamBookmarksModel;
import org.eurekastreams.web.client.model.StreamModel;
import org.eurekastreams.web.client.model.requests.AddGadgetToStartPageRequest;
import org.eurekastreams.web.client.ui.Session;
import org.eurekastreams.web.client.ui.common.SpinnerLabelButton;
import org.eurekastreams.web.client.ui.common.avatar.AvatarBadgeManager;
import org.eurekastreams.web.client.ui.common.avatar.AvatarLinkPanel;
import org.eurekastreams.web.client.ui.common.avatar.AvatarWidget.Size;
import org.eurekastreams.web.client.ui.common.dialog.Dialog;
import org.eurekastreams.web.client.ui.common.notifier.Notification;
import org.eurekastreams.web.client.ui.common.stream.ActivityDetailPanel;
import org.eurekastreams.web.client.ui.common.stream.GroupEmailSubscribeOptionsDialogContent;
import org.eurekastreams.web.client.ui.common.stream.StreamJsonRequestFactory;
import org.eurekastreams.web.client.ui.common.stream.StreamSearchStatusWidget;
import org.eurekastreams.web.client.ui.common.stream.StreamToUrlTransformer;
import org.eurekastreams.web.client.ui.common.stream.UnseenActivityNotificationPanel;
import org.eurekastreams.web.client.ui.common.stream.filters.list.CustomStreamDialogContent;
import org.eurekastreams.web.client.ui.common.stream.renderers.ShowRecipient;
import org.eurekastreams.web.client.ui.common.stream.renderers.StickyActivityRenderer;
import org.eurekastreams.web.client.ui.common.stream.renderers.StreamMessageItemRenderer;
import org.eurekastreams.web.client.ui.common.widgets.activity.PostBoxComposite;
import org.eurekastreams.web.client.ui.common.widgets.activity.StreamDetailsComposite;
import org.eurekastreams.web.client.ui.common.widgets.activity.StreamDetailsComposite.CustomAvatar;
import org.eurekastreams.web.client.ui.pages.master.StaticResourceBundle;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
//import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.Hyperlink;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.http.client.URL;

/**
 * Activity Page.
 */
public class ActivityContent extends Composite {
    /** Text displayed for locked users. */
    private static final String LOCKED_USER_TEXT = "This employee has no profile in Eureka Streams.  This could be due"
            + " to an incorrect or outdated link, a change in assignment within the company, or leaving the company.";

    /** Amount of time to wait after a key is pressed before performing a search. */
    private static final int SEARCH_UPDATE_DELAY = 500;

    /** Amount of time to wait after a key is pressed to update the URL with the search term. */
    private static final int SEARCH_URL_UPDATE_DELAY = 2000;

    /** Binder for building UI. */
    private static LocalUiBinder binder = GWT.create(LocalUiBinder.class);

    /**
     * CSS resource.
     */
    interface ActivityStyle extends CssResource {
        /**
         * Active sort style.
         *
         * @return Active sort style
         */
        String activeSort();

        /**
         * Active stream style.
         *
         * @return Active stream style.
         */
        String activeStream();

        /**
         * Stream options child.
         *
         * @return Stream options child.
         */
        String streamOptionChild();

        /**
         * Delete bookmark.
         *
         * @return delete bookmark.
         */
        String deleteBookmark();

        /**
         * Edit custom stream.
         *
         * @return edit custom stream.
         */
        String editCustomStream();

        /**
         * The stream name style.
         *
         * @return the stream name style.
         */
        String streamName();

        /**
         * Active search style.
         *
         * @return active search style.
         */
        String activeSearch();

        /**
         * Current user link style.
         *
         * @return current user stream style.
         */
        String currentUserStreamLink();

        /**
         * Small avatar.
         *
         * @return small avatar style.
         */
        String smallAvatar();

        /**
         * Current user configure link.
         *
         * @return current user configure link.
         */
        String currentUserConfLink();

        /**
         * Message when no bookmarks exist.
         *
         * @return message when no bookmarks exist.
         */
        String noBookmarksMessage();

        /** @return The stream container panel. */
        @ClassName("stream-container-panel")
        String streamContainerPanel();
    }

    /**
     * CSS style.
     */
    @UiField
    ActivityStyle style;

    /**
     * Stream details.
     */
    @UiField
    StreamDetailsComposite streamDetailsComposite;

    /**
     * No results panel.
     */
    @UiField
    DivElement noResults;

    /**
     * Search container.
     */
    @UiField
    DivElement searchContainer;

    /**
     * UI element for streams.
     */
    @UiField
    FlowPanel streamPanel;

    /**
     * UI element for bookmarks.
     */
    @UiField
    FlowPanel bookmarkList;

    /**
     * UI element for stream container.
     */
    @UiField
    HTMLPanel streamContainerPanel;

    /**
     * UI element for filters.
     */
    @UiField
    FlowPanel filterList;

    /**
     * UI element for default streams.
     */
    @UiField
    FlowPanel defaultList;

    /**
     * UI element for default streams.
     */
    @UiField
    FlowPanel errorPanel;

    /**
     * UI element for recent sort.
     */
    @UiField
    Hyperlink recentSort;

    /**
     * UI element for popular sort.
     */
    @UiField
    Hyperlink popularSort;

    /**
     * UI element for active sort.
     */
    @UiField
    Hyperlink activeSort;

    /**
     * UI element for activity loading spinner.
     */
    @UiField
    DivElement activitySpinner;

    /**
     * Feed link.
     */
    @UiField
    Label feedLink;

    /**
     * UI element for more spinner.
     */
    @UiField
    DivElement moreSpinner;

    /**
     * UI element for more link.
     */
    @UiField
    Label moreLink;

    /**
     * UI element for adding a bookmark.
     */
    @UiField
    Label addBookmark;

    /**
     * UI element for adding a a stream to the start page.
     */
    @UiField
    Label addToStartPage;

    /**
     * Create Filter.
     */
    @UiField
    Label createFilter;

    /**
     * Subscribe via email.
     */
    @UiField
    Label subscribeViaEmail;

    /**
     * Stream search status widget.
     */
    @UiField
    StreamSearchStatusWidget streamSearchStatusWidget;

    /** Link to get contact for emailing to stream. */
    //@UiField
    //Anchor getEmailContactLink;

    /**
     * Panel for unseen activity notifications - hidden when viewing a single activity.
     */
    @UiField
    UnseenActivityNotificationPanel unseenActivityNotificationPanel;

    /**
     * Message Renderer.
     */
    StreamMessageItemRenderer renderer = new StreamMessageItemRenderer(ShowRecipient.YES);

    /** Sticky activity renderer (for groups only). */
    private static StickyActivityRenderer stickyActivityRenderer = new StickyActivityRenderer();

    /**
     * Newest activity ID.
     */
    private long longNewestActivityId = 0L;

    /**
     * Oldest Activity ID.
     */
    private long longOldestActivityId = 0;

    /**
     * Current Request.
     */
    private JSONObject currentRequestObj = null;

    /**
     * Search Box.
     */
    @UiField
    TextBox searchBox;

    /**
     * Stream options panel.
     */
    @UiField
    DivElement streamOptionsPanel;

    /**
     * Current stream scope.
     */
    private StreamScope currentStream;

    /**
     * Current scope id.
     */
    private long currentScopeId;

    /**
     * Current stream display name.
     */
    private String currentDisplayName;

    /**
     * Entity for the current stream. Used by the sticky activity logic to determine if any stick/unstick events pertain
     * to the current view. Would use currentStream, but there is other code that clobbers the entity type if the stream
     * isn't postable and thus renders currentStream useless for the purpose.
     */
    private Identifiable currentStreamEntity;

    /**
     * New activity polling.
     */
    private static final int NEW_ACTIVITY_POLLING_DELAY = 1200000;

    /**
     * Custom streams map.
     */
    private final HashMap<Long, StreamNamePanel> customStreamWidgetMap = new HashMap<Long, StreamNamePanel>();

    /**
     * Stream bookmarks map.
     */
    private final HashMap<String, StreamNamePanel> bookmarksWidgetMap = new HashMap<String, StreamNamePanel>();

    /**
     * Currently active stream.
     */
    private Panel currentlyActiveStream = null;

    /**
     * Following filter panel.
     */
    private Panel followingFilterPanel = null;

    /**
     * Everyone filter panel.
     */
    private Panel everyoneFilterPanel = null;

    /**
     * Is subscribed.
     */
    private boolean isSubscribed = false;

    /**
     * Post Box.
     */
    @UiField
    PostBoxComposite postBox;

    /**
     * User panel.
     */
    @UiField
    FlowPanel userPanel;

    /**
     * Current sort keyword.
     */
    private String sortKeyword = "";

    /**
     * Single activity mode.
     */
    private boolean singleActivityMode;

    /**
     * Bookmarks initially loaded.
     */
    private boolean bookmarksLoaded = false;

    /**
     * Bookmarks initially loaded.
     */
    private boolean customStreamsLoaded = false;

    /**
     * If the page has ran init.
     */
    private boolean hasInited = false;

    /** Stream to URL transformer. */
    private static final StreamToUrlTransformer STREAM_URL_TRANSFORMER = new StreamToUrlTransformer();

    /**
     * If the entity still needs to be received before making the activity query.
     *
     * Explanation: Fetching the activities for a group requires knowing which activity is sticky so it can be excluded
     * on the query. So the query must be built in loadStream and the DomainGroupModelView must be recieved.
     * Unfortunately, the event bus notifies inline instead of queuing, thus the call to the GroupModel in loadStream
     * may fire its event immediately (if the group is in cache), causing the group to be received before loadStream
     * finishes. So we have to handle two different possible orders. The approach taken is to set this flag and the one
     * below; whenever they are both reset, the activity query is sent.
     */
    private boolean deferLoadAwaitingEntityReceived;

    /**
     * If the activity query isn't done being constructed yet (before making the activity query). See
     * deferLoadAwaitingEntityReceived.
     */
    private boolean deferLoadAwaitingQueryBuilt;

    /** Place to put sticky activity. */
    @UiField
    SimplePanel stickyActivityHolder;

    /** Area containing the sticky activity. */
    @UiField
    DivElement stickyActivityArea;

    /** Views used to load the current stream. */
    List<String> loadedViews = Collections.singletonList("[do not match]");

    /** So results for the wrong stream can be detected and ignored. */
    private String currentStreamRequest;

    /** Search term used to load the current stream. */
    String loadedSearchTerm = "";

    /** Timer to delay progressive search until the user pauses. */
    private final Timer searchTimer = new Timer() {
        @Override
        public void run() {
            String searchText = searchBox.getText();
            loadStream(Session.getInstance().getUrlViews(), searchText);
            searchUrlTimer.schedule(SEARCH_URL_UPDATE_DELAY);
        }
    };

    /** Timer to update the URL with the search term. */
    private final Timer searchUrlTimer = new Timer() {
        @Override
        public void run() {
            updateUrlWithSearchTerm();
        }
    };

    /**
     * Default constructor.
     */
    public ActivityContent() {
        initWidget(binder.createAndBindUi(this));
        buildPage();
    }

    /**
     * Build the page.
     */
    private void buildPage() {
        addEventHandlers();
        addObservers();
        setupStreamsAndBookmarks();
        moreLink.setVisible(false);
        streamSearchStatusWidget.setVisible(false);
        errorPanel.setVisible(false);

        followingFilterPanel = createPanel("Following", "following", "style/images/customStream.png", null, "", "",
                false);
        everyoneFilterPanel = createPanel("Everyone", "everyone", "style/images/customStream.png", null, "", "",
                false);

        defaultList.add(followingFilterPanel);
        defaultList.add(everyoneFilterPanel);

        final PersonModelView currentPerson = Session.getInstance().getCurrentPerson();

        AvatarLinkPanel userAvatar = new AvatarLinkPanel(currentPerson.getEntityType(),
                currentPerson.getAccountId(), currentPerson.getAvatarId(), Size.Small,
                currentPerson.getDisplayName());
        userPanel.add(userAvatar);

        FlowPanel userLinkPanel = new FlowPanel();

        String nameUrl = Session.getInstance()
                .generateUrl(new CreateUrlRequest(Page.PEOPLE, currentPerson.getAccountId()));
        Hyperlink name = new Hyperlink(currentPerson.getDisplayName(), nameUrl);
        name.setTitle(currentPerson.getDisplayName());
        name.addStyleName(style.currentUserStreamLink());
        name.addStyleName(StaticResourceBundle.INSTANCE.coreCss().ellipsisChild());
        userLinkPanel.add(name);

        String confUrl = Session.getInstance()
                .generateUrl(new CreateUrlRequest(Page.PERSONAL_SETTINGS, currentPerson.getAccountId()));
        Hyperlink conf = new Hyperlink("Configure My Stream", confUrl);
        conf.addStyleName(style.currentUserConfLink());
        userLinkPanel.add(conf);

        userPanel.add(userLinkPanel);

        CustomStreamModel.getInstance().fetch(null, true);
        StreamBookmarksModel.getInstance().fetch(null, true);

        moreSpinner.addClassName(StaticResourceBundle.INSTANCE.coreCss().displayNone());
        noResults.addClassName(StaticResourceBundle.INSTANCE.coreCss().displayNone());
        unseenActivityNotificationPanel.setActive(true);
    }

    /**
     * Got activity.
     *
     * @param event
     *            the event.
     */
    private void gotActivity(final GotActivityResponseEvent event) {
        streamPanel.clear();
        unseenActivityNotificationPanel.setActive(false);
        activitySpinner.addClassName(StaticResourceBundle.INSTANCE.coreCss().displayNone());

        if (event.getResponse() != null) {
            EntityType actorType = event.getResponse().getDestinationStream().getEntityType();
            String actorName = event.getResponse().getDestinationStream().getUniqueId();

            if (actorType.equals(EntityType.GROUP)) {
                GroupModel.getInstance().fetch(actorName, false);

            } else if (actorType.equals(EntityType.PERSON)) {
                PersonalInformationModel.getInstance().fetch(actorName, false);
            }
        }

        streamPanel.add(new ActivityDetailPanel(event.getResponse(), ShowRecipient.YES));
        streamPanel.removeStyleName(StaticResourceBundle.INSTANCE.coreCss().hidden());
    }

    /**
     * Add events.
     */
    private void addObservers() {
        final EventBus eventBus = EventBus.getInstance();
        eventBus.addObserver(GotActivityResponseEvent.class, new Observer<GotActivityResponseEvent>() {

            public void update(final GotActivityResponseEvent event) {
                gotActivity(event);
            }
        });
        eventBus.addObserver(GotStreamResponseEvent.class, new Observer<GotStreamResponseEvent>() {
            public void update(final GotStreamResponseEvent event) {
                // throw out results if for the wrong stream (or we don't want the results)
                if (currentStreamRequest == null || !currentStreamRequest.equals(event.getRequest())) {
                    return;
                }

                final PagedSet<ActivityDTO> activitySet = event.getStream();
                if (activitySet.getPagedSet().size() > 0) {
                    longNewestActivityId = activitySet.getPagedSet().get(0).getEntityId();
                    longOldestActivityId = activitySet.getPagedSet().get(activitySet.getPagedSet().size() - 1)
                            .getEntityId();
                }

                if (StreamJsonRequestFactory.getJSONRequest(event.getJsonRequest()).containsKey("minId")) {
                    for (int i = activitySet.getPagedSet().size(); i > 0; i--) {
                        appendActivity(activitySet.getPagedSet().get(i - 1));
                    }
                } else if (StreamJsonRequestFactory.getJSONRequest(event.getJsonRequest()).containsKey("maxId")) {
                    moreSpinner.addClassName(StaticResourceBundle.INSTANCE.coreCss().displayNone());
                    for (ActivityDTO activity : activitySet.getPagedSet()) {
                        streamPanel.add(renderer.render(activity));
                    }
                    moreLink.setVisible(activitySet.getTotal() > activitySet.getPagedSet().size());
                } else {
                    streamPanel.clear();
                    unseenActivityNotificationPanel.setActive(true);
                    activitySpinner.addClassName(StaticResourceBundle.INSTANCE.coreCss().displayNone());
                    streamPanel.removeStyleName(StaticResourceBundle.INSTANCE.coreCss().hidden());

                    List<ActivityDTO> activities = activitySet.getPagedSet();
                    for (ActivityDTO activity : activities) {
                        streamPanel.add(renderer.render(activity));
                    }
                    if (activities.size() == 0) {
                        noResults.removeClassName(StaticResourceBundle.INSTANCE.coreCss().displayNone());
                    }
                    moreLink.setVisible(activitySet.getTotal() > activities.size());
                }
                if (activitySet.getPagedSet().size() > 0) {
                    noResults.addClassName(StaticResourceBundle.INSTANCE.coreCss().displayNone());
                }
            }
        });

        // users are not initially subscribed for emails when following a person/group, so set the status properly (else
        // if you were following and unsubscribed, then re-subscribed, the status would be old and wrong)
        eventBus.addObserver(InsertedPersonFollowerResponseEvent.class,
                new Observer<InsertedPersonFollowerResponseEvent>() {
                    public void update(final InsertedPersonFollowerResponseEvent ev) {
                        setSubscribeStatus(false);
                    }
                });

        eventBus.addObserver(InsertedGroupMemberResponseEvent.class,
                new Observer<InsertedGroupMemberResponseEvent>() {
                    public void update(final InsertedGroupMemberResponseEvent ev) {
                        setSubscribeStatus(false);
                    }
                });

        eventBus.addObserver(GotPersonFollowerStatusResponseEvent.class,
                new Observer<GotPersonFollowerStatusResponseEvent>() {
                    public void update(final GotPersonFollowerStatusResponseEvent event) {
                        subscribeViaEmail.setVisible(event.getResponse().equals(FollowerStatus.FOLLOWING));
                    }
                });

        eventBus.addObserver(HistoryViewsChangedEvent.class, new Observer<HistoryViewsChangedEvent>() {
            public void update(final HistoryViewsChangedEvent event) {
                searchTimer.cancel();
                searchUrlTimer.cancel();
                handleViewsChanged(event.getViews());
                final String searchText = Session.getInstance().getParameterValue("search");
                if (!searchBox.getText().equals(searchText)) {
                    searchBox.setText(searchText);
                }
            }
        });

        eventBus.addObserver(MessageStreamAppendEvent.class, new Observer<MessageStreamAppendEvent>() {
            public void update(final MessageStreamAppendEvent event) {
                longNewestActivityId = event.getMessage().getId();

                if (sortKeyword.equals("date")) {
                    appendActivity(event.getMessage());
                    noResults.addClassName(StaticResourceBundle.INSTANCE.coreCss().displayNone());
                } else {
                    recentSort.getElement().dispatchEvent(
                            Document.get().createClickEvent(1, 0, 0, 0, 0, false, false, false, false));
                }

            }
        });

        eventBus.addObserver(CustomStreamCreatedEvent.class, new Observer<CustomStreamCreatedEvent>() {
            public void update(final CustomStreamCreatedEvent event) {
                CustomStreamModel.getInstance().fetch(null, true);
            }
        });

        eventBus.addObserver(CustomStreamDeletedEvent.class, new Observer<CustomStreamDeletedEvent>() {
            public void update(final CustomStreamDeletedEvent event) {
                CustomStreamModel.getInstance().fetch(null, true);
            }
        });

        eventBus.addObserver(CustomStreamUpdatedEvent.class, new Observer<CustomStreamUpdatedEvent>() {
            public void update(final CustomStreamUpdatedEvent event) {
                CustomStreamModel.getInstance().fetch(null, true);
            }
        });

        eventBus.addObserver(StreamReinitializeRequestEvent.class, new Observer<StreamReinitializeRequestEvent>() {
            public void update(final StreamReinitializeRequestEvent event) {
                loadStream(Session.getInstance().getUrlViews(), Session.getInstance().getParameterValue("search"));
            }
        });

        eventBus.addObserver(UpdatedHistoryParametersEvent.class, new Observer<UpdatedHistoryParametersEvent>() {
            public void update(final UpdatedHistoryParametersEvent event) {
                searchTimer.cancel();
                searchUrlTimer.cancel();
                if (!event.getViewChanged()) {
                    handleViewsChanged(Session.getInstance().getUrlViews());
                }
            }
        });

        eventBus.addObserver(AddedFeaturedStreamResponseEvent.class,
                new Observer<AddedFeaturedStreamResponseEvent>() {
                    public void update(final AddedFeaturedStreamResponseEvent event) {
                        eventBus.notifyObservers(
                                new ShowNotificationEvent(new Notification("Stream has been featured.")));
                    }
                });

        eventBus.addObserver(UpdatedGroupStickyActivityEvent.class,
                new Observer<UpdatedGroupStickyActivityEvent>() {
                    public void update(final UpdatedGroupStickyActivityEvent ev) {
                        // make sure event applies to the current view (since current view may not even be a group)
                        if (currentStreamEntity != null && currentStreamEntity.getEntityType() == EntityType.GROUP
                                && ev.getGroupId() == currentStreamEntity.getEntityId() && !singleActivityMode) {
                            if (ev.getActivity() == null) {
                                stickyActivityHolder.clear();
                                UIObject.setVisible(stickyActivityArea, false);
                            } else {
                                stickyActivityHolder.clear();
                                stickyActivityHolder.add(stickyActivityRenderer.render(ev.getActivity()));
                                UIObject.setVisible(stickyActivityArea, true);
                            }

                            // reload the stream to get prior sticky activities back in it (and a freshly stuck activity out)
                            eventBus.notifyObservers(StreamReinitializeRequestEvent.getEvent());
                        }
                    }
                });

        addEntityObservers();
    }

    /**
     * Add entity observers.
     */
    private void addEntityObservers() {
        EventBus.getInstance().addObserver(GotPersonalInformationResponseEvent.class,
                new Observer<GotPersonalInformationResponseEvent>() {
                    public void update(final GotPersonalInformationResponseEvent event) {
                        PersonModelView person = event.getResponse();
                        currentDisplayName = person.getDisplayName();
                        currentScopeId = person.getStreamId();

                        if (person.isAccountLocked()) {
                            streamOptionsPanel.getStyle().setDisplay(Display.NONE);
                            currentStream.setScopeType(null);
                            errorPanel.clear();
                            errorPanel.setVisible(true);
                            activitySpinner.addClassName(StaticResourceBundle.INSTANCE.coreCss().displayNone());
                            errorPanel.add(new Label("Employee profile not found"));
                            errorPanel.add(new Label(LOCKED_USER_TEXT));
                            streamPanel.removeStyleName(StaticResourceBundle.INSTANCE.coreCss().hidden());

                            streamDetailsComposite.setVisible(false);
                            currentStream.setScopeType(null);

                            // block display of activities
                            currentStreamRequest = null;
                            streamPanel.clear();
                            unseenActivityNotificationPanel.setActive(false);
                            activitySpinner.addClassName(StaticResourceBundle.INSTANCE.coreCss().displayNone());
                            streamPanel.addStyleName(StaticResourceBundle.INSTANCE.coreCss().hidden());
                            noResults.addClassName(StaticResourceBundle.INSTANCE.coreCss().displayNone());
                            moreLink.setVisible(false);
                        } else {
                            currentStream.setDisplayName(person.getDisplayName());
                            streamDetailsComposite.setVisible(true);
                        }
                        if (!person.isStreamPostable() && !person.getAccountId()
                                .equals(Session.getInstance().getCurrentPerson().getAccountId())) {
                            currentStream.setScopeType(null);
                        }
                        /*if (currentStream.getScopeType() != null)
                        {
                            getEmailContactLink.setHref("/resources/emailcontact/stream/person/" + person.getId());
                            getEmailContactLink.setVisible(true);
                        }*/
                        if (!singleActivityMode) {
                            EventBus.getInstance()
                                    .notifyObservers(new PostableStreamScopeChangeEvent(currentStream));
                        }
                    }
                });

        EventBus.getInstance().addObserver(GotGroupModelViewInformationResponseEvent.class,
                new Observer<GotGroupModelViewInformationResponseEvent>() {
                    public void update(final GotGroupModelViewInformationResponseEvent event) {
                        onGroupModelViewReceived(event.getResponse());
                    }
                });
    }

    /**
     * Processing when group information is received.
     *
     * @param group
     *            The group.
     */
    private void onGroupModelViewReceived(final DomainGroupModelView group) {
        // If group is null, then that means that the group 
        // was not found. As a result, a "Group not found" 
        // page should be displayed. 
        // This page is very similar to the "Activity not found" page
        if (group == null) {
            showGroupNotFoundPage();
        } else {
            currentDisplayName = group.getDisplayName();
            currentScopeId = group.getStreamId();

            currentStream.setDisplayName(group.getName());
            streamDetailsComposite.setVisible(true);

            if (group.isRestricted()) {
                streamOptionsPanel.getStyle().setDisplay(Display.NONE);
                currentStream.setScopeType(null);
                postBox.setVisible(false);

                errorPanel.clear();
                errorPanel.setVisible(true);
                activitySpinner.addClassName(StaticResourceBundle.INSTANCE.coreCss().displayNone());
                errorPanel.add(new Label("Access to this group is restricted"));
                errorPanel.add(new Label("To view this group's stream please request access from its coordinator"));

                final SpinnerLabelButton button = new SpinnerLabelButton(new ClickHandler() {
                    public void onClick(final ClickEvent inArg0) {
                        GroupMembershipRequestModel.getInstance().insert(group.getShortName());
                    }
                });

                EventBus.getInstance().addObserver(InsertedRequestForGroupMembershipResponseEvent.class,
                        new Observer<InsertedRequestForGroupMembershipResponseEvent>() {
                            public void update(final InsertedRequestForGroupMembershipResponseEvent inArg1) {
                                button.disable();
                                EventBus.getInstance().notifyObservers(new ShowNotificationEvent(
                                        new Notification("Your request for access has been sent")));
                            }
                        });

                button.addStyleName(StaticResourceBundle.INSTANCE.coreCss().requestAccessButton());
                errorPanel.add(button);

                streamPanel.clear();
                unseenActivityNotificationPanel.setActive(true);
            } else {
                currentStreamEntity = group;

                if (group.getStickyActivity() != null && !singleActivityMode
                        && (loadedSearchTerm == null || loadedSearchTerm == "")) {
                    stickyActivityHolder.add(stickyActivityRenderer.render(group.getStickyActivity()));
                    UIObject.setVisible(stickyActivityArea, true);
                }
            }
            boolean isCoordinator = false;

            for (PersonModelView coordinator : group.getCoordinators()) {
                AvatarBadgeManager.getInstance().setBadge(style.streamContainerPanel(), coordinator.getUniqueId());
                if (coordinator.getAccountId().equals(Session.getInstance().getCurrentPerson().getAccountId())) {
                    isCoordinator = true;
                }
            }
            if (!group.isStreamPostable() && !isCoordinator) {
                currentStream.setScopeType(null);
            }
            /*else
            {
                getEmailContactLink.setHref("/resources/emailcontact/stream/group/" + group.getId());
                getEmailContactLink.setVisible(true);
            }*/

            if (!singleActivityMode) {
                if (Session.getInstance().getCurrentPersonRoles().contains(Role.SYSTEM_ADMIN) || isCoordinator) {
                    streamContainerPanel.addStyleName(StaticResourceBundle.INSTANCE.coreCss().hasOwnerRights());
                }

                EventBus.getInstance().notifyObservers(new PostableStreamScopeChangeEvent(currentStream));
            }

            if (deferLoadAwaitingEntityReceived) {
                deferLoadAwaitingEntityReceived = false;
                currentRequestObj = StreamJsonRequestFactory.setExcludeId(group.getStickyActivityId(),
                        currentRequestObj);
                if (!deferLoadAwaitingQueryBuilt) {
                    fetchStream(currentRequestObj);
                }
            }
        }
    }

    /**
     * Shows a "not found" message.
     */
    private void showGroupNotFoundPage() {
        activitySpinner.addClassName(StaticResourceBundle.INSTANCE.coreCss().displayNone());
        streamPanel.clear();
        errorPanel.clear();
        postBox.setVisible(false);

        Panel errorReport = new FlowPanel();
        errorReport.addStyleName(StaticResourceBundle.INSTANCE.coreCss().warningReport());

        FlowPanel centeringPanel = new FlowPanel();
        centeringPanel.addStyleName(StaticResourceBundle.INSTANCE.coreCss().warningReportContainer());
        centeringPanel.add(errorReport);
        streamPanel.add(centeringPanel);

        FlowPanel msgPanel = new FlowPanel();

        Label msgHeader = new Label("Group not found");
        msgHeader.addStyleName(StaticResourceBundle.INSTANCE.coreCss().warningMessage());

        Label msgText = new Label("The group you were looking for has been deleted or could not be found.");
        FlowPanel text = new FlowPanel();
        text.add(msgText);
        text.addStyleName(StaticResourceBundle.INSTANCE.coreCss().errorMessageText());

        msgPanel.add(msgHeader);
        msgPanel.add(msgText);

        streamPanel.removeStyleName(StaticResourceBundle.INSTANCE.coreCss().hidden());

        streamPanel.setVisible(true);
        streamContainerPanel.setVisible(true);
        errorReport.add(msgPanel);
        errorPanel.setVisible(true);
        errorReport.setVisible(true);
        centeringPanel.setVisible(true);
        msgPanel.setVisible(true);
    }

    /**
     * Handle views changed.
     *
     * @param inViews
     *            the views.
     */
    protected void handleViewsChanged(final List<String> inViews) {
        String search = Session.getInstance().getParameterValue("search");

        // prevent reloading the same content
        if (!loadedViews.equals(inViews) || !loadedSearchTerm.equals(search)) {
            loadStream(inViews, search);
        }
        List<String> views = new ArrayList<String>(inViews);

        if (views.size() < 2 || !"sort".equals(views.get(views.size() - 2))) {
            views.add("sort");
            views.add("recent");
        }

        Map<String, String> params = (search == null || search.isEmpty()) ? Collections.EMPTY_MAP
                : Collections.singletonMap("search", search);

        views.set(views.size() - 1, "recent");
        recentSort.setTargetHistoryToken(
                Session.getInstance().generateUrl(new CreateUrlRequest(Page.ACTIVITY, views, params)));

        views.set(views.size() - 1, "popular");
        popularSort.setTargetHistoryToken(
                Session.getInstance().generateUrl(new CreateUrlRequest(Page.ACTIVITY, views, params)));

        views.set(views.size() - 1, "active");
        activeSort.setTargetHistoryToken(
                Session.getInstance().generateUrl(new CreateUrlRequest(Page.ACTIVITY, views, params)));
    }

    /**
     * Setup bookmarks and custom streams.
     */
    private void setupStreamsAndBookmarks() {
        final EventBus eventBus = EventBus.getInstance();

        eventBus.addObserver(GotCurrentUserStreamBookmarks.class, new Observer<GotCurrentUserStreamBookmarks>() {
            private final AvatarUrlGenerator groupUrlGen = new AvatarUrlGenerator(EntityType.GROUP);
            private final AvatarUrlGenerator personUrlGen = new AvatarUrlGenerator(EntityType.PERSON);

            public void update(final GotCurrentUserStreamBookmarks event) {
                bookmarkList.clear();
                bookmarksWidgetMap.clear();

                List<StreamFilter> sortedStreamFilters = event.getResponse();
                Collections.sort(sortedStreamFilters, new StreamFilterNameComparator());

                for (final StreamFilter filter : sortedStreamFilters) {
                    JSONObject req = StreamJsonRequestFactory.getJSONRequest(filter.getRequest());
                    String uniqueId = null;
                    String entityType = null;

                    String imgUrl = "";

                    if (req.containsKey("query")) {
                        JSONObject query = req.get("query").isObject();
                        if (query.containsKey(StreamJsonRequestFactory.RECIPIENT_KEY)) {
                            JSONArray recipient = query.get(StreamJsonRequestFactory.RECIPIENT_KEY).isArray();
                            if (recipient.size() > 0) {
                                JSONObject recipientObj = recipient.get(0).isObject();
                                uniqueId = recipientObj.get("name").isString().stringValue();
                                entityType = recipientObj.get("type").isString().stringValue().toLowerCase();

                                AvatarUrlGenerator urlGen = groupUrlGen;

                                if ("person".equals(entityType)) {
                                    urlGen = personUrlGen;
                                }

                                imgUrl = urlGen.getSmallAvatarUrl(filter.getOwnerAvatarId());

                            }
                        }

                    }

                    if (uniqueId != null && entityType != null) {
                        String bookmarkUrl = entityType + "/" + uniqueId;
                        StreamNamePanel bookmarkFilter = createPanel(filter.getName(), bookmarkUrl, imgUrl,
                                new ClickHandler() {
                                    public void onClick(final ClickEvent event) {
                                        if (new WidgetJSNIFacadeImpl()
                                                .confirm("Are you sure you want to delete this bookmark?")) {
                                            StreamBookmarksModel.getInstance().delete(filter.getId());
                                        }

                                        event.stopPropagation();
                                    }
                                }, style.deleteBookmark(), "", true);

                        bookmarkList.add(bookmarkFilter);
                        bookmarksWidgetMap.put(bookmarkUrl, bookmarkFilter);
                    }

                }
                if (sortedStreamFilters.size() == 0) {
                    Label defaultLabel = new Label("Bookmarks allow you to quickly jump to any stream in Eureka.");
                    defaultLabel.addStyleName(style.noBookmarksMessage());
                    bookmarkList.add(defaultLabel);
                }

                bookmarksLoaded = true;
                checkInit();
            }
        });

        eventBus.addObserver(StreamActivitySubscriptionChangedEvent.class,
                new Observer<StreamActivitySubscriptionChangedEvent>() {
                    public void update(final StreamActivitySubscriptionChangedEvent ev) {
                        boolean newStatus = ev.getResponse().getReceiveNewActivityNotifications();
                        setSubscribeStatus(newStatus);

                        String msg = newStatus ? "You will now receive emails for new activities to this stream"
                                : "You will no longer receive emails for new activities to this stream";
                        eventBus.notifyObservers(new ShowNotificationEvent(new Notification(msg)));
                    }
                });
        eventBus.addObserver(GotStreamActivitySubscriptionResponseEvent.class,
                new Observer<GotStreamActivitySubscriptionResponseEvent>() {
                    public void update(final GotStreamActivitySubscriptionResponseEvent result) {
                        setSubscribeStatus(result.isSubscribed());
                    }
                });

        eventBus.addObserver(GotCurrentUserCustomStreamsResponseEvent.class,
                new Observer<GotCurrentUserCustomStreamsResponseEvent>() {
                    public void update(final GotCurrentUserCustomStreamsResponseEvent event) {
                        filterList.clear();
                        customStreamWidgetMap.clear();

                        StreamNamePanel savedBy = createPanel("My Saved Items",
                                "custom/0/" + "{\"query\":{\"savedBy\":\""
                                        + Session.getInstance().getCurrentPerson().getAccountId() + "\"}}",
                                "style/images/customStream.png", null, "", "", false);

                        filterList.add(savedBy);
                        customStreamWidgetMap.put(0L, savedBy);

                        StreamNamePanel likedBy = createPanel("My Liked Items",
                                "custom/1/" + "{\"query\":{\"likedBy\":[{\"type\":\"PERSON\", \"name\":\""
                                        + Session.getInstance().getCurrentPerson().getAccountId()
                                        + "\"}]}}/My Liked Items",
                                "style/images/customStream.png", null, "", "", false);

                        filterList.add(likedBy);
                        customStreamWidgetMap.put(1L, likedBy);

                        for (final StreamFilter filter : event.getResponse().getStreamFilters()) {
                            StreamNamePanel filterPanel = createPanel(filter.getName(), "custom/" + filter.getId()
                                    + "/"
                                    + URL.encodeComponent(filter.getRequest().replace("%%CURRENT_USER_ACCOUNT_ID%%",
                                            Session.getInstance().getCurrentPerson().getAccountId())),
                                    "style/images/customStream.png", new ClickHandler() {

                                        public void onClick(final ClickEvent event) {
                                            Dialog.showCentered(new CustomStreamDialogContent((Stream) filter));
                                            event.stopPropagation();
                                        }
                                    }, style.editCustomStream(), "edit", false);

                            filterList.add(filterPanel);
                            customStreamWidgetMap.put(filter.getId(), filterPanel);
                        }

                        customStreamsLoaded = true;
                        checkInit();
                    }
                });

        eventBus.addObserver(StreamSearchBeginEvent.class, new Observer<StreamSearchBeginEvent>() {
            public void update(final StreamSearchBeginEvent event) {
                if (null == event.getSearchText()) {
                    eventBus.notifyObservers(new UpdateHistoryEvent(new CreateUrlRequest("search", "", false)));
                    searchBox.setText("");
                }
            }
        });
    }

    /**
     * Add events.
     */
    private void addEventHandlers() {
        moreLink.addClickHandler(new ClickHandler() {
            public void onClick(final ClickEvent event) {
                moreSpinner.removeClassName(StaticResourceBundle.INSTANCE.coreCss().displayNone());

                JSONObject moreItemsRequest = StreamJsonRequestFactory.setMaxId(longOldestActivityId,
                        StreamJsonRequestFactory.getJSONRequest(currentRequestObj.toString()));

                fetchStream(moreItemsRequest);
            }
        });

        searchBox.addKeyUpHandler(new KeyUpHandler() {
            private int lastSearchLength = 0;

            public void onKeyUp(final KeyUpEvent event) {
                final String searchText = searchBox.getText();
                final int searchTextLength = searchText.length();
                if (event.getNativeKeyCode() == KeyCodes.KEY_ENTER) {
                    lastSearchLength = searchTextLength;

                    searchTimer.cancel();
                    searchUrlTimer.cancel();

                    // don't load stream here - the URL change will cause it to be reloaded

                    EventBus.getInstance().notifyObservers(
                            new UpdateHistoryEvent(new CreateUrlRequest("search", searchText, false)));
                } else if ((searchTextLength > 2 && searchTextLength != lastSearchLength)
                        || searchTextLength < lastSearchLength) {
                    lastSearchLength = searchTextLength;

                    searchTimer.schedule(SEARCH_UPDATE_DELAY);
                }
            }
        });

        searchBox.addBlurHandler(new BlurHandler() {
            public void onBlur(final BlurEvent inEvent) {
                updateUrlWithSearchTerm();
            }
        });

        addBookmark.addClickHandler(new ClickHandler() {
            public void onClick(final ClickEvent event) {
                StreamBookmarksModel.getInstance().insert(currentScopeId);
                addBookmark.setVisible(false);
                EventBus.getInstance().notifyObservers(
                        new ShowNotificationEvent(new Notification("You have bookmarked this stream.")));
            }
        });

        subscribeViaEmail.addClickHandler(new ClickHandler() {
            public void onClick(final ClickEvent event) {
                if (!isSubscribed) {
                    if (currentStream.getScopeType().equals(ScopeType.GROUP)) {
                        Dialog.showCentered(
                                new GroupEmailSubscribeOptionsDialogContent(currentStream.getUniqueKey()));
                    } else if (currentStream.getScopeType().equals(ScopeType.PERSON)) {
                        PersonActivitySubscriptionModel.getInstance().insert(currentStream.getUniqueKey());
                        setSubscribeStatus(true);
                    }
                } else {
                    Deletable<String> deletable = null;

                    if (currentStream.getScopeType().equals(ScopeType.GROUP)) {
                        deletable = GroupActivitySubscriptionModel.getInstance();
                    } else if (currentStream.getScopeType().equals(ScopeType.PERSON)) {
                        deletable = PersonActivitySubscriptionModel.getInstance();
                    }

                    if (deletable != null) {
                        deletable.delete(currentStream.getUniqueKey());
                        setSubscribeStatus(false);
                    }
                }
            }
        });

        addToStartPage.addClickHandler(new ClickHandler() {
            public void onClick(final ClickEvent event) {
                // For the app's location, use the current URL minus a few parameters we know we don't want. (They are
                // used by other lists, but get left in the URL when switching tabs.)
                // We don't build the URL from the stream id, since that doesn't take search terms into account.
                HashMap<String, String> params = new HashMap<String, String>();
                params.put("listId", null);
                params.put("listFilter", null);
                params.put("listSort", null);
                params.put("startIndex", null);
                params.put("endIndex", null);
                String url = Session.getInstance().generateUrl(new CreateUrlRequest(params));

                // make a version of the query that doesn't have the exclusion list
                JSONObject request = currentRequestObj;
                if (request.containsKey("exclude")) {
                    request = new JSONObject();
                    for (String key : currentRequestObj.keySet()) {
                        if (!"exclude".equals(key)) {
                            request.put(key, currentRequestObj.get(key));
                        }
                    }
                }
                // TODO: get correct title from somewhere.
                String prefs = "{\"streamQuery\":"
                        + makeJsonString(STREAM_URL_TRANSFORMER.getUrl(null, request.toString()))
                        + ",\"gadgetTitle\":" + makeJsonString(currentDisplayName) + ",\"streamLocation\":"
                        + makeJsonString(url) + "}";

                GadgetModel.getInstance().insert(
                        new AddGadgetToStartPageRequest("{d7a58391-5375-4c76-b5fc-a431c42a7555}", null, prefs));
                EventBus.getInstance().notifyObservers(new ShowNotificationEvent(
                        new Notification("This stream will now show up on your start page.")));
            }
        });

        createFilter.addClickHandler(new ClickHandler() {
            public void onClick(final ClickEvent event) {
                Dialog.showCentered(new CustomStreamDialogContent());
            }
        });

        feedLink.addClickHandler(new ClickHandler() {
            public void onClick(final ClickEvent event) {
                Window.Location.assign("/resources/atom/stream/query/recipient/" + currentStream.getScopeType()
                        + ":" + currentStream.getUniqueKey());
            }
        });
    }

    /**
     * Requests a stream via the model and tracks it for proper reciept matching.
     *
     * @param request
     *            The request in JSON form.
     */
    private void fetchStream(final JSONObject request) {
        currentStreamRequest = request.toString();
        StreamModel.getInstance().fetch(currentStreamRequest, false);
    }

    /**
     * Updates the URL to include the search term.
     */
    private void updateUrlWithSearchTerm() {
        String searchText = searchBox.getText();
        String searchParam = Session.getInstance().getParameterValue("search");
        if (!searchText.equals(searchParam)) {
            EventBus.getInstance()
                    .notifyObservers(new UpdateHistoryEvent(new CreateUrlRequest("search", searchText, false)));
        }
    }

    /**
     * Update subscription status consistently.
     *
     * @param inIsSubscribed
     *            New status.
     */
    private void setSubscribeStatus(final boolean inIsSubscribed) {
        isSubscribed = inIsSubscribed;
        subscribeViaEmail.setText(isSubscribed ? "Unsubscribe to Emails" : "Subscribe via Email");
    }

    /**
     * Creates the JSON representation of a string value. (Escapes characters and adds string delimiters or returns null
     * keyword as applicable.) See http://www.json.org/ for syntax. Assumes the string contains no control characters.
     *
     * @param input
     *            Input string, possibly null.
     * @return JSON string representation.
     */
    private static native String makeJsonString(final String input)
    /*-{
     return input == null ? 'null' : '"' + input.replace(/\\/g,'\\\\').replace(/"/g,'\\"') + '"';
     }-*/;

    /**
     * Append a new message.
     *
     * @param message
     *            the messa.ge
     */
    private void appendActivity(final ActivityDTO message) {
        Panel newActivity = renderer.render(message);
        newActivity.setVisible(false);
        streamPanel.insert(newActivity, 0);
        EffectsFacade.nativeFadeIn(newActivity.getElement(), true);
    }

    /**
     * Load a stream.
     *
     * @param views
     *            the stream history link.
     * @param searchTerm
     *            the search term.
     */
    private void loadStream(final List<String> views, final String searchTerm) {
        // save for change detection
        loadedViews = new ArrayList<String>(views);
        loadedSearchTerm = searchTerm;

        Window.scrollTo(0, 0);
        noResults.addClassName(StaticResourceBundle.INSTANCE.coreCss().displayNone());
        Session.getInstance().getActionProcessor().setQueueRequests(true);

        addBookmark.setVisible(false);
        subscribeViaEmail.setVisible(false);
        feedLink.setVisible(false);

        streamOptionsPanel.getStyle().setDisplay(Display.BLOCK);
        streamDetailsComposite.init();

        errorPanel.clear();
        errorPanel.setVisible(false);

        AvatarBadgeManager.getInstance().clearBadges();

        singleActivityMode = false;
        activitySpinner.removeClassName(StaticResourceBundle.INSTANCE.coreCss().displayNone());
        moreLink.setVisible(false);
        streamPanel.addStyleName(StaticResourceBundle.INSTANCE.coreCss().hidden());
        currentRequestObj = StreamJsonRequestFactory.getEmptyRequest();
        currentStream = new StreamScope(ScopeType.PERSON, Session.getInstance().getCurrentPerson().getAccountId());
        ShowRecipient showRecipient = ShowRecipient.YES;

        streamContainerPanel.removeStyleName(StaticResourceBundle.INSTANCE.coreCss().hasOwnerRights());

        stickyActivityHolder.clear();
        UIObject.setVisible(stickyActivityArea, false);

        renderer.setShowStickActivity(false);

        currentStreamEntity = null;
        deferLoadAwaitingQueryBuilt = false;
        deferLoadAwaitingEntityReceived = false;

        //getEmailContactLink.setVisible(false);

        boolean streamIsAnEntity = false;

        if (views == null || views.size() == 0 || views.get(0).equals("following")
                || ((views.get(0).equals("sort") && (views.size() == 2)))) {
            currentRequestObj = StreamJsonRequestFactory.setSourceAsFollowing(currentRequestObj);
            setAsActiveStream(followingFilterPanel);
            EventBus.getInstance().notifyObservers(new PostableStreamScopeChangeEvent(currentStream));
            feedLink.setVisible(true);
            streamDetailsComposite.setStreamTitle("Following", CustomAvatar.FOLLOWING);
            streamDetailsComposite.setCondensedMode(true);
            currentDisplayName = "Following";
        } else if (views.get(0).equals("person") && views.size() >= 2) {
            streamIsAnEntity = true;
            showRecipient = ShowRecipient.RESOURCE_ONLY;
            String accountId = views.get(1);
            currentRequestObj = StreamJsonRequestFactory.addRecipient(EntityType.PERSON, accountId,
                    currentRequestObj);
            PersonalInformationModel.getInstance().fetch(accountId, false);
            currentStream.setScopeType(ScopeType.PERSON);
            currentStream.setUniqueKey(accountId);
            String mapKey = "person/" + accountId;
            setAsActiveStream(bookmarksWidgetMap.get(mapKey));
            if (!bookmarksWidgetMap.containsKey(mapKey)) {
                addBookmark.setVisible(true);
            } else {
                addBookmark.setVisible(false);
                currentDisplayName = bookmarksWidgetMap.get(mapKey).getStreamName();
            }
            subscribeViaEmail.setVisible(true);
            feedLink.setVisible(true);
            streamDetailsComposite.setCondensedMode(false);

            PersonActivitySubscriptionModel.getInstance().fetch(currentStream.getUniqueKey(), true);
        } else if (views.get(0).equals("group") && views.size() >= 2) {
            streamIsAnEntity = true;
            showRecipient = ShowRecipient.RESOURCE_ONLY;
            String shortName = views.get(1);
            currentRequestObj = StreamJsonRequestFactory.addRecipient(EntityType.GROUP, shortName,
                    currentRequestObj);
            GroupModel.getInstance().fetch(shortName, false);
            currentStream.setScopeType(ScopeType.GROUP);
            currentStream.setUniqueKey(shortName);
            String mapKey = "group/" + shortName;
            setAsActiveStream(bookmarksWidgetMap.get(mapKey));
            if (!bookmarksWidgetMap.containsKey(mapKey)) {
                addBookmark.setVisible(true);
            } else {
                addBookmark.setVisible(false);
                currentDisplayName = bookmarksWidgetMap.get(mapKey).getStreamName();
            }
            subscribeViaEmail.setVisible(true);
            feedLink.setVisible(true);
            streamDetailsComposite.setCondensedMode(false);

            // Note: the links this will generate will only be visible if user is an admin/coordinator (via CSS)
            renderer.setShowStickActivity(true);

            deferLoadAwaitingEntityReceived = true;
            deferLoadAwaitingQueryBuilt = true;

            GroupActivitySubscriptionModel.getInstance().fetch(currentStream.getUniqueKey(), true);
        } else if (views.get(0).equals("custom") && views.size() >= 3) {
            currentRequestObj = StreamJsonRequestFactory.getJSONRequest(URL.decodeComponent(views.get(2)));
            setAsActiveStream(customStreamWidgetMap.get(Long.parseLong(views.get(1))));
            currentStream.setScopeType(null);
            EventBus.getInstance().notifyObservers(new PostableStreamScopeChangeEvent(currentStream));
            feedLink.setVisible(true);
            String streamName = customStreamWidgetMap.get(Long.parseLong(views.get(1))).getStreamName();
            streamDetailsComposite.setStreamTitle(streamName, CustomAvatar.FOLLOWING);
            streamDetailsComposite.setCondensedMode(true);
            currentDisplayName = streamName;
        } else if (views.get(0).equals("everyone")) {
            currentRequestObj = StreamJsonRequestFactory.getEmptyRequest();
            setAsActiveStream(everyoneFilterPanel);
            EventBus.getInstance().notifyObservers(new PostableStreamScopeChangeEvent(currentStream));
            feedLink.setVisible(true);
            streamDetailsComposite.setStreamTitle("Everyone", CustomAvatar.EVERYONE);
            streamDetailsComposite.setCondensedMode(true);
            currentDisplayName = "Everyone";
        } else if (views.size() == 1) {
            showRecipient = ShowRecipient.RESOURCE_ONLY;
            singleActivityMode = true;
        }

        if (searchTerm != null && searchTerm.length() > 0) {
            streamSearchStatusWidget.setSearchTerm(searchTerm);
            currentRequestObj = StreamJsonRequestFactory.setSearchTerm(searchTerm, currentRequestObj);
            searchContainer.addClassName(style.activeSearch());
            searchBox.setText(searchTerm);
        } else {
            streamSearchStatusWidget.onSearchCanceled();
            searchContainer.removeClassName(style.activeSearch());
        }

        if (!streamIsAnEntity) {
            streamDetailsComposite.setVisible(true);
        }
        renderer.setShowRecipientInStream(showRecipient);

        if (!singleActivityMode) {
            streamOptionsPanel.getStyle().setDisplay(Display.BLOCK);

            String sortBy = "recent";

            if (views != null && views.size() >= 2 && "sort".equals(views.get(views.size() - 2))) {
                sortBy = views.get(views.size() - 1);
            }

            recentSort.removeStyleName(style.activeSort());
            popularSort.removeStyleName(style.activeSort());
            activeSort.removeStyleName(style.activeSort());

            sortKeyword = "date";

            if ("recent".equals(sortBy)) {
                recentSort.addStyleName(style.activeSort());
                sortKeyword = "date";
            } else if ("popular".equals(sortBy)) {
                popularSort.addStyleName(style.activeSort());
                sortKeyword = "interesting";
            } else if ("active".equals(sortBy)) {
                activeSort.addStyleName(style.activeSort());
                sortKeyword = "commentdate";
            }

            currentRequestObj = StreamJsonRequestFactory.setSort(sortKeyword, currentRequestObj);

            // see notes where field declared
            deferLoadAwaitingQueryBuilt = false;
            if (!deferLoadAwaitingEntityReceived) {
                fetchStream(currentRequestObj);
            }
        } else {
            streamDetailsComposite.setCondensedMode(false);
            streamOptionsPanel.getStyle().setDisplay(Display.NONE);
            postBox.setVisible(false);

            try {
                ActivityModel.getInstance().fetch(Long.parseLong(views.get(0)), true);
            } catch (Exception e) {
                // Do nothing.
                int x = 0;
            }
        }

        Session.getInstance().getActionProcessor().fireQueuedRequests();
        Session.getInstance().getActionProcessor().setQueueRequests(false);
    }

    /**
     * Set a stream as active.
     *
     * @param panel
     *            the panel.
     */
    private void setAsActiveStream(final Panel panel) {
        Scheduler.get().scheduleDeferred(new ScheduledCommand() {
            public void execute() {
                if (currentlyActiveStream != null) {
                    currentlyActiveStream.removeStyleName(style.activeStream());
                }

                if (panel != null) {
                    currentlyActiveStream = panel;
                    panel.addStyleName(style.activeStream());
                }
            }
        });

    }

    /**
     * Check if we should init.
     */
    private void checkInit() {
        if (bookmarksLoaded && customStreamsLoaded && !hasInited) {
            hasInited = true;
            handleViewsChanged(Session.getInstance().getUrlViews());
        }
    }

    /**
     * Create LI Element for stream lists.
     *
     * @param name
     *            the name of the item.
     * @param view
     *            the history token to load.
     * @param modifyClickHandler
     *            click handler for modify.
     * @param modifyClass
     *            the class for the modify button.
     * @param modifyText
     *            the text for the modify button.
     * @param imgUrl
     *            the img url.
     * @param isAvatar
     *            if the image is an avatar.
     * @return the LI.
     */
    private StreamNamePanel createPanel(final String name, final String view, final String imgUrl,
            final ClickHandler modifyClickHandler, final String modifyClass, final String modifyText,
            final boolean isAvatar) {
        StreamNamePanel panel = new StreamNamePanel(name);
        panel.addStyleName(style.streamOptionChild());
        panel.addClickHandler(new ClickHandler() {
            public void onClick(final ClickEvent event) {
                History.newItem(Session.getInstance().generateUrl(new CreateUrlRequest(Page.ACTIVITY, view)));
            }
        });

        FlowPanel innerPanel = new FlowPanel();

        Image streamImage = new Image(imgUrl);
        if (isAvatar) {
            streamImage.addStyleName(style.smallAvatar());
        }

        innerPanel.add(streamImage);

        Label streamName = new Label(name);
        streamName.setTitle(name);
        streamName.addStyleName(style.streamName());
        streamName.addStyleName(StaticResourceBundle.INSTANCE.coreCss().ellipsis());
        innerPanel.add(streamName);

        if (modifyClickHandler != null) {
            Label modifyLink = new Label(modifyText);
            modifyLink.addStyleName(modifyClass);
            modifyLink.addClickHandler(modifyClickHandler);
            innerPanel.add(modifyLink);
        }

        panel.add(innerPanel);

        return panel;
    }

    /**
     * Binder for building UI.
     */
    interface LocalUiBinder extends UiBinder<Widget, ActivityContent> {
    }
}