Table of Contents

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 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 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.

<script>
var FC = FC || {};
FC.onLoad = function() {
    FC.client.on('cart-submit', function(params, next) {
        $element = $(params.element);
        if (
            $element.attr('id') == 'my-specific-form'
            && $element.find('[name="date"]').length > 0
            && !$element.find('[name="date"]').val()
        ) {
            alert('Date must be filled out');
        } else {
            next();
        }
    });
};
</script>

"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:

<p data-fc-id="minicart" style="display:none;">
	<a href="https://YOURSTORE.foxycart.com/cart?cart=view">
		<span data-fc-id="minicart-quantity">0</span>
		<span data-fc-id="minicart-singular"> item </span>
		<span data-fc-id="minicart-plural"> items </span>
		in cart. Total cost: $
		<span data-fc-id="minicart-order-total">0</span>
	</a>
</p>

The data attributes are the key. Stick those on any HTML element and it'll be updated automatically.

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

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.

<script>
	var FC = FC || {};
	FC.override = FC.override || {};
 	FC.override.PROPERTY = VALUE;
 	FC.onLoad = function () {
 		// Attach FC event handlers here, before FC is defined. Like this:
 		FC.client.on('ready.done', function () {
 			// custom code goes here
 		});
 	};
 
</script>

The available properties are:

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:

  1. 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.
  2. 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:

<script type="text/javascript" charset="utf-8">
var FC = {
  onLoad: function () {
    FC.settings.sitedomain = 'store1.example.com';
  }
}
</script>

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

What Types of Events Are Available

If you find an event that isn't available to you but should be, let us know.

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/

External scripts

Pageload Events and Order of Execution

FC.client.init() is called on pageload for any CDN-loaded, store-specific javascript. This triggers:

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() checks and sets localStorage. Will trigger…

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.