/*
* Copyright (c) 2009-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.common.widgets.activity;
import java.util.HashMap;
import org.eurekastreams.commons.search.modelview.ModelView;
import org.eurekastreams.server.action.request.directory.GetDirectorySearchResultsRequest;
import org.eurekastreams.server.domain.EntityType;
import org.eurekastreams.server.domain.Page;
import org.eurekastreams.server.search.modelview.DomainGroupModelView;
import org.eurekastreams.server.search.modelview.PersonModelView;
import org.eurekastreams.web.client.events.EventBus;
import org.eurekastreams.web.client.events.Observer;
import org.eurekastreams.web.client.events.SwitchedHistoryViewEvent;
import org.eurekastreams.web.client.events.UpdateHistoryEvent;
import org.eurekastreams.web.client.events.data.GotSearchResultsResponseEvent;
import org.eurekastreams.web.client.history.CreateUrlRequest;
import org.eurekastreams.web.client.model.SearchResultsModel;
import org.eurekastreams.web.client.model.StreamBookmarksModel;
import org.eurekastreams.web.client.ui.Session;
import org.eurekastreams.web.client.ui.common.LabeledTextBox;
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.pages.master.CoreCss;
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.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.event.dom.client.MouseOverEvent;
import com.google.gwt.event.dom.client.MouseOverHandler;
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.uibinder.client.UiHandler;
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.FocusPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.Widget;
/**
* Global search composite. TODO break this out for testability.
*/
public class BookmarkSearchComposite extends Composite
{
/** Binder for building UI. */
private static LocalUiBinder binder = GWT.create(LocalUiBinder.class);
/** Local styles. */
@UiField
LocalStyle style;
/** Global styles. */
@UiField(provided = true)
CoreCss coreCss;
/** Title shown when collapsed. */
@UiField
Label collapsedTitle;
/** Title shown when expanded. */
@UiField
Label expandedTitle;
/** Panel shown when expanded. */
@UiField
DivElement expandedPanel;
/** The search term box. */
@UiField
LabeledTextBox searchTerm;
/** Results panel. */
@UiField
FlowPanel resultsPanel;
/** Results panel container. */
@UiField
FocusPanel resultsPanelContainer;
/** Last length of search term. */
private int termLength = -1;
/** Currently active item for keyboard selection. */
private Panel activeItem = null;
/**
* Constructor.
*/
public BookmarkSearchComposite()
{
coreCss = StaticResourceBundle.INSTANCE.coreCss();
Widget main = binder.createAndBindUi(this);
// resultsPanelContainer.setVisible(false);
initWidget(main);
// addStyleName(StaticResourceBundle.INSTANCE.coreCss().bookmarkSearch());
setupEvents();
}
/**
* Sets up event handling.
*/
private void setupEvents()
{
final EventBus eventBus = Session.getInstance().getEventBus();
searchTerm.addKeyUpHandler(new KeyUpHandler()
{
public void onKeyUp(final KeyUpEvent ev)
{
// ENTER key
if (ev.getNativeKeyCode() == KeyCodes.KEY_ENTER && !ev.isAnyModifierKeyDown())
{
// navigating the list of search results - pick it
if (activeItem != null)
{
activeItem.getElement().dispatchEvent(
Document.get().createClickEvent(1, 0, 0, 0, 0, false, false, false, false));
}
// search term in box - go to search page (should this be here?)
else if (!searchTerm.getText().isEmpty())
{
eventBus.notifyObservers(new UpdateHistoryEvent(new CreateUrlRequest(Page.SEARCH,
generateParams(searchTerm.getText()), false)));
}
}
else if (ev.isDownArrow() && activeItem != null)
{
int activeIndex = resultsPanel.getWidgetIndex(activeItem);
if (activeIndex + 1 < resultsPanel.getWidgetCount())
{
selectItem((Panel) resultsPanel.getWidget(activeIndex + 1));
}
}
else if (ev.isUpArrow() && activeItem != null)
{
int activeIndex = resultsPanel.getWidgetIndex(activeItem);
if (activeIndex >= 1)
{
selectItem((Panel) resultsPanel.getWidget(activeIndex - 1));
}
}
else if (termLength != searchTerm.getText().length())
{
termLength = searchTerm.getText().length();
if (termLength == 0)
{
resultsPanelContainer.setVisible(false);
resultsPanel.clear();
}
else
{
GetDirectorySearchResultsRequest request = new GetDirectorySearchResultsRequest(searchTerm
.getText(), "", 0, 4, "bookmark");
SearchResultsModel.getInstance().fetch(request, true);
}
}
}
});
eventBus.addObserver(GotSearchResultsResponseEvent.class, new Observer<GotSearchResultsResponseEvent>()
{
public void update(final GotSearchResultsResponseEvent event)
{
if ("bookmark".equals(event.getCallerKey()))
{
activeItem = null;
resultsPanel.clear();
resultsPanelContainer.setVisible(event.getResponse().getPagedSet().size() > 0);
for (ModelView result : event.getResponse().getPagedSet())
{
final FocusPanel itemContainer = new FocusPanel();
final FlowPanel itemPanel = new FlowPanel();
final Anchor name = new Anchor();
name.addStyleName(StaticResourceBundle.INSTANCE.coreCss().bookmarkSearchName());
name.addStyleName(StaticResourceBundle.INSTANCE.coreCss().ellipsis());
if (result instanceof PersonModelView)
{
final PersonModelView person = (PersonModelView) result;
itemPanel.add(AvatarLinkPanel.create(person, Size.VerySmall, false));
name.setText(person.getDisplayName());
name.setTitle(person.getDisplayName());
itemContainer.addClickHandler(new ClickHandler()
{
public void onClick(final ClickEvent event)
{
StreamBookmarksModel.getInstance().insert(person.getStreamId());
}
});
}
else if (result instanceof DomainGroupModelView)
{
final DomainGroupModelView group = (DomainGroupModelView) result;
itemPanel.add(new AvatarLinkPanel(EntityType.GROUP, group.getShortName(), group
.getAvatarId(), Size.VerySmall, false));
name.setText(group.getName());
name.setTitle(group.getName());
itemContainer.addClickHandler(new ClickHandler()
{
public void onClick(final ClickEvent event)
{
StreamBookmarksModel.getInstance().insert(group.getStreamId());
}
});
}
itemContainer.addMouseOverHandler(new MouseOverHandler()
{
public void onMouseOver(final MouseOverEvent arg0)
{
selectItem(itemContainer);
}
});
name.addStyleName(StaticResourceBundle.INSTANCE.coreCss().bookmarkNameLink());
name.addStyleName(StaticResourceBundle.INSTANCE.coreCss().ellipsis());
itemPanel.add(name);
itemContainer.add(itemPanel);
resultsPanel.add(itemContainer);
if (activeItem == null)
{
selectItem(itemContainer);
}
}
}
}
});
eventBus.addObserver(SwitchedHistoryViewEvent.class, new Observer<SwitchedHistoryViewEvent>()
{
public void update(final SwitchedHistoryViewEvent event)
{
if (event.getPage() != Page.SEARCH)
{
searchTerm.reset();
}
}
});
}
/**
* Resets search when results clicked.
*
* @param ev
* Event.
*/
@UiHandler("resultsPanelContainer")
void onResultsPanelContainerClick(final ClickEvent ev)
{
clearSearchResults();
}
/**
* Clears UI associated with the search results.
*/
private void clearSearchResults()
{
searchTerm.reset();
resultsPanelContainer.setVisible(false);
resultsPanel.clear();
removeStyleName(style.searchActive());
activeItem = null;
}
/**
* Expands panel when clicked.
*
* @param ev
* Event.
*/
@UiHandler("collapsedTitle")
void onExpandClick(final ClickEvent ev)
{
collapsedTitle.setVisible(false);
UIObject.setVisible(expandedPanel, true);
Scheduler.get().scheduleDeferred(new ScheduledCommand()
{
public void execute()
{
searchTerm.setFocus(true);
}
});
}
/**
* Collapses panel when clicked.
*
* @param ev
* Event.
*/
@UiHandler("expandedTitle")
void onCollapseClick(final ClickEvent ev)
{
collapsedTitle.setVisible(true);
UIObject.setVisible(expandedPanel, false);
clearSearchResults();
}
/**
* Select an item.
*
* @param item
* the item.
*/
private void selectItem(final Panel item)
{
if (activeItem != null)
{
activeItem.removeStyleName(StaticResourceBundle.INSTANCE.coreCss().active());
}
item.addStyleName(StaticResourceBundle.INSTANCE.coreCss().active());
activeItem = item;
}
/**
* Creates a hashmap for the history parameters to pass to the search page.
*
* @param query
* the search string.
* @return the hashmap of all necessary initial search parameters.
*/
private HashMap<String, String> generateParams(final String query)
{
HashMap<String, String> params = new HashMap<String, String>();
params.put("query", query);
params.put("startIndex", "0");
params.put("endIndex", "9");
return params;
}
/**
* Local styles.
*/
interface LocalStyle extends CssResource
{
/** @return Active style for overall widget. */
@ClassName("search-active")
String searchActive();
}
/**
* Binder for building UI.
*/
interface LocalUiBinder extends UiBinder<Widget, BookmarkSearchComposite>
{
}
}