20200709のJavaScriptに関する記事は26件です。

Nuxt.js: middlewareを使って特定のページ以外からのアクセスをリダイレクトする

成り行き

Nuxtを使用しているアプリを作成中、ログイン後の二段階認証ページなどログインした後にアクセスしてほしくないページにアドレス直打ちで入れることに気づきました。

Nuxtにはmiddlewareというページレンダリングを行う前に設定されたメソッドを実行してくれるAPIがあります。今回はmiddlewareを用いて、特定のページ以外からのアクセスを行なった場合リダイレクトするようにしました。

コード

auth.js
export default function({ from, redirect }) {
  if (!from || from.path !== '/login') { // 利用される場合、/loginには任意のパスを入れてください
    redirect('/');
  }
}

あとはこれをページのmiddlewareプロパティに設定するだけです。

解説

fromは遷移元のrouteデータが入っています。
今回の場合ログイン後に表示される二段階認証ページへのアクセスを制限したかったので、fromに入っているpath/login以外だった場合リダイレクトが実行されるようにしました。
またアドレス直打ちの場合、fromはundefinedになっていたのでその場合も含めました。(個人的にはこちらがメイン)

Nuxt - API: コンテキスト

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

【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の値を使用する事ができる。

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

WWDC 2020 Web Developer 向け情報

Web Developer 向けの WWDC 情報です。個人的には Safari Web Extension が追加されて、Safari を使ってる方にも自分の Web Extension が提供できるようになったのが一番嬉しかったです。
なんというかやっぱり色んなベンダーを超えたブラウザで同じような体験が提供できるのがウェブテクノロジーの強みなので、よかった。

WWDC for Web Developer

ざっくりまとめ

  1. Safari (WebKit) がアップデート
  2. iOS のデフォルトブラウザを Safari 以外に変更可能に
  3. App Clip が登場、ウェブページでもサポート
  4. Safari Web Extension が登場
  5. WKWebView のアップデート
  6. (今回は省略) Sign in with Apple の強化
  7. (今回は省略) Touch ID / Face ID の対応
  8. (今回は省略) 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);
        });
        
      • 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

        • バーチャルキーボードのエンターキーの名前を設定できるようになる スクリーンショット 2020-07-07 16.43.58.png
      • 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

      • アプリをインストールすることなくユーザーにアプリ体験を提供することができる機能

      スクリーンショット 2020-07-08 9.42.13.png

      スクリーンショット 2020-07-08 9.42.36.png
      - ウェブサイトに入った時にメタデータを定義しておくと、それを知らせることができる

      <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 になるとかそういうのではない
  • 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 をオンにしておく。

  1. xcode のプロジェクト作成で Safari Extension App を選択
  2. Type: Safari Web Extension を選択 (Safari App Extension ではない)
  3. Resources の中身を置き換えて、ビルドする。
  4. Safari の Extensions タブに拡張機能が追加される
  5. URL バーの左に拡張機能がインストールされ、実行できる(んじゃないかな〜)

その他

  • moz/Chrome 拡張機能をハードコードしている場合 Safari では動かないので、スキームを browser.runtime と標準の API を使用するようにしなければならない
  • 通知系 API はサポートされていない
  • ネイティブメッセージング API を使ってネイティブアプリと通信することができたりする
browser.runtime.sendNativeMessage // ネイティブにメッセージを送る
browser.runtime.connectNative // ネイティブと接続する
SFSafariExtensionManager.getstateOfSafariExtension // 拡張機能の状態を知る
SFSafariApplication.dispatchMessage // 拡張機能にメッセージを送る
  • 配布はどうやるのかまだ言及されてない気もするけどいまのところの雰囲気だと App Store からの配布になるのかな
    • Chrome Extension みたいに審査する感じですね
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Google スプレッドシートのデータを JS で fetch したい!

Google スプレッドシートを方眼紙としてではなく、ちゃんとデータを保存するテーブルとして使用していた場合、外部のシステムからそのデータを使用したいと思うのは必然ですよね?なんなら Web のフロントエンドから直接 JavaScript で fetch して使いたいですよね??

それ簡単にできますよ?そう、Google Apps Script ならね!

例えば Google スプレッドシートにこんなデータがあって、このデータを外部から JSON で取得したいとしましょう。この記事の最後に完成するこんな API を作るという事です。

user-list.png

それには、前述のように Google Apps Script を書く必要があるので、ツールからスクリプトエディタを開きます。

script-editor-menu.png
plain-editor.png

ただこのエディタ、挙動がおかしくてすこぶるストレスがたまるので、ちょっとしたスクリプトを書いたり既存のスクリプトの簡単な修正ならともかく、ある程度本格的に書くつもりならば手元の使い慣れたエディタで書いたものを貼り付けた方が良いでしょう。

さて、デフォルトの関数は要らないので消して、代わりの関数を書いてスプレッドシートのデータをオブジェクトのリストに詰めてみましょう。こんな風になります。

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 で結果を出力するようにしました。実行前に保存する必要があるので、適当な名前を付けて保存しましょう。

script-name.png

ここで上部の▶アイコンで getDate を実行しようとすると、初回のみ権限の付与が求められます。

authentication.png

これは「スプレッドシートの読み書きを誰の権限で実行するか」という設定で、通常はスプレッドシートの所有者として許可して良いでしょう。ところがその途中で下記のような警告が出ます。

unsafe-page.png

これの意味するところは「この Google が知らない謎のスクリプトは、今からお前のスプレッドシートを読み書きする権限を要求するぞ、気をつけろ!」です。マーケットプレイスなどに登録された Google 確認済みのアドオンに許可を与える場合などは表示されないものと思われます。

今回の場合、自分自身は信頼できるデベロッパーなので、詳細の中にある「ユーザ一覧 API (安全ではないページ) に移動」をクリックして進めます。権限の付与が済むと無事に関数が実行できるようになるので、▶アイコンで実行後にログを確認しましょう。

log-menu.png
log.png

スプレッドシート上のデータがいい感じにリストにまとまってることが確認できましたね!

次に、ここで取得したデータを 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 として公開します。公開からウェブアプリケーションとして導入を選んで下さい。

publish-menu.png
publish-new-version.png

ここまでの過程でもちょこちょこ翻訳漏れを見かけましたが、このダイアログはほぼ完全に英語です。いかに日本のユーザが少ないか、あるいは日本が軽視されているかが分かりますね。

ここで、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 としてアクセスすることが出来るようになります。

published.png

今回発行された URL は https://script.google.com/macros/s/AKfycbyRzwWIOOl6xI-c-kB6-rKYm5L-UwBRe_FZwD13_n0An4_Pyeg/exec です。試しに開いてみて下さい。どうでしょう?思った通りの JSON が取得出来ましたか?

なお、何かしらの修正を行って保存をしても、その内容が即座に反映されたりはしません。Google Apps Script 上でバージョン管理がなされているためです。API を更新したい場合は、再び公開からウェブアプリケーションとして導入を選びます。

new-version.png

初期状態では、最後に公開したバージョンが選ばれているので、Project version に "New" を選び直して更新することで、最新の状態を反映することが出来ます。この際すでに発行済みのユニーク URL は変わらないので、API を fetch しているクライアント側の変更は必要ありません。

Google スプレッドシートのデータを外部に API を通じて JSON として公開する方法は以上になります。当初は他にも色々と書こうと思っていたのですが、大分長くなったのでいったんここで一区切りとします。
この記事で扱ったのはあくまでデータを読み出す部分だけでしたが、反響が大きければ下記の内容も別に書こうと思います。

  • Google フォームを使ってデータを集めてスプレッドシートに溜める
  • POST メソッドも Google Apps Script で受けられるようにして、より高度なフォームを自作する
  • cron のように定期的に Google Apps Script を実行して、必要な時にメールで通知する

LGTM お待ちしています!

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

Firebase SDKをJestでMockしてテストする

やりたいこと

Firebase SDKを利用した関数のテストをしたいと思い立ちました。実際にAPIにアクセスせずにテストを実行したいので、Firebase SDKをMockしてテストを行うこととしました。テストはJestを使います。

テスト対象のコード

例としてCloud Firestoreを利用する際の手順を記載します。
なお、以下には記載していませんが、環境構築は終わっているものとすします。(手順はこちらの通り!)

以下のようなFirestoreのドキュメントを監視して更新があったときにsomethingをする関数をテストすることにしてみます。

target.js
  async watchDoc({ commit }) {
    await firebase
      .firestore()
      .collection('users')
      .onSnapshot((snapshot) => {
        snapshot.forEach((doc) => {
          doSomething(doc)
        })
      })
  }

Colud Firestoreは以下のように初期化されていることとします。

initFirebase.js
import 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.js
import * 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の構築方法などは別の記事などを参考にしてください。。

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

RailsとAjaxを使ったいいね機能の非同期通信

転職活動用に個人アプリを開発中です。
今回、RailsとAjaxを使って、いいね機能の非同期化を行いました。

AjaxではjQueryを使うため、jQueryを使えるようにしておく事前準備が必要です。それは参考記事を見てください。以下の記述はそれが設定済みのうえでの話です。

PFCMASTERいいね機能Ajax.png

実現した機能

・「いいね」ボタンを押すとリロードせずに「いいねを取り消す」に表示が変わる
・「いいね」ボタンを押すとリロードせずにlikesテーブルにデータが1つ追加される
・「いいね」ボタンを押すとリロードせずにいいね数が1つ増える
※その逆もしかり

このコードでうまくいきました

コントローラー(likes_controller.rb)

likes_controller.rb
class 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_tobutton_toには:remoteオプション(remote: true)がある。button_toremote: 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: true

3.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.rb
def show
  @post = Post.find(params[:id])
  Like.new
end

2._like.html.hamlbutton_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を使い非同期対応。で

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

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();

Image 1.png

Q:継承はとうなるの?
A:継承?Javascript 継承させる?

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

秘密にしておきたかったんだけど、実はTwitterにはWebページを埋め込めるんだよね

こんにちは、Twitter大好き丸の あかい です:baby_chick:

タイトルの通りなのですが、実はTwitterにはWebページを埋め込むことができます。(具体的には「ツイートには」ですが)

「WebページにTwitterを埋め込む」ではないですよ。
次のツイートをご覧ください。

……何やら再生できそうな感じのインターフェースが表示されていますよね?

そうなのです、このツイートを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 タイプのカードにはなんかリッチなコントロールがついた動画や音声だけに設定するようにしましょう……:pensive:

そういうわけで、拙作の 朱猪わらい は、便宜上、
スクショや共有のできる、ちょっと便利なコントロールのついたランダム性のあるビデオ です。

いやぁ、面白い仕様見つけたなぁと思ってワクワクしながらたまたま実装したのが、
ユーザーを驚かせたりしないようなビデオで良かったビデオで良かった

おまけ

朱猪わらいについて

この仕様を知った時、試してみたくて作ったゲ動画です。
とくに面白いところがあるわけではないのですが、Player カードの可能性を感じてもらうことは出来たかと思います。

リポジトリを置いておきますので、ぜひ参考にしてください。

iframeの詰まったポイント

iframeを利用すると、別ドメイン間の制約とかで苦しい思いをするのは
日本昔ばなしでもよく語られていますが(語られていません)、
今回朱猪わらいを実装する上でも引っかかりました。

中でも大変だったのは生成された画像の保存機能です。
調べれば色々でてくるかと思いますが、普通、iframe内のデータを端末に保存させることはできないのですよね。

これを回避するため、クエリパラムを利用して別ウィンドウで結果画像を再現し、
別ウィンドウでダウンロードさせて一瞬でウィンドウを閉じるという力技を使いました :muscle:

怪我の功名と言いますか、一石二鳥と言いますか、URLから状態を復元できる設計になったので、
結果をTweetで共有できるように作れたのはよかったですね。

Card Validatorのサムネが更新されない問題

Twitter カードの再生マークのところに表示されるサムネイルは、
twitter:imageという名前(name属性)の付いたメタタグで設定できるのですが、
一度設定した後、GitHub Pagesに画像を上げ直しても、新しい画像で反映されなくなってしまいました。

Card ValidatorのPreview Cardボタンを何度押しても変わりません。

どうしようかなぁと思ってしばらくなやんだのですが、
画像だけではなく、ファイル名とメタタグのurlを書き換えたらちゃんと反映されるようになりました。

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

更新後の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処理ができます

やったぜ。

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

【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.css
heigh: 100px;

w100

hoge.css
width: 100px;

bc

hoge.css
background-color: #fff;

c

hoge.css
color: #fff;

ta

hoge.css
text-align: center;

m10

hoge.css
margin: 10px;

p10

hoge.css
padding: 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/

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

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/

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

【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>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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を扱うことはできません <-

以上

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

吉野家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.js
let 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文や,関数,変数の置き方)のいい練習になると思います。

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

【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(, , 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/09

splitはjoinとは逆で文字列を配列要素として取得します。

const day = "2020/07/09";
day,split(":");
// 結果
[2020, 07, 09]

8.数値の操作

Math.floor(); 小数点以下切り捨て
Math.ciel(); 小数点以下切り上げ
Math.round(); 四捨五入
Math.変数.toFixed(桁数指定); 桁数を指定
Math.random(); 0以上1未満の数値をランダムに生成

以上

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

連想配列の重複チェック 【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-09 11.52.15.png

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

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です');
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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です');
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【非同期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.html
const 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.html
function 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 --> タスクD

JavaScriptはシングルスレッドだよ

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を使う頃には、非同期処理を行わないとできないようなケースをいくつも確認できることでしょう。かつでは非同期処理のコードを書くのは大変でした。依然として慣れるまで時間がかかりますが、でもすごい簡単になってきています。この記事の残りでは、なぜ非同期処理のコードが重要か、またどうやって上記に書いたような問題を解決できるコードを書けるかを勉強していきます。

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

【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 定数 = 引数 => 処理;
// ()も{}も必要なくなります

以上

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

JavaScriptのソースコードを読んでいる時に遭遇したこんな書き方知らなかったみたいなものたち

この記事は何?

 私は最近とある会社でインターンとして働きはじめ、JavaScriptのソースコードを読んだり書いたりすることが増えました。私はJavaScriptだけでなく、PHPやC#でプログラムを書いたこともあるので、基本的な構文についてはおおよそ把握しているつもりでしたが、プログラムを読んでいる中で、この記述は一体なんなんだろう?というようなコードを見かけることが多かったので、備忘録的にこの記事にそれらをまとめていこうと思いました。この記事は随時、内容を追加していきたいと思っています。

delete文

 delete オブジェクト名.プロパティ名 で指定したプロパティをオブジェクトから削除する。

sample.js
let obj = {};
obj.prop = 1;

console.log("プロパティ削除前:" + obj.prop );

delete obj.prop;
console.log("プロパティ削除後:" + obj.prop );

実行結果
samplejs.png

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

【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が呼ばれます。

onFulfilledonRejectedはどちらもオプショナル(任意)な引数なので、設定しなくても構いません。
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メソッドでonFulfilledonRejectedの両方を書くよりも、catchメソッドを使って処理を別々に分けたほうがわかりやすいかと思っています。

Promiseの状態

Promiseオブジェクトには3つの状態というものがあります。

Fulfilled

Promiseオブジェクト内でresolve(成功)した時。このときonFulfilledが呼ばれます。

Rejected

Promiseオブジェクト内でreject(失敗)した時。このときonRejectedが呼ばれます。

Pending

FulfilledまたはRejectedではない時。Promiseオブジェクトが作成された初期状態や処理の実行中がこの状態に当てはまります。

Promiseオブジェクトの状態は、一度PendingからFulfilledRejectedに移ると、そのPromiseオブジェクトの状態はそれ以降変化しません。なので、then等に書いた処理が実行されるのは一度きりになります。
また、このことからFulfilledRejectedのどちらかの状態であることを、「確立した、不変な」を意味するSettledと表現することがあるそうです。

Settled

Promiseオブジェクト内でresolve(成功) または reject(失敗) した時。つまりFulfilledRejectedの状態である時。

Promiseを抽象化するとPending(未決)からSettled(解決)の状態変化である、と言えるでしょう。Promiseの状態については実装等で直接使用することはありませんが、Promiseの理解の為に頭の片隅に入れておくと良いかと思われます。

まとめ

実際に実装する分には、今回のPromiseの書き方だけ押さえておけば問題ないかと思います。ただ、根本的な理解だとまだまだ足りないことが多いと感じているので引き続き学習していきたいと思います。

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

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});

と追加してあげれば祖先に当たる要素でも実行されます。

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

複数の画像を生成してローカルに保存

HTML 要素を画像化した複数のファイルをローカルの専用サーバーに送って保存します。

シリーズの記事です。

  1. dom-to-imageを試す
  2. html2canvasを試す
  3. 複数の画像を生成してローカルに保存 ← 今回の記事

ライブラリ

前回まで同じ目的の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 を起動しておく必要があります。

感想

ブラウザを画像生成のエンジンとして使えるようになったので、文字中心のコンテンツを動画化するのに威力を発揮しそうです。一部の色を変えるような単純な効果でも、手動だと大変でしたが、自動化できれば大幅に省力化できます。

以前書いた記事では連続画像を効果的に生成する方法がないため紙芝居レベルに留まっていましたが、今回の方法を応用すれば動きを付けることもできそうです。

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

ローランドの名言をコールバック関数を使って出力してみる

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);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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を使う方法【初心者向け】
-> クッキーの読み込み

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