001    // Copyright 2004, 2005 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    //     http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    package org.apache.tapestry.dojo;
015    
016    import org.apache.hivemind.util.Defense;
017    import org.apache.tapestry.*;
018    import org.apache.tapestry.html.Shell;
019    import org.apache.tapestry.json.JSONObject;
020    
021    import java.util.Locale;
022    
023    /**
024     * The default rendering delegate responseible for include the dojo sources in
025     * to the {@link Shell} component.
026     */
027    public class AjaxShellDelegate implements IRender {
028    
029        /** Client side debug log level. */
030        public static final String BROWSER_LOG_DEBUG="DEBUG";
031        /** Client side info log level. */
032        public static final String BROWSER_LOG_INFO="INFO";
033        /** Client side warning log level. */
034        public static final String BROWSER_LOG_WARNING="WARNING";
035        /** Client side error log level. */
036        public static final String BROWSER_LOG_ERROR="ERROR";
037        /** Client side critical log level. */
038        public static final String BROWSER_LOG_CRITICAL="CRITICAL";
039    
040        private static final String SYSTEM_NEWLINE= (String)java.security.AccessController.doPrivileged(
041          new sun.security.action.GetPropertyAction("line.separator"));
042    
043        private IAsset _dojoSource;
044    
045        private IAsset _dojoFormSource;
046    
047        private IAsset _dojoWidgetSource;
048    
049        private IAsset _dojoPath;
050    
051        private IAsset _tapestrySource;
052    
053        private IAsset _tapestryPath;
054    
055        private boolean _parseWidgets;
056    
057        private String _browserLogLevel = BROWSER_LOG_WARNING;
058    
059        private boolean _debug;
060    
061        private String _debugContainerId;
062    
063        private boolean _consoleEnabled;
064    
065        private boolean _preventBackButtonFix;
066    
067        private boolean _debugAtAllCosts;
068    
069        /** Default list of pre-bundled dojo supported locales */
070        protected String[] SUPPORTED_LOCALES = { "en-us", "de-de", "de", "en-gb",
071                                                 "es-es", "es", "fr-fr", "fr", "zh-cn",
072                                                 "zh-tw", "zh" , "it-it", "it", "ja-jp",
073                                                 "ja", "ko-kr", "ko", "pt-br", "pt", "en", "xx"};
074    
075        /**
076         * {@inheritDoc}
077         */
078        public void render(IMarkupWriter writer, IRequestCycle cycle)
079        {
080            // first configure dojo, has to happen before package include
081    
082            JSONObject dojoConfig = new JSONObject();
083    
084            // Debugging configuration , debugAtAlCosts causes the individual 
085            // .js files to included in the document head so that javascript errors
086            // are able to resolve to the context of the file instead of just "dojo.js"
087    
088            if (_debug)
089            {
090                dojoConfig.put("isDebug", _debug);
091            }
092    
093            if (_debugAtAllCosts)
094                dojoConfig.put("debugAtAllCosts", _debugAtAllCosts);
095            if (_debugContainerId != null)
096                dojoConfig.put("debugContainerId", _debugContainerId);
097    
098            IPage page = cycle.getPage();
099    
100            // The key to resolving everything out of the asset service
101    
102            dojoConfig.put("baseRelativePath", _dojoPath.buildURL());
103    
104            if (page.hasFormComponents())
105            {
106                dojoConfig.put("preventBackButtonFix", _preventBackButtonFix);
107            }
108            dojoConfig.put("parseWidgets", _parseWidgets);
109    
110            // Supports setting up locale in dojo environment to match the requested page locale.
111            // (for things that use these settings, like DropdownDatePicker / date parsing / etc..
112    
113            Locale locale = cycle.getPage().getLocale();
114    
115            String localeStr = locale.getLanguage().toLowerCase()
116                               + ((locale.getCountry() != null && locale.getCountry().trim().length() > 0)
117                                  ? "-" + locale.getCountry().toLowerCase()
118                                  : "");
119    
120            if (isLocaleSupported(localeStr))
121            {
122                dojoConfig.put("locale", localeStr);
123            }
124    
125            // Write the required script includes and dojo.requires
126    
127            StringBuffer str = new StringBuffer("<script type=\"text/javascript\">");
128            str.append("djConfig = ").append(dojoConfig.toString())
129              .append(" </script>")
130              .append(SYSTEM_NEWLINE).append(SYSTEM_NEWLINE);
131    
132            // include the core dojo.js package
133    
134            str.append("<script type=\"text/javascript\" src=\"")
135              .append(_dojoSource.buildURL()).append("\"></script>");
136    
137            if (page.hasFormComponents())
138            {
139                str.append("<script type=\"text/javascript\" src=\"")
140                  .append(_dojoFormSource.buildURL()).append("\"></script>");
141            }
142    
143            if (page.hasWidgets())
144            {
145                str.append("<script type=\"text/javascript\" src=\"")
146                  .append(_dojoWidgetSource.buildURL()).append("\"></script>");
147            }
148    
149            // configure basic dojo properties , logging includes
150    
151            if (_debug)
152            {
153                String logRequire = _consoleEnabled ? "dojo.require(\"dojo.debug.console\");" + SYSTEM_NEWLINE
154                                    : "dojo.require(\"dojo.logging.Logger\");" + SYSTEM_NEWLINE;
155    
156                str.append(SYSTEM_NEWLINE).append("<script type=\"text/javascript\">").append(SYSTEM_NEWLINE);
157                str.append(logRequire)
158                  .append("dojo.log.setLevel(dojo.log.getLevel(\"").append(_browserLogLevel)
159                  .append("\"));").append(SYSTEM_NEWLINE)
160                  .append("</script>");
161            }
162    
163            // module path registration to tapestry javascript sources
164    
165            String tapestryUrl = _tapestryPath.buildURL();
166            if (tapestryUrl.endsWith("/"))
167            {
168                tapestryUrl = tapestryUrl.substring(0, tapestryUrl.length() - 1);
169            }
170    
171            str.append(SYSTEM_NEWLINE).append("<script type=\"text/javascript\">").append(SYSTEM_NEWLINE)
172              .append("dojo.registerModulePath(\"tapestry\", \"")
173              .append(tapestryUrl).append("\");").append(SYSTEM_NEWLINE);
174            str.append("</script>").append(SYSTEM_NEWLINE);
175    
176            // include core tapestry.js package
177    
178            str.append("<script type=\"text/javascript\" src=\"")
179              .append(_tapestrySource.buildURL()).append("\"></script>");
180    
181            // namespace registration
182    
183            str.append(SYSTEM_NEWLINE).append("<script type=\"text/javascript\">").append(SYSTEM_NEWLINE);
184            str.append("dojo.require(\"tapestry.namespace\");").append(SYSTEM_NEWLINE)
185              .append("tapestry.requestEncoding='")
186              .append(cycle.getEngine().getOutputEncoding()).append("';")
187              .append(SYSTEM_NEWLINE).append("</script>");
188    
189            writer.printRaw(str.toString());
190            writer.println();
191        }
192    
193        /**
194         * Checks if the provided locale string matches one of the predefined {@link #SUPPORTED_LOCALES}
195         * in the dojo javascript library.
196         *
197         * @param locale
198         *          The Dojo formatted locale string to check.
199         *
200         * @return True if locale is supported and ok to define in dojoConfig - false otherwise.
201         */
202        protected boolean isLocaleSupported(String locale)
203        {
204            if (locale == null)
205                return false;
206    
207            for (int i=0; i < SUPPORTED_LOCALES.length; i++)
208            {
209                if (locale.equals(SUPPORTED_LOCALES[i]))
210                    return true;
211            }
212    
213            return false;
214        }
215    
216        /**
217         * Sets the dojo logging level. Similar to log4j style
218         * log levels. 
219         * @param level The string constant for the level, valid values
220         *              are:
221         *              <p>
222         *              <ul>
223         *              <li>{@link #BROWSER_LOG_DEBUG}</li>
224         *              <li>{@link #BROWSER_LOG_INFO}</li>
225         *              <li>{@link #BROWSER_LOG_WARNING}</li>
226         *              <li>{@link #BROWSER_LOG_ERROR}</li>
227         *              <li>{@link #BROWSER_LOG_CRITICAL}</li>
228         *              </ul>
229         *              </p>
230         */
231        public void setLogLevel(String level)
232        {
233            Defense.notNull("level", level);
234    
235            _browserLogLevel = level;
236        }
237    
238        /**
239         * Allows for turning browser debugging on/off.
240         *
241         * @param debug If false, no logging output will be written.
242         */
243        public void setDebug(boolean debug)
244        {
245            _debug = debug;
246        }
247    
248        /**
249         * Turns off deep context level javascript debugging mode for dojo. This means
250         * that exceptions/debug statements will show you line numbers from the actual 
251         * javascript file that generated them instead of the normal default which is 
252         * usually bootstrap.js .
253         *
254         * <p>The default value is false if not set.</p>
255         *
256         * <p>
257         *  People should be wary of turning this on as it may cause problems
258         *  under certain conditions, and you definitely don't ever want this 
259         *  on in production. 
260         * </p>
261         *
262         * @param value If true deep debugging will be turned on.
263         */
264        public void setDebugAtAllCosts(boolean value)
265        {
266            _debugAtAllCosts = value;
267        }
268    
269        /**
270         * Sets the html element node id of the element you would like all browser
271         * debug content to go to.
272         *
273         * @param debugContainerId the debugContainerId to set
274         */
275        public void setDebugContainerId(String debugContainerId)
276        {
277            _debugContainerId = debugContainerId;
278        }
279    
280        /**
281         * Enables/disables the dojo.debug.console functionality which should redirect
282         * most logging messages to your browsers javascript console. (if it supports 
283         * one).
284         *
285         * <p>
286         *  The debug console is disabled by default. Currently known supported 
287         *  browsers are FireFox(having FireBug extension helps a great deal)/Opera/Safari.
288         * </p>
289         *
290         * @param enabled Whether or not the enable debug console.
291         */
292        public void setConsoleEnabled(boolean enabled)
293        {
294            _consoleEnabled = enabled;
295        }
296    
297        /**
298         * Sets the dojo preventBackButtonFix djConfig configuration. This should
299         * typically be avoided but is provided for flexibility.
300         *
301         * @param prevent
302         *          Whether or not to prevent back button fix.
303         */
304        public void setPreventBackButtonFix(boolean prevent)
305        {
306            _preventBackButtonFix = prevent;
307        }
308    
309        /**
310         * Tells dojo whether or not to parse widgets by traversing the entire 
311         * dom node of your document. It is highly reccomended that you keep this
312         * at its default value of false.
313         *
314         * @param parseWidgets the parseWidgets to set
315         */
316        public void setParseWidgets(boolean parseWidgets)
317        {
318            _parseWidgets = parseWidgets;
319        }
320    
321        /**
322         * Sets a valid path to the base dojo javascript installation
323         * directory.
324         *
325         * @param dojoSource
326         *          Path to dojo source directory core "dojo.js" file.
327         */
328        public void setDojoSource(IAsset dojoSource)
329        {
330            _dojoSource = dojoSource;
331        }
332    
333        public void setDojoFormSource(IAsset formSource)
334        {
335            _dojoFormSource = formSource;
336        }
337    
338        public void setDojoWidgetSource(IAsset widgetSource)
339        {
340            _dojoWidgetSource = widgetSource;
341        }
342    
343        /**
344         * Sets the dojo baseRelativePath value.
345         *
346         * @param dojoPath
347         *          The base path to dojo directory.
348         */
349        public void setDojoPath(IAsset dojoPath)
350        {
351            _dojoPath = dojoPath;
352        }
353    
354        /**
355         * Sets a valid base path to resolve tapestry core.js.
356         *
357         * @param tapestrySource
358         *          Main tapestry core.js file.
359         */
360        public void setTapestrySource(IAsset tapestrySource)
361        {
362            _tapestrySource = tapestrySource;
363        }
364    
365        /**
366         * Sets the path to the tapestry javascript modules. (Needed for dojo to resolve the 
367         * path to tapestry javascript, esp when overriding the default bundled dojo.)
368         *
369         * @param tapestryPath The path to tapestry.
370         */
371        public void setTapestryPath(IAsset tapestryPath)
372        {
373            _tapestryPath = tapestryPath;
374        }
375    }