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