How do I connect menu items in contextual menus to controller actions?

classic Classic list List threaded Threaded
11 messages Options
Reply | Threaded
Open this post in threaded view
|

How do I connect menu items in contextual menus to controller actions?

Marcelo
I am modifying the simplified project from the examples.
I added a context menu to a to text field and inserted a menu item with fx:id="alsoSayHelloActionTarget".
I added a simple method to the controller that delegates to sayHello():

    public void alsoSayHello() {
        sayHello();
    }

What else do I need to do to have Griffon connect the menu item with the controller?
Thanks,

Marcelo.
Reply | Threaded
Open this post in threaded view
|

Re: How do I connect menu items in contextual menus to controller actions?

aalmiray
Administrator
As section 8.4.2 of the guide shows, you must mark the location where the action will be attached. (http://griffon-framework.org/guide/2.9.0/#_action_support). You've chosen to use the id option.

The next step is to connect the actions. You may have seen a line like the following one in the generated View

     connectActions(node, controller);

This instructs the View to connect the actions between the root node (whatever it may be) and the controller. Depending on how you've setup the hierarchy it may be the case that the ContextMenu is not reachable, in which case you must explicitly connect the actions like so

     connectActions(contextMenu, controller);

Make sure this invocation occurs once, otherwise you may end up with duplicate behavior or binding exceptions. If you're creating the ContextMenu on the go, then invoke connectActions immediately after adding all menus to the context menu.

Cheers,
Andres

Reply | Threaded
Open this post in threaded view
|

Re: How do I connect menu items in contextual menus to controller actions?

Marcelo
Hi Andres,

Thanks for the message.

I was using the id option and I had the
connectActions(node, controller);
 line, that's why I didn't know why it was not working. At first I thought all the information about actions was taken from parsing the fxml file (when there is one).

I saw an example using JavaFXUtils.configure():

JavaFXUtils.configure(button, (JavaFXAction) actionFor(controller, "sayHello").getToolkitAction());

but I noticed the button was defined in the initUI method.

I am using SceneBuilder to edit the view, and adding a context menu to any component can be easily done using it. Do I need to set an id in each menu item and inject the member with @FXML in the view? It seems overkilling.

SceneBuilder expects the action methods in the controller (the griffon view in this case). and I know I could implement the action methods in the view and forward them to the controller. Is this something that should be avoided for some reason? I am thinking this will potentially get out of control for implementing mouse, keyboard, zoom and touch events... How are those typically implemented in Griffon?

Thanks again!

Marcelo.



Reply | Threaded
Open this post in threaded view
|

Re: How do I connect menu items in contextual menus to controller actions?

aalmiray
Administrator
That's the thing. You either set an id on the target widget or apply `JavaFXUtils.griffonActionId` on it. These are the two strategies to identify the action injection point.

In your case you must define an id for each menuItem and the contextMenu itself. This way you can inject the ContextMenu in the view using  a field annotated with @FXML. You only need the ContextMenu field. The last step is to call `connectActions(contextMenu, controller);`.

There's no one size fits all strategy in this case. My recommendation is to attach controller actions when you know for a fact that the action requires some logic that's not attached to the UI/View.

A zoom in/out functionality could be implemented using the "standard" fxml way, that is, define an action handler on the widget such as "#handleZoom", then implement the method on the View. It really doesn't matter what value you put in fx:controller as it will always be set to the View, unless you load the fxml file yourself (which is also an option).

One reason to forward the zoom value to the Model is to take advantage of the Presentation Model pattern, which makes a lot of sense to test logic without involving the UI directly. The controller might want to know what's the current zoom level and react to changes, hence why you would put that value in the Model and trigger a Controller action. If that's not the case and the zoom is just a View only thing then no need to involve Model and Controller. Same reasoning applies to other user events such as focus, mouse events and so on.

In summary, forwarding actions from View to Controller is possible and makes sense in some scenarios. My personal preference is to follow the PMVC pattern. There have been a few cases where exclusive action handling by the View was needed. None for forwarding actions from View to Controller so far.

One more thing, if you do forward the action make sure to avoid calling the action method directly, that is `controller.sayHello()`, as this will skip invoking action handlers by the `ActionManager`. I believe the base View class gives provides a method to locate a controller action. Once you do simply call `execute()` on it, such as  `actionFor(controller, "sayHello").execute();`.

Cheers,
Andres
Reply | Threaded
Open this post in threaded view
|

Re: How do I connect menu items in contextual menus to controller actions?

Marcelo
This post was updated on .
Hi Andres,

Thanks for your answer. I tried following the tutorial where JavaFXUtils.griffonActionId is mentioned, but it does not work. I first made the import:
<?import griffon.javafx.support.JavaFXUtils?>
and edit the menu item to look like the one in the tutorial:
<MenuItem JavaFXUtils.griffonActionId="sayHello" text="sayHello" />
and finally call:
connectActions(node, controller)
in the view, but the action is not invoked.

Also, after doing those modifications in the fxml file, SceneBuilder is unable to load the file.

Thanks again!
Marcelo.

Ps: Sorry if you got mails with editions to this question. For some reason I was running the simplified example from the source code, and I noticed a dependency on version 2.7.0. Fixing that solved that part of the problem...
Reply | Threaded
Open this post in threaded view
|

Re: How do I connect menu items in contextual menus to controller actions?

Marcelo
Hi Andres,

I am thinking of ways to make JavaFXUtils "smarter".

Having SceneBuilder not being able to load the fxml file after adding a reference to JavaFXUtils is really a pain, so I guess it should be used as a last resource solution. Also, JavaFXUtils should be able to find context menus since every javafx.scene.control.Control is able to provide one.

After thinking for a while and looking at the code I came up with 2 ideas that I want to propose:

1 - In the findElements method of JavaFXUtils about line # 1319 add a check to see if root is an instance of javafx.scene.control.Control. If so, retrieve the context menu with getContextMenu, check if not null, and if it is not null call findElements with it to process its MenuItems.

2 - Improve how JavaFXUtils identifies the "ActionTarget"s to connect the actions. For this part I see 3 solutions:

    2.a - Deprecate the "ActionTarget" suffix altogether. It's too long. If deprecated consider 2.c and 2.d

    2.b - If not deprecated, add the ability to introduce a number that should be allow the fx:id to remain unique within the fxml file and that JavaFXUtils will disregard and connect to the desired action. Following this rule myActionActionTarget, myActionActionTarget2, myActionActionTarget3 will point to the same myAction action in the GriffonController

    2.c - Following the rationale of the previous point, replace the "ActionTarget" for "_GAT_" (short for Griffon Action Target) plus a number following it that will help enforcing unique fx:ids. Following this rule myAction_GAT_, myAction_GAT_2, myAction_GAT_3 will point to the same myAction action in the GriffonController. Obviously "_GAT_" could be replaced with something else if it makes more sense...

    2.d - Or, use the previous new Action Target Identifier "_GAT_" as a marker to specify the name of the action after it. This will allow fx:ids with the form "control_ActionTargetIdentifier_ActionName". Following this rule myButton_GAT_myAction, myMenuItem_GAT_myAction and whatever identifier ending in _GAT_myAction will point to the same myAction action in the GriffonController.

The last one, 2.d seems to me the best solution, because it will let someone reading variable in the GriffonView code know about the control and the action is supposed to execute. The solution could even be implemented even if "ActionTarget" is still part of the specification.

Do you think the improvements are viable?
Thanks,

Marcelo.

Reply | Threaded
Open this post in threaded view
|

Re: How do I connect menu items in contextual menus to controller actions?

aalmiray
Administrator
Ah I see, yes this is a know issue with SceneBuilder. Reason being that SceneBuilder must have access to the JAR files where additional widgets/controls may be available. In your case you must make sure that griffon-core and griffon-javafx are reachable to SceneBuilder. You may import these files in SceneBuilder using a menu option. You might need jsr-305 too. This will become a pain if newer releases of Griffon provide more updates in the JavaFX area (2.10.0 includes a lot of changes btw).

For this reason I don't use SceneBuilder at all. The FXML editing capabilities found in IntelliJ are good enough for my personal use.

This being said, for those that are comfortable using SceneBuilder (which can be launched from IntelliJ too, as well as NetBeans) then importing additional JARs into SB is the way to go.

#1 makes sense, will work on that.
#2 I kind of like using JavaFXUtils.griffonActionId more these days as you can bind the same action to different controllers.
#2b interesting idea. We currently use CSS selectors to locate target widgets by id. We'd have to switch to a regex approach (or id.startsWith(actionIdMinusNumber)) which might make connecting actions a bit slower. Worth the try.
#2c & #2d I'm not so keen on this convention _but_ what if the convention were to be defined by the application developer herself? Right now the convention is fixed but it could be opened in such a way that you could define your own ActionMatcher strategy. What do you think?

Cheers,
Andres
Reply | Threaded
Open this post in threaded view
|

Re: How do I connect menu items in contextual menus to controller actions?

Marcelo
Hi Andres,

# 2 can coexist with my proposed improvement, right?
Your solution for #2c & #2d is great! I think
I think anything that will help developers using SceneBuilder with Griffon will be a total hit!
Thanks!

Marcelo.
Reply | Threaded
Open this post in threaded view
|

Re: How do I connect menu items in contextual menus to controller actions?

aalmiray
Administrator
Yes, it should work without a problem.

Next week I'll figure out a way to expose the action matching strategy for configuration. In the meantime, if you'd like to give a try to the latest code on the development branch you'll find that findElements/findElement/findNode has been updated https://github.com/griffon/griffon/commit/366b4a8beadc6192fa5ddccb87a398f355d39721

Cheers,
Andres
Reply | Threaded
Open this post in threaded view
|

Re: How do I connect menu items in contextual menus to controller actions?

Marcelo
Hi Andres,

Thanks!!! I will give it a try.

Marcelo.
Reply | Threaded
Open this post in threaded view
|

Re: How do I connect menu items in contextual menus to controller actions?

aalmiray
Administrator
There's another update covering the ability to customize the action matching strategy

https://github.com/griffon/griffon/issues/209

Cheers,
Andres