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.
data-fc-id="minicart"
: If theFC.json.item_count
is greater than 0, these elements will be shown. Otherwise they'll be hidden.data-fc-id="minicart-empty"
: If theFC.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 inFC.json.item_count
.data-fc-id="minicart-order-total"
: The inner HTML will be replaced by the value inFC.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 inFC.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” forFC.json.item_count
= 1.data-fc-id="minicart-plural"
: The inner HTML will be shown if value inFC.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 withselector
andblock_id
attributes. SoFC.client.on('render.done', function(params){});
, whereparams.selector
andparams.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 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 updated
- Checkout events.
checkout-submit
: Called when the checkout is submittedcheckout-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 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.
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:
debug
: Iftrue
, enables FoxyCart'sFC.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 theFC
object is loaded and ready. Whatever you put in theonLoad()
function will be executed once, right before theFC.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 thesitedomain
value to something likewww1.example.com
. The actual value doesn't matter; it's only counting the dots in the value after it strips thewww
. - 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
anddonations.example.com
you cannot also have yourloader.js
atwww.example.com
orexample.com
, as that will set the session cookie at.example.com
, which will override theproducts
anddonations
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
- Present tense, like normal javascript events. So
cart-coupon-add
notcart-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
, wherequantity
is the attribute of theitem
), 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 theapi_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 thefcsid
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 thefcsid
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 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.
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
, thenFC.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 toFC.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.