changed form updated method to use diffs

This commit is contained in:
David Baldwynn 2016-07-05 18:48:25 -07:00
parent 4aa7cd7d62
commit 34dca69d6e
14 changed files with 290 additions and 277 deletions

View file

@ -12,6 +12,7 @@ var mongoose = require('mongoose'),
fs = require('fs-extra'), fs = require('fs-extra'),
async = require('async'), async = require('async'),
path = require('path'), path = require('path'),
diff = require('deep-diff'),
_ = require('lodash'); _ = require('lodash');
/** /**
@ -69,74 +70,6 @@ exports.uploadPDF = function(req, res, next) {
} }
}; };
/**
* Upload PDF
*/
/*
exports.uploadSubmissionFile = function(req, res, next) {
console.log('inside uploadPDF');
// console.log('\n\nProperty Descriptor\n-----------');
// console.log(Object.getOwnPropertyDescriptor(req.files.file, 'path'));
console.log(req.files);
if(req.files){
var file, _user, _path;
for(var i=0; i<req.files.length; i++){
file = req.files[i];
_user = req.user;
_path = file.path;
if (file.size === 0) {
return next(new Error('File uploaded is EMPTY'));
}else if(file.size > 100000000){
return next(new Error('File uploaded exceeds MAX SIZE of 100MB'));
}else {
fs.exists(_path, function(exists) {
//If file exists move to user's form directory
if(exists) {
var newDestination = config.tmpUploadPath+_user.username;
var stat = null;
try {
stat = fs.statSync(newDestination);
} catch (err) {
fs.mkdirSync(newDestination);
}
if (stat && !stat.isDirectory()) {
console.log('Directory cannot be created');
return next(new Error('Directory cannot be created because an inode of a different type exists at "' + newDestination + '"'));
}
console.log(path.join(newDestination, pdfFile.filename));
fs.move(pdfFile.path, path.join(newDestination, pdfFile.filename), function (err) {
if (err) {
return next(new Error(err.message));
}
pdfFile.path = path.join(newDestination, pdfFile.filename);
console.log(pdfFile.filename + ' uploaded to ' + pdfFile.path);
res.json(pdfFile);
});
} else {
return next(new Error('Did NOT get your file!'));
}
});
}
}
}else {
return next(new Error('Uploaded files were NOT detected'));
}
};
*/
/** /**
* Delete a forms submissions * Delete a forms submissions
*/ */
@ -294,7 +227,7 @@ exports.read = function(req, res) {
}); });
} }
return res.json(newForm); return res.json(newForm);
}; };
/** /**
@ -305,7 +238,16 @@ exports.update = function(req, res) {
delete req.body.form.__v; delete req.body.form.__v;
delete req.body.form._id; delete req.body.form._id;
//Unless we have 'admin' priviledges, updating form admin is disabled var formChanges = req.body.changes;
formChanges.forEach(function (change) {
diff.applyChange(form, true, change);
});
console.log(form.form_fields);
/*
//Unless we have 'admin' privileges, updating form admin is disabled
if(req.user.roles.indexOf('admin') === -1) delete req.body.form.admin; if(req.user.roles.indexOf('admin') === -1) delete req.body.form.admin;
//Do this so we can create duplicate fields //Do this so we can create duplicate fields
@ -316,8 +258,8 @@ exports.update = function(req, res) {
delete field._id; delete field._id;
} }
} }
form = _.extend(form, req.body.form); form = _.extend(form, req.body.form);
*/
form.save(function(err, form) { form.save(function(err, form) {
if (err) { if (err) {

View file

@ -37,7 +37,8 @@
"angular-ui-select": "whitef0x0/ui-select#compiled", "angular-ui-select": "whitef0x0/ui-select#compiled",
"angular-translate": "~2.11.0", "angular-translate": "~2.11.0",
"ng-device-detector": "^3.0.1", "ng-device-detector": "^3.0.1",
"ng-translate": "*" "ng-translate": "*",
"deep-diff": "^0.3.4"
}, },
"resolutions": { "resolutions": {
"angular-bootstrap": "^0.14.0", "angular-bootstrap": "^0.14.0",

View file

@ -34,6 +34,7 @@
"connect-mongo": "~0.8.2", "connect-mongo": "~0.8.2",
"consolidate": "~0.13.1", "consolidate": "~0.13.1",
"cookie-parser": "~1.4.0", "cookie-parser": "~1.4.0",
"deep-diff": "^0.3.4",
"dotenv": "^2.0.0", "dotenv": "^2.0.0",
"email-verification": "~0.4.1", "email-verification": "~0.4.1",
"envfile": "^2.0.1", "envfile": "^2.0.1",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -2,107 +2,108 @@
// coffeescript's for in loop // coffeescript's for in loop
var __indexOf = [].indexOf || function(item) { var __indexOf = [].indexOf || function(item) {
for (var i = 0, l = this.length; i < l; i++) { for (var i = 0, l = this.length; i < l; i++) {
if (i in this && this[i] === item) return i; if (i in this && this[i] === item) return i;
} }
return -1; return -1;
}; };
angular.module('view-form').directive('fieldDirective', ['$http', '$compile', '$rootScope', '$templateCache', 'supportedFields', angular.module('forms').directive('fieldDirective', ['$http', '$compile', '$rootScope', '$templateCache', 'supportedFields',
function($http, $compile, $rootScope, $templateCache, supportedFields) { function($http, $compile, $rootScope, $templateCache, supportedFields) {
var getTemplateUrl = function(fieldType) { var getTemplateUrl = function(fieldType) {
var type = fieldType; var type = fieldType;
var supported_fields = [ var supported_fields = [
'textfield', 'textfield',
'textarea', 'textarea',
'date', 'date',
'dropdown', 'dropdown',
'hidden', 'hidden',
'password', 'password',
'radio', 'radio',
'legal', 'legal',
'statement', 'statement',
'rating', 'rating',
'yes_no', 'yes_no',
'number', 'number',
'natural' 'natural'
]; ];
var templateUrl = 'modules/forms/base/views/directiveViews/field/'; var templateUrl = 'modules/forms/base/views/directiveViews/field/';
if (__indexOf.call(supportedFields, type) >= 0) { if (__indexOf.call(supportedFields, type) >= 0) {
templateUrl = templateUrl+type+'.html'; templateUrl = templateUrl+type+'.html';
}
return $templateCache.get(templateUrl);
};
return {
template: '<div>{{field.title}}</div>',
restrict: 'E',
scope: {
field: '=',
required: '&',
design: '=',
index: '=',
forms: '='
},
link: function(scope, element) {
$rootScope.chooseDefaultOption = scope.chooseDefaultOption = function(type) {
if(type === 'yes_no'){
scope.field.fieldValue = 'true';
}else if(type === 'rating'){
scope.field.fieldValue = 0;
}else if(scope.field.fieldType === 'radio'){
console.log(scope.field);
scope.field.fieldValue = scope.field.fieldOptions[0].option_value;
console.log(scope.field.fieldValue);
}else if(type === 'legal'){
scope.field.fieldValue = 'true';
$rootScope.nextField();
}
};
scope.setActiveField = $rootScope.setActiveField;
//Set format only if field is a date
if(scope.field.fieldType === 'date'){
scope.dateOptions = {
changeYear: true,
changeMonth: true,
altFormat: 'mm/dd/yyyy',
yearRange: '1900:-0',
defaultDate: 0
};
}
var fieldType = scope.field.fieldType;
if(scope.field.fieldType === 'number' || scope.field.fieldType === 'textfield' || scope.field.fieldType === 'email' || scope.field.fieldType === 'link'){
switch(scope.field.fieldType){
case 'textfield':
scope.field.input_type = 'text';
break;
case 'email':
scope.field.input_type = 'email';
scope.field.placeholder = 'joesmith@example.com';
break;
case 'number':
scope.field.input_type = 'text';
scope.field.validateRegex = /^-?\d+$/;
break;
default:
scope.field.input_type = 'url';
scope.field.placeholder = 'http://example.com';
break;
}
fieldType = 'textfield';
} }
var template = getTemplateUrl(fieldType); return $templateCache.get(templateUrl);
element.html(template).show(); };
var output = $compile(element.contents())(scope);
} return {
}; template: '<div>{{field.title}}</div>',
}]); restrict: 'E',
scope: {
field: '=',
required: '&',
design: '=',
index: '=',
forms: '='
},
link: function(scope, element) {
$rootScope.chooseDefaultOption = scope.chooseDefaultOption = function(type) {
if(type === 'yes_no'){
scope.field.fieldValue = 'true';
}else if(type === 'rating'){
scope.field.fieldValue = 0;
}else if(scope.field.fieldType === 'radio'){
console.log(scope.field);
scope.field.fieldValue = scope.field.fieldOptions[0].option_value;
console.log(scope.field.fieldValue);
}else if(type === 'legal'){
scope.field.fieldValue = 'true';
$rootScope.nextField();
}
};
scope.setActiveField = $rootScope.setActiveField;
//Set format only if field is a date
if(scope.field.fieldType === 'date'){
scope.dateOptions = {
changeYear: true,
changeMonth: true,
altFormat: 'mm/dd/yyyy',
yearRange: '1900:-0',
defaultDate: 0
};
}
var fieldType = scope.field.fieldType;
if(scope.field.fieldType === 'number' || scope.field.fieldType === 'textfield' || scope.field.fieldType === 'email' || scope.field.fieldType === 'link'){
switch(scope.field.fieldType){
case 'textfield':
scope.input_type = 'text';
break;
case 'email':
scope.input_type = 'email';
scope.placeholder = 'joesmith@example.com';
break;
case 'number':
scope.input_type = 'text';
scope.validateRegex = /^-?\d+$/;
break;
default:
scope.input_type = 'url';
scope.placeholder = 'http://example.com';
break;
}
fieldType = 'textfield';
}
var template = getTemplateUrl(fieldType);
element.html(template).show();
var output = $compile(element.contents())(scope);
}
};
}]);

View file

@ -21,9 +21,9 @@
<div class="col-xs-12 field-input"> <div class="col-xs-12 field-input">
<input ng-style="{'color': design.colors.answerColor, 'border-color': design.colors.answerColor}" <input ng-style="{'color': design.colors.answerColor, 'border-color': design.colors.answerColor}"
name="{{field.fieldType}}{{index}}" name="{{field.fieldType}}{{index}}"
type="{{field.input_type}}" type="{{input_type}}"
ng-pattern="field.validateRegex" ng-pattern="validateRegex"
placeholder="{{field.placeholder}}" placeholder="{{placeholder}}"
ng-class="{ 'no-border': !!field.fieldValue }" ng-class="{ 'no-border': !!field.fieldValue }"
class="focusOn text-field-input" class="focusOn text-field-input"
ng-model="field.fieldValue" ng-model="field.fieldValue"

View file

@ -92,7 +92,7 @@ angular.module('forms').controller('AdminFormController', ['$rootScope', '$scope
}; };
// Update existing Form // Update existing Form
$scope.update = $rootScope.update = function(updateImmediately, cb){ $scope.update = $rootScope.update = function(updateImmediately, diffChanges, cb){
var continueUpdate = true; var continueUpdate = true;
if(!updateImmediately){ if(!updateImmediately){
@ -105,7 +105,9 @@ angular.module('forms').controller('AdminFormController', ['$rootScope', '$scope
if(!updateImmediately){ $rootScope.saveInProgress = true; } if(!updateImmediately){ $rootScope.saveInProgress = true; }
$scope.updatePromise = $http.put('/forms/'+$scope.myform._id, {form: $scope.myform}) console.log(diffChanges);
$scope.updatePromise = $http.put('/forms/'+$scope.myform._id, { form: $scope.myform, changes: diffChanges })
.then(function(response){ .then(function(response){
$rootScope.myform = $scope.myform = response.data; $rootScope.myform = $scope.myform = response.data;
// console.log(response.data); // console.log(response.data);

View file

@ -32,7 +32,6 @@ angular.module('forms').directive('autoSaveForm', ['$rootScope', '$timeout', fun
$rootScope.finishedRender = false; $rootScope.finishedRender = false;
$scope.$on('editFormFields Started', function(ngRepeatFinishedEvent) { $scope.$on('editFormFields Started', function(ngRepeatFinishedEvent) {
// console.log('hello');
$rootScope.finishedRender = false; $rootScope.finishedRender = false;
}); });
$scope.$on('editFormFields Finished', function(ngRepeatFinishedEvent) { $scope.$on('editFormFields Finished', function(ngRepeatFinishedEvent) {
@ -52,13 +51,11 @@ angular.module('forms').directive('autoSaveForm', ['$rootScope', '$timeout', fun
return false; return false;
}; };
var debounceSave = function () { var debounceSave = function (diffChanges) {
$rootScope.saveInProgress = true;
$rootScope[$attrs.autoSaveCallback](true, $rootScope[$attrs.autoSaveCallback](true, diffChanges,
function(err){ function(err){
if(!err){ if(!err){
//console.log('\n\nForm data persisted -- setting pristine flag');
$formCtrl.$setPristine(); $formCtrl.$setPristine();
$formCtrl.$setUntouched(); $formCtrl.$setUntouched();
}else{ }else{
@ -70,12 +67,10 @@ angular.module('forms').directive('autoSaveForm', ['$rootScope', '$timeout', fun
//Update/Save Form if any Form fields are Dirty and Touched //Update/Save Form if any Form fields are Dirty and Touched
$scope.$watch(function(newValue, oldValue) { $scope.$watch(function(newValue, oldValue) {
//console.log('introParagraphStartPage.$dirty: '+$scope.editForm.introParagraphStartPage.$dirty);
//console.log('introParagraphStartPage.$touched: '+$scope.editForm.introParagraphStartPage.$touched);
if($rootScope.finishedRender && $scope.anyDirtyAndTouched($scope.editForm) && !$rootScope.saveInProgress){ if($rootScope.finishedRender && $scope.anyDirtyAndTouched($scope.editForm) && !$rootScope.saveInProgress){
//console.log('Form saving started'); delete newValue.visible_form_fields;
debounceSave(); debounceSave(DeepDiff.diff(oldValue, newValue));
//console.log('introParagraphStartPage.$dirty AFTER: '+$scope.editForm.introParagraphStartPage.$dirty);
} }
}); });
@ -89,7 +84,6 @@ angular.module('forms').directive('autoSaveForm', ['$rootScope', '$timeout', fun
oldValue.form_fields = _.removeDateFields(oldValue.form_fields); oldValue.form_fields = _.removeDateFields(oldValue.form_fields);
var changedFields = !_.isEqual(oldValue.form_fields,newValue.form_fields) || !_.isEqual(oldValue.startPage, newValue.startPage); var changedFields = !_.isEqual(oldValue.form_fields,newValue.form_fields) || !_.isEqual(oldValue.startPage, newValue.startPage);
var changedFieldMap = false;
if(oldValue.hasOwnProperty('plugins.oscarhost.settings.fieldMap')){ if(oldValue.hasOwnProperty('plugins.oscarhost.settings.fieldMap')){
changedFieldMap = !!oldValue.plugins.oscarhost.settings.fieldMap && !_.isEqual(oldValue.plugins.oscarhost.settings.fieldMap,newValue.plugins.oscarhost.settings.fieldMap); changedFieldMap = !!oldValue.plugins.oscarhost.settings.fieldMap && !_.isEqual(oldValue.plugins.oscarhost.settings.fieldMap,newValue.plugins.oscarhost.settings.fieldMap);
@ -117,7 +111,7 @@ angular.module('forms').directive('autoSaveForm', ['$rootScope', '$timeout', fun
} }
//Save form ONLY IF rendering is finished, form_fields have been changed AND currently not save in progress //Save form ONLY IF rendering is finished, form_fields have been changed AND currently not save in progress
if( $rootScope.finishedRender && ((changedFields && !$formCtrl.$dirty) || changedFieldMap) && !$rootScope.saveInProgress) { if( $rootScope.finishedRender && (changedFields && !$formCtrl.$dirty) && !$rootScope.saveInProgress) {
if(savePromise) { if(savePromise) {
$timeout.cancel(savePromise); $timeout.cancel(savePromise);
@ -125,7 +119,12 @@ angular.module('forms').directive('autoSaveForm', ['$rootScope', '$timeout', fun
} }
savePromise = $timeout(function() { savePromise = $timeout(function() {
debounceSave(); $rootScope.saveInProgress = true;
delete newValue.visible_form_fields;
delete newValue.visible_form_fields;
var _diff = DeepDiff.diff(oldValue, newValue);
debounceSave(_diff);
}); });
} }
//If we are finished rendering then form saving should be finished //If we are finished rendering then form saving should be finished

View file

@ -10,7 +10,6 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
}, },
controller: function($scope){ controller: function($scope){
console.log($scope.myform);
var field_ids = _($scope.myform.form_fields).pluck('_id'); var field_ids = _($scope.myform.form_fields).pluck('_id');
for(var i=0; i<field_ids.length; i++){ for(var i=0; i<field_ids.length; i++){
$scope.myform.plugins.oscarhost.settings.fieldMap[field_ids[i]] = null; $scope.myform.plugins.oscarhost.settings.fieldMap[field_ids[i]] = null;
@ -109,7 +108,7 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
} }
} }
var newField = { var newField = {
title: fieldTitle, title: fieldTitle + ' ' + $scope.myform.form_fields.length+1,
fieldType: fieldType, fieldType: fieldType,
fieldValue: '', fieldValue: '',
required: true, required: true,
@ -126,7 +125,6 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
}); });
} }
if(modifyForm){ if(modifyForm){
//Add newField to form_fields array //Add newField to form_fields array
$scope.myform.form_fields.push(newField); $scope.myform.form_fields.push(newField);

View file

@ -83,23 +83,24 @@ angular.module('forms').directive('fieldDirective', ['$http', '$compile', '$root
if(scope.field.fieldType === 'number' || scope.field.fieldType === 'textfield' || scope.field.fieldType === 'email' || scope.field.fieldType === 'link'){ if(scope.field.fieldType === 'number' || scope.field.fieldType === 'textfield' || scope.field.fieldType === 'email' || scope.field.fieldType === 'link'){
switch(scope.field.fieldType){ switch(scope.field.fieldType){
case 'textfield': case 'textfield':
scope.field.input_type = 'text'; scope.input_type = 'text';
break; break;
case 'email': case 'email':
scope.field.input_type = 'email'; scope.input_type = 'email';
scope.field.placeholder = 'joesmith@example.com'; scope.placeholder = 'joesmith@example.com';
break; break;
case 'number': case 'number':
scope.field.input_type = 'text'; scope.input_type = 'text';
scope.field.validateRegex = /^-?\d+$/; scope.validateRegex = /^-?\d+$/;
break; break;
default: default:
scope.field.input_type = 'url'; scope.input_type = 'url';
scope.field.placeholder = 'http://example.com'; scope.placeholder = 'http://example.com';
break; break;
} }
fieldType = 'textfield'; fieldType = 'textfield';
} }
var template = getTemplateUrl(fieldType); var template = getTemplateUrl(fieldType);
element.html(template).show(); element.html(template).show();
var output = $compile(element.contents())(scope); var output = $compile(element.contents())(scope);

View file

@ -1,8 +1,8 @@
<div class="textfield field row" <div class="textfield field row"
ng-click="setActiveField(field._id, index, true)"> ng-click="setActiveField(field._id, index, true)">
<div class="col-xs-12 field-title row-fluid" ng-style="{'color': design.colors.questionColor}"> <div class="col-xs-12 field-title row-fluid" ng-style="{'color': design.colors.questionColor}">
<h3 class="col-xs-12"> <h3 class="col-xs-12">
<small class="field-number"> <small class="field-number">
{{index+1}} {{index+1}}
<i class="fa fa-angle-double-right" aria-hidden="true"></i> <i class="fa fa-angle-double-right" aria-hidden="true"></i>
</small> </small>
@ -12,54 +12,119 @@
<span class="required-error" ng-show="!field.required"> <span class="required-error" ng-show="!field.required">
({{ 'OPTIONAL' | translate }}) ({{ 'OPTIONAL' | translate }})
</span> </span>
</h3> </h3>
<p class="col-xs-12"> <p class="col-xs-12">
<small>{{field.description}}</small> <small>{{field.description}}</small>
</p> </p>
</div> </div>
<div class="col-xs-12 field-input"> <div class="col-xs-12 field-input">
<input ng-style="{'color': design.colors.answerColor, 'border-color': design.colors.answerColor}" <input ng-style="{'color': design.colors.answerColor, 'border-color': design.colors.answerColor}"
name="{{field.fieldType}}{{index}}" name="{{field.fieldType}}{{index}}"
type="{{field.input_type}}" type="{{input_type}}"
ng-pattern="field.validateRegex" ng-pattern="validateRegex"
placeholder="{{field.placeholder}}" placeholder="{{placeholder}}"
ng-class="{ 'no-border': !!field.fieldValue }" ng-class="{ 'no-border': !!field.fieldValue }"
class="focusOn text-field-input" class="focusOn text-field-input"
ng-model="field.fieldValue" ng-model="field.fieldValue"
ng-model-options="{ debounce: 250 }" ng-model-options="{ debounce: 250 }"
value="field.fieldValue" value="field.fieldValue"
ng-focus="setActiveField(field._id, index, true)" ng-focus="setActiveField(field._id, index, true)"
on-enter-or-tab-key="nextField()" on-enter-or-tab-key="nextField()"
on-tab-and-shift-key="prevField()" on-tab-and-shift-key="prevField()"
ng-required="field.required" ng-required="field.required"
ng-disabled="field.disabled" ng-disabled="field.disabled"
aria-describedby="inputError2Status"> aria-describedby="inputError2Status">
</div> </div>
<div class="col-xs-12"> <div class="col-xs-12">
<div ng-show="forms.myForm.{{field.fieldType}}{{index}}.$invalid && !!forms.myForm.{{field.fieldType}}{{index}}.$viewValue " class="alert alert-danger" role="alert"> <div ng-show="forms.myForm.{{field.fieldType}}{{index}}.$invalid && !!forms.myForm.{{field.fieldType}}{{index}}.$viewValue " class="alert alert-danger" role="alert">
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span> <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
<span class="sr-only">Error:</span> <span class="sr-only">Error:</span>
<span ng-if="field.fieldType == 'email'"> {{ 'ERROR_EMAIL_INVALID' | translate }} </span> <span ng-if="field.fieldType == 'email'"> {{ 'ERROR_EMAIL_INVALID' | translate }} </span>
<span ng-if="field.validateRegex"> {{ 'ERROR_NOT_A_NUMBER' | translate }} </span> <span ng-if="field.validateRegex"> {{ 'ERROR_NOT_A_NUMBER' | translate }} </span>
<span ng-if="field.fieldType == 'link'"> {{ 'ERROR_URL_INVALID' | translate }} </span> <span ng-if="field.fieldType == 'link'"> {{ 'ERROR_URL_INVALID' | translate }} </span>
</div> </div>
</div> </div>
</div> </div>
<div> <div>
<div class="btn btn-lg btn-default hidden-xs" <div class="btn btn-lg btn-default col-xs-12 col-sm-4 hidden-xs"
style="padding: 4px; margin-top:8px; background: rgba(255,255,255,0.5)"> style="padding: 4px; margin-top:8px; background: rgba(255,255,255,0.5)">
<button ng-disabled="!field.fieldValue || forms.myForm.{{field.fieldType}}{{$index}}.$invalid" <button ng-disabled="!field.fieldValue || forms.myForm.{{field.fieldType}}{{$index}}.$invalid"
ng-style="{'background-color':design.colors.buttonColor, 'color':design.colors.buttonTextColor}" ng-style="{'background-color':design.colors.buttonColor, 'color':design.colors.buttonTextColor}"
ng-click="$root.nextField()" ng-click="$root.nextField()"
class="btn col-sm-5 col-xs-5"> class="btn col-sm-5 col-xs-5">
{{ 'OK' | translate }} <i class="fa fa-check"></i> {{ 'OK' | translate }} <i class="fa fa-check"></i>
</button> </button>
<div class="col-xs-6 col-sm-3" style="margin-top:0.2em"> <div class="col-xs-6 col-sm-3" style="margin-top:0.2em">
<small style="color:#ddd; font-size:70%"> <small style="color:#ddd; font-size:70%">
{{ 'ENTER' | translate }} {{ 'ENTER' | translate }}
</small> </small>
</div> </div>
</div>
</div>
<div class="textfield field row"
ng-click="setActiveField(field._id, index, true)">
<div class="col-xs-12 field-title row-fluid" ng-style="{'color': design.colors.questionColor}">
<h3 class="col-xs-12">
<small class="field-number">
{{index+1}}
<i class="fa fa-angle-double-right" aria-hidden="true"></i>
</small>
{{field.title}}
<span class="required-error" ng-show="!field.required">
({{ 'OPTIONAL' | translate }})
</span>
</h3>
<p class="col-xs-12">
<small>{{field.description}}</small>
</p>
</div>
<div class="col-xs-12 field-input">
<input ng-style="{'color': design.colors.answerColor, 'border-color': design.colors.answerColor}"
name="{{field.fieldType}}{{index}}"
type="{{input_type}}"
ng-pattern="validateRegex"
placeholder="{{placeholder}}"
ng-class="{ 'no-border': !!field.fieldValue }"
class="focusOn text-field-input"
ng-model="field.fieldValue"
ng-model-options="{ debounce: 250 }"
value="field.fieldValue"
ng-focus="setActiveField(field._id, index, true)"
on-enter-or-tab-key="nextField()"
on-tab-and-shift-key="prevField()"
ng-required="field.required"
ng-disabled="field.disabled"
aria-describedby="inputError2Status">
</div>
<div class="col-xs-12">
<div ng-show="forms.myForm.{{field.fieldType}}{{index}}.$invalid && !!forms.myForm.{{field.fieldType}}{{index}}.$viewValue " class="alert alert-danger" role="alert">
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
<span class="sr-only">Error:</span>
<span ng-if="field.fieldType == 'email'"> {{ 'ERROR_EMAIL_INVALID' | translate }} </span>
<span ng-if="field.validateRegex"> {{ 'ERROR_NOT_A_NUMBER' | translate }} </span>
<span ng-if="field.fieldType == 'link'"> {{ 'ERROR_URL_INVALID' | translate }} </span>
</div>
</div>
</div>
<div>
<div class="btn btn-lg btn-default col-xs-12 col-sm-4 hidden-xs"
style="padding: 4px; margin-top:8px; background: rgba(255,255,255,0.5)">
<button ng-disabled="!field.fieldValue || forms.myForm.{{field.fieldType}}{{$index}}.$invalid"
ng-style="{'background-color':design.colors.buttonColor, 'color':design.colors.buttonTextColor}"
ng-click="$root.nextField()"
class="btn col-sm-5 col-xs-5">
{{ 'OK' | translate }} <i class="fa fa-check"></i>
</button>
<div class="col-xs-6 col-sm-3" style="margin-top:0.2em">
<small style="color:#ddd; font-size:70%">
{{ 'ENTER' | translate }}
</small>
</div>
</div> </div>
</div> </div>