POC to create dynamic forms.
Created
December 28, 2014 01:32
-
-
Save mromanoff/b47a47c2fe18e9483ec0 to your computer and use it in GitHub Desktop.
Backbone-forms dynamic forms
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div class="container"> | |
<h1>Backbone forms</h1> | |
<p>dynamic fields</p> | |
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
console.clear(); | |
$(function () { | |
'use strict'; | |
var Field = function (form, editor) { | |
this.form = form; | |
this.editor = editor; | |
}; | |
Field.prototype = { | |
// actual callbacks | |
toggleCheckbox: function () { | |
return (_.isEqual(this.getValue(),this.getCondition())) ? this.showTarget() : this.hideTarget(); | |
}, | |
toggleRadio: function () { | |
console.log('%ccondition:', 'color: white; background: chocolate', this.getCondition()); | |
console.log('%cvalue:', 'color: white; background: LightCoral ', this.getValue()); | |
var passTest = _.some(this.getCondition(), function(item){ | |
return _.isEqual(item, this.getValue()); | |
}, this); | |
return (passTest) ? this.showTarget() : this.hideTarget(); | |
}, | |
toggleHelp: function () { | |
return (_.isEqual(this.getValue(),this.getCondition()[0])) ? this.showHelp() : this.hideHelp(); | |
}, | |
getWaisteToHipRatio: function () { | |
console.group('getWaisteToHipRatio'); | |
console.log('%cvalues to divide: %o', 'color: lime; background: #444', this.getTarget()); | |
console.groupEnd(); | |
var fields = this.getTarget(); | |
this.form.on(this.getEvents(), function (form, editor) { | |
var a = +form.fields[fields[0]].getValue(); | |
var b = +form.fields[fields[1]].getValue(); | |
var result = _.isNaN(this.divide(a, b)) ? '' : this.divide(a, b).toFixed(2); | |
this.editor.setValue(result); | |
}, this); | |
}, | |
getSumOfSkinFolds: function () { | |
console.group('getSumOfSkinFolds'); | |
console.log('%cvalues to sum: %o', 'color: lime; background: #444', this.getTarget()); | |
console.groupEnd(); | |
var fields = this.getTarget(); | |
//TODO do it better with _.values() + _.reduce() | |
this.form.on(this.getEvents(), function (form, editor) { | |
var a = +form.fields[fields[0]].getValue(); | |
var b = +form.fields[fields[1]].getValue(); | |
var c = +form.fields[fields[2]].getValue(); | |
var d = +form.fields[fields[3]].getValue(); | |
var sum = _.reduce([a, b, c, d], function (memo, num) { | |
return memo + num; | |
}, 0); | |
var result = _.isNaN(sum) ? '' : sum.toFixed(); | |
this.editor.setValue(result); | |
}, this); | |
}, | |
getLeanBodyMass: function () { | |
console.group('getLeanBodyMass'); | |
console.log('%cCalculation = Body Weight - (Body Weight x Body Fat%) %o', 'color: lime; background: #444', this.getTarget()); | |
console.groupEnd(); | |
var fields = this.getTarget(); | |
//TODO do it better with _.values() + _.reduce() | |
this.form.on(this.getEvents(), function (form, editor) { | |
var a = +form.fields[fields[0]].getValue(); | |
var b = +form.fields[fields[1]].getValue(); | |
//Calculation = Body Weight - (Body Weight x Body Fat%) Body Fat = 0.0(5) | |
var result = this.subtract(a, this.multiply(a, this.divide(b, 100)).toFixed(2)); | |
this.editor.setValue( _.isNaN(result) ? '' : result); | |
}, this); | |
}, | |
// helper funcntions | |
getTarget: function () { | |
return this.editor.$el.data('target').split(','); | |
}, | |
getEvents: function() { | |
return _.map(this.getTarget(), function(field){ | |
return field + ':change'; | |
}).join(' '); | |
}, | |
getCondition: function () { | |
return this.editor.$el.data('condition').split(','); | |
}, | |
getValue: function () { | |
return this.editor.getValue(); | |
}, | |
hideHelp: function () { | |
this.editor.$el.find('.' + this.getTarget() + '-block').slideUp(200); | |
}, | |
showHelp: function () { | |
this.editor.$el.find('.' + this.getTarget() + '-block').slideDown(200); | |
}, | |
hideTarget: function () { | |
var fieldType; | |
_.each(this.getTarget(), function (fieldName) { | |
//console.info('filed', fieldName); | |
//console.log('Editor', this.form.fields[fieldName].editor); | |
//console.log('Editor schema type', this.form.schema[fieldName].type); | |
fieldType = this.form.schema[fieldName].type; | |
switch(fieldType) { | |
case 'TextArea': | |
this.form.fields[fieldName].$el.find('textarea').val(''); | |
break; | |
case 'Radio': | |
this.form.fields[fieldName].$el.find('[name=' + fieldName +']').prop('checked', false); | |
break; | |
default: | |
return; | |
}; | |
//this.form.commit(); | |
//console.log('commited', this.form); | |
this.form.fields[fieldName].$el.hide(); | |
},this); | |
}, | |
showTarget: function () { | |
_.each(this.getTarget(), function (field) { | |
this.form.fields[field].$el.slideDown(200); | |
}, this); | |
}, | |
subtract: function (x, y) { | |
return x - y; | |
}, | |
multiply: function (x, y) { | |
return x * y; | |
}, | |
divide: function (a, b) { | |
return (b === 0) ? a : (a / b); | |
}, | |
bind: function (callback) { | |
this.form.on(this.editor.key + ':change', function () { | |
this[callback](); | |
}, this); | |
}, | |
init: function (callback) { | |
console.log('callback', callback); | |
if(_.isFunction(this[callback])) { | |
this[callback](); | |
this.bind(callback); | |
} | |
} | |
}; | |
// let's assume this object we get from server | |
var serverResponse = { | |
// describe fields | |
schema: { | |
medical01: { | |
type: "Radio", | |
title: "Do you experience an irregular or racing heart rate during rest or exercise?", | |
options: [ | |
{ | |
val: "yes", | |
label: "Yes" | |
},{ | |
val: "no", | |
label: "No" | |
} | |
], | |
help: "we recommend that you see a doctor before your Equifit or before you begin an exercise program.", | |
fieldAttrs: { | |
"data-bind": "toggleHelp", | |
"data-target": "help", | |
"data-condition": "yes" | |
} | |
}, | |
'medical01-dynamic': { | |
type: 'Checkboxes', | |
title: 'Head or <b>Neck</b>', | |
fieldAttrs: { | |
'data-bind': 'toggleCheckbox', | |
'data-target': 'medical01-dynamic-01,medical01-dynamic-02,medical01-dynamic-03', | |
'data-condition': "medical13-1" | |
}, | |
"options": [ | |
{ | |
"val": "medical13-1", | |
"label": "Hip" | |
} | |
] | |
}, | |
'medical01-dynamic-01': { | |
type: 'TextArea', | |
title: 'Area (Left, Right / Upper, Mid, Lower)', | |
editorAttrs: {maxlength: 100, title: 'Tooltip help'} | |
}, | |
'medical01-dynamic-02': { | |
type: 'TextArea', | |
title: 'When it happened?' | |
}, | |
'medical01-dynamic-03': { | |
type: 'Radio', | |
title: 'Do you drink?', | |
options: [{ | |
val: 'yes', | |
label: 'Yes' | |
}, { | |
val: 'no', | |
label: 'No' | |
}] | |
}, | |
drink: { | |
type: 'Radio', | |
title: 'Do you drink?', | |
options: [{ | |
val: 'yes', | |
label: 'Yes' | |
}, { | |
val: 'no', | |
label: 'No' | |
}], | |
fieldAttrs: { | |
'data-bind': 'toggleRadio', | |
'data-target': 'beer,milk', | |
'data-condition': 'yes' | |
} | |
}, | |
beer: { | |
type: 'Text', | |
title: 'only beer', | |
help: 'good choice' | |
}, | |
milk: { | |
type: 'Text', | |
title: 'goat milk', | |
help: 'you need help' | |
}, | |
// three radios | |
goal: { | |
type: 'Radio', | |
title: 'Have you ever achieved this goal in the past?', | |
options: [{ | |
val: 'yes', | |
label: 'Yes' | |
}, { | |
val: 'no', | |
label: 'No' | |
},{ | |
val: 'partialy', | |
label: 'Partialy' | |
}], | |
fieldAttrs: { | |
'data-bind': 'toggleRadio', | |
'data-target': 'goalHow', | |
'data-condition': 'yes' | |
} | |
}, | |
goalHow: { | |
type: 'TextArea', | |
title: 'if so, when and how?', | |
editorAttrs: {maxlength: 50} | |
}, | |
// toggle 5 radio buttons, show extra only for 1,2,3,4 options | |
goals14: { | |
type: "Radio", | |
title: "On a scale of 1 - 5, how committed are you to each goal?", | |
fieldClass: "bbf-radiobuttons", | |
fieldAttrs: { | |
"data-bind": "toggleRadio", | |
"data-target": "goals14-dynamic1", | |
"data-condition": "goals14-1,goals14-2,goals14-3,goals14-4" | |
}, | |
options: [ | |
{ | |
"val": "goals14-1", | |
"label": "1" | |
}, | |
{ | |
"val": "goals14-2", | |
"label": "2" | |
}, | |
{ | |
"val": "goals14-3", | |
"label": "3" | |
}, | |
{ | |
"val": "goals14-4", | |
"label": "4" | |
}, | |
{ | |
"val": "goals14-5", | |
"label": "5" | |
} | |
] | |
}, | |
"goals14-dynamic1": { | |
"type": "TextArea", | |
"title": "If you are not a 5 out 5 committed, what would make you a 5?", | |
"editorAttrs": {"maxlength": 100} | |
}, | |
waist: { | |
type: 'Text', | |
title: 'Waist Circumference', | |
editorClass: 'input-small', | |
help: 'inches' | |
}, | |
hip: { | |
type: 'Text', | |
title: 'Hip Circumference', | |
editorClass: 'input-small', | |
help: 'inches' | |
}, | |
waistHipRatio: { | |
type: 'Text', | |
title: 'Waist to Hip Ratio', | |
editorClass: 'input-small', | |
editorAttrs: { | |
readonly: true | |
}, | |
fieldAttrs: { | |
'data-bind': 'getWaisteToHipRatio', | |
'data-target': 'waist,hip' | |
} | |
}, | |
tricep: { | |
type: "Text", | |
title: "tricep", | |
editorClass: 'input-small', | |
help: 'mm' | |
}, | |
abdomen: { | |
type: "Text", | |
title: "abdomen", | |
editorClass: 'input-small', | |
help: 'mm' | |
}, | |
suprailiac: { | |
type: "Text", | |
title: "suprailiac", | |
editorClass: 'input-small', | |
help: 'mm' | |
}, | |
midThigh: { | |
type: "Text", | |
title: "midThigh", | |
editorClass: 'input-small', | |
help: 'mm' | |
}, | |
sumOfSkinFolds: { | |
type: "Text", | |
title: "Sum of skin folds", | |
editorClass: 'input-small', | |
editorAttrs: { | |
readonly: true | |
}, | |
fieldAttrs: { | |
'data-bind': 'getSumOfSkinFolds', | |
'data-target': 'tricep,abdomen,suprailiac,midThigh' | |
} | |
}, | |
bodyWeight: { | |
type: "Text", | |
title: "Weight", | |
editorClass: 'input-small', | |
help: 'lbs' | |
}, | |
bodyFat: { | |
type: "Text", | |
title: "Body Fat Percentage", | |
editorClass: 'input-small', | |
help: '%' | |
}, | |
leanBodyMass: { | |
type: "Text", | |
title: "Lean Body Mass", | |
editorClass: 'input-small', | |
editorAttrs: { | |
readonly: true | |
}, | |
help: 'lbs', | |
fieldAttrs: { | |
'data-bind': 'getLeanBodyMass', | |
'data-target': 'bodyWeight,bodyFat' | |
} | |
} | |
}, | |
fieldsets: [{ | |
legend: 'Toggle Help', | |
fields: [ 'medical01'] | |
}, | |
{ | |
legend: 'Toggle checkbox', | |
fields: ['medical01-dynamic','medical01-dynamic-01','medical01-dynamic-02', 'medical01-dynamic-03'] | |
}, | |
{ | |
legend: 'Toggle radio Yes/No', | |
fields: ['drink','milk','beer'] | |
}, | |
{ | |
legend: 'Toggle radio Yes/No/Partialy', | |
fields: ['goal', 'goalHow'] | |
}, | |
{ | |
legend: 'Toggle radio 1,2,3,4 and 5', | |
fields: ['goals14', 'goals14-dynamic1'] | |
}, | |
{ | |
legend: 'Body measurements', | |
fields: ['waist', 'hip', 'waistHipRatio'] | |
}, | |
{ | |
legend: 'Skinfold Measurement', | |
fields: [ | |
'tricep', | |
'abdomen', | |
'suprailiac', | |
'midThigh', | |
'sumOfSkinFolds'] | |
}, | |
{ | |
legend: 'Lean Body Mass(lbs)', | |
fields: [ | |
'bodyWeight', | |
'bodyFat', | |
'leanBodyMass'] | |
} | |
], | |
// data will be populte these fields | |
data: {} | |
}; | |
// these all Front End stuff. | |
var User = Backbone.Model.extend({ | |
schema: serverResponse.schema, | |
}); | |
// you can load data in model instance or as a defaults | |
var user = new User(serverResponse.data); | |
var form = new Backbone.Form({ | |
model: user, | |
fieldsets: serverResponse.fieldsets | |
}).render(); | |
_.each(form.fields, function (editor) { | |
//console.log('editor', editor); | |
if (editor.$el.data('bind')) { | |
var callback = editor.$el.data('bind'), | |
field = new Field(form, editor); | |
field.init(callback); | |
} | |
}, this); | |
$('.container').append(form.el); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* custom rules */ | |
ul{ | |
list-style-type: none; | |
} | |
[data-target="help"] .help-block { | |
display: none; | |
color: #c09853; | |
padding: 8px 35px 8px 14px; | |
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); | |
background-color: #fcf8e3; | |
border: 1px solid #fbeed5; | |
-webkit-border-radius: 0; | |
-moz-border-radius: 0; | |
border-radius: 0; | |
margin-top: 20px; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment