    001    // Copyright 2010, 2011 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
    015    package org.apache.tapestry5.internal.services.ajax;
    016
    017    import java.util.List;
    018    import java.util.Map;
    019    import java.util.Set;
    020
    021    import org.apache.tapestry5.Asset;
    022    import org.apache.tapestry5.ComponentResources;
    023    import org.apache.tapestry5.FieldFocusPriority;
    024    import org.apache.tapestry5.func.F;
    025    import org.apache.tapestry5.func.Worker;
    026    import org.apache.tapestry5.internal.InternalConstants;
    027    import org.apache.tapestry5.internal.services.DocumentLinker;
    028    import org.apache.tapestry5.internal.services.javascript.JavaScriptStackPathConstructor;
    029    import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
    030    import org.apache.tapestry5.ioc.internal.util.InternalUtils;
    031    import org.apache.tapestry5.ioc.util.IdAllocator;
    032    import org.apache.tapestry5.json.JSONArray;
    033    import org.apache.tapestry5.json.JSONObject;
    034    import org.apache.tapestry5.services.javascript.InitializationPriority;
    035    import org.apache.tapestry5.services.javascript.JavaScriptStack;
    036    import org.apache.tapestry5.services.javascript.JavaScriptStackSource;
    037    import org.apache.tapestry5.services.javascript.JavaScriptSupport;
    038    import org.apache.tapestry5.services.javascript.StylesheetLink;
    039
    040    public class JavaScriptSupportImpl implements JavaScriptSupport
    041    {
    042        private final IdAllocator idAllocator;
    043
    044        private final DocumentLinker linker;
    045
    046        // Using a Map as a case-insensitive set of stack names.
    047
    048        private final Map<String, Boolean> addedStacks = CollectionFactory.newCaseInsensitiveMap();
    049
    050        private final List<String> stackLibraries = CollectionFactory.newList();
    051
    052        private final List<String> otherLibraries = CollectionFactory.newList();
    053
    054        private final Set<String> importedStylesheetURLs = CollectionFactory.newSet();
    055
    056        private final List<StylesheetLink> stylesheetLinks = CollectionFactory.newList();
    057
    058        private final Map<InitializationPriority, JSONObject> inits = CollectionFactory.newMap();
    059
    060        private final JavaScriptStackSource javascriptStackSource;
    061
    062        private final JavaScriptStackPathConstructor stackPathConstructor;
    063
    064        private final boolean partialMode;
    065
    066        private FieldFocusPriority focusPriority;
    067
    068        private String focusFieldId;
    069
    070        public JavaScriptSupportImpl(DocumentLinker linker, JavaScriptStackSource javascriptStackSource,
    071                JavaScriptStackPathConstructor stackPathConstructor)
    072        {
    073            this(linker, javascriptStackSource, stackPathConstructor, new IdAllocator(), false);
    074        }
    075
    076        /**
    077         * @param linker
    078         *            responsible for assembling all the information gathered by JavaScriptSupport and
    079         *            attaching it to the Document (for a full page render) or to the JSON response (in a partial render)
    080         * @param javascriptStackSource
    081         *            source of information about {@link JavaScriptStack}s, used when handling the import
    082         *            of libraries and stacks (often, to handle transitive dependencies)
    083         * @param stackPathConstructor
    084         *            encapsulates the knowledge of how to represent a stack (which may be converted
    085         *            from a series of JavaScript libraries into a single virtual JavaScript library)
    086         * @param idAllocator
    087         *            used when allocating unique ids (it is usually pre-initialized in an Ajax request to ensure
    088         *            that newly allocated ids do not conflict with previous renders and partial updates)
    089         * @param partialMode
    090         *            if true, then the JSS configures itself for a partial page render (part of an Ajax request)
    091         *            which automatically assumes the "core" library has been added (to the original page render)
    092         *            and makes other minor changes to behavior.
    093         */
    094        public JavaScriptSupportImpl(DocumentLinker linker, JavaScriptStackSource javascriptStackSource,
    095                JavaScriptStackPathConstructor stackPathConstructor, IdAllocator idAllocator, boolean partialMode)
    096        {
    097            this.linker = linker;
    098            this.idAllocator = idAllocator;
    099            this.javascriptStackSource = javascriptStackSource;
    100            this.stackPathConstructor = stackPathConstructor;
    101            this.partialMode = partialMode;
    102
    103            // In partial mode, assume that the infrastructure stack is already present
    104            // (from the original page render).
    105
    106            if (partialMode)
    107                addedStacks.put(InternalConstants.CORE_STACK_NAME, true);
    108        }
    109
    110        public void commit()
    111        {
    112            if (focusFieldId != null)
    113                addInitializerCall("activate", focusFieldId);
    114
    115            F.flow(stylesheetLinks).each(new Worker<StylesheetLink>()
    116            {
    117                public void work(StylesheetLink value)
    118                {
    119                    linker.addStylesheetLink(value);
    120                }
    121            });
    122
    123            Worker<String> linkLibrary = new Worker<String>()
    124            {
    125                public void work(String value)
    126                {
    127                    linker.addScriptLink(value);
    128                }
    129            };
    130
    131            F.flow(stackLibraries).each(linkLibrary);
    132            F.flow(otherLibraries).each(linkLibrary);
    133
    134            for (InitializationPriority p : InitializationPriority.values())
    135            {
    136                JSONObject init = inits.get(p);
    137
    138                if (init != null)
    139                    linker.setInitialization(p, init);
    140            }
    141        }
    142
    143        public void addInitializerCall(InitializationPriority priority, String functionName, JSONObject parameter)
    144        {
    145            storeInitializerCall(priority, functionName, parameter);
    146        }
    147
    148        public void addInitializerCall(String functionName, JSONArray parameter)
    149        {
    150            storeInitializerCall(InitializationPriority.NORMAL, functionName, parameter);
    151        }
    152
    153        public void addInitializerCall(InitializationPriority priority, String functionName, JSONArray parameter)
    154        {
    155            storeInitializerCall(priority, functionName, parameter);
    156        }
    157
    158        private void storeInitializerCall(InitializationPriority priority, String functionName, Object parameter)
    159        {
    160            assert priority != null;
    161            assert parameter != null;
    162            assert InternalUtils.isNonBlank(functionName);
    163            addCoreStackIfNeeded();
    164
    165            JSONObject init = inits.get(priority);
    166
    167            if (init == null)
    168            {
    169                init = new JSONObject();
    170                inits.put(priority, init);
    171            }
    172
    173            JSONArray invocations = init.has(functionName) ? init.getJSONArray(functionName) : null;
    174
    175            if (invocations == null)
    176            {
    177                invocations = new JSONArray();
    178                init.put(functionName, invocations);
    179            }
    180
    181            invocations.put(parameter);
    182        }
    183
    184        public void addInitializerCall(String functionName, JSONObject parameter)
    185        {
    186            addInitializerCall(InitializationPriority.NORMAL, functionName, parameter);
    187        }
    188
    189        public void addInitializerCall(InitializationPriority priority, String functionName, String parameter)
    190        {
    191            storeInitializerCall(priority, functionName, parameter);
    192        }
    193
    194        public void addInitializerCall(String functionName, String parameter)
    195        {
    196            addInitializerCall(InitializationPriority.NORMAL, functionName, parameter);
    197        }
    198
    199        public void addScript(InitializationPriority priority, String format, Object... arguments)
    200        {
    201            assert priority != null;
    202            assert InternalUtils.isNonBlank(format);
    203
    204            addCoreStackIfNeeded();
    205
    206            String newScript = arguments.length == 0 ? format : String.format(format, arguments);
    207
    208            if (partialMode)
    209            {
    210                addInitializerCall(priority, "evalScript", newScript);
    211            }
    212            else
    213            {
    214                linker.addScript(priority, newScript);
    215            }
    216        }
    217
    218        public void addScript(String format, Object... arguments)
    219        {
    220            addScript(InitializationPriority.NORMAL, format, arguments);
    221        }
    222
    223        public String allocateClientId(ComponentResources resources)
    224        {
    225            return allocateClientId(resources.getId());
    226        }
    227
    228        public String allocateClientId(String id)
    229        {
    230            return idAllocator.allocateId(id);
    231        }
    232
    233        public void importJavaScriptLibrary(Asset asset)
    234        {
    235            assert asset != null;
    236
    237            importJavaScriptLibrary(asset.toClientURL());
    238        }
    239
    240        public void importJavaScriptLibrary(String libraryURL)
    241        {
    242            addCoreStackIfNeeded();
    243
    244            String stackName = findStackForLibrary(libraryURL);
    245
    246            if (stackName != null)
    247            {
    248                importStack(stackName);
    249                return;
    250            }
    251
    252            if (otherLibraries.contains(libraryURL))
    253                return;
    254
    255            otherLibraries.add(libraryURL);
    256        }
    257
    258        private Map<String, String> libraryURLToStackName;
    259
    260        /**
    261         * Locates the name of the stack that includes the library URL. Returns the stack,
    262         * or null if the library is free-standing.
    263         */
    264        private String findStackForLibrary(String libraryURL)
    265        {
    266            return getLibraryURLToStackName().get(libraryURL);
    267        }
    268
    269        private Map<String, String> getLibraryURLToStackName()
    270        {
    271            if (libraryURLToStackName == null)
    272            {
    273                libraryURLToStackName = CollectionFactory.newMap();
    274
    275                for (String stackName : javascriptStackSource.getStackNames())
    276                {
    277                    for (Asset library : javascriptStackSource.getStack(stackName).getJavaScriptLibraries())
    278                    {
    279                        libraryURLToStackName.put(library.toClientURL(), stackName);
    280                    }
    281                }
    282            }
    283
    284            return libraryURLToStackName;
    285        }
    286
    287        private void addCoreStackIfNeeded()
    288        {
    289            addAssetsFromStack(InternalConstants.CORE_STACK_NAME);
    290        }
    291
    292        private void addAssetsFromStack(String stackName)
    293        {
    294            if (addedStacks.containsKey(stackName))
    295                return;
    296
    297            JavaScriptStack stack = javascriptStackSource.getStack(stackName);
    298
    299            for (String dependentStackname : stack.getStacks())
    300            {
    301                addAssetsFromStack(dependentStackname);
    302            }
    303
    304            stackLibraries.addAll(stackPathConstructor.constructPathsForJavaScriptStack(stackName));
    305
    306            stylesheetLinks.addAll(stack.getStylesheets());
    307
    308            addedStacks.put(stackName, true);
    309
    310            String initialization = stack.getInitialization();
    311
    312            if (initialization != null)
    313                addScript(InitializationPriority.IMMEDIATE, initialization);
    314        }
    315
    316        public void importStylesheet(Asset stylesheet)
    317        {
    318            assert stylesheet != null;
    319            importStylesheet(new StylesheetLink(stylesheet));
    320        }
    321
    322        public void importStylesheet(StylesheetLink stylesheetLink)
    323        {
    324            assert stylesheetLink != null;
    325            String stylesheetURL = stylesheetLink.getURL();
    326
    327            if (importedStylesheetURLs.contains(stylesheetURL))
    328                return;
    329
    330            importedStylesheetURLs.add(stylesheetURL);
    331
    332            stylesheetLinks.add(stylesheetLink);
    333        }
    334
    335        public void importStack(String stackName)
    336        {
    337            assert InternalUtils.isNonBlank(stackName);
    338            addCoreStackIfNeeded();
    339
    340            addAssetsFromStack(stackName);
    341        }
    342
    343        public void autofocus(FieldFocusPriority priority, String fieldId)
    344        {
    345            assert priority != null;
    346            assert InternalUtils.isNonBlank(fieldId);
    347
    348            if (focusFieldId == null || priority.compareTo(focusPriority) > 0)
    349            {
    350                this.focusPriority = priority;
    351                focusFieldId = fieldId;
    352            }
    353        }
    354
    355    }