Monday, May 14, 2012

Wicket - Notification Dialog Component

In previous post I've given example of Info Dialog, which is being triggered trough AJAX request, shows notification message over current HTML page, bounces few times and finally disappears.
Java Code below contains the same dialog, but this time it's being re-factored to reusable component - the Notifier.

The Notifier can be included in parent page for all other pages, this parent page will have show-dialog method, which will forward calls to our Notifier panel.


Abstract parent class for all pages - MyParentPage.java

public abstract class MyParentPage extends WebPage {

  private Notifier notifier;

  public MyParentPage() {
    notifier = new Notifier("notifierDialog", new NotifierConfig());
    add(notifier);
  }

  protected void showNotification(AjaxRequestTarget target, String infoHeader,
          String infoContent) {
    notifier.showNotification(target, infoHeader, infoContent);
  }
}


MyParentPage.html

<html>
<body>
  <div wicket:id="notifierDialog" />
  <div class="xyz"></div>
  <wicket:child />
</body>
</html>


Notifier Component - Notifier.java

/**
 * Notify Dialog component - add this panel to any wicket page in order to support ajax
 * notifications.
 * 
 * Integration example:
 * 
 * 1) Add Notifier to markup in your WebPage or Panel
 *    Notifier notifier = new Notifier("notifierDialog", new NotifierConfig());
 *    add(notifier);
 * 
 * 2) Add Notifier as div in corresponding HTML page - on the top of the page
 *    <div wicket:id="notifierDialog" />
 *  
 * 3) In order to show info message call:
 *    #createNotifierMessage(WebMarkupContainer)
 * 
 * @author mmiklas
 */
public class Notifier extends Panel {

  /** Panel configuration */
  private NotifierConfig config;

  /** Parent for all display widgets in this dialog */
  private WebMarkupContainer dialogContainer = null;

  /** HTML ID of {@link #dialogContainer} */
  private String dialogContainerId = null;

  /** Dialog header model - use it to replace dialog's header message */
  Model<String> headerModel = null;

  /** Dialog content model - use it to replace dialogs content text */
  Model<String> messageModel = null;

  /**
   * Displays notification dialog with given header and message.
   * <p>
   * Dialog display command is being rendered as AJAX response
   * {@link AjaxRequestTarget#appendJavaScript(CharSequence)}. This response contains
   * dialog body, which will replace currently empty "notifier"-div, and also jquery
   * code, which plays bounce effect and hides dialog after one second.
   */
  public void showNotification(AjaxRequestTarget target, String header, String message) {
    Validate.notNull(target, "target");
    Validate.notNull(header, "header");
    Validate.notNull(message, "message");

    // update text models
    headerModel.setObject(header);
    messageModel.setObject(message);

    // replace empty #notifier with real content
    target.add(dialogContainer);
    dialogContainer.setVisible(true);

    // after setting dialog to visible play bounce effect
    JQueryEffectBehavior effectBehavior = new JQueryEffectBehavior(dialogContainerId,
            "bounce", config.getBounceTimeMilis());

    // dialog should disappear after one second - I did not
    // find any better way to add callback java script.
    effectBehavior.setCallback(new JQueryAjaxBehavior(this) {

      @Override
      public CharSequence getCallbackScript() {
        String fadeOut = "function(){$('" + dialogContainerId + ":visible').fadeOut();}";
        String callbackScript = "setTimeout(" + fadeOut + ", "
                + Long.toString(config.getNotificationDisplayTimeMilis()) + ");";
        return callbackScript;
      }

      @Override
      protected JQueryEvent newEvent(AjaxRequestTarget target) {
        return null;
      }
    });

    target.appendJavaScript(effectBehavior.toString());
  }

  public Notifier(String id, NotifierConfig config) {
    super(id);
    Validate.notNull(config, "config");
    this.config = config;

    // div containing notify dialog
    dialogContainer = createDialogContainer();
    add(dialogContainer);

    // HTML ID for java script references
    dialogContainerId = "#" + dialogContainer.getMarkupId();

    // initialize jquery
    initEffectLib(dialogContainerId);

    // labels building info dialog
    headerModel = createNotifierHeader(dialogContainer);
    messageModel = createNotifierMessage(dialogContainer);
  }

  /**
   * @return body of the info dialog. HTML ID: "notifierMessage"
   */
  private Model<String> createNotifierMessage(WebMarkupContainer dialogContainer) {
    Model<String> model = new Model<String>();
    Label notifierMessage = new Label("notifierMessage", model);
    dialogContainer.add(notifierMessage);
    return model;
  }

  /**
   * @return header of the info dialog. HTML ID: "notifierHeader"
   */
  private Model<String> createNotifierHeader(WebMarkupContainer dialogContainer) {
    Model<String> model = new Model<String>();
    Label notifierHeader = new Label("notifierHeader", model);
    dialogContainer.add(notifierHeader);
    return model;
  }

  @Override
  protected void onBeforeRender() {
    super.onBeforeRender();

    // showNotification(....) renders #notifier (div) with info dialog
    // content. Java script will hide this dialog on client side after one second
    // (callback after bounce effect).
    // Page refresh would re-render whole HTML page, this would include in this case
    // also #notifier containing recent dialog - wicket component remembers last ajax
    // update on #notifier - and this is the whole dialog.
    //
    // Setting visibility to false on #notifier ensures, that old dialog will not
    // re-appear on page refresh
    dialogContainer.setVisible(false);

  }

  /** Initializes jquery effects library */
  private void initEffectLib(String infoDialogId) {
    add(new JQueryBehavior(infoDialogId, "effect"));
  }

  /**
   * @return hidden dialog container. It must be rendered as empty div, in order to
   *         replace it with info dialog content
   */
  private WebMarkupContainer createDialogContainer() {
    WebMarkupContainer container = new WebMarkupContainer("notifier");
    container.setOutputMarkupId(true);
    container.setOutputMarkupPlaceholderTag(true);
    container.setVisible(false);
    return container;
  }
}


Notifier Config Class

public class NotifierConfig {

  private int notificationDisplayTimeMilis = 1000;

  private int bounceTimeMilis = 500;

  public int getNotificationDisplayTimeMilis() {
    return notificationDisplayTimeMilis;
  }

  public void setNotificationDisplayTimeMilis(int notificationDisplayTimeMilis) {
    if (notificationDisplayTimeMilis < 0) {
      return;
    }
    this.notificationDisplayTimeMilis = notificationDisplayTimeMilis;
  }

  public int getBounceTimeMilis() {
    return bounceTimeMilis;
  }

  public void setBounceTimeMilis(int bounceTimeMilis) {
    if (bounceTimeMilis < 0) {
      return;
    }
    this.bounceTimeMilis = bounceTimeMilis;
  }

}


Notifier.html

<!DOCTYPE html>
<html xmlns:wicket="http://wicket.apache.org">

<wicket:head>
    <wicket:link>
        <link rel="stylesheet" type="text/css" href="notifier.css" />
    </wicket:link>
</wicket:head>

<body>
    <wicket:panel>
        <div wicket:id="notifier" class="notifier-content">
            <div wicket:id="notifierHeader" class="notifier-header">Message Header</div>
            <div wicket:id="notifierMessage" class="notifier-message">Message Body Text</div>
        </div>
    </wicket:panel>
</body>
</html>


notifier.css

.notifier-content {
    font-size: 12px;
    height: 100px;
    width: 240px;
    padding: 4px;
    position: fixed;
    background-color: #EDEDED;
    border-color: #BFBFBF;
    border-width: 1px;
    border-style: solid;
    border-radius: 4px;
}

.notifier-header {
    background-color: #F5A729;
    border-color: #E78F08;
    border-style: solid;
    border-width: 1px;
    color: white;
    font-weight: bold;
    border-radius: 4px;
    margin: 0;
    padding: 4px;
    text-align: center;
}

.notifier-message {
    padding-top: 6px;
}

No comments:

Post a Comment