Merge branch 'stage' of github.com:whitef0x0/tellform into stage

Conflicts:
	README.md
	app/models/form_submission.server.model.js
	public/dist/application.min.js
	public/dist/form-application.min.js
This commit is contained in:
David Baldwynn 2016-11-08 17:25:12 -05:00
commit e88234dad5
79 changed files with 2709 additions and 12457 deletions

View file

@ -1,4 +1,4 @@
TellForm [<img src="/" width="250px">](https://digitalocean.com/)
======== ========
[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=UY555MCBZM722) [![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=UY555MCBZM722)
@ -14,8 +14,8 @@ TellForm is an *opensource alternative to TypeForm* built ontop of nodejs that c
[See examples here](https://tellform.com/examples) [See examples here](https://tellform.com/examples)
####Sponsored by ####Sponsored by
[<img src="https://upload.wikimedia.org/wikipedia/en/thumb/f/ff/DigitalOcean_logo.svg/1024px-DigitalOcean_logo.svg.png" width="250px">](https://digitalocean.com/) [<img src="https://www.digitalocean.com/assets/media/logos-badges/png/DO_Logo_Horizontal_Blue-3db19536.png" width="250px">](https://digitalocean.com/)
[<img src="https://raw.githubusercontent.com/docker-library/docs/831b07a52f9ff6577c915afc41af8158725829f4/sentry/logo.png" width="250px">](https://getsentry.com/) [<img src="https://a0wx592cvgzripj.global.ssl.fastly.net/_static/780f0361d74cc3da6680cfa4f855336a/getsentry/images/branding/png/sentry-horizontal-black.png" width="250px">](https://getsentry.com/)
[<img src="https://dka575ofm4ao0.cloudfront.net/assets/base/logos/common-aececb0b4319b8fb61ac5b47a6983f96.png" width="250px">](https://statuspage.io/) [<img src="https://dka575ofm4ao0.cloudfront.net/assets/base/logos/common-aececb0b4319b8fb61ac5b47a6983f96.png" width="250px">](https://statuspage.io/)
[<img src="http://bcsrq.com/wp-content/uploads/2014/04/StickerMuleLogo300.png" width="250px">](https://stickermule.com/) [<img src="http://bcsrq.com/wp-content/uploads/2014/04/StickerMuleLogo300.png" width="250px">](https://stickermule.com/)
[<img src="https://app.sparkpost.com/assets/images/sparkpost-logo-color.svg" width="250px">](https://sparkpost.com/) [<img src="https://app.sparkpost.com/assets/images/sparkpost-logo-color.svg" width="250px">](https://sparkpost.com/)
@ -50,6 +50,8 @@ Before you start, make sure you have
1. Redis installed and running at 127.0.0.1:6379 1. Redis installed and running at 127.0.0.1:6379
2. MongoDB installed and running at 127.0.0.1:27017 (OR specify the host and port in config/env/all) 2. MongoDB installed and running at 127.0.0.1:27017 (OR specify the host and port in config/env/all)
Also make sure to install DNS Masq or equivalent if running it locally on your computer (look at dns_masq_setup_osx for instructions on OSX)
Install dependencies first. Install dependencies first.
```bash ```bash
$ npm install $ npm install

View file

@ -13,11 +13,19 @@ exports.index = function(req, res) {
}; };
exports.form = function(req, res) { exports.form = function(req, res) {
//Allow form to be embeded //Allow form to be embedded
res.removeHeader('X-Frame-Options'); res.set('X-Frame-Options', 'GOFORIT');
res.render('form', { res.render('form', {
user: req.user || null, user: req.user || null,
request: req request: req
}); });
}; };
exports.redoc = function(req, res) {
res.render('redoc', {
request: req
});
};

View file

@ -12,6 +12,7 @@ var mongoose = require('mongoose'),
fs = require('fs-extra'), fs = require('fs-extra'),
async = require('async'), async = require('async'),
path = require('path'), path = require('path'),
diff = require('deep-diff'),
_ = require('lodash'); _ = require('lodash');
/** /**
@ -69,74 +70,6 @@ exports.uploadPDF = function(req, res, next) {
} }
}; };
/**
* Upload PDF
*/
/*
exports.uploadSubmissionFile = function(req, res, next) {
console.log('inside uploadPDF');
// console.log('\n\nProperty Descriptor\n-----------');
// console.log(Object.getOwnPropertyDescriptor(req.files.file, 'path'));
console.log(req.files);
if(req.files){
var file, _user, _path;
for(var i=0; i<req.files.length; i++){
file = req.files[i];
_user = req.user;
_path = file.path;
if (file.size === 0) {
return next(new Error('File uploaded is EMPTY'));
}else if(file.size > 100000000){
return next(new Error('File uploaded exceeds MAX SIZE of 100MB'));
}else {
fs.exists(_path, function(exists) {
//If file exists move to user's form directory
if(exists) {
var newDestination = config.tmpUploadPath+_user.username;
var stat = null;
try {
stat = fs.statSync(newDestination);
} catch (err) {
fs.mkdirSync(newDestination);
}
if (stat && !stat.isDirectory()) {
console.log('Directory cannot be created');
return next(new Error('Directory cannot be created because an inode of a different type exists at "' + newDestination + '"'));
}
console.log(path.join(newDestination, pdfFile.filename));
fs.move(pdfFile.path, path.join(newDestination, pdfFile.filename), function (err) {
if (err) {
return next(new Error(err.message));
}
pdfFile.path = path.join(newDestination, pdfFile.filename);
console.log(pdfFile.filename + ' uploaded to ' + pdfFile.path);
res.json(pdfFile);
});
} else {
return next(new Error('Did NOT get your file!'));
}
});
}
}
}else {
return next(new Error('Uploaded files were NOT detected'));
}
};
*/
/** /**
* Delete a forms submissions * Delete a forms submissions
*/ */
@ -185,8 +118,6 @@ exports.createSubmission = function(req, res) {
percentageComplete: req.body.percentageComplete percentageComplete: req.body.percentageComplete
}); });
if(!!form.plugins.oscarhost.baseUrl) submission.hasPlugins.oscarhost = true;
if(form.pdf) submission.pdf = form.pdf; if(form.pdf) submission.pdf = form.pdf;
//Save submitter's IP Address //Save submitter's IP Address
@ -256,23 +187,27 @@ exports.listSubmissions = function(req, res) {
* Create a new form * Create a new form
*/ */
exports.create = function(req, res) { exports.create = function(req, res) {
if(!req.body.form){
console.log(err);
return res.status(400).send({
message: "Invalid Input"
});
}
var form = new Form(req.body.form); var form = new Form(req.body.form);
form.admin = req.user._id; form.admin = req.user._id;
console.log('Create a new form');
console.log(form);
console.log(req.body.form);
console.log(req.user);
form.save(function(err) { form.save(function(err) {
if (err) { if (err) {
console.log(err); console.log(err);
res.status(400).send({ return res.status(405).send({
message: errorHandler.getErrorMessage(err) message: errorHandler.getErrorMessage(err)
}); });
} else {
res.json(form);
} }
res.json(form);
}); });
}; };
@ -280,10 +215,7 @@ exports.create = function(req, res) {
* Show the current form * Show the current form
*/ */
exports.read = function(req, res) { exports.read = function(req, res) {
var validUpdateTypes= Form.schema.path('plugins.oscarhost.settings.updateType').enumValues;
var newForm = req.form.toJSON({virtuals : true}); var newForm = req.form.toJSON({virtuals : true});
newForm.plugins.oscarhost.settings.validUpdateTypes = validUpdateTypes;
if (req.userId) { if (req.userId) {
if(req.form.admin._id+'' === req.userId+''){ if(req.form.admin._id+'' === req.userId+''){
@ -294,7 +226,6 @@ exports.read = function(req, res) {
}); });
} }
return res.json(newForm); return res.json(newForm);
}; };
/** /**
@ -302,10 +233,16 @@ exports.read = function(req, res) {
*/ */
exports.update = function(req, res) { exports.update = function(req, res) {
var form = req.form; var form = req.form;
delete req.body.form.__v;
delete req.body.form._id;
//Unless we have 'admin' priviledges, updating form admin is disabled if(req.body.changes){
console.log('SENDING DIFFS\n\n\n');
var formChanges = req.body.changes;
formChanges.forEach(function (change) {
diff.applyChange(form, true, change);
});
} else {
//Unless we have 'admin' privileges, updating form admin is disabled
if(req.user.roles.indexOf('admin') === -1) delete req.body.form.admin; if(req.user.roles.indexOf('admin') === -1) delete req.body.form.admin;
//Do this so we can create duplicate fields //Do this so we can create duplicate fields
@ -316,13 +253,13 @@ exports.update = function(req, res) {
delete field._id; delete field._id;
} }
} }
form = _.extend(form, req.body.form); form = _.extend(form, req.body.form);
}
form.save(function(err, form) { form.save(function(err, form) {
if (err) { if (err) {
console.log(err); console.log(err);
res.status(400).send({ res.status(405).send({
message: errorHandler.getErrorMessage(err) message: errorHandler.getErrorMessage(err)
}); });
} else { } else {
@ -382,7 +319,7 @@ exports.formByID = function(req, res, next, id) {
if (err) { if (err) {
return next(err); return next(err);
} else if (form === undefined || form === null) { } else if (form === undefined || form === null) {
res.status(400).send({ res.status(404).send({
message: 'Form not found' message: 'Form not found'
}); });
} }

View file

@ -11,7 +11,9 @@ var _ = require('lodash'),
config = require('../../../config/config'), config = require('../../../config/config'),
nodemailer = require('nodemailer'), nodemailer = require('nodemailer'),
crypto = require('crypto'), crypto = require('crypto'),
User = mongoose.model('User'); User = mongoose.model('User'),
tokgen = require("../../libs/tokenGenerator");
var nev = require('email-verification')(mongoose); var nev = require('email-verification')(mongoose);
@ -99,6 +101,7 @@ exports.resendVerificationEmail = function(req, res, next){
* Signup * Signup
*/ */
exports.signup = function(req, res) { exports.signup = function(req, res) {
debugger;
// For security measures we remove the roles from the req.body object // For security measures we remove the roles from the req.body object
delete req.body.roles; delete req.body.roles;
@ -172,7 +175,6 @@ exports.signin = function(req, res, next) {
*/ */
exports.signout = function(req, res) { exports.signout = function(req, res) {
req.logout(); req.logout();
//res.redirect('/');
return res.status(200).send('You have successfully logged out.'); return res.status(200).send('You have successfully logged out.');
}; };
@ -304,3 +306,43 @@ exports.removeOAuthProvider = function(req, res, next) {
}); });
} }
}; };
/* Generate API Key for User */
exports.generateAPIKey = function(req, res) {
if (!req.isAuthenticated()){
return res.status(400).send({
message: 'User is not Authorized'
});
}
User.findById(req.user.id)
.exec( function(err, user) {
if (err) return res.status(400).send(err);
if (!user) {
return res.status(400).send({
message: 'User does not Exist'
});
}
user.apiKey = tokgen();
user.save(function(err, _user) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
}
var newUser = _user.toObject();
delete newUser.salt;
delete newUser.__v;
delete newUser.passwordHash;
delete newUser.provider;
console.log(newUser);
return res.json(newUser);
});
});
};

View file

@ -23,7 +23,9 @@ exports.userByID = function (req, res, next, id) {
if (err) { if (err) {
return next(err); return next(err);
} else if (!user) { } else if (!user) {
return next(new Error('Failed to load User ' + id)); return res.status(404).send({
message: 'User does not exist'
});
} }
req.profile = user; req.profile = user;

View file

@ -27,13 +27,13 @@ exports.update = function(req, res) {
user.save(function(err) { user.save(function(err) {
if (err) { if (err) {
return res.status(400).send({ return res.status(500).send({
message: errorHandler.getErrorMessage(err) message: errorHandler.getErrorMessage(err)
}); });
} else { } else {
req.login(user, function(err) { req.login(user, function(err) {
if (err) { if (err) {
res.status(400).send(err); res.status(500).send(err);
} else { } else {
res.json(user); res.json(user);
} }
@ -41,7 +41,7 @@ exports.update = function(req, res) {
} }
}); });
} else { } else {
res.status(400).send({ res.status(401).send({
message: 'User is not signed in' message: 'User is not signed in'
}); });
} }

View file

@ -0,0 +1,8 @@
"use strict";
let TokenGenerator = require("uuid-token-generator");
let tokgen = new TokenGenerator(256, TokenGenerator.BASE62);
module.exports = function() {
return tokgen.generate();
};

View file

@ -92,11 +92,6 @@ var FormSchema = new Schema({
default: 'en', default: 'en',
required: 'Form must have a language' required: 'Form must have a language'
}, },
description: {
type: String,
default: ''
},
analytics:{ analytics:{
gaCode: { gaCode: {
type: String type: String
@ -146,18 +141,10 @@ var FormSchema = new Schema({
type: Boolean, type: Boolean,
default: false default: false
}, },
isGenerated: {
type: Boolean,
default: false
},
isLive: { isLive: {
type: Boolean, type: Boolean,
default: false default: false
}, },
autofillPDFs: {
type: Boolean,
default: false
},
design: { design: {
colors:{ colors:{
@ -187,57 +174,7 @@ var FormSchema = new Schema({
default: '#333' default: '#333'
} }
}, },
font: String, font: String
backgroundImage: { type: Schema.Types.Mixed }
},
plugins: {
oscarhost: {
baseUrl: {
type: String
},
settings: {
lookupField: {
type: Schema.Types.ObjectId,
ref: 'Field'
},
updateType: {
type: String,
enum: ['upsert', 'force_add', 'force_update', 'fetch'],
},
fieldMap: {
type: Schema.Types.Mixed,
},
validUpdateTypes: {
type: [String]
},
validFields : {
type: [String],
default: [
'address',
'city',
'email',
'firstName',
'hin',
'lastName',
'phone',
'postal',
'province',
'sex',
'spokenLanguage',
'title',
'DOB']
}
},
auth: {
user: {
type: String
},
pass: {
type: String
}
}
}
} }
}); });
@ -367,11 +304,6 @@ FormSchema.pre('save', function (next) {
} }
}); });
}, function(cb) { }, function(cb) {
//DAVID: TODO: Make this so we don't have to update the validFields property ever save
if (that.plugins.oscarhost.hasOwnProperty('baseUrl')) {
var validUpdateTypes = mongoose.model('Form').schema.path('plugins.oscarhost.settings.updateType').enumValues;
that.plugins.oscarhost.settings.validUpdateTypes = validUpdateTypes;
}
return cb(null); return cb(null);
}, },
function(cb) { function(cb) {
@ -489,12 +421,18 @@ FormSchema.pre('save', function (next) {
else return cb(); else return cb();
}, },
function(cb) { function(cb) {
var hasIds = true;
if(that.isModified('form_fields') && that.form_fields && _original){ for(var i=0; i<that.form_fields.length; i++){
if(!that.form_fields.hasOwnProperty('_id')){
hasIds = false;
break;
}
}
if(that.isModified('form_fields') && that.form_fields && _original && hasIds){
var old_form_fields = _original.form_fields, var old_form_fields = _original.form_fields,
new_ids = _.map(_.pluck(that.form_fields, '_id'), function(id){ return ''+id;}), new_ids = _.map(_.pluck(that.form_fields, 'id'), function(id){ return ''+id;}),
old_ids = _.map(_.pluck(old_form_fields, '_id'), function(id){ return ''+id;}), old_ids = _.map(_.pluck(old_form_fields, 'id'), function(id){ return ''+id;}),
deletedIds = getDeletedIndexes(old_ids, new_ids); deletedIds = getDeletedIndexes(old_ids, new_ids);
//Preserve fields that have at least one submission //Preserve fields that have at least one submission

View file

@ -7,7 +7,8 @@ var mongoose = require('mongoose'),
util = require('util'), util = require('util'),
mUtilities = require('mongoose-utilities'), mUtilities = require('mongoose-utilities'),
_ = require('lodash'), _ = require('lodash'),
Schema = mongoose.Schema; Schema = mongoose.Schema,
LogicJumpSchema = require('./logic_jump.server.model');
var FieldOptionSchema = new Schema({ var FieldOptionSchema = new Schema({
option_id: { option_id: {
@ -77,10 +78,7 @@ function BaseFieldSchema(){
default: '' default: ''
}, },
logicJump: { logicJump: LogicJumpSchema,
type: Schema.Types.ObjectId,
ref: 'LogicJump'
},
ratingOptions: { ratingOptions: {
type: RatingFieldSchema, type: RatingFieldSchema,
@ -162,6 +160,7 @@ FormFieldSchema.pre('validate', function(next) {
if(this.ratingOptions && this.ratingOptions.steps && this.ratingOptions.shape){ 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}); error.errors.ratingOptions = new mongoose.Error.ValidatorError({path: 'ratingOptions', message: 'ratingOptions is only allowed for type \'rating\' fields.', type: 'notvalid', value: this.ratingOptions});
console.error(error);
return(next(error)); return(next(error));
} }
@ -183,8 +182,9 @@ FormFieldSchema.pre('validate', function(next) {
//If field is multiple choice check that it has field //If field is multiple choice check that it has field
if(this.fieldType !== 'dropdown' && this.fieldType !== 'radio' && this.fieldType !== 'checkbox'){ if(this.fieldType !== 'dropdown' && this.fieldType !== 'radio' && this.fieldType !== 'checkbox'){
if(!this.fieldOptions || this.fieldOptions.length !== 0){ 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}); error.errors.ratingOptions = new mongoose.Error.ValidatorError({path:'fieldOptions', message: 'fieldOptions are only allowed for type dropdown, checkbox or radio fields.', type: 'notvalid', value: this.ratingOptions});
console.error(error);
return(next(error)); return(next(error));
} }
} }
@ -192,13 +192,18 @@ FormFieldSchema.pre('validate', function(next) {
return next(); return next();
}); });
//LogicJump Save
FormFieldSchema.pre('save', function(next) {
if(this.logicJump && this.logicJump.fieldA){
if(this.logicJump.jumpTo = '') delete this.logicJump.jumpTo;
}
next();
});
//Submission fieldValue correction //Submission fieldValue correction
FormFieldSchema.pre('save', function(next) { FormFieldSchema.pre('save', function(next) {
if(this.fieldType === 'dropdown' && this.isSubmission){ if(this.fieldType === 'dropdown' && this.isSubmission){
//console.log(this);
this.fieldValue = this.fieldValue.option_value; this.fieldValue = this.fieldValue.option_value;
//console.log(this.fieldValue);
} }
return next(); return next();

View file

@ -159,92 +159,11 @@ FormSubmissionSchema.pre('save', function (next) {
// console.log(_form); // console.log(_form);
// console.log('should push to api'); // console.log('should push to api');
// console.log( (!this.oscarDemoNum && !!_form.plugins.oscarhost.baseUrl && !!_form.plugins.oscarhost.settings.fieldMap) ); // console.log( (!this.oscarDemoNum && !!_form.plugins.oscarhost.baseUrl && !!_form.plugins.oscarhost.settings.fieldMap) );
if (!this.oscarDemoNum && _form.plugins.oscarhost.baseUrl && _form.plugins.oscarhost.settings.fieldMap) {
console.log('OSCARHOST API HOOK');
var url_login = _form.plugins.oscarhost.baseUrl + '/LoginService?wsdl',
url_demo = _form.plugins.oscarhost.baseUrl + '/DemographicService?wsdl';
var args_login = {arg0: config.oscarhost.auth.user, arg1: config.oscarhost.auth.pass};
var options = {
ignoredNamespaces: {
namespaces: ['targetNamespace', 'typedNamespace'],
override: true
}
};
// console.log(self.form_fields);
//Generate demographics from hashmap
var generateDemo = function (formFields, conversionMap, demographicsTemplate) {
console.log('generating Demo fields');
console.log(conversionMap);
var _generatedDemo = {}, currField, propertyName;
for (var y = 0; y < formFields.length; y++) {
currField = formFields[y];
propertyName = conversionMap[currField._id];
if (demographicsTemplate.hasOwnProperty(conversionMap[currField._id])) {
_generatedDemo[propertyName] = currField.fieldValue + '';
} else if (propertyName === 'DOB') {
var date = new Date(currField.fieldValue);
_generatedDemo.dateOfBirth = date.getDate() + '';
_generatedDemo.yearOfBirth = date.getFullYear() + '';
_generatedDemo.monthOfBirth = date.getMonth() + '';
}
}
var currDate = new Date();
var dateString = currDate.toISOString().split('T')[0] + ' ' + currDate.toISOString().split('T')[1].slice(0, 8);
_generatedDemo.lastUpdateDate = currDate.toISOString();
return _generatedDemo;
};
var submissionDemographic = generateDemo(self.form_fields, _form.plugins.oscarhost.settings.fieldMap, newDemoTemplate);
console.log(submissionDemographic);
async.waterfall([
function (callback) {
//Authenticate with API
soap.createClient(url_login, options, function (err, client) {
client.login(args_login, function (err, result) {
if (err) return callback(err);
console.log('SOAP authenticated');
return callback(null, result.return);
});
});
},
function (security_obj, callback) {
//Force Add Demographic
if (_form.plugins.oscarhost.settings.updateType === 'force_add') {
soap.createClient(url_demo, options, function (err, client) {
if (err) return callback(err);
client.setSecurity(new OscarSecurity(security_obj.securityId, security_obj.securityTokenKey));
client.addDemographic({arg0: submissionDemographic}, function (err, result) {
console.log('FORCE ADDING DEMOGRAPHIC \n');
// console.log(result.return);
if (err) return callback(err);
return callback(null, result);
});
});
}
}
], function (err, result) {
if (err) return next(err);
self.oscarDemoNum = parseInt(result.return, 10);
console.log('self.oscarDemoNum: ' + self.oscarDemoNum);
return next(); return next();
}); });
} else { } else {
return next(); return next();
} }
});
} else {
return next();
}
}); });

View file

@ -6,76 +6,76 @@
var mongoose = require('mongoose'), var mongoose = require('mongoose'),
Schema = mongoose.Schema, Schema = mongoose.Schema,
_ = require('lodash'), _ = require('lodash'),
math = require('math'); math = require('mathjs');
var schemaOptions = {
var BooleanExpressionSchema = new Schema({ toObject: {
expressionString: { virtuals: true
type: String,
}, },
result: { toJSON: {
type: Boolean, virtuals: true
}
});
BooleanExpressionSchema.methods.evaluate = function(){
if(this.expressionString){
//Get headNode
var headNode = math.parse(this.expressionString);
var expressionScope = {};
var that = this;
//Create scope
headNode.traverse(function (node, path, parent) {
if(node.type === 'SymbolNode'){
mongoose.model('Field')
.findOne({_id: node.name}).exec(function(err, field){
if(err) {
console.log(err);
throw new Error(err);
}
if(!!_.parseInt(field.fieldValue)){
that.expressionScope[node.name] = _.parseInt(field.fieldValue);
}else {
that.expressionScope[node.name] = field.fieldValue;
}
console.log('_id: '+node.name);
console.log('value: '+that.expressionScope[node.name]);
});
}
});
var code = headNode.compile();
var result = code.eval(expressionScope);
this.result = result;
return result;
}else{
return null;
} }
}; };
mongoose.model('BooleanExpression', BooleanExpressionSchema);
/**
* Form Schema
*/
var LogicJumpSchema = new Schema({ var LogicJumpSchema = new Schema({
created: { expressionString: {
type: Date, type: String,
default: Date.now enum: [
'field == static',
'field != static',
'field > static',
'field >= static',
'field <= static',
'field < static',
'field contains static',
'field !contains static',
'field begins static',
'field !begins static',
'field ends static',
'field !ends static',
]
}, },
lastModified: { fieldA: {
type: Date,
},
BooleanExpression: {
type: Schema.Types.ObjectId, type: Schema.Types.ObjectId,
ref: 'BooleanExpression' ref: 'FormField'
}, },
valueB: {
type: Schema.Types.String
},
jumpTo: {
type: Schema.Types.ObjectId,
ref: 'FormField'
}
}, schemaOptions);
}); /*
IS EQUAL TO statement
var scope = {
a: val1,
b: val2
};
math.eval('a == b', scope);
IS NOT EQUAL TO statement
var scope = {
a: val1,
b: val2
};
math.eval('a !== b', scope);
BEGINS WITH statement
ENDS WITH statement
CONTAINS statement
DOES NOT CONTAIN statement
*/
mongoose.model('LogicJump', LogicJumpSchema); mongoose.model('LogicJump', LogicJumpSchema);
module.exports = LogicJumpSchema;

View file

@ -75,6 +75,7 @@ var UserSchema = new Schema({
type: String, type: String,
unique: true, unique: true,
required: false, required: false,
lowercase: true,
trim: true trim: true
}, },
passwordHash: { passwordHash: {
@ -119,7 +120,13 @@ var UserSchema = new Schema({
resetPasswordExpires: { resetPasswordExpires: {
type: Date type: Date
}, },
token: String token: String,
apiKey: {
type: String,
unique: true,
index: true,
sparse: true
},
}); });
UserSchema.virtual('displayName').get(function () { UserSchema.virtual('displayName').get(function () {

View file

@ -7,10 +7,11 @@ var forms = require('../../app/controllers/forms.server.controller'),
core = require('../../app/controllers/core.server.controller'); core = require('../../app/controllers/core.server.controller');
module.exports = function(app) { module.exports = function(app) {
// Root routing // Core routing
app.route('/').get(core.index); app.route('/').get(core.index);
app.route('/subdomain/([a-zA-Z0-9]+)/').get(core.form);
app.route('/subdomain/*/forms/:formId([a-zA-Z0-9]+)') app.route('/subdomain/api/').get(core.redoc);
.get(forms.read) app.route('/subdomain/:userSlug((?!api$)[A-Za-z0-9]+)/').get(core.form);
app.route('/subdomain/:userSlug((?!api$)[A-Za-z0-9]+)/forms/:formId([a-zA-Z0-9]+)').get(forms.read)
.post(forms.createSubmission); .post(forms.createSubmission);
}; };

View file

@ -6,7 +6,8 @@
var users = require('../../app/controllers/users.server.controller'), var users = require('../../app/controllers/users.server.controller'),
forms = require('../../app/controllers/forms.server.controller'), forms = require('../../app/controllers/forms.server.controller'),
multer = require('multer'), multer = require('multer'),
config = require('../../config/config'); config = require('../../config/config'),
auth = require('../../config/passport_helpers');
// Setting the pdf upload route and folder // Setting the pdf upload route and folder
var storage = multer.diskStorage({ var storage = multer.diskStorage({
@ -27,21 +28,21 @@ var upload = multer({
module.exports = function(app) { module.exports = function(app) {
// Form Routes // Form Routes
app.route('/upload/pdf') app.route('/upload/pdf')
.post(users.requiresLogin, upload.single('file'), forms.uploadPDF); .post(auth.isAuthenticatedOrApiKey, upload.single('file'), forms.uploadPDF);
app.route('/forms') app.route('/forms')
.get(users.requiresLogin, forms.list) .get(auth.isAuthenticatedOrApiKey, forms.list)
.post(users.requiresLogin, forms.create); .post(auth.isAuthenticatedOrApiKey, forms.create);
app.route('/forms/:formId([a-zA-Z0-9]+)') app.route('/forms/:formId([a-zA-Z0-9]+)')
.get(forms.read) .get(forms.read)
.post(forms.createSubmission) .post(forms.createSubmission)
.put(users.requiresLogin, forms.hasAuthorization, forms.update) .put(auth.isAuthenticatedOrApiKey, forms.hasAuthorization, forms.update)
.delete(users.requiresLogin, forms.hasAuthorization, forms.delete); .delete(auth.isAuthenticatedOrApiKey, forms.hasAuthorization, forms.delete);
app.route('/forms/:formId([a-zA-Z0-9]+)/submissions') app.route('/forms/:formId([a-zA-Z0-9]+)/submissions')
.get(users.requiresLogin, forms.hasAuthorization, forms.listSubmissions) .get(auth.isAuthenticatedOrApiKey, forms.hasAuthorization, forms.listSubmissions)
.delete(users.requiresLogin, forms.hasAuthorization, forms.deleteSubmissions); .delete(auth.isAuthenticatedOrApiKey, forms.hasAuthorization, forms.deleteSubmissions);
// Finish by binding the form middleware // Finish by binding the form middleware
app.param('formId', forms.formByID); app.param('formId', forms.formByID);

View file

@ -3,16 +3,17 @@
/** /**
* Module dependencies. * Module dependencies.
*/ */
var passport = require('passport'); var passport = require('passport'),
var config = require('../../config/config'); config = require('../../config/config'),
auth = require('../../config/passport_helpers');
module.exports = function(app) { module.exports = function(app) {
// User Routes // User Routes
var users = require('../../app/controllers/users.server.controller'); var users = require('../../app/controllers/users.server.controller');
// Setting up the users profile api // Setting up the users profile api
app.route('/users/me').get(users.requiresLogin, users.getUser); app.route('/users/me').get(auth.isAuthenticatedOrApiKey, users.getUser);
app.route('/users').put(users.requiresLogin, users.update); app.route('/users').put(auth.isAuthenticatedOrApiKey, users.update);
app.route('/users/accounts').delete(users.requiresLogin, users.removeOAuthProvider); app.route('/users/accounts').delete(users.requiresLogin, users.removeOAuthProvider);
// Setting up the users account verification api // Setting up the users account verification api
@ -32,6 +33,8 @@ module.exports = function(app) {
app.route('/auth/signin').post(users.signin); app.route('/auth/signin').post(users.signin);
app.route('/auth/signout').get(users.signout); app.route('/auth/signout').get(users.signout);
app.route('/auth/genkey').get(users.requiresLogin, users.generateAPIKey);
// // Setting the facebook oauth routes // // Setting the facebook oauth routes
// app.route('/auth/facebook').get(passport.authenticate('facebook', { // app.route('/auth/facebook').get(passport.authenticate('facebook', {
// scope: ['email'] // scope: ['email']

View file

@ -55,12 +55,8 @@ module.exports = function (io, socket) {
// a user has visited our page - add them to the visitorsData object // a user has visited our page - add them to the visitorsData object
socket.on('form-visitor-data', function(data) { socket.on('form-visitor-data', function(data) {
console.log('\n\nuser has visited our page');
visitorsData[socket.id] = data; visitorsData[socket.id] = data;
console.log(data);
if (data.isSubmitted) { if (data.isSubmitted) {
saveVisitorData(data, function () { saveVisitorData(data, function () {
console.log('\n\n user submitted form'); console.log('\n\n user submitted form');

View file

@ -150,13 +150,6 @@ describe('FormSubmission Model Unit Tests:', function() {
beforeEach(function(done){ beforeEach(function(done){
var myFieldMap = {}; var myFieldMap = {};
myFieldMap[myForm.form_fields[0]._id+''] = 'firstName';
myFieldMap[myForm.form_fields[1]._id+''] = 'lastName';
myFieldMap[myForm.form_fields[2]._id+''] = 'sex';
myFieldMap[myForm.form_fields[3]._id+''] = 'DOB';
myFieldMap[myForm.form_fields[4]._id+''] = 'phone';
myForm.plugins.oscarhost.settings.fieldMap = myFieldMap;
myForm.save(function(err, form){ myForm.save(function(err, form){
if(err) done(err); if(err) done(err);
@ -175,49 +168,6 @@ describe('FormSubmission Model Unit Tests:', function() {
}); });
}); });
// it('should add Patient to OscarHost EMR after save', function(done){
// var url_login = myForm.plugins.oscarhost.baseUrl+'/LoginService?wsdl',
// url_demo = myForm.plugins.oscarhost.baseUrl+'/DemographicService?wsdl',
// args_login = {arg0: config.oscarhost.auth.user, arg1: config.oscarhost.auth.pass};
// var options = {
// ignoredNamespaces: {
// namespaces: ['targetNamespace', 'typedNamespace'],
// override: true
// }
// };
// async.waterfall([
// function (callback) {
// //Authenticate with API
// soap.createClient(url_login, options, function(err, client) {
// client.login(args_login, function (err, result) {
// if(err) callback(err);
// callback(null, result.return);
// });
// });
// },
// function (security_obj, callback) {
// soap.createClient(url_demo, options, function(err, client) {
// client.setSecurity(new OscarSecurity(security_obj.securityId, security_obj.securityTokenKey) );
// client.getDemographic({ arg0: oscar_demo_num }, function (err, result) {
// if(err) callback(err);
// callback(null, result);
// });
// });
// },
// ], function(err, result) {
// if(err) done(err);
// should.exist(result);
// console.log(result.return);
// done();
// });
// });
}); });
describe('Method Find', function(){ describe('Method Find', function(){

View file

@ -2,7 +2,7 @@
<html lang="en" xmlns="http://www.w3.org/1999/xhtml"> <html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head> <head>
<title>{{title}}</title> <title>{{title}} Form</title>
<!-- General META --> <!-- General META -->
<meta charset="utf-8"> <meta charset="utf-8">
@ -61,11 +61,11 @@
</head> </head>
<body ng-cloak> <body ng-cloak>
<div class="github-fork-ribbon-wrapper right-bottom hidden-xs"> <!--<div class="github-fork-ribbon-wrapper right-bottom hidden-xs">
<div class="github-fork-ribbon"> <div class="github-fork-ribbon">
<a href="https://github.com/whitef0x0/tellform">Fork me on GitHub</a> <a href="https://github.com/whitef0x0/tellform">Fork me on GitHub</a>
</div> </div>
</div> </div>-->
<section class="content"> <section class="content">
<section ui-view></section> <section ui-view></section>
</section> </section>

View file

@ -0,0 +1,94 @@
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>{{title}}</title>
<!-- General META -->
<meta charset="utf-8">
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<!-- Semantic META -->
<meta name="keywords" content="{{keywords}}">
<meta name="description" content="{{description}}">
<!-- Facebook META -->
<meta property="og:site_name" content="{{title}}">
<meta property="og:title" content="{{title}}">
<meta property="og:description" content="{{description}}">
<meta property="og:url" content="{{url}}">
<meta property="og:image" content="/img/brand/logo.png">
<meta property="og:type" content="website">
<!-- Twitter META -->
<meta name="twitter:title" content="{{title}}">
<meta name="twitter:description" content="{{description}}">
<meta name="twitter:url" content="{{url}}">
<meta name="twitter:image" content="/img/brand/logo.png">
<!-- Fav Icon -->
<link href="/static/modules/core/img/brand/favicon.ico" rel="shortcut icon" type="image/x-icon">
<link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,900'>
<!--Bower CSS dependencies-->
<!-- end Bower CSS dependencies-->
<!--Application CSS Files-->
{% for cssFile in cssFiles %}
<link rel="stylesheet" href="{{cssFile}}">
{% endfor %}
<!-- end Application CSS Files-->
<!-- HTML5 Shim -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
<body>
<redoc spec-url='/static/swagger.json'></redoc>
<script src="https://rebilly.github.io/ReDoc/releases/latest/redoc.min.js"> </script>
<!--Bower JS dependencies-->
{% for bowerJSFile in bowerJSFiles %}
<script type="text/javascript" src="{{bowerJSFile}}"></script>
{% endfor %}
<!-- end Bower JS dependencies-->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/angular-strap/2.3.8/angular-strap.min.js"></script>
{% if process.env.NODE_ENV === 'development' %}
<script src="https://cdn.ravenjs.com/2.3.0/angular/raven.min.js"></script>
<script>
Raven.config('https://825fefd6b4ed4a4da199c1b832ca845c@sentry.tellform.com/2').install();
</script>
<!-- [if lt IE 9]>
<section class="browsehappy jumbotron hide">
<h1>Hello there!</h1>
<p>You are using an old browser which we unfortunately do not support.</p>
<p>Please <a href="http://browsehappy.com/">click here</a> to update your browser before using the website.</p>
<p><a href="http://browsehappy.com" class="btn btn-primary btn-lg" role="button">Yes, upgrade my browser!</a></p>
</section>
<![endif] -->
<script>
(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', '{{google_analytics_id}}', 'auto');
ga('send', 'pageview');
</script>
</body>
</html>

View file

@ -36,8 +36,12 @@
"js-yaml": "^3.6.1", "js-yaml": "^3.6.1",
"angular-ui-select": "https://github.com/whitef0x0/ui-select.git#compiled", "angular-ui-select": "https://github.com/whitef0x0/ui-select.git#compiled",
"angular-translate": "~2.11.0", "angular-translate": "~2.11.0",
"ng-device-detector": "~3.0.1", "ng-device-detector": "^3.0.1",
"ng-translate": "*" "ng-translate": "*",
"deep-diff": "^0.3.4",
"mathjs": "^3.4.1",
"jsep": "^0.3.1",
"ngclipboard": "^1.1.1"
}, },
"resolutions": { "resolutions": {
"angular-bootstrap": "^0.14.0", "angular-bootstrap": "^0.14.0",

View file

@ -31,7 +31,7 @@ if( fs.existsSync('./config/env/api_keys.js') ){
exports, exports,
require('./env/api_keys') require('./env/api_keys')
); );
}else { } else {
module.exports = exports; module.exports = exports;
} }

2
config/env/all.js vendored
View file

@ -14,7 +14,7 @@ module.exports = {
reCAPTCHA_Key: process.env.reCAPTCHA_KEY || '', reCAPTCHA_Key: process.env.reCAPTCHA_KEY || '',
signupDisabled: !!process.env.SIGNUP_DISABLED, signupDisabled: (process.env.SIGNUP_DISABLED === "TRUE"),
baseUrl: '', baseUrl: '',
tempUserCollection: 'temporary_users', tempUserCollection: 'temporary_users',

View file

@ -72,7 +72,6 @@ module.exports = function(db) {
var subdomains = req.subdomains; var subdomains = req.subdomains;
var host = req.hostname; var host = req.hostname;
if(subdomains.slice(0, 4).join('.')+'' === '1.0.0.127'){ if(subdomains.slice(0, 4).join('.')+'' === '1.0.0.127'){
subdomains = subdomains.slice(4); subdomains = subdomains.slice(4);
} }
@ -81,18 +80,38 @@ module.exports = function(db) {
if (!subdomains.length) return next(); if (!subdomains.length) return next();
var urlPath = url.parse(req.url).path.split('/'); var urlPath = url.parse(req.url).path.split('/');
if(urlPath.indexOf('static')){ if(urlPath.indexOf('static') > -1){
//console.log("STATIC FILE\n\n\n\n");
urlPath.splice(1,1); urlPath.splice(1,1);
req.root = 'https://' + config.baseUrl + urlPath.join('/'); req.root = 'https://' + config.baseUrl + urlPath.join('/');
console.log(req.root);
return next(); return next();
} }
if(subdomains.indexOf('stage') || subdomains.indexOf('admin')){ if(urlPath.indexOf('users') > -1 && urlPath.indexOf('me') > -1){
return next(); return next();
} }
if(subdomains.indexOf('stage') > -1 || subdomains.indexOf('admin') > -1){
return next();
}
console.log(req.subdomains.reverse()[0]);
//console.log("is api subdomain: "+ (subdomains.indexOf("api") > -1));
//console.log(req.url);
if(subdomains.indexOf('api') > -1){
// rebuild url
path += 'api' + req.url;
console.log(req.url);
// TODO: check path and query strings are preserved
// reassign url
req.url = path;
console.log(req.url);
return next();
}
User.findOne({username: req.subdomains.reverse()[0]}).exec(function (err, user) { User.findOne({username: req.subdomains.reverse()[0]}).exec(function (err, user) {
if (err) { if (err) {
console.log(err); console.log(err);
req.subdomains = null; req.subdomains = null;
@ -113,10 +132,13 @@ module.exports = function(db) {
// TODO: check path and query strings are preserved // TODO: check path and query strings are preserved
// reassign url // reassign url
console.log("path: "+path);
req.url = path; req.url = path;
req.userId = user._id; req.userId = user._id;
console.log('\n\n\ngot subdomain: '+ req.subdomains.reverse()[0]);
// Q.E.D. // Q.E.D.
next(); next();
}); });

View file

@ -0,0 +1,54 @@
"use strict";
var config = require("./config");
var passport = require("passport");
var User = require('mongoose').model('User');
module.exports.isAuthenticatedOrApiKey = function isAuthenticated(req, res, next) {
debugger;
if (req.isAuthenticated()) {
return next();
} else {
// Try authenticate with API KEY
if (req.headers.apikey || req.query.apikey || req.body.apikey) {
passport.authenticate("localapikey", function (err, user, info) {
if (err)
return res.sendStatus(500);
if (!user)
return res.status(401).send(info.message || "");
req.login(user, function(err) {
if (err) return res.sendStatus(500);
req.user = user;
return next();
});
})(req, res, next);
} else {
return res.sendStatus(401);
}
}
};
module.exports.hasRole = function hasRole(roleRequired) {
if (!roleRequired)
throw new Error("Required role needs to be set");
return function(req, res, next) {
return module.exports.isAuthenticated(req, res, function() {
if (req.user && req.user.roles && req.user.roles.indexOf(roleRequired) !== -1)
next();
else
res.sendStatus(403);
});
};
};
module.exports.hasAdminRole = function hasAdminRole() {
return module.exports.hasRole("admin");
};

View file

@ -0,0 +1,25 @@
"use strict";
var passport = require("passport");
var LocalAPIKeyStrategy = require("passport-localapikey-update").Strategy;
var User = require('mongoose').model('User');
module.exports = function() {
passport.use(new LocalAPIKeyStrategy({
passReqToCallback : true
}, function(req, apiKey, done) {
return User.findOne({
"apiKey": apiKey
}, function(err, user) {
if (err)
return done(err);
if (!user)
return done(null, false, {
message: "Unknown API Key"
});
return done(null, user);
});
}));
};

40
dns_masq_setup_osx.md Normal file
View file

@ -0,0 +1,40 @@
# wildcard DNS in localhost development
- install [dnsmasq](http://www.thekelleys.org.uk/dnsmasq/doc.html)
```
$ brew install dnsmasq
...
$ cp /usr/local/opt/dnsmasq/dnsmasq.conf.example /usr/local/etc/dnsmasq.conf
```
- edit `/usr/local/etc/dnsmasq.conf`
```
address=/local/127.0.0.1
```
- start **dnsmasq**
```
$ sudo brew services start dnsmasq
```
- any time we change `dnsmasq.conf` we have to re-start **dnsmasq**:
```
$ sudo launchctl stop homebrew.mxcl.dnsmasq
$ sudo launchctl start homebrew.mxcl.dnsmasq
```
- For OS X to _resolve_ requests from `*.local` to **localhost** we need to add a _resolver_:
```
$ sudo mkdir /etc/resolver
$ sudo touch /etc/resolver/local
```
- edit `/etc/resolver/local`
```
nameserver 127.0.0.1
```
- re-start the computer to enable the _resolver_
===
**REFERENCES**
- [Using Dnsmasq for local development on OS X - Passing Curiosity](https://passingcuriosity.com/2013/dnsmasq-dev-osx/)
- [Using Dnsmasq Configure Wildcard DNS Record on Mac | Ri Xu Online](https://xuri.me/2014/12/13/using-dnsmasq-configure-wildcard-dns-record-on-mac.html)
- [unix - In my /etc/hosts/ file on Linux/OSX, how do I do a wildcard subdomain? - Server Fault](http://serverfault.com/questions/118378/in-my-etc-hosts-file-on-linux-osx-how-do-i-do-a-wildcard-subdomain)
- [hostname - Wildcard in /etc/hosts file - Unix & Linux Stack Exchange](http://unix.stackexchange.com/questions/3352/wildcard-in-etc-hosts-file)
- [Mac OS Lion - Wildcard subdomain virtual host - Stack Overflow](http://stackoverflow.com/questions/9562059/mac-os-lion-wildcard-subdomain-virtual-host)
- [How to put wildcard entry into /etc/hosts? - Stack Overflow](http://stackoverflow.com/questions/20446930/how-to-put-wildcard-entry-into-etc-hosts)

View file

@ -1,140 +0,0 @@
<?xml version='1.0' encoding='UTF-8'?><wsdl:definitions name="DemographicWsService" targetNamespace="http://ws.oscarehr.org/" xmlns:ns1="http://schemas.xmlsoap.org/soap/http" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://ws.oscarehr.org/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<wsdl:types>
<xs:schema elementFormDefault="unqualified" targetNamespace="http://ws.oscarehr.org/" version="1.0" xmlns:tns="http://ws.oscarehr.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="getDemographic" type="tns:getDemographic"/>
<xs:element name="getDemographicByMyOscarUserName" type="tns:getDemographicByMyOscarUserName"/>
<xs:element name="getDemographicByMyOscarUserNameResponse" type="tns:getDemographicByMyOscarUserNameResponse"/>
<xs:element name="getDemographicResponse" type="tns:getDemographicResponse"/>
<xs:complexType name="getDemographic">
<xs:sequence>
<xs:element minOccurs="0" name="arg0" type="xs:int"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="getDemographicResponse">
<xs:sequence>
<xs:element minOccurs="0" name="return" type="tns:demographicTransfer"/>
</xs:sequence>
</xs:complexType>
<xs:complexType final="extension restriction" name="demographicTransfer">
<xs:sequence>
<xs:element name="activeCount" type="xs:int"/>
<xs:element minOccurs="0" name="address" type="xs:string"/>
<xs:element minOccurs="0" name="alias" type="xs:string"/>
<xs:element minOccurs="0" name="anonymous" type="xs:string"/>
<xs:element minOccurs="0" name="chartNo" type="xs:string"/>
<xs:element minOccurs="0" name="children" type="xs:string"/>
<xs:element minOccurs="0" name="citizenship" type="xs:string"/>
<xs:element minOccurs="0" name="city" type="xs:string"/>
<xs:element minOccurs="0" name="dateJoined" type="xs:dateTime"/>
<xs:element minOccurs="0" name="dateOfBirth" type="xs:string"/>
<xs:element minOccurs="0" name="demographicNo" type="xs:int"/>
<xs:element minOccurs="0" name="displayName" type="xs:string"/>
<xs:element minOccurs="0" name="effDate" type="xs:dateTime"/>
<xs:element minOccurs="0" name="email" type="xs:string"/>
<xs:element minOccurs="0" name="endDate" type="xs:dateTime"/>
<xs:element minOccurs="0" name="familyDoctor" type="xs:string"/>
<xs:element minOccurs="0" name="firstName" type="xs:string"/>
<xs:element minOccurs="0" name="hcRenewDate" type="xs:dateTime"/>
<xs:element minOccurs="0" name="hcType" type="xs:string"/>
<xs:element minOccurs="0" name="headRecord" type="xs:int"/>
<xs:element minOccurs="0" name="hin" type="xs:string"/>
<xs:element name="hsAlertCount" type="xs:int"/>
<xs:element minOccurs="0" name="lastName" type="xs:string"/>
<xs:element minOccurs="0" name="lastUpdateDate" type="xs:dateTime"/>
<xs:element minOccurs="0" name="lastUpdateUser" type="xs:string"/>
<xs:element minOccurs="0" name="links" type="xs:string"/>
<xs:element minOccurs="0" name="monthOfBirth" type="xs:string"/>
<xs:element minOccurs="0" name="myOscarUserName" type="xs:string"/>
<xs:element minOccurs="0" name="officialLanguage" type="xs:string"/>
<xs:element minOccurs="0" name="patientStatus" type="xs:string"/>
<xs:element minOccurs="0" name="patientStatusDate" type="xs:dateTime"/>
<xs:element minOccurs="0" name="pcnIndicator" type="xs:string"/>
<xs:element minOccurs="0" name="phone" type="xs:string"/>
<xs:element minOccurs="0" name="phone2" type="xs:string"/>
<xs:element minOccurs="0" name="postal" type="xs:string"/>
<xs:element minOccurs="0" name="previousAddress" type="xs:string"/>
<xs:element minOccurs="0" name="providerNo" type="xs:string"/>
<xs:element minOccurs="0" name="province" type="xs:string"/>
<xs:element minOccurs="0" name="rosterDate" type="xs:dateTime"/>
<xs:element minOccurs="0" name="rosterStatus" type="xs:string"/>
<xs:element minOccurs="0" name="rosterTerminationDate" type="xs:dateTime"/>
<xs:element minOccurs="0" name="rosterTerminationReason" type="xs:string"/>
<xs:element minOccurs="0" name="sex" type="xs:string"/>
<xs:element minOccurs="0" name="sexDesc" type="xs:string"/>
<xs:element minOccurs="0" name="sin" type="xs:string"/>
<xs:element minOccurs="0" name="sourceOfIncome" type="xs:string"/>
<xs:element minOccurs="0" name="spokenLanguage" type="xs:string"/>
<xs:element minOccurs="0" name="title" type="xs:string"/>
<xs:element minOccurs="0" name="ver" type="xs:string"/>
<xs:element minOccurs="0" name="yearOfBirth" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="getDemographicByMyOscarUserName">
<xs:sequence>
<xs:element minOccurs="0" name="arg0" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="getDemographicByMyOscarUserNameResponse">
<xs:sequence>
<xs:element minOccurs="0" name="return" type="tns:demographicTransfer"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
</wsdl:types>
<wsdl:message name="getDemographicByMyOscarUserNameResponse">
<wsdl:part element="tns:getDemographicByMyOscarUserNameResponse" name="parameters">
</wsdl:part>
</wsdl:message>
<wsdl:message name="getDemographicByMyOscarUserName">
<wsdl:part element="tns:getDemographicByMyOscarUserName" name="parameters">
</wsdl:part>
</wsdl:message>
<wsdl:message name="getDemographicResponse">
<wsdl:part element="tns:getDemographicResponse" name="parameters">
</wsdl:part>
</wsdl:message>
<wsdl:message name="getDemographic">
<wsdl:part element="tns:getDemographic" name="parameters">
</wsdl:part>
</wsdl:message>
<wsdl:portType name="DemographicWs">
<wsdl:operation name="getDemographic">
<wsdl:input message="tns:getDemographic" name="getDemographic">
</wsdl:input>
<wsdl:output message="tns:getDemographicResponse" name="getDemographicResponse">
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="getDemographicByMyOscarUserName">
<wsdl:input message="tns:getDemographicByMyOscarUserName" name="getDemographicByMyOscarUserName">
</wsdl:input>
<wsdl:output message="tns:getDemographicByMyOscarUserNameResponse" name="getDemographicByMyOscarUserNameResponse">
</wsdl:output>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="DemographicWsServiceSoapBinding" type="tns:DemographicWs">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="getDemographic">
<soap:operation soapAction="" style="document"/>
<wsdl:input name="getDemographic">
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output name="getDemographicResponse">
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="getDemographicByMyOscarUserName">
<soap:operation soapAction="" style="document"/>
<wsdl:input name="getDemographicByMyOscarUserName">
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output name="getDemographicByMyOscarUserNameResponse">
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="DemographicWsService">
<wsdl:port binding="tns:DemographicWsServiceSoapBinding" name="DemographicWsPort">
<soap:address location="https://secure11.oscarhost.ca/kensington/ws/DemographicService"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>

File diff suppressed because one or more lines are too long

View file

@ -1,108 +0,0 @@
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ws="http://ws.oscarehr.org/">
<soapenv:Header/>
<soapenv:Body>
<ws:addDemographic>
<!--Optional:-->
<arg0>
<!-- <activeCount>1</activeCount>-->
<!--Optional:-->
<address>2286 Ottawa</address>
<!--Optional:-->
<!-- <alias>?</alias>-->
<!--Optional:-->
<!-- <anonymous>?</anonymous>-->
<!--Optional:-->
<!-- <chartNo>?</chartNo>-->
<!--Optional:-->
<!-- <children>?</children>-->
<!--Optional:-->
<!-- <citizenship>?</citizenship>-->
<!--Optional:-->
<city>West Vancouver</city>
<!--Optional:-->
<!-- <dateJoined></dateJoined>-->
<!--Optional:-->
<dateOfBirth>06</dateOfBirth>
<!--Optional:-->
<!-- <demographicNo>?</demographicNo>-->
<!--Optional:-->
<displayName>TestUserAddDemographic</displayName>
<!--Optional:-->
<!-- <effDate></effDate>-->
<!--Optional:-->
<email>polydaic@gmail.com</email>
<!--Optional:-->
<!-- <endDate></endDate>-->
<!--Optional:-->
<!-- <familyDoctor> </familyDoctor>-->
<!--Optional:-->
<firstName>TestUser</firstName>
<!--Optional:-->
<!-- <hcRenewDate></hcRenewDate>-->
<!--Optional:-->
<!-- <hcType></hcType>-->
<!--Optional:-->
<!-- <headRecord></headRecord>-->
<!--Optional:-->
<!-- <hin></hin>-->
<!-- <hsAlertCount>0</hsAlertCount>-->
<!--Optional:-->
<lastName>AddDemographic</lastName>
<!--Optional:-->
<lastUpdateDate>2015-10-01T18:39:46.817Z</lastUpdateDate>
<!--Optional:-->
<!-- <lastUpdateUser> </lastUpdateUser>-->
<!--Optional:-->
<!-- <links> </links>-->
<!--Optional:-->
<monthOfBirth>06</monthOfBirth>
<!--Optional:-->
<!-- <myOscarUserName> </myOscarUserName>-->
<!--Optional:-->
<!-- <officialLanguage> </officialLanguage>-->
<!--Optional:-->
<!-- <patientStatus> </patientStatus>-->
<!--Optional:-->
<!-- <patientStatusDate></patientStatusDate>-->
<!--Optional:-->
<!-- <pcnIndicator> </pcnIndicator>-->
<!--Optional:-->
<phone>6048786969</phone>
<!--Optional:-->
<!-- <phone2> </phone2>-->
<!--Optional:-->
<postal>V8V5S8</postal>
<!--Optional:-->
<!-- <previousAddress> </previousAddress>-->
<!--Optional:-->
<!-- <providerNo> </providerNo>-->
<!--Optional:-->
<province>BC</province>
<!--Optional:-->
<!-- <rosterDate></rosterDate>-->
<!--Optional:-->
<!-- <rosterStatus> </rosterStatus>-->
<!--Optional:-->
<!-- <rosterTerminationDate></rosterTerminationDate>-->
<!--Optional:-->
<!-- <rosterTerminationReason> </rosterTerminationReason>-->
<!--Optional:-->
<sex>M</sex>
<!--Optional:-->
<!-- <sexDesc> </sexDesc>-->
<!--Optional:-->
<!-- <sin> </sin>-->
<!--Optional:-->
<!-- <sourceOfIncome> </sourceOfIncome>-->
<!--Optional:-->
<!-- <spokenLanguage> </spokenLanguage>-->
<!--Optional:-->
<title>Mr.</title>
<!--Optional:-->
<!-- <ver> </ver>-->
<!--Optional:-->
<yearOfBirth>1994</yearOfBirth>
</arg0>
</ws:addDemographic>
</soapenv:Body>
</soapenv:Envelope>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -24,6 +24,7 @@
}, },
"dependencies": { "dependencies": {
"async": "^1.4.2", "async": "^1.4.2",
"async-boolean-expression-evaluator": "^1.1.1",
"aws-sdk": "^2.3.9", "aws-sdk": "^2.3.9",
"bcrypt": "^0.8.7", "bcrypt": "^0.8.7",
"body-parser": "~1.14.1", "body-parser": "~1.14.1",
@ -35,6 +36,7 @@
"connect-mongo": "~0.8.2", "connect-mongo": "~0.8.2",
"consolidate": "~0.13.1", "consolidate": "~0.13.1",
"cookie-parser": "~1.4.0", "cookie-parser": "~1.4.0",
"deep-diff": "^0.3.4",
"dotenv": "^2.0.0", "dotenv": "^2.0.0",
"email-verification": "~0.4.1", "email-verification": "~0.4.1",
"envfile": "^2.0.1", "envfile": "^2.0.1",
@ -64,6 +66,7 @@
"lodash": "^2.4.1", "lodash": "^2.4.1",
"main-bower-files": "~2.9.0", "main-bower-files": "~2.9.0",
"math": "0.0.3", "math": "0.0.3",
"mathjs": "^3.4.1",
"method-override": "~2.3.0", "method-override": "~2.3.0",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
"mongoose": "~4.4.19", "mongoose": "~4.4.19",
@ -81,6 +84,7 @@
"passport-google-oauth": "~0.2.0", "passport-google-oauth": "~0.2.0",
"passport-linkedin": "~1.0.0", "passport-linkedin": "~1.0.0",
"passport-local": "~1.0.0", "passport-local": "~1.0.0",
"passport-localapikey-update": "^0.5.0",
"passport-twitter": "~1.0.2", "passport-twitter": "~1.0.2",
"path-exists": "^2.1.0", "path-exists": "^2.1.0",
"pdffiller": "~0.1.1", "pdffiller": "~0.1.1",
@ -91,10 +95,14 @@
"socket.io": "^1.4.6", "socket.io": "^1.4.6",
"socket.io-redis": "^1.0.0", "socket.io-redis": "^1.0.0",
"swig": "~1.4.1", "swig": "~1.4.1",
"uuid-token-generator": "^0.5.0",
"wildcard-subdomains": "github:whitef0x0/wildcard-subdomains" "wildcard-subdomains": "github:whitef0x0/wildcard-subdomains"
}, },
"devDependencies": { "devDependencies": {
"chromedriver": "^2.25.1",
"coveralls": "^2.11.4", "coveralls": "^2.11.4",
"cross-spawn": "^5.0.0",
"del": "^2.2.2",
"glob": "^7.0.3", "glob": "^7.0.3",
"grunt-execute": "^0.2.2", "grunt-execute": "^0.2.2",
"grunt-mocha-istanbul": "^3.0.1", "grunt-mocha-istanbul": "^3.0.1",
@ -111,10 +119,12 @@
"karma-ng-html2js-preprocessor": "^0.2.0", "karma-ng-html2js-preprocessor": "^0.2.0",
"karma-phantomjs-launcher": "~0.2.1", "karma-phantomjs-launcher": "~0.2.1",
"mailosaur": "^1.0.1", "mailosaur": "^1.0.1",
"mocha": ">=1.20.0", "mocha": "^3.1.2",
"mocha-lcov-reporter": "^1.0.0", "mocha-lcov-reporter": "^1.0.0",
"nightwatch": "^0.9.8",
"node-mandrill": "^1.0.1", "node-mandrill": "^1.0.1",
"phantomjs": "^1.9.18", "phantomjs": "^1.9.18",
"selenium-server": "^3.0.1",
"should": "~7.1.1", "should": "~7.1.1",
"supertest": "~1.2.0", "supertest": "~1.2.0",
"supertest-session": "~2.0.1" "supertest-session": "~2.0.1"

View file

@ -180,7 +180,7 @@ ApplicationConfiguration.registerModule('core', ['users']);
// Use Application configuration module to register a new module // Use Application configuration module to register a new module
ApplicationConfiguration.registerModule('forms', [ ApplicationConfiguration.registerModule('forms', [
'ngFileUpload', 'ui.router.tabs', 'ui.date', 'ui.sortable', 'ngFileUpload', 'ui.router.tabs', 'ui.date', 'ui.sortable',
'angular-input-stars', 'users' 'angular-input-stars', 'users', 'ngclipboard'
]);//, 'colorpicker.module' @TODO reactivate this module ]);//, 'colorpicker.module' @TODO reactivate this module
'use strict'; 'use strict';
@ -228,6 +228,10 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
PREVIEW: 'Preview', PREVIEW: 'Preview',
//Edit Form View //Edit Form View
DISABLED: 'Disabled:',
YES: 'YES',
NO: 'NO',
ADD_LOGIC_JUMP: 'Add Logic Jump',
ADD_FIELD_LG: 'Click to Add New Field', ADD_FIELD_LG: 'Click to Add New Field',
ADD_FIELD_MD: 'Add New Field', ADD_FIELD_MD: 'Add New Field',
ADD_FIELD_SM: 'Add Field', ADD_FIELD_SM: 'Add Field',
@ -549,11 +553,12 @@ angular.module('core').controller('HeaderController', ['$rootScope', '$scope', '
$rootScope.signupDisabled = $window.signupDisabled; $rootScope.signupDisabled = $window.signupDisabled;
$scope.user = $rootScope.user = Auth.ensureHasCurrentUser(User); $scope.user = $rootScope.user = Auth.ensureHasCurrentUser(User);
console.log(Auth.ensureHasCurrentUser(User));
$scope.authentication = $rootScope.authentication = Auth; $scope.authentication = $rootScope.authentication = Auth;
$rootScope.languages = $scope.languages = ['en', 'fr', 'es', 'it', 'de']; $rootScope.languages = $scope.languages = ['en', 'fr', 'es', 'it', 'de'];
console.log($locale.id);
//Set global app language //Set global app language
if($scope.authentication.isAuthenticated()){ if($scope.authentication.isAuthenticated()){
$rootScope.language = $scope.user.language; $rootScope.language = $scope.user.language;
@ -952,7 +957,7 @@ angular.module('forms').config(['$stateProvider',
// Create a controller method for sending visitor data // Create a controller method for sending visitor data
function send(form, lastActiveIndex, timeElapsed) { function send(form, lastActiveIndex, timeElapsed) {
// Create a new message object // Create a new message object
var visitorData = { /*var visitorData = {
referrer: document.referrer, referrer: document.referrer,
isSubmitted: form.submitted, isSubmitted: form.submitted,
formId: form._id, formId: form._id,
@ -981,15 +986,15 @@ angular.module('forms').config(['$stateProvider',
} }
console.log(visitorData.deviceType); console.log(visitorData.deviceType);
Socket.emit('form-visitor-data', visitorData); Socket.emit('form-visitor-data', visitorData);
}); });*/
} }
function init(){ function init(){
// Make sure the Socket is connected // Make sure the Socket is connected
if (!Socket.socket) { /*if (!Socket.socket) {
Socket.connect(); Socket.connect();
} }*/
} }
var service = { var service = {
@ -1267,6 +1272,7 @@ angular.module('users').controller('AuthenticationController', ['$scope', '$loca
angular.module('users').controller('PasswordController', ['$scope', '$stateParams', '$state', 'User', angular.module('users').controller('PasswordController', ['$scope', '$stateParams', '$state', 'User',
function($scope, $stateParams, $state, User) { function($scope, $stateParams, $state, User) {
$scope.error = ''; $scope.error = '';
// Submit forgotten password account id // Submit forgotten password account id
@ -1303,11 +1309,13 @@ angular.module('users').controller('PasswordController', ['$scope', '$stateParam
}; };
} }
]); ]);
'use strict'; 'use strict';
angular.module('users').controller('SettingsController', ['$scope', '$rootScope', '$http', '$state', 'Users', angular.module('users').controller('SettingsController', ['$scope', '$rootScope', '$http', '$state', 'Users', 'Auth',
function($scope, $rootScope, $http, $state, Users) { function($scope, $rootScope, $http, $state, Users, Auth) {
$scope.user = $rootScope.user;
$scope.user = Auth.currentUser;
// Check if there are additional accounts // Check if there are additional accounts
$scope.hasConnectedAdditionalSocialAccounts = function(provider) { $scope.hasConnectedAdditionalSocialAccounts = function(provider) {
@ -1317,6 +1325,10 @@ angular.module('users').controller('SettingsController', ['$scope', '$rootScope'
return false; return false;
}; };
$scope.cancel = function(){
$scope.user = Auth.currentUser;
};
// Check if provider is already in use with current user // Check if provider is already in use with current user
$scope.isConnectedSocialAccount = function(provider) { $scope.isConnectedSocialAccount = function(provider) {
return $scope.user.provider === provider || ($scope.user.additionalProvidersData && $scope.user.additionalProvidersData[provider]); return $scope.user.provider === provider || ($scope.user.additionalProvidersData && $scope.user.additionalProvidersData[provider]);
@ -1371,6 +1383,7 @@ angular.module('users').controller('SettingsController', ['$scope', '$rootScope'
} }
]); ]);
'use strict'; 'use strict';
angular.module('users').controller('VerifyController', ['$scope', '$state', '$rootScope', 'User', 'Auth', '$stateParams', angular.module('users').controller('VerifyController', ['$scope', '$state', '$rootScope', 'User', 'Auth', '$stateParams',
@ -1697,8 +1710,21 @@ angular.module('core').config(['$translateProvider', function ($translateProvide
'use strict'; 'use strict';
// Forms controller // Forms controller
angular.module('forms').controller('AdminFormController', ['$rootScope', '$scope', '$stateParams', '$state', 'Forms', 'CurrentForm', '$http', '$uibModal', 'myForm', '$filter', angular.module('forms').controller('AdminFormController', ['$rootScope', '$scope', '$stateParams', '$state', 'Forms', 'CurrentForm', '$http', '$uibModal', 'myForm', '$filter', '$sce',
function($rootScope, $scope, $stateParams, $state, Forms, CurrentForm, $http, $uibModal, myForm, $filter) { function($rootScope, $scope, $stateParams, $state, Forms, CurrentForm, $http, $uibModal, myForm, $filter, $sce) {
$scope.trustSrc = function(src) {
return $sce.trustAsResourceUrl(src);
};
//Set active tab to Create
$scope.activePill = 0;
$scope.copied = false;
$scope.onCopySuccess = function(e) {
console.log("COPY SUCCESSFUL!");
$scope.copied = true;
};
$scope = $rootScope; $scope = $rootScope;
$scope.animationsEnabled = true; $scope.animationsEnabled = true;
@ -1707,24 +1733,34 @@ angular.module('forms').controller('AdminFormController', ['$rootScope', '$scope
CurrentForm.setForm($scope.myform); CurrentForm.setForm($scope.myform);
$scope.formURL = $scope.myform.admin.username + '.tellform.com'; $scope.formURL = "/#!/forms/" + $scope.myform._id;
$scope.actualFormURL = window.location.protocol + '//' + $scope.myform.admin.username + '.' + window.location.host + "/#!/forms/" + $scope.myform._id;
var refreshFrame = $scope.refreshFrame = function(){
if(document.getElementById('iframe')) {
document.getElementById('iframe').contentWindow.location.reload();
}
};
$scope.tabData = [ $scope.tabData = [
{ /*{
heading: $filter('translate')('CREATE_TAB'), heading: $filter('translate')('CREATE_TAB'),
route: 'viewForm.create' templateName: 'create'
}, },
{ {
heading: $filter('translate')('DESIGN_TAB'), heading: $filter('translate')('DESIGN_TAB'),
route: 'viewForm.design' templateName: 'design'
}, },*/
{ {
heading: $filter('translate')('CONFIGURE_TAB'), heading: $filter('translate')('CONFIGURE_TAB'),
route: 'viewForm.configure' templateName: 'configure'
}, },
{ {
heading: $filter('translate')('ANALYZE_TAB'), heading: $filter('translate')('ANALYZE_TAB'),
route: 'viewForm.analyze' templateName: 'analyze'
} }
]; ];
@ -1788,7 +1824,8 @@ angular.module('forms').controller('AdminFormController', ['$rootScope', '$scope
}; };
// Update existing Form // Update existing Form
$scope.update = $rootScope.update = function(updateImmediately, cb){ $scope.update = $rootScope.update = function(updateImmediately, diffChanges, cb){
refreshFrame();
var continueUpdate = true; var continueUpdate = true;
if(!updateImmediately){ if(!updateImmediately){
@ -1801,7 +1838,7 @@ angular.module('forms').controller('AdminFormController', ['$rootScope', '$scope
if(!updateImmediately){ $rootScope.saveInProgress = true; } if(!updateImmediately){ $rootScope.saveInProgress = true; }
$scope.updatePromise = $http.put('/forms/'+$scope.myform._id, {form: $scope.myform}) $scope.updatePromise = $http.put('/forms/'+$scope.myform._id, { changes: diffChanges })
.then(function(response){ .then(function(response){
$rootScope.myform = $scope.myform = response.data; $rootScope.myform = $scope.myform = response.data;
// console.log(response.data); // console.log(response.data);
@ -1954,7 +1991,6 @@ angular.module('forms').directive('autoSaveForm', ['$rootScope', '$timeout', fun
$rootScope.finishedRender = false; $rootScope.finishedRender = false;
$scope.$on('editFormFields Started', function(ngRepeatFinishedEvent) { $scope.$on('editFormFields Started', function(ngRepeatFinishedEvent) {
// console.log('hello');
$rootScope.finishedRender = false; $rootScope.finishedRender = false;
}); });
$scope.$on('editFormFields Finished', function(ngRepeatFinishedEvent) { $scope.$on('editFormFields Finished', function(ngRepeatFinishedEvent) {
@ -1974,13 +2010,11 @@ angular.module('forms').directive('autoSaveForm', ['$rootScope', '$timeout', fun
return false; return false;
}; };
var debounceSave = function () { var debounceSave = function (diffChanges) {
$rootScope.saveInProgress = true;
$rootScope[$attrs.autoSaveCallback](true, $rootScope[$attrs.autoSaveCallback](true, diffChanges,
function(err){ function(err){
if(!err){ if(!err){
//console.log('\n\nForm data persisted -- setting pristine flag');
$formCtrl.$setPristine(); $formCtrl.$setPristine();
$formCtrl.$setUntouched(); $formCtrl.$setUntouched();
}else{ }else{
@ -1990,56 +2024,48 @@ angular.module('forms').directive('autoSaveForm', ['$rootScope', '$timeout', fun
}); });
}; };
//Update/Save Form if any Form fields are Dirty and Touched
$scope.$watch(function(newValue, oldValue) {
//console.log('introParagraphStartPage.$dirty: '+$scope.editForm.introParagraphStartPage.$dirty);
//console.log('introParagraphStartPage.$touched: '+$scope.editForm.introParagraphStartPage.$touched);
if($rootScope.finishedRender && $scope.anyDirtyAndTouched($scope.editForm) && !$rootScope.saveInProgress){
//console.log('Form saving started');
debounceSave();
//console.log('introParagraphStartPage.$dirty AFTER: '+$scope.editForm.introParagraphStartPage.$dirty);
}
});
//Autosave Form when model (specified in $attrs.autoSaveWatch) changes //Autosave Form when model (specified in $attrs.autoSaveWatch) changes
$scope.$watch($attrs.autoSaveWatch, function(newValue, oldValue) { $scope.$watch($attrs.autoSaveWatch, function(newValue, oldValue) {
if( !newValue || !oldValue ) {
$rootScope.finishedRender = true;
return;
}
newValue = angular.copy(newValue); newValue = angular.copy(newValue);
oldValue = angular.copy(oldValue); oldValue = angular.copy(oldValue);
delete newValue.visible_form_fields;
delete oldValue.visible_form_fields;
newValue.form_fields = _.removeDateFields(newValue.form_fields); newValue.form_fields = _.removeDateFields(newValue.form_fields);
oldValue.form_fields = _.removeDateFields(oldValue.form_fields); oldValue.form_fields = _.removeDateFields(oldValue.form_fields);
var changedFields = !_.isEqual(oldValue.form_fields,newValue.form_fields) || !_.isEqual(oldValue.startPage, newValue.startPage); var changedFields = !!DeepDiff.diff(oldValue, newValue) && DeepDiff.diff(oldValue, newValue).length > 0;
var changedFieldMap = false;
if(oldValue.hasOwnProperty('plugins.oscarhost.settings.fieldMap')){
changedFieldMap = !!oldValue.plugins.oscarhost.settings.fieldMap && !_.isEqual(oldValue.plugins.oscarhost.settings.fieldMap,newValue.plugins.oscarhost.settings.fieldMap);
}
//If our form is undefined, don't save form //If our form is undefined, don't save form
if( (!newValue && !oldValue) || !oldValue ){ if(!changedFields){
$rootScope.finishedRender = true;
return; return;
} }
// console.log('Autosaving');
// console.log('\n\n----------');
// console.log('!$dirty: '+ !$formCtrl.$dirty );
// console.log('changedFields: '+changedFields);
// console.log('changedFieldMap: '+changedFieldMap);
// console.log('finishedRender: '+$rootScope.finishedRender);
// console.log('!saveInProgress: '+!$rootScope.saveInProgress);
// console.log('newValue: '+newValue);
// console.log('oldValue: '+oldValue);
// console.log(oldValue.form_fields);
// console.log(newValue.form_fields);
if(oldValue.form_fields.length === 0) { if(oldValue.form_fields.length === 0) {
$rootScope.finishedRender = true; $rootScope.finishedRender = true;
} }
//console.log('Autosaving');
//console.log('\n\n----------');
//console.log('!$dirty: '+ !$formCtrl.$dirty );
// console.log('changedFieldMap: '+changedFieldMap);
//console.log('finishedRender: '+$rootScope.finishedRender);
//console.log('!saveInProgress: '+!$rootScope.saveInProgress);
// console.log('newValue: '+newValue);
// console.log('oldValue: '+oldValue);
// console.log(oldValue.form_fields);
// console.log(newValue.form_fields);
//Save form ONLY IF rendering is finished, form_fields have been changed AND currently not save in progress //Save form ONLY IF rendering is finished, form_fields have been changed AND currently not save in progress
if( $rootScope.finishedRender && ((changedFields && !$formCtrl.$dirty) || changedFieldMap) && !$rootScope.saveInProgress) { if( $rootScope.finishedRender && (changedFields) && !$rootScope.saveInProgress) {
if(savePromise) { if(savePromise) {
$timeout.cancel(savePromise); $timeout.cancel(savePromise);
@ -2047,7 +2073,12 @@ angular.module('forms').directive('autoSaveForm', ['$rootScope', '$timeout', fun
} }
savePromise = $timeout(function() { savePromise = $timeout(function() {
debounceSave(); $rootScope.saveInProgress = true;
delete newValue.visible_form_fields;
delete newValue.visible_form_fields;
var _diff = DeepDiff.diff(oldValue, newValue);
debounceSave(_diff);
}); });
} }
//If we are finished rendering then form saving should be finished //If we are finished rendering then form saving should be finished
@ -2164,16 +2195,12 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
return { return {
templateUrl: 'modules/forms/admin/views/directiveViews/form/edit-form.client.view.html', templateUrl: 'modules/forms/admin/views/directiveViews/form/edit-form.client.view.html',
restrict: 'E', restrict: 'E',
transclude: true,
scope: { scope: {
myform:'=' myform:'='
}, },
controller: ["$scope", function($scope){ controller: ["$scope", function($scope){
console.log($scope.myform);
var field_ids = _($scope.myform.form_fields).pluck('_id');
for(var i=0; i<field_ids.length; i++){
$scope.myform.plugins.oscarhost.settings.fieldMap[field_ids[i]] = null;
}
/* /*
** Initialize scope with variables ** Initialize scope with variables
*/ */
@ -2222,23 +2249,23 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
//Populate local scope with rootScope methods/variables //Populate local scope with rootScope methods/variables
$scope.update = $rootScope.update; $scope.update = $rootScope.update;
//Many-to-many Select for Mapping OscarhostFields -> FormFields // LOGIC JUMP METHODS
$scope.oscarFieldsLeft = function(field_id){ $scope.removeLogicJump = function (field_index) {
var currField = $scope.myform.form_fields[field_index];
currField.logicJump = {};
};
if($scope.myform && $scope.myform.plugins.oscarhost.settings.validFields.length > 0){ $scope.addNewLogicJump = function (field_index) {
if(!$scope.myform.plugins.oscarhost.settings.fieldMap) $scope.myform.plugins.oscarhost.settings.fieldMap = {}; var form_fields = $scope.myform.form_fields;
var currField = form_fields[field_index];
console.log(currField);
if (form_fields.length > 1 && currField._id) {
var oscarhostFields = $scope.myform.plugins.oscarhost.settings.validFields; var newLogicJump = {
var currentFields = _($scope.myform.plugins.oscarhost.settings.fieldMap).invert().keys().value(); fieldA: currField._id
};
if( $scope.myform.plugins.oscarhost.settings.fieldMap.hasOwnProperty(field_id) ){ currField.logicJump = newLogicJump;
currentFields = _(currentFields).difference($scope.myform.plugins.oscarhost.settings.fieldMap[field_id]);
} }
//Get all oscarhostFields that haven't been mapped to a formfield
return _(oscarhostFields).difference(currentFields).value();
}
return [];
}; };
/* /*
@ -2268,12 +2295,13 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
} }
} }
var newField = { var newField = {
title: fieldTitle, title: fieldTitle + ' ' + $scope.myform.form_fields.length+1,
fieldType: fieldType, fieldType: fieldType,
fieldValue: '', fieldValue: '',
required: true, required: true,
disabled: false, disabled: false,
deletePreserved: false deletePreserved: false,
logicJump: {}
}; };
if($scope.showAddOptions(newField)){ if($scope.showAddOptions(newField)){
@ -2285,7 +2313,6 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
}); });
} }
if(modifyForm){ if(modifyForm){
//Add newField to form_fields array //Add newField to form_fields array
$scope.myform.form_fields.push(newField); $scope.myform.form_fields.push(newField);
@ -2294,15 +2321,11 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
}; };
// Delete particular field on button click // Delete particular field on button click
$scope.deleteField = function (field_index){ $scope.deleteField = function (field_index) {
console.log($scope.myform.form_fields);
//Delete field from field map
var currFieldId = $scope.myform.form_fields[field_index]._id;
if($scope.myform.hasOwnProperty('plugins.oscarhost.baseUrl')) delete $scope.myform.plugins.oscarhost.settings.fieldMap[currFieldId];
//Delete field
$scope.myform.form_fields.splice(field_index, 1); $scope.myform.form_fields.splice(field_index, 1);
}; };
$scope.duplicateField = function (field_index){ $scope.duplicateField = function (field_index){
var currField = _.cloneDeep($scope.myform.form_fields[field_index]); var currField = _.cloneDeep($scope.myform.form_fields[field_index]);
currField._id = 'cloned'+_.uniqueId(); currField._id = 'cloned'+_.uniqueId();
@ -2408,10 +2431,7 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
return false; return false;
} }
}; };
}] }]
}; };
} }
]); ]);
@ -2493,7 +2513,6 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
var visitors = $scope.myform.analytics.visitors; var visitors = $scope.myform.analytics.visitors;
console.log(visitors);
for(var i=0; i<visitors.length; i++){ for(var i=0; i<visitors.length; i++){
var visitor = visitors[i]; var visitor = visitors[i];
var deviceType = visitor.deviceType; var deviceType = visitor.deviceType;
@ -2854,23 +2873,24 @@ angular.module('forms').directive('fieldDirective', ['$http', '$compile', '$root
if(scope.field.fieldType === 'number' || scope.field.fieldType === 'textfield' || scope.field.fieldType === 'email' || scope.field.fieldType === 'link'){ if(scope.field.fieldType === 'number' || scope.field.fieldType === 'textfield' || scope.field.fieldType === 'email' || scope.field.fieldType === 'link'){
switch(scope.field.fieldType){ switch(scope.field.fieldType){
case 'textfield': case 'textfield':
scope.field.input_type = 'text'; scope.input_type = 'text';
break; break;
case 'email': case 'email':
scope.field.input_type = 'email'; scope.input_type = 'email';
scope.field.placeholder = 'joesmith@example.com'; scope.placeholder = 'joesmith@example.com';
break; break;
case 'number': case 'number':
scope.field.input_type = 'text'; scope.input_type = 'text';
scope.field.validateRegex = /^-?\d+$/; scope.validateRegex = /^-?\d+$/;
break; break;
default: default:
scope.field.input_type = 'url'; scope.input_type = 'url';
scope.field.placeholder = 'http://example.com'; scope.placeholder = 'http://example.com';
break; break;
} }
fieldType = 'textfield'; fieldType = 'textfield';
} }
var template = getTemplateUrl(fieldType); var template = getTemplateUrl(fieldType);
element.html(template).show(); element.html(template).show();
var output = $compile(element.contents())(scope); var output = $compile(element.contents())(scope);
@ -3330,7 +3350,8 @@ angular.module('users').config(['$translateProvider', function ($translateProvid
$translateProvider.translations('en', { $translateProvider.translations('en', {
ACCESS_DENIED_TEXT: 'You need to be logged in to access this page', ACCESS_DENIED_TEXT: 'You need to be logged in to access this page',
USERNAME_LABEL: 'Username or Email', USERNAME_OR_EMAIL_LABEL: 'Username or Email',
USERNAME_LABEL: 'Username',
PASSWORD_LABEL: 'Password', PASSWORD_LABEL: 'Password',
CURRENT_PASSWORD_LABEL: 'Current Password', CURRENT_PASSWORD_LABEL: 'Current Password',
NEW_PASSWORD_LABEL: 'New Password', NEW_PASSWORD_LABEL: 'New Password',
@ -3347,6 +3368,9 @@ angular.module('users').config(['$translateProvider', function ($translateProvid
SIGNIN_HEADER_TEXT: 'Sign in', SIGNIN_HEADER_TEXT: 'Sign in',
SIGNUP_ERROR_TEXT: 'Couldn\'t complete registration due to errors', SIGNUP_ERROR_TEXT: 'Couldn\'t complete registration due to errors',
ENTER_ACCOUNT_EMAIL: 'Enter your account email.',
RESEND_VERIFICATION_EMAIL: 'Resend Verification Email',
SAVE_CHANGES: 'Save Changes',
UPDATE_PROFILE_BTN: 'Update Profile', UPDATE_PROFILE_BTN: 'Update Profile',
PROFILE_SAVE_SUCCESS: 'Profile saved successfully', PROFILE_SAVE_SUCCESS: 'Profile saved successfully',

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -176,7 +176,7 @@ angular.module('NodeForm.templates', []).run(['$templateCache', function($templa
ApplicationConfiguration.registerModule('view-form', [ ApplicationConfiguration.registerModule('view-form', [
'ngFileUpload', 'ui.router.tabs', 'ui.date', 'ui.sortable', 'ngFileUpload', 'ui.router.tabs', 'ui.date', 'ui.sortable',
'angular-input-stars', 'pascalprecht.translate' 'angular-input-stars', 'pascalprecht.translate'
]);//, 'colorpicker.module' @TODO reactivate this module ]);
'use strict'; 'use strict';
@ -616,7 +616,7 @@ var __indexOf = [].indexOf || function(item) {
if (i in this && this[i] === item) return i; if (i in this && this[i] === item) return i;
} }
return -1; return -1;
}; };
angular.module('view-form').directive('fieldDirective', ['$http', '$compile', '$rootScope', '$templateCache', 'supportedFields', angular.module('view-form').directive('fieldDirective', ['$http', '$compile', '$rootScope', '$templateCache', 'supportedFields',
function($http, $compile, $rootScope, $templateCache, supportedFields) { function($http, $compile, $rootScope, $templateCache, supportedFields) {
@ -693,29 +693,30 @@ angular.module('view-form').directive('fieldDirective', ['$http', '$compile', '$
if(scope.field.fieldType === 'number' || scope.field.fieldType === 'textfield' || scope.field.fieldType === 'email' || scope.field.fieldType === 'link'){ if(scope.field.fieldType === 'number' || scope.field.fieldType === 'textfield' || scope.field.fieldType === 'email' || scope.field.fieldType === 'link'){
switch(scope.field.fieldType){ switch(scope.field.fieldType){
case 'textfield': case 'textfield':
scope.field.input_type = 'text'; scope.input_type = 'text';
break; break;
case 'email': case 'email':
scope.field.input_type = 'email'; scope.input_type = 'email';
scope.field.placeholder = 'joesmith@example.com'; scope.placeholder = 'joesmith@example.com';
break; break;
case 'number': case 'number':
scope.field.input_type = 'text'; scope.input_type = 'text';
scope.field.validateRegex = /^-?\d+$/; scope.validateRegex = /^-?\d+$/;
break; break;
default: default:
scope.field.input_type = 'url'; scope.input_type = 'url';
scope.field.placeholder = 'http://example.com'; scope.placeholder = 'http://example.com';
break; break;
} }
fieldType = 'textfield'; fieldType = 'textfield';
} }
var template = getTemplateUrl(fieldType); var template = getTemplateUrl(fieldType);
element.html(template).show(); element.html(template).show();
var output = $compile(element.contents())(scope); var output = $compile(element.contents())(scope);
} }
}; };
}]); }]);
'use strict'; 'use strict';
@ -824,6 +825,42 @@ angular.module('view-form').directive('onFinishRender', ["$rootScope", "$timeout
'use strict'; 'use strict';
//FIXME: Should find an appropriate place for this
//Setting up jsep
jsep.addBinaryOp("contains", 10);
jsep.addBinaryOp("!contains", 10);
jsep.addBinaryOp("begins", 10);
jsep.addBinaryOp("!begins", 10);
jsep.addBinaryOp("ends", 10);
jsep.addBinaryOp("!ends", 10);
/**
* Calculate a 32 bit FNV-1a hash
* Found here: https://gist.github.com/vaiorabbit/5657561
* Ref.: http://isthe.com/chongo/tech/comp/fnv/
*
* @param {string} str the input value
* @param {boolean} [asString=false] set to true to return the hash value as
* 8-digit hex string instead of an integer
* @param {integer} [seed] optionally pass the hash of the previous chunk
* @returns {integer | string}
*/
function hashFnv32a(str, asString, seed) {
/*jshint bitwise:false */
var i, l,
hval = (seed === undefined) ? 0x811c9dc5 : seed;
for (i = 0, l = str.length; i < l; i++) {
hval ^= str.charCodeAt(i);
hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);
}
if( asString ){
// Convert to 8 digit hex string
return ("0000000" + (hval >>> 0).toString(16)).substr(-8);
}
return hval >>> 0;
}
;
angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCounter', '$filter', '$rootScope', 'SendVisitorData', angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCounter', '$filter', '$rootScope', 'SendVisitorData',
function ($http, TimeCounter, $filter, $rootScope, SendVisitorData) { function ($http, TimeCounter, $filter, $rootScope, SendVisitorData) {
@ -916,6 +953,67 @@ angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCoun
/* /*
** Field Controls ** Field Controls
*/ */
var evaluateLogicJump = function(field){
console.log('evaluateLogicJump');
console.log(field.fieldValue);
var logicJump = field.logicJump;
if (logicJump.expressionString && logicJump.valueB && field.fieldValue) {
var parse_tree = jsep(logicJump.expressionString);
var left, right;
console.log(parse_tree);
if(parse_tree.left.name === 'field'){
left = field.fieldValue;
right = logicJump.valueB
} else {
left = logicJump.valueB;
right = field.fieldValue;
}
if(field.fieldType === 'number' || field.fieldType === 'scale' || field.fieldType === 'rating'){
switch(parse_tree.operator) {
case '==':
return (parseInt(left) === parseInt(right));
case '!==':
return (parseInt(left) !== parseInt(right));
case '>':
return (parseInt(left) > parseInt(right));
case '>=':
return (parseInt(left) > parseInt(right));
case '<':
return (parseInt(left) < parseInt(right));
case '<=':
return (parseInt(left) <= parseInt(right));
default:
return false;
}
} else {
switch(parse_tree.operator) {
case '==':
return (left === right);
case '!==':
return (left !== right);
case 'contains':
return (left.indexOf(right) > -1);
case '!contains':
return !(left.indexOf(right) > -1);
case 'begins':
return left.startsWith(right);
case '!begins':
return !left.startsWith(right);
case 'ends':
return left.endsWith(right);
case '!ends':
return left.endsWith(right);
default:
return false;
}
}
}
};
var getActiveField = function(){ var getActiveField = function(){
if($scope.selected === null){ if($scope.selected === null){
console.error('current active field is null'); console.error('current active field is null');
@ -941,6 +1039,15 @@ angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCoun
$scope.selected._id = field_id; $scope.selected._id = field_id;
$scope.selected.index = field_index; $scope.selected.index = field_index;
if(!field_index){
for(var i=0; i<$scope.myform.visible_form_fields.length; i++){
var currField = $scope.myform.visible_form_fields[i];
if(field_id == currField._id){
$scope.selected.index = i;
break;
}
}
}
var nb_valid = $filter('formValidity')($scope.myform); var nb_valid = $filter('formValidity')($scope.myform);
$scope.translateAdvancementData = { $scope.translateAdvancementData = {
@ -983,20 +1090,26 @@ angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCoun
}; };
$rootScope.nextField = $scope.nextField = function(){ $rootScope.nextField = $scope.nextField = function(){
//console.log('nextfield'); var currField = $scope.myform.visible_form_fields[$scope.selected.index];
//console.log($scope.selected.index);
//console.log($scope.myform.visible_form_fields.length-1); if($scope.selected && $scope.selected.index > -1){
//Jump to logicJump's destination if it is true
if(currField.logicJump && evaluateLogicJump(currField)){
$rootScope.setActiveField(currField.logicJump.jumpTo, null, true);
} else {
var selected_index, selected_id; var selected_index, selected_id;
if($scope.selected.index < $scope.myform.visible_form_fields.length-1){ if($scope.selected.index < $scope.myform.visible_form_fields.length-1){
selected_index = $scope.selected.index+1; selected_index = $scope.selected.index+1;
selected_id = $scope.myform.visible_form_fields[selected_index]._id; selected_id = $scope.myform.visible_form_fields[selected_index]._id;
$rootScope.setActiveField(selected_id, selected_index, true); $rootScope.setActiveField(selected_id, selected_index, true);
} else if($scope.selected.index === $scope.myform.visible_form_fields.length-1) { } else if($scope.selected.index === $scope.myform.visible_form_fields.length-1) {
//console.log('Second last element');
selected_index = $scope.selected.index+1; selected_index = $scope.selected.index+1;
selected_id = 'submit_field'; selected_id = 'submit_field';
$rootScope.setActiveField(selected_id, selected_index, true); $rootScope.setActiveField(selected_id, selected_index, true);
} }
}
}
}; };
$rootScope.prevField = $scope.prevField = function(){ $rootScope.prevField = $scope.prevField = function(){

File diff suppressed because one or more lines are too long

View file

@ -6,7 +6,7 @@ var __indexOf = [].indexOf || function(item) {
if (i in this && this[i] === item) return i; if (i in this && this[i] === item) return i;
} }
return -1; return -1;
}; };
angular.module('view-form').directive('fieldDirective', ['$http', '$compile', '$rootScope', '$templateCache', 'supportedFields', angular.module('view-form').directive('fieldDirective', ['$http', '$compile', '$rootScope', '$templateCache', 'supportedFields',
function($http, $compile, $rootScope, $templateCache, supportedFields) { function($http, $compile, $rootScope, $templateCache, supportedFields) {
@ -83,26 +83,27 @@ angular.module('view-form').directive('fieldDirective', ['$http', '$compile', '$
if(scope.field.fieldType === 'number' || scope.field.fieldType === 'textfield' || scope.field.fieldType === 'email' || scope.field.fieldType === 'link'){ if(scope.field.fieldType === 'number' || scope.field.fieldType === 'textfield' || scope.field.fieldType === 'email' || scope.field.fieldType === 'link'){
switch(scope.field.fieldType){ switch(scope.field.fieldType){
case 'textfield': case 'textfield':
scope.field.input_type = 'text'; scope.input_type = 'text';
break; break;
case 'email': case 'email':
scope.field.input_type = 'email'; scope.input_type = 'email';
scope.field.placeholder = 'joesmith@example.com'; scope.placeholder = 'joesmith@example.com';
break; break;
case 'number': case 'number':
scope.field.input_type = 'text'; scope.input_type = 'text';
scope.field.validateRegex = /^-?\d+$/; scope.validateRegex = /^-?\d+$/;
break; break;
default: default:
scope.field.input_type = 'url'; scope.input_type = 'url';
scope.field.placeholder = 'http://example.com'; scope.placeholder = 'http://example.com';
break; break;
} }
fieldType = 'textfield'; fieldType = 'textfield';
} }
var template = getTemplateUrl(fieldType); var template = getTemplateUrl(fieldType);
element.html(template).show(); element.html(template).show();
var output = $compile(element.contents())(scope); var output = $compile(element.contents())(scope);
} }
}; };
}]); }]);

View file

@ -1,5 +1,41 @@
'use strict'; 'use strict';
//FIXME: Should find an appropriate place for this
//Setting up jsep
jsep.addBinaryOp("contains", 10);
jsep.addBinaryOp("!contains", 10);
jsep.addBinaryOp("begins", 10);
jsep.addBinaryOp("!begins", 10);
jsep.addBinaryOp("ends", 10);
jsep.addBinaryOp("!ends", 10);
/**
* Calculate a 32 bit FNV-1a hash
* Found here: https://gist.github.com/vaiorabbit/5657561
* Ref.: http://isthe.com/chongo/tech/comp/fnv/
*
* @param {string} str the input value
* @param {boolean} [asString=false] set to true to return the hash value as
* 8-digit hex string instead of an integer
* @param {integer} [seed] optionally pass the hash of the previous chunk
* @returns {integer | string}
*/
function hashFnv32a(str, asString, seed) {
/*jshint bitwise:false */
var i, l,
hval = (seed === undefined) ? 0x811c9dc5 : seed;
for (i = 0, l = str.length; i < l; i++) {
hval ^= str.charCodeAt(i);
hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);
}
if( asString ){
// Convert to 8 digit hex string
return ("0000000" + (hval >>> 0).toString(16)).substr(-8);
}
return hval >>> 0;
}
;
angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCounter', '$filter', '$rootScope', 'SendVisitorData', angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCounter', '$filter', '$rootScope', 'SendVisitorData',
function ($http, TimeCounter, $filter, $rootScope, SendVisitorData) { function ($http, TimeCounter, $filter, $rootScope, SendVisitorData) {
@ -92,6 +128,67 @@ angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCoun
/* /*
** Field Controls ** Field Controls
*/ */
var evaluateLogicJump = function(field){
console.log('evaluateLogicJump');
console.log(field.fieldValue);
var logicJump = field.logicJump;
if (logicJump.expressionString && logicJump.valueB && field.fieldValue) {
var parse_tree = jsep(logicJump.expressionString);
var left, right;
console.log(parse_tree);
if(parse_tree.left.name === 'field'){
left = field.fieldValue;
right = logicJump.valueB
} else {
left = logicJump.valueB;
right = field.fieldValue;
}
if(field.fieldType === 'number' || field.fieldType === 'scale' || field.fieldType === 'rating'){
switch(parse_tree.operator) {
case '==':
return (parseInt(left) === parseInt(right));
case '!==':
return (parseInt(left) !== parseInt(right));
case '>':
return (parseInt(left) > parseInt(right));
case '>=':
return (parseInt(left) > parseInt(right));
case '<':
return (parseInt(left) < parseInt(right));
case '<=':
return (parseInt(left) <= parseInt(right));
default:
return false;
}
} else {
switch(parse_tree.operator) {
case '==':
return (left === right);
case '!==':
return (left !== right);
case 'contains':
return (left.indexOf(right) > -1);
case '!contains':
return !(left.indexOf(right) > -1);
case 'begins':
return left.startsWith(right);
case '!begins':
return !left.startsWith(right);
case 'ends':
return left.endsWith(right);
case '!ends':
return left.endsWith(right);
default:
return false;
}
}
}
};
var getActiveField = function(){ var getActiveField = function(){
if($scope.selected === null){ if($scope.selected === null){
console.error('current active field is null'); console.error('current active field is null');
@ -117,6 +214,15 @@ angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCoun
$scope.selected._id = field_id; $scope.selected._id = field_id;
$scope.selected.index = field_index; $scope.selected.index = field_index;
if(!field_index){
for(var i=0; i<$scope.myform.visible_form_fields.length; i++){
var currField = $scope.myform.visible_form_fields[i];
if(field_id == currField._id){
$scope.selected.index = i;
break;
}
}
}
var nb_valid = $filter('formValidity')($scope.myform); var nb_valid = $filter('formValidity')($scope.myform);
$scope.translateAdvancementData = { $scope.translateAdvancementData = {
@ -159,20 +265,26 @@ angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCoun
}; };
$rootScope.nextField = $scope.nextField = function(){ $rootScope.nextField = $scope.nextField = function(){
//console.log('nextfield'); var currField = $scope.myform.visible_form_fields[$scope.selected.index];
//console.log($scope.selected.index);
//console.log($scope.myform.visible_form_fields.length-1); if($scope.selected && $scope.selected.index > -1){
//Jump to logicJump's destination if it is true
if(currField.logicJump && evaluateLogicJump(currField)){
$rootScope.setActiveField(currField.logicJump.jumpTo, null, true);
} else {
var selected_index, selected_id; var selected_index, selected_id;
if($scope.selected.index < $scope.myform.visible_form_fields.length-1){ if($scope.selected.index < $scope.myform.visible_form_fields.length-1){
selected_index = $scope.selected.index+1; selected_index = $scope.selected.index+1;
selected_id = $scope.myform.visible_form_fields[selected_index]._id; selected_id = $scope.myform.visible_form_fields[selected_index]._id;
$rootScope.setActiveField(selected_id, selected_index, true); $rootScope.setActiveField(selected_id, selected_index, true);
} else if($scope.selected.index === $scope.myform.visible_form_fields.length-1) { } else if($scope.selected.index === $scope.myform.visible_form_fields.length-1) {
//console.log('Second last element');
selected_index = $scope.selected.index+1; selected_index = $scope.selected.index+1;
selected_id = 'submit_field'; selected_id = 'submit_field';
$rootScope.setActiveField(selected_id, selected_index, true); $rootScope.setActiveField(selected_id, selected_index, true);
} }
}
}
}; };
$rootScope.prevField = $scope.prevField = function(){ $rootScope.prevField = $scope.prevField = function(){

View file

@ -21,9 +21,9 @@
<div class="col-xs-12 field-input"> <div class="col-xs-12 field-input">
<input ng-style="{'color': design.colors.answerColor, 'border-color': design.colors.answerColor}" <input ng-style="{'color': design.colors.answerColor, 'border-color': design.colors.answerColor}"
name="{{field.fieldType}}{{index}}" name="{{field.fieldType}}{{index}}"
type="{{field.input_type}}" type="{{input_type}}"
ng-pattern="field.validateRegex" ng-pattern="validateRegex"
placeholder="{{field.placeholder}}" placeholder="{{placeholder}}"
ng-class="{ 'no-border': !!field.fieldValue }" ng-class="{ 'no-border': !!field.fieldValue }"
class="focusOn text-field-input" class="focusOn text-field-input"
ng-model="field.fieldValue" ng-model="field.fieldValue"
@ -34,7 +34,7 @@
on-tab-and-shift-key="prevField()" on-tab-and-shift-key="prevField()"
ng-required="field.required" ng-required="field.required"
ng-disabled="field.disabled" ng-disabled="field.disabled"
aria-describedby="inputError2Status"> aria-describedby="inputError2Status"/ >
</div> </div>
<div class="col-xs-12"> <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"> <div ng-show="forms.myForm.{{field.fieldType}}{{index}}.$invalid && !!forms.myForm.{{field.fieldType}}{{index}}.$viewValue " class="alert alert-danger" role="alert">
@ -51,7 +51,7 @@
style="padding: 4px; margin-top:8px; background: rgba(255,255,255,0.5)"> style="padding: 4px; margin-top:8px; background: rgba(255,255,255,0.5)">
<button ng-disabled="!field.fieldValue || forms.myForm.{{field.fieldType}}{{$index}}.$invalid" <button ng-disabled="!field.fieldValue || forms.myForm.{{field.fieldType}}{{$index}}.$invalid"
ng-style="{'background-color':design.colors.buttonColor, 'color':design.colors.buttonTextColor}" ng-style="{'background-color':design.colors.buttonColor, 'color':design.colors.buttonTextColor}"
ng-click="$root.nextField()" ng-click="nextField()"
class="btn col-sm-5 col-xs-5"> class="btn col-sm-5 col-xs-5">
{{ 'OK' | translate }} <i class="fa fa-check"></i> {{ 'OK' | translate }} <i class="fa fa-check"></i>

View file

@ -4,4 +4,4 @@
ApplicationConfiguration.registerModule('view-form', [ ApplicationConfiguration.registerModule('view-form', [
'ngFileUpload', 'ui.router.tabs', 'ui.date', 'ui.sortable', 'ngFileUpload', 'ui.router.tabs', 'ui.date', 'ui.sortable',
'angular-input-stars', 'pascalprecht.translate' 'angular-input-stars', 'pascalprecht.translate'
]);//, 'colorpicker.module' @TODO reactivate this module ]);

View file

@ -6,11 +6,12 @@ angular.module('core').controller('HeaderController', ['$rootScope', '$scope', '
$rootScope.signupDisabled = $window.signupDisabled; $rootScope.signupDisabled = $window.signupDisabled;
$scope.user = $rootScope.user = Auth.ensureHasCurrentUser(User); $scope.user = $rootScope.user = Auth.ensureHasCurrentUser(User);
console.log(Auth.ensureHasCurrentUser(User));
$scope.authentication = $rootScope.authentication = Auth; $scope.authentication = $rootScope.authentication = Auth;
$rootScope.languages = $scope.languages = ['en', 'fr', 'es', 'it', 'de']; $rootScope.languages = $scope.languages = ['en', 'fr', 'es', 'it', 'de'];
console.log($locale.id);
//Set global app language //Set global app language
if($scope.authentication.isAuthenticated()){ if($scope.authentication.isAuthenticated()){
$rootScope.language = $scope.user.language; $rootScope.language = $scope.user.language;

View file

@ -1,3 +1,5 @@
body { body {
overflow-x: hidden; overflow-x: hidden;
font-family: 'Source Sans Pro', sans-serif; font-family: 'Source Sans Pro', sans-serif;
@ -10,12 +12,34 @@ body {
float: none; float: none;
} }
/* Custom CSS for Buttons */
.btn-rounded {
border-radius: 100px;
font-size: 14px;
padding: 10px 28px;
margin: 0 2px;
margin-top: 1em;
text-transform: uppercase;
text-decoration: none!important;
}
.btn-secondary {
background: #DDDDDD;
color: #4c4c4c;
border: 2px #4c4c4c solid;
}
.btn-secondary:hover {
background: #cacaca;
border-color: #cacaca;
}
/*Navbar Custom CSS*/ /*Navbar Custom CSS*/
.navbar { .navbar {
min-height: 60px; min-height: 60px;
padding: 10px 0 10px 0;
} }
.navbar-inverse { .navbar-inverse {
background-color:#588EB4; background-color:#3FA2F7;
border: 0; border: 0;
color: white!important; color: white!important;
} }
@ -33,6 +57,7 @@ body {
min-height: 60px; min-height: 60px;
} }
.navbar-nav > li > a { .navbar-nav > li > a {
padding-top: 20px;
color: white; color: white;
} }
.navbar-nav > li:hover, .navbar-nav > li.active { .navbar-nav > li:hover, .navbar-nav > li.active {

View file

@ -5,25 +5,11 @@
<span class="sr-only">Toggle navigation</span> <span class="sr-only">Toggle navigation</span>
<span>{{ 'MENU_BTN' | translate }}</span> <span>{{ 'MENU_BTN' | translate }}</span>
</button> </button>
<a href="https://www.tellform.com/#!/" class="navbar-brand"> <a href="/#!/" class="navbar-brand">
<img src="/static/modules/core/img/logo_white.svg" height="100%"> <img src="/static/modules/core/img/logo_white.svg" height="100%">
</a> </a>
</div> </div>
<nav class="collapse navbar-collapse" collapse="!isCollapsed" role="navigation"> <nav class="collapse navbar-collapse" collapse="!isCollapsed" role="navigation">
<ul class="nav navbar-nav" data-ng-if="authentication.isAuthenticated()">
<li data-ng-repeat="item in menu.items | orderBy: 'position'" data-ng-if="item.shouldRender(authentication.isAuthenticated());" ng-switch="item.menuItemType" ui-route="{{item.uiRoute}}" class="{{item.menuItemClass}}" ng-class="{active: ($uiRoute)}" dropdown="item.menuItemType === 'dropdown'">
<a ng-switch-when="dropdown" class="dropdown-toggle" dropdown-toggle>
<span data-ng-bind="item.title"></span>
<b class="caret"></b>
</a>
<ul ng-switch-when="dropdown" class="dropdown-menu">
<li data-ng-repeat="subitem in item.items | orderBy: 'position'" data-ng-if="subitem.shouldRender(authentication.isAuthenticated());" ui-route="{{subitem.uiRoute}}" ng-class="{active: $uiRoute}">
<a href="/#!/{{subitem.link}}" data-ng-bind="subitem.title"></a>
</li>
</ul>
<a ng-switch-default href="/#!/{{item.link}}" data-ng-bind="item.title"></a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right" data-ng-hide="authentication.isAuthenticated()"> <ul class="nav navbar-nav navbar-right" data-ng-hide="authentication.isAuthenticated()">
<li ng-hide="$root.signupDisabled" ui-route="/signup" ng-class="{active: $uiRoute}"> <li ng-hide="$root.signupDisabled" ui-route="/signup" ng-class="{active: $uiRoute}">
<a href="/#!/signup">{{ 'SIGNUP_TAB' | translate }}</a> <a href="/#!/signup">{{ 'SIGNUP_TAB' | translate }}</a>

View file

@ -39,6 +39,10 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
PREVIEW: 'Preview', PREVIEW: 'Preview',
//Edit Form View //Edit Form View
DISABLED: 'Disabled:',
YES: 'YES',
NO: 'NO',
ADD_LOGIC_JUMP: 'Add Logic Jump',
ADD_FIELD_LG: 'Click to Add New Field', ADD_FIELD_LG: 'Click to Add New Field',
ADD_FIELD_MD: 'Add New Field', ADD_FIELD_MD: 'Add New Field',
ADD_FIELD_SM: 'Add Field', ADD_FIELD_SM: 'Add Field',

View file

@ -1,8 +1,21 @@
'use strict'; 'use strict';
// Forms controller // Forms controller
angular.module('forms').controller('AdminFormController', ['$rootScope', '$scope', '$stateParams', '$state', 'Forms', 'CurrentForm', '$http', '$uibModal', 'myForm', '$filter', angular.module('forms').controller('AdminFormController', ['$rootScope', '$scope', '$stateParams', '$state', 'Forms', 'CurrentForm', '$http', '$uibModal', 'myForm', '$filter', '$sce',
function($rootScope, $scope, $stateParams, $state, Forms, CurrentForm, $http, $uibModal, myForm, $filter) { function($rootScope, $scope, $stateParams, $state, Forms, CurrentForm, $http, $uibModal, myForm, $filter, $sce) {
$scope.trustSrc = function(src) {
return $sce.trustAsResourceUrl(src);
};
//Set active tab to Create
$scope.activePill = 0;
$scope.copied = false;
$scope.onCopySuccess = function(e) {
console.log("COPY SUCCESSFUL!");
$scope.copied = true;
};
$scope = $rootScope; $scope = $rootScope;
$scope.animationsEnabled = true; $scope.animationsEnabled = true;
@ -11,24 +24,34 @@ angular.module('forms').controller('AdminFormController', ['$rootScope', '$scope
CurrentForm.setForm($scope.myform); CurrentForm.setForm($scope.myform);
$scope.formURL = $scope.myform.admin.username + '.tellform.com'; $scope.formURL = "/#!/forms/" + $scope.myform._id;
$scope.actualFormURL = window.location.protocol + '//' + $scope.myform.admin.username + '.' + window.location.host + "/#!/forms/" + $scope.myform._id;
var refreshFrame = $scope.refreshFrame = function(){
if(document.getElementById('iframe')) {
document.getElementById('iframe').contentWindow.location.reload();
}
};
$scope.tabData = [ $scope.tabData = [
{ /*{
heading: $filter('translate')('CREATE_TAB'), heading: $filter('translate')('CREATE_TAB'),
route: 'viewForm.create' templateName: 'create'
}, },
{ {
heading: $filter('translate')('DESIGN_TAB'), heading: $filter('translate')('DESIGN_TAB'),
route: 'viewForm.design' templateName: 'design'
}, },*/
{ {
heading: $filter('translate')('CONFIGURE_TAB'), heading: $filter('translate')('CONFIGURE_TAB'),
route: 'viewForm.configure' templateName: 'configure'
}, },
{ {
heading: $filter('translate')('ANALYZE_TAB'), heading: $filter('translate')('ANALYZE_TAB'),
route: 'viewForm.analyze' templateName: 'analyze'
} }
]; ];
@ -92,7 +115,8 @@ angular.module('forms').controller('AdminFormController', ['$rootScope', '$scope
}; };
// Update existing Form // Update existing Form
$scope.update = $rootScope.update = function(updateImmediately, cb){ $scope.update = $rootScope.update = function(updateImmediately, diffChanges, cb){
refreshFrame();
var continueUpdate = true; var continueUpdate = true;
if(!updateImmediately){ if(!updateImmediately){
@ -105,7 +129,7 @@ angular.module('forms').controller('AdminFormController', ['$rootScope', '$scope
if(!updateImmediately){ $rootScope.saveInProgress = true; } if(!updateImmediately){ $rootScope.saveInProgress = true; }
$scope.updatePromise = $http.put('/forms/'+$scope.myform._id, {form: $scope.myform}) $scope.updatePromise = $http.put('/forms/'+$scope.myform._id, { changes: diffChanges })
.then(function(response){ .then(function(response){
$rootScope.myform = $scope.myform = response.data; $rootScope.myform = $scope.myform = response.data;
// console.log(response.data); // console.log(response.data);

View file

@ -32,7 +32,6 @@ angular.module('forms').directive('autoSaveForm', ['$rootScope', '$timeout', fun
$rootScope.finishedRender = false; $rootScope.finishedRender = false;
$scope.$on('editFormFields Started', function(ngRepeatFinishedEvent) { $scope.$on('editFormFields Started', function(ngRepeatFinishedEvent) {
// console.log('hello');
$rootScope.finishedRender = false; $rootScope.finishedRender = false;
}); });
$scope.$on('editFormFields Finished', function(ngRepeatFinishedEvent) { $scope.$on('editFormFields Finished', function(ngRepeatFinishedEvent) {
@ -52,13 +51,11 @@ angular.module('forms').directive('autoSaveForm', ['$rootScope', '$timeout', fun
return false; return false;
}; };
var debounceSave = function () { var debounceSave = function (diffChanges) {
$rootScope.saveInProgress = true;
$rootScope[$attrs.autoSaveCallback](true, $rootScope[$attrs.autoSaveCallback](true, diffChanges,
function(err){ function(err){
if(!err){ if(!err){
//console.log('\n\nForm data persisted -- setting pristine flag');
$formCtrl.$setPristine(); $formCtrl.$setPristine();
$formCtrl.$setUntouched(); $formCtrl.$setUntouched();
}else{ }else{
@ -68,56 +65,48 @@ angular.module('forms').directive('autoSaveForm', ['$rootScope', '$timeout', fun
}); });
}; };
//Update/Save Form if any Form fields are Dirty and Touched
$scope.$watch(function(newValue, oldValue) {
//console.log('introParagraphStartPage.$dirty: '+$scope.editForm.introParagraphStartPage.$dirty);
//console.log('introParagraphStartPage.$touched: '+$scope.editForm.introParagraphStartPage.$touched);
if($rootScope.finishedRender && $scope.anyDirtyAndTouched($scope.editForm) && !$rootScope.saveInProgress){
//console.log('Form saving started');
debounceSave();
//console.log('introParagraphStartPage.$dirty AFTER: '+$scope.editForm.introParagraphStartPage.$dirty);
}
});
//Autosave Form when model (specified in $attrs.autoSaveWatch) changes //Autosave Form when model (specified in $attrs.autoSaveWatch) changes
$scope.$watch($attrs.autoSaveWatch, function(newValue, oldValue) { $scope.$watch($attrs.autoSaveWatch, function(newValue, oldValue) {
if( !newValue || !oldValue ) {
$rootScope.finishedRender = true;
return;
}
newValue = angular.copy(newValue); newValue = angular.copy(newValue);
oldValue = angular.copy(oldValue); oldValue = angular.copy(oldValue);
delete newValue.visible_form_fields;
delete oldValue.visible_form_fields;
newValue.form_fields = _.removeDateFields(newValue.form_fields); newValue.form_fields = _.removeDateFields(newValue.form_fields);
oldValue.form_fields = _.removeDateFields(oldValue.form_fields); oldValue.form_fields = _.removeDateFields(oldValue.form_fields);
var changedFields = !_.isEqual(oldValue.form_fields,newValue.form_fields) || !_.isEqual(oldValue.startPage, newValue.startPage); var changedFields = !!DeepDiff.diff(oldValue, newValue) && DeepDiff.diff(oldValue, newValue).length > 0;
var changedFieldMap = false;
if(oldValue.hasOwnProperty('plugins.oscarhost.settings.fieldMap')){
changedFieldMap = !!oldValue.plugins.oscarhost.settings.fieldMap && !_.isEqual(oldValue.plugins.oscarhost.settings.fieldMap,newValue.plugins.oscarhost.settings.fieldMap);
}
//If our form is undefined, don't save form //If our form is undefined, don't save form
if( (!newValue && !oldValue) || !oldValue ){ if(!changedFields){
$rootScope.finishedRender = true;
return; return;
} }
// console.log('Autosaving');
// console.log('\n\n----------');
// console.log('!$dirty: '+ !$formCtrl.$dirty );
// console.log('changedFields: '+changedFields);
// console.log('changedFieldMap: '+changedFieldMap);
// console.log('finishedRender: '+$rootScope.finishedRender);
// console.log('!saveInProgress: '+!$rootScope.saveInProgress);
// console.log('newValue: '+newValue);
// console.log('oldValue: '+oldValue);
// console.log(oldValue.form_fields);
// console.log(newValue.form_fields);
if(oldValue.form_fields.length === 0) { if(oldValue.form_fields.length === 0) {
$rootScope.finishedRender = true; $rootScope.finishedRender = true;
} }
//console.log('Autosaving');
//console.log('\n\n----------');
//console.log('!$dirty: '+ !$formCtrl.$dirty );
// console.log('changedFieldMap: '+changedFieldMap);
//console.log('finishedRender: '+$rootScope.finishedRender);
//console.log('!saveInProgress: '+!$rootScope.saveInProgress);
// console.log('newValue: '+newValue);
// console.log('oldValue: '+oldValue);
// console.log(oldValue.form_fields);
// console.log(newValue.form_fields);
//Save form ONLY IF rendering is finished, form_fields have been changed AND currently not save in progress //Save form ONLY IF rendering is finished, form_fields have been changed AND currently not save in progress
if( $rootScope.finishedRender && ((changedFields && !$formCtrl.$dirty) || changedFieldMap) && !$rootScope.saveInProgress) { if( $rootScope.finishedRender && (changedFields) && !$rootScope.saveInProgress) {
if(savePromise) { if(savePromise) {
$timeout.cancel(savePromise); $timeout.cancel(savePromise);
@ -125,7 +114,12 @@ angular.module('forms').directive('autoSaveForm', ['$rootScope', '$timeout', fun
} }
savePromise = $timeout(function() { savePromise = $timeout(function() {
debounceSave(); $rootScope.saveInProgress = true;
delete newValue.visible_form_fields;
delete newValue.visible_form_fields;
var _diff = DeepDiff.diff(oldValue, newValue);
debounceSave(_diff);
}); });
} }
//If we are finished rendering then form saving should be finished //If we are finished rendering then form saving should be finished

View file

@ -5,16 +5,12 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
return { return {
templateUrl: 'modules/forms/admin/views/directiveViews/form/edit-form.client.view.html', templateUrl: 'modules/forms/admin/views/directiveViews/form/edit-form.client.view.html',
restrict: 'E', restrict: 'E',
transclude: true,
scope: { scope: {
myform:'=' myform:'='
}, },
controller: function($scope){ controller: function($scope){
console.log($scope.myform);
var field_ids = _($scope.myform.form_fields).pluck('_id');
for(var i=0; i<field_ids.length; i++){
$scope.myform.plugins.oscarhost.settings.fieldMap[field_ids[i]] = null;
}
/* /*
** Initialize scope with variables ** Initialize scope with variables
*/ */
@ -63,23 +59,23 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
//Populate local scope with rootScope methods/variables //Populate local scope with rootScope methods/variables
$scope.update = $rootScope.update; $scope.update = $rootScope.update;
//Many-to-many Select for Mapping OscarhostFields -> FormFields // LOGIC JUMP METHODS
$scope.oscarFieldsLeft = function(field_id){ $scope.removeLogicJump = function (field_index) {
var currField = $scope.myform.form_fields[field_index];
currField.logicJump = {};
};
if($scope.myform && $scope.myform.plugins.oscarhost.settings.validFields.length > 0){ $scope.addNewLogicJump = function (field_index) {
if(!$scope.myform.plugins.oscarhost.settings.fieldMap) $scope.myform.plugins.oscarhost.settings.fieldMap = {}; var form_fields = $scope.myform.form_fields;
var currField = form_fields[field_index];
console.log(currField);
if (form_fields.length > 1 && currField._id) {
var oscarhostFields = $scope.myform.plugins.oscarhost.settings.validFields; var newLogicJump = {
var currentFields = _($scope.myform.plugins.oscarhost.settings.fieldMap).invert().keys().value(); fieldA: currField._id
};
if( $scope.myform.plugins.oscarhost.settings.fieldMap.hasOwnProperty(field_id) ){ currField.logicJump = newLogicJump;
currentFields = _(currentFields).difference($scope.myform.plugins.oscarhost.settings.fieldMap[field_id]);
} }
//Get all oscarhostFields that haven't been mapped to a formfield
return _(oscarhostFields).difference(currentFields).value();
}
return [];
}; };
/* /*
@ -109,12 +105,13 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
} }
} }
var newField = { var newField = {
title: fieldTitle, title: fieldTitle + ' ' + $scope.myform.form_fields.length+1,
fieldType: fieldType, fieldType: fieldType,
fieldValue: '', fieldValue: '',
required: true, required: true,
disabled: false, disabled: false,
deletePreserved: false deletePreserved: false,
logicJump: {}
}; };
if($scope.showAddOptions(newField)){ if($scope.showAddOptions(newField)){
@ -126,7 +123,6 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
}); });
} }
if(modifyForm){ if(modifyForm){
//Add newField to form_fields array //Add newField to form_fields array
$scope.myform.form_fields.push(newField); $scope.myform.form_fields.push(newField);
@ -135,15 +131,11 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
}; };
// Delete particular field on button click // Delete particular field on button click
$scope.deleteField = function (field_index){ $scope.deleteField = function (field_index) {
console.log($scope.myform.form_fields);
//Delete field from field map
var currFieldId = $scope.myform.form_fields[field_index]._id;
if($scope.myform.hasOwnProperty('plugins.oscarhost.baseUrl')) delete $scope.myform.plugins.oscarhost.settings.fieldMap[currFieldId];
//Delete field
$scope.myform.form_fields.splice(field_index, 1); $scope.myform.form_fields.splice(field_index, 1);
}; };
$scope.duplicateField = function (field_index){ $scope.duplicateField = function (field_index){
var currField = _.cloneDeep($scope.myform.form_fields[field_index]); var currField = _.cloneDeep($scope.myform.form_fields[field_index]);
currField._id = 'cloned'+_.uniqueId(); currField._id = 'cloned'+_.uniqueId();
@ -249,10 +241,7 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
return false; return false;
} }
}; };
} }
}; };
} }
]); ]);

View file

@ -75,7 +75,6 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
var visitors = $scope.myform.analytics.visitors; var visitors = $scope.myform.analytics.visitors;
console.log(visitors);
for(var i=0; i<visitors.length; i++){ for(var i=0; i<visitors.length; i++){
var visitor = visitors[i]; var visitor = visitors[i];
var deviceType = visitor.deviceType; var deviceType = visitor.deviceType;

View file

@ -2,7 +2,7 @@
<div class="container" cg-busy="{promise:updatePromise,templateUrl:'modules/forms/admin/views/directiveViews/cgBusy/update-form-message-TypeB.html',message:'Updating form...', backdrop:false, wrapperClass:'.busy-updating-wrapper'}"></div> <div class="container" cg-busy="{promise:updatePromise,templateUrl:'modules/forms/admin/views/directiveViews/cgBusy/update-form-message-TypeB.html',message:'Updating form...', backdrop:false, wrapperClass:'.busy-updating-wrapper'}"></div>
<section class="container admin-form"> <section class="admin-form">
<!-- Modal Delete Dialog Template --> <!-- Modal Delete Dialog Template -->
<script type="text/ng-template" id="myModalContent.html"> <script type="text/ng-template" id="myModalContent.html">
@ -29,7 +29,7 @@
</div> </div>
</script> </script>
<div class="page-header row-fluid" style="padding-bottom: 1em;"> <div class="page-header" style="padding-bottom: 1em;">
<div class="col-xs-10 col-sm-8"> <div class="col-xs-10 col-sm-8">
<h1 class="hidden-sm hidden-xs" data-ng-bind="myform.title" style="margin-bottom: 0px;"></h1> <h1 class="hidden-sm hidden-xs" data-ng-bind="myform.title" style="margin-bottom: 0px;"></h1>
<h2 class="hidden-md hidden-lg" data-ng-bind="myform.title" style="margin-bottom: 0px;"></h2> <h2 class="hidden-md hidden-lg" data-ng-bind="myform.title" style="margin-bottom: 0px;"></h2>
@ -45,19 +45,19 @@
</div> </div>
<div class="col-xs-1 col-sm-2"> <div class="col-xs-1 col-sm-2">
<small class="pull-right"> <small class="pull-right">
<a class="btn btn-default view-form-btn" href="//{{formURL}}/#!/forms/{{myform._id}}"> <a class="btn btn-secondary view-form-btn" href="{{actualFormURL}}">
<span class="hidden-xs hidden-sm"> <span class="hidden-xs hidden-sm">
{{ 'VIEW' | translate }} {{ 'VIEW' | translate }}
<span ng-show="myform.isLive"> <span ng-show="myform.isLive">
{{ 'LIVE' | translate }} {{ 'LIVE' | translate }}
</span> </span>
<span ng-hide="myform.isLive">{{ 'PREVIEW' | translate }}</span> {{ 'FORM' | translate }} <span ng-hide="myform.isLive">{{ 'PREVIEW' | translate }}</span>
</span>
<span class="hidden-xs hidden-md hidden-lg">
View
<span ng-if="myform.isLive">{{ 'LIVE' | translate }}</span>
<span ng-if="!myform.isLive">{{ 'PREVIEW' | translate }}</span>
</span> </span>
<!--<span class="hidden-xs hidden-md hidden-lg">-->
<!--View-->
<!--<span ng-if="myform.isLive">{{ 'LIVE' | translate }}</span>-->
<!--<span ng-if="!myform.isLive">{{ 'PREVIEW' | translate }}</span>-->
<!--</span>-->
<i class="status-light status-light-on fa fa-dot-circle-o" ng-if="myform.isLive"></i> <i class="status-light status-light-on fa fa-dot-circle-o" ng-if="myform.isLive"></i>
<i class="status-light status-light-off fa fa-dot-circle-o" ng-if="!myform.isLive"></i> <i class="status-light status-light-off fa fa-dot-circle-o" ng-if="!myform.isLive"></i>
</a> </a>
@ -65,16 +65,133 @@
</div> </div>
</div> </div>
<div class="row-fluid"> <div class="row">
<div class="col-xs-12"> <div class="col-xs-12">
<!-- <tabset> --> <uib-tabset active="activePill" vertical="true" type="pills">
<tabs data="tabData"></tabs> <uib-tab index="0" heading="{{ 'CREATE_TAB' | translate }}">
<!-- </tabset> --> <edit-form-directive myform="myform"></edit-form-directive>
</uib-tab>
<uib-tab ng-repeat="tab in tabData" index="{{$index}}+1" heading="{{tab.heading}}">
<div class='row' data-ng-include="'/static/modules/forms/admin/views/adminTabs/'+tab.templateName+'.html'" onload="form_url = trustSrc(formURL)"></div>
</uib-tab>
<uib-tab ng-if="tabData" heading="Share" index="{{tabData.length}}">
<div class="config-form">
<div class="row">
<div class="col-sm-12">
<uib-tabset active="activePill" vertical="true" type="pills">
<uib-tab index="0" heading="Share your Form">
<div class="row">
<div class="col-sm-12">
Your TellForm is permanently at this URL
</div>
<div class="col-sm-8 form-input">
<span ngclipboard data-clipboard-target="#copyURL"> <input id="copyURL" ng-value="actualFormURL" class="form-control ng-pristine ng-untouched ng-valid"> </span>
</div>
<div class="col-sm-4">
<button class="btn btn btn-secondary view-form-btn" ngclipboard data-clipboard-target="#copyURL">
Copy <i class="fa fa-clipboard" aria-hidden="true"></i>
</button>
</div>
</div>
</uib-tab>
<uib-tab index="1" heading="Embed your Form">
<div class="row">
<div class="col-sm-12">
Copy and Paste this to add your TellForm to your website
</div>
<div class="col-sm-8 form-input">
<span ngclipboard data-clipboard-target="#copyEmbedded">
<textarea id="copyEmbedded" class="form-control ng-pristine ng-untouched ng-valid" style="min-height:200px; width:100%; background-color: #FFFFCC; color: #30313F;">
&lt;!-- Change the width and height values to suit you best --&gt;
<iframe id="iframe" ng-if="!!formURL" src="{{trustSrc(formURL)}}" style="width:100%;height:500px;"></iframe>
<div style="font-family: Sans-Serif;font-size: 12px;color: #999;opacity: 0.5; padding-top: 5px;">Powered by<a href="https://www.tellform.com" style="color: #999" target="_blank">TellForm</a></div>
</textarea>
</span>
</div>
<div class="col-sm-4">
<button class="btn btn btn-secondary view-form-btn" ngclipboard data-clipboard-target="#copyEmbedded">
Copy <i class="fa fa-clipboard" aria-hidden="true"></i>
</button>
</div>
</div>
</uib-tab>
</uib-tabset>
</div>
</div>
</div>
</uib-tab>
<uib-tab ng-if="tabData && myform.form_fields.length" heading="Design" index="{{tabData.length}}+1">
<div class="config-form design container">
<div class="row">
<div class="col-md-4 col-sm-12 container">
<div class="row field">
<div class="field-title col-sm-5">
<h5>{{ 'BACKGROUND_COLOR' | translate }}</h5>
</div>
<div class="field-input col-sm-6">
<input class="form-control" colorpicker="hex" type="text" ng-model="myform.design.colors.backgroundColor" ng-style="{ 'background-color': myform.design.colors.backgroundColor }"/>
</div>
</div> </div>
<div class="col-xs-12"> <div class="row field">
<ui-view></ui-view> <div class="field-title col-sm-5">
<h5>{{ 'QUESTION_TEXT_COLOR' | translate }}</h5>
</div> </div>
<div class="field-input col-sm-6">
<input class="form-control" colorpicker="hex" type="text" ng-model="myform.design.colors.questionColor" ng-style="{ 'background-color': myform.design.colors.questionColor }"/>
</div>
</div>
<div class="row field">
<div class="field-title col-sm-5">
<h5>{{ 'ANSWER_TEXT_COLOR' | translate }}</h5>
</div>
<div class="field-input col-sm-6">
<input class="form-control" colorpicker="hex" type="text" ng-model="myform.design.colors.answerColor" ng-style="{ 'background-color': myform.design.colors.answerColor }"/>
</div>
</div>
<div class="row field">
<div class="field-title col-sm-5">
<h5>{{ 'BTN_BACKGROUND_COLOR' | translate }}</h5>
</div>
<div class="field-input col-sm-6">
<input class="form-control" colorpicker="hex" type="text"
ng-model="myform.design.colors.buttonColor"
ng-style="{ 'background-color': myform.design.colors.buttonColor }"/>
</div>
</div>
<div class="row field">
<div class="field-title col-sm-5">
<h5>{{ 'BTN_TEXT_COLOR' | translate }}</h5>
</div>
<div class="field-input col-sm-6">
<input class="form-control" colorpicker="hex" type="text"
ng-model="myform.design.colors.buttonTextColor"
ng-style="{ 'background-color': myform.design.colors.buttonTextColor }"/>
</div>
</div>
</div>
<div class="col-md-8 hide-md hide-lg">
<iframe id="iframe" ng-if="!!formURL" src="{{trustSrc(formURL)}}" style="border: none; box-shadow: 0px 0px 10px 0px grey; overflow: hidden; height: 400px; width: 90%; position: absolute;"></iframe>
</div>
</div>
<div class="row">
<div class="col-sm-offset-4 col-sm-2">
<button class="btn btn-signup btn-rounded" type="button" ng-click="update(false, null)"><i class="icon-arrow-left icon-white"></i>{{ 'SAVE_CHANGES' | translate }}</button>
</div>
<div class="col-sm-1">
<button class="btn btn-secondary btn-rounded" type="button" ng-click="resetForm()"><i class="icon-eye-open icon-white"></i>{{ 'CANCEL' | translate }}</button>
</div>
</div>
</div>
</uib-tab>
</uib-tabset>
</div>
</div> </div>
</section> </section>

View file

@ -1,21 +1,12 @@
<div class="config-form design container"> <div class="config-form design container">
<div class="row"> <div class="row">
<div class="col-md-12 container"> <div class="col-md-4 col-sm-12 container">
<div class="row">
<div class="col-sm-12">
<h2 class="hidden-sm hidden-xs">{{ 'DESIGN_HEADER' | translate }}</h2>
<h3 class="hidden-lg hidden-md">{{ 'DESIGN_HEADER' | translate }}</h3>
</div>
</div>
<div class="row field"> <div class="row field">
<div class="field-title col-sm-3"> <div class="field-title col-sm-3">
<h5>{{ 'BACKGROUND_COLOR' | translate }}</h5> <h5>{{ 'BACKGROUND_COLOR' | translate }}</h5>
</div> </div>
<div class="field-input col-sm-6">
<div class="field-input col-sm-9"> <input ng-change="refreshIframe()" class="form-control" colorpicker="hex" type="text" ng-model="myform.design.colors.backgroundColor" ng-style="{ 'background-color': myform.design.colors.backgroundColor }"/>
<input colorpicker="hex" type="text" ng-model="myform.design.colors.backgroundColor" ng-style="{ 'background-color': myform.design.colors.backgroundColor }"/>
</div> </div>
</div> </div>
@ -24,11 +15,8 @@
<h5>{{ 'QUESTION_TEXT_COLOR' | translate }}</h5> <h5>{{ 'QUESTION_TEXT_COLOR' | translate }}</h5>
</div> </div>
<div class="field-input col-sm-9"> <div class="field-input col-sm-6">
<input ng-change="refreshIframe()" class="form-control" colorpicker="hex" type="text" ng-model="myform.design.colors.questionColor" ng-style="{ 'background-color': myform.design.colors.questionColor }"/>
<input colorpicker="hex" type="text" ng-model="myform.design.colors.questionColor" ng-style="{ 'background-color': myform.design.colors.questionColor }"/>
</div> </div>
</div> </div>
@ -37,8 +25,8 @@
<h5>{{ 'ANSWER_TEXT_COLOR' | translate }}</h5> <h5>{{ 'ANSWER_TEXT_COLOR' | translate }}</h5>
</div> </div>
<div class="field-input col-sm-9"> <div class="field-input col-sm-6">
<input colorpicker="hex" type="text" ng-model="myform.design.colors.answerColor" ng-style="{ 'background-color': myform.design.colors.answerColor }"/> <input ng-change="refreshIframe()" class="form-control" colorpicker="hex" type="text" ng-model="myform.design.colors.answerColor" ng-style="{ 'background-color': myform.design.colors.answerColor }"/>
</div> </div>
</div> </div>
@ -47,8 +35,8 @@
<h5>{{ 'BTN_BACKGROUND_COLOR' | translate }}</h5> <h5>{{ 'BTN_BACKGROUND_COLOR' | translate }}</h5>
</div> </div>
<div class="field-input col-sm-9"> <div class="field-input col-sm-6">
<input colorpicker="hex" type="text" <input ng-change="refreshIframe()" class="form-control" colorpicker="hex" type="text"
ng-model="myform.design.colors.buttonColor" ng-model="myform.design.colors.buttonColor"
ng-style="{ 'background-color': myform.design.colors.buttonColor }"/> ng-style="{ 'background-color': myform.design.colors.buttonColor }"/>
</div> </div>
@ -58,21 +46,24 @@
<h5>{{ 'BTN_TEXT_COLOR' | translate }}</h5> <h5>{{ 'BTN_TEXT_COLOR' | translate }}</h5>
</div> </div>
<div class="field-input col-sm-9"> <div class="field-input col-sm-6">
<input colorpicker="hex" type="text" <input ng-change="refreshIframe()" class="form-control" colorpicker="hex" type="text"
ng-model="myform.design.colors.buttonTextColor" ng-model="myform.design.colors.buttonTextColor"
ng-style="{ 'background-color': myform.design.colors.buttonTextColor }"/> ng-style="{ 'background-color': myform.design.colors.buttonTextColor }"/>
</div> </div>
</div> </div>
</div> </div>
<div class="col-md-8 hide-md hide-lg">
<iframe refreshable="refreshDesign" ng-if="!!form_url" src="{{form_url}}" style="border: none; box-shadow: 0px 0px 10px 0px grey; overflow: hidden; height: 95vh; width: 90%; position: absolute;"></iframe>
</div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-sm-offset-4 col-sm-2"> <div class="col-sm-offset-4 col-sm-2">
<button class="btn btn-primary btn-large" type="button" ng-click="update(false, null)"><i class="icon-arrow-left icon-white"></i> Save Changes</button> <button class="btn btn-signup btn-rounded" type="button" ng-click="update(false, null)"><i class="icon-arrow-left icon-white"></i>{{ 'SAVE_CHANGES' | translate }}</button>
</div> </div>
<div class="col-sm-1"> <div class="col-sm-1">
<button class="btn btn-default" type="button" ng-click="resetForm()"><i class="icon-eye-open icon-white"></i> Cancel</button> <button class="btn btn-secondary btn-rounded" type="button" ng-click="resetForm()"><i class="icon-eye-open icon-white"></i>{{ 'CANCEL' | translate }}</button>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,22 +1,21 @@
<div class="config-form container"> <div class="config-form container">
<div class="row"> <!--<div class="row">
<div class="col-sm-offset-2 col-sm-10">
<!-- Settings -->
<div class="col-sm-12 container-fluid">
<div class="row">
<div class="col-sm-12">
<h2 class="hidden-sm hidden-xs">{{ 'ADVANCED_SETTINGS' | translate }}</h2> <h2 class="hidden-sm hidden-xs">{{ 'ADVANCED_SETTINGS' | translate }}</h2>
<h3 class="hidden-lg hidden-md">{{ 'ADVANCED_SETTINGS' | translate }}</h3> <h3 class="hidden-lg hidden-md">{{ 'ADVANCED_SETTINGS' | translate }}</h3>
</div> </div>
</div> </div>-->
<!-- Settings -->
<div class="row">
<div class="col-sm-offset-2 col-sm-4">
<div class="row field"> <div class="row field">
<div class="field-title col-sm-4"> <div class="field-title col-sm-12">
<h5>{{ 'FORM_NAME' | translate }}</h5> <h5>{{ 'FORM_NAME' | translate }}</h5>
</div> </div>
<div class="col-sm-8"> <div class="col-sm-12">
<input type="text" <input class="form-control"
type="text"
ng-model="myform.title" ng-model="myform.title"
value="{{myform.title}}" value="{{myform.title}}"
style="width: 100%;" style="width: 100%;"
@ -26,18 +25,18 @@
</div> </div>
<div class="row field"> <div class="row field">
<div class="field-title col-sm-6"> <div class="field-title col-sm-12">
<h5>{{ 'FORM_STATUS' | translate }}</h5> <h5>{{ 'FORM_STATUS' | translate }}</h5>
</div> </div>
<div class="field-input col-sm-6"> <div class="field-input col-sm-12">
<label> <label style="display: inline-block;">
<input type="radio" data-ng-value="true" ng-model="myform.isLive" ng-required="true" style="background-color:#33CC00;"/> <input type="radio" data-ng-value="true" ng-model="myform.isLive" ng-required="true" style="background-color:#33CC00;"/>
&nbsp;<span>{{ 'PUBLIC' | translate }}</span> &nbsp;<span>{{ 'PUBLIC' | translate }}</span>
</label> </label>
<label> <label style="display: inline-block;">
<input type="radio" data-ng-value="false" ng-model="myform.isLive" ng-required="true" /> <input type="radio" data-ng-value="false" ng-model="myform.isLive" ng-required="true" />
&nbsp;<span>{{ 'PRIVATE' | translate }}</span> &nbsp;<span>{{ 'PRIVATE' | translate }}</span>
</label> </label>
@ -46,24 +45,8 @@
</div> </div>
<div class="row field"> <div class="row field">
<div class="field-title col-sm-4"> <div class="col-sm-12 field-title">Language</div>
<h5>{{ 'GA_TRACKING_CODE' | translate }}</h5> <div class="col-sm-12 field-input">
</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>
<div class="col-xs-4 field-input">
<select ng-model="myform.language"> <select ng-model="myform.language">
<option ng-repeat="language in languages" <option ng-repeat="language in languages"
ng-selected="language == myform.language" ng-selected="language == myform.language"
@ -74,18 +57,36 @@
<span class="required-error" ng-show="field.required && !field.fieldValue">* required</span> <span class="required-error" ng-show="field.required && !field.fieldValue">* required</span>
</div> </div>
</div> </div>
</div>
<div class="col-sm-4">
<div class="row field"> <div class="row field">
<div class="field-title col-sm-6"> <div class="field-title col-sm-12">
<h5>{{ 'GA_TRACKING_CODE' | translate }}</h5>
</div>
<div class="col-sm-12">
<input class="form-control"
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="field-title col-sm-12">
<h5>{{ 'DISPLAY_FOOTER' | translate }}</h5> <h5>{{ 'DISPLAY_FOOTER' | translate }}</h5>
</div> </div>
<div class="field-input col-sm-6"> <div class="field-input col-sm-12">
<label> <label style="display: inline-block;">
<input type="radio" data-ng-value="false" ng-model="myform.hideFooter" ng-required="true" /> <input type="radio" data-ng-value="false" ng-model="myform.hideFooter" ng-required="true" />
&nbsp;<span>{{ 'YES' | translate }}</span> &nbsp;<span>{{ 'YES' | translate }}</span>
</label> </label>
<label> <label style="display: inline-block;">
<input type="radio" data-ng-value="true" ng-model="myform.hideFooter" ng-required="true" /> <input type="radio" data-ng-value="true" ng-model="myform.hideFooter" ng-required="true" />
&nbsp;<span>{{ 'No' | translate }}</span> &nbsp;<span>{{ 'No' | translate }}</span>
</label> </label>
@ -93,18 +94,18 @@
</div> </div>
<div class="row field"> <div class="row field">
<div class="field-title col-sm-6"> <div class="field-title col-sm-12">
<h5>Display Start Page?</h5> <h5>Display Start Page?</h5>
</div> </div>
<div class="field-input col-sm-6"> <div class="field-input col-sm-12">
<label> <label style="display: inline-block;">
<input type="radio" data-ng-value="true" ng-model="myform.startPage.showStart" ng-required="true" style="background-color:#33CC00;"/> <input type="radio" data-ng-value="true" ng-model="myform.startPage.showStart" ng-required="true" style="background-color:#33CC00;"/>
&nbsp;<span>{{ 'YES' | translate }}</span> &nbsp;<span>{{ 'YES' | translate }}</span>
</label> </label>
<label> <label style="display: inline-block;">
<input type="radio" data-ng-value="false" ng-model="myform.startPage.showStart" ng-required="true" /> <input type="radio" data-ng-value="false" ng-model="myform.startPage.showStart" ng-required="true" />
&nbsp;<span>{{ 'NO' | translate }}</span> &nbsp;<span>{{ 'NO' | translate }}</span>
</label> </label>
@ -115,10 +116,10 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-sm-offset-4 col-sm-2"> <div class="col-sm-offset-4 col-sm-2">
<button class="btn btn-primary btn-large" type="button" ng-click="update(false, null)"><i class="icon-arrow-left icon-white"></i>{{ 'SAVE_CHANGES' | translate }}</button> <button class="btn btn-signup btn-rounded" type="button" ng-click="update(false, null)"><i class="icon-arrow-left icon-white"></i>{{ 'SAVE_CHANGES' | translate }}</button>
</div> </div>
<div class="col-sm-1"> <div class="col-sm-1">
<button class="btn btn-default" type="button" ng-click="resetForm()"><i class="icon-eye-open icon-white"></i>{{ 'CANCEL' | translate }}</button> <button class="btn btn-secondary btn-rounded" type="button" ng-click="resetForm()"><i class="icon-eye-open icon-white"></i>{{ 'CANCEL' | translate }}</button>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,7 +1,7 @@
<form class="row container" name="editForm" auto-save-form auto-save-watch="myform" auto-save-callback="update"> <form class="row container" name="editForm" auto-save-form auto-save-watch="myform" auto-save-callback="update">
<!-- Add Fields Element --> <!-- Add Fields Element -->
<div class="col-xs-2 col-sm-4 col-md-4 add-field"> <div class="col-xs-2 col-sm-4 add-field">
<div class="row add-field-title"> <div class="row add-field-title">
<h3 class="col-md-12 hidden-sm hidden-xs">{{ 'ADD_FIELD_LG' | translate }}</h3> <h3 class="col-md-12 hidden-sm hidden-xs">{{ 'ADD_FIELD_LG' | translate }}</h3>
@ -249,7 +249,6 @@
</div> </div>
</div> </div>
<div class="row" ng-show="showRatingOptions(field)"><br></div> <div class="row" ng-show="showRatingOptions(field)"><br></div>
<div class="row" ng-if="showRatingOptions(field)"> <div class="row" ng-if="showRatingOptions(field)">
<div class="col-md-9 col-sm-9">{{ 'NUM_OF_STEPS' | translate }}</div> <div class="col-md-9 col-sm-9">{{ 'NUM_OF_STEPS' | translate }}</div>
@ -293,7 +292,7 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-4 col-xs-12 field-input">Disabled:</div> <div class="col-md-4 col-xs-12 field-input">{{ 'DISABLED' | translate }}</div>
<div class="col-md-8 col-xs-12 field-input"> <div class="col-md-8 col-xs-12 field-input">
<label class="btn col-xs-5"> <label class="btn col-xs-5">
<input type="radio" ng-value="true" <input type="radio" ng-value="true"
@ -309,6 +308,106 @@
</div> </div>
</div> </div>
<div class="row" ng-if="myform.form_fields.length > 1">
<h4> Logic Jump </h4>
</div>
<div class="row" ng-if="myform.form_fields.length > 1">
<div class="col-md-4 col-xs-12 field-input">{{ 'ADD_LOGIC_JUMP' | translate }}</div>
<div class="col-md-8 col-xs-12 field-input">
<label class="btn col-xs-5">
<input type="radio" ng-checked="!!myform.form_fields[$index].logicJump.fieldA"
name="logicJumpYes{{field._id}}" ng-click="addNewLogicJump($index)"/>
<span> &nbsp; {{ 'YES' | translate }}</span>
</label>
<label class="btn col-xs-5 col-xs-offset-1">
<input type="radio" ng-checked="!myform.form_fields[$index].logicJump.fieldA"
name="logicJumpNo{{field._id}}" ng-click="removeLogicJump($index)"/>
<span> &nbsp; {{ 'NO' | translate }}</span>
</label>
</div>
</div>
<div class="row question" ng-if="myform.form_fields.length > 1 && myform.form_fields[$index].logicJump.fieldA">
<div class="col-md-4 col-sm-12">
<!-- FIX ME: translate this -->
<b> If this field </b>
</div>
<div class="col-md-4 col-sm-12">
<select style="width:100%" ng-model="field.logicJump.expressionString"
value="{{field.logicJump.expressionString}}"
name="logicjump_expressionString{{field._id}}">
<option value="field == static">
<!-- FIX ME: translate this -->
is equal to
</option>
<option value="field != static">
<!-- FIX ME: translate this -->
is not equal to
</option>
<option value="field > static" ng-if-start="field.fieldType === 'number' || field.fieldType === 'rating' || field.fieldType === 'number'">
<!-- FIX ME: translate this -->
is greater than
</option>
<option value="field >= static">
<!-- FIX ME: translate this -->
is greater or equal than
</option>
<option value="field < static">
<!-- FIX ME: translate this -->
is smaller than
</option>
<option value="field <= static" ng-if-end>
<!-- FIX ME: translate this -->
is smaller or equal than
</option>
<option value="field contains static" ng-if-start="field.fieldType !== 'number' && field.fieldType !== 'rating' && field.fieldType !== 'number'">
<!-- FIX ME: translate this -->
contains
</option>
<option value="field !contains static">
<!-- FIX ME: translate this -->
does not contain
</option>
<option value="field ends static">
<!-- FIX ME: translate this -->
ends with
</option>
<option value="field !ends static">
<!-- FIX ME: translate this -->
does not end with
</option>
<option value="field starts static">
<!-- FIX ME: translate this -->
starts with
</option>
<option value="field !starts static" ng-if-end>
<!-- FIX ME: translate this -->
does not start with
</option>
</select>
</div>
<div class="col-md-4 col-sm-12">
<input type="text" ng-model="field.logicJump.valueB"/>
</div>
<div class="col-md-2">
<!-- FIX ME: translate this -->
<b>Jumps to </b>
</div>
<div class="col-md-10">
<select style="width:100%" ng-model="field.logicJump.jumpTo"
value="{{field.logicJump.jumpTo}}"
name="logicjump_jumpTo{{field._id}}">
<option ng-repeat="jump_field in myform.form_fields"
value="{{jump_field._id}}">
{{jump_field.title}}
</option>
</select>
</div>
</div>
</div> </div>
</accordion-group> </accordion-group>

View file

@ -1,9 +1,6 @@
<div class="submissions-table row container"> <div class="submissions-table container">
<div class="row text-center analytics"> <div class="row text-center analytics">
<div class="row col-xs-12 header-numbers"> <div class="col-xs-12 header-title">
Overview Analytics
</div>
<div class="row col-xs-12 header-title">
<div class="col-xs-3"> <div class="col-xs-3">
{{ 'TOTAL_VIEWS' | translate }} {{ 'TOTAL_VIEWS' | translate }}
</div> </div>
@ -20,7 +17,7 @@
{{ 'AVERAGE_TIME_TO_COMPLETE' | translate }} {{ 'AVERAGE_TIME_TO_COMPLETE' | translate }}
</div> </div>
</div> </div>
<div class="row col-xs-12 header-numbers"> <div class="col-xs-12 header-numbers">
<div class="col-xs-3"> <div class="col-xs-3">
{{myform.analytics.views}} {{myform.analytics.views}}
</div> </div>
@ -37,10 +34,7 @@
{{AverageTimeElapsed | secondsToDateTime | date:'mm:ss'}} {{AverageTimeElapsed | secondsToDateTime | date:'mm:ss'}}
</div> </div>
</div> </div>
<div class="row col-xs-12 header-numbers"> <div class="col-xs-12 detailed-title">
Device Analytics
</div>
<div class="row col-xs-12 detailed-title">
<div class="col-xs-3"> <div class="col-xs-3">
{{ 'DESKTOP_AND_LAPTOP' | translate }} {{ 'DESKTOP_AND_LAPTOP' | translate }}
</div> </div>
@ -58,7 +52,7 @@
</div> </div>
</div> </div>
<div class="row col-xs-12 detailed-row"> <div class="col-xs-12 detailed-row">
<div class="col-xs-3"> <div class="col-xs-3">
<div class="row header"> <div class="row header">
{{ 'UNIQUE_VISITS' | translate }} {{ 'UNIQUE_VISITS' | translate }}
@ -96,7 +90,7 @@
</div> </div>
</div> </div>
<div class="row col-xs-12 detailed-row"> <div class="col-xs-12 detailed-row">
<div class="col-xs-3"> <div class="col-xs-3">
<div class="row header"> <div class="row header">
{{ 'RESPONSES' | translate }} {{ 'RESPONSES' | translate }}
@ -134,7 +128,7 @@
</div> </div>
</div> </div>
<div class="row col-xs-12 detailed-row"> <div class="col-xs-12 detailed-row">
<div class="col-xs-3"> <div class="col-xs-3">
<div class="row header"> <div class="row header">
{{ 'COMPLETION_RATE' | translate }} {{ 'COMPLETION_RATE' | translate }}
@ -172,7 +166,7 @@
</div> </div>
</div> </div>
<div class="row col-xs-12 detailed-row"> <div class="col-xs-12 detailed-row">
<div class="col-xs-3"> <div class="col-xs-3">
<div class="row header"> <div class="row header">
{{ 'AVERAGE_TIME_TO_COMPLETE' | translate }} {{ 'AVERAGE_TIME_TO_COMPLETE' | translate }}
@ -210,10 +204,7 @@
</div> </div>
</div> </div>
<div class="row col-xs-12 text-center" style="font-size:5em;"> <div class="col-xs-12 field-title-row">
Field Analytics
</div>
<div class="row col-xs-12 field-title-row">
<div class="col-xs-3"> <div class="col-xs-3">
<strong>{{ 'FIELD_TITLE' | translate }}</strong> <strong>{{ 'FIELD_TITLE' | translate }}</strong>
@ -230,7 +221,7 @@
</div> </div>
</div> </div>
<div class="row col-xs-12 field-detailed-row" ng-repeat="fieldStats in myform.analytics.fields"> <div class="col-xs-12 field-detailed-row" ng-repeat="fieldStats in myform.analytics.fields">
<div class="col-xs-3"> <div class="col-xs-3">
{{fieldStats.field.title}} {{fieldStats.field.title}}
@ -247,9 +238,6 @@
</div> </div>
</div> </div>
<br> <br>
<div class="row col-xs-12 header-numbers text-center" style="font-size:5em;">
Responses Table
</div>
<div class="row table-tools"> <div class="row table-tools">
<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();">

View file

@ -4,6 +4,13 @@
<section data-ng-controller="ListFormsController as ctrl" data-ng-init="findAll()" class="container"> <section data-ng-controller="ListFormsController as ctrl" data-ng-init="findAll()" class="container">
<br> <br>
<div class="row">
<div class="col-xs-4 col-xs-offset-4">
<h3 class="text-center forms-list-title">
My Forms
</h3>
</div>
</div>
<div class="row"> <div class="row">
<div ng-click="openCreateModal()" class="col-xs-6 col-xs-offset-3 col-sm-4 col-sm-offset-1 col-md-3 col-md-offset-1 form-item create-new"> <div ng-click="openCreateModal()" class="col-xs-6 col-xs-offset-3 col-sm-4 col-sm-offset-1 col-md-3 col-md-offset-1 form-item create-new">
<div class="title-row col-xs-12"> <div class="title-row col-xs-12">
@ -47,8 +54,8 @@
</form> </form>
<div data-ng-repeat="form in myforms" <div data-ng-repeat="form in myforms"
ng-style="{ 'background-color': form.design.colors.backgroundColor, 'color': form.design.colors.answerColor }" class="col-xs-6 col-xs-offset-3 col-sm-4 col-sm-offset-1 col-md-3 col-md-offset-1 form-item container"
class="col-xs-6 col-xs-offset-3 col-sm-4 col-sm-offset-1 col-md-3 col-md-offset-1 form-item container"> ng-class="{'paused': !form.isLive}">
<div class="row"> <div class="row">
<span class="pull-right"> <span class="pull-right">
@ -59,20 +66,18 @@
<div class="row"> <div class="row">
<a data-ng-href="#!/forms/{{form._id}}/admin/create" <a data-ng-href="#!/forms/{{form._id}}/admin/create"
ng-style="{ 'color': form.design.colors.answerColor }"
class="title-row col-xs-12"> class="title-row col-xs-12">
<h4 class="list-group-item-heading" data-ng-bind="form.title"></h4> <h4 class="list-group-item-heading" data-ng-bind="form.title"></h4>
</a> </a>
</div> <div class="col-xs-12 responses-row">
<div class="row footer">
<div class="col-xs-12 details-row">
<small class="list-group-item-text"> <small class="list-group-item-text">
{{ 'CREATED_ON' | translate }} <span> {{ form.submissions.length }} responses </span>
<span data-ng-bind="form.created | date:'shortDate'"></span> </small>
<br>
<br>
<small ng-if="!form.isLive" class="list-group-item-text">
<span> Form Paused </span>
</small> </small>
</div> </div>
</div> </div>
</div> </div>

0
public/modules/forms/base/bower.json Executable file → Normal file
View file

View file

@ -1,3 +1,19 @@
/* Custom Tab CSS */
.nav.nav-pills.nav-stacked {
width: 16.66666667%;
float: left;
position: relative;
min-height: 1px;
padding-right: 15px;
}
div.tab-content {
width: 83.33333333%;
position: relative;
min-height: 1px;
float:left;
padding-top: 0!important;
}
.panel-default.startPage { .panel-default.startPage {
border-style: dashed; border-style: dashed;
border-color: #a9a9a9; border-color: #a9a9a9;
@ -302,6 +318,8 @@ div.config-form .row.field {
} }
.admin-form .page-header { .admin-form .page-header {
border: none; border: none;
margin-top: none;
margin-bottom: none;
} }
/*Styles for admin view tabs */ /*Styles for admin view tabs */
@ -419,16 +437,26 @@ section.public-form .btn {
} }
.form-item { .form-item {
border-radius: 5px;
text-align: center; text-align: center;
border-bottom: 6px inset #ccc;
background-color: #eee; background-color: #eee;
width: 180px; width: 180px;
/*width:100%;*/
position: relative; position: relative;
height: 215px; height: 215px;
/*padding-bottom: 25%;*/
margin-bottom: 45px; margin-bottom: 45px;
} }
.form-item.paused {
background-color: red;
color: white;
}
.form-item.paused:hover {
background-color: darkred;
color: white;
}
.form-item.create-new input[type='text']{ .form-item.create-new input[type='text']{
width: inherit; width: inherit;
color:black; color:black;
@ -436,17 +464,22 @@ section.public-form .btn {
} }
.form-item.create-new { .form-item.create-new {
background-color: rgb(131,131,131); background-color: #3FA2F7;
color: white; color: white;
} }
.form-item.create-new:hover {
background-color: #276496;
}
/*CREATE-NEW FORM MODAL*/ /*CREATE-NEW FORM MODAL*/
.form-item.create-new.new-form { .form-item.create-new.new-form {
background-color: rgb(300,131,131); background-color: rgb(300,131,131);
z-index: 11; z-index: 11;
} }
.form-item.create-new.new-form:hover { .form-item.create-new.new-form:hover {
background-color: rgb(300,100,100); background-color: #3079b5;
} }
.form-item.new-form input[type='text'] { .form-item.new-form input[type='text'] {
margin-top:0.2em; margin-top:0.2em;
@ -513,10 +546,23 @@ section.public-form .btn {
.activeField input { .activeField input {
background-color: transparent; background-color: transparent;
} }
h3.forms-list-title {
color: #3FA2F7;
weight: 600;
margin-bottom: 3em;
}
.form-item {
color: #71AADD;
background-color: #E4F1FD;
}
.form-item:hover {
background-color: #3FA2F7;
color: #23527C;
}
.form-item:hover, .form-item.create-new:hover { .form-item.create-new:hover {
border-bottom: 8px inset #ccc;
background-color: #d9d9d9; background-color: #d9d9d9;
color: white;
} }
.form-item.create-new:hover { .form-item.create-new:hover {
@ -529,15 +575,17 @@ section.public-form .btn {
left: 30%; left: 30%;
} }
.form-item .title-row{ .form-item .title-row {
position: relative; position: relative;
top: 15px; top: 15px;
padding-top:3em; padding-top:3em;
padding-bottom:3.65em; padding-bottom:1em;
} }
.form-item .title-row h4 { .form-item .title-row h4 {
font-size: 1.3em; font-size: 1.3em;
} }
.form-item .responses-row {
}
.form-item.create-new .title-row{ .form-item.create-new .title-row{
padding: 0; padding: 0;

View file

@ -83,23 +83,24 @@ angular.module('forms').directive('fieldDirective', ['$http', '$compile', '$root
if(scope.field.fieldType === 'number' || scope.field.fieldType === 'textfield' || scope.field.fieldType === 'email' || scope.field.fieldType === 'link'){ if(scope.field.fieldType === 'number' || scope.field.fieldType === 'textfield' || scope.field.fieldType === 'email' || scope.field.fieldType === 'link'){
switch(scope.field.fieldType){ switch(scope.field.fieldType){
case 'textfield': case 'textfield':
scope.field.input_type = 'text'; scope.input_type = 'text';
break; break;
case 'email': case 'email':
scope.field.input_type = 'email'; scope.input_type = 'email';
scope.field.placeholder = 'joesmith@example.com'; scope.placeholder = 'joesmith@example.com';
break; break;
case 'number': case 'number':
scope.field.input_type = 'text'; scope.input_type = 'text';
scope.field.validateRegex = /^-?\d+$/; scope.validateRegex = /^-?\d+$/;
break; break;
default: default:
scope.field.input_type = 'url'; scope.input_type = 'url';
scope.field.placeholder = 'http://example.com'; scope.placeholder = 'http://example.com';
break; break;
} }
fieldType = 'textfield'; fieldType = 'textfield';
} }
var template = getTemplateUrl(fieldType); var template = getTemplateUrl(fieldType);
element.html(template).show(); element.html(template).show();
var output = $compile(element.contents())(scope); var output = $compile(element.contents())(scope);

View file

View file

View file

View file

View file

View file

@ -21,9 +21,9 @@
<div class="col-xs-12 field-input"> <div class="col-xs-12 field-input">
<input ng-style="{'color': design.colors.answerColor, 'border-color': design.colors.answerColor}" <input ng-style="{'color': design.colors.answerColor, 'border-color': design.colors.answerColor}"
name="{{field.fieldType}}{{index}}" name="{{field.fieldType}}{{index}}"
type="{{field.input_type}}" type="{{input_type}}"
ng-pattern="field.validateRegex" ng-pattern="validateRegex"
placeholder="{{field.placeholder}}" placeholder="{{placeholder}}"
ng-class="{ 'no-border': !!field.fieldValue }" ng-class="{ 'no-border': !!field.fieldValue }"
class="focusOn text-field-input" class="focusOn text-field-input"
ng-model="field.fieldValue" ng-model="field.fieldValue"
@ -47,7 +47,7 @@
</div> </div>
</div> </div>
<div> <div>
<div class="btn btn-lg btn-default hidden-xs" <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)"> style="padding: 4px; margin-top:8px; background: rgba(255,255,255,0.5)">
<button ng-disabled="!field.fieldValue || forms.myForm.{{field.fieldType}}{{$index}}.$invalid" <button ng-disabled="!field.fieldValue || forms.myForm.{{field.fieldType}}{{$index}}.$invalid"
ng-style="{'background-color':design.colors.buttonColor, 'color':design.colors.buttonTextColor}" ng-style="{'background-color':design.colors.buttonColor, 'color':design.colors.buttonTextColor}"

View file

@ -13,7 +13,7 @@
// Create a controller method for sending visitor data // Create a controller method for sending visitor data
function send(form, lastActiveIndex, timeElapsed) { function send(form, lastActiveIndex, timeElapsed) {
// Create a new message object // Create a new message object
var visitorData = { /*var visitorData = {
referrer: document.referrer, referrer: document.referrer,
isSubmitted: form.submitted, isSubmitted: form.submitted,
formId: form._id, formId: form._id,
@ -42,15 +42,15 @@
} }
console.log(visitorData.deviceType); console.log(visitorData.deviceType);
Socket.emit('form-visitor-data', visitorData); Socket.emit('form-visitor-data', visitorData);
}); });*/
} }
function init(){ function init(){
// Make sure the Socket is connected // Make sure the Socket is connected
if (!Socket.socket) { /*if (!Socket.socket) {
Socket.connect(); Socket.connect();
} }*/
} }
var service = { var service = {

View file

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

View file

@ -4,7 +4,8 @@ angular.module('users').config(['$translateProvider', function ($translateProvid
$translateProvider.translations('en', { $translateProvider.translations('en', {
ACCESS_DENIED_TEXT: 'You need to be logged in to access this page', ACCESS_DENIED_TEXT: 'You need to be logged in to access this page',
USERNAME_LABEL: 'Username or Email', USERNAME_OR_EMAIL_LABEL: 'Username or Email',
USERNAME_LABEL: 'Username',
PASSWORD_LABEL: 'Password', PASSWORD_LABEL: 'Password',
CURRENT_PASSWORD_LABEL: 'Current Password', CURRENT_PASSWORD_LABEL: 'Current Password',
NEW_PASSWORD_LABEL: 'New Password', NEW_PASSWORD_LABEL: 'New Password',
@ -21,6 +22,9 @@ angular.module('users').config(['$translateProvider', function ($translateProvid
SIGNIN_HEADER_TEXT: 'Sign in', SIGNIN_HEADER_TEXT: 'Sign in',
SIGNUP_ERROR_TEXT: 'Couldn\'t complete registration due to errors', SIGNUP_ERROR_TEXT: 'Couldn\'t complete registration due to errors',
ENTER_ACCOUNT_EMAIL: 'Enter your account email.',
RESEND_VERIFICATION_EMAIL: 'Resend Verification Email',
SAVE_CHANGES: 'Save Changes',
UPDATE_PROFILE_BTN: 'Update Profile', UPDATE_PROFILE_BTN: 'Update Profile',
PROFILE_SAVE_SUCCESS: 'Profile saved successfully', PROFILE_SAVE_SUCCESS: 'Profile saved successfully',

View file

@ -2,6 +2,7 @@
angular.module('users').controller('PasswordController', ['$scope', '$stateParams', '$state', 'User', angular.module('users').controller('PasswordController', ['$scope', '$stateParams', '$state', 'User',
function($scope, $stateParams, $state, User) { function($scope, $stateParams, $state, User) {
$scope.error = ''; $scope.error = '';
// Submit forgotten password account id // Submit forgotten password account id

View file

@ -1,8 +1,9 @@
'use strict'; 'use strict';
angular.module('users').controller('SettingsController', ['$scope', '$rootScope', '$http', '$state', 'Users', angular.module('users').controller('SettingsController', ['$scope', '$rootScope', '$http', '$state', 'Users', 'Auth',
function($scope, $rootScope, $http, $state, Users) { function($scope, $rootScope, $http, $state, Users, Auth) {
$scope.user = $rootScope.user;
$scope.user = Auth.currentUser;
// Check if there are additional accounts // Check if there are additional accounts
$scope.hasConnectedAdditionalSocialAccounts = function(provider) { $scope.hasConnectedAdditionalSocialAccounts = function(provider) {
@ -12,6 +13,10 @@ angular.module('users').controller('SettingsController', ['$scope', '$rootScope'
return false; return false;
}; };
$scope.cancel = function(){
$scope.user = Auth.currentUser;
};
// Check if provider is already in use with current user // Check if provider is already in use with current user
$scope.isConnectedSocialAccount = function(provider) { $scope.isConnectedSocialAccount = function(provider) {
return $scope.user.provider === provider || ($scope.user.additionalProvidersData && $scope.user.additionalProvidersData[provider]); return $scope.user.provider === provider || ($scope.user.additionalProvidersData && $scope.user.additionalProvidersData[provider]);

View file

@ -6,7 +6,7 @@ section.auth {
left: 0; left: 0;
width: 100%; width: 100%;
color: white; color: white;
background-color: #1e5799; /* Old browsers */ background-color: #50B5C1; /* Old browsers */
background: -moz-linear-gradient(137deg, #50B5C1 0%, #6450A0 85%); /* FF3.6-15 */ background: -moz-linear-gradient(137deg, #50B5C1 0%, #6450A0 85%); /* FF3.6-15 */
background: -webkit-linear-gradient(137deg, #50B5C1 0%, #6450A0 85%); /* Chrome10-25,Safari5.1-6 */ background: -webkit-linear-gradient(137deg, #50B5C1 0%, #6450A0 85%); /* Chrome10-25,Safari5.1-6 */
background: linear-gradient(137deg, #50B5C1 0%, #6450A0 85%); background: linear-gradient(137deg, #50B5C1 0%, #6450A0 85%);
@ -63,18 +63,31 @@ section.auth {
text-transform: uppercase; text-transform: uppercase;
} }
section.auth .btn-signup {
.btn-rounded.btn-signup {
background-color: #FFD747; background-color: #FFD747;
color: #896D0B; color: #896D0B;
border: 2px #FFD747 solid; border: 2px #FFD747 solid;
width: 100%;
} }
section.auth .btn-signup:hover {
.btn-rounded.btn-signup:hover {
color: #FFD747; color: #FFD747;
background-color: #896D0B; background-color: #896D0B;
border: 2px #896D0B solid; border: 2px #896D0B solid;
} }
.btn-rounded.btn-default {
background-color: transparent;
color: white;
border: 2px white solid;
}
.btn-rounded.btn-default:focus, .btn-rounded.btn-default:hover {
color: #6450A0;
background-color: white;
border-color: white;
}
@media (min-width: 992px) { @media (min-width: 992px) {
.nav-users { .nav-users {
position: fixed; position: fixed;
@ -90,10 +103,12 @@ section.auth {
position: absolute; position: absolute;
} }
input.form-control { section.auth input.form-control {
border: none; border: none;
}
input.form-control {
border-radius: 4px; border-radius: 4px;
color: white;
box-shadow: none; box-shadow: none;
font-size: 18px; font-size: 18px;
padding: 30px 20px; padding: 30px 20px;

View file

@ -23,7 +23,6 @@
<div class="col-md-4 col-md-offset-4"> <div class="col-md-4 col-md-offset-4">
<div class="col-md-12 text-center" style="padding-bottom: 50px;"> <div class="col-md-12 text-center" style="padding-bottom: 50px;">
<img src="/static/modules/core/img/logo_white.svg" height="100px"> <img src="/static/modules/core/img/logo_white.svg" height="100px">
</div> </div>
<div class="col-md-12"> <div class="col-md-12">
<form class="signin form-horizontal" autocomplete="off"> <form class="signin form-horizontal" autocomplete="off">
@ -33,14 +32,14 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<!--<label for="username">{{ 'USERNAME_LABEL' | translate }}</label>--> <!--<label for="username">{{ 'USERNAME_LABEL' | translate }}</label>-->
<input type="text" id="username" name="username" class="form-control" data-ng-model="credentials.username" placeholder="{{ 'USERNAME_LABEL' | translate }}" ng-minlength="4"> <input type="text" id="username" name="username" class="form-control" data-ng-model="credentials.username" placeholder="{{ 'USERNAME_OR_EMAIL_LABEL' | translate }}" ng-minlength="4">
</div> </div>
<div class="form-group"> <div class="form-group">
<!--<label for="password">{{ 'PASSWORD_LABEL' | translate }}</label>--> <!--<label for="password">{{ 'PASSWORD_LABEL' | translate }}</label>-->
<input type="password" id="password" name="password" class="form-control" data-ng-model="credentials.password" placeholder="{{ 'PASSWORD_LABEL' | translate }}" ng-minlength="4"> <input type="password" id="password" name="password" class="form-control" data-ng-model="credentials.password" placeholder="{{ 'PASSWORD_LABEL' | translate }}" ng-minlength="4">
</div> </div>
<div class="form-group"> <div class="form-group">
<button class="btn btn-signup" ng-click="signin()">{{ 'SIGNIN_BTN' | translate }}</button> <button class="btn btn-signup btn-rounded btn-block" ng-click="signin()">{{ 'SIGNIN_BTN' | translate }}</button>
</div> </div>
<div class="text-center forgot-password"> <div class="text-center forgot-password">
<a ui-sref="forgot">{{ 'FORGOT_PASSWORD_LINK' | translate }}</a> <a ui-sref="forgot">{{ 'FORGOT_PASSWORD_LINK' | translate }}</a>

View file

@ -29,7 +29,7 @@
<p> <p>
<strong>{{ 'BEFORE_YOU_CONTINUE' | translate }}</strong> <a href="mail:polydaic@gmail.com">polydaic@gmail.com</a></p> <strong>{{ 'BEFORE_YOU_CONTINUE' | translate }}</strong> <a href="mail:polydaic@gmail.com">polydaic@gmail.com</a></p>
<div class="text-center form-group"> <div class="text-center form-group">
<button type="submit" class="btn btn-large btn-primary"> <button type="submit" class="btn btn-primary btn-rounded">
<a href="/#!/" style="color: white; text-decoration: none;">Continue</a> <a href="/#!/" style="color: white; text-decoration: none;">Continue</a>
</button> </button>
</div> </div>

View file

@ -30,29 +30,6 @@
{{'SIGNUP_ERROR_TEXT' | translate}}: <br> {{'SIGNUP_ERROR_TEXT' | translate}}: <br>
<strong data-ng-bind="error"></strong> <strong data-ng-bind="error"></strong>
</div> </div>
<!--<div class="form-group">
<label for="firstName">{{ 'FIRST_NAME_LABEL' | translate }}</label>
<input type="text" ng-pattern="/^[a-zA-Z0-9 \-.]*$/" required id="firstName" name="firstName" class="form-control" ng-model="credentials.firstName" placeholder="First Name">
</div>
<div class="form-group">
<label for="lastName">{{ 'LAST_NAME_LABEL' | translate }}</label>
<input type="text" ng-pattern="/^[a-zA-Z0-9 \-.]*$/" required id="lastName" name="lastName" class="form-control" ng-model="credentials.lastName" placeholder="Last Name">
</div>-->
<!--<div class="row form-group">
<div class="field-title">
<b>{{ 'LANGUAGE_LABEL' | translate }}</b>
</div>
<div class="field-input">
<select ng-model="user.language" required>
<option ng-repeat="language in languages"
ng-selected="language == user.language"
value="{{language}}">
{{language}}
</option>
</select>
</div>
</div>
<hr>-->
<div class="form-group"> <div class="form-group">
<!--<label for="username">{{ 'USERNAME_LABEL' | translate }}</label>--> <!--<label for="username">{{ 'USERNAME_LABEL' | translate }}</label>-->
<input type="text" id="username" name="username" class="form-control" ng-pattern="languageRegExp" ng-minlength="4" ng-model="credentials.username" placeholder="{{ 'USERNAME_LABEL' | translate }}" ng-minlength="4"> <input type="text" id="username" name="username" class="form-control" ng-pattern="languageRegExp" ng-minlength="4" ng-model="credentials.username" placeholder="{{ 'USERNAME_LABEL' | translate }}" ng-minlength="4">
@ -66,7 +43,7 @@
<input type="password" id="password" name="password" class="form-control" ng-model="credentials.password" placeholder="{{ 'PASSWORD_LABEL' | translate }}" ng-minlength="4"> <input type="password" id="password" name="password" class="form-control" ng-model="credentials.password" placeholder="{{ 'PASSWORD_LABEL' | translate }}" ng-minlength="4">
</div> </div>
<div class="text-center form-group"> <div class="text-center form-group">
<button type="submit" class="btn btn-signup">{{ 'SIGNUP_BTN' | translate }}</button> <button type="submit" class="btn btn-signup btn-rounded btn-block">{{ 'SIGNUP_BTN' | translate }}</button>
</div> </div>
</fieldset> </fieldset>

View file

@ -1,14 +1,17 @@
<section class="auth row" data-ng-controller="PasswordController"> <section class="auth valign-wrapper" data-ng-controller="PasswordController">
<h3 class="col-md-12 text-center">{{ 'PASSWORD_RESTORE_HEADER' | translate }}</h3> <div class="row valign">
<p class="small text-center">{{ 'ENTER_YOUR_EMAIL' | translate }}</p> <div class="col-md-4 col-md-offset-4">
<div class="col-xs-offset-2 col-xs-8 col-md-offset-3 col-md-6"> <div class="col-md-12 text-center" style="padding-bottom: 50px;">
<form data-ng-submit="askForPasswordReset()" class="signin form-horizontal" autocomplete="off"> <img src="/static/modules/core/img/logo_white.svg" height="100px">
</div>
<div class="col-md-12">
<form data-ng-submit="askForPasswordReset()" autocomplete="off">
<fieldset> <fieldset>
<div class="form-group"> <div class="form-group">
<input type="text" id="username" name="username" class="form-control" data-ng-model="credentials.username" placeholder="{{ 'USERNAME_LABEL' | translate }}"> <input type="text" id="username" name="username" class="form-control" data-ng-model="credentials.username" placeholder="{{ 'USERNAME_OR_EMAIL_LABEL' | translate }}">
</div> </div>
<div class="text-center form-group"> <div class="text-center form-group">
<button type="submit" class="btn btn-primary">{{ 'UPDATE_PASSWORD_LABEL' | translate }}</button> <button type="submit" class="btn btn-signup btn-rounded btn-block">{{ 'PASSWORD_RESTORE_HEADER' | translate }}</button>
</div> </div>
<div data-ng-show="error" class="text-center text-danger"> <div data-ng-show="error" class="text-center text-danger">
<strong>{{error}}</strong> <strong>{{error}}</strong>
@ -19,4 +22,6 @@
</fieldset> </fieldset>
</form> </form>
</div> </div>
</div>
</div>
</section> </section>

View file

@ -1,7 +1,7 @@
<header data-ng-include="'/static/modules/core/views/header.client.view.html'"></header> <header data-ng-include="'/static/modules/core/views/header.client.view.html'"></header>
<section class="row" data-ng-controller="SettingsController"> <section class="row" data-ng-controller="SettingsController">
<h3 class="col-xs-offset-1 col-xs-10 text-center">Edit your profile</h3> <h2 class="col-xs-offset-1 col-xs-10 text-center">Edit your profile</h2>
<div class="col-xs-offset-3 col-xs-6"> <div class="col-xs-offset-3 col-xs-6">
<form name="userForm" data-ng-submit="updateUserProfile(userForm.$valid)" class="signin form-horizontal" autocomplete="off"> <form name="userForm" data-ng-submit="updateUserProfile(userForm.$valid)" class="signin form-horizontal" autocomplete="off">
<fieldset> <fieldset>
@ -15,18 +15,18 @@
<div class="form-group row"> <div class="form-group row">
<div class="col-xs-7 field-title"> <div class="col-xs-7 field-title">
<b>{{ 'FIRST_NAME_LABEL' | translate }}</b> {{ 'FIRST_NAME_LABEL' | translate }}
</div> </div>
<div class="col-xs-12 field-input"> <div class="col-xs-12 field-input">
<input type="text" id="firstName" name="firstName" class="form-control" data-ng-model="user.firstName" placeholder="First Name" ng-pattern="/^[a-zA-Z0-9 \-.]*$/"> <input type="text" id="firstName" name="firstName" class="form-control" data-ng-model="user.firstName" ng-pattern="/^[\w0-9 \-.]*$/">
</div> </div>
</div> </div>
<div class="form-group row"> <div class="form-group row">
<div class="col-xs-7 field-title"> <div class="col-xs-7 field-title">
<b>Last Name</b> {{ 'LAST_NAME_LABEL' | translate }}
</div> </div>
<div class="col-xs-12 field-input"> <div class="col-xs-12 field-input">
<input type="text" id="lastName" name="lastName" class="form-control" data-ng-model="user.lastName" placeholder="Last Name" ng-pattern="/^[a-zA-Z0-9 \-.]*$/"> <input type="text" id="lastName" name="lastName" class="form-control" data-ng-model="user.lastName" ng-pattern="/^[\w0-9 \-.]*$/">
</div> </div>
</div> </div>
<div class="row"> <div class="row">
@ -34,7 +34,7 @@
</div> </div>
<div class="row form-group"> <div class="row form-group">
<div class="col-xs-7 field-title"> <div class="col-xs-7 field-title">
<b>{{ 'LANGUAGE_LABEL' | translate }}</b> {{ 'LANGUAGE_LABEL' | translate }}
</div> </div>
<div class="col-xs-12 field-input"> <div class="col-xs-12 field-input">
<select ng-model="user.language" required> <select ng-model="user.language" required>
@ -49,25 +49,28 @@
<div class="row form-group"> <div class="row form-group">
<div class="col-xs-7 field-title"> <div class="col-xs-7 field-title">
<b>Username</b> {{ 'USERNAME_LABEL' | translate }}
</div> </div>
<div class="col-xs-12 field-input"> <div class="col-xs-12 field-input">
<input type="text" id="username" name="username" class="form-control" data-ng-model="user.username" placeholder="Username"> <input type="text" id="username" name="username" class="form-control" data-ng-model="user.username">
</div> </div>
</div> </div>
<div class="row form-group"> <div class="row form-group">
<div class="col-xs-7 field-title"> <div class="col-xs-7 field-title">
<b>{{ 'EMAIL_LABEL' | translate }}</b> {{ 'EMAIL_LABEL' | translate }}
</div> </div>
<div class="col-xs-12 field-input"> <div class="col-xs-12 field-input">
<input type="email" id="email" name="email" class="form-control" data-ng-model="user.email" placeholder="Email"> <input type="email" id="email" name="email" class="form-control" data-ng-model="user.email">
</div> </div>
</div> </div>
<div class="text-center form-group"> <div class="text-center form-group">
<button type="submit" class="btn btn-large btn-primary" style="font-size:1.6em;"> <button type="submit" class="btn btn-signup btn-rounded">
{{ 'SUBMIT_BTN' | translate }} {{ 'SAVE_CHANGES' | translate }}
</button>
<button type="none" ng-click="cancel()" class="btn btn-rounded">
{{ 'CANCEL' | translate }}
</button> </button>
</div> </div>

View file

@ -1,37 +1,46 @@
<section class="auth" data-ng-controller="VerifyController"> <section class="auth valign-wrapper" data-ng-controller="VerifyController">
<section ng-if="!isResetSent">
<h3 class="col-md-12 text-center">Resend your account verification email</h3> <section class="row valign" ng-if="!isResetSent">
<p class="small text-center">Enter your account email.</p> <div class="col-md-4 col-md-offset-4">
<div class="col-md-12 text-center" style="padding-bottom: 50px;">
<img src="/static/modules/core/img/logo_white.svg" height="100px">
</div>
<div data-ng-show="error" class="text-center text-danger"> <div data-ng-show="error" class="text-center text-danger">
<strong>{{error}}</strong> <strong>{{error}}</strong>
</div> </div>
<div class="col-xs-offset-2 col-xs-8 col-md-offset-3 col-md-6"> <div class="col-md-12">
<form data-ng-submit="resendVerifyEmail()" class="signin form-horizontal" autocomplete="off"> <form data-ng-submit="resendVerifyEmail()" class="signin form-horizontal" autocomplete="off">
<fieldset> <fieldset>
<div class="form-group"> <div class="form-group">
<input type="text" id="email" name="email" class="form-control" data-ng-model="credentials.email" placeholder="bob@example.com"> <input type="text" id="email" name="email" class="form-control" data-ng-model="credentials.email" placeholder="{{ 'ENTER_ACCOUNT_EMAIL' | translate}}">
</div> </div>
<div class="text-center form-group"> <div class="text-center form-group">
<button type="submit" class="btn btn-primary" ng-click="resendVerifyEmail()">{{ 'SUBMIT_BTN' | translate }}</button> <button type="submit" class="btn btn-signup btn-rounded btn-block" ng-click="resendVerifyEmail()">{{ 'RESEND_VERIFICATION_EMAIL' | translate }}</button>
</div> </div>
</fieldset> </fieldset>
</form> </form>
</div> </div>
</div>
</section> </section>
<section ng-if="isResetSent"> <section class="row valign" ng-if="isResetSent">
<h3 class="col-xs-offset-2 col-xs-8 col-md-offset-3 col-md-6 text-center">Verification Email has been Sent </h3> <div class="col-md-4 col-md-offset-4">
<div class="col-xs-offset-2 col-xs-8 col-md-offset-3 col-md-6"> <div class="col-md-12 text-center" style="padding-bottom: 50px;">
<img src="/static/modules/core/img/logo_white.svg" height="100px">
</div>
<h3 class="col-md-12 text-center">Verification Email has been Sent</h3>
<div class="col-md-12">
<h2> <h2>
{{ 'VERIFICATION_EMAIL_SENT' | translate }} {{username}}. {{ 'VERIFICATION_EMAIL_SENT' | translate }} {{username}}.
<br> {{ 'NOT_ACTIVATED_YET' | translate }} <br> {{ 'NOT_ACTIVATED_YET' | translate }}
</h2> </h2>
<p> {{ 'CHECK_YOUR_EMAIL' | translate }} <a href="mail:polydaic@gmail.com">polydaic@gmail.com</a></p> <p> {{ 'CHECK_YOUR_EMAIL' | translate }} <a href="mail:polydaic@gmail.com">polydaic@gmail.com</a></p>
<div class="text-center form-group"> <div class="text-center form-group">
<button type="submit" class="btn btn-large btn-primary"> <button type="submit" class="btn btn-large btn-primary btn-rounded">
<a href="/#!/" style="color:white;">Continue</a> <a href="/#!/" style="color:white;">Continue</a>
</button> </button>
</div> </div>
</div> </div>
</div>
</section> </section>
</section> </section>

View file

@ -1,14 +1,38 @@
<section class="auth" data-ng-controller="VerifyController" ng-init="validateVerifyToken()"> <section class="auth" data-ng-controller="VerifyController" ng-init="validateVerifyToken()">
<section class="row text-center" ng-if="isResetSent"> <section class="row text-center" ng-if="isResetSent">
<h3 class="col-md-12"></h3> <div class="col-md-4 col-md-offset-4">
<a href="/#!/signin" class="col-md-12">{{ 'CONTINUE_TO_LOGIN' | translate }}</a> <div class="col-md-12 text-center" style="padding-bottom: 50px;">
<img src="/static/modules/core/img/logo_white.svg" height="100px">
</div>
<h3 class="col-md-12">
{{ 'VERIFY_SUCCESS' | translate }}
</h3>
<div class="col-md-12">
<a href="/#!/signin" class="btn btn-signup btn-rounded btn-block">{{ 'CONTINUE_TO_LOGIN' | translate }}</a>
</div>
</div>
</section> </section>
<section class="row text-center" ng-if="!isResetSent"> <section class="row text-center" ng-if="!isResetSent">
<h3 class="col-md-12">{{ 'VERIFY_ERROR' | translate }}</h3> <div class="col-md-4 col-md-offset-4">
<a href="/#!/verify" class="col-md-6">{{ 'REVERIFY_ACCOUNT_LINK' | translate }}</a> <div class="col-md-12 text-center" style="padding-bottom: 50px;">
<a href="/#!/signin" class="col-md-6">{{ 'SIGNIN_BTN' | translate }}</a> <img src="/static/modules/core/img/logo_white.svg" height="100px">
</div>
<h3 class="col-md-12">
{{ 'VERIFY_ERROR' | translate }}
</h3>
<div class="col-md-12">
<a href="/#!/verify" class="btn btn-rounded btn-default">
{{ 'REVERIFY_ACCOUNT_LINK' | translate }}
</a>
</div>
<div class="col-sm-12">
<a href="/#!/signin" class="btn btn-rounded btn-primary">
{{ 'SIGNIN_BTN' | translate }}
</a>
</div>
</div>
</section> </section>
</section> </section>

882
public/swagger.json Normal file
View file

@ -0,0 +1,882 @@
{
"swagger": "2.0",
"info": {
"version": "1.0.0",
"title": "TellForm API",
"contact": {
"name": "TellForm Team",
"email": "team@tellform.com"
}
},
"externalDocs": {
"description": "Find out how to host your own TellForm instance.",
"url": "https://github.com/whitef0x0/tellform"
},
"host": "api.tellform.com",
"basePath": "/",
"schemes": [
"http",
"https"
],
"tags": [
{
"name": "form",
"description": "Everything about your Forms"
},
{
"name": "user",
"description": "Everything about your Account"
}
],
"securityDefinitions": {
"api_key": {
"type": "apiKey",
"name": "apikey",
"in": "header"
}
},
"paths": {
"/forms": {
"get": {
"tags": [
"form"
],
"summary": "Find all forms",
"responses": {
"405": {
"description": "Missing Form Input"
},
"400": {
"description": "Form is Invalid"
},
"404": {
"description": "Form not Found"
},
"200": {
"description": "forms response",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/Form"
}
}
}
},
"security": [
{
"api_key": []
}
]
}
},
"/form/:form_id": {
"get": {
"tags": [
"form"
],
"summary": "Find form by ID",
"responses": {
"200": {
"description": "forms response",
"schema": {
"$ref": "#/definitions/Form"
},
"headers": {
"x-expires": {
"type": "string"
}
}
}
},
"security": [
{
"api_key": []
}
]
},
"post": {
"tags": [
"form"
],
"summary": "Create a new form",
"description": "Create and save a new form",
"operationId": "addForm",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"parameters": [
{
"in": "body",
"name": "body",
"description": "Form object that is to be created",
"required": true,
"schema": {
"$ref": "#/definitions/Form"
}
}
],
"responses": {
"405": {
"description": "Missing Form Input"
},
"400": {
"description": "Form is Invalid"
},
"404": {
"description": "Form not Found"
},
"200": {
"description": "successful operation",
"schema": {
"$ref": "#/definitions/Form"
}
}
},
"security": [
{
"api_key": []
}
],
"x-code-samples": [
]
},
"put": {
"tags": [
"form"
],
"summary": "Update an existing form",
"description": "",
"operationId": "updateForm",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"parameters": [
{
"in": "body",
"name": "form",
"description": "Form object that needs to be updated",
"required": true,
"schema": {
"$ref": "#/definitions/Form"
}
}
],
"responses": {
"405": {
"description": "Missing Form Input"
},
"400": {
"description": "Form is Invalid"
},
"404": {
"description": "Form not Found"
},
"200": {
"description": "successful operation",
"schema": {
"$ref": "#/definitions/Form"
}
}
},
"security": [
{
"api_key": []
}
],
"x-code-samples": [
]
}
},
"/users/me": {
"get": {
"tags": [
"user"
],
"summary": "Retrieve current User",
"description": "",
"operationId": "getUser",
"produces": [
"application/json"
],
"responses": {
"500": {
"description": "Could not Update User"
},
"401": {
"description": "User is not Signed in"
},
"403": {
"description": "User is not Authorized"
},
"404": {
"description": "User does not exsit"
},
"200": {
"description": "successful operation",
"schema": {
"$ref": "#/definitions/User"
}
}
},
"security": [
{
"api_key": []
}
]
}
},
"/users": {
"put": {
"tags": [
"user"
],
"summary": "Update the current user",
"description": "",
"operationId": "updateUser",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"parameters": [
{
"in": "body",
"name": "body",
"description": "User object that needs to be updated",
"required": true,
"schema": {
"$ref": "#/definitions/User"
}
}
],
"responses": {
"500": {
"description": "Could not Update User"
},
"401": {
"description": "User is not Signed in"
},
"403": {
"description": "User is not Authorized"
},
"404": {
"description": "User does not exsit"
},
"200": {
"description": "successful operation",
"schema": {
"$ref": "#/definitions/User"
}
}
},
"security": [
{
"api_key": []
}
],
"x-code-samples": [
]
}
}
},
"definitions": {
"User": {
"type": "object",
"required": [
"language",
"email",
"username"
],
"properties": {
"firstName": {
"type": "string",
"description": "First name of User",
"example": "John"
},
"lastName": {
"type": "string",
"description": "First name of User",
"example": "Doe"
},
"language": {
"type": "string",
"enum": [
"en",
"fr",
"es",
"it",
"de"
],
"default": "en",
"required": "User must have a language",
"description": "Language of User (for internationalization)",
"example": "fr"
},
"email": {
"type": "string",
"format": "email",
"required": "User email cannot be blank",
"unique": "true",
"description": "Email of User",
"example": "john.doe@somewhere.com"
},
"username": {
"type": "string",
"required": "Username cannot be blank",
"unique": "true",
"description": "Username of User",
"example": "johndoe"
},
"roles": {
"type": "array",
"items": {
"type": "string",
"enum": [
"user",
"admin",
"superuser"
]
},
"default": [ "user" ],
"description": "Security Roles of User"
}
},
"lastModified": {
"type": "date",
"description": "Date that user was last modified",
"example": "2016-08-26T20:19:30.146Z"
},
"created": {
"type": "date",
"description": "Date that user was created on",
"example": "5dHuKJgeCZmFOdJTnmg0lrxApmz0tbbBrM59rTv4k79"
},
"resetPasswordToken": {
"type": "string",
"description": "Reset password token of User",
"example": "5dHuKJgeCZmFOdJTnmg0lrxApmz0tbbBrM59rTv4k79"
},
"resetPasswordExpires": {
"type": "date",
"example": "2016-08-26T20:19:30.146Z",
"description": "Date that the User's password reset token expires"
},
"token": {
"type": "string",
"description": "Verification token of User",
"example": "5dHuKJgeCZmFOdJTnmg0lrxApmz0tbbBrM59rTv4k79"
},
"apiKey": {
"type": "string",
"unique": true,
"index": true,
"sparse": true,
"description": "API Key of User",
"example": "5dHuKJgeCZmFOdJTnmg0lrxApmz0tbbBrM59rTv4k79"
}
},
"LogicJump": {
"expressionString": {
"type": "string",
"enum": [
"field == static",
"field != static",
"field > static",
"field >= static",
"field <= static",
"field < static",
"field contains static",
"field !contains static",
"field begins static",
"field !begins static",
"field ends static",
"field !ends static"
]
},
"fieldA": {
"$ref": "#/definitions/FormField"
},
"valueB": {
"type": "string"
},
"jumpTo": {
"$ref": "#/definitions/FormField"
}
},
"FieldOption": {
"type": "object",
"properties": {
"option_id": {
"type": "number"
},
"option_title": {
"type": "string"
},
"option_value": {
"type": "string",
"trim": true
}
}
},
"RatingField": {
"type": "object",
"properties": {
"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": "array",
"items": {
"type": "string"
}
}
}
},
"FormField": {
"required": [
"title",
"fieldType"
],
"properties": {
"isSubmission": {
"type": "boolean",
"default": false,
"description": "Specifies whether Field is part of a Submission or not",
"example": true
},
"submissionId": {
"type": "string",
"description": "ID of Submission that this Field belongs to",
"example": "57bca0969ca8e18b825bcc2b"
},
"title": {
"type": "string",
"trim": true,
"required": "Field Title cannot be blank",
"description": "Description of Field",
"example": "Your Current University"
},
"description": {
"type": "string",
"default": "",
"description": "Description of Field",
"example": "Please do not use abbreviations for your school name"
},
"logicJump": {
"$ref": "#/definitions/FormField"
},
"ratingOptions": {
"type": "#/definitions/RatingField"
},
"fieldOptions": {
"type": "array",
"items": {
"type": "FieldOption"
}
},
"required": {
"type": "boolean",
"default": true,
"description": "Specifies whether Field is required",
"example": true
},
"disabled": {
"type": "boolean",
"default": false,
"description": "Specifies whether Field is disabled",
"example": true
},
"deletePreserved": {
"type": "boolean",
"default": false,
"description": "Specifies whether Field should be preserved if it is deleted",
"example": false
},
"validFieldTypes": {
"type": "array",
"items": {
"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",
"stripe",
"number"
],
"description": "Type of Field",
"example": "textfield"
},
"fieldValue": {
"type": "string",
"description": "Value of Field",
"example": "University of British Columbia"
}
}
},
"VisitorData": {
"type": "object",
"properties": {
"referrer": {
"type": "string",
"description": "Referring site of Form Visitor",
"example": "http://google.com"
},
"lastActiveField": {
"type": "string",
"description": "ID of Last Active Field",
"example": "57bca0969ca8e18b825bcc2b"
},
"timeElapsed": {
"type": "number",
"description": "Time Elasped for Visitor on Form (in seconds)",
"example": "333.33"
},
"isSubmitted": {
"type": "boolean",
"description": "Specifies whether user submitted form before leaving page",
"example": false
},
"language": {
"type": "string",
"description": "Language of User using form",
"example": "en"
},
"ipAddr": {
"type": "string",
"default": "",
"description": "IP Address of User",
"example": "324.332.322.333"
},
"deviceType": {
"type": "string",
"enum": [
"desktop",
"phone",
"tablet",
"other"
],
"default": "other",
"description": "Device Type of User",
"example": "phone"
},
"userAgent": {
"type": "string",
"description": "User Agent of User",
"example": "Mozilla/5.0 (Linux; <Android Version>; <Build Tag etc.>) AppleWebKit/<WebKit Rev> (KHTML, like Gecko) Chrome/<Chrome Rev> Mobile Safari/<WebKit Rev>"
}
}
},
"Button": {
"type": "object",
"properties": {
"url": {
"type": "string",
"format": "url",
"description": "URL of Button Link",
"example": "http://you-are-awesome.com"
},
"action": {
"type": "string",
"description": "Angular Action fired during Button click",
"example": "openModal()"
},
"text": {
"type": "string",
"description": "Text of Button",
"example": "Go to HomePage"
},
"bgColor": {
"type": "string",
"pattern": "/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/",
"default": "#5bc0de",
"description": "Background Color of Button (in hex)",
"example": "#5bc0de"
},
"color": {
"type": "string",
"pattern": "/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/",
"default": "#ffffff",
"description": "Font Color of Button (in hex)",
"example": "#ffffff"
}
}
},
"FormSubmission": {
"type": "object",
"required": [
"language",
"admin",
"title"
],
"properties": {
"title": {
"type": "string",
"required": "Form Title cannot be blank"
},
"language": {
"type": "string",
"enum": [
"en",
"fr",
"es",
"it",
"de"
],
"default": "en",
"required": "Form must have a language"
},
"admin": {
"$ref": "#/definitions/User",
},
"ipAddr": {
"type": "string"
},
"geoLocation": {
"type": "object",
"properties": {
"Country": {
"type": "string"
},
"Region": {
"type": "string"
},
"City": {
"type": "string"
}
}
},
"device": {
"type": "object",
"properties": {
"type": {
"type": "string"
},
"name": {
"type": "string"
}
}
},
"pdfFilePath": {
"type": "string"
},
"pdf": {
"type": "string"
},
"timeElapsed": {
"type": "number"
},
"percentageComplete": {
"type": "number"
}
}
},
"Form": {
"type": "object",
"required": [
"language",
"admin",
"title"
],
"properties": {
"title": {
"type": "string",
"required": "Form Title cannot be blank",
"description": "Public Title of Form",
"example": "UBC CPSC221 Course Waitlist Form"
},
"language": {
"type": "string",
"enum": [
"en",
"fr",
"es",
"it",
"de"
],
"default": "en",
"required": "Form must have a language",
"description": "Language of Form",
"example": "en"
},
"analytics": {
"type": "object",
"description": "Analytics of Form",
"properties": {
"gaCode": {
"type": "string",
"description": "Analytics of Form",
"example": "UA-000000-01"
},
"visitors": {
"type": "array",
"items": {
"type": "#/definitions/VisitorData"
}
}
}
},
"form_fields": {
"type": "array",
"items": {
"type": "FormField"
}
},
"submissions": {
"type": "array",
"items": {
"$ref": "#/definitions/FormSubmission"
}
},
"admin": {
"type": "User",
"description": "User that this Form belongs to"
},
"startPage": {
"type": "object",
"properties": {
"showStart": {
"type": "boolean",
"default": false,
"description": "Specifies whether Form StarPage should be displayed",
"example": false
},
"introTitle": {
"type": "string",
"default": "Welcome to Form",
"description": "Title of Form's StartPage",
"example": "Welcome to our Awesome Form!"
},
"introParagraph": {
"type": "string",
"description": "Introduction paragraph for Form's StartPage.",
"example": "Welcome to our Awesome Form!"
},
"introButtonText": {
"type": "string",
"default": "Start",
"description": "StartPage Continue Button",
"example": "Continue"
},
"buttons": {
"type": "array",
"items": {
type: "Button"
}
}
}
},
"hideFooter": {
"type": "boolean",
"default": false,
"description": "Specifies whether to hide or show Form Footer",
"example": true
},
"isLive": {
"type": "boolean",
"default": false,
"description": "Specifies whether form is Publically available or Private",
"example": true
},
"design": {
"type": "object",
"properties": {
"colors": {
"type": "object",
"properties": {
"backgroundColor": {
"type": "string",
"pattern": "/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/",
"default": "#fff",
"description": "Background color of Form",
"example": "#4c4c4c"
},
"questionColor": {
"type": "string",
"match": "/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/",
"default": "#333",
"description": "Question text font color (in hex)",
"example": "#fff"
},
"answerColor": {
"type": "string",
"match": "/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/",
"default": "#333",
"description": "Answer text font color (in hex)",
"example": "#f9f9f9"
},
"buttonColor": {
"type": "string",
"match": "/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/",
"default": "#fff",
"description": "Background color of Form Buttons (in hex)",
"example": "#555"
},
"buttonTextColor": {
"type": "string",
"pattern": "/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/",
"default": "#333",
"description": "Font color of Form Buttons (in hex)",
"example": "#fff"
}
}
},
"font": {
"type": "string"
}
}
}
}
}
}
}