AMD JS Modules in KenedoViews

What is this article for

In this article you learn how to load JavaScript files in your custom KenedoViews views and have init functions execute once your view gets rendered.

Intro

In every KenedoView PHP class you define which AMD modules need to be loaded and which of their methods shall execute. The framework will read this information and handle loading and executing methods. The library used by the framework is require.js.

Concept

We made Views responsible to decide what JS to load and which functions to execute. There are two KenedoView methods where you specify AMD modules that need loading and (optionally) names of their functions that need executing.

How is it done

There are two KenedoView methods that handle AMD modules

  • getJsInitCallsOnce()

  • getJsInitCallsEach()

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class ConfigboxViewMyCustomView extends KenedoView { function getJsInitCallsOnce() { // General init calls need to be included $calls = parent::getJsInitCallsOnce(); // Here we add our own $calls[] = 'configbox/configurator::initConfiguratorPage'; return $calls; } function getJsInitCallsEach() { // General init calls need to be included $calls = parent::getJsInitCallsEach(); // Here we add our own $calls[] = 'configbox/configurator::initConfiguratorPageEach'; return $calls; } }

Each return an array of strings. Each string has a module ID, optionally followed by the name of an init function. The framework will

  1. read the module IDs and init function names of each rendered view

  2. require the modules

  3. execute the init functions

We will get to the specifics about module IDs and init functions later.

What's the difference between Once and Each? 

  • getJsInitCallsOnce:
    The framework will call these JS functions once per page load. Useful for registering delegated event handlers.

  • getJsInitCallsEach:
    The framework will call these JS functions on page load and each time this view gets injected dynamically during user interactions. Useful for initialising Chosen drop downs, Bootstrap tooltips etc.

Module paths

There are various require.js paths defined which map module IDs to JS files.

Examples:

configbox/server loads the file {application assets folder}/javascript/server.js

configbox/custom/mymodule loads the file {customization assets folder}/javascript/mymodule.js

Later in this article you find a list of built-in AMD modules with useful methods.

Init function names

Per convention, CB AMD modules do not execute any code when they’re loaded. They only return a JS object. With the settings in the view’s getJsInitCallsOnce() and ..Each() you can let the framework call one if the module’s methods:

Example of a getJsInitCallsOnce function and AMD module to match:

1 2 3 4 5 6 7 8 9 10 11 12 13 function getJsInitCallsOnce() { // Always return calls of the base class for standard JS calls $calls = parent::getJsInitCallsOnce(); // This will load module 'someModule', but not execute any of its methods $calls[] = 'configbox/custom/someModule'; // Adding ::initMethod, makes the framework load the method and exectute 'initMethod' $calls[] = 'configbox/custom/someModule::initMethod'; return $calls; }

Module in {customization assets folder}/javascript/someModule.js

1 2 3 4 5 6 7 define([], function() { return { initMethod: function() { // doing things } } }

Important when you inject HTML to your page

If your code injects KenedoView HTML during the user’s interactions, best use configbox/server.injectHtml(). This method handles HTML injection and triggering the framework’s check for initEach calls.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 cbrequire(['cbj', 'configbox/server'], function($, server) { let target = $('.target-div'); let controller = 'mycontroller'; let task = 'returnMyHtml'; let data = { "id": 1 } let callback = function() { console.log('HTML injected'); } server.injectHtml(target, controller, task, data, callback); });

Once the HTML is injected (and if the HTML is a KenedoView), then any CSS and AMD initEach calls will be made automatically.

Alternatively you can trigger the event ‘cbViewInjected’ on the document after the HTML has been injected:

1 2 3 cbrequire(['cbj'], function($) { $(document).trigger('cbViewInjected'); });

Creating an AMD module

An AMD module is a regular JS file with some structure.

Built-in Javascript goes to {application assets folder}/javascript/, customisation code goes to {customization assets folder}/javascript/

In this example, we add a module called 'moduleName' in our customisation folder. The module ID configbox/custom/moduleName comes together because configbox/custom is a path (defined in ConfigBox's built-in main.js) and maps to the custom javascript folder.  

Module structure

1 2 3 4 5 6 7 8 9 define(['cbj', 'configbox/server'], function(cbj, server) { var module = { initMethod: function() { // Your init function code } }; return module; });

 

define(..): This is a require.js function. The first parameter is your dependencies. It is an array of module IDs that your module needs). require.js will load these for you and call the second parameter's callback function once all dependencies are loaded.

function(cbj, server): This is the callback function mentioned before. We added the dependencies cbj and configbox/server, so require.js supplies two arguments to our callback (cbj is ConfigBox' jQuery). In that callback we will return our own module object. Mind that not all modules are designed to return an object (or anything), but it's the common way of doing things.

var module = {..}: Remember the parameters cbj and server of our callback? They are the module objects we got from our dependencies. Our module variable is the module object that our module returns when it gets required.

initMethod: function() {..}: This is one of the functions our module object will contain when it gets required by others. 

return module: This makes our module return our module object. Whatever our callback function returns is the module's object (can be anything, but typically it's an object).

Loading an AMD module in your KenedoView

We take above mentioned module as example and show you the code in your KenedoView. This loads the custom module called 'moduleName' from the customisation folder and makes the frontend call its method 'initMethod' once the file was loaded.

The module ID is configbox/custom/moduleName because configbox/custom/ is a path (defined in CB's main.js file) which points to the custom JavaScript folder.

1 2 3 4 5 6 7 8 // In your KenedoView sub class function getJsInitCallsOnce() { // Always get calls of the base class for standard JS calls $calls = parent::getJsInitCallsOnce(); // Add one or more calls for your view $calls[] = 'configbox/custom/moduleName::initMethod'; return $calls; }

Requiring other modules during runtime in your module

You can define dependencies in your define call, but if you need a module only after certain rare user interactions, then you can require a module during runtime.

1 2 3 4 5 6 7 8 9 10 var someFunction = function() { // do stuff // require a module cbrequire(['configbox/server', 'configbox/customerForm'], function(server, customerForm) { // do stuff with server and customerForm }); // Mind issues with asynchronous loading. Execution goes on after the cbrequire call. }

The interesting function is cbrequire. It is basically require.js' require function, but used in ConfigBox' own context. This is for minimising interoperability issues if the site has other software using require.js.

The parameters of cbrequire are the same as for the module's define call. First define dependencies, then have a callback that receives the module objects. 

Important note: Mind that script execution goes on after your cbrequire call. You must accommodate for that in your program flow.

Paths and module names

You may wonder why a module ID like configbox/server makes require.js find /assets/javascript/server.min.js. In ConfigBox's main.js, we got paths defined to make requiring modules easier. See the following for how to access the various modules:

  • kenedo: gives you the Kenedo module in appDir/assets/javascript/kenedo/assets/javascript/kenedo.min

  • configbox: is just a folder path leading you to appDir/assets/javascript

  • cbj: jQuery

  • cbj.ui: jQuery UI

  • cbj.bootstrap: Bootstrap 4.6

  • cbj.chosen: Chosen (jQuery plugin)

  • tinyMCE: tinyMCE

  • codemirror: CodeMirror (mind requiring additional code style modules)

There is more, see the {appAssetsDir}/main.js file (var configuration.paths) for the up-to-date list.

Examples

See ConfigboxViewCart in {appDir}/view/cart and the module in {appAssetsDir}/javascript/cart.js for example. Most views use one or more JS modules, you can see how it is all used there.