/* dCodes Framework */ var instant = instant || {}; (function ($) { 'use strict'; String.prototype.instant = Number.prototype.instant = $.fn.instant = function (callback, options) { var action = callback.split('.'), secondary; callback = action[0]; if (action.length > 1) { secondary = options; options = action[1]; } return typeof instant[callback] === 'function' ? instant[callback].call(this, options, secondary) : this; }; /** * DOMNodeInserted emulation (custom "instant" event) * * instant plugins love AJAX, and it's always awesome to not have the need to * call a plugin again when a new page has been loaded with AJAX. This can be * accomplished by listening to DOM mutation events. Unfortunately, * DOMNodeInserted is not supported in IE 7 and 8, and has been deprecated by * the W3C. A forceful workaround is to play with jQuery's DOM mutation * methods to trigger a custom event. * * @package instant * @since 1.3 */ $.each(['before', 'after', 'append', 'html'], function (k, v) { k = $.fn[v]; $.fn[v] = function (arg) { var result = k.apply(this, arguments); this.trigger('instant', [result, arg]); return result; }; }); instant.webstore = function (options) { var self = instant.webstore.prototype; // Register additional shopping cart containers. if (this.selector && (typeof options === 'string' || options.cartitem)) { $.each(this.selector.split(/\s*,\s*/), function (i, selector) { selector = selector.replace(/^\s+|\s+$/, ''); self.carts[selector] = (options.cartitem || options).toString(); }); } // Initialize the shopping cart and its options. if (!this.selector || typeof options === 'object') { self.init(options); } // Continue jQuery chaining. return this; }; instant.webstore.prototype = { // The initial cart. This contains an empty expiration time, shipping // method, discount code, country and region. cart: { items: [], timeout: null, shippingMethod: null, discountCode: null, country: null, region: null, each: function (callback) { var result, i, length = this.items.length; for (i = 0; i < length; i++) { result = callback.call(this.items[i], i); if (typeof result !== 'undefined' && result !== true) { return result; } } } }, // The list of shopping cart selectors and the HTML that will be used to // build each item. carts: {}, // A cached property to check if the shipping menu already has an item // in it. shippingMenuPopulated: false, // The cart's discount amount. discount: 0, // The cart's shipping cost. shipping: 0, // The cart's subtotal amount. subtotal: 0, // The cart's total quantity. quantity: 0, // The cart's tax amount. tax: 0, // The cart's total cost. total: 0, // Default option list. options: { // The list of class or data-attribute listeners for HTML elements. classes: { // The HTML tag that contains the cart's discount amount. cartdiscount: 'cart-discount', // HTML tag for the cart's total number of items. cartquantity: 'cart-quantity', // The cart's shipping cost. cartshipping: 'cart-shipping', // The cart's subtotal. cartsubtotal: 'cart-subtotal', // The cart's calculate tax amount. carttax: 'cart-tax', // The cart's total. carttotal: 'cart-total', // The field where a customer can type in a discount code. discount: 'discount', // The button that triggers emptying the shopping cart. empty: 'empty', // The class name or data attribute to store a product's ID/SKU. id: 'id', // Class or data attribute for a product's price. price: 'price', // The class name to identify a product container. product: 'product', // The class name to identify the button that adds an item. purchase: 'purchase', // Class or data attribute for a product's quantity. quantity: 'quantity', // Class name or data attribute for a product's title. title: 'title', // Class name to identify the cart's drop-down menu of shipping // options. shipping: 'shipping', // Class or data attribute to list a product's in stock amount. stock: 'stock', // The class name inside a single list item in the cart that // refers to the button that will remove the item. remove: 'remove' }, // Defines how prices should be formatted. For example, you can use // '$00,000.00' or '€ 00 000,00' or '€000' or any variation of // currency symbols and formats. currencyFormat: '$00,000.00', // The currency code used when checking out with a third-party // payment gateway, like PayPal. currencyCode: 'USD', // This can be a list of discount codes and the value which is // discounted. For example, { '10PERCENT': '10%', '10DOLLARS': 10 } // will discount 10% of the subtotal when entering "10PERCENT" into // a text field marked with a "discount" class, or $10 when entering // "10DOLLARS". discountCodes: false, // If this is set to a URL on your server, any discount code that is // entered into a "discount" text field will be sent to this URL // with a post request so you can determine if an amount should be // discounted. The response from your server should be a JSON string // with a "discount" index containing the amount to be discounted. discountURL: null, // Set this to true to use instant.webstore's experimental geolocation, // which utilizes services of geoplugin.com, a third-party provider. geolocation: false, // If set to true, instant will generate an ID for products based on // the options that are chosen. generateSKU: false, // The number of characters to set option names and values to when // generating a product SKU. generateLimit: 2, // An ISO-2 code used to determine the language available on the // payment gateway's checkout page. language: 'EN', // A list of additional properties that instant should look for in a // product container. For example, setting this to [ 'title' ] will // also look for a data-title attribute, or an element that has a // "title" class. properties: [], // Setting this to true will enable sandbox mode at the third-party // payment gateways. sandbox: false, // Can be a single amount or a list of shipping options and their // amounts. For example, you can offer various shipping carriers: // { 'USPS': 0.1, 'UPS': 0.15, 'FedEx': 0.17 } shippingRate: 0, // Set this to 'fixed', 'flat', 'range', or 'variable' to set the // shipping method. // 'flat' = flat rate shipping fee, 'fixed' = fee per item, 'range', or 'variable' = percentage based on cost of shopping cart (e.g. 0.10 = 10%) shippingType: 'variable', // This is a list of callback functions that are applied to each // matched shortcode in the item's cart HTML. A shortcode is a // string wrapped in curly brackets. For example, {title} will // trigger a callback function defined as "title". shortCodes: { pricesingle: function (product) { var c = this.options.classes; return this.formatPrice(product[c.price]); }, pricetotal: function (product) { var c = this.options.classes; return this.formatPrice(product[c.price] * product[c.quantity]); } }, // The list of storage methods in order of priority. If localStorage // is unavailable, instant attempts to use cookies. If cookies are // unavailable, instant attempts to user server-side sessions. If no // storage method is available, an error will be thrown. storage: 'local,cookie,session', // The identifying key name for the cart to be stored. storageName: 'instant_webstore', // When using server-side storage, this is the URL where instant sends // GET and POST requests. storageURL: null, // If set to true, calculated tax amounts are subtracted from the // subtotal to display the total amount of tax on each item. taxIncluded: false, // A single VAT or sales tax rate, multiple tax rates, or a list of // localities and their tax rates. Examples: // 0.07 // [ 0.035, 0.04, 0.02 ] // { 'US:NY': [ 0.0375, 0.04 ], 'FR': 0.23 } taxRate: 0, // Setting this option to true will also calculate tax on shipping. taxShipping: false, // The number of seconds until the shopping cart contents expire. timeout: 86400, // Triggers after a new item has been added to the cart. addItemAfter: function () {}, // Triggers before a new item has been added. addItemBefore: function () {}, // Triggers after the cart's HTML display has finished building. buildCartAfter: function () {}, // Triggers before the cart's HTML has started building. buildCartBefore: function () {}, // Triggers when the cart's discount is being calculated. calcDiscount: function () {}, // Triggers when the shipping amount is being calculated. calcShipping: function () {}, // Triggers when the subtotal is being calculated. calcSubtotal: function () {}, // Triggers when tax is being calculated. calcTax: function () {}, // Triggers when the total amount is being calculated. calcTotal: function () {}, // Triggers after the shopping cart has been emptied. emptyCartAfter: function () {}, // Triggers before the cart is emptied. emptyCartBefore: function () {}, // Triggers when the quantity of an item reaches the "stock" limit. itemSoldOut: function () {}, // Triggers when the cart has loaded after the page initially loads. ready: function () {}, // Triggers after a single item is removed from the cart. removeItemAfter: function () {}, // Triggers before an item is removed. removeItemBefore: function () {}, // Triggers when a customer changes to a different shipping option. shippingChanged: function () {}, // Triggers after an existing item is modified. updateItemAfter: function () {}, // Triggers before an existing item is modified. updateItemBefore: function () {} }, /** * Initializes a shopping cart. * * When this function is first run, various events are delegated to the * necessary HTML elements. This includes adding a non-existant cart to * the DOM, clicking on a purchase button, the "empty cart" button, * changing the shipping method, typing a code into the discount field, * clicking a "remove item" button, changing options for a single item, * and clicking any button that references a checkout method. * * It also sets up the storage method, builds the cart display for the * first time, and triggers the "ready" callback function. * * @since 2.0 * @param object options Configuration options */ init: function (options) { var self = this, o = this.options, c = o.classes, d = $(document), i; // Set up the cart configuration. $.extend(true, o, options); // Set up the storage method. this.saveCart = this.setStorageMethod(o.storage); if (this.saveCart) { this.webstore(); // Get the shopping cart. o.properties = $.merge([c.id, c.stock, c.price, c.quantity, o.title], o.properties); this.saveCart(true); if (this.cart.timeout === null) { this.cart.timeout = this.timeout(true); } // Experimental geolocation lookup. if (o.geolocation) { this.geolocation(); } // Check if the shipping menu is already populated. this.shippingMenuPopulated = !! $('select.' + c.shipping).children().length; $('input.' + c.discount).val(this.cart.discountCode); // Listen for specific events on marked HTML elements. d.bind('instant', 'build', $.proxy(this.listen, this)); d.delegate('.' + c.purchase, 'click', 'purchase', $.proxy(this.listen, this)); d.delegate('.' + c.empty, 'click', 'empty', $.proxy(this.listen, this)); d.delegate('select.' + c.shipping, 'change', 'shipping', $.proxy(this.listen, this)); d.delegate('input.' + c.discount, 'change', 'discount', $.proxy(this.listen, this)); // Listen for checkout events. for (i in this.checkout) { if (this.checkout.hasOwnProperty(i)) { d.delegate('.' + (this.options.classes[i] || i), 'click', i, $.proxy(this.checkout, this)); } } // Build the shopping cart. $.each(this.carts, function (cart) { d.delegate(cart + ' .' + c.remove, 'click', 'remove', $.proxy(self.listen, self)); d.delegate(cart + ' :input', 'change', 'cart-options', $.proxy(self.listen, self)); self.buildCart(cart); }); // Update total amounts. this.updateTotals(); o.ready.call(this); } }, /** * Builds the HTML for a single cart container. * * When setting up your shopping cart, you can register multiple carts * to have different displays. Each time a cart is updated, this * function is run for each registered cart. It will build the list of * items, and add them to an HTML unordered list, which is placed inside * of the cart's container. * * @since 2.0 * @param string cart The cart selector in the list of carts refering * to the cart being built */ buildCart: function (cart) { var self = this, o = this.options, html = this.carts[cart], list; cart = $(cart); if (cart.length && o.buildCartBefore.call(this, cart) !== false) { list = $('