JavaFX events/listeners/handlers - java

I have 2 listViews and 2 custom controls on my scene. When i press on an item in the listView, i would like to raise the event, and then be able to handle this anywhere else in the application. Ideally i would like my 2 custom controls to listen for this event, and handle it when it is raised.
Not sure if this is right, but so far i have this
Here is my custom event, i would also like to pass in an argument of which Offer is it, not sure how to do this though?
public class OfferChangedEvent extends Event {
private static final long serialVersionUID = 1L;
public static final EventType<OfferChangedEvent> OFFER_CHANGED = new EventType<OfferChangedEvent>(ANY, "OFFER_CHANGED");
public OfferChangedEvent() {
this(OFFER_CHANGED);
}
public OfferChangedEvent(EventType<? extends Event> arg0) {
super(arg0);
}
public OfferChangedEvent(Object arg0, EventTarget arg1, EventType<? extends Event> arg2) {
super(arg0, arg1, arg2);
}
}
Now on one of my custom controls, i raise the event when a button is clicked, like so
#FXML
public void showOffer(ActionEvent event) {
Button btnView = (Button)event.getSource();
// can i pass in parameter of the offer here??
btnView.fireEvent(new OfferChangedEvent());
}
Now i would like a listview to listen for the event and handle it, i have this but it aint working
// when offer is changed, we dont want to see events
this.eventListPane.addEventHandler(OfferChangedEvent.OFFER_CHANGED, new EventHandler<OfferChangedEvent>() {
#Override
public void handle(OfferChangedEvent event) {
Console.Log("Recived");
eventListPane.setVisible(false);
// How can i get the argument of the offer passed???
}
});

Unfortunately there isn't a straightforward answer to this question. For your case there are about 3 different ways to wire up your application so that different parts can react to changes in other parts:
You could bind to properties and listen for changes.
You could setup listeners and then notify them of changes.
You could use a messaging bus.
Which one you use depends on various factors, but considering what you have done above I would go with number three. You can try this https://github.com/bennidi/mbassador

Related

GWT Updating Main Content using EventBus and ActivityMapper - Can GWT help me to do this cleaner?

I would like to know
Am I doing things (the following) too complicated?
Is there a better way to update the main content of an activity that allows me to bookmark the event calendar of a store via URL like #MainPlace:eventCalendar?storeId=<id>?
I'm having this ActivityMapper here
public class AppActivityMapper implements ActivityMapper {
private ClientFactory clientFactory;
private MainActivity mainActivity;
// ..
#Override
public Activity getActivity(Place place) {
if (place instanceof LoginPlace) {
return new LoginActivity((LoginPlace) place, clientFactory);
} else if (place instanceof MainPlace) {
if(this.mainActivity == null) {
this.mainActivity = new MainActivity((MainPlace) place, clientFactory);
} else {
this.mainActivity.updateMainContent(((MainPlace) place).getMainContentToken());
}
return this.mainActivity;
}
return null;
}
}
and a MainActivity that controls my MainView that is just a menu ond the left side and the main content on the right side.
I want to decouple my views like in Best Practices for Architecting GWT App which is why I'm trying to control the main content by using events that get fired as something gets clicked in my MenuView.
Therefore I am initializing some event handlers in my MainActivity that react to clicks on the buttons in my menu to delegate the update to the MainView.
public class MainActivity extends AbstractActivity implements MainView.MainPresenter {
#Override
public void start(AcceptsOneWidget panel, EventBus eventBus) {
this.mainView = this.clientFactory.getMainView();
this.mainView.setPresenter(this);
this.mainView.initialize();
this.eventBus = eventBus;
this.eventBus.addHandler(HomeClickedEvent.TYPE, new HomeClickedHandler() {
#Override
public void onHomeClicked(HomeClickedEvent event) {
goTo(new MainPlace("home"));
}
});
this.eventBus.addHandler(EventCalendarClickedEvent.TYPE, new EventCalendarClickedHandler() {
#Override
public void onEventCalendarClicked(EventCalendarClickedEvent eventCalendarClickedEvent) {
goTo(new MainPlace("eventCalendar?storeId=" + eventCalendarClickedEvent.getStoreId()));
}
});
panel.setWidget(this.mainView.asWidget());
}
#Override
public void goTo(Place place) {
this.clientFactory.getPlaceController().goTo(place);
}
#Override
public void updateMainContent(String currentMainContentToken) {
this.mainView.updateMainContent(currentMainContentToken);
}
}
this event gets fired by MenuPresenter.clickedEventCalendar() that reacts to a click on the corresponding menu entry of the MenuView:
public class MenuPresenter implements MenuView.MenuPresenter {
// ..
#Override
public void clickedEventCalendar(Long storeId) {
this.eventBus.fireEvent(new EventCalendarClickedEvent(storeId));
}
}
One of the things I really don't like is this where I append parameters to the token e.g. to display the event calendar of a store given by storeId:
#Override
public void onEventCalendarClicked(EventCalendarClickedEvent eventCalendarClickedEvent) {
goTo(new MainPlace("eventCalendar?storeId=" + eventCalendarClickedEvent.getStoreId()));
}
is there a cleaner solution for a problem like this in GWT? I don't like the fact that I'd have to parse that string in my actual event calendar. Am I using the ActivityMapper wrong or is there simply no other way to do this?
This question should really be split into several separate ones, but that's maybe something to keep in mind for the future. If you're asking one thing then it's easier to answer thoroughly and others can find the answer easier too.
Anyway, I can see a few improvements:
use EventBinder to get rid a bit of the cruft when handling and creating new events.
if you just want to let the presenter know that a button was pressed on in the view (associated with that presenter) sending a custom event over the event bus is a bit of an overkill. Depending on your needs you can expose the button in your view's interface:
public interface Display {
HasClickHandlers getButton();
}
And then just register the ClickHandler in your presenter.
Or, if you need to do something view- and presenter- related on the click, register the ClickHandler in your view and call the presenter:
// In MainView:
#UiHandler("button")
void handleClick(ClickEvent event) {
// Do some stuff with view,
// like hide a panel or change colour
panel.setVisible(false);
// Let the presenter know that a click event has been fired
presenter.onEventCalendarClicked();
}
you're right - creating MainPlace like you are proposing is wrong. You are creating the token too soon - that's what the tokenizer associated with the place is for. You should create MainPlace by passing just the storeId to the constructor - why should MainPresenter (or any other class using this place) should know how to create the token? MainPlace should look more like this:
public class MainPlace extends Place {
private final Long storeId;
public MainPlace(Long storeId) {
this.storeId = storeId;
}
public Long getStoreId() {
return storeId;
}
public static class Tokenizer implements PlaceTokenizer<MainPlace> {
#Override
public MainPlace getPlace(String token) {
return new MainPlace(Long.valueOf(token));
}
#Override
public String getToken(MainPlace place) {
return "eventCalendar?storeId=" + place.getStoreId();
}
}
}
Now, it's the Tokenizer's responisibily to create and parse the token. Just remember to register it on your PlaceHistoryMapper.

GWT Event Handling best practice

I'm quite new to interface design and struggling to figure out what the best way to handle events is. In the straight forward case of the handler and the (in this case) buttons causing the event being in the same class, that's fine, I get it. The handler can see the buttons so that it can say:
if (event.getSource() == myButton)
and also, the handler is in the same class so it can add tabs to an object local to that class or similar.
Problem: I don't know how I should be dealing with the case when the handlers and event generators are in different classes.
e.g.
From my main layout class I create and show a custom dialog. That dialog is implemented in its own class. Ideally dialog would use the handler from the main layout class (it implements ClickHandler), which would be fine, but my application has a few different ClickEvents. I distinguish between them as above by checking the source. In this case the buttons are in the dialog class though, so I can't simply say:
if (event.getSource() == myDialogbutton)
as myDialogButton is not in scope.
Any hints for how this should work would be appreciated.
D
Perhaps I can help you with my solution ...
I inherited ClickHandler to an own class which is generic. You can give the ClickHandler any kind of object you want and will be able to access it from the method within.
Example:
import com.google.gwt.event.dom.client.ClickHandler;
public abstract class ClickHandlerData<T> implements ClickHandler {
private T data;
public ClickHandlerData(T data)
{
this.data = data;
}
public T getData()
{
return data;
}
public void setData(T data)
{
this.data = data;
}
}
Now, in case of a button:
Button btn = new Button("click me");
btn.addClickHandler(new ClickHandlerData<Button>(btn)) {
public void onClick(ClickEvent event) {
Button btn = getData();
...
}
}
I use this class to pass parameters like Integers or something else to the ClickHandler. For instance:
for (int i=0;i<10;i++)
{
Button btn = new Button("click me");
btn.addClickHandler(new ClickHandlerData<Integer>(i)) {
public void onClick(ClickEvent event) {
Window.alert("you klicked button "+getData());
...
}
}
}
I also do the same for AsyncCallbacks, for Commands, for everything else I need to pass data to.
Hope this helped you a bit.
It appears to me that you are trying to use one listener for multiple buttons, unless several of the buttons have the same function they should have different listeners.
In general you should try to have one listener per function, instead of one listener per "event generator".
If you have for example a logout button, it may have a listener from the LoginStatusWidget (displaying who the client is logged in as) and a listener from an object responsable of notefying the server of the logout.
It will serve to seperate the components from each other.
At first i recommend you to try to collect your Buttons and their ClickHandlers in the same class, but if in your case it is not possible, I have a suggestion to you:
When you are creating your Button you can add some information to them:
Button button = new Button("submit");
button.setLayoutData(someObj);
And then after firing event you can get your Button from event in your ClickHandler and find out which button it is :
Button button = (Button) event.getSource();
MetaData someObj = (MetaData) button.getLayoutData();
Try creating a new listener for each anonymous or serial widget e.g. button in a FlexTable. That way their life cycles are connected and they only refer to each other.
Extend the widget
Give it an id and add it to the constructor [make sure the id is one of a kind]
Implement the listener class.
create a new instance of the listener each time you create an item of the same kind.
I'm guessing there are specific objects connected to the widgets. If so keep a HashMap.
May the force be with you
Can't you just do:
final Button source= new Button("My Button");
button.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
doSomething(source);
}
}
Note the button instance has to be marked final.

Testing Presenters in MVP GWT application

I have a simple application and want to make it testable. I m new in this area.
Here is a simple Presenter, taking in mind this code ,could you advice or give me some example how to test it.
public class SomePresenter extends Presenter<MainPanelPresenter.Display>
{
public interface Display extends WidgetDisplay
{
HasClickHandlers getAddButton();
HasClickHandlers getDeleteButton();
void setData(ArrayList<Person> data);
ArrayList<String> getSelectedRows();
Widget asWidget();
}
private final DispatchAsync dispatcher;
public static final Place PLACE = new Place("main");
#Inject
public SomePresenter(DispatchAsync dispatcher, EventBus eventBus, Display display)
{
super(display, eventBus);
this.dispatcher = dispatcher;
bind();
}
protected void onBind()
{
display.getAddButton().addClickHandler(new ClickHandler()
{
public void onClick(ClickEvent event)
{
eventBus.fireEvent(new AddButtonEvent());
}
});
display.getDeleteButton().addClickHandler(new ClickHandler()
{
public void onClick(ClickEvent event)
{
ArrayList<String> list = display.getSelectedRows();
deletePerson(list);
}
});
}
....
private void loadDbData()
{
..........
}
private void deletePerson(ArrayList<String> ids)
{
..........
}
}
Edit:
What does the Presenter is, load initial data from db, have 2 buttons add and delete.
When add is press then a new form is load and user is able to input data and save to the db,
delete button just delete person from db.
Thanks
The general idea of unit testing such a class would be, like for any other class :
create Mock version of the dependencies (Display, EventBus, etc...)
set expectations on what the depdencies should do when the Presenter works
exercice the Presenter and check the expectations
However there are a couple of issues with your version of the Presenter :
The loadDbData() method is not showed, but I assumed it means the Presenter also has access to some other component that does the fetching. Can this component be abtracted in a dependency, and mocked liked the rest ?
Then there is the testing of bind(). The only responsibility of your Presenter in this method is to set up callbacks on some buttons provided by the Display. What you want to test is both :
That the callbacks are set
That the set callbacks do the expected things
A few ideas to help with the later :
You can reduce the coupling between Presenter and Button. If possible, change the Display interface from :
Button getAddButton();
to
addAddButtonClickedHandler(ClickHandler);
This means your Presenter does not have to use a Display object that returns actual BUtton
You can reduce the callbacks content to calling a single method, that you can then test in isolation
protected void bind() {
display.addAddButtonClickHandler(new ClickHandler() {
public void onClick(ClickEvent) {
fireAdded();
}
});
}
// The fireAdded function can be tested independenty of the Display, potentially with
// a mock EventBus
protected void fireAdded() {
event.fireEvent(....)
}
If you really want to check that the callbacks are properly set, than you can use a 'Dummy' implementation of the Display class, that provides you a list of all the callbacks, and let you call them
private class DummyDisplay implements Display {
private List<ClickHandler> addButtonClickHandlers;
public void addAddButtonClickHandler(ClickHandler handler) {
addButtonClickHandlers.add(handler);
}
public void fireAddButtonClick() {
for (ClickHandler h in addButtonClickHandlers) {
h.onClick(new ClickEvent());
}
}
// ....
}
Then your test would :
create a presenter with such a dummy display
use bind to set the callbacks
use display.fireAddButtonClick() to simulate a user clicking
check that has the result of the click, the effects of fireAdded are seen
This type of class (that mostly glue other classes together) can tend to be hard to test ; at some point, it the other classes are thoroughly tested it can become slightly counter productive to concentrate on the gluers, rather than the glued.
Hoping this helps.

How do I access the source of an ActionEvent when the ActionListener is located in a different class?

I can't get my head round this one. I've tried to adhere to the MVC pattern for the first time and now have difficulties accessing the source of an ActionEvent because the ActionListener is located in a different class. But let the code do the talking...
In the "view":
// ControlForms.java
...
private JPanel createSearchPanel() throws SQLException {
...
comboBoxCode = new JComboBox(); // Field comboBoxCode -> JComboBox()
SwingUtilities.invokeLater(new Runnable() {
public void run() {
AutoCompleteSupport<Object> support = AutoCompleteSupport.install(
comboBoxCode, GlazedLists.eventListOf(jnlCodeArray));
}
}); // Auto-Complete comboBox from GlazedLists
...
public void setComboListener(ComboListener comboListener) {
comboBoxCode.addActionListener(comboListener);
}
...
}
Then, in what I term the controller, I have two different classes:
// Controller.java
public MyController() throws SQLException {
...
addListeners();
}
...
private void addListeners(){
View view = getView();
getView().getControlForm().setComboListener(new ComboListener());
}
and
public class ComboListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
System.out.println("ComboBox listened to! e = " + e.toString());
}
}
Now, e obviously doesn't give the name of the variable (which at the moment I wish it would), so I cannot if test for e.getSource().
My question is thus: is there either a) a way to query (via if for example) the source of e, or b) a less complicated way to get to the variable name?
Many, many thanks in advance for your insights and tips!
Why do you need the name of the variable? Why can't you do the event handling like this
public class ComboListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
JComboBox source = (JComboBox)e.getSource();
//do processing here
}
}
I'd think that if you need to do processing according the variable name, obviously you need different listeners for different combo boxes.
Generally, there are only two situations in which you should use a listener like that: a) you're going to handle a certain event the same way for a bunch of objects, or b) you're only going to use the listener for one object. In the latter case, I'd prefer handling the event locally anyway.
That said, the direct answer to your question is: you shouldn't have to check inside your ActionListener implementation to see whether the appropriate object is the source of the event; you should simply only add the ActionListener to that one object.
One final note: without knowing the specifics of your architecture... generally, MVC will treat all event handling as part of the View (it reduces coupling) and the View will pass commands or method calls or your own events (i.e., not Swing's) to the Controller.

How to use the GWT EventBus

I wonder how to use the EventBus or whether there are some better solutions to send an Event through the project.
Widget1 has a Button. Widget2 has a Label, that should change when I press the button. These widgets are in a DockLayout:
RootLayoutPanel rootLayoutPanel = RootLayoutPanel.get();
DockLayoutPanel dock = new DockLayoutPanel(Unit.EM);
dock.addWest(new Widget1(), 10);
dock.add(new Widget2());
rootLayoutPanel.add(dock);
I have declared an handleClickAlert in Widget1:
#UiHandler("button")
void handleClickAlert(ClickEvent e) {
//fireEvent(e);
}
When you divide the project into logical parts (for example with MVP), then the different parts sometimes need to communicate. Typical this communication is done by sending status changes, e.g.:
user logged-in / logged-out.
user navigated directly via URL to a page, so the menu needs to be updated.
Using the event bus is quite logical in those cases.
To use it you instantiate one EventBus per app which is then used by all other classes. To achieve this use a static field, factory or dependency injection (GIN in case of GWT).
Example with your own event types:
public class AppUtils{
public static EventBus EVENT_BUS = GWT.create(SimpleEventBus.class);
}
Normally you'd also create your own event types and handlers:
public class AuthenticationEvent extends GwtEvent<AuthenticationEventHandler> {
public static Type<AuthenticationEventHandler> TYPE = new Type<AuthenticationEventHandler>();
#Override
public Type<AuthenticationEventHandler> getAssociatedType() {
return TYPE;
}
#Override
protected void dispatch(AuthenticationEventHandler handler) {
handler.onAuthenticationChanged(this);
}
}
and the handler:
public interface AuthenticationEventHandler extends EventHandler {
void onAuthenticationChanged(AuthenticationEvent authenticationEvent);
}
Then you use it like this:
AppUtils.EVENT_BUS.addHandler(AuthenticationEvent.TYPE, new AuthenticationEventHandler() {
#Override
public void onAuthenticationChanged(AuthenticationEvent authenticationEvent) {
// authentication changed - do something
}
});
and fire the event:
AppUtils.EVENT_BUS.fireEvent(new AuthenticationEvent());

Categories