merged
This commit is contained in:
commit
a8530472d5
|
@ -19,12 +19,12 @@ var mongoose = require('mongoose'),
|
|||
*/
|
||||
exports.uploadPDF = function(req, res, next) {
|
||||
|
||||
console.log('inside uploadPDF');
|
||||
//console.log('inside uploadPDF');
|
||||
|
||||
// console.log('\n\nProperty Descriptor\n-----------');
|
||||
// console.log(Object.getOwnPropertyDescriptor(req.files.file, 'path'));
|
||||
|
||||
console.log(req.file);
|
||||
//console.log(req.file);
|
||||
|
||||
if(req.file){
|
||||
var pdfFile = req.file;
|
||||
|
@ -159,9 +159,20 @@ exports.deleteSubmissions = function(req, res) {
|
|||
res.status(400).send({
|
||||
message: errorHandler.getErrorMessage(err)
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).send('Form submissions successfully deleted');
|
||||
form.analytics.visitors = [];
|
||||
form.save(function(err){
|
||||
if(err){
|
||||
res.status(400).send({
|
||||
message: errorHandler.getErrorMessage(err)
|
||||
});
|
||||
return;
|
||||
}
|
||||
res.status(200).send('Form submissions successfully deleted');
|
||||
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -171,8 +182,6 @@ exports.deleteSubmissions = function(req, res) {
|
|||
exports.createSubmission = function(req, res) {
|
||||
|
||||
var form = req.form;
|
||||
// console.log('in createSubmission()');
|
||||
// console.log(req.body);
|
||||
|
||||
var submission = new FormSubmission({
|
||||
admin: req.form.admin._id,
|
||||
|
@ -210,10 +219,9 @@ exports.createSubmission = function(req, res) {
|
|||
}
|
||||
|
||||
submission.save(function(err, submission){
|
||||
// console.log('in submissions.save()\n submission: '+JSON.stringify(submission) )
|
||||
if(err){
|
||||
console.log(err.message);
|
||||
res.status(400).send({
|
||||
res.status(500).send({
|
||||
message: errorHandler.getErrorMessage(err)
|
||||
});
|
||||
}
|
||||
|
@ -281,8 +289,9 @@ exports.create = function(req, res) {
|
|||
exports.read = function(req, res) {
|
||||
var validUpdateTypes= Form.schema.path('plugins.oscarhost.settings.updateType').enumValues;
|
||||
|
||||
var newForm = JSON.parse(JSON.stringify(req.form));
|
||||
var newForm = req.form.toJSON({virtuals : true});
|
||||
newForm.plugins.oscarhost.settings.validUpdateTypes = validUpdateTypes;
|
||||
|
||||
res.json(newForm);
|
||||
};
|
||||
|
||||
|
@ -377,11 +386,12 @@ exports.formByID = function(req, res, next, id) {
|
|||
}
|
||||
else {
|
||||
//Remove sensitive information from User object
|
||||
form.admin.password = undefined;
|
||||
form.admin.salt = undefined;
|
||||
form.provider = undefined;
|
||||
var _form = form;
|
||||
_form.admin.password = undefined;
|
||||
_form.admin.salt = undefined;
|
||||
_form.provider = undefined;
|
||||
|
||||
req.form = form;
|
||||
req.form = _form;
|
||||
return next();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -46,6 +46,21 @@ var ButtonSchema = new Schema({
|
|||
}
|
||||
});
|
||||
|
||||
var VisitorDataSchema = new Schema({
|
||||
referrer: {
|
||||
type: String
|
||||
},
|
||||
lastActiveField: {
|
||||
type: Schema.Types.ObjectId
|
||||
},
|
||||
timeElapsed: {
|
||||
type: Number
|
||||
},
|
||||
isSubmitted: {
|
||||
type: Boolean
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Form Schema
|
||||
*/
|
||||
|
@ -65,10 +80,15 @@ var FormSchema = new Schema({
|
|||
type: String,
|
||||
default: ''
|
||||
},
|
||||
form_fields: {
|
||||
type: [FieldSchema]
|
||||
|
||||
analytics:{
|
||||
gaCode: {
|
||||
type: String
|
||||
},
|
||||
visitors: [VisitorDataSchema]
|
||||
},
|
||||
|
||||
form_fields: [FieldSchema],
|
||||
submissions: [{
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'FormSubmission'
|
||||
|
@ -195,16 +215,91 @@ var FormSchema = new Schema({
|
|||
},
|
||||
auth: {
|
||||
user: {
|
||||
type: String,
|
||||
type: String
|
||||
},
|
||||
pass: {
|
||||
type: String,
|
||||
type: String
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
** In-Form Analytics Virtual Attributes
|
||||
*/
|
||||
FormSchema.virtual('analytics.views').get(function () {
|
||||
return this.analytics.visitors.length;
|
||||
});
|
||||
|
||||
FormSchema.virtual('analytics.submissions').get(function () {
|
||||
return this.submissions.length;
|
||||
});
|
||||
|
||||
FormSchema.virtual('analytics.conversionRate').get(function () {
|
||||
return this.submissions.length/this.analytics.visitors.length*100;
|
||||
});
|
||||
|
||||
FormSchema.virtual('analytics.fields').get(function () {
|
||||
var fieldDropoffs = [];
|
||||
var visitors = this.analytics.visitors;
|
||||
var that = this;
|
||||
|
||||
for(var i=0; i<this.form_fields.length; i++){
|
||||
var field = this.form_fields[i];
|
||||
|
||||
if(!field.deletePreserved){
|
||||
|
||||
var dropoffViews = _.reduce(visitors, function(sum, visitorObj){
|
||||
|
||||
if(visitorObj.lastActiveField+'' === field._id+'' && !visitorObj.isSubmitted){
|
||||
return sum + 1;
|
||||
}
|
||||
return sum;
|
||||
}, 0);
|
||||
|
||||
var continueViews, nextIndex;
|
||||
|
||||
if(i !== this.form_fields.length-1){
|
||||
continueViews = _.reduce(visitors, function(sum, visitorObj){
|
||||
nextIndex = that.form_fields.indexOf(_.find(that.form_fields, function(o) {
|
||||
return o._id+'' === visitorObj.lastActiveField+'';
|
||||
}));
|
||||
|
||||
if(nextIndex > i){
|
||||
return sum + 1;
|
||||
}
|
||||
return sum;
|
||||
}, 0);
|
||||
}else {
|
||||
continueViews = _.reduce(visitors, function(sum, visitorObj){
|
||||
if(visitorObj.lastActiveField+'' === field._id+'' && visitorObj.isSubmitted){
|
||||
return sum + 1;
|
||||
}
|
||||
return sum;
|
||||
}, 0);
|
||||
|
||||
}
|
||||
|
||||
var totalViews = dropoffViews+continueViews;
|
||||
var continueRate = continueViews/totalViews*100;
|
||||
var dropoffRate = dropoffViews/totalViews*100;
|
||||
|
||||
fieldDropoffs[i] = {
|
||||
dropoffViews: dropoffViews,
|
||||
continueViews: continueViews,
|
||||
totalViews: totalViews,
|
||||
continueRate: continueRate,
|
||||
dropoffRate: dropoffRate,
|
||||
field: field
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return fieldDropoffs;
|
||||
});
|
||||
|
||||
FormSchema.plugin(mUtilities.timestamp, {
|
||||
createdPath: 'created',
|
||||
modifiedPath: 'lastModified',
|
||||
|
@ -397,7 +492,7 @@ FormSchema.pre('save', function (next) {
|
|||
|
||||
//Find FormSubmissions that contain field with _id equal to 'deleted_id'
|
||||
FormSubmission.
|
||||
find({ form: that._id, admin: that.admin, form_fields: {$elemMatch: {_id: deleted_id} } }).
|
||||
find({ form: that._id, admin: that.admin, form_fields: {$elemMatch: {submissionId: deleted_id} } }).
|
||||
exec(function(err, submissions){
|
||||
if(err) {
|
||||
console.error(err);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* Module dependencies.
|
||||
*/
|
||||
var mongoose = require('mongoose'),
|
||||
util = require('util'),
|
||||
mUtilities = require('mongoose-utilities'),
|
||||
_ = require('lodash'),
|
||||
Schema = mongoose.Schema;
|
||||
|
@ -23,85 +24,188 @@ var FieldOptionSchema = new Schema({
|
|||
}
|
||||
});
|
||||
|
||||
var RatingFieldSchema = new Schema({
|
||||
steps: {
|
||||
type: Number,
|
||||
min: 1,
|
||||
max: 10
|
||||
},
|
||||
shape: {
|
||||
type: String,
|
||||
enum: [
|
||||
'Heart',
|
||||
'Star',
|
||||
'thumbs-up',
|
||||
'thumbs-down',
|
||||
'Circle',
|
||||
'Square',
|
||||
'Check Circle',
|
||||
'Smile Outlined',
|
||||
'Hourglass',
|
||||
'bell',
|
||||
'Paper Plane',
|
||||
'Comment',
|
||||
'Trash'
|
||||
]
|
||||
},
|
||||
validShapes: {
|
||||
type: [String]
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* FormField Schema
|
||||
*/
|
||||
var FormFieldSchema = new Schema({
|
||||
title: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: 'Field Title cannot be blank'
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
function BaseFieldSchema(){
|
||||
Schema.apply(this, arguments);
|
||||
|
||||
logicJump: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'LogicJump'
|
||||
},
|
||||
this.add({
|
||||
isSubmission: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
submissionId: {
|
||||
type: Schema.Types.ObjectId
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: 'Field Title cannot be blank'
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
|
||||
fieldOptions: [FieldOptionSchema],
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
logicJump: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'LogicJump'
|
||||
},
|
||||
|
||||
deletePreserved: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
validFieldTypes: {
|
||||
type: [String]
|
||||
},
|
||||
fieldType: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: [
|
||||
'textfield',
|
||||
'date',
|
||||
'email',
|
||||
'link',
|
||||
'legal',
|
||||
'url',
|
||||
'textarea',
|
||||
'statement',
|
||||
'welcome',
|
||||
'thankyou',
|
||||
'file',
|
||||
'dropdown',
|
||||
'scale',
|
||||
'rating',
|
||||
'radio',
|
||||
'checkbox',
|
||||
'hidden',
|
||||
'yes_no',
|
||||
'natural',
|
||||
'number'
|
||||
]
|
||||
},
|
||||
fieldValue: Schema.Types.Mixed
|
||||
ratingOptions: {
|
||||
type: RatingFieldSchema,
|
||||
required: false,
|
||||
default: {}
|
||||
},
|
||||
fieldOptions: [FieldOptionSchema],
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
deletePreserved: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
validFieldTypes: {
|
||||
type: [String]
|
||||
},
|
||||
fieldType: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: [
|
||||
'textfield',
|
||||
'date',
|
||||
'email',
|
||||
'link',
|
||||
'legal',
|
||||
'url',
|
||||
'textarea',
|
||||
'statement',
|
||||
'welcome',
|
||||
'thankyou',
|
||||
'file',
|
||||
'dropdown',
|
||||
'scale',
|
||||
'rating',
|
||||
'radio',
|
||||
'checkbox',
|
||||
'hidden',
|
||||
'yes_no',
|
||||
'natural',
|
||||
'number'
|
||||
]
|
||||
},
|
||||
fieldValue: Schema.Types.Mixed
|
||||
});
|
||||
|
||||
this.plugin(mUtilities.timestamp, {
|
||||
createdPath: 'created',
|
||||
modifiedPath: 'lastModified',
|
||||
useVirtual: false
|
||||
});
|
||||
|
||||
this.pre('save', function (next) {
|
||||
this.validFieldTypes = mongoose.model('Field').schema.path('fieldType').enumValues;
|
||||
|
||||
if(this.fieldType === 'rating' && this.ratingOptions.validShapes.length === 0){
|
||||
this.ratingOptions.validShapes = mongoose.model('RatingOptions').schema.path('shape').enumValues;
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
}
|
||||
util.inherits(BaseFieldSchema, Schema);
|
||||
|
||||
var FormFieldSchema = new BaseFieldSchema();
|
||||
|
||||
FormFieldSchema.pre('validate', function(next) {
|
||||
var error = new mongoose.Error.ValidationError(this);
|
||||
|
||||
//If field is rating check that it has ratingOptions
|
||||
if(this.fieldType !== 'rating'){
|
||||
|
||||
if(this.ratingOptions && this.ratingOptions.steps && this.ratingOptions.shape){
|
||||
error.errors.ratingOptions = new mongoose.Error.ValidatorError({path: 'ratingOptions', message: 'ratingOptions is only allowed for type \'rating\' fields.', type: 'notvalid', value: this.ratingOptions});
|
||||
return(next(error));
|
||||
}
|
||||
|
||||
}else{
|
||||
//Setting default values for ratingOptions
|
||||
if(!this.ratingOptions.steps){
|
||||
this.ratingOptions.steps = 10;
|
||||
}
|
||||
if(!this.ratingOptions.shape){
|
||||
this.ratingOptions.shape = 'Star';
|
||||
}
|
||||
|
||||
//Checking that the fieldValue is between 0 and ratingOptions.steps
|
||||
if(this.fieldValue+0 > this.ratingOptions.steps || this.fieldValue+0 < 0){
|
||||
this.fieldValue = 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//If field is multiple choice check that it has field
|
||||
if(this.fieldType !== 'dropdown' && this.fieldType !== 'radio' && this.fieldType !== 'checkbox'){
|
||||
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});
|
||||
return(next(error));
|
||||
}
|
||||
}
|
||||
|
||||
return next();
|
||||
});
|
||||
|
||||
FormFieldSchema.plugin(mUtilities.timestamp, {
|
||||
createdPath: 'created',
|
||||
modifiedPath: 'lastModified',
|
||||
useVirtual: false
|
||||
});
|
||||
//Submission fieldValue correction
|
||||
FormFieldSchema.pre('save', function(next) {
|
||||
|
||||
FormFieldSchema.pre('save', function (next){
|
||||
this.validFieldTypes = mongoose.model('Field').schema.path('fieldType').enumValues;
|
||||
next();
|
||||
if(this.fieldType === 'dropdown' && this.isSubmission){
|
||||
//console.log(this);
|
||||
this.fieldValue = this.fieldValue.option_value;
|
||||
//console.log(this.fieldValue);
|
||||
}
|
||||
|
||||
return next();
|
||||
});
|
||||
|
||||
|
||||
mongoose.model('Field', FormFieldSchema);
|
||||
var Field = mongoose.model('Field', FormFieldSchema);
|
||||
var RatingOptions = mongoose.model('RatingOptions', RatingFieldSchema);
|
||||
|
||||
module.exports = FormFieldSchema;
|
||||
|
||||
|
|
|
@ -17,6 +17,8 @@ var mongoose = require('mongoose'),
|
|||
FieldSchema = require('./form_field.server.model.js'),
|
||||
OscarSecurity = require('../../scripts/oscarhost/OscarSecurity');
|
||||
|
||||
var FieldSchema = require('./form_field.server.model.js');
|
||||
|
||||
var newDemoTemplate = {
|
||||
address: '880-9650 Velit. St.',
|
||||
city: '',
|
||||
|
@ -41,6 +43,18 @@ var newDemoTemplate = {
|
|||
yearOfBirth: '2015'
|
||||
};
|
||||
|
||||
|
||||
// Setter function for form_fields
|
||||
function formFieldsSetter(form_fields){
|
||||
for(var i=0; i<form_fields.length; i++){
|
||||
form_fields[i].isSubmission = true;
|
||||
form_fields[i].submissionId = form_fields[i]._id;
|
||||
form_fields[i]._id = new mongoose.mongo.ObjectID();
|
||||
}
|
||||
//console.log(form_fields)
|
||||
return form_fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form Submission Schema
|
||||
*/
|
||||
|
@ -55,9 +69,7 @@ var FormSubmissionSchema = new Schema({
|
|||
required: true
|
||||
},
|
||||
|
||||
form_fields: {
|
||||
type: [Schema.Types.Mixed]
|
||||
},
|
||||
form_fields: [FieldSchema],
|
||||
|
||||
form: {
|
||||
type: Schema.Types.ObjectId,
|
||||
|
@ -99,7 +111,7 @@ var FormSubmissionSchema = new Schema({
|
|||
},
|
||||
|
||||
timeElapsed: {
|
||||
type: Number,
|
||||
type: Number
|
||||
},
|
||||
percentageComplete: {
|
||||
type: Number
|
||||
|
@ -116,9 +128,10 @@ var FormSubmissionSchema = new Schema({
|
|||
default: false
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
FormSubmissionSchema.path('form_fields').set(formFieldsSetter);
|
||||
|
||||
FormSubmissionSchema.plugin(mUtilities.timestamp, {
|
||||
createdPath: 'created',
|
||||
modifiedPath: 'lastModified',
|
||||
|
@ -214,7 +227,7 @@ FormSubmissionSchema.pre('save', function (next) {
|
|||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
], function(err, result) {
|
||||
if(err) return next(err);
|
||||
|
@ -254,7 +267,6 @@ FormSubmissionSchema.pre('save', function (next) {
|
|||
self = this,
|
||||
_form = this.form;
|
||||
|
||||
|
||||
if(this.pdf && this.pdf.path){
|
||||
dest_filename = self.title.replace(/ /g,'')+'_submission_'+Date.now()+'.pdf';
|
||||
var __path = this.pdf.path.split('/').slice(0,this.pdf.path.split('/').length-1).join('/');
|
||||
|
|
80
app/sockets/analytics_service.js
Normal file
80
app/sockets/analytics_service.js
Normal file
|
@ -0,0 +1,80 @@
|
|||
'use strict';
|
||||
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var mongoose = require('mongoose'),
|
||||
config = require('../../config/config'),
|
||||
errorHandler = require('../controllers/errors.server.controller'),
|
||||
Form = mongoose.model('Form');
|
||||
|
||||
// Create the chat configuration
|
||||
module.exports = function (io, socket) {
|
||||
var visitorsData = {};
|
||||
|
||||
var saveVisitorData = function (data, cb){
|
||||
|
||||
Form.findById(data.formId, function(err, form) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
throw new Error(errorHandler.getErrorMessage(err));
|
||||
}
|
||||
|
||||
var newVisitor = {
|
||||
referrer: data.referrer,
|
||||
lastActiveField: data.lastActiveField,
|
||||
timeElapsed: data.timeElapsed,
|
||||
isSubmitted: data.isSubmitted
|
||||
};
|
||||
|
||||
form.analytics.visitors.push(newVisitor);
|
||||
|
||||
form.save(function (err) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
throw new Error(errorHandler.getErrorMessage(err));
|
||||
}
|
||||
console.log('\n\nVisitor data successfully added!');
|
||||
|
||||
delete visitorsData[socket.id];
|
||||
|
||||
if(cb) cb();
|
||||
});
|
||||
});
|
||||
|
||||
socket.disconnect(0);
|
||||
};
|
||||
|
||||
io.on('connection', function(socket) {
|
||||
|
||||
|
||||
// a user has visited our page - add them to the visitorsData object
|
||||
socket.on('form-visitor-data', function(data) {
|
||||
|
||||
console.log('\n\nuser has visited our page');
|
||||
|
||||
visitorsData[socket.id] = data;
|
||||
|
||||
console.log(data);
|
||||
|
||||
if (data.isSubmitted) {
|
||||
saveVisitorData(data, function () {
|
||||
console.log('\n\n user submitted form');
|
||||
|
||||
socket.disconnect(0);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('disconnect', function() {
|
||||
var data = visitorsData[socket.id];
|
||||
|
||||
if(data){
|
||||
if(!data.isSubmitted) {
|
||||
saveVisitorData(data);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
|
@ -80,8 +80,12 @@
|
|||
<!--Embedding The signupDisabled Boolean-->
|
||||
<script type="text/javascript">
|
||||
var signupDisabled = {{signupDisabled | safe}};
|
||||
var socketPort = {{socketPort | safe}};
|
||||
</script>
|
||||
|
||||
<!--Socket.io Client Dependency-->
|
||||
<script src="https://cdn.socket.io/socket.io-1.4.5.js"></script>
|
||||
|
||||
<!--Bower JS dependencies-->
|
||||
{% for bowerJSFile in bowerJSFiles %}
|
||||
<script type="text/javascript" src="{{bowerJSFile}}"></script>
|
||||
|
@ -103,7 +107,7 @@
|
|||
<script src="https://cdn.ravenjs.com/2.3.0/angular/raven.min.js"></script>
|
||||
|
||||
<script>
|
||||
Raven.config('http://825fefd6b4ed4a4da199c1b832ca845c@sentry.tellform.com/2').install();
|
||||
Raven.config('https://825fefd6b4ed4a4da199c1b832ca845c@sentry.tellform.com/2').install();
|
||||
</script>
|
||||
|
||||
<!-- [if lt IE 9]>
|
||||
|
|
|
@ -27,7 +27,6 @@
|
|||
"angular-bootstrap-colorpicker": "~3.0.19",
|
||||
"angular-ui-router-tabs": "~1.7.0",
|
||||
"angular-scroll": "^1.0.0",
|
||||
"ui-select": "angular-ui-select#^0.16.1",
|
||||
"angular-sanitize": "^1.5.3",
|
||||
"v-button": "^1.1.1",
|
||||
"angular-busy": "^4.1.3",
|
||||
|
@ -35,10 +34,13 @@
|
|||
"raven-js": "^3.0.4",
|
||||
"tableExport.jquery.plugin": "^1.5.1",
|
||||
"angular-translate": "~2.11.0"
|
||||
"js-yaml": "^3.6.1",
|
||||
"angular-ui-select": "whitef0x0/ui-select#compiled"
|
||||
},
|
||||
"resolutions": {
|
||||
"angular-bootstrap": "^0.14.0",
|
||||
"angular": "1.4.x"
|
||||
"angular": "~1.4.7",
|
||||
"angular-ui-select": "compiled"
|
||||
},
|
||||
"overrides": {
|
||||
"BOWER-PACKAGE": {
|
||||
|
|
|
@ -12,6 +12,7 @@ var _ = require('lodash'),
|
|||
var exists = require('path-exists').sync;
|
||||
|
||||
var minBowerFiles = function(type){
|
||||
console.log(type);
|
||||
return bowerFiles(type).map( function(path, index, arr) {
|
||||
var newPath = path.replace(/.([^.]+)$/g, '.min.$1');
|
||||
return exists( newPath ) ? newPath : path;
|
||||
|
|
36
config/env/all.js
vendored
36
config/env/all.js
vendored
|
@ -7,7 +7,9 @@ module.exports = {
|
|||
description: process.env.APP_DESC || 'Opensource form builder alternative to TypeForm',
|
||||
keywords: process.env.APP_KEYWORDS || 'typeform, pdfs, forms, opensource, formbuilder, google forms, nodejs'
|
||||
},
|
||||
port: process.env.PORT || 3000,
|
||||
port: process.env.PORT || 5000,
|
||||
socketPort: process.env.SOCKET_PORT || 35729,
|
||||
|
||||
templateEngine: 'swig',
|
||||
|
||||
reCAPTCHA_Key: process.env.reCAPTCHA_KEY || '',
|
||||
|
@ -70,9 +72,9 @@ module.exports = {
|
|||
assets: {
|
||||
css: [
|
||||
'public/modules/**/css/*.css',
|
||||
'!public/modules/**/demo/**/*.css',
|
||||
'!public/modules/**/dist/**/*.css',
|
||||
'!public/modules/**/node_modules/**/*.css'
|
||||
'public/modules/**/demo/**/*.css',
|
||||
'public/modules/**/dist/**/*.css',
|
||||
'public/modules/**/node_modules/**/*.css'
|
||||
],
|
||||
js: [
|
||||
'public/dist/populate_template_cache.js',
|
||||
|
@ -82,29 +84,29 @@ module.exports = {
|
|||
'public/modules/*/*.js',
|
||||
'public/modules/*/*/*.js',
|
||||
'public/modules/**/*.js',
|
||||
'!public/modules/**/gruntfile.js',
|
||||
'!public/modules/**/demo/**/*.js',
|
||||
'!public/modules/**/dist/**/*.js',
|
||||
'!public/modules/**/node_modules/**/*.js',
|
||||
'!public/modules/**/tests/**/*.js'
|
||||
'public/modules/**/gruntfile.js',
|
||||
'public/modules/**/demo/**/*.js',
|
||||
'public/modules/**/dist/**/*.js',
|
||||
'public/modules/**/node_modules/**/*.js',
|
||||
'public/modules/**/tests/**/*.js'
|
||||
],
|
||||
views: [
|
||||
'public/modules/**/*.html',
|
||||
'!public/modules/**/demo/**/*.html',
|
||||
'!public/modules/**/dist/**/*.html',
|
||||
'!public/modules/**/node_modules/**/*.html',
|
||||
'!public/modules/**/tests/**/*.html'
|
||||
'public/modules/**/demo/**/*.html',
|
||||
'public/modules/**/dist/**/*.html',
|
||||
'public/modules/**/node_modules/**/*.html',
|
||||
'public/modules/**/tests/**/*.html'
|
||||
],
|
||||
unit_tests: [
|
||||
'public/lib/angular-mocks/angular-mocks.js',
|
||||
'public/modules/*/tests/unit/**/*.js',
|
||||
'!public/modules/**/demo/**/*.js',
|
||||
'!public/modules/**/node_modules/**/*.js'
|
||||
'public/modules/**/demo/**/*.js',
|
||||
'public/modules/**/node_modules/**/*.js'
|
||||
],
|
||||
e2e_tests: [
|
||||
'public/modules/*/tests/e2e/**.js',
|
||||
'!public/modules/**/demo/**/*.js',
|
||||
'!public/modules/**/node_modules/**/*.js'
|
||||
'public/modules/**/demo/**/*.js',
|
||||
'public/modules/**/node_modules/**/*.js'
|
||||
]
|
||||
}
|
||||
};
|
||||
|
|
2
config/env/development.js
vendored
2
config/env/development.js
vendored
|
@ -1,7 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
baseUrl: process.env.BASE_URL || 'http://localhost:3000',
|
||||
baseUrl: process.env.BASE_URL || 'http://localhost:5000',
|
||||
db: {
|
||||
uri: 'mongodb://'+(process.env.DB_HOST || 'localhost')+'/mean',
|
||||
options: {
|
||||
|
|
17
config/env/local.js
vendored
17
config/env/local.js
vendored
|
@ -1,17 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
var mandrill_api_key = 'AVCCf1C2dFlrNhx9Iyi_yQ';
|
||||
|
||||
//Use mandrill mock API_key if we are testing
|
||||
// if(process.env.NODE_ENV === 'test') mandrill_api_key = '_YNOKLgT9DGb2sgVGR66yQ';
|
||||
|
||||
module.exports = {
|
||||
// db: {
|
||||
// uri: 'mongodb://localhost/local-dev',
|
||||
// options: {
|
||||
// user: '',
|
||||
// pass: ''
|
||||
// }
|
||||
// },
|
||||
sessionSecret: process.env.SESSION_SECRET || 'somethingheresecret',
|
||||
};
|
65
config/env/production-dev.js
vendored
65
config/env/production-dev.js
vendored
|
@ -1,65 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
baseUrl: process.env.BASE_URL || 'dev.tellform.com',
|
||||
db: {
|
||||
uri: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://' + (process.env.DB_1_PORT_27017_TCP_ADDR || 'localhost') + '/mean',
|
||||
options: {
|
||||
user: 'admin',
|
||||
pass: process.env.MONGOLAB_PASS || 'admin'
|
||||
}
|
||||
},
|
||||
log: {
|
||||
// Can specify one of 'combined', 'common', 'dev', 'short', 'tiny'
|
||||
format: 'dev',
|
||||
// Stream defaults to process.stdout
|
||||
// Uncomment to enable logging to a log on the file system
|
||||
options: {
|
||||
stream: 'access.log'
|
||||
}
|
||||
},
|
||||
sessionCookie: {
|
||||
domain: process.env.BASE_URL || 'dev.tellform.com'
|
||||
},
|
||||
assets: {
|
||||
css: 'public/dist/application.min.css',
|
||||
js: 'public/dist/application.min.js'
|
||||
},
|
||||
facebook: {
|
||||
clientID: process.env.FACEBOOK_ID || 'APP_ID',
|
||||
clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET',
|
||||
callbackURL: '/auth/facebook/callback'
|
||||
},
|
||||
twitter: {
|
||||
clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY',
|
||||
clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET',
|
||||
callbackURL: '/auth/twitter/callback'
|
||||
},
|
||||
google: {
|
||||
clientID: process.env.GOOGLE_ID || 'APP_ID',
|
||||
clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET',
|
||||
callbackURL: '/auth/google/callback'
|
||||
},
|
||||
linkedin: {
|
||||
clientID: process.env.LINKEDIN_ID || 'APP_ID',
|
||||
clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET',
|
||||
callbackURL: '/auth/linkedin/callback'
|
||||
},
|
||||
github: {
|
||||
clientID: process.env.GITHUB_ID || 'APP_ID',
|
||||
clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET',
|
||||
callbackURL: '/auth/github/callback'
|
||||
},
|
||||
mailer: {
|
||||
from: process.env.MAILER_FROM || 'no-reply@dev.tellform.com',
|
||||
options: {
|
||||
service: process.env.MAILER_SERVICE_PROVIDER || '',
|
||||
secure: false,
|
||||
requireTLS: true,
|
||||
auth: {
|
||||
user: process.env.MAILER_EMAIL_ID || '',
|
||||
pass: process.env.MAILER_PASSWORD || ''
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
|
@ -26,7 +26,16 @@ var fs = require('fs-extra'),
|
|||
device = require('express-device'),
|
||||
client = new raven.Client(config.DSN);
|
||||
|
||||
/**
|
||||
* Configure Socket.io
|
||||
*/
|
||||
var configureSocketIO = function (app, db) {
|
||||
// Load the Socket.io configuration
|
||||
var server = require('./socket.io')(app, db);
|
||||
|
||||
// Return server object
|
||||
return server;
|
||||
};
|
||||
|
||||
module.exports = function(db) {
|
||||
// Initialize express app
|
||||
|
@ -43,6 +52,7 @@ module.exports = function(db) {
|
|||
app.locals.signupDisabled = config.signupDisabled;
|
||||
app.locals.description = config.app.description;
|
||||
app.locals.keywords = config.app.keywords;
|
||||
app.locals.socketPort = config.socketPort;
|
||||
|
||||
app.locals.bowerJSFiles = config.getBowerJSAssets();
|
||||
app.locals.bowerCssFiles = config.getBowerCSSAssets();
|
||||
|
@ -147,11 +157,11 @@ module.exports = function(db) {
|
|||
|
||||
|
||||
// Add headers for Sentry
|
||||
/*
|
||||
|
||||
app.use(function (req, res, next) {
|
||||
|
||||
// Website you wish to allow to connect
|
||||
res.setHeader('Access-Control-Allow-Origin', 'http://sentry.polydaic.com');
|
||||
res.setHeader('Access-Control-Allow-Origin', 'https://sentry.polydaic.com');
|
||||
|
||||
// Request methods you wish to allow
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
|
||||
|
@ -166,7 +176,7 @@ module.exports = function(db) {
|
|||
// Pass to next layer of middleware
|
||||
next();
|
||||
});
|
||||
*/
|
||||
|
||||
// Sentry (Raven) middleware
|
||||
// app.use(raven.middleware.express.requestHandler(config.DSN));
|
||||
|
||||
|
@ -212,6 +222,8 @@ module.exports = function(db) {
|
|||
return httpsServer;
|
||||
}
|
||||
|
||||
app = configureSocketIO(app, db);
|
||||
|
||||
// Return Express server instance
|
||||
return app;
|
||||
};
|
||||
|
|
24
config/socket.io.js
Normal file
24
config/socket.io.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
'use strict';
|
||||
|
||||
// Load the module dependencies
|
||||
var config = require('./config'),
|
||||
path = require('path'),
|
||||
http = require('http'),
|
||||
socketio = require('socket.io');
|
||||
|
||||
// Define the Socket.io configuration method
|
||||
module.exports = function (app, db) {
|
||||
var server = http.createServer(app);
|
||||
|
||||
// Create a new Socket.io server
|
||||
var io = socketio.listen(server);
|
||||
|
||||
// Add an event listener to the 'connection' event
|
||||
io.on('connection', function (socket) {
|
||||
config.getGlobbedFiles('./app/sockets/**.js').forEach(function (socketConfiguration) {
|
||||
require(path.resolve(socketConfiguration))(io, socket);
|
||||
});
|
||||
});
|
||||
|
||||
return server;
|
||||
};
|
12
config/strategies/anonymous.js
Normal file
12
config/strategies/anonymous.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var passport = require('passport'),
|
||||
AnonymousStrategy = require('passport-anonymous').Strategy;
|
||||
|
||||
module.exports = function() {
|
||||
// Use local strategy
|
||||
passport.use(new AnonymousStrategy());
|
||||
};
|
|
@ -19,7 +19,7 @@
|
|||
"scripts": {
|
||||
"start": "grunt",
|
||||
"test": "grunt test && grunt coveralls",
|
||||
"postinstall": "bower install --config.interactive=false; grunt build"
|
||||
"postinstall": "bower install --config.interactive=false; grunt build; node scripts/setup.js;"
|
||||
},
|
||||
"dependencies": {
|
||||
"async": "^1.4.2",
|
||||
|
@ -63,13 +63,14 @@
|
|||
"math": "0.0.3",
|
||||
"method-override": "~2.3.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"mongoose": "3.8.40",
|
||||
"mongoose": "~4.4.19",
|
||||
"mongoose-utilities": "~0.1.1",
|
||||
"morgan": "~1.6.1",
|
||||
"multer": "~1.1.0",
|
||||
"node-freegeoip": "0.0.1",
|
||||
"nodemailer": "~1.10.0",
|
||||
"passport": "~0.3.0",
|
||||
"passport-anonymous": "^1.0.1",
|
||||
"passport-facebook": "~2.0.0",
|
||||
"passport-github": "~1.0.0",
|
||||
"passport-google-oauth": "~0.2.0",
|
||||
|
@ -82,6 +83,7 @@
|
|||
"random-js": "^1.0.8",
|
||||
"raven": "^0.9.0",
|
||||
"soap": "^0.11.0",
|
||||
"socket.io": "^1.4.6",
|
||||
"swig": "~1.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
532
public/dist/application.js
vendored
532
public/dist/application.js
vendored
File diff suppressed because one or more lines are too long
2
public/dist/application.min.css
vendored
2
public/dist/application.min.css
vendored
File diff suppressed because one or more lines are too long
12
public/dist/application.min.js
vendored
12
public/dist/application.min.js
vendored
File diff suppressed because one or more lines are too long
55
public/modules/core/services/socket.io.client.service.js
Normal file
55
public/modules/core/services/socket.io.client.service.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
// Create the Socket.io wrapper service
|
||||
angular
|
||||
.module('core')
|
||||
.factory('Socket', Socket);
|
||||
|
||||
Socket.$inject = ['$timeout', '$window'];
|
||||
|
||||
function Socket($timeout, $window) {
|
||||
var service = {
|
||||
connect: connect,
|
||||
emit: emit,
|
||||
on: on,
|
||||
removeListener: removeListener,
|
||||
socket: null
|
||||
};
|
||||
|
||||
console.log('https://'+window.location.hostname+':'+$window.socketPort);
|
||||
connect('https://'+window.location.hostname+':'+$window.socketPort);
|
||||
|
||||
return service;
|
||||
|
||||
// Connect to Socket.io server
|
||||
function connect(url) {
|
||||
service.socket = io();
|
||||
}
|
||||
|
||||
// Wrap the Socket.io 'emit' method
|
||||
function emit(eventName, data) {
|
||||
if (service.socket) {
|
||||
service.socket.emit(eventName, data);
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap the Socket.io 'on' method
|
||||
function on(eventName, callback) {
|
||||
if (service.socket) {
|
||||
service.socket.on(eventName, function (data) {
|
||||
$timeout(function () {
|
||||
callback(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap the Socket.io 'removeListener' method
|
||||
function removeListener(eventName) {
|
||||
if (service.socket) {
|
||||
service.socket.removeListener(eventName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}());
|
|
@ -79,7 +79,7 @@ angular.module('forms').directive('autoSaveForm', ['$rootScope', '$timeout', fun
|
|||
}
|
||||
});
|
||||
|
||||
//Autosave Form when model (specificed in $attrs.autoSaveWatch) changes
|
||||
//Autosave Form when model (specified in $attrs.autoSaveWatch) changes
|
||||
$scope.$watch($attrs.autoSaveWatch, function(newValue, oldValue) {
|
||||
|
||||
newValue = angular.copy(newValue);
|
||||
|
|
|
@ -24,8 +24,25 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
|
|||
forcePlaceholderSize: true
|
||||
};
|
||||
|
||||
|
||||
console.log($scope.sortableOptions);
|
||||
/*
|
||||
** Setup Angular-Input-Star Shape Dropdown
|
||||
*/
|
||||
//Populate Name to Font-awesomeName Conversion Map
|
||||
$scope.select2FA = {
|
||||
'Heart': 'Heart',
|
||||
'Star': 'Star',
|
||||
'thumbs-up': 'Thumbs Up',
|
||||
'thumbs-down':'Thumbs Down',
|
||||
'Circle': 'Circle',
|
||||
'Square':'Square',
|
||||
'Check Circle': 'Checkmark',
|
||||
'Smile Outlined': 'Smile',
|
||||
'Hourglass': 'Hourglass',
|
||||
'bell': 'Bell',
|
||||
'Paper Plane': 'Paper Plane',
|
||||
'Comment': 'Chat Bubble',
|
||||
'Trash': 'Trash Can'
|
||||
};
|
||||
|
||||
//Populate AddField with all available form field types
|
||||
$scope.addField = {};
|
||||
|
@ -67,11 +84,11 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
|
|||
/*
|
||||
** FormFields (ui-sortable) drag-and-drop configuration
|
||||
*/
|
||||
$scope.dropzone = {
|
||||
handle: ' .handle',
|
||||
containment: '.dropzoneContainer',
|
||||
cursor: 'grabbing'
|
||||
};
|
||||
$scope.dropzone = {
|
||||
handle: '.handle',
|
||||
containment: '.dropzoneContainer',
|
||||
cursor: 'grabbing'
|
||||
};
|
||||
|
||||
/*
|
||||
** Field CRUD Methods
|
||||
|
@ -79,7 +96,7 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
|
|||
// Add a new field
|
||||
$scope.addNewField = function(modifyForm, fieldType){
|
||||
|
||||
// incr field_id counter
|
||||
// increment lastAddedID counter
|
||||
$scope.addField.lastAddedID++;
|
||||
var fieldTitle;
|
||||
|
||||
|
@ -98,12 +115,19 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
|
|||
disabled: false,
|
||||
deletePreserved: false
|
||||
};
|
||||
// console.log('\n\n---------\nAdded field CLIENT');
|
||||
// console.log(newField);
|
||||
// newField._id = _.uniqueId();
|
||||
|
||||
// put newField into fields array
|
||||
if($scope.showAddOptions(newField)){
|
||||
newField.fieldOptions = [];
|
||||
newField.fieldOptions.push({
|
||||
'option_id' : Math.floor(100000*Math.random()), //Generate pseudo-random option id
|
||||
'option_title' : 'Option 0',
|
||||
'option_value' : 'Option 0'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if(modifyForm){
|
||||
//Add newField to form_fields array
|
||||
$scope.myform.form_fields.push(newField);
|
||||
}
|
||||
return newField;
|
||||
|
@ -168,20 +192,17 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
|
|||
// add new option to the field
|
||||
$scope.addOption = function(field_index){
|
||||
var currField = $scope.myform.form_fields[field_index];
|
||||
console.log(field_index);
|
||||
console.log(currField);
|
||||
//console.log(field_index);
|
||||
//console.log(currField);
|
||||
|
||||
if(currField.fieldType === 'checkbox' || currField.fieldType === 'dropdown' || currField.fieldType === 'radio'){
|
||||
if(!currField.fieldOptions) $scope.myform.form_fields[field_index].fieldOptions = [];
|
||||
if(!currField.fieldOptions){
|
||||
$scope.myform.form_fields[field_index].fieldOptions = [];
|
||||
}
|
||||
|
||||
var lastOptionID = 0;
|
||||
|
||||
if(currField.fieldOptions[currField.fieldOptions.length-1]){
|
||||
lastOptionID = currField.fieldOptions[currField.fieldOptions.length-1].option_id;
|
||||
}
|
||||
var lastOptionID = $scope.myform.form_fields[field_index].fieldOptions.length+1;
|
||||
|
||||
// new option's id
|
||||
var option_id = lastOptionID + 1;
|
||||
|
||||
var newOption = {
|
||||
'option_id' : Math.floor(100000*Math.random()),
|
||||
|
@ -219,7 +240,17 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
|
|||
}
|
||||
};
|
||||
|
||||
}
|
||||
// decides whether field options block will be shown (true for dropdown and radio fields)
|
||||
$scope.showRatingOptions = function (field){
|
||||
if(field.fieldType === 'rating'){
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
|
|
@ -271,6 +271,22 @@
|
|||
<!-- <span class="required-error" ng-show="field.required && !field.fieldValue">* required</span> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row field">
|
||||
<div class="field-title col-sm-4">
|
||||
<h5>Google Analytics Tracking Code</h5>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-8">
|
||||
<input type="text"
|
||||
ng-model="myform.analytics.gaCode"
|
||||
value="{{myform.analytics.gaCode}}"
|
||||
style="width: 100%;"
|
||||
ng-minlength="4"
|
||||
placeholder="UA-XXXXX-Y"
|
||||
ng-pattern="/\bUA-\d{4,10}-\d{1,4}\b/">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row field">
|
||||
<div class="col-xs-6 field-title">Language</div>
|
||||
|
|
|
@ -220,13 +220,13 @@
|
|||
|
||||
<div class="row"><br></div>
|
||||
|
||||
<div class="row description">
|
||||
<div class="row description" ng-hide="showRatingOptions(field)">
|
||||
<div class="col-md-4 col-sm-12">Description:</div>
|
||||
<div class="col-md-8 col-sm-12"><textarea type="text" ng-model="field.description" name="description{{field._id}}"value="{{field.description}}"></textarea> </div>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-show="showAddOptions(field)"><br></div>
|
||||
<div class="row options" ng-show="showAddOptions(field)">
|
||||
<div class="row options" ng-if="showAddOptions(field)">
|
||||
<div class="col-md-4 col-xs-12">Options:</div>
|
||||
<div class="col-md-8 col-xs-12">
|
||||
<div ng-repeat="option in field.fieldOptions track by option.option_id" class="row">
|
||||
|
@ -244,6 +244,32 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row" ng-show="showRatingOptions(field)"><br></div>
|
||||
<div class="row" ng-if="showRatingOptions(field)">
|
||||
<div class="col-md-9 col-sm-9">Number of Steps:</div>
|
||||
<div class="col-md-3 col-sm-3">
|
||||
<input style="width:100%" type="number"
|
||||
min="1" max="10"
|
||||
ng-model="field.ratingOptions.steps"
|
||||
name="ratingOptions_steps{{field._id}}"
|
||||
ng-value="{{field.ratingOptions.steps}}"
|
||||
required>
|
||||
</div>
|
||||
<br>
|
||||
<div class="col-md-5 col-sm-9">Shape:</div>
|
||||
<div class="col-md-7 col-sm-3">
|
||||
<select style="width:100%" ng-model="field.ratingOptions.shape"
|
||||
value="{{field.ratingOptions.steps}}"
|
||||
name="ratingOptions_shape{{field._id}}" required>
|
||||
<option ng-repeat="shapeType in field.ratingOptions.validShapes"
|
||||
value="{{shapeType}}">
|
||||
{{select2FA[shapeType]}}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row"><br></div>
|
||||
|
||||
<div class="row">
|
||||
|
|
|
@ -1,5 +1,47 @@
|
|||
<div class="submissions-table row container" ng-init="initFormSubmissions()">
|
||||
<div class="row">
|
||||
<div class="col-xs-4">
|
||||
Total Views: {{myform.analytics.views}}
|
||||
</div>
|
||||
|
||||
<div class="col-xs-4">
|
||||
Submissions: {{myform.analytics.submissions}}
|
||||
</div>
|
||||
|
||||
<div class="col-xs-4">
|
||||
Conversion Rate: {{myform.analytics.conversionRate}}%
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
|
||||
<div class="col-xs-2">
|
||||
<strong>Field Title</strong>
|
||||
</div>
|
||||
<div class="col-xs-2">
|
||||
<strong>Field Views</strong>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-4">
|
||||
<strong>User dropoff rate at this field</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-12" ng-repeat="fieldStats in myform.analytics.fields">
|
||||
|
||||
<div class="col-xs-2">
|
||||
{{fieldStats.field.title}}
|
||||
</div>
|
||||
<div class="col-xs-2">
|
||||
{{fieldStats.totalViews}}
|
||||
</div>
|
||||
|
||||
<div class="col-xs-4">
|
||||
{{fieldStats.dropoffRate}}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<div class="row">
|
||||
<div class="col-xs-2">
|
||||
<button class="btn btn-danger" ng-click="deleteSelectedSubmissions()" ng-disabled="!isAtLeastOneChecked();">
|
||||
|
@ -69,11 +111,8 @@
|
|||
<th class="scope">
|
||||
{{$index+1}}
|
||||
</th>
|
||||
<td ng-if="field.fieldType == 'dropdown'" data-ng-repeat="field in row.form_fields">
|
||||
{{field.fieldValue.field_title}}
|
||||
</td>
|
||||
|
||||
<td ng-if="field.fieldType != 'dropdown'" data-ng-repeat="field in row.form_fields">
|
||||
|
||||
<td data-ng-repeat="field in row.form_fields">
|
||||
{{field.fieldValue}}
|
||||
</td>
|
||||
<td ng-if="myform.plugins.oscarhost.baseUrl">
|
||||
|
|
|
@ -237,12 +237,6 @@ form .row.field {
|
|||
form .row.field.dropdown > .field-input input:focus {
|
||||
border: none;
|
||||
}
|
||||
|
||||
form .row.field.dropdown > .field-input .ui-select-match {
|
||||
border: 0 grey solid;
|
||||
border-width: 0 0 2px 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
form .dropdown > .field-input .ui-select-choices-row-inner {
|
||||
border-radius: 3px;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('forms').directive('fieldIconDirective', function() {
|
||||
|
||||
|
||||
return {
|
||||
template: '<i class="{{typeIcon}}"></i>',
|
||||
restrict: 'E',
|
||||
|
@ -28,6 +28,6 @@ angular.module('forms').directive('fieldIconDirective', function() {
|
|||
'number': 'fa fa-slack'
|
||||
};
|
||||
$scope.typeIcon = iconTypeMap[$scope.typeName];
|
||||
},
|
||||
}
|
||||
};
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,9 +13,24 @@ angular.module('forms').directive('fieldDirective', ['$http', '$compile', '$root
|
|||
|
||||
var getTemplateUrl = function(fieldType) {
|
||||
var type = fieldType;
|
||||
var templateUrl = 'modules/forms/base/views/directiveViews/field/';
|
||||
|
||||
if (__indexOf.call(supportedFields, type) >= 0) {
|
||||
var supported_fields = [
|
||||
'textfield',
|
||||
'textarea',
|
||||
'date',
|
||||
'dropdown',
|
||||
'hidden',
|
||||
'password',
|
||||
'radio',
|
||||
'legal',
|
||||
'statement',
|
||||
'rating',
|
||||
'yes_no',
|
||||
'number',
|
||||
'natural'
|
||||
];
|
||||
if (__indexOf.call(supported_fields, type) >= 0) {
|
||||
var templateUrl = 'modules/forms/views/directiveViews/field/';
|
||||
templateUrl = templateUrl+type+'.html';
|
||||
}
|
||||
return $templateCache.get(templateUrl);
|
||||
|
@ -24,7 +39,7 @@ angular.module('forms').directive('fieldDirective', ['$http', '$compile', '$root
|
|||
return {
|
||||
template: '<div>{{field.title}}</div>',
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
scope: {
|
||||
field: '=',
|
||||
required: '&',
|
||||
design: '=',
|
||||
|
@ -85,7 +100,7 @@ angular.module('forms').directive('fieldDirective', ['$http', '$compile', '$root
|
|||
}
|
||||
var template = getTemplateUrl(fieldType);
|
||||
element.html(template).show();
|
||||
$compile(element.contents())(scope);
|
||||
var output = $compile(element.contents())(scope);
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
'use strict';
|
||||
|
||||
//TODO: DAVID: Need to refactor this
|
||||
angular.module('forms').directive('onEnterKey', ['$rootScope', function($rootScope){
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function($scope, $element, $attrs) {
|
||||
$element.bind('keydown keypress', function(event) {
|
||||
|
||||
var keyCode = event.which || event.keyCode;
|
||||
if(keyCode === 13 && !event.shiftKey) {
|
||||
|
||||
var onEnterKeyDisabled = false;
|
||||
if($attrs.onEnterKeyDisabled !== null) onEnterKeyDisabled = $attrs.onEnterKeyDisabled;
|
||||
|
||||
if(keyCode === 13 && !event.shiftKey && !onEnterKeyDisabled) {
|
||||
event.preventDefault();
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.$eval($attrs.onEnterKey);
|
||||
|
@ -15,4 +21,56 @@ angular.module('forms').directive('onEnterKey', ['$rootScope', function($rootSco
|
|||
});
|
||||
}
|
||||
};
|
||||
}]).directive('onTabKey', ['$rootScope', function($rootScope){
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function($scope, $element, $attrs) {
|
||||
$element.bind('keydown keypress', function(event) {
|
||||
|
||||
var keyCode = event.which || event.keyCode;
|
||||
|
||||
if(keyCode === 9 && !event.shiftKey) {
|
||||
|
||||
event.preventDefault();
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.$eval($attrs.onTabKey);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}]).directive('onEnterOrTabKey', ['$rootScope', function($rootScope){
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function($scope, $element, $attrs) {
|
||||
$element.bind('keydown keypress', function(event) {
|
||||
|
||||
var keyCode = event.which || event.keyCode;
|
||||
|
||||
if((keyCode === 13 || keyCode === 9) && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.$eval($attrs.onEnterOrTabKey);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}]).directive('onTabAndShiftKey', ['$rootScope', function($rootScope){
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function($scope, $element, $attrs) {
|
||||
$element.bind('keydown keypress', function(event) {
|
||||
|
||||
var keyCode = event.which || event.keyCode;
|
||||
|
||||
if(keyCode === 9 && event.shiftKey) {
|
||||
event.preventDefault();
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.$eval($attrs.onTabAndShiftKey);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
|
|
@ -4,7 +4,7 @@ angular.module('forms').directive('onFinishRender', function ($rootScope, $timeo
|
|||
return {
|
||||
restrict: 'A',
|
||||
link: function (scope, element, attrs) {
|
||||
|
||||
|
||||
//Don't do anything if we don't have a ng-repeat on the current element
|
||||
if(!element.attr('ng-repeat') && !element.attr('data-ng-repeat')){
|
||||
return;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
'use strict';
|
||||
|
||||
angular.module('forms').directive('submitFormDirective',
|
||||
['$http', 'TimeCounter', '$filter', '$rootScope', 'Auth',
|
||||
function ($http, TimeCounter, $filter, $rootScope, Auth) {
|
||||
|
||||
angular.module('forms').directive('submitFormDirective', ['$http', 'TimeCounter', '$filter', '$rootScope', 'Auth', 'SendVisitorData',
|
||||
function ($http, TimeCounter, $filter, $rootScope, Auth, SendVisitorData) {
|
||||
return {
|
||||
templateUrl: 'modules/forms/base/views/directiveViews/form/submit-form.client.view.html', restrict: 'E',
|
||||
scope: {
|
||||
|
@ -49,6 +49,7 @@ angular.module('forms').directive('submitFormDirective',
|
|||
TimeCounter.restartClock();
|
||||
};
|
||||
|
||||
//Fire event when window is scrolled
|
||||
$window.onscroll = function(){
|
||||
$scope.scrollPos = document.body.scrollTop || document.documentElement.scrollTop || 0;
|
||||
var elemBox = document.getElementsByClassName('activeField')[0].getBoundingClientRect();
|
||||
|
@ -88,13 +89,22 @@ angular.module('forms').directive('submitFormDirective',
|
|||
}
|
||||
};
|
||||
|
||||
$rootScope.setDropdownOption = function(){
|
||||
console.log('setDropdownOption index: ');
|
||||
};
|
||||
|
||||
/*
|
||||
** Field Controls
|
||||
*/
|
||||
var 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 === 'submit_field') {
|
||||
return $scope.myform.form_fields.length - 1;
|
||||
} else {
|
||||
return $scope.selected.index;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.setActiveField = $rootScope.setActiveField = function(field_id, field_index, animateScroll) {
|
||||
if($scope.selected === null || $scope.selected._id === field_id){
|
||||
//console.log('not scrolling');
|
||||
|
@ -121,12 +131,15 @@ angular.module('forms').directive('submitFormDirective',
|
|||
$document.scrollToElement(angular.element('.activeField'), -10, 200).then(function() {
|
||||
$scope.noscroll = false;
|
||||
setTimeout(function() {
|
||||
if (document.querySelectorAll('.activeField .focusOn')[0]) {
|
||||
//console.log(document.querySelectorAll('.activeField .focusOn')[0]);
|
||||
if (document.querySelectorAll('.activeField .focusOn').length) {
|
||||
//Handle default case
|
||||
document.querySelectorAll('.activeField .focusOn')[0].focus();
|
||||
} else {
|
||||
//console.log(document.querySelectorAll('.activeField input')[0]);
|
||||
} else if(document.querySelectorAll('.activeField input').length) {
|
||||
//Handle case for rating input
|
||||
document.querySelectorAll('.activeField input')[0].focus();
|
||||
} else {
|
||||
//Handle case for dropdown input
|
||||
document.querySelectorAll('.activeField .selectize-input')[0].focus();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -134,13 +147,15 @@ angular.module('forms').directive('submitFormDirective',
|
|||
}else {
|
||||
setTimeout(function() {
|
||||
if (document.querySelectorAll('.activeField .focusOn')[0]) {
|
||||
//console.log(document.querySelectorAll('.activeField .focusOn')[0]);
|
||||
//FIXME: DAVID: Figure out how to set focus without scroll movement in HTML Dom
|
||||
document.querySelectorAll('.activeField .focusOn')[0].focus();
|
||||
} else {
|
||||
document.querySelectorAll('.activeField input')[0].focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
SendVisitorData.send($scope.myform, getActiveField(), TimeCounter.getTimeElapsed());
|
||||
};
|
||||
|
||||
$rootScope.nextField = $scope.nextField = function(){
|
||||
|
@ -178,30 +193,39 @@ angular.module('forms').directive('submitFormDirective',
|
|||
}
|
||||
};
|
||||
|
||||
$scope.goToInvalid = function() {
|
||||
$rootScope.goToInvalid = $scope.goToInvalid = function() {
|
||||
document.querySelectorAll('.ng-invalid.focusOn')[0].focus();
|
||||
};
|
||||
|
||||
$scope.submitForm = function() {
|
||||
$rootScope.submitForm = $scope.submitForm = function() {
|
||||
|
||||
var _timeElapsed = TimeCounter.stopClock();
|
||||
$scope.loading = true;
|
||||
|
||||
var form = _.cloneDeep($scope.myform);
|
||||
|
||||
form.timeElapsed = _timeElapsed;
|
||||
|
||||
form.percentageComplete = $filter('formValidity')($scope.myform) / $scope.myform.visible_form_fields.length * 100;
|
||||
delete form.visible_form_fields;
|
||||
|
||||
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, headers) {
|
||||
//console.log('form submitted successfully');
|
||||
|
||||
console.log($scope.myform.form_fields[0]);
|
||||
$scope.myform.submitted = true;
|
||||
$scope.loading = false;
|
||||
SendVisitorData.send($scope.myform, getActiveField(), _timeElapsed);
|
||||
})
|
||||
.error(function (error) {
|
||||
$scope.loading = false;
|
||||
//console.log(error);
|
||||
console.error(error);
|
||||
$scope.error = error.message;
|
||||
});
|
||||
}, 500);
|
||||
|
|
|
@ -8,7 +8,7 @@ angular.module('forms').factory('Forms', ['$resource', 'FORM_URL',
|
|||
}, {
|
||||
'query' : {
|
||||
method: 'GET',
|
||||
isArray: true,
|
||||
isArray: true
|
||||
//DAVID: TODO: Do we really need to get visible_form_fields for a Query?
|
||||
// transformResponse: function(data, header) {
|
||||
// var forms = angular.fromJson(data);
|
||||
|
@ -24,8 +24,8 @@ angular.module('forms').factory('Forms', ['$resource', 'FORM_URL',
|
|||
method: 'GET',
|
||||
transformResponse: function(data, header) {
|
||||
var form = angular.fromJson(data);
|
||||
//console.log(form);
|
||||
form.visible_form_fields = _.filter(form.form_fields, function(field){
|
||||
|
||||
form.visible_form_fields = _.filter(form.form_fields, function(field){
|
||||
return (field.deletePreserved === false);
|
||||
});
|
||||
return form;
|
||||
|
|
|
@ -2,22 +2,29 @@
|
|||
|
||||
angular.module('forms').service('TimeCounter', [
|
||||
function(){
|
||||
var _startTime, _endTime, that=this;
|
||||
var _startTime, _endTime = null, that=this;
|
||||
|
||||
this.timeSpent = 0;
|
||||
|
||||
this.restartClock = function(){
|
||||
_startTime = Date.now();
|
||||
_endTime = _startTime;
|
||||
_endTime = null;
|
||||
// console.log('Clock Started');
|
||||
};
|
||||
|
||||
this.getTimeElapsed = function(){
|
||||
if(_startTime) {
|
||||
return Math.abs(Date.now().valueOf() - _startTime.valueOf()) / 1000;
|
||||
}
|
||||
};
|
||||
|
||||
this.stopClock = function(){
|
||||
if(_startTime){
|
||||
if(_startTime && _endTime === null){
|
||||
_endTime = Date.now();
|
||||
that.timeSpent = Math.abs(_endTime.valueOf() - _startTime.valueOf())/1000;
|
||||
// console.log('Clock Ended');
|
||||
return that.timeSpent;
|
||||
this.timeSpent = Math.abs(_endTime.valueOf() - _startTime.valueOf())/1000;
|
||||
this._startTime = this._endTime = null;
|
||||
|
||||
return this.timeSpent;
|
||||
}else{
|
||||
return new Error('Clock has not been started');
|
||||
}
|
||||
|
@ -28,4 +35,4 @@ angular.module('forms').service('TimeCounter', [
|
|||
};
|
||||
|
||||
}
|
||||
]);
|
||||
]);
|
||||
|
|
|
@ -9,11 +9,13 @@
|
|||
{{field.title}}
|
||||
<span class="required-error" ng-show="!field.required && !field.fieldValue">{{ 'OPTIONAL' | translate }}</span>
|
||||
</h3>
|
||||
<p class="col-xs-12">
|
||||
<small>{{field.description}}</small>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-xs-12 field-input">
|
||||
<div class="control-group input-append">
|
||||
<input ng-focus="setActiveField(field._id, index, true)"
|
||||
class="focusOn"
|
||||
<input class="focusOn"
|
||||
ng-style="{'color': design.colors.answerColor, 'border-color': design.colors.answerColor}"
|
||||
ng-class="{ 'no-border': !!field.fieldValue }"
|
||||
ui-date="dateOptions"
|
||||
|
@ -22,7 +24,9 @@
|
|||
ng-required="field.required"
|
||||
ng-disabled="field.disabled"
|
||||
placeholder="MM/DD/YYYY"
|
||||
on-enter-key="nextField()"
|
||||
ng-focus="setActiveField(field._id, index, true)"
|
||||
on-tab-key="nextField()"
|
||||
on-tab-and-shift-key="prevField()"
|
||||
ng-change="$root.nextField()">
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<div class="field row dropdown"
|
||||
ng-click="setActiveField(field._id, index, true)"
|
||||
ng-if="field.fieldOptions.length > 0">
|
||||
<div class="col-xs-12 field-title" ng-style="{'color': design.colors.questionColor}">
|
||||
<h3>
|
||||
|
@ -10,15 +9,22 @@
|
|||
{{field.title}}
|
||||
<span class="required-error" ng-show="!field.required">{{ 'OPTIONAL' | translate }}</span>
|
||||
</h3>
|
||||
<p class="col-xs-12">
|
||||
<small>{{field.description}}</small>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-xs-12 field-input">
|
||||
<ui-select ng-model="field.fieldValue"
|
||||
theme="selectize"
|
||||
search-enabled="true"
|
||||
search-by="option_value"
|
||||
set-search-to-answer="true"
|
||||
ng-required="field.required"
|
||||
ng-disabled="field.disabled"
|
||||
on-tab-and-shift-key="prevField()"
|
||||
on-tab-key="nextField()"
|
||||
ng-change="$root.nextField()">
|
||||
<ui-select-match placeholder="Type or select an option">
|
||||
{{$select.selected.option_value}}
|
||||
</ui-select-match>
|
||||
<ui-select-choices repeat="option in field.fieldOptions | filter: $select.search"
|
||||
ng-class="{'active': option.option_value === field.fieldValue }">
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="field row radio legal"
|
||||
on-enter-key="nextField()"
|
||||
on-enter-or-tab-key="nextField()"
|
||||
key-to-truthy key-char-truthy="y" key-char-falsey="n" field="field">
|
||||
<div class="col-xs-12 field-title" ng-style="{'color': design.colors.questionColor}">
|
||||
<h3>
|
||||
|
@ -11,10 +11,12 @@
|
|||
<span class="required-error" ng-show="!field.required">{{ 'OPTIONAL' | translate }}</span>
|
||||
</h3>
|
||||
<br>
|
||||
<p style="color:#ddd;">{{field.description}}</p>
|
||||
<p class="col-xs-12">{{field.description}}</p>
|
||||
</div>
|
||||
<div class="col-xs-12 field-input container">
|
||||
<div class="row-fluid">
|
||||
<div class="row-fluid"
|
||||
on-enter-or-tab-key="nextField()"
|
||||
on-tab-and-shift-key="prevField()">
|
||||
<label class="btn col-md-5 col-xs-12"
|
||||
ng-class="{activeBtn: field.fieldValue == 'true'}">
|
||||
<input class="focusOn"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="field row radio"
|
||||
on-enter-key="nextField()"
|
||||
on-enter-or-tab-key="nextField()"
|
||||
key-to-option field="field"
|
||||
ng-if="field.fieldOptions.length > 0">
|
||||
<div class="col-xs-12 field-title" ng-style="{'color': design.colors.questionColor}">
|
||||
|
@ -11,6 +11,9 @@
|
|||
{{field.title}}
|
||||
<span class="required-error" ng-show="!field.required">{{ 'OPTIONAL' | translate }}</span>
|
||||
</h3>
|
||||
<p class="col-xs-12">
|
||||
<small>{{field.description}}</small>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-xs-12 field-input">
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="textfield field row"
|
||||
on-enter-key="nextField()">
|
||||
on-enter-or-tab-key="nextField()">
|
||||
<div class="col-xs-12 field-title" ng-style="{'color': design.colors.questionColor}">
|
||||
<h3>
|
||||
<small class="field-number">
|
||||
|
@ -8,19 +8,27 @@
|
|||
</small>
|
||||
{{field.title}}
|
||||
<span class="required-error" ng-show="!field.required">{{ 'OPTIONAL' | translate }}</span>
|
||||
|
||||
</h3>
|
||||
<p class="col-xs-12">
|
||||
<small>{{field.description}}</small>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-xs-12 field-input">
|
||||
<input-stars max="5"
|
||||
|
||||
<input-stars max="{{field.ratingOptions.steps}}"
|
||||
ng-init="field.fieldValue = 1"
|
||||
on-star-click="$root.nextField()"
|
||||
icon-full="fa-star"
|
||||
icon-full="{{field.ratingOptions.shape}}"
|
||||
icon-base="fa fa-3x"
|
||||
icon-empty="fa-star-o"
|
||||
icon-empty="{{field.ratingOptions.shape}}"
|
||||
ng-model="field.fieldValue"
|
||||
ng-model-options="{ debounce: 250 }"
|
||||
ng-required="field.required"
|
||||
ng-disabled="field.disabled"
|
||||
on-enter-or-tab-key="nextField()"
|
||||
on-tab-and-shift-key="prevField()"
|
||||
ng-focus="setActiveField(field._id, index, true)"
|
||||
class="angular-input-stars focusOn">
|
||||
</input-stars>
|
||||
</div>
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
<div class="statement field row"
|
||||
on-enter-key="$root.nextField()"
|
||||
on-enter-or-tab-key="nextField()"
|
||||
on-tab-and-shift-key="prevField()"
|
||||
ng-focus="setActiveField(field._id, index, true)">
|
||||
<div class="row field-title field-title">
|
||||
<div class="col-xs-1"><i class="fa fa-quote-left fa-1"></i></div>
|
||||
<h2 class="text-left col-xs-9">{{field.title}}</h2>
|
||||
<p class="col-xs-12">
|
||||
<small>{{field.description}}</small>
|
||||
</p>
|
||||
</div>
|
||||
<div class="row field-title field-input">
|
||||
<p class="col-xs-12" ng-if="field.description.length">{{field.description}} </p>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<div class="field row" ng-click="setActiveField(field._id, index, true)"
|
||||
ng-focus="setActiveField(field._id, index, true)">
|
||||
<div class="field row" ng-click="setActiveField(field._id, index, true)">
|
||||
<div class="col-xs-12 field-title" ng-style="{'color': design.colors.questionColor}">
|
||||
<h3>
|
||||
<small class="field-number">
|
||||
|
@ -10,8 +9,12 @@
|
|||
<span class="required-error" ng-show="!field.required">{{ 'OPTIONAL' | translate }}</span>
|
||||
</h3>
|
||||
<small>{{ 'NEWLINE' | translate }}</small>
|
||||
<p>
|
||||
<small>{{field.description}}</small>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-xs-12 field-input">
|
||||
<small style="font-size:0.6em;">Press SHIFT+ENTER to add a newline</small>
|
||||
<textarea class="textarea focusOn" type="text"
|
||||
ng-model="field.fieldValue"
|
||||
ng-model-options="{ debounce: 250 }"
|
||||
|
@ -19,14 +22,16 @@
|
|||
value="{{field.fieldValue}}"
|
||||
ng-required="field.required"
|
||||
ng-disabled="field.disabled"
|
||||
ng-focus="setActiveField(field._id, index, true)"
|
||||
on-enter-key="nextField()">
|
||||
ng-focus="setActiveField(field._id, index, true)"
|
||||
on-enter-or-tab-key="nextField()"
|
||||
on-tab-and-shift-key="prevField()"
|
||||
style="border: none; border-left: lightgrey dashed 2px;">
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="btn btn-lg btn-default col-xs-12 col-sm-4"
|
||||
<div class="btn btn-lg btn-default col-xs-12 col-sm-4 hidden-xs"
|
||||
style="padding: 4px; margin-top:8px; background: rgba(255,255,255,0.5)">
|
||||
<button ng-disabled="!field.fieldValue || forms.myForm.{{field.fieldType}}{{$index}}.$invalid"
|
||||
ng-style="{'background-color':design.colors.buttonColor, 'color':design.colors.buttonTextColor}"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<div class="textfield field row"
|
||||
ng-click="setActiveField(field._id, index, true)">
|
||||
<div class="col-xs-12 field-title" ng-style="{'color': design.colors.questionColor}">
|
||||
<h3>
|
||||
<div class="col-xs-12 field-title row-fluid" ng-style="{'color': design.colors.questionColor}">
|
||||
<h3 class="col-xs-12">
|
||||
<small class="field-number">
|
||||
{{index+1}}
|
||||
<i class="fa fa-angle-double-right" aria-hidden="true"></i>
|
||||
|
@ -13,10 +13,13 @@
|
|||
({{ 'OPTIONAL' | translate }})
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
<p class="col-xs-12">
|
||||
<small>{{field.description}}</small>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-xs-12 field-input">
|
||||
<input ng-style="{'color': design.colors.answerColor, 'border-color': design.colors.answerColor}"
|
||||
ng-focus="setActiveField(field._id, index, true)"
|
||||
name="{{field.fieldType}}{{index}}"
|
||||
type="{{field.input_type}}"
|
||||
ng-pattern="field.validateRegex"
|
||||
|
@ -26,10 +29,15 @@
|
|||
ng-model="field.fieldValue"
|
||||
ng-model-options="{ debounce: 250 }"
|
||||
value="field.fieldValue"
|
||||
ng-model="field.fieldValue"
|
||||
ng-model-options="{ debounce: 250 }"
|
||||
value="field.fieldValue"
|
||||
ng-focus="setActiveField(field._id, index, true)"
|
||||
on-enter-or-tab-key="nextField()"
|
||||
on-tab-and-shift-key="prevField()"
|
||||
ng-required="field.required"
|
||||
ng-disabled="field.disabled"
|
||||
aria-describedby="inputError2Status"
|
||||
on-enter-key="nextField()">
|
||||
aria-describedby="inputError2Status">
|
||||
</div>
|
||||
<div class="col-xs-12">
|
||||
<div ng-show="forms.myForm.{{field.fieldType}}{{index}}.$invalid && !!forms.myForm.{{field.fieldType}}{{index}}.$viewValue " class="alert alert-danger" role="alert">
|
||||
|
@ -42,7 +50,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="btn btn-lg btn-default col-xs-12 col-sm-4"
|
||||
<div class="btn btn-lg btn-default col-xs-12 col-sm-4 hidden-xs"
|
||||
style="padding: 4px; margin-top:8px; background: rgba(255,255,255,0.5)">
|
||||
<button ng-disabled="!field.fieldValue || forms.myForm.{{field.fieldType}}{{$index}}.$invalid"
|
||||
ng-style="{'background-color':design.colors.buttonColor, 'color':design.colors.buttonTextColor}"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div class="field row radio"
|
||||
ng-click="setActiveField(field._id, index, true)"
|
||||
on-enter-key="nextField()"
|
||||
on-tab-and-shift-key="prevField()"
|
||||
key-to-truthy key-char-truthy="y" key-char-falsey="n" field="field">
|
||||
<div class="col-xs-12 field-title" ng-style="{'color': design.colors.questionColor}">
|
||||
<h3 class="row">
|
||||
|
@ -25,10 +25,10 @@
|
|||
<input type="radio" value="true"
|
||||
class="focusOn"
|
||||
style="opacity: 0; margin-left: 0px;"
|
||||
ng-focus="setActiveField(field._id, index, true)"
|
||||
ng-model="field.fieldValue"
|
||||
ng-model-options="{ debounce: 250 }"
|
||||
ng-required="field.required"
|
||||
ng-focus="setActiveField(field._id, index, true)"
|
||||
ng-model-options="{ debounce: 250 }"
|
||||
ng-required="field.required"
|
||||
ng-change="$root.nextField()"
|
||||
ng-disabled="field.disabled" />
|
||||
<div class="letter">
|
||||
|
@ -45,10 +45,9 @@
|
|||
|
||||
<input type="radio" value="false"
|
||||
style="opacity:0; margin-left:0px;"
|
||||
ng-focus="setActiveField(field._id, index, true)"
|
||||
ng-model="field.fieldValue"
|
||||
ng-model-options="{ debounce: 250 }"
|
||||
ng-required="field.required"
|
||||
ng-model-options="{ debounce: 250 }"
|
||||
ng-required="field.required"
|
||||
ng-change="$root.nextField()"
|
||||
ng-disabled="field.disabled"/>
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ ng-style="{'color':button.color}">
|
|||
data-id="{{field._id}}"
|
||||
ng-class="{activeField: selected._id == field._id }"
|
||||
class="row field-directive">
|
||||
|
||||
<field-directive field="field" design="myform.design" index="$index" forms="forms">
|
||||
</field-directive>
|
||||
</div>
|
||||
|
@ -76,8 +77,10 @@ ng-style="{'color':button.color}">
|
|||
<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"
|
||||
ng-disabled="loading || forms.myForm.$invalid"
|
||||
ng-click="submitForm()"
|
||||
on-enter-key="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;">
|
||||
|
||||
|
@ -85,8 +88,10 @@ ng-style="{'color':button.color}">
|
|||
</button>
|
||||
|
||||
<button ng-if="forms.myForm.$invalid"
|
||||
class="Button btn col-sm-2 col-xs-8"
|
||||
class="Button btn col-sm-2 col-xs-8 focusOn"
|
||||
ng-click="goToInvalid()"
|
||||
on-enter-key="goToInvalid()"
|
||||
on-enter-key-disabled="!forms.myForm.$invalid"
|
||||
style="font-size: 1.6em; margin-left: 1em; margin-top: 1em; background-color:#990000; color:white">
|
||||
{{ 'REVIEW' | translate }}
|
||||
</button>
|
||||
|
|
|
@ -1,4 +1,17 @@
|
|||
|
||||
|
||||
<section class="public-form" ng-style="{ 'background-color': myform.design.colors.backgroundColor }">
|
||||
<submit-form-directive myform="myform"></submit-form-directive>
|
||||
</section>
|
||||
|
||||
<!-- User's Google Analytics -->
|
||||
<script ng-if="myform.analytics.gaCode">
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', '{{myform.analytics.gaCode}}', 'auto');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
<!-- End Google Analytics -->
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
|
||||
// Create the SendVisitorData service
|
||||
angular
|
||||
.module('forms')
|
||||
.factory('SendVisitorData', SendVisitorData);
|
||||
|
||||
SendVisitorData.$inject = ['Socket', '$state'];
|
||||
|
||||
function SendVisitorData(Socket, $state) {
|
||||
|
||||
// Create a controller method for sending visitor data
|
||||
function send(form, lastActiveIndex, timeElapsed) {
|
||||
|
||||
console.log(lastActiveIndex);
|
||||
// Create a new message object
|
||||
var visitorData = {
|
||||
referrer: document.referrer,
|
||||
isSubmitted: form.submitted,
|
||||
formId: form._id,
|
||||
lastActiveField: form.form_fields[lastActiveIndex]._id,
|
||||
timeElapsed: timeElapsed
|
||||
};
|
||||
Socket.emit('form-visitor-data', visitorData);
|
||||
}
|
||||
|
||||
function init(){
|
||||
// Make sure the Socket is connected
|
||||
if (!Socket.socket) {
|
||||
Socket.connect();
|
||||
}
|
||||
}
|
||||
|
||||
var service = {
|
||||
send: send
|
||||
};
|
||||
|
||||
init();
|
||||
return service;
|
||||
|
||||
}
|
||||
}());
|
||||
|
|
@ -68,7 +68,7 @@ angular.module('NodeForm.templates', []).run(['$templateCache', function($templa
|
|||
"<div class=\"field row\" ng-click=\"setActiveField(field._id, index, true)\"><div class=\"col-xs-12 field-title\" ng-style=\"{'color': design.colors.questionColor}\"><h3><span class=\"fa fa-angle-double-right\"></span> {{field.title}} <span class=required-error ng-show=\"field.required && !field.fieldValue\">*(required)</span></h3></div><div class=\"col-xs-12 field-input\"><textarea class=textarea type=text ng-model=field.fieldValue ng-model-options=\"{ debounce: 250 }\" ng-class=\"{ 'no-border': !!field.fieldValue }\" value={{field.fieldValue}} class=focusOn ng-required=field.required ng-disabled=field.disabled ng-focus=\"setActiveField(field._id, index, true)\">\n" +
|
||||
" </textarea></div></div><div class=\"col-xs-12 row\"><div class=\"btn btn-lg btn-default row-fluid\" style=\"padding: 4px; margin-top:8px; background: rgba(255,255,255,0.5)\"><button ng-disabled=!field.fieldValue ng-click=$root.nextField() ng-style=\"{'background-color':design.colors.buttonColor, 'color':design.colors.buttonTextColor}\" class=\"btn col-sm-5 col-xs-5\">OK <i class=\"fa fa-check\"></i></button><div class=\"col-sm-3 col-xs-6\" style=margin-top:0.2em><small style=\"color:#ddd; font-size:70%\">press ENTER</small></div></div></div>");
|
||||
$templateCache.put("../public/modules/forms/views/directiveViews/field/textfield.html",
|
||||
"<div class=\"textfield field row\" ng-click=\"setActiveField(field._id, index, true)\"><div class=\"col-xs-12 field-title\" ng-style=\"{'color': design.colors.questionColor}\" ng-style=\"{'color': design.colors.questionColor}\"><h3><span class=\"fa fa-angle-double-right\"></span> {{field.title}} <span class=required-error ng-show=\"field.required && !field.fieldValue\">*(required)</span></h3></div><div class=\"col-xs-12 field-input\"><input ng-style=\"{'color': design.colors.answerColor, 'border-color': design.colors.answerColor}\" ng-focus=\"setActiveField(field._id, index, true)\" name={{field.fieldType}}{{index}} type={{field.input_type}} placeholder={{field.placeholder}} ng-class=\"{ 'no-border': !!field.fieldValue }\" class=\"focusOn text-field-input\" ng-model=field.fieldValue ng-model-options=\"{ debounce: 250 }\" value=field.fieldValue ng-required=field.required ng-disabled=field.disabled aria-describedby=inputError2Status on-enter-key=nextField()></div><div class=col-xs-12><div ng-show=forms.myForm.{{field.fieldType}}{{index}}.$invalid class=\"alert alert-danger\" role=alert><span class=\"glyphicon glyphicon-exclamation-sign\" aria-hidden=true></span> <span class=sr-only>Error:</span> Enter a valid email address</div></div></div><div class=\"col-xs-12 row\"><div class=\"btn btn-lg btn-default row-fluid\" style=\"padding: 4px; margin-top:8px; background: rgba(255,255,255,0.5)\"><button ng-disabled=\"!field.fieldValue || forms.myForm.{{field.fieldType}}{{$index}}.$invalid\" ng-style=\"{'background-color':design.colors.buttonColor, 'color':design.colors.buttonTextColor}\" ng-click=$root.nextField() class=\"btn col-sm-5 col-xs-5\">OK <i class=\"fa fa-check\"></i></button><div class=\"col-sm-3 col-xs-6\" style=margin-top:0.2em><small style=\"color:#ddd; font-size:70%\">press ENTER</small></div></div></div>");
|
||||
"<div class=\"textfield field row\" ng-click=\"setActiveField(field._id, index, true)\"><div class=\"col-xs-12 field-title\" ng-style=\"{'color': design.colors.questionColor}\" ng-style=\"{'color': design.colors.questionColor}\"><h3><span class=\"fa fa-angle-double-right\"></span> {{field.title}} <span class=required-error ng-show=\"field.required && !field.fieldValue\">*(required)</span></h3></div><div class=\"col-xs-12 field-input\"><input ng-style=\"{'color': design.colors.answerColor, 'border-color': design.colors.answerColor}\" ng-focus=\"setActiveField(field._id, index, true)\" name={{field.fieldType}}{{index}} type={{field.input_type}} placeholder={{field.placeholder}} ng-class=\"{ 'no-border': !!field.fieldValue }\" class=\"focusOn text-field-input\" ng-model=field.fieldValue ng-model-options=\"{ debounce: 250 }\" value=field.fieldValue ng-required=field.required ng-disabled=field.disabled aria-describedby=inputError2Status on-enter-or-tab-key=nextField()></div><div class=col-xs-12><div ng-show=forms.myForm.{{field.fieldType}}{{index}}.$invalid class=\"alert alert-danger\" role=alert><span class=\"glyphicon glyphicon-exclamation-sign\" aria-hidden=true></span> <span class=sr-only>Error:</span> Enter a valid email address</div></div></div><div class=\"col-xs-12 row\"><div class=\"btn btn-lg btn-default row-fluid\" style=\"padding: 4px; margin-top:8px; background: rgba(255,255,255,0.5)\"><button ng-disabled=\"!field.fieldValue || forms.myForm.{{field.fieldType}}{{$index}}.$invalid\" ng-style=\"{'background-color':design.colors.buttonColor, 'color':design.colors.buttonTextColor}\" ng-click=$root.nextField() class=\"btn col-sm-5 col-xs-5\">OK <i class=\"fa fa-check\"></i></button><div class=\"col-sm-3 col-xs-6\" style=margin-top:0.2em><small style=\"color:#ddd; font-size:70%\">press ENTER</small></div></div></div>");
|
||||
$templateCache.put("../public/modules/forms/views/directiveViews/field/yes_no.html",
|
||||
"<div class=\"field row radio\" ng-click=\"setActiveField(field._id, index, true)\"><div class=\"col-xs-12 field-title\" ng-style=\"{'color': design.colors.questionColor}\"><h3 class=row><span class=\"fa fa-angle-double-right\"></span> {{field.title}} <span class=required-error ng-show=\"field.required && !field.fieldValue\">*(required)</span></h3><p class=row>{{field.description}}</p></div><div class=\"col-xs-12 field-input\"><div class=row><label class=\"btn btn-default col-md-2 col-sm-3 col-xs-4\" style=\"background: rgba(0,0,0,0.1); text-align:left\"><input type=radio value=true class=focusOn style=\"opacity: 0; margin-left: 0px\" ng-focus=\"setActiveField(field._id, index, true)\" ng-model=field.fieldValue ng-model-options=\"{ debounce: 250 }\" ng-required=field.required ng-click=$root.nextField() ng-disabled=\"field.disabled\"><div class=letter>Y</div><span>Yes</span> <i ng-show=\"field.fieldValue === 'true'\" class=\"fa fa-check\" aria-hidden=true></i></label></div><div class=row style=\"margin-top: 10px\"><label class=\"btn btn-default col-md-2 col-sm-3 col-xs-4\" style=\"background: rgba(0,0,0,0.1); text-align:left\"><input type=radio value=false style=\"opacity:0; margin-left:0px\" ng-focus=\"setActiveField(field._id, index, true)\" ng-model=field.fieldValue ng-model-options=\"{ debounce: 250 }\" ng-required=field.required ng-click=$root.nextField() ng-disabled=\"field.disabled\"><div class=letter>N</div><span>No</span> <i ng-show=\"field.fieldValue === 'false'\" class=\"fa fa-check\" aria-hidden=true></i></label></div></div></div><br>");
|
||||
$templateCache.put("../public/modules/forms/views/directiveViews/form/configure-form.client.view.html",
|
||||
|
|
|
@ -172,50 +172,51 @@ var questions = [
|
|||
}
|
||||
];
|
||||
|
||||
console.log(chalk.green('\n\nHi, welcome to TellForm Setup'));
|
||||
if(!fs.existsSync('./\.env')) {
|
||||
console.log(chalk.green('\n\nHi, welcome to TellForm Setup'));
|
||||
|
||||
console.log(chalk.green('You should only run this the first time you setup TellForm\n--------------------------------------------------\n\n'));
|
||||
console.log(chalk.green('You should only run this the first time you setup TellForm\n--------------------------------------------------\n\n'));
|
||||
|
||||
inquirer.prompt([questions[0]]).then(function (confirmAns) {
|
||||
if(confirmAns['shouldContinue']) {
|
||||
inquirer.prompt([questions[0]]).then(function (confirmAns) {
|
||||
if (confirmAns['shouldContinue']) {
|
||||
|
||||
inquirer.prompt(questions.slice(1)).then(function (answers) {
|
||||
answers['NODE_ENV'] = 'production';
|
||||
answers['SIGNUP_DISABLED'] = false ? answers['SIGNUP_DISABLED'] === false : true;
|
||||
inquirer.prompt(questions.slice(1)).then(function (answers) {
|
||||
answers['NODE_ENV'] = 'production';
|
||||
answers['SIGNUP_DISABLED'] = false ? answers['SIGNUP_DISABLED'] === false : true;
|
||||
|
||||
var email = answers['email'];
|
||||
var pass = answers['password'];
|
||||
delete answers['email'];
|
||||
delete answers['password'];
|
||||
|
||||
var email = answers['email'];
|
||||
var pass = answers['password'];
|
||||
delete answers['email'];
|
||||
delete answers['password'];
|
||||
envfile.stringify(answers, function (err, str) {
|
||||
fs.outputFile('./\.env', str, function (err) {
|
||||
if (err) return console.error(chalk.red(err));
|
||||
console.log(chalk.green('Successfully created .env file'));
|
||||
});
|
||||
user = new User({
|
||||
firstName: 'Admin',
|
||||
lastName: 'Account',
|
||||
email: email,
|
||||
username: email,
|
||||
password: pass,
|
||||
provider: 'local',
|
||||
roles: ['admin', 'user']
|
||||
});
|
||||
|
||||
envfile.stringify(answers, function (err, str) {
|
||||
fs.outputFile('..//.env', str, function(err){
|
||||
if (err) return console.error(chalk.red(err));
|
||||
console.log(chalk.green('Successfully created .env file'));
|
||||
});
|
||||
user = new User({
|
||||
firstName: 'Admin',
|
||||
lastName: 'Account',
|
||||
email: email,
|
||||
username: email,
|
||||
password: pass,
|
||||
provider: 'local',
|
||||
roles: ['admin', 'user']
|
||||
});
|
||||
user.save(function (err) {
|
||||
if (err) return console.error(chalk.red(err));
|
||||
console.log(chalk.green('Successfully created user'));
|
||||
delete email;
|
||||
delete pass;
|
||||
|
||||
user.save(function (err) {
|
||||
if (err) return console.error(chalk.red(err));
|
||||
console.log(chalk.green('Successfully created user'));
|
||||
delete email;
|
||||
delete pass;
|
||||
|
||||
console.log(chalk.green('Have fun using TellForm!'));
|
||||
process.exit(1);
|
||||
console.log(chalk.green('Have fun using TellForm!'));
|
||||
process.exit(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
console.log(chalk.green('Have fun using TellForm!'));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log(chalk.green('Have fun using TellForm!'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ if (process.env.NODE_ENV === 'secure') {
|
|||
console.log('--');
|
||||
|
||||
process.on('uncaughtException', function (err) {
|
||||
console.error((new Date).toUTCString() + ' uncaughtException:', err.message);
|
||||
console.error((new Date()).toUTCString() + ' uncaughtException:', err.message);
|
||||
console.error(err.stack);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue