/**/ ;(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: 'el', 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": 'Υπήρξε ένα σφάλμα κατά την επεξεργασία του αιτήματός σας.'}) } 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('Ευχαριστούμε!'); } } // 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 = "el".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 = 'Νομός...'; } else if (c == "AU") { var label = ''; } else if (c == "ZA" || c == "IT" || c == "TW" || c == "NZ") { var label = ''; } else if (c == "CA") { var label = ''; } else if (c == "GB") { var label = ''; } else { var label = ''; }; 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.' 'Υπήρξε ένα σφάλμα κατά την επεξεργασία του αιτήματός σας.' ); }; }; }; 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); }); }; });