Home Manual Reference Source Repository

src/entities/Cart.js

import Evee from 'evee';
import Queue from 'promise-queue';
import deepEqual from 'deep-equal';
import _ from 'lodash';

import config from 'config';
import log from 'log';
import ajaxApi from 'http/shopifyAJAXAPI';

const TAG = 'Cart';


/**
 * @typedef {Object}  ShopifyCartLineItem
 * @property {Number} id
 * @property {Object|null} properties
 * @property {Number} quantity
 * @property {Number} variant_id
 * @property {String} key
 * @property {String} title
 * @property {Number} price
 * @property {Number} original_price
 * @property {Number} discounted_price
 * @property {Number} original_line_price
 * @property {Number} line_price
 * @property {Number} total_discount
 * @property {Object[]} discounts
 * @property {String|null} sku
 * @property {Number} grams
 * @property {String} vendor
 * @property {Boolean} taxable
 * @property {Number} product_id
 * @property {Boolean} gift_card
 * @property {String} url
 * @property {String|null} image
 * @property {String} handle
 * @property {Boolean} requires_shipping
 * @property {String|null} product_type
 * @property {String} product_title
 * @property {String|null} product_description
 * @property {String} variant_title
 * @property {String[]} variant_options
 */

/**
 * @typedef {Object} ShopifyCart
 * @property {String} token
 * @property {String} note
 * @property {Object} attributes
 * @property {Number} total_price
 * @property {Number} total_weight
 * @property {ShopifyCartLineItem[]} items
 * @property {Boolean} requires_shipping
 */

/**
 * Shopify Cart entity.
 * @extends {evee}
 */
export default class Cart extends Evee {
    /**
     * Construct a new instance of the cart entity.
     */
    constructor() {
        super();

        /** @type {promise-queue} */
        this._queue = new Queue(1, Infinity);
        this._attributes = {};
        this._ready = false;

        /** @type {Object[]} */
        this._items = [];
        this._attributes = {};
    }

    /**
     * Initialize the cart entity with the current state of the visitor's cart.
     * @param {ShopifyCart} cartData - Shopify Cart object.
     */
    initialize(cartData) {
        this._loadFromShopifyCart(cartData);

        this._emitUpdate('init', null);
        if (this.items.length == 0) {
            this._emitClear([]);
        }

        log.send(log.DEBUG, TAG, 'Loaded.');
    }

    /**
     * Alias to getItems();
     * @type {ShopifyCartLineItem[]}
     */
    get items() { return this.getItems(); }
    /**
     * Returns items in the cart (in the normal Shopify line item format.
     * @returns {ShopifyCartLineItem[]}
     */
    getItems() {
        return this._items;
    }

    /**
     * Alias to getTotalPrice();
     * @type {Number}
     */
    get total_price() { return this.getTotalPrice(); }
    /**
     * Alias to getTotalPrice();
     * @type {Number}
     */
    get totalPrice() { return this.getTotalPrice(); }
    /**
     * Calculates the total value of the cart.
     * @returns {Number}
     */
    getTotalPrice() {
        return _.reduce(this.items, (total, item) => {
            return total + item.line_price;
        }, 0)
    }

    /**
     * Reloads the cart state via AJAX.
     * @returns {Promise<src/entities/Cart.js~Cart, Error>}
     */
    async reload() {
        var cartData = await ajaxApi.get('/cart.js');
        this._loadFromShopifyCart(cartData.data);
        return this;
    }
        



    /**
     * Alias to getAttributes()
     * @type {Object}
     */
    get attributes() { return this.getAttributes(); }
    /**
     * Returns cart attributes.
     * @returns {Object}
     */
    getAttributes() {
        return this._attributes;
    }

    /**
     * Sets all attributes on the cart. This will override/remove existing attributes.
     * @param {Object} attributes - New attributes for the cart.
     * @returns {Promise<src/entities/Cart.js~Cart, Error>}
     */
    setAttributes(attributes) {
        log.sendObject(log.DEBUG, TAG, 'Setting attributes...', {
            attributes
        });
        return this._queue.add(async () => {
            var cart = await ajaxApi.post({attributes});
            this._loadFromShopifyCart(cart.data);
            this._emitUpdate('attribute-updated', null);
        });
    }

    getAttribute(key) {
        return this._attributes[key];
    }

    setAttribute(key, value) {
        log.sendObject(log.DEBUG, TAG, 'Setting attribute...', {
            key,
            value
        });
        return this._queue.add(async () => {
            var newAttributes = Object.assign({}, this._attributes);
            newAttributes[key] = value;
            var cart = await ajaxApi.post({attributes: newAttributes});
            this._loadFromShopifyCart(cart.data);
            this._emitUpdate('attribute-updated', null);
        });
    }


    /**
     * Add a new item to the cart.
     * @param {Number|String} id - Variant ID to add to cart.
     * @param {Number} quantity - Quantity to add.
     * @param {Object} properties - Line item attributes.
     * @returns {Promise<ShopifyCartLineItem, Error>}
     */
    addItem(id, quantity, properties) {
        log.sendObject(log.DEBUG, TAG, 'Queuing addItem...', {
            id,
            quantity,
            properties
        });
        return this._queue.add(async () => {
            var res = await ajaxApi.post('/cart/add.js', {
                id,
                quantity,
                properties
            });
            var data = res.data;
            log.sendObject(log.DEBUG, TAG, 'addItem resonse', data);
            if (data.id) {
                var item = this._loadItem(data);
                this._emitAdd(item);
                this._emitUpdate('add', item);
                return item;
            }
            throw new Error(data);
        });
    }

    updateItem(lineNumber, quantity, properties) {
        return this._queue.add(() => {
            return new Promise((acc, rej) => {

            });
        });
    }

    removeItem(lineNumber) {
        return this._queue.add(() => {
            return new Promise((acc, rej) => {

            });
        });
    }

    updateItemById(id, quantity, properties) {
        return this._queue.add(() => {
            return new Promise((acc, rej) => {

            });
        });
    }

    updateItemById(id, quantity, properties) {
        return this._queue.add(() => {
            return new Promise((acc, rej) => {

            });
        });
    }

    updateItemQuantitiesById(updates) {
        return this._queue.add(() => {
            return new Promise((acc, rej) => {

            });
        });
    }

    removeItemById(id) {
        return this._queue.add(() => {
            return new Promise((acc, rej) => {

            });
        });
    }

    /**
     * Clear the cart.
     * @returns {Promise<src/entities/Cart.js~Cart, Error>}
     */
    clear() {
        return this._queue.add(async () => {
            var cartData = await ajaxApi.post('/cart/clear.js');
            var oldItems = this.items;
            this._loadFromShopifyCart(cartData);
            this._emitClear(oldItems);
        });
    }


    _loadItem(itemData) {
        for (var item of this.items) {
            if (item.id == itemData.id && deepEqual(item.properties, itemData.properties)) {
                item.quantity = itemData.quantity;
                return item;
            }
        }

        this.items.push(itemData);
        return itemData;
    }

    _emitUpdate(operation, item) {
        var data ={
            items: this.items,
            operation,
            item
        };
        log.sendObject(log.DEBUG, TAG, 'Event: update', data);
        this.emit('update', data);
    }

    _emitClear(oldItems) {
        var data ={
            oldItems
        };
        log.sendObject(log.DEBUG, TAG, 'Event: clear', data);
        this.emit('clear', data);
    }

    _emitAdd(item) {
        var data ={
            items: this.items,
            item
        };
        log.sendObject(log.DEBUG, TAG, 'Event: add', data);
        this.emit('add', data);
    }

    _emitUpdateItem(item) {
        var data ={
            items: this.items,
            item
        };
        log.sendObject(log.DEBUG, TAG, 'Event: updae-item', data);
        this.emit('update-item', data);
    }

    _emitRemove(item) {
        var data = {
            items: this.items,
            item
        };
        log.sendObject(log.DEBUG, TAG, 'Event: remove', data);
        this.emit('clear', data);
    }

    _loadFromShopifyCart(cart) {
        this._items = cart.items || [];
    }
}