whitef0x0/tellform

View on GitHub
public/form_modules/forms/base/directives/submit-form.client.directive.js

Summary

Maintainability
D
3 days
Test Coverage
'use strict';

//FIXME: Should find an appropriate place for this
//Setting up jsep
jsep.addBinaryOp('contains', 10);
jsep.addBinaryOp('!contains', 10);
jsep.addBinaryOp('begins', 10);
jsep.addBinaryOp('!begins', 10);
jsep.addBinaryOp('ends', 10);
jsep.addBinaryOp('!ends', 10);

angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCounter', '$filter', '$rootScope', 'SendVisitorData', '$translate', '$timeout',
    function ($http, TimeCounter, $filter, $rootScope, SendVisitorData, $translate, $timeout) {
        return {
            templateUrl: 'form_modules/forms/base/views/directiveViews/form/submit-form.client.view.html',
            restrict: 'E',
            scope: {
                myform:'=',
                ispreview: '='
            },
            controller: function($document, $window, $scope){
                var NOSCROLL = false;
                var FORM_ACTION_ID = 'submit_field';
                $scope.forms = {};
                
                //Don't start timer if we are looking at a design preview
                if($scope.ispreview){
                    TimeCounter.restartClock();
                }

                var form_fields_count = $scope.myform.visible_form_fields.filter(function(field){
                    return field.fieldType !== 'statement';
                }).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);

                    //Reset Timer
                    TimeCounter.restartClock();
                };

                /*
                ** Field Controls
                */
                var evaluateLogicJump = function(field){
                    var logicJump = field.logicJump;

                    if(logicJump.enabled){
                        if (logicJump.expressionString && logicJump.valueB && field.fieldValue) {
                            var parse_tree = jsep(logicJump.expressionString);
                            var left, right;

                            if(parse_tree.left.name === 'field'){
                                left = field.fieldValue;
                                right = logicJump.valueB;
                            } else {
                                left = logicJump.valueB;
                                right = field.fieldValue;
                            }

                            if(field.fieldType === 'number' || field.fieldType === 'scale' || field.fieldType === 'rating'){
                                switch(parse_tree.operator) {
                                    case '==':
                                        return (parseInt(left) === parseInt(right));
                                    case '!==':
                                        return (parseInt(left) !== parseInt(right));
                                    case '>':
                                        return (parseInt(left) > parseInt(right));
                                    case '>=':
                                        return (parseInt(left) > parseInt(right));
                                    case '<':
                                        return (parseInt(left) < parseInt(right));
                                    case '<=':
                                        return (parseInt(left) <= parseInt(right));
                                    default:
                                        return false;
                                }
                            } else {
                                switch(parse_tree.operator) {
                                    case '==':
                                        return (left === right);
                                    case '!==':
                                        return (left !== right);
                                    case 'contains':
                                        return (left.indexOf(right) > -1);
                                    case '!contains':
                                      /* jshint -W018 */
                                        return !(left.indexOf(right) > -1);
                                    case 'begins':
                                        return left.startsWith(right);
                                    case '!begins':
                                        return !left.startsWith(right);
                                    case 'ends':
                                        return left.endsWith(right);
                                    case '!ends':
                                        return left.endsWith(right);
                                    default:
                                        return false;
                                }
                            }
                        }
                    }
                };

                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 === FORM_ACTION_ID) {
                        return $scope.myform.form_fields.length - 1;
                    }
                    return $scope.selected.index;
                };

                $scope.isActiveField = function(field){
                    if($scope.selected._id === field._id) {
                        return true
                    }
                    return false;
                };

                $scope.setActiveField = $rootScope.setActiveField = function(field_id, field_index, animateScroll) {
                    if($scope.selected === null || (!field_id && field_index === null) )  {
                        return;
                    }
                    
                    if(!field_id){
                        field_id = $scope.myform.visible_form_fields[field_index]._id;
                    } else if(field_index === null){
                        field_index = $scope.myform.visible_form_fields.length

                        for(var i=0; i < $scope.myform.visible_form_fields.length; i++){
                            var currField = $scope.myform.visible_form_fields[i];
                            if(currField['_id'] == field_id){
                                field_index = i;
                                break;
                            }
                        }
                    }

                    if($scope.selected._id === field_id){
                        return;
                    }

                    $scope.selected._id = field_id;
                    $scope.selected.index = field_index;


                    var nb_valid = $filter('formValidity')($scope.myform);
                    $scope.translateAdvancementData = {
                        done: nb_valid,
                        total: form_fields_count,
                        answers_not_completed: form_fields_count - nb_valid
                    };

                    if(animateScroll){
                        NOSCROLL=true;
                        setTimeout(function() {
                            $document.scrollToElement(angular.element('.activeField'), -10, 200).then(function() {
                                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();
                                    }
                                });
                            });
                        });
                    }
                };

                $scope.$watch('selected.index', function(oldValue, newValue){
                    if(oldValue !== newValue && newValue < $scope.myform.form_fields.length){
                        //Only send analytics data if form has not been submitted
                        if(!$scope.myform.submitted){
                            console.log('SendVisitorData.send()');
                            SendVisitorData.send($scope.myform, newValue, TimeCounter.getTimeElapsed());
                        }
                    }
                });

                //Fire event when window is scrolled
                $window.onscroll = function(){
                    if(!NOSCROLL){

                        var scrollTop = $(window).scrollTop();
                        var elemBox = document.getElementsByClassName('activeField')[0].getBoundingClientRect();
                        var fieldTop = elemBox.top;
                        var fieldBottom = elemBox.bottom;

                        var field_id, field_index;
                        var elemHeight = $('.activeField').height();

                        var submitSectionHeight = $('.form-actions').height();
                        var maxScrollTop = $(document).height() - $(window).height();
                        var fieldWrapperHeight = $('form_fields').height();

                        var selector = 'form > .field-directive:nth-of-type(' + String($scope.myform.visible_form_fields.length - 1)+ ')'
                        var fieldDirectiveHeight = $(selector).height()
                        var scrollPosition = maxScrollTop - submitSectionHeight - fieldDirectiveHeight*1.2;

                        var fractionToJump = 0.9;

                        //Focus on field above submit form button
                        if($scope.selected.index === $scope.myform.visible_form_fields.length){
                            if(scrollTop < scrollPosition){
                                field_index = $scope.selected.index-1;
                                $scope.setActiveField(null, field_index, false);
                            }
                        }

                        //Focus on submit form button
                        else if($scope.selected.index === $scope.myform.visible_form_fields.length-1 && scrollTop > scrollPosition){
                            field_index = $scope.selected.index+1;
                            $scope.setActiveField(FORM_ACTION_ID, field_index, false);
                        }
                        
                        //If we scrolled bellow the current field, move to next field
                        else if(fieldBottom < elemHeight * fractionToJump && $scope.selected.index < $scope.myform.visible_form_fields.length-1 ){
                            field_index = $scope.selected.index+1;
                            $scope.setActiveField(null, field_index, false);
                        } 
                        //If we scrolled above the current field, move to prev field
                        else if ( $scope.selected.index !== 0 && fieldTop > elemHeight * fractionToJump) {
                            field_index = $scope.selected.index-1;
                            $scope.setActiveField(null, field_index, false);
                        }
                    }

                    $scope.$apply();
                };

                $rootScope.nextField = $scope.nextField = function(){
                    if($scope.selected && $scope.selected.index > -1){

                        if($scope.selected._id !== FORM_ACTION_ID){
                            var currField = $scope.myform.visible_form_fields[$scope.selected.index];
                        
                            //Jump to logicJump's destination if it is true
                            if(currField.logicJump && currField.logicJump.jumpTo && evaluateLogicJump(currField)){
                                $scope.setActiveField(currField.logicJump.jumpTo, null, true);
                            } else if($scope.selected.index < $scope.myform.visible_form_fields.length-1){
                                $scope.setActiveField(null, $scope.selected.index+1, true);
                            } else {
                                $scope.setActiveField(FORM_ACTION_ID, null, true);
                            }
                        } else {
                            //If we are at the submit actions page, go to the first field
                            $rootScope.setActiveField(null, 0, true);
                        }
                    } else {
                        //If selected is not defined go to the first field
                        $rootScope.setActiveField(null, 0, true);
                    }
    
                };

                $rootScope.prevField = $scope.prevField = function(){
                    console.log('prevField');
                    console.log($scope.selected);
                    var selected_index = $scope.selected.index - 1;
                    if($scope.selected.index > 0){
                        $scope.setActiveField(null, selected_index, true);
                    }
                };

                $rootScope.goToInvalid = $scope.goToInvalid = function() {
                    var field_id = $('.row.field-directive .ng-invalid.focusOn, .row.field-directive .ng-untouched.focusOn:not(.ng-valid)').first().parents('.row.field-directive').first().attr('data-id');
                    $scope.setActiveField(field_id, null, true);
                };

                /*
                ** Form Display Functions
                */
                $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;
                    }
                };

                var getDeviceData = function(){
                    var md = new MobileDetect(window.navigator.userAgent);
                    var deviceType = 'other';

                    if (md.tablet()){
                        deviceType = 'tablet';
                    } else if (md.mobile()) {
                        deviceType = 'mobile';
                    } else if (!md.is('bot')) {
                        deviceType = 'desktop';
                    }

                    return {
                        type: deviceType,
                        name: window.navigator.platform
                    };
                };

                var getIpAndGeo = function(){
                    //Get Ip Address and GeoLocation Data
                    $.ajaxSetup( { 'async': false } );
                    var geoData = $.getJSON('https://freegeoip.net/json/').responseJSON;
                    $.ajaxSetup( { 'async': true } );

                    if(!geoData || !geoData.ip){
                        geoData = {
                            ip: 'Adblocker'
                        };
                    }

                    return {
                        ipAddr: geoData.ip,
                        geoLocation: {
                            City: geoData.city,
                            Country: geoData.country_name
                        }
                    };
                };

                $rootScope.submitForm = $scope.submitForm = function() {
                    if($scope.forms.myForm.$invalid){
                        $scope.goToInvalid();
                        return;
                    }

                    var _timeElapsed = TimeCounter.stopClock();
                    $scope.loading = true;

                    var form = _.cloneDeep($scope.myform);

                    var deviceData = getDeviceData();
                    form.device = deviceData;

                    var geoData = getIpAndGeo();
                    form.ipAddr = geoData.ipAddr;
                    form.geoLocation = geoData.geoLocation;

                    form.timeElapsed = _timeElapsed;
                    form.percentageComplete = $filter('formValidity')($scope.myform) / $scope.myform.visible_form_fields.length * 100;
                    delete form.endPage
                    delete form.isLive
                    delete form.provider
                    delete form.startPage
                    delete form.visible_form_fields;
                    delete form.analytics;
                    delete form.design;
                    delete form.submissions;
                    delete form.submitted;
                    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;
                        }
                        
                        //Get rid of unnessecary attributes for each form field
                        delete form.form_fields[i].submissionId;
                                    delete form.form_fields[i].disabled;
                                    delete form.form_fields[i].ratingOptions;
                                       delete form.form_fields[i].fieldOptions;
                                    delete form.form_fields[i].logicJump;
                                    delete form.form_fields[i].description;
                                    delete form.form_fields[i].validFieldTypes;
                                    delete form.form_fields[i].fieldType;    
                     
                    }

                    setTimeout(function () {
                        $scope.submitPromise = $http.post('/forms/' + $scope.myform._id, form)
                            .success(function (data, status) {
                                $scope.myform.submitted = true;
                                $scope.loading = false;
                                SendVisitorData.send(form, getActiveField(), _timeElapsed);
                            })
                            .error(function (error) {
                                $scope.loading = false;
                                console.error(error);
                                $scope.error = error.message;
                            });
                    }, 500);
                };

                //Reload our form
                $scope.reloadForm();
            }
        };
    }
]);