Help on creating a confirmation dialog

I have been trying to create a confirmation dialog using toolkit , like the Yes/No dialog from JOptionPane class.

But I have not been successful, i need help on how to do that, and have one reusable dialog that i can pass a message and get back the Yes or No results.

Thanks

Here is a quick solution. First an example on how to use it:

package com.itmill.toolkit.tests;

import com.itmill.toolkit.Application;
import com.itmill.toolkit.ui.Button;
import com.itmill.toolkit.ui.Window;
import com.itmill.toolkit.ui.Button.ClickEvent;

public class DialogExample extends Application {

    Window main = new Window("Dialog test application");

    @Override
    public void init() {
        setMainWindow(main);
        main.addComponent(new Button("test", new Button.ClickListener() {

            public void buttonClick(ClickEvent event) {
                main
                        .addWindow(new YesNoDialog(
                                "Are you happy?",
                                "Please answer yes if this dialog example is ok for your use.",
                                new YesNoDialog.Callback() {
                                    public void onDialogResult(boolean happy) {
                                        main
                                                .showNotification(happy ? "Great, we have a winner"
                                                        : "Oh... Please post details on forum");
                                    }
                                }));
            }
        }));
    }

}

And then actual reusable YesNoDialog:

package com.itmill.toolkit.tests;

import com.itmill.toolkit.ui.Button;
import com.itmill.toolkit.ui.Label;
import com.itmill.toolkit.ui.OrderedLayout;
import com.itmill.toolkit.ui.Window;
import com.itmill.toolkit.ui.Button.ClickEvent;

public class YesNoDialog extends Window implements Button.ClickListener {

    Callback callback;
    Button yes = new Button("Yes", this);
    Button no = new Button("No", this);

    public YesNoDialog(String caption, String question, Callback callback) {
        super(caption);

        setModal(true);

        this.callback = callback;

        if (question != null) {
            addComponent(new Label(question));
        }

        OrderedLayout hl = new OrderedLayout(
                OrderedLayout.ORIENTATION_HORIZONTAL);
        hl.addComponent(yes);
        hl.addComponent(no);
        addComponent(hl);
        ((OrderedLayout) getLayout()).setComponentAlignment(hl,
                OrderedLayout.ALIGNMENT_RIGHT, OrderedLayout.ALIGNMENT_BOTTOM);
    }

    public void buttonClick(ClickEvent event) {
        if (getParent() != null) {
            ((Window) getParent()).removeWindow(this);
        }
        callback.onDialogResult(event.getSource() == yes);
    }

    public interface Callback {

        public void onDialogResult(boolean resultIsYes);
    }

}

Please post a follow-up on which button did you push :)

I clicked Yes! :smiley:

and it works like a charm for me, i just used it as follows

NOTE: the YesNoDialog class is on the original post.

import userInterface.confirmMessages.YesNoDialog;

import com.itmill.toolkit.Application;
import com.itmill.toolkit.ui.Button;
import com.itmill.toolkit.ui.Window;
import com.itmill.toolkit.ui.Button.ClickEvent;

public class DialogExample extends Application {

    Window main = new Window("Dialog test application");

    @Override
    public void init() {
        setMainWindow(main);
        main.addComponent(new Button("test", new Button.ClickListener() {

            public void buttonClick(ClickEvent event) {
                main
                        .addWindow(new YesNoDialog(
                                "You want to be fired?",
                                "Please answer yes if sleep in the office.",
                                new YesNoDialog.Callback() {
                                    public void onDialogResult(boolean happy) {
                                        main
                                                .showNotification(happy ? yesDoTheMagic()
                                                        : no_it_was_a_mistake());
                                    }
                                }));
            }
        }));
    }
    public String yesDoTheMagic(){		
		//TODO put what should happen when user selects yes
    	String instructions = "Please see the HR department for all your final payments";
		return instructions;
	}
    
    public String no_it_was_a_mistake(){		
		//TODO put what should happen when user selects No
    	String warning = "Next time you play this game, there will not be a discussion";
		return warning;
	}
}

if somebody else want to make it work otherwise; share with us :slight_smile:

I would enhance this “pattern” a bit, so that the you could specify the “Yes” and “No” button texts as well, for localization purposes.

How about:


new OptionDialog(new String[] {"Yes", "No", "Maybe"}, 
      new OptionDialog.Listener() { 
            void optionDialogSelected(int optionSelected) {
// Do something
}});

[quote=Joonas Lehtinen]
How about:


new OptionDialog(new String[] {"Yes", "No", "Maybe"}, 
      new OptionDialog.Listener() { 
            void optionDialogSelected(int optionSelected) {
// Do something
}});

[/quote]Looks good. Getters and setters for the message and button texts probably wouldn’t hurt either.

So when will this small enhacement, almost ready will go into Vaadin?

I like this! Every nontrivial project ends up implementing something similar at some point.

Hi,
Those of you who can’t wait until this is part of Vaadin itself, try mcvaadin.jar:
http://code.google.com/p/mcvaadin/
.

Here is how you would create a confirmation dialog for a button click:

win.addComponent(new Button("Delete", new Button.ClickListener() {

    public void buttonClick(ClickEvent event) {
        final UserMessages um = new UserMessages(win);
        um.confirm("Really delete?", "Deletion is permanent", new McListener(){

            public void exec(McEvent e) throws Exception {
                if (e.isConfirmed()) {
                    // TODO: Implement this
                    um.notification("Here we go then!");
                } else {
                    // TODO: Implement this too
                    um.notification("Wisely cancelled.");
                }

            }});
    }
}));

Sami, thanks to pointing to mcvaadin, did not know about this project before. It looks like close to my TPT (
code.google.com/p/tpt
) by ideology. UI builder functions seems to be very interesting !

P.S. In TPT, modal dialog is something similar :slight_smile: :

final OptionDialog dlg = new OptionDialog ( TPTApplication.getCurrentApplication () );
dlg.showConfirmationDialog ( "Hello", "That's a deal. Agree ?", new OptionDialog.OptionDialogResultListener () {
  public void dialogClosed ( OptionKind closeEvent )
  {
    TPTApplication.getCurrentApplication ().getMainWindow ().showNotification ( "The deal was " + + closeEvent.name ());
  }
});

Quite true. We are reinventing the wheel :slight_smile: … I think nearly every app has somehow solved this.

So, I think there is enough data how this should be implemented and
created a uservoice item for this
(vote, vote) to get this as part of the framework itself.

Basically we need:

  • One-liner for creating a modal confirmation dialog
  • Callback w/ result value (or two callbacks)
  • Auto resize based on message length (like in mcvaadin)
  • Extension point for customizing / overriding the dialog

Forgot something?

I think, not just only confirmation dialog but for question/input dialog as well. I’d vote for mirroring the functionality of enire Swing’s JOptionDialog class (which is currently mirrored in TPT)

You are right. Mirroring as much as possible an existing API is the safest for this and Swing is something most Java programmers know already.

Side note: I don’t personally like to use the input dialog pattern so much in web applications (usability-wise), but there are many applications that still would benefit from it.

InputDialog is useful, in rare usecases, when you need a quick single answer from a user and it is too lazy to build a complete window/dialog and controls for it :slight_smile:

I tried to use mcvaadin for the confirmation dialog functionality in my GAE application. It worked great except I kept getting the following fatal serialization errors:

Is mcvaadin still a live project or is this functionality intended for the next version of vaadin? In the interim, I worked around this by refactoring the UserMessages class to get rid of all the “mc” specific stuff. The class is:

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.Serializable;

import com.vaadin.event.ShortcutAction.KeyCode;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window;
import com.vaadin.ui.Window.CloseEvent;
import com.vaadin.ui.Window.Notification;

/**
 * Helper class for generic way of different kinds of messages to user.  Taken from
 * mcvaadin project and refactored to stand-alone.
 */
public class UserMessages implements Serializable {

    private static final long serialVersionUID = 2344274165863515876L;
    static final String CONFIRM_TITLE = "Confirm";
    static final String CONFIRM_CANCEL_TITLE = "Cancel";
    static final String CONFIRM_OK_TITLE = "Ok";
    static final int MAX_DESCRIPTION_LINES = 20;
    public static final String USER_CONFIRM_OK = "OK";
    public static final String USER_CONFIRM_CANCEL = "CANCEL";
	
    private Window win;
    private Window confirm;

    public UserMessages(Window w) {
        win = w;
        confirm = null;
        if (win == null) {
            throw new IllegalStateException(
                    "User messages require a window instance");
        }
    }

    public void error(String message) {
        error(message, null, null);
    }

    public void error(String message, String description) {
        error(message, description, null);
    }

    public void error(String message, String description, Throwable t) {
        if (t != null) {
            ByteArrayOutputStream stos = new ByteArrayOutputStream();
            PrintStream sto = new PrintStream(stos, false);
            t.printStackTrace(sto);
            sto.flush();
            try {
                stos.flush();
            } catch (IOException ignored) {
            }
            String st = stos.toString();
            t.printStackTrace();
            if (description == null) {
                description = st;
            } else {
                description += ":\n" + st;
            }
        }
        description = formatDescription(description);
        win.showNotification(escapeXML(message), description,
                Notification.TYPE_ERROR_MESSAGE);
    }

    public void error(String message, Throwable t) {
        error(message, null, t);
    }

    public void error(Throwable e) {
        error("Unhandled Exception", null, e);
    }

    public void warning(String message) {
        warning(message, null);
    }

    public void warning(String message, String description) {
        win.showNotification(message, formatDescription(description),
                Notification.TYPE_WARNING_MESSAGE);
    }

    public void trayNotification(String message) {
        trayNotification(message, null);
    }

    public void trayNotification(String message, String description) {
        win.showNotification(message, formatDescription(description),
                Notification.TYPE_TRAY_NOTIFICATION);
    }

    public void notification(String message) {
        notification(message, null);
    }

    public void notification(String message, String description) {
        win.showNotification(message, formatDescription(description));
    }

    public void alert(String message) {
        alert(message, null);
    }

    public void alert(String message, String description) {
        win.showNotification(message, formatDescription(description),
                Notification.DELAY_FOREVER);
    }

    private String formatDescription(String description) {
        if (description != null) {
            description = escapeXML(description);
            description = description.replaceAll("\n", "<br />");
            if (description.length() > 80) {
                String orig = description;
                description = "";
                while (orig.length() > 0) {
                    int last = Math.min(80, orig.length());
                    description += orig.substring(0, last);
                    int lastnl = description.lastIndexOf("<br");
                    int lastwb = description.lastIndexOf(' ');
                    if (lastwb - lastnl > 10
                            && lastwb < description.length() - 1) {
                        description = description.substring(0, lastwb)
                                + "<br />" + description.substring(lastwb);
                    }
                    orig = last == orig.length() ? "" : orig.substring(last);
                }
            }

            // limit number of lines
            int pos = description.indexOf("<br");
            int lineCount = 1;
            while (lineCount < MAX_DESCRIPTION_LINES && pos > 0
                    && pos < description.length()) {
                pos = description.indexOf("<br", pos + 3);
                lineCount++;
            }
            if (pos > 0 && lineCount >= MAX_DESCRIPTION_LINES) {
                description = description.substring(0, pos) + "<br />(...)";
            }
        }
        return description;
    }

    public Window confirm(String message, ClickListener listener) {
        return confirm(CONFIRM_TITLE, message, CONFIRM_OK_TITLE,
                CONFIRM_CANCEL_TITLE, listener);
    }

    public Window confirm(String title, String message, Button.ClickListener listener) {
        return confirm(title, message, CONFIRM_OK_TITLE,
                CONFIRM_CANCEL_TITLE, listener);
    }

    public Window confirm(String title, String message, String okTitle,
            String cancelTitle, Button.ClickListener listener) {

        // Check for default captions
        if (title == null) {
            title = CONFIRM_OK_TITLE;
        }
        if (cancelTitle == null) {
            cancelTitle = CONFIRM_CANCEL_TITLE;
        }
        if (okTitle == null) {
            okTitle = CONFIRM_OK_TITLE;
        }

        // Create a confirm dialog
        final Window confirm = new Window(title);
        this.confirm = confirm;
        win.addWindow(confirm);
        confirm.setStyleName(Window.STYLE_LIGHT);

        // Close listener implementation
        confirm.addListener(new Window.CloseListener() {

            private static final long serialVersionUID = 1971800928047045825L;

            public void windowClose(CloseEvent ce) {
                Object data = ce.getWindow().getData();
                if (data != null) {
                    try {
                        //listener.exec(new McEvent(ce));
                    } catch (Exception exception) {
                        error("Unhandled Exception", exception);
                    }
                }
            }
        });

        // Approximate the size of the dialog
        int chrW = 5;
        int chrH = 15;
        int txtWidth = Math.max(250, Math.min(350, message.length() * chrW));
        int btnHeight = 25;
        int vmargin = 100;
        int hmargin = 40;

        int txtHeight = 2 * chrH * (message.length() * chrW) / txtWidth;

        confirm.setWidth((txtWidth + hmargin) + "px");
        confirm.setHeight((vmargin + txtHeight + btnHeight) + "px");
        confirm.getContent().setSizeFull();

        // Modal position in the center
        confirm.center();
        confirm.setModal(true);

        // Create content
        Label text = new Label(message);
        text.setWidth("100%");
        text.setHeight("100%");
        //h.expand(text, 1f);

        HorizontalLayout buttons = new HorizontalLayout();
        buttons.setHeight(btnHeight + "px");
        buttons.setWidth("100%");
        Label spacer = new Label("");
        spacer.setWidth("100%");

        Button cancel = new Button(cancelTitle, listener);
        cancel.setData(USER_CONFIRM_CANCEL);
        cancel.setClickShortcut(KeyCode.ESCAPE);
        Button ok = new Button(okTitle, listener);
        ok.setData(USER_CONFIRM_OK);
        ok.setClickShortcut(KeyCode.ENTER);
        buttons.addComponent(ok);
        buttons.addComponent(cancel);
        
        confirm.addComponent(text);
        confirm.addComponent(buttons);
        ((VerticalLayout)confirm.getContent()).setExpandRatio(text,1f);
        confirm.setResizable(false);
        
        return confirm;
    }
    
    public void removeConfirm() {
    	if (this.confirm != null) {
    		win.getApplication().getMainWindow().removeWindow(confirm);
    	}
    }

    private String escapeXML(String str) {
        return str == null ? null
                : com.vaadin.terminal.gwt.server.JsonPaintTarget.escapeXML(str);
    }
}

It is then used as follows:

						final UserMessages um = new UserMessages(this
								.getApplication().getMainWindow());
						um.confirm("Really edit another row?",
								"Your changes will be lost", "Edit New Row",
								"Keep Editing", new Button.ClickListener() {

									private static final long serialVersionUID = 2376740681091516971L;

									@Override
									public void buttonClick(ClickEvent e) {
										// TODO Auto-generated method stub
										if (e.getButton().getData().equals(UserMessages.USER_CONFIRM_OK)) {
											// do the relevant action
										} else {
											// do the relevant action, probably nothing
										}
										um.removeConfirm();
									}
								});

Looked this very quickly, and the suspicious thing is this:

So there is some import to MouseEventDetails from GWT, even I think it is not needed.
Could not find it
in mcvaadin classes so maybe it is in your imports?

Btw: If you are only using confirmation dialog from mcvaadin, maybe you take a look at the
ConfirmDialog add-on
.

The only jars in the project are listed below. Anyway, all I needed was the confirm dialog so I will switch to that.

Thanks,

Jonathan
11544.gif
11545.gif

I’ve been trying to figure this out I’m left with the following problem. I have a mcvaadin UserMessages confirm window pop-up when a right-click Action is handled by handleAction. This works fine with no problems. However, when I use the same confirm window in response to a double-click action in an ItemClickListener, I get the NotSerializableException which points to mcvaadin.


Fatal error in serialization of the application: Class com.googlecode.mcvaadin.helpers.UserMessages$1 must implement serialization.
Fatal error in serialization of the application: Class com.googlecode.mcvaadin.helpers.UserMessages$2 must implement serialization.
Dec 22, 2010 6:11:32 PM com.vaadin.terminal.gwt.server.GAEApplicationServlet service
SEVERE: NotSerializableException: java.io.NotSerializableException: com.vaadin.terminal.gwt.client.MouseEventDetails

The error happens just before the confirm window comes up. In the stack trace, there is a mention of a HashSet and Hashmap, and my class does have a Hashmap which I think I’ve fully nailed down from a serialization perspective (everything in the class is final including the Hashmap, and I don’t believe anything going into the Hashmap has any non-serializable parts). Further, the hashmap is similarly used when the mcvaadin window is called in response to the right-click action.

So I’m at a loss. Any thoughts would be welcomed. I updated my GAEDataStoreContainers add-on on the add-ons page with this bug still in there.

Thanks,
Jonathan

For MouseEventDetails, this is a bug in Vaadin that causes problems on GAE - I created the ticket
#6199
for it.

For the other two non-serializable classes (as well as possibly others), they should be fixed in mcvaadin by implementing Serializable if they are to be used on GAE.