001package gwt.material.design.client.ui; 002 003/* 004 * #%L 005 * GwtMaterial 006 * %% 007 * Copyright (C) 2015 GwtMaterialDesign 008 * %% 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 * #L% 021 */ 022 023import com.google.gwt.event.dom.client.*; 024import com.google.gwt.event.logical.shared.CloseEvent; 025import com.google.gwt.event.logical.shared.CloseHandler; 026import com.google.gwt.event.logical.shared.HasCloseHandlers; 027import com.google.gwt.event.shared.HandlerRegistration; 028import com.google.gwt.user.client.ui.TextBox; 029import gwt.material.design.client.base.HasActive; 030import gwt.material.design.client.base.SearchObject; 031import gwt.material.design.client.constants.IconType; 032import gwt.material.design.client.constants.InputType; 033import gwt.material.design.client.events.SearchFinishEvent; 034import gwt.material.design.client.events.SearchNoResultEvent; 035import gwt.material.design.client.ui.html.Label; 036 037import java.util.ArrayList; 038import java.util.List; 039 040//@formatter:off 041 042/** 043 * Material Search is a value box component that returs a result based on your search 044 * 045 * <p> 046 * <h3>UiBinder Usage:</h3> 047 * <pre> 048 * {@code 049 * <m:MaterialSearch placeholder="Sample"/> 050 * } 051 * </pre> 052 * 053 * <h3>Populating the search result objects</h3> 054 * {@code 055 * 056 * List<SearchObject> objects = new ArrayList<>(); 057 * 058 * private void onInitSearch() { 059 * objects.add(new SearchObject(IconType.POLYMER, "Pushpin", "#!pushpin")); 060 * objects.add(new SearchObject(IconType.POLYMER, "SideNavs", "#!sidenavs")); 061 * objects.add(new SearchObject(IconType.POLYMER, "Scrollspy", "#!scrollspy")); 062 * objects.add(new SearchObject(IconType.POLYMER, "Tabs", "#!tabs")); 063 * txtSearch.setListSearches(objects); 064 * } 065 * 066 * } 067 * </p> 068 * 069 * @author kevzlou7979 070 * @author Ben Dol 071 * @see <a href="http://gwt-material-demo.herokuapp.com/#navigations">Material Search</a> 072 */ 073//@formatter:on 074public class MaterialSearch extends MaterialValueBox<String> implements HasCloseHandlers<String>, HasActive { 075 076 private Label label = new Label(); 077 private MaterialIcon iconSearch = new MaterialIcon(IconType.SEARCH); 078 private MaterialIcon iconClose = new MaterialIcon(IconType.CLOSE); 079 080 /** 081 * The list of search objects added to MaterialSearchResult panel to 082 * display the lists of result items 083 */ 084 private List<SearchObject> listSearches = new ArrayList<>(); 085 /** 086 * Used to determine the selected searches while matching the keyword to result 087 */ 088 private List<SearchObject> tempSearches = new ArrayList<>(); 089 /** 090 * Panel to display the result items 091 */ 092 private MaterialSearchResult searchResult; 093 /** 094 * Link selected to determine easily during the selection event (up / down key events) 095 */ 096 private MaterialLink selectedLink; 097 /** 098 * Gets the selected object after Search Finish event 099 */ 100 private SearchObject selectedObject; 101 /** 102 * -1 means that the selected index is not yet selected. 103 * It will increment or decrement once triggere by key up / down events 104 */ 105 private int curSel = -1; 106 107 public MaterialSearch() { 108 super(new TextBox()); 109 setType(InputType.SEARCH); 110 label.add(iconSearch); 111 label.getElement().setAttribute("for", "search"); 112 add(label); 113 add(iconClose); 114 iconClose.addClickHandler(new ClickHandler() { 115 @Override 116 public void onClick(ClickEvent event) { 117 CloseEvent.fire(MaterialSearch.this, getText()); 118 } 119 }); 120 } 121 122 @Override 123 public void onLoad() { 124 super.onLoad(); 125 126 // populate the lists of search result on search panel 127 searchResult = new MaterialSearchResult(); 128 add(searchResult); 129 // add keyup event to filter the searches 130 addKeyUpHandler(new KeyUpHandler() { 131 @Override 132 public void onKeyUp(KeyUpEvent event) { 133 String keyword = getText().toLowerCase(); 134 // Clear the panel and temp objects 135 searchResult.clear(); 136 tempSearches.clear(); 137 138 // Populate the search result items 139 for(final SearchObject obj : getListSearches()) { 140 MaterialLink link = new MaterialLink(); 141 link.setIconColor("grey"); 142 link.setTextColor("black"); 143 // Generate an icon 144 if(obj.getIcon()!=null) { 145 link.setIconType(obj.getIcon()); 146 } 147 148 // Generate an image 149 MaterialImage image = new MaterialImage(); 150 if(obj.getResource() != null) { 151 image.setResource(obj.getResource()); 152 link.insert(image, 0); 153 } 154 155 if(obj.getImageUrl() != null) { 156 image.setUrl(obj.getImageUrl()); 157 link.insert(image, 0); 158 } 159 160 if(!obj.getLink().isEmpty()) { 161 link.setHref(obj.getLink()); 162 } 163 link.setText(obj.getKeyword()); 164 link.addClickHandler(new ClickHandler() { 165 @Override 166 public void onClick(ClickEvent event) { 167 setSelectedObject(obj); 168 reset(obj.getKeyword()); 169 } 170 }); 171 // If matches add to search result container and object to temp searches 172 if (obj.getKeyword().toLowerCase().contains(keyword)){ 173 searchResult.add(link); 174 tempSearches.add(obj); 175 } 176 } 177 178 // Apply selected search 179 if(event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER && !tempSearches.isEmpty()) { 180 if(getCurSel()==-1) { 181 setSelectedObject(tempSearches.get(0)); 182 setSelectedLink((MaterialLink) searchResult.getWidget(0)); 183 }else{ 184 setSelectedObject(tempSearches.get(curSel)); 185 } 186 187 MaterialLink selLink = getSelectedLink(); 188 if(!selLink.getHref().isEmpty()) { 189 locateSearch(selLink.getHref()); 190 } 191 reset(selLink.getText()); 192 } 193 194 // Fire an event if theres no search result 195 if(searchResult.getWidgetCount() == 0) { 196 SearchNoResultEvent.fire(MaterialSearch.this); 197 } 198 199 // Selection logic using key down event to navigate the search results 200 int totalItems = searchResult.getWidgetCount(); 201 if(event.getNativeEvent().getKeyCode() == KeyCodes.KEY_DOWN) { 202 if(curSel >= totalItems) { 203 setCurSel(getCurSel()); 204 applyHighlightedItem((MaterialLink) searchResult.getWidget(curSel - 1)); 205 }else{ 206 setCurSel(getCurSel() + 1); 207 applyHighlightedItem((MaterialLink) searchResult.getWidget(curSel)); 208 } 209 } 210 211 // Selection logic using key up event to navigate the search results 212 if(event.getNativeEvent().getKeyCode() == KeyCodes.KEY_UP) { 213 if(curSel <= -1) { 214 setCurSel(-1); 215 applyHighlightedItem((MaterialLink) searchResult.getWidget(curSel)); 216 }else { 217 setCurSel(getCurSel() - 1); 218 applyHighlightedItem((MaterialLink) searchResult.getWidget(curSel)); 219 } 220 } 221 } 222 223 // Resets the search result panel 224 private void reset(String keyword) { 225 SearchFinishEvent.fire(MaterialSearch.this); 226 curSel = -1; 227 setText(keyword); 228 searchResult.clear(); 229 } 230 }); 231 } 232 233 @Override 234 protected void onUnload() { 235 super.onUnload(); 236 237 clear(); 238 setCurSel(-1); 239 } 240 241 protected void applyHighlightedItem(MaterialLink link) { 242 link.addStyleName("higlighted"); 243 setSelectedLink(link); 244 } 245 246 protected native void locateSearch(String location)/*-{ 247 $wnd.window.location.hash = location; 248 }-*/; 249 250 @Override 251 public HandlerRegistration addCloseHandler(final CloseHandler<String> handler) { 252 return addHandler(new CloseHandler<String>() { 253 @Override 254 public void onClose(CloseEvent<String> event) { 255 if(isEnabled()){ 256 handler.onClose(event); 257 } 258 } 259 }, CloseEvent.getType()); 260 } 261 262 @Override 263 public void setActive(boolean active) { 264 if(active) { 265 this.setTextColor("black"); 266 iconClose.setIconColor("black"); 267 iconSearch.setIconColor("black"); 268 }else{ 269 iconClose.setIconColor("white"); 270 iconSearch.setIconColor("white"); 271 } 272 } 273 274 @Override 275 public boolean isActive() { 276 return false; 277 } 278 279 public MaterialLink getSelectedLink() { 280 return selectedLink; 281 } 282 283 public void setSelectedLink(MaterialLink selectedLink) { 284 this.selectedLink = selectedLink; 285 } 286 287 public List<SearchObject> getListSearches() { 288 return listSearches; 289 } 290 291 public void setListSearches(List<SearchObject> listSearches) { 292 this.listSearches = listSearches; 293 } 294 295 public int getCurSel() { 296 return curSel; 297 } 298 299 public void setCurSel(int curSel) { 300 this.curSel = curSel; 301 } 302 303 public SearchObject getSelectedObject() { 304 return selectedObject; 305 } 306 307 public void setSelectedObject(SearchObject selectedObject) { 308 this.selectedObject = selectedObject; 309 } 310 311 /** 312 * Gets the tempory search objects 313 * @return 314 */ 315 public List<SearchObject> getTempSearches() { 316 return tempSearches; 317 } 318 319 /** 320 * This handler will be triggered when search is finish 321 */ 322 public HandlerRegistration addSearchFinishHandler(final SearchFinishEvent.SearchFinishHandler handler) { 323 return addHandler(new SearchFinishEvent.SearchFinishHandler() { 324 @Override 325 public void onSearchFinish(SearchFinishEvent event) { 326 if(isEnabled()){ 327 handler.onSearchFinish(event); 328 } 329 } 330 }, SearchFinishEvent.TYPE); 331 } 332 333 /** 334 * This handler will be triggered when there's no search result 335 */ 336 public HandlerRegistration addSearchNoResultHandler(final SearchNoResultEvent.SearchNoResultHandler handler) { 337 return addHandler(new SearchNoResultEvent.SearchNoResultHandler() { 338 @Override 339 public void onSearchNoResult(SearchNoResultEvent event) { 340 if(isEnabled()){ 341 handler.onSearchNoResult(event); 342 } 343 } 344 }, SearchNoResultEvent.TYPE); 345 } 346} 347 348