- 投稿日: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-04T23:35:13+09:00
WebSocket通信におけるJSONデータの利用方法 (Java , JavaScript)
背景
チャットアプリ作成を基準とし、サーバー⇔ブラウザ間でのWebSocket通信の記事を書いた。
サーバーをJava、クライアントをjavascriptで実装。[ソケット通信に関する過去記事]
・JavaとJavaScriptでwebブラウザとのソケット通信①
・JavaとJavaScriptでwebブラウザとのソケット通信②今回はチャットアプリを改造しつつ、『JSONデータの取扱方法』について学んでいく。
また、『ソケット通信でのJSONデータの送受信』についても学ぶ。目的
- Java、JavaScriptでのJSONの扱い方法を学ぶ。
- Webソケット通信においてJSONでのデータの送受信方法を学ぶ。
- JSONを用いてWebソケット通信の複数パスを実現する。
前提
この記事では主にJSONの扱い方を中心に記述していく。
ソケット通信についての解説は過去記事やググったページを参考にしてほしい。
⇒ サーバープログラムはJava、クライアントプログラムはJavaScriptで実装。JSONとは
http://www.tohoho-web.com/ex/json.html
例:{ "name": "Tanaka", "age": 26 }
連想配列のような形で値を保持しているもの。
要はただの文字列と考えると楽に理解することができる。(本当は違うけど)
詳細は割愛。実践内容
- JavaScriptでJSONデータの取り扱い (エンコード、デコード)
- JavaでJSONデータの取り扱い (エンコード、デコード)
- ソケット通信におけるJSONデータの送受信方法
- JSON送受信を利用した複数パスのチャットアプリ作成
1. JavaScriptでJSONデータの取り扱い
JavaScriptでは容易にJSONデータを扱うことができる。
エンコード
オブジェクト ⇒ JSON
JSON.stringify()
メソッドを使用する。使用例var obj = { name: '太郎', age: 30, area: 'Tokyo' } var json = JSON.stringify(obj); console.log(json);実行結果{"name":"Taro Tanaka","age":30}
- 変数
obj
に連想配列の形式で値を代入。(オブジェクトの作成)JSON.stringify(obj)
を用いてエンコードし変数json
に代入。(JSONへ変換)console.log(json)
でJSONデータをコンソールに表示。実行結果を見ると、連想配列形式のオブジェクトがJSON形式に変換されていることが分かる。
⇒ キーや文字列は「" "」で囲われている。数値は裸のまま。デコード
JSON ⇒ オブジェクト
JSON.parse()
メソッドを使用する。使用例var obj1 = { name: '太郎', age: 30, area: 'Tokyo' } var json = JSON.stringify(obj1); //-----ここまでJSONデータの準備----- var obj2 = JSON.parse(json); console.log(obj2); console.log(obj2.name);実行結果{name: "太郎", age: 30, area: "Tokyo"} 太郎
- 変数
obj1
に連想配列の形式で値を代入。(オブジェクトの作成)JSON.stringify(obj1)
を用いてエンコードし変数json
に代入。(JSONへ変換)
----------ここまでJSONデータの準備 (前項のエンコードと同様)----------JSON.parse(json)
を用いてデコードし変数obj2
に代入。(オブジェクトへ変換)console.log(obj2)
でオブジェクトをコンソールに表示。obj2.~
で各プロパティにもアクセス可能。実行結果を見ると、JSONデータがオブジェクトに変換されていることが分かる。
obj1
(オブジェクト) ⇒json
(JSON) ⇒obj2
(オブジェクト)2. JavaでJSONデータの取り扱い
Javaのオブジェクトはクラスを指し、JavaScriptのように簡単に作成できない。
JSONとオブジェクトを変換する場合、事前にJSONの変数に対応したプロパティを持つクラスを作成しておく必要がある。JavaでJSONを扱うなら、外部ライブラリを使用することを推奨。
⇒ Java標準APIにもJSONを扱うものは用意されているが、かなり手間がかかる。JSONを扱う外部ライブラリは以下のものが有名。
- Jackson
- GSON
- JSONIC
- JSON in Java などなど
基本の利用方法は似たようなものだが、今回は「Jackson」を使用する。
他ライブラリの使用方法や外部ライブラリの適用方法は、参考ページを見るか適宜ググってほしい。エンコード
オブジェクト ⇒ JSON
エンコードにはObjectMapper
クラスのwriteValueAsString()
メソッドを使用する。使用方法ObjectMapper mapper = new ObjectMapper(); String json = mapper.writeValueAsString(オブジェクトインスタンス);使用例import java.io.IOException; import com.fasterxml.jackson.databind.ObjectMapper; //JSONに変換するプロパティを持つクラス class Info { public String name = "Taro Tanaka"; public int age = 30; } //エンコードを実行するクラス public class Main { public static void main(String[] args) { Info info = new Info();//JSONへ変換するクラスをインスタンス化 ObjectMapper mapper = new ObjectMapper();//ObjectMapperクラスのインスタンスを作成 try { //writeValueAsString()メソッドでエンコード実施 String script = mapper.writeValueAsString(info); System.out.println(script); } catch (IOException e) { e.printStackTrace(); } } }実行結果{"name":"Taro Tanaka","age":30}
Info
クラスにてJSONに変換するためのプロパティを作成する。- エンコードを実行する
Main
クラスを作成。Info info = new Info()
にてJSONに変換するオブジェクトをインスタンス化。ObjectMapper mapper = new ObjectMapper()
にてObjectMapper
クラスをインスタンス化。mapper.writeValueAsString(info)
にてオブジェクトからJSONに変換。
このとき、try,catchでのエラー対応が必要。System.out.println(script)
でJSONデータをコンソールに出力。実行結果を見ると、オブジェクトクラスがJSON形式に変換されていることが分かる。
各プロパティの変数名と値がペアとなって格納されている。デコード
JSON ⇒ オブジェクト
デコードにはObjectMapper
クラスのreadValue()
メソッドを使用する。使用方法ObjectMapper mapper = new ObjectMapper(); mapper.readValue(JSONデータ,オブジェクトクラス.class);使用例import java.io.IOException; import com.fasterxml.jackson.databind.ObjectMapper; //JSONから変換されるプロパティを持つクラス class Info { public String name; public int age; } //デコードを実行するクラス public class Main { public static void main(String[] args) { String script = "{ \"name\":\"Taro Tanaka\", \"age\":30}";//文字列としてJSONデータを作成 ObjectMapper mapper = new ObjectMapper();//ObjectMapperクラスのインスタンスを作成 try { //readValue()メソッドでデコード実施 Info info = mapper.readValue(script, Info.class); System.out.println(info.name); System.out.println(info.age); } catch (IOException e) { e.printStackTrace(); } } }実行結果Taro Tanaka 30
Info
クラスにてJSONから変換されるプロパティを持つクラスを作成する。- デコードを実行する
Main
クラスを作成。- ここではStringの文字列としてJSONデータを用意する。
ObjectMapper mapper = new ObjectMapper()
にてObjectMapper
クラスをインスタンス化。mapper.readValue(script, Info.class)
にてJSONからオブジェクトに変換。
このとき、try,catchでのエラー対応が必要。System.out.println(info.~)
でオブジェクトプロパティをコンソールに出力。実行結果を見ると、JSONデータがオブジェクトクラスに変換されていることが分かる。
デコード後は指定したオブジェクトの各プロパティにアクセスすることで値を取得することができる。3. ソケット通信におけるJSONデータの送受信方法
サーバープログラム:Java
クライアントプログラム:JavaScriptソケット通信を行う際、JSONでの送受信方法を記述する。
JavaとJavaScriptで扱い方が異なるため、それぞれ解説する。JavaScriptでのJSON送受信
JavaScriptに関しては特筆すべきことはない。
前述した通り、JSON.stringify()
メソッド及びJSON.parse()
メソッドで簡単にオブジェクト ⇔ JSON間の変換が可能。
クライアントから送信前にエンコード、受信後にデコードすれば問題無い。送信時エンコード
オブジェクト ⇒ JSON
送信時var obj = { type:'A' , msg:'a' }; var json = JSON.stringify(obj); socket.send(json);
- オブジェクトを用意。
JSON.stringify()
メソッドでJSONへエンコード。- WebSocketの
send()
メソッドでJSONデータ送信受信時デコード
JSON ⇒ オブジェクト
受信時socket.onmessage = function(e) { var obj = JSON.parse(e.data); };
onmessage
でJSONデータを受信。JSON.parse(e.data)
でオブジェクトへデコードJavaでのJSON送受信
Javaのソケット通信時にJSONを用いる場合、JavaScriptのように簡単にはいかない。
⇒ エンコーダー、デコーダー、オブジェクトクラスの3つを用意する必要がある。送信時エンコード
オブジェクト ⇒ JSON
ソケット通信でJSONデータを送信する場合、エンコーダーを用いてオブジェクトからJSONに変換してから送信する。
通常、テキストデータを送信する場合には
sendText()
メソッドを使用するが、JSONを送信する場合はsendObject()
メソッドを使用する。
引数をオブジェクトとし、エンコーダーによってJSONに変換したのち送信する。
- 送信するオブジェクトを作成する。
- エンコーダーを作成する。
- @ServerEndpointアノテーションにエンコーダーを登録する。
1. 送信するオブジェクトを作成
通常のクラスと同様にプロパティを持つクラスを作成する。
コンストラクタ、セッター、ゲッターも通常通り記述。(無くても変換可能。)オブジェクトクラスpublic class JsonObj { private String type = "type1"; private String msg = "msg1"; //コンストラクタ public JsonObj() {} //セッター public void setType(String type) {this.type = type;} public void setMsg(String msg) {this.msg = msg;} //ゲッター public String getType() {return type;} public String getMsg() {return msg;} }2. エンコーダーを作成
エンコーダーは
javax.websocket
パッケージのEncoder.Text
クラスを実装する。
(Encoder.Binary
クラスも存在するが、バイナリデータを扱うためのクラスなので割愛)
Encoder.Text<>
のジェネリクスにはエンコードするオブジェクトクラスを記述しておく。エンコーダーimport javax.websocket.EncodeException; import javax.websocket.Encoder; import javax.websocket.EndpointConfig; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; public class JsonEncoder implements Encoder.Text<JsonObj>{ @Override//初期化は何もしない public void init(EndpointConfig config) {} @Override//エンコード処理 ( オブジェクト → JSON ) public String encode(JsonObj obj) throws EncodeException { ObjectMapper mapper = new ObjectMapper(); String json = ""; try { json = mapper.writeValueAsString(obj); } catch (JsonProcessingException e) { e.printStackTrace(); } return json; } @Override//破棄は何もしない public void destroy() {} }
Encoder.Text
クラスを継承した場合、以下のメソッドをオーバーライドする必要がある。
init(EndpointConfig config)
:エンコーダーが起動したときの処理。encode(Object obj)
:エンコード処理。destroy()
:エンコーダが破棄された時の処理。基本的に
init()
及びdestroy()
は何も処理しなくてよい。
encode(Object)
にて引数をオブジェクトとし、エンコード後に戻り値としてJSONを指定。3. エンコーダーを登録
@ServerEndpoint//エンコーダークラスを指定 @ServerEndpoint(value = "/json" , encoders = JsonEncoder.class) public class JsonTest { //中略 //sendObject()の引数はオブジェクト //送信前にエンコードされる session.getAsyncRemote().sendObject(obj) //中略 }エンコーダーを利用するとき、
@ServerEndpoint()
にエンコーダークラスを指定する。
ここで指定しておくことで、オブジェクト送信時に指定したエンコーダーによってJSONデータへ変換される。
sendObject(obj)
⇒ エンコード処理 (obj→JSON) ⇒ 送信受信時デコード
JSON ⇒ オブジェクト
ソケット通信でJSONデータを受信する場合、デコーダーを用いてJSONからオブジェクトに変換してから受信する。
通常は
onMessage()
メソッドの引数にString型文字列として受信するが、デコーダーを用いることで受信前にデコード処理が実施され、オブジェクトとして受信することとなる。
- 受信するJSONの変換先オブジェクトを作成する。
- デコーダーを作成する。
- @ServerEndpointアノテーションにデコーダーを登録する。
1. 変換先のオブジェクトを作成
デコードする場合、JSONの要素に対応したプロパティを持つオブジェクトクラスを用意しておく必要がある。
複数のJSONを受信するとき内容や形式が異なるなら、それぞれのJSONに対応した複数のオブジェクトクラスを用意しなければならない。エンコードの場合、全てのオブジェクトは任意のタイミングでJSONに変換し送信することが可能
デコードの場合、受信するJSONの内容をあらかじめ把握し、デコード前に受け口となるオブジェクトクラスを用意しておかなければならない。オブジェクトクラスpublic class JsonObj { private String type; private String msg; //コンストラクタ public JsonObj() {} //セッター public void setType(String type) {this.type = type;} public void setMsg(String msg) {this.msg = msg;} //ゲッター public String getType() {return type;} public String getMsg() {return msg;} }2. デコーダーを作成
デコーダーは
javax.websocket
パッケージのDecoder.Text
クラスを実装する。
(Decoder.Binary
クラスも存在するが、バイナリデータを扱うためのクラスなので割愛)
Decoder.Text<>
のジェネリクスにはデコード先のオブジェクトクラスを記述しておく。デコーダーimport javax.websocket.DecodeException; import javax.websocket.Decoder; import javax.websocket.EndpointConfig; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; public class JsonDecoder implements Decoder.Text<JsonObj> { @Override//初期化は何もしない public void init(EndpointConfig config) {} @Override//デコードできるかの判定 public boolean willDecode(String text) { return (text != null); } @Override//デコード処理 ( JSON → オブジェクト) public JsonObj decode(String text) throws DecodeException { ObjectMapper mapper = new ObjectMapper(); JsonObj obj = null; try { obj = mapper.readValue(text, JsonObj.class); } catch (JsonProcessingException e) { e.printStackTrace(); } return obj; } @Override//破棄は何もしない public void destroy() {} }
Decoder.Text
クラスを継承した場合、以下のメソッドをオーバーライドする必要がある。
init(EndpointConfig config)
:デコーダーが起動したときの処理。willDecode(String text)
:デコード処理を実行するかどうかの判定。decode(Object obj)
:デコード処理。destroy()
:デコーダが破棄された時の処理。基本的に
init()
及びdestroy()
は何も処理しなくてよい。
willDecode()
の引数をJSONとし、戻り値がtrue
なら以下のデコードを実行。false
ならデコードされず以降の@OnMessage
メソッドは実行されない。
decode()
にて引数をJSONとし、エンコード後に戻り値としてオブジェクトを指定。3. デコーダーを登録
@ServerEndpoint//デコーダークラスを指定 @ServerEndpoint(value = "/json" , decoders = JsonDecoder.class) public class JsonTest { //中略 //@OnMessageメソッドの前にデコードされる @OnMessage public void onMessage(JsonObj obj , Session mySession) { } //中略 }デコーダーを利用するとき、
@ServerEndpoint()
にデコーダークラスを指定する。
ここで指定しておくことで、データ受信時に指定したデコーダーによってオブジェクトへ変換される。
⇒@OnMessage
メソッドの引数はオブジェクト型となる。(通常はString型)
- 受信 ⇒ デコード処理 (JSON→obj) ⇒
@OnMessage
メソッド4. 複数パスのチャットアプリ作成
過去に作成したチャットアプリを改変する。
変更点
- 送受信するデータをテキストからJSONへ変更 (JSONの使用)
- チャット欄を1つから2つへ増加 (複数パスの実現)
WebSocket通信ではデータを受信するための受け口が1つしかない。
つまりワンパスのため複数の送信元があっても見分けがつかない。
もし見分けるなら、文字列の内容を分解して区別する他無いだろう。
どうせデコードするのならJSONを扱うのが都合がよい。実際は 1.テキスト 2.バイナリ 3.PingPong と3種類の受信メソッドがあるが、ここではテキスト形式のみを扱うため受け口は1つと考える。
作成ファイル
- JsonIndex.html:ブラウザ表示用のHTMLファイル
- JsonSocket.js:ソケット通信のクライアントプログラム
- JsonTest.java:ソケット通信のサーバープログラム
- JsonObj.java:JSONと相互変換するオブジェクトクラス
- JsonEncoder.java:Javaエンコーダー
- JsonDecoder.java:Javaデコーダー
1. 表示用HTML
JsonIndex.html<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>JSON送受信</title> <script type="text/javascript" src="JsonSocket.js"></script> </head> <body> <div style="width: 500px; height: 200px; overflow-y: auto; border: 1px solid #333;" id="show1"></div> <input type="text" size="80" id="msg1" name="msg1" /> <input type="button" value="送信" onclick="sendMsg1();" /> <p></p> <div style="width: 500px; height: 200px; overflow-y: auto; border: 1px solid #333;" id="show2"></div> <input type="text" size="80" id="msg2" name="msg2" /> <input type="button" value="送信" onclick="sendMsg2();" /> </body> </html>ポイント
1. チャット欄を2つに増加した。
表示枠はそれぞれshow1
とshow2
、テキストボックスはmsg1
とmsg2
とした。
後述するJavaScriptファイルでこれらを操作する。2. クライアントプログラム
JsonSocket.js//JSON用のオブジェクト作成 var obj = { type:null , msg:null }; //WebSocketオブジェクト生成 var wSck= new WebSocket("ws://localhost:8080/jsonTest/json"); //ソケット接続時のアクション wSck.onopen = function() { document.getElementById('show1').innerHTML += "接続しました。" + "<br/>"; document.getElementById('show2').innerHTML += "接続したよ~" + "<br/>"; }; //メッセージを受け取ったときのアクション wSck.onmessage = function(e) { //JSONデータをオブジェクトへデコード var json = JSON.parse(e.data); //JSONデータのtype値によって実行内容を変更 if(json.type === 'msg1'){document.getElementById('show1').innerHTML += json.msg + "<br/>";} else if(json.type === 'msg2'){document.getElementById('show2').innerHTML += json.msg + "<br/>";} }; //メッセージ送信1 var sendMsg1 = function(val) { var element = document.getElementById('msg1') obj.type = element.name;//オブジェクトの内容を代入 obj.msg = element.value; var json = JSON.stringify(obj);//オブジェクトをJSONへエンコード wSck.send(json);//JSONを送信 element.value = "";//内容をクリア }; //メッセージ送信2 var sendMsg2 = function(val) { var element = document.getElementById('msg2'); obj.type = element.name; obj.msg = element.value; var json = JSON.stringify(obj); wSck.send(json); element.value = ""; };ポイント
1. JSON用のオブジェクトobj
を作成 ⇒ これがJSONへ変換される。
2. ソケット接続時のアクションをチャット欄増加に合わせて追加。
3. メッセージ受信時にJSONをオブジェクトへデコードし、type値によって処理内容を変更。
4. メッセージ送信1,2:obj
に値を代入後、サーバーへの送信前にJSONへの変換を実施。3. サーバープログラム
JsonTest.javapackage jsonTest; import java.io.IOException; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import javax.websocket.OnClose; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; // 引数にデコーダー、エンコーダーを記述 @ServerEndpoint(value = "/json" , decoders = JsonDecoder.class , encoders = JsonEncoder.class) public class JsonTest { private static Set<Session> user = new CopyOnWriteArraySet<>(); @OnOpen public void onOpen(Session mySession) { System.out.println("connect ID:"+mySession.getId()); user.add(mySession); } //このメソッドの前にデコードされる @OnMessage public void onMessage(JsonObj obj , Session mySession) { for (Session user : user) { user.getAsyncRemote().sendObject(obj);//送信するものはオブジェクト(送信前にエンコードされる) System.out.println(user.getId()+"番目に"+mySession.getId()+"番目のメッセージを送りました!"); } if(obj.getMsg().equals("bye")) {onClose(mySession);} } @OnClose public void onClose(Session mySession) { System.out.println("disconnect ID:"+mySession.getId()); user.remove(mySession); try { mySession.close(); } catch (IOException e) { System.err.println("エラーが発生しました: " + e); } } }ポイント
1.@ServerEndpoint
にデコーダー及びエンコーダーのクラスを指定。
2. クライアントからデータを受信したとき、@OnMesaage
メソッドが実行される前に指定したデコーダーが実行されJSONからオブジェクトに変換される。
⇒@OnMesaage
メソッドの引数はObj
型となる。
3.sendObject(obj)
メソッドでオブジェクトを送信するとき、クライアントへ送信する前に指定したエンコーダーが実行されオブジェクトからJSONに変換される。
※ 文字列を送信する場合はsendText()
を使用するが、オブジェクトを送信する場合はsendObject()
を使用する。4. オブジェクトクラス
JsonObj.javapackage jsonTest; public class JsonObj { private String type; private String msg; //コンストラクタ public JsonObj() {} //セッター public void setType(String type) {this.type = type;} public void setMsg(String msg) {this.msg = msg;} //ゲッター public String getType() {return type;} public String getMsg() {return msg;} }ポイント
1. このクラスのプロパティはtype
とmsg
の2つ。
⇒ クライアントプログラムのオブジェクト (受信するJSON) に対応したクラスを作成している。
2. デコード後、type
プロパティ値によって送信元を判断する。5. エンコーダー
JsonEncoder.javapackage jsonTest; import javax.websocket.EncodeException; import javax.websocket.Encoder; import javax.websocket.EndpointConfig; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; public class JsonEncoder implements Encoder.Text<JsonObj>{ @Override//初期化は何もしない public void init(EndpointConfig config) {} @Override//エンコード処理 ( オブジェクト → JSON ) public String encode(JsonObj obj) throws EncodeException { ObjectMapper mapper = new ObjectMapper(); String json = ""; try { json = mapper.writeValueAsString(obj); } catch (JsonProcessingException e) { e.printStackTrace(); } return json; } @Override//破棄は何もしない public void destroy() {} }ポイント
1.Encoder.Text<JsonObj>
を実装したクラスを作成。
2.encode()
メソッドの引数はオブジェクト、戻り値はJSONとなる。
3. エンコードにはObjectMapper
クラスのwriteValueAsString()
メソッドを使用する。6. デコーダー
package jsonTest; import javax.websocket.DecodeException; import javax.websocket.Decoder; import javax.websocket.EndpointConfig; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; public class JsonDecoder implements Decoder.Text<JsonObj> { @Override//初期化は何もしない public void init(EndpointConfig config) {} @Override//デコードできるかの判定 public boolean willDecode(String text) { return (text != null); } @Override//デコード処理 ( JSON → オブジェクト) public JsonObj decode(String text) throws DecodeException { ObjectMapper mapper = new ObjectMapper(); JsonObj obj = null; try { obj = mapper.readValue(text, JsonObj.class); } catch (JsonProcessingException e) { e.printStackTrace(); } return obj; } @Override//破棄は何もしない public void destroy() {} }ポイント
1.Decoder.Text<JsonObj>
を実装したクラスを作成。
2.decode()
メソッドの引数は文字列(JSON)、戻り値はオブジェクトとなる。
3. エンコードにはObjectMapper
クラスのreadValue()
メソッドを使用する。実行結果
チャット欄が2つに増加した。
各チャット欄で独立して送受信が可能。
⇒ 複数パスの実現が完了。実行順序
長くなったので、クライアントとサーバーでどのような処理が行われているか記述する。
- HTMLでページを表示。
- ハンドシェイク完了後、ソケット通信開始。
- テキストボックスにメッセージを記述し送信ボタンをクリック。
- JavaScriptによりオブジェクトをJSONにエンコードしサーバーへ送信。
- サーバーでJSONデータを受信。
- デコーダーによりJSONからオブジェクトへデコード。
- デコードされたオブジェクトを引数に
@OnMessage
メソッドを実行。sendObject()
メソッドでオブジェクトを送信。- 送信前にエンコーダーによりオブジェクトからJSONへエンコードし、クライアントへ送信。
- クライアントでデータ受信。
- JSONデータをオブジェクトへデコード。
type
プロパティ値によって送信元を判別し、各送信元へメッセージを表示。その他
改善点
・未知のJSONデータを受信すると、対応したオブジェクトクラスを用意していないためエラーとなる。
⇒ 対応している人はいるので、そのうち調べるかも。結構ややこしそう。・複数の型のJSONデータを受信する場合に未対応。
⇒ そこまで難しくなさそう。デコーダーとオブジェクトクラスを増やしてJSONの中身に対応すればできる。・入れ子構造のJSONに対応するにはどういうオブジェクトクラスを作成すればいいか不明。
⇒ ググれば出るし、必要となれば調べる。・バイナリデータの扱いは理解が進んでいないため、バイナリデータの送受信には未対応。
⇒ 画像の送受信など、必要となれば調べる。感想
思った以上にややこしかった。長くなってしまったので反省。
もっとまとめるべきだった。JavaでJSONを扱うのに、外部ライブラリを使用したりオブジェクトクラスを用意したりと面倒。
JavaScriptなら1行で終わるのにね。Javaのソケット通信でJSONを扱うときに、エンコードクラスやデコードクラスが用意されているのは便利なのかどうなのか。
わざわざエンコーダークラスやデコーダークラスを用意しなくちゃいけないのは、面倒といえば面倒。面倒だが、JSONを使いたいならしょうがない。
JavaでのJSONの扱い方について理解が深まって良かった。
個人制作レベルなら利用していけそう。参考ページ
- 投稿日:2020-06-04T22:52:44+09:00
consoleオブジェクト完全版
consoleオブジェクト
フロントの開発をしてる時、いつもお世話になる
console.log()
先日、console.logでフォーマット指定子を使う!という記事を書きましたが、今回はconsole.log()
以外のconsole.XXX()
について、紹介したいと思います。一覧
関数名 用途 ○:おすすめ
△:非推奨、
非標準assert 引数の妥当性チェック clear コンソールをクリア count 回数をカウント debug デバッグログ出力 ○ dir オブジェクト出力 dirxml オブジェクト出力
※XML形式error エラーログ出力 exception errorの別名 △ group ログをグループ化 groupCollapsed ログをグループ化
※折りたたんだ状態で出力○ groupEnd グループ化終了 info 出力ログに!マーク付与 log 通常のログ出力 ○ profile プロファイル開始 △ profileEnd プロファイル停止 △ table 表形式でログ出力 ○ time 処理時間計測 timeEnd 処理時間計測 timeLog 処理時間計測 timeStamp タイムラインにマーカー追加 △ trace スタックトレース出力 ○ warn 警告ログ出力 (2020/05時点)
※MDN日本語版と英語版で情報に差異があるのですが、更新日が新しい英語版を基にしています。動作確認
それでは、それぞれ動作を見ていきます。
ブラウザはChromeです。
一部Chromeでは対応しておらず、他のブラウザを使用している箇所は都度明記しています。console.assert()
第1引数を評価してfalseの場合、第2引数のメッセージを出力します。
trueの場合は、何も出力されません。
console.clear()
console.count(), console.countReset()
第1引数の値をラベルとして、呼ばれた回数をカウントし出力します。
console.dir(), console.dirxml()
console.dir()
はDOMをJSON形式で、
console.dirxml()
はXML形式で、出力してくれます。
console.log(), console.info(), console.warn(), console.error(), console.debug()
標準的なログを出力します。
関数によって、それぞれログレベルが違います。
Chromeでは、
console.log()
とconsole.info()
が同じでしたので、Safariでも確認しました。
また、一部で「
console.debug()
はChromeで表示されない」との記事を見かけますが、おそらくログの出力レベルがDefaultになっているのだと思います。
console.debug()
はレベルVerboseですが、DefaultではVerboseにチェックがついていないので注意が必要です。
ログを
console.debug()
で出力して、フィルタをVerboseレベルでかければ、必要なログだけ抽出して確認できるので、おすすめです。また、フォーマット指定子(置換文字列)を使用して、出力フォーマットを整形することもできます。
こちらの記事で紹介していますので、参考にしてみてください。
console.logでフォーマット指定子を使う!console.exception()
一応MDNの一覧ページには載ってますが、個別のページは削除されているようです。
console.error()
を使いましょう。console.group(), console.groupCollapsed(), console.groupEnd()
ログをグループ化して出力します。
console.groupCollapsed()
だと初期表示の時に、折りたたまれた状態で出力されます。
(個人的にはgroupCollapsedのほうが好きです)
console.profile(), console.profileEnd()
プロファイルを記録して、各関数の処理性能の計測などができます。
console.table()
第2引数に値を設定すると、出力するプロパティを絞ることもできます。
console.time(), console.timeEnd(), console.timeLog()
処理時間を計測できます。
console.time()
で計測を開始し、
console.timeLog()
で途中経過を出力し、
console.timeEnd()
で計測時間を出力し、計測を止めます。
console.timeStamp()
開発者ツールでPerformanceを計測している間だけ有効になります。
何のために使うのかよくわかっていないです。
めちゃくちゃ小さく表示されるので、ずっと見つからず何も起こらない…と思って、何度もやり直しました。
この画面ショット撮るために2日かかりました…すみません、愚痴です。そもそも古い記事だと開発者ツールのTimelineパネルにTimeStampが表示されるのですが、今はPerformanceパネルに集約されているようです。
下記の記事に出会わなければ、見つけられなかったです。
進化を続けるChrome DevToolsの最新情報 2017console.trace()
スタックトレースを出力し、処理の流れを確認できます。
「この処理どこから呼ばれているんだろう…」というときに使えます。
まとめ
これだけの
console.XXX()
を使いこなせれば、見づらいデバッグログ地獄からも脱却できますね!と言いつつ、結局
console.log()
を打った方が楽だと思ってしまう自分もいますが…参考
- 投稿日:2020-06-04T22:52:44+09:00
【consoleオブジェクト完全版】ログ出力を極める!
consoleオブジェクト
フロントの開発をしてる時、いつもお世話になる
console.log()
先日、console.logでフォーマット指定子を使う!という記事を書きましたが、今回はconsole.log()
以外のconsole.XXX()
について、紹介したいと思います。一覧
関数名 用途 ○:おすすめ
△:非推奨、
非標準assert 引数の妥当性チェック clear コンソールをクリア count 回数をカウント debug デバッグログ出力 ○ dir オブジェクト出力 dirxml オブジェクト出力
※XML形式error エラーログ出力 exception errorの別名 △ group ログをグループ化 groupCollapsed ログをグループ化
※折りたたんだ状態で出力○ groupEnd グループ化終了 info 出力ログに!マーク付与 log 通常のログ出力 ○ profile プロファイル開始 △ profileEnd プロファイル停止 △ table 表形式でログ出力 ○ time 処理時間計測 timeEnd 処理時間計測 timeLog 処理時間計測 timeStamp タイムラインにマーカー追加 △ trace スタックトレース出力 ○ warn 警告ログ出力 (2020/05時点)
※MDN日本語版と英語版で情報に差異があるのですが、更新日が新しい英語版を基にしています。動作確認
それでは、それぞれ動作を見ていきます。
ブラウザはChromeです。
一部Chromeでは対応しておらず、他のブラウザを使用している箇所は都度明記しています。console.assert()
第1引数を評価してfalseの場合、第2引数のメッセージを出力します。
trueの場合は、何も出力されません。
console.clear()
console.count(), console.countReset()
第1引数の値をラベルとして、呼ばれた回数をカウントし出力します。
console.dir(), console.dirxml()
console.dir()
はDOMをJSON形式で、
console.dirxml()
はXML形式で、出力してくれます。
console.log(), console.info(), console.warn(), console.error(), console.debug()
標準的なログを出力します。
関数によって、それぞれログレベルが違います。
Chromeでは、
console.log()
とconsole.info()
が同じでしたので、Safariでも確認しました。
また、一部で「
console.debug()
はChromeで表示されない」との記事を見かけますが、おそらくログの出力レベルがDefaultになっているのだと思います。
console.debug()
はレベルVerboseですが、DefaultではVerboseにチェックがついていないので注意が必要です。
ログを
console.debug()
で出力して、フィルタをVerboseレベルでかければ、必要なログだけ抽出して確認できるので、おすすめです。また、フォーマット指定子(置換文字列)を使用して、出力フォーマットを整形することもできます。
こちらの記事で紹介していますので、参考にしてみてください。
console.logでフォーマット指定子を使う!console.exception()
一応MDNの一覧ページには載ってますが、個別のページは削除されているようです。
console.error()
を使いましょう。console.group(), console.groupCollapsed(), console.groupEnd()
ログをグループ化して出力します。
console.groupCollapsed()
だと初期表示の時に、折りたたまれた状態で出力されます。
(個人的にはgroupCollapsedのほうが好きです)
console.profile(), console.profileEnd()
プロファイルを記録して、各関数の処理性能の計測などができます。
console.table()
第2引数に値を設定すると、出力するプロパティを絞ることもできます。
console.time(), console.timeEnd(), console.timeLog()
処理時間を計測できます。
console.time()
で計測を開始し、
console.timeLog()
で途中経過を出力し、
console.timeEnd()
で計測時間を出力し、計測を止めます。
console.timeStamp()
開発者ツールでPerformanceを計測している間だけ有効になります。
何のために使うのかよくわかっていないです。
めちゃくちゃ小さく表示されるので、ずっと見つからず何も起こらない…と思って、何度もやり直しました。
この画面ショット撮るために2日かかりました…すみません、愚痴です。そもそも古い記事だと開発者ツールのTimelineパネルにTimeStampが表示されるのですが、今はPerformanceパネルに集約されているようです。
下記の記事に出会わなければ、見つけられなかったです。
進化を続けるChrome DevToolsの最新情報 2017console.trace()
スタックトレースを出力し、処理の流れを確認できます。
「この処理どこから呼ばれているんだろう…」というときに使えます。
まとめ
これだけの
console.XXX()
を使いこなせれば、見づらいデバッグログ地獄からも脱却できますね!と言いつつ、結局
console.log()
を打った方が楽だと思ってしまう自分もいますが…参考
- 投稿日: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:00:40+09:00
はじめてのポートフォリオ(技術<制作過程)
プログラミングを始めて半年を迎えます、上野栞音です。
スクールでは主にRailsアプリの作り方を教わり、現在は株式会社Wilicoでインターンとしてお世話になりながら就活中です。
プログラミングも字書きも不慣れなもんで、至らぬ点がありましたら教えてください。今回はスクール3ヶ月目のフェーズで作成したポートフォリオについて、雑多にはなりますが色々と書き留めていこうと思います。
ポートフォリオ概要
ToT
github
※かんたんログイン("Signin as a trial user")実装済み。開発コミュニティ向けのQAサイトを想定して制作しました。
詳細はgithubのREADMEに。開発経緯と目的
今回学習したかったことは大きく分けると2つ。
ユーザーのアクションを経て、どのようなデータが集められるのか。
そのデータを基にどのような分析結果を返せるのか。もともと、ユーザーに評価を付与→チャートで表示の機能は絶対に実装しようと決めていました。
きっかけとしてはAI(データの統計や分析)に興味が湧いたからなので、最初はPythonを使って何かしようと考えていたのですが却下。
理由としては、
- 初学者が1ヶ月という期限付きで新しい技術を得てアプリを作っても、強みのあるポートフォリオにはならない気がしたから。
- 当時教わっていたRailsについての理解が明らかに浅く、その理解度のまま別の技術に手を出すのが腑に落ちなかったから。
そんなこんなで いくつかサイトをチラ見しつつアプリの企画を練りました。
QAサイトの制作に至った理由としては、評価基準の設け方がパッとイメージできたからです。DB設計
静的(不変的)なデータのみを保管するようにするのがベスト!
というのが今回の学びです。開発はSQLite3、本番はMySQLで実装しています。はじめに考えていたもの
ユーザーの評価はユーザーテーブルに、それ以外の評価もコメントやスレッドのアクションに紐づけてアップデートする予定でしたが、途中でこんがらがってスクールのメンターさんに相談。
諸々差し置いて問題点として大きかったのが、この設計だとユーザーが何か操作するたびにアップデートが掛かってしまうため、正常にアップデートされない可能性があることです。(回避方も色々あるみたいですが、もっと掘り下げたいので省略)後々算出できるデータやユーザーの動作に依存して頻繁にデータが更新されるものは、理由がない限りDBで管理しないようにするため、分析するための材料だけ保管してチャートを描画するタイミングで算出する設計にしました。
最終的な設計
赤い部分をごっそり消しましたが、時間系の評価については他の評価に比べて算出するステップが一つ多い(2つのテーブルに登録されている登録日を基に差分を算出 → その差分を基に平均値算出)こともあり、集計のスピードを上げるために用途を変えてテーブルを残しています。
基本的な機能の実装
チャート実装を除いた部分です。チャートに時間が割きたかったこともあり、検索機能以外を1週間くらいでスケジュールを組んで実装しました。
さして難しい機能は実装してないのでアピールポイントを挙げると、
タグ付け機能と検索機能はgemを使わずに実装しました
改めて調べてみると「gemで出来たのでは…?」なんて思いますが、いい運動(?)になったので結果オーライ
以下備考
- タグ付け機能
- 同じ意味なのに違う表記(rails, Railsみたいな)のデータが入ると分析の精度が下がってしまうため、新規タグ作成の動作を重くしたかった。
- 基本1つのtext_fieldにカンマ区切りで書き込むようなやり方しか見つからず軽いなぁと悩んだあげく、普通に中間テーブルで結んでフォーム作った方が慣れたやり方だし早そうだと判断。
- 実装後、Qiitaのタグ付け機能を見て目ぇひん剥きました。なるほど。この手があったか。なるほど…
- 検索機能
チャート実装
開発経緯の通り、
今できる事→これやりたい!軸で企画を立ててここまで来たので、為せば成る精神で詳しい実装の目論見はほぼありませんでした。
調べてみるものの、当時jsに馴染みが無さ過ぎて悶絶寸前。<何をどうすれば これが出来るんですか…?
自分で考えたアプリのくせに、ここに来て教室の隅で静かに絶望してました。この時の学びとして大きかったのは、
分からない、初めて触るものは一度触ってみる大切さ何も分からず嘆いていた最中、スクールの同期生に相談したらchart.jsのcodepenを教えてくれました。
ここで少し触ってみた瞬間、chart.jsが面白いほど読める。要因としては、すでに完成しているコードを触れることが大きかったと思います。
どの値がどの軸のデータに影響しているかや、どの値がどのデザインに影響しているかなどが直観的に分かり、ここに配列渡せば勝ちじゃん!とゴールを定める事が出来ました。ちょっと無謀にも思いますが、試行錯誤も含めて工数を割くために基本的な機能 頑張ったので潔く実装に移って良かったなと思います(結果論)。
いざ、尋常にチャート実装
jsファイルとのデータの受け渡しは、gem 'gon'で行っています。
json形式に変換するのが基本ですが学習目的に含まれていないのと、これから実装するチャートの工数が読みきれずスピードを重視したかったので採用しました。大体こう。
- UserModelのロジックでチャートに渡す配列を計算するメソッド作成、Controllerで呼び出す
- UsersControllerでgonに渡す
- Viewでgonのタグ→jsファイルに渡す
- jsファイル→canvasタグに渡してチャート描画
※自分が流れを掴むために搔い摘んだものです。gon周りは特にもう少し検証しながら理解を深めたい。
UserModelのロジックは、大体こう。
コードがぼちぼち長いため、流れだけ伝わりますようにといった感じで書きます
代わりと言っては何ですが行単位でGithubのリンクを貼るので、気になる方はご覧ください。
評価基準ごとにまとめます。Questioner/Tags, Answer/Tags
ドーナツチャートの2項目です。少しデータを引っ張るロジックが違うだけなので、Questioner/Tagsを例にします。
Time to response, Time to solved, Total
バーチャートとレーダーチャートの項目です。
データを引っ張ってくるテーブルが違うだけなので、Time to responseを例にします。
ドーナツチャート以外は基本この流れです。細かい処理は省きます。
- 全ユーザーの平均値を算出
・ユーザーの動作に依存してグラフの階級を変えるため、このデータを基に基準になる値を算出します。- チャートの諸々を決めるのに使う値を算出
・返すのは各ユーザー平均値の [ 最小値, 最大値, それを基にした階級幅 ]。
・投稿されたIssueが1つも無い場合はfalseで返して例外処理。- 2を基に境界値を算出
・〇秒~〇秒のユーザーはスコア1、〇秒~〇秒のユーザーはスコア2… の〇だけ入ったような配列です。- 3の境界値を基に
- 各階級に何人ユーザーが含まれるかを算出して配列を作りながら
- 各階級に対象のユーザーが含まれるか否かを0,1で算出して配列を作る
- 6の配列を基に、ユーザーのスコアを算出
・3点の場合、[0,0,1,0,0,0,0,0,0,0] → each_with_indexで回すと2番目の値が1になる → 2+1でスコア算出- 5,7をControllerに返す
- ※レーダーチャートの配列だけControllerで作ります。
・ ControllerからModelのメソッドを呼び出す際、Model上に配列を作ろうとすると呼び出すたびに配列がリセットされるためです。
・[ 5(チャートに渡す値), 7(ユーザーのスコア) ] の配列が返り値なので、この配列の[1]を拾って配列を作ります。ほか
Time to response とほぼ同じなので、差分だけまとめます。
1.平均値
平均値が割り出せるほど絶対値が大きくなかったため合計値を算出しています。チャートの値が0ばかりになって変わり映えしなかった3. 階級の算出
階級の誤差をスコア1に寄せるため、呼び出すメソッドを変えています。
評価基準によってスコアが高くなる条件が分岐するのが肝で、
Time to ~ → 1.平均値算出の結果が低いと高スコア → calculate_evaluation_datas_sort_by_max
それ以外 → 1.合計値算出の結果が高いと高スコア → calculate_evaluation_datas_sort_by_min
大きく違う点としては、階級を決める基準が 最大値 → 最小値 であること(こちら基準で命名してます)と、配列をreverseするタイミングです。リファクタリング
制作期間が 2/15~3/15 くらいだったのですが、3月頭にβ版をデプロイして色んな方にレビューを頂きました。ありがとうございました
UserModelのメソッド
1. 最初は全てControllerに記述していたメソッドを
2. Modelに移行して
3. 共通するロジックをメソッド化(最新版)Viewの描画まわり
1. HTMLの部分テンプレートでscriptタグをrenderしていたのを
2. jsファイルに移行して(リンクはcomment_tags)
3. 一つのファイルにまとめて共通するロジックをメソッド化(最新版)チャートまとめ
このポートフォリオにおいて最大の学びでもあるのですが、
手を動かせば必ず答えは見つかる!
と確信を得られた制作物でした。
今扱えるパラメータを読み、それを基にロジックを組み、足りなければパラメータを送る。これを自分自身の経験をもって得られたのはとても貴重な学びだったなと思います。全てのチャートを最低限実装するまでの1週間くらいはコンソール画面にかじりついて模索する日々でしたが、ロジックができた瞬間の達成感が最高すぎて何だかんだ楽しかったです
さいごに
ポートフォリオについて調べていると「こういうのが有利!」ばかりで自分と同じレベル感の記事が上手いこと見つからず、望んでいた判断材料では無かったので書いてみました。
もちろん まだ改善の余地があるアプリとは思いますが、キリがないので一旦区切りにしようかなと思います。
これからポートフォリオを制作する方の目に留まり、少しでもインスピレーションの助けになれば幸いです
あと色んな方のこんな感じの記事見たいので是非書いてくださいありがとうございました!
参照
Ransackで簡単に検索フォームを作る73のレシピ
railsで複数ワードでの検索機能(or)とマイナス検索機能(-)を実装してみる
chart.jsのcodepen
gem 'gon'
- 投稿日:2020-06-04T20:15:30+09:00
microCMSのプレビュー機能をGatsby.jsで使う
Gatsby.jsは言わずと知れたReact製の静的サイトジェネレーターですが、ビルド時にページを生成するという性質ゆえに、microCMSのプレビュー機能を使うにはひと工夫必要だったので共有したいと思います。
この機能をご存知の方はコードの部分からご覧ください。microCMSの記事プレビュー機能について
microCMSのプレビュー機能について少し説明しておくと、記事執筆画面で
公開ボタンの左にある画面プレビューを押した時に、執筆中の記事が実際にはどのように表示されるか確認できる機能です。仕組みとしては、という感じです。
詳しくはhttps://microcms.io/blog/draftkey_and_preview/ に掲載されています。何が問題でどうしたらいいのか?
この機能をGatsbyで使用する時、考えなければならないのは、Gatsby.jsの記事データは基本ビルド時に生成された物なので、
「画面プレビュー」クリック時に送られてきたcontentId
とdraftKey
を基に記事データをリアルタイムに取得して、表示する仕組みを作らないといけない。ということです。ということでコード
前置きはそのくらいにしてコードはこのようになりました。
pages/previewPage.jsximport React, { useState, useEffect } from "react"; import { getSearchParams } from "gatsby-query-params"; //...その他コンポーネントなど function previewPage() { //microCMS側に設定するurlはhttps://xxxxxx.com/previewPage/?contentId={CONTENT_ID}&draftKey={DRAFT_KEY}みたいな感じ const queryParams = getSearchParams(); //queryParamsには //{contentId:"xxxx", //draftKey:"xxxx"} //というようなデータが入ります。 const contentId = queryParams.contentId; const draftKey = queryParams.draftKey; const [postData, setPostData] = useState(null);//最初、postDataにはnullが入ります。 useEffect(() => { if (!postData) { fetch( `https://xxxxxxxxx.microcms.io/api/v1/blogs/${contentId}?draftKey=${draftKey}`, { headers: { "X-API-KEY": "xxxxxxx-xxxx-xxxxx-xxxx-xxxxxxxx", }, } ) .then((response) => { if (response.ok) { return response.json(); } }) .then((json) => { postData = setPostData(json);//ここで、postDataに取得したコンテンツが格納されます。 }); } else { return function cleanup() { console.log("done"); }; } }); return ( <div dangerouslySetInnerHTML={{ __html: postData ? postData.sentence : "", }} ></div> ); } export default previewPage;このようなページを作成することで、アクセスした時にurlクエリパラメータの情報を基にコンテンツをとってきてくれます。
以下、詳しく説明していきます。API部分について
microCMSはPOSTとGETのAPI-KEYが違うのでGETのAPIが露出しても大して問題はないですが、なんとなく気持ちが悪いので、
X-API-KEY: "xxxxx-xxxx"としている部分とmicroCMSのurl部分は実際には環境変数に置き換えるのが望ましいです。環境変数の使い方に関してはこちらの記事の中でも説明しているので是非ご覧ください。
gatsby-query-params
について2行目に登場する
gatsby-query-params
はこちらです。
使用するにはターミナルでnpm i gatsby-query-params
してください。コメントアウト部分にもありますが、
getSearchParams
によって、urlクエリパラメータの情報をqueryParams
にオブジェクト形式で格納しています。
useState
useEffect
についてこれらはReactのhookで、詳しい説明はこちらにあります。
useEffect
の中で、↑で取得したクエリパラメータを基にmicroCMSにデータを取得しにいき、取得できたら、それをpostData
に格納しています。
responce.ok
についてこれがないとmicroCMSのレスポンスが404の時(useStateがnullの間)返ってくる空文字列
""
を処理しようとしてしまい、jsonオブジェクトでないのでエラーが出てしまいます。レンダリング部分について
return()
内のpostData ? postData.sentence : ''
としている部分で、postDataに↑でとってきた記事データが格納されたら、それが表示されるようになっています。postData.sentence
と書いていますが、sentence
の部分はmicroCMSで定義した表示したいデータのフィールドIDが入ります。レンダリング部分は他の記事ページのレイアウトに合わせて、煮るなり焼くなりしてください!まとめ
サイトを見にきた人が、
/今回作ったページ
と打つとアクセスできてしまいますが、contentsIdとdraftKeyがないので何も表示されないですし、一応は問題ないと思います。色々と改善の余地はありそうですが、とりあえずこれで投稿前の記事をプレビューで確認できるので、是非活用してみてください!
- 投稿日:2020-06-04T18:33:43+09:00
Chart.jsで横スクロール可能なグラフを作る
概要
Chart.jsでは普通にグラフを作ると画面の幅に合わせて1つのデータ当たりのサイズをレスポンシブに調整して表示してくれます.
とても便利ですが,データの量が多いとかなり見づらくなってしまいます.
そこでデータ1つ当たりの横幅を固定して画面に収まりきらない分は横スクロールで見えるようにしようと思いましたが,かなり苦労したのでメモを残します.参考になれば幸いです.
完成図
今回は棒グラフが必要だったので,棒グラフを作成しました.
他の種類のグラフは試していませんが,データ構造が同じようなグラフなら上手くいくと思います.
図の下にスクロールバーが描画され,スクロールバーを動かすとスクロールすることができます.
(右の方はラベルしか描画されていませんが,値を入れなかっただけなので気にしないでください.)実行環境
ブラウザ: Google Chrome
Chart.js バージョン: 2.9.3
CDNを読み込んで使っています<head> <script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.3/dist/Chart.min.js"></script> </head>HTML
まずhtmlがこちらです.
<div class="chartWrapper" style="position: relative; overflow-x: scroll;"> <div class="chartContainer" style="height:200px;"> //高さは好きに設定 <canvas id="chart" style="position: absolute; left: 0; top: 0;"></canvas> </div> </div>canvasを2つのdivで囲っています.
Javascript
続いてJavasciptがこちらです.
var xAxisLabelMinWidth = 15; // データ当たりの幅を設定 var data = [12, 19, 3, 5, 2]; var width = data.length*xAxisLabelMinWidth; // グラフ全体の幅を計算 document.getElementById('chart').style.width = width+"px"; // グラフの幅を設定 document.getElementById('chart').style.height = "200px"; //htmlと同じ高さを設定 var myChart = new Chart(document.getElementById('chart').getContext('2d'), { type: 'bar', data: { labels: ['a', 'b', 'c', 'd', 'e'], datasets: [{ label: 'sample data', data: data, }] }, options: { responsive: false, //trueにすると画面の幅に合わせて作図してしまう } });データ当たりの幅をまず設定して,そこからグラフ全体の幅を計算しています.
これによりデータごとの描画サイズを固定することができます.完成
以上のコードで横スクロール可能な図を作成できると思います.
参考
https://stackoverflow.com/questions/39473991/how-to-make-a-chart-js-bar-chart-scrollable
https://stackoverrun.com/ja/q/10874652これらを参考にしましたが,自分の環境ではうまくいかなかったので修正しています.
- 投稿日:2020-06-04T16:30:21+09:00
[JS1日クッキング]APIサーバーをCircleCIで自動テスト
何かを簡単に作って、ちょっとした勉強になる。そんなシリーズになる予定のものの第3回です。
今回は、シンプルなAPIサーバーをCircleCIで自動テストをします。テストは前回にしてあるものを使います。
完成品はこちら -> sequelize-todo-api-server
材料
- ユニットテストをした前回のサーバー
- CircleCI
- jest-junit
作り方
1. テストメタデータの設定
テストの結果をテストメタデータとしてファイルへ保存できるようにします。CircleCIでテストした後、テストメタデータをCircleCIへアップロードすると、テスト結果がCircleCIのダッシュボードで確認できるようになります。
Jestのテストメタデータを作成するために、jest-junitをインストールします。
npm install -D jest-junitjest.config.js というファイルを作り、設定を書いていきます。
jest.config.jsmodule.exports = { reporters: ["default", ["jest-junit", { outputDirectory: "reports/jest" }]], };ここまでできたら、
npm run test
でテストを実行すると、reports/jestディレクトリ内にjunit.xmlが生成されるようになります。この中にテストメタデータが入っています。2. CircleCIの設定ファイルの用意
npm-scriptに
"test:ci": "export NODE_ENV=test && npx jest --ci --runInBand"を加えます。
--ci
と--runInBand
がCIでJestを使うときに必要になります。CircleCIで使用する設定ファイルを用意します。設定ファイルは、.circleciディレクトリ内のconfig.ymlに書きます。
.circleci/config.ymlversion: 2.1 jobs: build: docker: - image: circleci/node:lts - image: circleci/mysql:8-ram environment: MYSQL_USER: sequelize MYSQL_PASSWORD: sequepass MYSQL_DATABASE: database_test steps: - checkout - restore_cache: name: キャッシュの読み込み key: dependency-cache-{{ checksum "package-lock.json" }} - run: name: パッケージをインストール command: npm install - save_cache: name: キャッシュを保存 key: dependency-cache-{{ checksum "package-lock.json" }} paths: - node_modules - run: name: db を待機 command: dockerize -wait tcp://localhost:3306 -timeout 1m - run: name: JUnit をレポーターとしてテストを実行 command: npm run test:ci - store_test_results: name: テスト結果を保存 path: reportsjobsのbuild内に、使うコンテナと作業を記述していきます。
dockerキーに使用するコンテナを指定します。最初に使うイメージがコマンドを実行するコンテナになります。なので、Node.jsのコンテナを最初に書き、続いてDBのイメージを書きます。DBの設定は環境変数を使用して設定します。circleci/mysqlの環境変数は、MySQLの公式イメージと同じです。
ここでは、sequelizeで設定したユーザー名とパスワードだけでなく、使用するデータベースも設定します。使用するデータベースを設定しないとアクセスが拒否されます。
stepsキーに処理を順番に書いていきます。主に、
- checkoutでリポジトリからデータをダウンロード
npm install
でライブラリのインストールdockerize -wait tcp://localhost:3306 -timeout 1m
でDBが準備できるまで待つnpm run test:ci
でCI用のテストを実行- store_test_resultsでテストメタデータをCircleCIへアップロード
ということをしています。
3. CircleCIへリポジトリの登録
CircleCIで自動テストができるようにします。CircleCIに登録をした後、「Projects」のページに移動します。
「Set Up Project」をクリックします。
「Start Building」をクリックします。
新しいブランチ作って、そこにデフォルトの設定ファイルを加えるか尋ねられますが、今回は自分で用意したものを使うので、「Add Manually」をクリックします。
「Start Building」をクリックします。これで、CircleCIで使用するリポジトリの設定ができました。
4. 自動テストをする
では、実際に自動テストをしましょう。Githubにプッシュすると、自動的にCircleCIが処理を始めます。
「Piplines」で、CircleCIの処理中の様子や結果をみることができます。
処理が終わった後、「build」をクリックすると、以下のように処理結果をみることができます。
「TESTS」をクリックすると、テストメタデータからテスト結果を表示してくれます。
テストが失敗しているときは、以下のようにテストメッセージが表示されます。
5. ステータスバッヂの表示
CircleCIのステータスバッヂをGithubに表示することができます。CircleCIのステータスバッヂは、以下のようなものです。
これは、
[![CircleCI](https://circleci.com/gh/[Githubアカウント]/[リポジトリ]/tree/[ブランチ].svg?style=svg)]([画像のリンク先(大抵はGithubのブランチ)])の中の[]を全部埋めると、MDファイルにステータスバッヂを表示できます。例えば、今回使ったブランチだと、
[![CircleCI](https://circleci.com/gh/kei-lvngbk/sequelize-todo-api-server/tree/ci-test.svg?style=shield)](https://circleci.com/gh/kei-lvngbk/sequelize-todo-api-server/tree/ci-test)のようになります。これをREADME.mdに含めて、Githubでステータスバッヂを表示しています。
詳しくは下のページを参考にしてください。
Adding Status Badges - CircleCI
おわりに
CircleCIで自動テストをしました。workflowを使ってテスト成功後にデプロイをすることもできるので、使いこなせれば楽ができそうです。Netlifyもそうですけど、設定すればGithubにプッシュすれば自動に何かやってくれます系のサービスはとても便利ですね。
コード -> sequelize-todo-api-server
- 投稿日:2020-06-04T16:01:51+09:00
【array-foreach-async】forEach()でasync/awaitを使おうとして失敗した皆へ
AWS Lambda関数でNode.jsを使用し、forEach()でasync/awaitしたい状況に陥ったのですが、コードを書いて実行してもawaitの文が実行されていませんでした。色々調べたところ、Node.jsの
array-foreach-async
というライブラリを利用することで簡単に解決することができたので、まとめていきます。環境
- macOS
- AWS Cloud9
- Node.js 12
修正前の実行コード
今回の説明において不要なコードは省略しています。
const env = process.env const requestPromise = require('request-promise'); const AWS = require('aws-sdk'); const DB = new AWS.DynamoDB.DocumentClient(); exports.handler = async(event, context) => { // (省略) event.Records.forEach(async(record) => { // ここでasync // (省略) const data = await DB.put(dbParams).promise(); // 1つ目のawait console.log(data); // (省略) const req = await requestPromise.post(options); // 2つ目のawait console.log(req); }); return "success!" };実行すると、出力には
success!
のみが表示され、console.logは無視されます。forEach()がawaitの処理完了を待たずに終了し、Lambda関数が閉じてしまっているためです。どちらか一方のawaitをコメントアウトしても結果は変わらず。forEach()ではasync/awaitが効いていないことが分かります。
そもそもArray.prototype.forEach()
自体がasync関数ではないため、引数のcallback関数にasync/awaitを付けたところで無意味ということです。修正後の実行コード
Node.jsの
array-foreach-async
というライブラリをインストールすることで、forEach()をasync関数にしたforEachAsync()
を使用できるようになります。
これを使えば、forEach()と同じ動作でasync/awaitを実現できます(参考:array-foreach-async - npm)。まずはライブラリをインストールします。
npm install array-foreach-asyncソースコードは次のようになります。
const env = process.env const requestPromise = require('request-promise'); const AWS = require('aws-sdk'); const DB = new AWS.DynamoDB.DocumentClient(); require('array-foreach-async'); // ライブラリの読み込み exports.handler = async(event, context) => { // (省略) await event.Records.forEachAsync(async(record) => { // 変更箇所 // (省略) const data = await DB.put(dbParams).promise(); console.log(data); // (省略) const req = await requestPromise.post(options); console.log(req); }); return "success!" };
forEach
をforEachAsync
に変更し、文の先頭にawaitを付けます。
実行すると、awaitの文が両方ともきちんと実行され、ログの出力を確認できました。補足
ずっとforEach()を使うことしか考えていなかったため後から気が付いたのですが、そもそもforEach()の代わりに
for〜of
を使用すれば解決できました。JavaScriptに慣れていなさすぎる...
修正前の実行コードを次のように変更すればOKです。const env = process.env const requestPromise = require('request-promise'); const AWS = require('aws-sdk'); const DB = new AWS.DynamoDB.DocumentClient(); exports.handler = async(event, context) => { // (省略) for(const record of event.Records) { // (省略) const data = await DB.put(dbParams).promise(); // 1つ目のawait console.log(data); // (省略) const req = await requestPromise.post(options); // 2つ目のawait console.log(req); }); return "success!" };コードも簡潔であり、わざわざ
array-foreach-async
をインストールする必要がないので、どうしてもforEach()を使わなければならない状況以外では、素直にfor〜of
を使う方が良いかもしれませんね。ご参考までに。
- 投稿日:2020-06-04T14:44:45+09:00
並列処理数を指定できてキャンセルできる Promise.all の実装
実装
import type { AbortSignal } from 'abort-controller' export const DOMException = ('undefined' !== typeof window && window.DOMException) || ('undefined' !== typeof self && self.DOMException) || class DOMException extends Error { constructor(message: string, name: string) { super(message) this.name = name } } type Options = number | { concurrency: number; signal?: AbortSignal } export const promiseAllWithConcurrencyLimit = async <T>(promises: (() => Promise<T>)[], options: Options) => { const opts = 'number' === typeof options ? { concurrency: options } : options const { concurrency, signal } = opts const results: T[] = [] let idx = 0 await Promise.all(Array.from({ length: concurrency }).map(async () => { while (true) { if (signal?.aborted) return Promise.reject(new DOMException('aborted', 'AbortError')) const cur = idx++ const task = promises[cur] if (!task) return results[cur] = await task() } })) return results }記事を書くまでの経緯
以前、並列処理数を指定できる Promise.all の実装という記事を書きました
長時間かかるタスクの実行中になんらかの要因でやっぱり中止したいと思うかもしれません
fetch の仕様を参考にキャンセルできるようにしたのが上記になります使用例
const controller = new AbortController() const onCancelButtonClicked = () => { controller.abort() } try { await promiseAllWithConcurrencyLimit( userIds.map(id => async () => fetchUserById(id)), { concurrency: 3, signal: controller.signal } ) } catch (err) { if ('AbortError' !== err?.name) throw err }
- 投稿日:2020-06-04T14:15:30+09:00
LaravelさんはどうやってAJAXかどうか区別してResponseでJSON返してくるの?
我らがBOOM TECH CAFEの若きエース、 @miriwo さんの最新記事「Laravel API データポスト時のバリデーションで弾かれた後、http://127.0.0.1:8000/にリダイレクトしてしまう」を読んで、「おや? ワイのLaravelちゃんはAJAXでアクセスすると普通にエラーのJSON返してくるんやが……?」と思って、調べてみました。
そもそもAJAXかどうかどうやって判定しているの?
Laravelの公式ドキュメント日本語訳にはこんなふうに書かれてるわけですよ。
https://readouble.com/laravel/5.5/ja/validation.html※一応、Laravel5.5での話をしていますが、後方バージョンでもだいたい同じだと思います。
伝統的なHTTPリクエストの場合は、リダイレクトレスポンスが生成され、一方でAJAXリクエストにはJSONレスポンスが返されます。
ほうほう、そうなんすね。
んで、AJAXリクエストか伝統的なHTTPリクエストかどうかはどこで判別してるねんと言うと、ここなわけです。vendor/laravel/framework/src/Illuminate/Http/Request.php(226-234)/** * Determine if the request is the result of an AJAX call. * * @return bool */ public function ajax() { return $this->isXmlHttpRequest(); }vendor/symfony/http-foundation/Request.php(1820-1833)/** * Returns true if the request is a XMLHttpRequest. * * It works if your JavaScript library sets an X-Requested-With HTTP header. * It is known to work with common JavaScript frameworks: * * @see http://en.wikipedia.org/wiki/List_of_Ajax_frameworks#JavaScript * * @return bool true if the request is an XMLHttpRequest, false otherwise */ public function isXmlHttpRequest() { return 'XMLHttpRequest' == $this->headers->get('X-Requested-With'); }なるほど、つまりRequest Headerに
X-Requested-With: XMLHttpRequest
ってのが入ってたらAJAXリクエストとして扱うってわけね……え、そんなHeaderどこで入れてたっけ……?そして我々は
X-Requested-With
を求めて旅に出たソースグレップするとこんなところにいるのを見つけます。
resources/assets/js/bootstrap.js(16-24)/** * We'll load the axios HTTP library which allows us to easily issue requests * to our Laravel back-end. This library automatically handles sending the * CSRF token as a header based on the value of the "XSRF" token cookie. */ window.axios = require('axios'); window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';なるほど、axiosのHeaderにデフォルトで入るようにしてるのね……え、でもBootstrap使ってないんやが……
そこで、肝心のAJAXしてるjsをのぞいてみると……require('./bootstrap');バッチリrequireしてたーーーーーー!!
やー、無意識って怖いっすね。今回はいい勉強になりました。
- 投稿日:2020-06-04T14:15:30+09:00
LaravelさんはどうやってAJAXかどうか区別してバリデーションエラーレスポンスでJSONを返してくるの?
我らがBOOM TECH CAFEの若きエース、 @miriwo さんの最新記事「Laravel API データポスト時のバリデーションで弾かれた後、http://127.0.0.1:8000/にリダイレクトしてしまう」を読んで、「おや? ワイのLaravelちゃんはAJAXでアクセスすると普通にバリデーションエラーのJSON返してくるんやが……?」と思って、調べてみました。
そもそもAJAXかどうかどうやって判定しているの?
Laravelの公式ドキュメント日本語訳にはこんなふうに書かれてるわけですよ。
https://readouble.com/laravel/5.5/ja/validation.html※一応、Laravel5.5での話をしていますが、後方バージョンでもだいたい同じだと思います。
伝統的なHTTPリクエストの場合は、リダイレクトレスポンスが生成され、一方でAJAXリクエストにはJSONレスポンスが返されます。
ほうほう、そうなんすね。
んで、AJAXリクエストか伝統的なHTTPリクエストかどうかはどこで判別してるねんと言うと、ここなわけです。vendor/laravel/framework/src/Illuminate/Http/Request.php(226-234)/** * Determine if the request is the result of an AJAX call. * * @return bool */ public function ajax() { return $this->isXmlHttpRequest(); }vendor/symfony/http-foundation/Request.php(1820-1833)/** * Returns true if the request is a XMLHttpRequest. * * It works if your JavaScript library sets an X-Requested-With HTTP header. * It is known to work with common JavaScript frameworks: * * @see http://en.wikipedia.org/wiki/List_of_Ajax_frameworks#JavaScript * * @return bool true if the request is an XMLHttpRequest, false otherwise */ public function isXmlHttpRequest() { return 'XMLHttpRequest' == $this->headers->get('X-Requested-With'); }なるほど、つまりRequest Headerに
X-Requested-With: XMLHttpRequest
ってのが入ってたらAJAXリクエストとして扱うってわけね……え、そんなHeaderどこで入れてたっけ……?そして我々は
X-Requested-With
を求めて旅に出たソースグレップするとこんなところにいるのを見つけます。
resources/assets/js/bootstrap.js(16-24)/** * We'll load the axios HTTP library which allows us to easily issue requests * to our Laravel back-end. This library automatically handles sending the * CSRF token as a header based on the value of the "XSRF" token cookie. */ window.axios = require('axios'); window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';なるほど、axiosのHeaderにデフォルトで入るようにしてるのね……え、でもBootstrap使ってないんやが……
そこで、肝心のAJAXしてるjsをのぞいてみると……require('./bootstrap');バッチリrequireしてたーーーーーー!!
やー、無意識って怖いっすね。今回はいい勉強になりました。
- 投稿日:2020-06-04T13:54:27+09:00
Javascript用語集
5-3イメージの切り替え
マッチする要素を全て取得する
document.querySelectorAll('CSSセレクタ') 例:const thumbs = document.querySelectprAll('.thumb');配列の各項目を繰り返し処理する(forEachメソッド)
配列.forEach(function(item,index){ 処理内容をここに書く });Javascriptでdata-なんでも属性の値を切り替える
<タグ名 data-A(自由に決めて良い) = "img1.jpg"> 取得した要素.dataset.A item.onclick =function(){ console.log(this.dataset.image); }属性の書き換えについて
属性の値を読み取る
取得した要素.属性属性の値を書き換える
取得した要素.属性 = 値;
- 投稿日:2020-06-04T13:42:07+09:00
【初心者向け】jQueryはじめの一歩
はじめに
理解せずに進めていってしまいそうなので、基本に立ち返るための忘備録
基本はjQuery 日本語リファレンスに書いてあることだけど、用語が全然わからない人(自分が筆頭)でも理解できるように書くつもり。ただひとつ、jQueryは覚えてなくてもググればいいというのは確実なので、その前段階にいる人向けの文章です。
jQueryとは
jQuery 日本語リファレンスによれば
JavaScriptのコーディングを強力に支援するライブラリ
らしい。分かるような分からないような日本語だなあ。
私自身はざっくり「JavaScriptを少ない記述で簡単に使えるようにいろいろなコードがまとめられたもの」だと解釈しています。もう少しエンジニアっぽい表現をすると、「JavaScriptのさまざまな機能を簡単に実装できるコード集」あたりでしょうか。
つまりやっていることはJavaScriptのプログラム(クリックすると色が変わったり、マウスをあてると大きくなったりなど)だけど、いちいちコードをイチから考えて記述するのが面倒だから、最初からある程度の形を用意しておいてそこから読み込めるようにしたものがjQueryの正体です。jQueryを使う手順
まずはjQueryを使う場所の確認。
- HTMLファイル、cssファイル、JavaScriptファイルを用意
- HTMLファイルにjQueryを読み込む
- JavaScriptファイルにjQueryコードを記述
おそらくファイルはHTMLとJavaScriptのみでも大丈夫だけど、メソッドの中にはcssプロパティを変化させる
.css()
というのもあるので一応あるほうがベター。jQueryの始め方
いよいよ本題!とは言え始めるといっても、説明した通りjQueryは読み込むものなので読み込めばほとんど終わり。
- 公式サイトからjQueryファイルをダウンロードして読み込む
- Web上のjQueryファイルを読み込む
方法としてはこの2パターンのどちらかだけど、2番目のWeb上のものを読み込む方法(CDNと言うらしい)のほうが手軽。私もこちらを習いました。
一応どちらもやり方は記述します。公式サイトからjQueryファイルをダウンロードして読み込む
ダウンロードページからダウンロードして、jQueryを使いたいファイル内の</body>直前に以下を記述。
このファイルでjQueryを使う.html<script src="jQueryの保存先ファイル名/jquery-min.js"></script>Web上のjQueryファイルを読み込む
jQueryを使いたいファイル内の</body>直前に以下を記述。
このファイルでjQueryを使う.html<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>余談1
記述場所が</body>直前の理由。
2つとも記述場所(=読み込む場所)は別にどこでも良いけど一旦ページの表示内容をすべて読み込んでからjQueryを読み込むほうが、ユーザーがjQueryの読み込み時間を気にすることなくページを閲覧できる、ということらしい。なるほどね。
たいていjQueryの発動場所は「このボタンをクリックしたら」とか「ここにカーソルが当たったら」とかなので、ユーザーがページを読んでいるうちに読み込めばよいと。余談2
ファイルの保存先(保存されている場所)のことをファイルのパス、ファイルパス、パスなど呼称する。やっているうちに覚えたのであんまり気にしなくていいと思います。
jQueryを使ってみる--基本の型
sample.js$(function(){ //ここにメインの処理を記述 }); jQuery(function(){ //ここにメインの処理を記述 });基礎の基礎はこれ。
$
とjQuery
は同じ意味。$
が省略版という立ち位置らしいけどまあこっち使うよねという。
以下の記述も$
で統一します。sample.js$(function(){ $("セレクタ").メソッド("引数"); });基礎はこれ。jQueryの中身は3つの要素で構成されている。
セレクタ
:どこが変化するかの場所を示す。メソッド
:どんな動作をするのかを示す。引数
:メソッドの詳細を指定。例
sample.js$(function(){ $("h1").css(color, "#ffff00"); });これはhtmlファイルで
h1
タグの部分のcss
プロパティ「color
を#ffff00
」に変化させるメソッド。jQueryを使ってみる--メソッド
まずはサンプルページのhtml。これをいろいろいじっていきます。
表示されるページ.html<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>サンプルサイト</title> <link href="/style.css" rel="stylesheet" /> </head> <body> <header> <h1>Sample Page</h1> </header> <div id="samplePage"> <h2>ようこそサンプルページへ</h2> <ul class="akasa"> <li>あいうえお</li> <li>かきくけこ</li> <li>さしすせそ</li> </ul> <p class="tanaha">たちつてと</p> <p class="tanaha">なにぬねの</p> <p class="tanaha">はひふへほ</p> </div> <script src="https://code.jquery.com/jquery-3.4.1.min.js"></script> </body> </html>セレクタによる記述の違い
少しずつ実例を出します。
sample.js$(function(){ //色を指定するのはどちらでもできる $("h2").css("color", "#0000ff"); $("li").css("color", "blue"); //cssで指定するものであれば色以外のプロパティもOK $("#samplePage").css("margin-left", '50px'); $(".tanaha").css("color", "red"); });解説
h1
~h6
やul
li
、img
p
などhtmlで最初から用意されているものをタイプセレクタ(要素セレクタ)と言って、特に何もつけずダブルクォーテーション” ”
で囲む。
ここではh2
とli
(リストの子要素)の色を#0000ff
に指定した。htmlで
id="〇〇"
として自分が付与したIDに変化をつけたいときは、ID名の前にシャープ#
をつけてダブルクォーテーション" "
で囲む。
ここではsamplePage
IDに左側の余白(mairgin-left
)を指定した。htmlで
class="〇〇"
として自分が付与したクラスに変化をつけたいときは、クラス名の前にドット.
をつけてダブルクォーテーション" "
で囲む。
ここではtanaha
クラスの色をred
に指定した。メソッド色々
正直あんまり覚えなくていい(どうせググればいくらでも出てくる)ので軽く表にしただけ。
メソッド 動き .css() CSSプロパティを変化させる .on() イベント発生時に実行する関数を割り当てる(後述) .text() 要素の文字列を取得したり書き換えたりする .fadeIn() 要素を徐々に表示する .fadeOut() 要素を徐々に薄くしていき最後は非表示にする .hide() 要素を非表示にする jQueryを使ってみる--イベント
イベントについて
多分jQueryの本領。
メソッドと組み合わせて使うもので、「イベントが起きた時メソッドの処理をする」という動きを与える。…よくわからないと思うので↓で説明します。sample.js$(function(){ $("セレクタ1").イベント(function(){ $("セレクタ2").メソッド("引数"); }); });
セレクタ1
にイベント
が起きた時、セレクタ2
にメソッド
の変化が起こる。例
sample.js$(function(){ $("h2").click(function(){ $(".akasa").fadeOut(); }); });htmlファイルで
h2
タグの部分をクリックすると、akasa
クラスがフェードアウトするイベント。
結果
h2
タグである「ようこそサンプルページへ」の部分をクリックすると・・・
akasa
クラスを与えた要素(リスト部分)が消えた。
実際はちゃんとゆっくりフェードアウトするのですが、私がGIF動画を作る技術がないのでお見せできず申し訳ないです。イベント色々
例によって雑な表。当然ほかにもいろいろある。
イベント タイミング .click() クリックしたら .mouseover() マウスをのせたら(その後もずっとメソッド発生) .mouseout() マウスを外したら(その後ずっとメソッド発生) .hover() マウスをのせているその時間のみ(詳細は次) hoverイベント
hoverイベントはほかのイベントと違い、「マウスをのせている間」と「マウスを外した後」の2種類の処理について記述が必要になる。
sample.js$(function(){ $("セレクタ1").hover( function(){ $("セレクタ2").メソッド2(); }, function();{ $("セレクタ3").メソッド3(); }); });
メソッド2
がマウスをあてている時のメソッド、メソッド3
がマウスを外した時のメソッド。
ほとんどの場合、セレクタ2
とセレクタ3
は同じになるしメソッド2
とメソッド3
は対になる動きになると思う。例
sample.js$(function(){ $("h2").hover( function(){ $(".akasa").css("color", "blue"); }, function();{ $(".akasa").css("color", "red"); }); });htmlファイルで
h2
タグの部分にマウスをあてるとakasa
クラスが青くなり、マウスを外すとakasa
クラスが赤くなるイベント。thisについて
イベント発生時、その対象のみにメソッドが適用される。
例sample.js$(function(){ $(".tanaha").click(function(){ $(this).css("font-weight", "bold"); }); });結果
tanaha
クラスの中でも「なにぬねの」をクリックすると・・・
クリックした対象のみが太字になり、ほかのtanaha
クラスに変化はない。jQueryを使ってみる--onメソッド
最後の難関、onメソッドについて。基本の型は
sample.js$(function(){ $("セレクタ1").on(イベント名, セレクタ2, データ, 関数) });それぞれの要素の説明をすると
セレクタ1
:対象とするセレクタを指定イベント名
:発生するイベントを指定セレクタ2
:さらにセレクタを指定したいときに設定データ
:任意のデータを渡したいときに設定関数
:イベント発生時に実行したい処理を記述
データ
とセレクタ2
は任意で設定するものなので、一番シンプルな形にするとsample.js$(function(){ $("セレクタ1").on(イベント名, 関数) });じゃあ関数ってどんなもんかという話ですね。
サンプルページにボタンを追加します。sample.html<button>ここをクリック</button>javaScriptファイルに、clickを使用したonイベントを追記します。
sample.js$(function() { $("button").on("click", function(){ $("button").text("クリックされました"); }) });htmlで
<button>
要素を付与した文字列が書き換わりました。onイベントのいいところ①同じ要素に複数のイベントを追加できる
イベント名部分に複数設定できる。イベント名同士はスペースで区切る。
sample.html<button>一度マウスを外してね</button>sample.js$(function() { $("button").on("dblclick mouseleave", function(){ $("button").text("ダブルクリックでも同じだよ"); }) });
ボタン部分に一度マウスをのせて外すと下の画像の表示になる。
ダブルクリックでも同じ挙動をする。
onイベントのいいところ②イベントごとに処理を設定できる
上ではスペース区切りで一気に指定しましたが、連想配列の形でイベントそれぞれに違う処理を設定することができます。
sample.js$(function() { $("button").on({ "dblclick": function(){ $("h1").css("color", "red") }, "mouseleave": function(){ $("button").text("ダブルクリックだとh1が赤くなるよ") } }); });onイベントのいいところ③デリゲート
シンプルなコードだとわかりにくい利点。
親要素に対してイベントを設定し、引数に子要素を指定することで、あとから追加した子要素にもイベントが適用される。
(※めちゃくちゃざっくり説明してます)sample.js$(function() { $("ul").on("click", "li", function() { $(this).css("color", "magenta"); }); });クリックした
li
要素が濃いピンクになった。
これだけだとわかりにくいが、今後サンプルページに情報が増えてli
の中身がなんらかの処理によって増えた時でも同じ挙動をする。
メインの起動セレクタをul
ではなくli
で指定してしまうと、要素が増えた時には対応できなくなってしまうらしい。onイベントのいいところ④オブジェクト形式のデータを関数に設定できる
onイベントが発生した時に行われる処理をオブジェクト形式のデータにすることで、より複雑な処理をすることが可能になる。
sample.js$("button").on("click", btnClick); function btnClick(){ alert("ボタンがクリックされました!"); }関数の名前は何でもいいし、基本はどんな処理も(関数であれば)できるので、onイベントが使われるのはほとんどこのためじゃないかなと思う・・・。
関数を別のファイルにまとめておいて、読み込む形にすればコードもスッキリする。
- 投稿日:2020-06-04T12:41:58+09:00
【備忘録】ブラウザで音声認識する
ブラウザに内蔵のAPIで音声認識する場合。
パラメータで認識中の途中結果を取得したり、連続で認識するといった変更はあるけど、とりあえず日本語の設定のみ。検証環境
- Google Chrome バージョン: 81.0.4044.138
ソースコード
html<button id='start'>開始</button>javascriptconst start = document.getElementById('start'); SpeechRecognition = webkitSpeechRecognition || SpeechRecognition; const recognition = new SpeechRecognition(); recognition.lang = 'ja-JP'; recognition.onresult = (event) => { console.log(event.results[0][0].transcript); } start.onclick = (event) => { recognition.start(); };チラシの裏(読まなくてもいい余談)
試してみると、結構正確に認識してくれます。音声の文書起こしをしたいときにわざわざ外部サービスを使わなくていいので便利です。
どちらかといえば、どんな局面で使うのか、を考える方が重要ですね。
- 投稿日:2020-06-04T11:54:28+09:00
【Rails】turbolinksを無効化する方法
開発環境
・Ruby: 2.5.7
・Rails: 5.2.4
・Vagrant: 2.2.7
・VirtualBox: 6.1
・OS: macOS Catalina完全に無効化する方法
1.
Gem
を無効化Gemfile# コメントアウトする # gem 'turbolinks', '~> 5'ターミナル$ bundle update2.
application.js
を編集
=
を削除する。application.js// 変更前 //= require turbolinks // 変更後 // require turbolinks3.
application.html.slim
を編集
'data-turbolinks-track': 'reload'
を削除する。application.html.slim/ 変更前 = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' = javascript_include_tag 'application', 'data-turbolinks-track': 'reload' / 変更後 = stylesheet_link_tag 'application', media: 'all' = javascript_include_tag 'application'部分的に無効化する方法
1.JavaScriptを編集する方法
①
~.js
ファイルの場合~.js$(document).on('turbolinks:load', function() { // turbolinksを無効化したい処理 });②
~.coffee
ファイルの場合~.coffee$(document).on 'turbolinks:load', -> # turbolinksを無効化したい処理2.リンクを編集する方法
①link_toに属性を追加する場合
~html.slim= link_to '', root_path, 'data-turbolinks': false②divで囲う場合
~html.slimdiv data-turbolinks='false' = link_to '', root_path
- 投稿日:2020-06-04T11:23:19+09:00
【Rails】Google Mapに複数マーカーを表示し、クリックしたら吹き出しを出す方法
目標
開発環境
・Ruby: 2.5.7
・Rails: 5.2.4
・Vagrant: 2.2.7
・VirtualBox: 6.1
・OS: macOS Catalina前提
下記実装済み。
・Slim導入
・ログイン機能実装
・Google Map表示
・Gocoding APIで緯度経度を算出実装
1.コントローラーを編集
users_controller.rbdef index @users = User.all gon.users = User.all end2.ビューを編集
users/index.html.slim#map style='height: 500px; width: 500px;' - google_api = "https://maps.googleapis.com/maps/api/js?key=#{ ENV['GOOGLE_MAP_API'] }&callback=initMap".html_safe script{ async src=google_api } javascript: let map; let marker = []; // マーカーを複数表示させたいので、配列化 let infoWindow = []; // 吹き出しを複数表示させたいので、配列化 let markerData = gon.users; // コントローラーで定義したインスタンス変数を変数に代入 function initMap() { geocoder = new google.maps.Geocoder() map = new google.maps.Map(document.getElementById('map'), { center: { lat: 35.6585, lng: 139.7486 }, // 東京タワーを中心に表示させている zoom: 12, }); // 繰り返し処理でマーカーと吹き出しを複数表示させる for (var i = 0; i < markerData.length; i++) { let id = markerData[i]['id'] // 各地点の緯度経度を算出 markerLatLng = new google.maps.LatLng({ lat: markerData[i]['latitude'], lng: markerData[i]['longitude'] }); // 各地点のマーカーを作成 marker[i] = new google.maps.Marker({ position: markerLatLng, map: map }); // 各地点の吹き出しを作成 infoWindow[i] = new google.maps.InfoWindow({ // 吹き出しの内容 content: markerData[i]['address'] }); // マーカーにクリックイベントを追加 markerEvent(i); } } // マーカーをクリックしたら吹き出しを表示 function markerEvent(i) { marker[i].addListener('click', function () { infoWindow[i].open(map, marker[i]); }); }吹き出しの内容ををリンクにしたい場合は下記の様に記述する。
// 各ユーザーのIDを変数化 let id = markerData[i]['id'] infoWindow[i] = new google.maps.InfoWindow({ // <a>タグでリンクを作成 content: `<a href='/users/${ id }'>${ markerData[i]['address'] }</a>` });注意
turbolinks
を無効化しないと地図が切り替わらないので、必ず無効化しておきましょう。
- 投稿日:2020-06-04T11:17:51+09:00
JScriptからダイアログを出す(Powershell、Windows.Forms利用)
例としてログインダイアログ
login.jsvar echo = function (str) { WScript.Echo(str); // console.log(str) }; var quit = function () { WScript.Quit(); }; var CredentialDialog = function () { var code = '\ Add-Type -AssemblyName System.Windows.Forms\r\n\ Add-Type -AssemblyName System\r\n\ \r\n\ $form = New-Object Windows.Forms.Form\r\n\ $form.Size = New-Object Drawing.Size @(400, 200)\r\n\ $form.ControlBox = $false\r\n\ $form.MinimizeBox = $false\r\n\ $form.MaximizeBox = $false\r\n\ $form.FormBorderStyle = [Windows.Forms.FormBorderStyle]::FixedDialog\r\n\ $form.Text = "ログイン"\r\n\ \r\n\ $okButton = New-Object Windows.Forms.Button\r\n\ $okButton.Location = New-Object Drawing.Size @(297, 115)\r\n\ $okButton.Size = New-Object Drawing.Size @(72, 25)\r\n\ $okButton.Font = New-Object Drawing.Font "MS UI Gothic", 12\r\n\ $okButton.TabIndex = 0\r\n\ $okButton.Text = "OK"\r\n\ $okButton.DialogResult = [System.Windows.Forms.DialogResult]::OK\r\n\ $okButton.Add_Click({[System.Console]::Write("OK\t" + $userName.Text + "\t" + $password.Text)})\r\n\ $form.Controls.Add($okButton)\r\n\ $form.AcceptButton = $okButton\r\n\ \r\n\ $cancelButton = New-Object Windows.Forms.Button\r\n\ $cancelButton.Location = New-Object Drawing.Size @(192, 115)\r\n\ $cancelButton.Size = New-Object Drawing.Size @(72, 25)\r\n\ $cancelButton.Font = New-Object Drawing.Font "MS UI Gothic", 12\r\n\ $cancelButton.TabIndex = 1\r\n\ $cancelButton.Text = "Cancel"\r\n\ $cancelButton.DialogResult = [System.Windows.Forms.DialogResult]::Cancel\r\n\ $form.Controls.Add($cancelButton)\r\n\ $form.CancelButton = $cancelButton\r\n\ \r\n\ $userName = New-Object Windows.Forms.TextBox\r\n\ $userName.Location = New-Object Drawing.Point @(108, 22)\r\n\ $userName.Size = New-Object Drawing.Size @(231, 25)\r\n\ $userName.Font = New-Object Drawing.Font "MS UI Gothic", 12\r\n\ $userName.TabIndex = 2\r\n\ $form.Controls.Add($userName)\r\n\ \r\n\ $password = New-Object Windows.Forms.TextBox\r\n\ $password.Location = New-Object Drawing.Point @(108, 69)\r\n\ $password.Size = New-Object Drawing.Size @(231, 25)\r\n\ $password.Font = New-Object Drawing.Font "MS UI Gothic", 12\r\n\ $password.PasswordChar = "*"\r\n\ $password.TabIndex = 3\r\n\ $form.Controls.Add($password)\r\n\ \r\n\ $label1 = New-Object System.Windows.Forms.Label\r\n\ $label1.Location = New-Object Drawing.Point @(12, 25)\r\n\ $label1.Size = New-Object Drawing.Point @(80, 16)\r\n\ $label1.Font = New-Object Drawing.Font "MS UI Gothic", 12\r\n\ $label1.Text = "ユーザーID"\r\n\ $form.Controls.Add($label1)\r\n\ \r\n\ $label2 = New-Object System.Windows.Forms.Label\r\n\ $label2.Location = New-Object Drawing.Point @(16, 72)\r\n\ $label2.Size = New-Object Drawing.Point @(80, 16)\r\n\ $label2.Font = New-Object Drawing.Font "MS UI Gothic", 12\r\n\ $label2.Text = "パスワード"\r\n\ $form.Controls.Add($label2)\r\n\ \r\n\ $result = $form.ShowDialog()\r\n\ '; var shellObj = WScript.CreateObject("WScript.Shell"); var execObj = shellObj.Exec("PowerShell -NonInteractive -WindowStyle Hidden -NoLogo -ExecutionPolicy Unrestricted -Command -"); execObj.StdIn.Write(code); execObj.StdIn.Close(); var res = execObj.StdOut.ReadAll(); var rex = new RegExp("^(OK)\t(.*)\t(.*)$"); var rer = rex.exec(res); if (rer) { var cred = { "userid": rer[2], "password": rer[3] } return cred; } else { echo("ログイン情報を入力してください"); quit(); } } var cred = CredentialDialog(); echo(cred.userid + cred.password);
- 投稿日:2020-06-04T10:36:06+09:00
jbuilderの扱い方
前提条件
以下のブログを読んでから本記事をお読みください
非同期通信の手始め
respond_toについてjbuilder
結論、jbuilderを用いるとrender json 〇〇をしなくていいです。
jbuilderは、viewと同じように該当するアクションと同じ名前にする必要があります。
今回はコメント機能の非同期通信を実装するので
commentのcreateアクションに対応するjbuilderのファイルを作成します。
作成する場所はviews/comments/create.json.jbuilderになります。json.text @comment.text json.user_id @comment.user.id json.user_name @comment.user.nicknameこのような形で記述します。
とても見やすいですよね。jbuilderの最大のメリットは
見やすく分かりやすくコードがかけることです。
積極的に使っていきましょう。
- 投稿日:2020-06-04T10:31:31+09:00
Slackとgoogleカレンダーで勤怠記録をしよう
はじめに
新型コロナウイルスの影響で自粛期間が長らく続いていましたが,6月あたりから感染対策を実施した上で各大学,企業でのが行われるようになりましたね.
僕の大学では研究室の入退室時間を管理することが義務付けられました.これは感染者が出た場合に,感染経路及び感染した可能性のある人を特定するためだと思われます.
他の大学や企業でもこのような感染対策を実施しているのではないでしょうか.特に管理法についての指定はなかったので,googleカレンダーに記入することで入退室時間を記録することになったのですが,研究室全員で使用するアプリはできる限り少ない方が効率的であるため,指導教員の先生からSlackだけで入退室時間を管理できるようにうまいことやってくれないかと依頼されました.
そこでSlackに勤怠管理チャンネルを作り,「おはよう」や,「さようなら」と呟けばgoogleカレンダーに予定が追加されるプログラムを作成しました.
初投稿であることと,GAS経験が少ないことをご容赦ください.
用意するものと使ったツール
- チームで共有しているgoogleカレンダー
- 勤怠管理用のSlackのチャンネル
- Outgoing webhook
- Incoming webhook
- Google Apps Script
Outgoing webhook
トリガーワードが呟かれたら反応して色々するアプリです
こちらからSlackの勤怠管理用のチャンネルにOutgoing webhookを追加します.
中身はGoogleAppsScriptで書いていきます.
引き金となる言葉は自由に変更できます.inとoutだけで構いませんが,味気ないので多めに設定しました.
URLは,GoogleAppsScriptのアプリのURLを張り付けるので,この時点では空欄で構いません.
トークンはGoogleAppsScriptで使います.Incoming webhook
呟いてレスポンスが無かったらカレンダーに追加されたのか懐疑的になるのと,味気ないので,反応してくれるアプリを追加します.
さきほどと同様にこちらから勤怠管理用のチャンネルにIncoming webhookを追加します.
レスポンスしてくれるbotのアイコンはここで設定できます.Google Apps Script
googleドライブから,Googleスプレッドシートを新規作成します.
(1, 1)から(1, 6)セルに
カレンダーへの登録,日付,タイトル(人),開始時間,終了時間,場所,
と記入します.スプレッドシートでも勤怠管理を登録していきます.
次にツールタブのスクリプトエディタを開きます.
以下のソースコードをコピペします.SlackToCalendar.gsfunction doPost(e) { //シート1はシート名に応じて変更 var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('シート1'); // googleカレンダーのID const calendar = CalendarApp.getCalendarById("[googleカレンダーの設定からIDをコピペ]") //Outgoing Webhookのトークン var token = '[Outgoing webhookのトークンをコピペ]' //送られてきたトークンが正しければ勤怠を記録 //parameterは必要に応じて変更 if (token == e.parameter.token){ var id_to_name = {"U0*********":"金子","U0*********":"本多","U0*********":"阿部"} var datetime = new Date(); var date = (datetime.getFullYear() + ('0' + (datetime.getMonth() + 1)).slice(-2) + ('0' + datetime.getDate()).slice(-2)) var user_name = id_to_name[e.parameter.user_id]; var trigger_word = e.parameter.trigger_word; var text = e.parameter.text; // 入室に反応する言葉(Outgoing webhookで設定した物)をリストに追加 var list = ["in","おはよう","出勤","こんにちは"] var message if (list.includes(trigger_word)) { // 出勤時のプログラム // 場所を追加 text = text.replace("_","_") var mark = "_" var index = text.indexOf(mark) text = text.slice(index + 1) //追加する配列を作成 array = ["",date,user_name,datetime,"",text]; //シートの最下行に配列を記述 sheet.appendRow(array); message = Utilities.formatString("%sさんが%sに%sしました",user_name,text,trigger_word) } else { // 退勤時のプログラム message = Utilities.formatString("%sさん%s",user_name,trigger_word) for(var i=2; i<= sheet.getLastRow(); i++) { // カレンダーに登録済みならパス if (sheet.getRange(i, 1).getValue().toString() == "済") { continue; } // 日付とuser_nameが一致しているものを探す var in_date = sheet.getRange(i,2).getValue() var in_datetime = sheet.getRange(i,4).getValue() var in_name = sheet.getRange(i,3).getValue().toString() var in_location = sheet.getRange(i,6).getValue().toString() // 一致していたらoutの時間を登録,カレンダーにも登録 if (( in_date == date ) && ( in_name == user_name )) { sheet.getRange(i,5).setValue(Utilities.formatDate(datetime,"JST", "yyyy/MM/dd HH:mm:ss")) var startTime = new Date(in_datetime) var endTime = datetime var options = { location: in_location } calendar.createEvent(user_name, startTime, endTime, options) sheet.getRange(i,1).setValue("済") break } } } var payload = { "text" : "\n" + message, // メッセージの本文 "channel" : "#勤怠管理", // チャネルの指定 "username" : "勤怠管理クマ", // Botの名前 } postSlack(payload); } return } // Slackでレスポンスする関数 function postSlack(payload) { var options = { "method" : "POST", "payload" : JSON.stringify(payload) } var url = "[Incoming webhookのURL]"; // Webhook URL var response = UrlFetchApp.fetch(url, options); var content = response.getContentText("UTF-8"); }コピペしたらこちらを参考にアプリケーションのURLを取得し,Outgoingq webhookのURL欄に入力します.
急ぎで作ったので仕様として次のような欠陥がありますので,実装する際は注意してください.
- 入室記録を忘れると退室記録をしても登録されない
- 0時をまたぐ入退室記録に対応していない
- Googleスプレッドシートの日付,開始時間,終了時間の見た目が頭悪そう
場所を指定するのに普通は@を使いますが,slackは"@"を入力するとメンションが入力されてしまい煩わしいので"_"を採用しました.
messageは所属するチームの雰囲気に合わせて調整してください.
user_idは,Slackのプロフィール欄からコピペできます.大人数のチームだと大変ですが,Outgoing webhookの仕様で名前を取得できないため,ひと手間かかります.おわりに
最後まで読んでいただきありがとうございました.
質問やコメントお待ちしております.以下のページを参考にしました.
Slackで簡単に勤怠管理!【GAS】
- 投稿日:2020-06-04T02:01:05+09:00
コーディング規約チェッカーを運用してみた使用感 (C#(Unity) JavaScript(CocosCreator), PHP(FuelPHP))
はじめに
コーディング規約を自動的にチェックしてくれるコーディング規約チェッカーは、
複数人で開発する際の可読性向上や各人の書き方の癖を平準化できる便利なものです。コードレビュー等で生産性のない指摘をし合わないよう、
チェックはシステムにやってもらうというのはとても合理的。一方で、規約に縛られた書き方を窮屈に感じたりすることもあると思います。
そこで、各開発環境における規約チェッカーを使ってみた所感を書き残します。C# (Visual Studio)
規約チェッカーであるStyleCopは、
・プロジェクトにNuGetパッケージとしてインストール
・Visual Studioの拡張機能としてインストールという2種類の方法があります。
前者が適用できるならそちらのほうがベターなようです。・プロジェクトにNuGetパッケージとしてインストール
NuGetでインストールするとプロジェクト内に管理される形となり、
自動でチェックが走るため扱いやすいです。また、NuGetではStyleCopの後継であるStyleCop.Analyzersが使用できるため、
その点でもオススメできる方法です。規約のカスタマイズはソリューションにファイルを追加することで可能です。
https://anderson02.com/cs/cs-rules/cs-rules19/・Visual Studioの拡張機能としてインストール
拡張機能としてインストールした場合はVisualStuioの右クリックメニューから、
「Run StyleCop」を選択することでファイルのチェックを走らせることができます。規約のルール設定は右クリックメニューの「StyleCop Settings」からGUIで行うことができ、
編集結果はXMLファイル「Settings.StyleCop」として保存されるため、
このファイルをGitで共有するなどすれば、
チームでカスタマイズされたルールを共有できます。Unityでの利用
Unityのプロジェクトで利用する場合、NuGetによる導入はやや煩雑です。
https://t-tutiya.hatenablog.com/entry/2019/11/07/200330拡張機能のほうはテキストファイルを検査するだけなので、
Unityプロジェクトであっても、通常のC#プロジェクトと同様の使い方ができます。Unityの場合は、とりあえず拡張機能のほうを使うのが現状は無難かと思います。
JavaScript (Visual Studio Code)
C#と違ってフレームワーク等により様々な書き方の流儀が存在するJavaScriptにおいては、
チェッカーもいくつかあり、それを適用するIDEも選択肢が多いため、
今回はVisual Studio Code + ESLintという組み合わせを選択しました。npmでESLintパッケージをプロジェクトにインストールし、
Visual Studio Codeの拡張機能としてのESLintをインストールする、という流れです。
https://qiita.com/yohei_nakamura/items/4cf4876b3e36a46f3750設定は「.eslintrc.json」というファイルに記述します。
npmのパッケージ設定とルールファイルをGitで共有することでチーム内でルールが共通化できる点については、StyleCopとほぼ同じです。チェッカー本体とIDEの連携機能を別々にインストールする必要がある、という点がStyleCopとは異なります。
上手く動作しないときの原因切り分けが少々厄介です。Cocos Creatorでの利用
Cocos Creatorで利用する場合であっても、C#のようにプログラム側にプロジェクトの概念がないため(単なるJavaScriptファイル群)、
Webサービス等でJavaScriptを使用する場合と同じ使い方ができます。ただし、Cocos Creatorの形式(クラス定義型とか)とルールがマッチしない部分もあるので、
必要に応じてルールをカスタマイズする等の対応は必要になります。PHP (Visual Studio Code)
PHPの場合もJavaScriptのように、複数のポリシーとIDEの組み合わせが想定されます。
今回はPHPの標準っぽさがあるPSR-2を使ってみました。導入等は他記事を参照してください。
https://mseeeen.msen.jp/php-codesniffer-with-vscode/PHPの場合、使用するフレームワークそのものが規約のような縛りがあったりするので、
ルールを調整して運用する必要がありそうです。FuelPHPでの利用
FuelPHPでも、フレームワークでクラスの書き方(スネークケースで書くなど)等が決まっているので、パスカルケースを使うようなルールになっているとそれだけで正しくかけないといった問題が発生します。
FuelPHP準拠のルールを使用するか、PSR-2のようなルールをベースにフレームワークに合わせてカスタマイズしていくか、
いずれにしろ運用に工夫が必要になります。
おわりに 複数言語の比較
複数言語を比較してみると、言語やチェッカーの特性などが見えてきます。
C#は.NET Frameworkの統一感があるため、規約チェッカーを導入する場合もあれこれ悩む必要が
少なかったです。Unityで公式に使えるようになると、もっといいですね。
JavaScriptとPHPは言語仕様が緩いことに加え、フレームワーク毎にまったく違う書き方があり、またエディタのデファクトも決定打がないため、様々な選択肢を試行錯誤する必要がありました。
カッコで改行するかどうか、など、細かいところも差異がでてくるため、しっかりと統一したルールを策定し、共有し、運用することが重要だと思います。
- 投稿日:2020-06-04T02:01:05+09:00
プログラミング言語毎のコーディング規約チェッカー使用感 (C#(Unity) JavaScript(CocosCreator), PHP(FuelPHP))
はじめに
コーディング規約を自動的にチェックしてくれるコーディング規約チェッカーは、
複数人で開発する際の可読性向上や各人の書き方の癖を平準化できる便利なものです。コードレビュー等で生産性のない指摘をし合わないよう、
チェックはシステムにやってもらうというのはとても合理的。一方で、規約に縛られた書き方を窮屈に感じたりすることもあると思います。
そこで、各開発環境における規約チェッカーを使ってみた所感を書き残します。C# (Visual Studio)
規約チェッカーであるStyleCopは、
・プロジェクトにNuGetパッケージとしてインストール
・Visual Studioの拡張機能としてインストールという2種類の方法があります。
前者が適用できるならそちらのほうがベターなようです。・プロジェクトにNuGetパッケージとしてインストール
NuGetでインストールするとプロジェクト内に管理される形となり、
自動でチェックが走るため扱いやすいです。また、NuGetではStyleCopの後継であるStyleCop.Analyzersが使用できるため、
その点でもオススメできる方法です。規約のカスタマイズはソリューションにファイルを追加することで可能です。
https://anderson02.com/cs/cs-rules/cs-rules19/・Visual Studioの拡張機能としてインストール
拡張機能としてインストールした場合はVisualStuioの右クリックメニューから、
「Run StyleCop」を選択することでファイルのチェックを走らせることができます。規約のルール設定は右クリックメニューの「StyleCop Settings」からGUIで行うことができ、
編集結果はXMLファイル「Settings.StyleCop」として保存されるため、
このファイルをGitで共有するなどすれば、
チームでカスタマイズされたルールを共有できます。Unityでの利用
Unityのプロジェクトで利用する場合、NuGetによる導入はやや煩雑です。
https://t-tutiya.hatenablog.com/entry/2019/11/07/200330拡張機能のほうはテキストファイルを検査するだけなので、
Unityプロジェクトであっても、通常のC#プロジェクトと同様の使い方ができます。Unityの場合は、とりあえず拡張機能のほうを使うのが現状は無難かと思います。
JavaScript (Visual Studio Code)
C#と違ってフレームワーク等により様々な書き方の流儀が存在するJavaScriptにおいては、
チェッカーもいくつかあり、それを適用するIDEも選択肢が多いため、
今回はVisual Studio Code + ESLintという組み合わせを選択しました。npmでESLintパッケージをプロジェクトにインストールし、
Visual Studio Codeの拡張機能としてのESLintをインストールする、という流れです。
https://qiita.com/yohei_nakamura/items/4cf4876b3e36a46f3750設定は「.eslintrc.json」というファイルに記述します。
npmのパッケージ設定とルールファイルをGitで共有することでチーム内でルールが共通化できる点については、StyleCopとほぼ同じです。チェッカー本体とIDEの連携機能を別々にインストールする必要がある、という点がStyleCopとは異なります。
上手く動作しないときの原因切り分けが少々厄介です。Cocos Creatorでの利用
Cocos Creatorで利用する場合であっても、C#のようにプログラム側にプロジェクトの概念がないため(単なるJavaScriptファイル群)、
Webサービス等でJavaScriptを使用する場合と同じ使い方ができます。ただし、Cocos Creatorの形式(クラス定義型とか)とルールがマッチしない部分もあるので、
必要に応じてルールをカスタマイズする等の対応は必要になります。PHP (Visual Studio Code)
PHPの場合もJavaScriptのように、複数のポリシーとIDEの組み合わせが想定されます。
今回はPHPの標準っぽさがあるPSR-2を使ってみました。導入等は他記事を参照してください。
https://mseeeen.msen.jp/php-codesniffer-with-vscode/PHPの場合、使用するフレームワークそのものが規約のような縛りがあったりするので、
ルールを調整して運用する必要がありそうです。FuelPHPでの利用
FuelPHPでも、フレームワークでクラスの書き方(スネークケースで書くなど)等が決まっているので、パスカルケースを使うようなルールになっているとそれだけで正しくかけないといった問題が発生します。
FuelPHP準拠のルールを使用するか、PSR-2のようなルールをベースにフレームワークに合わせてカスタマイズしていくか、
いずれにしろ運用に工夫が必要になります。
おわりに 複数言語の比較
複数言語を比較してみると、言語やチェッカーの特性などが見えてきます。
C#は.NET Frameworkの統一感があるため、規約チェッカーを導入する場合もあれこれ悩む必要が
少なかったです。Unityで公式に使えるようになると、もっといいですね。
JavaScriptとPHPは言語仕様が緩いことに加え、フレームワーク毎にまったく違う書き方があり、またエディタのデファクトも決定打がないため、様々な選択肢を試行錯誤する必要がありました。
カッコで改行するかどうか、など、細かいところも差異がでてくるため、しっかりと統一したルールを策定し、共有し、運用することが重要だと思います。
- 投稿日:2020-06-04T02:01:03+09:00
Web初心者がobnizとWeb VRで遊んでみようと試みた話...。
概要
javascriptで制御できるマイコンボード「obniz」。
javascriptで書けるなら、A-Frameと連携して何かできそう!と思い試みた話です。
試してみたこと
「A-Frameで表現したボタンを押すと、obnizのOLEDにメッセージが表示される」ということに試みました。
結果としては、obnizにつながらないような状況です。コードを見てコメントいただけると嬉しいです。
できたこと
A-Frameでボタンのようなものを作れました。(押したら戻ってこない)
あれから少し進化。押したら2度と使えんくなるタイプのボタンができた(笑)#protoout pic.twitter.com/Lur9AcAO0I
— まえぷー@出窓菜園ティスト (@kmaepu) June 3, 2020できなかったこと
「ボタンを押したらobnizにメッセージが表示される」は実現できませんでした。
エラーは出ていないようなので、obnizの処理が実行されていないのではないかと考えています。
次のように、ボタンを押したらobnizを接続し、コンソールへログ出力させているのですが、ログがでないです。obniz.onconnect = async function () { console.log("Connect obniz"); obniz.display.clear(); obniz.display.print("3D A-Frame"); obniz.display.print(" ↑↓"); obniz.display.print("obniz"); }ソースコード
index.html<!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@2.3.0/obniz.js" crossorigin="anonymous"></script> <script> let obniz = new Obniz("obniz-id"); // obniz idを入力する async function handlerClick(event) { console.log('handlerClick'); console.log(event); event.target.object3D.position.z -= 0.5; obniz.onconnect = async function () { console.log("Connect obniz"); 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-04T01:10:08+09:00
初学者が躓くreturnの概念
プログラミング初心者が躓くreturn(返り値)
なんとか理解することができたので
忘れないようにアウトプットも兼ねて投稿しておくreturnの意味は大きく2つ
・処理を終わらせる
・値を返す
値を返すという概念はなかなか理解しづらいですが
私は関数で処理して得た値を使いまわしたい時はretrunで返り値を持たせる!と覚えている試しに2パターン関数を書いてみる
returnなし$num = 10; function a($i) { $i * $i; } echo a($num); //結果は出力なしreturnあり$num = 10; function a($i) { return $i * $i; } echo a($num); //100結果returnをつけてやらないと、全く使いまわすことのできない関数となってしまう
$num = 10; function a($i) { echo $i * $i; } a($num); //100上記の場合は関数内で処理が完結しているため、そのまま出力しても問題ないが
処理結果を別の処理では使いまわせないため使い方は限定的だ
なぜreturnで躓くか
これは私の勝手な妄想である
プログラミングを勉強する方々はまずはじめにHTML・CSSを勉強するとおもう
そこからJavaScriptやphpを学習する人がほとんどのため、どの教材でも「HTMLに出力してみよう」
ここからプログラミングを試していることがほとんどである
HTMLに出力するこということは、出力して終わりである
関数の値を他で使いまわさないのだ実際にプログラミングを使って開発をする場合、ほとんどの関数には返り値をもたし他で使いまわせるようにするらしい
まとめ
WEB制作からプログラミングを勉強した方は躓きやすい
プログラミング言語から勉強した方は、理解しやすい私は最初WEB制作を勉強したのでガッツリ躓いた
- 投稿日:2020-06-04T00:57:42+09:00
[JavaScript] window.を省略してはいけない場面に注意しよう
windowオブジェクト
ブラウザでJavaScriptを実行する場合、
window
がグローバルオブジェクトとして存在します。みなさんご存知のとおり、windowオブジェクトのプロパティにアクセスする際の
window.
は省略できます。// OK window.alert('Hello world') // 省略してもOK alert('Hello world')しかし、省略してはいけない場面があります。
省略してはいけない場面
省略してはいけない場面、それはプロパティの存在チェック時です。
一部ブラウザにしか実装されていない実験的な機能を使う場面や、古いブラウザに対応させるために、windowオブジェクトにその実装が存在するかチェックするコードを書くことがあると思います。
このとき、
window.
を省略してしまうと思わぬエラーに遭遇します。省略時に発生するエラー
windowオブジェクトにチェック対象のプロパティが存在しない場合、
ReferenceError
となってしまいます。これは、宣言していない変数への参照とみなされてしまうためです。
// ====== NG! ====== // window.xxxxが存在するかチェックする意図だけど… // 宣言していない変数を参照してしまい、ReferenceErrorとなる if (xxxx) { xxxx() } // ====== OK! ====== // windowオブジェクトのプロパティの存在チェック時には明示的にwindow.を付与する必要がある // windowオブジェクトにxxxxプロパティが存在しなくてもundefinedが返り、エラーにはならない if (window.xxxx) { xxxx() }これの厄介なところは、対象の機能の実装がないブラウザで確認しないと、この問題に気づけない点です。
やはり、クロスブラウザのテストが大事ですね…
- 投稿日:2020-06-04T00:46:47+09:00
【A-Frame】WebVRを自分のサイトに組み込んでみる
A-Frameを使用してWebVRを自分のサイトに組み込んでみました。短いコードでWebVRが作成できるのがすごいです。
公開サイト
今回追加した機能
- 視点を合わせてクリックをするとブロックを配置
- 置いたブロックをクリックするとランダムで色変更
- 十字キーで左右上下の移動
- テキスト表示(英語)
今回追加しなかった機能
- 今後は置いたブロックをクリックしたら色を指定できるようにしたい
- テキスト表示(日本語) ここで出来そうです
- 空間(sky)と地面(ground)は最初はテクスチャを張っていたが、雰囲気に合わず一旦削除
コード
<!DOCTYPE html> <html> <head> <script src="https://aframe.io/releases/0.5.0/aframe.min.js"></script> <script src="https://unpkg.com/aframe-teleport-controls@0.2.x/dist/aframe-teleport-controls.min.js"></script> <script src="https://unpkg.com/aframe-controller-cursor-component@0.2.x/dist/aframe-controller-cursor-component.min.js"></script> <script src="https://rawgit.com/ngokevin/kframe/csstricks/scenes/aincraft/components/random-color.js"></script> <script src="https://rawgit.com/ngokevin/kframe/csstricks/scenes/aincraft/components/snap.js"></script> <script src="https://rawgit.com/ngokevin/kframe/csstricks/scenes/aincraft/components/intersection-spawn.js"></script> <body> <a-scene> <a-assets> <a-mixin id="voxel" geometry="primitive: box; height: 0.3; width: 0.3; depth: 0.3" material="shader: standard" random-color snap="offset: 0.25 0.25 0.25; snap: 0.3 0.3 0.3 " img id="boxTexture" src="https://i.imgur.com/mYmmbrp.jpg"></a-mixin> </a-assets> <a-cylinder id="ground" collar="white" radius="30" height="0.1"></a-cylinder> <a-sky color="#CCFFFF" radius="900"></a-sky> <!-- Hands. --> <a-entity id="teleHand" hand-controls="left" teleport-controls="type: parabolic; collisionEntities: [mixin='voxel'], #ground"></a-entity> <a-entity id="blockHand" hand-controls="right" controller-cursor intersection-spawn="event: click; mixin: voxel"></a-entity> <!-- Camera. --> <a-camera> <a-cursor intersection-spawn="event: click; mixin: voxel"></a-cursor> </a-camera> <a-text position="-3.5 1.25 -3" value="welcome to my website! (Move or Click!)" color="#222222" scale="3"></a-text> </a-scene> </body> </html>本当に短いです。。笑
解説
以下の部分でクリックしたらブロックが表示されるようにしています。
ブロックの大きさは、height・width・depthで定義しています。<a-assets> <a-mixin id="voxel" geometry="primitive: box; height: 0.3; width: 0.3; depth: 0.3" material="shader: standard" random-color snap="offset: 0.25 0.25 0.25; snap: 0.3 0.3 0.3 ></a-mixin> </a-assets> <a-scene> <a-text position="-3 2.25 -3" value="welcome to my website! (Move or Click!)" color="#222222" scale="3"></a-text> </a-scene>positionでx軸、y軸、z軸の順で座標を定義しており、半角スペースで区切ります。忘れた時には以下のイラストが役立ちます。
参考にしたサイト
公式ページ
- https://aframe.io/docs/1.0.0今後の改善点
- この状態でデザインがしやすいとは決して言えないため、改良していきたい
- objファイルの取込も活用して、もう少し世界観を表現したい
- 他の技術ももう少し組み合わせたかった