/*
_______ __ _______ __ _______ .______ __ __ __ _______ _______.
| ____|| | | ____|| | | \ | _ \ | | | | | | | ____| / |
| |__ | | | |__ | | | .--. | | |_) | | | | | | | | |__ | (----`
| __| | | | __| | | | | | | | / | | | | | | | __| \ \
| | | | | |____ | `----.| '--' | | |\ \----.| `--' | | `----.| |____.----) |
|__| |__| |_______||_______||_______/ | _| `._____| \______/ |_______||_______|_______/
Episode IV: A new hope
****************
this plugin has been manually updated to be able to used with bee editor. The modified line is below:
$('.bee-page-container').length ? $first_input.parents('.bee-form-row').hide() : $first_input.parents(this.control_group_wrapper).hide();
****************
*/
SITE.Field_rules_lib = (function($) {
function Field_rules(){
this.field_rules = null; //object containing all the field rules for the client
this.default_options = null; //object containing the full list of formatted and sorted options for each field
this.usable_fields = null; //object containing all the fields from the db that are associated to field rules
this.$form = null; //the form that the field rules are being applied to
this.form_fields = {}; //reference to elements on the form
this.applied_rules = {}; //the state for all currently applied field rules
}
//list of date field types that need to be converted to js dates for comparisons
Field_rules.prototype.date_field_types = [
'27', //DOB_FIELD_TYPE_ID
'4' //US_DATE_FIELD_TYPE_ID
];
Field_rules.prototype.control_group_wrapper = '.form-group';
Field_rules.prototype.control_wrapper = '.form-group div';
Field_rules.prototype.hidden_input_class = 'field-rule-hidden-input'; //needed for checkbox and radio fields
/**
* Retrieve the all the field rule related data via ajax and register all the fields on the form
* @param $form_element - jquery object containing the form to preform field rules on
*/
Field_rules.prototype.init = function($form_element){
var supportsCors = ($.support.cors || !$.ajaxTransport || !window.XDomainRequest) ? true : false,
supportsXDomain = (!$.support.cors || $.ajaxTransport || window.XDomainRequest) ? true : false,
isHTTPS = window.location.protocol === 'https:',
field_rules_endpoint,
that = this;
this.$form = $form_element;
//use WebCampaigns Web servers to proxy the requests
field_rules_endpoint = SITE.data.field_rules_url;
$.get(field_rules_endpoint,
function(data) {
if (data.status === 'success') {
that.field_rules = data.data.rules;
that.default_options = data.data.default_options;
that.usable_fields = data.data.fields;
that.form_fields = that.get_form_fields(that.$form);
that.attach_event_listeners();
that.process_field_rules(that.field_rules);
}
},
'json'
);
};
/**
* Grab all the fields on the form
* returns object in the form of:
* {
* field_id1: [html input1, html input2],
* field_id2: [html input1, html input2]
* }
* @param {Object} $form -jquery form object containing the form
* @returns {Object} object containing all the fields on the form
*/
Field_rules.prototype.get_form_fields = function ($form){
//find all the inputs with a name attribute on the form
var $inputs_on_form = $form.find(':input[name]'),
form_fields = {},
control_wrapper = this.control_wrapper,
hidden_input_class = this.hidden_input_class;
//index all the inputs by the form name
$inputs_on_form.each(function(){
// we need to strip out [] if they trail a field name - such as 608[]
var $field = $(this),
field_name = $field.attr('name').replace('[]', ''),
$field_container,
hidden_input_html;
if(!form_fields.hasOwnProperty(field_name)){
form_fields[field_name] = [];
//check if this is a field type that needs to have the hidden input injected
if(
$field.hasClass('iqs-form-checkbox')
|| $field.hasClass('iqs-form-radio')
|| $field.hasClass('iqs-form-multi-select')
) {
$field_container = $field.parents(control_wrapper);
hidden_input_html = '';
$field_container.prepend(hidden_input_html);
}
}
form_fields[field_name].push(this);
});
return form_fields;
};
/**
* Attach event listeners to all fields that have field rules and are a part of the form
*/
Field_rules.prototype.attach_event_listeners = function(){
for (var field_id in this.field_rules) {
if (this.is_field_on_form(field_id)){
this.attach_change_event_listener($(this.form_fields[field_id]));
}
}
};
/**
* Attach the change event to the specified field(s)
* @param {Object} $field - jquery object containing the field that needs the change event attached to it
*/
Field_rules.prototype.attach_change_event_listener = function($field){
var that = this;
$field.on('change.field_rules', function(e){
that.process_field_rules(that.field_rules);
});
};
/**
* Loop through all the field rules and apply the rules whose conditions have been satisfied
* and removing existing rules that should no longer be applied
* @param {Object} field_rules - object containing all the keyed by field_id
*/
Field_rules.prototype.process_field_rules = function(field_rules){
var that = this;
for (var field_id in field_rules) {
//only process rules if the field is on the form
if(!that.is_field_on_form(field_id)){
continue;
}
field_rules[field_id].forEach(function(rule){
//only process rule if the dependant field is on the form
if(!that.is_field_on_form(rule.dependentUsableFieldID)){
return;
}
//if the rule is not already applied and it should be applied then apply the rule
if(!that.is_rule_already_applied(rule) && that.should_rule_be_applied(rule)){
if(!that.applied_rules.hasOwnProperty(rule.dependentUsableFieldID)){
that.applied_rules[rule.dependentUsableFieldID] = {}
}
that.applied_rules[rule.dependentUsableFieldID][rule.id] = rule;
that.add_to_applied_rules(rule);
}
//if the rule already applied and it should be removed
else if (that.is_rule_already_applied(rule) && !that.should_rule_be_applied(rule)){
that.remove_rule(rule);
}
});
}
this.apply_all_rules();
};
/**
* Iterate through the list of applied rules make the resulting changes to the DOM
*/
Field_rules.prototype.apply_all_rules = function(){
for(var field_id in this.applied_rules) {
for (var rule_id in this.applied_rules[field_id]) {
this.apply_rule(this.applied_rules[field_id][rule_id]);
}
}
};
/**
* Find the intersection of 2 arrays. The arrays should only contain primitives/
* @param {Array} array1
* @param {Array} array2
* @returns {Array} the array containing only elements that were a part of both arrays
*/
Field_rules.prototype.intersect_arrays = function (array1, array2){
return array1.filter(function(val){
return array2.indexOf(val) !== -1;
});
};
/**
* Returns the current value(s) for a field
* @param {Number|String} field_id
* @returns {Array} - array containing all the values of a field
*/
Field_rules.prototype.get_field_value = function (field_id){
var $field,
field_value = [],
dob_date = [];
if(this.form_fields.hasOwnProperty(field_id)){
$field = $(this.form_fields[field_id]);
if ($field.hasClass('dob-field')) {
$.each($field, function() {
if ($(this).val() !== '') {
dob_date.push($(this).val());
}
});
//if a full date was selected
if (dob_date.length === 3) {
field_value = [dob_date[0] + '/' + dob_date[1] + '/' + dob_date[2]];
}
// post YYYY-mm-dd values as mm/dd/YYYY
} else if ($field.hasClass('iqs-form-date')) {
var date_string = $field.val(),
date = date_string.split('-');
//if a full date was selected
if (date.length === 3) {
field_value = [date[1] + '/' + date[2] + '/' + date[0]];
}
} else {
var temp_values = $(this.form_fields[field_id]).serializeArray();
if (temp_values.length !== 0) {
temp_values.forEach(function (val) {
field_value.push(val.value);
});
}
}
}
return field_value;
};
/**
* check if a field is a date type field
* @param {Number|String} field_id
* @returns {boolean}
*/
Field_rules.prototype.is_date_field = function(field_id){
return this.date_field_types.indexOf(this.usable_fields[field_id].fieldTypeID) !== -1;
};
/**
* check if a field rule is already being applied
* @param {Object} rule - object containing the field rule from the db
* @returns {boolean}
*/
Field_rules.prototype.is_rule_already_applied = function (rule){
return (
this.applied_rules.hasOwnProperty(rule.dependentUsableFieldID)
&& this.applied_rules[rule.dependentUsableFieldID].hasOwnProperty(rule.id)
);
};
/**
* check if a field is on the form
* @param {Number|String} field_id
* @returns {boolean}
*/
Field_rules.prototype.is_field_on_form = function (field_id){
return this.form_fields.hasOwnProperty(field_id);
};
/**
* check to see if a field currently has other field rules applied
* useful in determining if a hidden field should be shown due to field rules being removed
* @param {Number|String} field_id
* @param {String} action_type
* @returns {boolean}
*/
Field_rules.prototype.has_other_applied_rules = function(field_id, action_type){
var has_other_applied_rules = false;
if(this.applied_rules.hasOwnProperty(field_id)){
for (var rule_id in this.applied_rules[field_id]){
if (this.applied_rules[field_id][rule_id].action === action_type){
has_other_applied_rules = true;
break;
}
}
}
return has_other_applied_rules;
};
/**
* Return any 1 of the currently applied field rules for the specified field
* @param {Number|String} field_id
* @returns {Object} - object containing the first field rule found
*/
Field_rules.prototype.get_any_applied_rule = function (field_id){
var other_applied_rule;
for (var rule_id in this.applied_rules[field_id]){
//return the first applied rule we find
other_applied_rule = this.applied_rules[field_id][rule_id];
break;
}
return other_applied_rule;
};
/**
* determine if a field rule's conditions are satisfied and should therefore be applied
* @param {Object} rule - object containing all the field rule data
* @returns {boolean}
*/
Field_rules.prototype.should_rule_be_applied = function (rule){
var field_values = this.get_field_value(rule.usableFieldID),
that = this,
apply_rule = false;
//check if the field is set to one of these values
if(rule.comparator === 'equal') {
apply_rule = this.intersect_arrays(field_values, rule.value).length !== 0;
}
else if(rule.comparator === 'not_equal'){
apply_rule = this.intersect_arrays(field_values, rule.value).length === 0;
}
else if(rule.comparator === 'greater_than'){
apply_rule = field_values.some(function(value){
//if DOB or a Date field convert the date string to a JS Dates for comparison
if(that.is_date_field(rule.usableFieldID)){
value = new Date(value);
rule.value[0] = new Date(rule.value[0]);
} else {
value = parseFloat(value);
rule.value[0] = parseFloat(rule.value[0]);
}
if(value > rule.value[0]){
return true;
}
});
}
else if(rule.comparator === 'less_than'){
//check if field value is less than rule value
apply_rule = field_values.some(function(value){
//if DOB or a Date field convert the date string to a JS Dates for comparison
if(that.is_date_field(rule.usableFieldID)){
value = new Date(value);
rule.value[0] = new Date(rule.value[0]);
} else {
value = parseFloat(value);
rule.value[0] = parseFloat(rule.value[0]);
}
if(value < rule.value[0]){
return true;
}
});
}
else if(rule.comparator === 'blank'){
//this rule type doesn't have any possible values - only need to check if the field is blank
if(field_values.length !== 0){
//the field does have values so check if all of them are blank
apply_rule = field_values.every(function(value){
return (value.trim() === '');
});
}
else{
//no values - so the field is blank
apply_rule = true;
}
}
else if(rule.comparator === 'not_blank'){
//this rule type doesn't have any possible values - only need to check if the field is not blank
if(field_values.length !== 0){
//the field does have values so check if at least one of them isn't blank
apply_rule = field_values.some(function(value){
if(value.trim() !== ''){
return true;
}
});
}
else{
//no values - so the field is blank
apply_rule = false;
}
}
return apply_rule;
};
/**
* Add a field rule to the list of currently applied rules
* @param rule - object - the field rule to add
*/
Field_rules.prototype.add_to_applied_rules = function(rule){
//add the rule to the list of applied rules
if (!this.applied_rules.hasOwnProperty(rule.dependentUsableFieldID)){
this.applied_rules[rule.dependentUsableFieldID] = {};
}
this.applied_rules[rule.dependentUsableFieldID][rule.id] = rule;
};
/**
* Make the DOM changes necessary for the rule to be applied
* @param rule
*/
Field_rules.prototype.apply_rule = function(rule){
//do the dom manipulations as a result of the field rule being applied
if (rule.action === 'hide') {
this.hide_field(rule.dependentUsableFieldID);
} else if (rule.action === 'show') {
this.show_field(rule.dependentUsableFieldID);
} else if (rule.action === 'limit_options') {
this.limit_options(rule.dependentUsableFieldID, rule.options, false);
}
};
/**
* Remove the rule and make any necessary updates to the DOM (re-show a previously hidden field)
* @param {Object} rule - object containing all the field rule data
*/
Field_rules.prototype.remove_rule = function(rule){
var other_applied_rule,
use_default_options = false;
if (
this.applied_rules.hasOwnProperty(rule.dependentUsableFieldID)
&& this.applied_rules[rule.dependentUsableFieldID].hasOwnProperty(rule.id)
){
delete this.applied_rules[rule.dependentUsableFieldID][rule.id];
}
if (rule.action === 'hide'){
//if there are no other applied rules stating to hide this field, then we need to show it
if(! this.has_other_applied_rules(rule.dependentUsableFieldID, 'hide') ) {
this.show_field(rule.dependentUsableFieldID);
}
}
else if (rule.action === 'limit_options'){
if(this.has_other_applied_rules(rule.dependentUsableFieldID, 'limit_options') ) {
//ensure the field is visible
this.show_field(rule.dependentUsableFieldID);
//get any of the other applied rules for this field
other_applied_rule = this.get_any_applied_rule(rule.dependentUsableFieldID);
this.limit_options(rule.dependentUsableFieldID, other_applied_rule.options, use_default_options);
}
else {
//field should be reverted back to the default set of options
use_default_options = true;
this.limit_options(rule.dependentUsableFieldID, [], use_default_options);
}
}
};
/**
* Hide a field and it's parent elements.
* This done when a field rule's action is to hide a field, or if there are no options after limit_options action
* @param {Number|String} field_id
*/
Field_rules.prototype.hide_field = function (field_id){
var $field_inputs = $(this.form_fields[field_id]),
$first_input = $field_inputs.first(),
isBeeForm = $('.bee-page-container').length ? true : false;
// some of these fields have additional labels/inputs to hide when the form is a bee form
if (isBeeForm) {
$first_input.parents('.bee-form-row').hide()
if ($first_input.hasClass('iqs-form-phone-number')) {
$('#' + $first_input.attr('id') + '-country-code').parents('.bee-form-row').hide();
$('#' + $first_input.attr('id') + '-text-opt-in').parents('.bee-form-row').hide();
} else if ($first_input.hasClass('bee_high_school_ceeb')) {
$('#beeHighSchoolButton').parents('.bee-form-row').hide();
} else if ($first_input.hasClass('bee_college_ceeb')) {
$('#beeCollegeButton').parents('.bee-form-row').hide();
}
} else {
$first_input.parents(this.control_group_wrapper).hide();
}
// temporarily hide any required class but be prepared to add it back if we have to reshow this field
if ($first_input.hasClass('required')) {
$field_inputs.removeClass('required').addClass('required-hidden');
}
};
/**
* Show a field and it's parent elements.
* This done when fields rules that were hiding a field are no longer applied
* @param {Number|String} field_id
*/
Field_rules.prototype.show_field = function(field_id){
var $field_inputs = $(this.form_fields[field_id]),
$first_input = $field_inputs.first(),
isBeeForm = $('.bee-page-container').length ? true : false;
// some of these fields have additional labels/inputs to hide when the form is a bee form
if (isBeeForm) {
$first_input.parents('.bee-form-row').show()
if ($first_input.hasClass('iqs-form-phone-number')) {
$('#' + $first_input.attr('id') + '-country-code').parents('.bee-form-row').show();
$('#' + $first_input.attr('id') + '-text-opt-in').parents('.bee-form-row').show();
} else if ($first_input.hasClass('bee_high_school_ceeb')) {
$('#beeHighSchoolButton').parents('.bee-form-row').show();
} else if ($first_input.hasClass('bee_college_ceeb')) {
$('#beeCollegeButton').parents('.bee-form-row').show();
}
} else {
$first_input.parents(this.control_group_wrapper).show();
}
// reshow any required fields that were previously hidden
if ($first_input.hasClass('required-hidden')) {
$field_inputs.removeClass('required-hidden').addClass('required');
}
};
/**
* Limit the set of options available for select, multi-select, checkbox, and radio fields
* @param {Number|String} field_id
* @param {Array} options - the list of options to be available for a field
* @param {boolean} use_default_options - if true the default list of options for the field should be used
*/
Field_rules.prototype.limit_options = function (field_id, options, use_default_options){
var $field = $(this.form_fields[field_id]);
if(!use_default_options) {
// intersect this rule's list of options with the list of rules from other applied rules
options = this.get_field_option_list(field_id, options);
options = this.format_options(this.default_options[field_id], options)
}
else {
options = this.default_options[field_id]
}
if ($field.is('select')) {
this.update_select_options(field_id, options);
}
else if ($field.hasClass('iqs-form-checkbox') || $field.hasClass('iqs-form-radio')) {
this.update_checkbox_and_radio_options(field_id, options);
}
};
/**
* Return the list of options for a field after considering all other applied rules that limit options for the field
* @param {Number|String} field_id
* @param {Array} options - list of options to start filtering
* @returns {Array} - array containing the filtered list of options based on the applied field rules
*/
Field_rules.prototype.get_field_option_list = function(field_id, options){
if (this.applied_rules.hasOwnProperty(field_id)){
for (var rule_id in this.applied_rules[field_id]) {
if (this.applied_rules[field_id].hasOwnProperty(rule_id)) {
options = this.intersect_arrays(
this.applied_rules[field_id][rule_id].options,
options
);
}
}
}
return options;
};
/**
* Update the DOM to display the specified list of options for a select field
* @param {Number|String} field_id
* @param {Array} options - list of options to apply to the select field
*/
Field_rules.prototype.update_select_options = function (field_id, options){
var $field = $(this.form_fields[field_id]).first(),
old_value = $field.val(), //save the current value and will try to reselect it after new options are set
old_blank_value_text = $field.find('option[value=""]').text(),
option_html = '',
is_multi_select = $field.hasClass('iqs-form-multi-select'),
trigger_change_event = true; //should we trigger a field change event
if(options.length === 0){
this.hide_field(field_id);
if (is_multi_select && old_value) {
//check if there is at least one non blank value selected in the multi-select
trigger_change_event = old_value.some(function(option){
return option !== '';
});
}
else {
trigger_change_event = (old_value !== '');
}
//update the options for the select field
option_html += '';
$field.html(option_html);
//try to reselect the original value
$field.val('');
}
else {
if (!is_multi_select) {
trigger_change_event = (old_value !== '');
option_html += '';
}
//loop over the passed in formatted options and build the options html
options.forEach(function (option) {
for (var option_id in option) {
if (option.hasOwnProperty(option_id)) {
if (option[option_id] instanceof Array) {
option_html += '';
}
else {
option_html += '';
if (old_value == option_id) {
trigger_change_event = false;
}
}
}
}
});
//update the options for the select field
$field.html(option_html);
//try to reselect the original value
$field.val(old_value);
this.show_field(field_id);
}
//if the selected value has changed trigger a change event
if (trigger_change_event) {
$field.trigger('change.field_rules');
}
};
/**
* Update the DOM to display the specified list of options for a checkbox group or radio group field
* @param {Number|String} field_id
* @param {Array} options - list of options to apply to the select field
*/
Field_rules.prototype.update_checkbox_and_radio_options = function(field_id, options){
var $field_items = $(this.form_fields[field_id]),
that = this;
if(options.length === 0){
//hide the field
this.hide_field(field_id);
//uncheck all the selected items
$field_items.each(function (index, input){
var $input = $(input);
if($input.is(':checked')) {
$input.attr('checked', false);
$input.trigger('change.field_rules');
}
});
}
else {
$field_items.each(function() {
var input_value = $(this).val();
if(that.is_valid_checkbox_radio_option(options, input_value)){
that.show_checkbox_radio_label(field_id, input_value)
}
else {
that.hide_checkbox_radio_label(field_id, input_value)
}
});
this.show_field(field_id);
}
};
/**
* Return true if a specified value is the array of options
*
* @param options
* @param value
* @return {boolean}
*/
Field_rules.prototype.is_valid_checkbox_radio_option = function(options, value){
return options.some(function(option){
if(option.hasOwnProperty(value)){
return true;
}
});
};
/**
* Uncheck a specific option of a checkbox/radio field and hide it
* @param field_id
* @param value
*/
Field_rules.prototype.hide_checkbox_radio_label = function(field_id, value){
var $field_inputs = $(this.form_fields[field_id]),
$input = $field_inputs.filter('[value="' + value + '"]');
if($input.is(':checked')) {
$input.attr('checked', false);
$input.trigger('change.field_rules');
}
$input.parent('label').hide();
};
/**
* Show a specific option of a checkbox/radio field
* @param field_id
* @param value
*/
Field_rules.prototype.show_checkbox_radio_label = function(field_id, value){
var $field_inputs = $(this.form_fields[field_id]),
$input = $field_inputs.filter('[value="' + value + '"]');
$input.parent('label').show();
};
/**
* return the formatted and sorted list of options based on the array of filtered options
* @param {Array} default_formatted_options - array of default options for the the field
* @param {Array} filtered_options - array containing the filtered list of options
* @returns {Array} - the filtered list of options after being sorted and formatted
*/
Field_rules.prototype.format_options = function (default_formatted_options, filtered_options){
var formatted_options = [];
//loop through the default options and populate the formatted_options which will sort them as well
default_formatted_options.forEach(function(option, index){
//check if this option is in the filtered list of options
for(var option_id in option){
//check if this is a nested option
if(option[option_id] instanceof Array){
var temp = [];
temp[option_id] = [];
option[option_id].forEach(function(nested_option, nested_index){
if(filtered_options.indexOf(nested_option) !== -1){
//the option was in the list, so add it to the temp array
temp[option_id].push(nested_option);
}
});
if(temp[option_id].length !== 0){
formatted_options.push(temp);
}
}
else {
if (filtered_options.indexOf(option_id) !== -1) {
//the option was in the list, so add it to the formatted list
formatted_options.push(option);
}
}
}
});
return formatted_options;
};
return Field_rules;
}(jQuery));