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
:
{
"__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:
{
"__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:
// 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:
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
: returnstrue
whether the wfRolePath is emptyExample:
javascriptisWfRoot([]); // 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 pathsExample:
javascriptjoinPath(["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:
javascriptjoinPrimaryPath(["related", "item"]); // "related/item" joinPrimaryPath(["related", "item--1"]); // "related/item"
splitPath
: the opposite ofjoinPath
- converts a string representation of a wfRolePath to its original array valueExample:
javascriptsplitPath("related/item--1"); // ["related", "item--1"]
getRole
: returns the last role of the wfRolePathExample:
javascriptgetRole(["related", "item--1"]); // "item--1"
getPrimaryRole
: returns the primary roleExample:
javascriptgetPrimaryRole("item"); // "item" getPrimaryRole("item--1"); // "item"
getPrimaryRolePath
: returns the primary role pathExample:
javascriptgetPrimaryRolePath(["related", "item"]); // ["related", "item"] getPrimaryRolePath(["related", "item--1"]); // ["related", "item--1"]
getParentPath
: return the parent wfRolePathExample:
javascriptgetParentPath(["related", "item"]); // ["related"]
Note: One might be tempted to
wfRolePath.shift()
to obtain the parent wfRolePath, but this mutates the originalwfRolePath
variableisParent
: checks whether a wfRolePath is the parent of another oneExample:
javascriptisParent(["related"], ["related", "item--1"]); // true isParent(["related", "item--1"], ["related", "item--2", "title"]); // false
areSiblings
: returnstrue
if two modules are siblings of the same parentExample:
javascriptareSiblings(["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:
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:
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 itswf-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 receivemain_image.__config
, but passing{ full: true }
you'd receive the entiremain_image
object.