Learning the widgettemplate

The most important thing to learn is the widgettemplate. It dictates which functions are being called at which moment. Without knowdledge of this vital part you will be scratching your head at the strange behaviour of your widget.

Prerequisites

We assume you have at least:


Basic understanding of Mendix, i.e. you know how to create microflows and make pages.
Basic understanding of Javascript, i.e. you know how functions and callbacks work,
Basic understanding of CSS, i.e. you know how to give text a different color and know the basics of selector specificity.
Basic understanding of HTML.


If not follow the excellent tutorials in the links.

Goal

We will be displaying different currencies normally not available in stock Mendix. Using this example we will learn the different lifecycle stages of a custom widget.

Dijit Widgets

The frontend of the current Mendix applications use the Dojo framework. Custom and stock widgets are written using the Dojo’s UI framwork Dijit. This framework governs the way in which widgets are created, modified and eventually destroyed. So let’s take a look at the different key moments, a.k.a the lifecycle, of Mendix custom widgets.

The beginning of a widget

Open up the Mendix project in the intial code section, run it, and log in using the demo_user account and click on ‘go to shopping cart’. Open up the developer console of Google Chrome, by pressing F12. In the sources tab, go to localhost:8080/widgets/CustomCurrency/widget/CustomCurrency.js. Click on the linenumber to set a breakpoint inside the constructor, the postcreate and the update. Refresh the page, click ‘go to shopping cart’, the page will stop loading and the ‘Paused in debugger’ message will apear.

Constructor

First the constructor is called, this moment is used to set instance specific variables. Type in this in the console, and inspect the mxcontext property. Note that most of it’s properties are empty. Press continue and read on.

Postcreate

Second, the postcreate method is called. This method is very important because it happens before the rendering of the DOM, but you have all the nodes from your /widget/template/CustomCurrency.html file. This makes it perfect for some last minute adjustments before the widget is presented to your audience. Type this in the console, and you will see the domnode property referring to the root node in the CustomCurrency.html file. Just before this method a neat thing called ‘variable substitution’ happens. Let’s assume you have a currency widget that has a footer that tells you where to change the currency preference. When you use this widget for a different client, you might want to change the explanation. We don’t want to let the business developer change it in the HTML template, so we add a dialog to the modeler by editing the CustomCurrency.xml.

<property key="currencynotice" type="string" required="true" defaultValue="You can change your preferential currency in the account section of the site.">
    <caption>Currency change txt</caption>
    <category>Appearance</category>
    <description>Explanation on how to change the currency preferences.</description>
</property>

Then inside the CustomCurrency.html file we add a div containing a ‘${variablename}’.

<div class="widgetname">
    <div>${currencynotice}</div>
</div>

The ‘${currencynotice}’ will now be replaced by the currencynotice property available in the postcreate method. So please make sure that you type the variable name correctly! The more eagle-eyed readers would have noticed that this method does not work for Mendix context objects, because when you checked this earlier on, the mxcontext property was not filled, and therefore the variable substitution will fail. But we can still make this work in the update method.

Update

Third, the update method is called, this is a method specific to Mendix custom widgets, so you don’t see any documentation on the official Dijit docs about this method. The paramaters consist of the Mendix object that is in the context and a callback you should execute after you’re done in the update function. Usually the this._contextObj is set to the contextobject so that we can access it in another function. If you want to do modifications to the DOM based on the Mendix context object, you should do it in this method! So let’s get the ‘price’ attribute from the product entity and display it on the page. Remove all the breakpoints and continue on.

Filling the DOM with data

The boilerplate gives us a few predefined methods for updating the DOM and executing microflows and callbacks. The _updateRendering function is used to make changes to the DOM, however you may add functions yourself to do various other DOM manipulations. In line 5-11 you will see that a lot of Dojo libaries are imported to used to manipulate the DOM. This widget mainly uses the dojo/dom-construct. Using the toDom() function we create a Domnode that we can place into the dom with the place(newNode,targetNode) function. Please note that the this.domNode field is always filled with the domNode of the widget, so we can use this as an orientation point for our dom manipulations. To add some other custom domNodes that we can readilly access in our widget we can add the data-dojo-attach-point property in our HTML template like so:

<div class="widgetname" data-dojo-attach-point="widgetBase">
    <div data-dojo-attach-point="priceNode"></div>
    <div>${currencynotice}</div>
</div>

The this.priceNode property will now point to the pricenode div. First let’s change the update method so that it will call a custom method

      update: function(obj, callback) {
        logger.debug(this.id + ".update");

        this._contextObj = obj;
        // Add this
        this._getCurrentAccount();
        this._updateRendering(callback);
      },

Next, let’s make that method. In our Mendix project we save our currency preference in the account entity. Let’s get that account entity to our custom widget! We can of course set the widget in the acount context entity, however we can only pick one context entity. I chose the ‘product’ entity since this widget is all about product price, and not the current account!

    _getCurrentAccount: function(){
        logger.debug(this.id +  "_getCurrentAccount");
        // Get the userID, this is the same as the $Account/id attribute.
        var userID = mx.session.getUserId();
        // Get the account object from the database using a XPath query.
        // When the object is retrieved, execute the _getLocalisedString method with the current context.
        mx.data.get({
            xpath: "//Administration.Account[id = " + userID + "]",
            callback: this._getLocalisedString.bind(this)
            });
        },

We get the user ID from the mx.session.getUserId() function, and use this id to get the account entity from the datase using mx.data.get() function, using a XPath. We specify a callback in this function, this basically is another function that we want to execute after the result of the query is known. Let’s make this function that does something with the account that we got.

        _getLocalisedString: function(currentAccount) {
            logger.debug(this.id +  "_getLocalisedString");
            // Get the currency preference from the Account entity.
            // The currentAccount variable is an array, this will only have one element, since you only have one account.
            var currencyPreference = currentAccount[0].get("CurrencyPreference");
            // Get the price attribute from the contextobject, and rounding the price two 2 decimals.
            var priceString = this._contextObj.get("Price").toFixed(2);
            // We need a float instead of a string.
            var priceFloat = parseFloat(priceString);
            // This is were the magic happens.
            this._setPriceString(priceFloat, currencyPreference);
        },

Just as in the previous lesson we get an attribute from an MxObject, in this case the currency preference. Furthermore we get the price from the contextobject rounded to two decimals, we parse it into and float and pass this to the next function.

      _setPriceString: function(priceFloat, locale) {
        logger.debug(this.id + "_setPriceString");
        var localeString;
        var options;
        // Switch over the locale from the account variable, and set the localestring and options object for the toLocaleString() method.
        switch (locale) {
          case "Dutch":
            localeString = "nl-NL";
            options = { style: "currency", currency: "EUR" };
            break;

          case "English":
            localeString = "en-UK";
            options = { style: "currency", currency: "GBP" };
            break;

          case "German":
            localeString = "de-DE";
            options = { style: "currency", currency: "EUR" };
            break;

          case "Norwegian":
            localeString = "nn-NO";
            options = { style: "currency", currency: "NOK" };
            break;
          default:
            localeString = "";
            options = {};
        }
        // We use the toLocaleString() method to print out the correct currency format.
        var formattedPrice = priceFloat.toLocaleString(localeString, options);
        // The pricenode comes from the HTML template in the data-dojo-attach-point="priceNode"
        this.priceNode.innerHTML = formattedPrice;

In this function we check what the currency preference is, and based on this we set two variables; the localeString string and options object. We use these variables in the toLocaleString function of the float variable. You can find more about this functionality in the MDN docs.. We update the innerHTML of the priceNode that we set in the CustomCurrency.html file, and we are done!

Uninitialize

After we navigate away, the widget is teared down. This is done in the Uninitialize method. If you have registered any event handlers, unregister them in this method.

Gotcha’s

You need to execute the callback that is provided in the update function! If not, your widget will fail to load and will be stuck in an infinite loading loop.

    update: function(obj, callback) {
        logger.debug(this.id + ".update");

        this._contextObj = obj;
        // Do not forget to execute the callback!
        this._updateRendering(callback);
      },

You might have noticed that the currency notation changes, but not the amount. This is intentional, as you want to keep your logic for changing the currencies and the exchange rates server side.

Conclusion

In this lesson we made a widget that displays currency in the preference of the user. In the next lesson we will be setting up tools that help us develop faster by automating the zipping and copying to and from folders.

Final code

The final code is available here.

Further reading

Widget lifecycle