20210127のvue.jsに関する記事は9件です。

コピペでOK!?大学に通いながらプログラミングを勉強している、貧弱エンジニアによるVue.js+Firebase+Stripeを使ったサブスクリプション講義(セットアップ&実装偏)

大分タイトルが長くなりました。

皆さんこんにちは!

今や第4次産業革命とも呼ばれている「サブスクリプション」。

知らない人はいませんよね???

僕自身アマゾンプライム、ネットフリックス、U-NEXTのサブスクリプションに加入しております。(洋画と海外ドラマめちゃ好き。一番好きなのはプリズンブレイクです。好きな俳優はジェイソンステイサムです。)

元々、サブスクリプションというものは、企業の利益をより確実に財務表などに反映させるために作られたサービスです。これをすることで、リスクをある程度回避したうえで、株主や投資家といったお偉いさんたちにお話をできるわけです。

今やあの世界的に有名な「ポルシェ」もサブスクリプションを始めています。

まぁ、サブスクリプションの話はこの辺にしておきます。

今回はVue.js、Firebase、Stripeを使って、サブスクリプションの記事を書いていこうかなと思います。

まず初めに、@mogmetさんが書いた記事

Firebase ExtensionsのRun Subscription Payments with Stripeを使ってサブスク課金をコードを書かずに実装する

を参考にさせて頂きました。

とても分かりやすかったです!!

まずは、こちらの記事を見て下さい。

こちらの記事を見た艇で話を進めていきます。

なぜこの記事を書こうと思ったかと言うと、Vue.jsで実行するとなると色々変えなければいけないポイントがあったからです。

記事は見たはいいものの、実装できない!

とか

これから導入していきたい!

と考えている方に丁寧に解説していくので、肩の力を抜いてご覧ください。

ただし、1つ1つの動作が何を意味をしているかはキリがないので説明致しません。

それでは一緒に説明を見ていきましょう!!!

1, スクリプトファイルの読み込み

headタグにStripeのスクリプトファイルを読み込みます。

index.html
<script src="https://js.stripe.com/v3"></script>

また、特定のページだけ読み込みたいという方は、こちらの記事にやり方が書いてあるのでぜひご覧ください!

Vue.jsでページごとにhead,meta,titleを変える方法

2, HTML要素、template要素の配置

こちら、最初だからと言って舐めない方が良いです。

僕は、このHTML要素について十分に理解していなかったので中々時間取られました。

最初に言っておきます。

template要素は必ずindex.htmlに書いてください!

絶対に.vueファイルのtemplate内に書かないでください!!!

index.html
<body>
    <template id="product">
      <div class="product">
        <img src="" alt="" />
        <h2>name</h2>
        <p class="description">description</p>
        <form class="product-form">
          <label for="price">Choose pricing plan</label>
          <select id="price" name="price"></select>
          <button type="submit">Subscribe</button>
        </form>
      </div>
    </template>
</body>
credit.vue
<template>
<button id="signout" type="button">Sign out</button>
    <div id="subscribe">
      <h2>Subscribe</h2>
      <div class="test-card-notice">
        Use any of the
        <a href="https://stripe.com/docs/testing#cards" target="_blank" rel="noopener noreferrer">Stripe test cards </a>
        for this demo, e.g.
        <div class="card-number">4242<span></span>4242<span></span>4242<span></span>4242</div>
      </div>
      <section class="products"></section>
    </div>
    <section id="my-subscription">
      <h2>My subscription</h2>
      <p></p>
      <h3>View invoices, update subscription & payment methods</h3>
      <button id="billing-portal-button" @click="subscribeApply">Access customer portal</button>
    </section>

    <footer>
      <p>
        Made with ❤️ and ☕️ by Stripe & Firebase | View
        <a
          href="https://github.com/stripe-samples/firebase-subscription-payments"
          target="_blank"
          rel="noopener noreferrer"
          >source
        </a>
      </p>
    </footer>
</template>

もう一回言います。<template id="product">index.htmlに書かないと後々沼にハマるのでご注意ください。

3, dataプロパティに変数を宣言

stripedbcurrentUserと言う名前の変数をdataプロパティに宣言します。

credit.vue
<script>
export default {
  data() {
    return {
      stripe: null,
      db: null,
      currentUser: null
    }
  },
</script>

4, コンテンツの表示

次に、Stripeで追加したコンテンツ(商品)を表示していきます。

Stripeに商品を表示すると、以下のようなproductsテーブルが作成されると思います。

無題.png

現時点でcustomersテーブルはあっても無くても構いません。

無くても焦らずに読み進めて下さい。

productsテーブルが作成されない場合は、設定段階でなんらかの記入ミスがあるので、見返してみてください。

そして、startDataListenersと言う名前の関数をmethods内に作ります。

credit.vue
<script>
export default {
  data() {
    return {
      stripe: null,
      db: null,
      currentUser: null
    }
  },
  methods: {
    async startDataListeners() {
      const products = document.querySelector('.products')
      const template = document.querySelector('#product')

      await this.db
        .collection('products')
        .where('active', '==', true)
        .get()
        .then((querySnapshot) => {
          querySnapshot.forEach((doc) => {
            doc.ref.collection('prices').orderBy('unit_amount').get().then((priceSnap) => {
              if (!('content' in document.createElement('template'))) {
                console.error('Your browser doesn’t support HTML template elements.')
              }

              const product = doc.data()
              const container = template.content.cloneNode(true)

              container.querySelector('h2').innerText = product.name.toUpperCase()
              container.querySelector('.description').innerText = product.description?.toUpperCase() || ''

              // Prices dropdown
              priceSnap.docs.forEach((doc) => {
                const priceId = doc.id
                const priceData = doc.data()
                const content = document.createTextNode(`${priceData.unit_amount}`)
                const option = document.createElement('option')
                option.value = priceId
                option.appendChild(content)
                container.querySelector('#price').appendChild(option)
              })

              if (product.images.length) {
                const img = container.querySelector('img')
                img.src = product.images[0]
                img.alt = product.name
              }

              const form = container.querySelector('form')
              form.addEventListener('submit', this.createStripeCustomers)

              products.appendChild(container)
            })
          })
        })
      // Get all subscriptions for the customer
      this.db
        .collection('customers')
        .doc(this.currentUser)
        .collection('subscriptions')
        .where('status', 'in', ['trialing', 'active'])
        .onSnapshot(async (snapshot) => {
          if (snapshot.empty) {
            // Show products
            document.querySelector('#subscribe').style.display = 'block'
            return
          }
        })
    },
  },
}
</script>

ここで、const template = document.querySelector('#product')に注目。

先ほど口うるさく言った、「<template id="product">はindex.html内に書いて!」とはこのことです。

これをindex.html内に書かないと、document.querySelector('#product')nullの値を返します。

正直なぜかは分かりません。

分かる方がいたら、ぜひコメント欄にてご教えて下さると幸いです。

5, customersテーブルの作成、およびカード情報登録画面へ遷移

次に、customersテーブルを作成していきます。

下記のボタンを押したときの処理になります。

無題.png

createStripeCustomersと言う名前の関数をmethods内に作成します。

コードが長くなるので、これまでのコードは書かないので、ご了承ください。

間違って消さないように。

credit.vue
async createStripeCustomers(event) {
      event.preventDefault()

      document.querySelectorAll('button').forEach((b) => (b.disabled = true))
      const formData = new FormData(event.target)

      const docRef = await this.db
        .collection('customers')
        .doc(this.currentUser)
        .collection('checkout_sessions')
        .add({
          price: formData.get('price'),
          allow_promotion_codes: true,
          tax_rates: [process.env.VUE_APP_STRIPE_TAX_RATES],
          success_url: process.env.VUE_APP_BASIC_URL + '/user/account?isActive=0',
          cancel_url: process.env.VUE_APP_BASIC_URL + '/creditcardregistration',
          metadata: {
            tax_rate: '10% sales tax exclusive'
          }
        })
        // Wait for the CheckoutSession to get attached by the extension
        docRef.onSnapshot((snap) => {
        const { error, sessionId } = snap.data()
        if (error) {
          // Show an error to your customer and then inspect your function logs.
          alert(`An error occured: ${error.message}`)
          document.querySelectorAll('button').forEach((b) => (b.disabled = false))
        }
        if (sessionId) {
          // We have a session, let's redirect to Checkout
          // Init Stripe
          const stripe = window.Stripe(process.env.VUE_APP_STRIPE_PUBLIC_KEY)
          stripe.redirectToCheckout({ sessionId })
        }
        return false
      })
    },

ここで、注意すべき点はtax_ratesです。

何を注意するのかと言うと、[process.env.VUE_APP_STRIPE_TAX_RATES]を配列として書いていることです。

なぜかは、憶測に過ぎないのですが、国によって税率が違うからだと思います。

悪魔で憶測です。鵜呑みにはしないでくださいね。

また、process.envなんて初めて見たという方は、こちらの記事

初心者必見!固定値(キー、URLなど)は.envファイルに書いて再利用しよう!!

をご覧ください!

環境変数(process.env)は非常に便利なので、ぜひこの機会にご利用ください!!!

6, 請求書ページに遷移

最後に、下記のボタンを押したときの処理を書いてきます。

無題.png

当然のように、クレジットカードを利用している人が請求書を見れるようにしないといけません。

viewInvoiceと言う名前の関数をmethods内に作成します。

これも今までのコードは書かないのでご了承ください。

間違って消さないように。。。

credit.vue
async viewInvoice(event) {
      this.isLoading = true
      event.preventDefault()

      document.querySelectorAll('button').forEach((b) => (b.disabled = true))

      // Call billing portal function
      const functionRef = firebase
        .app()
        .functions(process.env.VUE_APP_FUNCTION_ROCATION)
        .httpsCallable('ext-firestore-stripe-subscriptions-createPortalLink')
      const { data } = await functionRef({ returnUrl: window.location.origin })
      window.location.assign(data.url)
    },

process.env.VUE_APP_FUNCTION_ROCATIONにはFirebaseで最初に設定した地域を指定します。

日本なら「asia-northeast」ですね。

これで終わりです!!

本当にコピペして頂くだけで、実行できます!

ちなみに、クレジットカードを登録しているかの確認は色々とあるのですが、下記の画像を見て下さい。

・クレジット決済を行っていない場合

無題.png

・クレジット決済を行っている場合

無題.png

クレジット決済を行っている場合は、subscriptionsテーブルが作成されていますね。

このようにして判断をすることもできます。

また、こんなことをしなくても「firebase-functions」を自分で作成することで判断することもできます。

その際は、下記の記事が参考になると思うので、時間がある方はぜひご覧ください!

さぁ、firebaseでユーザーが管理者かどうかの判別を行おう!

いかがだったでしょうか?

Stripeの導入は初の試みだったので、すごく大変でした。

ただ、これから学習を進めていく方々にはあまり時間を無駄にしてほしくはないので、この記事をみながら第4次産業革命の波に乗っていきましょう!!

以上、「コピペでOK!?大学に通いながらプログラミングを勉強している、貧弱エンジニアによるVue.js+Firebase+Stripeを使ったサブスクリプション講義」でした!

良ければ、LGTM、コメントお願いします。

また、何か間違っていることがあればご指摘頂けると幸いです。

他にも毎日初心者さん向けに記事を投稿しているので、時間があれば他の記事も見て下さい!!

Thank you for reading

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コピペでOK!?大学に通いながらプログラミングを勉強している、貧弱エンジニアによるVue.js+Firebase+Stripeを使ったサブスクリプション講義

大分タイトルが長くなりました。

皆さんこんにちは!

今や第4次産業革命とも呼ばれている「サブスクリプション」。

知らない人はいませんよね???

僕自身アマゾンプライム、ネットフリックス、U-NEXTのサブスクリプションに加入しております。(洋画と海外ドラマめちゃ好き。一番好きなのはプリズンブレイクです。好きな俳優はジェイソンステイサムです。)

元々、サブスクリプションというものは、企業の利益をより確実に財務表などに反映させるために作られたサービスです。これをすることで、リスクをある程度回避したうえで、株主や投資家といったお偉いさんたちにお話をできるわけです。

今やあの世界的に有名な「ポルシェ」もサブスクリプションを始めています。

まぁ、サブスクリプションの話はこの辺にしておきます。

今回はVue.js、Firebase、Stripeを使って、サブスクリプションの記事を書いていこうかなと思います。

まず初めに、@mogmetさんが書いた記事

Firebase ExtensionsのRun Subscription Payments with Stripeを使ってサブスク課金をコードを書かずに実装する

を参考にさせて頂きました。

とても分かりやすかったです!!

まずは、こちらの記事を見て下さい。

こちらの記事を見た艇で話を進めていきます。

なぜこの記事を書こうと思ったかと言うと、Vue.jsで実行するとなると色々変えなければいけないポイントがあったからです。

記事は見たはいいものの、実装できない!

とか

これから導入していきたい!

と考えている方に丁寧に解説していくので、肩の力を抜いてご覧ください。

ただし、1つ1つの動作が何を意味をしているかはキリがないので説明致しません。

それでは一緒に説明を見ていきましょう!!!

1, スクリプトファイルの読み込み

headタグにStripeのスクリプトファイルを読み込みます。

index.html
<script src="https://js.stripe.com/v3"></script>

また、特定のページだけ読み込みたいという方は、こちらの記事にやり方が書いてあるのでぜひご覧ください!

Vue.jsでページごとにhead,meta,titleを変える方法

2, HTML要素、template要素の配置

こちら、最初だからと言って舐めない方が良いです。

僕は、このHTML要素について十分に理解していなかったので中々時間取られました。

最初に言っておきます。

template要素は必ずindex.htmlに書いてください!

絶対に.vueファイルのtemplate内に書かないでください!!!

index.html
<body>
    <template id="product">
      <div class="product">
        <img src="" alt="" />
        <h2>name</h2>
        <p class="description">description</p>
        <form class="product-form">
          <label for="price">Choose pricing plan</label>
          <select id="price" name="price"></select>
          <button type="submit">Subscribe</button>
        </form>
      </div>
    </template>
</body>
credit.vue
<template>
<button id="signout" type="button">Sign out</button>
    <div id="subscribe">
      <h2>Subscribe</h2>
      <div class="test-card-notice">
        Use any of the
        <a href="https://stripe.com/docs/testing#cards" target="_blank" rel="noopener noreferrer">Stripe test cards </a>
        for this demo, e.g.
        <div class="card-number">4242<span></span>4242<span></span>4242<span></span>4242</div>
      </div>
      <section class="products"></section>
    </div>
    <section id="my-subscription">
      <h2>My subscription</h2>
      <p></p>
      <h3>View invoices, update subscription & payment methods</h3>
      <button id="billing-portal-button" @click="subscribeApply">Access customer portal</button>
    </section>

    <footer>
      <p>
        Made with ❤️ and ☕️ by Stripe & Firebase | View
        <a
          href="https://github.com/stripe-samples/firebase-subscription-payments"
          target="_blank"
          rel="noopener noreferrer"
          >source
        </a>
      </p>
    </footer>
</template>

もう一回言います。<template id="product">index.htmlに書かないと後々沼にハマるのでご注意ください。

3, dataプロパティに変数を宣言

stripedbcurrentUserと言う名前の変数をdataプロパティに宣言します。

credit.vue
<script>
export default {
  data() {
    return {
      stripe: null,
      db: null,
      currentUser: null
    }
  },
</script>

4, コンテンツの表示

次に、Stripeで追加したコンテンツ(商品)を表示していきます。

Stripeに商品を表示すると、以下のようなproductsテーブルが作成されると思います。

無題.png

現時点でcustomersテーブルはあっても無くても構いません。

無くても焦らずに読み進めて下さい。

productsテーブルが作成されない場合は、設定段階でなんらかの記入ミスがあるので、見返してみてください。

そして、startDataListenersと言う名前の関数をmethods内に作ります。

credit.vue
<script>
export default {
  data() {
    return {
      stripe: null,
      db: null,
      currentUser: null
    }
  },
  methods: {
    async startDataListeners() {
      const products = document.querySelector('.products')
      const template = document.querySelector('#product')

      await this.db
        .collection('products')
        .where('active', '==', true)
        .get()
        .then((querySnapshot) => {
          querySnapshot.forEach((doc) => {
            doc.ref.collection('prices').orderBy('unit_amount').get().then((priceSnap) => {
              if (!('content' in document.createElement('template'))) {
                console.error('Your browser doesn’t support HTML template elements.')
              }

              const product = doc.data()
              const container = template.content.cloneNode(true)

              container.querySelector('h2').innerText = product.name.toUpperCase()
              container.querySelector('.description').innerText = product.description?.toUpperCase() || ''

              // Prices dropdown
              priceSnap.docs.forEach((doc) => {
                const priceId = doc.id
                const priceData = doc.data()
                const content = document.createTextNode(`${priceData.unit_amount}`)
                const option = document.createElement('option')
                option.value = priceId
                option.appendChild(content)
                container.querySelector('#price').appendChild(option)
              })

              if (product.images.length) {
                const img = container.querySelector('img')
                img.src = product.images[0]
                img.alt = product.name
              }

              const form = container.querySelector('form')
              form.addEventListener('submit', this.createStripeCustomers)

              products.appendChild(container)
            })
          })
        })
      // Get all subscriptions for the customer
      this.db
        .collection('customers')
        .doc(this.currentUser)
        .collection('subscriptions')
        .where('status', 'in', ['trialing', 'active'])
        .onSnapshot(async (snapshot) => {
          if (snapshot.empty) {
            // Show products
            document.querySelector('#subscribe').style.display = 'block'
            return
          }
        })
    },
  },
}
</script>

ここで、const template = document.querySelector('#product')に注目。

先ほど口うるさく言った、「<template id="product">はindex.html内に書いて!」とはこのことです。

これをindex.html内に書かないと、document.querySelector('#product')nullの値を返します。

正直なぜかは分かりません。

分かる方がいたら、ぜひコメント欄にてご教えて下さると幸いです。

5, customersテーブルの作成、およびカード情報登録画面へ遷移

次に、customersテーブルを作成していきます。

下記のボタンを押したときの処理になります。

無題.png

createStripeCustomersと言う名前の関数をmethods内に作成します。

コードが長くなるので、これまでのコードは書かないので、ご了承ください。

間違って消さないように。

credit.vue
async createStripeCustomers(event) {
      event.preventDefault()

      document.querySelectorAll('button').forEach((b) => (b.disabled = true))
      const formData = new FormData(event.target)

      const docRef = await this.db
        .collection('customers')
        .doc(this.currentUser)
        .collection('checkout_sessions')
        .add({
          price: formData.get('price'),
          allow_promotion_codes: true,
          tax_rates: [process.env.VUE_APP_STRIPE_TAX_RATES],
          success_url: process.env.VUE_APP_BASIC_URL + '/user/account?isActive=0',
          cancel_url: process.env.VUE_APP_BASIC_URL + '/creditcardregistration',
          metadata: {
            tax_rate: '10% sales tax exclusive'
          }
        })
        // Wait for the CheckoutSession to get attached by the extension
        docRef.onSnapshot((snap) => {
        const { error, sessionId } = snap.data()
        if (error) {
          // Show an error to your customer and then inspect your function logs.
          alert(`An error occured: ${error.message}`)
          document.querySelectorAll('button').forEach((b) => (b.disabled = false))
        }
        if (sessionId) {
          // We have a session, let's redirect to Checkout
          // Init Stripe
          const stripe = window.Stripe(process.env.VUE_APP_STRIPE_PUBLIC_KEY)
          stripe.redirectToCheckout({ sessionId })
        }
        return false
      })
    },

ここで、注意すべき点はtax_ratesです。

何を注意するのかと言うと、[process.env.VUE_APP_STRIPE_TAX_RATES]を配列として書いていることです。

なぜかは、憶測に過ぎないのですが、国によって税率が違うからだと思います。

悪魔で憶測です。鵜呑みにはしないでくださいね。

また、process.envなんて初めて見たという方は、こちらの記事

初心者必見!固定値(キー、URLなど)は.envファイルに書いて再利用しよう!!

をご覧ください!

環境変数(process.env)は非常に便利なので、ぜひこの機会にご利用ください!!!

6, 請求書ページに遷移

最後に、下記のボタンを押したときの処理を書いてきます。

無題.png

当然のように、クレジットカードを利用している人が請求書を見れるようにしないといけません。

viewInvoiceと言う名前の関数をmethods内に作成します。

これも今までのコードは書かないのでご了承ください。

間違って消さないように。。。

credit.vue
async viewInvoice(event) {
      this.isLoading = true
      event.preventDefault()

      document.querySelectorAll('button').forEach((b) => (b.disabled = true))

      // Call billing portal function
      const functionRef = firebase
        .app()
        .functions(process.env.VUE_APP_FUNCTION_ROCATION)
        .httpsCallable('ext-firestore-stripe-subscriptions-createPortalLink')
      const { data } = await functionRef({ returnUrl: window.location.origin })
      window.location.assign(data.url)
    },

process.env.VUE_APP_FUNCTION_ROCATIONにはFirebaseで最初に設定した地域を指定します。

日本なら「asia-northeast」ですね。

これで終わりです!!

本当にコピペして頂くだけで、実行できます!

ちなみに、クレジットカードを登録しているかの確認は色々とあるのですが、下記の画像を見て下さい。

・クレジット決済を行っていない場合

無題.png

・クレジット決済を行っている場合

無題.png

クレジット決済を行っている場合は、subscriptionsテーブルが作成されていますね。

このようにして判断をすることもできます。

また、こんなことをしなくても「firebase-functions」を自分で作成することで判断することもできます。

その際は、下記の記事が参考になると思うので、時間がある方はぜひご覧ください!

さぁ、firebaseでユーザーが管理者かどうかの判別を行おう!

いかがだったでしょうか?

Stripeの導入は初の試みだったので、すごく大変でした。

ただ、これから学習を進めていく方々にはあまり時間を無駄にしてほしくはないので、この記事をみながら第4次産業革命の波に乗っていきましょう!!

以上、「コピペでOK!?大学に通いながらプログラミングを勉強している、貧弱エンジニアによるVue.js+Firebase+Stripeを使ったサブスクリプション講義」でした!

良ければ、LGTM、コメントお願いします。

また、何か間違っていることがあればご指摘頂けると幸いです。

他にも毎日初心者さん向けに記事を投稿しているので、時間があれば他の記事も見て下さい!!

Thank you for reading

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.js で 親コンポーネントと同じ高さに設定する方法を考えた

背景

Vue.js で親コンポーネントと同じ高さにする。

具体的には、height:auto(コンテンツの内容に応じて、heightが変わる。)のコンポーネントに対して、
ある特定条件を満たしている場合、透明色の色をかぶせる。と言うことが必要となりました。

height: 100% でも指定しておけば、うまくいくだろう。と思って試してみましたが、
height:100% が適用されるのは、親コンポーネントのheightが指定されている場合のみらしく、今回適用したい height:auto ` の場合には、適用できないとのこと。異なる方法で対応を検討する必要が生じました。

結論

結論として、以下の方法で対応しました。

template
<template>
  <div>
    <div :style="{height:fullHeight}" /> // 高さを指定する。
 </div>
</template>
script(typescript)
import {Vue, Component, Prop} from 'vue-property-decorator';

@Component({})
export default class Sample extends Vue {
  fullHeight:string;

  mounted() {
    this.fullHeight = this.$el.clientHeight + 'px'; // DOMから高さを取得する。
  }
}

説明

概要

Vue.js のAPIでElementを取得して、高さを取得しています。
シンプルですが、この方法を取ることで、Elementが持つ他のプロパティを取得することも可能です。

Vue.js API
- https://jp.vuejs.org/v2/api/index.html#el
Element のプロパティ
- https://developer.mozilla.org/ja/docs/Web/API/Element

注意事項

mounted で取得する

createdcomputed を使用すると、 Element(DOM) を生成する前に、Element要素にアクセスすることになるので、エラーを吐いてしまいます。

詳細は、以下サイトにて
- https://jp.vuejs.org/v2/guide/instance.html#%E3%83%A9%E3%82%A4%E3%83%95%E3%82%B5%E3%82%A4%E3%82%AF%E3%83%AB%E3%83%80%E3%82%A4%E3%82%A2%E3%82%B0%E3%83%A9%E3%83%A0

時間があれば、調べた経緯とかも整理して、編集したいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vue-simple-suggestの機能を最小限で実装

概要

Vue.jsでサジェスト機能を実装したくてやり方を調べていたら、vue-simple-suggestなるライブラリがあることが分かったので使ってみる。

インストール方法は公式参照
公式GitHub→https://github.com/KazanExpress/vue-simple-suggest

環境

Vue.js 2.5.17
vue-simple-suggest 1.10.3

実装

全体

Form.vue
<template>
    <div class="form-group">
        <vue-simple-suggest
            v-model="selected"
            :list="getSuggestionList"
            :filter-by-query="true">
            <input type="text" name="tag" id="tag" placeholder="タグを入力してください" autocomplete="off">    
        </vue-simple-suggest>
    </div>
</template>
<script>
import VueSimpleSuggest from "vue-simple-suggest";
import 'vue-simple-suggest/dist/styles.css';

export default {
    components: {
        VueSimpleSuggest
    },
    data() {
        return {
            selected: '',
            List:'',
        };
    },
    methods: {
        async getSuggestionList() {
            return await axios.get('/api/tagList')
            .then(res => this.List = res.data )
            .catch((error)=>{
                this.errorMsg = 'Error! Could not reach the API. ' + error
                console.log(this.errorMsg)
            })
        },
    }
}
</script>

ポイント

form.vue
<vue-simple-suggest
    v-model="selected"         
    :list="getSuggestionList"  
    :filter-by-query="true">
          <input type="text" name="tag" id="tag" placeholder="入力してください" autocomplete="off">
</vue-simple-suggest>

フォームに入力された文字がv-modelに入る
getSuggestionList()APIで引っ張ってきたlistが入る
これがないとうまくサジェストされません
<vue-simple-suggest></vue-simple-suggest>の中に<input>を実装のもポイント

form.vue
<script>
import VueSimpleSuggest from "vue-simple-suggest";  //忘れずにインポート
import 'vue-simple-suggest/dist/styles.css';        //忘れずにインポート

export default {
    components: {
        VueSimpleSuggest
    },
    data() {
        return {
            selected: '',  //入力された文字を格納
            List:'',       //getSuggestionList()で引っ張ってきた配列を格納
        };
    },
    methods: {
        async getSuggestionList() {
            return await axios.get('/api/tagList')
            .then(res => this.List = res.data )
            .catch((error)=>{
                this.errorMsg = 'Error! Could not reach the API. ' + error
                console.log(this.errorMsg)
            })
        },
    }
}
</script>

最後に

困ったら公式ドキュメント!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vuejsのコンポーネントについて

HTMLテンプレートと機能などをまとめることができる機能としてコンポーネントがあります。
今回は、コンポーネントの基本的な機能についてまとめてみたいと思います。

コンポーネントを使う上で基本的な考え方

コンポーネント化することで何がメリットかというと
・機能ごとに区別して作成が可能
・再利用可能になり保守性が高めることができる
ことがあげられます。

そして、
コンポーネントには"使う側(親)”と"使われる側(子)"と言う関係になります。
この関係を抑えることでコードの記載する内容も変わってきますので注意しましょう。

グローバルコンポーネントとローカルコンポーネント

コンポーネントには2種類あります。
グローバルとローカルのコンポーネントです。
・Vueのインスタンスを複数作成した際にどのインスタンスでも適用させたい場合にはグローバルコンポーネントを使用します。
・特定のVueインスタンス内で使用したい場合にはローカルコンポーネントを使用します。

定義する場合には下記のようになります。
記載方法の違いと言えばcreateApp({})で定義するか、.componentで宣言するかの違いです。

グローバルコンポーネント

Vue.createApp({}).component('mydata',{
  data:function(){
    return{
      title:'',
    }
  },
  template: `<input type="text" v-model="title" />`,
})

ローカルコンポーネント

Vue.createApp({
  component: {
    'mydata': {
      data: function () {
        return {
          title: '',
        }
      },
      template: `<input type="text" v-model="title" />`,
    },
  },
}).mount('#app')

一点注意点としては、グローバルコンポーネントを利用する場合にはJavaScriptファイルが上から読み込まれてしまうので、適用したいVueインスタンスよりも前に記述することが必要です。

Vue.component('sample1', { 
   data() { 
       return {
           testms: 'hello!'
       }
   },
   template: '<div>{{ testms }}</div>'
})

new Vue({ 
   el: '#app'
})

//上記でVueインスタンスが定義されたため、ここから下のコンポーネントは読み込まれない。
Vue.component('sample2', {   
   data() { 
       return {
           testms2: 'bye!'
       }
   },
   template: '<div>{{ tsetms2 }}</div>'
})

参考:VueのComponent(コンポーネント)の書き方・使い方について解説

コンポーネント定義の内容について

コンポーネントの定義については、2つポイントを説明します。

sample.js
Vue.createApp({
  component: {
    'mydata': {
      data: function () {
        return {
          title: '',
        }
      },
      template: `<input type="text" v-model="title" />`,
    },
  },
}).mount('#app')
・dataプロパティの取り扱い

コンポーネント内で使用したいdataプロパティについては、定義する際にオブジェクトを返す関数である必要があります。

      data: function () {   //function定義
        return {     //return
          title: '',
        }
      },
・templeteの取り扱い

コンポーネントのHTMLを定義するためにはtempleteを定義する必要があります。

      template: `<input type="text" v-model="title" />`,

ただ、現実問題としてコンポーネントが複雑になると文字列として定義するには可読性が低くなってしまいます。
そのために、templeteの内容をHTMLへ記載する方法があります。

html
    <div id="app">
      <mydata></mydata>
    </div>
    <script type="text/x-template" id="mydata-template">
      <input type="text" v-model="title" />
    </script>
javascript
  template: '#mydata-template',

template内の要素を下記のようにscriptタグ内で定義することhtmlに記載することが可能です。

<script type="text/x-template" id="~~~~">
  //templeteの内容
</script>

後は、htmlのidとJavaScriptの'#~'で紐づけることで、コンポーネントのtemplete部分の可読性が上がり、記載も簡単になります。

コンポーネント自体が冗長になった時には。。

上記のsample.jsファイルのコンポーネントが冗長になり、可読性が低いと感じる時にはコンポーネントを切り離して,constで宣言してあげることで回避するのも一つの手です。

sample.js
//コンポーネントを切り離す
const mydataobj = {
      data: function () {
        return {
          title: '',
        }
      },
      template: '#mydata-template',
    }

Vue.createApp({
  component: {
    'mydata': mydataobj
  },
}).mount('#app')

と言うような形でコンポーネントの概要についてまとめてみました。
コンポーネントが理解できれば後は値の受け渡しが必要ですよね。
近日中に追記したいと思います。


近日中に追記予定


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue Apollo-compsableでqueryをシーケンシャルに実行する方法メモ

1番目のqueryのレスポンスを2番目のqueryのvariablesに渡したかった

単なる知っているか知らないかだけの問題で、良く読むと公式にも書かれていました。

ポイントは一つだけでuseQueryに渡せるオプションの中に
enabledというプロパティが存在していました。
このプロパティはデフォルトではtrueで、そのままだと即時リクエスト発行の挙動となりますが
falseに指定してやる事で、trueに変更するまでリクエストを発行しなくなります。
この仕組みを利用して、シーケンシャルにQueryを発行できるというわけです。

以下の例は1つ目のqueryで文字列uuidを受け取り
その値を2つ目のqueryのvariablesに条件として指定した場合の例示です。

おまけの情報としてuseQueryのレスポンスに含まれるloadingの値を利用した方が良いのではないか?
という意見もあるかと思いますが、
私自身当初はそれを試みたのですが、loadingはその名前が示す通り
現在読み込み中かという意味合いで、読み込みを開始する前はfalseとなっており
今回のような用途には利用できませんでした。

自分自身enabledの存在に気がつけず、少しハマったので備忘として記録を残します。

import * as VueApolloComposable from '@vue/apollo-composable'
import * as VueCompositionApi from '@vue/composition-api'
import { FirstQueryDocument, SecondQueryDocument, ThirdQueryDocument } from './graphql/queries.graphql'

const useFirstQueryResponse = () => {
  const isLoaded = ref<boolean>(false)
  const uuid = ref<string>('')
  const { onResult } = VueApolloComposable.useQuery(FirstQueryDocument)

  onResult((result) => {
    isLoaded.value = true,
    uuid = result?.data?.firstQueryResponse?.uuid
  }

  return {
    isLoaded,
    uuid,
  }
}
const useSecondQueryResponse = (
  firstQueryLoaded: Ref<boolean>,
  uuid: Ref<string>,
) => {
  const secondQueryResponse = ref<SecondQueryResponse>([])
  const isLoaded = ref<boolean>(false)

  const conditions = {
    page: ref(1), // 改ページ処理などのページ数の例
    uuid, // 最初のQueryで受け取ったレスポンスの値を2つ目のQueryの条件として渡す
  }

  const variables = computed(() => ({
    per: 20,// 1ページあたりに何件表示するかのパラメータの想定
    page: conditions.page.value,
    uuid: conditions.uuid.value,
  }))

  const options = computed(() => ({enabled: firstQueryLoaded.value }))
  const { onResult } = VueApolloComposable.useQuery(SecondQueryDocument, variables, options)
  onResult((result) => {
    secondQueryResponse.value = result?.data?.secondQueryResponse
    isLoaded.value = true
  })

  return {
    secondQueryResponse,
    isLoaded,
  }
}

const useThirdQueryResponse = (secondQueryLoaded: Ref<boolean>,) => {
  const thridQueryResponse = ref<thridQueryResponse>([])
  const variables = {}
  const options = computed(() => ({enabled: secondQueryLoaded.value }))
  const { onResult } = VueApolloComposable.useQuery(ThirdQueryDocument, variables, options) 
  onResult((result) => {
    thirdQueryResponse.value = result?.data?.thirdQueryResponse
  })

  return {
    thirdQueryResponse.value = result?.data?.thirdQueryResponse
  }
}

export default defineComponent({
  name: `SequentialQueryComp`
  setup(){
    const firstQueryResponse = useFirstQueryResponse()
    const secondQueryResponse = useSecondQueryResponse(
      firstQueryResponse.isLoaded,
      firstQueryResponse.uuid,
    )
    const thirdQueryResponse = useThirdQueryResponse(secondQueryResponse.isLoaded)
  }
})

// 以下FirstQueryDocumentの参考例
import gql from 'graphql-tag'

export const FirstQueryDocument = gql`
  query firstQuery {
    firstQueryResponse {
      uuid
    }
  }
`

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

jQueryを知っているだけの怠惰なひとが1時間以内にVue, React, Angularを完全に理解する (コピペするだけのサンプル付)

動機とこのページの趣旨

jQuery ...もう14年モノらしい。

業務で基本的にjQueryを10年ほど利用してきたが、スキルマップ作成とやらでVue, React, Angular の経験を問われたため、知ったかぶりしたいので調べた各種資料。以下を順に読んで index.html を3ファイル作るだけで1時間以内にVue, React, Angular「完全に理解した」顔をしよう。タイトルの「怠惰」はプログラマの美徳なのでお飾りフレーズとして書いてしまったが、コピペという怠惰はプログラム的には怠惰では無い、むしろ闇なので、これで一通りかじったらむしろナニモワカラナイの絶望の谷に堕ちてみることをお勧めする。そんな趣旨。

時代は「Angular」「React」「Vue」の3大フレームワークに集約 らしい...

Hello Vue world のために以下を読め

さて時間がないので早速始めよう!
Vue.jsでできること8選。凄さが分かる実用例スニペット集
凄い!けれどその凄さを実感している場合ではない。
Vue.jsで Hello World!

ひとまず元気に挨拶、Hello world

HTML表示:
image.png

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    <title>Title</title>
</head>
<body>
<div id="app">
    <!-- testValの内容を表示する -->
    {{ testVal }}
</div>
</body>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            testVal: 'Hello World!'
        }
    });
</script>
</html>

早速動かせた実感を得たら以下。
Vue.jsで湯婆婆を実装してみる
公式(日本語)
Vue.js は公式を読めと随所に書いてあるので、公式サイトを読むのが最も手っ取り早く完全に理解できる。

Hello React World のために以下を読め

次、React。こちらはまず。
React (JavaScript) で湯婆婆を実装してみる

元気に湯婆婆

令和のHelloworld湯婆婆。
HTML表示:
image.png

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>React 湯婆婆</title>
  <script src="https://unpkg.com/react/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/babel-standalone/babel.min.js"></script>
</head>
<body>
  <div id="root"></div>
  <script type="text/babel">
    (() => {
      'use strict';

      const {useState} = React;
      function Yubaba() {
        const [name, setName] = useState('');
        const newName = name.substr(Math.floor(Math.random() * name.length), 1);

        return (
          <div>
            <p>契約書だよそこに名前を書きな</p>
            <input type='text' value={name} onChange={e => setName(e.target.value)}/>
            <p>フン{name}というのかい贅沢な名だねぇ</p>
            <p>今からお前の名前は{newName}いいかい{newName}だよ分かったら返事をするんだ{newName}!!</p>
          </div>
        );
      }

      ReactDOM.render(
        <Yubaba/>,
        document.getElementById('root')
      );
    })();
  </script>
</body>
</html>

ReactでHello Worldをする前に...
Facebook公式のcreate-react-appコマンドを使ってReact.jsアプリを爆速で作成する
以上で一通りセットアップはできる。

ReactでHelloWorldをしてみよう!
公式(日本語)
をサラリと読もう。

Hello Angular World のために以下を読め

ここまでで30分経っただろうか。もう少しだ。
AngularJSでHello World
AngularJS で Hello World

元気にHello world! (3回め)

HTML表示:
image.png

<!DOCTYPE html>
<html ng-app>
  <head>
    <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.4/angular.min.js"></script>
    <script>
      var HelloWorld = {
        Controller: function($scope) {
          $scope.name = 'World';
          $scope.greeting = 'Hello';
          $scope.bye = function() {
            $scope.greeting = 'Good-bye';
          };
        }
      };
    </script>
    <link rel="stylesheet" href="css/main.css">
    <title>Hello World</title>
  </head>
  <body>
    <h1>AngularJS Example: Hello World</h1>
    <div ng-controller="HelloWorld.Controller">
      Input your name →
      <input type="text" ng-model="name" size="20">
      <hr>
      <p>{{greeting}} {{name}}!</p>
      <hr>
      <p><button ng-click="bye()">Bye!</button></p>
    </div>
  </body>
</html>

もう湯婆婆なのか何なのかわからなくなっているが、以下。
とほほのAngular入門
公式(日本語)
とほほの解説はとても心強い。

で、jQueryとどう違うの?

3種類動かしてみたところでウンチクくらいは語れるようにしておこう。以下で完璧だ。
JavaScriptが辿った変遷
jQuery愛好家のためのVue.js、React入門(いずれAngularも)
jQuery から Vue.js へのステップアップ

実際に実務でjQueryを置き換えられるかなんて言う顔をするなら以下。
Vue.jsとjQueryで同じ機能を作成し、コードを比較する
Vue.jsとjQueryで同じ機能を作成し、コードを比較する - その2
ReactとjQueryの比較
JavaScript: フレームワーク React/Vue/Angularについて

これで1時間以内かな?

最後に我らのjQuery

ここまでjQueryだけ知っているひとを想定読者にしたので、最後にウッカリこの記事を開いてしまった人のために、念の為jQueryについてもHello worldしておく。逆の境遇においてもこれでjQueryを完全に理解してほしい。

jQueryの基礎
頼まれてもいないのにcssのお餅付きだ。迎春なので鏡餅をCSSで作った参照。
jQuery公式については
https://jquery.com/ と、
http://semooh.jp/jquery/ がテッパンか。

HTML表示:
image.png

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Progate</title>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
  <style type="text/css">
.kagamimochi {
  width: 100px;
  text-align: center;
}
.mikan {
  width: 50px;
  height: 40px;
  background: #e88522;
  border-radius: 50%;
  position: relative;
  z-index: 10;
  display: inline-block;
}
.mikan::after {
  content: "";
  width: 15px;
  height: 5px;
  background: linear-gradient(#4f9c5d 50%, #6cb576 50%);
  border-radius: 50%;
  display: inline-block;
  position: absolute;
  top: 0;
  right: 10px;
  transform: rotate(-20deg);
  display: inline-block;
}
.mochi1 {
  width: 80px;
  height: 40px;
  background: #fff;
  border: 1px solid #000;
  border-radius: 50%;
  display: inline-block;
  margin-top: -15px;
  position: relative;
  z-index: 5;
}
.mochi2 {
  width: 100px;
  height: 50px;
  background: #fff;
  border: 1px solid #000;
  border-radius: 50%;
  display: inline-block;
  margin-top: -20px;
  position: relative;
  z-index: 4;
}
.kami {
  width: 92px;
  height: 92px;
  background: #fff;
  border: 5px solid #f00;
  transform: rotateX(45deg) rotateZ(45deg);
  display: inline-block;
  margin-top: -75px;
  position: relative;
  z-index: 1;
}

</style>
<script>
  $(function(){
    $('#hide-text').click(function(){
      $('#text').slideUp();
    });  
  });
</script>   
</head>
<body>
  <!-- このボタンを押すと -->
  <div class="btn" id="hide-text">説明を隠す</div>

<div class="kagamimochi">
  <div class="mikan"></div>
  <div class="mochi1"></div>
  <div class="mochi2"></div>
  <div class="kami"></div>
</div>

  <!-- この表示が隠れる -->
  <h1 id="text">Hello, World!</h1>
  <script src="script.js"></script>
</body>
</html>

これでどんどん具体的に、ナニモワカラナイを目指せそうです。すべてのJSはここからだ。Enjoy!
以上お粗末様でした。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Scoped CSSの破れ

Scoped CSSは、コンポーネント内にスタイルを閉じ込めるための便利な機能ですが、かなり困ったケースを見つけたので、既知かもしれませんが再現方法をメモっておきます。

確認環境

  • Vue.js
  • Scoped CSSを有効にしたSingle File Component
  • Slot機能は未使用

再現方法

  • コンポーネントを2つ用意し、親子関係にする(以下、ParentChild と呼ぶ)
  • Child 側で適当なスタイル .foo を定義しておく(未使用でも構わない)
  • Parent 側で、たまたま同名の .foo を定義しておく(スタイルの内容は違うもの)
  • Parent 側で、 <Child class="foo" /> とする(Parent 側で定義したスタイルだけが当たるハズ)
  • Child 内で定義した .fooChild に意図せず当たる

サンプル

https://codesandbox.io/s/loving-burnell-6ve2k?file=/src/components/Parent.vue
(Safariで開くとエラーになるのでChromeで)

Screen Shot 2021-01-27 at 0.45.56.png

何が問題か?

  • Child 内で定義したスタイルが、 Parent 側から呼び出せる
  • Child 内で定義したスタイルが、意図していない箇所に当たる
  • ParentChild を別々に開発していると、マージした際にスタイルが崩れる場合がある

とりま回避方法

  • Child 側でスタイルを定義する際に、簡単にはマッチしないセレクタにしておく(ユニークな親子構造とか)
  • または <Child class="foo" /> みたいにしない

備考

Scoped CSS は、技術的にはコンポーネントごとにカスタム data 属性を自動で振って、スタイルの影響範囲を閉じ込めているだけなので、Vueに限らず Scoped CSSを謳っている他のライブラリでも起きてるかもしれません。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者必見!固定値(キー、URLなど)は.envファイルに書いて再利用しよう!!

皆さんこんにちは!

今日は.envファイルについて書いていきます。

ところで皆さん、例えばバックエンドとのやり取りを行う際、URLはどういう風に書かれていますか?

axios.get('http://127.0.0.1')

こんな風に書くと思います。

ただ、これを毎回コピーして持ってくるのって正直めんどくさいかと。

また、実際にサーバーにあげる時って、http://127.0.0.1のようなローカルホストとは通信を行いませんよね???

他にも、シークレットキーなど第三者に知られてはいけない情報をファイルに直接書き込むのは良くありません。

ここで、登場するのがenvファイルです!

聞き覚えのある方もいれば、全く聞いたことがないという人もいます。

全く知らなくても大丈夫!

この記事を見て、.envファイルを使いこなしていきましょう!!

一度使うと恐らくやめられないくらい便利なので、ぜひ見て下さい!

それでは順を追って説明しています。

.envと.env.productionの作成

プロジェクト直下に.env.env.productionを作成します。

project/
┣ public/
┣ src/
.env
.env.produnction

こんな感じで作ります。

copy nul .env
copy nul .env.production

Macの方はtouchで作成してください。

また、コマンドプロンプトでの作業に慣れてない方は、手動で作成してもOKです。

固定値の設定

次に、固定値を以下のように定めます。

/.env
VUE_APP_BACKEND_SITE_URL = 'http://127.0.0.1'
/.env.production
VUE_APP_BACKEND_SITE_URL = 'https://example.com'

ローカルサーバーを立ち上げてる人は一旦止めて下さい。

止めないと、.envファイルは更新されません。

名前を付けるときは、VUE_APPから必ず始めて下さい!!!

そして、これを実際に呼んでみたいと思います。

index.js
axios.get(process.env.VUE_APP_BACKEND_SITE_URL).then(() => {})

process.envで始め、先ほど設定したキー名を付けます。

これで、ローカル環境の時はhttp://127.0.0.1がコールされ、実際のサーバーではhttps://example.comがコールされます。

他にも、Saasを利用する際のAPIキーなどもここに保管しておくことが重要です。

簡単かつ作業効率もぐっと上がります!

また、セキュリティ面でも活躍するのでどんどん使ってください!!!

以上、「初心者必見!固定値(キー、URLなど)は.envファイルに書いて再利用しよう!!」でした!

良ければ、LGTM、コメントお願いします。

また、何か間違っていることがあればご指摘頂けると幸いです。

他にも初心者さん向けに記事を投稿しているので、時間があれば他の記事も見て下さい!!

Thank you for reading

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む