Merge branch 'master' into emailNotifications
8
.dockerignore
Normal file
|
@ -0,0 +1,8 @@
|
|||
.git
|
||||
.idea
|
||||
.vagrant
|
||||
coverage
|
||||
design
|
||||
e2e_coverage
|
||||
Vagrantfile
|
||||
Procfile
|
57
Dockerfile
|
@ -1,25 +1,17 @@
|
|||
# Build:
|
||||
# docker build -t tellform -f ./Dockerfile .
|
||||
# docker build -t tellform-prod -f ./Dockerfile-production .
|
||||
#
|
||||
# Run:
|
||||
# docker run -it tellform
|
||||
# docker run -it tellform-prod
|
||||
|
||||
FROM phusion/baseimage:0.9.19
|
||||
MAINTAINER David Baldwynn <team@tellform.com>
|
||||
|
||||
# 3000 = TellForm server, 35729 = livereload, 8080 = node-inspector
|
||||
EXPOSE 3000 35729 8080
|
||||
|
||||
# Set development environment as default
|
||||
ENV NODE_ENV development
|
||||
ENV BASE_URL tellform.dev
|
||||
ENV PORT 3000
|
||||
|
||||
# Install Utilities
|
||||
RUN apt-get update -q \
|
||||
RUN apt-get update -q \
|
||||
&& apt-get install -yqq \
|
||||
curl \
|
||||
ant \
|
||||
default-jdk \
|
||||
git \
|
||||
gcc \
|
||||
make \
|
||||
|
@ -37,34 +29,31 @@ RUN sudo apt-get install -yq nodejs \
|
|||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
# Install MEAN.JS Prerequisites
|
||||
RUN npm install --quiet -g grunt bower && npm cache clean
|
||||
# Install NPM Global Libraries
|
||||
RUN npm install --quiet -g grunt bower pm2 && npm cache clean
|
||||
|
||||
RUN mkdir -p /opt/tellform/public/lib
|
||||
WORKDIR /opt/tellform
|
||||
RUN mkdir -p /opt/tellform/public/lib
|
||||
|
||||
# Add bower.json
|
||||
COPY bower.json /opt/tellform/bower.json
|
||||
COPY .bowerrc /opt/tellform/.bowerrc
|
||||
|
||||
COPY ./process.yml /opt/tellform/process.yml
|
||||
COPY ./app /opt/tellform/app
|
||||
COPY ./public /opt/tellform/public
|
||||
COPY ./config /opt/tellform/config
|
||||
COPY ./gruntfile.js /opt/tellform/gruntfile.js
|
||||
COPY ./server.js /opt/tellform/server.js
|
||||
COPY ./scripts/create_admin.js /opt/tellform/scripts/create_admin.js
|
||||
|
||||
# Copies the local package.json file to the container
|
||||
# and utilities docker container cache to not needing to rebuild
|
||||
# and install node_modules/ everytime we build the docker, but only
|
||||
# when the local package.json file changes.
|
||||
# Add npm package.json
|
||||
COPY package.json /opt/tellform/package.json
|
||||
RUN npm install
|
||||
COPY ./package.json /opt/tellform/package.json
|
||||
RUN npm install --only=production --quiet
|
||||
|
||||
# Add bower.json
|
||||
COPY bower.json /opt/tellform/bower.json
|
||||
COPY .bowerrc /opt/tellform/.bowerrc
|
||||
|
||||
COPY ./app /opt/tellform/app
|
||||
COPY ./public /opt/tellform/public
|
||||
COPY ./config /opt/tellform/config
|
||||
COPY ./gruntfile.js /opt/tellform/gruntfile.js
|
||||
COPY ./server.js /opt/tellform/server.js
|
||||
COPY ./.env /opt/tellform/.env
|
||||
COPY ./scripts/create_admin.js /opt/tellform/scripts/create_admin.js
|
||||
|
||||
# Run Development TellForm server
|
||||
COPY ./dev_entrypoint.sh /dev_entrypoint.sh
|
||||
RUN chmod +x /dev_entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/dev_entrypoint.sh"]
|
||||
# Run TellForm server
|
||||
CMD ["node", "server.js"]
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
# Build:
|
||||
# docker build -t tellform-prod -f ./Dockerfile-production .
|
||||
#
|
||||
# Run:
|
||||
# docker run -it tellform-prod
|
||||
|
||||
FROM phusion/baseimage:0.9.19
|
||||
MAINTAINER David Baldwynn <team@tellform.com>
|
||||
|
||||
# 4545 = TellForm server
|
||||
EXPOSE 4545
|
||||
|
||||
# Set development environment as default
|
||||
ENV NODE_ENV production
|
||||
ENV PORT 4545
|
||||
ENV BASE_URL tellform.com
|
||||
|
||||
# Install Utilities
|
||||
RUN apt-get update -q \
|
||||
&& apt-get install -yqq \
|
||||
curl \
|
||||
ant \
|
||||
default-jdk \
|
||||
git \
|
||||
gcc \
|
||||
make \
|
||||
build-essential \
|
||||
libkrb5-dev \
|
||||
python \
|
||||
sudo \
|
||||
apt-utils \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
# Install nodejs
|
||||
RUN curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
|
||||
RUN sudo apt-get install -yq nodejs \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
# Install MEAN.JS Prerequisites
|
||||
RUN npm install --quiet -g grunt bower pm2 && npm cache clean
|
||||
|
||||
RUN mkdir -p /opt/tellform/public/lib
|
||||
WORKDIR /opt/tellform
|
||||
|
||||
# Copies the local package.json file to the container
|
||||
# and utilities docker container cache to not needing to rebuild
|
||||
# and install node_modules/ everytime we build the docker, but only
|
||||
# when the local package.json file changes.
|
||||
# Add npm package.json
|
||||
COPY package.json /opt/tellform/package.json
|
||||
RUN npm install --production
|
||||
RUN mv ./node_modules ./node_modules.tmp && mv ./node_modules.tmp ./node_modules && npm install
|
||||
|
||||
# Add bower.json
|
||||
COPY bower.json /opt/tellform/bower.json
|
||||
COPY .bowerrc /opt/tellform/.bowerrc
|
||||
|
||||
COPY ./app /opt/tellform/app
|
||||
COPY ./public /opt/tellform/public
|
||||
COPY ./config /opt/tellform/config
|
||||
COPY ./gruntfile.js /opt/tellform/gruntfile.js
|
||||
COPY ./server.js /opt/tellform/server.js
|
||||
COPY ./.env /opt/tellform/.env
|
||||
COPY ./scripts/create_admin.js /opt/tellform/scripts/create_admin.js
|
||||
|
||||
RUN grunt build
|
||||
|
||||
# Run TellForm server
|
||||
CMD ["pm2-docker","process.yml"]
|
38
README.md
|
@ -1,8 +1,8 @@
|
|||
TellForm 2.0.0
|
||||
TellForm 2.1.0
|
||||
========
|
||||
|
||||
[![Build Status](https://travis-ci.org/tellform/tellform.svg?branch=master)](https://travis-ci.org/tellform/tellform)
|
||||
![Project Status](https://img.shields.io/badge/status-2.0.0-green.svg)
|
||||
![Project Status](https://img.shields.io/badge/status-2.1.0-green.svg)
|
||||
[![Code Climate](https://codeclimate.com/github/whitef0x0/tellform/badges/gpa.svg)](https://codeclimate.com/github/whitef0x0/tellform)
|
||||
[![Codacy Badge](https://api.codacy.com/project/badge/Grade/3491e86eb7194308b8fc80711d736ede)](https://www.codacy.com/app/david-baldwin/tellform?utm_source=github.com&utm_medium=referral&utm_content=whitef0x0/tellform&utm_campaign=Badge_Grade)
|
||||
[![Gitter](https://badges.gitter.im/whitef0x0/tellform.svg)](https://gitter.im/whitef0x0/tellform?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
|
@ -18,6 +18,7 @@ TellForm 2.0.0
|
|||
- [Quickstart](#quickstart)
|
||||
- [Deploying with Docker](#deploying-with-docker)
|
||||
- [Testing your Application](#testing-your-application)
|
||||
- [Advanced Configuration](#configuration)
|
||||
- [Where to Get Help](#where-to-get-help)
|
||||
- [Sponsors](#sponsors)
|
||||
- [Backers](#backers)
|
||||
|
@ -166,6 +167,39 @@ To calculate your client-side test coverage with Istanbul, run the coverage task
|
|||
$ grunt coverage:client
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
TellForm's configuration is done with environment variables. To set an option for TellForm, open/create your .env file and set add `ENV_VAR=somevalue` to set the ENV_VAR variable to the value `somevalue`.
|
||||
|
||||
| Property | Valid Values | Default Value | Description | Required? |
|
||||
|-------------------------|--------------------------------------------------------|----------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------|--------------------------------------------|
|
||||
| NODE_ENV | "development", "production", "test" or "secure" | development | Set which version of the app you want to run (either secure/SSL, dev, prod or test) | No |
|
||||
| SESSION_SECRET | Any string | CHANGE_ME_PLEASE | Value used to compute session hash | No |
|
||||
| BASE_URL | A valid URL | localhost:3000 | URL where the admin panel will live | Yes |
|
||||
| MONGODB_URI | A valid MongoDB URI | localhost/mean | URI of the MONGODB server/db that your server will use | Yes |
|
||||
| REDIS_URL | A valid Redis URI | redis://127.0.0.1:6379 | URI of the Redis instance that your server will use | Only if ENABLE_CLUSTER_MODE=TRUE |
|
||||
| SOCKET_PORT | A valid port number from 0 - 65535 | 20523 | Port that your SocketIO server will bind to | No |
|
||||
| SOCKET_URL | A valid URL | ws.tellform.com | Url that your SocketIO server will bind to | No |
|
||||
| SIGNUP_DISABLED | "TRUE" or "FALSE" | FALSE | Set this flag to disable signups. | No |
|
||||
| SUBDOMAINS_DISABLED | "TRUE" or "FALSE" | FALSE | Set this flag to disable subdomains. (Useful if hosting behind an uncontrolled domain or without a wildcard SSL cert) | No |
|
||||
| ENABLE_CLUSTER_MODE | "TRUE" or "FALSE" | FALSE | Disable support for running TellForm with pm2's cluster mode. Disabling this allows you to not run a Redis instance. | No |
|
||||
| MAILER_EMAIL_ID | A string | N/A | Username credential for the SMTP MAIL service used to send signup/verification/lost password emails. | Yes |
|
||||
| MAILER_PASSWORD | A string | | Password credential for the SMTP MAIL service used to send signup/verification/lost password emails. | Yes |
|
||||
| MAILER_FROM | A valid email | noreply@tellform.com | Email address that all mail should be sent from. | No |
|
||||
| MAILER_SERVICE_PROVIDER | A service from https://nodemailer.com/smtp/well-known/ | | A "well-known" email service that is supported by nodemail. If MAILER_SMTP_HOST is enabled, this is ignored. | Only if MAILER_SMTP_HOST is not set |
|
||||
| MAILER_SMTP_HOST | A valid URL | | URL to the SMTP server of your choice | Only if MAILER_SERVICE_PROVIDER is not set |
|
||||
| MAILER_SMTP_PORT | A valid port number from 0 - 65535 | | Port of the SMTP server of your choice. | Only if MAILER_SMTP_HOST is set |
|
||||
| MAILER_SMTP_SECURE | "TRUE" or "FALSE" | FALSE | Boolean that enables/disables SSL support for your SMTP client. | Only if MAILER_SMTP_HOST is set |
|
||||
| CREATE_ADMIN | "TRUE" or "FALSE" | FALSE | Setting this variable will create a admin user on startup with credentials as specified below | No |
|
||||
| ADMIN_EMAIL | A valid email | admin@admin.com | Email of generated admin user | No |
|
||||
| ADMIN_USERNAME | A string | root | Username of generated admin user | No |
|
||||
| ADMIN_PASSWORD | A string | root | Password of generated admin user | No |
|
||||
| APP_NAME | A string | TellForm | Sets the <title> property of your webapp. | No |
|
||||
| APP_DESC | A string | Opensource form builder alternative to TypeForm | Sets the,property of your webapp. | No |
|
||||
| APP_KEYWORDS | A comma-seperated list of phrases/words | typeform, pdfs, forms, opensource, formbuilder, google forms, nodejs | Sets the value of the <meta> description attribute. | No |
|
||||
| RAVEN_DSN | A valid Sentry.io DSN | N/A | Set this to your Sentry.io Public DSN to enable remote logging | No |
|
||||
| GOOGLE_ANALYTICS_ID | A valid Google Analytics ID | N/A | Set this to your GA id to enable GA tracking on your TellForm instance | No |
|
||||
|
||||
## Where to get help
|
||||
|
||||
[Gitter Chat](https://gitter.im/whitef0x0/tellform)
|
||||
|
|
|
@ -15,7 +15,7 @@ exports.index = function(req, res) {
|
|||
exports.form = function(req, res) {
|
||||
|
||||
//Allow form to be embedded
|
||||
res.set('X-Frame-Options', 'GOFORIT');
|
||||
res.removeHeader('X-Frame-Options');
|
||||
|
||||
res.render('form', {
|
||||
user: req.user || null,
|
||||
|
|
|
@ -57,7 +57,6 @@ exports.createSubmission = function(req, res) {
|
|||
}
|
||||
var submission = new FormSubmission({
|
||||
form: req.body._id,
|
||||
title: req.body.title,
|
||||
form_fields: req.body.form_fields,
|
||||
timeElapsed: timeElapsed,
|
||||
percentageComplete: req.body.percentageComplete,
|
||||
|
@ -66,7 +65,6 @@ exports.createSubmission = function(req, res) {
|
|||
device: req.body.device
|
||||
});
|
||||
|
||||
|
||||
submission.save(function(err, submission){
|
||||
if (err) {
|
||||
console.error(err.message);
|
||||
|
@ -120,9 +118,10 @@ exports.listSubmissions = function(req, res) {
|
|||
* Create a new form
|
||||
*/
|
||||
exports.create = function(req, res) {
|
||||
debugger;
|
||||
|
||||
if(!req.body.form){
|
||||
return res.status(400).send({
|
||||
return res.status(401).send({
|
||||
message: 'Invalid Input'
|
||||
});
|
||||
}
|
||||
|
@ -131,13 +130,14 @@ exports.create = function(req, res) {
|
|||
form.admin = req.user._id;
|
||||
|
||||
form.save(function(err) {
|
||||
debugger;
|
||||
if (err) {
|
||||
return res.status(405).send({
|
||||
return res.status(500).send({
|
||||
message: errorHandler.getErrorMessage(err)
|
||||
});
|
||||
}
|
||||
|
||||
res.json(form);
|
||||
return res.json(form);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -186,7 +186,7 @@ var readForRender = exports.readForRender = function(req, res) {
|
|||
delete newForm.__v;
|
||||
delete newForm.created;
|
||||
|
||||
if(!newForm.startPage.showStart){
|
||||
if(newForm.startPage && !newForm.startPage.showStart){
|
||||
delete newForm.startPage;
|
||||
}
|
||||
|
||||
|
@ -197,20 +197,31 @@ var readForRender = exports.readForRender = function(req, res) {
|
|||
* Update a form
|
||||
*/
|
||||
exports.update = function(req, res) {
|
||||
|
||||
var form = req.form;
|
||||
var updatedForm = req.body.form;
|
||||
if(form.form_fields === undefined){
|
||||
form.form_fields = [];
|
||||
}
|
||||
|
||||
delete updatedForm.__v;
|
||||
delete updatedForm.created;
|
||||
if(form.analytics === undefined){
|
||||
form.analytics = {
|
||||
visitors: [],
|
||||
gaCode: ''
|
||||
}
|
||||
}
|
||||
|
||||
if (req.body.changes) {
|
||||
var formChanges = req.body.changes;
|
||||
|
||||
formChanges.forEach(function (change) {
|
||||
diff.applyChange(form, true, change);
|
||||
diff.applyChange(form._doc, true, change);
|
||||
});
|
||||
} else {
|
||||
//Unless we have 'admin' privileges, updating form admin is disabled
|
||||
|
||||
delete updatedForm.__v;
|
||||
delete updatedForm.created;
|
||||
//Unless we have 'admin' privileges, updating the form's admin is disabled
|
||||
if(updatedForm && req.user.roles.indexOf('admin') === -1) {
|
||||
delete updatedForm.admin;
|
||||
}
|
||||
|
@ -222,7 +233,7 @@ exports.update = function(req, res) {
|
|||
|
||||
//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++){
|
||||
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;
|
||||
|
@ -233,7 +244,7 @@ exports.update = function(req, res) {
|
|||
|
||||
form.save(function(err, savedForm) {
|
||||
if (err) {
|
||||
res.status(405).send({
|
||||
res.status(500).send({
|
||||
message: errorHandler.getErrorMessage(err)
|
||||
});
|
||||
} else {
|
||||
|
@ -333,8 +344,7 @@ exports.formByIDFast = function(req, res, next, id) {
|
|||
}
|
||||
Form.findById(id)
|
||||
.lean()
|
||||
.cache()
|
||||
.select('emailNotifications title language form_fields startPage endPage hideFooter isLive design analytics.gaCode emailNotifications')
|
||||
.select('title language form_fields startPage endPage hideFooter isLive design analytics.gaCode')
|
||||
.exec(function(err, form) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
|
|
|
@ -8,7 +8,16 @@ var errorHandler = require('../errors.server.controller'),
|
|||
passport = require('passport'),
|
||||
config = require('../../../config/config'),
|
||||
User = mongoose.model('User'),
|
||||
tokgen = require('../../libs/tokenGenerator');
|
||||
tokgen = require('../../libs/tokenGenerator'),
|
||||
fs = require('fs');
|
||||
|
||||
require.extensions['.html'] = function (module, filename) {
|
||||
module.exports = fs.readFileSync(filename, 'utf8');
|
||||
};
|
||||
|
||||
var welcomeEmail = require("../../views/welcome.email.view.html");
|
||||
var verificationEmail = require("../../views/verification.email.view.html");
|
||||
|
||||
|
||||
|
||||
var nev = require('email-verification')(mongoose);
|
||||
|
@ -26,16 +35,15 @@ var config_nev = function () {
|
|||
transportOptions: config.mailer.options,
|
||||
verifyMailOptions: {
|
||||
from: config.mailer.from,
|
||||
subject: 'Confirm your account',
|
||||
html: '<p>Please verify your account by clicking <a href="http://${URL}">this link</a>. If you are unable to do so, copy and ' +
|
||||
'paste the following link into your browser:</p><p>${URL}</p>',
|
||||
subject: '✔ Activate your new TellForm account!',
|
||||
html: verificationEmail,
|
||||
text: 'Please verify your account by clicking the following link, or by copying and pasting it into your browser: ${URL}'
|
||||
},
|
||||
|
||||
confirmMailOptions: {
|
||||
from: config.mailer.from,
|
||||
subject: 'Account successfully verified!',
|
||||
html: '<p>Your account has been successfully verified.</p>',
|
||||
subject: '✔ Welcome to {{app.title}}!',
|
||||
html: welcomeEmail,
|
||||
text: 'Your account has been successfully verified.'
|
||||
},
|
||||
verifySendMailCallback: function(err, info) {
|
||||
|
|
|
@ -18,7 +18,7 @@ var smtpTransport = nodemailer.createTransport(config.mailer.options);
|
|||
/**
|
||||
* Forgot for reset password (forgot POST)
|
||||
*/
|
||||
exports.forgot = function(req, res, next) {
|
||||
exports.forgot = function(req, res) {
|
||||
async.waterfall([
|
||||
// Generate random token
|
||||
function(done) {
|
||||
|
@ -81,22 +81,33 @@ exports.forgot = function(req, res, next) {
|
|||
subject: 'Password Reset',
|
||||
html: emailHTML
|
||||
};
|
||||
smtpTransport.sendMail(mailOptions, function(err) {
|
||||
if (!err) {
|
||||
res.send({
|
||||
message: 'An email has been sent to ' + user.email + ' with further instructions.'
|
||||
});
|
||||
} else {
|
||||
return res.status(400).send({
|
||||
message: 'Failure sending email'
|
||||
});
|
||||
}
|
||||
|
||||
done(err);
|
||||
var userEmail = user.email;
|
||||
var user = userEmail.split('@')[0];
|
||||
var domain = userEmail.split('@')[1];
|
||||
|
||||
var obfuscatedUser = user.substring(0, 1) + user.substring(1).replace(/./g, '*');
|
||||
var domainName = domain.split('.')[0];
|
||||
var tld = domain.split('.')[1];
|
||||
|
||||
var obfuscatedDomainName = domainName.replace(/./g, '*');
|
||||
var obfuscatedEmail = obfuscatedUser + '@' + obfuscatedDomainName + '.' + tld;
|
||||
|
||||
smtpTransport.sendMail(mailOptions, function(err) {
|
||||
done(err, obfuscatedEmail);
|
||||
});
|
||||
}
|
||||
], function(err, obfuscatedEmail) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
return res.status(400).send({
|
||||
message: 'Couldn\'t send reset password email due to internal server errors. Please contact support at team@tellform.com.'
|
||||
});
|
||||
} else {
|
||||
return res.send({
|
||||
message: 'An email has been sent to ' + obfuscatedEmail + ' with further instructions.'
|
||||
});
|
||||
}
|
||||
], function(err) {
|
||||
if (err) return next(err);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
39
app/libs/timestamp.server.plugin.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
'use strict';
|
||||
|
||||
// Plugin
|
||||
module.exports = function timestamp (schema, options) {
|
||||
options || (options = {})
|
||||
|
||||
// Options
|
||||
var fields = {}
|
||||
, createdPath = options.createdPath || 'created'
|
||||
, modifiedPath = options.modifiedPath || 'modified'
|
||||
, useVirtual = (options.useVirtual !== undefined)
|
||||
? options.useVirtual
|
||||
: true
|
||||
|
||||
// Add paths to schema if not present
|
||||
if (!schema.paths[createdPath]) {
|
||||
fields[modifiedPath] = { type: Date }
|
||||
}
|
||||
if (useVirtual) {
|
||||
// Use the ObjectID for extracting the created time
|
||||
schema.virtual(createdPath).get(function () {
|
||||
return new Date(this._id.generationTime * 1000)
|
||||
})
|
||||
} else {
|
||||
if (!schema.paths[createdPath]) {
|
||||
fields[createdPath] = {
|
||||
type: Date
|
||||
, default: Date.now
|
||||
}
|
||||
}
|
||||
}
|
||||
schema.add(fields)
|
||||
|
||||
// Update the modified timestamp on save
|
||||
schema.pre('save', function (next) {
|
||||
this[modifiedPath] = new Date
|
||||
next()
|
||||
})
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
var mongoose = require('mongoose'),
|
||||
Schema = mongoose.Schema,
|
||||
_ = require('lodash'),
|
||||
mUtilities = require('mongoose-utilities'),
|
||||
timeStampPlugin = require('../libs/timestamp.server.plugin'),
|
||||
async = require('async'),
|
||||
Random = require('random-js'),
|
||||
mt = Random.engines.mt19937();
|
||||
|
@ -292,7 +292,7 @@ FormSchema.virtual('analytics.fields').get(function () {
|
|||
return fieldDropoffs;
|
||||
});
|
||||
|
||||
FormSchema.plugin(mUtilities.timestamp, {
|
||||
FormSchema.plugin(timeStampPlugin, {
|
||||
createdPath: 'created',
|
||||
modifiedPath: 'lastModified',
|
||||
useVirtual: false
|
||||
|
@ -313,7 +313,6 @@ FormSchema.pre('save', function (next) {
|
|||
this.language = 'de';
|
||||
break;
|
||||
default:
|
||||
this.language = 'en';
|
||||
break;
|
||||
}
|
||||
next();
|
||||
|
@ -345,54 +344,55 @@ FormSchema.pre('save', function (next) {
|
|||
var that = this;
|
||||
var _original;
|
||||
|
||||
async.series([function(cb) {
|
||||
that.constructor
|
||||
.findOne({_id: that._id}).exec(function (err, original) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
} else if (!original){
|
||||
return next();
|
||||
} else {
|
||||
_original = original;
|
||||
return cb(null);
|
||||
}
|
||||
});
|
||||
},
|
||||
function(cb) {
|
||||
if(that.form_fields && that.isModified('form_fields') && formFieldsAllHaveIds(that.toObject().form_fields)){
|
||||
async.series([
|
||||
function(cb) {
|
||||
that.constructor
|
||||
.findOne({_id: that._id}).exec(function (err, original) {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
} else if (!original){
|
||||
return next();
|
||||
} else {
|
||||
_original = original;
|
||||
return cb(null);
|
||||
}
|
||||
});
|
||||
},
|
||||
function(cb) {
|
||||
if(that.form_fields && that.isModified('form_fields') && formFieldsAllHaveIds(that.toObject().form_fields)){
|
||||
|
||||
var current_form = that.toObject(),
|
||||
old_form_fields = _original.toObject().form_fields,
|
||||
new_ids = _.map(_.map(current_form.form_fields, 'globalId'), function(id){ return ''+id;}),
|
||||
old_ids = _.map(_.map(old_form_fields, 'globalId'), function(id){ return ''+id;}),
|
||||
deletedIds = getDeletedIndexes(old_ids, new_ids);
|
||||
var current_form = that.toObject(),
|
||||
old_form_fields = _original.toObject().form_fields,
|
||||
new_ids = _.map(_.map(current_form.form_fields, 'globalId'), function(id){ return ''+id;}),
|
||||
old_ids = _.map(_.map(old_form_fields, 'globalId'), function(id){ return ''+id;}),
|
||||
deletedIds = getDeletedIndexes(old_ids, new_ids);
|
||||
|
||||
//Check if any form_fileds were deleted
|
||||
if( deletedIds.length > 0 ){
|
||||
//Check if any form_fileds were deleted
|
||||
if( deletedIds.length > 0 ){
|
||||
|
||||
var modifiedSubmissions = [];
|
||||
var modifiedSubmissions = [];
|
||||
|
||||
async.forEachOfSeries(deletedIds,
|
||||
function (deletedIdIndex, key, cb_id) {
|
||||
async.forEachOfSeries(deletedIds,
|
||||
function (deletedIdIndex, key, cb_id) {
|
||||
|
||||
var deleted_id = old_ids[deletedIdIndex];
|
||||
//Find FormSubmissions that contain field with _id equal to 'deleted_id'
|
||||
FormSubmission.
|
||||
find({ form: that, form_fields: {$elemMatch: {globalId: deleted_id} } }).
|
||||
exec(function(err, submissions){
|
||||
if(err) {
|
||||
return cb_id(err);
|
||||
}
|
||||
var deleted_id = old_ids[deletedIdIndex];
|
||||
//Find FormSubmissions that contain field with _id equal to 'deleted_id'
|
||||
FormSubmission.
|
||||
find({ form: that, form_fields: {$elemMatch: {globalId: deleted_id} } }).
|
||||
exec(function(err, submissions){
|
||||
if(err) {
|
||||
return cb_id(err);
|
||||
}
|
||||
|
||||
//Preserve fields that have at least one submission
|
||||
if (submissions.length) {
|
||||
//Add submissions
|
||||
modifiedSubmissions.push.apply(modifiedSubmissions, submissions);
|
||||
}
|
||||
//Preserve fields that have at least one submission
|
||||
if (submissions.length) {
|
||||
//Add submissions
|
||||
modifiedSubmissions.push.apply(modifiedSubmissions, submissions);
|
||||
}
|
||||
|
||||
return cb_id(null);
|
||||
});
|
||||
},
|
||||
return cb_id(null);
|
||||
});
|
||||
},
|
||||
function (err) {
|
||||
if(err){
|
||||
console.error(err.message);
|
||||
|
@ -434,17 +434,20 @@ FormSchema.pre('save', function (next) {
|
|||
}, function (err) {
|
||||
return cb(err);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
} else {
|
||||
return cb(null);
|
||||
}
|
||||
} else {
|
||||
return cb(null);
|
||||
}
|
||||
} else {
|
||||
return cb(null);
|
||||
}
|
||||
}],
|
||||
function(err, results){
|
||||
next(err);
|
||||
],
|
||||
function(err){
|
||||
if(err){
|
||||
return next(err);
|
||||
}
|
||||
next();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -5,13 +5,11 @@
|
|||
*/
|
||||
var mongoose = require('mongoose'),
|
||||
util = require('util'),
|
||||
mUtilities = require('mongoose-utilities'),
|
||||
timeStampPlugin = require('../libs/timestamp.server.plugin'),
|
||||
_ = require('lodash'),
|
||||
Schema = mongoose.Schema,
|
||||
LogicJumpSchema = require('./logic_jump.server.model');
|
||||
|
||||
const UIDGenerator = require('uid-generator');
|
||||
const uidgen3 = new UIDGenerator(256, UIDGenerator.BASE62);
|
||||
LogicJumpSchema = require('./logic_jump.server.model'),
|
||||
tokgen = require('../libs/tokenGenerator');
|
||||
|
||||
var FieldOptionSchema = new Schema({
|
||||
option_id: {
|
||||
|
@ -76,8 +74,7 @@ function BaseFieldSchema(){
|
|||
},
|
||||
title: {
|
||||
type: String,
|
||||
trim: true,
|
||||
required: 'Field Title cannot be blank'
|
||||
trim: true
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
|
@ -106,7 +103,6 @@ function BaseFieldSchema(){
|
|||
},
|
||||
fieldType: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: [
|
||||
'textfield',
|
||||
'date',
|
||||
|
@ -134,7 +130,7 @@ function BaseFieldSchema(){
|
|||
fieldValue: Schema.Types.Mixed
|
||||
});
|
||||
|
||||
this.plugin(mUtilities.timestamp, {
|
||||
this.plugin(timeStampPlugin, {
|
||||
createdPath: 'created',
|
||||
modifiedPath: 'lastModified',
|
||||
useVirtual: false
|
||||
|
@ -196,11 +192,8 @@ FormFieldSchema.pre('validate', function(next) {
|
|||
|
||||
//LogicJump Save
|
||||
FormFieldSchema.pre('save', function(next) {
|
||||
if(this.logicJump && this.logicJump.fieldA) {
|
||||
if(this.logicJump.jumpTo === '') delete this.logicJump.jumpTo;
|
||||
}
|
||||
if(!this.globalId){
|
||||
this.globalId = uidgen3.generateSync();
|
||||
this.globalId = tokgen();
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
|
|
@ -5,17 +5,13 @@
|
|||
*/
|
||||
var mongoose = require('mongoose'),
|
||||
Schema = mongoose.Schema,
|
||||
mUtilities = require('mongoose-utilities'),
|
||||
timeStampPlugin = require('../libs/timestamp.server.plugin'),
|
||||
FieldSchema = require('./form_field.server.model.js');
|
||||
|
||||
/**
|
||||
* Form Submission Schema
|
||||
*/
|
||||
var FormSubmissionSchema = new Schema({
|
||||
title: {
|
||||
type: String
|
||||
},
|
||||
|
||||
form_fields: [FieldSchema],
|
||||
|
||||
form: {
|
||||
|
@ -58,6 +54,19 @@ FormSubmissionSchema.pre('save', function (next) {
|
|||
if(this.form_fields[i].fieldType === 'dropdown'){
|
||||
this.form_fields[i].fieldValue = this.form_fields[i].fieldValue.option_value;
|
||||
}
|
||||
|
||||
delete form_fields[i].validFieldTypes;
|
||||
delete form_fields[i].disabled;
|
||||
delete form_fields[i].required;
|
||||
delete form_fields[i].isSubmission;
|
||||
delete form_fields[i].title;
|
||||
delete form_fields[i].fieldOptions;
|
||||
delete form_fields[i].ratingOptions;
|
||||
delete form_fields[i].logicJump;
|
||||
delete form_fields[i].description;
|
||||
delete form_fields[i].created;
|
||||
delete form_fields[i].lastModified;
|
||||
delete form_fields[i].deletePreserved;
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
@ -68,6 +77,17 @@ FormSubmissionSchema.path('form_fields', {
|
|||
form_fields[i].isSubmission = true;
|
||||
form_fields[i]._id = new mongoose.mongo.ObjectID();
|
||||
|
||||
delete form_fields[i].validFieldTypes;
|
||||
delete form_fields[i].disabled;
|
||||
delete form_fields[i].required;
|
||||
delete form_fields[i].isSubmission;
|
||||
delete form_fields[i].title;
|
||||
delete form_fields[i].fieldOptions;
|
||||
delete form_fields[i].ratingOptions;
|
||||
delete form_fields[i].logicJump;
|
||||
delete form_fields[i].description;
|
||||
delete form_fields[i].created;
|
||||
delete form_fields[i].lastModified;
|
||||
delete form_fields[i].deletePreserved;
|
||||
|
||||
}
|
||||
|
@ -75,7 +95,7 @@ FormSubmissionSchema.path('form_fields', {
|
|||
}
|
||||
});
|
||||
|
||||
FormSubmissionSchema.plugin(mUtilities.timestamp, {
|
||||
FormSubmissionSchema.plugin(timeStampPlugin, {
|
||||
createdPath: 'created',
|
||||
modifiedPath: 'lastModified',
|
||||
useVirtual: false
|
||||
|
|
|
@ -44,6 +44,10 @@ var LogicJumpSchema = new Schema({
|
|||
jumpTo: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'FormField'
|
||||
},
|
||||
enabled: {
|
||||
type: Schema.Types.Boolean,
|
||||
default: false
|
||||
}
|
||||
}, schemaOptions);
|
||||
|
||||
|
|
|
@ -7,8 +7,7 @@ var mongoose = require('mongoose'),
|
|||
Schema = mongoose.Schema,
|
||||
crypto = require('crypto'),
|
||||
config = require('../../config/config'),
|
||||
fs = require('fs-extra'),
|
||||
mUtilities = require('mongoose-utilities'),
|
||||
timeStampPlugin = require('../libs/timestamp.server.plugin'),
|
||||
path = require('path'),
|
||||
querystring = require('querystring'),
|
||||
nodemailer = require('nodemailer');
|
||||
|
@ -143,7 +142,7 @@ UserSchema.virtual('displayName').get(function () {
|
|||
return this.firstName + ' ' + this.lastName;
|
||||
});
|
||||
|
||||
UserSchema.plugin(mUtilities.timestamp, {
|
||||
UserSchema.plugin(timeStampPlugin, {
|
||||
createdPath: 'created',
|
||||
modifiedPath: 'lastModified',
|
||||
useVirtual: false
|
||||
|
|
|
@ -34,34 +34,7 @@ module.exports = function(app) {
|
|||
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']
|
||||
// }));
|
||||
// app.route('/auth/facebook/callback').get(users.oauthCallback('facebook'));
|
||||
|
||||
// // Setting the twitter oauth routes
|
||||
// app.route('/auth/twitter').get(passport.authenticate('twitter'));
|
||||
// app.route('/auth/twitter/callback').get(users.oauthCallback('twitter'));
|
||||
|
||||
// // Setting the google oauth routes
|
||||
// app.route('/auth/google').get(passport.authenticate('google', {
|
||||
// scope: [
|
||||
// 'https://www.googleapis.com/auth/userinfo.profile',
|
||||
// 'https://www.googleapis.com/auth/userinfo.email'
|
||||
// ]
|
||||
// }));
|
||||
// app.route('/auth/google/callback').get(users.oauthCallback('google'));
|
||||
|
||||
// // Setting the linkedin oauth routes
|
||||
// app.route('/auth/linkedin').get(passport.authenticate('linkedin'));
|
||||
// app.route('/auth/linkedin/callback').get(users.oauthCallback('linkedin'));
|
||||
|
||||
// // Setting the github oauth routes
|
||||
// app.route('/auth/github').get(passport.authenticate('github'));
|
||||
// app.route('/auth/github/callback').get(users.oauthCallback('github'));
|
||||
|
||||
|
||||
// Finish by binding the user middleware
|
||||
app.param('userId', users.userByID);
|
||||
};
|
||||
|
|
|
@ -5,13 +5,14 @@
|
|||
*/
|
||||
var mongoose = require('mongoose'),
|
||||
errorHandler = require('../controllers/errors.server.controller'),
|
||||
Form = mongoose.model('Form');
|
||||
Form = mongoose.model('Form'),
|
||||
request = require('request');
|
||||
|
||||
// Create the chat configuration
|
||||
module.exports = function (io, socket) {
|
||||
var visitorsData = {};
|
||||
|
||||
var saveVisitorData = function (data, cb){
|
||||
var saveVisitorData = function (data, socket, cb){
|
||||
Form.findById(data.formId, function(err, form) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
|
@ -25,22 +26,25 @@ module.exports = function (io, socket) {
|
|||
timeElapsed: data.timeElapsed,
|
||||
isSubmitted: data.isSubmitted,
|
||||
language: data.language,
|
||||
ipAddr: data.ipAddr,
|
||||
ipAddr: '',
|
||||
deviceType: data.deviceType
|
||||
};
|
||||
|
||||
form.analytics.visitors.push(newVisitor);
|
||||
|
||||
form.save(function (formSaveErr) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
throw new Error(errorHandler.getErrorMessage(formSaveErr));
|
||||
}
|
||||
|
||||
if(cb){
|
||||
return cb();
|
||||
}
|
||||
});
|
||||
form.form_fields = form.form_fields.map(v => Object.assign({}, v, { fieldValue: null }));
|
||||
|
||||
form.save(function (formSaveErr) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
throw new Error(errorHandler.getErrorMessage(formSaveErr));
|
||||
}
|
||||
|
||||
if(cb){
|
||||
return cb();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -50,6 +54,8 @@ module.exports = function (io, socket) {
|
|||
visitorsData[current_socket.id] = data;
|
||||
visitorsData[current_socket.id].socketId = current_socket.id;
|
||||
visitorsData[current_socket.id].isSaved = false;
|
||||
|
||||
|
||||
if (data.isSubmitted && !data.isSaved) {
|
||||
visitorsData[current_socket.id].isSaved = true;
|
||||
saveVisitorData(data, function() {
|
||||
|
@ -71,3 +77,4 @@ module.exports = function (io, socket) {
|
|||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
require('../../server.js');
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
'use strict';
|
||||
process.env.NODE_ENV = 'test';
|
||||
|
||||
var should = require('should'),
|
||||
lodash = require('lodash'),
|
||||
|
@ -19,8 +18,8 @@ var user, myForm, userSession;
|
|||
|
||||
// Create user credentials
|
||||
var credentials = {
|
||||
username: 'test1234',
|
||||
email: 'test1234@test.com',
|
||||
username: 'aeokjqjqkqaeoaoe',
|
||||
email: 'aeoaekjqjqqjkoeoa@test.com',
|
||||
password: 'password'
|
||||
};
|
||||
|
||||
|
@ -30,18 +29,17 @@ var credentials = {
|
|||
describe('Form Routes Unit tests', function() {
|
||||
|
||||
beforeEach(function(done) {
|
||||
|
||||
// Create a new user
|
||||
user = new User({
|
||||
firstName: 'Full',
|
||||
lastName: 'Name',
|
||||
displayName: 'Full Name',
|
||||
email: credentials.email,
|
||||
username: credentials.username,
|
||||
password: credentials.password,
|
||||
provider: 'local'
|
||||
});
|
||||
|
||||
|
||||
// Save a user to the test db and create new Form
|
||||
user.save(function(err) {
|
||||
should.not.exist(err);
|
||||
|
@ -65,10 +63,12 @@ describe('Form Routes Unit tests', function() {
|
|||
});
|
||||
|
||||
it(' > should not be able to create a Form if not logged in', function(done) {
|
||||
|
||||
userSession.post('/forms')
|
||||
.send({form: myForm})
|
||||
.expect(401)
|
||||
.end(function(FormSaveErr, FormSaveRes) {
|
||||
|
||||
// Call the assertion callback
|
||||
done(FormSaveErr);
|
||||
});
|
||||
|
@ -153,7 +153,7 @@ describe('Form Routes Unit tests', function() {
|
|||
// Save a new Form
|
||||
authenticatedSession.post('/forms')
|
||||
.send({form: myForm})
|
||||
.expect(405)
|
||||
.expect(500)
|
||||
.end(function(FormSaveErr, FormSaveRes) {
|
||||
// Handle Form save error
|
||||
if (FormSaveErr) {
|
||||
|
|
|
@ -168,7 +168,7 @@ describe('FormSubmission Model Unit Tests:', function() {
|
|||
});
|
||||
|
||||
it('should be able to find FormSubmission by $elemMatch on form_fields id', function(done){
|
||||
FormSubmission.findOne({ form: myForm._id, admin: user, form_fields: {$elemMatch: {_id: myForm.form_fields[0]._id} } })
|
||||
FormSubmission.findOne({ form: myForm._id, form_fields: {$elemMatch: {globalId: myForm.form_fields[0].globalId} } })
|
||||
.exec(function(err, submission){
|
||||
should.not.exist(err);
|
||||
should.exist(submission);
|
||||
|
|
|
@ -21,7 +21,8 @@ var credentials, user;
|
|||
* Form routes tests
|
||||
*/
|
||||
describe('Form Submission Routes Unit tests', function() {
|
||||
var FormObj, _Submission, submissionSession;
|
||||
var FormObj, _Submission, submissionSession, _SubmissionBody
|
||||
|
||||
|
||||
beforeEach(function(done) {
|
||||
|
||||
|
@ -60,15 +61,43 @@ describe('Form Submission Routes Unit tests', function() {
|
|||
if (formSaveErr) done(formSaveErr);
|
||||
|
||||
_Submission = {
|
||||
form_fields: [
|
||||
{'fieldType':'textfield', 'title':'First Name', 'fieldValue': 'David'},
|
||||
{'fieldType':'checkbox', 'title':'nascar', 'fieldValue': true},
|
||||
{'fieldType':'checkbox', 'title':'hockey', 'fieldValue': false}
|
||||
],
|
||||
form: form._id,
|
||||
admin: user._id,
|
||||
form_fields: [
|
||||
{'fieldType':'textfield', 'title':'First Name', 'fieldValue': 'David', _id: '', isSubmission: false, deletePreserved: false},
|
||||
{'fieldType':'checkbox', 'title':'nascar', 'fieldValue': true, _id: '', isSubmission: false, deletePreserved: true},
|
||||
{'fieldType':'checkbox', 'title':'hockey', 'fieldValue': false, _id: '', isSubmission: false, deletePreserved: false}
|
||||
],
|
||||
percentageComplete: 100,
|
||||
timeElapsed: 11.55
|
||||
timeElapsed: 11.55,
|
||||
ipAddr: '123.233.232.232',
|
||||
geoLocation: {
|
||||
Country: 'Canada',
|
||||
City: 'Vancouver'
|
||||
},
|
||||
device:{
|
||||
type: 'Mobile',
|
||||
name: 'iPhone'
|
||||
}
|
||||
};
|
||||
|
||||
_SubmissionBody ={
|
||||
_id: form._id,
|
||||
form_fields: [
|
||||
{'fieldType':'textfield', 'title':'First Name', 'fieldValue': 'David', _id: '', isSubmission: false, deletePreserved: false},
|
||||
{'fieldType':'checkbox', 'title':'nascar', 'fieldValue': true, _id: '', isSubmission: false, deletePreserved: true},
|
||||
{'fieldType':'checkbox', 'title':'hockey', 'fieldValue': false, _id: '', isSubmission: false, deletePreserved: false}
|
||||
],
|
||||
percentageComplete: 100,
|
||||
timeElapsed: 11.55,
|
||||
ipAddr: '123.233.232.232',
|
||||
geoLocation: {
|
||||
Country: 'Canada',
|
||||
City: 'Vancouver'
|
||||
},
|
||||
device:{
|
||||
type: 'Mobile',
|
||||
name: 'iPhone'
|
||||
}
|
||||
};
|
||||
|
||||
FormObj = form;
|
||||
|
@ -86,12 +115,11 @@ describe('Form Submission Routes Unit tests', function() {
|
|||
|
||||
//Create Submission
|
||||
submissionSession.post('/forms/' + FormObj._id)
|
||||
.send(_Submission)
|
||||
.send(_SubmissionBody)
|
||||
.expect(200)
|
||||
.end(function(err, res) {
|
||||
|
||||
should.not.exist(err);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -99,7 +127,7 @@ describe('Form Submission Routes Unit tests', function() {
|
|||
it(' > should be able to get Form Submissions if signed in', function(done) {
|
||||
//Create Submission
|
||||
submissionSession.post('/forms/' + FormObj._id)
|
||||
.send(_Submission)
|
||||
.send(_SubmissionBody)
|
||||
.expect(200)
|
||||
.end(function(err, res) {
|
||||
|
||||
|
|
70
app/tests/libs/timestamp.server.plugin.test.js
Normal file
|
@ -0,0 +1,70 @@
|
|||
// Dependencies
|
||||
var util = require('util')
|
||||
, assert = require('assert')
|
||||
, mongoose = require('mongoose')
|
||||
, timestamp = require('../../libs/timestamp.server.plugin')
|
||||
, Schema = mongoose.Schema
|
||||
, ObjectId = Schema.ObjectId
|
||||
|
||||
// Run tests
|
||||
describe('Timestamp', function () {
|
||||
describe('#default()', function () {
|
||||
var FooSchema = new Schema()
|
||||
FooSchema.plugin(timestamp)
|
||||
var FooModel = mongoose.model('timeFoo', FooSchema)
|
||||
, bar = new FooModel()
|
||||
|
||||
before(function () {
|
||||
FooModel.remove(function (err) {
|
||||
assert.strictEqual(err, null)
|
||||
})
|
||||
})
|
||||
|
||||
it('should have custom properties', function (done) {
|
||||
assert.strictEqual(typeof FooSchema.virtuals.created, 'object')
|
||||
assert.strictEqual(typeof FooSchema.paths.modified, 'object')
|
||||
done()
|
||||
})
|
||||
|
||||
it('should create the default attributes', function (done) {
|
||||
bar.save(function (err, doc) {
|
||||
assert.strictEqual(err, null)
|
||||
assert.strictEqual(util.isDate(doc.created), true)
|
||||
assert.strictEqual(util.isDate(doc.modified), true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('#custom()', function () {
|
||||
var FooSchema = new Schema()
|
||||
FooSchema.plugin(timestamp, {
|
||||
createdPath: 'oh'
|
||||
, modifiedPath: 'hai'
|
||||
, useVirtual: false
|
||||
})
|
||||
var BarModel = mongoose.model('timeBar', FooSchema)
|
||||
, bar = new BarModel()
|
||||
|
||||
before(function () {
|
||||
BarModel.remove(function (err) {
|
||||
assert.strictEqual(err, null)
|
||||
})
|
||||
})
|
||||
|
||||
it('should have custom properties', function (done) {
|
||||
assert.strictEqual(typeof FooSchema.paths.oh, 'object')
|
||||
assert.strictEqual(typeof FooSchema.paths.hai, 'object')
|
||||
done()
|
||||
})
|
||||
|
||||
it('should create custom attributes', function (done) {
|
||||
bar.save(function (err, doc) {
|
||||
assert.strictEqual(err, null)
|
||||
assert.strictEqual(util.isDate(doc.oh), true)
|
||||
assert.strictEqual(util.isDate(doc.hai), true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -76,11 +76,6 @@
|
|||
|
||||
|
||||
<script src="/static/lib/jquery/dist/jquery.min.js" type="text/javascript"></script>
|
||||
<script type="text/javascript">
|
||||
$(window).on("load", function() {
|
||||
$(".loader").fadeOut("slow");
|
||||
});
|
||||
</script>
|
||||
|
||||
<link rel="stylesheet" href="/static/lib/font-awesome/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/static/lib/bootstrap/dist/css/bootstrap.min.css">
|
||||
|
|
|
@ -1,13 +1,64 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<p>Dear {{name}},</p>
|
||||
<p></p>
|
||||
<p>This is a confirmation that the password for your account has just been changed</p>
|
||||
<br>
|
||||
<br>
|
||||
<p>The {{appName}} Support Team</p>
|
||||
</body>
|
||||
</html>
|
||||
<html>
|
||||
<head>@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700&subset=latin,latin-ext);
|
||||
<style>
|
||||
a {
|
||||
color: #007ee6;
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||||
</head>
|
||||
<body style="padding: 0; width: 100% !important; -webkit-text-size-adjust: 100%; margin: 0; -ms-text-size-adjust: 100%;" marginheight="0" marginwidth="0">
|
||||
<center>
|
||||
<table cellpadding="8" cellspacing="0" style="*width: 540px; padding: 0; width: 100% !important; background: #ffffff; margin: 0; background-color: #ffffff;" border="0">
|
||||
<tr>
|
||||
<td valign="top">
|
||||
<table cellpadding="0" cellspacing="0" style="border-radius: 6px; -webkit-border-radius: 6px; border: 1px #c0c0c0 solid; -moz-border-radius: 6px;" border="0" align="center">
|
||||
<tr>
|
||||
<td colspan="3" height="6"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<table cellpadding="0" cellspacing="0" style="line-height: 25px;" border="0" align="center">
|
||||
<tr>
|
||||
<td colspan="3" height="30"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="36"></td>
|
||||
<td width="454" align="left" style="color: #444444; border-collapse: collapse; font-size: 11pt; font-family: 'Open Sans', 'Lucida Grande', 'Segoe UI', Arial, Verdana, 'Lucida Sans Unicode', Tahoma, 'Sans Serif'; max-width: 454px;" valign="top">
|
||||
<p>Hello there!</p>
|
||||
<p>This is a courtesy message to confirm that your password was just changed.</p>
|
||||
<p>Thanks so much for using our services! If you have any questions, or suggestions, please feel free to email us here at <a href="mailto:team@tellform.com">team@tellform.com</a>.</p>
|
||||
<p> - The {{appName}} team</p>
|
||||
</td>
|
||||
<td width="36"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3" height="36"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table cellpadding="0" cellspacing="0" align="center" border="0">
|
||||
<tr>
|
||||
<td height="10"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 0; border-collapse: collapse;">
|
||||
<table cellpadding="0" cellspacing="0" align="center" border="0">
|
||||
<tr style="color: #c0c0c0; font-size: 11px; font-family: 'Open Sans', 'Lucida Grande', 'Segoe UI', Arial, Verdana, 'Lucida Sans Unicode', Tahoma, 'Sans Serif'; -webkit-text-size-adjust: none;">
|
||||
<td width="400" align="left"></td>
|
||||
<td width="128" align="right">© TellForm 2017</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
|
@ -1,18 +1,66 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<p>Dear {{name}},</p>
|
||||
<br>
|
||||
<p>
|
||||
You have requested to have your password reset for your account at {{appName}}
|
||||
</p>
|
||||
<p>Please visit this url to reset your password:</p>
|
||||
<p>{{url}}</p>
|
||||
<strong>If you didn't make this request, you can ignore this email.</strong>
|
||||
<br>
|
||||
<br>
|
||||
<p>The {{appName}} Support Team</p>
|
||||
</body>
|
||||
</html>
|
||||
<html>
|
||||
<head>@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700&subset=latin,latin-ext);
|
||||
<style>
|
||||
a {
|
||||
color: #007ee6;
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||||
</head>
|
||||
<body style="padding: 0; width: 100% !important; -webkit-text-size-adjust: 100%; margin: 0; -ms-text-size-adjust: 100%;" marginheight="0" marginwidth="0">
|
||||
<center>
|
||||
<table cellpadding="8" cellspacing="0" style="*width: 540px; padding: 0; width: 100% !important; background: #ffffff; margin: 0; background-color: #ffffff;" border="0">
|
||||
<tr>
|
||||
<td valign="top">
|
||||
<table cellpadding="0" cellspacing="0" style="border-radius: 6px; -webkit-border-radius: 6px; border: 1px #c0c0c0 solid; -moz-border-radius: 6px;" border="0" align="center">
|
||||
<tr>
|
||||
<td colspan="3" height="6"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<table cellpadding="0" cellspacing="0" style="line-height: 25px;" border="0" align="center">
|
||||
<tr>
|
||||
<td colspan="3" height="30"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="36"></td>
|
||||
<td width="454" align="left" style="color: #444444; border-collapse: collapse; font-size: 11pt; font-family: 'Open Sans', 'Lucida Grande', 'Segoe UI', Arial, Verdana, 'Lucida Sans Unicode', Tahoma, 'Sans Serif'; max-width: 454px;" valign="top">
|
||||
<p>Hello there!</p>
|
||||
<p>Here is a special link that will allow you to reset your password. Please note it will expire in one hour for your protection:</p>
|
||||
<p><a href="{{url}}">Reset Your Password</a></p>
|
||||
<p>If you did not request this, please ignore this email and your password will remain unchanged.</p>
|
||||
<p>Thanks so much for using our services! If you have any questions, or suggestions, please feel free to email us here at <a href="mailto:team@tellform.com">team@tellform.com</a>.</p>
|
||||
<p> - The {{appName}} team</p>
|
||||
</td>
|
||||
<td width="36"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3" height="36"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table cellpadding="0" cellspacing="0" align="center" border="0">
|
||||
<tr>
|
||||
<td height="10"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 0; border-collapse: collapse;">
|
||||
<table cellpadding="0" cellspacing="0" align="center" border="0">
|
||||
<tr style="color: #c0c0c0; font-size: 11px; font-family: 'Open Sans', 'Lucida Grande', 'Segoe UI', Arial, Verdana, 'Lucida Sans Unicode', Tahoma, 'Sans Serif'; -webkit-text-size-adjust: none;">
|
||||
<td width="400" align="left"></td>
|
||||
<td width="128" align="right">© TellForm 2017</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
|
@ -1,71 +0,0 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
|
||||
</head>
|
||||
<body style="padding: 0; width: 100% !important; -webkit-text-size-adjust: 100%; margin: 0; -ms-text-size-adjust: 100%;" marginheight="0" marginwidth="0">
|
||||
<center>
|
||||
<table cellpadding="8" cellspacing="0" style="*width: 540px; padding: 0; width: 100% !important; background: #ffffff; margin: 0; background-color: #ffffff;" border="0">
|
||||
<tr>
|
||||
<td valign="top">
|
||||
<table cellpadding="0" cellspacing="0" style="border-radius: 6px; -webkit-border-radius: 6px; border: 1px #c0c0c0 solid; -moz-border-radius: 6px;" border="0" align="center">
|
||||
<tr>
|
||||
<td colspan="3" height="6"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<table cellpadding="0" cellspacing="0" style="line-height: 25px;" border="0" align="center">
|
||||
<tr>
|
||||
<td colspan="3" height="30"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="36"></td>
|
||||
<td width="454" align="left" style="color: #444444; border-collapse: collapse; font-size: 11pt; font-family: 'Open Sans', 'Lucida Grande', 'Segoe UI', Arial, Verdana, 'Lucida Sans Unicode', Tahoma, 'Sans Serif'; max-width: 454px;" valign="top">
|
||||
//- -----------------------------------------------------------------------------------
|
||||
//- Only Edit Here
|
||||
//- -----------------------------------------------------------------------------------
|
||||
p Hello {{name}}!
|
||||
p
|
||||
| Welcome to {{appName}}! Here is a special link to activate your new account:
|
||||
p
|
||||
a(href='${URL}') Activate my account
|
||||
p
|
||||
| Thanks so much for using our services! If you have any questions, or suggestions, please feel free to email us here at
|
||||
a(href='mailto:{{contactEmail}}') {{contactEmail}}
|
||||
| .
|
||||
p - The {{appName}} team
|
||||
//- ----------------------------------------------------------------------------------
|
||||
|
||||
</td>
|
||||
<td width="36"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3" height="36"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table cellpadding="0" cellspacing="0" align="center" border="0">
|
||||
<tr>
|
||||
<td height="10"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 0; border-collapse: collapse;">
|
||||
<table cellpadding="0" cellspacing="0" align="center" border="0">
|
||||
<tr style="color: #c0c0c0; font-size: 11px; font-family: 'Open Sans', 'Lucida Grande', 'Segoe UI', Arial, Verdana, 'Lucida Sans Unicode', Tahoma, 'Sans Serif'; -webkit-text-size-adjust: none;"></tr>
|
||||
<td width="400" align="left">
|
||||
<td width="128" align="right">© </td>
|
||||
</td>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
65
app/views/verification.email.view.html
Normal file
|
@ -0,0 +1,65 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||
<head>
|
||||
@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700&subset=latin,latin-ext);
|
||||
<style>
|
||||
a {
|
||||
color: #007ee6;
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
|
||||
</head>
|
||||
<body style="padding: 0; width: 100% !important; -webkit-text-size-adjust: 100%; margin: 0; -ms-text-size-adjust: 100%;" marginheight="0" marginwidth="0">
|
||||
<center>
|
||||
<table cellpadding="8" cellspacing="0" style="*width: 540px; padding: 0; width: 100% !important; background: #ffffff; margin: 0; background-color: #ffffff;" border="0">
|
||||
<tr>
|
||||
<td valign="top">
|
||||
<table cellpadding="0" cellspacing="0" style="border-radius: 6px; -webkit-border-radius: 6px; border: 1px #c0c0c0 solid; -moz-border-radius: 6px;" border="0" align="center">
|
||||
<tr>
|
||||
<td colspan="3" height="6"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<table cellpadding="0" cellspacing="0" style="line-height: 25px;" border="0" align="center">
|
||||
<tr>
|
||||
<td colspan="3" height="30"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="36"></td>
|
||||
<td width="454" align="left" style="color: #444444; border-collapse: collapse; font-size: 11pt; font-family: 'Open Sans', 'Lucida Grande', 'Segoe UI', Arial, Verdana, 'Lucida Sans Unicode', Tahoma, 'Sans Serif'; max-width: 454px;" valign="top">
|
||||
<p>Hello there!</p>
|
||||
<p>Welcome to TellForm! Here is a special link to activate your new account:</p>
|
||||
<p><a href="https://${URL}">Activate my account</a></p>
|
||||
<p>Thanks so much for using our services! If you have any questions, or suggestions, please feel free to email us here at <a href="mailto:team@tellform.com">team@tellform.com</a>.</p>
|
||||
<p> - The TellForm team</p>
|
||||
</td>
|
||||
<td width="36"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3" height="36"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table cellpadding="0" cellspacing="0" align="center" border="0">
|
||||
<tr>
|
||||
<td height="10"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 0; border-collapse: collapse;">
|
||||
<table cellpadding="0" cellspacing="0" align="center" border="0">
|
||||
<tr style="color: #c0c0c0; font-size: 11px; font-family: 'Open Sans', 'Lucida Grande', 'Segoe UI', Arial, Verdana, 'Lucida Sans Unicode', Tahoma, 'Sans Serif'; -webkit-text-size-adjust: none;">
|
||||
<td width="400" align="left"></td>
|
||||
<td width="128" align="right">© TellForm 2017</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
</body>
|
||||
</head>
|
64
app/views/welcome.email.view.html
Normal file
|
@ -0,0 +1,64 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>@import url(//fonts.googleapis.com/css?family=Open+Sans:300,400,600,700&subset=latin,latin-ext);
|
||||
<style>
|
||||
a {
|
||||
color: #007ee6;
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
|
||||
</head>
|
||||
<body style="padding: 0; width: 100% !important; -webkit-text-size-adjust: 100%; margin: 0; -ms-text-size-adjust: 100%;" marginheight="0" marginwidth="0">
|
||||
<center>
|
||||
<table cellpadding="8" cellspacing="0" style="*width: 540px; padding: 0; width: 100% !important; background: #ffffff; margin: 0; background-color: #ffffff;" border="0">
|
||||
<tr>
|
||||
<td valign="top">
|
||||
<table cellpadding="0" cellspacing="0" style="border-radius: 6px; -webkit-border-radius: 6px; border: 1px #c0c0c0 solid; -moz-border-radius: 6px;" border="0" align="center">
|
||||
<tr>
|
||||
<td colspan="3" height="6"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<table cellpadding="0" cellspacing="0" style="line-height: 25px;" border="0" align="center">
|
||||
<tr>
|
||||
<td colspan="3" height="30"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="36"></td>
|
||||
<td width="454" align="left" style="color: #444444; border-collapse: collapse; font-size: 11pt; font-family: 'Open Sans', 'Lucida Grande', 'Segoe UI', Arial, Verdana, 'Lucida Sans Unicode', Tahoma, 'Sans Serif'; max-width: 454px;" valign="top">
|
||||
<p>Hello there!</p>
|
||||
<p>We would like to welcome you as our newest member!</p>
|
||||
<p>Thanks so much for using TellForm! If you have any questions, or suggestions, please feel free to email us here at <a href="mailto:team@tellform.com">team@tellform.com</a>.</p>
|
||||
<p> - The TellForm team</p>
|
||||
</td>
|
||||
<td width="36"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="3" height="36"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table cellpadding="0" cellspacing="0" align="center" border="0">
|
||||
<tr>
|
||||
<td height="10"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 0; border-collapse: collapse;">
|
||||
<table cellpadding="0" cellspacing="0" align="center" border="0">
|
||||
<tr style="color: #c0c0c0; font-size: 11px; font-family: 'Open Sans', 'Lucida Grande', 'Segoe UI', Arial, Verdana, 'Lucida Sans Unicode', Tahoma, 'Sans Serif'; -webkit-text-size-adjust: none;">
|
||||
<td width="400" align="left"></td>
|
||||
<td width="128" align="right">© TellForm 2017</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
48
config/env/all.js
vendored
|
@ -8,25 +8,52 @@ module.exports = {
|
|||
keywords: process.env.APP_KEYWORDS || 'typeform, pdfs, forms, opensource, formbuilder, google forms, nodejs'
|
||||
},
|
||||
db: {
|
||||
uri: 'mongodb://'+ (process.env.DB_PORT_27017_TCP_ADDR || process.env.DB_HOST || 'localhost')+'/mean',
|
||||
uri: process.env.MONGOLAB_URI || process.env.MONGODB_URI || 'mongodb://'+ (process.env.DB_PORT_27017_TCP_ADDR || '127.0.0.1') + '/mean',
|
||||
options: {
|
||||
user: '',
|
||||
pass: ''
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
admin:{
|
||||
email: process.env.ADMIN_EMAIL || 'admin@admin.com',
|
||||
username: process.env.ADMIN_USERNAME || 'root',
|
||||
password: process.env.ADMIN_PASSWORD || 'root',
|
||||
},
|
||||
|
||||
redisUrl: process.env.REDIS_URL || 'redis://127.0.0.1:6379',
|
||||
|
||||
port: process.env.PORT || 3000,
|
||||
socketPort: process.env.SOCKET_PORT || 20523,
|
||||
socketPortExternallyVisible: (process.env.SOCKET_PORT_EXTERN_VISIBLE === 'TRUE'),
|
||||
|
||||
templateEngine: 'swig',
|
||||
|
||||
reCAPTCHA_Key: process.env.reCAPTCHA_KEY || '',
|
||||
|
||||
signupDisabled: (process.env.SIGNUP_DISABLED === 'TRUE'),
|
||||
signupDisabled: (process.env.SIGNUP_DISABLED === 'TRUE'),
|
||||
enableClusterMode: (process.env.ENABLE_CLUSTER_MODE === 'TRUE'),
|
||||
baseUrl: '',
|
||||
baseUrl: process.env.BASE_URL || 'localhost:3000',
|
||||
tempUserCollection: 'temporary_users',
|
||||
|
||||
mailer: {
|
||||
from: process.env.MAILER_FROM || 'testing@'+process.env.SPARKPOST_SANDBOX_DOMAIN || 'no-reply@tellform.com',
|
||||
options: process.env.MAILER_SMTP_HOST ? { //Uses custom SMTP if MAILER_SMTP_HOST is set
|
||||
host: process.env.MAILER_SMTP_HOST || '',
|
||||
port: process.env.MAILER_SMTP_PORT || 465,
|
||||
secure: process.env.MAILER_SMTP_SECURE || true,
|
||||
auth: {
|
||||
user: process.env.MAILER_EMAIL_ID || '',
|
||||
pass: process.env.MAILER_PASSWORD || ''
|
||||
}
|
||||
} : {
|
||||
service: process.env.MAILER_SERVICE_PROVIDER || '',
|
||||
auth: {
|
||||
user: process.env.MAILER_EMAIL_ID || '',
|
||||
pass: process.env.MAILER_PASSWORD || ''
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
subdomainsDisabled: (process.env.SUBDOMAINS_DISABLED === 'TRUE'),
|
||||
|
||||
//Sentry DSN Client Key
|
||||
|
@ -52,17 +79,6 @@ module.exports = {
|
|||
// To set the cookie in a specific domain uncomment the following
|
||||
// setting:
|
||||
},
|
||||
|
||||
/*
|
||||
* Upload Configuration
|
||||
*/
|
||||
//Global upload path
|
||||
uploadPath : 'uploads/',
|
||||
//PDF storage path
|
||||
pdfUploadPath: 'uploads/pdfs/',
|
||||
//Temp files storage path
|
||||
tmpUploadPath: 'uploads/tmp/',
|
||||
|
||||
// The session cookie name
|
||||
sessionName: 'connect.sid',
|
||||
log: {
|
||||
|
|
27
config/env/development.js
vendored
|
@ -4,7 +4,7 @@ module.exports = {
|
|||
baseUrl: process.env.BASE_URL || 'http://localhost:5000',
|
||||
port: process.env.PORT || 5000,
|
||||
db: {
|
||||
uri: 'mongodb://'+( process.env.DB_PORT_27017_TCP_ADDR || process.env.DB_HOST || '0.0.0.0') +'/mean',
|
||||
uri: process.env.MONGODB_URI || 'mongodb://'+( process.env.DB_PORT_27017_TCP_ADDR || '127.0.0.1') +'/mean',
|
||||
options: {
|
||||
user: '',
|
||||
pass: ''
|
||||
|
@ -16,31 +16,6 @@ module.exports = {
|
|||
// Stream defaults to process.stdout
|
||||
// Uncomment to enable logging to a log on the file system
|
||||
},
|
||||
facebook: {
|
||||
clientID: process.env.FACEBOOK_ID || 'APP_ID',
|
||||
clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET',
|
||||
callbackURL: '/auth/facebook/callback'
|
||||
},
|
||||
twitter: {
|
||||
clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY',
|
||||
clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET',
|
||||
callbackURL: '/auth/twitter/callback'
|
||||
},
|
||||
google: {
|
||||
clientID: process.env.GOOGLE_ID || 'APP_ID',
|
||||
clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET',
|
||||
callbackURL: '/auth/google/callback'
|
||||
},
|
||||
linkedin: {
|
||||
clientID: process.env.LINKEDIN_ID || 'APP_ID',
|
||||
clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET',
|
||||
callbackURL: '/auth/linkedin/callback'
|
||||
},
|
||||
github: {
|
||||
clientID: process.env.GITHUB_ID || 'APP_ID',
|
||||
clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET',
|
||||
callbackURL: '/auth/github/callback'
|
||||
},
|
||||
mailer: {
|
||||
from: process.env.MAILER_FROM || 'no-reply@tellform.com',
|
||||
options: process.env.MAILER_SMTP_HOST ? { //Uses custom SMTP if MAILER_SMTP_HOST is set
|
||||
|
|
46
config/env/production.js
vendored
|
@ -3,10 +3,11 @@
|
|||
module.exports = {
|
||||
baseUrl: process.env.BASE_URL || process.env.HEROKU_APP_NAME + '.herokuapp.com' || 'tellform.com',
|
||||
db: {
|
||||
uri: process.env.MONGODB_URI || process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://' + (process.env.DB_1_PORT_27017_TCP_ADDR || '0.0.0.0') + '/mean',
|
||||
uri: process.env.MONGODB_URI || process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://' + (process.env.DB_PORT_27017_TCP_ADDR || '127.0.0.1') + '/mean',
|
||||
},
|
||||
port: process.env.PORT || 5000,
|
||||
socketUrl: process.env.SOCKET_URL || 'ws.tellform.com',
|
||||
socketPortExternallyVisible: (process.env.SOCKET_PORT_EXTERN_VISIBLE === 'TRUE' || !process.env.SOCKET_PORT_EXTERN_VISIBLE), //socketPortExternallyVisible set to true in production config by default
|
||||
socketPort: process.env.SOCKET_PORT || 20523,
|
||||
log: {
|
||||
// Can specify one of 'combined', 'common', 'dev', 'short', 'tiny'
|
||||
|
@ -31,48 +32,5 @@ module.exports = {
|
|||
css: 'public/dist/application.min.css',
|
||||
js: 'public/dist/application.min.js',
|
||||
form_js: 'public/dist/form-application.min.js'
|
||||
},
|
||||
facebook: {
|
||||
clientID: process.env.FACEBOOK_ID || 'APP_ID',
|
||||
clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET',
|
||||
callbackURL: '/auth/facebook/callback'
|
||||
},
|
||||
twitter: {
|
||||
clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY',
|
||||
clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET',
|
||||
callbackURL: '/auth/twitter/callback'
|
||||
},
|
||||
google: {
|
||||
clientID: process.env.GOOGLE_ID || 'APP_ID',
|
||||
clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET',
|
||||
callbackURL: '/auth/google/callback'
|
||||
},
|
||||
linkedin: {
|
||||
clientID: process.env.LINKEDIN_ID || 'APP_ID',
|
||||
clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET',
|
||||
callbackURL: '/auth/linkedin/callback'
|
||||
},
|
||||
github: {
|
||||
clientID: process.env.GITHUB_ID || 'APP_ID',
|
||||
clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET',
|
||||
callbackURL: '/auth/github/callback'
|
||||
},
|
||||
mailer: {
|
||||
from: process.env.MAILER_FROM || 'testing@'+process.env.SPARKPOST_SANDBOX_DOMAIN || 'no-reply@tellform.com',
|
||||
options: process.env.MAILER_SMTP_HOST ? { //Uses custom SMTP if MAILER_SMTP_HOST is set
|
||||
host: process.env.MAILER_SMTP_HOST || '',
|
||||
port: process.env.MAILER_SMTP_PORT || 465,
|
||||
secure: process.env.MAILER_SMTP_SECURE || true,
|
||||
auth: {
|
||||
user: process.env.MAILER_EMAIL_ID || '',
|
||||
pass: process.env.MAILER_PASSWORD || ''
|
||||
}
|
||||
} : {
|
||||
service: process.env.MAILER_SERVICE_PROVIDER || '',
|
||||
auth: {
|
||||
user: process.env.MAILER_EMAIL_ID || '',
|
||||
pass: process.env.MAILER_PASSWORD || ''
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
29
config/env/secure.js
vendored
|
@ -4,7 +4,7 @@ module.exports = {
|
|||
baseUrl: 'https://forms.polydaic.com',
|
||||
port: 8443,
|
||||
db: {
|
||||
uri: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || 'mongodb://localhost/mean',
|
||||
uri: process.env.MONGOHQ_URL || process.env.MONGOLAB_URI || process.env.MONGODB_URI || 'mongodb://127.0.0.1/mean',
|
||||
options: {
|
||||
user: '',
|
||||
pass: ''
|
||||
|
@ -33,37 +33,12 @@ module.exports = {
|
|||
maxAge: 7200,
|
||||
// To set the cookie in a specific domain uncomment the following
|
||||
// setting:
|
||||
domain: 'forms.polydaic.com'
|
||||
domain: process.env.BASE_URL || 'localhost:3000'
|
||||
},
|
||||
assets: {
|
||||
css: 'public/dist/application.min.css',
|
||||
js: 'public/dist/application.min.js'
|
||||
},
|
||||
facebook: {
|
||||
clientID: process.env.FACEBOOK_ID || 'APP_ID',
|
||||
clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET',
|
||||
callbackURL: 'https://localhost:443/auth/facebook/callback'
|
||||
},
|
||||
twitter: {
|
||||
clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY',
|
||||
clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET',
|
||||
callbackURL: 'https://localhost:443/auth/twitter/callback'
|
||||
},
|
||||
google: {
|
||||
clientID: process.env.GOOGLE_ID || 'APP_ID',
|
||||
clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET',
|
||||
callbackURL: 'https://localhost:443/auth/google/callback'
|
||||
},
|
||||
linkedin: {
|
||||
clientID: process.env.LINKEDIN_ID || 'APP_ID',
|
||||
clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET',
|
||||
callbackURL: 'https://localhost:443/auth/linkedin/callback'
|
||||
},
|
||||
github: {
|
||||
clientID: process.env.GITHUB_ID || 'APP_ID',
|
||||
clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET',
|
||||
callbackURL: 'https://localhost:443/auth/github/callback'
|
||||
},
|
||||
mailer: {
|
||||
from: process.env.MAILER_FROM || '',
|
||||
options: process.env.MAILER_SMTP_HOST ? { //Uses custom SMTP if MAILER_SMTP_HOST is set
|
||||
|
|
25
config/env/test.js
vendored
|
@ -25,31 +25,6 @@ module.exports = {
|
|||
sessionCookie: {
|
||||
maxAge: 24 * 60 * 60 * 1000 // 24 hours
|
||||
},
|
||||
facebook: {
|
||||
clientID: process.env.FACEBOOK_ID || 'APP_ID',
|
||||
clientSecret: process.env.FACEBOOK_SECRET || 'APP_SECRET',
|
||||
callbackURL: '/auth/facebook/callback'
|
||||
},
|
||||
twitter: {
|
||||
clientID: process.env.TWITTER_KEY || 'CONSUMER_KEY',
|
||||
clientSecret: process.env.TWITTER_SECRET || 'CONSUMER_SECRET',
|
||||
callbackURL: '/auth/twitter/callback'
|
||||
},
|
||||
google: {
|
||||
clientID: process.env.GOOGLE_ID || 'APP_ID',
|
||||
clientSecret: process.env.GOOGLE_SECRET || 'APP_SECRET',
|
||||
callbackURL: '/auth/google/callback'
|
||||
},
|
||||
linkedin: {
|
||||
clientID: process.env.LINKEDIN_ID || 'APP_ID',
|
||||
clientSecret: process.env.LINKEDIN_SECRET || 'APP_SECRET',
|
||||
callbackURL: '/auth/linkedin/callback'
|
||||
},
|
||||
github: {
|
||||
clientID: process.env.GITHUB_ID || 'APP_ID',
|
||||
clientSecret: process.env.GITHUB_SECRET || 'APP_SECRET',
|
||||
callbackURL: '/auth/github/callback'
|
||||
},
|
||||
mailer: {
|
||||
from: process.env.MAILER_FROM || 'MAILER_FROM',
|
||||
options: process.env.MAILER_SMTP_HOST ? { //Uses custom SMTP if MAILER_SMTP_HOST is set
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var fs = require('fs-extra'),
|
||||
var fs = require('fs'),
|
||||
https = require('https'),
|
||||
express = require('express'),
|
||||
morgan = require('morgan'),
|
||||
|
@ -17,22 +17,13 @@ var fs = require('fs-extra'),
|
|||
passport = require('passport'),
|
||||
raven = require('raven'),
|
||||
MongoStore = require('connect-mongo')(session),
|
||||
flash = require('connect-flash'),
|
||||
config = require('./config'),
|
||||
consolidate = require('consolidate'),
|
||||
path = require('path'),
|
||||
device = require('express-device'),
|
||||
client = new raven.Client(config.DSN);
|
||||
|
||||
var mongoose = require('mongoose');
|
||||
|
||||
var cacheOpts = {
|
||||
max:100000,
|
||||
maxAge:1000*60
|
||||
};
|
||||
|
||||
require('mongoose-cache').install(mongoose, cacheOpts);
|
||||
|
||||
/**
|
||||
* Configure Socket.io
|
||||
*/
|
||||
|
@ -63,7 +54,7 @@ module.exports = function(db) {
|
|||
|
||||
app.locals.subdomainsDisabled = config.subdomainsDisabled;
|
||||
|
||||
if(config.socketPort && process.env.NODE_ENV !== 'production'){
|
||||
if(config.socketPortExternallyVisible){
|
||||
app.locals.socketPort = config.socketPort;
|
||||
} else {
|
||||
app.locals.socketPort = '';
|
||||
|
@ -246,12 +237,6 @@ module.exports = function(db) {
|
|||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
|
||||
// setup express-device
|
||||
app.use(device.capture({ parseUserAgent: true }));
|
||||
|
||||
// connect flash for flash messages
|
||||
app.use(flash());
|
||||
|
||||
// Globbing routing files
|
||||
config.getGlobbedFiles('./app/routes/**/*.js').forEach(function(routePath) {
|
||||
require(path.resolve(routePath))(app);
|
||||
|
@ -295,16 +280,16 @@ module.exports = function(db) {
|
|||
// Log it
|
||||
client.captureError(err);
|
||||
|
||||
/*if(process.env.NODE_ENV === 'production'){
|
||||
if(process.env.NODE_ENV === 'production'){
|
||||
res.status(500).render('500', {
|
||||
error: 'Internal Server Error'
|
||||
});
|
||||
} else {*/
|
||||
error: 'Internal Server Error'
|
||||
});
|
||||
} else {
|
||||
// Error page
|
||||
res.status(500).render('500', {
|
||||
error: err.stack
|
||||
});
|
||||
//}
|
||||
}
|
||||
});
|
||||
|
||||
// Assume 404 since no middleware responded
|
||||
|
|
|
@ -13,7 +13,12 @@ module.exports = function (app, db) {
|
|||
|
||||
if(config.enableClusterMode){
|
||||
var redis = require('socket.io-redis');
|
||||
io.adapter(redis( process.env.REDIS_URL || { host: process.env.REDIS_DB_PORT_6379_TCP_ADDR || '127.0.0.1' , port: process.env.REDIS_DB_PORT_6379_TCP_PORT || 6379 }));
|
||||
if( process.env.REDIS_DB_PORT_6379_TCP_ADDR ){
|
||||
io.adapter(redis({ host: process.env.REDIS_DB_PORT_6379_TCP_ADDR || '127.0.0.1' , port: process.env.REDIS_DB_PORT_6379_TCP_PORT || 6379 }));
|
||||
|
||||
} else {
|
||||
io.adapter(redis( config.redisUrl ));
|
||||
}
|
||||
}
|
||||
// Add an event listener to the 'connection' event
|
||||
io.on('connection', function (socket) {
|
||||
|
@ -23,4 +28,4 @@ module.exports = function (app, db) {
|
|||
});
|
||||
|
||||
return server;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var passport = require('passport'),
|
||||
FacebookStrategy = require('passport-facebook').Strategy,
|
||||
config = require('../config'),
|
||||
users = require('../../app/controllers/users.server.controller');
|
||||
|
||||
module.exports = function() {
|
||||
// Use facebook strategy
|
||||
passport.use(new FacebookStrategy({
|
||||
clientID: config.facebook.clientID,
|
||||
clientSecret: config.facebook.clientSecret,
|
||||
callbackURL: config.facebook.callbackURL,
|
||||
passReqToCallback: true
|
||||
},
|
||||
function(req, accessToken, refreshToken, profile, done) {
|
||||
// Set the provider data and include tokens
|
||||
var providerData = profile._json;
|
||||
providerData.accessToken = accessToken;
|
||||
providerData.refreshToken = refreshToken;
|
||||
|
||||
// Create the user OAuth profile
|
||||
var providerUserProfile = {
|
||||
firstName: profile.name.givenName,
|
||||
lastName: profile.name.familyName,
|
||||
displayName: profile.displayName,
|
||||
email: profile.emails[0].value,
|
||||
username: profile.username,
|
||||
provider: 'facebook',
|
||||
providerIdentifierField: 'id',
|
||||
providerData: providerData
|
||||
};
|
||||
|
||||
// Save the user OAuth profile
|
||||
users.saveOAuthUserProfile(req, providerUserProfile, done);
|
||||
}
|
||||
));
|
||||
};
|
|
@ -1,46 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var passport = require('passport'),
|
||||
GithubStrategy = require('passport-github').Strategy,
|
||||
config = require('../config'),
|
||||
users = require('../../app/controllers/users.server.controller');
|
||||
|
||||
module.exports = function() {
|
||||
// Use github strategy
|
||||
passport.use(new GithubStrategy({
|
||||
clientID: config.github.clientID,
|
||||
clientSecret: config.github.clientSecret,
|
||||
callbackURL: config.github.callbackURL,
|
||||
passReqToCallback: true
|
||||
},
|
||||
function(req, accessToken, refreshToken, profile, done) {
|
||||
// Set the provider data and include tokens
|
||||
var providerData = profile._json;
|
||||
providerData.accessToken = accessToken;
|
||||
providerData.refreshToken = refreshToken;
|
||||
|
||||
// Create the user OAuth profile
|
||||
var displayName = profile.displayName.trim();
|
||||
var iSpace = displayName.indexOf(' '); // index of the whitespace following the firstName
|
||||
var firstName = iSpace !== -1 ? displayName.substring(0, iSpace) : displayName;
|
||||
var lastName = iSpace !== -1 ? displayName.substring(iSpace + 1) : '';
|
||||
|
||||
var providerUserProfile = {
|
||||
firstName: firstName,
|
||||
lastName: lastName,
|
||||
displayName: displayName,
|
||||
email: profile.emails[0].value,
|
||||
username: profile.username,
|
||||
provider: 'github',
|
||||
providerIdentifierField: 'id',
|
||||
providerData: providerData
|
||||
};
|
||||
|
||||
// Save the user OAuth profile
|
||||
users.saveOAuthUserProfile(req, providerUserProfile, done);
|
||||
}
|
||||
));
|
||||
};
|
|
@ -1,41 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var passport = require('passport'),
|
||||
GoogleStrategy = require('passport-google-oauth').OAuth2Strategy,
|
||||
config = require('../config'),
|
||||
users = require('../../app/controllers/users.server.controller');
|
||||
|
||||
module.exports = function() {
|
||||
// Use google strategy
|
||||
passport.use(new GoogleStrategy({
|
||||
clientID: config.google.clientID,
|
||||
clientSecret: config.google.clientSecret,
|
||||
callbackURL: config.google.callbackURL,
|
||||
passReqToCallback: true
|
||||
},
|
||||
function(req, accessToken, refreshToken, profile, done) {
|
||||
// Set the provider data and include tokens
|
||||
var providerData = profile._json;
|
||||
providerData.accessToken = accessToken;
|
||||
providerData.refreshToken = refreshToken;
|
||||
|
||||
// Create the user OAuth profile
|
||||
var providerUserProfile = {
|
||||
firstName: profile.name.givenName,
|
||||
lastName: profile.name.familyName,
|
||||
displayName: profile.displayName,
|
||||
email: profile.emails[0].value,
|
||||
username: profile.username,
|
||||
provider: 'google',
|
||||
providerIdentifierField: 'id',
|
||||
providerData: providerData
|
||||
};
|
||||
|
||||
// Save the user OAuth profile
|
||||
users.saveOAuthUserProfile(req, providerUserProfile, done);
|
||||
}
|
||||
));
|
||||
};
|
|
@ -1,42 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var passport = require('passport'),
|
||||
LinkedInStrategy = require('passport-linkedin').Strategy,
|
||||
config = require('../config'),
|
||||
users = require('../../app/controllers/users.server.controller');
|
||||
|
||||
module.exports = function() {
|
||||
// Use linkedin strategy
|
||||
passport.use(new LinkedInStrategy({
|
||||
consumerKey: config.linkedin.clientID,
|
||||
consumerSecret: config.linkedin.clientSecret,
|
||||
callbackURL: config.linkedin.callbackURL,
|
||||
passReqToCallback: true,
|
||||
profileFields: ['id', 'first-name', 'last-name', 'email-address']
|
||||
},
|
||||
function(req, accessToken, refreshToken, profile, done) {
|
||||
// Set the provider data and include tokens
|
||||
var providerData = profile._json;
|
||||
providerData.accessToken = accessToken;
|
||||
providerData.refreshToken = refreshToken;
|
||||
|
||||
// Create the user OAuth profile
|
||||
var providerUserProfile = {
|
||||
firstName: profile.name.givenName,
|
||||
lastName: profile.name.familyName,
|
||||
displayName: profile.displayName,
|
||||
email: profile.emails[0].value,
|
||||
username: profile.username,
|
||||
provider: 'linkedin',
|
||||
providerIdentifierField: 'id',
|
||||
providerData: providerData
|
||||
};
|
||||
|
||||
// Save the user OAuth profile
|
||||
users.saveOAuthUserProfile(req, providerUserProfile, done);
|
||||
}
|
||||
));
|
||||
};
|
|
@ -1,45 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var passport = require('passport'),
|
||||
TwitterStrategy = require('passport-twitter').Strategy,
|
||||
config = require('../config'),
|
||||
users = require('../../app/controllers/users.server.controller');
|
||||
|
||||
module.exports = function() {
|
||||
// Use twitter strategy
|
||||
passport.use(new TwitterStrategy({
|
||||
consumerKey: config.twitter.clientID,
|
||||
consumerSecret: config.twitter.clientSecret,
|
||||
callbackURL: config.twitter.callbackURL,
|
||||
passReqToCallback: true
|
||||
},
|
||||
function(req, token, tokenSecret, profile, done) {
|
||||
// Set the provider data and include tokens
|
||||
var providerData = profile._json;
|
||||
providerData.token = token;
|
||||
providerData.tokenSecret = tokenSecret;
|
||||
|
||||
// Create the user OAuth profile
|
||||
var displayName = profile.displayName.trim();
|
||||
var iSpace = displayName.indexOf(' '); // index of the whitespace following the firstName
|
||||
var firstName = iSpace !== -1 ? displayName.substring(0, iSpace) : displayName;
|
||||
var lastName = iSpace !== -1 ? displayName.substring(iSpace + 1) : '';
|
||||
|
||||
var providerUserProfile = {
|
||||
firstName: firstName,
|
||||
lastName: lastName,
|
||||
displayName: displayName,
|
||||
username: profile.username,
|
||||
provider: 'twitter',
|
||||
providerIdentifierField: 'id_str',
|
||||
providerData: providerData
|
||||
};
|
||||
|
||||
// Save the user OAuth profile
|
||||
users.saveOAuthUserProfile(req, providerUserProfile, done);
|
||||
}
|
||||
));
|
||||
};
|
Before Width: | Height: | Size: 6.8 KiB |
BIN
design/tellform_mascot.png
Normal file
After Width: | Height: | Size: 27 KiB |
|
@ -1,22 +0,0 @@
|
|||
version: "3"
|
||||
services:
|
||||
tellform:
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: dockerfile
|
||||
image: tellform
|
||||
ports:
|
||||
- 3000:3000
|
||||
links:
|
||||
- tellform-redis:redis-db
|
||||
- tellform-mongo:db
|
||||
env_file:
|
||||
- .env
|
||||
tellform-redis:
|
||||
image: redis
|
||||
ports:
|
||||
- 6379
|
||||
tellform-mongo:
|
||||
image: mongo
|
||||
ports:
|
||||
- 27017
|
|
@ -1,96 +0,0 @@
|
|||
[
|
||||
[{
|
||||
"fieldType": "textfield",
|
||||
"fieldValue": "snthsnth",
|
||||
"_id": "55aec5d284bae1a1996210bd",
|
||||
"disabled": false,
|
||||
"fieldOptions": [],
|
||||
"description": "",
|
||||
"title": "Short Text2",
|
||||
"lastModified": "2015-07-21T22:21:06.653Z",
|
||||
"created": "2015-07-21T22:21:06.653Z",
|
||||
"$$hashKey": "02J"
|
||||
}, {
|
||||
"fieldType": "textfield",
|
||||
"fieldValue": "duieedi",
|
||||
"_id": "55aec5b084bae1a1996210b4",
|
||||
"disabled": false,
|
||||
"fieldOptions": [],
|
||||
"description": "",
|
||||
"title": "Last Name",
|
||||
"lastModified": "2015-07-21T22:20:32.053Z",
|
||||
"created": "2015-07-21T22:20:32.053Z",
|
||||
"$$hashKey": "02K"
|
||||
}],
|
||||
|
||||
[{
|
||||
"fieldType": "textfield",
|
||||
"fieldValue": "snthsnth",
|
||||
"_id": "55aec5d284bae1a1996210bd",
|
||||
"disabled": false,
|
||||
"fieldOptions": [],
|
||||
"description": "",
|
||||
"title": "Short Text2",
|
||||
"lastModified": "2015-07-21T22:21:06.653Z",
|
||||
"created": "2015-07-21T22:21:06.653Z",
|
||||
"$$hashKey": "02J"
|
||||
}, {
|
||||
"fieldType": "textfield",
|
||||
"fieldValue": "duieedi",
|
||||
"_id": "55aec5b084bae1a1996210b4",
|
||||
"disabled": false,
|
||||
"fieldOptions": [],
|
||||
"description": "",
|
||||
"title": "Last Name",
|
||||
"lastModified": "2015-07-21T22:20:32.053Z",
|
||||
"created": "2015-07-21T22:20:32.053Z",
|
||||
"$$hashKey": "02K"
|
||||
}],
|
||||
[{
|
||||
"fieldType": "textfield",
|
||||
"fieldValue": "snthsnth",
|
||||
"_id": "55aec5d284bae1a1996210bd",
|
||||
"disabled": false,
|
||||
"fieldOptions": [],
|
||||
"description": "",
|
||||
"title": "Short Text2",
|
||||
"lastModified": "2015-07-21T22:21:06.653Z",
|
||||
"created": "2015-07-21T22:21:06.653Z",
|
||||
"$$hashKey": "02J"
|
||||
}, {
|
||||
"fieldType": "textfield",
|
||||
"fieldValue": "duieedi",
|
||||
"_id": "55aec5b084bae1a1996210b4",
|
||||
"disabled": false,
|
||||
"fieldOptions": [],
|
||||
"description": "",
|
||||
"title": "Last Name",
|
||||
"lastModified": "2015-07-21T22:20:32.053Z",
|
||||
"created": "2015-07-21T22:20:32.053Z",
|
||||
"$$hashKey": "02K"
|
||||
}],
|
||||
[{
|
||||
"fieldType": "textfield",
|
||||
"fieldValue": "snthsnth",
|
||||
"_id": "55aec5d284bae1a1996210bd",
|
||||
"disabled": false,
|
||||
"fieldOptions": [],
|
||||
"description": "",
|
||||
"title": "Short Text2",
|
||||
"lastModified": "2015-07-21T22:21:06.653Z",
|
||||
"created": "2015-07-21T22:21:06.653Z",
|
||||
"$$hashKey": "02J"
|
||||
}, {
|
||||
"fieldType": "textfield",
|
||||
"fieldValue": "duieedi",
|
||||
"_id": "55aec5b084bae1a1996210b4",
|
||||
"disabled": false,
|
||||
"fieldOptions": [],
|
||||
"description": "",
|
||||
"title": "Last Name",
|
||||
"lastModified": "2015-07-21T22:20:32.053Z",
|
||||
"created": "2015-07-21T22:20:32.053Z",
|
||||
"$$hashKey": "02K"
|
||||
}]
|
||||
|
||||
]
|
|
@ -1,758 +0,0 @@
|
|||
[
|
||||
{
|
||||
"_id": "56450d120761e9d7d68d3543",
|
||||
"lastModified": "2015-11-12T22:05:06.380Z",
|
||||
"fdfData": null,
|
||||
"admin": "55d270df2749c1ceb47c0f8b",
|
||||
"form": "5644dde5507b2572635dcd50",
|
||||
"title": "Sample Form",
|
||||
"timeElapsed": 11.925,
|
||||
"percentageComplete": 75,
|
||||
"__v": 0,
|
||||
"created": "2015-11-12T22:05:06.378Z",
|
||||
"form_fields": [
|
||||
{
|
||||
"_id": "5644e0d5507b2572635dcd56",
|
||||
"fieldValue": "",
|
||||
"fieldType": "statement",
|
||||
"title": "Statement2",
|
||||
"lastModified": "2015-11-12T18:56:21.730Z",
|
||||
"created": "2015-11-12T18:56:21.660Z",
|
||||
"validFieldTypes": [
|
||||
"textfield",
|
||||
"date",
|
||||
"email",
|
||||
"link",
|
||||
"legal",
|
||||
"url",
|
||||
"textarea",
|
||||
"statement",
|
||||
"welcome",
|
||||
"thankyou",
|
||||
"file",
|
||||
"dropdown",
|
||||
"scale",
|
||||
"rating",
|
||||
"radio",
|
||||
"checkbox",
|
||||
"hidden",
|
||||
"yes_no",
|
||||
"natural",
|
||||
"number"
|
||||
],
|
||||
"deletePreserved": true,
|
||||
"disabled": false,
|
||||
"required": true,
|
||||
"fieldOptions": [],
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"lastModified": "2015-11-12T21:30:20.769Z",
|
||||
"title": "Short Text2",
|
||||
"fieldType": "textfield",
|
||||
"fieldValue": "first",
|
||||
"_id": "5644dde7507b2572635dcd51",
|
||||
"created": "2015-11-12T18:43:51.290Z",
|
||||
"validFieldTypes": [
|
||||
"textfield",
|
||||
"date",
|
||||
"email",
|
||||
"link",
|
||||
"legal",
|
||||
"url",
|
||||
"textarea",
|
||||
"statement",
|
||||
"welcome",
|
||||
"thankyou",
|
||||
"file",
|
||||
"dropdown",
|
||||
"scale",
|
||||
"rating",
|
||||
"radio",
|
||||
"checkbox",
|
||||
"hidden",
|
||||
"yes_no",
|
||||
"natural",
|
||||
"number"
|
||||
],
|
||||
"deletePreserved": false,
|
||||
"disabled": false,
|
||||
"required": true,
|
||||
"fieldOptions": [],
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"lastModified": "2015-11-12T21:30:20.770Z",
|
||||
"title": "Short Text3",
|
||||
"fieldType": "textfield",
|
||||
"fieldValue": "first",
|
||||
"_id": "5644dde7507b2572635dcd52",
|
||||
"created": "2015-11-12T18:43:51.977Z",
|
||||
"validFieldTypes": [
|
||||
"textfield",
|
||||
"date",
|
||||
"email",
|
||||
"link",
|
||||
"legal",
|
||||
"url",
|
||||
"textarea",
|
||||
"statement",
|
||||
"welcome",
|
||||
"thankyou",
|
||||
"file",
|
||||
"dropdown",
|
||||
"scale",
|
||||
"rating",
|
||||
"radio",
|
||||
"checkbox",
|
||||
"hidden",
|
||||
"yes_no",
|
||||
"natural",
|
||||
"number"
|
||||
],
|
||||
"deletePreserved": false,
|
||||
"disabled": false,
|
||||
"required": true,
|
||||
"fieldOptions": [],
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"lastModified": "2015-11-12T21:30:20.771Z",
|
||||
"title": "Paragraph Text2",
|
||||
"fieldType": "textarea",
|
||||
"fieldValue": "first",
|
||||
"_id": "5644ddea507b2572635dcd54",
|
||||
"created": "2015-11-12T18:43:54.594Z",
|
||||
"validFieldTypes": [
|
||||
"textfield",
|
||||
"date",
|
||||
"email",
|
||||
"link",
|
||||
"legal",
|
||||
"url",
|
||||
"textarea",
|
||||
"statement",
|
||||
"welcome",
|
||||
"thankyou",
|
||||
"file",
|
||||
"dropdown",
|
||||
"scale",
|
||||
"rating",
|
||||
"radio",
|
||||
"checkbox",
|
||||
"hidden",
|
||||
"yes_no",
|
||||
"natural",
|
||||
"number"
|
||||
],
|
||||
"deletePreserved": false,
|
||||
"disabled": false,
|
||||
"required": true,
|
||||
"fieldOptions": [],
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"lastModified": "2015-11-12T21:30:20.771Z",
|
||||
"title": "Rating2",
|
||||
"fieldType": "rating",
|
||||
"fieldValue": 1,
|
||||
"_id": "5644e0d4507b2572635dcd55",
|
||||
"created": "2015-11-12T18:56:20.324Z",
|
||||
"validFieldTypes": [
|
||||
"textfield",
|
||||
"date",
|
||||
"email",
|
||||
"link",
|
||||
"legal",
|
||||
"url",
|
||||
"textarea",
|
||||
"statement",
|
||||
"welcome",
|
||||
"thankyou",
|
||||
"file",
|
||||
"dropdown",
|
||||
"scale",
|
||||
"rating",
|
||||
"radio",
|
||||
"checkbox",
|
||||
"hidden",
|
||||
"yes_no",
|
||||
"natural",
|
||||
"number"
|
||||
],
|
||||
"deletePreserved": false,
|
||||
"disabled": false,
|
||||
"required": true,
|
||||
"fieldOptions": [],
|
||||
"description": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"_id": "56450d1d0761e9d7d68d3544",
|
||||
"lastModified": "2015-11-12T22:05:17.806Z",
|
||||
"fdfData": null,
|
||||
"admin": "55d270df2749c1ceb47c0f8b",
|
||||
"form": "5644dde5507b2572635dcd50",
|
||||
"title": "Sample Form",
|
||||
"timeElapsed": 10.301,
|
||||
"percentageComplete": 75,
|
||||
"__v": 0,
|
||||
"created": "2015-11-12T22:05:17.805Z",
|
||||
"form_fields": [
|
||||
{
|
||||
"_id": "5644e0d5507b2572635dcd56",
|
||||
"fieldValue": "",
|
||||
"fieldType": "statement",
|
||||
"title": "Statement2",
|
||||
"lastModified": "2015-11-12T18:56:21.730Z",
|
||||
"created": "2015-11-12T18:56:21.660Z",
|
||||
"validFieldTypes": [
|
||||
"textfield",
|
||||
"date",
|
||||
"email",
|
||||
"link",
|
||||
"legal",
|
||||
"url",
|
||||
"textarea",
|
||||
"statement",
|
||||
"welcome",
|
||||
"thankyou",
|
||||
"file",
|
||||
"dropdown",
|
||||
"scale",
|
||||
"rating",
|
||||
"radio",
|
||||
"checkbox",
|
||||
"hidden",
|
||||
"yes_no",
|
||||
"natural",
|
||||
"number"
|
||||
],
|
||||
"deletePreserved": true,
|
||||
"disabled": false,
|
||||
"required": true,
|
||||
"fieldOptions": [],
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"lastModified": "2015-11-12T21:30:20.769Z",
|
||||
"title": "Short Text2",
|
||||
"fieldType": "textfield",
|
||||
"fieldValue": "second",
|
||||
"_id": "5644dde7507b2572635dcd51",
|
||||
"created": "2015-11-12T18:43:51.290Z",
|
||||
"validFieldTypes": [
|
||||
"textfield",
|
||||
"date",
|
||||
"email",
|
||||
"link",
|
||||
"legal",
|
||||
"url",
|
||||
"textarea",
|
||||
"statement",
|
||||
"welcome",
|
||||
"thankyou",
|
||||
"file",
|
||||
"dropdown",
|
||||
"scale",
|
||||
"rating",
|
||||
"radio",
|
||||
"checkbox",
|
||||
"hidden",
|
||||
"yes_no",
|
||||
"natural",
|
||||
"number"
|
||||
],
|
||||
"deletePreserved": false,
|
||||
"disabled": false,
|
||||
"required": true,
|
||||
"fieldOptions": [],
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"lastModified": "2015-11-12T21:30:20.770Z",
|
||||
"title": "Short Text3",
|
||||
"fieldType": "textfield",
|
||||
"fieldValue": "second",
|
||||
"_id": "5644dde7507b2572635dcd52",
|
||||
"created": "2015-11-12T18:43:51.977Z",
|
||||
"validFieldTypes": [
|
||||
"textfield",
|
||||
"date",
|
||||
"email",
|
||||
"link",
|
||||
"legal",
|
||||
"url",
|
||||
"textarea",
|
||||
"statement",
|
||||
"welcome",
|
||||
"thankyou",
|
||||
"file",
|
||||
"dropdown",
|
||||
"scale",
|
||||
"rating",
|
||||
"radio",
|
||||
"checkbox",
|
||||
"hidden",
|
||||
"yes_no",
|
||||
"natural",
|
||||
"number"
|
||||
],
|
||||
"deletePreserved": false,
|
||||
"disabled": false,
|
||||
"required": true,
|
||||
"fieldOptions": [],
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"lastModified": "2015-11-12T21:30:20.771Z",
|
||||
"title": "Paragraph Text2",
|
||||
"fieldType": "textarea",
|
||||
"fieldValue": "second",
|
||||
"_id": "5644ddea507b2572635dcd54",
|
||||
"created": "2015-11-12T18:43:54.594Z",
|
||||
"validFieldTypes": [
|
||||
"textfield",
|
||||
"date",
|
||||
"email",
|
||||
"link",
|
||||
"legal",
|
||||
"url",
|
||||
"textarea",
|
||||
"statement",
|
||||
"welcome",
|
||||
"thankyou",
|
||||
"file",
|
||||
"dropdown",
|
||||
"scale",
|
||||
"rating",
|
||||
"radio",
|
||||
"checkbox",
|
||||
"hidden",
|
||||
"yes_no",
|
||||
"natural",
|
||||
"number"
|
||||
],
|
||||
"deletePreserved": false,
|
||||
"disabled": false,
|
||||
"required": true,
|
||||
"fieldOptions": [],
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"lastModified": "2015-11-12T21:30:20.771Z",
|
||||
"title": "Rating2",
|
||||
"fieldType": "rating",
|
||||
"fieldValue": 3,
|
||||
"_id": "5644e0d4507b2572635dcd55",
|
||||
"created": "2015-11-12T18:56:20.324Z",
|
||||
"validFieldTypes": [
|
||||
"textfield",
|
||||
"date",
|
||||
"email",
|
||||
"link",
|
||||
"legal",
|
||||
"url",
|
||||
"textarea",
|
||||
"statement",
|
||||
"welcome",
|
||||
"thankyou",
|
||||
"file",
|
||||
"dropdown",
|
||||
"scale",
|
||||
"rating",
|
||||
"radio",
|
||||
"checkbox",
|
||||
"hidden",
|
||||
"yes_no",
|
||||
"natural",
|
||||
"number"
|
||||
],
|
||||
"deletePreserved": false,
|
||||
"disabled": false,
|
||||
"required": true,
|
||||
"fieldOptions": [],
|
||||
"description": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"_id": "56450d260761e9d7d68d3545",
|
||||
"lastModified": "2015-11-12T22:05:26.874Z",
|
||||
"fdfData": null,
|
||||
"admin": "55d270df2749c1ceb47c0f8b",
|
||||
"form": "5644dde5507b2572635dcd50",
|
||||
"title": "Sample Form",
|
||||
"timeElapsed": 7.975,
|
||||
"percentageComplete": 75,
|
||||
"__v": 0,
|
||||
"created": "2015-11-12T22:05:26.874Z",
|
||||
"form_fields": [
|
||||
{
|
||||
"_id": "5644e0d5507b2572635dcd56",
|
||||
"fieldValue": "",
|
||||
"fieldType": "statement",
|
||||
"title": "Statement2",
|
||||
"lastModified": "2015-11-12T18:56:21.730Z",
|
||||
"created": "2015-11-12T18:56:21.660Z",
|
||||
"validFieldTypes": [
|
||||
"textfield",
|
||||
"date",
|
||||
"email",
|
||||
"link",
|
||||
"legal",
|
||||
"url",
|
||||
"textarea",
|
||||
"statement",
|
||||
"welcome",
|
||||
"thankyou",
|
||||
"file",
|
||||
"dropdown",
|
||||
"scale",
|
||||
"rating",
|
||||
"radio",
|
||||
"checkbox",
|
||||
"hidden",
|
||||
"yes_no",
|
||||
"natural",
|
||||
"number"
|
||||
],
|
||||
"deletePreserved": true,
|
||||
"disabled": false,
|
||||
"required": true,
|
||||
"fieldOptions": [],
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"lastModified": "2015-11-12T21:30:20.769Z",
|
||||
"title": "Short Text2",
|
||||
"fieldType": "textfield",
|
||||
"fieldValue": "third",
|
||||
"_id": "5644dde7507b2572635dcd51",
|
||||
"created": "2015-11-12T18:43:51.290Z",
|
||||
"validFieldTypes": [
|
||||
"textfield",
|
||||
"date",
|
||||
"email",
|
||||
"link",
|
||||
"legal",
|
||||
"url",
|
||||
"textarea",
|
||||
"statement",
|
||||
"welcome",
|
||||
"thankyou",
|
||||
"file",
|
||||
"dropdown",
|
||||
"scale",
|
||||
"rating",
|
||||
"radio",
|
||||
"checkbox",
|
||||
"hidden",
|
||||
"yes_no",
|
||||
"natural",
|
||||
"number"
|
||||
],
|
||||
"deletePreserved": false,
|
||||
"disabled": false,
|
||||
"required": true,
|
||||
"fieldOptions": [],
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"lastModified": "2015-11-12T21:30:20.770Z",
|
||||
"title": "Short Text3",
|
||||
"fieldType": "textfield",
|
||||
"fieldValue": "third",
|
||||
"_id": "5644dde7507b2572635dcd52",
|
||||
"created": "2015-11-12T18:43:51.977Z",
|
||||
"validFieldTypes": [
|
||||
"textfield",
|
||||
"date",
|
||||
"email",
|
||||
"link",
|
||||
"legal",
|
||||
"url",
|
||||
"textarea",
|
||||
"statement",
|
||||
"welcome",
|
||||
"thankyou",
|
||||
"file",
|
||||
"dropdown",
|
||||
"scale",
|
||||
"rating",
|
||||
"radio",
|
||||
"checkbox",
|
||||
"hidden",
|
||||
"yes_no",
|
||||
"natural",
|
||||
"number"
|
||||
],
|
||||
"deletePreserved": false,
|
||||
"disabled": false,
|
||||
"required": true,
|
||||
"fieldOptions": [],
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"lastModified": "2015-11-12T21:30:20.771Z",
|
||||
"title": "Paragraph Text2",
|
||||
"fieldType": "textarea",
|
||||
"fieldValue": "third",
|
||||
"_id": "5644ddea507b2572635dcd54",
|
||||
"created": "2015-11-12T18:43:54.594Z",
|
||||
"validFieldTypes": [
|
||||
"textfield",
|
||||
"date",
|
||||
"email",
|
||||
"link",
|
||||
"legal",
|
||||
"url",
|
||||
"textarea",
|
||||
"statement",
|
||||
"welcome",
|
||||
"thankyou",
|
||||
"file",
|
||||
"dropdown",
|
||||
"scale",
|
||||
"rating",
|
||||
"radio",
|
||||
"checkbox",
|
||||
"hidden",
|
||||
"yes_no",
|
||||
"natural",
|
||||
"number"
|
||||
],
|
||||
"deletePreserved": false,
|
||||
"disabled": false,
|
||||
"required": true,
|
||||
"fieldOptions": [],
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"lastModified": "2015-11-12T21:30:20.771Z",
|
||||
"title": "Rating2",
|
||||
"fieldType": "rating",
|
||||
"fieldValue": 2,
|
||||
"_id": "5644e0d4507b2572635dcd55",
|
||||
"created": "2015-11-12T18:56:20.324Z",
|
||||
"validFieldTypes": [
|
||||
"textfield",
|
||||
"date",
|
||||
"email",
|
||||
"link",
|
||||
"legal",
|
||||
"url",
|
||||
"textarea",
|
||||
"statement",
|
||||
"welcome",
|
||||
"thankyou",
|
||||
"file",
|
||||
"dropdown",
|
||||
"scale",
|
||||
"rating",
|
||||
"radio",
|
||||
"checkbox",
|
||||
"hidden",
|
||||
"yes_no",
|
||||
"natural",
|
||||
"number"
|
||||
],
|
||||
"deletePreserved": false,
|
||||
"disabled": false,
|
||||
"required": true,
|
||||
"fieldOptions": [],
|
||||
"description": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"_id": "56450d320761e9d7d68d3546",
|
||||
"lastModified": "2015-11-12T22:05:38.717Z",
|
||||
"fdfData": null,
|
||||
"admin": "55d270df2749c1ceb47c0f8b",
|
||||
"form": "5644dde5507b2572635dcd50",
|
||||
"title": "Sample Form",
|
||||
"timeElapsed": 10.47,
|
||||
"percentageComplete": 75,
|
||||
"__v": 0,
|
||||
"created": "2015-11-12T22:05:38.716Z",
|
||||
"form_fields": [
|
||||
{
|
||||
"_id": "5644e0d5507b2572635dcd56",
|
||||
"fieldValue": "",
|
||||
"fieldType": "statement",
|
||||
"title": "Statement2",
|
||||
"lastModified": "2015-11-12T18:56:21.730Z",
|
||||
"created": "2015-11-12T18:56:21.660Z",
|
||||
"validFieldTypes": [
|
||||
"textfield",
|
||||
"date",
|
||||
"email",
|
||||
"link",
|
||||
"legal",
|
||||
"url",
|
||||
"textarea",
|
||||
"statement",
|
||||
"welcome",
|
||||
"thankyou",
|
||||
"file",
|
||||
"dropdown",
|
||||
"scale",
|
||||
"rating",
|
||||
"radio",
|
||||
"checkbox",
|
||||
"hidden",
|
||||
"yes_no",
|
||||
"natural",
|
||||
"number"
|
||||
],
|
||||
"deletePreserved": true,
|
||||
"disabled": false,
|
||||
"required": true,
|
||||
"fieldOptions": [],
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"lastModified": "2015-11-12T21:30:20.769Z",
|
||||
"title": "Short Text2",
|
||||
"fieldType": "textfield",
|
||||
"fieldValue": "fourth",
|
||||
"_id": "5644dde7507b2572635dcd51",
|
||||
"created": "2015-11-12T18:43:51.290Z",
|
||||
"validFieldTypes": [
|
||||
"textfield",
|
||||
"date",
|
||||
"email",
|
||||
"link",
|
||||
"legal",
|
||||
"url",
|
||||
"textarea",
|
||||
"statement",
|
||||
"welcome",
|
||||
"thankyou",
|
||||
"file",
|
||||
"dropdown",
|
||||
"scale",
|
||||
"rating",
|
||||
"radio",
|
||||
"checkbox",
|
||||
"hidden",
|
||||
"yes_no",
|
||||
"natural",
|
||||
"number"
|
||||
],
|
||||
"deletePreserved": false,
|
||||
"disabled": false,
|
||||
"required": true,
|
||||
"fieldOptions": [],
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"lastModified": "2015-11-12T21:30:20.770Z",
|
||||
"title": "Short Text3",
|
||||
"fieldType": "textfield",
|
||||
"fieldValue": "fourth",
|
||||
"_id": "5644dde7507b2572635dcd52",
|
||||
"created": "2015-11-12T18:43:51.977Z",
|
||||
"validFieldTypes": [
|
||||
"textfield",
|
||||
"date",
|
||||
"email",
|
||||
"link",
|
||||
"legal",
|
||||
"url",
|
||||
"textarea",
|
||||
"statement",
|
||||
"welcome",
|
||||
"thankyou",
|
||||
"file",
|
||||
"dropdown",
|
||||
"scale",
|
||||
"rating",
|
||||
"radio",
|
||||
"checkbox",
|
||||
"hidden",
|
||||
"yes_no",
|
||||
"natural",
|
||||
"number"
|
||||
],
|
||||
"deletePreserved": false,
|
||||
"disabled": false,
|
||||
"required": true,
|
||||
"fieldOptions": [],
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"lastModified": "2015-11-12T21:30:20.771Z",
|
||||
"title": "Paragraph Text2",
|
||||
"fieldType": "textarea",
|
||||
"fieldValue": "fourth",
|
||||
"_id": "5644ddea507b2572635dcd54",
|
||||
"created": "2015-11-12T18:43:54.594Z",
|
||||
"validFieldTypes": [
|
||||
"textfield",
|
||||
"date",
|
||||
"email",
|
||||
"link",
|
||||
"legal",
|
||||
"url",
|
||||
"textarea",
|
||||
"statement",
|
||||
"welcome",
|
||||
"thankyou",
|
||||
"file",
|
||||
"dropdown",
|
||||
"scale",
|
||||
"rating",
|
||||
"radio",
|
||||
"checkbox",
|
||||
"hidden",
|
||||
"yes_no",
|
||||
"natural",
|
||||
"number"
|
||||
],
|
||||
"deletePreserved": false,
|
||||
"disabled": false,
|
||||
"required": true,
|
||||
"fieldOptions": [],
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"lastModified": "2015-11-12T21:30:20.771Z",
|
||||
"title": "Rating2",
|
||||
"fieldType": "rating",
|
||||
"fieldValue": 2,
|
||||
"_id": "5644e0d4507b2572635dcd55",
|
||||
"created": "2015-11-12T18:56:20.324Z",
|
||||
"validFieldTypes": [
|
||||
"textfield",
|
||||
"date",
|
||||
"email",
|
||||
"link",
|
||||
"legal",
|
||||
"url",
|
||||
"textarea",
|
||||
"statement",
|
||||
"welcome",
|
||||
"thankyou",
|
||||
"file",
|
||||
"dropdown",
|
||||
"scale",
|
||||
"rating",
|
||||
"radio",
|
||||
"checkbox",
|
||||
"hidden",
|
||||
"yes_no",
|
||||
"natural",
|
||||
"number"
|
||||
],
|
||||
"deletePreserved": false,
|
||||
"disabled": false,
|
||||
"required": true,
|
||||
"fieldOptions": [],
|
||||
"description": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -1,22 +0,0 @@
|
|||
##First Form Auto-Update
|
||||
|
||||
1. Added field CLIENT
|
||||
time: 2553
|
||||
|
||||
2. Finding form SERVER
|
||||
time: 2841
|
||||
|
||||
3. Update form CLIENT
|
||||
time: 2870
|
||||
|
||||
4.Updated form SERVER
|
||||
time: 2863
|
||||
|
||||
|
||||
##Second Form Auto-Update
|
||||
|
||||
1. Added field CLIENT
|
||||
time: 2755
|
||||
|
||||
2. Finding form SERVER
|
||||
time: 2898
|
Before Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 9.5 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 88 KiB |
Before Width: | Height: | Size: 14 KiB |
|
@ -1,249 +0,0 @@
|
|||
#Installing Sentry Server
|
||||
|
||||
### Before You Begin
|
||||
Make sure you understand what sentry server does. You can view documentation for sentry server [here](https://sentry.readthedocs.org/). This document was written for a server running *Ubuntu 14.04 LTS server* (we used Azure).
|
||||
|
||||
|
||||
### Prerequisites
|
||||
Make sure you have these installed:
|
||||
* apt-get
|
||||
* python2.7
|
||||
* vim
|
||||
|
||||
|
||||
|
||||
### Installation Steps
|
||||
1. Add non-root user.
|
||||
```
|
||||
sudo adduser sentry
|
||||
sudo adduser sentry sudo
|
||||
```
|
||||
|
||||
2. Update all apt-get packages
|
||||
```
|
||||
sudo apt-get update
|
||||
sudo apt-get dist-upgrade
|
||||
sudo apt-get autoremove
|
||||
sudo apt-get install libxml2-dev libxslt1-dev libffi-dev libpq-dev python-dev
|
||||
sudo reboot
|
||||
```
|
||||
|
||||
3. Install easy_install and pip
|
||||
```
|
||||
wget https://bootstrap.pypa.io/ez_setup.py -O - | sudo python
|
||||
sudo easy_install pip
|
||||
```
|
||||
|
||||
4. Install virtualenv and lmxl
|
||||
```
|
||||
sudo pip install virtualenv
|
||||
sudo pip install lxml
|
||||
```
|
||||
|
||||
5. Install Sentry and Setup
|
||||
```
|
||||
# make server directory
|
||||
mkdir ~/SentryServer; cd ~/SentryServer;
|
||||
# make virtualenv
|
||||
virtualenv ./
|
||||
#activate virtualenv
|
||||
source ./bin/activate
|
||||
|
||||
# install sentry and its postgresql dependencies
|
||||
pip install -U sentry[postgres]
|
||||
```
|
||||
|
||||
6. Install postgresql
|
||||
```
|
||||
# install postgres
|
||||
sudo apt-get install postgresql postgresql-contrib libpq-dev
|
||||
|
||||
# install postgres adminpack
|
||||
sudo -u postgres psql
|
||||
CREATE EXTENSION "adminpack";
|
||||
\q
|
||||
```
|
||||
|
||||
7. Setup postgresql DB
|
||||
```
|
||||
# change postgres password & create database
|
||||
sudo passwd postgres
|
||||
sudo su - postgres
|
||||
psql -d template1 -c "ALTER USER postgres WITH PASSWORD 'changeme';"
|
||||
createdb sentry
|
||||
createuser sentry_user --pwprompt
|
||||
psql -d template1 -U postgres
|
||||
GRANT ALL PRIVILEGES ON DATABASE sentry to sentry_user;
|
||||
\q
|
||||
exit
|
||||
```
|
||||
|
||||
8. Setup Sentry Configuration
|
||||
```
|
||||
# initialize conf file
|
||||
sentry init
|
||||
|
||||
#edit sentry configuration
|
||||
vim ~/.sentry/sentry.conf.py
|
||||
```
|
||||
|
||||
The following are the contents of my sentry.conf.py file (replace name, user and password with your that of your DB)
|
||||
|
||||
```
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
||||
'NAME': 'sentry',
|
||||
'USER': 'sentry_user',
|
||||
'PASSWORD': 'your_password',
|
||||
'HOST': 'localhost',
|
||||
}
|
||||
}
|
||||
# No trailing slash!
|
||||
SENTRY_URL_PREFIX = 'http://sentry.example.com'
|
||||
|
||||
SENTRY_WEB_HOST = '0.0.0.0'
|
||||
SENTRY_WEB_PORT = 9000
|
||||
SENTRY_WEB_OPTIONS = {
|
||||
'workers': 3, # the number of gunicorn workers
|
||||
'secure_scheme_headers': {'X-FORWARDED-PROTO': 'https'}, # detect HTTPS mode from X-Forwarded-Proto header
|
||||
}
|
||||
|
||||
#CONFIGURE REDIS
|
||||
SENTRY_REDIS_OPTIONS = {
|
||||
'hosts': {
|
||||
0: {
|
||||
'host': '127.0.0.1',
|
||||
'port': 6379,
|
||||
'timeout': 3,
|
||||
#'password': 'redis auth password'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#CONFIGURE OUTGOING MAIL
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
||||
EMAIL_USE_TLS = True
|
||||
EMAIL_HOST = 'smtp.gmail.com'
|
||||
EMAIL_PORT = 587
|
||||
EMAIL_HOST_USER = 'your_gmail_username@gmail.com'
|
||||
EMAIL_HOST_PASSWORD = 'your_gmail_password'
|
||||
DEFAULT_FROM_EMAIL = 'testing@testing.com
|
||||
```
|
||||
|
||||
9. Setup Database and Start Sentry
|
||||
```
|
||||
#install and run redis-server
|
||||
wget http://download.redis.io/releases/redis-stable.tar.gz
|
||||
tar xzf redis-stable.tar.gz
|
||||
cd redis-stable
|
||||
make
|
||||
make test
|
||||
sudo make install
|
||||
cd utils
|
||||
sudo ./install_server.sh
|
||||
|
||||
#Go back to app directory
|
||||
cd ~/SentryServer
|
||||
sudo service redis_6379 start
|
||||
|
||||
# set up databse
|
||||
sentry upgrade
|
||||
|
||||
# let's try it out!
|
||||
sentry start
|
||||
```
|
||||
|
||||
10. Install nginx
|
||||
|
||||
```
|
||||
# install nginx
|
||||
sudo apt-get install nginx
|
||||
|
||||
# remove the default symbolic link
|
||||
sudo rm /etc/nginx/sites-enabled/default
|
||||
|
||||
# create a new blank config, and make a symlink to it
|
||||
sudo touch /etc/nginx/sites-available/sentry
|
||||
cd /etc/nginx/sites-enabled
|
||||
sudo ln -s ../sites-available/sentry
|
||||
|
||||
# edit the nginx configuration file
|
||||
sudo vim /etc/nginx/sites-available/sentry
|
||||
```
|
||||
*Here are the contents of my nginx file:*
|
||||
```
|
||||
server {
|
||||
# listen on port 80
|
||||
listen 80;
|
||||
|
||||
# for requests to these domains
|
||||
server_name yourdomain.com www.yourdomain.com;
|
||||
|
||||
# keep logs in these files
|
||||
access_log /var/log/nginx/sentry.access.log;
|
||||
error_log /var/log/nginx/sentry.error.log;
|
||||
|
||||
# You need this to allow users to upload large files
|
||||
# See http://wiki.nginx.org/HttpCoreModule#client_max_body_size
|
||||
# I'm not sure where it goes, so I put it in twice. It works.
|
||||
client_max_body_size 0;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:9000;
|
||||
proxy_redirect off;
|
||||
|
||||
proxy_read_timeout 5m;
|
||||
|
||||
# make sure these HTTP headers are set properly
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
11. Start the worker processes
|
||||
```
|
||||
# restart nginx
|
||||
sudo nginx -t
|
||||
sudo service nginx reload
|
||||
|
||||
#start sentry
|
||||
sentry celery worker -B
|
||||
```
|
||||
|
||||
12. Install and Configure supervisord
|
||||
```
|
||||
pip install supervisord
|
||||
sudo echo_supervisord_conf > ~/SentryServer/etc/supervisord.conf
|
||||
|
||||
#Edit yuour supervisord Config
|
||||
vim /etc/supervisord.conf
|
||||
```
|
||||
|
||||
Configuration file should look like this
|
||||
```
|
||||
[program:sentry-web]
|
||||
directory=~/SentryServer/
|
||||
command=~/SentryServer/bin/sentry start
|
||||
autostart=true
|
||||
autorestart=true
|
||||
redirect_stderr=true
|
||||
stdout_logfile=syslog
|
||||
stderr_logfile=syslog
|
||||
|
||||
[program:sentry-worker]
|
||||
directory=~/SentryServer/
|
||||
command=~/SentryServer/bin/sentry celery worker -B
|
||||
autostart=true
|
||||
autorestart=true
|
||||
redirect_stderr=true
|
||||
stdout_logfile=syslog
|
||||
stderr_logfile=syslog
|
||||
```
|
||||
|
||||
13. Run Server (with supervisord)
|
||||
```
|
||||
supervisord
|
||||
```
|
|
@ -1,29 +0,0 @@
|
|||
UX of Updating Forms
|
||||
====================
|
||||
|
||||
##Form Updating
|
||||
- Action: Update Configuration
|
||||
1. User clicks on Form from FormList page (visits form admin page)
|
||||
2. User clicks on "Configure Form" Tab
|
||||
3. User changes inputs in form page
|
||||
4. Save Button is Pressed
|
||||
5. Loading/Busy Indicator fills the screen
|
||||
6. Loading/Busy Indicator exits the screen
|
||||
7. Configuration page is shown with updated settings
|
||||
|
||||
- Action: Add Form Field
|
||||
1. User clicks on Form from FormList page
|
||||
2. User clicks on a Tab in "AddField" column
|
||||
3. Loading/Busy Indicator fills the screen
|
||||
4. Loading/Busy Indicator exits the screen
|
||||
|
||||
- Action: Edit Form Field Title
|
||||
1. User clicks on Form from FormList page (visits form admin page)
|
||||
2. User clicks on a current Form Input accordion (must be either the name or the caret)
|
||||
3. The clicked accordion (the one interacted with in Step#2) expands
|
||||
4. User clicks on 'Question Title' text input (aka focuses on said text input)
|
||||
5. User starts typing in text input
|
||||
6. User defocuses/clicks off of text input
|
||||
7. Loading/Busy Indicator fills the screen
|
||||
8. Loading/Busy Indicator exits the screen
|
||||
9. Field Accordion heading is updated
|
40
gruntfile.js
|
@ -28,10 +28,10 @@ module.exports = function(grunt) {
|
|||
|
||||
clientViews: ['public/modules/**/*.html', 'public/form_modules/forms/base/**/*.html', '!public/modules/forms/base/**/*.html',],
|
||||
clientJS: ['public/js/*.js', 'public/form_modules/**/*.js', 'public/modules/**/*.js'],
|
||||
clientCSS: ['public/modules/**/*.css', 'public/form_modules/**/*.css', '!public/modules/**/demo/**/*.css', '!public/modules/**/dist/**/*.css'],
|
||||
clientCSS: ['public/modules/**/*.css'],
|
||||
|
||||
serverTests: ['app/tests/**/*.js'],
|
||||
clientTests: ['public/modules/**/tests/*.js', '!public/modules/**/demo/**/*.js', '!public/modules/**/dist/**/*.js', '!public/modules/**/node_modules/**/*.js']
|
||||
clientTests: ['public/modules/**/tests/*.js']
|
||||
};
|
||||
|
||||
watchFiles.allTests = watchFiles.serverTests.concat(watchFiles.clientTests);
|
||||
|
@ -127,19 +127,6 @@ module.exports = function(grunt) {
|
|||
}
|
||||
}
|
||||
},
|
||||
'closure-compiler': {
|
||||
vendor_file: {
|
||||
closurePath: './scripts',
|
||||
js: 'public/dist/vendor_forms_uglified.js',
|
||||
jsOutputFile: 'public/dist/vendor.min.js',
|
||||
maxBuffer: 10000000000,
|
||||
options: {
|
||||
warning_level: 'QUIET',
|
||||
compilation_level: 'SIMPLE_OPTIMIZATIONS',
|
||||
language_in: 'ECMASCRIPT5'
|
||||
}
|
||||
}
|
||||
},
|
||||
cssmin: {
|
||||
combine: {
|
||||
files: {
|
||||
|
@ -157,19 +144,6 @@ module.exports = function(grunt) {
|
|||
}
|
||||
}
|
||||
},
|
||||
'node-inspector': {
|
||||
custom: {
|
||||
options: {
|
||||
'web-port': 1337,
|
||||
'web-host': 'localhost',
|
||||
'debug-port': 5858,
|
||||
'save-live-edit': true,
|
||||
'no-preload': true,
|
||||
'stack-trace-limit': 50,
|
||||
'hidden': []
|
||||
}
|
||||
}
|
||||
},
|
||||
ngAnnotate: {
|
||||
production: {
|
||||
files: {
|
||||
|
@ -180,28 +154,26 @@ module.exports = function(grunt) {
|
|||
},
|
||||
concurrent: {
|
||||
default: ['nodemon', 'watch'],
|
||||
debug: ['nodemon', 'watch', 'node-inspector'],
|
||||
debug: ['nodemon', 'watch'],
|
||||
options: {
|
||||
logConcurrentOutput: true,
|
||||
limit: 10
|
||||
}
|
||||
},
|
||||
env: {
|
||||
src: '.env',
|
||||
|
||||
test: {
|
||||
NODE_ENV: 'test',
|
||||
src: '.env'
|
||||
},
|
||||
secure: {
|
||||
NODE_ENV: 'secure',
|
||||
src: '/opt/deploy/.env'
|
||||
},
|
||||
production: {
|
||||
NODE_ENV: 'production',
|
||||
src: '/opt/deploy/.env'
|
||||
},
|
||||
dev: {
|
||||
NODE_ENV: 'development',
|
||||
src: '.env'
|
||||
}
|
||||
},
|
||||
mochaTest: {
|
||||
|
@ -211,7 +183,7 @@ module.exports = function(grunt) {
|
|||
quiet: false,
|
||||
require: 'server.js',
|
||||
ui: 'bdd',
|
||||
debug: true
|
||||
debug: false
|
||||
}
|
||||
},
|
||||
karma: {
|
||||
|
|
45
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "TellForm",
|
||||
"description": "Opensource alternative to TypeForm",
|
||||
"version": "2.0.0",
|
||||
"version": "2.1.0",
|
||||
"homepage": "https://github.com/whitef0x0/tellform",
|
||||
"authors": [
|
||||
"David Baldwynn <polydaic@gmail.com> (http://baldwynn.me)"
|
||||
|
@ -13,8 +13,8 @@
|
|||
"url": "https://github.com/whitef0x0/tellform.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": "6.11.2",
|
||||
"npm": "3.10.10"
|
||||
"node": "6.x.x",
|
||||
"npm": "3.x.x"
|
||||
},
|
||||
"scripts": {
|
||||
"addcontrib": "all-contributors add",
|
||||
|
@ -22,82 +22,55 @@
|
|||
"start": "npm run version && grunt",
|
||||
"test": "npm run version && grunt test",
|
||||
"postinstall": "bower install --config.interactive=false; grunt build;",
|
||||
"init": "node scripts/setup.js",
|
||||
"version": "check-node-version --package"
|
||||
"init": "node scripts/setup.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"async": "^1.4.2",
|
||||
"async-boolean-expression-evaluator": "^1.1.1",
|
||||
"bcrypt": "^0.8.7",
|
||||
"bcrypt-nodejs": "0.0.3",
|
||||
"body-parser": "~1.14.1",
|
||||
"bower": "~1.6.5",
|
||||
"chalk": "^1.1.3",
|
||||
"check-node-version": "^2.1.0",
|
||||
"compression": "~1.6.0",
|
||||
"connect": "^3.4.1",
|
||||
"connect-flash": "~0.1.1",
|
||||
"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",
|
||||
"express": "~4.13.3",
|
||||
"express-device": "~0.4.2",
|
||||
"express-session": "~1.12.1",
|
||||
"forever": "~0.15.1",
|
||||
"fs-extra": "~0.26.2",
|
||||
"glob": "^7.0.3",
|
||||
"google-cdn": "^1.1.0",
|
||||
"grunt": "~0.4.1",
|
||||
"grunt-cli": "~0.1.13",
|
||||
"grunt-concurrent": "~2.3.0",
|
||||
"grunt-contrib-csslint": "~1.0.0",
|
||||
"grunt-contrib-cssmin": "~1.0.1",
|
||||
"grunt-contrib-jshint": "~1.0.0",
|
||||
"grunt-contrib-uglify": "~0.11.0",
|
||||
"grunt-contrib-watch": "~0.6.1",
|
||||
"grunt-env": "~0.4.1",
|
||||
"grunt-html2js": "~0.3.5",
|
||||
"grunt-karma": "~0.12.1",
|
||||
"grunt-newer": "~1.1.1",
|
||||
"grunt-ng-annotate": "~1.0.1",
|
||||
"grunt-node-inspector": "~0.4.1",
|
||||
"grunt-nodemon": "~0.4.0",
|
||||
"helmet": "3.5.0",
|
||||
"inquirer": "^1.0.2",
|
||||
"jit-grunt": "^0.9.1",
|
||||
"lodash": "^4.17.4",
|
||||
"main-bower-files": "~2.9.0",
|
||||
"method-override": "~2.3.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"mongoose": "~4.4.19",
|
||||
"mongoose-cache": "^0.1.5",
|
||||
"mongoose-utilities": "~0.1.1",
|
||||
"morgan": "~1.8.1",
|
||||
"multer": "^1.3.0",
|
||||
"nodemailer": "~4.0.0",
|
||||
"nodemailer-sendgrid-transport": "^0.2.0",
|
||||
"nodemailer-sparkpost-transport": "^1.0.0",
|
||||
"passport": "~0.3.0",
|
||||
"passport-anonymous": "^1.0.1",
|
||||
"passport-facebook": "~2.0.0",
|
||||
"passport-github": "~1.0.0",
|
||||
"passport-google-oauth": "~0.2.0",
|
||||
"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",
|
||||
"prerender-node": "^2.2.1",
|
||||
"random-js": "^1.0.8",
|
||||
"raven": "^0.9.0",
|
||||
"request": "^2.83.0",
|
||||
"socket.io": "^1.4.6",
|
||||
"socket.io-redis": "^1.0.0",
|
||||
"swig": "~1.4.1",
|
||||
"uid-generator": "^0.1.1",
|
||||
"uuid-token-generator": "^0.5.0",
|
||||
"wildcard-subdomains": "github:tellform/wildcard-subdomains",
|
||||
"winston": "^2.3.1",
|
||||
|
@ -109,7 +82,7 @@
|
|||
"coveralls": "^2.11.4",
|
||||
"cross-spawn": "^5.0.0",
|
||||
"del": "^2.2.2",
|
||||
"email-verification": "github:tellform/node-email-verification",
|
||||
"grunt-cli": "~0.1.13",
|
||||
"grunt-closure-compiler": "0.0.21",
|
||||
"grunt-contrib-concat": "^1.0.1",
|
||||
"grunt-contrib-copy": "^1.0.0",
|
||||
|
@ -117,7 +90,11 @@
|
|||
"grunt-execute": "^0.2.2",
|
||||
"grunt-mocha-istanbul": "^3.0.1",
|
||||
"grunt-mocha-test": "~0.12.1",
|
||||
"grunt-contrib-watch": "~0.6.1",
|
||||
"grunt-newer": "~1.1.1",
|
||||
"grunt-usemin": "^3.1.1",
|
||||
"grunt-karma": "~0.12.1",
|
||||
"grunt-nodemon": "~0.4.0",
|
||||
"grunt-wiredep": "^3.0.1",
|
||||
"istanbul": "^0.4.0",
|
||||
"jasmine-core": "^2.4.1",
|
||||
|
@ -130,11 +107,9 @@
|
|||
"karma-mocha-reporter": "^1.1.1",
|
||||
"karma-ng-html2js-preprocessor": "^0.2.0",
|
||||
"karma-phantomjs-launcher": "^1.0.4",
|
||||
"mailosaur": "^1.0.1",
|
||||
"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",
|
||||
|
|
566
public/dist/application.js
vendored
2
public/dist/application.min.css
vendored
10
public/dist/application.min.js
vendored
378
public/dist/form-application.js
vendored
4
public/dist/form-application.min.js
vendored
22
public/dist/form_populate_template_cache.js
vendored
16
public/dist/vendor.min.js
vendored
|
@ -5,6 +5,9 @@ angular.module('view-form').controller('SubmitFormController', [
|
|||
'$scope', '$rootScope', '$state', '$translate', 'myForm',
|
||||
function($scope, $rootScope, $state, $translate, myForm) {
|
||||
$scope.myform = myForm;
|
||||
|
||||
$(".loader").fadeOut("slow");
|
||||
document.body.style.background = myForm.design.colors.backgroundColor;
|
||||
$translate.use(myForm.language);
|
||||
}
|
||||
]);
|
||||
|
|
|
@ -25,7 +25,7 @@ angular.module('view-form').directive('onEnterKey', ['$rootScope', function($roo
|
|||
return {
|
||||
restrict: 'A',
|
||||
link: function($scope, $element, $attrs) {
|
||||
$element.bind('keydown keypress', function(event) {
|
||||
$element.bind('keyup keypress', function(event) {
|
||||
|
||||
var keyCode = event.which || event.keyCode;
|
||||
|
||||
|
@ -65,6 +65,8 @@ angular.module('view-form').directive('onEnterKey', ['$rootScope', function($roo
|
|||
var keyCode = event.which || event.keyCode;
|
||||
|
||||
if(keyCode === 9 && event.shiftKey) {
|
||||
|
||||
console.log('onTabAndShiftKey');
|
||||
event.preventDefault();
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.$eval($attrs.onTabAndShiftKey);
|
||||
|
|
|
@ -9,8 +9,8 @@ jsep.addBinaryOp('!begins', 10);
|
|||
jsep.addBinaryOp('ends', 10);
|
||||
jsep.addBinaryOp('!ends', 10);
|
||||
|
||||
angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCounter', '$filter', '$rootScope', 'SendVisitorData', '$translate',
|
||||
function ($http, TimeCounter, $filter, $rootScope, SendVisitorData, $translate) {
|
||||
angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCounter', '$filter', '$rootScope', 'SendVisitorData', '$translate', '$timeout',
|
||||
function ($http, TimeCounter, $filter, $rootScope, SendVisitorData, $translate, $timeout) {
|
||||
return {
|
||||
templateUrl: 'form_modules/forms/base/views/directiveViews/form/submit-form.client.view.html',
|
||||
restrict: 'E',
|
||||
|
@ -19,7 +19,8 @@ angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCoun
|
|||
ispreview: '='
|
||||
},
|
||||
controller: function($document, $window, $scope){
|
||||
$scope.noscroll = false;
|
||||
var NOSCROLL = false;
|
||||
var FORM_ACTION_ID = 'submit_field';
|
||||
$scope.forms = {};
|
||||
|
||||
//Don't start timer if we are looking at a design preview
|
||||
|
@ -59,99 +60,64 @@ angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCoun
|
|||
TimeCounter.restartClock();
|
||||
};
|
||||
|
||||
//Fire event when window is scrolled
|
||||
$window.onscroll = function(){
|
||||
$scope.scrollPos = document.body.scrollTop || document.documentElement.scrollTop || 0;
|
||||
var elemBox = document.getElementsByClassName('activeField')[0].getBoundingClientRect();
|
||||
$scope.fieldTop = elemBox.top;
|
||||
$scope.fieldBottom = elemBox.bottom;
|
||||
|
||||
var field_id;
|
||||
var field_index;
|
||||
|
||||
if(!$scope.noscroll){
|
||||
//Focus on submit button
|
||||
if( $scope.selected.index === $scope.myform.visible_form_fields.length-1 && $scope.fieldBottom < 200){
|
||||
field_index = $scope.selected.index+1;
|
||||
field_id = 'submit_field';
|
||||
$scope.setActiveField(field_id, field_index, false);
|
||||
}
|
||||
//Focus on field above submit button
|
||||
else if($scope.selected.index === $scope.myform.visible_form_fields.length){
|
||||
if($scope.fieldTop > 200){
|
||||
field_index = $scope.selected.index-1;
|
||||
field_id = $scope.myform.visible_form_fields[field_index]._id;
|
||||
$scope.setActiveField(field_id, field_index, false);
|
||||
}
|
||||
} else if( $scope.fieldBottom < 0){
|
||||
field_index = $scope.selected.index+1;
|
||||
field_id = $scope.myform.visible_form_fields[field_index]._id;
|
||||
$scope.setActiveField(field_id, field_index, false);
|
||||
} else if ( $scope.selected.index !== 0 && $scope.fieldTop > 0) {
|
||||
field_index = $scope.selected.index-1;
|
||||
field_id = $scope.myform.visible_form_fields[field_index]._id;
|
||||
$scope.setActiveField(field_id, field_index, false);
|
||||
}
|
||||
$scope.$apply();
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
** Field Controls
|
||||
*/
|
||||
var evaluateLogicJump = function(field){
|
||||
var logicJump = field.logicJump;
|
||||
|
||||
if (logicJump.expressionString && logicJump.valueB && field.fieldValue) {
|
||||
var parse_tree = jsep(logicJump.expressionString);
|
||||
var left, right;
|
||||
if(logicJump.enabled){
|
||||
if (logicJump.expressionString && logicJump.valueB && field.fieldValue) {
|
||||
var parse_tree = jsep(logicJump.expressionString);
|
||||
var left, right;
|
||||
|
||||
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;
|
||||
if(parse_tree.left.name === 'field'){
|
||||
left = field.fieldValue;
|
||||
right = logicJump.valueB;
|
||||
} else {
|
||||
left = logicJump.valueB;
|
||||
right = field.fieldValue;
|
||||
}
|
||||
} else {
|
||||
switch(parse_tree.operator) {
|
||||
case '==':
|
||||
return (left === right);
|
||||
case '!==':
|
||||
return (left !== right);
|
||||
case 'contains':
|
||||
return (left.indexOf(right) > -1);
|
||||
case '!contains':
|
||||
/* jshint -W018 */
|
||||
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;
|
||||
|
||||
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':
|
||||
/* jshint -W018 */
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -163,35 +129,46 @@ angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCoun
|
|||
throw new Error('current active field is null');
|
||||
}
|
||||
|
||||
if($scope.selected._id === 'submit_field') {
|
||||
if($scope.selected._id === FORM_ACTION_ID) {
|
||||
return $scope.myform.form_fields.length - 1;
|
||||
}
|
||||
return $scope.selected.index;
|
||||
};
|
||||
|
||||
$scope.isActiveField = function(field){
|
||||
if($scope.selected._id === field._id) {
|
||||
return true
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
$scope.setActiveField = $rootScope.setActiveField = function(field_id, field_index, animateScroll) {
|
||||
if($scope.selected === null || $scope.selected._id === field_id){
|
||||
//console.log('not scrolling');
|
||||
//console.log($scope.selected);
|
||||
return;
|
||||
}
|
||||
//console.log('field_id: '+field_id);
|
||||
//console.log('field_index: '+field_index);
|
||||
//console.log($scope.selected);
|
||||
if($scope.selected === null || (!field_id && field_index === null) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!field_id){
|
||||
field_id = $scope.myform.visible_form_fields[field_index]._id;
|
||||
} else if(field_index === null){
|
||||
field_index = $scope.myform.visible_form_fields.length
|
||||
|
||||
$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++){
|
||||
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;
|
||||
if(currField['_id'] == field_id){
|
||||
field_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($scope.selected._id === field_id){
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.selected._id = field_id;
|
||||
$scope.selected.index = field_index;
|
||||
|
||||
|
||||
var nb_valid = $filter('formValidity')($scope.myform);
|
||||
$scope.translateAdvancementData = {
|
||||
done: nb_valid,
|
||||
|
@ -200,10 +177,10 @@ angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCoun
|
|||
};
|
||||
|
||||
if(animateScroll){
|
||||
$scope.noscroll=true;
|
||||
NOSCROLL=true;
|
||||
setTimeout(function() {
|
||||
$document.scrollToElement(angular.element('.activeField'), -10, 200).then(function() {
|
||||
$scope.noscroll = false;
|
||||
NOSCROLL = false;
|
||||
setTimeout(function() {
|
||||
if (document.querySelectorAll('.activeField .focusOn').length) {
|
||||
//Handle default case
|
||||
|
@ -218,54 +195,109 @@ angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCoun
|
|||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
setTimeout(function() {
|
||||
if (document.querySelectorAll('.activeField .focusOn')[0]) {
|
||||
//FIXME: DAVID: Figure out how to set focus without scroll movement in HTML Dom
|
||||
document.querySelectorAll('.activeField .focusOn')[0].focus();
|
||||
} else if (document.querySelectorAll('.activeField input')[0]){
|
||||
document.querySelectorAll('.activeField input')[0].focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//Only send analytics data if form has not been submitted
|
||||
if(!$scope.myform.submitted){
|
||||
SendVisitorData.send($scope.myform, getActiveField(), TimeCounter.getTimeElapsed());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$rootScope.nextField = $scope.nextField = function(){
|
||||
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);
|
||||
}
|
||||
$scope.$watch('selected.index', function(oldValue, newValue){
|
||||
if(oldValue !== newValue && newValue < $scope.myform.form_fields.length){
|
||||
//Only send analytics data if form has not been submitted
|
||||
if(!$scope.myform.submitted){
|
||||
console.log('SendVisitorData.send()');
|
||||
SendVisitorData.send($scope.myform, newValue, TimeCounter.getTimeElapsed());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//Fire event when window is scrolled
|
||||
$window.onscroll = function(){
|
||||
if(!NOSCROLL){
|
||||
|
||||
var scrollTop = $(window).scrollTop();
|
||||
var elemBox = document.getElementsByClassName('activeField')[0].getBoundingClientRect();
|
||||
var fieldTop = elemBox.top;
|
||||
var fieldBottom = elemBox.bottom;
|
||||
|
||||
var field_id, field_index;
|
||||
var elemHeight = $('.activeField').height();
|
||||
|
||||
var submitSectionHeight = $('.form-actions').height();
|
||||
var maxScrollTop = $(document).height() - $(window).height();
|
||||
var fieldWrapperHeight = $('form_fields').height();
|
||||
|
||||
var selector = 'form > .field-directive:nth-of-type(' + String($scope.myform.visible_form_fields.length - 1)+ ')'
|
||||
var fieldDirectiveHeight = $(selector).height()
|
||||
var scrollPosition = maxScrollTop - submitSectionHeight - fieldDirectiveHeight*1.2;
|
||||
|
||||
var fractionToJump = 0.9;
|
||||
|
||||
//Focus on field above submit form button
|
||||
if($scope.selected.index === $scope.myform.visible_form_fields.length){
|
||||
if(scrollTop < scrollPosition){
|
||||
field_index = $scope.selected.index-1;
|
||||
$scope.setActiveField(null, field_index, false);
|
||||
}
|
||||
}
|
||||
|
||||
//Focus on submit form button
|
||||
else if($scope.selected.index === $scope.myform.visible_form_fields.length-1 && scrollTop > scrollPosition){
|
||||
field_index = $scope.selected.index+1;
|
||||
$scope.setActiveField(FORM_ACTION_ID, field_index, false);
|
||||
}
|
||||
|
||||
//If we scrolled bellow the current field, move to next field
|
||||
else if(fieldBottom < elemHeight * fractionToJump && $scope.selected.index < $scope.myform.visible_form_fields.length-1 ){
|
||||
field_index = $scope.selected.index+1;
|
||||
$scope.setActiveField(null, field_index, false);
|
||||
}
|
||||
//If we scrolled above the current field, move to prev field
|
||||
else if ( $scope.selected.index !== 0 && fieldTop > elemHeight * fractionToJump) {
|
||||
field_index = $scope.selected.index-1;
|
||||
$scope.setActiveField(null, field_index, false);
|
||||
}
|
||||
}
|
||||
|
||||
$scope.$apply();
|
||||
};
|
||||
|
||||
$rootScope.nextField = $scope.nextField = function(){
|
||||
if($scope.selected && $scope.selected.index > -1){
|
||||
|
||||
if($scope.selected._id !== FORM_ACTION_ID){
|
||||
var currField = $scope.myform.visible_form_fields[$scope.selected.index];
|
||||
|
||||
//Jump to logicJump's destination if it is true
|
||||
if(currField.logicJump && currField.logicJump.jumpTo && evaluateLogicJump(currField)){
|
||||
$scope.setActiveField(currField.logicJump.jumpTo, null, true);
|
||||
} else if($scope.selected.index < $scope.myform.visible_form_fields.length-1){
|
||||
$scope.setActiveField(null, $scope.selected.index+1, true);
|
||||
} else {
|
||||
$scope.setActiveField(FORM_ACTION_ID, null, true);
|
||||
}
|
||||
} else {
|
||||
//If we are at the submit actions page, go to the first field
|
||||
$rootScope.setActiveField(null, 0, true);
|
||||
}
|
||||
} else {
|
||||
//If selected is not defined go to the first field
|
||||
$rootScope.setActiveField(null, 0, true);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
$rootScope.prevField = $scope.prevField = function(){
|
||||
console.log('prevField');
|
||||
console.log($scope.selected);
|
||||
var selected_index = $scope.selected.index - 1;
|
||||
if($scope.selected.index > 0){
|
||||
var selected_index = $scope.selected.index - 1;
|
||||
var selected_id = $scope.myform.visible_form_fields[selected_index]._id;
|
||||
$scope.setActiveField(selected_id, selected_index, true);
|
||||
$scope.setActiveField(null, selected_index, true);
|
||||
}
|
||||
};
|
||||
|
||||
$rootScope.goToInvalid = $scope.goToInvalid = function() {
|
||||
var field_id = $('.row.field-directive .ng-invalid.focusOn, .row.field-directive .ng-untouched.focusOn:not(.ng-valid)').first().parents('.row.field-directive').first().attr('data-id');
|
||||
$scope.setActiveField(field_id, null, true);
|
||||
};
|
||||
|
||||
/*
|
||||
** Form Display Functions
|
||||
*/
|
||||
|
@ -276,10 +308,6 @@ angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCoun
|
|||
}
|
||||
};
|
||||
|
||||
$rootScope.goToInvalid = $scope.goToInvalid = function() {
|
||||
document.querySelectorAll('.ng-invalid.focusOn')[0].focus();
|
||||
};
|
||||
|
||||
var getDeviceData = function(){
|
||||
var md = new MobileDetect(window.navigator.userAgent);
|
||||
var deviceType = 'other';
|
||||
|
@ -320,6 +348,10 @@ angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCoun
|
|||
};
|
||||
|
||||
$rootScope.submitForm = $scope.submitForm = function() {
|
||||
if($scope.forms.myForm.$invalid){
|
||||
$scope.goToInvalid();
|
||||
return;
|
||||
}
|
||||
|
||||
var _timeElapsed = TimeCounter.stopClock();
|
||||
$scope.loading = true;
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
<div class="col-xs-12 field-input">
|
||||
<div class="control-group input-append">
|
||||
<input class="focusOn"
|
||||
ng-focus="setActiveField(field._id, null, false)"
|
||||
ng-style="{'color': design.colors.answerColor, 'border-color': design.colors.answerColor}"
|
||||
ng-class="{ 'no-border': !!field.fieldValue }"
|
||||
ui-date="dateOptions"
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
</div>
|
||||
<div class="col-xs-12 field-input">
|
||||
<ui-select ng-model="field.fieldValue"
|
||||
ng-focus="setActiveField(field._id, null, false)"
|
||||
theme="selectize"
|
||||
search-enabled="true"
|
||||
search-by="option_value"
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
{{$index+1}}
|
||||
</div>
|
||||
<input ng-style="{'color': design.colors.answerColor, 'border-color': design.colors.answerColor}"
|
||||
ng-focus="setActiveField(field._id, null, false)"
|
||||
type="radio" class="focusOn"
|
||||
value="{{option.option_value}}"
|
||||
ng-model="field.fieldValue"
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<p class="col-xs-12" ng-if="field.description.length">{{field.description}} </p>
|
||||
<br>
|
||||
<div class="col-xs-offset-1 col-xs-11">
|
||||
<button class="btn focusOn">
|
||||
<button class="btn focusOn"
|
||||
ng-style="{'font-size': '1.3em', 'background-color':design.colors.buttonColor, 'color':design.colors.buttonTextColor}"
|
||||
ng-click="nextField()">
|
||||
{{ 'CONTINUE' | translate }}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
<div class="col-xs-12 field-input">
|
||||
<small style="font-size:0.6em;">Press SHIFT+ENTER to add a newline</small>
|
||||
<textarea class="textarea focusOn" type="text"
|
||||
ng-focus="setActiveField(field._id, null, false)"
|
||||
ng-model="field.fieldValue"
|
||||
ng-model-options="{ debounce: 250 }"
|
||||
ng-class="{ 'no-border': !!field.fieldValue }"
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
placeholder="{{placeholder}}"
|
||||
ng-class="{ 'no-border': !!field.fieldValue }"
|
||||
class="focusOn text-field-input"
|
||||
ng-focus="setActiveField(field._id, null, false)"
|
||||
ng-model="field.fieldValue"
|
||||
ng-model-options="{ debounce: 250 }"
|
||||
value="field.fieldValue"
|
||||
|
@ -45,7 +46,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="btn btn-lg btn-default"
|
||||
<div class="btn btn-lg btn-default" ng-disabled="!field.fieldValue || field.$invalid"
|
||||
style="padding: 4px; margin-top:8px; background: rgba(255,255,255,0.5)">
|
||||
<button ng-disabled="!field.fieldValue || field.$invalid"
|
||||
ng-style="{'background-color':design.colors.buttonColor, 'color':design.colors.buttonTextColor}"
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
<input type="radio" value="true"
|
||||
class="focusOn"
|
||||
style="opacity: 0; margin-left: 0px;"
|
||||
ng-focus="setActiveField(field._id, null, false)"
|
||||
ng-model="field.fieldValue"
|
||||
ng-model-options="{ debounce: 250 }"
|
||||
ng-required="field.required"
|
||||
|
@ -46,6 +47,7 @@
|
|||
|
||||
<input type="radio" value="false"
|
||||
style="opacity:0; margin-left:0px;"
|
||||
ng-focus="setActiveField(field._id, null, false)"
|
||||
ng-model="field.fieldValue"
|
||||
ng-model-options="{ debounce: 250 }"
|
||||
ng-required="field.required"
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
<section class="overlay submitform" ng-if="!ispreview && (loading || (!myform.submitted && !myform.startPage.showStart))"></section>
|
||||
<section class="overlay previewform submitform" ng-if="ispreview && (loading || (!myform.submitted && !myform.startPage.showStart))"></section>
|
||||
|
||||
<!-- Start Page View -->
|
||||
<div ng-show="!myform.submitted && myform.startPage.showStart" class="form-submitted" style="padding-top: 35vh;">
|
||||
<div class="row">
|
||||
<div class="col-xs-12 text-center" style="overflow-wrap: break-word;">
|
||||
<h1 style="font-weight: 400; nont-size: 25px;" ng-style="{'color': form.design.colors.questionColor}">
|
||||
<h1 style="font-weight: 400; nont-size: 25px;" ng-style="{'color': myform.design.colors.questionColor}">
|
||||
{{myform.startPage.introTitle}}
|
||||
</h1>
|
||||
</div>
|
||||
<div class="col-xs-10 col-xs-offset-1 text-center" style="overflow-wrap: break-word;">
|
||||
<p style="font-weight: 100; font-size: 16px;" ng-style="{'color': form.design.colors.questionColor}">
|
||||
<p style="font-weight: 100; font-size: 16px;" ng-style="{'color': myform.design.colors.questionColor}">
|
||||
{{myform.startPage.introParagraph}}
|
||||
</p>
|
||||
</div>
|
||||
|
@ -37,42 +34,46 @@
|
|||
</div>
|
||||
|
||||
<!-- Form Fields View -->
|
||||
<div class="form-fields" ng-show="!myform.submitted && !myform.startPage.showStart" ng-style="{ 'border-color': myform.design.colors.buttonTextColor }">
|
||||
<div class="form-fields"
|
||||
ng-show="!myform.submitted && !myform.startPage.showStart"
|
||||
ng-style="{ 'border-color': myform.design.colors.buttonTextColor }">
|
||||
|
||||
<div class="row">
|
||||
<form name="forms.myForm" novalidate class="submission-form col-sm-12 col-md-offset-1 col-md-10">
|
||||
<div class="row form-field-wrapper">
|
||||
<form name="forms.myForm" novalidate class="submission-form">
|
||||
|
||||
<div ng-repeat="field in myform.form_fields" ng-if="!field.deletePreserved" data-index="{{$index}}" data-id="{{field._id}}" ng-class="{activeField: selected._id == field._id }" class="row field-directive">
|
||||
|
||||
<field-directive field="field" design="myform.design" index="$index" forms="forms">
|
||||
</field-directive>
|
||||
</div>
|
||||
<div class="row form-actions" id="submit_field" ng-class="{activeField: selected._id == 'submit_field' }"
|
||||
ng-style="{ 'background-color':myform.design.colors.buttonColor}" style="border-top: 1px solid #ddd; margin-top: 30vh; height: 100vh; margin-left: 1%; margin-right: 1%;"
|
||||
on-tab-and-shift-key="prevField()"
|
||||
on-tab-key="nextField()"
|
||||
on-enter-key="submitForm()">
|
||||
|
||||
<div class="col-xs-12 text-left" style="background-color:#990000; color:white;" ng-if="forms.myForm.$invalid">
|
||||
{{ 'COMPLETING_NEEDED' | translate:translateAdvancementData }}
|
||||
</div>
|
||||
|
||||
<button ng-if="!forms.myForm.$invalid" class="Button btn col-sm-2 col-xs-8 focusOn" v-busy="loading" v-busy-label="Please wait" v-pressable ng-disabled="loading || forms.myForm.$invalid" ng-click="submitForm()" on-enter-key-disabled="loading || forms.myForm.$invalid" ng-style="{'background-color':myform.design.colors.buttonColor, 'color':myform.design.colors.buttonTextColor}" style="font-size: 1.6em; margin-left: 1em; margin-top: 1em;">
|
||||
|
||||
{{ 'SUBMIT' | translate }}
|
||||
</button>
|
||||
|
||||
<button ng-if="forms.myForm.$invalid" class="Button btn col-sm-2 col-xs-8 focusOn" ng-click="goToInvalid()" on-enter-key="goToInvalid()" on-enter-key-disabled="!forms.myForm.$invalid" style="font-size: 1.6em; margin-left: 1em; margin-top: 1em; background-color:#990000; color:white">
|
||||
{{ 'REVIEW' | translate }}
|
||||
</button>
|
||||
|
||||
<div class="col-sm-2 hidden-xs" style="font-size: 75%; margin-top:3.25em">
|
||||
<small>
|
||||
{{ 'ENTER' | translate }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="row form-actions" id="submit_field" ng-class="{activeField: selected._id == 'submit_field' }" ng-style="{ 'background-color':myform.design.colors.buttonColor}" style="border-top: 1px solid #ddd; margin-right: -13%; margin-left: -13%; margin-top: 30vh; height: 100vh">
|
||||
|
||||
<div class="col-xs-12 text-left" style="background-color:#990000; color:white;" ng-if="forms.myForm.$invalid">
|
||||
{{ 'COMPLETING_NEEDED' | translate:translateAdvancementData }}
|
||||
</div>
|
||||
|
||||
<button ng-if="!forms.myForm.$invalid" class="Button btn col-sm-2 col-xs-8 focusOn" v-busy="loading" v-busy-label="Please wait" v-pressable ng-disabled="loading || forms.myForm.$invalid" ng-click="submitForm()" on-enter-key="submitForm()" on-enter-key-disabled="loading || forms.myForm.$invalid" ng-style="{'background-color':myform.design.colors.buttonColor, 'color':myform.design.colors.buttonTextColor}" style="font-size: 1.6em; margin-left: 1em; margin-top: 1em;">
|
||||
|
||||
{{ 'SUBMIT' | translate }}
|
||||
</button>
|
||||
|
||||
<button ng-if="forms.myForm.$invalid" class="Button btn col-sm-2 col-xs-8 focusOn" ng-click="goToInvalid()" on-enter-key="goToInvalid()" on-enter-key-disabled="!forms.myForm.$invalid" style="font-size: 1.6em; margin-left: 1em; margin-top: 1em; background-color:#990000; color:white">
|
||||
{{ 'REVIEW' | translate }}
|
||||
</button>
|
||||
|
||||
<div class="col-sm-2 hidden-xs" style="font-size: 75%; margin-top:3.25em">
|
||||
<small>
|
||||
{{ 'ENTER' | translate }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section ng-if="!myform.hideFooter" class="navbar navbar-fixed-bottom" ng-style="{ 'background-color':myform.design.colors.buttonColor, 'padding-top': '15px', 'border-top': '2px '+ myform.design.colors.buttonTextColor +' solid', 'color':myform.design.colors.buttonTextColor}">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
<script>
|
||||
$(".loader").fadeOut("slow");
|
||||
</script>
|
||||
<section class="public-form auth sigin-view valign-wrapper">
|
||||
<div class="row valign">
|
||||
<h3 class="col-md-12 text-center">404 - Form Does not Exist </h3>
|
||||
<div class="col-md-4 col-md-offset-4">
|
||||
<div class="col-md-12 text-center" style="padding-bottom: 50px;">
|
||||
The form you are trying to access does not exist. Sorry about that!
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
|
@ -1,4 +1,7 @@
|
|||
<section class="auth sigin-view valign-wrapper">
|
||||
<script>
|
||||
$(".loader").fadeOut("slow");
|
||||
</script>
|
||||
<section class="public-form auth sigin-view valign-wrapper">
|
||||
<div class="row valign">
|
||||
<h3 class="col-md-12 text-center">Not Authorized to Access Form</h3>
|
||||
<div class="col-md-4 col-md-offset-4">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<section class="public-form" ng-style="{ 'background-color': myform.design.colors.backgroundColor }">
|
||||
<section class="public-form">
|
||||
<submit-form-directive myform="myform"></submit-form-directive>
|
||||
</section>
|
||||
|
||||
|
|
|
@ -27,7 +27,11 @@ angular.module('view-form').config(['$stateProvider',
|
|||
}).
|
||||
state('unauthorizedFormAccess', {
|
||||
url: '/forms/unauthorized',
|
||||
templateUrl: '/static/form_modules/forms/base/views/form-unauthorized.client.view.html',
|
||||
templateUrl: '/static/form_modules/forms/base/views/form-unauthorized.client.view.html'
|
||||
})
|
||||
.state('formNotFound', {
|
||||
url: '*path',
|
||||
templateUrl: '/static/form_modules/forms/base/views/form-not-found.client.view.html'
|
||||
});
|
||||
}
|
||||
]);
|
||||
|
|
|
@ -28,18 +28,6 @@
|
|||
deviceType = 'desktop';
|
||||
}
|
||||
|
||||
$.ajaxSetup( { 'async': false } );
|
||||
var geoData = $.getJSON('https://freegeoip.net/json/').responseJSON;
|
||||
$.ajaxSetup( { 'async': true } );
|
||||
|
||||
if(!geoData){
|
||||
geoData = {
|
||||
ip: '',
|
||||
city: '',
|
||||
country_name: ''
|
||||
};
|
||||
}
|
||||
|
||||
// Create a new message object
|
||||
var visitorData = {
|
||||
referrer: document.referrer,
|
||||
|
@ -49,11 +37,8 @@
|
|||
timeElapsed: timeElapsed,
|
||||
language: lang,
|
||||
deviceType: deviceType,
|
||||
ipAddr: geoData.ip,
|
||||
geoLocation: {
|
||||
city: geoData.city,
|
||||
country: geoData.country_name
|
||||
}
|
||||
ipAddr: null,
|
||||
geoLocation: null
|
||||
};
|
||||
|
||||
Socket.emit('form-visitor-data', visitorData);
|
||||
|
|
|
@ -1,3 +1,18 @@
|
|||
/*Modal overlay (for lightbox effect)*/
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: rgb(0,0,0);
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
z-index: 10;
|
||||
}
|
||||
.overlay.submitform {
|
||||
background-color: rgb(256,256,256);
|
||||
background-color: rgba(256,256,256,0.8);
|
||||
}
|
||||
|
||||
.form-item .title-row > .list-group-item-heading {
|
||||
color: #34628a;
|
||||
|
|
|
@ -16,6 +16,7 @@ angular.module('forms').controller('AdminFormController', ['$rootScope', '$windo
|
|||
$scope.animationsEnabled = true;
|
||||
$scope.myform = myForm;
|
||||
$rootScope.saveInProgress = false;
|
||||
$scope.oldForm = _.cloneDeep($scope.myform);
|
||||
|
||||
CurrentForm.setForm($scope.myform);
|
||||
|
||||
|
@ -42,13 +43,23 @@ angular.module('forms').controller('AdminFormController', ['$rootScope', '$windo
|
|||
}
|
||||
};
|
||||
|
||||
$scope.tabData = [
|
||||
$scope.tabData = [
|
||||
{
|
||||
heading: $filter('translate')('CONFIGURE_TAB'),
|
||||
templateName: 'configure'
|
||||
}
|
||||
];
|
||||
|
||||
$scope.designTabActive = false
|
||||
|
||||
$scope.deactivateDesignTab = function(){
|
||||
$scope.designTabActive = false
|
||||
}
|
||||
|
||||
$scope.activateDesignTab = function(){
|
||||
$scope.designTabActive = true
|
||||
}
|
||||
|
||||
$scope.setForm = function(form){
|
||||
$scope.myform = form;
|
||||
};
|
||||
|
@ -102,16 +113,20 @@ angular.module('forms').controller('AdminFormController', ['$rootScope', '$windo
|
|||
}
|
||||
};
|
||||
|
||||
// Update existing Form
|
||||
$scope.update = $rootScope.update = function(updateImmediately, data, isDiffed, refreshAfterUpdate, cb){
|
||||
refreshFrame();
|
||||
$scope.updateDesign = function(updateImmediately, data, shouldDiff, refreshAfterUpdate){
|
||||
$scope.update(updateImmediately, data, shouldDiff, refreshAfterUpdate, function(){
|
||||
refreshFrame();
|
||||
});
|
||||
}
|
||||
|
||||
// Update existing Form
|
||||
$scope.update = $rootScope.update = function(updateImmediately, data, shouldDiff, refreshAfterUpdate, cb){
|
||||
var continueUpdate = true;
|
||||
if(!updateImmediately){
|
||||
continueUpdate = !$rootScope.saveInProgress;
|
||||
}
|
||||
|
||||
//Update form **if we are not currently updating** or if **shouldUpdateNow flag is set**
|
||||
//Update form **if we are not in the middle of an update** or if **shouldUpdateNow flag is set**
|
||||
if(continueUpdate) {
|
||||
var err = null;
|
||||
|
||||
|
@ -119,11 +134,24 @@ angular.module('forms').controller('AdminFormController', ['$rootScope', '$windo
|
|||
$rootScope.saveInProgress = true;
|
||||
}
|
||||
|
||||
if (isDiffed) {
|
||||
if (shouldDiff) {
|
||||
//Do this so we can create duplicate fields
|
||||
var checkForValidId = new RegExp('^[0-9a-fA-F]{24}$');
|
||||
for(var i=0; i < $scope.myform.form_fields.length; i++){
|
||||
var field = $scope.myform.form_fields[i];
|
||||
if(!checkForValidId.exec(field._id+'')){
|
||||
delete $scope.myform.form_fields[i]._id;
|
||||
delete $scope.myform.form_fields[i].id;
|
||||
}
|
||||
}
|
||||
|
||||
var data = DeepDiff.diff($scope.oldForm, $scope.myform);
|
||||
|
||||
$scope.updatePromise = $http.put('/forms/' + $scope.myform._id, {changes: data})
|
||||
.then(function (response) {
|
||||
if (refreshAfterUpdate) {
|
||||
$rootScope.myform = $scope.myform = response.data;
|
||||
$scope.oldForm = _.cloneDeep($scope.myform);
|
||||
}
|
||||
}).catch(function (response) {
|
||||
err = response.data;
|
||||
|
@ -146,6 +174,22 @@ angular.module('forms').controller('AdminFormController', ['$rootScope', '$windo
|
|||
delete dataToSend.submissions;
|
||||
}
|
||||
|
||||
if(dataToSend.visible_form_fields){
|
||||
delete dataToSend.visible_form_fields;
|
||||
}
|
||||
|
||||
if(dataToSend.analytics){
|
||||
delete dataToSend.analytics.visitors;
|
||||
delete dataToSend.analytics.fields;
|
||||
delete dataToSend.analytics.submissions;
|
||||
delete dataToSend.analytics.views;
|
||||
delete dataToSend.analytics.conversionRate;
|
||||
}
|
||||
|
||||
delete dataToSend.created;
|
||||
delete dataToSend.lastModified;
|
||||
delete dataToSend.__v;
|
||||
|
||||
$scope.updatePromise = $http.put('/forms/' + $scope.myform._id, {form: dataToSend})
|
||||
.then(function (response) {
|
||||
if (refreshAfterUpdate) {
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
'use strict';
|
||||
|
||||
// Forms controller
|
||||
angular.module('forms').controller('ListFormsController', ['$rootScope', '$scope', '$stateParams', '$state', 'GetForms', 'CurrentForm', '$http', '$uibModal',
|
||||
function($rootScope, $scope, $stateParams, $state, GetForms, CurrentForm, $http, $uibModal) {
|
||||
angular.module('forms').controller('ListFormsController', ['$rootScope', '$scope', '$stateParams', '$state', 'GetForms', 'CurrentForm', '$http', '$uibModal', 'myForms',
|
||||
function($rootScope, $scope, $stateParams, $state, GetForms, CurrentForm, $http, $uibModal, myForms) {
|
||||
|
||||
$scope = $rootScope;
|
||||
$scope.forms = {};
|
||||
$scope.showCreateModal = false;
|
||||
$scope.myforms = myForms
|
||||
|
||||
$rootScope.languageRegExp = {
|
||||
regExp: /[@!#$%^&*()\-+={}\[\]|\\/'";:`.,~№?<>]+/i,
|
||||
|
@ -49,13 +50,6 @@ angular.module('forms').controller('ListFormsController', ['$rootScope', '$scope
|
|||
}
|
||||
};
|
||||
|
||||
// Return all user's Forms
|
||||
$scope.findAll = function() {
|
||||
GetForms.query(function(_forms){
|
||||
$scope.myforms = _forms;
|
||||
});
|
||||
};
|
||||
|
||||
//Modal functions
|
||||
$scope.openCreateModal = function(){
|
||||
if(!$scope.showCreateModal){
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormFields', '$uibModal',
|
||||
|
@ -23,7 +24,7 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
|
|||
forceHelperSize: true,
|
||||
forcePlaceholderSize: true,
|
||||
update: function(e, ui) {
|
||||
$scope.update(false, $scope.myform, false, false, function(err){
|
||||
$scope.update(false, $scope.myform, true, false, function(err){
|
||||
});
|
||||
},
|
||||
};
|
||||
|
@ -31,7 +32,7 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
|
|||
/*
|
||||
** EditModal Functions
|
||||
*/
|
||||
$scope.openEditModal = function(curr_field){
|
||||
$scope.openEditModal = function(curr_field, isEdit, field_index){
|
||||
$scope.editFieldModal = $uibModal.open({
|
||||
animation: true,
|
||||
templateUrl: 'editFieldModal.html',
|
||||
|
@ -40,9 +41,11 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
|
|||
$scope.field = curr_field;
|
||||
$scope.showLogicJump = false;
|
||||
|
||||
$scope.isEdit = isEdit;
|
||||
|
||||
// decides whether field options block will be shown (true for dropdown and radio fields)
|
||||
$scope.showAddOptions = function (field){
|
||||
if(field.fieldType === 'dropdown' || field.fieldType === 'checkbox' || field.fieldType === 'radio'){
|
||||
if($scope.field.fieldType === 'dropdown' || $scope.field.fieldType === 'checkbox' || $scope.field.fieldType === 'radio'){
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
|
@ -66,13 +69,13 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
|
|||
];
|
||||
|
||||
// add new option to the field
|
||||
$scope.addOption = function(currField){
|
||||
if(currField.fieldType === 'checkbox' || currField.fieldType === 'dropdown' || currField.fieldType === 'radio'){
|
||||
if(!currField.fieldOptions){
|
||||
currField.fieldOptions = [];
|
||||
$scope.addOption = function(){
|
||||
if($scope.field.fieldType === 'checkbox' || $scope.field.fieldType === 'dropdown' || $scope.field.fieldType === 'radio'){
|
||||
if(!$scope.field.fieldOptions){
|
||||
$scope.field.fieldOptions = [];
|
||||
}
|
||||
|
||||
var lastOptionID = currField.fieldOptions.length+1;
|
||||
var lastOptionID = $scope.field.fieldOptions.length+1;
|
||||
|
||||
// new option's id
|
||||
|
||||
|
@ -83,17 +86,17 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
|
|||
};
|
||||
|
||||
// put new option into fieldOptions array
|
||||
currField.fieldOptions.push(newOption);
|
||||
$scope.field.fieldOptions.push(newOption);
|
||||
}
|
||||
};
|
||||
|
||||
// delete particular option
|
||||
$scope.deleteOption = function (currField, option){
|
||||
if(currField.fieldType === 'checkbox' || currField.fieldType === 'dropdown' || currField.fieldType === 'radio'){
|
||||
for(var i = 0; i < currField.fieldOptions.length; i++){
|
||||
if(currField.fieldOptions[i].option_id === option.option_id){
|
||||
$scope.deleteOption = function (option){
|
||||
if($scope.field.fieldType === 'checkbox' || $scope.field.fieldType === 'dropdown' || $scope.field.fieldType === 'radio'){
|
||||
for(var i = 0; i < $scope.field.fieldOptions.length; i++){
|
||||
if($scope.field.fieldOptions[i].option_id === option.option_id){
|
||||
|
||||
currField.fieldOptions.splice(i, 1);
|
||||
$scope.field.fieldOptions.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -118,8 +121,8 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
|
|||
};
|
||||
|
||||
// decides whether field options block will be shown (true for dropdown and radio fields)
|
||||
$scope.showRatingOptions = function (field){
|
||||
if(field.fieldType === 'rating'){
|
||||
$scope.showRatingOptions = function (){
|
||||
if($scope.field.fieldType === 'rating'){
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
|
@ -127,11 +130,16 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
|
|||
};
|
||||
|
||||
$scope.saveField = function(){
|
||||
if($scope.isEdit){
|
||||
$scope.myform.form_fields[field_index] = $scope.field;
|
||||
} else {
|
||||
$scope.myform.form_fields.push(curr_field);
|
||||
}
|
||||
|
||||
$scope.myform.form_fields.push(curr_field);
|
||||
$scope.$parent.update(false, $scope.$parent.myform, false, true, function(){
|
||||
$scope.$parent.update(false, $scope.$parent.myform, true, true, function(){
|
||||
$uibModalInstance.close();
|
||||
});
|
||||
|
||||
};
|
||||
$scope.cancel = function(){
|
||||
$uibModalInstance.close();
|
||||
|
@ -184,7 +192,7 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
|
|||
};
|
||||
|
||||
$scope.saveStartPage = function(){
|
||||
$scope.$parent.update(false, $scope.$parent.myform, false, true, function(){
|
||||
$scope.$parent.update(false, $scope.$parent.myform, true, true, function(){
|
||||
$uibModalInstance.close();
|
||||
});
|
||||
};
|
||||
|
@ -196,7 +204,7 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
|
|||
};
|
||||
|
||||
/*
|
||||
** EditStartPageModal Functions
|
||||
** EditEndPageModal Functions
|
||||
*/
|
||||
$scope.openEditEndPageModal = function(){
|
||||
$scope.editEndPageModal = $uibModal.open({
|
||||
|
@ -239,7 +247,7 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
|
|||
};
|
||||
|
||||
$scope.saveEndPage = function(){
|
||||
$scope.$parent.update(false, $scope.$parent.myform, false, true, function(){
|
||||
$scope.$parent.update(false, $scope.$parent.myform, true, true, function(){
|
||||
$uibModalInstance.close();
|
||||
});
|
||||
};
|
||||
|
@ -267,7 +275,7 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
|
|||
** Field CRUD Methods
|
||||
*/
|
||||
// Add a new field
|
||||
$scope.addNewField = function(modifyForm, fieldType){
|
||||
$scope.addNewField = function(fieldType){
|
||||
// increment lastAddedID counter
|
||||
$scope.addField.lastAddedID++;
|
||||
var fieldTitle = fieldType;
|
||||
|
@ -306,12 +314,7 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
|
|||
});
|
||||
}
|
||||
|
||||
if(modifyForm){
|
||||
//Add newField to form_fields array
|
||||
$scope.myform.form_fields.push(newField);
|
||||
}
|
||||
|
||||
$scope.openEditModal(newField);
|
||||
$scope.openEditModal(newField, false, $scope.myform.form_fields.length);
|
||||
};
|
||||
|
||||
// decides whether field options block will be shown (true for dropdown and radio fields)
|
||||
|
|
|
@ -16,27 +16,10 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
|
|||
rows: []
|
||||
};
|
||||
|
||||
var submissions = $scope.myform.submissions || [];
|
||||
|
||||
//Iterate through form's submissions
|
||||
for(var i = 0; i < submissions.length; i++){
|
||||
for(var x = 0; x < submissions[i].form_fields.length; x++){
|
||||
if(submissions[i].form_fields[x].fieldType === 'dropdown'){
|
||||
submissions[i].form_fields[x].fieldValue = submissions[i].form_fields[x].fieldValue.option_value;
|
||||
}
|
||||
//var oldValue = submissions[i].form_fields[x].fieldValue || '';
|
||||
//submissions[i].form_fields[x] = _.merge(defaultFormFields, submissions[i].form_fields);
|
||||
//submissions[i].form_fields[x].fieldValue = oldValue;
|
||||
}
|
||||
submissions[i].selected = false;
|
||||
}
|
||||
|
||||
$scope.table.rows = submissions;
|
||||
|
||||
var initController = function(){
|
||||
$http({
|
||||
method: 'GET',
|
||||
url: '/someUrl'
|
||||
url: '/forms'+$scope.myform._id+'/submissions'
|
||||
}).then(function successCallback(response) {
|
||||
var defaultFormFields = _.cloneDeep($scope.myform.form_fields);
|
||||
|
||||
|
@ -59,6 +42,8 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
|
|||
});
|
||||
};
|
||||
|
||||
initController();
|
||||
|
||||
|
||||
/*
|
||||
** Analytics Functions
|
||||
|
|
|
@ -59,16 +59,16 @@
|
|||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<uib-tabset active="activePill" vertical="true" type="pills">
|
||||
<uib-tab index="0" heading="{{ 'CREATE_TAB' | translate }}">
|
||||
<uib-tab index="0" heading="{{ 'CREATE_TAB' | translate }}" select="deactivateDesignTab()">
|
||||
<edit-form-directive myform="myform"></edit-form-directive>
|
||||
</uib-tab>
|
||||
<uib-tab ng-repeat="tab in tabData" index="{{$index+1}}" heading="{{tab.heading}}">
|
||||
<uib-tab ng-repeat="tab in tabData" index="{{$index+1}}" heading="{{tab.heading}}" select="deactivateDesignTab()">
|
||||
<div class='row' data-ng-include="'/static/modules/forms/admin/views/adminTabs/'+tab.templateName+'.html'"></div>
|
||||
</uib-tab>
|
||||
<uib-tab index="2" heading="{{ 'ANALYZE_TAB' | translate }}">
|
||||
<uib-tab index="2" heading="{{ 'ANALYZE_TAB' | translate }}" select="deactivateDesignTab()">
|
||||
<edit-submissions-form-directive myform="myform" user="myform.admin"></edit-submissions-form-directive>
|
||||
</uib-tab>
|
||||
<uib-tab ng-if="tabData" heading="{{ 'SHARE_TAB' | translate }}" index="{{tabData.length}}">
|
||||
<uib-tab ng-if="tabData" heading="{{ 'SHARE_TAB' | translate }}" index="{{tabData.length}}" select="deactivateDesignTab()">
|
||||
<div class="config-form">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
|
@ -114,7 +114,8 @@
|
|||
</div>
|
||||
</div>
|
||||
</uib-tab>
|
||||
<uib-tab ng-if="tabData && myform.form_fields.length" heading="{{ 'DESIGN_TAB' | translate }}" index="{{tabData.length}}+1">
|
||||
<uib-tab class="design-tab" ng-if="tabData && myform.form_fields.length" heading="{{ 'DESIGN_TAB' | translate }}" index="{{tabData.length}}+1"
|
||||
select="activateDesignTab()">
|
||||
<div class="config-form design container">
|
||||
<div class="row">
|
||||
<div class="col-sm-4 col-xs-12">
|
||||
|
@ -175,7 +176,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-8 hidden-xs">
|
||||
<div class="col-sm-8 hidden-xs" ng-if="designTabActive">
|
||||
<div class="public-form" ng-style="{ 'background-color': myform.design.colors.backgroundColor }">
|
||||
<iframe id="iframe" ng-if="!!formURL" ng-src="{{formURL | trustSrc}}" style="border: none; box-shadow: 0px 0px 10px 0px grey; overflow: hidden; height: 400px; width: 90%; position: absolute;"></iframe>
|
||||
</div>
|
||||
|
@ -184,7 +185,7 @@
|
|||
|
||||
<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, myform, false, false, null)"><i class="icon-arrow-left icon-white"></i>{{ 'SAVE_CHANGES' | translate }}</button>
|
||||
<button class="btn btn-signup btn-rounded" type="button" ng-click="updateDesign(false, myform, false, false)"><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>
|
||||
|
|
|
@ -146,8 +146,6 @@
|
|||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<!-- Edit StartPage Modal Dialog Template -->
|
||||
|
@ -291,8 +289,6 @@
|
|||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<!-- Edit Field Modal Dialog Template -->
|
||||
|
@ -312,34 +308,34 @@
|
|||
|
||||
<div class="row"><br></div>
|
||||
|
||||
<div class="row description" ng-hide="showRatingOptions(field)">
|
||||
<div class="row description" ng-hide="showRatingOptions()">
|
||||
<div class="col-md-12 bold">{{ 'QUESTION_DESCRIPTION' | translate }}</div>
|
||||
<div class="col-md-12">
|
||||
<textarea type="text" class="form-control" ng-model="field.description" name="description{{field._id}}"value="{{field.description}}"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-show="showAddOptions(field)"><br></div>
|
||||
<div class="row options" ng-if="showAddOptions(field)">
|
||||
<div class="row" ng-show="showAddOptions()"><br></div>
|
||||
<div class="row options" ng-if="showAddOptions()">
|
||||
<div class="col-md-4 col-xs-12">{{ 'OPTIONS' | translate }}</div>
|
||||
<div class="col-md-8 col-xs-12">
|
||||
<div ng-repeat="option in field.fieldOptions track by option.option_id" class="row">
|
||||
<input type="text" name="{{option.option_value}}{{field._id}}" ng-model="option.option_value" class="col-xs-5">
|
||||
|
||||
<a class="btn btn-danger btn-mini right" type="button" ng-click="deleteOption(field, option)" class="col-xs-3">
|
||||
<a class="btn btn-danger btn-mini right" type="button" ng-click="deleteOption(option)" class="col-xs-3">
|
||||
<i class="fa fa-trash-o"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div class="row">
|
||||
<button class="btn btn-primary btn-small col-md-offset-0 col-md-6 col-sm-4 col-sm-offset-4 col-xs-6 col-xs-offset-6" type="button" ng-click="addOption(field)">
|
||||
<button class="btn btn-primary btn-small col-md-offset-0 col-md-6 col-sm-4 col-sm-offset-4 col-xs-6 col-xs-offset-6" type="button" ng-click="addOption()">
|
||||
<i class="icon-plus icon-white"></i> {{ 'ADD_OPTION' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row" ng-show="showRatingOptions(field)"><br></div>
|
||||
<div class="row" ng-if="showRatingOptions(field)">
|
||||
<div class="row" ng-show="showRatingOptions()"><br></div>
|
||||
<div class="row" ng-if="showRatingOptions()">
|
||||
<div class="col-md-9 col-sm-9">{{ 'NUM_OF_STEPS' | translate }}</div>
|
||||
<div class="col-md-3 col-sm-3">
|
||||
<input style="width:100%" type="number"
|
||||
|
@ -383,7 +379,7 @@
|
|||
<div class="col-md-4 col-xs-12 field-input">{{ 'LOGIC_JUMP' | translate }}</div>
|
||||
<div class="col-md-8 col-xs-12 field-input">
|
||||
<label class="switch-light switch-holo" onclick="">
|
||||
<input type="checkbox" ng-model="showLogicJump">
|
||||
<input type="checkbox" ng-model="field.logicJump.enabled">
|
||||
<span>
|
||||
<span> {{ 'OFF' | translate }}</span>
|
||||
<span> {{ 'ON' | translate }}</span>
|
||||
|
@ -392,7 +388,7 @@
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row question" ng-if="!!showLogicJump">
|
||||
<div class="row question" ng-if="field.logicJump.enabled"">
|
||||
<div class="col-md-4 col-sm-12">
|
||||
|
||||
<b> {{ 'IF_THIS_FIELD' | translate }} </b>
|
||||
|
@ -483,7 +479,7 @@
|
|||
</div>
|
||||
|
||||
<div class="preview-field-panel col-md-6 hidden-sm hidden-xs">
|
||||
<form class="public-form"ss>
|
||||
<form class="public-form">
|
||||
<field-directive field="field" validate="false" class="preview-field">
|
||||
</field-directive>
|
||||
</form>
|
||||
|
@ -507,7 +503,7 @@
|
|||
|
||||
<div class="col-xs-12 col-sm-12 col-md-6" ng-repeat="type in addField.types" style="padding-top:7.5px;">
|
||||
<div class="panel panel-default" style="background-color:#f5f5f5;">
|
||||
<div class="panel-heading" ng-click="addNewField(false, type.name)" style="cursor: pointer; font-size:12px; padding-left: 10px; padding-right: 10px;">
|
||||
<div class="panel-heading" ng-click="addNewField(type.name)" style="cursor: pointer; font-size:12px; padding-left: 10px; padding-right: 10px;">
|
||||
<span>
|
||||
<field-icon-directive type-name="{{type.name}}">
|
||||
</field-icon-directive>
|
||||
|
@ -545,7 +541,7 @@
|
|||
|
||||
<div class="col-xs-12 field-row" ng-repeat="field in myform.form_fields track by $id($index)" ng-if="!field.deletePreserved">
|
||||
<div class="col-xs-10">
|
||||
<div class="panel panel-default" ng-click="openEditModal(field)">
|
||||
<div class="panel panel-default" ng-click="openEditModal(field, true, $index)">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<span class="col-xs-1" ng-switch="field.fieldType">
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
</div>
|
||||
</script>
|
||||
|
||||
<section data-ng-controller="ListFormsController as ctrl" data-ng-init="findAll()" class="container">
|
||||
<section class="container">
|
||||
<br>
|
||||
<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">
|
||||
|
@ -71,7 +71,8 @@
|
|||
|
||||
<div data-ng-repeat="form in myforms"
|
||||
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}">
|
||||
ng-class="{'paused': !form.isLive}"
|
||||
ng-clkc="goToWithId('viewForm.create', form._id)">
|
||||
|
||||
<div class="row">
|
||||
<span class="pull-right">
|
||||
|
|
|
@ -1,3 +1,23 @@
|
|||
/*Modal overlay (for lightbox effect)*/
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: rgb(0,0,0);
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
z-index: 10;
|
||||
}
|
||||
.overlay.submitform {
|
||||
background-color: rgb(256,256,256);
|
||||
background-color: rgba(256,256,256,0.8);
|
||||
}
|
||||
|
||||
form .btn {
|
||||
border-color: grey;
|
||||
}
|
||||
|
||||
.public-form.preview {
|
||||
border: none;
|
||||
box-shadow: 0px 0px 10px 0px grey;
|
||||
|
@ -11,37 +31,38 @@
|
|||
.public-form input, .public-form textarea {
|
||||
background-color: #000000;
|
||||
background-color: rgba(0,0,0,0);
|
||||
border: 2px dashed #ddd!important;
|
||||
border-width: 0px;
|
||||
}
|
||||
|
||||
.public-form input:focus, .public-form textarea:focus {
|
||||
border: 2px dashed #ddd!important;
|
||||
form .btn {
|
||||
border-color: grey;
|
||||
}
|
||||
|
||||
.public-form input.ng-untouched, .public-form textarea.ng-untouched {
|
||||
border-width: 0 0 2px 0;
|
||||
border-color: rgba(246, 255, 181, 0.4);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
/*.public-form input.no-border.ng-invalid, .public-form textarea.no-border {
|
||||
border-color: none;
|
||||
}*/
|
||||
.public-form input.ng-valid, .public-form textarea.ng-valid {
|
||||
/*border-color: #20FF20!important;
|
||||
border-style: solid!important;
|
||||
border-width: 3px!important;*/
|
||||
.public-form input:focus, .public-form textarea:focus {
|
||||
border-width: 0 0 2px 0;
|
||||
border-color: rgba(246, 255, 181, 0.4);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.public-form input.ng-invalid.ng-dirty, .public-form textarea.ng-invalid.ng-dirty {
|
||||
/*border-color: #FA787E!important;
|
||||
border-style: solid!important;
|
||||
border-width: 3px!important;*/
|
||||
.public-form input.ng-dirty, .public-form textarea.ng-dirty {
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
.public-form input.empty, .public-form textarea.empty {
|
||||
border-width: 0 0 2px 0;
|
||||
border-color: rgba(246, 255, 181, 0.4);
|
||||
}
|
||||
|
||||
section.content p.breakwords {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.public-form .btn {
|
||||
border: 1px solid #c6c6c6;
|
||||
}
|
||||
|
||||
.public-form .btn[type='submit'] {
|
||||
font-size: 1.5em;
|
||||
padding: 0.35em 1.2em 0.35em 1.2em;
|
||||
|
@ -142,7 +163,7 @@ form .row.field {
|
|||
form .row.field > .field-title {
|
||||
margin-top:0.5em;
|
||||
font-size:1.2em;
|
||||
padding-bottom: 1.8em;
|
||||
padding-bottom: 0.5em;
|
||||
width: inherit;
|
||||
}
|
||||
form .row.field > .field-input {
|
||||
|
@ -243,15 +264,12 @@ form .row.field {
|
|||
}
|
||||
|
||||
/* Styles for form list view (/forms) */
|
||||
section.public-form {
|
||||
padding: 0 10% 0 10%;
|
||||
}
|
||||
section.public-form .form-submitted {
|
||||
height: 100vh;
|
||||
section.public-form field-directive .btn.btn-lg.btn-default {
|
||||
background: none;
|
||||
}
|
||||
|
||||
section.public-form .btn {
|
||||
border: 1px solid;
|
||||
section.public-form field-directive .btn[disabled]{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
|
@ -330,42 +348,16 @@ section.public-form .btn {
|
|||
top:0;
|
||||
}
|
||||
|
||||
/*Modal overlay (for lightbox effect)*/
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: #000;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
z-index: 10;
|
||||
}
|
||||
.overlay.submitform {
|
||||
background-color: #fff;
|
||||
background-color: rgba(256,256,256,0.8);
|
||||
.field-directive {
|
||||
opacity: 0.2;
|
||||
padding: 2.5% 10% 2.5% 10%;
|
||||
}
|
||||
|
||||
.field-directive {
|
||||
z-index: 9;
|
||||
padding: 10% 10% 10% 0;
|
||||
border: 25px transparent solid;
|
||||
position: relative;
|
||||
.form-field-wrapper .form-actions.activeField, .field-directive.activeField {
|
||||
opacity: 1;
|
||||
}
|
||||
.activeField {
|
||||
z-index: 11;
|
||||
position: relative;
|
||||
background-color: transparent;
|
||||
}
|
||||
.activeField.field-directive {
|
||||
display: inline-block;
|
||||
border-radius: 7px;
|
||||
width: 100%;
|
||||
border: 25px transparent solid;
|
||||
}
|
||||
.activeField input {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
|
||||
h3.forms-list-title {
|
||||
color: #3FA2F7;
|
||||
font-weight: 600;
|
||||
|
|
|
@ -8,7 +8,21 @@ angular.module('forms').config(['$stateProvider',
|
|||
$stateProvider.
|
||||
state('listForms', {
|
||||
url: '/forms',
|
||||
templateUrl: 'modules/forms/admin/views/list-forms.client.view.html'
|
||||
templateUrl: 'modules/forms/admin/views/list-forms.client.view.html',
|
||||
resolve: {
|
||||
Forms: 'GetForms',
|
||||
myForms: function (GetForms, $q) {
|
||||
var deferred = $q.defer();
|
||||
|
||||
GetForms.query(function(forms){
|
||||
deferred.resolve(forms);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
},
|
||||
controller: 'ListFormsController',
|
||||
controllerAs: 'ctrl'
|
||||
}).state('submitForm', {
|
||||
url: '/forms/:formId',
|
||||
templateUrl: '/static/form_modules/forms/base/views/submit-form.client.view.html',
|
||||
|
|
|
@ -60,22 +60,13 @@
|
|||
var newFakeModal = function(){
|
||||
var result = {
|
||||
opened: true,
|
||||
result: {
|
||||
then: function(confirmCallback, cancelCallback) {
|
||||
//Store the callbacks for later when the user clicks on the OK or Cancel button of the dialog
|
||||
this.confirmCallBack = confirmCallback;
|
||||
this.cancelCallback = cancelCallback;
|
||||
}
|
||||
},
|
||||
close: function( item ) {
|
||||
//The user clicked OK on the modal dialog, call the stored confirm callback with the selected item
|
||||
this.opened = false;
|
||||
this.result.confirmCallBack( item );
|
||||
},
|
||||
dismiss: function( type ) {
|
||||
//The user clicked cancel on the modal dialog, call the stored cancel callback
|
||||
this.opened = false;
|
||||
this.result.cancelCallback( type );
|
||||
}
|
||||
};
|
||||
return result;
|
||||
|
@ -173,7 +164,6 @@
|
|||
beforeEach(inject(function($uibModal) {
|
||||
var modal = newFakeModal();
|
||||
spyOn($uibModal, 'open').and.returnValue(modal);
|
||||
//spyOn($uibModal, 'close').and.callFake(modal.close());
|
||||
}));
|
||||
|
||||
// The injector ignores leading and trailing underscores here (i.e. _$httpBackend_).
|
||||
|
|
|
@ -115,7 +115,3 @@ input.form-control {
|
|||
font-size: 18px;
|
||||
padding: 20px 10px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
border: none;
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
<br><br>
|
||||
|
||||
<p>
|
||||
<strong>{{ 'BEFORE_YOU_CONTINUE' | translate }}</strong> <a href="mail:polydaic@gmail.com">polydaic@gmail.com</a></p>
|
||||
<strong>{{ 'BEFORE_YOU_CONTINUE' | translate }}</strong> <a href="mail:team@tellform.com">team@tellform.com</a></p>
|
||||
<div class="text-center form-group">
|
||||
<button type="submit" class="btn btn-primary btn-rounded">
|
||||
<a href="/#!/" style="color: white; text-decoration: none;">{{ 'CONTINUE' | translate }}</a>
|
||||
|
|
|
@ -13,10 +13,10 @@
|
|||
<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 data-ng-show="error" class="text-center">
|
||||
<strong>Error: {{error}}</strong>
|
||||
</div>
|
||||
<div data-ng-show="success" class="text-center text-success">
|
||||
<div data-ng-show="success" class="text-center">
|
||||
<strong>{{success}}</strong>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
|
|
@ -5,17 +5,14 @@ var config = require('../config/config'),
|
|||
exports.run = function(app, db, cb) {
|
||||
|
||||
var User = mongoose.model('User');
|
||||
|
||||
var email = config.ADMIN_EMAIL || 'admin@admin.com';
|
||||
var username = config.ADMIN_USERNAME || 'root';
|
||||
var password = config.ADMIN_PASSWORD || 'root';
|
||||
|
||||
var email = 'admin@admin.com' || config.admin.email;
|
||||
|
||||
var newUser = new User({
|
||||
firstName: 'Admin',
|
||||
lastName: 'Account',
|
||||
email: email,
|
||||
username: username,
|
||||
password: password,
|
||||
username: 'root' || config.admin.username,
|
||||
password: 'root' || config.admin.password,
|
||||
provider: 'local',
|
||||
roles: ['admin', 'user']
|
||||
});
|
||||
|
|
|
@ -3,11 +3,13 @@
|
|||
* Module dependencies.
|
||||
*/
|
||||
|
||||
//Load ENV vars from .env
|
||||
if ((process.env.NODE_ENV || 'development') === 'development') {
|
||||
require('dotenv').config();
|
||||
require('dotenv').config({path: './.env'});
|
||||
|
||||
if(!process.env.NODE_ENV){
|
||||
process.env.NODE_ENV = 'development';
|
||||
}
|
||||
|
||||
|
||||
require('events').EventEmitter.prototype._maxListeners = 0;
|
||||
|
||||
var config = require('./config/config'),
|
||||
|
|
4
start.sh
Executable file
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
|
||||
node server.js
|
||||
#pm2 start process.yml
|
57
tellform_installation_blog_post.mdown
Normal file
|
@ -0,0 +1,57 @@
|
|||
Introduction
|
||||
===========
|
||||
|
||||
TellForm is a powerful, open-source form/survey tool that allows you to get data from your users quickly and easily while collecting powerful analytics to improve your surveys. TellForm offers a field by field analytics, custom subdomains per-user, an extendable API, logic-based field jumping, per-device visitor analytics and 11 types of form fields.
|
||||
|
||||
By default, TellForm is setup to serve custom subdomains, but you can also configure it to work on a single domain, for those using it in more restricted environments.
|
||||
|
||||
Exporters—both the official ones that the Prometheus team maintains as well as the community-contributed ones—provide information about everything from infrastructure, databases, and web servers to messaging systems, APIs, and more.
|
||||
|
||||
In this tutorial, you'll install, configure, and secure TellForm to selfhost forms that will make it easy for you and your users to use.
|
||||
|
||||
Prerequisites
|
||||
Before following this tutorial make sure you have:
|
||||
|
||||
One Ubuntu 16.x or 14.x Droplet, set up by following the [Initial Server Setup with Ubuntu 16.04 tutorial](https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-16-04), including a sudo non-root user and a firewall.
|
||||
Nginx installed by following the first two steps of the [How To Install Nginx on Ubuntu 16.04 tutorial](https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-16-04).
|
||||
Docker installed by following the steps of the [How To Install and Use Docker on Ubuntu 16.04 tutorial](https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-16-04)
|
||||
|
||||
|
||||
## Step 1 - Fetch Docker-Compose Files
|
||||
|
||||
```
|
||||
$ curl -L github.com/tellform/tellform/stable/blob/docker-compose.yml
|
||||
$ curl -L github.com/tellform/tellform/stable/blob/.env
|
||||
```
|
||||
|
||||
### Prepare .env file:
|
||||
Create `.env` file at project root folder. Fill in `MAILER_SERVICE_PROVIDER`, `MAILER_EMAIL_ID`, `MAILER_PASSWORD` and `MAILER_FROM`.
|
||||
```
|
||||
APP_NAME=TellForm
|
||||
BASE_URL=localhost:3000
|
||||
PORT=3000
|
||||
DB_PORT_27017_TCP_ADDR=tellform-mongo
|
||||
REDIS_DB_PORT_6379_TCP_ADDR=tellform-redis
|
||||
MAILER_SERVICE_PROVIDER=<TO-FILL-IN>
|
||||
MAILER_EMAIL_ID=<TO-FILL-IN>
|
||||
MAILER_PASSWORD=<TO-FILL-IN>
|
||||
MAILER_FROM=<TO-FILL-IN>
|
||||
SIGNUP_DISABLED=false
|
||||
SUBDOMAINS_DISABLED=true
|
||||
DISABLE_CLUSTER_MODE=true
|
||||
```
|
||||
|
||||
### Build docker image
|
||||
|
||||
```
|
||||
$ docker-compose build
|
||||
```
|
||||
|
||||
### Run docker containers with docker-compose
|
||||
|
||||
Create and start mongo & redis docker container:
|
||||
```
|
||||
$ docker-compose up -d
|
||||
```
|
||||
|
||||
Your application should run at the BASE_URL you specified on port 443.
|