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.