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.
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.
If you'd like to completely clear out the FoxyCart session, you can call this.
FC.session.reset();
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>
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.
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 hiddendata-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.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 });
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-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-submit
: When an item is added to the cart.cart-item-quantity-update
: When a quantity is changed in the cartcart-item-remove
: When a quantity is removed from the cartcart-coupon-add
: When a coupon or gift card is addedcart-coupon-remove
: When a coupon is removedcart-gift-card-remove
: When a gift card is removedcart-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 updatedcheckout-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 checkoutcustomer-email-update
: Called when an email address change triggers the email to be checkedcustomer-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.
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.
Note that .on() and .off() methods can be chained.
FC.client.on("cart-item-remove", cartItemRemoveHandler) .on("cart-item-remove.done", cartItemRemoveDoneHandler) .on("cart-submit", cartSubmitHandler) .on("cart-submit.done", cartSubmitDoneHandler);
FC.client.off("cart-item-remove", cartItemRemoveHandler) .off("cart-submit", cartSubmitHandler);
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; } });
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'); });
var event = FC.client.event("cart-item-remove");
FC.client.event("cart-item-remove").override(newDefaultHandler);
FC.client.wrap("your-own-event", function (params) { // body }); FC.client.event("your-own-event").trigger(params);
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); } });
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:
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.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.
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:
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
.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.
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/; } }
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
.
cart-coupon-add
not cart-coupon-added
.cart-item-quantity
, where quantity
is the attribute of the item
), and finally the verb (add, remove, update, change, etc.).“.done”
: client.on(“cart-item-remove.done”, handler);
If you find an event that isn't available to you but should be, let us know.
init
method.
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.saveContactInfo
, saveShippingServiceInfo
, getShippingOptions
, getTaxes
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.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.fc.client.js
: Methods for maintaining and handling the fcsid
session value, and preparing links and forms to submit to the cart.cart-submit
fc.event.js
: The event model, defined.fc.locations.js
: Shipping and billing country and state/province/region functionality.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.money_format
, str_pad
, unserialize
External scripts
foxycart.js
: namespace, session management, events, and utilitiesfoxycart.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.
FC.client.init()
is called on pageload for any CDN-loaded, store-specific javascript. This triggers:
FC.client.applySession()
FC.client.preventDefaultActions()
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.FC.checkout.render() FC.checkout.renderLoginRegister() FC.checkout.renderCustomerShipping() FC.checkout.renderCustomerBilling() FC.checkout.renderShippingRates() FC.checkout.renderPaymentMethod() FC.checkout.renderAdditionalFields() FC.checkout.renderRequiredHiddenFields()
FC.cart.render() FC.cart.renderShippingRates() FC.cart.renderTaxes() FC.cart.renderOrderTotals() FC.cart.renderCouponEntry() FC.cart.renderAddressEntry() FC.cart.renderCartItemsDivs()
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.