1
0

Merge remote-tracking branch 'origin/fixUXBugs' into features/missing

# Conflicts:
#	.gitignore
#	app/views/form.server.view.pug
#	package-lock.json
#	package.json
#	public/form_modules/forms/base/config/i18n/french.js
#	public/modules/core/config/core.client.routes.js
#	public/modules/core/config/i18n/french.js
#	public/modules/core/controllers/header.client.controller.js
#	public/modules/forms/admin/css/admin-form.css
#	public/modules/users/controllers/authentication.client.controller.js
#	public/modules/users/services/auth.client.service.js
#	scripts/create_admin.js
#	server.js
This commit is contained in:
wodka 2019-07-11 23:32:25 +02:00
commit 1caf34c230
130 changed files with 3281 additions and 2324 deletions
.jshintrcDockerfile-dev
app
bower.json
config
docker-compose.yamlgruntfile.jskarma.conf.js
public
application.js
form_modules/forms
modules
core
forms
users

@ -21,6 +21,7 @@
"globals": { // Globals variables.
"jasmine": true,
"angular": true,
"devel": false,
"_": true,
"saveAs": true,
"ApplicationConfiguration": true,

@ -10,12 +10,23 @@ MAINTAINER David Baldwynn <team@tellform.com>
# 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"]

@ -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<forms.length; i++){
forms[i] = helpers.removeSensitiveModelData('private_form', forms[i]);
currIndex = result_ids.indexOf(forms[i]._id.id)
if(currIndex > -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
});

@ -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<keys.length; i++){
var curr_key = keys[i];
if( dict.hasOwnProperty(curr_key) ){
delete dict[curr_key];
}
module.exports = {
removeKeysFromDict: function(dict, keys){
for(var i=0; i<keys.length; i++){
var curr_key = keys[i];
if( dict.hasOwnProperty(curr_key) ){
delete dict[curr_key];
}
}
return dict;
},
removeSensitiveModelData: function(type, actual_object){
var object = _.cloneDeep(actual_object);
switch(type){
case 'private_form':
removeKeysFromDict(object, privateFields[type]);
if(object.admin){
removeKeysFromDict(object.admin, privateFields.private_user);
}
break;
case 'public_form':
removeKeysFromDict(object, privateFields[type]);
if(object.admin){
removeKeysFromDict(object.admin, privateFields.public_user);
}
break;
default:
if(privateFields.hasOwnProperty(type)){
removeKeysFromDict(object, privateFields[type]);
}
break;
}
if(constants.privateFields.hasOwnProperty(type)) {
object = this.removeKeysFromDict(object, constants.privateFields[type]);
}
if(object.admin){
object.admin = this.removeKeysFromDict(object.admin, constants.privateFields.private_user);
}
debugger;
return object;
}

@ -126,7 +126,6 @@ exports.signup = function(req, res) {
// Then save the temporary user
nev.createTempUser(user, function (err, existingPersistentUser, newTempUser) {
if (err) {
console.log(err);
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
@ -181,7 +180,7 @@ exports.signin = function(req, res, next) {
res.cookie('langCookie', user.language, { maxAge: 90000, httpOnly: true });
user = helpers.removeSensitiveModelData('private_user', user);
user = helpers.removeSensitiveModelData('private_user', user.toJSON());
return res.json(user);
});
}

@ -113,7 +113,6 @@ exports.forgot = function(req, res) {
}
], function(err, obfuscatedEmail) {
if (err) {
console.log(err);
return res.status(400).send({
message: 'Couldn\'t send reset password email due to internal server errors. Please contact support at team@tellform.com.'
});

@ -18,6 +18,8 @@ exports.update = function(req, res) {
// To improve security we remove the roles from the req.body object
delete req.body.roles;
debugger;
// Merge existing user
user = _.extend(user, req.body);
user.updated = Date.now();
@ -32,7 +34,7 @@ exports.update = function(req, res) {
if (err) {
res.status(500).send(loginErr);
} else {
user = helpers.removeSensitiveModelData('private_user', user);
user = helpers.removeSensitiveModelData('private_user', user.toJSON());
res.json(user);
}
});
@ -44,7 +46,7 @@ exports.update = function(req, res) {
* Send User
*/
exports.getUser = function(req, res) {
var user = helpers.removeSensitiveModelData('private_user', req.user);
var user = helpers.removeSensitiveModelData('private_user', req.user.toJSON());
return res.json(user);
};

@ -1,41 +1,54 @@
'use strict';
module.exports = {
fieldTypes: ['textfield',
'date',
'email',
'legal',
'url',
'textarea',
'statement',
'welcome',
'thankyou',
'file',
'dropdown',
'scale',
'rating',
'radio',
'checkbox',
'hidden',
'yes_no',
'natural',
'stripe',
'number'],
var constants = module.exports = {
ratingShapeTypes: ['Heart',
'Star',
'thumbs-up',
'thumbs-down',
'Circle',
'Square',
'Check Circle',
'Smile Outlined',
'Hourglass',
'bell',
'Paper Plane',
'Comment',
'Trash'],
extraneousFormFieldProps: [
'validFieldTypes',
'disabled',
'required',
'isSubmission',
'title',
'fieldOptions',
'ratingOptions',
'logicJump',
'description',
'created',
'lastModified',
'deletePreserved'
],
fieldTypes: [
'textfield',
'date',
'email',
'legal',
'textarea',
'link',
'statement',
'dropdown',
'rating',
'radio',
'hidden',
'yes_no',
'number'
],
ratingShapeTypes: [
'Heart',
'Star',
'thumbs-up',
'thumbs-down',
'Circle',
'Square',
'Check Circle',
'Smile Outlined',
'Hourglass',
'bell',
'Paper Plane',
'Comment',
'Trash'
],
deviceTypes: ['desktop', 'phone', 'tablet', 'other'],
languageTypes: ['en', 'fr', 'es', 'it', 'de'],
@ -55,6 +68,13 @@ module.exports = {
'Deutsch': 'de'
},
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']
},
expressionStringTypes: ['field == static',
'field != static',
'field > 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: /<var(.*)id(.*)>(.|\n)*?<\/var>/g
},
varFormat: ['<var([^<>]+)id=["\']{1}field:', '["\']{1}>([^<>]+)*?<\/var>'],
}
};

@ -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('<!doctype html>'+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;

@ -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<form_fields.length; i++){
if(form_fields[i] && !form_fields[i].hasOwnProperty('_id') && !form_fields[i].hasOwnProperty('globalId')){
return false;
}
}
}
return true;
}
FormSchema.pre('save', function (next) {
var that = this;
var _original;
async.series([
function(cb) {
that.constructor
.findOne({_id: that._id}).exec(function (err, original) {
if (err) {
return cb(err);
} else if (!original){
return next();
} else {
_original = original;
return cb(null);
}
});
},
function(cb) {
if(that.form_fields && that.isModified('form_fields') && formFieldsAllHaveIds(that.toObject().form_fields)){
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);
//Check if any form_fileds were deleted
if( deletedIds.length > 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');

@ -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){

@ -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');

@ -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');

@ -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();

@ -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)

@ -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() {

@ -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,

@ -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 = '<p><var class="tag" id="field:56340745f59a6fc9e22028e9">First Name</var> \
<br><var class="tag" id="field:5c9e22028e907634f45f59a6">Your Website</var> \
<br><var class="tag" id="field:56e90745f5934fc9e22028a6">Your Age</var></p>';
const htmlTemplate = '<p><span class="placeholder-tag" data-id="56340745f59a6fc9e22028e9">First Name</span>'+
'<br><span class="placeholder-tag" data-id="5c9e22028e907634f45f59a6">Your Website</span>'+
'<br><span class="placeholder-tag" data-id="56e90745f5934fc9e22028a6">Your Age</span></p>';
const renderedTemplate = '<p>John Smith \
<br>https://johnsmith.me \
<br>45</p>';
const renderedTemplate = '<!DOCTYPE html><html><head></head><body><p>John Smith<br>https://johnsmith.me<br>45</p></body></html>';
/**
* 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 = '<p>John Smith \
<br><var class="tag" id="field:5c9e22028e907634f45f59a6">Your Website</var> \
<br><var class="tag" id="field:56e90745f5934fc9e22028a6">Your Age</var></p>';
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 <var class="tag" id="field:56340745f59a6fc9e22028e9">First Name</var>!',
subject: 'Hello <span class="placeholder-tag" data-id="56340745f59a6fc9e22028e9">First Name</span>!',
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();
});

@ -5,8 +5,8 @@
*/
var should = require('should'),
mongoose = require('mongoose'),
User = mongoose.model('User');
User = require('../models/user.server.model.js');
/**
* Globals
*/

@ -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');

@ -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").

@ -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)

@ -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": {

15
config/env/all.js vendored

@ -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: [

@ -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: {

@ -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',

4
config/env/test.js vendored

@ -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'
},

@ -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,

@ -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 || '');
}

@ -7,7 +7,7 @@ services:
volumes:
- .:/code
ports:
- "5000:5000"
- "5000"
- 587:587
env_file:
- .env

@ -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: {

@ -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/'
},

@ -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');

@ -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')

@ -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!',

@ -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.',
});

@ -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.',
});
}]);

@ -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!',

@ -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!',

@ -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);

@ -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();
}
};
}
]);

@ -28,7 +28,7 @@
ng-model="field.fieldValue"
ng-model-options="{ debounce: 250 }"
ng-required="field.required"
ng-change="nextField()"/>
ng-click="nextField()"/>
<div class="letter" style="float:left">
{{ 'Y' | translate }}
</div>
@ -43,7 +43,7 @@
ng-model="field.fieldValue"
ng-model-options="{ debounce: 250 }"
ng-required="field.required"
ng-change="nextField()"/>
ng-click="nextField()"/>
<div class="letter" style="float:left">
{{ 'N' | translate }}
</div>

@ -22,8 +22,8 @@
<label class="btn col-md-4 col-xs-12 col-sm-12"
style="margin: 0.5em; padding-left:30px"
ng-class="{activeBtn: field.fieldValue == field.fieldOptions[$index].option_value}">
<div class="letter" style="float:left">
{{$index+1}}
<div ng-if="field.fieldOptions.length <= 26" class="letter" style="float:left">
{{$index | indexToAlphabet}}
</div>
<input ng-style="{'color': design.colors.answerColor, 'border-color': design.colors.answerColor}"
name="{{field.fieldType}}{{index}}"
@ -31,8 +31,7 @@
value="{{option.option_value}}"
ng-model="field.fieldValue"
ng-required="field.required"
ng-click="$root.nextField()"
ng-change="$root.nextField()"/>
ng-click="$root.nextField()"/>
<span ng-bind="option.option_value"></span>
</label>

@ -46,7 +46,8 @@
</field-directive>
</div>
<div class="row form-actions" id="submit_field" du-scrollspy="submit_field"
ng-style="{ 'background-color':myform.design.colors.buttonColor}" style="border-top: 1px solid #ddd; margin-top: 30vh; height: 100vh; margin-left: 1%; margin-right: 1%;"
ng-style="{ 'background-color':myform.design.colors.buttonColor}"
style="border-top: 1px solid #ddd; margin-top: 30vh; height: 100vh; margin-left: 1%; margin-right: 1%;"
on-tab-and-shift-key="prevField()"
on-tab-key="nextField()"
on-enter-key="submitForm()">
@ -55,7 +56,10 @@
{{ 'COMPLETING_NEEDED' | translate:translateAdvancementData }}
</div>
<button ng-if="!forms.myForm.$invalid" class="Button btn col-sm-2 col-xs-8 focusOn" v-busy="loading" v-busy-label="Please wait" v-pressable ng-disabled="loading || forms.myForm.$invalid" ng-click="submitForm()" on-enter-key-disabled="loading || forms.myForm.$invalid" ng-style="{'background-color':myform.design.colors.buttonColor, 'color':myform.design.colors.buttonTextColor}" style="font-size: 1.6em; margin-left: 1em; margin-top: 1em;">
<button ng-if="!forms.myForm.$invalid" class="Button btn col-sm-2 col-xs-8 focusOn" v-busy="loading" v-busy-label="{{ 'WAIT_LABEL' | translate }}" v-pressable
ng-disabled="loading || forms.myForm.$invalid" ng-click="submitForm()" on-enter-key-disabled="loading || forms.myForm.$invalid"
ng-style="{'background-color':myform.design.colors.buttonColor, 'color':myform.design.colors.buttonTextColor}"
style="font-size: 1.6em; margin-left: 1em; margin-top: 1em;">
{{ 'SUBMIT' | translate }}
</button>
@ -72,7 +76,7 @@
</form>
</div>
<section ng-if="!myform.hideFooter" class="navbar navbar-fixed-bottom" ng-style="{ 'background-color':myform.design.colors.buttonColor, 'padding-top': '15px', 'border-top': '2px '+ myform.design.colors.buttonTextColor +' solid', 'color':myform.design.colors.buttonTextColor}">
<section ng-if="myform.showFooter" class="navbar navbar-fixed-bottom" ng-style="{ 'background-color':myform.design.colors.buttonColor, 'padding-top': '15px', 'border-top': '2px '+ myform.design.colors.buttonTextColor +' solid', 'color':myform.design.colors.buttonTextColor}">
<div class="container-fluid">
<div class="row">
<div class="col-sm-5 col-md-6 col-xs-5" ng-show="!myform.submitted">

@ -11,7 +11,7 @@
function SendVisitorData(Socket, $state) {
// Create a controller method for sending visitor data
function send(form, lastActiveIndex, timeElapsed) {
function send(form, lastActiveId, timeElapsed) {
var lang = window.navigator.userLanguage || window.navigator.language;
lang = lang.slice(0,2);
@ -33,7 +33,7 @@
referrer: document.referrer,
isSubmitted: form.submitted,
formId: form._id,
lastActiveField: form.form_fields[lastActiveIndex]._id,
lastActiveField: lastActiveId,
timeElapsed: timeElapsed,
language: lang,
deviceType: deviceType,

@ -1,25 +1,29 @@
'use strict';
angular.module('view-form').directive('keyToOption', function(){
angular.module('view-form').directive('keyToOption', ['$rootScope', function($rootScope){
return {
restrict: 'A',
scope: {
field: '='
},
link: function($scope, $element, $attrs, $select) {
$element.bind('keydown keypress', function(event) {
link: function($scope, $element, $attrs) {
$('body').on('keypress', function(event) {
var keyCode = event.which || event.keyCode;
var index = parseInt(String.fromCharCode(keyCode))-1;
if (index < $scope.field.fieldOptions.length) {
var index = -1;
if(keyCode <= 122 && keyCode >= 97){
index = keyCode - 97;
} else if (keyCode <= 90 && keyCode >= 65){
index = keyCode - 65;
}
if ($scope.field.fieldOptions.length <= 26 && $scope.field._id === $rootScope.getActiveField() && index !== -1 && index < $scope.field.fieldOptions.length) {
event.preventDefault();
$scope.$apply(function () {
$scope.field.fieldValue = $scope.field.fieldOptions[index].option_value;
});
}
});
}
};
});
}]);

@ -8,7 +8,9 @@ angular.module('core').config(['$stateProvider', '$urlRouterProvider',
}
]);
angular.module(ApplicationConfiguration.applicationModuleName).run(['$rootScope', 'Auth', '$state', '$stateParams',
var statesWithoutAuth = ['access_denied', 'signin', 'resendVerifyEmail', 'verify', 'signup', 'signup-success', 'forgot', 'reset-invalid', 'reset', 'reset-success'];
angular.module(ApplicationConfiguration.applicationModuleName).run(['$rootScope', '$state', '$stateParams',
function($rootScope, Auth, $state, $stateParams) {
$rootScope.$state = $state;
@ -20,46 +22,35 @@ angular.module(ApplicationConfiguration.applicationModuleName).run(['$rootScope'
state: fromState,
params: fromParams
}
var statesToIgnore = ['', 'home', 'signin', 'resendVerifyEmail', 'verify', 'signup', 'signup-success', 'forgot', 'reset-invalid', 'reset', 'reset-success'];
//Redirect to listForms if user is authenticated
if(statesToIgnore.indexOf(toState.name) > 0){
if(Auth.isAuthenticated()){
event.preventDefault(); // stop current execution
$state.go('listForms'); // go to listForms page
}
}
//Redirect to 'signup' route if user is not authenticated
else if(toState.name !== 'access_denied' && !Auth.isAuthenticated() && toState.name !== 'submitForm'){
event.preventDefault(); // stop current execution
$state.go('listForms'); // go to listForms page
}
});
}
]);
//Page access/authorization logic
angular.module(ApplicationConfiguration.applicationModuleName).run(['$rootScope', 'Auth', 'User', 'Authorizer', '$state', '$stateParams',
function($rootScope, Auth, User, Authorizer, $state, $stateParams) {
$rootScope.$on('$stateChangeStart', function(event, next) {
var authenticator, permissions, user;
permissions = next && next.data && next.data.permissions ? next.data.permissions : null;
angular.module(ApplicationConfiguration.applicationModuleName).run(['$rootScope', 'Auth', 'Authorizer', '$state', '$stateParams',
function($rootScope, Auth, Authorizer, $state, $stateParams) {
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
Auth.ensureHasCurrentUser();
user = Auth.currentUser;
//Only run permissions check if it is an authenticated state
if(statesWithoutAuth.indexOf(toState.name) > -1){
Auth.ensureHasCurrentUser().then(
function onSuccess(currentUser){
if(currentUser){
var authenticator = new Authorizer(user);
var permissions = toState && toState.data && toState.data.permissions ? toState.data.permissions : null;
if(user){
authenticator = new Authorizer(user);
if( (permissions !== null) ){
if( !authenticator.canAccess(permissions) ){
if( permissions !== null && !authenticator.canAccess(permissions) ){
event.preventDefault();
$state.go('access_denied');
}
}
},
function onError(error){
event.preventDefault();
$state.go('access_denied');
}
}
);
}
});
}]);

@ -6,10 +6,10 @@ angular.module('core').config(['$translateProvider', function ($translateProvide
MENU: 'MENU',
SIGNUP_TAB: 'Créer un compte',
SIGNIN_TAB: 'Connexion',
SIGNOUT_TAB: 'Créer un compte',
EDIT_PROFILE: 'Modifier mon profil',
MY_SETTINGS: 'Mes paramètres',
CHANGE_PASSWORD: 'Changer mon mot de passe',
SIGNOUT_TAB: 'Déconnexion',
EDIT_PROFILE: 'Modifier Mon Profil',
MY_SETTINGS: 'Mes Paramètres',
CHANGE_PASSWORD: 'Changer mon Mot de Pass',
TOGGLE_NAVIGATION: 'Basculer la navigation',
});
}]);

@ -5,51 +5,67 @@ angular.module('core').controller('HeaderController', ['$rootScope', '$scope', '
$rootScope.signupDisabled = $window.signupDisabled;
$scope.user = $rootScope.user = Auth.ensureHasCurrentUser();
Auth.ensureHasCurrentUser().then(function(currUser){
$scope.user = $rootScope.user = currUser;
$scope.authentication = $rootScope.authentication = Auth;
$scope.authentication = $rootScope.authentication = Auth;
//Set global app language
$rootScope.language = $scope.user.language;
$translate.use($scope.user.language);
$rootScope.languages = $scope.languages = ['en', 'fr', 'es', 'it', 'de'];
//Set global app language
$rootScope.language = $scope.user.language;
$translate.use($scope.user.language);
$scope.isCollapsed = false;
$rootScope.hideNav = false;
$scope.menu = Menus.getMenu('topbar');
$scope.signout = function() {
var promise = User.logout();
promise.then(function() {
Auth.logout();
Auth.ensureHasCurrentUser();
$scope.user = $rootScope.user = null;
$state.go('listForms');
//Refresh view
$state.reload();
},
function(reason) {
console.error('Logout Failed: ' + reason);
});
};
$scope.toggleCollapsibleMenu = function() {
$scope.isCollapsed = !$scope.isCollapsed;
};
// Collapsing the menu after navigation
$scope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams) {
$scope.isCollapsed = false;
$rootScope.hideNav = false;
if ( angular.isDefined( toState.data ) ) {
$scope.menu = Menus.getMenu('topbar');
$rootScope.languages = ['en', 'fr', 'es', 'it', 'de'];
$rootScope.langCodeToWord = {
'en': 'English',
'fr': 'Français',
'es': 'Español',
'it': 'Italiàno',
'de': 'Deutsch'
};
$rootScope.wordToLangCode = {
'English': 'en',
'Français': 'fr',
'Español': 'es',
'Italiàno': 'it',
'Deutsch': 'de'
};
$scope.signout = function() {
var promise = User.logout();
promise.then(function() {
Auth.logout();
$scope.user = $rootScope.user = null;
$state.go('signin', { reload: true });
},
function(reason) {
console.error('Logout Failed: ' + reason);
});
};
$scope.toggleCollapsibleMenu = function() {
$scope.isCollapsed = !$scope.isCollapsed;
};
// Collapsing the menu after navigation
$scope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams) {
$scope.isCollapsed = false;
$rootScope.hideNav = false;
if ( angular.isDefined( toState.data ) ) {
if ( angular.isDefined( toState.data.hideNav ) ) {
$rootScope.hideNav = toState.data.hideNav;
}
}
});
}, function(){
$state.go('signup');
})
if ( angular.isDefined( toState.data.hideNav ) ) {
$rootScope.hideNav = toState.data.hideNav;
}
}
});
}
]);

@ -20,19 +20,21 @@
// Load the main application module
beforeEach(module(ApplicationConfiguration.applicationModuleName));
beforeEach(module('module-templates'));
//Mock Authentication Service
beforeEach(module(function($provide) {
$provide.service('Auth', function() {
return {
ensureHasCurrentUser: function() {
return sampleUser;
return {
then: function(onFulfilled, onRejected, progressBack) {
return onFulfilled(sampleUser);
}
};
},
isAuthenticated: function() {
return true;
},
getUserState: function() {
return true;
}
};
});
@ -45,9 +47,5 @@
$scope: scope
});
}));
it('should expose the authentication service', function() {
expect(scope.authentication).toBeTruthy();
});
});
})();

@ -1,20 +0,0 @@
'use strict';
(function() {
describe('HomeController', function() {
//Initialize global variables
var scope;
// Load the main application module
beforeEach(module(ApplicationConfiguration.applicationModuleName));
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope.$new();
$controller('HomeController', {
$scope: scope
});
}));
});
})();

@ -65,6 +65,9 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
VIEW_MY_TELLFORM: 'View my tellform',
LIVE: 'Live',
PREVIEW: 'Preview',
//Share Tab
COPIED_LABEL: 'Copied',
COPY: 'Copy',
COPY_AND_PASTE: 'Copy and Paste this to add your TellForm to your website',
CHANGE_WIDTH_AND_HEIGHT: 'Change the width and height values to suit you best',

@ -65,6 +65,9 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
VIEW_MY_TELLFORM: "Afficher ma forme",
LIVE: "Live",
PREVIEW: 'Aperçu',
//Share Tab
COPIED_LABEL: 'Copié',
COPY: "Copier",
COPY_AND_PASTE: "Copiez et collez ceci pour ajouter votre TellForm à votre site Web",
CHANGE_WIDTH_AND_HEIGHT: "Changez les valeurs de largeur et de hauteur pour mieux vous convenir",

@ -65,9 +65,11 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
VIEW_MY_TELLFORM: 'Mein tellform anzeigen',
LIVE: 'Leben',
PREVIEW: 'Vorschau',
//Share Tab
COPIED_LABEL: 'Kopiert',
COPY: 'Kopieren',
COPY_AND_PASTE: 'Kopieren und einfügen, um Ihre TellForm auf Ihrer Website hinzuzufügen',
CHANGE_WIDTH_AND_HEIGHT: 'Ändern Sie die Werte für Breite und Höhe, um Ihnen am besten zu entsprechen',
POWERED_BY: 'Unterstützt von',
TELLFORM_URL: "Ihr TellForm ist dauerhaft unter dieser URL",

@ -65,9 +65,11 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
VIEW_MY_TELLFORM: 'Visualizza la mia informazione',
LIVE: 'Live',
PREVIEW: 'Anteprima',
// Share Tab
COPIED_LABEL: "Copiato",
COPY: 'Copia',
COPY_AND_PASTE: 'Copia e incolla questo per aggiungere il tuo TellForm al tuo sito web',
CHANGE_WIDTH_AND_HEIGHT: 'Modifica i valori di larghezza e di altezza per adattarti al meglio',
POWERED_BY: 'Offerto da',
TELLFORM_URL: 'Il tuo TellForm è permanente in questo URL',

@ -65,9 +65,11 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
VIEW_MY_TELLFORM: 'Ver mi tellform',
LIVE: 'Online',
PREVIEW: 'Vista previa',
// Share Tab
COPIED_LABEL: 'Copiado',
COPY: 'Copiar',
COPY_AND_PASTE: 'Copiar y pegar esto para agregar su TellForm a su sitio web',
CHANGE_WIDTH_AND_HEIGHT: 'Cambie los valores de ancho y altura para adaptar el formulario a sus necesidades',
POWERED_BY: 'Con la tecnlogía de',
TELLFORM_URL: 'Tu TellForm está en esta URL permanente',

@ -53,7 +53,7 @@ angular.module('forms').controller('AdminFormController', ['$rootScope', '$windo
},
{
heading: $filter('translate')('SHARE_TAB'),
route: 'viewForm.share',
route: 'viewForm.share.share_form',
active: false
},
{
@ -115,7 +115,6 @@ angular.module('forms').controller('AdminFormController', ['$rootScope', '$windo
$scope.deleteModal = $uibModal.open({
animation: $scope.animationsEnabled,
templateUrl: 'formDeleteModal.html',
controller: 'AdminFormController',
resolve: {
myForm: function(){
return $scope.myform;
@ -208,22 +207,11 @@ angular.module('forms').controller('AdminFormController', ['$rootScope', '$windo
if(dataToSend.analytics && dataToSend.analytics.visitors){
delete dataToSend.analytics.visitors;
}
if(dataToSend.submissions){
delete dataToSend.submissions;
}
if(dataToSend.visible_form_fields){
delete dataToSend.visible_form_fields;
}
if(dataToSend.analytics){
delete dataToSend.analytics.visitors;
delete dataToSend.analytics.fields;
delete dataToSend.analytics.submissions;
delete dataToSend.analytics.views;
delete dataToSend.analytics.conversionRate;
}
delete dataToSend.created;
delete dataToSend.lastModified;
delete dataToSend.__v;
@ -252,4 +240,4 @@ angular.module('forms').controller('AdminFormController', ['$rootScope', '$windo
}
]);
]);

@ -1,13 +1,14 @@
'use strict';
// Forms controller
angular.module('forms').controller('ListFormsController', ['$rootScope', '$scope', '$stateParams', '$state', 'GetForms', 'CurrentForm', '$http', '$uibModal', 'myForms',
function($rootScope, $scope, $stateParams, $state, GetForms, CurrentForm, $http, $uibModal, myForms) {
angular.module('forms').controller('ListFormsController', ['$rootScope', '$scope', '$stateParams', '$state', 'GetForms', 'CurrentForm', '$http', '$uibModal', 'myForms', '$window', '$location',
function($rootScope, $scope, $stateParams, $state, GetForms, CurrentForm, $http, $uibModal, myForms, $window, $location) {
$scope = $rootScope;
$scope.forms = {};
$scope.showCreateModal = false;
$scope.myforms = myForms
$scope.formLanguage = $window.$locale;
$rootScope.languageRegExp = {
regExp: /[@!#$%^&*()\-+={}\[\]|\\/'";:`.,~№?<>]+/i,
@ -43,7 +44,6 @@ angular.module('forms').controller('ListFormsController', ['$rootScope', '$scope
});
};
$scope.cancelDeleteModal = function(){
if($scope.deleteModal){
$scope.deleteModal.dismiss('cancel');
@ -65,18 +65,20 @@ angular.module('forms').controller('ListFormsController', ['$rootScope', '$scope
$scope.setForm = function (form) {
$scope.myform = form;
};
$scope.goToWithId = function(route, id) {
$state.go(route, {'formId': id}, {reload: true});
};
$scope.duplicateForm = function(form_index){
var form = _.cloneDeep($scope.myforms[form_index]);
delete form._id;
delete form.id;
form.title += ' copy';
$http.post('/forms', {form: form})
.success(function(data, status, headers){
$scope.myforms.splice(form_index+1, 0, data);
}).error(function(errorResponse){
.then(function(resp_data, status, headers){
var result_form = resp_data.data;
result_form.submissionNum = 0;
$scope.myforms.splice(form_index+1, 0, result_form);
}, function(errorResponse){
console.error(errorResponse);
if(errorResponse === null){
$scope.error = errorResponse.data.message;
@ -86,20 +88,19 @@ angular.module('forms').controller('ListFormsController', ['$rootScope', '$scope
// Create new Form
$scope.createNewForm = function(){
var form = {};
form.title = $scope.forms.createForm.title.$modelValue;
form.language = $scope.forms.createForm.language.$modelValue;
if($scope.forms.createForm.$valid && $scope.forms.createForm.$dirty){
$http.post('/forms', {form: form})
.success(function(data, status, headers){
// Redirect after save
$scope.goToWithId('viewForm.create', data._id+'');
}).error(function(errorResponse){
console.error(errorResponse);
$scope.error = errorResponse.data.message;
});
.then(function(response, status, headers){
// Redirect after save
$state.go('viewForm.create', {formId: response.data.id}, {reload: true});
}, function(errorResponse){
console.error(errorResponse);
$scope.error = errorResponse.data.message;
});
}
};
@ -109,10 +110,10 @@ angular.module('forms').controller('ListFormsController', ['$rootScope', '$scope
}
$http.delete('/forms/'+$scope.myforms[form_index]._id)
.success(function(data, status, headers){
.then(function(data, status, headers){
$scope.myforms.splice(form_index, 1);
$scope.cancelDeleteModal();
}).error(function(error){
}, function(error){
console.error(error);
});
};

@ -1,4 +1,212 @@
.admin-form-title {
margin-bottom: 0px;
overflow-wrap: break-word;
}
.admin-form .pull-top {
display: inline-block;
vertical-align: top;
float: none;
}
.admin-form .box {
padding: 0 5px 0 5px!important;
}
.admin-form .current-fields .field-row {
padding: 5px 0;
}
.admin-form .current-fields .panel {
background-color: #f1f1f1;
margin-top: 0!important;
}
.admin-form .current-fields .panel:hover {
background-color: #fff;
cursor: pointer;
}
.admin-form .current-fields .panel.tool-panel {
background-color: white;
}
.admin-form .current-fields .panel-heading {
background-color: #f1f1f1;
position: relative;
}
.admin-form .current-fields .panel-heading:hover {
background-color: #fff;
cursor: pointer;
}
.admin-form .current-fields .panel-heading a:hover {
text-decoration: none;
}
.admin-form .current-fields .tool-panel.panel:hover {
border-color: #9d9d9d;
background-color: #eee;
cursor: pointer;
}
.admin-form .current-fields .tool-panel.panel:hover .panel-heading {
background-color: inherit;
color: #000;
cursor: pointer;
}
.admin-form .current-fields .tool-panel.panel .panel-heading {
background-color: #fff;
color: #9d9d9d;
}
.admin-form .current-fields .tool-panel.panel .panel-heading a {
color: inherit;
}
.admin-form .current-fields .tool-panel.panel .panel-heading a:hover{
text-decoration: none;
}
/* Custom Tab CSS */
.admin-form .nav.nav-pills.nav-stacked {
width: 16.66666667%;
float: left;
position: relative;
min-height: 1px;
padding-right: 15px;
}
.admin-form .tab-content {
width: 83.33333333%;
position: relative;
min-height: 1px;
float:left;
padding-top: 0;
}
.admin-form .panel-default.startPage {
border-style: dashed;
border-color: #a9a9a9;
border-width:3px;
}
.admin-form .busy-updating-wrapper {
text-align: center;
font-size: 20px;
position: fixed;
bottom: 0;
right: 55px;
z-index: 1;
}
.admin-form .busy-submitting-wrapper {
position: fixed;
top: 50%;
left: 0;
right: 0;
bottom: 0;
}
.admin-form .dropzone h4.panel-title {
height: 17px;
overflow: hidden;
}
.container.admin-form {
margin-top: 70px;
}
/*
** Edit Modal
*/
.edit-modal-window .modal-dialog {
width: 90%;
}
.edit-modal-window .modal-body {
padding: 0;
}
.edit-modal-window .edit-panel {
background-color: #F1F1F1;
padding: 0 35px 0 35px;
}
.edit-modal-window .preview-field-panel {
display: flex;
flex-direction: column;
justify-content: center;
}
.edit-modal-window .preview-field-panel form {
padding-right: 20px;
}
.edit-modal-window .preview-field {
resize: vertical;
}
.admin-form .ui-sortable-placeholder {
visibility: visible !important;
border: none;
padding:1px;
background: #000;
background:rgba(0, 0, 0, 0.5) !important;
}
/* Styles for form admin view (/forms/:formID/admin) */
.admin-form > .page-header {
padding-bottom: 0;
margin-bottom: 40px;
}
.admin-form > .page-header h1 {
margin-bottom: 0;
margin-top: 0;
}
.admin-form > .page-header > .col-xs-3 {
padding-top: 1.4em;
}
.admin-form .form-controls .row {
padding: 5px;
}
.admin-form .page-header {
border: none;
margin-top: none;
margin-bottom: none;
}
/*Styles for add fields tab*/
.admin-form .add-field {
background-color: #ddd;
padding: 0 2% 0 2%;
border-radius: 3px;
}
.admin-form .add-field .col-xs-6 {
padding: 0.25em 0.4em;
}
.admin-form .add-field .col-xs-6 .panel-heading {
border-width: 1px;
border-style: solid;
border-color: #bbb;
border-radius: 4px;
}
.view-form-btn {
border: none;
}
.view-form-btn.span {
padding-right:0.6em;
}
.notification-row {
display: inline-block;
padding: 0 5px;
}
.status-light {
font-size: 10px;
}
.notification-row .status-light {
padding-top: 15px;
}
.status-light.status-light-off {
color: #BE0000;
}
.status-light.status-light-on {
color: #33CC00;
}

@ -0,0 +1,79 @@
configure-form-directive .placeholder-tag {
background-color: #999;
border-radius: 3px 3px 3px 3px;
border: 0;
color: #FFFFFF;
font-style: inherit;
font-size: 11px;
padding: 4px 5px;
margin: 0 2px 2px 2px;
font-family: inherit;
white-space: nowrap;
vertical-align: middle;
cursor: pointer !important;
display: inline!important;
width: 100%;
}
configure-form-directive .ui-select input.form-control {
height: 34px;
padding: 6px;
}
configure-form-directive .config-form .btn-secondary {
border-color: #DDDDDD;
}
configure-form-directive .notification-toggle.toggle-switch {
margin: 5px 0;
}
/* QuillJS Custom Theming */
configure-form-directive .ql-editor {
background: white;
font-size: 18px;
}
configure-form-directive .ql-picker.ql-placeholder {
width: 118px;
}
configure-form-directive .ql-picker.ql-placeholder > span.ql-picker-label:before {
content: attr(data-before);
}
configure-form-directive .ql-picker.ql-placeholder > span.ql-picker-options > span.ql-picker-item::before {
content: attr(data-label);
}
configure-form-directive .config-form .row.field {
padding-top:1.5em;
}
configure-form-directive .config-form > .row > .container:nth-of-type(odd){
border-right: 1px #ddd solid;
}
configure-form-directive .config-form .row > .field-input {
padding-left:0.1em;
}
configure-form-directive .config-form .row > .field-input label {
padding-left:1.3em;
display: block;
}
configure-form-directive .config-form {
max-width: 100%;
}
configure-form-directive .config-form > .row {
padding: 19px;
margin-bottom: 20px;
background-color: #f5f5f5;
border: 1px solid #e3e3e3;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
}

@ -0,0 +1,33 @@
design-form-directive .ui-select input.form-control {
height: 34px;
padding: 6px;
}
design-form-directive .config-form .btn-secondary {
border-color: #DDDDDD;
}
design-form-directive .config-form .row.field {
padding-top: 1.5em;
}
design-form-directive .config-form > .row > .container{
border-right: 1px #ddd solid;
border-right: none;
}
design-form-directive .config-form {
max-width: 100%;
}
design-form-directive .config-form > .row {
padding: 19px;
margin-bottom: 20px;
background-color: #f5f5f5;
border: 1px solid #e3e3e3;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
}

@ -1,43 +0,0 @@
.tag {
background-color: #999;
border-radius: 3px 3px 3px 3px;
border: 0;
color: #FFFFFF;
font-style: inherit;
font-size: 11px;
padding: 4px 5px;
margin: 0 2px 2px 2px;
font-family: inherit;
white-space: nowrap;
vertical-align: middle;
cursor: pointer !important;
pointer-events: none;
}
.email-subject.ta-root .ta-editor.ta-html, .email-subject .ta-scroll-window.form-control {
min-height: 0;
overflow: hidden;
height: auto;
border-radius: 4px;
box-shadow: none;
font-size: 18px;
padding-top: 10px;
}
.email-subject.ta-root .ta-scroll-window > .ta-bind {
min-height: 0;
outline: 0;
}
.ui-select input.form-control {
height: 34px;
padding: 6px;
}
.config-form .btn-secondary {
border-color: #DDDDDD;
}
.notification-toggle.toggle-switch {
margin: 5px 0;
}

@ -1,275 +0,0 @@
.pull-top {
display: inline-block;
vertical-align: top;
float: none;
}
.box {
padding: 0 5px 0 5px!important;
}
.current-fields .field-row {
padding: 5px 0;
}
.current-fields .panel {
background-color: #f1f1f1;
margin-top: 0!important;
}
.current-fields .panel:hover {
background-color: #fff;
cursor: pointer;
}
.current-fields .panel.tool-panel {
background-color: white;
}
.current-fields .panel-heading {
background-color: #f1f1f1;
position: relative;
}
.current-fields .panel-heading:hover {
background-color: #fff;
cursor: pointer;
}
.current-fields .panel-heading a:hover {
text-decoration: none;
}
.current-fields .tool-panel.panel:hover {
border-color: #9d9d9d;
background-color: #eee;
cursor: pointer;
}
.current-fields .tool-panel.panel:hover .panel-heading {
background-color: inherit;
color: #000;
cursor: pointer;
}
.current-fields .tool-panel.panel .panel-heading {
background-color: #fff;
color: #9d9d9d;
}
.current-fields .tool-panel.panel .panel-heading a {
color: inherit;
}
.current-fields .tool-panel.panel .panel-heading a:hover{
text-decoration: none;
}
/* Custom Tab CSS */
.nav.nav-pills.nav-stacked {
width: 16.66666667%;
float: left;
position: relative;
min-height: 1px;
padding-right: 15px;
}
div.tab-content {
width: 83.33333333%;
position: relative;
min-height: 1px;
float:left;
padding-top: 0!important;
}
.panel-default.startPage {
border-style: dashed;
border-color: #a9a9a9;
border-width:3px;
}
.busy-updating-wrapper {
text-align: center;
font-size: 20px;
position: fixed;
bottom: 0;
right: 55px;
z-index: 1;
}
.busy-submitting-wrapper {
position: fixed;
top: 50%;
left: 0;
right: 0;
bottom: 0;
}
.dropzone h4.panel-title {
height: 17px;
overflow: hidden;
}
.container.admin-form {
margin-top: 70px;
}
/*
** Edit Modal
*/
.edit-modal-window .modal-dialog {
width: 90%;
}
.edit-modal-window .modal-body {
padding: 0;
}
.edit-modal-window .edit-panel {
background-color: #F1F1F1;
padding: 0 35px 0 35px;
}
.edit-modal-window .preview-field-panel {
display: flex;
flex-direction: column;
justify-content: center;
}
.edit-modal-window .preview-field-panel form {
padding-right: 20px;
}
.edit-modal-window .preview-field {
resize: vertical;
}
.admin-form .ui-sortable-placeholder {
visibility: visible !important;
border: none;
padding:1px;
background: #000;
background:rgba(0, 0, 0, 0.5) !important;
}
.config-form {
max-width: 100%;
}
.config-form > .row {
padding: 19px;
margin-bottom: 20px;
background-color: #f5f5f5;
border: 1px solid #e3e3e3;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
}
div.config-form .row.field {
padding-top:1.5em;
}
div.config-form > .row > .container:nth-of-type(odd){
border-right: 1px #ddd solid;
}
div.config-form.design > .row > .container:nth-of-type(odd){
border-right: none;
}
div.config-form .row > .field-input {
padding-left:0.1em;
}
div.config-form .row > .field-input label {
padding-left:1.3em;
display: block;
}
/* Styles for form admin view (/forms/:formID/admin) */
.admin-form > .page-header {
padding-bottom: 0;
margin-bottom: 40px;
}
.admin-form > .page-header h1 {
margin-bottom: 0;
margin-top: 0;
}
.admin-form > .page-header > .col-xs-3 {
padding-top: 1.4em;
}
.admin-form .form-controls .row {
padding: 5px;
}
.admin-form .page-header {
border: none;
margin-top: none;
margin-bottom: none;
}
/*Styles for admin view tabs */
.admin-form .tab-content {
padding-top: 3em;
}
/*Styles for submission table*/
.submissions-table .table-outer.row {
margin: 1.5em 0 2em 0!important;
}
.submissions-table .table-outer .col-xs-12 {
padding-left: 0!important;
border:1px solid #ddd;
overflow-x: scroll;
border-radius:3px;
}
.submissions-table .table > thead > tr > th {
min-width:8em;
}
.submissions-table .table > tbody > tr.selected {
background-color:#efefef;
}
/*Styles for add fields tab*/
.admin-form .add-field {
background-color: #ddd;
padding: 0 2% 0 2%;
border-radius: 3px;
}
.admin-form .add-field .col-xs-6 {
padding: 0.25em 0.4em;
}
.admin-form .add-field .col-xs-6 .panel-heading {
border-width: 1px;
border-style: solid;
border-color: #bbb;
border-radius: 4px;
}
.admin-form .oscar-field-select {
margin: 10px 0 10px;
}
.view-form-btn {
border: none;
}
.view-form-btn.span {
padding-right:0.6em;
}
.notification-row {
display: inline-block;
padding: 0 5px;
}
.status-light {
font-size: 10px;
}
.notification-row .status-light {
padding-top: 15px;
}
.status-light.status-light-off {
color: #BE0000;
}
.status-light.status-light-on {
color: #33CC00;
}

@ -1,4 +1,19 @@
/*Styles for submission table*/
.submissions-table .table-outer.row {
margin: 1.5em 0 2em 0!important;
}
.submissions-table .table-outer .col-xs-12 {
padding-left: 0!important;
border:1px solid #ddd;
overflow-x: scroll;
border-radius:3px;
}
.submissions-table .table > thead > tr > th {
min-width:8em;
}
.submissions-table .table > tbody > tr.selected {
background-color:#efefef;
}
.analytics .header-title {
font-size: 1em;

@ -0,0 +1,26 @@
share-form-directive #copyEmbedded {
min-height: fit-content;
width: 100%;
background-color: #36404B;
color: white;
padding: 18px;
}
share-form-directive .share-row {
padding-top: 2.5em;
}
share-form-directive .config-form {
max-width: 100%;
}
share-form-directive .config-form > .row {
padding: 19px;
margin-bottom: 20px;
background-color: #f5f5f5;
border: 1px solid #e3e3e3;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
}

@ -1,7 +1,7 @@
'use strict';
angular.module('forms').directive('configureFormDirective', ['$rootScope', '$filter', '$state',
function ($rootScope, $filter, $state) {
angular.module('forms').directive('configureFormDirective', ['$rootScope', '$state', '$translate', '$timeout', '$window',
function ($rootScope, $state, $translate, $timeout, $window) {
return {
templateUrl: 'modules/forms/admin/views/directiveViews/form/configure-form.client.view.html',
restrict: 'E',
@ -12,49 +12,20 @@ angular.module('forms').directive('configureFormDirective', ['$rootScope', '$fil
$rootScope.myform = $scope.myform;
$scope.languages = $rootScope.languages;
$scope.resetForm = $rootScope.resetForm;
$scope.update = $rootScope.update;
console.log($scope.myform);
$scope.$evalAsync(function() {
angular.element('.tag')
});
$scope.languages = ['en', 'fr', 'es', 'it', 'de'];
$scope.langCodeToWord = {
'en': 'English',
'fr': 'Français',
'es': 'Español',
'it': 'Italiàno',
'de': 'Deutsch'
};
$scope.wordToLangCode = {
'English': 'en',
'Français': 'fr',
'Español': 'es',
'Italiàno': 'it',
'Deutsch': 'de'
};
$scope.configureTabs = [
{
heading: $filter('translate')('GENERAL_TAB'),
route: 'viewForm.configure.general',
active: false
},
{
heading: $filter('translate')('SELF_NOTIFICATIONS_TAB'),
route: 'viewForm.configure.self_notifications',
active: false
},
{
heading: $filter('translate')('RESPONDENT_NOTIFICATIONS_TAB'),
route: 'viewForm.configure.respondent_notifications',
active: false
Quill.register('modules/placeholder', PlaceholderModule.default(Quill))
$scope.customModules = {
placeholder: {
placeholders: $scope.myform.visible_form_fields.map(function(field){
return {
id: field.id,
label: field.title
};
}),
className: 'placeholder-tag',
delimiters: ['', '']
}
];
};
$scope.emailFields = $scope.myform.form_fields.filter(function(field){
return field.fieldType === 'email';
@ -62,6 +33,25 @@ angular.module('forms').directive('configureFormDirective', ['$rootScope', '$fil
$scope.formHasEmailField = ($scope.emailFields.length > 0);
/* Tab Routing Logic */
$scope.configureTabs = [
{
heading: $translate.instant('GENERAL_TAB'),
route: 'viewForm.configure.general',
active: false
},
{
heading: $translate.instant('SELF_NOTIFICATIONS_TAB'),
route: 'viewForm.configure.self_notifications',
active: false
},
{
heading: $translate.instant('RESPONDENT_NOTIFICATIONS_TAB'),
route: 'viewForm.configure.respondent_notifications',
active: false
}
];
$scope.go = function(tab){
tab.active = true;
$state.go(tab.route);
@ -76,6 +66,22 @@ angular.module('forms').directive('configureFormDirective', ['$rootScope', '$fil
setActiveTab();
$scope.$on("$stateChangeSuccess", setActiveTab());
$scope.$on('$viewContentLoaded', function ($evt, data) {
$timeout(function(){
if(!$('.ql-picker.ql-placeholder > span.ql-picker-label').attr('data-before')){
$('.ql-picker.ql-placeholder > span.ql-picker-label').attr('data-before', $translate.instant('ADD_VARIABLE_BUTTON'));
}
}, 500);
});
$scope.saveInProgress = false;
$scope.saveChanges = function(){
$scope.saveInProgress = true;
$rootScope.update(false, $scope.myform, false, false, function(){
$scope.saveInProgress = false;
});
};
}
};
}

@ -16,14 +16,20 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
*/
var newField;
//Populate local scope with rootScope methods/variables
$scope.update = $rootScope.update;
//Setup UI-Sortable
$scope.sortableOptions = {
appendTo: '.dropzone',
//helper: 'clone',
items: '.sortable-fields',
forceHelperSize: true,
forcePlaceholderSize: true,
update: function(e, ui) {
stop: function(e, ui) {
$scope.update(false, $scope.myform, true, false, function(err){
if(err){
console.error(err);
}
});
},
};
@ -122,10 +128,6 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
'Trash': 'Trash Can'
};
if($scope.field.fieldType === 'dropdown' || $scope.field.fieldType === 'radio'){
}
$scope.saveField = function(){
if($scope.options.isEdit){
$scope.myform.form_fields[field_index] = $scope.field;
@ -255,19 +257,6 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
});
};
//Populate local scope with rootScope methods/variables
$scope.update = $rootScope.update;
/*
** FormFields (ui-sortable) drag-and-drop configuration
*/
$scope.dropzone = {
handle: '.handle',
containment: '.dropzoneContainer',
cursor: 'grabbing'
};
/*
** Field CRUD Methods
*/

@ -24,9 +24,7 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
//Waits until deletionInProgress is false before running getSubmissions
$scope.$watch("deletionInProgress",function(newVal, oldVal){
if(newVal === oldVal) return;
if(newVal === false && $scope.waitingForDeletion) {
if(newVal !== oldVal && newVal === false && $scope.waitingForDeletion) {
$scope.getSubmissions();
$scope.waitingForDeletion = false;
}
@ -40,6 +38,52 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
}
};
/*
** Analytics Functions
*/
var formatGlobalStatistics = function(globalStatData){
if(!globalStatData || !globalStatData.length){
return {
visits: 0,
responses: 0,
conversion_rate: 0,
average_time: 0
};
}
return globalStatData[0];
}
var formatDeviceStatistics = function(deviceStatData){
var newStatItem = function(){
return {
visits: 0,
responses: 0,
conversion_rate: 0,
average_time: 0,
total_time: 0
};
};
var stats = {
desktop: newStatItem(),
tablet: newStatItem(),
phone: newStatItem(),
other: newStatItem()
};
if(deviceStatData && deviceStatData.length){
for(var i=0; i<deviceStatData.length; i++){
var currDevice = deviceStatData[i];
//_id here is deviceType of field due to aggregation in getVisitorData
if(stats[currDevice._id]){
stats[currDevice._id] = currDevice;
}
}
}
return stats;
};
$scope.getSubmissions = function(cb){
$http({
method: 'GET',
@ -80,11 +124,15 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
var data = response.data || [];
$scope.analyticsData = data[0];
$scope.analyticsData.globalStatistics = $scope.analyticsData.globalStatistics[0];
$scope.analyticsData.globalStatistics = formatGlobalStatistics($scope.analyticsData.globalStatistics);
$scope.analyticsData.deviceStatistics = formatDeviceStatistics($scope.analyticsData.deviceStatistics);
});
};
//Initialize analytics data
$scope.analyticsData.globalStatistics = formatGlobalStatistics();
$scope.analyticsData.deviceStatistics = formatDeviceStatistics();
$scope.handleSubmissionsRefresh();
$scope.getVisitors();
@ -103,38 +151,6 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
}
});
/*
** Analytics Functions
*/
var formatDeviceStatistics = function(deviceStatData){
var newStatItem = function(){
return {
visits: 0,
responses: 0,
conversion_rate: 0,
average_time: 0,
total_time: 0
};
};
var stats = {
desktop: newStatItem(),
tablet: newStatItem(),
phone: newStatItem(),
other: newStatItem()
};
if(deviceStatData.length){
for(var i=0; i<deviceStatData.length; i++){
var currDevice = deviceStatData[i];
if(stats[currDevice._id]){
stats[currDevice._id] = currDevice;
}
}
}
return stats;
};
/*
** Table Functions
*/
@ -172,14 +188,13 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
method: 'DELETE',
data: {deleted_submissions: delete_ids},
headers: {'Content-Type': 'application/json;charset=utf-8'}
}).success(function(data, status){
}).then(function(data, status){
$scope.deletionInProgress = true;
//Remove deleted ids from table
$scope.table.rows = $scope.table.rows.filter(function(field){
return !field.selected;
});
})
.error(function(err){
}, function(err){
$scope.deletionInProgress = true;
console.error(err);
});

@ -1,15 +1,15 @@
'use strict';
angular.module('view-form').directive('fieldIconDirective', function() {
angular.module('forms').directive('fieldIconDirective', function() {
return {
template: '<i class="{{typeIcon}}"></i>',
template: '<i class="{{iconTypeMap[typeName]}}"></i>',
restrict: 'E',
scope: {
typeName: '@'
typeName: '='
},
controller: function($scope){
var iconTypeMap = {
$scope.iconTypeMap = {
'textfield': 'fa fa-pencil-square-o',
'dropdown': 'fa fa-th-list',
'date': 'fa fa-calendar',
@ -27,7 +27,6 @@ angular.module('view-form').directive('fieldIconDirective', function() {
'yes_no': 'fa fa-toggle-on',
'number': 'fa fa-slack'
};
$scope.typeIcon = iconTypeMap[$scope.typeName];
}
};
});

@ -1,7 +1,7 @@
'use strict';
angular.module('forms').directive('shareFormDirective', ['$rootScope',
function ($rootScope) {
angular.module('forms').directive('shareFormDirective', ['$rootScope', '$translate', '$state',
function ($rootScope, $translate, $state) {
return {
templateUrl: 'modules/forms/admin/views/directiveViews/form/share-form.client.view.html',
restrict: 'E',
@ -10,6 +10,48 @@ angular.module('forms').directive('shareFormDirective', ['$rootScope',
},
controller: function($scope){
$scope.actualFormURL = $scope.actualformurl;
$scope.isCopied = false;
$scope.onCopySuccess = function(){
$scope.isCopied = true;
}
$scope.embedCode = "<iframe id='iframe' src='" + $scope.actualFormURL + "' style='width:100%;height:500px;'></iframe>"+
"<div style='font-family: Sans-Serif;font-size: 12px;color: #999;opacity: 0.5; padding-top: 5px;'>"+
$translate.instant('POWERED_BY')+
"<a href='https://www.tellform.com' style='color: #999' target='_blank'>TellForm</a>"+
"</div>";
/* Tab Routing Logic */
$scope.shareTabs = [
{
heading: $translate.instant('SHARE_YOUR_FORM'),
route: 'viewForm.share.share_form',
active: false
},
{
heading: $translate.instant('EMBED_YOUR_FORM'),
route: 'viewForm.share.embed_form',
active: false
}
];
$scope.go = function(tab){
$scope.isCopied = false;
tab.active = true;
$state.go(tab.route);
};
function setActiveTab() {
$scope.shareTabs.forEach(function(tab) {
tab.active = ($state.current.name === tab.route);
});
}
setActiveTab();
$scope.$on("$stateChangeSuccess", setActiveTab());
}
};
}

@ -4,7 +4,7 @@
angular.module('forms').service('FormFields', [ '$rootScope', '$translate', 'Auth',
function($rootScope, $translate, Auth) {
var language = Auth.ensureHasCurrentUser().language;
var language = $rootScope.language;
$translate.use(language);
this.types = [

@ -43,7 +43,7 @@
</div>
<div class="col-xs-1 col-sm-2">
<small class="pull-right">
<a class="btn btn-secondary view-form-btn" href="{{actualFormURL}}">
<a class="btn btn-secondary view-form-btn" href="{{actualFormURL}}" target="_blank">
<i class="fa fa-external-link"></i>
<span>
{{ 'VIEW_MY_TELLFORM' | translate }}

@ -22,10 +22,10 @@
<div class="col-sm-8 field-input">
<ui-select ng-model="myform.language" search-enabled="false" theme="selectize">
<ui-select-match>
{{ langCodeToWord[$select.selected] }}
{{ $root.langCodeToWord[$select.selected] }}
</ui-select-match>
<ui-select-choices repeat="language in languages">
<span ng-bind-html="langCodeToWord[language] | highlight: $select.search">
<ui-select-choices repeat="language in $root.languages">
<span ng-bind-html="$root.langCodeToWord[language] | highlight: $select.search">
</span>
</ui-select-choices>
</select>
@ -71,7 +71,7 @@
<div class="field-input col-sm-4 text-right">
<input class="toggle-switch" type="checkbox"
bs-switch ng-model="myform.hideFooter"
bs-switch ng-model="myform.showFooter"
switch-on-text="{{ 'ON' | translate }}"
switch-off-text="{{ 'OFF' | translate }}">
</div>

@ -13,23 +13,22 @@
<i class="status-light status-light-off fa fa-circle" ng-if="!myform.respondentNotifications.enabled"></i>
</div>
<div class="notification-row">
<h5>{{ 'ENABLE_RESPONDENT_NOTIFICATIONS' | translate }}</h5>
<h4>{{ 'ENABLE_RESPONDENT_NOTIFICATIONS' | translate }}</h4>
</div>
<div class="notification-row">
<input class="toggle-switch notification-toggle" type="checkbox" switch-size="small"
bs-switch ng-model="myform.respondentNotifications.enabled"
<input bs-switch class="toggle-switch notification-toggle" type="checkbox"
switch-size="small"
ng-model="myform.respondentNotifications.enabled"
switch-on-text="{{ 'ON' | translate }}"
switch-off-text="{{ 'OFF' | translate }}"
ng-disabled="!formHasEmailField"
ng-true-value="true"
ng-false-value="false">
switch-active="{{formHasEmailField}}">
</div>
</div>
<div class="row field" ng-if="formHasEmailField">
<div class="field-title col-sm-12">
<h5>{{ 'SEND_NOTIFICATION_TO' | translate }}:</h5>
<h4>{{ 'SEND_NOTIFICATION_TO' | translate }}</h4>
</div>
<div class="col-sm-12 field-input">
@ -37,7 +36,7 @@
<ui-select-match placeholder="{{ 'OPTION_PLACEHOLDER' | translate }}">
{{$select.selected.title}}
</ui-select-match>
<ui-select-choices repeat="field.globalId as field in emailFields | filter: { title: $select.search }">
<ui-select-choices repeat="field.id as field in emailFields | filter: { title: $select.search }">
<span ng-bind-html="field.title | highlight: $select.search">
</span>
</ui-select-choices>
@ -47,7 +46,7 @@
<div class="row field">
<div class="field-title col-sm-12">
<h5>{{ 'REPLY_TO' | translate }}:</h5>
<h4>{{ 'REPLY_TO' | translate }}</h4>
</div>
<div class="col-sm-12 field-input">
@ -60,25 +59,50 @@
<div class="row field">
<div class="field-title col-sm-12">
<h5>{{ 'EMAIL_SUBJECT' | translate }}:</h5>
<h4>{{ 'EMAIL_SUBJECT' | translate }}</h4>
</div>
<div class="col-sm-12 field-input">
<text-angular class="email-subject" ng-model="myform.respondentNotifications.subject" ta-toolbar="[['insertField']]" ta-default-wrap="n" ta-unsafe-sanitizer="true">
</text-angular>
<ng-quill-editor modules="customModules"
ng-model="myform.respondentNotifications.subject">
<ng-quill-toolbar>
<div id="ng-quill-toolbar">
<span class="ql-formats">
<select class="ql-placeholder">
<option ng-repeat="placeholder in customModules.placeholder.placeholders" value="{{placeholder.id}}">
{{placeholder.label}}
</option>
</select>
</span>
</div>
</ng-quill-toolbar>
</ng-quill-editor>
</div>
</div>
<div class="row field">
<div class="field-title col-sm-12">
<h5>{{ 'EMAIL_MESSAGE' | translate }}</h5>
<h4>{{ 'EMAIL_MESSAGE' | translate }}</h4>
</div>
<div class="col-sm-12 field-input">
<text-angular class="email-subject" ng-model="myform.respondentNotifications.htmlTemplate"
ta-toolbar="[['bold','italics', 'insertField']]"
ta-unsafe-sanitizer="true">
</text-angular>
<div ng-bind="myform.respondentNotifications.htmlTemplate"></div>
<ng-quill-editor modules="customModules"
ng-model="myform.respondentNotifications.htmlTemplate">
<ng-quill-toolbar>
<div id="ng-quill-toolbar">
<span class="ql-formats">
<button class="ql-bold"></button>
<button class="ql-italic"></button>
</span>
<span class="ql-formats">
<select class="ql-placeholder">
<option ng-repeat="placeholder in customModules.placeholder.placeholders" value="{{placeholder.id}}">
{{placeholder.label}}
</option>
</select>
</span>
</div>
</ng-quill-toolbar>
</ng-quill-editor>
</div>
</div>
</div>

@ -8,7 +8,7 @@
</div>
<div class='notification-row'>
<h5>{{ 'ENABLE_SELF_NOTIFICATIONS' | translate }}</h5>
<h4>{{ 'ENABLE_SELF_NOTIFICATIONS' | translate }}</h4>
</div>
<div class='notification-row'>
@ -21,7 +21,7 @@
<div class="row field">
<div class="field-title col-sm-12">
<h5>{{ 'SEND_NOTIFICATION_TO' | translate }}</h5>
<h4>{{ 'SEND_NOTIFICATION_TO' | translate }}</h4>
</div>
<div class="col-sm-12 field-input">
@ -34,7 +34,7 @@
<div class="row field" ng-if="formHasEmailField">
<div class="field-title col-sm-12">
<h5>{{ 'REPLY_TO' | translate }}</h5>
<h4>{{ 'REPLY_TO' | translate }}</h4>
</div>
<div class="col-sm-12 ui-select field-input">
@ -42,7 +42,7 @@
<ui-select-match placeholder="{{ 'OPTION_PLACEHOLDER' | translate }}">
{{$select.selected.title}}
</ui-select-match>
<ui-select-choices repeat="field.globalId as field in emailFields | filter: { title: $select.search }">
<ui-select-choices repeat="field.id as field in emailFields | filter: { title: $select.search }">
<span ng-bind-html="field.title | highlight: $select.search">
</span>
</ui-select-choices>
@ -52,22 +52,51 @@
<div class="row field">
<div class="field-title col-sm-12">
<h5>{{ 'EMAIL_SUBJECT' | translate }}</h5>
<h4>{{ 'EMAIL_SUBJECT' | translate }}</h4>
</div>
<div class="col-sm-12 field-input">
<text-angular class="email-subject" ng-model="myform.selfNotifications.subject" ta-toolbar="[['insertField']]"
ta-unsafe-sanitizer="true" ta-default-wrap="n"></text-angular>
<ng-quill-editor modules="customModules"
ng-model="myform.selfNotifications.subject">
<ng-quill-toolbar>
<div id="ng-quill-toolbar">
<span class="ql-formats">
<select class="ql-placeholder">
<option ng-repeat="placeholder in customModules.placeholder.placeholders" value="{{placeholder.id}}">
{{placeholder.label}}
</option>
</select>
</span>
</div>
</ng-quill-toolbar>
</ng-quill-editor>
</div>
</div>
<div class="row field">
<div class="field-title col-sm-12">
<h5>{{ 'EMAIL_MESSAGE' | translate }}</h5>
<h4>{{ 'EMAIL_MESSAGE' | translate }}</h4>
</div>
<div class="col-sm-12 field-input">
<text-angular ng-model="myform.selfNotifications.htmlTemplate" ta-toolbar="[['bold','italics', 'insertField']]" ta-unsafe-sanitizer="true"></text-angular>
<ng-quill-editor modules="customModules"
ng-model="myform.selfNotifications.htmlTemplate">
<ng-quill-toolbar>
<div id="ng-quill-toolbar">
<span class="ql-formats">
<button class="ql-bold"></button>
<button class="ql-italic"></button>
</span>
<span class="ql-formats">
<select class="ql-placeholder">
<option ng-repeat="placeholder in customModules.placeholder.placeholders" value="{{placeholder.id}}">
{{placeholder.label}}
</option>
</select>
</span>
</div>
</ng-quill-toolbar>
</ng-quill-editor>
</div>
</div>
</div>

@ -0,0 +1,27 @@
<div class="row share-row">
<div class="col-sm-12">
{{ 'COPY_AND_PASTE' | translate }}
</div>
</div>
<div class="row">
<div class="col-sm-10 form-input">
<span>
<div id="copyEmbedded" class="form-control">
{{ embedCode }}
</div>
</span>
</div>
<div class="col-sm-2">
<button class="btn btn-secondary view-form-btn"
ngclipboard data-clipboard-text="{{embedCode}}" ngclipboard-success="onCopySuccess()">
<span ng-if="!isCopied">
{{ 'COPY' | translate }} <i class="fa fa-clipboard" aria-hidden="true"></i>
</span>
<span ng-if="isCopied">
{{ 'COPIED_LABEL' | translate }}
</span>
</button>
</div>
</div>

@ -0,0 +1,23 @@
<div class="row share-row">
<div class="col-sm-12">
{{ 'TELLFORM_URL' | translate }}
</div>
</div>
<div class="row">
<div class="col-sm-10 form-input">
<span>
<input id="copyURL" ng-value="actualFormURL" class="form-control ng-pristine ng-untouched ng-valid">
</span>
</div>
<div class="col-sm-2">
<button class="btn btn-secondary view-form-btn"
ngclipboard data-clipboard-target="#copyURL" ngclipboard-success="onCopySuccess()">
<span ng-if="!isCopied">
{{ 'COPY' | translate }} <i class="fa fa-clipboard" aria-hidden="true"></i>
</span>
<span ng-if="isCopied">
{{ 'COPIED_LABEL' | translate }}
</span>
</button>
</div>
</div>

@ -1,16 +1,21 @@
<div class="config-form container">
<div class="row">
<uib-tabset active="activePill" type="pills">
<uib-tab ng-repeat="tab in configureTabs" active="tab.active" select="go(tab)" heading="{{tab.heading}}">
<div ui-view></div>
<div ng-if="tab.active" ui-view></div>
</uib-tab>
</uib-tabset>
</div>
<div class="row">
<div class="col-sm-offset-4 col-sm-2">
<button class="btn btn-signup btn-rounded" type="button" ng-click="update(false, myform, false, false, null)"><i class="icon-arrow-left icon-white"></i>{{ 'SAVE_CHANGES' | translate }}</button>
<button class="btn btn-signup btn-rounded" v-busy="saveInProgress"
v-busy-label="{{ 'WAIT_LABEL' | translate }}"
v-busy-text="{{ 'SAVE_CHANGES' | translate }}"
v-pressable ng-click="saveChanges()">
<i class="icon-arrow-left icon-white"></i>
</button>
</div>
<div class="col-sm-1">
<button class="btn btn-secondary btn-rounded" type="button" ng-click="resetForm()"><i class="icon-eye-open icon-white"></i>{{ 'CANCEL' | translate }}</button>

@ -186,7 +186,6 @@
</div>
</script>
<!-- Edit EndPage Modal Dialog Template -->
<script type="text/ng-template" id="editEndPageModal.html" class="edit-endpage-modal">
<div class="modal-body">
@ -488,11 +487,12 @@
</div>
<div class="panel-group row" class="draggable" ng-model="addField.types">
<div class="col-xs-12 col-sm-12 col-md-6" ng-repeat="type in addField.types" style="padding-top:7.5px;">
<div class="panel panel-default" style="background-color:#f5f5f5;">
<div class="col-xs-12 col-sm-12 col-md-6"
ng-repeat="type in addField.types" style="padding-top: 7.5px;">
<div class="panel panel-default" style="background-color: #f5f5f5;">
<div class="panel-heading" ng-click="addNewField(type.name)" style="cursor: pointer; font-size:12px; padding-left: 10px; padding-right: 10px;">
<span>
<field-icon-directive type-name="{{type.name}}">
<field-icon-directive type-name="type.name">
</field-icon-directive>
</span>
@ -527,13 +527,14 @@
<div class="panel-group dropzone col-xs-12" ui-sortable="sortableOptions" ng-model="myform.form_fields">
<div class="col-xs-12 field-row" ng-repeat="field in myform.form_fields track by $id($index)" ng-if="!field.deletePreserved">
<div class="col-xs-10">
<div class="col-xs-12 field-row sortable-fields" ng-repeat="field in myform.form_fields track by $index">
<div class="col-xs-10 sortable-panel">
<div class="panel panel-default" ng-click="openEditModal(field, true, $index)">
<div class="panel-heading">
<div class="row">
<span class="col-xs-1" ng-switch="field.fieldType">
<field-icon-directive type-name="{{field.fieldType}}"></field-icon-directive>
<field-icon-directive type-name="field.fieldType">
</field-icon-directive>
</span>
<span class="col-xs-11">
{{field.title}}
@ -552,7 +553,6 @@
</div>
</div>
</div>
<div class="col-xs-1 box">
<div class="panel tool-panel panel-default">
<div class="panel-heading" style="padding: 10px 10px;" ng-click="duplicateField($index)">

@ -1,4 +1,4 @@
<div class="submissions-table row">
<div class="submissions-table container-fluid">
<div class="row text-center analytics" ng-if="analyticsData">
<div class="col-xs-12 header-title">
<div class="col-xs-3">

@ -1,44 +1,9 @@
<div class="config-form row">
<div class="config-form container">
<div class="row">
<div class="col-sm-12">
<uib-tabset active="activePill" vertical="true" type="pills">
<uib-tab index="0" heading="{{ 'SHARE_YOUR_FORM' | translate }}">
<div class="row">
<div class="col-sm-12">
{{ 'TELLFORM_URL' | translate }}
</div>
<div class="col-sm-8 form-input">
<span ngclipboard data-clipboard-target="#copyURL"> <input id="copyURL" ng-value="actualFormURL" class="form-control ng-pristine ng-untouched ng-valid"> </span>
</div>
<div class="col-sm-4">
<button class="btn btn btn-secondary view-form-btn" ngclipboard data-clipboard-target="#copyURL">
{{ 'COPY' | translate }} <i class="fa fa-clipboard" aria-hidden="true"></i>
</button>
</div>
</div>
</uib-tab>
<uib-tab index="1" heading="{{ 'EMBED_YOUR_FORM' | translate }}">
<div class="row">
<div class="col-sm-12">
{{ 'COPY_AND_PASTE' | translate }}
</div>
<div class="col-sm-8 form-input">
<span ngclipboard data-clipboard-target="#copyEmbedded">
<textarea id="copyEmbedded" class="form-control ng-pristine ng-untouched ng-valid" style="min-height:200px; width:100%; background-color: #FFFFCC; color: #30313F;">
&lt;!-- {{ 'CHANGE_WIDTH_AND_HEIGHT' | translate }} --&gt;
<iframe id="iframe" src="{{actualFormURL}}" style="width:100%;height:500px;"></iframe>
<div style="font-family: Sans-Serif;font-size: 12px;color: #999;opacity: 0.5; padding-top: 5px;">{{ 'POWERED_BY' | translate }} <a href="https://www.tellform.com" style="color: #999" target="_blank">TellForm</a></div>
</textarea>
</span>
</div>
<div class="col-sm-4">
<button class="btn btn btn-secondary view-form-btn" ngclipboard data-clipboard-target="#copyEmbedded">
{{ 'COPY' | translate }} <i class="fa fa-clipboard" aria-hidden="true"></i>
</button>
</div>
</div>
</uib-tab>
</uib-tabset>
</div>
<uib-tabset active="activePill" type="pills">
<uib-tab ng-repeat="tab in shareTabs" active="tab.active" select="go(tab)" heading="{{tab.heading}}">
<div ui-view></div>
</uib-tab>
</uib-tabset>
</div>
</div>

@ -52,9 +52,9 @@
<div class="col-xs-5 field-title text-left"> {{ 'LANGUAGE' | translate }} </div>
<div class="col-xs-12 field-input">
<div class="button custom-select">
<select style="color:black;" name="language" required ng-model="formLanguage" ng-init="formLanguage = user.language">
<option ng-repeat="language in languages" value="{{language}}">
{{language}}
<select style="color:black;" name="language" required ng-init="formLanguage = user.language" ng-model="formLanguage">
<option ng-repeat="language in $root.languages" value="{{language}}">
{{ $root.langCodeToWord[language] }}
</option>
</select>
</div>

@ -17,34 +17,8 @@ angular.module('forms').run(['Menus',
}
]).filter('secondsToDateTime', [function() {
return function(seconds) {
return new Date(1970, 0, 1).setSeconds(seconds);
return new Date(0).setSeconds(seconds);
};
}]).filter('formValidity', [function(){
return function(formObj){
if(formObj && formObj.form_fields && formObj.visible_form_fields){
//get keys
var formKeys = Object.keys(formObj);
//we only care about things that don't start with $
var fieldKeys = formKeys.filter(function(key){
return key[0] !== '$';
});
var fields = formObj.form_fields;
var valid_count = fields.filter(function(field){
if(typeof field === 'object' && field.fieldType !== 'statement' && field.fieldType !== 'rating'){
return !!(field.fieldValue);
} else if(field.fieldType === 'rating'){
return true;
}
}).length;
return valid_count - (formObj.form_fields.length - formObj.visible_form_fields.length);
}
return 0;
};
}]).filter('trustSrc', ['$sce', function($sce){
return function(formUrl){
return $sce.trustAsResourceUrl(formUrl);
@ -55,31 +29,4 @@ angular.module('forms').run(['Menus',
directive.replace = true;
return $delegate;
});
}]).config(['$provide', function ($provide){
$provide.decorator('taOptions', ['$delegate', 'taRegisterTool', '$translate', '$window', function(taOptions, taRegisterTool, $translate, $window) {
taRegisterTool('insertField', {
display: '<div class="dropdown" uib-dropdown is-open="isopen">\
<div class="dropdown-toggle" ng-disabled="isDisabled()" uib-dropdown-toggle>\
<span>{{ "ADD_VARIABLE_BUTTON" | translate }}</span>\
<b class="caret"></b>\
</div>\
<ul class="dropdown-menu">\
<li ng-repeat="field in $root.myform.form_fields" ng-click="onClickField(field.globalId, field.title)">\
{{field.title}}\
</li>\
</ul>\
</div>',
onClickField: function(field_id, field_name){
this.$editor().wrapSelection('insertHTML', '<var class="tag" contenteditable="false" id="field:' + field_id + '">' + field_name + '</var>', false);
},
action: function(){
}
});
taOptions.defaultTagAttributes['var'] = {
'contenteditable': 'false'
};
return taOptions;
}]);
}]);

@ -84,13 +84,22 @@ angular.module('forms').config(['$stateProvider',
templateUrl: 'modules/forms/admin/views/adminTabs/configureTabs/respondent-notifications.html'
})
.state('viewForm.share', {
abstract: true,
url: '/share',
templateUrl: 'modules/forms/admin/views/adminTabs/share.html'
}).state('viewForm.share.share_form', {
url: '/share_form',
templateUrl: 'modules/forms/admin/views/adminTabs/shareTabs/share_form.html'
}).state('viewForm.share.embed_form', {
url: '/embed_form',
templateUrl: 'modules/forms/admin/views/adminTabs/shareTabs/embed_form.html'
})
.state('viewForm.design', {
url: '/design',
templateUrl: 'modules/forms/admin/views/adminTabs/design.html'
}).state('viewForm.share', {
url: '/share',
templateUrl: 'modules/forms/admin/views/adminTabs/share.html'
}).state('viewForm.analyze', {
}).state('viewForm.analyze', {
url: '/analyze',
templateUrl: 'modules/forms/admin/views/adminTabs/analyze.html'
});

@ -3,6 +3,6 @@
// Use Application configuration module to register a new module
ApplicationConfiguration.registerModule('forms', [
'ngFileUpload', 'ui.date', 'ui.sortable',
'angular-input-stars', 'users', 'ngclipboard', 'textAngular',
'frapontillo.bootstrap-switch'
'angular-input-stars', 'users', 'ngclipboard',
'frapontillo.bootstrap-switch', 'ngQuill'
]);//, 'colorpicker.module' @TODO reactivate this module

@ -65,7 +65,6 @@
});
}));
// The $resource service augments the response object with methods for updating and deleting the resource.
// If we were to use the standard toEqual matcher, our tests would fail because the test values would not match
// the responses exactly. To solve the problem, we define a new toEqualData Jasmine matcher.
@ -95,34 +94,32 @@
$provide.service('User', function($q) {
return {
getCurrent: function() {
var deferred = $q.defer();
deferred.resolve( JSON.stringify(sampleUser) );
return deferred.promise;
return {
then: function(onFulfilled, onRejected, progressBack) {
return onFulfilled(sampleUser);
}
};
},
login: function(credentials) {
var deferred = $q.defer();
if( credentials.password === sampleUser.password && credentials.username === sampleUser.username){
deferred.resolve( JSON.stringify(sampleUser) );
}else {
deferred.resolve('Error: User could not be loggedin');
}
return deferred.promise;
return {
then: function(onFulfilled, onRejected, progressBack) {
return onFulfilled(sampleUser);
}
};
},
logout: function() {
var deferred = $q.defer();
deferred.resolve(null);
return deferred.promise;
return {
then: function(onFulfilled, onRejected, progressBack) {
return onFulfilled(null);
}
};
},
signup: function(credentials) {
var deferred = $q.defer();
if( credentials.password === sampleUser.password && credentials.username === sampleUser.username){
deferred.resolve( JSON.stringify(sampleUser) );
}else {
deferred.resolve('Error: User could not be signed up');
}
return deferred.promise;
return {
then: function(onFulfilled, onRejected, progressBack) {
return onFulfilled(sampleUser);
}
};
}
};
});
@ -133,13 +130,14 @@
$provide.service('Auth', function() {
return {
ensureHasCurrentUser: function() {
return sampleUser;
return {
then: function(onFulfilled, onRejected, progressBack) {
return onFulfilled(sampleUser);
}
};
},
isAuthenticated: function() {
return true;
},
getUserState: function() {
return true;
}
};
});

@ -30,7 +30,8 @@
{fieldType:'checkbox', title:'nascar', fieldValue: '', deletePreserved: false},
{fieldType:'checkbox', title:'hockey', fieldValue: '', deletePreserved: false}
],
_id: '525a8422f6d0f87f0e407a33'
_id: '525a8422f6d0f87f0e407a33',
submissionNum: 0
},{
title: 'Form Title2',
admin: '39223933b1f1dea0ce12fab9',
@ -40,7 +41,8 @@
{fieldType:'checkbox', title:'nascar', fieldValue: '', deletePreserved: false},
{fieldType:'checkbox', title:'hockey', fieldValue: '', deletePreserved: false}
],
_id: '52f6d0f87f5a407a384220e3'
_id: '52f6d0f87f5a407a384220e3',
submissionNum: 0
},{
title: 'Form Title3',
admin: '2fab9ed873937f0e1dea0ce1',
@ -50,7 +52,8 @@
{fieldType:'checkbox', title:'nascar', fieldValue: '', deletePreserved: false},
{fieldType:'checkbox', title:'hockey', fieldValue: '', deletePreserved: false}
],
_id: '922f6d0f87fed8730e4e1233'
_id: '922f6d0f87fed8730e4e1233',
submissionNum: 0
}
];
@ -102,6 +105,7 @@
$httpBackend.whenGET(/\.html$/).respond('');
$httpBackend.whenGET('/users/me/').respond('');
$httpBackend.whenGET('/forms').respond(200, sampleFormList);
// Initialize the Forms controller.
createListFormsController = function(){
@ -188,6 +192,5 @@
$httpBackend.flush();
$state.ensureAllTransitionsHappened();
}));
});
}());

@ -1,165 +0,0 @@
'use strict';
(function() {
// Forms Controller Spec
describe('SubmitForm Controller Tests', function() {
// Initialize global variables
var SubmitFormController,
createSubmitFormController,
scope,
$httpBackend,
$stateParams,
$location,
$state,
vm;
var sampleUser = {
firstName: 'Full',
lastName: 'Name',
email: 'test@test.com',
username: 'test@test.com',
password: 'password',
provider: 'local',
roles: ['user'],
_id: 'ed873933b1f1dea0ce12fab9'
};
var sampleForm = {
title: 'Form Title',
admin: 'ed873933b1f1dea0ce12fab9',
language: 'english',
form_fields: [
{'fieldType':'textfield', 'title':'First Name', 'fieldValue': '', 'deletePreserved': false},
{'fieldType':'checkbox', 'title':'nascar', 'fieldValue': '', 'deletePreserved': false},
{'fieldType':'checkbox', 'title':'hockey', 'fieldValue': '', 'deletePreserved': false}
],
isLive: false,
_id: '525a8422f6d0f87f0e407a33',
visible_form_fields: [
{'fieldType':'textfield', 'title':'First Name', 'fieldValue': '', 'deletePreserved': false},
{'fieldType':'checkbox', 'title':'nascar', 'fieldValue': '', 'deletePreserved': false},
{'fieldType':'checkbox', 'title':'hockey', 'fieldValue': '', 'deletePreserved': false}
]
};
// Load the main application module
beforeEach(function(){
module('view-form'),
module(ApplicationConfiguration.applicationModuleName)
});
beforeEach(module('module-templates'));
beforeEach(module('stateMock'));
//Mock Users Service
beforeEach(module(function($provide) {
$provide.service('myForm', function($q) {
var deferred = $q.defer();
deferred.resolve(sampleForm);
return deferred.promise;
});
}));
//Mock Authentication Service
beforeEach(module(function($provide) {
$provide.service('Auth', function() {
return {
ensureHasCurrentUser: function() {
return sampleUser;
},
isAuthenticated: function() {
return true;
},
getUserState: function() {
return true;
}
};
});
}));
//Mock Users Service
beforeEach(module(function($provide) {
$provide.service('User', function($q) {
return {
getCurrent: function() {
var deferred = $q.defer();
deferred.resolve( JSON.stringify(sampleUser) );
return deferred.promise;
},
login: function(credentials) {
var deferred = $q.defer();
if( credentials.password === sampleUser.password && credentials.username === sampleUser.username){
deferred.resolve( JSON.stringify(sampleUser) );
}else {
deferred.resolve('Error: User could not be loggedin');
}
return deferred.promise;
},
logout: function() {
var deferred = $q.defer();
deferred.resolve(null);
return deferred.promise;
},
signup: function(credentials) {
var deferred = $q.defer();
if( credentials.password === sampleUser.password && credentials.username === sampleUser.username){
deferred.resolve( JSON.stringify(sampleUser) );
}else {
deferred.resolve('Error: User could not be signed up');
}
return deferred.promise;
}
};
});
}));
// The injector ignores leading and trailing underscores here (i.e. _$httpBackend_).
// This allows us to inject a service but then attach it to a variable
// with the same name as the service.
beforeEach(inject(function($controller, $rootScope, _$state_, _$location_, _$stateParams_, _$httpBackend_, CurrentForm) {
// Set a new global scope
scope = $rootScope.$new();
//Set CurrentForm
CurrentForm.setForm(sampleForm);
// Point global variables to injected services
$stateParams = _$stateParams_;
$httpBackend = _$httpBackend_;
$location = _$location_;
$state = _$state_;
$httpBackend.whenGET('/users/me/').respond('');
// Initialize the Forms controller.
createSubmitFormController = function(){
return $controller('SubmitFormController', { $scope: scope });
};
vm = createSubmitFormController();
}));
/*
//FIX ME: Need to get thi sto work with view-form dependency
it('on controller instantiation it should populate $scope.myform with current Form', inject(function() {
//var controller = createSubmitFormController();
console.log(vm);
$stateParams.formId = '525a8422f6d0f87f0e407a33';
// Set GET response
$httpBackend.expectGET(/^(\/forms\/)([0-9a-fA-F]{24})$/).respond(200, sampleForm);
// Test scope value
expect( scope.myform ).toEqualData(sampleForm);
expect( scope.hideNav ).toEqual(false);
}));
*/
});
}());

@ -26,15 +26,11 @@
{fieldType:'checkbox', title:'nascar', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed83b0ce121f17393deafab9'},
{fieldType:'checkbox', title:'hockey', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed8317393deab0ce121ffab9'}
],
pdf: {},
pdfFieldMap: {},
startPage: {
showStart: false
},
hideFooter: false,
isGenerated: false,
showFooter: false,
isLive: false,
autofillPDFs: false,
_id: '525a8422f6d0f87f0e407a33'
};

@ -59,8 +59,7 @@
endPage: {
showEnd: false
},
hideFooter: false,
isGenerated: false,
showFooter: false,
isLive: false,
_id: '525a8422f6d0f87f0e407a33'
};
@ -141,8 +140,7 @@
sampleForm.submissions = sampleSubmissions;
$httpBackend.whenGET('/users/me/').respond('');
$httpBackend.whenGET(/^(\/forms\/)([0-9a-fA-F]{24})$/).respond(200, sampleForm);
$httpBackend.whenGET('/forms').respond(200, sampleForm);
$httpBackend.whenGET('/forms').respond(200, [sampleForm]);
$httpBackend.whenGET(/^(\/forms\/)([0-9a-fA-F]{24})$/).respond(200, sampleForm);
$httpBackend.whenGET(/^(\/forms\/)([0-9a-fA-F]{24})\/submissions$/).respond(200, sampleSubmissions);
$httpBackend.whenGET(/^(\/forms\/)([0-9a-fA-F]{24})\/visitors$/).respond(200, sampleVisitors);

@ -26,14 +26,11 @@
{fieldType:'checkbox', title:'nascar', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed83b0ce121f17393deafab9'},
{fieldType:'checkbox', title:'hockey', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed8317393deab0ce121ffab9'}
],
pdf: {},
pdfFieldMap: {},
startPage: {
showStart: false,
buttons: []
},
hideFooter: false,
isGenerated: false,
showFooter: false,
isLive: false,
_id: '525a8422f6d0f87f0e407a33'
};
@ -109,40 +106,39 @@
}));
beforeEach(inject(function($compile, $controller, $rootScope, _$httpBackend_) {
// Point global variables to injected services
$httpBackend = _$httpBackend_;
$httpBackend.whenGET('/users/me/').respond('');
$httpBackend.whenGET('/forms').respond(200, [sampleForm]);
$httpBackend.whenGET(/^(\/forms\/)([0-9a-fA-F]{24})$/).respond(200, sampleForm);
//Instantiate directive.
var tmp_scope = $rootScope.$new();
tmp_scope.myform = _.cloneDeep(sampleForm);
tmp_scope.user = _.cloneDeep(sampleUser)
//gotacha: Controller and link functions will execute.
el = angular.element('<edit-form-directive myform=\'myform\'></edit-form-directive>');
$compile(el)(tmp_scope);
$rootScope.$digest();
// Point global variables to injected services
$httpBackend = _$httpBackend_;
//$httpBackend.whenGET(/.+\.html$/).respond('');
$httpBackend.whenGET('/users/me/').respond('');
//Grab controller instance
controller = el.controller();
//Grab scope. Depends on type of scope.
//See angular.element documentation.
scope = el.isolateScope() || el.scope();
scope.update = function(updateImmediately, data, isDiffed, refreshAfterUpdate, cb){
if(cb) cb();
};
scope.update = function(updateImmediately, data, isDiffed, refreshAfterUpdate, cb){
if(cb) cb();
};
}));
describe('> Form Field >',function(){
beforeEach(function(){
scope.myform = _.cloneDeep(sampleForm);
});
beforeEach(function(){
scope.myform = _.cloneDeep(sampleForm);
})
it('$scope.addNewField() should open the new field modal', function() {
//Run controller methods
@ -152,9 +148,10 @@
});
it('$scope.deleteField() should DELETE a field to $scope.myform.form_fields', function() {
spyOn(scope, 'update');
expect(scope.myform.form_fields).toEqualData(sampleForm.form_fields);
//Run controller methods
scope.deleteField(0);

@ -97,7 +97,9 @@
currType = FormFields.types[i];
currClass = faClasses[currType.name];
var element = $compile('<field-icon-directive type-name="'+currType.name+'"></field-icon-directive>')(scope);
scope.currType = currType;
var element = $compile('<field-icon-directive type-name="currType.name"></field-icon-directive>')(scope);
scope.$digest();
expect(currClass).toBeDefined();

@ -46,16 +46,13 @@ var MobileDetect = function(userAgentStr){
visible_form_fields: [
{fieldType:'textfield', title:'First Name', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed873933b0ce121f1deafab9'},
{fieldType:'checkbox', title:'nascar', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed83b0ce121f17393deafab9'},
{fieldType:'checkbox', title:'hockey', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed8317393deab0ce121ffab9'} ],
pdf: {},
pdfFieldMap: {},
{fieldType:'checkbox', title:'hockey', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed8317393deab0ce121ffab9'}
],
startPage: {
showStart: false
},
hideFooter: false,
isGenerated: false,
showFooter: false,
isLive: false,
autofillPDFs: false,
_id: '525a8422f6d0f87f0e407a33'
};
@ -130,7 +127,7 @@ var MobileDetect = function(userAgentStr){
// Point global variables to injected services
$httpBackend = _$httpBackend_;
$httpBackend.whenGET('/users/me/').respond('');
$httpBackend.whenGET('/forms').respond('');
//Instantiate directive.
var tmp_scope = $rootScope.$new();

@ -18,11 +18,13 @@
var timeSpent = 0;
TimeCounter.restartClock();
setTimeout(function(){
// TODO: David - come up with a better way to test this that is time-efficient
/*setTimeout(function(){
timeSpent = TimeCounter.stopClock();
expect(timeSpent).toEqual(1);
},1000);
expect(timeSpent).toBeGreaterThanOrEqual(3);
done();
}, 3000);
*/
});
});
}());

@ -9,7 +9,7 @@ angular.module('stateMock').service('$state', function($q){
if(expectedState !== stateName){
throw Error('Expected transition to state: ' + expectedState + ' but transitioned to ' + stateName );
}
}else{
} else {
throw Error('No more transitions were expected! Tried to transition to '+ stateName );
}
var deferred = $q.defer();

@ -4,26 +4,25 @@
angular.module('users').config(['$stateProvider',
function($stateProvider) {
var checkLoggedin = function($q, $timeout, $state, User, Auth) {
var deferred = $q.defer();
if (Auth.currentUser && Auth.currentUser.email) {
$timeout(deferred.resolve);
}
else {
Auth.currentUser = User.getCurrent(
function() {
Auth.login();
$timeout(deferred.resolve());
},
function() {
Auth.logout();
$timeout(deferred.reject());
$state.go('signin', {reload: true});
});
}
var checkCurrentUser = function($q, $state, User, Auth) {
var deferred = $q.defer();
if (Auth.currentUser && Auth.currentUser.email) {
deferred.resolve(Auth.currentUser);
} else {
User.getCurrent().then(
function(user) {
Auth.login();
deferred.resolve(user);
},
function() {
Auth.logout();
deferred.reject();
$state.go('signin', {reload: true});
});
}
return deferred.promise;
return deferred.promise;
};
var checkSignupDisabled = function($window, $timeout, $q) {
@ -40,24 +39,19 @@ angular.module('users').config(['$stateProvider',
$stateProvider.
state('profile', {
resolve: {
loggedin: checkLoggedin
currentUser: ['$q', '$state', 'User', 'Auth', checkCurrentUser]
},
url: '/settings/profile',
templateUrl: 'modules/users/views/settings/edit-profile.client.view.html'
templateUrl: 'modules/users/views/settings/edit-profile.client.view.html',
controller: 'SettingsController'
}).
state('password', {
resolve: {
loggedin: checkLoggedin
},
currentUser: ['$q', '$state', 'User', 'Auth', checkCurrentUser]
},
url: '/settings/password',
templateUrl: 'modules/users/views/settings/change-password.client.view.html'
}).
state('accounts', {
resolve: {
loggedin: checkLoggedin
},
url: '/settings/accounts',
templateUrl: 'modules/users/views/settings/social-accounts.client.view.html'
templateUrl: 'modules/users/views/settings/change-password.client.view.html',
controller: 'SettingsController'
}).
state('signup', {
resolve: {

@ -2,45 +2,44 @@
angular.module('users').controller('AuthenticationController', ['$scope', '$location', '$state', '$rootScope', 'User', 'Auth', '$translate', '$window',
function($scope, $location, $state, $rootScope, User, Auth, $translate, $window) {
$scope = $rootScope;
$scope.credentials = {};
//This helps us test the controller by allowing tests to inject their own scope variables
if(!$scope.credentials) $scope.credentials = {};
if(!$scope.forms) $scope.forms = {};
$scope.error = '';
$scope.forms = {};
var statesToIgnore = ['', 'home', 'signin', 'resendVerifyEmail', 'verify', 'signup', 'signup-success', 'forgot', 'reset-invalid', 'reset', 'reset-success'];
$scope.signin = function() {
if($scope.forms && $scope.forms.signinForm.$valid){
if($scope.credentials.hasOwnProperty('username') && $scope.forms.hasOwnProperty('signinForm') && $scope.forms.signinForm.$valid){
User.login($scope.credentials).then(
function(response) {
Auth.login(response);
$scope.user = $rootScope.user = Auth.ensureHasCurrentUser();
function(currUser) {
Auth.login(currUser);
$rootScope.user = $scope.user = currUser;
if(statesToIgnore.indexOf($state.previous.state.name) === -1) {
if($state.previous && statesToIgnore.indexOf($state.previous.state.name) === -1) {
$state.go($state.previous.state.name, $state.previous.params);
} else {
$state.go('listForms');
}
},
function(error) {
$rootScope.user = Auth.ensureHasCurrentUser();
$scope.user = $rootScope.user;
$scope.error = error;
console.error('loginError: '+error);
}
);
}
}
};
$scope.signup = function() {
if($scope.credentials === 'admin'){
//TODO - David : need to put this somewhere more appropriate
if($scope.credentials.username === 'admin'){
$scope.error = 'Username cannot be \'admin\'. Please pick another username.';
return;
}
if($scope.forms && $scope.forms.signupForm.$valid){
if($scope.credentials && $scope.forms.hasOwnProperty('signupForm') && $scope.forms.signupForm.$valid){
User.signup($scope.credentials).then(
function(response) {
$state.go('signup-success');

@ -1,11 +1,10 @@
'use strict';
angular.module('users').controller('PasswordController', ['$scope', '$stateParams', '$state', 'User', '$translate', '$window',
function($scope, $stateParams, $state, User, $translate, $window) {
$translate.use($window.locale);
angular.module('users').controller('PasswordController', ['$scope', '$stateParams', '$state', 'User', '$window',
function($scope, $stateParams, $state, User, $window) {
$scope.error = '';
$scope.forms = {};
if(!$scope.forms) $scope.forms = {};
// Submit forgotten password account id
$scope.askForPasswordReset = function() {
@ -25,7 +24,7 @@ angular.module('users').controller('PasswordController', ['$scope', '$stateParam
// Change user password
$scope.resetUserPassword = function() {
if(!$scope.forms.resetPasswordForm.$invalid){
if($scope.forms.hasOwnProperty('resetPasswordForm') && $scope.forms.resetPasswordForm.$valid){
$scope.success = $scope.error = null;
User.resetPassword($scope.passwordDetails, $stateParams.token).then(
function(response){

@ -1,62 +1,30 @@
'use strict';
angular.module('users').controller('SettingsController', ['$scope', '$rootScope', '$http', '$state', 'Users', 'Auth',
function($scope, $rootScope, $http, $state, Users, Auth) {
angular.module('users').controller('SettingsController', ['$scope', '$rootScope', '$http', '$state', 'Users', 'Auth', 'currentUser', 'USERS_URL', '$translate',
function($scope, $rootScope, $http, $state, Users, Auth, currentUser, USERS_URL, $translate) {
$scope.user = Auth.currentUser;
// Check if there are additional accounts
$scope.hasConnectedAdditionalSocialAccounts = function(provider) {
for (var i in $scope.user.additionalProvidersData) {
return true;
}
return false;
};
$scope.user = currentUser;
$scope.cancel = function(){
$scope.user = Auth.currentUser;
};
// Check if provider is already in use with current user
$scope.isConnectedSocialAccount = function(provider) {
return $scope.user.provider === provider || ($scope.user.additionalProvidersData && $scope.user.additionalProvidersData[provider]);
};
// Remove a user social account
$scope.removeUserSocialAccount = function(provider) {
$scope.success = $scope.error = null;
$http.delete('/users/accounts', {
params: {
provider: provider
}
}).success(function(response) {
// If successful show success message and clear form
$scope.success = true;
$scope.error = null;
$scope.user = response;
}).error(function(response) {
$scope.success = null;
$scope.error = response.message;
});
$scope.user = currentUser;
};
// Update a user profile
$scope.updateUserProfile = function(isValid) {
if (isValid) {
if (isValid && $scope.user) {
$scope.success = $scope.error = null;
var user = new Users($scope.user);
user.$update(function(response) {
$http.put(USERS_URL, $scope.user).then(function(response){
$scope.success = true;
$scope.error = null;
$scope.user = response;
}, function(response) {
$scope.user = response.data;
$translate.use($scope.user.language);
Auth.update($scope.user);
}, function(error) {
$scope.success = null;
$scope.error = response.data.message;
$scope.error = 'Could not update your profile due to an error with the server. Sorry about this!'
});
} else {
$scope.submitted = true;
}
};
@ -64,14 +32,14 @@ angular.module('users').controller('SettingsController', ['$scope', '$rootScope'
$scope.changeUserPassword = function() {
$scope.success = $scope.error = null;
$http.post('/users/password', $scope.passwordDetails).success(function(response) {
$http.post('/users/password', $scope.passwordDetails).then(function(response) {
// If successful show success message and clear form
$scope.success = true;
$scope.error = null;
$scope.passwordDetails = null;
}).error(function(response) {
}, function(errResponse) {
$scope.success = null;
$scope.error = response.message;
$scope.error = errResponse.message;
});
};

@ -2,34 +2,33 @@
angular.module('users').controller('VerifyController', ['$scope', '$state', '$rootScope', 'User', 'Auth', '$stateParams', '$translate', '$window',
function($scope, $state, $rootScope, User, Auth, $stateParams, $translate, $window) {
$translate.use($window.locale);
$scope.isResetSent = false;
$scope.credentials = {};
if(!$scope.credentials) $scope.credentials = {};
$scope.error = '';
// Submit forgotten password account id
$scope.resendVerifyEmail = function() {
User.resendVerifyEmail($scope.credentials.email).then(
function(response){
$scope.success = response.message;
$scope.error = null;
$scope.credentials = null;
$scope.isResetSent = true;
},
function(error){
$scope.error = error;
$scope.success = null;
$scope.credentials.email = null;
$scope.isResetSent = false;
}
);
if($scope.credentials.hasOwnProperty('email')){
User.resendVerifyEmail($scope.credentials.email).then(
function(response){
$scope.success = response.message;
$scope.error = null;
$scope.credentials = null;
$scope.isResetSent = true;
},
function(error){
$scope.error = error.message || error;
$scope.success = null;
$scope.credentials.email = null;
$scope.isResetSent = false;
}
);
}
};
//Validate Verification Token
$scope.validateVerifyToken = function() {
if($stateParams.token){
console.log($stateParams.token);
User.validateVerifyToken($stateParams.token).then(
function(response){
$scope.success = response.message;

Some files were not shown because too many files have changed in this diff Show More