From 6e93db3fd1a7468de48de9f62b3da5f608ccc9e5 Mon Sep 17 00:00:00 2001 From: David Baldwynn Date: Mon, 20 Jun 2016 15:35:41 -0700 Subject: [PATCH 1/7] got subdomains to work in production --- config/env/production.js | 3 +- config/express.js | 3 +- gruntfile.js | 13 +- public/dist/form-application.js | 1219 +++++++++++++++++ public/dist/form-application.min.js | 3 + .../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 - 19 files changed, 1234 insertions(+), 1865 deletions(-) create mode 100644 public/dist/form-application.js create mode 100644 public/dist/form-application.min.js delete mode 100644 public/form_modules/forms/tests/unit/controllers/admin-form.client.controller.test.js delete mode 100644 public/form_modules/forms/tests/unit/controllers/list-forms.client.controller.test.js delete mode 100644 public/form_modules/forms/tests/unit/controllers/submit-form.client.controller.test.js delete mode 100644 public/form_modules/forms/tests/unit/directives/configure-form.client.directive.test.js delete mode 100644 public/form_modules/forms/tests/unit/directives/edit-form-submissions.client.directive.test.js delete mode 100644 public/form_modules/forms/tests/unit/directives/edit-form.client.directive.test.js delete mode 100644 public/form_modules/forms/tests/unit/directives/entry-page.client.directive.test.js delete mode 100644 public/form_modules/forms/tests/unit/directives/field-icon.client.directive.test.js delete mode 100644 public/form_modules/forms/tests/unit/directives/field.client.directive.test.js delete mode 100644 public/form_modules/forms/tests/unit/directives/on-finish-render.client.directive.test.js delete mode 100644 public/form_modules/forms/tests/unit/directives/submit-form.client.directive.test.js delete mode 100644 public/form_modules/forms/tests/unit/services/current-form.client.service.test.js delete mode 100644 public/form_modules/forms/tests/unit/services/time-counter.client.service.test.js delete mode 100644 public/form_modules/forms/tests/unit/stateMock.js diff --git a/config/env/production.js b/config/env/production.js index c60edf43..170830a1 100755 --- a/config/env/production.js +++ b/config/env/production.js @@ -24,7 +24,8 @@ module.exports = { }, assets: { css: 'public/dist/application.min.css', - js: 'public/dist/application.min.js' + js: 'public/dist/application.min.js', + form_js: 'public/dist/form-application.min.js' }, facebook: { clientID: process.env.FACEBOOK_ID || 'APP_ID', diff --git a/config/express.js b/config/express.js index e8c534ff..0c0fb742 100755 --- a/config/express.js +++ b/config/express.js @@ -74,7 +74,8 @@ module.exports = function(db) { var subdomains = req.subdomains; var host = req.hostname; - + console.log(subdomains); + console.log(host); // remove www if chosen to ignore if (ignoreWWW) { var wwwi = subdomains.indexOf('www'); diff --git a/gruntfile.js b/gruntfile.js index 711ef147..003f9a0c 100755 --- a/gruntfile.js +++ b/gruntfile.js @@ -12,8 +12,8 @@ module.exports = function(grunt) { serverJS: ['gruntfile.js', 'server.js', 'config/**/*.js', 'app/**/*.js', '!app/tests/'], clientViews: ['public/modules/**/views/**/*.html', '!public/modules/**/demo/**/*.html', '!public/modules/**/dist/**/*.html', '!public/modules/**/node_modules/**/*.html'], - clientJS: ['public/js/*.js', 'public/modules/**/*.js', '!public/modules/**/gruntfile.js', '!public/modules/**/demo/**/*.js', '!public/modules/**/dist/**/*.js', '!public/modules/**/node_modules/**/*.js'], - clientCSS: ['public/modules/**/*.css', '!public/modules/**/demo/**/*.css', '!public/modules/**/dist/**/*.css', '!public/modules/**/node_modules/**/*.css'], + clientJS: ['public/js/*.js', 'public/form_modules/**/*.js', 'public/modules/**/*.js', '!public/modules/**/gruntfile.js', '!public/modules/**/demo/**/*.js', '!public/modules/**/dist/**/*.js', '!public/modules/**/node_modules/**/*.js'], + clientCSS: ['public/modules/**/*.css', 'public/form_modules/**/*.css', '!public/modules/**/demo/**/*.css', '!public/modules/**/dist/**/*.css', '!public/modules/**/node_modules/**/*.css'], serverTests: ['app/tests/**/*.js'], clientTests: ['public/modules/**/tests/*.js', '!public/modules/**/demo/**/*.js', '!public/modules/**/dist/**/*.js', '!public/modules/**/node_modules/**/*.js'] @@ -97,7 +97,8 @@ module.exports = function(grunt) { mangle: false }, files: { - 'public/dist/application.min.js': 'public/dist/application.js' + 'public/dist/application.min.js': 'public/dist/application.js', + 'public/dist/form-application.min.js': 'public/dist/form-application.js' } } }, @@ -134,7 +135,8 @@ module.exports = function(grunt) { ngAnnotate: { production: { files: { - 'public/dist/application.js': '<%= applicationJavaScriptFiles %>' + 'public/dist/application.js': '<%= applicationJavaScriptFiles %>', + 'public/dist/form-application.js': '<%= formApplicationJavaScriptFiles %>' } } }, @@ -177,7 +179,7 @@ module.exports = function(grunt) { unit: { configFile: 'karma.conf.js', singleRun: true - } + } }, protractor: { options: { @@ -286,6 +288,7 @@ module.exports = function(grunt) { var config = require('./config/config'); grunt.config.set('applicationJavaScriptFiles', config.assets.js); + grunt.config.set('formApplicationJavaScriptFiles', config.assets.form_js); grunt.config.set('applicationCSSFiles', config.assets.css); }); diff --git a/public/dist/form-application.js b/public/dist/form-application.js new file mode 100644 index 00000000..fa07d851 --- /dev/null +++ b/public/dist/form-application.js @@ -0,0 +1,1219 @@ +'use strict'; + +// Init the application configuration module for AngularJS application +var ApplicationConfiguration = (function() { + // Init module configuration options + var applicationModuleName = 'NodeForm'; + var applicationModuleVendorDependencies = ['duScroll', 'ui.select', 'cgBusy', 'ngSanitize', 'vButton', 'ngResource', 'NodeForm.templates', 'ui.router', 'ui.bootstrap', 'ui.utils', 'pascalprecht.translate', 'ng.deviceDetector']; + + // Add a new vertical module + var registerModule = function(moduleName, dependencies) { + // Create angular module + angular.module(moduleName, dependencies || []); + + // Add the module to the AngularJS configuration file + angular.module(applicationModuleName).requires.push(moduleName); + }; + + return { + applicationModuleName: applicationModuleName, + applicationModuleVendorDependencies: applicationModuleVendorDependencies, + registerModule: registerModule + }; +})(); + +'use strict'; + +//Start by defining the main module and adding the module dependencies +angular.module(ApplicationConfiguration.applicationModuleName, ApplicationConfiguration.applicationModuleVendorDependencies); + +// Setting HTML5 Location Mode +angular.module(ApplicationConfiguration.applicationModuleName).config(['$locationProvider', + function($locationProvider) { + $locationProvider.hashPrefix('!'); + } +]); + +//Permission Constants +angular.module(ApplicationConfiguration.applicationModuleName).constant('APP_PERMISSIONS', { + viewAdminSettings: 'viewAdminSettings', + editAdminSettings: 'editAdminSettings', + 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'); + +//Then define the init function for starting up the application +angular.element(document).ready(function() { + //Fixing facebook bug with redirect + if (window.location.hash === '#_=_') window.location.hash = '#!'; + + //Then init the app + angular.bootstrap(document, [ApplicationConfiguration.applicationModuleName]); +}); + +angular.module('NodeForm.templates', []).run(['$templateCache', function($templateCache) { + "use strict"; + $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

{{ '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", + "

{{myform.startPage.introTitle}}

{{myform.startPage.introParagraph}}

{{ 'COMPLETING_NEEDED' | translate:translateAdvancementData }}
{{ 'ENTER' | translate }}

{{ 'ADVANCEMENT' | translate:translateAdvancementData }}

{{ 'FORM_SUCCESS' | translate }}

"); + $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",'

{{myform.startPage.introTitle}}

{{myform.startPage.introParagraph}}

{{ \'COMPLETING_NEEDED\' | translate:translateAdvancementData }}
{{ \'FORM_SUCCESS\' | translate }}

'),$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",'

{{ \'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

'),$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 de3ba6a7a312bd32f062856dfcb5e8fff6ce3ff9 Mon Sep 17 00:00:00 2001 From: David Baldwynn Date: Mon, 20 Jun 2016 16:57:21 -0700 Subject: [PATCH 2/7] fixed french translation bug --- public/modules/users/config/i18n/french.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/modules/users/config/i18n/french.js b/public/modules/users/config/i18n/french.js index 2a73f983..e36da5c5 100644 --- a/public/modules/users/config/i18n/french.js +++ b/public/modules/users/config/i18n/french.js @@ -2,7 +2,7 @@ angular.module('users').config(['$translateProvider', function ($translateProvider) { - $translateProvider.translations('en', { + $translateProvider.translations('fr', { ACCESS_DENIED_TEXT: 'Vouz n’êtes pas autorisé à accéder à cette page.', USERNAME_LABEL: 'Nom d’utilisateur', PASSWORD_LABEL: 'Mot de Passe', From 6d65599963be291aee7efcf28506a1780b881f72 Mon Sep 17 00:00:00 2001 From: David Baldwynn Date: Tue, 21 Jun 2016 00:49:21 -0700 Subject: [PATCH 3/7] made jsonip https --- .../users/users.authentication.server.controller.js | 4 ---- .../forms/directives/analytics-service.client.directive.js | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/app/controllers/users/users.authentication.server.controller.js b/app/controllers/users/users.authentication.server.controller.js index e95b3bb4..ce3f8680 100755 --- a/app/controllers/users/users.authentication.server.controller.js +++ b/app/controllers/users/users.authentication.server.controller.js @@ -100,7 +100,6 @@ exports.resendVerificationEmail = function(req, res, next){ */ 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); @@ -142,9 +141,6 @@ exports.signup = function(req, res) { } } }); - } else { - res.status(500).send('Incomplete Data'); - } }; /** diff --git a/public/modules/forms/directives/analytics-service.client.directive.js b/public/modules/forms/directives/analytics-service.client.directive.js index 228de8cd..1466d69a 100644 --- a/public/modules/forms/directives/analytics-service.client.directive.js +++ b/public/modules/forms/directives/analytics-service.client.directive.js @@ -25,7 +25,7 @@ deviceType: '' }; - $http.get('http://jsonip.com/').success(function(response) { + $http.get('https://jsonip.com/').success(function(response) { visitorData.ipAddr = response['ip']+''; }).error(function(error) { console.error('Could not get users\'s ip'); From cf4fc6c906ab8432e6a49585ff9622a3036c0745 Mon Sep 17 00:00:00 2001 From: David Baldwynn Date: Mon, 4 Jul 2016 14:13:15 -0700 Subject: [PATCH 4/7] 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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