// Copyright 2012 Google Inc. All Rights Reserved. // // 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 com.google.collide.client.search; import com.google.collide.client.code.FileSelectedPlace; import com.google.collide.client.code.EditableContentArea.Content; import com.google.collide.client.history.Place; import com.google.collide.client.util.Elements; import com.google.collide.client.util.PathUtil; import com.google.collide.dto.SearchResponse; import com.google.collide.dto.SearchResult; import com.google.collide.dto.Snippet; import com.google.collide.json.client.JsoArray; import com.google.collide.mvp.CompositeView; import com.google.collide.mvp.UiComponent; import com.google.gwt.resources.client.ClientBundle; import com.google.gwt.resources.client.CssResource; import com.google.gwt.resources.client.ImageResource; import elemental.events.Event; import elemental.events.EventListener; import elemental.html.AnchorElement; import elemental.html.DivElement; import elemental.html.Element; import elemental.html.SpanElement; /** * Container for search results, at least for those searches that have a results * page. (Local search and search-and-replace do not...) * */ public class SearchContainer extends UiComponent<SearchContainer.View> implements Content { public interface Css extends CssResource { String container(); String next(); String otherpage(); String pager(); String previous(); String second(); String snippet(); String thispage(); String title(); } public interface Resources extends ClientBundle { @Source("next_page.png") ImageResource nextPage(); @Source("previous_page.png") ImageResource prevPage(); @Source("SearchContainer.css") Css searchContainerCss(); } public static class View extends CompositeView<Void> { Css css; DivElement pager; DivElement results; public View(Css css) { super(Elements.createDivElement(css.container())); this.css = css; createDom(); } private void createDom() { Element top = getElement(); results = Elements.createDivElement(); top.appendChild(results); pager = Elements.createDivElement(css.pager()); top.appendChild(pager); } public void clear() { results.removeFromParent(); pager.removeFromParent(); createDom(); } } private final String query; private final Place currentPlace; public SearchContainer(Place currentPlace, View view, final String query) { super(view); this.currentPlace = currentPlace; this.query = query; } /** * Updates with new results. * * @param message the message containing the new results. */ public void showResults(SearchResponse message) { getView().clear(); showResultsImpl( message.getPage(), message.getPageCount(), (JsoArray<SearchResult>) message.getResults()); } @Override public Element getContentElement() { return getView().getElement(); } @Override public void onContentDisplayed() { } // TODO: Clean up the code below. It does not properly follow the // View/Presenter contracts used by other UiComponents. It also makes many // static references to Places on dispatch. /** * Updates the view to displays results and appropriate pager widgetry. * * @param page the page of "this" result page, one-based * @param pageCount the total number of pages * @param items the {@link SearchResult} items on this page. */ private void showResultsImpl(final int page, int pageCount, JsoArray<SearchResult> items) { Css css = getView().css; buildPager(page, pageCount, css); for (int i = 0; i < items.size(); i++) { SearchResult item = items.get(i); DivElement outer = Elements.createDivElement(); if (i > 0) { outer.setClassName(css.second()); } final PathUtil path = new PathUtil(item.getTitle()); AnchorElement title = Elements.createAnchorElement(css.title()); title.setTextContent(item.getTitle()); if (item.getUrl() != null) { // this is unusual, but allows search results to point outside of this // workspace, e.g. to language API docs. title.setHref(item.getUrl()); } else { // this is the common case; the title will be a path in this workspace // and clicking on the link should take us to its editor. title.setOnClick(new EventListener() { @Override public void handleEvent(Event evt) { currentPlace.fireChildPlaceNavigation( FileSelectedPlace.PLACE.createNavigationEvent(path)); } }); } outer.appendChild(title); JsoArray<Snippet> snippets = (JsoArray<Snippet>) item.getSnippets(); for (int j = 0; j < snippets.size(); j++) { DivElement snippetDiv = Elements.createDivElement(css.snippet()); final int lineNo = snippets.get(j).getLineNumber(); snippetDiv.setTextContent(lineNo + ": " + snippets.get(j).getSnippetText()); snippetDiv.setOnClick(new EventListener() { @Override public void handleEvent(Event evt) { // lineNo is 1-based, whereas the editor expects 0-based int documentLineNo = lineNo - 1; currentPlace.fireChildPlaceNavigation( FileSelectedPlace.PLACE.createNavigationEvent(path, documentLineNo)); } }); outer.appendChild(snippetDiv); } getView().results.appendChild(outer); } } private void buildPager(final int page, final int pageCount, Css css) { if (pageCount > 1) { if (page > 1) { DivElement previous = Elements.createDivElement(css.previous()); getView().pager.appendChild(previous); previous.setOnClick(new EventListener() { @Override public void handleEvent(Event evt) { currentPlace.fireChildPlaceNavigation( SearchPlace.PLACE.createNavigationEvent(query, page - 1)); } }); } if (page > 7) { SpanElement elipsis = Elements.createSpanElement(css.thispage()); elipsis.setTextContent("..."); getView().pager.appendChild(elipsis); } // page numbers are one-based (i.e. human-oriented) for (int i = page > 6 ? page - 6 : 1; i < pageCount + 1 && i < page + 6; i++) { SpanElement counter = Elements.createSpanElement(i == page ? css.thispage() : css.otherpage()); counter.setTextContent(Integer.toString(i)); getView().pager.appendChild(counter); final int pageNumber = i; counter.setOnClick(new EventListener() { @Override public void handleEvent(Event evt) { currentPlace.fireChildPlaceNavigation( SearchPlace.PLACE.createNavigationEvent(query, pageNumber)); } }); if (page + 7 < pageCount + 1) { SpanElement elipsis = Elements.createSpanElement(css.thispage()); elipsis.setTextContent("..."); getView().pager.appendChild(elipsis); } } if (page < pageCount) { DivElement next = Elements.createDivElement(css.next()); getView().pager.appendChild(next); next.setOnClick(new EventListener() { @Override public void handleEvent(Event evt) { currentPlace.fireChildPlaceNavigation( SearchPlace.PLACE.createNavigationEvent(query, page + 1)); } }); } } } }