20200604のvue.jsに関する記事は7件です。

入門: 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.js
module.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.js
import 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.js
import 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!

参考

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

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.js
module.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.js
import 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.js
import 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!

参考

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

Web VRとobnizを連携することに成功!

概要

「Web VR(A-Framee)のボタンを押すと、obnizの画面にメッセージが表示される」

というものが作りたくて奮闘しました
https://qiita.com/kmaepu/items/55689772aa41bd5d8433
こちらの記事ではobnizとの連携まで至りませんでした。

というのをTwitterにつぶやいた所、中の人からアドバイス頂き、実現することができました!
ありがとうございます。

実際の動き

少し見づらいですが、PC画面上のボタンを押すと、obnizの液晶画面に文字が映し出されます。

ソースコード解説

前回の記事にあったところとの差分を抜き出すと次の箇所になります。

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>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

本当に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 nodebrew

nodebrewのバージョンを確認してバージョンが返ってくればインストールされている

nodebrew -v

node.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/src

node.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 -v

yarnインストール

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を実行

image

vue環境構築完了!

参考文献

https://qiita.com/white0221/items/d371a19b59af4cba8e8b

https://qiita.com/Mitsuzara/items/4dea8c0aa95d6284980a

https://utano.jp/entry/2018/02/vue-cli-genearte-webpack-project/

https://qiita.com/zaburo/items/29fe23c1ceb6056109fd

https://qiita.com/kyosuke5_20/items/c5f68fc9d89b84c0df09

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

Vue.jsで動的な複数のclassを指定する時の書き方

それぞれstringを返すなら以下のようにclassを付与できます
なお:classv-bind:classの省略記法です

<div :class="[activeClass, errorClass]"></div>
data: {
  activeClass: 'active',
  errorClass: 'text-danger'
}

また、Vueはbooleanな値を使って以下のようにclassを付け替えすることもできます

<div :class="{ 'active': isActive }"></div>
data: {
  isActive: true
}

以上2つの指定方法が混在しているDOMについては以下のように纏められます。

<div :class="[activeClass, errorClass, { active: isActive }]"></div>
data: {
  activeClass: 'active',
  errorClass: 'text-danger',
  isActive: true
}

配列の形でそれぞれの指定方法に則って記述するだけですね。

また、この指定方法ではDOMにロジックが寄って見通しが悪くなることが考えられます。
その場合、算出プロパティを用いて付与するクラスをまとめると良いでしょう。
同様なロジックが存在している場合にも、算出プロパティは変更がある場合のみ再計算してそれ以外はキャッシュして返してくれるのでパフォーマンスの面でも優れます。

<div :class="classObject"></div>
data: {
  isActive: true,
  error: null
},
computed: {
  classObject: function () {
    return {
      active: this.isActive && !this.error,
      'text-danger': this.error && this.error.type === 'fatal'
    }
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

備忘録: Rails Vueアプリ作成時のチートシート

app作成コマンド

rails バージョン new アプリ名 -d mysql --webpacker=vue --skip-test --skip-bundle --skip-turbolinks

assets内のapplication.jsから以下の一行を削除する。

追加するgem list

gem 'devise'
gem 'devise-i18n'
gem 'rails-i18n'

gem 'faker'
gem 'factory_bot_rails'

gem 'rubocop', require: false
gem 'rubocop-rails', require: false
gem 'pry'
gem 'pry-doc'
gem 'pry-rails'
gem 'pry-byebug'
gem 'foreman'

gem 'rspec-rails', '~> 4.0.0'
gem 'rubocop-rscpe', require: false
gem 'spring-commands-rspec'

実行コマンド

$ rails webpacker:install

$ rails webpacker:install:vue

$ yarn add axios

$ yarn add vue-router

$ rails g rspec:install

$ rails g devise:install

$ rails g devise:user

$ rails g devise:views

$ yarn add -D eslint eslint-loader eslint-plugin-vue

rspecの設定

.rspec
--require spec_helper
--format documentation

foremanの設定

Profile.dev
web: bundle exec rails s
# watcher: ./bin/webpack-watcher
hot: ./bin/webpack-dev-server
bin/server
#!/bin/bash -i
bundle install
bundle exec foreman start -f Procfile.dev
$ chmod 777 bin/server

deviseの設定

config/environments/development.rb
Rails.application.configure do
  # Settings specified here will take precedence over those in config/application.rb.

  (省略)...

  # mailer setting
  config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
end
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>

日本語対応といらないファイルを作成しない設定

config/application.rb
config.i18n.default_locale = :ja
config.time_zone = "Tokyo"

config.generators do |g|
  g.template_engine false 
  g.assets false
  g.helper false
  g.test_framework :rspec,
                    fixtures: false,
                    view_specs: false,
                    helper_specs: false,
                    routing_specs: false
end

ESLintの設定

config/webpack/loaders/eslint.js
module.exports = {
  test: /\.(js|vue)$/,
  loader: 'eslint-loader',
  enforce: 'pre',
  options: {}
}
environment.js
const { environment } = require('@rails/webpacker')
const vue =  require('./loaders/vue')
const eslint =  require('./loaders/eslint')

environment.loaders.append('vue', vue)
environment.loaders.append('eslint', eslint)
module.exports = environment

よく使う作成コマンド

$ rails g api/v1/コントローラー名 index

$ rails g model モデル名 title:string likable:references{polymorphic}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

APIで取ってきた画像をVueでbackground-imageを指定する

Vue.jsでbackground-imageを指定する

APIで取得してきたものをhtml内でなんとか指定したいっていう感じ

諦めてimgタグでレイアウト整えようとしたところで解決したので、感動した。

vueなどで普段から書いているフロントエンドエンジニアたちにとってこの書き方ができるかできないかで生涯年収が2倍違うと言っても過言。

index.vue
<template>
  <div :style="{backgroundImage: 'url(' + img[img.length-1].img_url + ')'}">
    HelloWorld!
  </div>
</template>

<script>
export default {
  async asyncData() {
    return await axios
      .get('/api/img/get_img')
      .then(res => {
        return {
         img: res.data.details.img
        }
      })
      .catch(error => {
        // throw error;
      });
  },
}
</script>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む