Skip to content

Instantly share code, notes, and snippets.

@keidarcy
Created January 23, 2023 22:25
Show Gist options
  • Save keidarcy/61c7f057bf7b6585031a5a9d001b1f18 to your computer and use it in GitHub Desktop.
Save keidarcy/61c7f057bf7b6585031a5a9d001b1f18 to your computer and use it in GitHub Desktop.
Shopify Notes

Resources list with node

nodejs with shopify-api-node package for private app with Admin Rest

const Shopify = require('shopify-api-node');
const dotenv = require('dotenv');

dotenv.config();

const shopify = new Shopify({
  shopName: process.env.shopName,
  apiKey: process.env.apiKey,
  password: process.env.password
});

const createSmartCollection = async (title, column, relation) => {
  const rules = [
    {
      column,
      relation,
      condition: title
    }
  ];
  try {
    await shopify.smartCollection.create({ title, body_html: '', rules });
    console.log(`---${title}---SUCCESS------`);
  } catch (error) {
    console.log('------ERROR------');
    console.log(error.message);
  }
};
  • get products
import Shopify from 'shopify-api-node';
import fetch from 'node-fetch';

const getProducts = async () => {
  const shopify = new Shopify({
    shopName: 'xxx.myshopify.com',
    apiKey: 'aaa',
    password: 'sss'
  });

  const products = await shopify.product.list({ limit: 5 });
  console.log(products[0]);
};

getProducts();

nodejs with no package for private app with Admin Rest

import fetch from 'node-fetch';

const url = 'https://xx.myshopify.com/admin/api/2020-07/shop.json';
const username = 'aaa';
const password = 'bbb';

fetch(url, {
  method: 'GET',
  headers: {
    Authorization: 'Basic ' + Buffer.from(username + ':' + password).toString('base64')
  }
})
  .then((response) => response.json())
  .then((json) => console.log(json));

broswer js for private app with Admin Rest

fetch('https://xxx.myshopify.com/admin/api/2020-07/shop.json', {
  method: 'GET',
  headers: { Authorization: 'Basic ' + btoa('apiKey:password') }
})
  .then((response) => response.json())
  .then((json) => console.log(json));

Curlprivate app with Stronfront Api(Graphql)

curl --request POST \
  --url https://xxx.myshopify.com/api/2019-07/graphql \
  --header 'accept: application/json' \
  --header 'content-type: application/json' \
  --header 'x-shopify-storefront-access-token: xxxx' \
  --cookie __cfduid=xxxxxx \
  --data '{"query":"{\n  shop {\n    name\n    primaryDomain {\n      url\n      host\n        \n    }\n  }\n}"}'
# Drag this into insomnia!

Curl prvate app with Admin API(rest)

curl --request GET \
  --url https://xxxxxx.myshopify.com//admin/api/2020-10/smart_collections.json \
  --header 'X-Shopify-Access-Token: shppa_xxxx'

Store Pickup by Secomapp

Bespoke_Shipping

-Bespoke Shipping

instruction

existing sample

/* This macro will be parsed as PHP code (see http://www.php.net)

The calculateshipping function is called every time a shipping calculation request is made by Shopify.

The function must return an array of available shipping options, otherwise no shipping options will be returned to your customers.
*/
function calculateshipping($DATA) {

	/* do not edit above this line */

	/*
	NB: only select php functions are allowed. Use of anything else will return a syntax error when you try to save. Consult the user guide at http://www.parcelintelligence.com.au/cs/documentation for more information
	Examples to get you started are available here: http://parcelintelligence.com.au/cs/documentation/examples/
	To take advantage of our assisted setup option, please email hello@parcelintelligence.com.au with a detailed description of how you want your shipping rates setup for a quote.
	*/

	//this holds the rates that will be returned to your customer
	$_RATES = array();

	/*
	this is what $DATA looks like, you can use any of the info in $DATA to generate your shipping rate
	use print_r($DATA); to see actual contents of the $DATA array in the logs.
	Array
	(
		[origin] => Array
			(
				[country] => AU
				[postal_code] => 3000
				[province] => VIC
				[city] => melbourne
				[name] =>
				[address1] => 1 main street
				[address2] =>
				[address3] =>
				[phone] =>
				[fax] =>
				[address_type] =>
				[company_name] =>
			)

		[destination] => Array
			(
				[country] => AU
				[postal_code] => 2000
				[province] => NSW
				[city] =>
				[name] =>
				[address1] =>
				[address2] =>
				[address3] =>
				[phone] =>
				[fax] =>
				[address_type] =>
				[company_name] =>
			)

		[items] => Array
			(
				[0] => Array
					(
						[name] => product10
						[sku] => SKUP00310
						[quantity] => 1
						[grams] => 1000
						[price] => 300
						[vendor] => PRODUCT
						[requires_shipping] => 1
						[taxable] => 1
						[fulfillment_service] => manual
						[product_id] => 128436738
						[variant_id] => 290813760
					)

				[1] => Array
					(
						[name] => product11
						[sku] => SKUP0011
						[quantity] => 1
						[grams] => 1100
						[price] => 300
						[vendor] => PRODUCT
						[requires_shipping] => 1
						[taxable] => 1
						[fulfillment_service] => manual
						[product_id] => 128436744
						[variant_id] => 290813772
					)

			)

		[currency] => AUD
	)

	//this is how you insert a rate
	$_RATES[] = array(
		"service_name" => "Standard Shipping", //this is what the customer will see
		"service_code" => "STANDARD_SHIPPING", //can be anything you like
		"total_price" => 10000, //in cents
		"currency" => "AUD",
	);
	*/

	return $_RATES;

	/* do not edit below this line */

}

Basic

Operation Names and Variables

query ProductTitleAndDescription($id: ID!) {
  product(id: $id){
    title
    description
  }
}

{
  "id": "gid://shopify/Product/xxx"
}

# Operation Names => ProductTitleAndDescription
# Variables => $id: ID!

graphql-request + graphql.macro

query getProduct($id: ID!) {
  product(id: $id) {
    id
    title
  }
}
require('dotenv').config();
const { loader } = require('graphql.macro');
const { GraphQLClient, gql } = require('graphql-request');

async function main() {
  const endpoint = process.env.SHOPIFY_URL;

  const graphqlClient = new GraphQLClient(endpoint, {
    headers: {
      'Content-type': 'application/json',
      'X-Shopify-Access-Token': process.env.SHOPIFY_API_PASSWORD
    }
  });

  // const query = gql`
  //   query getProduct($id: ID!) {
  //     product(id: $id) {
  //       id
  //       title
  //     }
  //   }
  // `;

  const variables = {
    id: 'gid://shopify/Product/XXX'
  };
  const query = loader('../graphqls/getOneProduct.gql');

  const data = await graphqlClient.request(query, variables);
  console.log(JSON.stringify(data, undefined, 2));
}

main().catch((error) => console.error(error));

Aliases

  • usage
query ProductTitleAndDescription($id: ID!) {
  product(id: $id) {
    myRenamedAliasesTitle: title
    description
  }
}
  • usage
query  {
  product1:product(id: 'gid://shopify/Product/XXX') {
    title
    description
  }
  product2:product(id: 'gid://shopify/Product/XXX') {
    title
    description
  }
}

Fragments

query  {
  product1:product(id: 'gid://shopify/Product/XXX') {
    ...TitleAndDescription
  }
  product2:product(id: 'gid://shopify/Product/XXX') {
    ...TitleAndDescription
  }
}
fragment TitleAndDescription on Product {
  title
  description
  featuredImage {
    src
  }
}
  • inline fragment
mutation tagsAdd($id: ID!, $tags: [String!]!) {
  tagsAdd(id: $id, tags: $tags) {
    node {
      id
    }
    userErrors {
      field
      message
    }
  }
}

{
  "id": "Z2lkOi8vU2hvcGlmeS9FeGFtcGxlLzE=",
  "tags": [
    "placeholder"
  ]
}

Customer and Product object implments node interface, so use inline fragment to query field in Customer and Product value.

mutation tagsAdd($id: ID!, $tags: [String!]!) {
  tagsAdd(id: $id, tags: $tags) {
    node {
      id
      ... on Product {
        title
        tags
      }
      ... on Customer {
        email
      }
    }
    userErrors {
      field
      message
    }
  }
}

Pagination

query threeProducts {
  products(first: 3) {
    edges {
      cursor
      node {
        id
        title
      }
    }
    pageInfo {
      hasNextPage
      hasPreviousPage
    }
  }
}

query threeProducts {
  products(first: 3, after: "xxxx") {
    edges {
      cursor
      node {
        id
        title
      }
    }
    pageInfo {
      hasNextPage
      hasPreviousPage
    }
  }
}

Query argument

  • find products that tagged with 'a' AND not tagged with 'b'
query taggedProducts {
  products(first: 3, query: '-tag: a AND tag: b') {
    edges {
      node {
        title
        description
        tags
      }
    }
  }
}

Admin API

Get product list with requirement

{
  products(first: 30, query: "", after: "{/{ the cursor you want to use }/}") {
    pageInfo {
      hasNextPage
      hasPreviousPage
    }
    edges {
      cursor
      node {
        id
        title
        images(first: 1) {
          edges {
            node {
              originalSrc
            }
          }
        }
      }
    }
  }
}

Get variant with metafields

query VariantWithMeta($id: ID!) {
  productVariant(id: $id) {
    metafields(first: 10) {
      edges {
        node {
          id
          key
          value
        }
      }
    }
  }
}
{
  "id": "gid://shopify/ProductVariant/id"
}

Update product metafields

mutation($input: ProductInput!) {
    productUpdate(input: $input) {
        product {
            id
        }
        userErrors {
            field
            message
        }
    }
}

{
    "id": "gid://shopify/Product/id",
    "metafields" => [
        {
            "id": "gid://shopify/Metafield/id",
            "key": "string",
            "value": "string",
            "valueType" "STRING",
            "namespace": "string"
        },
        {
            "id": "gid://shopify/Metafield/id",
            "key": "string",
            "value": "string",
            "valueType" "STRING",
            "namespace": "string"
        }
    ]
}

Storefront Api

Shop information

query {
  shop {
    primaryDomain {
      host
      sslEnabled
      url
    }
    description
    paymentSettings {
      countryCode
      acceptedCardBrands
      enabledPresentmentCurrencies
    }
    moneyFormat
  }
}

Get price with different currencies(which are setted in admin page)

{
  productByHandle(handle: "adidas-classic-backpack") {
    id
    title
    variants(first: 1) {
      edges {
        node {
          presentmentPrices(first: 1, presentmentCurrencies: [USD, CNY]) {
            edges {
              node {
                compareAtPrice {
                  amount
                  currencyCode
                }
                price {
                  amount
                  currencyCode
                }
              }
            }
          }
          unitPrice {
            amount
          }
          requiresShipping
          availableForSale
          id
          title
          priceV2 {
            currencyCode
            amount
          }
        }
      }
    }
  }
}

Useful shopify notes

External Links

Private app auth for admin api and storefront api

  • Admin API
    • graphql endpoint /admin/api/2020-10/graphql.json
    • access token -> Admin API -> password
curl --request POST \
  --url https://STORE.myshopify.com/admin/api/2020-10/graphql.json \
  --header 'content-type: application/json' \
  --header 'x-shopify-access-token: TOKEN' \
  --data '{"query":"query {\n  shop{\n    id\n    primaryDomain{\n      host\n      sslEnabled\n      url\n    }\n    description\n    paymentSettings{\n       supportedDigitalWallets\n    }\n  }\n}"}'
  • Storefront API
    • graphql endponint /api/2020-07/graphql.json
    • access token -> Storefront API -> Storefront access token
curl --request POST \
  --url https://STORE.myshopify.com/api/2020-07/graphql.json \
  --header 'accept: application/json' \
  --header 'content-type: application/json' \
  --header 'x-shopify-storefront-access-token: TOKEN' \
  --data '{"query":"query {\n  shop{\n    primaryDomain{\n      host\n      sslEnabled\n      url\n    }\n    description\n    paymentSettings{\n      countryCode\n      acceptedCardBrands\n      enabledPresentmentCurrencies\n    }\n    moneyFormat\n  }\n}"}'

How theme urls map

url - template

{/{ request.page_type }/} == {/{ template }/} which will be 404 | blog | cart ...
 - /thisisntarealurl → 404.liquid
 - /blogs/{blog-name}/{article-id-handle} → article.liquid
 - /blogs/{blog-name} → blog.liquid
 - /cart → cart.liquid
 - /collections → list-collections.liquid
 - /collections/{collection-handle} → collection.liquid
 - /collections/{collection-handle}/{tag} → collection.liquid
 - / → index.liquid
 - /pages/{page-handle} → page.liquid
 - /products → list-collections.liquid
 - /products/{product-handle} → product.liquid
 - /search?q={search-term} → search.liquid
 - /account/login → customers/login.liquid
 - /account → customers/account
 - /account/addresses  → customers/addresses.liquid
 - /account/register →  customers/register.liquid

hidden url

# recommendation api endpoint per product
https://STORE.myshopify.com/recommendations/products.json?product_id=ID&limit=4

Checkout process

  • "checkout_token": TOKEN
https://STORE.myshopify.com/NUMBER/checkouts/TOKEN
https://STORE.myshopify.com/NUMBER/checkouts/TOKEN?previous_step=contact_information&step=shipping_method
https://STORE.myshopify.com/NUMBER/checkouts/TOKEN?previous_step=shipping_method&step=payment_method
https://STORE.myshopify.com/NUMBER/checkouts/TOKEN/thank_you
# order_status_url
https://STORE.myshopify.com/NUMBER/orders/token

Multipass

Multiple currencies

money filter depends on admin setting /admin/settings/general.

Pass liquid data to Vue instance

<script id="data" type="application/json">{/{ product | json }/}</script>
const data = JSON.parse(document.getElementById('data').innerHTML);
new Vue({
  extends: MyComponent,
  propsData: data,
})

Theme editor

  • the name of /admin/themes/id/editor?picker=section is presets.name.en

Cart attribute

<p class="cart-attribute__field">
  <label for="name">name</label>
  <input id="name" type="text" name="attributes[name]" value="{/{ cart.attributes["name"]
  }/}">
</p>
  • name attribute will be add to shopify order
{
  "order": {
    "id": ID,
    "note_attributes": [
      {
        "name": "name",
        "value": "value"
      }
    ]
  }
}

Additional scripts order status page

{/% if first_time_accessed %/}
  // Conversion scripts you want to run only once
{/% endif %/}

Order status

link

order status

  • Open
  • Archived
  • Canceled

Payment status

  • Authorized
  • Paid
  • Partially refunded
  • Partially paid
  • Pending
  • Refunded
  • Unpaid
  • Voided

  • Fulfilled
  • Unfulfilled
  • Partially fulfilled
  • Scheduled

Checkout

  • outside of shopify use storefront API to checkout
mutation checkoutCreate($input: CheckoutCreateInput!) {
  checkoutCreate(input: $input) {
    checkout {
      id
      webUrl
    }
    checkoutUserErrors {
      code
      field
      message
    }
  }
}

webUrl is the checkout url

  • shopify to checkout
<form action="/cart/add" method="post">
  <input type="text" name="id" value="{product.variants[0].id}" />
  <input id="_key" type="text" name="properties[_key]" />
  <input type="number" name="quantity" value="1" min="1" />
  <button type="submit">カートにテスト追加</button>
</form>

Collections urls

sample

  • /collections/all?sort_by=manual
  • /collections/all?sort_by=best-selling
  • /collections/all?sort_by=title-ascending
  • /collections/all?sort_by=title-descending
  • /collections/all?sort_by=price-ascending
  • /collections/all?sort_by=price-descending
  • /collections/all?sort_by=created-ascending
  • /collections/all?sort_by=created-descending
  • /collections/all/dress+purple+25-50?sort_by=

Shopify Theme Liquid Code Snippets

offical liquid code examples

extra snippets

extra snippets

New theme helper

{/{ 'main.min.css' | asset_url | stylesheet_tag }/} {/{ 'main.min.js' | asset_url |
script_tag }/}
<script>
  // TODO: remove this helper
  {/% assign current_handle = '' %/}
  {/% case template %/}
    {/% when 'page' %/}
      {/% assign current_handle = page.handle %/}
    {/% when 'blog' %/}
      {/% assign current_handle = blog.handle %/}
    {/% when 'article' %/}
      {/% assign current_handle = blog.handle %/}
    {/% when 'collection' %/}
      {/% assign current_handle = collection.handle %/}
    {/% when 'product' %/}
      {/% assign current_handle = product.handle %/}
  {/% endcase %/}
  {/% assign current_url = '' %/}

  {/% case template %/}
    {/% when 'page' %/}
      {/% assign current_url = page.url %/}
    {/% when 'blog' %/}
      {/% assign current_url = blog.url %/}
    {/% when 'article' %/}
      {/% assign current_url = blog.url %/}
    {/% when 'collection' %/}
      {/% assign current_url = collection.url %/}
    {/% when 'product' %/}
      {/% assign current_url = product.url %/}
  {/% endcase %/}
    console.log('template: {/{ template }/}, theme.name: {/{ theme.name }/}');
    console.log('current_handle: {/{ current_handle }/}');
    console.log('current_url: {/{ current_url }/}');
</script>

Add link loop

{/% for category in linklists.category.links %/}
  {/{ category.title }/}
{/% endfor %/}

Add custom fileds

Add fields to product form

<p class="line-item-property__field">
  <label for="your-name">Your name</label>
  <input id="your-name" type="text" name="properties[Your name]" />
</p>

Add fields to cart form

<p class="cart-attribute__field">
  <label for="your-name">Your name</label>
  <input id="your-name" type="text" name="attributes[Your name]" value="{/{
  cart.attributes["Your name"] }/}">
</p>

Add fields to the customer registration form

<label for="CustomerFormAllergies">Allergies</label>
<input
  type="text"
  id="CustomerFormAllergies"
  name="customer[note][Allergies]"
  placeholder="Allergies"
/>

Product form

Minimal product form

<form action="/cart/add" method="post">
  <select name="id">
    {/% for variant in product.variants %/}
    {/% if variant.available %/}
    <option value="{/{ variant.id }/}">
      {/{ variant.title }/}
    </option>
    {/% else %/}
    <option disabled="disabled">{/{ variant.title }/} - {/{ 'products.product.sold_out' | t }/}</option>
    {/% endif %/}
    {/% endfor %/}
  </select>
  <input type="number" name="quantity" value="1" min="1" class="QuantityInput">
  <button type="submit">カートにテスト追加</button>
</form>

Show full featured collection of products

 {/% for product in collections['ADIDAS'].products limit: 6 %/}
		<a href="{/{ product.url }/}">
			<img src="{/{ product.featured_image | img_url }/}" alt="" srcset="">
			<h1>{/{ product.title }/}</h1>
		</a>
		<div>
				<a href="{/{ product.url }/}">
						<img src="{/{ product.featured_image | img_url :'x600'}/}" alt="" srcset="">
				</a>
		</div>
		<div>
			<h1>{/{ product.title }/}</h1>
			<div class="plan">
					<h3 id="_product_price_{/{ product.id }/}">{/{ product.price | money }/}</h3>
			</div>
			{/{ product.description }/}

			{/% form 'product', product, data-productid: product.id %/}
			<div class="selector-wrapper js product-form__item" style="display: {/% if product.has_only_default_variant %/} none;{/% endif %/}">
					<p>Variants</p>
					<label {/% if option.name == 'default' %/}class="label--hidden"
							{/% endif %/}for="SingleOptionSelector-{/{ forloop.index0 }/}">
							{/{ option.name }/}
					</label>
					<select name="id" data-productid="{/{ product.id }/}" id="ProductSelect-{/{ product.id }/}">
							{/% for variant in product.variants %/}
							<option price="{/{ variant.price | money }/}" value="{/{ variant.id }/}"
									{/%- if variant == current_variant %/} selected="selected" {/%- endif -%/}>
									{/{ variant.title }/} {/%- if variant.available == false %/} -
									{/{ 'products.product.sold_out' | t }/}{/% endif %/}
							</option>
							{/% endfor %/}
					</select>
			</div>


			<div class="product-form__controls-group">
					<div class="product-form__item">
							<div>Quantity</div>
							<input type="number" id="Quantity-{/{ section.id }/}" name="quantity" value="1" min="1"
									pattern="[0-9]*" class="product-form__input product-form__input--quantity"
									data-quantity-input>
					</div>
			</div>

			<button class="btn product-form__cart-submit btn--secondary-accent" type="submit" name="add"
					{/% if product.available %/}{/% else %/}disabled{/% endif %/}>
					{/% unless product.available %/}
					{/{ 'products.product.sold_out' | t }/}
					{/% else %/}
					{/{ 'products.product.add_to_cart' | t }/}
					{/% endunless %/}
			</button>
			{/{ form | payment_button }/}
			{/% endform %/}
	</div>
	<script>
		if(document.querySelector('#ProductSelect-{/{ product.id }/}')){
			const select_{/{ product.id }/} = document.querySelector('#ProductSelect-{/{ product.id }/}');
			select_{/{ product.id }/}.addEventListener('change', () => {
					const price = select_{/{ product.id }/}.selectedOptions[0].getAttribute('price')
					document.querySelector('#_product_price_{/{ product.id }/}').innerHTML = price;
			});
		}
	</script>
{/% endfor %/}

Show theme information in console with theme.html

Multiple currency selector

  {/% form 'currency' %/}
    {/{ form | currency_selector }/}
  {/% endform %/}
{/% form 'currency' %/}
  <select name="currency">
    {/% for currency in shop.enabled_currencies %/}
{/% if currency == cart.currency %/}
  <option selected="true" value="{/{ currency.iso_code }/}">{/{currency.iso_code}/} {/{currency.symbol}/}</option>
  {/% else %/}
  <option value="{/{ currency.iso_code }/}">{/{currency.iso_code}/} {/{currency.symbol}/}</option>
{/% endif %/}
    {/% endfor %/}
  </select>
{/% endform %/}
$('.shopify-currency-form select').on('change', function () {
  $(this).parents('form').submit();
});

Member only page

{/% unless customer %/}
    {/% if template contains 'customers' %/}
        {/% assign send_to_login = false %/}
    {/% else %/}
        {/% assign send_to_login = true %/}
    {/% endif %/}
{/% endunless %/}

{/% if send_to_login %/}
<meta content="0; url=/account/login?checkout_url=/" http-equiv="refresh" />
{/% else %/}
CONTENT
{/% endif %/}

Member only and special tagged customer only

{/% if customer %/}
  {/% for tag in customer.tags %/}
    {/% unless tag contains 'multipass'  %/}
      <script type="text/javascript">
        location.href = "/";
      </script>
    {/% endunless %/}
  {/% endfor %/}
  {/% if customer.tags.size == 0 %/}
	  <script type="text/javascript">
        location.href = "/";
      </script>
  {/% endif %/}
{/% else %/}
  <script type="text/javascript">
    location.href = "/";
  </script>
{/% endif %/}

Add recommend section in product page with alphinejs

<ul class="brandList" x-data="recommendData()" x-init="init()">
  <template x-for="recommend in recommendedProducts" :key="recommend.id">
    <li>
      <a :href="recommend.url">
        <div class="photoBox">
          <img :src="recommend.featured_image" :alt="recommend.title" />
        </div>
        <p class="ttl" x-text="recommend.title"></p>
        <p class="price" x-text="recommend.price"></p>
        <p class="tag" x-text="recommend.vendor"></p>
        {/% render 'sake-stars' %/}
      </a>
    </li>
  </template>
</ul>
<script>
  const URL =
    '{/{ routes.product_recommendations_url }/}' +
    '.json?product_id=' +
    '{/{ product.id }/}' +
    '&limit=4';
  function recommendData() {
    return {
      recommendedProducts: [],
      init() {
        fetch(URL)
          .then((response) => response.json())
          .then((response) => {
            this.recommendedProducts = response.products;
          });
      }
    };
  }
</script>

Use money formatter with multiple currenies

  • {/{ "{/{ this " }/}}/} => {/{ this }/} in html.
<script>
  var theme = {
  	moneyFormat: {/{ shop.money_format | json }/}
  }
  theme.moneyFormat.replace(/{/{ "{/{[a-zA-Z0-9_]*" }/}}/}/, p.price)
</script>

Cart attribute

Add cart attribute

  • cart note
<div>
  <label>Add a note to your order</label>
  <textarea name="note"></textarea>
</div>
<p class="cart-attribute__field">
  <label for="your-name">memo1</label>
  <textarea id="your-name" name="attributes[memo1]">
{/{ cart.attributes["memo1"] }/}</textarea
  >
</p>

<p class="cart-attribute__field">
  <label for="your-name">memo2</label>
  <textarea id="your-name" name="attributes[memo2]">
{/{ cart.attributes["memo2"] }/}</textarea
  >
</p>

Render added cart attribute

{/% if order.attributes %/}
<ul>
  {/% for attribute in order.attributes %/}
  <li><strong>{/{ attribute | first }/}</strong>: {/{ attribute | last }/}</li>
  <script>
    console.log('{/{ attribute }/}');
  </script>
  {/% endfor %/}
</ul>
{/% endif %/}
deliveryDate2020-10-31
deliveryTime16~18
deliveryCode1618

Add item to cart with fetch()

(function () {
  var addData = {
    id: 21373873027 /* for testing, change this to a variant ID on your store */,
    quantity: 1
  };

  fetch('/cart/add.js', {
    body: JSON.stringify(addData),
    credentials: 'same-origin',
    headers: {
      'Content-Type': 'application/json',
      'X-Requested-With':
        'xmlhttprequest' /* XMLHttpRequest is ok too, it's case insensitive */
    },
    method: 'POST'
  })
    .then(function (response) {
      return response.json();
    })
    .then(function (json) {
      /* we have JSON */
      console.log(json);
    })
    .catch(function (err) {
      /* uh oh, we have error. */
      console.error(err);
    });
})();

Create product handles array and render product list

{/% assign prodlist = '' %/}
{/% for item in items limit: limit %/}
    {/% prodlist = prodlist | append: item.handle | append: ';' %/}
{/% endfor %/}
{/% assign myproducts = prodlist | remove_last: ';' | split: ';' %/}
{/% for handle in myproducts %/}
  {/{ handle }/}
  {/{ all_products[ handle ].title }/}
{/% endfor%/}

Nested for loop get index

{/% for outerItem in outerItems %/}
    {/% assign outer_forloop = forloop %/}
    {/% for item in items%/}
        <div>{/{ outer_forloop.counter }/}.&nbsp;{/{ item }/}</div>
    {/% endfor %/}
{/% endfor %/}

Show tagged articles with limited number

{/% assign count = 1 %/}
{/% for article in blogs["handle"].articles %/}
  {/% if article.tags contains 'tag name' and count < 9 %/}
  {/% assign count = count | plus: 1 %/}
  <li><a href="{/{ article.url }/}"><div class="photo"><img src="{/{ article | img_url: 'master' }/}" alt="{/{ article.title }/}"></div><div class="txtInner">
    <p class="ttl">{/{ article.title }/}</p>
    <p>{/{ article.metafields.global.products }/}</p>
    <p class="link"><span>detail</span></p>
  </div></a></li>
  {/% endif %/}
{/% endfor %/}

Storefront search sytax

search

https://xxx.myshopify.com/search?q=classic+tag%3A女性%2C+adidas+product_type%3AACCESSORIES+vendor%3Aadidas&options%5Bprefix%5D=last classic tag:女性, adidas product_type:ACCESSORIES vendor:adidas

collection filter

https://xxx.myshopify.com/collections/all/SCA_STOREPICKUP_PRODUCT+男性+man

Show vendor list

{/% for product_vendor in shop.vendors limit: 6 %/}
  {/%- assign brand = collections[product_vendor] -%/}
  {/% if brand %/}
    <a class="py-3 mt-1 t-style-icon-size" href="{/{ product_vendor | url_for_vendor }/}">
      <img class="w-full" src="{/{ brand.image | img_url: 'master' }/}" alt="{/{ product_vendor }/}"/>
    </a>
  {/% endif %/}
{/% endfor %/}

Show multiple options selector in product page

<form action="/cart/add" method="post">
  {/% if product.variants.size > 1 %/} {/% if product.options[0] %/} {/% assign used = '' %/}
  <label for="select-one">{/{ product.options[0] }/}</label>
  <select id="select-one" onchange="letsDoThis()">
    {/% for variant in product.variants %/} {/% unless used contains variant.option1 %/}
    <option value="{/{ variant.option1 }/}">{/{ variant.option1 }/}</option>
    {/% capture used %/} {/{ used }/} {/{ variant.option1 }/} {/% endcapture %/} {/% endunless %/}
    {/% endfor %/}
  </select>
  {/% endif %/} {/% if product.options[1] %/} {/% assign used = '' %/}
  <label for="select-one">{/{ product.options[1] }/}</label>
  <select id="select-two" onchange="letsDoThis()">
    {/% for variant in product.variants %/} {/% unless used contains variant.option2 %/}
    <option value="{/{ variant.option2 }/}">{/{ variant.option2 }/}</option>
    {/% capture used %/} {/{ used }/} {/{ variant.option2 }/} {/% endcapture %/} {/% endunless %/}
    {/% endfor %/}
  </select>
  {/% endif %/} {/% if product.options[2] %/} {/% assign used = '' %/}
  <label for="select-one">{/{ product.options[2] }/}</label>
  <select id="select-three" onchange="letsDoThis()">
    {/% for variant in product.variants %/} {/% unless used contains variant.option3 %/}
    <option value="{/{ variant.option3 }/}">{/{ variant.option3 }/}</option>
    {/% capture used %/} {/{ used }/} {/{ variant.option3 }/} {/% endcapture %/} {/% endunless %/}
    {/% endfor %/}
  </select>
  {/% endif %/} {/% endif %/}
  <input
    type="hidden"
    name="id"
    id="product-select"
    value="{/{ product.variants.first.id }/}"
  />
</form>
<script>
  function letsDoThis() {
    {/% if product.options[0] %/}
      var opt1 = document.getElementById("select-one").value;{/% endif %/}{/% if product.options[1] %/}
      var opt2 = document.getElementById("select-two").value;{/% endif %/}{/% if product.options[2] %/}
      var opt3 = document.getElementById("select-three").value;{/% endif %/}var id = "";{/% for v in product.variants %/}
      if (
        opt1 == "{/{ v.option1 }/}"{/% if product.options[1] %/} && opt2 == "{/{ v.option2 }/}"{/% endif %/}{/% if product.options[2] %/} && opt3 == "{/{ v.option3 }/}"{/% endif %/}
      ) {
        var id = {/{ v.id }/};
        var price = "{/{ v.price | money }/}";
      }
    {/% endfor %/}
    if (id != "") {
      document.getElementById("product-select").value = id;
      document.getElementById("price").innerHTML = price;
    } else {
      document.getElementById("product-select").value = "";
      document.getElementById("price").innerHTML = "Unavailable";
    }
  }
</script>

Toggle variant options

document.addEventListener('DOMContentLoaded', () => {
  const stockObj = Array.from(document.querySelector('select[name="id"]').children).map(
    (ele, index) => {
      return {
        name: ele.innerText.replace(/\s-.+/, ''),
        disabled: ele.getAttribute('disabled') === 'disabled'
      };
    }
  );

  const toggleEnable = (ele, isEnable) => {
    if (isEnable) {
      ele.style.pointerEvents = '';
      ele.style.cursor = 'pointer';
    } else {
      ele.style.pointerEvents = 'none';
      ele.style.cursor = 'not-allowed';
    }
  };

  // 0-color, 1-size

  Array.from(
    document.querySelector('ul.SingleOptionSelectorJS-0').querySelectorAll('label')
  ).forEach((ele) => {
    ele.addEventListener('click', function () {
      const sizes = stockObj.filter((option) => option.name.includes(this.innerText));
      Array.from(
        document.querySelector('ul.SingleOptionSelectorJS-1').querySelectorAll('label')
      ).forEach((element, index) => {
        if (sizes[index].disabled) {
          toggleEnable(element, false);
        } else {
          toggleEnable(element, true);
        }
      });
    });
  });

  Array.from(
    document.querySelector('ul.SingleOptionSelectorJS-1').querySelectorAll('label')
  ).forEach((ele) => {
    ele.addEventListener('click', function () {
      const colors = stockObj.filter((option) => option.name.includes(this.innerText));
      const sizes = stockObj.filter((option) => option.name.includes(this.innerText));
      Array.from(
        document.querySelector('ul.SingleOptionSelectorJS-0').querySelectorAll('label')
      ).forEach((element, index) => {
        if (
          colors[index].disabled &&
          stockObj
            .filter((option) => option.name.includes(sizes[index].name.split('/')[0]))
            .every((option) => option.disabled)
        ) {
          toggleEnable(element, false);
        } else {
          toggleEnable(element, true);
        }
      });
    });
  });
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment