ISIS-349, ISIS-350: jgrowl integration, error page for exceptions

implemented.


Project: http://git-wip-us.apache.org/repos/asf/isis/repo
Commit: http://git-wip-us.apache.org/repos/asf/isis/commit/345f22fb
Tree: http://git-wip-us.apache.org/repos/asf/isis/tree/345f22fb
Diff: http://git-wip-us.apache.org/repos/asf/isis/diff/345f22fb

Branch: refs/heads/dan/ISIS-233-ro
Commit: 345f22fbfb56de82b41721fbb49d4fc0fcdc7f23
Parents: 4ad06ff
Author: Dan Haywood <[email protected]>
Authored: Thu Feb 21 19:30:10 2013 +0000
Committer: Dan Haywood <[email protected]>
Committed: Thu Feb 21 19:30:10 2013 +0000

----------------------------------------------------------------------
 .../integration/wicket/WebRequestCycleForIsis.java |   38 +-
 .../viewer/wicket/model/models/ActionModel.java    |   29 ++-
 .../ui/components/widgets/cssmenu/CssMenuItem.java |    7 +-
 .../viewer/wicket/ui/feedback/JGrowlBehavior.java  |  101 ++++
 .../isis/viewer/wicket/ui/pages/PageAbstract.css   |   37 ++
 .../isis/viewer/wicket/ui/pages/PageAbstract.html  |   20 +-
 .../isis/viewer/wicket/ui/pages/PageAbstract.java  |   38 ++-
 .../viewer/wicket/ui/pages/action/ActionPage.java  |    1 +
 .../viewer/wicket/ui/pages/error/ErrorPage.css     |   81 ++++
 .../viewer/wicket/ui/pages/error/ErrorPage.html    |   60 +++
 .../viewer/wicket/ui/pages/error/ErrorPage.java    |   71 +++
 .../isis/viewer/wicket/ui/pages/jquery.jgrowl.css  |  136 ++++++
 .../isis/viewer/wicket/ui/pages/jquery.jgrowl.js   |  352 +++++++++++++++
 13 files changed, 934 insertions(+), 37 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/345f22fb/component/viewer/wicket/impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/WebRequestCycleForIsis.java
----------------------------------------------------------------------
diff --git 
a/component/viewer/wicket/impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/WebRequestCycleForIsis.java
 
b/component/viewer/wicket/impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/WebRequestCycleForIsis.java
index a135449..debf3ff 100644
--- 
a/component/viewer/wicket/impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/WebRequestCycleForIsis.java
+++ 
b/component/viewer/wicket/impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/WebRequestCycleForIsis.java
@@ -19,16 +19,19 @@
 
 package org.apache.isis.viewer.wicket.viewer.integration.wicket;
 
-import org.apache.log4j.Logger;
-import org.apache.wicket.protocol.http.WebSession;
-import org.apache.wicket.request.cycle.AbstractRequestCycleListener;
-import org.apache.wicket.request.cycle.RequestCycle;
-
 import org.apache.isis.core.commons.authentication.AuthenticationSession;
 import org.apache.isis.core.runtime.system.context.IsisContext;
 import org.apache.isis.core.runtime.system.session.IsisSession;
 import org.apache.isis.core.runtime.system.transaction.IsisTransaction;
 import org.apache.isis.core.runtime.system.transaction.IsisTransactionManager;
+import org.apache.isis.viewer.wicket.ui.pages.error.ErrorPage;
+import org.apache.log4j.Logger;
+import org.apache.wicket.core.request.handler.PageProvider;
+import org.apache.wicket.core.request.handler.RenderPageRequestHandler;
+import org.apache.wicket.protocol.http.WebSession;
+import org.apache.wicket.request.IRequestHandler;
+import org.apache.wicket.request.cycle.AbstractRequestCycleListener;
+import org.apache.wicket.request.cycle.RequestCycle;
 
 /**
  * Isis-specific implementation of the Wicket's {@link WebRequestCycle},
@@ -39,18 +42,6 @@ public class WebRequestCycleForIsis /*extends 
WebRequestCycle*/ extends Abstract
 
     private static final Logger LOG = 
Logger.getLogger(WebRequestCycleForIsis.class);
 
-//    public WebRequestCycleForIsis(final WebApplication application, final 
WebRequest request, final Response response) {
-//        super(application, request, response);
-//    }
-//
-//    /**
-//     * Convenience, downcasts.
-//     */
-//    @Override
-//    public AuthenticatedWebSessionForIsis getWebSession() {
-//        return (AuthenticatedWebSessionForIsis) super.getWebSession();
-//    }
-
       private AuthenticatedWebSessionForIsis getWebSession() {
           return (AuthenticatedWebSessionForIsis) WebSession.get();
       }
@@ -82,7 +73,6 @@ public class WebRequestCycleForIsis /*extends 
WebRequestCycle*/ extends Abstract
             commitTransactionIfAny();
             getIsisContext().closeSessionInstance();
         }
-        //super.onEndRequest();
     }
 
     private void commitTransactionIfAny() {
@@ -96,6 +86,11 @@ public class WebRequestCycleForIsis /*extends 
WebRequestCycle*/ extends Abstract
         }
     }
 
+    @Override
+    public IRequestHandler onException(RequestCycle cycle, Exception ex) {
+        return new RenderPageRequestHandler(new PageProvider(new 
ErrorPage(ex)));
+    }
+    
     /**
      * Factored out so can be overridden in testing.
      */
@@ -103,13 +98,6 @@ public class WebRequestCycleForIsis /*extends 
WebRequestCycle*/ extends Abstract
         return IsisContext.getInstance();
     }
 
-//    /**
-//     * Simply downcasts superclass' implementation, for convenience of 
callers.
-//     */
-//    @Override
-//    protected WebClientInfo newClientInfo() {
-//        return (WebClientInfo) super.newClientInfo();
-//    }
 
     protected IsisTransactionManager getTransactionManager() {
         return IsisContext.getTransactionManager();

http://git-wip-us.apache.org/repos/asf/isis/blob/345f22fb/component/viewer/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/ActionModel.java
----------------------------------------------------------------------
diff --git 
a/component/viewer/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/ActionModel.java
 
b/component/viewer/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/ActionModel.java
index 0fbe844..aed3834 100644
--- 
a/component/viewer/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/ActionModel.java
+++ 
b/component/viewer/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/ActionModel.java
@@ -19,18 +19,24 @@
 
 package org.apache.isis.viewer.wicket.model.models;
 
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import com.google.common.base.Predicate;
+import com.google.common.base.Throwables;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
 
 import org.apache.wicket.Component;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
 
+import org.apache.isis.applib.ApplicationException;
 import org.apache.isis.applib.Identifier;
+import org.apache.isis.core.commons.exceptions.IsisApplicationException;
 import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
 import 
org.apache.isis.core.metamodel.adapter.mgr.AdapterManager.ConcurrencyChecking;
 import org.apache.isis.core.metamodel.adapter.oid.OidMarshaller;
@@ -433,12 +439,31 @@ public class ActionModel extends 
BookmarkableModel<ObjectAdapter> {
         return results;
     }
 
+    
+    // TODO: hacky!!
+    public static ThreadLocal<String> applicationError = new 
ThreadLocal<String>();
+    
     private ObjectAdapter executeAction() {
         final ObjectAdapter targetAdapter = getTargetAdapter();
         final ObjectAdapter[] arguments = getArgumentsAsArray();
         final ObjectAction action = getActionMemento().getAction();
-        final ObjectAdapter results = action.execute(targetAdapter, arguments);
-        return results;
+        try {
+            final ObjectAdapter results = action.execute(targetAdapter, 
arguments);
+            return results;
+        } catch(RuntimeException ex) {
+            final ApplicationException appEx = 
getApplicationExceptionIfAny(ex);
+            if(appEx != null) {
+                applicationError.set(appEx.getMessage());
+                return null;
+            }
+            throw ex;
+        }
+    }
+
+    private ApplicationException getApplicationExceptionIfAny(Exception ex) {
+        Iterable<ApplicationException> appEx = 
Iterables.filter(Throwables.getCausalChain(ex), ApplicationException.class);
+        Iterator<ApplicationException> iterator = appEx.iterator();
+        return iterator.hasNext() ? iterator.next() : null;
     }
 
     public String getReasonInvalidIfAny() {

http://git-wip-us.apache.org/repos/asf/isis/blob/345f22fb/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuItem.java
----------------------------------------------------------------------
diff --git 
a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuItem.java
 
b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuItem.java
index 541c5f9..3a1e3f0 100644
--- 
a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuItem.java
+++ 
b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuItem.java
@@ -48,6 +48,7 @@ import 
org.apache.isis.core.metamodel.spec.feature.ObjectAction;
 import org.apache.isis.core.progmodel.facets.actions.bulk.BulkFacet;
 import org.apache.isis.viewer.wicket.model.links.LinkAndLabel;
 import org.apache.isis.viewer.wicket.model.mementos.ObjectAdapterMemento;
+import org.apache.isis.viewer.wicket.ui.feedback.JGrowlBehavior;
 import org.apache.isis.viewer.wicket.ui.pages.PageAbstract;
 import org.apache.isis.viewer.wicket.ui.util.Components;
 import org.apache.isis.viewer.wicket.ui.util.CssClassAppender;
@@ -229,6 +230,8 @@ public class CssMenuItem implements Serializable {
 
         final LinkAndLabel linkAndLabel = cssMenuLinkFactory.newLink(null, 
objectAction, PageAbstract.ID_MENU_LINK);
 
+        linkAndLabel.getLink().add(new JGrowlBehavior());
+
         final AbstractLink link = linkAndLabel.getLink();
         final String actionLabel = linkAndLabel.getLabel();
 
@@ -262,8 +265,8 @@ public class CssMenuItem implements Serializable {
             // hide link...
             Components.permanentlyHide(markupContainer, ID_MENU_LINK);
             // ... and show label, along with disabled reason
-            label.add(new AttributeModifier("title", true, 
Model.of(this.getDisabledReason())));
-            label.add(new AttributeModifier("class", true, 
Model.of("disabled")));
+            label.add(new AttributeModifier("title", 
Model.of(this.getDisabledReason())));
+            label.add(new AttributeModifier("class", Model.of("disabled")));
             markupContainer.add(label);
 
             return label;

http://git-wip-us.apache.org/repos/asf/isis/blob/345f22fb/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/feedback/JGrowlBehavior.java
----------------------------------------------------------------------
diff --git 
a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/feedback/JGrowlBehavior.java
 
b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/feedback/JGrowlBehavior.java
new file mode 100644
index 0000000..24fdb98
--- /dev/null
+++ 
b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/feedback/JGrowlBehavior.java
@@ -0,0 +1,101 @@
+package org.apache.isis.viewer.wicket.ui.feedback;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.isis.core.runtime.system.context.IsisContext;
+import org.apache.isis.viewer.wicket.model.models.ActionModel;
+import org.apache.wicket.Component;
+import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.feedback.FeedbackMessage;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
+
+/**
+ * Attach to any component to display jGrowl messages.
+ * 
+ * Displays only session-level messages. If you need component-level messages,
+ * see http://pastebin.com/f6db2ec0e for an example. Basically, instead of
+ * Session.get().getFeedbackMessages(), you would call
+ * getComponent().getFeedbackMessage().
+ * 
+ * Requires the following be included: "jquery.js", "jquery.ui.all.js",
+ * "jquery.jgrowl.js", "jquery.jgrowl.css". These can be downloaded from
+ * http://plugins.jquery.com/files/jGrowl-1.2.0.tgz.
+ * 
+ * @author jsinai Based on an example by Alex Objelean, see the above link.
+ */
+public class JGrowlBehavior extends AbstractDefaultAjaxBehavior {
+    
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Displays an info message that is sticky. The default is non-sticky.
+     * Sample usage: session.getFeedbackMessages().add(new 
FeedbackMessage(null,
+     * "my message", JGrowlBehavior.INFO_STICKY));
+     */
+    public static final int INFO_STICKY = 250;
+
+    @Override
+    protected void respond(AjaxRequestTarget target) {
+        final String feedbackMsg = renderFeedback();
+        if (!StringUtils.isEmpty(feedbackMsg)) {
+            target.appendJavaScript(feedbackMsg);
+        }
+    }
+
+    @Override
+    public void renderHead(Component component, IHeaderResponse response) {
+        super.renderHead(component, response);
+        final String feedbackMsg = renderFeedback();
+        if (!StringUtils.isEmpty(feedbackMsg)) {
+            response.render(OnDomReadyHeaderItem.forScript(feedbackMsg));
+        }
+    }
+
+    private String renderFeedback() {
+
+        final StringBuilder buf = new StringBuilder();
+        
+        for (String info : IsisContext.getMessageBroker().getMessages()) {
+            addJGrowlCall(info, "INFO", false, buf);
+        }
+
+        for (String warning : IsisContext.getMessageBroker().getWarnings()) {
+            addJGrowlCall(warning, "WARNING", true, buf);
+        }
+        
+        try {
+            final String error = ActionModel.applicationError.get();
+            if(error!=null) {
+                    addJGrowlCall(error, "ERROR", true, buf);
+            }
+        } finally {
+            ActionModel.applicationError.remove();
+        }
+
+        return buf.toString();
+    }
+
+    void addJGrowlCall(final String msg, final String cssClassSuffix, boolean 
sticky, final StringBuilder buf) {
+        buf.append("$.jGrowl(\"").append(msg).append('\"');
+        buf.append(", {");
+        buf.append("theme: \'jgrowl-").append(cssClassSuffix).append("\'");
+        if (sticky) {
+            buf.append(", sticky: true");
+        }
+        buf.append("}");
+        buf.append(");");
+    }
+
+    boolean isSticky(final FeedbackMessage message) {
+        return message.getLevel() > FeedbackMessage.INFO;
+    }
+
+    String messageFor(final FeedbackMessage message) {
+        return (message.getMessage() == null) ? StringUtils.EMPTY : 
message.getMessage().toString();
+    }
+
+    String levelFor(final FeedbackMessage message) {
+        return (message.getLevel() == INFO_STICKY) ? "INFO" : 
message.getLevelAsString();
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/345f22fb/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.css
----------------------------------------------------------------------
diff --git 
a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.css
 
b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.css
index e24076b..1cdadb5 100644
--- 
a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.css
+++ 
b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.css
@@ -689,3 +689,40 @@ div.actionPanelHeaderNew .actions {
 
 
 
+div#jGrowl {
+ margin-top: 55px;
+ margin-right: 25px;
+ color: white;
+ font-size: larger;
+ opacity: .90;
+ filter: alpha(opacity = 90);
+}
+
+div#jGrowl div.jgrowl-ERROR {
+ background-color: #BF0B0B;
+}
+div#jGrowl div.jgrowl-WARNING {
+ background-color: orange;
+}
+div#jGrowl div.jgrowl-INFO {
+ background-color: #20B5C2;
+}
+div#jGrowl div.jgrowl-WARNING {
+ background-color: orange;
+}
+div#jGrowl div.jGrowl-closer {
+ background-color: #F0EFEA;
+ color: #46423C;
+ font-size: small;
+}
+
+/*
+colors
+#413D37 - dark banner
+#46423C - dark (writing?)
+#E4E4DB - page background
+#20B5C2 - accent
+#BF0B0B - jgrowl error
+#00477F - dark blue
+#F0EFEA - bookmark background
+*/

http://git-wip-us.apache.org/repos/asf/isis/blob/345f22fb/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.html
----------------------------------------------------------------------
diff --git 
a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.html
 
b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.html
index d3da481..f5bbc65 100644
--- 
a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.html
+++ 
b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.html
@@ -22,13 +22,14 @@
       
xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.4-strict.dtd";  
       xml:lang="en"  
       lang="en">
-       <head>
+       <wicket:head>
+        <wicket:link>
+               <link href="cssreset.css" rel="stylesheet" type="text/css"/>
+               <link href="PageAbstract.css" rel="stylesheet" type="text/css"/>
+            <link href="jquery.jgrowl.css" rel="stylesheet" type="text/css"/>
+        </wicket:link>
                <title wicket:id="pageTitle"></title>
-               <wicket:link>
-                       <link href="cssreset.css" rel="stylesheet" 
type="text/css"/>
-                       <link href="PageAbstract.css" rel="stylesheet" 
type="text/css"/>
-               </wicket:link>
-       </head>
+       </wicket:head>
        <body>
                
                <div id="container" class="page">
@@ -60,7 +61,12 @@
 
                                <div class="clear"/>
                        </div>
-
+            
+            <form wicket:id="form">
+            <input type="submit" value="Normal OK"/>
+            <input type="submit" value="Ajax OK" wicket:id="ajaxbutton"/>
+            </form>
+            
                        <div id="footer">
                                <div class="links">
                                        powered by: <a 
href="http://isis.apache.org";>Apache Isis</a>

http://git-wip-us.apache.org/repos/asf/isis/blob/345f22fb/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.java
----------------------------------------------------------------------
diff --git 
a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.java
 
b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.java
index d4d59da..5c283a3 100644
--- 
a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.java
+++ 
b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.java
@@ -36,20 +36,26 @@ import org.apache.isis.viewer.wicket.ui.ComponentFactory;
 import org.apache.isis.viewer.wicket.ui.ComponentType;
 import org.apache.isis.viewer.wicket.ui.app.registry.ComponentFactoryRegistry;
 import 
org.apache.isis.viewer.wicket.ui.app.registry.ComponentFactoryRegistryAccessor;
+import org.apache.isis.viewer.wicket.ui.feedback.JGrowlBehavior;
 import org.apache.isis.viewer.wicket.ui.pages.about.AboutPage;
 import org.apache.isis.viewer.wicket.ui.pages.login.WicketSignInPage;
-
 import org.apache.log4j.Logger;
 import org.apache.wicket.RestartResponseAtInterceptPageException;
+import org.apache.wicket.Session;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.form.AjaxButton;
+import org.apache.wicket.feedback.FeedbackMessage;
 import org.apache.wicket.markup.head.CssReferenceHeaderItem;
 import org.apache.wicket.markup.head.IHeaderResponse;
 import org.apache.wicket.markup.head.JavaScriptReferenceHeaderItem;
 import org.apache.wicket.markup.html.WebPage;
 import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.Form;
 import org.apache.wicket.markup.html.link.ExternalLink;
 import org.apache.wicket.markup.html.link.Link;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.request.resource.JavaScriptResourceReference;
 
 import com.google.inject.Inject;
 import com.google.inject.name.Named;
@@ -99,6 +105,7 @@ public abstract class PageAbstract extends WebPage {
     @Named("applicationJs")
     private String applicationJs;
 
+    
     public PageAbstract(final PageParameters pageParameters, final 
ComponentType... childComponentIds) {
         try {
             addApplicationActionsComponent();
@@ -106,6 +113,7 @@ public abstract class PageAbstract extends WebPage {
             this.pageParameters = pageParameters;
             addHomePageLinkAndApplicationName();
             addUserName();
+            addNotificationPanel();
             addLogoutLink();
             addAboutLink();
             add(new Label(ID_PAGE_TITLE, 
PageParameterNames.PAGE_TITLE.getStringFrom(pageParameters, applicationName)));
@@ -118,9 +126,12 @@ public abstract class PageAbstract extends WebPage {
         }
     }
 
+    private static final JavaScriptResourceReference JQUERY_JGROWL_JS = new 
JavaScriptResourceReference(PageAbstract.class, "jquery.jgrowl.js");
+    
     @Override
     public void renderHead(IHeaderResponse response) {
         super.renderHead(response);
+        
response.render(JavaScriptReferenceHeaderItem.forReference(JQUERY_JGROWL_JS));
         if(applicationCss != null) {
             response.render(CssReferenceHeaderItem.forUrl(applicationCss));
         }
@@ -164,6 +175,31 @@ public abstract class PageAbstract extends WebPage {
         });
     }
 
+    private void addNotificationPanel() {
+        Form<?> form = new Form("form") {
+
+            @Override
+            protected void onSubmit() {
+                Session.get().error("Test error");
+                Session.get().warn("Test warning");
+                Session.get().info("Test info");
+                Session.get().getFeedbackMessages().add(new 
FeedbackMessage(null, "Test sticky info", JGrowlBehavior.INFO_STICKY));
+            }
+        };
+        add(form);
+
+        AjaxButton b = new AjaxButton("ajaxbutton", form) {
+
+            @Override
+            protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
+                target.add(form);
+            }
+        };
+        form.add(b);
+        form.add(new JGrowlBehavior());
+    
+    }
+
 
     /**
      * As provided in the {@link #PageAbstract(ComponentType) constructor}.

http://git-wip-us.apache.org/repos/asf/isis/blob/345f22fb/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/action/ActionPage.java
----------------------------------------------------------------------
diff --git 
a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/action/ActionPage.java
 
b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/action/ActionPage.java
index 8aaddd0..bfbb8fe 100644
--- 
a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/action/ActionPage.java
+++ 
b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/action/ActionPage.java
@@ -27,6 +27,7 @@ import org.apache.isis.applib.annotation.ActionSemantics;
 import org.apache.isis.viewer.wicket.model.models.ActionModel;
 import org.apache.isis.viewer.wicket.model.models.ActionModel.Mode;
 import org.apache.isis.viewer.wicket.ui.ComponentType;
+import org.apache.isis.viewer.wicket.ui.feedback.JGrowlBehavior;
 import org.apache.isis.viewer.wicket.ui.pages.PageAbstract;
 
 /**

http://git-wip-us.apache.org/repos/asf/isis/blob/345f22fb/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/error/ErrorPage.css
----------------------------------------------------------------------
diff --git 
a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/error/ErrorPage.css
 
b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/error/ErrorPage.css
new file mode 100644
index 0000000..1685bf1
--- /dev/null
+++ 
b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/error/ErrorPage.css
@@ -0,0 +1,81 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+
+.errorPage {
+    margin-left: 50px;
+    margin-right: 50px;
+    margin-top: 50px;
+}
+
+.errorPage #message {
+    background:#FFFFFF;
+    border-radius:4px;
+    -moz-border-radius:4px;
+    -webkit-border-radius:4px;
+    padding: 15px;
+    display: block;
+    text-align:center;
+    font-size:1.2em;
+}
+
+ .errorPage .errorDetail {
+    margin-top: 30px; 
+}
+
+.errorPage .heading {
+    border-radius:4px;
+    -moz-border-radius:4px;
+    -webkit-border-radius:4px;
+    background-color:#F0EFEA;
+
+    display:block;
+    font-style:normal !important;
+
+    padding:1px 6px 1px 6px;
+}
+
+.errorPage .heading span {
+    display:block;
+    font-style:normal !important;
+    padding:3px 3px 3px 3px;
+    text-transform: uppercase;
+    font-size: 0.8em;
+    text-transform:uppercase;
+    font-weight:bold;
+}
+
+.errorPage .heading:hover {
+    background-color:#FFFFFF;
+    cursor: pointer;
+}
+
+.errorPage h3 {
+    font-size: larger;
+}
+
+.errorPage .exceptionMessage {
+    margin-top: 30px; 
+}
+
+ .errorPage .exceptionStackTrace {
+    margin-top: 30px; 
+}
+
+

http://git-wip-us.apache.org/repos/asf/isis/blob/345f22fb/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/error/ErrorPage.html
----------------------------------------------------------------------
diff --git 
a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/error/ErrorPage.html
 
b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/error/ErrorPage.html
new file mode 100644
index 0000000..76adce9
--- /dev/null
+++ 
b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/error/ErrorPage.html
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+  
+         http://www.apache.org/licenses/LICENSE-2.0
+         
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml";  
+      
xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.4-strict.dtd";  
+      xml:lang="en"  
+      lang="en">
+       <wicket:head>
+               <wicket:link>
+                       <link href="ErrorPage.css" rel="stylesheet" 
type="text/css"/>
+               </wicket:link>
+<script type="text/javascript">
+    jQuery(document).ready(function() {
+      jQuery(".errorPage .content").hide();
+      //toggle the componenet with class msg_body
+      jQuery(".errorPage .heading").click(function()
+      {
+        jQuery(this).next(".errorPage .content").slideToggle(500);
+      });
+    });
+</script>
+       </wicket:head>
+       <body>
+               <wicket:extend>
+                       <div class="errorPage">
+                <span id="message">Sorry, an unexpected error occurred.</span>
+                <div class="errorDetail">
+                    <div class="heading"><span>Show detail</span></div>
+                    <div class="content">
+                        <div class="exceptionMessage">
+                            <h3>Message:</h3>
+                            <p wicket:id="message">Message goes here</p>
+                        </div>
+                        <div class="exceptionStackTrace">
+                            <h3>Stack trace:</h3>
+                            <p wicket:id="stackTrace">Stacktrace goes here</p>
+                        </div>
+                    </div>
+                </div>
+                       </div>
+               </wicket:extend>
+       </body>
+</html>

http://git-wip-us.apache.org/repos/asf/isis/blob/345f22fb/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/error/ErrorPage.java
----------------------------------------------------------------------
diff --git 
a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/error/ErrorPage.java
 
b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/error/ErrorPage.java
new file mode 100644
index 0000000..3147f8d
--- /dev/null
+++ 
b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/error/ErrorPage.java
@@ -0,0 +1,71 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.wicket.ui.pages.error;
+
+import org.apache.isis.viewer.wicket.ui.pages.PageAbstract;
+import 
org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeInstantiation;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+
+import com.google.common.base.Throwables;
+
+/**
+ * Web page representing the home page (showing a welcome message).
+ */
+@AuthorizeInstantiation("org.apache.isis.viewer.wicket.roles.USER")
+public class ErrorPage extends PageAbstract {
+
+    private static final long serialVersionUID = 1L;
+
+    private static final String ID_MESSAGE = "message";
+    private static final String ID_STACK_TRACE = "stackTrace";
+
+    public ErrorPage(Exception ex) {
+        super(new PageParameters());
+        add(new Label(ID_MESSAGE, ex.getMessage()));
+        add(new Label(ID_STACK_TRACE, stackTraceAsString(ex)));
+    }
+
+    private static String stackTraceAsString(Throwable ex) {
+        StringBuilder buf = new StringBuilder();
+        appendStackTrace(ex, buf);
+        Throwable cause = ex.getCause();
+        while(cause != null) {
+            buf.append("\n\nCaused by:\n");
+            appendStackTrace(cause, buf);
+            cause = cause.getCause();
+        }
+        return buf.toString();
+    }
+
+    private static void appendStackTrace(Throwable ex, StringBuilder buf) {
+        for (StackTraceElement el : ex.getStackTrace()) {
+            buf. append(el.getClassName())
+                .append(el.getMethodName())
+                .append("(")
+                .append(el.getFileName())
+                .append(":")
+                .append(el.getLineNumber())
+                .append(")\n")
+                ;
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/345f22fb/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/jquery.jgrowl.css
----------------------------------------------------------------------
diff --git 
a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/jquery.jgrowl.css
 
b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/jquery.jgrowl.css
new file mode 100644
index 0000000..dbfde23
--- /dev/null
+++ 
b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/jquery.jgrowl.css
@@ -0,0 +1,136 @@
+
+div.jGrowl {
+       z-index:                        9999;
+       color:                          #fff;
+       font-size:                      12px;
+}
+
+/** Special IE6 Style Positioning **/
+div.ie6 {
+       position:                       absolute;
+}
+
+div.ie6.top-right {
+       right:                          auto;
+       bottom:                         auto;
+       left:                           expression( ( 0 - jGrowl.offsetWidth + 
( document.documentElement.clientWidth ? document.documentElement.clientWidth : 
document.body.clientWidth ) + ( ignoreMe2 = document.documentElement.scrollLeft 
? document.documentElement.scrollLeft : document.body.scrollLeft ) ) + 'px' );
+       top:                            expression( ( 0 + ( ignoreMe = 
document.documentElement.scrollTop ? document.documentElement.scrollTop : 
document.body.scrollTop ) ) + 'px' );
+}
+
+div.ie6.top-left {
+       left:                           expression( ( 0 + ( ignoreMe2 = 
document.documentElement.scrollLeft ? document.documentElement.scrollLeft : 
document.body.scrollLeft ) ) + 'px' );
+       top:                            expression( ( 0 + ( ignoreMe = 
document.documentElement.scrollTop ? document.documentElement.scrollTop : 
document.body.scrollTop ) ) + 'px' );
+}
+
+div.ie6.bottom-right {
+       left:                           expression( ( 0 - jGrowl.offsetWidth + 
( document.documentElement.clientWidth ? document.documentElement.clientWidth : 
document.body.clientWidth ) + ( ignoreMe2 = document.documentElement.scrollLeft 
? document.documentElement.scrollLeft : document.body.scrollLeft ) ) + 'px' );
+       top:                            expression( ( 0 - jGrowl.offsetHeight + 
( document.documentElement.clientHeight ? document.documentElement.clientHeight 
: document.body.clientHeight ) + ( ignoreMe = 
document.documentElement.scrollTop ? document.documentElement.scrollTop : 
document.body.scrollTop ) ) + 'px' );
+}
+
+div.ie6.bottom-left {
+       left:                           expression( ( 0 + ( ignoreMe2 = 
document.documentElement.scrollLeft ? document.documentElement.scrollLeft : 
document.body.scrollLeft ) ) + 'px' );
+       top:                            expression( ( 0 - jGrowl.offsetHeight + 
( document.documentElement.clientHeight ? document.documentElement.clientHeight 
: document.body.clientHeight ) + ( ignoreMe = 
document.documentElement.scrollTop ? document.documentElement.scrollTop : 
document.body.scrollTop ) ) + 'px' );
+}
+
+div.ie6.center {
+       left:                           expression( ( 0 + ( ignoreMe2 = 
document.documentElement.scrollLeft ? document.documentElement.scrollLeft : 
document.body.scrollLeft ) ) + 'px' );
+       top:                            expression( ( 0 + ( ignoreMe = 
document.documentElement.scrollTop ? document.documentElement.scrollTop : 
document.body.scrollTop ) ) + 'px' );
+       width:                          100%;
+}
+
+/** Normal Style Positions **/
+div.jGrowl {
+       position:                       absolute;
+}
+
+body > div.jGrowl {
+       position:                       fixed;
+}
+
+div.jGrowl.top-left {
+       left:                           0px;
+       top:                            0px;
+}
+
+div.jGrowl.top-right {
+       right:                          0px;
+       top:                            0px;
+}
+
+div.jGrowl.bottom-left {
+       left:                           0px;
+       bottom:                         0px;
+}
+
+div.jGrowl.bottom-right {
+       right:                          0px;
+       bottom:                         0px;
+}
+
+div.jGrowl.center {
+       top:                            0px;
+       width:                          50%;
+       left:                           25%;
+}
+
+/** Cross Browser Styling **/
+div.center div.jGrowl-notification, div.center div.jGrowl-closer {
+       margin-left:            auto;
+       margin-right:           auto;
+}
+
+div.jGrowl div.jGrowl-notification, div.jGrowl div.jGrowl-closer {
+       background-color:               #000;
+       opacity:                                .85;
+       -ms-filter:                     
"progid:DXImageTransform.Microsoft.Alpha(Opacity=85)"; 
+       filter:                                 
progid:DXImageTransform.Microsoft.Alpha(Opacity=85); 
+       zoom:                                   1;
+       width:                                  235px;
+       padding:                                10px;
+       margin-top:                     5px;
+       margin-bottom:                  5px;
+       font-family:                    Tahoma, Arial, Helvetica, sans-serif;
+       font-size:                              1em;
+       text-align:                     left;
+       display:                                none;
+       -moz-border-radius:     5px;
+       -webkit-border-radius:  5px;
+}
+
+div.jGrowl div.jGrowl-notification {
+       min-height:                     40px;
+}
+
+div.jGrowl div.jGrowl-notification,
+div.jGrowl div.jGrowl-closer {
+       margin:                                 10px;
+}
+
+div.jGrowl div.jGrowl-notification div.jGrowl-header {
+       font-weight:                    bold;
+       font-size:                              .85em;
+}
+
+div.jGrowl div.jGrowl-notification div.jGrowl-close {
+       z-index:                                99;
+       float:                                  right;
+       font-weight:                    bold;
+       font-size:                              1em;
+       cursor:                                 pointer;
+}
+
+div.jGrowl div.jGrowl-closer {
+       padding-top:                    4px;
+       padding-bottom:                 4px;
+       cursor:                                 pointer;
+       font-size:                              .9em;
+       font-weight:                    bold;
+       text-align:                     center;
+}
+
+/** Hide jGrowl when printing **/
+@media print {
+       div.jGrowl {
+               display:                        none;
+       }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/345f22fb/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/jquery.jgrowl.js
----------------------------------------------------------------------
diff --git 
a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/jquery.jgrowl.js
 
b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/jquery.jgrowl.js
new file mode 100644
index 0000000..cd15d4a
--- /dev/null
+++ 
b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/jquery.jgrowl.js
@@ -0,0 +1,352 @@
+/**
+ * jGrowl 1.2.10
+ *
+ * Dual licensed under the MIT 
(http://www.opensource.org/licenses/mit-license.php)
+ * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
+ *
+ * Written by Stan Lemon <[email protected]>
+ * Last updated: 2013.02.14
+ *
+ * jGrowl is a jQuery plugin implementing unobtrusive userland notifications.  
These 
+ * notifications function similarly to the Growl Framework available for
+ * Mac OS X (http://growl.info).
+ *
+ * To Do:
+ * - Move library settings to containers and allow them to be changed per 
container
+ *
+ * Changes in 1.2.10
+ * - Fix beforeClose to be called in click event
+ *
+ * Changes in 1.2.9
+ * - Fixed BC break in jQuery 2.0 beta
+ *
+ * Changes in 1.2.8
+ * - Fixes for jQuery 1.9 and the MSIE6 check, note that with jQuery 2.0 
support
+ *   jGrowl intends to drop support for IE6 altogether
+ *
+ * Changes in 1.2.6
+ * - Fixed js error when a notification is opening and closing at the same time
+ * 
+ * Changes in 1.2.5
+ * - Changed wrapper jGrowl's options usage to "o" instead of $.jGrowl.defaults
+ * - Added themeState option to control 'highlight' or 'error' for jQuery UI
+ * - Ammended some CSS to provide default positioning for nested usage.
+ * - Changed some CSS to be prefixed with jGrowl- to prevent namespacing issues
+ * - Added two new options - openDuration and closeDuration to allow 
+ *   better control of notification open and close speeds, respectively 
+ *   Patch contributed by Jesse Vincet.
+ * - Added afterOpen callback.  Patch contributed by Russel Branca.
+ *
+ * Changes in 1.2.4
+ * - Fixed IE bug with the close-all button
+ * - Fixed IE bug with the filter CSS attribute (special thanks to gotwic)
+ * - Update IE opacity CSS
+ * - Changed font sizes to use "em", and only set the base style
+ *
+ * Changes in 1.2.3
+ * - The callbacks no longer use the container as context, instead they use 
the actual notification
+ * - The callbacks now receive the container as a parameter after the options 
parameter
+ * - beforeOpen and beforeClose now check the return value, if it's false - 
the notification does
+ *   not continue.  The open callback will also halt execution if it returns 
false.
+ * - Fixed bug where containers would get confused
+ * - Expanded the pause functionality to pause an entire container.
+ *
+ * Changes in 1.2.2
+ * - Notification can now be theme rolled for jQuery UI, special thanks to 
Jeff Chan!
+ *
+ * Changes in 1.2.1
+ * - Fixed instance where the interval would fire the close method multiple 
times.
+ * - Added CSS to hide from print media
+ * - Fixed issue with closer button when div { position: relative } is set
+ * - Fixed leaking issue with multiple containers.  Special thanks to Matthew 
Hanlon!
+ *
+ * Changes in 1.2.0
+ * - Added message pooling to limit the number of messages appearing at a 
given time.
+ * - Closing a notification is now bound to the notification object and 
triggered by the close button.
+ *
+ * Changes in 1.1.2
+ * - Added iPhone styled example
+ * - Fixed possible IE7 bug when determining if the ie6 class shoudl be 
applied.
+ * - Added template for the close button, so that it's content could be 
customized.
+ *
+ * Changes in 1.1.1
+ * - Fixed CSS styling bug for ie6 caused by a mispelling
+ * - Changes height restriction on default notifications to min-height
+ * - Added skinned examples using a variety of images
+ * - Added the ability to customize the content of the [close all] box
+ * - Added jTweet, an example of using jGrowl + Twitter
+ *
+ * Changes in 1.1.0
+ * - Multiple container and instances.
+ * - Standard $.jGrowl() now wraps $.fn.jGrowl() by first establishing a 
generic jGrowl container.
+ * - Instance methods of a jGrowl container can be called by 
$.fn.jGrowl(methodName)
+ * - Added glue preferenced, which allows notifications to be inserted before 
or after nodes in the container
+ * - Added new log callback which is called before anything is done for the 
notification
+ * - Corner's attribute are now applied on an individual notification basis.
+ *
+ * Changes in 1.0.4
+ * - Various CSS fixes so that jGrowl renders correctly in IE6.
+ *
+ * Changes in 1.0.3
+ * - Fixed bug with options persisting across notifications
+ * - Fixed theme application bug
+ * - Simplified some selectors and manipulations.
+ * - Added beforeOpen and beforeClose callbacks
+ * - Reorganized some lines of code to be more readable
+ * - Removed unnecessary this.defaults context
+ * - If corners plugin is present, it's now customizable.
+ * - Customizable open animation.
+ * - Customizable close animation.
+ * - Customizable animation easing.
+ * - Added customizable positioning (top-left, top-right, bottom-left, 
bottom-right, center)
+ *
+ * Changes in 1.0.2
+ * - All CSS styling is now external.
+ * - Added a theme parameter which specifies a secondary class for styling, 
such
+ *   that notifications can be customized in appearance on a per message basis.
+ * - Notification life span is now customizable on a per message basis.
+ * - Added the ability to disable the global closer, enabled by default.
+ * - Added callbacks for when a notification is opened or closed.
+ * - Added callback for the global closer.
+ * - Customizable animation speed.
+ * - jGrowl now set itself up and tears itself down.
+ *
+ * Changes in 1.0.1:
+ * - Removed dependency on metadata plugin in favor of .data()
+ * - Namespaced all events
+ */
+(function($) {
+       /** Compatibility holdover for 1.9 to check IE6 **/
+       var $ie6 = (function(){
+               return false === $.support.boxModel && $.support.objectAll && 
$support.leadingWhitespace;
+       })();
+
+       /** jGrowl Wrapper - Establish a base jGrowl Container for 
compatibility with older releases. **/
+       $.jGrowl = function( m , o ) {
+               // To maintain compatibility with older version that only 
supported one instance we'll create the base container.
+               if ( $('#jGrowl').size() == 0 ) 
+                       $('<div id="jGrowl"></div>').addClass( (o && 
o.position) ? o.position : $.jGrowl.defaults.position ).appendTo('body');
+
+               // Create a notification on the container.
+               $('#jGrowl').jGrowl(m,o);
+       };
+
+
+       /** Raise jGrowl Notification on a jGrowl Container **/
+       $.fn.jGrowl = function( m , o ) {
+               if ( $.isFunction(this.each) ) {
+                       var args = arguments;
+
+                       return this.each(function() {
+                               var self = this;
+
+                               /** Create a jGrowl Instance on the Container 
if it does not exist **/
+                               if ( $(this).data('jGrowl.instance') == 
undefined ) {
+                                       $(this).data('jGrowl.instance', 
$.extend( new $.fn.jGrowl(), { notifications: [], element: null, interval: null 
} ));
+                                       
$(this).data('jGrowl.instance').startup( this );
+                               }
+
+                               /** Optionally call jGrowl instance methods, or 
just raise a normal notification **/
+                               if ( 
$.isFunction($(this).data('jGrowl.instance')[m]) ) {
+                                       
$(this).data('jGrowl.instance')[m].apply( $(this).data('jGrowl.instance') , 
$.makeArray(args).slice(1) );
+                               } else {
+                                       $(this).data('jGrowl.instance').create( 
m , o );
+                               }
+                       });
+               };
+       };
+
+       $.extend( $.fn.jGrowl.prototype , {
+
+               /** Default JGrowl Settings **/
+               defaults: {
+                       pool:                   0,
+                       header:                 '',
+                       group:                  '',
+                       sticky:                 false,
+                       position:               'top-right',
+                       glue:                   'after',
+                       theme:                  'default',
+                       themeState:     'highlight',
+                       corners:                '10px',
+                       check:                  250,
+                       life:                   3000,
+                       closeDuration:  'normal',
+                       openDuration:   'normal',
+                       easing:                 'swing',
+                       closer:                 true,
+                       closeTemplate: '&times;',
+                       closerTemplate: '<div>[ close all ]</div>',
+                       log:                    function(e,m,o) {},
+                       beforeOpen:     function(e,m,o) {},
+                       afterOpen:              function(e,m,o) {},
+                       open:                   function(e,m,o) {},
+                       beforeClose:    function(e,m,o) {},
+                       close:                  function(e,m,o) {},
+                       animateOpen:    {
+                               opacity:        'show'
+                       },
+                       animateClose:   {
+                               opacity:        'hide'
+                       }
+               },
+               
+               notifications: [],
+               
+               /** jGrowl Container Node **/
+               element:        null,
+       
+               /** Interval Function **/
+               interval:   null,
+               
+               /** Create a Notification **/
+               create:         function( message , o ) {
+                       var o = $.extend({}, this.defaults, o);
+
+                       /* To keep backward compatibility with 1.24 and 
earlier, honor 'speed' if the user has set it */
+                       if (typeof o.speed !== 'undefined') {
+                               o.openDuration = o.speed;
+                               o.closeDuration = o.speed;
+                       }
+
+                       this.notifications.push({ message: message , options: o 
});
+                       
+                       o.log.apply( this.element , [this.element,message,o] );
+               },
+               
+               render:                 function( notification ) {
+                       var self = this;
+                       var message = notification.message;
+                       var o = notification.options;
+
+                       // Support for jQuery theme-states, if this is not used 
it displays a widget header
+                       o.themeState = (o.themeState == '') ? '' : 'ui-state-' 
+ o.themeState;
+
+                       var notification = $('<div/>')
+                       .addClass('jGrowl-notification ' + o.themeState + ' 
ui-corner-all' + ((o.group != undefined && o.group != '') ? ' ' + o.group : ''))
+                       
.append($('<div/>').addClass('jGrowl-close').html(o.closeTemplate))
+                       
.append($('<div/>').addClass('jGrowl-header').html(o.header))
+                       
.append($('<div/>').addClass('jGrowl-message').html(message))
+                       .data("jGrowl", 
o).addClass(o.theme).children('div.jGrowl-close').bind("click.jGrowl", 
function() {
+                               $(this).parent().trigger('jGrowl.beforeClose'); 
                
+                       })
+                       .parent();
+
+
+                       /** Notification Actions **/
+                       $(notification).bind("mouseover.jGrowl", function() {
+                               $('div.jGrowl-notification', 
self.element).data("jGrowl.pause", true);
+                       }).bind("mouseout.jGrowl", function() {
+                               $('div.jGrowl-notification', 
self.element).data("jGrowl.pause", false);
+                       }).bind('jGrowl.beforeOpen', function() {
+                               if ( o.beforeOpen.apply( notification , 
[notification,message,o,self.element] ) != false ) {
+                                       $(this).trigger('jGrowl.open');
+                               }
+                       }).bind('jGrowl.open', function() {
+                               if ( o.open.apply( notification , 
[notification,message,o,self.element] ) != false ) {
+                                       if ( o.glue == 'after' ) {
+                                               
$('div.jGrowl-notification:last', self.element).after(notification);
+                                       } else {
+                                               
$('div.jGrowl-notification:first', self.element).before(notification);
+                                       }
+                                       
+                                       $(this).animate(o.animateOpen, 
o.openDuration, o.easing, function() {
+                                               // Fixes some anti-aliasing 
issues with IE filters.
+                                               if ($.support.opacity === 
false) 
+                                                       
this.style.removeAttribute('filter');
+
+                                               if ( $(this).data("jGrowl") != 
null ) // Happens when a notification is closing before it's open.
+                                                       
$(this).data("jGrowl").created = new Date();
+                                               
+                                               
$(this).trigger('jGrowl.afterOpen');
+                                       });
+                               }
+                       }).bind('jGrowl.afterOpen', function() {
+                               o.afterOpen.apply( notification , 
[notification,message,o,self.element] );
+                       }).bind('jGrowl.beforeClose', function() {
+                               if ( o.beforeClose.apply( notification , 
[notification,message,o,self.element] ) != false )
+                                       $(this).trigger('jGrowl.close');
+                       }).bind('jGrowl.close', function() {
+                               // Pause the notification, lest during the 
course of animation another close event gets called.
+                               $(this).data('jGrowl.pause', true);
+                               $(this).animate(o.animateClose, 
o.closeDuration, o.easing, function() {
+                                       if ( $.isFunction(o.close) ) {
+                                               if ( o.close.apply( 
notification , [notification,message,o,self.element] ) !== false )
+                                                       $(this).remove();
+                                       } else {
+                                               $(this).remove();
+                                       }
+                               });
+                       }).trigger('jGrowl.beforeOpen');
+               
+                       /** Optional Corners Plugin **/
+                       if ( o.corners != '' && $.fn.corner != undefined ) 
$(notification).corner( o.corners );
+
+                       /** Add a Global Closer if more than one notification 
exists **/
+                       if ( $('div.jGrowl-notification:parent', 
self.element).size() > 1 && 
+                                $('div.jGrowl-closer', self.element).size() == 
0 && this.defaults.closer != false ) {
+                               
$(this.defaults.closerTemplate).addClass('jGrowl-closer ' + 
this.defaults.themeState + ' ui-corner-all').addClass(this.defaults.theme)
+                                       
.appendTo(self.element).animate(this.defaults.animateOpen, this.defaults.speed, 
this.defaults.easing)
+                                       .bind("click.jGrowl", function() {
+                                               
$(this).siblings().trigger("jGrowl.beforeClose");
+
+                                               if ( $.isFunction( 
self.defaults.closer ) ) {
+                                                       
self.defaults.closer.apply( $(this).parent()[0] , [$(this).parent()[0]] );
+                                               }
+                                       });
+                       };
+               },
+
+               /** Update the jGrowl Container, removing old jGrowl 
notifications **/
+               update:  function() {
+                       
$(this.element).find('div.jGrowl-notification:parent').each( function() {
+                               if ( $(this).data("jGrowl") != undefined && 
$(this).data("jGrowl").created != undefined && 
+                                        
($(this).data("jGrowl").created.getTime() + 
parseInt($(this).data("jGrowl").life))  < (new Date()).getTime() && 
+                                        $(this).data("jGrowl").sticky != true 
&& 
+                                        ($(this).data("jGrowl.pause") == 
undefined || $(this).data("jGrowl.pause") != true) ) {
+
+                                       // Pause the notification, lest during 
the course of animation another close event gets called.
+                                       $(this).trigger('jGrowl.beforeClose');
+                               }
+                       });
+
+                       if ( this.notifications.length > 0 && 
+                                (this.defaults.pool == 0 || 
$(this.element).find('div.jGrowl-notification:parent').size() < 
this.defaults.pool) )
+                               this.render( this.notifications.shift() );
+
+                       if ( 
$(this.element).find('div.jGrowl-notification:parent').size() < 2 ) {
+                               
$(this.element).find('div.jGrowl-closer').animate(this.defaults.animateClose, 
this.defaults.speed, this.defaults.easing, function() {
+                                       $(this).remove();
+                               });
+                       }
+               },
+
+               /** Setup the jGrowl Notification Container **/
+               startup:        function(e) {
+                       this.element = $(e).addClass('jGrowl').append('<div 
class="jGrowl-notification"></div>');
+                       this.interval = setInterval( function() { 
+                               $(e).data('jGrowl.instance').update(); 
+                       }, parseInt(this.defaults.check));
+                       
+                       if ($ie6) {
+                               $(this.element).addClass('ie6');
+                       }
+               },
+
+               /** Shutdown jGrowl, removing it and clearing the interval **/
+               shutdown:   function() {
+                       
$(this.element).removeClass('jGrowl').find('div.jGrowl-notification').remove();
+                       clearInterval( this.interval );
+               },
+               
+               close:  function() {
+                       
$(this.element).find('div.jGrowl-notification').each(function(){
+                               $(this).trigger('jGrowl.beforeClose');
+                       });
+               }
+       });
+       
+       /** Reference the Defaults Object for compatibility with older versions 
of jGrowl **/
+       $.jGrowl.defaults = $.fn.jGrowl.prototype.defaults;
+
+})(jQuery);

Reply via email to