Options: Receive Options
What are we learning here?
┌──────────────┐ ┌─────────────────────────────────────────────────┐
│ Loading │ │ Component │
│ Component │ │ │
│ ┌──────────┐ │ │ ┌──────────────────┐ ╔═════════╗ │
│ │ options ├─┼─┼─Transform───►│ Received Options ├──►║ Final ║ │
│ └──────────┘ │ │ (optional) └────────▲─────────┘ ║ Options ║ │
│ │ │ Merge ╚═════════╝ │
└──────────────┘ │ ┌────────┴─────────┐ │
│ │ Static Defaults │ │
│ └──────────────────┘ │
└─────────────────────────────────────────────────┘
- A component can be loaded with
load(ComponentType, ComponentOptions)
- The passed options are available over
this.options[...]
- The default values can be defined in
static defaults = {...}
Any component can receive options and easily define default values for them. Options and the default
values will be defined in a static variable named defaults
. The received options will be available
over this.options
.
First we add two container to show the values in our template:
src/demo/apps/component-basics/content.ejs.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Now we define two options myOption1
and myOption2
with default values in our widget using the
static variable defaults
.
As you will see we will define the options in a namespace @codecoupler.walktrough
. You should
never mess up the root of the defaults
variable. Put your options always into a namespace.
You'll soon understand why.
Then we could access the values from the object
this.options["@codecoupler"].walkthrough.YourOptionName
and pass them into our template.
I can already hear everyone screaming. Should we write such a long statement every time to determine a single value? And I agree with you. In many cases you will need to access many of your options and use this statement over and over again.
It is therefore always advisable to provide a small help function. This should be private and point to your own namespace:
get #options() {
return this.options["@codecoupler"].walkthrough;
}
Thus the instruction from this.options["@codecoupler"].walkthrough.YourOptionName
is shortened to
this.#options.YourOptionName
.
Why private and why in this way?
If you're wondering why the helper function has to be private and why this can't be solved any other way: Components are based on inheritance and each base class needs access to its own namespace.
All automation approaches are far too complex in relation to this simple help function.
Let's display our values in our HTML structure:
src/demo/apps/component-basics/content.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
|
Ok, you should see now the default values in the right place.
Let's start pass the option myOption1
to the widget in our application. For this we will use here
a further load method signature this.load(ComponentType, ComponentOptions)
. This would look like:
await this.stage.load(Content, {
"@codecoupler": { walkthrough: { myOption1: "Value 1" } }
});
And again I hear everyone screaming. Should we prescribe such large instructions to the callers of our components for very simple use cases? Just to pass a simple value?
Well, here I only agree with you in part. In fact there are very simple components. Like ours now, for example. Or even those already included in the basic package, such as a component for displaying a simple message.
In these cases it is actually a bit exaggerated. But there are also more complex components that have multiple base classes where this instruction size should be retained.
Fortunately, there is a solution for this too. For simple use cases, you define your own namespace
in a property called $map
of the defaults
variable:
static defaults = {
$map: "@codecoupler.walkthrough"
};
This allows the caller to pass the options directly at the lowest level and the call is shortened to:
await this.stage.load(Content, { myOption1: "Value 1" });
Not all options will be maped
The $map
keyword will map all options in the root level to the given namespace. Exceptions to
this are: namespaced options (starting with @
) and well known options (starting with $
).
A combination of mapped top level options and namespaced or well known options is valid:
await this.stage.load(Content, {
"@someNamespace": { subspace: { anOption: "aValue" } },
myOption1: "Value 1"
});
Let's add this property first:
src/demo/apps/component-basics/content.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
|
And now let's pass the value with the short notation:
src/demo/apps/component-basics/index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Start Definition Object vs Shortcut Signature
We will use here a signature of the load method which expects a Component
and its options.
This is just a shortcut signature. The load method normally expects a so called start definition
object. This would look like:
load({
component: ChildComponent,
options: { myOption1: "Value 1" }
})
Now you see the passed value of myOption1
and the default value of myOption2
as we did not pass
another value for this option.