Dynamic Window Loading and Destroying (i.e. Closing)

What I would like to achieve is a single windowArea that can spawn a window or two depending on the user actions. The idea is to have this as a single point of window creation in the system and therefore to be able to dynamically create and destroy the windows.

I can get this to do what I want to an extent, but I begin to struggle around the closing of the window. The process is not easily synchronized client and server side.

I've created in my main page 'index.jsp':

<bjsf:windowArea id="windowArea" binding="#{WindowBackingBean.windowArea}">

The backing bean is scoped to session to avoid any data loss issues. In here attached to a commandButton I have a link to the following method to produce a window dynamically.

public void openModelessWindow(ActionEvent event)
{
        final UIBackbaseWindow window = new UIBackbaseWindow();
        window.setTitle("Modeless Window");
        window.addActionListener(
                        new ActionListener() {
                                public void processAction(ActionEvent event1) throws AbortProcessingException {
                                        System.out.println("Window closed.");
                                }
                        }
        );
       
        window.setUrl("WindowContents.jsp");
        getWindowArea().addChild(window);
}

The above code works great, except for one thing. When I try to close the window it fails to trigger the actionListener. I've tried using customActionListeners and manually coded bindings and these did not trigger either.

The plan was for the actionListener to remove the window from the windowArea and therefore keep the server and client in synch with each other.

I saw a post on the site about using the following code to trigger the event.

<bjsf:window binding="#{sampleBackingBean.window}" label="Sample Window" actionListener="#{sampleBackingBean.closeWindow}">
    <e:handler event="close" type="text/javascript"><![CDATA[bb.bjsf.sync(this,'command');]]></e:handler>
    <bjsf:outputText value="Some Content"/>
</bjsf:window>

Reference: http://bdn.backbase.com/node/3846

The problem with this is I cannot see how I can trigger a window to be dynamically created with this work around. The number of windows that will be spawned is not known, therefore I think I need to code this in pure java.

I'm not sure how I can achieve this?

Thanks,
Stuart

Hi Stuart, I was able

Hi Stuart,

I was able trigger the actionListner on a bjsf:window on click of the close button in the following way. The window area and associated windows are created dynamically and also the actionListner via the backingBean.

Hope this helps,

-Senaka

<bjsf:panelSet rows="50% 50%" fullScreen="true">

<bjsf:panel id="container" />

<bjsf:panel>
<bjsf:commandButton value="Create 2 windows dynamically" actionListener="#{myBean.create}"></bjsf:commandButton>
</bjsf:panel>

</bjsf:panelSet>

public void create(ActionEvent event)
{
FacesContext context = FacesContext.getCurrentInstance();

Application app = context.getApplication();
Class[] paramTypes = new Class[] { ActionEvent.class };
MethodBinding actionListener = app.createMethodBinding("#{myBean.closeWindow}", paramTypes);

UIBackbaseComponentBase destComp = (UIBackbaseComponentBase) ComponentUtils.findComponent(context.getViewRoot(), "container");

UIBackbaseWindowArea bbWindowArea  = new UIBackbaseWindowArea();
bbWindowArea.setId("bbWindowArea01");
bbWindowArea.setShow(true);

UIBackbaseTaskBar bbTaskBar = new UIBackbaseTaskBar();
bbTaskBar.setId("bbTaskBar");
bbTaskBar.setOrientation("top");

UIBackbaseWindow bbWindow01 = new UIBackbaseWindow();
bbWindow01.setId("bbWindow01");
bbWindow01.setLabel("Backbase Window 01");
bbWindow01.setTop("50px");
bbWindow01.setLeft("50px");
bbWindow01.setActionListener(actionListener);
bbWindow01.setShow(true);

UIBackbaseWindow bbWindow02 = new UIBackbaseWindow();
bbWindow02.setId("bbWindow02");
bbWindow02.setLabel("Backbase Window 02");
bbWindow02.setTop("100px");
bbWindow02.setLeft("100px");
bbWindow02.setActionListener(actionListener);
bbWindow02.setShow(true);

bbWindowArea.addChild(bbWindow01);
bbWindowArea.addChild(bbWindow02);
bbWindowArea.addChild(bbTaskBar);
destComp.addChild(bbWindowArea);

}

public void closeWindow(ActionEvent event)
{
System.out.println("Window "+event.getComponent().getId()+ " closed");

}

Thanks for your reply Senaka

Hi Senaka,

I tried your example and this worked better, it's definately a big step forward, but not 100% perfect yet. I took the liberty of modifying your code to get it to work as I plan to and hit a secondary problem.

Incidentally, I had general update issues with the taskbar updating so I removed that for now, we can get onto that at another time.

The good news is, it works well dynamically creating the window object id's with multiple presses of the "create 2 windows dynamically" button. However, when I close a window (it doesn't seem to matter which) it then cannot create new windows anymore.

<bjsf:panelSet rows="50% 50%" fullScreen="true">
        <bjsf:panel id="container">
                <bjsf:windowArea id="bbWindowArea01"/>
        </bjsf:panel>
        <bjsf:panel>
                <bjsf:commandButton value="Create 2 windows dynamically" actionListener="#{WindowBackingBean.create}"></bjsf:commandButton>
        </bjsf:panel>
</bjsf:panelSet>

public void create(ActionEvent event)
{
        FacesContext context = FacesContext.getCurrentInstance();
        Application app = context.getApplication();
               
        Class[] paramTypes = new Class[] { ActionEvent.class };
        MethodBinding actionListener = app.createMethodBinding("#{WindowBackingBean.closeWindow}", paramTypes);
               
        // UIBackbaseComponentBase destComp = (UIBackbaseComponentBase) ComponentUtils.findComponent(context.getViewRoot(), "container");
        UIBackbaseComponentBase bbWindowArea = (UIBackbaseComponentBase)ComponentUtils.findComponent(context.getViewRoot(), "bbWindowArea01");
       
        /*
        UIBackbaseWindowArea bbWindowArea  = new UIBackbaseWindowArea();
        bbWindowArea.setId("bbWindowArea01");
        bbWindowArea.setShow(true);
       
        UIBackbaseTaskBar bbTaskBar = new UIBackbaseTaskBar();
        bbTaskBar.setId("bbTaskBar");
        bbTaskBar.setOrientation("top");
        */
       
        UIBackbaseWindow bbWindow01 = new UIBackbaseWindow();
        // bbWindow01.setId("bbWindow01");
        bbWindow01.setLabel("Backbase Window 01");
        bbWindow01.setTop("50px");
        bbWindow01.setLeft("50px");
        bbWindow01.setActionListener(actionListener);
        bbWindow01.setShow(true);
       
        UIBackbaseWindow bbWindow02 = new UIBackbaseWindow();
        // bbWindow02.setId("bbWindow02");
        bbWindow02.setLabel("Backbase Window 02");
        bbWindow02.setTop("100px");
        bbWindow02.setLeft("100px");
        bbWindow02.setActionListener(actionListener);
        bbWindow02.setShow(true);
       
        bbWindowArea.addChild(bbWindow01);
        bbWindowArea.addChild(bbWindow02);
       
        // bbWindowArea.addChild(bbTaskBar);
        // destComp.addChild(bbWindowArea);
}

I thought the answer maybe obvious, so I tried to remove the window component from the windowArea in the following fashion. I figured this might be required to keep the application server and the client in synch with each other.

public void closeWindow(ActionEvent event)
{
        System.out.println("Window "+event.getComponent().getId()+ " closed");
        FacesContext context = FacesContext.getCurrentInstance();
        UIBackbaseComponentBase bbWindowArea = (UIBackbaseComponentBase) ComponentUtils.findComponent(context.getViewRoot(), "bbWindowArea01");
        bbWindowArea.removeChild(event.getComponent());
        bbWindowArea.setStructureDirty(true);
}

This caused another error at runtime - instead on the close of the window, not on the opening of a new window after a close. There is obviously something wrong?

For it to be truly dynamic I would like to be able to close and open windows in a user defined order. Due to the nature of our application that we are planning we'd like to use a framework to generically handle this kind of action in a single place - mainly to reduce code maintenance by avoiding repeated code.

Thanks,
Stuart

One other quirk I've noticed

When you create two windows, move a window and then create two more windows. The position of the moved windows are forgotten. Is there a way to make the windows remember their position?

Thanks,
Stuart

Adding new windows

Hi Stuart,

By examining the example from Senaka I found that the problem of adding another set of windows has to do with duplicate id's in the client. Maybe you could try to use some static counter integer instance in your backing bean and use it to create unique id's...

Duplicate windows

Hi Alexander,

Thank you for your reply.

I too noticed the id issue and you will see in the code that I placed on the thread that I commented out these hard coded id's. This therefore uses backbase generated id's for each window.

Thanks,
Stuart

Does this solve you

Does this solve your problem..?

Also your post about the positioning of the windows, is it that you would like to set the position of the newly created windows the same as the previous created windows and then the position values of their current state?

Not solved.

Hi Alexander,

The problem is not solved.

It is fine having a button that produces unlimited windows, that is working okay. The problem is that when you close a window and then press the button again you get an error.

The positioning problem is you press the open window button, move that window to somewhere else in the window area and then press the open window button again. It moves all windows back to their starting position regardless of where the user placed it.

I think this is all made clear in post http://bdn.backbase.com/node/4622#comment-10195.

Thanks,
Stuart

Hi Stuart, could you give me

Hi Stuart,

could you give me the error message(s) you get.

Thanks.

See Client Side Errors Below

Hi Alexander, I replied to the wrong part of the thread. The answer is seen in the one titled "Client Side Errors".

Thanks,
Stuart

Client Side Errors

Sorry for the delay, I spent a bit of time upgrading to 4.2.0 from 4.1.2 to see if that helped. Unfortunately it did not.

I do the following to produce the errors:
1. Press the button to open a window or two.
2. Close one of the open windows using the x at the top right of the window.
3. Click the button to open a new window again and get errors outlined below.

* Execution: Invalid method context, method "setAttribute" was called on a node which has been destroyed.. anonymous()
* GENERIC: Javascript error: "Execution: Invalid method context, method "setAttribute" was called on a node which has been destroyed..". b:windowArea function()
* GENERIC: Javascript error: "GENERIC: Javascript error: "Execution: Invalid method context, method "setAttribute" was called on a node which has been destroyed..".". b:windowArea function()

Thanks,
Stuart

Hi stuartstephen, I've

Hi stuartstephen,
I've create an example for you in which you can create multiple windows from one single point, you can close them and keep the positions of the windows after a window was destroyed.
This example is a little tricky because you have to synchronize the position from the server with the position from the client manually. This is needed because when you will create a new window the content of the parent container will be refreshed.

The .jsp page is:

<!-- -->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>
<%@ taglib uri="http://www.backbase.com/2007/jsf" prefix="bjsf"%>
<html>
  <body>
    <f:view>
       <bjsf:application>
        <f:verbatim>
                        <xi:include href="<%=application.getInitParameter("com.backbase.bjsf.CLIENT_BOOT_DIR")%>/bindings/config.xhtml_btl.chameleon.xml" parse="xml"></xi:include>
                        <xi:include href="<%=application.getInitParameter("com.backbase.bjsf.CLIENT_BOOT_DIR")%>/bindings/www.backbase.com.2007.jsf.client/server.xml" parse="xml"></xi:include>
                        <bjsfc:server url="index.jsf" identify="id" loadingMessage="__loadingMsg"></bjsfc:server>
                    <b:loadingMessage id="__loadingMsg">Loading...</b:loadingMessage>
                   
                    <d:namespace xmlns:d="http://www.backbase.com/2006/tdl" name="http://www.backbase.com/2007/jsf/client">
                            <d:behavior name="syncIt">
                                        <d:handler event="dragEnd" type="application/javascript"><![CDATA[
                                                 bb.bjsf.sync(this, 'position', 'top',  bb.html.getBoxObject(this.viewNode).top, 'left', bb.html.getBoxObject(this.viewNode).left);
                                        ]]></d:handler>
                                        <d:handler event="close" type="application/javascript"><![CDATA[
                                                 bb.bjsf.sync(this, 'close');
                                        ]]></d:handler>                                
                                </d:behavior>
                        </d:namespace>
                       
                </f:verbatim>  
               
                <bjsf:panelSet rows="50% 50%" fullScreen="true">
                       
                        <bjsf:panel id="container"/>
                 
                        <bjsf:panel>
                                <bjsf:commandButton value="Create a window dynamically" actionListener="#{testBean.create}"></bjsf:commandButton>
                        </bjsf:panel>
                       
                </bjsf:panelSet>
               
      </bjsf:application>
    </f:view>
  </body>
</html>

and the bean is:

package com.backbase.test;

import javax.faces.application.Application;
import javax.faces.context.FacesContext;
import javax.faces.el.MethodBinding;
import javax.faces.event.ActionEvent;

import com.backbase.bjsf.component.UIBBComponentBase;
import com.backbase.bjsf.component.UIBackbaseComponentBase;
import com.backbase.bjsf.component.btl.UIBackbaseWindow;
import com.backbase.bjsf.event.CustomActionEvent;
import com.backbase.bjsf.event.DropEvent;
import com.backbase.bjsf.util.ComponentUtils;

public class TestBean {
       
        public void create(ActionEvent event){
                FacesContext context = FacesContext.getCurrentInstance();
               
                UIBackbaseWindow window = new UIBackbaseWindow();
                window.setLabel("Backbase Window");
                window.setTop("50px");
                window.setLeft("50px");
               
                window.setShow(true);
                window.setBehavior("bjsfc:syncIt");
               
                Application app = context.getApplication();
                Class[] paramTypes = new Class[] { CustomActionEvent.class };
                MethodBinding customActionListener = app.createMethodBinding("#{testBean.windowActions}", paramTypes);
                window.setCustomActionListener(customActionListener);

                UIBackbaseComponentBase destComp = (UIBackbaseComponentBase) ComponentUtils.findComponent(context.getViewRoot(), "container");
                destComp.addChild(window);
        }
       
        public void windowActions (CustomActionEvent event){
               
                String eventType = event.getAtts().get("event").toString();
               
                if(eventType.equals("position")) {
                        UIBackbaseWindow win =(UIBackbaseWindow)event.getComponent();
                        win.setTop((String)event.getAtts().get("top")+"px");
                        win.setLeft((String)event.getAtts().get("left")+"px");
                        return;
                }
               
                if(eventType.equals("close")) {
                        UIBackbaseWindow window = (UIBackbaseWindow)event.getComponent();
                        UIBBComponentBase parent = (UIBBComponentBase)window.getParent();
                       
                        parent.removeChild(window);            
                }
        }
}

I hope this example will be helpful and if you have any more problems we will help you with pleasure.

Thanks

Hi ionut,

Thanks for the reply, that's really helpful.

There is just one thing that I noticed. I saw that you had removed the WindowArea from the picture so I modified the code you provided to make it use that instead of the container.

i.e.

//UIBackbaseComponentBase destComp = (UIBackbaseComponentBase) ComponentUtils.findComponent(context.getViewRoot(), "container");
//destComp.addChild(bbWindow01);
               
UIBackbaseComponentBase bbWindowArea = (UIBackbaseComponentBase) ComponentUtils.findComponent(context.getViewRoot(), "windowArea");
bbWindowArea.addChild(bbWindow01);

<bjsf:panel id="container">
  <bjsf:windowArea id="windowArea" binding="#{WindowBackingBean.windowArea}"/>
</bjsf:panel>

This led me back to the following errors:

Execution: Invalid method context, method "setAttribute" was called on a node which has been destroyed.. anonymous()
GENERIC: Javascript error: "Execution: Invalid method context, method "setAttribute" was called on a node which has been destroyed..". b:windowArea function()
GENERIC: Javascript error: "GENERIC: Javascript error: "Execution: Invalid method context, method "setAttribute" was called on a node which has been destroyed..".". c:create function()

I did all this because I noticed that without the WindowArea actions such as zIndex for windows are not implemented otherwise. I presume I need to implement some events in a similar way to that you have shown me for the window, but I am not entirely sure how to get this right?

Thanks,
Stuart

Hi

Hi Stuartsthphen,
If you want to have a windowArea you have to change the :

<bjsf:panel id="container"/>

with:
<bjsf:panel>
   <bjsf:windowArea id="container"/>
</bjsf:panel>

But if you want to also have a bjsf:taskbar inside the windowArea you will have a problem. We just found a problem and right now is reported in our bug tracking system and we will take care of this as soon as possible.

Best regards, Ionut

Thanks again

Hi ionut,

Thanks again for the reply.

I am confused though, isn't what I did effectively the same? I changed the code to find the id of "windowArea" instead of "container".

Thanks,
Stuart

HI

Hi,
You are creating your windowArea like this:

<bjsf:windowArea id="windowArea" binding="#{WindowBackingBean.windowArea}"/>

In my example I created like this:

<bjsf:windowArea id="container"/>

The diference is that you bind your windowArea with a UIBackbaseWindowArea. The problem that you are facing is from the way you initialize your UIBackbaseWindowArea. If you are doing just like in the example that Senaka showed you is not good because the UIBackbaseWindowArea object is created every time when you click the button. Senaka example was made to create two windows only once.
I think, if this is the case, you should declare your UIBackbaseWindowArea object as a object property and initialize it on the constructor, in this way will be initialized only once.
If this is not your case please send us your complete test case.

Best regardes, Ionut