diff --git a/VIPSWeb/static/js/util.js b/VIPSWeb/static/js/util.js
index 8712370441ba851dd5f2f844c20c4282e0a5b19d..e58eb5d30bf1ee535dc52767f585972d115ab3fc 100644
--- a/VIPSWeb/static/js/util.js
+++ b/VIPSWeb/static/js/util.js
@@ -69,4 +69,48 @@ function getElementComputedDisplay(theElement)
 {
 	return theElement.currentStyle ? theElement.currentStyle.display :
         getComputedStyle(theElement, null).display;
-}
\ No newline at end of file
+}
+
+/* Sorting functions used on JSON objects returned from VIPSLogic */
+
+/**
+ * Comparing weather stations alphabetically by name
+ */
+var compareWeatherStations = function(a,b)
+{
+	return compareStrings[a["name"],b["name"]]
+}
+
+/**
+ * Comparing preparations alphabetically
+ */
+var comparePreparations = function(a,b)
+{
+	return compareStrings(a["preparationName"],b["preparationName"]);
+}
+
+var compareStrings = function(a,b)
+{
+	if(a < b)
+	{
+		return -1;
+	}
+	if(a > b)
+	{
+		return 1;
+	}
+	return 0;
+}
+
+var compareForecastResults = function(a,b)
+{
+	if(a["resultValidTime"] < b["resultValidTime"])
+	{
+		return -1;
+	}
+	if(a["resultValidTime"] > b["resultValidTime"])
+	{
+		return 1;
+	}
+	return 0;
+}
diff --git a/VIPSWeb/static/js/validateForm.js b/VIPSWeb/static/js/validateForm.js
new file mode 100644
index 0000000000000000000000000000000000000000..72b6aaff9108a5a436713213667f83b603c37c2b
--- /dev/null
+++ b/VIPSWeb/static/js/validateForm.js
@@ -0,0 +1,576 @@
+/*
+ * Copyright (c) 2015 NIBIO <http://www.nibio.no/>. 
+ * 
+ * This file is part of VIPSWeb.
+ * VIPSWeb 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.
+ * 
+ * VIPSWeb 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 VIPSWeb.  If not, see <http://www.nibio.no/licenses/>.
+ * 
+ */
+
+
+// Constant defs for field types. 
+var dataTypes = {
+    TYPE_STRING: "STRING",
+    TYPE_INTEGER: "INTEGER",
+    TYPE_DOUBLE: "DOUBLE",
+    TYPE_DATE: "DATE",
+    TYPE_TIMESTAMP: "TIMESTAMP",
+    TYPE_PASSWORD: "PASSWORD",
+    TYPE_EMAIL: "EMAIL",
+    TYPE_POINT_WGS84: "POINT_WGS84"
+    
+    
+};
+
+var fieldTypes= {
+    TYPE_SELECT_SINGLE: "SELECT_SINGLE",
+    TYPE_SELECT_MULTIPLE: "SELECT_MULTIPLE",
+    TYPE_INPUT: "INPUT"
+};
+
+
+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
+var formDefinitions = {};
+
+// The regular expression for email strings
+// Checks that string contains at least one ".", an "@" symbol and an 
+// appropriate top-level domain suffix (e.g. .com, .net, etc)
+// Stolen from this web page: http://www.designchemical.com/blog/index.php/jquery/email-validation-using-jquery/
+var emailReg = /^([\w-\.]+@([\w-]+\.)+[\w-]{2,4})?$/;
+
+/**
+ * Validation function for all forms
+ * @copyright 2013 <a href="http://www.nibio.no/">NIBIO</a>
+ * @author Tor-Einar Skog <tor-einar.skog@bioforsk.no>
+ * 
+ * It depends on the following:
+ * <ul>
+ *  <li>theForm - the form to validate, passed in to the function by reference</li>
+ *  <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.
+ * 
+ * @param {Element} theForm the form to validate
+ * @param {String} formDefinitionKey
+ * @return {boolean} If the form is valid or not
+ */
+function validateForm(theForm, formDefinitionKey)
+{
+    var isValid = true;
+    if(formDefinitionKey === null || formDefinitionKey === undefined)
+    {
+        formDefinitionKey = theForm.id;
+    }
+    var formDefinition = formDefinitions[formDefinitionKey];
+    // Iterate through fields in form definition
+    for(var i in formDefinition.fields){
+        var fieldDefinition = formDefinition.fields[i];
+        if(!validateFieldActual(theForm[fieldDefinition.name], theForm, formDefinitionKey)){
+            //alert("Validation failed for " + fieldDefinition.name);
+            isValid = false;
+        }
+    }
+    
+    // Check relations
+    if(formDefinition.relations !== undefined && formDefinition.relations !== null)
+    {
+        for(var i in formDefinition.relations)
+        {
+            var item = formDefinition.relations[i];
+            var primaryFieldWebValue = theForm[item.primaryField].value;
+            var secondaryFieldWebValue = theForm[item.secondaryField].value;
+            if(primaryFieldWebValue === null || secondaryFieldWebValue === null)
+            {
+                continue;
+            }
+
+            // String equality NOW!
+            //console.log("relationTYpe=" + item.relationType);
+            if(item.relationType === relationTypes.RELATION_TYPE_EQUALS)
+            {
+                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);
+        }
+    }
+
+    //console.log("isValid=" + isValid);
+    return isValid;
+}
+
+/**
+ * Attempts to load and index the requested form definition
+ * @param {String} formDefinitionKey
+ * @param {String} path if set, the form is placed outside the standard path
+ * @returns {void}
+ */
+function loadFormDefinition(formDefinitionKey, path)
+{
+    if(path === null || path === undefined)
+    {
+        path = "/formdefinitions/";
+    }
+    
+    $.getJSON( path + formDefinitionKey + ".json")
+        .done(function(json){
+            formDefinitions[formDefinitionKey] = json;
+        })
+        .fail(function() {
+            alert("Error getting form definition with key=" + formDefinitionKey);
+        });
+}
+
+/**
+ * Prepares for the validation of a field
+ * @param {DOMElement} fieldEl
+ * @param {String} formDefinitionKey
+ * @returns {void}
+ */
+function validateField(fieldEl, formDefinitionKey)
+{
+    // Which form does this element belong to?
+    var theForm = getFormForField(fieldEl);
+    if(formDefinitionKey === null || formDefinitionKey === undefined)
+    {    
+        formDefinitionKey = theForm.id;
+    }
+    // If form definition is null, get it before we proceed
+    if(formDefinitions[formDefinitionKey] === undefined)
+    {
+        $.getJSON( "/formdefinitions/" + theForm.id + ".json")
+        .done(function(json){
+            formDefinitions[theForm.id] = json;
+            return validateFieldActual(fieldEl, theForm, formDefinitionKey);
+        });
+    }
+    else
+    {
+        return validateFieldActual(fieldEl, theForm, formDefinitionKey);
+    }
+}
+
+/**
+ * Returns the requested field definition in a given form definition
+ * @param {type} fieldName
+ * @param {type} formDefinition
+ * @returns {unresolved}
+ */
+function getFieldDefinition(fieldName, formDefinition)
+{
+    for(i in formDefinition.fields)
+    {
+        if(formDefinition.fields[i].name === fieldName)
+        {
+            return formDefinition.fields[i];
+        }
+    }
+    return null;
+}
+
+/**
+ * Uniform way of getting the element where to put the evaluation message
+ * @param {Element} fieldEl
+ * @param {Element} theForm
+ * @returns {Element}
+ */
+function getValidationOutputEl(fieldEl, theForm)
+{
+    return document.getElementById(theForm.id + "_" + fieldEl.name + "_validation");
+}
+
+/**
+ * Performs the actual field validation
+ * @param {Element} fieldEl the form element to be validated
+ * @param {Element} theForm the form
+ * @param {String} formDefinitionKey optional key to a formdefinition for a part of the form that has been dynamically added to the main form
+ * @returns {boolean}
+ */
+function validateFieldActual(fieldEl, theForm, formDefinitionKey)
+{
+    var webValue = fieldEl.value;
+    
+    var fieldDefinition = getFieldDefinition(fieldEl.name, formDefinitions[formDefinitionKey !== null ? formDefinitionKey : theForm.id]);
+    
+    //console.log(fieldDefinition);
+    //console.log(webValue);
+    // Empty?
+    if(webValue === null || webValue === "")
+    {
+        if(fieldDefinition.required === true)
+        {   
+            invalidizeField(fieldEl, theForm, getI18nMsg("Field is required",null));
+            return false;
+        }
+        else
+        {
+            validizeField(fieldEl, theForm);
+            return true;
+        }
+    }
+    
+    
+    
+    // Single select field - check for nullValue
+    if(fieldDefinition.fieldType === fieldTypes.TYPE_SELECT_SINGLE)
+    {
+        webValue = fieldEl.options[fieldEl.selectedIndex].value;
+        if(fieldDefinition.nullValue === webValue && fieldDefinition.required === true)
+        {
+            invalidizeField(fieldEl, theForm, getI18nMsg("Field is required",null));
+            return false;
+        }
+        else
+        {
+            validizeField(fieldEl, theForm);
+            // We don't return yet, because we want to validate the data type
+        }
+    }
+    
+    // Multiple select field
+    // If required - check that at least one item has been selected
+    if(fieldDefinition.fieldType === fieldTypes.TYPE_SELECT_MULTIPLE)
+    {
+        if(fieldDefinition.required)
+        {
+            var selectedOptions = 0;
+            for(var i=0;i<fieldEl.options.length;i++)
+            {
+                selectedOptions += fieldEl.options[i].selected ? 1 : 0;
+            }
+            if(selectedOptions === 0)
+            {
+                invalidizeField(fieldEl, theForm, getI18nMsg("fieldIsRequired",null));
+            return false;
+            }
+            else
+            {
+                validizeField(fieldEl, theForm);
+                return true;
+            }
+        }
+        else
+        {
+            return true;
+        }
+    }
+
+    // STRINGS
+    // Strings are checked for length
+    if(fieldDefinition.dataType === dataTypes.TYPE_STRING)
+    {
+        if(fieldDefinition.maxLength !== null && fieldDefinition.maxLength < webValue.length)
+        {
+            invalidizeField(fieldEl, theForm, getI18nMsg("exceedsMaxLengthOf",[fieldDefinition.maxLength]));
+            return false;
+        }
+        else
+        {
+            validizeField(fieldEl, theForm);
+            return true;
+        }
+    }
+    
+    // Dates: check date format
+    if(fieldDefinition.dataType === dataTypes.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("doesNotMatchFormatX",[validFormat]));
+            return false;
+        }
+        else
+        {
+            validizeField(fieldEl, theForm);
+            return true;
+        }
+    }
+    
+    if(fieldDefinition.dataType === dataTypes.TYPE_TIMESTAMP)
+    {
+        var validDateFormat = (fieldDefinition.dateFormat !== null && fieldDefinition.dateFormat !== undefined) ? 
+                                fieldDefinition.dateFormat : defaultValues.DEFAULT_DATE_FORMAT;
+        var validTimestampFormat = (fieldDefinition.timestampFormatMomentJS !== null && fieldDefinition.timestampFormatMomentJS !== undefined) ?
+                                fieldDefinition.timestampFormatMomentJS: validDateFormat.toUpperCase() + "THH:mm:ssZZ";
+        if(!moment(webValue, validTimestampFormat, true).isValid())
+        {
+            invalidizeField(fieldEl, theForm, getI18nMsg("doesNotMatchFormatX",[validTimestampFormat]));
+            return false;
+        }
+        else
+        {
+            validizeField(fieldEl, theForm);
+            return true;
+        }
+    }
+
+    // Check email format
+    // We use a simple regExp for this
+    // TODO: This may acutally call a REST service for uniform checking with Java code
+    if(fieldDefinition.dataType === dataTypes.TYPE_EMAIL)
+    {
+        if(!emailReg.test(webValue))
+        {
+            invalidizeField(fieldEl, theForm, getI18nMsg("invalidFormat",null));
+            return false;
+        }
+        else
+        {
+            validizeField(fieldEl, theForm);
+            return true;
+        }
+    }
+
+    // NUMBERS
+    // TODO: INTEGERS should not have decimal values
+    if(fieldDefinition.dataType === dataTypes.TYPE_INTEGER || fieldDefinition.dataType === dataTypes.TYPE_DOUBLE)
+    {
+        if(!$.isNumeric(webValue.replace(",",".")))
+        {
+            invalidizeField(fieldEl, theForm, getI18nMsg("Number required",null));
+            return false;
+        }
+        else if(fieldDefinition.minValue !== null && webValue < fieldDefinition.minValue)
+        {
+            invalidizeField(fieldEl, theForm, getI18nMsg("Lower than mMinimum",[fieldDefinition.minValue]));
+            return false;
+        }
+        else if(fieldDefinition.maxValue !== null && webValue > fieldDefinition.maxValue)
+        {
+            invalidizeField(fieldEl, theForm, getI18nMsg("Higher than maximum",[fieldDefinition.maxValue]));
+            return false;
+        }
+        else
+        {
+            validizeField(fieldEl, theForm);
+            return true;
+        }
+    }
+    
+    // GIS
+    // Point in WGS84 format
+    // Format [Double], [Double]
+    // TODO Check for minimum and maximum values
+    if(fieldDefinition.dataType === dataTypes.TYPE_POINT_WGS84)
+    {
+        var possibleDoubles = webValue.split(",");
+        if(possibleDoubles.length < 2)
+        {
+            invalidizeField(fieldEl, theForm, getI18nMsg("missingSeparatorComma"));
+            return false;
+        }
+        else if(possibleDoubles.length > 2)
+        {
+            invalidizeField(fieldEl, theForm, getI18nMsg("tooManySeparatorCommas"));
+            return false;
+        }
+        else
+        {
+            for(index in possibleDoubles)
+            {
+                if(!$.isNumeric(possibleDoubles[index]))
+                {
+                    invalidizeField(fieldEl, theForm, getI18nMsg("invalidFormat"));
+                    return false;
+                }
+            }
+        }
+        validizeField(fieldEl, theForm);
+        return true;
+    }
+    
+    // Passwords are evaluated in evaluatePassword
+    if(fieldDefinition.dataType === dataTypes.TYPE_PASSWORD)
+    {
+        return true;
+    }
+    
+    alert("ERROR: Field " + fieldEl.name + " is of type " + fieldDefinition.type + ", and evaluation of that type is not implemented.");
+    return false;
+}
+
+/**
+ * Recursive function to travers upwards in tree until we find the form
+ * for the given element
+ * @param {DOMElement} fieldEl
+ * @returns {DOMelement} the form
+ */
+function getFormForField(fieldEl)
+{
+    // Stop condition
+    if(fieldEl.tagName.toLowerCase() === "form")
+    {
+        return fieldEl;
+    }
+    else
+    {
+        if(fieldEl.parentNode === null || fieldEl.parentNode === undefined)
+        {
+            alert("Could not find a form!");
+            return null;
+        }
+        return getFormForField(fieldEl.parentNode);
+    }
+}
+
+/**
+ * Sends a request to server and gets password evaluation. 
+ * If not OK, highlights field and displays error (somehow)
+ * @param {DOMElement} passwordEl
+ */
+function evaluatePassword(passwordEl)
+{
+    // Get ID for evaluation output
+    var validationOutputEl = getValidationOutputEl(passwordEl, getFormForField(passwordEl));
+    var passwordAttempt = passwordEl.value;
+    // If empty, we return immediately
+    if(passwordAttempt === null || passwordAttempt === "")
+    {
+        validationOutputEl.innerHTML = getI18nMsg("fieldIsRequired",null);
+        styleInvalid(passwordEl);
+        return;
+    }
+    
+    $.ajax({
+        type:"GET",
+        url: "/rest/evaluatepassword/" + passwordAttempt,
+        statusCode:{
+            200: function(data,textStatus, jqXHR){
+                if(data === "true")
+                {
+                    validationOutputEl.innerHTML="";
+                    styleValid(passwordEl);
+                }
+                else
+                {
+                    validationOutputEl.innerHTML=data.replace(/\n/g, "<br/>");
+                    styleInvalid(passwordEl);
+                }
+            },
+            404: function(jqXHR,textStatus,errorThrown){
+                alert ("Error: " + jqXHR.responseText);
+            },
+            500: function(jqXHR,textStatus,errorThrown){
+                alert ("Error: " + jqXHR.responseText);
+            }
+        }
+    });
+}
+
+/**
+ * Marks a field as invalid. Adds validation error message
+ * @param {Element} inputEl
+ * @param {Element} theForm
+ * @param {String} message the validation error message
+ * @returns {void}
+ */
+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;
+}
+
+/**
+ * 
+ * @param {type} inputEl
+ * @param {type} theForm
+ * @returns {undefined}
+ */
+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 = "";
+}
+
+/**
+ * 
+ * @param {type} inputEl
+ * @returns {undefined}
+ */
+function styleInvalid(inputEl)
+{
+    $(inputEl.parentNode).addClass("has-error");
+}
+
+function styleValid(inputEl)
+{
+    $(inputEl.parentNode).removeClass("has-error");
+}
+
+/**
+ * Gets a message by key. The message should be translated
+ * Depends on /js/resourcebundle.js for the dictionary i18Texts
+ * @param {String} msgKey
+ * @param {Array} positionalArguments - for string substitution in message
+ * @returns {String} the message
+ */
+function getI18nMsg(msgKey,positionalArguments)
+{
+    return replaceParams(gettext(msgKey), positionalArguments);
+}
+
+/**
+ * Mimic of the MessageFormat.format method in Java.
+ * Very nicely explained here: http://stackoverflow.com/questions/1353408/messageformat-in-javascript-parameters-in-localized-ui-strings
+ * @param {String} string
+ * @param {Array} replacements
+ * @returns {String}
+ */
+function replaceParams(string, replacements) {
+    return string.replace(/\{(\d+)\}/g, function() {
+        return replacements[arguments[1]];
+    });
+}
\ No newline at end of file
diff --git a/cerealblotchmodels/locale/nb/LC_MESSAGES/django.mo b/cerealblotchmodels/locale/nb/LC_MESSAGES/django.mo
index 9ce97966df7f7f6c232471318e8bde395d8ed13a..bdf1de024f2c80855615a3b84e7611864ff0a126 100644
Binary files a/cerealblotchmodels/locale/nb/LC_MESSAGES/django.mo and b/cerealblotchmodels/locale/nb/LC_MESSAGES/django.mo differ
diff --git a/cerealblotchmodels/locale/nb/LC_MESSAGES/django.po b/cerealblotchmodels/locale/nb/LC_MESSAGES/django.po
index 94e91dcfe43cecce5f9e6d7ff128d66aece5baaf..deb28952733d84163a7b4a42134ba6e3e3fe3a1c 100644
--- a/cerealblotchmodels/locale/nb/LC_MESSAGES/django.po
+++ b/cerealblotchmodels/locale/nb/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2015-05-29 16:02+0200\n"
+"POT-Creation-Date: 2015-06-03 10:11+0200\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -27,92 +27,110 @@ msgstr "Byggbrunflekk"
 msgid "Background data for the barley net blotch model"
 msgstr "Bakgrunnsdata for byggbrunflekkmodellen"
 
-#: templates/cerealblotchmodels/barleynetblotchform.html:39
+#: templates/cerealblotchmodels/barleynetblotchform.html:40
 msgid "Weather station"
 msgstr "Målestasjon"
 
-#: templates/cerealblotchmodels/barleynetblotchform.html:42
+#: templates/cerealblotchmodels/barleynetblotchform.html:46
 msgid "Sowing date"
 msgstr "Sådato"
 
-#: templates/cerealblotchmodels/barleynetblotchform.html:44
+#: templates/cerealblotchmodels/barleynetblotchform.html:51
 msgid "Crop"
 msgstr "Kultur"
 
-#: templates/cerealblotchmodels/barleynetblotchform.html:48
+#: templates/cerealblotchmodels/barleynetblotchform.html:58
 msgid "Same crop as last season"
 msgstr "Samme kultur som i fjor"
 
-#: templates/cerealblotchmodels/barleynetblotchform.html:50
+#: templates/cerealblotchmodels/barleynetblotchform.html:63
 msgid "Plowed"
 msgstr "Pløyd"
 
-#: templates/cerealblotchmodels/barleynetblotchform.html:53
+#: templates/cerealblotchmodels/barleynetblotchform.html:69
 msgid "Observation date"
 msgstr "Observasjonsdato"
 
-#: templates/cerealblotchmodels/barleynetblotchform.html:55
-#, fuzzy, python-format
+#: templates/cerealblotchmodels/barleynetblotchform.html:74
+#, fuzzy
 msgid "%% Infected leaves"
 msgstr "% infiserte blad"
 
-#: templates/cerealblotchmodels/barleynetblotchform.html:57
+#: templates/cerealblotchmodels/barleynetblotchform.html:79
 msgid "Spraying date"
 msgstr "Sprøytedato"
 
-#: templates/cerealblotchmodels/barleynetblotchform.html:59
+#: templates/cerealblotchmodels/barleynetblotchform.html:84
 msgid "Preparation"
 msgstr "Plantevernmiddel"
 
-#: templates/cerealblotchmodels/barleynetblotchform.html:62
+#: templates/cerealblotchmodels/barleynetblotchform.html:90
 msgid "Preparation dose"
 msgstr "Dose"
 
-#: templates/cerealblotchmodels/barleynetblotchform.html:69
+#: templates/cerealblotchmodels/barleynetblotchform.html:99
 msgid "Run model"
 msgstr "Kjør modell"
 
-#: templates/cerealblotchmodels/barleynetblotchform.html:76
+#: templates/cerealblotchmodels/barleynetblotchform.html:106
 msgid "Model is running, please wait"
 msgstr "Modellen kjører, vennligst vent"
 
-#: templates/cerealblotchmodels/barleynetblotchform.html:132
+#: templates/cerealblotchmodels/barleynetblotchform.html:188
+msgid "Temperature, daily mean"
+msgstr "Temperatur, døgngjennomsnitt"
+
+#: templates/cerealblotchmodels/barleynetblotchform.html:189
+msgid "Rain last 28 days"
+msgstr "Regn siste 28 døgn"
+
+#: templates/cerealblotchmodels/barleynetblotchform.html:190
+msgid "Rain, daily"
+msgstr "Regn, daglig"
+
+#: templates/cerealblotchmodels/barleynetblotchform.html:191
+msgid "Day degrees since sowing"
+msgstr "Døgngrader siden såing"
+
+#: templates/cerealblotchmodels/barleynetblotchform.html:192
+#: templates/cerealblotchmodels/barleynetblotchform.html:253
+msgid "Threshold"
+msgstr "Terskelverdi"
+
+#: templates/cerealblotchmodels/barleynetblotchform.html:193
+#: templates/cerealblotchmodels/barleynetblotchform.html:254
+msgid "Disease"
+msgstr "Sykdom"
+
+#: templates/cerealblotchmodels/barleynetblotchform.html:200
 msgid "Time"
 msgstr "Tid"
 
-#: templates/cerealblotchmodels/barleynetblotchform.html:140
+#: templates/cerealblotchmodels/barleynetblotchform.html:208
 msgid "Warning status"
 msgstr "Varselstatus"
 
-#: templates/cerealblotchmodels/barleynetblotchform.html:164
+#: templates/cerealblotchmodels/barleynetblotchform.html:237
 msgid "No data returned"
 msgstr "Ingen data returnert"
 
-#: templates/cerealblotchmodels/barleynetblotchform.html:175
+#: templates/cerealblotchmodels/barleynetblotchform.html:250
 msgid "Disease value"
 msgstr "Sykdomsverdi"
 
-#: templates/cerealblotchmodels/barleynetblotchform.html:178
-msgid "Threshold"
-msgstr "Terskelverdi"
-
-#: templates/cerealblotchmodels/barleynetblotchform.html:179
-msgid "Disease"
-msgstr "Sykdom"
-
-#: templates/cerealblotchmodels/barleynetblotchform.html:182
+#: templates/cerealblotchmodels/barleynetblotchform.html:257
 msgid "Barley net blotch development"
 msgstr "Utvikling av byggbrunflekk"
 
-#: templates/cerealblotchmodels/barleynetblotchform.html:194
+#: templates/cerealblotchmodels/barleynetblotchform.html:269
 msgid "Select weather station"
 msgstr "Velg målestasjon"
 
-#: templates/cerealblotchmodels/barleynetblotchform.html:220
+#: templates/cerealblotchmodels/barleynetblotchform.html:296
 msgid "Select crop"
 msgstr "Velg kultur"
 
-#: templates/cerealblotchmodels/barleynetblotchform.html:247
+#: templates/cerealblotchmodels/barleynetblotchform.html:324
 msgid "Select preparation"
 msgstr "Velg plantevernmiddel"
 
diff --git a/cerealblotchmodels/static/cerealblotchmodels/formdefinitions/barleyNetBlotchForm.json b/cerealblotchmodels/static/cerealblotchmodels/formdefinitions/barleyNetBlotchForm.json
new file mode 100644
index 0000000000000000000000000000000000000000..3de2dd8a633d0856ab88072f0ef614c1007204c7
--- /dev/null
+++ b/cerealblotchmodels/static/cerealblotchmodels/formdefinitions/barleyNetBlotchForm.json
@@ -0,0 +1,78 @@
+{
+    "_licenseNote": [
+        "Copyright (c) 2014 Bioforsk <http://www.bioforsk.no/>. ",
+        "",
+        "This file is part of VIPSLogic. ",
+        "VIPSLogic is free software: you can redistribute it and/or modify ",
+        "it under the terms of the Bioforsk Open Source License as published by ",
+        "Bioforsk, 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 ",
+        "Bioforsk Open Source License for more details. ",
+        "",
+        "You should have received a copy of the Bioforsk Open Source License ",
+        "along with VIPSLogic.  If not, see <http://www.bioforsk.no/licenses/>. "
+    ],
+    "_comment" : "Structure of the observationForm and how to validate it",
+    "fields": [
+        {
+            "name" : "weatherStationId",
+            "dataType" : "INTEGER",
+            "fieldType" : "SELECT_SINGLE",
+            "required" : true,
+            "nullValue" : "-1"
+        },
+        {
+            "name" : "sowingDate",
+            "dataType" : "DATE",
+            "required" : true
+        },
+        {
+            "name" : "cropOrganismId",
+            "dataType" : "INTEGER",
+            "fieldType" : "SELECT_SINGLE",
+            "required" : true,
+            "nullValue" : "-1"
+        },
+        {
+            "name" : "sameCropAsLastSeason",
+            "dataType" : "STRING",
+            "required" : false
+        },
+        {
+            "name" : "plowed",
+            "dataType" : "STRING",
+            "required" : false
+        },
+        {
+            "name" : "observationDate",
+            "dataType" : "DATE",
+            "required" : false
+        },
+        {
+            "name" : "observationValue",
+            "dataType" : "DOUBLE",
+            "required" : false
+        },
+        {
+            "name" : "sprayingDate",
+            "dataType" : "DATE",
+            "required" : false
+        },
+        {
+            "name" : "preparationId",
+            "dataType" : "INTEGER",
+            "fieldType" : "SELECT_SINGLE",
+            "required" : false,
+            "nullValue" : "-1"
+        },
+        {
+            "name" : "preparationDose",
+            "dataType" : "DOUBLE",
+            "required" : false
+        }
+    ]
+}
diff --git a/cerealblotchmodels/templates/cerealblotchmodels/barleynetblotchform.html b/cerealblotchmodels/templates/cerealblotchmodels/barleynetblotchform.html
index e4eb874f8be1eb50019d6353d044873c63d0b755..40efecc5f6c1b38d2e62895b5bede9450c8ae6e5 100644
--- a/cerealblotchmodels/templates/cerealblotchmodels/barleynetblotchform.html
+++ b/cerealblotchmodels/templates/cerealblotchmodels/barleynetblotchform.html
@@ -22,51 +22,81 @@
  
 {% endcomment %}
 {% load i18n %}
-{% block title%}{% trans "Barley net blotch" %}{%endblock%}
+{% block title %}{% trans "Barley net blotch" %}{% endblock %}
 
 {% block content %}
 <h1>{% trans "Barley net blotch" %}</h1>
 
-<form role="form" id="barleynetblotchForm">
+<form role="form" id="{{ form_id }}">
 	<div class="row">
 		<div class="col-md-12">
 			<h2>{% trans "Background data for the barley net blotch model" %}</h2>
 		</div>
 	</div>
 	<div class="row">
-		<div class="col-md-6 form-group">
+		<div class="col-md-6">
 			<input type="hidden" name="timeZone" value="Europe/Oslo"/>
-			<label for="weatherStationId">{% trans "Weather station" %}</label>
-			<select name="weatherStationId" id="weatherStationId" class="form-control">
-			</select>
-			<label for="sowingDate">{% trans "Sowing date" %}</label>
-			<input type="date" name="sowingDate" class="form-control"/>
-			<label for="cropOrganismId">{% trans "Crop" %}</label>
-			<select name="cropOrganismId" id="cropOrganismId" class="form-control">
-			</select>
-			<input type="checkbox" name="sameCropAsLastSeason"/>
-			<label for="sameCropAsLastSeason">{% trans "Same crop as last season" %}</label><br/>
-			<input type="checkbox" name="plowed"/>
-			<label for="plowed"><span>{% trans "Plowed" %}</span></label><br/>
+			<div class="form-group">
+				<label for="weatherStationId">{% trans "Weather station" %}</label>
+				<select name="weatherStationId" id="weatherStationId" class="form-control" onblur="validateField(this);">
+				</select>
+				<span class="help-block" id="{{ form_id }}_weatherStationId_validation"></span>
+			</div>
+			<div class="form-group">
+				<label for="sowingDate">{% trans "Sowing date" %}</label>
+				<input type="date" name="sowingDate" class="form-control" onblur="validateField(this);"/>
+				<span class="help-block" id="{{ form_id }}_sowingDate_validation"></span>
+			</div>
+			<div class="form-group">
+				<label for="cropOrganismId">{% trans "Crop" %}</label>
+				<select name="cropOrganismId" id="cropOrganismId" class="form-control" onblur="validateField(this);">
+				</select>
+				<span class="help-block" id="{{ form_id }}_cropOrganismId_validation"></span>
+			</div>
+			<div class="form-group">
+				<input type="checkbox" name="sameCropAsLastSeason"/>
+				<label for="sameCropAsLastSeason">{% trans "Same crop as last season" %}</label><br/>
+				<span class="help-block" id="{{ form_id }}_sameCropAsLastSeason_validation"></span>
+			</div>
+			<div class="form-group">
+				<input type="checkbox" name="plowed"/>
+				<label for="plowed"><span>{% trans "Plowed" %}</span></label><br/>
+				<span class="help-block" id="{{ form_id }}_plowed_validation"></span>
+			</div>
 		</div>
 		<div class="col-md-6 form-group">
-			<label for="observationDate">{% trans "Observation date" %}</label>
-			<input type="date" name="observationDate" class="form-control"/>
-			<label for ="observationValue">{% trans "% Infected leaves" %}</label>
-			<input type="number" name="observationValue" class="form-control"/>
-			<label for="sprayingDate">{% trans "Spraying date" %}</label>
-			<input type="date" name="sprayingDate" class="form-control"/>
-			<label for="preparationId">{% trans "Preparation" %}</label>
-			<select name="preparationId" id="preparationId" class="form-control">
-			</select>
-			<label for="preparationDose">{% trans "Preparation dose" %} (ml/daa)</label>
-			<input type="number" name="preparationDose" class="form-control"/>
+			<div class="form-group">
+				<label for="observationDate">{% trans "Observation date" %}</label>
+				<input type="date" name="observationDate" class="form-control"/>
+				<span class="help-block" id="{{ form_id }}_observationDate_validation"></span>
+			</div>
+			<div class="form-group">
+				<label for ="observationValue">{% trans "% Infected leaves" %}</label>
+				<input type="number" name="observationValue" class="form-control" min="0" max="100"/>
+				<span class="help-block" id="{{ form_id }}_observationValue_validation"></span>
+			</div>
+			<div class="form-group">
+				<label for="sprayingDate">{% trans "Spraying date" %}</label>
+				<input type="date" name="sprayingDate" class="form-control"/>
+				<span class="help-block" id="{{ form_id }}_sprayingDate_validation"></span>
+			</div>
+			<div class="form-group">
+				<label for="preparationId">{% trans "Preparation" %}</label>
+				<select name="preparationId" id="preparationId" class="form-control">
+				</select>
+				<span class="help-block" id="{{ form_id }}_preparationId_validation"></span>
+			</div>
+			<div class="form-group">
+				<label for="preparationDose">{% trans "Preparation dose" %} (ml/daa)</label>
+				<input type="number" name="preparationDose" class="form-control"/>
+				<span class="help-block" id="{{ form_id }}_preparationDose_validation"></span>
+			</div>
 			
 		</div>
 	</div>
 	<div class="row">
 		<div class="col-md-6 form-group">
-			<button type="button" class="btn btn-default" onclick="runModel();">{% trans "Run model" %}</button>
+			<button type="button" class="btn btn-default" onclick="if(validateForm(document.getElementById('{{ form_id }}')) & validateFormExtra()){runModel();}">{% trans "Run model" %}</button>
 		</div>
 	</div>
 	
@@ -88,20 +118,46 @@
 			<table id="resultsTable" class="table table-striped"></table>
 		</div>
 </div>
+
 {% endblock %}
 {% block customJS %}
+<script type="text/javascript" src="{% url "django.views.i18n.javascript_catalog" %}"></script>
 <script type="text/javascript" src="{% url "views.settings_js" %}"></script>
 <script type="text/javascript" src="{% static "js/3rdparty/moment.min.js" %}"></script>
 <script type="text/javascript" src="{% static "js/3rdparty/highcharts.js" %}"></script>
 <script type="text/javascript" src="{% static "js/util.js" %}"></script>
+<script type="text/javascript" src="{% static "js/validateForm.js" %}"></script>
 <script type="text/javascript" src="{% static "organisms/organismsUtil.js" %}"></script>
 <script type="text/javascript" src="{% static "forecasts/js/forecasts.js" %}"></script>
 <script type="text/javascript">
 
+	function validateFormExtra()
+	{
+		var theForm = document.getElementById("{{ form_id }}");
+		// Observation: Either no fields or all fields must be set
+		if(theForm["observationDate"].value.trim() == "" ^  theForm["observationValue"].value.trim() == "")
+		{
+			alert(gettext("Missing observation information"));
+			return false;
+		}
+		// Spraying: Either no fields or all fields must be set
+		var truthies = (theForm["sprayingDate"].value.trim() == "" ? 1 : 0)
+						+ (theForm["preparationId"].options[theForm["preparationId"].selectedIndex].value == "-1" ? 1 : 0)
+						+ (theForm["preparationDose"].value.trim() == "" ? 1 : 0);
+		if(truthies != 0 && truthies != 3)
+		{
+			alert(gettext("Missing spraying information"));
+			return false;
+		}
+		return true;
+	}
+
 	$(document).ready(function() {
 		initWeatherStations();
 		initCrops();
 		initPreparations();
+		// Init form validation
+		loadFormDefinition("{{ form_id }}","/static/cerealblotchmodels/formdefinitions/")
 	});
 
 	var VIPSOrganizationId = {{vips_organization_id}};
@@ -111,7 +167,8 @@
 		document.getElementById("errorMessageContainer").style.display="none";
 		document.getElementById("runningModelMessage").style.display="block";
 		// TODO: Validate form
-		var formStr = $("#barleynetblotchForm").serialize();
+		// 
+		var formStr = $("#{{ form_id }}").serialize();
 		var request = $.ajax({
 	        type:"GET",
 	        url: "http://" + settings.vipslogicServerName + "/rest/barleynetblotchmodel/runmodel/" + VIPSOrganizationId + "?" + formStr,
@@ -126,8 +183,19 @@
 		//console.log(formStr);
 	}
 	
+	var paramDict = {
+		"BARLEYNETB.DG2410" : "DG2410",
+		"WEATHER.TMD": "{% trans "Temperature, daily mean" %}",
+		"WEATHER.RR28" : "{% trans "Rain last 28 days" %}",
+		"WEATHER.RRD" : "{% trans "Rain, daily" %}",
+		"WEATHER.TMDD0C" :"{% trans "Day degrees since sowing" %}",
+		"BARLEYNETB.Y" : "{% trans "Threshold" %}",
+		"BARLEYNETB.X_ADJUSTED" : "{% trans "Disease" %}",
+	};
+	
 	var renderResults = function(data,textStatus, jqXHR)
 	{
+		data.sort(compareForecastResults).reverse();
 		// First attempt: A table!
 		var headingLine = "<tr><td style=\"font-weight: bold;\">{% trans "Time" %}</td>";
         if(data.length > 0)
@@ -135,7 +203,7 @@
             var allKeys = data[0].keys;
             for(var i in allKeys)
             {
-                headingLine +="<td style=\"font-weight: bold;\">" + allKeys[i] + "</td>";
+                headingLine +="<td style=\"font-weight: bold;\">" + paramDict[allKeys[i]] + "</td>";
             }
             headingLine +="<td style=\"font-weight: bold;\">{% trans "Warning status" %}</td>";
             headingLine += "</tr>";
@@ -143,10 +211,15 @@
             var table = ["<table border=\"1\">",headingLine];
             for(item in data)
             {
-                var resultLine = "<tr><td>" + new Date(data[item].resultValidTime) + "</td>";
+                var resultLine = "<tr><td>" + moment(data[item].resultValidTime).format() + "</td>";
                 for(var i in allKeys)
                 {
-                    resultLine +="<td>" + data[item].allValues[allKeys[i]] + "</td>";
+                	var value = data[item].allValues[allKeys[i]];
+                	if(value != null && $.isNumeric(value))
+                	{
+                		value = parseFloat(value).toFixed(2);
+                	}
+                    resultLine +="<td>" + value + "</td>";
                 }
                 resultLine +="<td style=\"background-color:";
                 var st = data[item].warningStatus;
@@ -167,6 +240,8 @@
         document.getElementById("errorMessageContainer").style.display="none";
         document.getElementById("results").style.display="block";
         // Then: The chart
+        // We must sort data ascending again
+        data.reverse();
         var warningStatusPlotBandData = getWarningStatusPlotBandData(data);
         //console.log(warningStatusPlotBandData);
         var data = getHighChartsSeries(
@@ -192,6 +267,7 @@
 	            200: function(data,textStatus, jqXHR){
 	            	// Building result HTML
 	            	var wsHTML=["<option value=\"-1\">-- {% trans "Select weather station" %} --</option>"];
+	            	data.sort(compareWeatherStations);
 	            	for(var i in data)
 	            	{
 	            		var ws = data[i];
@@ -218,12 +294,13 @@
 	            200: function(data,textStatus, jqXHR){
 	            	// Building result HTML
 	            	var cropHTML=["<option value=\"-1\">-- {% trans "Select crop" %} --</option>"];
+	            	data.sort(compareOrganisms);
 	            	for(var i in data)
 	            	{
 	            		var crop = data[i];
 	            		
 	            		//console.log(ws);
-	            		cropHTML.push("<option value=\"" + crop["cropOrganismId"] + "\">" + getOrganismLocalNameWithFallback(crop,settings.currentLanguage,settings.defaultLanguage) + "</option>");
+	            		cropHTML.push("<option value=\"" + crop["organismId"] + "\">" + getOrganismLocalNameWithFallback(crop,settings.currentLanguage,settings.defaultLanguage) + "</option>");
 	            	}
 	            	document.getElementById("cropOrganismId").innerHTML = cropHTML.join("");
 	        	},
@@ -245,10 +322,10 @@
 	            200: function(data,textStatus, jqXHR){
 	            	// Building result HTML
 	            	var preparationHTML=["<option value=\"-1\">-- {% trans "Select preparation" %} --</option>"];
+	            	data.sort(comparePreparations);
 	            	for(var i in data)
 	            	{
 	            		var preparation = data[i];
-	            		
 	            		//console.log(ws);
 	            		preparationHTML.push("<option value=\"" + preparation["preparationId"] + "\">" + preparation["preparationName"] + "</option>");
 	            	}
diff --git a/cerealblotchmodels/views.py b/cerealblotchmodels/views.py
index 74001dab53b58bb5977660e7f77be34f911fc42e..120ffaa0806e8ab0dedbc3e774aaf446e10cd198 100644
--- a/cerealblotchmodels/views.py
+++ b/cerealblotchmodels/views.py
@@ -18,5 +18,8 @@ def barleynetblotchform(request):
     vips_organization_id = settings.VIPS_ORGANIZATION_ID
     if request.GET.get("organizationId") != None:
         vips_organization_id = request.GET.get("organizationId")
-    context = {"vips_organization_id": vips_organization_id}
+    context = {
+               "vips_organization_id": vips_organization_id,
+               "form_id" : "barleyNetBlotchForm"
+               }
     return render(request, 'cerealblotchmodels/barleynetblotchform.html', context)
\ No newline at end of file
diff --git a/forecasts/static/forecasts/js/forecasts.js b/forecasts/static/forecasts/js/forecasts.js
index 0ad33f12ee434d2383bebce5870afd018af7fc63..d3dd3bef2ce357307fea0b3c3fac95482767da3c 100644
--- a/forecasts/static/forecasts/js/forecasts.js
+++ b/forecasts/static/forecasts/js/forecasts.js
@@ -215,7 +215,7 @@ function getWarningStatusPlotBandData(forecastResults)
 	var previousForecastResult = null;
 	var plotBand = null;
 	var bandOffset = 0;
-	if (forecastResults.length >=2)
+	if (forecastResults != null && forecastResults.length >=2)
 	{
 		// Replace with Moment.js
 		var start = forecastResults[0]["resultValidTime"];
diff --git a/organisms/static/organisms/organismsUtil.js b/organisms/static/organisms/organismsUtil.js
index a64454e609040a64354067c399a183ace1c0b985..a64bff3a0a15f9b999144f92701985f44d7839f3 100644
--- a/organisms/static/organisms/organismsUtil.js
+++ b/organisms/static/organisms/organismsUtil.js
@@ -45,3 +45,10 @@ function getOrganismLocalNameWithFallback(organism, currentLanguage, defaultLang
 	}
 	return organism["latinName"];
 }
+
+var compareOrganisms = function(a,b)
+{
+	var aName = getOrganismLocalNameWithFallback(a,settings.currentLanguage,settings.defaultLanguage);
+	var bName = getOrganismLocalNameWithFallback(b,settings.currentLanguage,settings.defaultLanguage);
+	return compareStrings(aName,bName);
+}