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) {
|
||||
|
||||
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,
|
||||
|
@ -212,7 +221,7 @@ exports.createSubmission = function(req, res) {
|
|||
submission.save(function(err, submission){
|
||||
if(err){
|
||||
console.log(err.message);
|
||||
res.status(400).send({
|
||||
res.status(500).send({
|
||||
message: errorHandler.getErrorMessage(err)
|
||||
});
|
||||
}
|
||||
|
@ -280,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);
|
||||
};
|
||||
|
||||
|
@ -376,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
|
||||
*/
|
||||
|
@ -66,6 +81,13 @@ var FormSchema = new Schema({
|
|||
default: ''
|
||||
},
|
||||
|
||||
analytics:{
|
||||
gaCode: {
|
||||
type: String
|
||||
},
|
||||
visitors: [VisitorDataSchema]
|
||||
},
|
||||
|
||||
form_fields: [FieldSchema],
|
||||
submissions: [{
|
||||
type: Schema.Types.ObjectId,
|
||||
|
@ -193,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',
|
||||
|
|
|
@ -193,15 +193,11 @@ FormFieldSchema.pre('validate', function(next) {
|
|||
|
||||
//Submission fieldValue correction
|
||||
FormFieldSchema.pre('save', function(next) {
|
||||
console.log('pre save');
|
||||
|
||||
console.log(this.isSubmission);
|
||||
console.log(this._id);
|
||||
console.log(this.submissionId);
|
||||
console.log(this.fieldType);
|
||||
|
||||
if(this.isSubmission && this.fieldType === 'dropdown'){
|
||||
if(this.fieldType === 'dropdown' && this.isSubmission){
|
||||
//console.log(this);
|
||||
this.fieldValue = this.fieldValue.option_value;
|
||||
//console.log(this.fieldValue);
|
||||
}
|
||||
|
||||
return next();
|
||||
|
|
|
@ -51,6 +51,7 @@ function formFieldsSetter(form_fields){
|
|||
form_fields[i].submissionId = form_fields[i]._id;
|
||||
form_fields[i]._id = new mongoose.mongo.ObjectID();
|
||||
}
|
||||
//console.log(form_fields)
|
||||
return form_fields;
|
||||
}
|
||||
|
||||
|
@ -127,7 +128,6 @@ var FormSubmissionSchema = new Schema({
|
|||
default: false
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
FormSubmissionSchema.path('form_fields').set(formFieldsSetter);
|
||||
|
@ -267,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]>
|
||||
|
|
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'
|
||||
},
|
||||
port: process.env.PORT || 3000,
|
||||
socketPort: process.env.SOCKET_PORT || 35729,
|
||||
|
||||
templateEngine: 'swig',
|
||||
|
||||
reCAPTCHA_Key: process.env.reCAPTCHA_KEY || '',
|
||||
|
@ -81,15 +83,15 @@ module.exports = {
|
|||
views: [
|
||||
'public/modules/*/views/*.html',
|
||||
'public/modules/*/views/*/*.html',
|
||||
'public/modules/*/views/*/*/*.html',
|
||||
'public/modules/*/views/*/*/*.html'
|
||||
],
|
||||
unit_tests: [
|
||||
'public/lib/angular-mocks/angular-mocks.js',
|
||||
'public/modules/*/tests/unit/*.js',
|
||||
'public/modules/*/tests/unit/**/*.js',
|
||||
'public/modules/*/tests/unit/**/*.js'
|
||||
],
|
||||
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'),
|
||||
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,7 +52,8 @@ 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();
|
||||
app.locals.bowerOtherFiles = config.getBowerOtherAssets();
|
||||
|
@ -146,11 +156,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');
|
||||
|
@ -165,7 +175,7 @@ module.exports = function(db) {
|
|||
// Pass to next layer of middleware
|
||||
next();
|
||||
});
|
||||
*/
|
||||
|
||||
// Sentry (Raven) middleware
|
||||
// app.use(raven.middleware.express.requestHandler(config.DSN));
|
||||
|
||||
|
@ -211,6 +221,8 @@ module.exports = function(db) {
|
|||
return httpsServer;
|
||||
}
|
||||
|
||||
app = configureSocketIO(app, db);
|
||||
|
||||
// Return Express server instance
|
||||
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",
|
||||
"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": {
|
||||
|
|
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';
|
||||
|
||||
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/views/directiveViews/form/submit-form.client.view.html',
|
||||
restrict: 'E',
|
||||
|
@ -41,6 +41,7 @@ angular.module('forms').directive('submitFormDirective', ['$http', 'TimeCounter'
|
|||
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();
|
||||
|
@ -80,13 +81,22 @@ angular.module('forms').directive('submitFormDirective', ['$http', 'TimeCounter'
|
|||
}
|
||||
};
|
||||
|
||||
$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');
|
||||
|
@ -129,6 +139,8 @@ angular.module('forms').directive('submitFormDirective', ['$http', 'TimeCounter'
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
SendVisitorData.send($scope.myform, getActiveField(), TimeCounter.getTimeElapsed());
|
||||
};
|
||||
|
||||
$rootScope.nextField = $scope.nextField = function(){
|
||||
|
@ -171,22 +183,30 @@ angular.module('forms').directive('submitFormDirective', ['$http', 'TimeCounter'
|
|||
};
|
||||
|
||||
$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($scope.myform.form_fields[0]);
|
||||
$scope.myform.submitted = true;
|
||||
$scope.loading = false;
|
||||
SendVisitorData.send($scope.myform, getActiveField(), _timeElapsed);
|
||||
})
|
||||
.error(function (error) {
|
||||
$scope.loading = false;
|
||||
|
|
|
@ -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', [
|
|||
};
|
||||
|
||||
}
|
||||
]);
|
||||
]);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();">
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<!-- Start Page View -->
|
||||
<div ng-show="!myform.submitted && myform.startPage.showStart"
|
||||
class="form-submitted"
|
||||
class="form-submitted"
|
||||
style="padding-top: 35vh;">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 text-center" style="overflow-wrap: break-word;">
|
||||
|
@ -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>
|
||||
|
|
|
@ -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 -->
|
||||
|
|
Loading…
Reference in a new issue