JavaScript API

Lorikeet comes with a small JavaScript library to make manipulating the cart from client JavaScript a little easier. It provides convenience creation, update and delete methods for line items, delivery addresses and payment methods, and also keeps the state of the shopping cart in sync if it’s open in multiple tabs using localStorage.

It supports IE11, the latest versions of Safari, Edge and Firefox, and the two latest versions of Chrome. It requires a window.fetch polyfill for IE and Safari.

Installation

The JavaScript component of Lorikeet can be installed via NPM (to be used with a bundler like Webpack). In the future, it will also be provided as a CDN-hosted file you can reference in a <script> tag. To install it, run npm install https://gitlab.com/abre/lorikeet.

Usage

If you’re using the NPM version, import CartClient from 'lorikeet' or var CartClient = require('lorikeet') as appropriate for your setup.

Use the CartClient constructor to instantiate the client. This is the object you’ll use to interact with the API.

var client = new CartClient('/_cart/')

You can now access the current state of the cart on client.cart, which exposes the entire contents of the main endpoint of the HTTP API.

console.log(client.cart.grand_total) // "123.45"
console.log(client.cart.items.length) // 3

You can listen for changes using the addListener and removeListener events.

var listenerRef = client.addListener(function(cart){console.log("Cart updated", cart)})
client.removeListener(listenerRef)

All of the members of the lists at client.cart.items, client.cart.delivery_addresses and client.cart.payment_methods have a delete() method. Members of client.cart.items also have update(data) method, which performs a partial update (PATCH request) using the data you pass, and members of the other two have a select() method that, makes them the active delivery address or payment method.

client.cart.items[0].update({quantity: 3})
client.cart.items[1].delete()
client.cart.delivery_addresses[2].select()
client.cart.payment_methods[3].delete()

There’s also addItem, addAddress and addPaymentMethod methods, which take a type of line item, address or payment method as their first item, and a blob in the format expected by the corresponding serializer as the second.

client.addItem("MyLineItem", {product: 1, quantity: 2})
client.addAddress("AustralianDeliveryAddress", {
  addressee: "Adam Brenecki",
  address: "Commercial Motor Vehicles Pty Ltd\nLevel 1, 290 Wright St",
  suburb: "Adelaide", state: "SA", postcode: "5000",
})
client.addPaymentMethod("PipeCard", {card_token: "tok_zdchtodladvrcmkxsgvq"})

Reference

class CartClient(cartUrl, cartData)

A client that interacts with the Lorikeet API.

Arguments:
  • cartUrl (string) – URL to the shopping cart API endpoint.
  • cartData (object) – Current state of the cart. If provided, should match the expected strcuture returned by the cart endpoint.
CartClient.addItem(type, data)

Add an item to the shopping cart.

Arguments:
  • type (string) – Type of LineItem to create
  • data (object) – Data that the corresponding LineItem serializer is expecting.
CartClient.addAddress(type, data)

Add a delivery address to the shopping cart.

Arguments:
  • type (string) – Type of DeliveryAddress to create
  • data (object) – Data that the corresponding DeliveryAddress serializer is expecting.
CartClient.addPaymentMethod(type, data)

Add a delivery address to the shopping cart.

Arguments:
  • type (string) – Type of PaymentMethod to create
  • data (object) – Data that the corresponding PaymentMethod serializer is expecting.
CartClient.setEmail(address)

Set an email address for the shopping cart.

Arguments:
  • address (string|null) – Email address to set. Use null to clear the address field.
CartClient.addListener(listener)

Register a listener function to be called every time the cart is updated.

Arguments:
  • listener (CartClient~cartCallback) – The listener to add.
Returns:

CartClient~cartCallback – Returns the listener function that was passed in, so you can pass in an anonymous function and still have something to pass to removeListener later.

CartClient.removeListener(listener)
Arguments:
  • listener (CartClient~cartCallback) – The listener to remove.
class CartItem(client, data)

A single item in a cart.

CartItem.update(newData)

Update this cart item with new data, e.g. changing a quantity count. Note that calling this method will not update the current CartItem; you’ll have to retrieve a new CartItem from the client’s cart property or from an event handler.

Arguments:
  • newData (object) – The data to patch this cart item with. Can be a partial update (i.e. something you’d send to a HTTP PATCH call).
class AddressOrPayment(client, data)

A single delivery address, or a single payment method. (Both have the same shape and methods, so they share a class.)

AddressOrPayment.select()

Make this the active address or payment method.

Promise Behaviour

All of the methods that modify the cart (CartClient.addItem(), CartClient.addAddress(), CartClient.addPaymentMethod(), CartItem.update(), and AddressOrPayment.select()) return Promises, which have the following behaviour.

If the request succeeds, the promise will resolve with the JSON-decoded representation of the response returned by the relevant API endpoint.

If the request fails with a network error, the promise will reject with an object that has the following shape:

{
    reason: 'network',
    error: TypeError("Failed to fetch"), // error from fetch() call
}

If the request is made, but receives an error response from the server, the promise will reject with an object that has the following shape:

{
    reason: 'api',
    status: 422,
    statusText: 'Unprocessable Entity',
    body: "{\"suburb\":[\"This field is…", // Raw response body
    data: {
        suburb: ["This field is required."],
        // ...
    }, // JSON-decoded response body
}

If an error response is returned from the server, and the response is not valid JSON, such as a 500 response with DEBUG=True or a 502 from a reverse proxy, the promise will instead reject with an object that has the following shape:

{
    reason: 'api',
    status: 502,
    statusText: 'Bad Gateway',
    body: "<html><body><h1>Bad Gateway…", // Raw response body
    decodeError: SyntaxError("Unexpected token < in JSON at position 0"),
}

Reducing Round Trips

The CartClient() constructor takes an optional second argument cart, which it will use instead of hitting the API if there’s no data already in local storage. (Even if there is, it’ll update it if it’s stale, so it’s always a good idea.)

You can use it alongside the lorikeet_cart() template tag like this:

{% load lorikeet %}
{# ... #}
<body data-cart="{% lorikeet_cart %}">
var cart = JSON.parse(document.body.attributes['data-cart'].value)
var client = new CartClient('/_cart/', cart)

React

Lorikeet also comes with optional support for React. To use it, wrap your React app’s outermost component in CartProvider, providing your Lorikeet client instance as the client prop.

import { CartProvider } from 'lorikeet/react'

class App extends Component {
  render() {
    return <CartProvider client={myClient}>
        // ...
    </CartProvider>
  }
}

Then, in any component where you want to use the client, decorate it with cartify, and you’ll have access to the client as props.cartClient, as well as a shortcut to the cart itself on props.cart.

import cartify from 'lorikeet/react'

class MyCart extends Component {
  handleAddItem(item){
    this.props.cartClient.addItem('ItemType', item)
  }
  render(){
    return <div>My cart has {this.props.cart.items.length} items!</div>
  }
}

MyCart = cartify(MyCart)