added analytics
This commit is contained in:
parent
709c2c68d2
commit
20634f8f19
|
@ -19,12 +19,12 @@ var mongoose = require('mongoose'),
|
||||||
*/
|
*/
|
||||||
exports.uploadPDF = function(req, res, next) {
|
exports.uploadPDF = function(req, res, next) {
|
||||||
|
|
||||||
console.log('inside uploadPDF');
|
//console.log('inside uploadPDF');
|
||||||
|
|
||||||
// console.log('\n\nProperty Descriptor\n-----------');
|
// console.log('\n\nProperty Descriptor\n-----------');
|
||||||
// console.log(Object.getOwnPropertyDescriptor(req.files.file, 'path'));
|
// console.log(Object.getOwnPropertyDescriptor(req.files.file, 'path'));
|
||||||
|
|
||||||
console.log(req.file);
|
//console.log(req.file);
|
||||||
|
|
||||||
if(req.file){
|
if(req.file){
|
||||||
var pdfFile = req.file;
|
var pdfFile = req.file;
|
||||||
|
@ -159,9 +159,20 @@ exports.deleteSubmissions = function(req, res) {
|
||||||
res.status(400).send({
|
res.status(400).send({
|
||||||
message: errorHandler.getErrorMessage(err)
|
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) {
|
exports.createSubmission = function(req, res) {
|
||||||
|
|
||||||
var form = req.form;
|
var form = req.form;
|
||||||
// console.log('in createSubmission()');
|
|
||||||
// console.log(req.body);
|
|
||||||
|
|
||||||
var submission = new FormSubmission({
|
var submission = new FormSubmission({
|
||||||
admin: req.form.admin._id,
|
admin: req.form.admin._id,
|
||||||
|
@ -212,7 +221,7 @@ exports.createSubmission = function(req, res) {
|
||||||
submission.save(function(err, submission){
|
submission.save(function(err, submission){
|
||||||
if(err){
|
if(err){
|
||||||
console.log(err.message);
|
console.log(err.message);
|
||||||
res.status(400).send({
|
res.status(500).send({
|
||||||
message: errorHandler.getErrorMessage(err)
|
message: errorHandler.getErrorMessage(err)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -280,8 +289,9 @@ exports.create = function(req, res) {
|
||||||
exports.read = function(req, res) {
|
exports.read = function(req, res) {
|
||||||
var validUpdateTypes= Form.schema.path('plugins.oscarhost.settings.updateType').enumValues;
|
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;
|
newForm.plugins.oscarhost.settings.validUpdateTypes = validUpdateTypes;
|
||||||
|
|
||||||
res.json(newForm);
|
res.json(newForm);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -376,11 +386,12 @@ exports.formByID = function(req, res, next, id) {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
//Remove sensitive information from User object
|
//Remove sensitive information from User object
|
||||||
form.admin.password = undefined;
|
var _form = form;
|
||||||
form.admin.salt = undefined;
|
_form.admin.password = undefined;
|
||||||
form.provider = undefined;
|
_form.admin.salt = undefined;
|
||||||
|
_form.provider = undefined;
|
||||||
|
|
||||||
req.form = form;
|
req.form = _form;
|
||||||
return next();
|
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
|
* Form Schema
|
||||||
*/
|
*/
|
||||||
|
@ -66,6 +81,13 @@ var FormSchema = new Schema({
|
||||||
default: ''
|
default: ''
|
||||||
},
|
},
|
||||||
|
|
||||||
|
analytics:{
|
||||||
|
gaCode: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
visitors: [VisitorDataSchema]
|
||||||
|
},
|
||||||
|
|
||||||
form_fields: [FieldSchema],
|
form_fields: [FieldSchema],
|
||||||
submissions: [{
|
submissions: [{
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
|
@ -193,16 +215,91 @@ var FormSchema = new Schema({
|
||||||
},
|
},
|
||||||
auth: {
|
auth: {
|
||||||
user: {
|
user: {
|
||||||
type: String,
|
type: String
|
||||||
},
|
},
|
||||||
pass: {
|
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, {
|
FormSchema.plugin(mUtilities.timestamp, {
|
||||||
createdPath: 'created',
|
createdPath: 'created',
|
||||||
modifiedPath: 'lastModified',
|
modifiedPath: 'lastModified',
|
||||||
|
|
|
@ -193,15 +193,11 @@ FormFieldSchema.pre('validate', function(next) {
|
||||||
|
|
||||||
//Submission fieldValue correction
|
//Submission fieldValue correction
|
||||||
FormFieldSchema.pre('save', function(next) {
|
FormFieldSchema.pre('save', function(next) {
|
||||||
console.log('pre save');
|
|
||||||
|
|
||||||
console.log(this.isSubmission);
|
if(this.fieldType === 'dropdown' && this.isSubmission){
|
||||||
console.log(this._id);
|
//console.log(this);
|
||||||
console.log(this.submissionId);
|
|
||||||
console.log(this.fieldType);
|
|
||||||
|
|
||||||
if(this.isSubmission && this.fieldType === 'dropdown'){
|
|
||||||
this.fieldValue = this.fieldValue.option_value;
|
this.fieldValue = this.fieldValue.option_value;
|
||||||
|
//console.log(this.fieldValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
|
|
|
@ -51,6 +51,7 @@ function formFieldsSetter(form_fields){
|
||||||
form_fields[i].submissionId = form_fields[i]._id;
|
form_fields[i].submissionId = form_fields[i]._id;
|
||||||
form_fields[i]._id = new mongoose.mongo.ObjectID();
|
form_fields[i]._id = new mongoose.mongo.ObjectID();
|
||||||
}
|
}
|
||||||
|
//console.log(form_fields)
|
||||||
return form_fields;
|
return form_fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +128,6 @@ var FormSubmissionSchema = new Schema({
|
||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
FormSubmissionSchema.path('form_fields').set(formFieldsSetter);
|
FormSubmissionSchema.path('form_fields').set(formFieldsSetter);
|
||||||
|
@ -267,7 +267,6 @@ FormSubmissionSchema.pre('save', function (next) {
|
||||||
self = this,
|
self = this,
|
||||||
_form = this.form;
|
_form = this.form;
|
||||||
|
|
||||||
|
|
||||||
if(this.pdf && this.pdf.path){
|
if(this.pdf && this.pdf.path){
|
||||||
dest_filename = self.title.replace(/ /g,'')+'_submission_'+Date.now()+'.pdf';
|
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('/');
|
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-->
|
<!--Embedding The signupDisabled Boolean-->
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var signupDisabled = {{signupDisabled | safe}};
|
var signupDisabled = {{signupDisabled | safe}};
|
||||||
|
var socketPort = {{socketPort | safe}};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!--Socket.io Client Dependency-->
|
||||||
|
<script src="https://cdn.socket.io/socket.io-1.4.5.js"></script>
|
||||||
|
|
||||||
<!--Bower JS dependencies-->
|
<!--Bower JS dependencies-->
|
||||||
{% for bowerJSFile in bowerJSFiles %}
|
{% for bowerJSFile in bowerJSFiles %}
|
||||||
<script type="text/javascript" src="{{bowerJSFile}}"></script>
|
<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 src="https://cdn.ravenjs.com/2.3.0/angular/raven.min.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
Raven.config('http://825fefd6b4ed4a4da199c1b832ca845c@sentry.tellform.com/2').install();
|
Raven.config('https://825fefd6b4ed4a4da199c1b832ca845c@sentry.tellform.com/2').install();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- [if lt IE 9]>
|
<!-- [if lt IE 9]>
|
||||||
|
|
8
config/env/all.js
vendored
8
config/env/all.js
vendored
|
@ -8,6 +8,8 @@ module.exports = {
|
||||||
keywords: process.env.APP_KEYWORDS || 'typeform, pdfs, forms, opensource, formbuilder, google forms, nodejs'
|
keywords: process.env.APP_KEYWORDS || 'typeform, pdfs, forms, opensource, formbuilder, google forms, nodejs'
|
||||||
},
|
},
|
||||||
port: process.env.PORT || 3000,
|
port: process.env.PORT || 3000,
|
||||||
|
socketPort: process.env.SOCKET_PORT || 35729,
|
||||||
|
|
||||||
templateEngine: 'swig',
|
templateEngine: 'swig',
|
||||||
|
|
||||||
reCAPTCHA_Key: process.env.reCAPTCHA_KEY || '',
|
reCAPTCHA_Key: process.env.reCAPTCHA_KEY || '',
|
||||||
|
@ -81,15 +83,15 @@ module.exports = {
|
||||||
views: [
|
views: [
|
||||||
'public/modules/*/views/*.html',
|
'public/modules/*/views/*.html',
|
||||||
'public/modules/*/views/*/*.html',
|
'public/modules/*/views/*/*.html',
|
||||||
'public/modules/*/views/*/*/*.html',
|
'public/modules/*/views/*/*/*.html'
|
||||||
],
|
],
|
||||||
unit_tests: [
|
unit_tests: [
|
||||||
'public/lib/angular-mocks/angular-mocks.js',
|
'public/lib/angular-mocks/angular-mocks.js',
|
||||||
'public/modules/*/tests/unit/*.js',
|
'public/modules/*/tests/unit/*.js',
|
||||||
'public/modules/*/tests/unit/**/*.js',
|
'public/modules/*/tests/unit/**/*.js'
|
||||||
],
|
],
|
||||||
e2e_tests: [
|
e2e_tests: [
|
||||||
'public/modules/*/tests/e2e/**.js',
|
'public/modules/*/tests/e2e/**.js'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,7 +26,16 @@ var fs = require('fs-extra'),
|
||||||
device = require('express-device'),
|
device = require('express-device'),
|
||||||
client = new raven.Client(config.DSN);
|
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) {
|
module.exports = function(db) {
|
||||||
// Initialize express app
|
// Initialize express app
|
||||||
|
@ -43,6 +52,7 @@ module.exports = function(db) {
|
||||||
app.locals.signupDisabled = config.signupDisabled;
|
app.locals.signupDisabled = config.signupDisabled;
|
||||||
app.locals.description = config.app.description;
|
app.locals.description = config.app.description;
|
||||||
app.locals.keywords = config.app.keywords;
|
app.locals.keywords = config.app.keywords;
|
||||||
|
app.locals.socketPort = config.socketPort;
|
||||||
|
|
||||||
app.locals.bowerJSFiles = config.getBowerJSAssets();
|
app.locals.bowerJSFiles = config.getBowerJSAssets();
|
||||||
app.locals.bowerCssFiles = config.getBowerCSSAssets();
|
app.locals.bowerCssFiles = config.getBowerCSSAssets();
|
||||||
|
@ -146,11 +156,11 @@ module.exports = function(db) {
|
||||||
|
|
||||||
|
|
||||||
// Add headers for Sentry
|
// Add headers for Sentry
|
||||||
/*
|
|
||||||
app.use(function (req, res, next) {
|
app.use(function (req, res, next) {
|
||||||
|
|
||||||
// Website you wish to allow to connect
|
// 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
|
// Request methods you wish to allow
|
||||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
|
||||||
|
@ -165,7 +175,7 @@ module.exports = function(db) {
|
||||||
// Pass to next layer of middleware
|
// Pass to next layer of middleware
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
*/
|
|
||||||
// Sentry (Raven) middleware
|
// Sentry (Raven) middleware
|
||||||
// app.use(raven.middleware.express.requestHandler(config.DSN));
|
// app.use(raven.middleware.express.requestHandler(config.DSN));
|
||||||
|
|
||||||
|
@ -211,6 +221,8 @@ module.exports = function(db) {
|
||||||
return httpsServer;
|
return httpsServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app = configureSocketIO(app, db);
|
||||||
|
|
||||||
// Return Express server instance
|
// Return Express server instance
|
||||||
return app;
|
return app;
|
||||||
};
|
};
|
||||||
|
|
25
config/socket.io.js
Normal file
25
config/socket.io.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Load the module dependencies
|
||||||
|
var config = require('./config'),
|
||||||
|
path = require('path'),
|
||||||
|
http = require('http'),
|
||||||
|
socketio = require('socket.io'),
|
||||||
|
session = require('express-session');
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
};
|
|
@ -70,6 +70,7 @@
|
||||||
"node-freegeoip": "0.0.1",
|
"node-freegeoip": "0.0.1",
|
||||||
"nodemailer": "~1.10.0",
|
"nodemailer": "~1.10.0",
|
||||||
"passport": "~0.3.0",
|
"passport": "~0.3.0",
|
||||||
|
"passport-anonymous": "^1.0.1",
|
||||||
"passport-facebook": "~2.0.0",
|
"passport-facebook": "~2.0.0",
|
||||||
"passport-github": "~1.0.0",
|
"passport-github": "~1.0.0",
|
||||||
"passport-google-oauth": "~0.2.0",
|
"passport-google-oauth": "~0.2.0",
|
||||||
|
@ -82,6 +83,7 @@
|
||||||
"random-js": "^1.0.8",
|
"random-js": "^1.0.8",
|
||||||
"raven": "^0.9.0",
|
"raven": "^0.9.0",
|
||||||
"soap": "^0.11.0",
|
"soap": "^0.11.0",
|
||||||
|
"socket.io": "^1.4.6",
|
||||||
"swig": "~1.4.1"
|
"swig": "~1.4.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}());
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
|
}());
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('forms').directive('submitFormDirective', ['$http', 'TimeCounter', '$filter', '$rootScope', 'Auth',
|
angular.module('forms').directive('submitFormDirective', ['$http', 'TimeCounter', '$filter', '$rootScope', 'Auth', 'SendVisitorData',
|
||||||
function ($http, TimeCounter, $filter, $rootScope, Auth) {
|
function ($http, TimeCounter, $filter, $rootScope, Auth, SendVisitorData) {
|
||||||
return {
|
return {
|
||||||
templateUrl: 'modules/forms/views/directiveViews/form/submit-form.client.view.html',
|
templateUrl: 'modules/forms/views/directiveViews/form/submit-form.client.view.html',
|
||||||
restrict: 'E',
|
restrict: 'E',
|
||||||
|
@ -41,6 +41,7 @@ angular.module('forms').directive('submitFormDirective', ['$http', 'TimeCounter'
|
||||||
TimeCounter.restartClock();
|
TimeCounter.restartClock();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//Fire event when window is scrolled
|
||||||
$window.onscroll = function(){
|
$window.onscroll = function(){
|
||||||
$scope.scrollPos = document.body.scrollTop || document.documentElement.scrollTop || 0;
|
$scope.scrollPos = document.body.scrollTop || document.documentElement.scrollTop || 0;
|
||||||
var elemBox = document.getElementsByClassName('activeField')[0].getBoundingClientRect();
|
var elemBox = document.getElementsByClassName('activeField')[0].getBoundingClientRect();
|
||||||
|
@ -80,13 +81,22 @@ angular.module('forms').directive('submitFormDirective', ['$http', 'TimeCounter'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$rootScope.setDropdownOption = function(){
|
|
||||||
console.log('setDropdownOption index: ');
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** Field Controls
|
** 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) {
|
$scope.setActiveField = $rootScope.setActiveField = function(field_id, field_index, animateScroll) {
|
||||||
if($scope.selected === null || $scope.selected._id === field_id){
|
if($scope.selected === null || $scope.selected._id === field_id){
|
||||||
//console.log('not scrolling');
|
//console.log('not scrolling');
|
||||||
|
@ -129,6 +139,8 @@ angular.module('forms').directive('submitFormDirective', ['$http', 'TimeCounter'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SendVisitorData.send($scope.myform, getActiveField(), TimeCounter.getTimeElapsed());
|
||||||
};
|
};
|
||||||
|
|
||||||
$rootScope.nextField = $scope.nextField = function(){
|
$rootScope.nextField = $scope.nextField = function(){
|
||||||
|
@ -171,6 +183,7 @@ angular.module('forms').directive('submitFormDirective', ['$http', 'TimeCounter'
|
||||||
};
|
};
|
||||||
|
|
||||||
$rootScope.submitForm = $scope.submitForm = function() {
|
$rootScope.submitForm = $scope.submitForm = function() {
|
||||||
|
|
||||||
var _timeElapsed = TimeCounter.stopClock();
|
var _timeElapsed = TimeCounter.stopClock();
|
||||||
$scope.loading = true;
|
$scope.loading = true;
|
||||||
|
|
||||||
|
@ -181,12 +194,19 @@ angular.module('forms').directive('submitFormDirective', ['$http', 'TimeCounter'
|
||||||
form.percentageComplete = $filter('formValidity')($scope.myform) / $scope.myform.visible_form_fields.length * 100;
|
form.percentageComplete = $filter('formValidity')($scope.myform) / $scope.myform.visible_form_fields.length * 100;
|
||||||
delete form.visible_form_fields;
|
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 () {
|
setTimeout(function () {
|
||||||
$scope.submitPromise = $http.post('/forms/' + $scope.myform._id, form)
|
$scope.submitPromise = $http.post('/forms/' + $scope.myform._id, form)
|
||||||
.success(function (data, status, headers) {
|
.success(function (data, status, headers) {
|
||||||
|
console.log($scope.myform.form_fields[0]);
|
||||||
$scope.myform.submitted = true;
|
$scope.myform.submitted = true;
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
|
SendVisitorData.send($scope.myform, getActiveField(), _timeElapsed);
|
||||||
})
|
})
|
||||||
.error(function (error) {
|
.error(function (error) {
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
|
|
|
@ -2,22 +2,29 @@
|
||||||
|
|
||||||
angular.module('forms').service('TimeCounter', [
|
angular.module('forms').service('TimeCounter', [
|
||||||
function(){
|
function(){
|
||||||
var _startTime, _endTime, that=this;
|
var _startTime, _endTime = null, that=this;
|
||||||
|
|
||||||
this.timeSpent = 0;
|
this.timeSpent = 0;
|
||||||
|
|
||||||
this.restartClock = function(){
|
this.restartClock = function(){
|
||||||
_startTime = Date.now();
|
_startTime = Date.now();
|
||||||
_endTime = _startTime;
|
_endTime = null;
|
||||||
// console.log('Clock Started');
|
// console.log('Clock Started');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.getTimeElapsed = function(){
|
||||||
|
if(_startTime) {
|
||||||
|
return Math.abs(Date.now().valueOf() - _startTime.valueOf()) / 1000;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this.stopClock = function(){
|
this.stopClock = function(){
|
||||||
if(_startTime){
|
if(_startTime && _endTime === null){
|
||||||
_endTime = Date.now();
|
_endTime = Date.now();
|
||||||
that.timeSpent = Math.abs(_endTime.valueOf() - _startTime.valueOf())/1000;
|
this.timeSpent = Math.abs(_endTime.valueOf() - _startTime.valueOf())/1000;
|
||||||
// console.log('Clock Ended');
|
this._startTime = this._endTime = null;
|
||||||
return that.timeSpent;
|
|
||||||
|
return this.timeSpent;
|
||||||
}else{
|
}else{
|
||||||
return new Error('Clock has not been started');
|
return new Error('Clock has not been started');
|
||||||
}
|
}
|
||||||
|
|
|
@ -272,6 +272,22 @@
|
||||||
</div>
|
</div>
|
||||||
</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="row field">
|
||||||
<div class="col-xs-6 field-title">Language</div>
|
<div class="col-xs-6 field-title">Language</div>
|
||||||
<div class="col-xs-4 field-input">
|
<div class="col-xs-4 field-input">
|
||||||
|
|
|
@ -1,5 +1,47 @@
|
||||||
<div class="submissions-table row container" ng-init="initFormSubmissions()">
|
<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="row">
|
||||||
<div class="col-xs-2">
|
<div class="col-xs-2">
|
||||||
<button class="btn btn-danger" ng-click="deleteSelectedSubmissions()" ng-disabled="!isAtLeastOneChecked();">
|
<button class="btn btn-danger" ng-click="deleteSelectedSubmissions()" ng-disabled="!isAtLeastOneChecked();">
|
||||||
|
|
|
@ -53,6 +53,7 @@ ng-style="{'color':button.color}">
|
||||||
data-id="{{field._id}}"
|
data-id="{{field._id}}"
|
||||||
ng-class="{activeField: selected._id == field._id }"
|
ng-class="{activeField: selected._id == field._id }"
|
||||||
class="row field-directive">
|
class="row field-directive">
|
||||||
|
|
||||||
<field-directive field="field" design="myform.design" index="$index" forms="forms">
|
<field-directive field="field" design="myform.design" index="$index" forms="forms">
|
||||||
</field-directive>
|
</field-directive>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,4 +1,17 @@
|
||||||
|
|
||||||
|
|
||||||
<section class="public-form" ng-style="{ 'background-color': myform.design.colors.backgroundColor }">
|
<section class="public-form" ng-style="{ 'background-color': myform.design.colors.backgroundColor }">
|
||||||
<submit-form-directive myform="myform"></submit-form-directive>
|
<submit-form-directive myform="myform"></submit-form-directive>
|
||||||
</section>
|
</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 -->
|
||||||
|
|
Loading…
Reference in a new issue