diff --git a/.jshintrc b/.jshintrc index 94d52d77..73f3a588 100755 --- a/.jshintrc +++ b/.jshintrc @@ -21,6 +21,7 @@ "globals": { // Globals variables. "jasmine": true, "angular": true, + "devel": false, "_": true, "saveAs": true, "ApplicationConfiguration": true, diff --git a/Dockerfile-dev b/Dockerfile-dev index 5c041477..48f18e7b 100644 --- a/Dockerfile-dev +++ b/Dockerfile-dev @@ -10,12 +10,23 @@ MAINTAINER David Baldwynn # Install NPM Global Libraries RUN npm install --quiet -g grunt bower && npm cache clean -ADD . /code WORKDIR /code +# Add bower.json +COPY package.json /code/package.json +COPY bower.json /code/bower.json +COPY .bowerrc /code/.bowerrc + +COPY ./process.yml /code/process.yml +COPY ./app /code/app +COPY ./public /code/public +COPY ./config /code/config +COPY ./gruntfile.js /code/gruntfile.js +COPY ./server.js /code/server.js +COPY ./scripts/create_admin.js /code/scripts/create_admin.js + RUN npm install --quiet -RUN bower install --interactive-mode=false --allow-root -RUN grunt build +RUN bower install --config.interactive=false --allow-root # Run TellForm server -CMD ["grunt"] +CMD ["node", "server.js"] diff --git a/app/controllers/forms.server.controller.js b/app/controllers/forms.server.controller.js index f76f6581..34032727 100644 --- a/app/controllers/forms.server.controller.js +++ b/app/controllers/forms.server.controller.js @@ -34,18 +34,8 @@ exports.deleteSubmissions = function(req, res) { }); return; } - - form.analytics.visitors = []; - form.save(function(formSaveErr){ - if(formSaveErr){ - res.status(400).send({ - message: errorHandler.getErrorMessage(formSaveErr) - }); - return; - } - res.status(200).send('Form submissions successfully deleted'); - - }); + + res.status(200).send('Form submissions successfully deleted'); }); }; @@ -76,15 +66,19 @@ exports.createSubmission = function(req, res) { message: errorHandler.getErrorMessage(err) }); } - var form = req.body + var form = req.body; var formFieldDict = emailNotifications.createFieldDict(form.form_fields); async.waterfall([ function(callback) { - if (form.selfNotifications && form.selfNotifications.enabled && form.selfNotifications.fromField) { - form.selfNotifications.fromEmails = formFieldDict[form.selfNotifications.fromField]; - - emailNotifications.send(form.selfNotifications, formFieldDict, smtpTransport, constants.varFormat, function(err){ + if (form.selfNotifications && form.selfNotifications.enabled) { + if(form.selfNotifications.fromField){ + form.selfNotifications.fromEmails = formFieldDict[form.selfNotifications.fromField]; + } else { + form.selfNotifications.fromEmails = config.mailer.options.from; + } + + emailNotifications.send(form.selfNotifications, formFieldDict, smtpTransport, function(err){ if(err){ return callback({ message: 'Failure sending submission self-notification email' @@ -101,8 +95,7 @@ exports.createSubmission = function(req, res) { if (form.respondentNotifications && form.respondentNotifications.enabled && form.respondentNotifications.toField) { form.respondentNotifications.toEmails = formFieldDict[form.respondentNotifications.toField]; - debugger; - emailNotifications.send(form.respondentNotifications, formFieldDict, smtpTransport, constants.varFormat, function(err){ + emailNotifications.send(form.respondentNotifications, formFieldDict, smtpTransport, function(err){ if(err){ return callback({ message: 'Failure sending submission respondent-notification email' @@ -154,7 +147,7 @@ exports.getVisitorData = function(req, res) { }, { $facet: { - "deviceStatistics": [ + 'deviceStatistics': [ { $unwind: '$analytics.visitors' }, @@ -184,32 +177,40 @@ exports.getVisitorData = function(req, res) { }, { $group: { - _id: "$deviceType", - total_time: { $sum: "$SubmittedTimeElapsed" }, - responses: { $sum: "$SubmittedResponses" }, + _id: '$deviceType', + total_time: { $sum: '$SubmittedTimeElapsed' }, + responses: { $sum: '$SubmittedResponses' }, visits: { $sum: 1 } } }, { $project: { - total_time: "$total_time", - responses: "$responses", - visits: "$visits", + total_time: '$total_time', + responses: '$responses', + visits: '$visits', average_time: { - $divide : ["$total_time", "$responses"] + $cond: [ + { $eq: [ '$responses', 0 ] }, + 0, + { $divide: ['$total_time', '$responses'] } + ] }, conversion_rate: { $multiply: [ 100, { - $divide : ["$responses", "$visits"] + $cond: [ + { $eq: [ '$visits', 0 ] }, + 0, + { $divide: ['$responses', '$visits'] } + ] } ] } } } ], - "globalStatistics": [ + 'globalStatistics': [ { $unwind: '$analytics.visitors' }, @@ -240,25 +241,33 @@ exports.getVisitorData = function(req, res) { { $group: { _id: null, - total_time: { $sum: "$SubmittedTimeElapsed" }, - responses: { $sum: "$SubmittedResponses" }, + total_time: { $sum: '$SubmittedTimeElapsed' }, + responses: { $sum: '$SubmittedResponses' }, visits: { $sum: 1 } } }, { $project: { _id: 0, - total_time: "$total_time", - responses: "$responses", - visits: "$visits", + total_time: '$total_time', + responses: '$responses', + visits: '$visits', average_time: { - $divide : ["$total_time", "$responses"] + $cond: [ + { $eq: [ '$responses', 0 ] }, + 0, + { $divide: ['$total_time', '$responses'] } + ] }, conversion_rate: { $multiply: [ 100, { - $divide : ["$responses", "$visits"] + $cond: [ + { $eq: [ '$visits', 0 ] }, + 0, + { $divide: ['$responses', '$visits'] } + ] } ] } @@ -299,7 +308,7 @@ exports.create = function(req, res) { }); } - createdForm = helpers.removeSensitiveModelData('private_form', createdForm); + createdForm = helpers.removeSensitiveModelData('private_form', createdForm.toJSON()); return res.json(createdForm); }); }; @@ -317,13 +326,8 @@ exports.read = function(req, res) { }); } - var newForm = req.form.toJSON(); - - if(newForm.admin === req.user._id){ - return res.json(newForm); - } - - newForm = helpers.removeSensitiveModelData('private_form', newForm); + var newForm = helpers.removeSensitiveModelData('private_form', req.form.toJSON()); + return res.json(newForm); } }; @@ -339,7 +343,7 @@ var readForRender = exports.readForRender = function(req, res) { }); } - newForm = helpers.removeSensitiveModelData('public_form', newForm); + newForm = helpers.removeSensitiveModelData('public_form', newForm.toJSON()); if(newForm.startPage && !newForm.startPage.showStart){ delete newForm.startPage; @@ -355,8 +359,8 @@ exports.update = function(req, res) { var form = req.form; var updatedForm = req.body.form; - - if(!form.analytics){ + + if(!form.analytics && req.body.form.analytics){ form.analytics = { visitors: [], gaCode: '' @@ -370,9 +374,18 @@ exports.update = function(req, res) { diff.applyChange(form._doc, true, change); }); } else { + if(!updatedForm){ + res.status(400).send({ + message: 'Updated Form is empty' + }); + } - delete updatedForm.__v; + delete updatedForm.lastModified; delete updatedForm.created; + delete updatedForm.id; + delete updatedForm._id; + delete updatedForm.__v; + //Unless we have 'admin' privileges, updating the form's admin is disabled if(updatedForm && req.user.roles.indexOf('admin') === -1) { delete updatedForm.admin; @@ -395,7 +408,7 @@ exports.update = function(req, res) { message: errorHandler.getErrorMessage(err) }); } else { - savedForm = helpers.removeSensitiveModelData('private_form', savedForm); + savedForm = helpers.removeSensitiveModelData('private_form', savedForm.toJSON()); res.json(savedForm); } }); @@ -463,15 +476,17 @@ exports.list = function(req, res) { }); } - const result_ids = results.map(function(result){ return result._id.id }); + const result_ids = results.map(function(result){ + return ''+result._id; + }); + var currIndex = -1; for(var i=0; i -1){ + currIndex = result_ids.indexOf(forms[i]._id); + if(currIndex > -1){ forms[i].submissionNum = results[currIndex].responses; } else { forms[i].submissionNum = 0; @@ -494,7 +509,7 @@ exports.formByID = function(req, res, next, id) { } Form.findById(id) - .select('admin title language form_fields startPage endPage hideFooter isLive design analytics.gaCode respondentNotifications selfNotifications') + .select('admin title language form_fields startPage endPage showFooter isLive design analytics.gaCode respondentNotifications selfNotifications') .populate('admin') .exec(function(err, form) { if (err) { @@ -506,7 +521,7 @@ exports.formByID = function(req, res, next, id) { } else { //Remove sensitive information from User object - req.form = helpers.removeSensitiveModelData('private_form', form); + req.form = helpers.removeSensitiveModelData('private_form', form.toJSON()); return next(); } }); @@ -523,7 +538,7 @@ exports.formByIDFast = function(req, res, next, id) { } Form.findById(id) .lean() - .select('title language form_fields startPage endPage hideFooter isLive design analytics.gaCode selfNotifications respondentNotifications') + .select('title language form_fields startPage endPage showFooter isLive design analytics.gaCode selfNotifications respondentNotifications') .exec(function(err, form) { if (err) { return next(err); @@ -545,7 +560,7 @@ exports.formByIDFast = function(req, res, next, id) { */ exports.hasAuthorization = function(req, res, next) { var form = req.form; - if (req.form.admin.id !== req.user.id && req.user.roles.indexOf('admin') === -1) { + if (req.form.admin.id !== req.user.id || req.user.roles.indexOf('admin') > -1) { res.status(403).send({ message: 'User '+req.user.username+' is not authorized to edit Form: '+form.title }); diff --git a/app/controllers/helpers.server.controller.js b/app/controllers/helpers.server.controller.js index f769187f..9b4c3792 100644 --- a/app/controllers/helpers.server.controller.js +++ b/app/controllers/helpers.server.controller.js @@ -1,44 +1,28 @@ 'use strict'; -module.exports = { - removeSensitiveModelData: function(type, object){ - var privateFields = { - 'public_form': ['__v', 'analytics.visitors', 'analytics.views', 'analytics.conversionRate', 'analytics.fields', 'lastModified', 'created'], - 'private_form': ['__v'], - 'public_user': ['passwordHash', 'password', 'provider', 'salt', 'lastModified', 'created', 'resetPasswordToken', 'resetPasswordExpires', 'token', 'apiKey', '__v'], - 'private_user': ['passwordHash', 'password', 'provider', 'salt', 'resetPasswordToken', 'resetPasswordExpires', 'token', '__v'] - }; +const constants = require('../libs/constants'); +const _ = require('lodash'); - function removeKeysFromDict(dict, keys){ - for(var i=0; i static', @@ -71,11 +91,9 @@ module.exports = { userRoleTypes: ['user', 'admin', 'superuser'], regex: { + username: /^[a-zA-Z0-9\-]+$/, url: /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\+\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\+~%\/.\w-_]*)?\??(?:[-\+=&;%@.\w_]*)#?(?:[\w]*))?)/, hexCode: /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/, email: /^(([^<>()\[\]\\.,;:\s@']+(\.[^<>()\[\]\\.,;:\s@']+)*)|('.+'))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, - templateVariable: /(.|\n)*?<\/var>/g - }, - - varFormat: [']+)id=["\']{1}field:', '["\']{1}>([^<>]+)*?<\/var>'], + } }; \ No newline at end of file diff --git a/app/libs/send-email-notifications.js b/app/libs/send-email-notifications.js index 30af3aad..158882e7 100644 --- a/app/libs/send-email-notifications.js +++ b/app/libs/send-email-notifications.js @@ -1,9 +1,12 @@ 'use strict'; +const jsdom = require('jsdom'); +var JSDOM = jsdom.JSDOM; module.exports = { - send: function(emailSettings, emailTemplateVars, smtpTransport, varFormat, cb){ - var parsedTemplate = this.parseTemplate(emailSettings.htmlTemplate, emailTemplateVars, varFormat); - var parsedSubject = this.parseTemplate(emailSettings.subject, emailTemplateVars, varFormat); + send: function(emailSettings, emailTemplateVars, smtpTransport, cb){ + var parsedTemplate = this.parseTemplate(emailSettings.htmlTemplate, emailTemplateVars, false); + var parsedSubject = this.parseTemplate(emailSettings.subject, emailTemplateVars, true); + var mailOptions = { replyTo: emailSettings.fromEmails, from: 'noreply@tellform.com', @@ -12,31 +15,32 @@ module.exports = { html: parsedTemplate }; - console.log('HERE'); - smtpTransport.sendMail(mailOptions, function(){ - console.log('THERE'); - cb(); + smtpTransport.sendMail(mailOptions, function(err){ + cb(err); }); }, - parseTemplate: function(emailTemplate, emailAttrs, varFormat){ - var resolvedTemplate = emailTemplate; - var that = this; - Object.keys(emailAttrs).forEach(function (key) { - resolvedTemplate = that.replaceTemplateVal(key, emailAttrs[key], resolvedTemplate, varFormat); - }); - return resolvedTemplate; - }, + parseTemplate: function(emailTemplate, emailTemplateVars, onlyText){ + var dom = new JSDOM(''+emailTemplate); - replaceTemplateVal: function(key, val, template, varFormat){ - return template.replace( new RegExp(varFormat[0] + key + varFormat[1], 'g'), val); + Object.keys(emailTemplateVars).forEach(function (key) { + var elem = dom.window.document.querySelector('span.placeholder-tag[data-id=\'' + key + '\']'); + if(elem !== null){ + elem.outerHTML = emailTemplateVars[key]; + } + }); + + if(onlyText){ + return dom.window.document.documentElement.textContent; + } + return dom.serialize(); }, createFieldDict: function(form_fields){ var formFieldDict = {}; form_fields.forEach(function(field){ - if(field.hasOwnProperty('globalId') && field.hasOwnProperty('fieldValue')){ - formFieldDict[field.globalId] = field.fieldValue; + if(field.hasOwnProperty('fieldValue') && field.hasOwnProperty('_id')){ + formFieldDict[field._id] = String(field.fieldValue); } }); return formFieldDict; diff --git a/app/models/form.server.model.js b/app/models/form.server.model.js index 6134bab8..b7992478 100644 --- a/app/models/form.server.model.js +++ b/app/models/form.server.model.js @@ -13,10 +13,6 @@ var mongoose = require('mongoose'), //Mongoose Models var FieldSchema = require('./form_field.server.model.js'); -var FormSubmissionSchema = require('./form_submission.server.model.js'), - FormSubmission = mongoose.model('FormSubmission', FormSubmissionSchema); - - var ButtonSchema = new Schema({ url: { type: String, @@ -97,18 +93,10 @@ var FormSchema = new Schema({ }, visitors: [VisitorDataSchema] }, - form_fields: { type: [FieldSchema], default: [] }, - submissions: { - type: [{ - type: Schema.Types.ObjectId, - ref: 'FormSubmission' - }], - default: [] - }, admin: { type: Schema.Types.ObjectId, ref: 'User', @@ -192,9 +180,9 @@ var FormSchema = new Schema({ } }, - hideFooter: { + showFooter: { type: Boolean, - default: false + default: true }, isLive: { @@ -240,142 +228,17 @@ FormSchema.plugin(timeStampPlugin, { useVirtual: false }); -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){ - if(form_fields){ - for(var i=0; i 0 ){ - - var modifiedSubmissions = []; - - async.forEachOfSeries(deletedIds, - function (deletedIdIndex, key, cb_id) { - - var deleted_id = old_ids[deletedIdIndex]; - //Find FormSubmissions that contain field with _id equal to 'deleted_id' - FormSubmission. - find({ form: that, form_fields: {$elemMatch: {globalId: deleted_id} } }). - exec(function(err, submissions){ - if(err) { - return cb_id(err); - } - - //Preserve fields that have at least one submission - if (submissions.length) { - //Add submissions - modifiedSubmissions.push.apply(modifiedSubmissions, submissions); - } - - return cb_id(null); - }); - }, - function (err) { - if(err){ - console.error(err.message); - return cb(err); - } - - //Iterate through all submissions with modified form_fields - async.forEachOfSeries(modifiedSubmissions, function (submission, key, callback) { - - var submission_form_fields = submission.toObject().form_fields; - var currentform_form_fields = that.toObject().form_fields; - - //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 + ''; - return tmp_id === old_ids[deletedIds[i]]; - }); - - var deletedField = submission_form_fields[index]; - - //Hide field if it exists - if (deletedField) { - - //Delete old form_field - submission_form_fields.splice(index, 1); - - deletedField.deletePreserved = true; - - //Move deleted form_field to start - submission_form_fields.unshift(deletedField); - currentform_form_fields.unshift(deletedField); - } - } - submission.form_fields = submission_form_fields; - that.form_fields = currentform_form_fields; - - return callback(null); - }, function (err) { - return cb(err); - }); - }); - } else { - return cb(null); - } - } else { - return cb(null); - } - } - ], - function(err){ - if(err){ - return next(err); - } - next(); - }); + if(this.form_fields && this.form_fields.length){ + this.form_fields = this.form_fields.filter(function(field){ + return !field.deletePreserved; + }); + } + next(); }); FormSchema.index({created: 1}); mongoose.model('Form', FormSchema); +module.exports = mongoose.model('Form'); \ No newline at end of file diff --git a/app/models/form_field.server.model.js b/app/models/form_field.server.model.js index c7a5e1fc..9338a9d7 100644 --- a/app/models/form_field.server.model.js +++ b/app/models/form_field.server.model.js @@ -49,13 +49,6 @@ function BaseFieldSchema(){ Schema.apply(this, arguments); this.add({ - newOptionSchema: { - type: Boolean, - default: false - }, - globalId: { - type: String, - }, isSubmission: { type: Boolean, default: false @@ -151,21 +144,13 @@ FormFieldSchema.pre('validate', function(next) { if(this.fieldOptions && this.fieldOptions.length > 0){ error.errors.ratingOptions = new mongoose.Error.ValidatorError({path:'fieldOptions', message: 'fieldOptions are only allowed for type dropdown, checkbox or radio fields.', type: 'notvalid', value: this.ratingOptions}); console.error(error); - return(next(error)); + return next(error); } } return next(); }); -//LogicJump Save -FormFieldSchema.pre('save', function(next) { - if(!this.globalId){ - this.globalId = tokgen(); - } - next(); -}); - //Submission fieldValue correction FormFieldSchema.pre('save', function(next) { if(this.fieldType === 'dropdown' && this.isSubmission){ diff --git a/app/models/form_submission.server.model.js b/app/models/form_submission.server.model.js index 6e77529f..a7f93bd5 100644 --- a/app/models/form_submission.server.model.js +++ b/app/models/form_submission.server.model.js @@ -6,7 +6,9 @@ var mongoose = require('mongoose'), Schema = mongoose.Schema, timeStampPlugin = require('../libs/timestamp.server.plugin'), - FieldSchema = require('./form_field.server.model.js'); + FieldSchema = require('./form_field.server.model'), + helpers = require('../controllers/helpers.server.controller'), + constants = require('../libs/constants'); /** * Form Submission Schema @@ -55,18 +57,7 @@ FormSubmissionSchema.pre('save', function (next) { this.form_fields[i].fieldValue = this.form_fields[i].fieldValue.option_value; } - delete this.form_fields[i].validFieldTypes; - delete this.form_fields[i].disabled; - delete this.form_fields[i].required; - delete this.form_fields[i].isSubmission; - delete this.form_fields[i].title; - delete this.form_fields[i].fieldOptions; - delete this.form_fields[i].ratingOptions; - delete this.form_fields[i].logicJump; - delete this.form_fields[i].description; - delete this.form_fields[i].created; - delete this.form_fields[i].lastModified; - delete this.form_fields[i].deletePreserved; + helpers.removeKeysFromDict(this.form_fields[i], constants.extraneousFormFieldProps); } next(); }); @@ -77,19 +68,7 @@ FormSubmissionSchema.path('form_fields', { form_fields[i].isSubmission = true; form_fields[i]._id = new mongoose.mongo.ObjectID(); - delete form_fields[i].validFieldTypes; - delete form_fields[i].disabled; - delete form_fields[i].required; - delete form_fields[i].isSubmission; - delete form_fields[i].title; - delete form_fields[i].fieldOptions; - delete form_fields[i].ratingOptions; - delete form_fields[i].logicJump; - delete form_fields[i].description; - delete form_fields[i].created; - delete form_fields[i].lastModified; - delete form_fields[i].deletePreserved; - + helpers.removeKeysFromDict(form_fields[i], constants.extraneousFormFieldProps); } return form_fields; } @@ -101,4 +80,6 @@ FormSubmissionSchema.plugin(timeStampPlugin, { useVirtual: false }); -module.exports = FormSubmissionSchema; +mongoose.model('FormSubmission', FormSubmissionSchema); + +module.exports = mongoose.model('FormSubmission'); \ No newline at end of file diff --git a/app/models/user.server.model.js b/app/models/user.server.model.js index fd80602d..d5484c72 100755 --- a/app/models/user.server.model.js +++ b/app/models/user.server.model.js @@ -38,7 +38,7 @@ var UserSchema = new Schema({ type: String, unique: true, lowercase: true, - match: [/^[a-zA-Z0-9\-]+$/, 'Username can only contain alphanumeric characters and \'-\''], + match: [constants.regex.username, 'Username can only contain alphanumeric characters and \'-\''], required: [true, 'Username is required'] }, passwordHash: { @@ -108,7 +108,7 @@ UserSchema.virtual('password').get(function () { /** * Create instance method for hashing a password */ -UserSchema.methods.hashPassword = function(password) { +UserSchema.statics.hashPassword = UserSchema.methods.hashPassword = function(password) { var encoding = 'base64'; var iterations = 10000; var keylen = 128; @@ -165,4 +165,6 @@ UserSchema.methods.isAdmin = function() { return false; }; -module.exports = mongoose.model('User', UserSchema); +mongoose.model('User', UserSchema); + +module.exports = mongoose.model('User'); \ No newline at end of file diff --git a/app/tests/form.server.model.test.js b/app/tests/form.server.model.test.js index 573e631b..c2c16a65 100644 --- a/app/tests/form.server.model.test.js +++ b/app/tests/form.server.model.test.js @@ -7,8 +7,8 @@ require('../../server.js'); */ var should = require('should'), mongoose = require('mongoose'), - User = mongoose.model('User'), - Form = mongoose.model('Form'); + User = require('../models/user.server.model.js'), + Form = require('../models/form.server.model.js'); /** * Globals @@ -40,8 +40,8 @@ describe('Form Model Unit Tests:', function() { language: 'en', form_fields: [ {'fieldType':'textfield', title:'First Name', 'fieldValue': ''}, - {'fieldType':'checkbox', title:'nascar', 'fieldValue': ''}, - {'fieldType':'checkbox', title:'hockey', 'fieldValue': ''} + {'fieldType':'legal', title:'nascar', 'fieldValue': ''}, + {'fieldType':'legal', title:'hockey', 'fieldValue': ''} ] }); done(); diff --git a/app/tests/form.server.routes.test.js b/app/tests/form.server.routes.test.js index a4c16889..b967a364 100644 --- a/app/tests/form.server.routes.test.js +++ b/app/tests/form.server.routes.test.js @@ -6,11 +6,26 @@ var should = require('should'), request = require('supertest'), Session = require('supertest-session'), mongoose = require('mongoose'), - User = mongoose.model('User'), - Form = mongoose.model('Form'), + User = require('../models/user.server.model.js'), + Form = require('../models/form.server.model.js'), + FormSubmission = require('../models/form_submission.server.model.js'), Field = mongoose.model('Field'), - FormSubmission = mongoose.model('FormSubmission'), - async = require('async'); + async = require('async'), + _ = require('lodash'); + +function omitDeep(collection, excludeKeys) { + + function omitFn(value) { + + if (value && typeof value === 'object') { + excludeKeys.forEach((key) => { + delete value[key]; + }); + } + } + + return _.cloneDeepWith(collection, omitFn); +} /** * Globals @@ -24,6 +39,18 @@ var credentials = { password: 'password' }; +var sampleVisitorData = [{ + socketId: 'ntneooe8989eotnoeeo', + referrer: 'http://google.com', + timeElapsed: 89898989, + isSubmitted: true, + language: 'en', + ipAddr: '192.168.1.1', + deviceType: 'desktop', + userAgent: 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36', + filledOutFields: [] +}]; + /** * Form routes tests */ @@ -50,8 +77,8 @@ describe('Form Routes Unit tests', function() { admin: user.id, form_fields: [ new Field({'fieldType':'textfield', 'title':'First Name', 'fieldValue': ''}), - new Field({'fieldType':'checkbox', 'title':'nascar', 'fieldValue': ''}), - new Field({'fieldType':'checkbox', 'title':'hockey', 'fieldValue': ''}) + new Field({'fieldType':'legal', 'title':'nascar', 'fieldValue': ''}), + new Field({'fieldType':'legal', 'title':'hockey', 'fieldValue': ''}) ], isLive: true }; @@ -91,7 +118,7 @@ describe('Form Routes Unit tests', function() { FormObj.save(function(err, form) { if(err) return done(err); - userSession.get('/subdomain/' + credentials.username + '/forms/' + form._id + '/render') + userSession.get('/forms/' + form._id + '/render') .expect(200) .end(function(err, res) { if(err) return done(err) @@ -114,7 +141,7 @@ describe('Form Routes Unit tests', function() { FormObj.save(function(err, form) { if(err) return done(err); - userSession.get('/subdomain/' + credentials.username + '/forms/' + form._id + '/render') + userSession.get('/forms/' + form._id + '/render') .expect(401, {message: 'Form is Not Public'}) .end(function(err, res) { done(err); @@ -315,8 +342,8 @@ describe('Form Routes Unit tests', function() { admin: user.id, form_fields: [ new Field({'fieldType':'textfield', 'title':'First Name', 'fieldValue': ''}), - new Field({'fieldType':'checkbox', 'title':'nascar', 'fieldValue': ''}), - new Field({'fieldType':'checkbox', 'title':'hockey', 'fieldValue': ''}) + new Field({'fieldType':'legal', 'title':'nascar', 'fieldValue': ''}), + new Field({'fieldType':'legal', 'title':'hockey', 'fieldValue': ''}) ], isLive: true }; @@ -327,8 +354,8 @@ describe('Form Routes Unit tests', function() { admin: user.id, form_fields: [ new Field({'fieldType':'textfield', 'title':'Last Name', 'fieldValue': ''}), - new Field({'fieldType':'checkbox', 'title':'formula one', 'fieldValue': ''}), - new Field({'fieldType':'checkbox', 'title':'football', 'fieldValue': ''}) + new Field({'fieldType':'legal', 'title':'formula one', 'fieldValue': ''}), + new Field({'fieldType':'legal', 'title':'football', 'fieldValue': ''}) ], isLive: true }; @@ -364,6 +391,123 @@ describe('Form Routes Unit tests', function() { }); }); + it(' > should preserve visitor data when updating a Form', function(done) { + // Create new Form model instance + + var formObject = { + title: 'First Form', + language: 'en', + admin: user.id, + form_fields: [ + new Field({'fieldType':'textfield', 'title':'First Name', 'fieldValue': ''}), + new Field({'fieldType':'legal', 'title':'nascar', 'fieldValue': ''}), + new Field({'fieldType':'legal', 'title':'hockey', 'fieldValue': ''}) + ], + isLive: true, + analytics: { + gaCode: '', + visitors: sampleVisitorData + } + }; + + var formUpdateObject = { + title: 'Second Form', + language: 'en', + admin: user.id, + form_fields: [ + new Field({'fieldType':'textfield', 'title':'Last Name', 'fieldValue': ''}), + new Field({'fieldType':'legal', 'title':'formula one', 'fieldValue': ''}), + new Field({'fieldType':'legal', 'title':'football', 'fieldValue': ''}) + ], + isLive: true + }; + + var CurrentForm = new Form(formObject); + + // Save the Form + CurrentForm.save(function(err, form) { + if(err) return done(err); + + loginSession.put('/forms/' + form.id) + .send({ form: formUpdateObject }) + .expect(200) + .end(function(err, res) { + + should.not.exist(err); + + Form.findById(form.id, function (FormFindErr, UpdatedForm){ + should.not.exist(FormFindErr); + should.exist(UpdatedForm); + + var updatedFormObj = UpdatedForm.toJSON(); + var oldFormObj = CurrentForm.toJSON(); + + updatedFormObj.analytics.should.deepEqual(oldFormObj.analytics); + + done(FormFindErr); + }); + }); + }); + }); + + it(' > shouldn\'t allow a user to change the id when updating a form', function(done) { + // Create new Form model instance + + var formObject = { + title: 'First Form', + language: 'en', + admin: user.id, + form_fields: [ + new Field({'fieldType':'textfield', 'title':'First Name', 'fieldValue': ''}), + new Field({'fieldType':'legal', 'title':'nascar', 'fieldValue': ''}), + new Field({'fieldType':'legal', 'title':'hockey', 'fieldValue': ''}) + ], + isLive: true + }; + + var formUpdateObject = { + id: mongoose.Types.ObjectId(), + title: 'First Form', + language: 'en', + admin: user.id, + form_fields: [ + new Field({'fieldType':'textfield', 'title':'Last Name', 'fieldValue': ''}), + new Field({'fieldType':'legal', 'title':'formula one', 'fieldValue': ''}), + new Field({'fieldType':'legal', 'title':'football', 'fieldValue': ''}) + ], + isLive: true + }; + + var CurrentForm = new Form(formObject); + + // Save the Form + CurrentForm.save(function(err, InitialForm) { + if(err) return done(err); + + loginSession.put('/forms/' + InitialForm.id) + .send({ form: formUpdateObject }) + .expect(200) + .end(function(err, OldForm) { + should.not.exist(err); + + Form.findById(InitialForm.id, function (FormFindErr, UpdatedForm){ + should.not.exist(FormFindErr); + should.exist(UpdatedForm); + + var updatedFormObj = UpdatedForm.toJSON(); + var oldFormObj = InitialForm.toJSON(); + + updatedFormObj = omitDeep('lastModified'); + oldFormObj = omitDeep('lastModified'); + + updatedFormObj.should.deepEqual(oldFormObj); + + done(FormFindErr); + }); + }); + }); + }); + afterEach('should be able to signout user', function(done){ authenticatedSession.get('/auth/signout') .expect(200) diff --git a/app/tests/form_submission.model.test.js b/app/tests/form_submission.model.test.js index 262fbf04..d08851a0 100644 --- a/app/tests/form_submission.model.test.js +++ b/app/tests/form_submission.model.test.js @@ -11,7 +11,7 @@ var should = require('should'), _ = require('lodash'), async = require('async'), config = require('../../config/config'), - FormSubmission = mongoose.model('FormSubmission'); + FormSubmission = require('../models/form_submission.server.model.js'); var exampleDemo = { address: '880-9650 Velit. St.', @@ -166,7 +166,7 @@ describe('FormSubmission Model Unit Tests:', function() { }); it('should be able to find FormSubmission by $elemMatch on form_fields id', function(done){ - FormSubmission.findOne({ form: myForm._id, form_fields: {$elemMatch: {globalId: myForm.form_fields[0].globalId} } }) + FormSubmission.findOne({ form: myForm.id, form_fields: {$elemMatch: {_id: myForm.form_fields[0]._id} } }) .exec(function(err, submission){ should.not.exist(err); should.exist(submission); @@ -176,76 +176,6 @@ describe('FormSubmission Model Unit Tests:', function() { }); }); - describe('Test FormField and Submission Logic', function() { - - beforeEach(function(done){ - - //Create Submission - mySubmission = new FormSubmission({ - form_fields: _.merge(sampleSubmission, myForm.form_fields), - admin: user, - form: myForm, - timeElapsed: 17.55 - }); - - mySubmission.save(function(err){ - should.not.exist(err); - done(); - }); - - }); - - it('should preserve deleted form_fields that have submissions without any problems', function(done) { - - var fieldPropertiesToOmit = ['deletePreserved', 'globalId', 'lastModified', 'created', '_id', 'submissionId', 'isSubmission', 'validFieldTypes', 'title']; - var old_fields = myForm.toObject().form_fields; - var new_form_fields = _.clone(myForm.toObject().form_fields); - new_form_fields.splice(0, 1); - - myForm.form_fields = new_form_fields; - - myForm.save(function(err, _form) { - - should.not.exist(err); - should.exist(_form.form_fields); - - var actual_fields = _.deepOmit(_form.toObject().form_fields, fieldPropertiesToOmit); - old_fields = _.deepOmit(old_fields, fieldPropertiesToOmit); - - should.deepEqual(actual_fields, old_fields, 'old form_fields not equal to newly saved form_fields'); - done(); - }); - }); - - it('should delete \'preserved\' form_fields whose submissions have been removed without any problems', function(done) { - - var old_fields = myForm.toObject().form_fields; - old_fields.splice(0,1); - var new_form_fields = _.clone(myForm.toObject().form_fields); - new_form_fields.splice(0, 1); - - myForm.form_fields = new_form_fields; - - myForm.save(function(err, _form){ - should.not.exist(err); - should.exist(_form.form_fields); - should.exist(old_fields); - - var actual_fields = _.deepOmit(_form.toObject().form_fields, ['lastModified', 'created', '_id']); - old_fields = _.deepOmit(old_fields, ['lastModified', 'created', '_id']); - - should.deepEqual(JSON.stringify(actual_fields), JSON.stringify(old_fields)); //'old form_fields not equal to newly saved form_fields'); - done(); - }); - }); - - afterEach(function(done){ - mySubmission.remove(function(){ - done(); - }); - }); - }); - afterEach(function(done) { Form.remove().exec(function() { User.remove().exec(function() { diff --git a/app/tests/form_submission.routes.test.js b/app/tests/form_submission.routes.test.js index 6398828e..17c77ace 100644 --- a/app/tests/form_submission.routes.test.js +++ b/app/tests/form_submission.routes.test.js @@ -54,8 +54,8 @@ describe('Form Submission Routes Unit tests', function() { admin: user._id, form_fields: [ new Field({'fieldType':'textfield', 'title':'First Name', 'fieldValue': ''}), - new Field({'fieldType':'checkbox', 'title':'nascar', 'fieldValue': ''}), - new Field({'fieldType':'checkbox', 'title':'hockey', 'fieldValue': ''}) + new Field({'fieldType':'legal', 'title':'nascar', 'fieldValue': ''}), + new Field({'fieldType':'legal', 'title':'hockey', 'fieldValue': ''}) ], selfNotifications: { fromField: mongoose.Types.ObjectId(), @@ -81,8 +81,8 @@ describe('Form Submission Routes Unit tests', function() { form: form._id, form_fields: [ {'fieldType':'textfield', 'title':'First Name', 'fieldValue': 'David', _id: '', isSubmission: false, deletePreserved: false}, - {'fieldType':'checkbox', 'title':'nascar', 'fieldValue': true, _id: '', isSubmission: false, deletePreserved: true}, - {'fieldType':'checkbox', 'title':'hockey', 'fieldValue': false, _id: '', isSubmission: false, deletePreserved: false} + {'fieldType':'legal', 'title':'nascar', 'fieldValue': true, _id: '', isSubmission: false, deletePreserved: true}, + {'fieldType':'legal', 'title':'hockey', 'fieldValue': false, _id: '', isSubmission: false, deletePreserved: false} ], percentageComplete: 100, timeElapsed: 11.55, @@ -101,8 +101,8 @@ describe('Form Submission Routes Unit tests', function() { _id: form._id, form_fields: [ {'fieldType':'textfield', 'title':'First Name', 'fieldValue': 'David', _id: '', isSubmission: false, deletePreserved: false}, - {'fieldType':'checkbox', 'title':'nascar', 'fieldValue': true, _id: '', isSubmission: false, deletePreserved: true}, - {'fieldType':'checkbox', 'title':'hockey', 'fieldValue': false, _id: '', isSubmission: false, deletePreserved: false} + {'fieldType':'legal', 'title':'nascar', 'fieldValue': true, _id: '', isSubmission: false, deletePreserved: true}, + {'fieldType':'legal', 'title':'hockey', 'fieldValue': false, _id: '', isSubmission: false, deletePreserved: false} ], percentageComplete: 100, timeElapsed: 11.55, diff --git a/app/tests/libs/send-email-notifications.test.js b/app/tests/libs/send-email-notifications.test.js index 10f8d98f..772328ee 100644 --- a/app/tests/libs/send-email-notifications.test.js +++ b/app/tests/libs/send-email-notifications.test.js @@ -3,24 +3,26 @@ /** * Module dependencies. */ -const emailNotifications = require('../../libs/send-email-notifications'), - constants = require('../../libs/constants'), - mockTransport = require("nodemailer").createTransport("Stub"), +const should = require('should'), + emailNotifications = require('../../libs/send-email-notifications'), + mockTransport = require('nodemailer').createTransport({ + jsonTransport: true + }), config = require('../../../config/config'); /** * Globals */ const validFormFields = [ - {fieldType:'textfield', title:'First Name', fieldValue: 'John Smith', deletePreserved: false, globalId:'56340745f59a6fc9e22028e9'}, - {fieldType:'link', title:'Your Website', fieldValue: 'https://johnsmith.me', deletePreserved: false, globalId:'5c9e22028e907634f45f59a6'}, - {fieldType:'number', title:'Your Age', fieldValue: 45, deletePreserved: false, globalId:'56e90745f5934fc9e22028a6'} + {fieldType:'textfield', title:'First Name', fieldValue: 'John Smith', deletePreserved: false, _id:'56340745f59a6fc9e22028e9'}, + {fieldType:'link', title:'Your Website', fieldValue: 'https://johnsmith.me', deletePreserved: false, _id:'5c9e22028e907634f45f59a6'}, + {fieldType:'number', title:'Your Age', fieldValue: 45, deletePreserved: false, _id:'56e90745f5934fc9e22028a6'} ]; const validFieldDict = { '56340745f59a6fc9e22028e9': 'John Smith', '5c9e22028e907634f45f59a6': 'https://johnsmith.me', - '56e90745f5934fc9e22028a6': 45 + '56e90745f5934fc9e22028a6': '45' }; const invalidFormFields = [ @@ -29,13 +31,11 @@ const invalidFormFields = [ {fieldType:'number', title:'Your Age'} ]; -const htmlTemplate = '

First Name \ -
Your Website \ -
Your Age

'; +const htmlTemplate = '

First Name'+ + '
Your Website'+ + '
Your Age

'; -const renderedTemplate = '

John Smith \ -
https://johnsmith.me \ -
45

'; +const renderedTemplate = '

John Smith
https://johnsmith.me
45

'; /** * Unit tests @@ -56,36 +56,24 @@ describe('Send Email Notification Unit Tests', function() { describe('Method parseTemplate', function(){ it('should properly render a template given a valid field dict', function() { - var actualRenderedTemplate = emailNotifications.parseTemplate(htmlTemplate, validFieldDict, constants.varFormat).replace((/ |\r\n|\n|\r|\t/gm),''); + var actualRenderedTemplate = emailNotifications.parseTemplate(htmlTemplate, validFieldDict, false).replace((/ |\r\n|\n|\r|\t/gm),''); actualRenderedTemplate.should.equal(renderedTemplate.replace((/ |\r\n|\n|\r|\t/gm),'')); }); }); - describe('Method replaceTemplateVal', function() { - it('should properly replace a template var in a valid template', function() { - var expectedHtml = '

John Smith \ -
Your Website \ -
Your Age

'; - - var actualRenderedTemplate = emailNotifications.replaceTemplateVal('56340745f59a6fc9e22028e9', validFieldDict['56340745f59a6fc9e22028e9'], htmlTemplate, constants.varFormat).replace((/ |\r\n|\n|\r|\t/gm),''); - actualRenderedTemplate.should.equal(expectedHtml.replace((/ |\r\n|\n|\r|\t/gm),'')); - }); - }); - describe('Method send', function() { this.timeout(10000); const emailSettings = { fromEmails: 'somewhere@somewhere.com', toEmails: 'there@there.com', - subject: 'Hello First Name!', + subject: 'Hello First Name!', htmlTemplate: htmlTemplate }; const emailTemplateVars = validFieldDict; - const varFormat = constants.varFormat; it('should properly replace a template var in a valid template', function(done) { - emailNotifications.send(emailSettings, emailTemplateVars, mockTransport, varFormat, function(err){ + emailNotifications.send(emailSettings, emailTemplateVars, mockTransport, function(err){ should.not.exist(err); done(); }); diff --git a/app/tests/user.server.model.test.js b/app/tests/user.server.model.test.js index 5a7cb9ad..b15fb3b1 100755 --- a/app/tests/user.server.model.test.js +++ b/app/tests/user.server.model.test.js @@ -5,8 +5,8 @@ */ var should = require('should'), mongoose = require('mongoose'), - User = mongoose.model('User'); - + User = require('../models/user.server.model.js'); + /** * Globals */ diff --git a/app/tests/user.server.routes.test.js b/app/tests/user.server.routes.test.js index 7198d449..7ed5e006 100644 --- a/app/tests/user.server.routes.test.js +++ b/app/tests/user.server.routes.test.js @@ -4,7 +4,7 @@ var should = require('should'), app = require('../../server'), Session = require('supertest-session'), mongoose = require('mongoose'), - User = mongoose.model('User'), + User = require('../models/user.server.model.js'), config = require('../../config/config'), tmpUser = mongoose.model(config.tempUserCollection), async = require('async'); diff --git a/app/views/form.server.view.pug b/app/views/form.server.view.pug index 1a14773b..8893954d 100644 --- a/app/views/form.server.view.pug +++ b/app/views/form.server.view.pug @@ -2,24 +2,24 @@ doctype html html(lang='en', xmlns='http://www.w3.org/1999/xhtml') head title=title - // General META + // General META meta(charset='utf-8') meta(http-equiv='Content-type', content='text/html;charset=UTF-8') meta(http-equiv='X-UA-Compatible', content='IE=edge,chrome=1') meta(name='viewport', content='width=device-width,initial-scale=1,maximum-scale=1') meta(name='apple-mobile-web-app-capable', content='yes') meta(name='apple-mobile-web-app-status-bar-style', content='black') - // Semantic META + // Semantic META meta(name='keywords', content='keywords') meta(name='description', content='description') - // Facebook META + // Facebook META meta(property='og:site_name', content=title) meta(property='og:title', content=title) meta(property='og:description', content='description') meta(property='og:url', content='url') meta(property='og:image', content='/img/brand/logo.png') meta(property='og:type', content='website') - // Twitter META + // Twitter META meta(name='twitter:title', content=title) meta(name='twitter:description', content='description') meta(name='twitter:url', content='url') @@ -35,18 +35,18 @@ html(lang='en', xmlns='http://www.w3.org/1999/xhtml') background: url('/static/modules/core/img/loaders/page-loader.gif') 50% 35% no-repeat rgb(249,249,249); background-size: 50px 50px; } - // Fav Icon + // Fav Icon link(href='/static/modules/core/img/brand/favicon.ico', rel='shortcut icon', type='image/x-icon') body(ng-cloak='') .loader section.content section(ui-view='') - //Embedding The User Object signupDisabled, socketPort and socketUrl Boolean + //Embedding The User Object signupDisabled, socketPort and socketUrl Boolean script(type='text/javascript'). var signupDisabled = !{signupDisabled}; - var socketPort = false; - var socketUrl = false; + var socketPort = false; + var socketUrl = "ws.tellform.com"; var subdomainsDisabled = !{subdomainsDisabled}; //Embedding socketPort @@ -93,14 +93,14 @@ html(lang='en', xmlns='http://www.w3.org/1999/xhtml') // end Application Javascript dependencies if process.env.NODE_ENV === 'development' - //Livereload script rendered + //Livereload script rendered script(async='', type='text/javascript', src='http://#{request.hostname}:35729/livereload.js') //script Raven.config('https://825fefd6b4ed4a4da199c1b832ca845c@sentry.tellform.com/2').install(); - + if google_analytics_id script window.ga=function(){ga.q.push(arguments)};ga.q=[];ga.l=+new Date;ga('create','{{google_analytics_id}}','auto');ga('send','pageview') - + script(src='https://www.google-analytics.com/analytics.js', async='') script(type="text/javascript"). diff --git a/app/views/index.server.view.pug b/app/views/index.server.view.pug index 0d0c17fc..1c08be19 100644 --- a/app/views/index.server.view.pug +++ b/app/views/index.server.view.pug @@ -3,9 +3,9 @@ extends layout.server.view.pug block content section.content(ui-view='', ng-cloak='') - + link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/quill/1.3.4/quill.snow.min.css') + link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/quill/1.3.4/quill.bubble.min.css') link(rel='stylesheet', href='/static/lib/jquery-ui/themes/flick/jquery-ui.min.css') - script(src='/static/lib/jquery/jquery.min.js') //Embedding The User Object script(type='text/javascript'). @@ -44,6 +44,10 @@ block content script(type='text/javascript', src='https://cdnjs.cloudflare.com/ajax/libs/angular-strap/2.3.8/angular-strap.min.js') + script(src='https://cdnjs.cloudflare.com/ajax/libs/quill/1.3.4/quill.min.js') + script(src='https://cdnjs.cloudflare.com/ajax/libs/ng-quill/3.5.1/ng-quill.js') + script(src='https://unpkg.com/quill-placeholder-module@0.2.0/dist/placeholder-module.js') + //Application JavaScript Files each jsFile in jsFiles script(type='text/javascript', src=jsFile) diff --git a/bower.json b/bower.json index 7810660e..68349a13 100755 --- a/bower.json +++ b/bower.json @@ -13,16 +13,13 @@ "bootstrap": "^3.3.7", "angular-resource": "~1.4.7", "angular-cache-buster": "~0.4.3", - "angular-mocks": "~1.4.7", "angular-bootstrap": "~0.14.3", "angular-ui-utils": "~3.0.0", - "angular-ui-router": "~0.2.11", "ng-file-upload": "^12.0.4", "angular-raven": "~0.5.11", "angular-ui-date": "~0.0.11", "lodash": "~3.10.0", "angular-ui-sortable": "~0.13.4", - "angular-permission": "~1.1.1", "file-saver.js": "~1.20150507.2", "angular-bootstrap-colorpicker": "~3.0.19", "angular-scroll": "^1.0.0", @@ -44,18 +41,21 @@ "angular-ui-select": "^0.19.8", "angular-bootstrap-switch": "^0.5.2", "jquery": "^3.2.1", - "textAngular": "^1.5.16" + "ng-quill": "https://github.com/KillerCodeMonkey/ng-quill.git#master", + "angular-ui-router": "^1.0.11", + "angular-permission": "^5.3.2", + "angular-mocks": "^1.6.6", + "quill": "https://github.com/quilljs/quill/releases/download/v1.3.4/quill.tar.gz", + "jspdf": "^1.3.5" }, "resolutions": { "angular-bootstrap": "^0.14.0", - "angular": "1.4.14", - "jspdf": "~1.0.178", - "angular-sanitize": "1.4.14", - "angular-ui-sortable": "^0.17.1", - "angular-ui-date": "~0.0.11", - "angular-input-stars-directive": "master", - "angular-ui-select": "^0.19.8", - "jquery": "^3.2.1" + "jquery": "^3.2.1", + "angular-ui-router": "^1.0.11", + "angular": "1.6", + "angular-mocks": "^1.6.6", + "quill": "e-tag:792062a8d", + "jspdf": "1.1.239 || 1.3.2" }, "overrides": { "BOWER-PACKAGE": { diff --git a/config/env/all.js b/config/env/all.js index 35fe1db4..15ca1f6a 100755 --- a/config/env/all.js +++ b/config/env/all.js @@ -10,16 +10,14 @@ module.exports = { db: { uri: process.env.MONGOLAB_URI || process.env.MONGODB_URI || 'mongodb://'+ (process.env.DB_PORT_27017_TCP_ADDR || '127.0.0.1') + '/mean', options: { - user: '', - pass: '' + useMongoClient: true } }, - - - admin:{ + admin: { email: process.env.ADMIN_EMAIL || 'admin@admin.com', username: process.env.ADMIN_USERNAME || 'root', password: process.env.ADMIN_PASSWORD || 'root', + roles: ['user', 'admin'] }, redisUrl: process.env.REDIS_URL || 'redis://127.0.0.1:6379', @@ -103,18 +101,18 @@ module.exports = { 'public/config.js', 'public/application.js', 'public/dist/populate_template_cache.js', + 'public/dist/form_populate_template_cache.js', 'public/modules/*/*.js', 'public/modules/*/*/*.js', 'public/modules/*/*/*/*.js', 'public/modules/*/*/*/*/*.js', + '!public/modules/*/tests/**/*.js', 'public/form_modules/forms/*.js', 'public/form_modules/forms/directives/*.js', 'public/form_modules/forms/base/config/*.js', 'public/form_modules/forms/base/config/*/*.js', 'public/form_modules/forms/base/**/*.js', 'public/form_modules/forms/base/*/*.js', - '!public/modules/*/tests/**/*.js', - '!public/modules/*/tests/*.js' ], form_js: [ 'public/form-config.js', @@ -123,8 +121,7 @@ module.exports = { 'public/form_modules/forms/*.js', 'public/form_modules/forms/*/*.js', 'public/form_modules/forms/*/*/*.js', - 'public/form_modules/forms/*/*/*/*.js', - 'public/form_modules/forms/**.js', + 'public/form_modules/forms/**/*.js', '!public/form_modules/**/tests/**/*.js' ], views: [ diff --git a/config/env/development.js b/config/env/development.js index a62673f6..7335664b 100755 --- a/config/env/development.js +++ b/config/env/development.js @@ -6,8 +6,7 @@ module.exports = { db: { uri: process.env.MONGODB_URI || 'mongodb://'+( process.env.DB_PORT_27017_TCP_ADDR || '127.0.0.1') +'/mean', options: { - user: '', - pass: '' + useMongoClient: true } }, log: { diff --git a/config/env/production.js b/config/env/production.js index 08ebc529..5dfc8ee0 100755 --- a/config/env/production.js +++ b/config/env/production.js @@ -4,6 +4,9 @@ module.exports = { baseUrl: process.env.BASE_URL || process.env.HEROKU_APP_NAME + '.herokuapp.com' || 'tellform.com', db: { uri: process.env.MONGODB_URI || process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://' + (process.env.DB_PORT_27017_TCP_ADDR || '127.0.0.1') + '/mean', + options: { + useMongoClient: true + } }, port: process.env.PORT || 5000, socketUrl: process.env.SOCKET_URL || 'ws.tellform.com', diff --git a/config/env/test.js b/config/env/test.js index d3a02f24..cddd377a 100755 --- a/config/env/test.js +++ b/config/env/test.js @@ -5,8 +5,7 @@ module.exports = { db: { uri: 'mongodb://localhost/mean-test', options: { - user: '', - pass: '' + useMongoClient: true } }, port: 3001, @@ -19,6 +18,7 @@ module.exports = { //stream: 'access.log' } }, + subdomainsDisabled: true, app: { title: 'TellForm Test' }, diff --git a/config/express.js b/config/express.js index e0685c2b..5b87f8e0 100755 --- a/config/express.js +++ b/config/express.js @@ -256,7 +256,7 @@ module.exports = function(db) { resave: true, secret: config.sessionSecret, store: new MongoStore({ - mongooseConnection: db.connection, + mongooseConnection: mongoose.connection, collection: config.sessionCollection }), cookie: config.sessionCookie, diff --git a/config/passport_helpers.js b/config/passport_helpers.js index 0543dff3..9d5ce00a 100644 --- a/config/passport_helpers.js +++ b/config/passport_helpers.js @@ -21,7 +21,6 @@ module.exports.isAuthenticatedOrApiKey = function isAuthenticated(req, res, next } if (!user) { - console.log('no user for apikey'); return res.status(401).send(info.message || ''); } diff --git a/docker-compose.yaml b/docker-compose.yaml index c31c0850..c06dd64f 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -7,7 +7,7 @@ services: volumes: - .:/code ports: - - "5000:5000" + - "5000" - 587:587 env_file: - .env diff --git a/gruntfile.js b/gruntfile.js index a4c0b83e..75868a9f 100755 --- a/gruntfile.js +++ b/gruntfile.js @@ -18,16 +18,21 @@ var bowerArray = ['public/lib/angular/angular.min.js', 'public/lib/js-yaml/dist/js-yaml.js', 'public/lib/angular-sanitize/angular-sanitize.min.js']; +const bowerFiles = require('main-bower-files'); +const bowerDep = bowerFiles('**/**.js'); + module.exports = function(grunt) { require('jit-grunt')(grunt); + var angularTestDeps = ['public/lib/angular/angular.js', 'public/lib/angular-mocks/angular-mocks.js']; + // Unified Watch Object var watchFiles = { serverViews: ['app/views/**/*.pug'], serverJS: ['gruntfile.js', 'server.js', 'config/**/*.js', 'app/**/*.js', '!app/tests/'], clientViews: ['public/modules/**/*.html', 'public/form_modules/forms/base/**/*.html', '!public/modules/forms/base/**/*.html',], - clientJS: ['public/form_modules/**/*.js', 'public/modules/**/*.js'], + clientJS: ['public/config.js', 'public/form-config.js', 'public/application.js', 'public/form-application.js', 'public/form_modules/**[!tests]/*.js', 'public/modules/**[!tests]/*.js'], clientCSS: ['public/modules/**/*.css'], serverTests: ['app/tests/**/*.js'], @@ -201,7 +206,7 @@ module.exports = function(grunt) { level: 'log', terminal: true }, - singleRun: true + singleRun: false } }, mocha_istanbul: { @@ -219,7 +224,7 @@ module.exports = function(grunt) { options: { emitters: ['event'], }, - src: ['./coverageServer/*.info', './coverageClient/lcov-report/*.info'] + src: ['./coverageServer/*.info', './coverageClient/**/*.info'] }, html2js: { options: { diff --git a/karma.conf.js b/karma.conf.js index c3c2661a..979f49ea 100755 --- a/karma.conf.js +++ b/karma.conf.js @@ -18,7 +18,7 @@ module.exports = function(config) { frameworks: ['jasmine'], // List of files / patterns to load in the browser - files: bowerDep.concat(['public/lib/socket.io-client/dist/socket.io.js', 'public/lib/mobile-detect/mobile-detect.js'], applicationConfiguration.assets.js, applicationConfiguration.assets.views, applicationConfiguration.assets.unit_tests), + files: bowerDep.concat(['public/lib/socket.io-client/dist/socket.io.js', 'public/lib/mobile-detect/mobile-detect.js', 'public/lib/quill/quill.js', 'public/lib/ng-quill/src/ng-quill.js'], applicationConfiguration.assets.js, applicationConfiguration.assets.views, applicationConfiguration.assets.unit_tests), // Test results reporter to use // Possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' @@ -31,14 +31,16 @@ module.exports = function(config) { 'public/form_modules/forms/base/views/**/*.html': ['ng-html2js'], 'public/form_modules/forms/base/views/*.html': ['ng-html2js'], 'public/modules/*/*.js': ['coverage'], - 'public/modules/*/*[!tests]*/*.js': ['coverage'] + 'public/modules/*/*[!tests]*/*.js': ['coverage'], + 'public/form_modules/*/*.js': ['coverage'], + 'public/form_modules/*/*[!tests]*/*.js': ['coverage'] }, // configure coverage reporter coverageReporter: { reporters: [ - { type: 'html', subdir: 'report-html' }, - { type: 'lcov', subdir: 'report-lcov' }, + //{ type: 'html', subdir: 'report-html' }, + { type: 'lcov' }, ], dir : 'coverageClient/' }, diff --git a/public/application.js b/public/application.js index a99ca02d..251dc6ba 100755 --- a/public/application.js +++ b/public/application.js @@ -25,6 +25,9 @@ angular.module(ApplicationConfiguration.applicationModuleName).constant('USER_RO superuser: 'superuser' }); +//users url +angular.module(ApplicationConfiguration.applicationModuleName).constant('USERS_URL', '/users'); + //form url angular.module(ApplicationConfiguration.applicationModuleName).constant('FORM_URL', '/forms/:formId'); diff --git a/public/form_modules/forms/base/config/forms.client.config.js b/public/form_modules/forms/base/config/forms.client.config.js index a6e45ff4..efd9ee37 100644 --- a/public/form_modules/forms/base/config/forms.client.config.js +++ b/public/form_modules/forms/base/config/forms.client.config.js @@ -28,7 +28,8 @@ angular.module('view-form') } return 0; }; -}).value('supportedFields', [ +}) +.value('supportedFields', [ 'textfield', 'textarea', 'date', @@ -42,7 +43,14 @@ angular.module('view-form') 'yes_no', 'number', 'natural' -]).constant('VIEW_FORM_URL', '/forms/:formId/render'); +]) +.constant('VIEW_FORM_URL', '/forms/:formId/render') +.filter('indexToAlphabet', function(){ + return function(index){ + var char = String.fromCharCode(index + 65); + return char; + }; +}) //Angular-Scroll Settings angular.module('view-form').value('duScrollActiveClass', 'activeField') diff --git a/public/form_modules/forms/base/config/i18n/english.js b/public/form_modules/forms/base/config/i18n/english.js index fe12abaf..9941d5ae 100644 --- a/public/form_modules/forms/base/config/i18n/english.js +++ b/public/form_modules/forms/base/config/i18n/english.js @@ -34,6 +34,9 @@ angular.module('view-form').config(['$translateProvider', function ($translatePr ADD_NEW_LINE_INSTR: 'Press SHIFT+ENTER to add a newline', ERROR: 'Error', + LOADING_LABEL: 'Loading', + WAIT_LABEL: 'Please wait', + FORM_404_HEADER: '404 - Form Does Not Exist', FORM_404_BODY: 'The form you are trying to access does not exist. Sorry about that!', diff --git a/public/form_modules/forms/base/config/i18n/french.js b/public/form_modules/forms/base/config/i18n/french.js index 2db54e34..0d2bca0c 100644 --- a/public/form_modules/forms/base/config/i18n/french.js +++ b/public/form_modules/forms/base/config/i18n/french.js @@ -34,11 +34,14 @@ angular.module('view-form').config(['$translateProvider', function ($translatePr ADD_NEW_LINE_INSTR: 'Appuyez sur MAJ + ENTRÉE pour ajouter une nouvelle ligne', ERROR: 'Erreur', + LOADING_LABEL: 'Chargement', + WAIT_LABEL: "Veuillez patienter", + FORM_404_HEADER: '404 - Le formulaire n\'existe pas', FORM_404_BODY: 'Le formulaire auquel vous essayez d\'accéder n\'existe pas. Désolé pour ça !', FORM_UNAUTHORIZED_HEADER: 'Non autorisé à accéder au formulaire', -   FORM_UNAUTHORIZED_BODY1: 'Le formulaire auquel vous essayez d\'accéder est actuellement privé et inaccessible publiquement.', +   FORM_UNAUTHORIZED_BODY1: 'Le formulaire auquel vous essayez d\'accéder est actuellement privé et inaccessible publiquement.',    FORM_UNAUTHORIZED_BODY2: 'Si vous êtes le propriétaire du formulaire, vous pouvez le définir en "Public" dans le panneau "Configuration" du formulaire admin.', }); diff --git a/public/form_modules/forms/base/config/i18n/german.js b/public/form_modules/forms/base/config/i18n/german.js index df335ce5..f9d53732 100644 --- a/public/form_modules/forms/base/config/i18n/german.js +++ b/public/form_modules/forms/base/config/i18n/german.js @@ -33,13 +33,16 @@ angular.module('view-form').config(['$translateProvider', function ($translatePr OPTION_PLACEHOLDER: 'Geben oder wählen Sie eine Option aus', ADD_NEW_LINE_INSTR: 'Drücken Sie UMSCHALT + EINGABETASTE, um eine neue Zeile hinzuzufügen', ERROR: 'Fehler', + + LOADING_LABEL: 'Laden', + WAIT_LABEL: 'Bitte warten', FORM_404_HEADER: '404 - Formular existiert nicht', FORM_404_BODY: 'Das Formular, auf das Sie zugreifen möchten, existiert nicht. Das tut mir leid!', FORM_UNAUTHORIZED_HEADER: 'Nicht zum Zugriffsformular berechtigt\' ', -   FORM_UNAUTHORIZED_BODY1: 'Das Formular, auf das Sie zugreifen möchten, ist derzeit privat und nicht öffentlich zugänglich.', -   FORM_UNAUTHORIZED_BODY2: 'Wenn Sie der Eigentümer des Formulars sind, können Sie es im Fenster "Konfiguration" im Formular admin auf "Öffentlich" setzen.', +   FORM_UNAUTHORIZED_BODY1: 'Das Formular, auf das Sie zugreifen möchten, ist derzeit privat und nicht öffentlich zugänglich.', +   FORM_UNAUTHORIZED_BODY2: 'Wenn Sie der Eigentümer des Formulars sind, können Sie es im Fenster "Konfiguration" im Formular admin auf "Öffentlich" setzen.', }); }]); diff --git a/public/form_modules/forms/base/config/i18n/italian.js b/public/form_modules/forms/base/config/i18n/italian.js index 62058a2e..20f34143 100644 --- a/public/form_modules/forms/base/config/i18n/italian.js +++ b/public/form_modules/forms/base/config/i18n/italian.js @@ -33,6 +33,9 @@ angular.module('view-form').config(['$translateProvider', function ($translatePr OPTION_PLACEHOLDER: 'Digitare o selezionare un\'opzione', ADD_NEW_LINE_INSTR: 'Premere SHIFT + INVIO per aggiungere una nuova riga', ERROR: 'Errore', + + LOADING_LABEL: 'Caricamento', + WAIT_LABEL: "Attendere prego", FORM_404_HEADER: '404 - Il modulo non esiste', FORM_404_BODY: 'La forma che stai cercando di accedere non esiste. Ci dispiace!', diff --git a/public/form_modules/forms/base/config/i18n/spanish.js b/public/form_modules/forms/base/config/i18n/spanish.js index 86861eb3..90c0b8ca 100644 --- a/public/form_modules/forms/base/config/i18n/spanish.js +++ b/public/form_modules/forms/base/config/i18n/spanish.js @@ -34,6 +34,9 @@ angular.module('view-form').config(['$translateProvider', function ($translatePr ADD_NEW_LINE_INSTR: 'Presione MAYÚS + ENTRAR para agregar una nueva línea', ERROR: 'Error', + LOADING_LABEL: 'Cargando', + WAIT_LABEL: 'Espera', + FORM_404_HEADER: '404 - La forma no existe', FORM_404_BODY: 'El formulario al que intenta acceder no existe. ¡Lo siento por eso!', diff --git a/public/form_modules/forms/base/directives/on-enter-key.client.directive.js b/public/form_modules/forms/base/directives/on-enter-key.client.directive.js index eb4e5cfd..c90fd5ac 100644 --- a/public/form_modules/forms/base/directives/on-enter-key.client.directive.js +++ b/public/form_modules/forms/base/directives/on-enter-key.client.directive.js @@ -65,8 +65,6 @@ angular.module('view-form').directive('onEnterKey', ['$rootScope', function($roo var keyCode = event.which || event.keyCode; if(keyCode === 9 && event.shiftKey) { - - console.log('onTabAndShiftKey'); event.preventDefault(); $rootScope.$apply(function() { $rootScope.$eval($attrs.onTabAndShiftKey); diff --git a/public/form_modules/forms/base/directives/submit-form.client.directive.js b/public/form_modules/forms/base/directives/submit-form.client.directive.js index 4e9b2b1d..244f8226 100644 --- a/public/form_modules/forms/base/directives/submit-form.client.directive.js +++ b/public/form_modules/forms/base/directives/submit-form.client.directive.js @@ -13,44 +13,37 @@ angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCoun function ($http, TimeCounter, $filter, $rootScope, SendVisitorData, $translate, $timeout) { return { templateUrl: 'form_modules/forms/base/views/directiveViews/form/submit-form.client.view.html', - restrict: 'E', + restrict: 'E', scope: { - myform:'=', - ispreview: '=' + myform: '=' }, controller: function($document, $window, $scope){ - var FORM_ACTION_ID = 'submit_field'; + var FORM_ACTION_ID = 'submit_field'; $scope.forms = {}; - //Don't start timer if we are looking at a design preview - if($scope.ispreview){ - TimeCounter.restartClock(); + var form_fields_count = $scope.myform.visible_form_fields.filter(function(field){ + return field.fieldType !== 'statement'; + }).length; + + $scope.$watch('myform', function(oldVal, newVal){ + $scope.myform.visible_form_fields = $scope.myform.form_fields.filter(function(field){ + return !field.deletePreserved; + }); + }); + + $scope.updateFormValidity = function(){ + $timeout(function(){ + var nb_valid = $scope.myform.form_fields.filter(function(field){ + return (field.fieldType === 'statement' || field.fieldValue !== '' || !field.required); + }).length; + $scope.translateAdvancementData = { + done: nb_valid, + total: $scope.myform.visible_form_fields.length + }; + }); } - var form_fields_count = $scope.myform.visible_form_fields.filter(function(field){ - return field.fieldType !== 'statement'; - }).length; - - $scope.$watch('myform', function(oldVal, newVal){ - $scope.myform.visible_form_fields = $scope.myform.form_fields.filter(function(field){ - return !field.deletePreserved; - }); - console.log($scope.myform.visible_form_fields); - }) - - $scope.updateFormValidity = function(){ - $timeout(function(){ - var nb_valid = $scope.myform.form_fields.filter(function(field){ - return (field.fieldType === 'statement' || field.fieldValue !== '' || !field.required); - }).length; - $scope.translateAdvancementData = { - done: nb_valid, - total: $scope.myform.visible_form_fields.length - }; - }); - } - - $scope.updateFormValidity(); + $scope.updateFormValidity(); $scope.reloadForm = function(){ //Reset Form @@ -60,7 +53,7 @@ angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCoun return field; }).value(); - $scope.loading = false; + $scope.loading = false; $scope.error = ''; $scope.selected = { @@ -76,163 +69,179 @@ angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCoun /* ** Field Controls */ - var evaluateLogicJump = function(field){ - var logicJump = field.logicJump; + var evaluateLogicJump = function(field){ + var logicJump = field.logicJump; - if(logicJump.enabled){ - if (logicJump.expressionString && logicJump.valueB && field.fieldValue) { - var parse_tree = jsep(logicJump.expressionString); - var left, right; + if(logicJump.enabled){ + if (logicJump.expressionString && logicJump.valueB && field.fieldValue) { + var parse_tree = jsep(logicJump.expressionString); + var left, right; - if(parse_tree.left.name === 'field'){ - left = field.fieldValue; - right = logicJump.valueB; - } else { - left = logicJump.valueB; - right = field.fieldValue; - } + if(parse_tree.left.name === 'field'){ + left = field.fieldValue; + right = logicJump.valueB; + } else { + left = logicJump.valueB; + right = field.fieldValue; + } - if(field.fieldType === 'number' || field.fieldType === 'scale' || field.fieldType === 'rating'){ - switch(parse_tree.operator) { - case '==': - return (parseInt(left) === parseInt(right)); - case '!==': - return (parseInt(left) !== parseInt(right)); - case '>': - return (parseInt(left) > parseInt(right)); - case '>=': - return (parseInt(left) > parseInt(right)); - case '<': - return (parseInt(left) < parseInt(right)); - case '<=': - return (parseInt(left) <= parseInt(right)); - default: - return false; - } - } else { - switch(parse_tree.operator) { - case '==': - return (left === right); - case '!==': - return (left !== right); - case 'contains': - return (left.indexOf(right) > -1); - case '!contains': - /* jshint -W018 */ - return !(left.indexOf(right) > -1); - case 'begins': - return left.startsWith(right); - case '!begins': - return !left.startsWith(right); - case 'ends': - return left.endsWith(right); - case '!ends': - return left.endsWith(right); - default: - return false; - } - } - } - } - }; + if(field.fieldType === 'number' || field.fieldType === 'scale' || field.fieldType === 'rating'){ + switch(parse_tree.operator) { + case '==': + return (parseInt(left) === parseInt(right)); + case '!==': + return (parseInt(left) !== parseInt(right)); + case '>': + return (parseInt(left) > parseInt(right)); + case '>=': + return (parseInt(left) > parseInt(right)); + case '<': + return (parseInt(left) < parseInt(right)); + case '<=': + return (parseInt(left) <= parseInt(right)); + default: + return false; + } + } else { + switch(parse_tree.operator) { + case '==': + return (left === right); + case '!==': + return (left !== right); + case 'contains': + return (left.indexOf(right) > -1); + case '!contains': + /* jshint -W018 */ + return !(left.indexOf(right) > -1); + case 'begins': + return left.startsWith(right); + case '!begins': + return !left.startsWith(right); + case 'ends': + return left.endsWith(right); + case '!ends': + return left.endsWith(right); + default: + return false; + } + } + } + } + }; - var getActiveField = function(){ - if($scope.selected === null){ - console.error('current active field is null'); - throw new Error('current active field is null'); - } + $rootScope.getActiveField = function(){ + if($scope.selected === null){ + console.error('current active field is null'); + throw new Error('current active field is null'); + } - if($scope.selected._id === FORM_ACTION_ID) { - return $scope.myform.form_fields.length - 1; - } - return $scope.selected.index; - }; + if($scope.selected._id === FORM_ACTION_ID) { + return $scope.myform.visible_form_fields[$scope.selected.index - 1]._id; + } + return $scope.selected._id; + }; $scope.setActiveField = $rootScope.setActiveField = function(field_id, field_index, animateScroll) { - if($scope.selected === null || (!field_id && field_index === null) ) { - return; + if(!field_id && field_index === null) { + return; } - - if(field_id === FORM_ACTION_ID){ - field_index = $scope.myform.visible_form_fields.length; - } else if(!field_id) { - field_id = $scope.myform.visible_form_fields[field_index]._id; - } else if(field_index === null){ - field_index = $scope.myform.visible_form_fields.length + + if(field_id === FORM_ACTION_ID){ + field_index = $scope.myform.visible_form_fields.length; + } else if(!field_id) { + field_id = $scope.myform.visible_form_fields[field_index]._id; + } else if(field_index === null){ + field_index = $scope.myform.visible_form_fields.length - for(var i=0; i < $scope.myform.visible_form_fields.length; i++){ - var currField = $scope.myform.visible_form_fields[i]; - if(currField['_id'] == field_id){ - field_index = i; - break; - } - } - } - - if($scope.selected._id === field_id){ - return; - } + for(var i=0; i < $scope.myform.visible_form_fields.length; i++){ + var currField = $scope.myform.visible_form_fields[i]; + if(currField['_id'] == field_id){ + field_index = i; + break; + } + } + } + if(!$scope.selected){ + $scope.selected = { + _id: '', + index: 0 + } + } + if($scope.selected._id === field_id){ + return; + } + $scope.selected._id = field_id; $scope.selected.index = field_index; if(animateScroll){ - $document.scrollToElement(angular.element('#'+field_id), -10, 50).then(function() { - if (angular.element('#'+field_id+' .focusOn').length) { - //Handle default case - angular.element('#'+field_id+' .focusOn')[0].focus(); - } else if(angular.element('#'+field_id+' input').length) { - //Handle case for rating input - angular.element('#'+field_id+' input')[0].focus(); - } else { - //Handle case for dropdown input - angular.element('#'+field_id+'.selectize-input')[0].focus(); - } + $document.scrollToElement(angular.element('#'+field_id), -10, 300).then(function() { + if (angular.element('#'+field_id+' .focusOn').length) { + //Handle default case + angular.element('#'+field_id+' .focusOn')[0].focus(); + } else if(angular.element('#'+field_id+' input').length) { + //Handle case for rating input + angular.element('#'+field_id+' input')[0].focus(); + } else { + //Handle case for dropdown input + angular.element('#'+field_id+'.selectize-input')[0].focus(); + } }); - } + } else { + if (angular.element('#'+field_id+' .focusOn').length) { + //Handle default case + angular.element('#'+field_id+' .focusOn')[0].focus(); + } else if(angular.element('#'+field_id+' input').length) { + //Handle case for rating input + angular.element('#'+field_id+' input')[0].focus(); + } else if(angular.element('#'+field_id+'.selectize-input').length) { + //Handle case for dropdown input + angular.element('#'+field_id+'.selectize-input')[0].focus(); + } + } }; $rootScope.$on('duScrollspy:becameActive', function($event, $element, $target){ - $scope.setActiveField($element.prop('id'), null, false); - $scope.updateFormValidity(); - $scope.$apply() - if(!$scope.myform.submitted){ - SendVisitorData.send($scope.myform, getActiveField(), TimeCounter.getTimeElapsed()); - } + $scope.setActiveField($element.prop('id'), null, false); + $scope.updateFormValidity(); + $scope.$apply() + if(!$scope.myform.submitted){ + SendVisitorData.send($scope.myform, $rootScope.getActiveField(), TimeCounter.getTimeElapsed()); + } }); $rootScope.nextField = $scope.nextField = function(){ - if($scope.selected && $scope.selected.index > -1){ - - if($scope.selected._id !== FORM_ACTION_ID){ - var currField = $scope.myform.visible_form_fields[$scope.selected.index]; - - //Jump to logicJump's destination if it is true - if(currField.logicJump && currField.logicJump.jumpTo && evaluateLogicJump(currField)){ - $scope.setActiveField(currField.logicJump.jumpTo, null, true); - } else if($scope.selected.index < $scope.myform.visible_form_fields.length-1){ - $scope.setActiveField(null, $scope.selected.index+1, true); - } else { - $scope.setActiveField(FORM_ACTION_ID, null, true); - } - } - } else { - //If selected is not defined go to the first field - $scope.setActiveField(null, 0, true); - } + if($scope.selected && $scope.selected.index > -1){ + if($scope.selected._id !== FORM_ACTION_ID){ + var currField = $scope.myform.visible_form_fields[$scope.selected.index]; + + //Jump to logicJump's destination if it is true + if(currField.logicJump && currField.logicJump.jumpTo && evaluateLogicJump(currField)){ + $scope.setActiveField(currField.logicJump.jumpTo, null, true); + } else if($scope.selected.index < $scope.myform.visible_form_fields.length-1){ + $scope.setActiveField(null, $scope.selected.index+1, true); + } else { + $scope.setActiveField(FORM_ACTION_ID, null, true); + } + } + } else { + //If selected is not defined go to the first field + $scope.setActiveField(null, 0, true); + } }; $rootScope.prevField = $scope.prevField = function(){ - var selected_index = $scope.selected.index - 1; + var selected_index = $scope.selected.index - 1; if($scope.selected.index > 0){ $scope.setActiveField(null, selected_index, true); } }; $rootScope.goToInvalid = $scope.goToInvalid = function() { - var field_id = $('.ng-invalid, .ng-untouched').first().parents('.row.field-directive').first().attr('id'); - $scope.setActiveField(field_id, null, true); - }; + var field_id = $('.ng-invalid, .ng-untouched').first().parents('.row.field-directive').first().attr('id'); + $scope.setActiveField(field_id, null, true); + }; /* ** Form Display Functions @@ -244,98 +253,98 @@ angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCoun } }; - var getDeviceData = function(){ - var md = new MobileDetect(window.navigator.userAgent); - var deviceType = 'other'; + var getDeviceData = function(){ + var md = new MobileDetect(window.navigator.userAgent); + var deviceType = 'other'; - if (md.tablet()){ - deviceType = 'tablet'; - } else if (md.mobile()) { - deviceType = 'mobile'; - } else if (!md.is('bot')) { - deviceType = 'desktop'; - } + if (md.tablet()){ + deviceType = 'tablet'; + } else if (md.mobile()) { + deviceType = 'mobile'; + } else if (!md.is('bot')) { + deviceType = 'desktop'; + } - return { - type: deviceType, - name: window.navigator.platform - }; - }; + return { + type: deviceType, + name: window.navigator.platform + }; + }; - var getIpAndGeo = function(){ - //Get Ip Address and GeoLocation Data - $.ajaxSetup( { 'async': false } ); - var geoData = $.getJSON('https://freegeoip.net/json/').responseJSON; - $.ajaxSetup( { 'async': true } ); + var getIpAndGeo = function(){ + //Get Ip Address and GeoLocation Data + $.ajaxSetup( { 'async': false } ); + var geoData = $.getJSON('https://freegeoip.net/json/').responseJSON; + $.ajaxSetup( { 'async': true } ); - if(!geoData || !geoData.ip){ - geoData = { - ip: 'Adblocker' - }; - } + if(!geoData || !geoData.ip){ + geoData = { + ip: 'Adblocker' + }; + } - return { - ipAddr: geoData.ip, - geoLocation: { - City: geoData.city, - Country: geoData.country_name - } - }; - }; + return { + ipAddr: geoData.ip, + geoLocation: { + City: geoData.city, + Country: geoData.country_name + } + }; + }; - $rootScope.submitForm = $scope.submitForm = function() { - if($scope.forms.myForm.$invalid){ - $scope.goToInvalid(); - return; - } + $rootScope.submitForm = $scope.submitForm = function() { + if($scope.forms.myForm.$invalid){ + $scope.goToInvalid(); + return; + } - var _timeElapsed = TimeCounter.stopClock(); - $scope.loading = true; + var _timeElapsed = TimeCounter.stopClock(); + $scope.loading = true; - var form = _.cloneDeep($scope.myform); + var form = _.cloneDeep($scope.myform); - var deviceData = getDeviceData(); - form.device = deviceData; + var deviceData = getDeviceData(); + form.device = deviceData; - var geoData = getIpAndGeo(); - form.ipAddr = geoData.ipAddr; - form.geoLocation = geoData.geoLocation; + var geoData = getIpAndGeo(); + form.ipAddr = geoData.ipAddr; + form.geoLocation = geoData.geoLocation; - form.timeElapsed = _timeElapsed; - form.percentageComplete = $filter('formValidity')($scope.myform) / $scope.myform.visible_form_fields.length * 100; - delete form.endPage - delete form.isLive - delete form.provider - delete form.startPage - delete form.visible_form_fields; - delete form.analytics; - delete form.design; - delete form.submissions; - delete form.submitted; - for(var i=0; i < $scope.myform.form_fields.length; i++){ - if($scope.myform.form_fields[i].fieldType === 'dropdown' && !$scope.myform.form_fields[i].deletePreserved){ - $scope.myform.form_fields[i].fieldValue = $scope.myform.form_fields[i].fieldValue.option_value; - } - } + form.timeElapsed = _timeElapsed; + form.percentageComplete = $filter('formValidity')($scope.myform) / $scope.myform.visible_form_fields.length * 100; + delete form.endPage + delete form.isLive + delete form.provider + delete form.startPage + delete form.visible_form_fields; + delete form.analytics; + delete form.design; + delete form.submissions; + delete form.submitted; + for(var i=0; i < $scope.myform.form_fields.length; i++){ + if($scope.myform.form_fields[i].fieldType === 'dropdown' && !$scope.myform.form_fields[i].deletePreserved){ + $scope.myform.form_fields[i].fieldValue = $scope.myform.form_fields[i].fieldValue.option_value; + } + } - setTimeout(function () { - $scope.submitPromise = $http.post('/forms/' + $scope.myform._id, form) - .success(function (data, status) { - $scope.myform.submitted = true; - $scope.loading = false; - SendVisitorData.send(form, getActiveField(), _timeElapsed); - }) - .error(function (error) { - $scope.loading = false; - console.error(error); - $scope.error = error.message; - }); - }, 500); + setTimeout(function () { + $scope.submitPromise = $http.post('/forms/' + $scope.myform._id, form) + .then(function (data, status) { + $scope.myform.submitted = true; + $scope.loading = false; + SendVisitorData.send(form, $rootScope.getActiveField(), _timeElapsed); + }, function (error) { + $scope.loading = false; + console.error(error); + $scope.error = error.message; + }); + }, 500); }; //Reload our form - $scope.reloadForm(); + $scope.reloadForm(); } }; } ]); + diff --git a/public/form_modules/forms/base/views/directiveViews/field/legal.html b/public/form_modules/forms/base/views/directiveViews/field/legal.html index 02de692a..24b620e1 100644 --- a/public/form_modules/forms/base/views/directiveViews/field/legal.html +++ b/public/form_modules/forms/base/views/directiveViews/field/legal.html @@ -28,7 +28,7 @@ ng-model="field.fieldValue" ng-model-options="{ debounce: 250 }" ng-required="field.required" - ng-change="nextField()"/> + ng-click="nextField()"/>
{{ 'Y' | translate }}
@@ -43,7 +43,7 @@ ng-model="field.fieldValue" ng-model-options="{ debounce: 250 }" ng-required="field.required" - ng-change="nextField()"/> + ng-click="nextField()"/>
{{ 'N' | translate }}
diff --git a/public/form_modules/forms/base/views/directiveViews/field/radio.html b/public/form_modules/forms/base/views/directiveViews/field/radio.html index 79427339..bea521f7 100755 --- a/public/form_modules/forms/base/views/directiveViews/field/radio.html +++ b/public/form_modules/forms/base/views/directiveViews/field/radio.html @@ -22,8 +22,8 @@