    001// Licensed under the Apache License, Version 2.0 (the "License");
    002// you may not use this file except in compliance with the License.
    003// You may obtain a copy of the License at
    004//
    005// http://www.apache.org/licenses/LICENSE-2.0
    006//
    007// Unless required by applicable law or agreed to in writing, software
    008// distributed under the License is distributed on an "AS IS" BASIS,
    009// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    010// See the License for the specific language governing permissions and
    011// limitations under the License.
    012
    013package org.apache.tapestry5.modules;
    014
    015import java.util.Locale;
    016
    017import org.apache.tapestry5.BooleanHook;
    018import org.apache.tapestry5.MarkupWriter;
    019import org.apache.tapestry5.SymbolConstants;
    020import org.apache.tapestry5.annotations.Path;
    021import org.apache.tapestry5.corelib.components.FontAwesomeIcon;
    022import org.apache.tapestry5.corelib.components.Glyphicon;
    023import org.apache.tapestry5.internal.InternalConstants;
    024import org.apache.tapestry5.internal.services.DocumentLinker;
    025import org.apache.tapestry5.internal.services.ResourceStreamer;
    026import org.apache.tapestry5.internal.services.ajax.JavaScriptSupportImpl;
    027import org.apache.tapestry5.internal.services.assets.ResourceChangeTracker;
    028import org.apache.tapestry5.internal.services.javascript.AddBrowserCompatibilityStyles;
    029import org.apache.tapestry5.internal.services.javascript.ConfigureHTMLElementFilter;
    030import org.apache.tapestry5.internal.services.javascript.Internal;
    031import org.apache.tapestry5.internal.services.javascript.JavaScriptStackPathConstructor;
    032import org.apache.tapestry5.internal.services.javascript.JavaScriptStackSourceImpl;
    033import org.apache.tapestry5.internal.services.javascript.ModuleDispatcher;
    034import org.apache.tapestry5.internal.services.javascript.ModuleManagerImpl;
    035import org.apache.tapestry5.internal.util.MessageCatalogResource;
    036import org.apache.tapestry5.ioc.MappedConfiguration;
    037import org.apache.tapestry5.ioc.OperationTracker;
    038import org.apache.tapestry5.ioc.OrderedConfiguration;
    039import org.apache.tapestry5.ioc.Resource;
    040import org.apache.tapestry5.ioc.ServiceBinder;
    041import org.apache.tapestry5.ioc.annotations.Contribute;
    042import org.apache.tapestry5.ioc.annotations.Primary;
    043import org.apache.tapestry5.ioc.annotations.Symbol;
    044import org.apache.tapestry5.ioc.services.FactoryDefaults;
    045import org.apache.tapestry5.ioc.services.SymbolProvider;
    046import org.apache.tapestry5.ioc.util.IdAllocator;
    047import org.apache.tapestry5.json.JSONObject;
    048import org.apache.tapestry5.services.ComponentOverride;
    049import org.apache.tapestry5.services.Core;
    050import org.apache.tapestry5.services.Dispatcher;
    051import org.apache.tapestry5.services.Environment;
    052import org.apache.tapestry5.services.EnvironmentalShadowBuilder;
    053import org.apache.tapestry5.services.LocalizationSetter;
    054import org.apache.tapestry5.services.MarkupRenderer;
    055import org.apache.tapestry5.services.MarkupRendererFilter;
    056import org.apache.tapestry5.services.PartialMarkupRenderer;
    057import org.apache.tapestry5.services.PartialMarkupRendererFilter;
    058import org.apache.tapestry5.services.PathConstructor;
    059import org.apache.tapestry5.services.Request;
    060import org.apache.tapestry5.services.compatibility.Compatibility;
    061import org.apache.tapestry5.services.compatibility.Trait;
    062import org.apache.tapestry5.services.javascript.AMDWrapper;
    063import org.apache.tapestry5.services.javascript.ExtensibleJavaScriptStack;
    064import org.apache.tapestry5.services.javascript.JavaScriptModuleConfiguration;
    065import org.apache.tapestry5.services.javascript.JavaScriptStack;
    066import org.apache.tapestry5.services.javascript.JavaScriptStackSource;
    067import org.apache.tapestry5.services.javascript.JavaScriptSupport;
    068import org.apache.tapestry5.services.javascript.ModuleManager;
    069import org.apache.tapestry5.services.javascript.StackExtension;
    070import org.apache.tapestry5.services.javascript.StackExtensionType;
    071import org.apache.tapestry5.services.messages.ComponentMessagesSource;
    072
    073/**
    074 * Defines the services related to JavaScript and {@link org.apache.tapestry5.services.javascript.JavaScriptStack}s.
    075 *
    076 * @since 5.4
    077 */
    078public class JavaScriptModule
    079{
    080    private final static String ROOT = "${tapestry.asset.root}";
    081
    082    private final Environment environment;
    083
    084    private final EnvironmentalShadowBuilder environmentalBuilder;
    085
    086    public JavaScriptModule(Environment environment, EnvironmentalShadowBuilder environmentalBuilder)
    087    {
    088        this.environment = environment;
    089        this.environmentalBuilder = environmentalBuilder;
    090    }
    091
    092    public static void bind(ServiceBinder binder)
    093    {
    094        binder.bind(ModuleManager.class, ModuleManagerImpl.class);
    095        binder.bind(JavaScriptStackSource.class, JavaScriptStackSourceImpl.class);
    096        binder.bind(JavaScriptStack.class, ExtensibleJavaScriptStack.class).withMarker(Core.class).withId("CoreJavaScriptStack");
    097        binder.bind(JavaScriptStack.class, ExtensibleJavaScriptStack.class).withMarker(Internal.class).withId("InternalJavaScriptStack");
    098    }
    099
    100    /**
    101     * Contributes the "core" and "internal" {@link JavaScriptStack}s
    102     *
    103     * @since 5.2.0
    104     */
    105    @Contribute(JavaScriptStackSource.class)
    106    public static void provideBuiltinJavaScriptStacks(MappedConfiguration<String, JavaScriptStack> configuration,
    107                                                      @Core JavaScriptStack coreStack,
    108                                                      @Internal JavaScriptStack internalStack)
    109    {
    110        configuration.add(InternalConstants.CORE_STACK_NAME, coreStack);
    111        configuration.add("internal", internalStack);
    112    }
    113
    114    // These are automatically bundles with the core JavaScript stack; some applications may want to add a few
    115    // additional ones, such as t5/core/zone.
    116    private static final String[] bundledModules = new String[]{
    117            "alert", "ajax", "bootstrap", "console", "dom", "events", "exception-frame", "fields", "forms",
    118            "pageinit", "messages", "utils", "validation"
    119    };
    120
    121    /**
    122     * The core JavaScriptStack has a number of entries:
    123     * <dl>
    124     * <dt>requirejs</dt> <dd>The RequireJS AMD JavaScript library</dd>
    125     * <dt>scriptaculous.js, effects.js</dt> <dd>Optional JavaScript libraries in compatibility mode (see {@link Trait#SCRIPTACULOUS})</dd>
    126     * <dt>t53-compatibility.js</dt> <dd>Optional JavaScript library (see {@link Trait#INITIALIZERS})</dd>
    127     * <dt>underscore-library, underscore-module</dt>
    128     * <dt>The Underscore JavaScript library, and the shim that allows underscore to be injected</dt>
    129     * <dt>t5/core/init</dt> <dd>Optional module related to t53-compatibility.js</dd>
    130     * <dt>jquery-library</dt> <dd>The jQuery library</dd>
    131     * <dt>jquery-noconflict</dt> <dd>Switches jQuery to no-conflict mode (only present when the infrastructure is "prototype").</dd>
    132     * <dt>jquery</dt> <dd>A module shim that allows jQuery to be injected (and also switches jQuery to no-conflict mode)</dd>
    133     * <dt>bootstrap.css, tapestry.css, exception-frame.css, tapestry-console.css, tree.css</dt>
    134     * <dd>CSS files</dd>
    135     * <dt>t5/core/[...]</dt>
    136     * <dd>Additional JavaScript modules</dd>
    137     * <dt>jquery</dt>
    138     * <dd>Added if the infrastructure provider is "jquery".</dd>
    139     * </dl>
    140     *
    141     * User modules may replace or extend this list.
    142     */
    143    @Contribute(JavaScriptStack.class)
    144    @Core
    145    public static void setupCoreJavaScriptStack(OrderedConfiguration<StackExtension> configuration,
    146                                                Compatibility compatibility,
    147                                                @Symbol(SymbolConstants.JAVASCRIPT_INFRASTRUCTURE_PROVIDER)
    148                                                String provider)
    149    {
    150        configuration.add("requirejs", StackExtension.library(ROOT + "/require.js"));
    151        configuration.add("underscore-library", StackExtension.library(ROOT + "/underscore-1.8.3.js"));
    152
    153        if (provider.equals("prototype"))
    154        {
    155            final String SCRIPTY = "${tapestry.scriptaculous}";
    156
    157            add(configuration, StackExtensionType.LIBRARY, SCRIPTY + "/prototype.js");
    158
    159            if (compatibility.enabled(Trait.SCRIPTACULOUS))
    160            {
    161                add(configuration, StackExtensionType.LIBRARY,
    162                        SCRIPTY + "/scriptaculous.js",
    163                        SCRIPTY + "/effects.js");
    164            }
    165        }
    166
    167        if (compatibility.enabled(Trait.INITIALIZERS))
    168        {
    169            add(configuration, StackExtensionType.LIBRARY, ROOT + "/t53-compatibility.js");
    170            configuration.add("t5/core/init", new StackExtension(StackExtensionType.MODULE, "t5/core/init"));
    171        }
    172
    173        configuration.add("jquery-library", StackExtension.library(ROOT + "/jquery.js"));
    174
    175        if (provider.equals("prototype"))
    176        {
    177            configuration.add("jquery-noconflict", StackExtension.library(ROOT + "/jquery-noconflict.js"));
    178        }
    179
    180        add(configuration, StackExtensionType.MODULE, "jquery");
    181
    182        addCoreStylesheets(configuration, "${" + SymbolConstants.FONT_AWESOME_ROOT + "}/css/font-awesome.css");
    183
    184        if (compatibility.enabled(Trait.BOOTSTRAP_3) && compatibility.enabled(Trait.BOOTSTRAP_4))
    185        {
    186            throw new RuntimeException("You cannot have Trait.BOOTSTRAP_3 and Trait.BOOTSTRAP_4 enabled at the same time. Check your contributions to the Compatibility service.");
    187        }
    188
    189        if (compatibility.enabled(Trait.BOOTSTRAP_3))
    190        {
    191            addCoreStylesheets(configuration, "${" + SymbolConstants.BOOTSTRAP_ROOT + "}/css/bootstrap.css");
    192        }
    193
    194        if (compatibility.enabled(Trait.BOOTSTRAP_4))
    195        {
    196            addCoreStylesheets(configuration, "${" + SymbolConstants.BOOTSTRAP_ROOT + "}/css/bootstrap.css");
    197            addCoreStylesheets(configuration, "${" + SymbolConstants.BOOTSTRAP_ROOT + "}/css/bootstrap-grid.css");
    198        }
    199
    200        if (!compatibility.enabled(Trait.BOOTSTRAP_3) && !compatibility.enabled(Trait.BOOTSTRAP_4))
    201        {
    202            configuration.add("defaultcss", StackExtension.stylesheet("${" + SymbolConstants.DEFAULT_STYLESHEET + "}"));
    203        }
    204
    205        for (String name : bundledModules)
    206        {
    207            String full = "t5/core/" + name;
    208            configuration.add(full, StackExtension.module(full));
    209        }
    210
    211        configuration.add("underscore-module", StackExtension.module("underscore"));
    212    }
    213
    214    @Contribute(Compatibility.class)
    215    public static void setupCompatibilityDefaults(MappedConfiguration<Trait, Boolean> configuration)
    216    {
    217        configuration.add(Trait.BOOTSTRAP_4, false);
    218    }
    219
    220    @Contribute(JavaScriptStack.class)
    221    @Internal
    222    public static void setupInternalJavaScriptStack(OrderedConfiguration<StackExtension> configuration)
    223    {
    224
    225        // For the internal stack, ignore the configuration and just use the Bootstrap CSS shipped with the
    226        // framework. This is part of a hack to make internal pages (such as ExceptionReport and T5Dashboard)
    227        // render correctly even when the Bootstrap CSS has been replaced by the application.
    228
    229        addCoreStylesheets(configuration, ROOT + "/bootstrap/css/bootstrap.css");
    230    }
    231
    232    private static void addCoreStylesheets(OrderedConfiguration<StackExtension> configuration, String bootstrapPath)
    233    {
    234        add(configuration, StackExtensionType.STYLESHEET,
    235                bootstrapPath,
    236
    237                ROOT + "/tapestry.css",
    238
    239                ROOT + "/exception-frame.css",
    240
    241                ROOT + "/tapestry-console.css",
    242
    243                ROOT + "/tree.css");
    244    }
    245
    246    private static void add(OrderedConfiguration<StackExtension> configuration, StackExtensionType type, String... paths)
    247    {
    248        for (String path : paths)
    249        {
    250            int slashx = path.lastIndexOf('/');
    251            String id = path.substring(slashx + 1);
    252
    253            configuration.add(id, new StackExtension(type, path));
    254        }
    255    }
    256
    257
    258    /**
    259     * Builds a proxy to the current {@link JavaScriptSupport} inside this thread's {@link org.apache.tapestry5.services.Environment}.
    260     *
    261     * @since 5.2.0
    262     */
    263    public JavaScriptSupport buildJavaScriptSupport()
    264    {
    265        return environmentalBuilder.build(JavaScriptSupport.class);
    266    }
    267
    268    @Contribute(Dispatcher.class)
    269    @Primary
    270    public static void setupModuleDispatchers(OrderedConfiguration<Dispatcher> configuration,
    271                                              ModuleManager moduleManager,
    272                                              OperationTracker tracker,
    273                                              ResourceStreamer resourceStreamer,
    274                                              PathConstructor pathConstructor,
    275                                              JavaScriptStackSource javaScriptStackSource,
    276                                              JavaScriptStackPathConstructor javaScriptStackPathConstructor,
    277                                              LocalizationSetter localizationSetter,
    278                                              @Symbol(SymbolConstants.MODULE_PATH_PREFIX)
    279                                              String modulePathPrefix,
    280                                              @Symbol(SymbolConstants.ASSET_PATH_PREFIX)
    281                                              String assetPathPrefix)
    282    {
    283        configuration.add("Modules",
    284                new ModuleDispatcher(moduleManager, resourceStreamer, tracker, pathConstructor,
    285                    javaScriptStackSource, javaScriptStackPathConstructor, localizationSetter, modulePathPrefix,
    286                    assetPathPrefix, false),
    287                "after:Asset", "before:ComponentEvent");
    288
    289        configuration.add("ComnpressedModules",
    290                new ModuleDispatcher(moduleManager, resourceStreamer, tracker, pathConstructor,
    291                    javaScriptStackSource, javaScriptStackPathConstructor, localizationSetter, modulePathPrefix,
    292                    assetPathPrefix, true),
    293                "after:Modules", "before:ComponentEvent");
    294    }
    295
    296    /**
    297     * Adds page render filters, each of which provides an {@link org.apache.tapestry5.annotations.Environmental}
    298     * service. Filters
    299     * often provide {@link org.apache.tapestry5.annotations.Environmental} services needed by
    300     * components as they render.
    301     * <dl>
    302     * <dt>JavascriptSupport</dt>
    303     * <dd>Provides {@link JavaScriptSupport}</dd>
    304     * </dl>
    305     */
    306    @Contribute(MarkupRenderer.class)
    307    public void exposeJavaScriptSupportForFullPageRenders(OrderedConfiguration<MarkupRendererFilter> configuration,
    308                                                          final JavaScriptStackSource javascriptStackSource,
    309                                                          final JavaScriptStackPathConstructor javascriptStackPathConstructor,
    310                                                          final Request request)
    311    {
    312
    313        final BooleanHook suppressCoreStylesheetsHook = createSuppressCoreStylesheetHook(request);
    314
    315        MarkupRendererFilter javaScriptSupport = new MarkupRendererFilter()
    316        {
    317            public void renderMarkup(MarkupWriter writer, MarkupRenderer renderer)
    318            {
    319                DocumentLinker linker = environment.peekRequired(DocumentLinker.class);
    320
    321                JavaScriptSupportImpl support = new JavaScriptSupportImpl(linker, javascriptStackSource,
    322                        javascriptStackPathConstructor, suppressCoreStylesheetsHook);
    323
    324                environment.push(JavaScriptSupport.class, support);
    325
    326                renderer.renderMarkup(writer);
    327
    328                environment.pop(JavaScriptSupport.class);
    329
    330                support.commit();
    331            }
    332        };
    333
    334        configuration.add("JavaScriptSupport", javaScriptSupport, "after:DocumentLinker");
    335    }
    336
    337    /**
    338     * Contributes {@link PartialMarkupRendererFilter}s used when rendering a
    339     * partial Ajax response.
    340     * <dl>
    341     * <dt>JavaScriptSupport
    342     * <dd>Provides {@link JavaScriptSupport}</dd>
    343     * </dl>
    344     */
    345    @Contribute(PartialMarkupRenderer.class)
    346    public void exposeJavaScriptSupportForPartialPageRender(OrderedConfiguration<PartialMarkupRendererFilter> configuration,
    347                                                            final JavaScriptStackSource javascriptStackSource,
    348
    349                                                            final JavaScriptStackPathConstructor javascriptStackPathConstructor,
    350
    351                                                            final Request request)
    352    {
    353        final BooleanHook suppressCoreStylesheetsHook = createSuppressCoreStylesheetHook(request);
    354
    355        PartialMarkupRendererFilter javascriptSupport = new PartialMarkupRendererFilter()
    356        {
    357            public void renderMarkup(MarkupWriter writer, JSONObject reply, PartialMarkupRenderer renderer)
    358            {
    359                IdAllocator idAllocator;
    360
    361                if (request.getParameter(InternalConstants.SUPPRESS_NAMESPACED_IDS) == null)
    362                {
    363                    String uid = Long.toHexString(System.nanoTime());
    364
    365                    String namespace = "_" + uid;
    366
    367                    idAllocator = new IdAllocator(namespace);
    368                } else
    369                {
    370                    // When suppressed, work just like normal rendering.
    371                    idAllocator = new IdAllocator();
    372                }
    373
    374                DocumentLinker linker = environment.peekRequired(DocumentLinker.class);
    375
    376                JavaScriptSupportImpl support = new JavaScriptSupportImpl(linker, javascriptStackSource,
    377                        javascriptStackPathConstructor, idAllocator, true, suppressCoreStylesheetsHook);
    378
    379                environment.push(JavaScriptSupport.class, support);
    380
    381                renderer.renderMarkup(writer, reply);
    382
    383                environment.pop(JavaScriptSupport.class);
    384
    385                support.commit();
    386            }
    387        };
    388
    389        configuration.add("JavaScriptSupport", javascriptSupport, "after:DocumentLinker");
    390    }
    391
    392    private BooleanHook createSuppressCoreStylesheetHook(final Request request)
    393    {
    394        return new BooleanHook()
    395        {
    396            @Override
    397            public boolean checkHook()
    398            {
    399                return request.getAttribute(InternalConstants.SUPPRESS_CORE_STYLESHEETS) != null;
    400            }
    401        };
    402    }
    403
    404
    405    @Contribute(ModuleManager.class)
    406    public static void setupBaseModules(MappedConfiguration<String, Object> configuration,
    407                                        @Path("${tapestry.asset.root}/underscore-shim.js")
    408                                        Resource underscoreShim,
    409
    410                                        @Path("${tapestry.asset.root}/jquery-shim.js")
    411                                        Resource jqueryShim,
    412
    413                                        @Path("${tapestry.asset.root}/typeahead.js")
    414                                        Resource typeahead,
    415
    416                                        @Path("${tapestry.asset.root}/moment-2.15.1.js")
    417                                        Resource moment,
    418
    419                                        @Path("${tapestry.asset.root}/bootstrap/js/transition.js")
    420                                        Resource transition,
    421
    422                                        @Path("${tapestry.asset.root}/bootstrap4/js/bootstrap-util.js")
    423                                        Resource bootstrapUtil,
    424
    425                                        Compatibility compatibility)
    426    {
    427        // The underscore shim module allows Underscore to be injected
    428        configuration.add("underscore", new JavaScriptModuleConfiguration(underscoreShim));
    429        configuration.add("jquery", new JavaScriptModuleConfiguration(jqueryShim));
    430
    431        if (compatibility.enabled(Trait.BOOTSTRAP_3))
    432        {
    433            final String[] modules = new String[]{"affix", "alert", "button", "carousel", "collapse", "dropdown", "modal",
    434                    "scrollspy", "tab", "tooltip"};
    435            addBootstrap3Modules(configuration, transition, modules);
    436
    437            Resource popover = transition.forFile("popover.js");
    438
    439            configuration.add("bootstrap/popover", new AMDWrapper(popover).require("bootstrap/tooltip").asJavaScriptModuleConfiguration());
    440        }
    441
    442        if (compatibility.enabled(Trait.BOOTSTRAP_4))
    443        {
    444            configuration.add("bootstrap/bootstrap-util", new JavaScriptModuleConfiguration(bootstrapUtil));
    445            configuration.add("bootstrap/popper", new JavaScriptModuleConfiguration(
    446                    bootstrapUtil.forFile("popper.js")));
    447
    448            for (String name : new String[]{"alert", "button", "carousel", "collapse", "dropdown", "modal",
    449                    "scrollspy", "tab", "tooltip"})
    450            {
    451                Resource lib = bootstrapUtil.forFile(name + ".js");
    452                if (lib.exists())
    453                {
    454                    configuration.add("bootstrap/" + name,
    455                            new JavaScriptModuleConfiguration(lib)
    456                                .dependsOn("bootstrap/bootstrap-util")
    457                                .dependsOn("bootstrap/popper"));
    458                }
    459            }
    460        }
    461
    462        // Just the minimum to have alerts and AJAX validation working when Bootstrap
    463        // is completely disabled
    464        if (!compatibility.enabled(Trait.BOOTSTRAP_3) && !compatibility.enabled(Trait.BOOTSTRAP_4))
    465        {
    466            final String[] modules = new String[]{"alert", "dropdown", "collapse"};
    467            addBootstrap3Modules(configuration, transition, modules);
    468        }
    469
    470        configuration.add("t5/core/typeahead", new JavaScriptModuleConfiguration(typeahead).dependsOn("jquery"));
    471
    472        configuration.add("moment", new JavaScriptModuleConfiguration(moment));
    473
    474    }
    475
    476    private static void addBootstrap3Modules(MappedConfiguration<String, Object> configuration, Resource transition, final String[] modules) {
    477        configuration.add("bootstrap/transition", new AMDWrapper(transition).require("jquery", "$").asJavaScriptModuleConfiguration());
    478
    479        for (String name : modules)
    480        {
    481            Resource lib = transition.forFile(name + ".js");
    482
    483            configuration.add("bootstrap/" + name, new AMDWrapper(lib).require("bootstrap/transition").asJavaScriptModuleConfiguration());
    484        }
    485    }
    486
    487    @Contribute(SymbolProvider.class)
    488    @FactoryDefaults
    489    public static void setupFactoryDefaults(MappedConfiguration<String, Object> configuration)
    490    {
    491        configuration.add(SymbolConstants.JAVASCRIPT_INFRASTRUCTURE_PROVIDER, "prototype");
    492        configuration.add(SymbolConstants.MODULE_PATH_PREFIX, "modules");
    493    }
    494
    495    @Contribute(ModuleManager.class)
    496    public static void setupFoundationFramework(MappedConfiguration<String, Object> configuration,
    497                                                @Symbol(SymbolConstants.JAVASCRIPT_INFRASTRUCTURE_PROVIDER)
    498                                                String provider,
    499                                                @Path("classpath:org/apache/tapestry5/t5-core-dom-prototype.js")
    500                                                Resource domPrototype,
    501                                                @Path("classpath:org/apache/tapestry5/t5-core-dom-jquery.js")
    502                                                Resource domJQuery)
    503    {
    504        if (provider.equals("prototype"))
    505        {
    506            configuration.add("t5/core/dom", new JavaScriptModuleConfiguration(domPrototype));
    507        }
    508
    509        if (provider.equals("jquery"))
    510        {
    511            configuration.add("t5/core/dom", new JavaScriptModuleConfiguration(domJQuery));
    512        }
    513
    514        // If someone wants to support a different infrastructure, they should set the provider symbol to some other value
    515        // and contribute their own version of the t5/core/dom module.
    516    }
    517
    518    @Contribute(ModuleManager.class)
    519    public static void setupApplicationCatalogModules(MappedConfiguration<String, Object> configuration,
    520                                                      LocalizationSetter localizationSetter,
    521                                                      ComponentMessagesSource messagesSource,
    522                                                      ResourceChangeTracker resourceChangeTracker,
    523                                                      @Symbol(SymbolConstants.COMPACT_JSON) boolean compactJSON)
    524    {
    525        for (Locale locale : localizationSetter.getSupportedLocales())
    526        {
    527            MessageCatalogResource resource = new MessageCatalogResource(locale, messagesSource, resourceChangeTracker, compactJSON);
    528
    529            configuration.add("t5/core/messages/" + locale.toString(), new JavaScriptModuleConfiguration(resource));
    530        }
    531    }
    532
    533    /**
    534     * Contributes 'ConfigureHTMLElement', which writes the attributes into the HTML tag to describe locale, etc.
    535     * Contributes 'AddBrowserCompatibilityStyles', which writes {@code <style/>} elements into the {@code <head/>}
    536     * element that modifies the page loading mask to work on IE 8 and IE 9.
    537     */
    538    @Contribute(MarkupRenderer.class)
    539    public static void prepareHTMLPageOnRender(OrderedConfiguration<MarkupRendererFilter> configuration)
    540    {
    541        configuration.addInstance("ConfigureHTMLElement", ConfigureHTMLElementFilter.class);
    542        configuration.add("AddBrowserCompatibilityStyles", new AddBrowserCompatibilityStyles());
    543    }
    544
    545    /**
    546     * Overrides the {@link Glyphicon} component with {@link FontAwesomeIcon} if Bootstrap 3
    547     * isn't enabled.
    548     * @see Trait#BOOTSTRAP_3
    549     * @see Compatibility
    550     */
    551    @Contribute(ComponentOverride.class)
    552    public static void overrideGlyphiconWithFontAwesomeIfNeeded(MappedConfiguration<Class, Class> configuration,
    553            Compatibility compatibility)
    554    {
    555        if (!compatibility.enabled(Trait.BOOTSTRAP_3))
    556        {
    557            configuration.add(Glyphicon.class, FontAwesomeIcon.class);
    558        }
    559    }
    560
    561}