20211007のJavaScriptに関する記事は19件です。

AngularアプリでBootStrapを使ってみる

BootStrapとは BootStrapはCSSのフレームワークです。BootStrapを使うとデザイナーでなくても簡単にそれっぽい見た目の画面を作成することができます。 また、Sassが採用されているため変数を使う・CSSクラスを継承して新しいCSSクラスを作るなど、プログラミング言語っぽくCSSを記載することができます。 Angularを使用していない場合でももちろんBootStrapは使えますが、jQuery等の関連ライブラリを読み込む必要があります。 また、Sassは直接ブラウザで読み込むことができないためCSSファイルにコンパイルする必要があります。 Angularを使っている場合はそれらの面倒をすべてAngularが補ってくれるため、比較的簡単にBootStrapの恩恵を受けることができるみたいです。 見た目には特にこだわりはないけどそれなりに見やすい画面を作成したいといった場合に使えそうですね。 bootstrap(ng-bootstrap)を使ってみる こちらを参考にしました。 bootstrapの変数の書き換え方 bootstrapの主な変数 SCSSの記法 インストールは以下のコマンドで行う(Angular9.x以降の場合) ng add @ng-bootstrap/ng-bootstrap node_modulesフォルダ下にbootstrapがインストールされて、Angularアプリ内での利用が可能になる。(node_modules\bootstrap) app.module.tsのimports配列にNgbModuleが追加される。 app.module.ts ... import { ParamComponent } from './param/param.component'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; @NgModule({ declarations: [ AppComponent, EventComponent, ... ], imports: [ ... NgbModule ], ... }) export class AppModule { } また、angular.jsonではAngularアプリ全体の設定(ビルドの設定)が記載されている。 以下のようにアプリで共通のCSSファイル(src/styles.scss)がAngularではデフォルトで用意されているが、bootstrapをインストールすると共通のCSSファイルにbootstrapがimportされる。 angular.json "styles": [ "src/styles.scss" ], styles.scss /* You can add global styles to this file, and also import other style files */ /* Importing Bootstrap SCSS file. */ @import '~bootstrap/scss/bootstrap'; //bootstrapがimportされている この時点でAngularアプリにはbootstrapが適用されていて、見た目が変わっている。 bootstrapの設定を書き換えたい場合は、SCSSの変数を書き換えればいい。 bootstrap全体で使われている変数はnode_modules\bootstrap\scss\_variables.scssに記載されており、 変数の後ろに!default;が記載されている。 styles.scssでbootstrapを読み込む前に変数を定義することで、上書きすることができる。 _variables.scss $font-size-base: 1rem !default; // Assumes the browser default, typically `16px` $font-size-lg: $font-size-base * 1.25 !default; $font-size-sm: $font-size-base * .875 !default; ↓変数を上書きして独自の見た目に書き換える styles.scss /* You can add global styles to this file, and also import other style files */ $theme-colors: ( //テーマの色を変えている primary: rgb(49, 190, 148) ); $font-size-base: 2rem; //文字のサイズをデフォルトの1remから2remに変更 /* Importing Bootstrap SCSS file. */ @import '~bootstrap/scss/bootstrap'; 結果 文字のサイズが2倍になり、リンク文字の色が変わっている
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript入門(オブジェクト②)

概要 ...JavaScript入門(オブジェクト①)の続きです。 JavaScriptを学習、理解を深めるため「JavaScript Primer 迷わないための入門書」を読み、 理解した内容等を記載していく。 JavaScript入門一覧に他の記事をまとめています。 ※下記「動作環境情報」を参考にし、開発者コンソールで結果を確認してください 動作環境情報はコチラ 【環境】 PC:Mac エディタ:Visual Studio Code プラグイン:Live ServerというVisual Studio Codeのプラグインを使用し、サーバ環境を用意。 開発者コンソール:GoogleChrome 【登場ファイル】 object.js:オブジェクトを作成、定義するファイル index.html:「object.js」を使用、および開発者コンソールで確認する際に開くファイル 【★ポイント】 index.htmlのheadタグ内に<script src="object.js" type="module"></script>のように記述する index.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>JavaScript入門</title> <script src="object.js" type="module"></script> </head> <body> </body> </html> ※最終的に以下のような構成になる。 (任意の場所に「js」フォルダを作成および、フォルダに上記2ファイルを作成・配置する) プロパティの追加 オブジェクトは、プロパティの追加などオブジェクト作成後も値を変更できる。 プロパティの追加方法は、オブジェクト.プロパティ名 = 値;と追加したいプロパティ名に値を代入する。 オブジェクトに指定したプロパティが存在しない場合、自動的にプロパティが作成される。 ドット記法、ブラケット記法のどちらでも追加可能。 プロパティ名を変数、変数の命名規則には適合しない文字列、Symbolを指定する際は、ブラケット記法を使用する。 object.js // 空のオブジェクト「obj」を作成 const obj = {}; // nameプロパティをドット記法で追加して値を代入 obj.name = "Taro"; // 自動的にnameプロパティが作成される console.log(obj.name); // => Taro // 変数「key」に変数の命名規則には適合しないプロパティ名「e-mail」を代入 const key = "e-mail"; // 変数「key」をブラケット記法で指定し、値を代入 obj[key] = "sample@mail.com;" // 変数「key」に代入したe-mailプロパティが作成、および指定した値が設定される console.log(obj[key]); // => sample@mail.com Tips① ブラケット記法を用いたプロパティの定義は、オブジェクトリテラル{ }内でも可能。 その際、プロパティ名は「Computed property names」と呼ばれる。 「Computed property names」はES2015から導入された記法 object.js // 変数「key」に変数の命名規則には適合しないプロパティ名「e-mail」を代入 const key = "e-mail"; // 空のオブジェクト「obj」を作成 const obj = { [key]: "sample@mail.com" //  =>  変数「key」が評価されプロパティ名「e-mail」の設定、および値が設定される }; // 変数「key」をブラケット記法で指定 console.log(obj[key]); // => sample@mail.com 注意点 冒頭で記載した通り、JavaScriptのオブジェクトはオブジェクト作成後にプロパティを変更可能という特性を持つ。 オブジェクトの作成時以外でプロパティを追加すると、オブジェクトがどんなプロパティを持っているか不明確になりやすい。 できる限りオブジェクト作成後に新しいプロパティは追加せず、オブジェクトの作成時のオブジェクトリテラルの中でプロパティを定義することが推奨されている。 プロパティの削除 オブジェクトのプロパティを削除する場合は、delete演算子を使用する。 ドット記法の場合delete オブジェクト.プロパティ名;で削除可能。 ブラケット記法の場合delete オブジェクト[プロパティ名];で削除可能。 object.js // 3つのプロパティを持つ、オブジェクト「obj」を作成 const obj = { "name": "Taro", "age": 24, "e_mail": "main@mail.com", }; // オブジェクト「obj」からプロパティ「age」を削除 delete obj.age; // オブジェクト「obj」からプロパティ「e_mail」を削除 delete obj["e_mail"]; console.log(obj); // => {name: 'Taro'} プロパティの存在を確認する JavaScriptでは、存在しないプロパティにアクセスした場合、例外ではなくundefinedを返す。 あるオブジェクトがあるプロパティを持っているかを確認する方法として、次の3つがある。 undefinedとの比較 in演算子 hasOwnPropertyメソッド undefinedとの比較 存在しないプロパティにアクセスした場合undefinedが返されるため、プロパティにアクセスすると存在を確認できる。 しかし、仮にプロパティの値がundefinedだった場合(プロパティ名: 値の値がundefined)区別ができない。 そのため、プロパティが存在するかの判定にはin演算子かhasOwnPropertyメソッドを使用する。 プロパティの値が存在するかどうかの判定をしたい場合には有効かもしれない。 object.js // オブジェクト「obj」を作成 const obj = { "name": "Taro", "age": undefined }; // 事前に確認してみる console.log(obj.name); // => Taro console.log(obj.age); // => undefined // オブジェクト「obj」にプロパティ「name」が存在するか確認 if (obj.name !== undefined) { // undefinedじゃなければconsole.logが実行される console.log("undefinedではない"); // => undefinedではない } // オブジェクト「obj」にプロパティ「name」が存在するか確認 if (obj.age !== undefined) { // undefinedじゃなければconsole.logが実行される console.log("undefinedではない"); } // => プロパティageが存在しているが何も表示されない = プロパティの値がundefinedのためif文内の処理が実行されていない in演算子 "プロパティ名" in オブジェクト;で指定したオブジェクトにプロパティが存在するかを判定できる。 in演算子はプロパティの値は関係なく、プロパティが存在するかの判定を行う。 tureもしくはfalseが返される。 object.js // オブジェクト「obj」を作成 const obj = { "name": "Taro", "age": undefined }; // "プロパティ名" in オブジェクト;でプロパティが存在するかを判定 console.log("name" in obj); // => true console.log("age" in obj); // => 値はundefinedだがプロパティが存在するためtrue console.log("city" in obj); // => プロパティが存在しないためfalse hasOwnPropertyメソッド オブジェクト.hasOwnProperty("プロパティ名");でプロパティが存在するかを判定できる。 hasOwnPropertyメソッドの引数には、存在を判定したいプロパティ名を指定する。 hasOwnPropertyメソッドもプロパティの値は関係なく、プロパティが存在するかの判定を行う。 tureもしくはfalseが返される。 object.js // オブジェクト「obj」を作成 const obj = { "name": "Taro", "age": undefined }; // オブジェクト.hasOwnProperty("プロパティ名");でプロパティが存在するかを判定 console.log(obj.hasOwnProperty("name")); // => true console.log(obj.hasOwnProperty("age")); // => 値はundefinedだがプロパティが存在するためtrue console.log(obj.hasOwnProperty("city")); // => プロパティが存在しないためfalse Tips② ES2020ではOptional chaining演算子(?.)が導入された。 ネストしたプロパティの存在確認とアクセスを簡単に行えるのが特徴。 (プロパティを持つオブジェクトの場合)オブジェクト?.プロパティ名と指定しアクセスする。 左辺のアクセス対象がnullまたはundefinedの場合は、それ以上評価せずundefinedを返す。 プロパティが存在する場合は、プロパティの評価結果を返す。 object.js // オブジェクト「obj」を作成 const obj = { "team_a": { "name": "Taro", "age": undefined, "e_mail": "main@mail.com" } }; // ドット記法やブラケット記法の場合、ネストしたプロパティのいずれかが「undefined」の場合、例外が発生してしまう console.log(obj.team_b.name); // => Uncaught TypeError: Cannot read properties of undefined (reading 'name') console.log(obj["team_b"]["name"]); // => Uncaught TypeError: Cannot read properties of undefined (reading 'name') // オブジェクト.hasOwnProperty("プロパティ名");でプロパティが存在するかを判定 console.log(obj?.team_a?.name); // => Taro console.log(obj?.team_a?.age); // => undefined console.log(obj?.team_a?.["e_mail"]); // => main @mail.com(ブラケット記法との組み合わせも可能) console.log(obj?.team_b?.name); // => 例外の発生、「team_b」以降の評価はされず「team_b」の評価結果「undefined」が返される toStringメソッド toStringメソッドはオブジェクト自身を文字列化するメソッド。 Stringコンストラクタ関数でこのtoStringメソッドが呼び出されている。 オブジェクト.toString();でオブジェクトを文字列化可能。 object.js // オブジェクト「obj」を作成 const obj = { "team_a": { "name": "Taro", "age": undefined, "e_mail": "main@mail.com" } }; // オブジェクト「obj」をtoStringメソッドで文字列化する console.log(obj.toString()); // => オブジェクトを文字列化すると[object Object]という文字列になる オブジェクトの静的メソッド(スタティックメソッド) オブジェクトの静的メソッドとは、インスタンスの元となる「Object」そのものから呼び出せるメソッドのことである オブジェクトの列挙やマージなどオブジェクトを操作する静的メソッドが存在する。 オブジェクトの列挙 オブジェクトのプロパティを列挙する3つの以下の静的メソッドが存在する。 Object.keysメソッド(オブジェクトのプロパティ名を配列で返す) Object.valuesメソッド(オブジェクトの値を配列で返す) ※ES2017から Object.entriesメソッド(オブジェクトのプロパティ名と値の配列を返す) ※ES2017から object.js // オブジェクト「obj」を作成 const obj = { "name": "Taro", "age": undefined, "e_mail": "main@mail.com" }; // Object.keys(オブジェクト);でプロパティ名を配列で返す console.log(Object.keys(obj)); // => (3) ['name', 'age', 'e_mail'] // Object.values(オブジェクト);でオブジェクトの値を配列で返す console.log(Object.values(obj)); // => (3) ['Taro', undefined, 'main@mail.com'] // Object.entries(オブジェクト);でオブジェクトのプロパティ名と値の配列を返す console.log(Object.entries(obj)); // => [Array(2), Array(2), Array(2)] // 0: (2)['name', 'Taro'] // 1: (2) ['age', undefined] // 2: (2) ['e_mail', 'main@mail.com'] ... オブジェクトのマージと複製 Objectassginメソッドは、あるオブジェクトを別のオブジェクトに代入することが可能。 オブジェクトの複製やマージを行うことが可能。 Object.assgin(コピー先オブジェクト, コピー元オブジェクト1, コピー元オブジェクト2..)と定義し「コピー先オブジェクト」が返される。 既存のオブジェクトには影響を与えずマージしたオブジェクトを作ることができるため、第一引数(上記ではコピー先オブジェクト)には空のオブジェクトを指定することが典型的な方法とされている。 プロパティ名が重複した場合は、後ろのオブジェクトのプロパティにより上書きされることに注意。 ES2015から使用可能。 object.js // オブジェクト「returnObj」を作成 const returnObj = {}; const objA = { "name": "Hanako", "age": 22, "e_mail": "main@mail.com" }; const objB = { "name": "Eita", "age": 28 }; // 結果的にobjAを複製した形でオブジェクト「returnObj」が返される console.log(Object.assign(returnObj, objA)); // => {name: 'Hanako', age: 22, e_mail: 'main@mail.com'} // objAとobjBではプロパティ「name」と「age」が重複しているためobjAのプロパティの値が上書きされている console.log(Object.assign(returnObj, objA, objB)); // => {name: 'Eita', age: 28, e_mail: 'main@mail.com'} Tips③ ES2018からオブジェクトのspread構文(...)が追加された。 ES2015で配列の要素を展開するspread構文はサポートされていたが、オブジェクトに対してはES2018からサポートされた。 新たにオブジェクトを作成するオブジェクトリテラル({ })の中に指定し、使用する。 プロパティ名が重複した場合は、後ろのオブジェクトのプロパティにより上書きされることに注意。 object.js const objA = { "name": "Hanako", "age": 22, "e_mail": "main@mail.com" }; const objB = { "name": "Eita", "age": 28 }; // spread構文でマージするオブジェクトを指定 const returnObj = { ...objA, ...objB }; // マージしたオブジェクトを確認してみる console.log(returnObj); // => {name: 'Eita', age: 28, e_mail: 'main@mail.com'}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Nuxt/FirebaseのGoogleログイン時に毎回アカウント選択を表示する

生きてます。 Webアプリケーションにつきもののユーザ管理ですが、APIベースのアプリだと非常に面倒です。出来るだけ個人情報は自前で持ちたくないですし、大体のケースにおいてここに労力をかけることが本題ではないと思います。今回は自主的に夜な夜な開発していたNuxt.js製社内用Webアプリケーションで、尚更そこに手間をかけたくなかったので、Firebaseを使ってちゃちゃっと済ませます。 といっても今回は表題の件がメインなので、それまでの過程についてはかなり端折ります。一応Nuxtは使って入るものの、主題に関しては特に関係なく流用出来るんじゃないかと思います。 導入 npm install firebase@8.10.0 npm install @nuxtjs/firebase パッケージマネージャで入れます。 留意点としてはfirebase のメジャーバージョンが上がったことに伴い、最新版の9.xに@nuxtjs/firebaseが2021/10/08現在では対応していないようで、npm install firebaseで最新版を入れてるとこけるようです。(私のときは、firebaseのアプデ3日後のことだったので、地味にハマってました) 将来的には対応されると思いますが、取り急ぎは8系の最終リリース版を指定して入れました。手元ではこれで問題なく動いていますので、これでよしとします。 実装 とりあえずちゃちゃっと実装します。 サインイン Googleアカウントでのログインのみを想定しているので、@clickを適当なボタンに張ってplugin化したサインイン操作の関数を実行します。 plugins/auth.js import firebase from 'firebase'; export const UserLogin = { provider: new firebase.auth.GoogleAuthProvider(), login: () => { firebase .auth() .signInWithPopup(UserLogin.provider) .then(res => { console.log(res); }) .catch(error => { console.log(error); }); }, これでサインインが実装完了です。別タブが開いてGoogleアカウント選択画面が表示され、選択するとアプリにリダイレクトされてくると思います。あとは@nuxtjs/firebaseの機構に沿って、サインイン時に更新されたstoreのステートを使ってよしなにしますが、ここでは割愛します。 サインアウト plugins/auth.js logout() { firebase .auth() .signOut() .then(() => {}) .catch(error => { console.log(error); }); }, サインアウトも同様に実装します。涙が出るほど簡単ですが、アプリケーション側での後処理を忘れると残念なことになるので各自ちゃんとしておきましょう。 一度ログアウトして、再度サインインする すると、Googleアカウントの選択をすっ飛ばしてサインインが完了します。 便利だけど… 毎回選択する手間を考えたらこれはこれで親切設計なんですが、うっかりサインインするGoogleアカウントを間違えた時など、Googleアカウントを選択し直したいケースもあると思います。 公式を色々読み直してみたんですが、求める答えが得られませんでした。半分くらいアカウントを間違えるほうが悪いという強い心で生きて行こうと思いましたが、どうにも気持ち悪さが拭えなかったので色々調べたんですが、再ログインに言及されている記事がかなり少なく非常に苦労しました。(AndroidやFirebase CLIについての言及は見つかったのですが…) そこまで特殊な事象じゃないとは思うんですが、皆横着しないでちゃんと実装してるってことなんでしょうか? 答え ともあれブチキレつつ半分あきらめながら調べまくり、結果としては以下で回答が得られました。 はい神 Googleのドキュメントまでは見に行ってなかった…。firebase.auth.GoogleAuthProvider()インスタンスをちゃんと掘らなかった自分の落ち度でした。 ログイン実装の修正 というわけで、上記の通りパラメータを設定して、再認証時のアカウント選択を明示的に指示します。 plugins/auth.js login: () => { + UserLogin.provider.setCustomParameters({ + prompt: 'select_account', + }); firebase .auth() .signInWithPopup(UserLogin.provider) .then(res => { console.log(res); }) .catch(error => { console.log(error); }); }, これで初回以降のログイン時でも、Googleアカウントの選択画面が表示されますので、うっかり初回サインイン時のアカウントを間違えたときもこれで安心です。 そもそもこっちが標準じゃないのか?という気持ちもありますが、一人1アカウントという考え方が根底にあるのだとしたらそりゃまぁ再認証時の選択なんて煩わしいだけか、とも思うのでこういう作りなのでしょう。 というわけで認証処理がようやく片付いたので本題の実装に入れたのでした。めでたし。 本日は以上です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

javascriptで多次元配列の重複を削除

1・名前の重複を削除したい 2・名前が重複していた場合、moneyが2000のデータだけ欲しい process.stdin.resume(); process.stdin.setEncoding('utf8'); var arr = [ {id:1,name:"花子",money:5000}, {id:2,name:"江藤",money:4000}, {id:3,name:"山田",money:3000}, {id:4,name:"山田",money:2000}, {id:5,name:"山田",money:8888}, {id:6,name:"山田",money:9999}, {id:7,name:"松井",money:2000}, {id:8,name:"松井",money:2093}, {id:9,name:"松井",money:2090}, {id:10,name:"大川",money:1000} ]; var check = []; arr.forEach(function(list) { check[list.name] = (list.name in check) ? true : false; }); var filtered = arr.filter(function (e) { return false === check[ e.name ] || true === check[ e.name ] && e.money === 2000; } ); console.log( filtered ); 結果 [ { id: 1, name: '花子', money: 5000 }, { id: 2, name: '江藤', money: 4000 }, { id: 4, name: '山田', money: 2000 }, { id: 7, name: '松井', money: 2000 }, { id: 10, name: '大川', money: 1000 } ]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue.js】scrollTopが効かない時、代わりに使えるscrollIntoView

はじめに 実現したいこと あるボタンを押すと、画面一番上に移動するような動作(画面遷移はしない) やり方を探ってみる 「Vue.js トップに戻るボタン」とかで検索すると、ほとんどが?のようなwindow.scrollToを使ったようなやり方でした。 <template> <div> <button @click="moveToTop">一番上に</button> </div> </template> <script> moveToTop() { window.scrollTo({ top: 0, behavior: 'smooth' }); } </script> なぜかwindow.scrollTo反応しない が!!! なんでやろう??僕が実装したいVue.jsのプロジェクトでは、window.scrollToが全く反応してくれなかったのですね。。。 ?これも試しました。。 ?の例で使われてるwindow.scrollByも全く反応がなかったので、、、windowの時点で何かしらの理由があって無効になっていると仮定しました scrollIntoViewを代わりに使いましょう! 色々と調べまくってる時でに運命的な出会いをしました。。 ここでscrollIntoViewを知りました! そこからどうやって実装したか? 一番上の階層のvueファイルでid="page-top"を指定 Layout.vue <template> <div> <div class="page-content-wrapper" id="page-top"> <Sample></Sample> </div> <FooterVue></FooterVue> </div> </template> <script> import FooterVue from './_partials/Footer'; import Sample from './_partials/Sample'; export default { components: { 'FooterVue': FooterVue, 'Sample': Sample }, } // 省略 </script> コンポーネントファイルでmethodを入れる sample.vue <template> <div> <button @click="moveToTop"> 次へ </button> </div> </template> <script> export default { methods: { moveToTop() { document.getElementById('page-top').scrollIntoView({ behavior: 'smooth', block: 'start' }); } } // 省略 </script> これでwindow.scrollToと同じようなことが実現できました わーい!! 注意点 scrollIntoViewはoffsetが使えません(もうちょっと移動した時の位置を上にしたい!とかが無理っぽい) まぁそれでも一番上に移動させるだけなら全く問題はありません。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue.js】scrollToが効かない時、代わりに使えるscrollIntoView

はじめに 実現したいこと あるボタンを押すと、画面一番上に移動するような動作(画面遷移はしない) やり方を探ってみる 「Vue.js トップに戻るボタン」とかで検索すると、ほとんどが?のようなwindow.scrollToを使ったようなやり方でした。 <template> <div> <button @click="moveToTop">一番上に</button> </div> </template> <script> moveToTop() { window.scrollTo({ top: 0, behavior: 'smooth' }); } </script> なぜかwindow.scrollTo反応しない が!!! なんでやろう??僕が実装したいVue.jsのプロジェクトでは、window.scrollToが全く反応してくれなかったのですね。。。 ?これも試しました。。 ?の例で使われてるwindow.scrollByも全く反応がなかったので、、、windowの時点で何かしらの理由があって無効になっていると仮定しました scrollIntoViewを代わりに使いましょう! 色々と調べまくってる時でに運命的な出会いをしました。。 ここでscrollIntoViewを知りました! そこからどうやって実装したか? 一番上の階層のvueファイルでid="page-top"を指定 Layout.vue <template> <div> <div class="page-content-wrapper" id="page-top"> <Sample></Sample> </div> <FooterVue></FooterVue> </div> </template> <script> import FooterVue from './_partials/Footer'; import Sample from './_partials/Sample'; export default { components: { 'FooterVue': FooterVue, 'Sample': Sample }, } // 省略 </script> コンポーネントファイルでmethodを入れる sample.vue <template> <div> <button @click="moveToTop"> 次へ </button> </div> </template> <script> export default { methods: { moveToTop() { document.getElementById('page-top').scrollIntoView({ behavior: 'smooth', block: 'start' }); } } // 省略 </script> これでwindow.scrollToと同じようなことが実現できました わーい!! 注意点 scrollIntoViewはoffsetが使えません(もうちょっと移動した時の位置を上にしたい!とかが無理っぽい) まぁそれでも一番上に移動させるだけなら全く問題はありません。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JSでresizeイベントを使うときの注意点

Webページでウィンドウサイズが変化したときに何か処理をさせるのに便利な window.addEventListener("resize", function() {}); ですが、 このまま素の記述で実装してしまうと、スマホのブラウザで表示したときにURLのバーが伸び縮みする動作もウィンドウサイズが変更されたと判定されてしまうので、単にスクロールしただけでイベントが発火してしまいます。 (例:ウィンドウサイズが変更されたときにページをリロードするように記述していた場合、スクロールしてURLバーのサイズが変わるたびにリロードされてしまう。) これはPCブラウザのレスポンシブモードでは確認できない現象なので注意が必要です。 「ウィンドウサイズの変化」とは少し性質が違いますが、 例えばスマホ画面の縦・横の向きが変化したときに処理を発火したい場合などは、 window.addEventListener("orientationchange", function() {}); を使う方がいいかもしれません。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

terraformで構成管理しつつaws lambdaをデプロイする

DMMデータインフラ部に所属しているyuuaです terraformで作成しlambdaをデプロイする際にterraformにlambda関数を含めるとchangeがでたり、扱いづらく 悩むときがあるのでその際の手順的な備忘録です 今回のlambdaはlambda containerではありません 初回の手順 1.terraformで基本的なlambdaの事前の情報を作成する(配置先など) 2.lambda関数をzip化しs3バケットに配置する 継続的なデプロイの手順 基本的にgithub actionsなどのCI/CDを使います 1.github actionsでlambda function / lambda layerをバケットに配置 2.github actionsでupdate functionの実行 下準備 lambdaを配置するようのs3 keyなどをterraformで作成します s3.tf // bucket作成 resource "aws_s3_bucket" "lambda_bucket" { bucket = "lambda-bucket" } // lambda関数配置するようのkeyを作成 resource "aws_s3_bucket_object" "lambda_function" { key = "functions/nodejs/" bucket = aws_s3_bucket.lambda_bucket.id } // layerを使う場合はlayer用も resource "aws_s3_bucket_object" "lambda_layer" { key = "layers/nodejs/hoge/" bucket = aws_s3_bucket.lambda_bucket.id } これで一旦 terraform apply を実施する lambda functionを配置 default のnode.js lambda aiu.js exports.handler = async (event) => { // TODO implement const response = { statusCode: 200, body: JSON.stringify('Hello from Lambda!'), }; return response; }; 上記を zip 化し先ほど作成したs3のkeyに配置します。 配置自体はcliでもconsoleでもciでも配置できれば何でも良いです lambdaのリソース定義 関数をs3に配置したらterraformでlambda関数を定義します lambda.tf data "aws_iam_policy_document" "role" { statement { actions = ["sts:AssumeRole"] effect = "Allow" principals { identifiers = ["lambda.amazonaws.com"] type = "Service" } } } resource "aws_iam_role" "role" { name = "${var.function_name}-lambda" assume_role_policy = data.aws_iam_policy_document.role.json } // logginなど必要であれば、適宜roleにattachなどしてください // 関数定義 resource "aws_lambda_function" "function" { function_name = var.function_name handler = var.handler role = aws_iam_role.role.arn runtime = var.runtime s3_bucket = bucketを指定 s3_key = 配置したkeyを指定 layers = [layerを使うのであれば指定] } terraform applyを実施する 上記でlambda関数の作成自体は完了です publishなどのoptionは適宜 継続的なデプロイをするために 基本的に一度作ったら終わりということはあまりないと思うので継続的なdeployをするために定義を作成します ここではgithub actionsを定義して、Pull Requestから各branchにPR close/mergeされたタイミングでdeployできるようにします 外部からdeployする際は iam で 必要な権限を付与したuserを作成し、そのユーザのsecretを使います。 ざっと必要になりそうな権限 s3:PutObject s3:GetObject s3:DeleteObject s3:ListBucket lambda:GetFunction lambda:UpdateFunctionCode lambda:UpdateFunctionConfiguration // layerがいるなら lambda:GetLayerVersion lambda:PublishLayerVersion lambda:ListLayerVersions lambda:AddLayerVersionPermission github actions定義 lambda function 下記はセルフホストランナーでの定義ですが通常通りgihtub hosting/セルフホスティングどちらでも問題ないです function.deploy.yml name: deploy-function on: pull_request: branches: - ブランチ名 types: [closed] jobs: update-functions: name: lambda deploy runs-on: [self-hosted, linux] if: github.event.pull_request.merged == true // mergeされたら処理継続 steps: - name: Checkout uses: actions/checkout@v2 - name: code build uses: actions/setup-node@v2 with: node-version: "14" cache: "npm" - run: npm install // 必要であれば - run: npx webpack // buildなどあれば // aws credentialを設定 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v1 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: リージョン // zipファイルを作成 - name: compression working-directory: ./dist run: zip function.zip main.js - name: lambda update function codes working-directory: ./dist run: | aws lambda update-function-code --function-name function名 --zip-file fileb://function.zip // 必要であればpublish コードをzip化し aws cli の update-function-codeを実行しています これでPR Close/merge時にdeployが実行されます lambda layer layerはnodeの場合 nodejs/node_modules をzip化する必要があります ここではlambda functionとは別にlayer用のディレクトリを定義しそこにpackage.jsonをおいています layer.deploy.yml name: lambda-layer-deploy on: pull_request: branches: - develop types: [closed] paths: - "layerのpath/**" jobs: update-functions: name: lambda layer deploy runs-on: [self-hosted, linux] if: github.event.pull_request.merged == true steps: - name: Checkout uses: actions/checkout@v2 - name: code build uses: actions/setup-node@v2 with: node-version: "14" cache: "npm" - name: lambda layer working-directory: ./layer run: | mkdir nodejs cp package*.json nodejs/ npm install --prefix ./nodejs --production zip -r package.zip nodejs/node_modules - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v1 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: リージョン - name: lambda layer deploy working-directory: ./layer run: aws lambda publish-layer-version --layer-name layer名 --zip-file fileb://package.zip --compatible-runtimes nodejs14.x // 現在のlatestを取得 - name: lambda layer latest version id: step1 run: | layer=$(aws lambda list-layer-versions --layer-name layer名 --query 'LayerVersions[0].LayerVersionArn') echo "::set-output name=layer_ver::$layer" - name: lambda update configure run: | aws lambda update-function-configuration --function-name function名 --layers ${{ steps.step1.outputs.layer_ver }} //複数layerがある場合は ${{ hogehoge }} ${{ foobar }} このような形で続けてかけます まとめ terraformでlambdaのリソースを作成しつつ継続的なdeployはCI/CDで行う環境を備忘録的な形でまとめました。 IaCとlambda関数のcodeが分離され、意識することもすくないのでlambda containerを使わない場合は 今のところ一番使いやすいかなと思っています。 データインフラ部では、AWSでのビッグデータ基盤関連の運用を行っており、 様々リソースを活用したプロダクト開発を行っています。 中途採用などもおこなっておりますので、興味のある方は、一度弊社HPなどから カジュアル面談など、是非ご応募ください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React:Bootstrap Tabタイトルを子コンポーネントから変更

ReactでBootstrapのTabsを使っていて、keyのあるオブジェクトから作ったTabのタイトルを子コンポーネントからkeyを指定して変更した。メモを2点ほど。 ComponentParent export default function ComponentParent(props) { const [tabTitles, setTabTitles] = useState(null) const [currentKey, setCurrentKey] = useState(null) useEffect(() => { // ここでは、例なので単純にオブジェクト直接記述 setTabTitles({ 111: "タブ1", 222: "タブ2", 333: "タブ3" }) setCurrentKey(111) }, []) useEffect(() => { // 何か選択が変わった時の処理 },[currentKey]} const TabSample = () => { if (!currentKey || !tabTitles) return <div>Loading...</div>; return ( <Tabs id="tab-sample" activeKey={ currentKey } onSelect={(k) => setCurrentKey(k)} >                 {/* メモ1: オブジェクトをmapで展開するにはこう書けばよいみたい。 */} { Object.keys(tabTitles).map(key => <Tab eventKey={ key } title={ tabTitles[key]} key={ key }></Tab> } </Tabs> ) } const changeTabTitle = (key, title) => { /* メモ2: tabTitles[key] = title してセットする方法だと更新されない。展開すること。 */ setTabTitles({...tabTitles, [key]: title}) } return ( <TabSample /> <ComponentChild changeTabTitle={ (key, title) => changeTabTitle(key, title) }  /> ) } ComponentChild export default function ComponentChild(props) { const clickChangeTabTitle1 = () => { props.changeTabTitle(111, "タブA") } return ( <button onClick={ clickChangeTabTitle1 } >タブ1のタイトル変更</button> ) }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TypeORMでQueryBuilderを使わずにJOIN先のテーブルのカラムをWhere句で使用する

注意 この記事の公開から時間が経っていたり、typeormのバージョンが異なる場合は、仕様が異なる場合があります。 動作確認: 2021/10/7 typeorm: v0.2.34 TL;DR JOIN先の主キー(ID)で絞り込む場合 const teenProgrammers = await getRepository(User).find({ relations: ['role'], // なくてもOK where: { age: LessThanOrEqual(19), role: { id: programmer.id } } }) JOIN先の主キー(ID)以外で絞り込む場合 const teenProgrammers = await getRepository(User).find({ join: { alias: "user", innerJoin: { role: "user.role" }, }, where: (qb: SelectQueryBuilder<User>) => { qb.where({ age: LessThanOrEqual(19) }).andWhere( "role.name = :roleName", { roleName: "programmer" } ); }, }); 前提条件 エンティティ entity/role.ts @Entity() export class Role { @PrimaryGeneratedColumn() id?: number; @Column() name: string; @OneToMany(() => User, user => user.id) users: User[]; } entity/user.ts @Entity() export class User { @PrimaryGeneratedColumn() id?: number; @Column() name: string; @Column() age: number; @ManyToOne(() => Role) role?: Role } メイン index.ts createConnection().then(async connection => { const [programmer, boxer] = await getRepository(Role).save([ { name: 'programmer' }, { name: 'boxer' } ]) await getRepository(User).save([ { name: 'alice', age: 18, role: programmer }, { name: 'bob', age: 55, role: boxer }, { name: 'carol', age: 30, role: programmer } ]) }) }).catch(error => console.log(error)) データベース内の状態 role id name 1 programmer 2 boxer user id name age roleId 1 alice 18 1 2 bob 55 2 3 carol 30 1 他の方法 クエリビルダを使う 表現力が高くSQLライクなのはいいですが、冗長です。 const teenProgrammers = await getRepository(User) .createQueryBuilder("user") .innerJoinAndSelect("user.role", "role") .where("role.id = :roleId", { roleId: programmer.id }) .andWhere("age <= 19") .getMany(); できない例 SQLも問題なく生成されますが、パラメータにnullが渡されてしまうのでダメです。 const teenProgrammers = await getRepository(User).find({ relations: ["role"], where: { age: LessThanOrEqual(19), "role.id": programmer.id, }, }); 発行されるSQL(抜粋) LEFT JOIN "role" "User__role" ON "User__role"."id"="User"."roleId" WHERE "User"."age" <= ? AND "User"."roleId" = ? -- PARAMETERS: [19,null] ID以外をQBを使わずに指定するとダメ この場合も発行されるSQLと渡されるパラメータは上記と同じでroleId = nullになります。 const teenProgrammers = await getRepository(User).find({ relations: ["role"], where: { age: LessThanOrEqual(19), role: { name: "programmer", }, }, }); QBとrelationsを併用すると微妙 role.nameと指定することはできません。 const teenProgrammers = await getRepository(User).find({ relations: ["role"], where: (qb: SelectQueryBuilder<User>) => { qb.where({ age: LessThanOrEqual(19) }).andWhere( "role.name = :roleName", { roleName: "programmer" } ); }, }); FROM "user" "User" LEFT JOIN "role" "User__role" ON "User__role"."id"="User"."roleId" WHERE "User"."age" <= ? AND role.name = ? -- PARAMETERS: [19 "programmer"] User__role.nameと書けば冒頭のコードと同様に動作しますが、内部的に自動生成された(されるはずの)文字列を指定するのは微妙な気がします。 const teenProgrammers = await getRepository(User).find({ relations: ["role"], where: (qb: SelectQueryBuilder<User>) => { qb.where({ age: LessThanOrEqual(19) }).andWhere( "User__role.name = :roleName", { roleName: "programmer" } ); }, }); 参考 Finding entity with with relation condition #4396
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

jQueryリダイレクトが遅くて画面が表示する。

ある条件の時リダイレクト、別の画面に移動してくれと依頼発生 リダイレクトってどうやるんだろう...から調べていきました。 .htaccessの設定を操作する、サーバーサイドで操作する等が出てきて、 どうしようかなと思っていたところ。 jQueryでリダイレクト設定できるというのを見つけたので実装しました。 環境 MovableType 7 MtappjQuery jQuery 3.4.1 jQuery リダイレクト設定 window.location.href = "https://sample.com" windowはいるのか要らないのかよくわかってないです...。 なくてもできると思います。 jQuery 初期実装 $(document).ready(function(){ if((mtappVars.me.name == "sample1" || mtappVars.me.name == "sample2") && mtappVars.scope_type == "user" && mtappVars.screen_id == "dashboard"){ window.location.href = "https://sample.com" } }); ただ単に入れるだけだと飛んでほしくないページが少しの時間表示され、 読み込みが完了後リダイレクトされるようになっていてカッコ悪い...。 .readyを入れて読み込むタイミングを変えても上手くいかなかったです。 最終結果 jQuery if((mtappVars.me.name == "sample1" || mtappVars.me.name == "sample2") && mtappVars.scope_type == "user" && mtappVars.screen_id == "dashboard"){ $('body').hide();//読み込み遅いためページ全てを非表示 window.location.href = "https://sample.com" } 読み込んでいる間[body]要素を非表示にして読み込んでるんだなと思わせることにしました。 これだと真っ白な画面が表示されます。 まとめ 根本的な改善にはならなかったので、 もっと時間をもらったときよく調べたいなと思いました。 2021-10-08追記 いろいろ調べた結果bodyの一番下でjsファイルが読み込まれている為 動作が遅いことが判明しました。head内に移動させるとスムーズに移動できました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails]非同期(Ajax)でのコメント機能実装

この記事の目的 非同期処理(ajax)でのコメント投稿機能を学んだのでアウトプットします 目標 要件 コメント投稿は非同期で行う 環境 ・Ruby 2.7.1 ・Rails 5.2.6 前提 ・UserとPostは作成済み ・bootstrapとjquery-railsが入っている 設計 ER図 ルーティング POST /posts/:post_id/comments comments#create GET /comments/:id/edit comments#edit PATCH /comments/:id comments#update DELETE /comments/:id comments#destroy ※ post_idが必要なのはコメントがcreateされる場合だけであり、一度コメントが作成されればそれらコメントは一意のidを持つので、edit,update,destroyの場合はコメントの自身のidのみで特定可能である。 よって、このようなルーティングにする。(shallow: trueで実現可能) このようなshallowルーティングのメリットは、shallowを使わない場合と比較してルーティングがスッキリするという点である。 実装 データベース $ rails g model Comment body:text user:references post:references実行 マイグレーションファイル編集(任意) $ rails db:migrate実行 モデル models/comment.rb class Comment < ApplicationRecord belongs_to :user belongs_to :post validates :body, presence: true, length: { maximum: 1000 } end models/user.rb has_many :comments, dependent: :destroy models/post.rb has_many :comments, dependent: :destroy ※ has_many,belongs_toはメソッドを作るためのメソッド。 ※ Userモデルにhas_many :commentsを記述することで、Userモデルのインスタンスは.commentsメソッドを使い、自身のidに対応するCommentモデルのuser_idを通じて、コメント情報にアクセスできるようになる。Postも同様。 ※ 逆に、Commentモデルにbelongs_to :userを記述することで、Commentモデルのインスタンスは.userメソッドを使い、自身のuser_idに対応するUserモデルのidを通じて、ユーザー情報にアクセスできるようになる。belongs_to :postでも同様。 ルーティング routes.rb resources :posts, shallow: true do resources :comments end ※ 上述したルーティング設計に合わせるため、shallowルーティングを使用している (参考:Railsの”shallow(浅い)”ルーティングを理解する) コントローラー comments_controller.rb class CommentsController < ApplicationController def create @comment = current_user.comments.build(comment_params) @comment.save end def edit @comment = current_user.comments.find(params[:id]) end def update @comment = current_user.comments.find(params[:id]) @comment.update(comment_update_params) end def destroy @comment = current_user.comments.find(params[:id]) @comment.destroy! end private def comment_params params.require(:comment).permit(:body).merge(post_id: params[:post_id]) end def comment_update_params params.require(:comment).permit(:body) end end ※ current_user.comments.findをComment.findにしてしまうと、コメントしたユーザー以外のユーザーがURL直打ちなどによって他人のコメントを編集できてしまうようになるので注意。 ※ ユーザから受け取った情報としてparamsにはないけれども、レコード作成時に追加したい値がある場合はmergeメソッドで含めることができる(今回のpost_id)。 mergeメソッドは、ハッシュ同士を統合できるメソッド。 例 x = {age: "25", email: "tarou@example.com"} {name: "太郎"}.merge(x) #=> {name: "太郎", age: "25", email: "tarou@example.com"} (参考) 【初学者向け】Rails mergeメソッド mergeメソッドについて改めて理解を深めた ※ buildメソッドはnewメソッドと同じ。慣習的に、関連するモデルを生成するときはbuildを使う(参考: railsのnewとbuildの違い) ※ createアクションの処理が終わると、views/comments/ディレクトリ配下にcreate.html.erbまたはcreate.js.erbを探しに行く。edit,update,destroyも同様。 view jsの記述 views/comments/create.js.erb <% if @comment.errors.present? %> alert("<%= @comment.errors.full_messages.join('\n') %>"); <% else %> $('.comments-box').prepend("<%= escape_javascript(render('comments/comment', comment: @comment) ) %>"); $('.input-comment-body').val(''); <% end %> ※ slimならRubyのコードを囲む<%=...%>は#{...}となる ※ escape_javascriptはjと省略して書くこともできる ※ 最初の<% if @comment.errors.present? %>の部分は、ユーザーのコメントがバリデーションに引っかかった場合にエラー文を表示する処理。 ・<% else %>の1行目の部分は、htmlのcomments-boxクラスで囲まれた部分にrender('comments/comment', comment: @comment) をぶちこんでいる。 イメージ <div class="comments-box"> <li>コメント1</li> <li>コメント2</li> <li>コメント3</li> <li>新規コメント</li> ⇦ こいつがjsで追加されるイメージ </div> 検証ツールで実際の挙動を確認 ※ コメントを投稿したとき、画面遷移はせずに<div id="comment-29">...</div>だけが追加されていることがわかる htmlの記述(投稿されたコメントの表示画面) views/posts/show.html.erb(postの詳細画面) ・ ・ ・ <hr> / コメント一覧 <%= render 'comments/comments', comments: @comments %> <hr class="m-0"> <div class="post-comment p-3"> <%= render 'comments/form', post: @post, comment: @comment %> </div> </hr> </hr> views/comments/_comments.html.erb <div class="comments-box"> <%= render comments %> </div> ※ commentsは@comments。つまりrender @commentsで_comment.html.erbに飛ばしているのと同じ(each文省略するやつ) views/comments/_comment.html.erb ( @comments.each do |comment| が省略されている ) (<div class="comments-box">) ・ ・ ・ / コメント一覧のうちのコメントひとつひとつ(|comment|) <div class="col-9"> <span class="font-weight-bold.pr-1"> <%= comment.user.username %> </span> <%= comment.body %> </div> (</div>) htmlの記述(コメントの入力フォーム) views/comments/_form.html.erb <%= form_with model: [post, comment], class: 'd-flex mb-0 flex-nowrap justify-content-between', remote: true do |f| %> <%= f.text_field :body, class: 'form-control input-comment-body', placeholder: 'コメント' %> <%= f.submit '投稿', class: 'btn btn-primary btn-raised' %> <% end %> ※ form_withはデフォルトでremote: trueなので書く必要はないが、local: trueではないことを強調するためにremote: trueを明示。 ちなみにlink_toの場合はremote: trueを必ず書かなければいけない。 ※ ルーティングをネストさせているのでmodel: [post, comment]と2つ書かなければいけない ※ もしform_with model: commentにしたら、/commentsにPOSTリクエストが送られるが、ルーティングのPOST /posts/:post_id/comments comments#createにマッチしないため、post_idが不存在ということでエラーが起きる。 ※ edit, update, destroyについてもcreate同様にviewを作っていく ちなみに、検証ツールを見ればTypeがxhrになっており、form_withのremote: trueが非同期処理をしていることがわかる。 XHR(XMLHttpRequest)とは、JavaScriptなどのウェブブラウザ搭載のスクリプト言語でサーバとのHTTP通信を行うための、組み込みオブジェクト(API)である。 すでに読み込んだページからさらにHTTPリクエストを発することができ、ページ遷移することなしにデータを送受信できるAjaxの基幹技術である。(Wikipedia引用)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TypeORMでSQLiteを指定してQueryFailedError: SQLITE_CONSTRAINT: NOT NULL constraint failed エラーが出たときの対処法

解決方法 解決策は4つあります。4つ目が本命です。 1. synchronizeをやめる これでエラーはなくなるけど、エンティティが同期されなくなるので不便。。。 ormconfig.json { "synchronize": true } 2. カラムをnullableにする これも根本的な解決じゃない。。。 entity/user.ts @Column({ nullable: true }) name: string; 3. SQLiteを使わない 身も蓋もないですが、ありだと思います。 4. トランスパイルする 私の環境では、ts-nodeを使わないようにすると、エラーが解決されました。 以下の手順を行ってください。 STEP 1 tsconfig.jsonのCompilerOptionsに"skipLibCheck": trueを指定する。 これは、STEP3のtscコマンド実行時にエラーを表示させないためです。 基本的に推奨されるオプションではないので、エラーが出なかったら消してください。 tsconfig.json { "compilerOptions": { "lib": [ "es5", "es6" ], "module": "commonjs", "moduleResolution": "node", "outDir": "./build", "emitDecoratorMetadata": true, "experimentalDecorators": true, "sourceMap": true, "skipLibCheck": true } } STEP 2 ormconfig.jsonを以下のように書き換えます。 これは、トランスパイルしたJavaScriptを実行するためにパスを変更しています。 /src → /build .ts → .js ormconfig.json { "type": "sqlite", "database": "database.sqlite", "synchronize": true, "logging": false, "entities": [ "build/entity/**/*.js" ], "migrations": [ "build/migration/**/*.js" ], "subscribers": [ "build/subscriber/**/*.js" ], "cli": { "entitiesDir": "build/entity", "migrationsDir": "build/migration", "subscribersDir": "build/subscriber" } } STEP 3 tscでトランスパイルします。 $ tsc すると、buildディレクトリに.jsファイルがたくさん書き出されるはずです。 実行できないときは、typescriptをグローバルにインストールしてください。 $ npm i -g typescript STEP 4 nodeでトランスパイルしたJavaScriptファイルを実行します。 $ node build/index.js これでエラーが出なければOKです。 STEP 5 毎回コマンドを打ち込むのは大変なので、package.jsonに書き加えてスクリプトにします。 package.json { ... "scripts": { "start": "tsc && node build/index.js", } } 最初は実行できたけど、後でエラーになった場合 自分は遭遇してませんが、前の実行で出力したJavaScriptファイルが残っていると、エラーになる場合があるようです。その場合、以下のようなコマンドで実行前にbuildディレクトリを削除してください。 $ rm -r build && tsc && node dist/index.js 参考: [typeORM] NOT NULL constraint Failedなどのsynchronize回りのエラーの解決方法 発生したエラーの詳細 $ ts-node src/index.ts QueryFailedError: SQLITE_CONSTRAINT: NOT NULL constraint failed: temporary_user.name at new QueryFailedError (/Users/src/error/QueryFailedError.ts:11:9) at Statement.handler (/Users/src/driver/sqlite/SqliteQueryRunner.ts:79:26) { errno: 19, code: 'SQLITE_CONSTRAINT', query: 'INSERT INTO "temporary_user"("id") SELECT "id" FROM "user"', parameters: [] }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】スコープの学習の振り返り④

はじめに Udemyの【JS】ガチで学びたい人のためのJavaScriptメカニズムの講座の振り返りです。 前回の記事 目的 スコープについての理解を深める 本題 1.即時関数(IIFE) 即時関数とは関数定義と同時に一度だけ実行される関数のこと。 →実行結果が呼び出し元に返される。 基本構文 let result = (function (仮引数){ return 戻り値; })(実引数); 戻り値はresultに返ります。 function a() { console.log("called"); } a(); // 上記と下記は同じ結果が出力される (function(){ console.log("called"); return })(); // 最後に実行する()がないとSyntaxエラーが発生する // 関数式の場合は()がいらない(エラーにならない) 即時関数で表すと下記のとおりになります。 // 即時関数 let c = (function(d){ console.log("called" + d); // 呼び出し元変数cに0が返される return 0; // 仮にここに引数10を渡すと上記dに返される })(10); この即時関数はどのようなタイミングで使用するのか。 それは、関数スコープを利用して関数の中でしか使えない変数や関数と関数の外で使う変数や関数を明確に区別したいときに使用します。 let c = (function(){ console.log("called"); // 変数privateValを定義 関数内でしか使えない let privateVal  = 0; // 変数publicValを定義 これは関数の外で使えるようにしたい let publicVal = 10; // 関数も上記の様に生成 function privateFn(){ console.log("privateFn is called"); } function publicFn(){ console.log("publicFn is called"); } // 戻り値を設定してこの結果が変数cに格納される return { privateVal, publicFn }; })() // 関数の外から下記のように呼び出すとコンソールに出力される c.publicFn(); console.log(c.publicVal); // c.privateValと書くことはできない 今日はここまで! 参考にさせて頂いた記事 【JS】ガチで学びたい人のためのJavaScriptメカニズム
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

これからReact始めたい人のための今日だけでできるTODO#18 useCallback

useCallbackとは? 関数をメモ化するフックです。 React.memoを利用した場合でも親コンポーネントからコールバック関数をpropsで受け取った場合子コンポーネントは再レンダリングされます。 コンポーネントが再レンダリングされるたびにコールバック関数が再生成されるため処理が同じでも新しい関数を生成されているとみなされるからです。 例えば下記のような場合 (※前回のコードをこコンポーネントにpropsで関数を渡す形で修正しました) App.js import React, { useState } from 'react'; import { Counter } from './Counter'; const initialCount = 0; export default function App() { const [countA, setCountA] = useState(initialCount); const [countB, setCountB] = useState(initialCount); const countIncrementA = () => setCountA((prevCountA) => prevCountA + 1); const countIncrementB = () => setCountB((prevCountB) => prevCountB + 1); return ( <> <Counter text='Aボタン' count={countA} countIncrement={countIncrementA} /> <Counter text='Bボタン' count={countB} countIncrement={countIncrementB} /> </> ); }; CoounterコンポーネントにApp.jsで定義したcountIncrementAをpropsで渡すようにして実装してみました。 ボタンをクリックするとconsoleには下記の画像のように表示されます。 ボタンAをクリックすると、ボタンBも反応しています。 useCallbackを利用 定義されている2つの関数をuseCallbackでラップします。 importするのも忘れずに。 App.js import React, { useState, useCallback } from 'react'; export default function App() { const [countA, setCountA] = useState(initialCount); const [countB, setCountB] = useState(initialCount); const countIncrementA = useCallback( () => setCountA((prevCountA) => prevCountA + 1), [countA] ); const countIncrementB = useCallback( () => setCountB((prevCountB) => prevCountB + 1), [countB] ); //以下省略 変更してボタンをクリックすると変更されたコンポーネントしかレンダリングされなくなります。 useCallback()でメモ化されたコールバック関数をReact.memo()でメモ化された子コンポーネントへpropsで渡されることで不要な再レンダリングをスキップさせることができます。 どちらか1つでは機能しないので。useCallback()とReact.memo()は2つで1つということを覚えておくと良いと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Node/Vue-CLI/Viteを使わずに最速でVueのSPAを作る

はじめに 本記事は、Node/Vue-CLI/Viteを使わず、CDNを用いることで最速のVueのSPA環境を構築する記事となります。 環境汚染を気にせず簡易的にVueの実行環境を作りたい方、CUIの知識が薄くてもVueの実行環境を作りたい方、そしてNodeを構築できないサーバでSPAを作りたい方、向けです。 注意:JS-Fiddleは使いません。あくまでローカルでの構築です。 注意:VueのSyntax等は触れません。あくまで環境構築記事です。 背景 自分の大学では無料のサーバが全生徒に用意されているのですが、権限等々の問題からnodeの環境を構築することができず、htmlやCSS程度しか実行することができません。 就活も進む中、せっかくなのでプロフィールページのようなものをSPAで作りたいと思うものの、わざわざアクセス数などしょうもないページを有料で作るのもなんだかなということで、この制約の多い環境でもSPAを作ろう!という意図の下、このような技術を得る経緯となりました。 作成前に持っておいてほしい知識 ある程度のhtmlの知識。あとはコピペでいけます。 私自身が作成前に持っていた知識 私自身は業務でVue3系を使ったフロントエンドエンジニアをやっております。(といいつつもVueの経験は浅い。) そのためある程度の知識はあります。 ですが本記事では上述の通り特にそれに準ずるような必要ありません。 使用技術 Vue2系 CDN vue-router CDN http-vue-loader CDN JavaScript さらっと調べたところCDNのみで3系とvue-routerを実装する方法を見つけられなかったので諦めて2系にしました。(そもそも3系CDNの需要が現状なさそう) また、SCSSもCDNで構築できるそうなのですが、面倒だったので止めました。 環境構築 ディレクトリ図 root/ ┣ index.html ┣ main.js ┣ app.vue ┣ page1.vue ┗ page2.vue Vue-CLIやViteを用いないため、上記ファイルは全てCUIの touch コマンドや単純にファイルの新規作成などで作成していただいて構いません。 上記ディレクトリアーキテクチャはあくまで一例です。 コンポーネントディレクトリ/srcディレクトリ/cssディレクトリ/staticディレクトリなどなど自由に用意していただいて構いません。お任せします。 PATHを全て自分で書き連ねるため、自由度が高いです。 index.html まずは index.html からとなります。 こちらの用途は「描画のベースとなる」「各種外部ファイル/CDN読込の起点となる」というところです。 主なポイントとしては 1. 各種VueのCDNを読み込む・2. id="app"のタグを用意する・3. main.jsを読み込む です。 index.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <!-- point.1 各種VueのCDNを読み込む --> <!-- Vue2 CDN --> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14"></script> <!-- Vue Router CDN --> <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script> <!-- Http Vue Loader CDN --> <script src="https://unpkg.com/http-vue-loader"></script> </head> <body> <h1>index</h1> <!-- point.2 id="app"のタグを用意する --> <div id="app"> <app-vue></app-vue> </div> </body> <!-- point.3 main.jsを読み込む --> <script src="./main.js"></script> </html> point.1 各種VueのCDNを読み込みについて こちらが全CDNとなります。これで実行準備OKです。かんたん。 *2021年10月現在、Vue2系の最新版は2.6.14ですが、ご自身の使用時期等に合わせてバージョンの指定を行ってください。 point.2 id="app"のタグを用意するについて Vueの慣例として id="app" となるタグを起点とします。.vue ファイルに記載されるtemplateなどあらゆるコンポーネントの描画は全てこの中で行われます。 また、今回は <app-vue> というタグを用いております。こちらはVueを用いるにあたり、一旦Vueのロジックは全て index.html ではなく .vue ファイルで完結させたいと思いこのようなアーキテクチャを検討しました。 point.3 main.jsの読み込みについて これがあることで main.js と紐付きます。 main.js 次に main.js となります。 こちらの用途は「Vueインスタンスの作成(=Vueの起動場所)」「vue-routerの処理置場」というところです。 主なポイントも同様となり 1. Vueインスタンスの作成・2. vue-routerのルーティング です。 main.js // point.2 vue-router のルーティング const router = new VueRouter({ routes: [{ path: '/', component: httpVueLoader('./page1.vue') }, { path: '/page2', component: httpVueLoader('./page2.vue') }] }); // point.1 Vueインスタンスの作成 const app = new Vue({ el: "#app", components: { // 起点となる app.vue コンポーネントの指定 'app-vue': httpVueLoader('./app.vue') }, router // ← vue-router のマウントも忘れずに }); point.1 【重要】Vueインスタンスの作成について Vueインスタンスの作成となります。こちらは通常のVue2系とほぼ同様ですが、1点注意点として、今回は Vue-CLI や Vite の代わりに http-vue-loader を用いているためコンポーネントの指定が   'コンポーネント名': httpVueLoader('VueファイルのPATH指定') といった書き方なります。 先程 index.html で書いた <app-vue> はここで宣言をしております。 point.2 vue-routerのルーティングについて vue-routerのルーティングとなります。こちらはVue2系に対応するvue-routerとほぼ同様です。上と同様にこちらもVueファイルへのPATH指定時に http-vue-loader を用います。 app.vue お次は app.vue となります。 こちらの用途は「Vueロジックの起点」というところです。 主なポイントは 1. vue-router描画の起点処理・2.export方法 です。 app.vue <template> <div> <div>Hello CDN Vue!</div> <router-view></router-view> </div> </template> <script> module.exports = { data() { return { } }, } </script> point.1 vue-routerの描画の起点処理について 4行目、template内に <router-view> タグを記載しております。ここがvue-routerで指定したvueファイルの描画起点となります。 世の中のhttp-vue-loaderを用いた多くの記事ではこのタグをindex.htmlに描いていることが多いですが、その場合、router-viewの外に動的コンポーネントなどを置いた際に処理が煩雑になると感じたため、このようなアーキテクチャを検討しました。 point.2 【重要】export方法について 通常のVueでは export default {} を用いてモジュールのExportを行いますが、http-vue-loaderを用いた場合、module.exports = {} を用います。間違いやすいので注意。 オブジェクトの中身は通常のVue2系と同じです。 page1.vue, page2.vue 最後にvue-routerで読み込むページです。 page1.vue <template> <div>page1</div> </template> <script> module.exports = { data() { return { } }, } </script> page2.vue <template> <div>page2</div> </template> <script> module.exports = { data() { return { } }, } </script> ただのVue2系ファイルです。先程同様、export だけ異なるので注意。 以上で準備は完了です。 実行&トラブルシューティング あとは通常のhtmlファイル同様、index.html をCLICKするなどして、ブラウザで開きます! これで下画像のようになっていればOKです。 しかしながら、場合によってはホワイトアウトしていることもあります。 原因1. CORS問題 一番多発するホワイトアウト要因としてCORS問題があります。(恐らく上記の実行方法だと99%そうなります。) これが原因の場合、まず "index" の文字しか表示されず、更にデベロッパーツールのコンソールを見ると、 Access to XMLHttpRequest at 'hogehogehoge' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, chrome-untrusted, https. といったエラーが出ています。blocked by CORS policy とあるようにサーバを経由しないためCORSのセキュリティに抵触し、実行が拒否されています。 この場合、最も簡単な回避方法はブラウザの拡張機能で仮想的なサーバを経由することです。 Chromeの場合は "Web Server for Chrome" という拡張機能がおすすめです。 ChromeWebストア:Web Server for Chrome 使い方は各自、調べてください。また、FF / Safari / Edge などの対処法は割愛させていただきます。 原因2. SyntaxERROR など この他に考えられる大きな原因は文法のエラーやCDNの読み込みエラーです。(逆に言えばCORS以外に面倒なエラーは出づらいです。) 各自ご確認ください。 終わりに これで最速Vue環境構築は以上となります。 サーバに上げる際は今回用意したファイルをただアップロードするだけで実行可能となります。とても便利です。 CDNという性質上、アーキテクチャの自由度がかなり高いです。ぜひご自身に合わせた改良を行ってください。 また、経験が浅いため修正必要箇所等あるかもしれません。もし見つけられた場合はコメントなどいただけると助かります。 【宣伝】 Qiitaで同様に、Vue3系のViteを用いたSPA開発環境の構築記事も出しております。もしでしたらぜひご覧ください。今回の記事と比較するとかなり重量級です。 Vue3系+TS+Vite を使った0からのSPA開発【 [序章] 環境構築編】
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TypeORMのMigrationでCannot use import statement outside a moduleエラーの対処法

TL;DR あまり綺麗ではないですが、これで解決しました。 typeormコマンドを使用せず、直接ts-nodeでtypeormを呼び出します。 $ ts-node ./node_modules/typeorm/cli.js migration:generate -n 'User' NestJSを使用しているときにこのエラーに遭遇した場合は、ページ下部のormconfig.jsonの書き換えを試してみてください。 よくみたらドキュメントに書いてあった... TypeORM > If entities files are in typescript ドキュメントの推奨している方法だと、、、 STEP 1 ts-nodeをグローバルにインストール $ npm install -g ts-node STEP 2 package.jsonにスクリプトを書き加える "scripts": { ... "typeorm": "node --require ts-node/register ./node_modules/typeorm/cli.js" } STEP 3 そしたら、こんな感じで実行する --は必要なので注意! $ npm run typeorm migration:generate -- -n UserMigration 遭遇したエラー内容 $ npx typeorm init $ npm install $ typeorm migration:generate -n UserMigration Error during migration generation: /Users/michinosuke/archive/web-app/typeorm-playground/src/entity/role.ts:1 import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm' ^^^^^^ SyntaxError: Cannot use import statement outside a module at wrapSafe (internal/modules/cjs/loader.js:984:16) at Module._compile (internal/modules/cjs/loader.js:1032:27) at Object.Module._extensions..js (internal/modules/cjs/loader.js:1097:10) at Module.load (internal/modules/cjs/loader.js:933:32) at Function.Module._load (internal/modules/cjs/loader.js:774:14) at Module.require (internal/modules/cjs/loader.js:957:19) at require (internal/modules/cjs/helpers.js:88:18) at /usr/local/lib/node_modules/typeorm/util/DirectoryExportedClassesLoader.js:42:39 at Array.map (<anonymous>) at Object.importClassesFromDirectories (/usr/local/lib/node_modules/typeorm/util/DirectoryExportedClassesLoader.js:42:10) 上のやつで解決しないとき 一応、他の可能性も考えてみます。 ts-nodeも直接パスを指定する ts-nodeがグローバルにインストールされていない場合は、これで解決するかも。 $ ./node_modules/.bin/ts-node ./node_modules/typeorm/cli.js migration:generate -n 'User' 環境変数をつける TS_NODE_PROJECTでtsconfig.jsonのパスを渡します。 TS_NODE_TRANSPILE_ONLYにtrueを指定して、TypeScriptのtranspileModuleを使ってみる。 transpileModuleについての参考 ts-node Typechecking TypeScriptコンパイラとそのAPI TS_NODE_PROJECT=tsconfig.json TS_NODE_TRANSPILE_ONLY=true ./node_modules/.bin/ts-node ./node_modules/typeorm/cli.js migration:generate -n 'User' tsconfig.jsonを書き換える compilerOptionsのmoduleがcommonjs以外になっているときは、commonjsに変更してみてください。 tsconfig.json { "compilerOptions": { "module": "commonjs" } } さらに他の解決法 一旦TypeScriptで.tsファイルを.jsにトランスパイルしちゃう方法です。綺麗な解決策ではありませんが、ほぼ確実に解決できると思います。 もしあなたがNestJSなどを使用してこのエラーに遭遇している場合、下の手順3のormconfig.jsonの書き換えだけで解決する可能性が高いです。 手順1 tsconfig.jsonのcompilerOptionsのskipLibCheckにtrueを指定します。 あまりいい方法じゃないんですが、これを指定しないとトランスパイルにエラーが大量に出ます。 参考: ERROR TS1086: An accessor cannot be declared in an ambient context { "compilerOptions": { "skipLibCheck": true } } 手順2 トランスパイルします。 $ tsc 上のコマンドが打てなかったら、TypeScriptをグローバルインストールしてください。 $ npm i -g typescript 実行できたら、プロジェクトルートにbuildディレクトリが作成されて、jsファイルがたくさん入ってるはずです。 手順3 ormconfig.jsonを以下のように書き換えます。 * src/ → build/ * .ts → .js Before { "type": "sqlite", "database": "database.sqlite", "synchronize": true, "logging": false, "entities": [ "src/entity/**/*.ts" ], "migrations": [ "src/migration/**/*.ts" ], "subscribers": [ "src/subscriber/**/*.ts" ], "cli": { "entitiesDir": "src/entity", "migrationsDir": "src/migration", "subscribersDir": "src/subscriber" } } After { "type": "sqlite", "database": "database.sqlite", "synchronize": true, "logging": false, "entities": [ "build/entity/**/*.js" ], "migrations": [ "build/migration/**/*.js" ], "subscribers": [ "build/subscriber/**/*.js" ], "cli": { "entitiesDir": "build/entity", "migrationsDir": "build/migration", "subscribersDir": "build/subscriber" } } 手順4 nodeでtypeormを呼び出します。 $ node ./node_modules/typeorm/cli.js migration:generate -n 'User' 参考 migration:generate throws a SyntaxError with clean project template #5087 同じようなissue一覧
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

画像をたくさん読み込む

画像の取り込みをコントロールする javascript var images = document.getElementsByTagName('img'); var count = 0; for (var i = 0; i < images.length; i++) { var img = new Image(); img.onload = function() { count += 1; } img.onerror = function() { count += 1; } img.src = images[i].src; }; var waiting = setInterval(function() { if(count == images.length) { clearInterval(waiting); ここに画像を取り込んだ後の処理を書く } }, 100); 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptでPythonのrange関数を実装する

はじめに これらと同じことをJavaScriptでやる。 for i in range(10): print(i) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] list(range(10)) # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] list(range(1, 11)) # [0, 5, 10, 15, 20, 25] list(range(0, 30, 5)) # [0, 3, 6, 9] list(range(0, 10, 3)) # [0, -1, -2, -3, -4, -5, -6, -7, -8, -9] list(range(0, -10, -1)) # [] list(range(0)) # [] list(range(1, 0)) 仕様 すべて数値型の range(end) と range(start, end[, step]) の引数を受け取る。 渡された引数がない場合はエラーを投げる。 最初に載せたPythonのコードと同じ挙動をする。 実装 範囲などは事前に計算し、Symbol.iterator メソッドを実装したイテラブルなオブジェクトを返す。 /** * @param {number} start * @param {number} end * @param {number} [step = 1] * @returns {{ start: number, end: number, step: number, [Symbol.iterator](): Generator<number, void, unknown> }} */ const range = (start, end, step = 1) => { // range() if (typeof start === 'undefined' && typeof end === 'undefined') { throw new TypeError('range expected at least 1 argument, got 0') } // overload range(end) if (typeof end === 'undefined') { end = start start = 0 } /** * @type {number} */ const max = ( ( step === 0 || (start > end && step > -1) || (start < end && step < 0) ) ? 0 : Math.abs(Math.ceil((end - start) / step)) ) return { start, end, step, *[Symbol.iterator]() { for (let i = 0; i < max; i++) { yield start + step * i } } } } 使い方 以下のように使用する。 for (const i of range(10)) { console.log(i) } // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] const array = [...range(10)] // Array.from(range(10))
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む