Potions Tag Example - La becanerie bundle

Goal

On https://www.la-becanerie.com/carburateur-dell-orto-phbn-175-ls-booster-nitro.html, Potions Tag is in charge of the “Fréquemment achetés ensemble”
Image without caption

Code

Here is the entire typescript code of this banner (we removed the confirmation banner for simplicity sake)
In main.ts, the code declares a global property where everything that concerns Potions is pushed : window.pts
javascript
window[GLOBAL] = window[GLOBAL] ?? ([] as WizardInput); window[GLOBAL].push(...Object.entries(spells)); window[GLOBAL] = Preview(window, window[GLOBAL]) ?? Wizard(window[GLOBAL]);
We are going to see that pushing a “spell” in Potions Wizard is equivalent to writing a formula in a Google Sheet cell : as soon as the spell is pushed, the wizard tries to resolve it and all its dependents.

Potions Tag in action

Type in the console
javascript
pts.grimoire.log(true)
You’ll get the following table
pts.grimoire is like a Google sheet and each line of this table is what we call a “spell”.
Exactly like a cell in a Google sheet, a spell has :
  • a name (the reference of the cell)
  • a resolve function that can take the whole grimoire (Google sheet) as input and returns the spell value (the parsed formula of a cell)
  • a value (the value that is displayed in the cell)
  • dependencies : the references on which the resolve function depends
  • dependents : the references that depends on this reference value
You can access a spell’s value by typing pts.grimoire.value([SPELL_NAME])
javascript
pts.grimoire.value("sum") //logs e=>e.reduce(((e,r)=>e+r),0)
A spell’s value can be anything :
  • a primitive (”siteId”),
  • an object ("🎨bundle")
  • a function (”sum”)
  • an observable ()…

Spells

You can see in the example’s code that there are two types of spells that are pushed in the Wizard:
  • ingredients : functions or objects that are provided directly by the @potions/spells library
  • recipes : plain objects that are using keys equal to other spells’ name
By providing all the ingredients that are needed to show a product recommendation banner, Potions Spells library enable developers to provide only the recipes that can be the result of a JSON.parse.
For instance “location$” is an ingredient that is imported.
Image without caption
location$ spell’s value is a function that takes a location config and returns an observable of location.
It depends on nothing but 2 recipes depends on it : 📌locationIDX24📌locationIDAW5
Those 2 recipes’ values are observables of location, so in order to see the observable’s content we need to subscribe to it.
javascript
pts.grimoire.value("📌locationIDX24").subscribe(console.log) //logs /* { "id": "locationIDX24", "name": "append_product_infos", "element": div#product-infos.row.row-5, "selector": "#product-infos", "insertionMethod": "appendChild", "containerTag": "div" } */
Those observables are used to insert the product recommendation banner in the element that is contained in the location with the right containerTag, and the right insertionMethod.
Using observables abstract the complexity of waiting for the container to appear.

Formulas

Every spell has a name and a formula input, again exactly like in a Google sheet.
Except that in Potions Wizard, the formula is a Js Object (and not a string) and the context object is also a Js object (and not a map of cells’ values).
For instance the following recipe
javascript
{ "📌locationIDX24": { location$: [ "#locationIDX24", "#append_product_infos", "##product-infos", "#appendChild", "#div" ] } }
has a 📌locationIDX24 name and { location$: [ "#locationIDX24", "#append_product_infos", "##product-infos", "#appendChild", "#div"] } formula input
When pushed into Potions wizard, the wizard creates a Formula from the formula input.
The Formula has a resolve(ctx) method and a dependencies property.
is more or less parsed to the following resolve function
javascript
(ctx) => ctx.value("location$")( "locationIDX24", "append_product_infos", "#product-infos", "appendChild", "div" )
We see that
javascript
"key"
javascript
{key : [arg1, arg2]}
javascript
"#blabla"
becomes
javascript
ctx.value(key)
javascript
ctx.value(key)(arg1, arg2...)
javascript
"blabla"
This way we transform deeply nested Js objects in resolve functions that take a ctx as argumentt