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