You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by rm...@apache.org on 2013/03/24 18:45:18 UTC

[21/24] Restructuring Scimpi to remove dependencies and enable easier testing.

http://git-wip-us.apache.org/repos/asf/isis/blob/7700b437/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/Collection.java
----------------------------------------------------------------------
diff --git a/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/Collection.java b/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/Collection.java
index 20e0cc9..e139836 100644
--- a/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/Collection.java
+++ b/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/Collection.java
@@ -27,22 +27,23 @@ import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
 import org.apache.isis.core.runtime.system.context.IsisContext;
 import org.apache.isis.viewer.scimpi.ScimpiException;
 import org.apache.isis.viewer.scimpi.dispatcher.context.Request;
+import org.apache.isis.viewer.scimpi.dispatcher.context.RequestState;
 import org.apache.isis.viewer.scimpi.dispatcher.context.Request.Scope;
-import org.apache.isis.viewer.scimpi.dispatcher.processor.TagProcessor;
-import org.apache.isis.viewer.scimpi.dispatcher.processor.TagProcessor.RepeatMarker;
+import org.apache.isis.viewer.scimpi.dispatcher.processor.TemplateProcessor;
+import org.apache.isis.viewer.scimpi.dispatcher.processor.TemplateProcessor.RepeatMarker;
 import org.apache.isis.viewer.scimpi.dispatcher.view.AbstractElementProcessor;
 
 public class Collection extends AbstractElementProcessor {
 
     @Override
-    public void process(final TagProcessor tagProcessor) {
-        final Request context = tagProcessor.getContext();
+    public void process(final TemplateProcessor templateProcessor, RequestState state) {
+        final Request context = templateProcessor.getContext();
 
         ObjectAdapter collection;
 
-        final String field = tagProcessor.getOptionalProperty(FIELD);
+        final String field = templateProcessor.getOptionalProperty(FIELD);
         if (field != null) {
-            final String id = tagProcessor.getOptionalProperty(OBJECT);
+            final String id = templateProcessor.getOptionalProperty(OBJECT);
             final ObjectAdapter object = context.getMappedObjectOrResult(id);
             final ObjectAssociation objectField = object.getSpecification().getAssociation(field);
             if (!objectField.isOneToManyAssociation()) {
@@ -51,16 +52,16 @@ public class Collection extends AbstractElementProcessor {
             IsisContext.getPersistenceSession().resolveField(object, objectField);
             collection = objectField.get(object);
         } else {
-            final String id = tagProcessor.getOptionalProperty(COLLECTION);
+            final String id = templateProcessor.getOptionalProperty(COLLECTION);
             collection = context.getMappedObjectOrResult(id);
         }
 
-        final RepeatMarker marker = tagProcessor.createMarker();
+        final RepeatMarker marker = templateProcessor.createMarker();
 
-        final String variable = tagProcessor.getOptionalProperty(ELEMENT_NAME);
-        final String scopeName = tagProcessor.getOptionalProperty(SCOPE);
+        final String variable = templateProcessor.getOptionalProperty(ELEMENT_NAME);
+        final String scopeName = templateProcessor.getOptionalProperty(SCOPE);
         final Scope scope = Request.scope(scopeName, Scope.REQUEST);
-        final String rowClassesList = tagProcessor.getOptionalProperty(ROW_CLASSES, ODD_ROW_CLASS + "|" + EVEN_ROW_CLASS);
+        final String rowClassesList = templateProcessor.getOptionalProperty(ROW_CLASSES, ODD_ROW_CLASS + "|" + EVEN_ROW_CLASS);
         String[] rowClasses = new String[0];
         if (rowClassesList != null) {
             rowClasses = rowClassesList.split("[,|/]");
@@ -68,7 +69,7 @@ public class Collection extends AbstractElementProcessor {
 
         final CollectionFacet facet = collection.getSpecification().getFacet(CollectionFacet.class);
         if (facet.size(collection) == 0) {
-            tagProcessor.skipUntilClose();
+            templateProcessor.skipUntilClose();
         } else {
             final Iterator<ObjectAdapter> iterator = facet.iterator(collection);
             int row = 0;
@@ -80,7 +81,7 @@ public class Collection extends AbstractElementProcessor {
                 }
                 context.addVariable(variable, context.mapObject(element, scope), scope);
                 marker.repeat();
-                tagProcessor.processUtilCloseTag();
+                templateProcessor.processUtilCloseTag();
                 row++;
             }
         }

http://git-wip-us.apache.org/repos/asf/isis/blob/7700b437/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/CountElements.java
----------------------------------------------------------------------
diff --git a/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/CountElements.java b/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/CountElements.java
new file mode 100644
index 0000000..086977d
--- /dev/null
+++ b/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/CountElements.java
@@ -0,0 +1,54 @@
+/*
+ *  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.scimpi.dispatcher.view.collection;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.facets.collections.modify.CollectionFacet;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+import org.apache.isis.viewer.scimpi.dispatcher.processor.TemplateProcessor;
+import org.apache.isis.viewer.scimpi.dispatcher.view.AbstractObjectProcessor;
+
+public class CountElements extends AbstractObjectProcessor {
+
+    @Override
+    protected void process(final TemplateProcessor templateProcessor, final ObjectAdapter collection) {
+        final CollectionFacet facet = collection.getSpecification().getFacet(CollectionFacet.class);
+        final int size = facet.size(collection);
+        if (size == 0) {
+            templateProcessor.appendHtml(templateProcessor.getOptionalProperty("none", "0"));
+        } else if (size == 1) {
+            templateProcessor.appendHtml(templateProcessor.getOptionalProperty("one", "1"));
+        } else {
+            final String text = templateProcessor.getOptionalProperty("many", "" + size);
+            templateProcessor.appendHtml(String.format(text, size));
+        }
+    }
+
+    @Override
+    protected String checkFieldType(final ObjectAssociation objectField) {
+        return objectField.isOneToManyAssociation() ? null : "must be a collection";
+    }
+
+    @Override
+    public String getName() {
+        return "count";
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/7700b437/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/ElementType.java
----------------------------------------------------------------------
diff --git a/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/ElementType.java b/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/ElementType.java
new file mode 100644
index 0000000..8038658
--- /dev/null
+++ b/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/ElementType.java
@@ -0,0 +1,62 @@
+/*
+ *  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.scimpi.dispatcher.view.collection;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+import org.apache.isis.viewer.scimpi.ScimpiException;
+import org.apache.isis.viewer.scimpi.dispatcher.context.Request;
+import org.apache.isis.viewer.scimpi.dispatcher.context.RequestState;
+import org.apache.isis.viewer.scimpi.dispatcher.processor.TemplateProcessor;
+import org.apache.isis.viewer.scimpi.dispatcher.view.AbstractElementProcessor;
+
+public class ElementType extends AbstractElementProcessor {
+
+    @Override
+    public void process(final TemplateProcessor templateProcessor, RequestState state) {
+        ObjectAdapter collection;
+        final String field = templateProcessor.getOptionalProperty(FIELD);
+        final Request context = templateProcessor.getContext();
+        if (field != null) {
+            final String id = templateProcessor.getRequiredProperty(OBJECT);
+            final ObjectAdapter object = context.getMappedObjectOrResult(id);
+            final ObjectAssociation objectField = object.getSpecification().getAssociation(field);
+            if (!objectField.isOneToManyAssociation()) {
+                throw new ScimpiException("Field " + objectField.getId() + " is not a collection");
+            }
+            collection = objectField.get(object);
+        } else {
+            final String id = templateProcessor.getOptionalProperty(COLLECTION);
+            collection = context.getMappedObjectOrResult(id);
+        }
+
+        final ObjectSpecification elementSpecification = collection.getElementSpecification();
+        final String name = elementSpecification.getSingularName();
+
+        templateProcessor.appendAsHtmlEncoded(name);
+    }
+
+    @Override
+    public String getName() {
+        return "element-type";
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/7700b437/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/ListView.java
----------------------------------------------------------------------
diff --git a/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/ListView.java b/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/ListView.java
new file mode 100644
index 0000000..1e4627b
--- /dev/null
+++ b/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/ListView.java
@@ -0,0 +1,93 @@
+/*
+ *  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.scimpi.dispatcher.view.collection;
+
+import java.util.Iterator;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.facets.collections.modify.CollectionFacet;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+import org.apache.isis.viewer.scimpi.Names;
+import org.apache.isis.viewer.scimpi.dispatcher.context.Request;
+import org.apache.isis.viewer.scimpi.dispatcher.context.Request.Scope;
+import org.apache.isis.viewer.scimpi.dispatcher.processor.TemplateProcessor;
+import org.apache.isis.viewer.scimpi.dispatcher.view.AbstractObjectProcessor;
+import org.apache.isis.viewer.scimpi.dispatcher.view.determine.LinkedObject;
+
+public class ListView extends AbstractObjectProcessor {
+
+    @Override
+    public String checkFieldType(final ObjectAssociation objectField) {
+        return objectField.isOneToManyAssociation() ? null : "is not a collection";
+    }
+
+    @Override
+    public void process(final TemplateProcessor templateProcessor, final ObjectAdapter object) {
+        final String linkRowView = templateProcessor.getOptionalProperty(LINK_VIEW);
+        final String linkObjectName = templateProcessor.getOptionalProperty(ELEMENT_NAME, Names.RESULT);
+        final String linkObjectScope = templateProcessor.getOptionalProperty(SCOPE, Scope.INTERACTION.toString());
+        LinkedObject linkedRow = null;
+        if (linkRowView != null) {
+            linkedRow = new LinkedObject(linkObjectName, linkObjectScope, templateProcessor.getContext().fullUriPath(linkRowView));
+        }
+        final String bulletType = templateProcessor.getOptionalProperty("type");
+        write(templateProcessor, object, linkedRow, bulletType);
+    }
+
+    public static void write(final TemplateProcessor templateProcessor, final ObjectAdapter collection, final LinkedObject linkRow, final String bulletType) {
+
+        if (bulletType == null) {
+            templateProcessor.appendHtml("<ol>");
+        } else {
+            templateProcessor.appendHtml("<ul type=\"" + bulletType + "\">");
+        }
+
+        final CollectionFacet facet = collection.getSpecification().getFacet(CollectionFacet.class);
+        final Iterator<ObjectAdapter> iterator = facet.iterator(collection);
+        while (iterator.hasNext()) {
+            final ObjectAdapter element = iterator.next();
+
+            templateProcessor.appendHtml("<li>");
+            if (linkRow != null) {
+                final Scope scope = linkRow == null ? Scope.INTERACTION : Request.scope(linkRow.getScope());
+                final String rowId = templateProcessor.getContext().mapObject(element, scope);
+                templateProcessor.appendHtml("<a class=\"item-select\" href=\"" + linkRow.getForwardView() + "?" + linkRow.getVariable() + "=" + rowId + "\">");
+            }
+            templateProcessor.appendAsHtmlEncoded(element.titleString());
+            if (linkRow != null) {
+                templateProcessor.appendHtml("</a>");
+            }
+
+            templateProcessor.appendHtml("</li>\n");
+        }
+        if (bulletType == null) {
+            templateProcessor.appendHtml("</ol>");
+        } else {
+            templateProcessor.appendHtml("</ul>");
+        }
+
+    }
+
+    @Override
+    public String getName() {
+        return "list";
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/7700b437/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/RemoveElement.java
----------------------------------------------------------------------
diff --git a/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/RemoveElement.java b/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/RemoveElement.java
new file mode 100644
index 0000000..0d3830e
--- /dev/null
+++ b/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/RemoveElement.java
@@ -0,0 +1,144 @@
+/*
+ *  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.scimpi.dispatcher.view.collection;
+
+import java.util.List;
+
+import org.apache.isis.applib.annotation.Where;
+import org.apache.isis.applib.filter.Filter;
+import org.apache.isis.core.commons.authentication.AuthenticationSession;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.consent.Consent;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociationFilters;
+import org.apache.isis.core.metamodel.spec.feature.OneToManyAssociation;
+import org.apache.isis.core.runtime.system.context.IsisContext;
+import org.apache.isis.viewer.scimpi.ForbiddenException;
+import org.apache.isis.viewer.scimpi.Names;
+import org.apache.isis.viewer.scimpi.ScimpiException;
+import org.apache.isis.viewer.scimpi.dispatcher.action.RemoveAction;
+import org.apache.isis.viewer.scimpi.dispatcher.context.Request;
+import org.apache.isis.viewer.scimpi.dispatcher.context.RequestState;
+import org.apache.isis.viewer.scimpi.dispatcher.context.Request.Scope;
+import org.apache.isis.viewer.scimpi.dispatcher.processor.TemplateProcessor;
+import org.apache.isis.viewer.scimpi.dispatcher.util.MethodsUtils;
+import org.apache.isis.viewer.scimpi.dispatcher.view.AbstractElementProcessor;
+
+public class RemoveElement extends AbstractElementProcessor {
+
+    // REVIEW: should provide this rendering context, rather than hardcoding.
+    // the net effect currently is that class members annotated with 
+    // @Hidden(where=Where.ANYWHERE) or @Disabled(where=Where.ANYWHERE) will indeed
+    // be hidden/disabled, but will be visible/enabled (perhaps incorrectly) 
+    // for any other value for Where
+    private final static Where where = Where.ANYWHERE;
+
+    @Override
+    public void process(final TemplateProcessor templateProcessor, RequestState state) {
+        final String title = templateProcessor.getOptionalProperty(BUTTON_TITLE, "Remove From List");
+        final String cls = templateProcessor.getOptionalProperty(CLASS, "action in-line delete confirm");
+        final String object = templateProcessor.getOptionalProperty(OBJECT);
+        final String resultOverride = templateProcessor.getOptionalProperty(RESULT_OVERRIDE);
+        final Request context = templateProcessor.getContext();
+        final String objectId = object != null ? object : (String) context.getVariable(Names.RESULT);
+        final ObjectAdapter adapter = MethodsUtils.findObject(context, objectId);
+
+        final String element = templateProcessor.getOptionalProperty(ELEMENT, (String) context.getVariable(ELEMENT));
+        final ObjectAdapter elementId = MethodsUtils.findObject(context, element);
+
+        final String fieldName = templateProcessor.getRequiredProperty(FIELD);
+
+        String view = templateProcessor.getOptionalProperty(VIEW);
+        view = context.fullFilePath(view == null ? context.getResourceFile() : view);
+        String error = templateProcessor.getOptionalProperty(ERROR);
+        error = context.fullFilePath(error == null ? context.getResourceFile() : error);
+
+        templateProcessor.processUtilCloseTag();
+
+        write(templateProcessor, adapter, fieldName, elementId, resultOverride, view, error, title, cls);
+    }
+
+    @Override
+    public String getName() {
+        return "remove-element";
+    }
+
+    public static void write(final TemplateProcessor templateProcessor, final ObjectAdapter adapter, final String fieldName, final ObjectAdapter element, final String resultOverride, final String view, final String error, final String title, final String cssClass) {
+        final ObjectAssociation field = adapter.getSpecification().getAssociation(fieldName);
+        if (field == null) {
+            throw new ScimpiException("No field " + fieldName + " in " + adapter.getSpecification().getFullIdentifier());
+        }
+        if (!field.isOneToManyAssociation()) {
+            throw new ScimpiException("Field " + fieldName + " not a collection, in " + adapter.getSpecification().getFullIdentifier());
+        }
+        if (field.isVisible(IsisContext.getAuthenticationSession(), adapter, where).isVetoed()) {
+            throw new ForbiddenException(field, ForbiddenException.VISIBLE);
+        }
+        IsisContext.getPersistenceSession().resolveField(adapter, field);
+
+        Consent usable = field.isUsable(IsisContext.getAuthenticationSession(), adapter, where);
+        if (usable.isAllowed()) {
+            usable = ((OneToManyAssociation) field).isValidToRemove(adapter, element);
+        }
+
+        if (usable.isVetoed()) {
+            templateProcessor.appendHtml("<div class=\"" + cssClass + " disabled-form\">"); 
+            templateProcessor.appendHtml("<div class=\"button disabled\" title=\""); 
+            templateProcessor.appendAsHtmlEncoded(usable.getReason());
+            templateProcessor.appendHtml("\" >" + title);
+            templateProcessor.appendHtml("</div>");
+            templateProcessor.appendHtml("</div>");
+        } else {
+            if (valid(templateProcessor, adapter)) {
+                final String classSegment = " class=\"" + cssClass + "\"";
+
+                final String objectId = templateProcessor.getContext().mapObject(adapter, Scope.INTERACTION);
+                final String elementId = templateProcessor.getContext().mapObject(element, Scope.INTERACTION);
+                final String action = RemoveAction.ACTION + Names.COMMAND_ROOT;
+                templateProcessor.appendHtml("<form" + classSegment + " method=\"post\" action=\"" + action + "\" >");
+                templateProcessor.appendHtml("<input type=\"hidden\" name=\"" + OBJECT + "\" value=\"" + objectId + "\" />");
+                templateProcessor.appendHtml("<input type=\"hidden\" name=\"" + FIELD + "\" value=\"" + fieldName + "\" />");
+                templateProcessor.appendHtml("<input type=\"hidden\" name=\"" + ELEMENT + "\" value=\"" + elementId + "\" />");
+                if (resultOverride != null) {
+                    templateProcessor.appendHtml("<input type=\"hidden\" name=\"" + RESULT_OVERRIDE + "\" value=\"" + resultOverride + "\" />");
+                }
+                templateProcessor.appendHtml("<input type=\"hidden\" name=\"" + VIEW + "\" value=\"" + view + "\" />");
+                templateProcessor.appendHtml("<input type=\"hidden\" name=\"" + ERROR + "\" value=\"" + error + "\" />");
+                templateProcessor.appendHtml(templateProcessor.getContext().interactionFields());
+                templateProcessor.appendHtml("<input class=\"button\" type=\"submit\" value=\"" + title + "\" />");
+                templateProcessor.appendHtml("</form>");
+            }
+        }
+    }
+
+    private static boolean valid(final TemplateProcessor templateProcessor, final ObjectAdapter adapter) {
+        // TODO is this check valid/necessary?
+
+        // TODO check is valid to remove element
+        final AuthenticationSession session = IsisContext.getAuthenticationSession();
+        final Filter<ObjectAssociation> filter = ObjectAssociationFilters.dynamicallyVisible(session, adapter, where);
+        final List<ObjectAssociation> visibleFields = adapter.getSpecification().getAssociations(filter);
+        if (visibleFields.size() == 0) {
+            return false;
+        }
+
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/7700b437/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/TableBlock.java
----------------------------------------------------------------------
diff --git a/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/TableBlock.java b/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/TableBlock.java
new file mode 100644
index 0000000..7671317
--- /dev/null
+++ b/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/TableBlock.java
@@ -0,0 +1,86 @@
+/*
+ *  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.scimpi.dispatcher.view.collection;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.viewer.scimpi.dispatcher.processor.BlockContent;
+import org.apache.isis.viewer.scimpi.dispatcher.processor.TemplateProcessor.RepeatMarker;
+
+public class TableBlock implements BlockContent {
+
+    // {{ collection
+    private ObjectAdapter collection;
+
+    public void setCollection(final ObjectAdapter collection) {
+        this.collection = collection;
+    }
+
+    public ObjectAdapter getCollection() {
+        return collection;
+    }
+    // }}
+    
+    // {{ linkView
+    private String linkView;
+
+    public String getlinkView() {
+        return linkView;
+    }
+
+    public void setlinkView(final String linkView) {
+        this.linkView = linkView;
+    }
+    // }}
+    
+    // {{ linkName
+    private String linkName;
+
+    public String getlinkName() {
+        return linkName;
+    }
+
+    public void setlinkName(final String linkName) {
+        this.linkName = linkName;
+    }
+    // }}
+
+    // {{ elementName
+    private String elementName;
+
+    public String getElementName() {
+        return elementName;
+    }
+
+    public void setElementName(final String linkObject) {
+        this.elementName = linkObject;
+    }
+    // }}
+
+    private RepeatMarker marker;
+
+    public RepeatMarker getMarker() {
+        return marker;
+    }
+
+    public void setMarker(final RepeatMarker marker) {
+        this.marker = marker;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/7700b437/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/TableBuilder.java
----------------------------------------------------------------------
diff --git a/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/TableBuilder.java b/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/TableBuilder.java
new file mode 100644
index 0000000..7809e74
--- /dev/null
+++ b/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/TableBuilder.java
@@ -0,0 +1,92 @@
+/*
+ *  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.scimpi.dispatcher.view.collection;
+
+import java.util.List;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+import org.apache.isis.viewer.scimpi.dispatcher.context.Request;
+import org.apache.isis.viewer.scimpi.dispatcher.context.Request.Scope;
+import org.apache.isis.viewer.scimpi.dispatcher.processor.PageWriter;
+import org.apache.isis.viewer.scimpi.dispatcher.processor.TemplateProcessor;
+import org.apache.isis.viewer.scimpi.dispatcher.processor.TemplateProcessor.RepeatMarker;
+
+public class TableBuilder extends AbstractTableView {
+
+    @Override
+    protected TableContentWriter createRowBuilder(final TemplateProcessor templateProcessor, final Request context, final String parent, final List<ObjectAssociation> allFields, final ObjectAdapter collection) {
+
+        final String title = templateProcessor.getOptionalProperty(TABLE_TITLE);
+        final String variable = templateProcessor.getOptionalProperty(ELEMENT_NAME, ELEMENT);
+        final String headerClass = templateProcessor.getOptionalProperty("head-" + CLASS);
+
+        final TableBlock block = new TableBlock();
+        block.setCollection(collection);
+        block.setElementName(variable);
+        templateProcessor.pushBlock(block);
+        templateProcessor.pushNewBuffer();
+        templateProcessor.processUtilCloseTag();
+        final String headers = templateProcessor.popBuffer();       
+        return new TableContentWriter() {
+
+            @Override
+            public void writeFooters(final PageWriter writer) {
+            }
+
+            public void tidyUp() {
+                templateProcessor.popBlock();
+            }
+            
+            @Override
+            public void writeCaption(PageWriter writer) {
+                if (title != null) {
+                    writer.appendHtml("<caption>");
+                    writer.appendHtml(title);
+                    writer.appendHtml("</thead>");
+                }
+            }
+            
+            @Override
+            public void writeHeaders(final PageWriter writer) {
+                final String headerSegment = headerClass == null ? "" : (" class=\"" + headerClass + "\"");
+                writer.appendHtml("<thead" + headerSegment + ">");
+                writer.appendHtml(headers);
+                writer.appendHtml("</thead>");
+            }
+
+            @Override
+            public void writeElement(final TemplateProcessor templateProcessor, final Request context, final ObjectAdapter element) {
+                context.addVariable(variable, context.mapObject(element, Scope.REQUEST), Scope.REQUEST);
+                final RepeatMarker end = templateProcessor.createMarker();
+                final RepeatMarker marker = block.getMarker();
+                marker.repeat();
+                templateProcessor.processUtilCloseTag();
+                end.repeat();
+            }
+        };
+    }
+
+    @Override
+    public String getName() {
+        return "table-builder";
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/7700b437/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/TableCell.java
----------------------------------------------------------------------
diff --git a/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/TableCell.java b/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/TableCell.java
new file mode 100644
index 0000000..9160a0a
--- /dev/null
+++ b/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/TableCell.java
@@ -0,0 +1,96 @@
+/*
+ *  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.scimpi.dispatcher.view.collection;
+
+import org.apache.isis.applib.annotation.Where;
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+import org.apache.isis.core.runtime.system.context.IsisContext;
+import org.apache.isis.viewer.scimpi.Names;
+import org.apache.isis.viewer.scimpi.ScimpiException;
+import org.apache.isis.viewer.scimpi.dispatcher.context.Request;
+import org.apache.isis.viewer.scimpi.dispatcher.context.RequestState;
+import org.apache.isis.viewer.scimpi.dispatcher.context.Request.Scope;
+import org.apache.isis.viewer.scimpi.dispatcher.processor.TemplateProcessor;
+import org.apache.isis.viewer.scimpi.dispatcher.view.AbstractElementProcessor;
+
+public class TableCell extends AbstractElementProcessor {
+
+    // REVIEW: should provide this rendering context, rather than hardcoding.
+    // the net effect currently is that class members annotated with
+    // @Hidden(where=Where.ALL_TABLES) or @Disabled(where=Where.ALL_TABLES) will indeed
+    // be hidden from all tables but will be visible/enabled (perhaps incorrectly) 
+    // if annotated with Where.PARENTED_TABLE or Where.STANDALONE_TABLE
+    private final Where where = Where.ALL_TABLES;
+
+    @Override
+    public void process(final TemplateProcessor templateProcessor, RequestState state) {
+        final TableBlock tableBlock = (TableBlock) templateProcessor.peekBlock();
+        final String id = templateProcessor.getOptionalProperty(OBJECT);
+        final String fieldName = templateProcessor.getRequiredProperty(FIELD);
+        final String linkView = templateProcessor.getOptionalProperty(LINK_VIEW);
+        String className = templateProcessor.getOptionalProperty(CLASS);
+        className = className == null ? "" : " class=\"" + className + "\"";
+        Request context = templateProcessor.getContext();
+        final ObjectAdapter object = context.getMappedObjectOrVariable(id, tableBlock.getElementName());
+        final ObjectAssociation field = object.getSpecification().getAssociation(fieldName);
+        if (field == null) {
+            throw new ScimpiException("No field " + fieldName + " in " + object.getSpecification().getFullIdentifier());
+        }
+        templateProcessor.appendHtml("<td" + className + ">");
+        if (field.isVisible(IsisContext.getAuthenticationSession(), object, where).isAllowed()) {
+            final ObjectAdapter fieldReference = field.get(object);
+            final String source = fieldReference == null ? "" : context.mapObject(fieldReference, Scope.REQUEST);
+            final String name = templateProcessor.getOptionalProperty(RESULT_NAME, fieldName);
+            context.addVariable(name, templateProcessor.encodeHtml(source), Scope.REQUEST);
+
+            if (linkView != null) {
+                final String linkId = context.mapObject(object, Scope.REQUEST);
+                final String linkName = templateProcessor.getOptionalProperty(LINK_NAME, Names.RESULT);
+                final String linkObject = templateProcessor.getOptionalProperty(LINK_OBJECT, linkId);
+                templateProcessor.appendHtml("<a href=\"" + linkView + "?" + linkName + "=" + linkObject + context.encodedInteractionParameters() + "\">");
+            } else if(tableBlock.getlinkView() != null) {
+                String linkObjectInVariable = tableBlock.getElementName();
+                final String linkId = (String) context.getVariable(linkObjectInVariable);
+                templateProcessor.appendHtml("<a href=\"" + tableBlock.getlinkView() + "?" + tableBlock.getlinkName() + "=" + linkId + context.encodedInteractionParameters() + "\">");                
+            }
+            templateProcessor.pushNewBuffer();
+            templateProcessor.processUtilCloseTag();
+            final String buffer = templateProcessor.popBuffer();
+            if (buffer.trim().length() == 0) {
+                templateProcessor.appendAsHtmlEncoded(fieldReference == null ? "" : fieldReference.titleString());
+            } else {
+                templateProcessor.appendHtml(buffer);
+            }
+            if (linkView != null) {
+                templateProcessor.appendHtml("</a>");
+            }
+        } else {
+            templateProcessor.skipUntilClose();
+        }
+        templateProcessor.appendHtml("</td>");
+    }
+
+    @Override
+    public String getName() {
+        return "table-cell";
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/7700b437/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/TableContentWriter.java
----------------------------------------------------------------------
diff --git a/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/TableContentWriter.java b/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/TableContentWriter.java
new file mode 100644
index 0000000..5683264
--- /dev/null
+++ b/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/TableContentWriter.java
@@ -0,0 +1,39 @@
+/*
+ *  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.scimpi.dispatcher.view.collection;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.viewer.scimpi.dispatcher.context.Request;
+import org.apache.isis.viewer.scimpi.dispatcher.processor.PageWriter;
+import org.apache.isis.viewer.scimpi.dispatcher.processor.TemplateProcessor;
+
+public interface TableContentWriter {
+
+    void writeCaption(PageWriter writer);
+
+    void writeHeaders(PageWriter writer);
+
+    void writeFooters(PageWriter writer);
+
+    void writeElement(TemplateProcessor templateProcessor, Request context, ObjectAdapter element);
+
+    void tidyUp();
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/7700b437/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/TableEmpty.java
----------------------------------------------------------------------
diff --git a/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/TableEmpty.java b/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/TableEmpty.java
new file mode 100644
index 0000000..4b918bc
--- /dev/null
+++ b/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/TableEmpty.java
@@ -0,0 +1,54 @@
+/*
+ *  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.scimpi.dispatcher.view.collection;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.facets.collections.modify.CollectionFacet;
+import org.apache.isis.viewer.scimpi.dispatcher.context.RequestState;
+import org.apache.isis.viewer.scimpi.dispatcher.processor.TemplateProcessor;
+import org.apache.isis.viewer.scimpi.dispatcher.view.AbstractElementProcessor;
+
+public class TableEmpty extends AbstractElementProcessor {
+
+    @Override
+    public void process(final TemplateProcessor templateProcessor, RequestState state) {
+        final TableBlock tableBlock = (TableBlock) templateProcessor.peekBlock();
+        final ObjectAdapter collection = tableBlock.getCollection();
+        final CollectionFacet facet = collection.getSpecification().getFacet(CollectionFacet.class);
+        if (facet.size(collection) == 0) {
+            String className = templateProcessor.getOptionalProperty(CLASS);
+            className = className == null ? "" : " class=\"" + className + "\"";
+            templateProcessor.appendHtml("<tr" + className + ">");
+            templateProcessor.pushNewBuffer();
+            templateProcessor.processUtilCloseTag();
+            final String buffer = templateProcessor.popBuffer();
+            templateProcessor.appendHtml(buffer);
+            templateProcessor.appendHtml("</td>");
+        } else {
+            templateProcessor.skipUntilClose();
+        }
+    }
+
+    @Override
+    public String getName() {
+        return "table-empty";
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/7700b437/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/TableHeader.java
----------------------------------------------------------------------
diff --git a/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/TableHeader.java b/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/TableHeader.java
new file mode 100644
index 0000000..c3d15f4
--- /dev/null
+++ b/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/TableHeader.java
@@ -0,0 +1,40 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.isis.viewer.scimpi.dispatcher.view.collection;
+
+import org.apache.isis.viewer.scimpi.dispatcher.context.RequestState;
+import org.apache.isis.viewer.scimpi.dispatcher.processor.TemplateProcessor;
+import org.apache.isis.viewer.scimpi.dispatcher.view.AbstractElementProcessor;
+
+public class TableHeader extends AbstractElementProcessor {
+
+    @Override
+    public String getName() {
+        return "table-header";
+    }
+
+    @Override
+    public void process(final TemplateProcessor templateProcessor, RequestState state) {
+        templateProcessor.appendHtml("<thead><tr>");
+        templateProcessor.processUtilCloseTag();
+        templateProcessor.appendHtml("</tr></thead>");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/7700b437/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/TableRow.java
----------------------------------------------------------------------
diff --git a/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/TableRow.java b/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/TableRow.java
new file mode 100644
index 0000000..46b7c03
--- /dev/null
+++ b/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/TableRow.java
@@ -0,0 +1,50 @@
+/*
+ *  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.scimpi.dispatcher.view.collection;
+
+import org.apache.isis.viewer.scimpi.Names;
+import org.apache.isis.viewer.scimpi.dispatcher.context.RequestState;
+import org.apache.isis.viewer.scimpi.dispatcher.processor.TemplateProcessor;
+import org.apache.isis.viewer.scimpi.dispatcher.processor.TemplateProcessor.RepeatMarker;
+import org.apache.isis.viewer.scimpi.dispatcher.view.AbstractElementProcessor;
+
+public class TableRow extends AbstractElementProcessor {
+
+    @Override
+    public String getName() {
+        return "table-row";
+    }
+
+    @Override
+    public void process(final TemplateProcessor templateProcessor, RequestState state) {
+        final TableBlock block = (TableBlock) templateProcessor.peekBlock();
+        
+        final RepeatMarker start = templateProcessor.createMarker();
+        block.setMarker(start);
+        
+        final String linkView = templateProcessor.getOptionalProperty(LINK_VIEW);
+        if (linkView != null) {
+            block.setlinkView(linkView);
+            block.setlinkName(templateProcessor.getOptionalProperty(LINK_NAME, Names.RESULT));
+        }
+        
+        templateProcessor.skipUntilClose();
+    }
+}

http://git-wip-us.apache.org/repos/asf/isis/blob/7700b437/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/TableView.java
----------------------------------------------------------------------
diff --git a/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/TableView.java b/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/TableView.java
new file mode 100644
index 0000000..72baeaf
--- /dev/null
+++ b/component/viewer/scimpi/dispatcher/src/main/java/org/apache/isis/viewer/scimpi/dispatcher/view/collection/TableView.java
@@ -0,0 +1,328 @@
+/*
+ *  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.scimpi.dispatcher.view.collection;
+
+import java.util.List;
+
+import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
+import org.apache.isis.core.metamodel.facets.object.parseable.ParseableFacet;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+import org.apache.isis.core.runtime.persistence.ObjectNotFoundException;
+import org.apache.isis.viewer.scimpi.Names;
+import org.apache.isis.viewer.scimpi.dispatcher.context.Request;
+import org.apache.isis.viewer.scimpi.dispatcher.context.Request.Scope;
+import org.apache.isis.viewer.scimpi.dispatcher.processor.PageWriter;
+import org.apache.isis.viewer.scimpi.dispatcher.processor.TemplateProcessor;
+import org.apache.isis.viewer.scimpi.dispatcher.view.determine.LinkedFieldsBlock;
+import org.apache.isis.viewer.scimpi.dispatcher.view.determine.LinkedObject;
+
+public class TableView extends AbstractTableView {
+
+   public static final class SimpleTableBuilder implements TableContentWriter {
+        private final String parent;
+        private final boolean includeHeader;
+        private final boolean includeFooter;
+        private final String title;
+        private final String[] headers;
+        private final List<ObjectAssociation> fields;
+        private final boolean showTitle;
+        private final boolean showIcons;
+        private final boolean showSelectOption;
+        private final boolean showDeleteOption;
+        private final boolean showEditOption;
+        private final String fieldName;
+        private final LinkedObject[] linkedFields;
+        private final LinkedObject linkRow;
+        private final int noColumns;
+
+        public SimpleTableBuilder(
+                final String parent,
+                final boolean includeHeader,
+                final boolean includeFooter,
+                final String title,
+                final int noColumns,
+                final String[] headers,
+                final List<ObjectAssociation> fields,
+                final boolean showTitle,
+                final boolean showIcons,
+                final boolean showSelectOption,
+                final boolean showDeleteOption,
+                final boolean showEditOption,
+                final String fieldName,
+                final LinkedObject[] linkedFields,
+                final LinkedObject linkRow) {
+            this.parent = parent;
+            this.includeHeader = includeHeader;
+            this.includeFooter = includeFooter;
+            this.title = title;
+            this.showTitle = showTitle;
+            this.noColumns = noColumns < 1 ? fields.size() : noColumns;
+            this.headers = headers;
+            this.fields = fields;
+            this.showIcons = showIcons;
+            this.showSelectOption = showSelectOption;
+            this.showDeleteOption = showDeleteOption;
+            this.showEditOption = showEditOption;
+            this.fieldName = fieldName;
+            this.linkedFields = linkedFields;
+            this.linkRow = linkRow;
+        }
+
+        @Override
+        public void writeFooters(final PageWriter writer) {
+            if (includeFooter) {
+                writer.appendHtml("<tfoot>");
+                columnHeaders(writer, headers);
+                writer.appendHtml("</tfoot>");
+            }
+        }
+
+        @Override
+        public void writeCaption(PageWriter writer) {
+            if (title != null) {
+                writer.appendHtml("<caption>");
+                writer.appendHtml(title);
+                writer.appendHtml("</caption>");
+            }
+        }
+        
+        @Override
+        public void writeHeaders(final PageWriter writer) {
+            if (includeHeader) {
+                writer.appendHtml("<thead>");
+                columnHeaders(writer, headers);
+                writer.appendHtml("</thead>");
+            }
+        }
+
+        private void columnHeaders(final PageWriter writer, final String[] headers) {
+            writer.appendHtml("<tr class=\"column-headers\">");
+            if (showTitle) {
+                writer.appendHtml("<th></th>");
+            }
+            final String[] columnHeaders = headers;
+            for (final String columnHeader : columnHeaders) {
+                if (columnHeader != null) {
+                    writer.appendHtml("<th>");
+                    writer.appendAsHtmlEncoded(columnHeader);
+                    writer.appendHtml("</th>");
+                }
+            }
+            writer.appendHtml("<th class=\"controls\"></th>");
+            writer.appendHtml("</tr>");
+        }
+
+        public void tidyUp() {
+       //     request.popBlockContent();
+            
+        //    Is it the block that is left over, or is the collection form not being closed?
+        }
+        
+        @Override
+        public void writeElement(final TemplateProcessor templateProcessor, final Request context, final ObjectAdapter element) {
+            final String rowId = context.mapObject(element, Scope.INTERACTION);
+            final String scope = linkRow == null ? "" : "&amp;" + SCOPE + "=" + linkRow.getScope();
+            String result = "";
+            result = context.encodedInteractionParameters();
+
+            if (noColumns == 0) {
+                templateProcessor.appendHtml("<td>");
+                if (linkRow != null) {
+                    templateProcessor.appendHtml("<td><a href=\"" + linkRow.getForwardView() + "?" + linkRow.getVariable() + "=" + rowId + result + scope + "\">");
+                    templateProcessor.appendAsHtmlEncoded(element.titleString());
+                    templateProcessor.appendHtml("</a>");
+                } else {
+                    templateProcessor.appendAsHtmlEncoded(element.titleString());
+                }
+                templateProcessor.appendHtml("</td>");
+
+            } else {
+                if (showTitle) {
+                    templateProcessor.appendHtml("<td>");
+                    templateProcessor.appendAsHtmlEncoded(element.titleString());
+                    templateProcessor.appendHtml("</td>");
+                }
+
+                for (int i = 0; i < noColumns; i++) {
+                    if (fields.get(i).isOneToManyAssociation()) {
+                        continue;
+                    }
+                    templateProcessor.appendHtml("<td>");
+                    final ObjectAdapter field = fields.get(i).get(element);
+                    if (field != null) {
+                        if (showIcons && !fields.get(i).getSpecification().containsFacet(ParseableFacet.class)) {
+                            templateProcessor.appendHtml("<img class=\"" + "small-icon" + "\" src=\"" + templateProcessor.getContext().imagePath(field) + "\" alt=\"" + fields.get(i).getSpecification().getShortIdentifier() + "\"/>");
+                        }
+                        if (linkRow != null) {
+                            templateProcessor.appendHtml("<a href=\"" + linkRow.getForwardView() + "?" + linkRow.getVariable() + "=" + rowId + result + scope + "\">");
+                        } else if (linkedFields[i] != null) {
+                            final ObjectAdapter fieldObject = fields.get(i).get(element);
+                            final String id = context.mapObject(fieldObject, Scope.INTERACTION);
+                            templateProcessor.appendHtml("<a href=\"" + linkedFields[i].getForwardView() + "?" + linkedFields[i].getVariable() + "=" + id + "\">");
+                            context.mapObject(fieldObject, Request.scope(linkedFields[i].getScope()));
+
+                        }
+                        try {
+                            templateProcessor.appendAsHtmlEncoded(field.titleString());
+                        } catch (final ObjectNotFoundException e) {
+                            templateProcessor.appendAsHtmlEncoded(e.getMessage());
+                        }
+                        if (linkRow != null || linkedFields[i] != null) {
+                            templateProcessor.appendHtml("</a>");
+                        }
+                    }
+                    templateProcessor.appendHtml("</td>");
+
+                }
+            }
+            templateProcessor.appendHtml("<td class=\"controls\">");
+            if (showSelectOption) {
+                templateProcessor.appendHtml("<a class=\"button element-select\" href=\"" + "_generic." + Names.EXTENSION + "?" + Names.RESULT + "=" + rowId + result + scope + "\">view</a>");
+            }
+            if (showEditOption) {
+                templateProcessor.appendHtml(" <a class=\"button element-edit\" href=\"" + "_generic_edit." + Names.EXTENSION + "?" + Names.RESULT + "=" + rowId + result + scope + "\">edit</a>");
+            }
+
+            if (showDeleteOption && parent != null) {
+                String view = templateProcessor.getViewPath();
+                view = context.fullFilePath(view == null ? context.getResourceFile() : view);
+                RemoveElement.write(templateProcessor, context.getMappedObject(parent), fieldName, element, null, view, view, "delete", "action in-line element-delete confirm");
+            }
+
+            templateProcessor.appendHtml("</td>");
+
+        }
+    }
+
+    @Override
+    protected TableContentWriter createRowBuilder(
+            final TemplateProcessor templateProcessor,
+            final Request context,
+            final String parent,
+            final List<ObjectAssociation> allFields,
+            final ObjectAdapter collection) {
+        final String fieldName = templateProcessor.getOptionalProperty(FIELD);
+        final String title = templateProcessor.getOptionalProperty(FORM_TITLE);
+        return rowBuilder(templateProcessor, context, title, parent, fieldName, allFields, showIconByDefault());
+    }
+
+    private static TableContentWriter rowBuilder(
+            final TemplateProcessor templateProcessor,
+            final Request context,
+            final String title,
+            final String object,
+            final String fieldName,
+            final List<ObjectAssociation> allFields,
+            final boolean showIconByDefault) {
+        final String linkRowView = templateProcessor.getOptionalProperty(LINK_VIEW);
+        final String linkObjectName = templateProcessor.getOptionalProperty(ELEMENT_NAME, Names.RESULT);
+        final String linkObjectScope = templateProcessor.getOptionalProperty(SCOPE, Scope.INTERACTION.toString());
+        final LinkedObject linkRow = linkRowView == null ? null : new LinkedObject(linkObjectName, linkObjectScope, context.fullUriPath(linkRowView));
+        final boolean includeHeader = templateProcessor.isRequested(HEADER, true);
+        final boolean includeFooter = templateProcessor.isRequested(FOOTER, false);
+
+        final boolean linkFields = templateProcessor.isRequested("link-fields", true);
+        final boolean showTitle = templateProcessor.isRequested(SHOW_TITLE, false);
+        final boolean showIcons = templateProcessor.isRequested(SHOW_ICON, showIconByDefault);
+        final boolean showSelectOption = templateProcessor.isRequested(SHOW_SELECT, true);
+        final boolean showEditOption = templateProcessor.isRequested(SHOW_EDIT, true);
+        final boolean showDeleteOption = templateProcessor.isRequested(SHOW_DELETE, true);
+
+        final String noColumnsString = templateProcessor.getOptionalProperty("no-columns", "3");
+
+        final LinkedFieldsBlock block = new LinkedFieldsBlock();
+        templateProcessor.pushBlock(block);
+        templateProcessor.processUtilCloseTag();
+        final List<ObjectAssociation> fields = block.includedFields(allFields);
+        final LinkedObject[] linkedFields = block.linkedFields(fields);
+        for (int i = 0; i < linkedFields.length; i++) {
+            if (linkedFields[i] == null && linkFields && !fields.get(i).getSpecification().containsFacet(ParseableFacet.class)) {
+                linkedFields[i] = new LinkedObject("_generic.shtml");
+            }
+            if (linkedFields[i] != null) {
+                linkedFields[i].setForwardView(context.fullUriPath(linkedFields[i].getForwardView()));
+            }
+        }
+
+        int noColumns;
+        if (noColumnsString.equalsIgnoreCase("all")) {
+            noColumns = fields.size();
+        } else {
+            noColumns = Math.min(fields.size(), Integer.valueOf(noColumnsString));
+        }
+
+        final String headers[] = new String[noColumns];
+        int h = 0;
+        for (int i = 0; i < noColumns; i++) {
+            if (fields.get(i).isOneToManyAssociation()) {
+                continue;
+            }
+            headers[h++] = fields.get(i).getName();
+        }
+
+        templateProcessor.popBlock();
+
+        return new SimpleTableBuilder(object, includeHeader, includeFooter, title, noColumns, headers, fields, showTitle,
+                showIcons, showSelectOption, showDeleteOption, showEditOption, fieldName, linkedFields, linkRow);
+    }
+
+    public static void write(
+            final TemplateProcessor templateProcessor,
+            final String summary,
+            final ObjectAdapter object,
+            final ObjectAssociation field,
+            final ObjectAdapter collection,
+            final int noColumns,
+            final List<ObjectAssociation> fields,
+            final boolean linkAllFields,
+            final boolean showIconByDefault,
+            final String tableClass,
+            final String[] rowClasses,
+            LinkedObject linkedObject) {
+        final LinkedObject[] linkedFields = new LinkedObject[fields.size()];
+        if (linkAllFields) {
+            for (int i = 0; i < linkedFields.length; i++) {
+                if (fields.get(i).isOneToOneAssociation()) {
+                    linkedFields[i] = linkedObject == null ? new LinkedObject("_generic.shtml") : linkedObject;  
+                }
+            }
+        }
+        
+        final String headers[] = new String[fields.size()];
+        int h = 0;
+        for (int i = 0; i < fields.size(); i++) {
+            if (fields.get(i).isOneToManyAssociation()) {
+                continue;
+            }
+            headers[h++] = fields.get(i).getName();
+        }
+        
+        final Request context = templateProcessor.getContext();
+        final TableContentWriter rowBuilder = rowBuilder(templateProcessor, context, null, context.mapObject(object, Scope.REQUEST), field.getIdentifier().getMemberName(), fields, 
+                showIconByDefault);
+        write(templateProcessor, collection, summary, rowBuilder, null, null, null);
+    }
+
+    @Override
+    public String getName() {
+        return "table";
+    }
+
+}