Internal communication between client side elements (MessageBus)

Introduction

The purpose of this document is to explain the use of the bus of message on the client side.

As the interface is divided in several components and plugins a bus has been written to allow them to communicate.

For example, it allows windows (tools) to communicate which each other.
The search tool can say in the bus : The content X had been selected. As a consequence, the history tool can say : I am out of date ; while the edit button will ask the server for a refresh (to know if user can edit X).

The big advantage of the bus of message, is that tools do not know each other ; and at any time a new tool can be added (opened) : it will access to all information and will be updated by other tools.



Sample

Here is a sample of code to say something to the bus

var msgBuilder = org.ametys.messagebus.bus.MessageBuilder.getInstance();
var targets = [];
for (var i = 0; i < records.length; i++)
{
    targets.push(msgBuilder.createTarget("content", {'id': records[i].data.id}));
}

var message = msgBuilder.createMessage(org.ametys.ribbon.RibbonManager.EVENTTYPE_SELECTIONCHANGED, null, targets);
org.ametys.messagebus.MessageBus.getInstance().fireMessages([message]);

This sample iterate on a record array to say something like : The content X, Y and Z are selected.

Message

 Constitution

A message has a type and mutiple targets.

The type declares the kind of message : selection, focus...

The targets declares what is impacted by the message. A target can be a content, a page, a tool...

See the API of org.ametys.messagebus.message.Message for more information.

 Creation

To create a message, you must use MessageBuilder#createMessage with the following parameters:

  • the event type name
    Commons event use constants from Ext.ametys.ribbon.RibbonManager to transmit the name.
    Any string is a type name and the goal of such a name is to be shared with other tools, so try to use the same conventions as everybody!
    For exemple, if you send the event type "foo" to say that something has been selected... don't be surprise if other tools just ignore it.
  • the event type parameters
    Some event type will need parameters. See the description of the event type you use to know what parameters are needed.
    Be careful as there is no verification on parameters, but if you omit one this may lead into bugs on other tools : for example, if the "Delete" event needs as a parameter the login of the user that has deleted ; the history view may be bogus when this event will be thrown without this parameter... Of course, it will be visible only if your tool is opened at the same time as the history.
  • the array of targets
    See under for more information on targets.

The target must be created using a factory.
Creating a target is more complicated that it could appears at first and moreover it could change from one application to another.

The point is that a target has subtargets, that reflects the "involved" target of the message.
Imagine a tool that only know about "contents" such as the history view. If you say "I have selected the Page A", nothing will happen, whereas if you say "I have selected the Page A (so the content X)" : the history view will work fine.

The third parameter of MessageBuilder#createMessage is an array of targets involved, but each target can have subtargets and so on.
The value for a message of several pages selection can be the following :

  • Page A
    • Content X
    • Content Y
  • Page B
    • Service S

The MessageBuilder allows you to create a target (it will call the right factory depending on the target type)

See the API of org.ametys.messagebus.bus.MessageBuilder for more information.

Send a message

Once created you can simply send the message using the MessageBus.

See the API of org.ametys.messagebus.MessageBus for more information.

Receive a message

Registering

To receive messages from the bus you have to register a callback.

Be careful, many components are automatically registered on the bus. Check that before manually register or you will receive every messages twice and that will slow down the application.

To register, use MessageBus#register. Do not forget to unregister when you are closed or destroy.

org.ametys.messagebus.MessageBus.getInstance().register(this);

When registered you have to implement the MessageListener interface. You will have to write a onMessage handler.

Be careful, there are MANY messages on the bus so you code have to be very optimized.

Handling

Here is a sample of a handler to a selection event on contents:

onMessage = function (messages)
{
  // SELECTION
  for (var i = messages.length - 1; i >= 0; i--)
  {
    if (messages[i].getType().getName() == org.ametys.ribbon.RibbonManager.EVENTTYPE_SELECTIONCHANGED)
    {
      var targets = messages[i].getTargets();
      var allTargets = org.ametys.messagebus.message.MessageTargetHelper.findAll(targets, function(target) {return target.getType() == 'content'});

      // YOUR CODE HERE
      return;
    }
  }
}

As you can see, you may receive many events at once.
In that case we read on in the reverse order because only the last selection do interest us (if there are many at once).

Here is a sample of behavior you may implement in that method:
When search tool receives the focus, it will send into the bus its current selection (the one that was selected before it lost focus): Content X, Y, Z are selected. So other tools can synchronize.
When search tool is closed, it will send a null selection event.
When content tool receives the focus, it must of course send "the content X is selected"

Be careful, when the focus is lost (blur event) do not send a null selection: read tools like history tool would be cleared as soon as you click on it.

As targets is an array, and could have subtargets, that have subtargets and so on... finding a target can be complex. Use the MessageTargetHelper to explore the targets.

DO NOT send a message in the onMessage method. Use external mecanism, such as the outofdate/refresh for the tools

See the API of org.ametys.messagebus.MessageBus ; org.ametys.messagebus.MessageListener and org.ametys.messagebus.message.MessageTargetHelper for more information.

 Off topic

You understand now how to be aware of the current selection... only when you receive the event.

If you want to know what is the selection in other situations (you are not registered, you have just registered because your tool is just opened or you don't want to remember the last selection in your tool...).

You then can use org.ametys.ribbon.RibbonManager.getInstance().getCurrentSelectionTargets()

Be careful, DO NOT CALL getCurrentSelectionTargets in your onMessage method (except if the message has nothing to deal with selection) because sometimes you will have the previous selection, sometimes the new one.

Note

Most of the tools send a null selection message to the bus, even if they have nothing to deal with.

If not you will meet strange behaviors: buttons that are still available, view not synchronized.

To do so, add the 'focus' listener on the panel of your tool and here is the code of the listener:

var message = org.ametys.messagebus.bus.MessageBuilder.getInstance().createMessage(org.ametys.ribbon.RibbonManager.EVENTTYPE_SELECTIONCHANGED, null, []);
org.ametys.messagebus.MessageBus.getInstance().fireMessages([message]);

 A very few number of tools may not send this event: these are the 'read' tools such as Details or History.

 Note also that you should send a null selection to the bus on the "close" handler of your tool

MessageFactory

To use a message factory, please read the "creation" part of this document.

To create a message factory, you have to declare it in a plugin

<extension id="org.ametys.cms.userinterface.BasicMessageTargetFactory"
           point="org.ametys.cms.workspace.uitool.MessageTargetFactoriesManager"
           class="org.ametys.cms.workspace.impl.StaticContextualClientSideElement">
    <action class="org.ametys.messagebus.bus.impl.BasicMessageTargetFactory">
         <param name="type">*</param>
    </action>
</extension>

The JS class used have to implement the org.ametys.messagebus.bus.MessageTargetFactory interface. This API is very simple: there is a single method that ask for the creation of a type of event.

The type parameter is mandatory, it will be used to know which factory can handle which kind of targets.

The '*' type is for the factory that handle all unknown types.

For a simple application, this BasicMessageTargetFactory can be enough.

Here is a sample of this basic factory:

org.ametys.messagebus.bus.impl.BasicMessageTargetFactory.prototype.createTarget = function (type, parameters)
{
	return new org.ametys.messagebus.message.MessageTarget(type, parameters, []);
}

Here is a sample of a "page" message target factory

	var target = new org.ametys.messagebus.message.MessageTarget(type, parameters, []);

	var response = Tools.postFromUrl(getPluginDirectUrl("web") + "/repository/page-info", "id=" + pageId);
	if (response != null)
	{
		var contents = response.selectNodes("/page/contents/content");
		for (var i = 0; i < contents.length; i++)
		{
			var contentName = contents[i].selectSingleNode("name")[Tools.xmlTextContent];
			var subTarget = org.ametys.messagebus.bus.MessageBuilder.getInstance().createTarget("content", {'id': contentName});
			target.getSubTargets().push(subTarget);
		}
	}
        return target;