gwt.material.design.client.ui.MaterialSearch Maven / Gradle / Ivy
/*
* #%L
* GwtMaterial
* %%
* Copyright (C) 2015 - 2017 GwtMaterialDesign
* %%
* 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.
* #L%
*/
package gwt.material.design.client.ui;
import com.google.gwt.core.client.Scheduler;
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.logical.shared.*;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.ui.TextBox;
import gwt.material.design.client.base.*;
import gwt.material.design.client.constants.*;
import gwt.material.design.client.events.SearchFinishEvent;
import gwt.material.design.client.events.SearchNoResultEvent;
import gwt.material.design.client.ui.html.Label;
import java.util.ArrayList;
import java.util.List;
import static gwt.material.design.jquery.client.api.JQuery.$;
//@formatter:off
/**
* Material Search is a value box component that returns a result based on your search
*
*
*
UiBinder Usage:
*
* {@code
*
* }
*
*
*
Populating the search result objects
* {@code
*
* List objects = new ArrayList<>();
*
* private void onInitSearch() {
* objects.add(new SearchObject(IconType.POLYMER, "Pushpin", "#!pushpin"));
* objects.add(new SearchObject(IconType.POLYMER, "SideNavs", "#!sidenavs"));
* objects.add(new SearchObject(IconType.POLYMER, "Scrollspy", "#!scrollspy"));
* objects.add(new SearchObject(IconType.POLYMER, "Tabs", "#!tabs"));
* txtSearch.setListSearches(objects);
* }
*
* }
*
*
* @author kevzlou7979
* @author Ben Dol
* @see Material Search
* @see Material Design Specification
*/
//@formatter:on
public class MaterialSearch extends MaterialValueBox implements HasOpenHandlers, HasCloseHandlers,
HasActive, HasSearchHandlers, HasOpenClose {
private Label label = new Label();
private MaterialIcon iconSearch = new MaterialIcon(IconType.SEARCH);
private MaterialIcon iconClose = new MaterialIcon(IconType.CLOSE);
private SearchMatcher matcher = new DefaultSearchMatcher();
/**
* The list of search objects added to MaterialSearchResult panel to
* display the lists of result items
*/
private List listSearches = new ArrayList<>();
/**
* Used to determine the selected searches while matching the keyword to result
*/
private List tempSearches = new ArrayList<>();
/**
* Panel to display the result items
*/
private MaterialSearchResult searchResultPanel = new MaterialSearchResult();
/**
* Link selected to determine easily during the selection event (up / down key events)
*/
private MaterialLink selectedLink;
/**
* Gets the selected object after Search Finish event
*/
private SearchObject selectedObject;
/**
* -1 means that the selected index is not yet selected.
* It will increment or decrement once trigger by key up / down events
*/
private int curSel = -1;
private boolean active;
public MaterialSearch() {
super(new TextBox());
}
public MaterialSearch(String placeholder) {
this();
setPlaceholder(placeholder);
}
public MaterialSearch(String placeholder, Color backgroundColor, Color iconColor, boolean active, int shadow) {
this(placeholder);
setBackgroundColor(backgroundColor);
setIconColor(iconColor);
setActive(active);
setShadow(shadow);
}
@Override
protected void onLoad() {
super.onLoad();
setType(InputType.SEARCH);
setAutocomplete(Autocomplete.OFF);
label.add(iconSearch);
label.getElement().setAttribute("for", "search");
add(label);
add(iconClose);
registerHandler(iconClose.addClickHandler(clickEvent -> CloseEvent.fire(MaterialSearch.this, getText())));
add(searchResultPanel);
// Add Key Up event to filter the searches
registerHandler(addKeyUpHandler(new KeyUpHandler() {
@Override
public void onKeyUp(KeyUpEvent event) {
String keyword = getText().toLowerCase();
int keyCode = event.getNativeEvent().getKeyCode();
// Clear the panel and temp objects
searchResultPanel.clear();
tempSearches.clear();
// Populate the search result items
for (final SearchObject obj : getListSearches()) {
MaterialLink link = new MaterialLink();
link.setIconColor(Color.GREY);
link.setTextColor(Color.BLACK);
// Generate an icon
if (obj.getIcon() != null) {
link.setIconType(obj.getIcon());
}
// Generate an image
MaterialImage image = new MaterialImage();
if (obj.getResource() != null) {
image.setResource(obj.getResource());
link.insert(image, 0);
}
if (obj.getImageUrl() != null) {
image.setUrl(obj.getImageUrl());
link.insert(image, 0);
}
if (!obj.getLink().isEmpty()) {
link.setHref(obj.getLink());
}
link.setText(obj.getKeyword());
link.addClickHandler(event1 -> {
setSelectedObject(obj);
reset(obj.getKeyword());
});
// If matches add to search result container and object to temp searches
if (match(obj, keyword)) {
searchResultPanel.add(link);
tempSearches.add(obj);
}
searchResultPanel.setVisible(true);
}
// Apply selected search
if (keyCode == KeyCodes.KEY_ENTER && !tempSearches.isEmpty()) {
if (getCurSel() == -1) {
setSelectedObject(tempSearches.get(0));
setSelectedLink((MaterialLink) searchResultPanel.getWidget(0));
} else {
setSelectedObject(tempSearches.get(curSel));
}
MaterialLink selLink = getSelectedLink();
if (!selLink.getHref().isEmpty()) {
locateSearch(selLink.getHref());
}
reset(selLink.getText());
}
// Fire an event if there's no search result
if (searchResultPanel.getWidgetCount() == 0) {
SearchNoResultEvent.fire(MaterialSearch.this);
}
// Selection logic using key down event to navigate the search results
int totalItems = searchResultPanel.getWidgetCount();
if (totalItems > 0) {
if (keyCode == KeyCodes.KEY_DOWN) {
if (++curSel >= totalItems)
curSel = 0;
applyHighlightedItem((MaterialLink) searchResultPanel.getWidget(curSel));
}
// Selection logic using key up event to navigate the search results
if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_UP) {
if (--curSel < 0)
curSel = totalItems - 1;
applyHighlightedItem((MaterialLink) searchResultPanel.getWidget(curSel));
}
}
// Add Escape key for closing the result panel
if (keyCode == KeyCodes.KEY_ESCAPE) {
close();
}
}
// Resets the search result panel
private void reset(String keyword) {
SearchFinishEvent.fire(MaterialSearch.this);
curSel = -1;
setText(keyword);
$(valueBoxBase.getElement()).focus();
searchResultPanel.clear();
}
}));
}
protected boolean match(SearchObject obj, String keyword) {
return matcher.match(obj, keyword);
}
public SearchMatcher getMatcher() {
return matcher;
}
/**
* Will set a custom matcher to match the keyword.
* By Default {@lnk DefaultSearchMatcher}
*/
public void setMatcher(SearchMatcher matcher) {
this.matcher = matcher;
}
@Override
protected void onUnload() {
super.onUnload();
clear();
setCurSel(-1);
}
/**
* Programmatically open the search input field component
*/
@Override
public void open() {
setActive(true);
Scheduler.get().scheduleDeferred(() -> $(valueBoxBase.getElement()).focus());
OpenEvent.fire(MaterialSearch.this, getText());
}
@Override
public void close() {
close(true);
}
@Override
public boolean isOpen() {
return isActive();
}
public void close(boolean fireEvents) {
setActive(false);
if (fireEvents) {
Scheduler.get().scheduleDeferred(() -> $(valueBoxBase.getElement()).blur());
CloseEvent.fire(MaterialSearch.this, getText());
}
}
protected void applyHighlightedItem(MaterialLink link) {
link.addStyleName(CssName.HIGLIGHTED);
setSelectedLink(link);
}
//TODO: Convert to JSInterop
protected native void locateSearch(String location)/*-{
$wnd.window.location.hash = location;
}-*/;
@Override
public void setActive(boolean active) {
this.active = active;
if (active) {
setTextColor(Color.BLACK);
iconClose.setIconColor(Color.BLACK);
iconSearch.setIconColor(Color.BLACK);
} else {
iconClose.setIconColor(Color.WHITE);
iconSearch.setIconColor(Color.WHITE);
}
searchResultPanel.setVisible(active);
}
@Override
public boolean isActive() {
return active;
}
public MaterialLink getSelectedLink() {
return selectedLink;
}
public void setSelectedLink(MaterialLink selectedLink) {
this.selectedLink = selectedLink;
}
public List getListSearches() {
return listSearches;
}
public void setListSearches(List listSearches) {
this.listSearches = listSearches;
}
public int getCurSel() {
return curSel;
}
public void setCurSel(int curSel) {
this.curSel = curSel;
}
public SearchObject getSelectedObject() {
return selectedObject;
}
public void setSelectedObject(SearchObject selectedObject) {
this.selectedObject = selectedObject;
if (selectedObject != null) {
setText(selectedObject.getKeyword());
}
}
/**
* Gets the temporary search objects.
*/
public List getTempSearches() {
return tempSearches;
}
public MaterialIcon getIconClose() {
return iconClose;
}
public MaterialSearchResult getSearchResultPanel() {
return searchResultPanel;
}
@Override
public Label getLabelWidget() {
return label;
}
public MaterialIcon getIconSearch() {
return iconSearch;
}
@Override
public HandlerRegistration addCloseHandler(final CloseHandler handler) {
return addHandler((CloseHandler) handler::onClose, CloseEvent.getType());
}
@Override
public HandlerRegistration addOpenHandler(OpenHandler handler) {
return addHandler((OpenHandler) handler::onOpen, OpenEvent.getType());
}
/**
* This handler will be triggered when search is finish
*/
@Override
public HandlerRegistration addSearchFinishHandler(final SearchFinishEvent.SearchFinishHandler handler) {
return addHandler(handler, SearchFinishEvent.TYPE);
}
/**
* This handler will be triggered when there's no search result
*/
@Override
public HandlerRegistration addSearchNoResultHandler(final SearchNoResultEvent.SearchNoResultHandler handler) {
return addHandler(handler, SearchNoResultEvent.TYPE);
}
}