Passing customer data to the Minicart

In a previous article, How Magento passes server data to minicart.js, we saw how Magento will use the general window object to pass server data, and later in article Pass our own server data to the Minicart we saw how to pass data via the <script type=”text/x-magento-init”>.

But, where exactly is the customer data, like, the cart items, coming from?

I will start looking at the original minicart.js, as set in the module-checkout and reffered to quite a lot so far. This is a good entry point

https://gist.github.com/marjan7790/eed6ae2be8382a7695a28f721af46d8f

First to notice, var items is acquired from this.getCartParam() function which is defined as

https://gist.github.com/marjan7790/635c97c21314894066f21cc5df123e0b

which means that there is internal property named cart, that among other keys, contains the items key as well.

Indeed, we can see this is a “class level” attribute and is defined as an empty object like cart: {}.

Lets take a look at initialize() again, specifically at

https://gist.github.com/marjan7790/b98c8aaa8b06f0c74b7953a8dde5bdf5

And then this leads to the update() function

https://gist.github.com/marjan7790/6323a8ef5cc7dec662a637be46fa2f88

OK, it is now clear that the update() function does add keys and assigns values to the this.cart, and that as an input parameter it uses: customerData.get(‘cart’);

And what is customerData? Well, lets look at the define section of this RequireJs module and notice that this is another module, defined originally in Magento_Customer/js/customer-data and loaded in this minicart.js

What this means? This minicart.js is dependent on another module, and that module on its own is responsible to provide the data. The minicart.js methods are merely reading this and re-distributing in its own properties (this.cart) in the way that will be most convenient to do the display of the data.

So, we are coming to the an important notice here: minicart.js is within the web/js/view folder of the module-checkout. Please notice that there are /web/js/action and web/js/model folders as well.
This all means, there are .js modules that will be focusing on modeling (defining the essence of the js object) or taking actions only (like collecting and submitting data from and to the server), and sure enough, there are .js modules, just like our minicart.js that will be used for the rendering part of this whole idea.

You see, the web/js folder is now a complicated mini JS application in itself. Slightly of topic here, but each Magento module, may have its own JS application embedded with its /web folder. Just keep this in mind. This well may be the future of the Web, not just Magento: any reusable headless back-end component (module, bundle) may come with at least one default front-end implementation, i.e. the default head.

Anyway, the question of how the minicart.js gets its data from the server, has now transposed into how Magento_Customer/js/customer-data gets its data?

Open

https://gist.github.com/marjan7790/b9e501134d74af084cf0fadf8c347ad4

First thing to notice this js file returns a js object like return customerData;

Containing other function and objects as well as few plain old listeners ($(document).on()), the essence of this file is to produce and return the customerData variable, which means whoever claims that they depend on the customer-data.js, they will get this customerData object.

So, what to focus to? Its not easy to know what to look at, so it is always a good practice to set a couple of break points here and there and see when they get to be executed in the js stack.

But, by habit almost, lets start at the init() function. I lead you to focus on this code

https://gist.github.com/marjan7790/43e9be18d9fb25f37983c7578a7e8585

There are there different sections and the get their data from dataProvider.getFromStorage().

And dataProvider is defined in this same file, above the customerData object. Looking at the getFromStorage definition we see

https://gist.github.com/marjan7790/cf539aeff59a5a43c7eba3b65297e588

The last ingredient here is to notice that storage is defined even further above in this file like:

https://gist.github.com/marjan7790/8db46baca3599a9a004928fd8ec75b38

Suffice it to say the browser’s local storage is a mechanism for storing data on a browser level and applications can use this for storing variables that are going to be used between two requests (remember HTTP being stateless?). For more info, please google and find more suitable articles.

Anyway, Magento has set its own storage key named ‘mage-cache-storage‘ and you can observe this is you were to load any magento frontend page and the inspect then , in Chrome -> Application -> Local storage, or in Mozilla -> Storage -> Local Storage.

There are of course other keys, but lets focus on the ‘cart’ one. It looks something like

cart-from-storage

So, this is how some of the server data is available to the js scripts. Perhaps in another article in the future I will tackle the topic of how this cart section of the mage-cache-storage gets set in the first place, but you do not have to go far to see how this might be done. Search, within customer-data.js, the update() function and you should find this implementation

https://gist.github.com/marjan7790/5f34944dd438d5a294c022d098b57f95

I leave it up to you how all of this flows. For this you could set a debug point, perhaps before the _.each function. Remove an item from the cart and this code will be triggered, but regardless of how complicated it might look, updating the storage boils down to storage.set(your-section, your-section-data);

After this I hope you have a nicer understanding how data is passed from the server and used by the frontend JS scripts. And again, “scripts” here is a sort of diminishment, since, all of the front scripts, as pointed earlier are specializing and contributing to somewhat larger – they product front head that nicely correlates to the back-end.

In the last article of this series, lets write some simple code to use the local browser storage capacity ourselves.

Thank you for reading.

Pass our own server data to the Minicart

From the previous post how server side data is being passed to the Minicart, you remember that Magento uses window.checkout object to configure the original minicart.js component. And I had an idea to modify the window.checkout object inside minicart.phtml, which seems rather straightforward and nice.

While this is all practical, lets try to figure out a way to pass data to our component in a way that the value is actually a property of the component.

If you are not the type that likes too much experiments, I promise, this is that type of article that has high chances of giving a headache, so, now is the moment to go back to your favorite series :). For the rest of you, lets start playing around with Magento by opening

https://gist.github.com/marjan7790/d6331447c83d420014d22c4a05a30ca2

and take a look at this code more carefully

https://gist.github.com/marjan7790/36fe1702a71ab2bc710eeea5c57ac983

In essence, this says:

Pass a JSON object to Magento_Ui/js/core/app

Practically, Magento_Ui/js/core/app is alias to app.js, which is found in vendor/magento/module-ui/view/base/web/js/core/app.js, and serves to create the UI components instances according to the configuration of the JSON using uiLayout.
(original text from: https://devdocs.magento.com/guides/v2.2/ui_comp_guide/concepts/ui_comp_config_flow_concept.html)

Next, lets see where this $block->getJsLayout() code comes from.
Well, at the top of the minicart.phtml, we can see

https://gist.github.com/marjan7790/8986394570f50f45ed01c1965535ffc0

but in the mentioned class there is no getJsLayout() method. Tracking through inheritance, gives us this chain

https://gist.github.com/marjan7790/05091cc53bd9d7621fd4615f8ad5c587

and inside the AbstractBlock we can see

https://gist.github.com/marjan7790/c568048be3528a3f3968b1f8593f543e

If we were to setup a break point over here, we will see that $this->jsLayout is a mere PHP array that will be turned into json object, the content of which looks like

p2

OK, since app.js serves to create instances according to configuration, and there is “config” key in this array, lets try to add data to the config.

Go ahead and create etc/di.xml with the following content

https://gist.github.com/marjan7790/69107f04517ec70acee169db6a5a1f30

and inside the Block/Cart folder, create the class Sidebar.js with

https://gist.github.com/marjan7790/3c096f592258023039d36165f8139772

If effect, I’ve set new key named maxItems with the config sub-array of the minicart_content component.

You probably need to re-compile so the server end actually considers using your block instead of the original, and you might need to re deploy the static content to get the .js changes.

Eventually, you will re-run the page and right click on the browser and “View Page Source”.  Search within the source for

[data-block='minicart']

and you will see what gets send to Magento_Ui/js/core/app.

I’ve copied the JSON that is passed and send it to https://jsoneditoronline.org/, so I have a nice tree like representation which helps visualize things. Thus, the JSON object looks like

p3

Great, the new key is there. But, how do we use it?

Replacing in minicart.js,
maxItemsToDisplay: 3

with
maxItemsToDisplay: origMiniCart.maxItems

does not work.

Well, if you make the code to be like

https://gist.github.com/marjan7790/65f89a8153dc04fdcc58c8e1a51d3550

and set a break point at var tmp = origMiniCart; you will see that that tmp is actually uiClass constructor, which in essence means we still do not actually have our object. Remember, from previous tutorial, the original initialize() has not executed yet.

If we don’t have an object, then, lets just create one! Update the code to be like

https://gist.github.com/marjan7790/6899d704c38328dee78a80cdfda94b17

Now, this works! But … why exactly?

It is important to know that you could send anything to your component in the config key and surely it will be available as property to your object. For example, lets say you have something like this in your .phtml

https://gist.github.com/marjan7790/acb101eb0e892e0f24df638dc653dbd9

With this code

  • You have declared that your component is named mycomponentname
  • have told that the actual js file is Vendor_YourModule/js/mycomponent
  • have told what the .html template is Vendor_YourModule/mycomponenttemplate
  • set a component title
  • added a new config property named valueToPass

If you have your component instantiated at some point you should be able to call
    mycomponentname.valueToPass
and this will return 55.

But, the origMiniCart is still merely a constructor function, so, this is exactly what we remedy against.

Lets dig in the original minicart.js initialize() function. It surely does first its own specific things, but eventually returns

https://gist.github.com/marjan7790/9874b3376536db681de15f53a33b41ad

Well, lets track this and figure who is the parent of this component?

It actually says return Component.extend({ …,

and if we scroll up in the “define” section of this RequireJs module, we will see that Component is alias for uiComponent and actually refers to Magento_Ui/js/lib/core/collection.

If you wonder where this alias comes from, open

https://gist.github.com/marjan7790/1acc044a4743851fa34f8be279a4b058

But anyways, opening Magento_Ui/js/lib/core/collection we see returns Element.extend({ and here Element is actually alias to uiElement, which (again from requirejs-config.js) is alias to Magento_Ui/js/lib/core/element/element.

Element is where for the first time we will see initialize() method that actually returns return this;, and not return this._super(); as wee saw in minicart.js.

So, uiElement is as far as the hierarchy goes. Therefore, if we typed in our initialize() something like

https://gist.github.com/marjan7790/7693e5aed1d9d053d6d46bbbe25a84a1

we will eventually force uiElement to return an actual object.

After that, its easier. We simple refer to the new property maxItems and assign it to the actual property that the original script uses, which is maxItemsToDisplay.

Finally, we return the parent object.

Check the 0.2.0 version of the module.


Conclusion

This article showed how to send new properties to a given component by passing customized json to app.js that added to the config array. Please keep in mind, that with this example, the new maxItems property is set to the original minicart component first. And this is all done in place after server side PHP has executed.

However, on the client end, at the moment of applying the mixins, I have instantiated an actual original minicart object, read from it the new property and updated an existing one. Not the most natural thing to do, but after this I hope you get a sense of what could potentially work as a way to pass server side data.

In the next article, we will continue to do an actual override of the component.

Thank you for reading.

How Magento passes server data to minicart.js

If you have ever build some admin grids that would list some entity data, you may have come across Data Sources. Magento recognizes the need to have a separate “channel” to be able to pass any server data into the java script objects and uses this Data Sources heavily in the admin section.

But, at the end of the day, its java script and if you want to make a variable available, you could always add it to the window object and all other js objects will have access to it.
Not ideal, but practical for sure!

In fact this is how its done in the original code:

https://gist.github.com/marjan7790/fba5c72f16a118cc66f60d7cc1fd67f2

Where is the checkout object set? Lets try common sense again! 🙂

It should be part of the checkout module, and it would be probably in a .pthml file. So, ask your favorite IDE to search in the vendor/magento/module-checkout folder for phrases like “window.checkout =”. Restrict this to a .pthml files only for faster processing.

Your IDE should find a match in

https://gist.github.com/marjan7790/d6331447c83d420014d22c4a05a30ca2

The code looks like

https://gist.github.com/marjan7790/b6c7b2fcb8bfadfd3121ac77a6899acf

Yes, the $block has a method that creates a checkout object, which among other properties, has the maxItemsToDisplay set as well.

By the way, if you were to debug, eventually you will figure the maxItemsToDisplay gets to be populated from the configuration value from

Configuration -> Sales -> Checkout (Common sense, remember?) -> Shopping cart Sidebar -> Maximum number of items to display

But regardless of that, you should by now have an idea how to edit this if needed. There can be many ways like:

  • override the block by adding new method like getMySerializedConfig() and call that method in an overridden minicart.phtml file
  • override the minicart.phtml by adding a new like

https://gist.github.com/marjan7790/004041751ed52cd46cf449899d1b31f5

There are various possibilities, it really depends on the needs, but, eventually, we will need to focus on passing values directly to the <script type=”text/x-magento-init”>, or in particular, extending this code:

https://gist.github.com/marjan7790/eadbdbe6a8e23dd7b95eea7de1fa80ff

The next article tries to pass our own server data to the Minicart.