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

express+postgreSQL+Herokuでアプリ公開した手順

使用するパッケージがexpress,pg(とejs)のみで作成したWebアプリを、Herokuにて公開するまでをまとめました。git自体の詳細は省いたりしていますが、基本この通りやっていけば公開まではいけると思います。
全体的に情報が古めだったけど、良いサイトないかな。

OS:Windows10
terminal:powershell
テキストエディタ:VScode

1.準備

1-1.ローカル環境でexpress+postgreSQLのアプリ作成

この記事は主にローカル環境で出来上がっているけど公開の手順が分からない人向けなので、ここは省略します。

1-2.Herokuアカウント作成

https://dashboard.heroku.com

1-3.HerokuのCLIを使えるようにする。

https://devcenter.heroku.com/articles/heroku-cli
でDLしたあとpathを通す。

terminal
> heroku -v
heroku/7.42.1 win32-x64 node-v12.16.2

などでバージョン確認ができればok。

2.Herokuアプリに接続する

ここから先はだいたいこのサイトを参考にしてます。追加情報が必要なときのみその時の参考サイトも貼りました。
https://tacamy.hatenablog.com/entry/2013/02/16/235127

2-1.Herokuへログインする
terminal
> heroku login

ブラウザへ飛ばされたあと、そこに「成功」的な画面が出ていればok。

2-2.Herokuにアプリ作成
terminal
> heroku create

アプリ名にこだわるなら別の手順が必要ですが、僕はどうせheroku.appってurlに入るからどうでもいいかなと思って素のままいきました。

2-3.Procfile作成

app.js(自分のjsファイル名)が置かれている層にPricfile(拡張子なし)というページを作り、以下の内容のみ記述します。

Procfile
web: node app.js

WSGIという、Webアプリを動かす指示書的なものらしい。詳しくはhttps://creepfablic.site/2019/05/01/heroku-procfile/

2-4.gitignore作成

先程のProcfile,app.jsと同じ層に.gitignoreを作成して、以下の内容を記述。nodeのパッケージ等はHeroku側で用意されているので、gitでアップロードしないように制御する。

.gitignore
node_modules

現在はこのような位置関係になっていると思います。

-app.js
-package.json
-Procfile
-.gitignore
-node_module
-public
...etc

2-5.package.jsonにengineの記述追加

特定のバージョンのみでしか動かないようにするために、package.jsonのdependenciesの後に以下の内容を記述。

package.json
...
  "dependencies": {
    "ejs": "^3.1.3",
    "express": "^4.17.1",
    "pg": "^8.2.1",
  },
  "engines": {
    "node": "^12.17.0", //自分のバージョン
    "npm":  "^6.14.4" // 自分のバージョン
  }
...

https://qiita.com/suin/items/994458418c737cc9c3e8

3.HerokuのDBへ接続する

3-1.Heroku上でDBを追加

Herokuのサイトに行き、アプリ名をクリック→Resources→Add-onsからHeroku Postgresを検索し、追加

3-2.各種データの確認

SettingsのReveal config varでURLを確認し、各種必要な値を得て、ローカル環境でのものと置き換える。

URLの構成:postgres:// ユーザー名:パスワード @ホスト名:ポート番号/データベース名
app.js
const pool = new Pool({
  user: 'ユーザー名',
  host: 'ホスト名',
  database: 'データベース名',
  password: 'パスワード',
  port: 5432, //ポート番号
});

https://qiita.com/shosho/items/5ebabf11efb1f3b604f7#url%E3%81%AE%E6%A7%8B%E6%88%90

3-3.portをHeroku用に変える

ローカル環境だとapp.listen(3000)みたいになってるところを以下の内容に変更。

app.js
let port = process.env.PORT || 3000;
app.listen(port, () => {
    console.log("Listening on " + port); //ここは要らないけど一応
});

適当な人のgithubからパクった。
https://github.com/FTraian/node-express-postgresql-heroku-tutorial/blob/master/web.js

4.gitでファイルをアップロードする

4-1.git commitまで
terminal
> git init
> git add .
> git commit -m "first commit"
4-2.公開鍵を設定(一番面倒でした)

ここは僕自身理解度が低いですが、ひとまず僕が成功した手順を書いておきます。
まず、公開鍵が設定されていないことを確認します。

terminal
> heroku keys
!  You have no SSH keys.

確認できたら、ssh公開鍵を作成します。"xxx@gmail.com"の部分はherokuに登録したメールアドレスを入力します。

terminal
> ssh-keygen -t rsa -C "xxx@gmail.com"

すると以下のようにでてくるので、Enterキーを押したりpassphraseを設定したりします。

terminal
Generating public/private rsa key pair.
Enter file in which to save the key (C:\Users\ユーザー名/.ssh/id_rsa):  //そのままEnterキー
Created directory 'C:\Users\ユーザー名/.ssh'.
Enter passphrase (empty for no passphrase): //パスフレーズを入力(忘れないように)

その後、Herokuに登録して完了です。

terminal
> heroku keys:add C:\Users\ユーザー名\.ssh\id_rsa.pub //id_rsa.pubがあるファイル名を指定
> heroku keys
=== xxx@gmail.com keys
...(公開鍵) xxx@gmail.com

https://daipresents.com/2012/post-5010/
https://knowledge.sakura.ad.jp/3543/

4-3.git push

初回のみ以下のコマンド。

terminal
> git remote add heroku git@アプリ名.git

あとはいつもどおり。

terminal
> git push origin master

ここまでで、自分のアプリのURLをクリックしたら開けると思います。ありがとうございました。

おまけ

ローカル環境からDBをいじるには
terminal
> heroku pg:psql -a  アプリ名
アプリ名::DATABASE=>

 
https://sysrigar.com/2019/01/20/heroku-postgres%E3%82%92%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E7%92%B0%E5%A2%83%E3%81%8B%E3%82%89%E6%89%8B%E5%8B%95%E3%81%A7%E6%93%8D%E4%BD%9C%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95%E3%81%AB%E3%81%A4/

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

express+postgreSQL+HerokuでWebアプリを公開した手順

使用するパッケージがexpress,pg(とejs)のみで作成したWebアプリを、Herokuにて公開するまでをまとめました。git自体の説明は省いたりしていますが、基本この通りやっていけば公開まではいけると思います。
全体的に情報が古めだったけど、良いサイトないかな。

OS:Windows10
terminal:powershell
テキストエディタ:VScode

1.準備

1-1.ローカル環境でexpress+postgreSQLのアプリ作成

この記事は主にローカル環境で出来上がっているけど公開の手順が分からない人向けなので、ここは省略します。

1-2.Herokuアカウント作成

https://www.heroku.com/

1-3.HerokuのCLIを使えるようにする

https://devcenter.heroku.com/articles/heroku-cli
でDLしたあとpathを通す。

terminal
> heroku -v
heroku/7.42.1 win32-x64 node-v12.16.2

などでバージョン確認ができればok。

2.Herokuアプリに接続する

ここから先はだいたいこのサイトを参考にしてます。追加情報が必要なときのみその時の参考サイトも貼りました。
https://tacamy.hatenablog.com/entry/2013/02/16/235127

2-1.Herokuへログインする
terminal
> heroku login

ブラウザへ飛ばされたあと、そこに「成功」的な画面が出ていればok。

2-2.Herokuにアプリ作成
terminal
> heroku create

アプリ名にこだわるなら別の手順が必要ですが、僕はどうせheroku.appってurlに入るからどうでもいいかなと思って素のままいきました。

2-3.Procfile作成

app.js(自分のjsファイル名)が置かれている層にPricfile(拡張子なし)というページを作り、以下の内容のみ記述します。

Procfile
web: node app.js

WSGIという、Webアプリを動かす指示書的なものらしい。詳しくはhttps://creepfablic.site/2019/05/01/heroku-procfile/

2-4.gitignore作成

先程のProcfile,app.jsと同じ層に.gitignoreを作成して、以下の内容を記述。nodeのパッケージ等はHeroku側で用意されているので、gitでアップロードしないように制御する。

.gitignore
node_modules

現在はこのような位置関係になっていると思います。

-app.js
-package.json
-Procfile
-.gitignore
-node_module
-public
...etc

2-5.package.jsonにengineの記述追加

特定のバージョンのみでしか動かないようにするために、package.jsonのdependenciesの後に以下の内容を記述。

package.json
...
  "dependencies": {
    "ejs": "^3.1.3",
    "express": "^4.17.1",
    "pg": "^8.2.1",
  },
  "engines": {
    "node": "^12.17.0", //自分のバージョン
    "npm":  "^6.14.4" // 自分のバージョン
  }
...

https://qiita.com/suin/items/994458418c737cc9c3e8

3.HerokuのDBへ接続する

3-1.Heroku上でDBを追加

Herokuのサイトに行き、アプリ名をクリック→Resources→Add-onsからHeroku Postgresを検索し、追加

3-2.各種データの確認

SettingsのReveal config varでURLを確認し、各種必要な値を得て、ローカル環境でのものと置き換える。

URLの構成:postgres:// ユーザー名:パスワード @ホスト名:ポート番号/データベース名
app.js
const pool = new Pool({
  user: 'ユーザー名',
  host: 'ホスト名',
  database: 'データベース名',
  password: 'パスワード',
  port: 5432, //ポート番号
});

https://qiita.com/shosho/items/5ebabf11efb1f3b604f7#url%E3%81%AE%E6%A7%8B%E6%88%90

3-3.portをHeroku用に変える

ローカル環境だとapp.listen(3000)みたいになってるところを以下の内容に変更。

app.js
let port = process.env.PORT || 3000;
app.listen(port, () => {
    console.log("Listening on " + port); //ここは要らないけど一応
});

適当な人のgithubからパクった。
https://github.com/FTraian/node-express-postgresql-heroku-tutorial/blob/master/web.js

4.gitでファイルをアップロードする

4-1.git commitまで
terminal
> git init
> git add .
> git commit -m "first commit"
4-2.公開鍵を設定

ここは僕自身理解度が低いですが、ひとまず僕が成功した手順を書いておきます。
まず、公開鍵が設定されていないことを確認します。

terminal
> heroku keys
!  You have no SSH keys.

確認できたら、ssh公開鍵を作成します。"xxx@gmail.com"の部分はherokuに登録したメールアドレスを入力します。

terminal
> ssh-keygen -t rsa -C "xxx@gmail.com"

すると以下のようにでてくるので、Enterキーを押したりpassphraseを設定したりします。

terminal
Generating public/private rsa key pair.
Enter file in which to save the key (C:\Users\ユーザー名/.ssh/id_rsa):  //そのままEnterキー
Created directory 'C:\Users\ユーザー名/.ssh'.
Enter passphrase (empty for no passphrase): //パスフレーズを入力(忘れないように)

その後、Herokuに登録して完了です。

terminal
> heroku keys:add C:\Users\ユーザー名\.ssh\id_rsa.pub //id_rsa.pubがあるファイル名を指定
> heroku keys
=== xxx@gmail.com keys
...(公開鍵) xxx@gmail.com

https://daipresents.com/2012/post-5010/
https://knowledge.sakura.ad.jp/3543/

4-3.git push

初回のみ以下のコマンド。

terminal
> git remote add heroku git@アプリ名.git

あとはいつもどおり。

terminal
> git push origin master

ここまでで、自分のアプリのURLをクリックしたら開けると思います。ありがとうございました。

おまけ

ローカル環境からDBをいじるには
terminal
> heroku pg:psql -a  アプリ名
アプリ名::DATABASE=>

 
https://sysrigar.com/2019/01/20/heroku-postgres%E3%82%92%E3%83%AD%E3%83%BC%E3%82%AB%E3%83%AB%E7%92%B0%E5%A2%83%E3%81%8B%E3%82%89%E6%89%8B%E5%8B%95%E3%81%A7%E6%93%8D%E4%BD%9C%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95%E3%81%AB%E3%81%A4/

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

JSFiddleでReactを実行する

Reactとは

Reactとは、WebサイトのUIパーツを構築するためのJavaScriptライブラリです。
Facebookが開発し、OSSとして公開されています。
また、Reactは「宣言的な」ライブラリなので、Webデザイナー向けとも言われています。

JSFiddleとは

HTML、CSS、およびJavaScriptを簡単に実行できるオンラインのIDEサービスです。

■JSFiddle
https://jsfiddle.net/

JSFiddleでReactを実行する

JSFiddleでReactを実行してみましょう。
今回は「Hello World」を表示してみます。
ソースコードは以下の2つです。

hello.html
<!-- Reactの読み込み -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>

<div id="hello_container"></div>
hello.js
ReactDOM.render(
  React.createElement('h1', null, 'Hello World'),
  document.getElementById('hello_container')
);

JSFiddleの画面にて、上記のHTMLとJavaScriptを貼り付けて、左上の「Run」ボタンをクリックするだけでReactが実行できます。
実行結果は右下に表示されます。
JSFiddle_Code_Playground.png
「hello_container」の要素に対して、「Hello World」が出力されていればOKです。

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

PlayCanvasでモデルがクリックされたときにHTMLの表示を切り替える方法

PlayCanvasとHTMLを組み合わせる

PlayCanvasとHTMLを組み合わせる方法について紹介をします。

PlayCanvasは特性上、エディターを使用した開発でHTMLを触ろうとすると少し癖があります。JavaScriptのフレームワークと組み合わせることで解消も可能だったりもします。

一例
playcanvas-next-test

PlayCanvas界隈では様々な方法が試されていると思いますがその中で自分が、メンテナンス性などのも考えて、使いやすかったものを紹介します。

今回はPlayCanvasの基本的な説明は省いています。

今回作るもの

このような3Dモデルが並んでいるページで、モデルがクリックされたらHTMLの表示を切り替えるものを作ります。

例: https://playcanv.as/p/9uV03p1Y/

モデルがクリックされたらモデルの情報をDOMを切り替えるものになります。
  
(モデルはサンディちゃんを使用しています。)

HTMLを組み込む

PlayCanvasのエディター上でHTML, CSSを組み込む

HTMLを組み込むためにそれぞれのスクリプトを追加します。
こちらは様々な方法があるかと思いますが、insertAdjacentHTMLを使用してHTMLを組み込みます。

1. ui.js index.html, style.cssを作成する

ui.js
const Ui = pc.createScript("ui");

Ui.attributes.add("css", {
  type: "asset",
  assetType: "css",
  title: "CSS Asset"
});
Ui.attributes.add("html", {
  type: "asset",
  assetType: "html",
  title: "HTML Asset"
});

Ui.prototype.initialize = function() {
  const body = document.getElementsByTagName("body")[0];
  const head = document.getElementsByTagName("head")[0];
  const style = `<style>${this.css.resource}</style>`;
  body.insertAdjacentHTML("afterbegin", this.html.resource);
  head.insertAdjacentHTML("afterbegin", style);
};
index.html
<div class="container">
    <h2 class="title">タイトル</h2>
    <h2 class="url">URL</h2>
    <img class="image" />
</div>
style.css
.container{
    position: absolute;

    background-color: white;
    z-index:1;
}

.container img{
    object-fit: cover;
    width:300px;
    height:300px;
}

スクリプトにCSSHTMLを追加する

Rootのエンテティにui.jsを追加します。追加ができたら、style.cssindex.htmlを設定します。

スクリプト属性
スクリプトのアトリビュート機能は、スクリプト内で使用する変数をPlayCanvasエディタ内で編集することができるようにする便利な機能です。この機能を使うことで、一度コードを書いた後にエンティティごと作られるインスタンスにそれぞれ違うパラメータを設定する調整ができるようになります。これにより、アーティスト、デザイナーやその他のプログラマーではないチームメンバーがコードを書かずに値を変更できるにプロパティを露出させることができます。
https://developer.playcanvas.com/ja/user-manual/scripting/script-attributes/

情報を保持するためのスクリプトを追加

次に、各モデルに、クリックされた時の情報を追加します。

info.jsを作成

info.jsとしてスクリプトを作成します。
このスクリプトの役割は、プログラムを変更せずに変更できる値を保存しておくために存在しています。

info.js
/*jshint esversion: 6, asi: true, laxbreak: true*/
const Info = pc.createScript('info');
Info.attributes.add("title", {"type": "string", default: "example"})
Info.attributes.add("url", {"type": "string" , default: "https://example.com"})
Info.attributes.add("image", {"type": "asset"})

スクリプトを適用する

スクリプトを追加すると、それぞれtitleurlimageと値を設定できるようになります。クリックされたときにはエンテティのこの値を使用して表示を切り替えるようにします。

クリックされたときに値を切り替えるスクリプトを追加

最後にクリックされたときに情報を切り替えるためのスクリプトを追加します。
こちらはCAMERAコンポーネントを持っている持っているエンテティに追加します。

マウスが押されたときに、直接DOMの切り替えを行なっています。

picker-raycast.js
/*jshint esversion: 6, asi: true, laxbreak: true*/

const PickerRaycast = pc.createScript('pickerRaycast');

// initialize code called once per entity
PickerRaycast.prototype.initialize = function() {

    // マウスがクリックされたときに、onSelect関数を実行します
    this.app.mouse.on(pc.EVENT_MOUSEDOWN, this.onSelect, this);
};

PickerRaycast.prototype.onSelect = function (e) {
    const from = this.entity.camera.screenToWorld(e.x, e.y, this.entity.camera.nearClip);
    const to = this.entity.camera.screenToWorld(e.x, e.y, this.entity.camera.farClip);

    // クリックされた位置にエンティティがあれば result.entityにentityの情報が入ります
    const result = this.app.systems.rigidbody.raycastFirst(from, to);

    if (result) {
        const entity = result.entity;
        // entityにinfoというスクリプトが含まれているかを調べます。
        if(entity.script && entity.script.hasOwnProperty('info')){

        // title: string
        // スクリプト名 infoの属性 titleを参照します
        const title = entity.script.info.title

        // url: string
        // スクリプト名 infoの属性 url
        const url = entity.script.info.url

        // image: Asset
        // スクリプト名 infoの属性 imageを参照します
        // imageのtypeはAssetなので、getFileUrl()関数を使用してAssetのURLを参照します。
        const image = entity.script.info.image
        const imageUrl = image.getFileUrl()

        // ui.jsで追加したHTMLの要素を取得します
        const titleElement = document.getElementsByClassName("title")[0]
        const urlElement = document.getElementsByClassName("url")[0]
        const imgElement = document.getElementsByClassName("image")[0]

        // HTMLの要素を書き換えます
        titleElement.textContent = title
        urlElement.textContent = url
        imgElement.src = imageUrl
        }    
    }
};

感想

すべてのモデルで決まった値を表示したい場合にはこちらの方法を使うことで表示を切り替えることができます。
ただ、複雑に切り替わる場合などには向いていないためシンプルに実装したいときに使っております。

今回のプロジェクトで質問や意見がありましたら。@mxcn3まで連絡をお願いします。

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

JavaScript/jQuery初心者が、アコーディオンメニューの開閉に伴って回転する矢印アイコンをなんとか実装したときの記録

はじめに

JavaScript/jQuery初心者が、コンテンツの開閉状態と連動する矢印アニメーションを、jQueryで実装するまでにやったことの記録です。

3行で

  • thisでクリックした要素にだけ処理を行う
  • アニメーション用のCSSを書く
  • toggleClassでアニメーション用クラスの付け替えをする

環境 + 前提

Rails 5.2.4.3
Ruby 2.6.5
Bootstrap 4.5.0
jquery-rails 4.4.0
sass-rails 5.1.0

このポートフォリオに実装
YWT Quest

Bootstrapのカードを使用。
カードコンポーネントによるアコーディオンを実装していて、card-headerをクリックすると開閉する。

やりたいこと

  1. このようなカードが並んでいて、開閉可能なものには矢印がついている。 スクリーンショット 2020-06-30 0.12.14.png
  2. カードのヘッダー部分をクリックすることで開閉。
  3. カードが開いた時に矢印も回転(アニメーション)する。
  4. カードが閉じる時に矢印が回転(アニメーション)し元の状態に戻す

実装に必要そうなこと

  • card-headerをJavaScriptで指定し、クリックされた時にクラス名を追加する。
  • 追加したクラスに、アイコンの表示を変えるCSSを書く。
  • もう1度クリックされた時には上で追加されたクラスを削除する。

実際にやったこと

1. viewファイルに直書きではなく、JavaScriptを読み込んで使う

まず、assets/javascripts/logs/index.jsを読み込む設定をする。

rails 任意のviewのみで、特定のjsを読み込む方法 - Qiita
logs/index.jsviews/logs/index.slim.htmlに対してだけ読み込ませたいので、上記を参考にviewファイルの下部に以下を記述。

views/logs/index.slim.html
<%= javascript_include_tag 'logs/index.js' %>

2. jQueryでクリックイベントを追加する

card-header部分をクリックすることで開閉可能で、カードが開いた時に矢印も回転するのであれば、card-headerをクリックした時に、矢印が回転するような処理 を書かないといけない。

card-headerはココ

スクリーンショット 2020-06-29 22.32.08.png

card-headerをクリックした、とJavaScript側に分かってもらえてるかどうかをまずチェック。

logs/index.js
$(function(){
  $('.card-header').click(function(){
    alert("click event")
  });
});

card-headerをクリックしてアラートが出ればOK

click-event.gif

どうやらうまくいってるみたい。

3. カードの開閉状態で条件分岐させる

次に、クリックしたカードが開くかどうかをチェック。
現在はcontentカラム(詳細文)が空でなければ開くし、card-headerの文字の右側に矢印が表示される。

つまり矢印の有無でカードが開くかどうかをチェックできる、と考えた。
クリックしたcard-headerfa-chevron-downというクラスが存在すれば開く、しなければ開かない。

jQueryにfindという子孫要素を取得できるメソッドがあったので使ってみる。

【jQuery入門】find()で子要素を取得する手法まとめ! | 侍エンジニア塾ブログ(Samurai Blog) - プログラミング入門者向けサイト
find(expr) - jQuery 日本語リファレンス

以下のようなコードを書いた。まずは条件分岐が成功するかどうかをチェックする。

logs/index.js
$(function(){
  $('.card-header').click(function(){
    var icon = $(this).find('.fa-chevron-down');
    // 本文がなく、アイコンがないcard-headerをクリックした時はundefinedが返されることを利用した
    if(icon[0] !== undefined) {
      console.log("true");
    } else {
      console.log("false");
    }
  });
});

最初はicon[0] == "<i class=\"fas fa-chevron-down\"></i>"と、icon[0]に入っている情報そのものと照らし合わせたり
icon[0].toString();として文字列に変換してから比較したり
indexOfを使って前方一致(<i classだけ等)で検索をかけてみたりしたが、全て失敗。

どうしよう?とconsole.logを見ていると、開閉できないカードを押したときundefinedが返ってきているのを発見。

「じゃあ、undefinedが返ってきていないときにアニメーション用の処理をすればいいのでは……?」

と考えて、ifの中身をif(icon[0] !== undefined)と書き換えることで、開くカードのcard-headerをクリックしたときにはtrueが、開かないカードのcard-headerをクリックしたときにはfalseが返ってくるようになった。

4. 矢印をアニメーションさせる処理を書く

rotate() - CSS: カスケーディングスタイルシート | MDN

矢印を回転させるにはrotate()プロパティでアイコンを回転させれば良いと考えた。
しかし、肝心のアニメーションの実装は……?

jQueryでCSSのtransform: rotate()を使った回転アニメーションする際のメモ ‹ jQuery ‹ JavaScript ‹ emwaiblog

この記事では、回転用のクラス名を追加することで、回転を実装している。
また、transitionを使うことでtransformをアニメーション化している。

transitionのオプションやプロパティについてはここを参考にした。

【CSS3】Transition(変化)関連のまとめ

まず、開閉に伴ってクラスを付けたり外したりする処理をjQueryで書く。

assets/javascripts/logs/index.js
$(function(){
  $('.card-header').click(function(){
    var icon = $(this).find('.fa-chevron-down');
    // 本文がなく、アイコンがないcard-headerをクリックした時はundefinedが返されることを利用した
    if(icon[0] !== undefined) {
      $('.fa-chevron-down').toggleClass("spined-icon")
    }
    // else以下を削除
  });
});

toggleClassで、fa-chevron-downクラスを持つ要素にspined-iconクラスがついてなければ追加、ついてたら削除する。

これも検証ツールで確認する。

class-toggle.gif

<i class="fas fa-chevron-down>"の横にspined-iconが追加されているのが確認できた。

CSSでspined-iconに矢印を回転させる処理を書く。

assets/stylesheets/logs/index.scss
.spined-icon {
  transform: rotate(90deg);
  transition-duration: 0.5s;
}

transform: rotate(90deg);で要素を90度回転、それが始まってから終わるまでの時間をtransition-duration: 0.5sで設定している。

「0.5秒かけて要素を90度回転させてください」ということ。実際の動きを見てみる。

ohno.gif

閉じるときのアニメーションがない。それはともかく……

クリックしてないカードの矢印も動いちゃってる……

何がいけなかったのか?

コードをもう一度見てみる。

assets/javascripts/logs/index.js
$(function(){
  // 'card-header'クラスを持つ要素をクリックしたとき、
  $('.card-header').click(function(){
    // 'card-header'クラスの子孫要素の中から'fa-chevron-down'クラスを持つ要素を探し、変数iconに格納する
    var icon = $(this).find('.fa-chevron-down');
    if(icon[0] !== undefined) {
      // 'fa-chevron-down'クラスを持つ要素に'.spined-icon'クラスを追加(既にある場合は削除)
      $('.fa-chevron-down').toggleClass("spined-icon")
    }
  });
});

このコード$('.fa-chevron-down').toggleClass("spined-icon")では、fa-chevron-downクラスを持つ全てのi要素にspined-iconクラスを付与することになってしまい、全ての矢印が回転してしまう。

「クリックした要素のみ」「クリックした要素 jQuery」等で検索していると、この記事を見つけた。
jQueryの$(this)の使い方(どこを指してるのか?)

ここに、thisを使ってクリックした要素にのみ処理を行っている例と、thisではなく要素(記事中ではp)を指定してしまった時の例があり、後者では自分の失敗例と同じで全てのp要素に処理が行われてしまっている。

これを参考に、クリックしたcard-headeriにのみクラスを追加/削除する処理を行うコードを考える。
また、クリックしたcard-headeriに処理を行うため、再びfindを使った。

jQueryで子要素を取得するいくつかの方法〜children,find,contents

完成形

icon-animation-kansei.gif

logs/index.js
$(function(){
  $('.card-header').click(function(){
    var icon = $(this).find('.fa-chevron-down');
    // 詳細文がなく、アイコンがないcard-headerをクリックした時はundefinedが返されることを利用した
    if(icon[0] !== undefined) { 
      // thisをつけてクリックした要素にだけ効かせる。そうしないと全部の矢印にtoggleClassが効いてアニメーションしてしまう
      // findで.fa-chevron-downのついた要素を見つけ、そこにクラスを追加している
      // spined-iconというクラスでアニメーションをつけている
      $(this).find('.fa-chevron-down').toggleClass("spined-icon")
    }
  });
});

また、spined-icontoggleClassによって削除されても、fa-cheveron-downtransition-durationを設定することでアニメーションがうまくいった。

logs/index.scss
// ywtのカードヘッダーに、開閉可能な場合つける矢印
@mixin ywt-header-angle($anglecolor: $second-color) {
  i.fa-chevron-down {
    color: darken($anglecolor, 30%);
    // アイコンのクラスに、transitionをつけることで、閉じる際にもアニメーションを有効化している
    transition-duration: 0.5s;
  }
}

// 矢印のアニメーション用
.spined-icon {
  transform: rotate(90deg);
  transition-duration: 0.5s;
}

おわりに

JavaScriptやjQueryへの苦手意識はまだまだ消えないが、console.logで処理や戻り値を追いながら、1つずつ進めていくことで、なんとか実装。
心残りとしては、カードの開閉が可能かどうかをチェックするためのif(icon[0] !== undefined)について。
if(icon[0] == "<i class=\"fas fa-chevron-down\"></i>")じゃなぜダメだったのか?
indexOfが使えなかったのはなぜか?
iconの中の情報はどのように使うのが正解だったのか?

ここらへんに対してしっかり調べて解決することが出来なかったこと。
学習が進み、分かり次第追記予定。

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

TurbolinksでpushStateするには

JavaScriptでURLを変更し、ブラウザーではページ遷移しないようにするには、history.pushStateを使います。Turbolinksが動いている環境でpushStateを使うと、ブラウザーの進む/戻るで挙動がおかしくなります。Turbolinks以外がpushStateしたときページでは、popstateイベントをTurbolinksが処理しないからです。

Aページ -(pushState)-> Bページ -(リンク)-> Cページ -(戻るボタン)-> URLはBだけど画面が変わらない

Turbolinksを使っているときは、次のようにします。/users?name=Taro に戻ると、turbolinks:load イベントが発火するようになります。

let path = "/users?name=Taro";
Turbolinks.controller.
  pushHistoryWithLocationAndRestorationIdentifier(path, Turbolinks.uuid());

参考: Manage browser history manually for one part of my Turbolinks-enabled Rails 5 app - Stack Overflow

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

【Rails】lightbox2を用いて画像拡大機能の実装

目標

ezgif.com-video-to-gif.gif

開発環境

・Ruby: 2.5.7
・Rails: 5.2.4
・Vagrant: 2.2.7
・VirtualBox: 6.1
・OS: macOS Catalina

前提

下記実装済み。

Slim導入
投稿機能実装

実装

1.application.html.slimを編集

application.html.slim
doctype html
html
  head
    title
      | Bookers2
    = csrf_meta_tags
    = csp_meta_tag
    = stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload'
    / 追記
    link href='https://cdnjs.cloudflare.com/ajax/libs/lightbox2/2.7.1/css/lightbox.css' rel='stylesheet'
    = javascript_include_tag 'application', 'data-turbolinks-track': 'reload'
    / 追記
    script src='https://cdnjs.cloudflare.com/ajax/libs/lightbox2/2.7.1/js/lightbox.min.js' type='text/javascript'

2.image_tagを編集

books/show.html.slim
= link_to @book.image.url, 'data-lightbox': @book.image do
  = image_tag @book.image.to_s
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Angular 'Tour of Heroes' でMEANスタック

始めに

前回の記事MEANスタックの一歩前。Angular と Express の連携の続きです。
Angular の公式チュートリアル、'Tour of Heroes'をMEANスタック化します。

前回はAngular の公式からダウンロードした'Tour of Heroes'を若干修正して実際のサーバにアクセスするようにしました。
その後Expressサーバーを構築しました。
ですがデータベースは使用しておらず、サーバー内に配列としてヒーローのリストを持つのみとしていました。

今回はWindows環境ですが、MongoDBをセットアップしてMEANの「M」の部分を完成させたいと思います。

MongoDB のインストール

まずはMongoDBのダウンロードを行います。MongoDB公式で、「Available Downloads」に必要な環境情報を登録して「Download」をクリック。インストールパッケージをダウンロードします。私が使用したパッケージファイル名は「mongodb-win32-x86_64-2012plus-4.2.7-signed.msi」でした。

ダウンロードしたら、一応ウイルスチェックを行いましょう。
公式サイトでウイルスを検出してしまうことは経験上ありませんが、念のため。

続けてインストールを進めていきます。
今回は、すべてデフォルト設定を受け入れるだけなので難しい部分はありませんでした。

では早速、インストールを進めていきます。
まずはインストーラをダブルクリックで起動。
0001.png
Nextで次に進みます。

0002.png
ライセンスに同意する必要があります。

0003.png
Complete を選択します。

0004.png
問題が無ければデフォルト設定のまま、Nextをクリックします。

0005.png
GUI環境であるCompassを使用するため、チェックしたままNextをクリックします。

0006.png
Install をクリックしてインストールを開始します。

0007.png
インストール作業の途中でMongoDBのGUIツールであるCompasが起動しました。
0008.png
完了です。
自動で起動したCompasの画面です。
image.png

MongoDBにデータベースを登録

'Tour of Heroes'で使用するデータベースをMongoDBに登録します。
先ほどのCompasの画面中央にある緑のConnectボタンをクリックします。
1002.png
画面上部の「CREATE DATABASE」をクリックします。

1003.png
データベースの名前の登録を求められるので、「Database Name」と「Collection Name」に"heroes"と入力し、「CREATE DATABASE」をクリックします。

1004.png
heroes のデータベースが追加されているのが分かります。
「Heroes」をクリックします。

1005.png
データベースのコレクションとしても、先ほど登録したheroes が登録されているのが分かります。
さらにheroesをクリックします。
1006.png
すると、データが何もないことが分かります。
ここに新しいデータを登録していきます。

このタイミングでデータを登録しなくても、この後で作成する
MEANスタックの完成形から1項目ずつ登録する事で、'Tour of Heroes'の
データを再現する事もできます。

1007.png
「ADD DATA」をクリックし、ドロップダウンの「Insert Document」をクリックします。
1008.png
この様にJSON形式でデータを登録します。
ちなみに、数値以外の項目はすべて「"」でくくる必要があります。
チョットだけはまりポイントでした。コピペ用にテキストも張っておきます。

[
{ "id": 11, "name": "Dr Nice" },
{ "id": 12, "name": "Narco" },
{ "id": 13, "name": "Bombasto" },
{ "id": 14, "name": "Celeritas" },
{ "id": 15, "name": "Magneta" },
{ "id": 16, "name": "RubberMan" },
{ "id": 17, "name": "Dynama" },
{ "id": 18, "name": "Dr IQ" },
{ "id": 19, "name": "Magma" },
{ "id": 20, "name": "Tornado" }
]
1009.png
この様に、データが登録されました。

Express のMongoDB 対応

まずは Node でMongoDBにアクセスするため mongoose のモジュールを追加します。
コマンドプロンプトでプロジェクトのディレクトリに移動し、

D:\angular\toh-srv>npm install mongoose

として、mongooseをサーバープログラムに追加します。

次にソースファイルを修正していきます。
MongoDB に登録したデータベース対応するため、モデルを定義します。
前回の記事MEANスタックの一歩前。Angular と Express の連携のソースファイルに
model.js を追加します。

toh-srv\model.js
const mongoose = require('mongoose');

mongoose.connect("mongodb://localhost/heroes");

const HeroesModelSchema = new mongoose.Schema({
  id: Number,
  name: String
});

module.exports = mongoose.model('heroes', HeroesModelSchema);

この時、

mongoose.connect("mongodb://localhost/heroes");

の heroes は、MongoDBにデータベースを登録で登録した「Database Name」に対応しています。

続けて、サーバーのメインプログラムを修正していきます。

index.js
const express = require('express'); // expressモジュールを読み込む
const bodyParser = require('body-parser');  // body-parserモジュールを読み込む 
const multer = require('multer'); // multerモジュールを読み込む

var HeroesModel = require('./model');

const app = express(); // expressアプリを生成する
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())

// heroes リストデータ
/*const heroes  = [
    { id: 11, name: 'Dr Nice' },
    { id: 12, name: 'Narco' },
    { id: 13, name: 'Bombasto' },
    { id: 14, name: 'Celeritas' },
    { id: 15, name: 'Magneta' },
    { id: 16, name: 'RubberMan' },
    { id: 17, name: 'Dynama' },
    { id: 18, name: 'Dr IQ' },
    { id: 19, name: 'Magma' },
    { id: 20, name: 'Tornado' }
  ];
//*/
// 新しいヒーローIDを作成.
function genId(){
    return new Promise(function (resolve, reject){
        HeroesModel.find().sort({id:-1}).limit(1).exec({}, function(err, heroes){
            if(err) return handleError(err);
            if(heroes.length>0) resolve(heroes[0].id + 1);
            else resolve(11);
        });
    });
}

// ヒーローのリストを取得
app.get('/heroes', (req, res) => {
    console.log('@@ get heroes');

    if(req.query.name)
    {
        // req.query.name を含むヒーローのリストを作成して返す.
        const query_name = req.query.name;

        HeroesModel.find( { "name" : new RegExp( ".*"+query_name+".*") } ).select('id name').exec({}, function(err, heroes){
            if(err) return handleError(err);
            res.json(heroes);
        });
    }else{
        HeroesModel.find().select('id name').exec({}, function(err, heroes){
            if(err) return handleError(err);
            res.json(heroes);
        });
    }
});

// idを指定してヒーローを取得
app.get('/heroes/:id', (req, res) => {
    const query_id = req.params.id;
    console.log('@@ get heroes id:' + query_id);

    HeroesModel.find( { "id" : query_id } ).select('id name').exec({}, function(err, heroes){
        if(err) return handleError(err);
        if(heroes.length<=0)res.sendStatus(404);
        res.json(heroes[0]);
    });
});

// 新しいヒーローを登録
app.post('/heroes', (req, res) => {
    const query_name = req.body.name;
    console.log('@@post heroes :' + query_name);

    genId().then(function(newId){
        console.log(newId);
        var heroes = new HeroesModel({
            id: newId,
            name: query_name
        });
        heroes.save(function(err){
            if(err) return handleError(err);
            // 追加した項目をクライアントに返す
            res.json(heroes);
        });
    }).catch(function (error) {
        // 非同期処理失敗。呼ばれない
        console.log('catch err :' );
        console.log(error);
    });
});

// ヒーロー削除
app.delete('/heroes/:id', (req, res) => {
    const query_id = req.params.id;
    console.log('@@delete heroes id:' + query_id);

    HeroesModel.remove( { "id" : query_id }, function(err){
        if(err) return handleError(err);
        // ステータスコード200:OKを送信
        res.sendStatus(200);
    });
});

// ヒーロー名修正
app.put('/heroes', (req, res) => {
    // URLの:idと同じIDを持つ項目を検索
    const query_id = req.body.id;
    const query_name = req.body.name;
    console.log('@@put heroes / req.body:' + query_id);
    console.log('@@put heroes / req.body:' + query_name);

    HeroesModel.update( { "id" : query_id }, {$set: { name: query_name}} ).select('id name').exec({}, function(err){
        if(err) return handleError(err);
        // ステータスコード200:OKを送信
        res.sendStatus(200);
    });
});

// ポート3000でサーバを立てる
app.listen(3000, () => console.log('Listening on port 3000'));

先ほど定義したモデルをインポートしています。
代わりに heroes リストデータ をコメントアウトしています。
この部分は削除して構いません。

新しいヒーローIDを作成する genId 関数と併せて、
get、post、delete、put、それぞれの部分でMongoDBに対応したコードに置き換わっています。
特に post で genId を使用する部分では少し苦労しました。
Promise についての理解がなかったため MDNのサイトExpress Web フレームワーク (Node.js/JavaScript)で基本から学び直しました。

テスト

さて、これで完了です。
あとはサーバーとクライアントを双方起動させれば'Tour of Heroes'の元の状態を再現出来ていると思います。

終わりに

一応 MEANスタックと呼べる状態には出来たと思います。
しかし、本格的なWebアプリを構築しようとするとまだまだやるべき事があるように思います。
ここから何かしらの改造をできるかどうか・・・モチベーション次第かと(^^ゞ

リンク

MDN web docs。 Express だけではなく、Web系の開発のイロハが盛りだくさんでした。
Express Web フレームワーク (Node.js/JavaScript)

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

【Javascript】 メソッド まとめ 基礎基本コード メモ

【ゴール】

JSのメソッド、あれこれ

【メリット】

■ javascript理解度向上

【開発環境】

■ Mac OS catalina
■ Ruby on Rails (5.2.4.2)
■ Virtual Box:6.1
■ Vagrant: 2.2.7

【コード】

console.log

*括弧内()出力
*括弧内()には、変数、定数も突っ込める
*数字は""不要

hoge.js
console.log ("hoge"); //hoge出力

console.log(2); //2出力

①getElementById

*idタグの値を取得

②addEventListner

*処理を追加

③classList

*括弧内()指定したクラスを追加

hoge.html
<style>
  .font-color{
    color: red;
  }
</style>

<div id="target">pppp</div>


<script>
const id = document.getElementById('taregt'); //①

id.addEventListner(click, () =>{   // ②  クリックしたら
  id.classList(font-color);       //  ③ 上述のfont-colorというクラスを追加しますよ-
});
</script>

toggle

*クラスがあれば排除、なければ追加

hoge.html
<style>
  .font-color{
    color: red;
  }
</style>

<div id="target">pppp</div>


<script>
const id = document.getElementById('taregt');

id.addEventListner(click, () =>{   //  クリックしたら
  id.toggle(font-color);       // 上述のfont-colorというクラスをつけたり消したり-
});
</script>

【留意点】

末尾の「;」つけ忘れるべからず
*変数、定数の違いをしっかりと理解する
*時間かけてでもスペルミス撲滅、記述が他ファイルにまたがる為

【合わせて読みたい】

■ 【Javascript】JS 変数 定数 違い  一言でまとめました
https://qiita.com/tanaka-yu3/items/51b8b0630a1e4e2d52c8

■ 【JavaScript】 js 繰り返し文 for / while
https://qiita.com/tanaka-yu3/items/942d9d4838ebe14be1c2

■ 【jQuery】初心者でもよく理解できたやつ
https://qiita.com/tanaka-yu3/items/a03734b248c3f2ee8461

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

プレーンのJavaScriptで配列を回したいときはfindが便利

こんな配列があります。

const fruit = ["apple", "banana", "mango"];

for分の場合

一致したものを取得したいときに以下のように書いていました。

const fruit = ["apple", "banana", "mango"];
const target = "mango"; 
for (var i = 0; i < fruit.length i++) {
  if (target === fruit[i] {
    console.log("mangoと一致!");
  }
}

findを使った場合

const fruit = [apple, banana, mango];
const target = mango; 
const found = fruit.find(element => element === target); // mango

まとめ

簡単にかつシンプルにコードを書けるようになるので便利です。

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

[JavaScript]タイマー機能つけたくない?

タイマー機能で使うメソッド

主に、setInterval()メソッドと、setTimeout()メソッドがあります。
似てますが、少し違うメソッドなので使い分けていけましょう!

setInterval()メソッド

setIntervalは第一引数に与えられた関数を、第二引数に与えられた間隔で実行します。指定した時間ごとに何度も動作させたい時に使います。

// iが2を超えるまでshowTimeメソッドを1秒ごとに繰り返す。
{
  let i = 0;

  function showTime() {
    console.log(new Date());
    i++;
    if (i > 2) {
    //clearIntervalで指定することで、setIntervalの処理を好きなタイミングでストップ出来る
      clearInterval(intervalId);
    }
  }

  const intervalId = setInterval(showTime, 1000);
}

setTimeout()メソッド

setTimeout()メソッドは、関数が実行されてから関数内で指定した時間の分だけ時間が経過した後に、特定の動作をさせたいという場合に使うことができるメソッドです。

// iが2を超えるまでshowTimeメソッドを1秒ごとに繰り返す。
  let i = 0;

  function showTime() {
    console.log(new Date());
    const timeoutId = setTimeout(showTime, 1000);
    i++;
    if (i > 2) {
      clearTimeout(timeoutId);
    }
  }

  showTime();

違いは??

指定した時間ごとに繰り返したいとき→setInterval()メソッド
関数が終わってから指定した時間経過後に関数を繰り返す→setTimeout()メソッド

まとめ

たとえば、WEBページが画面上に表示されてからすぐではないタイミングでアラートを表示させたいというときに、出したいタイミングのミリ秒を2つめの引数に設定するときです。
または、サーバーなどに登録されているステータスが更新されるのを待っているというときにも使えます。数秒後にリロードさせるために、setTimeoutを使って再読み込みし、サーバー側の最新の状態を確認することができるようになります。
さらに、ある一定の時間経過したらログアウトさせたいというときにも、この関数を使うとよいでしょう。途中で、画面の操作が入力されたらタイマーをキャンセルして、もう一度最初からタイマーをカウントさせるということもできます。
setIntervalを使うタイミングは、スライドショーなど、画面上に表示させてあるものを変更したり動かしたりするときによく使われます。clearIntervalで停止させない限りずっと同じ間隔で画像の変更というプログラムを実行しているので、スライドショーのように動いてくれるのです。

使いどころはたくさんあるので使ってみましょう!!!

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

[JavaScript]数値の操作まとめ!

Mathオブジェクトを使いこなそう!!

数学的な定数と関数を提供するプロパティとメソッドを持つ、組み込みのオブジェクトです。関数オブジェクトではありません。
※Math は Number 型で動作します。 BigInt 型では動作しません。

Math.floor() メソッド

Math.floor() メソッドは、引数として与えた数以下の最大の整数を返します。

Math.floor( 45.95); //  45
Math.floor( 45.05); //  45
Math.floor(  4   ); //   4
Math.floor(-45.05); // -46 
Math.floor(-45.95); // -46

Math.ceil()メソッド

Math.ceil(メソッドは、引数として与えた数以上の最小の整数を返します。

Math.ceil(.95);    // 1
Math.ceil(4);      // 4
Math.ceil(7.004);  // 8
Math.ceil(-0.95);  // -0
Math.ceil(-4);     // -4
Math.ceil(-7.004); // -7

Math.round()メソッド

Math.round()メソッドは、引数として与えた数を四捨五入して、もっとも近似の整数を返します。

Math.round( 20.49); //  20
Math.round( 20.5);  //  21
Math.round( 42  );  //  42
Math.round(-20.5);  // -20
Math.round(-20.51); // -21

toFixed()

toFixed() メソッドは、数を固定小数点表記を用いてフォーマットします。

var numObj = 12345.6789;

numObj.toFixed();       // '12346'
numObj.toFixed(1);      // '12345.7'
numObj.toFixed(6);      // '12345.678900'

Math.random()メソッド

Math.random()メソッドは、0–1(0以上、1未満)の範囲で浮動小数点の擬似乱数を返します。その範囲ではほぼ均一な分布で、ユーザーは範囲の拡大をすることができます。実装側で乱数生成アルゴリズムの初期シードを選択し、ユーザーが初期シードを選択、またはリセットすることは出来ません。

// 0~1内でランダムな数を作成
var random = Math.random();

console.log( random );
// =>0.28230041446763

// 5~10内でランダムな数を作成
var min = 5 ;
var max = 10 ;

var a = Math.floor( Math.random() * (max + 1 - min) ) + min ;
// =>7.608635983586865
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】PlayCanvasを使ってモバイル・デスクトップで動く3D空間上で動画が見れるゲーム作る【WebGL】

大体10分ほどで、ブラウザ上で動く3D空間上で動画が見れるゲームを作ります。
動画を見る空間を作るまでを1つの記事として書きたかったのですが、分量の都合上、3つの記事に分けて公開させていただきました。
これが3つ目の記事になります。

PlayCanvasを使って3D空間を作る記事につきましてはこちらの2つの記事で作り方を解説しています。

  1. 【JavaScript】 PlayCanvasの公式サンプルを使ってモバイル・デスクトップで動く一人称視点の空間を作る【WebGL】
  2. 【JavaScript】Photon + PlayCanvasを使ってモバイル・デスクトップで動く一人称視点のマルチプレイができる空間を作る【WebGL】

今回作るもの

bigbugbunny.gif

前回の記事 【JavaScript】Photon + PlayCanvasを使ってモバイル・デスクトップで動く一人称視点のマルチプレイができる空間を作る【WebGL】のプロジェクトを元にして、3Dで動画が見れる空間を作成します。

完成品のプロジェクトはこちらになります。

https://playcanv.as/p/jfhaQCKG/

準備 アカウントの作成〜フォーク

準備するもの

プロジェクトの準備

フォーク

1.フォークするプロジェクトへアクセス

前回の記事で作成したプロジェクトのファイルをフォークします
https://playcanvas.com/project/696634/

プロジェクトにアクセスをしたらフォークボタンをクリックします。

2. Forkをクリックしてプロジェクトを複製

プロジェクト名を入力してForkボタンをクリックします。

3. フォークしたプロジェクトからEDITORへアクセス

プロジェクトをフォークしたら、EDITORをクリックします

4. シーンを選択

シーンを選択します。今回はUntitledというシーンを選択します。

ビデオテクスチャの実装

これまで、前回までの記事で作成したシーンを複製することができました。

ビデオを再生するための準備をしましょう。ビデオを再生するためには、ビデオテクスチャを使用します。今回は四角いエンティティを作成して、そこにビデオをテクスチャを貼ります。

スクリーンにするエンティティを作成

3D空間に動画を流すために、まず、映像を映し出すためのエンティティを作成します。

1. Boxエンティティを新規作成する

2. エンティティの大きさを変更

エンティティの大きさを変更しましょう。
それぞれ下記の値に設定をします。

プロパティ ( scale)
x 16
y 9
z 0

3. エンティティのポジションを変更

エンティティの位置を変更しましょう。こちらは好きな位置に配置をしてしまって大丈夫です。

4. エンティティの名前をScreenに変更

エンティティが増えてきたときにわかりやすいように、右のINSPECTORからnameの値をBoxScreenに変更します。

マテリアルの作成

動画を映し出すためのMaterialを作成します。

1. ASSETSで右クリックをしてマテリアルを作成

2. DIFFUSEの値を黒色に設定

マテリアルのDIFFUSEを黒色に変更します。

3. エンティティにマテリアルをドラッグアンドドロップで追加

エンティティにマテリアルを追加します。
マテリアルを選択して、エンテティにドラッグアンドドロップをすることでマテリアルを追加できます。

スクリプトを追加

エンテティ、マテリアルの設定ができたので、次に動画テクスチャーのスクリプトを追加します。

動画テクスチャについては下記リンクのチュートリアルにもサンプルのプロジェクトがあります。

動画テクスチャー
https://developer.playcanvas.com/ja/tutorials/video-textures/

動画については、BigBuckBunnyを使用します。

https://gist.github.com/jsturgis/3b19447b304616f18657

1. Rootエンテティを選択し、SCRIPTコンポーネントのCreate Scriptをクリック

2. ファイルを新規作成

Create Scriptをクリックすると、入力欄が出てきますので、ここにvideo-texture.jsと入力をしてエンターを押します。

3. ASSETSからvideo-texture.jsをダブルクリック

4. 開かれたコードエディタにスクリプトをコピー

コードエディタが開かれますが、そこに下記のスクリプトをコピーします。

/*jshint esversion: 6, asi: true, laxbreak: true*/

const VideoTexture = pc.createScript('videoTexture')

VideoTexture.attributes.add('material', {
    type: 'asset',
    assetType: 'material'
})

VideoTexture.attributes.add('videoSrc', {
    type: 'string',
    default:
        'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4'
})

VideoTexture.prototype.initialize = function () {
    this.videoTexture = new pc.Texture(this.app.graphicsDevice, {
        format: pc.PIXELFORMAT_R8_G8_B8,
        autoMipmap: false
    })
    this.videoTexture.minFilter = pc.FILTER_LINEAR
    this.videoTexture.magFilter = pc.FILTER_LINEAR
    this.videoTexture.addressU = pc.ADDRESS_CLAMP_TO_EDGE
    this.videoTexture.addressV = pc.ADDRESS_CLAMP_TO_EDGE

    var video = document.createElement('video')
    video.addEventListener('canplay', () => {
        this.videoTexture.setSource(video)
    })

    const loadVideo = () => {
        video.src = this.videoSrc
        video.loop = true
        video.muted = true
        video.autoplay = false
        video.crossOrigin = 'anonymouse'
        video.setAttribute('playsinline', '')
        video.setAttribute('webkit-playsinline', '')
    }
    loadVideo(this.videoSrc)

    this.app.mouse.on(pc.EVENT_MOUSEDOWN, () => {
        video.muted = false
        video.play()
    })
    if (pc.platform.mobile) {
        this.app.touch.on(pc.EVENT_TOUCHSTART, () => {
            video.muted = false
            video.play()
        })
    }

    const material = this.material.resource
    material.emissiveMap = this.videoTexture
    material.update()
}

VideoTexture.prototype.update = function (dt) {
    this.videoTexture.upload()
}

5. スクリプトの変更を保存する

Ctrl + sやMacの場合はCommand + sを押して、スクリプトの変更を保存します。
左側のファイル名が白色になっていれば、変更が保存されています。

6. エディタへ戻り、スクリプトのPARSEをクリック

スクリプトの保存ができたら、エディタに戻り、video-texture.jsをエディタ側で設定をします。

まず、スクリプト名の横にあるPARSEボタンをクリックします。
すると、material, videoSrcの選択肢が出てくるかと思います。
videoSrcについては先程コピーしたスクリプトで、デフォルトで値を設定しているURLになっています。

7. マテリアルをドラッグアンドドロップでスクリプト属性に追加する

materialの属性に、先程作成したマテリアルをドラッグアンドドロップで適用します。

起動

3D空間に動画の配置をするための設定はこれで完成です。
ビデオテクスチャとして使用することで、動画を再生するプロジェクトは簡単に作ることができました。

完成品 : https://playcanv.as/p/jfhaQCKG/

ゲームを公開する

PlayCanvasでは作成したゲームをウェブ上ですぐに公開できます。
こちらは今回作成したゲームを公開するための方法になります。

1. 左のMEBUからPUBLISH/DOWNLOADをクリック

publish.png

2. PUBLISH TO PLAYCANVASから公開

oc.png

3. BUILDSを確認

PUBLISHが成功するとBUILDSに共有できるURLが生成されます。
こちらを共有することで、第三者に完成したプロジェクトを公開できます。

URL.png

今回のプロジェクトで質問や意見がありましたら。GitHubのIssueか@mxcn3まで連絡をお願いします。

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

safari(ios)でMediaRecorderを使う

前提

使用デバイス等の説明

機種

iPad 13.5.1
safari

実現すること

MediaRecorderを使おうと調べたらSafari未対応という記事しかない
が、iosでMediarecorderを使うことができるっぽいことを見つけたので
まとめておく

方法

方法としては非常に簡単である

①設定を開く
②Safariの項目を開く
③詳細の項目を開く
④Experimental Featureを開く
⑤MediaRecorderをオンにする

試用機能なのかな?詳しくはわからないが、これで使えるはずです。

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

旧石器時代のJavaScriptで30分でChrome拡張機能を作った(Qitta夏祭り)

はじめに

本記事は、普段旧石器時代のJavaScriptで業務をしている私が、
旧石器時代の技術を用いてChrome拡張機能を作ったので、ソースコードを公開する敷居を下げて成果物の公開をためらう人のお役に立てればいいなぁという記事です。
参考になる情報はあまりないため、本当に暇つぶしに読んでいただければ幸いです。

今回作るもの

今回作るものは以下です。

  1. Qitta記事の読了目安時間を表示する。
  2. 記事作成時にタブスペースできるようにする。 ソース全文はこの記事と以下のリポジトリに配置しています。

https://github.com/mamoru12150927/QittaExtension

想定読者

  • JavaScriptよくわからない人
  • 何らかのプログラムを公開してみたいがためらっている人
  • Chrome拡張機能を作って見たい人

Chrome拡張機能とは

文字通り、Webブラウザの1つ「Google Chrome」を拡張するものです。
Web御三家の「HTML」「CSS」「JavaScript」と設定ファイル「manifest.json」
使って作成でき、
公開もとても簡単なためプログラムを公開してみたい人の練習にももってこいです。

早速作ろう

上記の通り、拡張機能はmanifest.jsonと各種言語で作成します。
manifest.jsonはその拡張機能が何を使うか、何をするものかを定義します。
普段お使いのフレームワークやライブラリにも似たようなものがあるはずです。
(pom.xmlとかPackage.jsonとか・・・)
まずは作業ディレクトリを作成し、「manifest.json」から作成しましょう。
今回使うものは以下の内容です。

manifest.json
{
    "name" : "QittaExtensionPack",
    "version" : "1.0",
    "description" : "Qittaでかゆいところに手が届く拡張機能です。",
    "manifest_version" : 2,
    "permissions": ["declarativeContent", "storage"],
    "content_scripts":[
        {
          "matches": ["https://qiita.com/*"],
          "js": ["jquery_3.5.1.js","content.js"]
        }
      ]
}

それぞれの役割は以下の通りです。

キー 役割
name 拡張機能の名前です。公開の際はこの名前が使用されます。
version 拡張機能のバージョンです。任意の数値を指定できます。
description 拡張機能の説明です。公開の際はこの説明が使用されます。
manifest_version ルールで2を指定します。
permissions この拡張機能が使用するブラウザの機能を決めます。
content_scripts matchesで指定したURLでのみ、動作させるjsファイルを指定します。

他にもかなり多くの設定があるのですが、今回はこれだけで十分です。
Qitta夏祭りにChrome拡張機能でエントリーしたい方は、作る機能によってはほとんど同じ設定を流用できます。

content.jsの中身

今回すべてのコードは、JavaScript(ES2015以前) + Jquery3.5.1を利用しています。

コードは以下になります。

content.js
$(function(){
  let readText = $('section').text();
  $('textarea').on('keydown' , 
  function (e){
    if(e.keyCode === 9) {
      e.preventDefault();
      let elm = e.target;
      let val = elm.value;
      let pos = elm.selectionStart;
      elm.value = val.substr(0, pos) + '\t' + val.substr(pos, val.length);
      elm.setSelectionRange(pos + 1, pos + 1);
    }
  });
  $('.it-Tags').append(addText(readText));
});

function addText(text) {
  if(text !== void 0) {
    let textLen = text.length === 0 ? 1 : text.length;
    let calcReadTime = Math.round(textLen / 400);
    return '<p>' + calcReadTime + '分で読めます' + '</p>';
  }
}

$(function()は、ロード時に実行される関数で、ここで
記事内のSection要素のテキストを取得します。
Section要素には、Qittaの記事の全文が記録されており、
これをもとに読了時間を計算します。
また、さり気なくロード時にテキストエリアへのキーダウンを検知するよう設定し、
タブキー(keycode:9)が押されたときにタブスペースを入れるようにします。

実際にChromeで動かす。

上記で作成した「manifest.json」と「content.js」、「Jquery_3.5.1.js」を一つのディレクトリに保存します。
このようなイメージです。
directory構造.png

Google Chromeを開き、拡張機能設定のページを表示します。
ページ上部の「デベロッパーモード」をONにし、その付近の「パッケージ化されていない拡張機能を読み込む」から、先程のディレクトリを指定します。

これで拡張機能のインストールが完了しました。
実際にQittaの記事を表示すると、タグの横に読了目安が表示されているはずです。

実際にやってみた。.png

また、Qittaのテキストエリア全てで、タブスペースが使えるはずです。

終わりに

雑な記事でエントリーになりましたが、Qittaをより便利にするのは
技術力ではなく目の付け所もあると思います。
こういう今となっては微妙なコードでも便利だと感じてくれるひとがいるのであれば、
やる価値は大きいです。
クソレガシーな技術で飯食ってる僕でも作れたので、皆様ならもっと良い機能を作ることができるはず!
今回作ったものはお布施の5ドルが払える給料日後に公開します。

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

いのべこ夏休みアドベントカレンダー 2020

はじめに

「いのべこ」とは、株式会社富士通システムズウェブテクノロジーの社内技術コミュニティで、「イノベーション推進ミュニティ」が略されたものです。
「いのべこ」の2020年夏企画として「いのべこ夏休みアドベントカレンダー」を企画しました。昨冬のアドベントカレンダー同様、参加者の皆さんで盛り上げていきましょう!
参考:富士通システムズウェブテクノロジーアドベントカレンダー 2020

ルール

  • 参加者:当社社員・派遣社員および富士通グループ社員に限らせて頂きます
  • テーマ:「技術」に関することならなんでもOK
  • 記事形式:自由です。Qiita、個人ブログ、YouTube、なんでもありです
  • Fujitsu Way、Fujitsu Wayのサイトにある「ソーシャルメディアを利用する場合のルールとマナー」の記述を守るようにしましょう
  • Qiitaのガイドラインも目を通しましょう

参加登録方法

  • 記事の下方にある編集リクエスト機能を使い、参加したい日付に以下の項目を書いて送ってください
    • 執筆者名(ペンネームでもOKです)
    • タイトル(仮で大丈夫です)
  • なるべく早く反映するようにします
  • 後からでも修正可能です。同様にご連絡ください
  • 毎週水曜日は、当社社員以外の優先枠とします(が、埋まり具合で見直しもあります)

記事作成後の登録方法

  • 編集リクエストで記事のタイトルとURLを送ってください
  • (できるだけ)当日までに記事を書いて登録してください

さいごに

このページの記述はUnityゆるふわサマーアドベントカレンダー 2019を運営された@nkjzmさんの下記記事を参考とさせていただきました。ありがとうございました!
2年連続で夏のアドカレ運営をやってみての感想 【ゆるふわアドカレ】

いのべこ夏休みアドベントカレンダー 2020 記事一覧

Date 記事名 執筆者名
8/1(土) 先鋒やります。テーマは未定。 ikeda.hideya
8/2(日) (募集中)
8/3(月) 今回もスマスピで何か書く(仮) じゅげむ(今井 隆)
8/4(火) (募集中)
8/5(水) (募集中)
8/6(木) (募集中)
8/7(金) 検討中! takapom
8/8(土) (募集中)
8/9(日) (募集中)
8/10(月) Vue.jsでアプリを作って遊んでみた eight-cotton(八幡怜奈)
8/11(火) (募集中)
8/12(水) (募集中)
8/13(木) (募集中)
8/14(金) (募集中)
8/15(土) (募集中)
8/16(日) (募集中)
8/17(月) Pivotal流の振り返り会、教えます! 小とろ(花岡 舜)
8/18(火) (募集中)
8/19(水) (募集中)
8/20(木) (募集中)
8/21(金) (募集中)
8/22(土) (募集中)
8/23(日) (募集中)
8/24(月) (募集中)
8/25(火) (募集中)
8/26(水) (募集中)
8/27(木) (募集中)
8/28(金) (募集中)
8/29(土) 何か書く(ネタ枠) George22e(筒井城治)
8/30(日) (募集中)
8/31(月) (募集中)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ブラウザが認識できる言語に変換する仕組み

高級言語と低級言語

・高級言語
高級言語は、機械よりも人間が理解しやすいように設計されたプログラミング言語のことです。
また、「高級」とは優れた言語という意味ではなく、機種やOSなどに固有の要素を抽象度の高い記述が可能であることを表しており、アプリケーションソフトの開発などに用いられる言語の多くは高級言語に分類されるから「高級」という表現を用いています。

・低級言語
機械が認識しやすい言語は低級言語と呼ばれます。
「低級」とは劣った言語であることを意味するのではなく、ハードウェアに近いことを意味
することから「低級」という表現を用いています。

ファイルをブラウザに表示させるには、高級言語で開発したあとに、それを機械が認識
できる低級言語に翻訳する必要があり、その翻訳の事をコンパイルと呼びます。

コンパイル

プログラミング言語を、動作する機械が理解できるように翻訳する作業のことです。
コンパイルは、コンパイラ(機会にやさしいプログラミング言語のこと)と呼ばれる
プログラムによって行われます。
もしコンパイラで認識できない言語があれば、あらかじめ認識できる言語にプリコンパイルしておく必要があります。

プリコンパイル

コンパイラが翻訳できない言語を翻訳できるようにする事前コンパイルのことです。あるいは、メインとなる処理に対して必要な前処理のことを指します。
つまり、コンパイルやメインの処理の前に行われるべきコンパイルが、プリコンパイルです。

たとえば、Webアプリケーションでは、Webブラウザが静的ファイルを読み込んで表示するというメインの処理ができるように、高級言語で書かれたファイルをプリコンパイルしてからブラウザに渡しています。

このようなプリコンパイルを行うための手法として、アセットパイプラインという仕組みがあります。

アセットパイプライン

JavaScriptやCSSなどのアセットと呼ばれる静的ファイルを小さくまとめてくれる機能です。
アセットパイプラインには、高級言語のプリコンパイルを行う機能も追加できるため、これを応用してSCSSやTypeScriptなどを使用した開発もできるようになります。

アセットパイプラインの処理は、「①プリコンパイル」「②連結」「③圧縮」「④配置」の流れで行われます。
複数の静的ファイルをプリコンパイルして連結したのち、圧縮して軽量化したものをpublicディレクトリに配置して、ブラウザへ渡せるようにします。

これまでRailsでは、CSSやJavaScript等をプリコンパイルするために、SprocketsというGemによるアセットパイプラインをデフォルトで用いていました。

しかし、近年JavaScriptのライブラリ機能を合わせて開発することが大体となってきたため、Railsではバージョン6以降から、モジュールバンドラを使用したプリコンパイルを主流とするようになりました。

モジュールバンドラ

モジュールバンドラは、JavaScriptのモジュールを依存関係を考慮しながら管理するツールです。

モジュールとは、機能を1つずつ分けて他のファイルから読み込めるようにした処理のまとまりのことです。
JavaScriptでは複数の機能をファイルごとに分割すると、1つのファイルへまとめる際に不具合が生じるため、Node.js環境では機能をモジュールという単位で扱います。

モジュールによって、大量の機能を複数のファイルに分けることができ、分けられた機能を1つのファイルにまとめても問題なく動作できます。
ただし、複数に分かれるため依存関係が生まれ、読み込みができてないモジュールがあると正しく動作できない問題があります。

これを解消するのが、モジュールを一括で束ねて管理してくれるモジュールバンドラです。
近年では、モジュールバンドラの中でもwebpackが主流となりつつあります。

webpack

webpackとは、Webアプリケーションを作成する際に必要な、さまざまなJavaScriptをひとまとめに管理するためのツールです。
webpackが行うことは、大きく分けて4つあります。

基本要素 役割
Entry 依存関係を解決するために、どのファイルを基準(エントリーポイント)とするかを決める
Output エントリーポイントにされ、webpackによってまとめられたファイルを、どこへどのような名前で出力(アウトプット)するのか指定する
Loaders JavaScript以外のCSSやHTMLなどのファイルをモジュールに変換する方法を読み込み(ロード)、指定した処理を行う
Plugins 圧縮などの、ファイルをまとめる以外でローダーが実行できないタスクを導入し、拡張(プラグイン)する

つまり、webpackを用いることで、JavaScriptのライブラリとJavaScript以外のさまざまな言語を変換・圧縮した上で、好きな場所に配置することが可能となります。

Railsにもwebpackを導入してコマンド操作が可能ですが、設定ファイルの記述がやや難しいことから、設定を簡易化してくれるWebpackerというGemがあります。

Webpacker

webpackをRails仕様にし、専用の設定ファイルやヘルパーメソッドを用意してくれるGemです。Railsバージョン6系以降からは、デフォルトでWebpackerが導入されます。
Webpackerによって、Sprocketsのアセットパイプラインと同じような静的ファイルのプリコンパイルに加え、JavaScriptのパッケージが利用できるようになります。

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

Alibaba CloudでMongoDBを使ってNode.jsでREST APIを作成する方法

この記事では、Alibaba Cloud上でMongoDBを使ってNode.jsで強力なREST APIを作成する方法を探っていきます。

アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ

Alibaba Cloud ECSにMongoDBをインストールする

MongoDBのインストールは簡単です。

パッケージ名 説明
mongodb-org 以下の4つのコンポーネントパッケージを自動的にインストールするメタパッケージです。
mongodb-org-server デーモンと関連する設定スクリプトと init スクリプトが含まれています。
mongodb-org-mongos デーモンが含まれています。
mongodb-org-shell シェルが含まれています。
mongodb-org-tools 以下の MongoDB ツールが含まれています: mongoimport bsondump, mongodump, mongoexport, mongofiles, mongorestore, mongostat, and mongotop.

mongodb-org-serverパッケージは、/etc/mongod.conf設定ファイルでmongodを起動する初期化スクリプトを提供します。

このリンクを使用して、ECS Ubuntu ServerにMongoDBをインストールするには、以下の手順に従ってください: https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/

インストール後、シンプルなREST APIの構築に進むことができます。

REST API

RESTとはRepresentational State Transferの頭文字をとったものです。最近ではREST APIが非常に普及しており、Webアプリケーションとモバイルアプリケーションの両方のサーバーサイドを処理するために使用されています。実際、ほとんどの大手インターネット企業では、GoogleカレンダーAPIなど、少なくともいくつかのREST APIを導入しています。データベースが構築されれば、APIを使ってデータやコンテンツを簡単にアプリケーションに配信することができます。これらのAPIは、CRUDプロセスを可能にするために、サーバーがリクエストにどのように応答し、受け入れるかを表しています。このチュートリアルでは、質問を投稿したり、回答を投稿したり、スケーラブルな MongoDB 上で回答を投票したり削除したりするためのシンプルな API を構築します。プロジェクトの実装を成功させるための鍵は、ルートを正しく構造化することです。この場合、ルートは以下のようにプログラムされます。

1、投稿された質問を分析する
2、回答の読み取り、作成、編集、削除
3、回答のアップダウン投票
この例では、以下のようになります。

開発環境

このプロジェクトでは以下のツールを使用しています。

1、Plain JavaScript
2、Node.js
3、Express (JS framework)
4、MongoDB (データベース)
5、Yarn (パッケージ管理)
6、Visual Studio Code as editor
7、Postman (APIエンドポイントをテストするため)

依存関係

使用する依存パッケージ

1、Parser (受信エンドポイントリクエストの本文を解析します)
2、Express(アプリケーションを起動)
3、Nodemon(変更を反映させるためにサーバーを再起動する)
4、Mongoose (オブジェクトデータモデルを使って MongoDB のインタラクションを簡素化)
5、Morgan(HTTP リクエストをログに記録するミドルウェア)
6、Eslint with Airbnb (高品質なコードの作成を支援するための拡張機能です)

チュートリアルのまとめ

この例では、以下の作業を行います。

1、express を使用して API ルートを作成する
2、モデリングAPIデータ
3、Mongooseを使ってMongoDBに連絡する
4、Postmanを使ったAPIのクリーンアップとテスト

Expressを使ったAPIルートの構築

基本的なこと

開発を簡単にするには、nodemonをインストールして、パッケージのpackage.jsonにスクリプトを追加します。

1、expressを使って基本的なWebサーバーを作成します(expressにはこのプロセスのチュートリアルがあります)。
2、エクスプレスミドルウェアが柔軟に設定されていることを確認してください。
3、リクエストを処理するためのボディパーサを含む
4、パーサーをエクスプレスミドルウェアに組み込む

質問のためのルートを作成する

1、サーバーで、ルートを格納するファイルを作成します。
2、POST と GET の両方のルートを使用して質問を検索し、新しい質問を追加します。
3、具体的な質問は、GETルートを作成します。

router.get('/', (req, res) => {
  res.json({ response: 'a GET request for LOOKING at questions' });
});
router.post('/', (req, res) => {
  res.json({
    response: 'a POST request for CREATING questions',
    body: req.body
  });
});
router.get('/:qID', (req, res) => {
  res.json({
    response: `a GET request for LOOKING at a special answer id: ${req.params.qID}`
  });
});

回答ルート

1、最初のステップは、Morgan HTTPリクエストロガーをインストールすることです。このパッケージは、あなたのためにリクエストを分析するのに役立ちます。
2、次に、回答を受け入れるためのPOSTルートを作成します。
3、次に、回答の編集と削除を可能にするためにPUTとDELETEをインクルードします。
4、回答のアップ投票とダウン投票のためのPOSTルートを作成します(このチュートリアルでは簡単な投票を使用します)。

router.post('/:qID/answers', (req, res) => {
  res.json({
    response: 'a POST request for CREATING answers',
    question: req.params.qID,
    body: req.body
  });
});
router.put('/:qID/answers/:aID', (req, res) => {
  res.json({
    response: 'a PUT request for EDITING answers',
    question: req.params.qID,
    answer: req.params.aID,
    body: req.body
  });
});
router.delete('/:qID/answers/:aID', (req, res) => {
  res.json({
    response: 'a DELETE request for DELETING answers',
    question: req.params.qID,
    answer: req.params.aID,
    body: req.body
  });
});
router.post('/:qID/answers/:aID/vote-:dec', (req, res) => {
  res.json({
    response: 'a POST request for VOTING on answers',
    question: req.params.qID,
    answer: req.params.aID,
    vote: req.params.dec,
    body: req.body
  });
});

エラーハンドラ

どんなアプリケーションでもエラーを回避することはほぼ不可能です。この例では、以下の手順でエラーハンドラを設定します。

1、ミドルウェアは「エラーキャッチャー」として動作します。
2、404エラーをキャッチした後、ミドルウェアを使用して、JSON応答形式で応答するカスタムハンドラにそれを渡します(そうでなければ、以下のように500を使用します)。
3、検証ミドルウェアを設定して、エラーのアップ投票やダウン投票を可能にします。

app.use((req, res, next) => {
  const err = new Error('Not Found');
  err.status = 404;
  next(err);
});
app.use((err, req, res, next) => {
  res.status(err.status || 500);
  res.json({
    error: {
      message: err.message
    }
  });
});

Mongooseを使ってMongoDBに接続する

APIのデータモデルを選択する

MongoDB のストレージに選ばれたデータ型が、正しい構造と関係性を明確に表現していることが非常に重要です。Mongoose は MongoDB へのゲートウェイとして機能します。JSONデータ形式を受け入れるスキーマを作成するために使用します。今回のケースでは、回答のプロパティを持つ質問オブジェクトを使用するのがベストなアプローチです。とはいえ、Mongo ドキュメントにはストレージユニットの最大数があるので、質問に対する回答は無制限ではないことを覚えておきましょう。

スキーマの作成

1、選択した親子構造に応じて、Mongoose で適切なスキーマを作成します。
2、スキーマを使ってモデルを構築する

const AnswerSchema = new Schema({
  text: String,
  createdAt: { type: Date, default: Date.now },
  updatedAt: { type: Date, default: Date.now },
  votes: { type: Number, default: 0 }
});
const QuestionSchema = new Schema({
  text: String,
  createdAt: { type: Date, default: Date.now },
  answers: [AnswerSchema]
});
const Question = mongoose.model('Question', QuestionSchema);

機能にSortとVoteを組み込む

1、最新の回答を優先します
2、回答スキーマに投票の保存オプションを含める
3、Mongoose に、すべての答えを保存しながら事前にソートするように指示します。
4、parentメソッドを使用して、質問の親ドキュメントに回答が参照されるようにします。
5、仕様書で矢印関数の使用を避ける

const sortAnswers = (a, b) => {
  if (a.votes === b.votes) {
    return b.updatedAt - a.updatedAt;
  }
  return b.votes - a.votes;
};
QuestionSchema.pre('save', function (next) {
  this.answers.sort(sortAnswers);
  next();
});
AnswerSchema.method('update', function (updates, callback) {
  Object.assign(this, updates, { updatedAt: new Date() });
  this.parent().save(callback);
});
AnswerSchema.method('vote', function (vote, callback) {
  if (vote === 'up') {
    this.votes += 1;
  } else {
    this.votes -= 1;
  }
  this.parent().save(callback);
});

APIをMongoDBにリンクする

あなたは、私の考えでは最も困難な課題に取り組もうとしています。この時点でMongooseのドキュメントをよく見てください。

エラー処理

1、この場合、paramメソッドを使用してqIDとaIDルートのコールバックを持つことにします。
2、このメソッドは、質問や回答がデータベースで利用できない場合にエラーを識別することができます。

router.param('qID', (req, res, next, id) => {
  Question.findById(id, (err, doc) => {
    if (err) return next(err);
    if (!doc) {
      err = new Error('Document not found');
      err.status = 404;
      return next(err);
    }
    req.question = doc;
    return next();
  });
});
router.param('aID', (req, res, next, id) => {
  req.answer = req.question.answers.id(id);
  if (!req.answer) {
    err = new Error('Answer not found');
    err.status = 404;
    return next(err);
  }
  return next();
});

質問ルートの準備

1、これは、Mongo データベース内の質問を GET ルートを使って探します。
2、適切な質問を返します。
3、また、新しい質問を JSON 形式で POST ルートを使ってデータベースに投稿します。
4、1つの質問に対してGETルートを指定します。

router.get('/', (req, res, next) => {
  Question.find({}).sort({ createdAt: -1 }).exec((err, questions) => {
    if (err) return next(err);
    res.json(questions);
  });
});
router.post('/', (req, res) => {
  const question = new Question(req.body);
  question.save((err, question) => {
    if (err) return next(err);
    res.status(201);
    res.json(question);
  });
});
router.get('/:qID', (req, res) => {
  res.json(req.question);
});

回答を更新するためのルート

1、回答を作成するためのPOSTの実行は簡単です。このメソッドでは、質問ドキュメントに回答をプッシュし、JSON形式で保存します。
2、新しいJSON入力を返すことで回答を更新できるようにするために、updateメソッドを使用します。
3、したがって、回答を削除するためにDELETEルートを使用する場合は、removeメソッドを使用します。
4、投票の場合は、ミドルウェアに埋め込まれたPOSTルートを使用して、voteメソッドで投票を保存します。
以下の例では、この操作を説明しています。

router.post('/:qID/answers', (req, res, next) => {
  req.question.answers.push(req.body);
  req.question.save((err, question) => {
    if (err) return next(err);
    res.status(201);
    res.json(question);
  });
});
router.put('/:qID/answers/:aID', (req, res, next) => {
  req.answer.update(req.body, (err, result) => {
    if (err) return next(err);
    res.json(result);
  });
});
router.delete('/:qID/answers/:aID', (req, res) => {
  req.answer.remove(err => {
    req.question.save((err, question) => {
      if (err) return next(err);
      res.json(question);
    });
  });
});
router.post(
  '/:qID/answers/:aID/vote-:dec',
  (req, res, next) => {
    if (req.params.dec.search(/^(up|down)$/) === -1) {
      const err = new Error(`Not possible to vot for ${req.params.dec}!`);
      err.status = 404;
      next(err);
    } else {
      req.vote = req.params.dec;
      next();
    }
  },
  (req, res, next) => {
    req.answer.vote(req.vote, (err, question) => {
      if (err) return next(err);
      res.json(question);
    });
  }
);

これで、REST APIが消費されるようになりました。これでREST APIは消費するための準備が整いました。これから次のフェーズに入ります。

エンドポイントのクリーンアップとテスト

APIのテスト

すべてのエンドポイントの機能をテストする最も簡単な方法は、Postmanを使用することです。Chromeの拡張機能を使うこともできますし、デスクトップアプリケーションをダウンロードすることもできます。どちらを選んでも構いませんが、個人的にはChromeの拡張機能が好きです。Postmanは使い方がシンプルで、HTTPテストができます。手動でテストしたくない場合は自動テストを設定することもできます。

クロスオリジンリソース共有

CORSはセキュリティ上の理由で制限されています。基本的には、ブラウザによるドメインリソースへのアクセスを許可する方法です。そのため、以下のようなステップを経て、ドメインからのAPI消費を可能にするミドルウェアが必要となります。

1、ヘッダはすべてのオリジンへのアクセスを持つ必要があります。
2、HTTP リクエストを有効にする

app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header(
    'Access-Control-Allow-Headers',
    'Origin, X-Requested-With, Content-Type, Accept'
  );
  if (req.method === 'Options') {
    res.header('Access-Control-Allow-Methods', 'PUT, POST, DELETE');
    return res.status(200).json({});
  }
});

フロントエンドの作業を開始する

あなたのAPIは、あらゆるタイプのフロントエンドと接続できるように設定されています。プロセスは、適切なヘッダーとルートを使用して投稿、取得、配置、削除を行うのと同じくらい簡単です。

結論

REST APIは、非常にスケーラブルなバックエンドマイクロサービスのセットアップに使える素晴らしいツールです。今回のプロジェクトではMongooseを使って、スケーラブルなオープンソースデータベースであるMongoDB上にシンプルなAPIを実装しました。Alibaba Cloudを使ってMongoDB上でモバイルアプリケーションを動かしてみましょう。この記事をお楽しみいただけたでしょうか。

アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ

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

モノリスはクラピカ

Reactを学習中のRailsエンジニアです。学習していて思うのは、Reactは(サービス開発全体のことを考えると)Railsよりはるか学習コストが高いということ。似たようなサービスを作るために必要な手数も、考えなければいけないこともかなり多いです。

正直最初は「技術が大好きなエンジニアの自己満だろ」「Railsに飽きた奴らがやってるだけだろ」くらいに思っていたのですが、そうとも限らないことが腹落ちして理解できるようになったので、現在の考えをまとめてみようと思います。

おことわり

この記事の中で登場するSPAという単語はフロントエンドとバックエンドを明確に分けて開発されるアプリケーションくらいの意味として解釈してください。フロントエンドのロジックをNuxt.jsで作ってNetlifyにデプロイして、Goで作ったAPIをAWSで動かす、みたいな構成のやつです。乱暴ですみません。他に良い表現があればコメントください。

モノリスはRailsやLarabelでviewファイルの生成まで行っているアプリケーションを指します。

SPA⇄MPA
マイクロサービス⇄モノリス
で比較しろよ、って話ですが、マイクロサービスについて語れるほどの知識はないし、MPAというよりモノリスの話がしたかったので、雰囲気で読んでください。

SPAが台頭した理由

よりリッチな表現ができる(UXの向上)

ページ遷移が高速、DOMを色々動かしてもコードがカオスになりにくいなど。SPAのメリットとして真っ先にあげられることが多いので、みなさんもよくご存知かと思います。

フロントエンドとバックエンドが疎結合になる

疎結合になると、新技術を部分的に採用することが容易になります。またサービスの規模が大きくなっても、コードがカオスになりづらいです。Railsエンジニアをやっていると、成長し大規模化したRailsアプリ開発者がつらそうにしている記事をよく見かけます。

クロスプラットフォーム対応

SPAを採用すると、web、iOS、Android、macOS用アプリ、windows用アプリで同じAPIを使うことができます。
元々web以外のプラットフォームでは、表示周りやページ切り替え等のロジックを先にインストールして、他に必要なデータのやりとりだけをAPIを使って行う、というスタイルで統一されていました。webも同じスタイルに揃うと構成がキレイになってすっきりしますね。

マネージドサービスの充実

この記事を書くに至った理由です。この視点を得て、SPAが普及したことの必然性を理解しました。
2020年現在、ざっと思いつくだけでも以下のようなマネージドサービスが存在します。

認証: Firebase Auth, Auth0, Cognito
決済: Stripe
検索: Algolia
サーバーレスコンピューティング: Lambda, Cloud Functions
NoSQL: CloudFirestore, DynamoDB
ストレージ: S3
メール送信: SendGrid

ここで言えるのは、バックエンドで自前で実装しなければいけない機能が大幅に減ったということです。
「外部サービスをどれだけ有効に活用できるか」が重要になってくると、モノリスの魅力は相対的に薄れていきます。

そもそもRuby on Railsが登場した2004年にはAWSすら存在しておらず、モノリスがwebアプリ開発のど真ん中に鎮座していた2010年代前半にも、上記で紹介したサービスの多くは存在していませんでした。

いくらRuby on Railsも進化しているとはいえ、これだけ状況が変わってしまえば、開発のメインストリームから外れてしまうのは仕方のないことだと感じます。

また余談ですが、Rubyの認証ライブラリで1番人気があるDeviseを使っている人は、全員つらそうな顔をしています。

その他

  • コンポーネント単位で分割することで保守性や再利用性が高くなったり、デザイナーとの協業がしやすくなる
  • TypeScriptとVSCodeの連携がすごい

などなど他にもSPAのメリットは色々とありそうですが、これらはどちらかというと副産物に近く、SPAが台頭したメインの理由では無いと考えています。

モノリスは用済みになったのか

全くそんなことは無いと考えています。
以下の条件を満たすアプリケーション開発では、今でもモノリスがファーストチョイスです。
「モノリスでも問題ない」ではなく、「モノリスの方が圧倒的に良い」です。

  • webだけで良い
  • 関わる開発者が少ない
  • UXの要件がそれほど厳しくない

具体的な場面としては

  • 多くのwebアプリののプロトタイプ
  • リリース後の修正が少ないと予想されるシステム
    • 機能要件が指定された受託開発など
    • 「この機能を削ればこの予算で実現できます」など仕様をモノリスに寄せることで双方が得をする場面は必ず存在する
  • 小~中規模のwebアプリの一部
    • 必要な要件や、想定されるユーザー数などから技術選定。モノリスの方が効率良く開発できるサービスは、今後もそこそこの割合で残り続けるはず。

などが考えられます。

モノリスの最大の弱点とされる密結合は、必ずしも悪ではないのです。
分割は短期的な生産性を下げます。
密結合は短期的な生産性を上げます。
この点を考慮して技術選定をするべきです。

結論

特定の制約の上で、モノリスはもの凄い力を発揮します。

参考

Next.js,、Nuxt.jsが次世代のRailsになるという話についてどう思いますか? - Quora

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

【javascript 】新しいオブジェクトリテラル

簡易表記のプロパティ名

以下は、プロパティ名textに変数textを代入している。

const message={text:text};

プロパティ名と変数名が同じになる場合はこのように簡潔にできる。

const message={text};

これは分割との組み合わせでも機能する。

let {count}=stateManager.getState();
count+=amount
stateManager.update({count})

簡易表記のメソッド名

function manager() {
  const state = {};
  return {
    update: function(changes) {
      Object.assign(state, changes);
    },
    getState: function() {
      return Object.assign({}, state);
    }
  }
}

上のコードは以下のように簡易化できる。

function manager() {
  const state = {};
  return {
    update(changes) {
      Object.assign(state, changes);
    },
    getState() {
      return Object.assign({}, state);
    }
  }
}

ここでは
{update:function(){}}を{update(){}}に短縮している。

プロパティと同様にメソッドでも:以降が省略されている。

また、この簡易表記のメソッドは無名関数である。
つまり関数愛のメンバーを参照する時にその関数を名前で参照することは出来ない。

const ex = {
  at(n) {
    if (n <= 2) return 1;
    return at(n - 1) + at(n + 2);
  }
}

ex.at(7);// ReferenceError: at is not defined

つまりaとcが同等であり、bのような挙動は出来なということ

const a={
  meth(){
    //...
  }
}

const b={
  meth:function meth(){
    //...
  }
}

const c={
  meth:function(){
    //...
  }
}

これが問題になるのは関数が自己参照する場合。
自己参照が必要な場合はカニ表記メソッドを使用してはいけない。

まとめ

  • 簡易表記プロパティ名はキーと値が同じ名前である場合に省略できる。
  • 簡易表記メソッドは無名関数であり、再帰に使用すべきでない。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「JavaScript」信頼できるフロントエンドのコード保護への道

成功したWebシステムで信頼できるプラットフォームモジュールを構築するには、データのセキュリティを確保し、比較的信頼できるフロントエンド環境を構築する必要があります。

本ブログは英語版からの翻訳です。オリジナルはこちらからご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。

image.png

序章

情報セキュリティ分野における魅力的な目的として、信頼されたシステムとは、特定のセキュリティポリシーを実行することで、一定の信頼性を実現するシステムのことを指します。

コンピュータでは、すでにTrusted Platform Module(TPM)が使用されており、Trusted Computing Group(TCG)が定めるTPMの仕様に準拠しています。TPMは、信頼されたシステムを実現するために設計された安全なチップです。TPMは信頼されたシステムの根幹であり、信頼されたコンピューティングのコアモジュールであり、コンピュータのセキュリティをしっかりと保護します。

image.png

私たちのWebシステムでは、「クライアントの入力を絶対に信用しない」というのがセキュリティの基本的なガイドラインであるのに対し、信頼できるシステムを構築することは擬似的な命題のように思えます。実際には、システムの信頼性が絶対的なセキュリティを意味するわけではなく、ウィキペディアは次のように説明しています:ユーザーにとって、「信頼される」というのは、実際には「信頼できる」という意味ではありません。正確には、設計者やソフトウェア開発者が禁止している動作を行うのではなく、信頼されたシステムの動作が完全に設計に沿ったものであることを完全に確信できるということです。

このような観点から、あなたはそれを魅力的なビジョンとして見ることができますし、あなたは単に悪意のある行動を低確率に制限するために、WebシステムにTPMを構築し、比較的信頼できるWebシステムを実装したいと願っているだけです。

信頼できるフロントエンド

信頼されたシステムでは、TPMの重要な特徴の一つは、端末が信頼されていることを保証するために、メッセージの真正性を識別することです。Webシステムでは、メッセージの発信元はユーザである。搾取データベースや悪意のある登録、解約の拡散に伴い、ユーザーデータを保護するために、様々なシナリオで真正なユーザーのリクエストデータ識別が必須になってきています。

WebシステムでTPMを成功させるためには、まず入力データが安全であることを確認し、比較的信頼できるフロントエンド環境を構築する必要があります。しかし、フロントエンドがデータ収集の最前線として機能している間、ウェブシステムはネイティブにオープンであるため、JavaScriptのコードは常に公開されています。このような状況では、悪意のある偽造を防ぐことが難しくなり、信頼できるフロントエンドの実装は難しいものとなります。

1. なぜJavaScriptを難読化するのか?

JavaScript難読化の導入は、明らかにフロントエンドのコードロジックを保護するためです。

Webシステム開発の初期段階では、JavaScriptはWebシステムで大きな役割を果たすことはなく、単にフォームを送信するだけでした。JavaScriptファイルは非常にシンプルで、特別な保護は必要ありませんでした。

JavaScriptファイルの量が急増すると、JavaScriptの量を減らし、HTTP転送効率を向上させるために、uglify、compressor、clouserなどのJavaScript圧縮ツールがいくつかリリースされました。これらのツールは以下のことができます。

· Merge multiple JavaScript files;
· Remove spaces and line breaks from JavaScript code;
· Compress variable names in JavaScript files;
· Remove annotations.

image.png

[圧縮コード]
圧縮ツールの設計は、JavaScriptファイルのサイズを小さくするのを助けることですが、圧縮されたコードは、はるかに低い可読性を作成し、副作用としてコードを保護します。そのため、JavaScriptファイルの圧縮は、フロントエンドの公開における標準的なステップとなります。しかし、ChromeやFirefoxなどの市場で利用可能な主流ブラウザが、圧縮されたJavaScriptコードを素早く再配列し、強力なデバッグ機能を提供するためのJavaScriptフォーマットのサポートを開始すると、この方法では悪意のあるユーザーに対する強固な保護を提供することはできなくなります。

image.png

[Chrome開発者ツールでフォーマットされたコード]
ますます多くのウェブアプリケーションが登場し、ブラウザのパフォーマンスとネットワークアクセスの速度が向上する一方で、JavaScriptのワークロードシェアが高くなり、バックエンドのロジックの多くがフロントエンドに移行されています。一方で、より多くの犯罪者がそれを利用し始めています。ウェブモデルでは、通常、JavaScriptはそれらの犯罪者の突破口となる。フロントエンドのロジックを学ぶことで、犯罪者は一般的なユーザーとして悪意のある行動を行うことができます。その中で、重要なサービスやリスク管理システムのJavaScriptコードは、ログインページ、サインアップページ、決済ページ、トランザクションページなど、ハッキングに対する高いセキュリティが求められています。ここで、JavaScriptの難読化が明らかになります。

2. JavaScript難読化のロバスト性

JavaScript の難読化が十分に堅牢であるかどうかは、以前から話題になっていました。実際、コード難読化は、プログラマがコード難読化とシェル暗号化でほとんどのソフトウェアプログラムを再処理してコードを保護していたデスクトップソフトウェア時代に早くも登場しました。Javaと.NETはどちらも独自の難読化機能を持っています。ハッカーはこれをよく知っており、多くのウイルス対策プログラムは、ウイルス対策のために高度に難読化されています。しかし、JavaScriptは動的なスクリプト言語であり、HTTPでのソースコードの転送やリバースエンジニアリングは、コンパイルされたソフトウェアを解凍するよりもはるかに簡単であるため、多くのユーザーは難読化を冗長なものと見なしています。

image.png

[.NET難読化ツール、dotFuscator]
一方で、JavaScriptはソースコードを転送するため、難読化が必要であり、公開されたコードは常にリスクを伴います。一方で、精巧な難読化コードは悪意のあるユーザーには厳しく、開発者にとってはより多くの時間を節約することができます。クラッキングと比較して、難読化コードはより手頃な価格で、高強度のコード耐性でクラッカーの作業量を劇的に増加させ、防御的な役割を果たすことができます。このような観点から、鍵となるコードを難読化することは不可欠です。

3. JavaScriptの難読化方法

通常、JavaScriptの難読化装置は2つの分類に分けて存在します。
- 正規表現の置換によって実装された難読化ツール
- 構文ツリーの置換によって実装された難読化装置

前者のタイプの難読化装置は、低コストでありながら適度な機能を備えており、難読化を必要としないシナリオに適しています。後者のタイプの難読化装置は、コストは高いが柔軟性とセキュリティが高く、抵抗性の高いシナリオに最適です。次の図に後者のタイプの詳細を示します。構文ベースの難読化器はコンパイラによく似ており、どちらも基本的な原理は似ています。まず、コンパイラについて探っていきます。

用語と略語

トークン:トークンは、辞書的な単位(辞書的マーカーとして知られている)として、辞書解析器によって生成され、分割テキストストリームの最小単位となります。
AST:構文解析器は、抽象構文ツリーを生成し、ソースコードの抽象構文構造のツリー状の存在を持ちます。

image.png

[コンパイラ vs. 難読化ツール]
コンパイラのワークフロー
簡単に言えば、システムが文字列テキスト(ソースコード)を読み込むと、字句解析器はそれを小さな単位(トークン)に分割します。例えば、一桁の数字1はトークンであり、文字列 "abc "は別のトークンです。次に、構文解析器は、これらの小さな単位を、異なるトークンの構成関係を表すツリー(AST)に整理します。例えば、中央のトークンが加算を示す一方で、左右のノードがトークン-1とトークン-2である加算ツリーとして「1+2」を表示することができる。そして、コンパイラは、生成されたASTに基づいて中間コードを生成し、最終的にマシンコードに変換します。

難読化装置のワークフロー
コンパイラはソースコードを中間コードやマシンコードに翻訳する必要がありますが、難読化ツールの出力はJavaScriptのコードのままであり、構文解析に続くステップを必要としません。また、ここでの目的は、元の JavaScript コードの構造を変更することです。この構造は何に対応しているのでしょうか?それはASTに対応しており、正しく開発されたJavaScriptコードであれば、どのようなものでもASTを構築することができます。同様に、ASTは異なるトークンの論理関係を表しているので、JavaScriptコードを生成することもできます。このように、ASTを構築するだけで任意のJavaScriptコードを生成することができます。上図の右手は難読化処理の様子を示しています。

ASTを修正することで、新しいJavaScriptコードに対応したASTを作成することができます。

企画・設計
難読化のプロセスを学ぶと、設計と計画が最も重要な部分になります。前述したように、ASTを生成することで、元のコードとは異なるJavaScriptのコードが生成されます。しかし、難読化は元のコードの実行結果を崩してはいけません。したがって、難読化ルールは、コードの実行結果が変わらない間、コードが読みにくくなることを確実にしなければなりません。

開発者は、文字列や配列を分割したり、無駄なコードを追加するなど、特定の要件に応じて難読化ルールをカスタマイズすることができます。

image.png

実装
字句解析や構文解析は、コンパイルの原理を深く理解する必要があるため、このステップは多くのユーザーにとって困難なものになるかもしれません。それを理解するためのツールに頼ることができます。そのようなツールを使えば、ASTを修正するという最後のステップに直接進むことができます。

多くの市販のJavaScript字句解析・構文解析ツールは、v8、SpiderMonkey for Mozilla、esprimaなど、簡単にアクセスできます。以下では、nodejsベースのパーサーである推奨のuglifyについて説明します。このツールには以下のような機能があります。

· Parser - which can parse JavaScript code as the AST
· Code generator - which can generate code by using the AST
· Scope analyzer - which can analyze definitions of variables
· Tree walker - which can traverse tree nodes
· Tree transformer - which can change tree nodes

上で提供された難読化器の設計スケッチを確認すると、構文ツリーを変更しただけであることがわかります。


難読化ツールの設計方法を理解するために、以下の簡単な例では、「var a = 1」の「1」という数字を難読化ルールで16進数に変換する方法を詳しく説明しています。まず、ソースコードに対して字句解析と構文解析を行い、uglifyメソッドを使って簡単に構文ツリーを生成します。次に、シンタックスツリーから数字を探し出し、以下のように16進数に変換します。

image.png

例のコード

var UglifyJS = require("uglify-js");
var code = "var a = 1;";
var toplevel = UglifyJS.parse(code); //toplevel is actually the syntax tree.
var transformer = new UglifyJS.TreeTransformer(function (node) {
if (node instanceof UglifyJS.AST_Number) { //Locate the leaf node that needs to be modified.
        node.value = '0x' + Number(node.value).toString(16);
        return node; //A new leaf node is returned to replace the original leaf node.
    };
});
toplevel.transform(transformer);  //Traverse the AST.
var ncode = toplevel.print_to_string(); //Restore to strings from the AST.
console.log(ncode); // var a = 0x1;

上の簡単なコードを見ると、まず構文ツリーをparseメソッドを使って構築し、TreeTransformerを使ってツリーをなぞる必要があることが理解できると思います。UglifyJS.AST_Number型のノードをたどると(AST型の完全なリストはASTを参照)、このノードのトークンには "value "という属性があり、数値型の特定の値が格納されています。そして、値を16進数形式に変更し、"return node "を実行して元のノードを新しいノードに置き換えます。

結果
以下に難読化前と難読化後のコードを示します。
image.png

4. 難読化がパフォーマンスに与える影響

無駄なコードが追加されると、元のASTが変更され、難読化がパフォーマンスに影響を与えます。にもかかわらず、難読化ルールを使用して影響を最小限に抑えることができます。

· Reduce cyclic obfuscation as too many obfuscations can impact code execution efficiency
· Avoid too many concatenated strings as string concatenation imposes performance issues in earlier versions of IE
· Control code volume - Specifically, control the proportion of inserted waste code as an oversized file can exhaust network request and code execution performance.

難読化ルールを使用することで、パフォーマンスへの影響を合理的な程度に絞り込むことができます。実際、難読化ルールの中にはコードの実行を高速化するものさえあります。例えば、変数名と属性名の圧縮難読化はファイルサイズを小さくすることができ、グローバル変数の複製はアクションスコープのルックアップを最小限に抑えることができます。最近のブラウザでは、難読化はコードにほとんど影響を与えず、合理的な難読化ルールを導入した後は自信を持って難読化を使用することができます。

5. 難読化のセキュリティ

難読化の目的は、元の機能を損なうことなくコードを保護することです。

難読化後のASTは元のASTとは異なるが、難読化後のファイルと元のファイルの実行結果は同じでなければならないことを考えると、コードの実行を中断することなく十分な難読化強度を確保するにはどうすればよいのでしょうか?この問題を解決するためには、カバレッジの高いテストが必要です。

· Develop detailed unit tests for obfuscators
· Perform high-coverage functional tests on obfuscation target code to ensure that the execution results of the pre- and post-obfuscation code are consistent
· Perform multi-sample tests to obfuscate the class libraries complemented by unit tests, such as obfuscating jQuery and AngularJS. Then, perform those unit tests again using the obfuscated code and ensure that the execution results are the same before and after the obfuscation.

概要

どのような企業にとっても、信頼できるWebシステムを構築することはビジョンの一部であり、信頼できるフロントエンド環境を構築することは、信頼できるWebシステムの要件です。そのためには、コード耐性のための JavaScript 難読化が必要であり、難読化装置の実装は実行可能なプロセスです。また、難読化のパフォーマンスへの影響をコントロールできるため、信頼されたWebシステムを構築しやすくなるのでおすすめです。

参考文献

https://en.wikipedia.org/wiki/Trusted_Platform_Module
https://en.wikipedia.org/wiki/Trusted_system
http://lisperator.net/uglifyjs
http://esprima.org

アリババクラウドは日本に2つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
アリババクラウドジャパン公式ページ

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

BOT UIを使ってBOTを作成する

BOT UIとは

BOTを簡単に作れるJavaScriptフレームワーク。
2019年1月にやってみたことを書いていきます。

ドキュメント:docs.botui.org/index.html

https://examples.botui.org/

Git

HTML

<div class="botui-app-container" id="botui-app">
<bot-ui></bot-ui>
</div>

js

Gitから抜粋

var botui = new BotUI('botui-app') // id of container

botui.message.bot({ // show first message
  delay: 200,
  content: 'hello'
}).then(() => {
  return botui.message.bot({ // second one
    delay: 1000, // wait 1 sec.
    content: 'how are you?'
  })
}).then(() => {
  return botui.action.button({ // let user do something
    delay: 1000,
    action: [
      {
        text: 'Good',
        value: 'good'
      },
      {
        text: 'Really Good',
        value: 'really_good'
      }
    ]
  })
}).then(res => {
  return botui.message.bot({
    delay: 1000,
    content: `You are feeling ${res.text}!`
  })
})

ひっかかったところ

  • 最新のVue.jsだと動かなかった
    <script src="https://cdn.jsdelivr.net/vue/latest/vue.min.js"></script>

  • fontawesomeはv4のアイコンから選ぶ。

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

【JavaScript】変数を定義するとき var, let, constの違いは?

はじめに

JavaScriptを書く際に、変数定義する際にvarやletやconstを使うかと思います。
正直これの違いってよくわからないまま書いていたりしないでしょうか?

自分もなんとなくで使い分けてたりするわけですが、最低限エラーにはならないようにしたいものです。
そこでそれぞれの違いを少し丁寧に解説してみようと思います。

概要

まずはそれぞれの違いについて簡単に説明します。
一言で説明するとそれぞれ以下のような特徴があります。

var

再定義、再代入可能

let

再定義不可、再代入可能

const

再定義、再代入不可

もう少しわかりやすく

再定義とか再代入とか言われてもよくわからんと思うかもしれないです。

■ 定義
 定義とは変数の宣言のことで、varとかletとかconst〜と記述することです。
■ 代入
 代入とは一度宣言した変数に値を入れることです。

具体的にコードでみてみましょう。(JQueryを使って書きます)

jsファイル
$(function() {
  var varSample = "var1です"
  let letSample = "let1です"
  const constSample = "const1です"

  console.log(varSample) // var1です
  console.log(letSample) // let1です
  console.log(constSample) // const1です

  var varSample = "var2です" // 再定義可能
  let letSample = "let2です" // 再定義不可(エラーになります) 
  const constSample = "const2です" // 再定義不可(エラーになります)

  varSample = "var3です" // 再代入可能
  letSample = "let3です" // 再代入可能
  constSample = "const3です" // 再代入不可(エラーになります)
})

同じ変数名ではletは1回だけ使用可能、constは1回だけ使用可能で中身の変更は不可ということです。

補足

とりあえずvarを使っておけばエラーにはならないですが、letかconstのみで使い分けることが推奨されているようです。
確かに変数は再宣言する必要がないですし、上書きされたくない変数はconstを使って定数とすれば予期せぬエラーが起こりにくそうですね。
(以前書いた記事でvarを使ったことがあったので反省しておきます。)

変数にすべきか定数にすべきかの判断は、コードを書きながら「やっぱりこっち」みたいになるかと思いますが、その辺の感覚はだんだん鍛えていくしかなさそうです。

最後に

変数の違いが少し理解できたかと思うので、なるべくletとconstだけで使い分けて書けるようになりたいものです。ご意見あればコメントいただけると助かります。

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

ブックマークレット作成でハマったこと一覧

if文の指定方法 = ではなく、==で動く

動かない
let a=window.prompt('好きな野菜を選択してください\rきゅうりなら【1】 / トマトなら【2】','');
if(a = 1){a='きゅうり'}else{a='トマト'};
動く
let a=window.prompt('好きな野菜\rきゅうりなら【1】 / トマトなら【2】','');
if(a == 1){a='きゅうり'}else{a='トマト'};
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む