Skip to content
Snippets Groups Projects
Commit 673d2a65 authored by Tor-Einar Skog's avatar Tor-Einar Skog
Browse files

Added forecastconfiguration form

Improved form validation, added control for date ordering
parent 99625740
Branches
Tags
No related merge requests found
Showing
with 987 additions and 57 deletions
package no.bioforsk.vips.logic.controller.servlet;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import no.bioforsk.vips.logic.controller.session.ForecastBean;
import no.bioforsk.vips.logic.controller.session.UserBean;
import no.bioforsk.vips.logic.entity.PointOfInterest;
import no.bioforsk.vips.logic.entity.PointOfInterestWeatherStation;
import no.bioforsk.vips.logic.entity.VipsLogicRole;
import no.bioforsk.vips.logic.entity.VipsLogicUser;
import no.bioforsk.vips.logic.scheduling.model.ForecastConfiguration;
import no.bioforsk.vips.logic.util.SessionControllerGetter;
import no.bioforsk.vips.util.ServletUtil;
import no.bioforsk.web.forms.FormField;
import no.bioforsk.web.forms.FormValidation;
import no.bioforsk.web.forms.FormValidator;
/**
* Handles form configuration actions
* @copyright 2013 <a href="http://www.bioforsk.no/">Bioforsk</a>
* @author Tor-Einar Skog <tor-einar.skog@bioforsk.no>
*/
public class ForecastConfigurationController extends HttpServlet {
@PersistenceContext(unitName="VIPSLogic-PU")
EntityManager em;
/**
* Processes requests for both HTTP <code>GET</code> and <code>POST</code>
* methods.
*
* @param request servlet request
* @param response servlet response
* @throws ServletException if a servlet-specific error occurs
* @throws IOException if an I/O error occurs
*/
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
String action = request.getParameter("action");
VipsLogicUser user = (VipsLogicUser) request.getSession().getAttribute("user");
ForecastBean forecastBean = SessionControllerGetter.getForecastBean();
UserBean userBean = SessionControllerGetter.getUserBean();
// Default: View list of users
// for SUPERUSERS and ORGANIZATION ADMINS
if(action == null)
{
if(userBean.authorizeUser(user, VipsLogicRole.ORGANIZATION_ADMINISTRATOR, VipsLogicRole.SUPERUSER))
{
List<ForecastConfiguration> forecasts;
if(user.isSuperUser())
{
forecasts = forecastBean.getForecastConfigurations();
}
else
{
forecasts = forecastBean.getForecastConfigurations(user.getOrganizationId());
}
request.setAttribute("forecastConfigurations", forecasts);
request.setAttribute("modelInformation", forecastBean.getIndexedModelInformation());
// If this is a redirect from a controller, with a message to be passed on
request.setAttribute("messageKey", request.getParameter("messageKey"));
request.getRequestDispatcher("/forecastConfigurationList.ftl").forward(request, response);
}
else
{
response.sendError(403,"Access not authorized"); // HTTP Forbidden
}
}
// View and edit a forecast configuration
// for SUPERUSERS and ORGANIZATION ADMINS
else if(action.equals("viewForecastConfiguration"))
{
if(userBean.authorizeUser(user, VipsLogicRole.ORGANIZATION_ADMINISTRATOR, VipsLogicRole.SUPERUSER))
{
try
{
Long forecastConfigurationId = Long.valueOf(request.getParameter("forecastConfigurationId"));
ForecastConfiguration forecastConfiguration = em.find(ForecastConfiguration.class, forecastConfigurationId);
// No forecastconfiguration found, assuming user want to register new
if(forecastConfiguration == null)
{
forecastConfiguration = new ForecastConfiguration();
}
// Only superusers can view and edit forecasts from other organizations
if(! user.isSuperUser() && forecastConfiguration.getVipsLogicUserId() != null && !forecastConfiguration.getVipsLogicUserId().getOrganizationId().equals(user.getOrganizationId()))
{
response.sendError(403,"Access not authorized"); // HTTP Forbidden
}
else
{
// We must get date formats!
Map<String, FormField> formFields = FormValidator.getFormFields("forecastConfigurationForm",getServletContext());
// TODO: More intelligent selection of locations, weather stations and users
request.setAttribute("locationPointOfInterests", em.createNamedQuery("PointOfInterest.findAll").getResultList());
request.setAttribute("weatherStationPointOfInterests", em.createNamedQuery("PointOfInterestWeatherStation.findAll").getResultList());
request.setAttribute("vipsLogicUsers", em.createNamedQuery("VipsLogicUser.findAll").getResultList());
request.setAttribute("dateStart_dateFormat", formFields.get("dateStart").getDateFormat());
request.setAttribute("dateEnd_dateFormat", formFields.get("dateEnd").getDateFormat());
request.setAttribute("forecastConfiguration", forecastConfiguration);
request.setAttribute("modelInformations", em.createNamedQuery("ModelInformation.findAll").getResultList());
request.setAttribute("messageKey", request.getParameter("messageKey"));
request.getRequestDispatcher("/forecastConfigurationForm.ftl").forward(request, response);
}
}
catch(NullPointerException | NumberFormatException ex)
{
response.sendError(500, "Invalid forecast configurationId " + request.getParameter("forecastConfigurationId"));
}
}
else
{
response.sendError(403,"Access not authorized"); // HTTP Forbidden
}
}
// Store forecast configuration
// Authorization: SUPERUSERS and ORGANIZATION ADMINS
else if(action.equals("forecastConfigurationFormSubmit"))
{
if(userBean.authorizeUser(user, VipsLogicRole.ORGANIZATION_ADMINISTRATOR, VipsLogicRole.SUPERUSER))
{
try
{
Long forecastConfigurationId = Long.valueOf(request.getParameter("forecastConfigurationId"));
ForecastConfiguration forecastConfiguration = em.find(ForecastConfiguration.class, forecastConfigurationId);
// No forecastconfiguration found, assuming user want to register new
if(forecastConfiguration == null)
{
forecastConfiguration = new ForecastConfiguration();
}
// Only superusers can view and edit forecasts from other organizations
if(! user.isSuperUser() && forecastConfiguration.getVipsLogicUserId() != null && !forecastConfiguration.getVipsLogicUserId().getOrganizationId().equals(user.getOrganizationId()))
{
response.sendError(403,"Access not authorized"); // HTTP Forbidden
}
else
{
// Standard form validation
FormValidation formValidation = FormValidator.validateForm("forecastConfigurationForm",request,getServletContext());
if(formValidation.isValid())
{
// Store form config
forecastConfiguration.setModelId(formValidation.getFormField("modelId").getWebValue());
PointOfInterest locationPoi = em.find(PointOfInterest.class, formValidation.getFormField("locationPointOfInterestId").getValueAsInteger());
forecastConfiguration.setLocationPointOfInterestId(locationPoi);
PointOfInterest weatherStationPoi = em.find(PointOfInterestWeatherStation.class, formValidation.getFormField("weatherStationPointOfInterestId").getValueAsInteger());
forecastConfiguration.setWeatherStationPointOfInterestId(weatherStationPoi);
forecastConfiguration.setDateStart(formValidation.getFormField("dateStart").getValueAsDate());
forecastConfiguration.setDateEnd(formValidation.getFormField("dateEnd").getValueAsDate());
VipsLogicUser forecastConfigurationUser = em.find(VipsLogicUser.class, formValidation.getFormField("vipsLogicUserId").getValueAsInteger());
forecastConfiguration.setVipsCoreUserId(forecastConfigurationUser);
forecastBean.storeForecastConfiguration(forecastConfiguration);
request.setAttribute("messageKey", request.getParameter("formConfigurationUpdated"));
response.sendRedirect(new StringBuilder("http://").append(ServletUtil.getServerName(request)).append("/forecastConfiguration?action=viewForecastConfiguration&forecastConfigurationId=").append(forecastConfiguration.getForecastConfigurationId()).append("&messageKey=").append("forecastConfigurationUpdated").toString());
}
else
{
// Return to form with error messages
request.setAttribute("formValidation", formValidation);
// We must get date formats!
Map<String, FormField> formFields = FormValidator.getFormFields("forecastConfigurationForm",getServletContext());
// TODO: More intelligent selection of locations, weather stations and users
request.setAttribute("locationPointOfInterests", em.createNamedQuery("PointOfInterest.findAll").getResultList());
request.setAttribute("weatherStationPointOfInterests", em.createNamedQuery("PointOfInterestWeatherStation.findAll").getResultList());
request.setAttribute("vipsLogicUsers", em.createNamedQuery("VipsLogicUser.findAll").getResultList());
request.setAttribute("dateStart_dateFormat", formFields.get("dateStart").getDateFormat());
request.setAttribute("dateEnd_dateFormat", formFields.get("dateEnd").getDateFormat());
request.setAttribute("modelInformations", em.createNamedQuery("ModelInformation.findAll").getResultList());
request.setAttribute("forecastConfiguration", forecastConfiguration);
request.getRequestDispatcher("/forecastConfigurationForm.ftl").forward(request, response);
}
}
}
catch(NullPointerException | NumberFormatException ex)
{
response.sendError(500, "Invalid forecast configurationId " + request.getParameter("forecastConfigurationId"));
}
}
else
{
response.sendError(403,"Access not authorized"); // HTTP Forbidden
}
}
}
// <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code.">
/**
* Handles the HTTP <code>GET</code> method.
*
* @param request servlet request
* @param response servlet response
* @throws ServletException if a servlet-specific error occurs
* @throws IOException if an I/O error occurs
*/
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* Handles the HTTP <code>POST</code> method.
*
* @param request servlet request
* @param response servlet response
* @throws ServletException if a servlet-specific error occurs
* @throws IOException if an I/O error occurs
*/
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* Returns a short description of the servlet.
*
* @return a String containing servlet description
*/
@Override
public String getServletInfo() {
return "Short description";
}// </editor-fold>
}
......@@ -29,7 +29,7 @@ import no.bioforsk.web.forms.FormValidator;
* @copyright 2013 <a href="http://www.bioforsk.no/">Bioforsk</a>
* @author Tor-Einar Skog <tor-einar.skog@bioforsk.no>
*/
public class UserControllerServlet extends HttpServlet {
public class UserController extends HttpServlet {
@PersistenceContext(unitName="VIPSLogic-PU")
EntityManager em;
......
package no.bioforsk.vips.logic.controller.session;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;
import no.bioforsk.vips.coremanager.service.ManagerResource;
import no.bioforsk.vips.logic.entity.ForecastResult;
import no.bioforsk.vips.logic.entity.ModelInformation;
import no.bioforsk.vips.logic.entity.Organization;
import no.bioforsk.vips.logic.entity.VipsLogicUser;
import no.bioforsk.vips.logic.scheduling.model.ForecastConfiguration;
import org.codehaus.jackson.JsonNode;
import org.jboss.resteasy.client.jaxrs.ResteasyWebTarget;
/**
* @copyright 2013 <a href="http://www.bioforsk.no/">Bioforsk</a>
......@@ -51,6 +64,24 @@ public class ForecastBean {
return q.getResultList();
}
/**
* Returns _ALL_ forecasts. Not for the faint hearted
* @return
*/
public List<ForecastConfiguration> getForecastConfigurations()
{
return em.createNamedQuery("ForecastConfiguration.findAll").getResultList();
}
public List<ForecastConfiguration> getForecastConfigurations(Organization organization)
{
List<VipsLogicUser> organizationUsers = em
.createNamedQuery("VipsLogicUser.findByOrganizationId")
.setParameter("organizationId", organization)
.getResultList();
return em.createNamedQuery("ForecastConfiguration.findByVipsLogicUserIds").setParameter("vipsLogicUserIds", organizationUsers).getResultList();
}
/**
* Fetches one specific forecast configuration
* @param forecastConfigurationId
......@@ -60,4 +91,70 @@ public class ForecastBean {
{
return em.find(ForecastConfiguration.class, forecastConfigurationId);
}
/**
* Requests all info about models currently available in VIPSCoreManager
* Stores in local db for easy access.
*/
public void updateModelInformation()
{
// Get all model Ids from Core Manager
Response resp = this.getManagerResource().printModelListJSON();
for(JsonNode modelIdItem: resp.readEntity(JsonNode.class).findValues("modelId"))
{
String modelId = modelIdItem.getValueAsText();
// We get the corresponding modelInformation entry
ModelInformation modelInformation = em.find(ModelInformation.class, modelId);
if(modelInformation == null)
{
modelInformation = new ModelInformation(modelId);
em.persist(modelInformation);
modelInformation.setDateFirstRegistered(new Date());
}
// Retrieve and store information
modelInformation.setDefaultName(this.getManagerResource().printModelName(modelId).readEntity(String.class));
modelInformation.setDefaultDescription(this.getManagerResource().printModelDescription(modelId).readEntity(String.class));
modelInformation.setLicense(this.getManagerResource().printModelLicense(modelId).readEntity(String.class));
modelInformation.setCopyrightHolder(this.getManagerResource().printModelCopyright(modelId).readEntity(String.class));
modelInformation.setUsage(this.getManagerResource().printModelUsage(modelId).readEntity(String.class));
modelInformation.setSampleConfig(this.getManagerResource().printModelSampleConfig(modelId).readEntity(String.class));
modelInformation.setDateLastRegistered(new Date());
}
}
/**
*
* @return All registered models accessible by ModelId as key
*/
public Map<String,ModelInformation> getIndexedModelInformation()
{
Map<String, ModelInformation> retVal = new HashMap<>();
for(ModelInformation mi: (List<ModelInformation>) em.createNamedQuery("ModelInformation.findAll").getResultList())
{
retVal.put(mi.getModelId(), mi);
}
return retVal;
}
public void storeForecastConfiguration(ForecastConfiguration forecastConfiguration)
{
em.merge(forecastConfiguration);
}
/**
* Get the interface for REST resources in VIPSCoreManager
* @return
*/
private ManagerResource getManagerResource()
{
Client client = ClientBuilder.newClient();
WebTarget target = client.target(System.getProperty("no.bioforsk.vips.coremanager.VIPSCOREMANAGER_URL"));
ResteasyWebTarget rTarget = (ResteasyWebTarget) target;
ManagerResource resource = rTarget.proxy(ManagerResource.class);
return resource;
}
}
......@@ -123,15 +123,6 @@ public class UserBean {
public void deleteUser(VipsLogicUser user)
{
user = em.find(VipsLogicUser.class, user.getUserId());
// Remove dependent stuff
/* User authentication for this user
for(UserAuthentication auth: user.getUserAuthenticationSet())
{
System.out.println("Auth[DEBUG]=" + auth.getUsername());
em.remove(auth);
}
*/
em.remove(user);
}
......
package no.bioforsk.vips.logic.entity;
import java.io.Serializable;
import java.util.Date;
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.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import javax.xml.bind.annotation.XmlRootElement;
/**
* @copyright 2013 <a href="http://www.bioforsk.no/">Bioforsk</a>
* @author Tor-Einar Skog <tor-einar.skog@bioforsk.no>
*/
@Entity
@Table(name = "model_information")
@XmlRootElement
@NamedQueries({
@NamedQuery(name = "ModelInformation.findAll", query = "SELECT m FROM ModelInformation m"),
@NamedQuery(name = "ModelInformation.findByModelId", query = "SELECT m FROM ModelInformation m WHERE m.modelId = :modelId"),
@NamedQuery(name = "ModelInformation.findByDateFirstRegistered", query = "SELECT m FROM ModelInformation m WHERE m.dateFirstRegistered = :dateFirstRegistered"),
@NamedQuery(name = "ModelInformation.findByDateLastRegistered", query = "SELECT m FROM ModelInformation m WHERE m.dateLastRegistered = :dateLastRegistered"),
@NamedQuery(name = "ModelInformation.findByDefaultName", query = "SELECT m FROM ModelInformation m WHERE m.defaultName = :defaultName"),
@NamedQuery(name = "ModelInformation.findByCopyrightHolder", query = "SELECT m FROM ModelInformation m WHERE m.copyrightHolder = :copyrightHolder"),
@NamedQuery(name = "ModelInformation.findByLicense", query = "SELECT m FROM ModelInformation m WHERE m.license = :license"),
@NamedQuery(name = "ModelInformation.findByDefaultDescription", query = "SELECT m FROM ModelInformation m WHERE m.defaultDescription = :defaultDescription"),
@NamedQuery(name = "ModelInformation.findBySampleConfig", query = "SELECT m FROM ModelInformation m WHERE m.sampleConfig = :sampleConfig"),
@NamedQuery(name = "ModelInformation.findByUsage", query = "SELECT m FROM ModelInformation m WHERE m.usage = :usage")})
public class ModelInformation implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@Basic(optional = false)
@NotNull
@Size(min = 1, max = 10)
@Column(name = "model_id")
private String modelId;
@Column(name = "date_first_registered")
@Temporal(TemporalType.DATE)
private Date dateFirstRegistered;
@Column(name = "date_last_registered")
@Temporal(TemporalType.DATE)
private Date dateLastRegistered;
@Size(max = 63)
@Column(name = "default_name")
private String defaultName;
@Size(max = 255)
@Column(name = "copyright_holder")
private String copyrightHolder;
@Size(max = 2147483647)
@Column(name = "license")
private String license;
@Size(max = 2147483647)
@Column(name = "default_description")
private String defaultDescription;
@Size(max = 2147483647)
@Column(name = "sample_config")
private String sampleConfig;
@Size(max = 2147483647)
@Column(name = "usage")
private String usage;
public ModelInformation() {
}
public ModelInformation(String modelId) {
this.modelId = modelId;
}
public String getModelId() {
return modelId;
}
public void setModelId(String modelId) {
this.modelId = modelId;
}
public Date getDateFirstRegistered() {
return dateFirstRegistered;
}
public void setDateFirstRegistered(Date dateFirstRegistered) {
this.dateFirstRegistered = dateFirstRegistered;
}
public Date getDateLastRegistered() {
return dateLastRegistered;
}
public void setDateLastRegistered(Date dateLastRegistered) {
this.dateLastRegistered = dateLastRegistered;
}
public String getDefaultName() {
return defaultName;
}
public void setDefaultName(String defaultName) {
this.defaultName = defaultName;
}
public String getCopyrightHolder() {
return copyrightHolder;
}
public void setCopyrightHolder(String copyrightHolder) {
this.copyrightHolder = copyrightHolder;
}
public String getLicense() {
return license;
}
public void setLicense(String license) {
this.license = license;
}
public String getDefaultDescription() {
return defaultDescription;
}
public void setDefaultDescription(String defaultDescription) {
this.defaultDescription = defaultDescription;
}
public String getSampleConfig() {
return sampleConfig;
}
public void setSampleConfig(String sampleConfig) {
this.sampleConfig = sampleConfig;
}
public String getUsage() {
return usage;
}
public void setUsage(String usage) {
this.usage = usage;
}
@Override
public int hashCode() {
int hash = 0;
hash += (modelId != null ? modelId.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 ModelInformation)) {
return false;
}
ModelInformation other = (ModelInformation) object;
if ((this.modelId == null && other.modelId != null) || (this.modelId != null && !this.modelId.equals(other.modelId))) {
return false;
}
return true;
}
@Override
public String toString() {
return "no.bioforsk.vips.logic.entity.ModelInformation[ modelId=" + modelId + " ]";
}
}
......@@ -22,6 +22,7 @@ import javax.validation.constraints.Size;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import no.bioforsk.vips.logic.entity.PointOfInterest;
import no.bioforsk.vips.logic.entity.VipsLogicUser;
import org.codehaus.jackson.annotate.JsonIgnore;
/**
......@@ -37,7 +38,9 @@ import org.codehaus.jackson.annotate.JsonIgnore;
@NamedQuery(name = "ForecastConfiguration.findByModelId", query = "SELECT f FROM ForecastConfiguration f WHERE f.modelId = :modelId"),
@NamedQuery(name = "ForecastConfiguration.findByDateStart", query = "SELECT f FROM ForecastConfiguration f WHERE f.dateStart = :dateStart"),
@NamedQuery(name = "ForecastConfiguration.findByDateEnd", query = "SELECT f FROM ForecastConfiguration f WHERE f.dateEnd = :dateEnd"),
@NamedQuery(name = "ForecastConfiguration.findByVipsCoreUserId", query = "SELECT f FROM ForecastConfiguration f WHERE f.vipsCoreUserId = :vipsCoreUserId")})
@NamedQuery(name = "ForecastConfiguration.findByVipsLogicUserId", query = "SELECT f FROM ForecastConfiguration f WHERE f.vipsLogicUserId = :vipsLogicUserId"),
@NamedQuery(name = "ForecastConfiguration.findByVipsLogicUserIds", query = "SELECT f FROM ForecastConfiguration f WHERE f.vipsLogicUserId IN (:vipsLogicUserIds)")
})
public class ForecastConfiguration implements Serializable {
@OneToMany(cascade = CascadeType.ALL, mappedBy = "forecastConfiguration")
private Set<ForecastModelConfiguration> forecastModelConfigurationSet;
......@@ -56,8 +59,9 @@ public class ForecastConfiguration implements Serializable {
@Column(name = "date_end")
@Temporal(TemporalType.DATE)
private Date dateEnd;
@Column(name = "vips_core_user_id")
private Integer vipsCoreUserId;
@JoinColumn(name = "vips_logic_user_id", referencedColumnName = "user_id")
@ManyToOne(optional = false)
private VipsLogicUser vipsLogicUserId;
@JoinColumn(name = "location_point_of_interest_id", referencedColumnName = "point_of_interest_id")
@ManyToOne
private PointOfInterest locationPointOfInterestId;
......@@ -104,12 +108,12 @@ public class ForecastConfiguration implements Serializable {
this.dateEnd = dateEnd;
}
public Integer getVipsCoreUserId() {
return vipsCoreUserId;
public VipsLogicUser getVipsLogicUserId() {
return vipsLogicUserId;
}
public void setVipsCoreUserId(Integer vipsCoreUserId) {
this.vipsCoreUserId = vipsCoreUserId;
public void setVipsCoreUserId(VipsLogicUser vipsLogicUserId) {
this.vipsLogicUserId = vipsLogicUserId;
}
public PointOfInterest getLocationPointOfInterestId() {
......
package no.bioforsk.web.forms;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.codehaus.jackson.annotate.JsonIgnore;
/**
......@@ -17,12 +20,14 @@ public class FormField {
public static String FORM_FIELD_TYPE_STRING = "STRING";
public static String FORM_FIELD_TYPE_INTEGER = "INTEGER";
public static String FORM_FIELD_TYPE_DOUBLE = "DOUBLE";
public static String FORM_FIELD_TYPE_DATE = "DATE";
public static String FORM_FIELD_TYPE_PASSWORD = "PASSWORD";
public static String FORM_FIELD_TYPE_EMAIL = "EMAIL";
public static String FORM_FIELD_TYPE_SELECT_SINGLE = "SELECT_SINGLE";
public static String FORM_FIELD_TYPE_SELECT_MULTIPLE = "SELECT_MULTIPLE";
// Standard format for dates is the ISO-8861-format
public static String DATE_DEFAULT_FORMAT = "yyyy-MM-dd";
private String name;
private String label;
......@@ -36,6 +41,7 @@ public class FormField {
private Integer minValue;
private Integer maxValue;
private String nullValue;
private String dateFormat;
public Integer getValueAsInteger()
......@@ -53,6 +59,17 @@ public class FormField {
return Double.valueOf(this.webValue[0]);
}
public Date getValueAsDate()
{
try
{
return new SimpleDateFormat(this.getDateFormat()).parse(this.getWebValue());
}
catch(ParseException ex)
{
return null;
}
}
/**
......@@ -253,5 +270,26 @@ public class FormField {
this.nullValue = nullValue;
}
/**
* @return the dateFormat (if not set, it returns FormField.DATE_DEFAULT_FORMAT
*/
public String getDateFormat() {
if(this.dateFormat != null)
{
return dateFormat;
}
else
{
return DATE_DEFAULT_FORMAT;
}
}
/**
* @param dateFormat the dateFormat to set
*/
public void setDateFormat(String dateFormat) {
this.dateFormat = dateFormat;
}
}
......@@ -3,8 +3,13 @@ package no.bioforsk.web.forms;
import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import no.bioforsk.vips.logic.authenticate.PasswordValidationException;
......@@ -27,15 +32,14 @@ import org.codehaus.jackson.type.TypeReference;
* @author Tor-Einar Skog <tor-einar.skog@bioforsk.no>
*/
public class FormValidator {
public static String RELATION_TYPE_EQUALS = "EQUALS";
public static String RELATION_TYPE_AFTER = "AFTER";
public static FormValidation validateForm(String formName, HttpServletRequest request, ServletContext servletContext) throws IOException {
ObjectMapper mapper = new ObjectMapper();
JsonFactory factory = mapper.getJsonFactory();
InputStream in = servletContext.getResourceAsStream("/formdefinitions/" + formName + ".json");
JsonParser parser = factory.createJsonParser(in);
JsonNode formDefinition = mapper.readTree(parser);
List<FormField> fields = mapper.convertValue(formDefinition.findValue("fields"), new TypeReference<List<FormField>>(){});
JsonNode formDefinition = getFormDefinition(formName, servletContext);
List<FormField> fields = new ObjectMapper().convertValue(formDefinition.findValue("fields"), new TypeReference<List<FormField>>(){});
FormValidation retVal = new FormValidation();
for(FormField field: fields)
......@@ -63,6 +67,23 @@ public class FormValidator {
}
}
// Dates
// We try to conver to date using the given format
if(field.getType().equals(FormField.FORM_FIELD_TYPE_DATE))
{
SimpleDateFormat format = new SimpleDateFormat(field.getDateFormat());
try
{
Date date = format.parse(field.getWebValue());
}
catch(ParseException ex)
{
field.setValid(false);
field.setValidationMessage(MessageFormat.format(SessionLocaleUtil.getI18nText(request, "doesNotMatchDateFormat"), field.getDateFormat()));
}
}
if(field.getType().equals(FormField.FORM_FIELD_TYPE_PASSWORD))
{
try
......@@ -147,34 +168,93 @@ public class FormValidator {
}
// Check repeats
JsonNode repeatFields = formDefinition.findValue("repeatFields");
if(repeatFields != null)
JsonNode relations = formDefinition.findValue("relations");
if(relations != null)
{
for(JsonNode item: repeatFields)
for(JsonNode item: relations)
{
String relationType = item.findValue("relationType").getTextValue();
String primaryFieldName = item.findValue("primaryField").getTextValue();
String repeatFieldName = item.findValue("repeatField").getTextValue();
String secondaryFieldName = item.findValue("secondaryField").getTextValue();
FormField primaryField = retVal.getFormField(primaryFieldName);
FormField repeatField = retVal.getFormField(repeatFieldName);
if(primaryField == null || repeatField == null)
FormField secondaryField = retVal.getFormField(secondaryFieldName);
if(primaryField == null || secondaryField == null)
{
continue;
}
if(!primaryField.getWebValue().equals(repeatField.getWebValue()))
// Repetition of strings
if(relationType.equals(RELATION_TYPE_EQUALS))
{
repeatField.setValid(false);
repeatField.setValidationMessage(
MessageFormat.format(SessionLocaleUtil.getI18nText(request, "xIsNotEqualToY"),
SessionLocaleUtil.getI18nText(request, repeatFieldName),
SessionLocaleUtil.getI18nText(request, primaryFieldName)
)
);
if(!primaryField.getWebValue().equals(secondaryField.getWebValue()))
{
primaryField.setValid(false);
primaryField.setValidationMessage(
MessageFormat.format(SessionLocaleUtil.getI18nText(request, "xIsNotEqualToY"),
SessionLocaleUtil.getI18nText(request, primaryFieldName),
SessionLocaleUtil.getI18nText(request, secondaryFieldName)
)
);
}
}
// Ordering of dates
else if(relationType.equals(RELATION_TYPE_AFTER))
{
if(primaryField.getValueAsDate().compareTo(secondaryField.getValueAsDate()) < 0)
{
primaryField.setValid(false);
primaryField.setValidationMessage(
MessageFormat.format(SessionLocaleUtil.getI18nText(request, "xIsNotAfterY"),
SessionLocaleUtil.getI18nText(request, primaryFieldName),
SessionLocaleUtil.getI18nText(request, secondaryFieldName)
)
);
}
}
}
}
return retVal;
}
/**
* Fetches all fields and their constrants from form definition
* @param formName name of JSON file with form definition
* @param servletContext
* @return
* @throws IOException
*/
public static JsonNode getFormDefinition(String formName, ServletContext servletContext) throws IOException
{
ObjectMapper mapper = new ObjectMapper();
JsonFactory factory = mapper.getJsonFactory();
InputStream in = servletContext.getResourceAsStream("/formdefinitions/" + formName + ".json");
JsonParser parser = factory.createJsonParser(in);
JsonNode formDefinition = mapper.readTree(parser);
return formDefinition;
}
/**
*
* @param formName
* @param servletContext
* @return all form fields form the form definition, indexed by their name
* @throws IOException
*/
public static Map<String, FormField> getFormFields(String formName, ServletContext servletContext) throws IOException
{
JsonNode formDefinition = getFormDefinition(formName, servletContext);
ObjectMapper mapper = new ObjectMapper();
JsonNode fields = formDefinition.findValue("fields");
Map<String, FormField> retVal = new HashMap<>();
for(JsonNode fieldNode:fields)
{
FormField formField = mapper.convertValue(fieldNode, FormField.class);
retVal.put(formField.getName(), formField);
}
return retVal;
}
}
......@@ -73,3 +73,19 @@ userUpdated=User was updated
delete=Delete
confirmDelete=Do you really want to delete?
userDeleted=User was deleted
forecasts=Forecasts
dateStart=Date start
dateEnd=Date end
poi=Point of interest
viewForecastConfiguration=View forecast configuration
forecastConfigurationUpdated=Forecast configuration was updated
forecastConfigurationId=Forecast configuration
vipsLogicUserId=User
doesNotMatchDateFormat=Does not match format {0}
modelId=Forecasting model
APPLESCABM=Apple scab model
NAERSTADMO=N\u00e6rstad's model
locationPointOfInterestId=Location
weatherStationPointOfInterestId=Weather station
addNew=Add new
xIsNotAfterY={0} is not after {1}
......@@ -73,3 +73,19 @@ userUpdated=Brukeren ble oppdatert
delete=Slett
confirmDelete=\u00d8nsker du virkelig \u00e5 slette?
userDeleted=Brukeren ble slettet
forecasts=Varsler
dateStart=Startdato
dateEnd=Sluttdato
poi=Geografisk punkt
viewForecastConfiguration=Se varseloppsett
forecastConfigurationUpdated=Varseloppsettet ble oppdatert
forecastConfigurationId=Varseloppsett
vipsLogicUserId=Bruker
doesNotMatchDateFormat=Stemmer ikke med formatet {0}
modelId=Varslingsmodell
APPLESCABM=Epleskurvmodell
NAERSTADMO=N\u00e6rstads modell
locationPointOfInterestId=Lokalitet
weatherStationPointOfInterestId=M\u00e5lestasjon
addNew=Legg til ny
xIsNotAfterY={0} er ikke etter {1}
......@@ -18,7 +18,7 @@
</servlet>
<servlet>
<servlet-name>UserControllerServlet</servlet-name>
<servlet-class>no.bioforsk.vips.logic.controller.servlet.UserControllerServlet</servlet-class>
<servlet-class>no.bioforsk.vips.logic.controller.servlet.UserController</servlet-class>
</servlet>
<servlet>
<servlet-name>ResourceBundleJSServlet</servlet-name>
......@@ -28,6 +28,10 @@
<servlet-name>HttpErrorServlet</servlet-name>
<servlet-class>no.bioforsk.vips.logic.controller.servlet.HttpErrorServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>ForecastController</servlet-name>
<servlet-class>no.bioforsk.vips.logic.controller.servlet.ForecastConfigurationController</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>PointOfInterestController</servlet-name>
<url-pattern>/poi/*</url-pattern>
......@@ -58,6 +62,10 @@
<url-pattern>/error/404</url-pattern>
<url-pattern>/error/403</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>ForecastController</servlet-name>
<url-pattern>/forecastConfiguration</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
......
{
"_comment" : "Structure of the forecastConfigurationForm and how to validate it",
"fields": [
{
"name" : "forecastConfigurationId",
"type" : "INTEGER",
"required" : true
},
{
"name" : "vipsLogicUserId",
"type" : "INTEGER",
"type" : "SELECT_SINGLE",
"nullValue": "-1",
"required" : true
},
{
"name" : "modelId",
"type" : "SELECT_SINGLE",
"nullValue": "-1",
"required" : true
},
{
"name" : "locationPointOfInterestId",
"type" : "SELECT_SINGLE",
"nullValue": "-1",
"required" : true
},
{
"name" : "weatherStationPointOfInterestId",
"type" : "SELECT_SINGLE",
"nullValue": "-1",
"required" : true
},
{
"name" : "dateStart",
"type" : "DATE",
"dateFormat" : "yyyy-MM-dd",
"required" : true
},
{
"name" : "dateEnd",
"type" : "DATE",
"dateFormat" : "yyyy-MM-dd",
"required" : true
}
],
"relations":[
{"primaryField":"dateEnd","secondaryField":"dateStart", "relationType": "AFTER"}
]
}
......@@ -49,7 +49,7 @@
}
],
"repeatFields":[
{"primaryField":"password","repeatField":"password2"}
"relations":[
{"primaryField":"password2","secondaryField":"password", "relationType": "EQUALS"}
]
}
//! moment.js
//! version : 2.4.0
//! authors : Tim Wood, Iskren Chernev, Moment.js contributors
//! license : MIT
//! momentjs.com
(function(a){function b(a,b){return function(c){return i(a.call(this,c),b)}}function c(a,b){return function(c){return this.lang().ordinal(a.call(this,c),b)}}function d(){}function e(a){u(a),g(this,a)}function f(a){var b=o(a),c=b.year||0,d=b.month||0,e=b.week||0,f=b.day||0,g=b.hour||0,h=b.minute||0,i=b.second||0,j=b.millisecond||0;this._input=a,this._milliseconds=+j+1e3*i+6e4*h+36e5*g,this._days=+f+7*e,this._months=+d+12*c,this._data={},this._bubble()}function g(a,b){for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return b.hasOwnProperty("toString")&&(a.toString=b.toString),b.hasOwnProperty("valueOf")&&(a.valueOf=b.valueOf),a}function h(a){return 0>a?Math.ceil(a):Math.floor(a)}function i(a,b){for(var c=a+"";c.length<b;)c="0"+c;return c}function j(a,b,c,d){var e,f,g=b._milliseconds,h=b._days,i=b._months;g&&a._d.setTime(+a._d+g*c),(h||i)&&(e=a.minute(),f=a.hour()),h&&a.date(a.date()+h*c),i&&a.month(a.month()+i*c),g&&!d&&bb.updateOffset(a),(h||i)&&(a.minute(e),a.hour(f))}function k(a){return"[object Array]"===Object.prototype.toString.call(a)}function l(a){return"[object Date]"===Object.prototype.toString.call(a)||a instanceof Date}function m(a,b,c){var d,e=Math.min(a.length,b.length),f=Math.abs(a.length-b.length),g=0;for(d=0;e>d;d++)(c&&a[d]!==b[d]||!c&&q(a[d])!==q(b[d]))&&g++;return g+f}function n(a){if(a){var b=a.toLowerCase().replace(/(.)s$/,"$1");a=Kb[a]||Lb[b]||b}return a}function o(a){var b,c,d={};for(c in a)a.hasOwnProperty(c)&&(b=n(c),b&&(d[b]=a[c]));return d}function p(b){var c,d;if(0===b.indexOf("week"))c=7,d="day";else{if(0!==b.indexOf("month"))return;c=12,d="month"}bb[b]=function(e,f){var g,h,i=bb.fn._lang[b],j=[];if("number"==typeof e&&(f=e,e=a),h=function(a){var b=bb().utc().set(d,a);return i.call(bb.fn._lang,b,e||"")},null!=f)return h(f);for(g=0;c>g;g++)j.push(h(g));return j}}function q(a){var b=+a,c=0;return 0!==b&&isFinite(b)&&(c=b>=0?Math.floor(b):Math.ceil(b)),c}function r(a,b){return new Date(Date.UTC(a,b+1,0)).getUTCDate()}function s(a){return t(a)?366:365}function t(a){return 0===a%4&&0!==a%100||0===a%400}function u(a){var b;a._a&&-2===a._pf.overflow&&(b=a._a[gb]<0||a._a[gb]>11?gb:a._a[hb]<1||a._a[hb]>r(a._a[fb],a._a[gb])?hb:a._a[ib]<0||a._a[ib]>23?ib:a._a[jb]<0||a._a[jb]>59?jb:a._a[kb]<0||a._a[kb]>59?kb:a._a[lb]<0||a._a[lb]>999?lb:-1,a._pf._overflowDayOfYear&&(fb>b||b>hb)&&(b=hb),a._pf.overflow=b)}function v(a){a._pf={empty:!1,unusedTokens:[],unusedInput:[],overflow:-2,charsLeftOver:0,nullInput:!1,invalidMonth:null,invalidFormat:!1,userInvalidated:!1,iso:!1}}function w(a){return null==a._isValid&&(a._isValid=!isNaN(a._d.getTime())&&a._pf.overflow<0&&!a._pf.empty&&!a._pf.invalidMonth&&!a._pf.nullInput&&!a._pf.invalidFormat&&!a._pf.userInvalidated,a._strict&&(a._isValid=a._isValid&&0===a._pf.charsLeftOver&&0===a._pf.unusedTokens.length)),a._isValid}function x(a){return a?a.toLowerCase().replace("_","-"):a}function y(a,b){return b.abbr=a,mb[a]||(mb[a]=new d),mb[a].set(b),mb[a]}function z(a){delete mb[a]}function A(a){var b,c,d,e,f=0,g=function(a){if(!mb[a]&&nb)try{require("./lang/"+a)}catch(b){}return mb[a]};if(!a)return bb.fn._lang;if(!k(a)){if(c=g(a))return c;a=[a]}for(;f<a.length;){for(e=x(a[f]).split("-"),b=e.length,d=x(a[f+1]),d=d?d.split("-"):null;b>0;){if(c=g(e.slice(0,b).join("-")))return c;if(d&&d.length>=b&&m(e,d,!0)>=b-1)break;b--}f++}return bb.fn._lang}function B(a){return a.match(/\[[\s\S]/)?a.replace(/^\[|\]$/g,""):a.replace(/\\/g,"")}function C(a){var b,c,d=a.match(rb);for(b=0,c=d.length;c>b;b++)d[b]=Pb[d[b]]?Pb[d[b]]:B(d[b]);return function(e){var f="";for(b=0;c>b;b++)f+=d[b]instanceof Function?d[b].call(e,a):d[b];return f}}function D(a,b){return a.isValid()?(b=E(b,a.lang()),Mb[b]||(Mb[b]=C(b)),Mb[b](a)):a.lang().invalidDate()}function E(a,b){function c(a){return b.longDateFormat(a)||a}var d=5;for(sb.lastIndex=0;d>=0&&sb.test(a);)a=a.replace(sb,c),sb.lastIndex=0,d-=1;return a}function F(a,b){var c;switch(a){case"DDDD":return vb;case"YYYY":case"GGGG":case"gggg":return wb;case"YYYYY":case"GGGGG":case"ggggg":return xb;case"S":case"SS":case"SSS":case"DDD":return ub;case"MMM":case"MMMM":case"dd":case"ddd":case"dddd":return zb;case"a":case"A":return A(b._l)._meridiemParse;case"X":return Cb;case"Z":case"ZZ":return Ab;case"T":return Bb;case"SSSS":return yb;case"MM":case"DD":case"YY":case"GG":case"gg":case"HH":case"hh":case"mm":case"ss":case"M":case"D":case"d":case"H":case"h":case"m":case"s":case"w":case"ww":case"W":case"WW":case"e":case"E":return tb;default:return c=new RegExp(N(M(a.replace("\\","")),"i"))}}function G(a){var b=(Ab.exec(a)||[])[0],c=(b+"").match(Hb)||["-",0,0],d=+(60*c[1])+q(c[2]);return"+"===c[0]?-d:d}function H(a,b,c){var d,e=c._a;switch(a){case"M":case"MM":null!=b&&(e[gb]=q(b)-1);break;case"MMM":case"MMMM":d=A(c._l).monthsParse(b),null!=d?e[gb]=d:c._pf.invalidMonth=b;break;case"D":case"DD":null!=b&&(e[hb]=q(b));break;case"DDD":case"DDDD":null!=b&&(c._dayOfYear=q(b));break;case"YY":e[fb]=q(b)+(q(b)>68?1900:2e3);break;case"YYYY":case"YYYYY":e[fb]=q(b);break;case"a":case"A":c._isPm=A(c._l).isPM(b);break;case"H":case"HH":case"h":case"hh":e[ib]=q(b);break;case"m":case"mm":e[jb]=q(b);break;case"s":case"ss":e[kb]=q(b);break;case"S":case"SS":case"SSS":case"SSSS":e[lb]=q(1e3*("0."+b));break;case"X":c._d=new Date(1e3*parseFloat(b));break;case"Z":case"ZZ":c._useUTC=!0,c._tzm=G(b);break;case"w":case"ww":case"W":case"WW":case"d":case"dd":case"ddd":case"dddd":case"e":case"E":a=a.substr(0,1);case"gg":case"gggg":case"GG":case"GGGG":case"GGGGG":a=a.substr(0,2),b&&(c._w=c._w||{},c._w[a]=b)}}function I(a){var b,c,d,e,f,g,h,i,j,k,l=[];if(!a._d){for(d=K(a),a._w&&null==a._a[hb]&&null==a._a[gb]&&(f=function(b){return b?b.length<3?parseInt(b,10)>68?"19"+b:"20"+b:b:null==a._a[fb]?bb().weekYear():a._a[fb]},g=a._w,null!=g.GG||null!=g.W||null!=g.E?h=X(f(g.GG),g.W||1,g.E,4,1):(i=A(a._l),j=null!=g.d?T(g.d,i):null!=g.e?parseInt(g.e,10)+i._week.dow:0,k=parseInt(g.w,10)||1,null!=g.d&&j<i._week.dow&&k++,h=X(f(g.gg),k,j,i._week.doy,i._week.dow)),a._a[fb]=h.year,a._dayOfYear=h.dayOfYear),a._dayOfYear&&(e=null==a._a[fb]?d[fb]:a._a[fb],a._dayOfYear>s(e)&&(a._pf._overflowDayOfYear=!0),c=S(e,0,a._dayOfYear),a._a[gb]=c.getUTCMonth(),a._a[hb]=c.getUTCDate()),b=0;3>b&&null==a._a[b];++b)a._a[b]=l[b]=d[b];for(;7>b;b++)a._a[b]=l[b]=null==a._a[b]?2===b?1:0:a._a[b];l[ib]+=q((a._tzm||0)/60),l[jb]+=q((a._tzm||0)%60),a._d=(a._useUTC?S:R).apply(null,l)}}function J(a){var b;a._d||(b=o(a._i),a._a=[b.year,b.month,b.day,b.hour,b.minute,b.second,b.millisecond],I(a))}function K(a){var b=new Date;return a._useUTC?[b.getUTCFullYear(),b.getUTCMonth(),b.getUTCDate()]:[b.getFullYear(),b.getMonth(),b.getDate()]}function L(a){a._a=[],a._pf.empty=!0;var b,c,d,e,f,g=A(a._l),h=""+a._i,i=h.length,j=0;for(d=E(a._f,g).match(rb)||[],b=0;b<d.length;b++)e=d[b],c=(F(e,a).exec(h)||[])[0],c&&(f=h.substr(0,h.indexOf(c)),f.length>0&&a._pf.unusedInput.push(f),h=h.slice(h.indexOf(c)+c.length),j+=c.length),Pb[e]?(c?a._pf.empty=!1:a._pf.unusedTokens.push(e),H(e,c,a)):a._strict&&!c&&a._pf.unusedTokens.push(e);a._pf.charsLeftOver=i-j,h.length>0&&a._pf.unusedInput.push(h),a._isPm&&a._a[ib]<12&&(a._a[ib]+=12),a._isPm===!1&&12===a._a[ib]&&(a._a[ib]=0),I(a),u(a)}function M(a){return a.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(a,b,c,d,e){return b||c||d||e})}function N(a){return a.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function O(a){var b,c,d,e,f;if(0===a._f.length)return a._pf.invalidFormat=!0,a._d=new Date(0/0),void 0;for(e=0;e<a._f.length;e++)f=0,b=g({},a),v(b),b._f=a._f[e],L(b),w(b)&&(f+=b._pf.charsLeftOver,f+=10*b._pf.unusedTokens.length,b._pf.score=f,(null==d||d>f)&&(d=f,c=b));g(a,c||b)}function P(a){var b,c=a._i,d=Db.exec(c);if(d){for(a._pf.iso=!0,b=4;b>0;b--)if(d[b]){a._f=Fb[b-1]+(d[6]||" ");break}for(b=0;4>b;b++)if(Gb[b][1].exec(c)){a._f+=Gb[b][0];break}Ab.exec(c)&&(a._f+="Z"),L(a)}else a._d=new Date(c)}function Q(b){var c=b._i,d=ob.exec(c);c===a?b._d=new Date:d?b._d=new Date(+d[1]):"string"==typeof c?P(b):k(c)?(b._a=c.slice(0),I(b)):l(c)?b._d=new Date(+c):"object"==typeof c?J(b):b._d=new Date(c)}function R(a,b,c,d,e,f,g){var h=new Date(a,b,c,d,e,f,g);return 1970>a&&h.setFullYear(a),h}function S(a){var b=new Date(Date.UTC.apply(null,arguments));return 1970>a&&b.setUTCFullYear(a),b}function T(a,b){if("string"==typeof a)if(isNaN(a)){if(a=b.weekdaysParse(a),"number"!=typeof a)return null}else a=parseInt(a,10);return a}function U(a,b,c,d,e){return e.relativeTime(b||1,!!c,a,d)}function V(a,b,c){var d=eb(Math.abs(a)/1e3),e=eb(d/60),f=eb(e/60),g=eb(f/24),h=eb(g/365),i=45>d&&["s",d]||1===e&&["m"]||45>e&&["mm",e]||1===f&&["h"]||22>f&&["hh",f]||1===g&&["d"]||25>=g&&["dd",g]||45>=g&&["M"]||345>g&&["MM",eb(g/30)]||1===h&&["y"]||["yy",h];return i[2]=b,i[3]=a>0,i[4]=c,U.apply({},i)}function W(a,b,c){var d,e=c-b,f=c-a.day();return f>e&&(f-=7),e-7>f&&(f+=7),d=bb(a).add("d",f),{week:Math.ceil(d.dayOfYear()/7),year:d.year()}}function X(a,b,c,d,e){var f,g,h=new Date(Date.UTC(a,0)).getUTCDay();return c=null!=c?c:e,f=e-h+(h>d?7:0),g=7*(b-1)+(c-e)+f+1,{year:g>0?a:a-1,dayOfYear:g>0?g:s(a-1)+g}}function Y(a){var b=a._i,c=a._f;return"undefined"==typeof a._pf&&v(a),null===b?bb.invalid({nullInput:!0}):("string"==typeof b&&(a._i=b=A().preparse(b)),bb.isMoment(b)?(a=g({},b),a._d=new Date(+b._d)):c?k(c)?O(a):L(a):Q(a),new e(a))}function Z(a,b){bb.fn[a]=bb.fn[a+"s"]=function(a){var c=this._isUTC?"UTC":"";return null!=a?(this._d["set"+c+b](a),bb.updateOffset(this),this):this._d["get"+c+b]()}}function $(a){bb.duration.fn[a]=function(){return this._data[a]}}function _(a,b){bb.duration.fn["as"+a]=function(){return+this/b}}function ab(a){var b=!1,c=bb;"undefined"==typeof ender&&(this.moment=a?function(){return!b&&console&&console.warn&&(b=!0,console.warn("Accessing Moment through the global scope is deprecated, and will be removed in an upcoming release.")),c.apply(null,arguments)}:bb)}for(var bb,cb,db="2.4.0",eb=Math.round,fb=0,gb=1,hb=2,ib=3,jb=4,kb=5,lb=6,mb={},nb="undefined"!=typeof module&&module.exports,ob=/^\/?Date\((\-?\d+)/i,pb=/(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,qb=/^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/,rb=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g,sb=/(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g,tb=/\d\d?/,ub=/\d{1,3}/,vb=/\d{3}/,wb=/\d{1,4}/,xb=/[+\-]?\d{1,6}/,yb=/\d+/,zb=/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i,Ab=/Z|[\+\-]\d\d:?\d\d/i,Bb=/T/i,Cb=/[\+\-]?\d+(\.\d{1,3})?/,Db=/^\s*\d{4}-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d:?\d\d|Z)?)?$/,Eb="YYYY-MM-DDTHH:mm:ssZ",Fb=["YYYY-MM-DD","GGGG-[W]WW","GGGG-[W]WW-E","YYYY-DDD"],Gb=[["HH:mm:ss.SSSS",/(T| )\d\d:\d\d:\d\d\.\d{1,3}/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],Hb=/([\+\-]|\d\d)/gi,Ib="Date|Hours|Minutes|Seconds|Milliseconds".split("|"),Jb={Milliseconds:1,Seconds:1e3,Minutes:6e4,Hours:36e5,Days:864e5,Months:2592e6,Years:31536e6},Kb={ms:"millisecond",s:"second",m:"minute",h:"hour",d:"day",D:"date",w:"week",W:"isoWeek",M:"month",y:"year",DDD:"dayOfYear",e:"weekday",E:"isoWeekday",gg:"weekYear",GG:"isoWeekYear"},Lb={dayofyear:"dayOfYear",isoweekday:"isoWeekday",isoweek:"isoWeek",weekyear:"weekYear",isoweekyear:"isoWeekYear"},Mb={},Nb="DDD w W M D d".split(" "),Ob="M D H h m s w W".split(" "),Pb={M:function(){return this.month()+1},MMM:function(a){return this.lang().monthsShort(this,a)},MMMM:function(a){return this.lang().months(this,a)},D:function(){return this.date()},DDD:function(){return this.dayOfYear()},d:function(){return this.day()},dd:function(a){return this.lang().weekdaysMin(this,a)},ddd:function(a){return this.lang().weekdaysShort(this,a)},dddd:function(a){return this.lang().weekdays(this,a)},w:function(){return this.week()},W:function(){return this.isoWeek()},YY:function(){return i(this.year()%100,2)},YYYY:function(){return i(this.year(),4)},YYYYY:function(){return i(this.year(),5)},gg:function(){return i(this.weekYear()%100,2)},gggg:function(){return this.weekYear()},ggggg:function(){return i(this.weekYear(),5)},GG:function(){return i(this.isoWeekYear()%100,2)},GGGG:function(){return this.isoWeekYear()},GGGGG:function(){return i(this.isoWeekYear(),5)},e:function(){return this.weekday()},E:function(){return this.isoWeekday()},a:function(){return this.lang().meridiem(this.hours(),this.minutes(),!0)},A:function(){return this.lang().meridiem(this.hours(),this.minutes(),!1)},H:function(){return this.hours()},h:function(){return this.hours()%12||12},m:function(){return this.minutes()},s:function(){return this.seconds()},S:function(){return q(this.milliseconds()/100)},SS:function(){return i(q(this.milliseconds()/10),2)},SSS:function(){return i(this.milliseconds(),3)},SSSS:function(){return i(this.milliseconds(),3)},Z:function(){var a=-this.zone(),b="+";return 0>a&&(a=-a,b="-"),b+i(q(a/60),2)+":"+i(q(a)%60,2)},ZZ:function(){var a=-this.zone(),b="+";return 0>a&&(a=-a,b="-"),b+i(q(10*a/6),4)},z:function(){return this.zoneAbbr()},zz:function(){return this.zoneName()},X:function(){return this.unix()}},Qb=["months","monthsShort","weekdays","weekdaysShort","weekdaysMin"];Nb.length;)cb=Nb.pop(),Pb[cb+"o"]=c(Pb[cb],cb);for(;Ob.length;)cb=Ob.pop(),Pb[cb+cb]=b(Pb[cb],2);for(Pb.DDDD=b(Pb.DDD,3),g(d.prototype,{set:function(a){var b,c;for(c in a)b=a[c],"function"==typeof b?this[c]=b:this["_"+c]=b},_months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),months:function(a){return this._months[a.month()]},_monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),monthsShort:function(a){return this._monthsShort[a.month()]},monthsParse:function(a){var b,c,d;for(this._monthsParse||(this._monthsParse=[]),b=0;12>b;b++)if(this._monthsParse[b]||(c=bb.utc([2e3,b]),d="^"+this.months(c,"")+"|^"+this.monthsShort(c,""),this._monthsParse[b]=new RegExp(d.replace(".",""),"i")),this._monthsParse[b].test(a))return b},_weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdays:function(a){return this._weekdays[a.day()]},_weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysShort:function(a){return this._weekdaysShort[a.day()]},_weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),weekdaysMin:function(a){return this._weekdaysMin[a.day()]},weekdaysParse:function(a){var b,c,d;for(this._weekdaysParse||(this._weekdaysParse=[]),b=0;7>b;b++)if(this._weekdaysParse[b]||(c=bb([2e3,1]).day(b),d="^"+this.weekdays(c,"")+"|^"+this.weekdaysShort(c,"")+"|^"+this.weekdaysMin(c,""),this._weekdaysParse[b]=new RegExp(d.replace(".",""),"i")),this._weekdaysParse[b].test(a))return b},_longDateFormat:{LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D YYYY",LLL:"MMMM D YYYY LT",LLLL:"dddd, MMMM D YYYY LT"},longDateFormat:function(a){var b=this._longDateFormat[a];return!b&&this._longDateFormat[a.toUpperCase()]&&(b=this._longDateFormat[a.toUpperCase()].replace(/MMMM|MM|DD|dddd/g,function(a){return a.slice(1)}),this._longDateFormat[a]=b),b},isPM:function(a){return"p"===(a+"").toLowerCase().charAt(0)},_meridiemParse:/[ap]\.?m?\.?/i,meridiem:function(a,b,c){return a>11?c?"pm":"PM":c?"am":"AM"},_calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},calendar:function(a,b){var c=this._calendar[a];return"function"==typeof c?c.apply(b):c},_relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},relativeTime:function(a,b,c,d){var e=this._relativeTime[c];return"function"==typeof e?e(a,b,c,d):e.replace(/%d/i,a)},pastFuture:function(a,b){var c=this._relativeTime[a>0?"future":"past"];return"function"==typeof c?c(b):c.replace(/%s/i,b)},ordinal:function(a){return this._ordinal.replace("%d",a)},_ordinal:"%d",preparse:function(a){return a},postformat:function(a){return a},week:function(a){return W(a,this._week.dow,this._week.doy).week},_week:{dow:0,doy:6},_invalidDate:"Invalid date",invalidDate:function(){return this._invalidDate}}),bb=function(b,c,d,e){return"boolean"==typeof d&&(e=d,d=a),Y({_i:b,_f:c,_l:d,_strict:e,_isUTC:!1})},bb.utc=function(b,c,d,e){var f;return"boolean"==typeof d&&(e=d,d=a),f=Y({_useUTC:!0,_isUTC:!0,_l:d,_i:b,_f:c,_strict:e}).utc()},bb.unix=function(a){return bb(1e3*a)},bb.duration=function(a,b){var c,d,e,g=bb.isDuration(a),h="number"==typeof a,i=g?a._input:h?{}:a,j=null;return h?b?i[b]=a:i.milliseconds=a:(j=pb.exec(a))?(c="-"===j[1]?-1:1,i={y:0,d:q(j[hb])*c,h:q(j[ib])*c,m:q(j[jb])*c,s:q(j[kb])*c,ms:q(j[lb])*c}):(j=qb.exec(a))&&(c="-"===j[1]?-1:1,e=function(a){var b=a&&parseFloat(a.replace(",","."));return(isNaN(b)?0:b)*c},i={y:e(j[2]),M:e(j[3]),d:e(j[4]),h:e(j[5]),m:e(j[6]),s:e(j[7]),w:e(j[8])}),d=new f(i),g&&a.hasOwnProperty("_lang")&&(d._lang=a._lang),d},bb.version=db,bb.defaultFormat=Eb,bb.updateOffset=function(){},bb.lang=function(a,b){var c;return a?(b?y(x(a),b):null===b?(z(a),a="en"):mb[a]||A(a),c=bb.duration.fn._lang=bb.fn._lang=A(a),c._abbr):bb.fn._lang._abbr},bb.langData=function(a){return a&&a._lang&&a._lang._abbr&&(a=a._lang._abbr),A(a)},bb.isMoment=function(a){return a instanceof e},bb.isDuration=function(a){return a instanceof f},cb=Qb.length-1;cb>=0;--cb)p(Qb[cb]);for(bb.normalizeUnits=function(a){return n(a)},bb.invalid=function(a){var b=bb.utc(0/0);return null!=a?g(b._pf,a):b._pf.userInvalidated=!0,b},bb.parseZone=function(a){return bb(a).parseZone()},g(bb.fn=e.prototype,{clone:function(){return bb(this)},valueOf:function(){return+this._d+6e4*(this._offset||0)},unix:function(){return Math.floor(+this/1e3)},toString:function(){return this.clone().lang("en").format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},toDate:function(){return this._offset?new Date(+this):this._d},toISOString:function(){return D(bb(this).utc(),"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]")},toArray:function(){var a=this;return[a.year(),a.month(),a.date(),a.hours(),a.minutes(),a.seconds(),a.milliseconds()]},isValid:function(){return w(this)},isDSTShifted:function(){return this._a?this.isValid()&&m(this._a,(this._isUTC?bb.utc(this._a):bb(this._a)).toArray())>0:!1},parsingFlags:function(){return g({},this._pf)},invalidAt:function(){return this._pf.overflow},utc:function(){return this.zone(0)},local:function(){return this.zone(0),this._isUTC=!1,this},format:function(a){var b=D(this,a||bb.defaultFormat);return this.lang().postformat(b)},add:function(a,b){var c;return c="string"==typeof a?bb.duration(+b,a):bb.duration(a,b),j(this,c,1),this},subtract:function(a,b){var c;return c="string"==typeof a?bb.duration(+b,a):bb.duration(a,b),j(this,c,-1),this},diff:function(a,b,c){var d,e,f=this._isUTC?bb(a).zone(this._offset||0):bb(a).local(),g=6e4*(this.zone()-f.zone());return b=n(b),"year"===b||"month"===b?(d=432e5*(this.daysInMonth()+f.daysInMonth()),e=12*(this.year()-f.year())+(this.month()-f.month()),e+=(this-bb(this).startOf("month")-(f-bb(f).startOf("month")))/d,e-=6e4*(this.zone()-bb(this).startOf("month").zone()-(f.zone()-bb(f).startOf("month").zone()))/d,"year"===b&&(e/=12)):(d=this-f,e="second"===b?d/1e3:"minute"===b?d/6e4:"hour"===b?d/36e5:"day"===b?(d-g)/864e5:"week"===b?(d-g)/6048e5:d),c?e:h(e)},from:function(a,b){return bb.duration(this.diff(a)).lang(this.lang()._abbr).humanize(!b)},fromNow:function(a){return this.from(bb(),a)},calendar:function(){var a=this.diff(bb().zone(this.zone()).startOf("day"),"days",!0),b=-6>a?"sameElse":-1>a?"lastWeek":0>a?"lastDay":1>a?"sameDay":2>a?"nextDay":7>a?"nextWeek":"sameElse";return this.format(this.lang().calendar(b,this))},isLeapYear:function(){return t(this.year())},isDST:function(){return this.zone()<this.clone().month(0).zone()||this.zone()<this.clone().month(5).zone()},day:function(a){var b=this._isUTC?this._d.getUTCDay():this._d.getDay();return null!=a?(a=T(a,this.lang()),this.add({d:a-b})):b},month:function(a){var b,c=this._isUTC?"UTC":"";return null!=a?"string"==typeof a&&(a=this.lang().monthsParse(a),"number"!=typeof a)?this:(b=this.date(),this.date(1),this._d["set"+c+"Month"](a),this.date(Math.min(b,this.daysInMonth())),bb.updateOffset(this),this):this._d["get"+c+"Month"]()},startOf:function(a){switch(a=n(a)){case"year":this.month(0);case"month":this.date(1);case"week":case"isoWeek":case"day":this.hours(0);case"hour":this.minutes(0);case"minute":this.seconds(0);case"second":this.milliseconds(0)}return"week"===a?this.weekday(0):"isoWeek"===a&&this.isoWeekday(1),this},endOf:function(a){return a=n(a),this.startOf(a).add("isoWeek"===a?"week":a,1).subtract("ms",1)},isAfter:function(a,b){return b="undefined"!=typeof b?b:"millisecond",+this.clone().startOf(b)>+bb(a).startOf(b)},isBefore:function(a,b){return b="undefined"!=typeof b?b:"millisecond",+this.clone().startOf(b)<+bb(a).startOf(b)},isSame:function(a,b){return b="undefined"!=typeof b?b:"millisecond",+this.clone().startOf(b)===+bb(a).startOf(b)},min:function(a){return a=bb.apply(null,arguments),this>a?this:a},max:function(a){return a=bb.apply(null,arguments),a>this?this:a},zone:function(a){var b=this._offset||0;return null==a?this._isUTC?b:this._d.getTimezoneOffset():("string"==typeof a&&(a=G(a)),Math.abs(a)<16&&(a=60*a),this._offset=a,this._isUTC=!0,b!==a&&j(this,bb.duration(b-a,"m"),1,!0),this)},zoneAbbr:function(){return this._isUTC?"UTC":""},zoneName:function(){return this._isUTC?"Coordinated Universal Time":""},parseZone:function(){return"string"==typeof this._i&&this.zone(this._i),this},hasAlignedHourOffset:function(a){return a=a?bb(a).zone():0,0===(this.zone()-a)%60},daysInMonth:function(){return r(this.year(),this.month())},dayOfYear:function(a){var b=eb((bb(this).startOf("day")-bb(this).startOf("year"))/864e5)+1;return null==a?b:this.add("d",a-b)},weekYear:function(a){var b=W(this,this.lang()._week.dow,this.lang()._week.doy).year;return null==a?b:this.add("y",a-b)},isoWeekYear:function(a){var b=W(this,1,4).year;return null==a?b:this.add("y",a-b)},week:function(a){var b=this.lang().week(this);return null==a?b:this.add("d",7*(a-b))},isoWeek:function(a){var b=W(this,1,4).week;return null==a?b:this.add("d",7*(a-b))},weekday:function(a){var b=(this.day()+7-this.lang()._week.dow)%7;return null==a?b:this.add("d",a-b)},isoWeekday:function(a){return null==a?this.day()||7:this.day(this.day()%7?a:a-7)},get:function(a){return a=n(a),this[a]()},set:function(a,b){return a=n(a),"function"==typeof this[a]&&this[a](b),this},lang:function(b){return b===a?this._lang:(this._lang=A(b),this)}}),cb=0;cb<Ib.length;cb++)Z(Ib[cb].toLowerCase().replace(/s$/,""),Ib[cb]);Z("year","FullYear"),bb.fn.days=bb.fn.day,bb.fn.months=bb.fn.month,bb.fn.weeks=bb.fn.week,bb.fn.isoWeeks=bb.fn.isoWeek,bb.fn.toJSON=bb.fn.toISOString,g(bb.duration.fn=f.prototype,{_bubble:function(){var a,b,c,d,e=this._milliseconds,f=this._days,g=this._months,i=this._data;i.milliseconds=e%1e3,a=h(e/1e3),i.seconds=a%60,b=h(a/60),i.minutes=b%60,c=h(b/60),i.hours=c%24,f+=h(c/24),i.days=f%30,g+=h(f/30),i.months=g%12,d=h(g/12),i.years=d},weeks:function(){return h(this.days()/7)},valueOf:function(){return this._milliseconds+864e5*this._days+2592e6*(this._months%12)+31536e6*q(this._months/12)},humanize:function(a){var b=+this,c=V(b,!a,this.lang());return a&&(c=this.lang().pastFuture(b,c)),this.lang().postformat(c)},add:function(a,b){var c=bb.duration(a,b);return this._milliseconds+=c._milliseconds,this._days+=c._days,this._months+=c._months,this._bubble(),this},subtract:function(a,b){var c=bb.duration(a,b);return this._milliseconds-=c._milliseconds,this._days-=c._days,this._months-=c._months,this._bubble(),this},get:function(a){return a=n(a),this[a.toLowerCase()+"s"]()},as:function(a){return a=n(a),this["as"+a.charAt(0).toUpperCase()+a.slice(1)+"s"]()},lang:bb.fn.lang,toIsoString:function(){var a=Math.abs(this.years()),b=Math.abs(this.months()),c=Math.abs(this.days()),d=Math.abs(this.hours()),e=Math.abs(this.minutes()),f=Math.abs(this.seconds()+this.milliseconds()/1e3);return this.asSeconds()?(this.asSeconds()<0?"-":"")+"P"+(a?a+"Y":"")+(b?b+"M":"")+(c?c+"D":"")+(d||e||f?"T":"")+(d?d+"H":"")+(e?e+"M":"")+(f?f+"S":""):"P0D"}});for(cb in Jb)Jb.hasOwnProperty(cb)&&(_(cb,Jb[cb]),$(cb.toLowerCase()));_("Weeks",6048e5),bb.duration.fn.asMonths=function(){return(+this-31536e6*this.years())/2592e6+12*this.years()},bb.lang("en",{ordinal:function(a){var b=a%10,c=1===q(a%100/10)?"th":1===b?"st":2===b?"nd":3===b?"rd":"th";return a+c}}),nb?(module.exports=bb,ab(!0)):"function"==typeof define&&define.amd?define("moment",function(b,c,d){return d.config().noGlobal!==!0&&ab(d.config().noGlobal===a),bb}):ab()}).call(this);
\ No newline at end of file
......@@ -9,10 +9,23 @@ var formFieldTypes = {
TYPE_STRING: "STRING",
TYPE_INTEGER: "INTEGER",
TYPE_DOUBLE: "DOUBLE",
TYPE_DATE: "DATE",
TYPE_PASSWORD: "PASSWORD",
TYPE_EMAIL: "EMAIL",
TYPE_SELECT_SINGLE: "SELECT_SINGLE",
TYPE_SELECT_MULTIPLE: "SELECT_MULTIPLE"
};
var defaultValues = {
DEFAULT_DATE_FORMAT: "yyyy-MM-dd"
};
// Primary field relates to secondary field as TYPE
var relationTypes = {
RELATION_TYPE_EQUALS: "EQUALS",
RELATION_TYPE_AFTER: "AFTER"
};
// Storage for all form definitions for current page
......@@ -35,6 +48,7 @@ var emailReg = /^([\w-\.]+@([\w-]+\.)+[\w-]{2,4})?$/;
* <li>The form must have an id that corresponds to a JSON file accessible in "/formdefinitions/[form_id].json"</li>
* <li>A DOM element with id "errorMsgEl" for where to put validation error messages</li>
* <li>jQuery for Ajax calls</li>
* <li><a href="http://momentjs.com/">moment.js</a> for date validation</li>
* </ul>
*
* This function prepares for the validateFormActual function.
......@@ -73,23 +87,41 @@ function validateFormActual(theForm)
}
}
// Check repeats
for(var i in formDefinition.repeatFields)
// Check relations
for(var i in formDefinition.relations)
{
var item = formDefinition.repeatFields[i];
var item = formDefinition.relations[i];
var primaryFieldWebValue = theForm[item.primaryField].value;
var repeatFieldWebValue = theForm[item.repeatField].value;
if(primaryFieldWebValue === null || repeatFieldWebValue === null)
var secondaryFieldWebValue = theForm[item.secondaryField].value;
if(primaryFieldWebValue === null || secondaryFieldWebValue === null)
{
continue;
}
if(primaryFieldWebValue !== repeatFieldWebValue)
// String equality NOW!
console.log("relationTYpe=" + item.relationType);
if(item.relationType === relationTypes.RELATION_TYPE_EQUALS)
{
isValid = false;
invalidizeField(theForm[item.repeatField], theForm, getI18nMsg("xIsNotEqualToY",[
getI18nMsg(item.repeatField),
getI18nMsg(item.primaryField).toLowerCase()
]));
if(primaryFieldWebValue !== secondaryFieldWebValue)
{
isValid = false;
invalidizeField(theForm[item.primaryField], theForm, getI18nMsg("xIsNotEqualToY",[
getI18nMsg(item.primaryField),
getI18nMsg(item.secondaryField).toLowerCase()
]));
}
}
// Primary field is expected to be a date AFTER secondary key
else if(item.relationType === relationTypes.RELATION_TYPE_AFTER)
{
if(!moment(primaryFieldWebValue).isAfter(moment(secondaryFieldWebValue)))
{
isValid = false;
invalidizeField(theForm[item.primaryField], theForm, getI18nMsg("xIsNotAfterY",[
getI18nMsg(item.primaryField),
getI18nMsg(item.secondaryField).toLowerCase()
]));
}
}
//console.log(item.primaryField);
}
......@@ -193,6 +225,22 @@ function validateFieldActual(fieldEl, theForm)
return true;
}
}
// Dates: check date format
if(fieldDefinition.type === formFieldTypes.TYPE_DATE)
{
var validFormat = (fieldDefinition.dateFormat !== null && fieldDefinition.dateFormat !== undefined) ? fieldDefinition.dateFormat : defaultValues.DEFAULT_DATE_FORMAT;
if(!moment(webValue, validFormat.toUpperCase(),true).isValid())
{
invalidizeField(fieldEl, theForm, getI18nMsg("doesNotMatchDateFormat",[validFormat]));
return false;
}
else
{
validizeField(fieldEl, theForm);
return true;
}
}
// Check email format
// We use a simple regExp for this
......@@ -368,6 +416,11 @@ function evaluatePassword(passwordEl)
*/
function invalidizeField(inputEl, theForm, message)
{
// Make sure we don't try to manipulate hidden fields
if(inputEl.type === "hidden")
{
return;
}
styleInvalid(theForm[inputEl.name]);
getValidationOutputEl(inputEl, theForm).innerHTML = message;
}
......@@ -380,6 +433,11 @@ function invalidizeField(inputEl, theForm, message)
*/
function validizeField(inputEl, theForm)
{
// Make sure we don't try to manipulate hidden fields
if(inputEl.type === "hidden")
{
return;
}
styleValid(theForm[inputEl.name]);
getValidationOutputEl(inputEl, theForm).innerHTML = "";
}
......
<#include "master.ftl">
<#macro page_head>
<title></title>
</#macro>
<#macro custom_js>
<script src="/js/resourcebundle.js"></script>
<script src="/js/validateForm.js"></script>
<script src="http://code.jquery.com/ui/1.10.3/jquery-ui.min.js"></script>
<link href="http://code.jquery.com/ui/1.10.3/themes/redmond/jquery-ui.css" rel="stylesheet" />
<script type="text/javascript" src="/js/modernizr_custom.js"></script>
<script type="text/javascript" src="/js/moment.min.js"></script>
<script type="text/javascript">
// Make sure that there is a date picker present for HTML5
// date input fields
if (!Modernizr.inputtypes.date) {
$('input[type=date]').datepicker({ dateFormat: 'yy-mm-dd' });
}
</script>
</#macro>
<#macro page_contents>
<h1>${i18nBundle.viewForecastConfiguration}</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>
</div>
<#if messageKey?has_content>
<div class="alert alert-success">${i18nBundle(messageKey)}</div>
</#if>
<#assign formId = "forecastConfigurationForm">
<form id="${formId}" role="form" action="/forecastConfiguration?action=forecastConfigurationFormSubmit" method="POST" onsubmit="try{return validateForm(this);}catch(err){alert(err);return false;}">
<input type="hidden" name="forecastConfigurationId" value="${forecastConfiguration.forecastConfigurationId!"-1"}"/>
<div class="form-group">
<label for="modelId">${i18nBundle.modelId}</label>
<select class="form-control" name="modelId" onblur="validateField(this);">
<option value="-1">${i18nBundle.pleaseSelect} ${i18nBundle.modelId?lower_case}</option>
<#list modelInformations as modelInformation>
<option value="${modelInformation.modelId}"<#if forecastConfiguration.modelId?has_content && modelInformation.modelId == forecastConfiguration.modelId> selected="selected"</#if>>
<#if i18nBundle.containsKey(modelInformation.modelId)>
${i18nBundle[modelInformation.modelId]}
<#else>
${modelInformation.defaultName}
</#if>
</option>
</#list>
</select>
<span class="help-block" id="${formId}_modelId_validation"></span>
</div>
<div class="form-group">
<label for="locationPointOfInterestId">${i18nBundle.locationPointOfInterestId}</label>
<select class="form-control" name="locationPointOfInterestId" onblur="validateField(this);">
<option value="-1">${i18nBundle.pleaseSelect} ${i18nBundle.locationPointOfInterestId?lower_case}</option>
<#list locationPointOfInterests?sort_by("name") as poi>
<option value="${poi.pointOfInterestId}"<#if forecastConfiguration.locationPointOfInterestId?has_content && poi.pointOfInterestId == forecastConfiguration.locationPointOfInterestId.pointOfInterestId> selected="selected"</#if>>${poi.name}</option>
</#list>
</select>
<span class="help-block" id="${formId}_locationPointOfInterestId_validation"></span>
</div>
<div class="form-group">
<label for="weatherStationPointOfInterestId">${i18nBundle.weatherStationPointOfInterestId}</label>
<select class="form-control" name="weatherStationPointOfInterestId" onblur="validateField(this);">
<option value="-1">${i18nBundle.pleaseSelect} ${i18nBundle.weatherStationPointOfInterestId?lower_case}</option>
<#list weatherStationPointOfInterests?sort_by("name") as poi>
<option value="${poi.pointOfInterestId}"<#if forecastConfiguration.weatherStationPointOfInterestId?has_content && poi.pointOfInterestId == forecastConfiguration.weatherStationPointOfInterestId.pointOfInterestId> selected="selected"</#if>>${poi.name}</option>
</#list>
</select>
<span class="help-block" id="${formId}_weatherStationPointOfInterestId_validation"></span>
</div>
<div class="form-group">
<label for="dateStart">${i18nBundle.dateStart}</label>
<input type="date" class="form-control" name="dateStart" placeholder="${i18nBundle.dateStart}" value="${(forecastConfiguration.dateStart?string(dateStart_dateFormat))!""}" onblur="validateField(this);" />
<span class="help-block" id="${formId}_dateStart_validation"></span>
</div>
<div class="form-group">
<label for="dateEnd">${i18nBundle.dateEnd}</label>
<input type="date" class="form-control" name="dateEnd" placeholder="${i18nBundle.dateEnd}" value="${(forecastConfiguration.dateEnd?string(dateEnd_dateFormat))!""}" onblur="validateField(this);" />
<span class="help-block" id="${formId}_dateEnd_validation"></span>
</div>
<div class="form-group">
<label for="vipsLogicUserId">${i18nBundle.vipsLogicUserId}</label>
<select class="form-control" name="vipsLogicUserId" onblur="validateField(this);">
<option value="-1">${i18nBundle.pleaseSelect} ${i18nBundle.vipsLogicUserId?lower_case}</option>
<#list vipsLogicUsers?sort_by("lastName") as user>
<option value="${user.userId}"<#if forecastConfiguration.vipsLogicUserId?has_content && user.userId == forecastConfiguration.vipsLogicUserId.userId> selected="selected"</#if>>${user.lastName}, ${user.firstName}</option>
</#list>
</select>
<span class="help-block" id="${formId}_vipsLogicUserId_validation"></span>
</div>
<button type="submit" class="btn btn-default">${i18nBundle.submit}</button>
<button type="button" class="btn btn-danger" onclick="if(confirm('${i18nBundle.confirmDelete}')){alert('Not implemented');}">${i18nBundle.delete}</button>
</form>
</#macro>
<@page_html/>
<#include "master.ftl">
<#macro page_head>
<title>${i18nBundle.forecasts}</title>
</#macro>
<#macro page_contents>
<h1>${i18nBundle.forecasts}</h1>
<#if messageKey?has_content>
<div class="alert alert-success">${i18nBundle(messageKey)}</div>
</#if>
<button type="button" class="btn btn-default" onclick="window.location.href='/forecastConfiguration?action=viewForecastConfiguration&forecastConfigurationId=-1'">${i18nBundle.addNew}</button>
<div class="table-responsive">
<table class="table table-striped">
<thead>
<th>${i18nBundle.modelId}</th>
<th>${i18nBundle.poi}</th>
<th>${i18nBundle.dateStart}</th>
<th>${i18nBundle.dateEnd}</th>
</thead>
<tbody>
<#list forecastConfigurations?sort_by("dateStart")?reverse as forecastConfiguration>
<tr style="cursor: pointer;" onclick="window.location.href='/forecastConfiguration?action=viewForecastConfiguration&forecastConfigurationId=${forecastConfiguration.forecastConfigurationId}';">
<td>
<#if i18nBundle.containsKey(forecastConfiguration.modelId)>
${i18nBundle[forecastConfiguration.modelId]}
<#else>
${modelInformation[forecastConfiguration.modelId].defaultName}
</#if>
</td>
<td>${forecastConfiguration.locationPointOfInterestId.name}</td>
<td>${forecastConfiguration.dateStart}</td>
<td>${forecastConfiguration.dateEnd}</td>
</tr>
</#list>
</tbody>
</table>
</div>
</#macro>
<@page_html/>
......@@ -31,8 +31,8 @@
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Admin<b class="caret"></b></a>
<ul class="dropdown-menu">
<li><a href="/user">Users</a></li>
<li><a href="#">Another action</a></li>
<li><a href="/user">${i18nBundle.users}</a></li>
<li><a href="/forecastConfiguration">${i18nBundle.forecasts}</a></li>
<li><a href="#">Something else here</a></li>
<li><a href="#">Separated link</a></li>
<li><a href="#">One more separated link</a></li>
......
......@@ -15,7 +15,7 @@
<div class="alert alert-success">${i18nBundle(messageKey)}</div>
</#if>
<#assign formId = "userForm">
<form id="${formId}" role="form" action="/user?action=userFormSubmit" method="POST" onsubmit="return validateForm(this);" accept-charset="UTF-8">
<form id="${formId}" role="form" action="/user?action=userFormSubmit" method="POST" onsubmit="return validateForm(this);">
<input type="hidden" name="userId" value="${viewUser.userId}"/>
<!-- Authentication info should be dealt with outside this form -->
<div class="form-group">
......
<%--
Document : updatemodelinfo
Created on : Dec 6, 2013, 5:11:53 PM
Author : treinar
--%>
<%@page import="no.bioforsk.vips.logic.controller.session.ForecastBean"%>
<%@page import="no.bioforsk.vips.logic.util.SessionControllerGetter"%>
<%
ForecastBean fb = SessionControllerGetter.getForecastBean();
fb.updateModelInformation();
%>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Update model info</title>
</head>
<body>
<h1>This page calls the methods for updating model info. It also displays the updated model info(?)</h1>
</body>
</html>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment