Skip to content

Modules

Intro

In the database, the articles/boards are saved in the page table under the module column. This holds a JSON object, where every module is identified by its wf-role:

json
{
  "__roles": ["title", "main_image", "paragraph", "paragraph--1"],
  "title": {
    "content": "Article title"
  },
  "main_image": {
    "__roles": ["image_description", "image_credit"],
    "__contentModels": [
      "type": "image", id: 1
    ]
    "image_description": {
        "content": "Image description"
    },
    "image_credit": {
        "content": "Image credit"
    },
    "paragraph": {
        "content": "First paragraph"
    },
    "paragraph--1": {
      "content": "Second paragraph"
    }
  }
}

The title, main_image, image_description, image_credit are the strings used for the wf-role attribute, and main_image is a composite module, consisting of an embedded content model and two text submodules, image_description and image_credit. The template also allows adding one or more paragraphs, the example above shows how subsequent paragraphs' roles are being derived (value of wf-role + -- + unique_idx).

When the wf-html is bundled by webpack in JS, XalokNext extracts a JSON object including the configuration of the modules:

json
{
  "__config": {
    "submodules": [
      "title",
      "main_image",
      "paragraph"
    ]
  },
  "title": {
    "module": "inline_text",
    "new": 1,
    "data": {
      "content": "Title module placeholder"
    }
  },
  "main_image": {
    "__config": {
      "module": "composite",
      "editor": "WfModuleEditorContentModels",
      "new": 1,
      "submodules": [
        "image_description",
        "image_credit"
      ]
    },
    "image_description": {
      "__config": {
        "module": "inline_text",
        "editor": "WfModuleEditorInlineText",
        "new": 1,
        "data": {
          "content": "Image description placeholder"
        }
      }
    },
    "image_credit": {
      "__config": {
        "module": "inline_text",
        "editor": "WfModuleEditorInlineText",
        "new": 1,
        "data": {
          "content": "Image credit placeholder"
        }
      }
    }
  },
  "paragraph": {
    "__config": {
      "module": "body_text",
      "editor": "WfModuleEditorBodyText",
      "new": 1,
      "data": {
        "content": "Paragraph placeholder"
      }
    }
  }
}

wfRolePath

Throughout the code, a wfRolePath is used to identify a module. This is an array of the roles used to get to the module in the above object. Example wfRolePath values:

  • ["title"]
  • ["paragraph--1"]
  • ["main_image", "image_description"]

Since the need of uniquely identifying a module is real, only one module can have a given wfRolePath. That is, you cannot define two modules with the same role.

Example:

html
// NOT allowed, the `heading` role is repeated twice
<h2 wf-role="heading">
</h2>
<h3 wf-role="heading">
</h3>

// Allowed, the two `title` roles are being used in two different container modules
// one has wfRolePath `['main', 'title']`, the other `['secondary', 'title']`
<div wf-role="main">
  <h2 wf-role="title"></h2>
</div>
<div wf-role="secondary">
  <h2 wf-role="title"></h2>
</div>

Primary roles/role paths

The primary role is the role used to derive the key from. That is, for paragraph--1 module, the primary role is paragraph. wfed/wfvue/store/util/wf_role_path includes methods for working with these:

javascript
define('wfed/wfvue/store/util/wf_role_path', function({getPrimaryRole, getPrimaryRolePath}) {
    getPrimaryRole('paragraph--1'); // 'paragraph'
    getPrimaryRolePath(['paragraph--1']); // ['paragraph']
});

Role path utils

wfed/wfvue/store/util/wf_role_path exports a list of small function that ease working with wfRolePaths

  • isWfRoot: returns true whether the wfRolePath is empty

    Example:

    javascript
    isWfRoot([]); // true
    isWfRoot(["x"]); // false
  • joinPath: returns a string representation of the wfRolePath. Working with strings is easier than working with the array when one wants to compare paths

    Example:

    javascript
    joinPath(["a", "b", "c"]); // "a/b/c"
    
    `${joinPath(["a", "b", "c"])}/`.startsWith(`${joinPath(["a", "b"])}/`); // checks whether "a/b/c" is a submodule of "a/b" (it is :) ). 
    // Note: There is also a `isParent` util that should be used instead of this, this was written just for illustration purposes
  • joinPrimaryPath: returns a string representation of the primary role path. Useful for checking whether the module is an instance of a certain type.

    Example:

    javascript
    joinPrimaryPath(["related", "item"]); // "related/item"
    joinPrimaryPath(["related", "item--1"]); // "related/item"
  • splitPath: the opposite of joinPath - converts a string representation of a wfRolePath to its original array value

    Example:

    javascript
    splitPath("related/item--1"); // ["related", "item--1"]
  • getRole: returns the last role of the wfRolePath

    Example:

    javascript
    getRole(["related", "item--1"]); // "item--1"
  • getPrimaryRole: returns the primary role

    Example:

    javascript
    getPrimaryRole("item"); // "item"
    getPrimaryRole("item--1"); // "item"
  • getPrimaryRolePath: returns the primary role path

    Example:

    javascript
    getPrimaryRolePath(["related", "item"]); // ["related", "item"]
    getPrimaryRolePath(["related", "item--1"]); // ["related", "item--1"]
  • getParentPath: return the parent wfRolePath

    Example:

    javascript
    getParentPath(["related", "item"]); // ["related"]

    Note: One might be tempted to wfRolePath.shift() to obtain the parent wfRolePath, but this mutates the original wfRolePath variable

  • isParent: checks whether a wfRolePath is the parent of another one

    Example:

    javascript
    isParent(["related"], ["related", "item--1"]); // true
    isParent(["related", "item--1"], ["related", "item--2", "title"]); // false
  • areSiblings: returns true if two modules are siblings of the same parent

    Example:

    javascript
    areSiblings(["main_image", "image_credits"], ["main_image", "image_description"]); // true

Working with modules in PHP code

Check this page for details. It's been written for v6+ and still applies for v7.

Working with modules in Javascript code

While the above structure seems deceptively simple to work with (let's just loop the __roles array and take data from there), there are many corner cases that developers stumble over & over. You are thus being encouraged to use wfed/wfvue/store/util/modules_data_walker util class to work with this data - it irons out the corner cases for you.

For working with modules' configurations, a similar util can be found in wfed/wfvue/store/util/modules_config_walker.

modules_data_walker and modules_config_walker work in a similar way, they share most of the options together:

javascript
define(["wfed/wfvue/store/util/modules_data_walker", 
    "wfed/wfvue/store/util/modules_config_walker"],
    function(walkModulesData,
             walkModulesConfig) {
    walkModulesData(page.module, (moduleData, wfRolePath) => {
        // handle moduleData
    });
    walkModulesConfig(page.config, (moduleConfig, wfRolePath) => {
        // handle moduleConfig
    });
});

NOTE: The above code uses an unexisting page variable. For getting access to either module data or config can be done using vuex store extenders.

They can take as the first argument either the entire page.module/page.config object, but they work equally well when passing composites' data or (full) configs:

javascript
    walkModulesData(page.module.main_image, (submoduleData, relativeWfRolePath) => {
        // handle moduleData
    });
    walkModulesConfig(page.config.main_image, (submoduleConfig, relativeWfRolePath) => {
        // handle moduleConfig
    });

One common gotcha in this case is that the relativeWfRolePath second argument passed to the callback is - as the name implies - relative to the module which's data is being iterated. That is, using the example in the Intro section, it'll have a value of ["image_description"].

Options

The walkers accept a third options argument. The following are options shared by both data and config walkers:

  • depth (int) - default: null. By default, the walkers walk recursively, when they encounter a composite, its submodules are read too. Use { depth: 1 } to limit the walking to direct submodules.
  • includeSelf - default: false. By default, the walkers walk only submodules of the passed data/config. Use { includeSelf: true } to call the callback with the module's data/config itself too.

modules_data_walker options

  • includeEmpty - default: false. A module's role might be listed in the __roles key, but if the module wasn't touched up to that point, the key corresponding to its wf-role might not exist in the data object. By default, the modules data walker skips invoking the callback you're passing. Pass { includeEmpty: true } option to include these modules too

modules_config_walker options

  • full - default: false. Using the example in the Intro section, walking the template config would invoke the callback using the __config object as the first argument. Pass { full: true } if you need the full configuration. E.g.: by default you'd receive main_image.__config, but passing { full: true } you'd receive the entire main_image object.