頑張らないために頑張る

ゆるく頑張ります

Vue cliとBuefyでシンプルに始めるVue.js - テキスト生成の機能を実装してデプロイまで

Posted at — Sep 2, 2019

はじめに

前回は、とりあえず見た目のコンポーネントを実装しました。今回はテキストを生成する機能を実装してデプロイまでしてしまいます。後述しますがデプロイ先はGitHub Pagesです。便利ですよね。

今回の成果物

こちらです。

Vue.jsのオフィシャルを参考に

機能を実装するにあたっては何はともあれ、まずはVue.jsのガイドを読みます。とくにコンポーネントの部分はよく参照することになると思います。自分はここを見つつソースの編集をしてました。

実際書いたソース

結構長いので折りたたみます。

JavaScriptのソース

<template>
<div>
    <b-button
    size="is-medium"
    v-bind:icon-left="buttonIcon"
    @click="generateText(quoteTextname, outputNumber, genOptions)"
    >{{ buttonMsg }}</b-button>
</div>
</template>

<script>
export default {
name: "GenerateText",
props: {
    buttonMsg: {
    type: String,
    required: true
    },
    buttonIcon: {
    type: String,
    required: true
    },
    quoteTextname: {
    type: String,
    required: true
    },
    outputNumber: {
    type: Number,
    required: true
    },
    genOptions: {
    type: Array,
    required: false
    }
},
methods: {
    generateText: function(quoteTextname, outputNumber, genOptions) {
    // コピーのコマンドに対応しているか確認する
    if (document.queryCommandSupported("copy")) {
        // テキスト生成する元ネタから指定された数の分だけ文字列を切り出す
        this.quoteTexts.forEach(quoteText => {
        if (quoteTextname === quoteText[0]) {
            this.returnText = quoteText[1].slice(0, outputNumber);
        }
        });

        // 指定されたオプションに従って文字列を編集する
        if (genOptions.length > 0) {
            const breakChar = "\n";
            let periodChar = "。";
            if (quoteTextname === "Lorem") {
                periodChar = ". ";
            }

            if (genOptions.find(genOption => genOption === "needBreak")){
                // 改行を付与する
                this.returnText = this.returnText.split(periodChar).join(periodChar + breakChar);
            }

            if (genOptions.find(genOption => genOption === "makeWide")){
                // 全角に変換する
                this.returnText = this.returnText.replace(/[A-Za-z0-9]/g, function(s) {
                    return String.fromCharCode(s.charCodeAt(0) + 65248);
                });
            }

            if (genOptions.find(genOption => genOption === "endWithPeriod")){
                // 句点あるいはピリオドで終わるよう編集する
                const lastPeriodIndex = this.returnText.lastIndexOf(periodChar) + 1;
                this.returnText = this.returnText.slice(0, lastPeriodIndex);
            }

            if (genOptions.find(genOption => genOption === "addPtag")){
                // 文章ごとにPタグを付与する
                if (genOptions.find(genOption => genOption === "needBreak")){
                    // 改行コード付与済みの場合は、Pタグを付与後再度改行コードを付与する
                    const splitedByBreak = this.returnText.split(breakChar);
                    this.returnText = "";
                    splitedByBreak.forEach(word => {
                        this.returnText = this.returnText + "<p>" + word + "</p>" + breakChar;
                    });
                } else {
                    const splitedByPeriod = this.returnText.split(periodChar);
                    this.returnText = "";
                    splitedByPeriod.forEach(word => {
                        this.returnText = this.returnText + "<p>" + word + periodChar + "</p>";
                    });
                }
            }
        }

        // コピー実行の下準備として新規のテキストエリアを生成し配置する
        const copyFrom = document.createElement("textarea");
        copyFrom.textContent = this.returnText;
        const bodyElm = document.getElementsByTagName("body")[0];
        bodyElm.appendChild(copyFrom);

        // テキストをクリップボードにコピーしてテキストエリアは破棄する
        copyFrom.select();
        const retVal = document.execCommand("copy");
        bodyElm.removeChild(copyFrom);

        // 正常に終わった旨を表示する
        this.$buefy.toast.open({
            message: "クリップボードにコピーしました!",
            type: "is-success"
        });

        this.$emit("child-event", this.returnText + retVal);
    } else {
        // コピー機能にブラウザが未対応または無効ならエラーメッセージを出力
        this.$buefy.toast.open({
        duration: 5000,
        message: "コピーに失敗しました。",
        type: "is-danger"
        });

        this.$emit("child-event", this.returnText);
    }
    }
},
data: function() {
    return {
    returnText: "nothing!",
    quoteTexts: [
        [
        "Lorem",
        "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Dui id ornare arcu odio ut sem nulla pharetra diam. Risus viverra adipiscing at in. Augue lacus viverra vitae congue eu consequat ac felis donec. Volutpat sed cras ornare arcu dui. Vitae nunc sed velit dignissim sodales ut eu sem integer. Faucibus in ornare quam viverra orci. Dictumst vestibulum rhoncus est pellentesque elit ullamcorper dignissim. Nulla facilisi nullam vehicula ipsum a arcu cursus vitae congue. Placerat in egestas erat imperdiet. Dolor sit amet consectetur adipiscing elit duis. Adipiscing commodo elit at imperdiet dui. Ut tellus elementum sagittis vitae et leo duis ut. Tristique magna sit amet purus. Commodo odio aenean sed adipiscing diam donec."
        ],
        [
        "Kokoro",
        "私はその人を常に先生と呼んでいた。だからここでもただ先生と書くだけで本名は打ち明けない。これは世間を憚かる遠慮というよりも、その方が私にとって自然だからである。私はその人の記憶を呼び起すごとに、すぐ「先生」といいたくなる。筆を執っても心持は同じ事である。よそよそしい頭文字などはとても使う気にならない。私が先生と知り合いになったのは鎌倉である。その時私はまだ若々しい書生であった。暑中休暇を利用して海水浴に行った友達からぜひ来いという端書を受け取ったので、私は多少の金を工面して、出掛ける事にした。私は金の工面に二、三日を費やした。ところが私が鎌倉に着いて三日と経たないうちに、私を呼び寄せた友達は、急に国元から帰れという電報を受け取った。電報には母が病気だからと断ってあったけれども友達はそれを信じなかった。友達はかねてから国元にいる親たちに勧まない結婚を強いられていた。彼は現代の習慣からいうと結婚するにはあまり年が若過ぎた。それに肝心の当人が気に入らなかった。それで夏休みに当然帰るべきところを、わざと避けて東京の近くで遊んでいたのである。彼は電報を私に見せてどうしようと相談をした。私にはどうしていいか分らなかった。けれども実際彼の母が病気であるとすれば彼は固より帰るべきはずであった。それで彼はとうとう帰る事になった。せっかく来た私は一人取り残された。学校の授業が始まるにはまだ大分日数があるので鎌倉におってもよし、帰ってもよいという境遇にいた私は、当分元の宿に留まる覚悟をした。友達は中国のある資産家の息子で金に不自由のない男であったけれども、学校が学校なのと年が年なので、生活の程度は私とそう変りもしなかった。したがって一人ぼっちになった私は別に恰好な宿を探す面倒ももたなかったのである。宿は鎌倉でも辺鄙な方角にあった。玉突きだのアイスクリームだのというハイカラなものには長い畷を一つ越さなければ手が届かなかった。車で行っても二十銭は取られた。けれども個人の別荘はそこここにいくつでも建てられていた。"
        ],
        [
        "Galaxy",
        "「ではみなさんは、そういうふうに川だと言われたり、乳の流れたあとだと言われたりしていた、このぼんやりと白いものがほんとうは何かご承知ですか」先生は、黒板につるした大きな黒い星座の図の、上から下へ白くけぶった銀河帯のようなところを指しながら、みんなに問いをかけました。カムパネルラが手をあげました。それから四、五人手をあげました。ジョバンニも手をあげようとして、急いでそのままやめました。たしかにあれがみんな星だと、いつか雑誌で読んだのでしたが、このごろはジョバンニはまるで毎日教室でもねむく、本を読むひまも読む本もないので、なんだかどんなこともよくわからないという気持ちがするのでした。ところが先生は早くもそれを見つけたのでした。「ジョバンニさん。あなたはわかっているのでしょう」ジョバンニは勢いよく立ちあがりましたが、立ってみるともうはっきりとそれを答えることができないのでした。ザネリが前の席からふりかえって、ジョバンニを見てくすっとわらいました。ジョバンニはもうどぎまぎしてまっ赤になってしまいました。先生がまた言いました。「大きな望遠鏡で銀河をよっく調べると銀河はだいたい何でしょう」やっぱり星だとジョバンニは思いましたが、こんどもすぐに答えることができませんでした。先生はしばらく困ったようすでしたが、眼をカムパネルラの方へ向けて、「ではカムパネルラさん」と名指しました。するとあんなに元気に手をあげたカムパネルラが、やはりもじもじ立ち上がったままやはり答えができませんでした。先生は意外なようにしばらくじっとカムパネルラを見ていましたが、急いで、「では、よし」と言いながら、自分で星図を指しました。「このぼんやりと白い銀河を大きないい望遠鏡で見ますと、もうたくさんの小さな星に見えるのです。ジョバンニさんそうでしょう」ジョバンニはまっ赤になってうなずきました。けれどもいつかジョバンニの眼のなかには涙がいっぱいになりました。そうだ僕は知っていたのだ、もちろんカムパネルラも知っている、それはいつかカムパネルラのお父さんの博士のうちでカムパネルラといっしょに読んだ雑誌のなかにあったのだ。それどこでなくカムパネルラは、その雑誌を読むと、すぐお父さんの書斎から巨きな本をもってきて、ぎんがというところをひろげ、まっ黒な頁いっぱいに白に点々のある美しい写真を二人でいつまでも見たのでした。それをカムパネルラが忘れるはずもなかったのに、すぐに返事をしなかったのは、このごろぼくが、朝にも午後にも仕事がつらく、学校に出てももうみんなともはきはき遊ばず、カムパネルラともあんまり物を言わないようになったので、カムパネルラがそれを知ってきのどくがってわざと返事をしなかったのだ、そう考えるとたまらないほど、じぶんもカムパネルラもあわれなような気がするのでした。"
        ],
        [
        "Wikipedia",
        "超弦理論が登場する以前に最も小さなスケールを記述した理論は場の量子論である。そこでは粒子を点、すなわち点粒子として扱ってきた(局所場の理論に代わる、広がりを持った粒子の概念を導入したS行列理論や非局所場理論などもあった)。一方、超弦理論では粒子を弦の振動として表す。1960年代、イタリアの物理学者、ガブリエーレ・ヴェネツィアーノが核子の内部で働く強い力の性質をベータ関数で表し、その式の示す構造が「弦 (string)」によって記述されることに南部陽一郎、レオナルド・サスキンド、ホルガー・ベック・ニールセンらが気付いたことから始まる。弦には「閉じた弦」と「開いた弦」の2種類を考えることができ、開いた弦はスピン1のゲージ粒子(光子、ウィークボソン、グルーオンなどに相当)を含み、閉じた弦はスピン2の重力子を含む。開いた弦の相互作用を考えるとどうしても閉じた弦、すなわち重力子を含まざるを得ない。そのため、強い力のみを記述する理論と捉えることは難しいことが分かった。逆に言えば、弦を基本要素と考えることで、自然に重力を量子化したものが得られると考えられる。そのため、超弦理論は万物の理論となりうる可能性がある。超弦理論は素粒子の標準模型の様々な粒子を導出しうる大きな自由度を持ち、それを元に現在までに様々なモデルが提案されている。このように極めて小さい弦を宇宙の最小基本要素と考え、自然界の全ての力を数学的に表現しようというのが、いわゆる弦理論(超弦理論、M理論を含む)の目指すところである。"
        ]
    ]
    };
}
};
</script>

機能の概要

templateの部分

ダミーテキストを生成するボタンを表示します。実際のページでは、トップ部分と下位の部分にそれぞれボタンを1つずつ配置しています。これ、見た目は違いますが中身は同一のコンポーネントを指しています。Vue.jsが言うところのコンポーネントの再利用をしていると言っていいのでは。

ちなみに、ボタンとアイコンは、任意のものに変更できるよう実装しています。趣味です_(:3」∠)_

pic

pic

ちなみに、アイコンを指定している部分はv-bindしているのですが、省略記法を利用していません。「v-onの省略記法@を使っているのになんで?」ってところなんですが、なんとなく「:だけってのがしっくりこない」ってだけの理由です_(:3」∠)_

scriptの部分

JavaScriptにまだそんなに慣れてないせいで、「こんな書き方しないよ!」っていう書き方をしている可能性が微レ存どころの騒ぎじゃないので、おかしかったらご指摘いただければ幸いです。

基本的にやっていること

やっていることは至極単純で「ひたすら文字列操作」です。

自分でやっておいて何なんですが、オプションを複数選択可能にしたため「改行しつつPタグ付与」という場合と「単純にPタグ付与」する場合で、ちょっと条件分岐して処理内容を変える必要があったのは面倒くさかったです。まぁ、こういうのってやってみないと分からないので、いい経験になりました。

生成したテキストの処理

生成したテキストの処遇ですが、当初から考えていたように「クリップボードにコピーする」方法を採用しました。execCommandMDNを参考にしながら実装しました。なお、これのせいで古いInternet Explorerでは動作しません。まぁ、そんなに困ることもないだろうと思ってそのままです。

テキスト生成後

ボタンを押して何の反応もないと正常に動作したのかわかりませんし、何よりつまらないです。なので、Buefyのドキュメントを見て気になっていたToastを実装してみました。要は「正常に処理できたよ!」あるいは「できなかったよ!」というレスポンスを出力できるようにしました。「できなかったよ!」とエラーが返るのはqueryCommandSupported('copy')でコマンドの実行ができないと判断された場合です。

その他

テキストの元ネタをべた書きしているので、ちょっと不格好ですがあまり気にしても仕方ないかなーと思ってそのままです。

なお、「句点まで生成する」を選択した際に出力文字数が少なすぎると、「クリップボードにコピーしたよ!」というメッセージが出力されるにもかかわらず、文章がコピーできない事象を確認しています。抽出元テキストの文章はものによって冒頭の1文がとても長いため、1文が終わる(始めての句点が来る)前に指定された出力文字数へ到達してしまうのが原因です。

そのうち直そうと思います。ええ、そのうちに_(:3」∠)_

デプロイ先の選定

作成したアプリケーションをデプロイします。デプロイする先の候補としては下記のところがあげられると思います。

他にもたくさんありますが、だいたいこのあたりだと思います。今回はGitHub Pagesにデプロイします。手順はここを参考にしました。

デプロイ

vue.config.jsの準備

GitHub Pagesにデプロイする場合で、デプロイ先のURLがhttps://<ユーザー名>.github.io/<リポジトリ名>/である場合はvue.config.jsというファイルが必要です。もともと存在するファイルではないので、新規で作成する必要があります。内容はこんな感じ。

module.exports = {
    publicPath: process.env.NODE_ENV === 'production'
      ? '/リポジトリ名/'
      : '/'
  }

なお、デプロイ先のURLがhttps://<ユーザー名>.github.io/のタイプはこの作業を行わなくてOKです。

ビルドする

コンソールにて下記のコマンドを実行します。

npm run build

しばらく待っているとdistフォルダ中にファイルが生成されます。なお、このファイルをローカルで開いても、真っ白なページが表示されるだけで何も動作しません。これはnpm run buildで生成されたファイル達が「デプロイされた先で動作する」仕様にチューニングされているからです。なので、ローカルでは動作しないのですね。

GitHubにプッシュする

先にGitHubにてリポジトリを作成しておきます。なお、ここで作成するリポジトリ名は、vue.config.jsに記述したリポジトリ名と同一の名前でないといけません。

ここでとあるシェルスクリプトを作成します。こんな感じ。作成するフォルダはプロジェクトフォルダ直下です。作成したらおもむろに実行します。

#!/usr/bin/env sh

set -e

npm run build

cd dist

git init
git add -A
git commit -m 'deploy'

git push -f git@github.com:ユーザー名/リポジトリ名.git master:gh-pages

cd -

最後のgit pushでデプロイ先をgh-pagesブランチにしてるのがミソですね。というのは、リポジトリを作成してgh-pagesブランチにプッシュするだけで、あとは勝手にGitHubがサイトのソース設定を自動的に有効へ設定してくれます。なので、「プッシュしてからリポジトリのSettingを変更する」という手間が省けるわけです。あとはURLにアクセスすればもう動作します。簡単!

なお、アップロードしてから実際のページに反映されるまでは即時反映とは行かず、微妙に時間がかかります。とは言っても数十秒、遅くても数分ですが。

もちろん、masterブランチの/docsフォルダにファイルを配置して公開する、という方法もあります。こちらはリポジトリのSettingsでサイトのソース設定を変更する必要があるので注意。

おわりに

これでVue.jsを利用した簡単なアプリケーションの実装ができました。今回は外部APIなどは利用しませんでしたが、axiosを利用すればAPIからデータを取得し利用できます。次は外部APIの利用を含めて、もうちょっと機能や見た目について向上したアプリケーションを実装したいものです。

頑張ります。_(:3」∠)_

comments powered by Disqus