File: //home/plan4ev/public_html/wp-content/plugins/caldera-forms/assets/js/state/state.js
/**
* State management for front-end
*
* @since 1.5.3
*
* @param formId {String} ID of form this is tracking state for.
* @param $ {jquery} jQuery
*
* @constructor
*/
function CFState(formId, $ ){
var
self = this,
fields = {},
events = new CFEvents(this),
unBound = {},
fieldVals = {},
calcVals = {};
/**
* Initialized ( or re-initialize) state with specific fields.
*
* @since 1.5.3
*
* @param formFields {Object} Should be flat field ID attribute : Field default
*/
this.init = function (formFields, calcDefaults) {
for ( var id in formFields ){
if( 'object' === typeof calcDefaults[id] ){
if( 'calculation' == calcDefaults[id].type ){
bindCalcField(id,calcDefaults[id])
}
}else if( bindField(id)){
fieldVals[id] = formFields[id];
if( calcDefaults.hasOwnProperty(id) ){
calcVals[id] = calcDefaults[id];
}else{
calcVals[id] = null;
}
}else{
fieldVals[id] = '';
unBound[id] = true;
calcVals[id] = null;
}
}
};
/**
* Get current state for a field
*
* @since 1.5.3
*
* @param id {String} Field id attribute
* @returns {String|Array}
*/
this.getState = function(id){
if( ! inState(id) ){
return false;
}
return fieldVals[id];
};
/**
*Get calculation value for a field
*
* @since 1.5.6
*
* @param id {String} Field id attribute
* @param highest {Boolean}
* @returns {float}
*/
this.getCalcValue = function (id,highest) {
var val = 0;
if (! inState( id )) {
return val;
}
if( highest ){
highest = 0;
var value = highest,
$item;
$( '#' + id ).each(function(){
value = 0;
$item = $( this );
if( $item.prop('checked' ) ){
value = findCalcVal( $item );
if( parseFloat( value ) > parseFloat( highest ) ){
highest = parseFloat( value );
}
}
});
return parseFloat( highest );
}
if (calcVals.hasOwnProperty(id) ) {
if( false === calcVals[id] || null === calcVals[id] || 0 === calcVals[id] ){
//@TODO use let here, when ES6.
var _val = findCalcVal( $( document.getElementById( id ) ) );
if( isString( _val ) ) {
_val = parseFloat( _val );
}
if( isNumber( _val ) ){
calcVals[id] = _val;
}
}
val = calcVals[id];
} else {
val = self.getState(id);
if ($.isArray(val)) {
val = val.reduce( function ( a, b) {
return parseFloat( a ) + parseFloat( b );
}, 0);
}
if( isNumber( val ) ){
calcVals[id] = val;
}
}
return parseFloat( val );
};
/**
* Change state for a field
*
* @since 1.5.3
*
* @param id {String} Field id attribute
* @param value {String|Array} New value
*/
this.mutateState = function(id, value ){
if( ! inState(id) ){
return false;
}
if( fieldVals[id] != value ){
fieldVals[id] = value;
events.trigger(id,value);
}
return true;
};
/**
* Unbind field -- used when hiding via conditional logic
*
* @since 1.5.3
*
* @param id {String} Field id attribute
*/
this.unbind = function(id){
self.mutateState(id,'');
unBound[id] = true;
delete calcVals[id];
};
/**
* Rebind field -- used when unhiding via conditional logic
*
* @since 1.5.3
*
* @param id {String} Field id attribute
*/
this.rebind = function(id){
bindField(id);
delete unBound[id];
};
/**
* Accessor for the CFEvents object used for this state
*
* @since 1.5.3
*
* @returns {{subscribe: subscribe, detach: detach}}
*/
this.events = function(){
return {
/**
* Attach an event to change of an input in the state
*
* @since 1.5.3
*
* @param id {String} Field ID attribute
* @param callback {Function} The callback function
*/
subscribe: function( id, callback ){
if( inState(id)){
events.subscribe(id,callback);
}
},
/**
* Detach an event to change of an input in the state
*
* @since 1.5.3
*
* @param id {String} Field ID attribute
* @param callback {Function|null} The callback function. Pass null to detach all.
*/
detach: function(id,callback){
events.detach(id,callback);
},
emit: function (eventName,payload) {
events.emit(eventName,payload);
},
attatchEvent: function(eventName,callback){
events.subscribe(eventName,callback);
}
}
};
/**
* Check if value is tracked in state
*
* @since 1.5.3
*
* @param id {String} Field ID attribute
*
* @returns {boolean}
*/
function inState(id){
return fieldVals.hasOwnProperty(id);
}
/**
* Bind a field's change events
*
* @since 1.5.3
*
* @param {String} id
* @returns {boolean}
*/
function bindField(id) {
var $field = $('#' + id);
if ($field.length) {
$field.on('change keyup', function () {
var $el = $(this);
calcVals[$el.attr('id')] = findCalcVal( $el );
self.mutateState([$el.attr('id')],$el.val());
});
calcVals[id] = findCalcVal( $( document.getElementById( id ) ) );
self.mutateState([$field.attr('id')],$field.val());
$field.trigger('cf.bind', {
field: $field.attr('id')
});
return true;
} else {
$field = $('.' + id);
if ($field.length) {
//Rebind checkbox options when the checkbow field is unhidden
if( 'object' == typeof $field ){
var val = [];
var allSums = 0;
$field.each(function ( i, el ) {
var $this = $(el);
var sum = 0;
if ($this.prop('checked')) {
sum += parseFloat(findCalcVal($this));
allSums += sum;
val.push($this.val());
}
calcVals[id] = allSums;
});
}
$field.on('change', function () {
var val = [];
var $el = $(this),
id,
$collection,
type = $el.attr( 'type' );
switch ( type ){
case 'radio' :
id = $el.data( 'radio-field' );
$collection = $( '[data-radio-field=' + id +']' );
val = '';
break;
case 'checkbox' :
id = $el.data( 'checkbox-field' );
$collection = $( '[data-checkbox-field=' + id +']' );
break;
default :
id = $el.data( 'field' );
$collection = $( '[data-field=' + id +']' );
break;
}
if ( 'checkbox' === type ) {
var $v, sum = 0;
if ( $collection.length ) {
$collection.each(function (k, v) {
$v = $(v);
if ($v.prop('checked')) {
sum += parseFloat(findCalcVal($v));
val.push($v.val());
}
});
}else{
val = [];
}
calcVals[id] = sum;
} else if( ! $collection.length ){
val = 0;
} else if ( 1 == $collection.length ){
val = findCalcVal( $($collection[0]));
} else{
$collection.each(function (i, el) {
var $this = $(el);
if ($this.prop('checked')) {
if ('radio' === type) {
calcVals[id] = findCalcVal($this);
val = $this.val();
} else {
val.push($this.val());
}
}
});
}
self.mutateState(id,val);
$field.trigger('cf.bind', {
field: $field.attr('id')
});
});
return true;
}
}
self.unbind(id);
return false;
}
/**
* Bind change on a calculation field so that when state changes, calc value changes with it.
*
* @since 1.5.6.2
*
* @param {String} id
* @param {Object} config
*/
function bindCalcField(id,config) {
fieldVals[id] = 0;
calcVals[id] = 0;
self.events().subscribe(id,function (id,value) {
calcVals[id] = value;
});
}
/**
* Find calculation value for an element
*
* @since 1.5.6
* @param {jQuery} $field
* @returns {float}
*/
function findCalcVal( $field ) {
if( $field.is( 'select' ) && $field.has( 'option' ) ){
$field = $field.find(':selected');
}
if( ! $field.length ){
return 0;
}
if( $field.is( 'hidden' ) ){
return $field.val();
}
var val = 0;
var attr = $field.attr('data-calc-value');
if (typeof attr !== typeof undefined && attr !== false && ! isNaN(attr)) {
val = $field.data( 'calc-value' );
}else{
val = $field.val();
}
return parseFloat(val);
}
/**
* Parse float if we can parse float, else 0.
*
* @since 1.5.6
*
* @param number
* @returns {*}
*/
function parseFloat( number ) {
if( ! number || isNaN( number) ){
return 0.0;
}
return window.parseFloat( number );
}
/**
* Parse integer if we can parse integer, else 0.
*
* @since 1.5.6
*
* @param number
* @returns {*}
*/
function parseInt( number ) {
if( ! number || isNaN( number) ){
return 0;
}
return window.parseInt( number );
}
/**
* Determine if a value is a Number
*
* @since 1.5.6.2
*
* Copied from axios/lib/utils.js
*
* @param {Object} val The value to test
* @returns {boolean} True if value is a Number, otherwise false
*/
function isNumber(val) {
return typeof val === 'number';
}
/**
* Determine if a value is a String
*
* @since 1.5.6.2
*
* Copied from axios/lib/utils.js
* @param {Object} val The value to test
* @returns {boolean} True if value is a String, otherwise false
*/
function isString(val) {
return typeof val === 'string';
}
}