Home » Eclipse Projects » Remote Application Platform (RAP) » Custom widget question
Custom widget question [message #555325] |
Thu, 26 August 2010 10:21 |
Massimo Zugno 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 |
Ivan Furnadjiev 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 |
Massimo Zugno 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 |
Ivan Furnadjiev 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 |
Massimo Zugno 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 |
Ivan Furnadjiev 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 #558142 is a reply to message #558110] |
Fri, 10 September 2010 10:22 |
Ivan Furnadjiev 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.
|
|
|
Goto Forum:
Current Time: Sat Jul 27 15:37:54 GMT 2024
Powered by FUDForum. Page generated in 0.04255 seconds
|