/* * 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.pages.search; import java.util.ArrayList; 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.ui.Session; import org.eurekastreams.web.client.ui.TimerFactory; import org.eurekastreams.web.client.ui.TimerHandler; 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.StaticResourceBundle; import com.google.gwt.dom.client.Document; 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.event.dom.client.MouseOverEvent; import com.google.gwt.event.dom.client.MouseOverHandler; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.FocusPanel; import com.google.gwt.user.client.ui.Hyperlink; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.Panel; /** * Global search composite. TODO break this out for testability. */ public class GlobalSearchComposite extends FlowPanel { /** * The search term. */ private final LabeledTextBox searchTerm; /** * Results panel. */ private final FlowPanel resultsPanel = new FlowPanel(); /** * Results panel container. */ private final FocusPanel resultsPanelContainer = new FocusPanel(); /** * Last length of search term. */ private int termLength = -1; /** * Currently active item. */ private Panel activeItem = null; /** * Timer factory. */ private final TimerFactory timerFactory = new TimerFactory(); /** * Hide delay after blur on post box. */ private static final Integer BLUR_DELAY = 250; /** * Constructor. * * @param label * the label for the uninitialized textbox. */ public GlobalSearchComposite(final String label) { searchTerm = new LabeledTextBox(label); resultsPanelContainer.setVisible(false); addStyleName(StaticResourceBundle.INSTANCE.coreCss().searchList()); add(searchTerm); resultsPanelContainer.addStyleName(StaticResourceBundle.INSTANCE.coreCss().searchResultsAutocompleteResults()); resultsPanelContainer.addStyleName(StaticResourceBundle.INSTANCE.coreCss() .globalSearchResultsAutocompleteResults()); add(resultsPanelContainer); resultsPanelContainer.add(resultsPanel); final EventBus eventBus = Session.getInstance().getEventBus(); final GlobalSearchComposite thisClass = this; searchTerm.addKeyUpHandler(new KeyUpHandler() { public void onKeyUp(final KeyUpEvent ev) { if (ev.getNativeKeyCode() == KeyCodes.KEY_ENTER && !ev.isAnyModifierKeyDown() && searchTerm.getText().length() > 0 && activeItem != null) { activeItem.getElement().dispatchEvent( Document.get().createClickEvent(1, 0, 0, 0, 0, false, false, false, false)); clearSearch(); } else if (ev.getNativeKeyCode() == KeyCodes.KEY_DOWN && activeItem != null) { int activeIndex = resultsPanel.getWidgetIndex(activeItem); if (activeIndex + 1 < resultsPanel.getWidgetCount()) { selectItem((Panel) resultsPanel.getWidget(activeIndex + 1)); } } else if (ev.getNativeKeyCode() == KeyCodes.KEY_UP && activeItem != null) { int activeIndex = resultsPanel.getWidgetIndex(activeItem); if (activeIndex - 1 >= 0) { selectItem((Panel) resultsPanel.getWidget(activeIndex - 1)); } } else if (termLength != searchTerm.getText().length()) { String searchTermText = searchTerm.getText().trim(); termLength = searchTermText.length(); if (termLength == 0) { resultsPanelContainer.setVisible(false); resultsPanel.clear(); thisClass.removeStyleName(StaticResourceBundle.INSTANCE.coreCss().globalSearchBoxActive()); } else { GetDirectorySearchResultsRequest request = new GetDirectorySearchResultsRequest( searchTermText, "", 0, 4, "global"); SearchResultsModel.getInstance().fetch(request, true); thisClass.addStyleName(StaticResourceBundle.INSTANCE.coreCss().globalSearchBoxActive()); } } } }); searchTerm.addBlurHandler(new BlurHandler() { public void onBlur(final BlurEvent arg0) { timerFactory.runTimer(BLUR_DELAY, new TimerHandler() { public void run() { clearSearch(); } }); } }); eventBus.addObserver(GotSearchResultsResponseEvent.class, new Observer<GotSearchResultsResponseEvent>() { public void update(final GotSearchResultsResponseEvent event) { if ("global".equals(event.getCallerKey())) { resultsPanel.clear(); activeItem = null; resultsPanelContainer.setVisible(event.getResponse().getPagedSet().size() > 0); String historyToken = ""; for (ModelView result : event.getResponse().getPagedSet()) { final FocusPanel itemContainer = new FocusPanel(); final FlowPanel itemPanel = new FlowPanel(); final Hyperlink name = new Hyperlink(); name.addStyleName(StaticResourceBundle.INSTANCE.coreCss().globalSearchItemName()); name.addStyleName(StaticResourceBundle.INSTANCE.coreCss().ellipsisChild()); if (result instanceof PersonModelView) { final PersonModelView person = (PersonModelView) result; itemPanel.add(AvatarLinkPanel.create(person, Size.Small, false)); name.setText(person.getDisplayName()); name.setTitle(person.getDisplayName()); historyToken = Session.getInstance().generateUrl( new CreateUrlRequest(Page.PEOPLE, person.getAccountId())); itemContainer.addClickHandler(new ClickHandler() { public void onClick(final ClickEvent event) { ArrayList<String> views = new ArrayList<String>(); views.add(person.getAccountId()); eventBus.notifyObservers(new UpdateHistoryEvent(new CreateUrlRequest(Page.PEOPLE, views))); } }); } else if (result instanceof DomainGroupModelView) { final DomainGroupModelView group = (DomainGroupModelView) result; itemPanel.add(new AvatarLinkPanel(EntityType.GROUP, group.getShortName(), group .getAvatarId(), Size.Small, false)); name.setText(group.getName()); name.setTitle(group.getName()); historyToken = Session.getInstance().generateUrl( new CreateUrlRequest(Page.GROUPS, group.getShortName())); itemContainer.addClickHandler(new ClickHandler() { public void onClick(final ClickEvent event) { ArrayList<String> views = new ArrayList<String>(); views.add(group.getShortName()); eventBus.notifyObservers(new UpdateHistoryEvent(new CreateUrlRequest(Page.GROUPS, views))); } }); } itemContainer.addMouseOverHandler(new MouseOverHandler() { public void onMouseOver(final MouseOverEvent arg0) { selectItem(itemContainer); } }); name.setTargetHistoryToken(historyToken); itemPanel.add(name); itemContainer.add(itemPanel); resultsPanel.add(itemContainer); if (activeItem == null) { selectItem(itemContainer); } } if (event.getResponse().getTotal() > event.getResponse().getPagedSet().size()) { final FocusPanel itemContainer = new FocusPanel(); final FlowPanel itemPanel = new FlowPanel(); itemContainer.addStyleName(StaticResourceBundle.INSTANCE.coreCss().searchResultsMoreButton()); itemPanel.add(new Label("See more results")); itemContainer.addClickHandler(new ClickHandler() { public void onClick(final ClickEvent event) { eventBus.notifyObservers(new UpdateHistoryEvent(new CreateUrlRequest(Page.SEARCH, generateParams(searchTerm.getText()), false))); } }); itemContainer.addMouseOverHandler(new MouseOverHandler() { public void onMouseOver(final MouseOverEvent arg0) { selectItem(itemContainer); } }); itemContainer.add(itemPanel); resultsPanel.add(itemContainer); } } } }); eventBus.addObserver(SwitchedHistoryViewEvent.class, new Observer<SwitchedHistoryViewEvent>() { public void update(final SwitchedHistoryViewEvent event) { if (event.getPage() != Page.SEARCH) { searchTerm.reset(); } } }); } /** * Clear the search. */ protected void clearSearch() { termLength = -1; searchTerm.reset(); resultsPanelContainer.setVisible(false); resultsPanel.clear(); removeStyleName(StaticResourceBundle.INSTANCE.coreCss().globalSearchBoxActive()); } /** * 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; } }