// (c) Copyright 2009 Jaded Pixel. Author: Caroline Schnapp. All Rights Reserved. /* IMPORTANT: Ajax requests that update Shopify's cart must be queued and sent synchronously to the server. Meaning: you must wait for your 1st ajax callback to send your 2nd request, and then wait for its callback to send your 3rd request, etc. */ if ((typeof Shopify) === 'undefined') { Shopify = {}; } /* Override so that Shopify.formatMoney returns pretty money values instead of cents. */ Shopify.money_format = '${{amount}}'; /* Events (override!) Example override: ... add to your theme.liquid's script tag.... Shopify.onItemAdded = function(line_item) { $('message').update('Added '+line_item.title + '...'); } */ Shopify.onError = function(XMLHttpRequest, textStatus) { // Shopify returns a description of the error in XMLHttpRequest.responseText. // It is JSON. // Example: {"description":"The product 'Amelia - Small' is already sold out.","status":500,"message":"Cart Error"} var data = eval('(' + XMLHttpRequest.responseText + ')'); if (!!data.message) { alert(data.message + '(' + data.status + '): ' + data.description); } else { alert('Error : ' + Shopify.fullMessagesFromErrors(data).join('; ') + '.'); } }; Shopify.fullMessagesFromErrors = function(errors) { var fullMessages = []; jQuery.each(errors, function(attribute, messages) { jQuery.each(messages, function(index, message) { fullMessages.push(attribute + ' ' + message); }); }); return fullMessages } Shopify.onCartUpdate = function(cart) { alert('There are now ' + cart.item_count + ' items in the cart.'); }; Shopify.onCartShippingRatesUpdate = function(rates, shipping_address) { var readable_address = ''; if (shipping_address.zip) readable_address += shipping_address.zip + ', '; if (shipping_address.province) readable_address += shipping_address.province + ', '; readable_address += shipping_address.country alert('There are ' + rates.length + ' shipping rates available for ' + readable_address +', starting at '+ Shopify.formatMoney(rates[0].price) +'.'); }; Shopify.onItemAdded = function(line_item) { alert(line_item.title + ' was added to your shopping cart.'); }; Shopify.onProduct = function(product) { alert('Received everything we ever wanted to know about ' + product.title); }; /* Tools */ /* Examples of call: Shopify.formatMoney(600000, '€{{amount_with_comma_separator}} EUR') Shopify.formatMoney(600000, '€{{amount}} EUR') Shopify.formatMoney(600000, '${{amount_no_decimals}}') Shopify.formatMoney(600000, '{{ shop.money_format }}') in a Liquid template! In a Liquid template, you have access to a shop money formats with: {{ shop.money_format }} {{ shop.money_with_currency_format }} {{ shop.money_without_currency_format }} All these formats are editable on the Preferences page in your admin. */ Shopify.formatMoney = function(cents, format) { if (typeof cents == 'string') { cents = cents.replace('.',''); } var value = ''; var placeholderRegex = /\{\{\s*(\w+)\s*\}\}/; var formatString = (format || this.money_format); function defaultOption(opt, def) { return (typeof opt == 'undefined' ? def : opt); } function formatWithDelimiters(number, precision, thousands, decimal) { precision = defaultOption(precision, 2); thousands = defaultOption(thousands, ','); decimal = defaultOption(decimal, '.'); if (isNaN(number) || number == null) { return 0; } number = (number/100.0).toFixed(precision); var parts = number.split('.'), dollars = parts[0].replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1' + thousands), cents = parts[1] ? (decimal + parts[1]) : ''; return dollars + cents; } switch(formatString.match(placeholderRegex)[1]) { case 'amount': value = formatWithDelimiters(cents, 2); break; case 'amount_no_decimals': value = formatWithDelimiters(cents, 0); break; case 'amount_with_comma_separator': value = formatWithDelimiters(cents, 2, '.', ','); break; case 'amount_no_decimals_with_comma_separator': value = formatWithDelimiters(cents, 0, '.', ','); break; } return formatString.replace(placeholderRegex, value); } Shopify.resizeImage = function(image, size) { try { if(size == 'original') { return image; } else { var matches = image.match(/(.*\/[\w\-\_\.]+)\.(\w{2,4})/); return matches[1] + '_' + size + '.' + matches[2]; } } catch (e) { return image; } }; /* Ajax API */ // ------------------------------------------------------------------------------------- // POST to cart/add.js returns the JSON of the line item associated with the added item. // ------------------------------------------------------------------------------------- Shopify.addItem = function(variant_id, quantity, callback) { var quantity = quantity || 1; var params = { type: 'POST', url: '/cart/add.js', data: 'quantity=' + quantity + '&id=' + variant_id, dataType: 'json', success: function(line_item) { if ((typeof callback) === 'function') { callback(line_item); } else { Shopify.onItemAdded(line_item); } }, error: function(XMLHttpRequest, textStatus) { Shopify.onError(XMLHttpRequest, textStatus); } }; jQuery.ajax(params); }; // --------------------------------------------------------- // POST to cart/add.js returns the JSON of the line item. // --------------------------------------------------------- Shopify.addItemFromForm = function(form_id, callback) { var params = { type: 'POST', url: '/cart/add.js', data: jQuery('#' + form_id).serialize(), dataType: 'json', success: function(line_item) { if ((typeof callback) === 'function') { callback(line_item); } else { Shopify.onItemAdded(line_item); } }, error: function(XMLHttpRequest, textStatus) { Shopify.onError(XMLHttpRequest, textStatus); } }; jQuery.ajax(params); }; // --------------------------------------------------------- // GET cart.js returns the cart in JSON. // --------------------------------------------------------- Shopify.getCart = function(callback) { jQuery.getJSON('/cart.js', function (cart, textStatus) { if ((typeof callback) === 'function') { callback(cart); } else { Shopify.onCartUpdate(cart); } }); }; // --------------------------------------------------------- // GET cart/shipping_rates.js returns the cart in JSON. // --------------------------------------------------------- Shopify.getCartShippingRatesForDestination = function(shipping_address, callback) { var params = { type: 'GET', url: '/cart/shipping_rates.json', data: Shopify.param({'shipping_address': shipping_address}), dataType: 'json', success: function(response) { rates = response.shipping_rates if ((typeof callback) === 'function') { callback(rates, shipping_address); } else { Shopify.onCartShippingRatesUpdate(rates, shipping_address); } }, error: function(XMLHttpRequest, textStatus) { Shopify.onError(XMLHttpRequest, textStatus); } } jQuery.ajax(params); }; // --------------------------------------------------------- // GET products/.js returns the product in JSON. // --------------------------------------------------------- Shopify.getProduct = function(handle, callback) { jQuery.getJSON('/products/' + handle + '.js', function (product, textStatus) { if ((typeof callback) === 'function') { callback(product); } else { Shopify.onProduct(product); } }); }; // --------------------------------------------------------- // POST to cart/change.js returns the cart in JSON. // --------------------------------------------------------- Shopify.changeItem = function(variant_id, quantity, callback) { var params = { type: 'POST', url: '/cart/change.js', data: 'quantity='+quantity+'&id='+variant_id, dataType: 'json', success: function(cart) { if ((typeof callback) === 'function') { callback(cart); } else { Shopify.onCartUpdate(cart); } }, error: function(XMLHttpRequest, textStatus) { Shopify.onError(XMLHttpRequest, textStatus); } }; jQuery.ajax(params); }; // --------------------------------------------------------- // POST to cart/change.js returns the cart in JSON. // --------------------------------------------------------- Shopify.removeItem = function(variant_id, callback) { var params = { type: 'POST', url: '/cart/change.js', data: 'quantity=0&id='+variant_id, dataType: 'json', success: function(cart) { if ((typeof callback) === 'function') { callback(cart); } else { Shopify.onCartUpdate(cart); } }, error: function(XMLHttpRequest, textStatus) { Shopify.onError(XMLHttpRequest, textStatus); } }; jQuery.ajax(params); }; // --------------------------------------------------------- // POST to cart/clear.js returns the cart in JSON. // It removes all the items in the cart, but does // not clear the cart attributes nor the cart note. // --------------------------------------------------------- Shopify.clear = function(callback) { var params = { type: 'POST', url: '/cart/clear.js', data: '', dataType: 'json', success: function(cart) { if ((typeof callback) === 'function') { callback(cart); } else { Shopify.onCartUpdate(cart); } }, error: function(XMLHttpRequest, textStatus) { Shopify.onError(XMLHttpRequest, textStatus); } }; jQuery.ajax(params); }; // --------------------------------------------------------- // POST to cart/update.js returns the cart in JSON. // --------------------------------------------------------- Shopify.updateCartFromForm = function(form_id, callback) { var params = { type: 'POST', url: '/cart/update.js', data: jQuery('#' + form_id).serialize(), dataType: 'json', success: function(cart) { if ((typeof callback) === 'function') { callback(cart); } else { Shopify.onCartUpdate(cart); } }, error: function(XMLHttpRequest, textStatus) { Shopify.onError(XMLHttpRequest, textStatus); } }; jQuery.ajax(params); }; // --------------------------------------------------------- // POST to cart/update.js returns the cart in JSON. // To clear a particular attribute, set its value to an empty string. // Receives attributes as a hash or array. Look at comments below. // --------------------------------------------------------- Shopify.updateCartAttributes = function(attributes, callback) { var data = ''; // If attributes is an array of the form: // [ { key: 'my key', value: 'my value' }, ... ] if (jQuery.isArray(attributes)) { jQuery.each(attributes, function(indexInArray, valueOfElement) { var key = attributeToString(valueOfElement.key); if (key !== '') { data += 'attributes[' + key + ']=' + attributeToString(valueOfElement.value) + '&'; } }); } // If attributes is a hash of the form: // { 'my key' : 'my value', ... } else if ((typeof attributes === 'object') && attributes !== null) { jQuery.each(attributes, function(key, value) { data += 'attributes[' + attributeToString(key) + ']=' + attributeToString(value) + '&'; }); } var params = { type: 'POST', url: '/cart/update.js', data: data, dataType: 'json', success: function(cart) { if ((typeof callback) === 'function') { callback(cart); } else { Shopify.onCartUpdate(cart); } }, error: function(XMLHttpRequest, textStatus) { Shopify.onError(XMLHttpRequest, textStatus); } }; jQuery.ajax(params); }; // --------------------------------------------------------- // POST to cart/update.js returns the cart in JSON. // --------------------------------------------------------- Shopify.updateCartNote = function(note, callback) { var params = { type: 'POST', url: '/cart/update.js', data: 'note=' + attributeToString(note), dataType: 'json', success: function(cart) { if ((typeof callback) === 'function') { callback(cart); } else { Shopify.onCartUpdate(cart); } }, error: function(XMLHttpRequest, textStatus) { Shopify.onError(XMLHttpRequest, textStatus); } }; jQuery.ajax(params); }; if (jQuery.fn.jquery >= '1.4') { Shopify.param = jQuery.param; } else { Shopify.param = function( a ) { var s = [], add = function( key, value ) { // If value is a function, invoke it and return its value value = jQuery.isFunction(value) ? value() : value; s[ s.length ] = encodeURIComponent(key) + "=" + encodeURIComponent(value); }; // If an array was passed in, assume that it is an array of form elements. if ( jQuery.isArray(a) || a.jquery ) { // Serialize the form elements jQuery.each( a, function() { add( this.name, this.value ); }); } else { for ( var prefix in a ) { Shopify.buildParams( prefix, a[prefix], add ); } } // Return the resulting serialization return s.join("&").replace(/%20/g, "+"); } Shopify.buildParams = function( prefix, obj, add ) { if ( jQuery.isArray(obj) && obj.length ) { // Serialize array item. jQuery.each( obj, function( i, v ) { if ( rbracket.test( prefix ) ) { // Treat each array item as a scalar. add( prefix, v ); } else { Shopify.buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, add ); } }); } else if ( obj != null && typeof obj === "object" ) { if ( Shopify.isEmptyObject( obj ) ) { add( prefix, "" ); // Serialize object item. } else { jQuery.each( obj, function( k, v ) { Shopify.buildParams( prefix + "[" + k + "]", v, add ); }); } } else { // Serialize scalar item. add( prefix, obj ); } } Shopify.isEmptyObject = function( obj ) { for ( var name in obj ) { return false; } return true; } } /* Used by Tools */ function floatToString(numeric, decimals) { var amount = numeric.toFixed(decimals).toString(); if(amount.match(/^\.\d+/)) {return "0"+amount; } else { return amount; } } /* Used by API */ function attributeToString(attribute) { if ((typeof attribute) !== 'string') { // Converts to a string. attribute += ''; if (attribute === 'undefined') { attribute = ''; } } // Removing leading and trailing whitespace. return jQuery.trim(attribute); }