- 投稿日:2020-04-07T23:43:31+09:00
PdfMakeを使ったPDF作成APIをZeit Nowで動かしたメモ
概要
PDFをサーバサイドで作成し、ダウンロードするAPIを作成した。
ローカルで動いていたものがデプロイ時に動かないなどのトラブルがあったが、
最終的には成功できた。ローカル環境
- node:13.12.0
- Now CLI 17.1.1
フォルダ構成
- api - pdf.ts - fonts - ipaexg.ttf - ipaexm.ttf - src - pages - index.tsx - PdfArea.tsx - .babelrc.js - next-env.d.ts - next.config.js - now.json - package.json - tsconfig.jsonハマった点
APIを
src/pages/api/pdf.ts
に書いていたとき、ローカルで動かすときには成功し、デプロイしたときにエラーとなる現象が発生した。
デプロイ時にはfonts
フォルダが見えなくてファイルが開けない問題が発生していた。
apiはソース通りのフォルダ構成ではなく、ラムダ環境にコンパイル済で配置されていたことが原因と考えられる。
apiファイルの場所を、src/pages/api
から直下のapi
に移し、now.jsonにincluceFiles
を追加することで解決した。now.json{ "version": 2, "functions": { "api/pdf.ts": { "includeFiles": "fonts/**" } } }
incluceFiles
を使うときは、nextの仕組みのpages/api
は使えない模様。
以下では、コンパイルは通るが、フォルダがアップロードされなかった。now.jsonのうまくいかなかった例{ "version": 2, "functions": { "src/pages/api/pdf.ts": { "includeFiles": "fonts/**" } } }ファイル
src/pages/index.tsximport Head from 'next/head' import * as React from 'react' import { NextPage } from 'next' import PdfArea from './PdfArea' const Home: NextPage = () => { return ( <div className="container"> <main> <PdfArea /> </main> </div> ) } export default Homesrc/pages/PdfArea.tsximport React, { useState, useEffect } from 'react' import { useDispatch } from 'react-redux' import scenarioModule, { useScenario, usePdf } from '../store/modules/scenarioModule' const makePdf = async (scenario, dispatch) => { const pdf = await ( await fetch('/api/pdf', { method: 'POST', headers: { 'Content-Type': 'application/json; charset=utf-8' }, body: JSON.stringify(scenario) }) ).text() dispatch(scenarioModule.actions.setPdf(pdf)) } const PdfArea: React.FC = () => { const scenario = useScenario() const pdf = usePdf() const dispatch = useDispatch() if (!scenario) { return <div>読込失敗</div> } return ( <> <button onClick={(e) => makePdf(scenario, dispatch)}>PDFを作る</button> {pdf !== '' && ( <a href={pdf} download="scenario.pdf"> 作成したPDFをダウンロード </a> )} </> ) } export default PdfAreaapi/pdf.tsimport { NextApiRequest, NextApiResponse } from 'next' import path from 'path' import PdfPrinter from 'pdfmake' import { Scenario } from '../src/store/modules/scenarioModule' function createPdfBinary(pdfDoc, callback) { const baseDir = 'fonts/' const gPath = path.resolve(baseDir + 'ipaexg.ttf') const mPath = path.resolve(baseDir + 'ipaexm.ttf') const fontDescriptors = { IPASerif: { normal: mPath, bold: mPath, italics: mPath, bolditalics: mPath }, IPAGothic: { normal: gPath, bold: gPath, italics: gPath, bolditalics: gPath } } const printer = new PdfPrinter(fontDescriptors) const doc = printer.createPdfKitDocument(pdfDoc) const chunks = [] doc.on('data', function(chunk) { chunks.push(chunk) }) doc.on('end', function() { const result = Buffer.concat(chunks) callback('data:application/pdf;base64,' + result.toString('base64')) }) doc.end() } export default (req: NextApiRequest, res: NextApiResponse) => { const scenario: Scenario = req.body const docDefinition = { content: [ { text: scenario.title, fontSize: 55, font: 'IPASerif' }, ], defaultStyle: { font: 'IPAGothic', alignment: 'center' } } createPdfBinary(docDefinition, (binary) => { res.setHeader('Content-Type', 'application/json') res.status(200).send(binary) }) }tsconfig.json{ "compilerOptions": { "baseUrl": ".", "paths": { "~/*": [ "./src/*" ], }, "target": "es6", "lib": [ "dom", "dom.iterable", "esnext" ], "allowJs": true, "skipLibCheck": true, "strict": false, "forceConsistentCasingInFileNames": true, "noEmit": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve" }, "exclude": [ "node_modules" ], "include": [ "next-env.d.ts", "**/*.ts", "**/*.tsx" ] }next.config.jsconst { resolve } = require('path') const withOffline = require('next-offline') const nextConfig = { webpack: (config) => { // src ディレクトリをエイリアスのルートに設定 config.resolve.alias['~'] = resolve(__dirname, 'src') return config }, // manifest設定 target: 'serverless', transformManifest: (manifest) => ['/'].concat(manifest), generateInDevMode: true, workboxOpts: { swDest: 'static/service-worker.js', runtimeCaching: [ { urlPattern: /^https?.*/, handler: 'NetworkFirst', options: { cacheName: 'https-calls', networkTimeoutSeconds: 15, expiration: { maxEntries: 150, maxAgeSeconds: 30 * 24 * 60 * 60 // 1 month }, cacheableResponse: { statuses: [0, 200] } } } ] } } // PWA に対応 module.exports = withOffline(nextConfig).babelrc.js// ローカルの開発サーバー側の SSR 時と クライアント側のCSR 時に styled-components が付与するクラス名に差が生まれるエラーの対応 module.exports = { presets: ['next/babel'], plugins: [ ['styled-components', { ssr: true, displayName: true, preprocess: false }] ] }参考
including-additional-files
pdfmake
IPAex フォント ダウンロードページ
IPAex フォント ダウンロードページ
Zeit Now の具体的な Tips 集
Javascript で PDF を作成する
- 投稿日:2020-04-07T22:04:15+09:00
Kinx ライブラリ - Integer
Integer
はじめに
「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。言語はライブラリが命。ということでライブラリの使い方編。
リポジトリ(https://github.com/Kray-G/kinx) のほうの説明を "Looks like JavaScript, feels like Ruby, and it is the script language fitting in C programmers." に変えてみた。意味的には例の名探偵のオマージュですが、英語の表現は違っていてオリジナルです。
今回は Integer です。
- 参考
- 最初の動機 ... スクリプト言語 KINX(ご紹介)
- 個別記事へのリンクは全てここに集約してあります。
- リポジトリ ... https://github.com/Kray-G/kinx
- Pull Request 等お待ちしております。
Integer オブジェクトに括り付いたメソッドは特殊メソッドで、整数値に直接作用させることができる。特殊メソッド、および特殊オブジェクトに関する詳細は Kinx ライブラリ - String を参照してください。
Integer 特殊オブジェクト
Integer オブジェクトに対して関数定義する例は以下の通り。
Integer.times100 = function(value) { return value * 100; }; var val = 100.times100(); System.println(val);実行してみよう。
10000レシーバーが第 1 引数に来ます。
Integer
組み込み特殊メソッド
メソッド 意味 Integer.times(val, callback) i = 0 ~ (val - 1) の範囲として、 callback
があればcallback(i)
の結果で、無ければ i で配列を作成して返す。Integer.upto(val, max, callback) i = val ~ max の範囲で引数として callback(i)
を呼ぶ。Integer.downto(val, min, callback) i = min ~ val の範囲で引数として callback(i)
を呼ぶ。Integer.toString(val, base) val
を文字列に変換する。base
は 10 と 16 のみサポート。Integer.toDouble(val) val
を Double に変換する。Math オブジェクト・メソッド
Integer オブジェクトには Math オブジェクトと同じ特殊メソッドが存在する。詳細は以下を参照。
具体例で書くと、例えば以下のように書ける。
var a = 2.pow(10); // Math.pow(2, 10) と同じ => 1024 var b = (-10).abs(); // Math.abs(-10) と同じ => 10単項マイナス(
-
)は関数呼び出しより優先順位が低いため、カッコで括る必要があることに注意。特殊オペレーター
単項
*
オペレーター単項
*
オペレーターを整数値に適用した場合、文字コードに対応した文字列(1 文字)を返す。var a = *97; // => "a"ちなみに逆変換(
*a
)は元に戻らない。文字列に対する単項*
オペレーターは配列になるため、上記例で*a
とすると[97]
と配列となることに注意。単独で文字コードを得るには、a[0]
とする。おわりに
Integer に特殊メソッドがあることでグッと Ruby っぽい感じがしてますが気のせいですかね。
2.pow(10)
とか書けるのが結構感慨深いなー。ここ 見ながら、今サポートしてないメソッドとかサポートしてみようかなー、と。
ではまた次回。
- 投稿日:2020-04-07T21:54:43+09:00
【Node.js】複数のファイルパスをオブジェクトでの階層表現に変換する
経緯
Node.jsのfsを使って特定のディレクトリの中身をファイルパスにて取得した際に、ディレクトリの階層と互換性のあるオブジェクトに変換したかった。
結論
index.jsconst data = [ '/public/aaa/1.file', '/public/aaa/2.file', '/public/bbb/1.file', '/public/ccc/1.file', '/public/ccc/2.file', '/public/ccc/3.file', '/public/ddd/1.file' ]; const output = {}; let current; for (const path of data) { current = output; for (const segment of path.split('/')) { if (segment !== '') { if (!(segment in current)) { current[segment] = {}; } current = current[segment]; } } } console.log(output); /* 実行結果 { public: { aaa: { '1.file': {}, '2.file': {} }, bbb: { '1.file': {} }, ccc: { '1.file': {}, '2.file': {}, '3.file': {} }, ddd: { '1.file': {} } } } */あとは取得した結果から
子要素に値が入っていたらディレクトリ
子要素に値が入っていなかったらファイルと認識して分岐させれば色々使えると思う。
- 投稿日:2020-04-07T21:27:59+09:00
googleスプレッドシートのPDF化を楽にするブックマークレット
googleスプレッドシートをPDF化するときの自分用設定をブックマークレット化してみました。
javascript:(function(){const current = location.href;if (current.match(/^.+docs\.google\.com\/spreadsheets\/d\//)) {const m = current.match(/^.+\/d\/(.+)\/edit#gid=(.+)$/);const printUrl = `https://docs.google.com/spreadsheets/d/${m[1]}/export?exportFormat=pdf&format=pdf&size=A4&portrait=false&sheetnames=false&printtitle=false&fitw=true&pagenumbers=false&gridlines=false&top_margin=0.75&bottom_margin=0.75&left_margin=0.25&right_margin=0.25&horizontal_alignment=LEFT&vertical_alignment=TOP&fzr=false&gid=${m[2]}`;window.open(printUrl);}})()下記のように各種パラメータを設定しています。
exportFormat=pdf
format=pdf
PDF形式size=A4
A4サイズportrait=false
横向き印刷sheetnames=false
シート名を印刷しないprinttitle=false
タイトルを印刷しないfitw=true
幅に揃えるpagenumbers=false
ページ番号を表示しないgridlines=false
グリッド線を印刷しないtop_margin=0.75
bottom_margin=0.75
上下余白0.75インチ(プリセットの余白「狭」)left_margin=0.25
right_margin=0.25
左右余白0.25インチ(プリセットの余白「狭」)horizontal_alignment=LEFT
vertical_alignment=TOP
左上寄せfzr=false
固定行を表示しないコード
(function(){ const current = location.href; if (current.match(/^.+docs\.google\.com\/spreadsheets\/d\//)) { const m = current.match(/^.+\/d\/(.+)\/edit#gid=(.+)$/); const printUrl = `https://docs.google.com/spreadsheets/d/${m[1]}/export?exportFormat=pdf&format=pdf&size=A4&portrait=false&sheetnames=false&printtitle=false&fitw=true&pagenumbers=false&gridlines=false&top_margin=0.75&bottom_margin=0.75&left_margin=0.25&right_margin=0.25&horizontal_alignment=LEFT&vertical_alignment=TOP&fzr=false&gid=${m[2]}`; window.open(printUrl); } })()
- 投稿日:2020-04-07T20:09:28+09:00
TypeScriptで学ぶデザインパターン〜Factory Method編〜
対象読者
- デザインパターンを学習あるいは復習したい方
- TypeScriptが既に読めるあるいは気合いで読める方
- いずれかのオブジェクト指向言語を知っている方は気合いで読めると思います
- UMLが既に読めるあるいは気合いで読める方
環境
- OS: macOS Mojave
- Node.js: v12.7.0
- npm: 6.14.3
- TypeScript: Version 3.8.3
本シリーズ記事一覧(随時更新)
- TypeScriptで学ぶデザインパターン〜Iterator編〜
- TypeScriptで学ぶデザインパターン〜Adapter編〜
- TypeScriptで学ぶデザインパターン〜Template Method編〜
- TypeScriptで学ぶデザインパターン〜Factory Method編〜
Factory Methodパターンとは
インスタンス生成における大枠を持たせるためのパターンです。
factoryというのが工場という意味であることを知っておくと理解が進みやすいと思います。
サンプルコード
Factory Methodパターンで作られたクラス群がどんなものになるのか確認していきましょう。
今回は、題材として"PCの製造"を想定します。GitHubにも公開しています。
modules/framework/Product.ts
製品を表現する抽象クラスです。
Product.tsexport default abstract class Product { public abstract check(): void; }
check
メソッドのみ定義されています。少なくとも"動作確認ができるモノ"という定義がなされています。modules/framework/Factory.ts
工場を表現する抽象クラスです。
Factory.tsimport Product from '../framework/Product'; export default abstract class Factory { create(id: string): Product { const product: Product = this.createProduct(id); this.registerProduct(product); return product; } protected abstract createProduct(id: string): Product; protected abstract registerProduct(product: Product): void; }"
Product
のインスタンスを製造する工場"という定義がなされています。具体的には、インスタンスを作成(createProduct
)して登録(registerProduct
)するという大枠をcreate
メソッドが規定しています。ちなみに本クラスとそのサブクラスはTemplate Methodパターンになっています。
create
メソッドがテンプレートメソッドになっています。modules/pc/Pc.ts
具体的な製品を表現するクラスです。
Pc.tsimport Product from '../framework/Product'; export default class Pc extends Product { private id: number; constructor(id: number) { super(); console.log('製造番号' + id + 'のPCを製造します。'); this.id = id; } check(): void { console.log('製造番号' + this.id + 'のPCは正常に動作しています。'); } }modules/pc/PcFactory.ts
具体的な工場を表現するクラスです。
PcFactory.tsimport Product from '../framework/Product'; import Factory from '../framework/Factory'; import Pc from './Pc'; export default class PcFactory extends Factory { private pcList: Pc[]; private last: number; constructor(lot: number) { super(); this.pcList = new Array(lot); this.last = 0; } protected createProduct(id: number) { return new Pc(id); } protected registerProduct(product: Product) { this.pcList[this.last] = product as Pc; this.last++; } }単に
Pc
のインスタンスを作成するだけでなく登録処理(registerProduct
)も実現しています。登録といっても配列につめているだけではありますが、インスタンス生成時の手順を規定できているところがポイントです。Main.ts
Factory Methodパターンで作られたクラス群を実際に使う処理が書かれています。
Main.tsimport Factory from './modules/framework/Factory'; import Product from './modules/framework/Product'; import PcFactory from './modules/pc/PcFactory'; const factory: Factory = new PcFactory(3); const pc1: Product = factory.create(1); const pc2: Product = factory.create(2); const pc3: Product = factory.create(3); pc1.check(); pc2.check(); pc3.check();クラス図
ここまでFactory Methodパターンで作られたクラス群を1つずつ確認してきました。次にクラス図を示します。Factory Methodパターンの全体像を整理するのにお役立てください。
- 枠組み
- Product: サンプルコードでは
Product
が対応- Creator: サンプルコードでは
Factory
が対応- 肉付け
- ConcreteProduct: サンプルコードでは
Pc
が対応- ConcreteCreator: サンプルコードでは
PcFactory
が対応解説
最後に、このデザインパターンの存在意義を考えます。
とはいっても、Template Methodの応用なのでTypeScriptで学ぶデザインパターン〜Template Method編〜と同様です。インスタンス生成時の手順を切り出すことができます。
Product
やFactory
に全く手を加えずともPc
やPcFactory
といった具体的な製品や工場を追加していくことができるのです。補足
サンプルコードの実行方法はこちらと同様です。
参考
あとがたり
Laravelなどのアプリケーション開発フレームワークでFactoryは見てきたので、理解が進みやすいデザインパターンだった。
- 投稿日:2020-04-07T19:00:07+09:00
100日後にエンジニアになるキミ - 18日目 - Javascript - JavaScriptの基礎1
今日からプログラミングの方に入って行きます。
本日はJavaScriptというプログラム言語についてのお話です。JavaScriptについて
JavaScriptはwebブラウザーなどで動作するスクリプト言語で
ESxxというバージョンで呼ばれたりしています。6th editionの「ECMAScript2015」以降は年号で呼ばれており
新仕様は従来のものよりも効率的にコードが書けます。JavaScriptはHTMLのスクリプトタグに直接記述するか
jsファイルとして分離しておく方法があります。jsファイルはスクリプトタグの中身を書き
.js
と拡張子を付けて保存します。なおJavaScriptとJavaは別物であるため
略称をJavaにすることは混乱を招きます、間違えないようにしましょう。もしJavaって呼んでいる人がいたら
DS(だっせえ!!!)って言ってあげましょう。略称は
js
です。jkでもjdでもなくwwwwwwJavascriptはブラウザ上ではwebサイトの機能部分を司ります。
PCやスマホなど、ブラウザ側のことをクライアントと読んでいます。一方で、JavaScriptはサーバー上でも動きます。
こちら側はサーバーサイドと読んでいたりします。クライアントサイドだけでなく
Node.jsなどでサーバー側でも処理をさせることができるので
両側を同じ言語で書けることも大きな利点となります。フロントエンドエンジニアになるならば必須
サーバーサイドエンジニアになるとしても覚えておいて
損はないと思います。習得にはやや癖があるので初めて覚える言語としては
難しいかもしれません。もし難しいと感じた場合は、後日行うPython言語の講義を先に見てから
こちらを参照してみてください。JavaScriptの動かし方
HTMLのscriptタグに書く方法
jsファイルに書いてHTMLから呼び出す方法
ブラウザーで直接記述して動かす方法
などがあります。
もっとも簡単な方法として
ブラウザーから動かしてみましょう。GoogleChromeを立ち上げて「右クリック」から「検証」を選択。
検証ツールを開いたらメニューの中から「Console」を選択。
これでスクリプトを直接打ち込む準備ができました。
試しに
1 + 1
とか打ち込んで「エンターキー」を押して実行してみましょう。コードの実行結果が反映されるはずです。
短めのコードの確認は簡単に行うことができます。次にHTMLに打ち込んでみましょう。
sample.html<!DOCTYPE html> <html> <head> <script> console.log('おはよう'); </script> </head> <body> </body> </html>HTMLでスクリプトを実行させるには
スクリプトタグ<script> ~ </script>
が必要です。タグの中に実行するコードを書きます。
ブラウザでみてみましょう。
何も写っていませんね。
上記のコード
console.log
はコンソール上に結果を出力するコードになっています。
コンソールは検証ツールの中にありますので、検証ツールからConsoleをみてみましょう。コンソールの方には結果が反映されています。
Javascriptではコンソールにだけ表示をさせたり
HTMLの方に反映させたり、結果の出力先を制御できます。確かめながらコードを書く際はコンソールに出力すると分かりやすいです。
コードは
headタグ
やbodyタグ
の中に書くことができます。次にjsファイルに分離して書く方法です。
まずはjsファイルを用意します。sample.jsconsole.log('sample.js');HTML側では読み込むjsファイルの指定を行います。
<script type="text/javascript" src="ファイルパス"></script>
jsファイルをHTMLファイルと同じ場所においている場合は
jsファイル名を書くだけで実行できます。sample.html<!DOCTYPE html> <html> <head> <script type="text/javascript" src="sample.js"></script> </head> <body> </body> </html>結果を見てみると
動かし方は自由ですが
WEBサイトとして公開する場合、ScriptファイルとHTMLファイルを分けておくと
複数人で開発する場合、後々都合が良くなります。最初からjsファイルに書き出しておいて、それをHTMLから呼び出すのが良いでしょう。
通常のWEBサイトの場合
CSS
,Javascript
,画像
などのファイル種別ごとに階層を区切って
配置することがよくあります。コードをどこに書くのかは分かりやすい配置を考えながら書くと良いでしょう。
まとめ
JavaScriptの動かし方を把握しておき
すぐに動かせる体制を取っておこう明日からはプログラムの文法に。
君がエンジニアになるまであと82日
作者の情報
乙pyのHP:
http://www.otupy.net/Youtube:
https://www.youtube.com/channel/UCaT7xpeq8n1G_HcJKKSOXMwTwitter:
https://twitter.com/otupython
- 投稿日:2020-04-07T18:40:42+09:00
opencv.jsのダウンロード方法
結論
以下のコマンドでとってこれる。(ver 4.3.0)
curl https://docs.opencv.org/4.3.0/opencv.js -o opencv.jsOpenCV.jsとは
公式から出ているOpenCVのJavascript版です。
https://docs.opencv.org/4.3.0/d5/d10/tutorial_js_root.html
公式での入手方法
一応、公式ではopencv.jsに関しては自前でビルドするように書いてある。
https://docs.opencv.org/4.3.0/d4/da1/tutorial_js_setup.html
これをwslのUbuntu18.04でやってみたが、環境変数周りがうまくいかないのか、ビルドできなかった。↓これのあと
$ source ./emsdk_env.sh Setting environment variables: EMSDK = /mnt/c/Users/hattt/Desktop/emsdk This is rm_wrap: Do not use any options.↓これになってビルドが通らない
$ python ./platforms/js/build_js.py build_js Args: Namespace(build_dir='build_js', build_doc=False, build_flags=None, build_perf=False, build_test=False, build_wasm=False, build_wasm_intrin_test=False, clean_build_dir=False, cmake_option=None, config='/mnt/c/Users/hattt/Desktop/opencv/platforms/js/opencv_js.config.py', config_only=False, disable_wasm=False, emscripten_dir=None, enable_exception=False, opencv_dir='/mnt/c/Users/hattt/Desktop/opencv', simd=False, skip_config=False, threads=False) Cannot get Emscripten path, please specify it either by EMSCRIPTEN environment variable or --emscriptenここで粘ってもよかったのだが、もうめんどくさくなったので公式のサイト上で動いているビルド済みのものをダウンロードすることにした。
意外とダウンロードについて触れている記事がなかったので記述しておく。ダウンロード方法
curlでさくっと取ってくる。
取ってきたもので試してみたが、問題なく動いた。
ビルドしないといけない理由をいまいち理解していないので、不具合もしかしたら使用方法によっては不具合があるのかも。
curlコマンドはWindowsでも最近実装されたので、どのOSでも行けるはず。curl https://docs.opencv.org/4.3.0/opencv.js -o opencv.js
- 投稿日:2020-04-07T18:24:53+09:00
htmlで共通メニュー部分をajaxで対応させる
index.html<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>タイトル</title> <script src="https://code.jquery.com/jquery-3.4.1.js" integrity="sha256-WpOohJOqMqqyKL9FccASB9O0KwACQJpFTUBLTYOVvVU=" crossorigin="anonymous"></script> <script type="text/javascript" src="template.js"></script> </head> <body> <script type="text/javascript">header();</script> <script type="text/javascript">footer();</script> </body> </html>footer.html表示させたい内容を記入template.jsfunction footer(){ $.ajax({ url: "footer.html", cache: false, async: false, dataType: 'html', success: function(html){ document.write(html); } }); } function header(){ $.ajax({ url: "header.html", cache: false, async: false, dataType: 'html', success: function(html){ document.write(html); } }); }
- 投稿日:2020-04-07T17:24:23+09:00
【React】ルーティング画面を作ってみた【react-router-dom】
React Router: Declarative Routing for React.js
このサイトを参考に自分なりにアレンジimport React from 'react'; import { useSelector } from "react-redux"; import { HashRouter as Router, Switch, Route, Redirect } from 'react-router-dom'; import Top from './components/Top'; import Nav from './components/Nav'; import SignIn from './components/SignIn'; import SignUp from './components/SignUp'; export default () => { return ( <Router> <Switch> <Route path="/signin" component={SignIn} /> <Route path="/signup" component={SignUp} /> <PrivateRoute path="/" token={token}> {/* ↓↓↓ ここに書かれているものが children に渡される ↓↓↓ */} <Top /> <Nav /> {/* ↑↑↑ ここに書かれているものが children に渡される ↑↑↑ */} </PrivateRoute> </Switch> </Router> ); } const PrivateRoute = ({ children, ...rest }) => { const token = useSelector(state => state.token); return ( <Route {...rest} render={({ location }) => { if (isAuthenticated(token)) { // 認証済みの場合は、トップページを表示する return children; } if (location.pathname === "/") { // URL が "/" の場合はサインインページを表示する return <SignIn />; } // 未認証で、URL が "/" 以外の場合は "/" にリダイレクトする // URL が "/" となるので、結果的にサインインページが表示される return <Redirect to={{ pathname: "/" }} />; }} /> ); } // 認証判定用のダミー関数 const isAuthenticated = token => token !== null;
- 投稿日:2020-04-07T17:19:43+09:00
Node.js: child_process.fork()で起動したプロセスを子子孫孫殺す方法
本稿では、Node.jsにて、子プロセス、そこから派生した孫プロセス、さらにそこから派生したひ孫プロセス……を、一括して終了する方法を説明します。
※説明にあたって、実行環境はUNIX/Linuxを前提にしています。
子プロセスを殺しても、孫プロセスは死なない
Node.jsのchild_process.fork()は、子プロセスを起動できて便利です。子プロセスの中で、
fork()
を使って、孫プロセスを起動することもでき、さらに、孫プロセスでfork()
して、ひ孫プロセスを、といった具合に子プロセスはネストして起動することができます。起動した子プロセスはsubprocess.kill()で終了することができます。しかし、これは直接の子プロセスしか殺すことができません。どういうことかというと、
- oya.js が ko.js のプロセスを起動する。
- ko.js が mago.js のプロセスを起動する。
- このとき、 oya.js が ko.js のプロセスを
kill()
したとする。- ko.js は終了する。
- mago.js は生存する。 (※このとき、 mago.js はinitプロセスの養子に出され、親pidは1になる)
といった事態が発生します。
孫プロセスが残存するサンプルコード
上のようなシナリオを再現できるコードを書いてみたいと思います。
まず、oya.jsの実装:
oya.jsconsole.log('oya.js: running') // SIGINTを受け付けたとき process.on('SIGINT', () => { console.log('oya.js: SIGINT') process.exit() }) // プロセスが終了するとき process.on('exit', () => { console.log('oya.js: exit') }) // 子プロセスを起動 const ko = require('child_process') .fork(__dirname + '/ko.js') // 3秒後にproc2.jsを終了する setTimeout(() => { console.log('oya.js: ko.jsを終了させてます...') ko.kill('SIGINT') }, 3000) // ko.jsが終了したとき ko.on('exit', () => { console.log('> Ctrl-Cを押してください...') }) // このプロセスがずっと起動し続けるためのおまじない setInterval(() => null, 10000)oya.jsはko.jsを起動し、3秒後にko.jsを終了するコードになっています。ko.jsを
kill()
する際には、SIGINT
シグナルを送るようにしています。Linuxのシグナルについては、ここでは詳しく説明しません。ここでは単にSIGINT
シグナルはプロセス終了を指示するものと考えてください。次に、ko.js:
ko.jsconsole.log('ko.js: running') // SIGINTを受け付けたとき process.on('SIGINT', () => { console.log('ko.js: SIGINT') process.exit() }) // プロセスが終了するとき process.on('exit', () => { console.log('ko.js: exit') }) // 孫プロセスを起動する require('child_process') .fork(__dirname + '/mago.js') // このプロセスがずっと起動し続けるためのおまじない setInterval(() => null, 10000)最後に、mago.js:
mago.jsconsole.log('mago.js: running') // SIGINTを受け付けたとき process.on('SIGINT', () => { console.log('mago.js: SIGINT') process.exit() }) // プロセスが終了するとき process.on('exit', () => { console.log('mago.js: exit') }) // このプロセスがずっと起動し続けるためのおまじない setInterval(() => null, 10000)このコードを実行してみます:
$ node oya.js oya.js: running ko.js: running mago.js: running oya.js: ko.jsを終了させてます... ko.js: SIGINT ko.js: exit > Ctrl-Cを押してください...3秒後にこのような出力がされ、oya.jsがko.jsを
kill()
し、ko.jsが終了したことが確認できます。一方、mago.jsはまだ
SIGINT
を受け取っていませんし、終了もしておらず、残存しています。ここで、Ctrl-Cを押すと、oya.jsとmago.jsに
SIGINT
が送信されます:... > Ctrl-Cを押してください... ^Coya.js: SIGINT mago.js: SIGINT mago.js: exit oya.js: exitこのタイミングではじめて、mago.jsが終了することが分かります。
感想を言うと、ko.jsに
SIGINT
を送信したら、mago.jsにもSIGINT
が伝搬されていくものと誤解していたので、この結果は意外でした。起動したプロセスを子子孫孫殺す方法
では、起動した子プロセスを
kill()
したタイミングで、孫プロセスも終了になるようにするにはどうしたらいいのでしょうか? それについて、ここで説明したいと思います。プロセスグループ = 「世帯」
まず、Linuxのプロセスの基本として、プロセスグループというものがあります。これはプロセスの「世帯」のような概念で、親プロセス、子プロセス、孫プロセスをグループ化するものです。たとえば、Bashでnodeプロセスであるoya.jsを起動すると、そこから
fork()
したko.jsやmago.jsは、同じプロセスグループに属し、同一のグループIDが与えられます。
ps
コマンドでグループID(GPID)を確認すると、現に同じグループIDが3つのnodeプロセスに割り当てられていることが分かります:$ ps -xo pid,ppid,pgid,command | grep node | grep .js PID PPID GPID COMMAND 17553 3528 17553 node oya.js 17554 17553 17553 node ko.js 17555 17554 17553 node mago.jsこの結果をよく見ると分かりますが、GPIDはoya.jsのプロセスID(PID)と同じです。つまり、親のPIDが子孫のGPIDになるわけです。
プロセスを「世帯」ごと殺す方法
Node.jsでは、グループIDを指定して、プロセスを終了させることができます。やりかたは、process.kill()にGPIDを渡すだけです。このとき、与える値は負の数にしてあげます。正の数を渡してしまうと、プロセスグループではなく個別のプロセスを
kill()
するだけになるので注意です。const groupId = 123456 process.kill(-groupId, 'SIGINT')ちなみに、シェルでCtrl-Cを押したときに、親・子・孫がもろとも終了されるのは、Ctrl-Cが送る
SIGINT
が親プロセスに対してではなく、プロセスグループに対して送られているからです。(要出典)detached = 別世帯を作る
今回やりたいことは、oya.jsのプロセスは生かしつつ、ko.jsとmago.jsを
kill()
したいことです。しかし、GPIDを指定したkill()
では、oya.jsまで終了してしまいます。三者とも同じGPIDだからです:PID PPID GPID COMMAND 17553 3528 17553 node oya.js 17554 17553 17553 node ko.js 17555 17554 17553 node mago.jsko.jsとmago.jsを別のGPIDを割り振る必要があります。それをするには、
fork()
のオプションにdetached
を指定します。oya.js// 子プロセスを起動 const ko = require('child_process') .fork(__dirname + '/ko.js', [], {detached: true})これを指定すると、ko.jsとmago.jsがいわば「別世帯」になり、別のプロセスグループに属するようになります。GPIDもoya.jsとは別のものが割り当てられているのが確認できます:
$ ps -xo pid,ppid,pgid,command | grep node | grep .js PID PPID GPID COMMAND 21404 3528 21404 node oya.js 21405 21404 21405 node ko.js 21406 21405 21405 node mago.jsプロセスを子子孫孫殺すoya.jsの完成形
以上を踏まえて、oya.jsを子プロセス、孫プロセスを一括して終了できるように変更すると次のようになります:
oya.jsconsole.log('oya.js: running') // SIGINTを受け付けたとき process.on('SIGINT', () => { console.log('oya.js: SIGINT') process.exit() }) // プロセスが終了するとき process.on('exit', () => { console.log('oya.js: exit') }) // 子プロセスを起動 const ko = require('child_process') .fork(__dirname + '/ko.js', [], {detached: true}) // 重要な変更箇所! // 3秒後にko.jsを終了する setTimeout(() => { console.log('oya.js: ko.jsを終了させてます...') process.kill(-ko.pid, 'SIGINT') // 重要な変更箇所! }, 30000) // ko.jsが終了したとき ko.on('exit', () => { console.log('> Ctrl-Cを押してください...') }) // このプロセスがずっと起動し続けるためのおまじない setInterval(() => null, 10000)最後に、このoya.jsを実行して、ko.jsとmago.jsが一緒に終了しているか確認してみましょう:
$ node oya.js oya.js: running ko.js: running mago.js: running oya.js: ko.jsを終了させてます... mago.js: SIGINT ko.js: SIGINT mago.js: exit ko.js: exit > Ctrl-Cを押してください... ^Coya.js: SIGINT oya.js: exit期待通り、ko.jsとmago.jsは同じタイミングで
SIGINT
を受け取り終了しています。oya.jsはCtrl-Cを押すまで生存していることも分かります。以上、Node.jsの
child_process.fork()
で起動したプロセスを子子孫孫殺す方法についての説明でした。
- 投稿日:2020-04-07T15:59:19+09:00
Nuxt.js Vuetify2系でFontAwesome5(無料)とmaterial-icon、Font Awesome SVGを導入する
はじめに
Nuxt.jsでVuetify2系を使っている場合に、FontAwesome5(無料)を導入する方法がわからなくて、調べちゃったので備忘録として残します。
以下の手順でGithubで公開しています。
https://github.com/idani/nuxt-vuetify-fontAwesome5
初期状態
Nuxt.jsでVuetifyを導入した初期プログラムを作成しました。
ダークモードは見づらいのでオフにして、
index.vue
を以下のように修正しています。<template> <v-layout column justify-center align-center> <v-flex xs12 sm8 md6> <v-card> <v-row justify="space-around"> <v-col> <v-icon>mdi-domain</v-icon> //デフォルトのMaterial Design Icons <v-icon>home</v-icon> //Material Icons <v-icon>fas fa-lock</v-icon> //Font Awesome 5 </v-col> </v-row> </v-card> </v-flex> </v-layout> </template> <script> export default {} </script>
v-icon
で、Material Design Icons
フォント(以後MDI)はデフォルトで表示されました。
Material Icons
フォント(以後MI)、Font Awesome 5
フォント(fa5)は表示されません。FontAwesome5の導入
vuetify
のFont Awesome 5 アイコンのインストールを参考にしています。まずは、フォントの導入
yarn add @fortawesome/fontawesome-free -D
Pluginsフォルダに
vuetify.js
を作成して、ドキュメントの通りに記載src/plugins/vuetify.js// src/plugins/vuetify.js import '@fortawesome/fontawesome-free/css/all.css' // Ensure you are using css-loader import Vue from 'vue' import Vuetify from 'vuetify/lib' Vue.use(Vuetify) export default new Vuetify({ icons: { iconfont: 'fa', }, })
nuxt.config.js
に先程作成したplugins/vuetify
を追記します。nuxt.config.js~~~省略 /* ** Global CSS */ css: [], /* ** Plugins to load before mounting the App */ plugins: ['plugins/vuetify'], /* ** Nuxt.js dev-modules */ buildModules: [ // Doc: https://github.com/nuxt-community/eslint-module '@nuxtjs/eslint-module', '@nuxtjs/vuetify' ], /* ~~~省略FontAwesome5のフォントが表示されるようになりました。
material-iconの導入
Material Iconsのインストールを参考に進めます。
material-icon
をインストールします。yarn add material-design-icons-iconfont -D
plugins/vuetify.js
を以下のように修正します。src/plugins/vuetify.js// src/plugins/vuetify.js import '@fortawesome/fontawesome-free/css/all.css' // Ensure you are using css-loader import 'material-design-icons-iconfont/dist/material-design-icons.css' import Vue from 'vue' import Vuetify from 'vuetify/lib' Vue.use(Vuetify) export default new Vuetify({ icons: { iconfont: ['fa', 'md'] } })Font Awesome SVG アイコンのインストール
Font Awesome SVG アイコンのインストールを参考にしていきます。
まずは、インストール済みの
Font Awesome 5
を削除します。yarn remove @fortawesome/fontawesome-free -D
plugins/vuetify.js
も以下のよう修正します。plugins/vuetify.js// src/plugins/vuetify.js import 'material-design-icons-iconfont/dist/material-design-icons.css' import Vue from 'vue' import Vuetify from 'vuetify/lib' Vue.use(Vuetify) export default new Vuetify({ icons: { iconfont: ['md'] } })FontAwesome5を使った時計アイコンが非表示になりました。
FontAwesome5 SVGをインストールします。
yarn add @fortawesome/fontawesome-svg-core @fortawesome/vue-fontawesome @fortawesome/free-solid-svg-icons -D
plugins.vuetify.js
を以下のように書き換えます。plugins/vuetify.js// src/plugins/vuetify.js import 'material-design-icons-iconfont/dist/material-design-icons.css' import Vue from 'vue' import Vuetify from 'vuetify/lib' import { library } from '@fortawesome/fontawesome-svg-core' import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' import { fas } from '@fortawesome/free-solid-svg-icons' Vue.component('font-awesome-icon', FontAwesomeIcon) // Register component globally library.add(fas) // Include needed icons Vue.use(Vuetify) export default new Vuetify({ icons: { iconfont: ['md', 'faSvg'] } })最後に、SVG版のフォントは
v-icon
ではなく、font-awesome-icon
タグで使うようです。
以下のようにfont-awesome-icon
を追加します。index.vue<template> <v-layout column justify-center align-center> <v-flex xs12 sm8 md6> <v-card> <v-row justify="space-around"> <v-col> <v-icon>mdi-domain</v-icon> <v-icon>home</v-icon> <v-icon>fas fa-lock</v-icon> <font-awesome-icon :icon="['fas', 'lock']"></font-awesome-icon> </v-col> </v-row> </v-card> </v-flex> </v-layout> </template> <script> export default {} </script>以下のように表示されます。
以下のURLで、
v-icon
で表示する方法も書かれています。
私自身が、これ以上必要としないので、以下のURLを出しておしまいといたします。
- 投稿日:2020-04-07T15:51:14+09:00
カルーセル実装で気をつけたこと
前提条件
- React環境で動くこと
- カルーセルを入れることによりパフォーマンスが低下しないこと
実装
props
- 表示領域に表示するコンテンツ数
- keyExtractor
- カルーセルに表示するデータ(array)
- カルーセルに表示するコンポーネント(render props)
ライブラリの導入検討
- react-slick(https://react-slick.neostack.com/ )について導入検討したが、以下理由で断念。
- パフォーマンスは問題なし
- リンク数が奇数だった場合にうまく挙動しない
- 表示数が端数の場合うまく挙動しない
- 投稿日:2020-04-07T15:51:14+09:00
WIP: カルーセル実装で気をつけたこと
前提条件
- React環境で動くこと
- カルーセルを入れることによりパフォーマンスが低下しないこと
設計
- 表示数が端数の場合、端数分だけ遷移するように調整する
ex.表示領域に表示する数が4なのに対し総数が5の場合、1つ分だけスクロールさせる- 画面幅をリサイズした際にカルーセルの表示サイズ、移動距離を可変させる
- SPページではスクロール表示に切り替え
実装
カルーセルコンポーネント全体
import React, { useState, useEffect, useRef } from 'react' import styled from '@emotion/styled' import { PressableView, Row, View } from '../../Foundations' import { layout, motion, color } from '../../../style' import { Ticker } from '../../../lib/client/Ticker' const MD_PADDING = layout.grid * 4 //画像間のpadding interface Props<T> { displayContentsNum: number keyExtractor: (arg: T, index: number) => string list: T[] render: (arg: T) => React.ReactNode } // カルーセルの移動距離を計算 const createTranslateXByRawWidth = ( width: number, displayContentsNum: number, mdPadding: number, nowRowNum: number, isRemainder: boolean, remainder: number ) => { const bodyWidth = -width - mdPadding const remainderWidth = (bodyWidth / displayContentsNum) * remainder const val = isRemainder ? bodyWidth * (nowRowNum - 1) + remainderWidth // eslint-disable-line no-mixed-operators : bodyWidth * nowRowNum // eslint-disable-line no-mixed-operators return `translateX(${val}px)` } const CarouselComponent = <T extends any>(props: Props<T>) => { const { displayContentsNum, keyExtractor, list, render } = props const mdPadding = MD_PADDING const totalContentsNum = list.length const remainder = totalContentsNum % displayContentsNum const totalRowNum = Math.ceil(totalContentsNum / displayContentsNum) - 1 const isLessThanTotal = totalRowNum > 0 const elem = useRef<HTMLDivElement>(null) const scrollElem = useRef<HTMLDivElement>(null) const [bodyWidth, setBodyWidth] = useState(0) const [isRemainder, setIsRemainder] = useState(false) const [rowIndex, setRowIndex] = useState(0) const [leftBtnIsActive, setLeftBtnIsActive] = useState(false) const [rightBtnIsActive, setRightBtnIsActive] = useState(isLessThanTotal) const [animationIsActive, setAnimationIsActive] = useState(true) const rowTransform = createTranslateXByRawWidth( bodyWidth, displayContentsNum, mdPadding, rowIndex, isRemainder, remainder ) const resetScroll = () => { if (scrollElem.current) { scrollElem.current.scrollLeft = 0 } } useEffect(() => { const getWidthTicker = new Ticker({ onTicking: () => { if (elem.current) { setBodyWidth(elem.current.clientWidth) } }, onStart: () => { setAnimationIsActive(false) }, onEnd: () => { setAnimationIsActive(true) }, throttle: 30, }) const handler = (e: Event) => { // Tickingぜす、また2重でわざわざifで判定しているのは、リサイズイベントの最初で確実に走らせて、 // 二回め以降window.innerWidthプロパティを参照したくないので細かく制御しています。 if (scrollElem.current) { const isScrollLeftNotZero = scrollElem.current.scrollLeft !== 0 if (isScrollLeftNotZero) { const isOverMd = window.innerWidth > layout.breakpoint.md if (isOverMd) { resetScroll() } } } getWidthTicker.dispatch(e) } window.addEventListener('resize', handler) return () => window.removeEventListener('resize', handler) }, []) useEffect(() => { setAnimationIsActive(false) setIsRemainder(false) setRowIndex(0) resetScroll() setLeftBtnIsActive(false) setRightBtnIsActive(isLessThanTotal) requestAnimationFrame(() => setAnimationIsActive(true)) }, [list]) const setBtnIsActive = (rowNum: number) => { const newLeftBtnIsActive = rowNum !== 0 const newRightBtnIsActive = rowNum !== totalRowNum setLeftBtnIsActive(newLeftBtnIsActive) setRightBtnIsActive(newRightBtnIsActive) } const onButtonLeftClick = () => { if (leftBtnIsActive) { const nextRowNum = rowIndex - 1 const isNotRemaindable = nextRowNum === 0 && remainder setRowIndex(nextRowNum) setBtnIsActive(nextRowNum) if (elem.current) { setBodyWidth(elem.current.clientWidth) } if (isNotRemaindable) { setIsRemainder(false) } } } const onButtonRightClick = () => { if (rightBtnIsActive) { const nextRowNum = rowIndex + 1 const isRemaindable = totalRowNum === nextRowNum && remainder setRowIndex(nextRowNum) setBtnIsActive(nextRowNum) if (elem.current) { setBodyWidth(elem.current.clientWidth) } if (isRemaindable) { setIsRemainder(true) } } } return ( <View ref={elem} onSwipeRightAuto={onButtonLeftClick} onSwipeLeftAuto={onButtonRightClick}> <CarouselButton moveDirection="left" onClick={onButtonLeftClick} isActive={leftBtnIsActive} animationIsActive={animationIsActive} /> <CarouselButton moveDirection="right" onClick={onButtonRightClick} isActive={rightBtnIsActive} animationIsActive={animationIsActive} /> <ContentsWrap ref={scrollElem}> <ContentsRow style={{ transform: rowTransform }} animationIsActive={animationIsActive}> {list.map((data, index) => ( <BoxWrap key={keyExtractor(data, index)} displayContentsNum={displayContentsNum} mdPadding={mdPadding}> {render(data)} </BoxWrap> ))} </ContentsRow> </ContentsWrap> </View> ) } export const Carousel = React.memo(CarouselComponent) as typeof CarouselComponent const Button = styled(PressableView)({ display: 'none', display: 'flex', position: 'absolute', top: '50%', transform: 'translateY(-50%)', width: 48, height: 48, border: `solid 1px ${color.ui.bgDark}`, borderRadius: '50%', background: color.pallet.surface, zIndex: 10, '&::after': { position: 'absolute', top: 0, bottom: 0, margin: 'auto', content: '""', width: 12, height: 12, borderRight: `2px solid ${color.ui.anotherArea.onWhite}`, borderTop: `2px solid ${color.ui.anotherArea.onWhite}`, }, ':hover': { borderColor: color.pallet.accent, '&::after': { borderColor: color.pallet.accent, }, }, }, }) type CarouselButtonProps = { moveDirection: 'left' | 'right' isActive: boolean animationIsActive: boolean } const CarouselButton = styled(Button)<CarouselButtonProps>(({ moveDirection, isActive, animationIsActive }) => { const dirStyle = (dir => { switch (dir) { case 'left': { return { left: -32, '&::after': { left: 19, transform: 'rotate(225deg)', }, } } case 'right': { return { right: -32, '&::after': { right: 19, transform: 'rotate(45deg)', }, } } default: return {} } })(moveDirection) return { ...dirStyle, [layout.mqOfBreakpoint.md]: { transition: animationIsActive ? `all ${motion.duration.expand}ms ${motion.curves.base}` : 'unset', display: isActive ? 'flex' : 'none', '&::after': { transition: animationIsActive ? `all ${motion.duration.expand}ms ${motion.curves.base}` : 'unset', }, }, } }) const ContentsWrap = styled(View)({ overflowX: 'scroll', overflow: 'hidden', }, }) type ContentsRowProps = { animationIsActive: boolean } const ContentsRow = styled(Row)<ContentsRowProps>(({ animationIsActive }) => ({ flexWrap: 'nowrap', transform: 'none !important', }, transition: animationIsActive ? `transform ${motion.duration.base}ms ${motion.curves.base}` : 'unset', willChange: 'transform', }, })) type BoxWrapProps = { displayContentsNum: number mdPadding: number } const BoxWrap = styled(Row)<BoxWrapProps>(({ displayContentsNum, mdPadding }) => ({ marginRight: layout.grid, flexGrow: 0, ':first-of-type': { marginLeft: layout.grid * 2, }, ':last-of-type': { marginRight: 0, paddingRight: layout.grid * 2, }, width: `calc((100% - ${mdPadding * (displayContentsNum - 1)}px) / ${displayContentsNum})`, maxWidth: `calc((100% - ${mdPadding * (displayContentsNum - 1)}px) / ${displayContentsNum})`, marginRight: mdPadding, ':first-of-type': { marginLeft: 0, }, ':last-of-type': { paddingRight: 0, }, }, }))props
- 表示領域に表示する数
- keyExtractor
- カルーセルに表示するデータ(array)
- カルーセルに表示するコンポーネント(render props)
実装中の気づきなど
画像間のpaddingは固定で持つ
- 同サイトに使うカルーセルであればデザインが統一されるため固定で持ったほうがいい
- デザイナーと調整してどうしても変更が必要になった場合に可変できるよう調整する
画像幅については画像自体に持たせずにCarouselComponent内でcalcで算出する
- 画像幅は下記計算にてcalcで算出。
// mdPadding ・・・画像間のpadding // displayContentsNum - 1 ・・・表示領域上の画像間のpaddingの個数 // displayContentsNum ・・・表示領域上の画像の個数 width: `calc((100% - ${mdPadding * (displayContentsNum - 1)}px) / ${displayContentsNum})`, maxWidth: `calc((100% - ${mdPadding * (displayContentsNum - 1)}px) / ${displayContentsNum})`,カルーセルの表示位置は position: left ではなく transform: translateX() をつかって動かす
- カルーセルの表示位置を
position: left
で持つとパフォーマンス性が低く、カルーセル自体の動きもなめらかにならないため、transform: translateX()
で使うwillChange: 'transform'
を入れることにより、よりなめらかな動きを再現できる
参考:https://www.webprofessional.jp/achieve-60-fps-mobile-animations-with-css3/
transform: translateX()
でカルーセル移動させた場合
カルーセルの移動距離の計算
- 移動距離については以下関数で計算。
const createTranslateXByRawWidth = ( width: number, displayContentsNum: number, mdPadding: number, nowRowNum: number, isRemainder: boolean, remainder: number ) => { const bodyWidth = -width - mdPadding const remainderWidth = (bodyWidth / displayContentsNum) * remainder const val = isRemainder ? bodyWidth * (nowRowNum - 1) + remainderWidth // eslint-disable-line no-mixed-operators : bodyWidth * nowRowNum // eslint-disable-line no-mixed-operators return `translateX(${val}px)` }
- 画面幅がリサイズされた際に関数が発火するようにする
レンダリング時のみアニメーションを無効化する
requestAnimationFrameを使う
https://developer.mozilla.org/ja/docs/Web/API/Window/requestAnimationFrame
- requestAnimationFrameはlayoutとpaintの間で実行される
- 下記手順で再現
// アニメーション実行有無 const [animationIsActive, setAnimationIsActive] = useState(true) useEffect(() => { //アニメーションの実行をfalseにする setAnimationIsActive(false) // 表示を初期化 setCarouselPosition(0) setNowContentsNum(displayContentsNum) setLeftBtnIsActive(false) setRightBtnIsActive(totalContentsNum > displayContentsNum) // アニメーション実行をtrueにする requestAnimationFrame(setAnimationIsActive(true)) }, [totalContentsNum])ライブラリの導入検討
- react-slick(https://react-slick.neostack.com/ )について導入検討したが、以下理由で断念。
- パフォーマンスは問題なし
- リンク数が奇数だった場合にうまく挙動しない
- 表示数が端数の場合うまく挙動しない
- 投稿日:2020-04-07T15:20:25+09:00
Componentの外部をクリックしたら発火するCustom Hooks【React】
概要
Componentの外部をクリックしたときに発火するイベントを管理したい。
コード
Componentの外部をクリックしたら発火するCustom Hooks作りました。
export const useOutsideClickEvent = ( ref: MutableRefObject<any>, onClick: () => void ) => { const clickListener = useCallback( (e: MouseEvent) => { if ((ref?.current as any).contains(e.target)) { return; } onClick(); }, [ref.current, onClick] ); useEffect(() => { document.addEventListener('click', clickListener); return () => { document.removeEventListener('click', clickListener); }; }, []); };こんな感じで使うと、refで指定されたdivの外部をクリックされたときに
setIsOpen(false)
が実行されます。const Dialog: FC = () => { const ref = useRef(null); const [isOpen, setIsOpen] = useState<boolean>(false); useOutsideClickEvent(ref, () => setIsOpen(false)); return ( <div ref={ref}> ... </div> ) }注意
ClickEventの実行時にrefで指定したDOMの中に要素が入っていないといけないので、動的にコンテンツを書き換える場合は注意が必要です。
if ((ref.current as any).contains(e.target)) {
- 投稿日:2020-04-07T14:31:13+09:00
Vue.js クラスとスタイルのバインディング
クラスのデータバインディングの基本
- クラスを動的に変更するには
v-bind:class
を利用する- クラスの付け替えが楽になる
例<p v-bind:class="{クラス名: 真偽値}">jsvar app = new Vue({ el: '#app', data: { isLarge: true // 真偽値を定義 } })html<!-- isLargeがtrueなので、largeクラスが適用される--> <p v-bind:class="{large: isLarge}">Hello Vue.js!</p>複数のクラスを動的に切り替える
- カンマ区切りで、複数のクラスを指定できる。
html<p v-bind:class="{クラス名: 真偽値, クラス名: 真偽値, ...}">html<p v-bind:class="{large: isLarge, 'text-danger': hasError}">Hello Vue.js!</p>
'text-danger'
のように、クラス名にハイフンを含む場合は''(シングルクォート)
で囲む。プレーンなクラス属性と共存させる
一般的なクラス定義と、データバインディングしたクラスは共存できる。
例<p class="クラス名" v-bind:class="{クラス名: 真偽値, クラス名: 真偽値, ...}">配列構文によるクラスのデータバインディング
v-bind:class
にクラスのリストを渡す事で展開できる例<p v-bind:class="[プロパティ名, プロパティ名, ...]">jsdata: { // dataにクラス名を持ったプロパティを定義 largeClass: 'large', dangerClass: 'text-danger' }html<!-- クラスを適用するにはブランケットで囲む --> <p v-bind:class="[largeClass, dangerClass]">Hello Vue.js!</p> <!-- <p class="large text-danger">Hello Vue.js!</p>オブジェクトのデータを利用する
複数のクラスをテンプレート(HTML)に書くと見通しが悪くなるので、オブジェクトで定義して
v-bind:class
に渡すことができる。例<p v-bind:class="オブジェクト">jsdata: { classObject: { //クラスのオブジェクトを定義 large: true, 'text-danger': true } }html<!-- largeクラスとtext-dangerクラスが適用される --> <p v-bind:class="classObject">Hello Vue.js!</p>クラスの条件に三項演算子を使う
html<!-- isTrueがtrueの時、trueClassクラスが適用される --> <p v-bind:class="isTrue ? trueClass : '' ">Hello Vue.js!</p>三項演算子の結果に関わらず、常に適用したいプロパティがある場合は
[]
で囲んでカンマで区切る。html<!-- fooClassは常に適用される --> <span v-bind:class="[isTrue ? trueClass : '', fooClass ]">インラインスタイルのデータバインディング
- インラインスタイルを動的に変更するには
v-bind:style
を利用する
data
オプションに、インラインスタイルで使用したいプロパティをセットjsdata: { color: 'blue', fontSize: 48 }
v-bind:style
でデータバインディングさせたインラインスタイルを適用できる。CSSのプロパティ名は
キャメルケース
にする必要がある。html<p v-bind:style="{color: color, fontSize: fontSize + 'px'}">Hello Vue.js!</p> <!-- <p style="color: blue; font-size: 48px;">Hello Vue.js!</p> -->
ケバブケース
で書きたいときは''(シングルクォート)
で囲うhtml<p v-bind:style="{'font-size': fontSize + 'px'}"></p>また、クラスのようにオブジェクトを渡すこともできる。
jsdata: { styleObject: { // オブジェクトを定義 color: 'blue', fontSize: '48px' } }html<p v-bind:style="styleObject"></p> <!-- <p style="color: blue; font-size: 48px;">Hello Vue.js!</p> -->
- 投稿日:2020-04-07T12:07:57+09:00
【Rails】Ajaxを用いた非同期投稿の実装
目標
開発環境
・Ruby: 2.5.7
・Rails: 5.2.4
・Vagrant: 2.2.7
・VirtualBox: 6.1
・OS: macOS Catalina前提
ログイン機能を実装済み。
ログイン機能 ➡︎ https://qiita.com/matsubishi5/items/5bd8fdd45af955cf137d
投稿機能の実装
テーブル
schema.rbActiveRecord::Schema.define(version: 2020_04_05_115005) do create_table "books", force: :cascade do |t| t.string "title" t.text "body" t.integer "user_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false end create_table "users", force: :cascade do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" t.integer "sign_in_count", default: 0, null: false t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" t.string "current_sign_in_ip" t.string "last_sign_in_ip" t.string "name" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true end endモデル
user.rbclass User < ApplicationRecord has_many :books, dependent: :destroy endbook.rbclass Book < ApplicationRecord belongs_to :user endルーティング
routes.rbRails.application.routes.draw do devise_for :users resources :users resources :books endコントローラー
books.controll.rbclass BooksController < ApplicationController def create book = Book.new(book_params) book.user_id = current_user.id if book.save redirect_to book, notice: "successfully created book!" else @books = Book.all.order(created_at: :desc) #降順 render 'index' end end def index @book = Book.new @books = Book.all.order(created_at: :desc) #降順 end private def book_params params.require(:book).permit(:title, :body) end endビュー
books/index.html.erb<% if @book.errors.any? %> <h2><%= @book.errors.count %>error</h2> <div> <ul> <% @book.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %> <%= form_with model: @book do |f| %> <div class="field row"> <%= f.label :title %> <%= f.text_field :title, class: "col-xs-12 book_title" %> </div> <div class="field row"> <%= f.label :body %> <%= f.text_area :body, class: "col-xs-12 book_body" %> </div> <div class="actions row"> <%= f.submit class: "btn btn-primary col-xs-12" %> </div> <% end %> <h2>Books index</h2> <table class="table table-hover table-inverse"> <thead> <tr> <th></th> <th>Title</th> <th>Opinion</th> </tr> </thead> <tbody> <% @books.each do |book| %> <tr> <td> <%= link_to book.user do %> <%= attachment_image_tag book.user, :profile_image, :fill, 50, 50, fallback: "no-image-mini.jpg" %> <% end %> </td> <td><%= link_to book.title, book %></td> <td><%= book.body %></td> </tr> <% end %> </tbody> </table>非同期機能の実装
1. jQueryの導入
Gemfilegem 'jquery-rails'ターミナル$ bundleapplication.js//= require rails-ujs //= require activestorage //= require turbolinks //= require jquery //= require_tree .2. booksコントローラーのcreateアクションを編集
books.controller.rbclass BooksController < ApplicationController #ローカル変数をインスタンス変数に変更 def create @book = Book.new(book_params) @book.user_id = current_user.id unless @book.save render 'index' end end end3. フォームを編集する
非同期投稿を行うときは必ず「form_with」を使用する。
※「form_for」「form_tag」だとレイアウトが崩れた。
books/index.html.erb<!-- form_withの引数に「remote: true」を追記 --> <%= form_with model: @book, remote: true do |f| %> <div class="field row"> <%= f.label :title %> <%= f.text_field :title, class: "col-xs-12 book_title" %> </div> <div class="field row"> <%= f.label :body %> <%= f.text_area :body, class: "col-xs-12 book_body" %> </div> <div class="actions row"> <%= f.submit class: "btn btn-primary col-xs-12" %> </div> <% end %>4. 投稿一覧を編集する
books/index.html.erb<tbody class="new_book"> <!-- 投稿一覧の親要素にクラスをつける --> <% @books.each do |book| %> <%= render 'books', book: book %> <!-- 投稿一覧をパーシャル化 --> <% end %> </tbody>books/_books.html.erb<tr> <td> <%= link_to book.user do %> <%= attachment_image_tag book.user, :profile_image, :fill, 50, 50, fallback: "no-image-mini.jpg" %> <% end %> </td> <td><%= link_to book.title, book %></td> <td><%= book.body %></td> </tr>5. JavaScriptファイルの作成
books/create.js.erb$(".book_title").val(''); $(".book_body").val(''); $(".new_book").prepend("<%= j(render 'books', book: @book) %>");
$(".new_book")
➡︎ 「4」で付けたクラスを指定
.prepend("<%= j(render 'books', book: @book) %>");
➡︎ 既存投稿の先頭に新規投稿を表示
$(".book_title").val('');
$(".book_body").val('');
➡︎ 入力フォームを空にする参考サイト
- 投稿日:2020-04-07T11:26:07+09:00
Jest encountered an unexpected token解決法
react-native-cameraを使ったアプリのテストをJestでする際、こんなエラーに遭遇した
TypeError: Cannot read property 'Aspect' of undefined at Object.<anonymous> (node_modules/react-native-camera/src/Camera.js:425:113) at Object.<anonymous> (node_modules/react-native-camera/src/index.js:3:38)他にもreact-native-unimodulesで
Jest encountered an unexpected tokenなんかが出たりする
結論から先に書くと、これらのエラーは
jest.mock('{モジュール名}', () => '{クラス名}');でモック化すると解決した。
react-native-cameraなら
jest.mock('react-native-camera', () => 'Camera');となる。
Jest公式によると、react-nativeに組み込まれたJestプリセットにはデフォルトのモックが付属しているものの、いくつかのコンポーネントにはそれが無く、ネイティブコードに依存しているためマニュアルでモック化する必要があるとかなんとか
正直Jestのモックとはそもそも何ぞや?というところからわかっておらず現状おまじないと化してるので、後で調べておきたい
- 投稿日:2020-04-07T10:04:21+09:00
Nuxt.jsでhead要素・404ページの設定を行う
前提
$ npx create-nuxt-app プロジェクト名
でモジュールをインストールして開始すると、nuxt.config.js
ファイルが生成されます。
このファイルで行う設定内容まとめです。nuxt.config.js ファイル
export default { ...
以下をJSON形式で記述します。- アプリケーション共通設定を行う事ができます。
head要素の記述
export default { ... head: { title: "タイトル", meta: [ { charset: 'utf-8' }, { name: 'viewport', content: 'width=device-width, initial-scale=1' }, { hid: 'description', name: 'description', content: process.env.npm_package_description || '' }, /* OGP設定も可能です */ { property: 'og:title', content: 'OGPタイトル'}, { property: 'og:type', content: 'website'}, { property: 'og:url', content: 'OGP URL'}, { property: 'og:image', content: 'OGP image'}, ], link: [ { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }, /* CDN等 */ { rel: 'stylesheet', type: '', href: 'https://fonts.googleapis.com/css?family=Sen&display=swap' } ] }, ... }ローディングカラーの変更
- Nuxt.jsでは、初期状態でローディングアニメーションが設定されています。
- 好みのカラーへ変更が可能です。
export default { ... loading: { color: '#カラーコード' }, ... }404ページの設置
- Nuxt.jsでは、
/pages
配下で自動的にルーティングが行えます。- 存在しないURLへのアクセスが行われた場合の404リクエストで表示するページ設定を行う事ができます。
export default { ... router: { extendRoutes (routes, resolve) { routes.push({ name: 'custom', path: '*', component: resolve(__dirname, './components/404.vue') }) } }, ... }
- 投稿日:2020-04-07T09:23:15+09:00
npmパッケージのvulnerability対応フロー
概要
(GitHub等が親切に"We found potential security vulnerabilities in your dependencies."のように通知してくれるので便利)
対応フロー
ざっくり全体像は以下のとおり。
①最新のコードを取得する
いうまでもないが、手元のコードは最新のコードにしておく
git pull最新であることが確認された
Already up to date.ちなみに、今回は自作プロジェクト(https://github.com/riversun/simple-date-format) で実際にハンズオンした
②プロジェクトが使用しているnpmパッケージが最新かどうか確認する
脆弱性対応の前に、いま自分のプロジェクトが使っているnpmパッケージを最新のものにする。
プロジェクトが使っているnpmパッケージが最新かどうかは
npm outdated
コマンドで確認することができ、最新バージョンがある場合は、その情報を表示してくれるnpm outdatedするとこのように、最新版が存在するパッケージを一覧表示してくれる
いくつかのパッケージで最新版があるようだパッケージをアップデートする
package.jsonに記載されるnpmパッケージ一覧は以下のようになっている。
(「^」がついたキャレット表記についてはこちらで説明)package.json抜粋"@babel/core": "^7.8.4", "@babel/preset-env": "^7.8.4", "babel-jest": "^25.1.0", ...パッケージのバージョンの付け方セマンティックバージョニングに従っている。
セマンティックバージョニングにおいて、メジャーバージョン、マイナーバージョンは以下の意味をもっている。
これをふまえ、「パッケージを最新にする」には2種類の方法があるといえる。(パッチバージョンを上げるというのもいれれば3種類だが本筋にあまり影響ないので割愛)
- ②-1 マイナーバージョンまで最新にする
- ②-2 メジャーバージョンまで最新にする
②-1をとるか ②-2をとるかはポリシー次第だがメジャーバージョンを最新にする場合はAPIの後方互換が無いことを想定しておいたほうがいい。
②-1 マイナーバージョンまで最新にする場合
キャレット表記「^」つきで定義されたパッケージのバージョンを、マイナーバージョンまでを最新にするにはnpm updateコマンドで可能
npm update以下のように、マイナーバージョンまでが最新になった
(ただし、メジャーバージョンは最新になっていない。)+ jest@25.2.7 + babel-loader@8.1.0 + cross-env@7.0.2 + @babel/core@7.9.0 + babel-jest@25.2.6 + @babel/preset-env@7.9.0 + webpack@4.42.1 added 36 packages from 17 contributors, removed 6 packages, updated 140 packages, moved 1 package and audited 263490 packages in 18.399s
②-2 メジャーバージョンまで最新にする場合
npm update
では、マイナーバージョンまでしかアップデートしてくれなかったが、ここではメジャーバージョンまで容赦なくアップデートしてくれるパッケージnpm-check-updatesを導入する以下のようにしてnpm-check-updatesをインストールする
npm install -g npm-check-updatesnpm-check-updatesをインストールすると
ncu
というコマンドが使えるようになる。ちなみに、ncuコマンドだけをたたくと現在のパッケージバージョンと最新のパッケージバージョンを表示してくれるnpm outdatedコマンドのような動作をする
ncuを実行すると、以下のように現在のバージョンと最新バージョンが表示される。
つづいて、ncu -uを実行すれば、上でみたとおりパッケージが最新バージョンになるようにpackage.jsonを書き換えてくれる
ncu -unpm updateと違ってパッケージのインストールそのものはやってくれない。package.jsonが書き換わるだけなので、自分で npm install する必要がある
npm installこれでパッケージは最新になった
③npm auditで脆弱性のある依存パッケージを確認する
今、最新のパッケージにした状態だが、この状態でも脆弱性(valnerability)のあるパッケージが含まれていることがある。
npm auditコマンドを使えば脆弱性のあるパッケージを洗い出すことができる。npm auditをやってみたら、180個の脆弱性がみつかった。レベルはlow。
found 180 low severity vulnerabilities in 263397 scanned packages run `npm audit fix` to fix 174 of them. 6 vulnerabilities require manual review. See the full report for details.④npm audit fixで自動修復をこころみる
npm audit fixをすると、脆弱性のあるパッケージのバージョンを自動的に脆弱性の無いバージョンに置き換えてくれる(努力をしてくれる)
npm audit fixすると以下のようになった。
180個の脆弱性のうち174個が修正された。removed 1 package and updated 2 packages in 4.641s 31 packages are looking for funding run `npm fund` for details fixed 174 of 180 vulnerabilities in 263397 scanned packages 6 vulnerabilities required manual review and could not be updated残り6個はマニュアルレビューしてくれとかいてある。
⑤npm dedupeで重複したパッケージの整理統合をこころみる
npm dedupeを理解する
まず、dedupeの概念を理解するために以下の状態を考える
- 自プロジェクトがnpmパッケージAとnpmパッケージBに依存している
- npmパッケージAはnpmパッケージC @2.1.1に依存している。
- npmパッケージBはnpmパッケージC @2.2.0に依存している。
この場合、npmを使っていると1npmパッケージC @2.1.1もnpmパッケージC @2.2.0もインストールされる。
パッケージとしてはnpmパッケージCで同じなのに、npmパッケージC @2.1.1とnpmパッケージC @2.2.0でバージョンが違いがあるので両方保持されしまう。
だったら、バージョンを新しいほうの@2.2.0のほうに合わせて、npmパッケージC @2.2.0のほうを、npmパッケージAとnpmパッケージBで共通化して使いましょう、というのがdedupeの発想。
npm dedupeは新しいバージョンをインストールしてくれない
npm update等でdedupeはひととおりのパッケージを最新にした後にやる。
なぜなら、dedupe自身はパッケージの重複排除などはやってくれるが、最新のパッケージを入れてくれるわけではないので古いパッケージバージョンで状態でdedupeしてもあまり意味がない。dedupeする
さて、ではnpm dedupe (npm ddpでもOK)してみる
removed 10 packages and audited 263298 packages in 3.622s found 0 vulnerabilities脆弱性が0になった!
なぜdedupeで脆弱性がゼロになった?
これは、dedupeが脆弱性を排除しているというより、古いパッケージバージョンに依存していたライブラリが新しいパッケージバージョンを参照するようにdedupeが変更してくれた効果のため。
上の例でいうと以下のようになる。
- 自プロジェクトがnpmパッケージAとnpmパッケージBに依存している
- npmパッケージAはnpmパッケージC @2.1.1(脆弱性ありバージョン)に依存している。
- npmパッケージBはnpmパッケージC @2.2.0(安全バージョン)に依存している。
の状況をdedupeが↓のように変更してくれた効果
- 自プロジェクトがnpmパッケージAとnpmパッケージBに依存している
- npmパッケージAはnpmパッケージC @2.2.0(安全バージョン)に依存している。
- npmパッケージBはnpmパッケージC @2.2.0(安全バージョン)に依存している。
⑦回帰テストを実行する
さてここまでで、脆弱性の無い状態にできたら、最後にテストをしてパッケージバージョンを変更したことによるプロジェクトのデグレが発生していないかどうかを確認する。
npm testテスト無事通過!
(といっても、今回はdevDependenciesの依存のみだったけど)
これで対応完了!
途中でつまずいた場合
上述のコマンドだけでうまくいかず、つまずくことも多々ある。そうした場合は、コマンドだけで楽に突破できない状況になっている可能性があるので、それなりの調査分析工数を覚悟するしかない。
つまずき例
vulnerabilityが無くならなかった
- 対応案:
npm audit
でvulnerabilityのあるパッケージに依存している上位のパッケージを特定する。そのパッケージがdeprecateになっていないか。ちゃんとメンテされているか確認する。deprecatedだったりメンテされていなければ使うのをやめたりPR送ってみたり、自分で直してみたり、代替パッケージを探したり。そういった工数を覚悟、確保する。最後のテストでつまづいた
- 対応案:手順を1手ずつロールバック(手順を戻す)しながら、都度
npm test
を実行し、どの段階でつまづいたのか特定する。ありがちなのは②-1メジャーアップデートでAPIに破壊的変更まとめ
こういった関連サービスもあります
yarnは自動的にdedupeしてくれる(https://classic.yarnpkg.com/ja/docs/cli/dedupe/) ↩
- 投稿日:2020-04-07T03:30:58+09:00
obnizとLineBotで防犯ツールを作ってみた(NO MORE XX 泥棒)
この記事を見てできること
LINEBOTに「スタート」とメッセージを送ると遠隔操作で監視をしてくれるものが作れます。
異常があったら必要以上にしつこく知らせてくれます。
(処理をストップさせる処理は書いてないです)obnizの配線
コード内にも書いてありますが、人感センサーは
{vcc:11, signal:10, gnd:9}
につなぎます。ソースコード
はじめobniz.onconnectをするタイミングでハマって動かなかった。
handleEventよりも先にobniz.onconnectをしておく必要がある。line_sensor.js'use strict'; // obniz呼び出し const Obniz = require('obniz'); var obniz = new Obniz("XXXXXX"); // Obniz_ID に自分のIDを入れます const express = require('express'); const line = require('@line/bot-sdk'); const PORT = process.env.PORT || 3000; const config = { channelAccessToken: 'XXXXXX', channelSecret: 'XXXXXX' }; const app = express(); app.post('/webhook', line.middleware(config), (req, res) => { console.log(req.body.events); Promise .all(req.body.events.map(handleEvent)) .then((result) => res.json(result)); }); const client = new line.Client(config); // obniz接続////////// // スコープ(変数の影響範囲)を最上部に置いておく var sensor; var led; // 接続した段階でセンサーの準備はしておく obniz.onconnect = async function(){ obniz.display.clear(); obniz.display.print("obniz LINE bots"); sensor = obniz.wired("HC-SR505", {vcc:11, signal:10, gnd:9}); led = obniz.wired("LED",{anode:0,cathode:1}); } function handleEvent(event) { if (event.type !== 'message' || event.message.type !== 'text') { return Promise.resolve(null); } let mes = '' if(event.message.text === 'スタート'){ mes = 'OK!監視始めるね!'; //メッセージだけ先に処理 // ディスプレイ処理 obniz.display.clear(); // 一旦クリアする obniz.display.print("Hello obniz!!!!!!"); // Hello obniz!という文字を出す // setIntervalで間隔を作る setInterval(async function(){ // 非同期で取得 var detected = await sensor.getWait(); // console.log(detected); // displayに反映 obniz.display.clear(); // 一旦クリアする // obniz.display.print(detected); // 英語が出力できる // 近づいてきたら判定する if(detected == true ){ // 何かを感知したら obniz.display.clear(); // 一旦クリアする obniz.display.print("someone moving!"); led.on(); // ライトON getAskObniz(event.source.userId); // message }else{ led.off(); } },1000); // 1000ミリ秒 = 1秒 }else{ mes = event.message.text;} return client.replyMessage(event.replyToken, { type: 'text', text: mes }); } const getAskObniz = async (userId) => { await client.pushMessage(userId, { type: 'text', text: "誰かいるかも?", }); } app.listen(PORT); console.log(`Server running at ${PORT}`);XXXXXXになっている箇所は自分のObniz IDや、channelAccessToken、channelSecretを入れることをお忘れなく。
ほぼ完成
ここまで機能としては完成です。こんな感じで動きます。
人を感知すると知らせてくれます。このままでは終われない
こういった手で触れるものは見た目も大事。そして遊び心も必要です(たぶん)
この時点で平日の夜中2時を回っている。当然明日も朝から仕事である。
考えても何もアイデア浮かばない。。
ティッシュがあるからてるてる坊主?
いや、最近ティッシュやトイレットペーパーがなかなか売ってないから無駄使いはダメだ...こんな感じで動きます
人を検知すると光ります。#obniz #映画泥棒 pic.twitter.com/xFeI6oJ136
— shima-07 (@y_kawashima_) April 6, 2020obnizで作った映画泥棒くんは、誰かが来たことをLINEでお知らせしてくれる#obniz #IoT #人感センサー pic.twitter.com/sqEylRzefS
— shima-07 (@y_kawashima_) April 6, 2020最後に
物理的に触れるものは愛着湧きますね。もっと時間がある時にもっとそれっぽく作りたい。
あと、LINEでストップとメッセージしたらモニタリングがストップするような処理も書きたい。
- 投稿日:2020-04-07T02:15:23+09:00
[Rails]JS(jquery)が動かない 初歩的ミス
jquery使用の際、同じコード引用下のに別アプリで
なぜか動かない。
と言う原因の一例です。確認した場所
■Gemfile→jqueryがインストールされているか
■application.jsにjqueryの記述があるか。
■js内にturbolinks:load
が入っているかどうか原因
コメントアウトされているコードの並び順
*これ、すごく大事でした。
初歩の初歩でテキストに書いてあった気がする。。。
//= require_tree .
が、最初はこの位置におりました。
移動!!最下部へ
こちらでjqueryが動かない問題は解決いたしました。require_tree .とは?
参考資料によると以下の記載があります(ちなみに
require_tree
はcssにも記述あります)
そしてアセットパイプライン
と言うそうです。
application内にあるcssやjsの読み込み順を司るものです。require_treeディレクティブは、指定されたディレクトリ以下の すべての JavaScriptファイルを再帰的にインクルードし、出力に含めます。 このパスは、マニフェストファイルからの相対パスとして指定する必要があります。 require_directoryディレクティブを使用すると、指定されたディレクトリの 直下にあるすべてのJavaScriptファイルのみをインクルードします。 この場合サブディレクトリを再帰的に探索しません。日本語難しい(´・ω・)
少し簡単な説明require の部分は ディレクティブ (何種類かあります)
require_directory
→与えられたディレクトリ以下のファイルを、
自身よりも前に挿入する。
順番はアルファベット順(さらに大文字→小文字)になる
require_tree
→require=directory と同じ動きをするが、再帰的に読み込む
読み込みはコードの 上から順番に 読み込まれます。上記より考えると、
require_tree
より下に書かれていたjqueryが読み込まれなかったため動かなかった(と、解釈いたしました。)[備考]
require_treeには引数として与えられたディレクトリ以下のファイルをアルファベット順に全て読み込むという意味があります。
現在require_treeの引数には.(ドット)
が渡されています。
引数.(ドット)
はカレントディレクトリを表します。
つまり、この記述によってapp/assets/javascriptsというディレクトリにあるファイルは全て読み込まれることになります。参考ページ
Rails のアセットパイプライン(Asset Pipeline)について
終わりに
こちらは、個人的解釈をもとに解決した方法を備忘録として書いております。
プログラミング初学者ゆえ、
誤記や不備、アドバイス等ございましたら御指摘いただけると幸いです。
最後まで読んでいただきありがとうございます。