How to? Custom Widget constrain by parent entity and refresh on changes within the parent.

0
Hello Mendix and widget developers, i'm in need of information and preferably example widgets that show the desired functionality described below or information that answers the following question: Which Client API functions should I use to create a widget that offers the same options as seen in the Selectable objects tab of the original Mendix reference selector? Which consists of: Source (Database / Microflow) Xpath constraint Apply context Remove from context Constrained by Sort order With a special interest in how I can make my widget context aware and making my own widget update available options either limited by "xpath", "Constrained by" or re-trigger the microflow source based on changes made inside that context and other child widgets inside that the context. I would like it if people either offered example widgets so that i can analyse the implementation. Or any information about what the possibilities are for context awareness of a custom widget and which functions to use. Thank you for any information provided, Kind regards, Marnix
asked
3 answers
6

Hi,

To give an answer to the question "Contrained by". You can find the answer in a combination of both the Mendix Client API documentation and the XML reference Guide that can be found if you go to "http://developers.mendix.com" -> "How to's" -> "Custom Widget Development" -> "XML reference guide":

https://world.mendix.com/display/howto50/XML+Reference+Guide

There you will find all the property possibilities you can configure for your custom widgets. Including an example how to configure a property that allows you to set a constraint.


EntityConstraint Property

The EntityConstraint lets you put a constraint on either the entity you specify with entityProperty or, if needsEntityContext is set to true and no entityProperty is defined, to the entity passed as context.

<property key="constraint" type="entityConstraint" entityProperty="color">
    <caption>Color constraint</caption>
    <category>Data source</category>
    <description>The xpath constraint on the entity.</description>
</property>

Now there is a pitfall in setting a constraint. An entityConstraint only works when you search for entities through an XPATH mx.data.get request:

Getting an object through XPath


mx.data.get({
    xpath: "//MyModule.Entity[" + this.constraint + "]",
    callback : function(objs) {
        console.log("Received " + objs.length + " MxObjects");
    }
}, this);

You can normally configure something like [%CurrentObject%] inside your constraint. This is not supported out of the box by just composing the XPATH through a custom widget property as you see in the example above.

So how to do this?!??


Example combining [%CurrentObject%] in constraint

// From the AppStoreWidgetBoilerplate
postCreate: function (obj, callback) {
    this._contextObj = obj // Will get the MxObj and remember this inside your widget.
}
...
_updateRendering: functing (callback) {
    var alteredConstraint = this.constraint.split("[%CurrentObject%]").join(this._contextObj.getGuid()),
        contraintComposed = "//MyModule.Entity[MyModule.MyReference_OtherEntity/MyModule.OtherEntity=" + this.alteredConstraint + "]";

    mx.data.get({
        xpath: contraintComposed,
        callback : function(objs) {
            console.log("Received " + objs.length + " MxObjects");
        }
    }, this);

    callback();
}

The MxObject you receive in postCreate can be saved in your widget with this._contextObj. Then in the _updateRendering you can get it's GUID and replace the text (string) "[%CurrentObject%]" with this GUID. The following helper class can get your parts of your XPATH that you need to construct a proper XPATH with constraint.


Helper Class

define([], function() {
    'use strict';

    return function() {

        return {

            xpath: {

                getLastAttribute: function (xpath) {

                    var xpathParts = xpath.split('/');

                    return xpathParts[ xpathParts.length - 1 ];

                },

                getLastEntity: function (xpath) {

                    var xpathParts = xpath.split('/');
                    xpathParts.pop();

                    return xpathParts[ xpathParts.length - 1 ];

                },

                getReference: function (xpath) {

                    var xpathParts = xpath.split('/');

                    return xpathParts[ 0 ];

                },

                noAttribute: function (xpath) {

                    var xpathParts = xpath.split('/'),
                        noAttributeXpathPath = null;

                    xpathParts.pop();
                    noAttributeXpathPath = xpathParts.join('/');

                    return noAttributeXpathPath;

                },

                noLastEntity: function (xpath) {

                    var xpathParts = xpath.split('/'),
                        noAttributeXpathPath = null;

                    xpathParts.pop();
                    noAttributeXpathPath = xpathParts.join('/');

                    return noAttributeXpathPath;

                },

                reverse: function (xpath) {

                    var xpathParts = xpath.split('/'),
                        reverseXpathPath = null;

                    xpathParts.reverse();
                    reverseXpathPath = xpathParts.join('/');

                    return reverseXpathPath;

                },

                buildReverseXpath: function (xpath) {

                    var newXpath = '//' + this.getLastEntity(xpath) + '[';

                    newXpath += this.reverse(this.noLastEntity(this.noAttribute(xpath)));

                    newXpath += ' = \'[%CurrentObject%]\']';

                    return newXpath;

                },

                replaceCurrentObject: function (xpath, obj) {

                    return xpath.split('[%CurrentObject%]').join(obj.getGuid());

                }

            }

        };
    };

});

Hopes this will help you out.

answered
1

Hi,

Your thinking is correct. And you should implement it that way. If you wish to dynamicly / magicly descover references and start to attach your widget to changes on entities that might over association be connected to some entity.

Take a look at the "this.subscribe" to hook your widget to updates on the collected entiteit. Getting mxobject over association can also be written a bit easier with a function called .fetch. That is available on each mxobject.

But remember using these functions might trigger allot of requests to the server. It is always recommended to look how you can simplify your widget so that it can work with eighter one or over one assiociation related entity.

You are getting freedom in the Mendix Client API. But with freedom there comes a great responsebility.

Sometimes collecting complex data structures can just performance wise be better done with a custom requesthandler. That returns JSON.

answered
0

Hi Richard,

thanks for the elaborate answer on how to apply the context itself.

By the looks of it when i have the context object GUID, i should be able to call retrieve any GUID's related to my context entity? with

mendix.lib.MxMetaObject.getReferenceAttributes()

which should then allow me to retrieve the existing references by mendix.lib.MxObject.getReferences()

which should then allow me to get their GUID's and track any changes made to those entities whenever the context is loaded/updated, if these are not empty i should be able to include them in my constaint for the reference i'm trying to select?

Is my thinking here correct? Or do you see any limitations?

So the scenario i'm trying to cover is, my context object has 2 or more reference selectors, and these references should limit each other too. I think my thoughts above are complex but should cover this kind of functionality.

answered