====== FoxyCart's JavaScript ====== FoxyCart's JavaScript files are full of goodies that you probably won't ever need to think twice about. But if default configurations are an anathema to you, if you need to get advanced for a custom integration, or if you're just curious about what's under the hood please explore this page and [[http://forum.foxycart.com|let us know]] if you have any questions. **WARNING:** This page is pretty dense. Again, let us know if you need assistance. The FoxyCart javascript is modular, and has a unified event system that's easy to hook into. The same javascript is used on your own site as well as on the FoxyCart-hosted cart, checkout, and receipt pages. If you want to hook into anything that FoxyCart does, you can use the FoxyCart events to do so. ===== Common Examples & Helpful Helpers ===== ==== Making a JSONP Cart Request ==== If you like to add a product to the cart behind the scenes or with your own custom behaviors, there's a simple helper to make the request and get back the JSON you need to update things on your end. See [[v:2.0:json|the JSONP page]] for more information on this. ==== Resetting the FoxyCart Session & Cookies ==== If you'd like to completely clear out the FoxyCart session, you can call this. FC.session.reset(); ==== Validating an Input ==== Let's say you have a specific form with ''id="my-specific-form"'' and an input inside that form with ''name="date"''. You could add this block of javascript (below) to your page to require a value on the ''date'' input. ==== "Mini-Cart" Display and HTML Helper Functionality ==== If you'd like to have a persistent display showing your website visitor their current cart total (and a quick link to open the cart), you'd add something like this:

0 item items in cart. Total cost: $ 0

The ''data'' attributes are the key. Stick those on any HTML element and it'll be updated automatically. * ''%%data-fc-id="minicart"%%'': If the ''FC.json.item_count'' is greater than 0, these elements will be shown. Otherwise they'll be hidden. * ''%%data-fc-id="minicart-empty"%%'': If the ''FC.json.item_count'' is 0 (the cart is empty), these elements will be shown. Otherwise they'll be hidden * ''%%data-fc-id="minicart-quantity"%%'': The inner HTML will be replaced by the value in ''FC.json.item_count''. * ''%%data-fc-id="minicart-order-total"%%'': The inner HTML will be replaced by the value in ''FC.json.total_price'', formatted using the ''_currency_format'' function (which adds decimals but doesn't add currency symbols). * ''%%data-fc-id="minicart-singular"%%'': The inner HTML will be shown if value in ''FC.json.item_count'' is equal to 1, and is hidden otherwise. It can be useful if you want to show "Item/Product" instead of "Items/Products" for ''FC.json.item_count'' = 1. * ''%%data-fc-id="minicart-plural"%%'': The inner HTML will be shown if value in ''FC.json.item_count'' is greater than 1 or less than 1 (zero), and is hidden otherwise. ===== Events: What they are and how to use them ===== Any time FoxyCart's javascript does anything you might need to hook into (do something before or after), we try to make that available as an event. The best way to understand this is by example. Let's say you need to attach the FoxyCart session ID to some custom cart links. You can't do that on document ready / loaded event. To do this, you'd attach some custom code to the FoxyCart ''ready.done'' event, like this: FC.client.on('ready.done', function() { // Custom code to attach the session ID to your links. }); Another common example is product form validation that prevents a cart add from happening if required fields haven't been input. That'd look like this: FC.client.on('cart-submit', function(params, next){ // Check the product }); ==== Available Events ==== * Template events. * ''render.done'': This event gets called //after// the template is updated. It gets passed an object with ''selector'' and ''block_id'' attributes. So ''FC.client.on('render.done', function(params){});'', where ''params.selector'' and ''params.block_id'' exist. * Sidecart events. * ''sidecart-show'': When the sidecart is displayed. * ''sidecart-hide'': When the sidecart is hidden. * ''sidecart-detach'': After the sidecart is hidden, the sidecart is actually detached from the DOM and this event fires. * Cart events. * ''cart-submit'': When an item is added to the cart. * ''cart-item-quantity-update'': When a quantity is changed in the cart * ''cart-item-remove'': When a quantity is removed from the cart * ''cart-coupon-add'': When a coupon or gift card is added * ''cart-coupon-remove'': When a coupon is removed * ''cart-gift-card-remove'': When a gift card is removed * ''cart-update'': Called when the cart is updated. Things like rendering Twig templates or setting the FC.json. * ''cart-shipping-options-update'': Called when the shipping options are updated * Checkout events. * ''checkout-submit'': Called when the checkout is submitted * ''checkout-submit-enable'': Triggered when the checkout submit button is re-enabled (if a checkout submit attempt is stopped due to validation errors). * ''checkout-submit-disable'': Called when checkout submit button is clicked, disabling the button and triggering validations to run. * ''checkout-shipping-options-update'': Called when the shipping options are updated on the checkout * Customer events. Called on the cart and the checkout. * ''customer-email-update'': Called when an email address change triggers the email to be checked * ''customer-login'': Called when a returning customer has successfully logged in. * ''customer-address-update'': Triggered when a complete address is entered for an address type (like billling/shipping etc). * ''customer-address-change'': Triggered when any of the customer address fields had been changed. ''params'' should include address type, like shipping/billing/multiship. * ''customer-shipping-option-select'': called when a customer selects a shipping rate. ==== Accessing the FC object on your own website ==== The above events are available on your own site, but because the FoxyCart javascript loads asynchronously, the ''FC'' object (and thus all events) aren't defined on pageload or document ready. To work around this, you can define a method at ''FC.onLoad''. The ''FC.onload'' method will be executed prior to the ''FC.client.init()''. Here's a quick example: var FC = FC || {}; FC.onLoad = function () { FC.client.on('ready.done', function () { // Your custom code goes here. }); }; Please note that this won't execute properly if you've got it inside a jQuery ''$(document).ready()'' call. ==== Event usage examples ==== Note that .on() and .off() methods can be chained. === Binding === FC.client.on("cart-item-remove", cartItemRemoveHandler) .on("cart-item-remove.done", cartItemRemoveDoneHandler) .on("cart-submit", cartSubmitHandler) .on("cart-submit.done", cartSubmitDoneHandler); === Unbinding === FC.client.off("cart-item-remove", cartItemRemoveHandler) .off("cart-submit", cartSubmitHandler); === Pausing and canceling the event queue === == Synchronous handlers == Return ''false'' to stop processing the event queue. FC.client.on("cart-item-remove", function (params) { if (params.itemPrice == 0) { alert("You cannot remove free products from your cart"); return false; } }); == Asynchronous handlers == Declare your handler as a function with two parameters to convert it into an asynchronous handler. The usual name for the second parameter is ''next''. The event queue will be paused until you call next() to continue processing the event queue or next(false) to stop it. Note that returned value of an asynchronous handler will be ignored. FC.client.on("cart-coupon-add", function (params, next) { $.ajax({ type: "get", url: "http://api.example.com/check-coupon-availability", dataType: "jsonp", data: { couponCode: params.couponCode } }).done(function (data) { if (data.ok) { next(); } else { alert('Sorry, your coupon is not available today.'); next(false); } }); }).on("cart-coupon-add.done", function (params) { alert('Coupon '+params.couponCode+' has been added'); }); === Getting the event === var event = FC.client.event("cart-item-remove"); === Overriding default action === FC.client.event("cart-item-remove").override(newDefaultHandler); === Creating and triggering your own event === FC.client.wrap("your-own-event", function (params) { // body }); FC.client.event("your-own-event").trigger(params); === Preventing product add to cart actions with custom criteria=== Here's an example that prevents adding product category ''Product2'' if there are products in the cart that belong to the category ''Product1''. FC.client.on('cart-submit', function(params, next){ var product1_exists = false; if(!jQuery.isEmptyObject(FC.json.items)) { jQuery.each(FC.json.items, function(i, item){ if(item.category == "Product1") { product1_exists = true; } }); } if(params.data.category == "Product2" && product1_exists) { next(false); } else { next(true); } }); ===== Configuration and Loading Options ===== There are a few things that are set by default in FoxyCart's javascript, but that can be overridden. Typically, these are handled with the template config options in the admin, but there are a few situations where javascript overrides are needed or make more sense. To use these, add code like this //before// you include the FoxyCart javascript. The available properties are: * ''debug'': If ''true'', enables FoxyCart's ''FC.util.log()'' method so you can see what's going on. This setting is available as a template config value, but that value doesn't work on pageload on your own site (rather, it only works on the full-page cart, checkout, and receipt). * ''onLoad'': Sometimes you'll need to attach an event handler once the ''FC'' object is loaded and ready. Whatever you put in the ''onLoad()'' function will be executed once, right before the ''FC.client.init()'' is called. ==== Sessions & Cookies ==== Most of the following is handled automatically, but if you need your FoxyCart customer sessions to only exist at certain (sub)domains or sections within your site, read on. === ''sitedomain'': The Domain on Which Your Visitor's FoxyCart Cookies Are Set === The ''sitedomain'' is only used to determine where to set the cookie for the visitor's FoxyCart session. If your ''sitedomain'' is passed in as ''example.com'' or ''www.example.com'', the ''fcsid'' (FoxyCart Session ID) cookie is set at the second-level domain, which is ''.example.com''. If your ''sitedomain'' is a third-level domain like ''example.co.uk'' or ''subdomain.example.com'', the cookie would be set at ''.example.co.uk'' or ''.subdomain.example.com''. This setting is only important if your site isn't at the second-level domain, or if you want to restrict your FoxyCart sessions by subdomain. For example, if your site is ''example.co.uk'', you don't want the cookie set at ''.co.uk''. Or if you want to have different FoxyCart sites at ''donations.example.com'' and ''products.example.com'', you'd need the cookies //not// to be set at ''.example.com'' or the sessions would overlap with unexpected results. (Another example would be if your site is at a 3rd party provider like ''example.squarespace.com'', you don't want all ''*.squarespace.com'' sites sharing your FoxyCart sessions.) The only two things to keep in mind are: - The ''www'' subdomain is effectively ignored, so if you //do// want to lock your sessions down to ''.www.example.com'' you'll need to set the ''sitedomain'' value to something like ''www1.example.com''. The actual value doesn't matter; it's only counting the dots in the value after it strips the ''www''. - You //cannot// have the FoxyCart javascript at any other subdomains if you are isolating by subdomain. For example, if you have two separate stores at ''products.example.com'' and ''donations.example.com'' you //cannot// also have your ''loader.js'' at ''www.example.com'' or ''example.com'', as that will set the session cookie at ''.example.com'', which will override the ''products'' and ''donations'' session cookies. **Example:** If you're testing two different sites at subdomains of the same domain (like ''store1.example.com'' and ''store2.example.com''), you'd put this script tag before the FoxyCart script tag: **Note:** You probably wouldn't want to keep this in production, unless the site //only// lives at a subdomain, and is never available at the ''example.com'' top level domain. === ''cookiepath'': The Path on Which Your Visitor's FoxyCart Cookies Are Set === The ''cookiepath'' determines the ''fcsid'' cookie's path. This is almost always going to be empty, so the cookies would be set at ''.example.com'', but if you do need to have multiple FoxyCart sessions on the same domain you could use this setting to restrict cookies to something like ''.example.com/en/'' and ''.example.com/es/'' (to split English and Spanish FoxyCart stores, for example). The ''cookiepath'' value must start and end in a ''/'' or you may run into issues, especially with Internet Explorer. **Example:** Suppose you have a donations section and a bookstore, located at ''http://example.com/donations/'' and ''http://example.com/bookstore/''. (Note that the trailing slash is critical. If you have something like ''http://example.com/bookstore'' and ''http://example.com/donations'' this method will not work and the sessions will collide.) Add the following code before your calls to ''loader.js''. Obviously, change out the "donations" with "bookstore" depending on where you're placing this code. var FC = { onLoad: function () { FC.settings.cookiepath = '/donations/; } } ===== Reference ====== ==== JavaScript Naming Conventions ==== Similar to Google's conventions, but using snake_case instead of camelCase for variable names: Use ''functionNamesLikeThis'', ''variable_names_like_this'', ''ClassNamesLikeThis'', ''EnumNamesLikeThis'', ''methodNamesLikeThis'', ''CONSTANT_VALUES_LIKE_THIS'', ''foo.namespaceNamesLikeThis.bar'', and ''filenameslikethis.js''. ==== Event Naming Conventions ==== * Present tense, like normal javascript events. So ''cart-coupon-add'' not ''cart-coupon-added''. * Hyphens for the event names themselves. * The names should be "section" (cart, checkout, receipt), "noun" (coupon, email, address, etc.), modifier/attribute (if applicable, like ''cart-item-quantity'', where ''quantity'' is the attribute of the ''item''), and finally the verb (add, remove, update, change, etc.). * The "after" event gets the suffix ''".done"'': ''client.on("cart-item-remove.done", handler);'' === What Types of Events Are Available === If you find an event that isn't available to you but should be, let us know. * Anything in an ''init'' method. * Anything on change, click, keyup/down, blur, focusin/out. * Not selectors. Those aren't events. They return a string. * Rendering. * Interactions with API JSON. ==== File Structure ==== Internal pieces used to compile the external scripts. TODO: We'll be putting all this source on GitHub soon. Until then, you can access each file using the base url ''admin.foxycart.com/static/v/2.0.0/js/src/'' * ''fc.api.js'': Methods that make requests to the ''api_json.php'' endpoint. * Internal methods: ''saveContactInfo'', ''saveShippingServiceInfo'', ''getShippingOptions'', ''getTaxes'' * Events: //none// * ''fc.cart.js'': Methods that make requests to the ''/cart'' endpoint, and methods that update the cart template (using Twig.js). Also initialization for the cart DOM, like setting behaviors for interacting with the cart. * Internal methods: * Events: ''cart-item-quantity-update'', ''cart-item-remove'', ''cart-coupon-add'', ''cart-coupon-remove'', ''customer-shipping-options-update'', ''customer-address-update'' * ''fc.checkout.js'': Checkout page initialization and methods. * Events: more info coming soon * ''fc.client.js'': Methods for maintaining and handling the ''fcsid'' session value, and preparing links and forms to submit to the cart. * Events: ''cart-submit'' * ''fc.event.js'': The event model, defined. * ''fc.locations.js'': Shipping and billing country and state/province/region functionality. * Events: * ''fc.namespace.js'': ''var FC = FC || {};'' * ''fc.postal-code.js'': Methods to handle the postal code -> city+state/region functionality. * ''fc.session.js'': Methods to set the ''fcsid'' cookie. * ''fc.sidecart.js'': Everything related to the Sidecart cart approach. * ''fc.template.js'': Twig.js related methods. Also, custom Twig.js methods to mirror custom serverside methods. * ''fc.util.js'': Helper functions. * Internal methods: ''money_format'', ''str_pad'', ''unserialize'' External scripts * ''foxycart.js'': namespace, session management, events, and utilities * ''foxycart.jsonp.js'': Used as a base for custom integrations and for the default Sidecart approach. * ''foxycart.jsonp.sidecart.js'': The cart in a Sidecart approach. === Pageload Events and Order of Execution === ''FC.client.init()'' is called on pageload for any CDN-loaded, store-specific javascript. This triggers: * ''FC.client.applySession()'' * ''FC.client.preventDefaultActions()'' * IF ''context==cart'', then ''FC.client.updateCart()'' * ''FC.client.event('ready').trigger();'' * ''FC.client.updateMiniCart();'' ''FC.client.updateCart()'' makes a ''FC.client.request'' to get the cart JSON ''FC.client.request'' triggers ''FC.client.updateJSON()'' on completion. ''FC.client.updateJSON()'' calls ''FC.client.updateMiniCart()'' ''ready'' event triggers… * ''FC.cart.updateConfig();'' * ''FC.cart.updatePaymentInfoRequired();'' ''FC.cart.updateConfig()'' checks and sets ''localStorage''. Will trigger… * ''FC.client.requestConfig()'', which is a call to ''FC.client._request'' that sets the config JSON with the updated data. ===== Twig.js, Template Rendering via JavaScript ===== ==== Checkout ==== FC.checkout.render() FC.checkout.renderLoginRegister() FC.checkout.renderCustomerShipping() FC.checkout.renderCustomerBilling() FC.checkout.renderShippingRates() FC.checkout.renderPaymentMethod() FC.checkout.renderAdditionalFields() FC.checkout.renderRequiredHiddenFields() ==== Cart ==== FC.cart.render() FC.cart.renderShippingRates() FC.cart.renderTaxes() FC.cart.renderOrderTotals() FC.cart.renderCouponEntry() FC.cart.renderAddressEntry() FC.cart.renderCartItemsDivs() ==== Sequential Rendering ==== You're allowed to make as many sequential render calls as you need. The real Twig rendering will be called only once. The following sequential commands FC.cart.renderShippingRates(); FC.cart.renderTaxes(); FC.cart.renderOrderTotals(); will call the Twig renderer once, then the needed blocks will be replaced.