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.ajax;
018
019import java.util.ArrayList;
020import java.util.List;
021
022import org.apache.wicket.Component;
023import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
024import org.apache.wicket.markup.head.IHeaderResponse;
025import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
026import org.apache.wicket.markup.head.OnLoadHeaderItem;
027import org.apache.wicket.util.lang.Args;
028import org.apache.wicket.util.lang.Checks;
029import org.apache.wicket.util.string.Strings;
030import org.danekja.java.util.function.serializable.SerializableConsumer;
031import org.slf4j.Logger;
032import org.slf4j.LoggerFactory;
033
034/**
035 * An ajax behavior that is attached to a certain client-side (usually javascript) event, such as
036 * click, change, keydown, etc.
037 * <p>
038 * Example:
039 * 
040 * <pre>
041 *         WebMarkupContainer div=new WebMarkupContainer(...);
042 *         div.setOutputMarkupId(true);
043 *         div.add(new AjaxEventBehavior(&quot;click&quot;) {
044 *             protected void onEvent(AjaxRequestTarget target) {
045 *                 System.out.println(&quot;ajax here!&quot;);
046 *             }
047 *         }
048 * </pre>
049 * 
050 * This behavior will be linked to the <em>click</em> javascript event of the div WebMarkupContainer
051 * represents, and so anytime a user clicks this div the {@link #onEvent(AjaxRequestTarget)} of the
052 * behavior is invoked.
053 *
054 * <p>
055 * <strong>Note</strong>: {@link #getEvent()} method cuts any <em>on</em> prefix from the given event name(s).
056 * This is being done for easier migration of applications coming from Wicket 1.5.x where Wicket used
057 * inline attributes like 'onclick=...'. If the application needs to use custom events with names starting with
058 * <em>on</em> then {@link #getEvent()} should be overridden.
059 * </p>
060 *
061 * @since 1.2
062 * 
063 * @author Igor Vaynberg (ivaynberg)
064 * @see #onEvent(AjaxRequestTarget)
065 */
066public abstract class AjaxEventBehavior extends AbstractDefaultAjaxBehavior
067{
068        private static final Logger LOGGER = LoggerFactory.getLogger(AjaxEventBehavior.class);
069
070        private static final long serialVersionUID = 1L;
071
072        private static final char EVENT_NAME_SEPARATOR = ' ';
073
074        private final String event;
075
076        /**
077         * Construct.
078         * 
079         * @param event
080         *      the event this behavior will be attached to
081         */
082        public AjaxEventBehavior(String event)
083        {
084                Args.notEmpty(event, "event");
085
086                if ("inputchange".equals(event))
087                {
088                        // TODO Wicket 10 remove (see WICKET-6667)
089                        event = "input";
090                        LOGGER.warn("Since version 9.0.0 Wicket no longer supports 'inputchange' events, please use 'input' instead");
091                }
092
093                this.event = event;
094        }
095
096        @Override
097        public void renderHead(final Component component, final IHeaderResponse response)
098        {
099                super.renderHead(component, response);
100
101                if (component.isEnabledInHierarchy())
102                {
103                        CharSequence js = getCallbackScript(component);
104
105                        if ("load".equals(getEvent()))
106                        {
107                                response.render(OnLoadHeaderItem.forScript(js.toString()));
108                        }
109                        else
110                        {
111                                response.render(OnDomReadyHeaderItem.forScript(js.toString()));
112                        }
113                }
114        }
115
116        @Override
117        protected void updateAjaxAttributes(AjaxRequestAttributes attributes)
118        {
119                super.updateAjaxAttributes(attributes);
120
121                String evt = getEvent();
122                Checks.notEmpty(evt, "getEvent() should return non-empty event name(s)");
123                attributes.setEventNames(evt);
124        }
125
126        /**
127         * @return event
128         *      the event this behavior is attached to
129         */
130        public String getEvent()
131        {
132                if (event.indexOf(EVENT_NAME_SEPARATOR) == -1)
133                {
134                        return event;
135                }
136
137                String[] splitEvents = Strings.split(event, EVENT_NAME_SEPARATOR);
138                List<String> cleanedEvents = new ArrayList<>(splitEvents.length);
139                for (String evt : splitEvents)
140                {
141                        evt = evt.trim();
142                        if (!Strings.isEmpty(evt))
143                        {
144                                cleanedEvents.add(evt);
145                        }
146                }
147
148                return Strings.join(" ", cleanedEvents);
149        }
150
151        @Override
152        protected final void respond(final AjaxRequestTarget target)
153        {
154                onEvent(target);
155        }
156
157        /**
158         * Listener method for the ajax event
159         * 
160         * @param target
161         *      the current request handler
162         */
163        protected abstract void onEvent(final AjaxRequestTarget target);
164
165        /**
166         * Creates an {@link AjaxEventBehavior} based on lambda expressions
167         * 
168         * @param eventName
169         *            the event name
170         * @param onEvent
171         *            the {@code SerializableConsumer} which accepts the {@link AjaxRequestTarget}
172         * @return the {@link AjaxEventBehavior}
173         */
174        public static AjaxEventBehavior onEvent(String eventName, SerializableConsumer<AjaxRequestTarget> onEvent)
175        {
176                Args.notNull(onEvent, "onEvent");
177
178                return new AjaxEventBehavior(eventName)
179                {
180                        private static final long serialVersionUID = 1L;
181
182                        @Override
183                        protected void onEvent(AjaxRequestTarget target)
184                        {
185                                onEvent.accept(target);
186                        }
187                };
188        }
189}