While going through my list of requirements for our AMIS re-usable ADF taskflow library, I ran into the following requirement: publish a contextual event on a Drag and Drop action. If you know contextual events, you immediately notice that there is no way to publish a contextual event from a Drag and Drop action. There are two types of events: ADF Binding Type events or JSF Component Type Events such as actionEvents, valueChangeEvents or clickEvents (also called eventBindings). However none of those is directly related to Drag and Drop. With some knowledge of the ADF Framework you will be able to create contextual events anyway. This post describes how to publish contextual events when a Drag and Drop action occurs.
This post contains several approaches to implement Drag and Drop between independent ADF libraries and how to publish contextual events associated with the Drag and Drop action. The functionality described in this post is contained in one ADF Master Application that contains several taskflows that are included from ADF libraries. For each step there are two ADF libraries involved; One for dragging, and one for dropping.
Step one : Does Drag and Drop work between components from independent ADF Libraries ?
To get this started I have to make sure Drag and Drop can work between components that are located in two independent ADF Libraries.
I created two simple ADF applications (dndOne and dndTwo). The first one contains a taskflow with a pagefragment having a clientAttribute and an attributeDragSource in a couple of panelboxes.
<?xml version='1.0' encoding='UTF-8'?> <jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1" xmlns:af="http://xmlns.oracle.com/adf/faces/rich"> <af:panelGroupLayout id="pgl1"> <af:panelBox id="pb2" text="Drag the text below"> <af:inputText value="Luc Bors" id="it1"> <af:clientAttribute name="invisible" value="You cannot see this untill dropped"/> <af:attributeDragSource attribute="invisible"/> </af:inputText> </af:panelBox> <af:panelBox id="pb1" text="Drop below"> <af:outputText value=".................................." id="ot1"> <af:attributeDropTarget attribute="value"/> </af:outputText> </af:panelBox> </af:panelGroupLayout> </jsp:root>
The second application is almost the same but contains an attributeDropTarget for the attributeDragSource that is created in the first.
<?xml version='1.0' encoding='UTF-8'? > <jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1" xmlns:af="http://xmlns.oracle.com/adf/faces/rich"> <af:panelGroupLayout id="pgl1"> <af:panelBox background="medium" id="pb2" text="Drag the text below"> <af:inputText value="Luc Bors but different" id="it1"> <af:attributeDragSource attribute="value"/> </af:inputText> </af:panelBox> <af:panelBox background="medium" id="pb1" text="Drop below"> <af:outputText value=".................................." id="ot1"> <af:attributeDropTarget attribute="value"/> <af:attributeDropTarget attribute="invisible"/> </af:outputText> </af:panelBox> </af:panelGroupLayout> </jsp:root>
I deployed both applications as ADF libraries, and the taskflows in these libraries I dropped as regions on a page in the master application.
<?xml version='1.0' encoding='UTF-8'?> <jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1" xmlns:af="http://xmlns.oracle.com/adf/faces/rich"> <af:panelGroupLayout layout="horizontal" valign="top" halign="center" id="pgl1"> <af:region value="#{bindings.dndOneTF1.regionModel}" id="r1"/> <af:region value="#{bindings.dndTwoTF1.regionModel}" id="r2"/> </af:panelGroupLayout> </jsp:root>
When running the Application you will see that Attribute Drag and Drop works. Try to drag the “text below” from the left hand upper panelbox to the left hand lower one. This doesn’t work because the attributeDropTarget expects the “value attribute”, and not the “invisible attribute”. Dragging to the right hand lower box however does work because this one expects the “invisible attribute”.
Step 2 : Contextual Events on Drag and Drop; a not so preferred way
With that being proofed, it is now time to extend the functionality. What I actually need is some kind of communication from the component that was the dropTarget to the component that was the dragSource. In some situations I can use the dragDropEndListener on the dragSource, but this is not available for all types of dragSources. That is the main reason to look into contextual events. As I stated in the intro of this post, there are no contextual events related with Drag and Drop actions.
Let’s try a different approach: Catch the dropEvent, and from there somehow produce an event.
Intercepting the drop event is easy. The framework provides a dropTarget component with a dropListener attribute.
<af:dropTarget dropListener="#{pageFlowScope.DndBean.attributeDropped}"> <af:dataFlavor flavorClass="java.lang.String"/> </af:dropTarget>
This way I can receive a dropEvent and handle it in the attributeDropped() method in a pageflow scoped bean.
public DnDAction attributeDropped(DropEvent dropEvent) { // Add event code here... String type = (String)dropEvent.getDragComponent().getAttributes().get("invisible"); System.out.println("type = " + type);
From within this method I have to find a way to produce the contextual event somehow. I have no access to ADF Binding Type events, because I do not have any Business Components and I don’t want to create them. I could use JSF Component Type Events like an ActionEvent. For that I need to produce an ActionEvent. Too bad however that an ActionEvent is in no way associated with a Drag and Drop event. So am I out of options here ? Not completely.
An ActionEvent is associated with a commandButton and to produce an ActionEvent a commandButton has to be invoked. That’s two things: Create a button and invoke it….. The commandButton can be placed in the toolbar facet of the panelBox and I can make it an invisible clinetcomponent. That will make sure the button is present but nobody can see it. I also add an ActionListener to the button in order to have a handle where I can produce the Contextual Event and also add custom payload to it.
<af:panelBox background="medium" id="pb1" text="Drop below" inlineStyle="background-color:Aqua;"> <f:facet name="toolbar"> <af:commandButton text="dummyButton" visible="false" clientComponent="true" actionListener="#{pageFlowScope.DndBean.doAction}" partialSubmit="true" id="cb1"/> </f:facet> ...........................
In the actionListener I add two extra attributes to the component, and I produce the Contextual Event.
public void doAction(ActionEvent actionEvent) { UIComponent component = actionEvent.getComponent(); Map attributes = component.getAttributes(); attributes.put("ParamOne", "Value For Param One"); attributes.put("ParamTwo", Integer.valueOf(11)); BindingContainer bindingContainer = BindingContext.getCurrent().getCurrentBindingsEntry(); JUEventBinding eventBinding = (JUEventBinding)bindingContainer.get("DropEvent"); ActionListener actionListener = (ActionListener)eventBinding.getListener(); actionListener.processAction(actionEvent); }
The only thing I have left to do is to invoke the button. So the question here is:”Can I invoke a button from a backing bean ?”
Here is one way to do it: add JavaScript to the page and invoke it from the bean.
On the pagefragment containing the button I added a javaCode snippet where I find the commandButton by its Id, and queue a partialSubmit on the commandButton.
<af:resource type="javascript"> function pressButton() { var component = AdfPage.PAGE.findComponentByAbsoluteId("pt1:pt_region0:1:r4:0:cb1"); AdfActionEvent.queue(component, component.getPartialSubmit()); } </af:resource>
The Javascript function can be called form the dropListener using the ExtendedRenderKitService. You can add javascript to it and it will be executed for you. I added “pressButton” which is exactly the name of the javascript function above.
public DnDAction attributeDropped(DropEvent dropEvent) { // Add event code here... String type = (String)dropEvent.getDragComponent().getAttributes().get("invisible"); FacesContext context = FacesContext.getCurrentInstance(); ExtendedRenderKitService service = (ExtendedRenderKitService)Service.getRenderKitService(context, ExtendedRenderKitService.class); service.addScript(context, "pressButton();"); ..............................
This is all from the producers point of view.
There is no sense in producing a Contextual Event, if there is no consumer involved. So I also need to handle the event that is being raised.
In the application that needs to consume the event I create a Java class and a datacontrol based on this class.
public class ConsumeEventsBean { public ConsumeEventsBean() { } public void handleDnDEvent(Object payload) { ActionEvent actionEvent = (ActionEvent)payload; UIComponent component = (UIComponent)actionEvent.getSource(); String ParamOne = (String)component.getAttributes().get("ParamOne"); Integer ParamTwo = (Integer)component.getAttributes().get("ParamTwo"); System.out.println(" ParamOne " + ParamOne); System.out.println(" ParamTwo " + ParamTwo); }
In the pageDefinition associated with the page that needs to consume the event I create a Method Action. Easiest way to do this is to drag the operation from the datacontrol onto the page.
Save all, and than delete the button from the page. Make sure to do that in sourceview because that will make sure the methodAction will remain in the pageDefinition.
<methodAction id="handleDnDEvent" InstanceName="ConsumeEventsBean.dataProvider" DataControl="ConsumeEventsBean" RequiresUpdateModel="true" Action="invokeMethod" MethodName="handleDnDEvent" IsViewObjectMethod="false"> <NamedData NDName="payload" NDType="java.lang.Object"/> </methodAction>
After redeploying both ADF libraries I can associate the producer with the consumer. To do that I need to create an EventMap in the pagedefinition of the page in the master application that contains the two regions.
In this event map I can associate the producer with the consumer. The producer is located in one taskFlow and is called DropEvent. The consumer is located in the other one and is called handleDndEvent. Select both from the lists, and add parameters for the consumer.
After running the application and invoking Drag and Drop, you will see in your console that the event is produced, and the consumer prints out the values in the payload. That is the proof that I created a Contextual Event after a Drag and Drop action occurred.
Step 3 : Contextual Events on Drag and Drop; a much better way
Obviously the approach in the previous section is somewhat far fetched and is not a favorable one, however it does work. What I really need here is an approach that I can use very easy and that does not involve a lot of coding in the receiving page or any of its beans.
There is however a very straightforward approach, first described by Edwin Biemond. This approach uses a class to generate and handle Contextual Events.
package nl.amis.technology.dndtwoadvdc.view.ce; import java.util.HashMap; import java.util.Map; public class CeEventPistol { public CeEventPistol() { } public Map<String, Object> fireComplexEvent() { Map<String, Object> eventData = new HashMap<String, Object>(); eventData.put("text1", "hello"); eventData.put("text2", "hello2"); return eventData; } public String captureComplexEvents(Object parameter) { System.out.println("dndOneAdv - capture complex event"); Map<String, Object> eventData = (Map<String, Object>)parameter; System.out.println((String)eventData.get("text1") + " / " + (String)eventData.get("text2")); return (String)eventData.get("text1") + " / " + (String)eventData.get("text2"); } }
You can create a datacontrol based on this class and create a method binding in your pageDef, based on the fireComplexEvent() method. Once you have this, it is very easy to invoke this method binding from within the dropListener. Just find the bindingContainer, the operationBinding associated with the fireComplexEvent method, execute it and the Event is fired.
public DnDAction attributeDropped(DropEvent dropEvent) { // Add event code here... String type = (String)dropEvent.getDragComponent().getAttributes().get("invisible"); BindingContainer bindingContainer = BindingContext.getCurrent().getCurrentBindingsEntry(); OperationBinding oper = bindingContainer.getOperationBinding("fireComplexEvent"); oper.execute(); return DnDAction.COPY; }
So instead of an invisible commandButton invoked from javaScript, I now directly create an event from within the dropListener. Handling the event is the same as in step 2, I only use a different method for handling.
The event Map in the MasterApplication looks just as below, where the fireComplexEvent is based on the custom DataControl in the producer, and the captureComplexEvent is based on the custom DataControl in the Consumer.
<eventMap xmlns="http://xmlns.oracle.com/adfm/contextualEvent"> <event name="dropOccured"> <producer region="dndTwoAdvDcTF1.dndTwoAdvDcPFPageDef.fireComplexEvent"> <consumer region="dndOneAdvTF1" handler="dndOnePFPageDef.captureComplexEvents"> <parameters> <parameter name="theParameter" value="${payLoad}"/> </parameters> </consumer> </producer> </event> </eventMap>
As before, when running the application and invoking Drag and Drop, you will see in your console that the event is produced, and the consumer prints out the values in the payload. That is the proof that I created a Contextual Event after a Drag and Drop action occurred.
Conclusion
By going through all these steps I got a much clearer view of what is involved in creating and consuming Contextual Events. What started as a simple Drag and Drop question resulted in an approach for creating Reusable taskflows. I will work some more on this issue and blog on it as soon as I have an even better and cleaner approach. Anybody feel free to comment on this post as usual. I’m a member of the ADF-EMG InterRegion Communication Expert Panel and I can use the comments as input for discussions in this panel. By the way, if you are wondering why I used all these fancy colors, it’s is to make sure that I am looking to the correct version of my ADF libraries. Please take a look at the references below. I have to give credit to both Chris and Edwin. Just as I, did great work on exploring contextual events.
The method used in step 3 is inspired Edwins post referenced below and by discussions on the ADF-EMG.
References
Luc Bors: ADF 11g Contextual Event Framework: An Example
Edwin Biemond : ADF Task Flow interaction with WebCenter Composer
Chris Muir : JDev 11g: Programmatic Contextual Events
ADF Enterprise Methodology Group : The Rise and Rise of Contextual Events (or the birth of Implicit Contextual Events)