//On page load $(function() { $('#checkout_same_address').sameAddress(); $('span#bcountry select').change(function() { update_state('b'); }); $('span#scountry select').change(function() { update_state('s'); }); get_states(); $('input#order_creditcards_attributes_0_number').blur(set_card_validation); // hook up the continue buttons for each section for(var i=0; i < regions.length; i++) { var section = regions[i]; $('#continue_' + section).click(function() { eval( "continue_button(this);") }); // enter key should be same as continue button (don't submit form though) $('#' + section + ' input').bind("keyup", section, function(e) { if(e.keyCode == 13) { continue_section(e.data); } }); } //disable submit $('div#checkout :submit').attr('disabled', 'disabled'); // hookup the radio buttons for registration $('#choose_register').click(function() { $('div#new_user').show(); $('div#guest_user, div#existing_user').hide(); }); $('#choose_existing').click(function() { $('div#existing_user').show(); $('div#guest_user, div#new_user').hide(); }); $('#choose_guest').click(function() { $('div#guest_user').show(); $('div#existing_user, div#new_user').hide(); }); var reg_choice = $('input[name=choose_registration]:checked').val(); if(reg_choice) { $('#choose_' + reg_choice).click(); } else { $('#choose_register').attr('checked', true); } // activate first region shift_to_region(regions[0]); }) jQuery.fn.sameAddress = function() { this.click(function() { if(!$(this).attr('checked')) { //Clear ship values? return; } $('input#hidden_sstate').val($('input#hidden_bstate').val()); $("#billing input, #billing select").each(function() { $("#shipping #"+ $(this).attr('id').replace('bill', 'ship')).val($(this).val()); }) update_state('s'); }) }; //Initial state mapper on page load var state_mapper; var get_states = function() { $.getJSON('/states.js', function(json) { state_mapper = json; $('span#bcountry select').val($('input#hidden_bcountry').val()); update_state('b'); $('span#bstate :only-child').val($('input#hidden_bstate').val()); $('span#scountry select').val($('input#hidden_scountry').val()); update_state('s'); $('span#sstate :only-child').val($('input#hidden_sstate').val()); }); }; // replace the :only child of the parent with the given html, and transfer // {name,id} attributes over, returning the new child var chg_state_input_element = function (parent, html) { var child = parent.find(':only-child'); var name = child.attr('name'); var id = child.attr('id'); //Toggle back and forth between id and name if(html.attr('type') == 'text' && child.attr('type') != 'text') { name = name.replace('_id', '_name'); id = id.replace('_id', '_name'); } else if(html.attr('type') != 'text' && child.attr('type') == 'text') { name = name.replace('_name', '_id'); id = id.replace('_name', '_id'); } html.addClass('required') .attr('name', name) .attr('id', id); child.remove(); // better as parent-relative? parent.append(html); return html; }; // TODO: better as sibling dummy state ? // Update the input method for address.state var update_state = function(region) { var country = $('span#' + region + 'country :only-child').val(); var states = state_mapper[country]; var hidden_element = $('input#hidden_' + region + 'state'); var replacement; if(states) { // recreate state selection list replacement = $(document.createElement('select')); var states_with_blank = [["",""]].concat(states); $.each(states_with_blank, function(pos,id_nm) { var opt = $(document.createElement('option')) .attr('value', id_nm[0]) .html(id_nm[1]); replacement.append(opt); if (id_nm[0] == hidden_element.val()) { opt.attr('selected', 'true') } // set this directly IFF the old value is still valid }); } else { // recreate an input box replacement = $(document.createElement('input')); if (! hidden_element.val().match(/^\d+$/)) { replacement.val(hidden_element.val()) } } chg_state_input_element($('span#' + region + 'state'), replacement); hidden_element.val(replacement.val()); // callback to update val when form object is changed // This is only needed if we want to preserve state when someone refreshes the checkout page // Or... if someone changes between countries with no given states replacement.change(function() { $('input#hidden_' + region + 'state').val($(this).val()); }); }; var continue_button = function(button) { continue_section(button.id.substring(9)); }; var continue_section = function(section) { // validate if (!validate_section(section)) { return; }; // submit var success = eval("submit_" + section + "();"); if (!success) { return; } // move to next section for(var i=0; i'; address += $('p#' + region + 'address input').val() + '
'; if($('p#' + region + 'address2').val() != '') { address += $('p#' + region + 'address2').val() + '
'; } address += $('p#' + region + 'city input').val() + ', '; if($('span#' + region + 'state input').length > 0) { address += $('span#' + region + 'state input').val(); } else { address += $('span#' + region + 'state :selected').html(); } address += ' ' + $('p#' + region + 'zip input').val() + '
'; address += $('p#' + region + 'country :selected').html() + '
'; address += $('p#' + region + 'phone input').val(); $('div#' + region + 'display div').html(address); return; }; var submit_shipping = function() { $('div#methods :child').remove(); $('div#shipping_method div.error').hide(); $('div#methods').append($(document.createElement('img')).attr('src', '/images/ajax_loader.gif').attr('id', 'shipping_loader')); // Save what we have so far and get the list of shipping methods via AJAX $.ajax({ type: "POST", url: 'checkout', beforeSend : function (xhr) { xhr.setRequestHeader('Accept-Encoding', 'identity'); }, dataType: "json", data: $('#checkout_form').serialize(), success: function(json) { update_shipping_methods(json.available_methods); }, error: function (XMLHttpRequest, textStatus, errorThrown) { $('div#methods :child').remove(); $('div#shipping_method div.error').show(); return false; } }); build_address('s'); return true; }; var submit_shipping_method = function() { //TODO: Move to validate_section('shipping_method'), but must debug how to validate radio buttons var valid = false; $('div#methods :child input').each(function() { if($(this).attr('checked')) { valid = true; } }); if(valid) { // Save what we have so far and get the updated order totals via AJAX $.ajax({ type: "POST", url: 'checkout', beforeSend : function (xhr) { xhr.setRequestHeader('Accept-Encoding', 'identity'); }, dataType: "json", data: $('#checkout_form').serialize(), success: function(json) { update_confirmation(json); }, error: function (XMLHttpRequest, textStatus, errorThrown) { // TODO - put some real error handling in here //$("#error").html(XMLHttpRequest.responseText); return false; } }); return true; } else { var p = document.createElement('p'); $(p).append($(document.createElement('label')).addClass('error').html('Please select a shipping method').css('width', '300px').css('top', '0px')); $('div#methods').append(p); return false; } }; var update_shipping_methods = function(methods) { $(methods).each( function(i) { $('div$methods img#shipping_loader').remove(); var p = document.createElement('p'); var s = this.name + ' ' + this.rate; var i = $(document.createElement('input')) .attr('id', this.id) .attr('type', 'radio') .attr('name', 'method_id') .val(this.id) .click(function() { $('div#methods input').attr('checked', ''); $(this).attr('checked', 'checked'); }); if($(methods).length == 1) { i.attr('checked', 'checked'); } var l = $(document.createElement('label')) .attr('for', this.id) .html(s); $('div#methods').append($(p).append(i).append(l)); }); $('div#methods input:first').attr('validate', 'required:true'); return; }; var update_confirmation = function(order) { $('span#order_total').html(order.order_total); $('span#ship_amount').html(order.ship_amount); $('span#tax_amount').html(order.tax_amount); $('span#ship_method').html(order.ship_method); }; var submit_registration = function() { // no need to do any ajax, user is already logged in if ($('div#already_logged_in:hidden').size() == 0) return true; var register_method = $('input[name=choose_registration]:checked').val(); $('div#registration_error').removeClass('error').html(''); if (register_method == 'existing') { ajax_login(); } else if (register_method == 'register') { ajax_register(); } return ($('div#registration_error').html() == ""); }; var ajax_login = function() { $.ajax({ async: false, type: "POST", url: '/user_session', beforeSend : function (xhr) { xhr.setRequestHeader('Accept-Encoding', 'identity'); }, dataType: "json", data: $('#checkout_form').serialize(), success: function(result) { if (result) { $('div#already_logged_in').show(); $('div#register_or_guest').hide(); update_login(); } else { registration_error("Invalid username or password."); }; }, error: function (XMLHttpRequest, textStatus, errorThrown) { registration_error("Unable to perform login due to a server error."); } }); }; var ajax_register = function() { $.ajax({ async: false, type: "POST", url: '/users', beforeSend : function (xhr) { xhr.setRequestHeader('Accept-Encoding', 'identity'); }, dataType: "json", data: $('#checkout_form').serialize(), success: function(result) { if (result == true) { $('div#already_logged_in').show(); $('div#register_or_guest').hide(); update_login(); } else { var error_msg = "Unable to register user"; for (var i=0; i < result.length; i++) { error_msg += "
"; error_msg += result[i][0] + ": " + result[i][1]; } registration_error(error_msg); }; }, error: function (XMLHttpRequest, textStatus, errorThrown) { registration_error("Unable to register due to a server error."); } }); }; var registration_error = function(error_message) { $('div#registration_error').addClass('error').html(error_message); }; var submit_payment = function() { return true; }; var submit_confirmation = function() { //$('form').submit(); $('#post-final').click(); }; // update login partial var update_login = function() { $.ajax({ url: '/user_session/login_bar', beforeSend : function (xhr) { xhr.setRequestHeader('Accept-Encoding', 'identity'); }, dataType: "html", success: function(result) { $("div#login-bar").html(result); }, error: function (XMLHttpRequest, textStatus, errorThrown) { // TODO (maybe do nothing) } }); }; /* validating card details */ /* this info is held in an array to get a strict order on the tests in case of overlap * (original info taken from activemerchant, but a better reference seems to be * http://en.wikipedia.org/wiki/Credit_card_number - though there's nothing definitive?) * this info could be used to tell AM what the correct type is... (and remove the hook...?) * switch now identified as maestro * TODO: finish this list * (from AM_protx, extra - ELECTRON = /^(424519|42496[23]|450875|48440[6-8]|4844[1-5][1-5]|4917[3-5][0-9]|491880)\d{10}(\d{3})?$/ */ var current_card_type = null; var card_regexps = [ [ 'Visa Electron' , /^(417500|(4917|4913|4508|4844)\d{2})\d{10}$/ ] , [ 'Visa' , /^4\d{12}(\d{3})?$/ ] , [ 'MasterCard' , /^(5[1-5]\d{4}|677189)\d{10}$/ ] // , [ 'discover' , /^(6011|65\d{2})\d{12}$/ ] , [ 'American Express' , /^(34|37)\d{13}$/ ] , [ 'Diners Club' , /^3(0[0-5]|[68]\d)\d{11}$/ ] , [ 'JCB' , /^35(2[89]|[3-8]\d)\d{12}$/ ] , [ 'Solo' , /^(6767|6334)\d{12}(\d{2,3})?$/ ] // , [ 'dankort' , /^5019\d{12}$/ ] , [ 'Maestro' , /^((5018|5020|5038|6304|6759|6761|4903|4905|4911|4936|6333|6759)\d{2}|564182|633100)\d{10,13}$/ ] // , [ 'forbrugsforeningen' , /^600722\d{10}$/ ] , [ 'Laser' , /^(6304|6706|6771|6709)\d{12,15}$/ ] ]; var card_type = function(number) { var name = null; $.each(card_regexps, function (i,e) { if (number.match(e[1])) { name = e[0]; return false; } }); return name; }; var set_card_validation = function () { if ($("#order_creditcards_attributes_0_number").val().match(/^\s*$/)) { $('#card_type').hide(); return; } current_card_type = card_type($("#order_creditcards_attributes_0_number").val()); $('#card_type').show(); $('#card_type #looks_like').hide(); $('#card_type #unrecognized').hide(); if (current_card_type == null) { $('#card_type #unrecognized').show(); current_card_type = "unknown"; } else { $('#card_type #looks_like #type').html(current_card_type); $('#card_type #looks_like').show(); } if (current_card_type.match(/unknown|maestro|solo|switch/i)) { $('p#maestro_extra').show('slow'); } else { $('p#maestro_extra').hide('slow'); $('p#maestro_extra input, p#maestro_extra select').val("") // clear the values } };