20210914のJavaScriptに関する記事は21件です。

【個人開発】タイピングゲーム「じゅげ〜む」を作ってみた

はじめに Vue.jsの勉強をかねて、タイピングゲームを作ってみました。 制作期間は5日。 意外と時間をかけてしまいました。 https://flamboyant-beaver-eb2250.netlify.app 使用技術 フレームワーク:Vue.js UIフレームワーク:Vuetify ライブラリ:vue-confetti デプロイ netlify 作ってみての感想 コンポーネントについての理解の甘さを実感し、次はコンポーネントをしっかりと活用したアプリケーションを作成してみたいと思います。 デザインがちょっと物足りなさがあるので、今後バージョンアップするかもしれません。 おわりに 暇な時間のお供に是非ご活用ください。 作ってみてなんですが、私はまだ一度もクリアしてません。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

タイピングゲーム「じゅげ〜む」を作ってみた

はじめに Vue.jsの勉強をかねて、タイピングゲームを作ってみました。 制作期間は5日。 意外と時間をかけてしまいました。 https://flamboyant-beaver-eb2250.netlify.app 使用技術 フレームワーク:Vue.js UIフレームワーク:Vuetify ライブラリ:vue-confetti デプロイ netlify 作ってみての感想 コンポーネントについての理解の甘さを実感し、次はコンポーネントをしっかりと活用したアプリケーションを作成してみたいと思います。 デザインがちょっと物足りなさがあるので、今後バージョンアップするかもしれません。 おわりに 暇な時間のお供に是非ご活用ください。 作ってみてなんですが、私はまだ一度もクリアしてません。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Javascript】 (小技) CSS Selectorで最初に見つけた1件以下を処理するワンライナー。

「先っぽだけだから!」 一番信用のないセリフNo.1 思い立ち あるサイト用に、最初に見つけた画像を開く bookmerklet を作成しようと考えていた。 基本 ワンライナー は嫌いだが、bookmerklet となると少々事情が異なる。 今回は 新しく開く 動作を行うため、何度も実行されてしまうと、よもやセルフな ブラクラ になるのでなんとか避けたい。 そんな訳で butter better の な 犬 解 を模索してみた。 思案 ① querySelectorAll + forEach document.querySelectorAll('img[src]').forEach((img) => window.open(img.src)); ('A`).。oO (1回ででええねんで...) ② Optional Chaining window.open(document.querySelector('img[src]')?.src); ('A`).。oO (必ずopnenが処理されちゃって、無い時にエラーになっちゃって意味ないなぁ...) ③ if文 let img = document.querySelector('img[src]'); if(img) { window.open(img.src); } ('A`).。oO (if文書きたくないなぁ...変数もわざわざ作りたくないでござる...) ④ querySelector + スプレッド構文 [...document.querySelector('img[src]')].forEach((img) => window.open(img.src)); ('A`).。oO (ぬるぅ....) ⑤ スプレッド構文 + Optional Chaining [...document.querySelector('img[src]')?].(略); ('A`).。oO (そんな構文はねぇ...) ⑥ 関数 let f = function(val, callback) { if(val) { callback.call(val); } }; f(document.querySelector('img[src]'), (img) => window.open(img.src)); ('A`).。oO (これならif文のほうがマシ...) 解 querySelectorAll + スプレッド構文 + slice + forEach [...document.querySelectorAll('img[src]')].slice(0, 1).forEach((img) => window.open(img.src)); ('A`).。oO (要素数が 0 or 1 で querySelectorAll の forEach がイキるぜ!) おまけ( bookmarklet) javascript:(function() {[...document.querySelectorAll('img[src]')].slice(0, 1).forEach((img) => window.open(img.src));})(); ('A`).。oO (やっぱ bookmarklet には ワンライーナー の方が良い...) 余談 Selector とか Spread とか、なんか WIXOSS みたいだなと思った。 (iTMSでサントラ売ってるからみんな買うべし。井内舞子さん最高。)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

jQueryを用いたフォーム送信時に空欄がある場合にアラートを出す処理

概要 この記事は、以下のUdmeyの講座のセクション6の52,53の内容をまとめたものです フォーム送信時に指定した項目に、空欄もしくは未選択がある場合にイベントが発火し、フォームの送信を中断し、アラートを出す実装内容です。 コード概要 2つのメソッドを用いて実装します checkBlankメソッド:空欄があるかどうかのチェック。ある場合には、アラートを表示&falseを返す $("#f").submit:フォームにイベントを登録。送信されると発火 実装内容 想定されるフォーム <form action="/" name="f" id="f"> <label>名前</label> <input type="text" id="name" name="name" value="Mike" class="form-control" /> <label> <input type="checkbox" id="chck" name="ok" value="ok" checked /> ok? </label> <p>趣味</p> <label> <input type="checkbox" id="hbyMovie" name="hbyMovie" checked /> 映画 </label> <label> <input type="checkbox" id="hbyComic" name="hbyComic" checked /> マンガ </label> <p>性別</p> <label> <input type="radio" id="sexMale" name="sex" value="male" /> 男性 </label> <label> <input type="radio" id="sexFemale" name="sex" value="female" /> 女性 </label> <p>リスト</p> <select class="form-control" id="lst" name="lst" multiple size=4> <option value="lst1" selected>リスト1</option> <option value="lst2">リスト2</option> <option value="lst3" selected>リスト3</option> <option value="lst4">リスト4</option> <option value="lst5">リスト5</option> </select> <p>自由記入</p> <textarea class="form-control" rows="3" id="free" name="free">aaaaa</textarea> <button type="submit" class="btn btn-default" id="btnSubmit">Submit</button> </form> フォームを中断・アラートを出すメソッド // 入力内容が空かどうか判断する var checkBlank = function() { if (("#name").val() == "") { alert("名前が空です") return true } if (("#free").val() == "") { alert("自由記入欄が空です") return true } if (("input[name=sex]:checked").val() === undefined) { alert("[性別]が選択されていません") return true } if (("#lsts").val() === null) { alert("[リスト]が選択されていません") return true } return false } // [Submit]ボタンによりイベントが発火 $("#f").submit(function() { var isBlank = checkBlank(); if (isBlank) { console.log("stop") return false; } }); おわりに 実際に開発現場で開発を行うにあたり、自身のフロントエンドの技術力の乏しさを実感したので、今後JSを中心に学習を進め、その内容を発信していこうと思います! 最後までお読みいただきありがとうございました!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WindowsなどからLinuxサーバのログを気軽に参照するサーバ

UbuntuなどのLinuxサーバにあるログを参照するためにわざわざSSHでログインするのが面倒なので、ブラウザから参照できるようにした。 適当につくったものなので、セキュリティも何もない。自己責任でお願いします。 画面はこんな感じ。一応行番号も付くようにしています。 ログの先頭や末尾をサクッと見れたり、前ページ・後ページに移動できたりします。 なんならファイルダウンロードもできます。 一応、APIKeyで守ってますが、適当実装なので、ローカルネットワークでのみ使てください。 ソースコードもろもろはGitHubに上げておきました。 poruruba/SimpleLogViewer ソースコード(サーバ) そのままソースコード載せておきます。 api/controllers/tail-file/index.js 'use strict'; const HELPER_BASE = process.env.HELPER_BASE || '../../helpers/'; const Response = require(HELPER_BASE + 'response'); const BinResponse = require(HELPER_BASE + 'binresponse'); const TextResponse = require(HELPER_BASE + 'textresponse'); const APIKEY = "【お好きなAPIKey】"; const logfile_list = [ // 参照したいログファイル名の配列 ]; const { exec } = require('child_process'); const streamBuffers = require('stream-buffers'); const archiver = require('archiver'); const path = require('path'); exports.handler = async (event, context, callback) => { if( event.path == '/tail-view-file'){ if (!event.requestContext.apikeyAuth || event.requestContext.apikeyAuth.apikey != APIKEY ) throw "wrong apikey"; var body = JSON.parse(event.body); console.log(body); if( logfile_list.indexOf(body.fname) < 0 ) throw 'not allowed'; var num = Number(body.num); var start = Number(body.start); return new Promise((resolve, reject) =>{ var exec_batch; if (body.order == 'head'){ exec_batch = `cat -n ${body.fname} | head -n ${start - 1 + num} | tail -n ${num} | sed -r "s/\\x1B\\[([0-9]{1,2}(;[0-9]{1,2})*)?m//g" | col -bx`; } else if (body.order == 'tail'){ exec_batch = `cat -n ${body.fname} | tail -n ${start - 1 + num} | head -n ${num} | sed -r "s/\\x1B\\[([0-9]{1,2}(;[0-9]{1,2})*)?m//g" | col -bx`; }else{ reject('unknown order'); } exec(exec_batch, (err, stdout, stderr) => { if (err) { reject(err); return; } resolve(new TextResponse("text/plain", stdout)); }); }); }else if( event.path == '/tail-get-file'){ if (!event.requestContext.apikeyAuth || event.requestContext.apikeyAuth.apikey != APIKEY ) throw "wrong apikey"; var body = JSON.parse(event.body); console.log(body); if( logfile_list.indexOf(body.fname) < 0 ) throw 'not allowed'; return new Promise((resolve, reject) =>{ var dest_stream = new streamBuffers.WritableStreamBuffer(); const archive = archiver('zip', { zlib: { level: 9 } }); dest_stream.on('finish', () => { console.log('stream finish'); var response = new BinResponse('application/zip', dest_stream.getContents()); response.set_filename(path.basename(body.fname) + '.zip'); resolve(response); }); archive.pipe(dest_stream); archive.on('error', (err) => { reject(err); }); archive.file(body.fname, { name: path.basename(body.fname) }); archive.finalize(); }); }else if( event.path == '/tail-list' ){ return new Response({ list: logfile_list }); } }; 要は、以下のようなコマンドを実行しているだけです。 行番号付けたり、画面制御コードを省いたりしています。 cat -n ${body.fname} | tail -n ${start - 1 + num} | head -n ${num} | sed -r "s/\\x1B\\[([0-9]{1,2}(;[0-9]{1,2})*)?m//g" | col -bx ソースコード(クライアント) クライアント側ソースコード。 特に難しいことはしていません。Vueのおかげで。 public/log_viwer/js/start.js 'use strict'; //const vConsole = new VConsole(); //window.datgui = new dat.GUI(); const base_url = "http://【本サーバのURL】"; var vue_options = { el: "#top", mixins: [mixins_bootstrap], data: { apikey: '', file_list: [], num_of_col: 1, select_file: ["", ""], log_data: ['', ''], select_order: ["tail", "tail"], start_line: [1, 1], get_line: [30, 30], top_line: [-1, -1] }, computed: { class_row: function () { return "col-sm-" + Math.floor(12 / this.num_of_col); } }, methods: { check_top_line: function (index, log_data) { try { var i = 0; for (; ; i++) if (log_data.charAt(i) != ' ' || !log_data.charAt(i)) break; var j = i; for (; ; j++) if (log_data.charAt(j) == ' ' || !log_data.charAt(j)) break; if (i < j) this.top_line[index] = Number(log_data.substring(i, j)); else this.top_line[index] = -1; } catch (error) { console.log(error); } }, add_num: function (index, target, num) { if (target == 'start_line') { var line = this.start_line[index] + num; if (line < 1) line = 1; this.$set(this.start_line, index, line); } else if (target == 'get_line') { var line = this.get_line[index] + num; if (line < 1) line = 1; this.$set(this.get_line, index, line); } }, log_get_file: async function (index) { try { this.progress_open(); var param = { fname: this.select_file[index], }; var blob = await do_post_blob_with_apikey(base_url + "/tail-get-file", param, this.apikey); Cookies.set('tail-apikey', this.apikey, { expires: 3650 }); var url = window.URL.createObjectURL(blob); var a = document.createElement('a'); a.href = url; a.target = '_blank'; a.download = "download.zip"; a.click(); window.URL.revokeObjectURL(url); } catch (error) { console.error(error); alert(error); } finally { this.progress_close(); } }, log_view_file: async function (index) { var param = { fname: this.select_file[index], order: this.select_order[index], start: this.start_line[index], num: this.get_line[index], }; try { var log_data = await do_post_text_with_apikey(base_url + "/tail-view-file", param, this.apikey); if (!log_data) return; this.check_top_line(index, log_data); this.$set(this.log_data, index, log_data); Cookies.set('tail-apikey', this.apikey, { expires: 3650 }); } catch (error) { console.error(error); alert(error); } }, log_next: async function (index) { if (this.top_line[index] < 0) return; var start; if (this.select_order[index] == 'tail') { start = this.top_line[index] - this.get_line[index]; if (start < 1) start = 1; } else if (this.select_order[index] == 'head') { start = this.top_line[index] + this.get_line[index]; } var param = { fname: this.select_file[index], order: 'head', start: start, num: this.get_line[index], }; try { var log_data = await do_post_text_with_apikey(base_url + "/tail-view-file", param, this.apikey); if (!log_data) return; this.check_top_line(index, log_data); this.$set(this.log_data, index, log_data); } catch (error) { console.error(error); alert(error); } }, log_prev: async function (index) { if (this.top_line[index] < 0) return; var start; if (this.select_order[index] == 'tail') { start = this.top_line[index] + this.get_line[index]; } else if (this.select_order[index] == 'head') { start = this.top_line[index] - this.get_line[index]; if (start < 1) start = 1; } var param = { fname: this.select_file[index], order: 'head', start: start, num: this.get_line[index], }; try { var log_data = await do_post_text_with_apikey(base_url + "/tail-view-file", param, this.apikey); if (!log_data) return; this.check_top_line(index, log_data); this.$set(this.log_data, index, log_data); } catch (error) { console.error(error); alert(error); } }, }, created: function () { }, mounted: async function () { proc_load(); this.apikey = Cookies.get('tail-apikey'); try { var result = await do_post(base_url + "/tail-list", {}); this.file_list = result.list; this.select_file[0] = this.file_list[0]; this.select_file[1] = this.file_list[0]; } catch (error) { console.error(error); alert(error); } } }; vue_add_data(vue_options, { progress_title: '' }); // for progress-dialog vue_add_global_components(components_bootstrap); vue_add_global_components(components_utils); /* add additional components */ window.vue = new Vue(vue_options); function do_post_text_with_apikey(url, body, apikey) { const headers = new Headers({ "Content-Type": "application/json; charset=utf-8", "X-API-KEY": apikey }); return fetch(url, { method: 'POST', body: JSON.stringify(body), headers: headers }) .then((response) => { if (!response.ok) throw 'status is not 200'; // return response.json(); return response.text(); // return response.blob(); // return response.arrayBuffer(); }); } function do_post_blob_with_apikey(url, body, apikey) { const headers = new Headers({ "Content-Type": "application/json; charset=utf-8", "X-API-KEY": apikey }); return fetch(url, { method: 'POST', body: JSON.stringify(body), headers: headers }) .then((response) => { if (!response.ok) throw 'status is not 200'; // return response.json(); // return response.text(); return response.blob(); // return response.arrayBuffer(); }); } 以上
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

props.childrenにpropsを渡す。【React/cloneElement】

はじめに props.childrenに対してpropsを持たせたい!となったので実装方法を探してみました。 *こちらは学習用メモです *ドキュメントにあります。 cloneElement() ReactにはcloneElementというAPIがあります。 今回はこちらを使用し、propsを渡していきました。 cloneElementの役割 element から新しい React 要素を複製して返します。結果の要素は元の要素の props と新しい props が浅くマージされたものを持ちます。新しい子要素は既存の子要素を置き換えます。key と ref は元の要素から保持されます。(引用: https://ja.reactjs.org/docs/react-api.html) 上記の通りにelementから新しいReact要素を複製という部分のelementがprops.childrenに値するという考えです。 使用する際はkey と ref は元の要素から保持されます。こちらに注意しながら実装を行うといいかもしれません。 cloneElement/引数 React.cloneElement( element, // <= 複製するelement [props], // <= 複製するelementに適応させるprops [...children] // <=複製するelementのchildrenとなる要素を指定 ) 使用例 今回紹介する例はあくまで例なので、都合上props.childrenに渡したいという時に使用することと 本当にコンポーネント設計は正しいか確認の必要があると思います。 親コンポーネントの作成 ItemList.tsx import { Item } from './Item' import { ItemChild } from './ItemChild' export const ItemList = () => { return ( <div> <Item> <ItemChild /> </Item> </div> ) } props.childrenにpropsを渡す設定 Item.tsx export const Item = (props) => { const items = [ {id: 1, name: 'hoge1'}, {id: 2, name: 'hoge2'} ] // ここで渡す設定を行うコンポーネントを作成。 const childrenItem = (props) => { // children === props.children, 渡したいprops === ...newProps const { children, ...newProps } = props; const childrenWithProps = React.Children.map(children, (child: React.ReactElement) => React.cloneElement(child, { ...newProps })); } return ( { items.map((item, index) => { // item、 indexがprops.childrenに渡したいprops <childrenItem item={item} index={index}> {props.children} </childrenItem> } } ) } MyItemコンポーネント (propsを渡される props.children) MyItem.tsx export const MyItem = (props) => {  // ItemListコンポーネントではpropsを設定していないがcloneElementの設定により受け取ることができる。 const { item, index } = props return ( <div> <div>index: { index }</div> <div>itemName: { item.name }</div> </div> ) } 終わりに 実際に開発で使用した実装内容だと必要になったのですが、propsの順を追う時に若干厄介になる可能性があるので使用の際は本当に使う必要があるのかなど考える必要があることがわかりました。 参考 【React の最上位 API】 cloneElement() https://qiita.com/NeGI1009/items/e6ad87320391c836bcf9 React.Chilrenについてあまり説明をしていなかったので知りたい方は下記 Reactのchildren探訪
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Phaser3で複数のシーンを扱うときの物理(arcade)の追加方法!

Phaser3の公式?のコード例的な場所には複数のシーンの切り替え+物理演算?(arcade)の追加の欲しかったドンピシャの情報が無く、this.うんちゃら....と書かれていて脳筋の数打ちゃで時間を浪費したのでもしよければ用です。 ※出来てから気が付いたのですが、コード例の中にもドンピシャの情報はありました。 game = new Phaser.Game(config);で //configの今回関わりそうな部分のみ抜粋 let config = { type: Phaser.AUTO, parent: 'game', physics:     {   default: 'arcade',   arcade:       {     gravity:         {  debug: false,    y: 300         }       } }, scene: [ Title_Scene, About_Scene, Game_Scene, ] }; class Game_Scene extends Phaser.Scene { constructor() { super({ key: 'Game_Scene' }); } preload() { this.load.image('ball', 'img/ball.png'); } create() { ball = this.physics.add.sprite(50, 50, 'ball'); ball.body.velocity.set(150, 150); ball.body.collideWorldBounds = true; ball.body.bounce.set(1); } update() { } } で、画面の設定にもよると思いますが画面端で跳ね返るボールが出来ました!!! ポイントは、 game.physics.startSystem(Phaser.Physics.ARCADE); ball = game.add.sprite(50, 50, 'ball'); game.physics.enable(ball, Phaser.Physics.ARCADE); のあたりをまとめて、 ball = this.physics.add.sprite(50, 50, 'ball'); の一行でどうにかしていることにあるんですかね...? web上でスマホアプリみたいなゲームを作ろうとして、phaserの勉強がてらMNDのブロック崩しゲームを組み込んでみようとして陥った感じです。 広告で流れてくる鍵を引っこ抜いて溶岩でダバーするゲーム作りたいなと思って始めたphaserでしたがまだまだ道のりは長そうです...
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

parameter 1 is not of type 'Node'

実務でquerySelectorで最初のクラス要素を取得しようとしたらコンソールで以下のエラーが出た。 Uncaught TypeError: Failed to execute 'querySelector' on 'Node': parameter 1 is not of type 'Node' 例によってエラー文言で検索すると英語の記事が出てきた。 https://stackoverflow.com/questions/27079598/error-failed-to-execute-appendchild-on-node-parameter-1-is-not-of-type-no 存在しない要素を呼んでてnullが返ってるのだという。 確認すると後から追加された要素なのでDOMで取得できないのは当たり前だった。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ユーザビリティをあげるボタンの工夫(フロント)

はじめに 本記事では、ボタンでユーザービリティをあげるワザをちょっとご紹介します。 すぐ実践できるものばかりなので、是非参考にしてみてください。 ボタンの活性化・非活性化 例えば、フォームを送信するのに2つの質問に答えないといけないものがあるとします。 条件に当てはまっていないとき(質問に1つしか答えていないときなど)は、ボタンを非活性化 条件クリアしたときに、ボタンを活性化しましょう。 こすうることでユーザーが「直感的」に「ボタンが押せないこと」、「何かが足りていないこと」に気づけます。 むやみにボタンを押して「あれ、、、なんで何も反応しないの、、、押してるよね???」なんて混乱を防げます。 ※↓いったい何のアプリだよっていうツッコミは一旦置いといてください。 ?ワンポイントアドバイス グレーアウトではなく、透明化をしよう! 非活性化時に、ボタンをグレーにする場合があります。 しかし、これは ・ボタンの有効時のフルカラーのデザインとかけ離れているためユーザーを驚かせる ・グレーは背景色に馴染まず浮いてします。目立つことになるため、ユーザーは「非活性」であることに気づかずクリックしてしまう を引き起こす可能性があるためお勧めしません。 代わりに透過させることで、ユーザーを驚かせることなくボタンの無効状態を伝えられます。 参考記事 https://coliss.com/articles/build-websites/operation/work/why-you-shouldnt-gray-out-disabled-buttons.html hover, tap時に変化をつける ユーザーがボタンをhoverしたときや、クリックしたとき ボタンの色を変えたり、サイズを変えたり、変化を起こしましょう。 ユーザーが「直感的」に「押せるもの」であること、「正しくクリックできている」ことを認識できます。 アニメーションを付ける 促したい動作にはアニメーションを付けて見ましょう。 ユーザーに気づいてもらうこと、正しくネクストアクションを提示させることができます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

度分秒(DMS)表記を十進数(Decimal)に変換する雑なやり方

"北緯35度39分31秒 東経139度44分44秒"や"35°39′31″N 139°44′44″E"などの表記を十進数表記に雑に変換する。 function dms2decimal(dms) { return dms.split(/\D/).filter(d => d != "").reduce((a, c, i) => { const opt = { 1: 60, 2: 3600 } const na = +a; const nc = +c; return na + (nc / opt[i]) }) } > dms2decimal("北緯35度39分31秒"); <- 35.65861111111111 > dms2decimal("東経139度44分44"); <- 139.74555555555554 > dms2decimal("35°39′31″N"); <- 35.65861111111111 > dms2decimal("139°44′44″E"); <- 139.74555555555554 実に雑なので、誤作動するときがあるかも。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Firebaseの開発環境(local)を作ってみるよ

経緯 ・firebase-toolsのバージョンが上がってCloud StorageのEmulatorが実装されたみたい ・Node.jsバージョン16がCloudFunctionsでサポートされたみたい 必要なもの、必要な知識 ・Firebaseとは何か勉強しておいて ・Terminal操作ができるようにしておいて(コマンドコピペできる程度で) ・Homebrew インストールしておいて ・Googleアカウントは作っておいて ・GitHubのアカウントは作っておいて ・Node.jsはインストールしておいて(記事内ではVer.16+でやるよ) ・エディタはVisual Studio Code使うよ ・Gitは使うからインストールしといて 環境とか ・MacBookPro '13 (2017) BigSur ・Homebrew 3.2.11 ・Visual Studio Code 1.16.0 ・Node.js 16.9.1 事前に作成しておくと良いこと ・Googleのアカウント作成とfirebaseのプロジェクト作成、firestoreの作成 ・GitHubのアカウントと作ったプロジェクトをcommitするようのリポジトリ  ※privateリポジトリにしておいたほうが良いかも。 Let's Try!!! 作業用ディレクトリの作成・移動 $ mkdir ~/Documents/Sample/firebase-local $ cd ~/Documents/Sample/firebase-local git初期化(いつものやつね) $ git init .gitignore作成(自動生成させちゃうよ) giboがインストールされてない場合は、brew install gibo オプションはmacosだけでよさそうだけど、まとめて全部ガツンと行っちゃえ! $ gibo dump macos linux windows node > .gitignore (念の為)ちゃんと出来てるか確認しておこう $ ls -a . .. .git .gitignor よさげね。 (ここで?)Node.jsとnpmのバージョン確認しておこうか( ・`ω・´) $ node -v v16.9.1 $ npm -v 7.21.1 FirebaseCLIが必要だからインストールするんだぜ $ npm install -g firebase-tools firebaseのバージョンを確認しておこう! $ firebase --version 9.18.0 firebaseにログインする $ firebase login ブラウザが自動的に開いてログイン。 流れに沿ってfirebaseCLIへのアクセス許可をする。 Firebase CLI Login Successful が表示されるとOK!(画面は閉じてOK) Firebase Consoleにログインしてプロジェクトを作成! アナリティクスはOFFに 完成! firebaseのプロジェクトディレクトリとして初期化するよ ココからはTerminalに戻って作業続行するよ $ firebase init ######## #### ######## ######## ######## ### ###### ######## ## ## ## ## ## ## ## ## ## ## ## ###### ## ######## ###### ######## ######### ###### ###### ## ## ## ## ## ## ## ## ## ## ## ## #### ## ## ######## ######## ## ## ###### ######## You're about to initialize a Firebase project in this directory: /Users/UserName/Documents/Sample/firebase-local ? Which Firebase features do you want to set up for this directory? Press Space to select features, then Enter to confirm your choices. (Press <space> to select, <a> to toggle all, <i> to invert selection) ❯◯ Realtime Database: Configure a security rules file for Realtime Database and (optionally) provision default instance ◯ Firestore: Configure security rules and indexes files for Firestore ◯ Functions: Configure a Cloud Functions directory and its files ◯ Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys ◯ Hosting: Set up GitHub Action deploys ◯ Storage: Configure a security rules file for Cloud Storage ◯ Emulators: Set up local emulators for Firebase products (Move up and down to reveal more choices) せっかくなので、全部チェック入れちゃえ! ↑↓で移動して<space>キーでチェックを入れるんだ! 選択後。。。 ◉ Realtime Database: Configure a security rules file for Realtime Database and (optionally) provision default instance ◉ Firestore: Configure security rules and indexes files for Firestore ◉ Functions: Configure a Cloud Functions directory and its files ❯◉ Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys ◉ Hosting: Set up GitHub Action deploys ◉ Storage: Configure a security rules file for Cloud Storage ◉ Emulators: Set up local emulators for Firebase products 今回は新規プロジェクトとして作成Create a new projectを選択して <Enter> ? Which Firebase features do you want to set up for this directory? Press Space to select features, then Enter to confirm your choices. Realtime Database: Configure a security rules file for Realtime Database and (optionally) provision de fault instance, Firestore: Configure security rules and indexes files for Firestore, Functions: Configure a Cloud Functions directory and its files, Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deplo ys, Hosting: Set up GitHub Action deploys, Storage: Configure a security rules file for Cloud Storage, Emulators: Set up local emulators for Firebase products, Remote Config: Configure a template file for Remote Config === Project Setup First, let's associate this project directory with a Firebase project. You can create multiple project aliases by running firebase use --add, but for now we'll just set up a default project. ? Please select an option: ❯ Use an existing project   Create a new project Add Firebase to an existing Google Cloud Platform project Don't set up a default project Use an existing projectを選択して<Enter> ? Select a default Firebase project for this directory: (Use arrow keys) ❯ fb-tool-sample (fb-tool-sample) 先程画面から作成したfb-tool-sampleを選択して<Enter> === Database Setup i database: ensuring required API firebasedatabase.googleapis.com is enabled... ⚠ database: missing required API firebasedatabase.googleapis.com. Enabling now... ✔ database: required API firebasedatabase.googleapis.com is enabled ? It seems like you haven’t initialized Realtime Database in your project yet. Do you want to set it up? Yes ← "Y" で回答 ? Please choose the location for your default Realtime Database instance: asia-southeast1 ← "asia-southeast1"を選択 ✔ Creating your default Realtime Database instance: fb-tool-sample-default-rtdb Firebase Realtime Database Security Rules allow you to define how your data should be structured and when your data can be read from and written to. ? What file should be used for Realtime Database Security Rules? database.rules.json ← デフォルトのまま<Enter> ✔ Database Rules for fb-tool-sample-default-rtdb have been written to database.rules.json. Future modifications to database.rules.json will update Realtime Database Security Rules when you run firebase deploy. === Firestore Setup Error: It looks like you haven't used Cloud Firestore in this project before. Go to https://console.firebase.google.com/project/fb-tool-sample/firestore to create your Cloud Firestore database. あれ?エラーなった。。。 なに?なに? 要約するとあなたはFirestore使ったことがないみたいだから、URL踏んで作ってきて的な感じ。 言われたとおり、画面にログインしてプロジェクトを選択後、firestoreのデータベースを作成する。 画面キャプチャは省略〜テストモードでasia-noatheast3で作ってみた。 firebase init ######## #### ######## ######## ######## ### ###### ######## ## ## ## ## ## ## ## ## ## ## ## ###### ## ######## ###### ######## ######### ###### ###### ## ## ## ## ## ## ## ## ## ## ## ## #### ## ## ######## ######## ## ## ###### ######## You're about to initialize a Firebase project in this directory: /Users/UserName/Documents/Sample/firebase-local ? Which Firebase features do you want to set up for this directory? Press Space to select features, then Enter to confirm your choices. Realtime Database: Configure a security rules file for Realtime Database and (optionally) provision de fault instance, Firestore: Configure security rules and indexes files for Firestore, Functions: Configure a Cloud Functions directory and its files, Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deplo ys, Hosting: Set up GitHub Action deploys, Storage: Configure a security rules file for Cloud Storage, Emulators: Set up local emulators for Firebase products, Remote Config: Configure a template file for Remote Config === Project Setup First, let's associate this project directory with a Firebase project. You can create multiple project aliases by running firebase use --add, but for now we'll just set up a default project. ? Please select an option: Use an existing project ? Select a default Firebase project for this directory: fb-tool-sample (fb-tool-sample) i Using project fb-tool-sample (fb-tool-sample) === Database Setup i database: ensuring required API firebasedatabase.googleapis.com is enabled... ✔ database: required API firebasedatabase.googleapis.com is enabled Firebase Realtime Database Security Rules allow you to define how your data should be structured and when your data can be read from and written to. ? What file should be used for Realtime Database Security Rules? database.rules.json ? File database.rules.json already exists. Do you want to overwrite it with the Realtime Database Security Rules for fb-tool-sample-default-rtdb from the Firebase console? No Skipping overwrite of Realtime Database Security Rules. The security rules defined in database.rules.json will be published when you run firebase deploy. === Firestore Setup Firestore Security Rules allow you to define how and when to allow requests. You can keep these rules in your project directory and publish them with firebase deploy. ? What file should be used for Firestore Rules? firestore.rules ? File firestore.rules already exists. Do you want to overwrite it with the Firestore Rules from the Firebase Console? No Firestore indexes allow you to perform complex queries while maintaining performance that scales with the size of the result set. You can keep index definitions in your project directory and publish them with firebase deploy. ? What file should be used for Firestore indexes? firestore.indexes.json ? File firestore.indexes.json already exists. Do you want to overwrite it with the Firestore Indexes from the Firebase Console? No === Functions Setup A functions directory will be created in your project with sample code pre-configured. Functions can be deployed with firebase deploy. ? What language would you like to use to write Cloud Functions? JavaScript ? Do you want to use ESLint to catch probable bugs and enforce style? No ? File functions/package.json already exists. Overwrite? No i Skipping write of functions/package.json ? File functions/index.js already exists. Overwrite? No i Skipping write of functions/index.js ? File functions/.gitignore already exists. Overwrite? No i Skipping write of functions/.gitignore ? Do you want to install dependencies with npm now? Yes npm WARN EBADENGINE Unsupported engine { npm WARN EBADENGINE package: undefined, npm WARN EBADENGINE required: { node: '14' }, npm WARN EBADENGINE current: { node: 'v16.9.1', npm: '7.21.1' } npm WARN EBADENGINE } up to date, audited 323 packages in 3s 24 packages are looking for funding run `npm fund` for details found 0 vulnerabilities === Hosting Setup Your public directory is the folder (relative to your project directory) that will contain Hosting assets to be uploaded with firebase deploy. If you have a build process for your assets, use your build's output directory. ? What do you want to use as your public directory? public ? Configure as a single-page app (rewrite all urls to /index.html)? No ? Set up automatic builds and deploys with GitHub? No ✔ Wrote public/404.html ? File public/index.html already exists. Overwrite? No i Skipping write of public/index.html === Hosting:github Setup i Detected a .git folder at /Users/UserName/Documents/Sample/firebase-local i Authorizing with GitHub to upload your service account to a GitHub repository's secrets store. Visit this URL on this device to log in: https://github.com/login/oauth/authorize?client_id=xxxxxxxxxxxxxxxxxxxxxxxxxxxx&state=xxxxxxxxxxxxxxx&redirect_uri=http%3A%2F%2Flocalhost%3A9005&scope=read%3Auser%20repo%20public_repo Waiting for authentication... ✔ Success! Logged into GitHub as UserName ? For which GitHub repository would you like to set up a GitHub workflow? (format: user/repository) UserName/fb-tool-sample ✔ Created service account github-action-406020156 with Firebase Hosting admin permissions. ✔ Uploaded service account JSON to GitHub as secret FIREBASE_SERVICE_ACCOUNT_FB_TOOL_SAMPLE. i You can manage your secrets at https://github.com/UserName/fb-tool-sample/settings/secrets. ? Set up the workflow to run a build script before every deploy? No ✔ Created workflow file /Users/UserName/Documents/Sample/firebase-local/.github/workflows/firebase-hosting-pull-request.yml ? Set up automatic deployment to your site's live channel when a PR is merged? Yes ? What is the name of the GitHub branch associated with your site's live channel? main ✔ Created workflow file /Users/UserName/Documents/Sample/firebase-local/.github/workflows/firebase-hosting-merge.yml i Action required: Visit this URL to revoke authorization for the Firebase CLI GitHub OAuth App: https://github.com/settings/connections/applications/89cf50f02ac6aaed3484 i Action required: Push any new workflow file(s) to your repo === Storage Setup Firebase Storage Security Rules allow you to define how and when to allow uploads and downloads. You can keep these rules in your project directory and publish them with firebase deploy. ? What file should be used for Storage Rules? storage.rules ✔ Wrote storage.rules === Emulators Setup ? Which Firebase emulators do you want to set up? Press Space to select emulators, then Enter to confirm your choices. Authentication Emulator, Functions Emulator, Firestore Emulator, Database Emulator, Hosting Emulator, Pub/Sub Emulator, Storage Emulator ? Which port do you want to use for the auth emulator? 9099 ? Which port do you want to use for the functions emulator? 5001 ? Which port do you want to use for the firestore emulator? 8080 ? Which port do you want to use for the database emulator? 9000 ? Which port do you want to use for the hosting emulator? 5000 ? Which port do you want to use for the pubsub emulator? 8085 ? Which port do you want to use for the storage emulator? 9199 ? Would you like to enable the Emulator UI? Yes ? Which port do you want to use for the Emulator UI (leave empty to use any available port)? ? Would you like to download the emulators now? No === Remoteconfig Setup ? What file should be used for your Remote Config template? remoteconfig.template.json i Writing configuration info to firebase.json... i Writing project information to .firebaserc... i Writing gitignore file to .gitignore... ✔ Firebase initialization complete! Emulatorの初期化(インストール) エミュレータは全部選択。 ポートは全部デフォルト。 $ firebase init emulators ######## #### ######## ######## ######## ### ###### ######## ## ## ## ## ## ## ## ## ## ## ## ###### ## ######## ###### ######## ######### ###### ###### ## ## ## ## ## ## ## ## ## ## ## ## #### ## ## ######## ######## ## ## ###### ######## You're about to initialize a Firebase project in this directory: /Users/UserName/Documents/Sample/firebase-local Before we get started, keep in mind: * You are initializing within an existing Firebase project directory === Project Setup First, let's associate this project directory with a Firebase project. You can create multiple project aliases by running firebase use --add, but for now we'll just set up a default project. i .firebaserc already has a default project, using fb-tool-sample. === Emulators Setup ? Which Firebase emulators do you want to set up? Press Space to select emulators, then Enter to confirm your choices. (Press <space> to select, <a> to toggle all, <i> to invert selection)Authentication Emulator, Functions Emulator, Firestore Emulator, Database Emulator, Hosting Emulator, Pub/Sub Emulator, Storage Emulator i Port for auth already configured: 9099 i Port for functions already configured: 5001 i Port for firestore already configured: 8080 i Port for database already configured: 9000 i Port for hosting already configured: 5000 i Port for pubsub already configured: 8085 i Port for storage already configured: 9199 i Emulator UI already enabled with port: (automatic) ? Would you like to download the emulators now? No i Writing configuration info to firebase.json... i Writing project information to .firebaserc... ✔ Firebase initialization complete! エミュレータ起動じゃぁ! $ firebase emulators:start i emulators: Starting emulators: auth, functions, firestore, database, hosting, pubsub, storage ⚠ Your requested "node" version "14" doesn't match your global version "16" i firestore: downloading cloud-firestore-emulator-v1.13.1.jar... Progress: =====================================> (100% of 61MB) i firestore: Removing outdated emulator files: cloud-firestore-emulator-v1.11.15.jar i firestore: Firestore Emulator logging to firestore-debug.log i emulators: Shutting down emulators. i functions: Stopping Functions Emulator i firestore: Stopping Firestore Emulator i hub: Stopping emulator hub ⚠ database: Port 9000 is not open on localhost, could not start Database Emulator. ⚠ database: To select a different host/port, specify that host/port in a firebase.json config file: { // ... "emulators": { "database": { "host": "HOST", "port": "PORT" } } } i emulators: Shutting down emulators. Error: Could not start Database Emulator, port taken. あ。。。エラーです。 一旦、firebase.jsonを確認する cat firebase.json { "functions": { "source": "functions" }, "database": { "rules": "database.rules.json" }, "firestore": { "rules": "firestore.rules", "indexes": "firestore.indexes.json" }, "hosting": { "public": "public", "ignore": [ "firebase.json", "**/.*", "**/node_modules/**" ] }, "storage": { "rules": "storage.rules" }, "emulators": { "auth": { "port": 9099 }, "functions": { "port": 5001 }, "firestore": { "port": 8080 }, "database": { "port": 9000 }, "hosting": { "port": 5000 }, "pubsub": { "port": 8085 }, "storage": { "port": 9199 }, "ui": { "enabled": true } }, "remoteconfig": { "template": "remoteconfig.template.json" } } ホストの指定が足りないの?ポートがBusyなの? ポートから当たってみますか。。。 $ lsof -i :9000 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME php-fpm 67668 UserName 8u IPv4 0x615724fb70f47db1 0t0 TCP localhost:cslistener (LISTEN) php-fpm 67669 UserName 9u IPv4 0x615724fb70f47db1 0t0 TCP localhost:cslistener (LISTEN) php-fpm 67670 UserName 9u IPv4 0x615724fb70f47db1 0t0 TCP localhost:cslistener (LISTEN) やっぱり。。。ごめんなさい。ごめんなさい。ごめんなさいぃぃぃぃ。 じゃあ、別ポート使えばいいじゃん! 念の為、ポートが使われてないことを確認。 $ lsof -i :9001 結果が標準出力が何もなければ使われてない。 でもって、firebase.configを修正する。 もう、抜粋でいいよね。 "database": { "port": 9001 }, これで完璧のはずだ! $ firebase emulators:start i emulators: Starting emulators: auth, functions, firestore, database, hosting, pubsub, storage ⚠ Your requested "node" version "14" doesn't match your global version "16" i firestore: Firestore Emulator logging to firestore-debug.log i database: Database Emulator logging to database-debug.log i pubsub: Pub/Sub Emulator logging to pubsub-debug.log i hosting: Serving hosting files from: public ✔ hosting: Local server: http://localhost:5000 i ui: Emulator UI logging to ui-debug.log i functions: Watching "/Users/UserName/Documents/Sample/firebase-local/functions" for Cloud Functions... ┌─────────────────────────────────────────────────────────────┐ │ ✔ All emulators ready! It is now safe to connect your app. │ │ i View Emulator UI at http://localhost:4000 │ └─────────────────────────────────────────────────────────────┘ ┌────────────────┬────────────────┬─────────────────────────────────┐ │ Emulator │ Host:Port │ View in Emulator UI │ ├────────────────┼────────────────┼─────────────────────────────────┤ │ Authentication │ localhost:9099 │ http://localhost:4000/auth │ ├────────────────┼────────────────┼─────────────────────────────────┤ │ Functions │ localhost:5001 │ http://localhost:4000/functions │ ├────────────────┼────────────────┼─────────────────────────────────┤ │ Firestore │ localhost:8080 │ http://localhost:4000/firestore │ ├────────────────┼────────────────┼─────────────────────────────────┤ │ Database │ localhost:9001 │ http://localhost:4000/database │ ├────────────────┼────────────────┼─────────────────────────────────┤ │ Hosting │ localhost:5000 │ n/a │ ├────────────────┼────────────────┼─────────────────────────────────┤ │ Pub/Sub │ localhost:8085 │ n/a │ ├────────────────┼────────────────┼─────────────────────────────────┤ │ Storage │ localhost:9199 │ http://localhost:4000/storage │ └────────────────┴────────────────┴─────────────────────────────────┘ Emulator Hub running at localhost:4400 Other reserved ports: 4500 Issues? Report them at https://github.com/firebase/firebase-tools/issues and attach the *-debug.log files. ちゃんと起動した????? エミュレータの起動確認 ブラウザから、http://localhost:4000/にアクセスする。 全部起動してる! 終了するときは、TerminalでCtrl + cで終了します。 まとめ。所感とか。 最初から環境構築をする手順をまとめとくと良かったといつも思う。 今回は、その反省も踏まえてまとめてみた。 失敗したことやエラーになったこともちゃんと残しておくのは大事だと思う。 firebase init実行時の選択オプションなんかは、詳しく書かなかった(メンドウダッタ) 使いたい環境によって読み替えて設定すればいいと思う。TypeScript/JavaScriptの選択は好みや要件が絡むところだろうから。 基本的には、デフォルトで<Enter>バンバン叩いて勧めていけば問題ない感じだった。 試行錯誤しながらハンズオンでやってみたから、手順が変なところとかあるかもしれないので、指摘があれば修正していきたいと思います。 LGTM10件来たら続編でも書こうかな。 参考 Local Emulator Suite のインストール、構成、統合 Firebase CLI リファレンス 改訂履歴 2021.09.14 初版
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

スタートアップの小規模Webサービスのリアルな技術スタック

はじめに プレースホルダというスタートアップのWebエンジニア兼マネージャーのAkahoriです。 弊社はエンジニアは10人以上いるものの、Webエンジニアは私含め3人ほどです。 3人のWebチームで、どのような理由で、どのような技術を使っているか、苦労している点などを共有します。 サービス概要 先月、リトルスパークというサービスをリリースしました。 子ども向けの、オンラインでの習い事プラットフォームで、先生と生徒をマッチングしています。 技術的にはいくつかの特徴を持ち、今回サンプルとして解説します。 授業はライブ授業のみで、お互いにZoomで行います。 ZoomのIDは弊社で管理し、先生側、生徒側、双方が参加ボタン1つで参加できるようになっています。 コース登録(審査有り)や日程登録、プロフィール更新などは全て先生が行うため、その仕組みがあります。 言語・フレームワーク・ライブラリ サーバーサイドもフロントエンドもTypeScriptで統一しています。 いくつかのメリットを感じています。 学習効率が上がる 使い回せるコードがある 型やデータのやりとりがしやすい フロントエンド Next.jsを使っています。 弊社は元々はReact(CRA)を使っていたのですが、ルーティングの煩わしさと、SSR/SSGなどの技術が必要な場面が出てきたため、1年ほど前から切り替えています。 また、以下のようなライブラリを使っています。 material-ui コンポーネントのベースに利用 dayjs 日付関連操作に利用 formik フォーム管理に利用 yup フォームのバリデーションに利用 swr データ取得・状態管理に利用 next-seo タイトルタグなどのSEO関連設定やOGPの設定に利用 fullcalendar カレンダーコンポーネントとして利用 react-player 動画プレイヤーとして利用 styled-components スタイリングに利用 storybook 個別コンポーネントのデザイン確認に利用 next/bundle-analyzer バンドル内容の確認 サーバーサイド NestJSを使っています。 基本はExpressをコアにしたフレームワークで、スケーラブルなコードが書けます。 DBはMySQLを使っており、ORMとしてTypeScriptと相性の良いTypeORMを使っています。 最初はPrisma2を使っていましたが、トランザクション周りが微妙であったために移行しています。 また、以下のようなライブラリを使っています。 moment 日付関連操作に利用 ical-generator 自動更新カレンダーの生成に利用 passport 認証に利用 slack/webhook slackへの通知に利用 google-cloud  GCPの操作に利用、@google-cloud/tasksと@google-cloud/logging-winstonを使っています class-validator リクエストデータの検証に利用 class-transformer レスポンスデータの変換等に利用 googleapis スプレでマスタを管理しているため、通信に利用 連携 REST APIで通信しています。 NestJSのソースから、Open APIのライブラリでAPIクライアントを生成し、フロントではそれを利用することで型安全に通信ができています。 利用した外部サービス Stripe 決済周りに利用しています。 APIはとても良くできているのですが、Stripe Connectという仕組みを使ったため、ここが複雑で非常に苦労しました。 Stripe Connectは、自社、消費者、以外の、サービス提供者などに送金できる仕組みです。 次のような特徴を持ちます。手数料もそれほど高くはないです。 簡易にサービス提供者の顧客確認(KYC)ができる 消費者の支払いから、サービス提供者への銀行振り込みまでAPIで完結できる Zoom サーバーからZoom APIを叩いています。 ユーザーの作成やミーティングの作成を利用しています。 そこそこ複雑です。 Coudinary 画像の保管・加工・配信に利用しています。 パブリックな画像であれば、特定のURLから自動でリソースを紐づけることもできます。 この機能を利用し、メインの保管先であるGCPのCloud Storageから自動でアップロードしています。 Sendgrid メールの送信に利用しています。便利です。 リポジトリ monorepo構成を取っています。 ユーザー向けクライアント、サーバーアプリ、弊社管理用のクライアントの3つのアプリがあるのですが、一つのリポジトリで管理しています。 管理や全体の把握が容易なところや、連携しやすいというメリットがあります。 lernaというライブラリを使っており、基本便利なのですが、ライブラリのバージョンアップをする際などに煩わしい手順が必要になったりします。 ディレクトリ クライアント クリーンアーキテクチャ+Atomic Designのような構成でやっていますが、全く厳密ではないです。 サーバーサイド クリーンアーキテクチャ+NestJSの推奨、のような構成でやっています。 アーキテクチャ 全面的にGCPを使っています。 コストパフォーマンスと、複雑でない用途であれば使い勝手が良いことから選択しています。 まだ規模が大きくないため、シンプルな構成にしています。(矢印は適当です) スタートアッププログラムも充実しています。 まだ利用されていない会社の方は是非検討いかがでしょう。 まとめ 大手以外ですと技術を公表しているプロジェクト・サービスは少ないですが、いかがでしたでしょうか。 何か一つでも、参考にしてもらえる箇所がありましたら嬉しく思います。 個別のライブラリ等についても随時見直しを行っているため、またしばらく経ちましたら更新した内容を公開したいと考えています。 サービスを一緒に成長させるメンバー募集中です! 弊社では、エンジニアが楽しく効率的に働けることが生産性に直結するという考え方から、多くの裁量を持っています。 これより、「技術好きが楽しめる」会社であるかと考えています。 是非一度カジュアルにお話ししませんか? 正社員・業務委託、共に募集しています! https://www.wantedly.com/projects/410613
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TOPニュースを見れるLINEbotを作ってみた

はじめに 社会人になり、ニュース見るようにしないといけないな、と思いつつも元々習慣づいていない私はたくさんあるニュースサイトからどれかを選んで面白そうな記事を探すということが面倒だと思っていました。 そこで、日常的に開くLINEで簡単に何個か最新のニュースを取れるようにしたいと思い、ワンタッチでニュースが取得できるLINEbotを作ってみました。 完成イメージ 取得したいニュースのカテゴリーを選ぶと、カテゴリー名が送信され、当てはまるニュースが3つ返ってきます。 キーボードから一覧にないものを入力されると、「当てはまるニュースはありません」と返します。 実装 使用したもの ・Google App Script ・LINE bot ・NEWS API(https://newsapi.org/) LINEbotの設定 ①まずはLINE developerのコンソールにログイン。 ②Providersを作成します。 ③Channelsタブで、Create a new channelでMessaging APIを選択し、画像内の必須項目を埋めます。 他の項目はデフォルト/未入力のままCreateを押して進めます。 Google App Script GAS上では、LINEで入力されたメッセージを取得し、それをもとにNEWS APIを叩きます。 NEWS APIを叩く関数news(textInput)内 国とカテゴリーから絞り込みます。 カテゴリーはbusiness,entertainment,general,science,sports,technologyの中から入力されたものを用います。 function news(textInput){ baseurl = "https://newsapi.org/v2/top-headlines"; app="?apiKey=APIキー"; country = "&country=jp" category = "&category=" + textInput; pageSize = "&pageSize=3" fullurl = baseurl + app + country+ category + pageSize ; response = UrlFetchApp.fetch(fullurl).getContentText() json_response = JSON.parse(response) return json_response } 関数doPost(e)内 ①LINE上で送られたメッセージを取得したり、返信したりするためのお作法 // 環境変数を取り出す var prop = PropertiesService.getScriptProperties().getProperties(); // WebHookで受信した応答用Token var replyToken = JSON.parse(e.postData.contents).events[0].replyToken; // ユーザーのメッセージを取得 var userMessage = JSON.parse(e.postData.contents).events[0].message.text; // 応答メッセージ用のAPI URL var url = 'https://api.line.me/v2/bot/message/reply'; ②NEWS APIからの取得結果を応答メッセージとしてLINE上で表示 LINE developerのドキュメントで、応答メッセージのお作法がわかります。 NEWS APIから、リクエストに当てはまるニュースがあれば3記事分のタイトルと概要、発行日、URLを取得して表示させます。 当てはまる記事がなかった場合は「当てはまるニュースはありません」と表示させます。 //newsAPI呼び出す関数を呼び出す var news_res = news(userMessage); var available = news_res["totalResults"]; //このURLへpostリクエスト if(available !== 0){ UrlFetchApp.fetch(url, { 'headers': { 'Content-Type': 'application/json; charset=UTF-8', 'Authorization': 'Bearer ' + prop.ACCESS_TOKEN, }, 'method': 'post', 'payload': JSON.stringify({ 'replyToken': replyToken, 'messages': [{ 'type': 'text', 'text': "【タイトル】\n" + news_res["articles"][0]["title"] + "\n\n【概要】\n" + news_res["articles"][0]["description"] + "\n\n【発行日】\n" + news_res["articles"][0]["publishedAt"] + "\n\n【URL】\n" + news_res["articles"][0]["url"] },{ 'type': 'text', 'text': "【タイトル】\n" + news_res["articles"][1]["title"] + "\n\n【概要】\n" + news_res["articles"][1]["description"] + "\n\n【発行日】\n" + news_res["articles"][1]["publishedAt"] + "\n\n【URL】\n" + news_res["articles"][1]["url"] },{ 'type': 'text', 'text': "【タイトル】\n" + news_res["articles"][2]["title"] + "\n\n【概要】\n" + news_res["articles"][2]["description"] + "\n\n【発行日】\n" + news_res["articles"][2]["publishedAt"] + "\n\n【URL】\n" + news_res["articles"][2]["url"] } ] }), }); }else if(available == 0){ UrlFetchApp.fetch(url, { 'headers': { 'Content-Type': 'application/json; charset=UTF-8', 'Authorization': 'Bearer ' + prop.ACCESS_TOKEN, }, 'method': 'post', 'payload': JSON.stringify({ 'replyToken': replyToken, 'messages': [{ 'type': 'text', 'text': "当てはまるニュースはありません$", 'emojis': [{ 'index': 15, 'productId': "5ac1bfd5040ab15980c9b435", 'emojiId': "014" }] }] }) }); } アクセストークンの設定 ①LINE developerに戻り、先程作成したTOPニュース検索の管理画面でMessaging APIタブを開きます。 この画面の一番下にあるChannnel access tokenをコピーします。 ②GAS画面上の左上の「ファイル」から「プロジェクトのプロパティー」を選択し、スクリプトのプロパティタブを開きます。 プロパティに「ACCESS_TOKEN」 値にコピーしたアクセストークンを貼り付け保存します。 保存したら、GASのファイルをアプリケーションとして公開し、URLをコピーしておきます。 LINEbotにGASを連携させる ①TOPニュース検索の管理画面でMessaging APIを開き、Webhook settingsでWebhook URLにGASファイルを公開した時のURLを貼り付け、「Verify」を押します。 「Success」が表示されればOK! Use WebhookはONにします。 ②Webhookの下にあるauto-reply messagesを編集し、詳細設定で「応答メッセージ」をオフ、 Webhookをオンにします。 (今回は自動応答メッセージを利用しないため。) リッチメニューを作る 想定外のメッセージが送られるのをなるべく防ぐために、カテゴリー別のボタンをクリックするだけで結果が取得できるようにします。 リッチメニューの設定はここから行います。 ①リッチメニューをつけたいチャンネルを選択し、左側のトークルーム管理からリッチメニューを開きます。 ②右上の「作成」を押し、表示設定でタイトルと表示期間を入力します。 ③「コンテンツ設定」でテンプレートを選択し、ボタンの数などを選びます。 ④「アクション」でボタンを押すとどんなアクションを起こすかを選びます。 今回は「テキスト」を選択し、設定したテキストが自動で送られるようにします。 ⑤「画像を作成」から、デザインを整えます。 こんな感じ 最後に 飲食店などでよく見かけるLINEアカウントがどのように作られているかがわかり、自分が欲しいと思った機能をLINEbotで作ることが出来たのが面白かったです。 LINEbotを使えばフロント側はLINEから機能がたくさん提供されているので、リッチメニューなど使いたいものを選ぶだけで実装できるため、アプリ初心者でも手を出しやすくとても便利でした。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Markdown (とHTMLとCSS少し) でスライドを作るremark

remarkとは こういうのでいいんだよ、 「HTMLとCSSを使いこなせる人をターゲットにした、シンプルなブラウザ内マークダウン駆動のスライドショーツール」 A simple, in-browser, markdown-driven slideshow tool targeted at people who know their way around HTML and CSS <!DOCTYPE html> <html> <head> <title>タイトル</title> <meta charset="utf-8"> <style> @import url('https://fonts.googleapis.com/css?family=Noto Sans JP&display=swap'); @import url(https://fonts.googleapis.com/css?family=Yanone+Kaffeesatz); @import url(https://fonts.googleapis.com/css?family=Droid+Serif:400,700,400italic); @import url(https://fonts.googleapis.com/css?family=Ubuntu+Mono:400,700,400italic); body { font-family: 'Noto Sans JP', serif; } h1, h2, h3 { font-family: 'Noto Sans JP', serif; font-weight: normal; } .remark-code, .remark-inline-code { font-family: 'Ubuntu Mono'; } </style> </head> <body> <textarea id="source"> class: center, middle # タイトル --- # アジェンダ 1. はじめに 2. 本日のメニュー 3. ... --- # はじめに ``` System.out.println("Hello, world!!!"); ``` </textarea> <script src="https://remarkjs.com/downloads/remark-latest.min.js"> </script> <script> var slideshow = remark.create(); </script> </body> </html> 仕上がり 関連 凝りたかったらCSSでおめかしして、内容はMarkdownで書いておけば良いやつ。 参考になればさいわいです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vueでvue-pdfを使う時にしたエラーの対処

2021/9/14時点の対応 ■[SyntaxError: Unexpected token <]の対処法 // ver4.3.0時点での公式のバグらしい .\node_modules\vue-pdf\src\vuePdfNoSss.vue:1 ({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){<style src="./annotationLayer.css"></style> SyntaxError: Unexpected token < ./plugins/vue-pdf.jsを作成 import Vue from 'vue' import pdf from 'vue-pdf' Vue.component('pdf', pdf) export default { // } 2.main.jsに追記 import Vue from 'vue' import App from './App.vue' import vuetify from './plugins/vuetify' import router from './route' import store from './store/store' import pdf from './plugins/vue-pdf.js' // pluginsフォルダのvue-pdf.jsを指定 Vue.config.productionTip = false // 定期的なセッション更新処理を稼働 polling.methods.refreshSession() new Vue({ router, vuetify, pdf, store, }).$mount('#app') ■[TypeError: Cannot read property 'catch' of undefined]の対処法 // このバージョンならエラーが出ない "pdfjs-dist": "2.5.207", "vue-pdf": "4.2.0"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ブラウザ上でcsvやxlsx等シートファイルを読み込む

csvデータをアップロードするための方法を色々まとめてみる。 csvファイル const chardet = require("chardet") const iconv = require("iconv-lite") var csvReader = new FileReader(); csvReader.onload = (e) => { let data = new Uint8Array(e.target.result); let encoding = chardet.detect(data); let csv = iconv.decode(data, encoding); document.getElementById("<text field>").value = csv; }; document.getElementById("<file filed>").addEventListener("change", (e) => { let sheetFile = e.target.files[0]; csvReader.readAsArrayBuffer(sheetFile); }); chardetとiconv-liteを使うことで文字コードによらずに読み込めるようにしている。 ファイルが読み込まれたらFileReaderでバッファーとして読み込んで、それをcsvにしてテキストフィールドに入力する。 xlsxファイル import XLSX from "xlsx" var xlsxReader = new FileReader(); xlsxReader.onload = (e) => { let data = new Uint8Array(e.target.result); let workbook = XLSX.read(data, {type: "array"}); let sheetName = document.getElementById("<text filed to input a sheet name>").value; let csv = XLSX.utils.sheet_to_csv(workbook.Sheets[sheetName]); document.getElementById("<text field>").value = csv; }; document.getElementById("<file filed>").addEventListener("change", (e) => { let sheetFile = e.target.files[0]; xlsxReader.readAsArrayBuffer(sheetFile); }); document.getElementById("<text filed to input a sheet name>").addEventListener("change", (e) => { let sheetFile = document.getElementById("<file filed>").files[0]; xlsxReader.readAsArrayBuffer(sheetFile); }); xlsxで読み込みと変換を行っている。上のcsvReaderと使い分けるならファイルのmimeタイプで分岐させる。xlsx, xlsm(マクロ入)はそれぞれ以下。 application/vnd.openxmlformats-officedocument.spreadsheetml.sheet application/vnd.ms-excel.sheet.macroEnabled.12 Google スプレッドシート import Axios from "axios" document.getElementById("<text filed to input a sheet url>").addEventListener("change", (e) => { let spreadsheetId = /(?<=\/spreadsheets\/d\/).*?(?=\/)/.exec(e.target.value)[0] let sheetUrl = "https://docs.google.com/spreadsheets/d/" + spreadsheetId + "/export" Axios.get(sheetUrl, {params: {format: "csv"}}).then((res) => { document.getElementById("<text field>").value = res.data; }).catch((err) => { outputErrors(err); }); }); Googleスプレッドシートは https://docs.google.com/spreadsheets/d/{spreadsheetId}/export?format={format} でファイルをダウンロードできる。テキストフィールドには共有リンクを渡す。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ブラウザ上でcsvやxlsx等のシートを読み込む

csvデータをアップロードするための入力方法を色々まとめてみる。 csvファイル const chardet = require("chardet") const iconv = require("iconv-lite") var csvReader = new FileReader(); csvReader.onload = (e) => { let data = new Uint8Array(e.target.result); let encoding = chardet.detect(data); let csv = iconv.decode(data, encoding); document.getElementById("<text field>").value = csv; }; document.getElementById("<file filed>").addEventListener("change", (e) => { let sheetFile = e.target.files[0]; csvReader.readAsArrayBuffer(sheetFile); }); chardetとiconv-liteを使うことで文字コードによらずに読み込めるようにしている。 ファイルが読み込まれたらFileReaderでバッファーとして読み込んで、それをcsvにしてテキストフィールドに入力する。 xlsxファイル import XLSX from "xlsx" var xlsxReader = new FileReader(); xlsxReader.onload = (e) => { let data = new Uint8Array(e.target.result); let workbook = XLSX.read(data, {type: "array"}); let sheetName = document.getElementById("<text filed to input a sheet name>").value; let csv = XLSX.utils.sheet_to_csv(workbook.Sheets[sheetName]); document.getElementById("<text field>").value = csv; }; document.getElementById("<file filed>").addEventListener("change", (e) => { let sheetFile = e.target.files[0]; xlsxReader.readAsArrayBuffer(sheetFile); }); document.getElementById("<text filed to input a sheet name>").addEventListener("change", (e) => { let sheetFile = document.getElementById("<file filed>").files[0]; xlsxReader.readAsArrayBuffer(sheetFile); }); xlsxで読み込みと変換を行っている。上のcsvReaderと使い分けるならファイルのmimeタイプで分岐させる。xlsx, xlsm(マクロ入)はそれぞれ以下。 application/vnd.openxmlformats-officedocument.spreadsheetml.sheet application/vnd.ms-excel.sheet.macroEnabled.12 ただしエクセルをインストールしているとcsvのmimeタイプがapplication/vnd.ms-excelという2003年以前のエクセル(.xls)のものに変わってしまうので.xlsを入力する場合は注意。 Google スプレッドシート import Axios from "axios" document.getElementById("<text filed to input a sheet url>").addEventListener("change", (e) => { let spreadsheetId = /(?<=\/spreadsheets\/d\/).*?(?=\/)/.exec(e.target.value)[0] let sheetUrl = "https://docs.google.com/spreadsheets/d/" + spreadsheetId + "/export" Axios.get(sheetUrl, {params: {format: "csv"}}).then((res) => { document.getElementById("<text field>").value = res.data; }).catch((err) => { outputErrors(err); }); }); Googleスプレッドシートは https://docs.google.com/spreadsheets/d/{spreadsheetId}/export?format={format} でファイルをダウンロードできる。テキストフィールドには共有リンクを渡す。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React 電卓作ってみた

気が向いたのでReact+Javascriptで電卓を作ってみました。 「1+1」とか「3*10」みたいな 2つの整数の四則演算 にか対応していません。(細かすぎると趣旨から外れるので) TypeScriptは使っていません。 ソースコードはGithubにあげているので適当に見てください。 一緒に作りたい方向け ハンズオン形式で作ってみたい方は以下を参照 想定しているレベル感 公式のチュートリアルをやったり、いろいろな教材を触ってみたがいまいちピンと来ていない useStateやpropsの使い方は分かるけどいつ使うべきかはわからない Reactの基礎を学んだが何か自分で作ってみたい Todoリストを作ったけどいまいちピンとこない 目次 準備中 完成イメージ 設計 コンポーネント アプリ >> Appコンポーネント ボタン >> Buttonコンポーネント State left ... 1つ目の数字 ope ... 演算子(+,-,*,/のこと) right ... 2つ目の数字 ans ... 計算結果 数字のボタンが押されるとleftやrightに、演算子のボタンが押されるとopeに、「=」が押されるとansに、それぞれいい感じの値をsetしていきます。 環境構築 必要なツール Node.js(NPM)とgitをインストールしておいてください。 読者の方の多くはもうすでに大丈夫だと思いますが念のため。 ※不安な人は以下のコマンドを叩いて確認してください.. バージョン確認 node -v git -v インストール 本当はcreate-react-appしてCSS書いてとかいろいろやらないといけないのですが、それでは本記事の趣旨にそぐわぬためテンプレートを用意しています。以下のコマンドを叩いて環境をサクッと整えてください。 git clone https://github.com/TBSten/react-calc.git cd react-calc npm install git checkout base npm run start npm run startで開発用サーバが立ち上がります。 特にエラーが出なければ以上で環境構築終了です! テンプレートのコード確認 まずはテンプレートファイルの内容を見ていきます。 まずはプロジェクトフォルダ(先ほど環境構築したreact-calcフォルダ)をお好みのテキストエディタで開いてください。(筆者はVSCodeを使ています)するとフォルダ構成は以下のようになっているはずです。 react-calc +--node_module +--public +--src ...(その他いろいろ) Reactで開発するときは基本的にプロジェクトフォルダ/srcフォルダ(以下srcフォルダ)内のファイルしかいじりません。なのでsrcフォルダを開きます。srcフォルダ内は以下のようになっています。 src +--styleフォルダ +--index.js +--App.js ...(その他いろいろ) index.jsファイルで基本的な設定(画面への描画やCSSの適用など)を行っています。その中でAppコンポーネントを読み込んでいるので、App.jsにいろいろ書いていきます。 src/App.js function App() { return ( <div className="calc"> <header>電卓</header> <div className="display"> {/* ここに計算結果表示 */} </div> <div className="input"> <div className="numbers"> {/* ここに数字のボタンや「.」、「=」ボタン */} </div> <div className="operators"> {/* ここに「+」、「-」、「*」、「/」ボタン */} </div> </div> </div> ); } export default App; ところどころにコメントがうってある通り、その位置にJSXなどを記述していきます。 試しに{/* ここに計算結果表示 */}を書き換えて電卓だよと表示してみましょう。 <div className="display"> 電卓だよ </div> 表示できました!後でここを計算結果に書き換えるイメージで今は大丈夫です。 Stateの実装 ここで設計で上げたStateの設計を振り返ってみます。 State left ... 1つ目の数字 ope ... 演算子(+,-,*,/のこと) right ... 2つ目の数字 ans ... 計算結果 まずはこれら4つのStateを使うことが分かっているので、これを実装します。 useStateを使うのでimportする const [state,setState] = useState(初期値);の形式でそれぞれ記述する。 各Stateの初期値は数字のものは0(入力がないときはデフォルトで0になるように)、それ以外はnull(nullは「まだ分からない」を表す値です)を設定しておきましょう。 src/App.js //ファイルの先頭 import {useState} from "react" ; ..... //コンポーネント内 const [left, setLeft] = useState(0); const [ope, setOpe] = useState(null); const [right, setRight] = useState(0); const [ans, setAns] = useState(null); ここまででApp.jsの中身はは以下のようになっているはずです。 src/App.js import {useState} from "react" ; function App() { const [left, setLeft] = useState(0); const [ope, setOpe] = useState(null); const [right, setRight] = useState(0); const [ans, setAns] = useState(null); return ( <div className="calc"> <header>電卓</header> <div className="display"> 電卓だよ </div> <div className="input"> <div className="numbers"> {/* ここに数字のボタンや「.」、「=」ボタン */} </div> <div className="operators"> {/* ここに「+」、「-」、「*」、「/」ボタン */} </div> </div> </div> ); } export default App; Stateを描画 このままだとStateを宣言しただけなのでこれを描画に使っていきましょう。 まず、先ほど電卓だよと書いたところにStateを表示していきましょう。とりあえず今はleft,ope,right という順番で表示させていきたいので、電卓だよの部分を以下のように変えましょう。 {left} {ope} {right} こうすることでleft,ope,rightを順番に描画していきます。今は0,null,0なので以下のように表示されます。 ※JSX内の{ null }は空文字として扱われるためopeの部分に何も表示されていません。 これで表示がとりあえずできましたが、一番初めはopeとrightの部分は隠れていてopeに何か入力されたら(演算子が入力されたら)opeとrightを表示するように三項演算子を使ってみましょう。 {left} {ope} {right} //↓↓↓ {left} {ope === null ? "" : ope} {ope === null ? "" : right} こうすることで leftは常に表示 opeは opeがnullじゃない時だけ表示 rightは opeがnullじゃない時だけ表示 されるようになります。 ボタンを追加する 今のままではボタンがありません。ボタンを作っていきましょう。 設計にて次のように設計したので アプリ >> Appコンポーネント ボタン >> Buttonコンポーネント ボタンはButtonコンポーネントに分離していきましょう。コンポーネントは「src/components」フォルダ内に記述するとファイルの整理がきれいにできるので、「src/components」フォルダ内にButton.jsxファイルを作成しましょう。またそのなかでButtonコンポーネントを定義し、export defaultして外部から使用できるようにしましょう。 src/components/Button.jsx export default function Button(props){ return ( //ここにJSXを書く ) ; } Buttonコンポーネントはただのボタンなので//ここにJSXを書くにはbuttonタグを記述しましょう。 src/components/Button.jsx //..... <button> </button> //..... またButtonコンポーネントは呼び出されるときに次のように呼び出すことで,textプロップスを出力してほしいです。 Buttonコンポーネントの使い方 <Button text={"ぼたんだよー"} /> //↓↓↓↓↓ 出力 <button>ぼたんだよー</button> なのでpropsからtextプロップスを受け取ってそれをbuttonタグ内で表示するようにしましょう。 src/components/Button.jsx //..... <button> { props.text } </button> //..... また今回のような汎用的なコンポーネントを作る際は作る時点で意図していなかったプロップス (例えば今のところtextプロップスしか渡されることを考えていないので,onClickプロップスなどが該当します) を渡されたらをとりあえずそれをbuttonタグに渡しておくようにするといいでしょう。 src/components/Button.jsx //..... const { text , ...other } = props ; //..... こうすることでprops.textが変数textへ、それ以外のpropsの中身はotherへオブジェクトとして渡されます。そしてこのotherをbuttonタグに渡しましょう。 src/components/Button.jsx //..... <button {...other}> {text} </button> //..... これでButtonコンポーネントがひとまず完成しました。ここまででApp.js,Button.jsxコンポーネントは以下の通りになっているはずです。 src/components/Button.jsx export default function Button(props){ const { text , ...other } = props ; return ( <button {...other}> { text } </button> ) ; } src/App.jsx import {useState} from "react" ; function App() { const [left, setLeft] = useState(0); const [ope, setOpe] = useState(null); const [right, setRight] = useState(0); const [ans, setAns] = useState(null); return ( <div className="calc"> <header>電卓</header> <div className="display"> {left} {ope === null ? "" : ope} {ope === null ? "" : right} </div> <div className="input"> <div className="numbers"> {/* ここに数字のボタンや「.」、「=」ボタン */} </div> <div className="operators"> {/* ここに「+」、「-」、「*」、「/」ボタン */} </div> </div> </div> ); } export default App; ボタンをAppに組み込む 先ほど作ったButtonコンポーネントをAppコンポーネントの中に組み込みましょう。Appコンポーネントに組み込めば画面に表示されるはずです。まず{/* ここに数字のボタンや「.」、「=」ボタン */}の部分に追加してみます。 src/App.jsx //App.jsxファイルの先頭 import Button from "./components/Button" ; //..... <Button text="0"/> //..... ボタンが表示されました! あとは0,1,2,3,4,5,6,7,8,9のボタンを追加していきましょう。 src/App.jsx //..... <Button text="0"/> <Button text="1"/> <Button text="2"/> <Button text="3"/> <Button text="4"/> <Button text="5"/> <Button text="6"/> <Button text="7"/> <Button text="8"/> <Button text="9"/> //..... ※「ん?コピペとかして、こんなに面倒なことしないといけないの?」と違和感を持った方はぜひ私のGithubのページでソースコードを確認してみてください。もっと賢い方法があります。 「ボタンが押されたら」を実装する 設計を振り返ってみると 数字のボタンが押されるとleftやrightに、演算子のボタンが押されるとopeに、「=」が押されるとansに、それぞれいい感じの値をsetしていきます。 となっているのでボタンが押されたときの処理を書いていきましょう。ボタンが押されたときの処理はonClickプロップスに関数として渡します。例えば0のボタンが押されたときの処理は、 0のボタンが押されたときの処理 <Button onClick={ ()=> {/*ここにやりたい事*/} } /> という風に記述します。 とはいっても0を押したときと1を押したときの動作の違いはほぼないので、keyPressedという関数を用意して、0のボタンが押されたときはkeyPressed(0)を、1のボタンが押されたときはkeyPressed(1)を...というように指定すると同じコードを何度も記述しなくて済みます。 src/App.js function keyPressed(key){ //keyのボタンが押されたら... } //..... <Button text="0" onClick={ ()=> {keyPressed(0)} } /> <Button text="1" onClick={ ()=> {keyPressed(1)} } /> <Button text="2" onClick={ ()=> {keyPressed(2)} } /> //以下同様 ※繰り返しですが「ん?コピペとかして、こんなに面倒なことしないといけないの?」と違和感を持った方はぜひ私のGithubのページでソースコードを確認してみてください。もっと賢い方法があります。 さてkeyPressed関数の中では何をすべきでしょうか。設計を思い出してみましょう。 まず数字のボタンが押されたときは、leftまたはrightの値が変わってほしいです。leftかrightどちらの値を書き換えるべきかはopeによって変わります。 もしopeがnull(まだ入力されていない)なら leftを更新 もしopeがnullじゃない(何か入力されている)なら rightを更新 なのでkeyPressed関数の中では条件によってleft,rightを更新する処理を書きます。 src/App.js //..... if(ope === null){ //leftを更新 }else{ //rightを更新 } //..... ではそれぞれどのような値に更新すべきでしょうか?まずleftから考えてみます。 これを考えるには①どのような状態の時に、②どのようなボタンが押されたら、③どのような値になってほしいのかの3つを考えると良いでしょう。例えば、 パターン1 leftが ①「0」の時に ②「3」が押されたら ③leftは「3」になってほしい ではこんなパターンではどうでしょうか? パターン1 leftが ①「3」の時に ②「4」が押されたら ③leftは「34」になってほしい 結論から言うと①と②と③の関係は次のようになっています。 ③ = ①*10 + ② --------------- 3 = 0*10 + 3 34 = 3*10 + 4 このような思考は初心者にはすぐには理解できないかもしれませんので今は理解できなくてもいいでしょうが、いずれはできるようになれていくといいでしょう。 上の式をもとに考えるとleftの値は 更新前のleft * 10 + 入力された値 このような値に更新されるといいでしょう。 なのでifのYesの中には src/App.js //..... function keyPresesd(key){ if(ope === null){ setLeft( left*10 + key); }else{ //rightを更新 } } //..... と書くと良いでしょう。また、rightも同様に考えれるので src/App.js //..... function keyPresesd(key){ if(ope === null){ setLeft( left*10 + key); }else{ setRight( right*10 + key); } } //..... とすればいいでしょう。ここまででいったんうまく動くかチェックしてみましょう。 まだopeを書き換える処理を実装していないので左側の数字の入力しかありませんが問題なさそうです。次は演算子を実装していきましょう。 演算子のボタンを実装する 演算子のボタンは{/* ここに「+」、「-」、「*」、「/」ボタン */}のところに実装していきます。(数字のボタンの時とほぼ同じなので説明はほぼ割愛します。)以下のコードをここに書きましょう。 src/App.js //..... <Button text="+" onClick={ ()=> {/*後述*/} } /> <Button text="-" onClick={ ()=> {/*後述*/} } /> <Button text="*" onClick={ ()=> {/*後述*/} } /> <Button text="/" onClick={ ()=> {/*後述*/} } /> //..... 次にonClickプロップスに渡す関数を考えてみましょう。数字の時に作ったkeyPressed関数を使用したくなりますが、keyPressed関数を呼び出して実行されるのはあくまで数字が押されたときの処理です。演算子ボタンが押されたときの処理はそれとは少し違ったコードになるので、関数を分けるべきです。なので演算子ボタンがクリックされたとき用の関数opeKeyPressedを新たに定義してそれを実行するようにしましょう。 src/App.js function opeKeyPressed(key){ //keyのボタンが押されたら... } //..... <Button text="+" onClick={ ()=> {opeKeyPressed("+")} } /> <Button text="-" onClick={ ()=> {opeKeyPressed("-")} } /> <Button text="*" onClick={ ()=> {opeKeyPressed("*")} } /> <Button text="/" onClick={ ()=> {opeKeyPressed("/")} } /> //..... 次にopeKeyPressed(key)が実行されたときの処理を考えます。 速い話が、演算子ボタンが押されたらopeStateを更新するべきです。それが更新されることで、「opeやrightはopeがnullじゃない時だけ表示する」の動きを実装できるからです。またどの演算子で更新するかはopeKeyPressed関数の引数として"+"や"-"などの演算子が与えられるのでそれをそのまま設定してしまえばOKです。 src/App.js //..... function opeKeyPressed(key){ setOpe(key); } //..... ここまで実装できれば演算子ボタンを押すことで演算子が表示され、2つ目の数字が入力できるようになっているはずなので確認してみましょう。 ここまででApp.jsは以下のようになっているはずです。 src/App.js import {useState} from "react" ; import Button from "./components/Button" ; function App() { const [left, setLeft] = useState(0); const [ope, setOpe] = useState(null); const [right, setRight] = useState(0); const [ans, setAns] = useState(null); function keyPressed(key){ if(ope === null){ setLeft( left*10 + key); }else{ setRight( right*10 + key); } } function opeKeyPressed(key){ setOpe(key); } return ( <div className="calc"> <header>電卓</header> <div className="display"> {left} {ope === null ? "" : ope} {ope === null ? "" : right} </div> <div className="input"> <div className="numbers"> <Button text="0" onClick={ ()=> {keyPressed(0)} } /> <Button text="1" onClick={ ()=> {keyPressed(1)} } /> <Button text="2" onClick={ ()=> {keyPressed(2)} } /> <Button text="3" onClick={ ()=> {keyPressed(3)} } /> <Button text="4" onClick={ ()=> {keyPressed(4)} } /> <Button text="5" onClick={ ()=> {keyPressed(5)} } /> <Button text="6" onClick={ ()=> {keyPressed(6)} } /> <Button text="7" onClick={ ()=> {keyPressed(7)} } /> <Button text="8" onClick={ ()=> {keyPressed(8)} } /> <Button text="9" onClick={ ()=> {keyPressed(9)} } /> </div> <div className="operators"> <Button text="+" onClick={ ()=> {opeKeyPressed("+")} } /> <Button text="-" onClick={ ()=> {opeKeyPressed("-")} } /> <Button text="*" onClick={ ()=> {opeKeyPressed("*")} } /> <Button text="/" onClick={ ()=> {opeKeyPressed("/")} } /> </div> </div> </div> ); } export default App; ※Button.jsxは変更なし 「=」ボタンを押すと計算結果が表示される さていよいよこの記事も大詰めです。最後に「=」を押すと計算結果を表示してくれるようにしましょう。まず=ボタンを作らないことには始まらないので、数字のボタンの最後にtext="="であるボタンを追加しましょう。 src/App.js //..... <Button text="8" onClick={ ()=> {keyPressed(8)} } /> <Button text="9" onClick={ ()=> {keyPressed(9)} } /> <Button text="=" onClick={ ()=> {/*後述*/} } /> //←ここ //..... そしてまたまたonClickですが、=ボタンが押されたときの処理はこれまた他のボタンとは違った動きをします。なので新しくequalKeyPressed関数を用意してそれを実行するようにしましょう。ただし今回の=ボタンは1つしかないのでわざわざequalKeyPressed("=")としなくてもequalKeyPressed()だけで十分です。 src/App.js function equalKeyPressed(){ //=ボタンが押されたとき } //..... <Button text="=" onClick={ ()=> {equalKeyPressed()} } /> //←ここ //..... あとはequalKeyPressed()が実行されたときにおこる処理を考えるだけです。 ここで記事の初めの方で紹介した設計を思い出してください。 State left ... 1つ目の数字 ope ... 演算子(+,-,*,/のこと) right ... 2つ目の数字 ans ... 計算結果 数字のボタンが押されるとleftやrightに、演算子のボタンが押されるとopeに、「=」が押されるとansに、それぞれいい感じの値をsetしていきます。 「=」ボタンが押されたときは計算結果を表示しなければいけません。Stateの設計を見る限りその計算結果はansに入ってくると良さそうです。なのでansを更新しましょう。 src/App.js function equalKeyPressed(){ setAns(/*後述*/); } ではsetAnsの中には何を書くべきでしょうか? ここにはleft,ope,rightをもとに計算した結果を入れると良いです。 例えば、 leftに5,opeに"+",rightに7が入っていた場合,ansには12 を入れると良いです。 また、 leftに30,opeに"/",rightに5が入っていた場合,ansには6 を入れると良いです。 つまり、 もし ope が"+" なら setAns(left + right) もし ope が"-" なら setAns(left - right) //以下同様 としていけばいい訳です。これをコードにおこすと、 src/App.js function equalKeyPressed(){ if(ope === "+"){ setAns(left+right); }else if(ope === "-"){ setAns(left-right); }else if(ope === "*"){ setAns(left*right); }else if(ope === "/"){ setAns(left/right); } } となります。 これでボタンを押したときの処理が実装できました。 ですがansはAppコンポーネントのreturn ( ... ) の中にはどこにも書かれていないため、 表示されません。なので right の後に表示するよう追加してみましょう。 src/App.js <div className="display"> {left} {ope === null ? "" : ope} {ope === null ? "" : right} 答え:{ans} //ここを追加 </div> ここまで来たら計算結果が表示されるはずです。チェックしてみましょう。 うーん、これじゃない感が否めませんが、とりあえず=を押したら計算結果の表示が出来ました! これじゃない感を打破するために、「答え:」を=を押した後だけ表示するようにしてみましょう。これは三項演算子を使うことで手軽に実装できます。 src/App.js { ans === null ? "" : " 答え:"+ans } 三項演算子でansがnullじゃない(=ボタンが押された)ときだけ「 答え:〇〇」というように表示しています。 これで電卓の開発が出来ました!お疲れ様でした! おまけ ところどころに掲載したGithubのページのソースコードとこの記事で完成できるソースコードは多少異なっています。これはあえてです。何が違うのか、どちらの方が適切かぜひご自身で目を通して考えてみてください!(職務放棄)(ボソッ この記事では実装していない機能(完成イメージとちょっと違うところ)がいくつかあるので紹介します。ぜひチャレンジしてみてください。(職務放棄)(ボソッ 計算結果をクリアしてもう一度計算しなおす機能 小数点が入力できる機能 様々な演算子(^, sin, cos, tanなどなど)や特別な数(πやeなど) ボタンの並び方
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript入門(ECMAScriptモジュール③)

概要 ...JavaScript入門(ECMAScriptモジュール②)の続きです。 JavaScriptを学習、理解を深めるため「JavaScript Primer 迷わないための入門書」を読み、 理解した内容等を記載していく。 JavaScript入門一覧に他の記事をまとめています。 ECMAScriptモジュール(その他構文) これまで記載したECMAScriptモジュールについて、その他の構文についても記載する。 ※下記「動作環境情報」を参考にし、開発者コンソールで結果を確認してください 動作環境情報はコチラ 【環境】 PC:Mac エディタ:Visual Studio Code プラグイン:Live ServerというVisual Studio Codeのプラグインを使用し、サーバ環境を用意。 開発者コンソール:GoogleChrome 【登場ファイル】 export.js:エクスポートに関するコードを記述したエクスポート側のファイル import.js:インポートに関するコードを記述したインポート側のファイル index.html:「import.js」を使用、および開発者コンソールで確認する際に開くファイル 【★ポイント】 index.htmlのheadタグ内に<script src="import.js" type="module"></script>のように記述する index.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>JavaScript入門</title> <script src="import.js" type="module"></script> </head> <body> </body> </html> ※最終的に以下のような構成になる。 (任意の場所に「js」フォルダを作成および、フォルダに上記3ファイルを作成・配置する) 再エクスポート 別のモジュールをインポートし、再度自身のファイル内でエクスポートすること。 エクスポートされた複数のモジュールをまとめたモジュールを作成する際などに使用する。 再エクスポート方法はexport モジュール名 from モジュールパス;と記載する。 export.js // 名前つきエクスポートを再エクスポート export { foo, bar } from './export1.js'; // すべての名前つきエクスポートを再エクスポート export * from './export1.js'; // ..などその他にも様々なパターンで活用可能 すべてをインポート すべての名前つきエクスポートをまとめてインポートする方法 モジュールごとの名前空間となるオブジェクトをimport as構文で宣言する。 変数や関数にアクセスす際は名前空間オブジェクトのプロパティを使用する。 defaultという固有名を使用するとデフォルトエクスポートにもアクセス可能 export.js // wordという変数を定義 const word = 'word'; // helloという関数を定義 function hello() { console.log('Hello JavaScript'); }; // デフォルトエクスポート用の関数を定義 export default function defaultFunc() { console.log('defaultFunc'); } // 「word」「hello」を名前つきエクスポート export { word, hello }; import.js // 名前空間となるオブジェクトをimport as構文で宣言 import * as exportModule from './export.js'; console.log(exportModule.word); // => word exportModule.hello(); // => Hello JavaScript exportModule.default(); // => defaultFunc 副作用のためのインポート 単にモジュールのコードを実行し、変数や値など参照する必要のない場合などに使用(何かしらのセットアップするなど) import モジュールパス;でモジュールファイル全体をインポートする。 export.js console.log('毎回このメッセージを表示する!'); import.js // import モジュールパス;で副作用のためのインポート import './export.js' // => 読み込んだ時点で「毎回このメッセージを表示する!」が表示される
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript入門(ECMAScriptモジュール②)

概要 ...JavaScript入門(ECMAScriptモジュール①)の続きです。 JavaScriptを学習、理解を深めるため「JavaScript Primer 迷わないための入門書」を読み、 理解した内容等を記載していく。 JavaScript入門一覧に他の記事をまとめています。 デフォルトエクスポート/インポート デフォルトエクスポート/インポートについてそれぞれ記載していく。 ※下記「動作環境情報」を参考にし、開発者コンソールで結果を確認してください 動作環境情報はコチラ 【環境】 PC:Mac エディタ:Visual Studio Code プラグイン:Live ServerというVisual Studio Codeのプラグインを使用し、サーバ環境を用意。 開発者コンソール:GoogleChrome 【登場ファイル】 export.js:エクスポートに関するコードを記述したエクスポート側のファイル import.js:インポートに関するコードを記述したインポート側のファイル index.html:「import.js」を使用、および開発者コンソールで確認する際に開くファイル 【★ポイント】 index.htmlのheadタグ内に<script src="import.js" type="module"></script>のように記述する index.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>JavaScript入門</title> <script src="import.js" type="module"></script> </head> <body> </body> </html> ※最終的に以下のような構成になる。 (任意の場所に「js」フォルダを作成および、フォルダに上記3ファイルを作成・配置する) デフォルトエクスポート モジュールごとに1つしかエクスポートができない。(名前つきエクスポートは複数可能) エクスポート方法は、export default エクスポート対象;と記載する。 export.js // wordという変数を定義 const word = 'word'; // helloという関数を定義 function hello() { console.log('Hello JavaScript'); }; // export default エクスポート対象; でエクスポート export default word; ★Tips① export文を宣言の前に付けると、宣言とデフォルトエクスポートが同時に可能となる。 このTipsは関数やクラスのみに有効。(変数はカンマ区切りで複数定義可能なため) また、この時関数名やクラス名を省略可能。 export default function hello() {  "...処理" }; // 関数名を省略した場合 export default function () { "...処理" }; // 変数は使用できない export default const tmp_var; // =>エラーとなる デフォルトインポート 指定したモジュールのデフォルトエクスポートに名前をつけてインポートする。 インポート方法はimport インポート名 from モジュールパスと記載する。 export.js // wordという変数を定義 const word = 'word'; // helloという関数を定義 function hello() { console.log('Hello JavaScript'); }; // export default エクスポート対象; でエクスポート export default word; import.js // import インポート名 from モジュールパスでインポート import word from './export.js'; console.log(word); // => word ★Tips② デフォルトエクスポートは、defaultという固有の名前による名前つきエクスポートと同じものである。 そのため、名前つきエクスポートでas defaultとエイリアスをつけることでデフォルトエクスポートすることが可能。 また、名前つきインポートではdefaultという名前を指定することで、デフォルトインポートすることが可能。 export.js // wordという変数を定義 const word = 'word'; // 名前つきエクスポートでas defaultとすることでデフォルトエクスポートとなった export { word as default }; import.js // defaultは予約語のため、名前つきインポートでas構文を使用しエイリアスを付けることでインポート可能 import { default as defaultWord } from './export.js'; console.log(defaultWord); // => word ★Tips③ 名前つきインポートとデフォルトインポートの構文は同時に記述することも可能。 export.js // wordという名前つきエクスポート用変数を定義 const word = 'word'; // 関数defaultFuncをデフォルトエクスポート用に定義 export default defaultFunc () { console.log('defaultFunc!'); } // 「word」を名前つきエクスポート export { word }; import.js // デフォルトエクスポートを行った「defaultFunc」、名前つきエクスポートを行った「word」をインポート import defaultFunc, { word } from './export.js'; console.log(word); // => word defaultFunc(); // => defaultFunc!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript入門(ECMAScriptモジュール①)

概要 JavaScriptを学習、理解を深めるため「JavaScript Primer 迷わないための入門書」を読み、 理解した内容等を記載していく。 JavaScript入門一覧に他の記事をまとめています。 ECMAScriptモジュール モジュールには以下の利点がある。 1. 保守性 機能単位や関連性を持つ単位で切り離すことで、 他のコードとの依存性が減少し、修正・変更時には他のコードへ影響しにくい。 2. 名前空間 モジュールごとにスコープ(範囲)が明確になることで、 変数名の競合やどの機能を使用しているかの把握も容易になる。 3. 再利用性 後述するエクスポート/インポートすることで、 必要な機能を必要な場所で(モジュールとして)再利用でき、修正時には1箇所の修正で対応可能。 モジュールとは(引用:Wikipedia) モジュール(英: module)とは、工学などにおける設計上の概念で、システムを構成する要素となるもの。いくつかの部品的機能を集め、まとまりのある機能を持った部品のこと。モジュールに従っているものをモジュラー(英: modular)という。 ECMAScriptモジュールの構文 モジュールを利用するためには、以下の構文と方法がある。 1. エクスポート/インポートの構文 作成したモジュールを(エクスポート、インポートして)他のファイルでも利用するため、 export文とimport文という2種類の構文が存在する。 2. エクスポート/インポートの方法 名前つき(エクスポート/インポート)、デフォルト(エクスポート/インポート)という方法がある。 エクスポートとは ・(データを)出力すること。 ・今回の場合、宣言した変数や関数を他のファイルで取り込める状態にすること。 インポート ・(データを)取り入れて、使用できるようにすること。 ・今回の場合、エクスポート(出力)された他ファイルの変数や関数をモジュールとして取り入れ、使用できる状態にすること。 名前つきエクスポート/インポート 名前付きエクスポート/インポートについてそれぞれ記載していく。 ※下記「動作環境情報」を参考にし、開発者コンソールで結果を確認してください 動作環境情報はコチラ 【環境】 PC:Mac エディタ:Visual Studio Code プラグイン:Live ServerというVisual Studio Codeのプラグインを使用し、サーバ環境を用意。 開発者コンソール:GoogleChrome 【登場ファイル】 export.js:エクスポートに関するコードを記述したエクスポート側のファイル import.js:インポートに関するコードを記述したインポート側のファイル index.html:「import.js」を使用、および開発者コンソールで確認する際に開くファイル 【★ポイント】 index.htmlのheadタグ内に<script src="import.js" type="module"></script>のように記述する index.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>JavaScript入門</title> <script src="import.js" type="module"></script> </head> <body> </body> </html> ※最終的に以下のような構成になる。 (任意の場所に「js」フォルダを作成および、フォルダに上記3ファイルを作成・配置する) 名前つきエクスポート 複数の変数や関数などの宣言名(名前)を指定し、エクスポートできる。 エクスポート方法は、export { エクスポート対象 };と記載する。 複数エクスポートする場合は、,(カンマ)で区切り指定する。 export.js // wordという変数を定義 const word = 'word'; // helloという関数を定義 function hello() { console.log('Hello JavaScript'); }; // export { エクスポート対象 }; でエクスポート export { word, hello }; // 複数指定はカンマで区切る ★Tips① export文を宣言の前に付けると、宣言と名前つきエクスポートが同時に可能となる。 export const word = "word"; 名前つきインポート 指定したモジュールから名前を指定してインポート可能。 インポート方法は、import { インポート対象 } from モジュールパス;と記載する。 複数インポートする場合は、,(カンマ)で区切り指定する。 export.js // wordという変数を定義 const word = 'word'; // helloという関数を定義 function hello() { console.log('Hello JavaScript'); }; // export { エクスポート対象 }; でエクスポート export { word, hello }; // 複数指定はカンマで区切る モジュールパスについては現在位置を意味するドット(.)を用いた相対パスを記載することでサイトの階層構造の変更等にも対応できる。 import.js // import { インポート対象 } from モジュールパス; でインポート import { word, hello } from './export.js'; console.log(word); // => word hello(); // => Hello JavaScript 名前つきエクスポート/インポートのエイリアス 名前つきエクスポート/インポートにはエイリアスという仕組みがある。 エイリアスを使用すると、宣言済みの変数を別名でエクスポート/インポート可能となる。 エイリアスを使用するにはasを使用する。 エクスポートの場合、export { 定義済み変数 as 別名 }; インポートの場合、import { 定義済み変数 as 別名 } from モジュールパス; export.js // wordという変数を定義 const word = 'word'; // helloという関数を定義 function hello() { console.log('Hello JavaScript'); }; // 変数「word」を「w」というエイリアス名で、関数「hello」はそのまま名前でエクスポートしてみる export { word as w, hello }; import.js // エクスポートしたエイリアス名「w」、関数「hello」はエイリアス名「helloFunction」としてインポートしてみる import { w, hello as helloFunction } from './export.js'; // 結果に差異はない console.log(w); // => word helloFunction(); // => Hello JavaScript
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む