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:29 UTC
[38/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/entity/icontitle/EntityIconAndTitlePanel.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/icontitle/EntityIconAndTitlePanel.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/icontitle/EntityIconAndTitlePanel.java
new file mode 100644
index 0000000..54f64f4
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/icontitle/EntityIconAndTitlePanel.java
@@ -0,0 +1,217 @@
+/*
+ * 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.entity.icontitle;
+
+import com.google.inject.Inject;
+import org.apache.wicket.AttributeModifier;
+import org.apache.wicket.Page;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.image.Image;
+import org.apache.wicket.markup.html.link.AbstractLink;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.request.resource.ResourceReference;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager.ConcurrencyChecking;
+import org.apache.isis.core.metamodel.facets.members.cssclassfa.CssClassFaFacet;
+import org.apache.isis.viewer.wicket.model.isis.WicketViewerSettings;
+import org.apache.isis.viewer.wicket.model.mementos.ObjectAdapterMemento;
+import org.apache.isis.viewer.wicket.model.models.EntityModel;
+import org.apache.isis.viewer.wicket.model.models.ImageResourceCache;
+import org.apache.isis.viewer.wicket.model.models.PageType;
+import org.apache.isis.viewer.wicket.ui.components.actionmenu.entityactions.EntityActionLinkFactory;
+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.Components;
+import org.apache.isis.viewer.wicket.ui.util.CssClassAppender;
+import org.apache.isis.viewer.wicket.ui.util.Links;
+
+/**
+ * {@link PanelAbstract Panel} representing the icon and title of an entity,
+ * as per the provided {@link EntityModel}.
+ */
+public class EntityIconAndTitlePanel extends PanelAbstract<EntityModel> {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final String ID_ENTITY_LINK_WRAPPER = "entityLinkWrapper";
+ private static final String ID_ENTITY_FONT_AWESOME = "entityFontAwesome";
+ private static final String ID_ENTITY_LINK = "entityLink";
+ private static final String ID_ENTITY_TITLE = "entityTitle";
+ private static final String ID_ENTITY_ICON = "entityImage";
+
+ @SuppressWarnings("unused")
+ private Label label;
+ @SuppressWarnings("unused")
+ private Image image;
+
+ public EntityIconAndTitlePanel(final String id, final EntityModel entityModel) {
+ super(id, entityModel);
+ }
+
+ /**
+ * For the {@link EntityActionLinkFactory}.
+ */
+ public EntityModel getEntityModel() {
+ return getModel();
+ }
+
+ @Override
+ protected void onBeforeRender() {
+ buildGui();
+ super.onBeforeRender();
+ }
+
+ private void buildGui() {
+ addOrReplaceLinkWrapper();
+ setOutputMarkupId(true);
+ }
+
+ private void addOrReplaceLinkWrapper() {
+ EntityModel entityModel = getModel();
+ final WebMarkupContainer entityLinkWrapper = addOrReplaceLinkWrapper(entityModel);
+ addOrReplace(entityLinkWrapper);
+ }
+
+ protected WebMarkupContainer addOrReplaceLinkWrapper(final EntityModel entityModel) {
+ final ObjectAdapter adapter = entityModel.getObject();
+
+ final WebMarkupContainer entityLinkWrapper = new WebMarkupContainer(ID_ENTITY_LINK_WRAPPER);
+
+ final AbstractLink link = createIconAndTitle(adapter);
+ entityLinkWrapper.addOrReplace(link);
+
+ return entityLinkWrapper;
+ }
+
+ private AbstractLink createIconAndTitle(final ObjectAdapter adapter) {
+ final AbstractLink link = createLinkWrapper();
+
+ final String title = determineTitle();
+
+ final String iconName = adapter.getIconName();
+ final CssClassFaFacet cssClassFaFacet = adapter.getSpecification().getFacet(CssClassFaFacet.class);
+ if (iconName != null || cssClassFaFacet == null) {
+ link.addOrReplace(this.image = newImage(ID_ENTITY_ICON, adapter));
+ Components.permanentlyHide(link, ID_ENTITY_FONT_AWESOME);
+ } else {
+ Label dummy = new Label(ID_ENTITY_FONT_AWESOME, "");
+ link.addOrReplace(dummy);
+ dummy.add(new CssClassAppender(cssClassFaFacet.value() + " fa-2x"));
+ Components.permanentlyHide(link, ID_ENTITY_ICON);
+ }
+
+ link.addOrReplace(this.label = newLabel(ID_ENTITY_TITLE, titleAbbreviated(title)));
+
+ String entityTypeName = adapter.getSpecification().getSingularName();
+ link.add(new AttributeModifier("title", entityTypeName + ": " + title));
+
+ return link;
+ }
+
+ private AbstractLink createLinkWrapper() {
+ final PageParameters pageParameters = getModel().getPageParametersWithoutUiHints();
+
+ final Class<? extends Page> pageClass = getPageClassRegistry().getPageClass(PageType.ENTITY);
+ return Links.newBookmarkablePageLink(ID_ENTITY_LINK, pageParameters, pageClass);
+ }
+
+ private Label newLabel(final String id, final String title) {
+ return new Label(id, title);
+ }
+
+ private String titleAbbreviated(String titleString) {
+ int maxTitleLength = abbreviateTo(getModel(), titleString);
+ return abbreviated(titleString, maxTitleLength);
+ }
+
+ private String determineTitle() {
+ EntityModel model = getModel();
+ final ObjectAdapter adapter = model.getObject();
+ return adapter != null ? adapter.titleString(getContextAdapterIfAny()) : "(no object)";
+ }
+
+ private int abbreviateTo(EntityModel model, String titleString) {
+ if(model.getRenderingHint().isInStandaloneTableTitleColumn()) {
+ return getSettings().getMaxTitleLengthInStandaloneTables();
+ }
+ if(model.getRenderingHint().isInParentedTableTitleColumn()) {
+ return getSettings().getMaxTitleLengthInParentedTables();
+ }
+ return titleString.length();
+ }
+
+ protected Image newImage(final String id, final ObjectAdapter adapter) {
+ final ResourceReference imageResource = imageCache.resourceReferenceFor(adapter);
+
+ final Image image = new Image(id, imageResource) {
+ private static final long serialVersionUID = 1L;
+ @Override
+ protected boolean shouldAddAntiCacheParameter() {
+ return false;
+ }
+ };
+ return image;
+ }
+
+ public ObjectAdapter getContextAdapterIfAny() {
+ EntityModel model = getModel();
+ ObjectAdapterMemento contextAdapterMementoIfAny = model.getContextAdapterIfAny();
+ return contextAdapterMementoIfAny != null? contextAdapterMementoIfAny.getObjectAdapter(ConcurrencyChecking.NO_CHECK): null;
+ }
+
+ static String abbreviated(final String str, final int maxLength) {
+ int length = str.length();
+ if (length <= maxLength) {
+ return str;
+ }
+ return maxLength <= 3 ? "" : str.substring(0, maxLength - 3) + "...";
+ }
+
+
+
+ // ///////////////////////////////////////////////////////////////////
+ // Convenience
+ // ///////////////////////////////////////////////////////////////////
+
+ protected PageClassRegistry getPageClassRegistry() {
+ final PageClassRegistryAccessor pcra = (PageClassRegistryAccessor) getApplication();
+ return pcra.getPageClassRegistry();
+ }
+
+
+ // ///////////////////////////////////////////////
+ // Dependency Injection
+ // ///////////////////////////////////////////////
+
+ @Inject
+ private ImageResourceCache imageCache;
+ protected ImageResourceCache getImageCache() {
+ return imageCache;
+ }
+
+ @Inject
+ private WicketViewerSettings settings;
+ protected WicketViewerSettings getSettings() {
+ return settings;
+ }
+
+}
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/entity/icontitle/EntityIconAndTitlePanelFactory.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/icontitle/EntityIconAndTitlePanelFactory.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/icontitle/EntityIconAndTitlePanelFactory.java
new file mode 100644
index 0000000..9a2db9b
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/icontitle/EntityIconAndTitlePanelFactory.java
@@ -0,0 +1,46 @@
+/*
+ * 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.entity.icontitle;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.model.IModel;
+
+import org.apache.isis.viewer.wicket.model.models.EntityModel;
+import org.apache.isis.viewer.wicket.ui.ComponentFactory;
+import org.apache.isis.viewer.wicket.ui.ComponentType;
+import org.apache.isis.viewer.wicket.ui.components.entity.EntityComponentFactoryAbstract;
+
+/**
+ * {@link ComponentFactory} for {@link EntityIconAndTitlePanel}.
+ */
+public class EntityIconAndTitlePanelFactory extends EntityComponentFactoryAbstract {
+
+ private static final long serialVersionUID = 1L;
+
+ public EntityIconAndTitlePanelFactory() {
+ super(ComponentType.ENTITY_ICON_AND_TITLE, EntityIconAndTitlePanel.class);
+ }
+
+ @Override
+ public Component createComponent(final String id, final IModel<?> model) {
+ final EntityModel entityModel = (EntityModel) model;
+ return new EntityIconAndTitlePanel(id, entityModel);
+ }
+}
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/entity/icontitle/EntityIconTitleAndCopyLinkPanel.html
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/icontitle/EntityIconTitleAndCopyLinkPanel.html b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/icontitle/EntityIconTitleAndCopyLinkPanel.html
new file mode 100644
index 0000000..18b0521
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/icontitle/EntityIconTitleAndCopyLinkPanel.html
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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:wicket="http://wicket.apache.org">
+ <body>
+ <wicket:extend>
+ <span wicket:id="copyLink"></span>
+ </wicket:extend>
+ </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/components/entity/icontitle/EntityIconTitleAndCopyLinkPanel.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/icontitle/EntityIconTitleAndCopyLinkPanel.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/icontitle/EntityIconTitleAndCopyLinkPanel.java
new file mode 100644
index 0000000..e6500b9
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/icontitle/EntityIconTitleAndCopyLinkPanel.java
@@ -0,0 +1,49 @@
+/*
+ * 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.entity.icontitle;
+
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.isis.viewer.wicket.model.models.EntityModel;
+import org.apache.isis.viewer.wicket.ui.components.widgets.zclip.ZeroClipboardPanel;
+
+/**
+ * An extension of {@link org.apache.isis.viewer.wicket.ui.components.entity.icontitle.EntityIconAndTitlePanel}
+ * that additionally has a link allowing to copy the url to the shown entity
+ */
+public class EntityIconTitleAndCopyLinkPanel extends EntityIconAndTitlePanel {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final String ID_COPY_LINK = "copyLink";
+
+ public EntityIconTitleAndCopyLinkPanel(final String id, final EntityModel entityModel) {
+ super(id, entityModel);
+ }
+
+ @Override
+ protected WebMarkupContainer addOrReplaceLinkWrapper(final EntityModel entityModel) {
+ WebMarkupContainer linkWrapper = super.addOrReplaceLinkWrapper(entityModel);
+
+ ZeroClipboardPanel zClipCopyLink = new ZeroClipboardPanel(ID_COPY_LINK, entityModel);
+ linkWrapper.add(zClipCopyLink);
+
+ return linkWrapper;
+ }
+}
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/entity/icontitle/EntityIconTitleAndCopyLinkPanelFactory.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/icontitle/EntityIconTitleAndCopyLinkPanelFactory.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/icontitle/EntityIconTitleAndCopyLinkPanelFactory.java
new file mode 100644
index 0000000..a13e1dd
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/icontitle/EntityIconTitleAndCopyLinkPanelFactory.java
@@ -0,0 +1,44 @@
+/*
+ * 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.entity.icontitle;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.model.IModel;
+import org.apache.isis.viewer.wicket.model.models.EntityModel;
+import org.apache.isis.viewer.wicket.ui.ComponentType;
+import org.apache.isis.viewer.wicket.ui.components.entity.EntityComponentFactoryAbstract;
+
+/**
+ * {@link org.apache.isis.viewer.wicket.ui.ComponentFactory} for {@link org.apache.isis.viewer.wicket.ui.components.entity.icontitle.EntityIconAndTitlePanel}.
+ */
+public class EntityIconTitleAndCopyLinkPanelFactory extends EntityComponentFactoryAbstract {
+
+ private static final long serialVersionUID = 1L;
+
+ public EntityIconTitleAndCopyLinkPanelFactory() {
+ super(ComponentType.ENTITY_ICON_TITLE_AND_COPYLINK, EntityIconTitleAndCopyLinkPanel.class);
+ }
+
+ @Override
+ public Component createComponent(final String id, final IModel<?> model) {
+ final EntityModel entityModel = (EntityModel) model;
+ return new EntityIconTitleAndCopyLinkPanel(id, entityModel);
+ }
+}
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/entity/properties/EntityPropertiesForm.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/properties/EntityPropertiesForm.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/properties/EntityPropertiesForm.java
new file mode 100644
index 0000000..6ab8659
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/properties/EntityPropertiesForm.java
@@ -0,0 +1,769 @@
+/*
+ * 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.entity.properties;
+
+import java.util.List;
+import java.util.Map;
+
+import com.google.common.collect.Lists;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.MarkupContainer;
+import org.apache.wicket.Session;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
+import org.apache.wicket.ajax.markup.html.form.AjaxButton;
+import org.apache.wicket.feedback.ComponentFeedbackMessageFilter;
+import org.apache.wicket.markup.ComponentTag;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.Button;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.form.FormComponent;
+import org.apache.wicket.markup.repeater.RepeatingView;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.ResourceModel;
+import org.apache.wicket.request.Response;
+import org.apache.wicket.util.string.Strings;
+import org.apache.wicket.util.visit.IVisit;
+import org.apache.wicket.util.visit.IVisitor;
+
+import org.apache.isis.applib.annotation.ActionLayout;
+import org.apache.isis.applib.annotation.MemberGroupLayout.ColumnSpans;
+import org.apache.isis.applib.annotation.Where;
+import org.apache.isis.applib.filter.Filter;
+import org.apache.isis.applib.filter.Filters;
+import org.apache.isis.applib.services.exceprecog.ExceptionRecognizer;
+import org.apache.isis.applib.services.exceprecog.ExceptionRecognizerComposite;
+import org.apache.isis.core.commons.authentication.MessageBroker;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager.ConcurrencyChecking;
+import org.apache.isis.core.metamodel.adapter.version.ConcurrencyException;
+import org.apache.isis.core.metamodel.facets.object.membergroups.MemberGroupLayoutFacet;
+import org.apache.isis.core.metamodel.runtimecontext.ServicesInjector;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.ObjectSpecifications;
+import org.apache.isis.core.metamodel.spec.ObjectSpecifications.MemberGroupLayoutHint;
+import org.apache.isis.core.metamodel.spec.feature.Contributed;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+import org.apache.isis.core.metamodel.spec.feature.OneToOneAssociation;
+import org.apache.isis.core.runtime.memento.Memento;
+import org.apache.isis.core.runtime.system.DeploymentType;
+import org.apache.isis.core.runtime.system.context.IsisContext;
+import org.apache.isis.core.runtime.system.transaction.IsisTransactionManager;
+import org.apache.isis.viewer.wicket.model.links.LinkAndLabel;
+import org.apache.isis.viewer.wicket.model.mementos.PropertyMemento;
+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.EntityModel;
+import org.apache.isis.viewer.wicket.model.models.ScalarModel;
+import org.apache.isis.viewer.wicket.ui.ComponentType;
+import org.apache.isis.viewer.wicket.ui.components.actionmenu.entityactions.AdditionalLinksPanel;
+import org.apache.isis.viewer.wicket.ui.components.actionmenu.entityactions.EntityActionUtil;
+import org.apache.isis.viewer.wicket.ui.components.widgets.containers.UiHintPathSignificantWebMarkupContainer;
+import org.apache.isis.viewer.wicket.ui.components.widgets.formcomponent.CancelHintRequired;
+import org.apache.isis.viewer.wicket.ui.errors.JGrowlBehaviour;
+import org.apache.isis.viewer.wicket.ui.pages.entity.EntityPage;
+import org.apache.isis.viewer.wicket.ui.panels.FormAbstract;
+import org.apache.isis.viewer.wicket.ui.panels.IFormSubmitterWithPreValidateHook;
+import org.apache.isis.viewer.wicket.ui.util.Components;
+import org.apache.isis.viewer.wicket.ui.util.CssClassAppender;
+
+import de.agilecoders.wicket.core.markup.html.bootstrap.common.NotificationPanel;
+import de.agilecoders.wicket.core.util.Attributes;
+
+public class EntityPropertiesForm extends FormAbstract<ObjectAdapter> implements ActionPromptProvider {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final String ID_MEMBER_GROUP = "memberGroup";
+ private static final String ID_MEMBER_GROUP_NAME = "memberGroupName";
+
+ private static final String ID_ASSOCIATED_ACTION_LINKS_PANEL = "associatedActionLinksPanel";
+ private static final String ID_ASSOCIATED_ACTION_LINKS_PANEL_DROPDOWN = "associatedActionLinksPanelDropDown";
+
+ private static final String ID_LEFT_COLUMN = "leftColumn";
+ private static final String ID_MIDDLE_COLUMN = "middleColumn";
+ private static final String ID_RIGHT_COLUMN = "rightColumn";
+
+ private static final String ID_ENTITY_COLLECTIONS = "entityCollections";
+ private static final String ID_ENTITY_COLLECTIONS_OVERFLOW = "entityCollectionsOverflow";
+
+ private static final String ID_PROPERTIES = "properties";
+ private static final String ID_PROPERTY = "property";
+
+ private static final String ID_EDIT_BUTTON = "edit";
+ private static final String ID_OK_BUTTON = "ok";
+ private static final String ID_CANCEL_BUTTON = "cancel";
+
+ private static final String ID_FEEDBACK = "feedback";
+
+ private final Component owningPanel;
+
+ private Button editButton;
+ private Button okButton;
+ private Button cancelButton;
+
+ private NotificationPanel feedback;
+
+ private boolean renderedFirstField;
+
+ public EntityPropertiesForm(
+ final String id,
+ final EntityModel entityModel,
+ final Component owningPanel) {
+
+ super(id, entityModel);
+ this.owningPanel = owningPanel; // for repainting
+
+ buildGui();
+
+ // add any concurrency exception that might have been propagated into the entity model
+ // as a result of a previous action invocation
+ final String concurrencyExceptionIfAny = entityModel.getAndClearConcurrencyExceptionIfAny();
+ if(concurrencyExceptionIfAny != null) {
+ error(concurrencyExceptionIfAny);
+ }
+ }
+
+ private void buildGui() {
+
+ final EntityModel entityModel = (EntityModel) getModel();
+ final ColumnSpans columnSpans = entityModel.getObject().getSpecification().getFacet(MemberGroupLayoutFacet.class).getColumnSpans();
+
+ renderedFirstField = false;
+
+ // left column
+ MarkupContainer leftColumn = new WebMarkupContainer(ID_LEFT_COLUMN);
+ add(leftColumn);
+
+ boolean addedProperties;
+ if(columnSpans.getLeft() > 0) {
+ addedProperties = addPropertiesInColumn(leftColumn, MemberGroupLayoutHint.LEFT, columnSpans);
+ addButtons(leftColumn);
+ addFeedbackGui(leftColumn);
+ } else {
+ Components.permanentlyHide(this, ID_LEFT_COLUMN);
+ addedProperties = false;
+ }
+ if(!addedProperties) {
+ // a bit hacky...
+ Components.permanentlyHide(this,
+ ID_EDIT_BUTTON, ID_OK_BUTTON, ID_CANCEL_BUTTON,
+ ID_FEEDBACK);
+ }
+
+ // middle column
+ if(columnSpans.getMiddle() > 0) {
+ MarkupContainer middleColumn = new WebMarkupContainer(ID_MIDDLE_COLUMN);
+ add(middleColumn);
+ addPropertiesInColumn(middleColumn, MemberGroupLayoutHint.MIDDLE, columnSpans);
+ } else {
+ Components.permanentlyHide(this, ID_MIDDLE_COLUMN);
+ }
+
+ // right column
+ if(columnSpans.getRight() > 0) {
+ MarkupContainer rightColumn = new WebMarkupContainer(ID_RIGHT_COLUMN);
+ add(rightColumn);
+ addPropertiesInColumn(rightColumn, MemberGroupLayoutHint.RIGHT, columnSpans);
+ } else {
+ Components.permanentlyHide(this, ID_RIGHT_COLUMN);
+ }
+
+ // collections
+ if(columnSpans.getCollections() > 0) {
+ final String idCollectionsToShow;
+ final String idCollectionsToHide;
+ int collectionSpan;
+ if (columnSpans.exceedsRow()) {
+ idCollectionsToShow = ID_ENTITY_COLLECTIONS_OVERFLOW;
+ idCollectionsToHide = ID_ENTITY_COLLECTIONS;
+ collectionSpan = 12;
+ } else {
+ idCollectionsToShow = ID_ENTITY_COLLECTIONS;
+ idCollectionsToHide = ID_ENTITY_COLLECTIONS_OVERFLOW;
+ collectionSpan = columnSpans.getCollections();
+ }
+
+ final Component collectionsColumn = getComponentFactoryRegistry().addOrReplaceComponent(this, idCollectionsToShow, ComponentType.ENTITY_COLLECTIONS, entityModel);
+ addClassForSpan(collectionsColumn, collectionSpan);
+
+ Components.permanentlyHide(this, idCollectionsToHide);
+ } else {
+ Components.permanentlyHide(this, ID_ENTITY_COLLECTIONS);
+ Components.permanentlyHide(this, ID_ENTITY_COLLECTIONS_OVERFLOW);
+ }
+
+ }
+
+ private boolean addPropertiesInColumn(MarkupContainer markupContainer, MemberGroupLayoutHint hint, ColumnSpans columnSpans) {
+ final int span = hint.from(columnSpans);
+
+ final EntityModel entityModel = (EntityModel) getModel();
+ final ObjectAdapter adapter = entityModel.getObject();
+ final ObjectSpecification objSpec = adapter.getSpecification();
+
+ final List<ObjectAssociation> associations = visibleProperties(adapter, objSpec, Where.OBJECT_FORMS);
+
+ final RepeatingView memberGroupRv = new RepeatingView(ID_MEMBER_GROUP);
+ markupContainer.add(memberGroupRv);
+
+ Map<String, List<ObjectAssociation>> associationsByGroup = ObjectAssociation.Util.groupByMemberOrderName(associations);
+
+ final List<String> groupNames = ObjectSpecifications.orderByMemberGroups(objSpec, associationsByGroup.keySet(), hint);
+
+
+ for(String groupName: groupNames) {
+ final List<ObjectAssociation> associationsInGroup = associationsByGroup.get(groupName);
+ if(associationsInGroup==null) {
+ continue;
+ }
+
+ final WebMarkupContainer memberGroupRvContainer = new WebMarkupContainer(memberGroupRv.newChildId());
+ memberGroupRv.add(memberGroupRvContainer);
+ memberGroupRvContainer.add(new Label(ID_MEMBER_GROUP_NAME, groupName));
+
+ final List<LinkAndLabel> memberGroupActions = Lists.newArrayList();
+
+ final RepeatingView propertyRv = new RepeatingView(ID_PROPERTIES);
+ memberGroupRvContainer.add(propertyRv);
+
+ @SuppressWarnings("unused")
+ Component component;
+ for (final ObjectAssociation association : associationsInGroup) {
+ final WebMarkupContainer propertyRvContainer = new UiHintPathSignificantWebMarkupContainer(propertyRv.newChildId());
+ propertyRv.add(propertyRvContainer);
+
+ addPropertyToForm(entityModel, (OneToOneAssociation) association, propertyRvContainer, memberGroupActions);
+ }
+
+ final List<LinkAndLabel> actionsPanel = LinkAndLabel.positioned(memberGroupActions, ActionLayout.Position.PANEL);
+ final List<LinkAndLabel> actionsPanelDropDown = LinkAndLabel.positioned(memberGroupActions, ActionLayout.Position.PANEL_DROPDOWN);
+
+ AdditionalLinksPanel.addAdditionalLinks(
+ memberGroupRvContainer, ID_ASSOCIATED_ACTION_LINKS_PANEL,
+ actionsPanel,
+ AdditionalLinksPanel.Style.INLINE_LIST);
+ AdditionalLinksPanel.addAdditionalLinks(
+ memberGroupRvContainer, ID_ASSOCIATED_ACTION_LINKS_PANEL_DROPDOWN,
+ actionsPanelDropDown,
+ AdditionalLinksPanel.Style.DROPDOWN);
+ }
+
+ addClassForSpan(markupContainer, span);
+ return !groupNames.isEmpty();
+ }
+
+ private void addPropertyToForm(
+ final EntityModel entityModel,
+ final OneToOneAssociation association,
+ final WebMarkupContainer container,
+ final List<LinkAndLabel> entityActions) {
+ final OneToOneAssociation otoa = association;
+ final PropertyMemento pm = new PropertyMemento(otoa);
+
+ final ScalarModel scalarModel = entityModel.getPropertyModel(pm);
+ final Component component = getComponentFactoryRegistry().addOrReplaceComponent(container, ID_PROPERTY, ComponentType.SCALAR_NAME_AND_VALUE, scalarModel);
+
+ final List<ObjectAction> associatedActions = EntityActionUtil.getObjectActionsForAssociation(entityModel, otoa, getDeploymentType());
+
+ entityActions.addAll(EntityActionUtil.asLinkAndLabelsForAdditionalLinksPanel(entityModel, associatedActions));
+
+ if(!renderedFirstField) {
+ component.add(new CssClassAppender("first-field"));
+ renderedFirstField = true;
+ }
+ }
+
+
+ private List<ObjectAssociation> visibleProperties(final ObjectAdapter adapter, final ObjectSpecification objSpec, Where where) {
+ return objSpec.getAssociations(Contributed.INCLUDED, visiblePropertyFilter(adapter, where));
+ }
+
+ @SuppressWarnings("unchecked")
+ private Filter<ObjectAssociation> visiblePropertyFilter(final ObjectAdapter adapter, Where where) {
+ return Filters.and(ObjectAssociation.Filters.PROPERTIES, ObjectAssociation.Filters.dynamicallyVisible(getAuthenticationSession(), adapter, where));
+ }
+
+ @Override
+ protected void onComponentTag(ComponentTag tag) {
+ super.onComponentTag(tag);
+
+ Attributes.addClass(tag, "form-horizontal");
+ }
+
+ @Override
+ public ActionPrompt getActionPrompt() {
+ return ActionPromptProvider.Util.getFrom(this).getActionPrompt();
+ }
+
+ abstract class AjaxButtonWithOnError extends AjaxButton {
+
+ public AjaxButtonWithOnError(String id, IModel<String> model) {
+ super(id, model);
+ }
+
+ @Override
+ protected void onError(AjaxRequestTarget target, Form<?> form) {
+ super.onError(target, form);
+ toEditMode(target);
+ }
+
+ /**
+ * Render the 'type' attribute even for invisible buttons to avoid
+ * <a href="https://github.com/twbs/bootlint/wiki/W007">Bootlint W007</a>
+ *
+ * @param tag The component tag to render
+ * @param response The response to write to
+ */
+ // TODO mgrigorov Move this to Wicket Bootstrap project
+ @Override
+ protected void renderPlaceholderTag(ComponentTag tag, Response response) {
+ String ns = Strings.isEmpty(tag.getNamespace()) ? null : tag.getNamespace() + ':';
+
+ response.write("<");
+ if (ns != null)
+ {
+ response.write(ns);
+ }
+ response.write(tag.getName());
+ response.write(" id=\"");
+ response.write(getAjaxRegionMarkupId());
+
+ String type = tag.getAttribute("type");
+ if (!Strings.isEmpty(type)) {
+ response.write("\" type=\""+type);
+ }
+
+ response.write("\" style=\"display:none\"></");
+ if (ns != null)
+ {
+ response.write(ns);
+ }
+ response.write(tag.getName());
+ response.write(">");
+ }
+ }
+
+ public class AjaxButtonForValidate extends AjaxButtonWithOnError implements IFormSubmitterWithPreValidateHook {
+ private static final long serialVersionUID = 1L;
+ public AjaxButtonForValidate(String id, IModel<String> model) {
+ super(id, model);
+ }
+
+ @Override
+ public String preValidate() {
+ // attempt to load with concurrency checking, catching recognized exceptions
+ try {
+ getEntityModel().load(ConcurrencyChecking.CHECK); // could have also just called #getObject(), since CHECK is the default
+
+ } catch(ConcurrencyException ex){
+ String recognizedErrorMessage = recognizeException(ex);
+ if(recognizedErrorMessage == null) {
+ throw ex;
+ }
+
+ // reload
+ getEntityModel().load(ConcurrencyChecking.NO_CHECK);
+
+ getForm().clearInput();
+ getEntityModel().resetPropertyModels();
+
+ toViewMode(null);
+ toEditMode(null);
+
+ return recognizedErrorMessage;
+ }
+
+ return null;
+ }
+
+ @Override
+ public void validate() {
+ // add in any error message that we might have recognized from above
+ EntityPropertiesForm form = EntityPropertiesForm.this;
+ String preValidationErrorIfAny = form.getPreValidationErrorIfAny();
+
+ if(preValidationErrorIfAny != null) {
+ feedbackOrNotifyAnyRecognizedError(preValidationErrorIfAny, form);
+ // skip validation, because would relate to old values
+
+ final EntityPage entityPage = new EntityPage(EntityPropertiesForm.this.getModelObject(), null);
+ EntityPropertiesForm.this.setResponsePage(entityPage);
+ } else {
+ // run Wicket's validation
+ super.validate();
+ }
+ }
+
+ @Override
+ protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
+
+ if (getForm().hasError()) {
+ // stay in edit mode
+ return;
+ }
+
+ doPreApply();
+ if (applyFormChangesElse(target)) return;
+ final Object redirectIfAny = doPostApply();
+
+ if (flushChangesElse(target)) return;
+
+
+ getEntityModel().resetPropertyModels();
+
+ toViewMode(null);
+
+ // "redirect-after-post"
+ //
+ // RequestCycle.get().getActiveRequestHandler() indicates this is handled by the ListenerInterfaceRequestHandler
+ // which renders page at end.
+ //
+ // it's necessary to zap the page parameters (so mapping is to just wicket/page?nn)
+ // otherwise (what I think happens) is that the httpServletResponse.sendRedirect ends up being to the same URL,
+ // and this is rejected as invalid either by the browser or by the servlet container (perhaps only if running remotely).
+ //
+
+ final ObjectAdapter objectAdapter;
+ if(redirectIfAny != null) {
+ objectAdapter = getPersistenceSession().getAdapterManager().adapterFor(redirectIfAny);
+ } else {
+ // we obtain the adapter from the entity model because (if a view model) then the entity model may contain
+ // a different adapter (the cloned view model) to the one with which we started with.
+ objectAdapter = getEntityModel().getObjectAdapterMemento().getObjectAdapter(ConcurrencyChecking.NO_CHECK);
+ }
+
+ final EntityPage entityPage = new EntityPage(objectAdapter, null);
+ EntityPropertiesForm.this.setResponsePage(entityPage);
+ }
+
+ /**
+ * Optional hook to override.
+ *
+ * <p>
+ * If a non-null value is returned, then transition to it (ie eg the finish() transition for a wizard).
+ * </p>
+ */
+ protected void doPreApply() {
+ }
+
+ /**
+ * Optional hook to override.
+ *
+ * <p>
+ * If a non-null value is returned, then transition to it (ie eg the finish() transition for a wizard).
+ * </p>
+ */
+ protected Object doPostApply() {
+ return null;
+ }
+
+ }
+
+ abstract class AjaxButtonForCancel extends AjaxButtonWithOnError {
+
+ public AjaxButtonForCancel(String id, IModel<String> model) {
+ super(id, model);
+ setDefaultFormProcessing(false);
+ }
+ }
+
+
+ private void addButtons(MarkupContainer markupContainer) {
+
+ // edit button
+ editButton = new AjaxButtonWithOnError(ID_EDIT_BUTTON, new ResourceModel("editLabel")) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void validate() {
+
+ // same logic as in cancelButton; should this be factored out?
+ try {
+ getEntityModel().load(ConcurrencyChecking.CHECK);
+ } catch(ConcurrencyException ex) {
+ getMessageBroker().addMessage("Object changed by " + ex.getOid().getVersion().getUser() + ", automatically reloading");
+ getEntityModel().load(ConcurrencyChecking.NO_CHECK);
+ }
+
+ super.validate();
+ }
+
+ @Override
+ public void onSubmit(final AjaxRequestTarget target, final Form<?> form) {
+ getEntityModel().resetPropertyModels();
+ toEditMode(target);
+ }
+
+ @Override
+ protected void updateAjaxAttributes(AjaxRequestAttributes attributes) {
+ super.updateAjaxAttributes(attributes);
+ attributes.getAjaxCallListeners().add(new org.apache.wicket.ajax.attributes.AjaxCallListener(){
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public CharSequence getSuccessHandler(Component component) {
+ // scroll to the top of the entity panel
+ return "$('html, body').animate({"
+ + " scrollTop: $('.entityIconAndTitlePanel').offset().top"
+ + " }, 1000);";
+ }
+ });
+ }
+ };
+ editButton.add(new Label("editLabel", editButton.getModel()));
+ markupContainer.add(editButton);
+
+
+ // ok button
+ okButton = new AjaxButtonForValidate(ID_OK_BUTTON, new ResourceModel("okLabel"));
+ markupContainer.add(okButton);
+
+
+ // cancel button
+ cancelButton = new AjaxButtonForCancel(ID_CANCEL_BUTTON, new ResourceModel("cancelLabel")) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void validate() {
+
+ // same logic as in editButton; should this be factored out?
+ try {
+ getEntityModel().load(ConcurrencyChecking.CHECK);
+ } catch(ConcurrencyException ex) {
+ getMessageBroker().addMessage("Object changed by " + ex.getOid().getVersion().getUser() + ", automatically reloading");
+ getEntityModel().load(ConcurrencyChecking.NO_CHECK);
+ }
+ super.validate();
+ }
+
+ @Override
+ protected void onSubmit(final AjaxRequestTarget target, final Form<?> form) {
+ Session.get().getFeedbackMessages().clear();
+ getForm().clearInput();
+ getForm().visitFormComponentsPostOrder(new IVisitor<FormComponent<?>, Void>() {
+
+ @Override
+ public void component(FormComponent<?> formComponent, IVisit<Void> visit) {
+ if (formComponent instanceof CancelHintRequired) {
+ final CancelHintRequired cancelHintRequired = (CancelHintRequired) formComponent;
+ cancelHintRequired.onCancel();
+ }
+ }
+ });
+
+ try {
+ getEntityModel().resetPropertyModels();
+ } catch(RuntimeException ex) {
+ throw ex;
+ }
+ toViewMode(target);
+ }
+ };
+
+ markupContainer.add(cancelButton);
+
+ okButton.setOutputMarkupPlaceholderTag(true);
+ editButton.setOutputMarkupPlaceholderTag(true);
+ cancelButton.setOutputMarkupPlaceholderTag(true);
+
+ // flush any JGrowl messages (typically concurrency exceptions) if they are added.
+ okButton.add(new JGrowlBehaviour());
+ editButton.add(new JGrowlBehaviour());
+ cancelButton.add(new JGrowlBehaviour());
+ }
+
+ // to perform object-level validation, we must apply the changes first
+ // contrast this with ActionPanel (for validating actionarguments) where
+ // we do the validation prior to the execution of the action
+ private boolean applyFormChangesElse(AjaxRequestTarget target) {
+ final ObjectAdapter adapter = getEntityModel().getObject();
+ final Memento snapshotToRollbackToIfInvalid = new Memento(adapter);
+
+ getEntityModel().apply();
+ final String invalidReasonIfAny = getEntityModel().getReasonInvalidIfAny();
+ if (invalidReasonIfAny != null) {
+ error(invalidReasonIfAny);
+ snapshotToRollbackToIfInvalid.recreateObject();
+ toEditMode(target);
+
+ // abort otherwise the object will have been dirtied and JDO will end up committing,
+ // possibly bumping the version and resulting in a subsequent concurrency exception.
+ IsisContext.getTransactionManager().abortTransaction();
+ return true;
+ }
+ return false;
+ }
+
+ private boolean flushChangesElse(AjaxRequestTarget target) {
+ try {
+ this.getTransactionManager().flushTransaction();
+ } catch(RuntimeException ex) {
+
+ // There's no need to abort the transaction here, as it will have already been done
+ // (in IsisTransactionManager#executeWithinTransaction(...)).
+
+ String message = recognizeExceptionAndNotify(ex, this);
+ if(message == null) {
+ throw ex;
+ }
+
+ toEditMode(target);
+ return true;
+ }
+ return false;
+ }
+
+
+ private String recognizeExceptionAndNotify(RuntimeException ex, Component feedbackComponentIfAny) {
+
+ // see if the exception is recognized as being a non-serious error
+
+ String recognizedErrorMessageIfAny = recognizeException(ex);
+ feedbackOrNotifyAnyRecognizedError(recognizedErrorMessageIfAny, feedbackComponentIfAny);
+
+ return recognizedErrorMessageIfAny;
+ }
+
+ private void feedbackOrNotifyAnyRecognizedError(String recognizedErrorMessageIfAny, Component feedbackComponentIfAny) {
+ if(recognizedErrorMessageIfAny == null) {
+ return;
+ }
+
+ if(feedbackComponentIfAny != null) {
+ feedbackComponentIfAny.error(recognizedErrorMessageIfAny);
+ }
+ getMessageBroker().addWarning(recognizedErrorMessageIfAny);
+
+ // we clear the abort cause because we've handled rendering the exception
+ getTransactionManager().getTransaction().clearAbortCause();
+ }
+
+ private String recognizeException(RuntimeException ex) {
+
+ // REVIEW: this code is similar to stuff in EntityPropertiesForm, perhaps move up to superclass?
+ // REVIEW: similar code also in WebRequestCycleForIsis; combine?
+
+ final List<ExceptionRecognizer> exceptionRecognizers = getServicesInjector().lookupServices(ExceptionRecognizer.class);
+ final String message = new ExceptionRecognizerComposite(exceptionRecognizers).recognize(ex);
+ return message;
+ }
+
+ private void requestRepaintPanel(final AjaxRequestTarget target) {
+ if (target != null) {
+ target.add(owningPanel);
+ // TODO: is it necessary to add these too?
+ target.add(editButton, okButton, cancelButton, feedback);
+ }
+ }
+
+ private EntityModel getEntityModel() {
+ return (EntityModel) getModel();
+ }
+
+ void toViewMode(final AjaxRequestTarget target) {
+
+ getEntityModel().toViewMode();
+
+ setVisible(editButton, isAnythingEditable());
+ setVisible(okButton, false);
+ setVisible(cancelButton, false);
+
+ requestRepaintPanel(target);
+ }
+
+ private void setVisible(Button b, boolean editable) {
+ if(b != null) {
+ b.setVisible(editable);
+ }
+ }
+
+ private boolean isAnythingEditable() {
+ final EntityModel entityModel = (EntityModel) getModel();
+ final ObjectAdapter adapter = entityModel.getObject();
+
+ return !enabledAssociations(adapter, adapter.getSpecification()).isEmpty();
+ }
+
+ private List<ObjectAssociation> enabledAssociations(final ObjectAdapter adapter, final ObjectSpecification objSpec) {
+ return objSpec.getAssociations(Contributed.EXCLUDED, enabledAssociationFilter(adapter));
+ }
+
+ @SuppressWarnings("unchecked")
+ private Filter<ObjectAssociation> enabledAssociationFilter(final ObjectAdapter adapter) {
+ return Filters.and(ObjectAssociation.Filters.PROPERTIES, ObjectAssociation.Filters.enabled(getAuthenticationSession(), adapter, Where.OBJECT_FORMS));
+ }
+
+ private void toEditMode(final AjaxRequestTarget target) {
+ getEntityModel().toEditMode();
+
+ editButton.setVisible(false);
+ okButton.setVisible(true);
+ cancelButton.setVisible(true);
+
+ requestRepaintPanel(target);
+ }
+
+ private void addFeedbackGui(final MarkupContainer markupContainer) {
+ feedback = new NotificationPanel(ID_FEEDBACK, this, new ComponentFeedbackMessageFilter(this));
+ feedback.setOutputMarkupPlaceholderTag(true);
+ markupContainer.addOrReplace(feedback);
+
+ // 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)
+ // feedback.setEscapeModelStrings(false);
+
+ final ObjectAdapter adapter = getEntityModel().getObject();
+ if (adapter == null) {
+ feedback.error("cannot locate object:" + getEntityModel().getObjectAdapterMemento().toString());
+ }
+ }
+
+
+ private static void addClassForSpan(final Component component, final int numGridCols) {
+ component.add(new CssClassAppender("col-xs-"+numGridCols));
+ }
+
+
+
+ ///////////////////////////////////////////////////////
+ // Dependencies (from context)
+ ///////////////////////////////////////////////////////
+
+ protected IsisTransactionManager getTransactionManager() {
+ return IsisContext.getTransactionManager();
+ }
+
+ protected ServicesInjector getServicesInjector() {
+ return IsisContext.getPersistenceSession().getServicesInjector();
+ }
+
+ protected MessageBroker getMessageBroker() {
+ return getAuthenticationSession().getMessageBroker();
+ }
+
+ protected DeploymentType getDeploymentType() {
+ return IsisContext.getDeploymentType();
+ }
+
+}
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/entity/properties/EntityPropertiesForm.properties
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/properties/EntityPropertiesForm.properties b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/properties/EntityPropertiesForm.properties
new file mode 100644
index 0000000..71edcd9
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/properties/EntityPropertiesForm.properties
@@ -0,0 +1,22 @@
+#
+# 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.
+#
+
+okLabel=OK
+cancelLabel=Cancel
+editLabel=Edit
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/entity/properties/EntityPropertiesPanel.html
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/properties/EntityPropertiesPanel.html b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/properties/EntityPropertiesPanel.html
new file mode 100644
index 0000000..e65c157
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/properties/EntityPropertiesPanel.html
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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:wicket="http://wicket.apache.org">
+ <body>
+ <wicket:panel>
+ <div class="entityPropertiesPanel entityPropertiesComponentType">
+ <form wicket:id="entityProperties" class="inputForm" role="form">
+ <div class="row" style="padding-bottom: 20px">
+ <div wicket:id="leftColumn">
+ <div class="inputFormTable properties">
+ <fieldset wicket:id="memberGroup" class="memberGroup myBlockContainer">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <span wicket:id="memberGroupName" class="panel-title">[group name]</span>
+ <div class="pull-right additionalLinks">
+ <div wicket:id="associatedActionLinksPanel"></div>
+ <div wicket:id="associatedActionLinksPanelDropDown"></div>
+ </div>
+ </div>
+ <div class="properties panel-body">
+ <div wicket:id="properties">
+ <div wicket:id="property" class="property">[property]</div>
+ </div>
+ </div>
+ </div>
+ </fieldset>
+ </div>
+ <div class="feedbackPanel">
+ <span wicket:id="feedback"></span>
+ </div>
+ <div class="buttons">
+ <button type="submit" class="edit btn btn-primary btn-md" wicket:id="edit"><span class="fa fa-edit"></span> <wicket:container wicket:id="editLabel"></wicket:container></button>
+ <input type="submit" class="ok btn btn-primary btn-md" wicket:id="ok"/>
+ <input type="submit" class="cancel btn btn-default btn-md" wicket:id="cancel"/>
+ </div>
+ </div>
+ <div wicket:id="middleColumn">
+ <div class="inputFormTable properties">
+ <fieldset wicket:id="memberGroup" class="memberGroup myBlockContainer">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <span wicket:id="memberGroupName" class="panel-title">[group name]</span>
+ <div class="pull-right additionalLinks">
+ <div wicket:id="associatedActionLinksPanel"></div>
+ <div wicket:id="associatedActionLinksPanelDropDown"></div>
+ </div>
+ </div>
+ <div class="properties panel-body">
+ <div wicket:id="properties">
+ <div wicket:id="property" class="property">[property]</div>
+ </div>
+ </div>
+ </div>
+ </fieldset>
+ </div>
+ </div>
+ <div wicket:id="rightColumn">
+ <div class="inputFormTable properties">
+ <fieldset wicket:id="memberGroup" class="memberGroup myBlockContainer">
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <span wicket:id="memberGroupName" class="panel-title">[group name]</span>
+ <div class="pull-right additionalLinks">
+ <div wicket:id="associatedActionLinksPanel"></div>
+ <div wicket:id="associatedActionLinksPanelDropDown"></div>
+ </div>
+ </div>
+ <div class="properties panel-body">
+ <div wicket:id="properties">
+ <div wicket:id="property" class="property">[property]</div>
+ </div>
+ </div>
+ </div>
+ </fieldset>
+ </div>
+ </div>
+ <div wicket:id="entityCollections"></div>
+ </div>
+ <div class="row">
+ <div wicket:id="entityCollectionsOverflow"></div>
+ </div>
+
+ </form>
+ </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/components/entity/properties/EntityPropertiesPanel.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/properties/EntityPropertiesPanel.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/properties/EntityPropertiesPanel.java
new file mode 100644
index 0000000..0f49919
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/properties/EntityPropertiesPanel.java
@@ -0,0 +1,60 @@
+/*
+ * 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.entity.properties;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.viewer.wicket.model.models.EntityModel;
+import org.apache.isis.viewer.wicket.ui.panels.PanelAbstract;
+
+/**
+ * {@link PanelAbstract Panel} representing the properties of an entity, as per
+ * the provided {@link EntityModel}.
+ */
+public class EntityPropertiesPanel extends PanelAbstract<EntityModel> {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final String ID_ENTITY_PROPERTIES = "entityProperties";
+
+ private EntityPropertiesForm form;
+
+ public EntityPropertiesPanel(final String id, final EntityModel entityModel) {
+ super(id, entityModel);
+ buildGui();
+ form.toViewMode(null);
+ }
+
+
+ private void buildGui() {
+ buildEntityPropertiesAndOrCollectionsGui();
+ setOutputMarkupId(true);
+ }
+
+ private void buildEntityPropertiesAndOrCollectionsGui() {
+ final EntityModel model = getModel();
+ final ObjectAdapter adapter = model.getObject();
+ if (adapter != null) {
+ form = new EntityPropertiesForm(ID_ENTITY_PROPERTIES, model, this);
+ addOrReplace(form);
+ } else {
+ permanentlyHide(ID_ENTITY_PROPERTIES);
+ }
+ }
+}
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/entity/properties/EntityPropertiesPanelFactory.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/properties/EntityPropertiesPanelFactory.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/properties/EntityPropertiesPanelFactory.java
new file mode 100644
index 0000000..a236bd4
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/properties/EntityPropertiesPanelFactory.java
@@ -0,0 +1,46 @@
+/*
+ * 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.entity.properties;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.model.IModel;
+
+import org.apache.isis.viewer.wicket.model.models.EntityModel;
+import org.apache.isis.viewer.wicket.ui.ComponentFactory;
+import org.apache.isis.viewer.wicket.ui.ComponentType;
+import org.apache.isis.viewer.wicket.ui.components.entity.EntityComponentFactoryAbstract;
+
+/**
+ * {@link ComponentFactory} for {@link EntityPropertiesPanel}.
+ */
+public class EntityPropertiesPanelFactory extends EntityComponentFactoryAbstract {
+
+ private static final long serialVersionUID = 1L;
+
+ public EntityPropertiesPanelFactory() {
+ super(ComponentType.ENTITY_PROPERTIES, EntityPropertiesPanel.class);
+ }
+
+ @Override
+ public Component createComponent(final String id, final IModel<?> model) {
+ final EntityModel entityModel = (EntityModel) model;
+ return new EntityPropertiesPanel(id, entityModel);
+ }
+}
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/entity/selector/links/EntityLinksSelectorPanel.html
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/selector/links/EntityLinksSelectorPanel.html b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/selector/links/EntityLinksSelectorPanel.html
new file mode 100644
index 0000000..988f0a3
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/selector/links/EntityLinksSelectorPanel.html
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns:wicket="http://wicket.apache.org">
+<body>
+<wicket:panel>
+ <div class="linksSelectorPanel">
+ <div class="btn-group viewLinks" wicket:id="views">
+ <button type="button" class="btn btn-xs btn-info">
+ <span wicket:id="viewButtonIcon" class="ViewLinkItem"></span>
+ <span wicket:id="viewButtonTitle" class="ViewLinkItemTitle"></span>
+ </button>
+ <button type="button" class="btn btn-xs btn-info dropdown-toggle" data-toggle="dropdown">
+ <span class="caret"></span>
+ </button>
+ <ul wicket:id="viewList" class="dropdown-menu dropdown-menu-right" role="menu">
+ <li wicket:id="viewItem" class="viewItem">
+ <a href="#" wicket:id="viewLink">
+ <span wicket:id="viewItemIcon" class="ViewLinkItem"></span> <span wicket:id="viewItemTitle" class="ViewLinkItemTitle">[link title]</span>
+ </a>
+ </li>
+ </ul>
+ </div>
+
+ <span wicket:id="additionalLinks"></span>
+
+ <div class="views">
+ <div wicket:id="entity-0" class="entityLinksSelectorPanel entityComponentType"></div>
+ <div wicket:id="entity-1" class="entityLinksSelectorPanel entityComponentType"></div>
+ <div wicket:id="entity-2" class="entityLinksSelectorPanel entityComponentType"></div>
+ <div wicket:id="entity-3" class="entityLinksSelectorPanel entityComponentType"></div>
+ <div wicket:id="entity-4" class="entityLinksSelectorPanel entityComponentType"></div>
+ <div wicket:id="entity-5" class="entityLinksSelectorPanel entityComponentType"></div>
+ <div wicket:id="entity-6" class="entityLinksSelectorPanel entityComponentType"></div>
+ <div wicket:id="entity-7" class="entityLinksSelectorPanel entityComponentType"></div>
+ <div wicket:id="entity-8" class="entityLinksSelectorPanel entityComponentType"></div>
+ <div wicket:id="entity-9" class="entityLinksSelectorPanel entityComponentType"></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/components/entity/selector/links/EntityLinksSelectorPanel.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/selector/links/EntityLinksSelectorPanel.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/selector/links/EntityLinksSelectorPanel.java
new file mode 100644
index 0000000..2bd7cf1
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/entity/selector/links/EntityLinksSelectorPanel.java
@@ -0,0 +1,387 @@
+/*
+ * 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.entity.selector.links;
+
+import de.agilecoders.wicket.core.markup.html.bootstrap.button.Buttons;
+
+import java.util.ArrayList;
+import java.util.List;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.Lists;
+import org.apache.wicket.AttributeModifier;
+import org.apache.wicket.Component;
+import org.apache.wicket.MarkupContainer;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.AjaxLink;
+import org.apache.wicket.event.Broadcast;
+import org.apache.wicket.markup.ComponentTag;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.AbstractLink;
+import org.apache.wicket.markup.html.list.ListItem;
+import org.apache.wicket.markup.html.list.ListView;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.isis.core.commons.lang.StringExtensions;
+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.hints.UiHintPathSignificant;
+import org.apache.isis.viewer.wicket.model.links.LinkAndLabel;
+import org.apache.isis.viewer.wicket.model.links.LinksProvider;
+import org.apache.isis.viewer.wicket.model.models.EntityModel;
+import org.apache.isis.viewer.wicket.ui.CollectionContentsAsFactory;
+import org.apache.isis.viewer.wicket.ui.ComponentFactory;
+import org.apache.isis.viewer.wicket.ui.ComponentType;
+import org.apache.isis.viewer.wicket.ui.components.actionmenu.entityactions.AdditionalLinksPanel;
+import org.apache.isis.viewer.wicket.ui.panels.PanelAbstract;
+import org.apache.isis.viewer.wicket.ui.util.Components;
+import org.apache.isis.viewer.wicket.ui.util.CssClassAppender;
+import org.apache.isis.viewer.wicket.ui.util.CssClassRemover;
+
+/**
+ * Provides a list of links for selecting other views that support
+ * {@link ComponentType#ENTITY} with a backing {@link EntityModel}.
+ *
+ * <p>
+ * TODO: this code could be simplified
+ * (pushed down common code here and for the CollectionsSelectorPanel in order to do so);
+ * haven't simplified this yet because currently there is only one view, so the markup
+ * rendered by this component 'collapses' to just show that underlying view.
+ * </p>
+ */
+public class EntityLinksSelectorPanel extends PanelAbstract<EntityModel> implements UiHintPathSignificant {
+
+
+ private static final long serialVersionUID = 1L;
+
+ private static final String INVISIBLE_CLASS = "link-selector-panel-invisible";
+ private static final int MAX_NUM_UNDERLYING_VIEWS = 10;
+
+ private static final String ID_ADDITIONAL_LINKS = "additionalLinks";
+
+ private static final String ID_VIEWS = "views";
+ private static final String ID_VIEW_LIST = "viewList";
+ private static final String ID_VIEW_LINK = "viewLink";
+ private static final String ID_VIEW_ITEM = "viewItem";
+ private static final String ID_VIEW_ITEM_TITLE = "viewItemTitle";
+ private static final String ID_VIEW_ITEM_ICON = "viewItemIcon";
+
+ private static final String UIHINT_VIEW = "view";
+ private static final String ID_VIEW_BUTTON_TITLE = "viewButtonTitle";
+ private static final String ID_VIEW_BUTTON_ICON = "viewButtonIcon";
+
+ private final ComponentType componentType;
+ private final String underlyingIdPrefix;
+
+ private ComponentFactory selectedComponentFactory;
+ protected Component selectedComponent;
+
+
+ public EntityLinksSelectorPanel(final String id, final EntityModel model, final ComponentFactory factory) {
+ super(id, model);
+ this.underlyingIdPrefix = ComponentType.ENTITY.toString();
+ this.componentType = factory.getComponentType();
+ }
+
+
+ protected int determineInitialFactory(List<ComponentFactory> componentFactories, IModel<?> model) {
+ return 0;
+ }
+
+ @Override
+ public UiHintContainer getUiHintContainer() {
+ // disables hinting by this component
+ return null;
+ }
+
+
+ /**
+ * Build UI only after added to parent.
+ */
+ public void onInitialize() {
+ super.onInitialize();
+ ComponentFactory componentFactory = getComponentFactoryRegistry().findComponentFactoryElseFailFast(getComponentType(), getModel());
+ addAdditionalLinks(getModel());
+ addUnderlyingViews(underlyingIdPrefix, getModel(), componentFactory);
+ }
+
+ protected void addAdditionalLinks(final EntityModel model) {
+ if(!(model instanceof LinksProvider)) {
+ permanentlyHide(ID_ADDITIONAL_LINKS);
+ return;
+ }
+ LinksProvider linksProvider = (LinksProvider) model;
+ List<LinkAndLabel> links = linksProvider.getLinks();
+
+ addAdditionalLinks(this, links);
+ }
+
+ protected void addAdditionalLinks(MarkupContainer markupContainer, List<LinkAndLabel> links) {
+ if(links == null || links.isEmpty()) {
+ Components.permanentlyHide(markupContainer, ID_ADDITIONAL_LINKS);
+ return;
+ }
+ links = Lists.newArrayList(links); // copy, to serialize any lazy evaluation
+
+ AdditionalLinksPanel.addAdditionalLinks(
+ markupContainer, ID_ADDITIONAL_LINKS,
+ links,
+ AdditionalLinksPanel.Style.INLINE_LIST);
+ }
+
+ private void addUnderlyingViews(final String underlyingIdPrefix, final EntityModel model, final ComponentFactory factory) {
+ final List<ComponentFactory> componentFactories = findOtherComponentFactories(model, factory);
+
+ final int selected = honourViewHintElseDefault(componentFactories, model);
+
+ final EntityLinksSelectorPanel selectorPanel = this;
+
+ // create all, hide the one not selected
+ final Component[] underlyingViews = new Component[MAX_NUM_UNDERLYING_VIEWS];
+ int i = 0;
+ final EntityModel emptyModel = dummyOf(model);
+ for (ComponentFactory componentFactory : componentFactories) {
+ final String underlyingId = underlyingIdPrefix + "-" + i;
+
+ Component underlyingView = componentFactory.createComponent(underlyingId,i==selected? model: emptyModel);
+ underlyingViews[i++] = underlyingView;
+ selectorPanel.addOrReplace(underlyingView);
+ }
+
+ // hide any unused placeholders
+ while(i<MAX_NUM_UNDERLYING_VIEWS) {
+ String underlyingId = underlyingIdPrefix + "-" + i;
+ permanentlyHide(underlyingId);
+ i++;
+ }
+
+ // selector
+ if (componentFactories.size() <= 1) {
+ permanentlyHide(ID_VIEWS);
+ } else {
+ final Model<ComponentFactory> componentFactoryModel = new Model<>();
+
+ selectorPanel.selectedComponentFactory = componentFactories.get(selected);
+ componentFactoryModel.setObject(selectorPanel.selectedComponentFactory);
+
+ final WebMarkupContainer views = new WebMarkupContainer(ID_VIEWS);
+
+ final Label viewButtonTitle = new Label(ID_VIEW_BUTTON_TITLE, "Hidden");
+ views.addOrReplace(viewButtonTitle);
+
+ final Label viewButtonIcon = new Label(ID_VIEW_BUTTON_ICON, "");
+ views.addOrReplace(viewButtonIcon);
+
+ final WebMarkupContainer container = new WebMarkupContainer(ID_VIEW_LIST);
+
+ views.addOrReplace(container);
+ views.setOutputMarkupId(true);
+
+ this.setOutputMarkupId(true);
+
+ final ListView<ComponentFactory> listView = new ListView<ComponentFactory>(ID_VIEW_ITEM, componentFactories) {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected void populateItem(ListItem<ComponentFactory> item) {
+
+ final int underlyingViewNum = item.getIndex();
+
+ final ComponentFactory componentFactory = item.getModelObject();
+ final AbstractLink link = new AjaxLink<Void>(ID_VIEW_LINK) {
+ private static final long serialVersionUID = 1L;
+ @Override
+ public void onClick(AjaxRequestTarget target) {
+ EntityLinksSelectorPanel linksSelectorPanel = EntityLinksSelectorPanel.this;
+ linksSelectorPanel.setViewHintAndBroadcast(underlyingViewNum, target);
+
+ final EntityModel dummyModel = dummyOf(model);
+ for(int i=0; i<MAX_NUM_UNDERLYING_VIEWS; i++) {
+ final Component component = underlyingViews[i];
+ if(component == null) {
+ continue;
+ }
+ final boolean isSelected = i == underlyingViewNum;
+ applyCssVisibility(component, isSelected);
+ component.setDefaultModel(isSelected? model: dummyModel);
+ }
+
+ selectorPanel.selectedComponentFactory = componentFactory;
+ selectorPanel.selectedComponent = underlyingViews[underlyingViewNum];
+ selectorPanel.onSelect(target);
+ target.add(selectorPanel, views);
+ }
+
+ @Override
+ protected void onComponentTag(ComponentTag tag) {
+ super.onComponentTag(tag);
+ Buttons.fixDisabledState(this, tag);
+ }
+ };
+
+ IModel<String> title = nameFor(componentFactory);
+ Label viewItemTitleLabel = new Label(ID_VIEW_ITEM_TITLE, title);
+ link.add(viewItemTitleLabel);
+
+ Label viewItemIcon = new Label(ID_VIEW_ITEM_ICON, "");
+ link.add(viewItemIcon);
+
+ boolean isEnabled = componentFactory != selectorPanel.selectedComponentFactory;
+ if (!isEnabled) {
+ viewButtonTitle.setDefaultModel(title);
+ IModel<String> cssClass = cssClassFor(componentFactory, viewButtonIcon);
+ viewButtonIcon.add(AttributeModifier.replace("class", "ViewLinkItem " + cssClass.getObject()));
+ link.setVisible(false);
+ } else {
+ IModel<String> cssClass = cssClassFor(componentFactory, viewItemIcon);
+ viewItemIcon.add(new CssClassAppender(cssClass));
+ }
+
+ item.add(link);
+ }
+
+ private IModel<String> cssClassFor(final ComponentFactory componentFactory, Label viewIcon) {
+ IModel<String> cssClass = null;
+ if (componentFactory instanceof CollectionContentsAsFactory) {
+ CollectionContentsAsFactory collectionContentsAsFactory = (CollectionContentsAsFactory) componentFactory;
+ cssClass = collectionContentsAsFactory.getCssClass();
+ viewIcon.setDefaultModelObject("");
+ viewIcon.setEscapeModelStrings(true);
+ }
+ if (cssClass == null) {
+ String name = componentFactory.getName();
+ cssClass = Model.of(StringExtensions.asLowerDashed(name));
+ // Small hack: if there is no specific CSS class then we assume that background-image is used
+ // the span.ViewItemLink should have some content to show it
+ // FIX: find a way to do this with CSS (width and height don't seems to help)
+ viewIcon.setDefaultModelObject("     ");
+ viewIcon.setEscapeModelStrings(false);
+ }
+ return cssClass;
+ }
+
+ private IModel<String> nameFor(final ComponentFactory componentFactory) {
+ IModel<String> name = null;
+ if (componentFactory instanceof CollectionContentsAsFactory) {
+ CollectionContentsAsFactory collectionContentsAsFactory = (CollectionContentsAsFactory) componentFactory;
+ name = collectionContentsAsFactory.getTitleLabel();
+ }
+ if (name == null) {
+ name = Model.of(componentFactory.getName());
+ }
+ return name;
+ }
+ };
+ container.add(listView);
+ addOrReplace(views);
+ }
+
+ for(i=0; i<MAX_NUM_UNDERLYING_VIEWS; i++) {
+ Component component = underlyingViews[i];
+ if(component != null) {
+ if(i != selected) {
+ component.add(new CssClassAppender(INVISIBLE_CLASS));
+ } else {
+ selectedComponent = component;
+ }
+ }
+ }
+ }
+
+
+
+ protected void setViewHintAndBroadcast(int viewNum, AjaxRequestTarget target) {
+ final UiHintContainer uiHintContainer = getUiHintContainer();
+ if(uiHintContainer == null) {
+ return;
+ }
+ uiHintContainer.setHint(this, UIHINT_VIEW, ""+viewNum);
+ send(getPage(), Broadcast.EXACT, new IsisUiHintEvent(uiHintContainer, target));
+ }
+
+ /**
+ * Overrideable hook.
+ */
+ protected void onSelect(AjaxRequestTarget target) {
+ }
+
+ /**
+ * Ask for a dummy (empty) {@link Model} to pass into those components that are rendered but will be
+ * made invisible using CSS styling.
+ */
+ protected EntityModel dummyOf(EntityModel model) {
+ return model;
+ }
+
+ protected static void applyCssVisibility(final Component component, final boolean visible) {
+ if(component == null) {
+ return;
+ }
+ AttributeModifier modifier = visible ? new CssClassRemover(INVISIBLE_CLASS) : new CssClassAppender(INVISIBLE_CLASS);
+ component.add(modifier);
+ }
+
+ protected int honourViewHintElseDefault(final List<ComponentFactory> componentFactories, final IModel<?> model) {
+ // honour hints ...
+ final UiHintContainer hintContainer = getUiHintContainer();
+ if(hintContainer != null) {
+ String viewStr = hintContainer.getHint(this, UIHINT_VIEW);
+ if(viewStr != null) {
+ try {
+ int view = Integer.parseInt(viewStr);
+ if(view >= 0 && view < componentFactories.size()) {
+ return view;
+ }
+ } catch(NumberFormatException ex) {
+ // ignore
+ }
+ }
+ }
+
+ // ... else default
+ int initialFactory = determineInitialFactory(componentFactories, model);
+ if(hintContainer != null) {
+ hintContainer.setHint(this, UIHINT_VIEW, ""+initialFactory);
+ // don't broadcast (no AjaxRequestTarget, still configuring initial setup)
+ }
+ return initialFactory;
+ }
+
+
+ private List<ComponentFactory> findOtherComponentFactories(final EntityModel model, final ComponentFactory ignoreFactory) {
+ final List<ComponentFactory> componentFactories = getComponentFactoryRegistry().findComponentFactories(componentType, model);
+ ArrayList<ComponentFactory> otherFactories = Lists.newArrayList(Collections2.filter(componentFactories, new Predicate<ComponentFactory>() {
+ @Override
+ public boolean apply(final ComponentFactory input) {
+ return input != ignoreFactory;
+ }
+ }));
+ return ordered(otherFactories);
+ }
+
+ protected List<ComponentFactory> ordered(List<ComponentFactory> otherFactories) {
+ return otherFactories;
+ }
+
+
+
+}