001    package net.databinder.components.hib;
002    
003    import net.databinder.components.AjaxOnKeyPausedUpdater;
004    import net.databinder.components.AjaxCell;
005    import net.databinder.models.hib.PropertyQueryBinder;
006    import net.databinder.models.hib.QueryBinder;
007    
008    import org.apache.wicket.ResourceReference;
009    import org.apache.wicket.ajax.AjaxRequestTarget;
010    import org.apache.wicket.ajax.markup.html.AjaxLink;
011    import org.apache.wicket.markup.html.form.Form;
012    import org.apache.wicket.markup.html.form.TextField;
013    import org.apache.wicket.markup.html.image.Image;
014    import org.apache.wicket.markup.html.panel.Panel;
015    import org.apache.wicket.model.CompoundPropertyModel;
016    
017    /**
018     * Panel for a "live" search field with a clear button. Instances of this class must
019     * implement the onUpdate method to register external components for updating.
020     * It is possible to override the search button text with the key "searchbutton.text" 
021     * @author Nathan Hamblen
022     */
023    public abstract class SearchPanel extends Panel {
024            
025            private String searchInput;
026            
027            /**
028             * @param id Wicket id
029             */
030            public SearchPanel(String id) {
031                    super(id);
032                    add(new SearchForm("searchForm"));
033            }
034    
035            
036            /**
037             * Override to add components to be updated (or JavaScript to be executed)
038             * when the search string changes. Remember that added components must
039             * have a markup id; use component.setMarkupId(true) to assign one 
040             * programmatically.
041             * @param target Ajax target to register components for update
042             */
043            public abstract void onUpdate(AjaxRequestTarget target);
044            
045            /**
046             * Binds the search model to a "search" parameter in a query. The value in the 
047             * search field will be bracketed by percent signs (%) for a find-anywhere match.
048             * In the query itself, "search" must be the name of the  one and only parameter 
049             * If your needs differ, bind the model passed in to the SearchPanel constructor 
050             * to your own IQueryBinder instance; this is a convenience method. 
051             * @return binder for a "search" parameter
052             */
053            public QueryBinder getQueryBinder() {
054                    return new PropertyQueryBinder(this);
055            }
056            
057            public String getSearch() {
058                    return searchInput == null ? searchInput : "%" + searchInput + "%";
059            }
060            
061            /** Form with AJAX components and their AjaxCells. */
062            public class SearchForm extends Form {
063                    public SearchForm(String id) {
064                            super(id, new CompoundPropertyModel(SearchPanel.this));
065    
066                            final AjaxCell searchWrap = new AjaxCell("searchWrap");
067                            add(searchWrap);
068                            final TextField search = new TextField("searchInput");
069                            search.setOutputMarkupId(true);
070                            searchWrap.add(search);
071    
072                            final AjaxCell clearWrap = new AjaxCell("clearWrap");
073                            add(clearWrap);
074                            final AjaxLink clearLink = new AjaxLink("clearLink") {
075                                    /** Clear field and register updates. */
076                                    public void onClick(AjaxRequestTarget target) {
077                                            search.setModelObject(null);
078                                            target.addComponent(searchWrap);
079                                            target.addComponent(clearWrap);
080                                            SearchPanel.this.onUpdate(target);
081                                    }
082                                    /** Hide when search is blank. */
083                                    public boolean isVisible() {
084                                            return search.getModelObject() != null;
085                                    }
086                            };
087                            clearLink.setOutputMarkupId(true);
088                            clearLink.add( new Image("clear", 
089                                            new ResourceReference(this.getClass(), "clear.png")));
090                            clearWrap.add(clearLink);
091    
092                            // triggered when user pauses or tabs out
093                            search.add(new AjaxOnKeyPausedUpdater() {
094                                    protected void onUpdate(AjaxRequestTarget target) {
095                                            target.addComponent(clearWrap);
096                                            SearchPanel.this.onUpdate(target);
097                                    }
098                            });
099                    }
100            }
101    }