You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2013/02/26 00:28:30 UTC

[4/24] git commit: ISIS-349, 350: further improvements

ISIS-349, 350: further improvements

* rendering of the error page is prettier
* had forgotten to remove debug buttons at bottom of PageAbstract
* force JQuery to always be loaded
* inline JGrowlBehaviour instead into PageAbstract
* enhanced ToDo app to demonstrate this stuff.


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

Branch: refs/heads/dan/ISIS-233-ro
Commit: 0d9216c2d29c1c093c90417aea00310d99637ee4
Parents: 345f22f
Author: Dan Haywood <da...@apache.org>
Authored: Fri Feb 22 00:54:40 2013 +0000
Committer: Dan Haywood <da...@apache.org>
Committed: Fri Feb 22 00:54:40 2013 +0000

----------------------------------------------------------------------
 .../ui/components/widgets/cssmenu/CssMenuItem.java |    2 -
 .../viewer/wicket/ui/feedback/JGrowlBehavior.java  |  101 ---------------
 .../isis/viewer/wicket/ui/pages/PageAbstract.html  |    7 +-
 .../isis/viewer/wicket/ui/pages/PageAbstract.java  |   75 +++++++----
 .../viewer/wicket/ui/pages/error/ErrorPage.css     |   28 ++++-
 .../viewer/wicket/ui/pages/error/ErrorPage.html    |   14 +--
 .../viewer/wicket/ui/pages/error/ErrorPage.java    |   74 +++++++++--
 .../viewer/wicket/ui/pages/error/div-toggle.js     |    7 +
 .../dom/src/main/java/dom/todo/ToDoItem.java       |    1 +
 .../dom/src/main/java/dom/todo/ToDoItems.java      |   23 ++++
 .../main/java/objstore/jdo/todo/ToDoItemsJdo.java  |    5 +-
 11 files changed, 172 insertions(+), 165 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/0d9216c2/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 3a1e3f0..3ddacba 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
@@ -230,8 +230,6 @@ 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();
 

http://git-wip-us.apache.org/repos/asf/isis/blob/0d9216c2/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
deleted file mode 100644
index 24fdb98..0000000
--- a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/feedback/JGrowlBehavior.java
+++ /dev/null
@@ -1,101 +0,0 @@
-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/0d9216c2/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 f5bbc65..69eaa02 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
@@ -61,12 +61,7 @@
 
 				<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/0d9216c2/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 5c283a3..b663e7e 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
@@ -23,12 +23,14 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
+import org.apache.commons.lang.StringUtils;
 import org.apache.isis.core.commons.authentication.AuthenticationSession;
 import org.apache.isis.core.metamodel.services.ServicesInjectorSpi;
 import org.apache.isis.core.metamodel.spec.SpecificationLoaderSpi;
 import org.apache.isis.core.runtime.system.context.IsisContext;
 import org.apache.isis.core.runtime.system.persistence.PersistenceSession;
 import org.apache.isis.viewer.wicket.model.mementos.PageParameterNames;
+import org.apache.isis.viewer.wicket.model.models.ActionModel;
 import org.apache.isis.viewer.wicket.model.models.ApplicationActionsModel;
 import org.apache.isis.viewer.wicket.model.models.BookmarkableModel;
 import org.apache.isis.viewer.wicket.model.models.BookmarkedPagesModel;
@@ -40,6 +42,7 @@ 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.Application;
 import org.apache.wicket.RestartResponseAtInterceptPageException;
 import org.apache.wicket.Session;
 import org.apache.wicket.ajax.AjaxRequestTarget;
@@ -47,7 +50,9 @@ 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.JavaScriptHeaderItem;
 import org.apache.wicket.markup.head.JavaScriptReferenceHeaderItem;
+import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
 import org.apache.wicket.markup.html.WebPage;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.form.Form;
@@ -81,6 +86,8 @@ public abstract class PageAbstract extends WebPage {
     public static final String ID_LOGOUT_LINK = "logoutLink";
     public static final String ID_ABOUT_LINK = "aboutLink";
 
+    private static final JavaScriptResourceReference JQUERY_JGROWL_JS = new JavaScriptResourceReference(PageAbstract.class, "jquery.jgrowl.js");
+    
     private final List<ComponentType> childComponentIds;
     private final PageParameters pageParameters;
     
@@ -113,7 +120,6 @@ 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)));
@@ -126,12 +132,19 @@ 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(JavaScriptHeaderItem.forReference(Application.get().getJavaScriptLibrarySettings().getJQueryReference()));
         response.render(JavaScriptReferenceHeaderItem.forReference(JQUERY_JGROWL_JS));
+        
+        final String feedbackMsg = renderFeedback();
+        if (!StringUtils.isEmpty(feedbackMsg)) {
+            response.render(OnDomReadyHeaderItem.forScript(feedbackMsg));
+        }
+
+        
         if(applicationCss != null) {
             response.render(CssReferenceHeaderItem.forUrl(applicationCss));
         }
@@ -139,6 +152,39 @@ public abstract class PageAbstract extends WebPage {
             response.render(JavaScriptReferenceHeaderItem.forUrl(applicationJs));
         }
     }
+
+    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(");");
+    }
+
     
     private void addHomePageLinkAndApplicationName() {
         // this is a bit hacky, but it'll do...
@@ -175,31 +221,6 @@ 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/0d9216c2/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
index 1685bf1..dc92499 100644
--- 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
@@ -55,7 +55,6 @@
     display:block;
     font-style:normal !important;
     padding:3px 3px 3px 3px;
-    text-transform: uppercase;
     font-size: 0.8em;
     text-transform:uppercase;
     font-weight:bold;
@@ -74,8 +73,31 @@
     margin-top: 30px; 
 }
 
- .errorPage .exceptionStackTrace {
-    margin-top: 30px; 
+.errorPage .exceptionStackTrace .caused_by_label {
+    margin-top: 30px;
+	font-style:normal !important;
+    font-size: 0.8em;
+    text-transform:uppercase;
+    font-weight:bold;
+    color: #46423C;
+}
+
+.errorPage .exceptionStackTrace .exception_class_name {
+    margin-top: 15px;
+    font-size: 1.2em;
+    font-weight:bold;
+    color: #46423C;
 }
 
+.errorPage .exceptionStackTrace .exception_message {
+    margin-top: 10px;
+    margin-bottom: 10px;
+    font-style: italic;
+    font-size: 1.0em;
+    color: #00477F;
+}
+
+.errorPage .exceptionStackTrace .stacktrace_element{
+    margin-left: 30px;
+}
 

http://git-wip-us.apache.org/repos/asf/isis/blob/0d9216c2/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
index 76adce9..fc5f630 100644
--- 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
@@ -26,16 +26,6 @@
 		<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>
@@ -50,7 +40,9 @@
                         </div>
                         <div class="exceptionStackTrace">
                             <h3>Stack trace:</h3>
-                            <p wicket:id="stackTrace">Stacktrace goes here</p>
+                            <ul>
+                                <li wicket:id="detail"><span wicket:id="line">line</span></li>
+                            </ul>
                         </div>
                     </div>
                 </div>

http://git-wip-us.apache.org/repos/asf/isis/blob/0d9216c2/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
index 3147f8d..c306e96 100644
--- 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
@@ -19,12 +19,22 @@
 
 package org.apache.isis.viewer.wicket.ui.pages.error;
 
+import java.util.List;
+
 import org.apache.isis.viewer.wicket.ui.pages.PageAbstract;
 import org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeInstantiation;
+import org.apache.wicket.behavior.AttributeAppender;
+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.basic.Label;
+import org.apache.wicket.markup.html.list.ListItem;
+import org.apache.wicket.markup.html.list.ListView;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.request.resource.JavaScriptResourceReference;
 
 import com.google.common.base.Throwables;
+import com.google.common.collect.Lists;
 
 /**
  * Web page representing the home page (showing a welcome message).
@@ -35,29 +45,67 @@ 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";
+    private static final String ID_DETAIL = "detail";
+    private static final String ID_LINE = "line";
+
+    private static final JavaScriptResourceReference DIV_TOGGLE_JS = new JavaScriptResourceReference(ErrorPage.class, "div-toggle.js");
+
+    private static class Detail {
+        enum Type {
+            EXCEPTION_CLASS_NAME,
+            EXCEPTION_MESSAGE,
+            STACKTRACE_ELEMENT
+        }
+        private Type type;
+        private String line;
+        Detail(Type type, String line) {
+            this.type = type;
+            this.line = line;
+        }
+    }
 
     public ErrorPage(Exception ex) {
         super(new PageParameters());
         add(new Label(ID_MESSAGE, ex.getMessage()));
-        add(new Label(ID_STACK_TRACE, stackTraceAsString(ex)));
+        
+        add(new ListView<Detail>(ID_DETAIL, asStackTrace(ex)) {
+                private static final long serialVersionUID = 1L;
+
+                @Override
+                protected void populateItem(ListItem<Detail> item) {
+                    final Detail detail = item.getModelObject();
+                    Label label = new Label(ID_LINE, detail.line);
+                    item.add(new AttributeAppender("class", detail.type.name().toLowerCase()));
+                    item.add(label);
+                }
+            });
     }
 
-    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);
+    @Override
+    public void renderHead(IHeaderResponse response) {
+        super.renderHead(response);
+        response.render(JavaScriptReferenceHeaderItem.forReference(DIV_TOGGLE_JS));
+    }
+    
+
+    private static List<Detail> asStackTrace(Throwable ex) {
+        List<Detail> stackTrace = Lists.newArrayList();
+        List<Throwable> causalChain = Throwables.getCausalChain(ex);
+        for(Throwable cause: causalChain) {
+            stackTrace.add(new Detail(Detail.Type.EXCEPTION_CLASS_NAME, cause.getClass().getName()));
+            stackTrace.add(new Detail(Detail.Type.EXCEPTION_MESSAGE, cause.getMessage()));
+            addStackTraceElements(cause, stackTrace);
             cause = cause.getCause();
         }
-        return buf.toString();
+        return stackTrace;
     }
 
-    private static void appendStackTrace(Throwable ex, StringBuilder buf) {
+    private static void addStackTraceElements(Throwable ex, List<Detail> stackTrace) {
         for (StackTraceElement el : ex.getStackTrace()) {
-            buf. append(el.getClassName())
+            StringBuilder buf = new StringBuilder();
+            buf .append("    ")
+                .append(el.getClassName())
+                .append("#")
                 .append(el.getMethodName())
                 .append("(")
                 .append(el.getFileName())
@@ -65,7 +113,7 @@ public class ErrorPage extends PageAbstract {
                 .append(el.getLineNumber())
                 .append(")\n")
                 ;
+            stackTrace.add(new Detail(Detail.Type.STACKTRACE_ELEMENT, buf.toString()));
         }
     }
-
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/0d9216c2/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/error/div-toggle.js
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/error/div-toggle.js b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/error/div-toggle.js
new file mode 100644
index 0000000..7dd3f38
--- /dev/null
+++ b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/error/div-toggle.js
@@ -0,0 +1,7 @@
+jQuery(document).ready(function() {
+  jQuery(".errorPage .content").hide();
+  jQuery(".errorPage .heading").click(function()
+  {
+    jQuery(this).next(".errorPage .content").slideToggle(500);
+  });
+});

http://git-wip-us.apache.org/repos/asf/isis/blob/0d9216c2/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItem.java
----------------------------------------------------------------------
diff --git a/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItem.java b/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItem.java
index c3e3a5a..07dd3fd 100644
--- a/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItem.java
+++ b/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItem.java
@@ -109,6 +109,7 @@ public class ToDoItem implements Comparable<ToDoItem> /*, Locatable*/ { // GMAP3
     // {{ Description
     private String description;
 
+    @javax.jdo.annotations.Unique
     @RegEx(validation = "\\w[@&:\\-\\,\\.\\+ \\w]*")
     // words, spaces and selected punctuation
     @MemberOrder(sequence = "1")

http://git-wip-us.apache.org/repos/asf/isis/blob/0d9216c2/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItems.java
----------------------------------------------------------------------
diff --git a/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItems.java b/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItems.java
index 59af784..356ea1d 100644
--- a/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItems.java
+++ b/example/application/quickstart_wicket_restful_jdo/dom/src/main/java/dom/todo/ToDoItems.java
@@ -28,6 +28,7 @@ import org.apache.isis.applib.annotation.Hidden;
 import org.apache.isis.applib.annotation.MemberOrder;
 import org.apache.isis.applib.annotation.Named;
 import org.apache.isis.applib.annotation.NotInServiceMenu;
+import org.apache.isis.applib.clock.Clock;
 import org.apache.isis.applib.filter.Filter;
 import org.joda.time.LocalDate;
 
@@ -53,6 +54,14 @@ public class ToDoItems extends AbstractFactoryAndRepository {
     @ActionSemantics(Of.SAFE)
     @MemberOrder(sequence = "1")
     public List<ToDoItem> notYetComplete() {
+        List<ToDoItem> items = doNotYetComplete();
+        if(items.isEmpty()) {
+            getContainer().informUser("All to-do items have been completed :-)");
+        }
+        return items;
+    }
+
+    protected List<ToDoItem> doNotYetComplete() {
         return allMatches(ToDoItem.class, new Filter<ToDoItem>() {
             @Override
             public boolean accept(final ToDoItem t) {
@@ -66,6 +75,14 @@ public class ToDoItems extends AbstractFactoryAndRepository {
     @ActionSemantics(Of.SAFE)
     @MemberOrder(sequence = "2")
     public List<ToDoItem> complete() {
+        List<ToDoItem> items = doComplete();
+        if(items.isEmpty()) {
+            getContainer().informUser("No to-do items have yet been completed :-(");
+        }
+        return items;
+    }
+
+    protected List<ToDoItem> doComplete() {
         return allMatches(ToDoItem.class, new Filter<ToDoItem>() {
             @Override
             public boolean accept(final ToDoItem t) {
@@ -84,6 +101,9 @@ public class ToDoItems extends AbstractFactoryAndRepository {
         final String ownedBy = currentUserName();
         return newToDo(description, category, ownedBy, dueBy);
     }
+    public LocalDate default2NewToDo() {
+        return new LocalDate(Clock.getTime()).plusDays(14);
+    }
     // }}
 
     
@@ -94,6 +114,9 @@ public class ToDoItems extends AbstractFactoryAndRepository {
         final String currentUser = currentUserName();
         final List<ToDoItem> items = allMatches(ToDoItem.class, ToDoItem.thoseOwnedBy(currentUser));
         Collections.sort(items);
+        if(items.isEmpty()) {
+            getContainer().warnUser("No to-do items found.");
+        }
         return items;
     }
     // }}

http://git-wip-us.apache.org/repos/asf/isis/blob/0d9216c2/example/application/quickstart_wicket_restful_jdo/objstore-jdo/src/main/java/objstore/jdo/todo/ToDoItemsJdo.java
----------------------------------------------------------------------
diff --git a/example/application/quickstart_wicket_restful_jdo/objstore-jdo/src/main/java/objstore/jdo/todo/ToDoItemsJdo.java b/example/application/quickstart_wicket_restful_jdo/objstore-jdo/src/main/java/objstore/jdo/todo/ToDoItemsJdo.java
index 5098bc2..f5837f3 100644
--- a/example/application/quickstart_wicket_restful_jdo/objstore-jdo/src/main/java/objstore/jdo/todo/ToDoItemsJdo.java
+++ b/example/application/quickstart_wicket_restful_jdo/objstore-jdo/src/main/java/objstore/jdo/todo/ToDoItemsJdo.java
@@ -18,6 +18,7 @@
  */
 package objstore.jdo.todo;
 
+import java.util.Collections;
 import java.util.List;
 
 import com.google.common.base.Predicate;
@@ -33,7 +34,7 @@ public class ToDoItemsJdo extends ToDoItems {
 
     // {{ notYetComplete (action)
     @Override
-    public List<ToDoItem> notYetComplete() {
+    protected List<ToDoItem> doNotYetComplete() {
         return allMatches(
                 new QueryDefault<ToDoItem>(ToDoItem.class, 
                         "todo_notYetComplete", 
@@ -43,7 +44,7 @@ public class ToDoItemsJdo extends ToDoItems {
 
     // {{ done (action)
     @Override
-    public List<ToDoItem> complete() {
+    protected List<ToDoItem> doComplete() {
         return allMatches(
                 new QueryDefault<ToDoItem>(ToDoItem.class, 
                         "todo_complete",