Merge branch 'subdomains' into stage
This commit is contained in:
commit
77893b0c4e
@ -11,3 +11,10 @@ exports.index = function(req, res) {
|
||||
request: req
|
||||
});
|
||||
};
|
||||
|
||||
exports.form = function(req, res) {
|
||||
res.render('form', {
|
||||
user: req.user || null,
|
||||
request: req
|
||||
});
|
||||
};
|
||||
|
@ -285,7 +285,25 @@ exports.read = function(req, res) {
|
||||
var newForm = req.form.toJSON({virtuals : true});
|
||||
newForm.plugins.oscarhost.settings.validUpdateTypes = validUpdateTypes;
|
||||
|
||||
res.json(newForm);
|
||||
if(newForm){
|
||||
return res.json(newForm);
|
||||
}
|
||||
|
||||
if (req.userId) {
|
||||
if(req.form.admin._id+'' === req.userId+''){
|
||||
return res.json(newForm);
|
||||
}
|
||||
return res.status(404).send({
|
||||
message: 'Form Does Not Exist'
|
||||
});
|
||||
}else {
|
||||
if(newForm) return res.json(newForm);
|
||||
}
|
||||
|
||||
/*return res.status(404).send({
|
||||
message: 'Form Does Not Exist'
|
||||
});*/
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -102,14 +102,12 @@ exports.signup = function(req, res) {
|
||||
// For security measures we remove the roles from the req.body object
|
||||
if (req.body) {
|
||||
delete req.body.roles;
|
||||
console.log(req.body);
|
||||
|
||||
// Init Variables
|
||||
var user = new User(req.body);
|
||||
|
||||
// Add missing user fields
|
||||
user.provider = 'local';
|
||||
user.username = user.email;
|
||||
|
||||
// Then save the temporary user
|
||||
nev.createTempUser(user, function (err, newTempUser) {
|
||||
|
@ -24,7 +24,6 @@ exports.update = function(req, res) {
|
||||
// Merge existing user
|
||||
user = _.extend(user, req.body);
|
||||
user.updated = Date.now();
|
||||
// user.displayName = user.firstName + ' ' + user.lastName;
|
||||
|
||||
user.save(function(err) {
|
||||
if (err) {
|
||||
|
@ -54,12 +54,10 @@ var VisitorDataSchema = new Schema({
|
||||
type: Schema.Types.ObjectId
|
||||
},
|
||||
timeElapsed: {
|
||||
type: Number,
|
||||
required: true
|
||||
type: Number
|
||||
},
|
||||
isSubmitted: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
type: Boolean
|
||||
},
|
||||
language: {
|
||||
type: String
|
||||
|
@ -128,7 +128,6 @@ UserSchema.plugin(mUtilities.timestamp, {
|
||||
|
||||
//Create folder for user's pdfs
|
||||
UserSchema.pre('save', function (next) {
|
||||
this.username = this.email;
|
||||
if(process.env.NODE_ENV === 'local-development'){
|
||||
var newDestination = path.join(config.pdfUploadPath, this.username.replace(/ /g,'')),
|
||||
stat = null;
|
||||
|
@ -1,7 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var forms = require('../../app/controllers/forms.server.controller'),
|
||||
core = require('../../app/controllers/core.server.controller');
|
||||
|
||||
module.exports = function(app) {
|
||||
// Root routing
|
||||
var core = require('../../app/controllers/core.server.controller');
|
||||
app.route('/').get(core.index);
|
||||
app.route('/subdomain/([a-zA-Z0-9]+)/').get(core.form);
|
||||
app.route('/subdomain/*/forms/:formId([a-zA-Z0-9]+)')
|
||||
.get(forms.read)
|
||||
.post(forms.createSubmission);
|
||||
};
|
||||
|
133
app/views/form.server.view.html
Normal file
133
app/views/form.server.view.html
Normal file
@ -0,0 +1,133 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
||||
|
||||
<head>
|
||||
<title>{{title}}</title>
|
||||
|
||||
<!-- General META -->
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
|
||||
<!-- Semantic META -->
|
||||
<meta name="keywords" content="{{keywords}}">
|
||||
<meta name="description" content="{{description}}">
|
||||
|
||||
<!-- Facebook META -->
|
||||
<meta property="og:site_name" content="{{title}}">
|
||||
<meta property="og:title" content="{{title}}">
|
||||
<meta property="og:description" content="{{description}}">
|
||||
<meta property="og:url" content="{{url}}">
|
||||
<meta property="og:image" content="/img/brand/logo.png">
|
||||
<meta property="og:type" content="website">
|
||||
|
||||
<!-- Twitter META -->
|
||||
<meta name="twitter:title" content="{{title}}">
|
||||
<meta name="twitter:description" content="{{description}}">
|
||||
<meta name="twitter:url" content="{{url}}">
|
||||
<meta name="twitter:image" content="/img/brand/logo.png">
|
||||
|
||||
<!-- Fav Icon -->
|
||||
<link href="/static/modules/core/img/brand/favicon.ico" rel="shortcut icon" type="image/x-icon">
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.1/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/static/lib/bootstrap/dist/css/bootstrap.min.css">
|
||||
<!--Bower CSS dependencies-->
|
||||
{% for bowerCssFile in bowerCssFiles %}
|
||||
<link rel="stylesheet" href="{{bowerCssFile}}">
|
||||
{% endfor %}
|
||||
<link rel="stylesheet" href="/static/lib/angular-input-stars/angular-input-stars.css">
|
||||
<link rel="stylesheet" href="/static/lib/jquery-ui/themes/flick/jquery-ui.css"/>
|
||||
<link rel="stylesheet" href="/static/modules/core/css/github-fork-ribbon.css"/>
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<link rel="stylesheet" href="gh-fork-ribbon.ie.css">
|
||||
<![endif]-->
|
||||
<!-- end Bower CSS dependencies-->
|
||||
|
||||
<!--Application CSS Files-->
|
||||
{% for cssFile in cssFiles %}
|
||||
<link rel="stylesheet" href="{{cssFile}}">
|
||||
{% endfor %}
|
||||
<!-- end Application CSS Files-->
|
||||
|
||||
|
||||
<!-- HTML5 Shim -->
|
||||
<!--[if lt IE 9]>
|
||||
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
|
||||
<body ng-cloak>
|
||||
<div class="github-fork-ribbon-wrapper right-bottom hidden-xs">
|
||||
<div class="github-fork-ribbon">
|
||||
<a href="https://github.com/whitef0x0/tellform">Fork me on GitHub</a>
|
||||
</div>
|
||||
</div>
|
||||
<section class="content">
|
||||
<section ui-view></section>
|
||||
</section>
|
||||
|
||||
<script src="/static/lib/file-saver.js/FileSaver.js" type="text/javascript"></script>
|
||||
<!--Embedding The User Object-->
|
||||
<script type="text/javascript">
|
||||
var user = {{ user | json | safe }};
|
||||
</script>
|
||||
|
||||
<!--Embedding The signupDisabled Boolean-->
|
||||
<script type="text/javascript">
|
||||
var signupDisabled = {{signupDisabled | safe}};
|
||||
var socketPort = {{socketPort | safe}};
|
||||
</script>
|
||||
|
||||
<!--Socket.io Client Dependency-->
|
||||
<script src="https://cdn.socket.io/socket.io-1.4.5.js"></script>
|
||||
|
||||
<!--Bower JS dependencies-->
|
||||
{% for bowerJSFile in bowerJSFiles %}
|
||||
<script type="text/javascript" src="{{bowerJSFile}}"></script>
|
||||
{% endfor %}
|
||||
<!-- end Bower JS dependencies-->
|
||||
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/angular-strap/2.3.8/angular-strap.min.js"></script>
|
||||
|
||||
<!--Application JavaScript Files-->
|
||||
{% for jsFile in formJSFiles %}
|
||||
<script type="text/javascript" src="{{jsFile}}"></script>
|
||||
{% endfor %}
|
||||
<!-- end Application Javascript dependencies-->
|
||||
|
||||
{% if process.env.NODE_ENV === 'development' %}
|
||||
<!--Livereload script rendered -->
|
||||
<script type="text/javascript" src="http://{{request.hostname}}:35729/livereload.js"></script>
|
||||
{% endif %}
|
||||
|
||||
<script src="https://cdn.ravenjs.com/2.3.0/angular/raven.min.js"></script>
|
||||
|
||||
<script>
|
||||
Raven.config('https://825fefd6b4ed4a4da199c1b832ca845c@sentry.tellform.com/2').install();
|
||||
</script>
|
||||
|
||||
<!-- [if lt IE 9]>
|
||||
<section class="browsehappy jumbotron hide">
|
||||
<h1>Hello there!</h1>
|
||||
<p>You are using an old browser which we unfortunately do not support.</p>
|
||||
<p>Please <a href="http://browsehappy.com/">click here</a> to update your browser before using the website.</p>
|
||||
<p><a href="http://browsehappy.com" class="btn btn-primary btn-lg" role="button">Yes, upgrade my browser!</a></p>
|
||||
</section>
|
||||
<![endif] -->
|
||||
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', '{{google_analytics_id}}', 'auto');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
@ -31,16 +31,16 @@
|
||||
<meta name="twitter:image" content="/img/brand/logo.png">
|
||||
|
||||
<!-- Fav Icon -->
|
||||
<link href="/modules/core/img/brand/favicon.ico" rel="shortcut icon" type="image/x-icon">
|
||||
<link href="/static/modules/core/img/brand/favicon.ico" rel="shortcut icon" type="image/x-icon">
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.1/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="/lib/bootstrap/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="/static/lib/bootstrap/dist/css/bootstrap.min.css">
|
||||
<!--Bower CSS dependencies-->
|
||||
{% for bowerCssFile in bowerCssFiles %}
|
||||
<link rel="stylesheet" href="{{bowerCssFile}}">
|
||||
{% endfor %}
|
||||
<link rel="stylesheet" href="/lib/angular-input-stars/angular-input-stars.css">
|
||||
<link rel="stylesheet" href="/lib/jquery-ui/themes/flick/jquery-ui.css"/>
|
||||
<link rel="stylesheet" href="/modules/core/css/github-fork-ribbon.css"/>
|
||||
<link rel="stylesheet" href="/static/lib/angular-input-stars/angular-input-stars.css">
|
||||
<link rel="stylesheet" href="/static//lib/jquery-ui/themes/flick/jquery-ui.css"/>
|
||||
<link rel="stylesheet" href="/static/modules/core/css/github-fork-ribbon.css"/>
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<link rel="stylesheet" href="gh-fork-ribbon.ie.css">
|
||||
@ -66,12 +66,12 @@
|
||||
<a href="https://github.com/whitef0x0/tellform">Fork me on GitHub</a>
|
||||
</div>
|
||||
</div>
|
||||
<header data-ng-include="'/modules/core/views/header.client.view.html'"></header>
|
||||
<header data-ng-include="'/static/modules/core/views/header.client.view.html'"></header>
|
||||
<section class="content">
|
||||
{% block content %}{% endblock %}
|
||||
</section>
|
||||
|
||||
<script src="lib/file-saver.js/FileSaver.js" type="text/javascript"></script>
|
||||
<script src="/static/lib/file-saver.js/FileSaver.js" type="text/javascript"></script>
|
||||
<!--Embedding The User Object-->
|
||||
<script type="text/javascript">
|
||||
var user = {{ user | json | safe }};
|
||||
|
@ -39,11 +39,12 @@ if( fs.existsSync('./config/env/api_keys.js') ){
|
||||
/**
|
||||
* Get files by glob patterns
|
||||
*/
|
||||
module.exports.getGlobbedFiles = function(globPatterns, removeRoot) {
|
||||
module.exports.getGlobbedFiles = function(globPatterns, removeRoot, addRoot) {
|
||||
|
||||
var files = gruntFile.expand(globPatterns);
|
||||
if (removeRoot) {
|
||||
files = files.map(function(file) {
|
||||
if(addRoot) return file.replace(removeRoot, addRoot);
|
||||
return file.replace(removeRoot, '');
|
||||
});
|
||||
}
|
||||
@ -51,9 +52,10 @@ module.exports.getGlobbedFiles = function(globPatterns, removeRoot) {
|
||||
return files;
|
||||
};
|
||||
|
||||
module.exports.removeRootDir = function(files, root) {
|
||||
module.exports.removeRootDir = function(files, removeRoot, addRoot) {
|
||||
return files.map(function(file) {
|
||||
return file.replace(path.join(process.cwd(),root), '');
|
||||
if (addRoot) return file.replace(path.join(process.cwd(), removeRoot), addRoot);
|
||||
return file.replace(path.join(process.cwd(), removeRoot), '');
|
||||
});
|
||||
};
|
||||
|
||||
@ -61,24 +63,24 @@ module.exports.removeRootDir = function(files, root) {
|
||||
* Get the app's bower dependencies
|
||||
*/
|
||||
module.exports.getBowerJSAssets = function() {
|
||||
return this.removeRootDir(minBowerFiles('**/**.js'), 'public/');
|
||||
return this.removeRootDir(minBowerFiles('**/**.js'), 'public/', 'static/');
|
||||
};
|
||||
module.exports.getBowerCSSAssets = function() {
|
||||
return this.removeRootDir(minBowerFiles('**/**.css'), 'public/');
|
||||
return this.removeRootDir(minBowerFiles('**/**.css'), 'public/', 'static/');
|
||||
};
|
||||
module.exports.getBowerOtherAssets = function() {
|
||||
return this.removeRootDir(minBowerFiles('**/!(*.js|*.css|*.less)'), 'public/');
|
||||
return this.removeRootDir(minBowerFiles('**/!(*.js|*.css|*.less)'), 'public/', 'static/');
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the modules JavaScript files
|
||||
*/
|
||||
module.exports.getJavaScriptAssets = function(includeTests) {
|
||||
var output = this.getGlobbedFiles(this.assets.js, 'public/');
|
||||
var output = this.getGlobbedFiles(this.assets.js, 'public/', 'static/');
|
||||
|
||||
// To include tests
|
||||
if (includeTests) {
|
||||
output = _.union(output, this.getGlobbedFiles(this.assets.tests));
|
||||
output = _.union(output, this.getGlobbedFiles(this.assets.unit_tests));
|
||||
}
|
||||
|
||||
return output;
|
||||
@ -88,6 +90,20 @@ module.exports.getJavaScriptAssets = function(includeTests) {
|
||||
* Get the modules CSS files
|
||||
*/
|
||||
module.exports.getCSSAssets = function() {
|
||||
var output = this.getGlobbedFiles(this.assets.css, 'public/');
|
||||
var output = this.getGlobbedFiles(this.assets.css, 'public/', 'static/');
|
||||
return output;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the modules Form JavaScript files
|
||||
*/
|
||||
module.exports.getFormJavaScriptAssets = function(includeTests) {
|
||||
var output = this.getGlobbedFiles(this.assets.form_js, 'public/', 'static/');
|
||||
|
||||
// To include tests
|
||||
if (includeTests) {
|
||||
output = _.union(output, this.getGlobbedFiles(this.assets.form_unit_tests));
|
||||
}
|
||||
|
||||
return output;
|
||||
};
|
||||
|
24
config/env/all.js
vendored
24
config/env/all.js
vendored
@ -7,8 +7,8 @@ module.exports = {
|
||||
description: process.env.APP_DESC || 'Opensource form builder alternative to TypeForm',
|
||||
keywords: process.env.APP_KEYWORDS || 'typeform, pdfs, forms, opensource, formbuilder, google forms, nodejs'
|
||||
},
|
||||
port: process.env.PORT || 5000,
|
||||
socketPort: process.env.SOCKET_PORT || 35729,
|
||||
port: process.env.PORT || 3000,
|
||||
socketPort: process.env.SOCKET_PORT || 20523,
|
||||
|
||||
templateEngine: 'swig',
|
||||
|
||||
@ -90,6 +90,15 @@ module.exports = {
|
||||
'!public/modules/**/node_modules/**/*.js',
|
||||
'!public/modules/**/tests/**/*.js'
|
||||
],
|
||||
form_js: [
|
||||
'public/config.js',
|
||||
'public/application.js',
|
||||
'public/dist/populate_template_cache.js',
|
||||
'public/form_modules/forms/*.js',
|
||||
'public/form_modules/forms/*/*/*/*.js',
|
||||
'public/form_modules/forms/*/*.js',
|
||||
'public/form_modules/forms/*/*/*.js'
|
||||
],
|
||||
views: [
|
||||
'public/modules/**/*.html',
|
||||
'!public/modules/**/demo/**/*.html',
|
||||
@ -107,6 +116,17 @@ module.exports = {
|
||||
'public/modules/*/tests/e2e/**.js',
|
||||
'!public/modules/**/demo/**/*.js',
|
||||
'!public/modules/**/node_modules/**/*.js'
|
||||
],
|
||||
form_unit_tests: [
|
||||
'public/lib/angular-mocks/angular-mocks.js',
|
||||
'public/form_modules/*/tests/unit/**/*.js',
|
||||
'!public/form_modules/**/demo/**/*.js',
|
||||
'!public/form_modules/**/node_modules/**/*.js'
|
||||
],
|
||||
form_e2e_tests: [
|
||||
'public/form_modules/*/tests/e2e/**.js',
|
||||
'!public/form_modules/**/demo/**/*.js',
|
||||
'!public/form_modules/**/node_modules/**/*.js'
|
||||
]
|
||||
}
|
||||
};
|
||||
|
3
config/env/production.js
vendored
3
config/env/production.js
vendored
@ -24,7 +24,8 @@ module.exports = {
|
||||
},
|
||||
assets: {
|
||||
css: 'public/dist/application.min.css',
|
||||
js: 'public/dist/application.min.js'
|
||||
js: 'public/dist/application.min.js',
|
||||
form_js: 'public/dist/form-application.min.js'
|
||||
},
|
||||
facebook: {
|
||||
clientID: process.env.FACEBOOK_ID || 'APP_ID',
|
||||
|
@ -24,7 +24,10 @@ var fs = require('fs-extra'),
|
||||
consolidate = require('consolidate'),
|
||||
path = require('path'),
|
||||
device = require('express-device'),
|
||||
client = new raven.Client(config.DSN);
|
||||
client = new raven.Client(config.DSN),
|
||||
connect = require('connect');
|
||||
|
||||
var mongoose = require('mongoose');
|
||||
|
||||
/**
|
||||
* Configure Socket.io
|
||||
@ -40,6 +43,7 @@ var configureSocketIO = function (app, db) {
|
||||
module.exports = function(db) {
|
||||
// Initialize express app
|
||||
var app = express();
|
||||
var url = require('url');
|
||||
|
||||
// Globbing model files
|
||||
config.getGlobbedFiles('./app/models/**/*.js').forEach(function(modelPath) {
|
||||
@ -59,8 +63,68 @@ module.exports = function(db) {
|
||||
app.locals.bowerOtherFiles = config.getBowerOtherAssets();
|
||||
|
||||
app.locals.jsFiles = config.getJavaScriptAssets();
|
||||
app.locals.formJSFiles = config.getFormJavaScriptAssets();
|
||||
app.locals.cssFiles = config.getCSSAssets();
|
||||
|
||||
app.use(function (req, res, next) {
|
||||
var User = mongoose.model('User');
|
||||
var path = '/' + 'subdomain' + '/';
|
||||
var ignoreWWW = true;
|
||||
var ignoreWithStartPath = 'static';
|
||||
var subdomains = req.subdomains;
|
||||
var host = req.hostname;
|
||||
|
||||
console.log(subdomains);
|
||||
console.log(host);
|
||||
// remove www if chosen to ignore
|
||||
if (ignoreWWW) {
|
||||
var wwwi = subdomains.indexOf('www');
|
||||
if (wwwi >= 0) subdomains.splice(wwwi, 1);
|
||||
}
|
||||
|
||||
if(subdomains.slice(0, 4).join('.')+'' === '1.0.0.127'){
|
||||
subdomains = subdomains.slice(4);
|
||||
}
|
||||
|
||||
// continue if no subdomains
|
||||
if (!subdomains.length) return next();
|
||||
|
||||
if(ignoreWithStartPath !== ''){
|
||||
if(url.parse(req.url).path.split('/')[1] === ignoreWithStartPath) return next();
|
||||
}
|
||||
|
||||
User.findOne({username: req.subdomains.reverse()[0]}).exec(function (err, user) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
req.subdomains = null;
|
||||
// Error page
|
||||
return res.status(404).render('404', {
|
||||
error: 'Page Does Not Exist'
|
||||
});
|
||||
}
|
||||
if (user === null){
|
||||
// Error page
|
||||
return res.status(404).render('404', {
|
||||
error: 'Page Does Not Exist'
|
||||
});
|
||||
}
|
||||
|
||||
// rebuild url
|
||||
path += subdomains.join('/') + req.url;
|
||||
|
||||
// TODO: check path and query strings are preserved
|
||||
// reassign url
|
||||
req.url = path;
|
||||
|
||||
req.userId = user._id;
|
||||
|
||||
// Q.E.D.
|
||||
next();
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
//Setup Prerender.io
|
||||
app.use(require('prerender-node').set('prerenderToken', process.env.PRERENDER_TOKEN));
|
||||
|
||||
@ -120,7 +184,7 @@ module.exports = function(db) {
|
||||
|
||||
|
||||
// Setting the app router and static folder
|
||||
app.use('/', express.static(path.resolve('./public')));
|
||||
app.use('/static', express.static(path.resolve('./public')));
|
||||
app.use('/uploads', express.static(path.resolve('./uploads')));
|
||||
|
||||
// CookieParser should be above session
|
||||
@ -178,10 +242,10 @@ module.exports = function(db) {
|
||||
});
|
||||
|
||||
// Sentry (Raven) middleware
|
||||
// app.use(raven.middleware.express.requestHandler(config.DSN));
|
||||
app.use(raven.middleware.express.requestHandler(config.DSN));
|
||||
|
||||
// Should come before any other error middleware
|
||||
// app.use(raven.middleware.express.errorHandler(config.DSN));
|
||||
app.use(raven.middleware.express.errorHandler(config.DSN));
|
||||
|
||||
// Assume 'not found' in the error msgs is a 404. this is somewhat silly, but valid, you can do whatever you like, set properties, use instanceof etc.
|
||||
app.use(function(err, req, res, next) {
|
||||
@ -222,6 +286,7 @@ module.exports = function(db) {
|
||||
return httpsServer;
|
||||
}
|
||||
|
||||
|
||||
app = configureSocketIO(app, db);
|
||||
|
||||
// Return Express server instance
|
||||
|
BIN
design/screenshots/collapsed.png
Normal file
BIN
design/screenshots/collapsed.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 157 KiB |
13
gruntfile.js
13
gruntfile.js
@ -12,8 +12,8 @@ module.exports = function(grunt) {
|
||||
serverJS: ['gruntfile.js', 'server.js', 'config/**/*.js', 'app/**/*.js', '!app/tests/'],
|
||||
|
||||
clientViews: ['public/modules/**/views/**/*.html', '!public/modules/**/demo/**/*.html', '!public/modules/**/dist/**/*.html', '!public/modules/**/node_modules/**/*.html'],
|
||||
clientJS: ['public/js/*.js', 'public/modules/**/*.js', '!public/modules/**/gruntfile.js', '!public/modules/**/demo/**/*.js', '!public/modules/**/dist/**/*.js', '!public/modules/**/node_modules/**/*.js'],
|
||||
clientCSS: ['public/modules/**/*.css', '!public/modules/**/demo/**/*.css', '!public/modules/**/dist/**/*.css', '!public/modules/**/node_modules/**/*.css'],
|
||||
clientJS: ['public/js/*.js', 'public/form_modules/**/*.js', 'public/modules/**/*.js', '!public/modules/**/gruntfile.js', '!public/modules/**/demo/**/*.js', '!public/modules/**/dist/**/*.js', '!public/modules/**/node_modules/**/*.js'],
|
||||
clientCSS: ['public/modules/**/*.css', 'public/form_modules/**/*.css', '!public/modules/**/demo/**/*.css', '!public/modules/**/dist/**/*.css', '!public/modules/**/node_modules/**/*.css'],
|
||||
|
||||
serverTests: ['app/tests/**/*.js'],
|
||||
clientTests: ['public/modules/**/tests/*.js', '!public/modules/**/demo/**/*.js', '!public/modules/**/dist/**/*.js', '!public/modules/**/node_modules/**/*.js']
|
||||
@ -97,7 +97,8 @@ module.exports = function(grunt) {
|
||||
mangle: false
|
||||
},
|
||||
files: {
|
||||
'public/dist/application.min.js': 'public/dist/application.js'
|
||||
'public/dist/application.min.js': 'public/dist/application.js',
|
||||
'public/dist/form-application.min.js': 'public/dist/form-application.js'
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -134,7 +135,8 @@ module.exports = function(grunt) {
|
||||
ngAnnotate: {
|
||||
production: {
|
||||
files: {
|
||||
'public/dist/application.js': '<%= applicationJavaScriptFiles %>'
|
||||
'public/dist/application.js': '<%= applicationJavaScriptFiles %>',
|
||||
'public/dist/form-application.js': '<%= formApplicationJavaScriptFiles %>'
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -177,7 +179,7 @@ module.exports = function(grunt) {
|
||||
unit: {
|
||||
configFile: 'karma.conf.js',
|
||||
singleRun: true
|
||||
}
|
||||
}
|
||||
},
|
||||
protractor: {
|
||||
options: {
|
||||
@ -286,6 +288,7 @@ module.exports = function(grunt) {
|
||||
var config = require('./config/config');
|
||||
|
||||
grunt.config.set('applicationJavaScriptFiles', config.assets.js);
|
||||
grunt.config.set('formApplicationJavaScriptFiles', config.assets.form_js);
|
||||
grunt.config.set('applicationCSSFiles', config.assets.css);
|
||||
});
|
||||
|
||||
|
@ -87,7 +87,8 @@
|
||||
"soap": "^0.11.0",
|
||||
"socket.io": "^1.4.6",
|
||||
"socket.io-redis": "^1.0.0",
|
||||
"swig": "~1.4.1"
|
||||
"swig": "~1.4.1",
|
||||
"wildcard-subdomains": "github:whitef0x0/wildcard-subdomains"
|
||||
},
|
||||
"devDependencies": {
|
||||
"coveralls": "^2.11.4",
|
||||
|
@ -17,73 +17,17 @@ angular.module(ApplicationConfiguration.applicationModuleName).constant('APP_PER
|
||||
editForm: 'editForm',
|
||||
viewPrivateForm: 'viewPrivateForm'
|
||||
});
|
||||
|
||||
//User Role constants
|
||||
angular.module(ApplicationConfiguration.applicationModuleName).constant('USER_ROLES', {
|
||||
admin: 'admin',
|
||||
normal: 'user',
|
||||
superuser: 'superuser'
|
||||
});
|
||||
|
||||
//form url
|
||||
angular.module(ApplicationConfiguration.applicationModuleName).constant('FORM_URL', '/forms/:formId');
|
||||
|
||||
angular.module(ApplicationConfiguration.applicationModuleName).run(['$rootScope', 'Auth', '$state', '$stateParams',
|
||||
function($rootScope, Auth, $state, $stateParams) {
|
||||
|
||||
$rootScope.$state = $state;
|
||||
$rootScope.$stateParams = $stateParams;
|
||||
|
||||
// add previous state property
|
||||
$rootScope.$on('$stateChangeSuccess', function (event, toState, toParams, fromState) {
|
||||
$state.previous = fromState;
|
||||
//console.log('toState: '+toState.name);
|
||||
|
||||
var statesToIgnore = ['home', 'signin', 'resendVerifyEmail', 'verify', 'signup', 'signup-success', 'forgot', 'reset-invalid', 'reset', 'reset-success'];
|
||||
|
||||
//Redirect to listForms if user is authenticated
|
||||
if(statesToIgnore.indexOf(toState.name) > 0){
|
||||
if(Auth.isAuthenticated()){
|
||||
event.preventDefault(); // stop current execution
|
||||
//console.log('go to forms');
|
||||
$state.go('listForms'); // go to listForms page
|
||||
}
|
||||
}
|
||||
//Redirect to 'signup' route if user is not authenticated
|
||||
else if(toState.name !== 'access_denied' && !Auth.isAuthenticated() && toState.name !== 'submitForm'){
|
||||
console.log('go to signup');
|
||||
event.preventDefault(); // stop current execution
|
||||
$state.go('listForms'); // go to listForms page
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
]);
|
||||
|
||||
//Page access/authorization logic
|
||||
angular.module(ApplicationConfiguration.applicationModuleName).run(['$rootScope', 'Auth', 'User', 'Authorizer', '$state', '$stateParams',
|
||||
function($rootScope, Auth, User, Authorizer, $state, $stateParams) {
|
||||
$rootScope.$on('$stateChangeStart', function(event, next) {
|
||||
var authenticator, permissions, user;
|
||||
permissions = next && next.data && next.data.permissions ? next.data.permissions : null;
|
||||
|
||||
Auth.ensureHasCurrentUser(User);
|
||||
user = Auth.currentUser;
|
||||
|
||||
if(user){
|
||||
authenticator = new Authorizer(user);
|
||||
//console.log('access denied: '+!authenticator.canAccess(permissions));
|
||||
//console.log(permissions);
|
||||
if( (permissions != null) ){
|
||||
if( !authenticator.canAccess(permissions) ){
|
||||
event.preventDefault();
|
||||
//console.log('access denied');
|
||||
$state.go('access_denied');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}]);
|
||||
|
||||
//Then define the init function for starting up the application
|
||||
angular.element(document).ready(function() {
|
||||
//Fixing facebook bug with redirect
|
||||
|
608
public/dist/application.js
vendored
608
public/dist/application.js
vendored
File diff suppressed because one or more lines are too long
10
public/dist/application.min.js
vendored
10
public/dist/application.min.js
vendored
File diff suppressed because one or more lines are too long
1219
public/dist/form-application.js
vendored
Normal file
1219
public/dist/form-application.js
vendored
Normal file
File diff suppressed because one or more lines are too long
3
public/dist/form-application.min.js
vendored
Normal file
3
public/dist/form-application.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
45
public/form_modules/forms/base/config/forms.client.config.js
Normal file
45
public/form_modules/forms/base/config/forms.client.config.js
Normal file
@ -0,0 +1,45 @@
|
||||
'use strict';
|
||||
|
||||
// Configuring the Forms drop-down menus
|
||||
angular.module('view-form')
|
||||
.filter('formValidity', function(){
|
||||
return function(formObj){
|
||||
if(formObj && formObj.form_fields && formObj.visible_form_fields){
|
||||
|
||||
//get keys
|
||||
var formKeys = Object.keys(formObj);
|
||||
|
||||
//we only care about things that don't start with $
|
||||
var fieldKeys = formKeys.filter(function(key){
|
||||
return key[0] !== '$';
|
||||
});
|
||||
|
||||
var fields = formObj.form_fields;
|
||||
|
||||
var valid_count = fields.filter(function(field){
|
||||
if(typeof field === 'object' && field.fieldType !== 'statement' && field.fieldType !== 'rating'){
|
||||
return !!(field.fieldValue);
|
||||
}
|
||||
|
||||
}).length;
|
||||
return valid_count - (formObj.form_fields.length - formObj.visible_form_fields.length);
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
});
|
||||
|
||||
angular.module('view-form').value('supportedFields', [
|
||||
'textfield',
|
||||
'textarea',
|
||||
'date',
|
||||
'dropdown',
|
||||
'hidden',
|
||||
'password',
|
||||
'radio',
|
||||
'legal',
|
||||
'statement',
|
||||
'rating',
|
||||
'yes_no',
|
||||
'number',
|
||||
'natural'
|
||||
]);
|
37
public/form_modules/forms/base/config/i18n/english.js
Normal file
37
public/form_modules/forms/base/config/i18n/english.js
Normal file
@ -0,0 +1,37 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('view-form').config(['$translateProvider', function ($translateProvider) {
|
||||
|
||||
$translateProvider.translations('english', {
|
||||
FORM_SUCCESS: 'Form entry successfully submitted!',
|
||||
REVIEW: 'Review',
|
||||
BACK_TO_FORM: 'Go back to Form',
|
||||
EDIT_FORM: 'Edit this TellForm',
|
||||
CREATE_FORM: 'Create this TellForm',
|
||||
ADVANCEMENT: '{{done}} out of {{total}} answered',
|
||||
CONTINUE_FORM: 'Continue to Form',
|
||||
REQUIRED: 'required',
|
||||
COMPLETING_NEEDED: '{{answers_not_completed}} answer(s) need completing',
|
||||
OPTIONAL: 'optional',
|
||||
ERROR_EMAIL_INVALID: 'Please enter a valid email address',
|
||||
ERROR_NOT_A_NUMBER: 'Please enter valid numbers only',
|
||||
ERROR_URL_INVALID: 'Please a valid url',
|
||||
OK: 'OK',
|
||||
ENTER: 'press ENTER',
|
||||
YES: 'Yes',
|
||||
NO: 'No',
|
||||
NEWLINE: 'press SHIFT+ENTER to create a newline',
|
||||
CONTINUE: 'Continue',
|
||||
LEGAL_ACCEPT: 'I accept',
|
||||
LEGAL_NO_ACCEPT: 'I don’t accept',
|
||||
DELETE: 'Delete',
|
||||
CANCEL: 'Cancel',
|
||||
SUBMIT: 'Submit',
|
||||
UPLOAD_FILE: 'Upload your File',
|
||||
});
|
||||
|
||||
$translateProvider.preferredLanguage('english')
|
||||
.fallbackLanguage('english')
|
||||
.useSanitizeValueStrategy('escape');
|
||||
|
||||
}]);
|
35
public/form_modules/forms/base/config/i18n/french.js
Normal file
35
public/form_modules/forms/base/config/i18n/french.js
Normal file
@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('view-form').config(['$translateProvider', function ($translateProvider) {
|
||||
|
||||
$translateProvider.translations('french', {
|
||||
FORM_SUCCESS: 'Votre formulaire a été enregistré!',
|
||||
REVIEW: 'Incomplet',
|
||||
BACK_TO_FORM: 'Retourner au formulaire',
|
||||
EDIT_FORM: 'Éditer le Tellform',
|
||||
CREATE_FORM: 'Créer un TellForm',
|
||||
ADVANCEMENT: '{{done}} complétés sur {{total}}',
|
||||
CONTINUE_FORM: 'Aller au formulaire',
|
||||
REQUIRED: 'obligatoire',
|
||||
COMPLETING_NEEDED: '{{answers_not_completed}} réponse(s) doive(nt) être complétée(s)',
|
||||
OPTIONAL: 'facultatif',
|
||||
ERROR_EMAIL_INVALID: 'Merci de rentrer une adresse mail valide',
|
||||
ERROR_NOT_A_NUMBER: 'Merce de ne rentrer que des nombres',
|
||||
ERROR_URL_INVALID: 'Merci de rentrer une url valide',
|
||||
OK: 'OK',
|
||||
ENTER: 'presser ENTRÉE',
|
||||
YES: 'Oui',
|
||||
NO: 'Non',
|
||||
NEWLINE: 'presser SHIFT+ENTER pour créer une nouvelle ligne',
|
||||
CONTINUE: 'Continuer',
|
||||
LEGAL_ACCEPT: 'J’accepte',
|
||||
LEGAL_NO_ACCEPT: 'Je n’accepte pas',
|
||||
DELETE: 'Supprimer',
|
||||
CANCEL: 'Réinitialiser',
|
||||
SUBMIT: 'Enregistrer',
|
||||
UPLOAD_FILE: 'Envoyer un fichier',
|
||||
Y: 'O',
|
||||
N: 'N',
|
||||
});
|
||||
|
||||
}]);
|
35
public/form_modules/forms/base/config/i18n/german.js
Normal file
35
public/form_modules/forms/base/config/i18n/german.js
Normal file
@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('view-form').config(['$translateProvider', function ($translateProvider) {
|
||||
|
||||
$translateProvider.translations('german', {
|
||||
FORM_SUCCESS: 'Ihre Angaben wurden gespeichert.',
|
||||
REVIEW: 'Unvollständig',
|
||||
BACK_TO_FORM: 'Zurück zum Formular',
|
||||
EDIT_FORM: '',
|
||||
CREATE_FORM: '',
|
||||
ADVANCEMENT: '{{done}} von {{total}} beantwortet',
|
||||
CONTINUE_FORM: 'Zum Formular',
|
||||
REQUIRED: 'verpflichtend',
|
||||
COMPLETING_NEEDED: 'Es fehlen/fehtl noch {{answers_not_completed}} Antwort(en)',
|
||||
OPTIONAL: 'fakultativ',
|
||||
ERROR_EMAIL_INVALID: 'Bitte gültige Mailadresse eingeben',
|
||||
ERROR_NOT_A_NUMBER: 'Bitte nur Zahlen eingeben',
|
||||
ERROR_URL_INVALID: 'Bitte eine gültige URL eingeben',
|
||||
OK: 'Okay',
|
||||
ENTER: 'Eingabetaste drücken',
|
||||
YES: 'Ja',
|
||||
NO: 'Nein',
|
||||
NEWLINE: 'Für eine neue Zeile SHIFT+ENTER drücken',
|
||||
CONTINUE: 'Weiter',
|
||||
LEGAL_ACCEPT: 'I accept',
|
||||
LEGAL_NO_ACCEPT: 'I don’t accept',
|
||||
DELETE: 'Entfernen',
|
||||
CANCEL: 'Canceln',
|
||||
SUBMIT: 'Speichern',
|
||||
UPLOAD_FILE: 'Datei versenden',
|
||||
Y: 'J',
|
||||
N: 'N',
|
||||
});
|
||||
|
||||
}]);
|
35
public/form_modules/forms/base/config/i18n/italian.js
Normal file
35
public/form_modules/forms/base/config/i18n/italian.js
Normal file
@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('view-form').config(['$translateProvider', function ($translateProvider) {
|
||||
|
||||
$translateProvider.translations('italian', {
|
||||
FORM_SUCCESS: 'Il formulario è stato inviato con successo!',
|
||||
REVIEW: 'Incompleto',
|
||||
BACK_TO_FORM: 'Ritorna al formulario',
|
||||
EDIT_FORM: '',
|
||||
CREATE_FORM: '',
|
||||
ADVANCEMENT: '{{done}} su {{total}} completate',
|
||||
CONTINUE_FORM: 'Vai al formulario',
|
||||
REQUIRED: 'obbligatorio',
|
||||
COMPLETING_NEEDED: '{{answers_not_completed}} risposta/e deve/ono essere completata/e',
|
||||
OPTIONAL: 'opzionale',
|
||||
ERROR_EMAIL_INVALID: 'Si prega di inserire un indirizzo email valido',
|
||||
ERROR_NOT_A_NUMBER: 'Si prega di inserire solo numeri',
|
||||
ERROR_URL_INVALID: 'Grazie per inserire un URL valido',
|
||||
OK: 'OK',
|
||||
ENTER: 'premere INVIO',
|
||||
YES: 'Sì',
|
||||
NO: 'No',
|
||||
NEWLINE: 'premere SHIFT+INVIO per creare una nuova linea',
|
||||
CONTINUE: 'Continua',
|
||||
LEGAL_ACCEPT: 'I accept',
|
||||
LEGAL_NO_ACCEPT: 'I don’t accept',
|
||||
DELETE: 'Cancella',
|
||||
CANCEL: 'Reset',
|
||||
SUBMIT: 'Registra',
|
||||
UPLOAD_FILE: 'Invia un file',
|
||||
Y: 'S',
|
||||
N: 'N',
|
||||
});
|
||||
|
||||
}]);
|
35
public/form_modules/forms/base/config/i18n/spanish.js
Normal file
35
public/form_modules/forms/base/config/i18n/spanish.js
Normal file
@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('view-form').config(['$translateProvider', function ($translateProvider) {
|
||||
|
||||
$translateProvider.translations('spanish', {
|
||||
FORM_SUCCESS: '¡El formulario ha sido enviado con éxito!',
|
||||
REVIEW: 'Revisar',
|
||||
BACK_TO_FORM: 'Regresar al formulario',
|
||||
EDIT_FORM: '',
|
||||
CREATE_FORM: '',
|
||||
ADVANCEMENT: '{{done}} de {{total}} contestadas',
|
||||
CONTINUE_FORM: 'Continuar al formulario',
|
||||
REQUIRED: 'Información requerida',
|
||||
COMPLETING_NEEDED: '{{answers_not_completed}} respuesta(s) necesita(n) ser completada(s)',
|
||||
OPTIONAL: 'Opcional',
|
||||
ERROR_EMAIL_INVALID: 'Favor de proporcionar un correo electrónico válido',
|
||||
ERROR_NOT_A_NUMBER: 'Por favor, introduzca sólo números válidos',
|
||||
ERROR_URL_INVALID: 'Favor de proporcionar un url válido',
|
||||
OK: 'OK',
|
||||
ENTER: 'pulse INTRO',
|
||||
YES: 'Si',
|
||||
NO: 'No',
|
||||
NEWLINE: 'presione SHIFT+INTRO para crear una nueva línea',
|
||||
CONTINUE: 'Continuar',
|
||||
LEGAL_ACCEPT: 'I accept',
|
||||
LEGAL_NO_ACCEPT: 'I don’t accept',
|
||||
DELETE: 'Eliminar',
|
||||
CANCEL: 'Cancelar',
|
||||
SUBMIT: 'Registrar',
|
||||
UPLOAD_FILE: 'Cargar el archivo',
|
||||
Y: 'S',
|
||||
N: 'N'
|
||||
});
|
||||
|
||||
}]);
|
@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
// SubmitForm controller
|
||||
angular.module('view-form').controller('SubmitFormController', [
|
||||
'$scope', '$rootScope', '$state', '$translate', 'myForm',
|
||||
function($scope, $rootScope, $state, $translate, myForm) {
|
||||
$scope.myform = myForm;
|
||||
|
||||
$translate.use(myForm.language);
|
||||
|
||||
}
|
||||
]);
|
557
public/form_modules/forms/base/css/form.css
Normal file
557
public/form_modules/forms/base/css/form.css
Normal file
@ -0,0 +1,557 @@
|
||||
.panel-default.startPage {
|
||||
border-style: dashed;
|
||||
border-color: #a9a9a9;
|
||||
border-width:3px;
|
||||
}
|
||||
|
||||
.busy-updating-wrapper {
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 55px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.busy-submitting-wrapper {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.dropzone h4.panel-title {
|
||||
height: 17px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.container.admin-form {
|
||||
margin-top: 70px;
|
||||
}
|
||||
|
||||
.public-form input, .public-form textarea {
|
||||
background-color: #000000;
|
||||
background-color: rgba(0,0,0,0);
|
||||
border: 2px dashed #ddd!important;
|
||||
}
|
||||
|
||||
.public-form input:focus, .public-form textarea:focus {
|
||||
border: 2px dashed #ddd!important;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/*.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.ng-invalid.ng-dirty, .public-form textarea.ng-invalid.ng-dirty {
|
||||
border-color: #FA787E!important;
|
||||
border-style: solid!important;
|
||||
border-width: 3px!important;
|
||||
}
|
||||
|
||||
section.content p.breakwords {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.btn {
|
||||
border: 1px solid #c6c6c6;
|
||||
}
|
||||
|
||||
.btn[type='submit'] {
|
||||
font-size: 1.5em;
|
||||
padding: 0.35em 1.2em 0.35em 1.2em;
|
||||
}
|
||||
|
||||
section.content > section > section.container {
|
||||
margin-top: 70px;
|
||||
}
|
||||
|
||||
/*
|
||||
** Modal CSS Styles
|
||||
*/
|
||||
.modal-header {
|
||||
padding: 15px;
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
font-size: 18px;
|
||||
font-weight: normal;
|
||||
}
|
||||
.input-block {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
.modal-footer input[type='text'] {
|
||||
min-height: 34px;
|
||||
padding: 7px 8px;
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
vertical-align: middle;
|
||||
background-color: #fff;
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 8px center;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 3px;
|
||||
box-shadow: inset 0 1px 2px rgba(0,0,0,0.075);
|
||||
}
|
||||
.modal-footer input[type='text']:focus {
|
||||
outline: 0;
|
||||
}
|
||||
.modal-body > .modal-body-alert {
|
||||
color: #796620;
|
||||
background-color: #f8eec7;
|
||||
border-color: #f2e09a;
|
||||
margin: -16px -15px 15px;
|
||||
padding: 10px 15px;
|
||||
border-style: solid;
|
||||
border-width: 1px 0;
|
||||
}
|
||||
|
||||
div.form-fields {
|
||||
position: relative;
|
||||
padding-top: 35vh;
|
||||
}
|
||||
.letter {
|
||||
position: relative;
|
||||
display: -moz-inline-stack;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
zoom: 1;
|
||||
width: 16px;
|
||||
padding: 0;
|
||||
height: 17px;
|
||||
font-size: 12px;
|
||||
line-height: 19px;
|
||||
border: 1px solid #000;
|
||||
border: 1px solid rgba(0,0,0,.2);
|
||||
margin-right: 7px;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
div.form-submitted > .field.row {
|
||||
padding-bottom: 2%;
|
||||
margin-top: 35vh;
|
||||
}
|
||||
div.form-submitted > .field.row > div {
|
||||
font-size: 1.7em;
|
||||
}
|
||||
|
||||
/* Styles for accordion */
|
||||
form .accordion-edit {
|
||||
width: inherit;
|
||||
}
|
||||
|
||||
/*Styles for ui-datepicker*/
|
||||
.ui-datepicker.ui-widget {
|
||||
z-index: 99!important;
|
||||
}
|
||||
|
||||
form .row.field .field-number {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
/* Styles for form submission view (/forms/:formID) */
|
||||
form .row.field {
|
||||
padding: 1em 0 0 0;
|
||||
width: inherit;
|
||||
}
|
||||
form .row.field > .field-title {
|
||||
margin-top:0.5em;
|
||||
font-size:1.2em;
|
||||
padding-bottom: 1.8em;
|
||||
width: inherit;
|
||||
}
|
||||
form .row.field > .field-input {
|
||||
font-size: 1.4em;
|
||||
color: #777;
|
||||
}
|
||||
form.submission-form .row.field.statement > .field-title {
|
||||
font-size:1.7em;
|
||||
}
|
||||
form.submission-form .row.field.statement > .field-input {
|
||||
font-size:1em;
|
||||
color:#ddd;
|
||||
}
|
||||
|
||||
form.submission-form .select.radio > .field-input input, form.submission-form .select > .field-input input {
|
||||
width:20%;
|
||||
}
|
||||
|
||||
form.submission-form .field.row.radio .btn.activeBtn {
|
||||
background-color: rgb(0,0,0)!important;
|
||||
background-color: rgba(0,0,0,0.7)!important;
|
||||
color: white;
|
||||
}
|
||||
form.submission-form .field.row.radio .btn {
|
||||
margin-right:1.2em;
|
||||
}
|
||||
|
||||
form.submission-form .select > .field-input .btn {
|
||||
text-align: left;
|
||||
margin-bottom:0.7em;
|
||||
}
|
||||
form.submission-form .select > .field-input .btn > span {
|
||||
font-size: 1.10em;
|
||||
}
|
||||
|
||||
/*form.submission-form .field-input > input:focus {
|
||||
font-size:1em;
|
||||
}*/
|
||||
|
||||
form .field-input > textarea{
|
||||
padding: 0.45em 0.9em;
|
||||
width: 100%;
|
||||
line-height: 160%;
|
||||
}
|
||||
|
||||
form .field-input > input.hasDatepicker{
|
||||
padding: 0.45em 0.9em;
|
||||
width: 50%;
|
||||
line-height: 160%;
|
||||
}
|
||||
form .field-input > input.text-field-input{
|
||||
padding: 0.45em 0.9em;
|
||||
width: 100%;
|
||||
line-height: 160%;
|
||||
}
|
||||
form .required-error{
|
||||
color: #ddd;
|
||||
font-size:0.8em;
|
||||
}
|
||||
|
||||
form .row.field.dropdown > .field-input input {
|
||||
height: 34px;
|
||||
border-width: 0 0 2px 0;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
form .row.field.dropdown > .field-input input:focus {
|
||||
border: none;
|
||||
}
|
||||
|
||||
form .dropdown > .field-input .ui-select-choices-row-inner {
|
||||
border-radius: 3px;
|
||||
margin: 5px;
|
||||
padding: 10px;
|
||||
background-color: #000000;
|
||||
background-color: rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
form .dropdown > .field-input .ui-select-choices-row-inner.active, form .dropdown > .field-input .ui-select-choices-row-inner.active:focus {
|
||||
background-color: #000000;
|
||||
background-color: rgba(0,0,0,0.1);
|
||||
}
|
||||
.config-form {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.config-form > .row {
|
||||
padding: 19px;
|
||||
margin-bottom: 20px;
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #e3e3e3;
|
||||
border-radius: 4px;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
|
||||
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
|
||||
}
|
||||
|
||||
div.config-form .row.field {
|
||||
padding-top:1.5em;
|
||||
}
|
||||
|
||||
div.config-form > .row > .container:nth-of-type(odd){
|
||||
border-right: 1px #ddd solid;
|
||||
}
|
||||
div.config-form.design > .row > .container:nth-of-type(odd){
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
div.config-form .row > .field-input {
|
||||
padding-left:0.1em;
|
||||
}
|
||||
div.config-form .row > .field-input label {
|
||||
padding-left:1.3em;
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
/* Styles for form admin view (/forms/:formID/admin) */
|
||||
.admin-form > .page-header {
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
.admin-form > .page-header h1 {
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
.admin-form > .page-header > .col-xs-3 {
|
||||
padding-top: 1.4em;
|
||||
}
|
||||
.admin-form .form-controls .row {
|
||||
padding: 5px;
|
||||
}
|
||||
.admin-form .page-header {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/*Styles for admin view tabs */
|
||||
.admin-form .tab-content {
|
||||
padding-top: 3em;
|
||||
}
|
||||
|
||||
.admin-form .panel-heading {
|
||||
background-color: #f1f1f1;
|
||||
position: relative!important;
|
||||
}
|
||||
.admin-form .panel-heading:hover {
|
||||
background-color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
.admin-form .panel-heading a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.current-fields .panel-body .row.question input[type='text'], .current-fields .panel-body .row.description textarea{
|
||||
width: 100%;
|
||||
}
|
||||
.current-fields .panel-body .row.options input[type='text'] {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
/*Override Select2 UI*/
|
||||
.ui-select-choices.ui-select-dropdown {
|
||||
top:2.5em!important;
|
||||
}
|
||||
.ui-select-toggle {
|
||||
box-shadow:none!important;
|
||||
border:none!important;
|
||||
}
|
||||
|
||||
.current-fields .tool-panel > .panel-default:hover {
|
||||
border-color: #9d9d9d;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.current-fields .tool-panel > .panel-default .panel-heading {
|
||||
background-color: #fff;
|
||||
color: #9d9d9d!important;
|
||||
}
|
||||
.current-fields .tool-panel > .panel-default .panel-heading:hover {
|
||||
background-color: #eee;
|
||||
color: #000!important;
|
||||
cursor: pointer;
|
||||
}
|
||||
.current-fields .tool-panel > .panel-default .panel-heading a {
|
||||
color: inherit;
|
||||
}
|
||||
.current-fields .tool-panel > .panel-default .panel-heading a:hover{
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/*Styles for submission table*/
|
||||
.submissions-table .table-outer.row {
|
||||
margin: 1.5em 0 2em 0!important;
|
||||
}
|
||||
.submissions-table .table-outer .col-xs-12 {
|
||||
padding-left: 0!important;
|
||||
border:1px solid #ddd;
|
||||
overflow-x: scroll;
|
||||
border-radius:3px;
|
||||
}
|
||||
.submissions-table .table > thead > tr > th {
|
||||
min-width:8em;
|
||||
}
|
||||
.submissions-table .table > tbody > tr.selected {
|
||||
background-color:#efefef;
|
||||
}
|
||||
|
||||
|
||||
/*Styles for add fields tab*/
|
||||
.admin-form .add-field {
|
||||
background-color: #ddd;
|
||||
padding: 0 2% 0 2%;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.admin-form .add-field .col-xs-6 {
|
||||
padding: 0.25em 0.4em;
|
||||
}
|
||||
.admin-form .add-field .col-xs-6 .panel-heading {
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: #bbb;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.admin-form .oscar-field-select {
|
||||
margin: 10px 0 10px;
|
||||
}
|
||||
|
||||
.view-form-btn.span {
|
||||
padding-right:0.6em;
|
||||
}
|
||||
.status-light.status-light-off {
|
||||
color: #BE0000;
|
||||
}
|
||||
.status-light.status-light-on {
|
||||
color: #33CC00;
|
||||
}
|
||||
|
||||
/* Styles for form list view (/forms) */
|
||||
section.public-form {
|
||||
padding: 0 10% 0 10%;
|
||||
}
|
||||
section.public-form .form-submitted {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
section.public-form .btn {
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
text-align: center;
|
||||
border-bottom: 6px inset #ccc;
|
||||
background-color: #eee;
|
||||
width: 180px;
|
||||
/*width:100%;*/
|
||||
position: relative;
|
||||
height: 215px;
|
||||
/*padding-bottom: 25%;*/
|
||||
margin-bottom: 45px;
|
||||
}
|
||||
.form-item.create-new input[type='text']{
|
||||
width: inherit;
|
||||
color:black;
|
||||
border:none;
|
||||
}
|
||||
|
||||
.form-item.create-new {
|
||||
background-color: rgb(131,131,131);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/*CREATE-NEW FORM MODAL*/
|
||||
.form-item.create-new.new-form {
|
||||
background-color: rgb(300,131,131);
|
||||
z-index: 11;
|
||||
}
|
||||
.form-item.create-new.new-form:hover {
|
||||
background-color: rgb(300,100,100);
|
||||
}
|
||||
.form-item.new-form input[type='text'] {
|
||||
margin-top:0.2em;
|
||||
width: inherit;
|
||||
color:black;
|
||||
border:none;
|
||||
padding: 0.3em 0.6em 0.3em 0.6em;
|
||||
}
|
||||
.form-item.new-form .custom-select {
|
||||
margin-top: 0.2em
|
||||
}
|
||||
.form-item.new-form .custom-select select {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
|
||||
.form-item.new-form .details-row {
|
||||
margin-top: 1em;
|
||||
}
|
||||
.form-item.new-form .details-row.submit {
|
||||
margin-top: 1.7em;
|
||||
}
|
||||
.form-item.new-form .details-row.submit .btn {
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
.form-item.new-form .title-row {
|
||||
margin-top: 1em;
|
||||
top:0;
|
||||
}
|
||||
|
||||
/*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);
|
||||
}
|
||||
.field-directive {
|
||||
z-index: 9;
|
||||
padding: 10% 10% 10% 0;
|
||||
border: 25px transparent solid;
|
||||
position: relative;
|
||||
}
|
||||
.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;
|
||||
}
|
||||
|
||||
.form-item:hover, .form-item.create-new:hover {
|
||||
border-bottom: 8px inset #ccc;
|
||||
background-color: #d9d9d9;
|
||||
}
|
||||
|
||||
.form-item.create-new:hover {
|
||||
background-color: rgb(81,81,81);
|
||||
}
|
||||
|
||||
.form-item > .row.footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 30%;
|
||||
}
|
||||
|
||||
.form-item .title-row{
|
||||
position: relative;
|
||||
top: 15px;
|
||||
padding-top:3em;
|
||||
padding-bottom:3.65em;
|
||||
}
|
||||
.form-item .title-row h4 {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.form-item.create-new .title-row{
|
||||
padding: 0;
|
||||
}
|
||||
.form-item.create-new .title-row h4 {
|
||||
font-size: 7em;
|
||||
}
|
||||
|
||||
.form-item .details-row{
|
||||
margin-top: 3.2em;
|
||||
}
|
||||
.form-item .details-row small {
|
||||
font-size: 0.6em;
|
||||
}
|
||||
.form-item.create-new .details-row small {
|
||||
font-size: 0.95em;
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('view-form').directive('fieldIconDirective', function() {
|
||||
|
||||
return {
|
||||
template: '<i class="{{typeIcon}}"></i>',
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
typeName: '@'
|
||||
},
|
||||
controller: function($scope){
|
||||
var iconTypeMap = {
|
||||
'textfield': 'fa fa-pencil-square-o',
|
||||
'dropdown': 'fa fa-th-list',
|
||||
'date': 'fa fa-calendar',
|
||||
'checkbox': 'fa fa-check-square-o',
|
||||
'radio': 'fa fa-dot-circle-o',
|
||||
'email': 'fa fa-envelope-o',
|
||||
'textarea': 'fa fa-pencil-square',
|
||||
'legal': 'fa fa-legal',
|
||||
'file': 'fa fa-cloud-upload',
|
||||
'rating': 'fa fa-star-half-o',
|
||||
'link': 'fa fa-link',
|
||||
'scale': 'fa fa-sliders',
|
||||
'stripe': 'fa fa-credit-card',
|
||||
'statement': 'fa fa-quote-left',
|
||||
'yes_no': 'fa fa-toggle-on',
|
||||
'number': 'fa fa-slack'
|
||||
};
|
||||
$scope.typeIcon = iconTypeMap[$scope.typeName];
|
||||
}
|
||||
};
|
||||
});
|
@ -0,0 +1,108 @@
|
||||
'use strict';
|
||||
|
||||
// coffeescript's for in loop
|
||||
var __indexOf = [].indexOf || function(item) {
|
||||
for (var i = 0, l = this.length; i < l; i++) {
|
||||
if (i in this && this[i] === item) return i;
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
angular.module('view-form').directive('fieldDirective', ['$http', '$compile', '$rootScope', '$templateCache', 'supportedFields',
|
||||
function($http, $compile, $rootScope, $templateCache, supportedFields) {
|
||||
|
||||
var getTemplateUrl = function(fieldType) {
|
||||
var type = fieldType;
|
||||
|
||||
var supported_fields = [
|
||||
'textfield',
|
||||
'textarea',
|
||||
'date',
|
||||
'dropdown',
|
||||
'hidden',
|
||||
'password',
|
||||
'radio',
|
||||
'legal',
|
||||
'statement',
|
||||
'rating',
|
||||
'yes_no',
|
||||
'number',
|
||||
'natural'
|
||||
];
|
||||
|
||||
var templateUrl = 'modules/forms/base/views/directiveViews/field/';
|
||||
|
||||
if (__indexOf.call(supportedFields, type) >= 0) {
|
||||
templateUrl = templateUrl+type+'.html';
|
||||
}
|
||||
return $templateCache.get(templateUrl);
|
||||
};
|
||||
|
||||
return {
|
||||
template: '<div>{{field.title}}</div>',
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
field: '=',
|
||||
required: '&',
|
||||
design: '=',
|
||||
index: '=',
|
||||
forms: '='
|
||||
},
|
||||
link: function(scope, element) {
|
||||
|
||||
$rootScope.chooseDefaultOption = scope.chooseDefaultOption = function(type) {
|
||||
if(type === 'yes_no'){
|
||||
scope.field.fieldValue = 'true';
|
||||
}else if(type === 'rating'){
|
||||
scope.field.fieldValue = 0;
|
||||
}else if(scope.field.fieldType === 'radio'){
|
||||
console.log(scope.field);
|
||||
scope.field.fieldValue = scope.field.fieldOptions[0].option_value;
|
||||
console.log(scope.field.fieldValue);
|
||||
}else if(type === 'legal'){
|
||||
scope.field.fieldValue = 'true';
|
||||
$rootScope.nextField();
|
||||
}
|
||||
};
|
||||
|
||||
scope.setActiveField = $rootScope.setActiveField;
|
||||
|
||||
//Set format only if field is a date
|
||||
if(scope.field.fieldType === 'date'){
|
||||
scope.dateOptions = {
|
||||
changeYear: true,
|
||||
changeMonth: true,
|
||||
altFormat: 'mm/dd/yyyy',
|
||||
yearRange: '1900:-0',
|
||||
defaultDate: 0
|
||||
};
|
||||
}
|
||||
|
||||
var fieldType = scope.field.fieldType;
|
||||
|
||||
if(scope.field.fieldType === 'number' || scope.field.fieldType === 'textfield' || scope.field.fieldType === 'email' || scope.field.fieldType === 'link'){
|
||||
switch(scope.field.fieldType){
|
||||
case 'textfield':
|
||||
scope.field.input_type = 'text';
|
||||
break;
|
||||
case 'email':
|
||||
scope.field.input_type = 'email';
|
||||
scope.field.placeholder = 'joesmith@example.com';
|
||||
break;
|
||||
case 'number':
|
||||
scope.field.input_type = 'text';
|
||||
scope.field.validateRegex = /^-?\d+$/;
|
||||
break;
|
||||
default:
|
||||
scope.field.input_type = 'url';
|
||||
scope.field.placeholder = 'http://example.com';
|
||||
break;
|
||||
}
|
||||
fieldType = 'textfield';
|
||||
}
|
||||
var template = getTemplateUrl(fieldType);
|
||||
element.html(template).show();
|
||||
var output = $compile(element.contents())(scope);
|
||||
}
|
||||
};
|
||||
}]);
|
@ -0,0 +1,76 @@
|
||||
'use strict';
|
||||
|
||||
//TODO: DAVID: Need to refactor this
|
||||
angular.module('view-form').directive('onEnterKey', ['$rootScope', function($rootScope){
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function($scope, $element, $attrs) {
|
||||
$element.bind('keydown keypress', function(event) {
|
||||
|
||||
var keyCode = event.which || event.keyCode;
|
||||
|
||||
var onEnterKeyDisabled = false;
|
||||
if($attrs.onEnterKeyDisabled !== null) onEnterKeyDisabled = $attrs.onEnterKeyDisabled;
|
||||
|
||||
if(keyCode === 13 && !event.shiftKey && !onEnterKeyDisabled) {
|
||||
event.preventDefault();
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.$eval($attrs.onEnterKey);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}]).directive('onTabKey', ['$rootScope', function($rootScope){
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function($scope, $element, $attrs) {
|
||||
$element.bind('keydown keypress', function(event) {
|
||||
|
||||
var keyCode = event.which || event.keyCode;
|
||||
|
||||
if(keyCode === 9 && !event.shiftKey) {
|
||||
|
||||
event.preventDefault();
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.$eval($attrs.onTabKey);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}]).directive('onEnterOrTabKey', ['$rootScope', function($rootScope){
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function($scope, $element, $attrs) {
|
||||
$element.bind('keydown keypress', function(event) {
|
||||
|
||||
var keyCode = event.which || event.keyCode;
|
||||
|
||||
if((keyCode === 13 || keyCode === 9) && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.$eval($attrs.onEnterOrTabKey);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}]).directive('onTabAndShiftKey', ['$rootScope', function($rootScope){
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function($scope, $element, $attrs) {
|
||||
$element.bind('keydown keypress', function(event) {
|
||||
|
||||
var keyCode = event.which || event.keyCode;
|
||||
|
||||
if(keyCode === 9 && event.shiftKey) {
|
||||
event.preventDefault();
|
||||
$rootScope.$apply(function() {
|
||||
$rootScope.$eval($attrs.onTabAndShiftKey);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}]);
|
@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('view-form').directive('onFinishRender', function ($rootScope, $timeout) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function (scope, element, attrs) {
|
||||
|
||||
//Don't do anything if we don't have a ng-repeat on the current element
|
||||
if(!element.attr('ng-repeat') && !element.attr('data-ng-repeat')){
|
||||
return;
|
||||
}
|
||||
|
||||
var broadcastMessage = attrs.onFinishRender || 'ngRepeat';
|
||||
|
||||
if(scope.$first && !scope.$last) {
|
||||
scope.$evalAsync(function () {
|
||||
$rootScope.$broadcast(broadcastMessage+' Started');
|
||||
});
|
||||
}else if(scope.$last) {
|
||||
scope.$evalAsync(function () {
|
||||
// console.log(broadcastMessage+'Finished');
|
||||
$rootScope.$broadcast(broadcastMessage+' Finished');
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
@ -0,0 +1,239 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCounter', '$filter', '$rootScope', 'SendVisitorData',
|
||||
function ($http, TimeCounter, $filter, $rootScope, SendVisitorData) {
|
||||
return {
|
||||
templateUrl: 'modules/forms/base/views/directiveViews/form/submit-form.client.view.html',
|
||||
restrict: 'E',
|
||||
scope: {
|
||||
myform:'='
|
||||
},
|
||||
controller: function($document, $window, $scope){
|
||||
$scope.noscroll = false;
|
||||
$scope.forms = {};
|
||||
|
||||
var form_fields_count = $scope.myform.visible_form_fields.filter(function(field){
|
||||
if(field.fieldType === 'statement' || field.fieldType === 'rating'){
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}).length;
|
||||
|
||||
var nb_valid = $filter('formValidity')($scope.myform);
|
||||
$scope.translateAdvancementData = {
|
||||
done: nb_valid,
|
||||
total: form_fields_count,
|
||||
answers_not_completed: form_fields_count - nb_valid
|
||||
};
|
||||
|
||||
$scope.reloadForm = function(){
|
||||
//Reset Form
|
||||
$scope.myform.submitted = false;
|
||||
$scope.myform.form_fields = _.chain($scope.myform.visible_form_fields).map(function(field){
|
||||
field.fieldValue = '';
|
||||
return field;
|
||||
}).value();
|
||||
|
||||
$scope.loading = false;
|
||||
$scope.error = '';
|
||||
|
||||
$scope.selected = {
|
||||
_id: '',
|
||||
index: 0
|
||||
};
|
||||
$scope.setActiveField($scope.myform.visible_form_fields[0]._id, 0, false);
|
||||
|
||||
//console.log($scope.selected);
|
||||
//Reset Timer
|
||||
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;
|
||||
|
||||
//console.log($scope.forms.myForm);
|
||||
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);
|
||||
}
|
||||
//console.log('$scope.selected.index: '+$scope.selected.index);
|
||||
//console.log('scroll pos: '+$scope.scrollPos+' fieldTop: '+$scope.fieldTop+' fieldBottom: '+$scope.fieldBottom);
|
||||
$scope.$apply();
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
** Field Controls
|
||||
*/
|
||||
var getActiveField = function(){
|
||||
if($scope.selected === null){
|
||||
console.error('current active field is null');
|
||||
throw new Error('current active field is null');
|
||||
}
|
||||
|
||||
if($scope.selected._id === 'submit_field') {
|
||||
return $scope.myform.form_fields.length - 1;
|
||||
} else {
|
||||
return $scope.selected.index;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.setActiveField = $rootScope.setActiveField = function(field_id, field_index, animateScroll) {
|
||||
if($scope.selected === null || $scope.selected._id === field_id){
|
||||
//console.log('not scrolling');
|
||||
//console.log($scope.selected);
|
||||
return;
|
||||
}
|
||||
//console.log('field_id: '+field_id);
|
||||
//console.log('field_index: '+field_index);
|
||||
//console.log($scope.selected);
|
||||
|
||||
$scope.selected._id = field_id;
|
||||
$scope.selected.index = field_index;
|
||||
|
||||
var nb_valid = $filter('formValidity')($scope.myform);
|
||||
$scope.translateAdvancementData = {
|
||||
done: nb_valid,
|
||||
total: form_fields_count,
|
||||
answers_not_completed: form_fields_count - nb_valid
|
||||
};
|
||||
|
||||
if(animateScroll){
|
||||
$scope.noscroll=true;
|
||||
setTimeout(function() {
|
||||
$document.scrollToElement(angular.element('.activeField'), -10, 200).then(function() {
|
||||
$scope.noscroll = false;
|
||||
setTimeout(function() {
|
||||
if (document.querySelectorAll('.activeField .focusOn').length) {
|
||||
//Handle default case
|
||||
document.querySelectorAll('.activeField .focusOn')[0].focus();
|
||||
} else if(document.querySelectorAll('.activeField input').length) {
|
||||
//Handle case for rating input
|
||||
document.querySelectorAll('.activeField input')[0].focus();
|
||||
} else {
|
||||
//Handle case for dropdown input
|
||||
document.querySelectorAll('.activeField .selectize-input')[0].focus();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}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 {
|
||||
document.querySelectorAll('.activeField input')[0].focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
SendVisitorData.send($scope.myform, getActiveField(), TimeCounter.getTimeElapsed());
|
||||
};
|
||||
|
||||
$rootScope.nextField = $scope.nextField = function(){
|
||||
//console.log('nextfield');
|
||||
//console.log($scope.selected.index);
|
||||
//console.log($scope.myform.visible_form_fields.length-1);
|
||||
var selected_index, selected_id;
|
||||
if($scope.selected.index < $scope.myform.visible_form_fields.length-1){
|
||||
selected_index = $scope.selected.index+1;
|
||||
selected_id = $scope.myform.visible_form_fields[selected_index]._id;
|
||||
$rootScope.setActiveField(selected_id, selected_index, true);
|
||||
} else if($scope.selected.index === $scope.myform.visible_form_fields.length-1) {
|
||||
//console.log('Second last element');
|
||||
selected_index = $scope.selected.index+1;
|
||||
selected_id = 'submit_field';
|
||||
$rootScope.setActiveField(selected_id, selected_index, true);
|
||||
}
|
||||
};
|
||||
|
||||
$rootScope.prevField = $scope.prevField = function(){
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
** Form Display Functions
|
||||
*/
|
||||
$scope.exitStartPage = function(){
|
||||
$scope.myform.startPage.showStart = false;
|
||||
if($scope.myform.visible_form_fields.length > 0){
|
||||
$scope.selected._id = $scope.myform.visible_form_fields[0]._id;
|
||||
}
|
||||
};
|
||||
|
||||
$rootScope.goToInvalid = $scope.goToInvalid = function() {
|
||||
document.querySelectorAll('.ng-invalid.focusOn')[0].focus();
|
||||
};
|
||||
|
||||
$rootScope.submitForm = $scope.submitForm = function() {
|
||||
|
||||
var _timeElapsed = TimeCounter.stopClock();
|
||||
$scope.loading = true;
|
||||
|
||||
var form = _.cloneDeep($scope.myform);
|
||||
|
||||
form.timeElapsed = _timeElapsed;
|
||||
|
||||
form.percentageComplete = $filter('formValidity')($scope.myform) / $scope.myform.visible_form_fields.length * 100;
|
||||
delete form.visible_form_fields;
|
||||
|
||||
for(var i=0; i < $scope.myform.form_fields.length; i++){
|
||||
if($scope.myform.form_fields[i].fieldType === 'dropdown' && !$scope.myform.form_fields[i].deletePreserved){
|
||||
$scope.myform.form_fields[i].fieldValue = $scope.myform.form_fields[i].fieldValue.option_value;
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
$scope.submitPromise = $http.post('/forms/' + $scope.myform._id, form)
|
||||
.success(function (data, status, headers) {
|
||||
console.log($scope.myform.form_fields[0]);
|
||||
$scope.myform.submitted = true;
|
||||
$scope.loading = false;
|
||||
SendVisitorData.send($scope.myform, getActiveField(), _timeElapsed);
|
||||
})
|
||||
.error(function (error) {
|
||||
$scope.loading = false;
|
||||
console.error(error);
|
||||
$scope.error = error.message;
|
||||
});
|
||||
}, 500);
|
||||
};
|
||||
|
||||
//Reload our form
|
||||
$scope.reloadForm();
|
||||
}
|
||||
};
|
||||
}
|
||||
]);
|
@ -0,0 +1,18 @@
|
||||
'use strict';
|
||||
|
||||
//Forms service used for communicating with the forms REST endpoints
|
||||
angular.module('view-form').service('CurrentForm',
|
||||
function(){
|
||||
|
||||
//Private variables
|
||||
var _form = {};
|
||||
|
||||
//Public Methods
|
||||
this.getForm = function() {
|
||||
return _form;
|
||||
};
|
||||
this.setForm = function(form) {
|
||||
_form = form;
|
||||
};
|
||||
}
|
||||
);
|
@ -0,0 +1,42 @@
|
||||
'use strict';
|
||||
|
||||
//Forms service used for communicating with the forms REST endpoints
|
||||
angular.module('view-form').factory('Forms', ['$resource', 'FORM_URL',
|
||||
function($resource, FORM_URL) {
|
||||
return $resource(FORM_URL, {
|
||||
formId: '@_id'
|
||||
}, {
|
||||
'query' : {
|
||||
method: 'GET',
|
||||
isArray: true
|
||||
//DAVID: TODO: Do we really need to get visible_form_fields for a Query?
|
||||
// transformResponse: function(data, header) {
|
||||
// var forms = angular.fromJson(data);
|
||||
// angular.forEach(forms, function(form, idx) {
|
||||
// form.visible_form_fields = _.filter(form.form_fields, function(field){
|
||||
// return (field.deletePreserved === false);
|
||||
// });
|
||||
// });
|
||||
// return forms;
|
||||
// }
|
||||
},
|
||||
'get' : {
|
||||
method: 'GET',
|
||||
transformResponse: function(data, header) {
|
||||
var form = angular.fromJson(data);
|
||||
|
||||
form.visible_form_fields = _.filter(form.form_fields, function(field){
|
||||
return (field.deletePreserved === false);
|
||||
});
|
||||
return form;
|
||||
}
|
||||
},
|
||||
'update': {
|
||||
method: 'PUT'
|
||||
},
|
||||
'save': {
|
||||
method: 'POST'
|
||||
}
|
||||
});
|
||||
}
|
||||
]);
|
@ -0,0 +1,54 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
// Create the Socket.io wrapper service
|
||||
angular
|
||||
.module('view-form')
|
||||
.factory('Socket', Socket);
|
||||
|
||||
Socket.$inject = ['$timeout', '$window'];
|
||||
|
||||
function Socket($timeout, $window) {
|
||||
var service = {
|
||||
connect: connect,
|
||||
emit: emit,
|
||||
on: on,
|
||||
removeListener: removeListener,
|
||||
socket: null
|
||||
};
|
||||
|
||||
connect(window.location.protocol+'//'+window.location.hostname+':'+$window.socketPort);
|
||||
|
||||
return service;
|
||||
|
||||
// Connect to Socket.io server
|
||||
function connect(url) {
|
||||
service.socket = io(url, {'transports': ['websocket', 'polling']});
|
||||
}
|
||||
|
||||
// Wrap the Socket.io 'emit' method
|
||||
function emit(eventName, data) {
|
||||
if (service.socket) {
|
||||
service.socket.emit(eventName, data);
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap the Socket.io 'on' method
|
||||
function on(eventName, callback) {
|
||||
if (service.socket) {
|
||||
service.socket.on(eventName, function (data) {
|
||||
$timeout(function () {
|
||||
callback(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap the Socket.io 'removeListener' method
|
||||
function removeListener(eventName) {
|
||||
if (service.socket) {
|
||||
service.socket.removeListener(eventName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}());
|
@ -0,0 +1,38 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('view-form').service('TimeCounter', [
|
||||
function(){
|
||||
var _startTime, _endTime = null, that=this;
|
||||
|
||||
this.timeSpent = 0;
|
||||
|
||||
this.restartClock = function(){
|
||||
_startTime = Date.now();
|
||||
_endTime = null;
|
||||
// console.log('Clock Started');
|
||||
};
|
||||
|
||||
this.getTimeElapsed = function(){
|
||||
if(_startTime) {
|
||||
return Math.abs(Date.now().valueOf() - _startTime.valueOf()) / 1000;
|
||||
}
|
||||
};
|
||||
|
||||
this.stopClock = function(){
|
||||
if(_startTime && _endTime === null){
|
||||
_endTime = Date.now();
|
||||
this.timeSpent = Math.abs(_endTime.valueOf() - _startTime.valueOf())/1000;
|
||||
this._startTime = this._endTime = null;
|
||||
|
||||
return this.timeSpent;
|
||||
}else{
|
||||
return new Error('Clock has not been started');
|
||||
}
|
||||
};
|
||||
|
||||
this.clockStarted = function(){
|
||||
return !!this._startTime;
|
||||
};
|
||||
|
||||
}
|
||||
]);
|
@ -0,0 +1,24 @@
|
||||
<div class="field row text-center">
|
||||
<div class="col-xs-12 text-center">
|
||||
<h1>{{pageData.introTitle}}</h1>
|
||||
</div>
|
||||
<div class="col-xs-10 col-xs-offset-1 text-left">
|
||||
<p style="color:#ddd;">{{pageData.introParagraph}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row form-actions" style="padding-bottom:3em; padding-left: 1em; padding-right: 1em;">
|
||||
<p ng-repeat="button in pageData.buttons" class="text-center" style="display:inline;">
|
||||
<button class="btn btn-info" type="button" ng-style="{'background-color':button.bgColor, 'color':button.color}">
|
||||
<a href="{{button.url}}" style="font-size: 1.6em; text-decoration: none; color: inherit;" >
|
||||
{{button.text}}
|
||||
</a>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
<div class="row form-actions">
|
||||
<p class="col-xs-3 col-xs-offset-3 text-center">
|
||||
<button class="btn btn-info" type="button">
|
||||
<a ng-click="exitpageData()" style="color:white; font-size: 1.6em; text-decoration: none;">{{ 'CONTINUE_FORM' | translate }}</a>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
33
public/form_modules/forms/base/views/directiveViews/field/date.html
Executable file
33
public/form_modules/forms/base/views/directiveViews/field/date.html
Executable file
@ -0,0 +1,33 @@
|
||||
<div class="field row"
|
||||
ng-click="setActiveField(field._id, index, true)">
|
||||
<div class="col-xs-12 field-title" ng-style="{'color': design.colors.questionColor}">
|
||||
<h3>
|
||||
<small class="field-number">
|
||||
{{index+1}}
|
||||
<i class="fa fa-angle-double-right" aria-hidden="true"></i>
|
||||
</small>
|
||||
{{field.title}}
|
||||
<span class="required-error" ng-show="!field.required && !field.fieldValue">{{ 'OPTIONAL' | translate }}</span>
|
||||
</h3>
|
||||
<p class="col-xs-12">
|
||||
<small>{{field.description}}</small>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-xs-12 field-input">
|
||||
<div class="control-group input-append">
|
||||
<input class="focusOn"
|
||||
ng-style="{'color': design.colors.answerColor, 'border-color': design.colors.answerColor}"
|
||||
ng-class="{ 'no-border': !!field.fieldValue }"
|
||||
ui-date="dateOptions"
|
||||
ng-model="field.fieldValue"
|
||||
ng-model-options="{ debounce: 250 }"
|
||||
ng-required="field.required"
|
||||
ng-disabled="field.disabled"
|
||||
placeholder="MM/DD/YYYY"
|
||||
ng-focus="setActiveField(field._id, index, true)"
|
||||
on-tab-key="nextField()"
|
||||
on-tab-and-shift-key="prevField()"
|
||||
ng-change="$root.nextField()">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
36
public/form_modules/forms/base/views/directiveViews/field/dropdown.html
Executable file
36
public/form_modules/forms/base/views/directiveViews/field/dropdown.html
Executable file
@ -0,0 +1,36 @@
|
||||
<div class="field row dropdown"
|
||||
ng-if="field.fieldOptions.length > 0">
|
||||
<div class="col-xs-12 field-title" ng-style="{'color': design.colors.questionColor}">
|
||||
<h3>
|
||||
<small class="field-number">
|
||||
{{index+1}}
|
||||
<i class="fa fa-angle-double-right" aria-hidden="true"></i>
|
||||
</small>
|
||||
{{field.title}}
|
||||
<span class="required-error" ng-show="!field.required">{{ 'OPTIONAL' | translate }}</span>
|
||||
</h3>
|
||||
<p class="col-xs-12">
|
||||
<small>{{field.description}}</small>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-xs-12 field-input">
|
||||
<ui-select ng-model="field.fieldValue"
|
||||
theme="selectize"
|
||||
search-enabled="true"
|
||||
search-by="option_value"
|
||||
set-search-to-answer="true"
|
||||
ng-required="field.required"
|
||||
ng-disabled="field.disabled"
|
||||
on-tab-and-shift-key="prevField()"
|
||||
on-tab-key="nextField()"
|
||||
ng-change="$root.nextField()">
|
||||
<ui-select-match placeholder="Type or select an option">
|
||||
</ui-select-match>
|
||||
<ui-select-choices repeat="option in field.fieldOptions | filter: $select.search"
|
||||
ng-class="{'active': option.option_value === field.fieldValue }">
|
||||
<span ng-bind-html="option.option_value | highlight: $select.search"></span>
|
||||
</ui-select-choices>
|
||||
</ui-select>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
27
public/form_modules/forms/base/views/directiveViews/field/dropdown.html~HEAD
Executable file
27
public/form_modules/forms/base/views/directiveViews/field/dropdown.html~HEAD
Executable file
@ -0,0 +1,27 @@
|
||||
<div class="field row dropdown" ng-click="setActiveField(field._id, index, true)" ng-if="field.fieldOptions.length > 0">
|
||||
<div class="col-xs-12 field-title" ng-style="{'color': design.colors.questionColor}">
|
||||
<h3>
|
||||
<span class="fa fa-angle-double-right"></span>
|
||||
{{field.title}}
|
||||
<span class="required-error" ng-show="!field.required">{{ 'OPTIONAL' | translate }}</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="col-xs-12 field-input ">
|
||||
<ui-select ng-model="field.fieldValue"
|
||||
theme="selectize"
|
||||
ng-model-options="{ debounce: 250 }"
|
||||
ng-required="field.required"
|
||||
ng-disabled="field.disabled"
|
||||
ng-focus="setActiveField(field._id, index, true)">
|
||||
<ui-select-match placeholder="Type or select an option">
|
||||
{{$select.selected.option_value}}
|
||||
</ui-select-match>
|
||||
<ui-select-choices
|
||||
repeat="option in field.fieldOptions | filter: $select.search"
|
||||
ng-class="{'active': option.option_value === field.fieldValue }">
|
||||
<span ng-bind-html="option.option_value | highlight: $select.search"></span>
|
||||
</ui-select-choices>
|
||||
</ui-select>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
@ -0,0 +1,38 @@
|
||||
<div class="field row" ng-if="form.autofillPDFs" ng-click="setActiveField(field._id, index, true)">
|
||||
<div class="col-xs-12 field-title" ng-style="{'color': design.colors.questionColor}">
|
||||
<h3>
|
||||
<small class="field-number">
|
||||
{{index+1}}
|
||||
<i class="fa fa-angle-double-right" aria-hidden="true"></i>
|
||||
</small>
|
||||
{{field.title}}
|
||||
<span class="required-error" ng-show="!field.required">{{ 'OPTIONAL' | translate }}</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="col-sm-8 field-input">
|
||||
<div class="input-group ">
|
||||
<div tabindex="-1" class="form-control file-caption">
|
||||
<span class="file-caption-ellipsis" ng-if="!form.pdf">…</span>
|
||||
<div class="file-caption-name" ng-if="form.pdf">
|
||||
{{field.file.originalname}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-group-btn">
|
||||
<button type="button" ng-if="field.file" ng-click="removeFile(field);" title="Clear selected files" class="btn btn-danger fileinput-remove fileinput-remove-button">
|
||||
<i class="glyphicon glyphicon-trash" ></i>
|
||||
{{ 'DELETE' | translate }}
|
||||
</button>
|
||||
|
||||
<button type="button" ng-if="field.fileLoading" title="Abort ongoing upload" class="btn btn-default" ng-click="cancelFileUpload(field)">
|
||||
<i class="glyphicon glyphicon-ban-circle"></i>
|
||||
{{ 'CANCEL' | translate }}
|
||||
</button>
|
||||
|
||||
<div class="btn btn-success btn-file" ngf-select ngf-change="uploadPDF($files)" ng-if="!field.file">
|
||||
<i class="glyphicon glyphicon-upload"></i>
|
||||
{{ UPLOAD_FILE | translate }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
1
public/form_modules/forms/base/views/directiveViews/field/hidden.html
Executable file
1
public/form_modules/forms/base/views/directiveViews/field/hidden.html
Executable file
@ -0,0 +1 @@
|
||||
<input ng-focus="setActiveField(field._id, index, true)" ng-style="{'color': design.colors.answerColor, 'border-color': design.colors.answerColor}" type="hidden" ng-model="field.fieldValue" ng-model-options="{ debounce: 250 }" value="{{field.fieldValue}}" ng-disabled="field.disabled">
|
@ -0,0 +1,55 @@
|
||||
<div class="field row radio legal"
|
||||
on-enter-or-tab-key="nextField()"
|
||||
key-to-truthy key-char-truthy="y" key-char-falsey="n" field="field">
|
||||
<div class="col-xs-12 field-title" ng-style="{'color': design.colors.questionColor}">
|
||||
<h3>
|
||||
<small class="field-number">
|
||||
{{index+1}}
|
||||
<i class="fa fa-angle-double-right" aria-hidden="true"></i>
|
||||
</small>
|
||||
{{field.title}}
|
||||
<span class="required-error" ng-show="!field.required">{{ 'OPTIONAL' | translate }}</span>
|
||||
</h3>
|
||||
<br>
|
||||
<p class="col-xs-12">{{field.description}}</p>
|
||||
</div>
|
||||
<div class="col-xs-12 field-input container">
|
||||
<div class="row-fluid"
|
||||
on-enter-or-tab-key="nextField()"
|
||||
on-tab-and-shift-key="prevField()">
|
||||
<label class="btn col-md-5 col-xs-12"
|
||||
ng-class="{activeBtn: field.fieldValue == 'true'}">
|
||||
<input class="focusOn"
|
||||
ng-focus="setActiveField(field._id, index, true)"
|
||||
ng-style="{'color': design.colors.answerColor, 'border-color': design.colors.answerColor}"
|
||||
type="radio" value="true"
|
||||
ng-model="field.fieldValue"
|
||||
ng-model-options="{ debounce: 250 }"
|
||||
ng-required="field.required"
|
||||
ng-disabled="field.disabled"
|
||||
ng-change="$root.nextField()"/>
|
||||
<div class="letter" style="float:left">
|
||||
Y
|
||||
</div>
|
||||
<span>{{ 'LEGAL_ACCEPT' | translate }}</span>
|
||||
</label>
|
||||
<label class="btn col-md-5 col-md-offset-1 col-xs-12"
|
||||
ng-class="{activeBtn: field.fieldValue == 'false'}">
|
||||
<input class="focusOn"
|
||||
ng-style="{'color': design.colors.answerColor, 'border-color': design.colors.answerColor}"
|
||||
type="radio" value="false"
|
||||
ng-model="field.fieldValue"
|
||||
ng-model-options="{ debounce: 250 }"
|
||||
ng-required="field.required"
|
||||
ng-disabled="field.disabled"
|
||||
ng-change="$root.nextField()"/>
|
||||
<div class="letter" style="float:left">
|
||||
N
|
||||
</div>
|
||||
<span>{{ 'LEGAL_NO_ACCEPT' | translate }}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
42
public/form_modules/forms/base/views/directiveViews/field/radio.html
Executable file
42
public/form_modules/forms/base/views/directiveViews/field/radio.html
Executable file
@ -0,0 +1,42 @@
|
||||
<div class="field row radio"
|
||||
on-enter-or-tab-key="nextField()"
|
||||
key-to-option field="field"
|
||||
ng-if="field.fieldOptions.length > 0">
|
||||
<div class="col-xs-12 field-title" ng-style="{'color': design.colors.questionColor}">
|
||||
<h3>
|
||||
<small class="field-number">
|
||||
{{index+1}}
|
||||
<i class="fa fa-angle-double-right" aria-hidden="true"></i>
|
||||
</small>
|
||||
{{field.title}}
|
||||
<span class="required-error" ng-show="!field.required">{{ 'OPTIONAL' | translate }}</span>
|
||||
</h3>
|
||||
<p class="col-xs-12">
|
||||
<small>{{field.description}}</small>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-xs-12 field-input">
|
||||
|
||||
<div ng-repeat="option in field.fieldOptions" class="row-fluid">
|
||||
<label class="btn col-md-4 col-xs-12 col-sm-12"
|
||||
style="margin: 0.5em; padding-left:30px"
|
||||
ng-class="{activeBtn: field.fieldValue == field.fieldOptions[$index].option_value}">
|
||||
<div class="letter" style="float:left">
|
||||
{{$index+1}}
|
||||
</div>
|
||||
<input ng-style="{'color': design.colors.answerColor, 'border-color': design.colors.answerColor}"
|
||||
type="radio" class="focusOn"
|
||||
ng-focus="setActiveField(field._id, index, true)"
|
||||
value="{{option.option_value}}"
|
||||
ng-model="field.fieldValue"
|
||||
ng-model-options="{ debounce: 250 }"
|
||||
ng-required="field.required"
|
||||
ng-disabled="field.disabled"
|
||||
ng-change="$root.nextField()"/>
|
||||
|
||||
<span ng-bind="option.option_value"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
@ -0,0 +1,34 @@
|
||||
<div class="textfield field row"
|
||||
on-enter-or-tab-key="nextField()">
|
||||
<div class="col-xs-12 field-title" ng-style="{'color': design.colors.questionColor}">
|
||||
<h3>
|
||||
<small class="field-number">
|
||||
{{index+1}}
|
||||
<i class="fa fa-angle-double-right" aria-hidden="true"></i>
|
||||
</small>
|
||||
{{field.title}}
|
||||
<span class="required-error" ng-show="!field.required">{{ 'OPTIONAL' | translate }}</span>
|
||||
</h3>
|
||||
<p class="col-xs-12">
|
||||
<small>{{field.description}}</small>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-xs-12 field-input">
|
||||
|
||||
<input-stars max="{{field.ratingOptions.steps}}"
|
||||
ng-init="field.fieldValue = 1"
|
||||
on-star-click="$root.nextField()"
|
||||
icon-full="{{field.ratingOptions.shape}}"
|
||||
icon-base="fa fa-3x"
|
||||
icon-empty="{{field.ratingOptions.shape}}"
|
||||
ng-model="field.fieldValue"
|
||||
ng-model-options="{ debounce: 250 }"
|
||||
ng-required="field.required"
|
||||
ng-disabled="field.disabled"
|
||||
on-enter-or-tab-key="nextField()"
|
||||
on-tab-and-shift-key="prevField()"
|
||||
ng-focus="setActiveField(field._id, index, true)"
|
||||
class="angular-input-stars focusOn">
|
||||
</input-stars>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,24 @@
|
||||
<div class="statement field row"
|
||||
on-enter-or-tab-key="nextField()"
|
||||
on-tab-and-shift-key="prevField()"
|
||||
ng-focus="setActiveField(field._id, index, true)">
|
||||
<div class="row field-title field-title">
|
||||
<div class="col-xs-1"><i class="fa fa-quote-left fa-1"></i></div>
|
||||
<h2 class="text-left col-xs-9">{{field.title}}</h2>
|
||||
<p class="col-xs-12">
|
||||
<small>{{field.description}}</small>
|
||||
</p>
|
||||
</div>
|
||||
<div class="row field-title field-input">
|
||||
<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"
|
||||
ng-style="{'font-size': '1.3em', 'background-color':design.colors.buttonColor, 'color':design.colors.buttonTextColor}"
|
||||
ng-focused="setActiveField(field._id, index, true)"
|
||||
ng-click="$root.nextField()">
|
||||
{{ 'CONTINUE' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
49
public/form_modules/forms/base/views/directiveViews/field/textarea.html
Executable file
49
public/form_modules/forms/base/views/directiveViews/field/textarea.html
Executable file
@ -0,0 +1,49 @@
|
||||
<div class="field row" ng-click="setActiveField(field._id, index, true)">
|
||||
<div class="col-xs-12 field-title" ng-style="{'color': design.colors.questionColor}">
|
||||
<h3>
|
||||
<small class="field-number">
|
||||
{{index+1}}
|
||||
<i class="fa fa-angle-double-right" aria-hidden="true"></i>
|
||||
</small>
|
||||
{{field.title}}
|
||||
<span class="required-error" ng-show="!field.required">{{ 'OPTIONAL' | translate }}</span>
|
||||
</h3>
|
||||
<small>{{ 'NEWLINE' | translate }}</small>
|
||||
<p>
|
||||
<small>{{field.description}}</small>
|
||||
</p>
|
||||
</div>
|
||||
<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-model="field.fieldValue"
|
||||
ng-model-options="{ debounce: 250 }"
|
||||
ng-class="{ 'no-border': !!field.fieldValue }"
|
||||
value="{{field.fieldValue}}"
|
||||
ng-required="field.required"
|
||||
ng-disabled="field.disabled"
|
||||
ng-focus="setActiveField(field._id, index, true)"
|
||||
on-enter-or-tab-key="nextField()"
|
||||
on-tab-and-shift-key="prevField()"
|
||||
style="border: none; border-left: lightgrey dashed 2px;">
|
||||
</textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="btn btn-lg btn-default col-xs-12 col-sm-4 hidden-xs"
|
||||
style="padding: 4px; margin-top:8px; background: rgba(255,255,255,0.5)">
|
||||
<button ng-disabled="!field.fieldValue || forms.myForm.{{field.fieldType}}{{$index}}.$invalid"
|
||||
ng-style="{'background-color':design.colors.buttonColor, 'color':design.colors.buttonTextColor}"
|
||||
ng-click="$root.nextField()"
|
||||
class="btn col-sm-5 col-xs-5">
|
||||
|
||||
{{ 'OK' | translate }} <i class="fa fa-check"></i>
|
||||
</button>
|
||||
<div class="col-sm-3 col-xs-6" style="margin-top:0.2em">
|
||||
<small style="color:#ddd; font-size:70%">
|
||||
{{ 'ENTER' | translate }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
65
public/form_modules/forms/base/views/directiveViews/field/textfield.html
Executable file
65
public/form_modules/forms/base/views/directiveViews/field/textfield.html
Executable file
@ -0,0 +1,65 @@
|
||||
<div class="textfield field row"
|
||||
ng-click="setActiveField(field._id, index, true)">
|
||||
<div class="col-xs-12 field-title row-fluid" ng-style="{'color': design.colors.questionColor}">
|
||||
<h3 class="col-xs-12">
|
||||
<small class="field-number">
|
||||
{{index+1}}
|
||||
<i class="fa fa-angle-double-right" aria-hidden="true"></i>
|
||||
</small>
|
||||
|
||||
{{field.title}}
|
||||
|
||||
<span class="required-error" ng-show="!field.required">
|
||||
({{ 'OPTIONAL' | translate }})
|
||||
</span>
|
||||
</h3>
|
||||
|
||||
<p class="col-xs-12">
|
||||
<small>{{field.description}}</small>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-xs-12 field-input">
|
||||
<input ng-style="{'color': design.colors.answerColor, 'border-color': design.colors.answerColor}"
|
||||
name="{{field.fieldType}}{{index}}"
|
||||
type="{{field.input_type}}"
|
||||
ng-pattern="field.validateRegex"
|
||||
placeholder="{{field.placeholder}}"
|
||||
ng-class="{ 'no-border': !!field.fieldValue }"
|
||||
class="focusOn text-field-input"
|
||||
ng-model="field.fieldValue"
|
||||
ng-model-options="{ debounce: 250 }"
|
||||
value="field.fieldValue"
|
||||
ng-focus="setActiveField(field._id, index, true)"
|
||||
on-enter-or-tab-key="nextField()"
|
||||
on-tab-and-shift-key="prevField()"
|
||||
ng-required="field.required"
|
||||
ng-disabled="field.disabled"
|
||||
aria-describedby="inputError2Status">
|
||||
</div>
|
||||
<div class="col-xs-12">
|
||||
<div ng-show="forms.myForm.{{field.fieldType}}{{index}}.$invalid && !!forms.myForm.{{field.fieldType}}{{index}}.$viewValue " class="alert alert-danger" role="alert">
|
||||
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
|
||||
<span class="sr-only">Error:</span>
|
||||
<span ng-if="field.fieldType == 'email'"> {{ 'ERROR_EMAIL_INVALID' | translate }} </span>
|
||||
<span ng-if="field.validateRegex"> {{ 'ERROR_NOT_A_NUMBER' | translate }} </span>
|
||||
<span ng-if="field.fieldType == 'link'"> {{ 'ERROR_URL_INVALID' | translate }} </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="btn btn-lg btn-default col-xs-12 col-sm-4 hidden-xs"
|
||||
style="padding: 4px; margin-top:8px; background: rgba(255,255,255,0.5)">
|
||||
<button ng-disabled="!field.fieldValue || forms.myForm.{{field.fieldType}}{{$index}}.$invalid"
|
||||
ng-style="{'background-color':design.colors.buttonColor, 'color':design.colors.buttonTextColor}"
|
||||
ng-click="$root.nextField()"
|
||||
class="btn col-sm-5 col-xs-5">
|
||||
|
||||
{{ 'OK' | translate }} <i class="fa fa-check"></i>
|
||||
</button>
|
||||
<div class="col-xs-6 col-sm-3" style="margin-top:0.2em">
|
||||
<small style="color:#ddd; font-size:70%">
|
||||
{{ 'ENTER' | translate }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,63 @@
|
||||
<div class="field row radio"
|
||||
ng-click="setActiveField(field._id, index, true)"
|
||||
on-tab-and-shift-key="prevField()"
|
||||
key-to-truthy key-char-truthy="y" key-char-falsey="n" field="field">
|
||||
<div class="col-xs-12 field-title" ng-style="{'color': design.colors.questionColor}">
|
||||
<h3 class="row">
|
||||
<small class="field-number">
|
||||
{{index+1}}
|
||||
<i class="fa fa-angle-double-right" aria-hidden="true"></i>
|
||||
</small>
|
||||
{{field.title}}
|
||||
<span class="required-error" ng-show="!field.required">
|
||||
{{ 'OPTIONAL' | translate }}
|
||||
</span>
|
||||
</h3>
|
||||
<p class="row">
|
||||
{{field.description}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 field-input">
|
||||
<div class="row">
|
||||
<label class="btn btn-default col-md-2 col-sm-3 col-xs-7"
|
||||
style="background: rgba(0,0,0,0.1); text-align:left;">
|
||||
<input type="radio" value="true"
|
||||
class="focusOn"
|
||||
style="opacity: 0; margin-left: 0px;"
|
||||
ng-model="field.fieldValue"
|
||||
ng-focus="setActiveField(field._id, index, true)"
|
||||
ng-model-options="{ debounce: 250 }"
|
||||
ng-required="field.required"
|
||||
ng-change="$root.nextField()"
|
||||
ng-disabled="field.disabled" />
|
||||
<div class="letter">
|
||||
{{ 'Y' | translate }}
|
||||
</div>
|
||||
<span>{{ 'YES' | translate }}</span>
|
||||
<i ng-show="field.fieldValue === 'true'" class="fa fa-check" aria-hidden="true"></i>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="row" style="margin-top: 10px;">
|
||||
<label class="btn btn-default col-md-2 col-sm-3 col-xs-7"
|
||||
style="background: rgba(0,0,0,0.1); text-align:left;">
|
||||
|
||||
<input type="radio" value="false"
|
||||
style="opacity:0; margin-left:0px;"
|
||||
ng-model="field.fieldValue"
|
||||
ng-model-options="{ debounce: 250 }"
|
||||
ng-required="field.required"
|
||||
ng-change="$root.nextField()"
|
||||
ng-disabled="field.disabled"/>
|
||||
|
||||
<div class="letter">
|
||||
{{ 'N' | translate }}
|
||||
</div>
|
||||
<span>{{ 'NO' | translate }}</span>
|
||||
<i ng-show="field.fieldValue === 'false'" class="fa fa-check" aria-hidden="true"></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
@ -0,0 +1,156 @@
|
||||
<section class="overlay submitform" ng-if="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;">
|
||||
{{myform.startPage.introTitle}}
|
||||
</h1>
|
||||
</div>
|
||||
<div class="col-xs-10 col-xs-offset-1 text-center" style="overflow-wrap: break-word;">
|
||||
<p style="color: grey; font-weight: 100; font-size: 16px;">
|
||||
{{myform.startPage.introParagraph}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row form-actions text-center" style="padding: 5px 25px 5px 25px;">
|
||||
<button ng-click="exitStartPage()" class="btn" type="button"
|
||||
ng-style="{'background-color':myform.design.colors.buttonColor, 'color':myform.design.colors.buttonTextColor}">
|
||||
<span style="font-size: 1.6em;">
|
||||
{{myform.startPage.introButtonText}}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="row form-actions" style="padding-bottom:3em; padding-left: 1em; padding-right: 1em;">
|
||||
<p ng-repeat="button in myform.startPage.buttons" class="text-center" style="display:inline;">
|
||||
<button class="btn" style="background-color:rgb(156, 226, 235)" type="button" ng-style="{'background-color':button.bgColor, 'color':button.color}">
|
||||
<a href="{{button.url}}"
|
||||
style="font-size: 1.6em; text-decoration: none;"
|
||||
ng-style="{'color':button.color}">
|
||||
{{button.text}}
|
||||
</a>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</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="row">
|
||||
<form name="forms.myForm"
|
||||
novalidate
|
||||
class="submission-form col-sm-12 col-md-offset-1 col-md-10">
|
||||
|
||||
<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>
|
||||
|
||||
|
||||
</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">
|
||||
<div class="col-sm-5 col-md-6 col-xs-5" ng-show="!myform.submitted">
|
||||
<p class="lead">{{ 'ADVANCEMENT' | translate:translateAdvancementData }}</p>
|
||||
</div>
|
||||
<div class="col-md-6 col-md-offset-0 col-sm-offset-2 col-sm-3 col-xs-offset-1 col-xs-6 row">
|
||||
<div class="col-md-4 col-md-offset-2 hidden-sm hidden-xs">
|
||||
<a href="/#!/forms" class="btn"
|
||||
ng-style="{'background-color':myform.design.colors.buttonColor, 'color':myform.design.colors.buttonTextColor}">
|
||||
{{ 'CREATE_FORM' | translate }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-4 col-sm-10 col-md-offset-0 col-sm-offset-2 col-xs-12 row">
|
||||
<button class="btn btn-lg col-xs-6" id="focusDownButton"
|
||||
ng-style="{'background-color':myform.design.colors.buttonColor, 'color':myform.design.colors.buttonTextColor}"
|
||||
ng-click="nextField()"
|
||||
ng-disabled="selected.index > myform.form_fields.length-1">
|
||||
<i class="fa fa-chevron-down"></i>
|
||||
</button>
|
||||
<button class="btn btn-lg col-xs-6" id="focusUpButton"
|
||||
ng-style="{'background-color':myform.design.colors.buttonColor, 'color':myform.design.colors.buttonTextColor}"
|
||||
ng-click="prevField()"
|
||||
ng-disabled="selected.index == 0">
|
||||
<i class="fa fa-chevron-up"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- End Page View -->
|
||||
<div ng-if="myform.submitted && !loading" class="form-submitted"
|
||||
ng-style="{'color':myform.design.colors.buttonTextColor}"
|
||||
style="padding-top: 5vh;">
|
||||
|
||||
<div class="field row text-center">
|
||||
<div class="col-xs-12 col-sm-12 col-md-6 col-md-offset-3 text-center">{{ 'FORM_SUCCESS' | translate }}</div>
|
||||
</div>
|
||||
<div class="row form-actions">
|
||||
<p class="text-center">
|
||||
<button ng-click="reloadForm()" class="btn" type="button"
|
||||
ng-style="{'background-color':myform.design.colors.buttonColor, 'color':myform.design.colors.buttonTextColor}">
|
||||
<span style="font-size: 1.6em;"> {{ 'BACK_TO_FORM' | translate }}</span>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,17 @@
|
||||
|
||||
|
||||
<section class="public-form" ng-style="{ 'background-color': myform.design.colors.backgroundColor }">
|
||||
<submit-form-directive myform="myform"></submit-form-directive>
|
||||
</section>
|
||||
|
||||
<!-- User's Google Analytics -->
|
||||
<script ng-if="myform.analytics.gaCode">
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', '{{myform.analytics.gaCode}}', 'auto');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
<!-- End Google Analytics -->
|
29
public/form_modules/forms/config/forms.client.config.js
Normal file
29
public/form_modules/forms/config/forms.client.config.js
Normal file
@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
// Configuring the Forms drop-down menus
|
||||
angular.module('view-form').filter('formValidity',
|
||||
function(){
|
||||
return function(formObj){
|
||||
if(formObj && formObj.form_fields && formObj.visible_form_fields){
|
||||
|
||||
//get keys
|
||||
var formKeys = Object.keys(formObj);
|
||||
|
||||
//we only care about things that don't start with $
|
||||
var fieldKeys = formKeys.filter(function(key){
|
||||
return key[0] !== '$';
|
||||
});
|
||||
|
||||
var fields = formObj.form_fields;
|
||||
|
||||
var valid_count = fields.filter(function(field){
|
||||
if(typeof field === 'object' && field.fieldType !== 'statement' && field.fieldType !== 'rating'){
|
||||
return !!(field.fieldValue);
|
||||
}
|
||||
|
||||
}).length;
|
||||
return valid_count - (formObj.form_fields.length - formObj.visible_form_fields.length);
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
});
|
22
public/form_modules/forms/config/forms.client.routes.js
Normal file
22
public/form_modules/forms/config/forms.client.routes.js
Normal file
@ -0,0 +1,22 @@
|
||||
'use strict';
|
||||
|
||||
// Setting up route
|
||||
angular.module('view-form').config(['$stateProvider',
|
||||
|
||||
function($stateProvider) {
|
||||
// Forms state routing
|
||||
$stateProvider.
|
||||
state('submitForm', {
|
||||
url: '/forms/:formId',
|
||||
templateUrl: '/static/form_modules/forms/base/views/submit-form.client.view.html',
|
||||
resolve: {
|
||||
Forms: 'Forms',
|
||||
myForm: function (Forms, $stateParams) {
|
||||
return Forms.get({formId: $stateParams.formId}).$promise;
|
||||
}
|
||||
},
|
||||
controller: 'SubmitFormController',
|
||||
controllerAs: 'ctrl'
|
||||
})
|
||||
}
|
||||
]);
|
@ -0,0 +1,43 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
// Create the SendVisitorData service
|
||||
angular
|
||||
.module('view-form')
|
||||
.factory('SendVisitorData', SendVisitorData);
|
||||
|
||||
SendVisitorData.$inject = ['Socket', '$state'];
|
||||
|
||||
function SendVisitorData(Socket, $state) {
|
||||
|
||||
// Create a controller method for sending visitor data
|
||||
function send(form, lastActiveIndex, timeElapsed) {
|
||||
|
||||
// Create a new message object
|
||||
var visitorData = {
|
||||
referrer: document.referrer,
|
||||
isSubmitted: form.submitted,
|
||||
formId: form._id,
|
||||
lastActiveField: form.form_fields[lastActiveIndex]._id,
|
||||
timeElapsed: timeElapsed
|
||||
};
|
||||
Socket.emit('form-visitor-data', visitorData);
|
||||
}
|
||||
|
||||
function init(){
|
||||
// Make sure the Socket is connected
|
||||
if (!Socket.socket) {
|
||||
Socket.connect();
|
||||
}
|
||||
}
|
||||
|
||||
var service = {
|
||||
send: send
|
||||
};
|
||||
|
||||
init();
|
||||
return service;
|
||||
|
||||
}
|
||||
}());
|
||||
|
@ -0,0 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('view-form').directive('keyToOption', function(){
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
field: '='
|
||||
},
|
||||
link: function($scope, $element, $attrs, $select) {
|
||||
$element.bind('keydown keypress', function(event) {
|
||||
|
||||
var keyCode = event.which || event.keyCode;
|
||||
var index = parseInt(String.fromCharCode(keyCode))-1;
|
||||
//console.log($scope.field);
|
||||
|
||||
if (index < $scope.field.fieldOptions.length) {
|
||||
event.preventDefault();
|
||||
$scope.$apply(function () {
|
||||
$scope.field.fieldValue = $scope.field.fieldOptions[index].option_value;
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
@ -0,0 +1,30 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('view-form').directive('keyToTruthy', ['$rootScope', function($rootScope){
|
||||
return {
|
||||
restrict: 'A',
|
||||
scope: {
|
||||
field: '='
|
||||
},
|
||||
link: function($scope, $element, $attrs) {
|
||||
$element.bind('keydown keypress', function(event) {
|
||||
var keyCode = event.which || event.keyCode;
|
||||
var truthyKeyCode = $attrs.keyCharTruthy.charCodeAt(0) - 32;
|
||||
var falseyKeyCode = $attrs.keyCharFalsey.charCodeAt(0) - 32;
|
||||
|
||||
if(keyCode === truthyKeyCode ) {
|
||||
event.preventDefault();
|
||||
$scope.$apply(function() {
|
||||
$scope.field.fieldValue = 'true';
|
||||
});
|
||||
}else if(keyCode === falseyKeyCode){
|
||||
event.preventDefault();
|
||||
$scope.$apply(function() {
|
||||
$scope.field.fieldValue = 'false';
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}]);
|
||||
|
7
public/form_modules/forms/forms.client.module.js
Normal file
7
public/form_modules/forms/forms.client.module.js
Normal file
@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
// Use Application configuration module to register a new module
|
||||
ApplicationConfiguration.registerModule('view-form', [
|
||||
'ngFileUpload', 'ui.router.tabs', 'ui.date', 'ui.sortable',
|
||||
'angular-input-stars', 'pascalprecht.translate'
|
||||
]);//, 'colorpicker.module' @TODO reactivate this module
|
@ -4,13 +4,13 @@
|
||||
# TEAM
|
||||
|
||||
David Baldwynn -- Developer -- @davidbaldwynn
|
||||
Samuel Laulhau -- Developer -- @_samuel_
|
||||
|
||||
# THANKS
|
||||
|
||||
Grace Lam
|
||||
|
||||
|
||||
# TECHNOLOGY COLOPHON
|
||||
|
||||
HTML5, CSS3, AngularJS
|
||||
jQuery, Modernizr, ExpressJS
|
||||
jQuery, ExpressJS, NodeJS
|
||||
|
@ -7,3 +7,61 @@ angular.module('core').config(['$stateProvider', '$urlRouterProvider',
|
||||
$urlRouterProvider.otherwise('/forms');
|
||||
}
|
||||
]);
|
||||
|
||||
angular.module(ApplicationConfiguration.applicationModuleName).run(['$rootScope', 'Auth', '$state', '$stateParams',
|
||||
function($rootScope, Auth, $state, $stateParams) {
|
||||
|
||||
$rootScope.$state = $state;
|
||||
$rootScope.$stateParams = $stateParams;
|
||||
|
||||
// add previous state property
|
||||
$rootScope.$on('$stateChangeSuccess', function (event, toState, toParams, fromState) {
|
||||
$state.previous = fromState;
|
||||
//console.log('toState: '+toState.name);
|
||||
|
||||
var statesToIgnore = ['home', 'signin', 'resendVerifyEmail', 'verify', 'signup', 'signup-success', 'forgot', 'reset-invalid', 'reset', 'reset-success'];
|
||||
|
||||
//Redirect to listForms if user is authenticated
|
||||
if(statesToIgnore.indexOf(toState.name) > 0){
|
||||
if(Auth.isAuthenticated()){
|
||||
event.preventDefault(); // stop current execution
|
||||
//console.log('go to forms');
|
||||
$state.go('listForms'); // go to listForms page
|
||||
}
|
||||
}
|
||||
//Redirect to 'signup' route if user is not authenticated
|
||||
else if(toState.name !== 'access_denied' && !Auth.isAuthenticated() && toState.name !== 'submitForm'){
|
||||
console.log('go to signup');
|
||||
event.preventDefault(); // stop current execution
|
||||
$state.go('listForms'); // go to listForms page
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
]);
|
||||
|
||||
//Page access/authorization logic
|
||||
angular.module(ApplicationConfiguration.applicationModuleName).run(['$rootScope', 'Auth', 'User', 'Authorizer', '$state', '$stateParams',
|
||||
function($rootScope, Auth, User, Authorizer, $state, $stateParams) {
|
||||
$rootScope.$on('$stateChangeStart', function(event, next) {
|
||||
var authenticator, permissions, user;
|
||||
permissions = next && next.data && next.data.permissions ? next.data.permissions : null;
|
||||
|
||||
Auth.ensureHasCurrentUser(User);
|
||||
user = Auth.currentUser;
|
||||
|
||||
if(user){
|
||||
authenticator = new Authorizer(user);
|
||||
//console.log('access denied: '+!authenticator.canAccess(permissions));
|
||||
//console.log(permissions);
|
||||
if( (permissions !== null) ){
|
||||
if( !authenticator.canAccess(permissions) ){
|
||||
event.preventDefault();
|
||||
//console.log('access denied');
|
||||
$state.go('access_denied');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}]);
|
||||
|
9
public/modules/core/services/subdomain.client.service.js
Normal file
9
public/modules/core/services/subdomain.client.service.js
Normal file
@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
angular.module('core').factory('subdomain', ['$location', function ($location) {
|
||||
var host = $location.host();
|
||||
if (host.indexOf('.') < 0)
|
||||
return null;
|
||||
else
|
||||
return host.split('.')[0];
|
||||
}]);
|
@ -11,6 +11,8 @@ angular.module('forms').controller('AdminFormController', ['$rootScope', '$scope
|
||||
|
||||
CurrentForm.setForm($scope.myform);
|
||||
|
||||
$scope.formURL = $scope.myform.admin.username + '.' + window.location.host;
|
||||
|
||||
$scope.tabData = [
|
||||
{
|
||||
heading: $filter('translate')('CREATE_TAB'),
|
||||
|
@ -8,7 +8,7 @@ angular.module('forms').controller('ListFormsController', ['$rootScope', '$scope
|
||||
$scope.forms = {};
|
||||
$scope.showCreateModal = false;
|
||||
|
||||
$scope.languageRegExp = $scope.myPt = {
|
||||
$rootScope.languageRegExp = {
|
||||
regExp: /[@!#$%^&*()\-+={}\[\]|\\/'";:`.,~№?<>]+/i,
|
||||
test: function(val) {
|
||||
return !this.regExp.test(val);
|
||||
|
@ -63,7 +63,7 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
|
||||
completion: 0,
|
||||
average_time: 0,
|
||||
total_time: 0
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
var stats = {
|
||||
|
@ -43,7 +43,7 @@
|
||||
</div>
|
||||
<div class="col-xs-1 col-sm-2">
|
||||
<small class="pull-right">
|
||||
<a class="btn btn-default view-form-btn" href="/#!/forms/{{myform._id}}">
|
||||
<a class="btn btn-default view-form-btn" href="//{{formURL}}/#!/forms/{{myform._id}}">
|
||||
<span class="hidden-xs hidden-sm">
|
||||
{{ 'VIEW' | translate }}
|
||||
<span ng-show="myform.isLive">
|
||||
|
@ -9,22 +9,7 @@ angular.module('forms').config(['$stateProvider',
|
||||
state('listForms', {
|
||||
url: '/forms',
|
||||
templateUrl: 'modules/forms/admin/views/list-forms.client.view.html'
|
||||
}).
|
||||
state('submitForm', {
|
||||
url: '/forms/:formId',
|
||||
templateUrl: 'modules/forms/base/views/submit-form.client.view.html',
|
||||
data: {
|
||||
hideNav: true
|
||||
},
|
||||
resolve: {
|
||||
Forms: 'Forms',
|
||||
myForm: function (Forms, $stateParams) {
|
||||
return Forms.get({formId: $stateParams.formId}).$promise;
|
||||
}
|
||||
},
|
||||
controller: 'SubmitFormController',
|
||||
controllerAs: 'ctrl'
|
||||
}).state('viewForm', {
|
||||
}).state('viewForm', {
|
||||
url: '/forms/:formId/admin',
|
||||
templateUrl: 'modules/forms/admin/views/admin-form.client.view.html',
|
||||
data: {
|
||||
|
@ -15,6 +15,11 @@ angular.module('users').config(['$translateProvider', function ($translateProvid
|
||||
LANGUAGE_LABEL: 'Language',
|
||||
EMAIL_LABEL: 'Email',
|
||||
|
||||
SIGNUP_HEADER_TEXT: 'Sign up with your email',
|
||||
SIGNIN_HEADER_TEXT: 'Signup in with your email',
|
||||
|
||||
SIGNUP_ERROR_TEXT: 'Couldn\'t complete registration due to errors',
|
||||
|
||||
UPDATE_PROFILE_BTN: 'Update Profile',
|
||||
PROFILE_SAVE_SUCCESS: 'Profile saved successfully',
|
||||
PROFILE_SAVE_ERROR: 'Could\'t Save Your Profile.',
|
||||
|
@ -8,7 +8,6 @@ angular.module('users').controller('AuthenticationController', ['$scope', '$loca
|
||||
$scope.error = '';
|
||||
|
||||
$scope.signin = function() {
|
||||
$scope.credentials.email = $scope.credentials.username;
|
||||
User.login($scope.credentials).then(
|
||||
function(response) {
|
||||
Auth.login(response);
|
||||
|
@ -1,5 +1,5 @@
|
||||
<section class="row auth" data-ng-controller="AuthenticationController">
|
||||
<h3 class="col-md-12 text-center">Sign into your account</h3>
|
||||
<h3 class="col-md-12 text-center">{{ 'SIGNIN_HEADER_TEXT' | translate }}</h3>
|
||||
<!-- <div class="col-md-12 text-center">
|
||||
<a href="/auth/facebook" class="undecorated-link">
|
||||
<img src="/modules/users/img/buttons/facebook.png">
|
||||
@ -25,8 +25,8 @@
|
||||
Error: <strong data-ng-bind="error"></strong>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="username">{{ 'EMAIL_LABEL' | translate }}</label>
|
||||
<input type="text" id="username" name="username" class="form-control" data-ng-model="credentials.username" placeholder="{{ 'EMAIL_LABEL' | translate }}">
|
||||
<label for="username">{{ 'USERNAME_LABEL' | translate }}</label>
|
||||
<input type="text" id="username" name="username" class="form-control" data-ng-model="credentials.username" placeholder="{{ 'USERNAME_LABEL' | translate }}" ng-pattern="languageRegExp" ng-minlength="4">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">{{ 'PASSWORD_LABEL' | translate }}</label>
|
||||
@ -46,8 +46,6 @@
|
||||
</span>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -17,12 +17,12 @@
|
||||
<img src="/modules/users/img/buttons/github.png">
|
||||
</a>
|
||||
</div> -->
|
||||
<h3 class="col-md-12 text-center">Signup with your email</h3>
|
||||
<h3 class="col-md-12 text-center">{{ 'SIGNUP_HEADER_TEXT' | translate }}</h3>
|
||||
<div class="col-xs-offset-2 col-xs-8 col-md-offset-3 col-md-6">
|
||||
<form name="userForm" data-ng-submit="signup()" class="signin form-horizontal" novalidate autocomplete="off">
|
||||
<form name="userForm" data-ng-submit="signup()" class="signin form-horizontal" autocomplete="off">
|
||||
<fieldset>
|
||||
<div data-ng-show="error" id="signup_errors" class="text-center text-danger">
|
||||
Couldn't complete registration due to errors: <br>
|
||||
{{'SIGNUP_ERROR_TEXT' | translate}}: <br>
|
||||
<strong data-ng-bind="error"></strong>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@ -34,10 +34,10 @@
|
||||
<input type="text" ng-pattern="/^[a-zA-Z0-9 \-.]*$/" required id="lastName" name="lastName" class="form-control" ng-model="credentials.lastName" placeholder="Last Name">
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<div class="col-xs-7 field-title">
|
||||
<div class="field-title">
|
||||
<b>{{ 'LANGUAGE_LABEL' | translate }}</b>
|
||||
</div>
|
||||
<div class="col-xs-12 field-input">
|
||||
<div class="field-input">
|
||||
<select ng-model="user.language" required>
|
||||
<option ng-repeat="language in languages"
|
||||
ng-selected="language == user.language"
|
||||
@ -48,6 +48,10 @@
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="form-group">
|
||||
<label for="username">{{ 'USERNAME_LABEL' | translate }}</label>
|
||||
<input type="text" id="username" name="username" class="form-control" ng-pattern="languageRegExp" ng-minlength="4" ng-model="credentials.username" placeholder="Username">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">{{ 'EMAIL_LABEL' | translate }}</label>
|
||||
<input type="email" id="email" name="email" class="form-control" ng-model="credentials.email" placeholder="Email">
|
||||
|
@ -1,4 +1,4 @@
|
||||
<section class="row auth" data-ng-controller="SettingsController" >
|
||||
1<section class="row auth" data-ng-controller="SettingsController" >
|
||||
<h3 class="col-xs-offset-1 col-xs-10 text-center">Edit your profile</h3>
|
||||
<div class="col-xs-offset-3 col-xs-6">
|
||||
<form name="userForm" data-ng-submit="updateUserProfile(userForm.$valid)" class="signin form-horizontal" autocomplete="off">
|
||||
@ -45,6 +45,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row form-group">
|
||||
<div class="col-xs-7 field-title">
|
||||
<b>Username</b>
|
||||
</div>
|
||||
<div class="col-xs-12 field-input">
|
||||
<input type="text" id="username" name="username" class="form-control" data-ng-model="user.username" placeholder="Username">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row form-group">
|
||||
<div class="col-xs-7 field-title">
|
||||
<b>{{ 'EMAIL_LABEL' | translate }}</b>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<form data-ng-submit="resendVerifyEmail()" class="signin form-horizontal" autocomplete="off">
|
||||
<fieldset>
|
||||
<div class="form-group">
|
||||
<input type="text" id="username" name="email" class="form-control" data-ng-model="credentials.email" placeholder="bob@example.com">
|
||||
<input type="text" id="email" name="email" class="form-control" data-ng-model="credentials.email" placeholder="bob@example.com">
|
||||
</div>
|
||||
<div class="text-center form-group">
|
||||
<button type="submit" class="btn btn-primary" ng-click="resendVerifyEmail()">{{ 'SUBMIT_BTN' | translate }}</button>
|
||||
|
Loading…
Reference in New Issue
Block a user