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.markup.head;
018
019import java.util.Collections;
020import java.util.List;
021import java.util.Locale;
022import java.util.Objects;
023
024import org.apache.wicket.Application;
025import org.apache.wicket.Component;
026import org.apache.wicket.core.util.string.JavaScriptUtils;
027import org.apache.wicket.request.Response;
028import org.apache.wicket.request.resource.ResourceReference;
029import org.apache.wicket.settings.JavaScriptLibrarySettings;
030import org.apache.wicket.util.lang.Args;
031import org.apache.wicket.util.string.Strings;
032import org.apache.wicket.util.value.AttributeMap;
033
034/**
035 * {@link HeaderItem} for event triggered scripts.
036 *
037 * @author papegaaij
038 */
039public class OnEventHeaderItem extends AbstractCspHeaderItem
040{
041        private static final long serialVersionUID = 1L;
042
043        /**
044         * Creates a {@link OnEventHeaderItem} for the given parameters.
045         *
046         * @param literalTarget
047         *            The target of the event handler, for example 'window' or 'document'. Note that
048         *            this parameter is a literal and will be rendered unquoted.
049         * @param event
050         *            The event itself, for example 'click'.
051         * @param javaScript
052         *            The script to execute on the event.
053         *
054         * @return A newly created {@link OnEventHeaderItem}.
055         * @see #forComponent(Component, String, CharSequence)
056         * @see #forMarkupId(String, String, CharSequence)
057         */
058        public static OnEventHeaderItem forScript(String literalTarget, String event,
059                        CharSequence javaScript)
060        {
061                return new OnEventHeaderItem(literalTarget, event, javaScript);
062        }
063
064        /**
065         * Creates a {@link OnEventHeaderItem} for the given parameters.
066         *
067         * @param target
068         *            The target component of the event handler.
069         * @param event
070         *            The event itself, for example 'click'.
071         * @param javaScript
072         *            The script to execute on the event.
073         *
074         * @return A newly created {@link OnEventHeaderItem}.
075         */
076        public static OnEventHeaderItem forComponent(Component target, String event,
077                        CharSequence javaScript)
078        {
079                return forMarkupId(target.getMarkupId(), event, javaScript);
080        }
081
082        /**
083         * Creates a {@link OnEventHeaderItem} for the given parameters.
084         *
085         * @param id
086         *            The id of the component to bind the handler to.
087         * @param event
088         *            The event itself, for example 'click'.
089         * @param javaScript
090         *            The script to execute on the event.
091         *
092         * @return A newly created {@link OnEventHeaderItem}.
093         */
094        public static OnEventHeaderItem forMarkupId(String id, String event, CharSequence javaScript)
095        {
096                return forScript("'" + id + "'", event, javaScript);
097        }
098
099        private final String target;
100
101        private final String event;
102
103        private final CharSequence javaScript;
104
105        /**
106         * Constructor.
107         *
108         * The JavaScript should be provided by overloaded #getJavaScript
109         *
110         * @param target
111         * @param event
112         */
113        public OnEventHeaderItem(String target, String event)
114        {
115                this(target, event, null);
116        }
117
118        /**
119         * Construct.
120         *
121         * @param target
122         * @param event
123         * @param javaScript
124         */
125        public OnEventHeaderItem(String target, String event, CharSequence javaScript)
126        {
127                this.target = Args.notEmpty(target, "target");
128
129                Args.notEmpty(event, "event");
130                event = event.toLowerCase(Locale.ROOT);
131                this.event = event;
132                this.javaScript = javaScript;
133        }
134
135        /**
136         * @return The target of the event handler, for example 'window' or 'document'.
137         */
138        public String getTarget()
139        {
140                return target;
141        }
142
143        /**
144         * @return The event itself, for example 'onclick'.
145         */
146        public String getEvent()
147        {
148                return event;
149        }
150
151        /**
152         * @return The script to execute on the event.
153         */
154        public CharSequence getJavaScript()
155        {
156                return javaScript;
157        }
158
159        @Override
160        public void render(Response response)
161        {
162                if (Strings.isEmpty(getJavaScript()) == false)
163                {
164                        AttributeMap attributes = new AttributeMap();
165                        attributes.putAttribute(JavaScriptUtils.ATTR_TYPE, "text/javascript");
166                        attributes.putAttribute(JavaScriptUtils.ATTR_CSP_NONCE, getNonce());
167                        JavaScriptUtils.writeInlineScript(response, getCompleteJavaScript(), attributes);
168                }
169        }
170
171        /**
172         * @return The JavaScript that registers the event handler.
173         */
174        public CharSequence getCompleteJavaScript()
175        {
176                StringBuilder result = new StringBuilder();
177                result.append("Wicket.Event.add(")
178                                .append(getTarget())
179                                .append(", \'")
180                                .append(getEvent())
181                                .append("\', function(event) { ")
182                                .append(getJavaScript())
183                                .append(";});");
184                return result;
185        }
186
187        @Override
188        public Iterable<?> getRenderTokens()
189        {
190                return Collections.singletonList("javascript-event-" + getTarget() + "-" + getEvent() +
191                        "-" + getJavaScript());
192        }
193
194        @Override
195        public String toString()
196        {
197                return "OnEventHeaderItem(" + getTarget() + ", '" + getEvent() + "', '" + getJavaScript() + "')";
198        }
199
200        @Override
201        public int hashCode()
202        {
203                // Not using `Objects.hash` for performance reasons
204                int result = target != null ? target.hashCode() : 0;
205                result = 31 * result + (event != null ? event.hashCode() : 0);
206                result = 31 * result + (javaScript != null ? javaScript.hashCode() : 0);
207                return result;
208        }
209
210        @Override
211        public boolean equals(Object o)
212        {
213                if (this == o) return true;
214                if (o == null || getClass() != o.getClass()) return false;
215                OnEventHeaderItem that = (OnEventHeaderItem) o;
216                return Objects.equals(target, that.target) &&
217                                Objects.equals(event, that.event) &&
218                                Objects.equals(javaScript, that.javaScript);
219        }
220
221        @Override
222        public List<HeaderItem> getDependencies()
223        {
224                JavaScriptLibrarySettings ajaxSettings = Application.get().getJavaScriptLibrarySettings();
225                ResourceReference wicketAjaxReference = ajaxSettings.getWicketAjaxReference();
226                List<HeaderItem> dependencies = super.getDependencies();
227                dependencies.add(JavaScriptHeaderItem.forReference(wicketAjaxReference));
228                return dependencies;
229        }
230}