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 2015/09/03 14:32:17 UTC
[26/87] [abbrv] [partial] isis git commit: ISIS-1194: moving the
wicket submodules to be direct children of core;
removing the isis-viewer-wicket parent pom.
http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/zclip/ZeroClipboardPanel.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/zclip/ZeroClipboardPanel.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/zclip/ZeroClipboardPanel.java
new file mode 100644
index 0000000..15bd4b4
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/zclip/ZeroClipboardPanel.java
@@ -0,0 +1,177 @@
+/**
+ * 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.components.widgets.zclip;
+
+import de.agilecoders.wicket.jquery.util.Strings2;
+
+import org.apache.wicket.Page;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.AjaxLink;
+import org.apache.wicket.event.IEvent;
+import org.apache.wicket.markup.html.form.TextField;
+import org.apache.wicket.markup.html.link.AbstractLink;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.model.LoadableDetachableModel;
+import org.apache.wicket.request.Url;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.viewer.wicket.model.hints.IsisEnvelopeEvent;
+import org.apache.isis.viewer.wicket.model.hints.IsisUiHintEvent;
+import org.apache.isis.viewer.wicket.model.hints.UiHintContainer;
+import org.apache.isis.viewer.wicket.model.models.EntityModel;
+import org.apache.isis.viewer.wicket.model.models.PageType;
+import org.apache.isis.viewer.wicket.ui.pages.PageClassRegistry;
+import org.apache.isis.viewer.wicket.ui.pages.PageClassRegistryAccessor;
+import org.apache.isis.viewer.wicket.ui.panels.PanelAbstract;
+import org.apache.isis.viewer.wicket.ui.util.Links;
+
+public class ZeroClipboardPanel extends PanelAbstract<EntityModel> {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final String ID_SUBSCRIBING_LINK = "subscribingLink";
+ private static final String ID_COPY_LINK = "copyLink";
+ private static final String ID_SIMPLE_CLIPBOARD_MODAL_WINDOW = "simpleClipboardModalWindow";
+
+ private AbstractLink subscribingLink;
+ private AjaxLink<ObjectAdapter> copyLink;
+ private SimpleClipboardModalWindow simpleClipboardModalWindow;
+
+ public ZeroClipboardPanel(String id, EntityModel entityModel) {
+ super(id, entityModel);
+ }
+
+ @Override
+ protected void onInitialize() {
+ super.onInitialize();
+
+ if(copyLink == null) {
+ copyLink = createLink(ID_COPY_LINK);
+ addOrReplace(copyLink);
+ }
+ EntityModel model = getModel();
+ addSubscribingLink(model);
+ addSimpleClipboardModalWindow();
+
+ EntityModel.RenderingHint renderingHint = model.getRenderingHint();
+ EntityModel.Mode mode = model.getMode();
+ setVisible(renderingHint == EntityModel.RenderingHint.REGULAR && mode == EntityModel.Mode.VIEW);
+ }
+
+ private AjaxLink<ObjectAdapter> createLink(String linkId) {
+ return newSimpleClipboardLink(linkId);
+ }
+
+ private AjaxLink<ObjectAdapter> newSimpleClipboardLink(String linkId) {
+ return new AjaxLink<ObjectAdapter>(linkId) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void onClick(AjaxRequestTarget target) {
+
+ String contentId = simpleClipboardModalWindow.getContentId();
+ SimpleClipboardModalWindowPanel panel = new SimpleClipboardModalWindowPanel(contentId);
+ SimpleClipboardModalWindowForm form = new SimpleClipboardModalWindowForm("form");
+
+ final TextField<String> textField = new TextField<String>("textField", new LoadableDetachableModel<String>() {
+ private static final long serialVersionUID = 1L;
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ @Override
+ protected String load() {
+ if(subscribingLink instanceof BookmarkablePageLink) {
+ final BookmarkablePageLink<?> link = (BookmarkablePageLink<?>) subscribingLink;
+ final Class pageClass = link.getPageClass();
+ final PageParameters pageParameters = link.getPageParameters();
+ final CharSequence urlFor = link.urlFor(pageClass, pageParameters);
+ return getRequestCycle().getUrlRenderer().renderFullUrl(Url.parse(urlFor));
+ } else {
+ return "";
+ }
+ }
+ });
+ panel.add(form);
+ form.add(textField);
+
+ textField.setOutputMarkupId(true);
+
+ CharSequence modalId = Strings2.escapeMarkupId(simpleClipboardModalWindow.getMarkupId());
+ CharSequence textFieldId = Strings2.escapeMarkupId(textField.getMarkupId());
+ target.appendJavaScript(String.format("$('#%s').one('shown.bs.modal', function(){Wicket.$('%s').select();});", modalId, textFieldId));
+
+ simpleClipboardModalWindow.setPanel(panel, target);
+ simpleClipboardModalWindow.showPrompt(target);
+
+ target.focusComponent(textField);
+ }
+ };
+ }
+
+
+ private void addSubscribingLink(UiHintContainer uiHintContainer) {
+ if(uiHintContainer == null && subscribingLink != null) {
+ // ignore, since has already been primed
+ return;
+ }
+ AbstractLink subscribingLink = createSubscribingLink(uiHintContainer);
+ if(subscribingLink != null) {
+ this.subscribingLink = subscribingLink;
+ this.subscribingLink.setOutputMarkupId(true);
+ }
+ }
+
+ private void addSimpleClipboardModalWindow() {
+ simpleClipboardModalWindow = SimpleClipboardModalWindow.newModalWindow(ID_SIMPLE_CLIPBOARD_MODAL_WINDOW);
+ addOrReplace(simpleClipboardModalWindow);
+ }
+
+ private AbstractLink createSubscribingLink(UiHintContainer uiHintContainer) {
+ if(uiHintContainer == null || !(uiHintContainer instanceof EntityModel)) {
+ // return a no-op
+ return null;
+ } else {
+ final EntityModel entityModel = (EntityModel) uiHintContainer;
+ final PageParameters pageParameters = entityModel.getPageParameters();
+ final Class<? extends Page> pageClass = getPageClassRegistry().getPageClass(PageType.ENTITY);
+ return Links.newBookmarkablePageLink(ID_SUBSCRIBING_LINK, pageParameters, pageClass);
+ }
+ }
+
+ // //////////////////////////////////////
+
+ @Override
+ public void onEvent(IEvent<?> event) {
+ super.onEvent(event);
+
+ final IsisUiHintEvent uiHintEvent = IsisEnvelopeEvent.openLetter(event, IsisUiHintEvent.class);
+ if(uiHintEvent == null) {
+ return;
+ }
+ addSubscribingLink(uiHintEvent.getUiHintContainer());
+ final AjaxRequestTarget target = uiHintEvent.getTarget();
+ if(target != null) {
+ target.add(subscribingLink);
+ }
+ }
+
+ // //////////////////////////////////////
+
+ protected PageClassRegistry getPageClassRegistry() {
+ final PageClassRegistryAccessor pcra = (PageClassRegistryAccessor) getApplication();
+ return pcra.getPageClassRegistry();
+ }
+}
http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionModel.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionModel.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionModel.java
new file mode 100644
index 0000000..936c3fd
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionModel.java
@@ -0,0 +1,146 @@
+/*
+ * 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.errors;
+
+import java.util.Iterator;
+import java.util.List;
+import com.google.common.base.Throwables;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import org.apache.isis.applib.NonRecoverableException;
+import org.apache.isis.core.metamodel.spec.feature.ObjectMember;
+import org.apache.isis.viewer.wicket.model.models.ModelAbstract;
+
+public class ExceptionModel extends ModelAbstract<List<StackTraceDetail>> {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final String MAIN_MESSAGE_IF_NOT_RECOGNIZED = "Sorry, an unexpected error occurred.";
+
+ private List<StackTraceDetail> stackTraceDetailList;
+ private boolean recognized;
+ private boolean authorizationCause;
+
+ private final String mainMessage;
+
+ public static ExceptionModel create(String recognizedMessageIfAny, Exception ex) {
+ return new ExceptionModel(recognizedMessageIfAny, ex);
+ }
+
+ /**
+ * Three cases: authorization exception, else recognized, else or not recognized.
+ * @param recognizedMessageIfAny
+ * @param ex
+ */
+ private ExceptionModel(String recognizedMessageIfAny, Exception ex) {
+
+ final ObjectMember.AuthorizationException authorizationException = causalChainOf(ex, ObjectMember.AuthorizationException.class);
+ if(authorizationException != null) {
+ this.authorizationCause = true;
+ this.mainMessage = authorizationException.getMessage();
+ } else {
+ this.authorizationCause = false;
+ if(recognizedMessageIfAny != null) {
+ this.recognized = true;
+ this.mainMessage = recognizedMessageIfAny;
+ } else {
+ this.recognized =false;
+
+ // see if we can find a NonRecoverableException in the stack trace
+ Iterable<NonRecoverableException> appEx = Iterables.filter(Throwables.getCausalChain(ex), NonRecoverableException.class);
+ Iterator<NonRecoverableException> iterator = appEx.iterator();
+ NonRecoverableException nonRecoverableException = iterator.hasNext() ? iterator.next() : null;
+
+ this.mainMessage = nonRecoverableException != null? nonRecoverableException.getMessage() : MAIN_MESSAGE_IF_NOT_RECOGNIZED;
+ }
+ }
+ stackTraceDetailList = asStackTrace(ex);
+ }
+
+
+ @Override
+ protected List<StackTraceDetail> load() {
+ return stackTraceDetailList;
+ }
+
+ private static <T extends Exception> T causalChainOf(Exception ex, Class<T> exType) {
+
+ final List<Throwable> causalChain = Throwables.getCausalChain(ex);
+ for (Throwable cause : causalChain) {
+ if(exType.isAssignableFrom(cause.getClass())) {
+ return (T)cause;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void setObject(List<StackTraceDetail> stackTraceDetail) {
+ if(stackTraceDetail == null) {
+ return;
+ }
+ this.stackTraceDetailList = stackTraceDetail;
+ }
+
+ public boolean isRecognized() {
+ return recognized;
+ }
+
+ public String getMainMessage() {
+ return mainMessage;
+ }
+
+ /**
+ * Whether this was an authorization exception (so UI can suppress information, eg stack trace).
+ */
+ public boolean isAuthorizationException() {
+ return authorizationCause;
+ }
+
+
+ public List<StackTraceDetail> getStackTrace() {
+ return stackTraceDetailList;
+ }
+
+ private static List<StackTraceDetail> asStackTrace(Throwable ex) {
+ List<StackTraceDetail> stackTrace = Lists.newArrayList();
+ List<Throwable> causalChain = Throwables.getCausalChain(ex);
+ boolean firstTime = true;
+ for(Throwable cause: causalChain) {
+ if(!firstTime) {
+ stackTrace.add(StackTraceDetail.spacer());
+ stackTrace.add(StackTraceDetail.causedBy());
+ stackTrace.add(StackTraceDetail.spacer());
+ } else {
+ firstTime = false;
+ }
+ stackTrace.add(StackTraceDetail.exceptionClassName(cause));
+ stackTrace.add(StackTraceDetail.exceptionMessage(cause));
+ addStackTraceElements(cause, stackTrace);
+ }
+ return stackTrace;
+ }
+
+ private static void addStackTraceElements(Throwable ex, List<StackTraceDetail> stackTrace) {
+ for (StackTraceElement el : ex.getStackTrace()) {
+ stackTrace.add(StackTraceDetail.element(el));
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.css
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.css b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.css
new file mode 100644
index 0000000..521283c
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.css
@@ -0,0 +1,101 @@
+/*
+ * 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.
+ */
+
+ .exceptionStackTracePanel .errorInfo {
+ margin-left: 50px;
+ margin-right: 50px;
+ padding-top: 50px;
+}
+
+
+.exceptionStackTracePanel .mainMessage {
+ background:#FFFFFF;
+ border-radius:4px;
+ -moz-border-radius:4px;
+ -webkit-border-radius:4px;
+ padding: 15px;
+ display: block;
+ text-align:center;
+ font-size:1.2em;
+}
+
+ .exceptionStackTracePanel .errorDetail {
+ margin-top: 30px;
+}
+
+.exceptionStackTracePanel .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;
+}
+
+.exceptionStackTracePanel .heading span {
+ display:block;
+ font-style:normal !important;
+ padding:3px 3px 3px 3px;
+ font-size: 0.8em;
+ font-weight:bold;
+}
+
+.exceptionStackTracePanel .heading:hover {
+ background-color:#FFFFFF;
+ cursor: pointer;
+}
+
+.exceptionStackTracePanel h3 {
+ font-size: larger;
+}
+
+.exceptionStackTracePanel .exceptionMessage {
+ margin-top: 30px;
+}
+
+.exceptionStackTracePanel .exceptionStackTrace .caused_by_label {
+ margin-top: 30px;
+ font-style:normal !important;
+ font-size: 0.8em;
+ font-weight:bold;
+ color: #46423C;
+}
+
+.exceptionStackTracePanel .exceptionStackTrace .exception_class_name {
+ margin-top: 15px;
+ font-size: 1.2em;
+ font-weight:bold;
+ color: #46423C;
+}
+
+.exceptionStackTracePanel .exceptionStackTrace .exception_message {
+ margin-top: 10px;
+ margin-bottom: 10px;
+ font-style: italic;
+ font-size: 1.0em;
+ color: #00477F;
+}
+
+.exceptionStackTracePanel .exceptionStackTrace .stacktrace_element{
+ margin-left: 30px;
+}
+
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.html
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.html b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.html
new file mode 100644
index 0000000..2d61e32
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.html
@@ -0,0 +1,54 @@
+<?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">
+ <head>
+ <wicket:link>
+ <!--<link href="ExceptionStackTracePanel.css" rel="stylesheet" type="text/css"/>-->
+ </wicket:link>
+ </head>
+ <body>
+ <wicket:panel>
+ <div class="exceptionStackTracePanel">
+ <div class="errorInfo clear">
+ <h4 wicket:id="mainMessage" class="mainMessage">[main message text]</h4>
+ <div class="errorDetail panel panel-default" wicket:id="exceptionDetail">
+ <div class="heading panel-heading"><span class="panel-title">Show detail</span></div>
+ <div class="content panel-body">
+ <div class="exceptionStackTrace">
+ <h3>Stack trace:</h3>
+ <ul>
+ <li wicket:id="stackTraceElement">
+ <span wicket:id="stackTraceElementLine">[stack trace element line]</span>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </wicket:panel>
+ </body>
+</html>
+
+
http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.java
new file mode 100644
index 0000000..7504c47
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.java
@@ -0,0 +1,73 @@
+/*
+ * 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.errors;
+
+import org.apache.wicket.MarkupContainer;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.head.JavaScriptReferenceHeaderItem;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.request.resource.JavaScriptResourceReference;
+import org.apache.isis.viewer.wicket.ui.util.Components;
+
+public class ExceptionStackTracePanel extends Panel {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final String ID_MAIN_MESSAGE = "mainMessage";
+
+ private static final String ID_EXCEPTION_DETAIL = "exceptionDetail";
+
+ private static final String ID_STACK_TRACE_ELEMENT = "stackTraceElement";
+ private static final String ID_LINE = "stackTraceElementLine";
+
+ private static final JavaScriptResourceReference DIV_TOGGLE_JS = new JavaScriptResourceReference(ExceptionStackTracePanel.class, "div-toggle.js");
+
+ public ExceptionStackTracePanel(String id, ExceptionModel exceptionModel) {
+ super(id, exceptionModel);
+
+ final String mainMessage = exceptionModel.getMainMessage();
+ final Label label = new Label(ID_MAIN_MESSAGE, mainMessage);
+
+ // to avoid potential XSS attacks, no longer escape model strings
+ // (risk is low but could just happen: error message being rendered might accidentally or deliberately contain rogue Javascript)
+ // label.setEscapeModelStrings(false);
+
+ add(label);
+
+ final boolean suppressDetail = exceptionModel.isAuthorizationException() || exceptionModel.isRecognized();
+ if(suppressDetail) {
+ Components.permanentlyHide(this, ID_EXCEPTION_DETAIL);
+ } else {
+
+ MarkupContainer container = new WebMarkupContainer(ID_EXCEPTION_DETAIL) {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public void renderHead(IHeaderResponse response) {
+ response.render(JavaScriptReferenceHeaderItem.forReference(DIV_TOGGLE_JS));
+ }
+ };
+ container.add(new StackTraceListView(ID_STACK_TRACE_ELEMENT, ExceptionStackTracePanel.ID_LINE, exceptionModel.getStackTrace()));
+ add(container);
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/JGrowlBehaviour.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/JGrowlBehaviour.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/JGrowlBehaviour.java
new file mode 100644
index 0000000..90084e6
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/JGrowlBehaviour.java
@@ -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.
+ */
+package org.apache.isis.viewer.wicket.ui.errors;
+
+import com.google.common.base.Strings;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.head.JavaScriptHeaderItem;
+import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
+import org.apache.wicket.request.resource.JavaScriptResourceReference;
+
+import org.apache.isis.applib.RecoverableException;
+import org.apache.isis.core.commons.authentication.MessageBroker;
+import org.apache.isis.core.runtime.system.context.IsisContext;
+
+
+/**
+ * Attach to any Ajax button that might trigger a notification (ie calls
+ * {@link MessageBroker#addMessage(String)}, {@link MessageBroker#addWarning(String)},
+ * {@link MessageBroker#setApplicationError(String)} or throws an {@link RecoverableException}).
+ *
+ * <p>
+ * Attach using the standard Wicket code:
+ * <pre>
+ * Button editButton = new AjaxButton(ID_EDIT_BUTTON, Model.of("Edit")) { ... }
+ * editButton.add(new JGrowlBehaviour());
+ * </pre>
+ */
+public class JGrowlBehaviour extends AbstractDefaultAjaxBehavior {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected void respond(AjaxRequestTarget target) {
+ String feedbackMsg = JGrowlUtil.asJGrowlCalls(getMessageBroker());
+ if(!Strings.isNullOrEmpty(feedbackMsg)) {
+ target.appendJavaScript(feedbackMsg);
+ }
+ }
+
+ @Override
+ public void renderHead(Component component, IHeaderResponse response) {
+ super.renderHead(component, response);
+
+ renderFeedbackMessages(response);
+ }
+
+ public void renderFeedbackMessages(IHeaderResponse response) {
+ response.render(JavaScriptHeaderItem.forReference(new JavaScriptResourceReference(JGrowlBehaviour.class, "js/bootstrap-growl.js")));
+
+ String feedbackMsg = JGrowlUtil.asJGrowlCalls(getMessageBroker());
+ if(!Strings.isNullOrEmpty(feedbackMsg)) {
+ response.render(OnDomReadyHeaderItem.forScript(feedbackMsg));
+ }
+ }
+
+
+ protected MessageBroker getMessageBroker() {
+ return IsisContext.getMessageBroker();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/JGrowlUtil.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/JGrowlUtil.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/JGrowlUtil.java
new file mode 100644
index 0000000..686e011
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/JGrowlUtil.java
@@ -0,0 +1,68 @@
+/*
+ * 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.errors;
+
+import org.apache.wicket.util.string.Strings;
+import org.apache.isis.core.commons.authentication.MessageBroker;
+
+public class JGrowlUtil {
+
+ private JGrowlUtil(){}
+
+ public static String asJGrowlCalls(final MessageBroker messageBroker) {
+ final StringBuilder buf = new StringBuilder();
+
+ for (String info : messageBroker.getMessages()) {
+ addJGrowlCall(info, "info", false, buf);
+ }
+ for (String warning : messageBroker.getWarnings()) {
+ addJGrowlCall(warning, "warning", true, buf);
+ }
+
+ final String error = messageBroker.getApplicationError();
+ if(error!=null) {
+ addJGrowlCall(error, "danger", true, buf);
+ }
+ return buf.toString();
+ }
+
+ private static void addJGrowlCall(final String origMsg, final String cssClassSuffix, boolean sticky, final StringBuilder buf) {
+ final CharSequence escapedMsg = escape(origMsg);
+ buf.append("$.growl(\"")
+ .append(escapedMsg)
+ .append("   ") // add some space so that the dismiss icon (x) doesn't overlap with the text
+ .append('"');
+ buf.append(", {");
+ buf.append("type: \"").append(cssClassSuffix).append('"');
+ buf.append(", delay: " + (sticky ? "0" : "2000"));
+ buf.append(", placement: { from: 'top', align: 'right' }");
+ buf.append(", offset: 50");
+ buf.append('}');
+ buf.append(");\n");
+ }
+
+ static String escape(String origMsg) {
+ final String escaped = Strings.escapeMarkup(origMsg).toString();
+
+ // convert (what would originally have been either) ' or " to '
+ return escaped
+ .replace(""", "'")
+ .replace("'", "'");
+ }
+}
http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/StackTraceDetail.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/StackTraceDetail.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/StackTraceDetail.java
new file mode 100644
index 0000000..9f73c4a
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/StackTraceDetail.java
@@ -0,0 +1,78 @@
+/*
+ * 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.errors;
+
+import java.io.Serializable;
+
+public class StackTraceDetail implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ public static StackTraceDetail exceptionClassName(Throwable cause) {
+ return new StackTraceDetail(StackTraceDetail.Type.EXCEPTION_CLASS_NAME, cause.getClass().getName());
+ }
+
+ public static StackTraceDetail exceptionMessage(Throwable cause) {
+ return new StackTraceDetail(StackTraceDetail.Type.EXCEPTION_MESSAGE, cause.getMessage());
+ }
+
+ public static StackTraceDetail element(StackTraceElement el) {
+ StringBuilder buf = new StringBuilder();
+ buf .append(" ")
+ .append(el.getClassName())
+ .append("#")
+ .append(el.getMethodName())
+ .append("(")
+ .append(el.getFileName())
+ .append(":")
+ .append(el.getLineNumber())
+ .append(")\n")
+ ;
+ return new StackTraceDetail(StackTraceDetail.Type.STACKTRACE_ELEMENT, buf.toString());
+ }
+
+ public static StackTraceDetail spacer() {
+ return new StackTraceDetail(Type.LITERAL, "");
+ }
+
+ public static StackTraceDetail causedBy() {
+ return new StackTraceDetail(Type.LITERAL, "Caused by:");
+ }
+
+ enum Type {
+ EXCEPTION_CLASS_NAME,
+ EXCEPTION_MESSAGE,
+ STACKTRACE_ELEMENT,
+ LITERAL
+ }
+ private final Type type;
+ private final String line;
+
+ public StackTraceDetail(Type type, String line) {
+ this.type = type;
+ this.line = line;
+ }
+ public StackTraceDetail.Type getType() {
+ return type;
+ }
+ public String getLine() {
+ return line;
+ }
+
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/StackTraceListView.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/StackTraceListView.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/StackTraceListView.java
new file mode 100644
index 0000000..b703a28
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/StackTraceListView.java
@@ -0,0 +1,45 @@
+/*
+ * 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.errors;
+
+import java.util.List;
+
+import org.apache.wicket.behavior.AttributeAppender;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.list.ListItem;
+import org.apache.wicket.markup.html.list.ListView;
+
+public final class StackTraceListView extends ListView<StackTraceDetail> {
+
+ private static final long serialVersionUID = 1L;
+ private final String idLine;
+
+ public StackTraceListView(String id, String idLine, List<? extends org.apache.isis.viewer.wicket.ui.errors.StackTraceDetail> list) {
+ super(id, list);
+ this.idLine = idLine;
+ }
+
+ @Override
+ protected void populateItem(ListItem<StackTraceDetail> item) {
+ final StackTraceDetail detail = item.getModelObject();
+ Label label = new Label(idLine, detail.getLine());
+ item.add(new AttributeAppender("class", detail.getType().name().toLowerCase()));
+ item.add(label);
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/div-toggle.js
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/div-toggle.js b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/div-toggle.js
new file mode 100644
index 0000000..f5cc913
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/div-toggle.js
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+jQuery(document).ready(function() {
+ jQuery(".exceptionStackTracePanel .content").hide();
+ jQuery(".exceptionStackTracePanel .heading").click(function()
+ {
+ jQuery(this).next(".exceptionStackTracePanel .content").slideToggle(500);
+ });
+});
http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/js/bootstrap-growl.js
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/js/bootstrap-growl.js b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/js/bootstrap-growl.js
new file mode 100644
index 0000000..ec3012e
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/js/bootstrap-growl.js
@@ -0,0 +1,312 @@
+/*
+* Project: Bootstrap Growl - v2.0.0
+* Description: Turns standard Bootstrap alerts into "Growl-like" notifications.
+* Author: Mouse0270 aka Robert McIntosh
+* License: MIT License
+* Website: https://github.com/mouse0270/bootstrap-growl
+*/
+;(function ( $, window, document, undefined ) {
+ // Create the defaults once
+ var pluginName = "growl",
+ dataKey = "plugin_" + pluginName,
+ defaults = {
+ element: 'body',
+ type: "info",
+ allow_dismiss: true,
+ placement: {
+ from: "top",
+ align: "right"
+ },
+ offset: 20,
+ spacing: 10,
+ z_index: 1031,
+ delay: 5000,
+ timer: 1000,
+ url_target: '_blank',
+ mouse_over: false,
+ animate: {
+ enter: 'animated fadeInDown',
+ exit: 'animated fadeOutUp'
+ },
+ onShow: null,
+ onShown: null,
+ onHide: null,
+ onHidden: null,
+ icon_type: 'class',
+ template: '<div data-growl="container" class="alert" role="alert"><button type="button" class="close" data-growl="dismiss"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button><span data-growl="icon"></span><span data-growl="title"></span><span data-growl="message"></span><a href="#" data-growl="url"></a></div>'
+ };
+
+ // The actual plugin constructor
+ var setDefaults = function(element, options) {
+ defaults = $.extend(true, {}, defaults, options);
+ },
+ closeAll = function(options) {
+ if (!options) {
+ $('[data-growl="container"]').find('[data-growl="dismiss"]').trigger('click');
+ }else{
+ $('[data-growl="container"][data-growl-position="'+options+'"]').find('[data-growl="dismiss"]').trigger('click');
+ }
+ },
+ Plugin = function (element, content, options) {
+ var content = {
+ content: {
+ message: typeof content == 'object' ? content.message : content,
+ title: content.title ? content.title : null,
+ icon: content.icon ? content.icon : null,
+ url: content.url ? content.url : null
+ }
+ };
+
+ options = $.extend(true, {}, content, options);
+ this.settings = $.extend(true, {}, defaults, options);
+ plugin = this;
+ init(options, this.settings, plugin);
+ this.$template = $template;
+ },
+ init = function (options, settings, plugin) {
+
+ var base = {
+ settings: settings,
+ $element: $(settings.element),
+ template: settings.template
+ };
+
+ if (typeof settings.offset == 'number') {
+ settings.offset = {
+ x: settings.offset,
+ y: settings.offset
+ };
+ }
+
+ $template = buildGrowl(base);
+ addContent($template, base.settings);
+ placement($template, base.settings);
+ bindControls($template, base.settings,plugin);
+ },
+ buildGrowl = function(base) {
+
+ var $template = $(base.settings.template);
+
+ $template.addClass('alert-' + base.settings.type);
+ $template.attr('data-growl-position', base.settings.placement.from + '-' + base.settings.placement.align);
+
+ $template.find('[data-growl="dismiss"]').css('display', 'none');
+ if (base.settings.allow_dismiss) {
+ $template.find('[data-growl="dismiss"]').css('display', 'inline-block');
+ }
+
+ return $template;
+ },
+ addContent = function($template, settings) {
+
+ $template.find('[data-growl="dismiss"]').css({
+ 'position': 'absolute',
+ 'top': '5px',
+ 'right': '10px',
+ 'z-index': ((settings.z_index-1) >= 1 ? (settings.z_index-1) : 1)
+ });
+
+ if (settings.content.icon) {
+ if (settings.icon_type.toLowerCase() == 'class') {
+ $template.find('[data-growl="icon"]').addClass(settings.content.icon);
+ }else{
+ if ($template.find('[data-growl="icon"]').is('img')) {
+ $template.find('[data-growl="icon"]').attr('src', settings.content.icon);
+ }else{
+ $template.find('[data-growl="icon"]').append('<img src="'+settings.content.icon+'" />');
+ }
+ }
+ }
+
+ if (settings.content.title) {
+ $template.find('[data-growl="title"]').html(settings.content.title);
+ }
+
+ if (settings.content.message) {
+ $template.find('[data-growl="message"]').html(settings.content.message);
+ }
+
+ if (settings.content.url) {
+ $template.find('[data-growl="url"]').attr('href', settings.content.url).attr('target', settings.url_target);
+ $template.find('[data-growl="url"]').css({
+ 'position': 'absolute',
+ 'top': '0px',
+ 'left': '0px',
+ 'width': '100%',
+ 'height': '100%',
+ 'z-index': ((settings.z_index-2) >= 1 ? (settings.z_index-2) : 1)
+ });
+ }
+ },
+ placement = function($template, settings) {
+ var offsetAmt = settings.offset.y,
+ gCSS = {
+ 'position': (settings.element === 'body' ? 'fixed' : 'absolute'),
+ 'margin': 0,
+ 'z-index': settings.z_index,
+ 'display': 'inline-block'
+ },
+ hasAnimation = false;
+
+ $('[data-growl-position="' + settings.placement.from + '-' + settings.placement.align + '"]').each(function() {
+ return offsetAmt = Math.max(offsetAmt, parseInt($(this).css(settings.placement.from)) + $(this).outerHeight() + settings.spacing);
+ });
+
+ gCSS[settings.placement.from] = offsetAmt + "px";
+ $template.css(gCSS);
+
+ if (settings.onShow) {
+ settings.onShow(event);
+ }
+
+ $(settings.element).append($template);
+
+ switch (settings.placement.align) {
+ case 'center':
+ $template.css({
+ 'left': '50%',
+ 'marginLeft': -($template.outerWidth() / 2) + 'px'
+ });
+ break;
+ case 'left':
+ $template.css('left', settings.offset.x + 'px');
+ break;
+ case 'right':
+ $template.css('right', settings.offset.x + 'px');
+ break;
+ }
+ $template.addClass('growl-animated');
+
+ $template.one('webkitAnimationStart oanimationstart MSAnimationStart animationstart', function(event) {
+ hasAnimation = true;
+ });
+
+ $template.one('webkitAnimationEnd oanimationend MSAnimationEnd animationend', function(event) {
+ if (settings.onShown) {
+ settings.onShown(event);
+ }
+ });
+
+ setTimeout(function() {
+ if (!hasAnimation) {
+ if (settings.onShown) {
+ settings.onShown(event);
+ }
+ }
+ }, 600);
+ },
+ bindControls = function($template, settings, plugin) {
+ $template.addClass(settings.animate.enter);
+
+ $template.find('[data-growl="dismiss"]').on('click', function() {
+ plugin.close();
+ });
+
+ $template.on('mouseover', function(e) {
+ $template.addClass('hovering');
+ }).on('mouseout', function() {
+ $template.removeClass('hovering');
+ });
+
+ if (settings.delay >= 1) {
+ $template.data('growl-delay', settings.delay);
+ var timer = setInterval(function() {
+
+ var delay = parseInt($template.data('growl-delay')) - settings.timer;
+ if ((!$template.hasClass('hovering') && settings.mouse_over == 'pause') || settings.mouse_over != 'pause') {
+ $template.data('growl-delay', delay);
+ }
+
+ if (delay <= 0) {
+ clearInterval(timer);
+ plugin.close();
+ }
+ }, settings.timer);
+ }
+ };
+
+ // Avoid Plugin.prototype conflicts
+ Plugin.prototype = {
+ update: function(command, update) {
+ switch (command) {
+ case 'icon':
+ if (this.settings.icon_type.toLowerCase() == 'class') {
+ this.$template.find('[data-growl="icon"]').removeClass(this.settings.content.icon);
+ this.$template.find('[data-growl="icon"]').addClass(update);
+ }else{
+ if (this.$template.find('[data-growl="icon"]').is('img')) {
+ this.$template.find('[data-growl="icon"]')
+ }else{
+ this.$template.find('[data-growl="icon"]').find('img').attr().attr('src', update);
+ }
+ }
+ break;
+ case 'url':
+ this.$template.find('[data-growl="url"]').attr('href', update);
+ break;
+ case 'type':
+ this.$template.removeClass('alert-' + this.settings.type);
+ this.$template.addClass('alert-' + update);
+ break;
+ default:
+ this.$template.find('[data-growl="' + command +'"]').html(update);
+ }
+
+ return this;
+ },
+ close: function() {
+ var base = this.$template,
+ settings = this.settings,
+ posX = base.css(settings.placement.from),
+ hasAnimation = false;
+
+ if (settings.onHide) {
+ settings.onHide(event);
+ }
+
+ base.addClass(this.settings.animate.exit);
+
+ base.nextAll('[data-growl-position="' + this.settings.placement.from + '-' + this.settings.placement.align + '"]').each(function() {
+ $(this).css(settings.placement.from, posX);
+ posX = (parseInt(posX)+(settings.spacing)) + $(this).outerHeight();
+ });
+
+ base.one('webkitAnimationStart oanimationstart MSAnimationStart animationstart', function(event) {
+ hasAnimation = true;
+ });
+
+ base.one('webkitAnimationEnd oanimationend MSAnimationEnd animationend', function(event) {
+ $(this).remove();
+ if (settings.onHidden) {
+ settings.onHidden(event);
+ }
+ });
+
+ setTimeout(function() {
+ if (!hasAnimation) {
+ base.remove();
+ if (settings.onHidden) {
+ settings.onHidden(event);
+ }
+ }
+ }, 100);
+
+ return this;
+ }
+ };
+
+ // A really lightweight plugin wrapper around the constructor,
+ // preventing against multiple instantiations
+ $.growl = function ( content, options ) {
+ if (content == false && options.command == "closeAll") {
+ closeAll(options.position);
+ return false;
+ }else if (content == false) {
+ setDefaults(this, options);
+ return false;
+ }
+ var plugin = new Plugin( this, content, options );
+ return plugin;
+ };
+
+})( jQuery, window, document );
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/js/bootstrap-growl.min.js
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/js/bootstrap-growl.min.js b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/js/bootstrap-growl.min.js
new file mode 100644
index 0000000..ad059cc
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/js/bootstrap-growl.min.js
@@ -0,0 +1,2 @@
+/* Project: Bootstrap Growl - v2.0.0 | Author: Mouse0270 aka Robert McIntosh | License: MIT License | Website: https://github.com/mouse0270/bootstrap-growl */
+(function(e,t,n,r){var i="growl",s="plugin_"+i,o={element:"body",type:"info",allow_dismiss:true,placement:{from:"top",align:"right"},offset:20,spacing:10,z_index:1031,delay:5e3,timer:1e3,url_target:"_blank",mouse_over:false,animate:{enter:"animated fadeInDown",exit:"animated fadeOutUp"},onShow:null,onShown:null,onHide:null,onHidden:null,icon_type:"class",template:'<div data-growl="container" class="alert" role="alert"><button type="button" class="close" data-growl="dismiss"><span aria-hidden="true">×</span><span class="sr-only">Close</span></button><span data-growl="icon"></span><span data-growl="title"></span><span data-growl="message"></span><a href="#" data-growl="url"></a></div>'};var u=function(t,n){o=e.extend(true,{},o,n)},a=function(t){if(!t){e('[data-growl="container"]').find('[data-growl="dismiss"]').trigger("click")}else{e('[data-growl="container"][data-growl-position="'+t+'"]').find('[data-growl="dismiss"]').trigger("click")}},f=function(t,n,r){var n={content:{messa
ge:typeof n=="object"?n.message:n,title:n.title?n.title:null,icon:n.icon?n.icon:null,url:n.url?n.url:null}};r=e.extend(true,{},n,r);this.settings=e.extend(true,{},o,r);plugin=this;l(r,this.settings,plugin);this.$template=$template},l=function(t,n,r){var i={settings:n,$element:e(n.element),template:n.template};if(typeof n.offset=="number"){n.offset={x:n.offset,y:n.offset}}$template=c(i);h($template,i.settings);p($template,i.settings);d($template,i.settings,r)},c=function(t){var n=e(t.settings.template);n.addClass("alert-"+t.settings.type);n.attr("data-growl-position",t.settings.placement.from+"-"+t.settings.placement.align);n.find('[data-growl="dismiss"]').css("display","none");if(t.settings.allow_dismiss){n.find('[data-growl="dismiss"]').css("display","inline-block")}return n},h=function(e,t){e.find('[data-growl="dismiss"]').css({position:"absolute",top:"5px",right:"10px","z-index":t.z_index-1>=1?t.z_index-1:1});if(t.content.icon){if(t.icon_type.toLowerCase()=="class"){e.find('[data
-growl="icon"]').addClass(t.content.icon)}else{if(e.find('[data-growl="icon"]').is("img")){e.find('[data-growl="icon"]').attr("src",t.content.icon)}else{e.find('[data-growl="icon"]').append('<img src="'+t.content.icon+'" />')}}}if(t.content.title){e.find('[data-growl="title"]').html(t.content.title)}if(t.content.message){e.find('[data-growl="message"]').html(t.content.message)}if(t.content.url){e.find('[data-growl="url"]').attr("href",t.content.url).attr("target",t.url_target);e.find('[data-growl="url"]').css({position:"absolute",top:"0px",left:"0px",width:"100%",height:"100%","z-index":t.z_index-2>=1?t.z_index-2:1})}},p=function(t,n){var r=n.offset.y,i={position:n.element==="body"?"fixed":"absolute",margin:0,"z-index":n.z_index,display:"inline-block"},s=false;e('[data-growl-position="'+n.placement.from+"-"+n.placement.align+'"]').each(function(){return r=Math.max(r,parseInt(e(this).css(n.placement.from))+e(this).outerHeight()+n.spacing)});i[n.placement.from]=r+"px";t.css(i);if(n.on
Show){n.onShow(event)}e(n.element).append(t);switch(n.placement.align){case"center":t.css({left:"50%",marginLeft:-(t.outerWidth()/2)+"px"});break;case"left":t.css("left",n.offset.x+"px");break;case"right":t.css("right",n.offset.x+"px");break}t.addClass("growl-animated");t.one("webkitAnimationStart oanimationstart MSAnimationStart animationstart",function(e){s=true});t.one("webkitAnimationEnd oanimationend MSAnimationEnd animationend",function(e){if(n.onShown){n.onShown(e)}});setTimeout(function(){if(!s){if(n.onShown){n.onShown(event)}}},600)},d=function(e,t,n){e.addClass(t.animate.enter);e.find('[data-growl="dismiss"]').on("click",function(){n.close()});e.on("mouseover",function(t){e.addClass("hovering")}).on("mouseout",function(){e.removeClass("hovering")});if(t.delay>=1){e.data("growl-delay",t.delay);var r=setInterval(function(){var i=parseInt(e.data("growl-delay"))-t.timer;if(!e.hasClass("hovering")&&t.mouse_over=="pause"||t.mouse_over!="pause"){e.data("growl-delay",i)}if(i<=0){c
learInterval(r);n.close()}},t.timer)}};f.prototype={update:function(e,t){switch(e){case"icon":if(this.settings.icon_type.toLowerCase()=="class"){this.$template.find('[data-growl="icon"]').removeClass(this.settings.content.icon);this.$template.find('[data-growl="icon"]').addClass(t)}else{if(this.$template.find('[data-growl="icon"]').is("img")){this.$template.find('[data-growl="icon"]')}else{this.$template.find('[data-growl="icon"]').find("img").attr().attr("src",t)}}break;case"url":this.$template.find('[data-growl="url"]').attr("href",t);break;case"type":this.$template.removeClass("alert-"+this.settings.type);this.$template.addClass("alert-"+t);break;default:this.$template.find('[data-growl="'+e+'"]').html(t)}return this},close:function(){var t=this.$template,n=this.settings,r=t.css(n.placement.from),i=false;if(n.onHide){n.onHide(event)}t.addClass(this.settings.animate.exit);t.nextAll('[data-growl-position="'+this.settings.placement.from+"-"+this.settings.placement.align+'"]').each(f
unction(){e(this).css(n.placement.from,r);r=parseInt(r)+n.spacing+e(this).outerHeight()});t.one("webkitAnimationStart oanimationstart MSAnimationStart animationstart",function(e){i=true});t.one("webkitAnimationEnd oanimationend MSAnimationEnd animationend",function(t){e(this).remove();if(n.onHidden){n.onHidden(t)}});setTimeout(function(){if(!i){t.remove();if(n.onHidden){n.onHidden(event)}}},100);return this}};e.growl=function(e,t){if(e==false&&t.command=="closeAll"){a(t.position);return false}else if(e==false){u(this,t);return false}var n=new f(this,e,t);return n}})(jQuery,window,document)
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/BookmarkedPagesModelProvider.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/BookmarkedPagesModelProvider.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/BookmarkedPagesModelProvider.java
new file mode 100644
index 0000000..f4051ea
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/BookmarkedPagesModelProvider.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+import org.apache.isis.viewer.wicket.model.models.BookmarkedPagesModel;
+
+
+
+public interface BookmarkedPagesModelProvider {
+
+ BookmarkedPagesModel getBookmarkedPagesModel();
+
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/BootstrapOverridesCssResourceReference.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/BootstrapOverridesCssResourceReference.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/BootstrapOverridesCssResourceReference.java
new file mode 100644
index 0000000..f5c9157
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/BootstrapOverridesCssResourceReference.java
@@ -0,0 +1,33 @@
+/*
+ * 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;
+
+import org.apache.wicket.request.resource.CssResourceReference;
+
+/**
+ * A CSS resource reference that provides CSS rules which override the CSS rules
+ * provided by the currently active Bootstrap theme.
+ * Usually the overrides rules are about sizes and weights, but should not change any colors
+ */
+public class BootstrapOverridesCssResourceReference extends CssResourceReference {
+
+ public BootstrapOverridesCssResourceReference() {
+ super(BootstrapOverridesCssResourceReference.class, "bootstrap-overrides.css");
+ }
+}
http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/EmailVerificationUrlService.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/EmailVerificationUrlService.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/EmailVerificationUrlService.java
new file mode 100644
index 0000000..71c3b2c
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/EmailVerificationUrlService.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+import java.io.Serializable;
+import org.apache.wicket.Page;
+import org.apache.isis.viewer.wicket.model.models.PageType;
+
+/**
+ * A Wicket specific service that may be used to create a link to a
+ * page by {@link org.apache.isis.viewer.wicket.model.models.PageType page type}
+ * with encoded/encrypted datum as first indexed parameter in the url for
+ * mail verification purposes.
+ */
+public interface EmailVerificationUrlService extends Serializable {
+
+ /**
+ * Creates a url to the passed <em>pageType</em> by encrypting the given
+ * <em>datum</em> as a first indexed parameter
+ *
+ * @param pageType The type of the page to link to
+ * @param datum The data to encrypt in the url
+ * @return The full url to the page with the encrypted data
+ */
+ String createVerificationUrl(PageType pageType, String datum);
+
+ /**
+ * Creates a url to the passed <em>pageClass</em> by encrypting the given
+ * <em>datum</em> as a first indexed parameter
+ *
+ * @param pageClass The class of the page to link to
+ * @param datum The data to encrypt in the url
+ * @return The full url to the page with the encrypted data
+ */
+ String createVerificationUrl(final Class<? extends Page> pageClass, final String datum);
+}
http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.html
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.html b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.html
new file mode 100644
index 0000000..cf8a91f
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.html
@@ -0,0 +1,64 @@
+<?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"
+ xml:lang="en"
+ lang="en">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-COMPATIBLE" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=no">
+ <title wicket:id="pageTitle"></title>
+ <link rel="icon" wicket:id="favicon">
+ <wicket:header-items/>
+ </head>
+ <body>
+ <div id="container" class="page container-fluid">
+ <div wicket:id="theme">
+
+ <wicket:container wicket:id="header"></wicket:container>
+
+ <div class="clear"/>
+
+ <div id="body">
+
+ <wicket:child/>
+
+ <div class="clearfix" style="margin-bottom: 100px"></div>
+ </div>
+
+ <div wicket:id="actionPromptModalWindow"></div>
+
+ <wicket:container wicket:id="footer"></wicket:container>
+
+ </div>
+
+ </div>
+ <div class="javascriptInFooter">
+ <wicket:container wicket:id="footerJS"/>
+ </div>
+ <div id="veil">
+ <div class="text-center">
+ <i class="fa fa-spinner fa-spin fa-5x"></i>
+ </div>
+ </div>
+ </body>
+</html>
http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.java
new file mode 100644
index 0000000..daf886e
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.java
@@ -0,0 +1,450 @@
+/*
+ * 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;
+
+import de.agilecoders.wicket.core.Bootstrap;
+import de.agilecoders.wicket.core.markup.html.references.BootlintJavaScriptReference;
+import de.agilecoders.wicket.core.settings.IBootstrapSettings;
+import de.agilecoders.wicket.core.settings.ITheme;
+import de.agilecoders.wicket.extensions.markup.html.bootstrap.icon.FontAwesomeCssReference;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+import org.apache.wicket.Component;
+import org.apache.wicket.MarkupContainer;
+import org.apache.wicket.Page;
+import org.apache.wicket.RestartResponseAtInterceptPageException;
+import org.apache.wicket.behavior.Behavior;
+import org.apache.wicket.event.Broadcast;
+import org.apache.wicket.markup.head.CssHeaderItem;
+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.PriorityHeaderItem;
+import org.apache.wicket.markup.head.filter.HeaderResponseContainer;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.protocol.http.ClientProperties;
+import org.apache.wicket.protocol.http.WebSession;
+import org.apache.wicket.protocol.http.request.WebClientInfo;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.request.resource.CssResourceReference;
+import org.apache.wicket.request.resource.JavaScriptResourceReference;
+import org.apache.wicket.request.resource.PackageResource;
+import org.apache.wicket.request.resource.ResourceReference;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.isis.applib.services.exceprecog.ExceptionRecognizer;
+import org.apache.isis.applib.services.exceprecog.ExceptionRecognizerComposite;
+import org.apache.isis.core.commons.authentication.AuthenticationSession;
+import org.apache.isis.core.commons.authentication.MessageBroker;
+import org.apache.isis.core.commons.config.IsisConfiguration;
+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.common.PageParametersUtils;
+import org.apache.isis.viewer.wicket.model.hints.IsisEnvelopeEvent;
+import org.apache.isis.viewer.wicket.model.hints.IsisEventLetterAbstract;
+import org.apache.isis.viewer.wicket.model.models.ActionPrompt;
+import org.apache.isis.viewer.wicket.model.models.ActionPromptProvider;
+import org.apache.isis.viewer.wicket.model.models.BookmarkableModel;
+import org.apache.isis.viewer.wicket.model.models.BookmarkedPagesModel;
+import org.apache.isis.viewer.wicket.model.models.EntityModel;
+import org.apache.isis.viewer.wicket.model.models.PageType;
+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.components.actionprompt.ActionPromptModalWindow;
+import org.apache.isis.viewer.wicket.ui.components.widgets.favicon.Favicon;
+import org.apache.isis.viewer.wicket.ui.errors.ExceptionModel;
+import org.apache.isis.viewer.wicket.ui.errors.JGrowlBehaviour;
+import org.apache.isis.viewer.wicket.ui.util.CssClassAppender;
+
+/**
+ * Convenience adapter for {@link WebPage}s built up using {@link ComponentType}s.
+ */
+public abstract class PageAbstract extends WebPage implements ActionPromptProvider {
+
+ private static Logger LOG = LoggerFactory.getLogger(PageAbstract.class);
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * @see <a href="http://github.com/brandonaaron/livequery">livequery</a>
+ */
+ private static final JavaScriptResourceReference JQUERY_LIVEQUERY_JS = new JavaScriptResourceReference(PageAbstract.class, "jquery.livequery.js");
+ private static final JavaScriptResourceReference JQUERY_ISIS_WICKET_VIEWER_JS = new JavaScriptResourceReference(PageAbstract.class, "jquery.isis.wicket.viewer.js");
+
+ // not to be confused with the bootstrap theme...
+ // is simply a CSS class derived from the application's name
+ private static final String ID_THEME = "theme";
+
+ private static final String ID_BOOKMARKED_PAGES = "bookmarks";
+
+ private static final String ID_ACTION_PROMPT_MODAL_WINDOW = "actionPromptModalWindow";
+
+ private static final String ID_PAGE_TITLE = "pageTitle";
+
+ private static final String ID_FAVICON = "favicon";
+
+ public static final String ID_MENU_LINK = "menuLink";
+
+ /**
+ * This is a bit hacky, but best way I've found to pass an exception over to the WicketSignInPage
+ * if there is a problem rendering this page.
+ */
+ public static ThreadLocal<ExceptionModel> EXCEPTION = new ThreadLocal<>();
+
+ private final List<ComponentType> childComponentIds;
+
+ /**
+ * {@link Inject}ed when {@link #init() initialized}.
+ */
+ @Inject
+ @Named("applicationName")
+ private String applicationName;
+
+ /**
+ * {@link Inject}ed when {@link #init() initialized}.
+ */
+ @Inject(optional = true)
+ @Named("applicationCss")
+ private String applicationCss;
+
+ /**
+ * {@link Inject}ed when {@link #init() initialized}.
+ *///
+ @Inject(optional = true)
+ @Named("applicationJs")
+ private String applicationJs;
+
+ /**
+ * {@link Inject}ed when {@link #init() initialized}.
+ */
+ @Inject
+ private PageClassRegistry pageClassRegistry;
+
+ /**
+ * Top-level <div> to which all content is added.
+ *
+ * <p>
+ * Has <code>protected</code> visibility so that subclasses can also add directly to this div.
+ * </p>
+ */
+ protected MarkupContainer themeDiv;
+
+ public PageAbstract(
+ final PageParameters pageParameters,
+ final String title,
+ final ComponentType... childComponentIds) {
+ super(pageParameters);
+
+ try {
+ // for breadcrumbs support
+ getSession().bind();
+
+ setTitle(title);
+
+ add(new Favicon(ID_FAVICON));
+
+ themeDiv = new WebMarkupContainer(ID_THEME);
+ add(themeDiv);
+ if(applicationName != null) {
+ themeDiv.add(new CssClassAppender(CssClassAppender.asCssStyle(applicationName)));
+ }
+
+ MarkupContainer header = createPageHeader("header");
+ themeDiv.add(header);
+
+ MarkupContainer footer = createPageFooter("footer");
+ themeDiv.add(footer);
+
+ addActionPromptModalWindow(themeDiv);
+
+ this.childComponentIds = Collections.unmodifiableList(Arrays.asList(childComponentIds));
+
+ // ensure that all collected JavaScript contributions are loaded at the page footer
+ add(new HeaderResponseContainer("footerJS", "footerJS"));
+
+ } catch(final RuntimeException ex) {
+
+ LOG.error("Failed to construct page, going back to sign in page", ex);
+
+ // REVIEW: similar code in WebRequestCycleForIsis
+ final List<ExceptionRecognizer> exceptionRecognizers = getServicesInjector().lookupServices(ExceptionRecognizer.class);
+ final String recognizedMessageIfAny = new ExceptionRecognizerComposite(exceptionRecognizers).recognize(ex);
+ final ExceptionModel exceptionModel = ExceptionModel.create(recognizedMessageIfAny, ex);
+
+ getSession().invalidate();
+ getSession().clear();
+
+ // for the WicketSignInPage to render
+ EXCEPTION.set(exceptionModel);
+
+ throw new RestartResponseAtInterceptPageException(getSignInPage());
+ }
+ }
+
+ /**
+ * Creates the component that should be used as a page header/navigation bar
+ *
+ * @param id The component id
+ * @return The container that should be used as a page header/navigation bar
+ */
+ protected MarkupContainer createPageHeader(final String id) {
+ Component header = getComponentFactoryRegistry().createComponent(ComponentType.HEADER, id, null);
+ return (MarkupContainer) header;
+ }
+
+ /**
+ * Creates the component that should be used as a page header/navigation bar
+ *
+ * @param id The component id
+ * @return The container that should be used as a page header/navigation bar
+ */
+ protected MarkupContainer createPageFooter(final String id) {
+ Component footer = getComponentFactoryRegistry().createComponent(ComponentType.FOOTER, id, null);
+ return (MarkupContainer) footer;
+ }
+
+
+ protected void setTitle(final String title) {
+ addOrReplace(new Label(ID_PAGE_TITLE, title != null? title: applicationName));
+ }
+
+ private Class<? extends Page> getSignInPage() {
+ return pageClassRegistry.getPageClass(PageType.SIGN_IN);
+ }
+
+ @Override
+ public void renderHead(final IHeaderResponse response) {
+
+ super.renderHead(response);
+
+ response.render(new PriorityHeaderItem(JavaScriptHeaderItem.forReference(getApplication().getJavaScriptLibrarySettings().getJQueryReference())));
+ response.render(CssHeaderItem.forReference(FontAwesomeCssReference.instance()));
+ response.render(CssHeaderItem.forReference(new BootstrapOverridesCssResourceReference()));
+ contributeThemeSpecificOverrides(response);
+
+ response.render(JavaScriptReferenceHeaderItem.forReference(JQUERY_LIVEQUERY_JS));
+ response.render(JavaScriptReferenceHeaderItem.forReference(JQUERY_ISIS_WICKET_VIEWER_JS));
+
+ final JGrowlBehaviour jGrowlBehaviour = new JGrowlBehaviour();
+ jGrowlBehaviour.renderFeedbackMessages(response);
+
+ if(applicationCss != null) {
+ response.render(CssReferenceHeaderItem.forUrl(applicationCss));
+ }
+ if(applicationJs != null) {
+ response.render(JavaScriptReferenceHeaderItem.forUrl(applicationJs));
+ }
+
+ if(isModernBrowser()) {
+ addBootLint(response);
+ }
+ }
+
+ private void addBootLint(final IHeaderResponse response) {
+ response.render(JavaScriptHeaderItem.forReference(BootlintJavaScriptReference.INSTANCE));
+ }
+
+ private boolean isModernBrowser() {
+ return !isIePre9();
+ }
+
+ private boolean isIePre9() {
+ final WebClientInfo clientInfo = WebSession.get().getClientInfo();
+ final ClientProperties properties = clientInfo.getProperties();
+ if (properties.isBrowserInternetExplorer())
+ if (properties.getBrowserVersionMajor() < 9)
+ return true;
+ return false;
+ }
+
+ /**
+ * Contributes theme specific Bootstrap CSS overrides if there is such resource
+ *
+ * @param response The header response to contribute to
+ */
+ private void contributeThemeSpecificOverrides(final IHeaderResponse response) {
+ final IBootstrapSettings bootstrapSettings = Bootstrap.getSettings(getApplication());
+ final ITheme activeTheme = bootstrapSettings.getActiveThemeProvider().getActiveTheme();
+ final String name = activeTheme.name().toLowerCase(Locale.ENGLISH);
+ final String themeSpecificOverride = "bootstrap-overrides-" + name + ".css";
+ final ResourceReference.Key themeSpecificOverrideKey = new ResourceReference.Key(PageAbstract.class.getName(), themeSpecificOverride, null, null, null);
+ if (PackageResource.exists(themeSpecificOverrideKey)) {
+ response.render(CssHeaderItem.forReference(new CssResourceReference(themeSpecificOverrideKey)));
+ }
+ }
+
+ /**
+ * As provided in the {@link #PageAbstract(org.apache.wicket.request.mapper.parameter.PageParameters, String, org.apache.isis.viewer.wicket.ui.ComponentType...)} constructor}.
+ *
+ * <p>
+ * This superclass doesn't do anything with this property directly, but
+ * requiring it to be provided enforces standardization of the
+ * implementation of the subclasses.
+ */
+ public List<ComponentType> getChildModelTypes() {
+ return childComponentIds;
+ }
+
+ /**
+ * For subclasses to call.
+ *
+ * <p>
+ * Should be called in the subclass' constructor.
+ *
+ * @param model
+ * - used to find the best matching {@link ComponentFactory} to
+ * render the model.
+ */
+ protected void addChildComponents(final MarkupContainer container, final IModel<?> model) {
+ for (final ComponentType componentType : getChildModelTypes()) {
+ addComponent(container, componentType, model);
+ }
+ }
+
+ private void addComponent(final MarkupContainer container, final ComponentType componentType, final IModel<?> model) {
+ getComponentFactoryRegistry().addOrReplaceComponent(container, componentType, model);
+ }
+
+
+ ////////////////////////////////////////////////////////////////
+ // bookmarked pages
+ ////////////////////////////////////////////////////////////////
+
+ /**
+ * Convenience for subclasses
+ */
+ protected void addBookmarkedPages(final MarkupContainer container) {
+ Component bookmarks = getComponentFactoryRegistry().createComponent(ComponentType.BOOKMARKED_PAGES, ID_BOOKMARKED_PAGES, getBookmarkedPagesModel());
+ container.add(bookmarks);
+ bookmarks.add(new Behavior() {
+ @Override
+ public void onConfigure(Component component) {
+ super.onConfigure(component);
+
+ PageParameters parameters = getPageParameters();
+ component.setVisible(parameters.get(PageParametersUtils.ISIS_NO_HEADER_PARAMETER_NAME).isNull());
+ }
+ });
+ }
+
+ protected void bookmarkPage(final BookmarkableModel<?> model) {
+ getBookmarkedPagesModel().bookmarkPage(model);
+ }
+
+ protected void removeAnyBookmark(final EntityModel model) {
+ getBookmarkedPagesModel().remove(model);
+ }
+
+ private BookmarkedPagesModel getBookmarkedPagesModel() {
+ final BookmarkedPagesModelProvider session = (BookmarkedPagesModelProvider) getSession();
+ return session.getBookmarkedPagesModel();
+ }
+
+
+
+ // ///////////////////////////////////////////////////////////////////
+ // ActionPromptModalWindowProvider
+ // ///////////////////////////////////////////////////////////////////
+
+ private ActionPromptModalWindow actionPromptModalWindow;
+
+ public ActionPrompt getActionPrompt() {
+ return ActionPromptModalWindow.getActionPromptModalWindowIfEnabled(actionPromptModalWindow);
+ }
+
+ private void addActionPromptModalWindow(final MarkupContainer parent) {
+ actionPromptModalWindow = ActionPromptModalWindow.newModalWindow(ID_ACTION_PROMPT_MODAL_WINDOW);
+ parent.addOrReplace(actionPromptModalWindow);
+ }
+
+
+ // ///////////////////////////////////////////////////////////////////
+ // UI Hint
+ // ///////////////////////////////////////////////////////////////////
+
+ /**
+ * Propagates all {@link org.apache.isis.viewer.wicket.model.hints.IsisEventLetterAbstract letter} events down to
+ * all child components, wrapped in an {@link org.apache.isis.viewer.wicket.model.hints.IsisEnvelopeEvent envelope} event.
+ */
+ public void onEvent(final org.apache.wicket.event.IEvent<?> event) {
+ final Object payload = event.getPayload();
+ if(payload instanceof IsisEventLetterAbstract) {
+ final IsisEventLetterAbstract letter = (IsisEventLetterAbstract)payload;
+ final IsisEnvelopeEvent broadcastEv = new IsisEnvelopeEvent(letter);
+ send(this, Broadcast.BREADTH, broadcastEv);
+ }
+ }
+
+
+ // ///////////////////////////////////////////////////////////////////
+ // Convenience
+ // ///////////////////////////////////////////////////////////////////
+
+ protected ComponentFactoryRegistry getComponentFactoryRegistry() {
+ final ComponentFactoryRegistryAccessor cfra = (ComponentFactoryRegistryAccessor) getApplication();
+ return cfra.getComponentFactoryRegistry();
+ }
+
+ protected <T> T lookupService(final Class<T> serviceClass) {
+ return getServicesInjector().lookupService(serviceClass);
+ }
+
+ // ///////////////////////////////////////////////////
+ // System components
+ // ///////////////////////////////////////////////////
+
+ protected ServicesInjectorSpi getServicesInjector() {
+ return getPersistenceSession().getServicesInjector();
+ }
+
+ protected PersistenceSession getPersistenceSession() {
+ return IsisContext.getPersistenceSession();
+ }
+
+ protected SpecificationLoaderSpi getSpecificationLoader() {
+ return IsisContext.getSpecificationLoader();
+ }
+
+ protected AuthenticationSession getAuthenticationSession() {
+ return IsisContext.getAuthenticationSession();
+ }
+
+ protected MessageBroker getMessageBroker() {
+ return IsisContext.getMessageBroker();
+ }
+
+ protected IsisConfiguration getConfiguration() {
+ return IsisContext.getConfiguration();
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.properties
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.properties b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.properties
new file mode 100644
index 0000000..488c223
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageAbstract.properties
@@ -0,0 +1,21 @@
+#
+# 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.
+#
+
+aboutLabel=About
+logoutLabel=Logout
http://git-wip-us.apache.org/repos/asf/isis/blob/99094b7e/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageClassList.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageClassList.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageClassList.java
new file mode 100644
index 0000000..0e0b459
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/PageClassList.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+import java.io.Serializable;
+
+import org.apache.isis.viewer.wicket.model.models.PageType;
+
+/**
+ * Specify the pages to use for each {@link PageType}.
+ *
+ * <p>
+ * The <tt>PageClassListDefault</tt> default implementation returns pages that
+ * all inherit from {@link PageAbstract} and which (by component markup
+ * inheritance) therefore define a set of CSS, along with the overall layout of
+ * each pages. This interface can be used to selectively replace some or all of
+ * these pages.
+ */
+public interface PageClassList extends Serializable {
+
+ void registerPages(PageClassRegistrySpi pageRegistry);
+
+}