Skip to main content
Skip table of contents

Context, events, and binding



Learning objectives

In this lesson, you will learn about communication between portlets, including:

  • Portal bus events: events that can be broadcast between portlets.
  • Context events: shared context between portlets.
  • Binding of controls to the business model.
  • How to do all this from AFL portlets

Key concepts

  • The bus is where inter-portal action takes place. Typically, an event is raised, potentially with parameters, and we handle the event in another portlet.
  • Handlers within AFL portlets are typically "on<something>" events on UI controls (for example, onClick). Standard JavaScript stuff.
  • Sharing context or events between portlets where we communicate over the portal bus, which is a communication bus that enables us to send and receive events with parameters. An important event is sysContext, which provides a context variable name and value as parameters. One portlet can say "I've changed the EntityId context and it is now Tim Lamont," and other portlets that are listening can respond.
  • Context is shared between portlets. For instance, in the Contacts workspace, the main context variable is EntityId (case is important). There are two types of participants - context setters and context receivers.

  • The bus capabilities are supplied by the nexj/ui/bus plugin module to nexj/ui. It provides enhancements to the Portlet control for onContext events, the .context() property, the ui.ContextValue() object for binding, and more.
  • Binding is provided by the nexj/ui/obj plugin module to nexj/ui and also the nexj/rpc/obj module. These libraries provide query capabilities with the ui.ObjectCollection(...) and different types of bound values for controls such as ui.AttributeValue.

In preparation for the learning activities in this lesson, make sure that your files from the previous lesson contain the following source code. If not, copy the code into your files before continuing.

training:Contact.portal

XML
<Portal caption="idsa.training.portal.Contact.caption">
   <Tool name="toolNotifications" caption="ids.notifications" icon="icon:notifications" event="setSidebar" parameter="NOTIFICATIONS"/>   
   <WorkspaceRef name="refSandbox" workspace="training:Sandbox"/>
   <WorkspaceRef name="refHome" workspace="mda:Home"/>
   <WorkspaceRef name="refContacts" workspace="mda:Contacts"/>
   <Drawer event="setSidebar" name="refNotifications" portlet="mda:NotificationDrawer"/>
</Portal>


training:Sandbox.workspace

XML
<Workspace caption="idsa.training.workspace.Sandbox.caption" icon="icon:attach_money">
   <Page name="page">
      <Tab layout="cols:3 fluid:true" name="tabDetail" type="grid">
         <PortletRef name="mda_EntityNavigator_portlet" portlet="mda:EntityNavigator"/>
         <PortletRef name="app_myportlet" portlet="app/myportlet"/>
      </Tab>
   </Page>
</Workspace>


idsa.training.en.strings

CODE
idsa.training.portal.Contact.caption=My Training Application
idsa.training.workspace.Sandbox.caption=Sandbox


training/mod/app/myportlet.js

JS
/** Example AFL Portlet */
define("app/myportlet", ["nexj/sys", "nexj/ui"],
    function(sys, ui) {"use strict";
    return function() {
        return ui("Portlet", {children: [
        	ui("Card", {caption: "AFL Card", layout: {cols: 4}, children: [
				ui("Label", {caption: new ui.Value("Label"), layout: {span: 3}}),
	            ui("Text", {value: new ui.Value(), caption: "First Name", layout: {span: 2}}),
	            ui("Text", {value: new ui.Value(), caption: "Last Name", layout: {span: 2}}),
				ui("Button", {
					caption: "Display It", 
					layout: {span: 2}, 
					onClick: function () {
						ui("Dialog", {
							caption: "The Dialog Caption", 
							size: "x", 
							children: [
								ui("Card", {children: [
									ui("Text", {caption: "First Name"}), 
									ui("Text", {caption: "Last Name"})
								]})
							]
						}).open();
				}}),
            ]})
        ]});
    }}
)


Working with Context

First you are going to change the Label control to bind to a ui.ContextValue() model rather than a plain old ui.Value() model.

Change the Label definition to the following code:

JS
				ui("Label", {caption: new ui.ContextValue("EntityId"), layout: {span: 3}}),

This should result in something like:

Notice as you click around in the list that the Context label is updating to the unique identifier of the selected item. This is because you included the nexj/ui/bus plugin module and set the value of the context label based on the ui.ContextValue("EntityId") model. This kind of object binds to the context variable passed in i.e. "EntityId".

Another way to detect context changes is by inspecting your portlet's context property. Here you will add a button that will raise an alert with the value of the EntityId context.

Update your portlet as follows:

JS
/** Example AFL Portlet */
define("app/myportlet", ["nexj/sys", "nexj/ui", "nexj/rpc/obj", "nexj/ui/obj", "nexj/ui/bus"],
    function(sys, ui, obj) {"use strict";
	var portlet;
    return function() {
        return portlet = ui("Portlet", {children: [
        	ui("Card", {caption: "AFL Card", layout: {cols: 4}, children: [
				ui("Label", {caption: new ui.ContextValue("EntityId"), layout: {span: 3}}),
	            ui("Text", {value: new ui.Value(), caption: "First Name", layout: {span: 2}}),
	            ui("Text", {value: new ui.Value(), caption: "Last Name", layout: {span: 2}}),
				ui("Button", {caption: "Show Context in Flash", layout: {span: 2}, onClick: function () {ui.Flash.open(portlet.context("EntityId").value())}}),
            ]})
        ]});
    }}
)

First, you added a few more module dependencies to support binding and the context bus.


Although some of these modules would have been available just because our portlet is running in the Portal Application, which would load the bus for instance, we explicitly added them here as a best practice so the portlet can run outside of the Portal Application, if required.

Then we declared a variable called portlet and set it to the ui("Portlet" control. You then show the portlet.context("EntityId") context in a flash when you click the new button. This will result in something like:

As you move towards binding, let's look at the onContext event of the portlet. Update your portlet definition as follows.

training/mod/app/myportlet

JS
/** Example AFL Portlet */
define("app/myportlet", ["nexj/sys", "nexj/ui", "nexj/rpc/obj", "nexj/ui/obj", "nexj/ui/bus"],
    function(sys, ui, obj) {"use strict";
	var portlet;
    return function() {
        return portlet = ui("Portlet", {children: [
        	ui("Card", {caption: "AFL Card", layout: {cols: 4}, children: [
				ui("Label", {caption: new ui.ContextValue("EntityId"), layout: {span: 3}}),
	            ui("Text", {value: new ui.Value(), caption: "First Name", layout: {span: 2}}),
	            ui("Text", {value: new ui.Value(), caption: "Last Name", layout: {span: 2}}),
				ui("Button", {caption: "Show Context in Flash", layout: {span: 2}, onClick: function () {ui.Flash.open(portlet.context("EntityId").value())}}),
            ]})],
			onContext: {EntityId: function(name, value) {ui.Flash.open("name: '" + name + "', value: '" + value + "'")}}
		});
    }}
)

As you click around in the list, you should see a flash something like the following.

What's going on here? The portlet's onContext() event has a set of properties with context name and handler function pairs. The handler function for a particular context receives the context name and the current value of the context when it changes.

We will use this now to illustrate binding to the business model.

Binding

Most controls have the ability to bind to models. Some models are simply JSON structures. Some others are still JSON structures, but are created from intelligent and efficient rpc with the server.

Collection controls have properties like items or rows. Other controls, like Labels or Text controls, simply have a value property.

Again, a great reference is the demo apps at http://localhost:7080/training/ui/demo along with the source code found in `training/mod/app`. For an example of a collection binding, see the masterdetail-obj example in `training/mod/app/masterdetail-obj.js`


In this example, you will create a model that gets an instance of an Entity from the business model with an ObjectCollection() model. You will then bind some UI controls to that model with the ui.AttributeValue() model wrapper. It is really pretty simple.

First create the model and make a request to the server. Here you will:

  • add a model: property to our card that will select an instance where the firstName = "Tim".
  • bind the two text controls to the firstName and lastName of the current instance using the AttributeValue model.

Update your portlet definition as follows:

training/mod/app/myportlet.js

JS
/** Example AFL Portlet */
define("app/myportlet", ["nexj/sys", "nexj/ui", "nexj/rpc/obj", "nexj/ui/obj", "nexj/ui/bus"],
    function(sys, ui, obj) {"use strict";
	var portlet;
    return function() {
        return portlet = ui("Portlet", {children: [
        	ui("Card", {
        		caption: "AFL Card", 
        		layout: {cols: 4}, 
        		model: ui("ObjectCollection", {name: "contacts", type: "Entity", where: obj.op("=", obj.attr("firstName"), "Tim")}), 
        		children: [
					ui("Label", {caption: new ui.ContextValue("EntityId"), layout: {span: 3}}),
		            ui("Text", {value: new ui.AttributeValue("contacts", "firstName", "string"), caption: "First Name", layout: {span: 2}}),
		            ui("Text", {value: new ui.AttributeValue("contacts", "lastName", "string"), caption: "Last Name", layout: {span: 2}}),
					ui("Button", {caption: "Show Context in Flash", layout: {span: 2}, onClick: function () {ui.Flash.open(portlet.context("EntityId").value())}}),
            ]})],
			onContext: {EntityId: function(name, value) {ui.Flash.open("name: '" + name + "', value: '" + value + "'")}}
		});
    }}
)

Notice the changes on line 10, 13 and 14. Refresh your application and you should see Tim Lamont in your AFL Card.

On line 10 you are using the nexj/rpc/obj module to construct the where clause `(= (@ firstName) "Tim"). This is what the obj.op() function does.

This, however is not dynamic. We are always selecting firstName = "Tim" without listening to EntityId context changes. To make this happen:

  • add a variable called contacts
  • assign that variable to our model
  • change our model's initial where clause to false so we don't make an initial query
  • set the where clause on our model dynamically whenever the context changes.

Update your portlet definition as follows:

training/mod/app/myportlet.js

JS
/** Example AFL Portlet */
define("app/myportlet", ["nexj/sys", "nexj/ui", "nexj/rpc/obj", "nexj/ui/obj", "nexj/ui/bus"],
    function(sys, ui, obj) {"use strict";
	var portlet, contacts;
    return function() {
        return portlet = ui("Portlet", {children: [
        	ui("Card", {
        		caption: "AFL Card", 
        		layout: {cols: 4}, 
        		model: contacts = ui("ObjectCollection", {name: "contacts", type: "Entity", where: false}), 
        		children: [
					ui("Label", {caption: new ui.ContextValue("EntityId"), layout: {span: 3}}),
		            ui("Text", {value: new ui.AttributeValue("contacts", "firstName", "string"), caption: "First Name", layout: {span: 2}}),
		            ui("Text", {value: new ui.AttributeValue("contacts", "lastName", "string"), caption: "Last Name", layout: {span: 2}}),
					ui("Button", {caption: "Show Context in Flash", layout: {span: 2}, onClick: function () {ui.Flash.open(portlet.context("EntityId").value())}}),
            ]})],
			onContext: {EntityId: function(name, value) {contacts.where(obj.op("=", obj.attr(""), obj.oid(value))).reload();}}
		});
    }}
)

Now as you click around in the list, you should see the first name and last name updating correctly. We made changes here on line 4, 10, and 17.

Before you were selecting `(= (@ lastName) "Tim") with obj.op("=", obj.attr("lastName"), "Tim"). Now, the onContext handler sets the model's where clause to be `(= (@) ,(oid value)). Where obj.attr("") means the oid of the object (in other words, (@)) and obj.oid(value) converts the string context EntityId into an actual object identifier to pass to the server. Once the contacts model's where clause has been set, you chain on a .reload() to requery.

Events

To trigger events, simply use portlet.fire(<eventName>, <parameters>, <system>). For example:




JS
portlet.fire("sysContext", {"EntityId": previous, "ObjectId": previous}, true);





To handle events, you may use the onEvent: method of the portlet, similar to the onContext method. For example:

JS
			onEvent: function(event, params, system) {alert(event, params, system);


Summary

This lesson presented fundamental information and examples of using context, binding, and events in AFL portlets.

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.