20190820のvue.jsに関する記事は12件です。

SEIYU風のECサイトを作りましょう(2)Xadminを使って管理画面を一新します

前回の記事

SEIYU風のECサイトを作りましょう(1)要求分析とプロジェクト初期化

Xadminとは何か

GitHub上に公開されてるDjangoの管理画面の上位互換パッケージ。
キャプチャ.PNG

管理画面本来の機能を拡張し、見た目も良くします。
https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_320164_3cdce46d-cb8b-9fc9-3b6d-c38e8e618403.jpg
前回の記事で完成した管理画面を、Xadmin使用してリメイクすれば、以下のようになります。
キ21ャプチャ.PNG
また、スタイルの切り替えも可能ですし、データのexcel出力などもデフォルト機能としてはいってます。
キャプチャ.PNG

では、早速作っていきます。

Xadminダウンロード

githubのリポジトリはこちらになります。xadmin

  • ブランチはdjango2を選んでダウンロードしてください
  • あるいは本記事のリポジトリextra_appsからxadminを抜いて使ってください

本記事のリポジトリから抜いて使うことがオススメです:point_up:

注意事項
pip install xadminは絶対しないことです、古いバージョンのxadminをおとすことで、パッケージ依存がややこしいことになります。

依存パッケージインストール

以下コマンドを実行し、xadminに必要なパッケージをインストールします。

公式参考リンク

pip install django-crispy-forms
pip install django-import-export
pip install django-reversion
pip install django-formtools
pip install future
pip install httplib2
pip install six

ファイル配置

appsと同じディレクトリで、extra_appsという名のフォルダを作ります。
extra_apps配下にダウンロードしたxadminを置きます。
完成後のディレクトリ構成は以下になります。

qiita-Django-supermarket
|-- requirements.txt
|-- README.md
|-- LICENSE
|-- CHANGELOG.md
|-- api
|-- |--api
|-- |-- |-- __init__.py
|-- |-- |-- settings.py
|-- |-- |-- urls.py
|-- |-- |-- wsgi.py
|-- |--apps
|-- |-- |-- goods
|-- |-- |-- users
|-- |--extra_apps
|-- |-- |-- xadmin
|-- |--manage.py

その後settings.pyに以下の内容を追加します。

settings.py
...
sys.path.insert(0, os.path.join(BASE_DIR, 'extra_apps'))
...

INSTALLED_APPS = [
    ...
    'crispy_forms',
    'xadmin',
]

その後extra_appsappsと同じようにsouruces Rootに指定します。
PyCharmの操作方法は
1.extra_appsを右クリック
2. Mark Directory as
3. souruces Root

usersとgoods appにadminx.pyファイルを作る

usersアプリに移動して、adminx.pyファイルを作ります

cd apps/users
vim adminx.py
apps/users/adminx.py
import xadmin
from xadmin import views
from .models import VerifyCode


class BaseSetting(object):
    enable_themes = True
    use_bootswatch = True


class GlobalSettings(object):
    site_title = "ネットスーパー"
    site_footer = "supermarket_Back"


class VerifyCodeAdmin(object):
    list_display = ['code', 'mobile', "add_time"]


xadmin.site.register(VerifyCode, VerifyCodeAdmin)
xadmin.site.register(views.BaseAdminView, BaseSetting)
xadmin.site.register(views.CommAdminView, GlobalSettings)

その後goodsファイルに移動し、adminx.pyを作ります

cd ../goods
vim adminx.py
apps/goods/adminx.py
import xadmin
from .models import Goods, GoodsCategory, GoodsImage, GoodsCategoryBrand, Banner, HotSearchWords
from .models import IndexAd

class GoodsAdmin(object):
    list_display = ["name", "click_num", "sold_num", "fav_num", "goods_num", "market_price",
                    "shop_price", "goods_brief", "is_new", "is_hot", "add_time"]
    search_fields = ['name', ]
    list_editable = ["is_hot", ]
    list_filter = ["name", "click_num", "sold_num", "fav_num", "goods_num", "market_price",
                   "shop_price", "is_new", "is_hot", "add_time", "category__name"]

    class GoodsImagesInline(object):
        model = GoodsImage
        exclude = ["add_time"]
        extra = 1
        style = 'tab'

    inlines = [GoodsImagesInline]


class GoodsCategoryAdmin(object):
    list_display = ["name", "category_type", "parent_category", "add_time"]
    list_filter = ["category_type", "parent_category", "name"]
    search_fields = ['name', ]


class GoodsBrandAdmin(object):
    list_display = ["category", "image", "name", "desc"]

    def get_context(self):
        context = super(GoodsBrandAdmin, self).get_context()
        if 'form' in context:
            context['form'].fields['category'].queryset = GoodsCategory.objects.filter(category_type=1)
        return context


class BannerGoodsAdmin(object):
    list_display = ["goods", "image", "index"]


class HotSearchAdmin(object):
    list_display = ["keywords", "index", "add_time"]


class IndexAdAdmin(object):
    list_display = ["category", "goods"]


xadmin.site.register(Goods, GoodsAdmin)
xadmin.site.register(GoodsCategory, GoodsCategoryAdmin)
xadmin.site.register(Banner, BannerGoodsAdmin)
xadmin.site.register(GoodsCategoryBrand, GoodsBrandAdmin)

xadmin.site.register(HotSearchWords, HotSearchAdmin)
xadmin.site.register(IndexAd, IndexAdAdmin)

urls.pyにxadmin用のpathを追加

api/urls.py
import xadmin

urlpatterns = [
    ...
    path('xadmin/', xadmin.site.urls),
    ...
]

最後にマイグレートを実行します。
xadmin内部にmigrateファイルがあるため、それを実行します。

python manage.py makemigrations 
python manage.py migrate

ここまで来れば

python manage.py runserver

サーバーを立ち上げてhttp://127.0.0.1:8000/xadminにアクセスすると、xadminにアクセスできるようになってるはずです。

補足

アプリの表示名を修正したい場合。
キャプチャ.PNG

下記の二つの手順を実行してください。

apps/goods/apps.py
from django.apps import AppConfig


class GoodsConfig(AppConfig):
    name = 'goods'
    verbose_name = "商品だよ"

api/setting.py
INSTALLED_APPS = [
...
 'goods.apps.GoodsConfig'
...
]

修正後、以下のようになります。
キャプチャ.PNG

次回予告

思ったよりxadminを紹介するのに時間がかかりました:frowning2:
次回は一気にAPIを作って行きたいと思います。
そしていよいよVue.jsのセットアップです。

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

Vue.js初心者お勉強-その1-

前回までのおさらい

firebase で テンプレ環境は展開できた。この後は、firebaseでさくっとvue.jsで作成したフロントエンドアプリを公開したいが、その前に少しくらいvue.jsをカスタマイズできるようになっておくべきと思ったので、vue.jsを勉強。なお、vue.jsを勉強した後のfirebase + vue.jsでググると山ほど記事が出てくるので、写経すればそんなに難しくない、と予想します。

触ってみる

こちらの記事が簡単そうだったので、やってみます。

vue-cliのインストール

vue-cliは雛形からプロジェクトを作成してくれる公式ツールです。よくある対話型で答えていけばうまい事やってくれるやつ。npm initとか、firebase initとかみたいな。

npm install -g vue-cli

vue-cliによるvueプロジェクト作成

以下のコマンドでプロジェクトを作成します。

vue init [テンプレート名] [プロジェクト名]

テンプレート名は写経してwebpackを指定。他にもいろいろあって、メリットもさまざまなのでしょうか…?

PS C:\first_vuejs> vue init webpack first_vuejs

? Project name first_vuejs
? Project description A Vue.js project
? Author teraco
? Vue build standalone
? Install vue-router? Yes
? Use ESLint to lint your code? Yes
? Pick an ESLint preset Airbnb
? Set up unit tests Yes
? Pick a test runner karma
? Setup e2e tests with Nightwatch? Yes
? Should we run `npm install` for you after the project has been created? (recommended) npm

などと入力するとガリガリとプロジェクトを作成していき

※ 最近のvueでは、vue create [プロジェクト名]でプロジェクトを作成するのが流行りの様子。

To get started:

  cd first_vuejs
  npm run dev

と指示されるので実行。

> webpack-dev-server --inline --progress --config build/webpack.dev.conf.js

 13% building modules 30/33 modules 3 active ...t_vuejs\src\components\HelloWorld.vue{ parser: "babylon" } is deprecated; we now treat it as { parser: "babel" }.
 95% emitting

 DONE  Compiled successfully in 16857ms                                                                         22:54:14

 I  Your application is running here: http://localhost:8080

めでたくHello worldに成功したようです。

image.png

さて、中身を見てみるとindex.htmlが不思議…。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>first_vuejs</title>
  </head>
  <body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

index.htmlからjsなどへのリンクが張っていないが、Webpackを使って統合されるらしいです。
Vue.js初心者向け:インラインテンプレートから単一ファイルコンポーネントの使い方まで - Qiita

それではチュートリアル通りに中身をいじっていきます。

タイトル変更 & vueプロジェクトの構成

app.vue
<template>
  <div id="app">
    <img src="./assets/logo.png">
    <h1>Todo Management.</h1>
    <hr />
    <router-view/>
  </div>
</template>

index.htmlから呼ばれるapp.vueを編集し、H1を追加します。これで全てのページに反映されます。…とこの後のステップを言葉で書くのがつらいので、図にしてみました。
image.png

ちょいと拡大してみてほしいのですが、

  1. app.vueから個別ページ用の表示をスイッチする"view"機能を呼び出し。これをvueでは"ルーター"と呼ぶ。
  2. "ルーター"の実体はsrc/router/index.js。こいつで、どのパスにアクセスしたら、どのページを表示するかを決定する。ここでは "/" にアクセスしたら、HelloWorld.vueを表示、/hogeにアクセスしたらhoge.vueを表示としている。
  3. あとは、HelloWorld.vueとかhoge.vueを書けば、個別ページが書き変わる。

ここまでがvue-cliで始めるVue.jsチュートリアル (1) - Qiitaの内容です。


以下、vue-cliで始めるVue.jsチュートリアル (2) - Qiitaの内容。これも写経なのだが、画像で説明。

前述のHelloWorld.vueが個別ページの表示を決定するファイルだった。これを編集してチュートリアルを続ける。このxxxx.vueはの間にページの実体を書いていく形式なので、チュートリアル(1)で作成したの中身を削除し、チュートリアル(2)を進める。

リストの表示(ループ)

前述した通り、間にHTML(ぽいもの)を書けばページが表示できるがそれだと拡張性がないので、v-forを使って表示する。

image.png

v-forは公式ページでリストレンダリング — Vue.jsとして説明されているディレクティブである。ディレクティブとはテンプレート構文 — Vue.jsで説明されている通り、Vue.jsの世界でv-から始まる特別な属性、と理解すればよいみたい。

Todoの追加

これもVue.jsに用意されているディレクティブを利用してやってみる。

image.png

中のv-on:clickされたらaddTodo()メソッドを実行し、addTodoメソッドの内容は下部の赤枠に書いてある。内容はnewTodoに値を追加する事。

Todoの削除

Todoの削除とやっている事はあまり変わらず。v-onディレクティブを利用して、メソッドを呼び出して動作させる。

image.png

v-onの文字がなくなって@になっているのは、vue.jsの省略記法である。詳しくは以下ページを参照。

テンプレート構文 — Vue.js

Todoの編集 / 完了済みTodoへのclass付与

前者はv-ifディレクティブ、v-elseディレクティブを使って実装。後者はv-bindディレクティブを使って実装。画像省略。

GitHub Pagesでの配信

チュートリアルの通りにやったらできました。GitHub Pages初めて使ったけど、こりゃ簡単でいいですね。

以上です。


今後の勉強予定メモ

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

Svelteのコンポーネントを理解したくて『シュバババッ!』コンポーネントを作ってみた

こんにちは。ちょっと自分でもどこを目指しているのかわからない 1 あずきしろもち(@azukisiromochi) です。

今回は Svelte のコンポーネントの話。

前フリ

※ループさせていないので、右下の [Rerun] ボタンを押して試してみてください

See the Pen Syubababa!!! by あずきしろもち (@azukisiromochi) on CodePen.

CSSアニメーションを勉強しようと思ってこういうやつ作ったんです。

シュバババッ! 』って感じのやつ。
(あとで気づいたけど、右スクロールするとオブジェクト見えてるなw)

いま、ポートフォリオサイトを作成していて、モーダルをオープンするときのアニメーションに、この『 シュバババッ! 』を使ってみたいなーと思ったわけです。

どんなコンポーネントにするのか

そもそもコンポーネントって?

Svelte の公式リファレンスにはコンポーネントの定義が記載されていません(たぶん)。
SvelteVue.js に非常に近い構文ということもあって、そちらの定義を確認してみましょう。

小さく、自己完結的で、(多くの場合)再利用可能なインスタンスをコンポーネントという

キーワードは

  • 小さい
  • 自己完結的
  • 再利用可能

みたいですね。

『シュバババッ!』はどんなコンポーネントになるのか?

先程の CodePen のコードでは

<div class="syubababa a"></div>
<div class="syubababa b"></div>
<div class="syubababa c"></div>
<div class="syubababa d"></div>
<div class="syubababa e"></div>

こんな感じに <div> 要素が並んでいました。

つまり、5つの『 シュバッ 』が順々にアニメーションしているんです。
コンポーネントのキーワードにあった 再利用可能 に、非常によく当てはまると思いませんか?

ということで、この1つの『 シュバッ 』を Svelte のコンポーネントとして定義して進めてみましょう!

コンポーネントの仕様を明確にしよう!

コンポーネントは、1つの『 シュバッ 』の単位にすることは決めました。
ですが、ただこの『 シュバッ 』を並べるだけでは面白くありません。

シュバッ 』の 高さ を変えたり、 速度 を変えたり、 動き出すタイミング を変えたり……
このあたり(仕様)を明確にしていきます。

<div class="syubababa a"></div>
<div class="syubababa b"></div>
<div class="syubababa c"></div>
<div class="syubababa d"></div>
<div class="syubababa e"></div>

もう一度、このコードに戻ります。
それぞれの <div> に共通の .syubababa クラスと、個々の .a .b .c .d .e クラスがあります。

コンポーネント化に置き換えると、 .syubababa クラスがコンポーネントの共通仕様で、 .a .b .c .d .e クラスが動的に変動させることのできる要素になるでしょう。

例えば .a の場合、

.a {
  animation-duration: 800ms;
  animation-delay: 700ms;
}

と設定されていますし、 .d の場合、

.d {
  animation-delay: 1400ms;
  height: 10vh;
}

と設定されています。

コンポーネントの変動的な要素としては、次のようになりそうです。

animation-duration 仕様
animation-duration アニメーション一回分の時間の長さ ≒ 速度
animation-delay アニメーションの開始タイミング
height シュバッ 』の高さ

コンポーネントの単位では、

// Set style `animation-duration` (default: 800ms)
let duration = '';
// Set style `animation-delay` (default: 0)
let delay = '';
// Set style `height` (default: 15vh)
let height = '';

この3種類のプロパティーを扱って『 シュバッ 』を変動させていこうと思います。

コンポーネントを実装していく

ここまでで『 シュバババッ! 』は、『 シュバッ 』コンポーネントをいくつか並べて実現することを決めました。
また、『 シュバッ 』コンポーネントは、 duration delay height をプロパティに持ち、それぞれの『 シュバッ 』に個性を持たせることも決めました。

それでは順を追って Svelte のコンポーネントを実装していきましょう!

コンポーネントのファイルを作成する

components/Syubababa.svelte
<script>
  // JavaScript 書くところ
</script>

<style>
  /* CSS 書くところ */
</style>

<!-- html 書くところ -->
<div/>

.svelte ファイルの構造はこのようになっています。
Vue.js と非常によく似ていますが、 <template> タグはなく直接 html タグをコーディングする形になります。

コンポーネントの仕様により、実装する順序はまちまちですが、今回は『 シュバババッ! 』を作成した順序を一例として紹介したいと思います。

スタイルを適用させる

components/Syubababa.svelte
<script>
  // JavaScript 書くところ
</script>

<style lang="scss">
$color1: #e1315b;
$color2: #f47d4a;
$color3: #ffff42;
$color4: #00cffa;

div {
  background: $color1;
  height: 15vh;
  width: 10vw;
  margin-left: -15vw;
    animation-name: fadeIn;
  animation-duration: 800ms;
  animation-timing-function: ease;
  // animation-iteration-count: 1;
  animation-fill-mode:forwards;
}

@keyframes fadeIn {
  15% { background: $color1; }
    30% { background: $color2; }
    45% { background: $color3; }
  50% {
    transform: scaleX(15) skew(-30deg);
    transform-origin: 0% 50%;
  }
  75% { border-radius: 30% 20% / 40% 15%; }
    80% { background: $color4; }
  90% { box-shadow: -20px 10px 10px 10px rgba(0, 0, 0, .8); }
  100% {
    transform: translatex(120vw) scaleX(2) skew(0deg);
    transform-origin: 0% 50%;
    margin-left: 0;
  }
}
</style>

<!-- html 書くところ -->
<div/>

CodePen の実装を参考に CSS を設定しました。

SCSS を使ってしまっていますが、色を調整するための変数としてしか利用していません。
SvelteSapper で SCSS を使いたい人がいましたら こちら をどうぞ)

この状態でも『 シュバッ 』はコンポーネントとして動作します。

index.svelte
<script>
import Syubababa from '../components/Syubababa.svelte';
</script>

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

<Syubababa/>

index.svelte<script> タグでコンポーネントを import し、タグとして <Syubababa/> をコーディングすると……
syuba.gif
と、個性のない(変動的な要素のない)『 シュバッ 』コンポーネントが出来上がりました!

続いて、個性を出していきましょう!

コンポーネントにプロパティを持たせる

components/Syubababa.svelte
<script>
  // Set style `animation-duration` (default: 800ms)
    export let duration = '';
  // Set style `animation-delay` (default: 0)
  export let delay = '';
  // Set style `height` (default: 15vh)
  export let height = '';
</script>

<script> タグだけ切り出しましたが、仕様のところで決めた3つの変動要素(変数)を export させています。
これで親コンポーネントから『 シュバッ 』コンポーネントにプロパティを設定し、個性を出すことができるようになります。

変数を export させたのはいいですが、実際の html に変動要素のスタイルを適用する必要があります。
今度は html 部分を見ていきましょう。

components/Syubababa.svelte
<div style="animation-duration: {duration}; animation-delay: {delay}; height: {height}"/>

<div> タグの style 要素に animation-duration animation-delay height を設定し、それぞれに対して、 <script> タグで宣言した変数を設定しています。
Vue.js の場合は {{ hoge }} でバインドしますが、 Svelte の場合は { hoge } でバインドします。

それでは、親コンポーネント( index.svelte )側からプロパティを設定してみましょう。

index.svelte
<script>
import Syubababa from '../components/Syubababa.svelte';
</script>

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

<Syubababa duration={"800ms"} delay={"300ms"} height={"50vh"} />

<Syubababa/> コンポーネントの3つのプロパティを設定すると……
syuba2.gif
個性が出ましたね!

シュバババッ!

ここまでくれば、あとはコンポーネントを並べて『 シュバババッ! 』しましょう!

index.svelte
<script>
import Syubababa from '../components/Syubababa.svelte';
</script>

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

<Syubababa duration={"800ms"} delay={"700ms"} />
<Syubababa duration={"700ms"} delay={"1000ms"} height={"30vh"} />
<Syubababa duration={"600ms"} delay={"1200ms"} height={"20vh"} />
<Syubababa delay={"1400ms"} height={"10vh"} />
<Syubababa delay={"1500ms"} height={"25vh"} />

syubababa!!!.gif

まとめ

Svelte のコンポーネントは Vue.js のものと非常に似ていて、 Vue.js を触ったことのある人ならすんなり使えるのではないでしょうか。
今回は紹介していませんが、 Svelte にも <slot> が用意されており、より自由度の高いコンポーネントが作れると思います。

両方とも使っている身としては、 Svelte の早さは魅力的で もっと流行ってほしいな― と思います。

みなさんも興味を持ったなら、ぜひ使ってみてくださいね :yum:


それではお別れは作成途中のポートフォリオサイトのモーダルアニメーションで~ :wave:

modal.gif


  1. 色の勉強をしてみたり、コード書いたり、絵を描いたり、小説を書いたり……。絶賛迷走中。 

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

Vuetifyのv-btnをHTMLのbuttonタグに置き換えた時にハマったこと

何がおきたか

とあるプロジェクトで、Vue.jsのVuetifyを使っていたのだが
諸般の事情により、Vuetifyを使わなくなったので、途中までVuetifyで組んでいたものを
通常のHTMLタグに戻す、という作業をしていた時にちょっとハマったこと。

<v-form method="get" action="">

(いろいろなForm入力要素がここに入っている)

<v-btn @click="doSend">
送信する
</v-btn>
</v-form>

(doSend はこのボタンを押した後に処理されるメソッド)

これを、以下のように書き換えた。

<form method="get" action="">

(いろいろなForm入力要素がここに入っている)

<button @click="doSend">
送信する
</button>
</form>

すると、 doSend の処理が走らなくなってしまった。

原因と修正方法

Vuetifyの v-btn 要素は、実際には以下のようにレンダリングされる。

<button data-v-******* type="button">
送信する
</button>

type="button" が付与されるのがポイント。

一方、HTML要素の <button> タグは、type属性を指定しなかった場合、デフォルトは submit になる。

そのため、先程 <button> タグに置き換えた後、doSendメソッドが動作しなかったのは
clickイベントよりもsubmitイベントが優先されたため、
formaction要素に指定したURL(上の例では空文字だが、自分自身)へ遷移してしまったためだった。

修正としては、<button> タグに type="button" を指定すればよい。
あるいは、そもそもこの場合、JavaScriptで処理させてしまう(画面遷移させない)作りだったので
formのactionが作動しないよう、javascript:void(0)を指定する方法でもよいはず(こちらの方法は採用しなかった)。

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

Vue.jsのvuetifyコンポーネント「v-file-input」で、ファイル名を取得する方法

初投稿となる新米Webエンジニア(30代ですが汗)なので、初歩的な内容ですがご了承ください。

Vue.jsのvuetifyテンプレートを使ってアプリ開発に挑戦しているのですが、その際に「ファイルの選択→アップロード」という作業が必要になり、input要素で選択したファイル名を取得する必要がありました。

vuetifyコンポーネントの[v-file-input]を使ったファイル名取得がわからなかったので、備忘録として残しておきます。

vuetifyコンポーネント「v-file-input」で、ファイル名を取得する方法

やり方はすごく簡単でした。

template側

<v-file-input
  @change="getFileName"
  accept="image/*"
  label="File input"
></v-file-input>
method側

   getFileName(e) {
      console.log("fileが選択されました。");
      console.log(e.name);      
    },

この際、(e.name)の部分を(e.size)などと指定したりして、いろいろな情報の取得ができます。VisualStudioCodeで「e.」と入力すると、自動で候補がでてきてくれるのでありがたい。
(※Vueプラグイン入れたからかもしれません)

vuetifyを使った場合、この方法で選択したファイル名を取得するのが簡単そうです。

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

Nuxt.jsでウェブストレージを扱う

はじめに

ウェブストレージ便利ですよね、特にローカルストレージはCookieが使えないPWAや、DBを用意するほどでもないちょっとしたデータを保存したいときに使うことが多いのではないかと思います。

ただ、いざウェブストレージのデータを扱おうと実装を進めるとデータの同期や保存のタイミング等でつっかかることがままあります。
そこで今回はNuxt.jsでウェブストレージを簡単に扱う手法を考えましたので簡単にご紹介しようと思います。

実装方針

コンポーネントやページから直接ウェブストレージにアクセスしていると処理やデータが散乱してしまい辛い状況に陥りやすいです。
そこで、ウェブストレージの参照や処理は全てストアに一本化します。
各コンポーネントやページは共通のステートやアクションを参照することになりますのでデータが非常に扱いやすくなります。

実装

入力した値をウェブストレージに保存する簡単なデモを作成します。
今回はローカルストレージを使用しますがセッションストレージでも同様の方法で対応可能です。

ウェブストレージ操作用のクラスを作成

1つのウェブストレージを扱うだけならクラスを作るまでもないのですが、今回は複数のウェブストレージを管理することを前提に作成しておきます。
実装例ですので登録・削除する機能だけ用意してあります。

lib/storage.js
export default class {
  constructor(key) {
    this.key = key
    this.data = []
  }
  init() {
    this.data = JSON.parse(localStorage.getItem(this.key)) || []
  }
  save() {
    localStorage.setItem(this.key, JSON.stringify(this.data))
  }
  regist(payload) {
    this.data.push(payload)
    this.save()
  }
  remove(payload) {
    this.data = this.data.filter(el => el !== payload)
    this.save()
  }
}

クライアント起動時のプラグイン作成

クライアント起動時にウェブストレージのデータをストアへ注入する必要があるのですが、Nuxtのストアにはクライアント起動時の関数は用意されていませんのでその機能を作成します。
Nuxtのデフォルトにあるアクション「nuxtServerInit」にならって「nuxtClientInit」という名称で管理します。

plugins/nuxt-client-init.js
export default ({ store }) => {
  for (const key in store._actions) {
    if (key.match(/nuxtClientInit/)) {
      store.dispatch(key)
    }
  }
}
nuxt.config.js
plugins: [
  { src: '~/plugins/nuxt-client-init', mode: 'client' }
]

nuxt-client-init-moduleというモジュールが既にあるのですが、ルートモジュールでのみ機能するようです。
名前空間付きモジュールで「nuxtClientInit」を利用したい場合は上記のようなプラグインを作成してください。

ストア実装

ストアのアクションはあくまでインスタンスの関数を実行し、その結果をステートへ返すだけのシンプルな設計とします。
アクションに直接ウェブストレージ周りの機能を実装してもよいのですが、複数ウェブストレージを扱ったり機能が複雑になってくると煩雑になりやすいので、ウェブストレージの処理とストアの処理を明確に分担するように設計しています。

store/message.js
import LocalStorage from '~/lib/storage'

const storageMessage = new LocalStorage('message')

export const state = () => ({
  data: []
})

export const mutations = {
  dataUpdate(state, payload) {
    state.data = [...payload]
  }
}

export const actions = {
  nuxtClientInit({ commit }) {
    storageMessage.init()
    commit('dataUpdate', storageMessage.data)
  },
  regist({ commit }, payload) {
    storageMessage.regist(payload)
    commit('dataUpdate', storageMessage.data)
  },
  remove({ commit }, payload) {
    storageMessage.remove(payload)
    commit('dataUpdate', storageMessage.data)
  }
}

完成

あとは通常のストアと同様に利用できます。

pages/index.vue
<template>
  <div>
    <form @submit.prevent="regist()">
      <input type="text" v-model="message">
      <button type="button" @click="regist()">
        登録
      </button>
    </form>
    <ul>
      <li v-for="(item, index) in messageData" :key="index">
        {{ item }}
     <button type="button" @click="remove(item)">
            削除
         </button>
      </li>
    </ul>
  </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
  data() {
    return {
      message: ''
    }
  },
  computed: {
    ...mapState({
      messageData: state => state.message.data
    })
  },
  methods: {
    regist() {
      if (this.message) {
        this.$store.dispatch('message/regist', this.message)
        this.message = ''
      }
    },
    remove(payload) {
      if (window.confirm('削除しますか?')) {
        this.$store.dispatch('message/remove', payload)
      }
    }
  }
}
</script>

おまけ(Vueインスタンス版)

小規模でストア用意するほどでもないよって人向けにプラグインでVueインスタンスへ注入する方法も記載しておきます。
以下のデモでは上記で使ったクラスを流用してください。

プラグインを作成する

Vueインスタンスへ注入するプラグインファイルを作成します。

plugins/storage.js
import Vue from 'vue'
import LocalStorage from '~/lib/storage'

Vue.prototype.$storageMessage = new LocalStorage('message')
Vue.prototype.$storageMessage.init()
nuxt.config.js
  plugins: [
    { src: '~/plugins/storage', mode: 'client' }
  ]

完成

設定はこれだけで後はページやコンポーネントからウェブストレージのデータを操作できます。

pages/index.vue
<template>
  <div>
    <form @submit.prevent="regist()">
      <input type="text" v-model="message">
      <button type="button" @click="regist()">
        登録
      </button>
    </form>
    <ul>
      <li v-for="(item, index) in messageData" :key="index">
        {{ item }}
     <button type="button" @click="remove(item)">
            削除
         </button>
      </li>
    </ul>
  </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
  data() {
    return {
      messageData: [],
      message: ''
    }
  },
  mounted() {
    this.update()
  },
  methods: {
    update() {
      this.messageData = this.$storageMessage.data
    },
    regist() {
      if (this.message) {
        this.$storageMessage.regist(this.message)
        this.update()
        this.message = ''
      }
    },
    remove(payload) {
      if (window.confirm('削除しますか?')) {
        this.$storageMessage.remove(payload)
        this.update()
      }
    }
  }
}
</script>

さいごに

ウェブストレージのデータを更新するときに「ウェブストレージにあるデータ」と「ウェブストレージから引っ張ってきたデータ」と2つを管理する必要があるのでそこがもたつきやすいですよね。
今回のように「ウェブストレージ←→ストア←→ページ・コンポーネント」という形で一方向の処理で管理すると、土台さえ作ってしまえばウェブストレージ周りの処理を意識せずに扱えるのでオススメです。
大した手法ではないのですがウェブストレージの実装で手をこまねいている方は参考にしていただければ幸いです!

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

【Vue.js】 Google SpreadSheet をデータベースの代わりに使う。

はじめに

JS、Vue.js ともに初学者です。
間違いもあるかと思いますので、指摘していただけたら幸いです。

環境

今回グーグルアカウントはG Suiteを利用しています。
なのでGAS,Spreadsheetともに公開範囲は社内のみとなっていますが、一般のアカウントでもOKです。

また、Vue CLI3を使ってプロジェクトを作成しています。

方法

  1. SpreadsheetをJSON形式に変換し、Web APIとして公開する
  2. VueでそのAPIを叩く
  3. おしまい

問題

素直にJSONを取得しようとすると、CORSエラーが発生します。

CORS(Cross-Origin Resource Sharing)とは?

ブラウザがオリジン(HTMLを読み込んだサーバのこと)以外のサーバからデータを取得する仕組みです。
デフォルトでは、自分自身以外のドメインからのデータ取得を拒否する設定になっているようです。

→簡単に言うと、認証されてないドメインからデータ取得させないようにする仕様です。

サーバから送信されるレスポンスヘッダに
access-control-allow-origin:*
が付与されている場合、すべてのドメインにデータ取得を許可できます。

GASもCORSに対応しており、
レスポンスヘッダにも、access-control-allow-origin:*が付与されます。
なのに何故CORSエラーが発生するのでしょうか?
調べたところ、うまく行かないパターンがあるようです。

うまく行かないパターン
・JSONテキストをまるごと POST するような場合は エラーとなる
 →なぜなら、JSONテキストの content-typeが application/json となるため。
 →なぜ application/json だとエラーとなるかというと、 application/jsonの場合は preflightリクエストが発生するため
 →なぜなら、Google Apps Script は OPTION メソッドをサポートしていないため。

▲引用 - 今から10分ではじめる Google Apps Script(GAS) で Web API公開 - Qiita

正直理解しきれていないですが、どうやらJSONをそのままPOSTする場合はダメみたいです。
サーバ側の処理をいじることはできないので、クライアント側で対処します。

クライアント側の対処方法はいくつかあります。

  1. ブラウザのセキュリティをOFFにする。
  2. JSONPを使う。

ブラウザのセキュリティをOFFにする場合

参考 : クロスドメイン制約を回避するChromeショートカットを作る - Qiita
Chromeの起動オプションでセキュリティを切ります。

"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --disable-web-security --user-data-dir="C://Chrome dev session

手軽にできるので開発中には良いのですが、他人に成果物を共有するとき、
※ブラウザのセキュリティを切っています
なんて注意書きを入れなきゃいけません。

JSONPを使う場合

素のaxiosではJSONPが取得できないようなので、vue-jsonpというプラグインを導入します。
JSONPとは、コールバック関数を埋め込み、関数の戻り値としてJSON文字列を受け取る形式です。

※JSONPは情報漏洩の可能性があります。機密情報を取り扱わないようにしましょう。

→今回はGASの公開範囲によって社内限定にデータが守られているので、JSONPを使って実装します。

実装

1.SpreadsheetをJSON形式に変換し、Web APIとして公開する

JSON形式というかJSONP形式ですが、、
以下のGASを作成します。
参考 : Google SpreadSheet のデータを JSON 形式で取得する Web API をサクッと作る - Qiita

function getData(id, sheetName) {
  var sheet = SpreadsheetApp.openById(id).getSheetByName(sheetName);
  var rows = sheet.getDataRange().getValues();
  var keys = rows.splice(0, 1)[0];
  return rows.map(function(row) {
    var obj = {}
    row.map(function(item, index) {
      obj[keys[index]] = item;
    });
    return obj;
  });
}

function doGet() {
  var data = getData('スプレッドシートID', 'シート名');
  return ContentService.createTextOutput("callbackFunction" + '(' + JSON.stringify(data, null, 2) + ')')
  .setMimeType(ContentService.MimeType.JAVASCRIPT);
}

作成したGASを「公開>ウェブアプリケーションとして導入」と進み
アプリケーションにアクセス出来るユーザーを 全員(匿名ユーザーを含む) にして公開します。
末尾に「exec」のついたURLが表示されるので、そのURLを使ってGASにアクセスします。

2. VueでそのAPIを叩く

参考 : 覚え書き Vue.jsでjsonpを使ってみる - Qiita
先にvue-jsonpプラグインを導入しておき、以下のようにAPIを叩きます。
この場合jsonDateという変数にJSON形式のデータが格納されます。

data() {
  return {
    jsonData:'' // JSONを格納するための変数
  }
},
methods: {
  getData (apiUrl) {
    // this.$jsonp(url, dataObj, timeout) で使える。 Vueコンポーネント内だとthisで呼び出せる。
    this.$jsonp("APIのURL",{ callbackName: "callbackFunction" })
    .then(json => {
      // Success.
      this.jsonData = json
    }).catch(err => {
      // Failed.
    })
  }
}

これでSpreadsheetをデータベース代わりに使うことができるようになりました!
もっと良いやり方を知っている人がいらっしゃいましたら御教示頂けると幸いですm(_ _)m

参考一覧

Google SpreadSheet のデータを JSON 形式で取得する Web API をサクッと作る - Qiita
覚え書き Vue.jsでjsonpを使ってみる - Qiita
今から10分ではじめる Google Apps Script(GAS) で Web API公開 - Qiita
JSONPで悩むある程度の人々へ
CORS(Cross-Origin Resource Sharing)によるクロスドメイン通信の傾向と対策 | DevelopersIO

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

誰でもできるオリジナルwebサービスを着実にリリースする手順と考え方

スクリーンショット 2019-08-16 18.36.52.png

夏休みに小さいオリジナルのwebサービスを作ったので、構想からリリースまでに使ったツールなどオリジナルwebサービスをリリースするまでの手順と考え方ご紹介したいと思います:smiley:

技術的なことにはほぼ言及していません。これから初めてオリジナルのwebサービスを作ろうと思っている方にこんな感じでやってるのねって思ってもらえればうれしいです。

いままで作ったサービスはこちらにまとめています(ページからリンクしているそれぞれのサービスのまとめページはgif貼りすぎてめちゃくちゃ重いので開かないの推奨です;;)

作ったもの

名前
しるQ 「自分に気づこう」
https://siruq.site

コンセプト
しるQは毎日変わる問いに答えることで本当の自分を発見するサービスです。140文字にまとめてシェアして他の参加者の価値観と比べてみましょう!

作成期間
22時間くらい

なぜ作ったか

  • 最近自己認知とかコーチングに興味があるのでそのテーマで何か作りたかった
  • vueとfirebase使ってみたかった

お世話になったサービス
Sketch
Zoho show
hue360
adobe color
お名前.com
Firebase Realtime Databse
ためしがき

それでは以下の順に時系列でご紹介していきます!
1. サービス企画
2. サービスの具体化
3. 実装

企画

8/13日(火) 13:00

数ヶ月前からなんとなく以下のような構想が頭の中にありました。

  • cotreeというコーチングのサービス使わせてもらい自己認知って大切だよなーと思ってた
  • 数ヶ月前にQTODAYという家入一真さんの出題した質問にTwitterで答えるようなサービス(調べたらクローズしてた)があっていいなーって思ってた。(あまり情報のこってなかったので参考になりそうなnoteのリンク貼っておきます)
  • 同じ感じで自己認知を高めることをテーマにサービス作ってみようかなー
  • 最近会社でVueの勉強会やったからVueで何かつくってみたいなー

これだったら夏休み中にフロントだけで実装できるんじゃない?やろう!という流れで作るものが決定しました。

サービスのアイディアとか作りたいものは思いついたらtrelloにメモするようにしています。「アイディア」「概念」「面白そうなサービス」になんとなく分けて書いています。(最近はアイディアしか更新してない)

サービスの具体化

以下の手順でやることが多いです。

  1. 紙にUIとか機能書いてみる
  2. スライドにちょっと綺麗目に詳細書く

紙にUIとか機能書いてみる

今回はこんな感じで書いています。上がサービスの名前で下が機能と画面構成です。雑すぎて恥ずかしいw
「done is more than perfect」ってわざわざ紙に書いて自分に言い聞かせてます。これも間違っててただしくは「done is better than perfect」でした笑

スライドに綺麗目に書く

こちらに公開しました。
さすがに上記の雑な構成図で作り始めるのは辛いのと、この段階だと自分の中でもどこまで機能入れるかとか、サービスの目的とか決めきれてないのでスライド作りながら自分の頭の整理をしていきます。

作る目的書いてみたり

UIをちょっと綺麗に作ってみたり

UIデザイナーさんがよく使うらしいSketchというイラストレーターみたいなツールも数千円で買っているのですが全然使い方慣れないので、Zoho show(かgoogleのスライドのやつ)を使って作りました。素人なので背伸びせずにこっちの方が早いし楽です。

デザインはサービスのイメージからなんとなく連想して決めています。今回は

  • マインドフルネス的な感じ
  • 禅?
  • 「マインドフルネス」とか「Zen」で画像検索したら青系が多い

  • 青だとなんか欧米のマインドフルネス色が強い気がする紫にしたらなんか「禅」感でそう。

というふわっとした思考過程で紫になりました。色は以下のサービスを使って選びました。
hue360は既存の色から選ぶだけで楽なのでまず使えそうな色あるか探しています。ここで選んだ色をadobe colorのベースカラーにしていい感じの色のセットを使っています。作成途中にカラーコードコピペしたくなるのでスライドも雑に貼り付けます。

この段階でテーブルの設計とか、データの処理方法とかも考えますが今回は単純なサービスなのでやっていません。

サービス名も決めます

サービス名もこの段階でフィクスするようにしています。実装の前にgithubのレポジトリ作成したり、作業ディレクトリ作ったりする際にサービス名使う機会が多いのであとからサービス名変わるとずれて個人的に気持ちわるいです。
名前はコンセプトなどを書き出してそこから語呂合わせで決めました。決めるときはお名前.comでドメインが取れることを確認しています。一応ググって同じ名前のサービスがないことを確認しています。
「しるQ」は以下の候補から「自分をしるQ」に決めて、そのあとなんか長いなと思って「しるQ」に短縮しました。

自分に気づこう
ちょこっと自己分析
人生の問い
自分Q
自分をしるQ
Tweet自己分析

これで準備完了です!ここまでの作業に3~4時間つかっています。
この時点で早く終わらせないと俺の夏休みが開発だけで終わってしまう! と若干焦る:fearful:

実装

スライド見ながらガシガシ作っていきます。スマホから使うの前提なのでmax-width:600pxで作ります。ヘッダー、フッダー、ボディーのCSSとかはだいたいどのサービスも同じような実装なので、理想のサイトを参考にさせてもらいながら作ります。細かい調整は後でするのでそれっぽくなるように作ります。

最初こんな感じで

Twitterのウィジェット埋め込んだりしたら雰囲気でてきました。

今回はVueのコンポーネントやルーティングを勉強しながら作ったのでかなり時間かかってしまい、この時点で26時になり作業終了です。今日は13時~26時まで休憩除くと10時間くらい作業しました。

8/14日
出かける予定があったので毎日の質問を保存するためのFirebaseのRealtimeDatabseとのつなぎこみだけ実装しました。js側に質問全部保存するのが簡単そうですが、最近バックエンドエンジニア不要論で話題のFirebaseとかRealtime Databse使ってみたかったので連携させました。初NoSQLでした。

作業は3時間くらい

8/15日
しるQは投稿機能もユーザー登録機能も実装せずに、Twitterにおんぶに抱っこのサービスなので、どのようにTwitterに投稿して貰うか考えました。Twitterでシェアされた時に以下のようなOGPが出て毎日変わる質問を表示させたかったのですが、SPAだとプリレンダリングしないと実現できないことに気づきしかたなく実装しました。ここは結構詰まってしまって4時間くらい消費しました。

ところが...実装終わってテストしていると、PCの画面サイズだとOGPに質問文全部表示されていい感じなのですが、スマホでみると説明文カットされてました:upside_down:


スマホからの使用を想定しているのでこれでは意味がない...プリレンダリング無駄に実装してしまいました...たしかにQTODAYは質問ハッシュタグでつけてるんですよね...確認していなかった。大幅な時間ロスです。夏休み...
プリレンダリングでOGPに質問出すのはやめて、普通にハッシュタグに質問出すことにしました。

機能は大枠完成してきたので細かい所の調整をします。
ファビコン作ったり、ロゴを作ったり、サービス用のツイッターアカウント整えたりしました。
ロゴは一応Sketchで作りましたが特に難しいことはしておらず、"Hiragino Mincho Pro"という書体で文字書いて出力しただけです。Sketchの必要はもはやないです。

フォントはためしがきを利用させていただき雰囲気がでるフォントを探しました。今回はいろはまるを使用しています。最終的にはこんな感じに落ち着きました。

最初と比べると多少垢抜けましたかね...?
お気づきの方はいないかと思うのですが、この時点で最初にスライドで決めたカラーコードはガン無視していますw:joy:
クロームの開発者ツールでいい感じになるまで色をぐりぐり変えて調整しました。
image.png

作業は5時間くらい

8/16日
最終日はドメインを取得して設定したり、Firebaseに1ヶ月分の質問を登録したりしました。
siruq.netも取れたし「.net」の方がかっこいい気がするのですが千円くらいするんですよね。siruq.siteは60円だったので安さに負けました。作業は4時間くらい。以上で完成です!!

まとめ

1日でサクッとつくってやるぜ!とか思ってたんですが夏休みの 1/2くらいを吸い取られた開発となりました。随所で手を抜いたのですが(コードはひどいのでのせていない)それでも一つの形にするのは大変ですよね。個人開発は使える時間も限られてることが多いので、いかに最小限の機能で手を抜きつつそれなりの形にするためにライブラリとかツールを活用するの大切だなぁと思いました。

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

Vue基礎:インスタンスライフサイクル

Vue.jsはReactと同じようにライフサイクルがあります。
開発を入る前に第一歩として、インスタンスライフサイクルの理解は重要です。

インスタンスライフサイクル

image.png

赤い枠の文字はメソッド名です。
※図の元: https://jp.vuejs.org/v2/guide/instance.html

コード例

new Vue({
  data: function () {
    return {
      a: 1
    }
  },
  created: function () {
    // `this` は vm インスタンスを指します
    console.log('a is: ' + this.a)
  },
  mounted: function () {
    this.$nextTick(function () {
      // ビュー全体がレンダリングされた後にのみ実行されるコード
    })
  }
})

参考記事:https://jp.vuejs.org/v2/guide/instance.html

以上

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

Vue基礎:コーディングスタイル

コーディング規約ですが、JAVAでは歴史長いし、守ったらメリットが多いです。
特に可読性、保守性はよくなります。

Vue.js、Node.jsなどもeslintなどのツールでコーディングスタイルも統一できるようになりました。

有名な【コーディングの心得5か条】

・見やすさを重視せよ
・ネーミングはわかりやすく
・サンプルを鵜呑みにしない
・同じコードを二度書かない
・役割は一つに

JAVAのコーディング規約

Javaコーディング規約
https://future-architect.github.io/coding-standards/documents/forJava/Java%E3%82%B3%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0%E8%A6%8F%E7%B4%84.html

同じようにJavascriptもいくつかのコーディングスタイルがあります。

Airbnb JavaScript スタイルガイド

Airbnb JavaScript スタイルガイド
https://mitsuruog.github.io/javascript-style-guide/

Felix's Node.js Style Guide

Felix's Node.js Style Guide
http://popkirby.github.io/contents/nodeguide/style.html

Vue.jsのスタイル

スタイルガイド: https://jp.vuejs.org/v2/style-guide/index.html

prettier、eslintなどのチェッカー、フォーマッタのツールを設定し、スタイルを統一しましょう

以上

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

Vue基礎:IE11対応

Vue.jsではIE11はサポートされないですね。画面は真っ白になってしまう場合があります。

babel-polyfillというツールで対応できます。
ただし、古いブラウザ対応のため変換したコードは、長いし、速度も遅いようです。

いつかIE11のサポートは対象外になると幸せですね。

インストール

npm install babel-polyfill --save

babel.config.js

module.exports = {
  presets: [
    ['@vue/app', {
      polyfills: [
        'es6.promise',
        'es6.symbol'
      ]
    }]
  ]
}

import "babel/polyfill"

main.jsにてVueと同じ位置でimport

import Vue from 'vue'
import "babel/polyfill";

fetchメソッド

IE11ではfetchメソッドがないからエラーになる場合があります。
その際には、whatwg-fetchを利用して回避できます。

1. インストール

npm install whatwg-fetch --save

2. App.vueに設定追加

import 'whatwg-fetch'

if (window.fetch === undefined) {
  window.fetch = fetch;
}

うまくいかない場合はIE11のコンソールのエラーを見てGoogle先生に聞きながら調整しましょう。

参考記事:https://cli.vuejs.org/guide/browser-compatibility.html#browserslist

以上

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

vue.jsで簡単なtodoアプリを作る

この記事の目的

vue.jsの勉強のために以下の仕様のtodoアプリを作成する。

  • formからsubmitするとtodoに表示される
  • deleteで消せる

v-forでループ処理する

main.js
(function(){
  'use strict';
  var vm = new Vue({
    el: '#app',
    data: {
      todos: [
        'task 1',
        'task 2',
        'task 3'
      ]
    }
  });
})();

main.jsにtodosというプロパティをもって直書きでまずtaskを3つ入れる。これをループで3つとも表示するために

index.html
<!DOCTYPE>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>Myvueapp</title>
  <link rel="stylesheet" href="css/style.css">
</head>
<body>
  <div id="app">
    <ul>
      <li v-for="todo in todos">
        {{ todo }}
      </li>
    </ul>
    <p>
      <input type="text" v-model="name" />
    </p>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue"></script>
  <script src="js/main.js"></script>
</body>
</html>

v-for="todo in todosと記述し、todosの要素をtodoとしてあるだけループするようにする。

submitでtodo追加

イメージ (1)-1.jpg

main.js
(function(){
  'use strict';
  var vm = new Vue({
    el: '#app',
    data: {
      newItem: '',
      todos: [
        'task 1',
        'task 2',
        'task 3'
      ]
    },
    methods: {
      addItem: function(){
        this.todos.push(this.newItem);
        this.newItem = '';
      }
    }
  });
})();

index.html
<!DOCTYPE>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>Myvueapp</title>
  <link rel="stylesheet" href="css/style.css">
</head>
<body>
  <div id="app">
    <ul>
      <li v-for="todo in todos">
        {{ todo }}
      </li>
    </ul>
    <form v-on:submit.prevent=addItem>
      <input type="text" v-model="newItem" />
      <input type="submit" value="Add" />
    </form>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue"></script>
  <script src="js/main.js"></script>
</body>
</html>
  • v-onでsubmit時にaddItemを発火
  • addItem関数でtodosの配列にnewItemを入れる
  • その後値をリセットするために空の値を代入

todoの削除

main.js
(function(){
  'use strict';
  var vm = new Vue({
    el: '#app',
    data: {
      newItem: '',
      todos: [
        'task 1',
        'task 2',
        'task 3'
      ]
    },
    methods: {
      addItem: function(){
        this.todos.push(this.newItem);
        this.newItem = '';
      },
      deleteItem:function(index){
        if(confirm('are you sure?')){
          this.todos.splice(index, 1);
        }
      }
    }
  });
})();
index.html
<!DOCTYPE>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>Myvueapp</title>
  <link rel="stylesheet" href="css/style.css">
</head>
<body>
  <div id="app">
    <ul>
      <li v-for="(todo, index) in todos">
        {{ todo }}
        <span @click="deleteItem"></span>
      </li>
    </ul>
    <form v-on:submit.prevent=addItem>
      <input type="text" v-model="newItem" />
      <input type="submit" value="Add" />
    </form>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue"></script>
  <script src="js/main.js"></script>
</body>
</html>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む