Akeneo technology icon What should you know about the structure of RequireJS modules in Akeneo PIM?

What should you know about the structure of RequireJS modules in Akeneo PIM?

The first blog post was an introduction to the Akeneo UI. We presented you with the product itself and the technology stack. The next chapter will be devoted to the concept of a RequireJS module used in the old part of the frontend. Why RequireJS? As you already know, the new Akeneo tech stack includes webpack, React and TypeScript, but not all places have been refactored yet, which means that when you use components in Akeneo PIM, you come across this old set of technologies. In this article, you will explore what the .yml configuration file looks like, how the modules are structured and how dependencies are added to them.

Ready? Let’s get started!


My blog post series provides some context and explanation of the principles used to build UI in the PIM. Check the other articles:


What is RequireJS used for in Akeneo PIM?

We have indicated before that the application heavily relies on a JavaScript module loader: RequireJS. Although with time its tasks were taken over by webpack, it is still applied to newer versions of software.

1. Using RequireJS, JavaScript code in Akeneo is divided into smaller, functional and independent parts called modules. They can also be extended and imported with RequireJS.

2. RequireJS allows you to effectively manage class dependencies, file dependencies or component dependencies, as they can be loaded easily.

3. Thanks to RequireJS you are able to deliver high-quality code and achieve speed improvement.

What does an example RequireJS module look like and what does it consist of? 

RequireJS needs a .yml configuration file, which maps module names to file paths. This is one of the core RequireJS files that loads most components.

An example of a default configuration file of Akeneo in RequireJS importing html templates related to buttons and modals:

config:
   pim/template/form/index/create-button: pimui/templates/form/index/create-button.html
   pim/template/form/index/confirm-button: pimui/templates/form/index/confirm-button.html
   pim/template/form/creation/modal: pimui/templates/form/creation/modal.html

On the left we can see the key under which the module will be available – it can be added as a dependency to another component. On the right we will find the file path. Pay attention to the "pim/template/form/index/create-button" module which will be used in the component discussed below.

If you are interested in what the entire RequireJS config file looks like, click here

Now let's take a look at how a single module file is structured. Let this be the component responsible for rendering a create button, which is used where something is created, e.g. a product or a package:

"use strict";

/**
 * Create button
 *
 * @author    Alban Alnot <alban>
 * @copyright 2017 Akeneo SAS (http://www.akeneo.com)
 * @license   http://opensource.org/licenses/osl-3.0.php  Open Software License (OSL 3.0)
 */
define([
  "jquery",
  "underscore",
  "oro/translator",
  "pim/form",
  "pim/template/form/index/create-button",
  "routing",
  "pim/dialogform",
  "pim/form-builder",
], function ($, _, __, BaseForm, template, Routing, DialogForm, FormBuilder) {
  return BaseForm.extend({
    template: _.template(template),
    dialog: null,

    /**
     * {@inheritdoc}
     */
    initialize: function (config) {
      this.config = config.config;

      BaseForm.prototype.initialize.apply(this, arguments);
    },

    /**
     * {@inheritdoc}
     */
    render: function () {
      this.$el.html(
        this.template({
          title: __(this.config.title),
          iconName: this.config.iconName,
          url: this.config.url ? Routing.generate(this.config.url) : "",
        })
      );

      if (this.config.modalForm) {
        this.$el.on(
          "click",
          function () {
            FormBuilder.build(this.config.modalForm).then(function (modal) {
              modal.open();
            });
          }.bind(this)
        );

        return this;
      }

      this.dialog = new DialogForm("#create-button-extension");

      return this;
    },
  });
});</alban>

At the top, the define() function is declared. It can be used to load the modules.

Its first argument is a list of dependencies (other modules) that we want to import into the file. The second argument is a function whose arguments are dependencies, in the same order as they were declared.

Let's take "jquery" (the first element of the list) as an example. "jquery" will be available in the function as the first one, that is "$".

We can also find the "pim/template/form/index/create-button" module which was defined in the RequireJS file above.

Tip! Arguments in the function can be given any name, however, it is important that the name can be understood by a developer who will later deal with the code written by you. For instance, the fourth dependency in the list is "pim/form", according to Akeneo convention called "BaseForm" – therefore, call argument no. 4 the same. "BaseForm" is considered the basic class used in Akeneo, which we discussed more widely in the first chapter.

The function returns "BaseForm.extend" – thus it acts as a module extending the "BaseForm" file, which was imported and is available owing to RequireJS.

The "initialize" function is used in order to call the "BaseForm" component:

initialize: function (config) {
   this.config = config.config;
   BaseForm.prototype.initialize.apply(this, arguments);
},

Another important function is render(). It is responsible for rendering a button. As it can be easily observed, we have access to all dependencies added to "define" e.g. "FormBuilder".

Ahead of you there is more and more advanced content about user interface design. The upcoming chapters are devoted to creating new bundles, basic commands for rebuilding the frontend and modifications available for Akeneo modules. If you want to find out how to adapt a ready component to your needs, follow our blog!