You are viewing a plain text version of this content. The canonical link for it is here.
Posted to user@pivot.apache.org by Bill van Melle <bi...@gmail.com> on 2010/11/19 07:21:42 UTC

Simpler event listeners?

I'll be showing my WPF bias here as a Pivot newbie, but it seems to me that
with the introduction in Pivot 2.0 of the Bindable interface and the BXML
annotation, Pivot is getting close to the ease of control creation that we
have in Visual Studio.  In VS, if you want to create a user control, you get
a pair of a markup (.xaml) file, and a backing file (in C# or whatever).
 The compiler magically ties the two together into a single class.  Any
component with a Name attribute automatically gets a class field with the
same name, bound to the component when the constructor is run.  The caller
who wants to create the control simply calls the constructor (i.e., new
NameOfControl()).  The .xaml file can put event listeners on components just
by naming a method defined in the backing class (and VS helps with that, of
course).

So those last two items are kind of missing in Pivot at the moment.  The
first one isn't too bad.  Instead of requiring anyone who wants to construct
a control to make a messy call on the bxml serializer, I've taken to the
following methodology:

* For any new user control, create a pair of files ClassName.java and
classname.bxml.  (I'm following the naming convention I see in the samples,
but now that I think of it, is there any reason for them not to be named
identically up to the extension?)

* The bxml top-level element is my:ClassName, with a suitable "my" namespace
defined; ClassName is declared to implement Bindable and extend whatever
Pivot control I'm interested in acting like.  Eclipse helpfully supplies me
semi-automatically with an initialize method, which I can sort of treat like
a parameterless constructor.

* For components I want to manipulate in the backing class, I give them
bxml:id attributes, and add same-named variables to ClassName, with the
@BXML annotation (manually for now, but if I were ambitious, I could see
writing an Eclipse plugin to do that automatically).

* In lieu of a proper constructor, I add a static factory method

    public static ClassName create() throws IOException,
SerializationException { BXMLSerializer bxmlSerializer = new
BXMLSerializer(); return
(ClassName)bxmlSerializer.readObject(ClassName.class, "classname.bxml"); }

so that code which in other systems might call new ClassName() can almost as
painlessly call ClassName.create().

Is this reasonable so far?  Am I missing anything?

So the big remaining hole (well, besides the lack of Intellisense in the
bxml editor, which I'm not really expecting to see filled any time soon), is
hooking up event listeners.  It's kind of painful right now.  I typically
end up writing things like this in the initialize method:

    buttonLogin.getButtonPressListeners().add(new ButtonPressListener() {
        @Override
        public void buttonPressed(Button button) {
            loginButtonPressed();
        }
    });

and writing the program logic in a separate method (unless it's completely
trivial), which in many cases has to be a separate method anyway, because
"this" refers to the wrong thing in the event listener, thanks to Java not
really having closures.  Of course, what I want to write is something like
the following in the bxml file:

   <PushButton bxml:id="buttonLogin" buttonData="Login"
buttonPressed="loginButtonPressed"/>

There seem to be various scripting options where I can attach Javascript
code to a button with something almost that simple:

    ... ButtonPressListener.buttonPressed="buttonClicked(arguments[0])"

but then I have to write a bunch of Javascript that's at least as
complicated as the Java code I wrote above.  Does anyone know a simpler way?
 And if not, how far is Pivot from being able to implement something like my
fantasy above?  I would settle for the more verbose syntax in the JavaScript
example.  The idea would be that when the bxml parser encounters

    SomeListener,someHandler="someMethod"

where the root of the bxml file is a class with the appropriate kind of
listener list, and said class also has a method someMethod with the
signature required by the listener's method, it generates the appropriate
add of the appropriate anonymous class.  It seems to me that Pivot must have
to do most of that work already to make the various scripting cases work.

Re: Simpler event listeners?

Posted by Greg Brown <gk...@mac.com>.
No - script can only call public methods. But if you define a function in a script block, it is effectively private to the page you define it in:

<my:MyWindow>
    <bxml:script>
    // This function is "private" to the document
    function loginButtonPressed() {
        // Perform login
    }
    </bxml:script>

    <PushButton buttonData="Login" ButtonPressListeners.buttonPressed="loginButtonPressed()"/>
</my:MyWindow>


On Nov 19, 2010, at 10:31 AM, ocean ocean wrote:

> Greg,
> 
> So script elements can call private methods on the class they were defined in?
> 
> On Fri, Nov 19, 2010 at 9:24 AM, Greg Brown <gk...@mac.com> wrote:
> > <my:MyWindow bxml:id="myWindow">
> >    <!-- Calls script function -->
> >    <PushButton buttonData="Login" ButtonPressListeners.buttonPressed="loginButtonPressed()"/>
> >
> >    <!-- Calls login() method on root object -->
> >    <PushButton buttonData="Login" ButtonPressListeners.buttonPressed="myWindow.login()"/>
> > </my:MyWindow>
> >
> > The downside is that it is not possible to call private methods this way - however, it could be argued that exposing higher-level operations like "login" via public methods promotes better design anyways.
> 
> Note that, in the script case, the "loginButtonPressed()" function is effectively private to the page in which it is declared, so this approach does not break encapsulation.
> 
> G
> 
> 


Re: Simpler event listeners?

Posted by ocean ocean <ri...@gmail.com>.
Greg,

So script elements can call private methods on the class they were defined
in?

On Fri, Nov 19, 2010 at 9:24 AM, Greg Brown <gk...@mac.com> wrote:

> > <my:MyWindow bxml:id="myWindow">
> >    <!-- Calls script function -->
> >    <PushButton buttonData="Login"
> ButtonPressListeners.buttonPressed="loginButtonPressed()"/>
> >
> >    <!-- Calls login() method on root object -->
> >    <PushButton buttonData="Login"
> ButtonPressListeners.buttonPressed="myWindow.login()"/>
> > </my:MyWindow>
> >
> > The downside is that it is not possible to call private methods this way
> - however, it could be argued that exposing higher-level operations like
> "login" via public methods promotes better design anyways.
>
> Note that, in the script case, the "loginButtonPressed()" function is
> effectively private to the page in which it is declared, so this approach
> does not break encapsulation.
>
> G
>
>

Re: Simpler event listeners?

Posted by Greg Brown <gk...@mac.com>.
> <my:MyWindow bxml:id="myWindow">
>    <!-- Calls script function -->
>    <PushButton buttonData="Login" ButtonPressListeners.buttonPressed="loginButtonPressed()"/>
> 
>    <!-- Calls login() method on root object -->
>    <PushButton buttonData="Login" ButtonPressListeners.buttonPressed="myWindow.login()"/>
> </my:MyWindow>
> 
> The downside is that it is not possible to call private methods this way - however, it could be argued that exposing higher-level operations like "login" via public methods promotes better design anyways.

Note that, in the script case, the "loginButtonPressed()" function is effectively private to the page in which it is declared, so this approach does not break encapsulation.

G


Re: Simpler event listeners?

Posted by Greg Brown <gk...@mac.com>.
> Naming the files Foo.java and Foo.bxml implies that they are partial classes, which isn't the case. Developers may expect "new Foo()" to produce the same results as deserializing Foo.bxml - since it won't, this could be confusing. Using the "Foo.java/foo.bxml" convention avoids this ambiguity.
> 
> But it's also the case that neither can be used without the other -- you can't do new() on the class and get a working component, and you can't deserialize the bxml without casting it to the class.  So I think people already have to understand the structure.  If anything, my strategy of adding a static create method to the class simplifies that -- nobody other than the "owner" of a bxml file ever needs to touch the serializer or even know the name of the bxml file.
> 
> A practical benefit of naming them the same is that they're adjacent in Eclipse's case-sensitive explorer sort, instead of far apart.

Good points. And of course there's no reason you can't use whatever naming scheme you prefer in your own applications.

> > * In lieu of a proper constructor, I add a static factory method...
> > so that code which in other systems might call new ClassName() can almost as painlessly call ClassName.create().
> 
> This is an interesting idea. I wonder if it might be worth adding a parameterized static method to BXMLSerializer to do this, so you don't have to add this to every class:
> 
> public static <T> T create(URL, Resources) throws IOException, SerializationException { ... }
> 
> Doesn't do anything for me.  I add the create method as a way of simplifying the interface to the class, not because I dislike casting the result of the serializer -- I don't want anyone outside the class to even care that there's a bxml file and how to deserialize it.

Ah, OK.

> Hmm, this now makes me wonder if it would be easy to make an Eclipse plugin that would offer a "New" option "Pivot component with bxml", which would create two files with the kind of boilerplate I spoke of.

I had actually considered doing exactly this a few months back but didn't have time to get to it. I think it is a great idea.

> - In general, I'd expect handler methods such as "loginButtonPressed" to be private (you probably don't want to expose this logic to arbitrary callers). We can use reflection to call a private method, but that requires that the code be trusted (i.e. you couldn't do this in an applet unless it was signed). This isn't necessarily a blocker - the same applies to the @BXML annotation, which is often used to populate private members. It's just something to consider.
> 
> Oh dear, so I can't even use @BXML on private members in an unsigned applet?  Well, that kills part of my strategy if I ever want to do an unsigned applet (not important to me right at the moment, though).  Yes, in general listeners ought to be private.

That's correct - the JVM does not allow untrusted code to access private class members. Note that you can still access the values via these namespace argument to initialize(), though.

> - As a developer, I might expect the "loginButtonPressed" method to be defined in script within the page, rather than as a method on the root object. In other words, there's nothing in the syntax that lets me know what object defines the method.
> 
> True, but then currently there's no way at all to call methods on the Java class, so you're not yet thinking to look there.

Sure there is - just give it an ID and then you can invoke methods on it (as shown in the examples from my previous email).

> - Finally, it seems like the loginButtonPressed() method should implement the syntax defined by the buttonPressed() method. Because .NET uses delegates for event handlers (and XAML is compiled), this can be enforced by the compiler. However, since BXML is not compiled (and Java does not support delegates), we can't enforce it. This again suggests that the handler should be script code rather than the name of a method on the root object.
> 
> Can you not use reflection to verify that the specified listener method has a signature that matches what a buttonPressListener is expecting?  

You can - but the compiler won't verify it. The check would have to be done at runtime.

> FWIW, I generally don't use attribute-based event handlers - I prefer the element-based syntax:
> 
> <PushButton buttonData="Login">
>    <buttonPressListeners>
>    function buttonPressed(button) {
>        myWindow.login();
>    }
>    </buttonPressListeners>
> </PushButton>
> 
> Yes, it's a bit more verbose, but I find it easier to read and write. It is also much easier to implement listeners with multiple handler methods this way.
> 
> If I have to be that verbose, I'd much rather hide the verbosity in my Java class' initialize method, and try to keep the bxml file readable.

Makes sense, and I also tend to defer to Java as much as possible. I'm just saying that, when script is appropriate, I prefer this syntax.

G



Re: Simpler event listeners?

Posted by Bill van Melle <bi...@gmail.com>.
>
> Naming the files Foo.java and Foo.bxml implies that they are partial
> classes, which isn't the case. Developers may expect "new Foo()" to produce
> the same results as deserializing Foo.bxml - since it won't, this could be
> confusing. Using the "Foo.java/foo.bxml" convention avoids this ambiguity.
>

But it's also the case that neither can be used without the other -- you
can't do new() on the class and get a working component, and you can't
deserialize the bxml without casting it to the class.  So I think people
already have to understand the structure.  If anything, my strategy of
adding a static create method to the class simplifies that -- nobody other
than the "owner" of a bxml file ever needs to touch the serializer or even
know the name of the bxml file.

A practical benefit of naming them the same is that they're adjacent in
Eclipse's case-sensitive explorer sort, instead of far apart.


> * In lieu of a proper constructor, I add a static factory method...
> > so that code which in other systems might call new ClassName() can almost
> as painlessly call ClassName.create().
>
> This is an interesting idea. I wonder if it might be worth adding a
> parameterized static method to BXMLSerializer to do this, so you don't have
> to add this to every class:
>
> public static <T> T create(URL, Resources) throws IOException,
> SerializationException { ... }
>

Doesn't do anything for me.  I add the create method as a way of simplifying
the interface to the class, not because I dislike casting the result of the
serializer -- I don't want anyone outside the class to even care that
there's a bxml file and how to deserialize it.

Hmm, this now makes me wonder if it would be easy to make an Eclipse plugin
that would offer a "New" option "Pivot component with bxml", which would
create two files with the kind of boilerplate I spoke of.



> The "this" reference in the inner class points to itself, but you can
> always use <OuterClass>.this to get access to the containing object if you
> need to. However, in most cases this isn't necessary since inner classes
> don't need to qualify access to outer class methods.
>

Good to know (about qualified this).  I already knew I could call outer
methods without qualification, but a common thing that a button press
listener does is popup a dialog, something that requires passing the parent
reference in.



> - In general, I'd expect handler methods such as "loginButtonPressed" to be
> private (you probably don't want to expose this logic to arbitrary callers).
> We can use reflection to call a private method, but that requires that the
> code be trusted (i.e. you couldn't do this in an applet unless it was
> signed). This isn't necessarily a blocker - the same applies to the @BXML
> annotation, which is often used to populate private members. It's just
> something to consider.
>

Oh dear, so I can't even use @BXML on private members in an unsigned applet?
 Well, that kills part of my strategy if I ever want to do an unsigned
applet (not important to me right at the moment, though).  Yes, in general
listeners ought to be private.



> - As a developer, I might expect the "loginButtonPressed" method to be
> defined in script within the page, rather than as a method on the root
> object. In other words, there's nothing in the syntax that lets me know what
> object defines the method.


True, but then currently there's no way at all to call methods on the Java
class, so you're not yet thinking to look there.  I think having the Java
class of the bxml root be a place for a listener implementation would be no
harder or unexpected a place for a programmer to look than, say, inside an
included .js file.



> - Finally, it seems like the loginButtonPressed() method should implement
> the syntax defined by the buttonPressed() method. Because .NET uses
> delegates for event handlers (and XAML is compiled), this can be enforced by
> the compiler. However, since BXML is not compiled (and Java does not support
> delegates), we can't enforce it. This again suggests that the handler should
> be script code rather than the name of a method on the root object.
>

Can you not use reflection to verify that the specified listener method has
a signature that matches what a buttonPressListener is expecting?  (I don't
know -- I'm not a Java expert.)  And if not, what happens?  You get a Java
runtime error when you call the listener method? I'd live with that -- as it
is, bxml deserialization errors are often plenty cryptic :).



> FWIW, I generally don't use attribute-based event handlers - I prefer the
> element-based syntax:
>
> <PushButton buttonData="Login">
>    <buttonPressListeners>
>    function buttonPressed(button) {
>        myWindow.login();
>    }
>    </buttonPressListeners>
> </PushButton>
>
> Yes, it's a bit more verbose, but I find it easier to read and write. It is
> also much easier to implement listeners with multiple handler methods this
> way.


If I have to be that verbose, I'd much rather hide the verbosity in my Java
class' initialize method, and try to keep the bxml file readable.

Re: Simpler event listeners?

Posted by Greg Brown <gk...@mac.com>.
> Instead of requiring anyone who wants to construct a control to make a messy call on the bxml serializer, I've taken to the following methodology:
> 
> * For any new user control, create a pair of files ClassName.java and classname.bxml.  (I'm following the naming convention I see in the samples, but now that I think of it, is there any reason for them not to be named identically up to the extension?)

We talked about adopting this convention a while back. However, I actually prefer the current naming convention. Naming the files Foo.java and Foo.bxml implies that they are partial classes, which isn't the case. Developers may expect "new Foo()" to produce the same results as deserializing Foo.bxml - since it won't, this could be confusing. Using the "Foo.java/foo.bxml" convention avoids this ambiguity.

> * The bxml top-level element is my:ClassName, with a suitable "my" namespace defined; ClassName is declared to implement Bindable and extend whatever Pivot control I'm interested in acting like.  Eclipse helpfully supplies me semi-automatically with an initialize method, which I can sort of treat like a parameterless constructor.

It's sort of like a constructor, though you of course can also define an actual constructor.

> * In lieu of a proper constructor, I add a static factory method
> 
>     public static ClassName create() throws IOException, SerializationException {
>         BXMLSerializer bxmlSerializer = new BXMLSerializer();
>         return (ClassName)bxmlSerializer.readObject(ClassName.class, "classname.bxml");
>     }
> 
> so that code which in other systems might call new ClassName() can almost as painlessly call ClassName.create().

This is an interesting idea. I wonder if it might be worth adding a parameterized static method to BXMLSerializer to do this, so you don't have to add this to every class:

public static <T> T create(URL, Resources) throws IOException, SerializationException { ... }

(or something like that)

> So the big remaining hole (well, besides the lack of Intellisense in the bxml editor, which I'm not really expecting to see filled any time soon), is hooking up event listeners.  It's kind of painful right now.  I typically end up writing things like this in the initialize method:
> 
>     buttonLogin.getButtonPressListeners().add(new ButtonPressListener() {
>         @Override
>         public void buttonPressed(Button button) {
>             loginButtonPressed();
>         }
>     });

Yup - typical inner class listener implementation.

> writing the program logic in a separate method (unless it's completely trivial), which in many cases has to be a separate method anyway, because "this" refers to the wrong thing in the event listener, thanks to Java not really having closures.  

The "this" reference in the inner class points to itself, but you can always use <OuterClass>.this to get access to the containing object if you need to. However, in most cases this isn't necessary since inner classes don't need to qualify access to outer class methods.

> Of course, what I want to write is something like the following in the bxml file:
> 
>    <PushButton bxml:id="buttonLogin" buttonData="Login" buttonPressed="loginButtonPressed"/>

Seems like a reasonable request, and I like the concept. However, I see a couple potential issues with it:

- Without the "ButtonPressListener" prefix on "buttonPressed", we don't know which interface defines that method. We'd have to look for it based on naming convention. First, we'd have to look for all interfaces whose names start with "PushButton". Since none of those define "buttonPressed", we'd have to move up the class hierarchy to "Button", and so on (this isn't necessary in XAML because .NET uses delegates instead of interfaces for listeners).

- In general, I'd expect handler methods such as "loginButtonPressed" to be private (you probably don't want to expose this logic to arbitrary callers). We can use reflection to call a private method, but that requires that the code be trusted (i.e. you couldn't do this in an applet unless it was signed). This isn't necessarily a blocker - the same applies to the @BXML annotation, which is often used to populate private members. It's just something to consider.

- As a developer, I might expect the "loginButtonPressed" method to be defined in script within the page, rather than as a method on the root object. In other words, there's nothing in the syntax that lets me know what object defines the method. With the existing syntax, it is clear:

<my:MyWindow bxml:id="myWindow">
    <!-- Calls script function -->
    <PushButton buttonData="Login" ButtonPressListeners.buttonPressed="loginButtonPressed()"/>

    <!-- Calls login() method on root object -->
    <PushButton buttonData="Login" ButtonPressListeners.buttonPressed="myWindow.login()"/>
</my:MyWindow>

The downside is that it is not possible to call private methods this way - however, it could be argued that exposing higher-level operations like "login" via public methods promotes better design anyways.

- Finally, it seems like the loginButtonPressed() method should implement the syntax defined by the buttonPressed() method. Because .NET uses delegates for event handlers (and XAML is compiled), this can be enforced by the compiler. However, since BXML is not compiled (and Java does not support delegates), we can't enforce it. This again suggests that the handler should be script code rather than the name of a method on the root object.

FWIW, I generally don't use attribute-based event handlers - I prefer the element-based syntax:

<PushButton buttonData="Login">
    <buttonPressListeners>
    function buttonPressed(button) {
        myWindow.login();
    }
    </buttonPressListeners>
</PushButton>

Yes, it's a bit more verbose, but I find it easier to read and write. It is also much easier to implement listeners with multiple handler methods this way.

G