From 2876c69a5aa55d7f0d25613fc6c712551ba33b45 Mon Sep 17 00:00:00 2001 From: David Baldwynn Date: Wed, 8 Jun 2016 20:32:33 -0700 Subject: [PATCH 1/5] got subdomains to work --- app/controllers/core.server.controller.js | 7 + app/controllers/forms.server.controller.js | 15 +- .../users.authentication.server.controller.js | 2 - .../users/users.profile.server.controller.js | 1 - app/models/user.server.model.js | 1 - app/routes/core.server.routes.js | 9 + app/views/form.server.view.html | 133 +++++ app/views/layout.server.view.html | 14 +- config/config.js | 34 +- config/env/all.js | 25 + config/express.js | 67 ++- public/application.js | 60 +- .../forms/base/config/forms.client.config.js | 45 ++ .../forms/base/config/i18n/english.js | 37 ++ .../forms/base/config/i18n/french.js | 35 ++ .../forms/base/config/i18n/german.js | 35 ++ .../forms/base/config/i18n/italian.js | 35 ++ .../forms/base/config/i18n/spanish.js | 35 ++ .../submit-form.client.controller.js | 12 + public/form_modules/forms/base/css/form.css | 557 ++++++++++++++++++ .../directives/field-icon.client.directive.js | 33 ++ .../base/directives/field.client.directive.js | 108 ++++ .../on-enter-key.client.directive.js | 76 +++ .../on-finish-render.client.directive.js | 27 + .../submit-form.client.directive.js | 239 ++++++++ .../services/current-form.client.service.js | 18 + .../base/services/forms.client.service.js | 42 ++ .../base/services/socket.io.client.service.js | 54 ++ .../services/time-counter.client.service.js | 38 ++ .../directiveViews/entryPage/startPage.html | 24 + .../base/views/directiveViews/field/date.html | 33 ++ .../views/directiveViews/field/dropdown.html | 36 ++ .../directiveViews/field/dropdown.html~HEAD | 27 + .../base/views/directiveViews/field/file.html | 38 ++ .../views/directiveViews/field/hidden.html | 1 + .../views/directiveViews/field/legal.html | 55 ++ .../views/directiveViews/field/radio.html | 42 ++ .../views/directiveViews/field/rating.html | 34 ++ .../views/directiveViews/field/statement.html | 24 + .../views/directiveViews/field/textarea.html | 49 ++ .../views/directiveViews/field/textfield.html | 65 ++ .../views/directiveViews/field/yes_no.html | 63 ++ .../form/submit-form.client.view.html | 156 +++++ .../base/views/submit-form.client.view.html | 17 + .../forms/config/forms.client.config.js | 29 + .../forms/config/forms.client.routes.js | 22 + .../analytics-service.client.directive.js | 43 ++ .../key-to-option.client.directive.js | 26 + .../key-to-truthy.client.directive.js | 30 + .../form_modules/forms/forms.client.module.js | 7 + .../admin-form.client.controller.test.js | 256 ++++++++ .../list-forms.client.controller.test.js | 224 +++++++ .../submit-form.client.controller.test.js | 230 ++++++++ .../configure-form.client.directive.test.js | 135 +++++ ...-form-submissions.client.directive.test.js | 200 +++++++ .../edit-form.client.directive.test.js | 206 +++++++ .../entry-page.client.directive.test.js | 93 +++ .../field-icon.client.directive.test.js | 54 ++ .../directives/field.client.directive.test.js | 97 +++ .../on-finish-render.client.directive.test.js | 46 ++ .../submit-form.client.directive.test.js | 203 +++++++ .../current-form.client.service.test.js | 54 ++ .../time-counter.client.service.test.js | 28 + .../forms/tests/unit/stateMock.js | 32 + public/humans.txt | 4 +- .../modules/core/config/core.client.routes.js | 58 ++ .../core/services/subdomain.client.service.js | 9 + .../authentication.client.controller.js | 1 - .../authentication/signin.client.view.html | 2 +- .../authentication/signup.client.view.html | 4 + .../settings/edit-profile.client.view.html | 14 +- .../resend-verify-email.client.view.html | 7 +- 72 files changed, 4476 insertions(+), 96 deletions(-) create mode 100644 app/views/form.server.view.html create mode 100644 public/form_modules/forms/base/config/forms.client.config.js create mode 100644 public/form_modules/forms/base/config/i18n/english.js create mode 100644 public/form_modules/forms/base/config/i18n/french.js create mode 100644 public/form_modules/forms/base/config/i18n/german.js create mode 100644 public/form_modules/forms/base/config/i18n/italian.js create mode 100644 public/form_modules/forms/base/config/i18n/spanish.js create mode 100644 public/form_modules/forms/base/controllers/submit-form.client.controller.js create mode 100644 public/form_modules/forms/base/css/form.css create mode 100644 public/form_modules/forms/base/directives/field-icon.client.directive.js create mode 100644 public/form_modules/forms/base/directives/field.client.directive.js create mode 100644 public/form_modules/forms/base/directives/on-enter-key.client.directive.js create mode 100644 public/form_modules/forms/base/directives/on-finish-render.client.directive.js create mode 100644 public/form_modules/forms/base/directives/submit-form.client.directive.js create mode 100644 public/form_modules/forms/base/services/current-form.client.service.js create mode 100644 public/form_modules/forms/base/services/forms.client.service.js create mode 100644 public/form_modules/forms/base/services/socket.io.client.service.js create mode 100644 public/form_modules/forms/base/services/time-counter.client.service.js create mode 100644 public/form_modules/forms/base/views/directiveViews/entryPage/startPage.html create mode 100755 public/form_modules/forms/base/views/directiveViews/field/date.html create mode 100755 public/form_modules/forms/base/views/directiveViews/field/dropdown.html create mode 100755 public/form_modules/forms/base/views/directiveViews/field/dropdown.html~HEAD create mode 100644 public/form_modules/forms/base/views/directiveViews/field/file.html create mode 100755 public/form_modules/forms/base/views/directiveViews/field/hidden.html create mode 100644 public/form_modules/forms/base/views/directiveViews/field/legal.html create mode 100755 public/form_modules/forms/base/views/directiveViews/field/radio.html create mode 100644 public/form_modules/forms/base/views/directiveViews/field/rating.html create mode 100644 public/form_modules/forms/base/views/directiveViews/field/statement.html create mode 100755 public/form_modules/forms/base/views/directiveViews/field/textarea.html create mode 100755 public/form_modules/forms/base/views/directiveViews/field/textfield.html create mode 100644 public/form_modules/forms/base/views/directiveViews/field/yes_no.html create mode 100755 public/form_modules/forms/base/views/directiveViews/form/submit-form.client.view.html create mode 100644 public/form_modules/forms/base/views/submit-form.client.view.html create mode 100644 public/form_modules/forms/config/forms.client.config.js create mode 100644 public/form_modules/forms/config/forms.client.routes.js create mode 100644 public/form_modules/forms/directives/analytics-service.client.directive.js create mode 100644 public/form_modules/forms/directives/key-to-option.client.directive.js create mode 100644 public/form_modules/forms/directives/key-to-truthy.client.directive.js create mode 100644 public/form_modules/forms/forms.client.module.js create mode 100644 public/form_modules/forms/tests/unit/controllers/admin-form.client.controller.test.js create mode 100644 public/form_modules/forms/tests/unit/controllers/list-forms.client.controller.test.js create mode 100644 public/form_modules/forms/tests/unit/controllers/submit-form.client.controller.test.js create mode 100644 public/form_modules/forms/tests/unit/directives/configure-form.client.directive.test.js create mode 100644 public/form_modules/forms/tests/unit/directives/edit-form-submissions.client.directive.test.js create mode 100644 public/form_modules/forms/tests/unit/directives/edit-form.client.directive.test.js create mode 100644 public/form_modules/forms/tests/unit/directives/entry-page.client.directive.test.js create mode 100644 public/form_modules/forms/tests/unit/directives/field-icon.client.directive.test.js create mode 100644 public/form_modules/forms/tests/unit/directives/field.client.directive.test.js create mode 100644 public/form_modules/forms/tests/unit/directives/on-finish-render.client.directive.test.js create mode 100644 public/form_modules/forms/tests/unit/directives/submit-form.client.directive.test.js create mode 100644 public/form_modules/forms/tests/unit/services/current-form.client.service.test.js create mode 100644 public/form_modules/forms/tests/unit/services/time-counter.client.service.test.js create mode 100644 public/form_modules/forms/tests/unit/stateMock.js create mode 100644 public/modules/core/services/subdomain.client.service.js diff --git a/app/controllers/core.server.controller.js b/app/controllers/core.server.controller.js index 5d5c6e16..eb620c15 100755 --- a/app/controllers/core.server.controller.js +++ b/app/controllers/core.server.controller.js @@ -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 + }); +}; diff --git a/app/controllers/forms.server.controller.js b/app/controllers/forms.server.controller.js index d800f827..a6825169 100644 --- a/app/controllers/forms.server.controller.js +++ b/app/controllers/forms.server.controller.js @@ -292,7 +292,20 @@ exports.read = function(req, res) { var newForm = req.form.toJSON({virtuals : true}); newForm.plugins.oscarhost.settings.validUpdateTypes = validUpdateTypes; - 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' + }); + } + + return res.status(404).send({ + message: 'Form Does Not Exist' + }); + }; /** diff --git a/app/controllers/users/users.authentication.server.controller.js b/app/controllers/users/users.authentication.server.controller.js index 8a89a8d5..cec35c0b 100755 --- a/app/controllers/users/users.authentication.server.controller.js +++ b/app/controllers/users/users.authentication.server.controller.js @@ -99,14 +99,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) { diff --git a/app/controllers/users/users.profile.server.controller.js b/app/controllers/users/users.profile.server.controller.js index dc2d0132..56089804 100755 --- a/app/controllers/users/users.profile.server.controller.js +++ b/app/controllers/users/users.profile.server.controller.js @@ -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) { diff --git a/app/models/user.server.model.js b/app/models/user.server.model.js index f2b6739d..65098f12 100755 --- a/app/models/user.server.model.js +++ b/app/models/user.server.model.js @@ -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; diff --git a/app/routes/core.server.routes.js b/app/routes/core.server.routes.js index 71388226..fd2006f3 100755 --- a/app/routes/core.server.routes.js +++ b/app/routes/core.server.routes.js @@ -1,7 +1,16 @@ 'use strict'; +/** + * Module dependencies. + */ +var forms = require('../../app/controllers/forms.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); }; diff --git a/app/views/form.server.view.html b/app/views/form.server.view.html new file mode 100644 index 00000000..ec1cb129 --- /dev/null +++ b/app/views/form.server.view.html @@ -0,0 +1,133 @@ + + + + + {{title}} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% for bowerCssFile in bowerCssFiles %} + + {% endfor %} + + + + + + + + + {% for cssFile in cssFiles %} + + {% endfor %} + + + + + + + + + +
+
+
+ + + + + + + + + + + + +{% for bowerJSFile in bowerJSFiles %} + +{% endfor %} + + + + +{% for jsFile in formJSFiles %} + +{% endfor %} + + +{% if process.env.NODE_ENV === 'development' %} + + +{% endif %} + + + + + + + + + + + + diff --git a/app/views/layout.server.view.html b/app/views/layout.server.view.html index 212a3b84..bbec12de 100755 --- a/app/views/layout.server.view.html +++ b/app/views/layout.server.view.html @@ -31,16 +31,16 @@ - + - + {% for bowerCssFile in bowerCssFiles %} {% endfor %} - - - + + + + diff --git a/public/form_modules/forms/config/forms.client.config.js b/public/form_modules/forms/config/forms.client.config.js new file mode 100644 index 00000000..188c56e5 --- /dev/null +++ b/public/form_modules/forms/config/forms.client.config.js @@ -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; + }; +}); diff --git a/public/form_modules/forms/config/forms.client.routes.js b/public/form_modules/forms/config/forms.client.routes.js new file mode 100644 index 00000000..b0e0efb5 --- /dev/null +++ b/public/form_modules/forms/config/forms.client.routes.js @@ -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' + }) + } +]); diff --git a/public/form_modules/forms/directives/analytics-service.client.directive.js b/public/form_modules/forms/directives/analytics-service.client.directive.js new file mode 100644 index 00000000..2015eb2e --- /dev/null +++ b/public/form_modules/forms/directives/analytics-service.client.directive.js @@ -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; + + } +}()); + diff --git a/public/form_modules/forms/directives/key-to-option.client.directive.js b/public/form_modules/forms/directives/key-to-option.client.directive.js new file mode 100644 index 00000000..2a4a7de8 --- /dev/null +++ b/public/form_modules/forms/directives/key-to-option.client.directive.js @@ -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; + }); + } + + }); + } + }; +}); diff --git a/public/form_modules/forms/directives/key-to-truthy.client.directive.js b/public/form_modules/forms/directives/key-to-truthy.client.directive.js new file mode 100644 index 00000000..175bf36d --- /dev/null +++ b/public/form_modules/forms/directives/key-to-truthy.client.directive.js @@ -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'; + }); + } + }); + } + }; +}]); + diff --git a/public/form_modules/forms/forms.client.module.js b/public/form_modules/forms/forms.client.module.js new file mode 100644 index 00000000..c56beb5f --- /dev/null +++ b/public/form_modules/forms/forms.client.module.js @@ -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 diff --git a/public/form_modules/forms/tests/unit/controllers/admin-form.client.controller.test.js b/public/form_modules/forms/tests/unit/controllers/admin-form.client.controller.test.js new file mode 100644 index 00000000..7530812f --- /dev/null +++ b/public/form_modules/forms/tests/unit/controllers/admin-form.client.controller.test.js @@ -0,0 +1,256 @@ +'use strict'; + +(function() { + // Forms Controller Spec + describe('AdminForm Controller Tests', function() { + // Initialize global variables + var AdminFormController, + createAdminFormController, + scope, + $httpBackend, + $stateParams, + $location, + $state; + + var sampleUser = { + firstName: 'Full', + lastName: 'Name', + email: 'test@test.com', + username: 'test@test.com', + password: 'password', + provider: 'local', + roles: ['user'], + _id: 'ed873933b1f1dea0ce12fab9' + }; + + var sampleForm = { + title: 'Form Title', + admin: 'ed873933b1f1dea0ce12fab9', + language: 'english', + form_fields: [ + {fieldType:'textfield', title:'First Name', fieldValue: '', deletePreserved: false, _id:'56340745f59a6fc9e22028e9'}, + {fieldType:'checkbox', title:'nascar', fieldValue: '', deletePreserved: false, _id:'5c9e22028e907634f45f59a6'}, + {fieldType:'checkbox', title:'hockey', fieldValue: '', deletePreserved: false, _id:'56e90745f5934fc9e22028a6'} + ], + _id: '525a8422f6d0f87f0e407a33' + }; + + var expectedForm = { + title: 'Form Title', + admin: 'ed873933b1f1dea0ce12fab9', + language: 'english', + form_fields: [ + {fieldType:'textfield', title:'First Name', fieldValue: '', deletePreserved: false, _id:'56340745f59a6fc9e22028e9'}, + {fieldType:'checkbox', title:'nascar', fieldValue: '', deletePreserved: false, _id:'5c9e22028e907634f45f59a6'}, + {fieldType:'checkbox', title:'hockey', fieldValue: '', deletePreserved: false, _id:'56e90745f5934fc9e22028a6'} + ], + visible_form_fields: [ + {fieldType:'textfield', title:'First Name', fieldValue: '', deletePreserved: false, _id:'56340745f59a6fc9e22028e9'}, + {fieldType:'checkbox', title:'nascar', fieldValue: '', deletePreserved: false, _id:'5c9e22028e907634f45f59a6'}, + {fieldType:'checkbox', title:'hockey', fieldValue: '', deletePreserved: false, _id:'56e90745f5934fc9e22028a6'} + ], + _id: '525a8422f6d0f87f0e407a33' + }; + + var newFakeModal = function(){ + var result = { + opened: true, + result: { + then: function(confirmCallback, cancelCallback) { + //Store the callbacks for later when the user clicks on the OK or Cancel button of the dialog + this.confirmCallBack = confirmCallback; + this.cancelCallback = cancelCallback; + } + }, + close: function( item ) { + //The user clicked OK on the modal dialog, call the stored confirm callback with the selected item + this.opened = false; + this.result.confirmCallBack( item ); + }, + dismiss: function( type ) { + //The user clicked cancel on the modal dialog, call the stored cancel callback + this.opened = false; + this.result.cancelCallback( type ); + } + }; + return result; + }; + + //Mock Users Service + beforeEach(module(function($provide) { + $provide.service('myForm', function($q) { + return sampleForm; + }); + })); + + + // The $resource service augments the response object with methods for updating and deleting the resource. + // If we were to use the standard toEqual matcher, our tests would fail because the test values would not match + // the responses exactly. To solve the problem, we define a new toEqualData Jasmine matcher. + // When the toEqualData matcher compares two objects, it takes only object properties into + // account and ignores methods. + beforeEach(function() { + jasmine.addMatchers({ + toEqualData: function(util, customEqualityTesters) { + return { + compare: function(actual, expected) { + return { + pass: angular.equals(actual, expected) + }; + } + }; + } + }); + }); + + // Load the main application module + beforeEach(module(ApplicationConfiguration.applicationModuleName)); + + beforeEach(module('stateMock')); + + //Mock Users Service + beforeEach(module(function($provide) { + $provide.service('User', function($q) { + return { + getCurrent: function() { + var deferred = $q.defer(); + deferred.resolve( JSON.stringify(sampleUser) ); + return deferred.promise; + }, + login: function(credentials) { + var deferred = $q.defer(); + if( credentials.password === sampleUser.password && credentials.username === sampleUser.username){ + deferred.resolve( JSON.stringify(sampleUser) ); + }else { + deferred.resolve('Error: User could not be loggedin'); + } + + return deferred.promise; + }, + logout: function() { + var deferred = $q.defer(); + deferred.resolve(null); + return deferred.promise; + }, + signup: function(credentials) { + var deferred = $q.defer(); + if( credentials.password === sampleUser.password && credentials.username === sampleUser.username){ + deferred.resolve( JSON.stringify(sampleUser) ); + }else { + deferred.resolve('Error: User could not be signed up'); + } + + return deferred.promise; + } + }; + }); + })); + + //Mock Authentication Service + beforeEach(module(function($provide) { + $provide.service('Auth', function() { + return { + ensureHasCurrentUser: function() { + return sampleUser; + }, + isAuthenticated: function() { + return true; + }, + getUserState: function() { + return true; + } + }; + }); + })); + + + //Mock $uibModal + beforeEach(inject(function($uibModal) { + var modal = newFakeModal(); + spyOn($uibModal, 'open').and.returnValue(modal); + //spyOn($uibModal, 'close').and.callFake(modal.close()); + })); + + // The injector ignores leading and trailing underscores here (i.e. _$httpBackend_). + // This allows us to inject a service but then attach it to a variable + // with the same name as the service. + beforeEach(inject(function($controller, $rootScope, _$state_, _$location_, _$stateParams_, _$httpBackend_, CurrentForm, Forms) { + // Set a new global scope + scope = $rootScope.$new(); + + //Set CurrentForm + CurrentForm.setForm(sampleForm); + + // Point global variables to injected services + $stateParams = _$stateParams_; + $httpBackend = _$httpBackend_; + $location = _$location_; + $state = _$state_; + + $httpBackend.whenGET(/\.html$/).respond(''); + $httpBackend.whenGET('/users/me/').respond(''); + + // Initialize the Forms controller. + createAdminFormController = function(){ + return $controller('AdminFormController', { $scope: scope }); + }; + })); + + it('AdminFormController should fetch current Form when instantiated', function() { + // Run controller functionality + var controller = createAdminFormController(); + + // Test scope value + expect(scope.myform).toEqualData(sampleForm); + }); + + it('$scope.removeCurrentForm() with valid form data should send a DELETE request with the id of form', function() { + var controller = createAdminFormController(); + + //Set $state transition + $state.expectTransitionTo('listForms'); + + // Set DELETE response + $httpBackend.expect('DELETE', /^(\/forms\/)([0-9a-fA-F]{24})$/).respond(200, sampleForm); + + //Run controller functionality + scope.openDeleteModal(); + scope.removeCurrentForm(); + + $httpBackend.flush(); + $state.ensureAllTransitionsHappened(); + }); + + it('$scope.update() should send a PUT request with the id of form', function() { + var controller = createAdminFormController(); + + //Set PUT response + $httpBackend.expect('PUT', /^(\/forms\/)([0-9a-fA-F]{24})$/).respond(200, sampleForm); + + //Run controller functionality + scope.update(false, null); + + $httpBackend.flush(); + }); + + it('$scope.openDeleteModal() should open scope.deleteModal', function() { + var controller = createAdminFormController(); + + //Run controller functionality + scope.openDeleteModal(); + console.log(scope.deleteModal); + expect(scope.deleteModal.opened).toEqual(true); + }); + + it('$scope.cancelDeleteModal() should close $scope.deleteModal', inject(function($uibModal) { + var controller = createAdminFormController(); + + //Run controller functionality + scope.openDeleteModal(); + + //Run controller functionality + scope.cancelDeleteModal(); + expect( scope.deleteModal.opened ).toEqual(false); + })); + }); +}()); \ No newline at end of file diff --git a/public/form_modules/forms/tests/unit/controllers/list-forms.client.controller.test.js b/public/form_modules/forms/tests/unit/controllers/list-forms.client.controller.test.js new file mode 100644 index 00000000..c89e73cf --- /dev/null +++ b/public/form_modules/forms/tests/unit/controllers/list-forms.client.controller.test.js @@ -0,0 +1,224 @@ +'use strict'; + +(function() { + // Forms Controller Spec + describe('ListForms Controller Tests', function() { + // Initialize global variables + var ListFormsController, + createListFormsController, + scope, + $httpBackend, + $stateParams, + $location, + $state; + + var sampleForm = { + title: 'Form Title', + admin: 'ed873933b1f1dea0ce12fab9', + language: 'english', + form_fields: [ + {fieldType:'textfield', title:'First Name', fieldValue: '', deletePreserved: false}, + {fieldType:'checkbox', title:'nascar', fieldValue: '', deletePreserved: false}, + {fieldType:'checkbox', title:'hockey', fieldValue: '', deletePreserved: false} + ], + _id: '525a8422f6d0f87f0e407a33' + }; + + var sampleFormList = [{ + title: 'Form Title1', + admin: 'ed873933b1f1dea0ce12fab9', + language: 'english', + form_fields: [ + {fieldType:'textfield', title:'First Name', fieldValue: '', deletePreserved: false}, + {fieldType:'checkbox', title:'nascar', fieldValue: '', deletePreserved: false}, + {fieldType:'checkbox', title:'hockey', fieldValue: '', deletePreserved: false} + ], + _id: '525a8422f6d0f87f0e407a33' + },{ + title: 'Form Title2', + admin: '39223933b1f1dea0ce12fab9', + language: 'english', + form_fields: [ + {fieldType:'textfield', title:'First Name', fieldValue: '', deletePreserved: false}, + {fieldType:'checkbox', title:'nascar', fieldValue: '', deletePreserved: false}, + {fieldType:'checkbox', title:'hockey', fieldValue: '', deletePreserved: false} + ], + _id: '52f6d0f87f5a407a384220e3' + },{ + title: 'Form Title3', + admin: '2fab9ed873937f0e1dea0ce1', + language: 'english', + form_fields: [ + {fieldType:'textfield', title:'First Name', fieldValue: '', deletePreserved: false}, + {fieldType:'checkbox', title:'nascar', fieldValue: '', deletePreserved: false}, + {fieldType:'checkbox', title:'hockey', fieldValue: '', deletePreserved: false} + ], + _id: '922f6d0f87fed8730e4e1233' + } + ]; + + + // The $resource service augments the response object with methods for updating and deleting the resource. + // If we were to use the standard toEqual matcher, our tests would fail because the test values would not match + // the responses exactly. To solve the problem, we define a new toEqualData Jasmine matcher. + // When the toEqualData matcher compares two objects, it takes only object properties into + // account and ignores methods. + beforeEach(function() { + jasmine.addMatchers({ + toEqualData: function(util, customEqualityTesters) { + return { + compare: function(actual, expected) { + return { + pass: angular.equals(actual, expected) + }; + } + }; + } + }); + }); + + // Load the main application module + beforeEach(module(ApplicationConfiguration.applicationModuleName)); + + beforeEach(module('stateMock')); + + //Mock Users Service + beforeEach(module(function($provide) { + $provide.service('myForm', function($q) { + return sampleForm; + }); + })); + + + // The injector ignores leading and trailing underscores here (i.e. _$httpBackend_). + // This allows us to inject a service but then attach it to a variable + // with the same name as the service. + beforeEach(inject(function($controller, $rootScope, _$state_, _$location_, _$stateParams_, _$httpBackend_, CurrentForm, Forms) { + // Set a new global scope + scope = $rootScope.$new(); + + //Set CurrentForm + CurrentForm.setForm(sampleForm); + + // Point global variables to injected services + $stateParams = _$stateParams_; + $httpBackend = _$httpBackend_; + $location = _$location_; + $state = _$state_; + + $httpBackend.whenGET(/\.html$/).respond(''); + $httpBackend.whenGET('/users/me/').respond(''); + + // Initialize the Forms controller. + createListFormsController = function(){ + return $controller('ListFormsController', { $scope: scope }); + }; + })); + + it('$scope.findAll() should query all User\'s Forms', inject(function(Forms) { + + var controller = createListFormsController(); + + // Set GET response + $httpBackend.expectGET(/^(\/forms)$/).respond(200, sampleFormList); + + // Run controller functionality + scope.findAll(); + $httpBackend.flush(); + + // Test scope value + expect( scope.myforms ).toEqualData(sampleFormList); + })); + + it('$scope.duplicateForm() should duplicate a Form', inject(function(Forms) { + + var dupSampleForm = sampleFormList[2], + dupSampleForm_index = 3, + newSampleFormList = _.clone(sampleFormList); + dupSampleForm._id = 'a02df75b44c1d26b6a5e05b8'; + newSampleFormList.splice(3, 0, dupSampleForm); + + var controller = createListFormsController(); + + // Set GET response + $httpBackend.expectGET(/^(\/forms)$/).respond(200, sampleFormList); + // Run controller functionality + scope.findAll(); + $httpBackend.flush(); + + // Set GET response + $httpBackend.expect('POST', '/forms').respond(200, dupSampleForm); + // Run controller functionality + scope.duplicateForm(2); + $httpBackend.flush(); + + // Test scope value + expect( scope.myforms.length ).toEqual(newSampleFormList.length); + for(var i=0; i'); + $compile(el)(tmp_scope); + $rootScope.$digest(); + + // Point global variables to injected services + $httpBackend = _$httpBackend_; + + // $httpBackend.whenGET(/.+\.html$/).respond(''); + $httpBackend.whenGET('/users/me/').respond(''); + + //Grab controller instance + controller = el.controller(); + + //Grab scope. Depends on type of scope. + //See angular.element documentation. + scope = el.isolateScope() || el.scope(); + + })); + + it('$scope.uploadPDF() should upload a pdf file', function() { + // expect(scope.isInitialized).toBeDefined() + // expect(scope.log).toEqual(''); + + expect(scope.pdfLoading).toBe(false); + + //Set POST response + $httpBackend.when('POST', '/upload/pdf').respond(pdfObj); + + var files = [{}]; + scope.uploadPDF(files); + + $httpBackend.flush(); + expect(scope.myform.pdf).toEqualData(pdfObj); + }); + + it('$scope.removePDF() should removed uploaded pdf file', function() { + // expect(scope.isInitialized).toBeDefined() + // expect(scope.log).toEqual(''); + + scope.myform.pdf = pdfObj; + scope.myform.isGenerated = true; + scope.myform.autofillPDFs = true; + + scope.removePDF(); + + expect(scope.myform.pdf).toEqual(null); + expect(scope.myform.isGenerated).toBe(false); + expect(scope.myform.autofillPDFs).toBe(false); + }); + }); +}()); diff --git a/public/form_modules/forms/tests/unit/directives/edit-form-submissions.client.directive.test.js b/public/form_modules/forms/tests/unit/directives/edit-form-submissions.client.directive.test.js new file mode 100644 index 00000000..63520f48 --- /dev/null +++ b/public/form_modules/forms/tests/unit/directives/edit-form-submissions.client.directive.test.js @@ -0,0 +1,200 @@ +'use strict'; + +(function() { + // Forms Controller Spec + describe('EditSubmissions Directive-Controller Tests', function() { + // Initialize global variables + var el, scope, controller, $httpBackend; + + var sampleUser = { + firstName: 'Full', + lastName: 'Name', + email: 'test@test.com', + username: 'test@test.com', + password: 'password', + provider: 'local', + roles: ['user'], + _id: 'ed873933b1f1dea0ce12fab9' + }; + + var pdfObj = { + fieldname:'file', + originalname:'test.pdf', + name:'1440112660375.pdf', + encoding:'7bit', + mimetype:'application/pdf', + path:'uploads/tmp/test@test.com/1440112660375.pdf', + extension:'pdf', + size:56223, + truncated:false, + buffer:null + }; + + var sampleForm = { + title: 'Form Title', + admin: 'ed873933b1f1dea0ce12fab9', + language: 'english', + form_fields: [ + {fieldType:'textfield', title:'First Name', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed873933b0ce121f1deafab9'}, + {fieldType:'checkbox', title:'nascar', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed83b0ce121f17393deafab9'}, + {fieldType:'checkbox', title:'hockey', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed8317393deab0ce121ffab9'} + ], + pdf: {}, + pdfFieldMap: {}, + startPage: { + showStart: false + }, + hideFooter: false, + isGenerated: false, + isLive: false, + autofillPDFs: false, + _id: '525a8422f6d0f87f0e407a33' + }; + + var sampleSubmission = { + form_fields: [ + {fieldType:'textfield', title:'First Name', fieldValue: 'John Smith', deletePreserved: false}, + {fieldType:'checkbox', title:'nascar', fieldValue: 1, deletePreserved: false}, + {fieldType:'checkbox', title:'hockey', fieldValue: 0, deletePreserved: false} + ], + admin: sampleUser, + form: sampleForm, + timeElapsed: 17.55 + }; + + var sampleSubmissions = [{ + form_fields: [ + {fieldType:'textfield', title:'First Name', fieldValue: 'The Terminator', deletePreserved: false}, + {fieldType:'checkbox', title:'nascar', fieldValue: 0, deletePreserved: false}, + {fieldType:'checkbox', title:'hockey', fieldValue: 1, deletePreserved: false} + ], + admin: sampleUser, + form: sampleForm, + timeElapsed: 10.33 + }, + { + form_fields: [ + {fieldType:'textfield', title:'First Name', fieldValue: 'John Smith', deletePreserved: false}, + {fieldType:'checkbox', title:'nascar', fieldValue: 1, deletePreserved: false}, + {fieldType:'checkbox', title:'hockey', fieldValue: 0, deletePreserved: false} + ], + admin: sampleUser, + form: sampleForm, + timeElapsed: 2.33 + }, + { + form_fields: [ + {fieldType:'textfield', title:'First Name', fieldValue: 'Jane Doe', deletePreserved: false}, + {fieldType:'checkbox', title:'nascar', fieldValue: 1, deletePreserved: false}, + {fieldType:'checkbox', title:'hockey', fieldValue: 1, deletePreserved: false} + ], + admin: sampleUser, + form: sampleForm, + timeElapsed: 11.11 + }]; + + // The $resource service augments the response object with methods for updating and deleting the resource. + // If we were to use the standard toEqual matcher, our tests would fail because the test values would not match + // the responses exactly. To solve the problem, we define a new toEqualData Jasmine matcher. + // When the toEqualData matcher compares two objects, it takes only object properties into + // account and ignores methods. + beforeEach(function() { + jasmine.addMatchers({ + toEqualData: function(util, customEqualityTesters) { + return { + compare: function(actual, expected) { + return { + pass: angular.equals(actual, expected) + }; + } + }; + } + }); + }); + + // Load the main application module + beforeEach(module(ApplicationConfiguration.applicationModuleName)); + beforeEach(module('module-templates')); + beforeEach(module('stateMock')); + + beforeEach(inject(function($compile, $controller, $rootScope, _$httpBackend_) { + + // Point global variables to injected services + $httpBackend = _$httpBackend_; + + $httpBackend.whenGET('/users/me/').respond(''); + $httpBackend.whenGET(/^(\/forms\/)([0-9a-fA-F]{24})(\/submissions)$/).respond(200, sampleSubmissions); + + //Instantiate directive. + var tmp_scope = $rootScope.$new(); + tmp_scope.myform = sampleForm; + tmp_scope.user = sampleUser; + + //gotacha: Controller and link functions will execute. + el = angular.element(''); + $compile(el)(tmp_scope); + $rootScope.$digest(); + + //Grab controller instance + controller = el.controller(); + + //Grab scope. Depends on type of scope. + //See angular.element documentation. + scope = el.isolateScope() || el.scope(); + })); + + it('$scope.initFormSubmissions() should fetch all relevant form submissions', function() { + $httpBackend.expectGET(/^(\/forms\/)([0-9a-fA-F]{24})(\/submissions)$/).respond(200, sampleSubmissions); + scope.initFormSubmissions(); + $httpBackend.flush(); + scope.$digest(); + }); + + describe('Form Table Methods', function(){ + + it('$scope.toggleAllCheckers should toggle all checkboxes in table', function(){ + scope.initFormSubmissions(); + $httpBackend.flush(); + + //Run Controller Logic to Test + scope.table.masterChecker = true; + scope.toggleAllCheckers(); + + for(var i=0; i'); + $compile(el)(tmp_scope); + $rootScope.$digest(); + + // Point global variables to injected services + $httpBackend = _$httpBackend_; + + //$httpBackend.whenGET(/.+\.html$/).respond(''); + $httpBackend.whenGET('/users/me/').respond(''); + + //Grab controller instance + controller = el.controller(); + + //Grab scope. Depends on type of scope. + //See angular.element documentation. + scope = el.isolateScope() || el.scope(); + + })); + + describe('> Form Field >',function(){ + + beforeEach(function(){ + scope.myform = _.cloneDeep(sampleForm); + }); + + it('$scope.addNewField() should ADD a new field to $scope.myform.form_fields', function() { + + //Run controller methods + scope.addNewField(true, 'textfield'); + + var expectedFormField = { + title:'Short Text2', + fieldType:'textfield', + fieldValue: '', + required: true, + disabled: false, + deletePreserved: false + }; + + var actualFormField = _.cloneDeep(_.last(scope.myform.form_fields)); + delete actualFormField._id; + + expect(scope.myform.form_fields.length).toEqual(sampleForm.form_fields.length+1); + expect(actualFormField).toEqualData(expectedFormField); + }); + + it('$scope.deleteField() should DELETE a field to $scope.myform.form_fields', function() { + + //Run controller methods + scope.deleteField(0); + + expect(scope.myform.form_fields.length).toEqual(sampleForm.form_fields.length-1); + expect(_.first(scope.myform.form_fields)).toEqualData(sampleForm.form_fields[1]); + }); + + it('$scope.duplicateField() should DUPLICATE a field and update $scope.myform.form_fields', function() { + + //Run controller methods + scope.duplicateField(0); + + var originalField = _.cloneDeep(scope.myform.form_fields[0]); + originalField.title += ' copy'; + + delete originalField._id; + var copyField = _.cloneDeep(scope.myform.form_fields[1]); + delete copyField._id; + + expect(scope.myform.form_fields.length).toEqual(sampleForm.form_fields.length+1); + expect(originalField).toEqualData(copyField); + }); + + }); + + describe('> Form Field Button >',function(){ + + it('$scope.addButton() should ADD a button to $scope.myform.startPage.buttons', function() { + + var expectedStartPageBtn = { + bgColor:'#ddd', + color:'#ffffff', + text: 'Button' + }; + + //Run controller methods + scope.addButton(); + var actualStartPageBtn = _.cloneDeep(_.last(scope.myform.startPage.buttons)); + delete actualStartPageBtn._id; + + expect(scope.myform.startPage.buttons.length).toEqual(sampleForm.startPage.buttons.length+1); + expect(actualStartPageBtn).toEqualData(expectedStartPageBtn); + }); + + it('$scope.deleteButton() should DELETE a button from $scope.myform.startPage.buttons', function() { + //Run controller methods + scope.deleteButton(scope.myform.startPage.buttons[0]); + + expect(scope.myform.startPage.buttons.length).toEqual(0); + }); + }); + + describe('> Form Field Option >',function(){ + it('$scope.addOption() should ADD a new option to a field.fieldOptions', function() { + var originalOptionLen = scope.myform.form_fields[1].fieldOptions.length; + + //Run controller methods + scope.addOption(1); + + expect(originalOptionLen+1).toEqual(scope.myform.form_fields[1].fieldOptions.length); + expect(scope.myform.form_fields[1].fieldOptions[0].option_title).toEqualData('Option 0'); + expect(scope.myform.form_fields[1].fieldOptions[0].option_value).toEqualData('Option 0'); + }); + + it('$scope.deleteOption() should DELETE remove option from field.fieldOptions', function() { + //Run controller methods + scope.deleteOption(1, scope.myform.form_fields[1].fieldOptions[0]); + + expect(scope.myform.form_fields[0].fieldOptions.length).toEqual(0); + expect(scope.myform.form_fields[0].fieldOptions[0]).not.toBeDefined(); + }); + }); + }); +}()); \ No newline at end of file diff --git a/public/form_modules/forms/tests/unit/directives/entry-page.client.directive.test.js b/public/form_modules/forms/tests/unit/directives/entry-page.client.directive.test.js new file mode 100644 index 00000000..e24cfc48 --- /dev/null +++ b/public/form_modules/forms/tests/unit/directives/entry-page.client.directive.test.js @@ -0,0 +1,93 @@ +// 'use strict'; + +// (function() { +// // Forms Controller Spec +// describe('entryPage Directive Tests', function() { +// // Initialize global variables +// var scope, +// $templateCache, +// $httpBackend, +// $compile; + +// var sampleStartPage = { +// showStart: true, +// introTitle: 'Welcome to Form', +// introParagraph: 'Sample intro paragraph', +// buttons:[ +// { +// url: 'http://google.com', +// action: '', +// text: 'Google', +// bgColor: '#ffffff', +// color: '#000000', +// }, +// { +// url: 'http://facebook.com', +// action: '', +// text: 'Facebook', +// bgColor: '#0000ff', +// color: '#000000', +// } +// ] +// }; + + +// // The $resource service augments the response object with methods for updating and deleting the resource. +// // If we were to use the standard toEqual matcher, our tests would fail because the test values would not match +// // the responses exactly. To solve the problem, we define a new toEqualData Jasmine matcher. +// // When the toEqualData matcher compares two objects, it takes only object properties into +// // account and ignores methods. +// beforeEach(function() { +// jasmine.addMatchers({ +// toEqualData: function(util, customEqualityTesters) { +// return { +// compare: function(actual, expected) { +// return { +// pass: angular.equals(actual, expected) +// }; +// } +// }; +// } +// }); +// }); + +// // Load the main application module +// beforeEach(module(ApplicationConfiguration.applicationModuleName)); + +// beforeEach(inject(function($rootScope, _$compile_, _$httpBackend_) { +// scope = $rootScope.$new(); +// $compile = _$compile_; + +// // Point global variables to injected services +// $httpBackend = _$httpBackend_; +// })); + + +// it('should be able to render entryPage in html', function() { +// scope.myStartPage = _.cloneDeep(sampleStartPage); +// console.log(scope.myStartPage); +// var element = angular.element(''); +// $compile(element)(scope); +// scope.$digest(); + +// // console.log(element.html()); +// expect(element.html()).not.toEqual('
Start Page
'); +// }); + +// // it('exitStartPage should work for "startPage" type of entryPage', inject(function($rootScope) { +// // scope.myPage = _.cloneDeep(sampleStartPage); +// // var el = angular.element(''); +// // $compile(el)(scope); +// // scope.$digest(); + +// // $httpBackend.whenGET(/.+\.html$/).respond(''); +// // $httpBackend.whenGET('/users/me/').respond(''); + +// // scope = el.isolateScope() || el.scope(); + +// // scope.exitStartPage(); +// // // expect(scope.myStartPage.showStart).toBe(false); +// // expect(el.html()).not.toEqual('
Start Page
'); +// // })); +// }); +// }()); \ No newline at end of file diff --git a/public/form_modules/forms/tests/unit/directives/field-icon.client.directive.test.js b/public/form_modules/forms/tests/unit/directives/field-icon.client.directive.test.js new file mode 100644 index 00000000..32784832 --- /dev/null +++ b/public/form_modules/forms/tests/unit/directives/field-icon.client.directive.test.js @@ -0,0 +1,54 @@ +'use strict'; + +(function() { + // Forms Controller Spec + describe('FieldIcon Directive Tests', function() { + // Initialize global variables + var scope, + FormFields, + faClasses = { + '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' + }; + + // Load the main application module + beforeEach(module(ApplicationConfiguration.applicationModuleName)); + + beforeEach(inject(function ($rootScope, _FormFields_) { + scope = $rootScope.$new(); + FormFields = _FormFields_; + })); + + it('should be able render all field-icon types', inject(function($compile) { + var currType, currClass; + + for(var i=0; i')(scope); + scope.$digest(); + + expect(currClass).toBeDefined(); + + expect(element.find('i')).not.toBe(null); + expect(element.find('i').hasClass(currClass)).toBe(true); + } + + })); + }); +}()); \ No newline at end of file diff --git a/public/form_modules/forms/tests/unit/directives/field.client.directive.test.js b/public/form_modules/forms/tests/unit/directives/field.client.directive.test.js new file mode 100644 index 00000000..b5527d62 --- /dev/null +++ b/public/form_modules/forms/tests/unit/directives/field.client.directive.test.js @@ -0,0 +1,97 @@ +'use strict'; + +(function() { + // Forms Controller Spec + describe('Field Directive Tests', function() { + // Initialize global variables + var scope, + FormFields, + $templateCache, + $httpBackend, + $compile; + + var sampleUser = { + firstName: 'Full', + lastName: 'Name', + email: 'test@test.com', + username: 'test@test.com', + password: 'password', + provider: 'local', + roles: ['user'], + _id: 'ed873933b1f1dea0ce12fab9', + }; + + var sampleFields = [ + {fieldType:'textfield', title:'First Name', fieldValue: 'AoeuName', deletePreserved: false, required: true, disabled: false}, + {fieldType:'email', title:'Email', fieldValue: 'aoeu@aoeu.com', deletePreserved: false, required: true, disabled: false}, + {fieldType:'yes_no', title:'Do you Play Hockey?', fieldValue: 'true', deletePreserved: false, required: true, disabled: false}, + {fieldType:'url', title:'Github Account', fieldValue: 'http://github.com/aoeu', deletePreserved: false, required: true, disabled: false}, + {fieldType:'textarea', title:'Bio', fieldValue: 'This is my bio.', deletePreserved: false, required: true, disabled: false}, + {fieldType:'number', title:'Phone #', fieldValue: 5325325325, deletePreserved: false, required: true, disabled: false}, + {fieldType:'legal', title:'You agree to terms and conditions', description:'By selecting \'I agree\' you are agreeing under Canadian law that you have read and accept terms and conditions outlayed below', fieldValue: '', deletePreserved: false, required: true, disabled: false}, + {fieldType:'dropdown', title:'Your Sex', fieldValue: '', fieldOptions:[ { 'option_id': 0, 'option_title': 'M', 'option_value': 'male' }, { 'option_id': 1, 'option_title': 'F', 'option_value': 'female' }], deletePreserved: false, required: true, disabled: false}, + {fieldType:'radio', title:'Your Sexual Orientation', fieldValue: '', fieldOptions:[ { 'option_id': 0, 'option_title': 'Heterosexual', 'option_value': 'hetero' }, { 'option_id': 1, 'option_title': 'Homosexual', 'option_value': 'homo' }, { 'option_id': 2, 'option_title': 'Bisexual', 'option_value': 'bi' }, { 'option_id': 3, 'option_title': 'Asexual', 'option_value': 'asex' }], deletePreserved: false, required: true, disabled: false}, + {fieldType:'rating', title:'Your Current Happiness', fieldValue: '0', deletePreserved: false, required: true, disabled: false}, + ]; + + + // The $resource service augments the response object with methods for updating and deleting the resource. + // If we were to use the standard toEqual matcher, our tests would fail because the test values would not match + // the responses exactly. To solve the problem, we define a new toEqualData Jasmine matcher. + // When the toEqualData matcher compares two objects, it takes only object properties into + // account and ignores methods. + beforeEach(function() { + jasmine.addMatchers({ + toEqualData: function(util, customEqualityTesters) { + return { + compare: function(actual, expected) { + return { + pass: angular.equals(actual, expected) + }; + } + }; + } + }); + }); + + beforeEach(module(function ($sceProvider) { + $sceProvider.enabled(false); + })); + + // Load the main application module + beforeEach(module(ApplicationConfiguration.applicationModuleName)); + beforeEach(module('stateMock')); + beforeEach(module('module-templates')); + + beforeEach(module('ngSanitize', 'ui.select')); + + beforeEach(inject(function($rootScope, _FormFields_, _$compile_) { + scope = $rootScope.$new(); + FormFields = _FormFields_; + + $compile = _$compile_; + })); + + it('should be able to render all field types in html', inject(function($rootScope) { + scope.fields = sampleFields; + + for(var i=0; i'); + $compile(element)(scope); + scope.$digest(); + + console.log('Actual: '); + console.log(element.html()); + + console.log('\nExpected: '); + + console.log('
'+field.title+'
'); + } + })); + }); +}()); diff --git a/public/form_modules/forms/tests/unit/directives/on-finish-render.client.directive.test.js b/public/form_modules/forms/tests/unit/directives/on-finish-render.client.directive.test.js new file mode 100644 index 00000000..274f4acb --- /dev/null +++ b/public/form_modules/forms/tests/unit/directives/on-finish-render.client.directive.test.js @@ -0,0 +1,46 @@ +'use strict'; + +(function() { + // Forms Controller Spec + describe('onFinishRender Directive Tests', function() { + // Initialize global variables + var scope, + FormFields; + + // Load the main application module + beforeEach(module(ApplicationConfiguration.applicationModuleName)); + + beforeEach(inject(function ($rootScope, _FormFields_) { + scope = $rootScope.$new(); + FormFields = _FormFields_; + spyOn($rootScope, '$broadcast'); + + })); + + it('should emit Custom "Finished" and "Started" events on ng-repeat', inject(function($compile, $rootScope) { + + scope.myfields = FormFields.types; + + var e = $compile('
{{item.name}}
')(scope); + scope.$digest(); + + //run code to test + expect($rootScope.$broadcast).toHaveBeenCalledWith('editFormFields Started'); + expect(scope.$broadcast).toHaveBeenCalledWith('editFormFields Finished'); + })); + + it('should emit "ngRepeat Finished" and "ngRepeat Started" events on ng-repeat when attr is not set to string', inject(function($compile, $rootScope) { + + // console.log(FormFields.types); + scope.myfields = FormFields.types; + + var e = $compile('
{{item.name}}
')(scope); + scope.$digest(); + + //run code to test + expect($rootScope.$broadcast).toHaveBeenCalledWith('ngRepeat Started'); + expect(scope.$broadcast).toHaveBeenCalledWith('ngRepeat Finished'); + })); + + }); +}()); \ No newline at end of file diff --git a/public/form_modules/forms/tests/unit/directives/submit-form.client.directive.test.js b/public/form_modules/forms/tests/unit/directives/submit-form.client.directive.test.js new file mode 100644 index 00000000..db46b05c --- /dev/null +++ b/public/form_modules/forms/tests/unit/directives/submit-form.client.directive.test.js @@ -0,0 +1,203 @@ +'use strict'; + +(function() { + // Forms Controller Spec + describe('SubmitForm Directive-Controller Tests', function() { + // Initialize global variables + var scope, controller, $httpBackend; + + var sampleUser = { + firstName: 'Full', + lastName: 'Name', + email: 'test@test.com', + username: 'test@test.com', + password: 'password', + provider: 'local', + roles: ['user'], + _id: 'ed873933b1f1dea0ce12fab9' + }; + + var pdfObj = { + fieldname:'file', + originalname:'test.pdf', + name:'1440112660375.pdf', + encoding:'7bit', + mimetype:'application/pdf', + path:'uploads/tmp/test@test.com/1440112660375.pdf', + extension:'pdf', + size:56223, + truncated:false, + buffer:null + }; + + var sampleForm = { + title: 'Form Title', + admin: 'ed873933b1f1dea0ce12fab9', + language: 'english', + form_fields: [ + {fieldType:'textfield', title:'First Name', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed873933b0ce121f1deafab9'}, + {fieldType:'checkbox', title:'nascar', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed83b0ce121f17393deafab9'}, + {fieldType:'checkbox', title:'hockey', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed8317393deab0ce121ffab9'} ], + visible_form_fields: [ + {fieldType:'textfield', title:'First Name', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed873933b0ce121f1deafab9'}, + {fieldType:'checkbox', title:'nascar', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed83b0ce121f17393deafab9'}, + {fieldType:'checkbox', title:'hockey', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed8317393deab0ce121ffab9'} ], + pdf: {}, + pdfFieldMap: {}, + startPage: { + showStart: false + }, + hideFooter: false, + isGenerated: false, + isLive: false, + autofillPDFs: false, + _id: '525a8422f6d0f87f0e407a33' + }; + + var sampleSubmission = { + form_fields: [ + {fieldType:'textfield', title:'First Name', fieldValue: 'John Smith', deletePreserved: false, _id: 'ed873933b0ce121f1deafab9'}, + {fieldType:'yes_no', title:'Do you like nascar', fieldValue: true, deletePreserved: false, _id: 'ed83b0ce121f17393deafab9'}, + {fieldType:'yes_no', title:'Do you like hockey', fieldValue: false, deletePreserved: false, _id: 'ed8317393deab0ce121ffab9'} + ], + admin: sampleUser, + form: sampleForm, + timeElapsed: 17.55 + }; + + var sampleSubmissions = [{ + form_fields: [ + {fieldType:'textfield', title:'First Name', fieldValue: 'The Terminator', deletePreserved: false}, + {fieldType:'yes_no', title:'Do you like nascar', fieldValue: 'true', deletePreserved: false}, + {fieldType:'yes_no', title:'Do you like hockey', fieldValue: 'false', deletePreserved: false} + ], + admin: sampleUser, + form: sampleForm, + timeElapsed: 10.33 + }, + { + form_fields: [ + {fieldType:'textfield', title:'First Name', fieldValue: 'John Smith', deletePreserved: false}, + {fieldType:'yes_no', title:'Do you like nascar', fieldValue: 'true', deletePreserved: false}, + {fieldType:'yes_no', title:'Do you like hockey', fieldValue: 'true', deletePreserved: false} + ], + admin: sampleUser, + form: sampleForm, + timeElapsed: 2.33 + }, + { + form_fields: [ + {fieldType:'textfield', title:'First Name', fieldValue: 'Jane Doe', deletePreserved: false}, + {fieldType:'yes_no', title:'Do you like nascar', fieldValue: 'false', deletePreserved: false}, + {fieldType:'yes_no', title:'Do you like hockey', fieldValue: 'false', deletePreserved: false} + ], + admin: sampleUser, + form: sampleForm, + timeElapsed: 11.11 + }]; + + // The $resource service augments the response object with methods for updating and deleting the resource. + // If we were to use the standard toEqual matcher, our tests would fail because the test values would not match + // the responses exactly. To solve the problem, we define a new toEqualData Jasmine matcher. + // When the toEqualData matcher compares two objects, it takes only object properties into + // account and ignores methods. + beforeEach(function() { + jasmine.addMatchers({ + toEqualData: function(util, customEqualityTesters) { + return { + compare: function(actual, expected) { + return { + pass: angular.equals(actual, expected) + }; + } + }; + } + }); + }); + + // Load the main application module + beforeEach(module(ApplicationConfiguration.applicationModuleName)); + beforeEach(module('module-templates')); + beforeEach(module('stateMock')); + + beforeEach(inject(function($compile, $controller, $rootScope, _$httpBackend_) { + + // Point global variables to injected services + $httpBackend = _$httpBackend_; + $httpBackend.whenGET('/users/me/').respond(''); + + //Instantiate directive. + var tmp_scope = $rootScope.$new(); + tmp_scope.myform = sampleForm; + + //gotacha: Controller and link functions will execute. + var el = angular.element(''); + $compile(el)(tmp_scope); + tmp_scope.$digest(); + $rootScope.$digest(); + + //Grab controller instance + controller = el.controller(); + + //Grab scope. Depends on type of scope. + //See angular.element documentation. + scope = el.isolateScope() || el.scope(); + + console.log(scope); + })); + + var Validator = (function() { + return { + hasMinimumFields: function(entry) { + return !_.isEmpty(entry._id) && !_.isEmpty(entry.title); + }, + isNewForm: function(entry) { + return this.hasMinimumFields(entry); + } + }; + })(); + + + it('$scope.submitForm() should submit valid form', function(){ + //Initialize variables + scope.myform.form_fields = sampleSubmissions[0].form_fields; + + var expectedForm = _.cloneDeep(sampleForm); + expectedForm.form_fields = sampleSubmissions[0].form_fields; + delete expectedForm.visible_form_fields; + + var data = function(data) { + var form = angular.fromJson(data); + var compareForm = _.cloneDeep(form); + delete compareForm.timeElapsed; + delete compareForm.percentageComplete; + + return Validator.isNewForm(form) && _.isEqual(compareForm, expectedForm); + }; + + //Set expected HTTP requests + $httpBackend.expect('POST',/^(\/forms\/)([0-9a-fA-F]{24})$/, data).respond(200); + + //Run Controller Logic to Test + scope.submitForm(); + + $httpBackend.flush(); + + setTimeout(function(){ + expect(scope.myform.submitted).toBe(true); + expect(scope.error).toEqual(''); + }, 25); + }); + + it('$scope.reloadForm() should reset and reload form', function(){ + scope.submitForm(); + scope.reloadForm(); + + expect(scope.myform.submitted).toBe(false); + for(var i=0; i 0){ + var expectedState = this.expectedTransitions.shift(); + if(expectedState !== stateName){ + throw Error('Expected transition to state: ' + expectedState + ' but transitioned to ' + stateName ); + } + }else{ + throw Error('No more transitions were expected! Tried to transition to '+ stateName ); + } + console.log('Mock transition to: ' + stateName); + var deferred = $q.defer(); + var promise = deferred.promise; + deferred.resolve(); + return promise; + }; + + this.go = this.transitionTo; + this.expectTransitionTo = function(stateName){ + this.expectedTransitions.push(stateName); + }; + + this.ensureAllTransitionsHappened = function(){ + if(this.expectedTransitions.length > 0){ + throw Error('Not all transitions happened!'); + } + }; +}); \ No newline at end of file diff --git a/public/humans.txt b/public/humans.txt index 08d7ac69..3600069a 100755 --- a/public/humans.txt +++ b/public/humans.txt @@ -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 diff --git a/public/modules/core/config/core.client.routes.js b/public/modules/core/config/core.client.routes.js index 765db68f..149b3900 100755 --- a/public/modules/core/config/core.client.routes.js +++ b/public/modules/core/config/core.client.routes.js @@ -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'); + } + } + } + }); + }]); diff --git a/public/modules/core/services/subdomain.client.service.js b/public/modules/core/services/subdomain.client.service.js new file mode 100644 index 00000000..eaa9c90b --- /dev/null +++ b/public/modules/core/services/subdomain.client.service.js @@ -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]; +}]); diff --git a/public/modules/users/controllers/authentication.client.controller.js b/public/modules/users/controllers/authentication.client.controller.js index eaede4b1..f075f817 100755 --- a/public/modules/users/controllers/authentication.client.controller.js +++ b/public/modules/users/controllers/authentication.client.controller.js @@ -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); diff --git a/public/modules/users/views/authentication/signin.client.view.html b/public/modules/users/views/authentication/signin.client.view.html index 4a02c0e1..2f773df0 100755 --- a/public/modules/users/views/authentication/signin.client.view.html +++ b/public/modules/users/views/authentication/signin.client.view.html @@ -26,7 +26,7 @@
- +
diff --git a/public/modules/users/views/authentication/signup.client.view.html b/public/modules/users/views/authentication/signup.client.view.html index f66c3f6b..a0bcf63b 100644 --- a/public/modules/users/views/authentication/signup.client.view.html +++ b/public/modules/users/views/authentication/signup.client.view.html @@ -34,6 +34,10 @@

+
+ + +
diff --git a/public/modules/users/views/settings/edit-profile.client.view.html b/public/modules/users/views/settings/edit-profile.client.view.html index 4713ff41..97b32373 100755 --- a/public/modules/users/views/settings/edit-profile.client.view.html +++ b/public/modules/users/views/settings/edit-profile.client.view.html @@ -1,4 +1,4 @@ -
+1

Edit your profile

+
+
+ Username +
+
+ +
+
+
Email - (also your username)
@@ -62,4 +70,4 @@
- \ No newline at end of file + diff --git a/public/modules/users/views/verify/resend-verify-email.client.view.html b/public/modules/users/views/verify/resend-verify-email.client.view.html index 584f04ca..be108846 100644 --- a/public/modules/users/views/verify/resend-verify-email.client.view.html +++ b/public/modules/users/views/verify/resend-verify-email.client.view.html @@ -9,16 +9,11 @@
From f00edf73dd184ed5ff9c17cbfcd860212fb0582d Mon Sep 17 00:00:00 2001 From: David Baldwynn Date: Mon, 20 Jun 2016 15:06:41 -0700 Subject: [PATCH 2/5] got subdomain support to work --- app/controllers/forms.server.controller.js | 9 +- app/models/form.server.model.js | 6 +- app/routes/core.server.routes.js | 4 +- config/env/all.js | 17 +- config/express.js | 7 +- package.json | 3 +- public/dist/application.js | 601 ++++++------------ public/dist/application.min.js | 10 +- .../modules/core/config/core.client.routes.js | 2 +- .../admin-form.client.controller.js | 2 + .../edit-submissions-form.client.directive.js | 2 +- .../admin/views/admin-form.client.view.html | 2 +- .../forms/config/forms.client.routes.js | 17 +- 13 files changed, 219 insertions(+), 463 deletions(-) diff --git a/app/controllers/forms.server.controller.js b/app/controllers/forms.server.controller.js index 2061e8c1..d121a098 100644 --- a/app/controllers/forms.server.controller.js +++ b/app/controllers/forms.server.controller.js @@ -285,6 +285,9 @@ exports.read = function(req, res) { var newForm = req.form.toJSON({virtuals : true}); newForm.plugins.oscarhost.settings.validUpdateTypes = validUpdateTypes; + if(newForm){ + return res.json(newForm); + } if (req.userId) { if(req.form.admin._id+'' === req.userId+''){ @@ -293,11 +296,13 @@ exports.read = function(req, res) { return res.status(404).send({ message: 'Form Does Not Exist' }); + }else { + if(newForm) return res.json(newForm); } - return res.status(404).send({ + /*return res.status(404).send({ message: 'Form Does Not Exist' - }); + });*/ }; diff --git a/app/models/form.server.model.js b/app/models/form.server.model.js index 44a9b038..6ac9f2d6 100644 --- a/app/models/form.server.model.js +++ b/app/models/form.server.model.js @@ -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 diff --git a/app/routes/core.server.routes.js b/app/routes/core.server.routes.js index fd2006f3..c8077872 100755 --- a/app/routes/core.server.routes.js +++ b/app/routes/core.server.routes.js @@ -3,11 +3,11 @@ /** * Module dependencies. */ -var forms = require('../../app/controllers/forms.server.controller'); +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]+)') diff --git a/config/env/all.js b/config/env/all.js index 0fe30798..43427ca5 100755 --- a/config/env/all.js +++ b/config/env/all.js @@ -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', @@ -94,15 +94,10 @@ module.exports = { 'public/config.js', 'public/application.js', 'public/dist/populate_template_cache.js', - 'public/form_modules/*/*.js', - 'public/form_modules/*/*/*/*/*.js', - 'public/form_modules/*/*/*.js', - 'public/form_modules/*/*/*/*.js', - '!public/form_modules/**/gruntfile.js', - '!public/form_modules/**/demo/**/*.js', - '!public/form_modules/**/dist/**/*.js', - '!public/form_modules/**/node_modules/**/*.js', - '!public/form_modules/**/tests/**/*.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', diff --git a/config/express.js b/config/express.js index 5396eb8f..e8c534ff 100755 --- a/config/express.js +++ b/config/express.js @@ -74,12 +74,17 @@ module.exports = function(db) { var subdomains = req.subdomains; var host = req.hostname; + // 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(); @@ -87,7 +92,7 @@ module.exports = function(db) { if(url.parse(req.url).path.split('/')[1] === ignoreWithStartPath) return next(); } - User.findOne({username: req.subdomains[0]}).exec(function (err, user) { + User.findOne({username: req.subdomains.reverse()[0]}).exec(function (err, user) { if (err) { console.log(err); req.subdomains = null; diff --git a/package.json b/package.json index 0e9ce637..a9671802 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/public/dist/application.js b/public/dist/application.js index 6a45914c..b74afaa2 100644 --- a/public/dist/application.js +++ b/public/dist/application.js @@ -41,73 +41,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 @@ -119,35 +63,33 @@ angular.element(document).ready(function() { angular.module('NodeForm.templates', []).run(['$templateCache', function($templateCache) { "use strict"; - $templateCache.put("../public/modules/core/views/header.client.view.html", - "
"); - $templateCache.put("../public/modules/core/views/home.client.view.html", - "

TellForm

Craft beautiful forms in seconds.

Craft beautiful forms.

TellForm is an opensource alternative to TypeForm that can create stunning forms from PDFs or from scratch

TellForm is an opensource alternative to TypeForm that can create stunning forms from PDFs or from scratch

Create your next ______.

Tell a story with a form.

"); - $templateCache.put("../public/modules/forms/views/admin-form.client.view.html", - "
"); - $templateCache.put("../public/modules/forms/views/list-forms.client.view.html", - "

Create a new form
Name
Language

Created on
"); - $templateCache.put("../public/modules/forms/views/submit-form.client.view.html", + "
"); + $templateCache.put("modules/forms/admin/views/list-forms.client.view.html", + "

{{ 'CREATE_A_NEW_FORM' | translate }}
Name
Language

{{ 'CREATED_ON' | translate }}
"); + $templateCache.put("modules/forms/base/views/submit-form.client.view.html", "
"); - $templateCache.put("../public/modules/forms/views/adminTabs/analyze.html", + $templateCache.put("modules/forms/admin/views/adminTabs/analyze.html", ""); - $templateCache.put("../public/modules/forms/views/adminTabs/configure.html", + $templateCache.put("modules/forms/admin/views/adminTabs/configure.html", ""); - $templateCache.put("../public/modules/forms/views/adminTabs/create.html", + $templateCache.put("modules/forms/admin/views/adminTabs/create.html", ""); - $templateCache.put("../public/modules/forms/views/adminTabs/design.html", - "

Change how your Form Looks

Change how your Form Looks

Background Color
Question Text Color
Answer Text Color
Button Background Color
Button Text Color
"); - $templateCache.put("../public/modules/forms/views/directiveViews/cgBusy/update-form-message-TypeA.html", + $templateCache.put("modules/forms/admin/views/adminTabs/design.html", + "

{{ 'DESIGN_HEADER' | translate }}

{{ 'DESIGN_HEADER' | translate }}

{{ 'BACKGROUND_COLOR' | translate }}
{{ 'QUESTION_TEXT_COLOR' | translate }}
{{ 'ANSWER_TEXT_COLOR' | translate }}
{{ 'BTN_BACKGROUND_COLOR' | translate }}
{{ 'BTN_TEXT_COLOR' | translate }}
"); + $templateCache.put("modules/forms/admin/views/directiveViews/cgBusy/update-form-message-TypeA.html", "
{{$message}}
"); - $templateCache.put("../public/modules/forms/views/directiveViews/cgBusy/update-form-message-TypeB.html", + $templateCache.put("modules/forms/admin/views/directiveViews/cgBusy/update-form-message-TypeB.html", "
{{$message}}
"); - $templateCache.put("../public/modules/forms/views/directiveViews/entryPage/startPage.html", - "

{{pageData.introTitle}}

{{pageData.introParagraph}}

"); - $templateCache.put("../public/modules/forms/views/directiveViews/field/date.html", - "

{{index+1}} {{field.title}} optional

{{field.description}}

"); - $templateCache.put("../public/modules/forms/views/directiveViews/field/dropdown.html", - "
0\">

{{index+1}} {{field.title}} optional

{{field.description}}


"); - $templateCache.put("../public/modules/forms/views/directiveViews/field/hidden.html", + $templateCache.put("modules/forms/admin/views/directiveViews/form/configure-form.client.view.html", + "

{{ 'PDF Generation/EMR' | translate }}

{{ 'PDF Generation/EMR' | translate }}

{{ 'SAVE_PDF_SUBMISSIONS' | translate }}
{{ 'UPLOAD_YOUR_PDF' | translate }}
{{myform.pdf.name}}
{{ 'UPLOAD_YOUR_PDF' | translate }}
{{ 'Autogenerate Form?' | translate }}


{{ 'ADVANCED_SETTINGS' | translate }}

{{ 'ADVANCED_SETTINGS' | translate }}

{{ 'FORM_NAME' | translate }}
{{ 'FORM_STATUS' | translate }}
{{ 'GA_TRACKING_CODE' | translate }}
Language
* required
{{ 'DISPLAY_FOOTER' | translate }}
Display Start Page?
"); + $templateCache.put("modules/forms/admin/views/directiveViews/form/edit-form.client.view.html", + "

{{ 'ADD_FIELD_LG' | translate }}

{{ 'ADD_FIELD_MD' | translate }}

{{ 'ADD_FIELD_SM' | translate }}

Start Page

{{ 'PREVIEW_START_PAGE' | translate }}

    {{myform.startPage.introTitle}}

    {{myform.startPage.introParagraph}}

{{ 'EDIT_START_PAGE' | translate }}


{{ 'INTRO_TITLE' | translate }}:
{{ 'INTRO_PARAGRAPH' | translate }}:
{{ 'INTRO_BTN' | translate }}:


Buttons:
{{ 'BUTTON_TEXT' | translate }}
{{ 'BUTTON_LINK' | translate }}


{{field.title}} *

{{ 'PREVIEW_FIELD' | translate }}


{{ 'EDIT_FIELD' | translate }}


{{ 'QUESTION_TITLE' | translate }}:

{{ 'QUESTION_DESCRIPTION' | translate }}:

{{ 'OPTIONS' | translate }}:

{{ 'NUM_OF_STEPS' | translate }}

Shape:

Required:
Disabled:

{{ 'CLICK_FIELDS_FOOTER' | translate }}


"); + $templateCache.put("modules/forms/admin/views/directiveViews/form/edit-submissions-form.client.view.html", + "
Overview Analytics
{{ 'TOTAL_VIEWS' | translate }}
{{ 'RESPONSES' | translate }}
{{ 'COMPLETION_RATE' | translate }}
{{ 'AVERAGE_TIME_TO_COMPLETE' | translate }}
{{myform.analytics.views}}
{{myform.analytics.submissions}}
{{myform.analytics.conversionRate | number:0}}%
{{AverageTimeElapsed | secondsToDateTime | date:'mm:ss'}}
Device Analytics
{{ 'DESKTOP_AND_LAPTOP' | translate }}
{{ 'TABLETS' | translate }}
{{ 'PHONES' | translate }}
{{ 'OTHER' | translate }}
{{ 'UNIQUE_VISITS' | translate }}
{{DeviceStatistics.desktop.visits}}
{{ 'UNIQUE_VISITS' | translate }}
{{DeviceStatistics.tablet.visits}}
{{ 'UNIQUE_VISITS' | translate }}
{{DeviceStatistics.tablet.visits}}
{{ 'UNIQUE_VISITS' | translate }}
{{DeviceStatistics.other.visits}}
{{ 'RESPONSES' | translate }}
{{DeviceStatistics.desktop.responses}}
{{ 'RESPONSES' | translate }}
{{DeviceStatistics.tablet.responses}}
{{ 'RESPONSES' | translate }}
{{DeviceStatistics.phone.responses}}
{{ 'RESPONSES' | translate }}
{{DeviceStatistics.other.responses}}
{{ 'COMPLETION_RATE' | translate }}
{{DeviceStatistics.desktop.completion}}
{{ 'COMPLETION_RATE' | translate }}
{{DeviceStatistics.tablet.completion}}
{{ 'COMPLETION_RATE' | translate }}
{{DeviceStatistics.phone.completion}}
{{ 'COMPLETION_RATE' | translate }}
{{DeviceStatistics.other.completion}}
{{ 'AVERAGE_TIME_TO_COMPLETE' | translate }}
{{DeviceStatistics.desktop.average_time | secondsToDateTime | date:'mm:ss'}}
{{ 'AVERAGE_TIME_TO_COMPLETE' | translate }}
{{DeviceStatistics.tablet.average_time | secondsToDateTime | date:'mm:ss'}}
{{ 'AVERAGE_TIME_TO_COMPLETE' | translate }}
{{DeviceStatistics.phone.average_time | secondsToDateTime | date:'mm:ss'}}
{{ 'AVERAGE_TIME_TO_COMPLETE' | translate }}
{{DeviceStatistics.other.average_time | secondsToDateTime | date:'mm:ss'}}
Field Analytics
{{ 'FIELD_TITLE' | translate }}
{{ 'FIELD_VIEWS' | translate }}
{{ 'FIELD_RESPONSES' | translate }}
{{ 'FIELD_DROPOFF' | translate }}
{{fieldStats.field.title}}
{{fieldStats.totalViews}}
{{fieldStats.responses}}
{{fieldStats.continueRate}}%

Responses Table
#{{value.title}}{{ 'PERCENTAGE_COMPLETE' | translate }}{{ 'TIME_ELAPSED' | translate }}{{ 'DEVICE' | translate }}{{ 'LOCATION' | translate }}{{ 'IP_ADDRESS' | translate }}{{ 'DATE_SUBMITTED' | translate }} (UTC){{ 'GENERATED_PDF' | translate }}
{{$index+1}}{{field.fieldValue}}{{row.percentageComplete}}%{{row.timeElapsed | secondsToDateTime | date:'mm:ss'}}{{row.device.name}}, {{row.device.type}}{{row.geoLocation.city}}, {{row.geoLocation.country_name}}{{row.ipAddr}}{{row.created | date:'yyyy-MM-dd HH:mm:ss'}}{{ 'GENERATED_PDF' | translate }}
"); + $templateCache.put("modules/forms/base/views/directiveViews/entryPage/startPage.html", + "

{{pageData.introTitle}}

{{pageData.introParagraph}}

"); + $templateCache.put("modules/forms/base/views/directiveViews/field/date.html", + "

{{index+1}} {{field.title}} {{ 'OPTIONAL' | translate }}

{{field.description}}

"); + $templateCache.put("modules/forms/base/views/directiveViews/field/dropdown.html", + "
0\">

{{index+1}} {{field.title}} {{ 'OPTIONAL' | translate }}

{{field.description}}


"); + $templateCache.put("modules/forms/base/views/directiveViews/field/file.html", + "

{{index+1}} {{field.title}} {{ 'OPTIONAL' | translate }}

{{field.file.originalname}}
{{ UPLOAD_FILE | translate }}
"); + $templateCache.put("modules/forms/base/views/directiveViews/field/hidden.html", ""); - $templateCache.put("../public/modules/forms/views/directiveViews/field/legal.html", - "

{{index+1}} {{field.title}} optional


{{field.description}}


"); - $templateCache.put("../public/modules/forms/views/directiveViews/field/radio.html", - "
0\">

{{index+1}} {{field.title}} optional

{{field.description}}


"); - $templateCache.put("../public/modules/forms/views/directiveViews/field/rating.html", - "

{{index+1}} {{field.title}} optional

{{field.description}}

"); - $templateCache.put("../public/modules/forms/views/directiveViews/field/statement.html", - "

{{field.title}}

{{field.description}}

{{field.description}}


"); - $templateCache.put("../public/modules/forms/views/directiveViews/field/textarea.html", - "

{{index+1}} {{field.title}} optional

{{field.description}}

Press SHIFT+ENTER to add a newline
press ENTER
"); - $templateCache.put("../public/modules/forms/views/directiveViews/field/textfield.html", - "

{{index+1}} {{field.title}} (optional)

{{field.description}}

press ENTER
"); - $templateCache.put("../public/modules/forms/views/directiveViews/field/yes_no.html", - "

{{index+1}} {{field.title}} optional

{{field.description}}


"); - $templateCache.put("../public/modules/forms/views/directiveViews/form/configure-form.client.view.html", - "

PDF Generation/EMR

PDF Generation/EMR

Save Submissions as PDFs?
Upload Your PDF Template
{{myform.pdf.name}}
Upload your PDF
Autogenerate Form?
Use Oscarhost API?
Oscarhost API Username
Oscarhost API Password
Oscarhost API URL
Oscarhost API Update Type


Advanced Settings

Advanced Settings

Form Name
Form Status
Google Analytics Tracking Code
Language
* required
Display Form Footer?
Display Start Page?
"); - $templateCache.put("../public/modules/forms/views/directiveViews/form/edit-form.client.view.html", - "

Click to Add New Field

Add New Field

Add Field

Start Page

Preview Start Page

    {{myform.startPage.introTitle}}

    {{myform.startPage.introParagraph}}

Edit Start Page


Intro Title:
Intro Paragraph:
\n" + - "
\n" + - "\n" + - "

\n" + - "
\n" + - "
Options:
\n" + - "
\n" + - "
\n" + - " \n" + - "\n" + - " \n" + - " \n" + - " \n" + - "
\n" + - "
\n" + - " \n" + - "
\n" + - "
\n" + - "
\n" + - "\n" + - "\n" + - "

\n" + - "
\n" + - "
Number of Steps:
\n" + - "
\n" + - " \n" + - "
\n" + - "
\n" + - "
Shape:
\n" + - "
\n" + - " \n" + - "
\n" + - "
\n" + - "\n" + - "

\n" + - "\n" + - "
\n" + - "
Required:
\n" + - "
\n" + - " \n" + - "\n" + - " \n" + - "
\n" + - "
\n" + - "\n" + - "
\n" + - "
Disabled:
\n" + - "
\n" + - " \n" + - "\n" + - " \n" + - "
\n" + - "
\n" + - "\n" + - "
\n" + - " \n" + - "\n" + - "
\n" + - "
\n" + - "

\n" + - " Click on Fields to add them here\n" + - "

\n" + - "
\n" + - "
\n" + - "\n" + - "
\n" + - " \n" + - "
\n" + - "\n" + - "
\n" + - "
\n" + - "
\n" + - "
\n" + - " \n" + - " \n" + - " \n" + - "
\n" + - "
\n" + - "
\n" + - "
\n" + - "
\n" + - "
\n" + - "
\n" + - "
\n" + - " \n" + - " \n" + - " \n" + - "
\n" + - "
\n" + - "
\n" + - "
\n" + - "\n" + - "
\n" + - "
\n" + - "
\n" + - " \n" + - " \n" + - "
\n" + - "
\n" + - "
\n" + - "
\n" + - "
\n" + - "\n" + - ""); - $templateCache.put("../public/modules/forms/views/directiveViews/form/edit-submissions-form.client.view.html", - "
Total Views: {{myform.analytics.views}}
Submissions: {{myform.analytics.submissions}}
Conversion Rate: {{myform.analytics.conversionRate}}%

Field Title
Field Views
User dropoff rate at this field
{{fieldStats.field.title}}
{{fieldStats.totalViews}}
{{fieldStats.dropoffRate}}%

#{{value.title}}OscarEMR User ProfilePercentage CompleteTime ElapsedDeviceLocationIP AddressDate Submitted (UTC)Generated PDF
{{$index+1}}{{field.fieldValue.option_value}} {{field.fieldValue}}User Profile #{{row.oscarDemoNum}}{{row.percentageComplete}}%{{row.timeElapsed}}{{row.device.name}}, {{row.device.type}}{{row.geoLocation.city}}, {{row.geoLocation.country}}{{row.ipAddr}}{{row.created | date:'yyyy-MM-dd HH:mm:ss'}}Generated PDF
"); - $templateCache.put("../public/modules/forms/views/directiveViews/form/submit-form.client.view.html", - "
{{form_fields_count - (myform | formValidity)}} answer(s) need completing
press ENTER

{{myform | formValidity}} out of {{form_fields_count}} answered

"); - $templateCache.put("../public/modules/users/views/authentication/access-denied.client.view.html", - "

You need to be logged in to access this page

Login
"); - $templateCache.put("../public/modules/users/views/authentication/signin.client.view.html", - "

Sign into your account

Error:
  or  Sign up
"); - $templateCache.put("../public/modules/users/views/authentication/signup-success.client.view.html", - "

Signup Successful

You've successfully registered an account at TellForm.

But your account is not activated yet



Before you continue, make sure to check your email for our verification. If you don't receive it within 24h drop us a line at polydaic@gmail.com

"); - $templateCache.put("../public/modules/users/views/authentication/signup.client.view.html", - "

Signup with your email

Couldn't submit form due to errors:

"); - $templateCache.put("../public/modules/users/views/password/forgot-password.client.view.html", - "

Restore your password

Enter your account email.

{{error}}
{{success}}
"); - $templateCache.put("../public/modules/users/views/password/reset-password-invalid.client.view.html", - "

Password reset is invalid

Ask for a new password reset
"); - $templateCache.put("../public/modules/users/views/password/reset-password-success.client.view.html", - "

Password successfully reset

Continue to home page
"); - $templateCache.put("../public/modules/users/views/password/reset-password.client.view.html", - "

Reset your password

{{error}}
{{success}}
"); - $templateCache.put("../public/modules/users/views/settings/change-password.client.view.html", - "

Change your password


Password Changed Successfully
"); - $templateCache.put("../public/modules/users/views/settings/edit-profile.client.view.html", - "

Edit your profile

Profile Saved Successfully
Couldn't Save Your Profile.
Error:
First Name
Last Name

Language
Email (also your username)
"); - $templateCache.put("../public/modules/users/views/settings/social-accounts.client.view.html", + $templateCache.put("modules/forms/base/views/directiveViews/field/legal.html", + "

{{index+1}} {{field.title}} {{ 'OPTIONAL' | translate }}


{{field.description}}


"); + $templateCache.put("modules/forms/base/views/directiveViews/field/radio.html", + "
0\">

{{index+1}} {{field.title}} {{ 'OPTIONAL' | translate }}

{{field.description}}


"); + $templateCache.put("modules/forms/base/views/directiveViews/field/rating.html", + "

{{index+1}} {{field.title}} {{ 'OPTIONAL' | translate }}

{{field.description}}

"); + $templateCache.put("modules/forms/base/views/directiveViews/field/statement.html", + "

{{field.title}}

{{field.description}}

{{field.description}}


"); + $templateCache.put("modules/forms/base/views/directiveViews/field/textarea.html", + "

{{index+1}} {{field.title}} {{ 'OPTIONAL' | translate }}

{{ 'NEWLINE' | translate }}

{{field.description}}

Press SHIFT+ENTER to add a newline
{{ 'ENTER' | translate }}
"); + $templateCache.put("modules/forms/base/views/directiveViews/field/textfield.html", + "

{{index+1}} {{field.title}} ({{ 'OPTIONAL' | translate }})

{{field.description}}

{{ 'ENTER' | translate }}
"); + $templateCache.put("modules/forms/base/views/directiveViews/field/yes_no.html", + "

{{index+1}} {{field.title}} {{ 'OPTIONAL' | translate }}

{{field.description}}


"); + $templateCache.put("modules/forms/base/views/directiveViews/form/submit-form.client.view.html", + "
{{ 'COMPLETING_NEEDED' | translate:translateAdvancementData }}
{{ 'ENTER' | translate }}

{{ 'ADVANCEMENT' | translate:translateAdvancementData }}

"); + $templateCache.put("modules/users/views/authentication/access-denied.client.view.html", + "

{{ 'ACCESS_DENIED_TEXT' | translate }}

{{ 'SIGNIN_BTN' | translate }}
"); + $templateCache.put("modules/users/views/authentication/signin.client.view.html", + "

Sign into your account

Error:
  or  {{ 'SIGNUP_BTN' | translate }}
"); + $templateCache.put("modules/users/views/authentication/signup-success.client.view.html", + "

{{ 'SUCCESS_HEADER' | translate }}

{{ 'SUCCESS_TEXT' | translate }}

{{ 'NOT_ACTIVATED_YET' | translate }}



{{ 'BEFORE_YOU_CONTINUE' | translate }} polydaic@gmail.com

"); + $templateCache.put("modules/users/views/authentication/signup.client.view.html", + "

Signup with your email

Couldn't complete registration due to errors:
{{ 'LANGUAGE_LABEL' | translate }}

"); + $templateCache.put("modules/users/views/password/forgot-password.client.view.html", + "

{{ 'PASSWORD_RESTORE_HEADER' | translate }}

{{ 'ENTER_YOUR_EMAIL' | translate }}

{{error}}
{{success}}
"); + $templateCache.put("modules/users/views/password/reset-password-invalid.client.view.html", + "

{{ 'PASSWORD_RESET_INVALID' | translate }}

{{ 'ASK_FOR_NEW_PASSWORD' | translate }}
"); + $templateCache.put("modules/users/views/password/reset-password-success.client.view.html", + "

{{ 'PASSWORD_RESET_SUCCESS' | translate }}

{{ 'CONTINUE_TO_LOGIN' | translate }}
"); + $templateCache.put("modules/users/views/password/reset-password.client.view.html", + "

Reset your password

{{error}}
{{success}}
"); + $templateCache.put("modules/users/views/settings/change-password.client.view.html", + "

Change your password


{{ 'PASSWORD_CHANGE_SUCCESS' | translate }}
"); + $templateCache.put("modules/users/views/settings/edit-profile.client.view.html", + "1

Edit your profile

{{ 'PROFILE_SAVE_SUCCESS' | translate }}
{{ 'PROFILE_SAVE_ERROR' | translate }}
{{ 'FIRST_NAME_LABEL' | translate }}
Last Name

{{ 'LANGUAGE_LABEL' | translate }}
Username
{{ 'EMAIL_LABEL' | translate }}
"); + $templateCache.put("modules/users/views/settings/social-accounts.client.view.html", "

Connected social accounts:

Connect other social accounts:

"); - $templateCache.put("../public/modules/users/views/verify/resend-verify-email.client.view.html", - "

Resend your account verification email

Enter your account email.

{{error}}

Verification Email has been Sent

A verification email has been sent to {{username}}.
But your account is still not activated yet

Check your email and click on the activation link to activate your account. If you have any questions drop us a line at polydaic@gmail.com

"); - $templateCache.put("../public/modules/users/views/verify/verify-account.client.view.html", - "

Account successfuly activated

Continue to login page

Verification link is invalid or has expired

Resend your verification email Signin to your account
"); + $templateCache.put("modules/users/views/verify/resend-verify-email.client.view.html", + "

Resend your account verification email

Enter your account email.

{{error}}

Verification Email has been Sent

{{ 'VERIFICATION_EMAIL_SENT' | translate }} {{username}}.
{{ 'NOT_ACTIVATED_YET' | translate }}

{{ 'CHECK_YOUR_EMAIL' | translate }} polydaic@gmail.com

"); + $templateCache.put("modules/users/views/verify/verify-account.client.view.html", + "

{{ 'CONTINUE_TO_LOGIN' | translate }}

{{ 'VERIFY_ERROR' | translate }}

{{ 'REVERIFY_ACCOUNT_LINK' | translate }} {{ 'SIGNIN_BTN' | translate }}
"); }]); 'use strict'; @@ -771,6 +482,64 @@ angular.module('core').config(['$stateProvider', '$urlRouterProvider', } ]); +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'); + } + } + } + }); + }]); + 'use strict'; angular.module('core').controller('HeaderController', ['$rootScope', '$scope', 'Menus', '$state', 'Auth', 'User', '$window', '$translate', '$locale', @@ -1057,6 +826,16 @@ angular.module('core').service('Menus', [ '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]; +}]); + +'use strict'; + // Configuring the Forms drop-down menus angular.module('forms').run(['Menus', function(Menus) { @@ -1111,22 +890,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: ["Forms", "$stateParams", 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: { @@ -1437,7 +1201,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); @@ -1926,6 +1689,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'), @@ -2699,7 +2464,7 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope', completion: 0, average_time: 0, total_time: 0 - } + }; }; var stats = { @@ -3571,10 +3336,10 @@ angular.module('users').config(['$translateProvider', function ($translateProvid SAVE_PASSWORD_BTN: 'Save Password', SUCCESS_HEADER: 'Signup Successful', - SUCCESS_TEXT: 'You\'ve successfully registered an account at TellForm.', + SUCCESS_TEXT: 'You’ve successfully registered an account at TellForm.', VERIFICATION_EMAIL_SENT: 'A verification email has been sent to', NOT_ACTIVATED_YET: 'But your account is not activated yet', - BEFORE_YOU_CONTINUE: 'Before you continue, make sure to check your email for our verification. If you don\'t receive it within 24h drop us a line at ', + BEFORE_YOU_CONTINUE: 'Before you continue, make sure to check your email for our verification. If you don’t receive it within 24h drop us a line at ', CHECK_YOUR_EMAIL: 'Check your email and click on the activation link to activate your account. If you have any questions drop us a line at', PASSWORD_RESTORE_HEADER: 'Restore your password', @@ -3603,49 +3368,49 @@ angular.module('users').config(['$translateProvider', function ($translateProvid angular.module('users').config(['$translateProvider', function ($translateProvider) { $translateProvider.translations('en', { - ACCESS_DENIED_TEXT: 'Vouz est pas autorisé pour accese cete page.', - USERNAME_LABEL: 'Nom de Compte', - PASSWORD_LABEL: 'Mot de Pass', - CURRENT_PASSWORD_LABEL: 'Current Password', - NEW_PASSWORD_LABEL: 'Nouveau Mot de Pass Password', - VERIFY_PASSWORD_LABEL: 'Verify Password', - UPDATE_PASSWORD_LABEL: 'Update Password', - FIRST_NAME_LABEL: 'Premiere Nom Name', - LAST_NAME_LABEL: 'Surnom', - LANGUAGE_LABEL: 'Language', + ACCESS_DENIED_TEXT: 'Vouz n’êtes pas autorisé à accéder à cette page.', + USERNAME_LABEL: 'Nom d’utilisateur', + PASSWORD_LABEL: 'Mot de Passe', + CURRENT_PASSWORD_LABEL: 'Mot de passe actuel', + NEW_PASSWORD_LABEL: 'Nouveau Mot de Passe', + VERIFY_PASSWORD_LABEL: 'Vérifier le mot de passe', + UPDATE_PASSWORD_LABEL: 'Mettre à jour le mot de passe', + FIRST_NAME_LABEL: 'Prénom', + LAST_NAME_LABEL: 'Nom', + LANGUAGE_LABEL: 'Langue', EMAIL_LABEL: 'Email', - UPDATE_PROFILE_BTN: 'Modifier Profile', - PROFILE_SAVE_SUCCESS: 'Profile saved successfully', - PROFILE_SAVE_ERROR: 'Erreur: On peux pas enregistré votre Profile.', + UPDATE_PROFILE_BTN: 'Modifier le Profil', + PROFILE_SAVE_SUCCESS: 'Profil enregistré avec succès', + PROFILE_SAVE_ERROR: 'Erreur: impossible d’enregistrer votre Profile.', - FORGOT_PASSWORD_LINK: 'Oublier votre mot de pass?', - REVERIFY_ACCOUNT_LINK: 'Re-envoyez ton email de verification', + FORGOT_PASSWORD_LINK: 'Mot de passe oublié ?', + REVERIFY_ACCOUNT_LINK: 'Re-envoyez un email de vérification', SIGNIN_BTN: 'Connexion', SIGNUP_BTN: 'Créer un compte', - SAVE_PASSWORD_BTN: 'Enregistreé ton nouveau Mot de Pass', + SAVE_PASSWORD_BTN: 'Enregistrer votre nouveau Mot de Passe', - SUCCESS_HEADER: 'Votre Compte a été enregistré!', - SUCCESS_TEXT: 'Vouz a enregistré un compte a TellForm.', - VERIFICATION_EMAIL_SENT: 'Un email de verification a été envoyer a', + SUCCESS_HEADER: 'Votre Compte a été enregistré !', + SUCCESS_TEXT: 'Votre compte Tellform a été crée avec succès.', + VERIFICATION_EMAIL_SENT: 'Un email de verification a été envoyer à', NOT_ACTIVATED_YET: 'Mais votre compte n\'est pas activé', - BEFORE_YOU_CONTINUE: 'Plutôt que vouz continué, vouz devrez voire ton inbox pour notre message de verification. Si tu receivoir-pas un message de verification dan le prochaine 24h, contactez nous a ', - CHECK_YOUR_EMAIL: 'Check your email and click on the activation link to activate your account. If you have any questions drop us a line at', + BEFORE_YOU_CONTINUE: 'Avant de continuer, vous devez valider votre adresse mail. Merci de vérifier votre boite mail. Si vous ne l’avez pas reçu dans les prochaines 24h, contactez-nous a ', + CHECK_YOUR_EMAIL: 'Vérifiez vos emails, et cliquez sur le lien de validation pour activer votre compte. Si vous avez une question contactez-nous à', - PASSWORD_RESTORE_HEADER: 'Restore your password', - ENTER_YOUR_EMAIL: 'Entrer votre email de compte', + PASSWORD_RESTORE_HEADER: 'Mot de passe perdu', + ENTER_YOUR_EMAIL: 'Entrer votre email', SUBMIT_BTN: 'Enregistrer', ASK_FOR_NEW_PASSWORD: 'Demander un nouveau mot de pass ', - PASSWORD_RESET_INVALID: 'Password reset is invalid', - PASSWORD_RESET_SUCCESS: 'Passport successfully reset', - PASSWORD_CHANGE_SUCCESS: 'Passport successfully changed', + PASSWORD_RESET_INVALID: 'Le nouveau mot de passe est invalid', + PASSWORD_RESET_SUCCESS: 'Mot de passe réinitialisé avec succès', + PASSWORD_CHANGE_SUCCESS: 'Mot de passe enregistré avec succès', - CONTINUE_TO_LOGIN: 'Allez au page de connexion', + CONTINUE_TO_LOGIN: 'Allez à la page de connexion', - VERIFY_SUCCESS: 'Compte est activé!', - VERIFY_ERROR: 'Le fléche de verification est invalid ou expireé' + VERIFY_SUCCESS: 'Votre compte est activé !', + VERIFY_ERROR: 'Le lien de vérification est invalide ou à expiré' }); }]); diff --git a/public/dist/application.min.js b/public/dist/application.min.js index 2357d055..d4d2f289 100644 --- a/public/dist/application.min.js +++ b/public/dist/application.min.js @@ -1,5 +1,5 @@ -"use strict";function removeDateFieldsFunc(o){function eachObject(v,k){"lastModified"!==k&&"created"!==k||delete clone[k]}for(var clone=_.clone(o),i=0;i0?Auth.isAuthenticated()&&(event.preventDefault(),$state.go("listForms")):"access_denied"===toState.name||Auth.isAuthenticated()||"submitForm"===toState.name||(console.log("go to signup"),event.preventDefault(),$state.go("listForms"))})}]),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,user&&(authenticator=new Authorizer(user),null!=permissions&&(authenticator.canAccess(permissions)||(event.preventDefault(),$state.go("access_denied"))))})}]),angular.element(document).ready(function(){"#_=_"===window.location.hash&&(window.location.hash="#!"),angular.bootstrap(document,[ApplicationConfiguration.applicationModuleName])}),angular.module("NodeForm.templates",[]).run(["$templateCache",function($templateCache){$templateCache.put("../public/modules/core/views/header.client.view.html",''),$templateCache.put("../public/modules/core/views/home.client.view.html",'

Craft beautiful forms in seconds.

Create your next ______.

Tell a story with a form.

'),$templateCache.put("../public/modules/forms/views/admin-form.client.view.html",'
'),$templateCache.put("../public/modules/forms/views/list-forms.client.view.html",'

Create a new form
Name
Language

'),$templateCache.put("../public/modules/forms/views/submit-form.client.view.html","
"),$templateCache.put("../public/modules/forms/views/adminTabs/analyze.html",""),$templateCache.put("../public/modules/forms/views/adminTabs/configure.html",""),$templateCache.put("../public/modules/forms/views/adminTabs/create.html",""),$templateCache.put("../public/modules/forms/views/adminTabs/design.html",'
Background Color
Question Text Color
Answer Text Color
Button Background Color
Button Text Color
'),$templateCache.put("../public/modules/forms/views/directiveViews/cgBusy/update-form-message-TypeA.html",'
{{$message}}
'),$templateCache.put("../public/modules/forms/views/directiveViews/cgBusy/update-form-message-TypeB.html",'
{{$message}}
'),$templateCache.put("../public/modules/forms/views/directiveViews/entryPage/startPage.html",'

{{pageData.introTitle}}

{{pageData.introParagraph}}

'),$templateCache.put("../public/modules/forms/views/directiveViews/field/date.html",'

{{index+1}} {{field.title}} optional

{{field.description}}

'),$templateCache.put("../public/modules/forms/views/directiveViews/field/dropdown.html",'
'),$templateCache.put("../public/modules/forms/views/directiveViews/field/hidden.html",''),$templateCache.put("../public/modules/forms/views/directiveViews/field/legal.html",'
'),$templateCache.put("../public/modules/forms/views/directiveViews/field/radio.html",'

{{index+1}} {{field.title}} optional

{{field.description}}


'),$templateCache.put("../public/modules/forms/views/directiveViews/field/rating.html",'

{{index+1}} {{field.title}} optional

{{field.description}}

'),$templateCache.put("../public/modules/forms/views/directiveViews/field/statement.html",'

{{field.title}}

{{field.description}}

{{field.description}}


'),$templateCache.put("../public/modules/forms/views/directiveViews/field/textarea.html",'

{{index+1}} {{field.title}} optional

{{field.description}}

Press SHIFT+ENTER to add a newline
'),$templateCache.put("../public/modules/forms/views/directiveViews/field/textfield.html",'

{{index+1}} {{field.title}} (optional)

{{field.description}}

'),$templateCache.put("../public/modules/forms/views/directiveViews/field/yes_no.html",'

{{index+1}} {{field.title}} optional

{{field.description}}


'),$templateCache.put("../public/modules/forms/views/directiveViews/form/configure-form.client.view.html",'
Save Submissions as PDFs?
Upload Your PDF Template
{{myform.pdf.name}}
Upload your PDF
Autogenerate Form?
Use Oscarhost API?
Oscarhost API Username
Oscarhost API Password
Oscarhost API URL
Oscarhost API Update Type
Form Name
Form Status
Google Analytics Tracking Code
Language
* required
Display Form Footer?
Display Start Page?
'), -$templateCache.put("../public/modules/forms/views/directiveViews/form/edit-form.client.view.html",'

Edit Start Page


Intro Title:
Intro Paragraph:
\n
\n\n

\n
\n
Options:
\n
\n
\n \n\n \n \n \n
\n
\n \n
\n
\n
\n\n\n

\n
\n
Number of Steps:
\n
\n \n
\n
\n
Shape:
\n
\n \n
\n
\n\n

\n\n
\n
Required:
\n
\n \n\n \n
\n
\n\n
\n
Disabled:
\n
\n \n\n \n
\n
\n\n
\n \n\n
\n
\n

\n Click on Fields to add them here\n

\n
\n
\n\n
\n \n
\n\n \n \n\n \n
\n
\n\n'),$templateCache.put("../public/modules/forms/views/directiveViews/form/edit-submissions-form.client.view.html",'
Total Views: {{myform.analytics.views}}
Submissions: {{myform.analytics.submissions}}
Conversion Rate: {{myform.analytics.conversionRate}}%

Field Title
Field Views
User dropoff rate at this field
{{fieldStats.field.title}}
{{fieldStats.totalViews}}
{{fieldStats.dropoffRate}}%

#{{value.title}}OscarEMR User ProfilePercentage CompleteTime ElapsedDeviceLocationIP AddressDate Submitted (UTC)Generated PDF
{{$index+1}}{{field.fieldValue.option_value}} {{field.fieldValue}}User Profile #{{row.oscarDemoNum}}{{row.percentageComplete}}%{{row.timeElapsed}}{{row.device.name}}, {{row.device.type}}{{row.geoLocation.city}}, {{row.geoLocation.country}}{{row.ipAddr}}{{row.created | date:\'yyyy-MM-dd HH:mm:ss\'}}Generated PDF
'),$templateCache.put("../public/modules/forms/views/directiveViews/form/submit-form.client.view.html",'
{{form_fields_count - (myform | formValidity)}} answer(s) need completing
'),$templateCache.put("../public/modules/users/views/authentication/access-denied.client.view.html",'

You need to be logged in to access this page

Login
'),$templateCache.put("../public/modules/users/views/authentication/signin.client.view.html",'

Sign into your account

'),$templateCache.put("../public/modules/users/views/authentication/signup-success.client.view.html",''),$templateCache.put("../public/modules/users/views/authentication/signup.client.view.html",''),$templateCache.put("../public/modules/users/views/password/forgot-password.client.view.html",'

Restore your password

Enter your account email.

'),$templateCache.put("../public/modules/users/views/password/reset-password-invalid.client.view.html",'

Password reset is invalid

Ask for a new password reset
'),$templateCache.put("../public/modules/users/views/password/reset-password-success.client.view.html",'

Password successfully reset

Continue to home page
'),$templateCache.put("../public/modules/users/views/password/reset-password.client.view.html",'

Reset your password

'),$templateCache.put("../public/modules/users/views/settings/change-password.client.view.html",'

Change your password

'), -$templateCache.put("../public/modules/users/views/settings/edit-profile.client.view.html",'

Edit your profile

'),$templateCache.put("../public/modules/users/views/settings/social-accounts.client.view.html",'

Connected social accounts:

Connect other social accounts:

'),$templateCache.put("../public/modules/users/views/verify/resend-verify-email.client.view.html",'

Resend your account verification email

Enter your account email.

{{error}}

Verification Email has been Sent

A verification email has been sent to {{username}}.
But your account is still not activated yet

Check your email and click on the activation link to activate your account. If you have any questions drop us a line at polydaic@gmail.com

'),$templateCache.put("../public/modules/users/views/verify/verify-account.client.view.html",'

Account successfuly activated

Continue to login page

Verification link is invalid or has expired

Resend your verification email Signin to your account
')}]),ApplicationConfiguration.registerModule("core",["users"]),ApplicationConfiguration.registerModule("forms",["ngFileUpload","ui.router.tabs","ui.date","ui.sortable","angular-input-stars","users"]),ApplicationConfiguration.registerModule("users"),angular.module("forms").config(["$translateProvider",function($translateProvider){$translateProvider.translations("en",{PDF_GENERATION_EMR:"PDF Generation/EMR",SAVE_PDF_SUBMISSIONS:"Save Submissions as PDFs?",UPLOAD_YOUR_PDF:"Upload Your PDF Template",ADVANCED_SETTINGS:"Advanced Settings",FORM_NAME:"Form Name",FORM_STATUS:"Form Status",PUBLIC:"Public",PRIVATE:"Private",GA_TRACKING_CODE:"Google Analytics Tracking Code",DISPLAY_FOOTER:"Display Form Footer?",SAVE_CHANGES:"Save Changes",CANCEL:"Cancel",CREATE_A_NEW_FORM:"Create a new form",CREATE_FORM:"Create form",CREATED_ON:"Created on",ARE_YOU_SURE:"Are you ABSOLUTELY sure?",READ_WARNING:"Unexpected bad things will happen if you don’t read this!",DELETE_WARNING1:"This action CANNOT be undone.This will permanently delete the",DELETE_WARNING2:"form, form submissions and remove all associated pdfs.",DELETE_CONFIRM:"Please type in the name of the form to confirm",I_UNDERSTAND:"I understand the consequences, delete this form",DELETE_FORM_SM:"Delete",DELETE_FORM_MD:"Delete Form",DELETE:"Delete",FORM:"Form",VIEW:"View",LIVE:"Live",PREVIEW:"Preview",ADD_FIELD_LG:"Click to Add New Field",ADD_FIELD_MD:"Add New Field",ADD_FIELD_SM:"Add Field",PREVIEW_START_PAGE:"Preview Start Page",EDIT_START_PAGE:"Edit Start Page",INTRO_TTILE:"Intro Title",INTRO_PARAGRAPH:"Intro Paragraph",INTRO_BTN:"Intro Button",BUTTONS:"Buttons",BUTTON_TEXT:"Text",BUTTON_LINK:"Link",ADD_BUTTON:"Add Button",PREVIEW_FIELD:"Preview Field",EDIT_FIELD:"Edit Field",QUESTION_TITLE:"Question Title",QUESTION_DESCRIPTION:"Question Description",OPTIONS:"Options",ADD_OPTION:"Add Option",NUM_OF_STEPS:"Number of Steps",CLICK_FIELDS_FOOTER:"Click on fields to add them here",TOTAL_VIEWS:"total unique visits",RESPONSES:"responses",COMPLETION_RATE:"completion rate",AVERAGE_TIME_TO_COMPLETE:"avg. completion time",DESKTOP_AND_LAPTOP:"Desktops",TABLETS:"Tablets",PHONES:"Phones",OTHER:"Other",UNIQUE_VISITS:"Unique Visits",FIELD_TITLE:"Field Title",FIELD_VIEWS:"Field Views",FIELD_DROPOFF:"Field Completion",FIELD_RESPONSES:"Field Responses",DELETE_SELECTED:"Delete Selected",EXPORT_TO_EXCEL:"Export to Excel",EXPORT_TO_CSV:"Export to CSV",EXPORT_TO_JSON:"Export to JSON",PERCENTAGE_COMPLETE:"Percentage Complete",TIME_ELAPSED:"Time Elapsed",DEVICE:"Device",LOCATION:"Location",IP_ADDRESS:"IP Address",DATE_SUBMITTED:"Date Submitted",GENERATED_PDF:"Generated PDF",BACKGROUND_COLOR:"Background Color",DESIGN_HEADER:"Change how your Form Looks",QUESTION_TEXT_COLOR:"Question Text Color",ANSWER_TEXT_COLOR:"Answer Text Color",BTN_BACKGROUND_COLOR:"Button Background Color",BTN_TEXT_COLOR:"Button Text Color",CREATE_TAB:"Create",DESIGN_TAB:"Design",CONFIGURE_TAB:"Configure",ANALYZE_TAB:"Analyze"})}]),angular.module("forms").config(["$translateProvider",function($translateProvider){$translateProvider.translations("en",{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"})}]),angular.module("forms").config(["$translateProvider",function($translateProvider){$translateProvider.translations("fr",{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"})}]),angular.module("forms").config(["$translateProvider",function($translateProvider){$translateProvider.translations("de",{FORM_SUCCESS:"Ihre Angaben wurden gespeichert.",REVIEW:"Unvollständig",BACK_TO_FORM:"Zurück zum Formular",EDIT_FORM:"Bearbeiten Sie diese TellForm",CREATE_FORM:"Erstellen Sie eine TellForm",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:"Ich akzeptiere",LEGAL_NO_ACCEPT:"Ich akzeptiere nicht",DELETE:"Entfernen",CANCEL:"Canceln",SUBMIT:"Speichern",UPLOAD_FILE:"Datei versenden",Y:"J",N:"N"})}]),angular.module("forms").config(["$translateProvider",function($translateProvider){$translateProvider.translations("it",{FORM_SUCCESS:"Il formulario è stato inviato con successo!",REVIEW:"Incompleto",BACK_TO_FORM:"Ritorna al formulario",EDIT_FORM:"Modifica questo Tellform",CREATE_FORM:"Creare un TellForm",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:"Accetto",LEGAL_NO_ACCEPT:"Non accetto",DELETE:"Cancella",CANCEL:"Reset",SUBMIT:"Registra",UPLOAD_FILE:"Invia un file",Y:"S",N:"N"})}]),angular.module("forms").config(["$translateProvider",function($translateProvider){$translateProvider.translations("es",{FORM_SUCCESS:"¡El formulario ha sido enviado con éxito!",REVIEW:"Revisar",BACK_TO_FORM:"Regresar al formulario",EDIT_FORM:"Crear un TellForm",CREATE_FORM:"Editar este TellForm",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:"Acepto",LEGAL_NO_ACCEPT:"No acepto",DELETE:"Eliminar",CANCEL:"Cancelar",SUBMIT:"Registrar",UPLOAD_FILE:"Cargar el archivo",Y:"S",N:"N"})}]),angular.module("core").config(["$stateProvider","$urlRouterProvider",function($stateProvider,$urlRouterProvider,Authorization){$urlRouterProvider.otherwise("/forms")}]),angular.module("core").controller("HeaderController",["$rootScope","$scope","Menus","$state","Auth","User","$window","$translate","$locale",function($rootScope,$scope,Menus,$state,Auth,User,$window,$translate,$locale){$rootScope.signupDisabled=$window.signupDisabled,$scope.user=$rootScope.user=Auth.ensureHasCurrentUser(User),$scope.authentication=$rootScope.authentication=Auth,$rootScope.languages=$scope.languages=["en","fr","es","it","de"],console.log($locale.id),$scope.authentication.isAuthenticated()?$rootScope.language=$scope.user.language:$rootScope.language=$locale.id.substring(0,2),$translate.use($rootScope.language),$scope.isCollapsed=!1,$rootScope.hideNav=!1,$scope.menu=Menus.getMenu("topbar"),$scope.signout=function(){var promise=User.logout();promise.then(function(){Auth.logout(),Auth.ensureHasCurrentUser(User),$scope.user=$rootScope.user=null,$state.go("listForms")},function(reason){console.log("Logout Failed: "+reason)})},$scope.toggleCollapsibleMenu=function(){$scope.isCollapsed=!$scope.isCollapsed},$scope.$on("$stateChangeSuccess",function(event,toState,toParams,fromState,fromParams){$scope.isCollapsed=!1,$rootScope.hideNav=!1,angular.isDefined(toState.data)&&angular.isDefined(toState.data.hideNav)&&($rootScope.hideNav=toState.data.hideNav)})}]),angular.module("core").service("Menus",[function(){this.defaultRoles=["*"],this.menus={};var shouldRender=function(user){if(!user)return this.isPublic;if(~this.roles.indexOf("*"))return!0;for(var userRoleIndex in user.roles)for(var roleIndex in this.roles)if(console.log(this.roles[roleIndex]),console.log(this.roles[roleIndex]===user.roles[userRoleIndex]),this.roles[roleIndex]===user.roles[userRoleIndex])return!0;return!1};this.validateMenuExistance=function(menuId){if(menuId&&menuId.length){if(this.menus[menuId])return!0;throw new Error("Menu does not exists")}throw new Error("MenuId was not provided")},this.getMenu=function(menuId){return this.validateMenuExistance(menuId),this.menus[menuId]},this.addMenu=function(menuId,isPublic,roles){return this.menus[menuId]={isPublic:isPublic||!1,roles:roles||this.defaultRoles,items:[],shouldRender:shouldRender},this.menus[menuId]},this.removeMenu=function(menuId){this.validateMenuExistance(menuId),delete this.menus[menuId]},this.addMenuItem=function(menuId,menuItemTitle,menuItemURL,menuItemType,menuItemUIRoute,isPublic,roles,position){return this.validateMenuExistance(menuId),this.menus[menuId].items.push({title:menuItemTitle,link:menuItemURL,menuItemType:menuItemType||"item",menuItemClass:menuItemType,uiRoute:menuItemUIRoute||"/"+menuItemURL,isPublic:null===isPublic||"undefined"==typeof isPublic?this.menus[menuId].isPublic:isPublic,roles:null===roles||"undefined"==typeof roles?this.menus[menuId].roles:roles,position:position||0,items:[],shouldRender:shouldRender}),this.menus[menuId]},this.addSubMenuItem=function(menuId,rootMenuItemURL,menuItemTitle,menuItemURL,menuItemUIRoute,isPublic,roles,position){this.validateMenuExistance(menuId);for(var itemIndex in this.menus[menuId].items)this.menus[menuId].items[itemIndex].link===rootMenuItemURL&&this.menus[menuId].items[itemIndex].items.push({title:menuItemTitle,link:menuItemURL,uiRoute:menuItemUIRoute||"/"+menuItemURL,isPublic:null===isPublic||"undefined"==typeof isPublic?this.menus[menuId].items[itemIndex].isPublic:isPublic,roles:null===roles||"undefined"==typeof roles?this.menus[menuId].items[itemIndex].roles:roles,position:position||0,shouldRender:shouldRender});return this.menus[menuId]},this.removeMenuItem=function(menuId,menuItemURL){this.validateMenuExistance(menuId);for(var itemIndex in this.menus[menuId].items)this.menus[menuId].items[itemIndex].link===menuItemURL&&this.menus[menuId].items.splice(itemIndex,1);return this.menus[menuId]},this.removeSubMenuItem=function(menuId,submenuItemURL){this.validateMenuExistance(menuId);for(var itemIndex in this.menus[menuId].items)for(var subitemIndex in this.menus[menuId].items[itemIndex].items)this.menus[menuId].items[itemIndex].items[subitemIndex].link===submenuItemURL&&this.menus[menuId].items[itemIndex].items.splice(subitemIndex,1);return this.menus[menuId]},this.addMenu("topbar",!1,["*"]),this.addMenu("bottombar",!1,["*"])}]),function(){function Socket($timeout,$window){function connect(url){service.socket=io(url,{transports:["websocket","polling"]})}function emit(eventName,data){service.socket&&service.socket.emit(eventName,data)}function on(eventName,callback){service.socket&&service.socket.on(eventName,function(data){$timeout(function(){callback(data)})})}function removeListener(eventName){service.socket&&service.socket.removeListener(eventName)}var service={connect:connect,emit:emit,on:on,removeListener:removeListener,socket:null};return connect(window.location.protocol+"//"+window.location.hostname+":"+$window.socketPort),service}angular.module("core").factory("Socket",Socket),Socket.$inject=["$timeout","$window"]}(),angular.module("forms").run(["Menus",function(Menus){Menus.addMenuItem("topbar","My Forms","forms","","/forms",!1)}]).filter("secondsToDateTime",[function(){return function(seconds){return new Date(1970,0,1).setSeconds(seconds)}}]).filter("formValidity",function(){return function(formObj){if(formObj&&formObj.form_fields&&formObj.visible_form_fields){var formKeys=Object.keys(formObj),fields=(formKeys.filter(function(key){return"$"!==key[0]}),formObj.form_fields),valid_count=fields.filter(function(field){return"object"==typeof field&&"statement"!==field.fieldType&&"rating"!==field.fieldType?!!field.fieldValue:void 0}).length;return valid_count-(formObj.form_fields.length-formObj.visible_form_fields.length)}return 0}}).config(["$provide",function($provide){$provide.decorator("accordionDirective",["$delegate",function($delegate){var directive=$delegate[0];return directive.replace=!0,$delegate}])}]),angular.module("forms").config(["$stateProvider",function($stateProvider){$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:!0},resolve:{Forms:"Forms",myForm:["Forms","$stateParams",function(Forms,$stateParams){return Forms.get({formId:$stateParams.formId}).$promise}]},controller:"SubmitFormController",controllerAs:"ctrl"}).state("viewForm",{url:"/forms/:formId/admin",templateUrl:"modules/forms/admin/views/admin-form.client.view.html",data:{permissions:["editForm"]},resolve:{Forms:"Forms",myForm:["Forms","$stateParams",function(Forms,$stateParams){return Forms.get({formId:$stateParams.formId}).$promise}]},controller:"AdminFormController"}).state("viewForm.configure",{url:"/configure",templateUrl:"modules/forms/admin/views/adminTabs/configure.html"}).state("viewForm.design",{url:"/design",templateUrl:"modules/forms/admin/views/adminTabs/design.html"}).state("viewForm.analyze",{url:"/analyze",templateUrl:"modules/forms/admin/views/adminTabs/analyze.html"}).state("viewForm.create",{url:"/create",templateUrl:"modules/forms/admin/views/adminTabs/create.html"})}]),function(){function SendVisitorData(Socket,$state,$http,deviceDetector){function send(form,lastActiveIndex,timeElapsed){var visitorData={referrer:document.referrer,isSubmitted:form.submitted,formId:form._id,lastActiveField:form.form_fields[lastActiveIndex]._id,timeElapsed:timeElapsed,language:window.navigator.userLanguage||window.navigator.language,ipAddr:"",deviceType:""};$http.get("http://jsonip.com/").success(function(response){visitorData.ipAddr=response.ip+""}).error(function(error){console.error("Could not get users's ip")}).then(function(){visitorData.userAgent=deviceDetector.raw,deviceDetector.isTablet()?visitorData.deviceType="tablet":deviceDetector.isMobile()?visitorData.deviceType="phone":visitorData.deviceType="desktop",console.log(visitorData.deviceType),Socket.emit("form-visitor-data",visitorData)})}function init(){Socket.socket||Socket.connect()}var service={send:send};return init(),service}angular.module("forms").factory("SendVisitorData",SendVisitorData),SendVisitorData.$inject=["Socket","$state","$http","deviceDetector"]}(),angular.module("forms").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,index=parseInt(String.fromCharCode(keyCode))-1;index<$scope.field.fieldOptions.length&&(event.preventDefault(),$scope.$apply(function(){$scope.field.fieldValue=$scope.field.fieldOptions[index].option_value}))})}}}),angular.module("forms").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,truthyKeyCode=$attrs.keyCharTruthy.charCodeAt(0)-32,falseyKeyCode=$attrs.keyCharFalsey.charCodeAt(0)-32;keyCode===truthyKeyCode?(event.preventDefault(),$scope.$apply(function(){$scope.field.fieldValue="true"})):keyCode===falseyKeyCode&&(event.preventDefault(),$scope.$apply(function(){$scope.field.fieldValue="false"}))})}}}]),angular.module("users").config(["$httpProvider",function($httpProvider){$httpProvider.interceptors.push(["$q","$location",function($q,$location){return{responseError:function(response){return"/users/me"!==$location.path()&&response.config&&"/users/me"!==response.config.url&&(console.log("intercepted rejection of ",response.config.url,response.status),401===response.status?(console.log($location.path()),$location.nextAfterLogin=$location.path(),$location.path("/signin")):403===response.status&&$location.path("/access_denied")),$q.reject(response)}}}])}]),angular.module("users").config(["$stateProvider",function($stateProvider){var checkLoggedin=function($q,$timeout,$state,User,Auth){var deferred=$q.defer();return Auth.currentUser&&Auth.currentUser.email?$timeout(deferred.resolve):Auth.currentUser=User.getCurrent(function(){Auth.login(),$timeout(deferred.resolve())},function(){Auth.logout(),$timeout(deferred.reject()),$state.go("signin",{reload:!0})}),deferred.promise};checkLoggedin.$inject=["$q","$timeout","$state","User","Auth"];var checkSignupDisabled=function($window,$timeout,$q){var deferred=$q.defer();return $timeout($window.signupDisabled?deferred.reject():deferred.resolve()),deferred.promise};checkSignupDisabled.$inject=["$window","$timeout","$q"],$stateProvider.state("profile",{resolve:{loggedin:checkLoggedin},url:"/settings/profile",templateUrl:"modules/users/views/settings/edit-profile.client.view.html"}).state("password",{resolve:{loggedin:checkLoggedin},url:"/settings/password",templateUrl:"modules/users/views/settings/change-password.client.view.html"}).state("accounts",{resolve:{loggedin:checkLoggedin},url:"/settings/accounts",templateUrl:"modules/users/views/settings/social-accounts.client.view.html"}).state("signup",{resolve:{isDisabled:checkSignupDisabled},url:"/signup",templateUrl:"modules/users/views/authentication/signup.client.view.html"}).state("signup-success",{resolve:{isDisabled:checkSignupDisabled},url:"/signup-success",templateUrl:"modules/users/views/authentication/signup-success.client.view.html"}).state("signin",{url:"/signin",templateUrl:"modules/users/views/authentication/signin.client.view.html"}).state("access_denied",{url:"/access_denied",templateUrl:"modules/users/views/authentication/access-denied.client.view.html"}).state("verify",{resolve:{isDisabled:checkSignupDisabled},url:"/verify/:token",templateUrl:"modules/users/views/verify/verify-account.client.view.html"}).state("resendVerifyEmail",{resolve:{isDisabled:checkSignupDisabled},url:"/verify",templateUrl:"modules/users/views/verify/resend-verify-email.client.view.html"}).state("forgot",{url:"/password/forgot",templateUrl:"modules/users/views/password/forgot-password.client.view.html"}).state("reset-invalid",{url:"/password/reset/invalid",templateUrl:"modules/users/views/password/reset-password-invalid.client.view.html"}).state("reset-success",{url:"/password/reset/success",templateUrl:"modules/users/views/password/reset-password-success.client.view.html"}).state("reset",{url:"/password/reset/:token",templateUrl:"modules/users/views/password/reset-password.client.view.html"})}]),angular.module("users").controller("AuthenticationController",["$scope","$location","$state","$rootScope","User","Auth",function($scope,$location,$state,$rootScope,User,Auth){$scope=$rootScope,$scope.credentials={},$scope.error="",$scope.signin=function(){$scope.credentials.email=$scope.credentials.username,User.login($scope.credentials).then(function(response){Auth.login(response),$scope.user=$rootScope.user=Auth.ensureHasCurrentUser(User),"home"!==$state.previous.name&&"verify"!==$state.previous.name&&""!==$state.previous.name?$state.go($state.previous.name):$state.go("listForms")},function(error){$rootScope.user=Auth.ensureHasCurrentUser(User),$scope.user=$rootScope.user,$scope.error=error,console.log("loginError: "+error)})},$scope.signup=function(){console.log($scope.credentials),User.signup($scope.credentials).then(function(response){console.log("signup-success"),$state.go("signup-success")},function(error){console.log("Error: "),console.log(error),error?($scope.error=error,console.log(error)):console.log("No response received")})}}]),angular.module("users").controller("PasswordController",["$scope","$stateParams","$state","User",function($scope,$stateParams,$state,User){$scope.error="",$scope.askForPasswordReset=function(){User.askForPasswordReset($scope.credentials).then(function(response){$scope.success=response.message,$scope.credentials=null},function(error){$scope.error=error,$scope.credentials=null})},$scope.resetUserPassword=function(){$scope.success=$scope.error=null,User.resetPassword($scope.passwordDetails,$stateParams.token).then(function(response){$scope.success=response.message,$scope.passwordDetails=null,$state.go("reset-success")},function(error){$scope.error=error.message||error,$scope.passwordDetails=null})}}]),angular.module("users").controller("SettingsController",["$scope","$rootScope","$http","$state","Users",function($scope,$rootScope,$http,$state,Users){$scope.user=$rootScope.user,$scope.hasConnectedAdditionalSocialAccounts=function(provider){for(var i in $scope.user.additionalProvidersData)return!0;return!1},$scope.isConnectedSocialAccount=function(provider){return $scope.user.provider===provider||$scope.user.additionalProvidersData&&$scope.user.additionalProvidersData[provider]},$scope.removeUserSocialAccount=function(provider){$scope.success=$scope.error=null,$http["delete"]("/users/accounts",{params:{provider:provider}}).success(function(response){$scope.success=!0,$scope.user=response}).error(function(response){$scope.error=response.message})},$scope.updateUserProfile=function(isValid){if(isValid){$scope.success=$scope.error=null;var user=new Users($scope.user);user.$update(function(response){$scope.success=!0,$scope.user=response},function(response){$scope.error=response.data.message})}else $scope.submitted=!0},$scope.changeUserPassword=function(){$scope.success=$scope.error=null,$http.post("/users/password",$scope.passwordDetails).success(function(response){$scope.success=!0,$scope.passwordDetails=null}).error(function(response){$scope.error=response.message})}}]),angular.module("users").controller("VerifyController",["$scope","$state","$rootScope","User","Auth","$stateParams",function($scope,$state,$rootScope,User,Auth,$stateParams){$scope.isResetSent=!1,$scope.credentials={},$scope.error="",$scope.resendVerifyEmail=function(){User.resendVerifyEmail($scope.credentials.email).then(function(response){console.log(response),$scope.success=response.message,$scope.credentials=null,$scope.isResetSent=!0},function(error){$scope.error=error,$scope.credentials.email=null,$scope.isResetSent=!1})},$scope.validateVerifyToken=function(){$stateParams.token&&(console.log($stateParams.token),User.validateVerifyToken($stateParams.token).then(function(response){console.log("Success: "+response.message),$scope.success=response.message,$scope.isResetSent=!0,$scope.credentials.email=null},function(error){console.log("Error: "+error.message),$scope.isResetSent=!1,$scope.error=error,$scope.credentials.email=null}))}}]),angular.module("users").factory("Auth",["$window",function($window){var userState={isLoggedIn:!1},service={_currentUser:null,get currentUser(){return this._currentUser},ensureHasCurrentUser:function(User){return service._currentUser&&service._currentUser.username?service._currentUser:$window.user?(service._currentUser=$window.user,service._currentUser):void User.getCurrent().then(function(user){return service._currentUser=user,userState.isLoggedIn=!0,$window.user=service._currentUser,service._currentUser},function(response){return userState.isLoggedIn=!1,service._currentUser=null,$window.user=null,console.log("User.getCurrent() err",response),null})},isAuthenticated:function(){return!!service._currentUser},getUserState:function(){return userState},login:function(new_user){userState.isLoggedIn=!0,service._currentUser=new_user},logout:function(){$window.user=null,userState.isLoggedIn=!1,service._currentUser=null}};return service}]),angular.module("users").service("Authorizer",["APP_PERMISSIONS","USER_ROLES",function(APP_PERMISSIONS,USER_ROLES){return function(user){return{canAccess:function(permissions){var i,len,permission;for(angular.isArray(permissions)||(permissions=[permissions]),i=0,len=permissions.length;len>i;i++){if(permission=permissions[i],null===APP_PERMISSIONS[permission])throw"Bad permission value";if(!user||!user.roles)return!1;switch(permission){case APP_PERMISSIONS.viewAdminSettings:case APP_PERMISSIONS.editAdminSettings:return user.roles.indexOf(USER_ROLES.admin)>-1;case APP_PERMISSIONS.viewPrivateForm:case APP_PERMISSIONS.editForm:return user.roles.indexOf(USER_ROLES.admin)>-1||user.roles.indexOf(USER_ROLES.normal)>-1}}return!1}}}}]),angular.module("users").factory("User",["$window","$q","$timeout","$http","$state",function($window,$q,$timeout,$http,$state){var userService={getCurrent:function(){var deferred=$q.defer();return $http.get("/users/me").success(function(response){deferred.resolve(response)}).error(function(){deferred.reject("User's session has expired")}),deferred.promise},login:function(credentials){var deferred=$q.defer();return $http.post("/auth/signin",credentials).success(function(response){ -deferred.resolve(response)}).error(function(error){deferred.reject(error.message||error)}),deferred.promise},logout:function(){var deferred=$q.defer();return $http.get("/auth/signout").success(function(response){deferred.resolve(null)}).error(function(error){deferred.reject(error.message||error)}),deferred.promise},signup:function(credentials){var deferred=$q.defer();return $http.post("/auth/signup",credentials).success(function(response){deferred.resolve(response)}).error(function(error){deferred.reject(error.message||error)}),deferred.promise},resendVerifyEmail:function(_email){var deferred=$q.defer();return $http.post("/auth/verify",{email:_email}).success(function(response){deferred.resolve(response)}).error(function(error){deferred.reject(error.message||error)}),deferred.promise},validateVerifyToken:function(token){var validTokenRe=/^([A-Za-z0-9]{48})$/g;if(!validTokenRe.test(token))throw new Error("Error token: "+token+" is not a valid verification token");var deferred=$q.defer();return $http.get("/auth/verify/"+token).success(function(response){deferred.resolve(response)}).error(function(error){deferred.reject(error)}),deferred.promise},resetPassword:function(passwordDetails,token){var deferred=$q.defer();return $http.get("/auth/password/"+token,passwordDetails).success(function(response){deferred.resolve()}).error(function(error){deferred.reject(error.message||error)}),deferred.promise},askForPasswordReset:function(credentials){var deferred=$q.defer();return $http.post("/auth/forgot",credentials).success(function(response){deferred.resolve(response)}).error(function(error){deferred.reject(error.message||error)}),deferred.promise}};return userService}]),angular.module("users").factory("Users",["$resource",function($resource){return $resource("users",{},{update:{method:"PUT"}})}]),angular.module("core").config(["$translateProvider",function($translateProvider){$translateProvider.translations("en",{MENU:"MENU",SIGNUP_TAB:"Sign Up",SIGNIN_TAB:"Sign In",SIGNOUT_TAB:"Signout",EDIT_PROFILE:"Edit Profile",MY_FORMS:"My Forms",MY_SETTINGS:"My Settings",CHANGE_PASSWORD:"Change Password"}),$translateProvider.preferredLanguage("en").fallbackLanguage("en").useSanitizeValueStrategy("escape")}]),angular.module("core").config(["$translateProvider",function($translateProvider){$translateProvider.translations("fr",{MENU:"MENU",SIGNUP_TAB:"Créer un Compte",SIGNIN_TAB:"Connexion",SIGNOUT_TAB:"Créer un compte",EDIT_PROFILE:"Modifier Mon Profil",MY_FORMS:"Mes Formulaires",MY_SETTINGS:"Mes Paramètres",CHANGE_PASSWORD:"Changer mon Mot de Pass"})}]),angular.module("forms").controller("AdminFormController",["$rootScope","$scope","$stateParams","$state","Forms","CurrentForm","$http","$uibModal","myForm","$filter",function($rootScope,$scope,$stateParams,$state,Forms,CurrentForm,$http,$uibModal,myForm,$filter){$scope=$rootScope,$scope.animationsEnabled=!0,$scope.myform=myForm,$rootScope.saveInProgress=!1,CurrentForm.setForm($scope.myform),$scope.tabData=[{heading:$filter("translate")("CREATE_TAB"),route:"viewForm.create"},{heading:$filter("translate")("DESIGN_TAB"),route:"viewForm.design"},{heading:$filter("translate")("CONFIGURE_TAB"),route:"viewForm.configure"},{heading:$filter("translate")("ANALYZE_TAB"),route:"viewForm.analyze"}],$scope.setForm=function(form){$scope.myform=form},$rootScope.resetForm=function(){$scope.myform=Forms.get({formId:$stateParams.formId})},$scope.openDeleteModal=function(){$scope.deleteModal=$uibModal.open({animation:$scope.animationsEnabled,templateUrl:"myModalContent.html",controller:"AdminFormController",resolve:{myForm:function(){return $scope.myform}}}),$scope.deleteModal.result.then(function(selectedItem){$scope.selected=selectedItem},function(){console.log("Modal dismissed at: "+new Date)})},$scope.cancelDeleteModal=function(){$scope.deleteModal&&$scope.deleteModal.dismiss("cancel")},$scope.removeCurrentForm=function(){if($scope.deleteModal&&$scope.deleteModal.opened){$scope.deleteModal.close();var form_id=$scope.myform._id;if(!form_id)throw new Error("Error - removeCurrentForm(): $scope.myform._id does not exist");$http["delete"]("/forms/"+form_id).success(function(data,status,headers){console.log("form deleted successfully"),$state.go("listForms",{},{reload:!0})}).error(function(error){console.log("ERROR: Form could not be deleted."),console.error(error)})}},$scope.update=$rootScope.update=function(updateImmediately,cb){var continueUpdate=!0;if(updateImmediately||(continueUpdate=!$rootScope.saveInProgress),continueUpdate){var err=null;updateImmediately||($rootScope.saveInProgress=!0),$scope.updatePromise=$http.put("/forms/"+$scope.myform._id,{form:$scope.myform}).then(function(response){$rootScope.myform=$scope.myform=response.data})["catch"](function(response){console.log("Error occured during form UPDATE.\n"),err=response.data})["finally"](function(){return updateImmediately||($rootScope.saveInProgress=!1),"function"==typeof cb?cb(err):void 0})}}}]),angular.module("forms").controller("ListFormsController",["$rootScope","$scope","$stateParams","$state","Forms","CurrentForm","$http",function($rootScope,$scope,$stateParams,$state,Forms,CurrentForm,$http){$scope=$rootScope,$scope.forms={},$scope.showCreateModal=!1,$scope.languageRegExp=$scope.myPt={regExp:/[@!#$%^&*()\-+={}\[\]|\\/'";:`.,~№?<>]+/i,test:function(val){return!this.regExp.test(val)}},$scope.findAll=function(){Forms.query(function(_forms){$scope.myforms=_forms})},$scope.openCreateModal=function(){$scope.showCreateModal||($scope.showCreateModal=!0)},$scope.closeCreateModal=function(){$scope.showCreateModal&&($scope.showCreateModal=!1)},$scope.setForm=function(form){$scope.myform=form},$scope.goToWithId=function(route,id){$state.go(route,{formId:id},{reload:!0})},$scope.duplicateForm=function(form_index){var form=_.cloneDeep($scope.myforms[form_index]);delete form._id,$http.post("/forms",{form:form}).success(function(data,status,headers){$scope.myforms.splice(form_index+1,0,data)}).error(function(errorResponse){console.error(errorResponse),null===errorResponse&&($scope.error=errorResponse.data.message)})},$scope.createNewForm=function(){var form={};form.title=$scope.forms.createForm.title.$modelValue,form.language=$scope.forms.createForm.language.$modelValue,$scope.forms.createForm.$valid&&$scope.forms.createForm.$dirty&&$http.post("/forms",{form:form}).success(function(data,status,headers){$scope.goToWithId("viewForm.create",data._id+"")}).error(function(errorResponse){console.error(errorResponse),$scope.error=errorResponse.data.message})},$scope.removeForm=function(form_index){if(form_index>=$scope.myforms.length||0>form_index)throw new Error("Error: form_index in removeForm() must be between 0 and "+$scope.myforms.length-1);$http["delete"]("/forms/"+$scope.myforms[form_index]._id).success(function(data,status,headers){$scope.myforms.splice(form_index,1)}).error(function(error){console.error(error)})}}]),_.mixin({removeDateFields:removeDateFieldsFunc}),angular.module("forms").directive("autoSaveForm",["$rootScope","$timeout",function($rootScope,$timeout){return{require:["^form"],restrict:"AE",link:function($scope,$element,$attrs,$ctrls){angular.element(document).ready(function(){var $formCtrl=$ctrls[0],savePromise=null;$rootScope.finishedRender=!1,$scope.$on("editFormFields Started",function(ngRepeatFinishedEvent){$rootScope.finishedRender=!1}),$scope.$on("editFormFields Finished",function(ngRepeatFinishedEvent){$rootScope.finishedRender=!0}),$scope.anyDirtyAndTouched=function(form){var propCount=0;for(var prop in form)if(form.hasOwnProperty(prop)&&"$"!==prop[0]&&(propCount++,form[prop].$touched&&form[prop].$dirty))return!0;return!1};var debounceSave=function(){$rootScope.saveInProgress=!0,$rootScope[$attrs.autoSaveCallback](!0,function(err){err?(console.error("Error form data NOT persisted"),console.error(err)):($formCtrl.$setPristine(),$formCtrl.$setUntouched())})};$scope.$watch(function(newValue,oldValue){$rootScope.finishedRender&&$scope.anyDirtyAndTouched($scope.editForm)&&!$rootScope.saveInProgress&&debounceSave()}),$scope.$watch($attrs.autoSaveWatch,function(newValue,oldValue){newValue=angular.copy(newValue),oldValue=angular.copy(oldValue),newValue.form_fields=_.removeDateFields(newValue.form_fields),oldValue.form_fields=_.removeDateFields(oldValue.form_fields);var changedFields=!_.isEqual(oldValue.form_fields,newValue.form_fields)||!_.isEqual(oldValue.startPage,newValue.startPage),changedFieldMap=!1;oldValue.hasOwnProperty("plugins.oscarhost.settings.fieldMap")&&(changedFieldMap=!!oldValue.plugins.oscarhost.settings.fieldMap&&!_.isEqual(oldValue.plugins.oscarhost.settings.fieldMap,newValue.plugins.oscarhost.settings.fieldMap)),(newValue||oldValue)&&oldValue&&(0===oldValue.form_fields.length&&($rootScope.finishedRender=!0),$rootScope.finishedRender&&(changedFields&&!$formCtrl.$dirty||changedFieldMap)&&!$rootScope.saveInProgress?(savePromise&&($timeout.cancel(savePromise),savePromise=null),savePromise=$timeout(function(){debounceSave()})):$rootScope.finishedRender&&$rootScope.saveInProgress&&($rootScope.saveInProgress=!1))},!0)})}}}]),angular.module("forms").directive("configureFormDirective",["$rootScope","$http","Upload","CurrentForm",function($rootScope,$http,Upload,CurrentForm){return{templateUrl:"modules/forms/admin/views/directiveViews/form/configure-form.client.view.html",restrict:"E",scope:{myform:"=",user:"=",pdfFields:"@",formFields:"@"},controller:["$scope",function($scope){console.log($scope.myform),CurrentForm.getForm().plugins?CurrentForm.getForm().plugins.oscarhost.baseUrl&&($scope.oscarhostAPI=!0):$scope.oscarhostAPI=!1,$scope.log="",$scope.pdfLoading=!1,$scope.languages=$rootScope.languages,this._current_upload=null,$scope.resetForm=$rootScope.resetForm,$scope.update=$rootScope.update,this._unbindedPdfFields=$scope.pdfFields,$scope.cancelUpload=function(){this._current_upload.abort(),$scope.pdfLoading=!1,$scope.removePDF()},$scope.removePDF=function(){$scope.myform.pdf=null,$scope.myform.isGenerated=!1,$scope.myform.autofillPDFs=!1,console.log("form.pdf: "+$scope.myform.pdf+" REMOVED")},$scope.uploadPDF=function(file){file&&(console.log(file),Upload.upload({url:"/upload/pdf",data:{user:$scope.user,file:file}}).then(function(resp){var data=resp.data;$scope.log="file "+data.originalname+" uploaded as "+data.filename+". JSON: "+JSON.stringify(data)+"\n"+$scope.log,$scope.myform.pdf=angular.fromJson(angular.toJson(data)),$scope.pdfLoading=!1,console.log($scope.log),$scope.$$phase||$scope.$digest||$scope.$apply()},function(resp){$scope.pdfLoading=!1,console.log("Error occured during upload.\n"),console.log(resp.status)},function(evt){var progressPercentage=parseInt(100*evt.loaded/evt.total,10);$scope.log="progress: "+progressPercentage+"% "+evt.config.data.file.name+"\n"+$scope.log,console.log($scope.log),$scope.pdfLoading=!0}))}}]}}]),angular.module("forms").directive("editFormDirective",["$rootScope","FormFields",function($rootScope,FormFields){return{templateUrl:"modules/forms/admin/views/directiveViews/form/edit-form.client.view.html",restrict:"E",scope:{myform:"="},controller:["$scope",function($scope){console.log($scope.myform);for(var field_ids=_($scope.myform.form_fields).pluck("_id"),i=0;i0){$scope.myform.plugins.oscarhost.settings.fieldMap||($scope.myform.plugins.oscarhost.settings.fieldMap={});var oscarhostFields=$scope.myform.plugins.oscarhost.settings.validFields,currentFields=_($scope.myform.plugins.oscarhost.settings.fieldMap).invert().keys().value();return $scope.myform.plugins.oscarhost.settings.fieldMap.hasOwnProperty(field_id)&&(currentFields=_(currentFields).difference($scope.myform.plugins.oscarhost.settings.fieldMap[field_id])),_(oscarhostFields).difference(currentFields).value()}return[]},$scope.dropzone={handle:".handle",containment:".dropzoneContainer",cursor:"grabbing"},$scope.addNewField=function(modifyForm,fieldType){$scope.addField.lastAddedID++;for(var fieldTitle,i=0;i<$scope.addField.types.length;i++)if($scope.addField.types[i].name===fieldType){$scope.addField.types[i].lastAddedID++,fieldTitle=$scope.addField.types[i].value+$scope.addField.types[i].lastAddedID;break}var newField={title:fieldTitle,fieldType:fieldType,fieldValue:"",required:!0,disabled:!1,deletePreserved:!1};return $scope.showAddOptions(newField)&&(newField.fieldOptions=[],newField.fieldOptions.push({option_id:Math.floor(1e5*Math.random()),option_title:"Option 0",option_value:"Option 0"})),modifyForm&&$scope.myform.form_fields.push(newField),newField},$scope.deleteField=function(field_index){var currFieldId=$scope.myform.form_fields[field_index]._id;$scope.myform.hasOwnProperty("plugins.oscarhost.baseUrl")&&delete $scope.myform.plugins.oscarhost.settings.fieldMap[currFieldId],$scope.myform.form_fields.splice(field_index,1)},$scope.duplicateField=function(field_index){var currField=_.cloneDeep($scope.myform.form_fields[field_index]);currField._id="cloned"+_.uniqueId(),currField.title+=" copy",$scope.myform.form_fields.splice(field_index+1,0,currField)},$scope.addButton=function(){var newButton={};newButton.bgColor="#ddd",newButton.color="#ffffff",newButton.text="Button",newButton._id=Math.floor(1e5*Math.random()),$scope.myform.startPage.buttons.push(newButton)},$scope.deleteButton=function(button){for(var currID,i=0;i<$scope.myform.startPage.buttons.length;i++)if(currID=$scope.myform.startPage.buttons[i]._id,console.log(currID),currID===button._id){$scope.myform.startPage.buttons.splice(i,1);break}},$scope.addOption=function(field_index){var currField=$scope.myform.form_fields[field_index];if("checkbox"===currField.fieldType||"dropdown"===currField.fieldType||"radio"===currField.fieldType){currField.fieldOptions||($scope.myform.form_fields[field_index].fieldOptions=[]);var lastOptionID=$scope.myform.form_fields[field_index].fieldOptions.length+1,newOption={option_id:Math.floor(1e5*Math.random()),option_title:"Option "+lastOptionID,option_value:"Option "+lastOptionID};$scope.myform.form_fields[field_index].fieldOptions.push(newOption)}},$scope.deleteOption=function(field_index,option){var currField=$scope.myform.form_fields[field_index];if("checkbox"===currField.fieldType||"dropdown"===currField.fieldType||"radio"===currField.fieldType)for(var i=0;i',restrict:"E",scope:{typeName:"@"},controller:["$scope",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]}]}});var __indexOf=[].indexOf||function(item){for(var i=0,l=this.length;l>i;i++)if(i in this&&this[i]===item)return i;return-1};angular.module("forms").directive("fieldDirective",["$http","$compile","$rootScope","$templateCache","supportedFields",function($http,$compile,$rootScope,$templateCache,supportedFields){var getTemplateUrl=function(fieldType){var type=fieldType,templateUrl="modules/forms/base/views/directiveViews/field/";return __indexOf.call(supportedFields,type)>=0&&(templateUrl=templateUrl+type+".html"),$templateCache.get(templateUrl)};return{template:"
{{field.title}}
",restrict:"E",scope:{field:"=",required:"&",design:"=",index:"=",forms:"="},link:function(scope,element){$rootScope.chooseDefaultOption=scope.chooseDefaultOption=function(type){"yes_no"===type?scope.field.fieldValue="true":"rating"===type?scope.field.fieldValue=0:"radio"===scope.field.fieldType?(console.log(scope.field),scope.field.fieldValue=scope.field.fieldOptions[0].option_value,console.log(scope.field.fieldValue)):"legal"===type&&(scope.field.fieldValue="true",$rootScope.nextField())},scope.setActiveField=$rootScope.setActiveField,"date"===scope.field.fieldType&&(scope.dateOptions={changeYear:!0,changeMonth:!0,altFormat:"mm/dd/yyyy",yearRange:"1900:-0",defaultDate:0});var fieldType=scope.field.fieldType;if("number"===scope.field.fieldType||"textfield"===scope.field.fieldType||"email"===scope.field.fieldType||"link"===scope.field.fieldType){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"}fieldType="textfield"}var template=getTemplateUrl(fieldType);element.html(template).show();$compile(element.contents())(scope)}}}]),angular.module("forms").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,onEnterKeyDisabled=!1;null!==$attrs.onEnterKeyDisabled&&(onEnterKeyDisabled=$attrs.onEnterKeyDisabled),13!==keyCode||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;9!==keyCode||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;13!==keyCode&&9!==keyCode||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;9===keyCode&&event.shiftKey&&(event.preventDefault(),$rootScope.$apply(function(){$rootScope.$eval($attrs.onTabAndShiftKey)}))})}}}]),angular.module("forms").directive("onFinishRender",["$rootScope","$timeout",function($rootScope,$timeout){return{restrict:"A",link:function(scope,element,attrs){if(element.attr("ng-repeat")||element.attr("data-ng-repeat")){var broadcastMessage=attrs.onFinishRender||"ngRepeat";scope.$first&&!scope.$last?scope.$evalAsync(function(){$rootScope.$broadcast(broadcastMessage+" Started")}):scope.$last&&scope.$evalAsync(function(){$rootScope.$broadcast(broadcastMessage+" Finished")})}}}}]),angular.module("forms").directive("submitFormDirective",["$http","TimeCounter","$filter","$rootScope","Auth","SendVisitorData",function($http,TimeCounter,$filter,$rootScope,Auth,SendVisitorData){return{templateUrl:"modules/forms/base/views/directiveViews/form/submit-form.client.view.html",restrict:"E",scope:{myform:"="},controller:["$document","$window","$scope",function($document,$window,$scope){$scope.authentication=$rootScope.authentication,$scope.noscroll=!1,$scope.forms={};var form_fields_count=$scope.myform.visible_form_fields.filter(function(field){return"statement"!==field.fieldType&&"rating"!==field.fieldType}).length,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(){$scope.myform.submitted=!1,$scope.myform.form_fields=_.chain($scope.myform.visible_form_fields).map(function(field){return field.fieldValue="",field}).value(),$scope.loading=!1,$scope.error="",$scope.selected={_id:"",index:0},$scope.setActiveField($scope.myform.visible_form_fields[0]._id,0,!1),TimeCounter.restartClock()},$window.onscroll=function(){$scope.scrollPos=document.body.scrollTop||document.documentElement.scrollTop||0;var elemBox=document.getElementsByClassName("activeField")[0].getBoundingClientRect();$scope.fieldTop=elemBox.top,$scope.fieldBottom=elemBox.bottom;var field_id,field_index;$scope.noscroll||($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,!1)):$scope.selected.index===$scope.myform.visible_form_fields.length?$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,!1)):$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,!1)):0!==$scope.selected.index&&$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,!1)),$scope.$apply())};var getActiveField=function(){if(null===$scope.selected)throw console.error("current active field is null"),new Error("current active field is null");return"submit_field"===$scope.selected._id?$scope.myform.form_fields.length-1:$scope.selected.index};$scope.setActiveField=$rootScope.setActiveField=function(field_id,field_index,animateScroll){if(null!==$scope.selected&&$scope.selected._id!==field_id){$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},animateScroll?($scope.noscroll=!0,setTimeout(function(){$document.scrollToElement(angular.element(".activeField"),-10,200).then(function(){$scope.noscroll=!1,setTimeout(function(){document.querySelectorAll(".activeField .focusOn").length?document.querySelectorAll(".activeField .focusOn")[0].focus():document.querySelectorAll(".activeField input").length?document.querySelectorAll(".activeField input")[0].focus():document.querySelectorAll(".activeField .selectize-input")[0].focus()})})})):setTimeout(function(){document.querySelectorAll(".activeField .focusOn")[0]?document.querySelectorAll(".activeField .focusOn")[0].focus():document.querySelectorAll(".activeField input")[0].focus()}),SendVisitorData.send($scope.myform,getActiveField(),TimeCounter.getTimeElapsed())}},$rootScope.nextField=$scope.nextField=function(){var selected_index,selected_id;$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,!0)):$scope.selected.index===$scope.myform.visible_form_fields.length-1&&(selected_index=$scope.selected.index+1,selected_id="submit_field",$rootScope.setActiveField(selected_id,selected_index,!0))},$rootScope.prevField=$scope.prevField=function(){if($scope.selected.index>0){var selected_index=$scope.selected.index-1,selected_id=$scope.myform.visible_form_fields[selected_index]._id;$scope.setActiveField(selected_id,selected_index,!0)}},$scope.exitStartPage=function(){$scope.myform.startPage.showStart=!1,$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=!0;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++)"dropdown"!==$scope.myform.form_fields[i].fieldType||$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=!0,$scope.loading=!1,SendVisitorData.send($scope.myform,getActiveField(),_timeElapsed)}).error(function(error){$scope.loading=!1,console.error(error),$scope.error=error.message})},500)},$scope.reloadForm()}]}}]),angular.module("forms").service("CurrentForm",function(){var _form={};this.getForm=function(){return _form},this.setForm=function(form){_form=form}}),angular.module("forms").factory("Forms",["$resource","FORM_URL",function($resource,FORM_URL){return $resource(FORM_URL,{formId:"@_id"},{query:{method:"GET",isArray:!0},get:{method:"GET",transformResponse:function(data,header){var form=angular.fromJson(data);return form.visible_form_fields=_.filter(form.form_fields,function(field){return field.deletePreserved===!1}),form}},update:{method:"PUT"},save:{method:"POST"}})}]),angular.module("forms").service("TimeCounter",[function(){var _startTime,_endTime=null;this.timeSpent=0,this.restartClock=function(){_startTime=Date.now(),_endTime=null},this.getTimeElapsed=function(){return _startTime?Math.abs(Date.now().valueOf()-_startTime.valueOf())/1e3:void 0},this.stopClock=function(){return _startTime&&null===_endTime?(_endTime=Date.now(),this.timeSpent=Math.abs(_endTime.valueOf()-_startTime.valueOf())/1e3,this._startTime=this._endTime=null,this.timeSpent):new Error("Clock has not been started")},this.clockStarted=function(){return!!this._startTime}}]),angular.module("users").config(["$translateProvider",function($translateProvider){$translateProvider.translations("en",{ACCESS_DENIED_TEXT:"You need to be logged in to access this page",USERNAME_LABEL:"Username",PASSWORD_LABEL:"Password",CURRENT_PASSWORD_LABEL:"Current Password",NEW_PASSWORD_LABEL:"New Password",VERIFY_PASSWORD_LABEL:"Verify Password",UPDATE_PASSWORD_LABEL:"Update Password",FIRST_NAME_LABEL:"First Name",LAST_NAME_LABEL:"Last Name",LANGUAGE_LABEL:"Language",EMAIL_LABEL:"Email",UPDATE_PROFILE_BTN:"Update Profile",PROFILE_SAVE_SUCCESS:"Profile saved successfully",PROFILE_SAVE_ERROR:"Could't Save Your Profile.",FORGOT_PASSWORD_LINK:"Forgot your password?",REVERIFY_ACCOUNT_LINK:"Resend your verification email",SIGNIN_BTN:"Sign in",SIGNUP_BTN:"Sign up",SAVE_PASSWORD_BTN:"Save Password", -SUCCESS_HEADER:"Signup Successful",SUCCESS_TEXT:"You've successfully registered an account at TellForm.",VERIFICATION_EMAIL_SENT:"A verification email has been sent to",NOT_ACTIVATED_YET:"But your account is not activated yet",BEFORE_YOU_CONTINUE:"Before you continue, make sure to check your email for our verification. If you don't receive it within 24h drop us a line at ",CHECK_YOUR_EMAIL:"Check your email and click on the activation link to activate your account. If you have any questions drop us a line at",PASSWORD_RESTORE_HEADER:"Restore your password",ENTER_YOUR_EMAIL:"Enter your account email.",SUBMIT_BTN:"Submit",ASK_FOR_NEW_PASSWORD:"Ask for new password reset",PASSWORD_RESET_INVALID:"Password reset is invalid",PASSWORD_RESET_SUCCESS:"Passport successfully reset",PASSWORD_CHANGE_SUCCESS:"Passport successfully changed",CONTINUE_TO_LOGIN:"Continue to login page",VERIFY_SUCCESS:"Account successfully activated",VERIFY_ERROR:"Verification link is invalid or has expired"}),$translateProvider.preferredLanguage("en").fallbackLanguage("en").useSanitizeValueStrategy("escape")}]),angular.module("users").config(["$translateProvider",function($translateProvider){$translateProvider.translations("en",{ACCESS_DENIED_TEXT:"Vouz est pas autorisé pour accese cete page.",USERNAME_LABEL:"Nom de Compte",PASSWORD_LABEL:"Mot de Pass",CURRENT_PASSWORD_LABEL:"Current Password",NEW_PASSWORD_LABEL:"Nouveau Mot de Pass Password",VERIFY_PASSWORD_LABEL:"Verify Password",UPDATE_PASSWORD_LABEL:"Update Password",FIRST_NAME_LABEL:"Premiere Nom Name",LAST_NAME_LABEL:"Surnom",LANGUAGE_LABEL:"Language",EMAIL_LABEL:"Email",UPDATE_PROFILE_BTN:"Modifier Profile",PROFILE_SAVE_SUCCESS:"Profile saved successfully",PROFILE_SAVE_ERROR:"Erreur: On peux pas enregistré votre Profile.",FORGOT_PASSWORD_LINK:"Oublier votre mot de pass?",REVERIFY_ACCOUNT_LINK:"Re-envoyez ton email de verification",SIGNIN_BTN:"Connexion",SIGNUP_BTN:"Créer un compte",SAVE_PASSWORD_BTN:"Enregistreé ton nouveau Mot de Pass",SUCCESS_HEADER:"Votre Compte a été enregistré!",SUCCESS_TEXT:"Vouz a enregistré un compte a TellForm.",VERIFICATION_EMAIL_SENT:"Un email de verification a été envoyer a",NOT_ACTIVATED_YET:"Mais votre compte n'est pas activé",BEFORE_YOU_CONTINUE:"Plutôt que vouz continué, vouz devrez voire ton inbox pour notre message de verification. Si tu receivoir-pas un message de verification dan le prochaine 24h, contactez nous a ",CHECK_YOUR_EMAIL:"Check your email and click on the activation link to activate your account. If you have any questions drop us a line at",PASSWORD_RESTORE_HEADER:"Restore your password",ENTER_YOUR_EMAIL:"Entrer votre email de compte",SUBMIT_BTN:"Enregistrer",ASK_FOR_NEW_PASSWORD:"Demander un nouveau mot de pass ",PASSWORD_RESET_INVALID:"Password reset is invalid",PASSWORD_RESET_SUCCESS:"Passport successfully reset",PASSWORD_CHANGE_SUCCESS:"Passport successfully changed",CONTINUE_TO_LOGIN:"Allez au page de connexion",VERIFY_SUCCESS:"Compte est activé!",VERIFY_ERROR:"Le fléche de verification est invalid ou expireé"})}]); \ No newline at end of file +"use strict";function removeDateFieldsFunc(o){function eachObject(v,k){"lastModified"!==k&&"created"!==k||delete clone[k]}for(var clone=_.clone(o),i=0;i'),$templateCache.put("modules/forms/admin/views/admin-form.client.view.html",'
'),$templateCache.put("modules/forms/admin/views/list-forms.client.view.html",'

{{ \'CREATE_A_NEW_FORM\' | translate }}
Name
Language

'),$templateCache.put("modules/forms/base/views/submit-form.client.view.html","
"),$templateCache.put("modules/forms/admin/views/adminTabs/analyze.html",""),$templateCache.put("modules/forms/admin/views/adminTabs/configure.html",""),$templateCache.put("modules/forms/admin/views/adminTabs/create.html",""),$templateCache.put("modules/forms/admin/views/adminTabs/design.html",'
{{ \'BACKGROUND_COLOR\' | translate }}
{{ \'QUESTION_TEXT_COLOR\' | translate }}
{{ \'ANSWER_TEXT_COLOR\' | translate }}
{{ \'BTN_BACKGROUND_COLOR\' | translate }}
{{ \'BTN_TEXT_COLOR\' | translate }}
'),$templateCache.put("modules/forms/admin/views/directiveViews/cgBusy/update-form-message-TypeA.html",'
{{$message}}
'),$templateCache.put("modules/forms/admin/views/directiveViews/cgBusy/update-form-message-TypeB.html",'
{{$message}}
'),$templateCache.put("modules/forms/admin/views/directiveViews/form/configure-form.client.view.html",'
{{ \'SAVE_PDF_SUBMISSIONS\' | translate }}
{{ \'UPLOAD_YOUR_PDF\' | translate }}
{{myform.pdf.name}}
{{ \'UPLOAD_YOUR_PDF\' | translate }}
{{ \'Autogenerate Form?\' | translate }}
{{ \'FORM_NAME\' | translate }}
{{ \'FORM_STATUS\' | translate }}
{{ \'GA_TRACKING_CODE\' | translate }}
Language
* required
{{ \'DISPLAY_FOOTER\' | translate }}
Display Start Page?
'),$templateCache.put("modules/forms/admin/views/directiveViews/form/edit-form.client.view.html",'

{{ \'EDIT_START_PAGE\' | translate }}


{{ \'INTRO_TITLE\' | translate }}:
{{ \'INTRO_PARAGRAPH\' | translate }}:
{{ \'INTRO_BTN\' | translate }}:


Buttons:
{{ \'BUTTON_TEXT\' | translate }}
{{ \'BUTTON_LINK\' | translate }}


{{field.title}} *

{{ \'EDIT_FIELD\' | translate }}


{{ \'QUESTION_TITLE\' | translate }}:

{{ \'QUESTION_DESCRIPTION\' | translate }}:

{{ \'OPTIONS\' | translate }}:

{{ \'NUM_OF_STEPS\' | translate }}

Shape:

Required:
Disabled:

{{ \'CLICK_FIELDS_FOOTER\' | translate }}


'),$templateCache.put("modules/forms/admin/views/directiveViews/form/edit-submissions-form.client.view.html",'
Overview Analytics
{{ \'TOTAL_VIEWS\' | translate }}
{{ \'RESPONSES\' | translate }}
{{ \'COMPLETION_RATE\' | translate }}
{{ \'AVERAGE_TIME_TO_COMPLETE\' | translate }}
{{myform.analytics.views}}
{{myform.analytics.submissions}}
{{myform.analytics.conversionRate | number:0}}%
{{AverageTimeElapsed | secondsToDateTime | date:\'mm:ss\'}}
Device Analytics
{{ \'DESKTOP_AND_LAPTOP\' | translate }}
{{ \'TABLETS\' | translate }}
{{ \'PHONES\' | translate }}
{{ \'OTHER\' | translate }}
{{ \'UNIQUE_VISITS\' | translate }}
{{DeviceStatistics.desktop.visits}}
{{ \'UNIQUE_VISITS\' | translate }}
{{DeviceStatistics.tablet.visits}}
{{ \'UNIQUE_VISITS\' | translate }}
{{DeviceStatistics.tablet.visits}}
{{ \'UNIQUE_VISITS\' | translate }}
{{DeviceStatistics.other.visits}}
{{ \'RESPONSES\' | translate }}
{{DeviceStatistics.desktop.responses}}
{{ \'RESPONSES\' | translate }}
{{DeviceStatistics.tablet.responses}}
{{ \'RESPONSES\' | translate }}
{{DeviceStatistics.phone.responses}}
{{ \'RESPONSES\' | translate }}
{{DeviceStatistics.other.responses}}
{{ \'COMPLETION_RATE\' | translate }}
{{DeviceStatistics.desktop.completion}}
{{ \'COMPLETION_RATE\' | translate }}
{{DeviceStatistics.tablet.completion}}
{{ \'COMPLETION_RATE\' | translate }}
{{DeviceStatistics.phone.completion}}
{{ \'COMPLETION_RATE\' | translate }}
{{DeviceStatistics.other.completion}}
{{ \'AVERAGE_TIME_TO_COMPLETE\' | translate }}
{{DeviceStatistics.desktop.average_time | secondsToDateTime | date:\'mm:ss\'}}
{{ \'AVERAGE_TIME_TO_COMPLETE\' | translate }}
{{DeviceStatistics.tablet.average_time | secondsToDateTime | date:\'mm:ss\'}}
{{ \'AVERAGE_TIME_TO_COMPLETE\' | translate }}
{{DeviceStatistics.phone.average_time | secondsToDateTime | date:\'mm:ss\'}}
{{ \'AVERAGE_TIME_TO_COMPLETE\' | translate }}
{{DeviceStatistics.other.average_time | secondsToDateTime | date:\'mm:ss\'}}
Field Analytics
{{ \'FIELD_TITLE\' | translate }}
{{ \'FIELD_VIEWS\' | translate }}
{{ \'FIELD_RESPONSES\' | translate }}
{{ \'FIELD_DROPOFF\' | translate }}
{{fieldStats.field.title}}
{{fieldStats.totalViews}}
{{fieldStats.responses}}
{{fieldStats.continueRate}}%

Responses Table
#{{value.title}}{{ \'PERCENTAGE_COMPLETE\' | translate }}{{ \'TIME_ELAPSED\' | translate }}{{ \'DEVICE\' | translate }}{{ \'LOCATION\' | translate }}{{ \'IP_ADDRESS\' | translate }}{{ \'DATE_SUBMITTED\' | translate }} (UTC){{ \'GENERATED_PDF\' | translate }}
{{$index+1}}{{field.fieldValue}}{{row.percentageComplete}}%{{row.timeElapsed | secondsToDateTime | date:\'mm:ss\'}}{{row.device.name}}, {{row.device.type}}{{row.geoLocation.city}}, {{row.geoLocation.country_name}}{{row.ipAddr}}{{row.created | date:\'yyyy-MM-dd HH:mm:ss\'}}{{ \'GENERATED_PDF\' | translate }}
'), +$templateCache.put("modules/forms/base/views/directiveViews/entryPage/startPage.html",'

{{pageData.introTitle}}

{{pageData.introParagraph}}

'),$templateCache.put("modules/forms/base/views/directiveViews/field/date.html",'

{{index+1}} {{field.title}} {{ \'OPTIONAL\' | translate }}

{{field.description}}

'),$templateCache.put("modules/forms/base/views/directiveViews/field/dropdown.html",'
'),$templateCache.put("modules/forms/base/views/directiveViews/field/file.html",'

{{index+1}} {{field.title}} {{ \'OPTIONAL\' | translate }}

{{field.file.originalname}}
{{ UPLOAD_FILE | translate }}
'),$templateCache.put("modules/forms/base/views/directiveViews/field/hidden.html",''),$templateCache.put("modules/forms/base/views/directiveViews/field/legal.html",'
'),$templateCache.put("modules/forms/base/views/directiveViews/field/radio.html",'

{{index+1}} {{field.title}} {{ \'OPTIONAL\' | translate }}

{{field.description}}


'),$templateCache.put("modules/forms/base/views/directiveViews/field/rating.html",'

{{index+1}} {{field.title}} {{ \'OPTIONAL\' | translate }}

{{field.description}}

'),$templateCache.put("modules/forms/base/views/directiveViews/field/statement.html",'

{{field.title}}

{{field.description}}

{{field.description}}


'),$templateCache.put("modules/forms/base/views/directiveViews/field/textarea.html",'

{{index+1}} {{field.title}} {{ \'OPTIONAL\' | translate }}

{{ \'NEWLINE\' | translate }}

{{field.description}}

Press SHIFT+ENTER to add a newline
'),$templateCache.put("modules/forms/base/views/directiveViews/field/textfield.html",'

{{index+1}} {{field.title}} ({{ \'OPTIONAL\' | translate }})

{{field.description}}

'),$templateCache.put("modules/forms/base/views/directiveViews/field/yes_no.html",'

{{index+1}} {{field.title}} {{ \'OPTIONAL\' | translate }}

{{field.description}}


'),$templateCache.put("modules/forms/base/views/directiveViews/form/submit-form.client.view.html",'
{{ \'COMPLETING_NEEDED\' | translate:translateAdvancementData }}
'),$templateCache.put("modules/users/views/authentication/access-denied.client.view.html","

{{ 'ACCESS_DENIED_TEXT' | translate }}

{{ 'SIGNIN_BTN' | translate }}
"),$templateCache.put("modules/users/views/authentication/signin.client.view.html",'

Sign into your account

'),$templateCache.put("modules/users/views/authentication/signup-success.client.view.html",''),$templateCache.put("modules/users/views/authentication/signup.client.view.html",''),$templateCache.put("modules/users/views/password/forgot-password.client.view.html",'

{{ \'PASSWORD_RESTORE_HEADER\' | translate }}

{{ \'ENTER_YOUR_EMAIL\' | translate }}

'),$templateCache.put("modules/users/views/password/reset-password-invalid.client.view.html","

{{ 'PASSWORD_RESET_INVALID' | translate }}

{{ 'ASK_FOR_NEW_PASSWORD' | translate }}
"),$templateCache.put("modules/users/views/password/reset-password-success.client.view.html","

{{ 'PASSWORD_RESET_SUCCESS' | translate }}

{{ 'CONTINUE_TO_LOGIN' | translate }}
"),$templateCache.put("modules/users/views/password/reset-password.client.view.html",'

Reset your password

'),$templateCache.put("modules/users/views/settings/change-password.client.view.html",'

Change your password

'),$templateCache.put("modules/users/views/settings/edit-profile.client.view.html",'1

Edit your profile

'), +$templateCache.put("modules/users/views/settings/social-accounts.client.view.html",'

Connected social accounts:

Connect other social accounts:

'),$templateCache.put("modules/users/views/verify/resend-verify-email.client.view.html",'

Resend your account verification email

Enter your account email.

{{error}}

Verification Email has been Sent

{{ \'VERIFICATION_EMAIL_SENT\' | translate }} {{username}}.
{{ \'NOT_ACTIVATED_YET\' | translate }}

{{ \'CHECK_YOUR_EMAIL\' | translate }} polydaic@gmail.com

'),$templateCache.put("modules/users/views/verify/verify-account.client.view.html","

{{ 'CONTINUE_TO_LOGIN' | translate }}

{{ 'VERIFY_ERROR' | translate }}

{{ 'REVERIFY_ACCOUNT_LINK' | translate }} {{ 'SIGNIN_BTN' | translate }}
")}]),ApplicationConfiguration.registerModule("core",["users"]),ApplicationConfiguration.registerModule("forms",["ngFileUpload","ui.router.tabs","ui.date","ui.sortable","angular-input-stars","users"]),ApplicationConfiguration.registerModule("users"),angular.module("forms").config(["$translateProvider",function($translateProvider){$translateProvider.translations("en",{PDF_GENERATION_EMR:"PDF Generation/EMR",SAVE_PDF_SUBMISSIONS:"Save Submissions as PDFs?",UPLOAD_YOUR_PDF:"Upload Your PDF Template",ADVANCED_SETTINGS:"Advanced Settings",FORM_NAME:"Form Name",FORM_STATUS:"Form Status",PUBLIC:"Public",PRIVATE:"Private",GA_TRACKING_CODE:"Google Analytics Tracking Code",DISPLAY_FOOTER:"Display Form Footer?",SAVE_CHANGES:"Save Changes",CANCEL:"Cancel",CREATE_A_NEW_FORM:"Create a new form",CREATE_FORM:"Create form",CREATED_ON:"Created on",ARE_YOU_SURE:"Are you ABSOLUTELY sure?",READ_WARNING:"Unexpected bad things will happen if you don’t read this!",DELETE_WARNING1:"This action CANNOT be undone.This will permanently delete the",DELETE_WARNING2:"form, form submissions and remove all associated pdfs.",DELETE_CONFIRM:"Please type in the name of the form to confirm",I_UNDERSTAND:"I understand the consequences, delete this form",DELETE_FORM_SM:"Delete",DELETE_FORM_MD:"Delete Form",DELETE:"Delete",FORM:"Form",VIEW:"View",LIVE:"Live",PREVIEW:"Preview",ADD_FIELD_LG:"Click to Add New Field",ADD_FIELD_MD:"Add New Field",ADD_FIELD_SM:"Add Field",PREVIEW_START_PAGE:"Preview Start Page",EDIT_START_PAGE:"Edit Start Page",INTRO_TTILE:"Intro Title",INTRO_PARAGRAPH:"Intro Paragraph",INTRO_BTN:"Intro Button",BUTTONS:"Buttons",BUTTON_TEXT:"Text",BUTTON_LINK:"Link",ADD_BUTTON:"Add Button",PREVIEW_FIELD:"Preview Field",EDIT_FIELD:"Edit Field",QUESTION_TITLE:"Question Title",QUESTION_DESCRIPTION:"Question Description",OPTIONS:"Options",ADD_OPTION:"Add Option",NUM_OF_STEPS:"Number of Steps",CLICK_FIELDS_FOOTER:"Click on fields to add them here",TOTAL_VIEWS:"total unique visits",RESPONSES:"responses",COMPLETION_RATE:"completion rate",AVERAGE_TIME_TO_COMPLETE:"avg. completion time",DESKTOP_AND_LAPTOP:"Desktops",TABLETS:"Tablets",PHONES:"Phones",OTHER:"Other",UNIQUE_VISITS:"Unique Visits",FIELD_TITLE:"Field Title",FIELD_VIEWS:"Field Views",FIELD_DROPOFF:"Field Completion",FIELD_RESPONSES:"Field Responses",DELETE_SELECTED:"Delete Selected",EXPORT_TO_EXCEL:"Export to Excel",EXPORT_TO_CSV:"Export to CSV",EXPORT_TO_JSON:"Export to JSON",PERCENTAGE_COMPLETE:"Percentage Complete",TIME_ELAPSED:"Time Elapsed",DEVICE:"Device",LOCATION:"Location",IP_ADDRESS:"IP Address",DATE_SUBMITTED:"Date Submitted",GENERATED_PDF:"Generated PDF",BACKGROUND_COLOR:"Background Color",DESIGN_HEADER:"Change how your Form Looks",QUESTION_TEXT_COLOR:"Question Text Color",ANSWER_TEXT_COLOR:"Answer Text Color",BTN_BACKGROUND_COLOR:"Button Background Color",BTN_TEXT_COLOR:"Button Text Color",CREATE_TAB:"Create",DESIGN_TAB:"Design",CONFIGURE_TAB:"Configure",ANALYZE_TAB:"Analyze"})}]),angular.module("forms").config(["$translateProvider",function($translateProvider){$translateProvider.translations("en",{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"})}]),angular.module("forms").config(["$translateProvider",function($translateProvider){$translateProvider.translations("fr",{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"})}]),angular.module("forms").config(["$translateProvider",function($translateProvider){$translateProvider.translations("de",{FORM_SUCCESS:"Ihre Angaben wurden gespeichert.",REVIEW:"Unvollständig",BACK_TO_FORM:"Zurück zum Formular",EDIT_FORM:"Bearbeiten Sie diese TellForm",CREATE_FORM:"Erstellen Sie eine TellForm",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:"Ich akzeptiere",LEGAL_NO_ACCEPT:"Ich akzeptiere nicht",DELETE:"Entfernen",CANCEL:"Canceln",SUBMIT:"Speichern",UPLOAD_FILE:"Datei versenden",Y:"J",N:"N"})}]),angular.module("forms").config(["$translateProvider",function($translateProvider){$translateProvider.translations("it",{FORM_SUCCESS:"Il formulario è stato inviato con successo!",REVIEW:"Incompleto",BACK_TO_FORM:"Ritorna al formulario",EDIT_FORM:"Modifica questo Tellform",CREATE_FORM:"Creare un TellForm",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:"Accetto",LEGAL_NO_ACCEPT:"Non accetto",DELETE:"Cancella",CANCEL:"Reset",SUBMIT:"Registra",UPLOAD_FILE:"Invia un file",Y:"S",N:"N"})}]),angular.module("forms").config(["$translateProvider",function($translateProvider){$translateProvider.translations("es",{FORM_SUCCESS:"¡El formulario ha sido enviado con éxito!",REVIEW:"Revisar",BACK_TO_FORM:"Regresar al formulario",EDIT_FORM:"Crear un TellForm",CREATE_FORM:"Editar este TellForm",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:"Acepto",LEGAL_NO_ACCEPT:"No acepto",DELETE:"Eliminar",CANCEL:"Cancelar",SUBMIT:"Registrar",UPLOAD_FILE:"Cargar el archivo",Y:"S",N:"N"})}]),angular.module("core").config(["$stateProvider","$urlRouterProvider",function($stateProvider,$urlRouterProvider,Authorization){$urlRouterProvider.otherwise("/forms")}]),angular.module(ApplicationConfiguration.applicationModuleName).run(["$rootScope","Auth","$state","$stateParams",function($rootScope,Auth,$state,$stateParams){$rootScope.$state=$state,$rootScope.$stateParams=$stateParams,$rootScope.$on("$stateChangeSuccess",function(event,toState,toParams,fromState){$state.previous=fromState;var statesToIgnore=["home","signin","resendVerifyEmail","verify","signup","signup-success","forgot","reset-invalid","reset","reset-success"];statesToIgnore.indexOf(toState.name)>0?Auth.isAuthenticated()&&(event.preventDefault(),$state.go("listForms")):"access_denied"===toState.name||Auth.isAuthenticated()||"submitForm"===toState.name||(console.log("go to signup"),event.preventDefault(),$state.go("listForms"))})}]),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,user&&(authenticator=new Authorizer(user),null!==permissions&&(authenticator.canAccess(permissions)||(event.preventDefault(),$state.go("access_denied"))))})}]),angular.module("core").controller("HeaderController",["$rootScope","$scope","Menus","$state","Auth","User","$window","$translate","$locale",function($rootScope,$scope,Menus,$state,Auth,User,$window,$translate,$locale){$rootScope.signupDisabled=$window.signupDisabled,$scope.user=$rootScope.user=Auth.ensureHasCurrentUser(User),$scope.authentication=$rootScope.authentication=Auth,$rootScope.languages=$scope.languages=["en","fr","es","it","de"],console.log($locale.id),$scope.authentication.isAuthenticated()?$rootScope.language=$scope.user.language:$rootScope.language=$locale.id.substring(0,2),$translate.use($rootScope.language),$scope.isCollapsed=!1,$rootScope.hideNav=!1,$scope.menu=Menus.getMenu("topbar"),$scope.signout=function(){var promise=User.logout();promise.then(function(){Auth.logout(),Auth.ensureHasCurrentUser(User),$scope.user=$rootScope.user=null,$state.go("listForms")},function(reason){console.log("Logout Failed: "+reason)})},$scope.toggleCollapsibleMenu=function(){$scope.isCollapsed=!$scope.isCollapsed},$scope.$on("$stateChangeSuccess",function(event,toState,toParams,fromState,fromParams){$scope.isCollapsed=!1,$rootScope.hideNav=!1,angular.isDefined(toState.data)&&angular.isDefined(toState.data.hideNav)&&($rootScope.hideNav=toState.data.hideNav)})}]),angular.module("core").service("Menus",[function(){this.defaultRoles=["*"],this.menus={};var shouldRender=function(user){if(!user)return this.isPublic;if(~this.roles.indexOf("*"))return!0;for(var userRoleIndex in user.roles)for(var roleIndex in this.roles)if(console.log(this.roles[roleIndex]),console.log(this.roles[roleIndex]===user.roles[userRoleIndex]),this.roles[roleIndex]===user.roles[userRoleIndex])return!0;return!1};this.validateMenuExistance=function(menuId){if(menuId&&menuId.length){if(this.menus[menuId])return!0;throw new Error("Menu does not exists")}throw new Error("MenuId was not provided")},this.getMenu=function(menuId){return this.validateMenuExistance(menuId),this.menus[menuId]},this.addMenu=function(menuId,isPublic,roles){return this.menus[menuId]={isPublic:isPublic||!1,roles:roles||this.defaultRoles,items:[],shouldRender:shouldRender},this.menus[menuId]},this.removeMenu=function(menuId){this.validateMenuExistance(menuId),delete this.menus[menuId]},this.addMenuItem=function(menuId,menuItemTitle,menuItemURL,menuItemType,menuItemUIRoute,isPublic,roles,position){return this.validateMenuExistance(menuId),this.menus[menuId].items.push({title:menuItemTitle,link:menuItemURL,menuItemType:menuItemType||"item",menuItemClass:menuItemType,uiRoute:menuItemUIRoute||"/"+menuItemURL,isPublic:null===isPublic||"undefined"==typeof isPublic?this.menus[menuId].isPublic:isPublic,roles:null===roles||"undefined"==typeof roles?this.menus[menuId].roles:roles,position:position||0,items:[],shouldRender:shouldRender}),this.menus[menuId]},this.addSubMenuItem=function(menuId,rootMenuItemURL,menuItemTitle,menuItemURL,menuItemUIRoute,isPublic,roles,position){this.validateMenuExistance(menuId);for(var itemIndex in this.menus[menuId].items)this.menus[menuId].items[itemIndex].link===rootMenuItemURL&&this.menus[menuId].items[itemIndex].items.push({title:menuItemTitle,link:menuItemURL,uiRoute:menuItemUIRoute||"/"+menuItemURL,isPublic:null===isPublic||"undefined"==typeof isPublic?this.menus[menuId].items[itemIndex].isPublic:isPublic,roles:null===roles||"undefined"==typeof roles?this.menus[menuId].items[itemIndex].roles:roles,position:position||0,shouldRender:shouldRender});return this.menus[menuId]},this.removeMenuItem=function(menuId,menuItemURL){this.validateMenuExistance(menuId);for(var itemIndex in this.menus[menuId].items)this.menus[menuId].items[itemIndex].link===menuItemURL&&this.menus[menuId].items.splice(itemIndex,1);return this.menus[menuId]},this.removeSubMenuItem=function(menuId,submenuItemURL){this.validateMenuExistance(menuId);for(var itemIndex in this.menus[menuId].items)for(var subitemIndex in this.menus[menuId].items[itemIndex].items)this.menus[menuId].items[itemIndex].items[subitemIndex].link===submenuItemURL&&this.menus[menuId].items[itemIndex].items.splice(subitemIndex,1);return this.menus[menuId]},this.addMenu("topbar",!1,["*"]),this.addMenu("bottombar",!1,["*"])}]),function(){function Socket($timeout,$window){function connect(url){service.socket=io(url,{transports:["websocket","polling"]})}function emit(eventName,data){service.socket&&service.socket.emit(eventName,data)}function on(eventName,callback){service.socket&&service.socket.on(eventName,function(data){$timeout(function(){callback(data)})})}function removeListener(eventName){service.socket&&service.socket.removeListener(eventName)}var service={connect:connect,emit:emit,on:on,removeListener:removeListener,socket:null};return connect(window.location.protocol+"//"+window.location.hostname+":"+$window.socketPort),service}angular.module("core").factory("Socket",Socket),Socket.$inject=["$timeout","$window"]}(),angular.module("core").factory("subdomain",["$location",function($location){var host=$location.host();return host.indexOf(".")<0?null:host.split(".")[0]}]),angular.module("forms").run(["Menus",function(Menus){Menus.addMenuItem("topbar","My Forms","forms","","/forms",!1)}]).filter("secondsToDateTime",[function(){return function(seconds){return new Date(1970,0,1).setSeconds(seconds)}}]).filter("formValidity",function(){return function(formObj){if(formObj&&formObj.form_fields&&formObj.visible_form_fields){var formKeys=Object.keys(formObj),fields=(formKeys.filter(function(key){return"$"!==key[0]}),formObj.form_fields),valid_count=fields.filter(function(field){return"object"==typeof field&&"statement"!==field.fieldType&&"rating"!==field.fieldType?!!field.fieldValue:void 0}).length;return valid_count-(formObj.form_fields.length-formObj.visible_form_fields.length)}return 0}}).config(["$provide",function($provide){$provide.decorator("accordionDirective",["$delegate",function($delegate){var directive=$delegate[0];return directive.replace=!0,$delegate}])}]),angular.module("forms").config(["$stateProvider",function($stateProvider){$stateProvider.state("listForms",{url:"/forms",templateUrl:"modules/forms/admin/views/list-forms.client.view.html"}).state("viewForm",{url:"/forms/:formId/admin",templateUrl:"modules/forms/admin/views/admin-form.client.view.html",data:{permissions:["editForm"]},resolve:{Forms:"Forms",myForm:["Forms","$stateParams",function(Forms,$stateParams){return Forms.get({formId:$stateParams.formId}).$promise}]},controller:"AdminFormController"}).state("viewForm.configure",{url:"/configure",templateUrl:"modules/forms/admin/views/adminTabs/configure.html"}).state("viewForm.design",{url:"/design",templateUrl:"modules/forms/admin/views/adminTabs/design.html"}).state("viewForm.analyze",{url:"/analyze",templateUrl:"modules/forms/admin/views/adminTabs/analyze.html"}).state("viewForm.create",{url:"/create",templateUrl:"modules/forms/admin/views/adminTabs/create.html"})}]),function(){function SendVisitorData(Socket,$state,$http,deviceDetector){function send(form,lastActiveIndex,timeElapsed){var visitorData={referrer:document.referrer,isSubmitted:form.submitted,formId:form._id,lastActiveField:form.form_fields[lastActiveIndex]._id,timeElapsed:timeElapsed,language:window.navigator.userLanguage||window.navigator.language,ipAddr:"",deviceType:""};$http.get("http://jsonip.com/").success(function(response){visitorData.ipAddr=response.ip+""}).error(function(error){console.error("Could not get users's ip")}).then(function(){visitorData.userAgent=deviceDetector.raw,deviceDetector.isTablet()?visitorData.deviceType="tablet":deviceDetector.isMobile()?visitorData.deviceType="phone":visitorData.deviceType="desktop",console.log(visitorData.deviceType),Socket.emit("form-visitor-data",visitorData)})}function init(){Socket.socket||Socket.connect()}var service={send:send};return init(),service}angular.module("forms").factory("SendVisitorData",SendVisitorData),SendVisitorData.$inject=["Socket","$state","$http","deviceDetector"]}(),angular.module("forms").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,index=parseInt(String.fromCharCode(keyCode))-1;index<$scope.field.fieldOptions.length&&(event.preventDefault(),$scope.$apply(function(){$scope.field.fieldValue=$scope.field.fieldOptions[index].option_value}))})}}}),angular.module("forms").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,truthyKeyCode=$attrs.keyCharTruthy.charCodeAt(0)-32,falseyKeyCode=$attrs.keyCharFalsey.charCodeAt(0)-32;keyCode===truthyKeyCode?(event.preventDefault(),$scope.$apply(function(){$scope.field.fieldValue="true"})):keyCode===falseyKeyCode&&(event.preventDefault(),$scope.$apply(function(){$scope.field.fieldValue="false"}))})}}}]),angular.module("users").config(["$httpProvider",function($httpProvider){$httpProvider.interceptors.push(["$q","$location",function($q,$location){return{responseError:function(response){return"/users/me"!==$location.path()&&response.config&&"/users/me"!==response.config.url&&(console.log("intercepted rejection of ",response.config.url,response.status),401===response.status?(console.log($location.path()),$location.nextAfterLogin=$location.path(),$location.path("/signin")):403===response.status&&$location.path("/access_denied")),$q.reject(response)}}}])}]),angular.module("users").config(["$stateProvider",function($stateProvider){var checkLoggedin=function($q,$timeout,$state,User,Auth){var deferred=$q.defer();return Auth.currentUser&&Auth.currentUser.email?$timeout(deferred.resolve):Auth.currentUser=User.getCurrent(function(){Auth.login(),$timeout(deferred.resolve())},function(){Auth.logout(),$timeout(deferred.reject()),$state.go("signin",{reload:!0})}),deferred.promise};checkLoggedin.$inject=["$q","$timeout","$state","User","Auth"];var checkSignupDisabled=function($window,$timeout,$q){var deferred=$q.defer();return $timeout($window.signupDisabled?deferred.reject():deferred.resolve()),deferred.promise};checkSignupDisabled.$inject=["$window","$timeout","$q"],$stateProvider.state("profile",{resolve:{loggedin:checkLoggedin},url:"/settings/profile",templateUrl:"modules/users/views/settings/edit-profile.client.view.html"}).state("password",{resolve:{loggedin:checkLoggedin},url:"/settings/password",templateUrl:"modules/users/views/settings/change-password.client.view.html"}).state("accounts",{resolve:{loggedin:checkLoggedin},url:"/settings/accounts",templateUrl:"modules/users/views/settings/social-accounts.client.view.html"}).state("signup",{resolve:{isDisabled:checkSignupDisabled},url:"/signup",templateUrl:"modules/users/views/authentication/signup.client.view.html"}).state("signup-success",{resolve:{isDisabled:checkSignupDisabled},url:"/signup-success",templateUrl:"modules/users/views/authentication/signup-success.client.view.html"}).state("signin",{url:"/signin",templateUrl:"modules/users/views/authentication/signin.client.view.html"}).state("access_denied",{url:"/access_denied",templateUrl:"modules/users/views/authentication/access-denied.client.view.html"}).state("verify",{resolve:{isDisabled:checkSignupDisabled},url:"/verify/:token",templateUrl:"modules/users/views/verify/verify-account.client.view.html"}).state("resendVerifyEmail",{resolve:{isDisabled:checkSignupDisabled},url:"/verify",templateUrl:"modules/users/views/verify/resend-verify-email.client.view.html"}).state("forgot",{url:"/password/forgot",templateUrl:"modules/users/views/password/forgot-password.client.view.html"}).state("reset-invalid",{url:"/password/reset/invalid",templateUrl:"modules/users/views/password/reset-password-invalid.client.view.html"}).state("reset-success",{url:"/password/reset/success",templateUrl:"modules/users/views/password/reset-password-success.client.view.html"}).state("reset",{url:"/password/reset/:token",templateUrl:"modules/users/views/password/reset-password.client.view.html"})}]),angular.module("users").controller("AuthenticationController",["$scope","$location","$state","$rootScope","User","Auth",function($scope,$location,$state,$rootScope,User,Auth){$scope=$rootScope,$scope.credentials={},$scope.error="",$scope.signin=function(){User.login($scope.credentials).then(function(response){Auth.login(response),$scope.user=$rootScope.user=Auth.ensureHasCurrentUser(User),"home"!==$state.previous.name&&"verify"!==$state.previous.name&&""!==$state.previous.name?$state.go($state.previous.name):$state.go("listForms")},function(error){$rootScope.user=Auth.ensureHasCurrentUser(User),$scope.user=$rootScope.user,$scope.error=error,console.log("loginError: "+error)})},$scope.signup=function(){console.log($scope.credentials),User.signup($scope.credentials).then(function(response){console.log("signup-success"),$state.go("signup-success")},function(error){console.log("Error: "),console.log(error),error?($scope.error=error,console.log(error)):console.log("No response received")})}}]),angular.module("users").controller("PasswordController",["$scope","$stateParams","$state","User",function($scope,$stateParams,$state,User){$scope.error="",$scope.askForPasswordReset=function(){User.askForPasswordReset($scope.credentials).then(function(response){$scope.success=response.message,$scope.credentials=null},function(error){$scope.error=error,$scope.credentials=null})},$scope.resetUserPassword=function(){$scope.success=$scope.error=null,User.resetPassword($scope.passwordDetails,$stateParams.token).then(function(response){$scope.success=response.message,$scope.passwordDetails=null,$state.go("reset-success")},function(error){$scope.error=error.message||error,$scope.passwordDetails=null})}}]),angular.module("users").controller("SettingsController",["$scope","$rootScope","$http","$state","Users",function($scope,$rootScope,$http,$state,Users){$scope.user=$rootScope.user,$scope.hasConnectedAdditionalSocialAccounts=function(provider){for(var i in $scope.user.additionalProvidersData)return!0;return!1},$scope.isConnectedSocialAccount=function(provider){return $scope.user.provider===provider||$scope.user.additionalProvidersData&&$scope.user.additionalProvidersData[provider]},$scope.removeUserSocialAccount=function(provider){$scope.success=$scope.error=null,$http["delete"]("/users/accounts",{params:{provider:provider}}).success(function(response){$scope.success=!0,$scope.user=response}).error(function(response){$scope.error=response.message})},$scope.updateUserProfile=function(isValid){if(isValid){$scope.success=$scope.error=null;var user=new Users($scope.user);user.$update(function(response){$scope.success=!0,$scope.user=response},function(response){$scope.error=response.data.message})}else $scope.submitted=!0},$scope.changeUserPassword=function(){$scope.success=$scope.error=null,$http.post("/users/password",$scope.passwordDetails).success(function(response){$scope.success=!0,$scope.passwordDetails=null}).error(function(response){$scope.error=response.message})}}]),angular.module("users").controller("VerifyController",["$scope","$state","$rootScope","User","Auth","$stateParams",function($scope,$state,$rootScope,User,Auth,$stateParams){$scope.isResetSent=!1,$scope.credentials={},$scope.error="",$scope.resendVerifyEmail=function(){User.resendVerifyEmail($scope.credentials.email).then(function(response){console.log(response),$scope.success=response.message,$scope.credentials=null,$scope.isResetSent=!0},function(error){$scope.error=error,$scope.credentials.email=null,$scope.isResetSent=!1})},$scope.validateVerifyToken=function(){$stateParams.token&&(console.log($stateParams.token),User.validateVerifyToken($stateParams.token).then(function(response){console.log("Success: "+response.message),$scope.success=response.message,$scope.isResetSent=!0,$scope.credentials.email=null},function(error){console.log("Error: "+error.message),$scope.isResetSent=!1,$scope.error=error,$scope.credentials.email=null}))}}]),angular.module("users").factory("Auth",["$window",function($window){var userState={isLoggedIn:!1},service={_currentUser:null,get currentUser(){return this._currentUser},ensureHasCurrentUser:function(User){return service._currentUser&&service._currentUser.username?service._currentUser:$window.user?(service._currentUser=$window.user,service._currentUser):void User.getCurrent().then(function(user){return service._currentUser=user,userState.isLoggedIn=!0,$window.user=service._currentUser,service._currentUser},function(response){return userState.isLoggedIn=!1,service._currentUser=null,$window.user=null,console.log("User.getCurrent() err",response),null})},isAuthenticated:function(){return!!service._currentUser},getUserState:function(){return userState},login:function(new_user){userState.isLoggedIn=!0,service._currentUser=new_user},logout:function(){$window.user=null,userState.isLoggedIn=!1,service._currentUser=null}};return service}]),angular.module("users").service("Authorizer",["APP_PERMISSIONS","USER_ROLES",function(APP_PERMISSIONS,USER_ROLES){return function(user){return{canAccess:function(permissions){var i,len,permission;for(angular.isArray(permissions)||(permissions=[permissions]),i=0,len=permissions.length;len>i;i++){if(permission=permissions[i],null===APP_PERMISSIONS[permission])throw"Bad permission value";if(!user||!user.roles)return!1;switch(permission){case APP_PERMISSIONS.viewAdminSettings:case APP_PERMISSIONS.editAdminSettings:return user.roles.indexOf(USER_ROLES.admin)>-1;case APP_PERMISSIONS.viewPrivateForm:case APP_PERMISSIONS.editForm:return user.roles.indexOf(USER_ROLES.admin)>-1||user.roles.indexOf(USER_ROLES.normal)>-1}}return!1}}}}]),angular.module("users").factory("User",["$window","$q","$timeout","$http","$state",function($window,$q,$timeout,$http,$state){var userService={getCurrent:function(){var deferred=$q.defer();return $http.get("/users/me").success(function(response){deferred.resolve(response)}).error(function(){deferred.reject("User's session has expired")}),deferred.promise},login:function(credentials){var deferred=$q.defer();return $http.post("/auth/signin",credentials).success(function(response){deferred.resolve(response)}).error(function(error){deferred.reject(error.message||error)}),deferred.promise},logout:function(){var deferred=$q.defer();return $http.get("/auth/signout").success(function(response){deferred.resolve(null)}).error(function(error){deferred.reject(error.message||error)}),deferred.promise},signup:function(credentials){var deferred=$q.defer();return $http.post("/auth/signup",credentials).success(function(response){deferred.resolve(response)}).error(function(error){deferred.reject(error.message||error)}),deferred.promise},resendVerifyEmail:function(_email){var deferred=$q.defer();return $http.post("/auth/verify",{email:_email}).success(function(response){deferred.resolve(response)}).error(function(error){deferred.reject(error.message||error)}),deferred.promise},validateVerifyToken:function(token){var validTokenRe=/^([A-Za-z0-9]{48})$/g;if(!validTokenRe.test(token))throw new Error("Error token: "+token+" is not a valid verification token"); +var deferred=$q.defer();return $http.get("/auth/verify/"+token).success(function(response){deferred.resolve(response)}).error(function(error){deferred.reject(error)}),deferred.promise},resetPassword:function(passwordDetails,token){var deferred=$q.defer();return $http.get("/auth/password/"+token,passwordDetails).success(function(response){deferred.resolve()}).error(function(error){deferred.reject(error.message||error)}),deferred.promise},askForPasswordReset:function(credentials){var deferred=$q.defer();return $http.post("/auth/forgot",credentials).success(function(response){deferred.resolve(response)}).error(function(error){deferred.reject(error.message||error)}),deferred.promise}};return userService}]),angular.module("users").factory("Users",["$resource",function($resource){return $resource("users",{},{update:{method:"PUT"}})}]),angular.module("core").config(["$translateProvider",function($translateProvider){$translateProvider.translations("en",{MENU:"MENU",SIGNUP_TAB:"Sign Up",SIGNIN_TAB:"Sign In",SIGNOUT_TAB:"Signout",EDIT_PROFILE:"Edit Profile",MY_FORMS:"My Forms",MY_SETTINGS:"My Settings",CHANGE_PASSWORD:"Change Password"}),$translateProvider.preferredLanguage("en").fallbackLanguage("en").useSanitizeValueStrategy("escape")}]),angular.module("core").config(["$translateProvider",function($translateProvider){$translateProvider.translations("fr",{MENU:"MENU",SIGNUP_TAB:"Créer un Compte",SIGNIN_TAB:"Connexion",SIGNOUT_TAB:"Créer un compte",EDIT_PROFILE:"Modifier Mon Profil",MY_FORMS:"Mes Formulaires",MY_SETTINGS:"Mes Paramètres",CHANGE_PASSWORD:"Changer mon Mot de Pass"})}]),angular.module("forms").controller("AdminFormController",["$rootScope","$scope","$stateParams","$state","Forms","CurrentForm","$http","$uibModal","myForm","$filter",function($rootScope,$scope,$stateParams,$state,Forms,CurrentForm,$http,$uibModal,myForm,$filter){$scope=$rootScope,$scope.animationsEnabled=!0,$scope.myform=myForm,$rootScope.saveInProgress=!1,CurrentForm.setForm($scope.myform),$scope.formURL=$scope.myform.admin.username+"."+window.location.host,$scope.tabData=[{heading:$filter("translate")("CREATE_TAB"),route:"viewForm.create"},{heading:$filter("translate")("DESIGN_TAB"),route:"viewForm.design"},{heading:$filter("translate")("CONFIGURE_TAB"),route:"viewForm.configure"},{heading:$filter("translate")("ANALYZE_TAB"),route:"viewForm.analyze"}],$scope.setForm=function(form){$scope.myform=form},$rootScope.resetForm=function(){$scope.myform=Forms.get({formId:$stateParams.formId})},$scope.openDeleteModal=function(){$scope.deleteModal=$uibModal.open({animation:$scope.animationsEnabled,templateUrl:"myModalContent.html",controller:"AdminFormController",resolve:{myForm:function(){return $scope.myform}}}),$scope.deleteModal.result.then(function(selectedItem){$scope.selected=selectedItem},function(){console.log("Modal dismissed at: "+new Date)})},$scope.cancelDeleteModal=function(){$scope.deleteModal&&$scope.deleteModal.dismiss("cancel")},$scope.removeCurrentForm=function(){if($scope.deleteModal&&$scope.deleteModal.opened){$scope.deleteModal.close();var form_id=$scope.myform._id;if(!form_id)throw new Error("Error - removeCurrentForm(): $scope.myform._id does not exist");$http["delete"]("/forms/"+form_id).success(function(data,status,headers){console.log("form deleted successfully"),$state.go("listForms",{},{reload:!0})}).error(function(error){console.log("ERROR: Form could not be deleted."),console.error(error)})}},$scope.update=$rootScope.update=function(updateImmediately,cb){var continueUpdate=!0;if(updateImmediately||(continueUpdate=!$rootScope.saveInProgress),continueUpdate){var err=null;updateImmediately||($rootScope.saveInProgress=!0),$scope.updatePromise=$http.put("/forms/"+$scope.myform._id,{form:$scope.myform}).then(function(response){$rootScope.myform=$scope.myform=response.data})["catch"](function(response){console.log("Error occured during form UPDATE.\n"),err=response.data})["finally"](function(){return updateImmediately||($rootScope.saveInProgress=!1),"function"==typeof cb?cb(err):void 0})}}}]),angular.module("forms").controller("ListFormsController",["$rootScope","$scope","$stateParams","$state","Forms","CurrentForm","$http",function($rootScope,$scope,$stateParams,$state,Forms,CurrentForm,$http){$scope=$rootScope,$scope.forms={},$scope.showCreateModal=!1,$scope.languageRegExp=$scope.myPt={regExp:/[@!#$%^&*()\-+={}\[\]|\\/'";:`.,~№?<>]+/i,test:function(val){return!this.regExp.test(val)}},$scope.findAll=function(){Forms.query(function(_forms){$scope.myforms=_forms})},$scope.openCreateModal=function(){$scope.showCreateModal||($scope.showCreateModal=!0)},$scope.closeCreateModal=function(){$scope.showCreateModal&&($scope.showCreateModal=!1)},$scope.setForm=function(form){$scope.myform=form},$scope.goToWithId=function(route,id){$state.go(route,{formId:id},{reload:!0})},$scope.duplicateForm=function(form_index){var form=_.cloneDeep($scope.myforms[form_index]);delete form._id,$http.post("/forms",{form:form}).success(function(data,status,headers){$scope.myforms.splice(form_index+1,0,data)}).error(function(errorResponse){console.error(errorResponse),null===errorResponse&&($scope.error=errorResponse.data.message)})},$scope.createNewForm=function(){var form={};form.title=$scope.forms.createForm.title.$modelValue,form.language=$scope.forms.createForm.language.$modelValue,$scope.forms.createForm.$valid&&$scope.forms.createForm.$dirty&&$http.post("/forms",{form:form}).success(function(data,status,headers){$scope.goToWithId("viewForm.create",data._id+"")}).error(function(errorResponse){console.error(errorResponse),$scope.error=errorResponse.data.message})},$scope.removeForm=function(form_index){if(form_index>=$scope.myforms.length||0>form_index)throw new Error("Error: form_index in removeForm() must be between 0 and "+$scope.myforms.length-1);$http["delete"]("/forms/"+$scope.myforms[form_index]._id).success(function(data,status,headers){$scope.myforms.splice(form_index,1)}).error(function(error){console.error(error)})}}]),_.mixin({removeDateFields:removeDateFieldsFunc}),angular.module("forms").directive("autoSaveForm",["$rootScope","$timeout",function($rootScope,$timeout){return{require:["^form"],restrict:"AE",link:function($scope,$element,$attrs,$ctrls){angular.element(document).ready(function(){var $formCtrl=$ctrls[0],savePromise=null;$rootScope.finishedRender=!1,$scope.$on("editFormFields Started",function(ngRepeatFinishedEvent){$rootScope.finishedRender=!1}),$scope.$on("editFormFields Finished",function(ngRepeatFinishedEvent){$rootScope.finishedRender=!0}),$scope.anyDirtyAndTouched=function(form){var propCount=0;for(var prop in form)if(form.hasOwnProperty(prop)&&"$"!==prop[0]&&(propCount++,form[prop].$touched&&form[prop].$dirty))return!0;return!1};var debounceSave=function(){$rootScope.saveInProgress=!0,$rootScope[$attrs.autoSaveCallback](!0,function(err){err?(console.error("Error form data NOT persisted"),console.error(err)):($formCtrl.$setPristine(),$formCtrl.$setUntouched())})};$scope.$watch(function(newValue,oldValue){$rootScope.finishedRender&&$scope.anyDirtyAndTouched($scope.editForm)&&!$rootScope.saveInProgress&&debounceSave()}),$scope.$watch($attrs.autoSaveWatch,function(newValue,oldValue){newValue=angular.copy(newValue),oldValue=angular.copy(oldValue),newValue.form_fields=_.removeDateFields(newValue.form_fields),oldValue.form_fields=_.removeDateFields(oldValue.form_fields);var changedFields=!_.isEqual(oldValue.form_fields,newValue.form_fields)||!_.isEqual(oldValue.startPage,newValue.startPage),changedFieldMap=!1;oldValue.hasOwnProperty("plugins.oscarhost.settings.fieldMap")&&(changedFieldMap=!!oldValue.plugins.oscarhost.settings.fieldMap&&!_.isEqual(oldValue.plugins.oscarhost.settings.fieldMap,newValue.plugins.oscarhost.settings.fieldMap)),(newValue||oldValue)&&oldValue&&(0===oldValue.form_fields.length&&($rootScope.finishedRender=!0),$rootScope.finishedRender&&(changedFields&&!$formCtrl.$dirty||changedFieldMap)&&!$rootScope.saveInProgress?(savePromise&&($timeout.cancel(savePromise),savePromise=null),savePromise=$timeout(function(){debounceSave()})):$rootScope.finishedRender&&$rootScope.saveInProgress&&($rootScope.saveInProgress=!1))},!0)})}}}]),angular.module("forms").directive("configureFormDirective",["$rootScope","$http","Upload","CurrentForm",function($rootScope,$http,Upload,CurrentForm){return{templateUrl:"modules/forms/admin/views/directiveViews/form/configure-form.client.view.html",restrict:"E",scope:{myform:"=",user:"=",pdfFields:"@",formFields:"@"},controller:["$scope",function($scope){console.log($scope.myform),CurrentForm.getForm().plugins?CurrentForm.getForm().plugins.oscarhost.baseUrl&&($scope.oscarhostAPI=!0):$scope.oscarhostAPI=!1,$scope.log="",$scope.pdfLoading=!1,$scope.languages=$rootScope.languages,this._current_upload=null,$scope.resetForm=$rootScope.resetForm,$scope.update=$rootScope.update,this._unbindedPdfFields=$scope.pdfFields,$scope.cancelUpload=function(){this._current_upload.abort(),$scope.pdfLoading=!1,$scope.removePDF()},$scope.removePDF=function(){$scope.myform.pdf=null,$scope.myform.isGenerated=!1,$scope.myform.autofillPDFs=!1,console.log("form.pdf: "+$scope.myform.pdf+" REMOVED")},$scope.uploadPDF=function(file){file&&(console.log(file),Upload.upload({url:"/upload/pdf",data:{user:$scope.user,file:file}}).then(function(resp){var data=resp.data;$scope.log="file "+data.originalname+" uploaded as "+data.filename+". JSON: "+JSON.stringify(data)+"\n"+$scope.log,$scope.myform.pdf=angular.fromJson(angular.toJson(data)),$scope.pdfLoading=!1,console.log($scope.log),$scope.$$phase||$scope.$digest||$scope.$apply()},function(resp){$scope.pdfLoading=!1,console.log("Error occured during upload.\n"),console.log(resp.status)},function(evt){var progressPercentage=parseInt(100*evt.loaded/evt.total,10);$scope.log="progress: "+progressPercentage+"% "+evt.config.data.file.name+"\n"+$scope.log,console.log($scope.log),$scope.pdfLoading=!0}))}}]}}]),angular.module("forms").directive("editFormDirective",["$rootScope","FormFields",function($rootScope,FormFields){return{templateUrl:"modules/forms/admin/views/directiveViews/form/edit-form.client.view.html",restrict:"E",scope:{myform:"="},controller:["$scope",function($scope){console.log($scope.myform);for(var field_ids=_($scope.myform.form_fields).pluck("_id"),i=0;i0){$scope.myform.plugins.oscarhost.settings.fieldMap||($scope.myform.plugins.oscarhost.settings.fieldMap={});var oscarhostFields=$scope.myform.plugins.oscarhost.settings.validFields,currentFields=_($scope.myform.plugins.oscarhost.settings.fieldMap).invert().keys().value();return $scope.myform.plugins.oscarhost.settings.fieldMap.hasOwnProperty(field_id)&&(currentFields=_(currentFields).difference($scope.myform.plugins.oscarhost.settings.fieldMap[field_id])),_(oscarhostFields).difference(currentFields).value()}return[]},$scope.dropzone={handle:".handle",containment:".dropzoneContainer",cursor:"grabbing"},$scope.addNewField=function(modifyForm,fieldType){$scope.addField.lastAddedID++;for(var fieldTitle,i=0;i<$scope.addField.types.length;i++)if($scope.addField.types[i].name===fieldType){$scope.addField.types[i].lastAddedID++,fieldTitle=$scope.addField.types[i].value+$scope.addField.types[i].lastAddedID;break}var newField={title:fieldTitle,fieldType:fieldType,fieldValue:"",required:!0,disabled:!1,deletePreserved:!1};return $scope.showAddOptions(newField)&&(newField.fieldOptions=[],newField.fieldOptions.push({option_id:Math.floor(1e5*Math.random()),option_title:"Option 0",option_value:"Option 0"})),modifyForm&&$scope.myform.form_fields.push(newField),newField},$scope.deleteField=function(field_index){var currFieldId=$scope.myform.form_fields[field_index]._id;$scope.myform.hasOwnProperty("plugins.oscarhost.baseUrl")&&delete $scope.myform.plugins.oscarhost.settings.fieldMap[currFieldId],$scope.myform.form_fields.splice(field_index,1)},$scope.duplicateField=function(field_index){var currField=_.cloneDeep($scope.myform.form_fields[field_index]);currField._id="cloned"+_.uniqueId(),currField.title+=" copy",$scope.myform.form_fields.splice(field_index+1,0,currField)},$scope.addButton=function(){var newButton={};newButton.bgColor="#ddd",newButton.color="#ffffff",newButton.text="Button",newButton._id=Math.floor(1e5*Math.random()),$scope.myform.startPage.buttons.push(newButton)},$scope.deleteButton=function(button){for(var currID,i=0;i<$scope.myform.startPage.buttons.length;i++)if(currID=$scope.myform.startPage.buttons[i]._id,console.log(currID),currID===button._id){$scope.myform.startPage.buttons.splice(i,1);break}},$scope.addOption=function(field_index){var currField=$scope.myform.form_fields[field_index];if("checkbox"===currField.fieldType||"dropdown"===currField.fieldType||"radio"===currField.fieldType){currField.fieldOptions||($scope.myform.form_fields[field_index].fieldOptions=[]);var lastOptionID=$scope.myform.form_fields[field_index].fieldOptions.length+1,newOption={option_id:Math.floor(1e5*Math.random()),option_title:"Option "+lastOptionID,option_value:"Option "+lastOptionID};$scope.myform.form_fields[field_index].fieldOptions.push(newOption)}},$scope.deleteOption=function(field_index,option){var currField=$scope.myform.form_fields[field_index];if("checkbox"===currField.fieldType||"dropdown"===currField.fieldType||"radio"===currField.fieldType)for(var i=0;i',restrict:"E",scope:{typeName:"@"},controller:["$scope",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]}]}});var __indexOf=[].indexOf||function(item){for(var i=0,l=this.length;l>i;i++)if(i in this&&this[i]===item)return i;return-1};angular.module("forms").directive("fieldDirective",["$http","$compile","$rootScope","$templateCache","supportedFields",function($http,$compile,$rootScope,$templateCache,supportedFields){var getTemplateUrl=function(fieldType){var type=fieldType,templateUrl="modules/forms/base/views/directiveViews/field/";return __indexOf.call(supportedFields,type)>=0&&(templateUrl=templateUrl+type+".html"),$templateCache.get(templateUrl)};return{template:"
{{field.title}}
",restrict:"E",scope:{field:"=",required:"&",design:"=",index:"=",forms:"="},link:function(scope,element){$rootScope.chooseDefaultOption=scope.chooseDefaultOption=function(type){"yes_no"===type?scope.field.fieldValue="true":"rating"===type?scope.field.fieldValue=0:"radio"===scope.field.fieldType?(console.log(scope.field),scope.field.fieldValue=scope.field.fieldOptions[0].option_value,console.log(scope.field.fieldValue)):"legal"===type&&(scope.field.fieldValue="true",$rootScope.nextField())},scope.setActiveField=$rootScope.setActiveField,"date"===scope.field.fieldType&&(scope.dateOptions={changeYear:!0,changeMonth:!0,altFormat:"mm/dd/yyyy",yearRange:"1900:-0",defaultDate:0});var fieldType=scope.field.fieldType;if("number"===scope.field.fieldType||"textfield"===scope.field.fieldType||"email"===scope.field.fieldType||"link"===scope.field.fieldType){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"}fieldType="textfield"}var template=getTemplateUrl(fieldType);element.html(template).show();$compile(element.contents())(scope)}}}]),angular.module("forms").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,onEnterKeyDisabled=!1;null!==$attrs.onEnterKeyDisabled&&(onEnterKeyDisabled=$attrs.onEnterKeyDisabled),13!==keyCode||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;9!==keyCode||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;13!==keyCode&&9!==keyCode||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;9===keyCode&&event.shiftKey&&(event.preventDefault(),$rootScope.$apply(function(){$rootScope.$eval($attrs.onTabAndShiftKey)}))})}}}]),angular.module("forms").directive("onFinishRender",["$rootScope","$timeout",function($rootScope,$timeout){return{restrict:"A",link:function(scope,element,attrs){if(element.attr("ng-repeat")||element.attr("data-ng-repeat")){var broadcastMessage=attrs.onFinishRender||"ngRepeat";scope.$first&&!scope.$last?scope.$evalAsync(function(){$rootScope.$broadcast(broadcastMessage+" Started")}):scope.$last&&scope.$evalAsync(function(){$rootScope.$broadcast(broadcastMessage+" Finished")})}}}}]),angular.module("forms").directive("submitFormDirective",["$http","TimeCounter","$filter","$rootScope","Auth","SendVisitorData",function($http,TimeCounter,$filter,$rootScope,Auth,SendVisitorData){return{templateUrl:"modules/forms/base/views/directiveViews/form/submit-form.client.view.html",restrict:"E",scope:{myform:"="},controller:["$document","$window","$scope",function($document,$window,$scope){$scope.authentication=$rootScope.authentication,$scope.noscroll=!1,$scope.forms={};var form_fields_count=$scope.myform.visible_form_fields.filter(function(field){return"statement"!==field.fieldType&&"rating"!==field.fieldType}).length,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(){$scope.myform.submitted=!1,$scope.myform.form_fields=_.chain($scope.myform.visible_form_fields).map(function(field){return field.fieldValue="",field}).value(),$scope.loading=!1,$scope.error="",$scope.selected={_id:"",index:0},$scope.setActiveField($scope.myform.visible_form_fields[0]._id,0,!1),TimeCounter.restartClock()},$window.onscroll=function(){$scope.scrollPos=document.body.scrollTop||document.documentElement.scrollTop||0;var elemBox=document.getElementsByClassName("activeField")[0].getBoundingClientRect();$scope.fieldTop=elemBox.top,$scope.fieldBottom=elemBox.bottom;var field_id,field_index;$scope.noscroll||($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,!1)):$scope.selected.index===$scope.myform.visible_form_fields.length?$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,!1)):$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,!1)):0!==$scope.selected.index&&$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,!1)),$scope.$apply())};var getActiveField=function(){if(null===$scope.selected)throw console.error("current active field is null"),new Error("current active field is null");return"submit_field"===$scope.selected._id?$scope.myform.form_fields.length-1:$scope.selected.index};$scope.setActiveField=$rootScope.setActiveField=function(field_id,field_index,animateScroll){if(null!==$scope.selected&&$scope.selected._id!==field_id){$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},animateScroll?($scope.noscroll=!0,setTimeout(function(){$document.scrollToElement(angular.element(".activeField"),-10,200).then(function(){$scope.noscroll=!1,setTimeout(function(){document.querySelectorAll(".activeField .focusOn").length?document.querySelectorAll(".activeField .focusOn")[0].focus():document.querySelectorAll(".activeField input").length?document.querySelectorAll(".activeField input")[0].focus():document.querySelectorAll(".activeField .selectize-input")[0].focus()})})})):setTimeout(function(){document.querySelectorAll(".activeField .focusOn")[0]?document.querySelectorAll(".activeField .focusOn")[0].focus():document.querySelectorAll(".activeField input")[0].focus()}),SendVisitorData.send($scope.myform,getActiveField(),TimeCounter.getTimeElapsed())}},$rootScope.nextField=$scope.nextField=function(){var selected_index,selected_id;$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,!0)):$scope.selected.index===$scope.myform.visible_form_fields.length-1&&(selected_index=$scope.selected.index+1,selected_id="submit_field",$rootScope.setActiveField(selected_id,selected_index,!0))},$rootScope.prevField=$scope.prevField=function(){if($scope.selected.index>0){var selected_index=$scope.selected.index-1,selected_id=$scope.myform.visible_form_fields[selected_index]._id;$scope.setActiveField(selected_id,selected_index,!0)}},$scope.exitStartPage=function(){$scope.myform.startPage.showStart=!1,$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=!0;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++)"dropdown"!==$scope.myform.form_fields[i].fieldType||$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=!0,$scope.loading=!1,SendVisitorData.send($scope.myform,getActiveField(),_timeElapsed)}).error(function(error){$scope.loading=!1,console.error(error),$scope.error=error.message})},500)},$scope.reloadForm()}]}}]),angular.module("forms").service("CurrentForm",function(){var _form={};this.getForm=function(){return _form},this.setForm=function(form){_form=form}}),angular.module("forms").factory("Forms",["$resource","FORM_URL",function($resource,FORM_URL){return $resource(FORM_URL,{formId:"@_id"},{query:{method:"GET",isArray:!0},get:{method:"GET",transformResponse:function(data,header){var form=angular.fromJson(data);return form.visible_form_fields=_.filter(form.form_fields,function(field){return field.deletePreserved===!1}),form}},update:{method:"PUT"},save:{method:"POST"}})}]),angular.module("forms").service("TimeCounter",[function(){var _startTime,_endTime=null;this.timeSpent=0,this.restartClock=function(){_startTime=Date.now(),_endTime=null},this.getTimeElapsed=function(){return _startTime?Math.abs(Date.now().valueOf()-_startTime.valueOf())/1e3:void 0},this.stopClock=function(){return _startTime&&null===_endTime?(_endTime=Date.now(),this.timeSpent=Math.abs(_endTime.valueOf()-_startTime.valueOf())/1e3,this._startTime=this._endTime=null,this.timeSpent):new Error("Clock has not been started")},this.clockStarted=function(){return!!this._startTime}}]),angular.module("users").config(["$translateProvider",function($translateProvider){$translateProvider.translations("en",{ACCESS_DENIED_TEXT:"You need to be logged in to access this page",USERNAME_LABEL:"Username",PASSWORD_LABEL:"Password",CURRENT_PASSWORD_LABEL:"Current Password",NEW_PASSWORD_LABEL:"New Password",VERIFY_PASSWORD_LABEL:"Verify Password",UPDATE_PASSWORD_LABEL:"Update Password",FIRST_NAME_LABEL:"First Name",LAST_NAME_LABEL:"Last Name",LANGUAGE_LABEL:"Language",EMAIL_LABEL:"Email",UPDATE_PROFILE_BTN:"Update Profile",PROFILE_SAVE_SUCCESS:"Profile saved successfully",PROFILE_SAVE_ERROR:"Could't Save Your Profile.",FORGOT_PASSWORD_LINK:"Forgot your password?",REVERIFY_ACCOUNT_LINK:"Resend your verification email",SIGNIN_BTN:"Sign in",SIGNUP_BTN:"Sign up",SAVE_PASSWORD_BTN:"Save Password",SUCCESS_HEADER:"Signup Successful",SUCCESS_TEXT:"You’ve successfully registered an account at TellForm.",VERIFICATION_EMAIL_SENT:"A verification email has been sent to",NOT_ACTIVATED_YET:"But your account is not activated yet",BEFORE_YOU_CONTINUE:"Before you continue, make sure to check your email for our verification. If you don’t receive it within 24h drop us a line at ",CHECK_YOUR_EMAIL:"Check your email and click on the activation link to activate your account. If you have any questions drop us a line at",PASSWORD_RESTORE_HEADER:"Restore your password",ENTER_YOUR_EMAIL:"Enter your account email.",SUBMIT_BTN:"Submit",ASK_FOR_NEW_PASSWORD:"Ask for new password reset",PASSWORD_RESET_INVALID:"Password reset is invalid",PASSWORD_RESET_SUCCESS:"Passport successfully reset",PASSWORD_CHANGE_SUCCESS:"Passport successfully changed",CONTINUE_TO_LOGIN:"Continue to login page",VERIFY_SUCCESS:"Account successfully activated", +VERIFY_ERROR:"Verification link is invalid or has expired"}),$translateProvider.preferredLanguage("en").fallbackLanguage("en").useSanitizeValueStrategy("escape")}]),angular.module("users").config(["$translateProvider",function($translateProvider){$translateProvider.translations("en",{ACCESS_DENIED_TEXT:"Vouz n’êtes pas autorisé à accéder à cette page.",USERNAME_LABEL:"Nom d’utilisateur",PASSWORD_LABEL:"Mot de Passe",CURRENT_PASSWORD_LABEL:"Mot de passe actuel",NEW_PASSWORD_LABEL:"Nouveau Mot de Passe",VERIFY_PASSWORD_LABEL:"Vérifier le mot de passe",UPDATE_PASSWORD_LABEL:"Mettre à jour le mot de passe",FIRST_NAME_LABEL:"Prénom",LAST_NAME_LABEL:"Nom",LANGUAGE_LABEL:"Langue",EMAIL_LABEL:"Email",UPDATE_PROFILE_BTN:"Modifier le Profil",PROFILE_SAVE_SUCCESS:"Profil enregistré avec succès",PROFILE_SAVE_ERROR:"Erreur: impossible d’enregistrer votre Profile.",FORGOT_PASSWORD_LINK:"Mot de passe oublié ?",REVERIFY_ACCOUNT_LINK:"Re-envoyez un email de vérification",SIGNIN_BTN:"Connexion",SIGNUP_BTN:"Créer un compte",SAVE_PASSWORD_BTN:"Enregistrer votre nouveau Mot de Passe",SUCCESS_HEADER:"Votre Compte a été enregistré !",SUCCESS_TEXT:"Votre compte Tellform a été crée avec succès.",VERIFICATION_EMAIL_SENT:"Un email de verification a été envoyer à",NOT_ACTIVATED_YET:"Mais votre compte n'est pas activé",BEFORE_YOU_CONTINUE:"Avant de continuer, vous devez valider votre adresse mail. Merci de vérifier votre boite mail. Si vous ne l’avez pas reçu dans les prochaines 24h, contactez-nous a ",CHECK_YOUR_EMAIL:"Vérifiez vos emails, et cliquez sur le lien de validation pour activer votre compte. Si vous avez une question contactez-nous à",PASSWORD_RESTORE_HEADER:"Mot de passe perdu",ENTER_YOUR_EMAIL:"Entrer votre email",SUBMIT_BTN:"Enregistrer",ASK_FOR_NEW_PASSWORD:"Demander un nouveau mot de pass ",PASSWORD_RESET_INVALID:"Le nouveau mot de passe est invalid",PASSWORD_RESET_SUCCESS:"Mot de passe réinitialisé avec succès",PASSWORD_CHANGE_SUCCESS:"Mot de passe enregistré avec succès",CONTINUE_TO_LOGIN:"Allez à la page de connexion",VERIFY_SUCCESS:"Votre compte est activé !",VERIFY_ERROR:"Le lien de vérification est invalide ou à expiré"})}]); \ No newline at end of file diff --git a/public/modules/core/config/core.client.routes.js b/public/modules/core/config/core.client.routes.js index 149b3900..6a84af0e 100755 --- a/public/modules/core/config/core.client.routes.js +++ b/public/modules/core/config/core.client.routes.js @@ -55,7 +55,7 @@ angular.module(ApplicationConfiguration.applicationModuleName).run(['$rootScope' authenticator = new Authorizer(user); //console.log('access denied: '+!authenticator.canAccess(permissions)); //console.log(permissions); - if( (permissions != null) ){ + if( (permissions !== null) ){ if( !authenticator.canAccess(permissions) ){ event.preventDefault(); //console.log('access denied'); diff --git a/public/modules/forms/admin/controllers/admin-form.client.controller.js b/public/modules/forms/admin/controllers/admin-form.client.controller.js index a024b2e8..74bb4001 100644 --- a/public/modules/forms/admin/controllers/admin-form.client.controller.js +++ b/public/modules/forms/admin/controllers/admin-form.client.controller.js @@ -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'), diff --git a/public/modules/forms/admin/directives/edit-submissions-form.client.directive.js b/public/modules/forms/admin/directives/edit-submissions-form.client.directive.js index c02b8c70..bafc342f 100644 --- a/public/modules/forms/admin/directives/edit-submissions-form.client.directive.js +++ b/public/modules/forms/admin/directives/edit-submissions-form.client.directive.js @@ -63,7 +63,7 @@ angular.module('forms').directive('editSubmissionsFormDirective', ['$rootScope', completion: 0, average_time: 0, total_time: 0 - } + }; }; var stats = { diff --git a/public/modules/forms/admin/views/admin-form.client.view.html b/public/modules/forms/admin/views/admin-form.client.view.html index 47943672..8ebfea38 100644 --- a/public/modules/forms/admin/views/admin-form.client.view.html +++ b/public/modules/forms/admin/views/admin-form.client.view.html @@ -43,7 +43,7 @@
- +
"); + $templateCache.put("modules/forms/admin/views/admin-form.client.view.html", + "
"); + $templateCache.put("modules/forms/admin/views/list-forms.client.view.html", + "

{{ 'CREATE_A_NEW_FORM' | translate }}
Name
Language

{{ 'CREATED_ON' | translate }}
"); + $templateCache.put("modules/forms/base/views/submit-form.client.view.html", + "
"); + $templateCache.put("modules/forms/admin/views/adminTabs/analyze.html", + ""); + $templateCache.put("modules/forms/admin/views/adminTabs/configure.html", + ""); + $templateCache.put("modules/forms/admin/views/adminTabs/create.html", + ""); + $templateCache.put("modules/forms/admin/views/adminTabs/design.html", + "

{{ 'DESIGN_HEADER' | translate }}

{{ 'DESIGN_HEADER' | translate }}

{{ 'BACKGROUND_COLOR' | translate }}
{{ 'QUESTION_TEXT_COLOR' | translate }}
{{ 'ANSWER_TEXT_COLOR' | translate }}
{{ 'BTN_BACKGROUND_COLOR' | translate }}
{{ 'BTN_TEXT_COLOR' | translate }}
"); + $templateCache.put("modules/forms/admin/views/directiveViews/cgBusy/update-form-message-TypeA.html", + "
{{$message}}
"); + $templateCache.put("modules/forms/admin/views/directiveViews/cgBusy/update-form-message-TypeB.html", + "
{{$message}}
"); + $templateCache.put("modules/forms/admin/views/directiveViews/form/configure-form.client.view.html", + "

{{ 'PDF Generation/EMR' | translate }}

{{ 'PDF Generation/EMR' | translate }}

{{ 'SAVE_PDF_SUBMISSIONS' | translate }}
{{ 'UPLOAD_YOUR_PDF' | translate }}
{{myform.pdf.name}}
{{ 'UPLOAD_YOUR_PDF' | translate }}
{{ 'Autogenerate Form?' | translate }}


{{ 'ADVANCED_SETTINGS' | translate }}

{{ 'ADVANCED_SETTINGS' | translate }}

{{ 'FORM_NAME' | translate }}
{{ 'FORM_STATUS' | translate }}
{{ 'GA_TRACKING_CODE' | translate }}
Language
* required
{{ 'DISPLAY_FOOTER' | translate }}
Display Start Page?
"); + $templateCache.put("modules/forms/admin/views/directiveViews/form/edit-form.client.view.html", + "

{{ 'ADD_FIELD_LG' | translate }}

{{ 'ADD_FIELD_MD' | translate }}

{{ 'ADD_FIELD_SM' | translate }}

Start Page

{{ 'PREVIEW_START_PAGE' | translate }}

    {{myform.startPage.introTitle}}

    {{myform.startPage.introParagraph}}

{{ 'EDIT_START_PAGE' | translate }}


{{ 'INTRO_TITLE' | translate }}:
{{ 'INTRO_PARAGRAPH' | translate }}:
{{ 'INTRO_BTN' | translate }}:


Buttons:
{{ 'BUTTON_TEXT' | translate }}
{{ 'BUTTON_LINK' | translate }}


{{field.title}} *

{{ 'PREVIEW_FIELD' | translate }}


{{ 'EDIT_FIELD' | translate }}


{{ 'QUESTION_TITLE' | translate }}:

{{ 'QUESTION_DESCRIPTION' | translate }}:

{{ 'OPTIONS' | translate }}:

{{ 'NUM_OF_STEPS' | translate }}

Shape:

Required:
Disabled:

{{ 'CLICK_FIELDS_FOOTER' | translate }}


"); + $templateCache.put("modules/forms/admin/views/directiveViews/form/edit-submissions-form.client.view.html", + "
Overview Analytics
{{ 'TOTAL_VIEWS' | translate }}
{{ 'RESPONSES' | translate }}
{{ 'COMPLETION_RATE' | translate }}
{{ 'AVERAGE_TIME_TO_COMPLETE' | translate }}
{{myform.analytics.views}}
{{myform.analytics.submissions}}
{{myform.analytics.conversionRate | number:0}}%
{{AverageTimeElapsed | secondsToDateTime | date:'mm:ss'}}
Device Analytics
{{ 'DESKTOP_AND_LAPTOP' | translate }}
{{ 'TABLETS' | translate }}
{{ 'PHONES' | translate }}
{{ 'OTHER' | translate }}
{{ 'UNIQUE_VISITS' | translate }}
{{DeviceStatistics.desktop.visits}}
{{ 'UNIQUE_VISITS' | translate }}
{{DeviceStatistics.tablet.visits}}
{{ 'UNIQUE_VISITS' | translate }}
{{DeviceStatistics.tablet.visits}}
{{ 'UNIQUE_VISITS' | translate }}
{{DeviceStatistics.other.visits}}
{{ 'RESPONSES' | translate }}
{{DeviceStatistics.desktop.responses}}
{{ 'RESPONSES' | translate }}
{{DeviceStatistics.tablet.responses}}
{{ 'RESPONSES' | translate }}
{{DeviceStatistics.phone.responses}}
{{ 'RESPONSES' | translate }}
{{DeviceStatistics.other.responses}}
{{ 'COMPLETION_RATE' | translate }}
{{DeviceStatistics.desktop.completion}}
{{ 'COMPLETION_RATE' | translate }}
{{DeviceStatistics.tablet.completion}}
{{ 'COMPLETION_RATE' | translate }}
{{DeviceStatistics.phone.completion}}
{{ 'COMPLETION_RATE' | translate }}
{{DeviceStatistics.other.completion}}
{{ 'AVERAGE_TIME_TO_COMPLETE' | translate }}
{{DeviceStatistics.desktop.average_time | secondsToDateTime | date:'mm:ss'}}
{{ 'AVERAGE_TIME_TO_COMPLETE' | translate }}
{{DeviceStatistics.tablet.average_time | secondsToDateTime | date:'mm:ss'}}
{{ 'AVERAGE_TIME_TO_COMPLETE' | translate }}
{{DeviceStatistics.phone.average_time | secondsToDateTime | date:'mm:ss'}}
{{ 'AVERAGE_TIME_TO_COMPLETE' | translate }}
{{DeviceStatistics.other.average_time | secondsToDateTime | date:'mm:ss'}}
Field Analytics
{{ 'FIELD_TITLE' | translate }}
{{ 'FIELD_VIEWS' | translate }}
{{ 'FIELD_RESPONSES' | translate }}
{{ 'FIELD_DROPOFF' | translate }}
{{fieldStats.field.title}}
{{fieldStats.totalViews}}
{{fieldStats.responses}}
{{fieldStats.continueRate}}%

Responses Table
#{{value.title}}{{ 'PERCENTAGE_COMPLETE' | translate }}{{ 'TIME_ELAPSED' | translate }}{{ 'DEVICE' | translate }}{{ 'LOCATION' | translate }}{{ 'IP_ADDRESS' | translate }}{{ 'DATE_SUBMITTED' | translate }} (UTC){{ 'GENERATED_PDF' | translate }}
{{$index+1}}{{field.fieldValue}}{{row.percentageComplete}}%{{row.timeElapsed | secondsToDateTime | date:'mm:ss'}}{{row.device.name}}, {{row.device.type}}{{row.geoLocation.city}}, {{row.geoLocation.country_name}}{{row.ipAddr}}{{row.created | date:'yyyy-MM-dd HH:mm:ss'}}{{ 'GENERATED_PDF' | translate }}
"); + $templateCache.put("modules/forms/base/views/directiveViews/entryPage/startPage.html", + "

{{pageData.introTitle}}

{{pageData.introParagraph}}

"); + $templateCache.put("modules/forms/base/views/directiveViews/field/date.html", + "

{{index+1}} {{field.title}} {{ 'OPTIONAL' | translate }}

{{field.description}}

"); + $templateCache.put("modules/forms/base/views/directiveViews/field/dropdown.html", + "
0\">

{{index+1}} {{field.title}} {{ 'OPTIONAL' | translate }}

{{field.description}}


"); + $templateCache.put("modules/forms/base/views/directiveViews/field/file.html", + "

{{index+1}} {{field.title}} {{ 'OPTIONAL' | translate }}

{{field.file.originalname}}
{{ UPLOAD_FILE | translate }}
"); + $templateCache.put("modules/forms/base/views/directiveViews/field/hidden.html", + ""); + $templateCache.put("modules/forms/base/views/directiveViews/field/legal.html", + "

{{index+1}} {{field.title}} {{ 'OPTIONAL' | translate }}


{{field.description}}


"); + $templateCache.put("modules/forms/base/views/directiveViews/field/radio.html", + "
0\">

{{index+1}} {{field.title}} {{ 'OPTIONAL' | translate }}

{{field.description}}


"); + $templateCache.put("modules/forms/base/views/directiveViews/field/rating.html", + "

{{index+1}} {{field.title}} {{ 'OPTIONAL' | translate }}

{{field.description}}

"); + $templateCache.put("modules/forms/base/views/directiveViews/field/statement.html", + "

{{field.title}}

{{field.description}}

{{field.description}}


"); + $templateCache.put("modules/forms/base/views/directiveViews/field/textarea.html", + "

{{index+1}} {{field.title}} {{ 'OPTIONAL' | translate }}

{{ 'NEWLINE' | translate }}

{{field.description}}

Press SHIFT+ENTER to add a newline
{{ 'ENTER' | translate }}
"); + $templateCache.put("modules/forms/base/views/directiveViews/field/textfield.html", + "

{{index+1}} {{field.title}} ({{ 'OPTIONAL' | translate }})

{{field.description}}

{{ 'ENTER' | translate }}
"); + $templateCache.put("modules/forms/base/views/directiveViews/field/yes_no.html", + "

{{index+1}} {{field.title}} {{ 'OPTIONAL' | translate }}

{{field.description}}


"); + $templateCache.put("modules/forms/base/views/directiveViews/form/submit-form.client.view.html", + "
{{ 'COMPLETING_NEEDED' | translate:translateAdvancementData }}
{{ 'ENTER' | translate }}

{{ 'ADVANCEMENT' | translate:translateAdvancementData }}

"); + $templateCache.put("modules/users/views/authentication/access-denied.client.view.html", + "

{{ 'ACCESS_DENIED_TEXT' | translate }}

{{ 'SIGNIN_BTN' | translate }}
"); + $templateCache.put("modules/users/views/authentication/signin.client.view.html", + "

Sign into your account

Error:
  or  {{ 'SIGNUP_BTN' | translate }}
"); + $templateCache.put("modules/users/views/authentication/signup-success.client.view.html", + "

{{ 'SUCCESS_HEADER' | translate }}

{{ 'SUCCESS_TEXT' | translate }}

{{ 'NOT_ACTIVATED_YET' | translate }}



{{ 'BEFORE_YOU_CONTINUE' | translate }} polydaic@gmail.com

"); + $templateCache.put("modules/users/views/authentication/signup.client.view.html", + "

Signup with your email

Couldn't complete registration due to errors:
{{ 'LANGUAGE_LABEL' | translate }}

"); + $templateCache.put("modules/users/views/password/forgot-password.client.view.html", + "

{{ 'PASSWORD_RESTORE_HEADER' | translate }}

{{ 'ENTER_YOUR_EMAIL' | translate }}

{{error}}
{{success}}
"); + $templateCache.put("modules/users/views/password/reset-password-invalid.client.view.html", + "

{{ 'PASSWORD_RESET_INVALID' | translate }}

{{ 'ASK_FOR_NEW_PASSWORD' | translate }}
"); + $templateCache.put("modules/users/views/password/reset-password-success.client.view.html", + "

{{ 'PASSWORD_RESET_SUCCESS' | translate }}

{{ 'CONTINUE_TO_LOGIN' | translate }}
"); + $templateCache.put("modules/users/views/password/reset-password.client.view.html", + "

Reset your password

{{error}}
{{success}}
"); + $templateCache.put("modules/users/views/settings/change-password.client.view.html", + "

Change your password


{{ 'PASSWORD_CHANGE_SUCCESS' | translate }}
"); + $templateCache.put("modules/users/views/settings/edit-profile.client.view.html", + "1

Edit your profile

{{ 'PROFILE_SAVE_SUCCESS' | translate }}
{{ 'PROFILE_SAVE_ERROR' | translate }}
{{ 'FIRST_NAME_LABEL' | translate }}
Last Name

{{ 'LANGUAGE_LABEL' | translate }}
Username
{{ 'EMAIL_LABEL' | translate }}
"); + $templateCache.put("modules/users/views/settings/social-accounts.client.view.html", + "

Connected social accounts:

Connect other social accounts:

"); + $templateCache.put("modules/users/views/verify/resend-verify-email.client.view.html", + "

Resend your account verification email

Enter your account email.

{{error}}

Verification Email has been Sent

{{ 'VERIFICATION_EMAIL_SENT' | translate }} {{username}}.
{{ 'NOT_ACTIVATED_YET' | translate }}

{{ 'CHECK_YOUR_EMAIL' | translate }} polydaic@gmail.com

"); + $templateCache.put("modules/users/views/verify/verify-account.client.view.html", + "

{{ 'CONTINUE_TO_LOGIN' | translate }}

{{ 'VERIFY_ERROR' | translate }}

{{ 'REVERIFY_ACCOUNT_LINK' | translate }} {{ 'SIGNIN_BTN' | translate }}
"); +}]); + +'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 + +'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'); + +}]); + +'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', + }); + +}]); + +'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', + }); + +}]); + +'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', + }); + +}]); + +'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' + }); + +}]); + +'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; + }; +}); + +'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: ["Forms", "$stateParams", function (Forms, $stateParams) { + return Forms.get({formId: $stateParams.formId}).$promise; + }] + }, + controller: 'SubmitFormController', + controllerAs: 'ctrl' + }) + } +]); + +(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; + + } +}()); + + +'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; + }); + } + + }); + } + }; +}); + +'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'; + }); + } + }); + } + }; +}]); + + +'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' +]); + +'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); + + } +]); + +'use strict'; + +angular.module('view-form').directive('fieldIconDirective', function() { + + return { + template: '', + restrict: 'E', + scope: { + typeName: '@' + }, + controller: ["$scope", 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]; + }] + }; +}); + +'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: '
{{field.title}}
', + 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); + } + }; +}]); + +'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); + }); + } + }); + } + }; +}]); + +'use strict'; + +angular.module('view-form').directive('onFinishRender', ["$rootScope", "$timeout", 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'); + }); + } + } + }; +}]); + +'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: ["$document", "$window", "$scope", 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(); + }] + }; + } +]); + +'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; + }; + } +); + +'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' + } + }); + } +]); + +(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); + } + } + } +}()); + +'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; + }; + + } +]); diff --git a/public/dist/form-application.min.js b/public/dist/form-application.min.js new file mode 100644 index 00000000..c9b36171 --- /dev/null +++ b/public/dist/form-application.min.js @@ -0,0 +1,3 @@ +"use strict";var ApplicationConfiguration=function(){var applicationModuleName="NodeForm",applicationModuleVendorDependencies=["duScroll","ui.select","cgBusy","ngSanitize","vButton","ngResource","NodeForm.templates","ui.router","ui.bootstrap","ui.utils","pascalprecht.translate","ng.deviceDetector"],registerModule=function(moduleName,dependencies){angular.module(moduleName,dependencies||[]),angular.module(applicationModuleName).requires.push(moduleName)};return{applicationModuleName:applicationModuleName,applicationModuleVendorDependencies:applicationModuleVendorDependencies,registerModule:registerModule}}();angular.module(ApplicationConfiguration.applicationModuleName,ApplicationConfiguration.applicationModuleVendorDependencies),angular.module(ApplicationConfiguration.applicationModuleName).config(["$locationProvider",function($locationProvider){$locationProvider.hashPrefix("!")}]),angular.module(ApplicationConfiguration.applicationModuleName).constant("APP_PERMISSIONS",{viewAdminSettings:"viewAdminSettings",editAdminSettings:"editAdminSettings",editForm:"editForm",viewPrivateForm:"viewPrivateForm"}),angular.module(ApplicationConfiguration.applicationModuleName).constant("USER_ROLES",{admin:"admin",normal:"user",superuser:"superuser"}),angular.module(ApplicationConfiguration.applicationModuleName).constant("FORM_URL","/forms/:formId"),angular.element(document).ready(function(){"#_=_"===window.location.hash&&(window.location.hash="#!"),angular.bootstrap(document,[ApplicationConfiguration.applicationModuleName])}),angular.module("NodeForm.templates",[]).run(["$templateCache",function($templateCache){$templateCache.put("modules/core/views/header.client.view.html",''),$templateCache.put("modules/forms/admin/views/admin-form.client.view.html",'
'),$templateCache.put("modules/forms/admin/views/list-forms.client.view.html",'

{{ \'CREATE_A_NEW_FORM\' | translate }}
Name
Language

'),$templateCache.put("modules/forms/base/views/submit-form.client.view.html","
"),$templateCache.put("modules/forms/admin/views/adminTabs/analyze.html",""),$templateCache.put("modules/forms/admin/views/adminTabs/configure.html",""),$templateCache.put("modules/forms/admin/views/adminTabs/create.html",""),$templateCache.put("modules/forms/admin/views/adminTabs/design.html",'
{{ \'BACKGROUND_COLOR\' | translate }}
{{ \'QUESTION_TEXT_COLOR\' | translate }}
{{ \'ANSWER_TEXT_COLOR\' | translate }}
{{ \'BTN_BACKGROUND_COLOR\' | translate }}
{{ \'BTN_TEXT_COLOR\' | translate }}
'),$templateCache.put("modules/forms/admin/views/directiveViews/cgBusy/update-form-message-TypeA.html",'
{{$message}}
'),$templateCache.put("modules/forms/admin/views/directiveViews/cgBusy/update-form-message-TypeB.html",'
{{$message}}
'),$templateCache.put("modules/forms/admin/views/directiveViews/form/configure-form.client.view.html",'
{{ \'SAVE_PDF_SUBMISSIONS\' | translate }}
{{ \'UPLOAD_YOUR_PDF\' | translate }}
{{myform.pdf.name}}
{{ \'UPLOAD_YOUR_PDF\' | translate }}
{{ \'Autogenerate Form?\' | translate }}
{{ \'FORM_NAME\' | translate }}
{{ \'FORM_STATUS\' | translate }}
{{ \'GA_TRACKING_CODE\' | translate }}
Language
* required
{{ \'DISPLAY_FOOTER\' | translate }}
Display Start Page?
'),$templateCache.put("modules/forms/admin/views/directiveViews/form/edit-form.client.view.html",'

{{ \'EDIT_START_PAGE\' | translate }}


{{ \'INTRO_TITLE\' | translate }}:
{{ \'INTRO_PARAGRAPH\' | translate }}:
{{ \'INTRO_BTN\' | translate }}:


Buttons:
{{ \'BUTTON_TEXT\' | translate }}
{{ \'BUTTON_LINK\' | translate }}


{{field.title}} *

{{ \'EDIT_FIELD\' | translate }}


{{ \'QUESTION_TITLE\' | translate }}:

{{ \'QUESTION_DESCRIPTION\' | translate }}:

{{ \'OPTIONS\' | translate }}:

{{ \'NUM_OF_STEPS\' | translate }}

Shape:

Required:
Disabled:

{{ \'CLICK_FIELDS_FOOTER\' | translate }}


'),$templateCache.put("modules/forms/admin/views/directiveViews/form/edit-submissions-form.client.view.html",'
Overview Analytics
{{ \'TOTAL_VIEWS\' | translate }}
{{ \'RESPONSES\' | translate }}
{{ \'COMPLETION_RATE\' | translate }}
{{ \'AVERAGE_TIME_TO_COMPLETE\' | translate }}
{{myform.analytics.views}}
{{myform.analytics.submissions}}
{{myform.analytics.conversionRate | number:0}}%
{{AverageTimeElapsed | secondsToDateTime | date:\'mm:ss\'}}
Device Analytics
{{ \'DESKTOP_AND_LAPTOP\' | translate }}
{{ \'TABLETS\' | translate }}
{{ \'PHONES\' | translate }}
{{ \'OTHER\' | translate }}
{{ \'UNIQUE_VISITS\' | translate }}
{{DeviceStatistics.desktop.visits}}
{{ \'UNIQUE_VISITS\' | translate }}
{{DeviceStatistics.tablet.visits}}
{{ \'UNIQUE_VISITS\' | translate }}
{{DeviceStatistics.tablet.visits}}
{{ \'UNIQUE_VISITS\' | translate }}
{{DeviceStatistics.other.visits}}
{{ \'RESPONSES\' | translate }}
{{DeviceStatistics.desktop.responses}}
{{ \'RESPONSES\' | translate }}
{{DeviceStatistics.tablet.responses}}
{{ \'RESPONSES\' | translate }}
{{DeviceStatistics.phone.responses}}
{{ \'RESPONSES\' | translate }}
{{DeviceStatistics.other.responses}}
{{ \'COMPLETION_RATE\' | translate }}
{{DeviceStatistics.desktop.completion}}
{{ \'COMPLETION_RATE\' | translate }}
{{DeviceStatistics.tablet.completion}}
{{ \'COMPLETION_RATE\' | translate }}
{{DeviceStatistics.phone.completion}}
{{ \'COMPLETION_RATE\' | translate }}
{{DeviceStatistics.other.completion}}
{{ \'AVERAGE_TIME_TO_COMPLETE\' | translate }}
{{DeviceStatistics.desktop.average_time | secondsToDateTime | date:\'mm:ss\'}}
{{ \'AVERAGE_TIME_TO_COMPLETE\' | translate }}
{{DeviceStatistics.tablet.average_time | secondsToDateTime | date:\'mm:ss\'}}
{{ \'AVERAGE_TIME_TO_COMPLETE\' | translate }}
{{DeviceStatistics.phone.average_time | secondsToDateTime | date:\'mm:ss\'}}
{{ \'AVERAGE_TIME_TO_COMPLETE\' | translate }}
{{DeviceStatistics.other.average_time | secondsToDateTime | date:\'mm:ss\'}}
Field Analytics
{{ \'FIELD_TITLE\' | translate }}
{{ \'FIELD_VIEWS\' | translate }}
{{ \'FIELD_RESPONSES\' | translate }}
{{ \'FIELD_DROPOFF\' | translate }}
{{fieldStats.field.title}}
{{fieldStats.totalViews}}
{{fieldStats.responses}}
{{fieldStats.continueRate}}%

Responses Table
#{{value.title}}{{ \'PERCENTAGE_COMPLETE\' | translate }}{{ \'TIME_ELAPSED\' | translate }}{{ \'DEVICE\' | translate }}{{ \'LOCATION\' | translate }}{{ \'IP_ADDRESS\' | translate }}{{ \'DATE_SUBMITTED\' | translate }} (UTC){{ \'GENERATED_PDF\' | translate }}
{{$index+1}}{{field.fieldValue}}{{row.percentageComplete}}%{{row.timeElapsed | secondsToDateTime | date:\'mm:ss\'}}{{row.device.name}}, {{row.device.type}}{{row.geoLocation.city}}, {{row.geoLocation.country_name}}{{row.ipAddr}}{{row.created | date:\'yyyy-MM-dd HH:mm:ss\'}}{{ \'GENERATED_PDF\' | translate }}
'), +$templateCache.put("modules/forms/base/views/directiveViews/entryPage/startPage.html",'

{{pageData.introTitle}}

{{pageData.introParagraph}}

'),$templateCache.put("modules/forms/base/views/directiveViews/field/date.html",'

{{index+1}} {{field.title}} {{ \'OPTIONAL\' | translate }}

{{field.description}}

'),$templateCache.put("modules/forms/base/views/directiveViews/field/dropdown.html",'
'),$templateCache.put("modules/forms/base/views/directiveViews/field/file.html",'

{{index+1}} {{field.title}} {{ \'OPTIONAL\' | translate }}

{{field.file.originalname}}
{{ UPLOAD_FILE | translate }}
'),$templateCache.put("modules/forms/base/views/directiveViews/field/hidden.html",''),$templateCache.put("modules/forms/base/views/directiveViews/field/legal.html",'
'),$templateCache.put("modules/forms/base/views/directiveViews/field/radio.html",'

{{index+1}} {{field.title}} {{ \'OPTIONAL\' | translate }}

{{field.description}}


'),$templateCache.put("modules/forms/base/views/directiveViews/field/rating.html",'

{{index+1}} {{field.title}} {{ \'OPTIONAL\' | translate }}

{{field.description}}

'),$templateCache.put("modules/forms/base/views/directiveViews/field/statement.html",'

{{field.title}}

{{field.description}}

{{field.description}}


'),$templateCache.put("modules/forms/base/views/directiveViews/field/textarea.html",'

{{index+1}} {{field.title}} {{ \'OPTIONAL\' | translate }}

{{ \'NEWLINE\' | translate }}

{{field.description}}

Press SHIFT+ENTER to add a newline
'),$templateCache.put("modules/forms/base/views/directiveViews/field/textfield.html",'

{{index+1}} {{field.title}} ({{ \'OPTIONAL\' | translate }})

{{field.description}}

'),$templateCache.put("modules/forms/base/views/directiveViews/field/yes_no.html",'

{{index+1}} {{field.title}} {{ \'OPTIONAL\' | translate }}

{{field.description}}


'),$templateCache.put("modules/forms/base/views/directiveViews/form/submit-form.client.view.html",'
{{ \'COMPLETING_NEEDED\' | translate:translateAdvancementData }}
'),$templateCache.put("modules/users/views/authentication/access-denied.client.view.html","

{{ 'ACCESS_DENIED_TEXT' | translate }}

{{ 'SIGNIN_BTN' | translate }}
"),$templateCache.put("modules/users/views/authentication/signin.client.view.html",'

Sign into your account

'),$templateCache.put("modules/users/views/authentication/signup-success.client.view.html",''),$templateCache.put("modules/users/views/authentication/signup.client.view.html",''),$templateCache.put("modules/users/views/password/forgot-password.client.view.html",'

{{ \'PASSWORD_RESTORE_HEADER\' | translate }}

{{ \'ENTER_YOUR_EMAIL\' | translate }}

'),$templateCache.put("modules/users/views/password/reset-password-invalid.client.view.html","

{{ 'PASSWORD_RESET_INVALID' | translate }}

{{ 'ASK_FOR_NEW_PASSWORD' | translate }}
"),$templateCache.put("modules/users/views/password/reset-password-success.client.view.html","

{{ 'PASSWORD_RESET_SUCCESS' | translate }}

{{ 'CONTINUE_TO_LOGIN' | translate }}
"),$templateCache.put("modules/users/views/password/reset-password.client.view.html",'

Reset your password

'),$templateCache.put("modules/users/views/settings/change-password.client.view.html",'

Change your password

'),$templateCache.put("modules/users/views/settings/edit-profile.client.view.html",'1

Edit your profile

'), +$templateCache.put("modules/users/views/settings/social-accounts.client.view.html",'

Connected social accounts:

Connect other social accounts:

'),$templateCache.put("modules/users/views/verify/resend-verify-email.client.view.html",'

Resend your account verification email

Enter your account email.

{{error}}

Verification Email has been Sent

{{ \'VERIFICATION_EMAIL_SENT\' | translate }} {{username}}.
{{ \'NOT_ACTIVATED_YET\' | translate }}

{{ \'CHECK_YOUR_EMAIL\' | translate }} polydaic@gmail.com

'),$templateCache.put("modules/users/views/verify/verify-account.client.view.html","

{{ 'CONTINUE_TO_LOGIN' | translate }}

{{ 'VERIFY_ERROR' | translate }}

{{ 'REVERIFY_ACCOUNT_LINK' | translate }} {{ 'SIGNIN_BTN' | translate }}
")}]),ApplicationConfiguration.registerModule("view-form",["ngFileUpload","ui.router.tabs","ui.date","ui.sortable","angular-input-stars","pascalprecht.translate"]),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")}]),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"})}]),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"})}]),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"})}]),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"})}]),angular.module("view-form").filter("formValidity",function(){return function(formObj){if(formObj&&formObj.form_fields&&formObj.visible_form_fields){var formKeys=Object.keys(formObj),fields=(formKeys.filter(function(key){return"$"!==key[0]}),formObj.form_fields),valid_count=fields.filter(function(field){return"object"==typeof field&&"statement"!==field.fieldType&&"rating"!==field.fieldType?!!field.fieldValue:void 0}).length;return valid_count-(formObj.form_fields.length-formObj.visible_form_fields.length)}return 0}}),angular.module("view-form").config(["$stateProvider",function($stateProvider){$stateProvider.state("submitForm",{url:"/forms/:formId",templateUrl:"/static/form_modules/forms/base/views/submit-form.client.view.html",resolve:{Forms:"Forms",myForm:["Forms","$stateParams",function(Forms,$stateParams){return Forms.get({formId:$stateParams.formId}).$promise}]},controller:"SubmitFormController",controllerAs:"ctrl"})}]),function(){function SendVisitorData(Socket,$state){function send(form,lastActiveIndex,timeElapsed){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(){Socket.socket||Socket.connect()}var service={send:send};return init(),service}angular.module("view-form").factory("SendVisitorData",SendVisitorData),SendVisitorData.$inject=["Socket","$state"]}(),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,index=parseInt(String.fromCharCode(keyCode))-1;index<$scope.field.fieldOptions.length&&(event.preventDefault(),$scope.$apply(function(){$scope.field.fieldValue=$scope.field.fieldOptions[index].option_value}))})}}}),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,truthyKeyCode=$attrs.keyCharTruthy.charCodeAt(0)-32,falseyKeyCode=$attrs.keyCharFalsey.charCodeAt(0)-32;keyCode===truthyKeyCode?(event.preventDefault(),$scope.$apply(function(){$scope.field.fieldValue="true"})):keyCode===falseyKeyCode&&(event.preventDefault(),$scope.$apply(function(){$scope.field.fieldValue="false"}))})}}}]),angular.module("view-form").filter("formValidity",function(){return function(formObj){if(formObj&&formObj.form_fields&&formObj.visible_form_fields){var formKeys=Object.keys(formObj),fields=(formKeys.filter(function(key){return"$"!==key[0]}),formObj.form_fields),valid_count=fields.filter(function(field){return"object"==typeof field&&"statement"!==field.fieldType&&"rating"!==field.fieldType?!!field.fieldValue:void 0}).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"]),angular.module("view-form").controller("SubmitFormController",["$scope","$rootScope","$state","$translate","myForm",function($scope,$rootScope,$state,$translate,myForm){$scope.myform=myForm,$translate.use(myForm.language)}]),angular.module("view-form").directive("fieldIconDirective",function(){return{template:'',restrict:"E",scope:{typeName:"@"},controller:["$scope",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]}]}});var __indexOf=[].indexOf||function(item){for(var i=0,l=this.length;l>i;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,templateUrl="modules/forms/base/views/directiveViews/field/";return __indexOf.call(supportedFields,type)>=0&&(templateUrl=templateUrl+type+".html"),$templateCache.get(templateUrl)};return{template:"
{{field.title}}
",restrict:"E",scope:{field:"=",required:"&",design:"=",index:"=",forms:"="},link:function(scope,element){$rootScope.chooseDefaultOption=scope.chooseDefaultOption=function(type){"yes_no"===type?scope.field.fieldValue="true":"rating"===type?scope.field.fieldValue=0:"radio"===scope.field.fieldType?(console.log(scope.field),scope.field.fieldValue=scope.field.fieldOptions[0].option_value,console.log(scope.field.fieldValue)):"legal"===type&&(scope.field.fieldValue="true",$rootScope.nextField())},scope.setActiveField=$rootScope.setActiveField,"date"===scope.field.fieldType&&(scope.dateOptions={changeYear:!0,changeMonth:!0,altFormat:"mm/dd/yyyy",yearRange:"1900:-0",defaultDate:0});var fieldType=scope.field.fieldType;if("number"===scope.field.fieldType||"textfield"===scope.field.fieldType||"email"===scope.field.fieldType||"link"===scope.field.fieldType){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"}fieldType="textfield"}var template=getTemplateUrl(fieldType);element.html(template).show();$compile(element.contents())(scope)}}}]),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,onEnterKeyDisabled=!1;null!==$attrs.onEnterKeyDisabled&&(onEnterKeyDisabled=$attrs.onEnterKeyDisabled),13!==keyCode||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;9!==keyCode||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;13!==keyCode&&9!==keyCode||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;9===keyCode&&event.shiftKey&&(event.preventDefault(),$rootScope.$apply(function(){$rootScope.$eval($attrs.onTabAndShiftKey)}))})}}}]),angular.module("view-form").directive("onFinishRender",["$rootScope","$timeout",function($rootScope,$timeout){return{restrict:"A",link:function(scope,element,attrs){if(element.attr("ng-repeat")||element.attr("data-ng-repeat")){var broadcastMessage=attrs.onFinishRender||"ngRepeat";scope.$first&&!scope.$last?scope.$evalAsync(function(){$rootScope.$broadcast(broadcastMessage+" Started")}):scope.$last&&scope.$evalAsync(function(){$rootScope.$broadcast(broadcastMessage+" Finished")})}}}}]),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:["$document","$window","$scope",function($document,$window,$scope){$scope.noscroll=!1,$scope.forms={};var form_fields_count=$scope.myform.visible_form_fields.filter(function(field){return"statement"!==field.fieldType&&"rating"!==field.fieldType}).length,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(){$scope.myform.submitted=!1,$scope.myform.form_fields=_.chain($scope.myform.visible_form_fields).map(function(field){return field.fieldValue="",field}).value(),$scope.loading=!1,$scope.error="",$scope.selected={_id:"",index:0},$scope.setActiveField($scope.myform.visible_form_fields[0]._id,0,!1),TimeCounter.restartClock()},$window.onscroll=function(){$scope.scrollPos=document.body.scrollTop||document.documentElement.scrollTop||0;var elemBox=document.getElementsByClassName("activeField")[0].getBoundingClientRect();$scope.fieldTop=elemBox.top,$scope.fieldBottom=elemBox.bottom;var field_id,field_index;$scope.noscroll||($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,!1)):$scope.selected.index===$scope.myform.visible_form_fields.length?$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,!1)):$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,!1)):0!==$scope.selected.index&&$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,!1)),$scope.$apply())};var getActiveField=function(){if(null===$scope.selected)throw console.error("current active field is null"),new Error("current active field is null");return"submit_field"===$scope.selected._id?$scope.myform.form_fields.length-1:$scope.selected.index};$scope.setActiveField=$rootScope.setActiveField=function(field_id,field_index,animateScroll){if(null!==$scope.selected&&$scope.selected._id!==field_id){$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},animateScroll?($scope.noscroll=!0,setTimeout(function(){$document.scrollToElement(angular.element(".activeField"),-10,200).then(function(){$scope.noscroll=!1,setTimeout(function(){document.querySelectorAll(".activeField .focusOn").length?document.querySelectorAll(".activeField .focusOn")[0].focus():document.querySelectorAll(".activeField input").length?document.querySelectorAll(".activeField input")[0].focus():document.querySelectorAll(".activeField .selectize-input")[0].focus()})})})):setTimeout(function(){document.querySelectorAll(".activeField .focusOn")[0]?document.querySelectorAll(".activeField .focusOn")[0].focus():document.querySelectorAll(".activeField input")[0].focus()}),SendVisitorData.send($scope.myform,getActiveField(),TimeCounter.getTimeElapsed())}},$rootScope.nextField=$scope.nextField=function(){var selected_index,selected_id;$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,!0)):$scope.selected.index===$scope.myform.visible_form_fields.length-1&&(selected_index=$scope.selected.index+1,selected_id="submit_field",$rootScope.setActiveField(selected_id,selected_index,!0))},$rootScope.prevField=$scope.prevField=function(){if($scope.selected.index>0){var selected_index=$scope.selected.index-1,selected_id=$scope.myform.visible_form_fields[selected_index]._id;$scope.setActiveField(selected_id,selected_index,!0)}},$scope.exitStartPage=function(){$scope.myform.startPage.showStart=!1,$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=!0;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++)"dropdown"!==$scope.myform.form_fields[i].fieldType||$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=!0,$scope.loading=!1,SendVisitorData.send($scope.myform,getActiveField(),_timeElapsed)}).error(function(error){$scope.loading=!1,console.error(error),$scope.error=error.message})},500)},$scope.reloadForm()}]}}]),angular.module("view-form").service("CurrentForm",function(){var _form={};this.getForm=function(){return _form},this.setForm=function(form){_form=form}}),angular.module("view-form").factory("Forms",["$resource","FORM_URL",function($resource,FORM_URL){return $resource(FORM_URL,{formId:"@_id"},{query:{method:"GET",isArray:!0},get:{method:"GET",transformResponse:function(data,header){var form=angular.fromJson(data);return form.visible_form_fields=_.filter(form.form_fields,function(field){return field.deletePreserved===!1}),form}},update:{method:"PUT"},save:{method:"POST"}})}]),function(){function Socket($timeout,$window){function connect(url){service.socket=io(url,{transports:["websocket","polling"]})}function emit(eventName,data){service.socket&&service.socket.emit(eventName,data)}function on(eventName,callback){service.socket&&service.socket.on(eventName,function(data){$timeout(function(){callback(data)})})}function removeListener(eventName){service.socket&&service.socket.removeListener(eventName)}var service={connect:connect,emit:emit,on:on,removeListener:removeListener,socket:null};return connect(window.location.protocol+"//"+window.location.hostname+":"+$window.socketPort),service}angular.module("view-form").factory("Socket",Socket),Socket.$inject=["$timeout","$window"]}(),angular.module("view-form").service("TimeCounter",[function(){var _startTime,_endTime=null;this.timeSpent=0,this.restartClock=function(){_startTime=Date.now(),_endTime=null},this.getTimeElapsed=function(){return _startTime?Math.abs(Date.now().valueOf()-_startTime.valueOf())/1e3:void 0},this.stopClock=function(){return _startTime&&null===_endTime?(_endTime=Date.now(),this.timeSpent=Math.abs(_endTime.valueOf()-_startTime.valueOf())/1e3,this._startTime=this._endTime=null,this.timeSpent):new Error("Clock has not been started")},this.clockStarted=function(){return!!this._startTime}}]); \ No newline at end of file diff --git a/public/form_modules/forms/tests/unit/controllers/admin-form.client.controller.test.js b/public/form_modules/forms/tests/unit/controllers/admin-form.client.controller.test.js deleted file mode 100644 index 7530812f..00000000 --- a/public/form_modules/forms/tests/unit/controllers/admin-form.client.controller.test.js +++ /dev/null @@ -1,256 +0,0 @@ -'use strict'; - -(function() { - // Forms Controller Spec - describe('AdminForm Controller Tests', function() { - // Initialize global variables - var AdminFormController, - createAdminFormController, - scope, - $httpBackend, - $stateParams, - $location, - $state; - - var sampleUser = { - firstName: 'Full', - lastName: 'Name', - email: 'test@test.com', - username: 'test@test.com', - password: 'password', - provider: 'local', - roles: ['user'], - _id: 'ed873933b1f1dea0ce12fab9' - }; - - var sampleForm = { - title: 'Form Title', - admin: 'ed873933b1f1dea0ce12fab9', - language: 'english', - form_fields: [ - {fieldType:'textfield', title:'First Name', fieldValue: '', deletePreserved: false, _id:'56340745f59a6fc9e22028e9'}, - {fieldType:'checkbox', title:'nascar', fieldValue: '', deletePreserved: false, _id:'5c9e22028e907634f45f59a6'}, - {fieldType:'checkbox', title:'hockey', fieldValue: '', deletePreserved: false, _id:'56e90745f5934fc9e22028a6'} - ], - _id: '525a8422f6d0f87f0e407a33' - }; - - var expectedForm = { - title: 'Form Title', - admin: 'ed873933b1f1dea0ce12fab9', - language: 'english', - form_fields: [ - {fieldType:'textfield', title:'First Name', fieldValue: '', deletePreserved: false, _id:'56340745f59a6fc9e22028e9'}, - {fieldType:'checkbox', title:'nascar', fieldValue: '', deletePreserved: false, _id:'5c9e22028e907634f45f59a6'}, - {fieldType:'checkbox', title:'hockey', fieldValue: '', deletePreserved: false, _id:'56e90745f5934fc9e22028a6'} - ], - visible_form_fields: [ - {fieldType:'textfield', title:'First Name', fieldValue: '', deletePreserved: false, _id:'56340745f59a6fc9e22028e9'}, - {fieldType:'checkbox', title:'nascar', fieldValue: '', deletePreserved: false, _id:'5c9e22028e907634f45f59a6'}, - {fieldType:'checkbox', title:'hockey', fieldValue: '', deletePreserved: false, _id:'56e90745f5934fc9e22028a6'} - ], - _id: '525a8422f6d0f87f0e407a33' - }; - - var newFakeModal = function(){ - var result = { - opened: true, - result: { - then: function(confirmCallback, cancelCallback) { - //Store the callbacks for later when the user clicks on the OK or Cancel button of the dialog - this.confirmCallBack = confirmCallback; - this.cancelCallback = cancelCallback; - } - }, - close: function( item ) { - //The user clicked OK on the modal dialog, call the stored confirm callback with the selected item - this.opened = false; - this.result.confirmCallBack( item ); - }, - dismiss: function( type ) { - //The user clicked cancel on the modal dialog, call the stored cancel callback - this.opened = false; - this.result.cancelCallback( type ); - } - }; - return result; - }; - - //Mock Users Service - beforeEach(module(function($provide) { - $provide.service('myForm', function($q) { - return sampleForm; - }); - })); - - - // The $resource service augments the response object with methods for updating and deleting the resource. - // If we were to use the standard toEqual matcher, our tests would fail because the test values would not match - // the responses exactly. To solve the problem, we define a new toEqualData Jasmine matcher. - // When the toEqualData matcher compares two objects, it takes only object properties into - // account and ignores methods. - beforeEach(function() { - jasmine.addMatchers({ - toEqualData: function(util, customEqualityTesters) { - return { - compare: function(actual, expected) { - return { - pass: angular.equals(actual, expected) - }; - } - }; - } - }); - }); - - // Load the main application module - beforeEach(module(ApplicationConfiguration.applicationModuleName)); - - beforeEach(module('stateMock')); - - //Mock Users Service - beforeEach(module(function($provide) { - $provide.service('User', function($q) { - return { - getCurrent: function() { - var deferred = $q.defer(); - deferred.resolve( JSON.stringify(sampleUser) ); - return deferred.promise; - }, - login: function(credentials) { - var deferred = $q.defer(); - if( credentials.password === sampleUser.password && credentials.username === sampleUser.username){ - deferred.resolve( JSON.stringify(sampleUser) ); - }else { - deferred.resolve('Error: User could not be loggedin'); - } - - return deferred.promise; - }, - logout: function() { - var deferred = $q.defer(); - deferred.resolve(null); - return deferred.promise; - }, - signup: function(credentials) { - var deferred = $q.defer(); - if( credentials.password === sampleUser.password && credentials.username === sampleUser.username){ - deferred.resolve( JSON.stringify(sampleUser) ); - }else { - deferred.resolve('Error: User could not be signed up'); - } - - return deferred.promise; - } - }; - }); - })); - - //Mock Authentication Service - beforeEach(module(function($provide) { - $provide.service('Auth', function() { - return { - ensureHasCurrentUser: function() { - return sampleUser; - }, - isAuthenticated: function() { - return true; - }, - getUserState: function() { - return true; - } - }; - }); - })); - - - //Mock $uibModal - beforeEach(inject(function($uibModal) { - var modal = newFakeModal(); - spyOn($uibModal, 'open').and.returnValue(modal); - //spyOn($uibModal, 'close').and.callFake(modal.close()); - })); - - // The injector ignores leading and trailing underscores here (i.e. _$httpBackend_). - // This allows us to inject a service but then attach it to a variable - // with the same name as the service. - beforeEach(inject(function($controller, $rootScope, _$state_, _$location_, _$stateParams_, _$httpBackend_, CurrentForm, Forms) { - // Set a new global scope - scope = $rootScope.$new(); - - //Set CurrentForm - CurrentForm.setForm(sampleForm); - - // Point global variables to injected services - $stateParams = _$stateParams_; - $httpBackend = _$httpBackend_; - $location = _$location_; - $state = _$state_; - - $httpBackend.whenGET(/\.html$/).respond(''); - $httpBackend.whenGET('/users/me/').respond(''); - - // Initialize the Forms controller. - createAdminFormController = function(){ - return $controller('AdminFormController', { $scope: scope }); - }; - })); - - it('AdminFormController should fetch current Form when instantiated', function() { - // Run controller functionality - var controller = createAdminFormController(); - - // Test scope value - expect(scope.myform).toEqualData(sampleForm); - }); - - it('$scope.removeCurrentForm() with valid form data should send a DELETE request with the id of form', function() { - var controller = createAdminFormController(); - - //Set $state transition - $state.expectTransitionTo('listForms'); - - // Set DELETE response - $httpBackend.expect('DELETE', /^(\/forms\/)([0-9a-fA-F]{24})$/).respond(200, sampleForm); - - //Run controller functionality - scope.openDeleteModal(); - scope.removeCurrentForm(); - - $httpBackend.flush(); - $state.ensureAllTransitionsHappened(); - }); - - it('$scope.update() should send a PUT request with the id of form', function() { - var controller = createAdminFormController(); - - //Set PUT response - $httpBackend.expect('PUT', /^(\/forms\/)([0-9a-fA-F]{24})$/).respond(200, sampleForm); - - //Run controller functionality - scope.update(false, null); - - $httpBackend.flush(); - }); - - it('$scope.openDeleteModal() should open scope.deleteModal', function() { - var controller = createAdminFormController(); - - //Run controller functionality - scope.openDeleteModal(); - console.log(scope.deleteModal); - expect(scope.deleteModal.opened).toEqual(true); - }); - - it('$scope.cancelDeleteModal() should close $scope.deleteModal', inject(function($uibModal) { - var controller = createAdminFormController(); - - //Run controller functionality - scope.openDeleteModal(); - - //Run controller functionality - scope.cancelDeleteModal(); - expect( scope.deleteModal.opened ).toEqual(false); - })); - }); -}()); \ No newline at end of file diff --git a/public/form_modules/forms/tests/unit/controllers/list-forms.client.controller.test.js b/public/form_modules/forms/tests/unit/controllers/list-forms.client.controller.test.js deleted file mode 100644 index c89e73cf..00000000 --- a/public/form_modules/forms/tests/unit/controllers/list-forms.client.controller.test.js +++ /dev/null @@ -1,224 +0,0 @@ -'use strict'; - -(function() { - // Forms Controller Spec - describe('ListForms Controller Tests', function() { - // Initialize global variables - var ListFormsController, - createListFormsController, - scope, - $httpBackend, - $stateParams, - $location, - $state; - - var sampleForm = { - title: 'Form Title', - admin: 'ed873933b1f1dea0ce12fab9', - language: 'english', - form_fields: [ - {fieldType:'textfield', title:'First Name', fieldValue: '', deletePreserved: false}, - {fieldType:'checkbox', title:'nascar', fieldValue: '', deletePreserved: false}, - {fieldType:'checkbox', title:'hockey', fieldValue: '', deletePreserved: false} - ], - _id: '525a8422f6d0f87f0e407a33' - }; - - var sampleFormList = [{ - title: 'Form Title1', - admin: 'ed873933b1f1dea0ce12fab9', - language: 'english', - form_fields: [ - {fieldType:'textfield', title:'First Name', fieldValue: '', deletePreserved: false}, - {fieldType:'checkbox', title:'nascar', fieldValue: '', deletePreserved: false}, - {fieldType:'checkbox', title:'hockey', fieldValue: '', deletePreserved: false} - ], - _id: '525a8422f6d0f87f0e407a33' - },{ - title: 'Form Title2', - admin: '39223933b1f1dea0ce12fab9', - language: 'english', - form_fields: [ - {fieldType:'textfield', title:'First Name', fieldValue: '', deletePreserved: false}, - {fieldType:'checkbox', title:'nascar', fieldValue: '', deletePreserved: false}, - {fieldType:'checkbox', title:'hockey', fieldValue: '', deletePreserved: false} - ], - _id: '52f6d0f87f5a407a384220e3' - },{ - title: 'Form Title3', - admin: '2fab9ed873937f0e1dea0ce1', - language: 'english', - form_fields: [ - {fieldType:'textfield', title:'First Name', fieldValue: '', deletePreserved: false}, - {fieldType:'checkbox', title:'nascar', fieldValue: '', deletePreserved: false}, - {fieldType:'checkbox', title:'hockey', fieldValue: '', deletePreserved: false} - ], - _id: '922f6d0f87fed8730e4e1233' - } - ]; - - - // The $resource service augments the response object with methods for updating and deleting the resource. - // If we were to use the standard toEqual matcher, our tests would fail because the test values would not match - // the responses exactly. To solve the problem, we define a new toEqualData Jasmine matcher. - // When the toEqualData matcher compares two objects, it takes only object properties into - // account and ignores methods. - beforeEach(function() { - jasmine.addMatchers({ - toEqualData: function(util, customEqualityTesters) { - return { - compare: function(actual, expected) { - return { - pass: angular.equals(actual, expected) - }; - } - }; - } - }); - }); - - // Load the main application module - beforeEach(module(ApplicationConfiguration.applicationModuleName)); - - beforeEach(module('stateMock')); - - //Mock Users Service - beforeEach(module(function($provide) { - $provide.service('myForm', function($q) { - return sampleForm; - }); - })); - - - // The injector ignores leading and trailing underscores here (i.e. _$httpBackend_). - // This allows us to inject a service but then attach it to a variable - // with the same name as the service. - beforeEach(inject(function($controller, $rootScope, _$state_, _$location_, _$stateParams_, _$httpBackend_, CurrentForm, Forms) { - // Set a new global scope - scope = $rootScope.$new(); - - //Set CurrentForm - CurrentForm.setForm(sampleForm); - - // Point global variables to injected services - $stateParams = _$stateParams_; - $httpBackend = _$httpBackend_; - $location = _$location_; - $state = _$state_; - - $httpBackend.whenGET(/\.html$/).respond(''); - $httpBackend.whenGET('/users/me/').respond(''); - - // Initialize the Forms controller. - createListFormsController = function(){ - return $controller('ListFormsController', { $scope: scope }); - }; - })); - - it('$scope.findAll() should query all User\'s Forms', inject(function(Forms) { - - var controller = createListFormsController(); - - // Set GET response - $httpBackend.expectGET(/^(\/forms)$/).respond(200, sampleFormList); - - // Run controller functionality - scope.findAll(); - $httpBackend.flush(); - - // Test scope value - expect( scope.myforms ).toEqualData(sampleFormList); - })); - - it('$scope.duplicateForm() should duplicate a Form', inject(function(Forms) { - - var dupSampleForm = sampleFormList[2], - dupSampleForm_index = 3, - newSampleFormList = _.clone(sampleFormList); - dupSampleForm._id = 'a02df75b44c1d26b6a5e05b8'; - newSampleFormList.splice(3, 0, dupSampleForm); - - var controller = createListFormsController(); - - // Set GET response - $httpBackend.expectGET(/^(\/forms)$/).respond(200, sampleFormList); - // Run controller functionality - scope.findAll(); - $httpBackend.flush(); - - // Set GET response - $httpBackend.expect('POST', '/forms').respond(200, dupSampleForm); - // Run controller functionality - scope.duplicateForm(2); - $httpBackend.flush(); - - // Test scope value - expect( scope.myforms.length ).toEqual(newSampleFormList.length); - for(var i=0; i'); - $compile(el)(tmp_scope); - $rootScope.$digest(); - - // Point global variables to injected services - $httpBackend = _$httpBackend_; - - // $httpBackend.whenGET(/.+\.html$/).respond(''); - $httpBackend.whenGET('/users/me/').respond(''); - - //Grab controller instance - controller = el.controller(); - - //Grab scope. Depends on type of scope. - //See angular.element documentation. - scope = el.isolateScope() || el.scope(); - - })); - - it('$scope.uploadPDF() should upload a pdf file', function() { - // expect(scope.isInitialized).toBeDefined() - // expect(scope.log).toEqual(''); - - expect(scope.pdfLoading).toBe(false); - - //Set POST response - $httpBackend.when('POST', '/upload/pdf').respond(pdfObj); - - var files = [{}]; - scope.uploadPDF(files); - - $httpBackend.flush(); - expect(scope.myform.pdf).toEqualData(pdfObj); - }); - - it('$scope.removePDF() should removed uploaded pdf file', function() { - // expect(scope.isInitialized).toBeDefined() - // expect(scope.log).toEqual(''); - - scope.myform.pdf = pdfObj; - scope.myform.isGenerated = true; - scope.myform.autofillPDFs = true; - - scope.removePDF(); - - expect(scope.myform.pdf).toEqual(null); - expect(scope.myform.isGenerated).toBe(false); - expect(scope.myform.autofillPDFs).toBe(false); - }); - }); -}()); diff --git a/public/form_modules/forms/tests/unit/directives/edit-form-submissions.client.directive.test.js b/public/form_modules/forms/tests/unit/directives/edit-form-submissions.client.directive.test.js deleted file mode 100644 index 63520f48..00000000 --- a/public/form_modules/forms/tests/unit/directives/edit-form-submissions.client.directive.test.js +++ /dev/null @@ -1,200 +0,0 @@ -'use strict'; - -(function() { - // Forms Controller Spec - describe('EditSubmissions Directive-Controller Tests', function() { - // Initialize global variables - var el, scope, controller, $httpBackend; - - var sampleUser = { - firstName: 'Full', - lastName: 'Name', - email: 'test@test.com', - username: 'test@test.com', - password: 'password', - provider: 'local', - roles: ['user'], - _id: 'ed873933b1f1dea0ce12fab9' - }; - - var pdfObj = { - fieldname:'file', - originalname:'test.pdf', - name:'1440112660375.pdf', - encoding:'7bit', - mimetype:'application/pdf', - path:'uploads/tmp/test@test.com/1440112660375.pdf', - extension:'pdf', - size:56223, - truncated:false, - buffer:null - }; - - var sampleForm = { - title: 'Form Title', - admin: 'ed873933b1f1dea0ce12fab9', - language: 'english', - form_fields: [ - {fieldType:'textfield', title:'First Name', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed873933b0ce121f1deafab9'}, - {fieldType:'checkbox', title:'nascar', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed83b0ce121f17393deafab9'}, - {fieldType:'checkbox', title:'hockey', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed8317393deab0ce121ffab9'} - ], - pdf: {}, - pdfFieldMap: {}, - startPage: { - showStart: false - }, - hideFooter: false, - isGenerated: false, - isLive: false, - autofillPDFs: false, - _id: '525a8422f6d0f87f0e407a33' - }; - - var sampleSubmission = { - form_fields: [ - {fieldType:'textfield', title:'First Name', fieldValue: 'John Smith', deletePreserved: false}, - {fieldType:'checkbox', title:'nascar', fieldValue: 1, deletePreserved: false}, - {fieldType:'checkbox', title:'hockey', fieldValue: 0, deletePreserved: false} - ], - admin: sampleUser, - form: sampleForm, - timeElapsed: 17.55 - }; - - var sampleSubmissions = [{ - form_fields: [ - {fieldType:'textfield', title:'First Name', fieldValue: 'The Terminator', deletePreserved: false}, - {fieldType:'checkbox', title:'nascar', fieldValue: 0, deletePreserved: false}, - {fieldType:'checkbox', title:'hockey', fieldValue: 1, deletePreserved: false} - ], - admin: sampleUser, - form: sampleForm, - timeElapsed: 10.33 - }, - { - form_fields: [ - {fieldType:'textfield', title:'First Name', fieldValue: 'John Smith', deletePreserved: false}, - {fieldType:'checkbox', title:'nascar', fieldValue: 1, deletePreserved: false}, - {fieldType:'checkbox', title:'hockey', fieldValue: 0, deletePreserved: false} - ], - admin: sampleUser, - form: sampleForm, - timeElapsed: 2.33 - }, - { - form_fields: [ - {fieldType:'textfield', title:'First Name', fieldValue: 'Jane Doe', deletePreserved: false}, - {fieldType:'checkbox', title:'nascar', fieldValue: 1, deletePreserved: false}, - {fieldType:'checkbox', title:'hockey', fieldValue: 1, deletePreserved: false} - ], - admin: sampleUser, - form: sampleForm, - timeElapsed: 11.11 - }]; - - // The $resource service augments the response object with methods for updating and deleting the resource. - // If we were to use the standard toEqual matcher, our tests would fail because the test values would not match - // the responses exactly. To solve the problem, we define a new toEqualData Jasmine matcher. - // When the toEqualData matcher compares two objects, it takes only object properties into - // account and ignores methods. - beforeEach(function() { - jasmine.addMatchers({ - toEqualData: function(util, customEqualityTesters) { - return { - compare: function(actual, expected) { - return { - pass: angular.equals(actual, expected) - }; - } - }; - } - }); - }); - - // Load the main application module - beforeEach(module(ApplicationConfiguration.applicationModuleName)); - beforeEach(module('module-templates')); - beforeEach(module('stateMock')); - - beforeEach(inject(function($compile, $controller, $rootScope, _$httpBackend_) { - - // Point global variables to injected services - $httpBackend = _$httpBackend_; - - $httpBackend.whenGET('/users/me/').respond(''); - $httpBackend.whenGET(/^(\/forms\/)([0-9a-fA-F]{24})(\/submissions)$/).respond(200, sampleSubmissions); - - //Instantiate directive. - var tmp_scope = $rootScope.$new(); - tmp_scope.myform = sampleForm; - tmp_scope.user = sampleUser; - - //gotacha: Controller and link functions will execute. - el = angular.element(''); - $compile(el)(tmp_scope); - $rootScope.$digest(); - - //Grab controller instance - controller = el.controller(); - - //Grab scope. Depends on type of scope. - //See angular.element documentation. - scope = el.isolateScope() || el.scope(); - })); - - it('$scope.initFormSubmissions() should fetch all relevant form submissions', function() { - $httpBackend.expectGET(/^(\/forms\/)([0-9a-fA-F]{24})(\/submissions)$/).respond(200, sampleSubmissions); - scope.initFormSubmissions(); - $httpBackend.flush(); - scope.$digest(); - }); - - describe('Form Table Methods', function(){ - - it('$scope.toggleAllCheckers should toggle all checkboxes in table', function(){ - scope.initFormSubmissions(); - $httpBackend.flush(); - - //Run Controller Logic to Test - scope.table.masterChecker = true; - scope.toggleAllCheckers(); - - for(var i=0; i'); - $compile(el)(tmp_scope); - $rootScope.$digest(); - - // Point global variables to injected services - $httpBackend = _$httpBackend_; - - //$httpBackend.whenGET(/.+\.html$/).respond(''); - $httpBackend.whenGET('/users/me/').respond(''); - - //Grab controller instance - controller = el.controller(); - - //Grab scope. Depends on type of scope. - //See angular.element documentation. - scope = el.isolateScope() || el.scope(); - - })); - - describe('> Form Field >',function(){ - - beforeEach(function(){ - scope.myform = _.cloneDeep(sampleForm); - }); - - it('$scope.addNewField() should ADD a new field to $scope.myform.form_fields', function() { - - //Run controller methods - scope.addNewField(true, 'textfield'); - - var expectedFormField = { - title:'Short Text2', - fieldType:'textfield', - fieldValue: '', - required: true, - disabled: false, - deletePreserved: false - }; - - var actualFormField = _.cloneDeep(_.last(scope.myform.form_fields)); - delete actualFormField._id; - - expect(scope.myform.form_fields.length).toEqual(sampleForm.form_fields.length+1); - expect(actualFormField).toEqualData(expectedFormField); - }); - - it('$scope.deleteField() should DELETE a field to $scope.myform.form_fields', function() { - - //Run controller methods - scope.deleteField(0); - - expect(scope.myform.form_fields.length).toEqual(sampleForm.form_fields.length-1); - expect(_.first(scope.myform.form_fields)).toEqualData(sampleForm.form_fields[1]); - }); - - it('$scope.duplicateField() should DUPLICATE a field and update $scope.myform.form_fields', function() { - - //Run controller methods - scope.duplicateField(0); - - var originalField = _.cloneDeep(scope.myform.form_fields[0]); - originalField.title += ' copy'; - - delete originalField._id; - var copyField = _.cloneDeep(scope.myform.form_fields[1]); - delete copyField._id; - - expect(scope.myform.form_fields.length).toEqual(sampleForm.form_fields.length+1); - expect(originalField).toEqualData(copyField); - }); - - }); - - describe('> Form Field Button >',function(){ - - it('$scope.addButton() should ADD a button to $scope.myform.startPage.buttons', function() { - - var expectedStartPageBtn = { - bgColor:'#ddd', - color:'#ffffff', - text: 'Button' - }; - - //Run controller methods - scope.addButton(); - var actualStartPageBtn = _.cloneDeep(_.last(scope.myform.startPage.buttons)); - delete actualStartPageBtn._id; - - expect(scope.myform.startPage.buttons.length).toEqual(sampleForm.startPage.buttons.length+1); - expect(actualStartPageBtn).toEqualData(expectedStartPageBtn); - }); - - it('$scope.deleteButton() should DELETE a button from $scope.myform.startPage.buttons', function() { - //Run controller methods - scope.deleteButton(scope.myform.startPage.buttons[0]); - - expect(scope.myform.startPage.buttons.length).toEqual(0); - }); - }); - - describe('> Form Field Option >',function(){ - it('$scope.addOption() should ADD a new option to a field.fieldOptions', function() { - var originalOptionLen = scope.myform.form_fields[1].fieldOptions.length; - - //Run controller methods - scope.addOption(1); - - expect(originalOptionLen+1).toEqual(scope.myform.form_fields[1].fieldOptions.length); - expect(scope.myform.form_fields[1].fieldOptions[0].option_title).toEqualData('Option 0'); - expect(scope.myform.form_fields[1].fieldOptions[0].option_value).toEqualData('Option 0'); - }); - - it('$scope.deleteOption() should DELETE remove option from field.fieldOptions', function() { - //Run controller methods - scope.deleteOption(1, scope.myform.form_fields[1].fieldOptions[0]); - - expect(scope.myform.form_fields[0].fieldOptions.length).toEqual(0); - expect(scope.myform.form_fields[0].fieldOptions[0]).not.toBeDefined(); - }); - }); - }); -}()); \ No newline at end of file diff --git a/public/form_modules/forms/tests/unit/directives/entry-page.client.directive.test.js b/public/form_modules/forms/tests/unit/directives/entry-page.client.directive.test.js deleted file mode 100644 index e24cfc48..00000000 --- a/public/form_modules/forms/tests/unit/directives/entry-page.client.directive.test.js +++ /dev/null @@ -1,93 +0,0 @@ -// 'use strict'; - -// (function() { -// // Forms Controller Spec -// describe('entryPage Directive Tests', function() { -// // Initialize global variables -// var scope, -// $templateCache, -// $httpBackend, -// $compile; - -// var sampleStartPage = { -// showStart: true, -// introTitle: 'Welcome to Form', -// introParagraph: 'Sample intro paragraph', -// buttons:[ -// { -// url: 'http://google.com', -// action: '', -// text: 'Google', -// bgColor: '#ffffff', -// color: '#000000', -// }, -// { -// url: 'http://facebook.com', -// action: '', -// text: 'Facebook', -// bgColor: '#0000ff', -// color: '#000000', -// } -// ] -// }; - - -// // The $resource service augments the response object with methods for updating and deleting the resource. -// // If we were to use the standard toEqual matcher, our tests would fail because the test values would not match -// // the responses exactly. To solve the problem, we define a new toEqualData Jasmine matcher. -// // When the toEqualData matcher compares two objects, it takes only object properties into -// // account and ignores methods. -// beforeEach(function() { -// jasmine.addMatchers({ -// toEqualData: function(util, customEqualityTesters) { -// return { -// compare: function(actual, expected) { -// return { -// pass: angular.equals(actual, expected) -// }; -// } -// }; -// } -// }); -// }); - -// // Load the main application module -// beforeEach(module(ApplicationConfiguration.applicationModuleName)); - -// beforeEach(inject(function($rootScope, _$compile_, _$httpBackend_) { -// scope = $rootScope.$new(); -// $compile = _$compile_; - -// // Point global variables to injected services -// $httpBackend = _$httpBackend_; -// })); - - -// it('should be able to render entryPage in html', function() { -// scope.myStartPage = _.cloneDeep(sampleStartPage); -// console.log(scope.myStartPage); -// var element = angular.element(''); -// $compile(element)(scope); -// scope.$digest(); - -// // console.log(element.html()); -// expect(element.html()).not.toEqual('
Start Page
'); -// }); - -// // it('exitStartPage should work for "startPage" type of entryPage', inject(function($rootScope) { -// // scope.myPage = _.cloneDeep(sampleStartPage); -// // var el = angular.element(''); -// // $compile(el)(scope); -// // scope.$digest(); - -// // $httpBackend.whenGET(/.+\.html$/).respond(''); -// // $httpBackend.whenGET('/users/me/').respond(''); - -// // scope = el.isolateScope() || el.scope(); - -// // scope.exitStartPage(); -// // // expect(scope.myStartPage.showStart).toBe(false); -// // expect(el.html()).not.toEqual('
Start Page
'); -// // })); -// }); -// }()); \ No newline at end of file diff --git a/public/form_modules/forms/tests/unit/directives/field-icon.client.directive.test.js b/public/form_modules/forms/tests/unit/directives/field-icon.client.directive.test.js deleted file mode 100644 index 32784832..00000000 --- a/public/form_modules/forms/tests/unit/directives/field-icon.client.directive.test.js +++ /dev/null @@ -1,54 +0,0 @@ -'use strict'; - -(function() { - // Forms Controller Spec - describe('FieldIcon Directive Tests', function() { - // Initialize global variables - var scope, - FormFields, - faClasses = { - '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' - }; - - // Load the main application module - beforeEach(module(ApplicationConfiguration.applicationModuleName)); - - beforeEach(inject(function ($rootScope, _FormFields_) { - scope = $rootScope.$new(); - FormFields = _FormFields_; - })); - - it('should be able render all field-icon types', inject(function($compile) { - var currType, currClass; - - for(var i=0; i')(scope); - scope.$digest(); - - expect(currClass).toBeDefined(); - - expect(element.find('i')).not.toBe(null); - expect(element.find('i').hasClass(currClass)).toBe(true); - } - - })); - }); -}()); \ No newline at end of file diff --git a/public/form_modules/forms/tests/unit/directives/field.client.directive.test.js b/public/form_modules/forms/tests/unit/directives/field.client.directive.test.js deleted file mode 100644 index b5527d62..00000000 --- a/public/form_modules/forms/tests/unit/directives/field.client.directive.test.js +++ /dev/null @@ -1,97 +0,0 @@ -'use strict'; - -(function() { - // Forms Controller Spec - describe('Field Directive Tests', function() { - // Initialize global variables - var scope, - FormFields, - $templateCache, - $httpBackend, - $compile; - - var sampleUser = { - firstName: 'Full', - lastName: 'Name', - email: 'test@test.com', - username: 'test@test.com', - password: 'password', - provider: 'local', - roles: ['user'], - _id: 'ed873933b1f1dea0ce12fab9', - }; - - var sampleFields = [ - {fieldType:'textfield', title:'First Name', fieldValue: 'AoeuName', deletePreserved: false, required: true, disabled: false}, - {fieldType:'email', title:'Email', fieldValue: 'aoeu@aoeu.com', deletePreserved: false, required: true, disabled: false}, - {fieldType:'yes_no', title:'Do you Play Hockey?', fieldValue: 'true', deletePreserved: false, required: true, disabled: false}, - {fieldType:'url', title:'Github Account', fieldValue: 'http://github.com/aoeu', deletePreserved: false, required: true, disabled: false}, - {fieldType:'textarea', title:'Bio', fieldValue: 'This is my bio.', deletePreserved: false, required: true, disabled: false}, - {fieldType:'number', title:'Phone #', fieldValue: 5325325325, deletePreserved: false, required: true, disabled: false}, - {fieldType:'legal', title:'You agree to terms and conditions', description:'By selecting \'I agree\' you are agreeing under Canadian law that you have read and accept terms and conditions outlayed below', fieldValue: '', deletePreserved: false, required: true, disabled: false}, - {fieldType:'dropdown', title:'Your Sex', fieldValue: '', fieldOptions:[ { 'option_id': 0, 'option_title': 'M', 'option_value': 'male' }, { 'option_id': 1, 'option_title': 'F', 'option_value': 'female' }], deletePreserved: false, required: true, disabled: false}, - {fieldType:'radio', title:'Your Sexual Orientation', fieldValue: '', fieldOptions:[ { 'option_id': 0, 'option_title': 'Heterosexual', 'option_value': 'hetero' }, { 'option_id': 1, 'option_title': 'Homosexual', 'option_value': 'homo' }, { 'option_id': 2, 'option_title': 'Bisexual', 'option_value': 'bi' }, { 'option_id': 3, 'option_title': 'Asexual', 'option_value': 'asex' }], deletePreserved: false, required: true, disabled: false}, - {fieldType:'rating', title:'Your Current Happiness', fieldValue: '0', deletePreserved: false, required: true, disabled: false}, - ]; - - - // The $resource service augments the response object with methods for updating and deleting the resource. - // If we were to use the standard toEqual matcher, our tests would fail because the test values would not match - // the responses exactly. To solve the problem, we define a new toEqualData Jasmine matcher. - // When the toEqualData matcher compares two objects, it takes only object properties into - // account and ignores methods. - beforeEach(function() { - jasmine.addMatchers({ - toEqualData: function(util, customEqualityTesters) { - return { - compare: function(actual, expected) { - return { - pass: angular.equals(actual, expected) - }; - } - }; - } - }); - }); - - beforeEach(module(function ($sceProvider) { - $sceProvider.enabled(false); - })); - - // Load the main application module - beforeEach(module(ApplicationConfiguration.applicationModuleName)); - beforeEach(module('stateMock')); - beforeEach(module('module-templates')); - - beforeEach(module('ngSanitize', 'ui.select')); - - beforeEach(inject(function($rootScope, _FormFields_, _$compile_) { - scope = $rootScope.$new(); - FormFields = _FormFields_; - - $compile = _$compile_; - })); - - it('should be able to render all field types in html', inject(function($rootScope) { - scope.fields = sampleFields; - - for(var i=0; i'); - $compile(element)(scope); - scope.$digest(); - - console.log('Actual: '); - console.log(element.html()); - - console.log('\nExpected: '); - - console.log('
'+field.title+'
'); - } - })); - }); -}()); diff --git a/public/form_modules/forms/tests/unit/directives/on-finish-render.client.directive.test.js b/public/form_modules/forms/tests/unit/directives/on-finish-render.client.directive.test.js deleted file mode 100644 index 274f4acb..00000000 --- a/public/form_modules/forms/tests/unit/directives/on-finish-render.client.directive.test.js +++ /dev/null @@ -1,46 +0,0 @@ -'use strict'; - -(function() { - // Forms Controller Spec - describe('onFinishRender Directive Tests', function() { - // Initialize global variables - var scope, - FormFields; - - // Load the main application module - beforeEach(module(ApplicationConfiguration.applicationModuleName)); - - beforeEach(inject(function ($rootScope, _FormFields_) { - scope = $rootScope.$new(); - FormFields = _FormFields_; - spyOn($rootScope, '$broadcast'); - - })); - - it('should emit Custom "Finished" and "Started" events on ng-repeat', inject(function($compile, $rootScope) { - - scope.myfields = FormFields.types; - - var e = $compile('
{{item.name}}
')(scope); - scope.$digest(); - - //run code to test - expect($rootScope.$broadcast).toHaveBeenCalledWith('editFormFields Started'); - expect(scope.$broadcast).toHaveBeenCalledWith('editFormFields Finished'); - })); - - it('should emit "ngRepeat Finished" and "ngRepeat Started" events on ng-repeat when attr is not set to string', inject(function($compile, $rootScope) { - - // console.log(FormFields.types); - scope.myfields = FormFields.types; - - var e = $compile('
{{item.name}}
')(scope); - scope.$digest(); - - //run code to test - expect($rootScope.$broadcast).toHaveBeenCalledWith('ngRepeat Started'); - expect(scope.$broadcast).toHaveBeenCalledWith('ngRepeat Finished'); - })); - - }); -}()); \ No newline at end of file diff --git a/public/form_modules/forms/tests/unit/directives/submit-form.client.directive.test.js b/public/form_modules/forms/tests/unit/directives/submit-form.client.directive.test.js deleted file mode 100644 index db46b05c..00000000 --- a/public/form_modules/forms/tests/unit/directives/submit-form.client.directive.test.js +++ /dev/null @@ -1,203 +0,0 @@ -'use strict'; - -(function() { - // Forms Controller Spec - describe('SubmitForm Directive-Controller Tests', function() { - // Initialize global variables - var scope, controller, $httpBackend; - - var sampleUser = { - firstName: 'Full', - lastName: 'Name', - email: 'test@test.com', - username: 'test@test.com', - password: 'password', - provider: 'local', - roles: ['user'], - _id: 'ed873933b1f1dea0ce12fab9' - }; - - var pdfObj = { - fieldname:'file', - originalname:'test.pdf', - name:'1440112660375.pdf', - encoding:'7bit', - mimetype:'application/pdf', - path:'uploads/tmp/test@test.com/1440112660375.pdf', - extension:'pdf', - size:56223, - truncated:false, - buffer:null - }; - - var sampleForm = { - title: 'Form Title', - admin: 'ed873933b1f1dea0ce12fab9', - language: 'english', - form_fields: [ - {fieldType:'textfield', title:'First Name', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed873933b0ce121f1deafab9'}, - {fieldType:'checkbox', title:'nascar', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed83b0ce121f17393deafab9'}, - {fieldType:'checkbox', title:'hockey', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed8317393deab0ce121ffab9'} ], - visible_form_fields: [ - {fieldType:'textfield', title:'First Name', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed873933b0ce121f1deafab9'}, - {fieldType:'checkbox', title:'nascar', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed83b0ce121f17393deafab9'}, - {fieldType:'checkbox', title:'hockey', fieldOptions: [], fieldValue: '', required: true, disabled: false, deletePreserved: false, _id: 'ed8317393deab0ce121ffab9'} ], - pdf: {}, - pdfFieldMap: {}, - startPage: { - showStart: false - }, - hideFooter: false, - isGenerated: false, - isLive: false, - autofillPDFs: false, - _id: '525a8422f6d0f87f0e407a33' - }; - - var sampleSubmission = { - form_fields: [ - {fieldType:'textfield', title:'First Name', fieldValue: 'John Smith', deletePreserved: false, _id: 'ed873933b0ce121f1deafab9'}, - {fieldType:'yes_no', title:'Do you like nascar', fieldValue: true, deletePreserved: false, _id: 'ed83b0ce121f17393deafab9'}, - {fieldType:'yes_no', title:'Do you like hockey', fieldValue: false, deletePreserved: false, _id: 'ed8317393deab0ce121ffab9'} - ], - admin: sampleUser, - form: sampleForm, - timeElapsed: 17.55 - }; - - var sampleSubmissions = [{ - form_fields: [ - {fieldType:'textfield', title:'First Name', fieldValue: 'The Terminator', deletePreserved: false}, - {fieldType:'yes_no', title:'Do you like nascar', fieldValue: 'true', deletePreserved: false}, - {fieldType:'yes_no', title:'Do you like hockey', fieldValue: 'false', deletePreserved: false} - ], - admin: sampleUser, - form: sampleForm, - timeElapsed: 10.33 - }, - { - form_fields: [ - {fieldType:'textfield', title:'First Name', fieldValue: 'John Smith', deletePreserved: false}, - {fieldType:'yes_no', title:'Do you like nascar', fieldValue: 'true', deletePreserved: false}, - {fieldType:'yes_no', title:'Do you like hockey', fieldValue: 'true', deletePreserved: false} - ], - admin: sampleUser, - form: sampleForm, - timeElapsed: 2.33 - }, - { - form_fields: [ - {fieldType:'textfield', title:'First Name', fieldValue: 'Jane Doe', deletePreserved: false}, - {fieldType:'yes_no', title:'Do you like nascar', fieldValue: 'false', deletePreserved: false}, - {fieldType:'yes_no', title:'Do you like hockey', fieldValue: 'false', deletePreserved: false} - ], - admin: sampleUser, - form: sampleForm, - timeElapsed: 11.11 - }]; - - // The $resource service augments the response object with methods for updating and deleting the resource. - // If we were to use the standard toEqual matcher, our tests would fail because the test values would not match - // the responses exactly. To solve the problem, we define a new toEqualData Jasmine matcher. - // When the toEqualData matcher compares two objects, it takes only object properties into - // account and ignores methods. - beforeEach(function() { - jasmine.addMatchers({ - toEqualData: function(util, customEqualityTesters) { - return { - compare: function(actual, expected) { - return { - pass: angular.equals(actual, expected) - }; - } - }; - } - }); - }); - - // Load the main application module - beforeEach(module(ApplicationConfiguration.applicationModuleName)); - beforeEach(module('module-templates')); - beforeEach(module('stateMock')); - - beforeEach(inject(function($compile, $controller, $rootScope, _$httpBackend_) { - - // Point global variables to injected services - $httpBackend = _$httpBackend_; - $httpBackend.whenGET('/users/me/').respond(''); - - //Instantiate directive. - var tmp_scope = $rootScope.$new(); - tmp_scope.myform = sampleForm; - - //gotacha: Controller and link functions will execute. - var el = angular.element(''); - $compile(el)(tmp_scope); - tmp_scope.$digest(); - $rootScope.$digest(); - - //Grab controller instance - controller = el.controller(); - - //Grab scope. Depends on type of scope. - //See angular.element documentation. - scope = el.isolateScope() || el.scope(); - - console.log(scope); - })); - - var Validator = (function() { - return { - hasMinimumFields: function(entry) { - return !_.isEmpty(entry._id) && !_.isEmpty(entry.title); - }, - isNewForm: function(entry) { - return this.hasMinimumFields(entry); - } - }; - })(); - - - it('$scope.submitForm() should submit valid form', function(){ - //Initialize variables - scope.myform.form_fields = sampleSubmissions[0].form_fields; - - var expectedForm = _.cloneDeep(sampleForm); - expectedForm.form_fields = sampleSubmissions[0].form_fields; - delete expectedForm.visible_form_fields; - - var data = function(data) { - var form = angular.fromJson(data); - var compareForm = _.cloneDeep(form); - delete compareForm.timeElapsed; - delete compareForm.percentageComplete; - - return Validator.isNewForm(form) && _.isEqual(compareForm, expectedForm); - }; - - //Set expected HTTP requests - $httpBackend.expect('POST',/^(\/forms\/)([0-9a-fA-F]{24})$/, data).respond(200); - - //Run Controller Logic to Test - scope.submitForm(); - - $httpBackend.flush(); - - setTimeout(function(){ - expect(scope.myform.submitted).toBe(true); - expect(scope.error).toEqual(''); - }, 25); - }); - - it('$scope.reloadForm() should reset and reload form', function(){ - scope.submitForm(); - scope.reloadForm(); - - expect(scope.myform.submitted).toBe(false); - for(var i=0; i 0){ - var expectedState = this.expectedTransitions.shift(); - if(expectedState !== stateName){ - throw Error('Expected transition to state: ' + expectedState + ' but transitioned to ' + stateName ); - } - }else{ - throw Error('No more transitions were expected! Tried to transition to '+ stateName ); - } - console.log('Mock transition to: ' + stateName); - var deferred = $q.defer(); - var promise = deferred.promise; - deferred.resolve(); - return promise; - }; - - this.go = this.transitionTo; - this.expectTransitionTo = function(stateName){ - this.expectedTransitions.push(stateName); - }; - - this.ensureAllTransitionsHappened = function(){ - if(this.expectedTransitions.length > 0){ - throw Error('Not all transitions happened!'); - } - }; -}); \ No newline at end of file From cf4fc6c906ab8432e6a49585ff9622a3036c0745 Mon Sep 17 00:00:00 2001 From: David Baldwynn Date: Mon, 4 Jul 2016 14:13:15 -0700 Subject: [PATCH 4/5] added translation to signup header --- design/screenshots/collapsed.png | Bin 0 -> 160388 bytes public/modules/users/config/i18n/english.js | 3 +++ .../authentication/signup.client.view.html | 4 ++-- 3 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 design/screenshots/collapsed.png diff --git a/design/screenshots/collapsed.png b/design/screenshots/collapsed.png new file mode 100644 index 0000000000000000000000000000000000000000..88d28fa77260ac6a350dc4eaf718a0f46640b33f GIT binary patch literal 160388 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 e&O(| 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
--> -

Signup with your email

+

{{ 'SIGNUP_WITH_EMAIL' | translate }}