20210501のJavaScriptに関する記事は30件です。

Javascript あれこれ

Background 久しぶりにJavascriptの知識の整理を実施することになった 良くわかりにくくなる事項について、自分の理解の整理のために本記事を投稿する 1. 関数の記法 1-1. 通常版 通常版ではfunctionキーワードに関数名を続けて記載する (定義) // 関数名: sayHello // 引数: name function sayHello(name) { return `${name}さん、こんにちは!`; //処理 } (実行方法) console.log(sayHello('Taro')); 1-2. 無名関数 - 無名関数では関数定義を変数の値として代入して定義する - 関数定義は呼び出し側の前に行う必要がある(いわゆる巻き上げが発生しない) - 実行する場合は関数を代入した変数に引数をぶち込む - 関数リテラルとも呼ばれる(リテラルとはソースコードに直接ベタ書きした文字や数字のこと) (定義) // 関数を代入する変数名: myfunc // 引数: name const myfunc = function (name) { return `${name}さん、こんにちは!`; //処理 } (実行方法) console.log(myfunc('Taro')); 1-3. アロー関数 functionキーワードの代わりに「=>」(矢)を使って無名関数を記述する アロー関数では、thisはアロー関数が宣言された場所によって決定される つまり定義したコンテキストでthisが固定される これによってvar self = this; を書かなくてよくなったりする (定義) // 関数を代入する変数名: myfunc // 引数: name // 平たく書くと、 // 無名関数: function(引数,...){...関数の本体...} // アロー関数: (引数,...)=>{...関数の本体...} const myfunc = (name) => { return `${name}さん、こんにちは!`; //処理 } // 関数処理が1文の場合`{}`を省略できる const myfunc = (name) => `${name}さん、こんにちは!`; // 引数が1つの場合、引数の`()`も省略できる const myfunc = name => `${name}さん、こんにちは!` // 引数が無い場合は`()`だけを記載する const myfunc = () => "こんにちは!" (実行方法) console.log(myfunc('Taro')); Reference JavaScriptの無名関数について現役エンジニアが解説【初心者向け】 async/await 入門(JavaScript) export defaultってなんだろう イマドキのJavaScriptの書き方2018 【JavaScript入門】誰でも分かるPromiseの使い方とサンプル例まとめ!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

複数のセレクトボックスをJavaScriptで連動させる方法

まずは以下画像の赤枠部分を見ていただきたいのですが、2つのセレクトボックスを連動させて、左のセレクトボックス(画像では「食材群」)で値を選ぶと、右のセレクトボックス(同「食材」)の選択肢が動的に変更されるようなコードを調べました。 例えば、食材群で「野菜類」を選ぶと、食材の選択肢には野菜の仲間が表示されます。 また、食材群で「魚介類」を選ぶと、食材の選択肢には魚介の仲間が表示されます。 この機能は以下のコードで実現できます。 select_change.js // 食材群選択 $(function(){ var $children_default = $('.children'); var original = $children_default.html(); $('.parent').change(function() { var $children = $("#children"); var val1 = $(this).val(); $children.html(original).find('option').each(function() { var val2 = $(this).data('val'); if (val1 != val2) { $(this).remove(); } }); if ($(this).val() === '') { $children.attr('disabled', 'disabled'); } else { $children.removeAttr('disabled'); } }); }); food_register.html <form method="post"> 食材群: <select class="parent" id="parent" name="parent" required> <option value="" selected="selected" disabled> 食材群を選択して下さい <option value="vegetables">野菜</option> <option value="seafood">魚介類</option> </select> 食材: <select class="children" id="children" name="children" disabled> <option value="0" selected="selected" disabled> 食材群を選択して下さい </option> <option value="1" data-val="vegetables">キャベツ</option> <option value="2" data-val="vegetables">白菜</option> <option value="3" data-val="vegetables">レタス</option> <option value="4" data-val="seafood">アジ</option> <option value="5" data-val="seafood">タイ</option> <option value="6" data-val="seafood">マグロ</option> </select> </form> ちなみに、画像は食品ごとの食材(栄養価)を登録するアプリケーションの一部で、カレーライスの1人分の食材は以下のような分量となります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue】v-bindとv-onとv-modelの関係【基本】

0. はじめに 本記事は、Vueのディレクティブであるv-bindとv-onとv-modelの役割と関係を整理することが目標です。 今回の説明で出てくるプログラムはVue CLIの<template>, <script>内に書くことを想定しています。 1. v-bindとv-onとv-modelの関係 1-1. v-bindについて v-bindは htmlの属性をVueインスタンスのプロパティによって束縛する役割があります。 文章だとわかりにくいのでサンプルプログラムです。 template <template> <div id="app"> <input type="text" v-bind:value="myName"> </div> </template> Vueインスタンス export default { name: 'App', data() { return { myName: 'MouMou' } } } inputタグのvalue属性の前にv-bind:がついています。そのあとに="myName"がついています。 これはvalue属性の値を「VueインスタンスのdataプロパティのmyName」によって束縛します。という意味です。 dataプロパティのmyNameの値が変更するとv-bind:valueの値もmyNameに依存して変更されます。 ただし逆は成り立ちません。図にすると以下のようになります。 dataプロパティのmyNameの値を「太郎」にすると、テキストボックスの中身(= value)は「太郎」になりますが、 テキストボックスの中身(= value)を「太郎」にしても、dataプロパティのmyNameの値は「太郎」になりません。 v-bind:Vueインスタンス内のデータ→html属性 の単方向のみ 1-2.v-onについて v-onはイベントのサブスクリプションを行います。 サブスクリプションとはイベントの起動を監視して、イベント発火時に指定したプログラムの実行を行う機能です。 サンプルプログラムは以下です。 template <template> <div id="app"> <input type="text" v-on:input="inputEvent"> </div> </template> Vueインスタンス export default { name: 'App', methods:{ inputEvent(event) { console.log(event.target.value); } } } inputタグ内にv-on:input="inputEvent"があります。 これは、テキストボックスに文字がinputされたらinputEventメソッドを起動する。という意味です。 なお、inputEventメソッドに引数があります。こうすることでDOMイベントを渡すことが可能です。 (event.target.valueはテキストボックスに入った値を受け取ります。) こちらも図にすると以下のようになります。 methodsプロパティからhtml属性にアクセスできませんが、 イベント(html属性)が発火することでmethodsプロパティのメソッドを実行することはできます。 v-on:html属性(イベント)→Vueインスタンス内のデータ の単方向のみ 1-3. v-modelについて v-modelは双方向のデータバインディングを行います。 v-model=で定義された変数と、Vueインスタンスのデータプロパティの変数を紐づけ、 どちらかが変わるともう一方も変わる挙動をします。 template <template> <div id="app"> <input type="text" v-model="myName"> {{myName}} </div> </template> Vueインスタンス export default { name: 'App', data() { return { myName: 'MouMou' } } } <input type="text">の場合、 v-modelはvalue属性のバインディングとinputイベントのサブスクリプション両方の働きをしていることがわかります。 図にすると以下のようになります。 2. v-model = v-bind + v-on v-model は v-bind と v-on の組み合わせで実現できます。 言い換えると、v-modelは「v-bindとv-onの組み合わせの省略記法」とも言えますね。 1-3のv-modelサンプルをv-bindとv-onの組み合わせで実現すると以下になります。 template <template> <div id="app"> <input type="text" v-bind:value="myName" v-on:input="changeMyName"> {{myName}} </div> </template> Vueインスタンス export default { name: 'App', data() { return { myName: 'MouMou' } }, methods:{ changeMyName(event) { this.myName = event.target.value; } } } 1-3のv-modelのサンプルと比べて冗長でわかりにくいですが、同じ挙動をします。 v-modelを使用することで、双方向のバインディングがすっきりかけて、わかりやすくなりますね。 なお、Vue公式サイトには以下のような記載があります 内部的には、v-modelは異なる input 要素に対し異なるプロパティを使用し、異なるイベントを送出します。 text および textarea 要素には、valueプロパティとinputイベントを用います チェックボックスおよびラジオボタンには、checkedプロパティとchangeイベントを用います select フィールドには、valueプロパティとchangeイベントを用います 3. まとめ v-model は v-bind と v-onで実現できる 最後まで読んでいただきありがとうございました! 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LineBotに組み込む~テレ〇限定・今日のドラマ再放送は何やるの?LineBotを作ろうと思う

LineBotの作り方を教わったので、「14時くらいから17時までの「テレ朝 ドラマ再放送ゴールデンタイム」の番組タイトルを教えてくれるLineBotを作ってみる!」にチャレンジしています。今回は前回(スクレイピングしてみた)の続きになります。 スクレイピングでゴールデンタイムの情報を取得することができたのでLineBotとして組み込んでまいりたいとおもいます。 LineBotの作成 ひとまず動くプロトタイプの完成を目指したいので、環境はテスト用とします。外部のサーバなどは利用せず、自分のパソコンをサーバとして、テスト的にLineBotを機能させます。Lineの開発アカウントの取得、環境の作り方はmintak21さんのページが参考になります。 いろいろなサービスを使ってLINEbotを作ろう【ngrok編】 ひとまずできました とりあえずできました! なんでもよいので、Botちゃんに問いかけるとその日のゴールデンタイムに放送するドラマを返してくれます! いろいろ課題がありますが、こんな簡単に通信して情報を表示するアプリが作れるなんてすごい世の中になったなぁとしみじみ。。 今日は土曜日なので再放送ゴールデンタイムではなかったです、、残念。 非同期処理が実装できませんでした テレ朝のサイトに情報を取得しに行くため処理に時間がかかります。非同期な処理を実現する必要があり、見よう見まねでコードを書いてみましたが、うまくいかず。。。残念。 ひとまず動けばいいや!の精神でウェイトのようなものを入れて要件を満たしました。今後しっかり勉強して正しく解決したいところです。 Wait //非同期がうまくいかないので、とりあえず2秒待つ// const _sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); await _sleep(2000); スクレイピングとLineBotを合体させたコード 前回作ったスクレイプの機能とLineBotの機能を合体させました。そんなわけで非同期的な処理がうまくできていないですが、、、 doramaBot.js 'use strict'; // パッケージの設定 const express = require('express'); const line = require('@line/bot-sdk'); const cheerioh = require('cheerio-httpcli'); //スーパー再放送タイムの設定 任意の時間帯 const ssstarttime = '13:46'; const ssendtime = '16:30'; // ローカル(自分のPC)でサーバーを公開するときのポート番号です const PORT = process.env.PORT || 3000; // Messaging APIで利用するクレデンシャル(秘匿情報)です。 const config = { channelSecret: 'XXXXXXXXXXXXXXXXX', channelAccessToken: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXx' }; //テレ朝様から情報をもらいつつ、Lineに返します const tereasaFunction = async (event) => { // 「リプライ」を使って先に返事しておきます await client.replyMessage(event.replyToken, { type: 'text', text: '少々おまちください……' }); let pushText = ''; //テレ朝様に情報をもらいに行きます const res = await cheerioh.fetch('https://www.tv-asahi.co.jp/bangumi/', {}, function (err, $, res) { //当日の番組一覧を取得して配列に入れる let tvinfo = []; let r = 0; $('.new_table .today .new_day .prog_name .bangumiDetailOpen').each(function () { tvinfo[r] = []; tvinfo[r][0] = $(this).text(); tvinfo[r][1] = ""; r = r + 1; }); //開始時間を配列に格納する let c = 0; $('.new_table .today .new_day .min').each(function () { let title = $(this).text(); tvinfo[c][1] = title.trim(); //空白が入っているので削除する c = c + 1; }); //スーパー再放送タイムか確認 let conststr = '2000/01/01 '; let starttime = new Date(conststr + ssstarttime); let endtime = new Date(conststr + ssendtime); let tempdatestr; let targettime; for (let t = 0; t < tvinfo.length; t++) { //判定 tempdatestr = conststr + tvinfo[t][1]; //番組表から取得した各番組の開始時刻で日付の文字列を生成 targettime = Date.parse(tempdatestr); //スーパー再放送タイムであるか判定 if (targettime > starttime && targettime < endtime) { pushText = pushText + tvinfo[t][1] + ' ' + tvinfo[t][0] + '\n'; } } pushText = pushText + '...を放送するようです!' }); //非同期がうまくいかないので、とりあえず2秒待つ。。。。。// const _sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); await _sleep(2000); //非同期がうまくいかないので、とりあえず2秒待った あとでちゃんと勉強しよう// // 「プッシュ」でLineに送る return client.pushMessage(event.source.userId, { type: 'text', text: pushText, }); }; // ######################################## // LINEサーバーからのWebhookデータを処理する部分 // ######################################## // LINE SDKを初期化します const client = new line.Client(config); // LINEサーバーからWebhookがあると「サーバー部分」から以下の "handleEvent" という関数が呼び出されます async function handleEvent(event) { // 受信したWebhookが「テキストメッセージ以外」であればnullを返すことで無視します if (event.type !== 'message' || event.message.type !== 'text') { return Promise.resolve(null); } // サンプル関数を実行します return tereasaFunction(event); } // ######################################## // Expressによるサーバー部分 // ######################################## // expressを初期化します const app = express(); // HTTP POSTによって '/webhook' のパスにアクセスがあったら、POSTされた内容に応じて様々な処理をします app.post('/webhook', line.middleware(config), (req, res) => { // 検証ボタンをクリックしたときに飛んできたWebhookを受信したときのみ以下のif文内を実行 if (req.body.events.length === 0) { res.send('Hello LINE BOT! (HTTP POST)'); // LINEサーバーに返答します(なくてもよい) console.log('検証イベントを受信しました!'); // ターミナルに表示します return; // これより下は実行されません } else { // 通常のメッセージなど … Webhookの中身を確認用にターミナルに表示します console.log('受信しました:', req.body.events); } // あらかじめ宣言しておいた "handleEvent" 関数にWebhookの中身を渡して処理してもらい、 // 関数から戻ってきたデータをそのままLINEサーバーに「レスポンス」として返します Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result)); }); // 最初に決めたポート番号でサーバーをPC内だけに公開します // (環境によってはローカルネットワーク内にも公開されます) app.listen(PORT); console.log(`ポート${PORT}番でExpressサーバーを実行中です…`); つぎはユーザーインタフェースを修正してみます 機能はできたので、最後にUIをいじってみたいと思います。 今日はここまで。おつかれさまでしたー
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javascript 定数と変数の違い 私初心者なので 復習

定数と変数の違い 変数とよく似ている 定数っていうのがある 定数はletの代わりにconstを用いて定義するよ 変数は一度代入した値を更新することができましたが、定数は値を更新することはできません。 let name = "megumi"; console.log(name);//出力結果:megumi name = "qiita"; console.log(name);//出力結果:qiita ○変数は代入されている値を更新することができる //定数 const name = "megumi"; console.log(name);//出力結果:megumi name ="qiita";//エラー!!!!!定数は一度代入された値を更新することはできない!!!! console.log(name); なんか定数って不便そう・・・何がいいのか 定数のメリットは、「後から値を更新できない」ところ。 予期しない更新を防げて、より安全なコードがかける。コードの量が増えるにつれて便利になってくるって //変数の場合 let name = "megumi"; console.log(name);//途中で値が変わってるかもしれない //定数の場合 const name = "megumi"; console.log(name);//定数なのでmegumiから変わらない
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

webサービスを個人で開発・運営するときに知ってほしい6つのこと【個人開発】

初めに 自分が今まで個人開発をしてきて学んだこと、考えたことをまとめます。 筆者について webアプリを作るのが好きでここ一年間、公開したものだけで15個はあります 最近作ったもの(宣伝も兼ねて) AmmotというSNSで、コンセプトは「制限の少ない自由な投稿を」です。 「ツイッターは文字数制限がきつい、ただfacebookは実名制だしデザインがごちゃごちゃでいや」 という声を聴いたので作ってみました。 文字数制限が6000字まで、画像・動画・音声は同時に10個まで投稿可能です。 詳しくはこちらも見てください 1.アイデアを出す方法 一番いいのは自分が困った経験を思い出すことです。 「○○あったらいいのになぁ」「○○がなくなればいいのになぁ」など 特に「このサービス使いにくいなぁ」とか結構いいかもしれません。 そのサービスを使ってる人はある程度自分が作ったサービスに興味を持つだろうし、使う人(ペルソナ)が絞られているので。 ただし急にそんなことを思い出そうとしてもなかなか出ないので、日ごろからメモをしたり 付箋に適当なことを書きまくってそれをグループ分けして要らないものをそぎ落とし、残ったものを順序よく並べて整理するという方法もあります。(KJ法と言われています。) ↓自分の日ごろのメモ まともなアイディアがありませんね。はい アイディア出しの注意 めちゃ個人的意見ですが 基本的には悩みを解決するタイプ以外のwebアプリは使われにくいです。 理由は二つ。 1.検索されないから。 「なんかおもろいwebサービスないかなぁ」で検索されることはほぼないです。 ユーザーがwebサービスを探す理由は基本的に困ったことがあるからです。 2.実際に使ってくれる人が少ない 緊急でもないので 「面白そうだな」とは思ってもそこから実際に使ってくれる人は非常に少ないと思います。 そう考えるとBoketeってすごい 2.開発する前のアイディアの確認 開発する前に確認するべきことは大体こんな感じ 自分はそのサービスを使うか。 なぜそのサービスを作りたいのか。 悩みをどうやって解決することができるサービスでしょうか。 最初は日本語だけで運営可能か 個人で管理できるか ほぼコンテンツがいらない or 自分で初期コンテンツが作れる 1~2年は運用できそうか 差別化はできそうか ユーザーにどんな体験を与えるか 一番重要なのは「自分はそのサービスを使うか」です。 自分が使うサービスは成功しやすいです。 開発してる時もユーザーの気持ちなどをいちいち考えず「自分がどうしたら使いやすいか」という主観的な目線で作れるからです。 ただ注意してほしいのは「これは自分も使う!」と思って開発したが結局自分は使わなかったというパターンです。 これを防ぐのは結構難しいですが、いったん休憩をしてみて落ち着いた状態でもう一回そのアイディアを見つめなおすと良いかもしれません。 3.開発中 意識してるのは2つ デザインを重視する これは結構意見が分かれがちですが僕はデザインはかなり重要だと思ってます。 最初はデザインなんて適当でいいやと思っていましたが トップページのデザインは、ユーザーがどのくらい登録してくれるかに大きく左右します。 動作速度を見る railsは素の状態で使うと結構遅いので工夫が必要です。 僕がやってるは countメソッドを使わない キャッシュを使う scriptにはasync属性を付ける turbolinksは絶対に外さない render collectionを使う くらいですかね。 それでもAmmotはそこそこ遅いので困ってます。 4.リリースしたら qiita service-safari zenn note つくログ twitter 最低でもこのくらいは宣伝したほうがいいです。 無料で宣伝できるところがあったらなんでもやるくらいの心がけで挑みましょう。 名言: 宣伝はやりすぎるくらいがちょうどいい 5.少し過疎ってきたら... 内容を変えて再度、qiitaやnoteに宣伝してみる。 ログインをしないとコンテンツが見えないようにする 投稿時刻などを見せない など ツイッターやQiitaではスパム報告をされない程度に全力でやりましょう。 過疎ったということは一度は人を寄せ付けたということですから。 6.諦めるタイミング 何度も宣伝したが無理... 完璧に過疎った...PV数0 某掲示板製作者のひ○ゆきという方が言っていました 「口コミで広がらないサービスは伸びない」 宣伝をしたら最低でも100人くらいの人が作ったwebアプリを見てくれると思います。それを何度かやって無理なら、あきらめましょう。 期間でいえば大体1月くらい公開・宣伝して全く無反応だったらやめましょう。 愛着があるサービス程、閉鎖は悲しいと思いますがあなたの中でその失敗の原因を探して今後に生かせば失敗しても「作ってよかった」といえる日がくるはずです。 まとめ 一番重要なのは 自分が使うサービスを作ろう ただそれでも失敗することはあるので webアプリを当てるのは結構難しいのでへこまず前へ進もう。 数を作ろう(そのためにスピードを上げよう) 最後に僕の作ったAmmotもぜひ使ってみてください! AmmotのURL https://ammot.net/ 僕のAmmotのアカウント https://ammot.net/user/yamada(%E9%96%8B%E7%99%BA%E8%80%85) 僕のツイッターアカウント https://twitter.com/yamada1531
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【GAS】プロパティサービスについてまとめる

はじめに 最近GASを使い始め、プロパティサービスを覚えたので忘備録として記録します。 GASでアクセストークンやAPIキーを使用する際、はじめは以下のようにハードコーディングを行っていました。 ダメな例 const apiKey = 'xxxxxxxxxxx'; const accessToken = 'yyyyyyyyyyyy'; しかし、セキュリティ的にまずいなと思い調べたところ、GASにはプロパティサービスという機能がありました。 これを使用することでハードコーディングを避けてセキュリティ性やメンテナンス性の高いコードが書けるようになります。 プロパティサービスとは プロパティサービスとは、スクリプト内で使用する値を、キーと値をペアとして保存できるサービスです。 キーと値はいずれも文字列に変換されます。 プロジェクト内ならグローバルに読み書きが可能ですが、複数のプロジェクト間での共有はできません。 プロパティサービスの種類 プロパティサービスには以下の3種類のサービスが存在します。 1. スクリプトプロパティ 2. ユーザープロパティ 3. ドキュメントプロパティ それぞれの違いをみていきます。 スクリプトプロパティ プロジェクト内の全てのユーザーが、読み書き可能。 アプリケーション全体の設定値を格納するのに適している。 ユーザープロパティ そのユーザーのみ読み書き可能。 ユーザー固有の設定値を格納するのに適している。 ドキュメントプロパティ 開いているドキュメントのアドオンのユーザーが、読み書き可能。 ドキュメント固有の設定値を格納するのに適している。 スクリプトプロパティを使ってみる スクリプトプロパティの読み書きの方法についてみていきます。 少し話がずれるのですが、GASは2020年12月にエディタがアップデートされました。 このアップデートにより、旧エディタで可能であったGUI上でスクリプトプロパティの読み書きが、新エディタではできなくなりました。 セキュリティ上、GUIで設定値が見れるのはよくないと考えているので、 GUIでの読み書きの方法に関しては今回省略し、スクリプトでの読み書きの方法についてのみ記述します。 プロパティの追加 以下のように、各プロパティサービスのsetPropaty()を使用することで値を格納できます プロパティの追加 // スクリプトプロパティ var scriptProperties = PropertiesService.getScriptProperties(); scriptProperties.setProperty('SERVER_URL', 'http://www.example.com/'); // ユーザープロパティ var userProperties = PropertiesService.getUserProperties(); userProperties.setProperty('EMAIL', 'hoge@test.com'); // ドキュメントプロパティ var documentProperties = PropertiesService.getDocumentProperties(); documentProperties.setProperty('SOURCE_DATA_ID', '1234567890abcdefghijklmnopqrstuvwxyz'); getProperties()を使用すると一気に複数のプロパティを追加することも可能です。 var scriptProperties = PropertiesService.getScriptProperties(); scriptProperties.setProperties({ 'cow': 'moo', 'sheep': 'baa', 'chicken': 'cluck' }); プロパティの取得 以下のように、各プロパティサービスのgetProperty()を使用することで値を格納できます。 プロパティの追加 // スクリプトプロパティ var scriptProperties = PropertiesService.getScriptProperties(); scriptProperties.getProperty('SERVER_URL'); // ユーザープロパティ var userProperties = PropertiesService.getUserProperties(); userProperties.setProperty('EMAIL'); // ドキュメントプロパティ var documentProperties = PropertiesService.getDocumentProperties(); documentProperties.setProperty('SOURCE_DATA_ID'); getProperties()を使用すると複数の値を一気に取得できます。 プロパティの取得 var scriptProperties = PropertiesService.getScriptProperties(); var data = scriptProperties.getProperties(); for (var key in data) { Logger.log('Key: %s, Value: %s', key, data[key]); } プロパティの修正 プロパティの修正をしたい場合は再度serPropaty()を行うと、値が上書きされます。 プロパティの修正 var userProperties = PropertiesService.getUserProperties(); var units = userProperties.getProperty('DISPLAY_UNITS'); units = 'imperial'; userProperties.setProperty('DISPLAY_UNITS', units); プロパティの削除 プロパティを削除したい場合、deletePropaty()を使用すると削除できます。 プロパティの削除 var userProperties = PropertiesService.getUserProperties(); userProperties.deleteProperty('DISPLAY_UNITS'); deleteAllPropaties()を使用すると全てのプロパティを削除できます。 var userProperties = PropertiesService.getUserProperties(); userProperties.deleteAllProperties(); 機密情報を取り扱う 最後に、スクリプトプロパティでの機密情報の取り扱い方についてのメモ。 スクリプトプロパティは上述した通り、プロジェクト内のユーザーで編集権限があれば誰でも編集することができます。 そのため、機密情報を使用する際はスプレッドシートに値を記述し、そのスプレッドシートの編集権限(閲覧権限)とスクリプトの実行権限は特定のユーザーのみに限定することで、セキュリティを担保することができます。 やってみた記事をまた別で書こうと思います。 参考サイト Class PropertiesService Properties Service Google Apps Scriptで使う情報はプロパティを利用しよう
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JS~反復メソッドを使った処理~

概要 配列には、配列のすべての要素に対して処理を行うメソッドがあります。 配列のすべての要素に対して行うメソッドには3つあります。 index.js .forEach(f) .map(f) .filter() これらの反復メソッドは、アロー関数で表されます。 アロー関数とは 関数は、名前を付けて使用するとしてきました。しかし、関数は名前を付けずに使用することもできます。しかし、名前のない関数は呼び出すことができません。そこで、使用するためにオブジェクトのプロパティに入れる、関数の引数にする、すぐにその場で使用する。という方法があります。 1、オブジェクトのプロパティに入れる▶メソッド 2、関数の引数にする▶コールバック関数 3,その場で使う▶即時関数 アロー関数は、多くの場合関数の引数に設定するコールバック関数として使います。 コールバック関数 引数に、要素、位置、配列そのものを取ります。コールバック関数に渡される値はメソッド実行時の値です。コールバック関数が呼ばれるのは、値が代入済みの要素です。 forEach(f) 配列の各要素をコールバック関数fで処理します。 値を代入していない値は実行されません。 index.js const fruits = ["りんご","ばなな","いちご","メロン"]; fruits.forEach((fruit,index) =>{ console.log(`${index}個:${fruit}`); }); //0個:りんご //1個:ばなな //2個:いちご //3個:メロン .map(f) 配列の各要素をコールバック関数fで処理して、関数の戻り値から新しい配列を作って返します。数値から作った文字列の配列を作ります。 index.js const num = [1,2,3,4,5,6]; const num2 = num.map(x => `${x}番目`); console.log(num2); //["1番目", "2番目", "3番目", "4番目", "5番目", "6番目"] *アロー関数は、引数が1つの場合は()を省略することができ、処理が1つの場合は{}も省略することができます。 .filter(f) 配列の各要素をコールバック関数fで処理して、trueとみなす値を返した要素のみの新しい配列を作って返します。 index.js const num2 = num.filter(x =>{ if (x % 2 === 0) { return true; } else { return false; } }); console.log(num2); //[2, 4, 6] *細かく記述すると上記の様な記述になります。しかしif文は省略することができます。そして、アロー関数では処理が1行のみの場合、{}とreturnを省略することができます。その場合は、処理結果がそのまま戻り値になります。 index.js const num2 = num.filter(x =>x % 2 === 0); console.log(num2); //[2, 4, 6] メソッドチェーンの使用(番外編) メソッドチェーンとは、あるオブジェクトに対してメソッドを.(ドット)で連結して繋げていくことです。 前のメソッドで返ってきた値を次に繋げたメソッドが受け取って処理していきます。 index.js [400,533,632,333] .map((x,i) => ({i: i, n: x})) //位置と値を記録したオブジェクトの作成 .filter(x => x.n % 2 === 0) //偶数の要素のみを抜き出す .forEach(x => console.log(`${x.i}番目${x.n}`)); //文字列にして出力
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[JS] Fetch APIでFormDataをPOSTするときにContent-Typeを指定しないようにしよう。

(久しぶりにSwift以外の記事…) はじめに 結論はタイトルの通り: 「Fetch APIでFormDataをPOSTするときにContent-Typeを指定しないようにしよう。」 その理由は…? Content-Typeの指定あり/なしで比較 次のコードを実行してみてください。Content-typeをmultipart/form-dataに設定しています。 post.js const formData = new FormData(); formData.append('name', 'value'); const request = new Request('/do-something', { method: 'POST', headers: { 'Content-Type': 'multipart/form-data', }, body: formData, }); const response = fetch(request); そうすると次のようなリクエストが送られます(例: Safariの場合; 一部のヘッダは省略)。 Content-Type指定あり POST /do-something HTTP/1.1 Accept: */* Content-Type: multipart/form-data Content-Length: 140 Host: example.com Accept-Encoding: gzip, deflate, br Connection: keep-alive ------WebKitFormBoundaryHwUCy8Y0kR36DASN Content-Disposition: form-data; name="name" value ------WebKitFormBoundaryHwUCy8Y0kR36DASN-- これの何が問題なのでしょう? 既にお気づきの方もいらっしゃるかとは思いますが、次は前出のコードの'Content-Type': 'multipart/form-data',をコメントアウトして実行してみてください。その場合のリクエストは次のようになります。 Content-Type指定なし POST /do-something HTTP/1.1 Accept: */* Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryRAycGNoAYaQ0xKA0 Content-Length: 140 Host: example.com Accept-Encoding: gzip, deflate, br Connection: keep-alive ------WebKitFormBoundaryRAycGNoAYaQ0xKA0 Content-Disposition: form-data; name="name" value ------WebKitFormBoundaryRAycGNoAYaQ0xKA0-- さて、Content-Typeを指定する場合と指定しない場合のHTTPリクエストの違いが見えましたか? Content-Typeを指定しない場合、Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryRAycGNoAYaQ0xKA0のようにクライアントが適切なContent-Typeを設定します。そして大切なのは、FormDataをPOSTするときにはContent-Typeのパラメータに"boundary"を含めなければいけないということです1。 しかし、JavaScriptでRequestを生成する際にContent-Typeを指定してしまうと、適切なboundaryを指定することができません。すると、サーバ側でmultipartの境界を判別できずにデータの受け取りに失敗することになります。…リクエストボディの1行目をみればboundaryは一目瞭然じゃないかって?もしかしたら「優しいプログラム」は1行目をみてboundaryを判断することがあるかもしれません。しかし、サーバ側のすべてのプログラムがそのように「優しい」ことを期待してはいけません。サーバ側のプログラムを書く人間からすると、リクエストのContent-Typeにboundaryが指定されていない場合に1行目の文字列からそれが勝手にboundaryと判断することはデータを誤って解釈することになりかねず(入れ子になったmultipartとか…実は別のデータだったとか…)、基本的にはboundaryの指定がなければエラーとするでしょう。 もう一度結論 Fetch APIでFormDataをPOSTするときにContent-Typeを指定しないようにしよう。 "boundary"は基本的にクライアントがランダムな文字列を含めることが一般的です(実際Safariもリクエストごとに文字列が異なる) ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Vue.js]Vue.jsをインストール

はじめに Vue.js初心者です。Vue.jsをインストールしたのでメモ 下記の記事を参考にさせていただいています。 Vue.jsのインストール 今回はnpmを使ってインストールしていきます。 まずnodeとnpmが入っているか確認します。 node -vとnpm -vを実行してみてください 入っていない場合はインストールを行なってください $ npm install -g vue-cli $ vue -V 2.9.6 $ vue init webpack test $ cd test $ npm run dev Your application is running here: http://localhost:8080 localhost:8080にアクセスしてみてくださいVueの初期ページが表示されていると思います。 以上です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

javascript 私は初心者 復習

変数の更新 変数は、一度代入した値を変更することができます。一度値を代入した変数に、その後再び値を代入すると、のちに代入した値で変数の中身が上書きされます。定義するときと違って「let」は必要なく、「変数名=新しい値」と書けば値が変更されます。 let name = "qiiko"; console.log(name); name = "Javasc";// 更新するとき、「let」はつけない console.log(name); コンソールには qiiko Javasc→上書きした変数の値が出力される! 変更更新のイメージ プログラムは上から順に実行されるので、後で代入された値で変数の中身が更新されます。 let number = 7; //numberの値は7 number = 9; //numberの値は9 number = 12; //numberの値は12
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Fetch APIのResponseオブジェクトのjsonメソッドやtextメソッドはなぜPromiseを返すのか

Fetch APIの使い方といえば const response = await fetch("https://api.example.com/"); const json = await response.json(); のように、レスポンスオブジェクトを得るためにまずawait、そしてさらにそこからデータを取り出すためにもう一度Promiseの解決を待つ必要がある。これはなぜなのか。少し考えれば当たり前の話なのだが、日本語の記事が見つからなかったので書いておく。 ちなみにaxiosとかだと import axios from "axios"; const json = await axios.get("https://api.example.com/"); これだけ。JSONのパースも暗黙的に行われるし、レスポンスヘッダがエラーだったらPromiseはリジェクトされる。うーんeasy。さすがVueの標準なだけのことはある。 今まであまり考えずに使ってきたが、よくよく考えれば当たり前だった。 fetchメソッドのMDNに詳しい説明が書いてある。 fetch() は WindowOrWorkerGlobalScope ミックスインのメソッドで、ネットワークからリソースを取得するプロセスを開始し、レスポンスが利用できるようになったら満足するプロミスを返します。このプロミスはリクエストに対するレスポンスを表す Response で解決します。プロミスは HTTP エラーでは拒否されず、ネットワークエラーでのみ拒否されます。 HTTP エラーをチェックするには、 then ハンドラーを使用する必要があります。 簡潔で明確な説明である。Promiseがfullfilledになるのは、「レスポンスが利用できるようになったら」、つまりHTTPのレスポンスヘッダまで受信を終えてレスポンスボディの受信を開始するタイミングということなのだろう。 そして、fetchメソッドの戻り値に使用されているResponseオブジェクトは、Bodyを実装している。このオブジェクトはRequestオブジェクトにも実装されているが、その中核はbodyプロパティとしてアクセス可能な単なるReadableStreamだ。 ただ、Bodyにはリクエストボディやレスポンスボディを処理するための便利メソッドがたくさん用意されている。それがお馴染みのjsonやtextメソッドだ。ただ、JSONや文字列を返すためには、先にbodyのReadableStreamをメモリ上に最後まで読み切る必要がある。このため、これらのメソッドの戻り値はPromiseだったのだ。 Body.json() Response ストリームを取得し、完全に読み込む。 ボディのテキストを JSON として解析した結果で解決する promise を返す。 Body.text() Response ストリームを取得し、完全に読み込む。 USVString(テキスト)で解決する promise を返す。 レスポンスは常に UTF-8 でデコードする。 メモリの制限にかかるくらい大量のデータを扱う必要がある場合はBody#textやBody#arrayBufferよりもBody#bodyを直接使ったほうがよさそうだ。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

もしGISのオープンソース をさわることになったら押さえておくこと(OpenLayersを例にする)

はじめに GISのオープンソースに携わって最初結構苦労した。 色々記事はあるが、その知識を使えるまでに、初心者用にこの辺クローズアップされて整理されていれば、もっとスムーズに進んでいたのにと思う所を整理してみる。 GISのオープンソースに取り組んで必要だったこと ・オープンソースを使用した地図操作方法 ・バンドル(webpack等)の理解 ・地図の理解 上記の動きがどんなものかを早く掴むことが理解のスピードにつながる。 webpackは地図のオープンソースを使用する場合においてよく使用され、知らないと足かせになる。ただそんなにボリュームが大きい箇所ではなく、早急に理解をする必要はある。 地図の理解も必要だが、最初はそれに振り回されるかもしれない。 本件のテーマ 本件はオープンソースを使用した地図操作方法で例をあげる。 本件記載していることは他の記事等で記載されていることであるが、あまりにも簡潔に記載されているため、中々理解まで落ちなかった。そういった箇所をソースコードで例にあげ、Openlayersというオープンソースを使用する。なおバンドルツールは使用しない。 地図操作方法例 本テーマでは、Openlayersというオープンソースを使用した例とする。 地図表示から表示したいデータを地図に表示し、そのデータ箇所をクリックイベントを起こし何かをする所までを例にあげる。 最初はわかりにくいが簡潔に記載すると map表示 ol.Mapを呼び出す。 データを表示 データ表示用のmapオブジェクトのプロパティ(オプション)のlayer(レイヤー)に対し、地図表示とは別のデータ表示用のレイヤーを作る。 そのレイヤーのsouceオプションに対し、featureオブジェクトをセットする。 featureオブジェクトに、データをセットする。 そのデータをクリックして表示 mapオブジェクトのクリックイベントを起こしクリックした場合 クリックした時、forEachFeatureAtPixelというmapメソッドを使用し、featureオブジェクトを検知するメソッドを起動 fetureがあれば、そのfeatureから欲しいデータを取得し、overlayオプション等を使用し、プロットを表示する 押さえておくことは以上の1から3ができること、特に2のデータセットの動きです。 1.まず地図を表示することから行う。 <!doctype html> <html lang="en"> <head> <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/css/ol.css" type="text/css"> <style> .map { height: 400px; width: 100%; } </style> <script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/build/ol.js"></script> <title>OpenLayers example</title> </head> <body> <h2>My Map</h2> <div id="map" class="map"></div> <script type="text/javascript"> var map = new ol.Map({ target: 'map', layers: [ new ol.layer.Tile({ source: new ol.source.OSM() }) ], view: new ol.View({ center: ol.proj.fromLonLat([139.745433, 35.658581]), zoom: 16 }) }); </script> </body> </html> OpenLayersMapオブジェクトを作成 <div id="map" class="map"></div> OpenLayersの地図を表示する。レイヤー(layers)オプションで、表示したい地図レイヤーを記述する。 layers: [ new ol.layer.Tile({ source: new ol.source.OSM() }) ] viewオプションで、マップの中心、解像度、および回転を指定。 view: new ol.View({ center: ol.proj.fromLonLat([37.41, 8.82]), zoom: 4 }) 以上がまず最初のステップである。 この辺りまではそういうものなんだとわかると思う。 問題は次のステップである。 2.地図に提供された緯度を地図に出す処理を行う 黄色のプロットが、指定したデータの経度、緯度に基づき表示したものである。 <!doctype html> <html lang="en"> <head> <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/css/ol.css" type="text/css"> <style> .map { height: 600px; width: 100%; } </style> <script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/build/ol.js"></script> <title>OpenLayers example</title> </head> <body> <h2>My Map</h2> <div id="map" class="map"></div> <script type="text/javascript"> var map = new ol.Map({ target: 'map', layers: [ new ol.layer.Tile({ source: new ol.source.OSM() }) ], view: new ol.View({ center: ol.proj.fromLonLat([139.745433, 35.658581]), zoom: 16 }) }); // 浜松町〜東京タワーの道のりを表す座標をピックアップした配列 var coordinates = [ { name:'住所1', coordinate:[139.75655240134424, 35.6553463380788], }, { name:'住所2', coordinate:[139.75573020071425, 35.654585477741634], }, { name:'住所3', coordinate:[139.75390308820317, 35.65482672692599], }, { name:'住所4', coordinate:[139.74578081849876, 35.659007609306684], } ]; // 上記配列をのせるためのレイヤーを作成する var markerLayer = new ol.layer.Vector({ source: new ol.source.Vector() }); map.addLayer(markerLayer); // 点を出す drawDot(); function drawDot(){ // 点をレイヤーにプロットする for (var i in coordinates) { var info = coordinates[i]; // 地物オブジェクトを作成 var feature = new ol.Feature({ geometry: new ol.geom.Point(ol.proj.fromLonLat(info.coordinate)) }); feature.information = info; // 点のスタイルを設定 var style = new ol.style.Style({ image: new ol.style.Icon({ src: 'img/icon.png', anchor: [0.5, 1.0], scale: 0.8 }) }); feature.setStyle(style); // 地物を追加する markerLayer.getSource().addFeature(feature); } } </script> </body> </html> 経度、緯度の配列をどうやって、地図に表示しているかに注目して頂きたい。 点を出すためのレイヤーを定義し、現状の地図のレイヤーに追加している。 // 上記配列をのせるためのレイヤーを作成する var markerLayer = new ol.layer.Vector({ source: new ol.source.Vector() }); map.addLayer(markerLayer); そして、追加したレイヤーのsouceオプションにfeatureを追加している所である。 // 地物を追加する markerLayer.getSource().addFeature(feature); 今回はレイヤーからsouceオプションを取得し、featureを追加する形をとっている。 そしてfeatureはというと、 // 地物オブジェクトを作成 var feature = new ol.Feature({ geometry: new ol.geom.Point(ol.proj.fromLonLat(info.coordinate)) }); 上記のように、経度、緯度をFeatureをnewして、geometoryオプションに、点を表示するPointオブジェクトを呼んで、経度、緯度を追加している。ちなみに今回は割愛するがfromLonLatは、経度、緯度表示をopenlayersのメルカトル記法に合わせて変換している。 以上が経度、緯度のデータを表示している手順である。 ここが、何をしているのかわかりにくくさせている所ではないかと思う。 3.その点をクリックして、その情報を表示する所を行う。 <!doctype html> <html lang="en"> <head> <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/css/ol.css" type="text/css"> <style> .map { height: 600px; width: 100%; } #popup { position: relative; display: inline-block; margin: 1.5em 0; padding: 7px 10px; min-width: 120px; max-width: 100%; color: #555; font-size: 16px; background: #FFF; border: solid 3px #555; box-sizing: border-box; } #popup:before { content: ""; position: absolute; bottom: -24px; left: 50%; margin-left: -15px; border: 12px solid transparent; border-top: 12px solid #FFF; z-index: 2; } #popup:after { content: ""; position: absolute; bottom: -30px; left: 50%; margin-left: -17px; border: 14px solid transparent; border-top: 14px solid #555; z-index: 1; } .popup p { margin: 0; padding: 0; } </style> <script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/build/ol.js"></script> <title>OpenLayers example</title> </head> <body> <h2>My Map</h2> <div id="map" class="map"></div> <div id='popup'></div> <script type="text/javascript"> var map = new ol.Map({ target: 'map', layers: [ new ol.layer.Tile({ source: new ol.source.OSM() }) ], view: new ol.View({ center: ol.proj.fromLonLat([139.745433, 35.658581]), zoom: 16 }) }); // 浜松町〜東京タワーの道のりを表す座標をピックアップした配列 var coordinates = [ { name:'住所1', coordinate:[139.75655240134424, 35.6553463380788], }, { name:'住所2', coordinate:[139.75573020071425, 35.654585477741634], }, { name:'住所3', coordinate:[139.75390308820317, 35.65482672692599], }, { name:'住所4', coordinate:[139.74578081849876, 35.659007609306684], } ]; // 上記配列をのせるためのレイヤーを作成する var markerLayer = new ol.layer.Vector({ source: new ol.source.Vector() }); map.addLayer(markerLayer); // 点を出す drawDot(); // ポップアップを表示するためのオーバーレイを作成する var overlay = new ol.Overlay({ element: document.getElementById('popup'), positioning: 'bottom-center' }); function drawDot(){ // 点をレイヤーにプロットする for (var i in coordinates) { var info = coordinates[i]; // 地物オブジェクトを作成 var feature = new ol.Feature({ geometry: new ol.geom.Point(ol.proj.fromLonLat(info.coordinate)) }); feature.information = info; // 点のスタイルを設定 var style = new ol.style.Style({ image: new ol.style.Icon({ src: 'img/icon.png', anchor: [0.5, 1.0], scale: 0.8 }) }); feature.setStyle(style); // 地物を追加する markerLayer.getSource().addFeature(feature); } } // 地図のクリックイベントを設定 map.on('click', function (evt) { var feature = map.forEachFeatureAtPixel(evt.pixel, function (feature) { return feature; }); if (feature) { var coordinates = feature.getGeometry().getCoordinates(); var info = feature.information; var element = overlay.getElement(); console.log(element); var descriptionHTML = "<div>" + info.name + "</div>"; element.innerHTML = descriptionHTML; overlay.setPosition(coordinates); map.addOverlay(overlay); } else { map.removeOverlay(overlay); } }); </script> </body> </html> 地図をクリックして、点からプロットをどうやって表示している箇所は下記になる。 // ポップアップを表示するためのオーバーレイを作成する var overlay = new ol.Overlay({ element: document.getElementById('popup'), positioning: 'bottom-center' }); // 地図のクリックイベントを設定 map.on('click', function (evt) { var feature = map.forEachFeatureAtPixel(evt.pixel, function (feature) { return feature; }); if (feature) { var coordinates = feature.getGeometry().getCoordinates(); var info = feature.information; var element = overlay.getElement(); console.log(element); var descriptionHTML = "<div>" + info.name + "</div>"; element.innerHTML = descriptionHTML; overlay.setPosition(coordinates); map.addOverlay(overlay); } else { map.removeOverlay(overlay); } }); 地図をクリックすると、featureを検知し、そのfeatureからクリックしたfeatureのデータを取得している。 その値を、mapオブジェクトのoverlayオプション(プロパティ)に追加して、プロットという形で表示している。 地図をクリックした動作は、こういうのがあるということを知っておくべきではないかと思う。 まとめ GISのオープンソースを使用し、戸惑った箇所をわかりやすくするつもりで記載した(そうなってないと思われるが)。 最初この辺がわからず説明を受けても、理解できない状況で、事前にこういう理解があったらと思う所である。 地図のオープンソースとして、google mapsが一般的に使用されると思うが、大きな違いは、データセットがgoogl mapsでは充実し、簡単に使用できる所だと思うが、そのデータそのものを利用できない。オープンソースではデータを自由に読み込み、データをセットできるが、その箇所が初心者に情報が少なく戸惑いを感じさせる所でもあると思う。 参考にした本 下記を参考にしました。案件に入る前に、下記に取り組んでいればもっと理解が早かったと思います。 JavaScriptではじめるWebマップアプリケーション (PDF版) OpenLayers4で遊ぼう 無料の地図データをWebに表示! (技術の泉シリーズ(NextPublishing)) 今回記載しているソースは本参考書を参考にさせて頂いております。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

もしGISのオープンソース を使用することになったら押さえておくこと(OpenLayersを例にする)

はじめに GISのオープンソースに携わって最初結構苦労した。 色々記事はあるが、その知識を使えるまでに、初心者用にこの辺クローズアップされて整理されていれば、もっとスムーズに進んでいたのにと思う所を整理してみる。 GISのオープンソースに取り組んで必要だったこと ・オープンソースを使用した地図操作方法 ・バンドル(webpack等)の理解 ・地図の理解 上記の動きがどんなものかを早く掴むことが理解のスピードにつながる。 webpackは地図のオープンソースを使用する場合においてよく使用され、知らないと足かせになる。ただそんなにボリュームが大きい箇所ではなく、早急に理解をする必要はある。 地図の理解も必要だが、最初はそれに振り回されるかもしれない。 本件のテーマ 本件はオープンソースを使用した地図操作方法で例をあげる。 本件記載していることは他の記事等で記載されていることであるが、あまりにも簡潔に記載されているため、中々理解まで落ちなかった。そういった箇所をソースコードで例にあげ、Openlayersというオープンソースを使用する。なおバンドルツールは使用しない。 地図操作方法例 本テーマでは、Openlayersというオープンソースを使用した例とする。 地図表示から表示したいデータを地図に表示し、そのデータ箇所をクリックイベントを起こし何かをする所までを例にあげる。 最初はわかりにくいが簡潔に記載すると map表示 ol.Mapを呼び出す。 データを表示 データ表示用のmapオブジェクトのプロパティ(オプション)のlayer(レイヤー)に対し、地図表示とは別のデータ表示用のレイヤーを作る。 そのレイヤーのsouceオプションに対し、featureオブジェクトをセットする。 featureオブジェクトに、データをセットする。 そのデータをクリックして表示 mapオブジェクトのクリックイベントを起こしクリックした場合 クリックした時、forEachFeatureAtPixelというmapメソッドを使用し、featureオブジェクトを検知するメソッドを起動 fetureがあれば、そのfeatureから欲しいデータを取得し、overlayオプション等を使用し、プロットを表示する 押さえておくことは以上の1から3ができること、特に2のデータセットの動きです。 1.まず地図を表示することから行う。 <!doctype html> <html lang="en"> <head> <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/css/ol.css" type="text/css"> <style> .map { height: 400px; width: 100%; } </style> <script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/build/ol.js"></script> <title>OpenLayers example</title> </head> <body> <h2>My Map</h2> <div id="map" class="map"></div> <script type="text/javascript"> var map = new ol.Map({ target: 'map', layers: [ new ol.layer.Tile({ source: new ol.source.OSM() }) ], view: new ol.View({ center: ol.proj.fromLonLat([139.745433, 35.658581]), zoom: 16 }) }); </script> </body> </html> OpenLayersMapオブジェクトを作成 <div id="map" class="map"></div> OpenLayersの地図を表示する。レイヤー(layers)オプションで、表示したい地図レイヤーを記述する。 layers: [ new ol.layer.Tile({ source: new ol.source.OSM() }) ] viewオプションで、マップの中心、解像度、および回転を指定。 view: new ol.View({ center: ol.proj.fromLonLat([37.41, 8.82]), zoom: 4 }) 以上がまず最初のステップである。 この辺りまではそういうものなんだとわかると思う。 問題は次のステップである。 2.地図に提供された緯度を地図に出す処理を行う 黄色のプロットが、指定したデータの経度、緯度に基づき表示したものである。 <!doctype html> <html lang="en"> <head> <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/css/ol.css" type="text/css"> <style> .map { height: 600px; width: 100%; } </style> <script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/build/ol.js"></script> <title>OpenLayers example</title> </head> <body> <h2>My Map</h2> <div id="map" class="map"></div> <script type="text/javascript"> var map = new ol.Map({ target: 'map', layers: [ new ol.layer.Tile({ source: new ol.source.OSM() }) ], view: new ol.View({ center: ol.proj.fromLonLat([139.745433, 35.658581]), zoom: 16 }) }); // 浜松町〜東京タワーの道のりを表す座標をピックアップした配列 var coordinates = [ { name:'住所1', coordinate:[139.75655240134424, 35.6553463380788], }, { name:'住所2', coordinate:[139.75573020071425, 35.654585477741634], }, { name:'住所3', coordinate:[139.75390308820317, 35.65482672692599], }, { name:'住所4', coordinate:[139.74578081849876, 35.659007609306684], } ]; // 上記配列をのせるためのレイヤーを作成する var markerLayer = new ol.layer.Vector({ source: new ol.source.Vector() }); map.addLayer(markerLayer); // 点を出す drawDot(); function drawDot(){ // 点をレイヤーにプロットする for (var i in coordinates) { var info = coordinates[i]; // 地物オブジェクトを作成 var feature = new ol.Feature({ geometry: new ol.geom.Point(ol.proj.fromLonLat(info.coordinate)) }); feature.information = info; // 点のスタイルを設定 var style = new ol.style.Style({ image: new ol.style.Icon({ src: 'img/icon.png', anchor: [0.5, 1.0], scale: 0.8 }) }); feature.setStyle(style); // 地物を追加する markerLayer.getSource().addFeature(feature); } } </script> </body> </html> 経度、緯度の配列をどうやって、地図に表示しているかに注目して頂きたい。 点を出すためのレイヤーを定義し、現状の地図のレイヤーに追加している。 // 上記配列をのせるためのレイヤーを作成する var markerLayer = new ol.layer.Vector({ source: new ol.source.Vector() }); map.addLayer(markerLayer); そして、追加したレイヤーのsouceオプションにfeatureを追加している所である。 // 地物を追加する markerLayer.getSource().addFeature(feature); 今回はレイヤーからsouceオプションを取得し、featureを追加する形をとっている。 そしてfeatureはというと、 // 地物オブジェクトを作成 var feature = new ol.Feature({ geometry: new ol.geom.Point(ol.proj.fromLonLat(info.coordinate)) }); 上記のように、経度、緯度をFeatureをnewして、geometoryオプションに、点を表示するPointオブジェクトを呼んで、経度、緯度を追加している。ちなみに今回は割愛するがfromLonLatは、経度、緯度表示をopenlayersのメルカトル記法に合わせて変換している。 以上が経度、緯度のデータを表示している手順である。 ここが、何をしているのかわかりにくくさせている所ではないかと思う。 3.その点をクリックして、その情報を表示する所を行う。 <!doctype html> <html lang="en"> <head> <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/css/ol.css" type="text/css"> <style> .map { height: 600px; width: 100%; } #popup { position: relative; display: inline-block; margin: 1.5em 0; padding: 7px 10px; min-width: 120px; max-width: 100%; color: #555; font-size: 16px; background: #FFF; border: solid 3px #555; box-sizing: border-box; } #popup:before { content: ""; position: absolute; bottom: -24px; left: 50%; margin-left: -15px; border: 12px solid transparent; border-top: 12px solid #FFF; z-index: 2; } #popup:after { content: ""; position: absolute; bottom: -30px; left: 50%; margin-left: -17px; border: 14px solid transparent; border-top: 14px solid #555; z-index: 1; } .popup p { margin: 0; padding: 0; } </style> <script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/build/ol.js"></script> <title>OpenLayers example</title> </head> <body> <h2>My Map</h2> <div id="map" class="map"></div> <div id='popup'></div> <script type="text/javascript"> var map = new ol.Map({ target: 'map', layers: [ new ol.layer.Tile({ source: new ol.source.OSM() }) ], view: new ol.View({ center: ol.proj.fromLonLat([139.745433, 35.658581]), zoom: 16 }) }); // 浜松町〜東京タワーの道のりを表す座標をピックアップした配列 var coordinates = [ { name:'住所1', coordinate:[139.75655240134424, 35.6553463380788], }, { name:'住所2', coordinate:[139.75573020071425, 35.654585477741634], }, { name:'住所3', coordinate:[139.75390308820317, 35.65482672692599], }, { name:'住所4', coordinate:[139.74578081849876, 35.659007609306684], } ]; // 上記配列をのせるためのレイヤーを作成する var markerLayer = new ol.layer.Vector({ source: new ol.source.Vector() }); map.addLayer(markerLayer); // 点を出す drawDot(); // ポップアップを表示するためのオーバーレイを作成する var overlay = new ol.Overlay({ element: document.getElementById('popup'), positioning: 'bottom-center' }); function drawDot(){ // 点をレイヤーにプロットする for (var i in coordinates) { var info = coordinates[i]; // 地物オブジェクトを作成 var feature = new ol.Feature({ geometry: new ol.geom.Point(ol.proj.fromLonLat(info.coordinate)) }); feature.information = info; // 点のスタイルを設定 var style = new ol.style.Style({ image: new ol.style.Icon({ src: 'img/icon.png', anchor: [0.5, 1.0], scale: 0.8 }) }); feature.setStyle(style); // 地物を追加する markerLayer.getSource().addFeature(feature); } } // 地図のクリックイベントを設定 map.on('click', function (evt) { var feature = map.forEachFeatureAtPixel(evt.pixel, function (feature) { return feature; }); if (feature) { var coordinates = feature.getGeometry().getCoordinates(); var info = feature.information; var element = overlay.getElement(); console.log(element); var descriptionHTML = "<div>" + info.name + "</div>"; element.innerHTML = descriptionHTML; overlay.setPosition(coordinates); map.addOverlay(overlay); } else { map.removeOverlay(overlay); } }); </script> </body> </html> 地図をクリックして、点からプロットをどうやって表示している箇所は下記になる。 // ポップアップを表示するためのオーバーレイを作成する var overlay = new ol.Overlay({ element: document.getElementById('popup'), positioning: 'bottom-center' }); // 地図のクリックイベントを設定 map.on('click', function (evt) { var feature = map.forEachFeatureAtPixel(evt.pixel, function (feature) { return feature; }); if (feature) { var coordinates = feature.getGeometry().getCoordinates(); var info = feature.information; var element = overlay.getElement(); console.log(element); var descriptionHTML = "<div>" + info.name + "</div>"; element.innerHTML = descriptionHTML; overlay.setPosition(coordinates); map.addOverlay(overlay); } else { map.removeOverlay(overlay); } }); 地図をクリックすると、featureを検知し、そのfeatureからクリックしたfeatureのデータを取得している。 その値を、mapオブジェクトのoverlayオプション(プロパティ)に追加して、プロットという形で表示している。 地図をクリックした動作は、こういうのがあるということを知っておくべきではないかと思う。 まとめ GISのオープンソースを使用し、戸惑った箇所をわかりやすくするつもりで記載した(そうなってないと思われるが)。 最初この辺がわからず説明を受けても、理解できない状況で、事前にこういう理解があったらと思う所である。 地図のオープンソースとして、google mapsが一般的に使用されると思うが、大きな違いは、データセットがgoogl mapsでは充実し、簡単に使用できる所だと思うが、そのデータそのものを利用できない。オープンソースではデータを自由に読み込み、データをセットできるが、その箇所が初心者に情報が少なく戸惑いを感じさせる所でもあると思う。 参考にした本 下記を参考にしました。案件に入る前に、下記に取り組んでいればもっと理解が早かったと思います。 JavaScriptではじめるWebマップアプリケーション (PDF版) OpenLayers4で遊ぼう 無料の地図データをWebに表示! (技術の泉シリーズ(NextPublishing)) 今回記載しているソースは本参考書を参考にさせて頂いております。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javascript 私は初心者 復習 スコープ

スコープ const introduce = () =>{ const name ="qiita"; //○定数nameが使える範囲内 }; //×定数nameが使えない範囲内 関数の引数や関数内で定義した定数や変数は、その関数の中でしか使うことができません。 このような定数や変数の使用できる範囲のことをスコープという。 関数以外のスコープ if(条件式){ const name ="qiita"; //○定数nameが使えるスコープ } //×定数nameが使えない範囲内 while(条件式){ const name = "qiita" //○定数nameが使えるスコープ } //×定数nameが使えない範囲内 変数には、その変数が使える範囲を決定するスコープという概念があります。 ローカルスコープ(ある一定の場所) function introduce(){ var name = "qiita";//name ローカル変数 //○変数nameが使える! } //×変数nameが使えない!(name スコープ外) 関数の中で定義された変数はローカルスコープというスコープを持ち、 その変数が定義された関数の中でしか使うことができない。 また、ローカルスコープを持つ変数のことをローカル変数と呼びます。 グローバルスコープ(広い) var name = "qiita" function introduce(){ //○変数nameが使える!(nameのスコープ内) } //○変数nameが使える!(nameのスコープ内) 関数の外で定義された変数はグローバルスコープというスコープを持ちプログラムのどこでも使うことができます。 また、グローバルスコープを持つ変数のことをグローバル変数と呼びます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ループ内の要素をすべて書き換えたい

例えばforeach内のテキストを、innerHTMLですべて書き換えたい場合の方法 hoge.php @foreach($hoges as $hoge) <p class="rewrite">これから変更されます</p> @endforeach hoge.js var target = document.getElementsByClassName("rewrite") for(var i = 0; i < target.length; i++){ target[i].innerHTML = "<p>すべて変更されました〜</p>" }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

フロント側でフォームのバリデーションを追加したのでその忘備録

Qiita初投稿です。 とある会社でフロント開発に携わらせていただいてます。 フォームの作成において 「inputタグに特定のクラスをつけるだけで必須項目として認識するようにしたい」 とふと思ったのでjQueryを使って作成してみました。 バリデーション 入力内容が適当かどうかを入力チェックすこと (必須項目です! 適切なメールアドレスを記入してください! などとお叱りをいただけます) 正規表現 正規表現とは、「さまざまな文字列を一つの文字列で表すことができる記法」です。 javascriptだと/ac+c/などのようなスラッシュで囲まれた正規表現リテラルを利用します。 https://okinawanpizza.hatenablog.com/entry/2020/05/22/104507 例) ^[A-Za-z0-9]{1} = 先頭がアルファベットもしくは数字の文字列 電話番号、メールアドレスのチェック 先程の正規表現を用いると以下のようなチェックが可能となります。 //メールアドレスのバリデーション(例) var regexp = /^[A-Za-z0-9]{1}[A-Za-z0-9_.-]*@{1}[A-Za-z0-9_.-]{1,}\.[A-Za-z0-9]{1,}$/; if (!regexp.test(content)) { //正規表現とマッチしなかった時の処理 } test()メソッドは、正規表現と指定された文字列が一致するかどうかを調べるメソッドです。論理値を返却します。 //電話番号のバリデーション(例) let contentTel = content.replace(/\D/g, ''); if !contentTel.match(/^(0[5-9]0[0-9]{8}|0[1-9][1-9][0-9]{7})$/)) { flag = true; } replace()メソッドは文字列の中にある、指定した文字列を別の文字列へと置換してくれます。 ここで数字以外の文字をトリミングしてくれます。 --追記-- 電話番号のトリミングの部分に関しましてアドバイスいただきました。 @mmnn2o さんありがとうございます!! 指定した入力項目だけを必須項目にする 本題です(笑) inputタグに "require"というクラスをつけるだけで入力チェックを行えるようにします。 手順は以下の通り。 ①requireがついたinputタグのバリデーション結果を記憶するためのハッシュを作る。 ②requireという名前のクラスがついたinput全てに背番号をつけてハッシュに格納する。 ③入力が終了した瞬間に入力チェックを行い、結果をハッシュに登録する。 ④入力項目のタイプに応じたバリデーション処理を作成する。 ⑤ハッシュの値全てがtrueなら、送信ボタンを押せる状態にする。 ⑥おまけ:input以外のチェック項目を追加したい... ①、②必須項目を登録 let valitationHash = {}; let $require = $(".require"); $require.each(function (index, element) { let name = "input" + index; $(element).attr("data-input-name", name); //⑥番のおまけで少し変更あり valitationHash[name] = false; //valitationHash = {input1: false, input2: false, ....} }) requireのついたinput要素にdata-input-name属性を追加します。 こちらの値が背番号となり、入力チェックのタイミングで使用します。 ハッシュの中はvalitationHash = {input1: false, input2: false, ....}となり ⑤のチェック段階では全ての値がtrueになれば送信を押せるようにします。 ③入力チェックの結果をハッシュに登録 $(".require").change(function() { checkValitate(this); }) function checkValitate(elem) { let type = $(elem).attr("type"); let name = $(elem).attr("data-input-name"); let flag = true; let content = $(elem).val(); if(type == 'text') { //type="text"の場合の入力チェック if(content == ""){ flag = false; } } else if (type == 'email') { //type="email"の場合の入力チェック var regexp = /^[A-Za-z0-9]{1}[A-Za-z0-9_.-]*@{1}[A-Za-z0-9_.-]{1,}\.[A-Za-z0-9]{1,}$/; if (!regexp.test(content)) { flag = false; } } else if (type == 'tel') { let contentTel = content.replace(/[━.*‐.*―.*-.*\-.*ー.*\-]/gi,''); console.log(contentTel); if (!contentTel.match(/^(0[5-9]0[0-9]{8}|0[1-9][1-9][0-9]{7})$/)) { flag = false; } else if (type == 'example'){ if(checkExample()){ flag = false; } } setValitationHash(name , flag) //⑤番号とチェック結果を引数として渡します } inputのタイプべつにバリデーションの項目が違うので 入力項目の種類別に分岐させます。 ⑤ハッシュの値全てがtrueなら、ボタンを押せるようにする function setValitationHash (name, boolean){ valitationHash[name] = boolean; pushable(valitationHash); } function pushable (hash) { let pushableValue = true; for (let key in hash) { if(hash[key] == false){ pushableValue = false; } } if(pushableValue){ buttonOn() } else { buttonOff() } } function buttonOn() { $(".button").prop("disabled", false); } function buttonOff() { $(".button").prop("disabled", true); } for..in..文を利用してハッシュの全ての値をチェックします。 全てtrueであれば送信可能にして完了です。 //for in サンプル let ageList = {yusaku:"12", kiyomi: "25", yuki: "30"}; for (let name in ageList) { console.log("私は" + name + "、" + ageList[name] + "です。") } おまけ フォームってinputタグだけじゃないことをすっかり忘れておりました。 どうにかselectタグもrequire仲間に加えてあげたい、、 ⑥selectタグを認識できるように修正 汎用性を持たせるために、 inputの属性に新しく data-input-typeを追加します。 $require.each(function (index, element) { let name = "input" + index; setTypeAndName(element, name) valitationHash[name] = false; }) function setTypeAndName(element, name) { let type = "text"; let tagname = $(element).prop("tagName"); if(tagname == "SELECT"){ type = "select"; } else if (tagname == "INPUT"){ type = $(element).attr("type"); } else if (tagname == "SPAN") { type = "checkbox" } $(element).attr("data-input-name", name); $(element).attr("data-input-type", type); } タグの名前で判別してdata-input-type属性に"select"を追加しました。 prop("tagName")でタグの値を取得できます。 そしてcheckValitate(elem)関数にdata-input-type属性がselect時のパターンを追加します。 function checkValitate(elem) { let type = $(elem).attr("data-input-type"); // type属性ではなくdata-input-type属性を取得するように処理を変更 . .c } else if (type == 'select') { //処理内容 } . . } おしまい 今回Qiitaに初めて投稿させていただきました。 今回のコーディングでバリデーションの他のライブラリを調べるきっかけにもなってすごくいい経験になりました。 また、ワードプレスのコンタクトフォーム7など フロントでのバリデーション設定がないものでも意外に使えたりしたので、よかったかなと思います(笑) 未経験からweb制作に携わらせていただき一年が経ちましたがやっぱりJavaScriptを触るのは楽しいですね。 もっといい方法があればアドバイスいただけると嬉しいです。励みになります。 ご清澄ありがとうございました!! 参考 test()メソッド https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test 正規表現 https://qiita.com/soarflat/items/2b50c1efe2ea54a762d7 連想配列をループさせる https://qiita.com/wifeofvillon/items/15359535a834832e08ea
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JS~分割代入~

概要 配列やオブジェクトから値を取り出して1つずつ別の変数に代入するのはコード量が増えます。 そこで、分割代入を使うことにより配列要素やオブジェクトのプロパティの値を短い記述で取り出すことができます。 今回使用する配列はこちらです。 index.js const fruits = ["りんご","ぶどう","みかん","マンゴー"]; 分割代入 1、分割代入 index.js const fruits = ["りんご","ぶどう","みかん","マンゴー"]; const[f1,f2,f3] = fruits; console.log(f1,f2,f3); //りんご、ぶどう、みかん 配列fruitsを定義します。 分割代入では、左辺に[ ]に変数をかきます。そして右辺に配列名を書くことで、右辺の配列から要素を得て対応する位置に書いた変数に代入します。 2、分割代入のスキップ残余取得 index.js const[f1,,...f2] = fruits; console.log(f1,f2); //りんご ["みかん","マンゴー"] 変数を飛ばしたいときは変数を書かずに「,」のみかきます。 また今回のようにりんごだけ取り出し、あとは配列で取り出したい場合はレスト構文を使用します。表し方は「...」です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JS~スプレッド構文~

概要 スプレッド構文についての記事です。 スプレッド構文は配列内での使用と関数内での使用があります。 スプレッド構文 配列を展開して渡すことができます。 1.配列内 fruitsの配列内の要素をfruit2に追加します。 index.js const fruits = ["りんご", "ばなな", "ぶどう", "いちご"]; const fruits2= ["パパイヤ", "パイナップル", "みかん", ...fruits]; console.log(fruits2); //["パパイヤ", "パイナップル", "みかん", "りんご", "ばなな", "ぶどう", "いちご"] forEachを使用しても結果は同じでした。 このように繰り返し処理を使用して要素の展開をせずにfruits2に追加することができます。 index.js const fruits = ["りんご", "ばなな", "ぶどう", "いちご"]; const fruits2= ["パパイヤ", "パイナップル", "みかん"]; fruits.forEach((fruit) =>{ console.log(fruit); fruits2.push(fruit) }); console.log(fruits2); //["パパイヤ", "パイナップル", "みかん", "りんご", "ばなな", "ぶどう", "いちご"] 2.関数での使用 またこのスプレッド構文は関数内でも使用することができます。 関数に値を渡すときに配列を展開して渡す方法です。 関数に引数が複数ある場合、配列の要素を1つずつ展開して渡すのは記述量も多くなり大変です。そこで[...]を使用することで配列の要素を1つずつ渡すことができます。 index.js function fruitsOrder(f1,f2,f3,f4) { console.log(f1,f2,f3,f4); } const fruits = ["りんご", "ばなな", "ぶどう", "いちご"]; fruitsOrder(...fruits); //りんご ばなな ぶどう いちご index.js function fruitsOrder(f1,f2,f3,f4) { const fruits2 = [f1,f2,f3,f4]; fruits2.forEach(fruits3=>{console.log(`${fruits3}です`);}); } const fruits = ["りんご", "ばなな", "ぶどう", "いちご"]; fruitsOrder(...fruits); //りんご です //ばななです //ぶどうです //いちごです index.js function fruitsOrder(fruit) {  console.log(`${f}です`); } const fruits = ["りんご", "ばなな", "ぶどう", "いちご"]; fruits.forEach(fruit => { fruitsOrder(fruit); }); //りんごです //ばななです //ぶどうです //いちごです
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

?話は聞かせてもらった JavaScriptは滅亡する!

というわけで、「2035年あたりに互換の切られた後継言語に移り、2050年あたりにJavaScriptは廃止される」と妄想しているので、ロードマップや後継言語の仕様を適当に予言?‍♂️していきます。 この手の大きな話題を語るには知識不足すぎるので、仕様策定とかブラウザベンダー間の政治に詳しい有識者の人の突っ込みはもちろんのこと、そうでない人も気軽にコメントしてくださると嬉しいです。 ロードマップ 年 出来事 ~2025年 IE11全廃後を見据えて、後方互換性を切る大胆な改革を模索する動きが水面下で始まる 2025年~2030年 主要ブラウザベンダー間の政治で揉める。Chromeが先行独自実装を本番ブラウザに搭載し始める 2030年~2035年 ようやく後継言語の概要が纏まる。主要ブラウザに搭載開始 2035年~ 後継言語の普及開始 ~2050年 JavaScriptが主要ブラウザから全廃 変更点 言語名の変更 これは間違いない。 「Web Script」あたりであっさり決着しそう。……あ、でも普通すぎて検索性が悪そうだし、権利関係の兼ね合いもあるので、今流行りの造語系になるかも。じゃあ「W3bS」で? JSのみの破壊的変更に留めるか、HTML/CSS/JSを巻き込むレベルの大改革するかで揉める 個人的には五分五分だと思ってるので「揉める」という所までストップ。 昔ならいざ知らず、「今更GUI周りを言語仕様に直結させて肥大化させるのはどうか」というのも凄い分かります。 ……が、今なお最大の用途はブラウザ上のGUI構築だし、JSからHTML/CSS操作するのが常態化してて、疎結合すぎ/仕組みも文法もバラバラでしんどいってのもあるので、もうちょっと密になっていいのではないかという感覚もあります。 ただ、この系譜は流石に今の仕様と距離がありすぎて、まったくの別言語&フレームワークで作る流れになるかも。WASM次第。あとこの系譜はJSX的なGUIコンポーネントのためのテンプレートファイル(?)の言語レベルでのサポートも入りそう。 ES2015の時点で存在する主要機能は全て残る。クラスも残る。 ES2015以降のみとなるとかなり思い切ったように思えますが、あくまで使用策定が2025年前後に行われ始めるという前提のため、JSからの移行を意識して過去10年の仕様ぐらいは流石にカバーするだろうという読みです。 型を言語レベルでどこまでやるかで揉める こちらもどう転ぶんか読み切れないので揉めるという所まででストップ。 互換性切るんだったら……ということで、TypeScriptベースでかなり取り込みは行われるかと思いますが、どこまでやるか、どういう仕様にするかでめちゃくちゃ揉めそう。 次期JSの動きが活発化しそうなターニングポイント IE11終了によって実装の足並みが揃う(2025年) WASM系の進展 2020年代を制するようなブレイキングスルー(多分ハード系)が起きて、現行のJSの流れでは対応に制約が起きて、改革の需要が高まる 後書き 後継言語になっても既存のユーザーを移行させる必要があるので、極端な変更はできないでしょうね~。一方、切らなさすぎると後継言語の必要性が薄れてしまうので、そこらへんの激しい綱引きが今後10年ぐらいで見られると思っています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

関数のみでレトロゲーム音楽を再現

概要 状態を管理しない関数のみで、音楽波形データをシンプルに生成できないだろうか? 外部ライブラリを使うことなく、指定時間におけるサンプル値を返す関数、sample(t)のみで音楽を表現する。 再現した曲と生成物 ファルコム音楽フリー宣言 を活用させていただき、今回対象曲として、 翼をもった少年 / ミュージックフロム イースIII ワンダラーズフロムイース / Copyright © Nihon Falcom Corporation を利用させてもらった。たいへんありがたい。 リアルタイム生成を試せるページは こちら。(Chrome、Safariで動作確認した) wavファイル生成してmp3に変換したものは こちら。 コード(JavaScriptを主とする約600行のhtmlファイル)はこちら 。 準備 以下のhtmlを雛形として開始する。 <!DOCTYPE html> <meta charset="utf-8"> <div><button class="start" autofocus>Start</button></div> <script> const sampleRate = 44100; function sample(t) { return 0.25 * Math.sin(2 * Math.PI * 440 * t); } document.querySelector(".start").onclick = (e) => { const AudioContext = window.AudioContext || window.webkitAudioContext; const audioCtx = new AudioContext(); const seconds = 3; const audioBuffer = audioCtx.createBuffer(1, seconds * sampleRate, sampleRate); const data = audioBuffer.getChannelData(0); for (let i = 0; i < seconds * sampleRate; ++i) { const t = i / sampleRate; data[i] = sample(t); } const audioBufferSourceNode = audioCtx.createBufferSource(); audioBufferSourceNode.buffer = audioBuffer; audioBufferSourceNode.connect(audioCtx.destination); audioBufferSourceNode.start(audioCtx.currentTime + 0.5); } </script> 「start」ボタンを押すと、sampleを3秒間分呼び出しして、波形データを生成し、それを Web Audio を利用して再生する。 当然ながら Web Audio は波形データを再生することだけに使っていて、Web Audio のシンセサイザ機能は利用していない。 本質的な部分は、 function sample(t) { return 0.2 * Math.sin(2 * Math.PI * 440 * t); } と、時間から1サンプルデータを返している部分のみだ。 これで440Hzのラの音が3秒間鳴ることが確認できるはずだ。 今回の音楽生成は基本的にこの延長で実現することになる。 これで、今後実装していくsample(t)を確認するための環境の準備が整った。 簡単なFM変調 進める上で、sin関数そのままの音ではちょっと気持ち悪いし聞き取りづらい。 この段階で簡単でもよいのでFM音源的なFM変調を掛ける。 return 0.2 * Math.sin(angVel * t); となっていた部分を、 return 0.05 * Math.sin( angVel * t + 4 * Math.sin(1 * angVel * t) ); としよう。 FM変調は基本的に、上記のようにsinの中にもう1つsinを足してしまえばよい。 中のsinの角速度は、基本的には、基準となる角速度の整数倍にする。 倍音が出て音が少し聞き取りやすくなったと思う。 各種値を変更することで音色の変化を確認できる。 FM音源については FMシンセのあたらしいトリセツ がお勧めだ。 音色関数 音を任意の時間・音階で複数鳴らせるようにするために、音色部分を関数化する。 音階を周波数で指定するのは難しいため、まずはMIDI準拠のノートナンバーから周波数を計算できるようにしておく。 69が440Hzのラで、1増減するごとに半音ずつ増減する。 function angularVelFromNoteNumber(nn) { return 440 * Math.pow(2, (nn - 69) / 12) * Math.PI; } 時間・音の長さ・ノートナンバーを受け取って、1つの音として鳴らせるようにする。 function toneSimple(t, duration, nn) { if (t < 0 || duration < t) return 0; const angVel = angularVelFromNoteNumber(nn); return 0.2 * Math.sin(angVel * t); } 範囲外だった場合は、すぐに0を返すようにすることで、特定区間の間だけ鳴るようにしている。 この関数自体は、時間0から音が鳴り始める前提にしておき、0以外の時間に音を鳴らしたいときは、渡す前の時間を減算する。 上記を使って、1秒ずつドレミを鳴らすsample(t)は、以下になる。 function sample(t) { let d = 0; d += toneSimple(t - 0, 1, 72); d += toneSimple(t - 1, 1, 74); d += toneSimple(t - 2, 1, 76); return d; } これで任意の時間・音階で音色を鳴らせるようになった。 エンベロープ ここまでの実装だと音の開始と終了がプツプツ切れてしまうし、鳴っている間の音が平坦だ。 そこで、ノートオン・ノートオフに対して音量がどのように変化するかを表すエンベロープを導入する。 Wikipediaの ADSR の項目が参考になる。 今回は状態管理なしで実現する必要があるため、tを受け取り音量を返す関数が必要になる。 各傾斜をclamp関数で表現すると良さそうに思われたため、clamp関数を用意しておく。 function clamp(val, minVal, maxVal) { return Math.max(minVal, Math.min(val, maxVal)); } 各時間がゼロの場合、ゼロ除算で分岐が必要になり複雑になることと、速度にも影響があることから、 エンベロープ関数は必要な引数に応じて分離することにした。 function envelopeR(t, rt) { return Number(0 <= t) - clamp(t / rt, 0, 1); } function envelopeAR(t, at, rt) { return clamp(t / at, 0, 1) - clamp((t - at) / rt, 0, 1); } function envelopeASR(t, duration, at, rt) { return Math.max(0, ( clamp(t / at, 0, Math.min(duration / at, 1)) - clamp((t - duration) / rt, 0, 1) )); } function envelopeDR(t, duration, dt, rt) { return Math.max(0, ( Number(0 <= t) - clamp(t / dt, 0, duration / dt) - Math.max(0, (t - duration) / rt) )); } function envelopeADR(t, duration, at, dt, rt) { return Math.max(0, ( clamp(t / at, 0, Math.min(duration / at, 1)) - clamp((t - at) / dt, 0, Math.max(0, (duration - at) / dt)) - Math.max(0, (t - duration) / rt) )); } Sustain指定あり版は、今回使わなかったので用意しなかったが、普通に実装できるはず。 アタックタイム途中でノートオフ(durationを超えた)場合は、リリースはその時点での音量から下がるよう考慮が必要になるため、envelopeADRは少し複雑になっている。 これを音色関数に組み込んでみる。 function toneSimple(t, duration, nn) { const e = envelopeADR(t, duration, 0.02, 1.5, 0.1); if (e == 0) return 0; const angVel = angularVelFromNoteNumber(nn); return 0.05 * e * Math.sin( angVel * t + 4 * Math.sin(1 * angVel * t) ); } エンベロープによりだいぶ音が自然になった。 テンポと拍 ここまで、時間は秒で指定していたが、実際の音楽の入力を考えると、拍を使いたい。 拍を使うために必要なのは、拍と秒の変換だ。 拍と秒の変換はテンポに依存する。 const tempo = 120; const spb = 60 / tempo; spbは seconds per beat、1拍あたりの秒数だ。 使用頻度が高いので定数名も短くしている。 これを使えば、これまで秒数で指定してた音の関数への指定を拍指定にできる。 function sample(t) { let d = 0; d += toneSimple(t - 0 * spb, 1 * spb, 72); d += toneSimple(t - 1 * spb, 1 * spb, 74); d += toneSimple(t - 2 * spb, 1 * spb, 76); return d; } 四分音符指定でドレミが鳴らせるようになった。 途中でテンポが変化する場合は、ちょっと複雑になりそうだ。この記事では踏み込んでいない。 MML的な入力 ここまでの記述を見ると、ドレミを鳴らすだけでも記述量がだいぶ多いことが気になってくる。 これではさすがに規模の大きい曲を入力は厳しく感じられる。 そこで、最低限の実装で、ある程度MML的な記述ができるようにしていく。 まずは、ノートナンバーを文字列で指定できるようにするために、文字列からノートナンバーに変換する関数を作る。 function noteNumberFromString(str) { const octaveOffset = 12 * (str.codePointAt(0) - "4".codePointAt(0)); const scaleOffset = { "c": -9, "d": -7, "e": -5, "f": -4, "g":-2, "a": 0, "b": 2 }[str[1]]; const sharpFlatOffset = (str[2] === "+" ? 1 : 0) + (str[2] === "-" ? -1 : 0); return 69 + octaveOffset + scaleOffset + sharpFlatOffset; } これで、4c とか 5d+ とかのMML的な指定で音階を指定できるようになる。 また、入力自体もコンパクトにしたい。複数の音を同時に配列で記載できるようにする。 function notes(t, toneFunc, params) { let d = 0; let nt = 0; for (let p of params) { if (t < nt) break; [noteStr, gateBeat, stepBeat] = p; if (stepBeat === undefined) stepBeat = gateBeat; d += toneFunc(t - nt, gateBeat * spb, noteNumberFromString(noteStr)); nt += stepBeat * spb; } return d; } 色々仕様を考えた結果、 音階名・ゲートタイム(拍)・ステップタイム(拍)の3パラメータの配列を渡すのが、 実装の簡潔さ・表現幅・入力の簡潔さ、のバランスが良さそうに感じた。 ゲートタイムとステップタイムが同じ場合には、ステップタイムは省略できる(ほとんどの場合そうなる)。 また、和音を入力したい場合には、和音の最後以外のステップタイムで0を指定すればよい。 昔「ST/GT方式」と呼ばれる入力があったらしいが、それに近い部分があるかもしれない。 これを利用することで、ドレミは以下のように入力できるようになった。 function sample(t) { let d = 0; d += notes(t, toneSimple, [ ["5c", 1], ["5d", 1], ["5e", 1] ]); return d; } これで最低限の実装でコンパクトさを備えた音符の入力手段が手に入った。 繰り返し 入力をしていると繰り返しをしたくなる場面が出てくる。 この際、普通に for でループを回すこともできるのだが、 せっかく関数を使っているので、t の剰余を使おう。 function repeat(t, duration, count) { if (t < 0) return t; if (count * duration <= t) return t - (count - 1) * duration; return t % duration; } この関数は、t を受け取るが、0から count * duration の間を、duration で割ってその剰余にしてしまう。 こうすると、結果的に t が duration の間を count 回巻き戻って繰り返す値に変換でき、結果として繰り返しになる。 for を使うよりもこちらのほうが圧倒的に速度が速くなる。 実際本記事の対象曲でも、repeat を使わないとリルタイム生成は間に合わなくなることがあった。 function sample(t) { let d = 0; d += notes(repeat(t, 2 * spb, 4), toneSimple, [ ["5c", 0.5], ["5d", 0.5], ["5e", 0.5], ["5f", 0.5] ]); return d; } とすることで、ほぼ速度劣化なしで8分音符のドレミファを4回繰り返すことができる。 ノイズ 最初、シンバル音をFM変調で音色を作ろうとしたが、 オペレータをかなり増やしてもなかなか綺麗な音にならない。 そこで、ノイズを使う方法を検討した。 t から一意に返さなければならないため、単純に Math.random() を使うわけにはいかない。 単純なノイズだけでは問題ないかもしれないが、後述するフィルタに適用する場合、入力が一意である必要があるため、ノイズの結果が時間に対して一意であることは必須になる。 シンプルなノイズ関数としては 線形合同法 が良さそうで、 そのパラメータは Park & Miller が提示しているものを使おう。 サンプルのインデックスを受け取り、31bitの値を返す。 const noise31b = (s) => { if (s <= 0) return 1; return (48271 * noise31b(s - 1)) % (Math.pow(2, 31) - 1); }); とてもシンプルだが、再帰が使われている点が問題だ。 サンプルのインデックスが大きいと再帰が深くなりすぎてスタックオーバーフローが起きてしまう。 また、そうでなくても時間が掛かりすぎる。 そこで、渡した関数に対して、0から必要部分までをメモ化し、それを利用して値を返す機能を追加した関数を返す関数を作成する。 const memoiseFrom0 = (f) => { const memo = []; return (n) => { if (n < 0) return f(n); for (let i = memo.length; i <= n; ++i) memo[i] = f(i); return memo[n]; } }; これを使って先ほどの noise31b 関数を以下のように変更する。 const noise31b = memoiseFrom0((s) => { if (s <= 0) return 1; return (48271 * noise31b(s - 1)) % (Math.pow(2, 31) - 1); }); これで、noise31b 関数は、スタックオーバーフローを起こさないし、速度も速くなった。 noise31b は31bit値なので、そのままでは使いづらい、他と同じように -1 ~ 1 の値を返す noise 関数を作る。 function noise(s) { return noise31b(s) / Math.pow(2, 30) - 1; } また、このノイズ関数を使った音色関数も作ろう。 function toneNoise(t, duration, nn) { if (t < 0 || duration <= t) return 0; return 0.1 * noise(Math.round(t * sampleRate)); } これを使って、 function sample(t) { let d = 0; d = toneNoise(t, 1 * spb, 60); return d; } とすることでホワイトノイズが鳴らせるようになった。 とはいえ、このままではまだシンバルには遠い。次の項目ではこれにフィルタを掛けてシンバルに近づける。 フィルタ シンバルの音はノイズなのだが、特定の周波数付近のみに偏っているノイズになっている。 そこで、前項のノイズに対してバンドパスフィルタを掛ける。 バンドパスフィルタについては biquad filter という結果だけ使う分には簡単な方法が確立している。 const bandpassFilteredNoise = memoiseFrom0((s) { const freq = 8000; const q = 0.5; if (s < 0) return 0; const omega = 2 * Math.PI * freq / sampleRate, alpha = Math.sin(omega) * q; const a = [1 + alpha, -2 * Math.cos(omega), 1 - alpha]; const b = [alpha, 0, -alpha]; return ( b[0] / a[0] * noise(s) + b[1] / a[0] * noise(s - 1) + b[2] / a[0] * noise(s - 2) - a[1] / a[0] * bandpassFilteredNoise(s - 1) - a[2] / a[0] * bandpassFilteredNoise(s - 2) ); }); 上記の例では、ノイズに対して、8000Hz近辺でバンドパスフィルタを掛けたノイズを取得できる関数を作っている。 q の値は、バンドパスフィルタの幅を指定することができる。小さいほど幅が狭くなる。 こちらもノイズ関数と同じように、再帰が大きくなりすぎる問題があるため、memoiseFrom0 を適用している。 また、実際には、freq と q が異なる様々なバンドパスフィルタありノイズ関数を作りたいため、 バンドパスフィルタありノイズ関数を生成する関数を作る。 function makeBandpassFilteredNoiseFunc(freq, q) { const func = memoiseFrom0((s) => { if (s < 0) return 0; const omega = 2 * Math.PI * freq / sampleRate, alpha = Math.sin(omega) * q; const a = [1 + alpha, -2 * Math.cos(omega), 1 - alpha]; const b = [alpha, 0, -alpha]; return ( b[0] / a[0] * noise(s) + b[1] / a[0] * noise(s - 1) + b[2] / a[0] * noise(s - 2) - a[1] / a[0] * func(s - 1) - a[2] / a[0] * func(s - 2) ); }); return func; } これを利用して、シンバル音色関数を作る。 const cymbalNoise = makeBandpassFilteredNoiseFunc(4000, 0.2); function toneCymbal(t, duration, nn) { const e = envelopeAR(t, 0.05, 2); if (e == 0) return 0; const d = 0.1 * cymbalNoise(Math.round(t * sampleRate)); return d * Math.pow(e, 4); } これを鳴らす。 function sample(t) { let d = 0; d = toneCymbal(t, 1 * spb, 60); return d; } ここまで、それらしいシンバルの音が出せるようになった。 以下近日追記予定 各種音色 規模大きさへの対応 まとめ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript基礎】画面サイズに応じて処理を分ける

参考URL 参考著書 画面サイズがブレークポイントを超えたときに処理をする 画面サイズに応じて処理を分けたいときは、下記のメソッドとプロパティが役立ちます。 メソッド メソッド 意味 戻り値 matchMedia(メディアクエリ) メディアクエリの情報 オブジェクト(MediaQueryList) matchMedia(メディアクエリ).addListener(処理) メディアクエリに一致したときに処理を実行する なし matchMedia()メソッドは、引数に応じたメディアクエリの情報を返します。 例えば、「ウィンドウ幅が768px以上」を示す「(min-width:768px)」を渡すと、下記の情報が返ってきます。 const mediaQueryList = matchMedia('(min-width:768px)'); console.log(mediaQueryList); // 出力結果 // { // matches: true,  //ウィンドウサイズが768px以上のとき // media: "(min-width: 768px)", // onchange: null // } プロパティ プロパティ 内容 型 matchMedia(メディアクエリ).matches メディアクエリに一致するかどうか 真偽値 matchesプロパティを用いて、ブラウザウィンドウのサイズがメディアクエリに一致するかどうかを調べられます。 // ウィンドウサイズが768px以下ならtrue matchMedia('(min-width:768px)').matches; // ウィンドウサイズが500px以上1000px以下ならtrue matchMedia('(min-width:500px) and (max-width:1000px)').matches; スマートフォンの縦向き・横向き変更を検知したいとき スマートフォンの縦向き・横向き変更を検知したいときは、下記のようにコールバック処理が設定できます。 // (orientation:portrait)は横持ちを示す const mediaQueryList = matchMedia('(orientaton: portrait)'); mediaQueryList.addListener(() => { console.log('デバイスの向きが変更されました。'); }); 画面幅の変更タイミングで処理を行いたいとき 画面幅の変更(メディアクエリの変更タイミング)で処理を行いたい時は、下記のようにコールバック処理が設定できます。 // メディアクエリ情報 const mediaQueryList = matchMedia('(min-width:768px)'); mediaQueryList.addEventListener('change', (e) => { if (e.matches) { /**画面幅が768px以上のときの処理 */ console.log('画面幅が768px以上です。'); } else { /**画面幅が768px以上のときの処理 */ console.log('画面幅が768px未満です。'); } });
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コピペで済ませる。JavaScript 環境ならどこでも使えそうなそんなに長くないコードで、フォーマット指定して文字列からDate型に変換する関数

前がき タイトル長くてすいません。 前回、こんな記事を書きました。 その記事は、dateToString なので、日付を文字列に変換です。 今回の記事は stringToDate です。文字列から日付変換です。 単に日付を書式付きで出力するのは前回記事のようにまずまず簡単なのですが、出力した文字列を日付型に戻すのは結構たいへんなので、そこで自前実装をあきらめて Moment.js や Day.js を導入するきっかけにすることは非常に多くあるのではないでしょうか。 とはいえ、日付ライブラリをいれたくないなあ、という場面もあるかもしれなく、また、私は自前実装をあきらめないタイプなので実装してみました。 日付を文字列としてデータベースやlocalStorageやテキストファイルとかに記録しておいて、その文字列を日付に戻して動作させるという処理は便利なので多くありますので、そういう時に使えるでしょう。 少し長いのですがコピペコードは次の通り。コードが長いのは、前回の dateToString のマルチフォーマット版(前回記事のコメント欄で作ってる)を組み込んでいるからです。 コード const indexOfAnyFirst = ( str, searchArray, indexStart = 0, ) => { let result = Infinity; let searchIndex = -1; searchArray.forEach((search, index) => { const findIndex = str.indexOf(search, indexStart); if (findIndex !== -1) { if (findIndex < result) { result = findIndex; searchIndex = index; } } }); if (result === Infinity) { return { index: -1, searchIndex: -1, }; } return { index: result, searchIndex, }; }; const replaceAllArray = (str, replaceArray) => { const searchArray = replaceArray.map(element => element[0]); let start = 0; let result = ''; while (true) { const searchResult = indexOfAnyFirst(str, searchArray, start); if (searchResult.index === -1) { result += str.substring(start); break; } if (start < searchResult.index) { result += str.substring(start, searchResult.index); start = searchResult.index; } result += replaceArray[searchResult.searchIndex][1]; start += searchArray[searchResult.searchIndex].length; } return result; }; const dateToString = (format, date) => { const padFirstZero = (value) => { return ('0' + value).slice(-2); } const year4 = date.getFullYear(); const year2 = date.getFullYear().toString().slice(-2); const month1 = (date.getMonth() + 1).toString(); const month2 = padFirstZero(month1); const date1 = date.getDate().toString(); const date2 = padFirstZero(date1); const hours1 = date.getHours().toString(); const hours2 = padFirstZero(hours1); const minutes1 = date.getMinutes().toString(); const minutes2 = padFirstZero(minutes1); const seconds1 = date.getSeconds().toString(); const seconds2 = padFirstZero(seconds1); const day3 = ['Sun', 'Mon', 'Tue', 'Wed', 'Thr', 'Fri', 'Sat'][date.getDay()] const replaceTable = [ ['YYYY' , year4], ['YY' , year2], ['M' , month1], ['MM' , month2], ['D' , date1], ['DD' , date2], ['H' , hours1], ['HH' , hours2], ['m' , minutes1], ['mm' , minutes2], ['S' , seconds1], ['SS' , seconds2], ['DDD' , day3], ] replaceTable.sort((a, b) => b[0].length - a[0].length); let result = format; return replaceAllArray(result, replaceTable); }; // const day1 = new Date('2021-04-26') // console.log(dateToString('YYYY-MM-DD', day1)); // console.log(dateToString('D-M-YY', day1)); // console.log(dateToString('YYYY/MM/DD HH:mm:SS(DDD)', day1)); // console.log(dateToString('YYYYMMDDHHmmSSDDD', day1)); // console.log(dateToString('DDDSSmmHHDDMMYYYY', day1)); // 2021-04-26 // 26-4-21 // 2021/04/26 09:00:00(Mon) // 20210426090000Mon // Mon00000926042021 const stringToDate = (format, str) => { const YYYY = '(\\d{4})' const YY = '(\\d{2})' const MM = '(\\d{2})' const M = '(\\d{1,2})' const DD = '(\\d{2})' const D = '(\\d{1,2})' const HH = '(\\d{2})' const H = '(\\d{1,2})' const mm = '(\\d{2})' const m = '(\\d{1,2})' const SS = '(\\d{2})' const S = '(\\d{1,2})' const now = new Date(); const result = new Date(now.getFullYear(), now.getMonth(), now.getDate()); let resultArray; switch (format) { case 'YYYY/MM/DD': resultArray = str.replace( new RegExp(`^${YYYY}/${MM}/${DD}$`), '$1,$2,$3' ).split(',') result.setFullYear(parseInt(resultArray[0])); result.setMonth(parseInt(resultArray[1]) - 1); result.setDate(parseInt(resultArray[2])); break; case 'D-M-YY': resultArray = str.replace( new RegExp(`^${D}-${M}-${YY}$`), '$1,$2,$3' ).split(',') result.setFullYear(parseInt(resultArray[2]) + 2000); result.setMonth(parseInt(resultArray[1]) - 1); result.setDate(parseInt(resultArray[0])); break; case 'M-D-YY': resultArray = str.replace( new RegExp(`^${M}-${D}-${YY}$`), '$1,$2,$3' ).split(',') result.setFullYear(parseInt(resultArray[2]) + 2000); result.setMonth(parseInt(resultArray[0]) - 1); result.setDate(parseInt(resultArray[1])); break; case 'YYYY/MM/DD HH:mm:SS(DDD)': resultArray = str.replace( new RegExp(`^${YYYY}/${MM}/${DD} ${HH}:${mm}:${SS}.*$`), '$1,$2,$3,$4,$5,$6' ).split(',') result.setFullYear(parseInt(resultArray[0])); result.setMonth(parseInt(resultArray[1]) - 1); result.setDate(parseInt(resultArray[2])); result.setHours(parseInt(resultArray[3])); result.setMinutes(parseInt(resultArray[4])); result.setSeconds(parseInt(resultArray[5])); break; default: throw new Error(`stringToDate args:format(=${format}) is not supported.`); } if (dateToString(format, result) === str) { return result; } else { return new Date(''); // Invalid Date } } console.log(stringToDate('YYYY/MM/DD', '2021/05/01').toString()); // Sat May 01 2021 00:00:00 GMT+0900 (日本標準時) console.log(stringToDate('YYYY/MM/DD', '2021/05/00').toString()); // Invalid Date console.log(stringToDate('YYYY/MM/DD', '2021/04/30').toString()); // Fri Apr 30 2021 00:00:00 GMT+0900 (日本標準時) console.log(stringToDate('YYYY/MM/DD', '2021/04/31').toString()); // Invalid Date console.log(stringToDate('YYYY/MM/DD', '2021/5/01').toString()); // Invalid Date console.log(stringToDate('D-M-YY', '1-12-20').toString()); // Tue Dec 01 2020 00:00:00 GMT+0900 (日本標準時) console.log(stringToDate('M-D-YY', '12-1-20').toString()); // Tue Dec 01 2020 00:00:00 GMT+0900 (日本標準時) console.log(stringToDate('D-M-YY', '21-1-20').toString()); // Tue Jan 21 2020 00:00:00 GMT+0900 (日本標準時) console.log(stringToDate('M-D-YY', '1-21-20').toString()); // Tue Jan 21 2020 00:00:00 GMT+0900 (日本標準時) console.log(stringToDate('M-D-YY', '1-12-20').toString()); // Sun Jan 12 2020 00:00:00 GMT+0900 (日本標準時) console.log(stringToDate('M-D-YY', '21-1-20').toString()); // Invalid Date console.log( stringToDate('YYYY/MM/DD HH:mm:SS(DDD)', '2021/05/01 11:09:09(Sat)' ).toString()); // Sat May 01 2021 11:09:09 GMT+0900 (日本標準時) console.log( stringToDate('YYYY/MM/DD HH:mm:SS(DDD)', '2021/05/01 11:09:09(Mon)' ).toString()); // Invalid Date コード説明 stringToDate で、日付に変換できない文字列が与えられた場合は、Invalid Date を返すようにしています。(改良してnullやundefinedを返すようにしてもいいと思います。関数の最後の部分を書き換えるだけです。 stringToDate の内部では、dateToString で検算しているので、指定文字列の曜日が変わっていたり、桁数ミスしているだけで、Invalid Date を返します。日付文字列なので厳密判定のほうが安全性が高いのでそのようにしています。 dateToString は、どんなフォーマットでもコード修正なしに動きますが、stringToDate の方は内部でフォーマットを限定しているのであらたなフォーマット指定したければ自分でコード修正して改良してください。 改良しやすく作っているので、フォーマット増やすのは難しくないでしょう。 改良時に気をつけなければいけないのは、setFullYear setMonth など、年月日の順番で設定しなければいけないことです。 初期値を「今日」に指定してから、年月日を設定していっているのですが、「今日」が2月とかだったとして、日月年の順番で設定すると、日の設定で2月31日と設定されたときに、3月3日に変換されたあとに月の設定が動くので日付が正しくあわなくなるからです。 マルチなフォーマット対応のものも作っていきたいところですが、なかなか難しそうですね。 今回はこんなもので。 ではでは。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JS~配列操作~

概要 JSでは配列が最も多く使われるデータ構造です。配列には様々なメソッドが用意されています。 この記事では配列操作について触れていきます。 index.js const fruits = ["りんご", "ばなな", "ぶどう", "いちご"]; const fruits2= ["パパイヤ", "パイナップル", "みかん",]; 今回使用する元の配列です。 値の追加と削除 arrayオブジェクトには、配列の先頭、末尾に要素を追加または削除するメソッドが用意されています。 1.追加 (1)push index.js const fruits = ["りんご", "ばなな", "ぶどう", "いちご"]; const fruits2= ["パパイヤ", "パイナップル", "みかん",]; fruits.push("パパイヤ","パイナップル"); console.log(fruits); //["りんご", "ばなな", "ぶどう", "いちご","パパイヤ","パイナップル"] fruitsとfruits2という2つの配列があります。fruits2の要素をfruitsに追加します。 となります。push()に引数を追加します。複数追加したときは最後の引数が末尾になります。 (2)unshift index.js fruits.unshift("パパイヤ","パイナップル"); //["パパイヤ","パイナップル","りんご", "ばなな", "ぶどう", "いちご"] こちらは要素を先頭に追加します。 複数追加したときは、最初の引数が先頭になります。 削除 (1)pop index.js fruits.pop(); console.log(fruits); //[["りんご", "ばなな", "ぶどう"] 末尾から要素を取り除きます。 (2)shift index.js fruits.shift(); console.log(fruits); //["ばなな", "ぶどう","いちご"] 先頭から要素を削除します。 配列の結合と分離 配列は、複数の配列を結合した新しい配列を作る。配列の一部から新しい配列を作ったりすることができます。 1.concat index.js console.log(fruits.concat(fruits2)); console.log(fruits); //["りんご", "ばなな", "ぶどう", "いちご","パパイヤ", "パイナップル", "みかん"] //["りんご", "ばなな", "ぶどう", "いちご"] 引数の配列の要素、引数の値を順に並べた新しい配列を作ります。 元の配列は影響を受けません。 2.slice index.js console.log(fruits.slice(1,3)); console.log(fruits.slice(2)); console.log(fruits); //["ばなな","ぶどう"] //["ぶどう", "いちご"] //["りんご", "ばなな", "ぶどう", "いちご"] 第1引数から第2引数の手前までをの要素の新しい配列を作ります。 また引数が1つだった場合は、引数から末尾の要素までの新しい要素を作ります。 元の配列は影響を受けません。 3.splice index.js console.log(fruits.splice(1,2,"マンゴー","キウイ")); console.log(fruits); //["ばなな","ぶどう"] //["りんご", "マンゴー", "キウイ", "いちご"] 第1引数が要素インデックス番号、削除する数。更に追加する要素を記述します。 もとの配列は影響をうけます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LeafletでGoogle マップ、OpenStreetMap表示。マーカー表示。現在地ボタン。

地図データを扱うJavaScript ライブラリ「Leaflet」を使って、地図表示を色々試した。 詳細は以下のブログに記載。 Leaflet + OpenStreetMapで、地図にマーカー表示。サンプルソース。 https://2ndart.hatenablog.com/entry/2021/04/28/112259 Leaflet地図:「国土地理院」と「Esri World Topo Map」に切り替え。サンプルソース。 https://2ndart.hatenablog.com/entry/2021/04/28/164803 Leaflet地図:Google MAPS APIなしで「Googleマップ」が使える。サンプルソース。 https://2ndart.hatenablog.com/entry/2021/04/29/111336 Leaflet地図:クリックした地点の緯度・経度、標高を表示。サンプルソース。 https://2ndart.hatenablog.com/entry/2021/04/29/143307 Leaflet地図:現在地表示ボタン追加。位置情報継続的更新。サンプルソース。 https://2ndart.hatenablog.com/entry/2021/05/01/101739
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Redux Toolkit (Immer) で、複数のプロパティをまとめて変更する

あぶすとらくと Redux を使っていると、①ネストの深いところにあるオブジェクトについて、②(元のオブジェクトは変更せずに)複数のプロパティを変更したオブジェクトを作成したい時ってありますよね。 Redux Toolkit で使われている Immer というライブラリのおかげで、①再帰的に長くなるネストを書かなくても良くなりますが、②アップデートされる部分でスプレッド構文の場合よりも繰り返しが少し多くなってしまいます。 そこで、最近影の薄いObject.assign()メソッドを使って、アップデート部分の記述を DRY (Don't Repeat Yourself) にすることを試みました。 環境 Redux Toolkit 1.5.1 React Redux 7.2.3 Typescript 4.0 さらばスプレッド地獄 Redux の State はイミュータブルでなければならないので、状態を変化させるためにスプレッド構文を使うことになるのですが、Redux Toolkit は Immer というライブラリを使用していて、これが私たちをスプレッド地獄から解き放ってくれます。 地獄 return { ...state, textFields: { ...state.textFields, [fieldName]: { ...state.textFields[fieldName], value, isUntouched: false, } } } Immer が無ければ、このように、再帰的にチェーンが長くなっていく地獄みたいな return 文を書かなければいけませんでしたが、 immerのおかげで const {fieldName, value} = action.payload; state.textFields[fieldName].value = value; state.textFields[fieldName].isUntouched = false; Immer のおかげでこのようにチェーンの重複した記述を取り除く事ができます。 見た目からは、オブジェクトのプロパティーを手続き的に変更しているように見えますが、 Immer が魔法の力で新しいオブジェクトをイミュータブルに複製してくれています。ありがたい。 コード全体 store/name-form.ts interface TextField { value: string; isUntouched: boolean; isDisabled: boolean; } interface State { textFields: { name: TextField; } } type TextFieldName = keyof State["textFields"]; const initTextField = ( value: string = "", isUntouched: boolean = true, options?: {isDisabled?: boolean} ): TextField => ({ value, isUntouched, isDisabled: options?.isDisabled ?? false }); const initialState: State = { textFields: {name: initTextField() }}; const slice = createSlice({ name: "name-form", initialState, reducers: { // 入力した時にこのアクションが呼ばれる inputString(state, action: PayloadAction<{ fieldName: TextFieldName, value: string }>) { const {fieldName, value} = action.payload; state.textFields[fieldName].value = value; state.textFields[fieldName].isUntouched = false; }, }, }; それでもまだ、DRYじゃない 変更するべきプロパティが複数あるので、state.textFields[fieldName]を何度も書かないといけないのが面倒ですよね。 存在しない娘の「パパの書いたReducer、ぜんっぜんDRYじゃないね!」という声が聞こえてきそうです。 そんな時は、下のコードのように、その部分を一度変数に入れてしまいましょう。 そうしても、 Immer はきちんと新しいオブジェクトを作ってくれます。かしこいですね。 ちょっと修正 inputString(state, action: PayloadAction<{ fieldName: TextFieldName, value: string }>) { const {fieldName, value} = action.payload; const field = state.textFields[fieldName]; field.value = value; field.isUntouched = false; } それでも僕は、欲張りなので Immer によって、チェーンが消えて、アップデートする部分のみに着目するだけでよくなり、記述が楽になったのは良いですが、スプレッド構文の持ち味である、Shorthand property が使えなくなってしまいました。 むりにShorthandを使うとこうなる state.textFields[fieldName] = { ...state.textFields[fieldName], value, isUntouched: false }; 僕は欲張りなので両方のいいとこ取りをしたい。 そこで、Object.assign()の登場です。 最終 inputString(state, action: PayloadAction<{ fieldName: TextFieldName, value: string }>) { const { fieldName, value } = action.payload Object.assign(state.textFields[fieldName], { value, isUntouched: false }) }, Object.assign(target, source1, ..)メソッドは、シャローコピーのみを行うスプレッド構文とは違い、targetオブジェクトをアップデートしてくれる関数で、source1 etc. オブジェクトのプロパティの値をtargetのオブジェクトに代入して上書きしてくれます。 Immerとは相性抜群ですね。 これで、最大限に DRY な Reducer の完成です! めでたしめでたし。 参照記事 Immer の Github リポジトリにあるテストコード テストの中でObject.assignを使った箇所があります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptで数当てゲームを作りました

Javascript、HTML/CSSで数当てゲームを作りました。 ルール ・1から50までの数字を入力し、5回以内に正解の数を当てるゲームです。 プログラミングコード <!-- 数当てゲーム--> <!-- 新規作成 2021/04/30 --> <!-- author 乃木坂好きのITエンジニア --> <!doctype html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <title>数当てゲーム</title> <style> h1 { color:plum; font-size:100px; } h2 { color:#4f7911; font-size:50px; } body { background-color:whitesmoke; } </style> </head> <body id = "test"> <header> <div class="container"> <h1>数当てゲーム</h1> <h2>数を当ててみよう</h2> </div><!-- /.container --> </header> <main> <div class="container"> <section> </section> </div><!-- /.container --> </main> <footer> <div class="container"> </div><!-- /.container --> </footer> <script> 'use strict'; let message; let count = 1; let flag = 0; //乱数を発生させる。0~49まで数字がランダムに表示されるので1を足す。 const number = Math.floor(Math.random() * 50) + 1; //数当てゲームは5回挑戦できるようにする。 while (count <=5){ const answer = parseInt(window.prompt('数当てゲーム。1〜50の数字を入力してね。5回まで挑戦できます')); window.alert(count + "回目です。") //正解、数が小さい、数が大きい、1から50以外の数字が入った時の条件式 if(answer === number) { message = 'あたり!'; window.prompt(count + "回目で正解しました。おめでとうございます!"); document.querySelector("h1").innerHTML = "<h1>数当てゲーム、正解です!</h1>"; document.querySelector("h2").innerHTML = "<h2>おめでとうございます!</h2>"; flag = 1; break; } else if((answer < number) && (answer >= 1)) { message = '残念でした!もっと大きい'; } else if((answer > number) && (answer <=50)) { message = '残念でした!もっと小さい'; } else { message = '1〜50の数字を入力してね。'; } //入力した数字に応じてメッセージを出力する。 window.alert(message); // count = count + 1; } //5回挑戦して不正解だったら正解をアラートで表示する if (flag === 0){ window.alert("正解は" + number + "です"); } </script> </body> </html> 本来ならCSSとスクリプトファイルは別のほうが良いのですが、今回はまとめて1ファイルで作成いたしました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Javscript】async/awaitを試してみたーawaitについて(No.2)

初めに awaitはasyncで宣言した関数の中でのみ使うことができるということは分かりましたが、そもそも何故asyncで関数を宣言し、awaitを使うのかイマイチ理解できませんでしたのでいろいろ実験してみました。今回は気になったこと、疑問に思ったことについて解説していきます。 ※内容に間違いなどがある場合はご指摘をよろしくお願いします。 前編: https://qiita.com/redrabbit1104/items/1ce9f665a0fcd1d99bb2 https://qiita.com/redrabbit1104/items/b8b61a72f849fa3e8881 https://qiita.com/redrabbit1104/items/02bc16cf5abd4ed10ec1 asyncで宣言した関数はpromiseチェーンが起こるのか? asyncはpromiseを返すのでthenで繋げるのか試してみました。中身がpromiseの関数にasyncを付けて実験してみました。 async function testAsync(speed) { return new Promise(resolve => { let power = speed * 300; resolve(power); }) } testAsync(10).then(result => { console.log(result); return testAsync(result); }).then(result2 => { console.log(result2); return testAsync(result2); }) 3000, 90000と予想通りに結果が出力されています。 promiseの挙動と同じであればasyncを付ける必要があるのかという疑問が浮かんできました。awaitはasyncでのみ使うことができると聞いたので、asyncの存在理由はawaitを使うためではないかなと思いました。 awaitをいろいろ試してみた awaitは宣言した関数の前に付ければセット完了。また、awaitを使うとpromiseの結果が出るまで処理を行わないことを思い出しました。awaitはasync関数の中でのみ使うことができるので、早速使ってみることに。 function insideFunction(speed) { return new Promise(resolve => { let power = speed * 300; console.log(power); resolve(power); }) } async function aTest() { await insideFunction(10); insideFunction(0); await insideFunction(20); insideFunction(0); await insideFunction(30); insideFunction(0); } aTest(); asyncで宣言したaTest関数でinsideFunctionをawaitを付けて呼び出したり付けなかったりを交互にやってみました。 普通に書いた順番通りに実行されています。これではわざわざawaitを使う意味がないんじゃないかと思いました。時間が掛かるような処理だと違いが出てくるのではないかと思い、他の記事にもsetTimeoutを使っていたのでそれを試してみました。awaitを使わない場合と使う場合に分けて実験。 ①awaitを使わない場合 function waiting(seconds) { return new Promise(ok => { setTimeout(() => { console.log('本当に遅い'); ok() }, 1000 * seconds) }) }; async function aTest2() { console.log(1); waiting(2); console.log(3); waiting(3); console.log(5); } aTest2(); 1,3,5が表示され、その後'本当に遅い'が表示されました。setTimeoutで3秒後、5秒後表示されるようにしたので当たり前の結果です。 ②awaitを使った場合 今回はwaiting関数をawaitを付けて実行してみました。 function waiting(seconds) { return new Promise(ok => { setTimeout(() => { console.log('本当に遅い'); ok() }, 1000 * seconds) }) }; async function aTest2() { console.log(1); await waiting(2); console.log(3); await waiting(3); console.log(5); } aTest2(); あれ、先ほどとは結果が違います。書いた順番通りに実行されました。まず'1'が表示され、waiting(2)の実行が完了するまでは'3'が表示されません。その後はwaiting(3)が実行されますが、処理が終わるまでは'5'が表示されません。複数の処理があって、特に時間が掛かる処理があった場合でも書いた順番通りに実行させたい場合に使えると思いました。時間が掛かる処理であればsetTimeoutでなくても、同じような結果が得られるでしょう。 結論 asyncを使う理由はpromiseを簡潔に書けるなどの理由のほか、awaitという便利な構文を使うためにあることが理解できました。また、awaitを付けると指定した関数の処理が終わるまで次の関数の処理が行われないということが分かりました。 参考サイト https://developer.mozilla.org/ja/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout https://qiita.com/soarflat/items/1a9613e023200bbebcb3
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Javscript】async/awaitを試してみたーawaitについて、なぜ使うのか理由も考えてみた(No.2)

初めに awaitはasyncで宣言した関数の中でのみ使うことができるということは分かりましたが、そもそも何故asyncで関数を宣言し、awaitを使うのかイマイチ理解できませんでしたのでいろいろ実験してみました。今回は気になったこと、疑問に思ったことについて解説していきます。 ※内容に間違いなどがある場合はご指摘をよろしくお願いします。 前編: https://qiita.com/redrabbit1104/items/1ce9f665a0fcd1d99bb2 https://qiita.com/redrabbit1104/items/b8b61a72f849fa3e8881 https://qiita.com/redrabbit1104/items/02bc16cf5abd4ed10ec1 asyncで宣言した関数はpromiseチェーンが起こるのか? asyncはpromiseを返すのでthenで繋げるのか試してみました。中身がpromiseの関数にasyncを付けて実験してみました。 async function testAsync(speed) { return new Promise(resolve => { let power = speed * 300; resolve(power); }) } testAsync(10).then(result => { console.log(result); return testAsync(result); }).then(result2 => { console.log(result2); return testAsync(result2); }) 3000, 90000と予想通りに結果が出力されています。 promiseの挙動と同じであればasyncを付ける必要があるのかという疑問が浮かんできました。awaitはasyncでのみ使うことができると聞いたので、asyncの存在理由はawaitを使うためではないかなと思いました。 awaitをいろいろ試してみた awaitは宣言した関数の前に付ければセット完了。また、awaitを使うとpromiseの結果が出るまで処理を行わないことを思い出しました。awaitはasync関数の中でのみ使うことができるので、早速使ってみることに。 function insideFunction(speed) { return new Promise(resolve => { let power = speed * 300; console.log(power); resolve(power); }) } async function aTest() { await insideFunction(10); insideFunction(0); await insideFunction(20); insideFunction(0); await insideFunction(30); insideFunction(0); } aTest(); asyncで宣言したaTest関数でinsideFunctionをawaitを付けて呼び出したり付けなかったりを交互にやってみました。 普通に書いた順番通りに実行されています。これではわざわざawaitを使う意味がないんじゃないかと思いました。時間が掛かるような処理だと違いが出てくるのではないかと思い、他の記事にもsetTimeoutを使っていたのでそれを試してみました。awaitを使わない場合と使う場合に分けて実験。 ①awaitを使わない場合 function waiting(seconds) { return new Promise(ok => { setTimeout(() => { console.log('本当に遅い'); ok() }, 1000 * seconds) }) }; async function aTest2() { console.log(1); waiting(2); console.log(3); waiting(3); console.log(5); } aTest2(); 1,3,5が表示され、その後'本当に遅い'が表示されました。setTimeoutで3秒後、5秒後表示されるようにしたので当たり前の結果です。 ②awaitを使った場合 今回はwaiting関数をawaitを付けて実行してみました。 function waiting(seconds) { return new Promise(ok => { setTimeout(() => { console.log('本当に遅い'); ok() }, 1000 * seconds) }) }; async function aTest2() { console.log(1); await waiting(2); console.log(3); await waiting(3); console.log(5); } aTest2(); あれ、先ほどとは結果が違います。書いた順番通りに実行されました。まず'1'が表示され、waiting(2)の実行が完了するまでは'3'が表示されません。その後はwaiting(3)が実行されますが、処理が終わるまでは'5'が表示されません。複数の処理があって、特に時間が掛かる処理があった場合でも書いた順番通りに実行させたい場合に使えると思いました。時間が掛かる処理であればsetTimeoutでなくても、同じような結果が得られるでしょう。 結論 asyncを使う理由はpromiseを簡潔に書けるなどの理由のほか、awaitという便利な構文を使うためにあることが理解できました。また、awaitを付けると指定した関数の処理が終わるまで次の関数の処理が行われないということが分かりました。 参考サイト https://developer.mozilla.org/ja/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout https://qiita.com/soarflat/items/1a9613e023200bbebcb3
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Javascript] 配列の平均を求める

毎回忘れるので、、、 const array = [1,2,3,4]; const average = array.reduce((a,b)=>{return a+b}) / array.length;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む