Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migration of unmigrated content due to installation of a new plugin

The BA-Server's client-side Javascript is making the transition from plain Javascript files to AMD-compatible script modules. This began with the 4.5 release and has continued in 4.8 where we've also taken the opportunity to update to RequireJS 2.0. The following describes the system as it exists in 4.8.

What is AMD?

First-off, you have to understand AMD modules. Asynchronous Module Definitions are JavaScript files that declare a module name and optionally list their dependencies. These script files are not included directly in the HTML source, rather they're loaded using the RequireJS script. Once a call has be made to load a module, it's retrieved from the server based on the URL mappings, dependencies are computed and retrieved, and baring any unresolved dependencies it is finally loaded and returned to the requestor.

...

In practice we omit the module name from our script files as the system will infer the module name based on how the script is loaded.

Tying together AMD and Platform Plugins

As plugins become more and more capable we've started sharing Javascript code between them. This is most commonly done by providing Javascript from one plugin to extend the functionality of another. In other cases a plugin may have a hard dependency upon script provided by another. This cross-plugin loading is accomplished through RequireJS and setup by module path mappings.

Module Path Mappings

By default RequireJS will try to load modules relative to the current URL. So if the current page is /pentaho/foo/index.html, a call to load the "Utils" module will have the system try to load it from /pentaho/foo/Utils.js. This is not very valuable to us so we've adopted a namespaced approach. Each plugin is able to define root namespaces and provide URL path mappings for them. For instance the CDF plugin defines modules with the "cdf" namespace like "cdf/CoreComponents.js". It also provides a configuration for the RequireJS system mapping the "cdf" namespace like so:

...

Note that CONTEXT_PATH is provided by the webcontext.js file and is supplies the webapp name ("/pentaho/" by default). So when a user makes a call to require "cdf/CoreComponents" RequireJS will load it from "/pentaho/content/pentaho-cdf/js". This provides from a greater degree of abstraction in where scripts are loaded.

Providing RequireJS Configurations

To understand how plugins supply their configuration, you have to understand the webcontext.js Filter and External Resource definitions.

Anchor
webcontext
webcontext

webcontext.js

Almost every page supplied by our server will include a webcontext.js script tag. You won't find a webcontext.js file anywhere in the system, the content is actually supplied by an HTTP Servlet Filter. It's writes out several things which are critical for the execution of most of our client-side Javascript. Most calls to webcontext.js also include an additional "context" parameter telling it what area of the Platform it's intended for. This context is tied into the External Resources system described later.

WebContext performs writes out the following in order:

  1. The global CONTEXT_PATH variable holding the webapp name if any.
  2. The global FULLY_QUALIFIED_URL variable which holds the full URL of the server.
  3. The base requireCfg configuration Object which is extended by plugins.
  4. All External Resource scripts defined with the "requirejs" context. This is where plugins configure the RequireJS paths!
  5. The SESSION_LOCALE variable containing the computed locale for the request
  6. It then makes the call to configure the RequireJS system based on the "requireCfg" objectThe require.js and require-cfg.js files to initialize the RequireJS ystem.
  7. Finally it loads the remaining External Resources entries with the "global" context and those matching contexts the current context for the request ("dashboards", "analyzer", etc.)

...

You'll notice several scripts are injected before the inclusion of require.js and require-cfg.js. These scripts are provided by the plugins and contain code extending the RequireJS configuration object. These As mentioned, they are provided through the External Resource system.

External Resources

Plugins can provide scripts and CSS to be loaded in other areas of the platform by including entries in their plugin.xml. For instance, Analyzer provides it's Dashboard Widget and it's configuration for RequireJS by defining the following in it's plugin.xml:

...

You'll notice the name of Analyzer RequireJS config script and all of the others included in the sample webcontext.js end with "require-js-cfg.js". This is important as the Spring Security white-list is allowing all requests ending with this to return un-authenticated. Analyzer's RequireJS configuration file are is pretty for our pluginstypical:

Code Block
javascript
javascript
if(document.location.href.indexOf("debug=true") > 0){
	requireCfg['paths']['analyzer'] = CONTEXT_PATH+'content/analyzer/scripts';
} else {
	requireCfg['paths']['analyzer'] = CONTEXT_PATH+'content/analyzer/scripts/compressed';
}

It adds an "analyzer" root namespace to the "paths" entry. This is what routes requests for modules beginning with "analyzer/" to the appropriate URL location. The check for "debug=true" is changing the location where the system loads the scripts to allow developers to debug the system.so that the uncompressed files are available for debugging.

Helpful links!

https://github.com/amdjs/amdjs-api/wiki/AMD
http://requirejs.org/