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:32 UTC

[41/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/bookmarkedpages/BookmarkedPagesPanel.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/bookmarkedpages/BookmarkedPagesPanel.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/bookmarkedpages/BookmarkedPagesPanel.java
new file mode 100644
index 0000000..c9429b5
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/bookmarkedpages/BookmarkedPagesPanel.java
@@ -0,0 +1,239 @@
+/*
+ *  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.bookmarkedpages;
+
+import org.apache.isis.core.metamodel.adapter.oid.RootOid;
+import org.apache.isis.core.metamodel.spec.ObjectSpecId;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.SpecificationLoaderSpi;
+import org.apache.isis.core.runtime.persistence.ObjectNotFoundException;
+import org.apache.isis.core.runtime.system.context.IsisContext;
+import org.apache.isis.viewer.wicket.model.models.BookmarkTreeNode;
+import org.apache.isis.viewer.wicket.model.models.BookmarkedPagesModel;
+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.pages.PageClassRegistry;
+import org.apache.isis.viewer.wicket.ui.panels.PanelAbstract;
+import org.apache.isis.viewer.wicket.ui.util.CssClassAppender;
+import org.apache.isis.viewer.wicket.ui.util.Links;
+import org.apache.wicket.Component;
+import org.apache.wicket.Page;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.AjaxLink;
+import org.apache.wicket.markup.head.CssHeaderItem;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.head.JavaScriptReferenceHeaderItem;
+import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
+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.markup.html.list.ListItem;
+import org.apache.wicket.markup.html.list.ListView;
+import org.apache.wicket.model.AbstractReadOnlyModel;
+import org.apache.wicket.model.IModel;
+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.ResourceReference;
+import org.apache.wicket.util.string.Strings;
+
+import com.google.inject.Inject;
+
+public class BookmarkedPagesPanel extends PanelAbstract<BookmarkedPagesModel> {
+
+    private static final long serialVersionUID = 1L;
+    
+    private static final String ID_BOOKMARK_LIST = "bookmarkList";
+    private static final String ID_BOOKMARKS_HELP_TEXT = "helpText";
+    private static final String ID_BOOKMARKED_PAGE_LINK = "bookmarkedPageLink";
+    private static final String ID_CLEAR_BOOKMARK_LINK = "clearBookmarkLink";
+    private static final String ID_BOOKMARKED_PAGE_ITEM = "bookmarkedPageItem";
+    private static final String ID_BOOKMARKED_PAGE_TITLE = "bookmarkedPageTitle";
+    
+    private static final String ID_BOOKMARKED_PAGE_ICON = "bookmarkedPageImage";
+    
+    private static final String CLEAR_BOOKMARKS = "clearBookmarks";
+
+
+    private static final JavaScriptResourceReference SLIDE_PANEL_JS = new JavaScriptResourceReference(BookmarkedPagesPanel.class, "slide-panel.js");
+
+    @Inject
+    private PageClassRegistry pageClassRegistry;
+
+    public BookmarkedPagesPanel(final String id, final BookmarkedPagesModel bookmarkedPagesModel) {
+        super(id, bookmarkedPagesModel);
+        buildGui();
+    }
+
+    @Override
+    public void renderHead(IHeaderResponse response) {
+        super.renderHead(response);
+
+        response.render(OnDomReadyHeaderItem.forScript("$('.bookmarkRibbon').height($('.navbar.navbar-fixed-top').height()-5);"));
+    }
+
+    private void buildGui() {
+
+        final BookmarkedPagesModel bookmarkedPagesModel = getModel();
+
+        Component helpText = addHelpText(bookmarkedPagesModel);
+        addOrReplace(helpText);
+
+        final WebMarkupContainer container = new WebMarkupContainer(ID_BOOKMARK_LIST) {
+            private static final long serialVersionUID = 1L;
+            @Override
+            public void renderHead(IHeaderResponse response) {
+                response.render(CssHeaderItem.forReference(new CssResourceReference(BookmarkedPagesPanel.class, "BookmarkedPagesPanel.css")));
+                response.render(JavaScriptReferenceHeaderItem.forReference(SLIDE_PANEL_JS));
+            }
+        };
+        // allow to be updated by AjaxLink
+        container.setOutputMarkupId(true); 
+        add(container);
+
+        final AjaxLink<Void> clearAllBookmarksLink = new AjaxLink<Void>(CLEAR_BOOKMARKS){
+
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public void onClick(AjaxRequestTarget target) {
+                BookmarkedPagesPanel.this.getModel().clear();
+                setEnabled(false);
+                target.add(container, this);
+            }
+        };
+        clearAllBookmarksLink.setOutputMarkupId(true);
+        add(clearAllBookmarksLink);
+        clearAllBookmarksLink.setOutputMarkupId(true);
+
+        if(getModel().isEmpty()) {
+            clearAllBookmarksLink.setVisible(false);
+        }
+
+
+        final ListView<BookmarkTreeNode> listView = new ListView<BookmarkTreeNode>(ID_BOOKMARKED_PAGE_ITEM, bookmarkedPagesModel) {
+
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            protected void populateItem(ListItem<BookmarkTreeNode> item) {
+                final BookmarkTreeNode node = item.getModelObject();
+                try {
+                    final PageType pageType = node.getPageType();
+                    final Class<? extends Page> pageClass = pageClassRegistry.getPageClass(pageType);
+
+                    final AjaxLink<Object> clearBookmarkLink = new AjaxLink<Object>(ID_CLEAR_BOOKMARK_LINK) {
+
+                        private static final long serialVersionUID = 1L;
+                        
+                        @Override
+                        public void onClick(AjaxRequestTarget target) {
+                            bookmarkedPagesModel.remove(node);
+                            if(bookmarkedPagesModel.isEmpty()) {
+                                permanentlyHide(CLEAR_BOOKMARKS);
+                            }
+                            target.add(container, clearAllBookmarksLink);
+                        }
+                        
+                    };
+                    if(node.getDepth() == 0) {
+                        clearBookmarkLink.add(new CssClassAppender("clearBookmark"));
+                    } else {
+                        clearBookmarkLink.setEnabled(true);
+                    }
+                    item.add(clearBookmarkLink);
+                    
+                    PageParameters pageParameters = node.getPageParameters();
+                    final AbstractLink link = Links.newBookmarkablePageLink(ID_BOOKMARKED_PAGE_LINK, pageParameters, pageClass);
+
+                    ObjectSpecification objectSpec = null;
+                    RootOid oid = node.getOidNoVer();
+                    if(oid != null) {
+                        ObjectSpecId objectSpecId = oid.getObjectSpecId();
+                        objectSpec = getSpecificationLoader().lookupBySpecId(objectSpecId);
+                    }
+                    final ResourceReference imageResource = imageCache.resourceReferenceForSpec(objectSpec);
+                    final Image image = new Image(ID_BOOKMARKED_PAGE_ICON, imageResource) {
+                        private static final long serialVersionUID = 1L;
+                        @Override
+                        protected boolean shouldAddAntiCacheParameter() {
+                            return false;
+                        }
+                    };
+                    link.addOrReplace(image);
+
+                    String title = node.getTitle();
+                    final Label label = new Label(ID_BOOKMARKED_PAGE_TITLE, title);
+                    link.add(label);
+                    item.add(link);
+                    if(bookmarkedPagesModel.isCurrent(pageParameters)) {
+                        item.add(new CssClassAppender("disabled"));
+                    }
+                    item.add(new CssClassAppender("bookmarkDepth" + node.getDepth()));
+                } catch(ObjectNotFoundException ex) {
+                    // ignore
+                    // this is a partial fix for an infinite redirect loop.
+                    // should be a bit smarter here, though; see ISIS-596.
+                }
+
+            }
+        };
+        container.add(listView);
+    }
+
+    protected Component addHelpText(final BookmarkedPagesModel bookmarkedPagesModel) {
+
+        IModel<String> helpTextModel = new AbstractReadOnlyModel<String>() {
+            @Override
+            public String getObject() {
+                return bookmarkedPagesModel.isEmpty() ? "You have no bookmarks!" : "";
+            }
+        };
+
+        Label helpText = new Label(ID_BOOKMARKS_HELP_TEXT, helpTextModel) {
+            @Override
+            protected void onConfigure() {
+                super.onConfigure();
+
+                setVisible(!Strings.isEmpty(getDefaultModelObjectAsString()));
+            }
+        };
+        helpText.setOutputMarkupPlaceholderTag(true);
+        return helpText;
+    }
+
+    // ///////////////////////////////////////////////
+    // Dependency Injection
+    // ///////////////////////////////////////////////
+
+    @Inject
+    private ImageResourceCache imageCache;
+
+    protected ImageResourceCache getImageCache() {
+        return imageCache;
+    }
+
+    
+    protected SpecificationLoaderSpi getSpecificationLoader() {
+        return IsisContext.getSpecificationLoader();
+    }
+
+}

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/bookmarkedpages/BookmarkedPagesPanelFactory.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/bookmarkedpages/BookmarkedPagesPanelFactory.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/bookmarkedpages/BookmarkedPagesPanelFactory.java
new file mode 100644
index 0000000..06378bc
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/bookmarkedpages/BookmarkedPagesPanelFactory.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.bookmarkedpages;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.model.IModel;
+import org.apache.isis.viewer.wicket.model.models.BookmarkedPagesModel;
+import org.apache.isis.viewer.wicket.ui.ComponentFactoryAbstract;
+import org.apache.isis.viewer.wicket.ui.ComponentType;
+
+public class BookmarkedPagesPanelFactory extends ComponentFactoryAbstract {
+
+    private static final long serialVersionUID = 1L;
+
+    public BookmarkedPagesPanelFactory() {
+        super(ComponentType.BOOKMARKED_PAGES, BookmarkedPagesPanel.class);
+    }
+
+    @Override
+    public ApplicationAdvice appliesTo(final IModel<?> model) {
+        return appliesIf(model instanceof BookmarkedPagesModel);
+    }
+
+    @Override
+    public Component createComponent(final String id, final IModel<?> model) {
+        final BookmarkedPagesModel bookmarkedPagesModel = (BookmarkedPagesModel) model;
+        return new BookmarkedPagesPanel(id, bookmarkedPagesModel);
+    }
+
+}

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/bookmarkedpages/images/clear_bookmarks.png
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/bookmarkedpages/images/clear_bookmarks.png b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/bookmarkedpages/images/clear_bookmarks.png
new file mode 100644
index 0000000..b798031
Binary files /dev/null and b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/bookmarkedpages/images/clear_bookmarks.png differ

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/bookmarkedpages/slide-panel.js
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/bookmarkedpages/slide-panel.js b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/bookmarkedpages/slide-panel.js
new file mode 100644
index 0000000..ca5bbac
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/bookmarkedpages/slide-panel.js
@@ -0,0 +1,53 @@
+/*
+ *  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.
+ */
+$(function(){
+
+    'use strict';
+
+    var showBookmarks = function(){
+        $('#bookmarkedPagesSlidingDiv').slideDown('50');
+        $('.bookmarkRibbon').fadeOut(50);
+    };
+
+    var hideBookmarks = function(){
+        $('#bookmarkedPagesSlidingDiv').slideUp('50');
+        $('.bookmarkRibbon').fadeIn(50);
+     };
+
+     var hideBookmarksQuickly = function() {
+         $('#bookmarkedPagesSlidingDiv').hide();
+         $('.bookmarkRibbon').show();
+      };
+
+    $('.bookmarkRibbon').mouseenter(showBookmarks);
+    $('#bookmarkedPagesSlidingDiv').mouseleave(hideBookmarks);
+    
+    $('body').keydown(function(e) {
+        // alt+[
+        if(e.which === 219 && e.altKey) {
+            if($('#bookmarkedPagesSlidingDiv').is(":visible")) {
+                hideBookmarksQuickly();
+            } else {
+                showBookmarks();
+            }
+        } else if (e.which === 27) {
+            hideBookmarksQuickly();
+        }
+    });
+});

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/collection/CollectionPanel.html
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/CollectionPanel.html b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/CollectionPanel.html
new file mode 100644
index 0000000..569d0a6
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/CollectionPanel.html
@@ -0,0 +1,33 @@
+<?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="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.4-strict.dtd"  
+      xml:lang="en"  
+      lang="en">
+    <body>
+        <wicket:panel>
+            <div class="collectionPanel collectionNameAndContentsComponentType">
+                <div class="collectionContents" wicket:id="collectionContents"></div>
+                <span wicket:id="feedback"></span>
+            </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/collection/CollectionPanel.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/CollectionPanel.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/CollectionPanel.java
new file mode 100644
index 0000000..1ae3b3b
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/CollectionPanel.java
@@ -0,0 +1,130 @@
+/*
+ *  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.collection;
+
+import de.agilecoders.wicket.core.markup.html.bootstrap.common.NotificationPanel;
+
+import java.util.List;
+import com.google.common.collect.Lists;
+import org.apache.wicket.Component;
+import org.apache.wicket.feedback.ComponentFeedbackMessageFilter;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
+import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
+import org.apache.isis.core.runtime.system.DeploymentType;
+import org.apache.isis.core.runtime.system.context.IsisContext;
+import org.apache.isis.viewer.wicket.model.links.LinkAndLabel;
+import org.apache.isis.viewer.wicket.model.models.EntityCollectionModel;
+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.actionmenu.entityactions.EntityActionUtil;
+import org.apache.isis.viewer.wicket.ui.components.collection.selector.CollectionSelectorPanel;
+import org.apache.isis.viewer.wicket.ui.components.collection.selector.CollectionSelectorProvider;
+import org.apache.isis.viewer.wicket.ui.components.scalars.ScalarPanelAbstract;
+import org.apache.isis.viewer.wicket.ui.panels.PanelAbstract;
+
+/**
+ * Panel for rendering entity collection; analogous to (any concrete subclass
+ * of) {@link ScalarPanelAbstract}.
+ */
+public class CollectionPanel extends PanelAbstract<EntityCollectionModel> implements CollectionSelectorProvider {
+
+
+    private static final long serialVersionUID = 1L;
+
+    private static final String ID_FEEDBACK = "feedback";
+
+    private Component collectionContents;
+
+    private Label label;
+
+    public CollectionPanel(final String id, final EntityModel entityModel, OneToManyAssociation otma) {
+        this(id, newEntityCollectionModel(entityModel, otma), entityModel, otma);
+    }
+
+    private static EntityCollectionModel newEntityCollectionModel(final EntityModel entityModel, OneToManyAssociation otma) {
+        EntityCollectionModel collectionModel = EntityCollectionModel.createParented(entityModel, otma);
+        return collectionModel;
+    }
+
+    CollectionPanel(
+            final String id,
+            final EntityCollectionModel collectionModel) {
+        this(id, collectionModel, new EntityModel(collectionModel.getParentObjectAdapterMemento()), collectionModel.getCollectionMemento().getCollection());
+    }
+
+    CollectionPanel(
+            final String id,
+            final EntityCollectionModel collectionModel,
+            final EntityModel entityModel,
+            final OneToManyAssociation otma) {
+        super(id, collectionModel);
+
+        final List<LinkAndLabel> entityActionLinks = Lists.newArrayList();
+
+        final List<ObjectAction> associatedActions = EntityActionUtil.getObjectActionsForAssociation(entityModel, otma, getDeploymentType());
+
+        entityActionLinks.addAll(EntityActionUtil.asLinkAndLabelsForAdditionalLinksPanel(entityModel, associatedActions));
+
+        collectionModel.addEntityActions(entityActionLinks);
+    }
+
+    @Override
+    protected void onInitialize() {
+        super.onInitialize();
+        buildGui();
+    }
+
+    private void buildGui() {
+        collectionContents = getComponentFactoryRegistry().addOrReplaceComponent(this, ComponentType.COLLECTION_CONTENTS, getModel());
+
+        addOrReplace(new NotificationPanel(ID_FEEDBACK, collectionContents, new ComponentFeedbackMessageFilter(collectionContents)));
+    }
+
+    public Label createLabel(final String id, final String collectionName) {
+        this.label = new Label(id, collectionName);
+        label.setOutputMarkupId(true);
+        return this.label;
+    }
+
+    //region > SelectorDropdownPanel (impl)
+
+    private CollectionSelectorPanel selectorDropdownPanel;
+
+    @Override
+    public CollectionSelectorPanel getSelectorDropdownPanel() {
+        return selectorDropdownPanel;
+    }
+    public void setSelectorDropdownPanel(CollectionSelectorPanel selectorDropdownPanel) {
+        this.selectorDropdownPanel = selectorDropdownPanel;
+    }
+    //endregion
+
+
+
+    //region > dependencies
+
+    protected DeploymentType getDeploymentType() {
+        return IsisContext.getDeploymentType();
+    }
+
+    //endregion
+
+}

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/collection/CollectionPanelFactory.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/CollectionPanelFactory.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/CollectionPanelFactory.java
new file mode 100644
index 0000000..9a9f6e2
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/CollectionPanelFactory.java
@@ -0,0 +1,56 @@
+/*
+ *  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.collection;
+
+import org.apache.wicket.Component;
+import org.apache.wicket.model.IModel;
+
+import org.apache.isis.viewer.wicket.model.models.EntityCollectionModel;
+import org.apache.isis.viewer.wicket.ui.ComponentFactory;
+import org.apache.isis.viewer.wicket.ui.ComponentFactoryAbstract;
+import org.apache.isis.viewer.wicket.ui.ComponentType;
+
+/**
+ * {@link ComponentFactory} for {@link CollectionPanel}.
+ */
+public class CollectionPanelFactory extends ComponentFactoryAbstract {
+
+    private static final long serialVersionUID = 1L;
+
+    private static final String NAME = "labelled";
+
+    public CollectionPanelFactory() {
+        super(ComponentType.COLLECTION_NAME_AND_CONTENTS, NAME, CollectionPanel.class);
+    }
+
+    @Override
+    public ApplicationAdvice appliesTo(final IModel<?> model) {
+        if (!(model instanceof EntityCollectionModel)) {
+            return ApplicationAdvice.DOES_NOT_APPLY;
+        }
+        return ApplicationAdvice.APPLIES;
+    }
+
+    @Override
+    public Component createComponent(final String id, final IModel<?> model) {
+        final EntityCollectionModel collectionModel = (EntityCollectionModel) model;
+        return new CollectionPanel(id, collectionModel);
+    }
+}

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/collection/bulk/BulkActionsHelper.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/bulk/BulkActionsHelper.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/bulk/BulkActionsHelper.java
new file mode 100644
index 0000000..b54a4d7
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/bulk/BulkActionsHelper.java
@@ -0,0 +1,102 @@
+/*
+ *  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.collection.bulk;
+
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.List;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import org.apache.isis.applib.filter.Filters;
+import org.apache.isis.core.metamodel.spec.ActionType;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.feature.Contributed;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
+import org.apache.isis.core.runtime.system.context.IsisContext;
+import org.apache.isis.viewer.wicket.model.models.EntityCollectionModel;
+
+public class BulkActionsHelper implements Serializable {
+
+    private final EntityCollectionModel model;
+
+    private static final long serialVersionUID = 1L;
+
+    public BulkActionsHelper(final EntityCollectionModel model) {
+        this.model = model;
+    }
+
+    private EntityCollectionModel getModel() {
+        return model;
+    }
+
+    public List<ObjectAction> getBulkActions() {
+        final EntityCollectionModel model = getModel();
+
+        if(model.isParented()) {
+            return Collections.emptyList();
+        }
+
+        final ObjectSpecification typeSpec = model.getTypeOfSpecification();
+
+        List<ObjectAction> objectActions = typeSpec.getObjectActions(ActionType.USER, Contributed.INCLUDED, Filters.<ObjectAction>any());
+
+        if ( isExploring() || isPrototyping()) {
+            List<ObjectAction> explorationActions = typeSpec.getObjectActions(ActionType.EXPLORATION, Contributed.INCLUDED, Filters.<ObjectAction>any());
+            List<ObjectAction> prototypeActions = typeSpec.getObjectActions(ActionType.PROTOTYPE, Contributed.INCLUDED, Filters.<ObjectAction>any());
+            objectActions.addAll(explorationActions);
+            objectActions.addAll(prototypeActions);
+        }
+        if (isDebugMode()) {
+            List<ObjectAction> debugActions = typeSpec.getObjectActions(ActionType.DEBUG, Contributed.INCLUDED, Filters.<ObjectAction>any());
+            objectActions.addAll(debugActions);
+        }
+
+        List<ObjectAction> flattenedActions = objectActions;
+
+        return Lists.newArrayList(Iterables.filter(flattenedActions, BULK));
+    }
+
+
+    @SuppressWarnings("deprecation")
+    private static final Predicate<ObjectAction> BULK = Filters.asPredicate(ObjectAction.Filters.bulk());
+
+
+    //region > from context
+
+    public boolean isExploring() {
+        return IsisContext.getDeploymentType().isExploring();
+    }
+    public boolean isPrototyping() {
+        return IsisContext.getDeploymentType().isPrototyping();
+    }
+
+    /**
+     * Protected so can be overridden in testing if required.
+     */
+    protected boolean isDebugMode() {
+        // TODO: need to figure out how to switch into debug mode;
+        // probably call a Debug toggle page, and stuff into
+        // Session.getMetaData()
+        return true;
+    }
+
+    //endregion
+
+}

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/collection/bulk/BulkActionsLinkFactory.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/bulk/BulkActionsLinkFactory.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/bulk/BulkActionsLinkFactory.java
new file mode 100644
index 0000000..941d3ea
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/bulk/BulkActionsLinkFactory.java
@@ -0,0 +1,239 @@
+/*
+ *  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.collection.bulk;
+
+import java.util.List;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import org.apache.wicket.Session;
+import org.apache.wicket.markup.html.link.AbstractLink;
+import org.apache.wicket.markup.html.link.Link;
+import org.apache.isis.applib.RecoverableException;
+import org.apache.isis.applib.services.actinvoc.ActionInvocationContext;
+import org.apache.isis.applib.annotation.ActionLayout;
+import org.apache.isis.applib.annotation.Bulk;
+import org.apache.isis.core.metamodel.facets.members.cssclassfa.CssClassFaPosition;
+import org.apache.isis.applib.annotation.InvokedOn;
+import org.apache.isis.applib.services.command.Command;
+import org.apache.isis.applib.services.command.Command.Executor;
+import org.apache.isis.applib.services.command.CommandContext;
+import org.apache.isis.core.commons.authentication.AuthenticationSession;
+import org.apache.isis.core.commons.authentication.AuthenticationSessionProvider;
+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.runtimecontext.ServicesInjector;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
+import org.apache.isis.core.runtime.system.context.IsisContext;
+import org.apache.isis.viewer.wicket.model.links.LinkAndLabel;
+import org.apache.isis.viewer.wicket.model.mementos.ActionMemento;
+import org.apache.isis.viewer.wicket.model.mementos.ObjectAdapterMemento;
+import org.apache.isis.viewer.wicket.model.models.ActionModel;
+import org.apache.isis.viewer.wicket.model.models.EntityCollectionModel;
+import org.apache.isis.viewer.wicket.ui.actionresponse.ActionResultResponse;
+import org.apache.isis.viewer.wicket.ui.actionresponse.ActionResultResponseType;
+import org.apache.isis.viewer.wicket.ui.components.collectioncontents.ajaxtable.columns.ObjectAdapterToggleboxColumn;
+import org.apache.isis.viewer.wicket.ui.components.widgets.linkandlabel.ActionLinkFactory;
+import org.apache.isis.viewer.wicket.ui.errors.JGrowlBehaviour;
+
+public final class BulkActionsLinkFactory implements ActionLinkFactory {
+
+    private static final long serialVersionUID = 1L;
+    private final EntityCollectionModel model;
+
+    private final ObjectAdapterToggleboxColumn toggleboxColumn;
+
+    public BulkActionsLinkFactory(
+            final EntityCollectionModel model,
+            final ObjectAdapterToggleboxColumn toggleboxColumn) {
+        this.model = model;
+        this.toggleboxColumn = toggleboxColumn;
+    }
+
+
+    @Override
+    public LinkAndLabel newLink(
+            final ObjectAdapterMemento objectAdapterMemento,
+            final ObjectAction objectAction,
+            final String linkId) {
+
+        final ActionMemento actionMemento = new ActionMemento(objectAction);
+        final AbstractLink link = new Link<Object>(linkId) {
+
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public void onClick() {
+                final ObjectAction objectAction = actionMemento.getAction();
+                final ConcurrencyChecking concurrencyChecking =
+                        ConcurrencyChecking.concurrencyCheckingFor(objectAction.getSemantics());
+
+                try {
+                    final List<ObjectAdapterMemento> toggleMementosList = model.getToggleMementosList();
+
+                    final Iterable<ObjectAdapter> toggledAdapters =
+                            Iterables.transform(toggleMementosList, ObjectAdapterMemento.Functions.fromMemento(concurrencyChecking));
+
+                    final List<Object> domainObjects = Lists.newArrayList(Iterables.transform(toggledAdapters, ObjectAdapter.Functions.getObject()));
+
+
+                    final ActionInvocationContext actionInvocationContext = getServicesInjector().lookupService(ActionInvocationContext.class);
+                    if (actionInvocationContext != null) {
+                        actionInvocationContext.setInvokedOn(InvokedOn.COLLECTION);
+                        actionInvocationContext.setDomainObjects(domainObjects);
+                    }
+
+                    final Bulk.InteractionContext bulkInteractionContext = getServicesInjector().lookupService(Bulk.InteractionContext.class);
+                    if (bulkInteractionContext != null) {
+                        bulkInteractionContext.setInvokedAs(Bulk.InteractionContext.InvokedAs.BULK);
+                        bulkInteractionContext.setDomainObjects(domainObjects);
+                    }
+
+                    final CommandContext commandContext = getServicesInjector().lookupService(CommandContext.class);
+                    final Command command;
+                    if (commandContext != null) {
+                        command = commandContext.getCommand();
+                        command.setExecutor(Executor.USER);
+                    }
+
+
+                    ObjectAdapter lastReturnedAdapter = null;
+                    int i=0;
+                    for(final ObjectAdapter adapter : toggledAdapters) {
+
+                        int numParameters = objectAction.getParameterCount();
+                        if(numParameters != 0) {
+                            return;
+                        }
+                        if (bulkInteractionContext != null) {
+                            bulkInteractionContext.setIndex(i++);
+                        }
+
+                        lastReturnedAdapter = objectAction.executeWithRuleChecking(adapter, new ObjectAdapter[]{}, getAuthenticationSession(), ActionModel.WHERE_FOR_ACTION_INVOCATION);
+                    }
+
+
+
+                    model.clearToggleMementosList();
+                    toggleboxColumn.clearToggles();
+                    final ActionModel actionModelHint = model.getActionModelHint();
+                    if(actionModelHint != null && actionModelHint.getActionMemento().getAction().getSemantics().isIdempotentInNature()) {
+                        ObjectAdapter resultAdapter = actionModelHint.getObject();
+                        model.setObjectList(resultAdapter);
+                    } else {
+                        model.setObject(persistentAdaptersWithin(model.getObject()));
+                    }
+
+                    if(lastReturnedAdapter != null) {
+                        final ActionResultResponse resultResponse =
+                                ActionResultResponseType.determineAndInterpretResult(actionModelHint, null, lastReturnedAdapter);
+                        resultResponse.getHandlingStrategy().handleResults(this, resultResponse);
+                    }
+
+                } catch(final ConcurrencyException ex) {
+
+                    recover();
+                    // display a warning to the user so that they know that the action wasn't performed
+                    getMessageBroker().addWarning(ex.getMessage());
+                    return;
+
+                } catch(final RuntimeException ex) {
+
+                    final RecoverableException appEx = ActionModel.getApplicationExceptionIfAny(ex);
+                    if (appEx != null) {
+
+                        recover();
+
+                        getMessageBroker().setApplicationError(appEx.getMessage());
+
+                        // there's no need to abort the transaction, it will have already been done
+                        // (in IsisTransactionManager#executeWithinTransaction(...)).
+                        return;
+                    }
+                    throw ex;
+                }
+            }
+
+            private void recover() {
+                // resync with the objectstore
+                final List<ObjectAdapterMemento> toggleMementosList = Lists.newArrayList(model.getToggleMementosList());
+                for (ObjectAdapterMemento oam : toggleMementosList) {
+                    // just requesting the adapter will sync the OAM's version with the objectstore
+                    oam.getObjectAdapter(ConcurrencyChecking.NO_CHECK);
+                }
+
+                // discard any adapters that might have been deleted
+                model.setObject(persistentAdaptersWithin(model.getObject()));
+
+                // attempt to preserve the toggled adapters
+                final List<ObjectAdapter> adapters = model.getObject();
+                model.clearToggleMementosList();
+                for (ObjectAdapterMemento oam : toggleMementosList) {
+                    final ObjectAdapter objectAdapter = oam.getObjectAdapter(ConcurrencyChecking.NO_CHECK);
+                    if(adapters.contains(objectAdapter)) {
+                        // in case it has been deleted...
+                        model.toggleSelectionOn(objectAdapter);
+                    }
+                }
+            }
+
+            private List<ObjectAdapter> persistentAdaptersWithin(List<ObjectAdapter> adapters) {
+                return Lists.newArrayList(Iterables.filter(adapters, new Predicate<ObjectAdapter>() {
+                    @Override
+                    public boolean apply(ObjectAdapter input) {
+                        return !input.isTransient() && !input.isDestroyed();
+                    }
+                }));
+            }
+
+        };
+        link.add(new JGrowlBehaviour());
+
+        final boolean explorationOrPrototype = ObjectAction.Utils.isExplorationOrPrototype(objectAction);
+        final String actionIdentifier = ObjectAction.Utils.actionIdentifierFor(objectAction);
+        final String description = ObjectAction.Utils.descriptionOf(objectAction);
+        final String cssClass = ObjectAction.Utils.cssClassFor(objectAction, null);
+        final String cssClassFa = ObjectAction.Utils.cssClassFaFor(objectAction);
+        final CssClassFaPosition cssClassFaPosition = ObjectAction.Utils.cssClassFaPositionFor(objectAction);
+        final ActionLayout.Position position = ObjectAction.Utils.actionLayoutPositionOf(objectAction);
+
+        return new LinkAndLabel(link, objectAction.getName(), null, description, false, explorationOrPrototype, actionIdentifier, cssClass, cssClassFa, cssClassFaPosition, position);
+    }
+
+
+    ///////////////////////////////////////////////////////
+    // Dependencies (from context)
+    ///////////////////////////////////////////////////////
+
+    public AuthenticationSession getAuthenticationSession() {
+        final AuthenticationSessionProvider asa = (AuthenticationSessionProvider) Session.get();
+        return asa.getAuthenticationSession();
+    }
+
+    protected MessageBroker getMessageBroker() {
+        return getAuthenticationSession().getMessageBroker();
+    }
+
+    protected ServicesInjector getServicesInjector() {
+        return IsisContext.getPersistenceSession().getServicesInjector();
+    }
+
+}

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/collection/bulk/BulkActionsProvider.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/bulk/BulkActionsProvider.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/bulk/BulkActionsProvider.java
new file mode 100644
index 0000000..39f2b96
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/bulk/BulkActionsProvider.java
@@ -0,0 +1,28 @@
+/*
+ *  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.collection.bulk;
+
+import org.apache.isis.viewer.wicket.ui.components.collectioncontents.ajaxtable.columns.ObjectAdapterToggleboxColumn;
+
+public interface BulkActionsProvider {
+
+    void configureBulkActions(ObjectAdapterToggleboxColumn toggleboxColumn);
+
+    ObjectAdapterToggleboxColumn createToggleboxColumn();
+}

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/collection/count/CollectionCountProvider.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/count/CollectionCountProvider.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/count/CollectionCountProvider.java
new file mode 100644
index 0000000..0ff411f
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/count/CollectionCountProvider.java
@@ -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.
+ */
+package org.apache.isis.viewer.wicket.ui.components.collection.count;
+
+public interface CollectionCountProvider {
+    Integer getCount();
+}

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/collection/selector/CollectionSelectorHelper.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/selector/CollectionSelectorHelper.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/selector/CollectionSelectorHelper.java
new file mode 100644
index 0000000..a600923
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/selector/CollectionSelectorHelper.java
@@ -0,0 +1,169 @@
+/*
+ *  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.collection.selector;
+
+import java.io.Serializable;
+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.Component;
+import org.apache.wicket.model.IModel;
+import org.apache.isis.applib.annotation.Render;
+import org.apache.isis.core.metamodel.facets.members.render.RenderFacet;
+import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
+import org.apache.isis.viewer.wicket.model.hints.UiHintContainer;
+import org.apache.isis.viewer.wicket.model.models.EntityCollectionModel;
+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.app.registry.ComponentFactoryRegistry;
+import org.apache.isis.viewer.wicket.ui.components.collectioncontents.ajaxtable.CollectionContentsAsAjaxTablePanelFactory;
+import org.apache.isis.viewer.wicket.ui.components.collectioncontents.multiple.CollectionContentsMultipleViewsPanelFactory;
+import org.apache.isis.viewer.wicket.ui.components.collectioncontents.unresolved.CollectionContentsAsUnresolvedPanelFactory;
+
+public class CollectionSelectorHelper implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    static final String UIHINT_EVENT_VIEW_KEY = "view";
+
+    private final EntityCollectionModel model;
+    private final List<ComponentFactory> componentFactories;
+
+    public CollectionSelectorHelper(
+            final EntityCollectionModel model,
+            final ComponentFactoryRegistry componentFactoryRegistry) {
+        this.model = model;
+        this.componentFactories = locateComponentFactories(componentFactoryRegistry);
+    }
+
+    private List<ComponentFactory> locateComponentFactories(ComponentFactoryRegistry componentFactoryRegistry) {
+        final List<ComponentFactory> componentFactories = componentFactoryRegistry.findComponentFactories(ComponentType.COLLECTION_CONTENTS, model);
+        List<ComponentFactory> otherFactories = Lists.newArrayList(Collections2.filter(componentFactories, new Predicate<ComponentFactory>() {
+            @Override
+            public boolean apply(final ComponentFactory input) {
+                return input.getClass() != CollectionContentsMultipleViewsPanelFactory.class;
+            }
+        }));
+        return ordered(otherFactories);
+    }
+
+    public List<ComponentFactory> getComponentFactories() {
+        return componentFactories;
+    }
+
+    public int honourViewHintElseDefault(final Component component) {
+        // honour hints ...
+        final UiHintContainer hintContainer = getUiHintContainer(component);
+        if(hintContainer != null) {
+            String viewStr = hintContainer.getHint(component, UIHINT_EVENT_VIEW_KEY);
+            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();
+        if(hintContainer != null) {
+            hintContainer.setHint(component, UIHINT_EVENT_VIEW_KEY, ""+initialFactory);
+            // don't broadcast (no AjaxRequestTarget, still configuring initial setup)
+        }
+        return initialFactory;
+    }
+
+    //region > helpers
+
+    /**
+     * return the index of {@link org.apache.isis.viewer.wicket.ui.components.collectioncontents.unresolved.CollectionContentsAsUnresolvedPanelFactory unresolved panel} if present and not eager loading;
+     * else the index of {@link org.apache.isis.viewer.wicket.ui.components.collectioncontents.ajaxtable.CollectionContentsAsAjaxTablePanelFactory ajax table} if present,
+     * otherwise first factory.
+     */
+    private int determineInitialFactory() {
+        if(!hasRenderEagerlyFacet(model)) {
+            for(int i=0; i<componentFactories.size(); i++) {
+                if(componentFactories.get(i) instanceof CollectionContentsAsUnresolvedPanelFactory) {
+                    return i;
+                }
+            }
+        }
+        int ajaxTableIdx = findAjaxTable(componentFactories);
+        if(ajaxTableIdx>=0) {
+            return ajaxTableIdx;
+        }
+        return 0;
+    }
+
+    private static List<ComponentFactory> ordered(List<ComponentFactory> componentFactories) {
+        return orderAjaxTableToEnd(componentFactories);
+    }
+
+    static List<ComponentFactory> orderAjaxTableToEnd(List<ComponentFactory> componentFactories) {
+        int ajaxTableIdx = findAjaxTable(componentFactories);
+        if(ajaxTableIdx>=0) {
+            List<ComponentFactory> orderedFactories = Lists.newArrayList(componentFactories);
+            ComponentFactory ajaxTableFactory = orderedFactories.remove(ajaxTableIdx);
+            orderedFactories.add(ajaxTableFactory);
+            return orderedFactories;
+        } else {
+            return componentFactories;
+        }
+    }
+
+    private static int findAjaxTable(List<ComponentFactory> componentFactories) {
+        for(int i=0; i<componentFactories.size(); i++) {
+            if(componentFactories.get(i) instanceof CollectionContentsAsAjaxTablePanelFactory) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+
+    private static UiHintContainer getUiHintContainer(final Component component) {
+        return UiHintContainer.Util.hintContainerOf(component, EntityModel.class);
+    }
+
+
+    private static boolean hasRenderEagerlyFacet(IModel<?> model) {
+        if(!(model instanceof EntityCollectionModel)) {
+            return false;
+        }
+        final EntityCollectionModel entityCollectionModel = (EntityCollectionModel) model;
+        if(!entityCollectionModel.isParented()) {
+            return false;
+        }
+
+        final OneToManyAssociation collection =
+                entityCollectionModel.getCollectionMemento().getCollection();
+        RenderFacet renderFacet = collection.getFacet(RenderFacet.class);
+        return renderFacet != null && renderFacet.value() == Render.Type.EAGERLY;
+    }
+
+    //endregion
+
+}

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/collection/selector/CollectionSelectorPanel.html
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/selector/CollectionSelectorPanel.html b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/selector/CollectionSelectorPanel.html
new file mode 100644
index 0000000..8bec51a
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/selector/CollectionSelectorPanel.html
@@ -0,0 +1,44 @@
+<?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-sm btn-info">
+                <span wicket:id="viewButtonIcon" class="ViewLinkItem"></span>
+                <span wicket:id="viewButtonTitle" class="ViewLinkItemTitle"></span>
+            </button>
+            <button type="button" class="btn btn-sm 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>
+    </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/collection/selector/CollectionSelectorPanel.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/selector/CollectionSelectorPanel.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/selector/CollectionSelectorPanel.java
new file mode 100644
index 0000000..02aa720
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/selector/CollectionSelectorPanel.java
@@ -0,0 +1,213 @@
+/*
+ *  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.collection.selector;
+
+import de.agilecoders.wicket.core.markup.html.bootstrap.button.Buttons;
+
+import java.util.List;
+import org.apache.wicket.AttributeModifier;
+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.models.EntityCollectionModel;
+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.panels.PanelAbstract;
+import org.apache.isis.viewer.wicket.ui.util.CssClassAppender;
+
+/**
+ * Provides a list of links for selecting other views that support
+ * {@link org.apache.isis.viewer.wicket.ui.ComponentType#COLLECTION_CONTENTS} with a backing
+ * {@link org.apache.isis.viewer.wicket.model.models.EntityCollectionModel}.
+ */
+public class CollectionSelectorPanel
+        extends PanelAbstract<EntityCollectionModel> implements UiHintPathSignificant {
+
+    private static final long serialVersionUID = 1L;
+
+    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 ID_VIEW_BUTTON_TITLE = "viewButtonTitle";
+    private static final String ID_VIEW_BUTTON_ICON = "viewButtonIcon";
+
+    private final CollectionSelectorHelper selectorHelper;
+
+    private ComponentFactory selectedComponentFactory;
+
+    public CollectionSelectorPanel(final String id, final EntityCollectionModel model) {
+        super(id, model);
+        selectorHelper = new CollectionSelectorHelper(model, getComponentFactoryRegistry());
+    }
+
+    /**
+     * Build UI only after added to parent.
+     */
+    public void onInitialize() {
+        super.onInitialize();
+        addDropdown();
+    }
+
+
+
+    private void addDropdown() {
+        final List<ComponentFactory> componentFactories = selectorHelper.getComponentFactories();
+        final int selected = selectorHelper.honourViewHintElseDefault(this);
+
+        // selector
+        if (componentFactories.size() <= 1) {
+            permanentlyHide(ID_VIEWS);
+        } else {
+            final Model<ComponentFactory> componentFactoryModel = new Model<>();
+
+            this.selectedComponentFactory = componentFactories.get(selected);
+            componentFactoryModel.setObject(this.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) {
+                            CollectionSelectorPanel linksSelectorPanel = CollectionSelectorPanel.this;
+                            linksSelectorPanel.setViewHintAndBroadcast(underlyingViewNum, target);
+
+                            linksSelectorPanel.selectedComponentFactory = componentFactory;
+                            target.add(linksSelectorPanel, 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 != CollectionSelectorPanel.this.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("&#160;&#160;&#160;&#160;&#160;");
+                        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);
+        }
+    }
+
+
+    protected void setViewHintAndBroadcast(int viewNum, AjaxRequestTarget target) {
+        final UiHintContainer uiHintContainer = getUiHintContainer(getModel().isParented()? EntityModel.class: EntityCollectionModel.class);
+        if(uiHintContainer == null) {
+            return;
+        }
+        uiHintContainer.setHint(CollectionSelectorPanel.this, CollectionSelectorHelper.UIHINT_EVENT_VIEW_KEY, ""+viewNum);
+        send(getPage(), Broadcast.EXACT, new IsisUiHintEvent(uiHintContainer, target));
+    }
+}
+
+
+

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/collection/selector/CollectionSelectorProvider.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/selector/CollectionSelectorProvider.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/selector/CollectionSelectorProvider.java
new file mode 100644
index 0000000..340cad2
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/selector/CollectionSelectorProvider.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.collection.selector;
+
+import org.apache.wicket.Component;
+
+public interface CollectionSelectorProvider {
+    CollectionSelectorPanel getSelectorDropdownPanel();
+
+    public static class Util {
+
+        /**
+         * Searches up the component hierarchy looking for a parent that implements
+         * {@link org.apache.isis.viewer.wicket.ui.components.collection.selector.CollectionSelectorProvider}.
+         *
+         * @return the panel, or null (if there are no alternative views)
+         */
+        public static CollectionSelectorPanel getCollectionSelectorProvider(Component component) {
+            while(component != null) {
+                if(component instanceof CollectionSelectorProvider) {
+                    final CollectionSelectorPanel selectorDropdownPanelIfAny = ((CollectionSelectorProvider) component).getSelectorDropdownPanel();
+                    return selectorDropdownPanelIfAny;
+                }
+                component = component.getParent();
+            }
+            throw new IllegalStateException("Could not locate parent that implements CollectionSelectorProvider");
+        }
+    }
+
+}

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/collection/selector/UNUSED_CollectionSelectorPanel.css
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/selector/UNUSED_CollectionSelectorPanel.css b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/selector/UNUSED_CollectionSelectorPanel.css
new file mode 100644
index 0000000..8c3902e
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collection/selector/UNUSED_CollectionSelectorPanel.css
@@ -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.
+ */
+.linksSelectorPanel .viewLinks {
+    float:right;
+    display:block;
+}
+
+.linksSelectorPanel ul.dropdown-menu {
+    min-width: 0;
+}
+
+.linksSelectorPanel ul.dropdown-menu li.viewItem {
+    min-width: 0;
+}
+
+.linksSelectorPanel ul.viewList li.viewItem:first-child span {
+    border-radius:4px 0 0 4px;
+    -moz-border-radius:4px 0 0 4px;
+    -webkit-border-radius:4px 0 0 4px;
+}
+
+.linksSelectorPanel ul.viewList li.viewItem:last-child span {
+    border-radius:0 4px 4px 0;
+    -moz-border-radius:0 4px 4px 0;
+    -webkit-border-radius:0 4px 4px 0;
+}
+
+
+.linksSelectorPanel .link-selector-panel-invisible {
+    display:none;
+}
+
+.linksSelectorPanel .list-inline {
+    margin-left: 0;
+}
+
+.linksSelectorPanel .list-inline li {
+    padding-left: 0;
+}
+
+
+.collectionContentsLinksSelectorPanel select {
+	margin-bottom: 1em;
+}

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/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanel.html
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanel.html b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanel.html
new file mode 100644
index 0000000..cf45f34
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanel.html
@@ -0,0 +1,31 @@
+<?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="collectionContentsAsAjaxTablePanel collectionContentsComponentType">
+                <div class="clearfix"></div>
+                <div class="table-responsive">
+                    <table class="contents table table-striped table-condensed table-hover table-bordered" cellspacing="0" wicket:id="table">[table]</table>
+                </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/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanel.java
----------------------------------------------------------------------
diff --git a/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanel.java b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanel.java
new file mode 100644
index 0000000..a589269
--- /dev/null
+++ b/core/viewer-wicket-ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/CollectionContentsAsAjaxTablePanel.java
@@ -0,0 +1,227 @@
+/*
+ *  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.collectioncontents.ajaxtable;
+
+import java.util.List;
+import com.google.common.collect.Lists;
+import com.google.inject.Inject;
+import org.apache.wicket.Component;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.extensions.ajax.markup.html.repeater.data.table.AjaxFallbackDefaultDataTable;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
+import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
+import org.apache.wicket.model.Model;
+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.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.all.hide.HiddenFacet;
+import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.feature.Contributed;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+import org.apache.isis.viewer.wicket.model.common.OnConcurrencyExceptionHandler;
+import org.apache.isis.viewer.wicket.model.hints.UiHintPathSignificant;
+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.EntityCollectionModel;
+import org.apache.isis.viewer.wicket.ui.components.collection.bulk.BulkActionsProvider;
+import org.apache.isis.viewer.wicket.ui.components.collection.count.CollectionCountProvider;
+import org.apache.isis.viewer.wicket.ui.components.collectioncontents.ajaxtable.columns.ColumnAbstract;
+import org.apache.isis.viewer.wicket.ui.components.collectioncontents.ajaxtable.columns.ObjectAdapterPropertyColumn;
+import org.apache.isis.viewer.wicket.ui.components.collectioncontents.ajaxtable.columns.ObjectAdapterTitleColumn;
+import org.apache.isis.viewer.wicket.ui.components.collectioncontents.ajaxtable.columns.ObjectAdapterToggleboxColumn;
+import org.apache.isis.viewer.wicket.ui.panels.PanelAbstract;
+
+/**
+ * {@link PanelAbstract Panel} that represents a {@link EntityCollectionModel
+ * collection of entity}s rendered using {@link AjaxFallbackDefaultDataTable}.
+ */
+public class CollectionContentsAsAjaxTablePanel extends PanelAbstract<EntityCollectionModel> implements CollectionCountProvider , UiHintPathSignificant {
+
+    private static final long serialVersionUID = 1L;
+
+    private static final String ID_TABLE = "table";
+
+    private IsisAjaxFallbackDataTable<ObjectAdapter,String> dataTable;
+
+
+    public CollectionContentsAsAjaxTablePanel(final String id, final EntityCollectionModel model) {
+        super(id, model);
+    }
+    
+    @Override
+    protected void onInitialize() {
+        super.onInitialize();
+        buildGui();
+    }
+
+    private void buildGui() {
+
+        final List<IColumn<ObjectAdapter,String>> columns = Lists.newArrayList();
+
+        // bulkactions
+        final BulkActionsProvider bulkActionsProvider = getBulkActionsProvider();
+
+        ObjectAdapterToggleboxColumn toggleboxColumn = null;
+        if(bulkActionsProvider != null) {
+
+            toggleboxColumn = bulkActionsProvider.createToggleboxColumn();
+            if(toggleboxColumn != null) {
+                columns.add(toggleboxColumn);
+            }
+            bulkActionsProvider.configureBulkActions(toggleboxColumn);
+        }
+
+        final EntityCollectionModel model = getModel();
+        addTitleColumn(columns, model.getParentObjectAdapterMemento(), getSettings().getMaxTitleLengthInStandaloneTables(), getSettings().getMaxTitleLengthInStandaloneTables());
+        addPropertyColumnsIfRequired(columns);
+
+        final SortableDataProvider<ObjectAdapter,String> dataProvider = new CollectionContentsSortableDataProvider(model);
+        dataTable = new IsisAjaxFallbackDataTable<>(ID_TABLE, columns, dataProvider, model.getPageSize());
+        addOrReplace(dataTable);
+        dataTable.honourHints();
+
+        if(toggleboxColumn != null) {
+            final OnConcurrencyExceptionHandler handler2 = new OnConcurrencyExceptionHandler() {
+
+                private static final long serialVersionUID = 1L;
+
+                @Override
+                public void onConcurrencyException(
+                        final Component context,
+                        final ObjectAdapter selectedAdapter,
+                        final ConcurrencyException ex,
+                        final AjaxRequestTarget ajaxRequestTarget) {
+
+                    // this causes the row to be repainted
+                    // but it isn't possible (yet) to raise any warning
+                    // because that only gets flushed on page refresh.
+                    //
+
+                    // perhaps something to tackle in a separate ticket....
+                    ajaxRequestTarget.add(dataTable);
+                }
+            };
+            toggleboxColumn.setOnConcurrencyExceptionHandler(handler2);
+        }
+    }
+
+    private BulkActionsProvider getBulkActionsProvider() {
+        Component component = this;
+        while(component != null) {
+            if(component instanceof BulkActionsProvider) {
+                return (BulkActionsProvider) component;
+            }
+            component = component.getParent();
+        }
+        return null;
+    }
+
+
+
+    private void addTitleColumn(final List<IColumn<ObjectAdapter,String>> columns, ObjectAdapterMemento parentAdapterMementoIfAny, int maxTitleParented, int maxTitleStandalone) {
+        int maxTitleLength = getModel().isParented()? maxTitleParented: maxTitleStandalone;
+        columns.add(new ObjectAdapterTitleColumn(parentAdapterMementoIfAny, maxTitleLength));
+    }
+
+    private void addPropertyColumnsIfRequired(final List<IColumn<ObjectAdapter,String>> columns) {
+        final ObjectSpecification typeOfSpec = getModel().getTypeOfSpecification();
+
+        final Where whereContext = 
+                getModel().isParented()
+                    ? Where.PARENTED_TABLES
+                    : Where.STANDALONE_TABLES;
+        
+        final ObjectSpecification parentSpecIfAny = 
+                getModel().isParented() 
+                    ? getModel().getParentObjectAdapterMemento().getObjectAdapter(ConcurrencyChecking.NO_CHECK).getSpecification() 
+                    : null;
+        
+        @SuppressWarnings("unchecked")
+        final Filter<ObjectAssociation> filter = Filters.and(
+                ObjectAssociation.Filters.PROPERTIES, 
+                ObjectAssociation.Filters.staticallyVisible(whereContext),
+                associationDoesNotReferenceParent(parentSpecIfAny));
+        
+        final List<? extends ObjectAssociation> propertyList = typeOfSpec.getAssociations(Contributed.INCLUDED, filter);
+        for (final ObjectAssociation property : propertyList) {
+            final ColumnAbstract<ObjectAdapter> nopc = createObjectAdapterPropertyColumn(property);
+            columns.add(nopc);
+        }
+    }
+
+    static Filter<ObjectAssociation> associationDoesNotReferenceParent(final ObjectSpecification parentSpec) {
+        if(parentSpec == null) {
+            return Filters.any();
+        }
+        return new Filter<ObjectAssociation>() {
+            @Override
+            public boolean accept(ObjectAssociation association) {
+                final HiddenFacet facet = association.getFacet(HiddenFacet.class);
+                if(facet == null) {
+                    return true;
+                }
+                if (facet.where() != Where.REFERENCES_PARENT) {
+                    return true;
+                }
+                final ObjectSpecification assocSpec = association.getSpecification();
+                final boolean associationSpecIsOfParentSpec = parentSpec.isOfType(assocSpec);
+                final boolean isVisible = !associationSpecIsOfParentSpec;
+                return isVisible;
+            }
+        };
+    }
+
+    private ObjectAdapterPropertyColumn createObjectAdapterPropertyColumn(final ObjectAssociation property) {
+
+        final NamedFacet facet = property.getFacet(NamedFacet.class);
+        final boolean escaped = facet == null || facet.escaped();
+
+        return new ObjectAdapterPropertyColumn(Model.of(property.getName()), property.getId(), property.getId(), escaped);
+    }
+
+
+
+    @Override
+    protected void onModelChanged() {
+        buildGui();
+    }
+
+    @Override
+    public Integer getCount() {
+        final EntityCollectionModel model = getModel();
+        return model.getCount();
+    }
+
+
+    //region > dependencies
+
+    @Inject
+    private WicketViewerSettings settings;
+    protected WicketViewerSettings getSettings() {
+        return settings;
+    }
+
+    //endregion
+
+}