- 投稿日:2020-06-04T23:57:09+09:00
入門: Vue.jsでCRUDアプリケーションを作る
はじめに
フロントエンド開発、最近はやはりJSで書くのが基本だとは思うけどReactやAngularは少し敷居が高いのでVue.jsに手を出してみました。
とりあえず、自分へのメモの意味合でVue JSのCRUDアプリケーションのチュートリアルを書いてみます。CRUDが書ければあとは必要に応じて普通に拡張できるでしょうし。たぶん。今回はAPIサイドをJavaのQuarkusで、フロントエンドをVue.jsで書いています。
サーバサイドをQuarkusで作成
プロジェクト作成
まずサーバサイドを作成します。Mavenでまずは以下のようにQuarkusプロジェクトを生成します。なお、mavenのバージョンが3.6.2より低い場合は事前に上げておくこと。
$ mvn io.quarkus:quarkus-maven-plugin:1.3.0.Final:create \ -DprojectGroupId=crud \ -DprojectArtifactId=crud-server \ -DclassName="crud.ItemResource" \ -Dpath="/items"つづいて、必要なプラグインをインストール。今回はDB周りとJAX-RS周りを入れています。
$ cd crud-server/ $ ./mvnw quarkus:add-extension -Dextensions="quarkus-hibernate-orm-panache,quarkus -jdbc-h2,quarkus-resteasy-jackson,quarkus-resteasy-jsonb"ビルドの確認
$ ./mvnw compile quarkus:dev
永続化層の作成
まずは、application.propertiesの設定を変更。
# DB Config quarkus.datasource.db-kind=h2 quarkus.datasource.jdbc.url=jdbc:h2:mem: quarkus.hibernate-orm.database.generation = drop-and-create続いてEntityの作成。getter/setterは長いので省略。
@Entity public class Item { @Id @GeneratedValue(generator = "UUID") @GenericGenerator( name = "UUID", strategy = "org.hibernate.id.UUIDGenerator" ) @Column(name = "id", updatable = false, nullable = false) private UUID id; private String name; private int price; public Item() { } public Item(UUID id, String name, int price) { this.id = id; this.name = name; this.price = price; } public UUID getId() { return id; } public void setId(UUID id) { this.id = id; } - 略 - }最後にRepository.
@ApplicationScoped public class ItemRepository implements PanacheRepository<Item> { public Item findById(UUID id) { return this.find("id", id).list().get(0); } public void update(UUID id, Item item) { var x = findById(id); x.setName(item.getName()); x.setPrice(item.getPrice()); } }エンドポイント作成
エンドポイントとしてJAX-RSで簡単なREST APIを作成する
@Path("/items") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @Transactional public class ItemResource { @Inject ItemRepository repository; @GET @Path("{id}") public Item get(@PathParam("id") UUID id) { return repository.findById(id); } @GET public List<Item> list() { return repository.listAll(); } @POST public void create(Item item) { repository.persist(item); } @PUT @Path("{id}") public void update(@PathParam("id") UUID id, Item item) { repository.update(id, item); } @DELETE @Path("{id}") public void delete(@PathParam("id") UUID id) { repository.delete("id", id); } }CROSの設定
このままではポート番号が違うため例えローカルホストであってもCROSでエラーになる。
そのため、CROS対応に設定をapplication.propertiesに追加する. この例ではQuarkus側がポート3000, Vue.js側が8080で起動する想定。本番環境では構成によっては同一サーバで動くと思うので、その場合はCROSに関する設定はしなくて良い。
# HTTP Config quarkus.http.port=3000 quarkus.http.cors=true quarkus.http.cors.origins=http://localhost:8080,http://172.19.27.127:8080 quarkus.http.cors.methods=GET,PUT,POST,DELETE動作確認
サーバサイドの動作確認を行います。
$ ./mvnw compile quarkus:dev
crulでAPIを実行。
$ URL=http://localhost:3000/items $ JSON_TYPE="Content-Type:application/json" $ curl -XGET ${URL} -H $JSON_TYPE [] $ curl -XPOST ${URL} -H $JSON_TYPE -d '{"name" : "new item 02", "price" : 128}' $ curl -XPOST ${URL} -H $JSON_TYPE -d '{"name" : "new item 02", "price" : 128}' $ curl -XGET ${URL} -H $JSON_TYPE [{"id":"57cda117-474a-48bf-85f3-8f83dd33dac9","name":"new item 01","price":64},{"id":"bc5cc9fd-0ba9-4b02-91e8-93fdf9ed59fd","name":"new item 02","price":128}] $ curl -XPUT ${URL}/"57cda117-474a-48bf-85f3-8f83dd33dac9" -H $JSON_TYPE -d '{"name" : "new item 01", "price" : 1024}' $ curl -XGET ${URL} -H $JSON_TYPE [{"id":"bc5cc9fd-0ba9-4b02-91e8-93fdf9ed59fd","name":"new item 02","price":128},{"id":"57cda117-474a-48bf-85f3-8f83dd33dac9","name":"new item 01","price":1024}] $ curl -XDELETE ${URL}/"bc5cc9fd-0ba9-4b02-91e8-93fdf9ed59fd" -H $JSON_TYPE $ curl -XGET ${URL} -H $JSON_TYPE [{"id":"57cda117-474a-48bf-85f3-8f83dd33dac9","name":"new item 01","price":1024}]APIが適切に動作してるのが分かります。OpenAPIのUIで確認したいなら下記のURLから確認。
http://localhost:3000/swagger-ui/クライアントサイドをVue.jsで作成
続いて本命のクライアントサイドアプリをVue.jsで実装します。
プロジェクトの作成
まずはvue-cliのインストールを行います。
$ yarn global add @vue/cli
続いてプロジェクトの作成。選択肢はマニュアルを選択してRouterを追加します。面倒なので今回はLintも外します。後の選択肢は好みで。
$ vue create crud-client Vue CLI v4.2.3 ? Please pick a preset: Manually select features ? Check the features needed for your project: ◉ Babel ◯ TypeScript ◯ Progressive Web App (PWA) Support ◉ Router ◯ Vuex ◯ CSS Pre-processors ◯ Linter / Formatter ◯ Unit Testing ◯ E2E Testing開発サーバを立てて動作確認をします。
$ cd crud-client $ yarn serveネットワークライブラリの追加
JS標準のFetch APIだと記述が冗長なのでaxiosを使います。
$ yarn add axios vue-axios
UIフレームワークの追加
UIフレームワークとしてBootstrapを入れます。合わせてリッチなアラートを提供するsweetalertもインストールします。
$ yarn add bootstrap vue-sweetalert2
Vue Configの設定をする
通常、本番環境やテスト環境と開発環境ではAPIの接続先が異なると思います。APIサーバをローカルに立てたりするので。
なので、開発サーバの設定を変更して、そこで接続先を書き換えます。ルートディレクトリにvue.config.jsを追加して以下のように修正します。また、タイトルも同様にここで設定するので追加しておきます。
vue.config.jsmodule.exports = { devServer: { proxy: { "^/items": { target: "http://localhost:3000", ws: false, pathRewrite: { "^/items": "/items" } } } }, pages: { index: { entry: 'src/main.js', title: 'My CRUD Apps', } } };ライブラリのインポート
先ほど、yarnでインストールしたライブラリを読み込むためにmain.jsを以下のように修正します。
src/main.jsimport Vue from 'vue' import App from './App.vue' import router from './router' import axios from 'axios'; import VueAxios from 'vue-axios'; import VueSweetalert2 from 'vue-sweetalert2'; import '../node_modules/bootstrap/dist/css/bootstrap.min.css'; import '../node_modules/sweetalert2/dist/sweetalert2.min.css'; Vue.config.productionTip = false Vue.use(VueSweetalert2); Vue.use(VueAxios, axios); new Vue({ router, render: h => h(App) }).$mount('#app')CRUD画面を作る
本題のCRUD画面を作ります。メイン画面はviews配下に作るのが作法のようです。
とりあえず不要なファイルを消しましょう。$ rm src/views/About.vue $ rm src/views/Home.vue作成画面
Script側で定義したデータモデルに対してリアクティブにテンプレート側の値が変更されます。
サーバ側でエラーが発生すればmessage
にメッセージとして表示されるようにしています。
this
を多用しているので無名関数をアロー演算子ではなくfunctionで作成してしまうとスコープが変わって動かなくなるので注意。また、作成完了時には一覧画面に遷移しますが、遷移後に作成完了のメッセージを一覧画面に表示する代わりにSweetAlertでメッセージを出しています。
これは、Vue.jsではJavaEEやRailsのFlush領域のように画面遷移に際して値をバックエンドで受け渡す仕組みが無いためエラーメッセージを同じUIで出すのは無駄に困難です。なので似たUXとなるリーズナブルなUIを選択しています。src/views/Create.vue<template> <div class="container"> <div class="card"> <div class="card-header"> <h3>Add Item</h3> </div> <div class="card-body"> <form v-on:submit.prevent="addItem"> <div v-show="message" class="alert alert-danger">{{message}}</div> <div class="form-group"> <label>Item Name:</label> <input type="text" class="form-control" v-model="item.name" /> </div> <div class="form-group"> <label>Item Price:</label> <input type="text" class="form-control" v-model="item.price" /> </div> <div class="form-group"> <input type="submit" class="btn btn-primary" value="Add Item" /> </div> </form> </div> </div> </div> </template> <script> export default { components: { name: "AddItem" }, data() { return { item: {}, message: "" }; }, methods: { addItem() { let uri = "/items/"; this.axios .post(uri, this.item) .then(() => { this.$swal({ icon: "success", text: "Created Success!" }); this.$router.push({ name: "Index" }); }) .catch(error => { this.message = `status: ${error.response.status}, message: ${error.response.data}`; }); } } };一覧画面
一覧画面では画面を表示した際に呼ばれるcreatedメソッドの中で、一覧を取得するAPIを呼んでいます。
また、削除のAPIコールと編集への遷移も行っています。src/views/Index.vue<template> <div> <h1>Items</h1> <table class="table table-hover"> <thead> <tr> <td>ID</td> <td>Item Name</td> <td>Item Price</td> <td>Actions</td> </tr> </thead> <tbody> <tr v-for="item in items" :key="item._id"> <td>{{ item.id }}</td> <td>{{ item.name }}</td> <td>{{ item.price }}</td> <td> <router-link :to="{name: 'Edit', params: { id: item.id }}" class="btn btn-primary">Edit</router-link> </td> <td> <button class="btn btn-danger" v-on:click="deleteItem(item.id)">Delete</button> </td> </tr> </tbody> </table> </div> </template> <script> export default { data() { return { items: [] }; }, created: function() { this.fetchItems(); }, methods: { fetchItems() { let uri = "/items"; this.axios.get(uri).then(response => { this.items = response.data; }); }, deleteItem(id) { let uri = "/items/" + id; this.axios.delete(uri).then(() => { this.fetchItems(); }); } } }; </script>編集画面
編集画面は基本的に作成画面と同様です。違いはサーバサイドにIDで値を問い合わせてるかくらいですね。
src/views/Edit.vue<template> <div class="container"> <div class="card"> <div class="card-header"> <h3>Edit Item</h3> </div> <div class="card-body"> <form v-on:submit.prevent="updateItem"> <div v-show="message" class="alert alert-danger">{{message}}</div> <div class="form-group"> <label>Item Name:</label> <input type="text" class="form-control" v-model="item.name" /> </div> <div class="form-group"> <label>Item Price:</label> <input type="text" class="form-control" v-model="item.price" /> </div> <div class="form-group"> <input type="submit" class="btn btn-primary" value="Update Item" /> </div> </form> </div> </div> </div> </template> <script> export default { data() { return { item: {}, message: "" }; }, created: function() { this.getItem(); }, methods: { getItem() { let uri = "/items/" + this.$route.params.id; this.axios.get(uri).then(response => { this.item = response.data; }); }, updateItem() { let uri = "/items/" + this.$route.params.id; this.axios.put(uri, this.item).then(() => { this.$swal({ icon: "success", text: "Updated Success!" }); this.$router.push({ name: "Index" }); }); } } }; </script>メインテンプレートの変更
App.vueを変更してメインのテンプレートを変更します。
src/App.vue<template> <div id="app" class="container"> <nav class="navbar navbar-expand-sm bg-light"> <ul class="navbar-nav"> <li class="nav-item"> <router-link :to="{ name: 'Create' }" class="nav-link">Add Item</router-link> </li> <li class="nav-item"> <router-link :to="{ name: 'Index' }" class="nav-link">All Items</router-link> </li> </ul> </nav> <transition name="fade"> <div class="gap"> <router-view></router-view> </div> </transition> </div> </template> <script> export default { } </script> <style> .fade-enter-active, .fade-leave-active { transition: opacity .5s } .fade-enter, .fade-leave-active { opacity: 0 } .gap { margin-top: 50px; } </style>ルーティングの変更
先ほど作成した画面をルーティングに登録するために
src/router/index.js
を修正します。src/router/index.jsimport Vue from 'vue' import VueRouter from 'vue-router' import Create from '../views/Create.vue' import Edit from '../views/Edit.vue' import Index from '../views/Index.vue' Vue.use(VueRouter) const routes = [ { name: 'Index', path: '/', component: Index }, { name: 'Create', path: '/create', component: Create }, { name: 'Edit', path: '/edit', component: Edit } ] const router = new VueRouter({ mode: 'history', base: process.env.BASE_URL, routes }) export default routerまとめ
とりあえずVue.jsとJavaで作ったAPIでCRUDを実現することができました。
正直、そこまでスケーラビリティを求めないならアプリではPHPやJFR、あるいはRailsで作った方が楽じゃないかの疑惑も拭えないですが、次はログイン機能あたりを実装してみたいと思います。
それでは、Happy Hacking!
参考
- 投稿日:2020-06-04T23:57:09+09:00
Vue JSでCRUDアプリケーションを作る
はじめに
フロントエンド開発、最近はやはりJSで書くのが基本だとは思うけどReactやAngularは少し敷居が高いのでVue.jsに手を出してみました。
とりあえず、自分へのメモの意味合でVue JSのCRUDアプリケーションのチュートリアルを書いてみます。CRUDが書ければあとは必要に応じて普通に拡張できるでしょうし。たぶん。今回はAPIサイドをJavaのQuarkusで、フロントエンドをVue.jsで書いています。
サーバサイドをQuarkusで作成
プロジェクト作成
まずサーバサイドを作成します。Mavenでまずは以下のようにQuarkusプロジェクトを生成します。なお、mavenのバージョンが3.6.2より低い場合は事前に上げておくこと。
$ mvn io.quarkus:quarkus-maven-plugin:1.3.0.Final:create \ -DprojectGroupId=crud \ -DprojectArtifactId=crud-server \ -DclassName="crud.ItemResource" \ -Dpath="/items"つづいて、必要なプラグインをインストール。今回はDB周りとJAX-RS周りを入れています。
$ cd crud-server/ $ ./mvnw quarkus:add-extension -Dextensions="quarkus-hibernate-orm-panache,quarkus -jdbc-h2,quarkus-resteasy-jackson,quarkus-resteasy-jsonb"ビルドの確認
$ ./mvnw compile quarkus:dev
永続化層の作成
まずは、application.propertiesの設定を変更。
# DB Config quarkus.datasource.db-kind=h2 quarkus.datasource.jdbc.url=jdbc:h2:mem: quarkus.hibernate-orm.database.generation = drop-and-create続いてEntityの作成。getter/setterは長いので省略。
@Entity public class Item { @Id @GeneratedValue(generator = "UUID") @GenericGenerator( name = "UUID", strategy = "org.hibernate.id.UUIDGenerator" ) @Column(name = "id", updatable = false, nullable = false) private UUID id; private String name; private int price; public Item() { } public Item(UUID id, String name, int price) { this.id = id; this.name = name; this.price = price; } public UUID getId() { return id; } public void setId(UUID id) { this.id = id; } - 略 - }最後にRepository.
@ApplicationScoped public class ItemRepository implements PanacheRepository<Item> { public Item findById(UUID id) { return this.find("id", id).list().get(0); } public void update(UUID id, Item item) { var x = findById(id); x.setName(item.getName()); x.setPrice(item.getPrice()); } }エンドポイント作成
エンドポイントとしてJAX-RSで簡単なREST APIを作成する
@Path("/items") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @Transactional public class ItemResource { @Inject ItemRepository repository; @GET @Path("{id}") public Item get(@PathParam("id") UUID id) { return repository.findById(id); } @GET public List<Item> list() { return repository.listAll(); } @POST public void create(Item item) { repository.persist(item); } @PUT @Path("{id}") public void update(@PathParam("id") UUID id, Item item) { repository.update(id, item); } @DELETE @Path("{id}") public void delete(@PathParam("id") UUID id) { repository.delete("id", id); } }CORS対応のためにいくつか設定をapplication.propertiesに追加する. この例ではQuarkus側がポート3000, Vue.js側が8080で起動する想定。
本番完了では構成によってはCORSは起こらないので設定不要。
# HTTP Config quarkus.http.port=3000 quarkus.http.cors=true quarkus.http.cors.origins=http://localhost:8080,http://172.19.27.127:8080 quarkus.http.cors.methods=GET,PUT,POST,DELETE動作確認。
$ ./mvnw compile quarkus:dev
crulでAPIを実行。
$ URL=http://localhost:3000/items $ JSON_TYPE="Content-Type:application/json" $ curl -XGET ${URL} -H $JSON_TYPE [] $ curl -XPOST ${URL} -H $JSON_TYPE -d '{"name" : "new item 02", "price" : 128}' $ curl -XPOST ${URL} -H $JSON_TYPE -d '{"name" : "new item 02", "price" : 128}' $ curl -XGET ${URL} -H $JSON_TYPE [{"id":"57cda117-474a-48bf-85f3-8f83dd33dac9","name":"new item 01","price":64},{"id":"bc5cc9fd-0ba9-4b02-91e8-93fdf9ed59fd","name":"new item 02","price":128}] $ curl -XPUT ${URL}/"57cda117-474a-48bf-85f3-8f83dd33dac9" -H $JSON_TYPE -d '{"name" : "new item 01", "price" : 1024}' $ curl -XGET ${URL} -H $JSON_TYPE [{"id":"bc5cc9fd-0ba9-4b02-91e8-93fdf9ed59fd","name":"new item 02","price":128},{"id":"57cda117-474a-48bf-85f3-8f83dd33dac9","name":"new item 01","price":1024}] $ curl -XDELETE ${URL}/"bc5cc9fd-0ba9-4b02-91e8-93fdf9ed59fd" -H $JSON_TYPE $ curl -XGET ${URL} -H $JSON_TYPE [{"id":"57cda117-474a-48bf-85f3-8f83dd33dac9","name":"new item 01","price":1024}]APIが適切に動作してるのが分かります。
クライアントサイドをVue.jsで作成
続いて本命のクライアントサイドアプリをVue.jsで実装します。
プロジェクトの作成
まずはvue-cliのインストールを行います。
$ yarn global add @vue/cli
続いてプロジェクトの作成。選択肢はマニュアルを選択してRouterを追加します。面倒なので今回はLintも外します。後の選択肢は好みで。
$ vue create crud-client Vue CLI v4.2.3 ? Please pick a preset: Manually select features ? Check the features needed for your project: ◉ Babel ◯ TypeScript ◯ Progressive Web App (PWA) Support ◉ Router ◯ Vuex ◯ CSS Pre-processors ◯ Linter / Formatter ◯ Unit Testing ◯ E2E Testing開発サーバを立てて動作確認をします。
$ cd crud-client $ yarn serveネットワークライブラリの追加
JS標準のFetch APIだと記述が冗長なのでaxiosを使います。
$ yarn add axios vue-axios
UIフレームワークの追加
UIフレームワークとしてBootstrapを入れます。合わせてリッチなアラートを提供するsweetalertもインストールします。
$ yarn add bootstrap vue-sweetalert2
Vue Configの設定をする
通常、本番環境やテスト環境と開発環境ではAPIの接続先が異なると思います。APIサーバをローカルに立てたりするので。
なので、開発サーバの設定を変更して、そこで接続先を書き換えます。ルートディレクトリにvue.config.jsを追加して以下のように修正します。また、タイトルも同様にここで設定するので追加しておきます。
vue.config.jsmodule.exports = { devServer: { proxy: { "^/items": { target: "http://localhost:3000", ws: false, pathRewrite: { "^/items": "/items" } } } }, pages: { index: { entry: 'src/main.js', title: 'My CRUD Apps', } } };ライブラリのインポート
先ほど、yarnでインストールしたライブラリを読み込むためにmain.jsを以下のように修正します。
src/main.jsimport Vue from 'vue' import App from './App.vue' import router from './router' import axios from 'axios'; import VueAxios from 'vue-axios'; import VueSweetalert2 from 'vue-sweetalert2'; import '../node_modules/bootstrap/dist/css/bootstrap.min.css'; import '../node_modules/sweetalert2/dist/sweetalert2.min.css'; Vue.config.productionTip = false Vue.use(VueSweetalert2); Vue.use(VueAxios, axios); new Vue({ router, render: h => h(App) }).$mount('#app')CRUD画面を作る
本題のCRUD画面を作ります。メイン画面はviews配下に作るのが作法のようです。
とりあえず不要なファイルを消しましょう。$ rm src/views/About.vue $ rm src/views/Home.vue作成画面
Script側で定義したデータモデルに対してリアクティブにテンプレート側の値が変更されます。
サーバ側でエラーが発生すればmessage
にメッセージとして表示されるようにしています。
this
を多用しているので無名関数をアロー演算子ではなくfunctionで作成してしまうとスコープが変わって動かなくなるので注意。また、作成完了時には一覧画面に遷移しますが、遷移後に作成完了のメッセージを一覧画面に表示する代わりにSweetAlertでメッセージを出しています。
これは、Vue.jsではJavaEEやRailsのFlush領域のように画面遷移に際して値をバックエンドで受け渡す仕組みが無いためエラーメッセージを同じUIで出すのは無駄に困難です。なので似たUXとなるリーズナブルなUIを選択しています。src/views/Create.vue<template> <div class="container"> <div class="card"> <div class="card-header"> <h3>Add Item</h3> </div> <div class="card-body"> <form v-on:submit.prevent="addItem"> <div v-show="message" class="alert alert-danger">{{message}}</div> <div class="form-group"> <label>Item Name:</label> <input type="text" class="form-control" v-model="item.name" /> </div> <div class="form-group"> <label>Item Price:</label> <input type="text" class="form-control" v-model="item.price" /> </div> <div class="form-group"> <input type="submit" class="btn btn-primary" value="Add Item" /> </div> </form> </div> </div> </div> </template> <script> export default { components: { name: "AddItem" }, data() { return { item: {}, message: "" }; }, methods: { addItem() { let uri = "/items/"; this.axios .post(uri, this.item) .then(() => { this.$swal({ icon: "success", text: "Created Success!" }); this.$router.push({ name: "Index" }); }) .catch(error => { this.message = `status: ${error.response.status}, message: ${error.response.data}`; }); } } };一覧画面
一覧画面では画面を表示した際に呼ばれるcreatedメソッドの中で、一覧を取得するAPIを呼んでいます。
また、削除のAPIコールと編集への遷移も行っています。src/views/Index.vue<template> <div> <h1>Items</h1> <table class="table table-hover"> <thead> <tr> <td>ID</td> <td>Item Name</td> <td>Item Price</td> <td>Actions</td> </tr> </thead> <tbody> <tr v-for="item in items" :key="item._id"> <td>{{ item.id }}</td> <td>{{ item.name }}</td> <td>{{ item.price }}</td> <td> <router-link :to="{name: 'Edit', params: { id: item.id }}" class="btn btn-primary">Edit</router-link> </td> <td> <button class="btn btn-danger" v-on:click="deleteItem(item.id)">Delete</button> </td> </tr> </tbody> </table> </div> </template> <script> export default { data() { return { items: [] }; }, created: function() { this.fetchItems(); }, methods: { fetchItems() { let uri = "/items"; this.axios.get(uri).then(response => { this.items = response.data; }); }, deleteItem(id) { let uri = "/items/" + id; this.axios.delete(uri).then(() => { this.fetchItems(); }); } } }; </script>編集画面
編集画面は基本的に作成画面と同様です。違いはサーバサイドにIDで値を問い合わせてるかくらいですね。
src/views/Edit.vue<template> <div class="container"> <div class="card"> <div class="card-header"> <h3>Edit Item</h3> </div> <div class="card-body"> <form v-on:submit.prevent="updateItem"> <div v-show="message" class="alert alert-danger">{{message}}</div> <div class="form-group"> <label>Item Name:</label> <input type="text" class="form-control" v-model="item.name" /> </div> <div class="form-group"> <label>Item Price:</label> <input type="text" class="form-control" v-model="item.price" /> </div> <div class="form-group"> <input type="submit" class="btn btn-primary" value="Update Item" /> </div> </form> </div> </div> </div> </template> <script> export default { data() { return { item: {}, message: "" }; }, created: function() { this.getItem(); }, methods: { getItem() { let uri = "/items/" + this.$route.params.id; this.axios.get(uri).then(response => { this.item = response.data; }); }, updateItem() { let uri = "/items/" + this.$route.params.id; this.axios.put(uri, this.item).then(() => { this.$swal({ icon: "success", text: "Updated Success!" }); this.$router.push({ name: "Index" }); }); } } }; </script>メインテンプレートの変更
App.vueを変更してメインのテンプレートを変更します。
src/App.vue<template> <div id="app" class="container"> <nav class="navbar navbar-expand-sm bg-light"> <ul class="navbar-nav"> <li class="nav-item"> <router-link :to="{ name: 'Create' }" class="nav-link">Add Item</router-link> </li> <li class="nav-item"> <router-link :to="{ name: 'Index' }" class="nav-link">All Items</router-link> </li> </ul> </nav> <transition name="fade"> <div class="gap"> <router-view></router-view> </div> </transition> </div> </template> <script> export default { } </script> <style> .fade-enter-active, .fade-leave-active { transition: opacity .5s } .fade-enter, .fade-leave-active { opacity: 0 } .gap { margin-top: 50px; } </style>ルーティングの変更
先ほど作成した画面をルーティングに登録するために
src/router/index.js
を修正します。src/router/index.jsimport Vue from 'vue' import VueRouter from 'vue-router' import Create from '../views/Create.vue' import Edit from '../views/Edit.vue' import Index from '../views/Index.vue' Vue.use(VueRouter) const routes = [ { name: 'Index', path: '/', component: Index }, { name: 'Create', path: '/create', component: Create }, { name: 'Edit', path: '/edit', component: Edit } ] const router = new VueRouter({ mode: 'history', base: process.env.BASE_URL, routes }) export default routerまとめ
とりあえずVue.jsとJavaで作ったAPIでCRUDを実現することができました。
正直、そこまでスケーラビリティを求めないならアプリではPHPやJFR、あるいはRailsで作った方が楽じゃないかの疑惑も拭えないですが、次はログイン機能あたりを実装してみたいと思います。
それでは、Happy Hacking!
参考
- 投稿日:2020-06-04T23:38:45+09:00
Web VRとobnizを連携することに成功!
概要
「Web VR(A-Framee)のボタンを押すと、obnizの画面にメッセージが表示される」
というものが作りたくて奮闘しました
https://qiita.com/kmaepu/items/55689772aa41bd5d8433
こちらの記事ではobnizとの連携まで至りませんでした。というのをTwitterにつぶやいた所、中の人からアドバイス頂き、実現することができました!
ありがとうございます。実際の動き
少し見づらいですが、PC画面上のボタンを押すと、obnizの液晶画面に文字が映し出されます。
やりたかったことができた!
— まえぷー@出窓菜園ティスト (@kmaepu) June 4, 2020
Web VR上のボタンを押すとobnizの画面にメッセージが表示される!#obniz pic.twitter.com/MtTR3wpVCwソースコード解説
前回の記事にあったところとの差分を抜き出すと次の箇所になります。
let obniz = new Obniz("obniz_id"); // obniz idを入力する let connected = false; obniz.onconnect = async function () { console.log("Connect obniz"); connected = true; } obniz.onclose = async function () { connected = false; } async function handlerClick(event) { console.log('handlerClick'); console.log(event); event.target.object3D.position.z -= 0.5; if (connected) { obniz.display.clear(); obniz.display.print("3D A-Frame"); obniz.display.print(" ↑↓"); obniz.display.print("obniz"); } }何が原因だったかというと、obnizのconnectはscriptの走った最初だけ実行し、それ以降に何度もconnnectしようとしても動かないとのことです。
function内で何度もconnnectしちゃいけなかったのか!
<!DOCTYPE html> <html> <head> <title>Hello, WebVR! • A-Frame</title> <meta name="description" content="Hello, WebVR! • A-Frame"> <script src="https://aframe.io/releases/1.0.4/aframe.min.js"></script> </head> <body> <a-scene> <!--背景画像--> <a-sky src="https://aframe.io/aframe/examples/boilerplate/panorama/puydesancy.jpg" rotation="0 -130 0"></a-sky> <!--仮想ボタン--> <a-box color="#EEEEEE" position="0 2 -3" height="2" width="2"></a-box> <a-box color="#C0C0C0" position="0 2 -2.5" height="0.5" width="0.5" onclick="handlerClick(event)" onmouseenter="handlerMouseEnter(event)" onmouseleave="handlerMouseLeave(event)"> <a-animation attribute="scale" to="-3.5 -5 2" direction="alternate" dur="2000" repeat="indefinite" easing="linear"> </a-animation> </a-box> <!--足もとのブロック--> <a-box color="#99FFFF" position="0 0 0" height="2" width="2"></a-box> <!--自分の視点--> <a-camera> <a-cursor></a-cursor> </a-camera> </a-scene> <script src="https://unpkg.com/obniz@3.5.0/obniz.js" crossorigin="anonymous"></script> <script> let obniz = new Obniz("obniz_id"); // obniz idを入力する let connected = false; obniz.onconnect = async function () { console.log("Connect obniz"); connected = true; } obniz.onclose = async function () { connected = false; } async function handlerClick(event) { console.log('handlerClick'); console.log(event); event.target.object3D.position.z -= 0.5; if (connected) { obniz.display.clear(); obniz.display.print("3D A-Frame"); obniz.display.print(" ↑↓"); obniz.display.print("obniz"); } } function handlerMouseEnter(event) { console.log('handlerMouseEnter'); console.log(event); } function handlerMouseLeave(event) { console.log('handlerMouseLeave'); console.log(event); } </script> </body> </html>
- 投稿日:2020-06-04T22:38:24+09:00
本当に0からVue.js環境構築
Homebrewインストール
1. インストールされているか確認
brew -vバージョンが返ってくればインストールされている。(node.jsインストールへ)
2. brewインストール
- ターミナルに以下のスクリプトを実行
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
- パスワードを入力
- Installation Success! と表示されたらインストール完了。
node.jsインストール
1. インストールされているか確認
node -v
- バージョンが返ってくればインストールされている。(yarnインストールへ)
- 返ってこなかった場合はnode.jsインストールへ
2. node.jsインストール
ターミナルに以下のコマンドを入力
brew install nodebrewnodebrewのバージョンを確認してバージョンが返ってくればインストールされている
nodebrew -vnode.jsインストール準備
nodebrew ls-remoteバージョン番号がいっぱい出てきたら成功
こんな感じのエラーが出てきたら↓
Fetching: https://nodejs.org/dist/v7.10.0/node-v7.10.0-darwin-x64.tar.gz Warning: Failed to create the file Warning: /Users/whoami/.nodebrew/src/v7.10.0/node-v7.10.0-darwin-x64.ta Warning: r.gz: No such file or directory curl: (23) Failed writing body (0 != 941) download failed: https://nodejs.org/dist/v7.10.0/node-v7.10.0-darwin-x64.tar.gz以下のコマンドでディレクトリ作成
mkdir -p ~/.nodebrew/srcnode.jsインストール
nodebrew install-binary stableバージョン確認
nodebrew ls以下のように表示されたら
v7.1.0 current: none表示されているバージョンを有効化する
nodebrew use v7.1.0もう1度
nodebrew ls
を入力するとこんな感じになっているはずv7.1.0 current: v7.1.0環境パスを通す
echo 'export PATH=$HOME/.nodebrew/current/bin:$PATH' >> ~/.zprofileここでターミナルを再起動する or 以下のコマンドを入力
source ~/.bash_profile最後にnodeのバージョンを確認
node -vyarnインストール
brew install yarnバージョンを確認する
yarn -vバージョンが返ってくれば成功
Vueインストール(本題)
yarn global add @vue/cli yarn global add @vue/cli-initこれでVueがインストールされるのでバージョン確認
vue --versionバージョンが返ってくれば成功
Vueプロジェクトを作成する
vue init webpack vue-app質問がいっぱいくるので選択していく。基本的には全てpackage.jsonで変更できるのであまり気にする必要はない
? Project name (vue-app) //Enterを押す ? Project description (A Vue.js project) //Enterを押す ? Author (名前 <メアド>) //Enterを押す Runtime + Compiler: recommended for most users Runtime-only: about 6KB lighter min+gzip, but templates (or any Vue-specific HTML) are ONLY allowed in .vue files - render functions are required elsewhere //Runtime+Compilerの方を選択してEnterを押す ? Install vue-router? (Y/n) //nを押してEnterを押す ? Use ESLint to lint your code? (Y/n) //nを押してEnterを押す ? Set up unit tests (Y/n) //nを押してEnterを押す ? Setup e2e tests with Nightwatch? (Y/n) //nを押してEnterを押す ? Should we run `npm install` for you after the project has been created? (recommended) (Use arrow keys) ❯ Yes, use NPM Yes, use Yarn No, I will handle that myself //Yes, use Yarnを選択してEnterを押すダウンロードが完了したらディレクトリができているので移動
cd vue-app必要なモジュールをインストール
yarn installローカルでアプリケーションを起動する
yarn dev以下のように表示されたら成功。http://localhost:8080 に行くと最初のページが表示されている
最後のlocalhostが起動しなかった場合
- エディタでvue-appを開いてconfig/index.jsに行く
- 17行目のポート番号を8081に変更する
- もう一度ターミナルで
yarn dev
を実行vue環境構築完了!
参考文献
https://qiita.com/white0221/items/d371a19b59af4cba8e8b
https://qiita.com/Mitsuzara/items/4dea8c0aa95d6284980a
https://utano.jp/entry/2018/02/vue-cli-genearte-webpack-project/
- 投稿日:2020-06-04T21:21:43+09:00
Vue.jsで動的な複数のclassを指定する時の書き方
それぞれstringを返すなら以下のようにclassを付与できます
なお:class
はv-bind:class
の省略記法です<div :class="[activeClass, errorClass]"></div>data: { activeClass: 'active', errorClass: 'text-danger' }また、Vueはbooleanな値を使って以下のようにclassを付け替えすることもできます
<div :class="{ 'active': isActive }"></div>data: { isActive: true }以上2つの指定方法が混在しているDOMについては以下のように纏められます。
<div :class="[activeClass, errorClass, { active: isActive }]"></div>data: { activeClass: 'active', errorClass: 'text-danger', isActive: true }配列の形でそれぞれの指定方法に則って記述するだけですね。
また、この指定方法ではDOMにロジックが寄って見通しが悪くなることが考えられます。
その場合、算出プロパティを用いて付与するクラスをまとめると良いでしょう。
同様なロジックが存在している場合にも、算出プロパティは変更がある場合のみ再計算してそれ以外はキャッシュして返してくれるのでパフォーマンスの面でも優れます。<div :class="classObject"></div>data: { isActive: true, error: null }, computed: { classObject: function () { return { active: this.isActive && !this.error, 'text-danger': this.error && this.error.type === 'fatal' } } }
- 投稿日:2020-06-04T19:37:38+09:00
備忘録: Rails Vueアプリ作成時のチートシート
app作成コマンド
rails バージョン new アプリ名 -d mysql --webpacker=vue --skip-test --skip-bundle --skip-turbolinks assets内のapplication.jsから以下の一行を削除する。追加するgem list
gem 'devise' gem 'devise-i18n' gem 'rails-i18n' gem 'faker' gem 'factory_bot_rails' gem 'rubocop', require: false gem 'rubocop-rails', require: false gem 'pry' gem 'pry-doc' gem 'pry-rails' gem 'pry-byebug' gem 'foreman' gem 'rspec-rails', '~> 4.0.0' gem 'rubocop-rscpe', require: false gem 'spring-commands-rspec'実行コマンド
$ rails webpacker:install $ rails webpacker:install:vue $ yarn add axios $ yarn add vue-router $ rails g rspec:install $ rails g devise:install $ rails g devise:user $ rails g devise:views $ yarn add -D eslint eslint-loader eslint-plugin-vuerspecの設定
.rspec--require spec_helper --format documentation”foremanの設定
Profile.devweb: bundle exec rails s # watcher: ./bin/webpack-watcher hot: ./bin/webpack-dev-serverbin/server#!/bin/bash -i bundle install bundle exec foreman start -f Procfile.dev$ chmod 777 bin/serverdeviseの設定
config/environments/development.rbRails.application.configure do # Settings specified here will take precedence over those in config/application.rb. (省略)... # mailer setting config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } end<p class="notice"><%= notice %></p> <p class="alert"><%= alert %></p>日本語対応といらないファイルを作成しない設定
config/application.rbconfig.i18n.default_locale = :ja config.time_zone = "Tokyo" config.generators do |g| g.template_engine false g.assets false g.helper false g.test_framework :rspec, fixtures: false, view_specs: false, helper_specs: false, routing_specs: false endESLintの設定
config/webpack/loaders/eslint.jsmodule.exports = { test: /\.(js|vue)$/, loader: 'eslint-loader', enforce: 'pre', options: {} }environment.jsconst { environment } = require('@rails/webpacker') const vue = require('./loaders/vue') const eslint = require('./loaders/eslint') environment.loaders.append('vue', vue) environment.loaders.append('eslint', eslint) module.exports = environmentよく使う作成コマンド
$ rails g api/v1/コントローラー名 index $ rails g model モデル名 title:string likable:references{polymorphic}
- 投稿日:2020-06-04T13:36:28+09:00
APIで取ってきた画像をVueでbackground-imageを指定する
Vue.jsでbackground-imageを指定する
APIで取得してきたものをhtml内でなんとか指定したいっていう感じ
諦めてimgタグでレイアウト整えようとしたところで解決したので、感動した。
vueなどで普段から書いているフロントエンドエンジニアたちにとってこの書き方ができるかできないかで生涯年収が2倍違うと言っても過言。
index.vue<template> <div :style="{backgroundImage: 'url(' + img[img.length-1].img_url + ')'}"> HelloWorld! </div> </template> <script> export default { async asyncData() { return await axios .get('/api/img/get_img') .then(res => { return { img: res.data.details.img } }) .catch(error => { // throw error; }); }, } </script>