Search code examples
javascriptjsfprimefacesomnifacesprimefaces-extensions

Determine JSF Library Resource Using Javascript


I am a developer on the Primefaces Extensions project and I use a core Primefaces JS function to get a location of a resource in Javascript for the DocumentViewer and CKEditor components. The getFacesResource() function is in PrimeFaces core.js and looks like this..

   /**
     * Builds a resource URL for given parameters.
     *
     * @param {string} name The name of the resource. For example: primefaces.js
     * @param {string} library The library of the resource. For example: primefaces
     * @param {string} version The version of the library. For example: 5.1
     * @returns {string} The resource URL.
     */
getFacesResource : function(name, library, version) {
    // just get sure - name shoudln't start with a slash
    if (name.indexOf('/') === 0)
    {
        name = name.substring(1, name.length);
    }

    var scriptURI = $('script[src*="/' + PrimeFaces.RESOURCE_IDENTIFIER + '/core.js"]').attr('src');
    // portlet
    if (!scriptURI) {
        scriptURI = $('script[src*="' + PrimeFaces.RESOURCE_IDENTIFIER + '=core.js"]').attr('src');
    }

    scriptURI = scriptURI.replace('core.js', name);

What it does is it finds the "core.js" script in the DOM and then removes core.js so it has the proper location for a new URL you construct for looking up a resource.

Why do we need to do this?

The CKEDitor and DocumentViewer components are complex components that load lots of plugins, language files, and more. We can't modify the core JS files that these plugins use else every time we upgrade we would have to edit their core source. So for DocumentViewer to load a language pack the library value has "documentviewer/locale/en-GB.locale.txt" to load the English language pack for PDF.js that DocumentViewer uses. That however needs to become a real resource so PrimeFaces.getFacesResource('documentviewer/locale/en-GB.locale.txt'); turns the URL into a proper resource the PDF.JS code can find for example:

https://www.primefaces.org/showcase-ext/javax.faces.resource/documentviewer/locale/en-GB.locale.txt.jsf?ln=primefaces-extensions&v=6.2.5

It handles knowing the full URL, appending the library version and knowing whether the current server is serving .jsf or .xhtml extension etc.

Problem:

I am using OmniFaces CombinedResourceHandler which takes all of your JS files and creates 1 single script from all the JS files it finds. So 10 different JS files becomes now 1 resource for performance like this...

/javax.faces.resource/XXX.js.xhtml?ln=omnifaces.combined&v=1532916484000

Now this breaks the core PrimeFaces code because it can't lookup the core.js because core.js does not exist on the page anymore.

I tried excluding core.js from being combined using this switch..

<context-param>
    <param-name>org.omnifaces.COMBINED_RESOURCE_HANDLER_EXCLUDED_RESOURCES</param-name>
    <param-value>primefaces:core.js</param-value>
</context-param>

That loads PrimeFaces core.js after the omnifaces.combined.js which breaks the whole page. To confirm the output is this...

<script src="/primeext-showcase/javax.faces.resource/XXX.js.jsf?ln=omnifaces.combined&amp;v=1533319992000"></script>
<script src="/primeext-showcase/javax.faces.resource/core.js.jsf?ln=primefaces&amp;v=6.2">

So my question is how with pure JavaScript can I replace/fix the PrimeFaces.getFacesResource() JS function to work whether OmniFaces has combined the scripts or not?


Solution

  • I was able to rewrite the method to not assume a core.js was found on the page using regular expressions. It now works when PrimeFaces is in normal mode and will work if using the OmniFaces CombinedResourceHandler. I will be submitting this as a patch to PrimeFaces.

    Here is the final working method:

    /**
        * Builds a resource URL for given parameters.
        * 
        * @param {string}
        *        name The name of the resource. For example: primefaces.js
        * @param {string}
        *        library The library of the resource. For example: primefaces
        * @param {string}
        *        version The version of the library. For example: 5.1
        * @returns {string} The resource URL.
        */
       getFacesResource : function(name, library, version) {
          // just get sure - name shoudln't start with a slash
          if (name.indexOf('/') === 0) {
             name = name.substring(1, name.length);
          }
    
          // find any JS served JSF resource
          var scriptURI = $('script[src*="/' + PrimeFaces.RESOURCE_IDENTIFIER + '/"]').first().attr('src');
          // portlet
          if (!scriptURI) {
            scriptURI = $('script[src*="' + PrimeFaces.RESOURCE_IDENTIFIER + '="]').first().attr('src');
          }
    
          // find script...normal is '/core.js' and portlets are '=core.js'
          var scriptRegex = new RegExp('\\/' + PrimeFaces.RESOURCE_IDENTIFIER + '(\\/|=)(.*?)\\.js');
    
          // find script to replace e.g. 'core.js'
          var currentScriptName = scriptRegex.exec(scriptURI)[2] + '.js';
    
          // replace core.js with our custom name
          scriptURI = scriptURI.replace(currentScriptName, name);
    
          // find the library like ln=primefaces
          var libraryRegex = new RegExp('[?&]([^&=]*)ln=(.*?)(&|$)');
    
          // find library to replace e.g. 'ln=primefaces'
          var currentLibraryName = 'ln=' + libraryRegex.exec(scriptURI)[2];
    
          // In a portlet environment, url parameters may be namespaced.
          var namespace = '';
          var urlParametersAreNamespaced = !(scriptURI.indexOf('?' + currentLibraryName) > -1 || scriptURI.indexOf('&'
                + currentLibraryName) > -1);
    
          if (urlParametersAreNamespaced) {
             namespace = new RegExp('[?&]([^&=]+)' + currentLibraryName + '($|&)').exec(scriptURI)[1];
          }
    
          // If the parameters are namespaced, the namespace must be included
          // when replacing parameters.
          scriptURI = scriptURI.replace(namespace + currentLibraryName, namespace + 'ln=' + library);
    
          if (version) {
             var extractedVersion = new RegExp('[?&]' + namespace + 'v=([^&]*)').exec(scriptURI)[1];
             scriptURI = scriptURI.replace(namespace + 'v=' + extractedVersion, namespace + 'v=' + version);
          }
    
          var prefix = window.location.protocol + '//' + window.location.host;
          return scriptURI.indexOf(prefix) >= 0 ? scriptURI : prefix + scriptURI;
       },