001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.extensions.markup.html.repeater.data.table.filter;
018
019import org.apache.wicket.Component;
020import org.apache.wicket.behavior.Behavior;
021import org.apache.wicket.markup.ComponentTag;
022import org.apache.wicket.markup.MarkupStream;
023import org.apache.wicket.markup.head.IHeaderResponse;
024import org.apache.wicket.markup.head.JavaScriptHeaderItem;
025import org.apache.wicket.markup.head.OnEventHeaderItem;
026import org.apache.wicket.markup.head.OnLoadHeaderItem;
027import org.apache.wicket.markup.html.form.Form;
028import org.apache.wicket.markup.html.form.FormComponent;
029import org.apache.wicket.request.resource.JavaScriptResourceReference;
030import org.apache.wicket.request.resource.ResourceReference;
031import org.apache.wicket.util.string.Strings;
032
033/**
034 * A form with filter-related special functionality for its form components.
035 *
036 * <p>
037 * This form uses an invisible button to be able to submit when the user presses the <em>ENTER</em>
038 * key. If there is a need to add an explicit
039 * {@link org.apache.wicket.markup.html.form.IFormSubmittingComponent} to this form then
040 * {@link Form#setDefaultButton(org.apache.wicket.markup.html.form.IFormSubmittingComponent)} should
041 * be used to specify this custom submitting component.
042 * </p>
043 *
044 * @param <T>
045 *            type of filter state object
046 * @author igor
047 */
048public class FilterForm<T> extends Form<T>
049{
050        private static final long serialVersionUID = 1L;
051
052        private static final ResourceReference JS = new JavaScriptResourceReference(FilterForm.class,
053                "wicket-filterform.js");
054
055        private final IFilterStateLocator<T> locator;
056
057        /**
058         * @param id
059         *            component id
060         * @param locator
061         *            filter state locator
062         */
063        public FilterForm(final String id, final IFilterStateLocator<T> locator)
064        {
065                super(id, new FilterStateModel<>(locator));
066
067                this.locator = locator;
068        }
069
070        @Override
071        public void renderHead(final IHeaderResponse response)
072        {
073                super.renderHead(response);
074
075                response.render(JavaScriptHeaderItem.forReference(JS));
076
077                response.render(OnLoadHeaderItem.forScript(String.format(
078                        "Wicket.FilterForm.restore('%s');", getFocusTrackerFieldCssId())));
079        }
080
081        /**
082         * {@inheritDoc}
083         */
084        @Override
085        public void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag)
086        {
087                super.onComponentTagBody(markupStream, openTag);
088
089                getResponse().write(generateHiddenInputMarkup());
090        }
091
092        /**
093         * Generates the Markup for the hidden input. Can be overridden by subclasses if necessary.
094         *
095         * @return The markup to be appended to the response
096         */
097        protected String generateHiddenInputMarkup() {
098                String id = Strings.escapeMarkup(getFocusTrackerFieldCssId()).toString();
099                String value = getRequest().getPostParameters().getParameterValue(id).toString("");
100                String cssClass = getString(Form.HIDDEN_FIELDS_CSS_CLASS_KEY);
101
102                return String.format(
103                                "<div hidden='' class='%s'><input type='hidden' name='%s' id='%s' value='%s'/><input type='submit'/></div>",
104                                cssClass, id, id, Strings.escapeMarkup(value));
105        }
106
107        /**
108         * @return css id of the hidden form input that keeps track of the focused input field
109         */
110        public final String getFocusTrackerFieldCssId()
111        {
112                return getMarkupId() + "focus";
113        }
114
115        /**
116         * @return IFilterStateLocator passed to this form
117         */
118        public final IFilterStateLocator<T> getStateLocator()
119        {
120                return locator;
121        }
122
123        /**
124         * Adds behavior to the form component to allow this form to keep track of the component's focus
125         * which will be restored after a form submit.
126         *
127         * @param fc
128         *            form component
129         */
130        public final void enableFocusTracking(final FormComponent<?> fc)
131        {
132                fc.add(new Behavior()
133                {
134                        private static final long serialVersionUID = 1L;
135
136                        @Override
137                        public void bind(Component component)
138                        {
139                                super.bind(component);
140                                component.setOutputMarkupId(true);
141                        }
142
143                        @Override
144                        public void renderHead(Component component, IHeaderResponse response) {
145                                response.render(OnEventHeaderItem.forComponent(component, "focus",
146                                                getFocusTrackingHandler(component)));
147                        }
148                });
149        }
150
151        /**
152         * Returns the javascript focus handler necessary to notify the form of focus tracking changes
153         * on the component
154         *
155         * Useful when components want to participate in focus tracking but want to add the handler
156         * their own way.
157         *
158         * A unique css id is required on the form component for focus tracking to work.
159         *
160         * @param component
161         *            component to
162         * @return the javascript focus handler necessary to notify the form of focus tracking changes
163         *         on the component
164         */
165        public final String getFocusTrackingHandler(final Component component)
166        {
167                return String.format("Wicket.FilterForm.focused(this, '%s');", getFocusTrackerFieldCssId());
168        }
169}