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.core.util.string;
018
019import org.apache.wicket.request.Response;
020import org.apache.wicket.util.string.Strings;
021import org.apache.wicket.util.value.AttributeMap;
022
023/**
024 * Provide some helpers to write javascript related tags to the response object.
025 * 
026 * @author Juergen Donnerstag
027 */
028public class JavaScriptUtils
029{
030        /**
031         * Prefix for JavaScript CDATA content. If this is changed, also update
032         * Wicket.Head.Contributor.processScript() function from wicket-ajax-jquery.js
033         */
034        public final static String SCRIPT_CONTENT_PREFIX = "\n/*<![CDATA[*/\n";
035
036        /**
037         * Suffix for JavaScript CDATA content. If this is changed, also update
038         * Wicket.Head.Contributor.processScript() function from wicket-ajax-jquery.js
039         */
040        public final static String SCRIPT_CONTENT_SUFFIX = "\n/*]]>*/\n";
041
042        /** Script open tag including content prefix */
043        public final static String SCRIPT_OPEN_TAG = "<script type=\"text/javascript\">" +
044                SCRIPT_CONTENT_PREFIX;
045
046        /** Script close tag including content suffix */
047        public final static String SCRIPT_CLOSE_TAG = SCRIPT_CONTENT_SUFFIX + "</script>\n";
048
049        public static final String ATTR_ID = "id";
050        public static final String ATTR_TYPE = "type";
051        public static final String ATTR_SCRIPT_SRC = "src";
052        public static final String ATTR_SCRIPT_DEFER = "defer";
053        public static final String ATTR_SCRIPT_ASYNC = "async";
054        public static final String ATTR_CSP_NONCE = "nonce";
055        public static final String ATTR_CROSS_ORIGIN = "crossOrigin";
056        public static final String ATTR_INTEGRITY = "integrity";
057
058        /** The response object */
059        private final Response response;
060
061        /**
062         * Construct.
063         * 
064         * @param response
065         *            The response object
066         * @param id
067         */
068        public JavaScriptUtils(final Response response, String id)
069        {
070                this.response = response;
071                writeOpenTag(response, id);
072        }
073
074        /**
075         * Constructor without id for backward compatibility
076         * 
077         * @param response
078         *            The response object
079         */
080        public JavaScriptUtils(final Response response)
081        {
082                this.response = response;
083                writeOpenTag(response);
084        }
085
086
087        /**
088         * Escape single and double quotes so that they can be part of e.g. an alert call.
089         * 
090         * Note: JSON values need to escape only the double quote, so this method wont help.
091         * 
092         * @param input
093         *            the JavaScript which needs to be escaped
094         * @return Escaped version of the input
095         */
096        public static CharSequence escapeQuotes(final CharSequence input)
097        {
098                CharSequence s = input;
099                if (s != null)
100                {
101                        s = Strings.replaceAll(s, "'", "\\'");
102                        s = Strings.replaceAll(s, "\"", "\\\"");
103                }
104                return s;
105        }
106
107        /**
108         * Write a reference to a javascript file to the response object
109         * 
110         * @param response
111         *            The HTTP response
112         * @param url
113         *            The javascript file URL
114         * @param id
115         *            Unique identifier of element
116         * @deprecated please use {@link #writeScript(Response, AttributeMap)} instead
117         */
118        @Deprecated
119        public static void writeJavaScriptUrl(final Response response, final CharSequence url,
120                final String id)
121        {
122                writeJavaScriptUrl(response, url, id, false, null, false);
123        }
124
125        /**
126         * Write a reference to a javascript file to the response object
127         *
128         * @param response
129         *            The HTTP response
130         * @param url
131         *            The javascript file URL
132         * @param id
133         *            Unique identifier of element
134         * @param defer
135         *            specifies that the execution of a script should be deferred (delayed) until after
136         *            the page has been loaded.
137         * @param charset
138         *            a non null value specifies the charset attribute of the script tag
139         * @deprecated please use {@link #writeScript(Response, AttributeMap)} instead
140         */
141        @Deprecated
142        public static void writeJavaScriptUrl(final Response response, final CharSequence url,
143                                              final String id, boolean defer, String charset)
144        {
145                writeJavaScriptUrl(response, url, id, defer, charset, false);
146        }
147
148        /**
149         * Write a reference to a javascript file to the response object
150         * 
151         * @param response
152         *            The HTTP response
153         * @param url
154         *            The javascript file URL
155         * @param id
156         *            Unique identifier of element
157         * @param defer
158         *            specifies that the execution of a script should be deferred (delayed) until after
159         *            the page has been loaded.
160         * @param charset
161         *            a non null value specifies the charset attribute of the script tag
162         * @param async
163         *            specifies that the script can be loaded asynchronously by the browser
164         * @deprecated please use {@link #writeScript(Response, AttributeMap)} instead
165         */
166        @Deprecated
167        public static void writeJavaScriptUrl(final Response response, final CharSequence url,
168                final String id, boolean defer, String charset, boolean async)
169        {
170                AttributeMap attributes = new AttributeMap();
171                // XXX JS mimetype can be omitted (also see below)
172                attributes.putAttribute(ATTR_TYPE, "text/javascript");
173                attributes.putAttribute(ATTR_SCRIPT_SRC, url);
174                attributes.putAttribute(ATTR_ID, id);
175                attributes.putAttribute(ATTR_SCRIPT_DEFER, defer);
176                attributes.putAttribute(ATTR_SCRIPT_ASYNC, async);
177                // FIXME charset attr is deprecated
178                // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#Deprecated_attributes
179                attributes.putAttribute("charset", charset);
180                writeScript(response, attributes);
181        }
182
183        /**
184         * Write a reference to a javascript file to the response object
185         *
186         * @param response
187         *            The HTTP response
188         * @param attributes
189         *            Extra tag attributes
190         */
191        public static void writeScript(final Response response, AttributeMap attributes)
192        {
193                response.write("<script");
194                response.write(attributes.toCharSequence());
195                response.write("></script>");
196                response.write("\n");
197        }
198
199        /**
200         * Write a reference to a javascript file to the response object
201         * 
202         * @param response
203         *            The HTTP response
204         * @param url
205         *            The javascript file URL
206         * @deprecated please use {@link #writeScript(Response, AttributeMap)} instead
207         */
208        @Deprecated
209        public static void writeJavaScriptUrl(final Response response, final CharSequence url)
210        {
211                writeJavaScriptUrl(response, url, null);
212        }
213
214        /**
215         * Write the simple text to the response object surrounded by a script tag.
216         * 
217         * @param response
218         *            The HTTP: response
219         * @param text
220         *            The text to added in between the script tags
221         * @param id
222         *            Unique identifier of element
223         * @deprecated please use {@link #writeInlineScript(Response, CharSequence, AttributeMap)} instead
224         */
225        public static void writeJavaScript(final Response response, final CharSequence text, String id)
226        {
227                writeOpenTag(response, id);
228                response.write(Strings.replaceAll(text, "</", "<\\/"));
229                writeCloseTag(response);
230        }
231
232        /**
233         * Write the simple text to the response object surrounded by a script tag.
234         *
235         * @param response
236         *              The HTTP: response
237         * @param text
238         *              The text to added in between the script tags
239         * @param attributes
240         *              Extra tag attributes
241         */
242        public static void writeInlineScript(final Response response, final CharSequence text, AttributeMap attributes)
243        {
244                writeOpenTag(response, attributes);
245                response.write(Strings.replaceAll(text, "</", "<\\/"));
246                writeCloseTag(response);
247        }
248
249        /**
250         * Write the simple text to the response object surrounded by a script tag.
251         * 
252         * @param response
253         *            The HTTP: response
254         * @param text
255         *            The text to added in between the script tags
256         */
257        public static void writeJavaScript(final Response response, final CharSequence text)
258        {
259                AttributeMap attributes = new AttributeMap();
260                attributes.putAttribute(ATTR_TYPE, "text/javascript");
261                writeInlineScript(response, text, attributes);
262        }
263
264        /**
265         * 
266         * @param response
267         * @param id
268         * @deprecated please use {@link #writeOpenTag(Response, AttributeMap)}
269         */
270        @Deprecated
271        public static void writeOpenTag(final Response response, String id)
272        {
273                AttributeMap attributes = new AttributeMap();
274                attributes.putAttribute(ATTR_TYPE, "text/javascript");
275                attributes.putAttribute(ATTR_ID, id);
276                writeOpenTag(response, attributes);
277        }
278
279        /**
280         * Write open script tag for inline script.
281         * Content is prefixed with {@link #SCRIPT_CONTENT_PREFIX}.
282         *
283         * @param response
284         *              the response to write to
285         * @param attributes
286         *              Tag attributes map
287         */
288        public static void writeOpenTag(final Response response, AttributeMap attributes)
289        {
290                response.write("<script");
291                response.write(attributes.toCharSequence());
292                response.write(">");
293                response.write(SCRIPT_CONTENT_PREFIX);
294        }
295
296        /**
297         * 
298         * @param response
299         * @deprecated please use {@link #writeOpenTag(Response, AttributeMap)}
300         */
301        @Deprecated
302        public static void writeOpenTag(final Response response)
303        {
304                AttributeMap attributes = new AttributeMap();
305                attributes.putAttribute(ATTR_TYPE, "text/javascript");
306                writeOpenTag(response, attributes);
307        }
308
309        /**
310         * Write close script tag for inline script. The close tag is prefixed with {@link #SCRIPT_CONTENT_SUFFIX}
311         *
312         * @param response
313         *              the response to write to
314         */
315        public static void writeCloseTag(final Response response)
316        {
317                response.write(SCRIPT_CONTENT_SUFFIX);
318                response.write("</script>\n");
319        }
320
321        /**
322         * @see Response#write(java.lang.CharSequence)
323         * @param script
324         */
325        public void write(final CharSequence script)
326        {
327                response.write(script);
328        }
329
330        /**
331         * @see Response#write(CharSequence)
332         * @param script
333         */
334        public void println(final CharSequence script)
335        {
336                response.write(script);
337        }
338
339        /**
340         * Write the inline script close tag to the response. The response output stream remains open.
341         * Calls {@link #writeCloseTag(Response)} internally.
342         * The close tag is prefixed with {@link #SCRIPT_CONTENT_SUFFIX}.
343         */
344        public void close()
345        {
346                writeCloseTag(response);
347        }       
348}