MVP question

75 views
Skip to first unread message

davis

unread,
Aug 18, 2009, 12:40:07 PM8/18/09
to Google Web Toolkit
I'm trying to implement a version of MVP as discussed at Google I/O
talk:

http://code.google.com/events/io/sessions/GoogleWebToolkitBestPractices.html

So, one can have a presenter class that defines an internal interface,
and I see it typically done like this:

class PhoneEditor {
interface Display {
HasClickHandlers getSaveButton();
...
}

It seems to me that this tends to get a bit unwieldly in practice.
For example, I have an account registration page. Let's say it
contains a TextBox for username and a PasswordTextBox for password.

Here are some things I'd like to do to that box in my presenter:

get the value, so I could have

interface Display {
HasValue<String> getUsername();
HasValue<String> getPassword();
}

But I also have validation code in my presenter, which will validate
the content of the fields, and do things like set style errors.

#addStyleName( ) is defined on UIObject, so I'm kinda out of luck with
that interface unless I define something like:

interface Display {
HasValue<String> getUsername();
HasValue<String> getPassword();
UIObject getUsernameBox();
UIObject getPasswordBox();
}

but that is kind of a ridiculous and redundant interface. So, really,
my question is what is so wrong with:

interface Display {
TextBoxBase getUsernameBox();
TextBoxBase getPasswordBox();
}

I get the fact that if you can use the Has* interfaces, it helps
decouple the presenter from the view more by allowing the view to
change to other UI widgets later on, but I find it far too limiting in
being able to manipulate the view objects without either having large,
redundant interfaces or else doing lots of unsafe casting.

Has anyone else encountered this conundrum, and how are they
approaching it? I'm inclined to make my Display interfaces explicit.

Regards,
Davis

Thomas Broyer

unread,
Aug 18, 2009, 7:05:20 PM8/18/09
to Google Web Toolkit


On 18 août, 18:40, davis <davisf...@zenoconsulting.biz> wrote:
> I'm trying to implement a version of MVP as discussed at Google I/O
> talk:
>
> http://code.google.com/events/io/sessions/GoogleWebToolkitBestPractic...
Your presenter shouldn't deal with "styles", it should instead tell
the view to show error near/on the "username" and/or password (or
nowhere particularly). It's the responsibility of the view to
determine (be implemented that way) if it should set a red background
and/or red border on the textbox, and/or show an icon next to the box,
and/or show an "error summary" above or below the boxes, etc.

Davis Ford

unread,
Aug 18, 2009, 8:48:40 PM8/18/09
to google-we...@googlegroups.com
Hi Thomas, thanks for replying...

The style thing is I guess questionable, but the basic problem with
the pattern as presented which favors the Has* interfaces, etc. I
think is still a design issue to consider. Another post shortly after
mine basically issued the same question
http://groups.google.com/group/google-web-toolkit/browse_thread/thread/19b1600e34ca8f99/b6671eb90041154c#b6671eb90041154c

If it isn't addStyle( ) (your point is well taken)...it will be
something else the presenter wants to manipulate on the view's widget.

I guess my point is that if you have a view that is something beyond
most blog/tutorial posts, it just seems to me that not returning the
actual widget either makes the interface blow up, or you end up doing
lots of nasty casting.

My basic question is: why not just return TextBox if that is what is
in your view? The coupling is between 2 classes: view and presenter.
If you later change TextBox to SuperWidgetTextBox, you can re-factor
it in your IDE in 5 seconds and be done with it.

Am I missing something?

Regards,
Davis
--
Zeno Consulting, Inc.
home: http://www.zenoconsulting.biz
blog: http://zenoconsulting.wikidot.com
p: 248.894.4922
f: 313.884.2977

davis

unread,
Aug 18, 2009, 8:52:10 PM8/18/09
to Google Web Toolkit
Sorry, this was the link I meant to post to another thread ->
http://groups.google.com/group/google-web-toolkit/browse_thread/thread/19b1600e34ca8f99/b6671eb90041154c#b6671eb90041154c

> think is still a design issue to consider.  Another post shortly after
> mine basically issued the same questionhttp://groups.google.com/group/google-web-toolkit/browse_thread/threa...

Ian Bambury

unread,
Aug 19, 2009, 8:30:14 AM8/19/09
to google-we...@googlegroups.com
2009/8/19 Davis Ford <davi...@zenoconsulting.biz>


My basic question is: why not just return TextBox if that is what is
in your view?  The coupling is between 2 classes: view and presenter.
If you later change TextBox to SuperWidgetTextBox, you can re-factor
it in your IDE in 5 seconds and be done with it.

Am I missing something?

If that is the way you want to go then fine.

What you might be missing is that some of your views may not be using text boxes. One view may be using a text box. Another might use a label, another might use a button, or a text area, or a hyperlink, or a menu item. They can all display text.

An on/off indicator and a switch are just a boolean and something clickable, but you don't decide in the presenter that is should be the words 'on' and 'off' and demand a label, you leave it as boolean and let the view decide whether to put yes/no, on/off, true/false, a checkbox, an image of a lightbulb on and off, etc - and the clickable thing could be a button with those words, or a picture of a switch, or the checkbox.

The presenter can be used for all these views. Different views can register with the same presenter. Users can choose their preferred view and swap views in and out.



Alejandro D. Garin

unread,
Aug 19, 2009, 9:42:21 AM8/19/09
to google-we...@googlegroups.com
On Wed, Aug 19, 2009 at 9:30 AM, Ian Bambury <ianba...@gmail.com> wrote:
2009/8/19 Davis Ford <davi...@zenoconsulting.biz>


My basic question is: why not just return TextBox if that is what is
in your view?  The coupling is between 2 classes: view and presenter.
If you later change TextBox to SuperWidgetTextBox, you can re-factor
it in your IDE in 5 seconds and be done with it.

Am I missing something?

If your view interface return a TextBox you can't test the presenter with JUnit, you will need to use GWTTestCase.

davis

unread,
Aug 19, 2009, 10:09:29 AM8/19/09
to Google Web Toolkit

> If your view interface return a TextBox you can't test the presenter with
> JUnit, you will need to use GWTTestCase.

Sure you can:

import static org.easymock.classextension.EasyMock.*;
import com.google.gwt.user.client.ui.TextBox;

public class SomePresenterTestCase {

private MyPresenter presenter;
private MyView view;

@Test
public void testSomething() {
TextBox mockBox = createMock(TextBox.class);
MyView mockView = createMock(MyView.class);
presenter = new MyPresenter(mockView);
expect(mockView.getSomeTextBox()).andReturn(mockBox);
replay(mockView);
replay(mockBox);
TextBox box = presenter.getDisplay().getSomeTextBox();
verify(mockView);
}
}

davis

unread,
Aug 19, 2009, 10:14:16 AM8/19/09
to Google Web Toolkit
Point taken Ian. I think the design tradeoffs are at least on the
table. In most cases, I think I'm comfortable with coupling the view<-
>presenter in a 1:1 relationship -- (i.e. not making universally
generic presenters that can be used with any view), but I understand
your argument, and can see the need for this in some scenarios.

On Aug 19, 8:30 am, Ian Bambury <ianbamb...@gmail.com> wrote:
> 2009/8/19 Davis Ford <davisf...@zenoconsulting.biz>

Alejandro D. Garin

unread,
Aug 19, 2009, 10:59:36 AM8/19/09
to google-we...@googlegroups.com
Hi David,

I tried your example, but I have this error:

Caused by: java.lang.UnsupportedOperationException: ERROR: GWT.create() is only usable in client code!  It cannot be called, for example, from server code.  If you are running a unit test, check that your test case extends GWTTestCase and that GWT.create() is not called from within an initializer or constructor.

Stacktrace:

java.lang.ExceptionInInitializerError
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at net.sf.cglib.proxy.Enhancer.setCallbacksHelper(Enhancer.java:619)
    at net.sf.cglib.proxy.Enhancer.setThreadCallbacks(Enhancer.java:612)
    at net.sf.cglib.proxy.Enhancer.registerCallbacks(Enhancer.java:581)
    at org.easymock.classextension.internal.ClassProxyFactory.createProxy(ClassProxyFactory.java:108)
    at org.easymock.internal.MocksControl.createMock(MocksControl.java:51)
    at org.easymock.classextension.EasyMock.createMock(EasyMock.java:46)
    at example.publico.client.SimpleTest.testSimple(SimpleTest.java:10)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at junit.framework.TestCase.runTest(TestCase.java:168)
    at junit.framework.TestCase.runBare(TestCase.java:134)
    at junit.framework.TestResult$1.protect(TestResult.java:110)
    at junit.framework.TestResult.runProtected(TestResult.java:128)
    at junit.framework.TestResult.run(TestResult.java:113)
    at junit.framework.TestCase.run(TestCase.java:124)
    at junit.framework.TestSuite.runTest(TestSuite.java:232)
    at junit.framework.TestSuite.run(TestSuite.java:227)
    at org.junit.internal.runners.OldTestClassRunner.run(OldTestClassRunner.java:76)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:45)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
Caused by: java.lang.UnsupportedOperationException: ERROR: GWT.create() is only usable in client code!  It cannot be called, for example, from server code.  If you are running a unit test, check that your test case extends GWTTestCase and that GWT.create() is not called from within an initializer or constructor.
    at com.google.gwt.core.client.GWT.create(GWT.java:85)
    at com.google.gwt.user.client.ui.UIObject.<clinit>(UIObject.java:140)
    ... 30 more

davis

unread,
Aug 19, 2009, 11:10:49 AM8/19/09
to Google Web Toolkit
Alejandro, what is the command you were running?

I just downloaded the zip, and ran this:

mvn gwt:run -DrunTarget=com.example.User/User.html

It launched the hosted browser ok. I have a regex check in there that
pops up Window.alert if the url isn't what urlrewrite expects. You
can delete that code, or point your hosted browser instead to:

http://localhost:8888/user/abc

and you'll see a form that fills in the textbox with abc, and if you
click the button, it executes GWT-RPC to say hello.

You can also run this:

mvn gwt:run -DrunTarget=com.example.Admin/Admin.html

and you can hit this url:

http://localhost:8888/admin

It will show that the content is different.

Just for reference, I am using:

C:\TEMP\gwt-prototype>mvn -version
Apache Maven 2.2.0 (r788681; 2009-06-26 09:04:01-0400)
Java version: 1.6.0_14
Java home: C:\Program Files\Java\jdk1.6.0_14\jre
Default locale: en_US, platform encoding: Cp1252
OS name: "windows xp" version: "5.1" arch: "x86" Family: "windows"

Regards,
Davis

davis

unread,
Aug 19, 2009, 11:14:50 AM8/19/09
to Google Web Toolkit
My fault -- I thought you were talking about something else. It
appears you are correct. It looks like even easy mock class extension
can't do it. Somewhere in the initialization code of TextBox.class it
calls GWT.create()...major bummer.

On Aug 19, 10:59 am, "Alejandro D. Garin" <aga...@gmail.com> wrote:

davis

unread,
Aug 19, 2009, 11:19:51 AM8/19/09
to Google Web Toolkit
Probably stems from the constructor doing this:

/**
* Creates an empty text box.
*/
public TextBox() {
this(Document.get().createTextInputElement(), "gwt-TextBox");
}

http://code.google.com/p/google-web-toolkit/source/browse/releases/1.7/user/src/com/google/gwt/user/client/ui/TextBox.java

Anyone have a mock workaround for ui elements they'd like to share?
Is there a mock framework out there for GWT ui elements?

davis

unread,
Aug 19, 2009, 11:24:50 AM8/19/09
to Google Web Toolkit
Here you go...try it with this:

http://code.google.com/p/google-web-toolkit/source/browse/releases/1.7/user/src/com/google/gwt/junit/GWTMockUtilities.java

import static org.easymock.classextension.EasyMock.createMock;
import static org.junit.Assert.assertNotNull;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.google.gwt.junit.GWTMockUtilities;
import com.google.gwt.user.client.ui.TextBox;

public class TestSomething {

@Before
public void setUp() throws Exception {
GWTMockUtilities.disarm();
}

@After
public void tearDown() throws Exception {
GWTMockUtilities.restore();
}

@Test
public void testThatEasyMockWorksWithTextBox() {
TextBox box = createMock(TextBox.class);
assertNotNull(box);
}
}

On Aug 19, 11:19 am, davis <davisf...@zenoconsulting.biz> wrote:
> Probably stems from the constructor doing this:
>
>   /**
>    * Creates an empty text box.
>    */
>   public TextBox() {
>     this(Document.get().createTextInputElement(), "gwt-TextBox");
>   }
>
> http://code.google.com/p/google-web-toolkit/source/browse/releases/1....

davis

unread,
Aug 19, 2009, 11:29:31 AM8/19/09
to Google Web Toolkit
Unfortunately, I get this:

Caused by: java.lang.ClassNotFoundException:
com.google.gwt.core.client.GWTBridge

Not sure why, but it appears that this class was designed exactly for
the purpose of allowing EasyMock to mock UI widgets so it doesn't blow
up on GWT.create( ).

If anyone has any insight into why it can't find GWTBridge I would
love to hear it, b/c I'll be needing to do this very soon.

On Aug 19, 11:24 am, davis <davisf...@zenoconsulting.biz> wrote:
> Here you go...try it with this:
>
> http://code.google.com/p/google-web-toolkit/source/browse/releases/1....

Alejandro D. Garin

unread,
Aug 19, 2009, 11:28:12 AM8/19/09
to google-we...@googlegroups.com
David,

Maybe GWTMockUtilities could help, but didn't tried it.

Alejandro D. Garin

unread,
Aug 19, 2009, 11:30:10 AM8/19/09
to google-we...@googlegroups.com

Alejandro D. Garin

unread,
Aug 19, 2009, 11:33:25 AM8/19/09
to google-we...@googlegroups.com
yes david, that seems to work. Personally I still prefer to use interfaces in the view rather than using Widgets.

Cheers,

Nathan Wells

unread,
Aug 19, 2009, 11:35:39 AM8/19/09
to Google Web Toolkit
The way I see it (and there is obviously a lot of room for
interpretation based on app needs), you want to actually make your
view (i.e. widgets) as generic as possible, while the work of "what"
should be displayed is up to the presenter. For example, I created a
form-like section that has an interface that defines methods like

void addField(String label, String value);
HasValue<String> addEditableField(String label, String initialValue);
void addFlag(String label, boolean value);
HasValue<Boolean> addEditableFlag(String label, boolean initialValue);

That way, any presenter can use the same interface, which can be
swapped out for user experience reasons.

The idea is, you want the presenter to worry about getting data to and
from the view, not the intricacies of user experience. By the same
token, you don't want your view worrying about anything remotely
resembling your application model.

I realize this isn't the only way to do it, and doing it this way
requires a little bit of work getting components to fit together, but
it makes your architecture much more sane, IMHO.

Davis Ford

unread,
Aug 19, 2009, 11:52:38 AM8/19/09
to google-we...@googlegroups.com
Alejandro -- when you tried it, did you not get:

Caused by: java.lang.ClassNotFoundException:com.google.gwt.core.client.GWTBridge

If so, what GWT version are you using?

Dalla

unread,
Aug 19, 2009, 12:41:05 PM8/19/09
to Google Web Toolkit
I recall getting this error aswell.
The exception "Caused by:
java.lang.ClassNotFoundException:com.google.gwt.core.client.GWTBridge"
is thrown if you try to invoke

GWTMockUtilities.disarm();
GWTMockUtilities.restore();

without having gwt-dev.jar in your classpath.




On 19 Aug, 17:52, Davis Ford <davisf...@zenoconsulting.biz> wrote:
> Alejandro -- when you tried it, did you not get:
>
> Caused by: java.lang.ClassNotFoundException:com.google.gwt.core.client.GWTBridge
>
> If so, what GWT version are you using?
>
>
>
>
>
> On Wed, Aug 19, 2009 at 11:33 AM, Alejandro D. Garin<aga...@gmail.com> wrote:
> > yes david, that seems to work. Personally I still prefer to use interfaces
> > in the view rather than using Widgets.
>
> > Cheers,
>
> > On Wed, Aug 19, 2009 at 12:24 PM, davis <davisf...@zenoconsulting.biz>
> > wrote:
>
> >> Here you go...try it with this:
>
> >>http://code.google.com/p/google-web-toolkit/source/browse/releases/1....
> >> > > > org.easymock.classextension.internal.ClassProxyFactory.createProxy(ClassPro xyFactory.java:108)
> >> > > >     at
> >> > > > org.easymock.internal.MocksControl.createMock(MocksControl.java:51)
> >> > > >     at
> >> > > > org.easymock.classextension.EasyMock.createMock(EasyMock.java:46)
> >> > > >     at
> >> > > > example.publico.client.SimpleTest.testSimple(SimpleTest.java:10)
> >> > > >     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
> >> > > >     at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
> >> > > >     at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown
> >> > > > Source)
> >> > > >     at java.lang.reflect.Method.invoke(Unknown Source)
> >> > > >     at junit.framework.TestCase.runTest(TestCase.java:168)
> >> > > >     at junit.framework.TestCase.runBare(TestCase.java:134)
> >> > > >     at junit.framework.TestResult$1.protect(TestResult.java:110)
> >> > > >     at junit.framework.TestResult.runProtected(TestResult.java:128)
> >> > > >     at junit.framework.TestResult.run(TestResult.java:113)
> >> > > >     at junit.framework.TestCase.run(TestCase.java:124)
> >> > > >     at junit.framework.TestSuite.runTest(TestSuite.java:232)
> >> > > >     at junit.framework.TestSuite.run(TestSuite.java:227)
> >> > > >     at
>
> >> > > > org.junit.internal.runners.OldTestClassRunner.run(OldTestClassRunner.java:7 6)
> >> > > >     at
>
> >> > > > org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestRe ference.java:45)
> >> > > >     at
>
> >> > > > org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java: 38)
> >> > > >     at
>
> >> > > > org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestR unner.java:460)
> >> > > >     at
>
> >> > > > org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestR unner.java:673)
> >> > > >     at
>
> >> > > > org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner .java:386)
> >> > > >     at
>
> >> > > > org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunne r.java:196)

Dalla

unread,
Aug 19, 2009, 12:55:00 PM8/19/09
to Google Web Toolkit
I remember testing this approach when playing around with the GWT Maps
API,
(the GWT specific API for Google Maps), and it didn´t work very well.
It seems like you run into trouble when testing complex Widgets that
make calls to other widgets.
I was using Jmock, not EasyMock, but it should be pretty much the
same.

@Test
public void testPresenter() {
final MapDisplayWidget widget = context.mock
(MapDisplayWidget.class);
final MapWidget map = context.mock(MapWidget.class);
MapData data = new MapData();
HandlerManager eventBus = new HandlerManager(null);

context.checking(new Expectations() {{
final Sequence seq = context.sequence("test");
oneOf(widget).getMap();inSequence(seq);
}});

MapDisplayPresenter presenter = new MapDisplayPresenter
(widget,data,eventBus);
}

Doing this a get an UnsatisfiedLinkError from
com.google.gwt.maps.client.overlay.Overlay...

However, the problem here is that i couldn´t see any resonable way to
access the MapWidgets functions
without returning the entire widget from the interface.

Ian Bambury

unread,
Aug 19, 2009, 7:15:50 PM8/19/09
to google-we...@googlegroups.com
My feeling is that, if you design things properly, you will not need to end up with a bloated interface.

For example, in a registration page, you don't need to have something for every field if you require name/address/phone number, you just link in to HasInternationalContactDetails and that widget deals with all the associated problems. You just set it up with hicd.emailRequired(true) and hicd.phoneRequired(false) and so on. That widget then knows how to set itself up. If you need to insert current details, you use hicd.displayDetails(userContactDetails).

You check everything is OK with if(!hicd.isValid())hicd.displayErrors();

You get the user contact details back with hicd.getContactDetails();

All you need in your interface is HasInternationalContactDetails getContactDetails();

Everything else works from that interface, not yours.

Whatever it is that conforms to that knows all about zip and postal codes and what to validation apply (because it probably has a country dropdown and can use that) same with phone formats.

The HasInternationalContactDetails widget itself uses other widgets (like phone number) which can be used elsewhere if needed but at the very minimum break the functionality into manageable units.

I'd love to give this a try, but unfortunately my current project manager (and funder rolled into one) seems to have just dropped GWT in favour of Zend and the 'here's an idea for a screenshot, make it work' approach to system design.

Sigh!

Davis Ford

unread,
Aug 19, 2009, 8:49:49 PM8/19/09
to google-we...@googlegroups.com
You and others have won me over, Ian :)

I re-factored to all interfaces. One of my biggest pain points was
the crazy number of mock and mock returns I was ending up needing --
that, and I needed to included gwt-dev on the classpath, which the
codehaus gwt-maven-plugin warns not to do...

It still blows up kind of big when you have a form with even a
reasonable number of widgets on it. Example, I have 7 text boxes on
one form. For each, I want to get at its value and also set a blur
handler...so I end up with:

interface Display {
HasValue<String> foo();
HasBlurHandlers fooBlur();
...
}

That leads to 14 methods in the interface -- I wish there was some way
to collapse this. I also have 7-8 additional HasClickHandlers, and a
number of methods like:

void displayFooError(boolean toggle, String msg);

To tell the view to report an error. So in the end, the interface for
*this* particular presenter is fairly large, but so be it, I guess.

Sorry, to hear your project got hi-jacked. I enjoy your blog posts --
hope you will keep posting stuff on GWT.

Regards,
Davis

Dalla

unread,
Aug 20, 2009, 12:44:32 AM8/20/09
to Google Web Toolkit
I think I can see where you´re getting at, but I´m a bit confused here
so I need to ask a couple of more questions :-)

Let´s say we return an interface as in your example, our interface
would then look something like

interface ContactDetailsInterface {
HasInternationalContactDetails getContactDetails();
}

You then have your view:
public class ContactDetailsWidget extends Composite implements
ContactDetailsInterface{
}

But where does the HasInternationalContactDetails come in?
Would your model be implementing that interface?
Something like

public class ContactDetails implements HasInternationalContactDetails
{
}

Sorry for the confusion, I´m glad you´re trying to sort things out for
us though :-)
> 2009/8/19 davis <davisf...@zenoconsulting.biz>

Dean S. Jones

unread,
Aug 20, 2009, 1:17:53 AM8/20/09
to Google Web Toolkit
This is where I diverged from the typical MVP pattern, the Has*
interfaces just became to numerous and unwieldy. My solution was to
make the
"model" richer, and attach a Map of "state values" to each model
property. It was then up to the Presenter to interpret the associated
property state map, and display accordingly.

At first, I had general "model change listeners" drive the UI. I
wanted to convert to GWT 1.6+ custom events and event handlers(bus),
but these grew too numerous also.... still looking for a concise
solution.
> > 2009/8/19 davis <davisf...@zenoconsulting.biz>

Dalla

unread,
Aug 20, 2009, 1:59:39 AM8/20/09
to Google Web Toolkit
I think maybe Ian had other ideas than what I suggested above.

Implementering the HasInternationalContactDetails on the
ContactDetails model,
and then returning it from the ContactDetailsInterface would somehow
make the
widget aware of the model... Which I think would break the MVP
pattern...?
> > f: 313.884.2977- Dölj citerad text -
>
> - Visa citerad text -

Thomas Matthijs

unread,
Aug 20, 2009, 3:14:50 AM8/20/09
to google-we...@googlegroups.com
On Thu, Aug 20, 2009 at 07:59, Dalla<dalla_...@hotmail.com> wrote:
>
> I think maybe Ian had other ideas than what I suggested above.
>
> Implementering the HasInternationalContactDetails on the
> ContactDetails model,
> and then returning it from the ContactDetailsInterface would somehow
> make the
> widget aware of the model... Which I think would break the MVP
> pattern...?

Would you not create a seperate presenter for the
"InternationalContactDetails" and nest them ?

Ian Bambury

unread,
Aug 20, 2009, 7:15:50 AM8/20/09
to google-we...@googlegroups.com
The widget isn't aware of the model, it's just a widget for user input.

Dalla

unread,
Aug 20, 2009, 8:02:00 AM8/20/09
to Google Web Toolkit
I´m still not sure I´m following :-/
Could you please post a short example?

On 20 Aug, 13:15, Ian Bambury <ianbamb...@gmail.com> wrote:
> The widget isn't aware of the model, it's just a widget for user input.
> Ian
>
> http://examples.roughian.com
>
> 2009/8/20 Dalla <dalla_man...@hotmail.com>
> > > - Visa citerad text -- Dölj citerad text -

Thomas Matthijs

unread,
Aug 20, 2009, 8:13:35 AM8/20/09
to google-we...@googlegroups.com
On Thu, Aug 20, 2009 at 14:02, Dalla<dalla_...@hotmail.com> wrote:
>
> I´m still not sure I´m following :-/
> Could you please post a short example?

I think something like this:

interface HasAdres {
HasValue<String> getStreet();
}

class AdresWidget implements HasAdres {
}

class LargeFormWidget implements LargeForm.Display {
AdresWidget aw = new AdresWidget();

public HasAddres getAdress() { return aw; }

}

Ian Bambury

unread,
Aug 20, 2009, 10:17:31 AM8/20/09
to google-we...@googlegroups.com
2009/8/20 Dalla <dalla_...@hotmail.com>


I´m still not sure I´m following :-/
Could you please post a short example?

 Can't do a short example, but what I was imagining goes something like I've detailed below. 

I've used blindingly obvious names (I hope) which you could probably cut down, and I haven't included Composite, just gone for a straight extend of FlowPanel in order to simplify the code a bit. Styles have been hard-coded where normally I'd be adding and removing styles. It's just to show what I had in mind.

Now I realise that this is an awful lot of work just to get an email address :-) but bear in mind that now adding, say, a phone number to the contact details would require a lot less effort. The classes are all reusable. If there's a bug, it's pretty obvious where to look. And none of the classes are unmanageably large (which they might easily get if you hadn't split them up).

Changing the layout doesn't affect anything else. Changing a textbox for a textarea can be done in the one class. You can swap the submit button for an image in one class. You can have different views for different situations or give your users a choice of skins.

Should anyone wants a zip of the project, let me know.

Ian


=================================================================

So...


You have a registration panel of some sort. In its view along with other things, it includes a contact details panel.

The contact details panel includes, well, contact detail input widgets. Amongst them is one for an email address.

The Registration panel is instigated like this:

new Registration(new RegistrationView(this));

where 'this' is the view or whatever you want to add it to.

The Registration class looks like this:

public class Registration implements ClickHandler
{
private RegistrationViewInterface view;

public Registration(RegistrationViewInterface view)
{
this.view = view;
view.getSubmittingWidget().addClickHandler(this);
}

interface RegistrationViewInterface
{
// Submitting widget
HasClickHandlers getSubmittingWidget();

// Check if the input is valid
boolean inputIsAcceptable();

// Flag errors (if any)
void showErrors();
}

@Override
public void onClick(ClickEvent event)
{
/*
* Show any errors (and remove error indicators if OK)
*/
view.showErrors();
/*
* If errors, then give up
*/
if(!view.inputIsAcceptable()) return;
/*
* Input is acceptable
*/
}
}


The Registration view

public class RegistrationView extends FlowPanel implements RegistrationViewInterface
{
private Button submit = new Button("Submit");
ContactDetails contactDetails;

public RegistrationView(HasWidgets parentView)
{
parentView.add(this);
/*
* A few labels as placeholders for other input areas
*/
add(new Label("First Name Input Here"));
add(new Label("Last Name Input Here"));

/*
* Now add a contact details area
*/
contactDetails = new ContactDetails(new ContactDetailsView(this));

/*
* And the submit button
*/
add(submit);
}
@Override
public HasClickHandlers getSubmittingWidget()
{
return submit;
}
@Override
public boolean inputIsAcceptable()
{
boolean returnValue = true;
returnValue &= contactDetails.inputIsAcceptable();
/*
* Also check anything else on the screen here
*/
return returnValue;
}
@Override
public void showErrors()
{
contactDetails.showErrors();
/*
* Also show errors for anything else on the screen here
*/
}
}


So you have a registration screen which includes a contact details panel which can be reused elsewhere.

Contact details

public class ContactDetails
{
  private ContactDetailsViewInterface view;

public ContactDetails(ContactDetailsViewInterface view)
{
this.view = view;
}

interface ContactDetailsViewInterface
{
InputEmail getEmail();
/*
* Add other contact information requirements here
*/
void showErrors();
}

public boolean inputIsAcceptable()
{
boolean returnValue = true;
returnValue &= view.getEmail().inputIsAcceptable();
/*
* Check other contact information requirements here
*/
return returnValue;
}

public void showErrors()
{
view.showErrors();
}
}


Contact details view

public class ContactDetailsView extends FlowPanel implements ContactDetailsViewInterface
{
InputEmail inputEmail;

public ContactDetailsView(HasWidgets parentView)
{
parentView.add(this);

/*
* A few labels as placeholders for other input areas
*/
add(new Label("Land Line Input Here"));
add(new Label("Mobile No Input Here"));

/*
* And an email input widget
*/
inputEmail = new InputEmail(new InputEmailView(this));
}

@Override
public InputEmail getEmail()
{
return inputEmail;
}

@Override
public void showErrors()
{
  inputEmail.showErrors();
}
}

The email input

public class InputEmail
{
private InputEmailViewInterface view;

public InputEmail(InputEmailViewInterface view)
{
this.view = view;
}

interface InputEmailViewInterface
{
String getEmailText();
void showError(boolean error);
/*
* You might want also to be able to set text, flag if optional or not
* etc.
*/
}

public boolean inputIsAcceptable()
{
return BusinessRules.validateEmail(view.getEmailText());
}

public String getEmail()
{
return view.getEmailText();
}

public void showErrors()
{
view.showError(!BusinessRules.validateEmail(view.getEmailText()));
}
}

And the email input view

public class InputEmailView extends FlowPanel implements InputEmailViewInterface
{
private TextBox textbox = new TextBox();

public InputEmailView(HasWidgets parentView)
{
parentView.add(this);
add(new Label("Email"));
add(textbox);
}

@Override
public String getEmailText()
{
return textbox.getText();
}

@Override
public void showError(boolean error)
{
if(error)
{
textbox.getElement().getStyle().setProperty("border", "2px solid #f00");
}
else
{
textbox.getElement().getStyle().setProperty("border", "2px inset #eee");
}
}
}

Ian Bambury

unread,
Aug 20, 2009, 10:32:27 AM8/20/09
to google-we...@googlegroups.com
Hi Davis,

I think you need to move the functionality for each text box into its own set of MVP classes. Everything gets a lot simpler (interfaces, coding, debugging, testing, maintenance etc) and once done, these classes are reusable. You can usually knock out a new one based on one you already have. Except the first one, of course, but you might be able to nick my example code for that :-)

Davis Ford

unread,
Aug 20, 2009, 10:43:31 AM8/20/09
to google-we...@googlegroups.com
Hi Ian -- yep, I've already started moving most of this into model
classes, and then the display interface gets a lot simpler, like:

interface Display {
Model getModel( );
...
}

as opposed to:

interface Display {
HasValue<String> value1();
HasValue<String> value2();
...
}

but I still have a lot of click handlers, blur handlers, etc. i don't
suppose there is any way around that aside from something dean
mentioned which is to return something like a map, but then you have
to create a structured key (like enum), and it just makes coding
against it more of a pain, i think...so i'll live with the multiple
HasHandlers interface methods.

Ian Bambury

unread,
Aug 20, 2009, 12:51:34 PM8/20/09
to google-we...@googlegroups.com
I don't see a need for more than a couple of clickhandlers (submit, cancel, maybe reset) or any blurhandlers in your main interface. What are they all for?

A real-world example would help.

Davis Ford

unread,
Aug 20, 2009, 1:11:39 PM8/20/09
to google-we...@googlegroups.com
7 form fields and i do client side validation so 7 blurhandlers.

1 click handler for submit button.

I also have a help image icon next to each field that the user can
click and get popup help. so that is 7 additional click handlers.
although i'm starting to think this is just stupid to put in the
presenter, as it has little more logic than adding a click handler
that does something like this:

display.getFooHelp().addClickHandler(new ClickHandler() {
public void onClick(ClickEvent evt) {
display.showHelp(event, messages.fooHelp());
}});

There isn't really a lot to validate there in a JUnit test other than
the correct messages.whatever() was sent to display.showHelp().

Anyway, that is 15 interface methods + 1 for the getModel() = 16.

Alejandro D. Garin

unread,
Aug 20, 2009, 2:31:01 PM8/20/09
to google-we...@googlegroups.com
but I still have a lot of click handlers, blur handlers, etc.  i don't
suppose there is any way around that aside from something dean
mentioned which is to return something like a map, but then you have
to create a structured key (like enum), and it just makes coding
against it more of a pain, i think...so i'll live with the multiple
HasHandlers interface methods.

what am I doing is to have hight levels handlers. Suppose you have 4 buttons (new, edit, delete and quit) then your Widget interface will have one handler exposed for the buttons.
When the user clicks over the button one event with the information of which button was pressed and some other information you need will arrive to the presenter.

I'm new using MVP, opinions are welcome.


Eduardo Nunes

unread,
Aug 20, 2009, 3:56:11 PM8/20/09
to google-we...@googlegroups.com
I'm not putting everything in the presenter. For example, I do
validation in the presenter and I just call a method
display.showErrors(ValidationErrors errors); or display.hideErrors().
The widget has the logic to show or hide. For example, I have an
external class that add an "error" class to a widget and add a hint
popup with the error message. When I call showErrors, the widget just
call this class and voila the widget will have the class "error" and a
focus handler to show/hide the hint.

Best regards,
Eduardo S. Nunes
--
Eduardo S. Nunes
http://e-nunes.com.br

Eduardo Nunes

unread,
Aug 20, 2009, 3:57:43 PM8/20/09
to google-we...@googlegroups.com
It's a good way to do that.

Ian Bambury

unread,
Aug 21, 2009, 8:02:10 AM8/21/09
to google-we...@googlegroups.com
What if you broke out each form field into its own view, presenter and interface all of which extend basic abstracts?

Your main interface would look like

FirstNameInput getFirstName();
SurnameInput getSurname();
EmailInput getEmail();

and so on

You could then do things like 

FirstNameInput firstName = view.getFirstName();
firstName.setRequired(true);
if(firstname.isValid())...

FirstNameInput would worry about validation on blur, FirstNameInputView would pass the abstract input presenter the help text. The abstract input presenter would have the code to deal with showing the help text. The abstract input view would deal with putting the help symbol on the screen. The abstract input presenter would hook up to the click event of that symbol.

I know it sounds like a lot of work, but it's all reusable stuff and it's really only organising what you are already doing in a different way.
Reply all
Reply to author
Forward
0 new messages