fixed setup script

This commit is contained in:
David Baldwynn 2016-11-08 14:55:46 -08:00
commit 6e1a8960f2
84 changed files with 2763 additions and 12522 deletions

1
.gitignore vendored
View file

@ -4,6 +4,7 @@ scripts/test_oscarhost.js
scripts/oscarhost/private/
coverage/
uploads/
app/e2e_tests/screeshots/*
# iOS / Apple
# ===========

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)
@ -14,8 +14,8 @@ TellForm is an *opensource alternative to TypeForm* built ontop of nodejs that c
[See examples here](https://tellform.com/examples)
####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://raw.githubusercontent.com/docker-library/docs/831b07a52f9ff6577c915afc41af8158725829f4/sentry/logo.png" width="250px">](https://getsentry.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://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="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/)
@ -50,6 +50,8 @@ Before you start, make sure you have
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)
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.
```bash
$ npm install

View file

@ -13,11 +13,19 @@ exports.index = function(req, res) {
};
exports.form = function(req, res) {
//Allow form to be embeded
res.removeHeader('X-Frame-Options');
//Allow form to be embedded
res.set('X-Frame-Options', 'GOFORIT');
res.render('form', {
user: req.user || null,
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'),
async = require('async'),
path = require('path'),
diff = require('deep-diff'),
_ = 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
*/
@ -185,8 +118,6 @@ exports.createSubmission = function(req, res) {
percentageComplete: req.body.percentageComplete
});
if(!!form.plugins.oscarhost.baseUrl) submission.hasPlugins.oscarhost = true;
if(form.pdf) submission.pdf = form.pdf;
//Save submitter's IP Address
@ -256,23 +187,27 @@ exports.listSubmissions = function(req, res) {
* Create a new form
*/
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);
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) {
if (err) {
console.log(err);
res.status(400).send({
return res.status(405).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.json(form);
}
res.json(form);
});
};
@ -280,10 +215,7 @@ exports.create = function(req, res) {
* Show the current form
*/
exports.read = function(req, res) {
var validUpdateTypes= Form.schema.path('plugins.oscarhost.settings.updateType').enumValues;
var newForm = req.form.toJSON({virtuals : true});
newForm.plugins.oscarhost.settings.validUpdateTypes = validUpdateTypes;
if (req.userId) {
if(req.form.admin._id+'' === req.userId+''){
@ -294,7 +226,6 @@ exports.read = function(req, res) {
});
}
return res.json(newForm);
};
/**
@ -302,27 +233,33 @@ exports.read = function(req, res) {
*/
exports.update = function(req, res) {
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.user.roles.indexOf('admin') === -1) delete req.body.form.admin;
if(req.body.changes){
console.log('SENDING DIFFS\n\n\n');
var formChanges = req.body.changes;
//Do this so we can create duplicate fields
var checkForValidId = new RegExp('^[0-9a-fA-F]{24}$');
for(var i=0; i<req.body.form.form_fields.length; i++){
var field = req.body.form.form_fields[i];
if(!checkForValidId.exec(field._id+'')){
delete field._id;
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;
//Do this so we can create duplicate fields
var checkForValidId = new RegExp('^[0-9a-fA-F]{24}$');
for(var i=0; i<req.body.form.form_fields.length; i++){
var field = req.body.form.form_fields[i];
if(!checkForValidId.exec(field._id+'')){
delete field._id;
}
}
form = _.extend(form, req.body.form);
}
form = _.extend(form, req.body.form);
form.save(function(err, form) {
if (err) {
console.log(err);
res.status(400).send({
res.status(405).send({
message: errorHandler.getErrorMessage(err)
});
} else {
@ -382,7 +319,7 @@ exports.formByID = function(req, res, next, id) {
if (err) {
return next(err);
} else if (form === undefined || form === null) {
res.status(400).send({
res.status(404).send({
message: 'Form not found'
});
}

View file

@ -11,7 +11,9 @@ var _ = require('lodash'),
config = require('../../../config/config'),
nodemailer = require('nodemailer'),
crypto = require('crypto'),
User = mongoose.model('User');
User = mongoose.model('User'),
tokgen = require("../../libs/tokenGenerator");
var nev = require('email-verification')(mongoose);
@ -99,6 +101,7 @@ exports.resendVerificationEmail = function(req, res, next){
* Signup
*/
exports.signup = function(req, res) {
debugger;
// For security measures we remove the roles from the req.body object
delete req.body.roles;
@ -172,7 +175,6 @@ exports.signin = function(req, res, next) {
*/
exports.signout = function(req, res) {
req.logout();
//res.redirect('/');
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) {
return next(err);
} 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;

View file

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

View file

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

View file

@ -92,11 +92,6 @@ var FormSchema = new Schema({
default: 'en',
required: 'Form must have a language'
},
description: {
type: String,
default: ''
},
analytics:{
gaCode: {
type: String
@ -146,18 +141,10 @@ var FormSchema = new Schema({
type: Boolean,
default: false
},
isGenerated: {
type: Boolean,
default: false
},
isLive: {
type: Boolean,
default: false
},
autofillPDFs: {
type: Boolean,
default: false
},
design: {
colors:{
@ -187,57 +174,7 @@ var FormSchema = new Schema({
default: '#333'
}
},
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
}
}
}
font: String
}
});
@ -367,11 +304,6 @@ FormSchema.pre('save', function (next) {
}
});
}, 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);
},
function(cb) {
@ -489,12 +421,18 @@ FormSchema.pre('save', function (next) {
else return cb();
},
function(cb) {
if(that.isModified('form_fields') && that.form_fields && _original){
var hasIds = true;
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,
new_ids = _.map(_.pluck(that.form_fields, '_id'), function(id){ return ''+id;}),
old_ids = _.map(_.pluck(old_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;}),
deletedIds = getDeletedIndexes(old_ids, new_ids);
//Preserve fields that have at least one submission

View file

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

View file

@ -158,88 +158,7 @@ FormSubmissionSchema.pre('save', function (next) {
// console.log(_form);
// console.log('should push to api');
// 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();
});
} else {
return next();
}
return next();
});
} else {
return next();

View file

@ -6,76 +6,76 @@
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
_ = require('lodash'),
math = require('math');
math = require('mathjs');
var BooleanExpressionSchema = new Schema({
expressionString: {
type: String,
var schemaOptions = {
toObject: {
virtuals: true
},
result: {
type: Boolean,
}
});
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;
toJSON: {
virtuals: true
}
};
mongoose.model('BooleanExpression', BooleanExpressionSchema);
/**
* Form Schema
*/
var LogicJumpSchema = new Schema({
created: {
type: Date,
default: Date.now
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',
]
},
lastModified: {
type: Date,
},
BooleanExpression: {
fieldA: {
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);
module.exports = LogicJumpSchema;

View file

@ -75,6 +75,7 @@ var UserSchema = new Schema({
type: String,
unique: true,
required: false,
lowercase: true,
trim: true
},
passwordHash: {
@ -119,7 +120,13 @@ var UserSchema = new Schema({
resetPasswordExpires: {
type: Date
},
token: String
token: String,
apiKey: {
type: String,
unique: true,
index: true,
sparse: true
},
});
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');
module.exports = function(app) {
// Root routing
// Core routing
app.route('/').get(core.index);
app.route('/subdomain/([a-zA-Z0-9]+)/').get(core.form);
app.route('/subdomain/*/forms/:formId([a-zA-Z0-9]+)')
.get(forms.read)
app.route('/subdomain/api/').get(core.redoc);
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);
};

View file

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

View file

@ -3,16 +3,17 @@
/**
* Module dependencies.
*/
var passport = require('passport');
var config = require('../../config/config');
var passport = require('passport'),
config = require('../../config/config'),
auth = require('../../config/passport_helpers');
module.exports = function(app) {
// User Routes
var users = require('../../app/controllers/users.server.controller');
// Setting up the users profile api
app.route('/users/me').get(users.requiresLogin, users.getUser);
app.route('/users').put(users.requiresLogin, users.update);
app.route('/users/me').get(auth.isAuthenticatedOrApiKey, users.getUser);
app.route('/users').put(auth.isAuthenticatedOrApiKey, users.update);
app.route('/users/accounts').delete(users.requiresLogin, users.removeOAuthProvider);
// 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/signout').get(users.signout);
app.route('/auth/genkey').get(users.requiresLogin, users.generateAPIKey);
// // Setting the facebook oauth routes
// app.route('/auth/facebook').get(passport.authenticate('facebook', {
// 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
socket.on('form-visitor-data', function(data) {
console.log('\n\nuser has visited our page');
visitorsData[socket.id] = data;
console.log(data);
if (data.isSubmitted) {
saveVisitorData(data, function () {
console.log('\n\n user submitted form');

View file

@ -148,13 +148,6 @@ describe('FormSubmission Model Unit Tests:', function() {
beforeEach(function(done){
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){
if(err) done(err);

View file

@ -1,70 +0,0 @@
// 'use strict';
// var should = require('should'),
// mongoose = require('mongoose'),
// math = require('math'),
// Form = mongoose.model('Form'),
// LogicJump = mongoose.model('LogicJump'),
// BooleanƒExpression = mongoose.model('BooleanExpression'),
// Field = mongoose.model('Field');
// /**
// * Globals
// */
// var textField;
// /**
// * Form routes tests
// */
// describe('LogicJump Tests', function() {
// beforeEach(function(done) {
// textField = new Field({
// title: 'First Name',
// fieldType: 'textfield',
// fieldValue: 'David',
// });
// textField.save(function(err){
// if(err) done(err);
// done();
// });
// });
// describe('BooleanExpression', function(){
// var scope;
// beforeEach(function(){
// scope = {
// x: false,
// y: true,
// b: 5,
// a: 3
// }
// });
// it('should evaluate a simple boolean expression to be true', function(){
// var expression = 'and( or( x, y), (b > a))';
// var code = math.parse(expression);
// var actual = code.compile().eval(scope);
// // var expected = ( (false || true) && !!(5 > 3) );
// actual.should.equal(true);
// });
// afterEach(function(done){
// BooleanExpression.remove().exec(function() {
// done();
// });
// });
// });
// afterEach(function(done) {
// Field.remove().exec(function() {
// done();
// });
// });
// });

Binary file not shown.

View file

@ -2,7 +2,7 @@
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>{{title}}</title>
<title>{{title}} Form</title>
<!-- General META -->
<meta charset="utf-8">
@ -61,11 +61,11 @@
</head>
<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">
<a href="https://github.com/whitef0x0/tellform">Fork me on GitHub</a>
</div>
</div>
</div>-->
<section class="content">
<section ui-view></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",
"angular-ui-select": "https://github.com/whitef0x0/ui-select.git#compiled",
"angular-translate": "~2.11.0",
"ng-device-detector": "~3.0.1",
"ng-translate": "*"
"ng-device-detector": "^3.0.1",
"ng-translate": "*",
"deep-diff": "^0.3.4",
"mathjs": "^3.4.1",
"jsep": "^0.3.1",
"ngclipboard": "^1.1.1"
},
"resolutions": {
"angular-bootstrap": "^0.14.0",

View file

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

2
config/env/all.js vendored
View file

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

View file

@ -3,7 +3,7 @@
module.exports = {
baseUrl: process.env.BASE_URL || 'tellform.com',
db: {
uri: process.env.MONGODB_URI
uri: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://' + (process.env.DB_1_PORT_27017_TCP_ADDR || 'localhost') + '/mean',
},
port: process.env.PORT || 5000,
log: {

View file

@ -72,7 +72,6 @@ module.exports = function(db) {
var subdomains = req.subdomains;
var host = req.hostname;
if(subdomains.slice(0, 4).join('.')+'' === '1.0.0.127'){
subdomains = subdomains.slice(4);
}
@ -81,18 +80,38 @@ module.exports = function(db) {
if (!subdomains.length) return next();
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);
req.root = 'https://' + config.baseUrl + urlPath.join('/');
console.log(req.root);
return next();
}
if(subdomains.indexOf('stage') || subdomains.indexOf('admin')){
if(urlPath.indexOf('users') > -1 && urlPath.indexOf('me') > -1){
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) {
if (err) {
console.log(err);
req.subdomains = null;
@ -113,10 +132,13 @@ module.exports = function(db) {
// TODO: check path and query strings are preserved
// reassign url
console.log("path: "+path);
req.url = path;
req.userId = user._id;
console.log('\n\n\ngot subdomain: '+ req.subdomains.reverse()[0]);
// Q.E.D.
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": {
"async": "^1.4.2",
"async-boolean-expression-evaluator": "^1.1.1",
"aws-sdk": "^2.3.9",
"bcrypt": "^0.8.7",
"body-parser": "~1.14.1",
@ -35,6 +36,7 @@
"connect-mongo": "~0.8.2",
"consolidate": "~0.13.1",
"cookie-parser": "~1.4.0",
"deep-diff": "^0.3.4",
"dotenv": "^2.0.0",
"email-verification": "~0.4.1",
"envfile": "^2.0.1",
@ -64,6 +66,7 @@
"lodash": "^2.4.1",
"main-bower-files": "~2.9.0",
"math": "0.0.3",
"mathjs": "^3.4.1",
"method-override": "~2.3.0",
"mkdirp": "^0.5.1",
"mongoose": "~4.4.19",
@ -81,6 +84,7 @@
"passport-google-oauth": "~0.2.0",
"passport-linkedin": "~1.0.0",
"passport-local": "~1.0.0",
"passport-localapikey-update": "^0.5.0",
"passport-twitter": "~1.0.2",
"path-exists": "^2.1.0",
"pdffiller": "~0.1.1",
@ -90,10 +94,14 @@
"socket.io": "^1.4.6",
"socket.io-redis": "^1.0.0",
"swig": "~1.4.1",
"uuid-token-generator": "^0.5.0",
"wildcard-subdomains": "github:whitef0x0/wildcard-subdomains"
},
"devDependencies": {
"chromedriver": "^2.25.1",
"coveralls": "^2.11.4",
"cross-spawn": "^5.0.0",
"del": "^2.2.2",
"glob": "^7.0.3",
"grunt-execute": "^0.2.2",
"grunt-mocha-istanbul": "^3.0.1",
@ -110,10 +118,12 @@
"karma-ng-html2js-preprocessor": "^0.2.0",
"karma-phantomjs-launcher": "~0.2.1",
"mailosaur": "^1.0.1",
"mocha": ">=1.20.0",
"mocha": "^3.1.2",
"mocha-lcov-reporter": "^1.0.0",
"nightwatch": "^0.9.8",
"node-mandrill": "^1.0.1",
"phantomjs": "^1.9.18",
"selenium-server": "^3.0.1",
"should": "~7.1.1",
"supertest": "~1.2.0",
"supertest-session": "~2.0.1"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -2,107 +2,108 @@
// coffeescript's for in loop
var __indexOf = [].indexOf || function(item) {
for (var i = 0, l = this.length; i < l; i++) {
if (i in this && this[i] === item) return i;
}
return -1;
};
for (var i = 0, l = this.length; i < l; i++) {
if (i in this && this[i] === item) return i;
}
return -1;
};
angular.module('view-form').directive('fieldDirective', ['$http', '$compile', '$rootScope', '$templateCache', 'supportedFields',
function($http, $compile, $rootScope, $templateCache, supportedFields) {
function($http, $compile, $rootScope, $templateCache, supportedFields) {
var getTemplateUrl = function(fieldType) {
var type = fieldType;
var getTemplateUrl = function(fieldType) {
var type = fieldType;
var supported_fields = [
'textfield',
'textarea',
'date',
'dropdown',
'hidden',
'password',
'radio',
'legal',
'statement',
'rating',
'yes_no',
'number',
'natural'
];
var supported_fields = [
'textfield',
'textarea',
'date',
'dropdown',
'hidden',
'password',
'radio',
'legal',
'statement',
'rating',
'yes_no',
'number',
'natural'
];
var templateUrl = 'modules/forms/base/views/directiveViews/field/';
var templateUrl = 'modules/forms/base/views/directiveViews/field/';
if (__indexOf.call(supportedFields, type) >= 0) {
templateUrl = templateUrl+type+'.html';
}
return $templateCache.get(templateUrl);
};
return {
template: '<div>{{field.title}}</div>',
restrict: 'E',
scope: {
field: '=',
required: '&',
design: '=',
index: '=',
forms: '='
},
link: function(scope, element) {
$rootScope.chooseDefaultOption = scope.chooseDefaultOption = function(type) {
if(type === 'yes_no'){
scope.field.fieldValue = 'true';
}else if(type === 'rating'){
scope.field.fieldValue = 0;
}else if(scope.field.fieldType === 'radio'){
console.log(scope.field);
scope.field.fieldValue = scope.field.fieldOptions[0].option_value;
console.log(scope.field.fieldValue);
}else if(type === 'legal'){
scope.field.fieldValue = 'true';
$rootScope.nextField();
}
};
scope.setActiveField = $rootScope.setActiveField;
//Set format only if field is a date
if(scope.field.fieldType === 'date'){
scope.dateOptions = {
changeYear: true,
changeMonth: true,
altFormat: 'mm/dd/yyyy',
yearRange: '1900:-0',
defaultDate: 0
};
}
var fieldType = scope.field.fieldType;
if(scope.field.fieldType === 'number' || scope.field.fieldType === 'textfield' || scope.field.fieldType === 'email' || scope.field.fieldType === 'link'){
switch(scope.field.fieldType){
case 'textfield':
scope.field.input_type = 'text';
break;
case 'email':
scope.field.input_type = 'email';
scope.field.placeholder = 'joesmith@example.com';
break;
case 'number':
scope.field.input_type = 'text';
scope.field.validateRegex = /^-?\d+$/;
break;
default:
scope.field.input_type = 'url';
scope.field.placeholder = 'http://example.com';
break;
}
fieldType = 'textfield';
if (__indexOf.call(supportedFields, type) >= 0) {
templateUrl = templateUrl+type+'.html';
}
var template = getTemplateUrl(fieldType);
element.html(template).show();
var output = $compile(element.contents())(scope);
}
};
}]);
return $templateCache.get(templateUrl);
};
return {
template: '<div>{{field.title}}</div>',
restrict: 'E',
scope: {
field: '=',
required: '&',
design: '=',
index: '=',
forms: '='
},
link: function(scope, element) {
$rootScope.chooseDefaultOption = scope.chooseDefaultOption = function(type) {
if(type === 'yes_no'){
scope.field.fieldValue = 'true';
}else if(type === 'rating'){
scope.field.fieldValue = 0;
}else if(scope.field.fieldType === 'radio'){
console.log(scope.field);
scope.field.fieldValue = scope.field.fieldOptions[0].option_value;
console.log(scope.field.fieldValue);
}else if(type === 'legal'){
scope.field.fieldValue = 'true';
$rootScope.nextField();
}
};
scope.setActiveField = $rootScope.setActiveField;
//Set format only if field is a date
if(scope.field.fieldType === 'date'){
scope.dateOptions = {
changeYear: true,
changeMonth: true,
altFormat: 'mm/dd/yyyy',
yearRange: '1900:-0',
defaultDate: 0
};
}
var fieldType = scope.field.fieldType;
if(scope.field.fieldType === 'number' || scope.field.fieldType === 'textfield' || scope.field.fieldType === 'email' || scope.field.fieldType === 'link'){
switch(scope.field.fieldType){
case 'textfield':
scope.input_type = 'text';
break;
case 'email':
scope.input_type = 'email';
scope.placeholder = 'joesmith@example.com';
break;
case 'number':
scope.input_type = 'text';
scope.validateRegex = /^-?\d+$/;
break;
default:
scope.input_type = 'url';
scope.placeholder = 'http://example.com';
break;
}
fieldType = 'textfield';
}
var template = getTemplateUrl(fieldType);
element.html(template).show();
var output = $compile(element.contents())(scope);
}
};
}]);

View file

@ -1,5 +1,41 @@
'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',
function ($http, TimeCounter, $filter, $rootScope, SendVisitorData) {
@ -92,6 +128,67 @@ angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCoun
/*
** 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(){
if($scope.selected === 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.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);
$scope.translateAdvancementData = {
@ -159,20 +265,26 @@ angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCoun
};
$rootScope.nextField = $scope.nextField = function(){
//console.log('nextfield');
//console.log($scope.selected.index);
//console.log($scope.myform.visible_form_fields.length-1);
var selected_index, selected_id;
if($scope.selected.index < $scope.myform.visible_form_fields.length-1){
selected_index = $scope.selected.index+1;
selected_id = $scope.myform.visible_form_fields[selected_index]._id;
$rootScope.setActiveField(selected_id, selected_index, true);
} else if($scope.selected.index === $scope.myform.visible_form_fields.length-1) {
//console.log('Second last element');
selected_index = $scope.selected.index+1;
selected_id = 'submit_field';
$rootScope.setActiveField(selected_id, selected_index, true);
var currField = $scope.myform.visible_form_fields[$scope.selected.index];
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;
if($scope.selected.index < $scope.myform.visible_form_fields.length-1){
selected_index = $scope.selected.index+1;
selected_id = $scope.myform.visible_form_fields[selected_index]._id;
$rootScope.setActiveField(selected_id, selected_index, true);
} else if($scope.selected.index === $scope.myform.visible_form_fields.length-1) {
selected_index = $scope.selected.index+1;
selected_id = 'submit_field';
$rootScope.setActiveField(selected_id, selected_index, true);
}
}
}
};
$rootScope.prevField = $scope.prevField = function(){

View file

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

View file

@ -4,4 +4,4 @@
ApplicationConfiguration.registerModule('view-form', [
'ngFileUpload', 'ui.router.tabs', 'ui.date', 'ui.sortable',
'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;
$scope.user = $rootScope.user = Auth.ensureHasCurrentUser(User);
console.log(Auth.ensureHasCurrentUser(User));
$scope.authentication = $rootScope.authentication = Auth;
$rootScope.languages = $scope.languages = ['en', 'fr', 'es', 'it', 'de'];
console.log($locale.id);
//Set global app language
if($scope.authentication.isAuthenticated()){
$rootScope.language = $scope.user.language;

View file

@ -1,3 +1,5 @@
body {
overflow-x: hidden;
font-family: 'Source Sans Pro', sans-serif;
@ -10,12 +12,34 @@ body {
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 {
min-height: 60px;
padding: 10px 0 10px 0;
}
.navbar-inverse {
background-color:#588EB4;
background-color:#3FA2F7;
border: 0;
color: white!important;
}
@ -33,6 +57,7 @@ body {
min-height: 60px;
}
.navbar-nav > li > a {
padding-top: 20px;
color: white;
}
.navbar-nav > li:hover, .navbar-nav > li.active {

View file

@ -5,25 +5,11 @@
<span class="sr-only">Toggle navigation</span>
<span>{{ 'MENU_BTN' | translate }}</span>
</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%">
</a>
</div>
<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()">
<li ng-hide="$root.signupDisabled" ui-route="/signup" ng-class="{active: $uiRoute}">
<a href="/#!/signup">{{ 'SIGNUP_TAB' | translate }}</a>

View file

@ -39,6 +39,10 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
PREVIEW: 'Preview',
//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_MD: 'Add New Field',
ADD_FIELD_SM: 'Add Field',

View file

@ -1,8 +1,21 @@
'use strict';
// Forms controller
angular.module('forms').controller('AdminFormController', ['$rootScope', '$scope', '$stateParams', '$state', 'Forms', 'CurrentForm', '$http', '$uibModal', 'myForm', '$filter',
function($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, $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.animationsEnabled = true;
@ -11,24 +24,38 @@ angular.module('forms').controller('AdminFormController', ['$rootScope', '$scope
CurrentForm.setForm($scope.myform);
$scope.formURL = $scope.myform.admin.username + '.tellform.com';
$scope.formURL = "/#!/forms/" + $scope.myform._id;
$scope.tabData = [
{
if(window.location.host.split('.') < 3){
$scope.actualFormURL = window.location.protocol + '//' + $scope.myform.admin.username + '.' + window.location.host + "/#!/forms/" + $scope.myform._id;
} else {
$scope.actualFormURL = window.location.protocol + '//' + $scope.myform.admin.username + '.' + window.location.host.split('.').slice(1,3).join('.') + "/#!/forms/" + $scope.myform._id;
}
var refreshFrame = $scope.refreshFrame = function(){
if(document.getElementById('iframe')) {
document.getElementById('iframe').contentWindow.location.reload();
}
};
$scope.tabData = [
/*{
heading: $filter('translate')('CREATE_TAB'),
route: 'viewForm.create'
templateName: 'create'
},
{
heading: $filter('translate')('DESIGN_TAB'),
route: 'viewForm.design'
},
templateName: 'design'
},*/
{
heading: $filter('translate')('CONFIGURE_TAB'),
route: 'viewForm.configure'
templateName: 'configure'
},
{
heading: $filter('translate')('ANALYZE_TAB'),
route: 'viewForm.analyze'
templateName: 'analyze'
}
];
@ -92,7 +119,8 @@ angular.module('forms').controller('AdminFormController', ['$rootScope', '$scope
};
// Update existing Form
$scope.update = $rootScope.update = function(updateImmediately, cb){
$scope.update = $rootScope.update = function(updateImmediately, diffChanges, cb){
refreshFrame();
var continueUpdate = true;
if(!updateImmediately){
@ -105,7 +133,7 @@ angular.module('forms').controller('AdminFormController', ['$rootScope', '$scope
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){
$rootScope.myform = $scope.myform = response.data;
// console.log(response.data);

View file

@ -32,7 +32,6 @@ angular.module('forms').directive('autoSaveForm', ['$rootScope', '$timeout', fun
$rootScope.finishedRender = false;
$scope.$on('editFormFields Started', function(ngRepeatFinishedEvent) {
// console.log('hello');
$rootScope.finishedRender = false;
});
$scope.$on('editFormFields Finished', function(ngRepeatFinishedEvent) {
@ -52,13 +51,11 @@ angular.module('forms').directive('autoSaveForm', ['$rootScope', '$timeout', fun
return false;
};
var debounceSave = function () {
$rootScope.saveInProgress = true;
var debounceSave = function (diffChanges) {
$rootScope[$attrs.autoSaveCallback](true,
$rootScope[$attrs.autoSaveCallback](true, diffChanges,
function(err){
if(!err){
//console.log('\n\nForm data persisted -- setting pristine flag');
$formCtrl.$setPristine();
$formCtrl.$setUntouched();
}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
$scope.$watch($attrs.autoSaveWatch, function(newValue, oldValue) {
if( !newValue || !oldValue ) {
$rootScope.finishedRender = true;
return;
}
newValue = angular.copy(newValue);
oldValue = angular.copy(oldValue);
newValue.form_fields = _.removeDateFields(newValue.form_fields);
oldValue.form_fields = _.removeDateFields(oldValue.form_fields);
delete newValue.visible_form_fields;
delete oldValue.visible_form_fields;
newValue.form_fields = _.removeDateFields(newValue.form_fields);
oldValue.form_fields = _.removeDateFields(oldValue.form_fields);
var changedFields = !_.isEqual(oldValue.form_fields,newValue.form_fields) || !_.isEqual(oldValue.startPage, newValue.startPage);
var changedFieldMap = false;
var changedFields = !!DeepDiff.diff(oldValue, newValue) && DeepDiff.diff(oldValue, newValue).length > 0;
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(!changedFields){
$rootScope.finishedRender = true;
return;
}
//If our form is undefined, don't save form
if( (!newValue && !oldValue) || !oldValue ){
return;
}
if(oldValue.form_fields.length === 0) {
$rootScope.finishedRender = true;
}
//console.log('Autosaving');
//console.log('\n\n----------');
//console.log('!$dirty: '+ !$formCtrl.$dirty );
// 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('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) {
$rootScope.finishedRender = true;
}
//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) {
$timeout.cancel(savePromise);
@ -125,7 +114,12 @@ angular.module('forms').directive('autoSaveForm', ['$rootScope', '$timeout', fun
}
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

View file

@ -5,16 +5,12 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
return {
templateUrl: 'modules/forms/admin/views/directiveViews/form/edit-form.client.view.html',
restrict: 'E',
transclude: true,
scope: {
myform:'='
myform:'='
},
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
*/
@ -63,24 +59,24 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
//Populate local scope with rootScope methods/variables
$scope.update = $rootScope.update;
//Many-to-many Select for Mapping OscarhostFields -> FormFields
$scope.oscarFieldsLeft = function(field_id){
// LOGIC JUMP METHODS
$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){
if(!$scope.myform.plugins.oscarhost.settings.fieldMap) $scope.myform.plugins.oscarhost.settings.fieldMap = {};
$scope.addNewLogicJump = function (field_index) {
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 currentFields = _($scope.myform.plugins.oscarhost.settings.fieldMap).invert().keys().value();
if( $scope.myform.plugins.oscarhost.settings.fieldMap.hasOwnProperty(field_id) ){
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 [];
};
var newLogicJump = {
fieldA: currField._id
};
currField.logicJump = newLogicJump;
}
};
/*
** FormFields (ui-sortable) drag-and-drop configuration
@ -109,12 +105,13 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
}
}
var newField = {
title: fieldTitle,
title: fieldTitle + ' ' + $scope.myform.form_fields.length+1,
fieldType: fieldType,
fieldValue: '',
required: true,
disabled: false,
deletePreserved: false
deletePreserved: false,
logicJump: {}
};
if($scope.showAddOptions(newField)){
@ -126,7 +123,6 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
});
}
if(modifyForm){
//Add newField to form_fields array
$scope.myform.form_fields.push(newField);
@ -135,15 +131,11 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
};
// Delete particular field on button click
$scope.deleteField = function (field_index){
//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.deleteField = function (field_index) {
console.log($scope.myform.form_fields);
$scope.myform.form_fields.splice(field_index, 1);
};
$scope.duplicateField = function (field_index){
var currField = _.cloneDeep($scope.myform.form_fields[field_index]);
currField._id = 'cloned'+_.uniqueId();
@ -249,10 +241,7 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
return false;
}
};
}
};
}
]);

View file

@ -75,7 +75,6 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
var visitors = $scope.myform.analytics.visitors;
console.log(visitors);
for(var i=0; i<visitors.length; i++){
var visitor = visitors[i];
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>
<section class="container admin-form">
<section class="admin-form">
<!-- Modal Delete Dialog Template -->
<script type="text/ng-template" id="myModalContent.html">
@ -29,7 +29,7 @@
</div>
</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">
<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>
@ -45,19 +45,19 @@
</div>
<div class="col-xs-1 col-sm-2">
<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">
{{ 'VIEW' | translate }}
<span ng-show="myform.isLive">
{{ 'LIVE' | translate }}
</span>
<span ng-hide="myform.isLive">{{ 'PREVIEW' | translate }}</span> {{ 'FORM' | translate }}
</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 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>-->
<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>
</a>
@ -65,16 +65,133 @@
</div>
</div>
<div class="row-fluid">
<div class="row">
<div class="col-xs-12">
<!-- <tabset> -->
<tabs data="tabData"></tabs>
<!-- </tabset> -->
</div>
<uib-tabset active="activePill" vertical="true" type="pills">
<uib-tab index="0" heading="{{ 'CREATE_TAB' | translate }}">
<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 class="col-xs-12">
<ui-view></ui-view>
</div>
<div class="row field">
<div class="field-title col-sm-5">
<h5>{{ 'QUESTION_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.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>
</section>

View file

@ -1,21 +1,12 @@
<div class="config-form design container">
<div class="row">
<div class="col-md-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="col-md-4 col-sm-12 container">
<div class="row field">
<div class="field-title col-sm-3">
<h5>{{ 'BACKGROUND_COLOR' | translate }}</h5>
</div>
<div class="field-input col-sm-9">
<input colorpicker="hex" type="text" ng-model="myform.design.colors.backgroundColor" ng-style="{ 'background-color': myform.design.colors.backgroundColor }"/>
<div class="field-input col-sm-6">
<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 }"/>
</div>
</div>
@ -24,11 +15,8 @@
<h5>{{ 'QUESTION_TEXT_COLOR' | translate }}</h5>
</div>
<div class="field-input col-sm-9">
<input colorpicker="hex" type="text" ng-model="myform.design.colors.questionColor" ng-style="{ 'background-color': myform.design.colors.questionColor }"/>
<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 }"/>
</div>
</div>
@ -37,8 +25,8 @@
<h5>{{ 'ANSWER_TEXT_COLOR' | translate }}</h5>
</div>
<div class="field-input col-sm-9">
<input colorpicker="hex" type="text" ng-model="myform.design.colors.answerColor" ng-style="{ 'background-color': myform.design.colors.answerColor }"/>
<div class="field-input col-sm-6">
<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>
@ -47,8 +35,8 @@
<h5>{{ 'BTN_BACKGROUND_COLOR' | translate }}</h5>
</div>
<div class="field-input col-sm-9">
<input colorpicker="hex" type="text"
<div class="field-input col-sm-6">
<input ng-change="refreshIframe()" class="form-control" colorpicker="hex" type="text"
ng-model="myform.design.colors.buttonColor"
ng-style="{ 'background-color': myform.design.colors.buttonColor }"/>
</div>
@ -58,21 +46,24 @@
<h5>{{ 'BTN_TEXT_COLOR' | translate }}</h5>
</div>
<div class="field-input col-sm-9">
<input colorpicker="hex" type="text"
<div class="field-input col-sm-6">
<input ng-change="refreshIframe()" 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 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 class="row">
<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>
</div>
<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>
</div>
<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>

View file

@ -1,22 +1,21 @@
<div class="config-form container">
<div class="row">
<!-- 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>
<h3 class="hidden-lg hidden-md">{{ 'ADVANCED_SETTINGS' | translate }}</h3>
</div>
</div>
<!--<div class="row">
<div class="col-sm-offset-2 col-sm-10">
<h2 class="hidden-sm hidden-xs">{{ 'ADVANCED_SETTINGS' | translate }}</h2>
<h3 class="hidden-lg hidden-md">{{ 'ADVANCED_SETTINGS' | translate }}</h3>
</div>
</div>-->
<!-- Settings -->
<div class="row">
<div class="col-sm-offset-2 col-sm-4">
<div class="row field">
<div class="field-title col-sm-4">
<div class="field-title col-sm-12">
<h5>{{ 'FORM_NAME' | translate }}</h5>
</div>
<div class="col-sm-8">
<input type="text"
<div class="col-sm-12">
<input class="form-control"
type="text"
ng-model="myform.title"
value="{{myform.title}}"
style="width: 100%;"
@ -26,44 +25,28 @@
</div>
<div class="row field">
<div class="field-title col-sm-6">
<div class="field-title col-sm-12">
<h5>{{ 'FORM_STATUS' | translate }}</h5>
</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;"/>
&nbsp;<span>{{ 'PUBLIC' | translate }}</span>
</label>
<label>
<label style="display: inline-block;">
<input type="radio" data-ng-value="false" ng-model="myform.isLive" ng-required="true" />
&nbsp;<span>{{ 'PRIVATE' | translate }}</span>
</label>
</div>
</div>
<div class="row field">
<div class="field-title col-sm-4">
<h5>{{ 'GA_TRACKING_CODE' | translate }}</h5>
</div>
<div class="col-sm-8">
<input type="text"
ng-model="myform.analytics.gaCode"
value="{{myform.analytics.gaCode}}"
style="width: 100%;"
ng-minlength="4"
placeholder="UA-XXXXX-Y"
ng-pattern="/\bUA-\d{4,10}-\d{1,4}\b/">
</div>
</div>
<div class="row field">
<div class="col-xs-6 field-title">Language</div>
<div class="col-xs-4 field-input">
<div class="col-sm-12 field-title">Language</div>
<div class="col-sm-12 field-input">
<select ng-model="myform.language">
<option ng-repeat="language in languages"
ng-selected="language == myform.language"
@ -74,51 +57,69 @@
<span class="required-error" ng-show="field.required && !field.fieldValue">* required</span>
</div>
</div>
<div class="row field">
<div class="field-title col-sm-6">
<h5>{{ 'DISPLAY_FOOTER' | translate }}</h5>
</div>
<div class="field-input col-sm-6">
<label>
<input type="radio" data-ng-value="false" ng-model="myform.hideFooter" ng-required="true" />
&nbsp;<span>{{ 'YES' | translate }}</span>
</label>
<label>
<input type="radio" data-ng-value="true" ng-model="myform.hideFooter" ng-required="true" />
&nbsp;<span>{{ 'No' | translate }}</span>
</label>
</div>
</div>
<div class="row field">
<div class="field-title col-sm-6">
<h5>Display Start Page?</h5>
</div>
<div class="field-input col-sm-6">
<label>
<input type="radio" data-ng-value="true" ng-model="myform.startPage.showStart" ng-required="true" style="background-color:#33CC00;"/>
&nbsp;<span>{{ 'YES' | translate }}</span>
</label>
<label>
<input type="radio" data-ng-value="false" ng-model="myform.startPage.showStart" ng-required="true" />
&nbsp;<span>{{ 'NO' | translate }}</span>
</label>
</div>
</div>
</div>
<div class="col-sm-4">
<div class="row field">
<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>
</div>
<div class="field-input col-sm-12">
<label style="display: inline-block;">
<input type="radio" data-ng-value="false" ng-model="myform.hideFooter" ng-required="true" />
&nbsp;<span>{{ 'YES' | translate }}</span>
</label>
<label style="display: inline-block;">
<input type="radio" data-ng-value="true" ng-model="myform.hideFooter" ng-required="true" />
&nbsp;<span>{{ 'No' | translate }}</span>
</label>
</div>
</div>
<div class="row field">
<div class="field-title col-sm-12">
<h5>Display Start Page?</h5>
</div>
<div class="field-input col-sm-12">
<label style="display: inline-block;">
<input type="radio" data-ng-value="true" ng-model="myform.startPage.showStart" ng-required="true" style="background-color:#33CC00;"/>
&nbsp;<span>{{ 'YES' | translate }}</span>
</label>
<label style="display: inline-block;">
<input type="radio" data-ng-value="false" ng-model="myform.startPage.showStart" ng-required="true" />
&nbsp;<span>{{ 'NO' | translate }}</span>
</label>
</div>
</div>
</div>
</div>
<div class="row">
<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>
</div>
<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>
</div>
<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>

View file

@ -1,7 +1,7 @@
<form class="row container" name="editForm" auto-save-form auto-save-watch="myform" auto-save-callback="update">
<!-- 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">
<h3 class="col-md-12 hidden-sm hidden-xs">{{ 'ADD_FIELD_LG' | translate }}</h3>
@ -249,7 +249,6 @@
</div>
</div>
<div class="row" ng-show="showRatingOptions(field)"><br></div>
<div class="row" ng-if="showRatingOptions(field)">
<div class="col-md-9 col-sm-9">{{ 'NUM_OF_STEPS' | translate }}</div>
@ -293,7 +292,7 @@
</div>
<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">
<label class="btn col-xs-5">
<input type="radio" ng-value="true"
@ -309,6 +308,106 @@
</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>
</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 col-xs-12 header-numbers">
Overview Analytics
</div>
<div class="row col-xs-12 header-title">
<div class="col-xs-12 header-title">
<div class="col-xs-3">
{{ 'TOTAL_VIEWS' | translate }}
</div>
@ -20,7 +17,7 @@
{{ 'AVERAGE_TIME_TO_COMPLETE' | translate }}
</div>
</div>
<div class="row col-xs-12 header-numbers">
<div class="col-xs-12 header-numbers">
<div class="col-xs-3">
{{myform.analytics.views}}
</div>
@ -37,10 +34,7 @@
{{AverageTimeElapsed | secondsToDateTime | date:'mm:ss'}}
</div>
</div>
<div class="row col-xs-12 header-numbers">
Device Analytics
</div>
<div class="row col-xs-12 detailed-title">
<div class="col-xs-12 detailed-title">
<div class="col-xs-3">
{{ 'DESKTOP_AND_LAPTOP' | translate }}
</div>
@ -58,7 +52,7 @@
</div>
</div>
<div class="row col-xs-12 detailed-row">
<div class="col-xs-12 detailed-row">
<div class="col-xs-3">
<div class="row header">
{{ 'UNIQUE_VISITS' | translate }}
@ -96,7 +90,7 @@
</div>
</div>
<div class="row col-xs-12 detailed-row">
<div class="col-xs-12 detailed-row">
<div class="col-xs-3">
<div class="row header">
{{ 'RESPONSES' | translate }}
@ -134,7 +128,7 @@
</div>
</div>
<div class="row col-xs-12 detailed-row">
<div class="col-xs-12 detailed-row">
<div class="col-xs-3">
<div class="row header">
{{ 'COMPLETION_RATE' | translate }}
@ -172,7 +166,7 @@
</div>
</div>
<div class="row col-xs-12 detailed-row">
<div class="col-xs-12 detailed-row">
<div class="col-xs-3">
<div class="row header">
{{ 'AVERAGE_TIME_TO_COMPLETE' | translate }}
@ -210,10 +204,7 @@
</div>
</div>
<div class="row col-xs-12 text-center" style="font-size:5em;">
Field Analytics
</div>
<div class="row col-xs-12 field-title-row">
<div class="col-xs-12 field-title-row">
<div class="col-xs-3">
<strong>{{ 'FIELD_TITLE' | translate }}</strong>
@ -230,7 +221,7 @@
</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">
{{fieldStats.field.title}}
@ -247,9 +238,6 @@
</div>
</div>
<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="col-xs-2">
<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">
<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 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">
@ -47,8 +54,8 @@
</form>
<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">
<span class="pull-right">
@ -58,21 +65,19 @@
</div>
<div class="row">
<a data-ng-href="#!/forms/{{form._id}}/admin/create"
ng-style="{ 'color': form.design.colors.answerColor }"
class="title-row col-xs-12">
<h4 class="list-group-item-heading" data-ng-bind="form.title"></h4>
</a>
</div>
<div class="row footer">
<div class="col-xs-12 details-row">
<a data-ng-href="#!/forms/{{form._id}}/admin/create"
class="title-row col-xs-12">
<h4 class="list-group-item-heading" data-ng-bind="form.title"></h4>
</a>
<div class="col-xs-12 responses-row">
<small class="list-group-item-text">
{{ 'CREATED_ON' | translate }}
<span data-ng-bind="form.created | date:'shortDate'"></span>
<span> {{ form.submissions.length }} responses </span>
</small>
<br>
<br>
<small ng-if="!form.isLive" class="list-group-item-text">
<span> Form Paused </span>
</small>
</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 {
border-style: dashed;
border-color: #a9a9a9;
@ -302,6 +318,8 @@ div.config-form .row.field {
}
.admin-form .page-header {
border: none;
margin-top: none;
margin-bottom: none;
}
/*Styles for admin view tabs */
@ -417,18 +435,28 @@ section.public-form .form-submitted {
section.public-form .btn {
border: 1px solid;
}
.form-item {
border-radius: 5px;
text-align: center;
border-bottom: 6px inset #ccc;
background-color: #eee;
width: 180px;
/*width:100%;*/
position: relative;
height: 215px;
/*padding-bottom: 25%;*/
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']{
width: inherit;
color:black;
@ -436,17 +464,22 @@ section.public-form .btn {
}
.form-item.create-new {
background-color: rgb(131,131,131);
background-color: #3FA2F7;
color: white;
}
.form-item.create-new:hover {
background-color: #276496;
}
/*CREATE-NEW FORM MODAL*/
.form-item.create-new.new-form {
background-color: rgb(300,131,131);
z-index: 11;
}
.form-item.create-new.new-form:hover {
background-color: rgb(300,100,100);
background-color: #3079b5;
}
.form-item.new-form input[type='text'] {
margin-top:0.2em;
@ -513,10 +546,23 @@ section.public-form .btn {
.activeField input {
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 {
border-bottom: 8px inset #ccc;
.form-item.create-new:hover {
background-color: #d9d9d9;
color: white;
}
.form-item.create-new:hover {
@ -529,15 +575,17 @@ section.public-form .btn {
left: 30%;
}
.form-item .title-row{
.form-item .title-row {
position: relative;
top: 15px;
padding-top:3em;
padding-bottom:3.65em;
padding-bottom:1em;
}
.form-item .title-row h4 {
font-size: 1.3em;
}
.form-item .responses-row {
}
.form-item.create-new .title-row{
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'){
switch(scope.field.fieldType){
case 'textfield':
scope.field.input_type = 'text';
scope.input_type = 'text';
break;
case 'email':
scope.field.input_type = 'email';
scope.field.placeholder = 'joesmith@example.com';
scope.input_type = 'email';
scope.placeholder = 'joesmith@example.com';
break;
case 'number':
scope.field.input_type = 'text';
scope.field.validateRegex = /^-?\d+$/;
scope.input_type = 'text';
scope.validateRegex = /^-?\d+$/;
break;
default:
scope.field.input_type = 'url';
scope.field.placeholder = 'http://example.com';
scope.input_type = 'url';
scope.placeholder = 'http://example.com';
break;
}
fieldType = 'textfield';
}
var template = getTemplateUrl(fieldType);
element.html(template).show();
var output = $compile(element.contents())(scope);

View file

View file

View file

View file

View file

View file

@ -1,8 +1,8 @@
<div class="textfield field row"
ng-click="setActiveField(field._id, index, true)">
<div class="col-xs-12 field-title row-fluid" ng-style="{'color': design.colors.questionColor}">
<h3 class="col-xs-12">
<small class="field-number">
<h3 class="col-xs-12">
<small class="field-number">
{{index+1}}
<i class="fa fa-angle-double-right" aria-hidden="true"></i>
</small>
@ -12,54 +12,54 @@
<span class="required-error" ng-show="!field.required">
({{ 'OPTIONAL' | translate }})
</span>
</h3>
</h3>
<p class="col-xs-12">
<small>{{field.description}}</small>
</p>
</div>
<div class="col-xs-12 field-input">
</div>
<div class="col-xs-12 field-input">
<input ng-style="{'color': design.colors.answerColor, 'border-color': design.colors.answerColor}"
name="{{field.fieldType}}{{index}}"
type="{{field.input_type}}"
ng-pattern="field.validateRegex"
placeholder="{{field.placeholder}}"
ng-class="{ 'no-border': !!field.fieldValue }"
class="focusOn text-field-input"
ng-model="field.fieldValue"
ng-model-options="{ debounce: 250 }"
value="field.fieldValue"
ng-focus="setActiveField(field._id, index, true)"
on-enter-or-tab-key="nextField()"
on-tab-and-shift-key="prevField()"
ng-required="field.required"
ng-disabled="field.disabled"
aria-describedby="inputError2Status">
</div>
<div class="col-xs-12">
<div ng-show="forms.myForm.{{field.fieldType}}{{index}}.$invalid && !!forms.myForm.{{field.fieldType}}{{index}}.$viewValue " class="alert alert-danger" role="alert">
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
<span class="sr-only">Error:</span>
name="{{field.fieldType}}{{index}}"
type="{{input_type}}"
ng-pattern="validateRegex"
placeholder="{{placeholder}}"
ng-class="{ 'no-border': !!field.fieldValue }"
class="focusOn text-field-input"
ng-model="field.fieldValue"
ng-model-options="{ debounce: 250 }"
value="field.fieldValue"
ng-focus="setActiveField(field._id, index, true)"
on-enter-or-tab-key="nextField()"
on-tab-and-shift-key="prevField()"
ng-required="field.required"
ng-disabled="field.disabled"
aria-describedby="inputError2Status">
</div>
<div class="col-xs-12">
<div ng-show="forms.myForm.{{field.fieldType}}{{index}}.$invalid && !!forms.myForm.{{field.fieldType}}{{index}}.$viewValue " class="alert alert-danger" role="alert">
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
<span class="sr-only">Error:</span>
<span ng-if="field.fieldType == 'email'"> {{ 'ERROR_EMAIL_INVALID' | translate }} </span>
<span ng-if="field.validateRegex"> {{ 'ERROR_NOT_A_NUMBER' | translate }} </span>
<span ng-if="field.fieldType == 'link'"> {{ 'ERROR_URL_INVALID' | translate }} </span>
</div>
</div>
</div>
<div>
<div class="btn btn-lg btn-default hidden-xs"
style="padding: 4px; margin-top:8px; background: rgba(255,255,255,0.5)">
<button ng-disabled="!field.fieldValue || forms.myForm.{{field.fieldType}}{{$index}}.$invalid"
ng-style="{'background-color':design.colors.buttonColor, 'color':design.colors.buttonTextColor}"
ng-click="$root.nextField()"
class="btn col-sm-5 col-xs-5">
{{ 'OK' | translate }} <i class="fa fa-check"></i>
</button>
<div class="col-xs-6 col-sm-3" style="margin-top:0.2em">
<small style="color:#ddd; font-size:70%">
{{ 'ENTER' | translate }}
</small>
</div>
</div>
</div>
</div>
<div>
<div class="btn btn-lg btn-default col-xs-12 col-sm-4 hidden-xs"
style="padding: 4px; margin-top:8px; background: rgba(255,255,255,0.5)">
<button ng-disabled="!field.fieldValue || forms.myForm.{{field.fieldType}}{{$index}}.$invalid"
ng-style="{'background-color':design.colors.buttonColor, 'color':design.colors.buttonTextColor}"
ng-click="$root.nextField()"
class="btn col-sm-5 col-xs-5">
{{ 'OK' | translate }} <i class="fa fa-check"></i>
</button>
<div class="col-xs-6 col-sm-3" style="margin-top:0.2em">
<small style="color:#ddd; font-size:70%">
{{ 'ENTER' | translate }}
</small>
</div>
</div>
</div>

View file

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

View file

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

View file

@ -4,7 +4,8 @@ angular.module('users').config(['$translateProvider', function ($translateProvid
$translateProvider.translations('en', {
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',
CURRENT_PASSWORD_LABEL: 'Current Password',
NEW_PASSWORD_LABEL: 'New Password',
@ -21,6 +22,9 @@ angular.module('users').config(['$translateProvider', function ($translateProvid
SIGNIN_HEADER_TEXT: 'Sign in',
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',
PROFILE_SAVE_SUCCESS: 'Profile saved successfully',

View file

@ -2,8 +2,9 @@
angular.module('users').controller('PasswordController', ['$scope', '$stateParams', '$state', 'User',
function($scope, $stateParams, $state, User) {
$scope.error = '';
$scope.error = '';
// Submit forgotten password account id
$scope.askForPasswordReset = function() {
User.askForPasswordReset($scope.credentials).then(
@ -37,4 +38,4 @@ angular.module('users').controller('PasswordController', ['$scope', '$stateParam
);
};
}
]);
]);

View file

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

View file

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

View file

@ -23,7 +23,6 @@
<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 class="col-md-12">
<form class="signin form-horizontal" autocomplete="off">
@ -33,14 +32,14 @@
</div>
<div class="form-group">
<!--<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 class="form-group">
<!--<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">
</div>
<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 class="text-center forgot-password">
<a ui-sref="forgot">{{ 'FORGOT_PASSWORD_LINK' | translate }}</a>

View file

@ -29,7 +29,7 @@
<p>
<strong>{{ 'BEFORE_YOU_CONTINUE' | translate }}</strong> <a href="mail:polydaic@gmail.com">polydaic@gmail.com</a></p>
<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>
</button>
</div>

View file

@ -30,29 +30,6 @@
{{'SIGNUP_ERROR_TEXT' | translate}}: <br>
<strong data-ng-bind="error"></strong>
</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">
<!--<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">
@ -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">
</div>
<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>
</fieldset>

View file

@ -1,22 +1,27 @@
<section class="auth row" data-ng-controller="PasswordController">
<h3 class="col-md-12 text-center">{{ 'PASSWORD_RESTORE_HEADER' | translate }}</h3>
<p class="small text-center">{{ 'ENTER_YOUR_EMAIL' | translate }}</p>
<div class="col-xs-offset-2 col-xs-8 col-md-offset-3 col-md-6">
<form data-ng-submit="askForPasswordReset()" class="signin form-horizontal" autocomplete="off">
<fieldset>
<div class="form-group">
<input type="text" id="username" name="username" class="form-control" data-ng-model="credentials.username" placeholder="{{ 'USERNAME_LABEL' | translate }}">
</div>
<div class="text-center form-group">
<button type="submit" class="btn btn-primary">{{ 'UPDATE_PASSWORD_LABEL' | translate }}</button>
</div>
<div data-ng-show="error" class="text-center text-danger">
<strong>{{error}}</strong>
</div>
<div data-ng-show="success" class="text-center text-success">
<strong>{{success}}</strong>
</div>
</fieldset>
</form>
<section class="auth valign-wrapper" data-ng-controller="PasswordController">
<div class="row valign">
<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 class="col-md-12">
<form data-ng-submit="askForPasswordReset()" autocomplete="off">
<fieldset>
<div class="form-group">
<input type="text" id="username" name="username" class="form-control" data-ng-model="credentials.username" placeholder="{{ 'USERNAME_OR_EMAIL_LABEL' | translate }}">
</div>
<div class="text-center form-group">
<button type="submit" class="btn btn-signup btn-rounded btn-block">{{ 'PASSWORD_RESTORE_HEADER' | translate }}</button>
</div>
<div data-ng-show="error" class="text-center text-danger">
<strong>{{error}}</strong>
</div>
<div data-ng-show="success" class="text-center text-success">
<strong>{{success}}</strong>
</div>
</fieldset>
</form>
</div>
</div>
</div>
</section>

View file

@ -1,7 +1,7 @@
<header data-ng-include="'/static/modules/core/views/header.client.view.html'"></header>
<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">
<form name="userForm" data-ng-submit="updateUserProfile(userForm.$valid)" class="signin form-horizontal" autocomplete="off">
<fieldset>
@ -15,18 +15,18 @@
<div class="form-group row">
<div class="col-xs-7 field-title">
<b>{{ 'FIRST_NAME_LABEL' | translate }}</b>
{{ 'FIRST_NAME_LABEL' | translate }}
</div>
<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 class="form-group row">
<div class="col-xs-7 field-title">
<b>Last Name</b>
{{ 'LAST_NAME_LABEL' | translate }}
</div>
<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 class="row">
@ -34,7 +34,7 @@
</div>
<div class="row form-group">
<div class="col-xs-7 field-title">
<b>{{ 'LANGUAGE_LABEL' | translate }}</b>
{{ 'LANGUAGE_LABEL' | translate }}
</div>
<div class="col-xs-12 field-input">
<select ng-model="user.language" required>
@ -49,25 +49,28 @@
<div class="row form-group">
<div class="col-xs-7 field-title">
<b>Username</b>
{{ 'USERNAME_LABEL' | translate }}
</div>
<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 class="row form-group">
<div class="col-xs-7 field-title">
<b>{{ 'EMAIL_LABEL' | translate }}</b>
{{ 'EMAIL_LABEL' | translate }}
</div>
<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 class="text-center form-group">
<button type="submit" class="btn btn-large btn-primary" style="font-size:1.6em;">
{{ 'SUBMIT_BTN' | translate }}
<button type="submit" class="btn btn-signup btn-rounded">
{{ 'SAVE_CHANGES' | translate }}
</button>
<button type="none" ng-click="cancel()" class="btn btn-rounded">
{{ 'CANCEL' | translate }}
</button>
</div>

View file

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

View file

@ -1,14 +1,38 @@
<section class="auth" data-ng-controller="VerifyController" ng-init="validateVerifyToken()">
<section class="row text-center" ng-if="isResetSent">
<h3 class="col-md-12"></h3>
<a href="/#!/signin" class="col-md-12">{{ 'CONTINUE_TO_LOGIN' | translate }}</a>
<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>
<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 class="row text-center" ng-if="!isResetSent">
<h3 class="col-md-12">{{ 'VERIFY_ERROR' | translate }}</h3>
<a href="/#!/verify" class="col-md-6">{{ 'REVERIFY_ACCOUNT_LINK' | translate }}</a>
<a href="/#!/signin" class="col-md-6">{{ 'SIGNIN_BTN' | translate }}</a>
<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>
<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>

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"
}
}
}
}
}
}
}

View file

@ -13,8 +13,6 @@ var init = require('../config/init')(),
fs = require('fs-extra'),
chalk = require('chalk');
console.log(config);
// Bootstrap db connection
var db = mongoose.connect(config.db.uri, config.db.options, function(err) {
if (err) {