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.filter;
018
019import org.apache.wicket.ajax.AjaxRequestTarget;
020import org.apache.wicket.core.util.string.JavaScriptUtils;
021import org.apache.wicket.markup.head.AbstractJavaScriptReferenceHeaderItem;
022import org.apache.wicket.markup.head.HeaderItem;
023import org.apache.wicket.markup.head.IHeaderResponse;
024import org.apache.wicket.markup.head.IWrappedHeaderItem;
025import org.apache.wicket.markup.head.JavaScriptContentHeaderItem;
026import org.apache.wicket.markup.head.JavaScriptHeaderItem;
027import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
028import org.apache.wicket.markup.head.OnLoadHeaderItem;
029import org.apache.wicket.markup.html.DecoratingHeaderResponse;
030import org.apache.wicket.request.Response;
031import org.apache.wicket.request.cycle.RequestCycle;
032import org.apache.wicket.util.string.Strings;
033
034/**
035 * A header response which defers all {@link AbstractJavaScriptReferenceHeaderItem}s.
036 * <p>
037 * To prevent any error because of possible dependencies to referenced JavaScript files
038 * *all* {@link JavaScriptHeaderItem}s are replaced with suitable implementations that
039 * delay any execution until all deferred {@link AbstractJavaScriptReferenceHeaderItem}s
040 * have been loaded.
041 * <p>
042 * Note: This solution depends on the execution order of JavaScript in the browser:
043 * The 'DOMContentLoaded' event has to be fired <me>after</em> all deferred JavaScript
044 * resources have been loaded. This doesn't seem to be the case in all browsers, thus
045 * this class should be considered experimental.
046 * 
047 * @author svenmeier
048+ */
049public class JavaScriptDeferHeaderResponse extends DecoratingHeaderResponse
050{
051        /**
052         * Decorate the given response.
053         * 
054         * @param response
055         */
056        public JavaScriptDeferHeaderResponse(IHeaderResponse response)
057        {
058                super(response);
059        }
060        
061        @Override
062        public void render(HeaderItem item)
063        {
064                if (RequestCycle.get().find(AjaxRequestTarget.class).isEmpty()) {
065                        while (item instanceof IWrappedHeaderItem) {
066                                item = ((IWrappedHeaderItem)item).getWrapped();
067                        }
068
069                        if (item instanceof AbstractJavaScriptReferenceHeaderItem) {
070                                ((AbstractJavaScriptReferenceHeaderItem)item).setDefer(true);
071                        } else if (item instanceof JavaScriptContentHeaderItem) {
072                                item = new NativeOnDomContentLoadedHeaderItem(((JavaScriptContentHeaderItem)item).getJavaScript());
073                        } else if (item instanceof OnDomReadyHeaderItem) {
074                                item = new NativeOnDomContentLoadedHeaderItem(((OnDomReadyHeaderItem)item).getJavaScript());
075                        } else if (item instanceof OnLoadHeaderItem) {
076                                item = new NativeOnLoadHeaderItem(((OnLoadHeaderItem)item).getJavaScript());
077                        }
078                }
079                
080                super.render(item);
081        }
082
083        /**
084         * A specialization that uses native "DOMContentLoaded" events without dependency to external JavaScript.
085         */
086        private static class NativeOnDomContentLoadedHeaderItem extends OnDomReadyHeaderItem
087        {
088                private static final long serialVersionUID = 1L;
089
090                /**
091                 * Construct.
092                 *
093                 * @param javaScript
094                 */
095                public NativeOnDomContentLoadedHeaderItem(CharSequence javaScript)
096                {
097                        super(javaScript);
098                }
099
100                /**
101                 * Overriden to use native {@code addEventListener('DOMContentLoaded')} instead.
102                 */
103                @Override
104                public void render(Response response)
105                {
106                        CharSequence js = getJavaScript();
107                        if (Strings.isEmpty(js) == false)
108                        {
109                                JavaScriptUtils.writeJavaScript(response, "document.addEventListener('DOMContentLoaded', function() { " + js + "; });");
110                        }
111                }
112        }
113        
114        /**
115         * A specialization that uses native "load" events without dependency to external JavaScript 
116         */
117        private static class NativeOnLoadHeaderItem extends OnLoadHeaderItem
118        {
119                private static final long serialVersionUID = 1L;
120
121                /**
122                 * Construct.
123                 *
124                 * @param javaScript
125                 */
126                public NativeOnLoadHeaderItem(CharSequence javaScript)
127                {
128                        super(javaScript);
129                }
130
131                /**
132                 * Overriden to use native {@code addEventListener('load')} instead.
133                 */
134                @Override
135                public void render(Response response)
136                {
137                        CharSequence js = getJavaScript();
138                        if (Strings.isEmpty(js) == false)
139                        {
140                                JavaScriptUtils.writeJavaScript(response, "window.addEventListener('load', function() { " + js + "; });");
141                        }
142                }
143        }       
144}