diff --git a/src/main/java/no/nibio/vips/logic/VIPSLogicApplication.java b/src/main/java/no/nibio/vips/logic/VIPSLogicApplication.java
index 0100e091eba22d206ae3ddab7b164ec5fc0e74f5..f4d4b4e5ada3a4f2503dae891d71cf3eadace530 100644
--- a/src/main/java/no/nibio/vips/logic/VIPSLogicApplication.java
+++ b/src/main/java/no/nibio/vips/logic/VIPSLogicApplication.java
@@ -49,6 +49,7 @@ public class VIPSLogicApplication extends Application
         resources.add(no.nibio.vips.logic.service.VIPSMobileService.class);
         resources.add(no.nibio.vips.logic.modules.barleynetblotch.BarleyNetBlotchModelService.class);
         resources.add(no.nibio.vips.logic.modules.roughage.RoughageService.class);
+        resources.add(no.nibio.vips.observationdata.ObservationDataService.class);
         //resources.add(no.nibio.vips.logic.service.JacksonConfig.class);
         //resources.add(no.nibio.vips.coremanager.service.ManagerResourceImpl.class);
     }
@@ -65,6 +66,7 @@ public class VIPSLogicApplication extends Application
         resources.add(no.nibio.vips.logic.service.JacksonConfig.class);
         resources.add(no.nibio.vips.logic.service.LogicService.class);
         resources.add(no.nibio.vips.logic.service.VIPSMobileService.class);
+        resources.add(no.nibio.vips.observationdata.ObservationDataService.class);
         
     }
 }
\ No newline at end of file
diff --git a/src/main/java/no/nibio/vips/logic/controller/servlet/ObservationController.java b/src/main/java/no/nibio/vips/logic/controller/servlet/ObservationController.java
index 8caae2bdf9df78214f0aca2f1aa6ab614ba06a9f..dde0a0b3b34d789a0cfbc28904ae07ea700c07ef 100644
--- a/src/main/java/no/nibio/vips/logic/controller/servlet/ObservationController.java
+++ b/src/main/java/no/nibio/vips/logic/controller/servlet/ObservationController.java
@@ -34,6 +34,7 @@ import no.nibio.vips.logic.entity.VipsLogicRole;
 import no.nibio.vips.logic.entity.VipsLogicUser;
 import no.nibio.vips.logic.i18n.SessionLocaleUtil;
 import no.nibio.vips.logic.util.SessionControllerGetter;
+import no.nibio.vips.logic.util.SystemTime;
 import no.nibio.vips.util.ExceptionUtil;
 import no.nibio.vips.util.ServletUtil;
 import no.nibio.web.forms.FormValidation;
@@ -88,6 +89,10 @@ public class ObservationController extends HttpServlet {
                     // Hierarchy categories
                     request.setAttribute("hierarchyCategories", SessionControllerGetter.getOrganismBean().getHierarchyCategoryNames(SessionLocaleUtil.getCurrentLocale(request)));
                     request.setAttribute("observationMethods", em.createNamedQuery("ObservationMethod.findAll", ObservationMethod.class).getResultList());
+                    if(SessionControllerGetter.getUserBean().authorizeUser(user, VipsLogicRole.OBSERVATION_AUTHORITY, VipsLogicRole.SUPERUSER))
+                    {
+                        request.setAttribute("statusTypeIds", em.createNamedQuery("ObservationStatusType.findAll").getResultList());
+                    }
                     request.getRequestDispatcher("/observationForm.ftl").forward(request, response);
                 }
                 catch(NullPointerException | NumberFormatException ex)
@@ -120,6 +125,10 @@ public class ObservationController extends HttpServlet {
                     // Hierarchy categories
                     request.setAttribute("hierarchyCategories", SessionControllerGetter.getOrganismBean().getHierarchyCategoryNames(SessionLocaleUtil.getCurrentLocale(request)));
                     request.setAttribute("observationMethods", em.createNamedQuery("ObservationMethod.findAll", ObservationMethod.class).getResultList());
+                    if(SessionControllerGetter.getUserBean().authorizeUser(user, VipsLogicRole.OBSERVATION_AUTHORITY, VipsLogicRole.SUPERUSER))
+                    {
+                        request.setAttribute("statusTypeIds", em.createNamedQuery("ObservationStatusType.findAll").getResultList());
+                    }
                     if(request.getParameter("messageKey") != null)
                     {
                         request.setAttribute("messageKey",request.getParameter("messageKey"));
@@ -150,12 +159,37 @@ public class ObservationController extends HttpServlet {
                     if(formValidation.isValid())
                     {
                         // Storing observation
-                        observation.setOrganism(em.find(Organism.class, formValidation.getFormField("organismId").getValueAsInteger()));
-                        observation.setDenominator(formValidation.getFormField("denominator").getValueAsInteger());
-                        observation.setObservationMethodId(em.find(ObservationMethod.class, formValidation.getFormField("observationMethodId").getWebValue()));
+                        
+                        // Only new observations can set the organism
+                        if(observationId <= 0)
+                        {
+                            
+                            observation.setOrganism(em.find(Organism.class, formValidation.getFormField("organismId").getValueAsInteger()));
+                        }
+                        //observation.setDenominator(formValidation.getFormField("denominator").getValueAsInteger());
+                        //observation.setObservationMethodId(em.find(ObservationMethod.class, formValidation.getFormField("observationMethodId").getWebValue()));
                         observation.setTimeOfObservation(formValidation.getFormField("timeOfObservation").getValueAsTimestamp());
-                        observation.setObservedValue(formValidation.getFormField("observedValue").getValueAsDouble());
+                        //observation.setObservedValue(formValidation.getFormField("observedValue").getValueAsDouble());
                         observation.setUserId(user.getUserId());
+                        observation.setObservationHeading(formValidation.getFormField("observationHeading").getWebValue());
+                        observation.setObservationText(formValidation.getFormField("observationText").getWebValue());
+                        observation.setObservationData(formValidation.getFormField("observationData").getWebValue());
+                        
+                        // Storing approval status
+                        if(SessionControllerGetter.getUserBean().authorizeUser(user, VipsLogicRole.OBSERVATION_AUTHORITY, VipsLogicRole.SUPERUSER))
+                        {
+                            Integer statusTypeId = formValidation.getFormField("statusTypeId").getValueAsInteger();
+                            if(
+                                    observation.getStatusTypeId() == null
+                                    || ! observation.getStatusTypeId().equals(statusTypeId)
+                                    )
+                            {
+                                observation.setStatusChangedByUserId(user.getUserId());
+                                observation.setStatusChangedTime(SystemTime.getSystemTime());
+                            }
+                            observation.setStatusTypeId(statusTypeId);
+                            observation.setStatusRemarks(formValidation.getFormField("statusRemarks").getWebValue());
+                        }
                         //observation.setLocation(formValidation.getFormField("location").getValueAsPointWGS84());
                         //System.out.println(formValidation.getFormField("geoInfo").getWebValue());
                         observation.setGeoinfo(formValidation.getFormField("geoInfo").getWebValue());
diff --git a/src/main/java/no/nibio/vips/logic/entity/Observation.java b/src/main/java/no/nibio/vips/logic/entity/Observation.java
index a83286c4cd4db00e9f8768f6deeb98c605975308..1375e6a9c0c2011749c21a8c0f0e1d3174ea285c 100644
--- a/src/main/java/no/nibio/vips/logic/entity/Observation.java
+++ b/src/main/java/no/nibio/vips/logic/entity/Observation.java
@@ -37,25 +37,29 @@ import javax.persistence.Transient;
 import javax.validation.constraints.NotNull;
 import javax.xml.bind.annotation.XmlRootElement;
 import com.fasterxml.jackson.annotation.JsonIgnore;
-import com.vividsolutions.jts.geom.Geometry;
 import java.util.List;
+import javax.validation.constraints.Size;
 import no.nibio.vips.logic.util.GISUtil;
+import no.nibio.vips.logic.util.StringJsonUserType;
+import org.hibernate.annotations.Type;
+import org.hibernate.annotations.TypeDef;
+import org.hibernate.annotations.TypeDefs;
 
 /**
- * @copyright 2014 <a href="http://www.nibio.no/">NIBIO</a>
+ * @copyright 2016 <a href="http://www.nibio.no/">NIBIO</a>
  * @author Tor-Einar Skog <tor-einar.skog@nibio.no>
  */
 @Entity
 @Table(name = "observation")
 @XmlRootElement
+@TypeDefs( {@TypeDef( name= "StringJsonObject", typeClass = StringJsonUserType.class)})
 @NamedQueries({
     @NamedQuery(name = "Observation.findAll", query = "SELECT o FROM Observation o"),
     @NamedQuery(name = "Observation.findByObservationId", query = "SELECT o FROM Observation o WHERE o.observationId = :observationId"),
     @NamedQuery(name = "Observation.findByUserId", query = "SELECT o FROM Observation o WHERE o.userId IN(:userId)"),
     @NamedQuery(name = "Observation.findByOrganism", query = "SELECT o FROM Observation o WHERE o.organism =  :organism"),
-    @NamedQuery(name = "Observation.findByTimeOfObservation", query = "SELECT o FROM Observation o WHERE o.timeOfObservation = :timeOfObservation"),
-    @NamedQuery(name = "Observation.findByObservedValue", query = "SELECT o FROM Observation o WHERE o.observedValue = :observedValue"),
-    @NamedQuery(name = "Observation.findByDenominator", query = "SELECT o FROM Observation o WHERE o.denominator = :denominator")})
+    @NamedQuery(name = "Observation.findByTimeOfObservation", query = "SELECT o FROM Observation o WHERE o.timeOfObservation = :timeOfObservation")
+})
 public class Observation implements Serializable, no.nibio.vips.observation.Observation {
 
     private static final long serialVersionUID = 1L;
@@ -63,11 +67,18 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse
     private Date timeOfObservation;
     private Organism organism;
     private Integer userId;
-    //private Point location;
     private List<Gis> geoinfo;
-    private Double observedValue;
-    private Integer denominator;
-    private ObservationMethod observationMethodId;
+    //private Double observedValue;
+    //private Integer denominator;
+    //private ObservationMethod observationMethodId;
+    private String observationHeading;
+    private String observationText;
+    private Integer statusTypeId;
+    private Integer statusChangedByUserId;
+    private Date statusChangedTime;
+    private String statusRemarks;
+    private String observationData;
+    private Boolean isQuantified;
     
     private GISUtil GISUtil;
 
@@ -79,11 +90,11 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse
         this.observationId = observationId;
     }
 
-    public Observation(Integer observationId, Date timeOfObservation, Double registeredValue) {
+    /*public Observation(Integer observationId, Date timeOfObservation, Double registeredValue) {
         this.observationId = observationId;
         this.timeOfObservation = timeOfObservation;
         this.observedValue = registeredValue;
-    }
+    }*/
 
     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -154,7 +165,7 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse
         return this.location.getCoordinate();
     }*/
     
-    @NotNull
+    /*@NotNull
     @Basic(optional = false)
     @Column(name = "observed_value")
     @Override
@@ -174,8 +185,9 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse
 
     public void setDenominator(Integer denominator) {
         this.denominator = denominator;
-    }
+    }*/
 
+    /*
     @JoinColumn(name = "observation_method_id", referencedColumnName = "observation_method_id")
     @ManyToOne
     public ObservationMethod getObservationMethodId() {
@@ -185,7 +197,7 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse
     public void setObservationMethodId(ObservationMethod observationMethodId) {
         this.observationMethodId = observationMethodId;
     }
-
+*/
     @Override
     public int hashCode() {
         int hash = 0;
@@ -268,4 +280,129 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse
         return this.getOrganism().getLatinName();
     }
 
+    /**
+     * @return the observationHeading
+     */
+    @Column(name = "observation_heading")
+    @Size(max = 1023)
+    public String getObservationHeading() {
+        return observationHeading;
+    }
+
+    /**
+     * @param observationHeading the observationHeading to set
+     */
+    public void setObservationHeading(String observationHeading) {
+        this.observationHeading = observationHeading;
+    }
+
+    /**
+     * @return the observationText
+     */
+    @Column(name = "observation_text")
+    @Size(max = 2147483647)
+    public String getObservationText() {
+        return observationText;
+    }
+
+    /**
+     * @param observationText the observationText to set
+     */
+    public void setObservationText(String observationText) {
+        this.observationText = observationText;
+    }
+
+    /**
+     * @return the statusTypeId
+     */
+    @Column(name = "status_type_id")
+    public Integer getStatusTypeId() {
+        return statusTypeId;
+    }
+
+    /**
+     * @param statusTypeId the statusTypeId to set
+     */
+    public void setStatusTypeId(Integer statusTypeId) {
+        this.statusTypeId = statusTypeId;
+    }
+
+    /**
+     * @return the statusChangedByUserId
+     */
+    @Column(name = "status_changed_by_user_id")
+    public Integer getStatusChangedByUserId() {
+        return statusChangedByUserId;
+    }
+
+    /**
+     * @param statusChangedByUserId the statusChangedByUserId to set
+     */
+    public void setStatusChangedByUserId(Integer statusChangedByUserId) {
+        this.statusChangedByUserId = statusChangedByUserId;
+    }
+
+    /**
+     * @return the statusChangedTime
+     */
+    @Temporal(TemporalType.TIMESTAMP)
+    @Column(name = "status_changed_time")
+    public Date getStatusChangedTime() {
+        return statusChangedTime;
+    }
+
+    /**
+     * @param statusChangedTime the statusChangedTime to set
+     */
+    public void setStatusChangedTime(Date statusChangedTime) {
+        this.statusChangedTime = statusChangedTime;
+    }
+
+    /**
+     * @return the statusRemarks
+     */
+    @Column(name = "status_remarks")
+    @Size(max = 2147483647)
+    public String getStatusRemarks() {
+        return statusRemarks;
+    }
+
+    /**
+     * @param statusRemarks the statusRemarks to set
+     */
+    public void setStatusRemarks(String statusRemarks) {
+        this.statusRemarks = statusRemarks;
+    }
+
+    /**
+     * @return the observationData
+     */
+    @Type(type = "StringJsonObject")
+    @Column(name = "observation_data")
+    public String getObservationData() {
+        return observationData;
+    }
+
+    /**
+     * @param observationData the observationData to set
+     */
+    public void setObservationData(String observationData) {
+        this.observationData = observationData;
+    }
+
+    /**
+     * @return the isQuantified
+     */
+    @Column(name = "is_quantified")
+    public Boolean getIsQuantified() {
+        return isQuantified;
+    }
+
+    /**
+     * @param isQuantified the isQuantified to set
+     */
+    public void setIsQuantified(Boolean isQuantified) {
+        this.isQuantified = isQuantified;
+    }
+
 }
diff --git a/src/main/java/no/nibio/vips/logic/entity/ObservationMethod.java b/src/main/java/no/nibio/vips/logic/entity/ObservationMethod.java
index 0c93cc177c4ea1a1e4520794e7fba1c8f3e245d9..52e800817c7b8575330b776764203c61bc8c4842 100644
--- a/src/main/java/no/nibio/vips/logic/entity/ObservationMethod.java
+++ b/src/main/java/no/nibio/vips/logic/entity/ObservationMethod.java
@@ -53,8 +53,7 @@ public class ObservationMethod implements Serializable {
     @Size(min = 1, max = 63)
     @Column(name = "observation_method_id")
     private String observationMethodId;
-    @OneToMany(mappedBy = "observationMethodId")
-    private Set<Observation> observationSet;
+    
 
     public ObservationMethod() {
     }
@@ -71,16 +70,6 @@ public class ObservationMethod implements Serializable {
         this.observationMethodId = observationMethodId;
     }
 
-    @XmlTransient
-    @JsonIgnore
-    public Set<Observation> getObservationSet() {
-        return observationSet;
-    }
-
-    public void setObservationSet(Set<Observation> observationSet) {
-        this.observationSet = observationSet;
-    }
-
     @Override
     public int hashCode() {
         int hash = 0;
diff --git a/src/main/java/no/nibio/vips/logic/entity/ObservationStatusType.java b/src/main/java/no/nibio/vips/logic/entity/ObservationStatusType.java
new file mode 100644
index 0000000000000000000000000000000000000000..afa92961efcb6a00535f3cc6f992a593c08bb4d0
--- /dev/null
+++ b/src/main/java/no/nibio/vips/logic/entity/ObservationStatusType.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2016 NIBIO <http://www.nibio.no/>. 
+ * 
+ * This file is part of VIPSLogic.
+ * VIPSLogic is free software: you can redistribute it and/or modify
+ * it under the terms of the NIBIO Open Source License as published by 
+ * NIBIO, either version 1 of the License, or (at your option) any
+ * later version.
+ * 
+ * VIPSLogic is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * NIBIO Open Source License for more details.
+ * 
+ * You should have received a copy of the NIBIO Open Source License
+ * along with VIPSLogic.  If not, see <http://www.nibio.no/licenses/>.
+ * 
+ */
+
+package no.nibio.vips.logic.entity;
+
+import java.io.Serializable;
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * @copyright 2016 <a href="http://www.nibio.no/">NIBIO</a>
+ * @author Tor-Einar Skog <tor-einar.skog@nibio.no>
+ */
+@Entity
+@Table(name = "observation_status_type")
+@XmlRootElement
+@NamedQueries({
+    @NamedQuery(name = "ObservationStatusType.findAll", query = "SELECT o FROM ObservationStatusType o"),
+    @NamedQuery(name = "ObservationStatusType.findByStatusTypeId", query = "SELECT o FROM ObservationStatusType o WHERE o.statusTypeId = :statusTypeId"),
+    @NamedQuery(name = "ObservationStatusType.findByStatusTitle", query = "SELECT o FROM ObservationStatusType o WHERE o.statusTitle = :statusTitle")})
+public class ObservationStatusType implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+    @Id
+    @Basic(optional = false)
+    @NotNull
+    @Column(name = "status_type_id")
+    private Integer statusTypeId;
+    @Size(max = 63)
+    @Column(name = "status_title")
+    private String statusTitle;
+
+    public ObservationStatusType() {
+    }
+
+    public ObservationStatusType(Integer statusTypeId) {
+        this.statusTypeId = statusTypeId;
+    }
+
+    public Integer getStatusTypeId() {
+        return statusTypeId;
+    }
+
+    public void setStatusTypeId(Integer statusTypeId) {
+        this.statusTypeId = statusTypeId;
+    }
+
+    public String getStatusTitle() {
+        return statusTitle;
+    }
+
+    public void setStatusTitle(String statusTitle) {
+        this.statusTitle = statusTitle;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = 0;
+        hash += (statusTypeId != null ? statusTypeId.hashCode() : 0);
+        return hash;
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        // TODO: Warning - this method won't work in the case the id fields are not set
+        if (!(object instanceof ObservationStatusType)) {
+            return false;
+        }
+        ObservationStatusType other = (ObservationStatusType) object;
+        if ((this.statusTypeId == null && other.statusTypeId != null) || (this.statusTypeId != null && !this.statusTypeId.equals(other.statusTypeId))) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "no.nibio.vips.logic.entity.ObservationStatusType[ statusTypeId=" + statusTypeId + " ]";
+    }
+
+}
diff --git a/src/main/java/no/nibio/vips/logic/entity/VipsLogicRole.java b/src/main/java/no/nibio/vips/logic/entity/VipsLogicRole.java
index 6cfeb5eac03559b801dc16e2862c9a683055cf2b..af4a2e0c2392e8e7bed9b483b6c6bf3782200ee6 100644
--- a/src/main/java/no/nibio/vips/logic/entity/VipsLogicRole.java
+++ b/src/main/java/no/nibio/vips/logic/entity/VipsLogicRole.java
@@ -49,6 +49,7 @@ public class VipsLogicRole implements Serializable {
     public static Integer SUPERUSER = 1;
     public static Integer ORGANIZATION_ADMINISTRATOR = 2;
     public static Integer OBSERVER = 3;
+    public static Integer OBSERVATION_AUTHORITY = 4;
 
     private static final long serialVersionUID = 1L;
     @Id
diff --git a/src/main/java/no/nibio/vips/logic/entity/VipsLogicUser.java b/src/main/java/no/nibio/vips/logic/entity/VipsLogicUser.java
index 5ff81c3deb3a3358f6f7d2cd917a47c5406f765d..e543d0c21aa1b3a621104e6e88c3ac4b3747bca8 100644
--- a/src/main/java/no/nibio/vips/logic/entity/VipsLogicUser.java
+++ b/src/main/java/no/nibio/vips/logic/entity/VipsLogicUser.java
@@ -275,6 +275,17 @@ public class VipsLogicUser implements Serializable {
         }
         return false;
     }
+    
+    @JsonIgnore
+    @Transient
+    public boolean isObservationAuthority(){
+        for (VipsLogicRole role : this.getVipsLogicRoles()) {
+            if (role.getVipsLogicRoleId().equals(VipsLogicRole.OBSERVATION_AUTHORITY)) {
+                return true;
+            }
+        }
+        return false;
+    }
 
     @JsonIgnore
     public boolean hasRole(Integer... roles) {
diff --git a/src/main/java/no/nibio/vips/logic/modules/barleynetblotch/BarleyNetBlotchModelService.java b/src/main/java/no/nibio/vips/logic/modules/barleynetblotch/BarleyNetBlotchModelService.java
index 0e07d1f1f270e5ddbfc78ec9e0be6439627666f5..daed2eadd514f84ef9cff7eb2624ae7b70fa6a46 100644
--- a/src/main/java/no/nibio/vips/logic/modules/barleynetblotch/BarleyNetBlotchModelService.java
+++ b/src/main/java/no/nibio/vips/logic/modules/barleynetblotch/BarleyNetBlotchModelService.java
@@ -222,8 +222,7 @@ public class BarleyNetBlotchModelService {
         {
             ObservationImpl observation = new ObservationImpl();
             observation.setName("Pyrenophora teres");
-            observation.setObservedValue(observationValue);
-            observation.setDenominator(100);
+            observation.setObservationData("{\"percentInfectedLeaves\": " + observationValue + "}");
             observation.setTimeOfObservation(observationDate);
             config.setConfigParameter("observation", observation);
         }
diff --git a/src/main/java/no/nibio/vips/observationdata/ObservationDataService.java b/src/main/java/no/nibio/vips/observationdata/ObservationDataService.java
new file mode 100644
index 0000000000000000000000000000000000000000..7325237eeabe98cf8cab4fedd408c70f56730366
--- /dev/null
+++ b/src/main/java/no/nibio/vips/observationdata/ObservationDataService.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2016 NIBIO <http://www.nibio.no/>. 
+ * 
+ * This file is part of VIPSLogic.
+ * VIPSLogic is free software: you can redistribute it and/or modify
+ * it under the terms of the NIBIO Open Source License as published by 
+ * NIBIO, either version 1 of the License, or (at your option) any
+ * later version.
+ * 
+ * VIPSLogic is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * NIBIO Open Source License for more details.
+ * 
+ * You should have received a copy of the NIBIO Open Source License
+ * along with VIPSLogic.  If not, see <http://www.nibio.no/licenses/>.
+ * 
+ */
+
+package no.nibio.vips.observationdata;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Response;
+
+/**
+ * @copyright 2016 <a href="http://www.nibio.no/">NIBIO</a>
+ * @author Tor-Einar Skog <tor-einar.skog@nibio.no>
+ */
+@Path("rest/observationdata")
+public class ObservationDataService {
+
+    @GET
+    @Path("schema/{organizationId}/{organismId}")
+    @Produces("application/json;charset=UTF-8")
+    public Response getSchema(){
+        // Try to find schema for given organism/organization
+        // If not found, return standard nominator/denominator (unit) form 
+        return Response.ok().entity(this.getStandardSchema()).build();
+    }
+    
+    @GET
+    @Path("model/{organizationId}/{organismId}")
+    @Produces("application/json;charset=UTF-8")
+    public Response getModel(){
+        // Try to find schema for given organism/organization
+        // If not found, return standard nominator/denominator (unit) form 
+        return Response.ok().entity(this.getStandardModel()).build();
+    }
+    
+    private String getStandardSchema(){
+        return "{"
+                + "\"number\":{\"title\":\"Number\"},"
+                + "\"unit\":{\"title\":\"Unit\"}"
+                + "}";
+    }
+    
+    private String getStandardModel(){
+        return "{"
+                + "\"number\":0,"
+                + "\"unit\":\"Number\""
+                + "}";
+    }
+}
diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties
index d89a17dd9d0994067f0c27fc2148f350379eae52..20b43bf1abb410598946588850b0ed597d86eeb1 100644
--- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties
+++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties
@@ -311,3 +311,13 @@ forecastNotificationMessageHeadingTpl_2=Notification of moderate infection risk
 task_SendForecastEventNotificationsTask_name=Send forecast event notifications
 task_SendForecastEventNotificationsTask_description=Checks to see if there has been changes in forecasts to YELLOW or RED status. If so, finds subscribers to such events and sends notifications
 preferredLocale=Preferred locale
+VIPSLogicTitle=Administration system
+indexText=VIPSLogic is the common administration system for VIPS. This is where you configure users, weather stations and forecasts, and where you report observations and write messages, and more. Use the top menu to get started.
+observationHeading=Observation heading
+observationText=Observation text
+statusTypeId=Status
+statusRemarks=Status remarks
+vipsLogicRole_4=Observation authority
+statusTypeIdTitle_1=Pending
+statusTypeIdTitle_2=Rejected
+statusTypeIdTitle_3=Approved
diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_bs.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_bs.properties
index 35e0602a4b20cf1d6b21b9619109edc8539c41b6..b04b362230ea459dd84c5e40ae9da00b13d9b41e 100644
--- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_bs.properties
+++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_bs.properties
@@ -311,3 +311,13 @@ forecastNotificationMessageHeadingTpl_2=Notification of moderate infection risk
 task_SendForecastEventNotificationsTask_name=Send forecast event notifications
 task_SendForecastEventNotificationsTask_description=Checks to see if there has been changes in forecasts to YELLOW or RED status. If so, finds subscribers to such events and sends notifications
 preferredLocale=Preferred locale
+VIPSLogicTitle=VIPS administration system
+indexText=VIPSLogic is the common administration system for VIPS. This is where you configure users, weather stations and forecasts, and where you report observations and write messages, and more.
+observationHeading=Observation heading
+observationText=Observation text
+statusTypeId=Status
+statusRemarks=Status remarks
+vipsLogicRole_4=Observation authority
+statusTypeIdTitle_1=Pending
+statusTypeIdTitle_2=Rejected
+statusTypeIdTitle_3=Approved
diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_hr.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_hr.properties
index 314874f036189ec4d4011d2395541b4fe7075d6c..1d8f692be265b4a062e5317f0706cc8989e3a013 100644
--- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_hr.properties
+++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_hr.properties
@@ -310,3 +310,13 @@ forecastNotificationMessageHeadingTpl_2=Notification of moderate infection risk
 task_SendForecastEventNotificationsTask_name=Send forecast event notifications
 task_SendForecastEventNotificationsTask_description=Checks to see if there has been changes in forecasts to YELLOW or RED status. If so, finds subscribers to such events and sends notifications
 preferredLocale=Preferred locale
+VIPSLogicTitle=VIPS administration system
+indexText=VIPSLogic is the common administration system for VIPS. This is where you configure users, weather stations and forecasts, and where you report observations and write messages, and more.
+observationHeading=Observation heading
+observationText=Observation text
+statusTypeId=Status
+statusRemarks=Status remarks
+vipsLogicRole_4=Observation authority
+statusTypeIdTitle_1=Pending
+statusTypeIdTitle_2=Rejected
+statusTypeIdTitle_3=Approved
diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_nb.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_nb.properties
index 3f0b079b9e009ac3dd6e1b211d25f9ac21838bed..3b0a668be928b44222b3125a47e8e2225e79004a 100644
--- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_nb.properties
+++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_nb.properties
@@ -311,3 +311,13 @@ forecastNotificationMessageHeadingTpl_2=Melding om moderat infeksjonsrisiko
 task_SendForecastEventNotificationsTask_name=Send meldinger om endringer i varsel
 task_SendForecastEventNotificationsTask_description=Ser om varsler har endret set til GUL eller R\u00d8D status. I tilfelle s\u00f8kes abonnenter opp og meldinger blir distribuert.
 preferredLocale=Foretrukket spr\u00e5k
+VIPSLogicTitle=Administrasjonssystem
+indexText=VIPSLogic er felles administrasjonssystem for VIPS. Her konfigurerer du brukere, m\u00e5lestasjoner og varsler, og her registrerer du observasjoner og skriver meldinger, med mere. Bruk menyen i toppen for \u00e5 komme i gang.
+observationHeading=Observasjons-overskrift
+observationText=Observasjonstekst
+statusTypeId=Status
+statusRemarks=Merknader til status
+vipsLogicRole_4=Observasjonsgodkjenner
+statusTypeIdTitle_1=Venter p\u00e5 godkjenning
+statusTypeIdTitle_2=Avvist
+statusTypeIdTitle_3=Godkjent
diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_sr.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_sr.properties
index dbc26eb03d096638e74851dfe8669d38aee855dd..30b0987b8d486c9bdc5378d4950f9200b573c9e0 100644
--- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_sr.properties
+++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_sr.properties
@@ -311,3 +311,13 @@ forecastNotificationMessageHeadingTpl_2=Notification of moderate infection risk
 task_SendForecastEventNotificationsTask_name=Send forecast event notifications
 task_SendForecastEventNotificationsTask_description=Checks to see if there has been changes in forecasts to YELLOW or RED status. If so, finds subscribers to such events and sends notifications
 preferredLocale=Preferred locale
+VIPSLogicTitle=VIPS administration system
+indexText=VIPSLogic is the common administration system for VIPS. This is where you configure users, weather stations and forecasts, and where you report observations and write messages, and more.
+observationHeading=Observation heading
+observationText=Observation text
+statusTypeId=Status
+statusRemarks=Status remarks
+vipsLogicRole_4=Observation authority
+statusTypeIdTitle_1=Pending
+statusTypeIdTitle_2=Rejected
+statusTypeIdTitle_3=Approved
diff --git a/src/main/webapp/css/3rdparty/font-awesome.min.css b/src/main/webapp/css/3rdparty/font-awesome.min.css
new file mode 100644
index 0000000000000000000000000000000000000000..ee4e9782bf8b320c60a8d5d7aa070d47d752dba1
--- /dev/null
+++ b/src/main/webapp/css/3rdparty/font-awesome.min.css
@@ -0,0 +1,4 @@
+/*!
+ *  Font Awesome 4.4.0 by @davegandy - http://fontawesome.io - @fontawesome
+ *  License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
+ */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.4.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.4.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.4.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.4.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.4.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.4.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}
diff --git a/src/main/webapp/css/vipslogic.css b/src/main/webapp/css/vipslogic.css
index bf71bed29801753b50ee94683a8199fbd2cc982b..2de1c3e04487f57adb3878e627eb429e84159c57 100644
--- a/src/main/webapp/css/vipslogic.css
+++ b/src/main/webapp/css/vipslogic.css
@@ -24,9 +24,68 @@
         Main stylesheet of the VIPSLogic web app. Cusomizes the bootstrap.css
 */
 
+body {
+	 font-family: 'Source Sans Pro', sans-serif;
+}
+h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6
+{
+	font-family: 'Source Sans Pro', sans-serif;
+}
+
+h1 {
+	 font-size: 250%;
+	 line-height: 100%;
+	 /*font-family: "Bitter", Arial, Helvetica, sans-serif;*/
+	 font-weight: 400;
+	 font-style: normal;
+	 width: 100%;
+	 color: rgba(33, 37, 35, 1);
+	 /*padding: 15px 0px 10px 0px;*/
+}
+
+p {
+	 font-size: 16px;
+	 line-height: 130%;
+	 color: rgba(86, 91, 89, 1);
+	 /*font-family: "Roboto", Arial, Helvetica, sans-serif;*/
+	 font-weight: 300;
+	 font-style: normal;
+	 text-transform: none;
+	 width: 100%;
+}
+
+a, a:hover {
+	color: #008136;
+	text-decoration: underline;
+}
+
+
+.navbar{
+	background-color: #fff;
+	border: 0px none;
+	border-radius: 0px;
+	color: #222222 !important;
+	margin-bottom: 0px;
+	padding-top: 15px;
+	min-height: 80px;
+}
+
 .navbar-brand{
-    padding-top: 6px;
-    padding-bottom: 6px;
+	padding-top: 0px;
+	padding-bottom: 0px;
+	text-decoration: none;
+	color: #222222 !important;
+	font-weight: bold;
+	font-size: large; 
+}
+
+#siteTitle {
+	margin-left: 0px; 
+	padding-left: 0px; 
+	padding-top: 23px; 
+	font-size: 1.2em; 
+	font-weight: normal;
+	line-height: 130%;
 }
 
 fieldset {
@@ -37,10 +96,131 @@ fieldset {
     margin-bottom: 15px;
 }
 
+.navbar-nav li a {
+	color: #222222 !important; 
+	font-weight: normal !important;
+	font-size: large;
+	padding-top: 23px;
+}
+
+.navbar-default .navbar-nav>.active>a, 
+.navbar-default .navbar-nav>.active>a:hover, 
+.navbar-default .navbar-nav>.active>a:focus {
+	background-color: white;	
+}
+
+.dropdown-header {
+	font-size: 160%;
+	color: white;
+	background-color: #B6B8B5
+}
+
+.dropdown-menu {
+	border-radius: 0px;
+	padding: 0px;
+	border: 0px none;
+	background-color: #dddddd !important;
+	z-index: 101;
+}
+
+.dropdown-menu li a, li.divider { 
+	color: white; 
+	font-weight: normal !important;
+	font-size: medium !important;
+	/*border-top: 1px solid #B6B8B5;*/
+}
+
+.dropdown-menu li a:hover {
+	color: #295B40; 
+	font-weight: normal;
+	/**background-color: #31AB6E;*/
+		background-color: #aaaaaa;
+}
+
+
+.navbar-nav li a:hover {
+	color: #444444 !important; 
+	font-weight: bold !important;
+}
+
+.navbar-nav li a.currentLink {
+	/*color: #f7f6f2 !important;*/
+	font-weight: bold !important;
+	height: 65px;
+	border-bottom: 4px solid #008136;
+}
+
+.navbar-nav .dropdown a .caret{
+	border-top-color: #222222 !important;
+}
+
+
+
+.nav a
+{
+	text-decoration: none;
+}
+
+.navbar-default .navbar-nav>.open>a, .navbar-default .navbar-nav>.open>a:hover, .navbar-default .navbar-nav>.open>a:focus{
+	background-color: transparent;
+	border-bottom: 0px none;
+}
+
+.navbar-toggle {
+	margin-top: 48px;
+}
+
+a.dropdown-toggle{
+	height: 65px;
+}
+
 fieldset#modelSpecificFields{
     display: none;
 }
 
+footer {
+	padding: 30px 35px 35px 35px;
+	/*border-top: 4px solid #9f9f9f;*/
+	
+	background-color: #fff;
+}
+
+footer p {
+	color: #222222 !important;	
+	 line-height: 14px;
+	 margin-bottom: 10px;
+}
+
+footer p, footer a,footer a:visited{
+	/*color: white;*/
+	font-size: 12px;
+}
+
+footer a:hover {
+	color: rgba(49, 171, 110, 1);
+}
+
+table.table {
+	border-collapse: collapse;
+}
+
+table.table > tbody > tr > td {
+	background-color: white;
+	border-bottom: 3px solid #d9e6e4;
+}
+
+.table thead>tr>th {
+	border-bottom: 3px solid #d9e6e4;
+
+}
+
+.singleBlockContainer {
+	margin-bottom: 15px;
+	margin-top: 15px;
+	padding: 15px;
+	background-color: white !important;
+}
+
 legend {
     width: auto; /* Or auto */
     padding: 0 10px; /* To give a bit of padding on the left and right */
diff --git a/src/main/webapp/formdefinitions/observationForm.json b/src/main/webapp/formdefinitions/observationForm.json
index ba490d9baed297a79c40710829df4747ef73621e..bf111db093c4d13ad2398cbbb9c7159a7231f747 100644
--- a/src/main/webapp/formdefinitions/observationForm.json
+++ b/src/main/webapp/formdefinitions/observationForm.json
@@ -45,20 +45,37 @@
             "nullValue" : "{\"type\":\"FeatureCollection\",\"features\":[]}"
         },
         {
-            "name" : "observedValue",
-            "dataType" : "DOUBLE",
+            "name" : "observationData",
+            "dataType" : "STRING",
+            "fieldType" : "HIDDEN",
+            "required" : false
+        },
+        {
+            "name" : "observationMethodId",
+            "dataType" : "STRING",
+            "fieldType" : "SELECT_SINGLE",
             "required" : true
         },
         {
-            "name" : "denominator",
-            "dataType" : "INTEGER",
+            "name" : "observationHeading",
+            "dataType" : "STRING",
             "required" : false
         },
         {
-            "name" : "observationMethodId",
+            "name" : "observationText",
             "dataType" : "STRING",
+            "required" : false
+        },
+        {
+            "name" : "statusTypeId",
+            "dataType" : "INTEGER",
             "fieldType" : "SELECT_SINGLE",
-            "required" : true
+            "required" : false
+        },
+        {
+            "name" : "statusRemarks",
+            "dataType" : "STRING",
+            "required" : false
         }
         
     ]
diff --git a/src/main/webapp/images/logo_vips.svg b/src/main/webapp/images/logo_vips.svg
new file mode 100644
index 0000000000000000000000000000000000000000..0ef576dd552c26ca7c39ea45ba21ab63536f006e
--- /dev/null
+++ b/src/main/webapp/images/logo_vips.svg
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="225.7"
+   height="121.8"
+   id="svg3097"
+   version="1.1"
+   inkscape:version="0.48.4 r9939"
+   sodipodi:docname="logo.svg">
+  <defs
+     id="defs3099" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="0.48462403"
+     inkscape:cx="372.04724"
+     inkscape:cy="526.18109"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:window-width="1393"
+     inkscape:window-height="657"
+     inkscape:window-x="1657"
+     inkscape:window-y="244"
+     inkscape:window-maximized="0"
+     inkscape:snap-page="true"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0" />
+  <metadata
+     id="metadata3102">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-930.56218)">
+    <path
+       inkscape:connector-curvature="0"
+       class="st6"
+       d="M 150.8,1001.5622"
+       id="path26"
+       style="fill:#3d8052;fill-opacity:1" />
+    <path
+       inkscape:connector-curvature="0"
+       class="st4"
+       d="m 225.7,1011.5622 c 0,11.5 -3.6,20 -14.6,20 -6,0 -53.2,0.1 -53.2,0.1 0,0 -0.1,0 -0.1,-2 0,-7.1 6.4,-13.3 13.7,-13.3 7.1,0 12.7,0 24.5,-0.1 6.2,0 9.1,0.2 12.2,0.1 4.5,-0.2 4.2,-2.5 4.2,-5.2 0,-2.9 -0.8,-4.5 -4.6,-4.8 -5.9,-0.4 -32.9,-1.1 -39.6,-1.5 -9.9,-0.5 -10.9,-8.00002 -10.9,-20.10002 0,-12.8 6.7,-18.5 16.7,-18.7 4.6,-0.1 24.8,0 28.8,0 10,0 17.3,4.8 17.3,12.3 0,3 0,2.5 0,2.5 0,0 -44,-0.2 -45.1,-0.2 -3.5,0.2 -4.1,2.5 -4,5 0.2,4 1.5,4.2 6.7,4.5 5.8,0.4 26,0.9 35.7,1.3 9.2,0.6 12.3,8.80002 12.3,20.10002 z"
+       id="path28"
+       style="fill:#3d8052;fill-opacity:1" />
+    <g
+       transform="translate(-1,930.56218)"
+       id="g30"
+       style="fill:#3d8052;fill-opacity:1">
+      <g
+         id="g32"
+         style="fill:#3d8052;fill-opacity:1">
+        <path
+           inkscape:connector-curvature="0"
+           class="st4"
+           d="M 79.1,55.5 C 73.4,55.5 68.7,51.9 67,46.9 L 46.4,73.7 c 0,0 -17.1,-25.8 -19.3,-28.9 -2.9,-4.1 -8.4,-9.3 -18.1,-9.3 -3.1,0 -8,0 -8,0 l 45.6,65.8 25.7,-34.5 0,34.6 c 0,0 0.2,0 2.9,0 5.8,0 13.1,-6.4 13.2,-13.2 0,-6.4 0,-36.6 0,-36.6 -2.3,2.3 -5.6,3.9 -9.3,3.9 z"
+           id="path34"
+           style="fill:#3d8052;fill-opacity:1" />
+        <path
+           inkscape:connector-curvature="0"
+           class="st4"
+           d="m 132.9,35.9 c -10.7,0 -28.1,0 -37.6,-0.1 L 121.8,0.1 c 0,0 -4.8,-0.1 -9.4,-0.1 -5.5,0 -12.4,2.3 -15.9,6.8 -2.6,3.3 -17.4,24 -17.4,24 0,0 0,0 0,0 7,0 12.7,5.5 12.7,12.3 0,2.7 -0.9,5.2 -2.4,7.2 4.2,0 18,0 28.9,0 22.5,0 21.9,3.6 22,17.8 0.1,14.2 0.6,17.8 -21,17.8 -8,0 -17.4,0 -23.8,0 l -0.1,16.4 v 19.5 c 0,0 0,0 2,0 6.5,0 13.5,-6.1 13.5,-11.8 0,-2.6 -0.1,-8.9 -0.1,-8.9 8.8,0 18.2,0 24,0 16.5,0.1 21.6,-7.5 21.7,-33.1 0.2,-19.2 -7.3,-32.1 -23.6,-32.1 z"
+           id="path36"
+           style="fill:#3d8052;fill-opacity:1" />
+      </g>
+    </g>
+  </g>
+</svg>
diff --git a/src/main/webapp/js/3rdparty/metawidget/angular/metawidget-angular.js b/src/main/webapp/js/3rdparty/metawidget/angular/metawidget-angular.js
new file mode 100644
index 0000000000000000000000000000000000000000..0c705bf7ab0bb591940a20fe7278d04884fa9ee5
--- /dev/null
+++ b/src/main/webapp/js/3rdparty/metawidget/angular/metawidget-angular.js
@@ -0,0 +1,771 @@
+// Metawidget 4.2
+//
+// This file is dual licensed under both the LGPL
+// (http://www.gnu.org/licenses/lgpl-2.1.html) and the EPL
+// (http://www.eclipse.org/org/documents/epl-v10.php). As a
+// recipient of Metawidget, you may choose to receive it under either
+// the LGPL or the EPL.
+//
+// Commercial licenses are also available. See http://metawidget.org
+// for details.
+
+/**
+ * @author <a href="http://kennardconsulting.com">Richard Kennard</a>
+ */
+
+var metawidget = metawidget || {};
+
+( function() {
+
+	'use strict';
+
+	/**
+	 * Angular directive to expose <tt>metawidget.angular.AngularMetawidget</tt>.
+	 */
+
+	var directive = [ '$compile', '$parse', function( $compile, $parse ) {
+
+		// Returns the Metawidget
+
+		return {
+
+			/**
+			 * Metawidget is (E)lement level.
+			 */
+
+			restrict: 'E',
+
+			/**
+			 * Metawidget isolated scope.
+			 */
+
+			scope: {
+				ngModel: '=',
+				readOnly: '=',
+				config: '=',
+				ngShow: '=',
+				ngHide: '=',
+
+				// Configs cannot be 2-way ('=') because cannot 'watch' arrays
+
+				configs: '&'
+			},
+
+			/**
+			 * Metawidget must transclude child wigets, so that bindings in the
+			 * child widgets can natually refer to names in our parent scope,
+			 * rather than having to reference 'ng-model'.
+			 */
+
+			transclude: true,
+
+			/**
+			 * Angular compile function. Captures transcluded widgets, then
+			 * returns a <tt>postLink</tt> function that configures an
+			 * Angular-specific Metawidget and invokes buildWidgets on it.
+			 */
+
+			compile: function( element, attrs, transclude ) {
+
+				// Return postLink function
+
+				return function( scope, element, attrs ) {
+
+					// Set up an AngularMetawidget
+
+					var mw = new metawidget.angular.AngularMetawidget( element, attrs, transclude, scope, $compile, $parse );
+
+					// Build
+
+					var _oldToInspect = undefined;
+					_buildWidgets();
+
+					// Observe
+
+					var _watchConfig = scope.$watch( 'config', function( newValue, oldValue ) {
+
+						// Watch for config changes. These are rare, but
+						// otherwise we'd need to provide a way to externally
+						// trigger _buildWidgets
+						//
+						// Note: to be proper, we should process config changes
+						// *before* data changes, in the event they both change
+						// at once
+
+						if ( newValue !== oldValue ) {
+							mw.configure( newValue );
+							_buildWidgets();
+						}
+					} );
+
+					var _watchModel = scope.$watch( 'ngModel', function( newValue ) {
+
+						// Cannot test against mw.toInspect, because is pointed
+						// at the splitPath.type
+						//
+						// Re-inspect for 'undefined becoming defined' and
+						// 'object being updated'. But *not* for 'undefined
+						// becoming primitive, and then primitive being
+						// updated'. Otherwise every keypress will recreate the
+						// widget
+
+						if ( newValue !== _oldToInspect && typeof ( newValue ) === 'object' ) {
+							mw.invalidateInspection();
+							_buildWidgets();
+						}
+					} );
+
+					var _watchReadOnly = scope.$watch( 'readOnly', function( newValue ) {
+
+						// Test against mw.readOnly, not oldValue, because it
+						// may have been reset already by _buildWidgets
+
+						if ( newValue !== mw.readOnly ) {
+							// Do not mw.invalidateInspection()
+							_buildWidgets();
+						}
+					} );
+
+					var _watchNgShow = scope.$watch( 'ngShow', function( newValue, oldValue ) {
+
+						if ( newValue !== oldValue ) {
+							// Do not mw.invalidateInspection()
+							_buildWidgets();
+						}
+					} );
+
+					var _watchNgHide = scope.$watch( 'ngHide', function( newValue, oldValue ) {
+
+						if ( newValue !== oldValue ) {
+							// Do not mw.invalidateInspection()
+							_buildWidgets();
+						}
+					} );
+
+					// Clean up watches when element is destroyed
+
+					element.on( '$destroy', function() {
+
+						_watchConfig();
+						_watchModel();
+						_watchReadOnly();
+						_watchNgShow();
+						_watchNgHide();
+					} );
+
+					//
+					// Private method
+					//
+
+					function _buildWidgets() {
+
+						if ( scope.$eval( 'ngShow' ) === false || scope.$eval( 'ngHide' ) === true ) {
+							return;
+						}
+
+						_oldToInspect = scope.$eval( 'ngModel' );
+
+						mw.path = attrs.ngModel;
+						mw.toInspect = scope.$parent.$eval( metawidget.util.splitPath( mw.path ).type );
+						mw.readOnly = scope.$eval( 'readOnly' );
+						mw.buildWidgets();
+
+						// Note: when running under unit tests, errors get here.
+						// However, testing for 'jasmine !== undefined' caused
+						// problems at runtime
+					}
+				};
+			}
+		};
+	} ];
+
+	/**
+	 * AngularJS Metawidget module.
+	 */
+
+	var module = angular.module( 'metawidget', [] );
+	module.directive( 'metawidget', directive );
+
+	/**
+	 * Duplicate 'metawidget' directive, but with a namespace 'mw'. This allows
+	 * clients wishing to support IE8 to use &lt;mw:metawidget&gt; as the tag
+	 * name, as described at http://docs.angularjs.org/guide/ie
+	 */
+
+	module.directive( 'mwMetawidget', directive );
+
+	/**
+	 * @namespace Metawidget for AngularJS environments.
+	 */
+
+	metawidget.angular = metawidget.angular || {};
+
+	metawidget.angular.AngularMetawidget = function( element, attrs, transclude, scope, $compile, $parse ) {
+
+		if ( ! ( this instanceof metawidget.angular.AngularMetawidget ) ) {
+			throw new Error( "Constructor called as a function" );
+		}
+
+		// Pipeline (private)
+
+		var _pipeline = new metawidget.Pipeline( element[0] );
+		_pipeline._superLayoutWidget = _pipeline.layoutWidget;
+
+		_pipeline.layoutWidget = function( widget, elementName, attributes, container, mw ) {
+
+			_pipeline._superLayoutWidget.call( this, widget, elementName, attributes, container, mw );
+
+			// Compile so that 'ng-model', 'ng-required' etc become active. Do
+			// this as late as possible, in case directives want to use
+			// 'element.controller( 'form' )'
+			//
+			// Note: we ignore transcluded widgets. Compiling them again using
+			// $compile seemed to trigger 'ng-click' listeners twice?
+
+			if ( widget.overridden === undefined ) {
+				$compile( widget )( scope.$parent );
+			}
+		};
+
+		var _lastInspectionResult = undefined;
+
+		this.invalidateInspection = function() {
+
+			_lastInspectionResult = undefined;
+		};
+
+		// Configure defaults
+
+		_pipeline.inspector = new metawidget.inspector.PropertyTypeInspector();
+		_pipeline.inspectionResultProcessors = [ new metawidget.angular.inspectionresultprocessor.AngularInspectionResultProcessor( scope.$parent ) ];
+		_pipeline.widgetBuilder = new metawidget.widgetbuilder.CompositeWidgetBuilder( [ new metawidget.widgetbuilder.OverriddenWidgetBuilder(), new metawidget.widgetbuilder.ReadOnlyWidgetBuilder(),
+				new metawidget.widgetbuilder.HtmlWidgetBuilder() ] );
+		_pipeline.widgetProcessors = [ new metawidget.widgetprocessor.IdProcessor(), new metawidget.widgetprocessor.PlaceholderAttributeProcessor(),
+				new metawidget.widgetprocessor.DisabledAttributeProcessor(), new metawidget.angular.widgetprocessor.AngularWidgetProcessor( $parse, scope.$parent ) ];
+		_pipeline.layout = new metawidget.layout.HeadingTagLayoutDecorator( new metawidget.layout.TableLayout() );
+
+		this.configure = function( config ) {
+
+			_pipeline.configure( config );
+			this.invalidateInspection();
+		};
+
+		this.configure( scope.$eval( 'config' ) );
+		this.configure( scope.configs() );
+
+		// toInspect, path and readOnly set by _buildWidgets()
+
+		/**
+		 * Useful for WidgetBuilders to perform nested inspections (eg. for
+		 * Collections).
+		 */
+
+		this.inspect = function( toInspect, type, names ) {
+
+			return _pipeline.inspect( toInspect, type, names, this );
+		};
+
+		/**
+		 * Overridden to use jqLite.empty (safer for memory leaks).
+		 */
+
+		this.clearWidgets = function() {
+
+			var jqElement = angular.element( this.getElement() );
+
+			if ( jqElement.empty !== undefined ) {
+				jqElement.empty();
+			} else {
+
+				// Support older versions of Angular
+
+				jqElement.html( '' );
+			}
+		};
+
+		this.buildWidgets = function( inspectionResult ) {
+
+			// Rebuild the transcluded tree at the start of each build.
+			//
+			// Rebuilding only at the start of the <em>initial</em>
+			// build was sufficient for {{...}} expressions, but not
+			// 'ng-click' triggers.
+
+			var cloned = transclude( scope.$parent, function( clone ) {
+
+				return clone;
+			} );
+
+			this.overriddenNodes = [];
+
+			for ( var loop = 0; loop < cloned.length; loop++ ) {
+				var cloneNode = cloned[loop];
+
+				// Must check nodeType *and* other attributes,
+				// because Angular wraps everything (even text
+				// nodes) with a 'span class='ng-scope'' tag
+				//
+				// https://github.com/angular/angular.js/issues/1059
+
+				if ( cloneNode.nodeType === 1 && ( cloneNode.tagName !== 'SPAN' || cloneNode.attributes.length > 1 ) ) {
+					this.overriddenNodes.push( cloneNode );
+				}
+			}
+
+			// Inspect (if necessary)
+
+			if ( inspectionResult !== undefined ) {
+				_lastInspectionResult = inspectionResult;
+			} else if ( _lastInspectionResult === undefined ) {
+
+				// Safeguard against improperly implementing:
+				// http://blog.kennardconsulting.com/2013/02/metawidget-and-rest.html
+
+				if ( arguments.length > 0 ) {
+					throw new Error( "Calling buildWidgets( undefined ) may cause infinite loop. Check your argument, or pass no arguments instead" );
+				}
+
+				var splitPath = metawidget.util.splitPath( this.path );
+				_lastInspectionResult = _pipeline.inspect( this.toInspect, splitPath.type, splitPath.names, this );
+			}
+
+			// Cleanup children using Angular, so that $destroy gets triggered
+			// for nested Metawidgets
+
+			element.children().remove();
+
+			// Build widgets
+
+			_pipeline.buildWidgets( _lastInspectionResult, this );
+		};
+
+		/**
+		 * Overridden to inspect unused nodes by evaluating their 'ng-bind' or
+		 * 'ng-model' attribute.
+		 */
+
+		this.onEndBuild = function() {
+
+			while ( this.overriddenNodes.length > 0 ) {
+
+				var child = this.overriddenNodes[0];
+				this.overriddenNodes.splice( 0, 1 );
+
+				// Unused facets don't count
+
+				if ( child.tagName === 'FACET' ) {
+					continue;
+				}
+
+				var childAttributes = {};
+				var loop, length;
+
+				// Lookup binding attribute
+				//
+				// Note: be sure to normalize it
+				//
+				// Note: be sure to lowercase it too, because HTML attribute
+				// names are case-insensitive and Angular's template mechanism
+				// lowercases them
+
+				length = child.attributes.length;
+				for ( loop = 0; loop < length; loop++ ) {
+					var attribute = child.attributes[loop];
+					var normalizedName = attrs.$normalize( attribute.name ).toLowerCase();
+
+					if ( normalizedName === 'ngbind' || normalizedName === 'ngmodel' ) {
+						var splitPath = metawidget.util.splitPath( attribute.value );
+						var toInspect = scope.$parent.$eval( splitPath.type );
+						childAttributes = _pipeline.inspect( toInspect, splitPath.type, splitPath.names, this );
+						break;
+					}
+				}
+
+				// Manually created components default to no section
+
+				if ( childAttributes === undefined ) {
+					childAttributes = {
+						section: ''
+					};
+				}
+
+				// Stubs can supply their own metadata (such as 'title')
+
+				if ( child.tagName === 'STUB' ) {
+					length = child.attributes.length;
+					for ( loop = 0; loop < length; loop++ ) {
+						var prop = child.attributes[loop];
+						childAttributes[prop.nodeName] = prop.nodeValue;
+					}
+				}
+
+				_pipeline.layoutWidget( child, "property", childAttributes, _pipeline.element, this );
+			}
+		};
+
+		/**
+		 * Returns the element this Metawidget is attached to.
+		 */
+
+		this.getElement = function() {
+
+			return _pipeline.element;
+		};
+
+		this.buildNestedMetawidget = function( attributes, config ) {
+
+			var nestedMetawidget = metawidget.util.createElement( this, 'metawidget' );
+			nestedMetawidget.setAttribute( 'ng-model', metawidget.util.appendPath( attributes, this ) );
+
+			if ( metawidget.util.isTrueOrTrueString( attributes.readOnly ) ) {
+				nestedMetawidget.setAttribute( 'read-only', 'true' );
+			} else if ( attrs.readOnly !== undefined ) {
+				nestedMetawidget.setAttribute( 'read-only', attrs.readOnly );
+			}
+
+			// Duck-type our 'pipeline' as the 'config' of the nested
+			// Metawidget. This neatly passes everything down, including a
+			// decremented 'maximumInspectionDepth'
+			//
+			// Use a private counter to stop configIds conflicting. This is
+			// because scope.$parent is a very broad scope - it's hard to
+			// know what might be in it. We must use scope.$parent because we
+			// $compile relative to our $parent. And we do *that* so that our
+			// bindings look more 'natural' (eg. 'foo.bar' not 'toInspect.bar')
+
+			scope.$parent._nestedMetawidgetConfigId = scope.$parent._nestedMetawidgetConfigId || 0;
+			var configId = '_metawidgetConfig' + scope.$parent._nestedMetawidgetConfigId++;
+			scope.$parent[configId] = _pipeline;
+
+			if ( config !== undefined ) {
+				var configId2 = '_metawidgetConfig' + scope.$parent._nestedMetawidgetConfigId++;
+				scope.$parent[configId2] = config;
+				nestedMetawidget.setAttribute( 'configs', '[' + configId + ',' + configId2 + ']' );
+			} else {
+				nestedMetawidget.setAttribute( 'config', configId );
+			}
+
+			return nestedMetawidget;
+		};
+	};
+
+	/**
+	 * @namespace InspectionResultProcessors for AngularJS environments.
+	 */
+
+	metawidget.angular.inspectionresultprocessor = metawidget.angular.inspectionresultprocessor || {};
+
+	/**
+	 * @class InspectionResultProcessor to evaluate Angular expressions.
+	 * 
+	 * @param scope
+	 *            parent scope of the Metawidget directive
+	 * @param buildWidgets
+	 *            a function to use to rebuild the widgets following a $watch
+	 * @returns {metawidget.angular.AngularInspectionResultProcessor}
+	 */
+
+	metawidget.angular.inspectionresultprocessor.AngularInspectionResultProcessor = function( scope ) {
+
+		if ( ! ( this instanceof metawidget.angular.inspectionresultprocessor.AngularInspectionResultProcessor ) ) {
+			throw new Error( "Constructor called as a function" );
+		}
+
+		this.processInspectionResult = function( inspectionResult, mw ) {
+
+			/**
+			 * When a watched expression changes, reinspect and rebuild.
+			 */
+
+			function _watchExpression( newValue, oldValue ) {
+
+				if ( newValue !== oldValue ) {
+
+					// Clear all watches...
+
+					for ( var loop = 0, length = mw._angularInspectionResultProcessor.length; loop < length; loop++ ) {
+						mw._angularInspectionResultProcessor[loop]();
+					}
+
+					// ..and then reinspect
+
+					mw.invalidateInspection();
+					mw.buildWidgets();
+				}
+			}
+
+			mw._angularInspectionResultProcessor = mw._angularInspectionResultProcessor || [];
+
+			// For each property in the inspection result...
+
+			for ( var propertyName in inspectionResult ) {
+
+				// ...including recursing into 'properties'...
+
+				var expression = inspectionResult[propertyName];
+
+				if ( expression instanceof Object ) {
+					this.processInspectionResult( expression, mw );
+					continue;
+				}
+
+				// ...if the value looks like an expression...
+
+				if ( expression === undefined || expression === null || expression.slice === undefined ) {
+					continue;
+				}
+
+				if ( expression.length < 4 || expression.slice( 0, 2 ) !== '{{' || expression.slice( expression.length - 2, expression.length ) !== '}}' ) {
+					continue;
+				}
+
+				// ...evaluate it...
+
+				expression = expression.slice( 2, expression.length - 2 );
+				inspectionResult[propertyName] = scope.$eval( expression ) + '';
+
+				// ...and watch it for future changes
+
+				var watch = scope.$watch( expression, _watchExpression );
+
+				mw._angularInspectionResultProcessor.push( watch );
+			}
+
+			return inspectionResult;
+		};
+	};
+
+	/**
+	 * @namespace WidgetProcessors for AngularJS environments.
+	 */
+
+	metawidget.angular.widgetprocessor = metawidget.angular.widgetprocessor || {};
+
+	/**
+	 * @class WidgetProcessor to add Angular bindings and validation.
+	 * 
+	 * @param scope
+	 *            parent scope of the Metawidget directive
+	 * 
+	 * @returns {metawidget.angular.AngularWidgetProcessor}
+	 */
+
+	metawidget.angular.widgetprocessor.AngularWidgetProcessor = function( $parse, scope ) {
+
+		if ( ! ( this instanceof metawidget.angular.widgetprocessor.AngularWidgetProcessor ) ) {
+			throw new Error( "Constructor called as a function" );
+		}
+
+		this.processWidget = function( widget, elementName, attributes, mw ) {
+
+			// Binding
+			//
+			// Scope the binding to scope.$parent, not scope, so that the
+			// generated bindings look more 'natural' (eg. 'foo.bar' not
+			// 'toInspect.bar')
+
+			var binding = mw.path;
+
+			if ( elementName !== 'entity' ) {
+				binding = metawidget.util.appendPathWithName( binding, attributes );
+			}
+
+			if ( widget.tagName === 'OUTPUT' ) {
+
+				// Don't overwrite existing binding (if set by the
+				// WidgetBuilder)
+
+				if ( !widget.hasAttribute( 'ng-bind' ) ) {
+					if ( metawidget.util.isTrueOrTrueString( attributes.masked ) ) {
+
+						// Special support for masked output
+
+						scope.$parent.mwMaskedOutput = _maskedOutput;
+						widget.setAttribute( 'ng-bind', 'mwMaskedOutput(' + binding + ')' );
+					} else if ( attributes.type === 'array' ) {
+
+						// Special support for outputting arrays
+
+						widget.setAttribute( 'ng-bind', binding + ".join(', ')" );
+					} else if ( attributes.enumTitles !== undefined ) {
+
+						// Special support for enumTitles
+
+						scope.$parent.mwLookupEnumTitle = scope.$parent.mwLookupEnumTitle || {};
+						scope.$parent.mwLookupEnumTitle[binding] = function( value ) {
+
+							return metawidget.util.lookupEnumTitle( value, attributes['enum'], attributes.enumTitles );
+						};
+						widget.setAttribute( 'ng-bind', 'mwLookupEnumTitle["' + binding + '"](' + binding + ')' );
+
+					} else if ( attributes.type === 'date' ) {
+
+						// Special support for date formatting
+
+						widget.setAttribute( 'ng-bind', binding + "|date" );
+
+					} else {
+						widget.setAttribute( 'ng-bind', binding );
+					}
+				}
+
+			} else if ( widget.tagName === 'INPUT' && widget.getAttribute( 'type' ) === 'submit' ) {
+
+				// input type='submit' should not be bound: should go via
+				// ng-submit at the form level
+
+				widget.removeAttribute( 'ng-click' );
+
+			} else if ( widget.tagName === 'INPUT' && widget.getAttribute( 'type' ) === 'button' ) {
+
+				widget.setAttribute( 'ng-click', binding + '()' );
+
+			} else if ( attributes['enum'] !== undefined && ( attributes.type === 'array' || attributes.componentType !== undefined ) && widget.tagName === 'DIV' ) {
+
+				// Special support for multi-selects and radio buttons
+
+				for ( var loop = 0, length = widget.childNodes.length; loop < length; loop++ ) {
+					var label = widget.childNodes[loop];
+
+					if ( label.tagName === 'LABEL' && label.childNodes.length === 2 ) {
+						var child = label.childNodes[0];
+
+						if ( child.tagName === 'INPUT' ) {
+							if ( child.getAttribute( 'type' ) === 'radio' ) {
+								child.setAttribute( 'ng-model', binding );
+								if ( child.value === true || child.value === 'true' ) {
+									child.setAttribute( 'ng-value', 'true' );
+								} else if ( child.value === false || child.value === 'false' ) {
+									child.setAttribute( 'ng-value', 'false' );
+								}
+							} else if ( child.getAttribute( 'type' ) === 'checkbox' ) {
+								child.setAttribute( 'ng-checked', binding + ".indexOf('" + child.value + "')>=0" );
+								scope.mwUpdateSelection = _updateSelection;
+								child.setAttribute( 'ng-click', "mwUpdateSelection($event,'" + binding + "')" );
+							}
+						}
+					}
+				}
+
+			} else if ( widget.tagName === 'SELECT' ) {
+
+				widget.setAttribute( 'ng-model', binding );
+				
+				// Special support for non-string selects
+
+				if ( attributes.type === 'boolean' || attributes.type === 'integer' || attributes.type === 'number' ) {
+					widget.setAttribute( 'ng-change', "mwChangeAsType('" + attributes.type + "','" + binding + "')" );
+					scope.mwChangeAsType = _changeAsType;
+					
+					for ( var loop = 0, length = widget.childNodes.length; loop < length; loop++ ) {
+
+						var child = widget.childNodes[loop];
+
+						if ( child.tagName === 'OPTION' && child.value !== '' ) {
+							child.setAttribute( 'ng-selected', binding + "==" + child.value );
+						}
+					}
+				}
+
+			} else if ( widget.tagName === 'INPUT' || widget.tagName === 'TEXTAREA' ) {
+				widget.setAttribute( 'ng-model', binding );
+			}
+
+			// Validation
+
+			if ( !metawidget.util.isTrueOrTrueString( attributes.readOnly ) ) {
+
+				if ( attributes.required !== undefined ) {
+					widget.setAttribute( 'ng-required', attributes.required );
+				}
+
+				if ( attributes.minLength !== undefined ) {
+					widget.setAttribute( 'ng-minlength', attributes.minLength );
+				}
+
+				if ( attributes.maxLength !== undefined ) {
+					widget.setAttribute( 'ng-maxlength', attributes.maxLength );
+
+					// (retain maxlength set by HtmlWidgetBuilder)
+				}
+			}
+
+			return widget;
+		};
+
+		//
+		// Private methods
+		//
+
+		/**
+		 * Special support for multi-select checkboxes.
+		 */
+
+		function _updateSelection( $event, binding ) {
+
+			// Lookup the bound array (if any)...
+
+			var selected = scope.$eval( binding );
+
+			if ( selected === undefined ) {
+				selected = [];
+				$parse( binding ).assign( scope, selected );
+			}
+
+			// ...and either add our checkbox's value into it...
+
+			var checkbox = $event.target;
+			var indexOf = selected.indexOf( checkbox.value );
+
+			if ( checkbox.checked === true ) {
+				if ( indexOf === -1 ) {
+					selected.push( checkbox.value );
+				}
+				return;
+			}
+
+			// ...or remove our checkbox's value from it
+
+			if ( indexOf !== -1 ) {
+				selected.splice( indexOf, 1 );
+			}
+		}
+
+		/**
+		 * Special support for masked output.
+		 */
+
+		function _maskedOutput( value ) {
+
+			if ( value === undefined ) {
+				return;
+			}
+
+			return metawidget.util.fillString( '*', value.length );
+		}
+		
+		/**
+		 * Special support for non-string selects.
+		 */
+		
+		function _changeAsType( type, binding ) {
+			
+			var parsedBinding = $parse( binding ); 
+			
+			if ( type === 'boolean' ) {
+				parsedBinding.assign( scope, parsedBinding( scope ) === 'true' );
+				return;		
+			}
+			
+			if ( type === 'integer' ) {
+				parsedBinding.assign( scope, parseInt( parsedBinding( scope ) ));
+				return;		
+			}
+
+			if ( type === 'number' ) {
+				parsedBinding.assign( scope, parseFloat( parsedBinding( scope ) ));
+				return;		
+			}
+		}
+	};
+} )();
diff --git a/src/main/webapp/js/3rdparty/metawidget/angular/metawidget-angular.min.js b/src/main/webapp/js/3rdparty/metawidget/angular/metawidget-angular.min.js
new file mode 100644
index 0000000000000000000000000000000000000000..25b365f59b0c5111e692d45d8e16c5f6d2c5052e
--- /dev/null
+++ b/src/main/webapp/js/3rdparty/metawidget/angular/metawidget-angular.min.js
@@ -0,0 +1,18 @@
+// Metawidget 4.2 minified
+//
+// This file is dual licensed under both the LGPL
+// (http://www.gnu.org/licenses/lgpl-2.1.html) and the EPL
+// (http://www.eclipse.org/org/documents/epl-v10.php). As a
+// recipient of Metawidget, you may choose to receive it under either
+// the LGPL or the EPL.
+//
+// Commercial licenses are also available. See http://metawidget.org
+// for details.
+//
+// Author: Richard Kennard (http://kennardconsulting.com)
+
+var metawidget=metawidget||{};(function(){var b=["$compile","$parse",function(c,d){return{restrict:"E",scope:{ngModel:"=",readOnly:"=",config:"=",ngShow:"=",ngHide:"=",configs:"&"},transclude:true,compile:function(g,f,e){return function(r,k,p){var q=new metawidget.angular.AngularMetawidget(k,p,e,r,c,d);var j=undefined;m();var o=r.$watch("config",function(t,s){if(t!==s){q.configure(t);m()}});var h=r.$watch("ngModel",function(s){if(s!==j&&typeof(s)==="object"){q.invalidateInspection();m()}});var i=r.$watch("readOnly",function(s){if(s!==q.readOnly){m()}});var n=r.$watch("ngShow",function(t,s){if(t!==s){m()}});var l=r.$watch("ngHide",function(t,s){if(t!==s){m()}});k.on("$destroy",function(){o();h();i();n();l()});function m(){if(r.$eval("ngShow")===false||r.$eval("ngHide")===true){return}j=r.$eval("ngModel");q.path=p.ngModel;q.toInspect=r.$parent.$eval(metawidget.util.splitPath(q.path).type);q.readOnly=r.$eval("readOnly");q.buildWidgets()}}}}}];var a=angular.module("metawidget",[]);a.directive("metawidget",b);a.directive("mwMetawidget",b);metawidget.angular=metawidget.angular||{};metawidget.angular.AngularMetawidget=function(g,d,c,h,f,i){if(!(this instanceof metawidget.angular.AngularMetawidget)){throw new Error("Constructor called as a function")}var e=new metawidget.Pipeline(g[0]);e._superLayoutWidget=e.layoutWidget;e.layoutWidget=function(n,k,m,l,o){e._superLayoutWidget.call(this,n,k,m,l,o);if(n.overridden===undefined){f(n)(h.$parent)}};var j=undefined;this.invalidateInspection=function(){j=undefined};e.inspector=new metawidget.inspector.PropertyTypeInspector();e.inspectionResultProcessors=[new metawidget.angular.inspectionresultprocessor.AngularInspectionResultProcessor(h.$parent)];e.widgetBuilder=new metawidget.widgetbuilder.CompositeWidgetBuilder([new metawidget.widgetbuilder.OverriddenWidgetBuilder(),new metawidget.widgetbuilder.ReadOnlyWidgetBuilder(),new metawidget.widgetbuilder.HtmlWidgetBuilder()]);e.widgetProcessors=[new metawidget.widgetprocessor.IdProcessor(),new metawidget.widgetprocessor.PlaceholderAttributeProcessor(),new metawidget.widgetprocessor.DisabledAttributeProcessor(),new metawidget.angular.widgetprocessor.AngularWidgetProcessor(i,h.$parent)];
+e.layout=new metawidget.layout.HeadingTagLayoutDecorator(new metawidget.layout.TableLayout());this.configure=function(k){e.configure(k);this.invalidateInspection()};this.configure(h.$eval("config"));this.configure(h.configs());this.inspect=function(m,k,l){return e.inspect(m,k,l,this)};this.clearWidgets=function(){var k=angular.element(this.getElement());if(k.empty!==undefined){k.empty()}else{k.html("")}};this.buildWidgets=function(o){var k=c(h.$parent,function(p){return p});this.overriddenNodes=[];for(var l=0;l<k.length;l++){var n=k[l];if(n.nodeType===1&&(n.tagName!=="SPAN"||n.attributes.length>1)){this.overriddenNodes.push(n)}}if(o!==undefined){j=o}else{if(j===undefined){if(arguments.length>0){throw new Error("Calling buildWidgets( undefined ) may cause infinite loop. Check your argument, or pass no arguments instead")}var m=metawidget.util.splitPath(this.path);j=e.inspect(this.toInspect,m.type,m.names,this)}}g.children().remove();e.buildWidgets(j,this)};this.onEndBuild=function(){while(this.overriddenNodes.length>0){var l=this.overriddenNodes[0];this.overriddenNodes.splice(0,1);if(l.tagName==="FACET"){continue}var o={};var q,m;m=l.attributes.length;for(q=0;q<m;q++){var n=l.attributes[q];var p=d.$normalize(n.name).toLowerCase();if(p==="ngbind"||p==="ngmodel"){var r=metawidget.util.splitPath(n.value);var s=h.$parent.$eval(r.type);o=e.inspect(s,r.type,r.names,this);break}}if(o===undefined){o={section:""}}if(l.tagName==="STUB"){m=l.attributes.length;for(q=0;q<m;q++){var k=l.attributes[q];o[k.nodeName]=k.nodeValue}}e.layoutWidget(l,"property",o,e.element,this)}};this.getElement=function(){return e.element};this.buildNestedMetawidget=function(k,l){var n=metawidget.util.createElement(this,"metawidget");n.setAttribute("ng-model",metawidget.util.appendPath(k,this));if(metawidget.util.isTrueOrTrueString(k.readOnly)){n.setAttribute("read-only","true")}else{if(d.readOnly!==undefined){n.setAttribute("read-only",d.readOnly)}}h.$parent._nestedMetawidgetConfigId=h.$parent._nestedMetawidgetConfigId||0;
+var o="_metawidgetConfig"+h.$parent._nestedMetawidgetConfigId++;h.$parent[o]=e;if(l!==undefined){var m="_metawidgetConfig"+h.$parent._nestedMetawidgetConfigId++;h.$parent[m]=l;n.setAttribute("configs","["+o+","+m+"]")}else{n.setAttribute("config",o)}return n}};metawidget.angular.inspectionresultprocessor=metawidget.angular.inspectionresultprocessor||{};metawidget.angular.inspectionresultprocessor.AngularInspectionResultProcessor=function(c){if(!(this instanceof metawidget.angular.inspectionresultprocessor.AngularInspectionResultProcessor)){throw new Error("Constructor called as a function")}this.processInspectionResult=function(f,i){function d(m,k){if(m!==k){for(var j=0,l=i._angularInspectionResultProcessor.length;j<l;j++){i._angularInspectionResultProcessor[j]()}i.invalidateInspection();i.buildWidgets()}}i._angularInspectionResultProcessor=i._angularInspectionResultProcessor||[];for(var e in f){var h=f[e];if(h instanceof Object){this.processInspectionResult(h,i);continue}if(h===undefined||h===null||h.slice===undefined){continue}if(h.length<4||h.slice(0,2)!=="{{"||h.slice(h.length-2,h.length)!=="}}"){continue}h=h.slice(2,h.length-2);f[e]=c.$eval(h)+"";var g=c.$watch(h,d);i._angularInspectionResultProcessor.push(g)}return f}};metawidget.angular.widgetprocessor=metawidget.angular.widgetprocessor||{};metawidget.angular.widgetprocessor.AngularWidgetProcessor=function(f,e){if(!(this instanceof metawidget.angular.widgetprocessor.AngularWidgetProcessor)){throw new Error("Constructor called as a function")}this.processWidget=function(k,p,j,o){var m=o.path;if(p!=="entity"){m=metawidget.util.appendPathWithName(m,j)}if(k.tagName==="OUTPUT"){if(!k.hasAttribute("ng-bind")){if(metawidget.util.isTrueOrTrueString(j.masked)){e.$parent.mwMaskedOutput=d;k.setAttribute("ng-bind","mwMaskedOutput("+m+")")}else{if(j.type==="array"){k.setAttribute("ng-bind",m+".join(', ')")}else{if(j.enumTitles!==undefined){e.$parent.mwLookupEnumTitle=e.$parent.mwLookupEnumTitle||{};e.$parent.mwLookupEnumTitle[m]=function(q){return metawidget.util.lookupEnumTitle(q,j["enum"],j.enumTitles)
+};k.setAttribute("ng-bind",'mwLookupEnumTitle["'+m+'"]('+m+")")}else{if(j.type==="date"){k.setAttribute("ng-bind",m+"|date")}else{k.setAttribute("ng-bind",m)}}}}}}else{if(k.tagName==="INPUT"&&k.getAttribute("type")==="submit"){k.removeAttribute("ng-click")}else{if(k.tagName==="INPUT"&&k.getAttribute("type")==="button"){k.setAttribute("ng-click",m+"()")}else{if(j["enum"]!==undefined&&(j.type==="array"||j.componentType!==undefined)&&k.tagName==="DIV"){for(var l=0,i=k.childNodes.length;l<i;l++){var n=k.childNodes[l];if(n.tagName==="LABEL"&&n.childNodes.length===2){var h=n.childNodes[0];if(h.tagName==="INPUT"){if(h.getAttribute("type")==="radio"){h.setAttribute("ng-model",m);if(h.value===true||h.value==="true"){h.setAttribute("ng-value","true")}else{if(h.value===false||h.value==="false"){h.setAttribute("ng-value","false")}}}else{if(h.getAttribute("type")==="checkbox"){h.setAttribute("ng-checked",m+".indexOf('"+h.value+"')>=0");e.mwUpdateSelection=g;h.setAttribute("ng-click","mwUpdateSelection($event,'"+m+"')")}}}}}}else{if(k.tagName==="SELECT"){k.setAttribute("ng-model",m);if(j.type==="boolean"||j.type==="integer"||j.type==="number"){k.setAttribute("ng-change","mwChangeAsType('"+j.type+"','"+m+"')");e.mwChangeAsType=c;for(var l=0,i=k.childNodes.length;l<i;l++){var h=k.childNodes[l];if(h.tagName==="OPTION"&&h.value!==""){h.setAttribute("ng-selected",m+"=="+h.value)}}}}else{if(k.tagName==="INPUT"||k.tagName==="TEXTAREA"){k.setAttribute("ng-model",m)}}}}}}if(!metawidget.util.isTrueOrTrueString(j.readOnly)){if(j.required!==undefined){k.setAttribute("ng-required",j.required)}if(j.minLength!==undefined){k.setAttribute("ng-minlength",j.minLength)}if(j.maxLength!==undefined){k.setAttribute("ng-maxlength",j.maxLength)}}return k};function g(h,l){var j=e.$eval(l);if(j===undefined){j=[];f(l).assign(e,j)}var k=h.target;var i=j.indexOf(k.value);if(k.checked===true){if(i===-1){j.push(k.value)}return}if(i!==-1){j.splice(i,1)}}function d(h){if(h===undefined){return}return metawidget.util.fillString("*",h.length)
+}function c(h,j){var i=f(j);if(h==="boolean"){i.assign(e,i(e)==="true");return}if(h==="integer"){i.assign(e,parseInt(i(e)));return}if(h==="number"){i.assign(e,parseFloat(i(e)));return}}}})();
diff --git a/src/main/webapp/js/3rdparty/metawidget/bootstrap/metawidget-bootstrap.js b/src/main/webapp/js/3rdparty/metawidget/bootstrap/metawidget-bootstrap.js
new file mode 100644
index 0000000000000000000000000000000000000000..9fb6ddcd4c934c7d43d465accd826f486dc4d0b4
--- /dev/null
+++ b/src/main/webapp/js/3rdparty/metawidget/bootstrap/metawidget-bootstrap.js
@@ -0,0 +1,300 @@
+// Metawidget 4.2
+//
+// This file is dual licensed under both the LGPL
+// (http://www.gnu.org/licenses/lgpl-2.1.html) and the EPL
+// (http://www.eclipse.org/org/documents/epl-v10.php). As a
+// recipient of Metawidget, you may choose to receive it under either
+// the LGPL or the EPL.
+//
+// Commercial licenses are also available. See http://metawidget.org
+// for details.
+
+/**
+ * @author <a href="http://kennardconsulting.com">Richard Kennard</a>
+ */
+
+var metawidget = metawidget || {};
+
+( function() {
+
+	'use strict';
+
+	/**
+	 * @namespace Metawidget Twitter Bootstrap support.
+	 */
+
+	metawidget.bootstrap = metawidget.bootstrap || {};
+
+	/**
+	 * @namespace WidgetProcessors for Twitter Bootstrap environments.
+	 */
+
+	metawidget.bootstrap.widgetprocessor = metawidget.bootstrap.widgetprocessor || {};
+
+	/**
+	 * @class WidgetProcessor to add CSS styles for Bootstrap.
+	 *        <p>
+	 *        Note: in some cases this WidgetProcessor wraps the given widget
+	 *        with Bootstrap-specific markup (e.g. &lt;div
+	 *        class="input-prepend"&gt;). Therefore, BootstrapWidgetProcessor
+	 *        should come <em>after</em> WidgetProcessors that expect widgets
+	 *        to be unwrapped (such as <tt>SimpleBindingProcessor</tt>).
+	 */
+
+	metawidget.bootstrap.widgetprocessor.BootstrapWidgetProcessor = function( config ) {
+
+		if ( ! ( this instanceof metawidget.bootstrap.widgetprocessor.BootstrapWidgetProcessor ) ) {
+			throw new Error( "Constructor called as a function" );
+		}
+
+		var _version = config !== undefined ? config.version : 3;
+
+		this.processWidget = function( widget, elementName, attributes, mw ) {
+
+			var tagName = widget.tagName;
+
+			if ( tagName === 'TABLE' ) {
+
+				metawidget.util.appendToAttribute( widget, 'class', 'table table-striped table-bordered table-hover' );
+
+			} else if ( tagName === 'SELECT' || tagName === 'TEXTAREA' ) {
+
+				metawidget.util.appendToAttribute( widget, 'class', 'form-control' );
+
+			} else if ( tagName === 'OUTPUT' ) {
+
+				// Pad output tags the same way as .form-control pads input
+				// tags.
+				// See:
+				// https://github.com/twbs/bootstrap/issues/9969
+
+				metawidget.util.appendToAttribute( widget, 'style', 'padding:6px 12px', ';' );
+
+			} else if ( tagName === 'INPUT' ) {
+
+				var type = widget.getAttribute( 'type' );
+
+				switch ( type ) {
+
+					case 'submit':
+						metawidget.util.appendToAttribute( widget, 'class', 'btn btn-primary' );
+						break;
+
+					case 'button':
+						metawidget.util.appendToAttribute( widget, 'class', 'btn btn-default' );
+						break;
+
+					default: {
+
+						if ( type !== 'checkbox' ) {
+							metawidget.util.appendToAttribute( widget, 'class', 'form-control' );
+						}
+
+						if ( attributes.inputPrepend !== undefined || attributes.inputAppend !== undefined ) {
+							var div = metawidget.util.createElement( mw, 'div' );
+							var span;
+							if ( attributes.inputPrepend !== undefined ) {
+								div.setAttribute( 'class', 'input-prepend input-group' );
+								span = metawidget.util.createElement( mw, 'span' );
+								span.setAttribute( 'class', 'add-on input-group-addon' );
+								span.innerHTML = attributes.inputPrepend;
+								div.appendChild( span );
+							}
+							div.appendChild( widget );
+							if ( attributes.inputAppend !== undefined ) {
+								if ( attributes.inputPrepend !== undefined ) {
+									div.setAttribute( 'class', 'input-prepend input-append input-group' );
+								} else {
+									div.setAttribute( 'class', 'input-append input-group' );
+								}
+								span = metawidget.util.createElement( mw, 'span' );
+								span.setAttribute( 'class', 'add-on input-group-addon' );
+								span.innerHTML = attributes.inputAppend;
+								div.appendChild( span );
+							}
+							return div;
+						}
+					}
+				}
+			} else if ( _version === 3 && tagName === 'DIV' && attributes['enum'] !== undefined && ( attributes.type === 'array' || attributes.componentType !== undefined ) ) {
+
+				// Bootstrap 3.x likes a DIV around each LABEL
+				
+				for ( var loop = 0, length = widget.childNodes.length; loop < length; loop++ ) {
+
+					var label = widget.childNodes[loop];
+
+					var innerDiv = metawidget.util.createElement( mw, 'div' );
+					innerDiv.setAttribute( 'class', label.getAttribute( 'class' ) );
+					label.removeAttribute( 'class' );
+					widget.replaceChild( innerDiv, label );
+					innerDiv.appendChild( label );
+				}
+			}
+
+			return widget;
+		};
+	};
+
+	/**
+	 * @namespace Layouts for Twitter Bootstrap environments.
+	 */
+
+	metawidget.bootstrap.layout = metawidget.bootstrap.layout || {};
+
+	/**
+	 * @class Layout to wrap widgets with divs suitable for 'form-vertical' or
+	 *        'form-horizontal' Bootstrap layouts.
+	 *        <p>
+	 *        This Layout extends metawidget.layout.DivLayout. It adds Bootstrap
+	 *        CSS classes such as 'form-group' and 'control-label' to the divs.
+	 * 
+	 * @returns {metawidget.bootstrap.layout.BootstrapDivLayout}
+	 */
+
+	metawidget.bootstrap.layout.BootstrapDivLayout = function( config ) {
+
+		if ( ! ( this instanceof metawidget.bootstrap.layout.BootstrapDivLayout ) ) {
+			throw new Error( "Constructor called as a function" );
+		}
+
+		if ( config === undefined ) {
+			config = {};
+		}
+
+		if ( config.version === 2 ) {
+			if ( config.divStyleClasses === undefined ) {
+				config.divStyleClasses = [ 'control-group', undefined, 'controls' ];
+			}
+			if ( config.labelStyleClass === undefined ) {
+				config.labelStyleClass = 'control-label';
+			}
+		} else {
+			if ( config.divStyleClasses === undefined ) {
+				config.divStyleClasses = [ 'form-group', 'col-sm-2 control-label', 'col-sm-10' ];
+			}
+			if ( config.widgetDivSpanAllClass === undefined ) {
+				config.widgetDivSpanAllClass = 'col-sm-12';
+			}
+			if ( config.widgetDivOffsetClass === undefined ) {
+				config.widgetDivOffsetClass = 'col-sm-offset-2';
+			}
+			if ( config.suppressLabelSuffixOnCheckboxes === undefined ) {
+				config.suppressLabelSuffixOnCheckboxes = true;
+			}
+			if ( config.wrapInsideLabels === undefined ) {
+				config.wrapInsideLabels = [ 'checkbox', 'radio' ];
+			}
+			if ( config.wrapWithExtraDiv === undefined ) {
+				config.wrapWithExtraDiv = {
+					checkbox: 'checkbox',
+					radio: 'radio'
+				};
+			}
+		}
+
+		var layout = new metawidget.layout.DivLayout( config );
+
+		// If there is no label, Bootstrap 3 requires an explicit grid position
+		// to be set or the widget div will not automatically 'pull right'
+
+		if ( config.version !== 2 ) {
+			var superLayoutWidget = layout.layoutWidget;
+			layout.layoutWidget = function( widget, elementName, attributes, container, mw ) {
+
+				superLayoutWidget.call( this, widget, elementName, attributes, container, mw );
+
+				var outerDiv = container.childNodes[container.childNodes.length - 1];
+				if ( outerDiv !== undefined && outerDiv.childNodes.length === 1 ) {
+					if ( attributes.title === null ) {
+						outerDiv.childNodes[0].setAttribute( 'class', config.widgetDivSpanAllClass );
+					} else {
+						metawidget.util.appendToAttribute( outerDiv.childNodes[0], 'class', config.widgetDivOffsetClass );
+					}
+				}
+			};
+		}
+
+		return layout;
+	};
+
+	/**
+	 * @class LayoutDecorator to decorate widgets from different sections using
+	 *        Bootstrap tabs.
+	 */
+
+	metawidget.bootstrap.layout.TabLayoutDecorator = function( config ) {
+
+		if ( ! ( this instanceof metawidget.bootstrap.layout.TabLayoutDecorator ) ) {
+			throw new Error( 'Constructor called as a function' );
+		}
+
+		metawidget.layout.createNestedSectionLayoutDecorator( config, this, 'bootstrapTabLayoutDecorator' );
+	};
+
+	metawidget.bootstrap.layout.TabLayoutDecorator.prototype.createSectionWidget = function( previousSectionWidget, section, attributes, container, mw ) {
+
+		var tabs = previousSectionWidget;
+
+		// Whole new tabbed pane?
+
+		var ul, content;
+
+		if ( tabs === undefined ) {
+			tabs = metawidget.util.createElement( mw, 'div' );
+			tabs.setAttribute( 'id', metawidget.util.getId( "property", attributes, mw ) + '-tabs' );
+			tabs.setAttribute( 'class', 'tabs' );
+			ul = metawidget.util.createElement( mw, 'ul' );
+			ul.setAttribute( 'class', 'nav nav-tabs' );
+			tabs.appendChild( ul );
+			content = metawidget.util.createElement( mw, 'div' );
+			content.setAttribute( 'class', 'tab-content' );
+			tabs.appendChild( content );
+			this.getDelegate().layoutWidget( tabs, "property", {
+				wide: "true"
+			}, container, mw );
+
+			mw.bootstrapTabLayoutDecorator = mw.bootstrapTabLayoutDecorator || [];
+			mw.bootstrapTabLayoutDecorator.push( tabs );
+		} else {
+			tabs = previousSectionWidget.parentNode.parentNode;
+		}
+
+		// New Tab
+
+		ul = tabs.childNodes[0];
+		var tabId = tabs.getAttribute( 'id' ) + ( ul.childNodes.length + 1 );
+		var li = metawidget.util.createElement( mw, 'li' );
+		if ( ul.childNodes.length === 0 ) {
+			li.setAttribute( 'class', 'active' );
+		}
+		var a = metawidget.util.createElement( mw, 'a' );
+		a.setAttribute( 'data-toggle', 'tab' );
+		a.setAttribute( 'href', '#' + tabId );
+
+		// If Bootstrap is used with AngularJS, target=_self stops Angular from
+		// rewriting this link:
+		// https://groups.google.com/forum/#!topic/angular/yKv8jXYBsBI
+
+		a.setAttribute( 'target', '_self' );
+		li.appendChild( a );
+		ul.appendChild( li );
+
+		content = tabs.childNodes[1];
+		var tab = metawidget.util.createElement( mw, 'div' );
+		if ( content.childNodes.length === 0 ) {
+			tab.setAttribute( 'class', 'tab-pane active' );
+		} else {
+			tab.setAttribute( 'class', 'tab-pane' );
+		}
+		tab.setAttribute( 'id', tabId );
+		content.appendChild( tab );
+
+		// Tab name
+
+		a.innerHTML = section;
+
+		return tab;
+	};
+
+} )();
diff --git a/src/main/webapp/js/3rdparty/metawidget/bootstrap/metawidget-bootstrap.min.js b/src/main/webapp/js/3rdparty/metawidget/bootstrap/metawidget-bootstrap.min.js
new file mode 100644
index 0000000000000000000000000000000000000000..f4249354c485ca540fbe7fc90cd6710aa74d7972
--- /dev/null
+++ b/src/main/webapp/js/3rdparty/metawidget/bootstrap/metawidget-bootstrap.min.js
@@ -0,0 +1,16 @@
+// Metawidget 4.2 minified
+//
+// This file is dual licensed under both the LGPL
+// (http://www.gnu.org/licenses/lgpl-2.1.html) and the EPL
+// (http://www.eclipse.org/org/documents/epl-v10.php). As a
+// recipient of Metawidget, you may choose to receive it under either
+// the LGPL or the EPL.
+//
+// Commercial licenses are also available. See http://metawidget.org
+// for details.
+//
+// Author: Richard Kennard (http://kennardconsulting.com)
+
+var metawidget=metawidget||{};(function(){metawidget.bootstrap=metawidget.bootstrap||{};metawidget.bootstrap.widgetprocessor=metawidget.bootstrap.widgetprocessor||{};metawidget.bootstrap.widgetprocessor.BootstrapWidgetProcessor=function(a){if(!(this instanceof metawidget.bootstrap.widgetprocessor.BootstrapWidgetProcessor)){throw new Error("Constructor called as a function")}var b=a!==undefined?a.version:3;this.processWidget=function(i,n,g,m){var e=i.tagName;if(e==="TABLE"){metawidget.util.appendToAttribute(i,"class","table table-striped table-bordered table-hover")}else{if(e==="SELECT"||e==="TEXTAREA"){metawidget.util.appendToAttribute(i,"class","form-control")}else{if(e==="OUTPUT"){metawidget.util.appendToAttribute(i,"style","padding:6px 12px",";")}else{if(e==="INPUT"){var j=i.getAttribute("type");switch(j){case"submit":metawidget.util.appendToAttribute(i,"class","btn btn-primary");break;case"button":metawidget.util.appendToAttribute(i,"class","btn btn-default");break;default:if(j!=="checkbox"){metawidget.util.appendToAttribute(i,"class","form-control")}if(g.inputPrepend!==undefined||g.inputAppend!==undefined){var c=metawidget.util.createElement(m,"div");var l;if(g.inputPrepend!==undefined){c.setAttribute("class","input-prepend input-group");l=metawidget.util.createElement(m,"span");l.setAttribute("class","add-on input-group-addon");l.innerHTML=g.inputPrepend;c.appendChild(l)}c.appendChild(i);if(g.inputAppend!==undefined){if(g.inputPrepend!==undefined){c.setAttribute("class","input-prepend input-append input-group")}else{c.setAttribute("class","input-append input-group")}l=metawidget.util.createElement(m,"span");l.setAttribute("class","add-on input-group-addon");l.innerHTML=g.inputAppend;c.appendChild(l)}return c}}}else{if(b===3&&e==="DIV"&&g["enum"]!==undefined&&(g.type==="array"||g.componentType!==undefined)){for(var h=0,d=i.childNodes.length;h<d;h++){var k=i.childNodes[h];var f=metawidget.util.createElement(m,"div");f.setAttribute("class",k.getAttribute("class"));
+k.removeAttribute("class");i.replaceChild(f,k);f.appendChild(k)}}}}}}return i}};metawidget.bootstrap.layout=metawidget.bootstrap.layout||{};metawidget.bootstrap.layout.BootstrapDivLayout=function(b){if(!(this instanceof metawidget.bootstrap.layout.BootstrapDivLayout)){throw new Error("Constructor called as a function")}if(b===undefined){b={}}if(b.version===2){if(b.divStyleClasses===undefined){b.divStyleClasses=["control-group",undefined,"controls"]}if(b.labelStyleClass===undefined){b.labelStyleClass="control-label"}}else{if(b.divStyleClasses===undefined){b.divStyleClasses=["form-group","col-sm-2 control-label","col-sm-10"]}if(b.widgetDivSpanAllClass===undefined){b.widgetDivSpanAllClass="col-sm-12"}if(b.widgetDivOffsetClass===undefined){b.widgetDivOffsetClass="col-sm-offset-2"}if(b.suppressLabelSuffixOnCheckboxes===undefined){b.suppressLabelSuffixOnCheckboxes=true}if(b.wrapInsideLabels===undefined){b.wrapInsideLabels=["checkbox","radio"]}if(b.wrapWithExtraDiv===undefined){b.wrapWithExtraDiv={checkbox:"checkbox",radio:"radio"}}}var c=new metawidget.layout.DivLayout(b);if(b.version!==2){var a=c.layoutWidget;c.layoutWidget=function(h,d,f,e,i){a.call(this,h,d,f,e,i);var g=e.childNodes[e.childNodes.length-1];if(g!==undefined&&g.childNodes.length===1){if(f.title===null){g.childNodes[0].setAttribute("class",b.widgetDivSpanAllClass)}else{metawidget.util.appendToAttribute(g.childNodes[0],"class",b.widgetDivOffsetClass)}}}}return c};metawidget.bootstrap.layout.TabLayoutDecorator=function(a){if(!(this instanceof metawidget.bootstrap.layout.TabLayoutDecorator)){throw new Error("Constructor called as a function")}metawidget.layout.createNestedSectionLayoutDecorator(a,this,"bootstrapTabLayoutDecorator")};metawidget.bootstrap.layout.TabLayoutDecorator.prototype.createSectionWidget=function(j,k,e,b,m){var h=j;var g,f;if(h===undefined){h=metawidget.util.createElement(m,"div");h.setAttribute("id",metawidget.util.getId("property",e,m)+"-tabs");h.setAttribute("class","tabs");g=metawidget.util.createElement(m,"ul");
+g.setAttribute("class","nav nav-tabs");h.appendChild(g);f=metawidget.util.createElement(m,"div");f.setAttribute("class","tab-content");h.appendChild(f);this.getDelegate().layoutWidget(h,"property",{wide:"true"},b,m);m.bootstrapTabLayoutDecorator=m.bootstrapTabLayoutDecorator||[];m.bootstrapTabLayoutDecorator.push(h)}else{h=j.parentNode.parentNode}g=h.childNodes[0];var c=h.getAttribute("id")+(g.childNodes.length+1);var l=metawidget.util.createElement(m,"li");if(g.childNodes.length===0){l.setAttribute("class","active")}var i=metawidget.util.createElement(m,"a");i.setAttribute("data-toggle","tab");i.setAttribute("href","#"+c);i.setAttribute("target","_self");l.appendChild(i);g.appendChild(l);f=h.childNodes[1];var d=metawidget.util.createElement(m,"div");if(f.childNodes.length===0){d.setAttribute("class","tab-pane active")}else{d.setAttribute("class","tab-pane")}d.setAttribute("id",c);f.appendChild(d);i.innerHTML=k;return d}})();
diff --git a/src/main/webapp/js/3rdparty/metawidget/core/metawidget-core.min.js b/src/main/webapp/js/3rdparty/metawidget/core/metawidget-core.min.js
new file mode 100644
index 0000000000000000000000000000000000000000..b04ccb91d2412e87692d11314af63767dddf3ca0
--- /dev/null
+++ b/src/main/webapp/js/3rdparty/metawidget/core/metawidget-core.min.js
@@ -0,0 +1,39 @@
+// Metawidget 4.2 minified
+//
+// This file is dual licensed under both the LGPL
+// (http://www.gnu.org/licenses/lgpl-2.1.html) and the EPL
+// (http://www.eclipse.org/org/documents/epl-v10.php). As a
+// recipient of Metawidget, you may choose to receive it under either
+// the LGPL or the EPL.
+//
+// Commercial licenses are also available. See http://metawidget.org
+// for details.
+//
+// Author: Richard Kennard (http://kennardconsulting.com)
+
+var metawidget=metawidget||{};(function(){metawidget.inspectionresultprocessor=metawidget.inspectionresultprocessor||{}})();
+var metawidget=metawidget||{};(function(){metawidget.inspector=metawidget.inspector||{};metawidget.inspector.CompositeInspector=function(a){if(!(this instanceof metawidget.inspector.CompositeInspector)){throw new Error("Constructor called as a function")}var b;if(a.inspectors!==undefined){b=a.inspectors.slice(0)}else{b=a.slice(0)}this.inspect=function(j,d,h){var e={};for(var c=0,g=b.length;c<g;c++){var f;var i=b[c];if(i.inspect!==undefined){f=i.inspect(j,d,h)}else{f=i(j,d,h)}metawidget.util.combineInspectionResults(e,f)}return e}};metawidget.inspector.PropertyTypeInspector=function(){if(!(this instanceof metawidget.inspector.PropertyTypeInspector)){throw new Error("Constructor called as a function")}};metawidget.inspector.PropertyTypeInspector.prototype.inspect=function(f,b,e){function a(g){if(g instanceof Array){return"array"}else{if(g instanceof Date){return"date"}else{var h=typeof(g);if(h!=="object"){return h}}}}f=metawidget.util.traversePath(f,e);var d={};if(e!==undefined&&e.length>0){d.name=e[e.length-1]}else{if(f===undefined){return}}if(f!==undefined){d.type=a(f);if(d.type===undefined){d.properties={};for(var c in f){d.properties[c]={type:a(f[c])}}}}return d};metawidget.inspector.JsonSchemaInspector=function(a){if(!(this instanceof metawidget.inspector.JsonSchemaInspector)){throw new Error("Constructor called as a function")}var b;if(a.schema!==undefined){b=a.schema}else{b=a}this.inspect=function(j,e,i){function d(o,n){if(o===undefined){return undefined}if(n!==undefined){if(!(n instanceof Array)){throw new Error("Expected array of names")}for(var k=0,m=n.length;k<m;k++){var l=n[k];if(!isNaN(l)){o=o.items;if(o===undefined){return undefined}continue}o=o.properties;if(o===undefined){return undefined}o=o[l];if(o===undefined){return undefined}}}return o}var h=d(b,i);if(h===undefined){return undefined}var g={};if(i!==undefined){g.name=i[i.length-1]}metawidget.util.combineInspectionResults(g,h);if(g.required!==undefined){for(var c=0,f=g.required.length;c<f;c++){g.properties[g.required[c]].required=true
+}}return g}}})();
+var metawidget=metawidget||{};(function(){metawidget.layout=metawidget.layout||{};metawidget.layout.SimpleLayout=function(){if(!(this instanceof metawidget.layout.SimpleLayout)){throw new Error("Constructor called as a function")}};metawidget.layout.SimpleLayout.prototype.layoutWidget=function(d,a,c,b){if(d.tagName==="STUB"&&!metawidget.util.hasChildElements(d)){return}b.appendChild(d)};metawidget.layout.DefinitionListLayout=function(b){if(!(this instanceof metawidget.layout.DefinitionListLayout)){throw new Error("Constructor called as a function")}var c=b!==undefined?b.labelStyleClass:undefined;var a=b!==undefined&&b.labelSuffix!==undefined?b.labelSuffix:":";this.startContainerLayout=function(d,g){var e=metawidget.util.createElement(g,"dl");if(g.path!==undefined){var f=metawidget.util.getId("property",{},g);if(f!==undefined){e.setAttribute("id","dl-"+f)}}d.appendChild(e)};this.layoutWidget=function(i,e,g,f,j){if(i.tagName==="STUB"&&!metawidget.util.hasChildElements(i)){return}var h=f.childNodes[f.childNodes.length-1];this.layoutLabel(h,i,e,g,j);var d=metawidget.util.createElement(j,"dd");d.appendChild(i);h.appendChild(d)};this.layoutLabel=function(h,j,d,e,k){if(d==="entity"||d==="action"){return}if(e.name===undefined&&e.title===undefined){return}var g=metawidget.util.getLabelString(e,k);if(g===""||g===null){return}var i=metawidget.util.createElement(k,"dt");var f=metawidget.util.createElement(k,"label");if(j.getAttribute("id")!==null){f.setAttribute("for",j.getAttribute("id"))}if(c!==undefined){f.setAttribute("class",c)}f.innerHTML=g+a;i.appendChild(f);h.appendChild(i)}};metawidget.layout.DivLayout=function(b){if(!(this instanceof metawidget.layout.DivLayout)){throw new Error("Constructor called as a function")}var c=b!==undefined?b.divStyleClasses:undefined;var h=b!==undefined?b.labelStyleClass:undefined;var g=b!==undefined&&b.labelSuffix!==undefined?b.labelSuffix:":";var f=b!==undefined&&b.suppressDivAroundLabel!==undefined?b.suppressDivAroundLabel:false;var e=b!==undefined&&b.suppressDivAroundWidget!==undefined?b.suppressDivAroundWidget:false;
+var d=b!==undefined&&b.appendRequiredClassOnLabelDiv!==undefined?b.appendRequiredClassOnLabelDiv:undefined;var j=b!==undefined&&b.appendRequiredClassOnWidgetDiv!==undefined?b.appendRequiredClassOnWidgetDiv:undefined;var k=b!==undefined&&b.suppressLabelSuffixOnCheckboxes!==undefined?b.suppressLabelSuffixOnCheckboxes:false;var i=b!==undefined&&b.wrapInsideLabels!==undefined?b.wrapInsideLabels:undefined;var a=b!==undefined&&b.wrapWithExtraDiv!==undefined?b.wrapWithExtraDiv:undefined;this.layoutWidget=function(s,x,q,l,w){if(s.tagName==="STUB"&&!metawidget.util.hasChildElements(s)){return}if(s.tagName==="INPUT"&&(s.getAttribute("type")==="button"||s.getAttribute("type")==="submit")){if(l.childNodes.length>0){var o=l.childNodes[l.childNodes.length-1];if(o.childNodes.length===1){var u=o.childNodes[0];if(u.childNodes.length>0){var m=u.childNodes[u.childNodes.length-1];if(m.tagName==="INPUT"&&(m.getAttribute("type")==="button"||m.getAttribute("type")==="submit")){u.appendChild(s);return}}}}}var v=metawidget.util.createElement(w,"div");if(c!==undefined&&c[0]!==undefined){v.setAttribute("class",c[0])}var n=this.layoutLabel(v,s,x,q,w);var p=s;if(s.tagName==="INPUT"&&metawidget.util.niceIndexOf(i,s.getAttribute("type"))!==-1){n.insertBefore(s,n.firstChild);p=n}else{p=s}if(s.tagName==="INPUT"&&a!==undefined&&a[s.getAttribute("type")]!==undefined){var t=metawidget.util.createElement(w,"div");t.setAttribute("class",a[s.getAttribute("type")]);t.appendChild(p);p=t}if(e!==true){var r=metawidget.util.createElement(w,"div");if(c!==undefined&&c[2]!==undefined){r.setAttribute("class",c[2])}if(metawidget.util.isTrueOrTrueString(q.required)&&j!==undefined){metawidget.util.appendToAttribute(r,"class",j)}r.appendChild(p);p=r}v.appendChild(p);l.appendChild(v)};this.layoutLabel=function(p,q,l,m,s){if(l==="entity"||l==="action"){return}if(m.name===undefined&&m.title===undefined){return}var o=this.getLabelString(q,m,s);if(o===""||o===null){return}var n=metawidget.util.createElement(s,"label");if(q.getAttribute("id")!==null){n.setAttribute("for",q.getAttribute("id"));
+n.setAttribute("id",q.getAttribute("id")+"-label")}if(h!==undefined){n.setAttribute("class",h)}n.innerHTML=o;if(f===true){p.appendChild(n)}else{var r=metawidget.util.createElement(s,"div");if(c!==undefined&&c[1]!==undefined){r.setAttribute("class",c[1])}if(metawidget.util.isTrueOrTrueString(m.required)&&d!==undefined){metawidget.util.appendToAttribute(r,"class",d)}r.appendChild(n);p.appendChild(r)}return n};this.getLabelString=function(n,l,o){var m=metawidget.util.getLabelString(l,o);if(m===""||m===null){return m}if(k===true&&n.tagName==="INPUT"){if(n.getAttribute("type")==="checkbox"||n.getAttribute("type")==="radio"){return m}}return m+g}};metawidget.layout.TableLayout=function(c){if(!(this instanceof metawidget.layout.TableLayout)){throw new Error("Constructor called as a function")}var e=c!==undefined?c.tableStyleClass:undefined;var f=c!==undefined?c.columnStyleClasses:undefined;var a=c!==undefined?c.headerStyleClass:undefined;var b=c!==undefined?c.footerStyleClass:undefined;var d=c!==undefined&&c.numberOfColumns?c.numberOfColumns:1;this.startContainerLayout=function(g,o){var p=metawidget.util.createElement(o,"table");if(o.path!==undefined){var h=metawidget.util.getId("property",{},o);if(h!==undefined){p.setAttribute("id","table-"+h)}}if(e!==undefined){p.setAttribute("class",e)}g._currentColumn=0;g.appendChild(p);if(o.overriddenNodes!==undefined){for(var m=0,i=o.overriddenNodes.length;m<i;m++){var j=o.overriddenNodes[m];if(j.tagName!=="FACET"){continue}var n;if(j.getAttribute("name")==="header"){n=metawidget.util.createElement(o,"thead")}else{if(j.getAttribute("name")==="footer"){n=metawidget.util.createElement(o,"tfoot")}else{continue}}p.appendChild(n);var l=metawidget.util.createElement(o,"tr");n.appendChild(l);var k=metawidget.util.createElement(o,"td");k.setAttribute("colspan",d*3);if(j.getAttribute("name")==="header"){if(a!==undefined){k.setAttribute("class",a)}}else{if(b!==undefined){k.setAttribute("class",b)}}l.appendChild(k);while(j.childNodes.length>0){k.appendChild(j.removeChild(j.childNodes[0]))
+}}}p.appendChild(metawidget.util.createElement(o,"tbody"))};this.layoutWidget=function(l,q,j,h,o){if(l.tagName==="STUB"&&!metawidget.util.hasChildElements(l)){return}var m=metawidget.util.isSpanAllColumns(j);if(m===true&&h._currentColumn>0){h._currentColumn=0}var p=h.childNodes[h.childNodes.length-1];var g=undefined;if(j.name!==undefined){if(metawidget.util.hasAttribute(p,"id")){g=p.getAttribute("id")}if(g!==undefined){if(q!=="entity"){if(g.charAt(g.length-1)!=="-"){g+=metawidget.util.capitalize(j.name)}else{g+=j.name}}}else{g="table-"+j.name}}var k=p.childNodes[p.childNodes.length-1];var n;if(h._currentColumn===0){n=metawidget.util.createElement(o,"tr");if(g!==undefined){n.setAttribute("id",g+"-row")}k.appendChild(n)}else{n=k.childNodes[k.childNodes.length-1]}this.layoutLabel(n,g,l,q,j,o);var i=metawidget.util.createElement(o,"td");if(g!==undefined){i.setAttribute("id",g+"-cell")}if(f!==undefined&&f[1]!==undefined){i.setAttribute("class",f[1])}if(m===true){i.setAttribute("colspan",((d*3)-1)-n.childNodes.length)}else{if(n.childNodes.length<1){i.setAttribute("colspan",2-n.childNodes.length)}}i.appendChild(l);n.appendChild(i);this.layoutRequired(n,j,o);if(m===true){h._currentColumn=d-1}h._currentColumn=(h._currentColumn+1)%d};this.layoutLabel=function(l,g,k,o,j,n){if(o==="entity"){return}if(j.name===undefined&&j.title===undefined){return}var i=this.getLabelString(j,n);if(i===null){return}var h=metawidget.util.createElement(n,"th");if(g!==undefined){h.setAttribute("id",g+"-label-cell")}if(f!==undefined&&f[0]!==undefined){h.setAttribute("class",f[0])}if(o!=="action"&&i!==""){var m=metawidget.util.createElement(n,"label");if(metawidget.util.hasAttribute(k,"id")){m.setAttribute("for",k.getAttribute("id"))}if(g!==undefined){m.setAttribute("id",g+"-label")}m.innerHTML=i;h.appendChild(m)}l.appendChild(h)};this.layoutRequired=function(h,g,j){var i=metawidget.util.createElement(j,"td");if(f!==undefined&&f[2]!==undefined){i.setAttribute("class",f[2])}if(!metawidget.util.isTrueOrTrueString(g.readOnly)&&metawidget.util.isTrueOrTrueString(g.required)){i.innerHTML="*"
+}h.appendChild(i)};this.getLabelString=function(g,i){var h=metawidget.util.getLabelString(g,i);if(h===""||h===null){return h}return h+":"}};metawidget.layout._createSectionLayoutDecorator=function(b,a,d){var c;if(b.delegate!==undefined){c=b.delegate}else{c=b}a.getDelegate=function(){return c};a.onStartBuild=function(e){if(a.getDelegate().onStartBuild!==undefined){a.getDelegate().onStartBuild(e)}};a.startContainerLayout=function(e,f){e[d]={};if(a.getDelegate().startContainerLayout!==undefined){a.getDelegate().startContainerLayout(e,f)}};a.endContainerLayout=function(e,f){if(a.getDelegate().endContainerLayout!==undefined){a.getDelegate().endContainerLayout(e,f)}e[d]={}};a.onEndBuild=function(e){if(a.getDelegate().onEndBuild!==undefined){a.getDelegate().onEndBuild(e)}}};metawidget.layout.createFlatSectionLayoutDecorator=function(b,a,c){if(this instanceof metawidget.layout.createFlatSectionLayoutDecorator){throw new Error("Function called as a Constructor")}metawidget.layout._createSectionLayoutDecorator(b,a,c);a.layoutWidget=function(g,l,f,e,j){var i;if(a.getDelegate().nestedSectionLayoutDecorator===true){i=metawidget.util.stripSection(f);if(i===undefined||i===e[c].currentSection){return a.getDelegate().layoutWidget(g,l,f,e,j)}if(e[c].currentSection!==undefined){a.getDelegate().endContainerLayout(e,j)}e[c].currentSection=i;if(i!==""){a.addSectionWidget(i,0,f,e,j)}}else{if(f.section===undefined||f.section===e[c].currentSection){return a.getDelegate().layoutWidget(g,l,f,e,j)}var k=f.section;if(!(k instanceof Array)){k=[k]}var h;if(e[c].currentSection!==undefined){h=e[c].currentSection}else{h=[]}for(var d=0;d<k.length;d++){i=k[d];if(i===""){continue}if(d<h.length&&i===h[d]){continue}a.addSectionWidget(i,d,f,e,j)}e[c].currentSection=k}a.getDelegate().layoutWidget(g,l,f,e,j)}};metawidget.layout.createNestedSectionLayoutDecorator=function(b,a,d){if(this instanceof metawidget.layout.createNestedSectionLayoutDecorator){throw new Error("Function called as a Constructor")}metawidget.layout._createSectionLayoutDecorator(b,a,d);
+a.nestedSectionLayoutDecorator=true;a.layoutWidget=function(i,e,g,f,k){var j=metawidget.util.stripSection(g);if(j===undefined||j===f[d].currentSection){if(f[d].currentSectionWidget){return a.getDelegate().layoutWidget(i,e,g,f[d].currentSectionWidget,k)}return a.getDelegate().layoutWidget(i,e,g,f,k)}if(f[d].currentSectionWidget!==undefined){a.endContainerLayout(f[d].currentSectionWidget,k)}f[d].currentSection=j;var h=f[d].currentSectionWidget;delete f[d].currentSectionWidget;if(j===""){a.getDelegate().layoutWidget(i,e,g,f,k);return}f[d].currentSectionWidget=a.createSectionWidget(h,j,g,f,k);a.startContainerLayout(f[d].currentSectionWidget,k);a.getDelegate().layoutWidget(i,e,g,f[d].currentSectionWidget,k)};var c=a.endContainerLayout;a.endContainerLayout=function(e,f){if(e[d].currentSectionWidget!==undefined){a.endContainerLayout(e[d].currentSectionWidget,f)}c.call(this,e,f)}};metawidget.layout.HeadingTagLayoutDecorator=function(a){if(!(this instanceof metawidget.layout.HeadingTagLayoutDecorator)){throw new Error("Constructor called as a function")}var b=a!==undefined&&a.level!==undefined?a.level:1;metawidget.layout.createFlatSectionLayoutDecorator(a,this,"headingTagLayoutDecorator");this.addSectionWidget=function(f,h,d,c,g){var e=metawidget.util.createElement(g,"h"+(h+b));e.innerHTML=f;this.getDelegate().layoutWidget(e,"property",{wide:"true"},c,g)}};metawidget.layout.DivLayoutDecorator=function(a){if(!(this instanceof metawidget.layout.DivLayoutDecorator)){throw new Error("Constructor called as a function")}var b=a!==undefined?a.styleClass:undefined;metawidget.layout.createNestedSectionLayoutDecorator(a,this,"divLayoutDecorator");this.createSectionWidget=function(e,f,d,c,h){var g=metawidget.util.createElement(h,"div");g.setAttribute("title",f);if(b!==undefined){g.setAttribute("class",b)}this.getDelegate().layoutWidget(g,"property",{wide:"true"},c,h);return g}}})();
+var metawidget=metawidget||{};(function(){metawidget.util=metawidget.util||{};metawidget.util.getLabelString=function(b,d){if(b.title!==undefined){if(b.title===null){return null}return metawidget.util.getLocalizedString(b.title,d)}var c=b.name;if(d.l10n!==undefined&&d.l10n[c]!==undefined){return d.l10n[c]}return metawidget.util.uncamelCase(c)};metawidget.util.uncamelCase=function(e){function b(l){var k=l.charCodeAt(0);return(k>=48&&k<=57)}function g(l){var k=l.charCodeAt(0);return(k>=65&&k<=90)||(k>=97&&k<=122)}var h="";var i=true;var f=" ";for(var d=0;d<e.length;d++){var j=e.charAt(d);if(i===true){h+=j.toUpperCase();i=false}else{if(a(j)&&(!a(f)||(d<e.length-1&&e[d+1]!==" "&&!a(e[d+1])))){if(f!==" "){h+=" "}h+=j}else{if(b(j)&&g(f)&&f!==" "){h+=" "+j}else{h+=j}}}f=j}return h};metawidget.util.getLocalizedString=function(c,d){var b=metawidget.util.camelCase(c);if(d.l10n!==undefined&&d.l10n[b]!==undefined){return d.l10n[b]}return c};metawidget.util.decapitalize=function(b){if(b.length===0){return b}var c=b.charAt(0);if(!a(c)){return b}if(b.length>1){if(a(b.charAt(1))){return b}}return b.charAt(0).toLowerCase()+b.slice(1)};metawidget.util.capitalize=function(b){if(b.length===0){return b}if(b.length>1){if(a(b.charAt(1))){return b}}return b.charAt(0).toUpperCase()+b.slice(1)};metawidget.util.isTrueOrTrueString=function(b){return(b==="true"||b===true)};metawidget.util.camelCase=function(e){if(!(e instanceof Array)){e=e.split(" ")}var d="";var c=e.length;if(c>0){d+=metawidget.util.decapitalize(e[0])}for(var b=1;b<c;b++){d+=metawidget.util.capitalize(e[b])}return d};metawidget.util.fillString=function(b,d){var c="";for(;;){if(d&1){c+=b}d>>=1;if(d){b+=b}else{break}}return c};metawidget.util.lookupEnumTitle=function(d,e,b){var c=e.indexOf(d);if(c===-1||c>=b.length){c=e.indexOf(""+d);if(c===-1||c>=b.length){return d}}return b[c]};metawidget.util.getId=function(b,d,f){if(f.path!==undefined){var c=f.path.split(".");if(c[0]==="object"){c=c.slice(1)}if(d.name&&b!=="entity"){c.push(d.name)
+}else{if(c.length===0){return undefined}}var e=metawidget.util.camelCase(c);e=e.replace(/[\[\]]/g,"");return e}if(d!==undefined){return d.name}};metawidget.util.hasChildElements=function(d){var e=d.childNodes;for(var b=0,c=e.length;b<c;b++){if(e[b].nodeType===1){return true}}return false};metawidget.util.isSpanAllColumns=function(b){if(b===undefined){return false}if(metawidget.util.isTrueOrTrueString(b.large)){return true}return metawidget.util.isTrueOrTrueString(b.wide)};metawidget.util.splitPath=function(g){var c={};if(g!==undefined){var f=g.match(/([^\.\[\]]*)/g);c.type=f[0];for(var b=1,e=f.length;b<e;b++){if(f[b]===""){continue}if(c.names===undefined){c.names=[]}var d=f[b].match(/^(?:\s*(?:\'|\"))([^\']*)(?:(?:\'|\")\s*)$/);if(d!==null&&d[1]!==undefined){f[b]=d[1]}c.names.push(f[b])}}return c};metawidget.util.appendPath=function(b,c){if(c.path!==undefined){return metawidget.util.appendPathWithName(c.path,b)}if(c.toInspect!==undefined){return metawidget.util.appendPathWithName(typeof(c.toInspect),b)}return metawidget.util.appendPathWithName("object",b)};metawidget.util.appendPathWithName=function(d,b){var c=b.name;if(metawidget.util.isTrueOrTrueString(b.nameIncludesSeparator)){return d+c}if(c.indexOf(".")!==-1||c.indexOf("'")!==-1||c.indexOf('"')!==-1||c.indexOf(" ")!==-1){return d+"['"+c.replace("'","\\'")+"']"}return d+"."+c};metawidget.util.traversePath=function(e,d){if(e===undefined){return undefined}if(d!==undefined){if(!(d instanceof Array)){throw new Error("Expected array of names")}for(var b=0,c=d.length;b<c;b++){e=e[d[b]];if(e===undefined){return undefined}}}return e};metawidget.util.getSortedInspectionResultProperties=function(e){var d=[];if(e!==undefined){for(var b in e.properties){var c=e.properties[b];d.push(c);c.name=b;c._syntheticOrder=d.length}d.sort(function(g,f){if(g.propertyOrder===undefined){if(f.propertyOrder===undefined){return(g._syntheticOrder-f._syntheticOrder)}return 1}if(f.propertyOrder===undefined){return -1}var h=(g.propertyOrder-f.propertyOrder);
+if(h===0){return(g._syntheticOrder-f._syntheticOrder)}return h})}return d};metawidget.util.combineInspectionResults=function(d,c){if(c===undefined){return}e(c,d);if(c.properties===undefined){return}d.properties=d.properties||{};for(var b in c.properties){d.properties[b]=d.properties[b]||{};e(c.properties[b],d.properties[b])}function e(i,h){for(var g in i){var f=i[g];if(f instanceof Array){h[g]=f.slice(0);continue}if(f instanceof Object){continue}h[g]=i[g]}}};metawidget.util.stripSection=function(b){var c=b.section;if(c===undefined){return undefined}if(!(c instanceof Array)){delete b.section;return c}switch(c.length){case 0:delete b.section;return"";case 1:delete b.section;return c[0];default:b.section=c.slice(1);return c[0]}};metawidget.util.appendToAttribute=function(e,d,c,f){var b=e.getAttribute(d);if(f===undefined){f=" "}if(b===null){e.setAttribute(d,c);return}if(b.toString!==undefined){b=b.toString()}if(b!==c&&b.indexOf(c+f)===-1&&b.indexOf(f+c)===-1){e.setAttribute(d,b+f+c)}};metawidget.util.createElement=function(c,b){if(c.ownerDocument!==undefined){return c.ownerDocument.createElement(b.toUpperCase())}return c.getElement().ownerDocument.createElement(b.toUpperCase())};metawidget.util.createTextNode=function(c,b){if(c.ownerDocument!==undefined){return c.ownerDocument.createTextNode(b)}return c.getElement().ownerDocument.createTextNode(b)};metawidget.util.createEvent=function(d,b){var c;if(d.ownerDocument!==undefined){c=d.ownerDocument.createEvent("Event")}else{c=d.getElement().ownerDocument.createEvent("Event")}c.initEvent(b,true,true);return c};metawidget.util.niceIndexOf=function(c,b){if(c===undefined||b===undefined){return -1}return c.indexOf(b)};metawidget.util.hasAttribute=function(b,c){if(b.hasAttribute!==undefined){return b.hasAttribute(c)}return(b.getAttribute(c)!==null)};function a(d){var b=d.charCodeAt(0);return(b>=65&&b<=90)}})();
+var metawidget=metawidget||{};(function(a){function c(d){var e=this.getAttribute(d);if(e===null){return}var f=metawidget.util.splitPath(e);if(f===undefined){return}var g=a[f.type];return metawidget.util.traversePath(g,f.names)}if(a.document!==undefined&&a.document.registerElement!==undefined){var b=Object.create(HTMLElement.prototype);b.attachedCallback=function(){var d=this.createShadowRoot();this._pipeline=new metawidget.Pipeline(d);this._pipeline.inspector=new metawidget.inspector.PropertyTypeInspector();this._pipeline.widgetBuilder=new metawidget.widgetbuilder.CompositeWidgetBuilder([new metawidget.widgetbuilder.OverriddenWidgetBuilder(),new metawidget.widgetbuilder.ReadOnlyWidgetBuilder(),new metawidget.widgetbuilder.HtmlWidgetBuilder()]);this._pipeline.widgetProcessors=[new metawidget.widgetprocessor.IdProcessor(),new metawidget.widgetprocessor.RequiredAttributeProcessor(),new metawidget.widgetprocessor.PlaceholderAttributeProcessor(),new metawidget.widgetprocessor.DisabledAttributeProcessor(),new metawidget.widgetprocessor.SimpleBindingProcessor()];this._pipeline.layout=new metawidget.layout.HeadingTagLayoutDecorator(new metawidget.layout.TableLayout());this._pipeline.configure([c.call(this,"config"),this.config]);this.buildWidgets()};b.attributeChangedCallback=function(f,d,e){if(this._pipeline===undefined){return}switch(f){case"path":this.buildWidgets();break;case"readonly":this.buildWidgets();break;case"config":this._pipeline.configure(c.call(this,"config"));break}};b.clearWidgets=function(){while(this.shadowRoot.childNodes.length>0){this.shadowRoot.removeChild(this.shadowRoot.childNodes[0])}};b.buildWidgets=function(g){this.overriddenNodes=[];for(var d=0,f=this.childNodes.length;d<f;d++){if(this.childNodes[d].nodeType===1){this.overriddenNodes.push(this.childNodes[d].cloneNode(true))}}if(this.getAttribute("path")!==null){this.path=this.getAttribute("path");this.readOnly=metawidget.util.isTrueOrTrueString(this.getAttribute("readonly"));if(g===undefined){if(arguments.length>0){throw new Error("Calling buildWidgets( undefined ) may cause infinite loop. Check your argument, or pass no arguments instead")
+}var e=metawidget.util.splitPath(this.path);this.toInspect=a[e.type];g=this._pipeline.inspect(this.toInspect,e.type,e.names,this)}}this._pipeline.buildWidgets(g,this)};b.buildNestedMetawidget=function(d,e){var f=metawidget.util.createElement(this,"x-metawidget");f.getMetawidget=function(){return f};f.setAttribute("path",metawidget.util.appendPath(d,this));f.setAttribute("readonly",this.readOnly||metawidget.util.isTrueOrTrueString(d.readOnly));f.config=this._pipeline;return f};b.save=function(){return this.getWidgetProcessor(function(d){return d instanceof metawidget.widgetprocessor.SimpleBindingProcessor}).save(this)};b.inspect=function(f,d,e){return this._pipeline.inspect(f,d,e,this)};b.getWidgetProcessor=function(d){return this._pipeline.getWidgetProcessor(d)};b.setLayout=function(d){this._pipeline.layout=d};a.document.registerElement("x-metawidget",{prototype:b})}})(this);
+var metawidget=metawidget||{};(function(){metawidget.widgetbuilder=metawidget.widgetbuilder||{};metawidget.widgetbuilder.CompositeWidgetBuilder=function(a){if(!(this instanceof metawidget.widgetbuilder.CompositeWidgetBuilder)){throw new Error("Constructor called as a function")}var c;if(a.widgetBuilders!==undefined){c=a.widgetBuilders.slice(0)}else{c=a.slice(0)}function b(g,h){for(var d=0,f=c.length;d<f;d++){var e=c[d];if(e[g]!==undefined){e[g](h)}}}this.onStartBuild=function(d){b("onStartBuild",d)};this.buildWidget=function(d,f,j){for(var e=0,h=c.length;e<h;e++){var i;var g=c[e];if(g.buildWidget!==undefined){i=g.buildWidget(d,f,j)}else{i=g(d,f,j)}if(i!==undefined){return i}}};this.onEndBuild=function(d){b("onEndBuild",d)}};metawidget.widgetbuilder.OverriddenWidgetBuilder=function(){if(!(this instanceof metawidget.widgetbuilder.OverriddenWidgetBuilder)){throw new Error("Constructor called as a function")}};metawidget.widgetbuilder.OverriddenWidgetBuilder.prototype.buildWidget=function(b,d,g){if(g.overriddenNodes===undefined){return}var a=metawidget.util.getId(b,d,g);for(var c=0,e=g.overriddenNodes.length;c<e;c++){var f=g.overriddenNodes[c];if(f.nodeType===1&&f.getAttribute("id")===a){g.overriddenNodes.splice(c,1);return f}}};metawidget.widgetbuilder.ReadOnlyWidgetBuilder=function(){if(!(this instanceof metawidget.widgetbuilder.ReadOnlyWidgetBuilder)){throw new Error("Constructor called as a function")}};metawidget.widgetbuilder.ReadOnlyWidgetBuilder.prototype.buildWidget=function(a,b,c){if(!metawidget.util.isTrueOrTrueString(b.readOnly)){return}if(metawidget.util.isTrueOrTrueString(b.hidden)||b.type==="function"){return metawidget.util.createElement(c,"stub")}if(b["enum"]!==undefined||b.type==="string"||b.type==="boolean"||b.type==="number"||b.type==="integer"||b.type==="date"||b.type==="color"){return metawidget.util.createElement(c,"output")}if(metawidget.util.isTrueOrTrueString(b.dontExpand)){return metawidget.util.createElement(c,"output")}};metawidget.widgetbuilder.HtmlWidgetBuilder=function(a){if(!(this instanceof metawidget.widgetbuilder.HtmlWidgetBuilder)){throw new Error("Constructor called as a function")
+}var b=false;if(a!==undefined){b=a.alwaysUseNestedMetawidgetInTables}this.buildWidget=function(i,h,p){if(metawidget.util.isTrueOrTrueString(h.hidden)){return metawidget.util.createElement(p,"stub")}if(h.type==="boolean"&&h.componentType==="radio"&&h["enum"]===undefined){h["enum"]=[true,false];h.enumTitles=["Yes","No"]}if(h["enum"]!==undefined){var s,f,l;if(h.type==="array"||h.componentType!==undefined){var o=metawidget.util.createElement(p,"div");f=h["enum"].length;for(s=0;s<f;s++){var g=metawidget.util.createElement(p,"label");l=metawidget.util.createElement(p,"input");if(h.componentType!==undefined){g.setAttribute("class",h.componentType);l.setAttribute("type",h.componentType)}else{g.setAttribute("class","checkbox");l.setAttribute("type","checkbox")}l.value=h["enum"][s];g.appendChild(l);if(h.enumTitles!==undefined&&h.enumTitles[s]!==undefined){g.appendChild(metawidget.util.createTextNode(p,h.enumTitles[s]))}else{g.appendChild(metawidget.util.createTextNode(p,h["enum"][s]))}o.appendChild(g)}return o}var q=metawidget.util.createElement(p,"select");if(!metawidget.util.isTrueOrTrueString(h.required)){q.appendChild(metawidget.util.createElement(p,"option"))}f=h["enum"].length;for(s=0;s<f;s++){l=metawidget.util.createElement(p,"option");l.value=h["enum"][s];if(h.enumTitles!==undefined&&h.enumTitles[s]!==undefined){l.innerHTML=h.enumTitles[s]}else{l.innerHTML=h["enum"][s]}q.appendChild(l)}return q}if(h.type==="function"){var c=metawidget.util.createElement(p,"input");if(metawidget.util.isTrueOrTrueString(h.submit)){c.setAttribute("type","submit")}else{c.setAttribute("type","button")}c.setAttribute("value",metawidget.util.getLabelString(h,p));return c}if(h.type==="number"||h.type==="integer"){if(h.minimum!==undefined&&h.maximum!==undefined){var n=metawidget.util.createElement(p,"input");n.setAttribute("type","range");n.setAttribute("min",h.minimum);n.setAttribute("max",h.maximum);return n}var e=metawidget.util.createElement(p,"input");e.setAttribute("type","number");if(h.minimum!==undefined){e.setAttribute("min",h.minimum)
+}else{if(h.maximum!==undefined){e.setAttribute("max",h.maximum)}}return e}if(h.type==="boolean"){var m=metawidget.util.createElement(p,"input");m.setAttribute("type","checkbox");return m}if(h.type==="date"){var t=metawidget.util.createElement(p,"input");t.setAttribute("type","date");return t}if(h.type==="color"){var r=metawidget.util.createElement(p,"input");r.setAttribute("type","color");return r}if(h.type==="string"){if(metawidget.util.isTrueOrTrueString(h.masked)){var d=metawidget.util.createElement(p,"input");d.setAttribute("type","password");if(h.maxLength!==undefined){d.setAttribute("maxlength",h.maxLength)}return d}if(metawidget.util.isTrueOrTrueString(h.large)){return metawidget.util.createElement(p,"textarea")}var j=metawidget.util.createElement(p,"input");if(h.componentType!==undefined){j.setAttribute("type",h.componentType)}else{j.setAttribute("type","text")}if(h.maxLength!==undefined){j.setAttribute("maxlength",h.maxLength)}return j}if(h.type==="array"){return this.createTable(i,h,p)}if(metawidget.util.isTrueOrTrueString(h.dontExpand)){var k=metawidget.util.createElement(p,"input");k.setAttribute("type","text");return k}};this.createTable=function(q,e,k){var m=metawidget.util.createElement(k,"table");var p=metawidget.util.splitPath(k.path);var l=metawidget.util.traversePath(k.toInspect,p.names);if(p.names===undefined){p.names=[]}var i;if(q!=="entity"&&l!==undefined){i=l[e.name];p.names.push(e.name)}else{i=l}p.names.push("0");var d=k.inspect(k.toInspect,p.type,p.names);if(d!==undefined){var f=metawidget.util.createElement(k,"tbody");var o,n;if(d.properties===undefined){m.appendChild(f);if(i!==undefined){n=i.length;for(o=0;o<n;o++){this.addRow(f,i,o,[{type:d.type}],q,e,k)}}}else{var c=metawidget.util.getSortedInspectionResultProperties(d);var g=metawidget.util.createElement(k,"thead");m.appendChild(g);var j=this.addHeaderRow(g,c,k);var h=metawidget.util.createElement(k,"tfoot");this.addFooterRow(h,j);if(h.childNodes.length>0){m.appendChild(h)}m.appendChild(f);
+if(i!==undefined){n=i.length;for(o=0;o<n;o++){this.addRow(f,i,o,j,q,e,k)}}}}return m};this.addHeaderRow=function(i,f,j){var h=metawidget.util.createElement(j,"tr");i.appendChild(h);var d=[];for(var c=0,g=f.length;c<g;c++){var e=f[c];if(this.addHeader(h,e,j)){d.push(e)}}return d};this.addHeader=function(f,c,g){if(metawidget.util.isTrueOrTrueString(c.hidden)){return false}var e=metawidget.util.createElement(g,"th");var d="";if(c.columnWidth!==undefined){d+="width:"+c.columnWidth+";"}if(c.columnAlign!==undefined){d+="text-align:"+c.columnAlign+";"}if(d!==""){e.setAttribute("style",d)}if(c.type!=="function"){e.innerHTML=metawidget.util.getLabelString(c,g)}f.appendChild(e);return true};this.addRow=function(f,i,k,c,l,d,j){var h=metawidget.util.createElement(j,"tr");f.appendChild(h);for(var g=0,e=c.length;g<e;g++){this.addColumn(h,i,k,c[g],l,d,j)}return h};this.addColumn=function(h,i,n,l,o,d,k){var e=metawidget.util.createElement(k,"td");var c="";if(l.columnWidth!==undefined){c+="width:"+l.columnWidth+";"}if(l.columnAlign!==undefined){c+="text-align:"+l.columnAlign+";"}if(c!==""){e.setAttribute("style",c)}var m=i[n];if(m!==undefined&&l.name!==undefined){m=m[l.name]}if(l.type===undefined||l.type==="array"||l.type==="function"||b===true){var f={};for(var g in l){f[g]=l[g]}if(f.name===undefined){f.name="["+n+"]"}else{f.name=metawidget.util.appendPathWithName("["+n+"]",f)}f.nameIncludesSeparator=true;if(o!=="entity"){f.name="."+metawidget.util.appendPathWithName(d.name,f)}if(f.readOnly===undefined){f.readOnly=d.readOnly}var j;if(l.type===undefined){j=k.buildNestedMetawidget(f)}else{j=k.buildNestedMetawidget(f,{layout:new metawidget.layout.SimpleLayout()})}k.nestedMetawidgets=k.nestedMetawidgets||[];k.nestedMetawidgets.push(j);e.appendChild(j)}else{if(m!==undefined){e.innerHTML=""+m}}h.appendChild(e);return e};this.addFooterRow=function(d,c){}}})();
+var metawidget=metawidget||{};(function(){metawidget.widgetprocessor=metawidget.widgetprocessor||{};metawidget.widgetprocessor.IdProcessor=function(){if(!(this instanceof metawidget.widgetprocessor.IdProcessor)){throw new Error("Constructor called as a function")}};metawidget.widgetprocessor.IdProcessor.prototype.processWidget=function(c,a,b,e){if(!metawidget.util.hasAttribute(c,"id")){var d=metawidget.util.getId(a,b,e);if(d!==undefined){c.setAttribute("id",d)}}return c};metawidget.widgetprocessor.RequiredAttributeProcessor=function(){if(!(this instanceof metawidget.widgetprocessor.RequiredAttributeProcessor)){throw new Error("Constructor called as a function")}};metawidget.widgetprocessor.RequiredAttributeProcessor.prototype.processWidget=function(c,a,b){if(metawidget.util.isTrueOrTrueString(b.required)){c.setAttribute("required","required")}return c};metawidget.widgetprocessor.PlaceholderAttributeProcessor=function(){if(!(this instanceof metawidget.widgetprocessor.PlaceholderAttributeProcessor)){throw new Error("Constructor called as a function")}};metawidget.widgetprocessor.PlaceholderAttributeProcessor.prototype.processWidget=function(c,a,b){if(b.placeholder!==undefined){c.setAttribute("placeholder",b.placeholder)}return c};metawidget.widgetprocessor.DisabledAttributeProcessor=function(){if(!(this instanceof metawidget.widgetprocessor.DisabledAttributeProcessor)){throw new Error("Constructor called as a function")}};metawidget.widgetprocessor.DisabledAttributeProcessor.prototype.processWidget=function(c,a,b){if(metawidget.util.isTrueOrTrueString(b.disabled)){c.setAttribute("disabled","disabled")}return c};metawidget.widgetprocessor.SimpleBindingProcessor=function(){if(!(this instanceof metawidget.widgetprocessor.SimpleBindingProcessor)){throw new Error("Constructor called as a function")}};metawidget.widgetprocessor.SimpleBindingProcessor.prototype.onStartBuild=function(a){a._simpleBindingProcessor={}};metawidget.widgetprocessor.SimpleBindingProcessor.prototype.processWidget=function(f,a,b,h){var c=metawidget.util.splitPath(h.path);
+if(f.tagName==="INPUT"&&(f.getAttribute("type")==="button"||f.getAttribute("type")==="submit")){f.onclick=function(){try{return metawidget.util.traversePath(h.toInspect,c.names)[b.name]()}catch(i){if(alert!==undefined){alert(i)}else{throw i}}};return f}var e;if(a==="entity"){e=metawidget.util.traversePath(h.toInspect,c.names);if(c.names===undefined){h._simpleBindingProcessor.topLevel=true}else{h._simpleBindingProcessor.topLevelWithPath=true}}else{var g=metawidget.util.traversePath(h.toInspect,c.names);if(g!==undefined){e=g[b.name]}else{e=undefined}}var d=this.bindToWidget(f,e,a,b,h);if(d===true||f.getMetawidget!==undefined||f.nestedMetawidgets!==undefined){h._simpleBindingProcessor.bindings=h._simpleBindingProcessor.bindings||[];h._simpleBindingProcessor.bindings[b.name]={widget:f,elementName:a,attributes:b}}return f};metawidget.widgetprocessor.SimpleBindingProcessor.prototype.bindToWidget=function(e,g,j,d,h){var c=(e.tagName==="INPUT"||e.tagName==="SELECT"||e.tagName==="TEXTAREA");if(c===true&&metawidget.util.hasAttribute(e,"id")){e.setAttribute("name",e.getAttribute("id"))}var f,b;if(d["enum"]!==undefined&&e.tagName==="DIV"){if(d.type==="array"||d.componentType!==undefined){c=true;b=e.childNodes.length;for(f=0;f<b;f++){var a=e.childNodes[f];if(a.tagName==="DIV"){a=a.childNodes[0]}if(a.tagName==="LABEL"){var i=a.childNodes[0];if(i.tagName==="INPUT"){i.setAttribute("name",e.getAttribute("id"));if(d.type==="array"){i.checked=(g!==undefined&&g.indexOf(i.value)!==-1)}else{if(d.type==="boolean"){i.checked=(g===i.value||i.value===""+g)}else{i.checked=(g===i.value)}}}}}}}if(g!==undefined){if(e.tagName==="OUTPUT"||e.tagName==="TEXTAREA"){if(metawidget.util.isTrueOrTrueString(d.masked)){e.innerHTML=metawidget.util.fillString("*",g.length)}else{if(d.enumTitles!==undefined){if(d.type==="array"){b=g.length;for(f=0;f<b;f++){if(f===0){e.innerHTML=""}else{e.innerHTML+=", "}e.innerHTML+=metawidget.util.lookupEnumTitle(g[f],d["enum"],d.enumTitles)}}else{e.innerHTML=metawidget.util.lookupEnumTitle(g,d["enum"],d.enumTitles)
+}}else{if(d.type==="boolean"){if(g===true){e.innerHTML=metawidget.util.getLocalizedString("Yes",h)}else{if(g===false){e.innerHTML=metawidget.util.getLocalizedString("No",h)}else{e.innerHTML=g}}}else{e.innerHTML=g}}}}else{if(e.tagName==="INPUT"&&e.getAttribute("type")==="checkbox"){e.checked=g}else{if(c===true){e.value=g}}}}return c};metawidget.widgetprocessor.SimpleBindingProcessor.prototype.save=function(l){var m;var b=false;var n=metawidget.util.splitPath(l.path);if(n.names===undefined){m=l.toInspect}else{var f=n.names.slice(0,n.names.length-1);var j=metawidget.util.traversePath(l.toInspect,f);if(l._simpleBindingProcessor.topLevelWithPath===true){m=j}else{var k=n.names[n.names.length-1];m=j[k];if(m===undefined){m={};j[k]=m}}}for(var c in l._simpleBindingProcessor.bindings){var h=l._simpleBindingProcessor.bindings[c];var e=this.getWidgetFromBinding(h,l);if(e.getMetawidget!==undefined){var a=this.save(e.getMetawidget());if(a===true){b=true}continue}var i=this.saveFromWidget(h,l);if(b===false&&m[c]!==i){b=true}if(l._simpleBindingProcessor.topLevel===true){l.toInspect=i;return b}m[c]=i}if(l.nestedMetawidgets!==undefined){for(var g=0,d=l.nestedMetawidgets.length;g<d;g++){var a=this.save(l.nestedMetawidgets[g].getMetawidget());if(a===true){b=true}}}return b};metawidget.widgetprocessor.SimpleBindingProcessor.prototype.saveFromWidget=function(f,h){var c=this.getWidgetFromBinding(f,h);if(c.getAttribute("type")==="checkbox"){return c.checked}if(f.attributes.type==="integer"){var g=parseInt(c.value);if(isNaN(g)){return undefined}return g}if(f.attributes.type==="number"){var g=parseFloat(c.value);if(isNaN(g)){return undefined}return g}if(f.attributes["enum"]!==undefined&&c.tagName==="DIV"){if(f.attributes.type==="array"||f.attributes.componentType!==undefined){var e;for(var d=0,b=c.childNodes.length;d<b;d++){var a=c.childNodes[d];if(a.tagName==="DIV"){a=a.childNodes[0]}if(a.tagName==="LABEL"){var i=a.childNodes[0];if(i.checked){if(f.attributes.type==="boolean"){return(i.value===true||i.value==="true")
+}if(f.attributes.type!=="array"){return i.value}e=e||[];e.push(i.value)}}}return e}}if(f.attributes.type==="boolean"){return(c.value===true||c.value==="true")}if(c.value===""||c.value===null){return}return c.value};metawidget.widgetprocessor.SimpleBindingProcessor.prototype.getWidgetFromBinding=function(a,b){return a.widget};metawidget.widgetprocessor.SimpleBindingProcessor.prototype.reload=function(b,e){for(var a in e._simpleBindingProcessor.bindings){var d=e._simpleBindingProcessor.bindings[a];var c=this.getWidgetFromBinding(d,e);if(c.getMetawidget!==undefined){this.reload(b,c.getMetawidget());continue}this.bindToWidget(c,b[c.getAttribute("id")],d.elementName,d.attributes,e)}}})();
+var metawidget=metawidget||{};(function(){metawidget.Metawidget=function(d,a){if(!(this instanceof metawidget.Metawidget)){throw new Error("Constructor called as a function")}var f=this;d.getMetawidget=function(){return f};var c=new metawidget.Pipeline(d);c.inspector=new metawidget.inspector.PropertyTypeInspector();c.widgetBuilder=new metawidget.widgetbuilder.CompositeWidgetBuilder([new metawidget.widgetbuilder.OverriddenWidgetBuilder(),new metawidget.widgetbuilder.ReadOnlyWidgetBuilder(),new metawidget.widgetbuilder.HtmlWidgetBuilder()]);c.widgetProcessors=[new metawidget.widgetprocessor.IdProcessor(),new metawidget.widgetprocessor.RequiredAttributeProcessor(),new metawidget.widgetprocessor.PlaceholderAttributeProcessor(),new metawidget.widgetprocessor.DisabledAttributeProcessor(),new metawidget.widgetprocessor.SimpleBindingProcessor()];c.layout=new metawidget.layout.HeadingTagLayoutDecorator(new metawidget.layout.TableLayout());c.configure(a);var e=[];while(d.childNodes.length>0){var b=d.childNodes[0];d.removeChild(b);if(b.nodeType===1){e.push(b)}}this.reconfigure=function(g){return c.configure(g)};this.save=function(){return c.getWidgetProcessor(function(g){return g instanceof metawidget.widgetprocessor.SimpleBindingProcessor}).save(this)};this.getWidgetProcessor=function(g){return c.getWidgetProcessor(g)};this.setLayout=function(g){c.layout=g};this.inspect=function(i,g,h){return c.inspect(i,g,h,this)};this.buildWidgets=function(j){this.overriddenNodes=[];for(var g=0,i=e.length;g<i;g++){this.overriddenNodes.push(e[g].cloneNode(true))}if(j===undefined){if(arguments.length>0){throw new Error("Calling buildWidgets( undefined ) may cause infinite loop. Check your argument, or pass no arguments instead")}var h=metawidget.util.splitPath(this.path);j=c.inspect(this.toInspect,h.type,h.names,this)}c.buildWidgets(j,this)};this.getElement=function(){return c.element};this.clearWidgets=function(){var g=this.getElement();while(g.childNodes.length>0){g.removeChild(g.childNodes[0])
+}};this.buildNestedMetawidget=function(g,h){var j=metawidget.util.createElement(this,"div");var i=new metawidget.Metawidget(j,[c,h]);i.toInspect=this.toInspect;i.path=metawidget.util.appendPath(g,this);i.readOnly=this.readOnly||metawidget.util.isTrueOrTrueString(g.readOnly);i.buildWidgets();return j}};metawidget.Pipeline=function(a){if(!(this instanceof metawidget.Pipeline)){throw new Error("Constructor called as a function")}this.inspectionResultProcessors=[];this.widgetProcessors=[];this.element=a;this.maximumInspectionDepth=10};metawidget.Pipeline.prototype.configure=function(b){if(b===undefined){return}var a;if(b instanceof Array){for(a=0;a<b.length;a++){this.configure(b[a])}return}if(b.inspector!==undefined){this.inspector=b.inspector}if(b.inspectionResultProcessors!==undefined){this.inspectionResultProcessors=b.inspectionResultProcessors.slice(0)}if(b.prependInspectionResultProcessors!==undefined){if(!(b.prependInspectionResultProcessors instanceof Array)){b.prependInspectionResultProcessors=[b.prependInspectionResultProcessors]}for(a=0;a<b.prependInspectionResultProcessors.length;a++){this.inspectionResultProcessors.splice(a,0,b.prependInspectionResultProcessors[a])}}if(b.appendInspectionResultProcessors!==undefined){if(!(b.appendInspectionResultProcessors instanceof Array)){b.appendInspectionResultProcessors=[b.appendInspectionResultProcessors]}for(a=0;a<b.appendInspectionResultProcessors.length;a++){this.inspectionResultProcessors.push(b.appendInspectionResultProcessors[a])}}if(b.widgetBuilder!==undefined){this.widgetBuilder=b.widgetBuilder}if(b.widgetProcessors!==undefined){this.widgetProcessors=b.widgetProcessors.slice(0)}if(b.prependWidgetProcessors!==undefined){if(!(b.prependWidgetProcessors instanceof Array)){b.prependWidgetProcessors=[b.prependWidgetProcessors]}for(a=0;a<b.prependWidgetProcessors.length;a++){this.widgetProcessors.splice(a,0,b.prependWidgetProcessors[a])}}if(b.appendWidgetProcessors!==undefined){if(!(b.appendWidgetProcessors instanceof Array)){b.appendWidgetProcessors=[b.appendWidgetProcessors]
+}for(a=0;a<b.appendWidgetProcessors.length;a++){this.widgetProcessors.push(b.appendWidgetProcessors[a])}}if(b.layout!==undefined){this.layout=b.layout}if(b.maximumInspectionDepth!==undefined){this.maximumInspectionDepth=b.maximumInspectionDepth-1}if(b.styleClass!==undefined){this.styleClass=b.styleClass;metawidget.util.appendToAttribute(this.element,"class",b.styleClass)}};metawidget.Pipeline.prototype.getWidgetProcessor=function(b){for(var a=0,c=this.widgetProcessors.length;a<c;a++){var d=this.widgetProcessors[a];if(b(d)){return d}}};metawidget.Pipeline.prototype.inspect=function(g,c,f,h){var e;if(this.inspector.inspect!==undefined){e=this.inspector.inspect(g,c,f)}else{e=this.inspector(g,c,f)}if(e===undefined){return}for(var a=0,d=this.inspectionResultProcessors.length;a<d;a++){var b=this.inspectionResultProcessors[a];if(b.processInspectionResult!==undefined){e=b.processInspectionResult(e,h,g,c,f)}else{e=b(e,h,g,c,f)}if(e===undefined){return}}return e};metawidget.Pipeline.prototype.buildWidgets=function(h,m){m.clearWidgets();d(this,m);if(h!==undefined){var c=a(h,m,"properties");var n="entity";var i=g(this,n,c,m);if(i!==undefined){i=k(this,i,n,c,m);if(i!==undefined){this.layoutWidget(i,n,c,this.element,m)}}else{var f=metawidget.util.getSortedInspectionResultProperties(h);for(var j=0,e=f.length;j<e;j++){c=a(f[j],m);if(c.type==="function"){n="action"}else{n="property"}i=g(this,n,c,m);if(i===undefined){if(this.maximumInspectionDepth<=0){continue}i=m.buildNestedMetawidget(c);if(i===undefined){continue}}i=k(this,i,n,c,m);if(i!==undefined){this.layoutWidget(i,n,c,this.element,m)}}}}l(this,m);if(this.element.dispatchEvent!==undefined){this.element.dispatchEvent(metawidget.util.createEvent(m,"buildEnd"))}function a(o,s,r){var q={};for(var p in o){if(r!==undefined&&r.indexOf(p)!==-1){continue}q[p]=o[p]}if(s.readOnly===true){q.readOnly="true"}return q}function d(p,r){for(var o=0,q=r.overriddenNodes.length;o<q;o++){r.overriddenNodes[o].overridden=true}if(p.widgetBuilder.onStartBuild!==undefined){p.widgetBuilder.onStartBuild(r)
+}b("onStartBuild",p,r);if(p.layout.onStartBuild!==undefined){p.layout.onStartBuild(r)}if(p.layout.startContainerLayout!==undefined){p.layout.startContainerLayout(p.element,r)}}function g(q,o,p,r){if(q.widgetBuilder.buildWidget!==undefined){return q.widgetBuilder.buildWidget(o,p,r)}return q.widgetBuilder(o,p,r)}function k(r,t,o,q,v){for(var p=0,s=r.widgetProcessors.length;p<s;p++){var u=r.widgetProcessors[p];if(u.processWidget!==undefined){t=u.processWidget(t,o,q,v)}else{t=u(t,o,q,v)}if(t===undefined){return}}return t}function l(p,u){if(u.onEndBuild!==undefined){u.onEndBuild()}else{while(u.overriddenNodes.length>0){var t=u.overriddenNodes[0];u.overriddenNodes.splice(0,1);if(t.tagName==="FACET"){continue}var r={section:""};if(t.tagName==="STUB"){for(var o=0,q=t.attributes.length;o<q;o++){var s=t.attributes[o];r[s.nodeName]=s.nodeValue}}p.layoutWidget(t,"property",r,p.element,u)}}if(p.layout.endContainerLayout!==undefined){p.layout.endContainerLayout(p.element,u)}if(p.layout.onEndBuild!==undefined){p.layout.onEndBuild(u)}b("onEndBuild",p,u);if(p.widgetBuilder.onEndBuild!==undefined){p.widgetBuilder.onEndBuild(u)}}function b(r,p,t){for(var o=0,q=p.widgetProcessors.length;o<q;o++){var s=p.widgetProcessors[o];if(s[r]!==undefined){s[r](t)}}}};metawidget.Pipeline.prototype.layoutWidget=function(d,a,c,b,e){if(this.layout.layoutWidget!==undefined){this.layout.layoutWidget(d,a,c,b,e);return}this.layout(d,a,c,b,e)}})();
diff --git a/src/main/webapp/js/3rdparty/metawidget/core/metawidget-inspectionresultprocessors.js b/src/main/webapp/js/3rdparty/metawidget/core/metawidget-inspectionresultprocessors.js
new file mode 100644
index 0000000000000000000000000000000000000000..a4068f007abf8ef661a196507c7e6719f6364253
--- /dev/null
+++ b/src/main/webapp/js/3rdparty/metawidget/core/metawidget-inspectionresultprocessors.js
@@ -0,0 +1,28 @@
+// Metawidget 4.2
+//
+// This file is dual licensed under both the LGPL
+// (http://www.gnu.org/licenses/lgpl-2.1.html) and the EPL
+// (http://www.eclipse.org/org/documents/epl-v10.php). As a
+// recipient of Metawidget, you may choose to receive it under either
+// the LGPL or the EPL.
+//
+// Commercial licenses are also available. See http://metawidget.org
+// for details.
+
+/**
+ * @author <a href="http://kennardconsulting.com">Richard Kennard</a>
+ */
+
+var metawidget = metawidget || {};
+
+( function() {
+
+	'use strict';
+
+	/**
+	 * @namespace InspectionResultProcessors.
+	 */
+
+	metawidget.inspectionresultprocessor = metawidget.inspectionresultprocessor || {};
+	
+} )();
\ No newline at end of file
diff --git a/src/main/webapp/js/3rdparty/metawidget/core/metawidget-inspectors.js b/src/main/webapp/js/3rdparty/metawidget/core/metawidget-inspectors.js
new file mode 100644
index 0000000000000000000000000000000000000000..3ef91916547fc09c1d2d9604cca1765bd554805d
--- /dev/null
+++ b/src/main/webapp/js/3rdparty/metawidget/core/metawidget-inspectors.js
@@ -0,0 +1,314 @@
+// Metawidget 4.2
+//
+// This file is dual licensed under both the LGPL
+// (http://www.gnu.org/licenses/lgpl-2.1.html) and the EPL
+// (http://www.eclipse.org/org/documents/epl-v10.php). As a
+// recipient of Metawidget, you may choose to receive it under either
+// the LGPL or the EPL.
+//
+// Commercial licenses are also available. See http://metawidget.org
+// for details.
+
+/**
+ * @author <a href="http://kennardconsulting.com">Richard Kennard</a>
+ */
+
+var metawidget = metawidget || {};
+
+( function() {
+
+	'use strict';
+
+	/**
+	 * @namespace Inspectors.
+	 *            <p>
+	 *            Inspectors must implement an interface:
+	 *            </p>
+	 *            <tt>function( toInspect, type, names )</tt>
+	 *            <p>
+	 *            Each Inspector must look to the 'type' parameter and the
+	 *            'names' array. These form a path into the domain object model.
+	 *            For example the 'type' may be 'person' and the 'names' may be [
+	 *            'address', 'street' ]. This would form a path into the domain
+	 *            model of 'person/address/street' (i.e. return information on
+	 *            the 'street' property within the 'address' property of the
+	 *            'person' type).
+	 *            </p>
+	 */
+
+	metawidget.inspector = metawidget.inspector || {};
+
+	/**
+	 * @class Delegates inspection to one or more sub-inspectors, then combines
+	 *        the resulting metadata using
+	 *        <tt>metawidget.util.combineInspectionResults</tt>.
+	 *        <p>
+	 *        The combining algorithm should be suitable for most use cases, but
+	 *        one benefit of having a separate CompositeInspector is that
+	 *        developers can replace it with their own version, with its own
+	 *        combining algorithm, if required.
+	 *        <p>
+	 *        Note: the name <em>Composite</em>Inspector refers to the
+	 *        Composite design pattern.
+	 */
+
+	metawidget.inspector.CompositeInspector = function( config ) {
+
+		if ( ! ( this instanceof metawidget.inspector.CompositeInspector ) ) {
+			throw new Error( 'Constructor called as a function' );
+		}
+
+		var _inspectors;
+
+		if ( config.inspectors !== undefined ) {
+			_inspectors = config.inspectors.slice( 0 );
+		} else {
+			_inspectors = config.slice( 0 );
+		}
+
+		this.inspect = function( toInspect, type, names ) {
+
+			var compositeInspectionResult = {};
+
+			for ( var ins = 0, insLength = _inspectors.length; ins < insLength; ins++ ) {
+
+				var inspectionResult;
+				var inspector = _inspectors[ins];
+
+				if ( inspector.inspect !== undefined ) {
+					inspectionResult = inspector.inspect( toInspect, type, names );
+				} else {
+					inspectionResult = inspector( toInspect, type, names );
+				}
+
+				metawidget.util.combineInspectionResults( compositeInspectionResult, inspectionResult );
+			}
+
+			return compositeInspectionResult;
+		};
+	};
+
+	/**
+	 * @class Inspects JavaScript objects for their property names and types.
+	 *        <p>
+	 *        In principal, ordering of property names within JavaScript objects
+	 *        is not guaranteed. In practice, most browsers respect the original
+	 *        order that properties were defined in. However you may want to
+	 *        combine PropertyTypeInspector with a custom Inspector that imposes
+	 *        a defined ordering using 'propertyOrder' attributes.
+	 */
+
+	metawidget.inspector.PropertyTypeInspector = function() {
+
+		if ( ! ( this instanceof metawidget.inspector.PropertyTypeInspector ) ) {
+			throw new Error( 'Constructor called as a function' );
+		}
+	};
+
+	metawidget.inspector.PropertyTypeInspector.prototype.inspect = function( toInspect, type, names ) {
+
+		/**
+		 * Inspect the type of the property as best we can.
+		 */
+
+		function _getTypeOf( value ) {
+
+			// JSON Schema primitive types are: 'array', 'boolean',
+			// 'number', 'null', 'object' and 'string'
+
+			if ( value instanceof Array ) {
+
+				// typeof never returns 'array', even though JavaScript has
+				// a built-in Array type
+
+				return 'array';
+
+			} else if ( value instanceof Date ) {
+
+				// typeof never returns 'date', even though JavaScript has a
+				// built-in Date type
+
+				return 'date';
+
+			} else {
+
+				var typeOfProperty = typeof ( value );
+
+				// type 'object' doesn't convey much, and can override a
+				// more descriptive inspection result from a previous
+				// Inspector. If you leave it off, Metawidget's default
+				// behaviour is to recurse into the object anyway
+
+				if ( typeOfProperty !== 'object' ) {
+					return typeOfProperty;
+				}
+			}
+		}
+
+		// Traverse names
+
+		toInspect = metawidget.util.traversePath( toInspect, names );
+
+		var inspectionResult = {};
+
+		// Inspect root node. Important if the Metawidget is
+		// pointed directly at a primitive type
+
+		if ( names !== undefined && names.length > 0 ) {
+			inspectionResult.name = names[names.length - 1];
+		} else {
+
+			// Nothing useful to return?
+
+			if ( toInspect === undefined ) {
+				return;
+			}
+		}
+
+		if ( toInspect !== undefined ) {
+
+			inspectionResult.type = _getTypeOf( toInspect );
+
+			if ( inspectionResult.type === undefined ) {
+
+				inspectionResult.properties = {};
+
+				for ( var property in toInspect ) {
+
+					inspectionResult.properties[property] = {
+						type: _getTypeOf( toInspect[property] )
+					};
+				}
+			}
+		}
+
+		return inspectionResult;
+	};
+
+	/**
+	 * @class Inspects JSON Schemas for their properties.
+	 *        <p>
+	 *        Because Metawidget <em>already</em> uses JSON Schema (v3)
+	 *        internally as its inspection result format, this Inspector does
+	 *        not need to do much. However it adds support for:
+	 *        <p>
+	 *        <ul>
+	 *        <li>schemas that contain nested schemas (by traversing the given
+	 *        'names' array)</li>
+	 *        <li>checking the 'type' property of the schema</li>
+	 *        <li>schemas that describe arrays (by traversing the 'items'
+	 *        property)</li>
+	 *        <li>schemas that have a top-level 'required' array (JSON Schema
+	 *        v4)</li>
+	 *        </ul>
+	 */
+
+	metawidget.inspector.JsonSchemaInspector = function( config ) {
+
+		if ( ! ( this instanceof metawidget.inspector.JsonSchemaInspector ) ) {
+			throw new Error( 'Constructor called as a function' );
+		}
+
+		var _schema;
+
+		if ( config.schema !== undefined ) {
+			_schema = config.schema;
+		} else {
+			_schema = config;
+		}
+
+		this.inspect = function( toInspect, type, names ) {
+
+			/**
+			 * Specialized version of <tt>metawidget.util.traversePath</tt>
+			 * that supports 'properties' and 'items'.
+			 */
+
+			function _traversePath( toInspect, names ) {
+
+				if ( toInspect === undefined ) {
+					return undefined;
+				}
+
+				if ( names !== undefined ) {
+
+					// Sanity check for passing a single string
+
+					if ( ! ( names instanceof Array ) ) {
+						throw new Error( "Expected array of names" );
+					}
+
+					for ( var loop = 0, length = names.length; loop < length; loop++ ) {
+
+						// Support 'items' property (for arrays)
+
+						var name = names[loop];
+
+						if ( !isNaN( name ) ) {
+
+							toInspect = toInspect.items;
+
+							if ( toInspect === undefined ) {
+								return undefined;
+							}
+
+							// We ignore the actual array index. We assume the
+							// JSON Schema describes a homogeneous array,
+							// regardless of the index
+
+							continue;
+						}
+
+						// Support 'properties' property
+
+						toInspect = toInspect.properties;
+
+						if ( toInspect === undefined ) {
+							return undefined;
+						}
+
+						toInspect = toInspect[name];
+
+						// We don't need to worry about array indexes here: they
+						// should have been parsed out by splitPath
+
+						if ( toInspect === undefined ) {
+							return undefined;
+						}
+					}
+				}
+
+				return toInspect;
+			}
+
+			// Traverse names using 'properties' and 'items' as appropriate
+
+			var traversed = _traversePath( _schema, names );
+
+			if ( traversed === undefined ) {
+				return undefined;
+			}
+
+			// Copy values
+
+			var inspectionResult = {};
+			if ( names !== undefined ) {
+				inspectionResult.name = names[names.length - 1];
+			}
+			metawidget.util.combineInspectionResults( inspectionResult, traversed );
+
+			// Copy top-level 'required' array into each property (JSON Schema
+			// v4)
+
+			if ( inspectionResult.required !== undefined ) {
+
+				for ( var loop = 0, length = inspectionResult.required.length; loop < length; loop++ ) {
+
+					inspectionResult.properties[inspectionResult.required[loop]].required = true;
+				}
+			}
+
+			return inspectionResult;
+		};
+	};
+} )();
\ No newline at end of file
diff --git a/src/main/webapp/js/3rdparty/metawidget/core/metawidget-layouts.js b/src/main/webapp/js/3rdparty/metawidget/core/metawidget-layouts.js
new file mode 100644
index 0000000000000000000000000000000000000000..a487a6cc0380f26bc3e9987f170932609538f3aa
--- /dev/null
+++ b/src/main/webapp/js/3rdparty/metawidget/core/metawidget-layouts.js
@@ -0,0 +1,872 @@
+// Metawidget 4.2
+//
+// This file is dual licensed under both the LGPL
+// (http://www.gnu.org/licenses/lgpl-2.1.html) and the EPL
+// (http://www.eclipse.org/org/documents/epl-v10.php). As a
+// recipient of Metawidget, you may choose to receive it under either
+// the LGPL or the EPL.
+//
+// Commercial licenses are also available. See http://metawidget.org
+// for details.
+
+/**
+ * @author <a href="http://kennardconsulting.com">Richard Kennard</a>
+ */
+
+var metawidget = metawidget || {};
+
+( function() {
+
+	'use strict';
+
+	/**
+	 * @namespace Layouts.
+	 */
+
+	metawidget.layout = metawidget.layout || {};
+
+	/**
+	 * @class Layout to simply output components one after another, with no
+	 *        labels and no structure. This Layout is suited to rendering single
+	 *        components, or for rendering components whose layout relies
+	 *        entirely on CSS.
+	 */
+
+	metawidget.layout.SimpleLayout = function() {
+
+		if ( ! ( this instanceof metawidget.layout.SimpleLayout ) ) {
+			throw new Error( 'Constructor called as a function' );
+		}
+	};
+
+	metawidget.layout.SimpleLayout.prototype.layoutWidget = function( widget, elementName, attributes, container ) {
+
+		if ( widget.tagName === 'STUB' && !metawidget.util.hasChildElements( widget ) ) {
+			return;
+		}
+
+		container.appendChild( widget );
+	};
+
+	/**
+	 * @class Layout to arrange widgets using dl/dt/dd tags.
+	 */
+
+	metawidget.layout.DefinitionListLayout = function( config ) {
+
+		if ( ! ( this instanceof metawidget.layout.DefinitionListLayout ) ) {
+			throw new Error( 'Constructor called as a function' );
+		}
+
+		var _labelStyleClass = config !== undefined ? config.labelStyleClass : undefined;
+		var _labelSuffix = config !== undefined && config.labelSuffix !== undefined ? config.labelSuffix : ':';
+
+		this.startContainerLayout = function( container, mw ) {
+
+			var dl = metawidget.util.createElement( mw, 'dl' );
+			if ( mw.path !== undefined ) {
+				var id = metawidget.util.getId( "property", {}, mw );
+				if ( id !== undefined ) {
+					dl.setAttribute( 'id', 'dl-' + id );
+				}
+			}
+
+			container.appendChild( dl );
+		};
+
+		this.layoutWidget = function( widget, elementName, attributes, container, mw ) {
+
+			if ( widget.tagName === 'STUB' && !metawidget.util.hasChildElements( widget ) ) {
+				return;
+			}
+
+			// Label
+
+			var dl = container.childNodes[container.childNodes.length - 1];
+			this.layoutLabel( dl, widget, elementName, attributes, mw );
+
+			// Widget
+
+			var dd = metawidget.util.createElement( mw, 'dd' );
+			dd.appendChild( widget );
+			dl.appendChild( dd );
+		};
+
+		this.layoutLabel = function( dl, widget, elementName, attributes, mw ) {
+
+			if ( elementName === 'entity' || elementName === 'action' ) {
+				return;
+			}
+
+			if ( attributes.name === undefined && attributes.title === undefined ) {
+				return;
+			}
+
+			var labelString = metawidget.util.getLabelString( attributes, mw );
+
+			if ( labelString === '' || labelString === null ) {
+				return;
+			}
+
+			var dt = metawidget.util.createElement( mw, 'dt' );
+
+			var label = metawidget.util.createElement( mw, 'label' );
+			if ( widget.getAttribute( 'id' ) !== null ) {
+				label.setAttribute( 'for', widget.getAttribute( 'id' ) );
+			}
+
+			if ( _labelStyleClass !== undefined ) {
+				label.setAttribute( 'class', _labelStyleClass );
+			}
+
+			label.innerHTML = labelString + _labelSuffix;
+
+			dt.appendChild( label );
+			dl.appendChild( dt );
+		};
+	};
+
+	/**
+	 * @class Layout to arrange widgets using div tags.
+	 */
+
+	metawidget.layout.DivLayout = function( config ) {
+
+		if ( ! ( this instanceof metawidget.layout.DivLayout ) ) {
+			throw new Error( 'Constructor called as a function' );
+		}
+
+		var _divStyleClasses = config !== undefined ? config.divStyleClasses : undefined;
+		var _labelStyleClass = config !== undefined ? config.labelStyleClass : undefined;
+		var _labelSuffix = config !== undefined && config.labelSuffix !== undefined ? config.labelSuffix : ':';
+		var _suppressDivAroundLabel = config !== undefined && config.suppressDivAroundLabel !== undefined ? config.suppressDivAroundLabel : false;
+		var _suppressDivAroundWidget = config !== undefined && config.suppressDivAroundWidget !== undefined ? config.suppressDivAroundWidget : false;
+		var _appendRequiredClassOnLabelDiv = config !== undefined && config.appendRequiredClassOnLabelDiv !== undefined ? config.appendRequiredClassOnLabelDiv : undefined;
+		var _appendRequiredClassOnWidgetDiv = config !== undefined && config.appendRequiredClassOnWidgetDiv !== undefined ? config.appendRequiredClassOnWidgetDiv : undefined;
+
+		// REFACTOR: make this _suppressLabelSuffixOn and allow pass array of
+		// types
+
+		var _suppressLabelSuffixOnCheckboxes = config !== undefined && config.suppressLabelSuffixOnCheckboxes !== undefined ? config.suppressLabelSuffixOnCheckboxes : false;
+		var _wrapInsideLabels = config !== undefined && config.wrapInsideLabels !== undefined ? config.wrapInsideLabels : undefined;
+		var _wrapWithExtraDiv = config !== undefined && config.wrapWithExtraDiv !== undefined ? config.wrapWithExtraDiv : undefined;
+
+		this.layoutWidget = function( widget, elementName, attributes, container, mw ) {
+
+			if ( widget.tagName === 'STUB' && !metawidget.util.hasChildElements( widget ) ) {
+				return;
+			}
+
+			// Collapse buttons into the previous div, if it also contained a
+			// button
+
+			if ( widget.tagName === 'INPUT' && ( widget.getAttribute( 'type' ) === 'button' || widget.getAttribute( 'type' ) === 'submit' ) ) {
+
+				if ( container.childNodes.length > 0 ) {
+
+					var lastOuterDiv = container.childNodes[container.childNodes.length - 1];
+					if ( lastOuterDiv.childNodes.length === 1 ) {
+						var lastWidgetDiv = lastOuterDiv.childNodes[0];
+						if ( lastWidgetDiv.childNodes.length > 0 ) {
+							var lastWidget = lastWidgetDiv.childNodes[lastWidgetDiv.childNodes.length - 1];
+							if ( lastWidget.tagName === 'INPUT' && ( lastWidget.getAttribute( 'type' ) === 'button' || lastWidget.getAttribute( 'type' ) === 'submit' ) ) {
+								lastWidgetDiv.appendChild( widget );
+								return;
+							}
+						}
+					}
+				}
+			}
+
+			var outerDiv = metawidget.util.createElement( mw, 'div' );
+			if ( _divStyleClasses !== undefined && _divStyleClasses[0] !== undefined ) {
+				outerDiv.setAttribute( 'class', _divStyleClasses[0] );
+			}
+
+			// Label
+
+			var labelWidget = this.layoutLabel( outerDiv, widget, elementName, attributes, mw );
+
+			// Widget
+			
+			var toAppendToOuterDiv = widget;
+
+			// _wrapInsideLabels
+
+			if ( widget.tagName === 'INPUT' && metawidget.util.niceIndexOf( _wrapInsideLabels, widget.getAttribute( 'type' ) ) !== -1 ) {
+				labelWidget.insertBefore( widget, labelWidget.firstChild );
+				toAppendToOuterDiv = labelWidget;
+			} else {
+				toAppendToOuterDiv = widget;
+			}
+
+			// _wrapWithExtraDiv
+
+			if ( widget.tagName === 'INPUT' && _wrapWithExtraDiv !== undefined && _wrapWithExtraDiv[widget.getAttribute( 'type' )] !== undefined ) {
+
+				var extraDiv = metawidget.util.createElement( mw, 'div' );
+				extraDiv.setAttribute( 'class', _wrapWithExtraDiv[widget.getAttribute( 'type' )] );
+				extraDiv.appendChild( toAppendToOuterDiv );
+				toAppendToOuterDiv = extraDiv;
+			}
+
+			// Wrap with div
+			
+			if ( _suppressDivAroundWidget !== true ) {
+				var widgetDiv = metawidget.util.createElement( mw, 'div' );
+				if ( _divStyleClasses !== undefined && _divStyleClasses[2] !== undefined ) {
+					widgetDiv.setAttribute( 'class', _divStyleClasses[2] );
+				}
+
+				// Useful for CSS :after selectors
+
+				if ( metawidget.util.isTrueOrTrueString( attributes.required ) && _appendRequiredClassOnWidgetDiv !== undefined ) {
+					metawidget.util.appendToAttribute( widgetDiv, 'class', _appendRequiredClassOnWidgetDiv );
+				}
+				widgetDiv.appendChild( toAppendToOuterDiv );
+				toAppendToOuterDiv = widgetDiv;
+			}
+
+			outerDiv.appendChild( toAppendToOuterDiv );
+			container.appendChild( outerDiv );
+		};
+
+		/**
+		 * @return the label widget
+		 */
+
+		this.layoutLabel = function( outerDiv, widget, elementName, attributes, mw ) {
+
+			if ( elementName === 'entity' || elementName === 'action' ) {
+				return;
+			}
+
+			if ( attributes.name === undefined && attributes.title === undefined ) {
+				return;
+			}
+
+			var labelString = this.getLabelString( widget, attributes, mw );
+
+			if ( labelString === '' || labelString === null ) {
+				return;
+			}
+
+			var label = metawidget.util.createElement( mw, 'label' );
+			if ( widget.getAttribute( 'id' ) !== null ) {
+				label.setAttribute( 'for', widget.getAttribute( 'id' ) );
+				label.setAttribute( 'id', widget.getAttribute( 'id' ) + '-label' );
+			}
+
+			if ( _labelStyleClass !== undefined ) {
+				label.setAttribute( 'class', _labelStyleClass );
+			}
+
+			label.innerHTML = labelString;
+
+			if ( _suppressDivAroundLabel === true ) {
+				outerDiv.appendChild( label );
+			} else {
+				var labelDiv = metawidget.util.createElement( mw, 'div' );
+				if ( _divStyleClasses !== undefined && _divStyleClasses[1] !== undefined ) {
+					labelDiv.setAttribute( 'class', _divStyleClasses[1] );
+				}
+
+				// Useful for CSS :after selectors
+
+				if ( metawidget.util.isTrueOrTrueString( attributes.required ) && _appendRequiredClassOnLabelDiv !== undefined ) {
+					metawidget.util.appendToAttribute( labelDiv, 'class', _appendRequiredClassOnLabelDiv );
+				}
+
+				labelDiv.appendChild( label );
+				outerDiv.appendChild( labelDiv );
+			}
+
+			return label;
+		};
+
+		/**
+		 * @returns the label string, or a blank string if no label.
+		 */
+
+		this.getLabelString = function( widget, attributes, mw ) {
+
+			var labelString = metawidget.util.getLabelString( attributes, mw );
+
+			if ( labelString === '' || labelString === null ) {
+				return labelString;
+			}
+
+			// Some UI frameworks (like JQuery Mobile) reuse the checkbox label
+			// alongside the checkbox itself. This looks bad if we keep the
+			// suffix
+
+			if ( _suppressLabelSuffixOnCheckboxes === true && widget.tagName === 'INPUT' ) {
+				if ( widget.getAttribute( 'type' ) === 'checkbox' || widget.getAttribute( 'type' ) === 'radio' ) {
+					return labelString;
+				}
+			}
+
+			return labelString + _labelSuffix;
+		};
+	};
+
+	/**
+	 * @class Layout to arrange widgets in a table, with one column for the
+	 *        label and another for the widget.
+	 */
+
+	metawidget.layout.TableLayout = function( config ) {
+
+		if ( ! ( this instanceof metawidget.layout.TableLayout ) ) {
+			throw new Error( 'Constructor called as a function' );
+		}
+
+		var _tableStyleClass = config !== undefined ? config.tableStyleClass : undefined;
+		var _columnStyleClasses = config !== undefined ? config.columnStyleClasses : undefined;
+		var _headerStyleClass = config !== undefined ? config.headerStyleClass : undefined;
+		var _footerStyleClass = config !== undefined ? config.footerStyleClass : undefined;
+		var _numberOfColumns = config !== undefined && config.numberOfColumns ? config.numberOfColumns : 1;
+
+		this.startContainerLayout = function( container, mw ) {
+
+			var table = metawidget.util.createElement( mw, 'table' );
+			if ( mw.path !== undefined ) {
+				var id = metawidget.util.getId( "property", {}, mw );
+				if ( id !== undefined ) {
+					table.setAttribute( 'id', 'table-' + id );
+				}
+			}
+
+			if ( _tableStyleClass !== undefined ) {
+				table.setAttribute( 'class', _tableStyleClass );
+			}
+
+			container._currentColumn = 0;
+			container.appendChild( table );
+
+			// Facets
+
+			if ( mw.overriddenNodes !== undefined ) {
+				for ( var loop1 = 0, length1 = mw.overriddenNodes.length; loop1 < length1; loop1++ ) {
+
+					var child = mw.overriddenNodes[loop1];
+
+					if ( child.tagName !== 'FACET' ) {
+						continue;
+					}
+
+					// thead or tfoot
+
+					var parent;
+
+					if ( child.getAttribute( 'name' ) === 'header' ) {
+						parent = metawidget.util.createElement( mw, 'thead' );
+					} else if ( child.getAttribute( 'name' ) === 'footer' ) {
+						parent = metawidget.util.createElement( mw, 'tfoot' );
+					} else {
+						continue;
+					}
+
+					table.appendChild( parent );
+					var tr = metawidget.util.createElement( mw, 'tr' );
+					parent.appendChild( tr );
+					var td = metawidget.util.createElement( mw, 'td' );
+					td.setAttribute( 'colspan', _numberOfColumns * 3 );
+
+					if ( child.getAttribute( 'name' ) === 'header' ) {
+						if ( _headerStyleClass !== undefined ) {
+							td.setAttribute( 'class', _headerStyleClass );
+						}
+					} else {
+						if ( _footerStyleClass !== undefined ) {
+							td.setAttribute( 'class', _footerStyleClass );
+						}
+					}
+
+					tr.appendChild( td );
+
+					// Append children, so as to unwrap the 'facet' tag
+
+					while ( child.childNodes.length > 0 ) {
+						td.appendChild( child.removeChild( child.childNodes[0] ) );
+					}
+				}
+			}
+
+			// tbody
+
+			table.appendChild( metawidget.util.createElement( mw, 'tbody' ) );
+		};
+
+		this.layoutWidget = function( widget, elementName, attributes, container, mw ) {
+
+			// Do not render empty stubs
+
+			if ( widget.tagName === 'STUB' && !metawidget.util.hasChildElements( widget ) ) {
+				return;
+			}
+
+			// Special support for large components
+
+			var spanAllColumns = metawidget.util.isSpanAllColumns( attributes );
+
+			if ( spanAllColumns === true && container._currentColumn > 0 ) {
+				container._currentColumn = 0;
+			}
+
+			// Id
+
+			var table = container.childNodes[container.childNodes.length - 1];
+			var idPrefix = undefined;
+
+			if ( attributes.name !== undefined ) {
+				if ( metawidget.util.hasAttribute( table, 'id' )) {
+					idPrefix = table.getAttribute( 'id' );
+				}
+
+				if ( idPrefix !== undefined ) {
+					if ( elementName !== 'entity' ) {
+						if ( idPrefix.charAt( idPrefix.length - 1 ) !== '-' ) {
+							idPrefix += metawidget.util.capitalize( attributes.name );
+						} else {
+							idPrefix += attributes.name;
+						}
+					}
+				} else {
+					idPrefix = 'table-' + attributes.name;
+				}
+			}
+
+			// Start column
+
+			var tbody = table.childNodes[table.childNodes.length - 1];
+			var tr;
+
+			if ( container._currentColumn === 0 ) {
+				tr = metawidget.util.createElement( mw, 'tr' );
+				if ( idPrefix !== undefined ) {
+					tr.setAttribute( 'id', idPrefix + '-row' );
+				}
+				tbody.appendChild( tr );
+			} else {
+				tr = tbody.childNodes[tbody.childNodes.length - 1];
+			}
+
+			// Label
+
+			this.layoutLabel( tr, idPrefix, widget, elementName, attributes, mw );
+
+			// Widget
+
+			var td = metawidget.util.createElement( mw, 'td' );
+
+			if ( idPrefix !== undefined ) {
+				td.setAttribute( 'id', idPrefix + '-cell' );
+			}
+
+			if ( _columnStyleClasses !== undefined && _columnStyleClasses[1] !== undefined ) {
+				td.setAttribute( 'class', _columnStyleClasses[1] );
+			}
+
+			if ( spanAllColumns === true ) {
+				td.setAttribute( 'colspan', ( ( _numberOfColumns * 3 ) - 1 ) - tr.childNodes.length );
+			} else if ( tr.childNodes.length < 1 ) {
+				td.setAttribute( 'colspan', 2 - tr.childNodes.length );
+			}
+
+			td.appendChild( widget );
+			tr.appendChild( td );
+
+			// Required
+
+			this.layoutRequired( tr, attributes, mw );
+
+			// Next column
+
+			if ( spanAllColumns === true ) {
+				container._currentColumn = _numberOfColumns - 1;
+			}
+
+			container._currentColumn = ( container._currentColumn + 1 ) % _numberOfColumns;
+		};
+
+		this.layoutLabel = function( tr, idPrefix, widget, elementName, attributes, mw ) {
+
+			if ( elementName === 'entity' ) {
+				return;
+			}
+
+			if ( attributes.name === undefined && attributes.title === undefined ) {
+				return;
+			}
+
+			var labelString = this.getLabelString( attributes, mw );
+
+			if ( labelString === null ) {
+				return;
+			}
+
+			// Label
+
+			var th = metawidget.util.createElement( mw, 'th' );
+
+			if ( idPrefix !== undefined ) {
+				th.setAttribute( 'id', idPrefix + '-label-cell' );
+			}
+
+			if ( _columnStyleClasses !== undefined && _columnStyleClasses[0] !== undefined ) {
+				th.setAttribute( 'class', _columnStyleClasses[0] );
+			}
+
+			if ( elementName !== 'action' && labelString !== '' ) {
+				var label = metawidget.util.createElement( mw, 'label' );
+
+				if ( metawidget.util.hasAttribute( widget, 'id' )) {
+					label.setAttribute( 'for', widget.getAttribute( 'id' ) );
+				}
+
+				if ( idPrefix !== undefined ) {
+					label.setAttribute( 'id', idPrefix + '-label' );
+				}
+
+				label.innerHTML = labelString;
+				th.appendChild( label );
+			}
+
+			tr.appendChild( th );
+		};
+
+		this.layoutRequired = function( tr, attributes, mw ) {
+
+			var td = metawidget.util.createElement( mw, 'td' );
+
+			if ( _columnStyleClasses !== undefined && _columnStyleClasses[2] !== undefined ) {
+				td.setAttribute( 'class', _columnStyleClasses[2] );
+			}
+
+			if ( !metawidget.util.isTrueOrTrueString( attributes.readOnly ) && metawidget.util.isTrueOrTrueString( attributes.required ) ) {
+				td.innerHTML = '*';
+			}
+
+			tr.appendChild( td );
+		};
+
+		/**
+		 * @returns the label string, a blank string if no label, or null
+		 */
+
+		this.getLabelString = function( attributes, mw ) {
+
+			var labelString = metawidget.util.getLabelString( attributes, mw );
+
+			if ( labelString === '' || labelString === null ) {
+				return labelString;
+			}
+
+			return labelString + ':';
+		};
+	};
+
+	//
+	// LayoutDecorator
+	//
+
+	/**
+	 * Augment the given 'decorator' with methods suitable for making section
+	 * separator LayoutDecorators.
+	 * <p>
+	 * This includes implementing <tt>onStartBuild</tt>,
+	 * <tt>startContainerLayout</tt>, <tt>endContainerLayout</tt> and
+	 * <tt>onEndBuild</tt> methods.
+	 */
+
+	metawidget.layout._createSectionLayoutDecorator = function( config, decorator, decoratorName ) {
+
+		var _delegate;
+
+		if ( config.delegate !== undefined ) {
+			_delegate = config.delegate;
+		} else {
+			_delegate = config;
+		}
+
+		/**
+		 * Read-only getter.
+		 * <p>
+		 * Dangerous to add a public 'delegate' property, because can conflict
+		 * with 'config.delegate'.
+		 */
+
+		decorator.getDelegate = function() {
+
+			return _delegate;
+		};
+
+		decorator.onStartBuild = function( mw ) {
+
+			if ( decorator.getDelegate().onStartBuild !== undefined ) {
+				decorator.getDelegate().onStartBuild( mw );
+			}
+		};
+
+		decorator.startContainerLayout = function( container, mw ) {
+
+			container[decoratorName] = {};
+
+			if ( decorator.getDelegate().startContainerLayout !== undefined ) {
+				decorator.getDelegate().startContainerLayout( container, mw );
+			}
+		};
+
+		decorator.endContainerLayout = function( container, mw ) {
+
+			if ( decorator.getDelegate().endContainerLayout !== undefined ) {
+				decorator.getDelegate().endContainerLayout( container, mw );
+			}
+
+			container[decoratorName] = {};
+		};
+
+		decorator.onEndBuild = function( mw ) {
+
+			if ( decorator.getDelegate().onEndBuild !== undefined ) {
+				decorator.getDelegate().onEndBuild( mw );
+			}
+		};
+	};
+
+	/**
+	 * Augment the given 'decorator' with methods suitable for making flat (as
+	 * opposed to nested) section separator LayoutDecorators.
+	 * <p>
+	 * This includes an implementation of the <tt>layoutWidget</tt> method and
+	 * a declaration of a <tt>addSectionWidget</tt> method.
+	 */
+
+	metawidget.layout.createFlatSectionLayoutDecorator = function( config, decorator, decoratorName ) {
+
+		if ( this instanceof metawidget.layout.createFlatSectionLayoutDecorator ) {
+			throw new Error( 'Function called as a Constructor' );
+		}
+
+		metawidget.layout._createSectionLayoutDecorator( config, decorator, decoratorName );
+
+		decorator.layoutWidget = function( widget, elementName, attributes, container, mw ) {
+
+			var section;
+
+			// If our delegate is itself a NestedSectionLayoutDecorator, strip
+			// the section
+
+			if ( decorator.getDelegate().nestedSectionLayoutDecorator === true ) {
+
+				// Stay where we are?
+
+				section = metawidget.util.stripSection( attributes );
+
+				if ( section === undefined || section === container[decoratorName].currentSection ) {
+					return decorator.getDelegate().layoutWidget( widget, elementName, attributes, container, mw );
+				}
+
+				// End nested LayoutDecorator's current section
+
+				if ( container[decoratorName].currentSection !== undefined ) {
+					decorator.getDelegate().endContainerLayout( container, mw );
+				}
+
+				container[decoratorName].currentSection = section;
+
+				// Add a heading
+
+				if ( section !== '' ) {
+					decorator.addSectionWidget( section, 0, attributes, container, mw );
+				}
+			} else {
+
+				// Stay where we are?
+
+				if ( attributes.section === undefined || attributes.section === container[decoratorName].currentSection ) {
+					return decorator.getDelegate().layoutWidget( widget, elementName, attributes, container, mw );
+				}
+
+				// For each of the new sections...
+
+				var sections = attributes.section;
+
+				if ( ! ( sections instanceof Array ) ) {
+					sections = [ sections ];
+				}
+
+				var currentSections;
+
+				if ( container[decoratorName].currentSection !== undefined ) {
+					currentSections = container[decoratorName].currentSection;
+				} else {
+					currentSections = [];
+				}
+
+				for ( var level = 0; level < sections.length; level++ ) {
+					section = sections[level];
+
+					// ...that are different from our current...
+
+					if ( section === '' ) {
+						continue;
+					}
+
+					if ( level < currentSections.length && section === currentSections[level] ) {
+						continue;
+					}
+
+					// ...add a heading
+					//
+					// Note: we cannot stop/start the delegate layout here. It
+					// is tempting, but remember addSectionWidget needs to use
+					// the delegate. If you stop/add section heading/start the
+					// delegate, who is laying out the section heading?
+
+					decorator.addSectionWidget( section, level, attributes, container, mw );
+				}
+
+				container[decoratorName].currentSection = sections;
+			}
+
+			// Add component as normal
+
+			decorator.getDelegate().layoutWidget( widget, elementName, attributes, container, mw );
+		};
+	};
+
+	/**
+	 * Augment the given 'decorator' with methods suitable for making nested (as
+	 * opposed to flat) section separator LayoutDecorators.
+	 * <p>
+	 * This includes an implementation of the <tt>layoutWidget</tt> method and
+	 * a declaration of a <tt>createSectionWidget</tt> method.
+	 */
+
+	metawidget.layout.createNestedSectionLayoutDecorator = function( config, decorator, decoratorName ) {
+
+		if ( this instanceof metawidget.layout.createNestedSectionLayoutDecorator ) {
+			throw new Error( 'Function called as a Constructor' );
+		}
+
+		metawidget.layout._createSectionLayoutDecorator( config, decorator, decoratorName );
+
+		// Tag this NestedSectionLayoutDecorator so that
+		// FlatSectionLayoutDecorator can recognize it
+
+		decorator.nestedSectionLayoutDecorator = true;
+
+		decorator.layoutWidget = function( widget, elementName, attributes, container, mw ) {
+
+			// Stay where we are?
+
+			var section = metawidget.util.stripSection( attributes );
+
+			if ( section === undefined || section === container[decoratorName].currentSection ) {
+				if ( container[decoratorName].currentSectionWidget ) {
+					return decorator.getDelegate().layoutWidget( widget, elementName, attributes, container[decoratorName].currentSectionWidget, mw );
+				}
+				return decorator.getDelegate().layoutWidget( widget, elementName, attributes, container, mw );
+			}
+
+			// End current section
+
+			if ( container[decoratorName].currentSectionWidget !== undefined ) {
+				decorator.endContainerLayout( container[decoratorName].currentSectionWidget, mw );
+			}
+
+			container[decoratorName].currentSection = section;
+			var previousSectionWidget = container[decoratorName].currentSectionWidget;
+			delete container[decoratorName].currentSectionWidget;
+
+			// No new section?
+
+			if ( section === '' ) {
+				decorator.getDelegate().layoutWidget( widget, elementName, attributes, container, mw );
+				return;
+			}
+
+			// Start new section
+
+			container[decoratorName].currentSectionWidget = decorator.createSectionWidget( previousSectionWidget, section, attributes, container, mw );
+			decorator.startContainerLayout( container[decoratorName].currentSectionWidget, mw );
+
+			// Add component to new section
+
+			decorator.getDelegate().layoutWidget( widget, elementName, attributes, container[decoratorName].currentSectionWidget, mw );
+		};
+
+		var _superEndContainerLayout = decorator.endContainerLayout;
+
+		decorator.endContainerLayout = function( container, mw ) {
+
+			// End hanging layouts
+
+			if ( container[decoratorName].currentSectionWidget !== undefined ) {
+				decorator.endContainerLayout( container[decoratorName].currentSectionWidget, mw );
+			}
+
+			_superEndContainerLayout.call( this, container, mw );
+		};
+	};
+
+	/**
+	 * @class LayoutDecorator to decorate widgets from different sections using
+	 *        an HTML heading tag (i.e. <tt>h1</tt>, <tt>h2</tt> etc).
+	 */
+
+	metawidget.layout.HeadingTagLayoutDecorator = function( config ) {
+
+		if ( ! ( this instanceof metawidget.layout.HeadingTagLayoutDecorator ) ) {
+			throw new Error( 'Constructor called as a function' );
+		}
+
+		var _level = config !== undefined && config.level !== undefined ? config.level : 1;
+		
+		metawidget.layout.createFlatSectionLayoutDecorator( config, this, 'headingTagLayoutDecorator' );
+
+		this.addSectionWidget = function( section, level, attributes, container, mw ) {
+	
+			var h1 = metawidget.util.createElement( mw, 'h' + ( level + _level ) );
+			h1.innerHTML = section;
+	
+			this.getDelegate().layoutWidget( h1, "property", {
+				wide: 'true'
+			}, container, mw );
+		};
+	}
+
+	/**
+	 * @class LayoutDecorator to decorate widgets from different sections using
+	 *        nested <tt>div</tt> tags.
+	 */
+
+	metawidget.layout.DivLayoutDecorator = function( config ) {
+
+		if ( ! ( this instanceof metawidget.layout.DivLayoutDecorator ) ) {
+			throw new Error( 'Constructor called as a function' );
+		}
+
+		var _styleClass = config !== undefined ? config.styleClass : undefined;
+		
+		metawidget.layout.createNestedSectionLayoutDecorator( config, this, 'divLayoutDecorator' );
+		
+		this.createSectionWidget = function( previousSectionWidget, section, attributes, container, mw ) {
+
+			var div = metawidget.util.createElement( mw, 'div' );
+			div.setAttribute( 'title', section );
+			
+			if ( _styleClass !== undefined ) {
+				div.setAttribute( 'class', _styleClass );
+			}
+			
+			this.getDelegate().layoutWidget( div, "property", {
+				wide: 'true'
+			}, container, mw );
+	
+			return div;
+		}
+	};
+} )();
\ No newline at end of file
diff --git a/src/main/webapp/js/3rdparty/metawidget/core/metawidget-utils.js b/src/main/webapp/js/3rdparty/metawidget/core/metawidget-utils.js
new file mode 100644
index 0000000000000000000000000000000000000000..2d10473efebd40abaf65f9e63e23e04246b74b4f
--- /dev/null
+++ b/src/main/webapp/js/3rdparty/metawidget/core/metawidget-utils.js
@@ -0,0 +1,800 @@
+// Metawidget 4.2
+//
+// This file is dual licensed under both the LGPL
+// (http://www.gnu.org/licenses/lgpl-2.1.html) and the EPL
+// (http://www.eclipse.org/org/documents/epl-v10.php). As a
+// recipient of Metawidget, you may choose to receive it under either
+// the LGPL or the EPL.
+//
+// Commercial licenses are also available. See http://metawidget.org
+// for details.
+
+/**
+ * @author <a href="http://kennardconsulting.com">Richard Kennard</a>
+ */
+
+var metawidget = metawidget || {};
+
+( function() {
+
+	'use strict';
+
+	/**
+	 * @namespace Utilities.
+	 */
+
+	metawidget.util = metawidget.util || {};
+
+	/**
+	 * Returns a label for the given set of attributes.
+	 * <p>
+	 * The label is determined using the following algorithm:
+	 * <p>
+	 * <ul>
+	 * <li> if <tt>attributes.title</tt> exists...
+	 * <ul>
+	 * <li>if the given <tt>mw</tt> has a property <tt>l10n</tt>, then
+	 * <tt>attributes.title</tt> is camel-cased and used as a lookup into
+	 * <tt>mw.i10n[camelCasedTitle]</tt>. This means developers can initially
+	 * build their UIs without worrying about localization, then turn it on
+	 * later</li>
+	 * <li>if no such lookup exists (or <tt>mw.l10n</tt> does not exist),
+	 * return <tt>attributes.title</tt>
+	 * </ul>
+	 * </li>
+	 * <li> if <tt>attributes.title</tt> does not exist...
+	 * <ul>
+	 * <li>if the given <tt>mw</tt> has a property <tt>l10n</tt>, then
+	 * <tt>attributes.name</tt> is used as a lookup into
+	 * <tt>mw.i10n[attributes.name]</tt></li>
+	 * <li>if no such lookup exists (or <tt>mw.l10n</tt> does not exist),
+	 * return <tt>attributes.name</tt>
+	 * </ul>
+	 * </li>
+	 * </ul>
+	 * 
+	 * @return the label string. Empty string if no such name. Null if name has
+	 *         been forced to blank (i.e. should be hidden)
+	 * 
+	 */
+
+	metawidget.util.getLabelString = function( attributes, mw ) {
+
+		// Explicit title
+
+		if ( attributes.title !== undefined ) {
+
+			if ( attributes.title === null ) {
+				return null;
+			}
+
+			return metawidget.util.getLocalizedString( attributes.title, mw );
+		}
+
+		// Localize if possible
+
+		var name = attributes.name;
+
+		if ( mw.l10n !== undefined && mw.l10n[name] !== undefined ) {
+			return mw.l10n[name];
+		}
+
+		// Default name, uncamel case
+
+		return metawidget.util.uncamelCase( name );
+	};
+
+	/**
+	 * Uncamel case the given name (e.g. from 'fooBarBaz1' to 'Foo Bar Baz 1').
+	 * Ported from StringUtils.uncamelCase.
+	 */
+
+	metawidget.util.uncamelCase = function( name ) {
+
+		/**
+		 * @returns true if the character is a digit.
+		 */
+
+		function _isDigit( c ) {
+
+			var charCode = c.charCodeAt( 0 );
+			return ( charCode >= 48 && charCode <= 57 );
+		}
+
+		/**
+		 * @returns true if the character is an upper or lower case letter.
+		 */
+
+		function _isLetter( c ) {
+
+			var charCode = c.charCodeAt( 0 );
+			return ( charCode >= 65 && charCode <= 90 ) || ( charCode >= 97 && charCode <= 122 );
+		}
+
+		var uncamelCasedName = '';
+		var first = true;
+		var lastChar = ' ';
+
+		for ( var loop = 0; loop < name.length; loop++ ) {
+
+			// Use 'charAt', not '[]' for IE compatibility
+
+			var c = name.charAt( loop );
+
+			if ( first === true ) {
+				uncamelCasedName += c.toUpperCase();
+				first = false;
+			} else if ( _isUpperCase( c ) && ( !_isUpperCase( lastChar ) || ( loop < name.length - 1 && name[loop + 1] !== ' ' && !_isUpperCase( name[loop + 1] ) ) ) ) {
+				if ( lastChar !== ' ' ) {
+					uncamelCasedName += ' ';
+				}
+
+				// Don't do: if ( loop + 1 < length && !_isUpperCase( chars[loop
+				// + 1] ) ) uncamelCasedName += _toLowerCase( c );
+				//
+				// It's ambiguous if we should lowercase the letter following a
+				// space, but in general it looks nicer most of the time not to.
+				// The exception is 'joining' words such as 'of' in 'Date of
+				// Birth'
+
+				uncamelCasedName += c;
+			} else if ( _isDigit( c ) && _isLetter( lastChar ) && lastChar !== ' ' ) {
+				uncamelCasedName += ' ' + c;
+			} else {
+				uncamelCasedName += c;
+			}
+
+			lastChar = c;
+		}
+
+		return uncamelCasedName;
+	};
+
+	/**
+	 * Localizes the given value.
+	 * <p>
+	 * First, camelCases the given value to create a key. Then looks this key up
+	 * in <tt>mw.l10n</tt>. If it exists, returns the value associated with
+	 * that key. Otherwise, returns the original value.
+	 * <p>
+	 * Clients can either initialize a property called <tt>l10n</tt> on the
+	 * Metawidget, or replace this whole method with their own localization
+	 * approach. In the latter case, <tt>mw.path</tt> may also prove useful.
+	 */
+
+	metawidget.util.getLocalizedString = function( value, mw ) {
+
+		var key = metawidget.util.camelCase( value );
+
+		if ( mw.l10n !== undefined && mw.l10n[key] !== undefined ) {
+			return mw.l10n[key];
+		}
+
+		return value;
+	};
+
+	/**
+	 * Following the rules defined in <tt>capitalize</tt>: "This normally
+	 * means converting the first character from upper case to lower case, but
+	 * in the (unusual) special case when there is more than one character and
+	 * both the first and second characters are upper case, we leave it alone.
+	 * Thus 'FooBah' becomes 'fooBah' and 'X' becomes 'x', but 'URL' stays as
+	 * 'URL'"
+	 */
+
+	metawidget.util.decapitalize = function( name ) {
+
+		if ( name.length === 0 ) {
+			return name;
+		}
+
+		// Nothing to do?
+
+		var firstChar = name.charAt( 0 );
+
+		if ( !_isUpperCase( firstChar ) ) {
+			return name;
+		}
+
+		// Second letter uppercase?
+
+		if ( name.length > 1 ) {
+			if ( _isUpperCase( name.charAt( 1 ) ) ) {
+				return name;
+			}
+		}
+
+		return name.charAt( 0 ).toLowerCase() + name.slice( 1 );
+	};
+
+	/**
+	 * Capitalize by uppercasing the first letter of the given String (e.g. from
+	 * 'fooBarBaz' to 'FooBarBaz').
+	 * <p>
+	 * The rules for capitalizing are not clearly, but we try to make
+	 * <tt>capitalize</tt> the inverse of <tt>decapitalize</tt> (this
+	 * includes the 'second character' clause). For example, in Eclipse if you
+	 * define a property 'aB123' and then 'generate getters' Eclipse will
+	 * generate a method called 'getaB123' <em>not</em> 'getAB123'. See:
+	 * https://community.jboss.org/thread/203202?start=0&tstart=0
+	 */
+
+	metawidget.util.capitalize = function( name ) {
+
+		if ( name.length === 0 ) {
+			return name;
+		}
+
+		// Second letter uppercase?
+
+		if ( name.length > 1 ) {
+			if ( _isUpperCase( name.charAt( 1 ) ) ) {
+				return name;
+			}
+		}
+
+		return name.charAt( 0 ).toUpperCase() + name.slice( 1 );
+	};
+
+	/**
+	 * @return true if the value is boolean true or string 'true', but false for
+	 *         any other value (including other JavaScript 'truthy' values)
+	 */
+
+	metawidget.util.isTrueOrTrueString = function( value ) {
+
+		return ( value === 'true' || value === true );
+	};
+
+	/**
+	 * Camel cases the given array of names (e.g. from ['foo','bar','baz'] to
+	 * 'fooBarBaz'). The first name is decapitalized. Subsequent names are
+	 * capitalized.
+	 * <p>
+	 * If <tt>names</tt> is not an array, first calls
+	 * <tt>names.split( ' ' )</tt>.
+	 * 
+	 * @return the camel cased name. Or an empty string if no name
+	 */
+
+	metawidget.util.camelCase = function( names ) {
+
+		if ( ! ( names instanceof Array ) ) {
+			names = names.split( ' ' );
+		}
+
+		var toString = '';
+		var length = names.length;
+
+		if ( length > 0 ) {
+			toString += metawidget.util.decapitalize( names[0] );
+		}
+
+		for ( var loop = 1; loop < length; loop++ ) {
+			toString += metawidget.util.capitalize( names[loop] );
+		}
+
+		return toString;
+	};
+
+	metawidget.util.fillString = function( repeat, times ) {
+
+		// From:
+		// http://stackoverflow.com/questions/202605/repeat-string-javascript
+
+		var toReturn = '';
+
+		for ( ;; ) {
+
+			if ( times & 1 ) {
+				toReturn += repeat;
+			}
+
+			times >>= 1;
+
+			if ( times ) {
+				repeat += repeat;
+			} else {
+				break;
+			}
+		}
+
+		return toReturn;
+	};
+
+	metawidget.util.lookupEnumTitle = function( value, anEnum, enumTitles ) {
+
+		// Locate the value within the enums (if there)...
+
+		var indexOf = anEnum.indexOf( value );
+
+		if ( indexOf === -1 || indexOf >= enumTitles.length ) {
+
+			// ...(cope with Java's UiLookup only supporting strings)...
+
+			indexOf = anEnum.indexOf( '' + value );
+
+			if ( indexOf === -1 || indexOf >= enumTitles.length ) {
+				return value;
+			}
+		}
+
+		// ...and return its equivalent title (if any)
+
+		return enumTitles[indexOf];
+	};
+
+	/**
+	 * Gets a camelCased id based on the given attributes.name and the given
+	 * mw.path.
+	 */
+
+	metawidget.util.getId = function( elementName, attributes, mw ) {
+
+		if ( mw.path !== undefined ) {
+			var splitPath = mw.path.split( '.' );
+
+			if ( splitPath[0] === 'object' ) {
+				splitPath = splitPath.slice( 1 );
+			}
+
+			if ( attributes.name && elementName !== 'entity' ) {
+				splitPath.push( attributes.name );
+			} else if ( splitPath.length === 0 ) {
+				return undefined;
+			}
+
+			var id = metawidget.util.camelCase( splitPath );
+
+			// Strip array qualifiers
+
+			id = id.replace( /[\[\]]/g, '' );
+
+			return id;
+		}
+
+		if ( attributes !== undefined ) {
+			return attributes.name;
+		}
+	};
+
+	/**
+	 * Returns true if the given node has child <em>elements</em>. That is,
+	 * their <tt>nodeType === 1</tt>. Ignores other sorts of child nodes,
+	 * such as text nodes.
+	 */
+
+	metawidget.util.hasChildElements = function( node ) {
+
+		var childNodes = node.childNodes;
+
+		for ( var loop = 0, length = childNodes.length; loop < length; loop++ ) {
+
+			if ( childNodes[loop].nodeType === 1 ) {
+				return true;
+			}
+		}
+
+		return false;
+	};
+
+	/**
+	 * @true if the given attributes define 'large' or 'wide'.
+	 */
+
+	metawidget.util.isSpanAllColumns = function( attributes ) {
+
+		if ( attributes === undefined ) {
+			return false;
+		}
+
+		if ( metawidget.util.isTrueOrTrueString( attributes.large ) ) {
+			return true;
+		}
+
+		return metawidget.util.isTrueOrTrueString( attributes.wide );
+	};
+
+	/**
+	 * Splits the given path into its type and an array of names (e.g.
+	 * 'foo.bar['baz']' into type 'foo' and names ['bar','baz']).
+	 * 
+	 * @returns an object with properties 'type' and 'names' (provided there is
+	 *          at least 1 name)
+	 */
+
+	metawidget.util.splitPath = function( path ) {
+
+		var splitPath = {};
+
+		if ( path !== undefined ) {
+
+			// Match at every '.' and '[' boundary
+
+			var pathArray = path.match( /([^\.\[\]]*)/g );
+			splitPath.type = pathArray[0];
+
+			for ( var loop = 1, length = pathArray.length; loop < length; loop++ ) {
+
+				// Ignore empty matches
+
+				if ( pathArray[loop] === '' ) {
+					continue;
+				}
+
+				if ( splitPath.names === undefined ) {
+					splitPath.names = [];
+				}
+
+				// Strip surrounding spaces and quotes (eg. foo[ 'bar' ])
+
+				var stripQuotes = pathArray[loop].match( /^(?:\s*(?:\'|\"))([^\']*)(?:(?:\'|\")\s*)$/ );
+
+				if ( stripQuotes !== null && stripQuotes[1] !== undefined ) {
+					pathArray[loop] = stripQuotes[1];
+				}
+
+				splitPath.names.push( pathArray[loop] );
+			}
+		}
+
+		return splitPath;
+	};
+
+	/**
+	 * Appends the 'path' property from the given Metawidget to the 'name'
+	 * property in the given attributes.
+	 */
+
+	metawidget.util.appendPath = function( attributes, mw ) {
+
+		if ( mw.path !== undefined ) {
+			return metawidget.util.appendPathWithName( mw.path, attributes );
+		}
+
+		if ( mw.toInspect !== undefined ) {
+			return metawidget.util.appendPathWithName( typeof ( mw.toInspect ), attributes );
+		}
+
+		return metawidget.util.appendPathWithName( 'object', attributes );
+	};
+
+	/**
+	 * Returns the given path appended with the given name (e.g. 'foo' with
+	 * 'bar' becomes 'foo.bar'). Supports nameIncludesSeparator. Also supports
+	 * using bracket notation if the name contains illegal characters (e.g.
+	 * 'foo['bar bar']')
+	 */
+
+	metawidget.util.appendPathWithName = function( path, attributes ) {
+
+		var name = attributes.name;
+
+		// In general, add a dot before the attributes.name. However support
+		// nameIncludesSeparator for alwaysUseNestedMetawidgetInTables
+
+		if ( metawidget.util.isTrueOrTrueString( attributes.nameIncludesSeparator ) ) {
+			return path + name;
+		}
+
+		if ( name.indexOf( '.' ) !== -1 || name.indexOf( '\'' ) !== -1 || name.indexOf( '"' ) !== -1 || name.indexOf( ' ' ) !== -1 ) {
+			return path + '[\'' + name.replace( '\'', '\\\'' ) + '\']';
+		}
+
+		return path + '.' + name;
+	};
+
+	/**
+	 * Traverses the given 'toInspect' along properties defined by the array of
+	 * 'names'.
+	 * 
+	 * @param toInspect
+	 *            object to traverse
+	 * @param names
+	 *            array of propery names to traverse along
+	 */
+
+	metawidget.util.traversePath = function( toInspect, names ) {
+
+		if ( toInspect === undefined ) {
+			return undefined;
+		}
+
+		if ( names !== undefined ) {
+
+			// Sanity check against passing a single string
+
+			if ( ! ( names instanceof Array ) ) {
+				throw new Error( "Expected array of names" );
+			}
+
+			for ( var loop = 0, length = names.length; loop < length; loop++ ) {
+
+				toInspect = toInspect[names[loop]];
+
+				// We don't need to worry about array indexes here: they should
+				// have been parsed out by splitPath
+
+				if ( toInspect === undefined ) {
+					return undefined;
+				}
+			}
+		}
+
+		return toInspect;
+	};
+
+	/**
+	 * Return an array of the given inspection result's properties, sorted by
+	 * 'propertyOrder' (if any).
+	 * <p>
+	 * See: https://github.com/json-stylesheet/json-stylesheet/issues/1
+	 * https://github.com/json-schema/json-schema/issues/87
+	 */
+
+	metawidget.util.getSortedInspectionResultProperties = function( inspectionResult ) {
+
+		// Extract the given inspection result's properties into an array...
+
+		var sortedProperties = [];
+
+		if ( inspectionResult !== undefined ) {
+
+			for ( var propertyName in inspectionResult.properties ) {
+
+				var properties = inspectionResult.properties[propertyName];
+				sortedProperties.push( properties );
+
+				properties.name = propertyName;
+				properties._syntheticOrder = sortedProperties.length;
+			}
+
+			// ...sort the array...
+
+			sortedProperties.sort( function( a, b ) {
+
+				if ( a.propertyOrder === undefined ) {
+					if ( b.propertyOrder === undefined ) {
+						return ( a._syntheticOrder - b._syntheticOrder );
+					}
+					return 1;
+				}
+
+				if ( b.propertyOrder === undefined ) {
+					return -1;
+				}
+
+				var diff = ( a.propertyOrder - b.propertyOrder );
+
+				if ( diff === 0 ) {
+					return ( a._syntheticOrder - b._syntheticOrder );
+				}
+
+				return diff;
+			} );
+		}
+
+		// ...and return it
+
+		return sortedProperties;
+	};
+
+	/**
+	 * Combines the given first inspection result with the given second
+	 * inspection result.
+	 * <p>
+	 * Inspection results are expected to be JSON Schema (v3) objects. They are
+	 * combined based on their property name. If no elements match, new
+	 * properties are appended.
+	 */
+
+	metawidget.util.combineInspectionResults = function( existingInspectionResult, newInspectionResult ) {
+
+		// Inspector may return undefined
+
+		if ( newInspectionResult === undefined ) {
+			return;
+		}
+
+		// Combine based on propertyName
+
+		_copyPrimitives( newInspectionResult, existingInspectionResult );
+
+		if ( newInspectionResult.properties === undefined ) {
+			return;
+		}
+
+		existingInspectionResult.properties = existingInspectionResult.properties || {};
+
+		for ( var propertyName in newInspectionResult.properties ) {
+
+			existingInspectionResult.properties[propertyName] = existingInspectionResult.properties[propertyName] || {};
+			_copyPrimitives( newInspectionResult.properties[propertyName], existingInspectionResult.properties[propertyName] );
+		}
+
+		//
+		// Private methods
+		//
+
+		function _copyPrimitives( from, to ) {
+
+			for ( var propertyName in from ) {
+
+				var propertyValue = from[propertyName];
+
+				if ( propertyValue instanceof Array ) {
+					to[propertyName] = propertyValue.slice( 0 );
+					continue;
+				}
+
+				if ( propertyValue instanceof Object ) {
+					continue;
+				}
+
+				to[propertyName] = from[propertyName];
+			}
+		}
+	};
+
+	/**
+	 * Strips the first section off the section attribute (if any).
+	 */
+
+	metawidget.util.stripSection = function( attributes ) {
+
+		var section = attributes.section;
+
+		// (undefined means 'no change to current section')
+
+		if ( section === undefined ) {
+			return undefined;
+		}
+
+		if ( ! ( section instanceof Array ) ) {
+			delete attributes.section;
+			return section;
+		}
+
+		switch ( section.length ) {
+
+			// (empty String means 'end current section')
+			case 0:
+				delete attributes.section;
+				return '';
+
+			case 1:
+				delete attributes.section;
+				return section[0];
+
+			default:
+				attributes.section = section.slice( 1 );
+				return section[0];
+		}
+	};
+
+	/**
+	 * Sets the given 'toAppend' to the given widget's 'attributeName'. If the
+	 * given widget already has a value for 'attributeName', appends a space and
+	 * then adds 'toAppend'.
+	 * 
+	 * @param separator
+	 *            separator to use (defaults to a space)
+	 */
+
+	metawidget.util.appendToAttribute = function( widget, attributeName, toAppend, separator ) {
+
+		var existingAttribute = widget.getAttribute( attributeName );
+
+		if ( separator === undefined ) {
+			separator = ' ';
+		}
+
+		if ( existingAttribute === null ) {
+			widget.setAttribute( attributeName, toAppend );
+			return;
+		}
+
+		// IE compatibility (convert DispHTMLStyle to a string)
+
+		if ( existingAttribute.toString !== undefined ) {
+			existingAttribute = existingAttribute.toString();
+		}
+
+		if ( existingAttribute !== toAppend && existingAttribute.indexOf( toAppend + separator ) === -1 && existingAttribute.indexOf( separator + toAppend ) === -1 ) {
+			widget.setAttribute( attributeName, existingAttribute + separator + toAppend );
+		}
+	};
+
+	/**
+	 * Creates an element by calling <tt>ownerDocument</tt> rather than simply
+	 * <tt>document</tt>. This stops us relying on a global <tt>document</tt>
+	 * variable.
+	 */
+
+	metawidget.util.createElement = function( mw, element ) {
+
+		// Explicitly call toUpperCase, as IE8 doesn't appear to do this for
+		// non-HTML4 tags (like 'output')
+
+		if ( mw.ownerDocument !== undefined ) {
+			return mw.ownerDocument.createElement( element.toUpperCase() );
+		}
+
+		return mw.getElement().ownerDocument.createElement( element.toUpperCase() );
+	};
+
+	/**
+	 * Creates a text node by calling <tt>ownerDocument</tt> rather than
+	 * simply <tt>document</tt>. This stops us relying on a global
+	 * <tt>document</tt> variable.
+	 */
+
+	metawidget.util.createTextNode = function( mw, text ) {
+
+		if ( mw.ownerDocument !== undefined ) {
+			return mw.ownerDocument.createTextNode( text );
+		}
+
+		return mw.getElement().ownerDocument.createTextNode( text );
+	};
+
+	/**
+	 * Creates an event by calling <tt>ownerDocument</tt> rather than simply
+	 * <tt>document</tt>. This stops us relying on a global <tt>document</tt>
+	 * variable.
+	 */
+
+	metawidget.util.createEvent = function( mw, name ) {
+
+		var event;
+
+		if ( mw.ownerDocument !== undefined ) {
+			event = mw.ownerDocument.createEvent( 'Event' );
+		} else {
+			event = mw.getElement().ownerDocument.createEvent( 'Event' );
+		}
+
+		event.initEvent( name, true, true );
+
+		return event;
+	};
+
+	/**
+	 * Finds the indexOf the given item in the given array.
+	 * 
+	 * @return -1 if either array or item are undefined, otherwise indexOf
+	 */
+
+	metawidget.util.niceIndexOf = function( array, item ) {
+
+		if ( array === undefined || item === undefined ) {
+			return -1;
+		}
+
+		return array.indexOf( item );
+	}
+
+	/**
+	 * Backward compatibility for IE.
+	 */
+
+	metawidget.util.hasAttribute = function( element, attribute ) {
+
+		if ( element.hasAttribute !== undefined ) {
+			return element.hasAttribute( attribute );
+		}
+
+		return ( element.getAttribute( attribute ) !== null );
+	}
+
+	//
+	// Private methods
+	//
+
+	function _isUpperCase( c ) {
+
+		var charCode = c.charCodeAt( 0 );
+		return ( charCode >= 65 && charCode <= 90 );
+	}
+
+} )();
diff --git a/src/main/webapp/js/3rdparty/metawidget/core/metawidget-webcomponent.js b/src/main/webapp/js/3rdparty/metawidget/core/metawidget-webcomponent.js
new file mode 100644
index 0000000000000000000000000000000000000000..62298a6ab7518880c802992c1cefd9dd66ad8648
--- /dev/null
+++ b/src/main/webapp/js/3rdparty/metawidget/core/metawidget-webcomponent.js
@@ -0,0 +1,254 @@
+// Metawidget 4.2
+//
+// This file is dual licensed under both the LGPL
+// (http://www.gnu.org/licenses/lgpl-2.1.html) and the EPL
+// (http://www.eclipse.org/org/documents/epl-v10.php). As a
+// recipient of Metawidget, you may choose to receive it under either
+// the LGPL or the EPL.
+//
+// Commercial licenses are also available. See http://metawidget.org
+// for details.
+
+/**
+ * Web Component wrapper for Metawidget.
+ * 
+ * @author <a href="http://kennardconsulting.com">Richard Kennard</a>
+ */
+
+var metawidget = metawidget || {};
+
+( function( globalScope ) {
+
+	'use strict';
+
+	/**
+	 * Use the value of the given HTML 5 attribute to lookup an object in the
+	 * global scope. This includes traversing simple namespace paths such as
+	 * 'foo.bar'
+	 */
+
+	function _lookupObject( attributeName ) {
+
+		var attributeValue = this.getAttribute( attributeName );
+
+		if ( attributeValue === null ) {
+			return;
+		}
+
+		var typeAndNames = metawidget.util.splitPath( attributeValue );
+
+		if ( typeAndNames === undefined ) {
+			return;
+		}
+
+		var lookup = globalScope[typeAndNames.type];
+		return metawidget.util.traversePath( lookup, typeAndNames.names );
+	}
+
+	if ( globalScope.document !== undefined && globalScope.document.registerElement !== undefined ) {
+
+		var metawidgetPrototype = Object.create( HTMLElement.prototype );
+
+		/**
+		 * Upon attachedCallback, initialize an internal metawidget.Metawidget
+		 * object using the current 'config' attribute (if any).
+		 * <p>
+		 * During initialization, a Metawidget create a shadow root so this must
+		 * be called after the document is ready.
+		 */
+
+		metawidgetPrototype.attachedCallback = function() {
+
+			// First time in, create a shadow root. This allows us to preserve
+			// our original override nodes (if any)
+
+			var shadowRoot = this.createShadowRoot();
+
+			// Pipeline (private)
+
+			this._pipeline = new metawidget.Pipeline( shadowRoot );
+
+			// Configure defaults
+
+			this._pipeline.inspector = new metawidget.inspector.PropertyTypeInspector();
+			this._pipeline.widgetBuilder = new metawidget.widgetbuilder.CompositeWidgetBuilder( [ new metawidget.widgetbuilder.OverriddenWidgetBuilder(),
+					new metawidget.widgetbuilder.ReadOnlyWidgetBuilder(), new metawidget.widgetbuilder.HtmlWidgetBuilder() ] );
+			this._pipeline.widgetProcessors = [ new metawidget.widgetprocessor.IdProcessor(), new metawidget.widgetprocessor.RequiredAttributeProcessor(),
+					new metawidget.widgetprocessor.PlaceholderAttributeProcessor(), new metawidget.widgetprocessor.DisabledAttributeProcessor(),
+					new metawidget.widgetprocessor.SimpleBindingProcessor() ];
+			this._pipeline.layout = new metawidget.layout.HeadingTagLayoutDecorator( new metawidget.layout.TableLayout() );
+			this._pipeline.configure( [ _lookupObject.call( this, 'config' ), this.config ] );
+
+			this.buildWidgets();
+		};
+
+		/**
+		 * If 'path', 'readonly' or 'config' are updated, rebuild the
+		 * Metawidget.
+		 */
+
+		metawidgetPrototype.attributeChangedCallback = function( attrName, oldVal, newVal ) {
+
+			if ( this._pipeline === undefined ) {
+				return;
+			}
+
+			switch ( attrName ) {
+				case 'path':
+					this.buildWidgets();
+					break;
+				case 'readonly':
+					this.buildWidgets();
+					break;
+				case 'config':
+					this._pipeline.configure( _lookupObject.call( this, 'config' ) );
+					break;
+			}
+		};
+
+		/**
+		 * Clear all child elements from the shadow root.
+		 */
+
+		metawidgetPrototype.clearWidgets = function() {
+
+			while ( this.shadowRoot.childNodes.length > 0 ) {
+				this.shadowRoot.removeChild( this.shadowRoot.childNodes[0] );
+			}
+		};
+
+		/**
+		 * Rebuild the Metawidget, using the value of the current 'path'
+		 * attribute.
+		 * 
+		 * @param inspectionResult
+		 *            optional inspectionResult to use
+		 */
+
+		metawidgetPrototype.buildWidgets = function( inspectionResult ) {
+
+			// Take a copy of the original nodes. These may be inserted into the
+			// shadow DOM if the WidgetBuilders/Layouts wish
+
+			this.overriddenNodes = [];
+
+			for ( var loop = 0, length = this.childNodes.length; loop < length; loop++ ) {
+				if ( this.childNodes[loop].nodeType === 1 ) {
+					this.overriddenNodes.push( this.childNodes[loop].cloneNode( true ) );
+				}
+			}
+
+			// Traverse and build
+
+			if ( this.getAttribute( 'path' ) !== null ) {
+
+				this.path = this.getAttribute( 'path' );
+				this.readOnly = metawidget.util.isTrueOrTrueString( this.getAttribute( 'readonly' ) );
+
+				// Inspect (if necessary)
+
+				if ( inspectionResult === undefined ) {
+
+					// Safeguard against improperly implementing:
+					// http://blog.kennardconsulting.com/2013/02/metawidget-and-rest.html
+
+					if ( arguments.length > 0 ) {
+						throw new Error( "Calling buildWidgets( undefined ) may cause infinite loop. Check your argument, or pass no arguments instead" );
+					}
+
+					var splitPath = metawidget.util.splitPath( this.path );
+					this.toInspect = globalScope[splitPath.type];
+
+					inspectionResult = this._pipeline.inspect( this.toInspect, splitPath.type, splitPath.names, this );
+				}
+			}
+
+			// Build widgets
+
+			this._pipeline.buildWidgets( inspectionResult, this );
+
+			// Note: we don't attempt to use Object.observe on this.toInspect,
+			// at least not by default (clients could observe and call
+			// buildWidgets if they want). AngularJS Metawidget does this, but
+			// in Angular all sub-widgets are 2-way bound by default, so you
+			// never risk losing data when you rebuild. In Web Components,
+			// however, sub-widget values are only saved when clients call
+			// save()
+		};
+
+		/**
+		 * Returns a nested version of this same Metawidget, using the given
+		 * attributes.
+		 * <p>
+		 * Subclasses should override this method to use their preferred widget
+		 * creation methodology.
+		 */
+
+		metawidgetPrototype.buildNestedMetawidget = function( attributes, config ) {
+
+			var nestedMetawidget = metawidget.util.createElement( this, 'x-metawidget' );
+
+			// Wire up getMetawidget manually, because shadowRoot is not
+			// initialized until attachedCallback. This is important for
+			// SimpleBindingProcessor and nested Metawidgets
+
+			nestedMetawidget.getMetawidget = function() {
+
+				return nestedMetawidget;
+			};
+
+			// Duck-type our 'pipeline' as the 'config' of the nested
+			// Metawidget. This neatly passes everything down, including a
+			// decremented 'maximumInspectionDepth'
+
+			nestedMetawidget.setAttribute( 'path', metawidget.util.appendPath( attributes, this ) );
+			nestedMetawidget.setAttribute( 'readonly', this.readOnly || metawidget.util.isTrueOrTrueString( attributes.readOnly ) );
+			nestedMetawidget.config = this._pipeline;
+
+			return nestedMetawidget;
+		};
+
+		/**
+		 * Save the contents of the Metawidget using a SimpleBindingProcessor.
+		 * <p>
+		 * This is a convenience method. To access other Metawidget APIs,
+		 * clients can use the 'getWidgetProcessor' method
+		 * 
+		 * @returns true if the 'toInspect' was updated (i.e. is dirty)
+		 */
+
+		metawidgetPrototype.save = function() {
+
+			return this.getWidgetProcessor( function( widgetProcessor ) {
+
+				return widgetProcessor instanceof metawidget.widgetprocessor.SimpleBindingProcessor;
+			} ).save( this );
+		};
+
+		/**
+		 * Useful for WidgetBuilders to perform nested inspections (eg. for
+		 * Collections).
+		 */
+
+		metawidgetPrototype.inspect = function( toInspect, type, names ) {
+
+			return this._pipeline.inspect( toInspect, type, names, this );
+		};
+
+		metawidgetPrototype.getWidgetProcessor = function( testInstanceOf ) {
+
+			return this._pipeline.getWidgetProcessor( testInstanceOf );
+		};
+
+		metawidgetPrototype.setLayout = function( layout ) {
+
+			this._pipeline.layout = layout;
+		};
+
+		// Register Metawidget as a Web Component
+
+		globalScope.document.registerElement( 'x-metawidget', {
+			prototype: metawidgetPrototype
+		} );
+	}
+} )( this );
diff --git a/src/main/webapp/js/3rdparty/metawidget/core/metawidget-widgetbuilders.js b/src/main/webapp/js/3rdparty/metawidget/core/metawidget-widgetbuilders.js
new file mode 100644
index 0000000000000000000000000000000000000000..4a23f175c37aa8a0bd210db74528d26f66363e8e
--- /dev/null
+++ b/src/main/webapp/js/3rdparty/metawidget/core/metawidget-widgetbuilders.js
@@ -0,0 +1,690 @@
+// Metawidget 4.2
+//
+// This file is dual licensed under both the LGPL
+// (http://www.gnu.org/licenses/lgpl-2.1.html) and the EPL
+// (http://www.eclipse.org/org/documents/epl-v10.php). As a
+// recipient of Metawidget, you may choose to receive it under either
+// the LGPL or the EPL.
+//
+// Commercial licenses are also available. See http://metawidget.org
+// for details.
+
+/**
+ * @author <a href="http://kennardconsulting.com">Richard Kennard</a>
+ */
+
+var metawidget = metawidget || {};
+
+( function() {
+
+	'use strict';
+
+	/**
+	 * @namespace WidgetBuilders.
+	 */
+
+	metawidget.widgetbuilder = metawidget.widgetbuilder || {};
+
+	/**
+	 * @class Delegates widget building to one or more sub-WidgetBuilders.
+	 *        <p>
+	 *        Each sub-WidgetBuilder in the list is invoked, in order, calling
+	 *        its <code>buildWidget</code> method. The first result is
+	 *        returned. If all sub-WidgetBuilders return undefined, undefined is
+	 *        returned (the parent Metawidget will generally instantiate a
+	 *        nested Metawidget in this case).
+	 *        <p>
+	 *        Note: the name <em>Composite</em>WidgetBuilder refers to the
+	 *        Composite design pattern.
+	 */
+
+	metawidget.widgetbuilder.CompositeWidgetBuilder = function( config ) {
+
+		if ( ! ( this instanceof metawidget.widgetbuilder.CompositeWidgetBuilder ) ) {
+			throw new Error( 'Constructor called as a function' );
+		}
+
+		var _widgetBuilders;
+
+		if ( config.widgetBuilders !== undefined ) {
+			_widgetBuilders = config.widgetBuilders.slice( 0 );
+		} else {
+			_widgetBuilders = config.slice( 0 );
+		}
+
+		function _onStartEndBuild( functionName, mw ) {
+
+			for ( var loop = 0, length = _widgetBuilders.length; loop < length; loop++ ) {
+
+				var widgetBuilder = _widgetBuilders[loop];
+
+				if ( widgetBuilder[functionName] !== undefined ) {
+					widgetBuilder[functionName]( mw );
+				}
+			}
+		}
+
+		this.onStartBuild = function( mw ) {
+
+			_onStartEndBuild( 'onStartBuild', mw );
+		};
+
+		this.buildWidget = function( elementName, attributes, mw ) {
+
+			for ( var loop = 0, length = _widgetBuilders.length; loop < length; loop++ ) {
+
+				var widget;
+				var widgetBuilder = _widgetBuilders[loop];
+
+				if ( widgetBuilder.buildWidget !== undefined ) {
+					widget = widgetBuilder.buildWidget( elementName, attributes, mw );
+				} else {
+					widget = widgetBuilder( elementName, attributes, mw );
+				}
+
+				if ( widget !== undefined ) {
+					return widget;
+				}
+			}
+		};
+
+		this.onEndBuild = function( mw ) {
+
+			_onStartEndBuild( 'onEndBuild', mw );
+		};
+	};
+
+	/**
+	 * @class WidgetBuilder to override widgets based on
+	 *        <tt>mw.overriddenNodes</tt>.
+	 *        <p>
+	 *        Widgets are overridden based on id, not name, because name is not
+	 *        legal syntax for many nodes (e.g. <tt>table</tt>).
+	 */
+
+	metawidget.widgetbuilder.OverriddenWidgetBuilder = function() {
+
+		if ( ! ( this instanceof metawidget.widgetbuilder.OverriddenWidgetBuilder ) ) {
+			throw new Error( 'Constructor called as a function' );
+		}
+	};
+
+	metawidget.widgetbuilder.OverriddenWidgetBuilder.prototype.buildWidget = function( elementName, attributes, mw ) {
+
+		if ( mw.overriddenNodes === undefined ) {
+			return;
+		}
+
+		var overrideId = metawidget.util.getId( elementName, attributes, mw );
+
+		for ( var loop = 0, length = mw.overriddenNodes.length; loop < length; loop++ ) {
+
+			var child = mw.overriddenNodes[loop];
+			if ( child.nodeType === 1 && child.getAttribute( 'id' ) === overrideId ) {
+				mw.overriddenNodes.splice( loop, 1 );
+				return child;
+			}
+		}
+	};
+
+	/**
+	 * @class WidgetBuilder for read-only widgets in HTML 5 environments.
+	 */
+
+	metawidget.widgetbuilder.ReadOnlyWidgetBuilder = function() {
+
+		if ( ! ( this instanceof metawidget.widgetbuilder.ReadOnlyWidgetBuilder ) ) {
+			throw new Error( 'Constructor called as a function' );
+		}
+	};
+
+	metawidget.widgetbuilder.ReadOnlyWidgetBuilder.prototype.buildWidget = function( elementName, attributes, mw ) {
+
+		// Not read-only?
+
+		if ( !metawidget.util.isTrueOrTrueString( attributes.readOnly ) ) {
+			return;
+		}
+
+		// Hidden
+
+		if ( metawidget.util.isTrueOrTrueString( attributes.hidden ) || attributes.type === 'function' ) {
+			return metawidget.util.createElement( mw, 'stub' );
+		}
+
+		if ( attributes['enum'] !== undefined || attributes.type === 'string' || attributes.type === 'boolean' || attributes.type === 'number' || attributes.type === 'integer' || attributes.type === 'date'
+				|| attributes.type === 'color' ) {
+			return metawidget.util.createElement( mw, 'output' );
+		}
+
+		// Not simple, but don't expand
+
+		if ( metawidget.util.isTrueOrTrueString( attributes.dontExpand ) ) {
+			return metawidget.util.createElement( mw, 'output' );
+		}
+	};
+
+	/**
+	 * WidgetBuilder for pure JavaScript environments.
+	 * <p>
+	 * Creates native HTML 5 widgets, such as <code>input</code> and
+	 * <code>select</code>, to suit the inspected fields.
+	 * <p>
+	 * This WidgetBuilder can be configured with the following settings:
+	 * <ul>
+	 * <li>alwaysUseNestedMetawidgetInTables - by default, HtmlWidgetBuilder
+	 * will render simple values in tables as read-only labels. It will only
+	 * resort to using nested Metawidgets inside tables if the value is an
+	 * object. However, sometimes using a nested Metawidget is the desired
+	 * behaviour, even for simple values. Setting this flag forces this</li>
+	 * </ul>
+	 */
+
+	metawidget.widgetbuilder.HtmlWidgetBuilder = function( config ) {
+
+		if ( ! ( this instanceof metawidget.widgetbuilder.HtmlWidgetBuilder ) ) {
+			throw new Error( 'Constructor called as a function' );
+		}
+
+		var _alwaysUseNestedMetawidgetInTables = false;
+
+		if ( config !== undefined ) {
+			_alwaysUseNestedMetawidgetInTables = config.alwaysUseNestedMetawidgetInTables;
+		}
+
+		this.buildWidget = function( elementName, attributes, mw ) {
+
+			// Hidden
+
+			if ( metawidget.util.isTrueOrTrueString( attributes.hidden ) ) {
+				return metawidget.util.createElement( mw, 'stub' );
+			}
+
+			// Support booleans as radio buttons
+
+			if ( attributes.type === 'boolean' && attributes.componentType === 'radio' && attributes['enum'] === undefined ) {
+				attributes['enum'] = [ true, false ];
+				attributes['enumTitles'] = [ 'Yes', 'No' ];
+			}
+
+			// Select box
+
+			if ( attributes['enum'] !== undefined ) {
+
+				var loop, length, option;
+
+				// Multi-select and radio buttons
+
+				if ( attributes.type === 'array' || attributes.componentType !== undefined ) {
+
+					var div = metawidget.util.createElement( mw, 'div' );
+					length = attributes['enum'].length;
+
+					for ( loop = 0; loop < length; loop++ ) {
+
+						// Uses 'implicit label association':
+						// http://www.w3.org/TR/html4/interact/forms.html#h-17.9.1
+
+						var label = metawidget.util.createElement( mw, 'label' );
+						option = metawidget.util.createElement( mw, 'input' );
+
+						if ( attributes.componentType !== undefined ) {
+							label.setAttribute( 'class', attributes.componentType );
+							option.setAttribute( 'type', attributes.componentType );
+						} else {
+							label.setAttribute( 'class', 'checkbox' );
+							option.setAttribute( 'type', 'checkbox' );
+						}
+						option.value = attributes['enum'][loop];
+						label.appendChild( option );
+
+						if ( attributes.enumTitles !== undefined && attributes.enumTitles[loop] !== undefined ) {
+							label.appendChild( metawidget.util.createTextNode( mw, attributes.enumTitles[loop] ) );
+						} else {
+							label.appendChild( metawidget.util.createTextNode( mw, attributes['enum'][loop] ) );
+						}
+
+						div.appendChild( label );
+					}
+
+					return div;
+				}
+
+				// Single-select
+
+				var select = metawidget.util.createElement( mw, 'select' );
+
+				if ( !metawidget.util.isTrueOrTrueString( attributes.required ) ) {
+					select.appendChild( metawidget.util.createElement( mw, 'option' ) );
+				}
+
+				length = attributes['enum'].length;
+
+				for ( loop = 0; loop < length; loop++ ) {
+					option = metawidget.util.createElement( mw, 'option' );
+
+					// HtmlUnit needs an 'option' to have a 'value', even if the
+					// same as the innerHTML
+
+					option.value = attributes['enum'][loop];
+
+					if ( attributes.enumTitles !== undefined && attributes.enumTitles[loop] !== undefined ) {
+						option.innerHTML = attributes.enumTitles[loop];
+					} else {
+						option.innerHTML = attributes['enum'][loop];
+					}
+
+					select.appendChild( option );
+				}
+				return select;
+			}
+
+			// Button
+
+			if ( attributes.type === 'function' ) {
+				var button = metawidget.util.createElement( mw, 'input' );
+				if ( metawidget.util.isTrueOrTrueString( attributes.submit ) ) {
+					button.setAttribute( 'type', 'submit' );
+				} else {
+					button.setAttribute( 'type', 'button' );
+				}
+				button.setAttribute( 'value', metawidget.util.getLabelString( attributes, mw ) );
+				return button;
+			}
+
+			// Number
+
+			if ( attributes.type === 'number' || attributes.type === 'integer' ) {
+
+				if ( attributes.minimum !== undefined && attributes.maximum !== undefined ) {
+					var range = metawidget.util.createElement( mw, 'input' );
+					range.setAttribute( 'type', 'range' );
+					range.setAttribute( 'min', attributes.minimum );
+					range.setAttribute( 'max', attributes.maximum );
+					return range;
+				}
+
+				var number = metawidget.util.createElement( mw, 'input' );
+				number.setAttribute( 'type', 'number' );
+
+				if ( attributes.minimum !== undefined ) {
+					number.setAttribute( 'min', attributes.minimum );
+				} else if ( attributes.maximum !== undefined ) {
+					number.setAttribute( 'max', attributes.maximum );
+				}
+
+				return number;
+			}
+
+			// Boolean
+
+			if ( attributes.type === 'boolean' ) {
+				var checkbox = metawidget.util.createElement( mw, 'input' );
+				checkbox.setAttribute( 'type', 'checkbox' );
+				return checkbox;
+			}
+
+			// Date
+
+			if ( attributes.type === 'date' ) {
+				var date = metawidget.util.createElement( mw, 'input' );
+				date.setAttribute( 'type', 'date' );
+				return date;
+			}
+
+			// Color
+
+			if ( attributes.type === 'color' ) {
+				var color = metawidget.util.createElement( mw, 'input' );
+				color.setAttribute( 'type', 'color' );
+				return color;
+			}
+
+			// String
+
+			if ( attributes.type === 'string' ) {
+
+				if ( metawidget.util.isTrueOrTrueString( attributes.masked ) ) {
+					var password = metawidget.util.createElement( mw, 'input' );
+					password.setAttribute( 'type', 'password' );
+
+					if ( attributes.maxLength !== undefined ) {
+						password.setAttribute( 'maxlength', attributes.maxLength );
+					}
+
+					return password;
+				}
+
+				if ( metawidget.util.isTrueOrTrueString( attributes.large ) ) {
+					return metawidget.util.createElement( mw, 'textarea' );
+				}
+
+				var input = metawidget.util.createElement( mw, 'input' );
+
+				if ( attributes.componentType !== undefined ) {
+					input.setAttribute( 'type', attributes.componentType );
+				} else {
+					input.setAttribute( 'type', 'text' );
+				}
+
+				if ( attributes.maxLength !== undefined ) {
+					input.setAttribute( 'maxlength', attributes.maxLength );
+				}
+
+				return input;
+			}
+
+			// Collection
+
+			if ( attributes.type === 'array' ) {
+				return this.createTable( elementName, attributes, mw );
+			}
+
+			// Not simple, but don't expand
+
+			if ( metawidget.util.isTrueOrTrueString( attributes.dontExpand ) ) {
+				var text = metawidget.util.createElement( mw, 'input' );
+				text.setAttribute( 'type', 'text' );
+				return text;
+			}
+		};
+
+		/**
+		 * Create a table populated with the contents of an array property.
+		 * <p>
+		 * Subclasses may override this method to customize table creation.
+		 * Alternatively, they could override one of the sub-methods
+		 * <tt>addHeaderRow</tt>, <tt>addHeader</tt>, <tt>addRow</tt> or
+		 * <tt>addColumn</tt>.
+		 */
+
+		this.createTable = function( elementName, attributes, mw ) {
+
+			var table = metawidget.util.createElement( mw, 'table' );
+
+			// Inspect the first entry in the array to determine the table
+			// columns. This assumes the array is homogeneous. However because
+			// you can use JsonSchemaInspector as one of your Inspectors, it
+			// doesn't assume the array is populated, nor that the first entry
+			// has values in all fields
+
+			var typeAndNames = metawidget.util.splitPath( mw.path );
+			var toInspect = metawidget.util.traversePath( mw.toInspect, typeAndNames.names );
+
+			if ( typeAndNames.names === undefined ) {
+				typeAndNames.names = [];
+			}
+
+			var value;
+
+			if ( elementName !== 'entity' && toInspect !== undefined ) {
+				value = toInspect[attributes.name];
+				typeAndNames.names.push( attributes.name );
+			} else {
+				value = toInspect;
+			}
+
+			// Push '0' so that object-based inspectors (like
+			// PropertyTypeInspector) will try to look at the first entry.
+			// However this will fail gracefully if the array is empty or
+			// undefined
+
+			typeAndNames.names.push( '0' );
+
+			var inspectionResult = mw.inspect( mw.toInspect, typeAndNames.type, typeAndNames.names );
+
+			if ( inspectionResult !== undefined ) {
+
+				var tbody = metawidget.util.createElement( mw, 'tbody' );
+				var row, rows;
+
+				if ( inspectionResult.properties === undefined ) {
+
+					// Simple, single-column table. It is still useful to pass
+					// 'type', but we must be careful not to pass 'name'.
+
+					table.appendChild( tbody );
+
+					if ( value !== undefined ) {
+						rows = value.length;
+						for ( row = 0; row < rows; row++ ) {
+							this.addRow( tbody, value, row, [ {
+								type: inspectionResult.type
+							} ], elementName, attributes, mw );
+						}
+					}
+
+				} else {
+					var inspectionResultProperties = metawidget.util.getSortedInspectionResultProperties( inspectionResult );
+
+					// Create headers
+
+					var thead = metawidget.util.createElement( mw, 'thead' );
+					table.appendChild( thead );
+
+					var columnAttributes = this.addHeaderRow( thead, inspectionResultProperties, mw );
+
+					// Create footer (optional)
+					
+					var tfoot = metawidget.util.createElement( mw, 'tfoot' );
+					this.addFooterRow( tfoot, columnAttributes );
+					
+					if ( tfoot.childNodes.length > 0 ) {
+						table.appendChild( tfoot );
+					}
+
+					// Create body
+
+					table.appendChild( tbody );
+
+					if ( value !== undefined ) {
+						rows = value.length;
+						for ( row = 0; row < rows; row++ ) {
+							this.addRow( tbody, value, row, columnAttributes, elementName, attributes, mw );
+						}
+					}
+				}
+			}
+
+			return table;
+		};
+
+		/**
+		 * Adds a row to the table header. Subclasses may override this method
+		 * to add additional columns, or suppress the header row.
+		 * 
+		 * @param inspectionResultProperties
+		 *            an array of sorted inspection result properties
+		 * @return array of column attributes. For example, columnAttributes[0]
+		 *         contains an object containing attributes for the first column
+		 */
+
+		this.addHeaderRow = function( thead, inspectionResultProperties, mw ) {
+
+			var tr = metawidget.util.createElement( mw, 'tr' );
+			thead.appendChild( tr );
+
+			var columnAttributes = [];
+
+			for ( var loop = 0, length = inspectionResultProperties.length; loop < length; loop++ ) {
+
+				var columnAttribute = inspectionResultProperties[loop];
+
+				if ( this.addHeader( tr, columnAttribute, mw ) ) {
+					columnAttributes.push( columnAttribute );
+				}
+			}
+
+			return columnAttributes;
+		};
+
+		/**
+		 * Add a header column for the given attributes. Subclasses may override
+		 * this method to suppress certain columns. By default, suppresses
+		 * columns where 'hidden' is true.
+		 * 
+		 * @returns true if a header was added, false otherwise
+		 */
+
+		this.addHeader = function( tr, attributes, mw ) {
+
+			if ( metawidget.util.isTrueOrTrueString( attributes.hidden ) ) {
+				return false;
+			}
+
+			var th = metawidget.util.createElement( mw, 'th' );
+
+			// Support column widths
+
+			var style = '';
+			
+			if ( attributes.columnWidth !== undefined ) {
+				style += 'width:' + attributes.columnWidth + ';';
+			}
+
+			if ( attributes.columnAlign !== undefined ) {
+				style += 'text-align:' + attributes.columnAlign + ';';
+			}
+
+			if ( style !== '' ) {
+				th.setAttribute( 'style', style );
+			}
+
+			if ( attributes.type !== 'function' ) {
+				th.innerHTML = metawidget.util.getLabelString( attributes, mw );
+			}
+
+			tr.appendChild( th );
+
+			return true;
+		};
+
+		/**
+		 * Adds a row to the table body. Subclasses may override this method to
+		 * add additional columns, or suppress the row.
+		 * 
+		 * @param columnAttributesArray
+		 *            array of column attributes. For example,
+		 *            columnAttributesArray[0] contains an object containing
+		 *            columnAttributes for the first column
+		 * @return the added row, or undefined if no row was added. This can be
+		 *         useful for subclasses
+		 */
+
+		this.addRow = function( tbody, value, row, columnAttributesArray, elementName, tableAttributes, mw ) {
+
+			var tr = metawidget.util.createElement( mw, 'tr' );
+			tbody.appendChild( tr );
+
+			for ( var loop = 0, length = columnAttributesArray.length; loop < length; loop++ ) {
+				this.addColumn( tr, value, row, columnAttributesArray[loop], elementName, tableAttributes, mw );
+			}
+
+			return tr;
+		};
+
+		/**
+		 * Add a column to the given row, displaying the given value. Subclasses
+		 * may override this method to modify the column contents (for example,
+		 * to wrap them in an anchor tag).
+		 * 
+		 * @return the added column, or undefined if no column was added. This
+		 *         can be useful for subclasses
+		 */
+
+		this.addColumn = function( tr, value, row, columnAttributes, elementName, tableAttributes, mw ) {
+
+			var td = metawidget.util.createElement( mw, 'td' );
+
+			// Support column widths
+
+			var style = '';
+			
+			if ( columnAttributes.columnWidth !== undefined ) {
+				style += 'width:' + columnAttributes.columnWidth + ';';
+			}
+
+			if ( columnAttributes.columnAlign !== undefined ) {
+				style += 'text-align:' + columnAttributes.columnAlign + ';';
+			}
+
+			if ( style !== '' ) {
+				td.setAttribute( 'style', style );
+			}
+			
+			// Render either top-level value, or a property of that value
+
+			var valueToRender = value[row];
+
+			if ( valueToRender !== undefined && columnAttributes.name !== undefined ) {
+				valueToRender = valueToRender[columnAttributes.name];
+			}
+
+			// Render either nothing, a nested read-only Metawidget, or a
+			// toString()
+
+			if ( columnAttributes.type === undefined || columnAttributes.type === 'array' || columnAttributes.type === 'function' || _alwaysUseNestedMetawidgetInTables === true ) {
+
+				var attributes = {};
+
+				for ( var attributeName in columnAttributes ) {
+					attributes[attributeName] = columnAttributes[attributeName];
+				}
+
+				if ( attributes.name === undefined ) {
+					attributes.name = '[' + row + ']';
+				} else {
+					attributes.name = metawidget.util.appendPathWithName( '[' + row + ']', attributes );
+				}
+
+				attributes.nameIncludesSeparator = true;
+
+				if ( elementName !== 'entity' ) {
+					attributes.name = '.' + metawidget.util.appendPathWithName( tableAttributes.name, attributes );
+				}
+
+				// Allow users to mark the whole table as readOnly
+
+				if ( attributes.readOnly === undefined ) {
+					attributes.readOnly = tableAttributes.readOnly;
+				}
+
+				var nestedMetawidget;
+
+				if ( columnAttributes.type === undefined ) {
+
+					// If type is undefined, we will likely recurse, so use
+					// leading labels
+
+					nestedMetawidget = mw.buildNestedMetawidget( attributes );
+				} else {
+
+					// Render simple types with a simple layout, to avoid a
+					// leading label
+
+					nestedMetawidget = mw.buildNestedMetawidget( attributes, {
+						layout: new metawidget.layout.SimpleLayout()
+					} );
+				}
+
+				// Support SimpleBindingProcessor
+
+				mw.nestedMetawidgets = mw.nestedMetawidgets || [];
+				mw.nestedMetawidgets.push( nestedMetawidget );
+
+				td.appendChild( nestedMetawidget );
+			} else if ( valueToRender !== undefined ) {
+				td.innerHTML = '' + valueToRender;
+			}
+
+			tr.appendChild( td );
+
+			return td;
+		};
+		
+		this.addFooterRow = function( tfoot, columnAttributes ) {
+			
+			// No footer by default
+		};
+	};
+} )();
\ No newline at end of file
diff --git a/src/main/webapp/js/3rdparty/metawidget/core/metawidget-widgetprocessors.js b/src/main/webapp/js/3rdparty/metawidget/core/metawidget-widgetprocessors.js
new file mode 100644
index 0000000000000000000000000000000000000000..996967f8efb1fb52e171ae9774d2da6478776102
--- /dev/null
+++ b/src/main/webapp/js/3rdparty/metawidget/core/metawidget-widgetprocessors.js
@@ -0,0 +1,526 @@
+// Metawidget 4.2
+//
+// This file is dual licensed under both the LGPL
+// (http://www.gnu.org/licenses/lgpl-2.1.html) and the EPL
+// (http://www.eclipse.org/org/documents/epl-v10.php). As a
+// recipient of Metawidget, you may choose to receive it under either
+// the LGPL or the EPL.
+//
+// Commercial licenses are also available. See http://metawidget.org
+// for details.
+
+/**
+ * @author <a href="http://kennardconsulting.com">Richard Kennard</a>
+ */
+
+var metawidget = metawidget || {};
+
+( function() {
+
+	'use strict';
+
+	/**
+	 * @namespace WidgetProcessors.
+	 */
+
+	metawidget.widgetprocessor = metawidget.widgetprocessor || {};
+
+	/**
+	 * @class WidgetProcessor that sets the HTML 'id' attribute.
+	 */
+
+	metawidget.widgetprocessor.IdProcessor = function() {
+
+		if ( ! ( this instanceof metawidget.widgetprocessor.IdProcessor ) ) {
+			throw new Error( 'Constructor called as a function' );
+		}
+	};
+
+	metawidget.widgetprocessor.IdProcessor.prototype.processWidget = function( widget, elementName, attributes, mw ) {
+
+		// Dangerous to reassign an id. For example, some JQuery UI widgets
+		// assign temporary ids when they wrap widgets
+
+		if ( !metawidget.util.hasAttribute( widget, 'id' )) {
+			var id = metawidget.util.getId( elementName, attributes, mw );
+
+			if ( id !== undefined ) {
+				widget.setAttribute( 'id', id );
+			}
+		}
+
+		return widget;
+	};
+
+	/**
+	 * @class WidgetProcessor that sets the HTML 5 'required' attribute.
+	 */
+
+	metawidget.widgetprocessor.RequiredAttributeProcessor = function() {
+
+		if ( ! ( this instanceof metawidget.widgetprocessor.RequiredAttributeProcessor ) ) {
+			throw new Error( 'Constructor called as a function' );
+		}
+	};
+
+	metawidget.widgetprocessor.RequiredAttributeProcessor.prototype.processWidget = function( widget, elementName, attributes ) {
+
+		if ( metawidget.util.isTrueOrTrueString( attributes.required ) ) {
+			widget.setAttribute( 'required', 'required' );
+		}
+
+		return widget;
+	};
+
+	/**
+	 * @class WidgetProcessor that sets the HTML 5 'placeholder' attribute.
+	 */
+
+	metawidget.widgetprocessor.PlaceholderAttributeProcessor = function() {
+
+		if ( ! ( this instanceof metawidget.widgetprocessor.PlaceholderAttributeProcessor ) ) {
+			throw new Error( 'Constructor called as a function' );
+		}
+	};
+
+	metawidget.widgetprocessor.PlaceholderAttributeProcessor.prototype.processWidget = function( widget, elementName, attributes ) {
+
+		if ( attributes.placeholder !== undefined ) {
+			widget.setAttribute( 'placeholder', attributes.placeholder );
+		}
+
+		return widget;
+	};
+
+	/**
+	 * @class WidgetProcessor that sets the HTML 'disabled' attribute.
+	 */
+
+	metawidget.widgetprocessor.DisabledAttributeProcessor = function() {
+
+		if ( ! ( this instanceof metawidget.widgetprocessor.DisabledAttributeProcessor ) ) {
+			throw new Error( 'Constructor called as a function' );
+		}
+	};
+
+	metawidget.widgetprocessor.DisabledAttributeProcessor.prototype.processWidget = function( widget, elementName, attributes ) {
+
+		if ( metawidget.util.isTrueOrTrueString( attributes.disabled ) ) {
+			widget.setAttribute( 'disabled', 'disabled' );
+		}
+
+		return widget;
+	};
+
+	/**
+	 * @class Simple data/action binding implementation. Frameworks that supply
+	 *        their own data-binding mechanisms (such as Angular JS) should
+	 *        override this with their own WidgetProcessor.
+	 */
+
+	metawidget.widgetprocessor.SimpleBindingProcessor = function() {
+
+		if ( ! ( this instanceof metawidget.widgetprocessor.SimpleBindingProcessor ) ) {
+			throw new Error( 'Constructor called as a function' );
+		}
+	};
+
+	metawidget.widgetprocessor.SimpleBindingProcessor.prototype.onStartBuild = function( mw ) {
+
+		mw._simpleBindingProcessor = {};
+	};
+
+	metawidget.widgetprocessor.SimpleBindingProcessor.prototype.processWidget = function( widget, elementName, attributes, mw ) {
+
+		var typeAndNames = metawidget.util.splitPath( mw.path );
+
+		if ( widget.tagName === 'INPUT' && ( widget.getAttribute( 'type' ) === 'button' || widget.getAttribute( 'type' ) === 'submit' ) ) {
+			widget.onclick = function() {
+
+				try {
+					return metawidget.util.traversePath( mw.toInspect, typeAndNames.names )[attributes.name]();
+				} catch ( e ) {
+					if ( alert !== undefined ) {
+						alert( e );
+					} else {
+						throw e;
+					}
+				}
+			};
+
+			return widget;
+		}
+
+		var value;
+
+		if ( elementName === 'entity' ) {
+
+			value = metawidget.util.traversePath( mw.toInspect, typeAndNames.names );
+
+			if ( typeAndNames.names === undefined ) {
+				mw._simpleBindingProcessor.topLevel = true;
+			} else {
+				mw._simpleBindingProcessor.topLevelWithPath = true;
+			}
+		} else {
+			var toInspect = metawidget.util.traversePath( mw.toInspect, typeAndNames.names );
+
+			if ( toInspect !== undefined ) {
+				value = toInspect[attributes.name];
+			} else {
+				value = undefined;
+			}
+		}
+
+		var rememberBinding = this.bindToWidget( widget, value, elementName, attributes, mw );
+
+		if ( rememberBinding === true || widget.getMetawidget !== undefined || widget.nestedMetawidgets !== undefined ) {
+			mw._simpleBindingProcessor.bindings = mw._simpleBindingProcessor.bindings || [];
+			mw._simpleBindingProcessor.bindings[attributes.name] = {
+				widget: widget,
+				elementName: elementName,
+				attributes: attributes
+			};
+		}
+
+		return widget;
+	};
+
+	/**
+	 * Bind the given widget to the given value.
+	 * 
+	 * @return true if this binding should be remembered for when the user calls
+	 *         'save'
+	 */
+
+	metawidget.widgetprocessor.SimpleBindingProcessor.prototype.bindToWidget = function( widget, value, elementName, attributes, mw ) {
+
+		var isBindable = ( widget.tagName === 'INPUT' || widget.tagName === 'SELECT' || widget.tagName === 'TEXTAREA' );
+
+		if ( isBindable === true && metawidget.util.hasAttribute( widget, 'id' )) {
+
+			// Standard HTML needs 'name', not 'id', for binding
+
+			widget.setAttribute( 'name', widget.getAttribute( 'id' ) );
+		}
+
+		var loop, length;
+
+		// Special support for arrays of checkboxes/radio buttons
+
+		if ( attributes['enum'] !== undefined && widget.tagName === 'DIV' ) {
+
+			if ( attributes.type === 'array' || attributes.componentType !== undefined ) {
+
+				isBindable = true;
+
+				length = widget.childNodes.length;
+				for ( loop = 0; loop < length; loop++ ) {
+					var childNode = widget.childNodes[loop];
+					if ( childNode.tagName === 'DIV' ) {
+						childNode = childNode.childNodes[0];
+					}					
+					if ( childNode.tagName === 'LABEL' ) {
+						var inputChildNode = childNode.childNodes[0];
+						if ( inputChildNode.tagName === 'INPUT' ) {
+
+							// Name must be common across group
+
+							inputChildNode.setAttribute( 'name', widget.getAttribute( 'id' ) );
+
+							if ( attributes.type === 'array' ) {
+								inputChildNode.checked = ( value !== undefined && value.indexOf( inputChildNode.value ) !== -1 );
+							} else if ( attributes.type === 'boolean' ) {
+								inputChildNode.checked = ( value === inputChildNode.value || inputChildNode.value === '' + value );
+							} else {
+								inputChildNode.checked = ( value === inputChildNode.value );
+							}
+						}
+					}
+				}
+			}
+		}
+
+		// Check 'not undefined', rather than 'if value', in case value is a
+		// boolean of false
+		//
+		// Note: this is a general convention throughout Metawidget, as
+		// JavaScript has a surprisingly large number of 'falsy' values)
+
+		if ( value !== undefined ) {
+			if ( widget.tagName === 'OUTPUT' || widget.tagName === 'TEXTAREA' ) {
+
+				if ( metawidget.util.isTrueOrTrueString( attributes.masked )) {
+
+					// Special support for masked output
+
+					widget.innerHTML = metawidget.util.fillString( '*', value.length );
+
+				} else if ( attributes.enumTitles !== undefined ) {
+
+					// Special support for enumTitles
+
+					if ( attributes.type === 'array' ) {
+
+						length = value.length;
+						for ( loop = 0; loop < length; loop++ ) {
+
+							if ( loop === 0 ) {
+								widget.innerHTML = '';
+							} else {
+								widget.innerHTML += ', ';
+							}
+
+							widget.innerHTML += metawidget.util.lookupEnumTitle( value[loop], attributes['enum'], attributes.enumTitles );
+						}
+
+					} else {
+						widget.innerHTML = metawidget.util.lookupEnumTitle( value, attributes['enum'], attributes.enumTitles );
+					}
+
+				} else if ( attributes.type === 'boolean' ) {
+
+					// Special support for boolean
+
+					if ( value === true ) {
+						widget.innerHTML = metawidget.util.getLocalizedString( 'Yes', mw );
+					} else if ( value === false ) {
+						widget.innerHTML = metawidget.util.getLocalizedString( 'No', mw );
+					} else {
+						widget.innerHTML = value;
+					}
+
+				} else {
+					widget.innerHTML = value;
+				}
+
+			} else if ( widget.tagName === 'INPUT' && widget.getAttribute( 'type' ) === 'checkbox' ) {
+				widget.checked = value;
+			} else if ( isBindable === true ) {
+				widget.value = value;
+			}
+		}
+
+		return isBindable;
+	};
+
+	/**
+	 * Save the bindings associated with the given Metawidget.
+	 * 
+	 * @return true if data (including data from nested Metawidgets) was
+	 *         actually changed. False otherwise. Can be useful for 'dirty'
+	 *         flags
+	 */
+
+	metawidget.widgetprocessor.SimpleBindingProcessor.prototype.save = function( mw ) {
+
+		var toInspect;
+		var dirty = false;
+
+		// Traverse to the parent...
+
+		var typeAndNames = metawidget.util.splitPath( mw.path );
+
+		if ( typeAndNames.names === undefined ) {
+			toInspect = mw.toInspect;
+		} else {
+			var namesToParent = typeAndNames.names.slice( 0, typeAndNames.names.length - 1 );
+			var parent = metawidget.util.traversePath( mw.toInspect, namesToParent );
+
+			// ...then to the child...
+
+			if ( mw._simpleBindingProcessor.topLevelWithPath === true ) {
+				toInspect = parent;
+			} else {
+				var childName = typeAndNames.names[typeAndNames.names.length - 1];
+				toInspect = parent[childName];
+
+				// ...create the child 'just in time' if necessary...
+
+				if ( toInspect === undefined ) {
+					toInspect = {};
+					parent[childName] = toInspect;
+				}
+			}
+		}
+
+		// ...and populate it
+
+		for ( var name in mw._simpleBindingProcessor.bindings ) {
+
+			var binding = mw._simpleBindingProcessor.bindings[name];
+			var widgetFromBinding = this.getWidgetFromBinding( binding, mw );
+
+			// Support nested Metawidgets
+
+			if ( widgetFromBinding.getMetawidget !== undefined ) {
+				var nestedDirty = this.save( widgetFromBinding.getMetawidget() );
+				
+				if ( nestedDirty === true ) {
+					dirty = true;
+				}
+				
+				continue;
+			}
+
+			// saveFromWidget
+
+			var value = this.saveFromWidget( binding, mw );
+
+			if ( dirty === false && toInspect[name] !== value ) {
+				dirty = true;
+			}
+
+			if ( mw._simpleBindingProcessor.topLevel === true ) {
+				mw.toInspect = value;
+				return dirty;
+			}
+
+			toInspect[name] = value;
+		}
+
+		// Support alwaysUseNestedMetawidgetInTables
+
+		if ( mw.nestedMetawidgets !== undefined ) {
+
+			for ( var loop = 0, length = mw.nestedMetawidgets.length; loop < length; loop++ ) {
+				var nestedDirty = this.save( mw.nestedMetawidgets[loop].getMetawidget() );
+				
+				if ( nestedDirty === true ) {
+					dirty = true;
+				}
+			}
+		}
+
+		return dirty;
+	};
+
+	/**
+	 * @return the given binding's widget value
+	 */
+
+	metawidget.widgetprocessor.SimpleBindingProcessor.prototype.saveFromWidget = function( binding, mw ) {
+
+		var widget = this.getWidgetFromBinding( binding, mw );
+
+		if ( widget.getAttribute( 'type' ) === 'checkbox' ) {
+			return widget.checked;
+		}
+
+		if ( binding.attributes.type === 'integer' ) {
+
+			var parsed = parseInt( widget.value );
+
+			// Avoid pushing back 'NaN'
+
+			if ( isNaN( parsed ) ) {
+				return undefined;
+			}
+
+			return parsed;
+		}
+
+		if ( binding.attributes.type === 'number' ) {
+
+			// parseFloat can parse ints, but parseInt can't parse floats
+
+			var parsed = parseFloat( widget.value );
+
+			// Avoid pushing back 'NaN'
+
+			if ( isNaN( parsed ) ) {
+				return undefined;
+			}
+
+			return parsed;
+		}
+
+		// Support arrays of checkboxes/radio buttons
+
+		if ( binding.attributes['enum'] !== undefined && widget.tagName === 'DIV' ) {
+
+			if ( binding.attributes.type === 'array' || binding.attributes.componentType !== undefined ) {
+
+				var toReturn;
+				for ( var loop = 0, length = widget.childNodes.length; loop < length; loop++ ) {
+					var childNode = widget.childNodes[loop];
+					if ( childNode.tagName === 'DIV' ) {
+						childNode = childNode.childNodes[0];
+					}					
+					if ( childNode.tagName === 'LABEL' ) {
+						var inputChildNode = childNode.childNodes[0];
+						if ( inputChildNode.checked ) {
+
+							if ( binding.attributes.type === 'boolean' ) {
+								return ( inputChildNode.value === true || inputChildNode.value === 'true' );
+							}
+
+							if ( binding.attributes.type !== 'array' ) {
+								return inputChildNode.value;
+							}
+
+							toReturn = toReturn || [];
+							toReturn.push( inputChildNode.value );
+						}
+					}
+				}
+				return toReturn;
+			}
+		}
+
+		// Support non-checkbox booleans (e.g. a select box)
+
+		if ( binding.attributes.type === 'boolean' ) {
+			return ( widget.value === true || widget.value === 'true' );
+		}
+
+		// Avoid pushing back 'null'
+
+		if ( widget.value === '' || widget.value === null ) {
+			return;
+		}
+
+		return widget.value;
+	};
+
+	/**
+	 * Returns the widget associated with the given binding. By default, calls
+	 * <tt>binding.widget</tt>. Subclasses may override this method if their
+	 * framework has swapped out the widget.
+	 */
+
+	metawidget.widgetprocessor.SimpleBindingProcessor.prototype.getWidgetFromBinding = function( binding, mw ) {
+
+		return binding.widget;
+	};
+
+	/**
+	 * Reloads the values in the widgets using the values in the given Object.
+	 * The names of the values in the Object must match the 'name' attribute of
+	 * the widget.
+	 * <p>
+	 * Note this method does not update <tt>mw.toInspect</tt>, nor does it
+	 * save any values back from the widgets. It can be useful for re-populating
+	 * the widgets based on an HTTP request POST-back.
+	 */
+
+	metawidget.widgetprocessor.SimpleBindingProcessor.prototype.reload = function( reloadFrom, mw ) {
+
+		for ( var name in mw._simpleBindingProcessor.bindings ) {
+
+			var binding = mw._simpleBindingProcessor.bindings[name];
+			var widgetFromBinding = this.getWidgetFromBinding( binding, mw );
+
+			if ( widgetFromBinding.getMetawidget !== undefined ) {
+				this.reload( reloadFrom, widgetFromBinding.getMetawidget() );
+				continue;
+			}
+
+			// Use id, not name, to support arrays of checkboxes (id should be
+			// the same as name anyway)
+
+			this.bindToWidget( widgetFromBinding, reloadFrom[widgetFromBinding.getAttribute( 'id' )], binding.elementName, binding.attributes, mw );
+		}
+	};
+
+} )();
diff --git a/src/main/webapp/js/3rdparty/metawidget/core/metawidget.js b/src/main/webapp/js/3rdparty/metawidget/core/metawidget.js
new file mode 100644
index 0000000000000000000000000000000000000000..ad275476b89c7d936b95d94c3d0236066f332109
--- /dev/null
+++ b/src/main/webapp/js/3rdparty/metawidget/core/metawidget.js
@@ -0,0 +1,686 @@
+// Metawidget 4.2
+//
+// This file is dual licensed under both the LGPL
+// (http://www.gnu.org/licenses/lgpl-2.1.html) and the EPL
+// (http://www.eclipse.org/org/documents/epl-v10.php). As a
+// recipient of Metawidget, you may choose to receive it under either
+// the LGPL or the EPL.
+//
+// Commercial licenses are also available. See http://metawidget.org
+// for details.
+
+/**
+ * @namespace Metawidget for pure JavaScript environments.
+ * @author <a href="http://kennardconsulting.com">Richard Kennard</a>
+ */
+
+var metawidget = metawidget || {};
+
+( function() {
+
+	'use strict';
+
+	/**
+	 * Pure JavaScript Metawidget.
+	 * 
+	 * @param element
+	 *            the element to populate with UI components matching the
+	 *            properties of the domain object
+	 * @param config
+	 *            optional configuration object (see
+	 *            metawidget.Pipeline.configure)
+	 * @returns {metawidget.Metawidget}
+	 */
+
+	metawidget.Metawidget = function( element, config ) {
+
+		if ( ! ( this instanceof metawidget.Metawidget ) ) {
+			throw new Error( 'Constructor called as a function' );
+		}
+
+		// Attach ourselves as a property of the tag, rather than try to
+		// 'extend' the built-in HTML tags. This is used by
+		// SimpleBindingProcessor, among others
+
+		var mw = this;
+
+		element.getMetawidget = function() {
+
+			return mw;
+		};
+
+		// Pipeline (private)
+
+		var _pipeline = new metawidget.Pipeline( element );
+
+		// Configure defaults
+
+		_pipeline.inspector = new metawidget.inspector.PropertyTypeInspector();
+		_pipeline.widgetBuilder = new metawidget.widgetbuilder.CompositeWidgetBuilder( [ new metawidget.widgetbuilder.OverriddenWidgetBuilder(), new metawidget.widgetbuilder.ReadOnlyWidgetBuilder(),
+				new metawidget.widgetbuilder.HtmlWidgetBuilder() ] );
+		_pipeline.widgetProcessors = [ new metawidget.widgetprocessor.IdProcessor(), new metawidget.widgetprocessor.RequiredAttributeProcessor(),
+				new metawidget.widgetprocessor.PlaceholderAttributeProcessor(), new metawidget.widgetprocessor.DisabledAttributeProcessor(), new metawidget.widgetprocessor.SimpleBindingProcessor() ];
+		_pipeline.layout = new metawidget.layout.HeadingTagLayoutDecorator( new metawidget.layout.TableLayout() );
+		_pipeline.configure( config );
+
+		// First time in, capture the contents of the Metawidget, if any
+		// (private)
+
+		var _overriddenNodes = [];
+
+		while ( element.childNodes.length > 0 ) {
+			var childNode = element.childNodes[0];
+			element.removeChild( childNode );
+
+			if ( childNode.nodeType === 1 ) {
+				_overriddenNodes.push( childNode );
+			}
+		}
+
+		//
+		// Public methods
+		//
+
+		this.reconfigure = function( config ) {
+
+			return _pipeline.configure( config );
+		};
+
+		/**
+		 * Save the contents of the Metawidget using a SimpleBindingProcessor.
+		 * <p>
+		 * This is a convenience method. To access other Metawidget APIs,
+		 * clients can use the 'getWidgetProcessor' method
+		 * 
+		 * @returns true if the 'toInspect' was updated (i.e. is dirty)
+		 */
+
+		this.save = function() {
+
+			return _pipeline.getWidgetProcessor( function( widgetProcessor ) {
+
+				return widgetProcessor instanceof metawidget.widgetprocessor.SimpleBindingProcessor;
+			} ).save( this );
+		};
+
+		this.getWidgetProcessor = function( testInstanceOf ) {
+
+			return _pipeline.getWidgetProcessor( testInstanceOf );
+		};
+
+		this.setLayout = function( layout ) {
+
+			_pipeline.layout = layout;
+		};
+
+		/**
+		 * Useful for WidgetBuilders to perform nested inspections (eg. for
+		 * Collections).
+		 */
+
+		this.inspect = function( toInspect, type, names ) {
+
+			return _pipeline.inspect( toInspect, type, names, this );
+		};
+
+		this.buildWidgets = function( inspectionResult ) {
+
+			// Defensive copy
+
+			this.overriddenNodes = [];
+
+			for ( var loop = 0, length = _overriddenNodes.length; loop < length; loop++ ) {
+				this.overriddenNodes.push( _overriddenNodes[loop].cloneNode( true ) );
+			}
+
+			// Inspect (if necessary)
+
+			if ( inspectionResult === undefined ) {
+
+				// Safeguard against improperly implementing:
+				// http://blog.kennardconsulting.com/2013/02/metawidget-and-rest.html
+
+				if ( arguments.length > 0 ) {
+					throw new Error( "Calling buildWidgets( undefined ) may cause infinite loop. Check your argument, or pass no arguments instead" );
+				}
+
+				var splitPath = metawidget.util.splitPath( this.path );
+				inspectionResult = _pipeline.inspect( this.toInspect, splitPath.type, splitPath.names, this );
+			}
+
+			// Build widgets
+
+			_pipeline.buildWidgets( inspectionResult, this );
+		};
+
+		/**
+		 * Returns the element this Metawidget is attached to.
+		 */
+
+		this.getElement = function() {
+
+			return _pipeline.element;
+		};
+
+		/**
+		 * Clear all child elements from the Metawidget element.
+		 * <p>
+		 * This implementation uses plain JavaScript <tt>removeChild</tt>,
+		 * which has known problems (on some browsers) leaking event handlers.
+		 * This is not a problem for plain Metawidget, as it doesn't use event
+		 * handlers. However clients that introduce custom widgetprocessors that
+		 * use event handlers may wish to adopt a more robust technology for
+		 * tracking/clearing event handlers (such as JQuery.empty)
+		 */
+
+		this.clearWidgets = function() {
+
+			var element = this.getElement();
+
+			while ( element.childNodes.length > 0 ) {
+				element.removeChild( element.childNodes[0] );
+			}
+		};
+
+		/**
+		 * Returns a nested version of this same Metawidget, using the given
+		 * attributes.
+		 * <p>
+		 * Subclasses should override this method to use their preferred widget
+		 * creation methodology.
+		 */
+
+		this.buildNestedMetawidget = function( attributes, config ) {
+
+			// Create a 'div' not a 'metawidget', because whilst it's up to the
+			// user what they want their top-level element to be, for browser
+			// compatibility we should stick with something benign for nested
+			// elements
+
+			var nestedWidget = metawidget.util.createElement( this, 'div' );
+
+			// Duck-type our 'pipeline' as the 'config' of the nested
+			// Metawidget. This neatly passes everything down, including a
+			// decremented 'maximumInspectionDepth'
+
+			var nestedMetawidget = new metawidget.Metawidget( nestedWidget, [ _pipeline, config ] );
+			nestedMetawidget.toInspect = this.toInspect;
+			nestedMetawidget.path = metawidget.util.appendPath( attributes, this );
+			nestedMetawidget.readOnly = this.readOnly || metawidget.util.isTrueOrTrueString( attributes.readOnly );
+			nestedMetawidget.buildWidgets();
+
+			return nestedWidget;
+		};
+	};
+
+	/**
+	 * @class Convenience implementation for implementing pipelines (see
+	 *        http://metawidget.org/doc/reference/en/html/ch02.html).
+	 *        <p>
+	 *        Specifically, BasePipeline provides support for:
+	 *        </p>
+	 *        <ul>
+	 *        <li>Inspectors, InspectionResultProcessors, WidgetBuilders,
+	 *        WidgetProcessors and Layouts</li>
+	 *        <li>single/compound widgets</li>
+	 *        <li>stubs/stub attributes</li>
+	 *        <li>read-only/active widgets</li>
+	 *        <li>maximum inspection depth</li>
+	 *        </ul>
+	 */
+
+	metawidget.Pipeline = function( element ) {
+
+		if ( ! ( this instanceof metawidget.Pipeline ) ) {
+			throw new Error( 'Constructor called as a function' );
+		}
+
+		this.inspectionResultProcessors = [];
+		this.widgetProcessors = [];
+		this.element = element;
+		this.maximumInspectionDepth = 10;
+	};
+
+	/**
+	 * Configures the pipeline using the given config object.
+	 * <p>
+	 * This method is separate to the constructor, so that subclasses can set
+	 * defaults. The following configuration properties are supported:
+	 * <ul>
+	 * <li>inspector - an Inspector</li>
+	 * <li>inspectionResultProcessors - an array of InspectionResultProcessors</li>
+	 * <li>widgetBuilder - a WidgetBuilder</li>
+	 * <li>widgetProcessors - an array of WidgetProcessors</li>
+	 * <li>layout - a Layout</li>
+	 * </ul>
+	 * 
+	 * @param config
+	 *            the config object to use. This can be an array, in which case
+	 *            multiple configs will be applied (in the order they appear in
+	 *            the array)
+	 */
+
+	metawidget.Pipeline.prototype.configure = function( config ) {
+
+		if ( config === undefined ) {
+			return;
+		}
+
+		// Support arrays of configs
+
+		var loop;
+
+		if ( config instanceof Array ) {
+			for ( loop = 0; loop < config.length; loop++ ) {
+				this.configure( config[loop] );
+			}
+			return;
+		}
+		if ( config.inspector !== undefined ) {
+			this.inspector = config.inspector;
+		}
+		if ( config.inspectionResultProcessors !== undefined ) {
+			this.inspectionResultProcessors = config.inspectionResultProcessors.slice( 0 );
+		}
+
+		// Support prepending/adding to the existing array of
+		// InspectionResultProcessors
+		// (it may be hard for clients to redefine the originals)
+
+		if ( config.prependInspectionResultProcessors !== undefined ) {
+			if ( !( config.prependInspectionResultProcessors instanceof Array )) {
+				config.prependInspectionResultProcessors = [ config.prependInspectionResultProcessors ];
+			}
+			for ( loop = 0; loop < config.prependInspectionResultProcessors.length; loop++ ) {
+				this.inspectionResultProcessors.splice( loop, 0, config.prependInspectionResultProcessors[loop] );
+			}
+		}
+		if ( config.appendInspectionResultProcessors !== undefined ) {
+			if ( !( config.appendInspectionResultProcessors instanceof Array )) {
+				config.appendInspectionResultProcessors = [ config.appendInspectionResultProcessors ];
+			}
+			for ( loop = 0; loop < config.appendInspectionResultProcessors.length; loop++ ) {
+				this.inspectionResultProcessors.push( config.appendInspectionResultProcessors[loop] );
+			}
+		}
+		if ( config.widgetBuilder !== undefined ) {
+			this.widgetBuilder = config.widgetBuilder;
+		}
+		if ( config.widgetProcessors !== undefined ) {
+			this.widgetProcessors = config.widgetProcessors.slice( 0 );
+		}
+
+		// Support prepending/appending to the existing array of
+		// WidgetProcessors
+		// (it may be hard for clients to redefine the originals)
+
+		if ( config.prependWidgetProcessors !== undefined ) {
+			if ( !( config.prependWidgetProcessors instanceof Array )) {
+				config.prependWidgetProcessors = [ config.prependWidgetProcessors ];
+			}
+			for ( loop = 0; loop < config.prependWidgetProcessors.length; loop++ ) {
+				this.widgetProcessors.splice( loop, 0, config.prependWidgetProcessors[loop] );
+			}
+		}
+		if ( config.appendWidgetProcessors !== undefined ) {
+			if ( !( config.appendWidgetProcessors instanceof Array )) {
+				config.appendWidgetProcessors = [ config.appendWidgetProcessors ];
+			}
+			for ( loop = 0; loop < config.appendWidgetProcessors.length; loop++ ) {
+				this.widgetProcessors.push( config.appendWidgetProcessors[loop] );
+			}
+		}
+		if ( config.layout !== undefined ) {
+			this.layout = config.layout;
+		}
+
+		// Safeguard against infinite recursion
+
+		if ( config.maximumInspectionDepth !== undefined ) {
+			this.maximumInspectionDepth = config.maximumInspectionDepth - 1;
+		}
+
+		// CSS support
+
+		if ( config.styleClass !== undefined ) {
+			this.styleClass = config.styleClass;
+			metawidget.util.appendToAttribute( this.element, 'class', config.styleClass );
+		}
+	};
+
+	/**
+	 * Searches the pipeline's current list of WidgetProcessors and matches each
+	 * against the given function
+	 * 
+	 * @param testInstanceOf
+	 *            a function that accepts a WidgetProcessor and will perform an
+	 *            'instanceof' test on it
+	 */
+
+	metawidget.Pipeline.prototype.getWidgetProcessor = function( testInstanceOf ) {
+
+		for ( var loop = 0, length = this.widgetProcessors.length; loop < length; loop++ ) {
+
+			var widgetProcessor = this.widgetProcessors[loop];
+
+			if ( testInstanceOf( widgetProcessor ) ) {
+				return widgetProcessor;
+			}
+		}
+	};
+
+	/**
+	 * Inspect the 'toInspect' according to its 'type' and 'names', and return
+	 * the result as a JSON String.
+	 * <p>
+	 * This method mirrors the <code>Inspector</code> interface. Internally it
+	 * looks up the Inspector to use. It is a useful hook for subclasses wishing
+	 * to inspect different Objects using our same <code>Inspector</code>.
+	 * <p>
+	 * In addition, this method runs the <code>InspectionResultProcessors</code>.
+	 */
+
+	metawidget.Pipeline.prototype.inspect = function( toInspect, type, names, mw ) {
+
+		// Inspector
+
+		var inspectionResult;
+
+		if ( this.inspector.inspect !== undefined ) {
+			inspectionResult = this.inspector.inspect( toInspect, type, names );
+		} else {
+			inspectionResult = this.inspector( toInspect, type, names );
+		}
+
+		// Inspector may return undefined
+
+		if ( inspectionResult === undefined ) {
+			return;
+		}
+
+		// InspectionResultProcessors
+
+		for ( var loop = 0, length = this.inspectionResultProcessors.length; loop < length; loop++ ) {
+
+			var inspectionResultProcessor = this.inspectionResultProcessors[loop];
+
+			if ( inspectionResultProcessor.processInspectionResult !== undefined ) {
+				inspectionResult = inspectionResultProcessor.processInspectionResult( inspectionResult, mw, toInspect, type, names );
+			} else {
+				inspectionResult = inspectionResultProcessor( inspectionResult, mw, toInspect, type, names );
+			}
+
+			// InspectionResultProcessor may return undefined
+
+			if ( inspectionResult === undefined ) {
+				return;
+			}
+		}
+
+		return inspectionResult;
+	};
+
+	/**
+	 * Build widgets from the given JSON inspection result.
+	 * <p>
+	 * Note: the Pipeline expects the JSON to be passed in externally, rather
+	 * than fetching it itself, because some JSON inspections may be
+	 * asynchronous.
+	 * 
+	 * @param inspectionResult
+	 *            array of metadata to base widgets on.
+	 * @param mw
+	 *            Metawidget instance that will be passed down the pipeline
+	 *            (WidgetBuilders, WidgetProcessors etc). Expected to have
+	 *            'toInspect', 'path' and 'readOnly'.
+	 */
+
+	metawidget.Pipeline.prototype.buildWidgets = function( inspectionResult, mw ) {
+
+		// Clear existing contents
+
+		mw.clearWidgets();
+
+		_startBuild( this, mw );
+
+		// Build top-level widget...
+
+		if ( inspectionResult !== undefined ) {
+
+			var copiedAttributes = _forceReadOnly( inspectionResult, mw, 'properties' );
+			var elementName = "entity";
+			var widget = _buildWidget( this, elementName, copiedAttributes, mw );
+
+			if ( widget !== undefined ) {
+
+				widget = _processWidget( this, widget, elementName, copiedAttributes, mw );
+
+				if ( widget !== undefined ) {
+					this.layoutWidget( widget, elementName, copiedAttributes, this.element, mw );
+				}
+
+			} else {
+
+				// ...or try compound widget
+
+				var inspectionResultProperties = metawidget.util.getSortedInspectionResultProperties( inspectionResult );
+
+				for ( var loop = 0, length = inspectionResultProperties.length; loop < length; loop++ ) {
+
+					copiedAttributes = _forceReadOnly( inspectionResultProperties[loop], mw );
+
+					if ( copiedAttributes.type === 'function' ) {
+						elementName = "action";
+					} else {
+						elementName = "property";
+					}
+
+					widget = _buildWidget( this, elementName, copiedAttributes, mw );
+
+					if ( widget === undefined ) {
+
+						if ( this.maximumInspectionDepth <= 0 ) {
+							continue;
+						}
+
+						widget = mw.buildNestedMetawidget( copiedAttributes );
+
+						if ( widget === undefined ) {
+							continue;
+						}
+					}
+
+					widget = _processWidget( this, widget, elementName, copiedAttributes, mw );
+
+					if ( widget !== undefined ) {
+						this.layoutWidget( widget, elementName, copiedAttributes, this.element, mw );
+					}
+				}
+			}
+		}
+
+		// Even if no inspectors match, we still call startBuild()/endBuild()
+		// because you can use a Metawidget purely for layout, with no
+		// inspection
+
+		_endBuild( this, mw );
+
+		// Throw an event for interested parties (such as tests). Does not work
+		// on IE8
+
+		if ( this.element.dispatchEvent !== undefined ) {
+			this.element.dispatchEvent( metawidget.util.createEvent( mw, 'buildEnd' ) );
+		}
+
+		//
+		// Private methods
+		//
+
+		/**
+		 * Defensively copies the attributes (in case something like
+		 * stripSection changes them) and adds 'readOnly' if the given
+		 * Metawidget is readOnly.
+		 */
+
+		function _forceReadOnly( attributes, mw, excludes ) {
+
+			var copiedAttributes = {};
+
+			for ( var name in attributes ) {
+
+				if ( excludes !== undefined && excludes.indexOf( name ) !== -1 ) {
+					continue;
+				}
+
+				copiedAttributes[name] = attributes[name];
+			}
+
+			// Try to keep the exact nature of the 'readOnly' mechanism (i.e.
+			// set on attribute, or set on overall Metawidget) out of the
+			// WidgetBuilders/WidgetProcessors/Layouts. This is because not
+			// everybody will need/want a Metawidget-level 'setReadOnly'
+
+			if ( mw.readOnly === true ) {
+				copiedAttributes.readOnly = 'true';
+			}
+
+			return copiedAttributes;
+		}
+
+		function _startBuild( pipeline, mw ) {
+
+			// Mark overridden widgets. This is useful for Angular so that it
+			// doesn't $compile them again. It's useful for JQuery Mobile so it
+			// doesn't .trigger( 'create' ) them again
+
+			for ( var loop = 0, length = mw.overriddenNodes.length; loop < length; loop++ ) {
+				mw.overriddenNodes[loop].overridden = true;
+			}
+
+			if ( pipeline.widgetBuilder.onStartBuild !== undefined ) {
+				pipeline.widgetBuilder.onStartBuild( mw );
+			}
+
+			_onStartEndBuild( 'onStartBuild', pipeline, mw );
+
+			if ( pipeline.layout.onStartBuild !== undefined ) {
+				pipeline.layout.onStartBuild( mw );
+			}
+
+			if ( pipeline.layout.startContainerLayout !== undefined ) {
+				pipeline.layout.startContainerLayout( pipeline.element, mw );
+			}
+		}
+
+		function _buildWidget( pipeline, elementName, attributes, mw ) {
+
+			if ( pipeline.widgetBuilder.buildWidget !== undefined ) {
+				return pipeline.widgetBuilder.buildWidget( elementName, attributes, mw );
+			}
+
+			return pipeline.widgetBuilder( elementName, attributes, mw );
+		}
+
+		function _processWidget( pipeline, widget, elementName, attributes, mw ) {
+
+			for ( var loop = 0, length = pipeline.widgetProcessors.length; loop < length; loop++ ) {
+
+				var widgetProcessor = pipeline.widgetProcessors[loop];
+
+				if ( widgetProcessor.processWidget !== undefined ) {
+					widget = widgetProcessor.processWidget( widget, elementName, attributes, mw );
+				} else {
+					widget = widgetProcessor( widget, elementName, attributes, mw );
+				}
+
+				if ( widget === undefined ) {
+					return;
+				}
+			}
+
+			return widget;
+		}
+
+		function _endBuild( pipeline, mw ) {
+
+			if ( mw.onEndBuild !== undefined ) {
+				mw.onEndBuild();
+			} else {
+				while ( mw.overriddenNodes.length > 0 ) {
+
+					var child = mw.overriddenNodes[0];
+					mw.overriddenNodes.splice( 0, 1 );
+
+					// Unused facets don't count
+
+					if ( child.tagName === 'FACET' ) {
+						continue;
+					}
+
+					// Stubs can supply their own metadata (such as 'title')
+
+					var childAttributes = {
+						section: ''
+					};
+
+					if ( child.tagName === 'STUB' ) {
+						for ( var loop = 0, length = child.attributes.length; loop < length; loop++ ) {
+							var prop = child.attributes[loop];
+							childAttributes[prop.nodeName] = prop.nodeValue;
+						}
+					}
+
+					// Manually created components default to no section
+
+					pipeline.layoutWidget( child, "property", childAttributes, pipeline.element, mw );
+				}
+			}
+
+			// End all stages of the pipeline
+
+			if ( pipeline.layout.endContainerLayout !== undefined ) {
+				pipeline.layout.endContainerLayout( pipeline.element, mw );
+			}
+
+			if ( pipeline.layout.onEndBuild !== undefined ) {
+				pipeline.layout.onEndBuild( mw );
+			}
+
+			_onStartEndBuild( 'onEndBuild', pipeline, mw );
+
+			if ( pipeline.widgetBuilder.onEndBuild !== undefined ) {
+				pipeline.widgetBuilder.onEndBuild( mw );
+			}
+		}
+
+		function _onStartEndBuild( functionName, pipeline, mw ) {
+
+			for ( var loop = 0, length = pipeline.widgetProcessors.length; loop < length; loop++ ) {
+
+				var widgetProcessor = pipeline.widgetProcessors[loop];
+
+				if ( widgetProcessor[functionName] !== undefined ) {
+					widgetProcessor[functionName]( mw );
+				}
+			}
+		}
+	};
+
+	/**
+	 * Layout the given widget by delegating to the configured Layout.
+	 * <p>
+	 * Subclasses can override this method to perform any post-processing of the
+	 * widget, following layout. For <em>pre</em>-processing, subclasses
+	 * should use a WidgetProcessor.
+	 */
+
+	metawidget.Pipeline.prototype.layoutWidget = function( widget, elementName, attributes, container, mw ) {
+
+		if ( this.layout.layoutWidget !== undefined ) {
+			this.layout.layoutWidget( widget, elementName, attributes, container, mw );
+			return;
+		}
+
+		this.layout( widget, elementName, attributes, container, mw );
+	};
+} )();
\ No newline at end of file
diff --git a/src/main/webapp/js/3rdparty/metawidget/jquery-ui/metawidget-jqueryui.js b/src/main/webapp/js/3rdparty/metawidget/jquery-ui/metawidget-jqueryui.js
new file mode 100644
index 0000000000000000000000000000000000000000..1316f547326ad2943c8d9441700edbfce538005a
--- /dev/null
+++ b/src/main/webapp/js/3rdparty/metawidget/jquery-ui/metawidget-jqueryui.js
@@ -0,0 +1,432 @@
+// Metawidget 4.2
+//
+// This file is dual licensed under both the LGPL
+// (http://www.gnu.org/licenses/lgpl-2.1.html) and the EPL
+// (http://www.eclipse.org/org/documents/epl-v10.php). As a
+// recipient of Metawidget, you may choose to receive it under either
+// the LGPL or the EPL.
+//
+// Commercial licenses are also available. See http://metawidget.org
+// for details.
+
+var metawidget = metawidget || {};
+
+( function() {
+
+	'use strict';
+
+	/**
+	 * @namespace Metawidget for JQuery UI environments.
+	 */
+
+	metawidget.jqueryui = metawidget.jqueryui || {};
+
+	/**
+	 * @namespace JQuery UI WidgetBuilders.
+	 */
+
+	metawidget.jqueryui.widgetbuilder = metawidget.jqueryui.widgetbuilder || {};
+
+	/**
+	 * @class Builds widgets using JQuery UI.
+	 *        <p>
+	 *        Chooses JQuery UI widgets such as <tt>slider</tt> and
+	 *        <tt>spinner</tt> to suit the inspected fields. Returns undefined
+	 *        for everything else.
+	 */
+
+	metawidget.jqueryui.widgetbuilder.JQueryUIWidgetBuilder = function() {
+
+		if ( ! ( this instanceof metawidget.jqueryui.widgetbuilder.JQueryUIWidgetBuilder ) ) {
+			throw new Error( "Constructor called as a function" );
+		}
+	};
+
+	metawidget.jqueryui.widgetbuilder.JQueryUIWidgetBuilder.prototype.buildWidget = function( elementName, attributes, mw ) {
+
+		// Not for us?
+
+		if ( metawidget.util.isTrueOrTrueString( attributes.readOnly ) ) {
+			return;
+		}
+
+		if ( metawidget.util.isTrueOrTrueString( attributes.hidden ) ) {
+			return;
+		}
+
+		// Number
+
+		if ( attributes.type === 'number' || attributes.type === 'integer' ) {
+
+			if ( attributes.minimum && attributes.maximum ) {
+				var slider = metawidget.util.createElement( mw, 'div' );
+				$( slider ).slider();
+				return slider;
+			}
+
+			var spinner = metawidget.util.createElement( mw, 'input' );
+			$( spinner ).spinner();
+			return $( spinner ).spinner( 'widget' )[0];
+		}
+
+		// Datepicker
+
+		if ( attributes.type === 'date' ) {
+			var date = metawidget.util.createElement( mw, 'input' );
+			$( date ).datepicker();
+			return date;
+		}
+	};
+
+	/**
+	 * @namespace JQuery UI WidgetProcessors.
+	 */
+
+	metawidget.jqueryui.widgetprocessor = metawidget.jqueryui.widgetprocessor || {};
+
+	/**
+	 * @class Binds JQuery UI specific widgets, using the JQuery
+	 *        <tt>$( widget ).foo( 'value', value )</tt> syntax. Clients
+	 *        should still use SimpleBindingProcessor for all non-JQuery UI
+	 *        widgets.
+	 */
+
+	metawidget.jqueryui.widgetprocessor.JQueryUIBindingProcessor = function() {
+
+		if ( ! ( this instanceof metawidget.jqueryui.widgetprocessor.JQueryUIBindingProcessor ) ) {
+			throw new Error( "Constructor called as a function" );
+		}
+	};
+
+	metawidget.jqueryui.widgetprocessor.JQueryUIBindingProcessor.prototype.onStartBuild = function( mw ) {
+
+		mw._jQueryUIBindingProcessorBindings = {};
+	};
+
+	metawidget.jqueryui.widgetprocessor.JQueryUIBindingProcessor.prototype.processWidget = function( widget, elementName, attributes, mw ) {
+
+		var value;
+		var typeAndNames = metawidget.util.splitPath( mw.path );
+		var toInspect = metawidget.util.traversePath( mw.toInspect, typeAndNames.names );
+
+		if ( elementName !== 'entity' && toInspect ) {
+			value = toInspect[attributes.name];
+		} else {
+			value = toInspect;
+		}
+
+		var isBindable = false;
+
+		if ( widget.hasAttribute( 'class' ) ) {
+			var styleClass = widget.getAttribute( 'class' );
+
+			if ( styleClass.indexOf( 'ui-slider' ) !== -1 ) {
+				$( widget ).slider( 'value', value );
+				isBindable = true;
+			} else if ( styleClass.indexOf( 'ui-spinner' ) !== -1 ) {
+				$( widget.childNodes[0] ).spinner( 'value', value );
+				isBindable = true;
+			}
+		}
+
+		if ( isBindable === true || widget.getMetawidget !== undefined ) {
+			mw._jQueryUIBindingProcessorBindings[attributes.name] = widget;
+		}
+
+		return widget;
+	};
+
+	/**
+	 * Save the bindings associated with the given Metawidget.
+	 */
+
+	metawidget.jqueryui.widgetprocessor.JQueryUIBindingProcessor.prototype.save = function( mw ) {
+
+		var typeAndNames = metawidget.util.splitPath( mw.path );
+		var toInspect = metawidget.util.traversePath( mw.toInspect, typeAndNames.names );
+
+		for ( var name in mw._jQueryUIBindingProcessorBindings ) {
+
+			var widget = mw._jQueryUIBindingProcessorBindings[name];
+
+			if ( widget.getMetawidget !== undefined ) {
+				this.save( widget.getMetawidget() );
+				continue;
+			}
+
+			widget = mw.getElement().ownerDocument.getElementById( widget.id );
+
+			var styleClass = widget.getAttribute( 'class' );
+
+			if ( styleClass.indexOf( 'ui-slider' ) !== -1 ) {
+				toInspect[name] = $( widget ).slider( 'value' );
+			} else if ( styleClass.indexOf( 'ui-spinner' ) !== -1 ) {
+				toInspect[name] = $( widget.childNodes[0] ).spinner( 'value' );
+			}
+		}
+	};
+
+	metawidget.jqueryui.layout = metawidget.jqueryui.layout || {};
+
+	/**
+	 * @class LayoutDecorator to decorate widgets from different sections using
+	 *        JQuery UI tabs.
+	 */
+
+	metawidget.jqueryui.layout.TabLayoutDecorator = function( config ) {
+
+		if ( ! ( this instanceof metawidget.jqueryui.layout.TabLayoutDecorator ) ) {
+			throw new Error( "Constructor called as a function" );
+		}
+
+		metawidget.layout.createNestedSectionLayoutDecorator( config, this, 'tabLayoutDecorator' );
+
+		var _superOnEndBuild = this.onEndBuild;
+
+		/**
+		 * Wrap the tabs at the very end, to save using 'tabs.add'.
+		 */
+
+		this.onEndBuild = function( mw ) {
+
+			if ( mw.tabLayoutDecorator !== undefined ) {
+				for ( var loop = 0, length = mw.tabLayoutDecorator.length; loop < length; loop++ ) {
+					$( mw.tabLayoutDecorator[loop] ).tabs();
+				}
+			}
+
+			if ( _superOnEndBuild !== undefined ) {
+				_superOnEndBuild.call( this, mw );
+			}
+		};
+	};
+
+	metawidget.jqueryui.layout.TabLayoutDecorator.prototype.createSectionWidget = function( previousSectionWidget, section, attributes, container, mw ) {
+
+		var tabs = previousSectionWidget;
+
+		// Whole new tabbed pane?
+
+		if ( tabs === undefined ) {
+			tabs = metawidget.util.createElement( mw, 'div' );
+			tabs.setAttribute( 'id', metawidget.util.getId( "property", attributes, mw ) + '-tabs' );
+			tabs.appendChild( metawidget.util.createElement( mw, 'ul' ) );
+			this.getDelegate().layoutWidget( tabs, "property", {
+				wide: "true"
+			}, container, mw );
+
+			mw.tabLayoutDecorator = mw.tabLayoutDecorator || [];
+			mw.tabLayoutDecorator.push( tabs );
+		} else {
+			tabs = previousSectionWidget.parentNode;
+		}
+
+		// New Tab
+
+		var ul = tabs.childNodes[0];
+		var tabId = tabs.getAttribute( 'id' ) + ( ul.childNodes.length + 1 );
+		var li = metawidget.util.createElement( mw, 'li' );
+		var a = metawidget.util.createElement( mw, 'a' );
+		a.setAttribute( 'href', '#' + tabId );
+		a.hash = '#' + tabId;
+		li.appendChild( a );
+		ul.appendChild( li );
+
+		var tab = metawidget.util.createElement( mw, 'div' );
+		tab.setAttribute( 'id', tabId );
+		tabs.appendChild( tab );
+
+		// Tab name
+
+		a.innerHTML = section;
+
+		return tab;
+	};
+
+	/**
+	 * JQuery UI WidgetFactory-based Metawidget.
+	 */
+
+	$.widget( "metawidget.metawidget", {
+
+		/**
+		 * Default configuration
+		 */
+
+		options: {
+			inspector: new metawidget.inspector.PropertyTypeInspector(),
+			widgetBuilder: new metawidget.widgetbuilder.CompositeWidgetBuilder( [ new metawidget.widgetbuilder.OverriddenWidgetBuilder(),
+					new metawidget.jqueryui.widgetbuilder.JQueryUIWidgetBuilder(), new metawidget.widgetbuilder.ReadOnlyWidgetBuilder(), new metawidget.widgetbuilder.HtmlWidgetBuilder() ] ),
+			widgetProcessors: [ new metawidget.widgetprocessor.IdProcessor(), new metawidget.widgetprocessor.RequiredAttributeProcessor(),
+					new metawidget.widgetprocessor.PlaceholderAttributeProcessor(), new metawidget.widgetprocessor.DisabledAttributeProcessor(), new metawidget.jqueryui.widgetprocessor.JQueryUIBindingProcessor(),
+					new metawidget.widgetprocessor.SimpleBindingProcessor() ],
+			layout: new metawidget.layout.HeadingTagLayoutDecorator( new metawidget.layout.TableLayout() )
+		},
+
+		/**
+		 * Constructor
+		 */
+
+		_create: function() {
+
+			// Pipeline (private, based on convention here:
+			// http://forum.jquery.com/topic/what-s-the-right-way-to-store-private-data-in-widget-s-instance)
+
+			this._pipeline = new metawidget.Pipeline( this.element[0] );
+
+			// Configure defaults
+
+			this._pipeline.configure( this.options );
+
+			// First time in, capture the contents of the Metawidget (if any)
+
+			this._overriddenNodes = [];
+
+			var element = this.element[0];
+
+			var mw = this;
+			
+			element.getMetawidget = function() {
+
+				return mw;
+			};
+
+			for ( var loop = 0; loop < element.childNodes.length; ) {
+				if ( element.childNodes[loop].nodeType !== 1 ) {
+					loop++;
+					continue;
+				}
+
+				var childNode = element.childNodes[loop];
+				element.removeChild( childNode );
+				this._overriddenNodes.push( childNode );
+			}
+		},
+
+		/**
+		 * Called when created, and later when changing options.
+		 */
+
+		_refresh: function( inspectionResult ) {
+
+			// Defensive copy
+
+			this.overriddenNodes = [];
+
+			for ( var loop = 0, length = this._overriddenNodes.length; loop < length; loop++ ) {
+				this.overriddenNodes.push( this._overriddenNodes[loop].cloneNode( true ) );
+			}
+
+			// Inspect (if necessary)
+
+			if ( inspectionResult === undefined ) {
+
+				// Safeguard against improperly implementing:
+				// http://blog.kennardconsulting.com/2013/02/metawidget-and-rest.html
+
+				if ( arguments.length > 0 ) {
+					throw new Error( "Calling _refresh( undefined ) may cause infinite loop. Check your argument, or pass no arguments instead" );
+				}
+
+				var splitPath = metawidget.util.splitPath( this.path );
+				inspectionResult = this._pipeline.inspect( this.toInspect, splitPath.type, splitPath.names, this );
+			}
+
+			// Build widgets
+
+			this._pipeline.buildWidgets( inspectionResult, this );
+		},
+
+		/**
+		 * _setOptions is called with a hash of all options that are changing.
+		 */
+
+		_setOptions: function() {
+
+			this._superApply( arguments );
+			this._pipeline.configure( this.options );
+		},
+
+		setReadOnly: function( readOnly ) {
+
+			this.readOnly = readOnly;
+		},
+
+		/**
+		 * Useful for WidgetBuilders to perform nested inspections (eg. for
+		 * Collections).
+		 */
+
+		inspect: function( toInspect, type, names ) {
+
+			return this._pipeline.inspect( toInspect, type, names, this );
+		},
+
+		/**
+		 * Overridden to use JQuery.empty (safer for memory leaks).
+		 */
+		
+		clearWidgets: function() {
+		
+			$( this.getElement() ).empty();
+		},
+		
+		/**
+		 * Inspect the given toInspect/path and build widgets.
+		 * <p>
+		 * Invoke using
+		 * <tt>$( '#metawidget' ).metawidget( "buildWidgets", toInspect, path )</tt>.
+		 */
+
+		buildWidgets: function( toInspect, path ) {
+
+			if ( toInspect !== undefined ) {
+				this.toInspect = toInspect;
+				this.path = undefined;
+			}
+
+			if ( path !== undefined ) {
+				this.path = path;
+			}
+
+			this._refresh();
+		},
+
+		getWidgetProcessor: function( testInstanceOf ) {
+
+			return this._pipeline.getWidgetProcessor( testInstanceOf );
+		},
+		
+		/**
+		 * Returns the element this Metawidget is attached to.
+		 */
+
+		getElement: function() {
+
+			return this._pipeline.element;
+		},
+
+		buildNestedMetawidget: function( attributes, config ) {
+
+			// Create a 'div' not a 'metawidget', because whilst it's up to the
+			// user what they want their top-level element to be, for browser
+			// compatibility we should stick with something benign for nested
+			// elements
+
+			var nestedWidget = metawidget.util.createElement( this, 'div' );
+
+			// Duck-type our 'pipeline' as the 'config' of the nested
+			// Metawidget. This neatly passes everything down, including a
+			// decremented 'maximumInspectionDepth'
+
+			var nestedMetawidget = $( nestedWidget ).metawidget( this._pipeline );
+
+			nestedMetawidget.metawidget( "setReadOnly", this.readOnly || metawidget.util.isTrueOrTrueString( attributes.readOnly ) );
+			var nestedToInspect = this.toInspect;
+			var nestedPath = metawidget.util.appendPath( attributes, this );
+
+			nestedMetawidget.metawidget( "buildWidgets", nestedToInspect, nestedPath );
+			return nestedWidget;
+		}
+	} );
+} )();
\ No newline at end of file
diff --git a/src/main/webapp/js/3rdparty/metawidget/jquery-ui/metawidget-jqueryui.min.js b/src/main/webapp/js/3rdparty/metawidget/jquery-ui/metawidget-jqueryui.min.js
new file mode 100644
index 0000000000000000000000000000000000000000..87c18d2b129eb37781f329985c1cb25187bb7fcf
--- /dev/null
+++ b/src/main/webapp/js/3rdparty/metawidget/jquery-ui/metawidget-jqueryui.min.js
@@ -0,0 +1,16 @@
+// Metawidget 4.2 minified
+//
+// This file is dual licensed under both the LGPL
+// (http://www.gnu.org/licenses/lgpl-2.1.html) and the EPL
+// (http://www.eclipse.org/org/documents/epl-v10.php). As a
+// recipient of Metawidget, you may choose to receive it under either
+// the LGPL or the EPL.
+//
+// Commercial licenses are also available. See http://metawidget.org
+// for details.
+//
+// Author: Richard Kennard (http://kennardconsulting.com)
+
+var metawidget=metawidget||{};(function(){metawidget.jqueryui=metawidget.jqueryui||{};metawidget.jqueryui.widgetbuilder=metawidget.jqueryui.widgetbuilder||{};metawidget.jqueryui.widgetbuilder.JQueryUIWidgetBuilder=function(){if(!(this instanceof metawidget.jqueryui.widgetbuilder.JQueryUIWidgetBuilder)){throw new Error("Constructor called as a function")}};metawidget.jqueryui.widgetbuilder.JQueryUIWidgetBuilder.prototype.buildWidget=function(a,b,f){if(metawidget.util.isTrueOrTrueString(b.readOnly)){return}if(metawidget.util.isTrueOrTrueString(b.hidden)){return}if(b.type==="number"||b.type==="integer"){if(b.minimum&&b.maximum){var d=metawidget.util.createElement(f,"div");$(d).slider();return d}var e=metawidget.util.createElement(f,"input");$(e).spinner();return $(e).spinner("widget")[0]}if(b.type==="date"){var c=metawidget.util.createElement(f,"input");$(c).datepicker();return c}};metawidget.jqueryui.widgetprocessor=metawidget.jqueryui.widgetprocessor||{};metawidget.jqueryui.widgetprocessor.JQueryUIBindingProcessor=function(){if(!(this instanceof metawidget.jqueryui.widgetprocessor.JQueryUIBindingProcessor)){throw new Error("Constructor called as a function")}};metawidget.jqueryui.widgetprocessor.JQueryUIBindingProcessor.prototype.onStartBuild=function(a){a._jQueryUIBindingProcessorBindings={}};metawidget.jqueryui.widgetprocessor.JQueryUIBindingProcessor.prototype.processWidget=function(d,i,b,f){var e;var h=metawidget.util.splitPath(f.path);var g=metawidget.util.traversePath(f.toInspect,h.names);if(i!=="entity"&&g){e=g[b.name]}else{e=g}var a=false;if(d.hasAttribute("class")){var c=d.getAttribute("class");if(c.indexOf("ui-slider")!==-1){$(d).slider("value",e);a=true}else{if(c.indexOf("ui-spinner")!==-1){$(d.childNodes[0]).spinner("value",e);a=true}}}if(a===true||d.getMetawidget!==undefined){f._jQueryUIBindingProcessorBindings[b.name]=d}return d};metawidget.jqueryui.widgetprocessor.JQueryUIBindingProcessor.prototype.save=function(f){var c=metawidget.util.splitPath(f.path);
+var e=metawidget.util.traversePath(f.toInspect,c.names);for(var b in f._jQueryUIBindingProcessorBindings){var d=f._jQueryUIBindingProcessorBindings[b];if(d.getMetawidget!==undefined){this.save(d.getMetawidget());continue}d=f.getElement().ownerDocument.getElementById(d.id);var a=d.getAttribute("class");if(a.indexOf("ui-slider")!==-1){e[b]=$(d).slider("value")}else{if(a.indexOf("ui-spinner")!==-1){e[b]=$(d.childNodes[0]).spinner("value")}}}};metawidget.jqueryui.layout=metawidget.jqueryui.layout||{};metawidget.jqueryui.layout.TabLayoutDecorator=function(a){if(!(this instanceof metawidget.jqueryui.layout.TabLayoutDecorator)){throw new Error("Constructor called as a function")}metawidget.layout.createNestedSectionLayoutDecorator(a,this,"tabLayoutDecorator");var b=this.onEndBuild;this.onEndBuild=function(e){if(e.tabLayoutDecorator!==undefined){for(var c=0,d=e.tabLayoutDecorator.length;c<d;c++){$(e.tabLayoutDecorator[c]).tabs()}}if(b!==undefined){b.call(this,e)}}};metawidget.jqueryui.layout.TabLayoutDecorator.prototype.createSectionWidget=function(i,j,e,b,l){var g=i;if(g===undefined){g=metawidget.util.createElement(l,"div");g.setAttribute("id",metawidget.util.getId("property",e,l)+"-tabs");g.appendChild(metawidget.util.createElement(l,"ul"));this.getDelegate().layoutWidget(g,"property",{wide:"true"},b,l);l.tabLayoutDecorator=l.tabLayoutDecorator||[];l.tabLayoutDecorator.push(g)}else{g=i.parentNode}var f=g.childNodes[0];var c=g.getAttribute("id")+(f.childNodes.length+1);var k=metawidget.util.createElement(l,"li");var h=metawidget.util.createElement(l,"a");h.setAttribute("href","#"+c);h.hash="#"+c;k.appendChild(h);f.appendChild(k);var d=metawidget.util.createElement(l,"div");d.setAttribute("id",c);g.appendChild(d);h.innerHTML=j;return d};$.widget("metawidget.metawidget",{options:{inspector:new metawidget.inspector.PropertyTypeInspector(),widgetBuilder:new metawidget.widgetbuilder.CompositeWidgetBuilder([new metawidget.widgetbuilder.OverriddenWidgetBuilder(),new metawidget.jqueryui.widgetbuilder.JQueryUIWidgetBuilder(),new metawidget.widgetbuilder.ReadOnlyWidgetBuilder(),new metawidget.widgetbuilder.HtmlWidgetBuilder()]),widgetProcessors:[new metawidget.widgetprocessor.IdProcessor(),new metawidget.widgetprocessor.RequiredAttributeProcessor(),new metawidget.widgetprocessor.PlaceholderAttributeProcessor(),new metawidget.widgetprocessor.DisabledAttributeProcessor(),new metawidget.jqueryui.widgetprocessor.JQueryUIBindingProcessor(),new metawidget.widgetprocessor.SimpleBindingProcessor()],layout:new metawidget.layout.HeadingTagLayoutDecorator(new metawidget.layout.TableLayout())},_create:function(){this._pipeline=new metawidget.Pipeline(this.element[0]);
+this._pipeline.configure(this.options);this._overriddenNodes=[];var c=this.element[0];var d=this;c.getMetawidget=function(){return d};for(var a=0;a<c.childNodes.length;){if(c.childNodes[a].nodeType!==1){a++;continue}var b=c.childNodes[a];c.removeChild(b);this._overriddenNodes.push(b)}},_refresh:function(d){this.overriddenNodes=[];for(var a=0,c=this._overriddenNodes.length;a<c;a++){this.overriddenNodes.push(this._overriddenNodes[a].cloneNode(true))}if(d===undefined){if(arguments.length>0){throw new Error("Calling _refresh( undefined ) may cause infinite loop. Check your argument, or pass no arguments instead")}var b=metawidget.util.splitPath(this.path);d=this._pipeline.inspect(this.toInspect,b.type,b.names,this)}this._pipeline.buildWidgets(d,this)},_setOptions:function(){this._superApply(arguments);this._pipeline.configure(this.options)},setReadOnly:function(a){this.readOnly=a},inspect:function(c,a,b){return this._pipeline.inspect(c,a,b,this)},clearWidgets:function(){$(this.getElement()).empty()},buildWidgets:function(b,a){if(b!==undefined){this.toInspect=b;this.path=undefined}if(a!==undefined){this.path=a}this._refresh()},getWidgetProcessor:function(a){return this._pipeline.getWidgetProcessor(a)},getElement:function(){return this._pipeline.element},buildNestedMetawidget:function(a,c){var f=metawidget.util.createElement(this,"div");var d=$(f).metawidget(this._pipeline);d.metawidget("setReadOnly",this.readOnly||metawidget.util.isTrueOrTrueString(a.readOnly));var b=this.toInspect;var e=metawidget.util.appendPath(a,this);d.metawidget("buildWidgets",b,e);return f}})})();
diff --git a/src/main/webapp/js/3rdparty/metawidget/jquery.mobile/metawidget-jquerymobile.js b/src/main/webapp/js/3rdparty/metawidget/jquery.mobile/metawidget-jquerymobile.js
new file mode 100644
index 0000000000000000000000000000000000000000..ff7feb9daba6c14bce7c0766bcb5920451ea4b3f
--- /dev/null
+++ b/src/main/webapp/js/3rdparty/metawidget/jquery.mobile/metawidget-jquerymobile.js
@@ -0,0 +1,395 @@
+// Metawidget 4.2
+//
+// This file is dual licensed under both the LGPL
+// (http://www.gnu.org/licenses/lgpl-2.1.html) and the EPL
+// (http://www.eclipse.org/org/documents/epl-v10.php). As a
+// recipient of Metawidget, you may choose to receive it under either
+// the LGPL or the EPL.
+//
+// Commercial licenses are also available. See http://metawidget.org
+// for details.
+
+var metawidget = metawidget || {};
+
+( function() {
+
+	'use strict';
+
+	/**
+	 * @namespace Metawidget for JQuery Mobile environments.
+	 */
+
+	metawidget.jquerymobile = metawidget.jquerymobile || {};
+
+	/**
+	 * @namespace JQuery Mobile WidgetProcessors.
+	 */
+
+	metawidget.jquerymobile.widgetprocessor = metawidget.jquerymobile.widgetprocessor || {};
+
+	/**
+	 * @class adapts to JQuery Mobile-specific syntax.
+	 */
+
+	metawidget.jquerymobile.widgetprocessor.JQueryMobileWidgetProcessor = function() {
+
+		if ( ! ( this instanceof metawidget.jquerymobile.widgetprocessor.JQueryMobileWidgetProcessor ) ) {
+			throw new Error( "Constructor called as a function" );
+		}
+	};
+
+	metawidget.jquerymobile.widgetprocessor.JQueryMobileWidgetProcessor.prototype.processWidget = function( widget, elementName, attributes, mw ) {
+
+		// JQuery Mobile has a special syntax for arrays
+
+		if ( widget.tagName === 'DIV' && attributes.type === 'array' ) {
+
+			var fieldset = metawidget.util.createElement( mw, 'fieldset' );
+			fieldset.setAttribute( 'data-role', 'controlgroup' );
+
+			while ( widget.childNodes.length > 0 ) {
+				var label = widget.childNodes[0];
+
+				if ( label.tagName !== 'LABEL' ) {
+					return widget;
+				}
+
+				var id = widget.getAttribute( 'id' ) + widget.childNodes.length;
+				label.setAttribute( 'for', id );
+				var input = label.childNodes[0];
+				input.setAttribute( 'id', id );
+
+				fieldset.appendChild( input );
+				fieldset.appendChild( label );
+			}
+
+			widget = fieldset;
+		}
+
+		return widget;
+	};
+
+	metawidget.jquerymobile.widgetprocessor.JQueryMobileSimpleBindingProcessor = function() {
+
+		if ( ! ( this instanceof metawidget.jquerymobile.widgetprocessor.JQueryMobileSimpleBindingProcessor ) ) {
+			throw new Error( "Constructor called as a function" );
+		}
+
+		var processor = new metawidget.widgetprocessor.SimpleBindingProcessor();
+
+		// Overridden because some JQuery Mobile widgets (such as search inputs)
+		// swap out the existing DOM. We can resolve this using JQuery more
+		// safely then with pure JavaScript, because we can find *within* a node
+
+		processor.getWidgetFromBinding = function( binding, mw ) {
+
+			if ( binding.widget.getAttribute( 'type' ) === 'search' ) {
+				return $( mw.getElement() ).find( '#' + binding.widget.getAttribute( 'id' ) )[0];
+			}
+
+			// Try not to use a DOM search, because mobile is very performance
+			// sensitive
+
+			return binding.widget;
+		};
+
+		// Support arrays of checkboxes
+
+		var _superBindToWidget = processor.bindToWidget;
+		processor.bindToWidget = function( widget, value, elementName, attributes, mw ) {
+
+			var toReturn = _superBindToWidget.call( this, widget, value, elementName, attributes, mw );
+
+			if ( widget.tagName === 'FIELDSET' && attributes.type === 'array' ) {
+
+				if ( value !== undefined ) {
+					var checkboxes = widget.childNodes;
+					for ( var loop = 0, length = checkboxes.length; loop < length; loop++ ) {
+						var childNode = checkboxes[loop];
+						if ( childNode.type !== 'checkbox' ) {
+							continue;
+						}
+						if ( value.indexOf( childNode.value ) !== -1 ) {
+							childNode.checked = true;
+						}
+					}
+				}
+				return true;
+			}
+
+			return toReturn;
+		};
+		var _superSaveFromWidget = processor.saveFromWidget;
+		processor.saveFromWidget = function( binding, mw ) {
+
+			if ( binding.widget.tagName === 'FIELDSET' && binding.attributes.type === 'array' ) {
+				var toReturn = [];
+				var checkboxes = binding.widget.childNodes[0].childNodes;
+				for ( var loop = 0, length = checkboxes.length; loop < length; loop++ ) {
+					var childNode = checkboxes[loop];
+					var checkbox = $( childNode ).find( '[type=checkbox]' )[0];
+					if ( checkbox.checked ) {
+						toReturn.push( checkbox.value );
+					}
+				}
+				return toReturn;
+			}
+
+			return _superSaveFromWidget.call( this, binding, mw );
+		};
+
+		return processor;
+	};
+
+	/**
+	 * JQuery Mobile WidgetFactory-based Metawidget.
+	 */
+
+	$.widget( "mobile.metawidget", {
+
+		/**
+		 * Default configuration
+		 */
+
+		options: {
+			inspector: new metawidget.inspector.PropertyTypeInspector(),
+			widgetBuilder: new metawidget.widgetbuilder.CompositeWidgetBuilder( [ new metawidget.widgetbuilder.OverriddenWidgetBuilder(), new metawidget.widgetbuilder.ReadOnlyWidgetBuilder(),
+					new metawidget.widgetbuilder.HtmlWidgetBuilder() ] ),
+			widgetProcessors: [ new metawidget.widgetprocessor.IdProcessor(), new metawidget.widgetprocessor.RequiredAttributeProcessor(),
+					new metawidget.widgetprocessor.PlaceholderAttributeProcessor(), new metawidget.widgetprocessor.DisabledAttributeProcessor(),
+					new metawidget.jquerymobile.widgetprocessor.JQueryMobileWidgetProcessor(), new metawidget.jquerymobile.widgetprocessor.JQueryMobileSimpleBindingProcessor() ],
+			layout: new metawidget.layout.HeadingTagLayoutDecorator( new metawidget.layout.DivLayout( {
+				divStyleClasses: [ 'ui-field-contain' ],
+				suppressDivAroundLabel: true,
+				suppressDivAroundWidget: true,
+				suppressLabelSuffixOnCheckboxes: true,
+			} ) )
+		},
+
+		/**
+		 * Constructor
+		 */
+
+		_create: function() {
+
+			// Pipeline (private, based on convention here:
+			// http://forum.jquery.com/topic/what-s-the-right-way-to-store-private-data-in-widget-s-instance)
+
+			this._pipeline = new metawidget.Pipeline( this.element[0] );
+
+			// Configure defaults
+
+			this._pipeline.configure( this.options );
+
+			// JQuery Mobile automatically augments widgets with additional
+			// HTML. Clients must call trigger( 'create' ) manually for
+			// dynamically created components. This must be done on the widget's
+			// container, not the widget itself. However, it cannot be done at
+			// the top Metawidget-level, as that will 'double augment' any
+			// overridden widgets
+
+			var _superLayoutWidget = this._pipeline.layoutWidget;
+			this._pipeline.layoutWidget = function( widget, elementName, attributes, container, mw ) {
+
+				_superLayoutWidget.call( this, widget, elementName, attributes, container, mw );
+				if ( widget.overridden === undefined ) {
+
+					var childNodes = container.childNodes;
+					var containerNode = childNodes[childNodes.length - 1];
+
+					if ( containerNode === widget ) {
+
+						// Support SimpleLayout
+
+						container.removeChild( widget );
+						var wrapper = $( '<span>' ).append( widget );
+						container.appendChild( wrapper[0] );
+						wrapper.trigger( 'create' );
+
+					} else {
+
+						$( containerNode ).trigger( 'create' );
+					}
+				}
+			};
+			
+			// Force a useful convention from JQuery UI that JQuery Mobile
+			// doesn't seem to have (yet?)
+
+			this.element.data( 'metawidget', this );
+
+			// First time in, capture the contents of the Metawidget (if any)
+
+			this._overriddenNodes = [];
+
+			var element = this.element[0];
+
+			var mw = this;
+
+			element.getMetawidget = function() {
+
+				return mw;
+			};
+
+			for ( var loop = 0; loop < element.childNodes.length; ) {
+				if ( element.childNodes[loop].nodeType !== 1 ) {
+					loop++;
+					continue;
+				}
+
+				var childNode = element.childNodes[loop];
+				element.removeChild( childNode );
+
+				// De-augment before pushing, so that the widget works
+				// seamlessly with binding/override matching
+
+				if ( childNode.getAttribute( 'class' ) !== null && childNode.getAttribute( 'class' ).indexOf( 'ui-' ) !== -1 && childNode.childNodes.length === 1 ) {
+					childNode = childNode.childNodes[0];
+				}
+
+				this._overriddenNodes.push( childNode );
+			}
+		},
+
+		/**
+		 * Called when created, and later when changing options.
+		 */
+
+		_refresh: function( inspectionResult ) {
+
+			// Defensive copy
+
+			this.overriddenNodes = [];
+
+			for ( var loop = 0, length = this._overriddenNodes.length; loop < length; loop++ ) {
+				this.overriddenNodes.push( this._overriddenNodes[loop].cloneNode( true ) );
+			}
+
+			// Inspect (if necessary)
+
+			if ( inspectionResult === undefined ) {
+
+				// Safeguard against improperly implementing:
+				// http://blog.kennardconsulting.com/2013/02/metawidget-and-rest.html
+
+				if ( arguments.length > 0 ) {
+					throw new Error( "Calling _refresh( undefined ) may cause infinite loop. Check your argument, or pass no arguments instead" );
+				}
+
+				var splitPath = metawidget.util.splitPath( this.path );
+				inspectionResult = this._pipeline.inspect( this.toInspect, splitPath.type, splitPath.names, this );
+			}
+
+			// Build widgets
+
+			this._pipeline.buildWidgets( inspectionResult, this );
+		},
+
+		/**
+		 * _setOptions is called with a hash of all options that are changing.
+		 */
+
+		_setOptions: function() {
+
+			this._superApply( arguments );
+			this._pipeline.configure( this.options );
+		},
+
+		setReadOnly: function( readOnly ) {
+
+			this.readOnly = readOnly;
+		},
+
+		/**
+		 * Useful for WidgetBuilders to perform nested inspections (eg. for
+		 * Collections).
+		 */
+
+		inspect: function( toInspect, type, names ) {
+
+			return this._pipeline.inspect( toInspect, type, names, this );
+		},
+
+		/**
+		 * Overridden to use JQuery.empty (safer for memory leaks).
+		 */
+
+		clearWidgets: function() {
+
+			$( this.getElement() ).empty();
+		},
+
+		/**
+		 * Inspect the given toInspect/path and build widgets.
+		 * <p>
+		 * Invoke using
+		 * <tt>$( '#metawidget' ).metawidget( "buildWidgets", toInspect, path )</tt>.
+		 */
+
+		buildWidgets: function( toInspect, path ) {
+
+			if ( toInspect !== undefined ) {
+				this.toInspect = toInspect;
+				this.path = undefined;
+			}
+
+			if ( path !== undefined ) {
+				this.path = path;
+			}
+
+			this._refresh();
+		},
+
+		/**
+		 * Save the contents of the Metawidget using a SimpleBindingProcessor.
+		 * <p>
+		 * This is a convenience method. To access other Metawidget APIs,
+		 * clients can use the 'getWidgetProcessor' method
+		 */
+
+		save: function() {
+
+			this._pipeline.getWidgetProcessor( function( widgetProcessor ) {
+
+				return widgetProcessor instanceof metawidget.widgetprocessor.SimpleBindingProcessor;
+			} ).save( this );
+		},
+
+		getWidgetProcessor: function( testInstanceOf ) {
+
+			return this._pipeline.getWidgetProcessor( testInstanceOf );
+		},
+
+		/**
+		 * Returns the element this Metawidget is attached to.
+		 */
+
+		getElement: function() {
+
+			return this._pipeline.element;
+		},
+
+		buildNestedMetawidget: function( attributes, config ) {
+
+			// Create a 'div' not a 'metawidget', because whilst it's up to the
+			// user what they want their top-level element to be, for browser
+			// compatibility we should stick with something benign for nested
+			// elements
+
+			var nestedWidget = metawidget.util.createElement( this, 'div' );
+
+			// Duck-type our 'pipeline' as the 'config' of the nested
+			// Metawidget. This neatly passes everything down, including a
+			// decremented 'maximumInspectionDepth'
+
+			var nestedMetawidget = $( nestedWidget ).metawidget( this._pipeline );
+
+			nestedMetawidget.metawidget( "setReadOnly", this.readOnly || metawidget.util.isTrueOrTrueString( attributes.readOnly ) );
+			var nestedToInspect = this.toInspect;
+			var nestedPath = metawidget.util.appendPath( attributes, this );
+
+			nestedMetawidget.metawidget( "buildWidgets", nestedToInspect, nestedPath );
+			return nestedWidget;
+		}
+	} );
+} )();
\ No newline at end of file
diff --git a/src/main/webapp/js/3rdparty/metawidget/jquery.mobile/metawidget-jquerymobile.min.js b/src/main/webapp/js/3rdparty/metawidget/jquery.mobile/metawidget-jquerymobile.min.js
new file mode 100644
index 0000000000000000000000000000000000000000..ecd8088e3f7027f9c18280b6fca08fdda7db5e68
--- /dev/null
+++ b/src/main/webapp/js/3rdparty/metawidget/jquery.mobile/metawidget-jquerymobile.min.js
@@ -0,0 +1,16 @@
+// Metawidget 4.2 minified
+//
+// This file is dual licensed under both the LGPL
+// (http://www.gnu.org/licenses/lgpl-2.1.html) and the EPL
+// (http://www.eclipse.org/org/documents/epl-v10.php). As a
+// recipient of Metawidget, you may choose to receive it under either
+// the LGPL or the EPL.
+//
+// Commercial licenses are also available. See http://metawidget.org
+// for details.
+//
+// Author: Richard Kennard (http://kennardconsulting.com)
+
+var metawidget=metawidget||{};(function(){metawidget.jquerymobile=metawidget.jquerymobile||{};metawidget.jquerymobile.widgetprocessor=metawidget.jquerymobile.widgetprocessor||{};metawidget.jquerymobile.widgetprocessor.JQueryMobileWidgetProcessor=function(){if(!(this instanceof metawidget.jquerymobile.widgetprocessor.JQueryMobileWidgetProcessor)){throw new Error("Constructor called as a function")}};metawidget.jquerymobile.widgetprocessor.JQueryMobileWidgetProcessor.prototype.processWidget=function(f,b,d,h){if(f.tagName==="DIV"&&d.type==="array"){var a=metawidget.util.createElement(h,"fieldset");a.setAttribute("data-role","controlgroup");while(f.childNodes.length>0){var e=f.childNodes[0];if(e.tagName!=="LABEL"){return f}var g=f.getAttribute("id")+f.childNodes.length;e.setAttribute("for",g);var c=e.childNodes[0];c.setAttribute("id",g);a.appendChild(c);a.appendChild(e)}f=a}return f};metawidget.jquerymobile.widgetprocessor.JQueryMobileSimpleBindingProcessor=function(){if(!(this instanceof metawidget.jquerymobile.widgetprocessor.JQueryMobileSimpleBindingProcessor)){throw new Error("Constructor called as a function")}var a=new metawidget.widgetprocessor.SimpleBindingProcessor();a.getWidgetFromBinding=function(d,e){if(d.widget.getAttribute("type")==="search"){return $(e.getElement()).find("#"+d.widget.getAttribute("id"))[0]}return d.widget};var c=a.bindToWidget;a.bindToWidget=function(g,k,m,f,l){var j=c.call(this,g,k,m,f,l);if(g.tagName==="FIELDSET"&&f.type==="array"){if(k!==undefined){var h=g.childNodes;for(var i=0,e=h.length;i<e;i++){var d=h[i];if(d.type!=="checkbox"){continue}if(k.indexOf(d.value)!==-1){d.checked=true}}}return true}return j};var b=a.saveFromWidget;a.saveFromWidget=function(j,k){if(j.widget.tagName==="FIELDSET"&&j.attributes.type==="array"){var i=[];var h=j.widget.childNodes[0].childNodes;for(var d=0,f=h.length;d<f;d++){var e=h[d];var g=$(e).find("[type=checkbox]")[0];if(g.checked){i.push(g.value)}}return i}return b.call(this,j,k)};return a};$.widget("mobile.metawidget",{options:{inspector:new metawidget.inspector.PropertyTypeInspector(),widgetBuilder:new metawidget.widgetbuilder.CompositeWidgetBuilder([new metawidget.widgetbuilder.OverriddenWidgetBuilder(),new metawidget.widgetbuilder.ReadOnlyWidgetBuilder(),new metawidget.widgetbuilder.HtmlWidgetBuilder()]),widgetProcessors:[new metawidget.widgetprocessor.IdProcessor(),new metawidget.widgetprocessor.RequiredAttributeProcessor(),new metawidget.widgetprocessor.PlaceholderAttributeProcessor(),new metawidget.widgetprocessor.DisabledAttributeProcessor(),new metawidget.jquerymobile.widgetprocessor.JQueryMobileWidgetProcessor(),new metawidget.jquerymobile.widgetprocessor.JQueryMobileSimpleBindingProcessor()],layout:new metawidget.layout.HeadingTagLayoutDecorator(new metawidget.layout.DivLayout({divStyleClasses:["ui-field-contain"],suppressDivAroundLabel:true,suppressDivAroundWidget:true,suppressLabelSuffixOnCheckboxes:true,}))},_create:function(){this._pipeline=new metawidget.Pipeline(this.element[0]);
+this._pipeline.configure(this.options);var b=this._pipeline.layoutWidget;this._pipeline.layoutWidget=function(j,g,i,h,m){b.call(this,j,g,i,h,m);if(j.overridden===undefined){var k=h.childNodes;var f=k[k.length-1];if(f===j){h.removeChild(j);var l=$("<span>").append(j);h.appendChild(l[0]);l.trigger("create")}else{$(f).trigger("create")}}};this.element.data("metawidget",this);this._overriddenNodes=[];var d=this.element[0];var e=this;d.getMetawidget=function(){return e};for(var a=0;a<d.childNodes.length;){if(d.childNodes[a].nodeType!==1){a++;continue}var c=d.childNodes[a];d.removeChild(c);if(c.getAttribute("class")!==null&&c.getAttribute("class").indexOf("ui-")!==-1&&c.childNodes.length===1){c=c.childNodes[0]}this._overriddenNodes.push(c)}},_refresh:function(d){this.overriddenNodes=[];for(var a=0,c=this._overriddenNodes.length;a<c;a++){this.overriddenNodes.push(this._overriddenNodes[a].cloneNode(true))}if(d===undefined){if(arguments.length>0){throw new Error("Calling _refresh( undefined ) may cause infinite loop. Check your argument, or pass no arguments instead")}var b=metawidget.util.splitPath(this.path);d=this._pipeline.inspect(this.toInspect,b.type,b.names,this)}this._pipeline.buildWidgets(d,this)},_setOptions:function(){this._superApply(arguments);this._pipeline.configure(this.options)},setReadOnly:function(a){this.readOnly=a},inspect:function(c,a,b){return this._pipeline.inspect(c,a,b,this)},clearWidgets:function(){$(this.getElement()).empty()},buildWidgets:function(b,a){if(b!==undefined){this.toInspect=b;this.path=undefined}if(a!==undefined){this.path=a}this._refresh()},save:function(){this._pipeline.getWidgetProcessor(function(a){return a instanceof metawidget.widgetprocessor.SimpleBindingProcessor}).save(this)},getWidgetProcessor:function(a){return this._pipeline.getWidgetProcessor(a)},getElement:function(){return this._pipeline.element},buildNestedMetawidget:function(a,c){var f=metawidget.util.createElement(this,"div");var d=$(f).metawidget(this._pipeline);d.metawidget("setReadOnly",this.readOnly||metawidget.util.isTrueOrTrueString(a.readOnly));
+var b=this.toInspect;var e=metawidget.util.appendPath(a,this);d.metawidget("buildWidgets",b,e);return f}})})();
diff --git a/src/main/webapp/js/objectGISInfoMap.js b/src/main/webapp/js/objectGISInfoMap.js
index 9c91356e05854068b80a9714250ece279b41ebfc..89a98c8f095fe926eda885a0c1442fc74003785c 100755
--- a/src/main/webapp/js/objectGISInfoMap.js
+++ b/src/main/webapp/js/objectGISInfoMap.js
@@ -102,7 +102,7 @@ function initMap(containerId, center, zoomLevel, displayMarker, drawnObjs, choos
   });
 
   // Satellite photo map from Microsoft/Bing
-  var bingArial = new ol.layer.Tile({
+  /*var bingArial = new ol.layer.Tile({
     title: 'Bing Arial',
     type: 'base',
     visible: false,
@@ -110,7 +110,7 @@ function initMap(containerId, center, zoomLevel, displayMarker, drawnObjs, choos
       imagerySet: 'Aerial',
       key: 'Ak-dzM4wZjSqTlzveKz5u0d4IQ4bRzVI309GxmkgSVr1ewS6iPSrOvOKhA-CJlm3'
     })
-  });
+  });*/
 
   // Detailed map of Norway in shades of grey
   var topo2graatone = new ol.layer.Tile({
@@ -131,7 +131,7 @@ function initMap(containerId, center, zoomLevel, displayMarker, drawnObjs, choos
   //########################Get maplayers you can choose from########################
 
   //var layersObj = eval ("(" + chooseLayersObj + ")");
-  var allLayers = [osm, bingArial, topo2graatone] ;
+  var allLayers = [osm, topo2graatone] ;
   var chooseLayers = [];
   var choosenLayer;
   var hoverAttribute = '';
diff --git a/src/main/webapp/js/observationDataHandler.js b/src/main/webapp/js/observationDataHandler.js
new file mode 100644
index 0000000000000000000000000000000000000000..7380cc9e01fd4d3c02fd310d9aa161be9a8c9d19
--- /dev/null
+++ b/src/main/webapp/js/observationDataHandler.js
@@ -0,0 +1,28 @@
+/* 
+ * Copyright (c) 2016 NIBIO <http://www.nibio.no/>. 
+ * 
+ * This file is part of VIPSLogic.
+ * VIPSLogic is free software: you can redistribute it and/or modify
+ * it under the terms of the NIBIO Open Source License as published by 
+ * NIBIO, either version 1 of the License, or (at your option) any
+ * later version.
+ * 
+ * VIPSLogic is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * NIBIO Open Source License for more details.
+ * 
+ * You should have received a copy of the NIBIO Open Source License
+ * along with VIPSLogic.  If not, see <http://www.nibio.no/licenses/>.
+ * 
+ */
+
+/*
+ * Using MetaWidget (http://metawidget.sourceforge.net/) 4.2
+ */
+
+function initObservationDataForm(organismId)
+{
+    // Contact VIPSLogic, get correct model and schema for given organism and organization
+    // ...including localized label names
+}
\ No newline at end of file
diff --git a/src/main/webapp/js/validateForm.js b/src/main/webapp/js/validateForm.js
index 1545b80e3f2af535e753fb03761d112ed02547c4..d0bcdbd442a7f5aff9a57df27659d5eee2c1cf86 100644
--- a/src/main/webapp/js/validateForm.js
+++ b/src/main/webapp/js/validateForm.js
@@ -92,7 +92,9 @@ function validateForm(theForm, formDefinitionKey)
     var formDefinition = formDefinitions[formDefinitionKey];
     // Iterate through fields in form definition
     for(var i in formDefinition.fields){
+        
         var fieldDefinition = formDefinition.fields[i];
+        //console.log(fieldDefinition);
         if(!validateFieldActual(theForm[fieldDefinition.name], theForm, formDefinitionKey)){
             //alert("Validation failed for " + fieldDefinition.name);
             isValid = false;
diff --git a/src/main/webapp/templates/forecastConfigurationForm.ftl b/src/main/webapp/templates/forecastConfigurationForm.ftl
index 4886e4a988f2ae473d51a3f994403725b2da0d97..376f35a73ad7d8b3fc5abd0c90756a76365897c5 100644
--- a/src/main/webapp/templates/forecastConfigurationForm.ftl
+++ b/src/main/webapp/templates/forecastConfigurationForm.ftl
@@ -44,6 +44,7 @@
 	</script>
 </#macro>
 <#macro page_contents>
+<div class="singleBlockContainer">
 	<p><a href="/forecastConfiguration" class="btn btn-default back" role="button">${i18nBundle.back}</a></p>
         <h1>${i18nBundle.viewForecastConfiguration}</h1>
         <div id="errorMsgEl" class="alert alert-danger" <#if !formValidation?has_content> style="display:none;"</#if>>
@@ -173,5 +174,6 @@
 	  <button type="button" class="btn btn-danger" onclick="if(confirm('${i18nBundle.confirmDelete}')){window.location.href='/forecastConfiguration?action=deleteForecastConfiguration&forecastConfigurationId=${forecastConfiguration.forecastConfigurationId}';}">${i18nBundle.delete}</button>
 	  </#if>
         </form>
+</div>
 </#macro>
 <@page_html/>
diff --git a/src/main/webapp/templates/forecastConfigurationList.ftl b/src/main/webapp/templates/forecastConfigurationList.ftl
index 2dc093c3a81b2123a95b103ad4a665994c2b7ef1..4ba2845c6c6d5c9b75175cb448678ffe4f0cf7c7 100644
--- a/src/main/webapp/templates/forecastConfigurationList.ftl
+++ b/src/main/webapp/templates/forecastConfigurationList.ftl
@@ -19,6 +19,7 @@
         <title>${i18nBundle.forecasts}</title>
 </#macro>
 <#macro page_contents>
+<div class="singleBlockContainer">
         <h1>${i18nBundle.forecasts}</h1>
         <#if messageKey?has_content>
 		<div class="alert alert-success">${i18nBundle(messageKey)}</div>
@@ -94,5 +95,6 @@
 		</tbody>
         </table>
         </div>
+</div>
 </#macro>
 <@page_html/>
diff --git a/src/main/webapp/templates/index.ftl b/src/main/webapp/templates/index.ftl
index b33fc4e39f409ae95604f21230c12d773791c2d3..1915ff655f1de983ad4ac895b914757d6441ee8e 100644
--- a/src/main/webapp/templates/index.ftl
+++ b/src/main/webapp/templates/index.ftl
@@ -19,6 +19,9 @@
         <title>${i18nBundle.greeting} VIPSLogic</title>
 </#macro>
 <#macro page_contents>
-        <h1>${i18nBundle.greeting} VIPSLogic</h1>      
+	<div class="singleBlockContainer">
+		<h1>${i18nBundle.greeting} VIPSLogic</h1>
+		<p>${i18nBundle.indexText}</p>
+        </div>
 </#macro>
 <@page_html/>
diff --git a/src/main/webapp/templates/master.ftl b/src/main/webapp/templates/master.ftl
index 5679f781d0dbb14502d23e088b3a8bbf788a7520..3a99b4dad217fb25a8b50e51d6503478c10b3ca5 100644
--- a/src/main/webapp/templates/master.ftl
+++ b/src/main/webapp/templates/master.ftl
@@ -26,7 +26,7 @@
 </#macro>
 <#macro page_contentwrap_start>
 <!-- start contentwrapper-->
-<div class="container">
+<div class="container" id="topContainer">
 	<nav class="navbar navbar-default" role="navigation">
 	  <!-- Brand and toggle get grouped for better mobile display -->
 	  <div class="navbar-header">
@@ -36,7 +36,7 @@
 	      <span class="icon-bar"></span>
 	      <span class="icon-bar"></span>
 	    </button>
-	    <a class="navbar-brand" href="/"><img src="/images/logo_vips.png" alt="VIPS logo"/> VIPSLogic</a>
+	    <a class="navbar-brand" href="/"><img src="/images/logo_vips.svg" alt="VIPS logo" style="height:48px;"/></a><span id="siteTitle" class="navbar-brand">${i18nBundle.VIPSLogicTitle}</span>
 	  </div>
 	<!-- Collect the nav links, forms, and other content for toggling -->
 	  <div class="collapse navbar-collapse navbar-ex1-collapse">
@@ -69,10 +69,12 @@
 	  </div><!-- /.navbar-collapse -->
 	  
 	</nav>
-
-	<div class="row">
-		<!-- Start contents container -->
-		<div class="col-md-12">
+</div>
+<div style="background-color: #d9e6e4;">
+	<div class="container" id="contentContainer" >
+		<div class="row">
+			<!-- Start contents container -->
+			<div class="col-md-12">
 	
 	
 </#macro>
@@ -80,9 +82,11 @@
         This is the default page contents.
 </#macro>
 <#macro page_contentwrap_end>
-		</div><!-- End contents container -->
-	</div><!-- End row with sidebar and contents container -->
-	<hr>
+			</div><!-- End contents container -->
+		</div><!-- End row with sidebar and contents container -->
+	</div><!-- End contentContainer -->
+</div><!-- End container with green bgcolor -->
+<div class="container" id="bottomContainer">
 	<footer>
 		<div class="row">
 			<div class="col-md-2">&copy; ${.now?string("yyyy")} <a href="http://www.nibio.no/" target="new">NIBIO</a></div>
@@ -99,9 +103,14 @@
 <html lang="no">
 <head>
 	<meta charset="utf-8">
+	<meta http-equiv="X-UA-Compatible" content="IE=edge">
 	<meta name="viewport" content="width=device-width, initial-scale=1.0">
 	<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
 	<link href="/css/3rdparty/bootstrap.min.css" rel="stylesheet" media="screen" />
+	<!-- Fonts from Google -->
+	<link href='https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400italic,200,200italic,300,300italic' rel='stylesheet' type='text/css'>
+	<!-- FontAwesome for icons -->
+	<link rel="stylesheet" href="/css/3rdparty/font-awesome.min.css">
 	<!-- Custom styles for VIPSLogic -->
 	<link href="/css/vipslogic.css" rel="stylesheet" media="screen" />
 	<@custom_css/>
diff --git a/src/main/webapp/templates/messageForm.ftl b/src/main/webapp/templates/messageForm.ftl
index cdd16778eb16181429e7c44d5743374978d4af44..116e7fac8640c7deb88fa0159cf40d2bcdc0e468 100644
--- a/src/main/webapp/templates/messageForm.ftl
+++ b/src/main/webapp/templates/messageForm.ftl
@@ -87,6 +87,7 @@
 	<link href="/css/3rdparty/chosen.min.css" rel="stylesheet" />
 </#macro>
 <#macro page_contents>
+<div class="singleBlockContainer">
 	<p><a href="/message" class="btn btn-default back" role="button">${i18nBundle.back}</a><#if message.messageId?has_content><a href="/message?action=newMessageForm" class="btn btn-default" role="button">${i18nBundle.addNew}</a></#if></p>
 	<#if message.messageId?has_content>
 	<h1>${i18nBundle.editMessage}</h1>
@@ -229,5 +230,6 @@
 	  </#if>
         <a href="/message" class="btn btn-default" role="button">${i18nBundle.cancel}</a>
 	</form>
+</div>
 </#macro>
 <@page_html/>
diff --git a/src/main/webapp/templates/messageList.ftl b/src/main/webapp/templates/messageList.ftl
index 102315834eb6bdcbbe4a2715291ec21e4b7bdac7..7fae5c069bb6603f192f4d19c695f5aeace40eb6 100644
--- a/src/main/webapp/templates/messageList.ftl
+++ b/src/main/webapp/templates/messageList.ftl
@@ -31,6 +31,7 @@
 	</script>
 </#macro>
 <#macro page_contents>
+<div class="singleBlockContainer">
         <h1>${i18nBundle.messages}</h1>
         <#if messageKey?has_content>
 		<div class="alert alert-success">${i18nBundle(messageKey)}</div>
@@ -88,5 +89,6 @@
 		</tbody>
         </table>
         </div>
+</div>
 </#macro>
 <@page_html/>
diff --git a/src/main/webapp/templates/notificationSubscriptionForm.ftl b/src/main/webapp/templates/notificationSubscriptionForm.ftl
index d04c0c9ff9e1176de9fa8b7ab82ffdf79b1a1bb8..9d4cc102a499313635163632b20aa86ce3b2d733 100644
--- a/src/main/webapp/templates/notificationSubscriptionForm.ftl
+++ b/src/main/webapp/templates/notificationSubscriptionForm.ftl
@@ -33,6 +33,7 @@
 	<link href="/css/3rdparty/chosen.min.css" rel="stylesheet" />
 </#macro>
 <#macro page_contents>
+<div class="singleBlockContainer">
 	<p><a href="/user" class="btn btn-default back" role="button">${i18nBundle.back}</a></p>
         <h1><#if viewUser.userId?has_content>${viewUser.firstName!""} ${viewUser.lastName}<#else>${i18nBundle.newUser}</#if></h1>
         <a href="/user?action=viewUser&userId=${viewUser.userId}" class="btn btn-default" role="button">${i18nBundle.userAccountInformation}</a>
@@ -113,6 +114,6 @@
           
           <button type="submit" class="btn btn-default">${i18nBundle.submit}</button>
 	</form>
-
+</div>
 </#macro>
 <@page_html/>
diff --git a/src/main/webapp/templates/observationForm.ftl b/src/main/webapp/templates/observationForm.ftl
index ffda393c465c1d9eb011da2d8fda344742c2ee30..ad908f63343cb3d409b28dbf38131915db2b1207 100755
--- a/src/main/webapp/templates/observationForm.ftl
+++ b/src/main/webapp/templates/observationForm.ftl
@@ -28,13 +28,17 @@
 	<script type="text/javascript" src="/js/3rdparty/jquery.datetimepicker.js"></script>
 	<script type="text/javascript" src="/js/3rdparty/modernizr_custom.js"></script>
 	<script type="text/javascript" src="/js/3rdparty/moment.min.js"></script>
+	<script src="/js/3rdparty/metawidget/core/metawidget-core.min.js" type="text/javascript"></script>
 	<script type="text/javascript" src="/js/3rdparty/ol.js"></script>
         <script type="text/javascript" src="js/3rdparty/ol3-layerswitcher.js"></script>
+        
 	<script type="text/javascript" src="/js/constants.js"></script>
 	<script type="text/javascript" src="/js/resourcebundle.js"></script>
 	<script type="text/javascript" src="/js/validateForm.js"></script>
 	<script type="text/javascript" src="/js/objectGISInfoMap.js"></script>
 	<script type="text/javascript">
+		var organizationId = ${user.organizationId.organizationId};
+		
 		$(document).ready(function() {
 			// Make sure that there is a datetime picker present for HTML5 
 			// date input fields
@@ -59,10 +63,54 @@
 			var chooseFromMapLayers = {"chooseFromMapLayers":<#if mapLayers?has_content>${mapLayers}<#else>[]</#if>};
 			initMap("observationFormMap",[${defaultMapCenter.x?c},${defaultMapCenter.y?c}],${defaultMapZoom},false, geoInfo, chooseFromMapLayers);
 			</#if>
+			
+			<#if observation.observationData?has_content>
+				observationData = ${observation.observationData};
+				getDataSchema(${observation.organism.organismId}, organizationId);
+			<#else>
+			// Setting 
+				
+			</#if>
 		});
+		
+		// Global for observationData metawidgets
+		var mw = null;
+		var observationData = null;
+		function initObservationData(organismId, organizationId) {
+			$.getJSON( "/rest/observationdata/model/" + organizationId + "/" + organismId, function( json ) {
+				observationData = json;
+				getDataSchema(organismId, organizationId);
+			});
+		}
+		
+		function getDataSchema(organismId, organizationId)
+		{
+			$.getJSON( "/rest/observationdata/schema/" + organizationId + "/" + organismId, buildForm);
+		}
+		
+		var buildForm = function(schemaProperties)
+		{
+			// Erasing whatever was there before
+			var metawidgetC = document.getElementById( 'metawidget' );
+			metawidgetC.innerHTML = "";
+			mw = new metawidget.Metawidget(metawidgetC , {
+					inspector: new metawidget.inspector.CompositeInspector( [ new metawidget.inspector.PropertyTypeInspector(),
+						function( toInspect, type, names ) {
+								return {
+									properties: schemaProperties
+								};
+							}
+					] )
+				}
+			);
+			mw.toInspect = observationData;
+			mw.buildWidgets();
+		};
+		
 	</script>
 </#macro>
 <#macro page_contents>
+<div class="singleBlockContainer">
 	<p><a href="/observation" class="btn btn-default back" role="button">${i18nBundle.back}</a></p>
         <h1><#if observation.observationId?has_content>${i18nBundle.editObservation}<#else>${i18nBundle.newObservation}</#if></h1>
         <div id="errorMsgEl" class="alert alert-danger" <#if !formValidation?has_content> style="display:none;"</#if>>
@@ -74,12 +122,17 @@
 	<div class="row">
 		<div class="col-md-6">
 			<#assign formId = "observationForm">
-			<form id="${formId}" role="form" action="/observation?action=observationFormSubmit" method="POST" onsubmit="this['geoInfo'].value=getFeatures();return validateForm(this);">
+			<form id="${formId}" role="form" action="/observation?action=observationFormSubmit" method="POST" onsubmit="this['geoInfo'].value=getFeatures();try{mw.save();console.log(this['observationData']);this['observationData'].value=JSON.stringify(mw.toInspect);return validateForm(this);}catch(e){console.log(e.message);console.log(e);return false;}">
+			<!--form id="${formId}" role="form" action="/observation?action=observationFormSubmit" method="POST" onsubmit="this['geoInfo'].value=getFeatures();mw.save();console.log(this['observationData']);this['observationData'].value=JSON.stringify(mw.toInspect);return validateForm(this);"-->
+			<!--form id="${formId}" role="form" action="/observation?action=observationFormSubmit" method="POST" onsubmit="this['geoInfo'].value=getFeatures();mw.save();console.log(this['observationData']);this['observationData'].value=JSON.stringify(mw.toInspect);validateForm(this);return false;"-->
 			  <input type="hidden" name="geoInfo" value=""/>
+			  <input type="hidden" name="observationData" value=""/>
 			  <input type="hidden" name="observationId" value="${observation.observationId!"-1"}"/>
+			  <!--button type="button" onclick="var theForm=document.getElementById('${formId}');console.log(theForm['geoInfo']);console.log(theForm['observationData']);theForm['geoInfo'].value=getFeatures();mw.save();console.log(theForm['observationData']);theForm['observationData'].value=JSON.stringify(mw.toInspect);validateForm(theForm);">Test</button-->
 			  <div class="form-group">
 			    <label for="organismId">${i18nBundle.organism}</label>
-			    <select class="form-control" name="organismId" onblur="validateField(this);">
+			    
+			    <select class="form-control" name="organismId" <#if observation.organism?has_content>readonly="readonly"<#else>onchange="initObservationData(this.options[this.options.selectedIndex].value,organizationId);" onblur="validateField(this);"</#if>>
 				<option value="-1">${i18nBundle.pleaseSelect} ${i18nBundle.organism?lower_case}</option>
 				<#list allOrganisms?sort_by("latinName") as organism>
 					<option value="${organism.organismId}"
@@ -88,6 +141,7 @@
 				</#list>
 			    </select>
 			    <span class="help-block" id="${formId}_organismId_validation"></span>
+			    
 			  </div>
 			  <!--div class="form-group">
 			    <label for="location">${i18nBundle.location}</label>
@@ -100,6 +154,12 @@
 			    <span class="help-block" id="${formId}_timeOfObservation_validation"></span>
 			  </div>
 			  <div class="form-group">
+			  <fieldset>
+			  	<legend>Observation data</legend>
+			  	<div id="metawidget"></div>
+			  </fieldset>
+			  </div>
+			  <!--div class="form-group">
 			    <label for="observedValue">${i18nBundle.observedValue}</label>
 			    <input type="number" class="form-control" name="observedValue" placeholder="${i18nBundle.observedValue}" value="${(observation.observedValue?c)!""}" onblur="validateField(this);"/>
 			    <span class="help-block" id="${formId}_observedValue_validation"></span>
@@ -108,7 +168,7 @@
 			    <label for="denominator">${i18nBundle.denominator}</label>
 			    <input type="number" class="form-control" name="denominator" placeholder="${i18nBundle.denominator}" value="${(observation.denominator?c)!"1"}" onblur="validateField(this);"/>
 			    <span class="help-block" id="${formId}_denominator_validation"></span>
-			  </div>
+			  </div-->
 			  <div class="form-group">
 			    <label for="observationMethodId">${i18nBundle.observationMethodId}</label>
 			    <select class="form-control" name="observationMethodId" onblur="validateField(this);">
@@ -120,7 +180,35 @@
 			     </select>
 			     <span class="help-block" id="${formId}_observationMethodId_validation"></span>
 			  </div>
-			  
+			  <div class="form-group">
+			    <label for="observationHeading">${i18nBundle.observationHeading}</label>
+			    <input type="text" class="form-control" name="observationHeading" placeholder="" value="${observation.observationHeading!""}" onblur="validateField(this);"/>
+			    <span class="help-block" id="${formId}_observationHeading_validation"></span>
+			  </div>
+			  <div class="form-group">
+			    <label for="observationText">${i18nBundle.observationText}</label>
+			    <textarea class="form-control" name="observationText" placeholder="" >${observation.observationText!""}</textarea>
+			    <span class="help-block" id="${formId}_observationText_validation"></span>
+			  </div>
+			  <#if user.isObservationAuthority() || user.isSuperUser()>
+				  <div class="form-group">
+				    <label for="statusTypeId">${i18nBundle.statusTypeId}</label>
+				    <select class="form-control" name="statusTypeId" onblur="validateField(this);">
+					<#list statusTypeIds as statusTypeId>
+						<option value="${statusTypeId.statusTypeId}"
+							<#if observation.statusTypeId?has_content && observation.statusTypeId == statusTypeId.statusTypeId>selected="selected"</#if>
+							<#if !observation.statusTypeId?has_content && 3 == statusTypeId.statusTypeId>selected="selected"</#if>
+						>${i18nBundle("statusTypeIdTitle_" + statusTypeId.statusTypeId)}</option>
+					</#list>
+				     </select>
+				     <span class="help-block" id="${formId}_statusTypeId_validation"></span>
+				  </div>
+				  <div class="form-group">
+				    <label for="statusRemarks">${i18nBundle.statusRemarks}</label>
+				    <textarea class="form-control" name="statusRemarks" placeholder="" >${observation.statusRemarks!""}</textarea>
+				    <span class="help-block" id="${formId}_statusRemarks_validation"></span>
+				  </div>
+			  </#if>
 			  <button type="submit" class="btn btn-default">${i18nBundle.submit}</button>
 			  <#if observation.observationId?has_content>
 			  <button type="button" class="btn btn-danger" onclick="if(confirm('${i18nBundle.confirmDelete}')){window.location.href='/observation?action=deleteObservation&observationId=${observation.observationId}';}">${i18nBundle.delete}</button>
@@ -132,5 +220,6 @@
 		</div>
 	</div>
 	<!--div style="display: none;"><div id="marker" title="Marker"><img src="/images/bug_medium.png"/></div></div-->
+</div>
 </#macro>
 <@page_html/>
diff --git a/src/main/webapp/templates/observationList.ftl b/src/main/webapp/templates/observationList.ftl
index 64138ba7d70de0415cf0b47c05c100c539a68ca5..6f28ff32e2fdee79a377e664621f2b5ed5c554e5 100644
--- a/src/main/webapp/templates/observationList.ftl
+++ b/src/main/webapp/templates/observationList.ftl
@@ -19,6 +19,7 @@
         <title>${i18nBundle.observations}</title>
 </#macro>
 <#macro page_contents>
+<div class="singleBlockContainer">
         <h1>${i18nBundle.observations}</h1>
         <p>TODO: Add map and filters for organism and time</p>
         <#if messageKey?has_content>
@@ -43,5 +44,6 @@
 		</tbody>
         </table>
         </div>
+</div>
 </#macro>
 <@page_html/>
diff --git a/src/main/webapp/templates/organism.ftl b/src/main/webapp/templates/organism.ftl
index 568bf8f4be1cc205bee20d425ba2af2d7f63b948..bda24e3d02a4b1cc9b6fdfe402206f937c2c5554 100644
--- a/src/main/webapp/templates/organism.ftl
+++ b/src/main/webapp/templates/organism.ftl
@@ -62,6 +62,7 @@ $(document).ready(function() {
 </#macro>
 
 <#macro page_contents>
+<div class="singleBlockContainer">
         <h1>${i18nBundle.organisms}</h1>
         <div class="row">
 		<div class="col-md-6">
@@ -74,5 +75,6 @@ $(document).ready(function() {
 		Skjema for redigering av organisme
 		</div>
 	</div>
+</div>
 </#macro>
 <@page_html/>
diff --git a/src/main/webapp/templates/organismDetails.ftl b/src/main/webapp/templates/organismDetails.ftl
index 0fdd28546733931bb9911efcf4b94fc32c2eb24c..5c43c23382dd45291f7a399be04f71019cdc1d1c 100644
--- a/src/main/webapp/templates/organismDetails.ftl
+++ b/src/main/webapp/templates/organismDetails.ftl
@@ -20,6 +20,7 @@
 </#macro>
 
 <#macro page_contents>
+<div class="singleBlockContainer">
         <h1>${organism.getLocalName(currentLocale.language)!""}/${organism.tradeName!""}/${organism.latinName!""}</h1>
         <dl class="dl-horizontal">
         
@@ -53,6 +54,6 @@
 	<a href="/organism?action=editOrganismForm&organismId=${organism.organismId}" class="btn btn-default" role="button">${i18nBundle.edit}</a>
 	</#if>
 	<a href="/organism?action=listChildOrganisms&organismId=${organism.parentOrganismId!"null"}" class="btn btn-default" role="button">${i18nBundle.back}</a>
-        
+</div>
 </#macro>
 <@page_html/>
diff --git a/src/main/webapp/templates/organismForm.ftl b/src/main/webapp/templates/organismForm.ftl
index fe153a10768e9f23715e3fa3aae7dc2ce93dc408..855c9638488ba40b5a1f366705c19785a381ce00 100644
--- a/src/main/webapp/templates/organismForm.ftl
+++ b/src/main/webapp/templates/organismForm.ftl
@@ -33,6 +33,7 @@
 
 </#macro>
 <#macro page_contents>
+<div class="singleBlockContainer">
 	<#if organism.organismId?has_content>
         <h1>${i18nBundle.edit} - ${organism.latinName!organism.tradeName}</h1>
         <#else>
@@ -126,6 +127,6 @@
         <button type="submit" class="btn btn-default">${i18nBundle.submit}</button>
         <a href="/organism?action=listChildOrganisms&organismId=${organism.parentOrganismId!"null"}" class="btn btn-default" role="button">${i18nBundle.cancel}</a>
 	</form>
-	
+</div>	
 </#macro>
 <@page_html/>
diff --git a/src/main/webapp/templates/organismList.ftl b/src/main/webapp/templates/organismList.ftl
index 77a1bd4450d833a3d5dc7a8882324a51807511e7..59c097f64d06fafce306aecb76389f37e5a802d0 100644
--- a/src/main/webapp/templates/organismList.ftl
+++ b/src/main/webapp/templates/organismList.ftl
@@ -20,6 +20,7 @@
         <title>${i18nBundle.organisms}<#if organism.organismId?has_content> - ${organism.localName!""}<#if organism.tradeName?has_content> / ${organism.tradeName}</#if> (${organism.latinName!""})</#if></title>
 </#macro>
 <#macro page_contents>
+<div class="singleBlockContainer">
         <h1>${i18nBundle.organisms}</h1>
         <#if organism.organismId?has_content>
         <h2>${organism.getLocalName(currentLocale.language)!""}<#if organism.tradeName?has_content> / ${organism.tradeName}</#if> (<i>${organism.latinName!""}</i>)</h2>
@@ -67,5 +68,6 @@
 		</tbody>
         </table>
         </div>
+</div>
 </#macro>
 <@page_html/>
diff --git a/src/main/webapp/templates/poi.ftl b/src/main/webapp/templates/poi.ftl
index 292adc404148c9e4f5813c6e19e7d4ab796fe3cd..960dd8238c1da3a9eb967692ebd1e195e24444b9 100644
--- a/src/main/webapp/templates/poi.ftl
+++ b/src/main/webapp/templates/poi.ftl
@@ -19,11 +19,13 @@
         <title>POI</title>
 </#macro>
 <#macro page_contents>
+<div class="singleBlockContainer">
         <h1>POI</h1>
 	<ul>
 	<#list weatherstations?sort_by("name") as weatherstation>
 	    <li>${weatherstation.name}<#if weatherstation.weatherStationDataSourceId??>, ${weatherstation.weatherStationDataSourceId.uri!}<#else></#if></li>
 	</#list>
         </ul>
+</div>
 </#macro>
 <@page_html/>
diff --git a/src/main/webapp/templates/runTaskManuallyForm.ftl b/src/main/webapp/templates/runTaskManuallyForm.ftl
index 4112445fbad916ca690035fed2512bbdab018dfb..0ef546696b881c05a942a78f481841eadb75f0de 100644
--- a/src/main/webapp/templates/runTaskManuallyForm.ftl
+++ b/src/main/webapp/templates/runTaskManuallyForm.ftl
@@ -39,6 +39,7 @@
 	</script>
 </#macro>
 <#macro page_contents>
+<div class="singleBlockContainer">
         <h1>${i18nBundle.configureAndRunManually}</h1>
         <h2>${vipsLogicTask.getName(currentLocale)}</h2>
         <form id="runTaskManuallyForm" role="form" action="/scheduling?action=runTaskManuallyFormSubmit" method="POST" onsubmit2="return false;" onsubmit="try{ return validateForm(this);}catch(err){alert(err);return false;}">
@@ -47,5 +48,6 @@
         <button type="submit" class="btn btn-default">${i18nBundle.runTask}</button>
 	<button type="button" class="btn btn-warning" onclick="window.location.href='/scheduling?action=viewAllTasks'">${i18nBundle.cancel}</button>
 	</form>
+</div>
 </#macro>
 <@page_html/>
diff --git a/src/main/webapp/templates/schedulingOverview.ftl b/src/main/webapp/templates/schedulingOverview.ftl
index 84f554f62a6fd548b442becc509435baec4f4c38..f419b840ba08c04ea8f010ee2ff0ad72ec52ada6 100644
--- a/src/main/webapp/templates/schedulingOverview.ftl
+++ b/src/main/webapp/templates/schedulingOverview.ftl
@@ -22,6 +22,7 @@
 	</script>
 </#macro>
 <#macro page_contents>
+<div class="singleBlockContainer">
         <h1>${i18nBundle.schedulingOverview}</h1>
         <div id="errorMsgEl" class="alert alert-danger" <#if !formValidation?has_content> style="display:none;"</#if>>
 		<#if formValidation?has_content>${formValidation.validationMessages?replace("\n", "<br>")}</#if>
@@ -116,6 +117,6 @@
 		</tbody>
         </table>
         </div>
-        
+</div>
 </#macro>
 <@page_html/>
diff --git a/src/main/webapp/templates/taskHistoryDetails.ftl b/src/main/webapp/templates/taskHistoryDetails.ftl
index 0bfc9801a2c1dbe52efcf4098853ecff9b411726..fb482fa35efc1437b8cd92ea128ff5021eededf8 100644
--- a/src/main/webapp/templates/taskHistoryDetails.ftl
+++ b/src/main/webapp/templates/taskHistoryDetails.ftl
@@ -20,6 +20,7 @@
 </#macro>
 
 <#macro page_contents>
+<div class="singleBlockContainer">
         <h1>${i18nBundle.taskHistoryDetails}</h1>
         <dl class="dl-horizontal">
 	  <dt>${i18nBundle.startTime}</dt>
@@ -41,6 +42,6 @@
 	  <dd>${taskHistory.message!""}</dd>
 	</dl>
 	<button type="button" class="btn btn-default" onclick="window.location.href='/scheduling'">&lt;&lt; ${i18nBundle.back}</button>
-        
+</div>
 </#macro>
 <@page_html/>
diff --git a/src/main/webapp/templates/taskList.ftl b/src/main/webapp/templates/taskList.ftl
index 814e0b8b96ff2be2cdf542ad58095c6a03d752b4..353c2c232e29779696ba9604201cb7919b87398c 100644
--- a/src/main/webapp/templates/taskList.ftl
+++ b/src/main/webapp/templates/taskList.ftl
@@ -19,6 +19,7 @@
         <title>${i18nBundle.tasks}</title>
 </#macro>
 <#macro page_contents>
+<div class="singleBlockContainer">
         <h1>${i18nBundle.tasks}</h1>
         <#if messageKey?has_content>
 		<div class="alert alert-success">${i18nBundle(messageKey)}</div>
@@ -41,5 +42,6 @@
 		</tbody>
         </table>
         </div>
+</div>
 </#macro>
 <@page_html/>
diff --git a/src/main/webapp/templates/userDeleteForm.ftl b/src/main/webapp/templates/userDeleteForm.ftl
index 28408ea1507806727ccbb7b81d9b2067bf2d88ec..728578dc94a106688fe50854e7f89699dec8cfb6 100644
--- a/src/main/webapp/templates/userDeleteForm.ftl
+++ b/src/main/webapp/templates/userDeleteForm.ftl
@@ -22,6 +22,7 @@
 	<script src="/js/resourcebundle.js"></script>
 </#macro>
 <#macro page_contents>
+<div class="singleBlockContainer">
         <h1>${i18nBundle.deleteUser} <#if viewUser.userId?has_content>${viewUser.firstName!""} ${viewUser.lastName}<#else>${i18nBundle.newUser}</#if></h1>
         <#if errorMsg?has_content>
         <div id="errorMsgEl" class="alert alert-danger">
@@ -53,6 +54,6 @@
 	<button type="submit" class="btn btn-danger">${i18nBundle.transferAndDelete}</button>
 	</#if>
 
-
+</div>
 </#macro>
 <@page_html/>
diff --git a/src/main/webapp/templates/userForm.ftl b/src/main/webapp/templates/userForm.ftl
index 1c24a030325c9a4608f688177e04c10b427cacfc..0bdd25ae13667fdc99060fe0b02df7acda22854e 100644
--- a/src/main/webapp/templates/userForm.ftl
+++ b/src/main/webapp/templates/userForm.ftl
@@ -27,6 +27,7 @@
 	</script>
 </#macro>
 <#macro page_contents>
+<div class="singleBlockContainer">
 	<p><a href="/user" class="btn btn-default back" role="button">${i18nBundle.back}</a></p>
         <h1><#if viewUser.userId?has_content>${viewUser.firstName!""} ${viewUser.lastName}<#else>${i18nBundle.newUser}</#if></h1>
         <#if viewUser.userId?has_content>
@@ -133,6 +134,6 @@
 	  <button type="button" class="btn btn-danger" onclick="if(confirm('${i18nBundle.confirmDelete}')){window.location.href='/user?action=userDeleteForm&userId=${viewUser.userId}';}">${i18nBundle.delete}</button>
 	  </#if>
 	</form>
-
+</div>
 </#macro>
 <@page_html/>
diff --git a/src/main/webapp/templates/userList.ftl b/src/main/webapp/templates/userList.ftl
index b714c073a453f825794e93dfd1a8c33c4171db78..76689a0358bdc97c50e9ee1e1a8f2175e0f59f6b 100644
--- a/src/main/webapp/templates/userList.ftl
+++ b/src/main/webapp/templates/userList.ftl
@@ -19,6 +19,7 @@
         <title>${i18nBundle.users}</title>
 </#macro>
 <#macro page_contents>
+<div class="singleBlockContainer">
         <h1>${i18nBundle.users}</h1>
 		<p>
 		<a href="/user?action=newUser" class="btn btn-default" role="button">${i18nBundle.addNew}</a>
@@ -42,5 +43,6 @@
 		</tbody>
         </table>
         </div>
+</div>
 </#macro>
 <@page_html/>
diff --git a/src/main/webapp/templates/weatherstationDeletePreview.ftl b/src/main/webapp/templates/weatherstationDeletePreview.ftl
index 1a02f91841bd05e888a1c86fe61cd0c467308f05..366bd7eaab0adb23f1faf2d2b159a794de9f124f 100644
--- a/src/main/webapp/templates/weatherstationDeletePreview.ftl
+++ b/src/main/webapp/templates/weatherstationDeletePreview.ftl
@@ -27,6 +27,7 @@
 	<script type="text/javascript" src="/js/validateForm.js"></script>
 </#macro>
 <#macro page_contents>
+<div class="singleBlockContainer">
 	<p><a href="${returnURL}" class="btn btn-default cancel" role="button">${i18nBundle.cancel}</a></p>
         <h1>${i18nBundle.deleteWeatherStation} ${weatherStation.name}</h1>
         <div id="errorMsgEl" class="alert alert-danger" <#if !formValidation?has_content> style="display:none;"</#if>>
@@ -65,6 +66,6 @@
 		<a href="${returnURL}" class="btn btn-default cancel" role="button">${i18nBundle.cancel}</a>
 		<button type="button" class="btn btn-danger" onclick="if(confirm('${i18nBundle.confirmDelete}')){window.location.href='/weatherStation?action=deleteWeatherStation&pointOfInterestId=${weatherStation.pointOfInterestId}';}">${i18nBundle.deleteWeatherStation}</button>
 	</p>
-	
+</div>
 </#macro>
 <@page_html/>
diff --git a/src/main/webapp/templates/weatherstationForm.ftl b/src/main/webapp/templates/weatherstationForm.ftl
index 0ad09bdd988aab259c899ac19431178c860a1d0a..4873e82b86945d53728c69f3440b1d4967331ca4 100644
--- a/src/main/webapp/templates/weatherstationForm.ftl
+++ b/src/main/webapp/templates/weatherstationForm.ftl
@@ -44,6 +44,7 @@
 	</script>
 </#macro>
 <#macro page_contents>
+<div class="singleBlockContainer">
 	<p><a href="${returnURL}" class="btn btn-default back" role="button">${i18nBundle.back}</a></p>
         <h1><#if weatherStation.pointOfInterestId?has_content>${i18nBundle.editWeatherStation}<#else>${i18nBundle.newWeatherStation}</#if></h1>
         <div id="errorMsgEl" class="alert alert-danger" <#if !formValidation?has_content> style="display:none;"</#if>>
@@ -177,5 +178,6 @@
 		</div>
 	</div>
 	<div style="display: none;"><div id="stationMarker" title="Marker"><img src="/public/images/anemometer_mono.png"/></div></div>
+</div>
 </#macro>
 <@page_html/>
diff --git a/src/main/webapp/templates/weatherstationList.ftl b/src/main/webapp/templates/weatherstationList.ftl
index 37a199a4b02ec87f8df70e5740b7a3d530f12fea..c2900b81a3049a1dca51c3900f58523f0ae867e4 100644
--- a/src/main/webapp/templates/weatherstationList.ftl
+++ b/src/main/webapp/templates/weatherstationList.ftl
@@ -38,6 +38,7 @@
 	</script>
 </#macro>
 <#macro page_contents>
+<div class="singleBlockContainer">
 <h1>${i18nBundle.weatherStations}</h1>
 <#if messageKey?has_content>
 	<div class="alert alert-success">${i18nBundle(messageKey)}</div>
@@ -127,5 +128,6 @@
 		</div>
 	</div>
 </div>
+</div>
 </#macro>
 <@page_html/>
diff --git a/src/main/webapp/templates/weatherstationView.ftl b/src/main/webapp/templates/weatherstationView.ftl
index c0654aeefcd2eda5a42bc65046324f34caa13984..30e7d33626ab62e0714c802ad21215020b3b61ee 100644
--- a/src/main/webapp/templates/weatherstationView.ftl
+++ b/src/main/webapp/templates/weatherstationView.ftl
@@ -50,6 +50,7 @@
 </#macro>
 
 <#macro page_contents>
+<div class="singleBlockContainer">
 	<p><a href="${returnURL}" class="btn btn-default back" role="button">${i18nBundle.back}</a></p>
         <h1>${weatherStation.name}</h1>
         <div class="row">
@@ -171,6 +172,6 @@
 	</div>
         
 	
-	
+</div>
 </#macro>
 <@page_html/>