Merge branch 'subdomains' into stage

This commit is contained in:
David Baldwynn 2016-07-04 16:37:54 -07:00
commit 77893b0c4e
73 changed files with 4074 additions and 555 deletions

View file

@ -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
});
};

View file

@ -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'
});*/
};
/**

View file

@ -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) {

View file

@ -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) {

View file

@ -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

View file

@ -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;

View file

@ -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);
};

View 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>

View file

@ -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 }};

View file

@ -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
View file

@ -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'
]
}
};

View file

@ -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',

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

View file

@ -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);
});

View file

@ -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",

View file

@ -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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

View 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'
]);

View 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 dont accept',
DELETE: 'Delete',
CANCEL: 'Cancel',
SUBMIT: 'Submit',
UPLOAD_FILE: 'Upload your File',
});
$translateProvider.preferredLanguage('english')
.fallbackLanguage('english')
.useSanitizeValueStrategy('escape');
}]);

View 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: 'Jaccepte',
LEGAL_NO_ACCEPT: 'Je naccepte pas',
DELETE: 'Supprimer',
CANCEL: 'Réinitialiser',
SUBMIT: 'Enregistrer',
UPLOAD_FILE: 'Envoyer un fichier',
Y: 'O',
N: 'N',
});
}]);

View 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 dont accept',
DELETE: 'Entfernen',
CANCEL: 'Canceln',
SUBMIT: 'Speichern',
UPLOAD_FILE: 'Datei versenden',
Y: 'J',
N: 'N',
});
}]);

View 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 dont accept',
DELETE: 'Cancella',
CANCEL: 'Reset',
SUBMIT: 'Registra',
UPLOAD_FILE: 'Invia un file',
Y: 'S',
N: 'N',
});
}]);

View 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 dont accept',
DELETE: 'Eliminar',
CANCEL: 'Cancelar',
SUBMIT: 'Registrar',
UPLOAD_FILE: 'Cargar el archivo',
Y: 'S',
N: 'N'
});
}]);

View file

@ -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);
}
]);

View 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;
}

View file

@ -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];
}
};
});

View file

@ -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);
}
};
}]);

View file

@ -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);
});
}
});
}
};
}]);

View file

@ -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');
});
}
}
};
});

View file

@ -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();
}
};
}
]);

View file

@ -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;
};
}
);

View file

@ -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'
}
});
}
]);

View file

@ -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);
}
}
}
}());

View file

@ -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;
};
}
]);

View file

@ -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>

View 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>

View 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>

View 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>

View file

@ -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>

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

View file

@ -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>

View 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>

View file

@ -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>

View file

@ -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>

View 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>

View 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>

View file

@ -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>

View file

@ -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>

View file

@ -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 -->

View 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;
};
});

View 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'
})
}
]);

View file

@ -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;
}
}());

View file

@ -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;
});
}
});
}
};
});

View file

@ -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';
});
}
});
}
};
}]);

View 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

View file

@ -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

View file

@ -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');
}
}
}
});
}]);

View 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];
}]);

View file

@ -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'),

View file

@ -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);

View file

@ -63,7 +63,7 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope',
completion: 0,
average_time: 0,
total_time: 0
}
};
};
var stats = {

View file

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

View file

@ -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: {

View file

@ -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.',

View file

@ -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);

View file

@ -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>

View file

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

View file

@ -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>

View file

@ -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>