Lana UI > Tutorial
OverviewLana UITutorial
Tutorial

In this tutorial, we will construct a basic single shop page, focusing primarily on functionality rather than design. We will utilize 11ty, a static site generator, for this purpose.

Start

Begin by ensuring that you have a test shop set up. If not, navigate to the Lana frontend app and create one. Add a product or two that we can add to the cart.

TODO: better shop setup guide

Since 11ty is a NodeJS-based project, make sure that you have NodeJS installed on your system. Additionally, you'll require a npm-compatible package manager. Typically, NodeJS comes with npm pre-installed, so we'll use npm in this example.

Create a directory for our shop project. Run npm init and accept the defaults. At this point, we won't concern ourselves too much about the package.json contents.

Let's install the libraries and tools we'll need. Run the following command:

npm install -D @lana-commerce/core @11ty/eleventy cross-fetch

This command installs the following:

  • @11ty/eleventy is the main 11ty static site generator package.
  • @lana-commerce/core A library that we'll use to fetch data about our shop through the Lana API.
  • cross-fetch is a polyfill that adds fetch API to NodeJS. We'll need this because @lana-commerce/core uses the fetch API.

Configuration

Let's start by defining 11ty config file. Enter the following code into .eleventy.js:

const attrEscape = (s) =>
  s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");

module.exports = function (eleventyConfig) {
  eleventyConfig.addFilter("stringify", JSON.stringify);
  eleventyConfig.addFilter("attr_escape", attrEscape);
  return {
    dir: {
      input: "templates",
    },
  };
};

Note the helper filters added for some useful escaping in our template page. We also specify that templates will be located in the "templates" directory. Besides, we'll need a "_data" directory in our "templates" directory. Create the directories using the following commands:

mkdir templates
mkdir templates/_data

Data source

Next, let's establish a data source for our shop. This file will fetch all the dynamic data from the Lana API, which includes shop settings, all the products/variants we have there, and the latest @lana-commerce/ui lib version from the npm registry. Enter the following code into templates/_data/data.js (code includes extensive explanatory comments):

// Import "fetch" API polyfill.
require("cross-fetch/polyfill");

// Import GraphQL queries for fetching necessary data.
const getVariantPage = require("@lana-commerce/core/graphql/operations/GetVariantPageQuery").default;
const getBranding = require("@lana-commerce/core/graphql/operations/GetBrandingQuery").default;
const getGateways = require("@lana-commerce/core/graphql/operations/GetGatewaysQuery").default;
const getShopCurrencies = require("@lana-commerce/core/graphql/operations/GetShopCurrenciesQuery").default;
const getShop = require("@lana-commerce/core/graphql/operations/GetShopQuery").default;

// Import a helper function for fetching all items from a paginated API.
const { fetchAllItems } = require("@lana-commerce/core/fetchAllItems");

// Import a helper function for error printing, and the "request" API for making GraphQL requests.
const { prettyPrintRequestResponseError, request } = require("@lana-commerce/core/request");

// Import a helper function for generating <script> and <style> tags for @lana-commerce/ui.
const { embedUI } = require("@lana-commerce/core/script");

// Define the shop id for which we're generating the page.
const SHOP_ID = "sh_6XqAjeOAgYB8";

// If the response is not of the "data" kind, pretty print the error and return true.
// This function will be used to terminate this data source script if an error occurs.
function logBadResponse(v) {
  if (v.kind !== "data") {
    console.log(prettyPrintRequestResponseError(v));
    return true;
  }
  return false;
}

// Export an asynchronous function for 11ty data source.
// 11ty will use this function to generate arbitrary data available to all 11ty templates.
module.exports = async function () {
  // Fetch basic information about the shop.
  const shopResp = await request(getShop)({ shopID: SHOP_ID });
  if (logBadResponse(shopResp)) return;
  const shop = shopResp.data[0];

  // Fetch all the product variants in the shop.
  const variantsResp = await fetchAllItems(getVariantPage, { shopID: SHOP_ID });
  if (logBadResponse(variantsResp)) return;

  // Fetch the shop's currencies (used for currency formatting in @lana-commerce/ui).
  const shopCurrenciesResp = await request(getShopCurrencies)({ shopID: SHOP_ID });
  if (logBadResponse(shopCurrenciesResp)) return;

  // Fetch gateways from the shop. Depending on the gateway setup, @lana-commerce/ui might display a "saved cards" menu.
  const shopGatewaysResp = await request(getGateways)({ shopID: SHOP_ID });
  if (logBadResponse(shopGatewaysResp)) return;

  const ui = await embedUI({
    shop,
    currencies: shopCurrenciesResp.data,
    gateways: shopGatewaysResp.data,
  });

  // Return the data. Once loaded, this data will be accessible in all 11ty templates.
  return {
    shopTitle: shop.title,
    variants: variantsResp.data,
    ui,
  };
};

Okay, this simple script is enough to provide us with data for our shop page.

Shop page

Now, let's construct our shop page. Initially, it will be extremely simple, with no additional design elements. Incorporate the following into your templates/index.liquid file. For clarity, extensive explanatory comments are provided below:

<html>
  <head>
    <!-- Note that we set title dynamically from our data source. -->
    <title>{{ data.shopTitle }}</title>
    <!-- Basic responsive viewport setup. -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <!-- Include @lana-commerce/ui tags. -->
    {{ data.ui.fontTag }}
    {{ data.ui.styleTag }}
    {{ data.ui.scriptTag }}
    {{ data.ui.scriptInitTag }}
    <script>
      var G_VARIANTS = {
      {%- for v in data.variants %}
        {{ v.id }}: {{ v | stringify }},
      {%- endfor %}
      };
    </script>
    <!-- Very basic link styling. -->
    <style>
      .link {
        cursor: pointer;
        color: rgb(59, 130, 246);
      }
      .link:hover {
        text-decoration: underline;
      }
    </style>
  </head>
  <body>
    <!-- A bunch of buttons here that open modules of the @lana-commerce/ui. -->
    <div>
      <a class="link" onClick="LanaUI.showSearch()">[Search]</a>
      <a class="link" onClick="LanaUI.showCustomerProfile()">[Customer Profile]</a>
      <a class="link" onClick="LanaUI.showCart()">[Cart]</a>
    </div>
    <!-- Simple list of all product variants from our shop. The link will add item to cart on click and toggle the cart visibility. -->
    <div>
      <ul>
        {%- for v in data.variants %}
        <li>
          <a
            class="link"
            onClick="LanaUI.addItem({{ v.id | stringify | attr_escape }}, window.G_VARIANTS[{{ v.id | stringify | attr_escape }}]); LanaUI.showCart();"
          >
            {{ v.product.title }}
          </a>
        </li>
        {%- endfor %}
      </ul>
    </div>
  </body>
</html>

Information on Product Variants

In our approach, we furnish details about the product variant when adding it to the cart. Although technically adding an item to a cart necessitates only the variant ID, this extra information is used for client-side prediction within the cart code. When the LanaUI.addItem() function is invoked, the cart preemptively displays the added item by predicting the outcome of the API request. For the item display, the cart needs specific details about it, such as the variant image, product title, price, and options, hence the provision of such information.

This method of client-side prediction significantly enhances the customer experience. The cart synchronizes its client-side state with the server in the background, permitting the customer to continuously interact with the cart or the rest of the UI seamlessly.

Final step

At this point, all the necessary components are in place for you to generate your shop page. Next, inspect the package.json file, locate the "scripts" section and incorporate a "build" script. Your updated section should resemble the following:

{
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "eleventy"
  }
}

We're now set to proceed. Run the following command: npm run build.

If all steps were followed correctly, you should find the generated shop page as _site/index.html. You can open this file in a web browser, but be aware that some features may not function properly when loaded via a file:/// URL due to certain browser restrictions for security purposes. To enable all features, host the page on a https:// URL. For hosting your new shop, consider using a static site hosting service such as Netlify.

PREVIOUS
Intoduction
NEXT
Clay