技術メモ

習得したIT技術の備忘録

【Nuxt3】モーダルコンポーネントを作成してみよう!

モーダルの実装は色々方法がありますが、↓w3schoolsの方法がシンプルでよさそうだったのでNuxt3で実装してみました。

www.w3schools.com

環境

  • Nuxt 3.14
  • ssr: false

ゴールのイメージ

gif:モーダル表示・非表示が繰り返している
ボタンをクリックするとモーダルが表示される

コード

// app.vue
<template>
  <div>
    <button @click="open = true">Open Modal</button>
    <Modal v-if="open" @close="open = false" />
  </div>
</template>

<script setup lang="ts">
const open = ref(false);
</script>

<style>
 省略
</style>
// modal.vue
<template>
  <div class="modal">
    <div class="modal-content">
      <span class="close" @click="$emit('close')">&times;</span>
      <p>Some text in the Modal..</p>
    </div>
  </div>
</template>
</script>

<style>
// styleは基本w3schoolsのExampleコピペ

/* The Modal (background) */
.modal {
  position: fixed; /* Stay in place */
  z-index: 1; /* Sit on top */
  left: 0;
  top: 0;
  width: 100%; /* Full width */
  height: 100%; /* Full height */
  overflow: auto; /* Enable scroll if needed */
  background-color: rgb(0,0,0); /* Fallback color */
  background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
}

/* Modal Content/Box */
.modal-content {
  background-color: #fefefe;
  margin: 15% auto; /* 15% from the top and centered */
  padding: 20px;
  border: 1px solid #888;
  width: 80%; /* Could be more or less, depending on screen size */
}

/* The Close Button */
.close {
  color: #aaa;
  float: right;
  font-size: 28px;
  font-weight: bold;
}

.close:hover,
.close:focus {
  color: black;
  text-decoration: none;
  cursor: pointer;
}
</style>

コード解説

モーダル(modal.vue)のstyleがどのような実装になっているか解説します

1.オーバーレイが画面全体にかかるようにposition: fixed;にする
class="modal"に、position: fixed;を設定することで、親要素の影響を受けずtop,left..等指定した場所に固定した要素を表示することができます

スクショ:画面全体に黒い透過(オーバーレイ)がかかった状態
オーバーレイがかかった状態

以下を指定することで、画面全体が覆われます

 // class="modal" のstyle
  left: 0;
  top: 0;
  width: 100%; /* Full width */
  height: 100%; /* Full height */

2.オーバーレイの子要素にモーダルを追加する

  <div class="modal">
 // position: fixed; で親要素から影響を受けない状態になっている
    <div class="modal-content">
  // 親要素(class="modal")が画面全体に表示されているため子要素を中央寄せすればOK 
    </div>
  </div>

以下で中央寄せしている

 // class="modal-content" のstyle
 margin: 15% auto; /* 15% from the top and centered */

さいごに

モーダルの表示にtransformを使用するとposition: fixedが効かなくなる問題にぶち当たって、なにかいい方法はないかな?と思っていたら今回の実装方法の記事にたどり着きました。

position: fixedが効かなくなる問題は以下の記事でわかりやすく解説されていました(ありがたい)

qiita.com

【TypeScript】構造的部分型 (structural subtyping)を理解する

構造的部分型 (structural subtyping)とは

プログラミング言語の派生型(継承関係にある型)の方式の1つ。

派生型の方式は公称型(Nominal Typing)と構造的部分型(structural subtyping)があり、 TypeScriptは構造的部分型(structural subtyping)を採用している。

構造的部分型は、型の構造を比較し置換可能かを判定する。

つまりどういうことか?

  • Fruits型とApple型を定義する

  • それぞれ別の型なので、Fruits型の変数にApple型の変数を代入すると型エラーが発生しそうだけどしない

  • それははぜか?

    • Fruits型とApple型の構造を比較すると、両方string型のnameプロパティがある(構造が同じ)
    • 構造的部分型では「型の構造を比較し置換可能かを判定する」
    • Fruits型とApple型が構造が同じのためFruits型の変数にApple型の変数を代入してもエラーにならない
type Fruits = {
  name: string;
};

type Apple = {
  name: string;
};

const apple: Apple = {
  name: "ジョナゴールド",
};

// Fruits型の変数にApple型の変数を代入する
const fruits: Fruits = apple; // コンパイルエラーにならない

構造の比較について

コンパイルエラーになるパターン

以下のパターンはコンパイルエラーになる。

  • なぜエラーになるのか?
    • Apple型はcolorプロパティを持っているが、Fruits 型にはない
    • Apple型に必要なプロパティがない(=構造が一致していない)ためエラーになる
type Fruits = {
  name: string;
};

type Apple = {
  name: string;
  color: string;
};

const fruits: Fruits = {
  name: "ジョナゴールド",
};

// Apple型の変数にFruits型の変数を代入する
const apple: Apple = fruits // エラー「プロパティ 'color' は型 'Fruits' にありませんが、型 'Apple' では必須です。」

コンパイルエラーにならないパターン

以下のパターンはコンパイルエラーにならない。

  • なぜエラーにならないのか?
    • 構造の一致は必要なプロパティがあるかどうかで判定している
    • Fruits型に必要なstring型のnameプロパティは、Apple型にある
    • Apple型はFruits型に不要なcolorプロパティを持っているが、Fruits型は定義している以外のプロパティはあってもなくてもよい
    • Fruits型必要なプロパティがあるためFruits型とApple型は構造が一致していると判定され、Fruits型の変数にApple型の変数を代入できる
type Fruits = {
  name: string;
};

type Apple = {
  name: string;
  color: string;
};

const apple: Apple = {
  name: "ジョナゴールド",
  color: "赤",
};

// Fruits型の変数にApple型の変数を代入する
const fruits: Fruits = apple; // コンパイルエラーにならない

// 代入はエラーにならないがアクセスはエラーになる
fruits.color  // エラー「プロパティ 'color' は型 'Fruits' に存在しません。」

参考

【vuelidate】requiredIfにreactiveを設定したい!!!!

vuelidateでrequiredIf(特定の条件のときだけ必須にする)に、reactiveな値を設定するのに結構苦戦したので、備忘録です。 (ドキュメントに書いてあったのでその通りにすればよかっただけ)

ゴールのイメージ

チェックボックス(リアクティブな値)がtrueの場合は、テキストボックスが必須になる。
falseの場合は、必須にならない。

入力フォームのgif。チェックボックスとテキストボックスが横並びに表示されている。チェックを入れるとテキストボックスの下に必須エラーが表示される。チェックを外すとエラーが非表示になる
ゴールのイメージ

検証環境

  • vue: 3.3.2

  • Composition API

  • @vuelidate/core:2.0.2

  • @vuelidate/validators": 2.0.2

ダメだった書き方

state.isCheckedがreactiveなのでこれで良いと思ったのですがダメでした。

<template>
  <div>
    <h1>Vuelidate</h1>
    <input type="checkbox" v-model="state.isChecked" @change="v$.mailAddress.$touch" />
    <input type="text" v-model="state.mailAddress" @blur="v$.mailAddress.$touch" />
    <div class="input-errors" v-for="error of v$.mailAddress.$errors" :key="error.$uid">
      <div>{{ error.$message }}</div>
    </div>
  </div>
</template>

<script lang="ts">
import { computed, defineComponent, ref } from 'vue'
import { reactive } from 'vue'
import { useVuelidate } from '@vuelidate/core'
import { required, requiredIf } from '@vuelidate/validators'

export default defineComponent({
  setup() {
    const state = reactive({
      isChecked: false,
      mailAddress: ''
    })

    const rules = {
      return {
        mailAddress: { required: requiredIf(() => state.isChecked)} 
      }
    })

    const v$ = useVuelidate(rules.value, state)

    return { state, v$ }
  }
}
</script>

正解

rulesをcomputedにする!
あとはチェックボックスに @change="v$.mailAddress.$touch"を設定するのもポイントでした。

    const rules = computed(() => {
      return {
        mailAddress: { required: requiredIf(() => state.isChecked)} 
      }
    })

ちゃんと公式に書いてあった

vuelidate-next.netlify.app

結論

公式ドキュメントをちゃんと読もう!

【Firebase Hosting】削除したプロジェクトを復元してデプロイするとError: HTTP Error: 404, Requested entity was not found.になる

Firebaseで一度削除して復元したプロジェクトにHostingデプロイすると、エラーになりデプロイできなくなる事象の調査と解消した方法をご紹介します。 (前提:プロジェクト削除前はデプロイできていた)

Error: HTTP Error: 404, Requested entity was not found.

ターミナル画面のスクショ。firebase deployコマンドの後404エラーが発生している

解消した方法

ターミナル画面のスクショ。firebae deployコマンドの後deploy completeと表示されている

結論からいうと「プロジェクトを復元した後1日待ったらデプロイできるようになった」です。
(原因がわかっていないので、1日待つことが解消の要因かどうかは断定できませんが私の場合はそうでした)

解決までの流れ
1日目 am11:00頃 プロジェクトを復元する
1日目 am12:00頃 デプロイすると404エラーになる。調べて色々試してみるが解消しない
2日目 am10:00頃 デプロイに成功する(この日は特に何もしていない。1日目の状態のまま)

解消するまでに試したこと

まずはプロジェクトが存在するか確認

以下コマンドで、デプロイ先のプロジェクトが存在するか確認する

$ firebase projects:list

↓ 結果にデプロイ対象の「プロジェクトが表示されている」「Project ID に(current)が表示されている」が確認できればOK
ターミナル画面のスクショ。firebase projects:listの実行結果が表示されている

対象のプロジェクトに(current)が表示されていない場合は、以下のコマンドで切り替える

$ firebase use (PROJECT_ID)

①firebase-toolsを最新にする

以下コマンドでツールを更新する

$ npm install -g firebase-tools

②firebaseプロジェクトを初期化する

以下コマンドで初期化

$ firebase init

③デプロイ ターゲットを設定する

以下コマンドでデプロイターゲットを設定

$ firebase target:apply hostign (TARGET_NAME) (RESOURCE_IDENTIFIER)
  • TARGET_NAME・・・デプロイ先である Hosting サイトの一意の名前(Hosting サイト内で被ってなければ好きな名前を付けていいよということ)
  • RESOURCE_IDENTIFIER・・・Firebase プロジェクトにリストされている Hosting サイトの SITE_ID
  • SITE_ID・・・デプロイした場合に表示される「SITE_ID.web.app」のドメイン部分

これは複数のHostingサイトがある場合に設定するものなので、今回は関係なさそうでしたmm
以下コマンドで設定をクリアしました。

$ firebase target:clear  hosting (TARGET_NAME)

上記色々試しましたが、冒頭に書いた通りなぜか一日待ったらデプロイできるようになりました。
プロジェクトを復元した場合にデプロイできるようになるまで何かしらのタイムラグがあるのか、単純にローカル環境の問題だったのか。。。謎です

参考サイト

apprythm.com

uyamazak.hatenablog.com

Firebase CLI リファレンス  |  Firebase Documentation

デプロイ ターゲット  |  Firebase Documentation

【e-Tax × freee】フリーランスエンジニアの確定申告に必要なものリスト

今年初めてe-Tax × freeeで確定申告を行いました。
来年も忘れないようにするために、e-Tax × freeeで確定申告する際に必要だったものをリスト化しました。
確定申告の詳細な手順はありませんのでご了承ください。
(freeeはとても親切なので、freeeの手順通りにやっていれば確定申告できると思います)

1.事前に用意しておくもの

2.毎月対応しておくもの

確定申告の際にまとめて入力しようとすると大変なので毎月入力しておくとよい

  • 売上の入力
  • 経費の入力
    • 家賃、通信費、光熱費
    • 携帯代
    • 技術書の書籍代、勉強会の参加費等

※家賃や携帯代等私用と共有している場合は、全額は経費にできないので注意
freee家事按分を登録する

3.確定申告の際に必要になるもの

初年度に発生するもの

freeeの手順に従えば取得できます。 freee電子申告開始ナビ 使い方ガイド

毎年必要なもの

  • 利用者識別番号
  • 電子証明書
  • 取引先(エージェント等)からの支払調書

(以下は加入の場合)

  • 掛金払込証明書の封書(10月位に届くはず)
  • 社会保険料(国民年金保険料)控除証明書のはがき(10月位に届くはず)
  • 社会保険料(国民健康保険)・納付額お知らせのはがき (1月末までに届くはず)
    ※届く日付は自治体や団体によって異なる可能性があります

*1:青色申告する場合は開業届の際にこの手続きも必要になる

*2:確定申告までにあればいいが、品薄になる可能性があるので早めに準備する

*3:オンライン申請だと、利用番号も即時発行されます

【Docker-Compose】(超ざっくり)ローカルにDocker環境を構築する方法

Docker-Composeを使用して、ローカルにDocker環境を構築する方法をご紹介します。
備忘録なので詳細なことは書いていません。予めご了承ください。

Dockerとは

コンテナ仮想化を用いてアプリケーションを開発・配置・実行するためのオープンソースソフトウェアあるいはオープンプラットフォームである(Wikiより)

Docker-Composeとは

複数のコンテナを定義し実行する Docker アプリケーションのためのツールです。(公式より)

1.環境を構築する

(1)Docker Desktop をインストールする

Mac に Docker Desktop をインストールする
Windows に Docker Desktop をインストールする

(2)docker-compose.yml を作成する

任意のディレクトリに以下ファイルを作成します。

// docker-compose.yml 
version: '3'
services:
  web:
    build: .
    ports:
    - "5000:5000"
    volumes:
    - .:/code
    - logvolume01:/var/log
    links:
    - redis
  redis:
    image: redis
volumes:
  logvolume01: {}

servicesに必要なサービスを列記していくイメージです。
詳細は以下参照
Compose ファイル・リファレンス

2.起動する

①docker-compose.yml の場所に移動する
②以下コマンドを実行する

docker-compose up

3.エラー時の対応

■docker-composeを実行したら「指定されたファイルが見つかりません。となる
→WSLからWSL2に更新することで解消されました
Windows 10 用 Windows Subsystem for Linux のインストール ガイド

※備忘録:WSL2でメモリ圧迫する場合は以下対応する
WSL2によるホストのメモリ枯渇を防ぐための暫定対処 - Qiita

参考サイト

Docker - Wikipedia
Docker Compose 概要 — Docker-docs-ja 19.03 ドキュメント
youtu.be

【Nuxt+Firebase Authentication 】ソーシャルログイン実装方法

Nuxt+Firebase Authenticationを使用したソーシャルログイン実装方法を紹介します。

完成イメージ

今回はGoogle アカウントを使った認証機能を行います。

ソーシャルログインの完成イメージ
完成イメージ

1.ソーシャルログインとは

ソーシャルログインとは、ユーザーが使い慣れた既存WebサービスやSNSのアカウントを利用して、別のWebサービスにログインできる仕組みのことです。

connpassのログイン画面(ソーシャルログインが導入されている)
connpassのログイン画面

面倒なアカウント作成から解放されユーザとしても嬉しい、そしてログイン処理を自分たちで書かなくていい!という開発者としても嬉しい仕組みです。

そんなソーシャルログインを簡単に実装できるのがFirebaseで提供されている Authenticationです。

Firebase Authentication は、バックエンド サービス、使いやすい SDK、アプリでのユーザー認証に使用できる UI ライブラリが用意されています。
パスワード、電話番号、一般的なフェデレーション ID プロバイダ(Google、Facebook、Twitter)などを使用した認証を行うことができます。

2.実装方法

2-1Firebaseでの作業

(1)Firebaseプロジェクトの作成する

①「プロジェクトを作成」を押下する

firebaseログイン後トップページ

②プロジェクト名を入力する
プロジェクト名入力画面

③「続行」を押下する
googleアナリティクス同意画面

④「プロジェクトを作成」を押下する
googleアナリティクス構成画面
プロジェクトが作成されます
プロジェクト作成完了

(2)Authentication の設定

①「Authentication」を選択する
f:id:tecmemo_y:20201206160412p:plain:w300

②「Sing-in-method」タブにて、ログイン許可するプロバイダを選択する
f:id:tecmemo_y:20201206160557p:plain:w300

③「有効にする」に変更し、「保存」を押下する
f:id:tecmemo_y:20201206160631p:plain:w300

Googoleが有効になります
f:id:tecmemo_y:20201206160715p:plain:w300

(3)ウェブ設定の確認

①「プロジェクトを設定」を選択する
f:id:tecmemo_y:20201206160857p:plain:w300

②「プロジェクト名」「ウェブAPIキー」をメモ帳等に控える
f:id:tecmemo_y:20201206160925p:plain:w300
2-2Nuxtプロジェクトでの作業で使用します。

2-2Nuxtプロジェクトでの作業

(1)Nuxtプロジェクトの作成

以下コマンドにてプロジェクトを作成する

npm init nuxt-app <project-name>

npx, yarnでも作成可能です。詳細は以下参照
インストール - NuxtJS

(2)Firebase SDK の追加

①「cd」コマンドで上記プロジェクトに遷移する
②以下コマンドにてFirebase SDK をインストールする

npm install --save firebase

CDNでも追加可能です。詳細は以下参照
Add Firebase to your JavaScript project

(3)ログイン処理の実装

①firebaseをインポートする

import firebase from 'firebase'

②ログイン処理を入れたい箇所に以下コードを追加する

      const config = {
        // 2-1Firebaseでの作業-(3)ウェブ設定の確認の値 
         apiKey: 'ウェブAPIキー', 
         authDomain: '(プロジェクト名).firebaseapp.com',
       }

      firebase.initializeApp(config)

      const provider = new firebase.auth.GoogleAuthProvider()
      firebase
        .auth()
        .signInWithPopup(provider) // ポップアップでログイン画面を表示 ※1
        .then(function (result) {
        // 成功時の処理 ※2
        })
        .catch(function (error) {
           // エラー時の処理
        })

※1 signInWithPopup(provider)をsignInWithRedirect(provider)に変更することで
ログイン画面をリダイレクトに変更できます。

※2 resultで取得できる値については以下参照
UserInfo  |  Identity Platform のドキュメント  |  Google Cloud

(4)ログアウト処理の実装

ログアウト処理を入れたい箇所に以下コードを追加する

      firebase
        .auth()
        .signOut()
        .then(function () {
          // 成功時の処理
        })
        .catch(function (error) {
          // エラー時の処理
        })
(5)ログイン状態確認処理の実装

ログイン状態確認処理を入れたい箇所に以下コードを追加する

    firebase.auth().onAuthStateChanged(function (user) {
      if (user) {
        // ログイン中
      }
(6)ログイン・ログアウト・状態確認の実装例
<template>
  <div>
    <section class="container">
      <h1>{{ title }}</h1>
      <button v-if="!isLogin" @click="onClickLogin">Login</button>
      <button v-if="isLogin" @click="onClickLogout">Logout</button>
      <table v-if="isLogin">
        <tr>
          <th>Name</th>
          <td>{{ name }}</td>
        </tr>
        <tr>
          <th>Email</th>
          <td>{{ email }}</td>
        </tr>
      </table>
    </section>
  </div>
</template>

<script>
import firebase from 'firebase'

export default {
  data() {
    return {
      title: 'Firebase Auth Sample',
      name: '',
      email: '',
      isLogin: false,
      config: {
         apiKey: 'ウェブAPIキー', 
         authDomain: '(プロジェクト名).firebaseapp.com',
      },
    }
  },
  created() {
    firebase.initializeApp(this.config)
    const self = this
    firebase.auth().onAuthStateChanged(function (user) {
      if (user) {
        self.isLogin = true
        self.email = user.email
        self.name = user.displayName
      }
    })
  },
  methods: {
    onClickLogin() {
      const provider = new firebase.auth.GoogleAuthProvider()
      const self = this
      firebase
        .auth()
        .signInWithPopup(provider)
        .then(function (result) {
          self.isLogin = true
          self.email = result.user.email
          self.name = result.user.displayName
        })
    },
    onClickLogout() {
      if (!confirm('ログアウトしますか?')) {
        return
      }
      const self = this
      firebase
        .auth()
        .signOut()
        .then(function () {
          self.isLogin = false
        })
        .catch(function (error) {
          console.log(error)
        })
    },
  },
}
</script>  

今回は1ファイルでまとめていますが、状態管理はVuexで管理した方が初期表示がきれいになると思います。

参考サイト

Authenticate Using Google Sign-In with JavaScript  |  Firebase

Vue.js&Nuxt.js超入門

Vue.js&Nuxt.js超入門