[JavaScript]Utils.js

/**
 * 共通処理
 *
 * @class Utils
 * @typedef {Utils}
 */
class Utils{
    /**
     * Creates an instance of Utils.
     *
     * @constructor
     */
    constructor() {}
    /**
     * ブラウザバック判定
     *
     * @static
     * @readonly
     * @type {boolean}
     */
    static get isBrowserBack(){
        const perfEntries = performance.getEntriesByType("navigation");
        let result = false;
        perfEntries.forEach((perfEntry) => {
            if(perfEntry.type == 'back_forward'){
                result = true;
            }
        });
        return result;
    }
    /**
     * URLが/challengeで終わるか否か
     *
     * @static
     * @readonly
     * @type {*}
     */
    static get isChallenge(){
        return /^.*\/challenge$/.test(location.pathname);
    }
    /**
     * 指定要素の指定イベント発火
     *
     * @static
     * @param {HTMLElement} element
     * @param {String} eventName
     * @returns {Boolean}
     */
    static triggerEvent(element, eventName){
        const evt = new CustomEvent(eventName, {bubbles:true,cancelable:true});
        return element.dispatchEvent(evt);
    }
    /**
     * 指定要素の指定イベント発火(古いバージョン)
     *
     * @static
     * @param {HTMLElement} element
     * @param {String} eventName
     * @returns {Boolean}
     */
    static triggerEventOld(element, eventName){
        const evt = document.createEvent('HTMLEvents')
        evt.initEvent(eventName, true, true);
        return element.dispatchEvent(evt);
    }
    /**
     * イベントキャンセル
     *
     * @static
     * @param {Event} e
     */
    static cancelEvent(e){
        e.stopPropagation();/* キャプチャおよびバブリング段階において現在のイベントのさらなる伝播を阻止します。しかし、これは既定の動作の発生を妨げるものではありません。 */
        e.preventDefault();/* ユーザーエージェントに、このイベントが明示的に処理されない場合に、その既定のアクションを通常どおりに行うべきではないことを伝えます */
        e.stopImmediatePropagation();/* 呼び出されている同じイベントの他のリスナーを抑止します。同じイベントタイプで複数のリスナーが同じ要素に装着されている場合、追加された順番に呼び出されます。もし、そのような呼び出しの最中に stopImmediatePropagation() が呼び出された場合、残りのリスナーは呼び出されなくなります。 */
    }
    /**
     * this.elements初期化
     *
     * @static
     * @param {*} instance
     * @param {*} target
     */
    static initDataElements(instance,target){
        if(!target)target = document;
        instance.elements.html = document.getElementsByTagName('html')[0];
        instance.elements.body = document.getElementsByTagName('body')[0];
        let key;
        for(key in instance.elements.selectors){
            this.updateDataElements(instance,key,target);
        }
    }
    /**
     * this.elements更新
     *
     * @static
     * @param {*} instance
     * @param {*} key
     * @param {*} target
     */
    static updateDataElements(instance,key,target){
        if(!target)target = document;
        const keys = typeof key === 'string' ? [key] : key;
        keys.forEach((_key,i)=>{
            if(instance.elements.selectors[_key]) {
                if (/^.+All$/.test(_key)) {
                    instance.elements[_key] = target.querySelectorAll(instance.elements.selectors[_key]);
                }else{
                    instance.elements[_key] = target.querySelector(instance.elements.selectors[_key]);
                    if (/^.+Clone$/.test(_key)) {
                        instance.elements[_key] = instance.elements[_key].cloneNode(true);
                    }
                }
            }
        });
    }
    /**
     * タグがtagNameと一致するか判定
     *
     * @static
     * @param {HTMLElement} elm
     * @param {*} tagName
     * @param {*} type
     * @returns {boolean}
     */
    static tagNameIs(elm,tagName,type){
        let result = false;
        if(Array.isArray(tagName)){
            result = tagName.includes(elm.tagName.toLowerCase());
        }else{
            result = elm.tagName.toLowerCase() === tagName.toLowerCase();
        }
        if(result && type){
            if(Array.isArray(type)){
                result = type.includes(elm.type.toLowerCase());
            }else{
                result = elm.type.toLocaleLowerCase() === type.toLocaleLowerCase();
            }
        }
        return result;
    }
    /**
     * nameで指定した要素へvalueを設定する
     *
     * @static
     * @param {String} name
     * @param {*} value
     * @param {*} target
     */
    static setValueByName(name,value,target){
        if(!target)target = document;
        const elms = target.querySelectorAll(`[name="${name}"]`);
        elms.forEach((elm,i)=>{
            elm.value = value;
        });
    }
    /**
     * queryで指定した要素へvalueを設定する
     *
     * @static
     * @param {String} query
     * @param {*} value
     * @param {*} target
     */
    static setValueBySelector(query,value,target){
        if(!target)target = document;
        const elms = target.querySelectorAll(query);
        elms.forEach((elm,i)=>{
            elm.value = value;
        });
    }
    /**
     * nodeListで指定した要素へvalueを設定する
     *
     * @static
     * @param {NodeList} nodeList
     * @param {*} value
     */
    static setValueByNodeList(nodeList,value){
        nodeList.forEach((elm,i)=>{
            elm.value = value;
        });
    }
    /**
     * nameで指定した要素のdisabledを設定する
     *
     * @static
     * @param {String} name
     * @param {Boolean} disabled
     * @param {*} target
     */
    static setDisabledByName(name,disabled,target){
        if(!target)target = document;
        const elms = target.querySelectorAll(`[name="${name}"]`);
        elms.forEach((elm,i)=>{
            elm.disabled = disabled;
        });
    }
    /**
     * nameで指定した要素がselectタグかどうか判定
     *
     * @static
     * @param {String} name
     * @param {*} target
     * @returns {boolean}
     */
    static isSelectByName(name,target){
        if(!target)target = document;
        const elms = target.querySelectorAll(`[name="${name}"]`);
        if(elms.length){
            return Utils.tagNameIs(elms[0],'select');
        }
        return false;
    }
    /**
     * nameで指定した要素がcheckboxかどうか判定
     *
     * @static
     * @param {String} name
     * @param {*} target
     * @returns {boolean}
     */
    static isCheckboxByName(name,target){
        if(!target)target = document;
        const elms = target.querySelectorAll(`[name="${name}"]`);
        if(elms.length){
            return Utils.tagNameIs(elms[0],'input','checkbox');
        }
        return false;
    }
    /**
     * nameで指定した要素がradioかどうか判定
     *
     * @static
     * @param {String} name
     * @param {*} target
     * @returns {boolean}
     */
    static isRadioByName(name,target){
        if(!target)target = document;
        const elms = target.querySelectorAll(`[name="${name}"]`);
        if(elms.length){
            return Utils.tagNameIs(elms[0],'input','radio');
        }
        return false;
    }
    /**
     * nameで指定した要素のvalueを配列で返す
     * glue指定の場合はglueでjoinした文字列を返す
     *
     * @static
     * @param {String} name
     * @param {*} target
     * @param {String} glue
     * @returns {*}
     */
    static getValuesByName(name,target,glue){
        return Utils.getValuesBySelector(`[name="${name}"]`,target,glue);
    }
    /**
     * selectorで指定した要素のvalueを配列で返す
     * glue指定の場合はglueでjoinした文字列を返す
     *
     * @static
     * @param {String} selector
     * @param {*} target
     * @param {String} glue
     * @returns {*}
     */
    static getValuesBySelector(selector,target,glue){
        if(!target)target = document;
        let values = [];
        const inputs = target.querySelectorAll(selector);
        inputs.forEach((input,i)=>{
            if(Utils.tagNameIs(input,'select') || (Utils.tagNameIs(input,'input',['checkbox','radio']) && input.checked) || (Utils.tagNameIs(input,'input') && !['checkbox','radio'].includes(input.type))){
                values.push(input.value);
            }
        });
        return typeof glue === 'undefined' ? values : values.join(glue);
    }
    /**
     * チェック済みのcheckbox,radioの値を取得
     *
     * @static
     * @param {String} inputName
     * @param {*} target
     * @returns {[]}
     */
    static getCheckedValuesByInputName(inputName,target){
        if(!target)target = document;
        let values = [];
        const inputs = target.querySelectorAll(`input[name="${inputName}"]`);
        inputs.forEach((input,i)=>{
            if(input.checked){
                values.push(input.value);
            }
        });
        return values;
    }
    /**
     * classListを更新するためのメソッドチェーン
     *
     * @static
     * @param {*} elm
     * @returns {{ elm: any; add: (value: any) => ...; remove: (value: any) => ...; update: (add: any, value: any) => ...; switch: (addToLeft: any, value: any) => ...; }}
     */
    static classList(elm){
        const obj = {
            elm   : typeof elm === 'string' ? document.querySelectorAll(elm) : elm,
            add   : function(value){
                if(this.elm){
                    if(NodeList.prototype.isPrototypeOf(this.elm) || Array.isArray(this.elm)){
                        this.elm.forEach((_elm,i)=>{
                            _elm.classList.add(value);
                        });
                    }else{
                        this.elm.classList.add(value);
                    }
                }
                return this;
            },
            remove : function(value){
                if(this.elm){
                    if(NodeList.prototype.isPrototypeOf(this.elm) || Array.isArray(this.elm)){
                        this.elm.forEach((_elm,i)=>{
                            _elm.classList.remove(value);
                        });
                    }else{
                        this.elm.classList.remove(value);
                    }
                }
                return this;
            },
            update : function(add,value){
                if(this.elm){
                    if(add){
                        if(NodeList.prototype.isPrototypeOf(this.elm) || Array.isArray(this.elm)){
                            this.elm.forEach((_elm,i)=>{
                                _elm.classList.add(value);
                            });
                        }else{
                            this.elm.classList.add(value);
                        }
                    }else{
                        if(NodeList.prototype.isPrototypeOf(this.elm) || Array.isArray(this.elm)){
                            this.elm.forEach((_elm,i)=>{
                                _elm.classList.remove(value);
                            });
                        }else{
                            this.elm.classList.remove(value);
                        }
                    }
                }
                return this;
            },
            switch : function(addToLeft,value){
                if(this.elm){
                    if(NodeList.prototype.isPrototypeOf(this.elm) || Array.isArray(this.elm)){
                        if(1 < this.elm.length){
                            if(addToLeft){
                                this.elm[0].classList.add(value);
                                this.elm[1].classList.remove(value);
                            }else{
                                this.elm[0].classList.remove(value);
                                this.elm[1].classList.add(value);
                            }
                        }
                    }
                }
                return this;
            }
        };
        return obj;
    }
    /**
     * initWithScrollY
     *
     * @static
     */
    static initWithScrollY(){
        let scrollY = localStorage.getItem('scrollKey');
        if(!scrollY){
            return;
        }
        localStorage.removeItem('scrollKey');
        scrollY = Number(scrollY);
        window.scrollTo(0, scrollY);
    }
    /**
     * スクロール位置を保存しつつリロード
     *
     * @static
     * @param {*} delay
     */
    static reloadWithScrollY(delay){
        if(delay){
            setTimeout(()=>{
                Utils.reloadWithScrollY();
            },delay);
        }else{
            localStorage.setItem('scrollKey',window.scrollY);
            location.reload();
        }
    }
    /**
     * スクロール位置を保存
     *
     * @static
     */
    static setScrollYToLS(){
        localStorage.setItem('scrollKey',window.scrollY);
    }
    /**
     * ローカルストレージ保存
     *
     * @static
     */
    static setLS(key,value){
        localStorage.setItem(key,value);
    }
    /**
     * ローカルストレージ取得
     *
     * @static
     * @param {String} key
     * @param {Boolean} remove
     * @returns {*}
     */
    static getLS(key,remove){
        const value = localStorage.getItem(key);
        if(remove){
            localStorage.removeItem(key);
        }
        return value;
    }
    /**
     * fetch
     * 
     * @static
     * @param {String} url
     * @returns {Promise}
     */
    static fetch(url){
        return new Promise((resolve,reject) => {
            fetch(url)
                .then((response)=>{
                    if (!response.ok) {
                        throw new Error();
                    }
                    return response.text();
                })
                .then((text)=>{
                    let hasError = false;
                    let obj;
                    try {
                        obj = JSON.parse(text.replace(/<section[^>]+>/,'').replace(/<\/section>/,'').replace(/\s/g,''));
                    } catch (error) {
                        hasError = true;
                    }
                    if(hasError){
                        reject(false);
                    }else{
                        resolve(obj);
                    }
                })
                .catch((error)=>{
                    reject(false);
                });
        });
    }
}

[TailwindCSS]4系のセットアップ

npm

npm install tailwindcss @tailwindcss/vite

 
vite.config.ts

import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
  plugins: [
    tailwindcss(),
  ],
})

 
CSS

@import "tailwindcss";

 
下記は3系で動く

npx tailwindcss init -p

 
4系で上記を実行すると下記エラーが出る

npm error could not determine executable to run

 
https://tailwindcss.com/docs/installation/using-vite
 
cheatsheet
https://www.creative-tim.com/twcomponents/cheatsheet/

[JavaScript]lodash

npm i lodash
npm i --save-dev @types/lodash

 
ts
全部import

import _ from "lodash";

const list:number[] = [1,2,3,4,5];
const list2:number[] = _.shuffle(list);
console.log(list2);

 
一部import

import {shuffle} from "lodash";

const list:number[] = [1,2,3,4,5];
const list2:number[] = shuffle(list);
console.log(list2);

 
https://lodash.com/

[TypeScript]Auto-Accessor Decorator

class Person {
  @accessor
  name: string;

  constructor(name: string) {
    this.name = name;
  }
}

const person = new Person("John");
console.log(person.name); // John
person.name = "Jeff";
console.log(person.name); // Jeff

 
バリデーション付き Auto-Accessor

function MinLength(length: number) {
  return (target: any, context: ClassAccessorDecoratorContext) => {
    return {
      set(value: string) {
        if (value.length < length) {
          throw new Error(`Value must be at least ${length} characters long.`);
        }
        return value;
      }
    };
  };
}

class User {
  @accessor @MinLength(3)
  username: string;

  constructor(username: string) {
    this.username = username;
  }
}

const user = new User("John");
console.log(user.username); // John

user.username = "Bob"; // OK
// user.username = "Al"; // エラー: Value must be at least 3 characters long.

 
ログ出力付き Auto-Accessor

function LogAccessor(target: any, context: ClassAccessorDecoratorContext) {
  return {
    get(value: any) {
      console.log(`Getting value: ${value}`);
      return value;
    },
    set(value: any) {
      console.log(`Setting value to: ${value}`);
      return value;
    }
  };
}

class Product {
  @accessor @LogAccessor
  price: number;

  constructor(price: number) {
    this.price = price;
  }
}

const product = new Product(1000);
console.log(product.price); // Getting value: 1000, 1000

product.price = 1500; // Setting value to: 1500
console.log(product.price); // Getting value: 1500, 1500

[TypeScript]accessor

class Article{
    accessor title:string = "";
}

上記は下記と同じこと

class Article{
    #title:string = "";
    get title():string{
        return this.#title;
    }
    set title(value:string){
        this.#title = value;
    }
}

[Laravel Sail]Stripeでテスト決済#1

chashierインストール

sail composer require laravel/cashier

 
.envに下記追記

STRIPE_KEY="公開可能キー"
STRIPE_SECRET="シークレットキー"

キーは下図赤枠に記載されている

 
下図の赤枠をオンにしておく

 
StripeController作成

sail artisan make:controller StripeController

 
下記追記

use Stripe\Stripe;
use Stripe\Charge;

class StripeController extends Controller
{
    public function list()
    {
        return view('stripe.index');
    }

    public function charge(Request $request)
    {
        Stripe::setApiKey(env('STRIPE_SECRET'));
        $charge = Charge::create([
            'amount'   => 1000,
            'currency' => 'jpy',
            'source'   => request()->stripeToken,
        ]);
        return back();
    }
}

 
views/stripe/index.blade.phpを作成

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Stripe一覧表示') }}
        </h2>
    </x-slot>

    <div class="mx-auto px-6">
        {{-- @if(session('message'))
            <div class="text-red-600 font-bold">{{session('message')}}</div>
        @endif --}}
        <x-message :message="session('message')" />
        <div class="mt-4 p-8 bg-white w-full rounded-2xl">
            <h1 class="p-4 text-lg font-semibold">テスト決済1000円</h1>
            <hr class="w-full">
            <form action="{{route('stripe.charge')}}" method="POST">
                @csrf
                <script
                src="https://checkout.stripe.com/checkout.js" class="stripe-button"
                data-key="{{ env('STRIPE_KEY') }}"
                data-amount="1000"
                data-name="お支払い画面"
                data-label="テスト決済する"
                data-description="現在はデモ画面です"
                data-image="https://stripe.com/img/documentation/checkout/marketplace.png"
                data-locale="auto"
                data-currency="JPY">
                </script>
            </form>
        </div>
    </div>
</x-app-layout>

 
routes/web.phpに下記追記

use App\Http\Controllers\StripeController;
Route::post('/stripe/charge', [StripeController::class,'charge'])->name('stripe.charge');
Route::get('/stripe', [StripeController::class,'list'])->name('stripe.index');

 
localhost/stripeにアクセス後「テスト決済する」押下して下記を入力
適当なメールアドレス
4242424242424242
適当な日付 3桁の数字

 
決済に成功するとダッシュボードで確認出来る