Post Featured Image

Learn How to Use Multiple Modules in an Extension

This blog post is appropriate for SuiteCommerce sites, and SuiteCommerce Advanced sites running Aconcagua or newer. It is not relevant for older sites (as they don't have the extension framework).

A question I've been asked a couple of times is: can I have more than one module in an extension? And the answer is: yes! I think the difficulty in wrapping one's head around this is that all of the extensions we've covered so far on the blog have essentially been single module ones; or, to put it another way: we've accidentally encouraged a kind of one-to-one relation between extensions and modules. But this doesn't need to be the case.

I'll cover the details as well as some extra contextual information below, but the gist is this:

  1. Add your additional module to your Modules folder
  2. Add whatever files you need — you don't need another entry point file
  3. If you do add another entry point file, know that it won't be automatically called when the module is loaded (ie if you add an mountToApp() function, it won't automatically be called)
  4. Add the files from the second module as dependencies to the files in your first module as required
  5. Call the new files' methods like you normally would within your first module's files

If this isn't working out for you, or if you want to know more, read on — I have an example.

Folder Structure

Your extension has a folder under Workspace which is named after your extension. Underneath that is Modules which contains your modules, and then usually one folder named, again, after your extension. However, it doesn't need to be this way.

In my theoretical example, I'm going to create two simple modules with the following structure:

Assets
Modules

  • ModuleOne
    • JavaScript
      • ModuleOne.js
  • ModuleTwo
    • JavaScript
      • ModuleTwo.js

manifest.json

In each of the entry point files I've created entry point files that look like this:

define('ModuleOne'
, [
  ]
, function
  (
  )
{
  'use strict';

  return {
    mountToApp: function ()
    {
      console.log('ModuleOne loaded!');
    }
  }
})

Where I've substituted ModuleOne for ModuleTwo in the second module.

Finally, in my manifest.json file, I've added the path to ModuleOne.js as the entry point file for each of the application's JavaScript, and then run gulp extension:update-manifest. When that finished, I ran gulp extension:local and visited my site. The question arises: what will we see in the developer console?

ModuleOne loaded!

Yes, and that's expected behavior. The second module has not loaded, despite having its files appear in the manifest file. Why? Because we haven't called them.

When the extension is loaded, only the entry point file in the manifest file is called. Therefore, if we want extra code to be called, we need to put in calls in that entry point file's mountToApp() method. Whatever we put into there is up to us — including a call to files from other modules. But entry point files are a bit more nuanced than that in extensions.

Entry Points

In normal SCA development, you would create an entry point file for each module. However, in the world of extensions, that is not necessary. For extensions, there is one entry point for the entire extension, except if you want different entry point files for the different applications on your site. If you want to do this, you can modify the javascript : entry_points object in your manifest.

For example:

"javascript": {
    "entry_points": {
        "shopping": "Modules/ModuleOne/JavaScript/ModuleOne.js",
        "myaccount": "Modules/ModuleTwo/JavaScript/ModuleTwo.js",
        "checkout": "Modules/ModuleTwo/JavaScript/ModuleTwo.js"
    },

Now, when I visit the checkout or customer account areas of my local site (after saving the manifest and restarting the server, of course) I can see the following message in the console:

ModuleTwo loaded!

This is perfect for scenarios where you want to run different code in different parts of the site but have it inside the same extension. It also lets you manage dependencies: we don't make every core file available through all applications, and you wouldn't want to get errors in your file because you've referenced a file that the current application doesn't think exist.

Calling Files in Other Modules

So, reseting our manifest file so that all three applications use the same entry point, how do we call files from one module to load at the same time? With dependencies, of course.

If we modify ModuleOne.js and add ModuleTwo as a dependency, we can then call it from within our file. For example, we could call it's mountToApp() method if we wanted to:

define('ModuleOne'
, [
    'ModuleTwo'
  ]
, function
  (
    ModuleTwo
  )
{
  'use strict';

  return {
    mountToApp: function ()
    {
      console.log('ModuleOne loaded!');
      ModuleTwo.mountToApp();
    }
  }
})

And what do we get?

ModuleOne loaded!
ModuleTwo loaded!

Perfect.

Remember, it doesn't have to be only 'entry point' style files that you call across different modules: just like standard SCA development, you can reference any file (eg views or models) as long as it has been properly defined and made available to the application. In other words, instead of having an entry point file in your second module, you could just have a view, model, or whatever you want, instead. Just add it as a dependency and call one of its methods.

Bonus Tip: Asset Organization and File Re-Use

Finally, as a bonus tip, don't forget that if you want to create a folder structure in your top-level assets folder, you can. There is nothing stopping you from dividing your assets folders up like this:

assets

  • fonts
  • img
    • ModuleOne
      • img1.jpg
    • ModuleTwo
      • img1.jpg
  • services

In this scenario, we could write some code so that ModuleOne image file is loaded when the ModuleOne entry point file is loaded, and the ModuleTwo image when that file its entry point is loaded. All we would need to do is create a dynamic path to the image based on which one loaded.

To illustrate this, I've created an example that reuses the view and template files of ModuleOne in ModuleTwo, so that the only thing that is different is that I have duplicated the ModuleOne entry point file and changed its name (and a value in the file that captures its name). The idea is super simple: I want some text and a small image to appear in the header, but I want the text and image to be different depending on which application we're using.

I've reconfigured the manifest file so that ModuleTwo's entry point is loaded when the shopper hits the checkout application.

In the shopping application, I get something like this:

And in the checkout application, I have this:

ModuleOne's entry point look like this:

define('ModuleOne'
, [
    'ModuleOne.View'
  ]
, function
  (
    ModuleOneView
  )
{
  'use strict';

  return {
    name: 'ModuleOne'

  , mountToApp: function (container)
    {
      console.log(this.name + ' loaded!');
      var Layout = container.getComponent('Layout');

      var self = this;
      Layout.addChildView('cms:header_banner_top', function ()
      {
        return new ModuleOneView({moduleName: self.name});
      });
    }
  }
})

ModuleTwo's looks identical, except for the name in the opening of the define statement and the value of name; ie, it still adds the same view and we dynamically pass the module name to the view constructor.

The view looks like this:

define('ModuleOne.View'
, [
    'Backbone'
  , 'module_one.tpl'
  ]
, function
  (
    Backbone
  , module_one_tpl
  )
{
  'use strict';

  return Backbone.View.extend({
    template: module_one_tpl

  , getContext: function ()
    {
      return {
        message: 'This is ' + this.options.moduleName
      , image: 'img/' + this.options.moduleName + '/img1.jpg'
      }
    }
  })
})

When the view is created, it is passed whatever options the constructor is given and attaches them to its own options property. In our case, we passed it the name of the module, which I've used to build up the path for the image URL as well as a message to be passed to the template.

The template itself is super simple:

<p>{{message}}</p>
<p><img src="{{getExtensionAssetsPath image}}"></p>

Again, both the view and template are completely reusable in this context, and we could build them out how we wish. If we need to diverge and create a customization that is more particular to the scenario, then we could modify the view and templates with more conditionals, or we could simply go back to having separate files. It's really up to you. But the message, I hope, is clear: you can certainly use cross-module files in your extensions.

If you'd like a copy of my files, you can get them here: MultiModule.zip.