JavaScript

[Shopify][React]カスタムアプリでViewを表示する

import {
    View,
    Text,
    useSettings,
    reactExtension,
} from '@shopify/ui-extensions-react/checkout';

export default reactExtension("purchase.checkout.block.render", () => (
    <Extension />
));

function Extension() {
    const { title,left,border } = useSettings();
    const _title = title ? title : 'タイトル';
    const _left = left ? "start" : "center"
    const _border = border ? "none" : "base"
    return (
        <View display="block" padding="base" border={_border} cornerRadius="base" inlineAlignment={_left}>
            <Text size="base" emphasis="bold">{_title}</Text>
        </View>
    );
}

 
shopify.extension.tomlに追記

[extensions.settings]
[[extensions.settings.fields]]
key = "title"
type = "single_line_text_field"
name = "表示する文言"
description = "表示する文言を入力してください"
[[extensions.settings.fields]]
key = "left"
type = "boolean"
name = "テキストを左寄せにする"
[[extensions.settings.fields]]
key = "border"
type = "boolean"
name = "枠線を表示しない"

[Shopify][React]カスタムアプリでBannerを表示する

Checkout.jsx

import {
    Banner,
    TextBlock,
    reactExtension,
    useSettings,
} from '@shopify/ui-extensions-react/checkout';

export default reactExtension("purchase.checkout.block.render", () => (
    <Extension />
));
function Extension() {
    const { title,desc,banner_status } = useSettings();
    const _title = title ? title : '';
    const _desc = desc ? desc : '';
    const _banner_status = banner_status ? banner_status : 'warning';
    const descs = _desc.split("\n").map((item,i)=>{
    return <TextBlock size="base" appearance={_banner_status}>{item}</TextBlock>
    });
    return (
    <Banner title={_title} status={_banner_status}>
        {descs}
    </Banner>
    );
}

 

shopify.extension.tomlに追記

[extensions.settings]
[[extensions.settings.fields]]
key = "title"
type = "single_line_text_field"
name = "表示するタイトル"
description = "表示するタイトルを入力してください"
[[extensions.settings.fields]]
key = "desc"
type = "multi_line_text_field"
name = "表示する文言"
description = "表示する文言を入力してください"
[[extensions.settings.fields]]
key = "banner_status"
type = "single_line_text_field"
name = "バナーの状態"
[[extensions.settings.fields.validations]]
name = "choices"
value = '["info", "success","warning","critical"]'

[Shopify][React]カスタムアプリでCart.attributesを表示する

import {
  View,
  Text,
  Grid,
  GridItem,
  Link,
  Divider,
  useApi,
  useAttributeValues,
  useSettings,
  reactExtension,
} from '@shopify/ui-extensions-react/checkout';

export default reactExtension("purchase.checkout.block.render", () => (
  <Extension />
));

function Extension() {
  const { attributes_lines,hide_empty,hide_edit } = useSettings();
  const attributeKeys = attributes_lines ? attributes_lines.split("\n") : [];
  if(!attributeKeys.length){
    return '';
  }
  let i,j,tmp;
  let key,value;
  /* 指定キーのattributeを表示 */
  const attributeValues = attributeKeys.length ? useAttributeValues(attributeKeys) : [];
  const { shop } = useApi();
  const cartUrl = shop.storefrontUrl.replace(/\/$/,'') + '/cart';
  const rowSpan = attributeKeys.length + 1;
  let rows = [];
  let attributeKeysIndexList = [];
  for (i=0;i<attributeKeys.length;i++) {
    value = i < attributeValues.length ? attributeValues[i] : ' ';
    if(value){
      if(!hide_empty || (hide_empty && value.replace(/[  ]+/g,'') != '')){
        attributeKeysIndexList.push(i);
      }
    }
  }
  for (j=0;j<attributeKeysIndexList.length;j++) {
    i = attributeKeysIndexList[j];
    /* ラベル */
    key = attributeKeys[i];
    tmp = attributesAltLineKeyValues.find((elm) => elm[0] == key);
    if(tmp && 1 < tmp.length && tmp[1]){
      key = tmp[1];
    }
    /* 値 */
    value = i < attributeValues.length ? attributeValues[i] : ' ';
    rows.push(<View border="none" padding="base" key={`${j}_key`}><Text size="base">{key}</Text></View>);
    rows.push(<View border="none" padding="base" key={`${j}_value`}><Text size="base">{value}</Text></View>);
    if(j < 1 && !hide_edit){
      rows.push(
      <GridItem rowSpan={rowSpan} spacing="base" key={`${j}_griditem`}>
        <View border="none" padding="base" key={`${j}_grid`}>
          <Link to={cartUrl} padding="none">変更</Link>
        </View>
      </GridItem>
      );
    }
    if(1 < attributeKeysIndexList.length && j < attributeKeysIndexList.length-1){
      rows.push(
        <GridItem columnSpan={2} key={`${j}_divider`}>
          <Divider></Divider>
        </GridItem>
      );
    }
  }
  let gridColumns = hide_edit ? ['auto', 'fill'] : ['auto', 'fill', 'auto'];
  let gridRows = [];
  for(i=0;i<attributeKeysIndexList.length+(attributeKeysIndexList.length-1);i++){
    gridRows.push('auto');
  }
  return (
    <Grid
      border="base"
      cornerRadius='base'
      padding='none'
      columns={gridColumns}
      rows={gridRows}
    >
      {rows}
    </Grid>
  );
}

shopify.extension.toml

[extensions.settings]
[[extensions.settings.fields]]
key = "attributes_lines"
type = "multi_line_text_field"
name = "表示するattribute.key"
description = ""
[[extensions.settings.fields]]
key = "hide_attributes"
type = "multi_line_text_field"
name = "非表示条件のcart.attributesのキー:値"
description = ""
[[extensions.settings.fields]]
key = "hide_empty"
type = "boolean"
name = "値が空欄の項目を表示しない"
[[extensions.settings.fields]]
key = "hide_edit"
type = "boolean"
name = "変更リンクを表示しない"

[JavaScript]ShopifyCartAPIを叩く(update.js)

let sendData = {
    updates : {}
};
this.deleteItems.forEach((item,index)=>{
    sendData.updates[item.key] = 0;
});
let options = {
    method  : 'POST',
    credentials: 'same-origin',
    headers : {
        'Content-Type': 'application/json'
    },
    body    : JSON.stringify(sendData)
};
fetch(window.Shopify.routes.root+'cart/update.js',options)
.then((response)=>{
    if (!response.ok) {
        throw new Error();
    }
    return response.json;
})
.then((json)=>{
    this.finishFetch(2);
})
.catch((error)=>{
    console.log(error);
});

[JavaScript]ShopifyCartAPIを叩く(change.js)

let sendData = {
    id         : item.key,
    quantity   : item.quantity,
    properties : {}
};
item.properties.forEach((property,index)=>{
    sendData.properties['プロパティ'+(index+1)] = property.value;
});
let options = {
    method       : 'POST',
    credentials  : 'same-origin',
    headers : {
        'Content-Type' : 'application/json'
    },
    body         : JSON.stringify(sendData)
};
fetch(window.Shopify.routes.root+'cart/change.js',options)
.then((response)=>{
    if (!response.ok) {
        throw new Error();
    }
    return response.json;
})
.then((json)=>{
    this.finishFetch();
})
.catch((error)=>{
    console.log(error);
});

[Shopify][JavaScript]住所編集の国で都道府県更新

Liquid(HTML)

国<select name="address[country]" {% if form.country != blank %}data-default-label="{{ form.country }}{% else %}data-default="Japan{% endif %}">{{ country_option_tags }}</select><br>
都道府県<select name="address[province]" data-default-label="{{ form.province }}"></select>

JavaScript

document.querySelectorAll('[name="address[country]"]').forEach((elm,index)=>{
    elm.addEventListener('change',(e)=>{
        onChangeCountry(e);
    });
});
onChangeCountry(e){
    const option = e.target.querySelector('[value="'+e.target.value+'"]');
    const provinceSelect = e.target.form.querySelector('[name="address[province]"]');
    if(option && provinceSelect){
        let options = [];
        const provinces = JSON.parse(option.dataset['provinces']);
        provinces.forEach((province,index)=>{
            options.push('<option value="'+province[0]+'">'+province[1]+'</option>');
        });
        provinceSelect.innerHTML = options.join('');
    }
}

[JavaScript]ファイルサイズ取得

const options = {
   method  : "HEAD"
};
fetch(elm.href,options)
    .then((response)=>{
        if (!response.ok) {
            throw new Error();
        }
        return response;
    })
    .then((response)=>{
        const fileSize = response.headers.get("Content-Length");
        if(fileSize) {
            console.log(fileSize);
        }else{
            throw new Error();
        }
    })
    .catch((error)=>{
    });

[React][TypeScript]fetch

index.tsx

import React from 'react';
import ReactDOM from 'react-dom/client';
import FetchData from './FetchData';

const importantTarget = ReactDOM.createRoot(
  document.querySelector(".notice .notice-inner") as HTMLElement
);
importantTarget.render(<FetchData wrapper=".notice" url="/notice/?mode=important" />);

 
FetchData.tsx

import { useEffect,useState } from "react";
import parse from "html-react-parser";

type params = {
    wrapper  : string;
    url      : string;
};
function FetchData(props:params){
    const {wrapper,url} = props;
    const [html, setHtml] = useState<string>("");
    const wrapperElm:HTMLElement | null = document.querySelector(wrapper);

    useEffect(() => {
        fetch(url)
        .then((response)=>{
            if (!response.ok) {
                throw new Error();
            }
            return response.text();
        })
        .then((text) => {
            if(text.replace(/\s/g,'')){
                setHtml(text);
                if(wrapperElm){
                    wrapperElm.classList.remove("hide");
                }
            }
        })
        .catch((error) => {

        });
    }, []);

    return <>{html == "" ? "" : parse(html)}</>;
};
FetchData.defaultProps = {
    wrapper  : '',
    url      : '',
};
export default FetchData;

[JavaScript]分割代入備忘録

const d = {
    firstName : 'John',
    lastName  : 'Smith'
};
const t = `I'm ${d.firstName} ${d.lastName}.`;

const {firstName,lastName} = d;
const {firstName:namae,lastName:myoji} = d;

const d = {
    lastName  : 'Smith'
};
const {firstName = 'Jeff',lastName} = d;
const t = `I'm ${firstName} ${lastName}.`;

const d = ['John','Smith'];
const [firstName,lastName] = d;
const t = `I'm ${firstName} ${lastName}.`;

[JavaScript]アロー関数備忘録

const f = (v) => {
    return v;
};
const f = v => {
    return v;
};
const f = (v1,v2) => v1 + v2;
const f = (v1,v2) => (
    {
        firstName : v1,
        lastName  : v2
    }
)
const f = (v = 5) => {
    return v;
};