1
0

Merge branch 'logic_jump' into stage

This commit is contained in:
David Baldwynn 2016-10-07 08:30:09 -07:00
commit 1d65da9792
25 changed files with 460 additions and 142 deletions

View File

@ -260,7 +260,7 @@ exports.update = function(req, res) {
}
form = _.extend(form, req.body.form);
}
form.save(function(err, form) {
if (err) {

View File

@ -7,7 +7,8 @@ var mongoose = require('mongoose'),
util = require('util'),
mUtilities = require('mongoose-utilities'),
_ = require('lodash'),
Schema = mongoose.Schema;
Schema = mongoose.Schema,
LogicJumpSchema = require('./logic_jump.server.model');
var FieldOptionSchema = new Schema({
option_id: {
@ -77,10 +78,7 @@ function BaseFieldSchema(){
default: ''
},
logicJump: {
type: Schema.Types.ObjectId,
ref: 'LogicJump'
},
logicJump: LogicJumpSchema,
ratingOptions: {
type: RatingFieldSchema,
@ -162,6 +160,7 @@ FormFieldSchema.pre('validate', function(next) {
if(this.ratingOptions && this.ratingOptions.steps && this.ratingOptions.shape){
error.errors.ratingOptions = new mongoose.Error.ValidatorError({path: 'ratingOptions', message: 'ratingOptions is only allowed for type \'rating\' fields.', type: 'notvalid', value: this.ratingOptions});
console.error(error);
return(next(error));
}
@ -183,8 +182,9 @@ FormFieldSchema.pre('validate', function(next) {
//If field is multiple choice check that it has field
if(this.fieldType !== 'dropdown' && this.fieldType !== 'radio' && this.fieldType !== 'checkbox'){
if(!this.fieldOptions || this.fieldOptions.length !== 0){
if(this.fieldOptions && this.fieldOptions.length > 0){
error.errors.ratingOptions = new mongoose.Error.ValidatorError({path:'fieldOptions', message: 'fieldOptions are only allowed for type dropdown, checkbox or radio fields.', type: 'notvalid', value: this.ratingOptions});
console.error(error);
return(next(error));
}
}
@ -192,13 +192,18 @@ FormFieldSchema.pre('validate', function(next) {
return next();
});
//LogicJump Save
FormFieldSchema.pre('save', function(next) {
if(this.logicJump && this.logicJump.fieldA){
if(this.logicJump.jumpTo = '') delete this.logicJump.jumpTo;
}
next();
});
//Submission fieldValue correction
FormFieldSchema.pre('save', function(next) {
if(this.fieldType === 'dropdown' && this.isSubmission){
//console.log(this);
this.fieldValue = this.fieldValue.option_value;
//console.log(this.fieldValue);
}
return next();

View File

@ -6,76 +6,76 @@
var mongoose = require('mongoose'),
Schema = mongoose.Schema,
_ = require('lodash'),
math = require('math');
math = require('mathjs');
var BooleanExpressionSchema = new Schema({
expressionString: {
type: String,
var schemaOptions = {
toObject: {
virtuals: true
},
result: {
type: Boolean,
}
});
BooleanExpressionSchema.methods.evaluate = function(){
if(this.expressionString){
//Get headNode
var headNode = math.parse(this.expressionString);
var expressionScope = {};
var that = this;
//Create scope
headNode.traverse(function (node, path, parent) {
if(node.type === 'SymbolNode'){
mongoose.model('Field')
.findOne({_id: node.name}).exec(function(err, field){
if(err) {
console.log(err);
throw new Error(err);
}
if(!!_.parseInt(field.fieldValue)){
that.expressionScope[node.name] = _.parseInt(field.fieldValue);
}else {
that.expressionScope[node.name] = field.fieldValue;
}
console.log('_id: '+node.name);
console.log('value: '+that.expressionScope[node.name]);
});
}
});
var code = headNode.compile();
var result = code.eval(expressionScope);
this.result = result;
return result;
}else{
return null;
toJSON: {
virtuals: true
}
};
mongoose.model('BooleanExpression', BooleanExpressionSchema);
/**
* Form Schema
*/
var LogicJumpSchema = new Schema({
created: {
type: Date,
default: Date.now
expressionString: {
type: String,
enum: [
'field == static',
'field != static',
'field > static',
'field >= static',
'field <= static',
'field < static',
'field contains static',
'field !contains static',
'field begins static',
'field !begins static',
'field ends static',
'field !ends static',
]
},
lastModified: {
type: Date,
},
BooleanExpression: {
fieldA: {
type: Schema.Types.ObjectId,
ref: 'BooleanExpression'
ref: 'FormField'
},
valueB: {
type: Schema.Types.String
},
jumpTo: {
type: Schema.Types.ObjectId,
ref: 'FormField'
}
}, schemaOptions);
});
/*
IS EQUAL TO statement
var scope = {
a: val1,
b: val2
};
math.eval('a == b', scope);
IS NOT EQUAL TO statement
var scope = {
a: val1,
b: val2
};
math.eval('a !== b', scope);
BEGINS WITH statement
ENDS WITH statement
CONTAINS statement
DOES NOT CONTAIN statement
*/
mongoose.model('LogicJump', LogicJumpSchema);
module.exports = LogicJumpSchema;

View File

@ -2,7 +2,7 @@
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>{{title}}</title>
<title>{{title}} Form</title>
<!-- General META -->
<meta charset="utf-8">

View File

@ -36,9 +36,16 @@
"js-yaml": "^3.6.1",
"angular-ui-select": "https://github.com/whitef0x0/ui-select.git#compiled",
"angular-translate": "~2.11.0",
<<<<<<< HEAD
"ng-device-detector": "^3.0.1",
"ng-translate": "*",
"deep-diff": "^0.3.4"
=======
"ng-device-detector": "~3.0.1",
"ng-translate": "*",
"mathjs": "^3.4.1",
"jsep": "^0.3.1"
>>>>>>> logic_jump
},
"resolutions": {
"angular-bootstrap": "^0.14.0",

View File

@ -72,6 +72,7 @@ module.exports = function(db) {
var subdomains = req.subdomains;
var host = req.hostname;
console.log(subdomains);
if(subdomains.slice(0, 4).join('.')+'' === '1.0.0.127'){
subdomains = subdomains.slice(4);
@ -81,18 +82,18 @@ module.exports = function(db) {
if (!subdomains.length) return next();
var urlPath = url.parse(req.url).path.split('/');
if(urlPath.indexOf('static')){
if(urlPath.indexOf('static') > -1){
urlPath.splice(1,1);
req.root = 'https://' + config.baseUrl + urlPath.join('/');
return next();
}
if(subdomains.indexOf('stage') || subdomains.indexOf('admin')){
if(subdomains.indexOf('stage') > -1 || subdomains.indexOf('admin') > -1){
return next();
}
User.findOne({username: req.subdomains.reverse()[0]}).exec(function (err, user) {
console.log(user);
if (err) {
console.log(err);
req.subdomains = null;
@ -117,6 +118,8 @@ module.exports = function(db) {
req.userId = user._id;
console.log('\n\n\ngot subdomain: '+ req.subdomains.reverse()[0]);
// Q.E.D.
next();
});

View File

@ -24,6 +24,7 @@
},
"dependencies": {
"async": "^1.4.2",
"async-boolean-expression-evaluator": "^1.1.1",
"aws-sdk": "^2.3.9",
"bcrypt": "^0.8.7",
"body-parser": "~1.14.1",
@ -65,6 +66,7 @@
"lodash": "^2.4.1",
"main-bower-files": "~2.9.0",
"math": "0.0.3",
"mathjs": "^3.4.1",
"method-override": "~2.3.0",
"mkdirp": "^0.5.1",
"mongoose": "~4.4.19",

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

@ -1,5 +1,41 @@
'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);
/**
* Calculate a 32 bit FNV-1a hash
* Found here: https://gist.github.com/vaiorabbit/5657561
* Ref.: http://isthe.com/chongo/tech/comp/fnv/
*
* @param {string} str the input value
* @param {boolean} [asString=false] set to true to return the hash value as
* 8-digit hex string instead of an integer
* @param {integer} [seed] optionally pass the hash of the previous chunk
* @returns {integer | string}
*/
function hashFnv32a(str, asString, seed) {
/*jshint bitwise:false */
var i, l,
hval = (seed === undefined) ? 0x811c9dc5 : seed;
for (i = 0, l = str.length; i < l; i++) {
hval ^= str.charCodeAt(i);
hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);
}
if( asString ){
// Convert to 8 digit hex string
return ("0000000" + (hval >>> 0).toString(16)).substr(-8);
}
return hval >>> 0;
}
;
angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCounter', '$filter', '$rootScope', 'SendVisitorData',
function ($http, TimeCounter, $filter, $rootScope, SendVisitorData) {
@ -92,6 +128,67 @@ angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCoun
/*
** Field Controls
*/
var evaluateLogicJump = function(field){
console.log('evaluateLogicJump');
console.log(field.fieldValue);
var logicJump = field.logicJump;
if (logicJump.expressionString && logicJump.valueB && field.fieldValue) {
var parse_tree = jsep(logicJump.expressionString);
var left, right;
console.log(parse_tree);
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':
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');
@ -117,6 +214,15 @@ angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCoun
$scope.selected._id = field_id;
$scope.selected.index = field_index;
if(!field_index){
for(var i=0; i<$scope.myform.visible_form_fields.length; i++){
var currField = $scope.myform.visible_form_fields[i];
if(field_id == currField._id){
$scope.selected.index = i;
break;
}
}
}
var nb_valid = $filter('formValidity')($scope.myform);
$scope.translateAdvancementData = {
@ -159,20 +265,26 @@ angular.module('view-form').directive('submitFormDirective', ['$http', 'TimeCoun
};
$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);
var currField = $scope.myform.visible_form_fields[$scope.selected.index];
if($scope.selected && $scope.selected.index > -1){
//Jump to logicJump's destination if it is true
if(currField.logicJump && evaluateLogicJump(currField)){
$rootScope.setActiveField(currField.logicJump.jumpTo, null, true);
} else {
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) {
selected_index = $scope.selected.index+1;
selected_id = 'submit_field';
$rootScope.setActiveField(selected_id, selected_index, true);
}
}
}
};
$rootScope.prevField = $scope.prevField = function(){

View File

@ -51,7 +51,7 @@
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()"
ng-click="nextField()"
class="btn col-sm-5 col-xs-5">
{{ 'OK' | translate }} <i class="fa fa-check"></i>

View File

@ -4,4 +4,4 @@
ApplicationConfiguration.registerModule('view-form', [
'ngFileUpload', 'ui.router.tabs', 'ui.date', 'ui.sortable',
'angular-input-stars', 'pascalprecht.translate'
]);//, 'colorpicker.module' @TODO reactivate this module
]);

View File

@ -39,6 +39,10 @@ angular.module('forms').config(['$translateProvider', function ($translateProvid
PREVIEW: 'Preview',
//Edit Form View
DISABLED: 'Disabled:',
YES: 'YES',
NO: 'NO',
ADD_LOGIC_JUMP: 'Add Logic Jump',
ADD_FIELD_LG: 'Click to Add New Field',
ADD_FIELD_MD: 'Add New Field',
ADD_FIELD_SM: 'Add Field',

View File

@ -62,24 +62,24 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
//Populate local scope with rootScope methods/variables
$scope.update = $rootScope.update;
//Many-to-many Select for Mapping OscarhostFields -> FormFields
$scope.oscarFieldsLeft = function(field_id){
// LOGIC JUMP METHODS
$scope.removeLogicJump = function (field_index) {
var currField = $scope.myform.form_fields[field_index];
currField.logicJump = {};
};
if($scope.myform && $scope.myform.plugins.oscarhost.settings.validFields.length > 0){
if(!$scope.myform.plugins.oscarhost.settings.fieldMap) $scope.myform.plugins.oscarhost.settings.fieldMap = {};
$scope.addNewLogicJump = function (field_index) {
var form_fields = $scope.myform.form_fields;
var currField = form_fields[field_index];
console.log(currField);
if (form_fields.length > 1 && currField._id) {
var oscarhostFields = $scope.myform.plugins.oscarhost.settings.validFields;
var currentFields = _($scope.myform.plugins.oscarhost.settings.fieldMap).invert().keys().value();
if( $scope.myform.plugins.oscarhost.settings.fieldMap.hasOwnProperty(field_id) ){
currentFields = _(currentFields).difference($scope.myform.plugins.oscarhost.settings.fieldMap[field_id]);
}
//Get all oscarhostFields that haven't been mapped to a formfield
return _(oscarhostFields).difference(currentFields).value();
}
return [];
};
var newLogicJump = {
fieldA: currField._id
};
currField.logicJump = newLogicJump;
}
};
/*
** FormFields (ui-sortable) drag-and-drop configuration
@ -113,7 +113,8 @@ angular.module('forms').directive('editFormDirective', ['$rootScope', 'FormField
fieldValue: '',
required: true,
disabled: false,
deletePreserved: false
deletePreserved: false,
logicJump: {}
};
if($scope.showAddOptions(newField)){

View File

@ -249,7 +249,6 @@
</div>
</div>
<div class="row" ng-show="showRatingOptions(field)"><br></div>
<div class="row" ng-if="showRatingOptions(field)">
<div class="col-md-9 col-sm-9">{{ 'NUM_OF_STEPS' | translate }}</div>
@ -293,7 +292,7 @@
</div>
<div class="row">
<div class="col-md-4 col-xs-12 field-input">Disabled:</div>
<div class="col-md-4 col-xs-12 field-input">{{ 'DISABLED' | translate }}</div>
<div class="col-md-8 col-xs-12 field-input">
<label class="btn col-xs-5">
<input type="radio" ng-value="true"
@ -309,6 +308,106 @@
</div>
</div>
<div class="row" ng-if="myform.form_fields.length > 1">
<h4> Logic Jump </h4>
</div>
<div class="row" ng-if="myform.form_fields.length > 1">
<div class="col-md-4 col-xs-12 field-input">{{ 'ADD_LOGIC_JUMP' | translate }}</div>
<div class="col-md-8 col-xs-12 field-input">
<label class="btn col-xs-5">
<input type="radio" ng-checked="!!myform.form_fields[$index].logicJump.fieldA"
name="logicJumpYes{{field._id}}" ng-click="addNewLogicJump($index)"/>
<span> &nbsp; {{ 'YES' | translate }}</span>
</label>
<label class="btn col-xs-5 col-xs-offset-1">
<input type="radio" ng-checked="!myform.form_fields[$index].logicJump.fieldA"
name="logicJumpNo{{field._id}}" ng-click="removeLogicJump($index)"/>
<span> &nbsp; {{ 'NO' | translate }}</span>
</label>
</div>
</div>
<div class="row question" ng-if="myform.form_fields.length > 1 && myform.form_fields[$index].logicJump.fieldA">
<div class="col-md-4 col-sm-12">
<!-- FIX ME: translate this -->
<b> If this field </b>
</div>
<div class="col-md-4 col-sm-12">
<select style="width:100%" ng-model="field.logicJump.expressionString"
value="{{field.logicJump.expressionString}}"
name="logicjump_expressionString{{field._id}}">
<option value="field == static">
<!-- FIX ME: translate this -->
is equal to
</option>
<option value="field != static">
<!-- FIX ME: translate this -->
is not equal to
</option>
<option value="field > static" ng-if-start="field.fieldType === 'number' || field.fieldType === 'rating' || field.fieldType === 'number'">
<!-- FIX ME: translate this -->
is greater than
</option>
<option value="field >= static">
<!-- FIX ME: translate this -->
is greater or equal than
</option>
<option value="field < static">
<!-- FIX ME: translate this -->
is smaller than
</option>
<option value="field <= static" ng-if-end>
<!-- FIX ME: translate this -->
is smaller or equal than
</option>
<option value="field contains static" ng-if-start="field.fieldType !== 'number' && field.fieldType !== 'rating' && field.fieldType !== 'number'">
<!-- FIX ME: translate this -->
contains
</option>
<option value="field !contains static">
<!-- FIX ME: translate this -->
does not contain
</option>
<option value="field ends static">
<!-- FIX ME: translate this -->
ends with
</option>
<option value="field !ends static">
<!-- FIX ME: translate this -->
does not end with
</option>
<option value="field starts static">
<!-- FIX ME: translate this -->
starts with
</option>
<option value="field !starts static" ng-if-end>
<!-- FIX ME: translate this -->
does not start with
</option>
</select>
</div>
<div class="col-md-4 col-sm-12">
<input type="text" ng-model="field.logicJump.valueB"/>
</div>
<div class="col-md-2">
<!-- FIX ME: translate this -->
<b>Jumps to </b>
</div>
<div class="col-md-10">
<select style="width:100%" ng-model="field.logicJump.jumpTo"
value="{{field.logicJump.jumpTo}}"
name="logicjump_jumpTo{{field._id}}">
<option ng-repeat="jump_field in myform.form_fields"
value="{{jump_field._id}}">
{{jump_field.title}}
</option>
</select>
</div>
</div>
</div>
</accordion-group>

0
public/modules/forms/base/bower.json Executable file → Normal file
View File

View File

View File

View File

View File

View File

View File