- 投稿日:2020-07-09T23:27:47+09:00
Nuxt.js: middlewareを使って特定のページ以外からのアクセスをリダイレクトする
成り行き
Nuxtを使用しているアプリを作成中、ログイン後の二段階認証ページなどログインした後にアクセスしてほしくないページにアドレス直打ちで入れることに気づきました。
Nuxtには
middleware
というページレンダリングを行う前に設定されたメソッドを実行してくれるAPIがあります。今回はmiddlewareを用いて、特定のページ以外からのアクセスを行なった場合リダイレクトするようにしました。コード
auth.jsexport default function({ from, redirect }) { if (!from || from.path !== '/login') { // 利用される場合、/loginには任意のパスを入れてください redirect('/'); } }あとはこれをページのmiddlewareプロパティに設定するだけです。
解説
from
は遷移元のrouteデータが入っています。
今回の場合ログイン後に表示される二段階認証ページへのアクセスを制限したかったので、fromに入っているpath
が/login
以外だった場合リダイレクトが実行されるようにしました。
またアドレス直打ちの場合、fromはundefined
になっていたのでその場合も含めました。(個人的にはこちらがメイン)
- 投稿日:2020-07-09T23:22:22+09:00
【javascript】計算されたプロパティ名
書籍のアウトプット
計算されたプロパティ名
現在使用している状態マネージャを別のライブラリに対応させる必要があるとする。
このライブラリは状態に格納する必要があるいくつかの値を渡す。
問題はこのライブラリが渡すキーとペアが状態マネージャのメソッドに渡せるオブジェクトではなく、配列形式であるということそこで状態マネージャをうまくライブラリに渡すために仲介関数を作成する。
function setValue(name, value) { const changes = {} changes[name] = value stateManager.update(changes); } const [name, value] = otherLibrary.operate() setValue(name, value)ポイントはsetValue関数でプロパティを定義する前にからのオブジェクトを作成しなくていけないこと。
これはプロパティ名は動的に設定されるため。{name:value}というものを定義していたとすれば、プロパティ名はname変数に含まれる値ではなく、"name"という値になっていた。
つまりchanges.name=value
とおなじこと"changes[name]=value"とすることでnameの値を使用する事ができる。
- 投稿日:2020-07-09T23:16:51+09:00
WWDC 2020 Web Developer 向け情報
Web Developer 向けの WWDC 情報です。個人的には Safari Web Extension が追加されて、Safari を使ってる方にも自分の Web Extension が提供できるようになったのが一番嬉しかったです。
なんというかやっぱり色んなベンダーを超えたブラウザで同じような体験が提供できるのがウェブテクノロジーの強みなので、よかった。WWDC for Web Developer
ざっくりまとめ
- Safari (WebKit) がアップデート
- iOS のデフォルトブラウザを Safari 以外に変更可能に
- App Clip が登場、ウェブページでもサポート
- Safari Web Extension が登場
- WKWebView のアップデート
- (今回は省略) Sign in with Apple の強化
- (今回は省略) Touch ID / Face ID の対応
- (今回は省略) Web Inspector の強化
Web デベロッパのための新機能
▶︎ https://developer.apple.com/videos/play/wwdc2020/10663/
- 新しいバージョンの Safari 14 について
- パフォーマンスが向上(ページ読み込み、JavaScript の実行、その他もろもろ)
Web Kit
Safari 13.1 の新機能について
Web Animations API
- CSS アニメーション・トランジションを JavaScript でコントロールできる API
- もう HTML 要素のプロパティを操作しなくてもいいという話
- クリック起点でアニメーションを行う
// Web Animations API Code Example let needle = document.getElementById("needle"); let logo = document.getElementById("logo"); logo.addEventListener("click", () => { needle.animate({ transform: [ "rotateX(35deg) rotateZ(13deg)", "rotateX(35deg) rotateZ(733deg)", ], easing: ["ease-out"], }, 800); });
- Web Inspector Graphics タブにアニメーションの解析ができるようになった。
- Web Animations in Safari 13.1 | WebKit
ResizeObserver
- なんらかの要素のサイズが変更された時を検知する API
- ウィンドウのリサイズ、HTML 要素のサイズが変更されたことを検知できる
// Resize Observer Example let formatPanelObserver = new ResizeObserver((entries) => { entries.forEach((entry) => { let container = entry.target; container.classList.toggle("small", entry.contentRect.width < 175); } }); formatPanelObserver.observe(document.getElementById("format-panel"));Async Clipboard API
- コピーペーストできるボタンが作れるように
// Programmatic copy copyButtonElement.addEventListener("click", (event) => { navigator.clipboard.writeText("Plain text to copy.").then(() => { // Successful copy }, () => { // Copy failed }); }); // Programmatic paste pasteButtonElement.addEventListener("click", (event) => { navigator.clipboard.readText().then((clipText) => { document.querySelector(".editor").innerText += clipText); }); });
- https かつ、ユーザーのインタラクション起因でないと使えない(セキュリティとのトレードオフ)
- Async Clipboard API | WebKit
Event Target Constructor
- ライブラリの作者向け。DOM 以外のオブジェクトにもカスタムイベントをディスパッチできるようになる
Web Component
- CSS Shadow Parts
HTML enterkeyHint attributed
Web Authentication API
- Touch ID と Face ID の認証に対応
CSS の改良
- System Font Family のサポート font-family: ui-sans などの追加
- line-break: anywhere の追加
- :is() 擬似セレクタの追加 (Safari 14)
- :where() 擬似セレクタの追加 (Safari 14)
メディア
- webP 画像のサポート (Safari 14)
<picture> <source srcset="example.webp" type="image/webp"> <img src="example.jpg" alt="Example Image"> </picture> Accept: image/webp,image/png,image/svg+xml,image/*;…
- Safari 13.4 から、img に width height を追加しておけば、その分ちゃんと画像サイズ分を確保してくれるようになる
- EXIF 画像メタデータから画像の向きを取れる
image-orientation: from-image; or none;
- HDR ディスプレイの対応
<style> @media only screen (dynamic-range: high) { /* HDR-only CSS rules */ } </style> <script> if (window.matchMedia("dynamic-range: high")) { // HDR-specific JavaScript } </script>
- AirPlay サポート (Remote Playback API)
<video id="videoElement" src="https://site.example/video.mp4"></video> <button id="deviceButton">Send video to a remote device</button> <script> let videoElement = document.getElementById("videoElement"); let deviceButton = document.getElementById("deviceButton"); deviceButton.addEventListener("click", (event) => { videoElement.remote.prompt().then(updateRemotePlaybackState); }); </script>
- Picture in Picture API
<video id="videoElement" src="https://site.example/video.mp4"></video> <button id="pipButton">Enter picture-in-picture mode</button> <script> let videoElement = document.getElementById("videoElement"); let pipButton = document.getElementById("pipButton"); pipButton.addEventListener("click", (event) => { videoElement.requestPictureInPicture().then(handlePictureInPicture); }); </script>
- HLS で時間指定メタデータがサポート
- 字幕とキャプションのための機能強化
JavaScript
- BigInt のサポート
- JSON.stringifty には非対応
- Nullish Coalescing (Null合体演算子)
class Person { constructor(firstName, lastName, age) { this.firstName = firstName ?? "Unknown"; this.lastName = lastName ?? "Unknown"; this.age = age ?? NaN; } } console.log(new Person()); // { firstName: "Unknown", lastName: "Unknown", age: NaN } console.log(new Person(false, false, true)); // { firstName: false, lastName: false, age: true } console.log(new Person("John", "", 0)); // { firstName: "John", lastName: "", age: 0 } console.log(new Person("John", "Appleseed", 42)); // { firstName: "John", lastName: "Appleseed", age: 42 }
- Optional Chaining
console.log(person?.name.firstName);
- 論理演算子
a &&= b // and assignment operator a ||= b // or assignment operator a ??= b // nullish assignment operator // Nullish coalescing approach element.innerHTML = element.innerHTML ?? "Hello World!" // Logical assignment operator element.innerHTML ??= "Hello World!"
- パブリッククラスフィールド
class Person { firstName = ""; lastName = ""; age = NaN; children = []; constructor(firstName, lastName, age) { this.firstName = firstName ?? "Unknown"; this.lastName = lastName ?? "Unknown"; this.age = age ?? NaN; } }
- String replaceAll
- 正規表現使わなくなくてよくなった
"Hello world, Hello baby".replaceAll("Hello", "Good night") > Good night world, Good night babyプラットフォームとの機能
- AR Quick Look 表示に、広告バナーを表示することがでいるようになる
- Apple Pay のボタン画像の種類が追加され、請求の詳細とかを表示できるようになった
AppClip
- アプリをインストールすることなくユーザーにアプリ体験を提供することができる機能
- ウェブサイトに入った時にメタデータを定義しておくと、それを知らせることができる<meta name="apple-itunes-app" content="app-id=myAppStoreID, app-clip-bundle-id=clipBundleID, affiliate-data=myAffiliateData, app-argument=myURL">
- Instant App は使われてるところ見たことなかったけどこれは一定使われそう
WKWebView の強化のご紹介
▶︎ https://developer.apple.com/videos/play/wwdc2020/10188/
- WKWebView が例えば Blink / Gecko になるとかそういうのではない
- WebView は引き続き WKWebView (WebKit) が使われると思われる
- iPhoneではあらゆるWEBブラウザのエンジンがSafariと同じ? - いまさら聞けないiPhoneのなぜ
- WKWebView のデバッグが今までやりにくかったことがやりやすくなる
- JavaScript のサンドボックス機能がついて (WKContentWorld) 、コンテンツ側のバグなのかどうなのか評価しやすくなった
- CallAsyncJavascript API が導入されて非同期でネイティブアプリとメッセージングできるようになった
- インテリジェント・トラッキング・プリベンション (ITP) を WebView にも追加
- Facebook, Google はサードパーティクッキーによる「ユーザーが何のサイトを見ているか」を追跡監視している(クロスサイト・トラッキング)
- 主に広告、マーケティングに使用
- WKWebView にも追加された(13.4)
- ファーストパーティクッキーの保存期間が 24 時間以内となる
- Google Chrome も 2022 年にはサードパーティーの Cookie を完全にブロック可能になる
- その他
- 画面全体の pageZoom プロパティが追加された
- 画面全体のキャプチャ機能が追加された
- 画面全体の文字列検索機能が追加された
- Web アーカイブの強化
Safari Web Extensions の導入
▶︎ https://developer.apple.com/videos/play/wwdc2020/10665/
Chrome や Firefox 、Edge とかで使える拡張機能をポーティングできるようになった。
Firefox とか Opera もサポートされている Web 標準である extension API を実装したという感じ。
今まで Safari に拡張機能を入れる場合は Xcode + Swift で開発してた感じです。
Safari Technology Preview をインストールして、Xcode Beta を使って導入テストできます。
Web Extension Safari ポーティング用の Commandline Tools が導入された
こいつがまだ私の環境だとなんかアレだった(隠語)
% xcrun safari-web-extension-converter {path}
Xcode から実行してインストール
Xcode Beta を使用して実行。Safari 14 の Develop タブで、Allow Unsigned Extensions をオンにしておく。
- xcode のプロジェクト作成で Safari Extension App を選択
- Type: Safari Web Extension を選択 (Safari App Extension ではない)
- Resources の中身を置き換えて、ビルドする。
- Safari の Extensions タブに拡張機能が追加される
- URL バーの左に拡張機能がインストールされ、実行できる(んじゃないかな〜)
その他
- moz/Chrome 拡張機能をハードコードしている場合 Safari では動かないので、スキームを browser.runtime と標準の API を使用するようにしなければならない
- 通知系 API はサポートされていない
- ネイティブメッセージング API を使ってネイティブアプリと通信することができたりする
browser.runtime.sendNativeMessage // ネイティブにメッセージを送る browser.runtime.connectNative // ネイティブと接続するSFSafariExtensionManager.getstateOfSafariExtension // 拡張機能の状態を知る SFSafariApplication.dispatchMessage // 拡張機能にメッセージを送る
- 配布はどうやるのかまだ言及されてない気もするけどいまのところの雰囲気だと App Store からの配布になるのかな
- Chrome Extension みたいに審査する感じですね
- 投稿日:2020-07-09T22:58:01+09:00
Google スプレッドシートのデータを JS で fetch したい!
Google スプレッドシートを方眼紙としてではなく、ちゃんとデータを保存するテーブルとして使用していた場合、外部のシステムからそのデータを使用したいと思うのは必然ですよね?なんなら Web のフロントエンドから直接 JavaScript で
fetch
して使いたいですよね??それ簡単にできますよ?そう、Google Apps Script ならね!
例えば Google スプレッドシートにこんなデータがあって、このデータを外部から JSON で取得したいとしましょう。この記事の最後に完成するこんな API を作るという事です。
それには、前述のように Google Apps Script を書く必要があるので、ツールからスクリプトエディタを開きます。
ただこのエディタ、挙動がおかしくてすこぶるストレスがたまるので、ちょっとしたスクリプトを書いたり既存のスクリプトの簡単な修正ならともかく、ある程度本格的に書くつもりならば手元の使い慣れたエディタで書いたものを貼り付けた方が良いでしょう。
さて、デフォルトの関数は要らないので消して、代わりの関数を書いてスプレッドシートのデータをオブジェクトのリストに詰めてみましょう。こんな風になります。
function getData() { const spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); const sheet1 = spreadsheet.getSheetByName('シート1'); const range = sheet1.getRange('A2:G11'); const values = range.getValues(); const data = values.map(row => { let col = 0; return { id: row[col++], name: row[col++], furi: row[col++], gender: row[col++], bloodType: row[col++], birthDate: row[col++].getTime(), zip: row[col++], } }); console.log(data); return data; }初見で理解が難しいポイントになるのは一ヶ所くらいかと思います。
range.getValues()
はObject
の二次元配列を返しますが、実際の型はスプレッドシート上の型に依存するので、生年月日の列がDate
型で返ってきます。JSON では日付型をそのまま扱うことが出来ないので、それをgetTime()
でエポックタイムにしています。このエディタにはデバッガも搭載されていてステップ実行なども出来るのですが、残念ながらあまり快適ではないので、とりあえず
console.log
で結果を出力するようにしました。実行前に保存する必要があるので、適当な名前を付けて保存しましょう。ここで上部の▶アイコンで
getDate
を実行しようとすると、初回のみ権限の付与が求められます。これは「スプレッドシートの読み書きを誰の権限で実行するか」という設定で、通常はスプレッドシートの所有者として許可して良いでしょう。ところがその途中で下記のような警告が出ます。
これの意味するところは「この Google が知らない謎のスクリプトは、今からお前のスプレッドシートを読み書きする権限を要求するぞ、気をつけろ!」です。マーケットプレイスなどに登録された Google 確認済みのアドオンに許可を与える場合などは表示されないものと思われます。
今回の場合、自分自身は信頼できるデベロッパーなので、詳細の中にある「ユーザ一覧 API (安全ではないページ) に移動」をクリックして進めます。権限の付与が済むと無事に関数が実行できるようになるので、▶アイコンで実行後にログを確認しましょう。
スプレッドシート上のデータがいい感じにリストにまとまってることが確認できましたね!
次に、ここで取得したデータを JSON で返す API にします。それにはまず特殊な名前の関数
doGet
を実装する必要があります。こんな感じです。function doGet() { const data = getData(); const response = ContentService.createTextOutput(); response.setMimeType(MimeType.JSON); response.setContent(JSON.stringify(data)); return response; }特に難しいところはないですね。Google Apps Script の SDK を知っているか知っていないか、というだけの問題です。
ここまで来たら後は最後のステップ、この
doGet
にユニークな URL を与えて API として公開します。公開からウェブアプリケーションとして導入を選んで下さい。ここまでの過程でもちょこちょこ翻訳漏れを見かけましたが、このダイアログはほぼ完全に英語です。いかに日本のユーザが少ないか、あるいは日本が軽視されているかが分かりますね。
ここで、Project version は "New"、Execute the app as (誰の権限でスクリプトを実行するか) は "Me" (あなた自身) に設定し、Who has access to the app (誰が API にアクセスできるか) は用途に応じて変更して下さい。
ここを例えば、"Only myself" (自分だけ) にすると、Google ログイン状態の時しか使えない API になりますが、それだと JS からシンプルにfetch
する事は出来なくなるので、この記事の趣旨に照らせば "Anyone, even anonymous" (匿名であっても誰でも) を選ぶことになります。一つ注意点として、元データのスプレッドシートを読み書きする権限をスクリプトに付与していることを忘れないようにして下さい。万一のことがあった場合に外部に流出すると深刻な問題になるデータは、間接的にせよ公開状態にするスプレッドシートには置かない事が肝要です。
ここで Deploy ボタンを押すとユニークな URL が発行されて API としてアクセスすることが出来るようになります。
今回発行された URL は https://script.google.com/macros/s/AKfycbyRzwWIOOl6xI-c-kB6-rKYm5L-UwBRe_FZwD13_n0An4_Pyeg/exec です。試しに開いてみて下さい。どうでしょう?思った通りの JSON が取得出来ましたか?
なお、何かしらの修正を行って保存をしても、その内容が即座に反映されたりはしません。Google Apps Script 上でバージョン管理がなされているためです。API を更新したい場合は、再び公開からウェブアプリケーションとして導入を選びます。
初期状態では、最後に公開したバージョンが選ばれているので、Project version に "New" を選び直して更新することで、最新の状態を反映することが出来ます。この際すでに発行済みのユニーク URL は変わらないので、API を
fetch
しているクライアント側の変更は必要ありません。Google スプレッドシートのデータを外部に API を通じて JSON として公開する方法は以上になります。当初は他にも色々と書こうと思っていたのですが、大分長くなったのでいったんここで一区切りとします。
この記事で扱ったのはあくまでデータを読み出す部分だけでしたが、反響が大きければ下記の内容も別に書こうと思います。
- Google フォームを使ってデータを集めてスプレッドシートに溜める
- POST メソッドも Google Apps Script で受けられるようにして、より高度なフォームを自作する
- cron のように定期的に Google Apps Script を実行して、必要な時にメールで通知する
LGTM お待ちしています!
- 投稿日:2020-07-09T22:15:17+09:00
Firebase SDKをJestでMockしてテストする
やりたいこと
Firebase SDKを利用した関数のテストをしたいと思い立ちました。実際にAPIにアクセスせずにテストを実行したいので、Firebase SDKをMockしてテストを行うこととしました。テストはJestを使います。
テスト対象のコード
例としてCloud Firestoreを利用する際の手順を記載します。
なお、以下には記載していませんが、環境構築は終わっているものとすします。(手順はこちらの通り!)以下のようなFirestoreのドキュメントを監視して更新があったときにsomethingをする関数をテストすることにしてみます。
target.jsasync watchDoc({ commit }) { await firebase .firestore() .collection('users') .onSnapshot((snapshot) => { snapshot.forEach((doc) => { doSomething(doc) }) }) }Colud Firestoreは以下のように初期化されていることとします。
initFirebase.jsimport firebase from 'firebase' // Initialize Cloud Firestore through Firebase firebase.initializeApp({ apiKey: process.env.API_KEY, authDomain: process.env.AUTH_DOMAIN, databaseURL: process.env.DATABASE_URL, projectId: process.env.PROJECT_ID, storageBucket: process.env.STORAGE_BUCKET, messagingSenderId: process.env.MESSAGING_SENDER_ID }); export default firebaseテストコード
以下のようにMockを作成し、振る舞いを定義することで、実際にAPIを叩かずにdoSomething関数を動作させてテストを行うことができます。
firestore.spec.jsimport * as firebase from 'initFirebase' import * as firestore from 'target.js' // firebase.firestore()のMockを作成する jest.mock('initFirebase', () => ({ firestore: jest.fn() })) // firebase.firestore()の振る舞いを定義する const firebaseMock = firebase.firestore.mockImplementation(() => { return { collection: jest.fn(() => ({ onSnapshot: jest.fn((func) => { func(doc) // docにはAPIから返ってくるDocument相当のデータを入れる }) })) } }) // テストを記述していくMock方法のみ記載しています。テスト部分ややFirestoreの構築方法などは別の記事などを参考にしてください。。
- 投稿日:2020-07-09T21:22:14+09:00
RailsとAjaxを使ったいいね機能の非同期通信
転職活動用に個人アプリを開発中です。
今回、RailsとAjaxを使って、いいね機能の非同期化を行いました。AjaxではjQueryを使うため、jQueryを使えるようにしておく事前準備が必要です。それは参考記事を見てください。以下の記述はそれが設定済みのうえでの話です。
実現した機能
・「いいね」ボタンを押すとリロードせずに「いいねを取り消す」に表示が変わる
・「いいね」ボタンを押すとリロードせずにlikesテーブルにデータが1つ追加される
・「いいね」ボタンを押すとリロードせずにいいね数が1つ増える
※その逆もしかりこのコードでうまくいきました
コントローラー(likes_controller.rb)
likes_controller.rbclass LikesController < ApplicationController def create @post = Post.find(params[:post_id]) @like = current_user.likes.build(post_id: params[:post_id]) @like.save @likeCounts = Like.where(post_id: params[:post_id]) end def destroy @post = Post.find(params[:post_id]) @like = Like.find_by(post_id: params[:post_id], user_id: current_user.id) @like.destroy @likeCounts = Like.where(post_id: params[:post_id]) end end個別の投稿ページ(上記の画像のページ)
show.html.haml.like = render partial: "likes/like", locals: {post: @post}部分テンプレート(_like.html.haml)
_like.html.haml- if user_signed_in? - if current_user.already_liked?(post) = button_to 'いいねを取り消す', post_like_path(post_id: post.id, id: post.likes[0].id), method: :delete, remote: true - else = button_to 'いいね', post_likes_path(post.id), method: :post, remote: true .likeCounts いいね数: = post.likes.count更新したい部分のビュー(いいねしたとき)
create.js.erb$('.like').html("<%= j(render partial: 'likes/like', locals: {post: @post}) %>");更新したい部分のビュー(いいねを取り消すとき)
destroy.js.erb$('.like').html("<%= j(render partial: 'likes/like', locals: {post: @post}) %>");JavaScriptが動く仕組み
・
link_to
やbutton_to
には:remoteオプション(remote: true
)がある。button_to
にremote: true
を追加することで、js形式のリクエストを送信できるようになる。= button_to 'いいね', post_likes_path(post.id), method: :post, remote: true参考:Railsガイド
非同期通信の流れ
1.「いいね」ボタンを押す
2.下記のリンクでlikes#create
にjs形式でリクエストが飛ばされる= button_to 'いいね', post_likes_path(post.id), method: :post, remote: true3.likesコントローラーのcreateアクションが動き、いいねが保存される。Likeモデルを介してデータベースに追加される(リロードせずに)
4.更新したい部分のページcreate.js.erb
,destroy.js.erb
がレスポンスとして返される。.html
(jQueryのhtmlメソッド)は、.like
(likeクラス)の部分をhtmlの後ろの( )内に置き換える役割をはたす。今回苦労したところ
1.部分テンプレートの理解
create.js.erb$('.like').html("<%= j(render partial: 'likes/like', locals: {post: @post}) %>");partialオプション:部分テンプレートの呼び出しを行う。今回はlikesフォルダの_like.html.hamlを呼び出したいので
likes/like
となる。localsオプション:部分テンプレート内で{ }内の左辺が変数として使えるようになる。今回でいうと
post
が変数として使えるようになる。右辺の@post
は何かというと、右辺の@post
が左辺のpost
に代入して、それが変数として使えるようになる。右辺の@post
はどこからきているかというと、postsコントローラのshowアクションで定義しているので、そこからきている。posts_controller.rbdef show @post = Post.find(params[:id]) Like.new end2.
_like.html.haml
のbutton_to
のpathの設定
ずっとこのエラーに悩まされていました。ActionView::Template::Error No route matches (中略) missing required keys: [:id])この記事を見つけてようやく下記のように記述して解決できました。
= button_to 'いいねを取り消す', post_like_path(post_id: post.id, id: post.likes[0].id), method: :delete, remote: true解決はしたものの、この部分
id: post.likes[0].id
がまだちゃんと理解できていません。
1つの投稿に複数のいいねがついていたとして、その最初のいいねのidを取得している?今考えてみると、確かに1つの投稿に複数のいいねがある場合、「どのいいねを取り消すの?指定してくれないとわからないよ」と言われても無理ないなと思いました。
だとすると、必ずしもcurrent_userの付けたいいねではなく、他の人の付けたいいねを取り消してしまう?
まだ修正する必要があるかもしれません?
(追記)
Sequel Proで確認したところ、他の人のいいねを取り消してしまうことはなく、ちゃんとcurrent_userのいいねが取り消されていました。
一応いいねを消せることは消せます。
この点が明らかになったらまた追記します。参考記事
Railsで remote: true と js.erbを使って簡単にAjax(非同期通信)を実装しよう!(いいね機能のデモ付)
【Rails×Ajax】いいね機能ハンズオン
Railsでいいね機能を実装。Ajaxを使い非同期対応。で
- 投稿日:2020-07-09T21:07:17+09:00
Javascript クラスの書き方 進化版
かなりいいと思います。もうこれで決定ですね。
あえて何も説明を書かないが、わかる人がわかると思います。'use strict' let Cat = (function() { let _share = 'only one'; let privates = new WeakMap(); let $ = function(o) { return privates.get(o); }; function Cat(name) { privates.set(this, {}); $(this).name = name; } Cat.prototype = { //self:function(){return privates.get(this);}, init:function(){}, load:function(){}, say:function(){console.log('my name is', $(this).name)}, getShare:function(){console.log(_share);}, setShare:function(newValue){_share = newValue} }; return Cat; })(); let cat1 = new Cat('cat1'); let cat2 = new Cat('cat2'); console.log(cat1); console.log(cat1 === cat2); console.log(cat1.init === cat2.init); console.log(cat1.load === cat2.load); console.log(cat1.name); cat1.say(); cat2.say(); cat1.getShare(); cat2.getShare(); cat1.setShare('share changed by cat1'); cat1.getShare(); cat2.getShare();Q:継承はとうなるの?
A:継承?Javascript 継承させる?
- 投稿日:2020-07-09T20:52:54+09:00
秘密にしておきたかったんだけど、実はTwitterにはWebページを埋め込めるんだよね
こんにちは、Twitter大好き丸の あかい です
タイトルの通りなのですが、実はTwitterにはWebページを埋め込むことができます。(具体的には「ツイートには」ですが)
「WebページにTwitterを埋め込む」ではないですよ。
次のツイートをご覧ください。つくったから見て!!!!
— あかい (@Ver1000000000) July 9, 2020
特にPC版Twitterから《《《再生》》》してみて!!!!!https://t.co/a1KLMSXfaV #朱猪わらい……何やら再生できそうな感じのインターフェースが表示されていますよね?
そうなのです、このツイートをQiita上ではなく、Web版Twitter公式クライアントで展開するとなんと、
このために作った拙作の朱猪わらいの動画(?)がはじまります。
(かなり適当なES2015をpolifyllもいれず生で書いたので、動かないブラウザがあるかも)
(モバイルサファリclass構文対応してねぇのかよ!!!!びっくりだよ!!!!Babelでトランスパイルしたから諸々のブラウザで動くと思うよ)ランダムな動的挙動や、クリックによるインタラクションを成し遂げています。
どういう仕様?
これはいわゆるTwitter カード機能のPlayer タイプと呼ばれる仕様で実現されています。
Twitter カードについての諸々の仕様はみなさんに各自調べていただくとして、
Playerのカードについて、かいつまんで説明すると
- 動画サイトとか(rich media)を共有した時に、いい感じの再生可能なカードを表示するための仕様
- 10秒未満の動画はカードがTLビュー内に入った時に自動で再生できたりする
- 技術的にはiframeを利用して、外部のリソースをカードの枠内に表示しているよ
- レスポンシブなデザインで、動画などのメディアや、可変の枠に収まるように作られたWebページを想定している
というところでしょうか。
htmlに
<meta name="twitter:player" content="https://example.com/movie.mp4" />などとするとよいようで、ここの
content
にhtmlを記述すると、
記述したhtmlがrich mediaとしてTwitter カードにドンと現れるようになるわけです。つまり、htmlをメディアとしてカードに表示したい場合は、2つのhtmlを準備する必要があります。
GitHub Pagesとかで適当に公開して、Card Validator | Twitter Developersでテストしたら動作も含めて確認できます。
TwitterのTLでWebページ表示できるとか面白すぎwwwwww クソゲーいっぱい作ってツイートしよ!!!!!!!!!!!!!!!!!!!!!!
ちょっと待ってください!
実は先程貼った Player card — Twitter Developers の
Player card policy項目に、いくつか気になる文章が書いてあります。1文だけ、適当に要約します。
- Do not circumvent the intended use of the Card. Player Cards are reserved for linear audio and video consumption only.
「カードの目的逸脱しないでね、これ、ビデオとオーディオのためのモンだからね。」
……
………… ハ!?!? じゃぁなんでこんな何でもできるような可能性ありまくりの仕様実装しちゃったの?????
Player タイプのカードにはなんかリッチなコントロールがついた動画や音声だけに設定するようにしましょう……
そういうわけで、拙作の 朱猪わらい は、便宜上、
スクショや共有のできる、ちょっと便利なコントロールのついたランダム性のあるビデオ です。いやぁ、面白い仕様見つけたなぁと思ってワクワクしながらたまたま実装したのが、
ユーザーを驚かせたりしないようなビデオで良かった。 ビデオで良かった。おまけ
朱猪わらいについて
この仕様を知った時、試してみたくて作ったゲ動画です。
とくに面白いところがあるわけではないのですが、Player カードの可能性を感じてもらうことは出来たかと思います。リポジトリを置いておきますので、ぜひ参考にしてください。
iframeの詰まったポイント
iframeを利用すると、別ドメイン間の制約とかで苦しい思いをするのは
日本昔ばなしでもよく語られていますが(語られていません)、
今回朱猪わらいを実装する上でも引っかかりました。中でも大変だったのは生成された画像の保存機能です。
調べれば色々でてくるかと思いますが、普通、iframe内のデータを端末に保存させることはできないのですよね。これを回避するため、クエリパラムを利用して別ウィンドウで結果画像を再現し、
別ウィンドウでダウンロードさせて一瞬でウィンドウを閉じるという力技を使いました怪我の功名と言いますか、一石二鳥と言いますか、URLから状態を復元できる設計になったので、
結果をTweetで共有できるように作れたのはよかったですね。Card Validatorのサムネが更新されない問題
Twitter カードの再生マークのところに表示されるサムネイルは、
twitter:image
という名前(name属性)の付いたメタタグで設定できるのですが、
一度設定した後、GitHub Pagesに画像を上げ直しても、新しい画像で反映されなくなってしまいました。Card Validatorの
Preview Card
ボタンを何度押しても変わりません。どうしようかなぁと思ってしばらくなやんだのですが、
画像だけではなく、ファイル名とメタタグのurlを書き換えたらちゃんと反映されるようになりました。
- 投稿日:2020-07-09T18:55:13+09:00
更新後のstateの値を使ってdebounce処理をしたい
デモ
App.js
import React, { useState, useEffect, useCallback } from "react"; export default function App() { const [state, setState] = useState(""); const callback = useCallback(() => { //この中で更新後の値が使える console.log("debounced!!!", state); }, [state]); useEffect(() => { const handler = setTimeout(callback, 1000); //cleanup return () => { clearTimeout(handler); }; },[callback); return ( <div className="App"> <input type="text" value={state} onChange={e => setState(e.target.value)} /> </div> ); }普通にlodashとか使って作ったdebounce関数をuseEffectにぶちこむと更新後のstateを使うためには引数として渡さなければいけないのですが、useEffectのcleanup関数をうまく使ってあげることでstateが変わりcallback関数が再作成されるたびcleanupの中のclearTimeoutが待機中だった処理をキャンセルして、effectの中で新しいsetTimeoutを作成することでdebounce処理を行うことができます。
customHook版
useDebouncedEffect.js
import { useEffect, useCallback } from "react"; const useDebounceeEffect = (effect, deps, delay) => { const callback = useCallback(effect, deps); useEffect(() => { const handler = setTimeout(callback, delay); //cleanup return () => { clearTimeout(handler); }; }, [callback, delay]); }; export default useDebounceeEffect;
App.js
import React, { useState } from "react"; import useDebouncedEffect from "./useDebouncedEffect"; export default function App() { const [text, setText] = useState(""); useDebouncedEffect( () => { console.log("debounced!!!", text); }, [text], 1000 ); return ( <div className="App"> <input type="text" value={text} onChange={e => setText(e.target.value)} /> </div> ); }これでかんたんにstateの更新と紐付けたdebounce処理ができます
やったぜ。
- 投稿日:2020-07-09T15:56:50+09:00
【EMMET 記法】 基本 よく使うやつ エメット記法 まとめ
【メリット】
■ コーディングが早くなります。
■ ミスが少なくなります。【開発環境】
■ Mac OS catalina
■ VS code【やっていきましょう】
HTML版
div.hoge
hoge.html<div class="hoge"></div>div#hoge
hoge.html<div id="hoge"></div>div*3
hoge.html<div></div> <div></div> <div></div>div>h2
hoge.html<div> <h2></h2> </div>CSS版
h100
hoge.cssheigh: 100px;w100
hoge.csswidth: 100px;bc
hoge.cssbackground-color: #fff;c
hoge.csscolor: #fff;ta
hoge.csstext-align: center;m10
hoge.cssmargin: 10px;p10
hoge.csspadding: 10px;【まとめ】
■基本的に使うやつなので、他にもたくさんあります。
■実装5倍くらいのスピードになるので、是非【合わせて読みたい】
■ 【メソッド集】 rails メソッド まとめ 基礎 随時追加
https://qiita.com/tanaka-yu3/items/89abad875187494bec53■ 【Javascript】 メソッド まとめ 基礎基本コード メモ
https://qiita.com/tanaka-yu3/items/2438798d159fa402b1d5■ 【Rails new】Ruby on rails アプリケーション作成
https://qiita.com/tanaka-yu3/items/3fe1ed2852c6513d3583■参考 emmet記法
https://tracpath.com/works/development/emmet-for-web-developers/
- 投稿日:2020-07-09T15:47:04+09:00
WebStorageで1回目のアクセス時のみモーダルを表示する WebStorageで1回目のアクセス時のみモーダルを表示する
HTML
<div id="c-modal_bg"></div> <div id="c-modal"> <p>モーダルの中身です。<br /> モーダルの中身です。<br /> モーダルの中身です。</p> <p><input type="checkbox" name="acs_next" value="" />次回以降表示しない</p> <div id="c-modal_close">閉じる</div> </div>Javascript
// モーダルの処理 var bg = document.getElementById('c-modal_bg'); bg.addEventListener('click', modal_close, false); var close = document.getElementById('c-modal_close'); close.addEventListener('click', modal_close, false); function modal_close() { var modal = document.getElementById('c-modal'); modal.style.display = 'none'; bg.style.display = 'none'; // チェックボックスにチェックが入っている場合は、次回以降表示しないようにする if(document.getElementsByName('acs_next')[0].checked) { sessionStorage.setItem('acs2', 'on'); } } // 「次回以降表示しない」にチェックを入れていない場合 if(sessionStorage.getItem('acs2') === null) { } else { // チェックを入れている場合はモーダルを削除 modal_close(); }
ソース
http://cly7796.net/wp/javascript/display-modal-only-on-first-access-using-webstorage/
- 投稿日:2020-07-09T15:41:15+09:00
【JavaScript】検索クエリのパターンによって画像を出し分けたい
検索クエリのパターンによって画像を出し分けたい場合のサンプルソースを作成しました。
以下のような情報が支給されたと仮定
【支給内容】
?pattern=1
→ 「https://placehold.jp/800x400.png」を出力?pattern=2
→ 「https://placehold.jp/700x400.png」を出力?pattern=3
→ 「https://placehold.jp/600x400.png」を出力?pattern=4
→ 「https://placehold.jp/500x400.png」を出力例外として、以下のパターンも考慮します
【例外】
▼支給パラメータと検索パラメータが一致しない場合
https://www.xxx.co.jp/?patternXXX=1
→ デフォルトの「https://placehold.jp/800x400.png」を出力▼値のみ一致しない場合
https://www.xxx.co.jp/?pattern=99999
→ デフォルトの「https://placehold.jp/800x400.png」を出力▼条件に一致、不一致のパラメータが混ざっている場合
https://www.xxx.co.jp/?patternXX=1&pattern=1
→ 一致したパラメータの条件で出力▼パラメータとハッシュが混ざっていた場合
https://www.xxx.co.jp/#hash?pattern=1
→ 一致したパラメータの条件で出力そのまま使えます<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>検索クエリのパターンによって画像を出し分けたい</title> <style> body { display: flex; justify-content: center; align-items: center; } .mainimg { margin-top: 10%; } </style> </head> <body> <div class="mainimg"><img id="switching" src="https://placehold.jp/800x400.png" alt=""></div> <script> function getquery(){ var elem = document.getElementById("switching"); /* 現在ページURLのサーチ情報(?で始まる検索クエリ部分)を参照 || 現在ページURLのハッシュ情報を参照(パラメータとハッシュが混ざっていた場合を考慮) */ if(window.location.search || window.location.hash){ /* URLの「?」以降のパラメータを変数queryに代入 */ var query = window.location.search.substring(1,window.location.search.length); /* URLの「#」以降のパラメータを変数hashに代入 */ var hash = window.location.hash.substring(1,window.location.hash.length); if (query == "pattern=1" || query.match(/pattern=1/) || hash == "pattern=1" || hash.match(/pattern=1/)) { elem.src = "https://placehold.jp/800x400.png"; } else if(query == "pattern=2" || query.match(/pattern=2/) || hash == "pattern=2" || hash.match(/pattern=2/)) { elem.src = "https://placehold.jp/700x400.png"; } else if(query == "pattern=3" || query.match(/pattern=3/) || hash == "pattern=3" || hash.match(/pattern=3/)) { elem.src = "https://placehold.jp/600x400.png"; } else if(query == "pattern=4" || query.match(/pattern=4/) || hash == "pattern=4" || hash.match(/pattern=4/)) { elem.src = "https://placehold.jp/500x400.png"; } else { elem.src = "https://placehold.jp/800x400.png"; } } else { elem.src = "https://placehold.jp/800x400.png"; } } /* オンロード時に実行 */ window.onload=getquery; </script> </body> </html>
- 投稿日:2020-07-09T15:04:33+09:00
【JavaScript】クラス概念編
今回はクラス、インスタンス、継承といった概念の復習です。(自分用の備忘録)
0.クラスとインスタンス
クラスとは雛形や設計図と例えるのが一般的です。
インスタンスはクラスを元に作られるデータのことです。車の製造で例えるなら、
クラスは、車を製造する設計図で、
インスタンスは設計図を元にして作られた販売している車のことになります。1.クラス作成〜出力まで(完成)
簡単なクラス、インスタンス、継承の一連を作成しました。
細かく解説入れていきます。// ーーーーー 親クラス ーーーーー class Book { constructor(title, content) { this.title = title; this.content = content; } show() { console.log(`本のタイトルは ${this.title}`) console.log(`本の感想は ${this.content}`) } } // ーーーーー 子クラス ーーーーー class AuthorBook extends Book { constructor(title, content, author) { super(title, content); this.author = author } show() { super.show(); console.log(`本の著者は ${this.author}`) } } // ーーーーー インスタンス ーーーーー const books = [ new Book("メモの魔力", "メモの重要性がわかる") new Book("多動力", "ノリの良さが大切") new AuthorBook("革命のファンファーレ", "広告戦略の鏡", "西野亮廣") ] // ーーーーー 結果出力 ーーーーー books[0].show(); books[1].show(); books[2].show();2.クラスの作成
class Book { // クラス名の頭文字は大文字 constructor(title, content) { // constructor()メソッドを作ってプロパティの初期値を設定 this.title = title; // このクラスから作られるインスタンスはthisで表現する this.content = content; } show() { console.log(`本のタイトルは ${this.title}`) console.log(`本の感想は ${this.content}`) } }3.インスタンスの作成
const books = [ // new クラス名()でインスタンスを生成 // constructor()の引数に沿って出力したい値を入力する new Book("メモの魔力", "メモの重要性がわかる") new Book("多動力", "ノリの良さが大切") ]4.継承
class AuthorBook extends Book { // extends 親クラス名と記述することで、子クラスで親クラスの内容を継承できます constructor(title, content, author) { super(title, content); // super()を使用すれば、親クラスのconstructorを継承でき、親クラスのthisを併用できる this.author = author } show() { super.show(); // super.show()を使用すれば、親クラスのshowメソッドを子クラスで継承できます console.log(`本の著者は ${this.author}`) } }5.結果の出力
子クラスで作成したauthorの情報が入った、インスタンスも新しく生成します。
const books = [ // new クラス名()でインスタンスを生成 // constructor()の引数に沿って出力したい値を入力する new Book("メモの魔力", "メモの重要性がわかる") new Book("多動力", "ノリの良さが大切") new AuthorBook("革命のファンファーレ", "広告戦略の鏡", "西野亮廣") ]books[0].show(); books[1].show(); books[2].show();6.補足
静的メソッドについて
インスタンスを生成しなくても、クラスからメソッドを呼び出すことができるstaticの使い方を紹介します。
class Book { constructor(title, content) { this.title = title; this.content = content; } show() { console.log(`本のタイトルは ${this.title}`) console.log(`本の感想は ${this.content}`) } static showInfo() { // メソッド名の前にstaticと記述することでインスタンスを生成しなくてもメソッドを呼び出すことができる console.log("OK") } } Book.showInfo(); // クラス名.メソッド名()で呼び出すことができる -> 注意点:インスタンスを作成していないので静的メソッドでthisを扱うことはできません <-以上
- 投稿日:2020-07-09T14:59:48+09:00
吉野家1000円ガチャをJavaScriptで作ってみた.
超初心者プログラマーの平岡です(本当です).
ネットではAKRという名前をよく使っていて,本業は予備校講師で,おいしい数学 という,月間10万PV程の高校数学のサイトを運営しています.さて,Twitter上で,サイゼリア1000円ガチャというサイトが流れてきて,これならプログラミング勉強中(JavaScriptを勉強して半年)の私でも作れるのではないか??と思い,思いきって大好きな吉野家バージョンをネイティブのJavaScriptのみで作ってみることにしました.
それが吉野家1000円ガチャです。
JavaScriptを勉強して半年の私のコードに大して価値があると思えないので,イートインのHTMLとJavaScriptのコードを全公開します.
なお,初心者なので,もっと上手くコードが書ける可能性大ですし,この後コードを変える可能性もありますが,是非私に言って頂けると嬉しいです。
yoshinoya1000-eatin.html<h1 style="text-align: center; font-size: x-large">吉野家1000円ガチャ</h1> <p style="text-align: center; font-size: large"><b>イートイン</b></p> <p style="text-align: center"><a href="yoshinoya1000-eatin.html" class="btn-square">イートインガチャを回す</a></p> <div id="result" class="waku" style="background-color: #FFE7BF"></div> <p style="text-align: center"> <a class="share-7" id="getResult" > <i class="fab fa-twitter"></i> </a> </p> <script src="jsfile/gacha-yo-eatin.js"></script> <br><br> <p style="text-align: center; font-size: small"><a href="yoshinoya1000.html">吉野家1000円ガチャ</a></p> <p style="text-align: center; font-size: small">respect for <a href="https://saizeriya-1000yen.herokuapp.com" target="_blank">サイゼリア1000円ガチャ</a></p> <p style="text-align: center; font-size: small">made by <a href="https://twitter.com/akr_trader" target="_blank">AKR</a></p>上のCSSのコードは割愛します.
続いてJavaScriptです.gacha-yo-eatin.jslet getResult = document.getElementById('getResult'); let result =document.getElementById('result'); var menu = { "牛丼(小盛)" : 332, "牛丼(並盛)" : 352, "牛丼(アタマの大盛)" : 452, "牛丼(大盛)" : 452, "牛丼(特盛)" : 632, "牛丼(超特盛)" : 722, "豚丼(小盛)" : 318, "豚丼(並盛)" : 338, "豚丼(アタマの大盛)" : 438, "豚丼(大盛)" : 498, "豚丼(特盛)" : 618, "豚丼(超特盛)" : 708, "牛カルビ丼(小盛)" : 528, "牛カルビ丼(並盛)" : 548, "牛カルビ丼(アタマの大盛)" : 648, "牛カルビ丼(大盛)" : 708, "牛カルビ丼(特盛)" : 828, "牛カルビ丼(超特盛)" : 918, "牛皿定食(並盛)" : 498, "牛皿定食(大盛)" : 598, "牛皿定食(特盛)" : 698, "牛カルビ定食" : 598, "炙り塩鯖定食" : 598, "鯖みそ定食" : 598, "牛鮭定食" : 548, "豚鮭定食" : 548, "鰻重(一枚盛)" : 788, "スパイシーカレー(並盛)" : 328, "スパイシーカレー(大盛)" : 418, "チキンスパイシーカレー(並盛)" : 514, "チキンスパイシーカレー(大盛)" : 604, ライザップ牛サラダ: 500, ライザップ牛サラダエビアボガド: 600, とん汁: 186, しじみ汁: 158, あさり汁: 158, 味噌汁: 65, 生野菜サラダ: 102, ポテトサラダ: 130, ごぼうサラダ: 130, エビアボガドサラダ: 198, 牛小鉢: 167, 玉子: 65, 半熟玉子: 75, ねぎ玉子: 102, チーズ: 102, お新香: 102, キムチ: 102, 鮭: 195, ご飯: 139, のり: 65, のり: 65, 納豆: 84, "牛皿(並盛)" : 302, "牛皿(大盛)" : 402, "牛皿(特盛)" : 502, "豚皿(並盛)" : 288, "豚皿(大盛)" : 388, "豚皿(特盛)" : 488, "牛カルビ皿" : 458, "鰻皿(一枚盛)" : 698, "瓶ビール" : 417, "冷酒" : 315, } var menucalorie = { "牛丼(小盛)" : 488, "牛丼(並盛)" : 652, "牛丼(アタマの大盛)" : 741, "牛丼(大盛)" : 863, "牛丼(特盛)" : 1030, "牛丼(超特盛)" : 1169, "豚丼(小盛)" : 530, "豚丼(並盛)" : 707, "豚丼(アタマの大盛)" : 797, "豚丼(大盛)" : 931, "豚丼(特盛)" : 1172, "豚丼(超特盛)" : 1319, "牛カルビ丼(小盛)" : 618, "牛カルビ丼(並盛)" : 802, "牛カルビ丼(アタマの大盛)" : 1019, "牛カルビ丼(大盛)" : 1136, "牛カルビ丼(特盛)" : 1327, "牛カルビ丼(超特盛)" : 1502, "牛皿定食(並盛)" : 739, "牛皿定食(大盛)" : 797, "牛皿定食(特盛)" : 968, "牛カルビ定食" : 966, "炙り塩鯖定食" : 830, "鯖みそ定食" : 895, "牛鮭定食" : 712, "豚鮭定食" : 800, "鰻重(一枚盛)" : 670, "スパイシーカレー(並盛)" : 539, "スパイシーカレー(大盛)" : 691, "チキンスパイシーカレー(並盛)" : 747, "チキンスパイシーカレー(大盛)" : 898, ライザップ牛サラダ: 404, ライザップ牛サラダエビアボガド: 430, とん汁: 176, しじみ汁: 42, あさり汁: 51, 味噌汁: 20, 生野菜サラダ: 25, ポテトサラダ: 122, ごぼうサラダ: 70, エビアボガドサラダ: 83, 牛小鉢: 130, 玉子: 76, 半熟玉子: 76, ねぎ卵: 103, チーズ: 98, お新香: 13, キムチ: 26, 鮭: 133, ご飯: 386, のり: 5, のり: 5, 納豆: 98, "牛皿(並盛)" : 257, "牛皿(大盛)" : 315, "牛皿(特盛)" : 486, "豚皿(並盛)" : 320, "豚皿(大盛)" : 394, "豚皿(特盛)" : 634, "牛カルビ皿" : 416, "鰻皿(一枚盛)" : 242, "瓶ビール" : 215, "冷酒" : 185, } var menulength = 0; var menucalorielength = 0; var ary = []; var arycalorie = []; for(i in menu){ menulength++; ary.push(menu[i]); } for(i in menucalorie){ menucalorielength++; arycalorie.push(menucalorie[i]); } var rand = Math.floor(Math.random() * menulength); var key = Object.keys(menu); let total = ary[rand]; let totalcalorie = arycalorie[rand]; var key1 = key[rand]; result.innerHTML = '<p style="text-align: center"><b>イートインガチャ結果</b></p><p><strong style="font-size: large">' + key[rand] + '</strong>:'+ ary[rand] + '円 ' + arycalorie[rand] + 'kcal</p>'; while (total <= 1700){ var rand2 = Math.floor(Math.random() * menulength); total = total + ary[rand2]; totalcalorie = totalcalorie + arycalorie[rand2]; key1 = key1 + ',' + key[rand2]; if (total <= 844) { document.getElementById('result').innerHTML += '<p><strong style="font-size: large">' + key[rand2] + '</strong>:'+ ary[rand2] + '円 ' + arycalorie[rand2] + 'kcal</p>'; } else if (total <= 909){ document.getElementById('result').innerHTML += '<p><strong style="font-size: large">' + key[rand2] + '</strong>:'+ ary[rand2] + '円 ' + arycalorie[rand2] + 'kcal</p>'; break; } else { total = total - ary[rand2]; totalcalorie = totalcalorie - arycalorie[rand2]; key1 = key1.replace(',' + key[rand2], ''); } } document.getElementById('result').innerHTML += '<br><p style="text-align: center">税抜合計:' + total + '円<br><b>税込合計:' + Math.floor(total *1.1) + '円<br>カロリー合計:' + totalcalorie + 'kcal </b></p>'; getResult.addEventListener('click',function twitText() { var s, url; s = "吉野家1000円ガチャの結果は………" + key1 + " で税込合計" + Math.floor(total *1.1) + "円で,カロリーの合計は" + totalcalorie + "kcal です。"; url = 'hiraocafe.com/yoshinoya1000.html'; if (s != "") { if (s.length > 140) { //文字数制限 alert("テキストが140字を超えています"); } else { //投稿画面を開く url = "http://twitter.com/share?url=" + escape(url) + "&text=" + s; window.open(url, "_blank", "width=600,height=300"); } } } )解説します.
まず,連想配列を使って,吉野家の商品の名前と値段,名前とカロリーが対応したものを用意します。
この値段とカロリーを順に配列に格納していきます。
乱数を使って,ランダムに番号を選ぶようにし,let total = ary[rand];とlet totalcalorie = arycalorie[rand];で,最初の商品の値段とカロリーをそれぞれ,total と totalcalorie に代入します.同時にinnnerHTMLを使って,結果をHTMLに出力します.
その次はwhile文です.totalが1700以下で繰り返すとありますが,1700でなくていいです.大事なのは,税抜844円以下で,ランダムに商品を選ぶことを繰り返し,909円以下であればbreakで繰り返し処理を中止します。そして909より上であれば,最後に追加した商品の値段を引き,replaceで名前を削除することです.
なぜ税抜845円以上909円以下で中止するかというと,一番安い商品が税抜65円(例えば味噌汁)なので,この価格だと味噌汁すら買えません.なぜ買えないかというと,消費税が10%なので税抜価格だと合計909円までが限界だからです.
最後に苦労したのは,ガチャの結果を,twitterに引き渡すことです。
実用的には,玉子関連が多く出ますし,定食が出て味噌汁が出るのも避けたいので,のりを少し多めに登録したのがポイントです。
以上です.吉野家1000円ガチャは,初心者がプログラミング(特にwhile文や,関数,変数の置き方)のいい練習になると思います。
- 投稿日:2020-07-09T11:53:39+09:00
【JavaScript】オブジェクト編
自分用の備忘録です。。。
1.配列要素の挿入、削除
配列に要素を挿入したり、要素を削除する方法を見ていきます。
const alphabet = [a, b, c, d, e]; // 先頭に要素を追加したい場合 => unshift alphabet.unshift(追加したい要素); // 先頭の要素を削除したい場合 => shift alphabet.shift(); // 削除は1つずつしかできないので()の中は空白 // 末尾に要素を追加したい場合 => push alphabet.push(追加したい要素); // 末尾の要素を削除したい場合 => pop alphabet.pop(削除したい要素); // 削除は1つずつしかできないので()の中は空白 // 追加と削除を同時に行う場合 => splice alphabet.splice(変化が開始する位置[index], 削除数, 追加したい要素); alphabet.splice(1, 2, f, g); 出力結果は [a, f, g, d, e] になる // b,cを削除してf,gが挿入された2.スプレット構文
スプレット構文は配列の中に配列を展開したいときに便利です。
const alphabet = [a, b, c, d, e]; const others = [f, g, h, i]; const alphabet = [a, b, c, d, e, ...others]とすることで配列の中に配列を展開できる3.分割代入
分割代入とは、配列の要素を変数に代入したいときに使用します。
const scores = [10, 20, 30, 40, 50]; const [a, b, c, d, e] = scores; 上記記述により、 a = 10, b = 20, c = 30, d = 40, e = 50, となり、別々の変数を割り当ててくれる4.forEach();
forEachをしようすることで、配列の要素を1つずつ出力することできます。
const scores = [10, 20, 30, 40, 50]; scores.forEach((score, index) => { console.log(`- ${index + 1} 番目の点数は ${score} 点です`); }); // 出力結果 - 1番目の点数は10点です - 2番目の点数は20点です - 3番目の点数は30点です - 4番目の点数は40点です - 5番目の点数は50点です5.map();
mapは配列の要素に何らかの処理を加え、別の配列として新しく生成することができます。
const scores = [10, 20, 30, 40, 50]; // 別の定数として、新しく配列をつくる const scoreChange = scores.map((score) => { return score + 100; }); console.log(scoreChange); // 出力結果 [110, 120, 130, 140, 150]6.filter();
filterは条件に合う要素のみを抽出して別の配列として取得することができます。
const numbers = [1, 4, 7, 8, 10]; // 2で割り切れた要素のみ抽出 const evenNumbers = numbers.filter(number => { return number % 2 === 0; }); console.log(evenNumbers); // 出力結果 [4, 8, 10]7.join(); split();
joinは配列要素を文字列として結合するときに使用します。
const day = [2020, 07, 09]; day,join("/"); // 結果 2020/07/09splitはjoinとは逆で文字列を配列要素として取得します。
const day = "2020/07/09"; day,split(":"); // 結果 [2020, 07, 09]8.数値の操作
Math.floor(); 小数点以下切り捨て Math.ciel(); 小数点以下切り上げ Math.round(); 四捨五入 Math.変数.toFixed(桁数指定); 桁数を指定 Math.random(); 0以上1未満の数値をランダムに生成以上
- 投稿日:2020-07-09T11:51:45+09:00
連想配列の重複チェック 【Lodash, find】
概要
LodashのfindとArray.filterを利用して、連想配列の重複チェック&重複している物の配列と重複していない物の配列を作成します。
実装
const targetA = [ {name: "taro"}, {name: "ichiro"}, {name: "tetsuto"}, {name: "lisa"} ]; const targetB = [ {name: "yuki"}, {name: "ichiro"}, {name: "takeya"}, {name: "lisa"} ]; // 重複してるユーザーを取得 const duplicatedList = targetA.filter(user => { return _.find(targetB, user); }); // 重複していないユーザーを取得 const originalList = targetA.filter(user => { return !_.find(targetB, user); });結果
- 投稿日:2020-07-09T11:29:51+09:00
javascript 繰り返しの分 学習、メモ
はじめに
自分用のjavascriptの学習メモです。
for文の基本形
for ofの基本形
const scores = [10,20,30] for ( score of scores){ console.log(score); }for文の基本形
for(初期値; 繰り返す条件; 増減){}例文
for (let i = 0; i < 10; i++ ){ console.log(i); }上記は1から9が返る
switchの場合
const date = 2; switch(date){ case 1: console.log('1です'); break; case 2: console.log('2です'); break; case 3: console.log('3です'); break; default: console.log('該当しません'); break; } ====================== //簡単に書ける⬇️ if (date === 1){ console.log('1です'); } if (date === 2){ console.log('2です'); } if (date === 3){ console.log('3です'); }
- 投稿日:2020-07-09T11:29:51+09:00
javascript 繰り返しの文 学習、メモ
はじめに
自分用のjavascriptの学習メモです。
for文の基本形
for ofの基本形
const scores = [10,20,30] for ( score of scores){ console.log(score); }for文の基本形
for(初期値; 繰り返す条件; 増減){}例文
for (let i = 0; i < 10; i++ ){ console.log(i); }上記は1から9が返る
switchの場合
const date = 2; switch(date){ case 1: console.log('1です'); break; case 2: console.log('2です'); break; case 3: console.log('3です'); break; default: console.log('該当しません'); break; } ====================== //簡単に書ける⬇️ if (date === 1){ console.log('1です'); } if (date === 2){ console.log('2です'); } if (date === 3){ console.log('3です'); }
- 投稿日:2020-07-09T10:50:43+09:00
【非同期JavaScriptをざっと読む1】General asynchronous programming concepts
はじめに
この記事は非同期Javascriptの記事を2020年にざっと読んでみたものです。
JavaScriptを触り初めて一年が経ち非同期処理については少し理解しているものの、深くは理解していないので基本に立ち返ってしっかり読もうと思いました。
スピード重視で読むので無理な翻訳や間違いがあるかもしれません。ごめんなさい。今日はGeneral asynchronous programming conceptsを読みます。
一般的な非同期プログラミングの考え方
この記事では非同期プログラミングに関する多くの重要な考え方について学びます。そしてその非同期プログラミングがどのようにWebブラウザやJavaScriptで表現されいるかも学びます。これらの考え方を他の記事を読む前に理解することをおすすめします。
前提知識:基本的なコンピューターリテラシー、しっかりとしたJavaScriptの基礎の理解
目的:非同期処理の背景にある基本的な考え方の理解、またどうやってそれをブラウザとJavaScriptで実現しているかの理解非同期とは?
普通、与えられたプログラミングのコードは順々に処理が進んでいきます。このとき、ある一つのことは一回だけ起こります。もしある関数Aが他の関数Bに依存している場合、その関数Bが終了し値を返すまで関数Aは待つべきです。そしてこれら全ての処理が起こるまで、ユーザーから見ると全体のプログラムは基本的には止まっているように見えます。
例えばMacユーザーは時々、虹色のカーソル(あれビーチボールにも見えるよね)が回ったまま処理が止まっているように見えることを経験するでしょう。このカーソルは、OSが「現在、あなたが使っているプログラムは何かが完了するまで停止している必要があり、それにはとても時間がかかるため、あなたが今なにが起きているか疑問に思わないか私は心配してまっせ」と言っているようなものです。
これはイライラする経験で、そしてあまりコンピュータ処理の力をうまく利用できているとは言えません。 ______特にコンピュータが複数のプロセッサコアを使えるこの時代においては。他のタスクを他のプロセッサコアでシュッポッポと動かせ、さらにそれがいつ終わったか知らせることができるのにも関わらず、その何かが完了するまで座って待っていることはセンスがないです。この、待ってる間に他の仕事ができるようにすることこそが非同期プログラミングの基本と言えます。このようなタスクを非同期で実行できるAPIを提供するかどうかは、あなたの使っているプログラミング環境(例えばWeb開発の場合はWebブラウザ)次第です。
コード・ブロッキング(処理が止まって見える状態のこと)
非同期処理記述はとても便利です、特にwebプログラミングにおいて。あるWebアプリがあるブラウザで実行しており、ブラウザへの制御機能を保持することを無視してまでも、ある集中的なコードの塊を実行する場合、ブラウザは固まってしまったように見えるでしょう。これをブロッキングと言います。つまり、そのブラウザはユーザーの入力を処理しつづけることができなくなり、さらにそのWebアプリの処理が終わるまで他の処理を行うことができなくなります。
ここでいくつかの例で我々の言うブロッキングを示しましょう。
我々のつくったsimple-sync.htmlというファイルの例(実際にブラウザで動かしているリンクはこちら)では、クリックイベントリスナーをボタンにつけています。もしそれがクリックされたら、とある時間をめっちゃ無駄に使う処理(1000万の日付を計算し、最後の一つをコンソールに出力するもの)を行い、それが終わり次第DOMにパラグラフを挿入します。
まあコードを見てください、こんな感じです。
simple-sync.htmlconst btn = document.querySelector('button'); btn.addEventListener('click', () => { let myDate; for(let i = 0; i < 10000000; i++) { let date = new Date(); myDate = date } console.log(myDate); let pElem = document.createElement('p'); pElem.textContent = 'This is a newly-added paragraph.'; document.body.appendChild(pElem); });このサンプルファイルを実行しているとき、JavaScriptのコンソール画面を開いて、そんでボタンをクリックしてください。そうしたら、日付の計算が終了しコンソールメッセージが表示されたあとに初めてパラグラフが表示されることに気づくでしょう。このコードはソースに記述された通り順番に実行され、後の処理は前の処理が完了するまで実行されていません。
注:このサンプルはあんまり現実味がないです。だって実際のwebアプリ上で1000万の日付を計算することなんてないじゃないですか。まあでも、このサンプルはあなたに基本的な考えを教えてくれます。
次のサンプル、simple-sync-ui-blocking.htmlです。(実際にブラウザで動かしているリンクはこちら。)もうちょっと現実味のあるものをシミュレートしてるので、実際のWebページをイメージできるかもしれません。ここではユーザーの入力をユーザーインターフェースを表示することによってブロックしています。このサンプルファイルには2つのボタンがあります。
- "Fill canvas"ボタン。クリックされたときに
<canvas>
タグを100万の青い円で埋め尽くすボタン。- "Click me for alert"ボタン。クリックされた時にアラートメッセージを表示するボタン。
simple-sync-ui-blocking.htmlfunction expensiveOperation() { for(let i = 0; i < 1000000; i++) { ctx.fillStyle = 'rgba(0,0,255, 0.2)'; ctx.beginPath(); ctx.arc(random(0, canvas.width), random(0, canvas.height), 10, degToRad(0), degToRad(360), false); ctx.fill() } } fillBtn.addEventListener('click', expensiveOperation); alertBtn.addEventListener('click', () => alert('You clicked me!') );もし最初に"Fill canvas"ボタンをクリックしてそのあとすぐに"Click me for alert"ボタンをクリックした場合、処理中を示すサークルの表示が完了するまでアラートが表示されないことがわかります。ここでは最初の処理が2回目の処理の終了までブロックしています。
注:おっけー!我々のサンプルはちょっときもかったですね。ブロック処理を装ってみたんですけども。でも、こういうことってよくある問題なんですよ。実際のアプリ開発の現場においてはいつもこの問題を軽減できるよう頑張ってます。
なんでこんなことになってるって?一般的にJavaScriptがシングルスレッドだからです。ここで、スレッドの考え方について紹介しないといけませんね。
スレッド
スレッドとは基本的に単一の処理です。プログラムはタスクを完遂させるためにこれを使います。それぞれのタスクは一度に単一のタスクだけを行います。
タスクA --> タスクB --> タスクCそれぞれのタスクは順番に実行されます。つまり、あるタスクは次のタスクが開始される前に実行される必要があります。
さっき言ったように、多くのコンピュータは今やコアを複数持ってます。なので一度に複数のことを行うことができます。マルチスレッドをサポートしているプログラミング言語は複数のコアを、複数のタスクを同時に完遂するために使うことができます。
スレッド1: タスクA --> タスクB スレッド2: タスクC --> タスクDJavaScriptはシングルスレッドだよ
JavaScriptは元々はシングルスレッドです。複数のコアを持っている場合でも、シングルスレッドでタスクを実行することしかできません。このシングルスレッドをメインスレッドと言います。上記のサンプルファイルはこんな感じで実行されてます。
メインスレッド: サークルをキャンバスに表示 --> アラートを表示その後、JavaScriptはこのようなシングルスレッドでしか処理できないという問題を解決するためにいくつかのツールを得てきました。例えば、Web workersは一つ一つ分かれたスレッドに対してJavaScriptの処理を送ることを可能にします。このスレッドをワーカー(worker)と呼びます。これによって複数のJavaScriptの処理のまとまりを同時に実行することができます。複雑な処理を行う際にメインスレッドとは別のワーカーを使うよう実装することによって、ユーザーの入力がブロックされなくなります。
メインスレッド: タスクA --> タスクC ワーカースレッド: 複雑なタスクBこの処理を頭にイメージしてsimple-sync-worker.html(さっきとは違うよ。動いているサンプルはこちら)を見て、もう一度JavaScriptのコンソール画面を開いてみましょう。このサンプルファイルはさっきのサンプルファイルを書き直したものです。ここでは1000万の日付を独立したワーカースレッドで計算しています。今回はボタンをクリックしたときに、ブラウザはパラグラフを表示することができます。日付の計算をする前に。最初の処理はもはや次の処理をブロックしていません。
非同期処理を行うコード
Web workerはかなり便利ですが、いくつかできないことがあります。それの主要なものの一つがDOMにアクセスできないということです。つまり直接的にユーザーインターフェースを更新するためにワーカーを使うことができないということです。私たちはワーカーの処理によって100万の青い円を表示することはできない、したがって基本的には数値処理しかできないです。
二つ目の問題はワーカー内のコードはブロックされずに実行されますが、それは依然として基本的には同期的な処理であるということです。これは、ある関数が以前に起こった複数の処理の結果に依存する場合、問題となります。次のスレッドの図を見てみてください。
メインスレッド: タスクA --> タスクBこの場合において、タスクAはなにかサーバーから画像を取得するなどの処理を行い、タスクBがその後その画像に対してフィルターを適応するなどなんらかの処理を行うと考えることにしましょう。もしあなたがタスクAを開始し、そしてすぐにタスクBをを実行しようとするとエラーniなるでしょう。だってその画像はその時まだ取得できていないので。
メインスレッド: タスクA --> タスクB --> |タスク D| ワーカースレッド: タスクC ----------> | |この場合においては、タスクDがタスクBとCの両方の結果を利用する場合について考えています。もしこれらの結果が両方同時に利用できると保証できるのであれば大丈夫かもしれませんが、実際はそうじゃないことが多いです。もしタスクDがどちらかの入力が利用できない状態で実行される場合、それはエラーを出力することでしょう。
このような問題を解決するためにブラウザは特定の操作を非同期的に実行できるようになっています。Promises等はある実行処理(例えばサーバーから画像を取得するなと)をセットでき、セットした後、他の処理が実行される前にその結果が帰ってくるまで処理を止めることを可能とします。つまりこんな感じ。
メインスレッド: タスクA タスクB プロミス: |___非同期処理___|処理が別の場所で実行されているため、メインスレッドは非同期処理が行われている間ブロックされることはありません。
それでは次の記事で非同期処理を行うコードをどうやって書くのかをみていきましょう。すごいっしょ?がんばっていきましょ。
結論
現代のソフトウェアデザインは非同期プログラミングを使って動いているものが増加しており、それはプログラムに一つ以上のことを同時に行うことを可能にしている。あなたがより新しく強力なAPIを使う頃には、非同期処理を行わないとできないようなケースをいくつも確認できることでしょう。かつでは非同期処理のコードを書くのは大変でした。依然として慣れるまで時間がかかりますが、でもすごい簡単になってきています。この記事の残りでは、なぜ非同期処理のコードが重要か、またどうやって上記に書いたような問題を解決できるコードを書けるかを勉強していきます。
- 投稿日:2020-07-09T10:26:56+09:00
【JavaScript】基礎編
自分用の簡単な備忘録です。。。
1.switchで条件分岐
if文で条件の分岐が多数になるときに、switchを使用すればもっと簡単に書くことができます。
// 見本 変数名 = "値" switch (変数名) { case "値": 処理 break; case "値": 処理 break; case "値": 処理 break; default: 処理 break; }// 信号機の色別で出力内容を条件分岐してみます color = "red" switch (color) { case "red": console.log("STOP!!") break; case "yellow": console.log("be careful!!") break; case "blue": case "green": // caseは複数指定することもできます console.log("GO!!") break; default: // 条件のどれにも当てはまらなかった場合にdefaultで指定した内容が出力されます console.log("Not applicable!!") break; // 結果としてはredのSTOPが出力されます }2.whileのループ処理
while文は指定した条件が満たされるまで、ループ処理を繰り返して値を出力してくれる便利なプロパティです。
// 下記はiが1ずつ増えて100になるまでiの値を繰り返し出力するという命令です let i = 0; while (i <= 100) { console.log(i); i++; // この記述がないと無限に処理が実行されるので注意しましょう! }3.forのループ処理
for文はwhile文をさらに簡略化した記述ができるプロパティです。
// 見本 for (let 初期値を代入した変数名; 条件式; 変数の再代入) { console.log(i); }// 下記はiが1ずつ増えて100になるまでiの値を繰り返し出力するという命令です for (let i = 0; i <= 100; i++) { console.log(i); }4.テンプレートリテラル記法
下記のような記述で、文字列と変数を接続することができます。
const animal = "猫"; // シングルクオーテーションの代わりにバッククオートで${}に囲むことで文字列と変数を接続できます console.log(`この動物は ${animal} です` ); // 出力内容:この動物は猫です5.continueとbreak
continueは対象の値をスキップしたいときに、
breakは対象の値で処理を停止させたいときに使用しますfor文を使って見ていきます。
for (let i = 1; i <= 10; i++) { if (i === 4) { continue; } console.log(i); } // この条件式なら、1~10の出力の中で4の値のみスキップされますfor (let i = 1; i <= 10; i++) { if (i === 4) { break; } console.log(i); } // この条件式なら、4の値でループ処理が停止するので、3の値までしか出力されません6.関数
// 引数にはデフォルトで値を入れておくこともできる function 関数名(引数) { 処理 } // 呼び出す 関数名(引数);7.関数宣言と関数式の違い
6で紹介した関数の記述方法は関数宣言と言われています。
その他に関数宣言を変数に代入する関数式というものがあるので紹介しつつ違いを明確にしていきます。// 関数宣言 function 関数名(引数) { 処理 } 関数名(引数);関数式とは、関数を定数に代入することを言います。
// 関数式 const 定数 = function(引数) { // 関数名がいらなくなります 処理 }; // 定数に代入しているので末尾には;が必要です // 呼び出すときは定数名になります 定数名(引数);8.アロー関数
関数をもっと短く記述することができる記法です。
const 定数 = (引数) => { // functionがいらなくなり、=>が必要 return 処理 };また、引数が1つの場合やreturnの処理が1つの場合はもっと簡易的に気j仏することができます。
const 定数 = 引数 => 処理; // ()も{}も必要なくなります以上
- 投稿日:2020-07-09T10:00:24+09:00
JavaScriptのソースコードを読んでいる時に遭遇したこんな書き方知らなかったみたいなものたち
この記事は何?
私は最近とある会社でインターンとして働きはじめ、JavaScriptのソースコードを読んだり書いたりすることが増えました。私はJavaScriptだけでなく、PHPやC#でプログラムを書いたこともあるので、基本的な構文についてはおおよそ把握しているつもりでしたが、プログラムを読んでいる中で、この記述は一体なんなんだろう?というようなコードを見かけることが多かったので、備忘録的にこの記事にそれらをまとめていこうと思いました。この記事は随時、内容を追加していきたいと思っています。
delete文
delete オブジェクト名.プロパティ名
で指定したプロパティをオブジェクトから削除する。sample.jslet obj = {}; obj.prop = 1; console.log("プロパティ削除前:" + obj.prop ); delete obj.prop; console.log("プロパティ削除後:" + obj.prop );
- 投稿日:2020-07-09T03:39:22+09:00
【JavaScript】Promiseをしっかり理解したい【その2】
初めに
前回に引き続きPromiseの学習を行います。
【JavaScript】Promiseをしっかり理解したい【その1】Promiseの書き方
前回も軽く触れましたがPromiseを使った非同期処理の書き方について見ていきましょう。
Promiseによる非同期処理の書き方function asyncFunc(data) { // ①非同期処理を関数としてまとめる return new Promise((resolve, reject) => { // ②Promiseをインスタンス化し戻り値に設定 setTimeout(() => { console.log('--処理実行--'); if (data) { resolve(data * 2); // ③処理の成功時にresolveを呼び出す } else { reject('未入力です'); // ④処理の失敗時にrejectを呼び出す } }, 3000); }); } asyncFunc(2) // ⑤非同期関数の呼び出し .then((result) => { // ⑥Promiseオブジェクトでresolveされた場合thenメソッドへ処理が移る console.log(result); }) .catch((error) => { // ⑦Promiseオブジェクトでrejectされた場合catchメソッドへ処理が移る console.log(error); });実行結果--処理実行-- 4
もちろん他にも書き方はありますが、上記の例が個人的に簡潔でわかりやすいかなと思います。中身を見ていきましょう。①非同期処理を
asyncFunc
のように関数としてまとめます。②その関数では、Promiseオブジェクトが戻り値として返されるように記述します。⑤まとめた関数を呼び出し、③Promise内でresolveされた場合、⑥Promiseのインスタンスメソッドであるthen
に処理が移ります。then
メソッドでは主に非同期処理が完了した後に実行したい処理を記述します。また、④Promise内でrejectされた場合、⑦同じくPromiseのインスタンスメソッドであるcatch
に処理が移ります。rejectしてあげることでエラーハンドリングをすることができます。Promiseを使用しなかった場合
もし、上記の例でPromiseを使用しなかった場合はどうなるでしょうか。
Promiseを使用しなかった場合function asyncFunc(data) { setTimeout(() => { console.log('--処理実行--'); if (data) { return data * 2; } else { return '未入力です'; } }, 3000); } const result = asyncFunc(2); console.log(result);実行結果undefined --処理実行--
Promiseを使用しなかった場合、setTimeout関数内の処理が始まる前に最後の行のconsole.logが呼び出されてしまっていることがわかります。
thenメソッド
then
はPromiseのインスタンスメソッドです。thenの構文const p = new Promise(...); p.then(onFulfilled, onRejected);onFulfilled
Promiseオブジェクト内でresolveした時、
onFulfilled
が呼ばれます。onRejected
Promiseオブジェクト内でrejectした時、
onRejected
が呼ばれます。
onFulfilled
とonRejected
はどちらもオプショナル(任意)な引数なので、設定しなくても構いません。
thenメソッドでonRejected
が扱えるということは、thenメソッドだけで成功時と失敗時の両方の処理が書けるということになります。
今までの例でエラー処理はcatchメソッドを使用していましたが以下のようにthenメソッドで一纏めにすることも可能です。thenメソッド内でエラー処理も行う場合function asyncFunc(data) { return new Promise((resolve, reject) => { setTimeout(() => { console.log('--処理実行--'); if (data) { resolve(data * 2); } else { reject('未入力です'); } }, 3000); }); } asyncFunc() // 引数なし .then( (result) => { // ①onFulfilled console.log(result); }, (error) => { // ②onRejected console.log(error); } );実行結果--処理実行-- 未入力です
上記の例では、resolveされた場合は①の
onFulfilled
が呼ばれ、rejectされた場合は②のonRejected
が呼ばれます。asyncFunc関数の呼び出し時に引数を渡していないので、Promiseオブジェクト内でrejectされ、実行結果からもonRejected
が呼び出されていることがわかります。catchメソッド
catch
もthenと同様にPromiseのインスタンスメソッドです。catchの構文const p = new Promise(...); p.catch(onRejected);catchメソッドの場合は
onRejected
しかありません。
今までの例でもエラー処理はcatchメソッドを使用してきました。個人的にthenメソッドでonFulfilled
とonRejected
の両方を書くよりも、catchメソッドを使って処理を別々に分けたほうがわかりやすいかと思っています。Promiseの状態
Promiseオブジェクトには3つの
状態
というものがあります。Fulfilled
Promiseオブジェクト内でresolve(成功)した時。このとき
onFulfilled
が呼ばれます。Rejected
Promiseオブジェクト内でreject(失敗)した時。このとき
onRejected
が呼ばれます。Pending
Fulfilled
またはRejected
ではない時。Promiseオブジェクトが作成された初期状態や処理の実行中がこの状態に当てはまります。Promiseオブジェクトの状態は、一度
Pending
からFulfilled
やRejected
に移ると、そのPromiseオブジェクトの状態はそれ以降変化しません。なので、then等に書いた処理が実行されるのは一度きりになります。
また、このことからFulfilled
とRejected
のどちらかの状態であることを、「確立した、不変な」を意味するSettled
と表現することがあるそうです。Settled
Promiseオブジェクト内でresolve(成功) または reject(失敗) した時。つまり
Fulfilled
かRejected
の状態である時。Promiseを抽象化すると
Pending(未決)
からSettled(解決)
の状態変化である、と言えるでしょう。Promiseの状態については実装等で直接使用することはありませんが、Promiseの理解の為に頭の片隅に入れておくと良いかと思われます。まとめ
実際に実装する分には、今回のPromiseの書き方だけ押さえておけば問題ないかと思います。ただ、根本的な理解だとまだまだ足りないことが多いと感じているので引き続き学習していきたいと思います。
- 投稿日:2020-07-09T02:21:21+09:00
HTMLのchangeイベントを強制発火させる
次のような処理をした場合changeイベントは発火するのか
<!--HTML (Angular)--> <div> <input type="text" id="hoge" (change)="hogeEvent()"> </div>//TypeScript (Angular) (document.getElementById('hoge') as HTMLInputElement).value = huga;確認してみましたが、残念ながら発火してくれませんでした。というわけで次の行をTypeScriptに追加してあげましょう。
//TypeScript (Angular) let event = new Event('change'); (document.getElementById('wegpunkte') as HTMLInputElement).dispatchEvent(event);new Event('change')を生成してdispatchEventで発火させることで、changeと対応するイベント(例のコードではhogeEvent)が実行されます。
もし親要素にもchangeイベントがあって発火させたいのであれば、bubblesオプションで
let event = new Event('change',{bubbles:true});と追加してあげれば祖先に当たる要素でも実行されます。
- 投稿日:2020-07-09T01:57:11+09:00
複数の画像を生成してローカルに保存
HTML 要素を画像化した複数のファイルをローカルの専用サーバーに送って保存します。
シリーズの記事です。
- dom-to-imageを試す
- html2canvasを試す
- 複数の画像を生成してローカルに保存 ← 今回の記事
ライブラリ
前回まで同じ目的の2つのライブラリを比較しました。どちらも Firefox では文字サイズが変わってレイアウトが崩れるため、今回は Chrome を前提に進めます。
テキストだけを含む要素の画像化ではどちらを使ってもほとんど差がありませんでした。XMLHttpRequest でファイルを送るには Blob に変換する必要があるため、その機能をサポートしている dom-to-image を使用します。
HTML 要素を画像化した Blob の生成は簡単にできます。
let blob = await domtoimage.toBlob(src);サーバー
以下の記事で作ったファイルを受け取るサーバー (server.ts) を使用します。
ファイルを受け取りたいディレクトリで server.ts を起動して待ち受けます。
スクリプト
画像を生成するための描画は済んでいるという前提で、送信に関係するコードを示します。
log
コンソールを模した
div
にログを出力します。function log(str) { result.appendChild(document.createTextNode(str)); result.appendChild(document.createElement("br")); }以下の記事のコードを簡略化したものです。
XMLHttpRequest
XMLHttpRequest を Promise 化します。
function send(method, url, blob) { log(`${method} ${url}`); return new Promise((resolve, reject) => { let req = new XMLHttpRequest(); req.open(method, url); req.onload = () => { let result = `${req.status} ${req.response}`; if (req.status == 200) { log(result); resolve(); } else { reject(result); } }; req.onerror = () => reject("error"); req.onabort = () => reject("abort"); req.ontimeout = () => reject("timeout"); req.send(blob); }); }正常終了は
resolve
、エラーはreject
を呼びます。4種類のコールバックをチェックしているのは、以下の記事を参考にしました。
以下の4つのイベントは同時に発火することがありません
- onload
- onerror
- onabort
- ontimeout
よって、4つ全て指定しないと通信の終了を取りこぼす可能性があります
送信
画像の Blob を XMLHttpRequest で送信します。画像をすべて送信したら、サーバーを終了させます。
try { result.innerHTML = ""; for (let [i, src] of sources) { let blob = await domtoimage.toBlob(src); await send("POST", `http://127.0.0.1:8080/${i}.png`, blob); } await send("GET", "http://127.0.0.1:8080/end"); } catch (e) { log(e); log("Aborted."); }成功した時の状況です。
クライアント側(ブラウザ)POST http://127.0.0.1:8080/0.png 200 <m>created</m> POST http://127.0.0.1:8080/1.png 200 <m>created</m> POST http://127.0.0.1:8080/2.png 200 <m>created</m> POST http://127.0.0.1:8080/3.png 200 <m>created</m> POST http://127.0.0.1:8080/4.png 200 <m>created</m> GET http://127.0.0.1:8080/end 200 <m>bye!</m>サーバー側$ deno run --allow-net --allow-read --allow-write server.ts http://127.0.0.1:8080 127.0.0.1 OPTIONS /0.png 127.0.0.1 POST /0.png 0.png 18321 created 127.0.0.1 OPTIONS /1.png 127.0.0.1 POST /1.png 1.png 18537 created 127.0.0.1 OPTIONS /2.png 127.0.0.1 POST /2.png 2.png 18706 created 127.0.0.1 OPTIONS /3.png 127.0.0.1 POST /3.png 3.png 18573 created 127.0.0.1 OPTIONS /4.png 127.0.0.1 POST /4.png 4.png 18562 created 127.0.0.1 GET /endローカルで閉じた通信です。サーバーを起動したディレクトリには受信したファイルがあります。
$ ls 0.png 1.png 2.png 3.png 4.pngサンプル
文章の選択個所を変えた画像を生成するサンプルです。
[Send to Local] を実行するには、server.ts を起動しておく必要があります。
感想
ブラウザを画像生成のエンジンとして使えるようになったので、文字中心のコンテンツを動画化するのに威力を発揮しそうです。一部の色を変えるような単純な効果でも、手動だと大変でしたが、自動化できれば大幅に省力化できます。
以前書いた記事では連続画像を効果的に生成する方法がないため紙芝居レベルに留まっていましたが、今回の方法を応用すれば動きを付けることもできそうです。
- 投稿日:2020-07-09T01:29:06+09:00
ローランドの名言をコールバック関数を使って出力してみる
Javascriptにおいて
関数を変数として扱う場合も多いらしいので、
いろんな方法でローランドの発言を出力してみる。通常
console.log('俺か、俺以外か');関数
function roland(){ console.log('俺か,俺以外か'); } roland();コールバック関数(変数代入)
function roland(callback) { console.log(callback()); } const remark = function() { return '俺か俺以外か'; } roland(remark);
console.log(callback());
は関数の実行結果が返ってくる
console.log(callback);
は関数自体が返ってくるコールバック関数(無名関数)
function roland(callback) { console.log(callback()); } roland( function() { return '俺か、俺以外か'; })コールバック関数(文字列結合)
function roland(me, otherThanThat) { console.log(me(otherThanThat)) } roland( function(otherThanThat) { return '俺か' + otherThanThat; }, '俺以外か');ここ一番ややこしい。。
コールバック関数(演算)
function roland(a, b, callback) { const result = callback(a, b); console.log(result); } function remark(a, b) { return a + b; } roland('俺か', '俺以外か', remark);
- 投稿日:2020-07-09T00:09:45+09:00
cookieで条件分岐するときはjQueryよりPHPがいいってハナシ。
「同じページに2回目以降は表示させない」
というのをやりたかったのですが、
つまったので、苦労の後を記録しておきます。jQueryの場合
jQueryのクッキー使うときは、以下のように
- jQuery
- jquery.cookie.js
- cookieを発火させる記述
の順番に記述します。
<script src="js/jquery-3.4.1.min.js" type="text/javascript"></script> <script src="js/jquery.cookie.js" type="text/javascript"></script> <script> //クッキーがあれば、#js-loaderを非表示にする if ($.cookie("access")) { $("#js-loader").css('display','none'); } //クッキーを取得させる $.cookie("access","topPage"); //2つ目は任意の文字を入れます </script>こちら参考にしました
-> jQueryプラグイン「jquery.cookie.js」でcookieを簡単に扱うこれでも機能したのですが、
javascriptの読み込みが遅い場合、
#js-loaderが最初にちょこっと表示されてしまいます!!!
これだとクッキーで条件分岐する意味がない。。。。なので、最初っからキレイに非表示にしたい場合、
PHPを使いましょう!PHPの場合
以下のように記述しました。
まず、最初のページに表示させる条件分岐を書き、
その中に、クッキーがあるかどうかで条件分岐させます。<?php if(is_front_page()): if(!(isset($_COOKIE["topPage"]))): //クッキーが保存されていない時 []内は任意の文字 setcookie("topPage", 1); //クッキーをいれる 2つ目は任意の値を入れます ?> <div id="js-loader"> ローディングアニメーションの要素 </div> <?php endif; //クッキーの分 ?> <?php endif; //is_front_pageの分 ?>これでスッキリ解決しました!!
こちら参考にしました!
-> PHPでCookieを使う方法【初心者向け】
-> クッキーの読み込み