20190727のJavaScriptに関する記事は24件です。

vue-codemirrorはproductionモードだと挙動が変わるようです

こんにちは、プログラミングスクールのレビューサイト「スクールレポート」を運営しているアカネヤ(@ToshioAkaneya)です。

vue-codemirrorはproductionモードだと挙動が変わるようです。

タイトルの通りです。バグなのかわかりませんが、productionの時のみcodemirrorタグが生成されたりと、生成されるDOMエレメントが微妙に違います。

スタイルが適用されないなどのトラブルがある場合は、DOMを確認してみるのが良さそうです。
この記事が参考になれば幸いです。

終わりに

Ruby on RailsとVueで作成したプログラミングスクールのレビューサイトを運営しています。良ければご覧ください。https://school-report.com/

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

JavaScriptでオブジェクトの配列をグループ分け(groupby)して表示する

以下のように配列が与えられている時に、重複しているものをカテゴリ分けして、それぞれに紐づくプロパティの値を子要素として出力させる結果を得たいと思った。(画像①のようなイメージ)

constant.js
    const fruits = [{
            name: 'apple',
            price: 100
        },
        {
            name: 'apple',
            price: 500
        },
        {
            name: 'apple',
            price: 1000
        },
        {
            name: 'banana',
            price: 100
        },
        {
            name: 'banana',
            price: 300
        },
        {
            name: 'banana',
            price: 500
        },
        {
            name: 'banana',
            price: 700
        },
        {
            name: 'orange',
            price: 100
        },
        {
            name: 'orange',
            price: 300
        },
    ];

画像①
Screen Shot 2019-07-27 at 22.03.28.png

アプローチとしては以下。
1.配列のプロパティの値が全部で何種類なのか検出する
2.それぞれのpriceを配列として更にグループ分け
3.二重のfor文で表示させる

以下のようなコードを買いたところ、期待通りの結果を得ることが出来た。

index.js
    function fruitArrGroupBy(list, keyGetter) {
        const map = new Map();
        list.forEach((item) => {
            const key = keyGetter(item);
            const collection = map.get(key);
            if (!collection) {
                map.set(key, [item]);
            } else {
                collection.push(item);
            }
        });
        return map;
    }
    var groupedFruitArr = fruitArrGroupBy(fruits, fruit => fruit.name);

    //配列のキーが何種類あるのかを出す(重複を外した配列を生成する)
    var fruitTypeList = fruits.filter(function(v1,i1,a1){ 
        return (a1.findIndex(function(v2){ 
            return (v1.name===v2.name) 
        }) === i1);
    });

    //HTMLにレンダリング
    for (let i = 0; i < fruitTypeList.length; i++) {
        $("body").append(`<h1>${fruitTypeList[i].name}</h1>
                           <ul class="wrapper${i}"></ul>`);
        $("body").append(``);
        var fruitItemArray = groupedFruitArr.get(`${fruitTypeList[i].name}`);
        for(var j = 0;j < fruitItemArray.length ;j++){
            $(`.wrapper${i}`).append(`<li>${fruitItemArray[j].price}円</li>`)
        }
    }

しかし、もっとコードを改良出来るのではと思い信頼のおける先輩に相談したところ、親切に色々とフィードバックいただき、ここまで短くすることができた。
(※大きな表示の変更は変わらないという前提で)

index.js
        var fruitsObj = {};
        var fruitsObjArr = [];
        // fruitsObj[number] = { name: 'Newcomer!' },
        for (var k = 0; k < fruits.length; k++) {
            //キー名に配列の値が無かったら
            if (fruits[k].name in fruitsObj != true) fruitsObjArr = [];
            fruitsObjArr.push(fruits[k].price);
            console.log(fruitsObjArr);
            fruitsObj[fruits[k].name] = fruitsObjArr;
        }
        console.log(fruitsObj);

        for (const key in fruitsObj) {
            $('body').append(`<h1>${key}</h1><ul class="wrapper-${key}"></ul>`);
            if (fruitsObj.hasOwnProperty(key)) {
                for (let i = 0; i < fruitsObj[key].length; i++) {
                    $(`.wrapper-${key}`).append(`<li>${fruitsObj[key][i]}円</li>`)   
                }
            }
        }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[slick.js] 最後のスライドまでいったら逆戻りしてしまう

「Slick」スライダーが、最後のスライドまで行くと、最初のスライドに高速で逆戻りしてしまう

サイトのトップページとか、商品ページとかで「Slickスライダー(slick.js)」を使っています。でも、横方向へのスライドを無限ループで表示させたとき、1(最初) → 2 → 3 → 4 → 5(最後) → 1 … と、同じ方向へスライドしていってほしいのに、なぜか逆方向へ高速で戻って行ってしまうという現象に、長いこと悩まされておりました。

CSSに一文を追加すればOK

結論から言うと、私の場合は、スタイルシートに
.slick-slider div { transition: none; }
という一文を追加するだけで、逆戻りしなくなりました。

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

[JavaScript] plotly.jsで時刻を表示する線の出し方を考えてみた件

概要

 シミュレーションを行っているときに統計結果を表示していたのだが、一目でシミュレーション中のデータがどこなのか表示したかった。しかし調べてみてもなかなかいい感じのものがなかったので、考えてみた。

使用ツール等

  • plotly.js
    • ver 1.49.0

コード

 説明するより先にコードと動いている例を見てもらおうと思う。

  • html

    <!DOCTYPE html>
    <html>
    <head>
        <title>plotly.jsで時刻を表示する線の出し方を考えてみた件</title>
        <meta charset="UTF-8">
        <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
        <script src="./main.js"></script>
    </head>
    <body>
        <div id="graph" style="height: 400px; width: 100%"></div>
    </body>
    </html>
    
  • javascript

    window.onload = (e) => {
        /* グラフを表示 */
        let id = 'graph';
        let data = [
            /* グラフデータ */
            {
                x: [0, 1, 2, 3, 4, 5],
                y: [10, 30, 60, 40, 50, 70],
                type: 'line'
            },
            /* 時間を表す線の初期データ */
            {
                x: [0, 0],
                y: [10, 70],
                type: 'line'
            }
        ];
        let layout = {};
        Plotly.newPlot(id, data, layout);
        /* 1秒ごとに時間を表す線を移動させる */
        let i = 0;
        setInterval((e) => {
            /* データを更新する */
            i += 0.5;
            if (i > 5) {
                i = 0;
            }
            // data[1] = {...} の場合、凡例で非表示にしても1秒後に表示されしまうので注意
            data[1].x = [i, i];
            /* updateを行い反映させる */
            Plotly.update(id, data, layout);
        }, 1000);
    };
    
  • 動作例

See the Pen WVoZYm by P488 (@P488) on CodePen.

コードの解説

  1. グラフを表示する時に、グラフのデータに加えて線のデータを追加する。
    この時のxの値は開始位置、yの値はグラフの最大と最小を指定する。

    let data = [
        /* グラフデータ */
        {
            x: [0, 1, 2, 3, 4, 5],
            y: [10, 30, 60, 40, 50, 70],
            type: 'line'
        },
        /* 時間を表す線の初期データ */
        {
            x: [0, 0],
            y: [10, 70],
            type: 'line'
        }
    ];
    
  2. setIntervalを使用してx軸の値を変化させ、あたかも動いているように見せる。

    /* 1秒ごとに時間を表す線を移動させる */
    let i = 0;
    setInterval((e) => {
        /* データを更新する */
        i += 0.5;
        if (i > 5) {
            i = 0;
        }
        // data[1] = {...} とした場合、凡例で非表示にしてもupdateで再表示されしまうので注意
        data[1].x = [i, i];
        /* updateを行い反映させる */
        Plotly.update(id, data, layout);
    }, 1000);
    

終わりに

今回掲載したコードは掲載用に作成したコードなので、穴があると思うが容赦して貰いたい。今後もPlotlyを使用してもらえた思う次第...

何かあったらコメントください。

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

HTML13行あればWebVRアプリが作れる! A-Frame連載(1)

はじめに

Webブラウザで動かせるA-Frameの紹介です(今更ですが)。
何回続くか分かりませんが、週刊ぐらいでA-Frameの連載してみます。

完成図

aframe.gif

全体ソースコード

<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Web VR Test</title>
    <script src="https://aframe.io/releases/0.9.2/aframe.min.js"></script>
  </head>
  <body>
    <a-scene>
      <a-sky color="#a0d8ef" wireframe="true"></a-sky>
      <a-box position="0 0 -10" width="2" height="1" depth="1.5" color="#333"></a-box>
    </a-scene>
  </body>
</html>

ちなみに公式ドキュメントのサンプルも14行で、やっていることは似たようなものです。
https://aframe.io/docs/0.9.0/introduction/

コード解説

  <head>
...
    <script src="https://aframe.io/releases/0.9.2/aframe.min.js"></script>
  </head>

WebGLのJavaScriptライブラリ「Three.js」を元に作られた、WebVR用フレームワーク『A-Frame』を読み込んでいます。

    <a-scene>
...
    </a-scene>

A-Frameはこんな感じに、HTML要素のように記述していきます。

a-sceneの宣言は必須で、これを入れるだけでVR空間が出来上がります。
更に、完成図右下にあるようなVRアイコンや、VRデバイスで見た時のトラッキング等の実現がこれ一つでできます。
Three.jsでVRアプリを作ろうとしたことがある方なら、この手軽さには感動モノ。

      <a-sky color="#a0d8ef" wireframe="true"></a-sky>

a-skyで空を定義できます。
wireframe=trueを入れないとベタ塗りの空が表示されて上下左右が分かりづらいので、敢えてワイヤーフレームにしています。

      <a-box position="0 0 -10" width="2" height="1" depth="1.5" color="#333"></a-box>

a-boxを入れることで直方体(、立方体)ができます。
A-Frameは幅、高さ、奥行き(x,y,z軸)のサイズや位置・回転、色や各種プロパティをjsコードではなくHTML属性として記述することで操作ができるので、可読性高く記述ができます。

最後に

A-Frameは公式ドキュメントやチュートリアルが丁寧ですが英語なので、英語が分からないと少々ハードルが高いかもしれません。

https://aframe.io/

記述自体は非常にシンプルなので、ぜひいろいろ使ってみてください。
次回はA-Frameで3Dオブジェクトの選択できるようにしてみます。

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

2019 7月・令和元年・Nuxt .js・LIFF動向

はじめに

この記事は、WebBenchというプログラミングを「アクティブラーニング」形式で、
学ぶ勉強会を実施している筆者が分かりやすくプログラミングの技術を伝達するために作成しました。
過去に何回か、セミナーを都内で実施しております。
団体の説明などはこちら→Web Bench 公式 https://twitter.com/bench_web
note→https://note.mu/kaji_____/n/n50c684f7eb6b

Nuxt.jsとは、何ぞや

Nuxt.js(ナクストと読みます)とはユニバーサルなVue.jsアプリケーションを構築するためのフレームワーク
*UI構築のためのJSライブラリと思っていただければ大丈夫です。
*React.jsベースのSSR用フレームワークであるNext.jsに触発されて作成された、Vue.jsベースのフレームワークです。

UIの描画サポート、SSRをはじめとした様々なサポートを行ってくれるものとなります。

*SSR(サーバサイドレンダリング)とは「(元々ブラウザ上でしか動かなかった)JavaScriptをサーバー内部で実行して、HTMLを生成すること」を指します。

https://nuxtjs.org/

引用・参考資料・Vue.js製フレームワークNuxt.jsではじめるUniversalアプリケーション開発

https://html5experts.jp/potato4d/24346/

環境セットアップ

Node.jsのインストール
自分のPCのターミナルで $ node –vでバージョン確認できます

【Mac】 Homebrewを使う場合→brew –vでバージョン確認

$ brew update
$ brew install node.js

【Windows】http://nodejs.org/ja/から最新版をDL

vue-cliを利用

$ npm install -g vue-cli # 既にvue-cli を導入されているかたについて は不要です
$ vue init nuxt-community/starter-template nuxt_sample

サンプルプロジェクトのセットアップ

$ cd nuxt_sample
$ yarn
$ yarn dev

環境セットアップが難しいかた

Code sandboxというサービスを利用

https://codesandbox.io/

ブラウザ上で動くエディタ
npm package対応している。

こちらでも十分に演習することは可能です

以下・参考URL/記事

公式https://nuxtjs.org/

ルーティングの追加と非同期データの取得

Nuxt.js 上での開発における babel の対応文法のソース(設定プリ セット)https://github.com/vuejs/babel-preset-vue-app

参照URL https://html5experts.jp/potato4d/24346/

Laravel+NuxtでLIFFアプリを作ってみた
Laravel Vue.js LINE LIFF

https://techblog.scouter.co.jp/entry/2019/03/14/120000

LIFFとは

LINEアプリ上で動くフロントエンドのフレームワーク。
こちらもNuxtと組み合わせて使用されることがユースケースとして増えている

https://developers.line.biz/ja/docs/liff/overview/

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

Check! auth0-js の WebAuth.checkSession() でエラーが出てしまう場合の対処

こんにちは、 @dz_ こと大平かづみです。

Prologue

最近、 Auth0 をつかって認証機構を作ろうと試作しています。そこで躓いた点を共有します。

環境

フロントエンド(Vue.js)で Auth0 を使って認証をしようと試みています。基本的にはこのチュートリアル Auth0 Vue SDK Quickstarts: Login を参考にしています。

Vue.js に限らず、フロントエンドで Auth0 を使う場合は auth0-js を使うことが多いようです。

今回は、この auth0-js を利用している際にみつけた事象について記載します。

即タイムアウト?

タイムアウトするようなタイミングでもないのに下記のようなエラーが出た場合は、 Auth0 の管理画面から ApplicationsAllowed Web Origins の設定を確認してみてください。こちらのドキュメント Error response - Silent Authentication | Auth0 にも記載があるように、 Allowed Web Origins を正しく設定するとエラーが解消するかと思います。

Timeout during executing web_message communication

常に Login required エラー

上記エラーをクリアしたのに、今度は常に Login required

調査してみたところ、 auth0.js/README.md at master · auth0/auth0.jscheckSession() の説明を見ると、ソーシャルアカウントでログインしていて、開発環境(インターネット上でアクセス可能なドメインでない)場合は常に Login required になるようです。 :eyes:

Important: If you're not using the hosted login page to do social logins, you have to use your own social connection keys. If you use Auth0's dev keys, you'll always get login_required as an error when calling checkSession.

ですので、ソーシャルアカウントではなくメールアドレスとパスワードでログインするようにすれば、このエラーは解消できることを確認できました。 :raised_hands:

なお、インターネットからアクセスできるドメインで運用すればこのエラーは解消されると見受けられますが、まだ試しておりませんのでご了承くださいませ。

Epilogue

これで常にタイムアウトする事象は避けられました!(笑)
どんどん開発するぞ~!(๑•̀ㅂ•́)و✧

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

Vue.jsでカレンダーコンポーネント作ってみた

前置き

カレンダーと一言で言っても、機能も様々なので、
ひとまず機能もシンプルで、あまりごちゃごちゃさせてないものを紹介します。
vuemomentのみで作っています。

下記が主な機能:
1.<>ボタンで月単位での移動ができる
2.選択状態の概念があること(表示時は現在日付)
3.日付ごとにその日のTodoを表示できる

結構スタイルに依存するので、簡単なcssも記述します。

完成品

FireShot Capture 070 - vue_calender_component - localhost.png
んで、todoがある日付を選択すると、
FireShot Capture 072 - vue_calender_component - localhost.png
todoリストが出る仕組み。

まずはじめに

todoリストを別ファイルで作ってみます。
実際こういうコンポーネントを使うアプリを作る場合は、
だいたいDBから取得するパターンが多いと思います。
今回は簡易的に定数ファイル的なノリで作成。

todoList.js
const TODO_LIST = [
  {
    id: 1,
    title: "起床",
    description: "きっとねむい",
    date: "2019-07-11",
    time: "09:00",
  },
  {
    id: 2,
    title: "出勤",
    description: "まだねむい",
    date: "2019-07-11",
    time: "10:00",
  },
  {
    id: 3,
    title: "打ち合わせ",
    description: "",
    date: "2019-07-12",
    time: "11:00",
  },
  {
    id: 4,
    title: "作業",
    description: "なにやろうかななにやろうかな",
    date: "2019-07-12",
    time: "15:00",
  },
  {
    id: 5,
    title: "お風呂",
    description: "温度は43度",
    date: "2019-07-12",
    time: "19:00",
  },
  {
    id: 6,
    title: "カレンダー作り",
    description: "つくるぞつくるぞ",
    date: "2019-07-13",
    time: "14:00",
  },
];

export default TODO_LIST;

続いて、カレンダーのカレンダーの部分。

Calender.vue
<template>
  <div class="calender">
    <div class="calender-component">
      <div class="calender-header">
        <div class="arrow-back" v-on:click="changeMonth(0)"><</div>
        <div class="current-date">{{ dateLabel }}</div>
        <div class="arrow-next" v-on:click="changeMonth(1)">></div>
      </div>
      <div class="calender-body">
        <ul class="calender-panel-list">
          <li class="calender-panel_space" v-for="space in spaces"></li>
          <li
            class="calender-panel"
            v-on:click="selectDate"
            v-for="date in dates"
            :id="date.date"
            v-bind:class="selectedDate === date.date ? 'selected' : ''"
          >
            <div class="calender-date">
              {{ date.dateNumber }}
            </div>
            <div
              class="calender-todo"
              v-bind:class="date.todoNumber !== '-' ? 'number' : ''""
            >
              {{ date.todoNumber }}
            </div>
          </li>
        </ul>
      </div>
      <div class="calender-footer">
        <div
         class="calender-footer_todo"
         v-for="todo in todoList"
         v-show="todo.date === selectedDate"
        >
          <div class="calender-footer_todo-time">
            {{ todo.time }}
          </div>
          <div class="calender-footer_todo-title">
            {{ todo.title }}
          </div>
          <div class="calender-footer_todo-description">
            {{ todo.description }}
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import moment from 'moment';
import TODO_LIST from '../data/todoList';

export default {
  mixins: [TODO_LIST],
  data () {
    return {
      todoList: TODO_LIST, //todoリスト
      dates: [], //カレンダーの日付
      spaces: [], //その月の最初日が始まる場所
      dateLabel: "", //フォーマット="2019年7月"
      selectedMonth: null, //今選択している月
      selectedDate: null, //今選択している日付
    }
  },
  methods: {
    selectDate(event) { //日付を選択する
      this.selectedDate = event.currentTarget.id
    },
    changeMonth(num) { //月を変更
      if(num === 0) {
        this.selectedMonth = moment(this.selectedMonth).subtract(1, 'months');
      } else {
        this.selectedMonth = moment(this.selectedMonth).add(1, 'months');
      }
    }
  },
  created() { //画面表示時に今日の日付と月を設定。
    this.selectedDate = moment().format('YYYY-MM-DD');
    this.selectedMonth = moment();
  },
  watch: {
    selectedMonth: function() { //選択している月の変更時の処理、画面表示時も動きます
      this.dateLabel = moment(this.selectedMonth).format('YYYY年MM月'); //月ラベルを更新

      this.spaces = []; //スペースを初期化
      for(let i = 0; i < moment(this.selectedMonth).startOf('month').day(); i++) { //スペースを更新
        this.spaces[i] = i;
      }

      this.dates = []; //カレンダーパネルを初期化
      for(let i = 0; i < moment(this.selectedMonth).daysInMonth(); i++) { //カレンダーパネルを更新
        let todoNumber = '-';
        for(let k of Object.keys(this.todoList)) { //todoListの情報をカレンダーパネルに追加
          if(this.todoList[k].date === this.dates[i].date) {
            todoNumber++;
          }
        }
        this.dates[i] = {
          date: moment(this.selectedMonth).startOf('month').add(i, 'day').format('YYYY-MM-DD'),
          dateNumber: i + 1,
          todoNumber: todoNumber
        }
      }
    }
  }
}
</script>

ざっくりとした解説をしていきます。
createdにて、まずは今日の日付と、今月の値を取得。

this.selectedDate = moment().format('YYYY-MM-DD');
this.selectedMonth = moment();

methodsは二つ。
1.選択している日付を変更。

selectDate(event) { //日付を選択する
  this.selectedDate = event.currentTarget.id
},

2.選択している月を変更。

changeMonth(num) { //月を変更
  if(num === 0) {
    this.selectedMonth = moment(this.selectedMonth).subtract(1, 'months');
  } else {
    this.selectedMonth = moment(this.selectedMonth).add(1, 'months');
}

"<"には0、">"では1を返しております。

watchではselectedMonthの状態を監視。

selectedMonth: function() { //選択している月の変更時の処理、画面表示時も動きます
  this.dateLabel = moment(this.selectedMonth).format('YYYY年MM月'); //月ラベルを更新

  this.spaces = []; //スペースを初期化
  for(let i = 0; i < moment(this.selectedMonth).startOf('month').day(); i++) { //スペースを更新
    this.spaces[i] = i;
  }

  this.dates = []; //カレンダーパネルを初期化
  for(let i = 0; i < moment(this.selectedMonth).daysInMonth(); i++) { //カレンダーパネルを更新
    let todoNumber = '-';
    for(let k of Object.keys(this.todoList)) { //todoListの情報をカレンダーパネルに追加
      if(this.todoList[k].date === this.dates[i].date) {
        todoNumber++;
      }
    }
    this.dates[i] = {
      date: moment(this.selectedMonth).startOf('month').add(i, 'day').format('YYYY-MM-DD'),
      dateNumber: i + 1,
      todoNumber: todoNumber
    }
  }
}

ここで、選択されてる月(selectedMonth)の日数と、最初の日が何曜日かの計算と、todoListの情報も各日付に入れてます。
spacesとdatesの更新を行なっております。

this.spaces = [0]; //1日が火曜日の場合
this.spaces = [0, 1, 2, 3]; //1日が金曜日の場合

selectedMonthが変更されるたびに、watchの処理が走るので、
それに合わせてカレンダーも更新されます。

スタイルも乗せときます。

<style scoped>

.calender {
  width: 336px;
}

.calender-component {
  min-height: 320px;
  border: solid 1px gray;
  padding: 24px;
  box-sizing: border-box;
}

.calender-header {
  display: flex;
  justify-content: space-between;
}

.arrow-back {
  font-size: 16px;
  color: gray;
  user-select: none;
  cursor: pointer;
  text-align: center;
  width: 24px;
  height: 24px;
}

.arrow-back:hover {
  background-color: silver;
  border-radius: 4px;
}

.arrow-next {
  font-size: 16px;
  color: gray;
  user-select: none;
  cursor: pointer;
  text-align: center;
  width: 24px;
  height: 24px;
}

.arrow-next:hover {
  background-color: silver;
  border-radius: 4px;
}

.current-date {
  color: gray;
  user-select: none;
}

.calender-body {
  margin-top: 24px;
}

.calender-panel-list {
  display: flex;
  flex-wrap: wrap;
  padding: 0;
  justify-content: left;
}

.calender-panel {
  padding: 8px 0px;
  width: 40px;
  text-align: center;
  color: gray;
  font-size: 14px;
  user-select: none;
  list-style: none;
}

.calender-panel:hover {
  background-color: silver;
  border-radius: 4px;
}

.calender-date {
  cursor: pointer;
}

.selected {
  background-color: silver;
  border-radius: 4px;
}

.calender-todo {
  cursor: pointer;
  text-align:center;
  margin: 0px 8px;
  line-height: 25px;
}

.calender-panel_space {
  width: 40px;
  list-style: none;
}

.calender-footer_todo {
  border-top: 1px gray solid;
  padding-top: 12px;
  margin-top: 8px;
}

.calender-footer_todo-time {
  font-size: 18px;
  color: gray;
  word-break: break-word;
  text-decoration: underline;
}

.calender-footer_todo-title {
  font-size: 20px;
  color: gray;
  word-break: break-word;
}

.calender-footer_todo-description {
  font-size: 14px;
  color: gray;
  word-break: break-word;
}

.number {
  text-decoration: underline;
}

</style>

一行に日付が7日(曜日分)表示させるのは、スタイルで制御しちゃってます。

まとめと振り返り

実際にこれを使用したプロジェクトでは、1時間ぐらいで作ったので、
ところどころ直して、自分の備忘録として書いてみました。

ほどよくライフサイクルとディレクティブを使ってたりするので、
Vue初心者の学習にちょうどいいかもしれないです。

input type="date"コンポーネントの自作とかも、
これをベースに作れそうですよね。

ただ、スタイル依存の部分が多いという点で、
汎用性に怪しさを感じました。

あとqiitaに初めて投稿しましたが、
わかりやすく書くのってめちゃくちゃむずい。

気づいたところがあれば、都度更新していきたいです。

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

RailsでCSVダウンロード後の画面のリロード

あるデータの一覧のCSVダウンロードした後に画面の情報が変わっていることを確認させる必要があったときの開発メモ。

controllerからだとリダイレクトは出来ない。

自分の考えとしてはcontroller内のアクションにダウンロードした後にリダイレクトをさせれば良いと考え以下のように記載。

def download
    respond_to do |format|
      format.csv do
        send_data render_to_string, filename: "#{__method__}_#{Time.zone.now.strftime('%Y%m%d')}.csv", type: :csv
      end
    end
    redirect_to hoge_url
end

ただこれだとDouble renderとなり起こられてしまう。

解決 jsでリダイレクトさせる

slim

.text-right
  = link_to 'CSV', download_path(format: :csv),
    class: 'btn btn-link', id:'download-csv', target: '_blank'

js

  $('#download-csv').click -> location.reload()

参考: http://tshidax.hatenablog.com/entry/2017/09/11/120000

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

即席のinput要素が欲しいときはquicksettingsが便利そう

ブラウザ用JSライブラリやアプリの簡単なAPI動作確認などでinput要素が欲しいときがあります。
例えば以下のようなクラスがあったとして、add/subメソッドがちゃんと動いているのかを、実際に色々値を入れて確認したいとき、input type="number"要素を用意したりします。

class CounterApp {
  constructor(root) {
    this._count = 0;
    this.viewElement = document.createElement('div');
    root.appendChild(this.viewElement);
    this.viewElement.innerText = this._count;
  }

  add(v) {
    this._count += v;
    this.viewElement.innerText = this._count;
  }

  sub(v) {
    this._count -= v;
    this.viewElement.innerText = this._count;
  }
}

const app = new CounterApp(document.querySelector("#app"));
const input = document.createElement('input');
input.type = "number";
input.onchange = function(e) {
  const num = Number(e.target.value);
  app.add(num);
}
document.body.appendChild(input);

ただ、そのためだけに冗長なhtmlやらjsやらを正直書きたくはないので、ライブラリに頼りたいところです。
この手のライブラリはdat.guiしか知らなかったのですが、こちらはオブジェクトのpublicなプロパティを指定する必要があり、また値を直接変えたいわけではなので、用途としてはちょっと違うなと思いました。

もっと簡単で用途に合ったライブラリは無いものかとネットの海をさまよっていたら、quicksettingsというライブラリがsweeeetな感じだったので紹介します。
demo
デモページを見れば何となく分かると思いますが、簡単なコードでパネルコンポーネントの生成し、そこに色々なinput要素などを追加できます。

使い方

githubのREADMEを見れば大体わかりますが、ポイントだけ書いてみます。
以下に示すコードではライブラリはすでに読み込んでる前提となります。
scriptタグでもrequireでも読み込めます。

<script src="https://cdn.jsdelivr.net/npm/quicksettings@latest/quicksettings.min.js"></script>

インスタンス生成

create関数でインスタンスを生成・取得します。

// settings = QuickSettings.create(x, y, panelTitle, parent);
const settings = QuickSettings.create(0, 0, "タイトル", document.getElementById('qs-parent'));

引数で初期位置x、y、パネルのタイトル、親要素を指定できます。
引数はすべて省略可能。その場合、左上にパネルが表示されますが、邪魔になりそうならx、yを指定します。

個人的には以下のように指定して右上に配置するのがおすすめです。

const settings = QuickSettings.create(window.innerWidth-200);

※パネル幅がデフォルトの200pxの場合。パネルサイズをsettings.setSize等で変える場合は適宜調整します。
なお、パネルはドラッグ操作で移動させることもできます。

:grimacing:(ここはdat.guiを見習ってデフォルト右上配置にしてほしい…)

パネルに要素を追加

要素はsettings.addXxxみたいな関数で追加します。
それぞれ引数が違い、ラベル名や初期値、限界値などを指定できます。
またすべてのメソッドで値変更やインタラクション時のcallbackを指定できます。

const cat = new Cat();
const settings = QuickSettings.create();

// input:text
settings.addText("名前", (v)=> {
  // コールバックで変更値vが取得可能
  cat.setName(v);
});
// チェックボックス
settings.addBoolean("寝", cat.isSleep, (v)=> {
  cat.setSleep(v);
});
// ボタン
settings.addButton("鳴く", ()=> {
  cat.meow();
});

何を追加できるか、引数はどうなっているのかは公式のリストを参照して下さい。

他にも指定オブジェクトとバインディングしたり、localStorageに設定値を保存できたりするそうですが、使ったことが無いので保留。
古典SFチックなデザインと相まって、割と気に行ってます。

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

連想配列の基本操作【JavaScript/C#/VB/Python/CommonLisp/HSP】

最近いくつかの言語の連想配列の操作を調査したのでそれをここにまとめておく。

JavaScript

JavaScriptで連想配列を使うには昔ながらのオブジェクトを使ったものとES6で追加されたMapクラスを使う方法があります。

Object

"use strict";

//初期化
const kantoh={
    "茨城県":"水戸市",
    "栃木県":"宇都宮市",
    "群馬県":"前橋市",
    "埼玉県":"さいたま市",
    "神奈川県":"横浜市"
};
//代入
kantoh["千葉県"]="千葉市";
//要素の有無
console.log("千葉県" in kantoh);
//追加と代入は同じ
kantoh["東京都"]="新宿区";
//要素が登録済みなら代入したくない場合
if(!("東京都" in kantoh)){
    kantoh["東京都"]="新宿区";
}
else{
    console.log("同一のキーを含む項目が既に追加されています。");
}
//取得
console.log(kantoh["千葉県"]);
//代入演算子も使用可
kantoh["千葉県"]+="中央区";
console.log(kantoh["千葉県"]);
//要素数
console.log(Object.keys(kantoh).length);
//要素の削除
delete kantoh["千葉県"];
//キーの配列
console.log(Object.keys(kantoh).join(","));
//値の配列
console.log(Object.values(kantoh).join(","));
//ループ
for(let [key,value] of Object.entries(kantoh)){
    console.log(`key:${key} value:${value}`);   
}
//Join
const joinDict=
    dict=>Object.entries(dict).map(v=>v.join(":")).join(", ");
console.log(joinDict(kantoh));
//要素のクリア
for(let i of Object.keys(kantoh)) delete kantoh[i];
kantoh["なし"]="なにもないよ";
console.log(joinDict(kantoh));

Map

"use strict";

//初期化
const kantoh=new Map([
    ["茨城県","水戸市"],
    ["栃木県","宇都宮市"],
    ["群馬県","前橋市"],
    ["埼玉県","さいたま市"],
    ["神奈川県","横浜市"]
]);
//代入
kantoh.set("千葉県","千葉市");
//要素の有無
console.log(kantoh.has("千葉県"));
//追加と代入は同じ
kantoh.set("東京都","新宿区");
//要素が登録済みなら代入したくない場合
if(!kantoh.has("東京都")){
    kantoh.set("東京都","新宿区");
}
else{
    console.log("同一のキーを含む項目が既に追加されています。");
}
//取得
console.log(kantoh.get("千葉県"));
//代入演算子は使用不可
const resetAdd=(dict,key,value)=>dict.set(key,dict.get(key)+value);
resetAdd(kantoh,"千葉県","中央区");
console.log(kantoh.get("千葉県"));
//要素数
console.log(kantoh.size);
//要素の削除
kantoh.delete("千葉県");
//キーの配列
console.log(Array.from(kantoh.keys()));
//値の配列
console.log(Array.from(kantoh.values()));
//ループ
for(let [key,value] of kantoh){
    console.log(`key:${key} value:${value}`);   
}
//Join
const joinDict=
    dict=>Array.from(dict).map(v=>v.join(":")).join(", ");
console.log(joinDict(kantoh));
//要素のクリア
kantoh.clear();
kantoh.set("なし","なにもないよ");
console.log(joinDict(kantoh));

Mapは最近新しく追加された機能であるだけあってメソッドが綺麗に整備されています。
しかし、初期化や取得、代入リテラルがオブジェクトに比べて少し不自然なのが辛いところ。

Python

#初期化
kantoh={
    "茨城県":"水戸市",
    "栃木県":"宇都宮市",
    "群馬県":"前橋市",
    "埼玉県":"さいたま市",
    "神奈川県":"横浜市"
}
#代入
kantoh["千葉県"]="千葉市"
#要素の有無
print("千葉県" in kantoh)
#追加と代入は同じ
kantoh["東京都"]="新宿区"
#要素が登録済みなら代入したくない場合
if "東京都" not in kantoh:
    kantoh["東京都"]="新宿区"
else:
    print("同一のキーを含む項目が既に追加されています。")
#取得
print(kantoh["千葉県"])
#代入演算子も使用可
kantoh["千葉県"]+="中央区"
print(kantoh["千葉県"])
#要素数
print(len(kantoh))
#要素の削除
del kantoh["千葉県"]
#キーの配列
print(list(kantoh.keys()))
#値の配列
print(list(kantoh.values()))
#ループ
for key,value in kantoh.items():
    print(f"key:{key} value:{value}")
#Join
joinDict=(
    lambda dict:",".join([":".join(v) for v in dict.items()]))
print(joinDict(kantoh))
#要素のクリア
kantoh.clear()
kantoh["なし"]="なにもないよ"
print(joinDict(kantoh))

Pythonのdictは言語レベルで組み込まれていてリテラルも何も不自由なく使用できます。

C#

using System;
using System.Collections.Generic;
using System.Linq;

class Program{
    static void Main(){
        //初期化
        var kantoh=new Dictionary<string,string>{
            {"茨城県","水戸市"},
            {"栃木県","宇都宮市"},
            {"群馬県","前橋市"},
            {"埼玉県","さいたま市"},
            {"神奈川県","横浜市"}
        };
        //代入
        kantoh["千葉県"]="千葉市";
        //要素の有無
        Console.WriteLine(kantoh.ContainsKey("千葉県"));
        //追加
        kantoh.Add("東京都","新宿区");
        //要素が登録済みの場合、ArgumentException
        try{
            kantoh.Add("東京都","新宿区");
        }
        catch(ArgumentException ex){
            Console.WriteLine(ex.Message);
        }
        //取得
        Console.WriteLine(kantoh["千葉県"]);
        //代入演算子も使用可
        kantoh["千葉県"]+="中央区";
        Console.WriteLine(kantoh["千葉県"]);
        //要素数
        Console.WriteLine(kantoh.Count);
        //要素の削除
        kantoh.Remove("千葉県");
        //キーの配列
        Console.WriteLine(string.Join(",",kantoh.Keys));
        //値の配列
        Console.WriteLine(string.Join(",",kantoh.Keys));
        //ループ
        foreach(var v in kantoh){
            Console.WriteLine($"key:{v.Key} value:{v.Value}");
        }
        //Join
        Func<Dictionary<string,string>,string> joinDict=
            dict=>string.Join(", ",dict.Select(v=>v.Key+":"+v.Value));
        Console.WriteLine(joinDict(kantoh));  
        //要素のクリア
        kantoh.Clear();
        kantoh["なし"]="なにもないよ";
        Console.WriteLine(joinDict(kantoh));  
    }
}

極々普通な辞書。静的言語なので型定義はしておく必要があります。

VB

Option Strict On
Option Infer On
Imports System.Collections.Generic
Imports System.Linq

Module Program
    Sub Main()
        '初期化
        Dim kantoh As New Dictionary(Of String,String) From {
            {"茨城県","水戸市"},
            {"栃木県","宇都宮市"},
            {"群馬県","前橋市"},
            {"埼玉県","さいたま市"},
            {"神奈川県","横浜市"}
        }
        '代入
        kantoh("千葉県")="千葉市"
        '要素の有無
        Console.WriteLine(kantoh.ContainsKey("千葉県"))
        '追加
        kantoh.Add("東京都","新宿区")
        '要素が登録済みの場合、ArgumentException
        Try
            kantoh.Add("東京都","新宿区")
        Catch ex As ArgumentException
            Console.WriteLine(ex.Message)
        End Try
        '取得
        Console.WriteLine(kantoh("千葉県"))
        '代入演算子も使用可
        kantoh("千葉県")+="中央区"
        Console.WriteLine(kantoh("千葉県"))
        '要素数
        Console.WriteLine(kantoh.Count)
        '要素の削除
        kantoh.Remove("千葉県")
        'キーの配列
        Console.WriteLine(String.Join(",",kantoh.Keys))
        '値の配列
        Console.WriteLine(String.Join(",",kantoh.Keys))
        'ループ
        For Each v In kantoh
            Console.WriteLine($"key:{v.Key} value:{v.Value}")
        Next
        'Join
        Dim joinDict As Func(Of Dictionary(Of String,String),String)=
            Function(dict) String.Join(", ",dict.Select(Function(v) v.Key & ":" & v.Value))
        Console.WriteLine(joinDict(kantoh))
        '要素のクリア
        kantoh.Clear()
        kantoh("なし")="なにもないよ"
        Console.WriteLine(joinDict(kantoh))
    End Sub
End Module

C#と同じ。

HSP

HSPの言語機能に連想配列なんてものは存在しないので
以前自作した連想配列モジュール「DictionaryOnHSP」を使用します。
大体.NET(C#,VB)っぽい命令/関数群が用意されています。

#runtime "hsp3cl"
#cmpopt varinit 1
#include "Dictionary.as"

;初期化
new@Dictionary kantoh,"str",,,{"
    茨城県:水戸市,
    栃木県:宇都宮市,
    群馬県:前橋市,
    埼玉県:さいたま市,
    神奈川県:横浜市
"}
;代入
dcSet kantoh,"千葉県","千葉市"
;要素の有無
mes dcContainsKey(kantoh,"千葉県")
;追加
dcAdd kantoh,"東京都","新宿区"
;要素が登録済みの場合、stat1が返り何もしない
dcAdd kantoh,"東京都","新宿区"
if 1=stat{
    mes "同一のキーを含む項目が既に追加されています。"
}
;取得
mes dcItem(kantoh,"千葉県")
;代入演算子っぽい何かも使用可
dcReSet kantoh,"千葉県","+","中央区"
mes dcItem(kantoh,"千葉県")
;要素数
mes dcCount(kantoh)
;要素の削除
dcRemove kantoh,"千葉県"
;キーの配列
sdim keys: dcRefKeys kantoh,keys
mes dcJoinArray(keys,",")
;値の配列
sdim values: dcRefValues kantoh,values
mes dcJoinArray(values,",")
;ループ
dcForeach kantoh
    mes strf("key:%s value:%s",dcKeys(kantoh,cnt),dcValues(kantoh,cnt))   
loop
;Join
mes dcJoinDict(kantoh,":",",")   
;要素のクリア
dcClear kantoh
dcSet kantoh,"なし","なにもないよ"
mes dcJoinDict(kantoh)

CommonLisp

全体的にかなり冗長。
連想リストという選択肢もあります。

(let(
        ;初期化
        (kantoh (make-hash-table :test #'equal)))
    ;初期化は愚直
    (setf
        (gethash "茨城県" kantoh) "水戸市"
        (gethash "栃木県" kantoh) "宇都宮市"
        (gethash "群馬県" kantoh) "前橋市"
        (gethash "埼玉県" kantoh) "さいたま市"
        (gethash "神奈川県" kantoh) "横浜市")
    (format t "~A~%" (gethash "千葉県" kantoh))
    ;代入
    (setf (gethash "千葉県" kantoh) "千葉市")
    ;要素の有無 gethashの返り値の2価を
    (format t "~A~%" (multiple-value-list(gethash "千葉県" kantoh)))
    ;追加と代入は同じ
    (setf (gethash "東京都" kantoh) "新宿区")
    ;要素が登録済みなら代入したくない場合
    (if (not(cadr(multiple-value-list(gethash "東京都" kantoh))))
        (setf (gethash "東京都" kantoh) "新宿区")
        (write-line "同一のキーを含む項目が既に追加されています。"))
    ;取得
    (format t "~A~%" (gethash "東京都" kantoh))
    ;代入演算子なんてない
    (setf (gethash "千葉県" kantoh)
        (concatenate 'string (gethash "千葉県" kantoh) "中央区"))
    ;要素数
    (format t "~d~%" (hash-table-count kantoh))
    ;要素の削除
    (remhash "千葉県" kantoh)
    ;キーの配列
    (format t "~A~%" (loop for key being the hash-keys in kantoh collect key))
    ;値の配列
    (format t "~A~%" (loop for key being the hash-keys in kantoh using (hash-value value) collect value))
    ;ループ
    (maphash #'(lambda(key value)
            (format t "key:~A value:~A~%" key value))
        kantoh)
    ;Join
    (labels((joinDict(dict)
            (format nil "~{~A~^,~}" 
                (mapcar #'(lambda(item) (concatenate 'string (car item) ":"  (cadr item)))
                    (loop for key being the hash-keys in kantoh using (hash-value value) collect
                        (list key value))))))
        (format t "~A~%" (joinDict kantoh))
        ;要素のクリア
        (maphash #'(lambda(key value) (remhash key kantoh)) kantoh)
            (setf (gethash "なし" kantoh) "なにもないよ")
            (format t "~A~%" (joinDict kantoh))))
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

カスタムイベントを簡単にbindするためのmixinを用意する

これもionicから参照

export const connectListeners = (doc: Document) => {
  if (lastId === 0) {
    lastId = 1;
    // trap focus inside overlays
    doc.addEventListener('focusin', ev => {
      const lastOverlay = getOverlay(doc);
      if (lastOverlay && lastOverlay.backdropDismiss && !isDescendant(lastOverlay, ev.target as HTMLElement)) {
        const firstInput = lastOverlay.querySelector('input,button') as HTMLElement | null;
        if (firstInput) {
          firstInput.focus();
        }
      }
    });

    // handle back-button click
    doc.addEventListener('ionBackButton', ev => {
      const lastOverlay = getOverlay(doc);
      if (lastOverlay && lastOverlay.backdropDismiss) {
        (ev as BackButtonEvent).detail.register(100, () => {
          return lastOverlay.dismiss(undefined, BACKDROP);
        });
      }
    });

    // handle ESC to close overlay
    doc.addEventListener('keyup', ev => {
      if (ev.key === 'Escape') {
        const lastOverlay = getOverlay(doc);
        if (lastOverlay && lastOverlay.backdropDismiss) {
          lastOverlay.dismiss(undefined, BACKDROP);
        }
      }
    });
  }
};
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

HTML elementに手軽にpropertyを追加する方法

cosnt element = document.createElement('div');

Obejct.assign(elemnt, {
id: 'overlay',
width: 100,
height: 100,
});

プロパティ付きのdomが作れる話。

ionicのここら辺の作り方がすごく良いなぁと思った

    const doc = document;
    const element = doc.createElement(tagName) as HTMLIonOverlayElement;
    connectListeners(doc);

    // convert the passed in overlay options into props
    // that get passed down into the new overlay
    Object.assign(element, opts);
    element.classList.add('overlay-hidden');
    const overlayIndex = lastId++;
    element.overlayIndex = overlayIndex;
    if (!element.hasAttribute('id')) {
      element.id = `ion-overlay-${overlayIndex}`;
    }

    // append the overlay element to the document body
    getAppRoot(doc).appendChild(element);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

NeustarでJSONをPOSTする

ハマったので備忘。
APIにJSON渡して、戻ってきたJSONを受け取ってまたゴニョゴニョしたいときなどに。

var client = test.openHttpClient();
client.setFollowRedirects(false);

test.beginTransaction();
test.beginStep("Step-001");

// Request
var url = 'https://example.com/api/getName';
var request = client.newPost(url);
request.addRequestHeader("Content-Type", "application/x-www-form-urlencoded");

var json = {
   id:'hoge'
};
var strJson = JSON.stringify(json);
request.setRequestBody(strJson);

// Response
var response = request.execute();
var code = response.getStatusCode();
test.log(code);
var body = response.getBody();
test.log(body);

var obj = JSON.parse(body)
test.log(obj.name);

test.endStep();
test.endTransaction();
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

node.jsで簡易データベースとしてExcelファイルを使う

node.jsで簡易データベースとしてExcelファイルを使う

MySQLを使うほど大袈裟にせず手軽にデータベースを使いたい場合、簡易データベースとしてExcelが適している場合がある。
そこでnode.jsでExcelファイルを読み書きできるライブラリを探した。
ヘッダなどは色付けしておきたいので書式情報は維持したまま保存したい。

SheetJS

高機能だがPro版でないと書式情報が保存されないようなので却下。
公式ページ

xlsx-populate

xlsx-populateなら書式情報を維持しながらExcelファイルを読み書きできる。
Github

インストール方法

npm install xlsx-populate

使い方

const XlsxPopulate = require('xlsx-populate')

XlsxPopulate.fromFileAsync("./Input.xlsx")
    .then(book => {
        const sheet1 = book.sheet("Sheet1")
        sheet1.cell("A2").value(10)
        sheet1.cell("B2").value(20)
        sheet1.cell("C2").value(30)
        book.toFileAsync("./Output.xlsx")
    })

xlsx-populate-wrapper

xlsx-populateをラッピングしたxlsx-populate-wrapperを使うとさらに使いやすくなる。

Github

インストール方法

npm install xlsx-populate-wrapper

使い方

const xPopWrapper = require("xlsx-populate-wrapper")
const path = require("path")

const filePath = path.resolve('./sample_data.xlsx')
const workbook = new xPopWrapper(filePath)

workbook.init()
  .then(wb => {
    // シート名の一覧を取得
    console.log(wb.getSheetNames())
    // => ['Sheet1', 'Sheet2', 'Sheet3']

    // シート名を指定してヘッダ一覧の取得
    console.log(workbook.getHeadings('Sheet1'))
    // => ['Title1', 'Title2', 'Title3']

    // シート名を指定してJSONデータとして取得
    const jsonData = workbook.getData('Sheet1')
    console.log(jsonData)
    /*
      [
        {
          'Title1': '1',
          'Title2': '2',
          'Title3': '3',
        },
        {
          'Title1': '5',
          'Title2': '6',
          'Title3': '7',
        },
        {
          'Title1': '11',
          'Title2': '12',
          'Title3': '13',
        }
      ]
    */

    // データを書き換える
    jsonData[0].Title3 ='a'
    jsonData[1].Title3 ='b'
    jsonData[2].Title3 ='c'

    workbook.update('Sheet1', jsonData) // 更新

    return workbook.commit() // ファイルに書き込み
  })
  .catch(error => {
    throw error
  })

書き換え前のExcelファイル

image.png

実行ログ

image.png

書き換え後のExcel

image.png

ExcelデータをJSONデータとして扱えるのでjQueryやunderscoreを組み合わせるとwhere検索や絞り込みが行えるのでかなり使えるノウハウと思います☺

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

Svelteでwatchする

Svelteで使える router ないかなーと思ってsvelte-spa-router のソースを見ていたら、結構参考になったのでメモ。

Svelteで変数をwatchする

Svelteの機能で、vuejs の computed にあたる$:記法があり、これは通常は vuejs 同様に

svelte
$:fifth = $hogeStore.length * 5
$:tenth = $hogeStore.fuga.length * 10

みたいな感じで使うのだが

svelte
let fifth = null;
let tenth = null;
$:{
  fifth = $hogeStore.length * 5
  tenth = $hogeStore.fuga.length * 10
}

こういう感じで hogeStore を watch 出来る、この例だとあんまり有難みがないが、下記みたいな感じで利用されていた。

svelte-spa-router
/* routersListは最初に定義されているpathとcomponent名のセット */
/* $loc.location(現在のURL)が変更された場合に、
 そのpathに該当するcomponentを探してセットしている */

$: {
    component = null
    let i = 0
    while (!component && i < routesList.length) {
        const match = routesList[i].match($loc.location)
        if (match) {
            /* componentが表示されるコンポーネント */
            component = routesList[i].component
            componentParams = match
        }
        i++
    }
}

Storeでイベントを監視する

Svelte の store で個人的に謎だったのが readable の存在で、どういう時に使うものなのか疑問だったのだが、こちらも svelte-spa-router で「監視しているイベントが発火したら store を更新する」という使い方をされていた。

svelte-spa-router
export const loc = readable(
    /* getLocationは現在のURLとQueryを返してくる */
    getLocation(),

    function start(set) {
        const update = () => {
            set(getLocation())
        }
        /* hashchangeイベントが発火したらupdateを実行してstoreを更新する */
        window.addEventListener('hashchange', update, false)

        return function stop() {
            window.removeEventListener('hashchange', update, false)
        }
    }
)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript/jQuery】固定ヘッダーに苦労したので作ってみた

固定ヘッダーとは

テーブルのある画面でスクロールをした時、
下の方へスクロールしてもヘッダーが画面上部に表示されている状態。

デモ

fixheader.mov.gif

sampleで動かせます。

前提

  • サクッと固定ヘッダーを実装したい
  • 横スクロールやwindowスクロールにも対応が必要
  • Chrome/Firefox/IEでも動作必須(辛い)でstickyが使えない
  • jQuery使用可能(>1.8~)

動作確認環境

Chrome / FireFox / IE

背景

ライブラリもあるのですが、リサイズが絡むとかなり重くなってしまうため
簡易版を作ってみました。

リサイズがかかってしまうと全てのwidthにcss当て直しなので
そこだけどうしても重くなってしまうんですよね・・・
何かオススメの方法あれば教えてください。

ソース

Github

何をやっているのかというと
1. ヘッダー固定したい対象テーブルについて、同要素の直前にtableを追加
2. 元のテーブルのセル幅から、元テーブルとcloneテーブル双方のcolgroupに追加してcolで幅指定
3. テーブル単位でも幅を合わせる
4. windowのresizeやscollを起点にイベント追加

です。

JSのソース

fixHeader.js
$(document).ready(function () {
    let fixHeader = new FixHeader();
});

function FixHeader() {
    this.init();
}

FixHeader.prototype = {
    init: function () {
        this._tableClass = '.fix_header_table';
        this._headerClass = '.cloned_header';
        this._cloneTableElement = '<table class="cloned_header" style="display: none; position: fixed; top: 0; table-layout: fixed;"><colgroup></colgroup></table>';

        this.tableObj = $(this._tableClass);
        this.originalHeaderObj = this.tableObj.find('thead');
        this.cloneHeaderObj = {};

        this.colNum = this.originalHeaderObj.find('tr').children().length;

        this.appendElements();
        this.eventBuild();
    },

    eventBuild: function() {
        let self = this;
        self.switchHeaderWithScroll();
        self.detectWindowResize();
    },

    appendElements: function() {
        let self = this;

        self.tableObj.before(self._cloneTableElement);
        self.cloneHeaderObj = $(self._headerClass);
        self.cloneHeaderObj.append(self.tableObj.find('thead').clone());

        self.adjustHeaderAndTableWidth(self.getOffsetWidth(self.tableObj.get(0)))

        self.tableObj.prepend('<colgroup></colgroup>')
        for ($i = 0; $i < self.colNum; $i++) {
            colWidth = self.getOffsetWidth(self.originalHeaderObj.find('tr').children().get($i));

            self.cloneHeaderObj.find('colgroup').append("<col style='width: " + colWidth + "px'>");
            self.tableObj.find('colgroup').append("<col style='width: " + colWidth + "px'>");
        }
    },

    switchHeaderWithScroll: function() {
        let self = this;
        $(window).scroll(function () {
            if($(window).scrollTop() >  self.tableObj.offset().top
              && $(window).scrollTop() < ( self.tableObj.offset().top + self.tableObj.height() ) ) {
                self.cloneHeaderObj
                    .show()
                    .css('left', -$(window).scrollLeft() + self.tableObj.offset().left);
            } else {
                self.cloneHeaderObj.hide();
            }
        });
    },

    detectWindowResize: function() {
        let self = this;
        let timer = 0;

        $(window).on('resize', function () {
          if (timer > 0) {
            clearTimeout(timer);
          }

          timer = setTimeout(function () {
            self.resizeHeaderAllCol();
          }, 200);
        });
    },

    resizeHeaderOneCol: function() {
        let self = this;
        $(document).on('resizeFixHeaderCol', function (e, colNo) {
            self.tableObj.css({'min-width': ''});
            self.tableObj.find('colgroup').children().eq(colNo).css({'width': ''});

            self.adjustHeaderAndTableWidth(self.getOffsetWidth(self.tableObj.get(0)))
            self.adjustColWidthOriginHeaderByColNo(colNo)
        });
    },

    resizeHeaderAllCol: function() {
        let self = this;

        self.tableObj.css({'min-width': ''});
        self.tableObj.find('colgroup').children().css({'width': ''});

        self.adjustHeaderAndTableWidth(self.getOffsetWidth(self.tableObj.get(0)))
        for ($i = 0; $i < self.colNum; $i++) {
            self.adjustColWidthOriginHeaderByColNo($i)
        }
    },

    getOffsetWidth: function(el) {
        let rect = el.getBoundingClientRect();
        return rect.width || rect.right - rect.left;
    },

    adjustHeaderAndTableWidth: function(fixWidth) {
        let self = this;
        self.cloneHeaderObj.width(fixWidth);
        self.tableObj.css({'min-width': fixWidth});
    },

    adjustColWidthOriginHeaderByColNo: function(colNo) {
        let self = this;
        colWidth = self.getOffsetWidth(self.originalHeaderObj.find('tr').children().get(colNo));

        self.cloneHeaderObj.find('colgroup').children().eq(colNo).width(colWidth);
        self.tableObj.find('colgroup').children().eq(colNo).width(colWidth);
    }
};

英語とかここ変だよっていうのがあれば教えてください。

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

JavaとC++は学ぶな!!初心者が学ぶべき言語【さらに厳選】

注意事項

この記事には以下の要素が含まれます。苦手な方はご注意くださいね。

  • Pythonへの熱いdisり
  • C言語なんてなかった
  • JDK問題について深く知らないので怖がっている
  • いわゆる関数型言語に言及しないスタイル(F#とかScalaとか?)

はじめに

JavaとC++は学ぶな!!初心者が学ぶべき言語【厳選】

という記事が出ているのですが、C++erとしては遺憾の意を示さざるを得ません。そのうえでこれからプログラミングを始める人におすすめする言語を紹介します。え、C++?初心者にはおすすめできません。

初心者におすすめする言語が満たすべき要件

  • 人口がそこそこいる
  • Windowsでも容易に開発環境が構築できる
  • 動作結果が目で見てわかる
  • コンパイルしなくていい
  • 言語規格が少なくとも5年以内に更新されている
  • 言語規格が後方互換性を備えていること
  • 日本語資料が豊富にあること
  • 非力なPCでも開発できる

人口がそこそこいる

プログラミング言語を作っている人もたくさんいるので無限にプログラミング言語は存在し得るわけですが、さすがにある程度ユーザー数がいない言語を薦めるわけにはいきません。

Windowsでも容易に開発環境が構築できる

例えばPythonはなぜかWindowsだと動かないという事態によく遭遇します(私だけ・・・?)。初心者にエラー文を読んで原因を切り分けさせるのは無理です。

他の言語でもWindowsで完璧に動くぜ!って言うのは以外と少ないです。Rubyはだいぶ頑張っていると思いますがそれでも特に音声やグラフィックが絡むとcannot load such fileと言われがちです(なぜかmsys2 mingw64 rubyでruby2dが使えない、チャットで問い合わせ中)。

動作結果が目で見てわかる

初心者はコンソールなんてまともに扱えませんし、"Hello world!"っていう文字を見せられても感動できません。

なんか図形が動くとかそういう視覚に容易に訴えられるのが望ましいです。

C++はGUIを作るのが極めて難しいのでこういうのは無理です(2D graphics標準化は頓挫したしな)。

言語標準ライブラリに2D描画ライブラリがあるか、十分メンテされてユーザーが居る2Dライブラリがあることが望ましいですね。

コンパイルしなくていい

コンパイルはなれている人でもやっぱり面倒だと思います。もちろんいろいろ自動化することは出来ますが、自動化を組むのが今度は面倒です。

ちなみに面倒くさがれない人は多分プログラミングにあまり向いていません。

言語規格が少なくとも5年以内に更新されている

プログラミング言語も切磋琢磨し日々様々な改善がなされているべきです。ある程度保守的なC++ですら3年おきに更新しているんですから5年経っても更新されない言語は死んでます。え?C言語?2017年末にC11と全く同じ内容をリネームして出してたけどあれはないでしょ。

言語規格が後方互換性を備えていること

初心者が頑張ってググって出てきた情報どおりに書いて動かそうとしたら動かないとなるとモチベーションが吹き飛びます。初心者じゃなくても心が折れます。セキュリティ上の懸念などからdeprecatedになる機能が出るのは仕方ないとしても、Pythonみたいに巨大なbreaking changeを噛ましてくる言語はおすすめできません。

日本語資料が豊富にあること

ただでさえわからないプログラミング言語を学ぶのに、自分の母国語じゃない資料で勉強するとか脳みそがパンクしてしまいます。

非力なPCでも開発できる

プログラミングするのに15万超えるようなPC買えと言われても多くの人は困ってしまいます。Unityとかは結構メモリー持っていくのでつらそうです。一般に出回っているPCはメモリーが4GBのものが多いです。

おすすめしたい言語

これらを満たす言語を残念ながら私の狭い知識では一つしか上げることが出来ませんでした。

JavaScript

ブラウザ上で動くのがやっぱり大きいです。Canvas APIを叩くことで図形が描画できます。実行環境をブラウザにすることで、ChromeかFirefoxを入れてもらえば(運が良ければすでに入っている)ほぼセットアップが終わります。
そのままのJavaScriptを使ったブロックくずしゲーム - ゲーム開発 | MDN
というStep-by-StepでCanvas APIを使う資料がなんとMDNにありますし実に良いです。

言語の人口も計算不能なレベルで大きく、開発も盛んです。

おすすめしたかった言語

Ruby

Ruby自体はかなりいい言語で、日本語資料も開発者が日本人ですからたくさんあります。グラフィックではRuby2dが結構良さそうなライブラリなんですよね、動けば。うごけー。

おすすめできない言語の例

C++

C++erが言うんだから間違いない(確信)。Visual Studioはコンソールアプリケーションを作る分には手順を簡単にしてくれますが、外部ライブラリを使うには不向きです。外部ライブラリをまともに使うにはCMakeという別の言語の助けが必要になります。ものすごく難しいってわけではないですが、他の言語より難しいのは確かです。

C++自体もC++17以降を使う分には極端に難しいということはないと思うのですが(C++er基準)、何分古い情報が多いのとまともな入門書がないのが辛い気がします。

グラフィックライブラリ自体はopennframeworksとかDXライブラリとかがあります。

C#

Unityはゲーム業界でもっとも使われているライブラリです。これはC#で書くことが出来ますが、Unityで使えるC#のバージョンがものすごく古いという問題がありました。ようやく新しいC#への追従の動きが一昨年くらいからあるわけですが果たしてC#の進化に追いつけるのか・・・?

C#自体はC#7.0とかC#8.0で書く分には悪くないと思いますが古い情報に出くわしやすい印象です。

Java

広く使われている言語で進化を続けています。が!Oracle JDK vs OpenJDK問題を経て(Oracle) JDK8で止まる勢とOpenJDKに移る勢で分裂している気がします。おなじくJVMで動く言語ScalaのドキュメントにはJDK8をいれろって書いてあります。C++erとしては言語そのものよりJVMレベルのゴタゴタが気になってしまいます。

ちなみにOpenJDKは自分でPATH通さないといけない問題はchocolatey使えやって思ってます。

2Dグラフィックは言語標準ライブラリにある気がします。

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

JavaScriptで最後までスクロールしないとチェックできない規約を作る

利用規約と同意のチェックボタンを作る機会があり、その際に調べたことのまとめ

そもそもスクロール必須にするべきなのか?

これは少し気になったので、事例について調べてみました。

弁護士の方?が書いている内容で、適当にチェックだけできるような状態はよくなさそうだと思いました。
利用規約の有効性を基礎付ける「同意」の取得方法

利用規約への同意を取得する際には、ユーザーに利用規約をきちんと読んでもらい、これに同意した人だけがサービスを利用できるような仕組みにしておくことが重要です。

例えば、規約のリンクを貼ってあるだけよりも、規約全文をスクロールしなければチェックボックスに辿りつかないようにしてある方が、「読んでなかった」と主張されるリスクは減少します。

こちらに裁判例がまとめられていました。(3件しか見つからなかったそうです)
ここだけみると、スクロールが必須だったのかはわからないですが、どうやら規約の変更についても問題になることがあるようですね。
「利用規約に同意」画面のUIデザインについて言及された日本の裁判例まとめ

1件目は、伊藤雅浩(@redipsjp)先生からtwitterで情報を提供いただいた裁判例です。昔ながらの、利用規約がスクロールするテキストボックスの下に、承諾ボタンがレイアウトされたもの。裁判所は、このレイアウトなら規約に目を通すのは当然でしょう、と断じています。

ピュアJSでやってみる

まずはスクロールできるdiv要素を作ってみる

cssのoverflow-yをscrollにすることで、スクロールできる要素を作成できます。

See the Pen eqZodo by buntafujikawa (@buntafujikawa) on CodePen.

最後までスクロールされたかを判定する

次に先ほど作成したスクロールができる要素が、一番下までスクロールされたかを判定します。

判定方法

下記の計算で下までスクロールをしたかどうかわかりそうです。
clientHeight + scrollTop = scrollHeight

clientHeight

要素内のピクセル単位の高さ (height) を返します

scrollHeight

a measurement of the height of an element's content

scrollTo

the number of pixels that an element's content is scrolled vertically.

scrollHeight.png

スクロール時のイベント取得

onscrollでスクロールをした時のイベントを取得できます。

onscrollと先ほどの判定式を組みあわたものです。


See the Pen
gVLwjg
by buntafujikawa (@buntafujikawa)
on CodePen.


最終的にできたもの

単純にif文の条件分岐を入れただけですが


See the Pen
oKYzQX
by buntafujikawa (@buntafujikawa)
on CodePen.


おわりに

この記事を書いてから気が付いたのですが、Element.scrollHeight
の下の方にdemoが記載されてて、ほぼ同じようなコードの例が載っていました :joy:
ちゃんと最後までみておけばよかった...

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

JSON-SchemaからTypeScriptの型定義を生成する

REST APIの通信も型で守りたい!となった時に便利なライブラリがあったので紹介します。
json-schema-to-typeを使えばjson-schemaからTypeScriptの型定義を自動で生成することができます。

json-schema-to-type

使い方

コマンドラインの使い方です。試しに、Qiita API v2のjson-schemaで試します。
まずライブラリを追加

$ yarn add json-schema-to-typescript

次に、json-schemaをダウンロード

$ curl https://qiita.com/api/v2/schema > qiita-api-schema.json

そして変換

$ ./node_modules/.bin/json2ts qiita-api-schema.json > qiita-type.d.ts 

一瞬で型定義が生成できます。

qiita-type.d.ts
/* tslint:disable */
/**
 * This file was automatically generated by json-schema-to-typescript.
 * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
 * and run json-schema-to-typescript to regenerate this file.
 */

/**
 * In this schema file, we represents the public interface of Qiita API v2 in JSON Hyper Schema draft v4.
 */
export interface QiitaAPIV2JSONSchema {
  access_token: AccessToken;
  authenticated_user: AuthenticatedUser;
  comment: Comment;
  expanded_template: ExpandedTemplate;
  group: Group;
  item: Item;
  like: Like;
  project: Project;
  reaction: EmojiReaction;
  tag: Tag;
  tagging: Tagging;
  team: Team;
  team_invitation: InvitedMember;
  template: Template;
  user: User4;

}

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

Vue.js初学者が猫本の前半を読んだのでざっくり整理する

Vue.jsの入門書である基礎から学ぶVue.js(猫本)を前半の5章まで読みました。後半は今の自分には難しい&使わない部分も結構ありそうなので、前半部分の基本知識をざっくり整理します。

なお自分の知識としてはHTML,CSSが少しだけできる、生JavaScript、jQueryもまあ少しだけできるかなという程度です。jQueryしか知らないとマズイ時代なのかと感じ人気のある猫本を読み始めました。

Qiitaの投稿も初めて(若干コワイ)なのでお手柔らかにお願いします。間違っている部分や問題点があれば指摘お願いします。

1章 

Vue.jsについて

Vue.jsはjavascriptのフレームワークです。
フレームワークとは、作成するアプリケーションの設計の骨組みのようなものです。この骨組みに沿って実装することで実装速度の上昇や設計をシンプルに保つことができる等のメリットが得られます。
Vue.jsはJavaScriptのフレームワークなので、普通のJavaScriptとは少し違う書き方を求められます。書き方を覚えてしまえばよりシンプルに実装することができます。
また、フレームワークと類似した言葉にライブラリがあります。
フレームワークが全体を構成するための骨組みだとすると、ライブラリは局所を埋める部品のようなものです。局所的な処理に対して既存のライブラリを用いたりコードをライブラリとして共通化することでやはり実装処理の上昇や処理の切り分けが期待できます。

なぜVue.jsなのか

Vue.js以外にもJavaScriptのフレームワークは色々あります。
代表的なものとしては、

  • React.js

- Angular.js

等です。

これらのフレームワークについて全ての長所短所を調べたわけではないですが、少なくともVue.jsの良さとしては、

  • 学習コストが低い
  • スケールの柔軟性
  • 公式ドキュメント(日本語)の充実

が挙げられます。

Vue.jsでのHello world

  • テキストバインディング

Vue.jsのhello world的なもの。
テンプレートにプロパティを記述するとその場所に値を入れて描画してくれます。

html
<div id="app">
  <p>{{ message }} </p>
</div>
js
var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue.js'
  }
})

実際の描画
<p>Hello Vue.js</p>

2章

データバインディング

上記コードのようにdataオプションに文字列やオブジェクトなどのデータを定義することで、リアクティブデータを設定できます。
リアクティブデータというのは、Vue.js側でデータの取得時や更新時に自動的に反応してくれるデータのことです。
上記のコードではmessageをリアクティブデータとすることで、DOM更新時にHello Vue.jsというテキストに変換してくれたということです。
また、このようなテキストバインディングの場合は{{ message }}のように変換したい部分を中括弧で囲みます。この記法をMustache(マスタッシュ)と呼びます。

色々なデータバインディング

ネストと配列

js
new Vue({
  el: '#app',
  data: {
    message: {
      value: 'Hello Vue.js'
    },
    //配列にもできる
    list: ['りんご', 'ばなな', 'いちご'],
    //listから取り出す要素を動的にするためにセット
    num: 1
  }
})
html
<div id="app">
  <p>{{ message.value }} </p>
  <p>{{ message.value.length }} </p>
  <p>{{ list[2]}} </p>
  <p>{{ list[num] }} </p>
</div>
実際の描画
<p>Hello Vue.js</p>
<p>13</p>
<p>いちご</p>
<p>ばなな</p>

ifとshow

条件分岐によって対象の表示・非表示等を設定できます。
Vue.jsではv-ifv-showがあります。
この際のv-ifv-showをディレクティブと呼びます。
v-〇〇と書かれていれば、Vueで操作するために設定しているんだな、と思えばよいです。

html
<div id="app">
  <div v-if="ok">v-if条件による描画</div>
  <div v-show="ok">v-show条件による描画</div>
</div>
js
new Vue({
  el: '#app',
  data: {
    ok: false
  }
})
実際の描画
<!---->
<div style="display: none;">v-show条件による表示</div>

条件がfalseなので、v-ifはコメント化され、v-showではdisplay: noneが設定されていることがわかります。

for

v-forディレクティブを使います

html
<div id="app">
  <ul>
    <li v-for="item in list" v-bind:key="item.id">
      ID. {{ item.id }} {{ item.name }} HP. {{ item.hp }}
    </li>
  </ul>
</div>
js
new Vue({
  el: '#app',
  data: {
    list:[
      { id: 1, name: 'スライム', hp: 100 },
      { id: 2, name: 'いっかくうさぎ', hp: 200 },
      { id: 3, name: 'ゴーレム', hp: 500 }
    ]
  }
})
実際の描画
<div id="app">
  <ul>
    <li>ID.1 スライム HP.100</li>
    <li>ID.2 いっかくうさぎ HP.200</li>
    <li>ID.3 ゴーレム HP.500</li>
  </ul>
</div>

v-forのポイントはv-vind:key="item.id"の部分です。繰り返し処理の場合はこのようにkey属性をつけるようにします。
key属性には不変かつユニークなものを設定するので、今回はitem.idをセットしています。

3章

イベントハンドリング

「ボタンをクリックしたとき」というようにあるイベントに対しての処理を行うことをイベントハンドリングといいます。
JavascriptのaddEventListenerやjQueryの$(element).onに相当します。
Vue.jsでは、v-onディレクティブを使用します。

html
<button v-on:click="handleClick">クリック</button>
js
new Vue({
  el: '#app',
  methods: {
    handleClick: function() {
      alert('クリックされました!')
    }
  }
})

ボタンをクリックすると、Vueインスタンスで設定したhandleClickが呼び出される、という流れです。

フォーム入力バインディング

フォームの入力値とデータと同期させることができます(双方向データバインディング)。

html
<div id="app">
  <input v-model="message">
  <p>{{ message }}</p>
</div>
js
new Vue ({
  el: '#app',
  data: {
    message: 'Hello!'
  }
})

簡単に入力値と画面上の値を同期させることができます。

4章

算出プロパティ

算出プロパティはその名の通り計算等の処理を含めることのできるデータのことです。
Mustache記法をする際に、テンプレート側に式を書いてしまうと見にくくなるので、算出プロパティとして設定することで可読性を保持できます。
計算したデータをcomputedオプションに定義するかたちで実装します。

html
<p>{{ num }}の二倍は{{ doubleNum }}</p>
js
new Vue({
  el: '#app',
  data: {
    num: 50
  },
  computed: {
    doubleNum: function(){
      return this.num * 2
    }
  }
})

フィルタ

フィルタとは文字列の操作処理に特化した機能です。

html
<div id="app">
  {{ price | localeNum }}円
</div>
js
new Vue({
  el: '#app',
  data: {
    price: 59800
  },
  filters: {
    localeNum: function (val) {
      return val.toLocaleString()
    }
  }
})

実際の描画
59,800円

5章

コンポーネント

コンポーネントとは、WebサイトのヘッダーやフッターなどのUIの部分ごとにテンプレートとJavaScriptをセットで持つことで、他のUIと切り離して管理できる機能です。

html
<div id="app">
  <my-component></my-component>
</div>
js
Vue.component('my-component',{
  template: '<p>MyComponent</p>'
})

new Vue({
  el: '#app',
})
実際の描画
<div id="app">
  <p>MyComponent</p>
</div>

コンポーネント間の通信

テンプレートで他のコンポーネントを使用すると親子関係になります。
親子間のデータのやりとりとして、

  • 親からデータを属性として渡し、子はpropsで受け取る
  • 子からデータを$emitで渡し、親はonで受け取る

という構造になります。

まずは親から子の場合

html
<comp-child val="子1"></comp-child>
<comp-child val="子2"></comp-child>

属性をvalとして設定してます。

js
Vue.component('comp-child', {
  template: '<p>{{ val }}</p>',
  //受けとる属性名をpropsで指定
  props: ['val']
})

new Vue({
  el: '#app'
})

実際の描画
<p>子1</p>
<p>子2</p>

そして子から親の場合

html
<div id="app">
<comp-child v-on:childs-event="parentsMethod"></comp-child>
</div>
js
Vue.component('comp-child',{
  template: '<button v-on:click="handleClick">押すとイベントが発火するボタン</button>',
  methods: {
    handleClick: function(){
      this.$emit('childs-event')
    }
  }
})

new Vue({
  el: '#app',
  methods: {
    parentsMethod: function(){
      alert('イベントをキャッチしました!')
    }
  }
})

おわりに

マスコットキャラの猫のかわいいページランキング

1位. ライフサイクルで夜なのでおやすみ猫 p.45
2位. デプロイのたびに変更は面倒なリボン猫 p.237
3位. アニメーション・消える時の猫 p.201

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

【rails】リロードしないとインクリメンタルサーチが動かない時はこれ(turbolinksの無効化)

インクリメンタルサーチを実装したのに、
リロードしないと反応しないじゃん!!

ってなったので調べました。

原因はrails5で標準インストールされるturbolinksにあるようです。
(turbolinksについてはこちらの記事を→https://qiita.com/saboyutaka/items/bb089e8208239bf6fdc0)

とりあえず、今回はアプリケーション全体でturbolinksを無効化したかったわけではなく、
リンク先だけで無効化したかったので、以下のように記述して解決しました。

index.html.haml
中略
 = link_to edit_group_path(@group) do
   .main-header__edit-btn{"data-turbolinks": "false"}
     Edit

参考リンク

https://ryoutaku-jo.hatenablog.com/entry/2019/01/15/213420

https://qiita.com/Cheekyfunkymonkey/items/216bf7426493e6213927

https://qiita.com/hiroyayamamo/items/b258acbaa089d9482c8a

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

CocosCreator v.2.1.1備忘録

初めに

Cocos2dxやCocosCreatorの過去バージョンのソースコードを参考にし、
いざ実装! と思ったら命令が存在しない・・・
そんな事が何度かあったので、自分の分かる範囲で対応表を作成しました
色々あってJavaScriptとTypeScriptの両方を使用しているため混在しているかも

動作確認しているバージョンはv2.1.1です
また、目的別の早見表もついでに記載しています

名前が変わったもの対応表

各々のAPI説明は下記URL
https://docs.cocos.com/creator/2.1/api/en/

v2.1.1 備考
cc.eventManager.addListener(~) node.on(~)
cc.eventListener.TOUCH_ONE_BY_ONE 等 cc.Node.EventType.TOUCH_START 等
cc.director.getVisibleSize().width cc.WinSize.width
cc.director.getWinSize().width cc.WinSize.width
cc.Point cc.Vec2
cc.p(x, y) cc.v2(x, y)
cc.randomMinus1To1() 無し Math.random()使う
hoge: {
 default: null,
 url: cc.AudioClip
}
hoge: {
 default: null,
 type: cc.AudioClip
}
以前はtypeとurlが混在していたが、typeに統一されたっぽい

目的別機能早見表

目的 機能 備考
ログ出したい cc.log('hoge=' + hoge)
enum使いたい State = cc.Enum({
 hoge: 0,
 fuga: 1
})
TypeScriptを使う場合はenum対応しているので普通に使用可能
static使いたい statics: {
 State
}
node取得したい var hoge = cc.find('Canvas/hoge')
Component取得したい hoge.getComponent('fuga')
Prefabのロードしたい cc.instantiate(this.prefab)
node追加したい hoge.addChild(prefab)
自作Action作りたい var fuga = cc.callFunc(function() { ~ })
動かしたい node.runAction(cc.sequence(hoge, fuga))
アニメーション再生したい var anim = this.node.getComponent(cc.Animation);
anim.play('Jump');
BGM再生したい _bgmId = cc.audioEngine.play(this.audioClip, true) 返り値は停止時に使用する

その他

SwallowTouchは?

調べた限りでは非推奨な方法(eventManager.addListener)以外では設定出来なさそう
node.on を使っていると自動的にtrueになっているっぽい(CCNode.js にて確認)

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

CocosCreator v2.1.1でシェーダーを使う

初めに

色々あってCocosCreatorを触る事になったのですが、
日本語ドキュメントが圧倒的に少ない!
参考書が圧倒的に少ない!
という事で、実装以前の問題でハマる事が多々ある状況です。

そこで、同じ轍を踏む人が少しでも減ればと思い記事を書かせて頂きました。

v2.1.1版でシェーダーが動かない

CocosCreatorを触っている方でしたらご理解頂けると思うのですが、
バージョンによりメソッドが消えたり挙動が大きく変わったりと中々難儀な子です。

シェーダーについてもv2.0で大幅な変更がかかったそうです。
https://discuss.cocos2d-x.org/t/what-happened-with-cc-glprogram/44539

その時の解決策についてもv2.1.1版ではプロジェクトの実行すら出来ない状態。
過去は振り返らないそのお姿、素敵です。 現場は阿鼻叫喚です。

答えは意外と身近にあった

結論から言いますと、シェーダーは「Effect」というファイルにプログラムを記載する形になったようです
右クリック→「Create」の一覧にしれっと存在していました。

  • Effectファイル作成及び編集
  • MaterialにEffectファイルを登録
  • SpriteにMaterialをセット

以上でシェーダーを反映することが出来ます。

実際にやってみる

画像の用意

分かりやすいようにRGBそれぞれが255の3色画像を作成
rgb.png

Effectファイルの作成

effect1.png
今回は良くある灰色にするシェーダーを作成するので、「Gray」というファイル名にしました。

作成したファイルはテキスト形式なのですが、
JSやTSファイルのように自動でエディタが起動しないため、手動でファイルを開いて下さい。

Gray.effect
/**
 * 灰色シェーダー
 */

%{
    techniques : [
        {
            passes : [
                {
                    vert : vs
                    frag : fs
                    cullMode : none
                    blend : true
                }
            ]
            layer : 0
        }
    ]
    properties : {
        texture : {
            type : sampler2D
            value : null
        }
    }
%}

%% vs {
    uniform mat4 cc_matViewProj;

    attribute vec4 a_position;
    attribute vec2 a_uv0;
    attribute vec4 a_color;

    varying lowp vec4 v_fragmentColor;
    varying mediump vec2 v_texCoord;

    void main() {
        gl_Position = cc_matViewProj * a_position;
        v_fragmentColor = a_color;
        v_texCoord = a_uv0;
    }
}

%% fs {
    precision lowp float;

    uniform sampler2D texture;

    varying vec4 v_fragmentColor;
    varying vec2 v_texCoord;

    const vec3 grayScale = vec3(0.298912, 0.586611, 0.114478);

    void main() {
        vec4 color = v_fragmentColor * texture2D(texture, v_texCoord);

        float grayScaleColor = dot(color.rgb, grayScale);
        gl_FragColor = vec4(vec3(grayScaleColor), color.a);
    }
}

RGB値の重みが異なるのはNTSC加重平均法と言って、白黒テレビで使われていた規格によるものらしいです。
もしもRGB値を同じ重みにした場合、今回使用した画像は3色とも同じ濃さになってしまうため、
人の明るさの感じ方を考慮した配分になってます。

Material作成

effect2.png
Materialを新規作成し、名前を「Gray」に

effect3.png
先ほど作成したGray.effectを設定
textureは何も入れなくてOKです。

Script作成

ちゃちゃっと確認出来るように簡易的なScriptを組みます

Gray.ts
const {ccclass, property} = cc._decorator;

@ccclass
export default class Gray extends cc.Component {

    @property({
        // @ts-ignore
        type: cc.Material,
        displayName: "Material",
        tooltip: "マテリアル"
    })
    material = null;

    start () {
        let sprite = this.node.getComponent(cc.Sprite);
        sprite.setMaterial(0, this.material);
    }
}

effect4.png

実行結果

gray.png
灰色になりました!
RGBもそれっぽくなってますね。

補足

effectファイルの中身についてですが、uniform,attributeで使える変数名で少しハマりました。
こちらはCocosCreator本体側にある
enums.js
forward-renderer.js
に記載されています。

最後に

Cocosはクセが強いですが、Unityと比べてWeb版での起動が早い、メモリ消費が少ないというメリットがある事と
最近はHTML5のゲームも増えてきているので、
Webブラウザをメインとするゲームなどでは選択肢としてアリだと思います。
後はVFXツールが充実すると良いのですが・・・。

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