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

【rails】Ratyを使った星評価(評価の保存・表示・再評価)

こんばんは。プログラミング初学者です。 railsにてバイクのレビューを投稿するアプリを作成中です。 RatyというjQueryプラグインを用いて星評価を実装していたのですが、導入から表示までは参考記事がそこそこあったのですが、再評価というところが参考記事が少なかったため、本記事にまとめてみました。 間違いがございましたら、遠慮なくご指摘いただけますと幸いです。 前提条件 ・jQuery導入済み ・Raty導入済み ・星評価の対象となるカラム型はfloat型である。(星半分の評価も可能とするため) ※Raty導入につきましては下記の記事が参考になりました。 ・Railsで「Raty」を使った星機能をつける ・[Rails 6] Raty.jsを使った星型レビュー ・【Rails+jQuery Raty】レビュー用の星★の評価を実装する(入力、保存、表示) ・公式GitHub 評価の保存 ビューファイルの書き方 ※form_withを使用しています <%= form_with(model: @review, local: true) do |f| %> <div class="star-form-group" id="star1"> <%#id要素の付与がポイント%> <%= f.label :comfort,'乗り心地', class:'star-title' %> <%#保存したいカラムの指定%> </div> <%= end %> ratyを用いた星評価保存の関数定義 ※同じビューファイルにscriptタグとして埋め込み。私は評価項目が多いので、部分テンプレートにて切り出しました。 <script> $('#star1').raty({ size : 38, //星のサイズ starOff: '<%= asset_path('star-off.png') %>', //imagesフォルダより星画像の呼び出し starOn : '<%= asset_path('star-on.png') %>', starHalf: '<%= asset_path('star-half.png') %>', scoreName: 'review[comfort]', //reviewモデルのcomfortカラムに保存 half: true, //星半分を許可する。DBには0.5単位の数値が保存される }); </script> これで、保存するとDB上では小数を含む数値にて保存がなされました。 保存した評価の表示 ビューファイルの書き方 <div class="star-group"> <div class="detail-review">乗り心地</div> <div class="detail-value", id="star-comf-<%= @review.id %>"></div> <%# id要素の書き方がポイント %> <div class="eval-star"><%= @review.comfort %>/5点</div> <%# @モデル.カラムで保存された数値を表示 %> </div> ratyを用いた星評価の表示 <script> $('#star-comf-<%= @review.id %>').raty({ //.idでどの評価であるかを取得 size: 38, starOff: '<%= asset_path('star-off.png') %>', starOn : '<%= asset_path('star-on.png') %>', starHalf: '<%= asset_path('star-half.png') %>', half: true, readOnly: true, //readOnlyオプションで読み込み専用とする。編集できない。 score: <%= @review.comfort %>, //scoreオプションで評価内容を取得 }); </script> これでレビューの詳細ページなどに適用すれば、星評価が一目瞭然ですね。 再評価・編集 ビューファイルの書き方 基本的には評価の保存時の書き方と同じ。 <div class="star-edit-group"> <%= f.label :comfort,'乗り心地', class:'star-title' %> <div class="detail-value", id="edit-comf-<%= @review.id %>"></div> <%# .idでどの評価であるかを取得 %> </div> ratyを用いた再評価 <script> $('#edit-comf-<%= @review.id %>').raty({ size: 36, starOff: '<%= asset_path('star-off.png') %>', starOn : '<%= asset_path('star-on.png') %>', starHalf: '<%= asset_path('star-half.png') %>', half: true, score: <%= @review.comfort %>, //scoreオプションで最初の評価内容を取得 scoreName: 'review[comfort]', //scoreNameオプションで、新たに評価を保存 }); </script> これで最初に評価した内容が星で表示され、かつ再評価が可能で、新たに評価内容が保存できるようになりました。 まとめ 評価→表示→再評価といった一連の流れをまとめることができてよかったです。 今回は基礎的な部分のみの実装でしたが、改めて公式GitHubを見るとこれ以外にもたくさんオプションがありますので、応用が効きそうです。 余力があれば、評価の際にマウスオーバーした時に星の横にでも数値化された値が表示されるようになればより優れたUIになりそうなので、挑戦してみたいですね。 余談ですが、今回初めてGIFの埋め込みをしてみたのですが、縦横比があまりよろしくなく見にくくてすみません・・・。 ご覧いただきありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Denoの開発環境をVSCodeで作る【2021.4月版】

こんにちは。すずともです。 以前、Denoの開発環境をVSCodeで作るという記事を書きました。 あの時はDenoもv1.6でしたが、今日では、Deno v1.9.0と4ヵ月あまりで0.3もバージョンが上がりました! そろそろ2.0もリリースされそうなので、ますますホットになっていきそうですね! 同じように、Denoの拡張機能もバージョンアップされ、いつの間にかDeno(Canary)が無くなっていました笑 開発環境の作り方も少しだけ便利な方法が出てきましたので、まとめてみました。(実は前に記事書いた時から使えてたかも...) 流れ ※Denoはインストール済みとして話を進めていきます。 Deno拡張機能のインストール Deno拡張機能を有効化したいフォルダを開く 初期設定 launch.jsonの作成(任意) ①Deno拡張機能のインストール Deno - Visual Studio Marketplace 上記リンク またはVSCode内の拡張機能から公式denolandが出している「Deno」をインストールしましょう。 ②Deno拡張機能を有効化したいフォルダを開く 「ファイル」→「フォルダを開く」から目的のフォルダを開きましょう。 ③初期設定 ここが以前とは違う点です。 F1またはCtrl+Shift+pを押してコマンドパレットを開きます。そして、「deno」と入力してください。すると下記のように色々候補が出てくると思います。(筆者の環境は別の拡張機能も入ってるので全く同じにはならないかもしれません。) その中の、「Deno: Initialize Workspace Configuration」を選択してEnterを押してください。 すると以下の2つの質問を聞かれるので、YesかNoで答えてください。 僕のおすすめは、1つ目がYes、2つ目はNoです。(めんどくさい人であればEnter連打のYesYesでもいいですよw) Enable Deno linting? リンター(コンパイラよりも詳細にチェックを行ってくれるプログラム)を有効にするかという質問。例えば、Typescriptでanyを使った時に「推奨されてないから使わないようにね」と警告(エラーではない)してくれたりする。 Enable Deno unstable APIs? 直訳すると「不安定なAPIを有効にしますか?」という質問ですが、厳密には「不安定なAPIにもコード補完などを表示しますか?」という質問ですね。 Denoにはv1.0.0との互換性を保つためにunstable APIというAPIを実装しています。実装が変わる可能性があるため、実行時には--unstableフラグを付けて実行する必要があります。unstable APIはこのページにまとまっているので気になる人は覗いてみてください(o^―^o) 以上の質問に答えると、.vscodeフォルダが生成され、中にsettings.jsonが出来ていると思います。これで初期設定は終わりです。 Denoを実行してみよう! Denoのいいところは、サーバプログラムも完全コピペで動くところ。モジュールのインストールも必要ありません(o^―^o) ということで、以下の内容のmain.tsを作成してください。 main.ts import { serve } from "https://deno.land/std@0.93.0/http/server.ts"; const s = serve({ port: 8000 }); console.log("http://localhost:8000/"); for await (const req of s) { req.respond({ body: "Hello World\n" }); } 作成直後は以下の画像のようにimport文のところに赤波線が引かれているかもしれませんが、気にせず実行してみます。 main.tsを開いた状態でF5を押してください。すると以下のように何で実行しますか?と聞かれるので、Deno: Runを選択してEnterを押してください。 すると、デバッガが起動してファイルを実行してくれます。初めの実行時にはデバックコンソールに以下のように表示されてたくさんのDownloadが表示されていると思います。 これは、必要なモジュールをキャッシュするためにダウンロードしています。勝手にDenoが依存関係を解決してくれるので、コピペで動くんですねぇ。キャッシュしてあるので、2回目の起動からはPCがオフラインでも動きます。(逆に言うと初回はネット接続が必要です。) キャッシュが完了すると、プログラム内の赤波線も消えてると思います! Debugger session startedと表示されたら、ブラウザでlocalhost:8000にアクセスしてみましょう。「Hello world」と表示されたら成功です! (任意)④laungh.jsonの作成 laungh.jsonというファイルを作ることで、F5キーを押した後リストから選択しなくてもデバックできるようになります。F5ですぐに実行したい人はlaunch.jsonを追加しておくとよいでしょう。 .vscode内にlaunch.jsonを作成し、以下のJSONをコピペしてください。 launch.json { "version": "0.2.0", "configurations": [ { "name": "Deno", "type": "pwa-node", "request": "launch", "cwd": "${workspaceFolder}", "runtimeExecutable": "deno", "runtimeArgs": ["run", "--inspect-brk", "-A", "${file}"], "attachSimplePort": 9229 } ] } 最後に 最後までご覧くださりありがとうございます。結構手順が簡略化されて、jsonを手打ちしなくでもDenoの開発環境が整うようになりました。 Denoは会社も立ち上がり、ますます発展していくコンテンツなので、Denoをバンバン使っていきましょう! (なんか最近のアプデにより、httpサーバだとNodeJSよりも高速でうごくっぽい!?) すずともは、Denoを使ってBot作ったりいろいろやってみてるので、よかったら以下の記事も見てください!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TensorFlow.js学習メモ③ ロジスティック回帰(Logistic Regression)で車のエミッションの検査

はじめに ロジスティック回帰モデルを使って車の燃費を予測してみました。 学習メモなので基本用語の詳しい解説などは書いていません。 前の記事は以下 前提知識 実装前に必要となる知識をまとめました。 ロジスティック回帰(Logistic Regression) ロジスティック回帰は、ある事象が起こる確率を予測、分析したい時に用いられる手法です。 分類が曖昧なものを判別したいときに利用され、データが各クラスに所属する確率を計算することで分類を行います。 3種類以上の分類にも利用することができます。 シグモイド関数 年齢によってどんな趣味を好む人が多いかどうかを判定したいケースを考えます。 10, 20歳は「映画」、30, 40, 50歳は「読書」が好みだとして、それぞれの趣味を0と1に数値化すると、Training dataのプロットは線形になりません。 このようなケースにフィットするのがシグモイド関数です。 交差エントロピー(Cross Entropy) 交差エントロピーはロジスティック回帰モデルの性能を数値化する手法の一つです。 線形回帰モデルの性能評価で使う平均二乗誤差(MSE)と同じ損失関数と呼ばれます。 式で表すと以下のようになります。 Actualは実測値をエンコードした値、Guessはsigmoid(mx+b)を示しています。 また、交差エントリーの微分は以下のように表すことができます。 weightsはm, bのテンソルになります。 実装 ロジスティック回帰モデルのクラスをつくるにあたり、線形回帰モデルのクラスをリファクタリングしました。 メソッドの構成 線形回帰モデルのクラスとメソッドの構成はほぼ同じですが、MSEではなくCross Entropyを算出するため、処理の内容が若干異なります。 コード コードは以下です。 logistic-regression.js const tf = require('@tensorflow/tfjs'); const _ = require('lodash'); class LogisticRegression { constructor(features, labels, options) { this.features = this.processFeatures(features); this.labels = tf.tensor(labels); this.costHistory = []; this.options = Object.assign( { learningRate: 0.1, iterations: 100, decisionBoundary: 0.5 }, options ); this.weights = tf.zeros([this.features.shape[1], 1]); //m, bの初期値 } gradientDescent(features, labels) { const currentGuesses = features.matMul(this.weights).sigmoid(); const differences = currentGuesses.sub(labels); const slopes = features .transpose() .matMul(differences) .div(features.shape[0]); //列の個数で割る this.weights = this.weights.sub(slopes.mul(this.options.learningRate)); } train() { const batchQuantity = Math.floor( this.features.shape[0] / this.options.batchSize ); //バッチの回数 for (let i = 0; i < this.options.iterations; i++) { for (let j = 0; j < batchQuantity; j++) { const startIndex = j * this.options.batchSize; const { batchSize } = this.options; const featureSlice = this.features.slice( [startIndex, 0], [batchSize, -1] ); const labelSlice = this.labels.slice([startIndex, 0], [batchSize, -1]); this.gradientDescent(featureSlice, labelSlice); } this.recordCost(); this.updateLearningRate(); } } predict(observations) { return this.processFeatures(observations) .matMul(this.weights) .sigmoid() .greater(this.options.decisionBoundary) //指定値以上なら1 .cast('float32'); } //決定係数を出す test(testFeatures, testLabels) { const predictions = this.predict(testFeatures); //0.5以上は1にする testLabels = tf.tensor(testLabels); const incorrect = predictions.sub(testLabels).abs().sum().get(); //predictと一致しないカラムの数の合計 return (predictions.shape[0] - incorrect) / predictions.shape[0]; //予想と一致した割合 } processFeatures(features) { features = tf.tensor(features); features = tf.ones([features.shape[0], 1]).concat(features, 1); if (this.mean && this.variance) { features = features.sub(this.mean).div(this.variance.pow(0.5)); } else { features = this.standardize(features); } return features; } standardize(features) { const { mean, variance } = tf.moments(features, 0); this.mean = mean; this.variance = variance; return features.sub(mean).div(variance.pow(0.5)); } //Learning Rate調整のためにCostを記録する recordCost() { const guesses = this.features.matMul(this.weights).sigmoid(); const termOne = this.labels.transpose().matMul(guesses.log()); const termTwo = this.labels .mul(-1) .add(1) .transpose() .matMul(guesses.mul(-1).add(1).log()); const cost = termOne .add(termTwo) .div(this.features.shape[0]) .mul(-1) .get(0, 0); this.costHistory.unshift(cost); } //Learnin Rateの更新 updateLearningRate() { if (this.costHistory.length < 2) { return; } if (this.costHistory[0] > this.costHistory[1]) { this.options.learningRate /= 2; } else { this.options.learningRate *= 1.05; } } } module.exports = LogisticRegression; 予測値の算出 入力値と算出したweights(m, b)をシグモイド関数に当てはめた結果が、入力したしきい値より大きければ1、小さければ0とします。 ロジスティック回帰における予測値はこのように算出されます。 predict(observations) { return this.processFeatures(observations) .matMul(this.weights) .sigmoid() .greater(this.options.decisionBoundary) //指定値以上なら1 .cast('float32'); } おわりに 3記事書いたことで学習内容を整理することができました。 今後の学習の指針については、AIに詳しい人にきいて考えてみようと思います。 参考資料
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Angularコーディングスタイルガイド【個人的方法】

本記事は、Giancarlo Buomprisco氏による「An Opinionated Coding Styleguide for Angular」(2020年3月9日公開)の和訳を、著者の許可を得て掲載しているものです。 Angularコーディングスタイルガイド【個人的方法】 コードを書くための社内スタイルガイドは、開発チームがある時点、理想としてはプロジェクトの初期段階で定義し、合意すべき重要な決定事項です。 はじめに プロとしてコードを書いたことがあるなら、多くの、多くの開発者にとってスタイルがどれほど重要であるかをよく知っているはずです。私のキャリアの中でも、スタイルの議論に数え切れないほどの時間を費やしてきました。 しかし、なぜそれがそれほど重要なのでしょうか?プログラマはコードを書くことより読むことの方がはるかに多いのです。自分のためにも、特に仲間のためにも、このタスクをできるだけ単純化することが極めて重要です。 スタイルガイドは、最初のコードを書く前に定義するのが一般的ですが、だからと言ってプロジェクトのライフサイクル全体にわたり固定するべきではありません。実験と経験の積み重ねで得られる学びです。 また、毎日考えを変える必要があるという意味でもありません。プロジェクトの成長に合わせて、チームで評価し、議論し、決定する必要があるということです。 アルファ版の時代からAngularアプリを書いてきて、一緒に仕事をした人から強い影響を受け、たくさんの人のコードを読んだり、単に自分のプロジェクトで実験したりすることで、自分のスタイルを確立してきました。 この記事では、私がどのようにAngularアプリのスタイルを決定しているか、そしてその決定の論拠を紹介したいと思います。あなたやあなたのチームが採用したり、自分で作ったりするきっかけになればと思います。 また、このスタイルガイドを改善する提案も大歓迎です! 注意:このスタイルガイドは、技術的詳細やベストプラクティスに基づくものではなく、純粋にスタイルに関するものです。このスタイルガイドの目的は、単にコードの美しさや読みやすさを向上させることであり、パフォーマンスやデザインパターンなどを向上させるものではありません。 適切なスタイルガイドを適切なツールで補完する。 スタイルガイドを遵守するだけでなく、組織やオープンソースコミュニティの人にとって、コードの理解、保守、再利用が容易になるツールを使うことも重要です。私が愛用しているツールの1つはBit.devです。 Bit.devは、素晴らしいコンポーネントハブです。さまざまなAngularプロジェクトで再利用可能なコンポーネントをホストし、文書化し、管理する場所です。コンポーネントの分離と公開を担うオープンソースのツールBitと完璧に連携します。 例:Bit.devで共有Reactコンポーネントを検索する 1. HTML折り返しと順序 Angularテンプレートには、通常のHTMLにかなりの数の構文が追加されていて、読みにくいことがあります。 まず提案したいのは、折り返しについてです。私は通常、すべてのファイルで1列あたり80文字を超えないようにしています。単純に、横方向より縦方向の方がずっと読みやすいからです。 次は、規則なしで書いた要素です。 ごちゃごちゃしていますよね?私がコンサルティングで担当したプロジェクトでは、ほとんどが同じような書き方をしていました。 上のスニペットを、簡単な規則に基づいて書き直し、読みやすくしてみましょう。 HTMLタグの記述規則の定義 要素に2つ以上の属性がある場合、通常は1行に1つの属性しか書かない。 属性は特定の順序で書く。 属性が1つしかない場合を除き、終了タグは次の行に書く。 特定の順序を定義することをおすすめします。 構造的な指示 アニメーション 静的プロパティ 動的プロパティ イベント 先の例を、個人的にはどのように書くかを見てみましょう。 さらに言えば、構造ディレクティブはいつもng-containerでだけ使うようにしています。 主観的な判断で属性の順番を変えてもいいとは思いますが、私は何よりもまず構造ディレクティブを表示することに強いこだわりがあります。 構造ディレクティブは、(他にいろいろ知る必要がある前に)次のことを教えてくれます。 このフィールドは表示されているか?その理由は? このフィールドは繰り返されているか? 私の考えでは、これによりテンプレートの構造を読み、理解することが容易になります。 2. パイプ パイプはとても強力です。テンプレートの値を変換したり、コンポーネントの重複やロジックを回避したりできます。また、再利用や組み合わせも容易で、書くのも簡単です。 しかし、読みやすく、見分けやすいでしょうか?イエスでありノーです。 これはとても主観的で些細なことですが、それでも共有する価値があると思います。私はテンプレートにパイプが出てくると、いつも括弧で囲む傾向があります。括弧による分割の感覚は、値が変換されていることを示す手がかりになり、一般的に見ても分かりやすいです。 複数のパイプを使う場合は、さらに重要かもしれません。 3. ライフサイクルフック インターフェース ライフサイクルフックインターフェースの追加は、必須ではありませんが推奨されているので、これに従うことを強くおすすめします。 順序 私がライフサイクルフックを探す時、コンストラクタにすべてのフックがまとまっていて、他のクラスのメソッドと混同していないことを求めます。実行と同じ順序で定義されているのが理想です。 おすすめは次のことです。 インターフェースを必ず追加する。 コンストラクタの上に、パブリックプロパティとプライベートプロパティを追加する。 コンストラクタのすぐ下、コンポーネントのメソッドの上に、メソッドを追加する。 すべてのメソッドを近い場所に置く。 実行順に追加する。これを一貫させるのは少し難しいので、最重要事項ではないと思います。 ロジック 私は通常、ライフサイクルフックに直接ロジックを書くことは避けています。その代わりに提案するのは、プライベートメソッドにロジックをカプセル化し、ライフサイクルフックでそれを呼び出すことです。 4. コンポーネントのプロパティとメソッド Angularは、コンポーネントの機能を拡張するため、コンポーネントのメソッドやプロパティにデコレータを使います。 デコレータの数はとても多いので、特定の順序を定義するのは大変です。重要なのは、同じデコレータを持つプロパティとメソッド同士を近くに置くことです。 次は、悪い例です。 私は次のように書きます。同じデコレータを持つプロパティのグループの間に空行があることに注目してください。これで読みやすくなると思います。 強い意見ではありませんが、デコレートされていないプライベートコンポーネントとパブリックコンポーネントのプロパティは、デコレートされたプロパティとは別に置くようにしましょう。 経験上、それらを混在させると混乱を招き、ごちゃごちゃした印象を与えます。 5. 命名 命名は難しいですよね。 理解しやすく、明確で、検索しやすい名前を思い付くために、私はいつも2回以上考えます。 理解しやすい 一目見て何をするものか分かるか? 明確である 例えば、1つのコンポーネントに複数のクリックイベントがある場合、そのイベントがどれを参照するのか分かるか?つまりonClickという名前はつけるべきではない。 検索しやすい 命名規則はSEOに少し似ている。ユーザー(チームメイトや私)が命名対象をどのように検索するか、ユーザーがより検索しやすくなるにはどのように書けば良いか? ファイル名 私はすべてのファイル名にハイフンを使うのが好きです。今ではTypescriptプロジェクトでは標準だと思いますが、Angularプロジェクトでさえ、かなりのバリエーションを見てきたので、これに言及する必要があると思っています。 例 sign-up.component.ts profile-form.component.html ルートコンポーネント 私はルートコンポーネントの命名でpageというサフィックスを付けることが多いです。 例えば、認証ページは通常auth-page.component.tsと呼ばれます。これは、ルートコンポーネントであることを示し、通常router-outlet経由で他のコンポーネントをラップして表示するために使います。 component 次は、私が守っているコンポーネントの命名規則です。 (プレフィックスを除いて)3語以内にする。特に理由はないが、見た目があまり良くないため。もちろん、この規則を守るのが難しい場合もある。 他のコンポーネントで既に使われている単語や文脈を繰り返さない。そうしてしまうと、IDEの検索機能を使う時に遅くなったり、他のファイルを間違って開いたりして、結局時間の無駄になり、フラストレーションの原因になる。 同時に、一般的になりすぎないようにする。例えば、component settingsと言っても、何の設定か分からないので、さらに文脈を与えて手助けする(application-settings、profile-settings、organization-settingsなど)。 小規模アプリケーションでは大したことではないが、大規模では違いが出る。 イベント名 単純に見えますが、特に多くのイベントを持つ大規模コンポーネントでは違います。 次は、私が守っているイベントの命名規則です。 イベントや出力には、onというプレフィックスを付けない。代わりに、ハンドラーには付ける。 意味を考えさせないような名前にする。アクションそのものだけでなく、アクションが参照されるエンティティをいつも指定する。 valueがchanegeされたコンポーネントのイベントの場合、イベントのchangeはvalueChangeになる。 私の考えでは、これは明確で、何がchangeしたのか分かり、これがvalueなのか、statusなのか、それ以外なのか、疑問に思わない名前である。 過去形を使うか(valueChange対valueChanged)については議論の余地がある。反対意見の正当な理由を聞いたので、チームで議論する必要があるかもしれない。 どちらかの意見が正しいと思うなら、それでいいだろう。あなたの意見は? 6. ESインポート 特にIDEを使って入力時に自動追加する場合、ファイルのインポートを順序付けて整理するのは難しいです。ファイルが大きくなると、かなり乱雑になります。 次は、私のインポートの整理方法です。 最初に、Angularインポート Rxインポート サードパーティー(必須ではない) 最後に、ローカルインポートとプロジェクトインポート 各グループの上にコメントを残すのもおすすめです。 Opinionated Angularリポジトリ Opinionated AngularというGithubリポジトリを追加しました。読みやすく美しいAngularコードを書くため、ここに私の考えをもっと書き込みます。 よかったら参加してください! まとめ⭐ HTML要素を整理してラップする。1行に属性は1つだけ書き、属性はタイプ順に並べる。 パイプを使う値は括弧で囲む。 ライフサイクルフックはまとめて置き、実行順に並べる。 命名は、理解しやすく、明確で、検索しやすい名前にする。 ESインポートを整理して順序付ける。 あなたのチームが実践している規則の秘訣をぜひ教えてください。また、説明が必要な場合や、不明確な点や間違っている点がある場合は、ぜひコメントをください! この記事を楽しんでいただけたら嬉しいです。ソフトウェア開発、フロントエンド、RxJS、Typescriptなどに関する記事をお探しなら、Medium、Twitter、私のウェブサイトをフォローしてください! もっと見る AngularとBitによるコンポーネント共有Bit入門 Angularコンポーネントの構築と共有 blog.bitsrc.io 【2020年版】Angularデベロッパーツール トップ11 凄く気に入っている、ぜひ試すべきAngularライブラリとツール blog.bitsrc.io Angularで大きいリストをレンダリングする3つの方法 Angularで大きいリストをレンダリングするために使えるテクニック概要 blog.bitsrc.io 翻訳協力 この記事は以下の方々のご協力により公開する事ができました。改めて感謝致します。 Original Author: Giancarlo Buomprisco (gc@frontend.consulting) Original Article: An Opinionated Coding Styleguide for Angular Thank you for letting us share your knowledge! 選定担当: @gracen 翻訳担当: @gracen 監査担当: - 公開担当: @gracen ご意見・ご感想をお待ちしております 今回の記事はいかがでしたか? ・こういう記事が読みたい ・こういうところが良かった ・こうした方が良いのではないか などなど、率直なご意見を募集しております。 頂いたお声は、今後の記事の質向上に役立たせて頂きますので、お気軽に コメント欄にてご投稿ください。Twitterでもご意見を受け付けております。 皆様のメッセージをお待ちしております。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

復習 Javascript きほんの 「き」 初心者 配列 for文 を添えて

配列とfor文(ループ) <script>   var fruits = ['apple','grape','melon']; for (var i = 0; i < fruits.length; i++) {//.lengthをつけると、 //「配列の要素数」を取得できます。要素の数は3つだけど、配列のキーは0から始まる。 document.write(fruits[i]); } </script> ブラウザ表示↓ applegrapemelon 配列・for・.lengthを組み合わせて使うと、for文は要素の数だけループが行われるため、配列の要素が増減した際、for文に関しては何も手を加えなくても自動でループの数が調節される便利! おーfor文ってなんだっけ for文 繰り返し ループ <script> for (var i = 0; i < 11; i++) { document.write(i); } </script> ブラウザ表示 012345678910 こんな感じで出た。ぁー10まで順番に出るのね。 どんな感じで処理されていくか。 1・iに0を入れて 2・0<11を比較 3・ document.write(i) iの値の0を出力して 4・i++ i の値が1になる 1ずつ増やして繰り返していく。 最後は 11<11を比較して falseになるので、そこでストップ だから10までしか ブラウザにでない。 結果がfalseになったら終了なんだね
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

nvmを使用してNode.jsのバージョンを変更する方法

初めに 今回は、CentOSに導入したNode.jsのバージョンをnvmで変更します。 nvmとは、Node.jsのバージョンマネージャーです。 私の環境のNode.jsはバージョン12.21.0ですが、バージョン14.16.1に変更したいと思います。 # node -v v12.21.0 手順は、nvm公式のGitHubを参考にしました。 ↓ https://github.com/nvm-sh/nvm では、始めます。 環境 ■OS CentOS 8 nvmのインストール curlコマンドを使用してインストールスクリプトを実行します。 curlコマンドは、データの転送やダウンロードができるコマンドです。 # curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.2/install.sh | bash % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 13527 100 13527 0 0 40621 0 --:--:-- --:--:-- --:--:-- 40621 => Downloading nvm as script to '/root/.nvm' => Appending nvm source string to /root/.bashrc => Appending bash_completion source string to /root/.bashrc => You currently have modules installed globally with `npm`. These will no => longer be linked to the active version of Node when you install a new node => with `nvm`; and they may (depending on how you construct your `$PATH`) => override the binaries of modules installed with `nvm`: /usr/local/lib └── n@7.1.0 => If you wish to uninstall them at a later point (or re-install them under your => `nvm` Nodes), you can remove them from the system Node as follows: $ nvm use system $ npm uninstall -g a_module => Close and reopen your terminal to start using nvm or run the following to use it now: export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion インストールスクリプトを実行後、「~/.bashrc」に設定が入っていることを確認します。 # vi ~/.bashrc 〜略〜 export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion 設定が入っていることを確認しました。 sourceコマンドで、「~/.bashrc」を実行します。 # source ~/.bashrc Node.jsバージョンの切替 インストール可能なNode.jsを表示します。 # nvm list-remote ~略~ v14.16.1 (Latest LTS: Fermium) v15.0.0 v15.0.1 v15.1.0 v15.2.0 v15.2.1 v15.3.0 v15.4.0 v15.5.0 v15.5.1 v15.6.0 v15.7.0 v15.8.0 v15.9.0 v15.10.0 v15.11.0 v15.12.0 v15.13.0 v15.14.0 それでは、インストールを行います。 # nvm install 14.16.1 Downloading and installing node v14.16.1... Downloading https://nodejs.org/dist/v14.16.1/node-v14.16.1-linux-x64.tar.xz... ################################################################################################## 100.0% Computing checksum with sha256sum Checksums matched! Now using node v14.16.1 (npm v6.14.12) Creating default alias: default -> 14.16.1 (-> v14.16.1) インストールが成功し、デフォルトがバージョン14.16.1になっていることを確認できました。 最後にNode.jsのバージョンを確認します。 # node -v v14.16.1 以上になります。 ありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript勉強メモ4章

JavaScript勉強メモ4章 4章数値 浮動小数点数ってなに 小数点の位置が固定されていない(浮動)数のこと。 大きな数から小さな数まで表現できる。 リテラルってなに ルールのこと。例えば 文字リテラル:"この中身は文字として扱われます" 真偽値リテラル:true, false の二つは真偽値です 整数リテラル:42 とか1000 は整数です 浮動小数リテラル:3.14 とか2e8 は小数です 数値リテラル 整数リテラルと浮動小数リテラルのこと Numberオブジェクト 数値のラッパークラス。ラッパークラスは3章でやったけどよくわからなかった。プリミティブ型のデータである数値をオブジェクトとして使うとき、なにもせず使えるのはこのNumberオブジェクトがいるかららしい(感謝) Numberオブジェクトの静的プロパティ Numberオブジェクトに用意されてる設定みたいなもの。表示はもちろんconsole.logで console(Number.MAX_VALUE); >> 1.7976931348623157e+308/*扱える最大の数値*/ console.log(Number.MAX_VALUE*2);/*それを2倍したのを表示して*/ >>Infinity/*無限として扱います(無理)*/ 数値を指定の基数の形式で文字列に変換する (toStringメソッド) 日本語でok。以下訳 (10って2進数で表すといくつだっけ?答えは数値じゃなくて文字列で教えてね) ※基数とはこのときの2進数の"2"のこと let num = 10; /*numって箱に10っていう数値を入れるよ*/ console.log(num.toStoring(2))/*numを2進数で表して教えてね*/ >>1010 numって箱にいちいち入れないとだめなの? ←駄目じゃないけどtoString使いたかったら数値をかっこでくくってね console.log((10).toString(2)); >>1010 数値を指数形式で文字列に変換する(toExponentialメソッド) 指数形式でもできる...わかった気になっておきます。 浮動小数形式だから、勝手に小数点の前の桁数を1にしちゃうらしい。 let num = 34.5642; console.log(num.toExponential(2));/*小数点の前は1桁(固定)、小数点の後ろは()の中の"2"桁で教えてね*/ >>3.46e+1 数値を固定小数点数形式で(toFixed) 今度は固定小数点形式らしい。小数点の位置は対象の数値と同じまま。 console.log((34.5642).toFixed(3))/*小数点の位置はそのまま後ろは"3"桁まで教えてね*/ >>34.564 数値の有効桁数を指定して(toPrecisionメソッド) 有効桁数ってなに その数字を指定された桁数で表示するよ。有効数字3桁の例:3.14, 100, 5.68e+3, ...etc console.log((5678.9)toPrecision(4);/*5桁の数字を4桁で教えてね*/ >>5679 数値を指定したロケールに応じた形式で文字列に(toLocaleStringメソッド) ロケールってなに 日本だと数値を見やすくするために3桁ごとにカンマ(,)で区切るよね。そういうルールのことらしい()。 console.log((8242.56).toLocaleString()); >>8,242.56 文字列を整数に変換する(Number.parseIntメソッド) 今度は文字列を整数の数値にするよ。parseIntのIntは大文字のIを使って(アイ・エヌ・ティー)だよ。Lntにしないように。基数(何進法か)選べるよ。何も指定されなかったら空気読んで10進法にしとくよ。 console.log(Number.perseInt("9月"));/*文字列"9月"を整数で返してね。*/ >>9 console.log(Number.perseInt("month9")); >>NaN/*文字列の先頭に数値がないとばぐるよ*/ 文字列を浮動小数点数に変換する(Number.parseFloatメソッド) 文字列を浮動小数点数(小数点使って表すよ、小数点の位置は勝手に調整する)にするよ。 console.log(Number.perseFloat(42.25)); >>42.25 値がNaNかつ数値型であるか調べる(Number.isNaN) NaN(Not a Number):数値じゃないものを数値に変換したときの値。 注意しなきゃいけないのはNaN自体は数値として扱われるということ。(Not a Numberが数値とはこれいかに) タイトルのメソッドは引数がNaNかNaNじゃないかの判定を行うよ。 引数ってなに メソッドの対象になる数値や文字列のこと。 console.log(typeof(NaN));/*NaNは何型のデータか教えてね*/ >>number/*NaNは数値だよ(哲学)*/ console.log(Number.isNaN(NaN)); >>true console.log(Number.isNaN("apple")) >>false 値が有限でかつ数値型であるかどうか調べる(Number.isFinite) 引数が数値じゃなかったらfalse返すよ。数値でもNaNとか無限だったらfalse返すよ。まともな数値ならok。 console.log(Number.isFinite(100)); >>true console.log(Number.isFinite(Infinity)); >>false/*無限は帰れ*/ console.log(Number.isFinite("apple")); >>false/*文字列も帰れ*/ 仲良しなメソッドにisFiniteがいるよ。これは数値が文字列になってても数値だと判定するよ。 console.log(Number.isFinite("2")); >>false console.log(isFinite("2")); >>true 最後に値が整数かどうか調べるよ(Number.isInteger, Number.isSafeInteger) 二つともIntegerの頭文字は大文字のI(アイ)だから注意してね。名前の通り引数が整数ならtrue、それ以外ならfalse返すよ。 console.log(Number.isInteger(100)); >>true console.log(Number.isInteger("100")); >>false 二つ目のNumber.isSafeIntegerメソッドは値が整数であっても安全でなければfalse返すよ。安全な整数の定義は理解諦めました()。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript勉強メモ5章

JavaScript勉強メモ5章 5章数値 浮動小数点数ってなに 小数点の位置が固定されていない(浮動)数のこと。 大きな数から小さな数まで表現できる。 リテラルってなに ルールのこと。例えば 文字リテラル:"この中身は文字として扱われます" 真偽値リテラル:true, false の二つは真偽値です 整数リテラル:42 とか1000 は整数です 浮動小数リテラル:3.14 とか2e8 は小数です 数値リテラル 整数リテラルと浮動小数リテラルのこと Numberオブジェクト 数値のラッパークラス。ラッパークラスは3章でやったけどよくわからなかった。プリミティブ型のデータである数値をオブジェクトとして使うとき、なにもせず使えるのはこのNumberオブジェクトがいるかららしい(感謝) Numberオブジェクトの静的プロパティ Numberオブジェクトに用意されてる設定みたいなもの。表示はもちろんconsole.logで console(Number.MAX_VALUE); >> 1.7976931348623157e+308/*扱える最大の数値*/ console.log(Number.MAX_VALUE*2);/*それを2倍したのを表示して*/ >>Infinity/*無限として扱います(無理)*/ 数値を指定の基数の形式で文字列に変換する (toStringメソッド) 日本語でok。以下訳 (10って2進数で表すといくつだっけ?答えは数値じゃなくて文字列で教えてね) ※基数とはこのときの2進数の"2"のこと let num = 10; /*numって箱に10っていう数値を入れるよ*/ console.log(num.toStoring(2))/*numを2進数で表して教えてね*/ >>1010 numって箱にいちいち入れないとだめなの? ←駄目じゃないけどtoString使いたかったら数値をかっこでくくってね console.log((10).toString(2)); >>1010 数値を指数形式で文字列に変換する(toExponentialメソッド) 指数形式でもできる...わかった気になっておきます。 浮動小数形式だから、勝手に小数点の前の桁数を1にしちゃうらしい。 let num = 34.5642; console.log(num.toExponential(2));/*小数点の前は1桁(固定)、小数点の後ろは()の中の"2"桁で教えてね*/ >>3.46e+1 数値を固定小数点数形式で(toFixed) 今度は固定小数点形式らしい。小数点の位置は対象の数値と同じまま。 console.log((34.5642).toFixed(3))/*小数点の位置はそのまま後ろは"3"桁まで教えてね*/ >>34.564 数値の有効桁数を指定して(toPrecisionメソッド) 有効桁数ってなに その数字を指定された桁数で表示するよ。有効数字3桁の例:3.14, 100, 5.68e+3, ...etc console.log((5678.9)toPrecision(4);/*5桁の数字を4桁で教えてね*/ >>5679 数値を指定したロケールに応じた形式で文字列に(toLocaleStringメソッド) ロケールってなに 日本だと数値を見やすくするために3桁ごとにカンマ(,)で区切るよね。そういうルールのことらしい()。 console.log((8242.56).toLocaleString()); >>8,242.56 文字列を整数に変換する(Number.parseIntメソッド) 今度は文字列を整数の数値にするよ。parseIntのIntは大文字のIを使って(アイ・エヌ・ティー)だよ。Lntにしないように。基数(何進法か)選べるよ。何も指定されなかったら空気読んで10進法にしとくよ。 console.log(Number.perseInt("9月"));/*文字列"9月"を整数で返してね。*/ >>9 console.log(Number.perseInt("month9")); >>NaN/*文字列の先頭に数値がないとばぐるよ*/ 文字列を浮動小数点数に変換する(Number.parseFloatメソッド) 文字列を浮動小数点数(小数点使って表すよ、小数点の位置は勝手に調整する)にするよ。 console.log(Number.perseFloat(42.25)); >>42.25 値がNaNかつ数値型であるか調べる(Number.isNaN) NaN(Not a Number):数値じゃないものを数値に変換したときの値。 注意しなきゃいけないのはNaN自体は数値として扱われるということ。(Not a Numberが数値とはこれいかに) タイトルのメソッドは引数がNaNかNaNじゃないかの判定を行うよ。 引数ってなに メソッドの対象になる数値や文字列のこと。 console.log(typeof(NaN));/*NaNは何型のデータか教えてね*/ >>number/*NaNは数値だよ(哲学)*/ console.log(Number.isNaN(NaN)); >>true console.log(Number.isNaN("apple")) >>false 値が有限でかつ数値型であるかどうか調べる(Number.isFinite) 引数が数値じゃなかったらfalse返すよ。数値でもNaNとか無限だったらfalse返すよ。まともな数値ならok。 console.log(Number.isFinite(100)); >>true console.log(Number.isFinite(Infinity)); >>false/*無限は帰れ*/ console.log(Number.isFinite("apple")); >>false/*文字列も帰れ*/ 仲良しなメソッドにisFiniteがいるよ。これは数値が文字列になってても数値だと判定するよ。 console.log(Number.isFinite("2")); >>false console.log(isFinite("2")); >>true 最後に値が整数かどうか調べるよ(Number.isInteger, Number.isSafeInteger) 二つともIntegerの頭文字は大文字のI(アイ)だから注意してね。名前の通り引数が整数ならtrue、それ以外ならfalse返すよ。 console.log(Number.isInteger(100)); >>true console.log(Number.isInteger("100")); >>false 二つ目のNumber.isSafeIntegerメソッドは値が整数であっても安全でなければfalse返すよ。安全な整数の定義は理解諦めました()。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

駆け出しエンジニアがLeetCodeを30問解いてみた

概要 海外や外資系の面接では、多くがコーディングテストを取り入れているらしく、私自身コーディングテストを受ける身になったのでLeetCodeでEasyの問題を30問解いてみました。 LeetCodeとは コーディング面接で使われるプログラミングの学習サイト。あるお題に沿って、関数やクラスの中身をコーディングしていくというもの。好きな言語を使って学習をすることができますが、基本的なメソッドを理解していれば、あとは「アルゴリズムを自分でどのように組み立ていくか」が試される問題になっています。 著者のプログラミング歴 スキルセット JavaScript / PHP / Vue / React / Laravel / MySQL 学習歴 2020年6月 学習開始。 2020年11月 転職 実務経験は3ヶ月ほど。 LeetCodeを30問解いて変わったこと アルゴリズムを仮説を持って組み立てることができるようになってきた 最初の5〜10問ほどはアルゴリズムを自分で組み立てることができず、四苦八苦していたのですが、基本的な問題であれば仮説をもってコードを書くことで次のデバッグに生かすことができたり、1発で正解する問題もちらほら出てくるようになりました。 意外と理解していないメソッドがたくさんあることに気づいた JavaScriptでコーディング学習をしていたのですが、JavaScriptの代表的なメソッド(自分が勝手にそう思っている)mapやreduce、forEachなどはもちろんのこと、toStringやsplit、joinやspliceなどわかっていそうで深くは理解できていないメソッドがたくさんあることに気づきました。 ただLeetCodeで学習する分には、そんなにたくさんメソッドを理解しておく必要はありません。 コーディングテストそのものに抵抗がなくなって来た。 お話をいただいたときは、まだ駆け出しだし自分に解くことができるのか?と半信半疑でしたが、ある程度の数をこなしたことでコーディングそのものに対して自信を持って解くことができるようになったと感じます。 まとめ ITエンジニアとして、今後グローバルな企業に挑戦したい方、アルゴリズム力を鍛えたい方にはおすすめの学習サイトです。私自身アルゴリズムを考えるにあたって仮説を立てる力が身についてきたと感じます。今後もエンジニアとしてスキルアップができるように学習を続けていきたいと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【個人開発】文系実務未経験の大学生が、Nuxt.js+Vuetify+auth0+firestore+Netlifyでサーバレスにカロリー&栄養素計算アプリを作った話。

概要 筋トレにハマっている人ならではの悩み、それは… 「普通のカロリー計算アプリ、栄養計算の比重がカスタムできねえ!!」 「?」と思った筋トレ非マニアに向けて話すと、 例えばAさん(筋トレ趣味勢)は体重あたり1gのタンパク質が必要ですが、 ボディビルダーのBさんは体重あたり3gのタンパク質が必要です。 しかし、世に流布しているカロリー計算アプリは、その比率がカスタムできず、 世の中には、恋情に近い苦しい気持ちを抱え、日々にしびれを切らしているトレーニーが一定数います。 たぶん。 そこで表題の通り、個人の体にフォーカスしたカロリー計算アプリを作りました。 こちら!!:https://calomane.netlify.app/ 一応自己紹介 現在大学4年生、まさかの日本文学専攻(ハルキスト) 2月に就職活動が終わり、来年から金融ユーザー系SIerのSE(震え) 現在はCLOTOで開発・Twitter運用をちらほら プログラミング歴1年。JavaScriptがメインです 筋トレ歴3年。好きなBIG3はデッドリフト https://www.cloto.jp/ 使用技術 Nuxt.js サーバレスの開発ということで、実装が楽なSPAを使おうと思いました。 ページ遷移がサクサクなのが良い。。。 Vuetify レスポンシブをよしなにやってくれるし、デザインがすごい楽。 ドキュメントが最近日本語対応して読みやすい Auth0 認証を省コードで実装できる、認証用にUIを組み立てなくていい SNSログインを楽に追加できるetc… firestore 単純にDBとして使いたかった。情報ソースが多いので初心者に優しい Netlify githubと連携した自動デプロイができる静的サイトホスティングサービス。大変良い。 TOPページ よくあるLPみたいな感じで作りました アニメーションはCSSのtransitionプロパティと、 ScrollTriggerを使いました。 今考えたら、普通に座標取って別のデータに入れて、 その地点に来たらクラス付与とかでも良かったのかもしれない… 認証 auth0を使った認証機能です。 ここで取得したユーザー情報はfirebaseと連動して、 後ほどデータの取得等に使われます。 ユーザー用トップページ 表題の通りです。 メイン機能 カロリーや栄養素計算(PFC)の計算画面です。 基礎項目の記入、目的や頻度の選択、カスタム値に数字を入れることで、 それぞれの栄養素の量やカロリーのリザルトが出てきます。 「保存する」を押すと、firebaseにデータが保存できます。 また、データはfirestoreのdocs名に工夫をして、ユーザーごとに管理できます。(詳細は秘密) サブ機能 直近の履歴が残るようになってます。 クリックするとその時入れたデータの確認ができます。 (本当は日にちごとにログを残せればと思っていましたが、勉強不足で直近のみでアウトプットしました、、、DB構成とバックエンドの知識がない、、、) ハマった箇所 前述したとおり、勉強不足でDBの構成に手間がかかった。。。 auth0のドキュメントがわかりづらく、api取ってくるのに苦労した なんだかんだデザイン。キリがない 感想 NoSQLよりSQLのほうが好きならAWSを使えばよかった 早くNuxtとわかり合いたい auth0はドキュメント読みこなせれば、firebaseの認証より便利 これで理想のボディになれる(やかましい) 次やるべき備忘録 追加機能 日にちごとにログ表示 グラフで体脂肪などの推移を表示 食事メニュー管理 お金にするなら 広告をつけるなら方法を調べる SEO対策やanalytics導入 速度表示の改善 まとめ ここまで読んでいただきありがとうございました! つよつよエンジニアの皆さん、このアプリについて何かございましたら、 Topページのお問い合わせからお願いします?‍♂️ お世話になったリンク集 https://ja.nuxtjs.org/ https://auth0.com/ https://vuetifyjs.com/ https://firebase.google.com/docs/firestore?hl=ja https://developer.mozilla.org/ja/docs/Web/CSS/animation https://note.com/aliz/n/nacc97fe7d019 https://tech.smartcamp.co.jp/entry/2019/05/31/185456 https://www.to-r.net/media/scrolltrigger/ etc... ありがとうございました?‍♂️?‍♂️?‍♂️
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript勉強メモ3章

JavaScript勉強メモ3章 まずはじめに ブラウザ上でJavaScriptを実行できるサービスがあります。 ブラウザでプログラミング・実行ができる「オンライン実行環境」| paiza.IOを 試してみてください。 たくさん出てくるconsole.log()は()の中身を表示してねってお願いです。このお願いをしないとpcは結果を教えてくれません。 3. JavaScriptの基本事項 単語の間で空白・改行は自由 セミコロン(;)は文末に入れると良い 予約語(もともと意味や機能が定義されている単語)は変数名・関数名に使用不可 データの種類 プリミティブ型: /*単一データ*/ apple /*文字列*/ 3 /*数値*/ オブジェクト型: /*複数のプリミティブ型またはオブジェクト型を持つデータ*/ [apple, orange] /*配列*/ {width: 100, height: 80} /*オブジェクト*/ シンボル ユニークなプリミティブ型のデータらしい、オブジェクトのキーなどに使えそう?(勉強中) ラッパーオブジェクト いまいちわからない、プリミティブ型もオブジェクト用のメソッドが自動で使える仕組みはあるらしい。 strictモード 望ましくない記述をエラーにできる機能。ファイル全体に有効にするとき、ファイルの先頭に次を記述する。 'use strict'; 特定の関数にのみ適用するとき、以下のように記述する。 function myFunc(){ 'use strict'; ・・・ ・・・ } 型変換のルール 文字変換 num = 2 console.log(typeof num) >>>number console.log(typeof String(num)) console.log(typeof num.toString()) >>>string 数値への変換 str = "2" console.log(str) >>>string console.log(Number(str)) >>>number 理論値(true, false)への変換 if(0){ console.log("0という値は真だよ");/*if文が真(true)ならこっち*/ }else{ console.log("0という値は偽だよ");/*偽(false)ならこっち*/ } >>>"0という値は偽だよ"/*0は偽(false)だった*/ if文を使わなくてもboolean()を使えば真偽が確かめられます。 console.log(Boolean(0)); >>>false console.log(Boolean("false"));/* 文字列は""(空白)以外true*/ >>>true console.log(Boolean(false));/*値としてのfalseはfalse*/ >>>false
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript勉強メモ4章

JavaScript勉強メモ4章 まずはじめに ブラウザ上でJavaScriptを実行できるサービスがあります。 ブラウザでプログラミング・実行ができる「オンライン実行環境」| paiza.IOを 試してみてください。 たくさん出てくるconsole.log()は()の中身を表示してねってお願いです。このお願いをしないとpcは結果を教えてくれません。 4. JavaScriptの基本事項 単語の間で空白・改行は自由 セミコロン(;)は文末に入れると良い 予約語(もともと意味や機能が定義されている単語)は変数名・関数名に使用不可 データの種類 プリミティブ型: /*単一データ*/ apple /*文字列*/ 3 /*数値*/ オブジェクト型: /*複数のプリミティブ型またはオブジェクト型を持つデータ*/ [apple, orange] /*配列*/ {width: 100, height: 80} /*オブジェクト*/ シンボル ユニークなプリミティブ型のデータらしい、オブジェクトのキーなどに使えそう?(勉強中) ラッパーオブジェクト いまいちわからない、プリミティブ型もオブジェクト用のメソッドが自動で使える仕組みはあるらしい。 strictモード 望ましくない記述をエラーにできる機能。ファイル全体に有効にするとき、ファイルの先頭に次を記述する。 'use strict'; 特定の関数にのみ適用するとき、以下のように記述する。 function myFunc(){ 'use strict'; ・・・ ・・・ } 型変換のルール 文字変換 num = 2 console.log(typeof num) >>>number console.log(typeof String(num)) console.log(typeof num.toString()) >>>string 数値への変換 str = "2" console.log(str) >>>string console.log(Number(str)) >>>number 理論値(true, false)への変換 if(0){ console.log("0という値は真だよ");/*if文が真(true)ならこっち*/ }else{ console.log("0という値は偽だよ");/*偽(false)ならこっち*/ } >>>"0という値は偽だよ"/*0は偽(false)だった*/ if文を使わなくてもboolean()を使えば真偽が確かめられます。 console.log(Boolean(0)); >>>false console.log(Boolean("false"));/* 文字列は""(空白)以外true*/ >>>true console.log(Boolean(false));/*値としてのfalseはfalse*/ >>>false
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Codewars】Math.min()を使って配列の最小要素を取得する

はじめに Incrementsのnara_sakiです。Qiita広告の企画営業をしています。文系ビジネス職ですが「実際に自分でも手を動かしてみたい!」という思いから、JavaScriptを勉強しています。 現在は下記の方法で学習しています。 インプット:ドットインストール アウトプット:Codewars 本記事では、Codewarsの問題と解答を書いていきます。なお、言語はJavaScriptを使っています。 問題 Find the smallest integer in the array 問題(原文) Given an array of integers your solution should find the smallest integer. For example: Given [34, 15, 88, 2] your solution will return 2 Given [34, -345, -1, 100] your solution will return -345 You can assume, for the purpose of this kata, that the supplied array will not be empty. 問題(日本語訳) 整数の配列の中で、一番小さい要素を取得してください。 例:[34, 15, 88, 2] →答えは2 例:[34, -345, -1, 100] →答えは-345 ※今回配列は空になることはありません。 解答の大枠(Codewarsではあらかじめ下記の大枠が用意されています。必要箇所を埋めていきます) class SmallestIntegerFinder { findSmallestInt(args) { } } 解き方 複数の解き方がありますが、私は下記の手順で考えました。 Math.min()関数で要素の最小値を取得する スプレッド構文を使い、配列(args)を展開する 1. Math.min()関数で要素の最小値を取得する 要素の最小値を取得するには、Math.min()関数を使うと良さそうです。MDNのMath.min()を参照しました。 最初は下記のコードを書いたのですが、エラーになってしまいました(NaN(整数に変換できない)が返ってきてしまいました)。 エラー class SmallestIntegerFinder { findSmallestInt(args) { return Math.min(args); // エラーになってしまう } } 2. スプレッド構文を使い、配列(args)を展開する 1では、配列が展開されていなかったため、エラーになってしまいました。そこでスプレッド構文を使い、配列(args)を展開([a, b, c, ...]の形にする)していきます。 配列を展開するには、スプレッド構文(...)を使います。具体的には、return Math.min(args)をreturn Math.min(...args)に変えます。 解答 1,2をまとめると、下記のコードで正解になります。 正解 class SmallestIntegerFinder { findSmallestInt(args) { return Math.min(...args); // スプレッド構文を使い配列を展開する } } おわりに スプレッド構文はドットインストールでも学習していたはずですが、こちらの問題を解いたことで自分の理解度が足りなかったことに気付かされました。初見には...の使い方が難しかったですね。あらためてインプット→アウトプットの重要性を実感します。引き続き学習を進めていきたいと思います。 Codewarsとは 無料で利用できるプログラミング学習サイトです。@javacommonsさんの記事に詳しい説明が載っています。 Codewarsは英語なので最初は少しとっつきにくいかもしれませんが、問題数が豊富であり、他の人の解答も見られてとても勉強になります。初学者の方におすすめです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails6】共感(いいね)機能を、JavaScriptでAPIにfetchでリクエストを送って非同期通信(Ajax)で実装してみた

はじめに なぜこの記事を書くことにしたのか? jQueryを使用したAjaxの実装方法はあったのですが、素のJavaScriptでAPIにfetchでリクエストを送る実装方法が、ネット上であまり見受けられなかったからです。 私のようにJavaScriptで1から実装したいと考えている方の参考になればと思います。 私のポートフォリオでは、いいね機能のことを共感機能と名付けているので、これより下の説明では共感機能と呼びます。 共感機能の仕様を考える 画面のレイアウト・動作 投稿にある共感ボタンを押すと、色とテキストが変化します。 使用する言語・フレームワーク Ruby 3.0.0 Rails 6.1.3 MySQL 8.2.3 tailwindcss 1.9.0 データベースのテーブル設計 今回関係あるのは、usersテーブル、empathiesテーブル、postsテーブルです。 usersテーブルはユーザーを表しています。 postsテーブルは投稿を表しています。 empathiesは中間テーブルです。 テーブル名 カラム名 カラム名 カラム名 カラム名 users id nickname email password posts id text user_id password empathies id user_id post_id 共感機能の流れ 流れ 共感機能は、共感ボタンを押すことで、JavaScriptが作動し、共感ボタンの色とテキストを変化させます。そして、JavaScriptはサーバーと非同期通信を行い、データをRails側に渡しに行きます。Railsでは、データベースに必要な情報を保存・削除します。 以上が、大まかな共感機能の流れとなります。 では、非同期通信とは何なのか説明する前に、同期通信について説明します。 同期通信とは クライアントとサーバーが交互に処理を行い、同調して通信を行うことを同期通信と呼びます。同期通信の場合、サーバーが処理を待っている間、クライアントは待つことしかできず、HTMLファイルを受け取ってから表示の処理を行うため、全体としてページの更新に時間がかかってしまいます。また、送信するデータも多くなりがちで、サーバーに負担がかかってしまいます。 非同期通信とは 非同期通信はAjaxをも呼ばれています。Ajaxは同期通信の欠点を補うために誕生しました。AjaxではWebブラウザ上で、クライアントサイド・スクリプトとして動くJavaScriptが直接Webサーバーと通信を行い、取得したデータを用いて、表示するHTMLを更新します。HTMLそのものをやり取りするのではなく、更新に必要なデータのみをやりとりするため、送信するデータ量は同期通信と比べて少ないため、サーバーへの負担が抑えられます。 ルーティングの設定 共感ボタンをクリックした時に、JavaScriptにjson形式で値を渡せるように定義します。 config/routes.rb Rails.application.routes.draw do root to: "posts#index" devise_for :users resources :posts namespace :api, format: :json do namespace :v1 do resources :empathies, only: [:create, :destroy] end end end モデルの設定 app/models/user.rb class User < ApplicationRecord devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable has_many :posts, dependent: :destroy has_many :empathies, dependent: :destroy end app/models/post.rb class Post < ApplicationRecord belongs_to :user has_many :empathies, dependent: :destroy # postのuserに関するempathyレコードを取得する def empathy_by(user) empathies.where(empathies: { user_id: user }).last end # userが共感しているかチェックしている def empathy_by?(user) empathy_by(user).present? end end app/models/empathy.rb class Empathy < ApplicationRecord belongs_to :user belongs_to :post EMPATHY_COLOR = "inline-block border border-red-500 py-1 px-2 rounded-lg text-white bg-red-500".freeze UNEMPATHY_COLOR = "inline-block border border-red-500 py-1 px-2 rounded-lg text-red-500 bg-white".freeze end 「EMPATHY_COLOR」と「UNEMPATHY_COLOR」ですが、tailwindcssを使っているので、class名でcssを設定しています。これは、後ほどビューで使用するので、あらかじめモデル内で定義しています。 ビューの設定 共感ボタンを様々なページで使いまわしたので、パーシャルテンプレートを使います。 app/views/empathies/_empathies.html.erb <% if user_signed_in? %> <% empathy_button_color = post.empathy_by?(current_user) ? Empathy::EMPATHY_COLOR : Empathy::UNEMPATHY_COLOR %> <% if post.empathy_by?(current_user) %> <button class="js-empathy-button <%= empathy_button_color %>" id="<%= post.id %>" value="<%= post.empathy_by(current_user).id %>">共感済み</button> <% else %> <button class="js-empathy-button <%= empathy_button_color %>" id="<%= post.id %>" value=" ">共感する</button> <% end %> <% end %> empathy_button_color = post.empathy_by?(current_user) ? Empathy::EMPATHY_COLOR : Empathy::UNEMPATHY_COLOR この部分ですが、「AAA ? BBB : CCC」を使用しています。 これは、AAAという条件に該当する場合はBBBを実行、該当しない場合はCCCを実行するという意味です。 つまり、postにすでに共感していればEMPATHY_COLORを代入して、まだ共感していなければUNEMPATHY_COLORを代入するという意味です。 jsファイルの設定 app/javascript/js/empathies.js document.addEventListener('turbolinks:load', () => { const empathyColor = "js-empathy-button inline-block border border-red-500 py-1 px-2 rounded-lg text-white bg-red-500"; const unempathyColor = "js-empathy-button inline-block border border-red-500 py-1 px-2 rounded-lg text-red-500 bg-white"; const empathyEndpoint = '/api/v1/empathies'; const getCsrfToken = () => { const metas = document.getElementsByTagName('meta'); for (let meta of metas) { if (meta.getAttribute('name') === 'csrf-token') { return meta.getAttribute('content'); } } return ''; } const sendRequest = async (endpoint, method, json) => { const response = await fetch(endpoint, { headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-CSRF-Token': getCsrfToken() }, method: method, credentials: 'same-origin', body: JSON.stringify(json) }); if (!response.ok) { throw Error(response.statusText); } else { return response.json(); } } const empathyButtons = document.getElementsByClassName('js-empathy-button'); // postの一覧ページで複数要素がある時に対応できるようにfor文を使っている for (let i = 0; i < empathyButtons.length; i++) { // 共感ボタンをクリックしたときの処理 empathyButtons[i].addEventListener('click', event => { const button = event.target; const createEmpathy = (postId, button) => { sendRequest(empathyEndpoint, 'POST', { post_id: postId }) .then((data) => { button.value = data.empathy_id console.log(button.value); }); } const deleteEmpathy = (empathyId, button) => { const deleteEmpathyEndpoint = empathyEndpoint + '/' + `${empathyId}`; sendRequest(deleteEmpathyEndpoint, 'DELETE', { id: empathyId }) .then(() => { button.value = ''; console.log(button.value); }); } if (!!button) { const currentColor = button.className; const postId = button.id; const empathyId = button.value; // 共感する場合 if (currentColor === unempathyColor) { button.className = empathyColor; button.innerText = '共感済み'; createEmpathy(postId, button); } // 共感済みの場合 else { button.className = unempathyColor; button.innerText = '共感する'; deleteEmpathy(empathyId, button); } } }); } }); 記述がとても長いので、分割して説明します。 const empathyColor = "js-empathy-button inline-block border border-red-500 py-1 px-2 rounded-lg text-white bg-red-500"; const unempathyColor = "js-empathy-button inline-block border border-red-500 py-1 px-2 rounded-lg text-red-500 bg-white"; const empathyEndpoint = '/api/v1/empathies'; const getCsrfToken = () => { const metas = document.getElementsByTagName('meta'); for (let meta of metas) { if (meta.getAttribute('name') === 'csrf-token') { return meta.getAttribute('content'); } } return ''; } empathyColorとunempathyColorで、共感ボタンのスタイルを定義しています。tailwindを使っているので、後でclass名を上書きするのに使います。 empathyEndpointはRails側でどのコントローラーを使うのかを設定しています。こちらは、後でfetchメソッドというのが登場してくるのですが、その時にどのurlにデータを送信するのかを設定する時に使います。なので、あらかじめ設定しておきます。 getCsrfTokenですが、こちらを設定しておかないとエラーになってしまいます。なぜかというと、Railsの仕様で、app/views/layouts/application.html.erbに最初から書かれている<%= csrf_meta_tags %> などによって GET以外のあらゆる非同期通信Requestでは正しいX-CSRF-TokenをRequest Headerに含めないと サーバー側はRequestを弾くようにしているためです。クロスサイトリクエストフォージェリ(CSRF)というサイバー攻撃対策用のTokenを用いた仕組みです。以下に参考にしたサイトを張っていおきます。 const sendRequest = async (endpoint, method, json) => { const response = await fetch(endpoint, { headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-CSRF-Token': getCsrfToken() }, method: method, credentials: 'same-origin', body: JSON.stringify(json) }); if (!response.ok) { throw Error(response.statusText); } else { return response.json(); } } こちらでは、どのurl(endpoint)に、どのHTTPメソッド(method)で、どんなデータ(json)を送信するのかというの設定しています。 このコードの理解を深めるためには、fetch,async,awaitの使い方をしる必要があります。以下に参考になるサイトを貼るっていおきます。 // 画面上のボタン要素を全て取得する const empathyButtons = document.getElementsByClassName('js-empathy-button'); // postの一覧ページで複数要素がある時に対応できるようにfor文を使っている for (let i = 0; i < empathyButtons.length; i++) { // 共感ボタンをクリックしたときの処理 empathyButtons[i].addEventListener('click', event => { const button = event.target; const createEmpathy = (postId, button) => { sendRequest(empathyEndpoint, 'POST', { post_id: postId }) .then((data) => { button.value = data.empathy_id }); } const deleteEmpathy = (empathyId, button) => { const deleteEmpathyEndpoint = empathyEndpoint + '/' + `${empathyId}`; sendRequest(deleteEmpathyEndpoint, 'DELETE', { id: empathyId }) .then(() => { button.value = ''; }); } if (!!button) { const currentColor = button.className; const postId = button.id; const empathyId = button.value; // 共感する場合 if (currentColor === unempathyColor) { button.className = empathyColor; button.innerText = '共感済み'; createEmpathy(postId, button); } // 共感済みの場合 else { button.className = unempathyColor; button.innerText = '共感する'; deleteEmpathy(empathyId, button); } } }); } まず、画面上のボタンの要素をempathyButtonsに代入します。 画面上に投稿が一つだけの場合は、いきなりaddEventListenerのclickを使用しても問題ないのですが、画面上に複数投稿ある場合は、一旦全ての要素を取得してからでないと、うまく作動してくれません。なので、for文を使用しています。 そして、for文で一つ一つの要素にクリックした時にイベントが発火するようにしていきます。 「const button = event.target;」でクリックしたボタンをbuttonに代入しています。 createEmpathyでは、JavaScriptからRailsのempathiesコントローラーのcreateアクションにリクエストを送信し、送信されたデータを元に新たにempathyレコードが作成され、その作成されたレコードのidを受け取り、buttonタグのbalueに挿入します。 deleteEmpathyでは、JavaScriptからRailsのempathiesコントローラーのdestroyアクションにリクエストを送信し、されたデータを元にemapathyレコードを削除し、無事に削除が完了したら、buttonタグのvalueを空にします。 「!!button」は二重否定を使うことで、trueを返すようにしています。 それより以下は、ボタンのスタイルの上書きを行い、テキストの上書きを行い、それぞれの関数を実行するという感じです。 作成したjsファイルの読み込み方 以下のように記述してあげることで読み込みが完了します。 app/javascript/packs/application.js import "../js/empathies" まとめ 以上でJavaScriptでAPIにfetchでリクエストを送る実装は終了となります。 お疲れさまでした。 参考にしたサイト
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JS 配列内の連想配列の値を取得する

はじめに ES6の復習中に配列内の連想配列がうまく取得できず 苦戦したので己の戒めとして残します。 状況 こんな配列があります。 const items = [ {id: 1, name: "靴", price: 3000}, {id: 2, name: "服", price: 1000}, {id: 3, name: "手袋", price: 100}, {id: 4, name: "マスク", price: 50}, {id: 5, name: "帽子", price: 800}, ]; この時、keyのnameが「靴」である連想配列を探す getShoesという関数を作る時は const getShoes = items.find((item) => { return item.name === "靴" }); console.log(getShoes); // 実行結果 {id: 1, name: "靴", price: 3000} このような関数を定義します。 次に配列と、その中のkeyを指定して、一致する値があるか どうかを探し、一致する連想配列を表示する getAnyOne関数を作ります。 const getAnyOne = (array, element) => { return array.find((arr) => { const key = Object.keys(element)[0] return arr.key === element.key }) } こんな感じで書きました。 さっそく帽子の連想配列を取得できるか試すために console.log(getAnyOne(items, {name: "帽子"})); と書きました。するとconsoleは {id: 1, name: "靴", price: 3000} 何故か靴の連想配列を取得しています。どして? 問題の原因 今回getAnyOne関数のreturnする部分でキーを指定する際、 連想配列(arr).keyで指定しています。 しかしキーは今回keyという変数で定義しています。 変数で定義されたキーの値を取得する時は " . "ではなく" [ ] "を使う必要があるそうです。 よって今回の場合 const getAnyOne = (array, element) => { return array.find((arr) => { const key = Object.keys(element)[0] return arr[key] === element[key] }) } console.log(getAnyOne(items, {name: "帽子"})); とすれば正しくnameが帽子の連想配列を取得することができます。 おわりに JS、というか何かしらのアプリを作ろうと思うと ほぼ必ずといっていいほど配列を使用します。 しかしながらまだまだ配列のデータをうまく取得できないので、今後Reactもそうですがその前に前提のJSの知識をもっと深めないとなと反省しました。 (個人的にReact使ってても止まるのはJSの知識分野であることの方が多い) ここまで読んでくださりありがとうございました!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript [map filter forEach]の応用

JavaScriptで(map filter forEach)を使いこなすための練習として、作ったので、参考にしてみてください *基本的な使い方がわかっている方向けです。 ちなみにjQueryを使っています。 それぞれの細かい説明は省きます。 今回使ったのはこんなの 色を選択すると、その色の在庫があるサイズの選べるようになるシステム。 赤はSML全て選べて、黄はsかlからしか選べないようになっています。 早速作っていきますか。 htmlはこちら test.html <select name="color"> <option>色を選択してください</option> <option value="red">赤</option> <option value="blue">青</option> <option value="yellow">黄</option> </select> <select name="size"> <option value="">-</option> </select> JavaScriptはこちら test.js //在庫の情報 (一例) var product = { pattern: [ {color: 'red',size: 's'}, {color: 'red',size: 'm'}, {color: 'red',size: 'l'}, {color: 'blue',size: 's'}, {color: 'blue',size: 'm'}, {color: 'yellow',size: 'l'} ] }; // html側で書いた、selectのcolorの値が変わったら 変わった値のValue値を変数colorに入れる $('select[name=color]').on('change', function() { var color = $(this).val(); // sizeを初期化 var size = []; // 配列の中のcolorの値と、selectで変更したcolorの値が一緒の配列のみ返す size = product.pattern.filter(function(value) { return value.color == color; }); // sizeの配列からsizeの値のみ取り出すため、mapを使い値を更新する size = size.map(function(value) { return value.size; }) //サイズの初期化を色を変更する度に行う  //一致した値を追加する処理を行う。 $('select[name=size]').empty() size.forEach(function(value) { $('select[name=size]').append('<option>' + value + '</option>') }) }) $(this)で、変更された値のValue値を取得できるみたいです。自分もちょっとあやふやなので勉強しようと思います。 Html側のcolor size という値と、jsの変数のcolor sizeがあるので間違いやすいです。 それぞれが何を指しているかを考えると勉強になります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React 覚え書き

プロジェクト作成 npx create-react-app react-sample cd react-sample npm start または yarn start コメント jsxの中では、{/* コメント */} を使う。 タグの中は、/* */ // も使える クラスコンポーネント src/App.js import React, { Component } from "react"; class App extends Component { render() { return <p>Hello world {this.props.name}!</p>; } } export default App; 関数コンポーネント シンプルだが、State、ライフサイクルメソッドが使えない Hookを使うことにより、上記問題は解決する import React from "react"; const App = (props) => { return <p>Hello world {props.name}!</p>; }; export default App; props <App name="ichiro"></App> の場合 {this.props.name} = ichiro <App><p>こんにちは</p></App> の場合 {this.props.children} = <p>こんにちは</p> 繰り返し import React from "react"; const App = () => { const users = [{ name: "yamada" }, { name: "suziki" }, { name: "saito" }]; return ( <dl> {users.map((user, index) => ( <ul key={index}>{user.name}</ul> ))} </dl> ); }; export default App; 条件分岐 // 条件演算子 { this.props.isNew ? <NewIcon /> : null } // &&演算子 { this.props.isNew && <NewIcon /> } // 即時関数 {(() => { if (this.props.isNew) { return <NewIcon /> } })} stateを使ったフォーム constructor()の中で、stateの初期化を行う。 this.setState()でstateの更新を行う。 import React, { Component } from "react"; export default class App extends Component { constructor(props) { super(props); this.state = { name: "", prefecture: "", career: "", skill: [""], memo: "", }; this.handleChange = this.handleChange.bind(this); this.handleChangeMulti = this.handleChangeMulti.bind(this); this.file = React.createRef(); // fileを参照できるようにする(Uncontrolled Component) this.show = this.show.bind(this); } handleChange(e) { this.setState({ [e.target.name]: e.target.value, }); } handleChangeMulti(e) { const fs = this.state.skill; if (e.target.checked) { fs.push(e.target.value); } else { fs.splice(fs.indexOf(e.target.value), 1); } this.setState({ [e.target.name]: fs, }); } show() { console.log(`${this.state.name}`); console.log(`${this.state.prefecture}`); console.log(`${this.state.career}`); console.log(`${this.state.skill}`); console.log(`${this.state.memo}`); const f = this.file.current.files[0]; if (f) { console.log(`${f.name} ${f.type} ${f.size}`); } } render() { return ( <form> <label htmlFor="name">名前:</label> <input id="name" name="name" type="text" value={this.state.name} onChange={this.handleChange}></input> <br /> <label htmlFor="prefecture">住所:</label> <select id="prefecture" name="prefecture" value={this.state.prefecture} onChange={this.handleChange}> <option value=""></option> <option value="tokyo">東京都</option> <option value="kanagawa">神奈川県</option> <option value="saitama">埼玉県</option> <option value="chiba">千葉県</option> <option value="ibaraki">茨城県</option> <option value="gunma">群馬県</option> <option value="tochigi">栃木県</option> </select> <fieldset> <legend>経験年数:</legend> <input id="career_zero" name="career" type="radio" value="zero" checked={this.state.career === "zero"} onChange={this.handleChange} /> <label htmlFor="career_zero">未経験</label> <input id="career_one" name="career" type="radio" value="one" checked={this.state.career === "one"} onChange={this.handleChange} /> <label htmlFor="career_one">1年未満</label> <input id="career_three" name="career" type="radio" value="three" checked={this.state.career === "three"} onChange={this.handleChange} /> <label htmlFor="career_three">1~3年</label> <input id="career_five" name="career" type="radio" value="five" checked={this.state.career === "five"} onChange={this.handleChange} /> <label htmlFor="career_five">3年~5年</label> <input id="career_over" name="career" type="radio" value="over" checked={this.state.career === "over"} onChange={this.handleChange} /> <label htmlFor="career_over">5年以上</label> </fieldset> <fieldset> <legend>スキル:</legend> <input id="skill_java" name="skill" type="checkbox" value="java" checked={this.state.skill.includes("java")} onChange={this.handleChangeMulti} /> <label htmlFor="skill_java">Java</label> <input id="skill_php" name="skill" type="checkbox" value="php" checked={this.state.skill.includes("php")} onChange={this.handleChangeMulti} /> <label htmlFor="skill_php">PHP</label> <input id="skill_javascript" name="skill" type="checkbox" value="javascript" checked={this.state.skill.includes("javascript")} onChange={this.handleChangeMulti} /> <label htmlFor="skill_javascript">Javascript</label> <input id="skill_python" name="skill" type="checkbox" value="python" checked={this.state.skill.includes("python")} onChange={this.handleChangeMulti} /> <label htmlFor="skill_python">Python</label> <input id="skill_ruby" name="skill" type="checkbox" value="ruby" checked={this.state.skill.includes("ruby")} onChange={this.handleChangeMulti} /> <label htmlFor="skill_ruby">Ruby</label> </fieldset> <br /> <label htmlFor="memo">メモ:</label> <textarea id="memo" name="memo" value={this.state.memo} onChange={this.handleChange}></textarea> <br /> <label htmlFor="file">ファイル:</label> <input id="file" name="file" type="file" ref={this.file}></input> <br /> <button type="button" onClick={this.show}> 送信 </button> </form> ); } } useStateを使ったフォーム import React, { useState } from "react"; const App = () => { const [name, setName] = useState(""); const [prefecture, setPrefecture] = useState(""); const [career, setCareer] = useState(""); const [skill, setSkill] = useState([]); const [memo, setMemo] = useState(""); const file = React.createRef(); const handleChange = (event) => { switch (event.target.name) { case "name": setName(event.target.value); break; case "prefecture": setPrefecture(event.target.value); break; case "career": setCareer(event.target.value); break; case "memo": setMemo(event.target.value); break; default: console.log("key not found"); } }; const handleChangeMulti = (e) => { if (skill.includes(e.target.value)) { setSkill(skill.filter((item) => item !== e.target.value)); } else { setSkill([...skill, e.target.value]); } }; const show = () => { console.log(name); console.log(prefecture); console.log(career); console.log(skill); console.log(memo); const f = file.current.files[0]; if (f) { console.log(`${f.name} ${f.type} ${f.size}`); } }; return ( <form> <label htmlFor="name">名前:</label> <input id="name" name="name" type="text" value={name} onChange={handleChange}></input> <br /> <label htmlFor="prefecture">住所:</label> <select id="prefecture" name="prefecture" value={prefecture} onChange={handleChange}> <option value=""></option> <option value="tokyo">東京都</option> <option value="kanagawa">神奈川県</option> <option value="saitama">埼玉県</option> <option value="chiba">千葉県</option> <option value="ibaraki">茨城県</option> <option value="gunma">群馬県</option> <option value="tochigi">栃木県</option> </select> <fieldset> <legend>経験年数:</legend> <input id="career_zero" name="career" type="radio" value="zero" checked={career === "zero"} onChange={handleChange} /> <label htmlFor="career_zero">未経験</label> <input id="career_one" name="career" type="radio" value="one" checked={career === "one"} onChange={handleChange} /> <label htmlFor="career_one">1年未満</label> <input id="career_three" name="career" type="radio" value="three" checked={career === "three"} onChange={handleChange} /> <label htmlFor="career_three">1~3年</label> <input id="career_five" name="career" type="radio" value="five" checked={career === "five"} onChange={handleChange} /> <label htmlFor="career_five">3年~5年</label> <input id="career_over" name="career" type="radio" value="over" checked={career === "over"} onChange={handleChange} /> <label htmlFor="career_over">5年以上</label> </fieldset> <fieldset> <legend>スキル:</legend> <input id="skill_java" name="skill" type="checkbox" value="java" checked={skill.includes("java")} onChange={handleChangeMulti} /> <label htmlFor="skill_java">Java</label> <input id="skill_php" name="skill" type="checkbox" value="php" checked={skill.includes("php")} onChange={handleChangeMulti} /> <label htmlFor="skill_php">PHP</label> <input id="skill_javascript" name="skill" type="checkbox" value="javascript" checked={skill.includes("javascript")} onChange={handleChangeMulti} /> <label htmlFor="skill_javascript">Javascript</label> <input id="skill_python" name="skill" type="checkbox" value="python" checked={skill.includes("python")} onChange={handleChangeMulti} /> <label htmlFor="skill_python">Python</label> <input id="skill_ruby" name="skill" type="checkbox" value="ruby" checked={skill.includes("ruby")} onChange={handleChangeMulti} /> <label htmlFor="skill_ruby">Ruby</label> </fieldset> <br /> <label htmlFor="memo">メモ:</label> <textarea id="memo" name="memo" value={memo} onChange={handleChange}></textarea> <br /> <label htmlFor="file">ファイル:</label> <input id="file" name="file" type="file" ref={file}></input> <br /> <button type="button" onClick={show}> 送信 </button> </form> ); }; export default App; ライフサイクルメソッド Mounting Updating Unmounting 用途 constructor(props) 初期化 stateの初期化、thisの固定など render() 描画時 描画時 必須。React要素、文字列値、数値、など componentDidMount() 配置後 リソースの初期化、文書ツリーへのアクセスなど shouldComponentUpdate() 再描画前 再描画前にアクセスしたいとき componentDidUpdate() 再描画後 再描画後にアクセスしたいとき componentWillUnmount() 破棄時 リソースの破棄など ルーティング npm install react-router-dom または yarn add react-router-dom import React from "react"; import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom"; export default function BasicRouter() { return ( <Router> <div> {/* リンク表示部 リンクがここに表示される */} <ul> <li> <Link to="/">Home</Link> </li> <li> <Link to="/about">About</Link> </li> <li> <Link to="/dashboard">Dashboard</Link> </li> </ul> <hr /> {/* コンポーネント表示部 パスがマッチングしたコンポーネントがここに表示される */} <Switch> <Route exact path="/"> <Home /> </Route> <Route path="/about"> <About /> </Route> <Route path="/dashboard"> <Dashboard /> </Route> </Switch> </div> </Router> ); } function Home() { return ( <div> <h2>Home</h2> </div> ); } function About() { return ( <div> <h2>About</h2> </div> ); } function Dashboard() { return ( <div> <h2>Dashboard</h2> </div> ); } ローカルストレージ ローカルストレージ 値 = localStorade.getItem(キー) localStorade.setItem(キー, 値) JSON⇔オブジェクト変換 オブジェクト = JSON.parse(JSON文字列) JSON文字列 = JSON.stringify(オブジェクト) || [] はnullの場合、[]にする。 // データ取得 componentDidMount() { const todos = JSON.parse(localStorade.getItem('todos')) || []; this.setState({todos: todos}); } // データ1件追加 addTodo(todo) { const todos = this.state.todos; todos.push(todo); this.setState({todos: todos}); localStorade.setItem('todos', JSON.stringify(this.state.todos)); } // データ1件削除 deleteTodo(i) { const todos = this.state.todos; todos.splice(i, 1); this.setState({todos: todos}); localStorade.setItem('todos', JSON.stringify(this.state.todos)); } Fetch API Read List fetch("http://localhost:8080/todos") .then((res) => res.json()) .then((data) => { this.setState({todos: data}) }) .catch((err) => console.log(err)) Read fetch("http://localhost:8080/todos/${id}") .then((res) => res.json()) .then((data) => { this.setState({todo: data}) }) .catch((err) => console.log(err)) Create fetch("http://localhost:8080/todos" ,{ method: "POST", headers: { "Content-type": "application/json" }, body: JSON.stringify(todo) }) .then((res) => res.json()) .then((data) => { // 処理 }) .catch((err) => console.log(err)) Update fetch("http://localhost:8080/todos/${id}" ,{ method: "PUT", headers: { "Content-type": "application/json" }, body: JSON.stringify(todos) }) .then((res) => res.json()) .then((data) => { // 処理 }) .catch((err) => console.log(err)) Delete fetch("http://localhost:8080/todos/${id}" ,{ method: "DELETE" }) .then((res) => res.json()) .then((data) => { // 処理 }) .catch((err) => console.log(err)) 今後追加したいこと Redux Hook Spring Bootなどへの組み込み 参考 速習 React 速習シリーズ Kindle版 https://reactrouter.com/web/guides/quick-start
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsでスワイプで画像切り替えする方法

前提 Ruby 2.6.3 Rails 6.i.3 Font Awesome導入済み Bootstrapの導入 $ yarn add jquery bootstrap popper.js インストールが出来たらenvironment.jsに以下の文を記述してください config/webpack/environment.js const webpack = require('webpack') environment.plugins.append( 'Provide', new webpack.ProvidePlugin({ $: 'jquery/src/jquery', jQuery: 'jquery/src/jquery', Popper: ['popper.js', 'default'] }) ) application.jsにも追記してください。 app/javascript/packs/application.js import 'bootstrap'; import '../stylesheets/application'; Bootstrap導入出来ているはずです。 試しにボタンとか追加して確認してください。 Bootstrap公式ページ application.scss作成 mkdir app/javascript/stylesheets touch app/javascript/stylesheets/application.scss application.html.erb追加 app/views/layouts/application.html.erb <%= stylesheet_pack_tag 'application', 'data-turbolinks-track': 'reload' %> Hammer.js の導入 参考ドキュメント $ yarn add hammerjs package.json { "name": "match", "private": true, "dependencies": { "@fortawesome/fontawesome-free": "^5.15.3", "@rails/actioncable": "^6.0.0", "@rails/activestorage": "^6.0.0", "@rails/ujs": "^6.0.0", "@rails/webpacker": "5.2.1", "bootstrap": "^4.6.0", "hammerjs": "^2.0.8", //追記されてたらインストールできてます。 "jquery": "^3.6.0", "popper.js": "^1.16.1", "turbolinks": "^5.2.0" }, "version": "0.1.0", "devDependencies": { "webpack-dev-server": "^3.11.2" } } packs/application.jsに追加 app/javascript/packs/application.js import 'hammerjs'; //...(省略) require("src/swipe") swipe.js作成 touch app/javascript/src/swipe.jsを作成します。 app/javascript/src/swipe.js if (location.pathname == "/users") { $(function () { let allCards = document.querySelectorAll(".swipe--card"); let swipeContainer = document.querySelector(".swipe"); function initCards() { let newCards = document.querySelectorAll(".swipe--card:not(.removed)"); newCards.forEach(function (card, index) { card.style.zIndex = allCards.length - index; card.style.transform = "scale(" + (20 - index) / 20 + ") translateY(-" + 30 * index + "px)"; card.style.opacity = (10 - index) / 10; }); if (newCards.length == 0) { $(".no-user").addClass("is-active"); } } initCards(); allCards.forEach(function (el) { let hammertime = new Hammer(el); hammertime.on("pan", function (event) { if (event.deltaX === 0) return; if (event.center.x === 0 && event.center.y === 0) return; el.classList.add("moving"); swipeContainer.classList.toggle("swipe_like", event.deltaX > 0); swipeContainer.classList.toggle("swipe_dislike", event.deltaX < 0); let xMulti = event.deltaX * 0.03; let yMulti = event.deltaY / 80; let rotate = xMulti * yMulti; event.target.style.transform = "translate(" + event.deltaX + "px, " + event.deltaY + "px) rotate(" + rotate + "deg)"; }); hammertime.on("panend", function (event) { el.classList.remove("moving"); swipeContainer.classList.remove("swipe_like"); swipeContainer.classList.remove("swipe_dislike"); let moveOutWidth = document.body.clientWidth; let keep = Math.abs(event.deltaX) < 200; event.target.classList.toggle("removed", !keep); if (keep) { event.target.style.transform = ""; } else { let endX = Math.max(Math.abs(event.velocityX) * moveOutWidth, moveOutWidth) + 100; let toX = event.deltaX > 0 ? endX : -endX; let endY = Math.abs(event.velocityY) * moveOutWidth; let toY = event.deltaY > 0 ? endY : -endY; let xMulti = event.deltaX * 0.03; let yMulti = event.deltaY / 80; let rotate = xMulti * yMulti; event.target.style.transform = "translate(" + toX + "px, " + (toY + event.deltaY) + "px) rotate(" + rotate + "deg)"; initCards(); } }); }); function createButtonListener(reaction) { let cards = document.querySelectorAll(".swipe--card:not(.removed)"); if (!cards.length) return false; let moveOutWidth = document.body.clientWidth * 2; let card = cards[0]; card.classList.add("removed"); if (reaction == "like") { card.style.transform = "translate(" + moveOutWidth + "px, -100px) rotate(-30deg)"; } else { card.style.transform = "translate(-" + moveOutWidth + "px, -100px) rotate(30deg)"; } initCards(); } $("#like").on("click", function () { createButtonListener("like"); }); $("#dislike").on("click", function () { createButtonListener("dislike"); }); }); } index.html.erb 今回はindex.html.erbに実装しています。 users.index.html.erb <div class="user-index-page"> <div class="swipe"> <div class="swipe--status"> <i class="fa fa-times"></i> <i class="fa fa-heart"></i> </div> <div class="swipe--cards"> <% @users.each do |user| %> <div class="swipe--card" id="<%= user.id %>"> <% if user.profile_image.url.nil? %> <div class="profile-default-img"></div> <% else %> <%= image_tag user.profile_image.url, class: "profile-img" %> <% end %> <p class="profile-name"> <%= user.name %> </p> </div> <% end %> <div class="no-user">近くにお相手がいません</div> </div> <div class="swipe--buttons"> <button id="dislike"><i class="fas fa-times fa-2x"></i></button> <button id="like"><i class="fas fa-heart fa-2x"></i></button> </div> </div> </div> これで完成です。 Railsのバージョンが違うと動かなくなるのでその都度確認してから実装してください。 後は適当にスタイルつけたら完成です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JSONデータからAWS構成図を作図するスクリプト

はじめに AWS構成図の作図や図の更新作業をサクッとできないかと模索してます。模索第1弾としてJSONデータからAWS構成図を作図するスクリプトを作りました。 作ったスクリプトの動作はこんな感じ。通信経路の表示/非表示も簡単に切り替えできます。 参考情報 今回は、以前作成した記事のコードをベースにスクリプトを作成しました。 Webサーバ無しHTML(JavaScript)でJSONデータを扱う スクリプトソース 作成したスクリプトは以下のgithubに格納しました。ソースをダウンロードし、AwsDiagram.htmlを実行するとサンプルが動きます。WEBサーバは不要です。 Handling_JSON_with_no_web_server_HTML-JavaScript- 仕組み 以下の図のように、X軸とY軸で座標があって、各座標にはレイヤーがあります。この座標とレイヤーを指定して図形を描画します。 描画する図は、座標とレイヤーを指定することで描画する場所が指定されます。それらを設定するのが設定するのがlink.json.jsになります。link.json.jsを更新すれば作図内容を変更できます。 link.json.jsの作図例としてサンプル(1)~(5)を紹介します。 サンプル(1)EC2アイコンを4つ配置する 基本的なルールだけで描画したサンプル、EC2アイコンを4つ配置します。 link.json.jsです。 link.json_01.js let list = [ { "OBJECT": "NONE", "TYPE": "INIT", "MaxLayer": 6, "BaseWidth": 2, "CellWidth": 20, "EC2" : ["ec2.png","none","0.3","none","CENTER"], }, {"OBJECT": "EC2-11","TYPE": "FIG","SET": "EC2","LAYER": 1,"TopX": "1","TopY": "1","BtmX": "1","BtmY": "1","Width" : 0,"Height": 0,}, {"OBJECT": "EC2-12","TYPE": "FIG","SET": "EC2","LAYER": 1,"TopX": "1","TopY": "2","BtmX": "1","BtmY": "2","Width" : 0,"Height": 0,}, {"OBJECT": "EC2-21","TYPE": "FIG","SET": "EC2","LAYER": 1,"TopX": "2","TopY": "1","BtmX": "2","BtmY": "1","Width" : 0,"Height": 0,}, {"OBJECT": "EC2-22","TYPE": "FIG","SET": "EC2","LAYER": 1,"TopX": "2","TopY": "2","BtmX": "2","BtmY": "2","Width" : 0,"Height": 0,}, ]; link.json.jsの基本は、以下の A, B, C の3つです。 Aについて "OBJECT": "NONE" があるデータ部は、全体の定義を設定します。ここに、レイヤーの最大数やEC2アイコンの描画規則などを設定します。 変数名 説明 OBJECT NONE を設定すると、全体の定義を設定 TYPE INIT を記載します MaxLayer レイヤーの最大数を設定 BaseWidth 描画する図形の基本サイズ CellWidth 描画する図形の基本サイズ EC2 EC2アイコンの描画規則 Bについて EC2アイコンを描画する規則を設定しています。 値 意味 ec2.png 描画する画像データ none 面の色 none は色なし 0.3 面の色の透明度 none 線の色 none は色なし CENTER 描画する画像データの配置場所。選択肢は、HEAD,CENTER の2つ Cについて 配置するアイコンの位置、レイヤー、描画規則を設定します。 変数名 説明 OBJECT データ名 TYPE データのタイプを指定。FIG は図形の意味 SET "OBJECT": "NONE"で定義した定義の名前。 LAYER 描画するレイヤーを指定 TopX 描画する図形の左上のX座標 TopY 描画する図形の左上のY座標 BtmX 描画する図形の右下のX座標 BtmY 描画する図形の右下のY座標 Width 固定値 0 を設定(スクリプトが、実行時に適切な値に更新) Height 固定値 0 を設定(スクリプトが、実行時に適切な値に更新) 指定するレイヤーを変更すると、描画するサイズも変わる。先ほどのjsonでLAYER値を変更した場合、このようになります。 link.json_01-2.js let list = [ { "OBJECT": "NONE", "TYPE": "INIT", "MaxLayer": 6, "BaseWidth": 2, "CellWidth": 20, "EC2" : ["ec2.png","none","0.3","none","CENTER"], }, {"OBJECT": "EC2-11","TYPE": "FIG","SET": "EC2","LAYER": 1,"TopX": "1","TopY": "1","BtmX": "1","BtmY": "1","Width" : 0,"Height": 0,}, {"OBJECT": "EC2-12","TYPE": "FIG","SET": "EC2","LAYER": 2,"TopX": "1","TopY": "2","BtmX": "1","BtmY": "2","Width" : 0,"Height": 0,}, {"OBJECT": "EC2-21","TYPE": "FIG","SET": "EC2","LAYER": 3,"TopX": "2","TopY": "1","BtmX": "2","BtmY": "1","Width" : 0,"Height": 0,}, {"OBJECT": "EC2-22","TYPE": "FIG","SET": "EC2","LAYER": 4,"TopX": "2","TopY": "2","BtmX": "2","BtmY": "2","Width" : 0,"Height": 0,}, ]; サンプル(2)Web3層のネットワーク図 Web3層のAWSネットワーク図のサンプルです。 link.json_02.js let list = [ { "OBJECT": "NONE", "TYPE": "INIT", "MaxLayer": 6, "BaseWidth": 2, "CellWidth": 20, "VPC" : ["vpc.png", "none" ,"0.0","#000000","HEAD"], "AZ" : ["az.png", "none" ,"0.0","#5b9cd5","HEAD"], "PUB" : ["subnet-public.png", "#1f860f","0.2","#1f860f","HEAD"], "PRI" : ["subnet-private.png","#147eba","0.2","#147eba","HEAD"], }, {"OBJECT": "VPC-1", "TYPE": "FIG", "SET": "VPC","LAYER": 5,"TopX": "2","TopY": "1","BtmX": "4","BtmY": "2","Width" : 0,"Height": 0,}, {"OBJECT": "AZ-1", "TYPE": "FIG", "SET": "AZ","LAYER": 4,"TopX": "2","TopY": "1","BtmX": "4","BtmY": "1","Width" : 0,"Height": 0,}, {"OBJECT": "AZ-2", "TYPE": "FIG", "SET": "AZ","LAYER": 4,"TopX": "2","TopY": "2","BtmX": "4","BtmY": "2","Width" : 0,"Height": 0,}, {"OBJECT": "Subnet-01","TYPE": "FIG", "SET": "PUB","LAYER": 3,"TopX": "2","TopY": "1","BtmX": "2","BtmY": "1","Width" : 0,"Height": 0,}, {"OBJECT": "Subnet-02","TYPE": "FIG", "SET": "PUB","LAYER": 3,"TopX": "2","TopY": "2","BtmX": "2","BtmY": "2","Width" : 0,"Height": 0,}, {"OBJECT": "Subnet-11","TYPE": "FIG", "SET": "PRI","LAYER": 3,"TopX": "3","TopY": "1","BtmX": "3","BtmY": "1","Width" : 0,"Height": 0,}, {"OBJECT": "Subnet-12","TYPE": "FIG", "SET": "PRI","LAYER": 3,"TopX": "3","TopY": "2","BtmX": "3","BtmY": "2","Width" : 0,"Height": 0,}, {"OBJECT": "Subnet-21","TYPE": "FIG", "SET": "PRI","LAYER": 3,"TopX": "4","TopY": "1","BtmX": "4","BtmY": "1","Width" : 0,"Height": 0,}, {"OBJECT": "Subnet-22","TYPE": "FIG", "SET": "PRI","LAYER": 3,"TopX": "4","TopY": "2","BtmX": "4","BtmY": "2","Width" : 0,"Height": 0,}, ]; 長方形は、TopX,TopYとBtmX,BtmYの値によって設定します。長方形はVPCやAZなどの描画に使用できます。 サンプル(3) 線を描画する AWS構成図にデータの通信経路を描画する方法です。通信経路は描画のON/OFFを切り替えできます。 ライン(1)を描画した場合 ライン(2)を描画した場合 JSONデータはこちら link.json_03.js let list = [ { "OBJECT": "NONE", "TYPE": "INIT", "MaxLayer": 6, "BaseWidth": 2, "CellWidth": 20, "VPC" : ["vpc.png", "none" ,"0.0","#000000","HEAD"], "AZ" : ["az.png", "none" ,"0.0","#5b9cd5","HEAD"], "PUB" : ["subnet-public.png", "#1f860f","0.2","#1f860f","HEAD"], "PRI" : ["subnet-private.png","#147eba","0.2","#147eba","HEAD"], }, {"OBJECT": "Subnet-01","TYPE": "FIG", "SET": "PUB","LAYER": 3,"TopX": "2","TopY": "1","BtmX": "2","BtmY": "1","Width" : 0,"Height": 0,}, {"OBJECT": "Subnet-02","TYPE": "FIG", "SET": "PUB","LAYER": 3,"TopX": "2","TopY": "2","BtmX": "2","BtmY": "2","Width" : 0,"Height": 0,}, {"OBJECT": "Subnet-11","TYPE": "FIG", "SET": "PRI","LAYER": 3,"TopX": "3","TopY": "1","BtmX": "3","BtmY": "1","Width" : 0,"Height": 0,}, {"OBJECT": "Subnet-12","TYPE": "FIG", "SET": "PRI","LAYER": 3,"TopX": "3","TopY": "2","BtmX": "3","BtmY": "2","Width" : 0,"Height": 0,}, {"OBJECT": "Subnet-21","TYPE": "FIG", "SET": "PRI","LAYER": 3,"TopX": "4","TopY": "1","BtmX": "4","BtmY": "1","Width" : 0,"Height": 0,}, {"OBJECT": "Subnet-22","TYPE": "FIG", "SET": "PRI","LAYER": 3,"TopX": "4","TopY": "2","BtmX": "4","BtmY": "2","Width" : 0,"Height": 0,}, {"TYPE": "LINE","OBJECT": "LINE-01","LINE": ["Subnet-01","Subnet-11","Subnet-21"],}, {"TYPE": "LINE","OBJECT": "LINE-02","LINE": ["Subnet-01","Subnet-11","Subnet-21","Subnet-22","Subnet-12","Subnet-02","Subnet-01"],}, {"TYPE": "LINE","OBJECT": "LINE-03","LINE": ["Subnet-01","Subnet-22"],}, {"TYPE": "LINE","OBJECT": "LINE-04","LINE": ["Subnet-02","Subnet-21"],}, {"TYPE": "LINE-GRP","LAVEL": "ライン(1)","STROKE": "#000080","LINES": ["LINE-01"],}, {"TYPE": "LINE-GRP","LAVEL": "ライン(2)","STROKE": "#FF8FFF","LINES": ["LINE-02","LINE-03","LINE-04"],}, ]; 線の描画は、"TYPE": "LINE" と "TYPE": "LINE-GRP" の2つのデータを用意します。 "TYPE": "LINE"は、変数 OBJECT に名前を設定し、変数 LINE に線を引くOBJECT名を指定します。以下は、OBJECT "Subnet-01","Subnet-11","Subnet-21" の順に線を引く設定です。 {"TYPE": "LINE","OBJECT": "LINE-01","LINE": ["Subnet-01","Subnet-11","Subnet-21"],} "TYPE": "LINE-GRP"は以下のように、変数 LINES に LINE名 を指定します。STROKE は線の色を指定します。 {"TYPE": "LINE-GRP","LAVEL": "ライン(1)","STROKE": "#000080","LINES": ["LINE-01"],} サンプル(4)それっぽいAWS構成図 Web3層に、ログ収集、オンプレ環境の端末からのアクセス、エンドユーザーからの通信経路などを盛り込んだ構成図を描いてみました。 jsonデータはちょっと長いですが、やっていることはこれまで紹介したことと同じです。 link.json_04.js let list = [ { "OBJECT": "NONE", "TYPE": "INIT", "MaxLayer": 6, "BaseWidth": 2, "CellWidth": 20, "OFFICE" : ["office.png", "#000000","0.1","#000000","HEAD"], "VPC" : ["vpc.png", "none" ,"0.0","#000000","HEAD"], "AZ" : ["az.png", "none" ,"0.0","#5b9cd5","HEAD"], "PUB" : ["subnet-public.png", "#1f860f","0.2","#1f860f","HEAD"], "PRI" : ["subnet-private.png","#147eba","0.2","#147eba","HEAD"], "ALB" : ["alb.png", "#d86613","0.3","#d86613","CENTER"], "SG" : ["sg.png", "none" ,"0.0","#df3312","HEAD"], "EC2" : ["ec2.png", "none" ,"0.3","none","CENTER"], "CW" : ["cw.png", "none" ,"0.3","none","CENTER"], "SNS" : ["sns.png", "none" ,"0.3","none","CENTER"], "S3" : ["s3.png", "#000000" ,"0.5","none","CENTER"], "RDS" : ["rds.png", "none" ,"0.3","none","CENTER"], "USERS":["users.png", "none" ,"0.3","none","CENTER"], "PC":["pc.png", "#000000","0.5","none","CENTER"], "ALARM":["alarm.png", "none","0.5","none","CENTER"], }, {"OBJECT": "USERS","TYPE": "FIG", "SET": "USERS","LAYER": 1,"TopX": "1","TopY": "1","BtmX": "1","BtmY": "2","Width" : 0,"Height": 0,}, {"OBJECT": "VPC-1", "TYPE": "FIG", "SET": "VPC","LAYER": 5,"TopX": "2","TopY": "1","BtmX": "4","BtmY": "2","Width" : 0,"Height": 0,}, {"OBJECT": "AZ-1", "TYPE": "FIG", "SET": "AZ","LAYER": 4,"TopX": "2","TopY": "1","BtmX": "4","BtmY": "1","Width" : 0,"Height": 0,}, {"OBJECT": "AZ-2", "TYPE": "FIG", "SET": "AZ","LAYER": 4,"TopX": "2","TopY": "2","BtmX": "4","BtmY": "2","Width" : 0,"Height": 0,}, {"OBJECT": "Subnet-01","TYPE": "FIG", "SET": "PUB","LAYER": 3,"TopX": "2","TopY": "1","BtmX": "2","BtmY": "1","Width" : 0,"Height": 0,}, {"OBJECT": "Subnet-02","TYPE": "FIG", "SET": "PUB","LAYER": 3,"TopX": "2","TopY": "2","BtmX": "2","BtmY": "2","Width" : 0,"Height": 0,}, {"OBJECT": "Subnet-11","TYPE": "FIG", "SET": "PRI","LAYER": 3,"TopX": "3","TopY": "1","BtmX": "3","BtmY": "1","Width" : 0,"Height": 0,}, {"OBJECT": "Subnet-12","TYPE": "FIG", "SET": "PRI","LAYER": 3,"TopX": "3","TopY": "2","BtmX": "3","BtmY": "2","Width" : 0,"Height": 0,}, {"OBJECT": "Subnet-21","TYPE": "FIG", "SET": "PRI","LAYER": 3,"TopX": "4","TopY": "1","BtmX": "4","BtmY": "1","Width" : 0,"Height": 0,}, {"OBJECT": "Subnet-22","TYPE": "FIG", "SET": "PRI","LAYER": 3,"TopX": "4","TopY": "2","BtmX": "4","BtmY": "2","Width" : 0,"Height": 0,}, {"OBJECT": "ALB-01","TYPE": "FIG", "SET": "ALB","LAYER": 1,"TopX": "2","TopY": "1","BtmX": "2","BtmY": "2","Width" : 0,"Height": 0,}, {"OBJECT": "SG-01","TYPE": "FIG", "SET": "SG","LAYER": 2,"TopX": "2","TopY": "1","BtmX": "2","BtmY": "2","Width" : 0,"Height": 0,}, {"OBJECT": "S3-01","TYPE": "FIG", "SET": "S3","LAYER": 1,"TopX": "2","TopY": "3","BtmX": "2","BtmY": "3","Width" : 0,"Height": 0,}, {"OBJECT": "EC2-11","TYPE": "FIG", "SET": "EC2","LAYER": 1,"TopX": "3","TopY": "1","BtmX": "3","BtmY": "1","Width" : 0,"Height": 0,}, {"OBJECT": "EC2-12","TYPE": "FIG", "SET": "EC2","LAYER": 1,"TopX": "3","TopY": "2","BtmX": "3","BtmY": "2","Width" : 0,"Height": 0,}, {"OBJECT": "SG-11","TYPE": "FIG", "SET": "SG","LAYER": 2,"TopX": "3","TopY": "1","BtmX": "3","BtmY": "2","Width" : 0,"Height": 0,}, {"OBJECT": "CW-11","TYPE": "FIG", "SET": "CW","LAYER": 1,"TopX": "3","TopY": "3","BtmX": "3","BtmY": "3","Width" : 0,"Height": 0,}, {"OBJECT": "RDS-21","TYPE": "FIG", "SET": "RDS","LAYER": 1,"TopX": "4","TopY": "1","BtmX": "4","BtmY": "1","Width" : 0,"Height": 0,}, {"OBJECT": "RDS-22","TYPE": "FIG", "SET": "RDS","LAYER": 1,"TopX": "4","TopY": "2","BtmX": "4","BtmY": "2","Width" : 0,"Height": 0,}, {"OBJECT": "SG-21", "TYPE": "FIG", "SET": "SG","LAYER": 2,"TopX": "4","TopY": "1","BtmX": "4","BtmY": "2","Width" : 0,"Height": 0,}, {"OBJECT": "ALARM-01","TYPE": "FIG", "SET": "ALARM","LAYER": 1,"TopX": "4","TopY": "3","BtmX": "4","BtmY": "3","Width" : 0,"Height": 0,}, {"OBJECT": "SNS-01","TYPE": "FIG", "SET": "SNS","LAYER": 1,"TopX": "5","TopY": "3","BtmX": "5","BtmY": "3","Width" : 0,"Height": 0,}, {"OBJECT": "VPC-2", "TYPE": "FIG", "SET": "VPC","LAYER": 5,"TopX": "5","TopY": "1","BtmX": "5","BtmY": "2","Width" : 0,"Height": 0,}, {"OBJECT": "Subnet-31","TYPE": "FIG", "SET": "PUB","LAYER": 3,"TopX": "5","TopY": "1","BtmX": "5","BtmY": "1","Width" : 0,"Height": 0,}, {"OBJECT": "EC2-31","TYPE": "FIG", "SET": "EC2","LAYER": 1,"TopX": "5","TopY": "1","BtmX": "5","BtmY": "1","Width" : 0,"Height": 0,}, {"OBJECT": "SG-31","TYPE": "FIG", "SET": "SG","LAYER": 2,"TopX": "5","TopY": "1","BtmX": "5","BtmY": "1","Width" : 0,"Height": 0,}, {"OBJECT": "OFFICE","TYPE": "FIG", "SET": "OFFICE","LAYER": 5,"TopX": "6","TopY": "1","BtmX": "6","BtmY": "2","Width" : 0,"Height": 0,}, {"OBJECT": "PC-01","TYPE": "FIG", "SET": "PC","LAYER": 1,"TopX": "6","TopY": "1","BtmX": "6","BtmY": "1","Width" : 0,"Height": 0,}, {"TYPE": "LINE","OBJECT": "LINE-A1","LINE": ["ALB-01","EC2-11","RDS-21"],}, {"TYPE": "LINE","OBJECT": "LINE-A2","LINE": ["ALB-01","EC2-12","RDS-21"],}, {"TYPE": "LINE","OBJECT": "LINE-B1","LINE": ["RDS-21","RDS-22"],}, {"TYPE": "LINE","OBJECT": "LINE-A0","LINE": ["USERS","ALB-01"],}, {"TYPE": "LINE","OBJECT": "LINE-M0","LINE": ["PC-01","EC2-31","RDS-21"],}, {"TYPE": "LINE","OBJECT": "LINE-M1","LINE": ["EC2-31","RDS-22"],}, {"TYPE": "LINE","OBJECT": "LINE-M2","LINE": ["EC2-31","EC2-11"],}, {"TYPE": "LINE","OBJECT": "LINE-M3","LINE": ["EC2-31","EC2-12"],}, {"TYPE": "LINE","OBJECT": "LOG-01","LINE": ["ALB-01","S3-01"],}, {"TYPE": "LINE","OBJECT": "LOG-02","LINE": ["EC2-11","CW-11","S3-01"],}, {"TYPE": "LINE","OBJECT": "LOG-03","LINE": ["EC2-12","CW-11"],}, {"TYPE": "LINE","OBJECT": "LOG-04","LINE": ["RDS-21","CW-11"],}, {"TYPE": "LINE","OBJECT": "ALARM-01","LINE": ["CW-11","ALARM-01","SNS-01","PC-01"],}, {"TYPE": "LINE-GRP","LAVEL": "処理経路","STROKE": "#000080","LINES": ["LINE-A0","LINE-A1","LINE-A2"],}, {"TYPE": "LINE-GRP","LAVEL": "冗長化","STROKE": "#800000","LINES": ["LINE-B1"],}, {"TYPE": "LINE-GRP","LAVEL": "管理経路","STROKE": "#800000","LINES": ["LINE-M0","LINE-M1","LINE-M2","LINE-M3"],}, {"TYPE": "LINE-GRP","LAVEL": "ログ収集","STROKE": "#800000","LINES": ["LOG-01","LOG-02","LOG-03","LOG-04"],}, {"TYPE": "LINE-GRP","LAVEL": "障害通知","STROKE": "#800000","LINES": ["ALARM-01"],}, ]; サンプル(5)少し複雑なAWS構成図 少し複雑なAWS構成図です。以下の図は通信経路(破線)をすべて表示しています(通信経路(破線)の表示/非表示は切り替え可)。 jsonデータは少し長くなりましたが、これまでのサンプルと同じ規則なので"複雑さ"は変わってないです。 link.json_05.js let list = [ { "OBJECT": "NONE", "TYPE": "INIT", "MaxLayer": 7, "BaseWidth": 2, "CellWidth": 20, "OFFICE" : ["office.png", "#000000","0.1","#000000","HEAD"], "AWS" : ["aws.png", "none" ,"0.0","#000000","HEAD"], "VPC" : ["vpc.png", "none" ,"0.0","#000000","HEAD"], "AZ" : ["az.png", "none" ,"0.0","#5b9cd5","HEAD"], "PUB" : ["subnet-public.png", "#1f860f","0.2","#1f860f","HEAD"], "PRI" : ["subnet-private.png","#147eba","0.2","#147eba","HEAD"], "ALB" : ["alb.png", "#d86613","0.3","#d86613","CENTER"], "SG" : ["sg.png", "none" ,"0.0","#df3312","HEAD"], "NATGW": ["natgw.png", "none" ,"0.0","none","CENTER"], "EC2" : ["ec2.png", "none" ,"0.3","none","CENTER"], "CW" : ["cw.png", "none" ,"0.3","none","CENTER"], "SNS" : ["sns.png", "none" ,"0.3","none","CENTER"], "S3" : ["s3.png", "#000000" ,"0.5","none","CENTER"], "RDS" : ["rds.png", "none" ,"0.3","none","CENTER"], "USERS":["users.png", "none" ,"0.3","none","CENTER"], "PC1":["pc.png", "#CCCCFF","0.3","none","HEAD"], "PC2":["pc.png", "none","0.0","none","CENTER"], "ALARM":["alarm.png", "none","0.0","none","CENTER"], "MAIL":["mail.png", "none","0.0","none","CENTER"], "CPIPE":["cpipeline.png", "none","0.0","#000000","HEAD"], "CCOM":["ccommit.png", "none","0.0","none","CENTER"], "CBLD":["cbuild.png", "none","0.0","none","CENTER"], "CDEP":["cdeploy.png", "none","0.0","none","CENTER"], }, {"OBJECT": "USERS","TYPE": "FIG", "SET": "USERS","LAYER": 1,"TopX": "1","TopY": "2","BtmX": "1","BtmY": "3","Width" : 0,"Height": 0,}, {"OBJECT": "AWS-1", "TYPE": "FIG", "SET": "AWS","LAYER": 6,"TopX": "2","TopY": "1","BtmX": "5","BtmY": "4","Width" : 0,"Height": 0,}, {"OBJECT": "VPC-1", "TYPE": "FIG", "SET": "VPC","LAYER": 5,"TopX": "2","TopY": "2","BtmX": "4","BtmY": "3","Width" : 0,"Height": 0,}, {"OBJECT": "AZ-1", "TYPE": "FIG", "SET": "AZ","LAYER": 4,"TopX": "2","TopY": "2","BtmX": "4","BtmY": "2","Width" : 0,"Height": 0,}, {"OBJECT": "AZ-2", "TYPE": "FIG", "SET": "AZ","LAYER": 4,"TopX": "2","TopY": "3","BtmX": "4","BtmY": "3","Width" : 0,"Height": 0,}, {"OBJECT": "Subnet-01","TYPE": "FIG", "SET": "PUB","LAYER": 3,"TopX": "2","TopY": "2","BtmX": "2","BtmY": "2","Width" : 0,"Height": 0,}, {"OBJECT": "Subnet-02","TYPE": "FIG", "SET": "PUB","LAYER": 3,"TopX": "2","TopY": "3","BtmX": "2","BtmY": "3","Width" : 0,"Height": 0,}, {"OBJECT": "Subnet-11","TYPE": "FIG", "SET": "PRI","LAYER": 3,"TopX": "3","TopY": "2","BtmX": "3","BtmY": "2","Width" : 0,"Height": 0,}, {"OBJECT": "Subnet-12","TYPE": "FIG", "SET": "PRI","LAYER": 3,"TopX": "3","TopY": "3","BtmX": "3","BtmY": "3","Width" : 0,"Height": 0,}, {"OBJECT": "Subnet-21","TYPE": "FIG", "SET": "PRI","LAYER": 3,"TopX": "4","TopY": "2","BtmX": "4","BtmY": "2","Width" : 0,"Height": 0,}, {"OBJECT": "Subnet-22","TYPE": "FIG", "SET": "PRI","LAYER": 3,"TopX": "4","TopY": "3","BtmX": "4","BtmY": "3","Width" : 0,"Height": 0,}, {"OBJECT": "ALB-01","TYPE": "FIG", "SET": "ALB","LAYER": 1,"TopX": "2","TopY": "2","BtmX": "2","BtmY": "3","Width" : 0,"Height": 0,}, {"OBJECT": "SG-01","TYPE": "FIG", "SET": "SG","LAYER": 2,"TopX": "2","TopY": "2","BtmX": "2","BtmY": "3","Width" : 0,"Height": 0,}, {"OBJECT": "S3-01","TYPE": "FIG", "SET": "S3","LAYER": 1,"TopX": "2","TopY": "4","BtmX": "2","BtmY": "4","Width" : 0,"Height": 0,}, {"OBJECT": "EC2-11","TYPE": "FIG", "SET": "EC2","LAYER": 1,"TopX": "3","TopY": "2","BtmX": "3","BtmY": "2","Width" : 0,"Height": 0,}, {"OBJECT": "EC2-12","TYPE": "FIG", "SET": "EC2","LAYER": 1,"TopX": "3","TopY": "3","BtmX": "3","BtmY": "3","Width" : 0,"Height": 0,}, {"OBJECT": "SG-11","TYPE": "FIG", "SET": "SG","LAYER": 2,"TopX": "3","TopY": "2","BtmX": "3","BtmY": "3","Width" : 0,"Height": 0,}, {"OBJECT": "CW-11","TYPE": "FIG", "SET": "CW","LAYER": 1,"TopX": "3","TopY": "4","BtmX": "3","BtmY": "4","Width" : 0,"Height": 0,}, {"OBJECT": "RDS-21","TYPE": "FIG", "SET": "RDS","LAYER": 1,"TopX": "4","TopY": "2","BtmX": "4","BtmY": "2","Width" : 0,"Height": 0,}, {"OBJECT": "RDS-22","TYPE": "FIG", "SET": "RDS","LAYER": 1,"TopX": "4","TopY": "3","BtmX": "4","BtmY": "3","Width" : 0,"Height": 0,}, {"OBJECT": "SG-21", "TYPE": "FIG", "SET": "SG","LAYER": 2,"TopX": "4","TopY": "2","BtmX": "4","BtmY": "3","Width" : 0,"Height": 0,}, {"OBJECT": "ALARM-01","TYPE": "FIG", "SET": "ALARM","LAYER": 1,"TopX": "4","TopY": "4","BtmX": "4","BtmY": "4","Width" : 0,"Height": 0,}, {"OBJECT": "SNS-01","TYPE": "FIG", "SET": "SNS","LAYER": 1,"TopX": "5","TopY": "4","BtmX": "5","BtmY": "4","Width" : 0,"Height": 0,}, {"OBJECT": "VPC-2", "TYPE": "FIG", "SET": "VPC","LAYER": 5,"TopX": "5","TopY": "2","BtmX": "5","BtmY": "3","Width" : 0,"Height": 0,}, {"OBJECT": "Subnet-31","TYPE": "FIG", "SET": "PUB","LAYER": 3,"TopX": "5","TopY": "2","BtmX": "5","BtmY": "2","Width" : 0,"Height": 0,}, {"OBJECT": "Subnet-32","TYPE": "FIG", "SET": "PRI","LAYER": 3,"TopX": "5","TopY": "3","BtmX": "5","BtmY": "3","Width" : 0,"Height": 0,}, {"OBJECT": "NATGW-01","TYPE": "FIG", "SET": "NATGW","LAYER": 1,"TopX": "5","TopY": "2","BtmX": "5","BtmY": "2","Width" : 0,"Height": 0,}, {"OBJECT": "EC2-31","TYPE": "FIG", "SET": "EC2","LAYER": 1,"TopX": "5","TopY": "3","BtmX": "5","BtmY": "3","Width" : 0,"Height": 0,}, {"OBJECT": "SG-31","TYPE": "FIG", "SET": "SG","LAYER": 2,"TopX": "5","TopY": "3","BtmX": "5","BtmY": "3","Width" : 0,"Height": 0,}, {"OBJECT": "OFFICE","TYPE": "FIG", "SET": "OFFICE","LAYER": 6,"TopX": "6","TopY": "2","BtmX": "6","BtmY": "3","Width" : 0,"Height": 0,}, {"OBJECT": "PC-01","TYPE": "FIG", "SET": "PC1","LAYER": 5,"TopX": "6","TopY": "2","BtmX": "6","BtmY": "3","Width" : 0,"Height": 0,}, {"OBJECT": "PC-02","TYPE": "FIG", "SET": "PC2","LAYER": 2,"TopX": "6","TopY": "2","BtmX": "6","BtmY": "2","Width" : 0,"Height": 0,}, {"OBJECT": "ML-01","TYPE": "FIG", "SET": "MAIL","LAYER": 1,"TopX": "6","TopY": "3","BtmX": "6","BtmY": "3","Width" : 0,"Height": 0,}, {"OBJECT": "CP-01","TYPE": "FIG", "SET": "CPIPE","LAYER": 5,"TopX": "2","TopY": "1","BtmX": "5","BtmY": "1","Width" : 0,"Height": 0,}, {"OBJECT": "CD-01","TYPE": "FIG", "SET": "CDEP","LAYER": 1,"TopX": "2","TopY": "1","BtmX": "2","BtmY": "1","Width" : 0,"Height": 0,}, {"OBJECT": "S3-02","TYPE": "FIG", "SET": "S3","LAYER": 1,"TopX": "3","TopY": "1","BtmX": "3","BtmY": "1","Width" : 0,"Height": 0,}, {"OBJECT": "CB-01","TYPE": "FIG", "SET": "CBLD","LAYER": 1,"TopX": "4","TopY": "1","BtmX": "4","BtmY": "1","Width" : 0,"Height": 0,}, {"OBJECT": "CM-01","TYPE": "FIG", "SET": "CCOM","LAYER": 1,"TopX": "5","TopY": "1","BtmX": "5","BtmY": "1","Width" : 0,"Height": 0,}, {"TYPE": "LINE","OBJECT": "LINE-A1","LINE": ["ALB-01","EC2-11","RDS-21"],}, {"TYPE": "LINE","OBJECT": "LINE-A2","LINE": ["ALB-01","EC2-12","RDS-21"],}, {"TYPE": "LINE","OBJECT": "LINE-B1","LINE": ["RDS-21","RDS-22"],}, {"TYPE": "LINE","OBJECT": "LINE-A0","LINE": ["USERS","ALB-01"],}, {"TYPE": "LINE","OBJECT": "LINE-M0","LINE": ["PC-02","EC2-31","RDS-21"],}, {"TYPE": "LINE","OBJECT": "LINE-M1","LINE": ["EC2-31","RDS-22"],}, {"TYPE": "LINE","OBJECT": "LINE-M2","LINE": ["EC2-31","EC2-11"],}, {"TYPE": "LINE","OBJECT": "LINE-M3","LINE": ["EC2-31","EC2-12"],}, {"TYPE": "LINE","OBJECT": "LOG-01","LINE": ["ALB-01","S3-01"],}, {"TYPE": "LINE","OBJECT": "LOG-02","LINE": ["EC2-11","CW-11","S3-01"],}, {"TYPE": "LINE","OBJECT": "LOG-03","LINE": ["EC2-12","CW-11"],}, {"TYPE": "LINE","OBJECT": "LOG-04","LINE": ["RDS-21","CW-11"],}, {"TYPE": "LINE","OBJECT": "ALARM-01","LINE": ["CW-11","ALARM-01","SNS-01","ML-01"],}, {"TYPE": "LINE","OBJECT": "DEP-01","LINE": ["PC-02","EC2-31","NATGW-01", "CM-01", "CB-01", "S3-02", "CD-01", "EC2-11"],}, {"TYPE": "LINE","OBJECT": "DEP-02","LINE": ["CD-01", "EC2-12"],}, {"TYPE": "LINE-GRP","LAVEL": "処理経路","STROKE": "#000080","LINES": ["LINE-A0","LINE-A1","LINE-A2"],}, {"TYPE": "LINE-GRP","LAVEL": "冗長化","STROKE": "#008000","LINES": ["LINE-B1"],}, {"TYPE": "LINE-GRP","LAVEL": "管理経路","STROKE": "#80FF00","LINES": ["LINE-M0","LINE-M1","LINE-M2","LINE-M3"],}, {"TYPE": "LINE-GRP","LAVEL": "ログ収集","STROKE": "#F0F0F9","LINES": ["LOG-01","LOG-02","LOG-03","LOG-04"],}, {"TYPE": "LINE-GRP","LAVEL": "障害通知","STROKE": "#981234","LINES": ["ALARM-01"],}, {"TYPE": "LINE-GRP","LAVEL": "デプロイ","STROKE": "#AABBCC","LINES": ["DEP-01","DEP-02"],}, ]; おわりに デザインの修正や、CIDRや許可した通信ポート設定などの情報も表示するようにしたりと改善したいことはいろいろあります。マウスで作図できるようにするのもいいかもしれません。更新版ができたらまた紹介しようと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript 学習メモ

4/16   本日の学習内容 9時~  ボタンごとに違うテキストが表示されるボタンの仕組みを理解した。 test.html <label>アイスクリームの味: <select class="ice-cream" name="ice-cream"> <option value="">1つ選択してください …</option> <option value="chocolate">チョコレート</option> <option value="sardine">イワシ</option> <option value="vanilla">バニラ</option> </select> </label> <div class="result"></div> test.js document.querySelector('.ice-cream').addEventListener('change', (event) => { document.querySelector('.result').textContent= `You like ${event.target.value}`; }); ・ice-creamの値が変わったらイベントを起こす ・resultのテキストコンテントを'Youlike${event.target.value}'にする。 ・html側で、表示したいvalueの値と、表示したいところにresultの値を設定しているところがポイント 10時~ この記事の執筆 学習サイト探索 11時~ ストップウォッチの作成     htmlはこんな感じ test.html <div id="timer">00:00:000</div> <button id="start">start</button> <button id="stop">stop</button> <button id="reset">reset</button> jsファイルに書く内容をまとめる ・Startを押した時間から今の時間をひいて、それを表示する ・ミリ単位なので、変数を使って分、秒単位に直す 眠たくなったので、ストップウォッチは中止 14時~書籍で学習 15時~ foreach map の学習
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

BitcoinJSでデジタル署名を作成する。

※勉強内容のアウトプットです。 電子署名とは 電子文書上の署名全般を言う。 買い物のお会計の時にタブレットにするサインも電子署名であり、 以下で述べる公開鍵暗号方式を用いたデジタル署名も電子署名の一種である。 デジタル署名とは 公開鍵暗号方式を利用した電子署名である。 デジタル署名によって証明できることは以下のようなことである。 送信されたデータは確かに秘密鍵の持ち主から送信されたものである。 手元にある公開鍵は確かに送信者の秘密鍵に対応する公開鍵である。 送信データが改ざんされていない。 ※注意点としては、以上のことだけではなりすましを完全には防げない。デジタル署名の仕組みは別記事に起こそうと思います。 BitcoinJSを使った署名データの作成 // ライブラリ読み込み const bitcoinjs = require('bitcoinjs-lib') const crypto = require('crypto') const data = '署名対象データ' // 署名対象データをハッシュ化 const dataHash = crypto.createHash('sha256').update(data).digest() console.log('Date Hash: ' + dataHash.toString('hex')) // ECPair生成 const ECPair = bitcoinjs.ECPair.makeRandom({rng: crypto.randomBytes, compressed: false}) // 署名データ作成 const signature = ECPair.sign(dataHash) console.log('Signature: ' + signature.toString('hex')) 署名対象データはそのままだとサイズが大きいことがあるので、署名対象データは通常、事前にハッシュ化する。 今回はSHA256を使用し、32バイトのメッセージダイジェストに対して署名した。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React + react-dropzone: ファイルをページにドラッグ&ドロップする

react-dropzoneとは react-dropzoneは、ローカルのファイルをドラッグ&ドロップやダイアログで選んで扱うためのライブラリです。ファイルをアップロードするユーザーインタフェースなどに使えます。アップデートは小まめで、本稿執筆時のバージョンはv11.3.2です。最小限のコード例はとても短く、APIにはフックも用いられています(サンプル001)。 サンプル001■最小限のコード例 >> CodeSandboxへ インストール コマンドラインツールで、npmやyarnを使ってインストールしてください。 npm install --save react-dropzone yarn add react-dropzone 本稿の作例は、Create React Appのひな形アプリケーションをもとにつくっています。ひな形のつくり方については「Reactアプリケーションのひな形をつくる」をお読みください。 コード例を試す 使い方(Usage)に掲げられているコード例は、わずか20行足らずです。それでも、コードをコピー&ペーストすれば、最小限のユーザーインタフェースができ上がります。 それに少しだけ、スタイルなどの手を加えたのがつぎのコード001です。コードはたしかに短い。でも、ちょっと何やってるかわからないですね。まずは、冒頭のサンプル001のCodeSandboxコード例で、どういう動きになるのかをご覧ください。 コード001■最小限のコード例 src/App.js import { useCallback } from 'react'; import { useDropzone } from 'react-dropzone'; const style = { width: 200, height: 150, border: "1px dotted #888" }; function App() { const onDrop = useCallback((acceptedFiles) => { // Do something with the files console.log('acceptedFiles:', acceptedFiles); }, []); const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop }); return ( <div {...getRootProps()} style={style}> <input {...getInputProps()} /> { isDragActive ? <p>Drop the files here ...</p> : <p>Drag 'n' drop some files here, or click to select files</p> } </div> ); } export default App; フックuseDropzoneに引数として{ onDrop }が与えられています。すると、ページ内の領域(<div>要素)にファイルをドロップしたとき、コンポーネントのonDropに定めたコールバックが呼び出されます。コールバックに渡される引数は、ドロップしたFileオブジェクトが収められたFileListです。ここでは、コンソールに出力して中身を確かめています。 useDropzoneから取り出したisDragActiveは、領域にファイルがドラッグされているかどうかを調べる論理値です。前掲コード001では、ドラッグすると表示されるテキスト(p要素)が切り替わります。 getRootProps()の戻り値をコンソールに出力してみました。中身にはつぎのようなイベントハンドラが含まれています。これらがページの領域に加えられ、その中のひとつonDropがコンポーネントに定めたコールバックを呼び出したのです。 onBlur: ƒ (event) onClick: ƒ (event) onDragEnter: ƒ (event) onDragLeave: ƒ (event) onDragOver: ƒ (event) onDrop: ƒ (event) onFocus: ƒ (event) onKeyDown: ƒ (event) ref: {current: null} tabIndex: 0 getInputProps()の戻り値は、つぎのとおりでした。<input>要素の属性とイベントハンドラが含まれているようです。 accept: undefined autoComplete: "off" multiple: true onChange: ƒ (event) onClick: ƒ (event) ref: {current: null} style: {display: "none"} tabIndex: -1 type: "file" スタイルを定める 前掲コード001(サンプル001)では、ページの領域(<div>要素)のスタイルをオブジェクトにしてReactおなじみのstyleプロパティに与えました。同じことは、getRootProps()の引数オブジェクトにstyleプロパティとして渡しても実現できます。 src/App.js function App() { return ( // <div {...getRootProps()} style={style}> <div {...getRootProps({ style })}> </div> ); } ページ内の領域のスタイルは、ドラッグしたとき動的に変えてみましょう。ドラッグしているかどうかは、isDragActiveで調べられました。領域にドラッグしたときのスタイル(borderDragStyle)には、軽くアニメーション(transition)も加えています。なお、useMemoを用いたメモ化については、「Create React App 入門 08: useMemoフックで無駄な再計算を省く」をお読みください。 src/App.js // import { useCallback } from 'react'; import { useCallback, useMemo } from 'react'; // const style = { const baseStyle = { // border: "1px dotted #888" }; const borderNormalStyle = { border: "1px dotted #888" }; const borderDragStyle = { border: "1px solid #00f", transition: 'border .5s ease-in-out' }; function App() { const style = useMemo(() => ( { ...baseStyle, ...(isDragActive ? borderDragStyle : borderNormalStyle)} ), [isDragActive]); } これで、ファイルをドラッグすると領域の枠線スタイルが動的に変わります(サンプル002)。 サンプル002■ドラッグした領域のスタイルが動的に変わる >> CodeSandboxへ ダイアログはボタンで開く ドラッグ&ドロップはいいとして、ダイアログを開くのはボタンの方がわかりやすそうです。useDropzone()の引数に{ noClick: true }を渡すことにより領域クリックでダイアログが開くのは止め、開くための関数openを取り出します。そして、ボタン(<button>要素)のonClickハンドラからopenを呼び出すようにしたのがつぎのコードです。 src/App.js function App() { // const { getRootProps, getInputProps, isDragActive } = useDropzone({ const { getRootProps, getInputProps, isDragActive, open } = useDropzone({ noClick: true }); return ( <div {...getRootProps({ style })}> <button type="button" onClick={open}>Select files</button> </div> ); } ドロップしたファイルの情報を得る ドロップしたファイルのFileListオブジェクトは、useDropzoneフックからacceptedFilesとして得られます。この中から取り出したFileオブジェクトそれぞれのファイル名(path)とサイズ(size)をページに差し込んだのがつぎのコードです。 src/App.js function App() { // const { getRootProps, getInputProps, isDragActive, open } = useDropzone({ const { getRootProps, getInputProps, isDragActive, open, acceptedFiles } = useDropzone({ }); const files = useMemo(() => acceptedFiles.map(file => ( <li key={file.path}> {file.path} - {file.size} bytes </li> ) ), [acceptedFiles]); return ( <div className="container"> <div {...getRootProps({ style })}> </div> <aside> <h4>Files</h4> <ul>{files}</ul> </aside> </div> ); } 書き上がったルートモジュールsrc/App.jsの記述全体をまとめたのが、以下のコード002です。コードの動きは、CodeSandboxに掲げたサンプル003でお確かめください。 サンプル003■ドロップしたファイルの情報がページに示される >> CodeSandboxへ コード002■領域のスタイルやボタンを加えたファイル選択のインタフェース src/App.js import { useCallback, useMemo } from 'react'; import { useDropzone } from 'react-dropzone'; const baseStyle = { display: "flex", flexDirection: "column", width: 200, height: 150, }; const borderNormalStyle = { border: "1px dotted #888" }; const borderDragStyle = { border: "1px solid #00f", transition: 'border .5s ease-in-out' }; function App() { const onDrop = useCallback((acceptedFiles) => { // Do something with the files console.log('acceptedFiles:', acceptedFiles); }, []); const { getRootProps, getInputProps, isDragActive, open, acceptedFiles } = useDropzone({ onDrop, noClick: true }); const style = useMemo(() => ( { ...baseStyle, ...(isDragActive ? borderDragStyle : borderNormalStyle)} ), [isDragActive]); const files = useMemo(() => acceptedFiles.map((file) => ( <li key={file.path}> {file.path} - {file.size} bytes </li> ) ), [acceptedFiles]); return ( <div className="container"> <div {...getRootProps({ style })}> <input {...getInputProps()} /> { isDragActive ? <p>Drop the files here ...</p> : <p>Drag 'n' drop some files here</p> } <button type="button" onClick={open} className="btn btn-primary align-self-center">Select files</button> </div> <aside className="mt-1"> <h4 className="mb-0">Files</h4> <ul>{files}</ul> </aside> </div> ); } export default App;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AR.jsを段階的に学習できるサイトを作成【ソースコードも公開】

概要 AR.js を使ってブラウザからカメラで AR のコンテンツを表示します 公式の Hello World から始まりカスタムのマーカーを使用するなど段階的につくっていきます このサイトでは段階的に学んだことを1つずつサイトとして作って記録しました 実際に作ったモノはスマホで AR を楽しむことができます 構成 静的なサイトで構成されています コンテンツの内容はマークダウンのテキストから読み込むようにしています それ以外は AR.js のコンテンツごとにページを作っています Vue.jsなどのフレームワークを使用するとAR.jsの読み取れないときの原因特定が難しいためシンプルに作っています ソースコード glitch にて公開しています https://glitch.com/edit/#!/lesson-ar-js スマホで AR.js の AR を楽しむために・・・ 準備 まずはパソコンで開きながらスマホでもこのサイトを開いて準備します トップページはほぼこのQiitaの記事の内容とほぼ同じモノになります すでにこの記事をパソコンで開いている場合はスマホでQRCodeのサイトを開いてください スマホで開く場合は以下の QRCode をご利用ください AR のサイトを開いたときの注意 このサイトの AR のサイトを開く場合は Chrome の利用を推奨します カメラの許可を求められますので必ず許可してください AR.js Lesson Start 01: Hello World スマホで特定のマーカーを写すと AR のコンテンツを表示するモノをつくります 公式の Marker Based Example を使ってみます https://ar-js-org.github.io/AR.js-Docs/ Marker Based Example のソースコードを利用します パソコンで移した画像で AR を楽しむためにpositionとrotationを以下のように設定します position="0 0 1" rotation="-90 0 0" ソースコード Glitch を開いてコードを確認できます (01-ar-hello-world.html) スマホでの動作 スマホでリンクを開いて AR の動作を確認できます (01-ar-hello-world.html) 以下の QRCode をスマホのカメラで写すと AR のオブジェクト(恐竜)が表示されます 3D モデルの読み込みが上手くいかない場合があるので表示されない場合は Lesson2 で確認してください 02: 任意の 3D モデルファイルを読み込む Hello World では公式の恐竜の 3D モデルを表示していました 3D のモデルファイルを任意のモノを読み込ませ表示させてみます 使用する 3D モデルは以下を利用させていただきました https://sketchfab.com/3d-models/flash-cube-5b5ffe7d529e43ebaa8980c0f36eca87 注意点としては 3D モデルが読み込めず表示できないことがあります 最初はモデルの問題かソース側の問題か切り分けが難しいので慣れてから 3D モデルにチャレンジするのがオススメです ソースコード Glitch を開いてコードを確認できます (02-ar-custom-model.html) スマホでの動作 スマホでリンクを開いて AR の動作を確認できます 以下の QRCode をスマホのカメラで写すと AR のオブジェクト(キューブ)が表示されます 03: 自分で作成したマーカーを認識させる Hello World ではプリセットのマーカーで AR コンテンツを表示していました 今回は自分で用意した画像をマーカーにします 以下のサイトでマーカーの設定ファイル(.patt)とマーカーの画像ファイルを生成します AR.js Marker Training マーカーを作るポイントとしては背景は少し灰色にしてシンプルな模様にすると読み取りやすいです 色も多くなると読み取りづらいので灰色背景に黒い簡単な図形を描くことをオススメします ソースコード Glitch を開いてコードを確認できます (03-ar-custom-marker.html) スマホでの動作 スマホでリンクを開いて AR の動作を確認できます 今回は自作したマーカーを読み取ります 以下の QRCode をスマホのカメラで写すと AR のオブジェクト(キューブ)が表示されます 04: AR で任意の画像を表示する 3D モデルだと自作しづらいので AR で画像を表示させてみます カワイイ女の子の画像を AR で飛び出させてみます カワイイ女の子の画像の素材は以下のサイトから利用させていただきました http://iwayu2.blog.fc2.com/blog-entry-9.html ソースコード Glitch を開いてコードを確認できます (04-ar-display-image.html) スマホでの動作 スマホでリンクを開いて AR の動作を確認できます 今回は自作したマーカーを読み取ります 以下の QRCode をスマホのカメラで写すと AR のオブジェクト(かわいいメイドさん)が表示されます 05: AR で任意の Gif 画像を表示する AR.js は aframe を使用しています aframe の拡張ライブラリも使用できます 今回は Gif 画像を使用するために aframe-gif-shader を使用します Gif 画像は以下のサイトから利用させていただきました Cliply.co ソースコード Glitch を開いてコードを確認できます (05-ar-display-gif.html) スマホでの動作 スマホでリンクを開いて AR の動作を確認できます 今回は自作したマーカーを読み取ります 以下の QRCode をスマホのカメラで写すと AR のオブジェクト(動いてキラキラする何か)が表示されます 06: ARで複数のマーカーを設定する これまでの応用として複数の任意のマーカーを設定します それぞれのマーカーから異なるカワイイ女の子の画像を表示します マーカーさえ準備できればハーレムにできます ソースコード Glitch を開いてコードを確認できます (06-ar-multi-marker.html) スマホでの動作 スマホでリンクを開いて AR の動作を確認できます 今回は自作したマーカーを読み取ります 以下の QRCode をスマホのカメラで写すと AR のオブジェクト(かわいいメイドさん2人)が表示されます ふりかえり 初心者にもチャレンジしやすいかもしれない 段階的に学ぶように作ったため初心者にも学習しやすいと思います ARという実際にスマホで確認しながら楽しめるので取っつきやすいかもしれません ハマったときがキツイかも AR.jsでキツイのはモデルやパターンの読み込みがされないときで原因の特定がしづらいです デバッグモードもありますがそれでも特定するのに工夫が必要そうです 今回はできるだけ切り分けしやすいように静的なサイトで構築しました 次やりたいこと AR.jsでマーカーの記述のみ違うので変更箇所だけHTMLレンダリングで変えるようできればよさそうです ExpressやReactなどを採用してマーカーの記述部分のみ返すようにしてあげればイイかと
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ExpressとMongoDBで即席RestAPI

やること Node.jsのMVCフレームワークExpressとNoSQLのMongoDBを使って、ユーザ情報の登録・変更・削除ができる簡単なRestAPIを作る。 リクエストやレスポンスはjsonで(NoSQLとの相性抜群)。 MongoDBはDockerコンテナ上で起動させる。 超適当ですが全体像はこんな感じ。 準備 HomebrewとNode.jsを入れておく。 Docker上でMongoDB起動 MongoDBをDockerコンテナ上で起動する。 直に入れてもいいが、せっかくなのでDocker使ってみる。 # Dockerインストール $ brew install docker --cask # MongoDBイメージ取得 $ docker pull mongo # コンテナを作成して起動 # ホストOS側は27018ポート、コンテナ側は27017ポート(MongoDBのデフォルト)を使用 $ docker run -p 27018:27017 --name mongo-test -d mongo Node.jsプロジェクト作成 ベースとなるプロジェクトを作って環境構築する。 Docker起動中かと思うので、別のターミナルを起動する。 # ディレクトリ作成 $ mkdir test-app # 初期化処理(問合せ無しでpackage.json作成) $ npm init -y # 必要nodeモジュール(expressとmongoDB操作用モジュール)をインストール $ npm install express mongoose Model作成 スキーマとなるUserModelを定義する。 # ディレクトリ作成 $ mkdir server $ cd server $ mkdir models # Model定義JavaScript作成 $ touch models/user.js user.jsをVSCodeとかで開いて編集する。 肝心のModelは今回、ユニークなID、名前、年齢の3つのフィールドを持たせる。 user.js const mongoose = require('mongoose'); const Schema = mongoose.Schema; const userSchema = new Schema({ id: { type: String, required: true, unique: true }, name: { type: String }, age: { type: Number} }); module.exports = mongoose.model('users', userSchema); Router&Controller作成 Modelを利用して実際にHTTPリクエストが来た際にどう振舞うかを定義するRouterと、全体を制御するControllerを定義する。 今回は小規模なので、分けずにまとめてserver.jsとする。 # ディレクトリ移動 $ cd .. # Router&Controller定義JavaScript作成 $ touch server.js server.jsをVSCodeとかで開いて編集する。 httpリクエストは3001番ポートで待ち受けるとする。 コードを少なくしたかったので、登録以外はエラー処理を端折ってます。 server.js const express = require('express'); const mongoose = require('mongoose'); const User = require('./models/user'); // DB接続 mongoose.connect('mongodb://localhost:27018/sampledb'); const app = express(); app.use(express.json({extended: true})); // ユーザ情報登録 app.post('/users', (req, res) => { const user = new User(req.body); user.save((err, addedUser) => { if (err) return res.status(400).json({ errorMessage: 'failed to add the user.' }); res.send(addedUser); }); }); // ユーザ情報取得(全件) app.get('/users', (req, res) => { User.find({}, (err, users) => { res.send(users); }); }); // ユーザ情報取得(ID指定) app.get('/users/:id', (req, res) => { const params = req.params.id; User.find({id: params}, (err, user) => { res.send(user); }); }); // ユーザ情報削除(全件) app.delete('/users', (req, res) => { User.remove({}, err => { res.send(true); }); }); // ユーザ情報削除(ID指定) app.delete('/users/:id', (req, res) => { const params = req.params.id; User.remove({id: params}, (err, deletedUser) => { res.send(deletedUser); }); }); // ポート3001番でlisten app.listen(3001, () => { console.log(`Server up on 3001`); }); サーバ起動 $ node server.js 動作確認(curlコマンド) curlコマンドでユーザ情報の登録・取得・削除をやってみる。 _idと__vはMongoDBが自動付与するフィールドで、それぞれオブジェクトID、バージョン管理用として使われる。 # Allen登録 $ curl -X POST -H 'Content-Type:application/json' -d '{"id":"001","name":"Allen","age":20}' http://localhost:3001/users # Allen登録結果 {"_id":"607bce198776e444bd55422b","id":"001","name":"Allen","age":20,"__v":0}% # Yuki登録 $ curl -X POST -H 'Content-Type:application/json' -d '{"id":"002","name":"Yuki","age":26}' http://localhost:3001/users # Yuki登録結果 {"_id":"607c3bf502ac68477a41ee10","id":"002","name":"Yuki","age":26,"__v":0}% # 全件取得 $ curl -X GET http://localhost:3001/users # 取得結果 [{"_id":"607bce198776e444bd55422b","id":"001","name":"Allen","age":20,"__v":0},{"_id":"607c3bf502ac68477a41ee10","id":"002","name":"Yuki","age":26,"__v":0}]% # IDを指定して取得 $ curl -X GET http://localhost:3001/users/001 # 取得結果 [{"_id":"607bce198776e444bd55422b","id":"001","name":"Allen","age":20,"__v":0}]% # IDを指定して削除 $ curl -X DELETE http://localhost:3001/users/001 # 削除結果 {"n":1,"ok":1,"deletedCount":1}% # 削除確認のため全件取得 $ curl -X GET http://localhost:3001/users # 残ってるのはYukiだけ [{"_id":"607c3bf502ac68477a41ee10","id":"002","name":"Yuki","age":26,"__v":0}]% # 全件削除 $ curl -X DELETE http://localhost:3001/users # 削除結果 true% # 削除確認のため全件取得 $ curl -X GET http://localhost:3001/users # 何も残っていない []% 動作確認(API Tester) curlコマンドだと視覚的に分かりづらいので、Chrome拡張のTalend API Testerを使って、視覚的に確認する。 こちらでは登録と全件取得だけ確認してみる。 まずは登録。 この後、同じ要領でYukiも登録したとして、次は全件取得。 こんな感じで確認できた。 もちろん、METHODを変えることでDELETEも可能。 これで一通り、DBを操作する簡単なRestAPIを作成できた。 応用としてやってみたいこと フロントエンドをReactとかで作りたい。 今回DockerはMongoDBでしか使わなかったが、Node.jsそのものからDocker上で環境構築したい。VSCodeでDockerコンテナ上のワークスペースも編集できるらしい(参考)。 TypeScriptで書けるようにしたい(参考)。 参考サイト 初級JavascriptフルスタックエンジニアのためのReactとExpressの同時開発チュートリアル MongoDB(Mongoose)上級者への道 curlコマンドを使った操作
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む