/**/
;(function(factory){
if (typeof define === 'function' && define.amd) {
define(['jquery'], factory);
} else if (typeof exports !== 'undefined') {
module.exports = factory(require('jquery'));
} else {
factory(jQuery);
}
})(function($){
// EU country list
// https://www.binarymoon.co.uk/2012/05/eu-country-codes/
var gcui_eu_country_list = { AL: 1, AD: 1, AM: 1, AT: 1, BY: 1, BE: 1, BA: 1, BG: 1, CH: 1, CY: 1, CZ: 1, DE: 1, DK: 1, EE: 1, ES: 1, FO: 1, FI: 1, FR: 1, GB: 1, GE: 1, GI: 1, GR: 1, HU: 1, HR: 1, IE: 1, IS: 1, IT: 1, LT: 1, LU: 1, LV: 1, MC: 1, MK: 1, MT: 1, NO: 1, NL: 1, PL: 1, PT: 1, RO: 1, RU: 1, SE: 1, SI: 1, SK: 1, SM: 1, TR: 1, UA: 1, VA: 1};
// Prevent console.log() from breaking IE and other old browsers
if ( ! window.console ) console = { log: function(){} };
/*
* The constructor function for FormControl
*/
function FormControl(element, settings) {
this.defaults = {
logLevel: 'error',
locale: 'da',
form: {} // Defaults to empty
};
// We create a new property to hold our default settings after they
// have been merged with user supplied settings
this.settings = $.extend({},this,this.defaults,settings);
// This object holds values that will change as the plugin operates
this.initials = {
form: {},
countryList: {}
};
// Attaches the properties of this.initials as properties of FormControl
$.extend(this,this.initials);
// Here we'll hold a reference to the html tag of the document
this.$el = $('html');
// We'll call our initiator function to get things rolling!
this.init();
}
/**
* Called once per instance
* Calls starter methods and associate the '.zippy-carousel' class
* @params void
* @returns void
*/
FormControl.prototype.init = function(){
// Add a class to the body so we can add styles
this.$el.addClass('FormControl');
// Pull in the logger library as we can have good logging that doesn't
// break browsers and preserves stack traces (loglevel.js)
this.log = window.log.noConflict();
this.log.setLevel(this.settings.logLevel);
// Bind any events to make stuff happen on the real
this.activate();
// Register the events. Let the user click stuff to make things happen
this.events();
};
/**
* Activates the initial store stuff when the page loads
* @params void
* @returns void
*
*/
FormControl.prototype.activate = function(){
// Load in the form data if any is set
this._loadForm();
};
/**
* Associate event handlers to events
* For all events we'll add them in here.
* @params void
* @returns void
*
*/
FormControl.prototype.events = function(){
// It's like that and like this and like that and uh...
var that = this;
// I WISH THE BELOW WASN'T SO HORRIBLE LOOKING. BUT IT WORKS
/**
* ATTACH LISTENERS FOR COUNTRY AND STATE FIELDS
*
* These listen for changes on the country and state fields which
* will fire all the updates on the lists themselves.
*/
if (! $.isEmptyObject(this.form)) {
var that = this;
var form = this.form;
if (this._isset(form.CountryCode)) {
$('#'+form.CountryCode.Id).change(function() {
that.UpdateStateList(
form.StateProvince.Id,
form.CountryCode.Id
);
});
// When the state changes we should check if "other" was selected
// and handle appropriately
$('#'+form.StateProvince.Id).change(function() {
that._checkForOtherStateValAndHandle(
that.form.StateProvince.Id
);
});
}
}
// Do the captcha validation thing.
$(window).on('keydown', function(){
if ($('#'+form.Captcha.Id).val() == false) {
$('#'+form.Captcha.Id).val('V@3fsasdfasdfAAdgf9J*');
}
});
$(window).on('click', function(){
if ($('#'+form.Captcha.Id).val() == false) {
$('#'+form.Captcha.Id).val('V@3fsasdfasdfAAdgf9J*');
}
});
};
/**
* Submit a form.
*
* Once the form as been validated by the validator this will submit the form data to whatever
* endpoint indicated
*/
FormControl.prototype.SubmitToReaches = function() {
this._logger('Submitting form data from #' + this.settings.formId + ' to: ' + this.settings.action, "info");
var that = this;
var formData = new FormData(document.getElementById(this.settings.formId));
var itemCode = formData.get('textItemCode');
var countrySelected = formData.get('countryCode');
if (gcui_eu_country_list[countrySelected]) {
if(itemCode && itemCode.match(/beginningbookscatalog-2007.(.*).catalog/)) {
if(itemCode && itemCode.match(/beginningbookscatalog-2007.(da|de|el|es_ES|fr|iw|hu|it|ja|nl|no|pt|ru|sv).catalog/)) {
itemCode = itemCode.replace(/beginningbookscatalog-2007.(da|de|el|es_ES|fr|iw|hu|it|ja|nl|no|pt|ru|sv).catalog/, "beginningbookscatalog-2007-nep.$1.catalog");
formData.set('textItemCode', itemCode);
}
if(countrySelected == "RU") {
formData.set('textItemCode', 'beginningbookscatalog-2007-russia.ru.catalog');
} else if(countrySelected == "BE" && itemCode == "beginningbookscatalog-2007.fr.catalog") {
formData.set('textItemCode', 'beginningbookscatalog-2007-beligum.fr.catalog');
}
}
}
// The settings here allow the FormData() object to override the content-type header that
// jQuery defaults to. This is very important when submitting to reaches.
$.ajax({
url: this.settings.action,
data: formData,
processData: false,
contentType: false,
method: 'POST',
success: function(data){
// The SUCCESS response defaults to the string "SUCCESS"
that._logger("Success: " + data, "info");
that._handleSubmissionResponse(data);
},
error: function(data){
if (data.responseText) {
that._displayError({"code": 1000,"error": data.responseText});
}
else {
// 1000 errors present data returned by the server to the client. If th error is not 1000 its going to
// present a generic error code.
that._displayError({"code": 0, "error": 'Der var en fejl ved behandlingen af din forespørgsel.'})
}
that._logger("Failure:", "error");
that._logger(JSON.stringify(data), "error");
}
});
};
/**
* Submit a form.
*
* Once the form as been validated by the validator this will submit the form data to whatever
* endpoint indicated
*/
FormControl.prototype._handleSubmissionResponse = function(result) {
this._logger("Handling response from form submission: " + result, "info");
// SUCCESS RESULT
if (/^SUCCESS/.test(result)) {
// Redirect on success if this option is set
if (this._isset(this.settings.successUrl) && this.settings.successUrl) {
window.location = this.settings.successUrl;
}
// If there is a success message box, display success message
else if ($('#form_submission_success_message').length) {
$("#"+this.settings.formId).addClass('hidden');
$("#form_submission_success_message").removeClass('hidden');
}
// Otherwise, display generic message
else {
alert('Tak!');
}
}
// SOMETHING ELSE
else {
// Put the form in to "processing" state
var $formButton = $("#"+this.settings.formId + " button");
$formButton.prop('disabled', false).
html($formButton.data('default-text'));
}
};
/**
* UPDATE STATE LIST
*
* When the state list needs to be updated, pass in the id's of the
* state list and corresponding country list ID for a good time.
*/
FormControl.prototype.UpdateStateList = function(listId, cntyId) {
this._logger('Updating the state list for #' + listId, 'info');
$('#'+listId).addClass('disabled').attr('disabled', 'disabled');
$('#'+listId).parent().addClass('updating');
$('#'+listId+' option').remove();
this._setStateSelect(listId, $('#'+cntyId).val());
};
/**
* Load the form into the form object
*/
FormControl.prototype._loadForm = function() {
// Test if the form is set on the page
if ( ! $.isEmptyObject(this.settings.form)) {
// Transfer the form from settings to its own place
this.form = this.settings.form;
// Since we have a form and there is an address set on it
// we should call in the countries list to be loaded on the
// select option.
if (this._isset(this.form.CountryCode)) {
if (this._isset(this.form.CountryCode.Id)) {
this._loadCountries();
};
};
};
};
/*
* Check if the coutries are actually set, if not pull them down from the
* CDN and re-call this function with the countries inside. That will
* then skip the ajax step and move on to stuffing the FormControl.countryList
* object with the data we got back.
*/
FormControl.prototype._loadCountries = function(countries) {
var that = this;
if ( ! this._isset(countries)) {
this._logger('Loading countries...', 'info');
var url = "https://sd.ondemandhosting.info/lookups/country_list.html";
var locale = that.settings.locale.split('_');
var lang = locale[0];
$.ajaxq('FormControlq', {
context: this,
url: url+"?locale="+lang+"&callback=FormControl._loadCountries",
dataType: "script",
failure: function(errMsg) { this._throwError(errMsg) }
});
// If the countries argument is defined we're assuming the country
// list is being passed in via the callback in the URL, so we throw it
// on the form.
} else {
this.countryList = countries;
this._logger('Countries loaded successfully:', 'info');
this._logger(countries);
var form = this.form;
// Set for Country
if (cntyId = form.CountryCode.Id) {
this._logger('Setting CountryCode...');
var stateId = form.StateProvince.Id
this._setGeoSelectElementPair(cntyId, stateId);
};
};
};
/**
* SET GEO SELECT ELEMENT PAIR
*
* I know this has a crazy name but at least it hints as what it's for.
* Basically we are going to call this on multiple elements on the same
* page in succession so I wanted to keep it DRY by putting this
* into a function. It's for country and state dropdowns (pairs)
*/
FormControl.prototype._setGeoSelectElementPair = function(cntyId, stateId) {
country = this._setCountrySelect(cntyId);
// Now trigger the state list update
if (this._isset(stateId) && stateId != false) {
this._setStateSelect(stateId, country);
};
};
/**
* Basically build the country option set and append it to the
* select element by ID as pased in.
*/
FormControl.prototype._setCountrySelect = function(cntyId) {
var cntySet = this._createOptionSet(this.countryList, true);
var $list = $('#' + cntyId);
// Add the option set of countries to the select element
$list.append(cntySet);
// Now select the country that was last set on the list
var country = $list.data('last-country-code');
if (country) {
this._logger('Last country was '+country+', setting it.');
$list.val(country.toUpperCase());
} else {
this._logger('No last country set, falling back to locale...');
var ll = "da".split("_");
if (ll.length > 1) {
country = ll[1].toUpperCase();
this._logger('Locale found: ' + ll[1]);
$list.val(ll[1].toUpperCase());
} else {
country = "US";
this._logger('Locale not found, defaulting to "US".');
$list.val("US");
};
};
// Finally, we should update the last-country-set data attr so if we
// update this again on the same pageload, we won't reset user input
$list.attr('data-last-country-code', country);
$list.removeAttr('disabled');
return country;
};
// This function should be called any time the country changes
FormControl.prototype._setStateSelect = function(listId, country) {
var that = this;
var rand = parseInt(Math.random()*(99999999-10000000)+10000000);
var callback = '_stateList_' + rand;
this._logger('Name of the callback is: '+callback);
// This is the callback that will then update the correct state list
// I'm setting this FIRST to make sure it's there when the call comes back
window[callback] = function(stateList) {
that._logger('Got the states for #'+listId);
// Since the state list comes back with country prefixes on the state
// abbreviations, we need to strip them off, with this doohickey
var o = {};
$.each(stateList, function(code, name) {
var parts = code.split('-');
var abbr = parts[1];
o[abbr] = name;
});
stateList = o;
var stateSet = that._createOptionSet(stateList, true);
var $list = $('#' + listId);
// Add the option set of countries to the select element
$list.append(stateSet);
// Now select the state that was last set on the list if present
// and also make sure it's an option on the new list.
var last_state = $list.data('last-state');
if (last_state) {
that._logger('Last state was '+last_state+', checking if it\'s here.');
var last_state_in_list = false;
$.each(stateList, function(code, name) {
if (code == last_state.toUpperCase()) {
$list.val(last_state.toUpperCase());
last_state_in_list = true;
return;
};
});
}
// @TODO Make this somehow more elegant...
// Now let's add a little localization. We should set the first
// item in the list as a label for the state/region and use the
// local name for it (State, Territory, Region, Province, etc.)
var c = country;
if (c == "US" || c == "MX" || c == "BR" || c == "DE") {
var label = 'Stat ...';
} else if (c == "AU") {
var label = 'Amt/stat ...';
} else if (c == "ZA" || c == "IT" || c == "TW" || c == "NZ") {
var label = 'Provins ...';
} else if (c == "CA") {
var label = 'Provins/område ...';
} else if (c == "GB") {
var label = 'Kommune ...';
} else {
var label = 'Region';
};
if (last_state || last_state_in_list) {
$list.prepend('');
} else {
$list.prepend('');
}
$list.append('');
// Finally, we should update the last-state data attr so if we
// update this again on the same pageload, we won't reset user input
$list.attr('data-last-state', last_state);
$list.removeClass('disabled').removeAttr("disabled");
$list.parent().removeClass('updating');
};
// Make the call to get the states list by country id and let the callback
// do all the heavy lifting
this._loadStateProvinces(country, callback);
};
/**
* LOAD STATE/PROVINCES
*
* This is similar to _loadCountires() but for states on those countries.
* Right now it's called by _setStateSelect as that function attaches a
* unique callback to window that will take the state list it gets and
* makes the select dropdown for that form element specifically.
*
*/
FormControl.prototype._loadStateProvinces = function(data, callback) {
this._logger('Getting states for '+ data +'...', 'info');
var url = "https://sd.ondemandhosting.info/lookups/state_list.html";
this._logger("URL: "+url+"?countryCode=" + data + "&callback=" + callback);
$.ajaxq('FormControlq', {
context: this,
url: url+"?countryCode=" + data + "&callback=" + callback,
dataType: "script",
failure: function(errMsg) { this._throwError(errMsg) }
});
};
/**
* CREATE OPTION SET
* Since we're doing this on multiple dropdowns in the form we shoud just put
* the iterator here in one place.
*
* Takes a key-value pair of the data you want to make options with.
* Example: {IN:"Indiana"}
*/
FormControl.prototype._createOptionSet = function(map, sort) {
this._logger(map);
// Sort the text values because they come as sorted by keys. Lame.
if (sort) {
map = this._sortProperties(map);
};
// We make a documentFragment instead of using tons of jQuery.append's
// as this was proven to be 3-4x faster for large lists per John Resig:
// http://ejohn.org/blog/dom-documentfragments/
var optSet = document.createDocumentFragment();
$.each(map, function(code, name) {
var option = document.createElement("option");
option.textContent = name;
option.value = code;
optSet.appendChild(option);
});
return optSet;
};
// This is just quick and dirty. This is called by a listener on the state
// field ang checks if the value is "other". If so, do some magical stuff
// to create an input field to specify what is meant by "other".
FormControl.prototype._checkForOtherStateValAndHandle = function(listId) {
this._logger('Calling _checkForOtherStateValAndHandle', 'info');
var $list = $('#'+listId);
var listVal = $list.val();
if (listVal == "other") {
// Check if there is already an "other" input field there
if ( ! $('#'+listId+'_other').length) {
this._logger('There\'s no "other" input and it is selected. Making one...');
var $listParent = $list.parent();
// Create a new input for alt
var input = document.createElement("input");
input.type = "text";
input.setAttribute("id", listId + "_other");
input.setAttribute("style",
"display: inline-block; width: 68%; float: left; margin-left: 2.5%"
);
input.setAttribute("class", "form-control");
// Make room for the new input by shrinking the existing list
$list.css({
width: '29.5%',
float: 'left'
}).addClass('otherState');
// Slam it into the little container for the stae field
$listParent.append(input);
$('#'+listId + "_other").focus();
this._logger('done.');
};
} else {
this._logger('The selected state is not "other"');
// So now we know that the user selected something other than
// "other"
if ($list.hasClass('otherState')) {
this._logger('Removing "other" text input');
$list.removeClass('otherState pull-left').css({width:'',float:''});
$('#'+listId+'_other').remove();
};
};
};
/**
* UTILITIES
* ==============================
*/
/**
* Console.log shortcut
* This function utilizes a library called loglevel.js. It's a small tool
* that handles console.* way better than a lot of other things.
*/
FormControl.prototype._logger = function(data, level) {
/* */
this.log.table = function(data) {
if (typeof console.table !== "undefined") {
console.table(data);
}
else {
this.log.info(data);
}
}
var prefix = "FormControl:";
data = prefix+" "+data;
switch (level) {
case 'info': this.log.info(data); break;
case 'warn': this.log.warn(data); break;
case 'trace': this.log.info(trace); break;
case 'table': this.log.table(data); break;
case 'error': this.log.error(data); break;
default: this.log.debug(data);
}
};
// Make a custom error type
FormControl.prototype._throwError = function(msg) {
var err = Error.call(this, msg);
err.name = "FormControlError";
return err;
};
FormControl.prototype._displayError = function(data) {
if (data.error) {
console.error(data.error);
var err = data.error;
var alertElement;
// ErrCode 1000
if (err.code == 1000) {
for (var i = err.data.length - 1; i >= 0; i--) {
this._buildErrorMessage(
err.data[i].Code,
err.data[i].Message
);
// Log the fact in the console
this._logger('FormControl Error '+
err.data[i].Code +': '+
err.data[i].Message, 'error');
};
} else {
// This is probably a lower level error that would make no
// sense to the end-user. We can log this in the console and
// set a generic error message for the user.
// Log the fact in the console
this._logger('FormControl Error: '+
err, 'error');
this._buildErrorMessage(
'generic_err',
// 'An error occured. Please refresh the page or come back later.'
'Der var en fejl ved behandlingen af din forespørgsel.'
);
};
};
};
FormControl.prototype._buildErrorMessage = function(code, message) {
var alert = document.createElement('li');
alert.setAttribute('class', 'alert alert-danger alert-dismissible');
alert.setAttribute('id', code);
alert.setAttribute('role', 'alert');
alert.appendChild(document.createTextNode(message));
var btn = document.createElement('button');
btn.setAttribute('type', 'button');
btn.setAttribute('class', 'close');
btn.setAttribute('data-dismiss', 'alert');
btn.setAttribute('aria-label', 'Close');
var x = document.createElement('span');
x.setAttribute('aria-hidden', 'true');
x.appendChild(document.createTextNode('×'));
btn.appendChild(x);
alert.appendChild(btn);
if (code == 'jsvalidate_msg') {
$('#error_box #jsvalidate_msg').remove();
$('#error_box').append(alert);
} else if ( ! $('#error_box #'+code).length) {
$('#error_box').append(alert);
};
// Animate scroll to error box on page
var smscroll = new SmoothScroll();
var smanchor = document.querySelector('#error_box');
smscroll.animateScroll(smanchor, false, {speed: 500, offset: 50, easing: 'easeOutQuint',updateURL:false});
};
// Check if a variable is defined in any way
FormControl.prototype._isset = function(v) {
return (typeof v !== 'undefined') ? true : false;
};
/**
* Sort object properties (only own properties will be sorted).
* param object obj object to sort properties
* param bool isNumericSort true - sort object properties as numeric value
* returns object
*/
FormControl.prototype._sortProperties = function(obj, isNumericSort)
{
isNumericSort=isNumericSort || false; // by default text sort
var sortable=[];
for(var key in obj) {
if(obj.hasOwnProperty(key))
sortable.push([key, obj[key]]);
if(isNumericSort) {
sortable.sort(function(a, b) {
return a[1]-b[1];
});
} else {
sortable.sort(function(a, b) {
var x=a[1].toLowerCase(),
y=b[1].toLowerCase();
return xy ? 1 : 0;
});
}
}
// Convert back to object
obj = {};
$.each(sortable, function(i, arr) {
obj[arr[0]] = arr[1];
});
sorted = obj;
return sorted;
};
/**
* Initialize the plugin once for each DOM object passed to jQuery
* @params object options object
* @returns void
*
*/
$.fn.FormControl = function(options){
return this.each(function(index,el){
el.FormControl = new FormControl(el,options);
});
};
});