20190710のvue.jsに関する記事は18件です。

[備忘録]Qiitaのapiを使用する by vue.js

Qiitaの記事数を取得したい

api
https://qiita.com/api/v2/docs
今回は記事を取得するだけなのでapi取得しなくてもOK
操作したいならapi必要

ソースコード

vue.js
<template>
  <div class="git">
    <h>post num :{{userDatas}}</h>
  </div>
</template>

<script>
import axios from "axios";

export default {
  name: "GitData",
  props: {
    userName: String
  },
  data() {
    return { userDatas: Number };
  },
  mounted() {
    const request = axios.create({
      baseURL: "https://qiita.com/api/v2/"
    });
    request.get(`users/${this.userName}/items`).then(res => {
      this.userDatas = res.data.length;
    });
  }
};
</script>

<style lang="scss">
</style>

100daysofcode Day2

1日空いたことは触れないで
明日も空くかな...
やったこと
- qiitaのapiで記事数を取得
- ajaxで帰ってくるresponseの型について理解できてない...明日しらべよ

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

Vue template内でのthisのスコープについて

問題:v-bindの代入の仕方で:name=this.idとするとradioの挙動がおかしくなる

radioのコンポーネントを作っていたのですが、template内で、v-bindの代入でthis.変数名で記述すると、正常に動かないという現象が起きました。下記がコードです。

components/MyRadios.vue
<template>
  <div>
    <p>:name=this.id</p>
    <label v-for="item in myoptions" :key="item.id">
        <input type="radio" :name="this.id" :value="item.id">
        {{ item.name }}
    </label>
    <p>:name=id</p>
    <label v-for="item in myoptions" :key="item.id">
      <input type="radio" :name="id" :value="item.id">
      {{ item.name }}
    </label>
  </div>
</template>

<script>
export default {
  props: {
    id: {
      type: String,
      required: true
    },
    myoptions: {
      type: Array,
      required: true
    },
  }
};
</script>

結果が下記のスクリーンショットなのですが、なぜか :name=this.id とすると、radioボタンを両方選べるようになってしまいました。。

radio2.png

template内ではthisを使ってはいけない

https://stackoverflow.com/questions/43188317/use-this-in-vue-template

template内のthisを使ったときに挙動が変わるということらしく、Vue.jsのtemplate内ではthisを使わないほうがいいとの回答でした。(どなたか「なぜtemplate内ではthisを使ってはいけないのか」理由をご存知の方がいらっしゃいましたら良かったら教えてください。。。。)

前提条件

package.json
{
  "name": "codesandbox",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "vue": "^2.5.22"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "3.6.0",
    "@vue/cli-plugin-eslint": "3.6.0",
    "@vue/cli-service": "3.6.0",
    "babel-eslint": "^10.0.1",
    "eslint": "^5.8.0",
    "eslint-plugin-vue": "^5.0.0",
    "vue-template-compiler": "^2.5.21"
  },
  "eslintConfig": {
    "root": true,
    "env": {
      "node": true
    },
    "extends": ["plugin:vue/essential", "eslint:recommended"],
    "rules": {},
    "parserOptions": {
      "parser": "babel-eslint"
    }
  },
  "postcss": {
    "plugins": {
      "autoprefixer": {}
    }
  },
  "browserslist": ["> 1%", "last 2 versions", "not ie <= 8"]
}

以上です。

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

レファレンス協同データベースの記事をランダムに表示するサービスをvue.jsで作りました。

Vue.js、Firebaseを使用したwebサービスreftikaを作成しました。備忘録も兼ねて参考にした記事をまとめながらデプロイするまでの流れを書きます。

※とてつもなく初心者です

現職は広告業界。Pythonはちょこちょこ簡単なスクリプトを書いて便利に使っていましたが、webは全くの初心者です。
HTML/CSSの勉強から始めて1ヶ月足らず。

本記事ではこうした方が良い、こうする必要はないということがあるかと思われます。コメントでご指摘いただけると幸いです。

作ったもの

reftika
レファレンス協同データベースからの簡易キーワード検索とランダム表示を提供するサービスです。
Screen Shot.png

レファレンスってなに?

レファレンスサービスとは?

レファレンス協同データベース(レファ協)の名称にも使われているレファレンスサービスとは、図書館員が資料などを使って、みなさんの疑問や質問にお答えしたり、資料を探すお手伝いをするサービスです。

レファレンス協同データベースとは?

公共図書館、大学図書館、専門図書館、学校図書館がレファ協のデータベースにデータを登録し、それをみなさんや図書館員、研究者などが利用する。
レファレンス協同データベースは、全国の図書館等で日々行われているレファレンスサービスの記録や情報の調べ方などを図書館員がデータベースに登録し、そのデータをインターネットを通じてみなさんに提供するサービスです。この「図書館の知識が集まったデータベース」を使って、日常生活でふと感じた疑問からビジネスや調査研究に関する専門的なものまで、幅広いデータがご覧いただけます。

具体例

カラスが石鹸を食べるというのは事実か。また、なぜ石鹸を食べるのか。(鳥取県立図書館)

クラシック音楽作品の日本での演奏記録を調べる(桐朋学園大学音楽学部附属図書館)

自転車のことを「チャリンコ」「ママチャリ」「ケッタマシーン」などと呼ぶが、どういう語源か。 (岐阜県図書館)

参考:
レファ協活用術!

なぜつくったのか

第一に勉強用ですが、実用的なものが作りたい思いがあり、スマホではちょっと見づらいと感じていたレファレンス協同データベースの検索に目を付けました。

はじめは、APIを利用してスマホ用に検索表示を見やすくするサービスをつくってみようと思っていたのですが、雑学チックなものから専門的なものまで多種多様なレファレンスがあるこのデータベースをランダムに見れたら面白そうだと考え、ランダム表示の機能をメインにしました。

ちなみに、この記事のサービスにも影響されました。→Qiitaの記事をランダムに読める API / サービス を4時間ぐらいで作った (Firebase/AWS/Docker) (運用費0円※)

環境

APIの仕様はこちら。
レファレンス協同データベース・システム操作マニュアル参加館用(オンライン版)
レスポンスはxmlかrss形式です。
ランダムな記事取得機能は提供されていません。

実装

Vue.jsで作成し、FirebaseのHostingを利用しています。

レファレンス協同データベースのAPIは「同一生成元ポリシー(Same-Origin Policy)」に引っ掛かるため、別途中継サーバーをfalconで作成しHerokuで運用しています。

利用した技術
"vue": "^2.6.10",
"vue-router": "^3.0.3",
"vuex": "^3.0.1"
"vue-toasted": "^1.1.27",
"axios": "^0.19.0",

その他、vue-cli、lodash、Firebase(Hosting)、python、falcon、Heroku、またGoogle Font、fonto awesomeなどを利用しました。

公開までをざっくりと

0.この記事をベースに作り始めました。Vue.jsとAxiosなら驚くほど簡単に作れる!外部APIを使ったWebアプリの実例
1.apiの仕様の確認と取得したデータの処理
2.vue.jsでテスト実行→同一生成元ポリシーに引っ掛かる
3.同一生成元ポリシーを回避するためにfalconでサーバーを立てる&Herokuにアップ。
4.vue.jsで、一覧ページ・詳細ページの作成。routerの設定など
5.firebaseデプロイ
6.デザインの作成。レスポンシブ化。twitter card対応。

土日で作るつもりでした。しかし、CORSについてほとんど知識がなかったため2番の時点で終わらないことを覚悟。予定変更して、結果的に4日間で一通り完成しました。

公開までをきっちりと

0.この記事をベースに作り始めました。Vue.jsとAxiosなら驚くほど簡単に作れる!外部APIを使ったWebアプリの実例

上記記事ではcdn版のvue.jsを使用していますが、今回はpackage.jsonでの管理も学びたかったため、vue-cliを使用しました。また、cssの勉強のためfoundationも使用していません。

$ npm install -g @vue/cli
$ vue create reftika
$ cd reftika
$ npm run serve

でサンプルが表示されます。
参考:
Vue.js を vue-cli を使ってシンプルにはじめてみる

1.apiの仕様の確認と取得したデータの処理

http://crd.ndl.go.jp/api/refsearch?type=reference&query=question any 読書
で「読書」というワードに適合度順のソートされた検索結果が200件返ってきます。
参考:レファレンス協同データベース・システム操作マニュアル参加館用(オンライン版)

ランダムな記事取得機能は提供されていなかったので対応。

ランダムな記事取得機能はAPIでは提供されていませんでした。しかし、データ登録日による検索が可能でした。そこで、一番最初のデータの登録日~現在までの日付をランダムに生成し、その日付をもとに検索することで疑似的にランダムなデータ取得を行えるようにしました。
一番最初のデータの登録日が2004年3月6日だったため、以下の関数で2004年3月6日~今日までの間の日付をランダムに取得しています。

    makeRandomDate: function() {
      const start = new Date(2004, 3, 6);
      const end = new Date;
      const querydate = new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime()));
      const month = ("00" + (querydate.getMonth() + 1)).slice(-2);
      const date = ("00" + querydate.getDate()).slice(-2);
      const regdate = querydate.getFullYear() + month + date;
      return regdate;
    },
xml形式のレスポンス

「APIを使ってみた」系の記事のほとんどはjson形式でデータを取得していたので不安でしたが、xmlでも処理に困ることは特にありませんでした。エンティティ参照で「&」が「&amp;」で表示されてエンコード処理を加えた程度です。

const url = result.getElementsByTagName("reference")[0].getElementsByTagName("url")[0];
const obj = {
             省略
            url: url.innerHTML.replace(/&amp;/g, '&') + '\n',
          };
その他

2010年にレファレンス協同データベース主催の企画 API腕自慢という企画が行われていました。Twitterで今でも運用されているbot「おしえて!れはっち (@referty_bot)」はこの時に作られていたことを知りました。ほか、サービスは終了していますが、「ふわっとレファ協関連検索」も素晴らしいサービスだと思います。モチベーションに繋がりました。

2.vue.jsでテスト実行→同一生成元ポリシーに引っ掛かる

vue/cliをインストール。

npm -g @vue/cli

プロジェクトの作成

vue create refsearch

一度テストで起動してみる

cd refsearch
npm run

localhostにアクセスすると初期画面が出る。

次にAPIの仕様をもとにApp.vueを調整していく。
レファレンス協同データベースの結果はXMLとして返ってくるので、パーサーもいれる。
初めはこのようなリクエストを投げました。

App.vue
<template>
<div id="app">
  <div class="columns medium-3" v-for="refqa in refqas">
    <div class="card">
      <div class="card-divider">
        {{ refqa.question }}
      </div>
      <div class="card-section">
        <p>{{ refqa.answer }}.</p>
      </div>
    </div>
  </div>
</div>
</template>

<script>
export default {
  data() {
    return {
      refqas: [] ※APIから返ってきた記事のデータを格納
    }
  },
  mounted() {
    axios.get("http://crd.ndl.go.jp/api/refsearch?type=reference&query=question%20any%20%E8%AA%AD%E6%9B%B8")
      .then(response => {
         var oParser = new DOMParser();
         var oDOM = oParser.parseFromString(response.data, "application/xml");
        const results = oDOM.getElementsByTagName('result');
    ※返答XMLにおいて、11つの記事は<result></result>で囲まれている。
    ※その後、各記事の質問と回答を取得。
        for(const result of results){
          const question = result.getElementsByTagName("reference")[0].getElementsByTagName("question")[0];
          const answer = result.getElementsByTagName("reference")[0].getElementsByTagName("answer")[0];
          const obj ={
            question:question.innerHTML,
            answer:answer.innerHTML
          };
          this.refqas.push(obj)
        }
      }
    )
  }
}
</script>

しかし、

クロスオリジン要求をブロックしました: 同一生成元ポリシーによりhttp://crd.ndl.go.jp/api/refsearch?type=reference&query=question%20any%20%E8%AA%AD%E6%9B%B8にあるリモートリソースの読み込みは拒否されます

CORSまとめ
サーバサイドのCORS対応
Falconで光速のWeb APIサーバーを構築する
などを参考に「クロスオリジン要求」とは何かから調べました。

3.同一生成元ポリシーを回避するためにfalconでサーバーを立てる&Herokuにアップ。

Cloud Functions for Firebaseで対処できないかと思ったのですが、Google Cloud PlatformならPythonが使えると知って調べたり、試したり...。

紆余曲折の上falconとHerokuに落ち着きました。
調べてみると、開発環境では簡易的にサーバーを立てている方が多いらしい。
まずは、Falcon 「Access-Control-Allow-Origin」の回避方法 Python+FalconでサクッとAPIサーバを作るを参考にfalconでローカルにサーバーを立てる。
一旦、「読書」で検索した値だけを返すサーバーを作りました。

# -*- coding:utf-8 *-
import json
import falcon
import requests


class CORSMiddleware:
    def process_request(self, req, resp):
        resp.set_header('Access-Control-Allow-Origin', '*')

class SampleResource:
    def on_get(self, req, resp):
        r_get = requests.get("http://crd.ndl.go.jp/api/refsearch?type=reference&query=question%20any%20%E8%AA%AD%E6%9B%B8")
         ”%E8%AA%AD%E6%9B%B8”は”読書”のパーセントエンコーディング。
        r_get_text = r_get.text
        obj = {
            "body": {
                "xml":r_get_text,
            }
        }
        resp.body = r_get_text

app = falcon.API(middleware=[CORSMiddleware()])
app.add_route('/sample', SampleResource())

if __name__ == "__main__":
    from wsgiref import simple_server
    httpd = simple_server.make_server("0.0.0.0", 8000, app)
    httpd.serve_forever()

サーバーを8000番ポートで起動して、
App.vueのアクセス先を変更。

App.vue
axios.get("http://localhost:8000/sample")

再度起動

npm run serve

screenshot201907080300vue.JPG

うまくいきました。
検索機能の実装やレイアウトはこれから調整していきます。

Herokuの方は、Procfileやruntime.txtなどを作成。

Procfile.
web: gunicorn fal:app --log-file -
runtime.txt
python-3.7.3
requirements.txt
falcon==2.0.0
gunicorn==19.9.0
requests==2.22.0

Herokuクライアントのインストール後、

$ heroku create
$ git remote add heroku https://git.heroku.com/xxxxx.git
$ git push heroku master

もうサービスが動いている!!ワクワクです!!
Procfileやruntime.txtなどをつくるのも初めてでしたが、こんな簡単にできるように整備されているのかと驚きました。

参考:
Herokuを久々に使用する
PythonのWebアプリフレームワークFalcon試してみた
【HEROKUとは】これを読めばOK!デプロイの仕方まで徹底解説
HerokuにRailsアプリをデプロイする手順

4.vue.jsで、一覧ページ・詳細ページの作成。routerの設定など

この時点までrouterは使っていませんでした。
一覧画面から個別記事のページへの遷移後、戻るボタンを押すと一覧のデータがリフレッシュされるのは不都合・不自然であるためその対処として導入しました。また、無限スクロールを実装した後にこのサービスではあまり意味がない機能であることに気づいたり、同様の処理を行う関数をまとめたり、計画性のなさを痛感しました。

あちこちエラーが出ては直しての繰り返しでしたが、ググりながらなんとか進めていくことができました。

Vue.js で入力フォームを↑↓キーやタブ・シフトタブでフォーカス移動するなどのチップス的な記事やVue.js初心者向け:Vue.jsとaxiosでJsonを取得してコンポーネントに反映するメモの「勘違いしていたけどこうだった」という記事は本当に勉強になりました。

先人に感謝します。

loodash

また、
キーワードの入力後、SPAらしく自動で検索が行われるように。かつ、APIに負荷がかかりすぎないようにlodashというライブラリが使えるらしい。ということで導入しました。入力操作が止まってから検索を実行するまでに5秒後のタイムラグをつけています。

レファレンス協同データベースのAPIは一応呼び出し無制限とはなっていますが、私がAPIにあまり触ったことがないことを踏まえたうえで、安全のために少し長めにしています。

ただでさえHerokuを挟んで遅めなのにもっと遅くなってしまった...。ちなみに、少し負荷をかけたときにHerokuの方がエラーを吐きました。速度にはあまり期待しないでください。コンテンツは面白いものばかりなので、気長に待っていただけるとありがたいです。

 created: function() {
     this.debouncedGetAnswer = _.debounce(this.getAnswer, 5000);
     省略
  },
 省略
  getAnswer: function() {
      if (this.keyword === '') {
        this.loading = false;
        return;
      }
      this.refqas = [];
      axios.get(省略

5.firebaseデプロイ

以前Reactで勉強用の小さなサービスを作ったときや静的サイトのホスティングで使ったことはあったので、おさらいをしながらデプロイ。

一度SPA用の最適化の項目でNoと答えてしまったので、後でfirebase.jsonを修正しました。

? Configure as a single-page app (rewrite all urls to /index.html)? (y/N) N

としたために、

firebase.json
{
  "hosting": {
    "public": "dist",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [    ←この項目を後で付け足しました。![ScreenShotsp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89564/3132c61f-7ef3-aa2f-d8ae-0ca1ba6cdffc.png)
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}

firebase init
firebase deploy

参考:
Firebaseで初めてのデプロイ
[Firebase][iOS] Firebase Hosting でキャンペーンページを作ってみよう

6.デザインの作成。レスポンシブ化。twitter card対応。

スマホでの見づらさ解消が作り始めたきっかけでもあるのでモバイルファーストに。文字情報が多いので極力シンプルに作成しました。

Screen Shot 2019-07-10 at 13.12.02.png

twitter cardの対応については、人によってそれぞれやり方があるようです。

静的にどのページも固定で表示する派

Vue.jsで静的にOGP対応する
【Vue.js】ツイッターのツイートに画像がついてるツイッターカードのつけ方

動的に生成する派

Nuxt.jsでSPAしてFirebase Cloud FunctionsでOGPタグを作成する方法
vue-routerでtitleとdescriptionを動的に切り替える
SNS映えするWebアプリを...!FirebaseとVue.jsでSPAのOGP画像の動的生成をやってみたら案外楽だった

4つ目のSNS映えするWebアプリを...!FirebaseとVue.jsでSPAのOGP画像の動的生成をやってみたら案外楽だったは各ページ(画像を表示したいすべてのページ)の設定が必要です。動的に外部サーバーからデータを取得してページを生成する私のサイトでは難しそうです。

しかし、fiebase.jsonの設定を参考にできたので、静的に固定で表示する方法にしました。

firebase.json
{
  "hosting": {
    "public": "dist",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "/ogpimg", ←これを設定
        "destination": "/img/sns.jpg" ←画像格納先
      },
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}

ヘッダーのメタタグはこんな感じです。

  <meta name="twitter:card" content="summary">
  <meta property="og:url" content="https://testreftika.web.app/">
  <meta property="og:title" content="reftika">
  <meta property="og:description" content="図書館のレファレンスをもっと身近にするサービス">
  <meta property="og:image" content="https://testreftika.web.app/ogpimg">

その他仕様

キーワード検索について。入力後、初めて表示される結果はキーワードとのマッチ度順となっています(=何度やっても同じキーワードなら同じ結果)。
しかし、それだけでは面白みに欠けると思い、初回検索後、画面下部に表示される「もっと検索する」ボタンでは登録日のランダム検索と組み合わせて毎回変わるようにしました。

今後

ソースは今後Githubにアップする予定です。

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

<初心者>レファレンス協同データベースの記事をランダムに表示するWEBサービスをvue.jsで作りました。

Vue.js、Firebaseを使用したwebサービスreftikaを作成しました。備忘録も兼ねて参考にした記事をまとめながらデプロイするまでの流れを書きます。

※とてつもなく初心者です

現職は広告業界。Pythonはちょこちょこ簡単なスクリプトを書いて便利に使っていましたが、webは全くの初心者です。
HTML/CSSの勉強から始めて1ヶ月足らず。

本記事ではこうした方が良い、こうする必要はないということがあるかと思われます。コメントでご指摘いただけると幸いです。

作ったもの

reftika
レファレンス協同データベースからの簡易キーワード検索とランダム表示を提供するサービスです。
Screen Shot.png

レファレンスってなに?

レファレンスサービスとは?

レファレンス協同データベース(レファ協)の名称にも使われているレファレンスサービスとは、図書館員が資料などを使って、みなさんの疑問や質問にお答えしたり、資料を探すお手伝いをするサービスです。

レファレンス協同データベースとは?

公共図書館、大学図書館、専門図書館、学校図書館がレファ協のデータベースにデータを登録し、それをみなさんや図書館員、研究者などが利用する。
レファレンス協同データベースは、全国の図書館等で日々行われているレファレンスサービスの記録や情報の調べ方などを図書館員がデータベースに登録し、そのデータをインターネットを通じてみなさんに提供するサービスです。この「図書館の知識が集まったデータベース」を使って、日常生活でふと感じた疑問からビジネスや調査研究に関する専門的なものまで、幅広いデータがご覧いただけます。

具体例

カラスが石鹸を食べるというのは事実か。また、なぜ石鹸を食べるのか。(鳥取県立図書館)

クラシック音楽作品の日本での演奏記録を調べる(桐朋学園大学音楽学部附属図書館)

自転車のことを「チャリンコ」「ママチャリ」「ケッタマシーン」などと呼ぶが、どういう語源か。 (岐阜県図書館)

参考:
レファ協活用術!

なぜつくったのか

第一に勉強用ですが、実用的なものが作りたい思いがあり、スマホではちょっと見づらいと感じていたレファレンス協同データベースの検索に目を付けました。

はじめは、APIを利用してスマホ用に検索表示を見やすくするサービスをつくってみようと思っていたのですが、雑学チックなものから専門的なものまで多種多様なレファレンスがあるこのデータベースをランダムに見れたら面白そうだと考え、ランダム表示の機能をメインにしました。

ちなみに、この記事のサービスにも影響されました。→Qiitaの記事をランダムに読める API / サービス を4時間ぐらいで作った (Firebase/AWS/Docker) (運用費0円※)

環境

APIの仕様はこちら。
レファレンス協同データベース・システム操作マニュアル参加館用(オンライン版)
レスポンスはxmlかrss形式です。
ランダムな記事取得機能は提供されていません。

実装

Vue.jsで作成し、FirebaseのHostingを利用しています。

レファレンス協同データベースのAPIは「同一生成元ポリシー(Same-Origin Policy)」に引っ掛かるため、別途中継サーバーをfalconで作成しHerokuで運用しています。

利用した技術
"vue": "^2.6.10",
"vue-router": "^3.0.3",
"vuex": "^3.0.1"
"vue-toasted": "^1.1.27",
"axios": "^0.19.0",

その他、vue-cli、lodash、Firebase(Hosting)、python、falcon、Heroku、またGoogle Font、font awesomeなどを利用しました。

公開までをざっくりと

0.この記事をベースに作り始めました。Vue.jsとAxiosなら驚くほど簡単に作れる!外部APIを使ったWebアプリの実例
1.apiの仕様の確認と取得したデータの処理
2.vue.jsでテスト実行→同一生成元ポリシーに引っ掛かる
3.同一生成元ポリシーを回避するためにfalconでサーバーを立てる&Herokuにアップ。
4.vue.jsで、一覧ページ・詳細ページの作成。routerの設定など
5.firebaseデプロイ
6.デザインの作成。レスポンシブ化。twitter card対応。

土日で作るつもりでした。しかし、CORSについてほとんど知識がなかったため2番の時点で終わらないことを覚悟。予定変更して、結果的に4日間で一通り完成しました。

公開までをきっちりと

0.この記事をベースに作り始めました。Vue.jsとAxiosなら驚くほど簡単に作れる!外部APIを使ったWebアプリの実例

上記記事ではcdn版のvue.jsを使用していますが、今回はpackage.jsonでの管理も学びたかったため、vue-cliを使用しました。また、cssの勉強のためfoundationも使用していません。

$ npm install -g @vue/cli
$ vue create reftika
$ cd reftika
$ npm run serve

でサンプルが表示されます。
参考:
Vue.js を vue-cli を使ってシンプルにはじめてみる

1.apiの仕様の確認と取得したデータの処理

http://crd.ndl.go.jp/api/refsearch?type=reference&query=question any 読書
で「読書」というワードに適合度順のソートされた検索結果が200件返ってきます。
参考:レファレンス協同データベース・システム操作マニュアル参加館用(オンライン版)

ランダムな記事取得機能は提供されていなかったので対応。

ランダムな記事取得機能はAPIでは提供されていません。しかし、事例(記事)登録日による検索が可能です。そこで、一番最初のデータの登録日~現在までの日付をランダムに生成し、その日付をもとに検索することで疑似的にランダムなデータ取得を行えるようにします。
一番最初のデータの登録日が2004年3月6日だったため、以下の関数で2004年3月6日~今日までの間の日付をランダムに取得しています。

    makeRandomDate: function() {
      const start = new Date(2004, 3, 6);
      const end = new Date;
      const querydate = new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime()));
      const month = ("00" + (querydate.getMonth() + 1)).slice(-2);
      const date = ("00" + querydate.getDate()).slice(-2);
      const regdate = querydate.getFullYear() + month + date;
      return regdate;
    },
xml形式のレスポンス

「APIを使ってみた」系の記事のほとんどはjson形式でデータを取得していたので不安でしたが、xmlでも処理に困ることは特にありませんでした。エンティティ参照で「&」が「&amp;」で表示されてエンコード処理を加えた程度です。

const url = result.getElementsByTagName("reference")[0].getElementsByTagName("url")[0];
const obj = {
             省略
            url: url.innerHTML.replace(/&amp;/g, '&') + '\n',
          };
その他

2010年にレファレンス協同データベース主催の企画 API腕自慢という企画が行われていました。Twitterで今でも運用されているbot「おしえて!れはっち (@referty_bot)」はこの時に作られていたことを知りました。ほか、サービスは終了していますが、「ふわっとレファ協関連検索」も素晴らしいサービスだと思います。モチベーションに繋がりました。

2.vue.jsでテスト実行→同一生成元ポリシーに引っ掛かる

APIの仕様をもとにApp.vueを調整していきます。
レファレンス協同データベースの結果はXMLとして返ってくるので、パーサーもいれます。
初めはテストするために、キーワードは「読書」固定&マウント時に即APIを呼び出して画面に表示するように実装しました。

App.vue
<template>
<div id="app">
  <div class="columns medium-3" v-for="refqa in refqas">
    <div class="card">
      <div class="card-divider">
        {{ refqa.question }}
      </div>
      <div class="card-section">
        <p>{{ refqa.answer }}.</p>
      </div>
    </div>
  </div>
</div>
</template>

<script>
export default {
  data() {
    return {
      refqas: []  APIから返ってきた記事データの格納先
    }
  },
  mounted() {
    axios.get("http://crd.ndl.go.jp/api/refsearch?type=reference&query=question%20any%20%E8%AA%AD%E6%9B%B8")
      .then(response => {
         var oParser = new DOMParser();
         var oDOM = oParser.parseFromString(response.data, "application/xml");
        const results = oDOM.getElementsByTagName('result');
       ※返答XMLにおいて、11つの記事は<result></result>で囲まれている       ※その後、各記事の質問と回答を取得。
        for(const result of results){
          const question = result.getElementsByTagName("reference")[0].getElementsByTagName("question")[0];
          const answer = result.getElementsByTagName("reference")[0].getElementsByTagName("answer")[0];
          const obj ={
            question:question.innerHTML,
            answer:answer.innerHTML
          };
          this.refqas.push(obj)
        }
      }
    )
  }
}
</script>

しかし、

クロスオリジン要求をブロックしました: 同一生成元ポリシーによりhttp://crd.ndl.go.jp/api/refsearch?type=reference&query=question%20any%20%E8%AA%AD%E6%9B%B8にあるリモートリソースの読み込みは拒否されます

CORSまとめ
サーバサイドのCORS対応
Falconで光速のWeb APIサーバーを構築する
などを参考に「クロスオリジン要求」とは何かから調べました。

3.同一生成元ポリシーを回避するためにfalconでサーバーを立てる&Herokuにアップ。

調べてみると、開発環境では簡易的にサーバーを立てている方が多いらしい。
Cloud Functions for Firebaseで対処できないかと思ったのですが、Google Cloud PlatformならPythonが使えると知って調べたり、試したり...。

紆余曲折の上falconとHerokuに落ち着きました。

まずは、Falcon 「Access-Control-Allow-Origin」の回避方法 Python+FalconでサクッとAPIサーバを作るを参考にfalconでローカルにサーバーを立てる。
一旦、「読書」で検索した値だけを返すサーバーを作りました。

myfalcon.py
# -*- coding:utf-8 *-
import falcon
import requests

class CORSMiddleware:
    def process_request(self, req, resp):
        resp.set_header('Access-Control-Allow-Origin', '*')

class SampleResource:
    def on_get(self, req, resp):
        r_get = requests.get("http://crd.ndl.go.jp/api/refsearch?type=reference&query=question%20any%20%E8%AA%AD%E6%9B%B8")
        %E8%AA%AD%E6%9B%B8”は”読書”のパーセントエンコーディング。
        r_get_text = r_get.text
        obj = {
            "body": {
                "xml":r_get_text,
            }
        }
        resp.body = r_get_text

app = falcon.API(middleware=[CORSMiddleware()])
app.add_route('/sample', SampleResource())

if __name__ == "__main__":
    from wsgiref import simple_server
    httpd = simple_server.make_server("0.0.0.0", 8000, app)
    httpd.serve_forever()

サーバーを8000番ポートで起動して、
App.vueのアクセス先を変更。

App.vue
axios.get("http://localhost:8000/sample")

再度起動

npm run serve

screenshot201907080300vue.JPG

うまくいきました。

検索機能を実装したら、Herokuにデプロイします。
Procfileやruntime.txtなどを作成。

Procfile.
web: gunicorn fal:app --log-file -
runtime.txt
python-3.7.3
requirements.txt
falcon==2.0.0
gunicorn==19.9.0
requests==2.22.0

Herokuクライアントのインストール後、

$ heroku create
$ git remote add heroku https://git.heroku.com/xxxxx.git
$ git push heroku master

もうサービスが動いている!!ワクワクです!!

Procfileやruntime.txtなどをつくるのも初めてでしたが、こんな簡単にできるように整備されているのかと驚きました。

参考:
Herokuを久々に使用する
PythonのWebアプリフレームワークFalcon試してみた
【HEROKUとは】これを読めばOK!デプロイの仕方まで徹底解説
HerokuにRailsアプリをデプロイする手順

4.vue.jsで、一覧ページ・詳細ページの作成。routerの設定など

この時点までrouterは使っていませんでした。
一覧画面から個別記事のページへの遷移後、戻るボタンを押すと一覧のデータがリフレッシュされるのは不都合・不自然であるためその対処として導入しました。また、無限スクロールを実装した後にこのサービスではあまり意味がない機能であることに気づいたり、同様の処理を行う関数をまとめたり、計画性のなさを痛感しました。

あちこちエラーが出ては直しての繰り返しでしたが、ググりながらなんとか進めていくことができました。

Vue.js で入力フォームを↑↓キーやタブ・シフトタブでフォーカス移動するなどのチップス的な記事やVue.js初心者向け:Vue.jsとaxiosでJsonを取得してコンポーネントに反映するメモの「勘違いしていたけどこうだった」という記事は本当に勉強になりました。

先人に感謝します。

loodash

また、
キーワードの入力後、SPAらしく自動で検索が行われるように。かつ、APIに負荷がかかりすぎないようにlodashというライブラリが使えるらしい。ということで導入しました。入力操作が止まってから検索を実行するまでに5秒後のタイムラグをつけています。

レファレンス協同データベースのAPIは一応呼び出し無制限とはなっていますが、私がAPIにあまり触ったことがないことを前提に、安全のために少し長めにしています。

ただでさえHerokuを挟んで遅めなのにもっと遅くなってしまった...。ちなみに、少し負荷をかけたときにはHerokuの方がエラーを吐きました。応答性にはあまり期待しないでください。コンテンツは面白いものばかりなので、気長に待っていただけるとありがたいです。

 created: function() {
     this.debouncedGetAnswer = _.debounce(this.getAnswer, 5000);
     省略
  },
 省略
  getAnswer: function() {
      if (this.keyword === '') {
        this.loading = false;
        return;
      }
      this.refqas = [];
      axios.get(省略

5.firebaseデプロイ

以前Reactで勉強用の小さなサービスを作ったときや静的サイトのホスティングで使ったことはあったので、おさらいをしながらデプロイ。

一度SPA用の最適化の項目でNoと答えてしまったので、後でfirebase.jsonを修正しました。

? Configure as a single-page app (rewrite all urls to /index.html)? (y/N) N

としたために、

firebase.json
{
  "hosting": {
    "public": "dist",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [    ←この項目を後で付け足しました。![ScreenShotsp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/89564/3132c61f-7ef3-aa2f-d8ae-0ca1ba6cdffc.png)
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}

firebase init
firebase deploy

参考:
Firebaseで初めてのデプロイ
[Firebase][iOS] Firebase Hosting でキャンペーンページを作ってみよう

6.デザインの作成。レスポンシブ化。twitter card対応。

スマホでの見づらさ解消が作り始めたきっかけでもあるのでモバイルファーストに。文字情報が多いので極力シンプルに作成しました。

Screen Shot 2019-07-10 at 13.12.02.png

twitter cardの対応については、人によってそれぞれやり方があるようです。

静的にどのページも固定で表示する派

Vue.jsで静的にOGP対応する
【Vue.js】ツイッターのツイートに画像がついてるツイッターカードのつけ方

動的に生成する派

Nuxt.jsでSPAしてFirebase Cloud FunctionsでOGPタグを作成する方法
vue-routerでtitleとdescriptionを動的に切り替える
SNS映えするWebアプリを...!FirebaseとVue.jsでSPAのOGP画像の動的生成をやってみたら案外楽だった

4つ目のSNS映えするWebアプリを...!FirebaseとVue.jsでSPAのOGP画像の動的生成をやってみたら案外楽だったは各ページ(画像を表示したいすべてのページ)の設定が必要です。動的に外部サーバーからデータを取得してページを生成する私のサイトでは難しそうです。

fiebase.jsonの設定を参考にして、静的に対応する方法にしました。

firebase.json
{
  "hosting": {
    "public": "dist",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "/ogpimg", ←これを設定
        "destination": "/img/sns.jpg" ←画像格納先
      },
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}

ヘッダーのメタタグはこんな感じです。

  <meta name="twitter:card" content="summary">
  <meta property="og:url" content="https://testreftika.web.app/">
  <meta property="og:title" content="reftika">
  <meta property="og:description" content="図書館のレファレンスをもっと身近にするサービス">
  <meta property="og:image" content="https://testreftika.web.app/ogpimg">

その他仕様

キーワード検索について。入力後、初めて表示される結果はキーワードとのマッチ度順となっています(=何度やっても同じキーワードなら同じ結果)。
しかし、それだけでは面白みに欠けると思い、初回検索後、画面下部に表示される「もっと検索する」ボタンでは登録日のランダム検索と組み合わせて毎回変わるようにしました。

今後

ソースは今後Githubにアップする予定です。

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

BootstrapVueをRailsに導入してみる

初投稿です

RailsでBootstrapとVue.jsを使う方法を調べると、
Vue.jsをrails new project --webpack=vueで入れてきて、
BootstrapはGemfileにgem 'bootstrap'を書いてbundle installしてjsやscssをいじって、、、
というのがオーソドックスなやり方のよう

ちょっと面倒なのと、なぜかvueファイルのtemplateに書いたtooltipがちゃんと効いてくれなかったので、Bootstrapをgemで入れないようにして、yarnでBootstrapVueを入れるようにしてみた

BootstrapVue

おなじみBootstrapとVue.jsが合体したもの
公式サイト

やったこと

プロジェクト作成(webpackでvueもインストール)

$ rails new bootstrapvue_rails --webpack=vue

yarnでBootstrapVueをインストール

$ cd bootstrapvue_rails
$ yarn add bootstrap-vue

画面を用意して、ルーティングしてあげる

$ rails g controller Home index
config/routes.rb
Rails.application.routes.draw do
  root to: 'home#index'
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end

画面のjsファイルを作る

app/javascript/packs/home.js
import Vue from 'vue/dist/vue.esm'
import BootstrapVue from 'bootstrap-vue'
import App from '../app.vue'

import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'

Vue.use(BootstrapVue)

document.addEventListener('DOMContentLoaded', () => {
  const app = new Vue({
    el: '#hello',
     data: {
      message: "Can you say hello?"
    },
    components: { App }
  })
})

vueファイルを作る

app/javascript/app.vue
<template>
  <div id="app">
    <p>{{ message }}</p>
    <div class="text-center my-3">
      <b-button v-b-tooltip.hover title="Tooltip content">Hover Me</b-button>
    </div>
  </div>
</template>

<script>
export default {
  data: function () {
    return {
      message: "Hello Vue!"
    }
  }
}
</script>

<style scoped>
p {
  font-size: 2em;
  text-align: center;
}
</style>

viewで読み込む

app/views/home/index.html.erb
<h1>Home#index</h1>
<p>Find me in app/views/home/index.html.erb</p>

<div id='hello'>
  {{message}}
  <app></app>
</div>

<%= javascript_pack_tag 'home' %>

サーバーを立ち上げたらtooltipが効いてくれてる

$ ./bin/webpack-dev-server
$ rails s

スクリーンショット 2019-07-10 21.05.55.png

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

(Vue.js)methodsの処理の中でdataを参照できないエラーの解決法

問題: methodsの処理で、dataの参照に失敗する

button押下時に発生するgatherResultでdataの参照に失敗するエラーが出てしまいました。

App.vue
<template>
  <div id="app">
    <form v-on:submit.prevent>
      <input type="text" id="username" value="aaa"/>
      <button @click="gatherResult">send</button>
    </form>
    <div v-if=" this.result.length !== 0 ">{{ result }}</div>
  </div>
</template>

<script>
export default {
  name: "App",
  data: () => {
    return {
      result: []
    };
  },
  methods: {
    gatherResult: () => {
      this.result.push(document.getElementById("username").value);
    }
  },
};
</script>

エラー内容.txt
[Vue warn]: Error in v-on handler: "TypeError: Cannot read property 'result' of undefined"


found in


---> <App> at /src/App.vue

       <Root>

TypeError: Cannot read property 'result' of undefined
    at VueComponent.gatherResult 

最初はdataの定義の仕方が悪いのかと思ったり、いろいろ調べていると、gatherResult の定義の仕方の問題であることがわかりました。

原因: arrow関数で定義したmethodsの処理内でthisが参照できないため

https://stackoverflow.com/questions/49417410/how-to-save-reference-to-this-in-vue-component
Vueのmethods の処理にarrow関数をなんとなく使っていたのですが、arrow関数の中ではthisが参照できないので、dataにもアクセスが出来ないようでした。

解決方: 通常の関数として定義する

通常の関数として定義すればthisを参照できるようです。

App.vue
<template>
  <div id="app">
    <form v-on:submit.prevent>
      <input type="text" id="username" value="aaa"/>
      <button @click="gatherResult">send</button>
    </form>
    <div v-if=" this.result.length !== 0 ">{{ result }}</div>
  </div>
</template>

<script>
export default {
  name: "App",
  data: () => {
    return {
      result: []
    };
  },
  methods: {
    gatherResult(){
    this.result.push(document.getElementById("username").value);
    }
  },
};
</script>

これで、button押下時のエラーはなくなりました。
以上です。

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

VueやReactでコンポーネントやモジュールの依存関係を把握する

はじめに

なんかこのコンポーネント誰に依存されているかわかんないの、変更した時不安(´;ω;`)
って声があったので、コンポーネント間の依存関係を出すツールを調べて使ってみました。
結果かなり良かったです。

madge

DEMO

DEMO

概要

madgeはtsconfigやwebpackの設定を与えて、何をさせるのか指定します。
例えば、依存グラフをsvgで出力させたり、vg形式で出力させたりできます。
また、循環依存があるファイルリスト、依存されているのが1つ以上あるリスト、逆にどれにも依存されていないリストを出したりできます。
cliで軽くやるのからnodeでフルコントロールもできるのでかなり細かい事ができ、例えばこのモジュールはここからしか依存させないなどの設定をCIやエディターに簡単に組み込ませたりできます。(例:クリーンアーキテクチャーが持つポリシーの1つである、レイヤーを通り越して依存するの禁止等が簡単に実現できます。

React

一応jsxはtypescriptが標準サポートしているので、jsx間の依存は簡単に見れる。
graph.jpg

ちょっとボリュームのあるこのリポジトリの依存!
も出してみる。
graph.jpg
見やすくてかなり良い感じだと思います!

Vue

https://github.com/pahen/madge/issues/122
vueファイル間の依存を出したかったんですが、それはまだできないっぽいです(´;ω;`)

dependency-cruiser

dependency-cruiserは標準でvueの依存グラフを出せますし、madgeと同じように循環依存検知等色々できます。
また、出力はdot言語なので、dot言語のレイアウトエンジンを調整すればいろいろできます。

こちらのリポジトリをサンプルに依存グラフを出します。
https://github.com/kahirokunn/book-management

これがサンプルです
CreateBookForm_dependencygraph.jpg

特定コンポーネント単位なら割といけますね。

ちなみにデフォルト設定で全部出すとめっちゃカオスです
叩いたコマンドはこれです

$ depcruise --webpack-config ./node_modules/@vue/cli-service/webpack.config --exclude "^node_modules" --output-type dot src | dot -T jpg > dependencygraph.jpg && open ./dependencygraph.jpg

dependencygraph.jpg

レイアウトエンジンに関してはこちらの記事を見ていただきたいです
http://melborne.github.io/2013/04/02/graphviz-layouts/

デフォルトレイアウトエンジンはdotで、あまり見やすくないですね。。。
自分の好みはneatoです。
こんな感じになります。

dependencygraph.jpg

まぁカオスには変わりないですが、まだまし、、、?なのかなって(´;ω;`)

まとめ

現状の依存グラフのエコシステムだとこの辺までできるようです。
もしもっとこういうのもあってこうできるよ!とか、いろいろありましたら教えていただけると幸いです。

参考資料

https://qiita.com/rubytomato@github/items/51779135bc4b77c8c20d#edge
http://melborne.github.io/2013/04/02/graphviz-layouts

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

【Vue】単一ファイルコンポーネントの命名規則まとめ【ファイル名から記法まで】

早見表

記法 正しい例 推奨や補足
ファイル名 パスカルケース
or
ケバブケース
UserList.vue
user-list.vue
・複数単語にする
・親子関係の場合、親の名前を接頭辞にする
・最高レベルの単語からはじめて修飾語で終わる
・ベースコンポーネントはBase,App,Vを接頭辞にする
・単一インスタンスの場合はTheをつける
コンポーネントの記述 パスカルケース <UserList /> ・HTML内 → ケバブケース
・コンポーネント内 → パスカルケース
コンポーネントの登録 パスカルケース components: { UserList }
data キャメルケース data() { return { firstName } } ・関数で返す(必須)
メソッド名 キャメルケース hundleClick
イベント名 ケバブケース create-button-click ・末尾は不定形の動詞、名詞にすること
propsの受け渡し側 ケバブケース first-name
propsの受け取り側 キャメルケース firstName

ファイル名

・複数単語にする
・パスカルケース or ケバブケース
・親子関係の場合、親の名前を接頭辞にする
・最高レベルの単語からはじめて修飾語で終わる
・ベースとなるコンポーネントはBase,App,Vを接頭辞にする
・単一インスタンスの場合はTheをつける

複数単語にする

Todo.vu

⭕️

TodoList.vue

これは、タグ名の衝突が起きてしまう可能性があるためです。

パスカルケース or ケバブケースにする

user_list.vue //スネークケース
userList.vue  //キャメルケース

⭕️

user-list.vue //ケバブケース
UserList.vue  //パスカルケース

単一ファイルコンポーネント のファイル名は、すべてパスカルケース (PascalCase) にするか、すべてケバブケース (kebab-case) にするべきです。

やはり注意点としては、大文字と小文字を混合させずに統一することです。

強い結合関係がある場合、親の名前を接頭辞にする

強く関連するコンポーネント同士では、親となるコンポーネント名を接頭辞にすることが推奨されてます。

components/
├ UserList.vue
├ UserItem.vue
├ UserButton.vue

⭕️

components/
├ UserList.vue
├ UserListItem.vue
├ UserListItemButton.vue

最高レベルの単語からはじめて修飾語で終わる

関連するコンポーネントが一目でわかるように、最も関連する名詞を接頭辞にして、その修飾を後ろに書くやり方が推奨されてます。

components/
|- ClearSearchButton.vue
|- RunSearchButton.vue
|- SearchInput.vue
|- ExcludeFromSearchInput.vue

⭕️

components/
|- SearchButtonClear.vue
|- SearchButtonRun.vue
|- SearchInputQuery.vue
|- SearchInputExcludeGlob.vue

コンポーネントの数が膨大になった場合、関連するフォルダを作ってその配下に入れるやり方でもOK。
(上の例だと、serachフォルダを作って、その配下にコンポーネントを入れる)

ベースとなるコンポーネントは接頭辞をBase, App, Vにする

components/
|- VueInput.vue
|- MyButton.vue
|- Modal.vue

⭕️

components/
|- BaseInput.vue
|- BaseButton.vue
|- BaseModal.vue
-------------
components/
|- AppInput.vue
|- AppButton.vue
|- AppModal.vue
-------------
components/
|- VInput.vue
|- VButton.vue
|- VModal.vue

単一インスタンスの場合はTheをつける

ページごとに一つしか使われないコンポーネントは、プレフィックスにTheをつけること。

components/
|- VueHeader.vue
|- MySidebar.vue

⭕️

components/
|- TheHeader.vue
|- TheSidebar.vue

単一コンポーネント内の命名規則

※ 以下、<script>タグ等は省略します。

コンポーネントの登録(ES2015の場合)

・パスカルケース

export default {
  components: {
    'user-list': UserList
  }
}

⭕️

export default {
  components: {
    UserList
  }
}

コンポーネントの記述

・パスカルケース
・自己終了形式

<!-- パスカルケースじゃない -->
<!-- 自己終了形式じゃない -->
<user-list></user-list>

⭕️

<UserLiset />

[補足]

ほとんどのプロジェクトでは、コンポーネント名は 単一ファイルコンポーネント と文字列テンプレートの中では常にパスカルケース(PascalCase)になるべきです。 - しかし、 DOM テンプレートの中ではケバブケース(kebab-case)です。

この記事では単一ファイルコンポーネント内での記述方法を紹介していますが、DOMテンプレート内に記述するものはケバブケースにすべきです。

正確には以下を参考に。

<!-- 単一ファイルコンポーネント、文字列テンプレート、JSX の中 -->
<MyComponent />
<!-- DOM テンプレートの中 -->
<my-component></my-component>

これは、HTMLがカスタム要素の自己終了形式を許可していないためです。

data

・関数で返す
・プロパティはキャメルケース

export default {
  data: { // 関数で返してない
    first-name: 'suzuki' // プロパティがケバブケースになってる
  }
}

⭕️

export default {
  data () {
    return {
      firstName: 'suzuki'
    }
  }
}

メソッド名

・キャメルケース

export default {
  methods: {
    hundle-click() { //ケバブケースになってる
      console.log("clickしたよ")
    }
  }
}

⭕️

export default {
  methods: {
    hundleClick() {
      console.log("clickしたよ")
    }
  }
}

computedなども同様

propsの受け渡し

・渡す側 → ケバブケース(user-count)
・受け取る側 → キャメルケース(userCount)

渡す側 → ケバブケース

<UserListItem
  firstName="suzuki"
  last_name="itiro"
/>

⭕️

<UserListItem
  first-name="suzuki"
  last-name="itiro"
/>

受け取る側 → キャメルケース

export default {
  props: {
    first-name: String // ケバブケースになってる
    last_name: String // スネークケースになってる
  }
}

⭕️

export default {
  props: {
    firstName: String
    lastName: String
  }
}

イベント名

・ケバブケース
・末尾は不定形の動詞、名詞にすること

<template>
  <!-- キャメルケースになってる -->
  <button @click="this.$emit('CreateButtonClick')"></button>
  <!-- 末尾が不定形の動詞で終わってない -->
  <button @click="this.$emit('click-on-create-button')"></button>
</template>

⭕️

<template>
  <button @click="this.$emit('create-button-click')"></button>
</template>

まとめ

ざっくりとしたイメージですが、
迷ったら以下のように考えると自然な形になるのかなと思います。(後は適宜使い分けていただけると!)

・ファイル名 → パスカルケース
・HTML内  → ケバブケース
・JS内 → キャメルケース

公式
https://vuejs.org/v2/style-guide/
https://jp.vuejs.org/v2/style-guide/index.html

Qiita参考
https://qiita.com/isuke/items/3572e3b1c3677961f127
https://qiita.com/suin/items/e4d6a0e0d4912fbf9b77

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

JSES6のクラスの基本をまとめてみる。part1

クラスについて

クラスとはオブジェクト(データ)を作成するための設計図

main.js
class Data{   //クラス名は大文字で表記

}

インスタンス

クラス内指定されたオブジェクトのこと,
newでインスタンスを指定する

main.js
class Date{

}
 const data = new Data();
console.log(data);

クラスに処理の仕方をなにも指定していないので、空のオブジェクトData{}が出力される

コンストラクター

インスタンスに処理を指定すためのもの、クラス内にconstructor(){}と記述する。

main.js
class Date{
    constructor(){
     console.log("イチロー") //➀
 }
};
 const data = new Data();  //➁ ➀が実行される

コンストラクターはインスタンスが読み込まれた瞬間に実行される。

まとめ

プロゲートで学んだクラスの基礎まとめてみました。
プロゲートから察してもらって構わないが、筆者である自分はまだプログラミング2か月の初心者で、自分の理解度向上のために記事を書いているので、参考には決してしないでほしい。

  

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

JS ES6のクラスの基本をまとめてみる。part1

クラスについて

クラスとはオブジェクト(データ)を作成するための設計図

main.js
class Data{   //クラス名は大文字で表記

}

インスタンス

クラス内指定されたオブジェクトのこと,
newでインスタンスを指定する

main.js
class Date{

}
 const data = new Data();
console.log(data);

クラスに処理の仕方をなにも指定していないので、空のオブジェクトData{}が出力される

コンストラクター

インスタンスに処理を指定すためのもの、クラス内にconstructor(){}と記述する。

main.js
class Date{
    constructor(){
     console.log("イチロー") //➀
 }
};
const data = new Data();  //➁ ➀が実行されコンソール画面に"イチロー"と出力

コンストラクターはインスタンスが読み込まれた瞬間に実行される。

まとめ

プロゲートで学んだクラスの基礎まとめてみました。
プロゲートから察してもらって構わないが、筆者である自分はまだプログラミング2か月の初心者で、自分の理解度向上のために記事を書いているので、参考にはしないでほしい。

  

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

vueの基礎文法はJSES6の関数を用いたオブジェクトと似ている

javascriptの関数を用いたオブジェクト
index.html
<div id=pro></div>
main.js
const pro={
  data:()=>{
    document.write("イチロー");
  }
};
pro.data();
vueの基礎構文
index.html
<div id="app">  //vueを指定したいときはappをつける
   <p>{{message}}/p> //{{message}}にイチローと実行する
</div>
main.js;
const app=new Vue({
    el:"#app",
    data:{
     message:"イチロー"
     }
});

こんな感じで結構似ているので、vueを勉強する前にprogateのI~IVはやっておきたいところ

-ちなみに著者はプログラミング2か月の雑魚で、自分の理解度向上のためにやっているのであんまり参考にしないでいたただけると幸いです。

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

【Vue.js】テンプレートがコンパイルできない!!

vue-cliでvue-routerの学習をしようとしたらエラーが出てしばらくハマったので、解決法を載せます。

【条件】
vue --version: 3.9.2

【出力エラー】

[Vue warn]: You are using the runtime-only build of Vue where the
template compiler is not available. Either pre-compile the templates
into render functions, or use the compiler-included build.

エラーが出た経緯

vue-cliでvue-routerを取り入れ、こんな感じにいじりました。

App.vue
<template>
  <div id="app">   
      <router-link to="/top">トップ</router-link> |
      <router-link to="/users">ユーザー</router-link>
    <router-view/>
  </div>
</template>
router.js
import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/top',
      component: {
        template: '<div>トップページです</div>'
      }
    },
    {
      path: '/users',
      component: {
        template: '<div>ユーザーページです</div>'
      }
    },
  ]
})

「トップ」と「ユーザー」をクリックするとページが切り替わる、という簡単なコードです。
これでいけるはずなのですが、先ほどのエラーが出ました。

解決法

vue-cliで生成されたディレクトリ直下に『vue.config.js』というファイルを作成。
そこにこのコードをうちます。

vue.config.js
module.exports = {
  configureWebpack: {
    resolve: {
      alias: {
        'vue$': 'vue/dist/vue.esm.js'
      }
    }
  }
}

そして、npmサーバーをもう一回立ち上げます。
もう一回立ち上げないとずっとエラーのままですので注意してください。(ここでかなりハマりました)

原因

簡単に説明すると、templeteオプションはブラウザにJITコンパイルされる時にrender関数へ変換されます。
その為に必要なファイルがvue-cliのデフォルトの状態では入っていないので、完全ビルドをする必要があります。
(完全ビルドよりも最初の設定の方が30%軽量らしく、初期設定では入っていないようです。)
ちょっと古いですが、こちらの記事が大変参考になりました。
Vue2.x系のハマりどころ templateとコンパイラを完全解説するよ


以上です。
学習を始めようとした時にいきなりこういうエラーにハマるのは挫折の原因になりかねないので、記述しておきます。
補足、修正などありましたらご教授お願いします。
最後まで読んでいただきありがとうございます。

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

【Vue.js】テンプレートがコンパイルできない【Error】

vue-cliでvue-routerの学習をしようとしたらエラーが出てしばらくハマったので、解決法を載せます。

【条件】
vue --version: 3.9.2

【出力エラー】

[Vue warn]: You are using the runtime-only build of Vue where the
template compiler is not available. Either pre-compile the templates
into render functions, or use the compiler-included build.

エラーが出た経緯

vue-cliでvue-routerを取り入れ、こんな感じにいじりました。

App.vue
<template>
  <div id="app">   
      <router-link to="/top">トップ</router-link> |
      <router-link to="/users">ユーザー</router-link>
    <router-view/>
  </div>
</template>
router.js
import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/top',
      component: {
        template: '<div>トップページです</div>'
      }
    },
    {
      path: '/users',
      component: {
        template: '<div>ユーザーページです</div>'
      }
    },
  ]
})

「トップ」と「ユーザー」をクリックするとページが切り替わる、という簡単なコードです。
これでいけるはずなのですが、先ほどのエラーが出ました。

解決法

vue-cliで生成されたディレクトリ直下に『vue.config.js』というファイルを作成。
そこにこのコードをうちます。

vue.config.js
module.exports = {
  configureWebpack: {
    resolve: {
      alias: {
        'vue$': 'vue/dist/vue.esm.js'
      }
    }
  }
}

そして、npmサーバーをもう一回立ち上げます。
もう一回立ち上げないとずっとエラーのままですので注意してください。(ここでかなりハマりました)

原因

簡単に説明すると、templeteオプションはブラウザにJITコンパイルされる時にrender関数へ変換されます。
その為に必要なファイルがvue-cliのデフォルトの状態では入っていないので、完全ビルドをする必要があります。
(完全ビルドよりも最初の設定の方が30%軽量らしく、初期設定では入っていないようです。)
ちょっと古いですが、こちらの記事が大変参考になりました。
Vue2.x系のハマりどころ templateとコンパイラを完全解説するよ


以上です。
学習を始めようとした時にいきなりこういうエラーにハマるのは挫折の原因になりかねないので、記述しておきます。
補足、修正などありましたらご教授お願いします。
最後まで読んでいただきありがとうございます。

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

Nuxt.jsにGoogle reCAPTCHA v3 を入れる。

reCAPTCHA

Nuxt.jsで作った問い合わせフォームにbot対策でgoogle reCAPTCHA Version 3を導入しました。v3 は機械学習により読みにく文字を入力したりあいまいな画像選択させたりすることなしで、人間かbotかの判定を0(bot)から1(botではない)のスコアにして返してくれるそうな。

Google reCAPTCHA
https://developers.google.com/recaptcha/?hl=ja

はじめに

利用にはGoogle アカウントにてログイン、サイトを登録、ドメインの登録(本番環境と開発環境、localhostなど)して、サイトキーとシークレットキーを取得します。

APIの読み込み

reCAPTCHを利用するページでapiソースをパラメータに取得したサイトキーを指定して読み込み。nuxt.config.jsで読み込んでも構いません。その場合は全ページにreCAPCHAのラベルが表示されます。

pages/contact.vue
export default {
  head() {
   return {
     script: [
       {
         src: 'https://www.google.com/recaptcha/api.js?render=サイトキー'
       }
     ],
   }
  }
}

トークンの取得

 フォームを送信する前に、 バックエンド( 今回はphp)のメール送信プログラムにパラメータとして送るトークンを取得します。

pages/contact.vue
methods: {
   getToken: function(e) {

     grecaptcha.ready(function() {
       grecaptcha
         .execute('サイトキー', {
           action: 'homepage'
         })
         .then(function(token) {
           _this.sendForm(token)
         })    
     })
   }
}

バックエンド(php)にパラメーターを渡す

パラメーターは URLSearchParmasインターフェースで渡す必要があるようでけっこうハマりました。先ほど取得したトークンはg-recaptcha-response というパラメータ名でURLSearchParamsに追加、postでバックエンドへ。

pages/contact.vue
methods: {
    sendForm(token) {

     const params = new URLSearchParams()
     params.append('form-name', this.formName)
     params.append('form-email', this.formEmail)
     params.append('form-content', this.formContent)
     params.append('g-recaptcha-response', token)

     const res = await this.$axios.$post(
       'https://メール送信プログラムURL',
       params
     )
     ...
  }
}

バッグエンドでの処理

今回はphpにてメール送信をしました。

header('Access-Control-Allow-Origin: 許可するドメイン');
header('Access-Control-Allow-Methods: POST');
header('Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept');
header('Content-Type: application/json; charset=utf-8' ) ;

サーバーサイドによってアクセス制御を整えます。

シークレットキーとトークン

シークレットキーとトークンをパラメータにgoogle reCAPTCHにお伺いを立てます。その結果がjsonで返ってきます。閾値を設定し、それ以上なら送信、以下ならエラーをnuxtに返します。

// シークレット}キー
$secret_key = 'シークレットキー';

// エンドポイント
$endpoint  = 'https://www.google.com/recaptcha/api/siteverify';

$params = '?secret=' . $secret_key;
$params .= '&response=' . $_POST['g-recaptcha-response'];

// botの閾値
$safescore = 0.1;

// 判定結果の取得
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $endpoint . $params);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); // 証明書の検証を行わない
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); // curl_execの結果を文字列で返す
curl_setopt($curl, CURLOPT_TIMEOUT, 5); // タイムアウトの秒数
$json = curl_exec($curl);
curl_close($curl);

$obj = json_decode($json);

// 判定の成功と、スコアが閾値以上ならメール送信
if ($obj->success && $obj->score >= $safescore) {
 // メール送信
} else {
//閾値以下ならnuxtにjsonを返す
  $obj->isbot = true;
  echo json_encode($obj);
  exit();
}

このスコアは機械学習によるものだそうで、実際に運用してreCAPTCHAのコンソールで確認しならがら閾値を調整していくイメージでしょうか。

実際インド旅行中にホテルのwifiでのテストでは0.5-0.7くらいでしたがスタバのフリーwifiからだと0.1だったり、日本の自宅wifiからだと0.9だったりしました。

これで一通りの実装の流れは終了です。

bot判定だった場合

実際にbotだった場合はさておいて、人間だったけどbot 判定されて送信でき叶った場合にどういうメッセージを表示するのがいいか考えた。

表示する文言はさておいて、別のな方法で問い合わせしてもらう(メールや電話)のもひとつだが、入力した問い合わせ内容(名前、メールアドレス、問い合わせ内容など)を再度入力させるのは申し訳ないので、送れなかった場合はlocalStorageに各項目保存するようにした。これで後ほど違うwifi環境などで試してみたら遅れればlocalStrorageを削除する。

pages/contact.vue
// 判定は成功で、bot判定なら
if (res.success && res.isbot) {

    // add localStorage
    localStorage.setItem('formName', this.formName)
    localStorage.setItem('formEmail', this.formEmail)
    localStorage.setItem('formContent', this.formContent)
} else {
    // 送信できた場合、localStorageがあれば削除
    if(localStorage.getItem('formName')) localStorage.removeItem('formName')
    if(localStorage.getItem('formEmail')) localStorage.removeItem('formEmail')
    if(localStorage.getItem('formContent')) localStorage.removeItem('formContent')

}

ページリロード時、 localStorageにデータがある場合はそれをinputに渡す

pages/contact.vue
data: function() {
   return {
     ...
    // localStorage.getItem('formName') に保存されていればそこから取得
    formName: localStorage.getItem('formName') || '', 
    formEmail: localStorage.getItem('formEmail') || '',
    formContent: localStorage.getItem('formContent') || ''
   }
 },

デスクトップの場合違うwifiで、というのはあまりできないだろうが、せっかく書いた問い合わせ内容が消えてしまうよりはいいだろう。同じブラウザでもう一度試したり、コピペしてブラウザを変えたりメールやメモに移したりもできる。

以上、参考になれば幸いです。

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

Vueでイケてるインクリメンタルサーチのコンポーネントを作る

概要

入力値から自動的に候補を絞り込むUIってよく見かけます。
例えば、Slackとかでも"@"を入力して文字を入力すると
メンション先が自動的に絞り込まるので使いやすいですね。
こういうUIのことをインクリメンタルサーチと呼ぶらしいです。

今回はVueでイケてるインクリメンタルサーチのコンポーネントを作ります。

環境

サンプル

今回実装するコンポーネントのサンプルです。
このサンプルではQiitaのAPIで、Qiitaに投稿された記事を記事名でインクリメンタルサーチします。
上が単一選択で下が複数選択です
combotest.gif

全体のソースコードはGitHubにおいています
https://github.com/koushisa/incremental-search-sample

以下は解説です。

ソースコード

コンポーネントを使用している画面です。

App.vue
<template>
    <v-content>
      <v-container fluid>
        <v-layout column>
          <v-flex>
            <app-combobox
              label="Qiitaの記事を検索"
              :model.sync="singleItem"
              :search-input.sync="query1"
              :search-func="searchQiita1"
              :search-results="qiitaItems"
              item-text="title"
              item-sub-text="url"
              item-identifier="url"
              single
            ></app-combobox>
          </v-flex>
          <v-flex>
            <app-combobox
              label="Qiitaの記事を選択(複数選択可能)"
              :model.sync="multiItem"
              :search-input.sync="query2"
              :search-func="searchQiita2"
              :search-results="qiitaItems"
              item-text="title"
              item-sub-text="url"
              item-identifier="url"
            ></app-combobox>
          </v-flex>
        </v-layout>
      </v-container>
    </v-content>
</template>

<script>
import axios from "axios";
export default {
  data() {
    return {
      singleItem: null,
      multiItem: [],

      //記事の検索条件(前方一致)
      query1: "",
      query2: "",

      //記事検索のレスポンス
      qiitaItems: []
    };
  },

  methods: {
    //記事検索
    searchQiita1() {
      axios
        .get(`https://qiita.com/api/v2/items?query=${this.query1}`)
        .then(response => (this.qiitaItems = response.data));
    },
    searchQiita2() {
      axios
        .get(`https://qiita.com/api/v2/items?query=${this.query2}`)
        .then(response => (this.qiitaItems = response.data));
    }
  }
};
</script>

こちらはインクリメンタルサーチのコンポーネントです

AppCombobox.vue
<template>
  <v-combobox
    v-model="_model"
    :label="label"
    :items="searchResults"
    no-filter
    :search-input.sync="_searchInput"
    :allow-overflow="false"
    hide-no-data
    return-object
    :item-text="itemText"
    :item-value="itemIdentifier"
    chips
    small-chips
    deletable-chips
    :multiple="!single"
    :disabled="readonly"
  >
    <!-- 選択済みの要素はチップで表示 -->
    <template v-slot:selection="data">
      <!-- 単一 -->
      <template v-if="single">
        <v-chip
          v-if="typeof model[itemText] !== 'undefined'"
          :selected="data.selected"
          close
          class="chip--select-multi"
          @input="remove(model)"
        >{{ model[itemText]}}</v-chip>
      </template>
      <!-- 複数 -->
      <template v-else>
        <v-chip
          v-if="typeof data.item[itemText] !== 'undefined'"
          :selected="data.selected"
          close
          class="chip--select-multi"
          @input="remove(data.item)"
        >{{ data.item[itemText]}}</v-chip>
      </template>
    </template>

    <!-- 検索結果の表示部 -->
    <template v-slot:item="data">
      <template v-if="typeof data.item !== 'object'">
        <v-list-tile-content v-text="data.item"></v-list-tile-content>
      </template>
      <template v-else>
        <v-list-tile-content>
          <v-list-tile-title v-text="data.item[itemText]"></v-list-tile-title>
          <v-list-tile-sub-title v-text="data.item[itemSubText]"></v-list-tile-sub-title>
        </v-list-tile-content>
      </template>
    </template>
  </v-combobox>
</template>

<script>
import { throttle, isEmpty } from "lodash";

export default {
  props: {
    model: {
      type: [Object, Array, String],
      default: null
    },
    searchInput: {
      type: String,
      default: ""
    },
    searchFunc: {
      type: Function,
      required: true
    },
    searchResults: {
      type: Array,
      required: true
    },
    label: {
      type: String,
      default: ""
    },
    itemText: {
      type: String,
      default: ""
    },
    itemSubText: {
      type: String,
      default: ""
    },
    itemIdentifier: {
      type: String,
      default: ""
    },
    single: {
      type: Boolean
    },
    readonly: {
      type: Boolean,
      default: false
    },
  },
  computed: {
    _model: {
      get() {
        return this.model;
      },
      set(newVal) {
        return this.$emit("update:model", newVal);
      }
    },
    _searchInput: {
      get() {
        return this.searchInput;
      },
      set(newVal) {
        return this.$emit("update:search-input", newVal);
      }
    }
  },
  watch: {
    //インクリメンタルサーチ
    searchInput(val, prev) {
      if (!isEmpty(val)) {
        this.searchWithInterval();
      }
    },
    // APIで取得した値のみ入力可能にします。
    model(val, prev) {
      //単一
      if (this.single) {
        if (val === null) {
          this.$emit("update:model", prev);
          return;
        }
        if (typeof val === "string") {
          this.$emit("update:model", prev);
          return;
        }
      } else {
        // 複数
        if (val.length === 0) {
          return;
        }
        if (typeof val[val.length - 1][this.itemIdentifier] === "undefined") {
          this.model.splice(this.model.length - 1, 1);
        }
      }
    }
  },
  created() {
    //APIの実行を0.5秒に1回に制限します。
    this.searchWithInterval = throttle(this.searchFunc, 500);
  },
  methods: {
    //チップを削除します。
    remove(selected) {
      //単一
      if (this.single) {
        this.$emit("update:model", {});
      } else {
        //複数
        const index = this.model.findIndex(
          item => item[this.itemIdentifier] === selected[this.itemIdentifier]
        );
        if (index >= 0) {
          this.model.splice(index, 1);
        }
      }
    }
  }
};
</script>

解説

VComboboxをラップし、より汎用的に使えるようにしました。
検索結果の候補からしか入力できないようにするため、watchで小細工をしています。

使用できるプロパティは下記のようになっています。
今回はVComboboxの中で最低限のプロパティを使えるようにしました。

プロパティ 備考
model [Object, Array, String], 候補から選択したオブジェクトをバインド
後述のsingleを指定しない場合はArray型となる
searchInput String 入力値をバインド
この値をもとにインクリメンタルサーチを行う
searchFunc Function 検索処理
searchResults Array 検索結果
この結果が候補に表示される
label String ラベル
itemText String 候補のメインテキスト
候補のオブジェクトから表示したいキーを指定
itemSubText String 候補のサブテキスト
候補のオブジェクトから表示したいキーを指定
itemIdentifier String 候補のオブジェクトから一意になるキーを指定
single Boolean 検索結果から1つしか選択できないようにする
readonly Boolean 読み取り専用にする

まとめ

Vuetifyは個人的に使いやすいので気に入ってますが、
公式のドキュメントは若干不親切な感じはありますね。。。

使える機能は多いので、うまく付き合えるとかなりの価値を発揮できそうです。

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

vue.jsのディレクティブをまとめてみました。

v-if

qiita.html
<div id="app">
        <p v-if="error">エラーです</p>
    </div>
 <script src="https://cdn.jsdelivr.net/npm/vue"></script>
 <script>
let app=new Vue({
    el:"#app",
    data:{
        error:true//falseにすると画面に'エラーですが表示されなくなる'
    }
});
</script>

v-bind

{{}}マスタッシュ記号を、html属性の中に出力できないために使われる。

index.html
<div id="app">
        <p class="{{error_class}}">{{error_class}}</p>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
 <script>
   let app=new Vue({
  el:"#app",
  data:{
      error_class:"error"
  }
});

この状態だと出力されない2019-07-10.png

そこでv-bindをつかう

index.html
 <div id="app">
        <p v-bind:class="error_class">エラーです</p>
        <img v-bind:src="img_src">
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
 <script>
   let app=new Vue({
  el:"#app",
  data:{
      error_class:"abc",
      img_src:"img01.png"
  }
});
    </script>

うまく出力されました
2019-07-10 (1).png

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

vue.jsのディレクティブをまとめてみました。part1

v-if

qiita.html
<div id="app">
        <p v-if="error">エラーです</p>
    </div>
 <script src="https://cdn.jsdelivr.net/npm/vue"></script>
 <script>
let app=new Vue({
    el:"#app",
    data:{
        error:true//falseにすると画面に'エラーです'が表示されなくなる
    }
});
</script>

v-bind

{{}}マスタッシュ記号を、html属性の中に出力できないために使われる。

index.html
<div id="app">
        <p class="{{error_class}}">{{error_class}}</p>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
 <script>
   let app=new Vue({
  el:"#app",
  data:{
      error_class:"error"
  }
});

この状態だと出力されない2019-07-10.png

そこでv-bindをつかう

index.html
 <div id="app">
        <p v-bind:class="error_class">エラーです</p>
        <img v-bind:src="img_src">
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
 <script>
   let app=new Vue({
  el:"#app",
  data:{
      error_class:"abc",
      img_src:"img01.png"
  }
});
    </script>

うまく出力されました
2019-07-10 (1).png
参考url
Vue.js入門 #03:if構文とディレクティブ
Vue.js入門 #04:Vue.jsによる属性の書き換え

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

vue.jsの基本をアウトプットしてみた。

index.html

  <div id="app1">
        {{message}}
    </div>
    <div id="app2">
        {{message}}
    </div>
   <!--app1かapp2にイチローと出力したい-->
    <script src="https://cdn.jsdelivr.net/npm/vue"></script> //vueの読み込み
    <script src="main.js"></script>
</body>
</html>

app1とapp2の{{message}}どっちにマサディアンが出力されたのか

main.js
 let app=new Vue({
    el:"#app2",//対象を<div id="app2"></div>とする。
    data:{//ここdataでに処理のしかたをきめる。
        message:"イチロー"
    }
});

よって{{message}}にイチローが代入された出力結果はapp2

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