The magic of Extension Points: the csui-ext module

Have you ever thought of the magic of extension points in smartUI?

What is an extension point?

This are the dynamic configuration points of smartUI. Calling the config URL in smartUI, you will see all extension points with all configured extension modules in the current smarUI. Normally you’ll add your modules in the file <your-base-requirepath>-extensions.json in your projectstructure build by yo.

All of these modules indicated by these json files are loaded at initialization and form the dynamic smartUI system.

This is an example of an actual configuration in a Content Server System :

Extension Point

As you can see in the configuration, an extension point allows the extension of the extension point with several definable extension modules. Ok,this sounds like a typical lawyer sentence.

In plain english:

This technique allows you to extend the capabilities of a base module (the extension point) with the extension modules listed.

How to add your own extension point?

Use cs-ext as requirejs extensions. This is part of the SDK (/lib/src/csui/utils/load-extensions). There is the module and a very brief short documentation.

If you want more infos on custom requirejs modules, refer to the example of a conditional module,

This is a require.js module like the one which I introduced for conditional loading in Febr 2020. cs-ext is part of the smartUI core. If you want to modify the cs-ext, then use the easy way of loading an requirejs extension described in the conditional loading article.

Overview of cs-ext (in the sdk)

Lets take a look at cs-ext. But if you are more interested in the usage of cs-ext, skip the overview and go to the next chapter “Usage of cs-ext” below.

define(['module', 'csui/lib/underscore'], function (module, _) {
'use strict';

The cs-ext requires only module and underscore.

var config = _.defaults({}, module.config(), {
ignoreRequireErrors: false,
modulePrefixesToRetry: [
'csui', 'classifications', 'esoc', 'wiki', 'workflow', 'webreports'
]
});

This is the standard entry to add sme vars into the configuration. Interesting is the list of modulePrefixesToRetry, includes all modules to retry a reload, if the extension point loading fails.

function handleSuccess(onLoad, parameters) {
onLoad(Array.prototype.slice.call(parameters));
}

The success handler, handles the callback

function handleError(error, onLoad) {
if (config.ignoreRequireErrors) {
console.error(error);
console.warn('Loading extensions of "' + name +
'" failed:', error.requireModules);
onLoad([]);
} else {
onLoad.error(error);
}
}

The error Handler

function retryLoading(require, name, modules, onLoad, firstError) {
var droppedModules = [],
selectedModules = .filter(modules, function (module) { var slash = module.indexOf('/'); if (slash < 0 || .contains(config.modulePrefixesToRetry,
module.substring(0, slash))) {
return true;
} else {
droppedModules.push(module);
}
});
if (selectedModules.length && droppedModules.length) {
console.error(firstError);
console.warn('Loading extensions of "' + name +
'" failed:', firstError.requireModules);
console.warn('Dropping extensions:', droppedModules);
console.warn('Retrying extensions:', selectedModules);
require(selectedModules,
function () {
handleSuccess(onLoad, arguments);
},
function (error) {
handleError(error, onLoad);
});
return true;
}
}

The retryLoading handler. Demonstrates how the reload a module, if there is an error condition. Only the modules with the prefixes listed in modulesPrefixesToRetry (see above) will be reloaded.

return {
load: function (name, require, onLoad, runtimeConfig) {
if (runtimeConfig.isBuild) {
onLoad();
} else {
var moduleConfig = runtimeConfig.config[name] || {},
modules = moduleConfig.extensions;
if (modules) {
if (!.isArray(modules)) { modules = Array.prototype.concat.apply([], .values(modules));
}
if (modules.length) {
require(modules,
function () {
handleSuccess(onLoad, arguments);
},
function (error) {
if (!retryLoading(require, name, modules, onLoad, error)) {
handleError(error, onLoad);
}
});
} else {
onLoad([]);
}
} else {
onLoad();
}
}
}
};

The main method is simply returned as js object.

All listed modules in the extension point list will be loaded and executed before the callback (the carrier module with the cs-ext entry) will be executed.

This may sound quite theoretical. Lets use cs-ext.

Usage of cs-ext

  • Select the module which you want to amend with an extension point. Lets use the csui/controls/table/cells/cell.factory (MUST exist) as an example.
  • Configure the proper extension in the project json like

  • Open the cell factory and examine the source code.

Notice the csui-ext! line at the end of the define-module list. This will search the require.config entries to get a list of the extension modules for this extension point. All listed modules (here hello.view) will be loaded and executed before the callback (stated in the line csui-ext) will be executed.

Back in the callback , the _.extend function extends the prototype of the cell factory by the methods “hasCellViewByOtherKey” and “getCellView”.

Summary

To make a custom extensible module (with extension point), add the csui-ext requirejs extension to your module and set the modules to be loaded in the appropriate json extension file.

Then point to your extensible module from your widget, and you are done. Then your module is configurable and extendable by using the csui-ext util module.

If you want to extend existing modules, you’ll have to spawn the widgets from the library to reflect your new module.