Comparing version 0.5.6 to 0.5.7
(function () { | ||
var async=require('async'); | ||
var async = require('async'); | ||
/* | ||
* Sample Usage | ||
* | ||
* var engine = formsRulesEngine(form-definition); | ||
* | ||
* engine.validateForms(form-submission, function(err, res) {}); | ||
* res: | ||
* { | ||
* "validation": { | ||
* "fieldId": { | ||
* "fieldId": "", | ||
* "valid": true, | ||
* "errorMessages": [ | ||
* "length should be 3 to 5", | ||
* "should not contain dammit", | ||
* "should repeat at least 2 times" | ||
* ] | ||
* }, | ||
* "fieldId1": { | ||
* | ||
* } | ||
* } | ||
* } | ||
* | ||
* | ||
* engine.validateField(fieldId, submissionJSON, function(err,res) {}); | ||
* // validate only field values on validation (no rules, no repeat checking) | ||
* res: | ||
* "validation":{ | ||
* "fieldId":{ | ||
* "fieldId":"", | ||
* "valid":true, | ||
* "errorMessages":[ | ||
* "length should be 3 to 5", | ||
* "should not contain dammit" | ||
* ] | ||
* } | ||
* } | ||
* | ||
* engine.checkRules(submissionJSON, unction(err, res) {}) | ||
* // check all rules actions | ||
* res: | ||
* { | ||
* "actions": { | ||
* "pages": { | ||
* "targetId": { | ||
* "targetId": "", | ||
* "action": "show|hide" | ||
* } | ||
* }, | ||
* "fields": { | ||
* | ||
* } | ||
* } | ||
* } | ||
* | ||
*/ | ||
/* | ||
* Sample Usage | ||
* | ||
* var engine = formsRulesEngine(form-definition); | ||
* | ||
* engine.validateForms(form-submission, function(err, res) {}); | ||
* res: | ||
* { | ||
* "validation": { | ||
* "fieldId": { | ||
* "fieldId": "", | ||
* "valid": true, | ||
* "errorMessages": [ | ||
* "length should be 3 to 5", | ||
* "should not contain dammit", | ||
* "should repeat at least 2 times" | ||
* ] | ||
* }, | ||
* "fieldId1": { | ||
* | ||
* } | ||
* } | ||
* } | ||
* | ||
* | ||
* engine.validateField(fieldId, submissionJSON, function(err,res) {}); | ||
* // validate only field values on validation (no rules, no repeat checking) | ||
* res: | ||
* "validation":{ | ||
* "fieldId":{ | ||
* "fieldId":"", | ||
* "valid":true, | ||
* "errorMessages":[ | ||
* "length should be 3 to 5", | ||
* "should not contain dammit" | ||
* ] | ||
* } | ||
* } | ||
* | ||
* engine.checkRules(submissionJSON, unction(err, res) {}) | ||
* // check all rules actions | ||
* res: | ||
* { | ||
* "actions": { | ||
* "pages": { | ||
* "targetId": { | ||
* "targetId": "", | ||
* "action": "show|hide" | ||
* } | ||
* }, | ||
* "fields": { | ||
* | ||
* } | ||
* } | ||
* } | ||
* | ||
*/ | ||
var FIELD_TYPE_CHECKBOX = "checkboxes"; | ||
var FIELD_TYPE_DATETIME = "dateTime"; | ||
var FIELD_TYPE_DATETIME_DATETIMEUNIT_DATEONLY = "date"; | ||
var FIELD_TYPE_DATETIME_DATETIMEUNIT_TIMEONLY = "time"; | ||
var FIELD_TYPE_DATETIME_DATETIMEUNIT_DATETIME = "datetime"; | ||
var FIELD_TYPE_CHECKBOX = "checkboxes"; | ||
var FIELD_TYPE_DATETIME = "dateTime"; | ||
var FIELD_TYPE_DATETIME_DATETIMEUNIT_DATEONLY = "date"; | ||
var FIELD_TYPE_DATETIME_DATETIMEUNIT_TIMEONLY = "time"; | ||
var FIELD_TYPE_DATETIME_DATETIMEUNIT_DATETIME = "datetime"; | ||
var formsRulesEngine = function(formDef) { | ||
var initialised; | ||
var formsRulesEngine = function (formDef) { | ||
var initialised; | ||
var definition = formDef; | ||
var submission; | ||
var definition = formDef; | ||
var submission; | ||
var fieldMap = {}; | ||
var requiredFieldMap = {}; | ||
var submissionRequiredFieldsMap = {}; // map to hold the status of the required fields per submission | ||
var fieldRulePredicateMap = {}; | ||
var fieldRuleSubjectMap = {}; | ||
var pageRulePredicateMap = {}; | ||
var pageRuleSubjectMap = {}; | ||
var submissionFieldsMap = {}; | ||
var validatorsMap = { | ||
"text": validatorString, | ||
"textarea": validatorString, | ||
"number": validatorNumericString, | ||
"emailAddress": validatorEmail, | ||
"dropdown": validatorDropDown, | ||
"radio": validatorDropDown, | ||
"checkboxes": validatorCheckboxes, | ||
"location": validatorLocation, | ||
"locationMap": validatorLocationMap, | ||
"photo": validatorFile, | ||
"signature": validatorFile, | ||
"file": validatorFile, | ||
"dateTime": validatorDateTime, | ||
"url": validatorString, | ||
"sectionBreak": validatorSection | ||
}; | ||
var fieldMap = {}; | ||
var requiredFieldMap = {}; | ||
var submissionRequiredFieldsMap = {}; // map to hold the status of the required fields per submission | ||
var fieldRulePredicateMap = {}; | ||
var fieldRuleSubjectMap = {}; | ||
var pageRulePredicateMap = {}; | ||
var pageRuleSubjectMap = {}; | ||
var submissionFieldsMap = {}; | ||
var validatorsMap = { | ||
"text": validatorString, | ||
"textarea": validatorString, | ||
"number": validatorNumericString, | ||
"emailAddress": validatorEmail, | ||
"dropdown": validatorDropDown, | ||
"radio": validatorDropDown, | ||
"checkboxes": validatorCheckboxes, | ||
"location": validatorLocation, | ||
"locationMap": validatorLocationMap, | ||
"photo": validatorFile, | ||
"signature": validatorFile, | ||
"file": validatorFile, | ||
"dateTime": validatorDateTime, | ||
"url": validatorString, | ||
"sectionBreak": validatorSection | ||
}; | ||
var validatorsClientMap = { | ||
"text": validatorString, | ||
"textarea": validatorString, | ||
"number": validatorNumericString, | ||
"emailAddress": validatorEmail, | ||
"dropdown": validatorDropDown, | ||
"radio": validatorDropDown, | ||
"checkboxes": validatorCheckboxes, | ||
"location": validatorLocation, | ||
"locationMap": validatorLocationMap, | ||
"photo": validatorAnyFile, | ||
"signature": validatorAnyFile, | ||
"file": validatorAnyFile, | ||
"dateTime": validatorDateTime, | ||
"url": validatorString, | ||
"sectionBreak": validatorSection | ||
}; | ||
var validatorsClientMap = { | ||
"text": validatorString, | ||
"textarea": validatorString, | ||
"number": validatorNumericString, | ||
"emailAddress": validatorEmail, | ||
"dropdown": validatorDropDown, | ||
"radio": validatorDropDown, | ||
"checkboxes": validatorCheckboxes, | ||
"location": validatorLocation, | ||
"locationMap": validatorLocationMap, | ||
"photo": validatorAnyFile, | ||
"signature": validatorAnyFile, | ||
"file": validatorAnyFile, | ||
"dateTime": validatorDateTime, | ||
"url": validatorString, | ||
"sectionBreak": validatorSection | ||
}; | ||
var isFieldRuleSubject = function(fieldId) { | ||
return !!fieldRuleSubjectMap[fieldId]; | ||
}; | ||
var isFieldRuleSubject = function (fieldId) { | ||
return !!fieldRuleSubjectMap[fieldId]; | ||
}; | ||
var isPageRuleSubject = function(pageId) { | ||
return !!pageRuleSubjectMap[pageId]; | ||
}; | ||
var isPageRuleSubject = function (pageId) { | ||
return !!pageRuleSubjectMap[pageId]; | ||
}; | ||
function buildFieldMap(cb) { | ||
// Iterate over all fields in form definition & build fieldMap | ||
async.each(definition.pages, function(page, cbPages) { | ||
async.each(page.fields, function(field, cbFields) { | ||
field.pageId = page._id; | ||
function buildFieldMap(cb) { | ||
// Iterate over all fields in form definition & build fieldMap | ||
async.each(definition.pages, function (page, cbPages) { | ||
async.each(page.fields, function (field, cbFields) { | ||
field.pageId = page._id; | ||
field.fieldOptions = field.fieldOptions ? field.fieldOptions : {}; | ||
field.fieldOptions.definition = field.fieldOptions.definition ? field.fieldOptions.definition : {}; | ||
field.fieldOptions.validation = field.fieldOptions.validation ? field.fieldOptions.validation : {}; | ||
field.fieldOptions = field.fieldOptions ? field.fieldOptions : {}; | ||
field.fieldOptions.definition = field.fieldOptions.definition ? field.fieldOptions.definition : {}; | ||
field.fieldOptions.validation = field.fieldOptions.validation ? field.fieldOptions.validation : {}; | ||
fieldMap[field._id] = field; | ||
if (field.required) { | ||
requiredFieldMap[field._id] = {field: field, submitted: false, validated: false}; | ||
} | ||
return cbFields(); | ||
}, function (err) { | ||
return cbPages(); | ||
}); | ||
}, cb); | ||
} | ||
fieldMap[field._id] = field; | ||
if (field.required) { | ||
requiredFieldMap[field._id] = { | ||
field: field, | ||
submitted: false, | ||
validated: false | ||
}; | ||
} | ||
return cbFields(); | ||
}, function (err) { | ||
return cbPages(); | ||
}); | ||
}, cb); | ||
} | ||
function buildFieldRuleMaps(cb) { | ||
// Iterate over all rules in form definition & build ruleSubjectMap | ||
async.each(definition.fieldRules, function(rule, cbRules) { | ||
async.each(rule.ruleConditionalStatements, function(ruleConditionalStatement, cbRuleConditionalStatements) { | ||
var fieldId = ruleConditionalStatement.sourceField; | ||
fieldRulePredicateMap[fieldId] = fieldRulePredicateMap[fieldId] || []; | ||
fieldRulePredicateMap[fieldId].push(rule); | ||
return cbRuleConditionalStatements(); | ||
}, function (err) { | ||
fieldRuleSubjectMap[rule.targetField] = fieldRuleSubjectMap[rule.targetField] || []; | ||
fieldRuleSubjectMap[rule.targetField].push(rule); | ||
return cbRules(); | ||
}); | ||
}, cb); | ||
} | ||
function buildFieldRuleMaps(cb) { | ||
// Iterate over all rules in form definition & build ruleSubjectMap | ||
async.each(definition.fieldRules, function (rule, cbRules) { | ||
async.each(rule.ruleConditionalStatements, function (ruleConditionalStatement, cbRuleConditionalStatements) { | ||
var fieldId = ruleConditionalStatement.sourceField; | ||
fieldRulePredicateMap[fieldId] = fieldRulePredicateMap[fieldId] || []; | ||
fieldRulePredicateMap[fieldId].push(rule); | ||
return cbRuleConditionalStatements(); | ||
}, function (err) { | ||
fieldRuleSubjectMap[rule.targetField] = fieldRuleSubjectMap[rule.targetField] || []; | ||
fieldRuleSubjectMap[rule.targetField].push(rule); | ||
return cbRules(); | ||
}); | ||
}, cb); | ||
} | ||
function buildPageRuleMap(cb) { | ||
// Iterate over all rules in form definition & build ruleSubjectMap | ||
async.each(definition.pageRules, function(rule, cbRules) { | ||
var rulesId = rule._id; | ||
async.each(rule.ruleConditionalStatements, function(ruleConditionalStatement, cbRulePredicates) { | ||
var fieldId = ruleConditionalStatement.sourceField; | ||
pageRulePredicateMap[fieldId] = pageRulePredicateMap[fieldId] || []; | ||
pageRulePredicateMap[fieldId].push(rule); | ||
return cbRulePredicates(); | ||
}, function (err) { | ||
pageRuleSubjectMap[rule.targetPage] = pageRuleSubjectMap[rule.targetPage] || []; | ||
pageRuleSubjectMap[rule.targetPage].push(rule); | ||
return cbRules(); | ||
}); | ||
}, cb); | ||
} | ||
function buildPageRuleMap(cb) { | ||
// Iterate over all rules in form definition & build ruleSubjectMap | ||
async.each(definition.pageRules, function (rule, cbRules) { | ||
var rulesId = rule._id; | ||
async.each(rule.ruleConditionalStatements, function (ruleConditionalStatement, cbRulePredicates) { | ||
var fieldId = ruleConditionalStatement.sourceField; | ||
pageRulePredicateMap[fieldId] = pageRulePredicateMap[fieldId] || []; | ||
pageRulePredicateMap[fieldId].push(rule); | ||
return cbRulePredicates(); | ||
}, function (err) { | ||
pageRuleSubjectMap[rule.targetPage] = pageRuleSubjectMap[rule.targetPage] || []; | ||
pageRuleSubjectMap[rule.targetPage].push(rule); | ||
return cbRules(); | ||
}); | ||
}, cb); | ||
} | ||
function buildSubmissionFieldsMap(cb) { | ||
submissionRequiredFieldsMap = JSON.parse(JSON.stringify(requiredFieldMap)); // clone the map for use with this submission | ||
submissionFieldsMap = {}; // start with empty map, rulesEngine can be called with multiple submissions | ||
function buildSubmissionFieldsMap(cb) { | ||
submissionRequiredFieldsMap = JSON.parse(JSON.stringify(requiredFieldMap)); // clone the map for use with this submission | ||
submissionFieldsMap = {}; // start with empty map, rulesEngine can be called with multiple submissions | ||
// iterate over all the fields in the submissions and build a map for easier lookup | ||
async.each(submission.formFields, function(formField, cb) { | ||
if (!formField.fieldId) return cb(new Error("No fieldId in this submission entry: " + util.inspect(formField))); | ||
// iterate over all the fields in the submissions and build a map for easier lookup | ||
async.each(submission.formFields, function (formField, cb) { | ||
if (!formField.fieldId) return cb(new Error("No fieldId in this submission entry: " + util.inspect(formField))); | ||
submissionFieldsMap[formField.fieldId] = formField; | ||
return cb(); | ||
}, cb); | ||
} | ||
submissionFieldsMap[formField.fieldId] = formField; | ||
return cb(); | ||
}, cb); | ||
} | ||
function init(cb) { | ||
if(initialised) return cb(); | ||
async.parallel([ | ||
buildFieldMap, | ||
buildFieldRuleMaps, | ||
buildPageRuleMap | ||
], function(err) { | ||
if (err) return cb(err); | ||
initialised = true; | ||
return cb(); | ||
}); | ||
} | ||
function init(cb) { | ||
if (initialised) return cb(); | ||
async.parallel([ | ||
buildFieldMap, | ||
buildFieldRuleMaps, | ||
buildPageRuleMap | ||
], function (err) { | ||
if (err) return cb(err); | ||
initialised = true; | ||
return cb(); | ||
}); | ||
} | ||
function initSubmission(formSubmission, cb) { | ||
init(function(err){ | ||
if (err) return cb(err); | ||
function initSubmission(formSubmission, cb) { | ||
init(function (err) { | ||
if (err) return cb(err); | ||
submission = formSubmission; | ||
buildSubmissionFieldsMap(cb); | ||
}); | ||
} | ||
function getPreviousFieldValues(submittedField, previousSubmission, cb) { | ||
if(previousSubmission && previousSubmission.formFields) { | ||
async.filter(previousSubmission.formFields, function (formField, cb) { | ||
return cb(formField.fieldId.toString() == submittedField.fieldId.toString()); | ||
}, function (results) { | ||
var previousFieldValues = null; | ||
if (results && results[0] && results[0].fieldValues) { | ||
previousFieldValues = results[0].fieldValues; | ||
} | ||
return cb(undefined, previousFieldValues); | ||
submission = formSubmission; | ||
buildSubmissionFieldsMap(cb); | ||
}); | ||
} else { | ||
return cb(); | ||
} | ||
} | ||
function validateForm(submission, previousSubmission, cb) { | ||
if ("function" === typeof previousSubmission) { | ||
cb = previousSubmission; | ||
previousSubmission = null; | ||
function getPreviousFieldValues(submittedField, previousSubmission, cb) { | ||
if (previousSubmission && previousSubmission.formFields) { | ||
async.filter(previousSubmission.formFields, function (formField, cb) { | ||
return cb(formField.fieldId.toString() == submittedField.fieldId.toString()); | ||
}, function (results) { | ||
var previousFieldValues = null; | ||
if (results && results[0] && results[0].fieldValues) { | ||
previousFieldValues = results[0].fieldValues; | ||
} | ||
return cb(undefined, previousFieldValues); | ||
}); | ||
} else { | ||
return cb(); | ||
} | ||
} | ||
init(function(err){ | ||
if (err) return cb(err); | ||
initSubmission(submission, function (err) { | ||
function validateForm(submission, previousSubmission, cb) { | ||
if ("function" === typeof previousSubmission) { | ||
cb = previousSubmission; | ||
previousSubmission = null; | ||
} | ||
init(function (err) { | ||
if (err) return cb(err); | ||
async.waterfall([ | ||
function (cb) { | ||
return cb(undefined, {validation:{valid: true}}); // any invalid fields will set this to false | ||
}, | ||
function (res, cb) { | ||
validateSubmittedFields(res, previousSubmission, cb); | ||
}, | ||
checkIfRequiredFieldsNotSubmitted | ||
], function (err, results) { | ||
initSubmission(submission, function (err) { | ||
if (err) return cb(err); | ||
return cb(undefined, results); | ||
async.waterfall([ | ||
function (cb) { | ||
return cb(undefined, { | ||
validation: { | ||
valid: true | ||
} | ||
}); // any invalid fields will set this to false | ||
}, | ||
function (res, cb) { | ||
validateSubmittedFields(res, previousSubmission, cb); | ||
}, | ||
checkIfRequiredFieldsNotSubmitted | ||
], function (err, results) { | ||
if (err) return cb(err); | ||
return cb(undefined, results); | ||
}); | ||
}); | ||
}); | ||
}); | ||
} | ||
} | ||
function validateSubmittedFields(res, previousSubmission, cb) { | ||
// for each field, call validateField | ||
async.each(submission.formFields, function(submittedField, callback) { | ||
var fieldID = submittedField.fieldId; | ||
var fieldDef = fieldMap[fieldID]; | ||
function validateSubmittedFields(res, previousSubmission, cb) { | ||
// for each field, call validateField | ||
async.each(submission.formFields, function (submittedField, callback) { | ||
var fieldID = submittedField.fieldId; | ||
var fieldDef = fieldMap[fieldID]; | ||
getPreviousFieldValues(submittedField, previousSubmission, function (err, previousFieldValues) { | ||
if(err) return callback(err); | ||
getFieldValidationStatus(submittedField, fieldDef, previousFieldValues, function(err, fieldRes) { | ||
if(err) return callback(err); | ||
getPreviousFieldValues(submittedField, previousSubmission, function (err, previousFieldValues) { | ||
if (err) return callback(err); | ||
getFieldValidationStatus(submittedField, fieldDef, previousFieldValues, function (err, fieldRes) { | ||
if (err) return callback(err); | ||
if (!fieldRes.valid) { | ||
res.validation.valid = false; // indicate invalid form if any fields invalid | ||
res.validation[fieldID] = fieldRes; // add invalid field info to validate form result | ||
} | ||
if (!fieldRes.valid) { | ||
res.validation.valid = false; // indicate invalid form if any fields invalid | ||
res.validation[fieldID] = fieldRes; // add invalid field info to validate form result | ||
} | ||
return callback(); | ||
return callback(); | ||
}); | ||
}); | ||
}, function (err) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
return cb(undefined, res); | ||
}); | ||
}, function(err) { | ||
if( err ) { | ||
return cb(err); | ||
} | ||
return cb(undefined, res); | ||
}); | ||
} | ||
} | ||
function checkIfRequiredFieldsNotSubmitted(res, cb) { | ||
async.each(Object.keys(submissionRequiredFieldsMap), function (requiredFieldId, cb) { | ||
var resField = {}; | ||
if (!submissionRequiredFieldsMap[requiredFieldId].submitted) { | ||
isFieldVisible(requiredFieldId, true, function (err, visible) { | ||
if (err) return cb(err); | ||
if (visible) { // we only care about required fields if they are visible | ||
resField.fieldId = requiredFieldId; | ||
resField.valid = false; | ||
resField.fieldErrorMessage = ["Required Field Not Submitted"]; | ||
res.validation[requiredFieldId] = resField; | ||
res.validation.valid = false; | ||
} | ||
function checkIfRequiredFieldsNotSubmitted(res, cb) { | ||
async.each(Object.keys(submissionRequiredFieldsMap), function (requiredFieldId, cb) { | ||
var resField = {}; | ||
if (!submissionRequiredFieldsMap[requiredFieldId].submitted) { | ||
isFieldVisible(requiredFieldId, true, function (err, visible) { | ||
if (err) return cb(err); | ||
if (visible) { // we only care about required fields if they are visible | ||
resField.fieldId = requiredFieldId; | ||
resField.valid = false; | ||
resField.fieldErrorMessage = ["Required Field Not Submitted"]; | ||
res.validation[requiredFieldId] = resField; | ||
res.validation.valid = false; | ||
} | ||
return cb(); | ||
}); | ||
} else { // was included in submission | ||
return cb(); | ||
}); | ||
} else { // was included in submission | ||
return cb(); | ||
} | ||
}, function (err) { | ||
if (err) return cb(err); | ||
return cb(undefined, res); | ||
}); | ||
} | ||
} | ||
}, function (err) { | ||
if (err) return cb(err); | ||
return cb(undefined, res); | ||
}); | ||
} | ||
/* | ||
* validate only field values on validation (no rules, no repeat checking) | ||
* res: | ||
* "validation":{ | ||
* "fieldId":{ | ||
* "fieldId":"", | ||
* "valid":true, | ||
* "errorMessages":[ | ||
* "length should be 3 to 5", | ||
* "should not contain dammit" | ||
* ] | ||
* } | ||
* } | ||
*/ | ||
function validateField(fieldId, submission, cb) { | ||
init(function(err){ | ||
if (err) return cb(err); | ||
initSubmission(submission, function (err) { | ||
/* | ||
* validate only field values on validation (no rules, no repeat checking) | ||
* res: | ||
* "validation":{ | ||
* "fieldId":{ | ||
* "fieldId":"", | ||
* "valid":true, | ||
* "errorMessages":[ | ||
* "length should be 3 to 5", | ||
* "should not contain dammit" | ||
* ] | ||
* } | ||
* } | ||
*/ | ||
function validateField(fieldId, submission, cb) { | ||
init(function (err) { | ||
if (err) return cb(err); | ||
var submissionField = submissionFieldsMap[fieldId]; | ||
var fieldDef = fieldMap[fieldId]; | ||
getFieldValidationStatus(submissionField, fieldDef, null, function (err, res) { | ||
initSubmission(submission, function (err) { | ||
if (err) return cb(err); | ||
var ret = {validation: {}}; | ||
ret.validation[fieldId] = res; | ||
return cb(undefined, ret); | ||
var submissionField = submissionFieldsMap[fieldId]; | ||
var fieldDef = fieldMap[fieldId]; | ||
getFieldValidationStatus(submissionField, fieldDef, null, function (err, res) { | ||
if (err) return cb(err); | ||
var ret = { | ||
validation: {} | ||
}; | ||
ret.validation[fieldId] = res; | ||
return cb(undefined, ret); | ||
}); | ||
}); | ||
}); | ||
}); | ||
} | ||
/* | ||
* validate only single field value (no rules, no repeat checking) | ||
* cb(err, result) | ||
* example of result: | ||
* "validation":{ | ||
* "fieldId":{ | ||
* "fieldId":"", | ||
* "valid":true, | ||
* "errorMessages":[ | ||
* "length should be 3 to 5", | ||
* "should not contain dammit" | ||
* ] | ||
* } | ||
* } | ||
*/ | ||
function validateFieldValue(fieldId, inputValue, valueIndex, cb) { | ||
if ("function" === typeof valueIndex) { | ||
cb = valueIndex; | ||
valueIndex = 0; | ||
} | ||
init(function(err){ | ||
if (err) return cb(err); | ||
var fieldDefinition = fieldMap[fieldId]; | ||
var required = false; | ||
if(fieldDefinition.repeating && | ||
fieldDefinition.fieldOptions && | ||
fieldDefinition.fieldOptions.definition && | ||
fieldDefinition.fieldOptions.definition.minRepeat) { | ||
required = (valueIndex < fieldDefinition.fieldOptions.definition.minRepeat); | ||
} else { | ||
required = fieldDefinition.required; | ||
/* | ||
* validate only single field value (no rules, no repeat checking) | ||
* cb(err, result) | ||
* example of result: | ||
* "validation":{ | ||
* "fieldId":{ | ||
* "fieldId":"", | ||
* "valid":true, | ||
* "errorMessages":[ | ||
* "length should be 3 to 5", | ||
* "should not contain dammit" | ||
* ] | ||
* } | ||
* } | ||
*/ | ||
function validateFieldValue(fieldId, inputValue, valueIndex, cb) { | ||
if ("function" === typeof valueIndex) { | ||
cb = valueIndex; | ||
valueIndex = 0; | ||
} | ||
var validation = (fieldDefinition.fieldOptions && fieldDefinition.fieldOptions.validation) ? fieldDefinition.fieldOptions.validation : undefined; | ||
init(function (err) { | ||
if (err) return cb(err); | ||
var fieldDefinition = fieldMap[fieldId]; | ||
if( validation && false === validation.validateImmediately){ | ||
var ret = {validation: {}}; | ||
ret.validation[fieldId] = {"valid":true}; | ||
return cb(undefined, ret ); | ||
} | ||
if(fieldEmpty(inputValue)) { | ||
if(required) { | ||
return formatResponse("No value specified for required input", cb); | ||
var required = false; | ||
if (fieldDefinition.repeating && | ||
fieldDefinition.fieldOptions && | ||
fieldDefinition.fieldOptions.definition && | ||
fieldDefinition.fieldOptions.definition.minRepeat) { | ||
required = (valueIndex < fieldDefinition.fieldOptions.definition.minRepeat); | ||
} else { | ||
return formatResponse(undefined, cb); // optional field not supplied is valid | ||
required = fieldDefinition.required; | ||
} | ||
} | ||
// not empty need to validate | ||
getClientValidatorFunction(fieldDefinition.type, function (err, validator) { | ||
if (err) return cb(err); | ||
var validation = (fieldDefinition.fieldOptions && fieldDefinition.fieldOptions.validation) ? fieldDefinition.fieldOptions.validation : undefined; | ||
validator(inputValue, fieldDefinition, undefined, function (err) { | ||
var message; | ||
if(err) { | ||
if(err.message) { | ||
message = err.message; | ||
} else { | ||
message = "Unknown error message"; | ||
if (validation && false === validation.validateImmediately) { | ||
var ret = { | ||
validation: {} | ||
}; | ||
ret.validation[fieldId] = { | ||
"valid": true | ||
}; | ||
return cb(undefined, ret); | ||
} | ||
if (fieldEmpty(inputValue)) { | ||
if (required) { | ||
return formatResponse("No value specified for required input", cb); | ||
} else { | ||
return formatResponse(undefined, cb); // optional field not supplied is valid | ||
} | ||
} | ||
// not empty need to validate | ||
getClientValidatorFunction(fieldDefinition.type, function (err, validator) { | ||
if (err) return cb(err); | ||
validator(inputValue, fieldDefinition, undefined, function (err) { | ||
var message; | ||
if (err) { | ||
if (err.message) { | ||
message = err.message; | ||
} else { | ||
message = "Unknown error message"; | ||
} | ||
} | ||
} | ||
formatResponse(message, cb); | ||
formatResponse(message, cb); | ||
}); | ||
}); | ||
}); | ||
}); | ||
function formatResponse(msg, cb) { | ||
var messages = {errorMessages: []}; | ||
if(msg) { | ||
messages.errorMessages.push(msg); | ||
function formatResponse(msg, cb) { | ||
var messages = { | ||
errorMessages: [] | ||
}; | ||
if (msg) { | ||
messages.errorMessages.push(msg); | ||
} | ||
return createValidatorResponse(fieldId, messages, function (err, res) { | ||
if (err) return cb(err); | ||
var ret = { | ||
validation: {} | ||
}; | ||
ret.validation[fieldId] = res; | ||
return cb(undefined, ret); | ||
}); | ||
} | ||
return createValidatorResponse(fieldId, messages, function (err, res) { | ||
if (err) return cb(err); | ||
var ret = {validation: {}}; | ||
ret.validation[fieldId] = res; | ||
return cb(undefined, ret); | ||
}); | ||
} | ||
} | ||
function createValidatorResponse(fieldId, messages, cb) { | ||
// intentionally not checking err here, used further down to get validation errors | ||
var res = {}; | ||
res.fieldId = fieldId; | ||
res.errorMessages = messages.errorMessages || []; | ||
res.fieldErrorMessage = messages.fieldErrorMessage || []; | ||
async.some(res.errorMessages, function (item, cb) { | ||
return cb(item !== null); | ||
}, function (someErrors) { | ||
res.valid = !someErrors && (res.fieldErrorMessage.length < 1); | ||
function createValidatorResponse(fieldId, messages, cb) { | ||
// intentionally not checking err here, used further down to get validation errors | ||
var res = {}; | ||
res.fieldId = fieldId; | ||
res.errorMessages = messages.errorMessages || []; | ||
res.fieldErrorMessage = messages.fieldErrorMessage || []; | ||
async.some(res.errorMessages, function (item, cb) { | ||
return cb(item !== null); | ||
}, function (someErrors) { | ||
res.valid = !someErrors && (res.fieldErrorMessage.length < 1); | ||
return cb(undefined, res); | ||
}); | ||
} | ||
return cb(undefined, res); | ||
}); | ||
} | ||
function getFieldValidationStatus(submittedField, fieldDef, previousFieldValues, cb) { | ||
validateFieldInternal(submittedField, fieldDef, previousFieldValues, function (err, messages) { | ||
if(err) return cb(err); | ||
createValidatorResponse(submittedField.fieldId, messages, cb); | ||
}); | ||
} | ||
function getMapFunction(key, map, cb) { | ||
var validator = map[key]; | ||
if (!validator) { | ||
return cb(new Error("Invalid Field Type " + key)); | ||
function getFieldValidationStatus(submittedField, fieldDef, previousFieldValues, cb) { | ||
validateFieldInternal(submittedField, fieldDef, previousFieldValues, function (err, messages) { | ||
if (err) return cb(err); | ||
createValidatorResponse(submittedField.fieldId, messages, cb); | ||
}); | ||
} | ||
return cb(undefined, validator); | ||
} | ||
function getMapFunction(key, map, cb) { | ||
var validator = map[key]; | ||
if (!validator) { | ||
return cb(new Error("Invalid Field Type " + key)); | ||
} | ||
function getValidatorFunction(fieldType, cb) { | ||
return getMapFunction(fieldType, validatorsMap, cb); | ||
} | ||
return cb(undefined, validator); | ||
} | ||
function getClientValidatorFunction(fieldType, cb) { | ||
return getMapFunction(fieldType, validatorsClientMap, cb); | ||
} | ||
function getValidatorFunction(fieldType, cb) { | ||
return getMapFunction(fieldType, validatorsMap, cb); | ||
} | ||
function fieldEmpty(fieldValue) { | ||
return ('undefined' === typeof fieldValue || null === fieldValue || "" === fieldValue); // empty string also regarded as not specified | ||
} | ||
function getClientValidatorFunction(fieldType, cb) { | ||
return getMapFunction(fieldType, validatorsClientMap, cb); | ||
} | ||
function validateFieldInternal(submittedField, fieldDef, previousFieldValues, cb) { | ||
if ("function" === typeof previousFieldValues) { | ||
cb = previousFieldValues; | ||
previousFieldValues = null; | ||
function fieldEmpty(fieldValue) { | ||
return ('undefined' === typeof fieldValue || null === fieldValue || "" === fieldValue); // empty string also regarded as not specified | ||
} | ||
countSubmittedValues(submittedField, function(err, numSubmittedValues) { | ||
if(err) return cb(err); | ||
async.series({ | ||
valuesSubmitted: | ||
async.apply(checkValueSubmitted, submittedField, fieldDef), | ||
repeats: | ||
async.apply(checkRepeat, numSubmittedValues, fieldDef), | ||
values: | ||
async.apply(checkValues, submittedField, fieldDef, previousFieldValues) | ||
}, function (err, results) { | ||
if(err) return cb(err); | ||
function validateFieldInternal(submittedField, fieldDef, previousFieldValues, cb) { | ||
if ("function" === typeof previousFieldValues) { | ||
cb = previousFieldValues; | ||
previousFieldValues = null; | ||
} | ||
var fieldErrorMessages = []; | ||
if(results.valuesSubmitted) { | ||
fieldErrorMessages.push(results.valuesSubmitted); | ||
} | ||
if(results.repeats) { | ||
fieldErrorMessages.push(results.repeats); | ||
} | ||
return cb(undefined, {fieldErrorMessage: fieldErrorMessages, errorMessages: results.values}); | ||
countSubmittedValues(submittedField, function (err, numSubmittedValues) { | ||
if (err) return cb(err); | ||
async.series({ | ||
valuesSubmitted: async.apply(checkValueSubmitted, submittedField, fieldDef), | ||
repeats: async.apply(checkRepeat, numSubmittedValues, fieldDef), | ||
values: async.apply(checkValues, submittedField, fieldDef, previousFieldValues) | ||
}, function (err, results) { | ||
if (err) return cb(err); | ||
var fieldErrorMessages = []; | ||
if (results.valuesSubmitted) { | ||
fieldErrorMessages.push(results.valuesSubmitted); | ||
} | ||
if (results.repeats) { | ||
fieldErrorMessages.push(results.repeats); | ||
} | ||
return cb(undefined, { | ||
fieldErrorMessage: fieldErrorMessages, | ||
errorMessages: results.values | ||
}); | ||
}); | ||
}); | ||
}); | ||
return; // just functions below this | ||
return; // just functions below this | ||
function checkValueSubmitted(submittedField, fieldDefinition, cb) { | ||
if(! fieldDefinition.required) return cb(undefined, null); | ||
var valueSubmitted = submittedField && submittedField.fieldValues && (submittedField.fieldValues.length > 0); | ||
if (!valueSubmitted) { | ||
return cb(undefined, "No value submitted for field " + fieldDefinition.name); | ||
function checkValueSubmitted(submittedField, fieldDefinition, cb) { | ||
if (!fieldDefinition.required) return cb(undefined, null); | ||
var valueSubmitted = submittedField && submittedField.fieldValues && (submittedField.fieldValues.length > 0); | ||
if (!valueSubmitted) { | ||
return cb(undefined, "No value submitted for field " + fieldDefinition.name); | ||
} | ||
return cb(undefined, null); | ||
} | ||
return cb(undefined, null); | ||
} | ||
function countSubmittedValues(submittedField, cb) { | ||
var numSubmittedValues = 0; | ||
if(submittedField && submittedField.fieldValues && submittedField.fieldValues.length > 0) { | ||
for(var i=0; i<submittedField.fieldValues.length; i += 1) { | ||
if(submittedField.fieldValues[i]) { | ||
numSubmittedValues += 1; | ||
function countSubmittedValues(submittedField, cb) { | ||
var numSubmittedValues = 0; | ||
if (submittedField && submittedField.fieldValues && submittedField.fieldValues.length > 0) { | ||
for (var i = 0; i < submittedField.fieldValues.length; i += 1) { | ||
if (submittedField.fieldValues[i]) { | ||
numSubmittedValues += 1; | ||
} | ||
} | ||
} | ||
return cb(undefined, numSubmittedValues); | ||
} | ||
return cb(undefined, numSubmittedValues); | ||
} | ||
function checkRepeat(numSubmittedValues, fieldDefinition, cb) { | ||
function checkRepeat(numSubmittedValues, fieldDefinition, cb) { | ||
if(fieldDefinition.repeating && fieldDefinition.fieldOptions && fieldDefinition.fieldOptions.definition){ | ||
if(fieldDefinition.fieldOptions.definition.minRepeat){ | ||
if(numSubmittedValues < fieldDefinition.fieldOptions.definition.minRepeat){ | ||
return cb(undefined, "Expected min of " + fieldDefinition.fieldOptions.definition.minRepeat + " values for field " + fieldDefinition.name + " but got " + numSubmittedValues); | ||
if (fieldDefinition.repeating && fieldDefinition.fieldOptions && fieldDefinition.fieldOptions.definition) { | ||
if (fieldDefinition.fieldOptions.definition.minRepeat) { | ||
if (numSubmittedValues < fieldDefinition.fieldOptions.definition.minRepeat) { | ||
return cb(undefined, "Expected min of " + fieldDefinition.fieldOptions.definition.minRepeat + " values for field " + fieldDefinition.name + " but got " + numSubmittedValues); | ||
} | ||
} | ||
} | ||
if (fieldDefinition.fieldOptions.definition.maxRepeat){ | ||
if(numSubmittedValues > fieldDefinition.fieldOptions.definition.maxRepeat){ | ||
return cb(undefined, "Expected max of " + fieldDefinition.fieldOptions.definition.maxRepeat + " values for field " + fieldDefinition.name + " but got " + numSubmittedValues); | ||
if (fieldDefinition.fieldOptions.definition.maxRepeat) { | ||
if (numSubmittedValues > fieldDefinition.fieldOptions.definition.maxRepeat) { | ||
return cb(undefined, "Expected max of " + fieldDefinition.fieldOptions.definition.maxRepeat + " values for field " + fieldDefinition.name + " but got " + numSubmittedValues); | ||
} | ||
} | ||
} else { | ||
if (numSubmittedValues > 1) { | ||
return cb(undefined, "Should not have multiple values for non-repeating field"); | ||
} | ||
} | ||
} else { | ||
if(numSubmittedValues > 1) { | ||
return cb(undefined, "Should not have multiple values for non-repeating field"); | ||
} | ||
return cb(undefined, null); | ||
} | ||
return cb(undefined, null); | ||
} | ||
function checkValues(submittedField, fieldDefinition, previousFieldValues, cb) { | ||
getValidatorFunction(fieldDefinition.type, function (err, validator) { | ||
if (err) return cb(err); | ||
async.map(submittedField.fieldValues, function (fieldValue, cb) { | ||
if (fieldEmpty(fieldValue)) { | ||
return cb(undefined, null); | ||
} else { | ||
validator(fieldValue, fieldDefinition, previousFieldValues, function (validationError) { | ||
var errorMessage; | ||
if (validationError) { | ||
errorMessage = validationError.message || "Error during validation of field"; | ||
} else { | ||
errorMessage = null; | ||
} | ||
function checkValues(submittedField, fieldDefinition, previousFieldValues, cb) { | ||
getValidatorFunction(fieldDefinition.type, function (err, validator) { | ||
if (err) return cb(err); | ||
async.map(submittedField.fieldValues, function(fieldValue, cb){ | ||
if(fieldEmpty(fieldValue)) { | ||
return cb(undefined, null); | ||
} else { | ||
validator(fieldValue, fieldDefinition, previousFieldValues, function(validationError) { | ||
var errorMessage; | ||
if(validationError) { | ||
errorMessage = validationError.message || "Error during validation of field"; | ||
} else { | ||
errorMessage = null; | ||
} | ||
if (submissionRequiredFieldsMap[fieldDefinition._id]) { // set to true if at least one value | ||
submissionRequiredFieldsMap[fieldDefinition._id].submitted = true; | ||
} | ||
if (submissionRequiredFieldsMap[fieldDefinition._id]) { // set to true if at least one value | ||
submissionRequiredFieldsMap[fieldDefinition._id].submitted = true; | ||
} | ||
return cb(undefined, errorMessage); | ||
}); | ||
} | ||
}, function (err, results) { | ||
if (err) return cb(err); | ||
return cb(undefined, errorMessage); | ||
}); | ||
} | ||
}, function (err, results) { | ||
if (err) return cb(err); | ||
return cb(undefined, results); | ||
}); | ||
}); | ||
} | ||
return cb(undefined, results); | ||
}); | ||
}); | ||
} | ||
} | ||
function convertSimpleFormatToRegex(field_format_string) { | ||
var regex = "^"; | ||
var C = "c".charCodeAt(0); | ||
var N = "n".charCodeAt(0); | ||
function convertSimpleFormatToRegex(field_format_string) { | ||
var regex = "^"; | ||
var C = "c".charCodeAt(0); | ||
var N = "n".charCodeAt(0); | ||
var i; | ||
var ch; | ||
var match; | ||
var len = field_format_string.length; | ||
for (i = 0; i < len; i += 1) { | ||
ch = field_format_string.charCodeAt(i); | ||
switch (ch) { | ||
case C: | ||
match = "[a-zA-Z0-9]"; | ||
break; | ||
case N: | ||
match = "[0-9]"; | ||
break; | ||
default: | ||
var num = ch.toString(16).toUpperCase(); | ||
match = "\\u" + ("0000" + num).substr(-4); | ||
break; | ||
var i; | ||
var ch; | ||
var match; | ||
var len = field_format_string.length; | ||
for (i = 0; i < len; i += 1) { | ||
ch = field_format_string.charCodeAt(i); | ||
switch (ch) { | ||
case C: | ||
match = "[a-zA-Z0-9]"; | ||
break; | ||
case N: | ||
match = "[0-9]"; | ||
break; | ||
default: | ||
var num = ch.toString(16).toUpperCase(); | ||
match = "\\u" + ("0000" + num).substr(-4); | ||
break; | ||
} | ||
regex += match; | ||
} | ||
regex += match; | ||
return regex + "$"; | ||
} | ||
return regex + "$"; | ||
} | ||
function validFormatRegex(fieldValue, field_format_string) { | ||
var pattern = new RegExp(field_format_string); | ||
return pattern.test(fieldValue); | ||
} | ||
function validFormat(fieldValue, field_format_mode, field_format_string) { | ||
var regex; | ||
if ("simple" === field_format_mode) { | ||
regex = convertSimpleFormatToRegex(field_format_string); | ||
} else if ("regex" === field_format_mode) { | ||
regex = field_format_string; | ||
} else { // should never be anything else, but if it is then default to simple format | ||
regex = convertSimpleFormatToRegex(field_format_string); | ||
function validFormatRegex(fieldValue, field_format_string) { | ||
var pattern = new RegExp(field_format_string); | ||
return pattern.test(fieldValue); | ||
} | ||
return validFormatRegex(fieldValue, regex); | ||
} | ||
function validFormat(fieldValue, field_format_mode, field_format_string) { | ||
var regex; | ||
if ("simple" === field_format_mode) { | ||
regex = convertSimpleFormatToRegex(field_format_string); | ||
} else if ("regex" === field_format_mode) { | ||
regex = field_format_string; | ||
} else { // should never be anything else, but if it is then default to simple format | ||
regex = convertSimpleFormatToRegex(field_format_string); | ||
} | ||
function validatorString (fieldValue, fieldDefinition, previousFieldValues, cb) { | ||
if(typeof fieldValue !== "string"){ | ||
return cb(new Error("Expected string but got " + typeof(fieldValue))); | ||
return validFormatRegex(fieldValue, regex); | ||
} | ||
var validation = {}; | ||
if (fieldDefinition && fieldDefinition.fieldOptions && fieldDefinition.fieldOptions.validation) { | ||
validation = fieldDefinition.fieldOptions.validation; | ||
} | ||
function validatorString(fieldValue, fieldDefinition, previousFieldValues, cb) { | ||
if (typeof fieldValue !== "string") { | ||
return cb(new Error("Expected string but got " + typeof(fieldValue))); | ||
} | ||
var field_format_mode = validation.field_format_mode || ""; | ||
field_format_mode = field_format_mode.trim(); | ||
var field_format_string = validation.field_format_string || ""; | ||
field_format_string = field_format_string.trim(); | ||
var validation = {}; | ||
if (fieldDefinition && fieldDefinition.fieldOptions && fieldDefinition.fieldOptions.validation) { | ||
validation = fieldDefinition.fieldOptions.validation; | ||
} | ||
if (field_format_string && (field_format_string.length > 0) && field_format_mode && (field_format_mode.length > 0)) { | ||
if(!validFormat(fieldValue, field_format_mode, field_format_string)) { | ||
return cb(new Error("field value in incorrect format, expected format: " + field_format_string + " but submission value is: " + fieldValue)); | ||
var field_format_mode = validation.field_format_mode || ""; | ||
field_format_mode = field_format_mode.trim(); | ||
var field_format_string = validation.field_format_string || ""; | ||
field_format_string = field_format_string.trim(); | ||
if (field_format_string && (field_format_string.length > 0) && field_format_mode && (field_format_mode.length > 0)) { | ||
if (!validFormat(fieldValue, field_format_mode, field_format_string)) { | ||
return cb(new Error("field value in incorrect format, expected format: " + field_format_string + " but submission value is: " + fieldValue)); | ||
} | ||
} | ||
} | ||
if(fieldDefinition.fieldOptions && fieldDefinition.fieldOptions.validation && fieldDefinition.fieldOptions.validation.min){ | ||
if(fieldValue.length < fieldDefinition.fieldOptions.validation.min){ | ||
return cb(new Error("Expected minimum string length of " + fieldDefinition.fieldOptions.validation.min + " but submission is " + fieldValue.length + ". Submitted val: " + fieldValue)); | ||
if (fieldDefinition.fieldOptions && fieldDefinition.fieldOptions.validation && fieldDefinition.fieldOptions.validation.min) { | ||
if (fieldValue.length < fieldDefinition.fieldOptions.validation.min) { | ||
return cb(new Error("Expected minimum string length of " + fieldDefinition.fieldOptions.validation.min + " but submission is " + fieldValue.length + ". Submitted val: " + fieldValue)); | ||
} | ||
} | ||
} | ||
if(fieldDefinition.fieldOptions && fieldDefinition.fieldOptions.validation && fieldDefinition.fieldOptions.validation.max){ | ||
if(fieldValue.length > fieldDefinition.fieldOptions.validation.max){ | ||
return cb(new Error("Expected maximum string length of " + fieldDefinition.fieldOptions.validation.max + " but submission is " + fieldValue.length + ". Submitted val: " + fieldValue)); | ||
if (fieldDefinition.fieldOptions && fieldDefinition.fieldOptions.validation && fieldDefinition.fieldOptions.validation.max) { | ||
if (fieldValue.length > fieldDefinition.fieldOptions.validation.max) { | ||
return cb(new Error("Expected maximum string length of " + fieldDefinition.fieldOptions.validation.max + " but submission is " + fieldValue.length + ". Submitted val: " + fieldValue)); | ||
} | ||
} | ||
} | ||
return cb(); | ||
} | ||
function validatorNumericString (fieldValue, fieldDefinition, previousFieldValues, cb) { | ||
var testVal = (fieldValue - 0); // coerce to number (or NaN) | ||
var numeric = (testVal == fieldValue); // testVal co-erced to numeric above, so numeric comparison and NaN != NaN | ||
if(!numeric) { | ||
return cb(new Error("Expected numeric but got: " + fieldValue)); | ||
return cb(); | ||
} | ||
return validatorNumber(testVal, fieldDefinition, previousFieldValues, cb); | ||
} | ||
function validatorNumericString(fieldValue, fieldDefinition, previousFieldValues, cb) { | ||
var testVal = (fieldValue - 0); // coerce to number (or NaN) | ||
var numeric = (testVal == fieldValue); // testVal co-erced to numeric above, so numeric comparison and NaN != NaN | ||
if (!numeric) { | ||
return cb(new Error("Expected numeric but got: " + fieldValue)); | ||
} | ||
function validatorNumber (fieldValue, fieldDefinition, previousFieldValues, cb) { | ||
if(typeof fieldValue !== "number"){ | ||
return cb(new Error("Expected number but got " + typeof(fieldValue))); | ||
return validatorNumber(testVal, fieldDefinition, previousFieldValues, cb); | ||
} | ||
if(fieldDefinition.fieldOptions && fieldDefinition.fieldOptions.validation && fieldDefinition.fieldOptions.validation.min){ | ||
if(fieldValue < fieldDefinition.fieldOptions.validation.min){ | ||
return cb(new Error("Expected minimum Number " + fieldDefinition.fieldOptions.validation.min + " but submission is " + fieldValue + ". Submitted number: " + fieldValue)); | ||
function validatorNumber(fieldValue, fieldDefinition, previousFieldValues, cb) { | ||
if (typeof fieldValue !== "number") { | ||
return cb(new Error("Expected number but got " + typeof(fieldValue))); | ||
} | ||
} | ||
if (fieldDefinition.fieldOptions.validation.max){ | ||
if(fieldValue > fieldDefinition.fieldOptions.validation.max){ | ||
return cb(new Error("Expected maximum Number " + fieldDefinition.fieldOptions.validation.max + " but submission is " + fieldValue + ". Submitted number: " + fieldValue)); | ||
if (fieldDefinition.fieldOptions && fieldDefinition.fieldOptions.validation && fieldDefinition.fieldOptions.validation.min) { | ||
if (fieldValue < fieldDefinition.fieldOptions.validation.min) { | ||
return cb(new Error("Expected minimum Number " + fieldDefinition.fieldOptions.validation.min + " but submission is " + fieldValue + ". Submitted number: " + fieldValue)); | ||
} | ||
} | ||
} | ||
return cb(); | ||
} | ||
if (fieldDefinition.fieldOptions.validation.max) { | ||
if (fieldValue > fieldDefinition.fieldOptions.validation.max) { | ||
return cb(new Error("Expected maximum Number " + fieldDefinition.fieldOptions.validation.max + " but submission is " + fieldValue + ". Submitted number: " + fieldValue)); | ||
} | ||
} | ||
function validatorEmail (fieldValue, fieldDefinition, previousFieldValues, cb) { | ||
if(typeof(fieldValue) !== "string"){ | ||
return cb(new Error("Expected string but got " + typeof(fieldValue))); | ||
} | ||
if(fieldValue.match(/[-0-9a-zA-Z.+_]+@[-0-9a-zA-Z.+_]+\.[a-zA-Z]{2,4}/g) === null){ | ||
return cb(new Error("Invalid email address format: " + fieldValue)); | ||
} else { | ||
return cb(); | ||
} | ||
} | ||
function validatorDropDown (fieldValue, fieldDefinition, previousFieldValues, cb) { | ||
if(typeof(fieldValue) !== "string"){ | ||
return cb(new Error("Expected submission to be string but got " + typeof(fieldValue))); | ||
} | ||
function validatorEmail(fieldValue, fieldDefinition, previousFieldValues, cb) { | ||
if (typeof(fieldValue) !== "string") { | ||
return cb(new Error("Expected string but got " + typeof(fieldValue))); | ||
} | ||
//Check value exists in the field definition | ||
if(!fieldDefinition.fieldOptions.definition.options){ | ||
return cb(new Error("No options exist for field " + fieldDefinition.name)); | ||
} | ||
async.some(fieldDefinition.fieldOptions.definition.options, function (dropdownOption, cb) { | ||
return cb(dropdownOption.label === fieldValue); | ||
}, function (found) { | ||
if (!found) { | ||
return cb(new Error("Invalid option specified: " + fieldValue)); | ||
if (fieldValue.match(/[-0-9a-zA-Z.+_]+@[-0-9a-zA-Z.+_]+\.[a-zA-Z]{2,4}/g) === null) { | ||
return cb(new Error("Invalid email address format: " + fieldValue)); | ||
} else { | ||
return cb(); | ||
} | ||
}); | ||
} | ||
function validatorCheckboxes (fieldValue, fieldDefinition, previousFieldValues, cb) { | ||
var minVal; | ||
if (fieldDefinition && fieldDefinition.fieldOptions && fieldDefinition.fieldOptions.validation) { | ||
minVal = fieldDefinition.fieldOptions.validation.min; | ||
} | ||
var maxVal; | ||
if (fieldDefinition && fieldDefinition.fieldOptions && fieldDefinition.fieldOptions.validation) { | ||
maxVal = fieldDefinition.fieldOptions.validation.max; | ||
} | ||
if (minVal) { | ||
if(fieldValue.selections === null || fieldValue.selections === undefined || fieldValue.selections.length < minVal){ | ||
var len; | ||
if(fieldValue.selections) { | ||
len = fieldValue.selections.length; | ||
} | ||
return cb(new Error("Expected a minimum number of selections " + minVal + " but got " + len)); | ||
function validatorDropDown(fieldValue, fieldDefinition, previousFieldValues, cb) { | ||
if (typeof(fieldValue) !== "string") { | ||
return cb(new Error("Expected submission to be string but got " + typeof(fieldValue))); | ||
} | ||
} | ||
if(maxVal){ | ||
if(fieldValue.selections){ | ||
if(fieldValue.selections.length > maxVal){ | ||
return cb(new Error("Expected a maximum number of selections " + maxVal + " but got " + fieldValue.selections.length)); | ||
//Check value exists in the field definition | ||
if (!fieldDefinition.fieldOptions.definition.options) { | ||
return cb(new Error("No options exist for field " + fieldDefinition.name)); | ||
} | ||
async.some(fieldDefinition.fieldOptions.definition.options, function (dropdownOption, cb) { | ||
return cb(dropdownOption.label === fieldValue); | ||
}, function (found) { | ||
if (!found) { | ||
return cb(new Error("Invalid option specified: " + fieldValue)); | ||
} else { | ||
return cb(); | ||
} | ||
} | ||
}); | ||
} | ||
var optionsInCheckbox = []; | ||
function validatorCheckboxes(fieldValue, fieldDefinition, previousFieldValues, cb) { | ||
var minVal; | ||
if (fieldDefinition && fieldDefinition.fieldOptions && fieldDefinition.fieldOptions.validation) { | ||
minVal = fieldDefinition.fieldOptions.validation.min; | ||
} | ||
var maxVal; | ||
if (fieldDefinition && fieldDefinition.fieldOptions && fieldDefinition.fieldOptions.validation) { | ||
maxVal = fieldDefinition.fieldOptions.validation.max; | ||
} | ||
async.eachSeries(fieldDefinition.fieldOptions.definition.options, function(choice, cb){ | ||
for(var choiceName in choice){ | ||
optionsInCheckbox.push(choice[choiceName]); | ||
if (minVal) { | ||
if (fieldValue.selections === null || fieldValue.selections === undefined || fieldValue.selections.length < minVal) { | ||
var len; | ||
if (fieldValue.selections) { | ||
len = fieldValue.selections.length; | ||
} | ||
return cb(new Error("Expected a minimum number of selections " + minVal + " but got " + len)); | ||
} | ||
} | ||
return cb(); | ||
}, function(err){ | ||
async.eachSeries(fieldValue.selections, function(selection, cb){ | ||
if(typeof(selection) !== "string"){ | ||
return cb(new Error("Expected checkbox submission to be string but got " + typeof(selection))); | ||
if (maxVal) { | ||
if (fieldValue.selections) { | ||
if (fieldValue.selections.length > maxVal) { | ||
return cb(new Error("Expected a maximum number of selections " + maxVal + " but got " + fieldValue.selections.length)); | ||
} | ||
} | ||
} | ||
if(optionsInCheckbox.indexOf(selection) === -1){ | ||
return cb(new Error("Checkbox Option " + selection + " does not exist in the field.")); | ||
var optionsInCheckbox = []; | ||
async.eachSeries(fieldDefinition.fieldOptions.definition.options, function (choice, cb) { | ||
for (var choiceName in choice) { | ||
optionsInCheckbox.push(choice[choiceName]); | ||
} | ||
return cb(); | ||
}, cb); | ||
}); | ||
} | ||
}, function (err) { | ||
async.eachSeries(fieldValue.selections, function (selection, cb) { | ||
if (typeof(selection) !== "string") { | ||
return cb(new Error("Expected checkbox submission to be string but got " + typeof(selection))); | ||
} | ||
function validatorLocationMap (fieldValue, fieldDefinition, previousFieldValues, cb) { | ||
if(fieldValue.lat && fieldValue["long"]) { | ||
if(isNaN(parseFloat(fieldValue.lat)) || isNaN(parseFloat(fieldValue["long"]))) { | ||
return cb(new Error("Invalid latitude and longitude values")); | ||
} else { | ||
return cb(); | ||
} | ||
} else { | ||
return cb(new Error("Invalid object for locationMap submission")); | ||
if (optionsInCheckbox.indexOf(selection) === -1) { | ||
return cb(new Error("Checkbox Option " + selection + " does not exist in the field.")); | ||
} | ||
return cb(); | ||
}, cb); | ||
}); | ||
} | ||
} | ||
function validatorLocation (fieldValue, fieldDefinition, previousFieldValues, cb) { | ||
if(fieldDefinition.fieldOptions.definition.locationUnit === "latlong") { | ||
if(fieldValue.lat && fieldValue["long"]){ | ||
if(isNaN(parseFloat(fieldValue.lat)) || isNaN(parseFloat(fieldValue["long"]))){ | ||
function validatorLocationMap(fieldValue, fieldDefinition, previousFieldValues, cb) { | ||
if (fieldValue.lat && fieldValue["long"]) { | ||
if (isNaN(parseFloat(fieldValue.lat)) || isNaN(parseFloat(fieldValue["long"]))) { | ||
return cb(new Error("Invalid latitude and longitude values")); | ||
@@ -795,529 +800,593 @@ } else { | ||
} else { | ||
return cb(new Error("Invalid object for latitude longitude submission")); | ||
return cb(new Error("Invalid object for locationMap submission")); | ||
} | ||
} else { | ||
if(fieldValue.zone && fieldValue.eastings && fieldValue.northings){ | ||
//Zone must be 3 characters, eastings 6 and northings 9 | ||
return validateNorthingsEastings(fieldValue, cb); | ||
} else { | ||
return cb(new Error("Invalid object for northings easting submission. Zone, Eastings and Northings elemets are required")); | ||
} | ||
} | ||
function validateNorthingsEastings(fieldValue, cb){ | ||
if(typeof(fieldValue.zone) !== "string" || fieldValue.zone.length === 0){ | ||
return cb(new Error("Invalid zone definition for northings and eastings location. " + fieldValue.zone)); | ||
} | ||
var east = parseInt(fieldValue.eastings,10); | ||
if(isNaN(east)){ | ||
return cb(new Error("Invalid eastings definition for northings and eastings location. " + fieldValue.eastings)); | ||
function validatorLocation(fieldValue, fieldDefinition, previousFieldValues, cb) { | ||
if (fieldDefinition.fieldOptions.definition.locationUnit === "latlong") { | ||
if (fieldValue.lat && fieldValue["long"]) { | ||
if (isNaN(parseFloat(fieldValue.lat)) || isNaN(parseFloat(fieldValue["long"]))) { | ||
return cb(new Error("Invalid latitude and longitude values")); | ||
} else { | ||
return cb(); | ||
} | ||
} else { | ||
return cb(new Error("Invalid object for latitude longitude submission")); | ||
} | ||
} else { | ||
if (fieldValue.zone && fieldValue.eastings && fieldValue.northings) { | ||
//Zone must be 3 characters, eastings 6 and northings 9 | ||
return validateNorthingsEastings(fieldValue, cb); | ||
} else { | ||
return cb(new Error("Invalid object for northings easting submission. Zone, Eastings and Northings elemets are required")); | ||
} | ||
} | ||
var north = parseInt(fieldValue.northings, 10); | ||
if(isNaN(north)){ | ||
return cb(new Error("Invalid northings definition for northings and eastings location. " + fieldValue.northings)); | ||
} | ||
function validateNorthingsEastings(fieldValue, cb) { | ||
if (typeof(fieldValue.zone) !== "string" || fieldValue.zone.length === 0) { | ||
return cb(new Error("Invalid zone definition for northings and eastings location. " + fieldValue.zone)); | ||
} | ||
return cb(); | ||
} | ||
} | ||
var east = parseInt(fieldValue.eastings, 10); | ||
if (isNaN(east)) { | ||
return cb(new Error("Invalid eastings definition for northings and eastings location. " + fieldValue.eastings)); | ||
} | ||
function validatorAnyFile(fieldValue, fieldDefinition, previousFieldValues, cb) { | ||
// if any of the following validators return ok, then return ok. | ||
validatorBase64(fieldValue, fieldDefinition, previousFieldValues, function (err) { | ||
if(!err) { | ||
var north = parseInt(fieldValue.northings, 10); | ||
if (isNaN(north)) { | ||
return cb(new Error("Invalid northings definition for northings and eastings location. " + fieldValue.northings)); | ||
} | ||
return cb(); | ||
} | ||
validatorFile(fieldValue, fieldDefinition, previousFieldValues, function (err) { | ||
if(!err) { | ||
} | ||
function validatorAnyFile(fieldValue, fieldDefinition, previousFieldValues, cb) { | ||
// if any of the following validators return ok, then return ok. | ||
validatorBase64(fieldValue, fieldDefinition, previousFieldValues, function (err) { | ||
if (!err) { | ||
return cb(); | ||
} | ||
validatorFileObj(fieldValue, fieldDefinition, previousFieldValues, function (err) { | ||
if(!err) { | ||
validatorFile(fieldValue, fieldDefinition, previousFieldValues, function (err) { | ||
if (!err) { | ||
return cb(); | ||
} | ||
return cb(err); | ||
validatorFileObj(fieldValue, fieldDefinition, previousFieldValues, function (err) { | ||
if (!err) { | ||
return cb(); | ||
} | ||
return cb(err); | ||
}); | ||
}); | ||
}); | ||
}); | ||
} | ||
function validatorFile (fieldValue, fieldDefinition, previousFieldValues, cb) { | ||
if(typeof fieldValue !== "object"){ | ||
return cb(new Error("Expected object but got " + typeof(fieldValue))); | ||
} | ||
var keyTypes = [ | ||
{ keyName: "fileName", valueType: "string" }, | ||
{ keyName: "fileSize", valueType: "number" }, | ||
{ keyName: "fileType", valueType: "string" }, | ||
{ keyName: "fileUpdateTime", valueType: "number" }, | ||
{ keyName: "hashName", valueType: "string" } | ||
]; | ||
function checkFileSize(fieldDefinition, fieldValue, sizeKey, cb) { | ||
fieldDefinition = fieldDefinition || {}; | ||
var fieldOptions = fieldDefinition.fieldOptions || {}; | ||
var fieldOptionsDef = fieldOptions.definition || {}; | ||
var fileSizeMax = fieldOptionsDef.file_size || null; //FileSizeMax will be in KB. File size is in bytes | ||
async.each(keyTypes, function (keyType, cb) { | ||
var actualType = typeof fieldValue[keyType.keyName]; | ||
if (actualType !== keyType.valueType) { | ||
return cb(new Error("Expected " + keyType.valueType + " but got " + actualType)); | ||
if (fileSizeMax !== null) { | ||
var fieldValueSize = fieldValue[sizeKey]; | ||
var fieldValueSizeKB = 1; | ||
if (fieldValueSize > 1000) { | ||
fieldValueSizeKB = fieldValueSize / 1000; | ||
} | ||
console.log("Comparing File Size: ", fileSizeMax, fieldValueSize); | ||
if (fieldValueSize > (fileSizeMax * 1000)) { | ||
return cb(new Error("File size is too large. File can be a maximum of " + fileSizeMax + "KB. Size of file selected: " + fieldValueSizeKB + "KB")); | ||
} else { | ||
return cb(); | ||
} | ||
} else { | ||
return cb(); | ||
} | ||
if (keyType.keyName === "fileName" && fieldValue[keyType.keyName].length <=0) { | ||
return cb(new Error("Expected value for " + keyType.keyName)); | ||
} | ||
function validatorFile(fieldValue, fieldDefinition, previousFieldValues, cb) { | ||
if (typeof fieldValue !== "object") { | ||
return cb(new Error("Expected object but got " + typeof(fieldValue))); | ||
} | ||
return cb(); | ||
}, function (err) { | ||
if (err) return cb(err); | ||
var keyTypes = [ | ||
{ | ||
keyName: "fileName", | ||
valueType: "string" | ||
}, | ||
{ | ||
keyName: "fileSize", | ||
valueType: "number" | ||
}, | ||
{ | ||
keyName: "fileType", | ||
valueType: "string" | ||
}, | ||
{ | ||
keyName: "fileUpdateTime", | ||
valueType: "number" | ||
}, | ||
{ | ||
keyName: "hashName", | ||
valueType: "string" | ||
} | ||
]; | ||
if(fieldValue.hashName.indexOf("filePlaceHolder") > -1){ //TODO abstract out to config | ||
async.each(keyTypes, function (keyType, cb) { | ||
var actualType = typeof fieldValue[keyType.keyName]; | ||
if (actualType !== keyType.valueType) { | ||
return cb(new Error("Expected " + keyType.valueType + " but got " + actualType)); | ||
} | ||
if (keyType.keyName === "fileName" && fieldValue[keyType.keyName].length <= 0) { | ||
return cb(new Error("Expected value for " + keyType.keyName)); | ||
} | ||
return cb(); | ||
} else if (previousFieldValues && previousFieldValues.hashName && previousFieldValues.hashName.indexOf(fieldValue.hashName) > -1){ | ||
return cb(); | ||
} else { | ||
return cb(new Error("Invalid file placeholder text" + fieldValue.hashName)); | ||
} | ||
}, function (err) { | ||
if (err) return cb(err); | ||
}); | ||
} | ||
checkFileSize(fieldDefinition, fieldValue, "fileSize", function (err) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
function validatorFileObj (fieldValue, fieldDefinition, previousFieldValues, cb) { | ||
if((typeof File !== "function") || !(fieldValue instanceof File)) { | ||
return cb(new Error("Expected File object but got " + typeof(fieldValue))); | ||
if (fieldValue.hashName.indexOf("filePlaceHolder") > -1) { //TODO abstract out to config | ||
return cb(); | ||
} else if (previousFieldValues && previousFieldValues.hashName && previousFieldValues.hashName.indexOf(fieldValue.hashName) > -1) { | ||
return cb(); | ||
} else { | ||
return cb(new Error("Invalid file placeholder text" + fieldValue.hashName)); | ||
} | ||
}); | ||
}); | ||
} | ||
var keyTypes = [ | ||
{ keyName: "name", valueType: "string" }, | ||
{ keyName: "size", valueType: "number" } | ||
]; | ||
async.each(keyTypes, function (keyType, cb) { | ||
var actualType = typeof fieldValue[keyType.keyName]; | ||
if (actualType !== keyType.valueType) { | ||
return cb(new Error("Expected " + keyType.valueType + " but got " + actualType)); | ||
function validatorFileObj(fieldValue, fieldDefinition, previousFieldValues, cb) { | ||
if ((typeof File !== "function") || !(fieldValue instanceof File)) { | ||
return cb(new Error("Expected File object but got " + typeof(fieldValue))); | ||
} | ||
if (actualType === "string" && fieldValue[keyType.keyName].length <=0) { | ||
return cb(new Error("Expected value for " + keyType.keyName)); | ||
} | ||
if (actualType === "number" && fieldValue[keyType.keyName] <=0) { | ||
return cb(new Error("Expected > 0 value for " + keyType.keyName)); | ||
} | ||
return cb(); | ||
}, function (err) { | ||
if (err) return cb(err); | ||
return cb(); | ||
}); | ||
var keyTypes = [ | ||
{ | ||
keyName: "name", | ||
valueType: "string" | ||
}, | ||
{ | ||
keyName: "size", | ||
valueType: "number" | ||
} | ||
]; | ||
} | ||
async.each(keyTypes, function (keyType, cb) { | ||
var actualType = typeof fieldValue[keyType.keyName]; | ||
if (actualType !== keyType.valueType) { | ||
return cb(new Error("Expected " + keyType.valueType + " but got " + actualType)); | ||
} | ||
if (actualType === "string" && fieldValue[keyType.keyName].length <= 0) { | ||
return cb(new Error("Expected value for " + keyType.keyName)); | ||
} | ||
if (actualType === "number" && fieldValue[keyType.keyName] <= 0) { | ||
return cb(new Error("Expected > 0 value for " + keyType.keyName)); | ||
} | ||
function validatorBase64 (fieldValue, fieldDefinition, previousFieldValues, cb) { | ||
if(typeof fieldValue !== "string"){ | ||
return cb(new Error("Expected base64 string but got " + typeof(fieldValue))); | ||
} | ||
return cb(); | ||
}, function (err) { | ||
if (err) return cb(err); | ||
if(fieldValue.length <= 0){ | ||
return cb(new Error("Expected base64 string but was empty")); | ||
checkFileSize(fieldDefinition, fieldValue, "size", function (err) { | ||
if (err) { | ||
return cb(err); | ||
} | ||
return cb(); | ||
}); | ||
}); | ||
} | ||
return cb(); | ||
} | ||
function validatorBase64(fieldValue, fieldDefinition, previousFieldValues, cb) { | ||
if (typeof fieldValue !== "string") { | ||
return cb(new Error("Expected base64 string but got " + typeof(fieldValue))); | ||
} | ||
function validatorDateTime (fieldValue, fieldDefinition, previousFieldValues, cb) { | ||
var testDate; | ||
if (fieldValue.length <= 0) { | ||
return cb(new Error("Expected base64 string but was empty")); | ||
} | ||
if(typeof(fieldValue) !== "string"){ | ||
return cb(new Error("Expected string but got " + typeof(fieldValue))); | ||
return cb(); | ||
} | ||
switch (fieldDefinition.fieldOptions.definition.datetimeUnit) | ||
{ | ||
case FIELD_TYPE_DATETIME_DATETIMEUNIT_DATEONLY: | ||
try{ | ||
testDate = new Date(fieldValue); | ||
valid = (testDate.toString() !== "Invalid Date"); | ||
}catch(e){ | ||
valid = false; | ||
} | ||
if (valid) { | ||
return cb(); | ||
} else { | ||
return cb(new Error("Invalid date value " + fieldValue)); | ||
} | ||
break; | ||
case FIELD_TYPE_DATETIME_DATETIMEUNIT_TIMEONLY: | ||
var parts = fieldValue.split(':'); | ||
valid = (parts.length === 2) || (parts.length === 3); | ||
if (valid) { | ||
valid = isNumberBetween(parts[0], 0, 23); | ||
} | ||
if (valid) { | ||
valid = isNumberBetween(parts[1], 0, 59); | ||
} | ||
if (valid && (parts.length === 3)) { | ||
valid = isNumberBetween(parts[2], 0, 59); | ||
} | ||
if (valid) { | ||
return cb(); | ||
} else { | ||
return cb(new Error("Invalid date value " + fieldValue)); | ||
} | ||
break; | ||
case FIELD_TYPE_DATETIME_DATETIMEUNIT_DATETIME: | ||
try{ | ||
testDate = new Date(fieldValue); | ||
function validatorDateTime(fieldValue, fieldDefinition, previousFieldValues, cb) { | ||
var testDate; | ||
if(testDate.toString() === "Invalid Date"){ | ||
return cb(new Error("Invalid dateTime string " + fieldValue)); | ||
if (typeof(fieldValue) !== "string") { | ||
return cb(new Error("Expected string but got " + typeof(fieldValue))); | ||
} | ||
switch (fieldDefinition.fieldOptions.definition.datetimeUnit) { | ||
case FIELD_TYPE_DATETIME_DATETIMEUNIT_DATEONLY: | ||
try { | ||
testDate = new Date(fieldValue); | ||
valid = (testDate.toString() !== "Invalid Date"); | ||
} catch (e) { | ||
valid = false; | ||
} | ||
if (valid) { | ||
return cb(); | ||
} else { | ||
return cb(new Error("Invalid date value " + fieldValue)); | ||
} | ||
break; | ||
case FIELD_TYPE_DATETIME_DATETIMEUNIT_TIMEONLY: | ||
var parts = fieldValue.split(':'); | ||
valid = (parts.length === 2) || (parts.length === 3); | ||
if (valid) { | ||
valid = isNumberBetween(parts[0], 0, 23); | ||
} | ||
if (valid) { | ||
valid = isNumberBetween(parts[1], 0, 59); | ||
} | ||
if (valid && (parts.length === 3)) { | ||
valid = isNumberBetween(parts[2], 0, 59); | ||
} | ||
if (valid) { | ||
return cb(); | ||
} else { | ||
return cb(new Error("Invalid date value " + fieldValue)); | ||
} | ||
}catch(e){ | ||
return cb(new Error("Invalid dateTime string " + fieldValue)); | ||
} | ||
break; | ||
default: | ||
return cb(new Error("Invalid dateTime fieldtype " + fieldDefinition.fieldOptions.definition.datetimeUnit)); | ||
break; | ||
case FIELD_TYPE_DATETIME_DATETIMEUNIT_DATETIME: | ||
try { | ||
testDate = new Date(fieldValue); | ||
if (testDate.toString() === "Invalid Date") { | ||
return cb(new Error("Invalid dateTime string " + fieldValue)); | ||
} else { | ||
return cb(); | ||
} | ||
} catch (e) { | ||
return cb(new Error("Invalid dateTime string " + fieldValue)); | ||
} | ||
break; | ||
default: | ||
return cb(new Error("Invalid dateTime fieldtype " + fieldDefinition.fieldOptions.definition.datetimeUnit)); | ||
} | ||
} | ||
} | ||
function validatorSection (value, fieldDefinition, previousFieldValues, cb) { | ||
return cb(new Error("Should not submit section field: " + fieldDefinition.name)); | ||
} | ||
function validatorSection(value, fieldDefinition, previousFieldValues, cb) { | ||
return cb(new Error("Should not submit section field: " + fieldDefinition.name)); | ||
} | ||
function rulesResult(rules, cb) { | ||
var visible = true; | ||
function rulesResult(rules, cb) { | ||
var visible = true; | ||
// Itterate over each rule that this field is a predicate of | ||
async.each(rules, function(rule, cbRule) { | ||
// For each rule, itterate over the predicate fields and evaluate the rule | ||
var predicateMapQueries = []; | ||
var predicateMapPassed = []; | ||
async.each(rule.ruleConditionalStatements, function(ruleConditionalStatement, cbPredicates) { | ||
var field = fieldMap[ruleConditionalStatement.sourceField]; | ||
var passed = false; | ||
var submissionValues = []; | ||
var condition; | ||
var testValue; | ||
if (submissionFieldsMap[ruleConditionalStatement.sourceField] && submissionFieldsMap[ruleConditionalStatement.sourceField].fieldValues) { | ||
submissionValues = submissionFieldsMap[ruleConditionalStatement.sourceField].fieldValues; | ||
condition = ruleConditionalStatement.restriction; | ||
testValue = ruleConditionalStatement.sourceValue; | ||
// Itterate over each rule that this field is a predicate of | ||
async.each(rules, function (rule, cbRule) { | ||
// For each rule, itterate over the predicate fields and evaluate the rule | ||
var predicateMapQueries = []; | ||
var predicateMapPassed = []; | ||
async.each(rule.ruleConditionalStatements, function (ruleConditionalStatement, cbPredicates) { | ||
var field = fieldMap[ruleConditionalStatement.sourceField]; | ||
var passed = false; | ||
var submissionValues = []; | ||
var condition; | ||
var testValue; | ||
if (submissionFieldsMap[ruleConditionalStatement.sourceField] && submissionFieldsMap[ruleConditionalStatement.sourceField].fieldValues) { | ||
submissionValues = submissionFieldsMap[ruleConditionalStatement.sourceField].fieldValues; | ||
condition = ruleConditionalStatement.restriction; | ||
testValue = ruleConditionalStatement.sourceValue; | ||
// Validate rule predictes on the first entry only. | ||
passed = isConditionActive(field, submissionValues[0], testValue, condition); | ||
} | ||
predicateMapQueries.push({"field": field, | ||
"submissionValues": submissionValues, | ||
"condition": condition, | ||
"testValue": testValue, | ||
"passed" : passed | ||
}); | ||
// Validate rule predictes on the first entry only. | ||
passed = isConditionActive(field, submissionValues[0], testValue, condition); | ||
} | ||
predicateMapQueries.push({ | ||
"field": field, | ||
"submissionValues": submissionValues, | ||
"condition": condition, | ||
"testValue": testValue, | ||
"passed": passed | ||
}); | ||
if( passed ) { | ||
predicateMapPassed.push(field); | ||
} | ||
return cbPredicates(); | ||
}, function(err) { | ||
if(err) cbRule(err); | ||
if (passed) { | ||
predicateMapPassed.push(field); | ||
} | ||
return cbPredicates(); | ||
}, function (err) { | ||
if (err) cbRule(err); | ||
function rulesPassed (condition, passed, queries) { | ||
return ( (condition === "and" ) && (( passed.length == queries.length ))) || // "and" condition - all rules must pass | ||
( (condition === "or" ) && (( passed.length > 0 ))); // "or" condition - only one rule must pass | ||
} | ||
function rulesPassed(condition, passed, queries) { | ||
return ((condition === "and") && ((passed.length == queries.length))) || // "and" condition - all rules must pass | ||
((condition === "or") && ((passed.length > 0))); // "or" condition - only one rule must pass | ||
} | ||
if (rulesPassed(rule.ruleConditionalOperator, predicateMapPassed, predicateMapQueries)) { | ||
visible = (rule.type === "show"); | ||
if (rulesPassed(rule.ruleConditionalOperator, predicateMapPassed, predicateMapQueries)) { | ||
visible = (rule.type === "show"); | ||
} else { | ||
visible = (rule.type !== "show"); | ||
} | ||
return cbRule(); | ||
}); | ||
}, function (err) { | ||
if (err) return cb(err); | ||
return cb(undefined, visible); | ||
}); | ||
} | ||
function isPageVisible(pageId, cb) { | ||
init(function (err) { | ||
if (err) return cb(err); | ||
if (isPageRuleSubject(pageId)) { // if the page is the target of a rule | ||
return rulesResult(pageRuleSubjectMap[pageId], cb); // execute page rules | ||
} else { | ||
visible = (rule.type !== "show"); | ||
return cb(undefined, true); // if page is not subject of any rule then must be visible | ||
} | ||
return cbRule(); | ||
}); | ||
}, function(err) { | ||
if (err) return cb(err); | ||
} | ||
return cb(undefined, visible); | ||
}); | ||
} | ||
function isFieldVisible(fieldId, checkContainingPage, cb) { | ||
/* | ||
* fieldId = Id of field to check for reule predeciate references | ||
* checkContainingPage = if true check page containing field, and return false if the page is hidden | ||
*/ | ||
init(function (err) { | ||
if (err) return cb(err); | ||
function isPageVisible(pageId, cb) { | ||
init(function(err){ | ||
if (err) return cb(err); | ||
// Fields are visable by default | ||
var visible = true; | ||
if (isPageRuleSubject(pageId)) { // if the page is the target of a rule | ||
return rulesResult(pageRuleSubjectMap[pageId], cb); // execute page rules | ||
} else { | ||
return cb(undefined, true); // if page is not subject of any rule then must be visible | ||
} | ||
}); | ||
} | ||
var field = fieldMap[fieldId]; | ||
if (!fieldId) return cb(new Error("Field does not exist in form")); | ||
function isFieldVisible(fieldId, checkContainingPage, cb) { | ||
/* | ||
* fieldId = Id of field to check for reule predeciate references | ||
* checkContainingPage = if true check page containing field, and return false if the page is hidden | ||
*/ | ||
init(function(err){ | ||
if (err) return cb(err); | ||
async.waterfall([ | ||
// Fields are visable by default | ||
var visible = true; | ||
function testPage(cb) { | ||
if (checkContainingPage) { | ||
isPageVisible(field.pageId, cb); | ||
} else { | ||
return cb(undefined, true); | ||
} | ||
}, | ||
function testField(pageVisible, cb) { | ||
if (!pageVisible) { // if page containing field is not visible then don't need to check field | ||
return cb(undefined, false); | ||
} | ||
var field = fieldMap[fieldId]; | ||
if (!fieldId) return cb(new Error("Field does not exist in form")); | ||
async.waterfall([ | ||
function testPage(cb) { | ||
if (checkContainingPage) { | ||
isPageVisible(field.pageId, cb); | ||
} else { | ||
return cb(undefined, true); | ||
if (isFieldRuleSubject(fieldId)) { // If the field is the subject of a rule it may have been hidden | ||
return rulesResult(fieldRuleSubjectMap[fieldId], cb); // execute field rules | ||
} else { | ||
return cb(undefined, true); // if not subject of field rules then can't be hidden | ||
} | ||
} | ||
}, | ||
function testField(pageVisible, cb) { | ||
if (!pageVisible) { // if page containing field is not visible then don't need to check field | ||
return cb(undefined, false); | ||
} | ||
], cb); | ||
}); | ||
} | ||
if (isFieldRuleSubject(fieldId) ) { // If the field is the subject of a rule it may have been hidden | ||
return rulesResult(fieldRuleSubjectMap[fieldId], cb); // execute field rules | ||
} else { | ||
return cb(undefined, true); // if not subject of field rules then can't be hidden | ||
} | ||
} | ||
], cb); | ||
}); | ||
} | ||
/* | ||
* check all rules actions | ||
* res: | ||
* { | ||
* "actions": { | ||
* "pages": { | ||
* "targetId": { | ||
* "targetId": "", | ||
* "action": "show|hide" | ||
* } | ||
* }, | ||
* "fields": { | ||
* } | ||
* } | ||
* } | ||
*/ | ||
function checkRules(submissionJSON, cb) { | ||
init(function (err) { | ||
if (err) return cb(err); | ||
/* | ||
* check all rules actions | ||
* res: | ||
* { | ||
* "actions": { | ||
* "pages": { | ||
* "targetId": { | ||
* "targetId": "", | ||
* "action": "show|hide" | ||
* } | ||
* }, | ||
* "fields": { | ||
* } | ||
* } | ||
* } | ||
*/ | ||
function checkRules(submissionJSON, cb) { | ||
init(function(err){ | ||
if (err) return cb(err); | ||
initSubmission(submissionJSON, function (err) { | ||
if (err) return cb(err); | ||
var actions = {}; | ||
initSubmission(submissionJSON, function (err) { | ||
if(err) return cb(err); | ||
var actions = {}; | ||
async.parallel([ | ||
async.parallel([ | ||
function (cb) { | ||
actions.fields = {}; | ||
async.eachSeries(Object.keys(fieldRuleSubjectMap), function (fieldId, cb) { | ||
isFieldVisible(fieldId, false, function (err, fieldVisible) { | ||
if (err) return cb(err); | ||
actions.fields[fieldId] = {targetId: fieldId, action: (fieldVisible?"show":"hide")}; | ||
return cb(); | ||
}); | ||
}, cb); | ||
}, | ||
function (cb) { | ||
actions.pages = {}; | ||
async.eachSeries(Object.keys(pageRuleSubjectMap), function (pageId, cb) { | ||
isPageVisible(pageId, function (err, pageVisible) { | ||
if (err) return cb(err); | ||
actions.pages[pageId] = {targetId: pageId, action: (pageVisible?"show":"hide")}; | ||
return cb(); | ||
}); | ||
}, cb); | ||
} | ||
], function (err) { | ||
if(err) return cb(err); | ||
function (cb) { | ||
actions.fields = {}; | ||
async.eachSeries(Object.keys(fieldRuleSubjectMap), function (fieldId, cb) { | ||
isFieldVisible(fieldId, false, function (err, fieldVisible) { | ||
if (err) return cb(err); | ||
actions.fields[fieldId] = { | ||
targetId: fieldId, | ||
action: (fieldVisible ? "show" : "hide") | ||
}; | ||
return cb(); | ||
}); | ||
}, cb); | ||
}, | ||
function (cb) { | ||
actions.pages = {}; | ||
async.eachSeries(Object.keys(pageRuleSubjectMap), function (pageId, cb) { | ||
isPageVisible(pageId, function (err, pageVisible) { | ||
if (err) return cb(err); | ||
actions.pages[pageId] = { | ||
targetId: pageId, | ||
action: (pageVisible ? "show" : "hide") | ||
}; | ||
return cb(); | ||
}); | ||
}, cb); | ||
} | ||
], function (err) { | ||
if (err) return cb(err); | ||
return cb(undefined, {actions: actions}); | ||
return cb(undefined, { | ||
actions: actions | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
} | ||
} | ||
return { | ||
validateForm: validateForm, | ||
validateField: validateField, | ||
validateFieldValue: validateFieldValue, | ||
checkRules: checkRules, | ||
return { | ||
validateForm: validateForm, | ||
validateField: validateField, | ||
validateFieldValue: validateFieldValue, | ||
checkRules: checkRules, | ||
// The following are used internally, but exposed for tests | ||
validateFieldInternal: validateFieldInternal, | ||
initSubmission: initSubmission, | ||
isFieldVisible: isFieldVisible, | ||
isConditionActive: isConditionActive | ||
// The following are used internally, but exposed for tests | ||
validateFieldInternal: validateFieldInternal, | ||
initSubmission: initSubmission, | ||
isFieldVisible: isFieldVisible, | ||
isConditionActive: isConditionActive | ||
}; | ||
}; | ||
}; | ||
function isNumberBetween(num, min, max) { | ||
var numVal = parseInt(num,10); | ||
return (!isNaN(numVal) && (numVal >= min) && (numVal <= max)); | ||
} | ||
function isNumberBetween(num, min, max) { | ||
var numVal = parseInt(num, 10); | ||
return (!isNaN(numVal) && (numVal >= min) && (numVal <= max)); | ||
} | ||
function cvtTimeToSeconds(fieldValue) { | ||
var seconds = 0; | ||
if (typeof fieldValue === "string") { | ||
var parts = fieldValue.split(':'); | ||
valid = (parts.length === 2) || (parts.length === 3); | ||
if (valid) { | ||
valid = isNumberBetween(parts[0], 0, 23); | ||
seconds += (parseInt(parts[0], 10) * 60 * 60); | ||
function cvtTimeToSeconds(fieldValue) { | ||
var seconds = 0; | ||
if (typeof fieldValue === "string") { | ||
var parts = fieldValue.split(':'); | ||
valid = (parts.length === 2) || (parts.length === 3); | ||
if (valid) { | ||
valid = isNumberBetween(parts[0], 0, 23); | ||
seconds += (parseInt(parts[0], 10) * 60 * 60); | ||
} | ||
if (valid) { | ||
valid = isNumberBetween(parts[1], 0, 59); | ||
seconds += (parseInt(parts[1], 10) * 60); | ||
} | ||
if (valid && (parts.length === 3)) { | ||
valid = isNumberBetween(parts[2], 0, 59); | ||
seconds += parseInt(parts[2], 10); | ||
} | ||
} | ||
if (valid) { | ||
valid = isNumberBetween(parts[1], 0, 59); | ||
seconds += (parseInt(parts[1], 10) * 60); | ||
} | ||
if (valid && (parts.length === 3)) { | ||
valid = isNumberBetween(parts[2], 0, 59); | ||
seconds += parseInt(parts[2], 10); | ||
} | ||
return seconds; | ||
} | ||
return seconds; | ||
} | ||
function isConditionActive(field, fieldValue, testValue, condition) { | ||
function isConditionActive(field, fieldValue, testValue, condition) { | ||
var fieldType = field.type; | ||
var fieldOptions = field.fieldOptions ? field.fieldOptions : {}; | ||
var fieldType = field.type; | ||
var fieldOptions = field.fieldOptions ? field.fieldOptions : {}; | ||
var valid = true; | ||
if( "is equal to" === condition) { | ||
valid = fieldValue === testValue; | ||
} | ||
else if( "is greater than" === condition) { | ||
// TODO - do numeric checking | ||
valid = fieldValue > testValue; | ||
} | ||
else if( "is less than" === condition) { | ||
// TODO - do numeric checking | ||
valid = fieldValue < testValue; | ||
} | ||
else if( "is at" === condition) { | ||
valid = false; | ||
if( fieldType === FIELD_TYPE_DATETIME ) { | ||
switch (fieldOptions.definition.datetimeUnit) | ||
{ | ||
case FIELD_TYPE_DATETIME_DATETIMEUNIT_DATEONLY: | ||
try{ | ||
valid = (new Date(new Date(fieldValue).toDateString()).getTime() == new Date(new Date(testValue).toDateString()).getTime()); | ||
}catch(e){ | ||
valid = false; | ||
var valid = true; | ||
if ("is equal to" === condition) { | ||
valid = fieldValue === testValue; | ||
} else if ("is greater than" === condition) { | ||
// TODO - do numeric checking | ||
valid = fieldValue > testValue; | ||
} else if ("is less than" === condition) { | ||
// TODO - do numeric checking | ||
valid = fieldValue < testValue; | ||
} else if ("is at" === condition) { | ||
valid = false; | ||
if (fieldType === FIELD_TYPE_DATETIME) { | ||
switch (fieldOptions.definition.datetimeUnit) { | ||
case FIELD_TYPE_DATETIME_DATETIMEUNIT_DATEONLY: | ||
try { | ||
valid = (new Date(new Date(fieldValue).toDateString()).getTime() == new Date(new Date(testValue).toDateString()).getTime()); | ||
} catch (e) { | ||
valid = false; | ||
} | ||
break; | ||
case FIELD_TYPE_DATETIME_DATETIMEUNIT_TIMEONLY: | ||
valid = cvtTimeToSeconds(fieldValue) === cvtTimeToSeconds(testValue); | ||
break; | ||
case FIELD_TYPE_DATETIME_DATETIMEUNIT_DATETIME: | ||
try { | ||
valid = (new Date(fieldValue).getTime() == new Date(testValue).getTime()); | ||
} catch (e) { | ||
valid = false; | ||
} | ||
break; | ||
default: | ||
valid = false; // TODO should raise error here? | ||
break; | ||
} | ||
break; | ||
case FIELD_TYPE_DATETIME_DATETIMEUNIT_TIMEONLY: | ||
valid = cvtTimeToSeconds(fieldValue) === cvtTimeToSeconds(testValue); | ||
break; | ||
case FIELD_TYPE_DATETIME_DATETIMEUNIT_DATETIME: | ||
try{ | ||
valid = (new Date(fieldValue).getTime() == new Date(testValue).getTime()); | ||
}catch(e){ | ||
valid = false; | ||
} | ||
break; | ||
default: | ||
valid = false; // TODO should raise error here? | ||
break; | ||
} | ||
} | ||
} | ||
else if( "is before" === condition) { | ||
valid = false; | ||
if( fieldType === FIELD_TYPE_DATETIME ) { | ||
switch (fieldOptions.definition.datetimeUnit) | ||
{ | ||
case FIELD_TYPE_DATETIME_DATETIMEUNIT_DATEONLY: | ||
try{ | ||
valid = (new Date(new Date(fieldValue).toDateString()).getTime() < new Date(new Date(testValue).toDateString()).getTime()); | ||
}catch(e){ | ||
valid = false; | ||
} else if ("is before" === condition) { | ||
valid = false; | ||
if (fieldType === FIELD_TYPE_DATETIME) { | ||
switch (fieldOptions.definition.datetimeUnit) { | ||
case FIELD_TYPE_DATETIME_DATETIMEUNIT_DATEONLY: | ||
try { | ||
valid = (new Date(new Date(fieldValue).toDateString()).getTime() < new Date(new Date(testValue).toDateString()).getTime()); | ||
} catch (e) { | ||
valid = false; | ||
} | ||
break; | ||
case FIELD_TYPE_DATETIME_DATETIMEUNIT_TIMEONLY: | ||
valid = cvtTimeToSeconds(fieldValue) < cvtTimeToSeconds(testValue); | ||
break; | ||
case FIELD_TYPE_DATETIME_DATETIMEUNIT_DATETIME: | ||
try { | ||
valid = (new Date(fieldValue).getTime() < new Date(testValue).getTime()); | ||
} catch (e) { | ||
valid = false; | ||
} | ||
break; | ||
default: | ||
valid = false; // TODO should raise error here? | ||
break; | ||
} | ||
break; | ||
case FIELD_TYPE_DATETIME_DATETIMEUNIT_TIMEONLY: | ||
valid = cvtTimeToSeconds(fieldValue) < cvtTimeToSeconds(testValue); | ||
break; | ||
case FIELD_TYPE_DATETIME_DATETIMEUNIT_DATETIME: | ||
try{ | ||
valid = (new Date(fieldValue).getTime() < new Date(testValue).getTime()); | ||
}catch(e){ | ||
valid = false; | ||
} | ||
break; | ||
default: | ||
valid = false; // TODO should raise error here? | ||
break; | ||
} | ||
} | ||
} | ||
else if( "is after" === condition) { | ||
valid = false; | ||
if( fieldType === FIELD_TYPE_DATETIME ) { | ||
switch (fieldOptions.definition.datetimeUnit) | ||
{ | ||
case FIELD_TYPE_DATETIME_DATETIMEUNIT_DATEONLY: | ||
try{ | ||
valid = (new Date(new Date(fieldValue).toDateString()).getTime() > new Date(new Date(testValue).toDateString()).getTime()); | ||
}catch(e){ | ||
valid = false; | ||
} else if ("is after" === condition) { | ||
valid = false; | ||
if (fieldType === FIELD_TYPE_DATETIME) { | ||
switch (fieldOptions.definition.datetimeUnit) { | ||
case FIELD_TYPE_DATETIME_DATETIMEUNIT_DATEONLY: | ||
try { | ||
valid = (new Date(new Date(fieldValue).toDateString()).getTime() > new Date(new Date(testValue).toDateString()).getTime()); | ||
} catch (e) { | ||
valid = false; | ||
} | ||
break; | ||
case FIELD_TYPE_DATETIME_DATETIMEUNIT_TIMEONLY: | ||
valid = cvtTimeToSeconds(fieldValue) > cvtTimeToSeconds(testValue); | ||
break; | ||
case FIELD_TYPE_DATETIME_DATETIMEUNIT_DATETIME: | ||
try { | ||
valid = (new Date(fieldValue).getTime() > new Date(testValue).getTime()); | ||
} catch (e) { | ||
valid = false; | ||
} | ||
break; | ||
default: | ||
valid = false; // TODO should raise error here? | ||
break; | ||
} | ||
break; | ||
case FIELD_TYPE_DATETIME_DATETIMEUNIT_TIMEONLY: | ||
valid = cvtTimeToSeconds(fieldValue) > cvtTimeToSeconds(testValue); | ||
break; | ||
case FIELD_TYPE_DATETIME_DATETIMEUNIT_DATETIME: | ||
try{ | ||
valid = (new Date(fieldValue).getTime() > new Date(testValue).getTime()); | ||
}catch(e){ | ||
valid = false; | ||
} | ||
break; | ||
default: | ||
valid = false; // TODO should raise error here? | ||
break; | ||
} | ||
} | ||
} | ||
else if( "is" === condition) { | ||
if (fieldType === FIELD_TYPE_CHECKBOX) { | ||
valid = fieldValue && fieldValue.selections && fieldValue.selections.indexOf(testValue) !== -1; | ||
} else if ("is" === condition) { | ||
if (fieldType === FIELD_TYPE_CHECKBOX) { | ||
valid = fieldValue && fieldValue.selections && fieldValue.selections.indexOf(testValue) !== -1; | ||
} else { | ||
valid = fieldValue === testValue; | ||
} | ||
} else if ("is not" === condition) { | ||
if (fieldType === FIELD_TYPE_CHECKBOX) { | ||
valid = fieldValue && fieldValue.selections && fieldValue.selections.indexOf(testValue) === -1; | ||
} else { | ||
valid = fieldValue !== testValue; | ||
} | ||
} else if ("contains" === condition) { | ||
valid = fieldValue.indexOf(testValue) !== -1; | ||
} else if ("does not contain" === condition) { | ||
valid = fieldValue.indexOf(testValue) === -1; | ||
} else if ("begins with" === condition) { | ||
valid = fieldValue.substring(0, testValue.length) === testValue; | ||
} else if ("ends with" === condition) { | ||
valid = fieldValue.substring(Math.max(0, (fieldValue.length - testValue.length)), fieldValue.length) === testValue; | ||
} else { | ||
valid = fieldValue === testValue; | ||
valid = false; | ||
} | ||
return valid; | ||
} | ||
else if( "is not" === condition) { | ||
if (fieldType === FIELD_TYPE_CHECKBOX) { | ||
valid = fieldValue && fieldValue.selections && fieldValue.selections.indexOf(testValue) === -1; | ||
} else { | ||
valid = fieldValue !== testValue; | ||
} | ||
} | ||
else if( "contains" === condition) { | ||
valid = fieldValue.indexOf(testValue) !== -1; | ||
} | ||
else if( "does not contain" === condition) { | ||
valid = fieldValue.indexOf(testValue) === -1; | ||
} | ||
else if( "begins with" === condition) { | ||
valid = fieldValue.substring(0, testValue.length) === testValue; | ||
} | ||
else if( "ends with" === condition) { | ||
valid = fieldValue.substring(Math.max(0, (fieldValue.length - testValue.length)), fieldValue.length) === testValue; | ||
} | ||
else { | ||
valid = false; | ||
} | ||
return valid; | ||
} | ||
if (typeof module !== 'undefined' && module.exports) { | ||
if (typeof module !== 'undefined' && module.exports) { | ||
module.exports = formsRulesEngine; | ||
} | ||
} | ||
}()); | ||
}()); |
{ | ||
"name": "fh-forms", | ||
"version": "0.5.6", | ||
"version": "0.5.7", | ||
"description": "Cloud Forms API for form submission", | ||
@@ -5,0 +5,0 @@ "main": "lib/forms.js", |
@@ -1,1 +0,1 @@ | ||
0.5.6-37 | ||
0.5.7-38 |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
277074
7277