2017/12/12

[Vue.js]contenteditable属性を使って100マス計算アプリをつくる

きっかけは忘れたけどcontenteditable属性というものを知った。
contenteditable="true"を付与した要素を編集可能にする属性で、Dropbox Papercacooなど、主にオンラインエディタ的なサービスでよく使われている。

「contenteditable属性を使った何かをつくる」という目標を掲げて、100マス計算アプリを作ったので、それをサンプルにcontenteditableについて書こうと思う。

contenteditable属性について


contenteditable属性はグローバル属性なので、すべてのHTML要素で使える。
この属性に設定できるのは、以下の2つのみ。省略形は使えない。
  • true or 空文字: 要素は編集可能 
  • false: 要素は編集不可
親要素の設定を引き継ぐので、トップレベルにcontenteditable="true"を指定すると、すべての要素が編集可能になる。


100マス計算アプリをつくる


<main id="game">
  <table>
    <tr>
      <th>\</th>
      <th v-for="n in cols" :key="n">
        {{ n }}
      </th>
    </tr>
    
    <tr v-for="(m, i) in rows" :key="m">
      <th>{{ m }}</th>
      <td v-for="(_, j) in boxes" :key="j">
        <div contenteditable="true" @blur="onBlur($event, i, j)"></div>
      </td>
    </tr>
  </table>
  
  <button type="button" @click="finish">Finish</button>
</main>
table {
  text-align: center;
  border-collapse: collapse;
}

table tr {
  height: 40px;
}

table th, td {
  border: 3px solid #39B885;
  width: 40px;
}

table th {
  background-color: #39B885;
  color: white;
}
new Vue({
  el: '#game',
  data() {
    return {
      boxes: 10,
      rows: [], // 1〜10までのランダムな配列
      cols: [], // 1〜10までのランダムな配列
      result: []
    }
  },
  created() {
    this.rows = this.randomNumbers();
    this.cols = this.randomNumbers();
  
    // 0で初期化された10x10の2次元配列(結果格納用)
    let rows = new Array(this.boxes);
    [...Array(this.boxes)].forEach((_, i) => rows[i] = new Array(this.boxes).fill(0));
    
    this.result = rows;
  },
  methods: {
    /**
     * 1〜10までのランダムな配列を返す
     * computedだと再計算されないためmethodsで定義
     * @return {Array<Number>}
     */
    randomNumbers() {
      const nums = [...Array(this.boxes)].map((_, a) => a + 1);
      for (let i = nums.length - 1; 0 < i; i--) {
        let r = Math.floor(Math.random() * (i + 1));
        // nums[i] <-> nums[r]
        [nums[i], nums[r]] = [nums[r], nums[i]];
      }
      return nums;
    },
    /**
     * 入力した値を結果用配列に格納する
     * @param e イベント
     * @param i 行のインデックス
     * @param j 列のインデックス
     */
    onBlur(e, i, j) {
      this.result[i][j] = /^[0-9]+$/.test(e.target.innerText) ? +e.target.innerText : 0; 
    },
    /**
     * 正誤判定
     */
    finish() {
      const result = !this.cols.some((v, i) => {
        return this.rows.some((w, j) => {
          return this.result[i][j] !== v + w;
        });
      });
      
      alert(result ? '正解' : '不正解');
    }
  }
});
contenteditable属性を付与することで、input要素がなくても要素の変更&値の入力・取得ができる。ただし、v-modelでdataオブジェクトにバインドできないので、blurイベントを利用している。

1〜10までのランダムな配列を取得する詳しい方法は、以下の記事を参照してほしい。

正誤判定はArray.prototype.some()を使うことで、1つでも間違っていたら「不正解」と表示している。Array.prototype.every()という関数もあるが、こちらは全ての要素をチェックするため、今回の例でいうと100ループすることになる。どのマスが間違っているか表示したい場合はevery()を使うのが良さそう。



以上

written by @bc_rikko

0 件のコメント :

コメントを投稿