tellform/app/models/form.server.model.js

440 lines
9.5 KiB
JavaScript
Raw Normal View History

2015-06-29 22:51:29 +00:00
'use strict';
/**
* Module dependencies.
*/
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
_ = require('lodash'),
2015-10-06 20:14:38 +00:00
mUtilities = require('mongoose-utilities'),
2015-07-27 18:11:43 +00:00
async = require('async'),
2016-04-29 02:48:02 +00:00
Random = require('random-js'),
2017-04-23 02:13:21 +00:00
mt = Random.engines.mt19937();
2015-08-04 21:06:16 +00:00
2016-04-29 02:48:02 +00:00
mt.autoSeed();
2015-08-04 21:06:16 +00:00
//Mongoose Models
2015-09-18 16:32:17 +00:00
var FieldSchema = require('./form_field.server.model.js');
2015-09-10 22:06:28 +00:00
var FormSubmissionSchema = require('./form_submission.server.model.js'),
FormSubmission = mongoose.model('FormSubmission', FormSubmissionSchema);
2015-06-29 22:51:29 +00:00
2015-09-18 16:32:17 +00:00
2015-08-18 21:44:36 +00:00
var ButtonSchema = new Schema({
url: {
type: String,
match: [/((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)/],
},
action: String,
text: String,
bgColor: {
type: String,
match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/],
default: '#5bc0de'
},
color: {
type: String,
match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/],
default: '#ffffff'
}
});
2016-06-07 00:37:09 +00:00
var VisitorDataSchema = new Schema({
referrer: {
2016-06-17 21:51:17 +00:00
type: String
2016-06-07 00:37:09 +00:00
},
lastActiveField: {
type: Schema.Types.ObjectId
},
timeElapsed: {
2016-06-20 22:06:41 +00:00
type: Number
2016-06-07 00:37:09 +00:00
},
isSubmitted: {
2016-06-20 22:06:41 +00:00
type: Boolean
2016-06-17 21:33:33 +00:00
},
language: {
type: String
},
ipAddr: {
type: String,
default: ''
},
deviceType: {
type: String,
enum: ['desktop', 'phone', 'tablet', 'other'],
2016-06-17 21:51:17 +00:00
default: 'other'
2016-06-17 21:33:33 +00:00
},
userAgent: {
type: String
2016-06-07 00:37:09 +00:00
}
2016-06-17 21:33:33 +00:00
2016-06-07 00:37:09 +00:00
});
2016-11-09 18:02:12 +00:00
var formSchemaOptions = {
toJSON: {
virtuals: true
}
};
2015-06-29 22:51:29 +00:00
/**
* Form Schema
*/
2015-09-10 22:06:28 +00:00
var FormSchema = new Schema({
2015-06-29 22:51:29 +00:00
title: {
type: String,
trim: true,
2016-04-29 02:48:02 +00:00
required: 'Form Title cannot be blank'
2015-06-29 22:51:29 +00:00
},
2015-07-07 01:21:43 +00:00
language: {
type: String,
2016-06-16 00:38:22 +00:00
enum: ['en', 'fr', 'es', 'it', 'de'],
default: 'en',
required: 'Form must have a language'
2015-07-07 01:21:43 +00:00
},
2016-06-07 00:37:09 +00:00
analytics:{
gaCode: {
type: String
},
visitors: [VisitorDataSchema]
},
2016-06-01 01:06:45 +00:00
form_fields: [FieldSchema],
2015-06-30 02:14:43 +00:00
submissions: [{
2015-06-29 22:51:29 +00:00
type: Schema.Types.ObjectId,
ref: 'FormSubmission'
}],
admin: {
type: Schema.Types.ObjectId,
ref: 'User',
required: 'Form must have an Admin'
2015-06-29 22:51:29 +00:00
},
2015-08-07 21:02:44 +00:00
startPage: {
showStart:{
type: Boolean,
2016-04-29 02:48:02 +00:00
default: false
2015-08-07 21:02:44 +00:00
},
2015-08-18 21:44:36 +00:00
introTitle:{
2015-08-07 21:02:44 +00:00
type: String,
2015-08-18 21:44:36 +00:00
default: 'Welcome to Form'
2015-08-07 21:02:44 +00:00
},
2015-08-18 21:44:36 +00:00
introParagraph:{
2016-03-30 03:45:16 +00:00
type: String
2015-08-18 21:44:36 +00:00
},
2016-04-21 21:44:15 +00:00
introButtonText:{
type: String,
default: 'Start'
},
2015-08-18 21:44:36 +00:00
buttons:[ButtonSchema]
2015-08-06 05:52:59 +00:00
},
2017-03-13 19:08:21 +00:00
endPage: {
showEnd:{
type: Boolean,
default: false
},
title:{
type: String,
default: 'Thank you for filling out the form'
},
paragraph:{
type: String
},
buttonText:{
type: String,
default: 'Go back to Form'
},
buttons:[ButtonSchema]
},
2015-08-18 21:44:36 +00:00
2015-06-30 11:05:44 +00:00
hideFooter: {
type: Boolean,
2016-03-30 03:45:16 +00:00
default: false
2015-06-30 11:05:44 +00:00
},
2015-07-02 02:49:35 +00:00
isLive: {
type: Boolean,
2017-07-26 22:33:00 +00:00
default: true
2015-07-02 02:49:35 +00:00
},
2015-08-07 21:02:44 +00:00
design: {
colors:{
2016-03-30 03:45:16 +00:00
backgroundColor: {
type: String,
2015-10-30 21:40:22 +00:00
match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/],
default: '#fff'
},
2016-03-30 03:45:16 +00:00
questionColor: {
type: String,
2015-10-30 21:40:22 +00:00
match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/],
2016-04-21 23:48:59 +00:00
default: '#333'
},
2016-03-30 03:45:16 +00:00
answerColor: {
type: String,
2015-10-30 21:40:22 +00:00
match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/],
2016-04-21 23:48:59 +00:00
default: '#333'
},
2016-03-30 03:45:16 +00:00
buttonColor: {
type: String,
2016-04-21 23:48:59 +00:00
match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/],
default: '#fff'
},
buttonTextColor: {
type: String,
match: [/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/],
default: '#333'
2016-04-29 02:48:02 +00:00
}
},
2016-08-26 19:31:40 +00:00
font: String
}
2016-11-09 18:02:12 +00:00
}, formSchemaOptions);
2015-06-29 22:51:29 +00:00
2016-06-07 00:37:09 +00:00
/*
** In-Form Analytics Virtual Attributes
*/
FormSchema.virtual('analytics.views').get(function () {
2017-03-10 19:26:07 +00:00
if(this.analytics && this.analytics.visitors && this.analytics.visitors.length > 0){
return this.analytics.visitors.length;
} else {
return 0;
}
2016-06-07 00:37:09 +00:00
});
FormSchema.virtual('analytics.submissions').get(function () {
return this.submissions.length;
});
FormSchema.virtual('analytics.conversionRate').get(function () {
2017-03-10 19:26:07 +00:00
if(this.analytics && this.analytics.visitors && this.analytics.visitors.length > 0){
return this.submissions.length/this.analytics.visitors.length*100;
} else {
return 0;
}
2016-06-07 00:37:09 +00:00
});
FormSchema.virtual('analytics.fields').get(function () {
var fieldDropoffs = [];
var visitors = this.analytics.visitors;
var that = this;
if(this.form_fields.length === 0) {
2017-04-23 02:13:21 +00:00
return null;
}
2016-06-07 00:37:09 +00:00
for(var i=0; i<this.form_fields.length; i++){
var field = this.form_fields[i];
2017-03-01 20:11:20 +00:00
if(field && !field.deletePreserved){
2016-06-07 00:37:09 +00:00
var dropoffViews = _.reduce(visitors, function(sum, visitorObj){
if(visitorObj.lastActiveField+'' === field._id+'' && !visitorObj.isSubmitted){
return sum + 1;
}
return sum;
}, 0);
var continueViews, nextIndex;
if(i !== this.form_fields.length-1){
continueViews = _.reduce(visitors, function(sum, visitorObj){
nextIndex = that.form_fields.indexOf(_.find(that.form_fields, function(o) {
return o._id+'' === visitorObj.lastActiveField+'';
}));
if(nextIndex > i){
return sum + 1;
}
return sum;
}, 0);
} else {
2016-06-07 00:37:09 +00:00
continueViews = _.reduce(visitors, function(sum, visitorObj){
if(visitorObj.lastActiveField+'' === field._id+'' && visitorObj.isSubmitted){
return sum + 1;
}
return sum;
}, 0);
}
var totalViews = dropoffViews+continueViews;
2017-03-06 21:45:11 +00:00
var continueRate = (continueViews/totalViews*100).toFixed(0);
var dropoffRate = (dropoffViews/totalViews*100).toFixed(0);
2016-06-07 00:37:09 +00:00
fieldDropoffs[i] = {
dropoffViews: dropoffViews,
2016-06-17 22:11:02 +00:00
responses: continueViews,
2016-06-07 00:37:09 +00:00
totalViews: totalViews,
continueRate: continueRate,
dropoffRate: dropoffRate,
field: field
};
}
}
return fieldDropoffs;
});
2015-10-06 20:14:38 +00:00
FormSchema.plugin(mUtilities.timestamp, {
createdPath: 'created',
modifiedPath: 'lastModified',
useVirtual: false
});
2015-12-12 20:08:48 +00:00
2017-03-06 19:43:42 +00:00
FormSchema.pre('save', function (next) {
switch(this.language){
case 'spanish':
this.language = 'es';
break;
case 'french':
this.language = 'fr';
break;
case 'italian':
this.language = 'it';
break;
case 'german':
this.language = 'de';
break;
default:
this.language = 'en';
break;
}
next();
});
2017-07-29 00:04:16 +00:00
function getDeletedIndexes(needle, haystack){
var deletedIndexes = [];
if(haystack.length > 0){
for(var i = 0; i < needle.length; i++){
if(haystack.indexOf(needle[i]) === -1){
deletedIndexes.push(i);
}
}
}
return deletedIndexes;
}
function formFieldsAllHaveIds(form_fields){
for(var i=0; i<form_fields.length; i++){
if(!form_fields[i].hasOwnProperty('_id') && !form_fields[i].hasOwnProperty('globalId')){
return false;
}
}
return true;
}
2015-09-10 22:06:28 +00:00
FormSchema.pre('save', function (next) {
2016-04-29 02:48:02 +00:00
var that = this;
2017-07-29 00:04:16 +00:00
var _original;
2016-04-29 02:48:02 +00:00
2016-04-29 06:22:47 +00:00
async.series([function(cb) {
2016-04-29 02:48:02 +00:00
that.constructor
.findOne({_id: that._id}).exec(function (err, original) {
if (err) {
2016-04-29 06:00:41 +00:00
return cb(err);
2017-07-29 00:04:16 +00:00
} else if (!original){
return next();
2016-04-29 02:48:02 +00:00
} else {
_original = original;
2016-04-29 06:00:41 +00:00
return cb(null);
2015-07-03 00:49:23 +00:00
}
2015-07-02 02:49:35 +00:00
});
2016-04-29 02:48:02 +00:00
},
function(cb) {
2017-07-29 00:04:16 +00:00
if(that.form_fields && that.isModified('form_fields') && formFieldsAllHaveIds(that.toObject().form_fields)){
2016-04-29 02:48:02 +00:00
2017-07-29 00:04:16 +00:00
var current_form = that.toObject(),
old_form_fields = _original.toObject().form_fields,
new_ids = _.map(_.map(current_form.form_fields, 'globalId'), function(id){ return ''+id;}),
old_ids = _.map(_.map(old_form_fields, 'globalId'), function(id){ return ''+id;}),
deletedIds = getDeletedIndexes(old_ids, new_ids);
2016-04-29 02:48:02 +00:00
2017-07-29 00:04:16 +00:00
console.log(deletedIds);
console.log(new_ids);
console.log(old_ids);
//Check if any form_fileds were deleted
if( deletedIds.length > 0 ){
2016-04-29 02:48:02 +00:00
var modifiedSubmissions = [];
2016-04-29 02:48:02 +00:00
async.forEachOfSeries(deletedIds,
function (deletedIdIndex, key, cb_id) {
2016-04-29 02:48:02 +00:00
var deleted_id = old_ids[deletedIdIndex];
//Find FormSubmissions that contain field with _id equal to 'deleted_id'
FormSubmission.
2017-07-29 00:04:16 +00:00
find({ form: that, form_fields: {$elemMatch: {globalId: deleted_id} } }).
exec(function(err, submissions){
if(err) {
return cb_id(err);
2017-07-29 00:04:16 +00:00
}
//Preserve fields that have at least one submission
2017-04-23 02:13:21 +00:00
if (submissions.length) {
//Add submissions
modifiedSubmissions.push.apply(modifiedSubmissions, submissions);
}
2017-04-23 02:13:21 +00:00
return cb_id(null);
});
},
function (err) {
if(err){
console.error(err.message);
return cb(err);
2017-04-23 02:13:21 +00:00
}
//Iterate through all submissions with modified form_fields
async.forEachOfSeries(modifiedSubmissions, function (submission, key, callback) {
2017-07-29 00:04:16 +00:00
var submission_form_fields = submission.toObject().form_fields;
var currentform_form_fields = that.toObject().form_fields;
2017-07-29 00:04:16 +00:00
//Iterate through ids of deleted fields
for (var i = 0; i < deletedIds.length; i++) {
var index = _.findIndex(submission_form_fields, function (field) {
var tmp_id = field.globalId + '';
2017-04-23 02:13:21 +00:00
return tmp_id === old_ids[deletedIds[i]];
});
2017-07-29 00:04:16 +00:00
var deletedField = submission_form_fields[index];
2016-04-29 02:48:02 +00:00
2017-04-23 02:13:21 +00:00
//Hide field if it exists
if (deletedField) {
2016-04-29 02:48:02 +00:00
2017-04-23 02:13:21 +00:00
//Delete old form_field
2017-07-29 00:04:16 +00:00
submission_form_fields.splice(index, 1);
2017-04-23 02:13:21 +00:00
deletedField.deletePreserved = true;
2017-04-23 02:13:21 +00:00
//Move deleted form_field to start
2017-07-29 00:04:16 +00:00
submission_form_fields.unshift(deletedField);
currentform_form_fields.unshift(deletedField);
}
2017-04-23 02:13:21 +00:00
}
2017-07-29 00:04:16 +00:00
submission.form_fields = submission_form_fields;
that.form_fields = currentform_form_fields;
2017-04-23 02:13:21 +00:00
submission.save(function (saveErr) {
return callback(saveErr);
});
2017-04-23 02:13:21 +00:00
}, function (err) {
return cb(err);
});
}
);
2017-07-29 00:04:16 +00:00
} else {
return cb(null);
2016-04-29 02:48:02 +00:00
}
2017-07-29 00:04:16 +00:00
} else {
2017-04-23 02:13:21 +00:00
return cb(null);
}
}],
function(err, results){
2017-04-23 02:13:21 +00:00
next(err);
});
2015-07-27 18:11:43 +00:00
});
2015-06-29 22:51:29 +00:00
2015-09-10 22:06:28 +00:00
mongoose.model('Form', FormSchema);
2015-06-29 22:51:29 +00:00