Skip to main content


Eclipse Community Forums
Forum Search:

Search      Help    Register    Login    Home
Home » Eclipse Projects » Remote Application Platform (RAP) » Custom widget question
Custom widget question [message #555325] Thu, 26 August 2010 10:21 Go to next message
Massimo Zugno is currently offline Massimo ZugnoFriend
Messages: 9
Registered: July 2009
Junior Member
Hi,

I'm developing a custom widget and I'm trying to figure out the best way to achieve my goal. Currently I tried to be tight with SWT best practices, so my custom widget - that is a Text - is wrapped in a Composite instead of directly extending Text:
public class Real extends Composite {

	private Control control;

	public Real(Composite parent, int style) {
		super(parent, style);
		[...]
		createControl();
	}

	private void createControl() {
		Text text = new Text(this, SWT.BORDER);
		[...]
		this.setControl(text);
	}

	public void setControl(Control control) {
		this.control = control;
	}

	public Control getControl() {
		return control;
	}

}

Constructing the custom widget using pure SWT composition should be enough in most cases, since normally one would let RAP do all the stuff, but I have the requirement to handle particular behaviour of this widget client side, so I implemented the necessary javascript and life cycle adapter. And here comes the problem: client-side, the widget should be aware of the "control" property, therefore in the LCA i added the following code:
public class RealLCA extends AbstractWidgetLCA {
        [...]
	@Override
	public void renderInitialization(Widget widget) throws IOException {
		Real real = (Real) widget;
		JSWriter writer = JSWriter.getWriterFor(real);
		Control control = real.getControl();

		WidgetAdapter adapter = (WidgetAdapter) WidgetUtil.getAdapter(control);
		writer.newWidget(Real.class.getName(), new Object[] { adapter.getId() });

		writer.set("control", "control", control);

		ControlLCAUtil.writeStyleFlags(real);
	}
}

The problem is that at this time control has not been created yet (Text LCA will be called after my LCA), so the result is an error:
Error in property control of class com.test.rap.ui.widgets.Real in method setControl with incoming value 'undefined': Undefined value is not allowed!

As a solution for this issue, I called Text LCA's "renderInitialization()" programmatically from my LCA, after "newWidget()" call:
public class RealLCA extends AbstractWidgetLCA {
        [...]
	@Override
	public void renderInitialization(Widget widget) throws IOException {
		Real real = (Real) widget;
		JSWriter writer = JSWriter.getWriterFor(real);
		Control control = real.getControl();

		WidgetAdapter adapter = (WidgetAdapter) WidgetUtil.getAdapter(control);
		writer.newWidget(Real.class.getName(), new Object[] { adapter.getId() });

		AbstractWidgetLCA lca = WidgetUtil.getLCA(control);
		lca.renderInitialization(control);
		adapter.setInitialized(true);

		writer.set("control", "control", control);

		ControlLCAUtil.writeStyleFlags(real);
	}
}

This seems to work properly, the writer correctly set "control" property. The only drawback is that I have to call WidgetAdapter.setInitialized(true) to avoid the generation of another "newWidget" for Text that will override the existing one in the WidgetManager, but WidgetAdapter is internal API and generally I prefer to avoid using internals.
I would ask if this is a suitable solution or there are other ways to achieve this requirement.

Thanks,
Massimo.
Re: Custom widget question [message #555332 is a reply to message #555325] Thu, 26 August 2010 10:56 Go to previous messageGo to next message
Ivan Furnadjiev is currently offline Ivan FurnadjievFriend
Messages: 2426
Registered: July 2009
Location: Sofia, Bulgaria
Senior Member
Hi Massimo,

one idea that comes to my mind is to set the control Id instead of
control itself. Something like this:
public class RealLCA extends AbstractWidgetLCA {
[...]
@Override
public void renderInitialization(Widget widget) throws IOException {
Real real = (Real) widget;
JSWriter writer = JSWriter.getWriterFor(real);
Control control = real.getControl();

IWidgetAdapter adapter = WidgetUtil.getAdapter(control);

writer.set("controlId", "controlId", adapter.getId());

ControlLCAUtil.writeStyleFlags(real);
}
}
The interface IWidgetAdapter is not internal.
When you need the instance of the control in the JavaScript.... get it
by WidgetManager.findWidgetById( id ).

HTH,
Ivan

On 08/26/2010 1:21 PM, Massimo Zugno wrote:
> Hi,
>
> I'm developing a custom widget and I'm trying to figure out the best
> way to achieve my goal. Currently I tried to be tight with SWT best
> practices, so my custom widget - that is a Text - is wrapped in a
> Composite instead of directly extending Text:
>
> public class Real extends Composite {
>
> private Control control;
>
> public Real(Composite parent, int style) {
> super(parent, style);
> [...]
> createControl();
> }
>
> private void createControl() {
> Text text = new Text(this, SWT.BORDER);
> [...]
> this.setControl(text);
> }
>
> public void setControl(Control control) {
> this.control = control;
> }
>
> public Control getControl() {
> return control;
> }
>
> }
>
> Constructing the custom widget using pure SWT composition should be
> enough in most cases, since normally one would let RAP do all the
> stuff, but I have the requirement to handle particular behaviour of
> this widget client side, so I implemented the necessary javascript and
> life cycle adapter. And here comes the problem: client-side, the
> widget should be aware of the "control" property, therefore in the LCA
> i added the following code:
>
> public class RealLCA extends AbstractWidgetLCA {
> [...]
> @Override
> public void renderInitialization(Widget widget) throws IOException {
> Real real = (Real) widget;
> JSWriter writer = JSWriter.getWriterFor(real);
> Control control = real.getControl();
>
> WidgetAdapter adapter = (WidgetAdapter)
> WidgetUtil.getAdapter(control);
> writer.newWidget(Real.class.getName(), new Object[] {
> adapter.getId() });
>
> writer.set("control", "control", control);
>
> ControlLCAUtil.writeStyleFlags(real);
> }
> }
>
> The problem is that at this time control has not been created yet
> (Text LCA will be called after my LCA), so the result is an error:
>
> Error in property control of class com.test.rap.ui.widgets.Real in
> method setControl with incoming value 'undefined': Undefined value is
> not allowed!
>
> As a solution for this issue, I called Text LCA's
> "renderInitialization()" programmatically from my LCA, after
> "newWidget()" call:
>
> public class RealLCA extends AbstractWidgetLCA {
> [...]
> @Override
> public void renderInitialization(Widget widget) throws IOException {
> Real real = (Real) widget;
> JSWriter writer = JSWriter.getWriterFor(real);
> Control control = real.getControl();
>
> WidgetAdapter adapter = (WidgetAdapter)
> WidgetUtil.getAdapter(control);
> writer.newWidget(Real.class.getName(), new Object[] {
> adapter.getId() });
>
> AbstractWidgetLCA lca = WidgetUtil.getLCA(control);
> lca.renderInitialization(control);
> adapter.setInitialized(true);
>
> writer.set("control", "control", control);
>
> ControlLCAUtil.writeStyleFlags(real);
> }
> }
>
> This seems to work properly, the writer correctly set "control"
> property. The only drawback is that I have to call
> WidgetAdapter.setInitialized(true) to avoid the generation of another
> "newWidget" for Text that will override the existing one in the
> WidgetManager, but WidgetAdapter is internal API and generally I
> prefer to avoid using internals.
> I would ask if this is a suitable solution or there are other ways to
> achieve this requirement.
>
> Thanks,
> Massimo.
Re: Custom widget question [message #555639 is a reply to message #555332] Fri, 27 August 2010 13:17 Go to previous messageGo to next message
Massimo Zugno is currently offline Massimo ZugnoFriend
Messages: 9
Registered: July 2009
Junior Member
Hi Ivan,

thank you for your suggestion. Following your hint I managed to solve my issue moving the relevant part of the code at JavaScript side, although mantaining the Parent/Child containment at Java-side (to be SWT compliant). To solve the "timing" problem of the Control being created after parent container's creation, I added its initialization in an "appear" event:
// LCA
public void renderInitialization(Widget widget) throws IOException {
       [...]
       writer.newWidget(QX_TYPE, new Object[] { controlId });
}

// JavaScript
qx.Class.define( "com.test.rap.ui.widgets.Real", {
	extend : qx.ui.layout.CanvasLayout,

  	construct : function() {
		this.base(arguments);
		this._controlId = arguments[0];
		this.addEventListener("appear", this._onAppear);
	},

        properties : {
		control : {
			nullable : true,
			init : "",
			apply : "_applyControl"
		}
        },

        members : {
		_applyControl : function(){
			var c = this.getControl();
			c.addEventListener("blur", this._onBlur, this);
		},
				
		_onAppear : function(){
			var wm = org.eclipse.swt.WidgetManager.getInstance();  
                        var control = wm.findWidgetById(this._controlId);
                        this.setControl(control);
		},
		
		_onBlur : function(e){
                       [...]
		},
		
	}

Is this the correct way to proceed?

Another approach would be adding a plain qooxdoo TextField control directly in the constructor of my custom widget, but without coding it Java-side (I noticed this approach in other rap widgets like org.eclipse.swt.Combo where the widget is built with composition of pure qooxdoo components), but I don't like this idea very much.

Thank you,
Massimo.
Re: Custom widget question [message #555652 is a reply to message #555639] Fri, 27 August 2010 13:30 Go to previous messageGo to next message
Ivan Furnadjiev is currently offline Ivan FurnadjievFriend
Messages: 2426
Registered: July 2009
Location: Sofia, Bulgaria
Senior Member
Hi Massimo,

it's great that you managed to solve the problem. Your code looks good
to me too.

Best,
Ivan

On 08/27/2010 4:17 PM, Massimo Zugno wrote:
> Hi Ivan,
>
> thank you for your suggestion. Following your hint I managed to solve
> my issue moving the relevant part of the code at JavaScript side,
> although mantaining the Parent/Child containment at Java-side (to be
> SWT compliant). To solve the "timing" problem of the Control being
> created after parent container's creation, I added its initialization
> in an "appear" event:
>
> // LCA
> public void renderInitialization(Widget widget) throws IOException {
> [...]
> writer.newWidget(QX_TYPE, new Object[] { controlId });
> }
>
>
> // JavaScript
> qx.Class.define( "com.test.rap.ui.widgets.Real", {
> extend : qx.ui.layout.CanvasLayout,
>
> construct : function() {
> this.base(arguments);
> this._controlId = arguments[0];
> this.addEventListener("appear", this._onAppear);
> },
>
> properties : {
> control : {
> nullable : true,
> init : "",
> apply : "_applyControl"
> }
> },
>
> members : {
> _applyControl : function(){
> var c = this.getControl();
> c.addEventListener("blur", this._onBlur, this);
> },
>
> _onAppear : function(){
> var wm = org.eclipse.swt.WidgetManager.getInstance();
> var control = wm.findWidgetById(this._controlId);
> this.setControl(control);
> },
>
> _onBlur : function(e){
> [...]
> },
>
> }
>
> Is this the correct way to proceed?
>
> Another approach would be adding a plain qooxdoo TextField control
> directly in the constructor of my custom widget, but without coding it
> Java-side (I noticed this approach in other rap widgets like
> org.eclipse.swt.Combo where the widget is built with composition of
> pure qooxdoo components), but I don't like this idea very much.
>
> Thank you,
> Massimo.
Re: Custom widget question [message #557956 is a reply to message #555652] Thu, 09 September 2010 15:24 Go to previous messageGo to next message
Massimo Zugno is currently offline Massimo ZugnoFriend
Messages: 9
Registered: July 2009
Junior Member
Unfortunately I have to revive this thread, since the "onAppear" solution seems to lead to other problems. After some research I noticed the class CoolItemLCA, that seems to fit exactly my scenario: a CoolItem child control is rendered after its parent which has a reference to it, and the solution is provided in the writeControl method:
  private static void writeControl( final CoolItem coolItem ) throws IOException {
    Control control = coolItem.getControl();
    if( WidgetLCAUtil.hasChanged( coolItem, PROP_CONTROL, control, null ) ) {
      final JSWriter writer = JSWriter.getWriterFor( coolItem );
      final Object[] args = new Object[] { control };
      if( control != null ) {
        // defer call since controls are rendered after items
        WidgetAdapter adapter 
          = ( WidgetAdapter )WidgetUtil.getAdapter( control );
        adapter.setRenderRunnable( new IRenderRunnable() {
          public void afterRender() throws IOException {
            writer.call( SET_CONTROL, args );
          }
        } );
      } else {
        writer.call( SET_CONTROL, args );
      }
    }
  }

However, the code references two internal classes: org.eclipse.swt.internal.widgets.WidgetAdapter and org.eclipse.rwt.internal.lifecycle.IRenderRunnable. I'm wondering if I can insert a change request in RAP's bugzilla, asking for these classes being exposed in the public RAP api. I think this will make sense since that WidgetAdapter methods and IRenderRunnable itself can be very useful in many scenarios.
Please let me know your opinions/suggestions...

Thans, massimo.
Re: Custom widget question [message #557984 is a reply to message #557956] Thu, 09 September 2010 16:40 Go to previous messageGo to next message
Ivan Furnadjiev is currently offline Ivan FurnadjievFriend
Messages: 2426
Registered: July 2009
Location: Sofia, Bulgaria
Senior Member
Hi Massimo,

maybe you can achieve the same defer render with phase listener. See
this snippet:
------------------
final Button button = new Button( parent, SWT.PUSH );
button.setText( "This is button initial text!" );
RWT.getLifeCycle().addPhaseListener( new PhaseListener() {

private static final long serialVersionUID = 1L;

public void beforePhase( final PhaseEvent event ) {
// do nothing
}

public void afterPhase( final PhaseEvent event ) {
JSWriter writer = JSWriter.getWriterFor( button );
try {
writer.set( "text", "New button text" );
} catch( IOException e ) {
// TODO Auto-generated catch block
} finally {
RWT.getLifeCycle().removePhaseListener( this );
}
}

public PhaseId getPhaseId() {
return PhaseId.RENDER;
}
} );
------------

HTH,
Ivan


On 09/09/2010 6:24 PM, Massimo Zugno wrote:
> Unfortunately I have to revive this thread, since the "onAppear"
> solution seems to lead to other problems. After some research I
> noticed the class CoolItemLCA, that seems to fit exactly my scenario:
> a CoolItem child control is rendered after its parent which has a
> reference to it, and the solution is provided in the writeControl method:
>
> private static void writeControl( final CoolItem coolItem ) throws
> IOException {
> Control control = coolItem.getControl();
> if( WidgetLCAUtil.hasChanged( coolItem, PROP_CONTROL, control, null
> ) ) {
> final JSWriter writer = JSWriter.getWriterFor( coolItem );
> final Object[] args = new Object[] { control };
> if( control != null ) {
> // defer call since controls are rendered after items
> WidgetAdapter adapter = ( WidgetAdapter
> )WidgetUtil.getAdapter( control );
> adapter.setRenderRunnable( new IRenderRunnable() {
> public void afterRender() throws IOException {
> writer.call( SET_CONTROL, args );
> }
> } );
> } else {
> writer.call( SET_CONTROL, args );
> }
> }
> }
>
> However, the code references two internal classes:
> org.eclipse.swt.internal.widgets.WidgetAdapter and
> org.eclipse.rwt.internal.lifecycle.IRenderRunnable. I'm wondering if I
> can insert a change request in RAP's bugzilla, asking for these
> classes being exposed in the public RAP api. I think this will make
> sense since that WidgetAdapter methods and IRenderRunnable itself can
> be very useful in many scenarios.
> Please let me know your opinions/suggestions...
>
> Thans, massimo.
>
Re: Custom widget question [message #558110 is a reply to message #557984] Fri, 10 September 2010 08:36 Go to previous messageGo to next message
Massimo Zugno is currently offline Massimo ZugnoFriend
Messages: 9
Registered: July 2009
Junior Member
Hi Ivan,
I tried your solution and that worked fine, except for the fact that the PhaseListener is "global", so the RENDER phase is fired only after the whole page is rendered, while IRenderRunnable is fired immediately after widget's creation. Using PhaseListener can cause problems if another widget, after control parent initialization but before RENDER phase is fired, try to retrieve the control...
Will a change request for making WidgetAdapter/IRenderRunnable public API be considered? That would be useful in many scenarios.

Thanks,
Massimo.
Re: Custom widget question [message #558142 is a reply to message #558110] Fri, 10 September 2010 10:22 Go to previous message
Ivan Furnadjiev is currently offline Ivan FurnadjievFriend
Messages: 2426
Registered: July 2009
Location: Sofia, Bulgaria
Senior Member
Hi Massimo,

please file an enhancement request with a description of your problem.
We will consider your request, but I can't promise anything.

Best,
Ivan

On 09/10/2010 11:36 AM, Massimo Zugno wrote:
> Hi Ivan,
> I tried your solution and that worked fine, except for the fact that
> the PhaseListener is "global", so the RENDER phase is fired only after
> the whole page is rendered, while IRenderRunnable is fired immediately
> after widget's creation. Using PhaseListener can cause problems if
> another widget, after control parent initialization but before RENDER
> phase is fired, try to retrieve the control...
> Will a change request for making WidgetAdapter/IRenderRunnable public
> API be considered? That would be useful in many scenarios.
>
> Thanks,
> Massimo.
Previous Topic:line break in button text and image under button text
Next Topic:Redirection - RWT.getResponse().sendRedirect()
Goto Forum:
  


Current Time: Fri Apr 26 12:58:59 GMT 2024

Powered by FUDForum. Page generated in 0.02818 seconds
.:: Contact :: Home ::.

Powered by: FUDforum 3.0.2.
Copyright ©2001-2010 FUDforum Bulletin Board Software

Back to the top