- 投稿日:2020-04-01T23:18:18+09:00
プログラミングド素人でもAPIを使えるのか試してみた
プロトアウトスタジオという、プログラミングスクールではなく、プロトタイピングをアウトプットするというスクールに参加し始めた人の記事です。
前回の投稿で、LINE bot とAPIを繋げる宿題を達成できず、講師の方に対応策をご指南いただきました。
※前回投稿:ド素人がプロトタイピングスクールに入ったらどうなったか&宿題公開
https://qiita.com/hiromae0213繋げ先のAPIは、「リクエストをもらったらランダムに柴犬画像を返す」というAPIを使っています。
※API:
https://shibe.online/下記記事を参考にしています。
物知りLINE BOTを作ろうとしたらfugaしか答えない残念な奴になった話
https://qiita.com/zgw426/items/a5196ab7f26b785479ec'use strict'; const express = require('express'); const line = require('@line/bot-sdk'); const axios = require('axios'); const PORT = process.env.PORT || 3000; const config = { channelSecret: 'xxxx', channelAccessToken: 'xxxx' }; 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); function handleEvent(event) { if (event.type !== 'message' || event.message.type !== 'text') { return Promise.resolve(null); } let mes = 'ちょっとまってね'; // 待ってねってメッセージだけ先に処理 getQiitaTag(event.source.userId , event.message.text); // スクレイピング処理が終わったらプッシュメッセージ return client.replyMessage(event.replyToken, { type: 'text', text: mes }); } const getQiitaTag = async (userId,tag) => { let mes = ''; try { const res = await axios.get('http://shibe.online/api/shibes?count=1&urls=true&httpsUrls=true' ); const item = res.data; mes = item[0]; } catch (error) { // 該当しないものは404でエラーになる const { status, statusText } = error.response; console.log(`Error! HTTP Status: ${status} ${statusText}`); if( status == 404 ){ mes = 'Qiitaのタグ「' + tag + '」の記事はありませんでした'; } else { mes = `Error! HTTP Status: ${status} ${statusText}`; } } await client.pushMessage(userId, { type: 'text', text: mes, }); } app.listen(PORT); console.log(`Server running at ${PORT}`);LINEで何かを入力したら柴犬画像を返すAPIを呼び出して、柴犬画像のURLがLINE上に表示されました。
流れ的には下記のようなイメージを持っていますが、
今の私のレベルではわからない領域が多くてなぜ上記コードが
上手く動いているかは理解できていません。プロトアウトスタジオ第2回の宿題は、
「今日学んだこと(APIの仕組み)やLine Botを発展させる」です。
LINE Botは現状わからないことが多すぎて無理そうなので、まずは自分が理解できる領域で何かできないか考えてみました。Progateを通じてHTMLとJavaScriptの基本中の基本はわかってきたことをふまえ、
下記のような形で、APIで取得した画像をHTML上に表示させる仕組みの実装にチャレンジしました。が、エラー!
理由は、「axiosはサーバ側で動かすものなのでフロント側で使うものではない」そうです。
ただ、HTML上のscriptタグで記載しておけば使えることは使えるそうで、下記のような形で再チャレンジしました。が、再度エラー。
理由としては、取得した画像データを直接フロント側で読み込むのは何とかオリジン(←名前忘れた、、、)の制約に引っかかるケースが多く難しいそうです。最終的にはその制約に引っかからないAPIを講師の方に見繕っていただき、つなぐことができました!
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>犬の画像を呼び出す</title> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> </head> <body> <button id="btn">ボタン</button> <img src="#" id="shiba"> <script src="app.js"></script> </body> </html>let dogImage=''; document.querySelector('button').addEventListener('click',()=>{ axios.get('https://dog.ceo/api/breeds/image/random') .then(function (response) { dogImage=response.data.message; document.getElementById('shiba').src= dogImage; }) .catch(function (error) { // handle error console.log(error); }); })ということで、
ProgateのHTML/CSSとJavaScriptを2周やった程度でもAPIを呼んで取得した情報を表示させる、までは何とかたどり着けました。これを作りながら、本当は下記のような形にできれば柴犬APIで取得した画像も表示できるのでは?と考えましたが、まだ時間が足りないのでこれから勉強します。
- 投稿日:2020-04-01T23:05:55+09:00
Swiper.jsが動作しなかった時の対処法[備忘録]
はじめに
バージョン確認の重要性
Swiper.jsはバージョンによってサポートしてくれるブラウザが違う。ver5.0以降から、IEは完全にサポート対象外になった。今回私はchromeで開発を行っているので最新版でいけると思いきや、挙動が意図したものにならなかったので、4.5.0系を使うことにした。
便利なオプションたち
オプションが豊富で有名なSwiper.js
ここでは、私が参考にした記事を載せておくだけに留めておく。
参考記事
実例12パターン】画像スライダーはSwiper使っておけば間違いない!実用的な使い方を紹介
https://haniwaman.com/swiper/
swiper.js使ってみたからそのオプションについて(v4.1.6)
https://reiwinn-web.net/2018/03/15/swiper-4-1-6/実際のコード
swiper.js$(function(){ var mySwiper = new Swiper('.swiper-container', { slidesPerView: 3, slideToClickedSlide: true, pagination: { el: '.swiper-pagination', type: 'bullets', clickable: true }, navigation: { nextEl: '.swiper-button-next', prevEl: '.swiper-button-prev' }, breakpoints: { 767: { slidesPerView: 1, spaceBetween: 0 } } }); });html.haml.swiper-container.p-2.rounded .swiper-wrapper.p-1 - @posts.each do |post| .events__content.col-sm-6.col-md-4.mb-3 .swiper-slide .card{id: post.id} %label.m-1 - if post.image.present? %img.card-img-top.img-fluid.rounded{src: "#{post.image}"} - else %img.card-img-top.img-fluid.rounded{src: "/assets/noimage.png"} .card-body.event %h5= link_to "#{post.title}", post_path(post.id), class: "event-title stretched-link text-decoration-none" .event__name #{post.user.name} さん .text-right = l post.created_at, format: :long .swiper-button-prev .swiper-button-next .swiper-paginationこれでprevボタンnextボタンを押しても、スライドされない。consoleをみてもエラーはない。ドラッグしながらスクロールすると、一応.swiper-containerに要素が入っていることはわかる。jsファイルは読み込まれているが、メソッドが実行されていない。
階層構造は崩すな
原因がわかった。先ほどのコードは、.swiper-wrapperと.swiper-slidesの間に、.events__content......というクラスが入っている。これが原因だった。Swiper.jsのswiper-container, wrapper, slidesのクラスたちは必ず親子孫の関係でなくてはならなかった。
修正後のコード
html.haml.swiper-container.p-2.rounded .swiper-wrapper.p-1 - @posts.each do |post| .swiper-slide.events__content.col-sm-6.col-md-4.mb-3 .card{id: post.id} %label.m-1 - if post.image.present? %img.card-img-top.img-fluid.rounded{src: "#{post.image}"} - else %img.card-img-top.img-fluid.rounded{src: "/assets/noimage.png"} .card-body.event %h5= link_to "#{post.title}", post_path(post.id), class: "event-title stretched-link text-decoration-none" .event__name #{post.user.name} さん .text-right = l post.created_at, format: :long .card.col-auto .text-left - post.genre_list.each do |genre| .badge.badge-primary{data:{role: "tagsinput"}} = link_to "#{genre}", tag_path(genre), class: 'text-decoration-none text-white' - if user_signed_in? .offset-8.col-auto.card .text-center.likes = render partial: '/posts/posts', locals: {post: post} - if @posts.count >= 4 .swiper-button-prev .swiper-button-next .swiper-paginationこれで正常に動くようになった。
終わりに
私のように、こんなことで何時間も無駄にしないように、皆さんも気をつけてください。
参考記事
【Rails5】「Swiper」を使ってスライダー、カルーセルを作る方法
https://qiita.com/emincoring/items/18d07d0aec5d9836227c
[Rails]Swiperで画像スライド作成
https://qiita.com/yummy888/items/8528c7542f85ae7bbc55サンプル付き!簡単にスライドを作れるライブラリSwiper.js超解説(各種ナビゲーションカスタマイズ編)
https://garigaricode.com/swiper_navigation/
- 投稿日:2020-04-01T22:42:13+09:00
JavaScript JavaScriptでポップアップウィンドウを作成
完成品
HTML
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <link rel="stylesheet" href="style.css"> <script src="script.js"></script> </head> <body> <input type="button" id="btn" value="押す"> <!-- 変数に格納 --> <div id="box"> <!-- 変数に格納 --> <p id="close">× <!-- 変数に格納 --> <h2>ポップアップ</h2> </p> </div> </body> </html>CSS
@charset "UTF-8"; #box { background: #FFF; border: 1px solid #333; box-shadow: 0 10px 10px #999; display: none; /*! id要素に対してdisplay:none */ font-family: serif; padding: 10px; position: relative; text-align: center; width: 200px; } #box > #close { background-color: #EEE; color: #333; cursor: pointer; height: 30px; line-height: 30px; margin: 0; position: absolute; right: 1px; text-align: center; top: 1px; width: 30px; } #box > #close:hover { background-color: #F9F9F9; color: #999; } #btn { background-color: rgb(20, 114, 236); border: 0; color: #FFF; cursor: pointer; padding: 5px 20px; } #btn:hover { color: rgb(20, 114, 236); border: 1px solid rgb(20, 114, 236); background-color: rgb(255, 255, 255); } #btn:active { background-color: #4A4; }JavaScript
window.onload= function(){ //window の load イベントに対応するイベントハンドラ let box = document.querySelector("#box"); //id要素取得 let btn = document.querySelector("#btn"); //id要素取得 let close = document.querySelector("#close") //id要素取得 let boxstyle = box.style; //boxのstyle値をboxstyleに格納 btn.onclick = function(){ //btnがクリックされた時動かす関数 if(boxstyle.display === "block"){ boxstyle.display = "none"; }else{ boxstyle.display = "block"; } }; close.onclick= function(){ //closeがクリックされた時の関数 boxstyle.display = "none"; }; }
- 投稿日:2020-04-01T22:31:20+09:00
JavaScript の論理演算子 (&&, ||) の応用
基本
- JavaScript の処理は左から右へ進む!
- AND / OR 演算子は2つの値を左から順に Boolean にキャストしてチェックし、どちらかの値を返す.
- Boolean を返すわけではない.
例x = {foo:4} && null; // = null y = 1 || window; // = 1AND 演算
AND 演算
a1 && a2
の返り値はBoolean(a1)===false ? a1 : a2
と等価.
AND 演算は〔先に false と判定された値〕or〔最後の値〕を返す.
計算効率を上げるために、false 判定される可能性が高い方の値を先(左)に置く.AND_演算子(&&)// 1 2 true && true // = true ← 2 true && false // = false ← 2 false && true // = false ← 1 false && false // = false ← 1OR 演算
OR 演算
a1 || a2
の返り値はBoolean(a1)===true ? a1 : a2
と等価.
OR 演算は〔先に true と判定された値〕or〔最後の値〕を返す.
計算効率を上げるために、true 判定される可能性が高い方の値を先(左)に置く.OR_演算子(||)// 1 2 true || true // = true ← 1 true || false // = true ← 1 false || true // = true ← 2 false || false // = false ← 2応用: 論理演算をたくさん直列した場合
JavaScript の処理は左から右へ進むので、まず最も左の演算子が評価される。
その返り値に基づき次の演算子が評価され、右へと進んでいく。例-1a || b && c && d && e || f && g || h =((((((a || b)&& c)&& d)&& e)|| f)&& g)|| h例-2// 1 2 3 4 undefined && true || 0 && [6] // = 0 ← 3 =(undefined && true)|| 0 && [6] = undefined || 0 && [6] =(undefined || 0 )&& [6] = 0 && [6] = 0上記の例のように括弧を一切使わずに論理演算を直列すると、必ず全ての値がチェックされることになるので計算効率が悪い。
各値が true / false 判定される確率を考慮し、適切に括弧で括るべき.
- 投稿日:2020-04-01T21:29:45+09:00
TypeScriptで学ぶデザインパターン〜Adapter編〜
対象読者
- デザインパターンを学習あるいは復習したい方
- TypeScriptが既に読めるあるいは気合いで読める方
- いずれかのオブジェクト指向言語を知っている方は気合いで読めると思います
- UMLが既に読めるあるいは気合いで読める方
環境
- OS: macOS Mojave
- Node.js: v12.7.0
- npm: 6.14.3
- TypeScript: Version 3.8.3
本シリーズ記事一覧(随時更新)
- TypeScriptで学ぶデザインパターン〜Iterator編〜
- TypeScriptで学ぶデザインパターン〜Adapter編〜
Adapterパターンとは
インターフェースに互換性の無いクラス同士を組み合わせるためのパターンです。
AdapterのAdaptというのは"適合させる"という意味です。皆さんの中にも、USB Type-Aに対応していない端末にアダプターを用いてそれを使えるようにした...という経験をした方もいらっしゃると思います。そのアダプターと同様の意味合いです。
サンプルコード
Adapterパターンで作られたクラス群がどんなものになるのか確認していきましょう。
今回は、題材として"エディター"を想定します。GitHubにも公開しています。Adapterパターンには"継承を用いたもの"と"委譲を用いたもの"の2種類あります。題材は同じなので、どちらの説明なのかを意識しつつこれ以降読んでみてください。
継承
modules/inheritance/Editor.ts
エディターのインターフェースです。
Editor.tsexport default interface Editor { strong(): string; strike(): string; }
strong
メソッドでは、文字列を太字にする処理を想定しています。たとえば、'テキスト'
という文字列をMarkdown('**テキスト**'
)やHTML('<strong>テキスト</strong>'
)に変換します。
strike
メソッドでは、文字列に取り消し線を付与する処理を想定しています。今回はMarkdown記法で太字にしたり取り消し線を付与したりする処理を作り込んでいくこととします。
modules/BlogEditor.ts
実現したい機能(今回は太字化と取り消し線付与)が実装されているクラスです。
BlogEditor.tsexport default class BlogEditor { private text: string; constructor(text: string) { this.text = text; } addAsteriskForStrong(): string { const strongText: string = '**' + this.text + '**'; return strongText; } addTildeForStrike(): string { const strikeText: string = '~~' + this.text + '~~'; return strikeText; } }ここで重要な点は
Editor
インターフェースとメソッド名が異なっていることです。つまり、Editor
インターフェースを実装することができないわけです。modules/inheritance/MarkdownEditor.ts
インターフェースとクラスを結びつけるクラスです。このクラスがAdapterパターンの主人公です。
MarkdownEditor.tsimport Editor from './Editor'; import BlogEditor from '../BlogEditor'; export default class MarkdownEditor extends BlogEditor implements Editor { constructor(text: string) { super(text); } strong(): string { return this.addAsteriskForStrong(); } strike(): string { return this.addTildeForStrike(); } }
Editor
インターフェースを実装して、かつBlogEditor
クラスを継承することでその機能を実現しています。MainInheritance.ts
Adapterパターンで作られたクラス群を実際に使う処理が書かれています。
MainInheritance.tsimport Editor from './modules/inheritance/Editor'; import MarkdownEditor from './modules/inheritance/MarkdownEditor'; const editor: Editor = new MarkdownEditor('こんにちは!'); console.log(editor.strong()); console.log(editor.strike());委譲
継承セクションと処理内容は同様です。本セクション独自の説明のみ記載します。
modules/delegation/Editor.ts
エディターの抽象クラスです。継承セクションで
Editor
はインターフェースだったことを思い出しましょう。Editor.tsexport default abstract class Editor { abstract strong(): string; abstract strike(): string; }modules/BlogEditor.ts
上述の継承セクションと同ファイルであるため割愛します。
modules/delegation/MarkdownEditor.ts
抽象クラスとクラスを結びつけるクラスです。このクラスがAdapterパターンの主人公です。(重要なことなので繰り返します)
MarkdownEditor.tsimport Editor from './Editor'; import BlogEditor from '../BlogEditor'; export default class MarkdownEditor extends Editor { private blogEditor: BlogEditor; constructor(text: string) { super(); this.blogEditor = new BlogEditor(text); } strong(): string { return this.blogEditor.addAsteriskForStrong(); } strike(): string { return this.blogEditor.addTildeForStrike(); } }TypeScriptにおいて多重継承は認められていないため、
Editor
抽象クラスとBlogEditor
クラスを同時に継承することはできません。そのため、Editor
抽象クラスを継承してBlogEditor
インスタンスの利用は委譲によって実現しています。MainDelegation.ts
Adapterパターンで作られたクラス群を実際に使う処理が書かれています。
MainDelegation.tsimport Editor from './modules/delegation/Editor'; import MarkdownEditor from './modules/delegation/MarkdownEditor'; const editor: Editor = new MarkdownEditor('こんにちは!'); console.log(editor.strong()); console.log(editor.strike());クラス図
ここまでAdapterパターンで作られたクラス群を1つずつ確認してきました。次にクラス図を示します。Adapterパターンの全体像を整理するのにお役立てください。
- 継承
- Client: サンプルコードでは
MainInheritance
が対応- Target: サンプルコードでは
Editor
インターフェースが対応- Adapter: サンプルコードでは
MarkdownEditor
クラスが対応- Adaptee: サンプルコードでは
BlogEditor
クラスが対応- 委譲
- Client: サンプルコードでは
MainDelegation
が対応- Target: サンプルコードでは
Editor
抽象クラスが対応- Adapter: サンプルコードでは
MarkdownEditor
クラスが対応- Adaptee: サンプルコードでは
BlogEditor
クラスが対応解説
最後に、このデザインパターンの存在意義を考えます。これを考える上でサンプルコードに少々ストーリーを加えます。
BlogEditor
クラスは十分にテストされたものだとします。つまり、BlogEditor
は手を加えたくないわけです。一方で、Editor
インターフェース(または抽象クラス)のメソッド名もこれが設計上最善だとします。冒頭でも触れた通り、たとえば太字にする際にはMarkdownでもHTMLでもどちらでも良いわけです。そういった前提がある以上、Editor
インターフェース(または抽象クラス)のメソッド名はaddAsteriskForStrong
など具体的過ぎる名前だと不適切なわけです。このように、両者(
Editor
とBlogEditor
)の事情を鑑みた上で、両者に全く手を加えずに結びつける役割をMarkdownEditor
クラスが担っているわけです。補足
サンプルコードの実行方法はこちらと同様です。
参考
あとがたり
TypeScriptの学習というよりデザインパターンの学習の方が多い。TypeScriptの学習も続けていきたい。
以上です。
最後までご覧いただきありがとうございました!
- 投稿日:2020-04-01T21:28:12+09:00
Kinx ライブラリ - Signal
Zip
はじめに
「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。言語はライブラリが命。ということでライブラリの使い方編。
今回は Zip です。
- 参考
- 最初の動機 ... スクリプト言語 KINX(ご紹介)
- 個別記事へのリンクは全てここに集約してあります。
- リポジトリ ... https://github.com/Kray-G/kinx
- Pull Request 等お待ちしております。
アプリ作る時に Zip 機能は欠かせないですね。あと、脆弱だ、脆弱だ、言われてますけど、業務アプリの中ではパスワード付き Zip が(建前上)求められることも事実。なので、パスワード付き Zip が作れないと実用にならない(日本では...?)。
Zip
Zip アーカイブ作成
Zip インスタンスの作成(
class Zip
)基本的にはこんな感じ。
var zip = new Zip("zipfile.zip", File.READ|File.WRITE);ファイル名とモードを指定する。
モード
モード 意味 File.READ
読み込みモード File.WRITE
書き込みモード File.APPEND
追記モード File と同じ。
メソッド
Zip インスタンスのメソッドは以下の通り。
メソッド 内容 オプション extract(name, [opts])
展開して文字列で取得 { password, overwrite, skip, }
extractTo(name, file [, opts])
ファイルに展開 (同上) find(name)
エントリを検索、エントリ・オブジェクトを返す addFile(filename [, opts])
Zip ファイルにエントリを追加 { password, method, aes, level }
addString(text [, opts])
Zip ファイルにエントリを追加 (同上) setPassword(password)
全 Zip エントリで共通して使用するパスワードを設定 setOverwrite(truefalse)
上書き設定を一括で指定しておくために使用する エントリの追加(
addFile()
/addString()
)
addFile()
またはaddString()
を使います。即座にエントリが追加されます。zip.addFile("README.md");ファイルにディレクトリ名を与えた場合、ディレクトリは以下のファイルが追加される。また、第 2 引数にションを渡せる。オプションの内容は次の通り。
password
: パスワード付き Zip のパスワード。デフォルトは無し。method
: 圧縮方法。デフォルトはdeflate
。その他、指定できるのはstore
、bzip2
、lzma
。aes
: WinZIP 互換の AES 暗号化を有効にするか(true/false)。デフォルト false。尚、addString
の場合には無視される。level
: 圧縮レベル。0-9。暗号化
addFile()
の際にオプションで指定できるが、一括で最初に設定して置く場合はsetPassword()
を使うことができる。var zip = new Zip("zipfile.zip", File.READ|File.WRITE); zip.setPassword("password");Zip ファイル一覧の表示
zip
インスタンスには既に配列としてエントリ・オブジェクトが格納されている。以下のようにすると一覧表示することができる。尚、zip.totalFiles
にエントリ数が格納されている。var zip = new Zip("zipfile.zip", File.READ); System.println("totalFiles = ", zip.totalFiles); zip.each(function(e) { System.println("%s:" % e.filename); e.keySet().each(&(key) => { if (e[key].isFunction || e[key].isObject || e[key].isUndefined) { return; // 展開系の関数などはスキップ。 } if (key == "crc32") { // CRC は 16 進表示 System.println(" %-14s = %10X" % key % e[key]); } else if (key != "time" && key != "filename") { // 別で表示 System.println(" %-14s = %10d" % key % e[key]); } }); // time はさらにオブジェクト構造になっている e.time.keySet().each(&(k) => { System.println(" time.%-7d = %10d" % k % e.time[k]); }); // // エントリを個別に展開することも可能。 // if (e.filename == "README.md") { // e.extractTo("READMEXX.md", { password: "text", overwrite: true }); // } });以下のような感じで表示される。
totalFiles = 4 README.md: compsize = 4413 size = 11621 isDirectory = 0 crc32 = EFD9A09C isEncrypted = 1 method = deflate time.month = 3 time.minute = 1 time.day = 19 time.year = 120 time.second = 2 time.hour = 16 ...展開
Zip ファイルの展開は、以下の 2 通りの方法が可能。
- 直接 Zip インスタンスから展開する。
- Zip エントリオブジェクトから展開する。
Zip エントリから展開する場合は個別の展開になる。その場合、上記のようにイテレートして選択する方法と、
find
メソッドを使う方法の 2 種類がある。find
メソッドは、指定したファイル名のエントリがあれば Zip エントリオブジェクトを返す。展開時のオプションの意味は、以下の通り。
password
: 展開に使うパスワード。指定されなかった場合、setPassword()
で設定されたものを使う。setPassword()
でも設定されてなかった場合、パスワードなしで展開しようとする。overwrite
: true を指定し、同名ファイルが既に存在した場合、上書きする。skip
: true を指定し、同名ファイルが既に存在した場合、スキップする。尚、
overwrite
もskip
も指定されずに同名ファイルが存在した場合、ZipException
例外が送出される。すべて展開
すべて展開するには、上記イテレートしたエントリに対して
extractTo
を実施する。必要なディレクトリは自動的に作成される。zip.each(&(e) => e.extractTo("examples/zip/dst" / e.filename, { password: "text", skip: true }));なんか説明してなかった気がするが、文字列に対して
/
オペレータを適用すると、/
で連結された文字列になる。ファイルを指定して展開
直接 Zip インスタンスに対して
extract
またはextractTo
メソッドを使うことが可能。zip.extractTo("README.md", "READMEXX.md", { password: "text", skip: true });
extract
を使用した場合、展開した内容を文字列として返す。var text = zip.extract("README.md", { password: "text" });現在、バイナリで取得する方法が無いのに気がついたので、追加する予定。オプションに
{ binary: true }
をつけるイメージ。Zip エントリオブジェクトの場合、エントリ名を指定する引数がなくなる。
メソッド 内容 オプション extract([opts])
展開して文字列で取得 { password, overwrite, skip, }
extractTo(file [, opts])
ファイルに展開 (同上)
find
を使った例は以下の通り。zip.find("README.md") .extractTo("READMEXX.md", { password: "text", skip: true });var text = zip.find("README.md") .extract({ password: "text" });その他
使っているライブラリ
これです。
機能一覧。全然 Mini な感じがしないですね。
- Features
- Creating and extracting zip archives.
- Adding and removing entries from zip archives.
- Read and write raw zip entry data.
- Reading and writing zip archives from memory.
- Zlib, BZIP2, and LZMA compression methods.
- Password protection through Traditional PKWARE and WinZIP AES encryption.
- Buffered streaming for improved I/O performance.
- NTFS timestamp support for UTC last modified, last accessed, and creation dates.
- Disk split support for splitting zip archives into multiple files.
- Preservation of file attributes across file systems.
- Follow and store symbolic links.
- Unicode filename support through UTF-8 encoding.
- Legacy character encoding support CP437, CP932, CP936, CP950.
- Turn off compilation of compression, decompression, or encryption.
- Windows (Win32 & WinRT), macOS and Linux platform support.
- Streaming interface for easy implementation of additional platforms.
- Support for Apple's compression library ZLIB implementation.
- Zero out local file header information.
- Zip/unzip of central directory to reduce size.
- Ability to generate and verify CMS signature for each entry.
- Recover the central directory if it is corrupt or missing.
- Example minizip command line tool.
Zip64 対応
Zip64 も対応されている模様。4G 超えもいけるとの話だがテストできていない。
おわりに
Zip/Unzip はスクリプト言語を使う目的としては上位に来る機能でしょう。間違っても C で組みたいとは思わないし、簡単に Zip ファイル作りたい。
では、また次回。
- 投稿日:2020-04-01T21:28:12+09:00
Kinx ライブラリ - Zip
Zip
はじめに
「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。言語はライブラリが命。ということでライブラリの使い方編。
今回は Zip です。
- 参考
- 最初の動機 ... スクリプト言語 KINX(ご紹介)
- 個別記事へのリンクは全てここに集約してあります。
- リポジトリ ... https://github.com/Kray-G/kinx
- Pull Request 等お待ちしております。
アプリ作る時に Zip 機能は欠かせないですね。あと、脆弱だ、脆弱だ、言われてますけど、業務アプリの中ではパスワード付き Zip が(建前上)求められることも事実。なので、パスワード付き Zip が作れないと実用にならない(日本では...?)。
Zip
Zip アーカイブ作成
Zip インスタンスの作成(
class Zip
)基本的にはこんな感じ。
var zip = new Zip("zipfile.zip", File.READ|File.WRITE);ファイル名とモードを指定する。
モード
モード 意味 動作概要 File.READ
読み込みモード 単独で指定した場合、ファイルが存在しなければ ZipException
File.WRITE
書き込みモード ファイルが存在しても新規に作成しなおすモード File.APPEND
追記モード ファイルが存在した場合、そのファイルに追記するモード File と同じ。
メソッド
Zip インスタンスのメソッドは以下の通り。
メソッド 内容 オプション extract(name, [opts])
展開して文字列で取得 { password, overwrite, skip, }
extractTo(name, file [, opts])
ファイルに展開 (同上) find(name)
エントリを検索、エントリ・オブジェクトを返す addFile(filename [, opts])
Zip ファイルにエントリを追加 { password, method, aes, level }
addString(text [, opts])
Zip ファイルにエントリを追加 (同上) setPassword(password)
全 Zip エントリで共通して使用するパスワードを設定 setOverwrite(truefalse)
上書き設定を一括で指定しておくために使用する エントリの追加(
addFile()
/addString()
)
addFile()
またはaddString()
を使います。即座にエントリが追加される。zip.addFile("README.md");ファイルにディレクトリ名を与えた場合、ディレクトリは以下のファイルが追加される。また、第 2 引数にオプションを渡せる。オプションの内容は次の通り。
password
: パスワード付き Zip のパスワード。デフォルトは無し。method
: 圧縮方法。デフォルトはdeflate
。その他、指定できるのは"store"
、"bzip2"
、"lzma"
。aes
: WinZIP 互換の AES 暗号化を有効にするか(true/false)。デフォルト false。尚、addString
の場合には無視される。level
: 圧縮レベル。0-9。オプションを付ける例は以下の通り。
zip.addFile("README.md", { method: "bzip2", password: "password", aes: true, }); zip.addString("test/test1.txt", { content: "test/test\n", // aes: true, // addString では無視される. });パスワードは、展開の際に個別に指定するようにすればエントリごとに別々につけることもできる。
暗号化
addFile()
の際にオプションで指定できるが、一括で最初に設定して置く場合はsetPassword()
を使うことができる。var zip = new Zip("zipfile.zip", File.READ|File.WRITE); zip.setPassword("password");Zip ファイル一覧の表示
zip
インスタンスには既に配列としてエントリ・オブジェクトが格納されている。以下のようにすると一覧表示することができる。尚、zip.totalFiles
にエントリ数が格納されている。var zip = new Zip("zipfile.zip", File.READ); System.println("totalFiles = ", zip.totalFiles); zip.each(function(e) { System.println("%s:" % e.filename); e.keySet().each(&(key) => { if (e[key].isFunction || e[key].isObject || e[key].isUndefined) { return; // 展開系の関数などはスキップ。 } if (key == "crc32") { // CRC は 16 進表示 System.println(" %-14s = %10X" % key % e[key]); } else if (key != "time" && key != "filename") { // 別で表示 System.println(" %-14s = %10d" % key % e[key]); } }); // time はさらにオブジェクト構造になっている e.time.keySet().each(&(k) => { System.println(" time.%-7d = %10d" % k % e.time[k]); }); // // エントリを個別に展開することも可能。 // if (e.filename == "README.md") { // e.extractTo("READMEXX.md", { password: "text", overwrite: true }); // } });以下のような感じで表示される。
totalFiles = 4 README.md: compsize = 4413 size = 11621 isDirectory = 0 crc32 = EFD9A09C isEncrypted = 1 method = deflate time.month = 3 time.minute = 1 time.day = 19 time.year = 2020 time.second = 2 time.hour = 16 ...展開
Zip ファイルの展開は、以下の 2 通りの方法が可能。
- 直接 Zip インスタンスから展開する。
- Zip エントリオブジェクトから展開する。
Zip エントリから展開する場合は個別の展開になる。その場合、上記のようにイテレートして選択する方法と、
find
メソッドを使う方法の 2 種類がある。find
メソッドは、指定したファイル名のエントリがあれば Zip エントリオブジェクトを返す。展開時のオプションの意味は、以下の通り。
password
: 展開に使うパスワード。指定されなかった場合、setPassword()
で設定されたものを使う。setPassword()
でも設定されてなかった場合、パスワードなしで展開しようとする。overwrite
: true を指定し、同名ファイルが既に存在した場合、上書きする。skip
: true を指定し、同名ファイルが既に存在した場合、スキップする。尚、
overwrite
もskip
も指定されずに同名ファイルが存在した場合、ZipException
例外が送出される。すべて展開
すべて展開するには、上記イテレートしたエントリに対して
extractTo
を実施する。必要なディレクトリは自動的に作成される。zip.each(&(e) => e.extractTo("examples/zip/dst" / e.filename, { password: "text", skip: true }));なんか説明してなかった気がするが、文字列に対して
/
オペレータを適用すると、/
で連結された文字列になる。ファイルを指定して展開
直接 Zip インスタンスに対して
extract
またはextractTo
メソッドを使うことが可能。zip.extractTo("README.md", "READMEXX.md", { password: "text", skip: true });
extract
を使用した場合、展開した内容を文字列として返す。var text = zip.extract("README.md", { password: "text" });現在、バイナリで取得する方法が無いのに気がついたので、追加する予定。オプションに
{ binary: true }
をつけるイメージ。Zip エントリオブジェクトの場合、エントリ名を指定する引数がなくなる。
メソッド 内容 オプション extract([opts])
展開して文字列で取得 { password, overwrite, skip, }
extractTo(file [, opts])
ファイルに展開 (同上)
find
を使った例は以下の通り。zip.find("README.md") .extractTo("READMEXX.md", { password: "text", skip: true });var text = zip.find("README.md") .extract({ password: "text" });その他
使っているライブラリ
これです。
機能一覧。全然 Mini な感じがしないですね。
- Features
- Creating and extracting zip archives.
- Adding and removing entries from zip archives.
- Read and write raw zip entry data.
- Reading and writing zip archives from memory.
- Zlib, BZIP2, and LZMA compression methods.
- Password protection through Traditional PKWARE and WinZIP AES encryption.
- Buffered streaming for improved I/O performance.
- NTFS timestamp support for UTC last modified, last accessed, and creation dates.
- Disk split support for splitting zip archives into multiple files.
- Preservation of file attributes across file systems.
- Follow and store symbolic links.
- Unicode filename support through UTF-8 encoding.
- Legacy character encoding support CP437, CP932, CP936, CP950.
- Turn off compilation of compression, decompression, or encryption.
- Windows (Win32 & WinRT), macOS and Linux platform support.
- Streaming interface for easy implementation of additional platforms.
- Support for Apple's compression library ZLIB implementation.
- Zero out local file header information.
- Zip/unzip of central directory to reduce size.
- Ability to generate and verify CMS signature for each entry.
- Recover the central directory if it is corrupt or missing.
- Example minizip command line tool.
Zip64 対応
Zip64 も対応されている模様。4G 超えもいけるとの話だがテストできていない。
おわりに
Zip/Unzip はスクリプト言語を使う目的としては上位に来る機能でしょう。間違っても C で組みたいとは思わないし、簡単に Zip ファイル作りたい。
では、また次回。
- 投稿日:2020-04-01T20:39:53+09:00
JavaScript の配列の要素をすべて削除する
clear とか clearAll とか remove とか delete とか配列を空にするシンプルなメソッドがあるのかと思ったけどなかった。
もう少し高機能な splice メソッドを使うのが良さそう。
Array.prototype.splice() - JavaScript | MDN
splice() メソッドは、 (in place で) 既存の要素を取り除いたり、置き換えたり、新しい要素を追加したりすることで、配列の内容を変更します。
サンプルコード。
let array = ['Tanis'] // 配列要素を指定して初期化 array.push('Sturm') // 要素を1つ追加 array.push('Flint', 'Tasslehoff') // 要素を複数追加 Array.prototype.push.apply(array, ['Caramon', 'Raistlin']); // 配列の要素を追加 console.log(`配列の要素数: ${array.length}`); console.log(array); console.log(); // 配列の要素を全削除する (インデックス0以降のすべての要素を削除) array.splice(0) console.log(`配列の要素数: ${array.length}`); console.log(array);実行結果 (Node.js 13.12.0 にて確認)
$ node array.js 配列の要素数: 6 [ 'Tanis', 'Sturm', 'Flint', 'Tasslehoff', 'Caramon', 'Raistlin' ] 配列の要素数: 0 []
- 投稿日:2020-04-01T20:34:18+09:00
フィボナッチ数列 Ruby Perl JavaScript
はじめに
フィボナッチ数列の記事ってフィボナッチ数列100番目より遅いところですが、勉強中のもっとプログラマ脳を鍛える数学パズル アルゴリズムが脳にしみ込む70問 のアウトプットということでお許しください。
Ruby
rubymemo.rb@memo = {0 => 1, 1 => 1} def f(n) return @memo[n] if @memo[n] @memo[n] = f(n - 1) + f(n - 2) end puts f(10) # => 89rubyarray.rbN = 10 f = Array.new() f[0] = f[1] = 1 2.upto(N) do |i| f[i] = f[i - 1] + f[i - 2] end puts f[N] # => 89javascript の様にハッシュではなく配列でも実装可能と思われます。
Perl
perlmemo.plmy %memo = (0 => 1, 1 => 1); sub f { my $n = shift; return $memo{$n} if defined $memo{$n}; $memo{$n} = f($n - 1) + f($n - 2); } print f(10), "\n"; # => 89perlarray.plmy $n = 10; my @f; $f[0] = $f[1] = 1; for my $i (2..$n) { $f[$i] = $f[$i - 1] + $f[$i -2]; } print $f[$n], "\n"; # => 89ruby_perl.plreturn @memo[n] if @memo[n] # ruby return $memo{$n} if defined $memo{$n}; # perl return $memo{$n} if exists $memo{$n}; # perlruby と異なり、defined関数もしくは exists関数を使用する必要があります。
JavaScript
javascriptmemo.jsvar memo = []; memo[0] = memo[1] = 1; function f(n) { if (memo[n]) return memo[n]; return memo[n] = f(n - 1) + f(n - 2); } console.log(f(10)); // => 89javascriptarray.jsN = 10; var f = []; f[0] = f[1] = 1; for (var i = 2; i <= N; i++) { f[i] = f[i - 1] + f[i - 2]; } console.log(f[N]); // => 89javascript に詳しくないので、ハッシュ未使用で実装しています。
まとめ
- フィボナッチ数列を実装した
- それぞれの言語に違いがあって面白かった
参照したサイト
フィボナッチ数列
メモ化から学ぶ早期リターン
javascriptのハッシュライブラリを比較する
exists関数 - ハッシュのキーの存在確認
- 投稿日:2020-04-01T18:58:22+09:00
途中で戻したり進めたりできるイテレータ
構文解析する際に、文字列イテレート中に戻したりスキップしたりできるイテレータがあれば便利かと思い作ってみました。
参考: https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Iterators_and_Generatorsfunction iter(pattern) { let index = 0 return { [Symbol.iterator]: function() { return this }, next: function() { if (index < pattern.length) { return { value: pattern[index++], done: false } } else { return { done: true } } }, fetch: function() { return pattern[index] }, back: function() { index > 0 && index-- }, skip: function() { index++ }, } }実際に利用したコードは長くなってしまったので、ちょっと無理矢理な使用例を示します。
使用例let it = iter("Helo!!!!!, world!") for (let c of it) { console.log(c) // 'Hel' if (c === 'l') break } it.back() // back to 'l' console.log(it.next().value) // 'l' console.log(it.next().value) // 'o' while (it.fetch() === '!') it.skip() // skip '!' for (let c of it) { console.log(c) // ', world!' }実行結果H e l l o , w o r l d !
- 投稿日:2020-04-01T17:53:17+09:00
JavaScript : ブラウザ中央にポップアップウィンドウを開く
今どきポップアップウィンドウなんて時代でもないですが、TwitterやFacebookなどSNSのシェアボタンでは未だに使われており、それとよく併用するコードです。
window.open()
は特定の位置を指定してウィンドウを開けます。
ブラウザの位置・広さをもとにブラウザの中央にウィンドウを配置するよう指定します。function popupWindow(url, name, w, h) { url = encodeURI(decodeURI(url)); wTop = window.screenTop + (window.innerHeight / 2) - (h / 2); wLeft = window.screenLeft + (window.innerWidth / 2) - (w / 2); window.open(url, name, 'width=' + w + ', height=' + h + ', top=' + wTop + ', left=' + wLeft + ', personalbar=0, toolbar=0, scrollbars=1, resizable=!'); } //popupWindowへ投げる変数 //url : ウィンドウに出すURL //name : ウィンドウ名 //w : ウィンドウの横幅 //h : ウィンドウの縦幅 //wTop : 配置する横位置 //wToop: 配置する縦位置
- 投稿日:2020-04-01T17:22:16+09:00
Google Apps Scriptで快適にWEBスクレイピングするためのライブラリを作ったら、重すぎて動かなかったお話。
作るに至った経緯
GASでのWEBスクレイピングといえば、送られてきたhtmlソースをただの文字列として受け取り、正規表現やmatch、split、indexOfなどで無理矢理目的の要素・テキストを削り出してくるもの。一応スクレイピング用のライブラリもあるが、やっていることは対して変わらない。
「我々が求めるものはPythonのbs4のような、CSSセレクタなどで簡単に要素を見つけ出し、情報を抽出するものではなかろうか?」
そんなこんなで火が付いた。作ったプロトタイプもGoogle Chromeの開発者ツール上では、少々レスポンスが遅いもののキチンと想定道理に動いていたから、やれる自信はあった。再帰呼び出しの回数が多すぎて、どうやってもGASでは動かないと気付いた時、私は燃え尽きた。
どういうライブラリだったか
CSSセレクタベースで要素を見つけるもの。最初は親・子孫要素や属性(
[attr="hogehoge"]
)などにも対応させていたが、少しでも再帰呼び出しを減らすため、どんどんオプションを削られ、しまいには親要素からの検索も露と消えた。まあ結局動かなかったけど。というわけでこのライブラリ(笑)は、Google Chromeの開発者ツール上でしか、筆者は正常に動かしたことがない。悲しい。せっかく作ったのに。呼び出し
var h = HTMLparser(` <html> <head></head> <body> <p class="cls">htmlソーステキスト</p> ... </body> </html> `); var tag_p = h.search("p.cls"); //[{tagname: "p", attr: {…}, innerText: "htmlソーステキスト", tree: Array(1), parent: {…}}]呼び出し元のコード
function HTMLparser(html_str) { if(!(this instanceof HTMLparser)) { return new HTMLparser(html_str); }else { this.html = []; this.attr = {}; this.tags = {}; var html_obj = {tree: []}; //str: htmlソース, ary: htmlのツリー, parent: 親要素, self: HTMLparserのthisの参照用 function per(str, ary, parent, self) { var obj = {tagname: "_Node", attr: null, innerText: "", tree: [], parent: null}; obj.parent = parent; //開始タグを見つける var matchTag = str.match(/<([a-zA-Z][^\t\n\r\f \/>\x00]*?)(| [a-zA-Z][^\t\n\r\f>\x00]*?[^\/])>([\s\S]*?)$/); //[0]: 全体, [1]: タグ名, [2]: 属性, [3]: その開始タグ以降のテキスト function sameTagBothReg(tagname) { return new RegExp("(<" + tagname + ">|<" + tagname + " [a-zA-Z][^\t\n\r\f>\x00]*?[^\/]>|<\\/" + tagname + ">)"); } function sameTagStartReg(tagname) { return new RegExp("(<" + tagname + ">|<" + tagname + " [a-zA-Z][^\t\n\r\f>\x00]*?[^\/]>)"); } function sameTagEndReg(tagname, count) { return new RegExp("(<\\/" + tagname + ">)"); } var attrReg = /([a-zA-Z][^\t\n\r\f >\x00]*=\".*?\")/g; var attrReg_g = /([a-zA-Z][^\t\n\r\f >\x00]*)=\"(.*?)\"/g; if(matchTag) { var attr_obj = {}; var attr_node_list = matchTag[2].split(attrReg).filter(function(r) {return r.match(attrReg);}) attr_node_list.forEach(function(r) { //タグの属性を収集 var a = r.split(attrReg_g); if(!self.attr[a[1]]) { self.attr[a[1]] = {}; } var v; //クラスのみリスト化 if(a[1] == "class") { v = a[2].split(" "); v.forEach(function(r) { if(!self.attr[a[1]][r]) { self.attr[a[1]][r] = []; } self.attr[a[1]][r].push(obj); }); }else { v = a[2]; if(!self.attr[a[1]][v]) { self.attr[a[1]][v] = []; } self.attr[a[1]][v].push(obj); } attr_obj[a[1]] = v; }); obj.attr = attr_obj; obj.tagname = matchTag[1]; //その開始タグの対となる終了タグを見つける //もし開始タグがspanだった場合、その開始タグ以降のテキストから、 //spanの開始タグと終了タグをまとめて探索する var st_cnt = 1;//開始タグの数(開始タグはすでに一個あるので、初期値は1) var ed_cnt = 0;//終了タグの数 var sp_idx = 0;//目的の終了タグのインデックス var splitted_same_tag = matchTag[3].split(sameTagBothReg(matchTag[1])); splitted_same_tag.forEach(function(v, i) { if(sp_idx) { return; } //開始タグがマッチしたら+1 else if(v.match(sameTagStartReg(matchTag[1]))) { st_cnt++; return; } //終了タグがマッチしたら+1 else if(v.match(sameTagEndReg(matchTag[1]))) { ed_cnt++; //開始タグ数と終了タグ数が一致したら、インデックスを記録 if(st_cnt == ed_cnt) { sp_idx = i; }else { return; } } }); //始点からインデックスまでが、そのタグの子要素 var child = splitted_same_tag.slice(0, sp_idx).join(""); if(matchTag[1] == "title") { self.title = child; } //scriptタグの中には稀にhtmlのタグが紛れ込んでいるので、 //誤マッチ回避のため、scriptタグの中身は切り捨て if(matchTag[1] !== "script") { //子要素をターゲットにして自身(obj)を親とし、perを再帰的に呼び出し per(child, obj.tree, obj, self); } //インデックスから終点までが、そのタグの兄弟要素 var bro = splitted_same_tag.slice(sp_idx + 1).join(""); if(bro !== "") { //兄弟要素をターゲットにして自身と同じ親要素(parent) //を親とし、perを再帰的に呼び出し per(bro, ary, parent, self); } }else { obj.tree = [str]; } ary.unshift(obj); //もしタグ名が見つからなかったら(_Nodeのままなら)、 //自身と親・先祖のinnerTextに中身の文字列を追加 if(obj.tagname == "_Node") { Text(obj, obj.tree.join("")); function Text(o, txt) { o.innerText += txt; if(o.parent) { Text(o.parent, txt); } } }else { if(!self.tags[obj.tagname]) { self.tags[obj.tagname] = []; } self.tags[obj.tagname].push(obj); } } per(html_str.replace(/<!--[\s\S]*?-->/g, ""), this.html, null, this); } } //検索機能(最も削り取られた部分) HTMLparser.prototype.search = function(selector) { var sel = sel_parse(selector); //終端の1要素のみ読み取る //CSSセレクタは「右から」読み取るのが効率的 return check_tree(sel[0], this); function check_tree(s, self) { var r = []; var _tag = s.tag; if(_tag) { if(self.tags[_tag]) { for(var t of self.tags[_tag]) { var _atr = t.attr; if(s.class.length) { if(_atr.class) { var _chk = false; for(var cls of s.class) { if(_atr.class.indexOf(cls) < 0) { _chk = true; break; } } if(_chk) {continue;} }else { continue; } } if(s.id.length) { if(s.id[0] !== _atr.id) { continue; } } r.push(t); } } }else { var c_ary = []; var i_ary = []; var a_ary = [];//削り取られた残滓 if(s.class.length) { var _chk = true; var clsList = Object.keys(self.attr.class); for(var cls of s.class) { if(clsList.indexOf(cls) < 0) { break; }else { if(!c_ary.length) { c_ary = self.attr.class[cls]; }else { c_ary = c_ary.concat(self.attr.class[cls]).filter(function(x, i, self) { return self.indexOf(x) === i && i !== self.lastIndexOf(x); }); } } } } if(s.id.length) { var idList = Object.keys(self.attr.id); if(-1 < idList.indexOf(s.id[0])) { i_ary = self.attr.id[s.id[0]]; } } if(s.attr.length) { var _chk = false; for(var a of s.attr) { var _k = a.atr; var _m = false; if(_k.match(/(\*|\^|\$)$/)) { _m = _k.match(/(\*|\^|\$)$/)[1]; _k = _k.replace(/(\*|\^|\$)$/, ""); } } } var full_cnt = 0; if(c_ary.length) { full_cnt++; r = r.concat(c_ary); } if(i_ary.length) { full_cnt++; r = r.concat(i_ary); } if(a_ary.length) { full_cnt++; r = r.concat(a_ary); } if(1 < full_cnt) { r = r.filter(function(x, i, self) { return self.indexOf(x) === i && i !== self.lastIndexOf(x); }); } } return r; } //CSSセレクタの解析用 function sel_parse(sel) { if(sel.match(/ ?[\+\~] ?/g)) { throw Error('You cannot use Adjacent sibling combinator "+/~".'); } else if(sel.match(/\:(nth-child\(|nth-of-type\(|not\(|first-child|first-of-type|last-child|last-of-type)/g)) { throw Error('You cannot use Pseudo-elements like ":nth-of-type()"'); } var sp = sel.split(/( ?> ?|(?<=[a-zA-Z0-9\]\_\-]) (?=[a-zA-Z\[\.\#\_]))/g); var a = [], nxt = false; sp.forEach(function(s, idx) { if(s.match(/^ $/g)) { return; } else if(s.match(/^ ?> ?$/g)) { nxt = true; return; } var _o = {"tag": null, "class": [], "id": [], "attr": [], "next": false}; if(nxt) { _o.next = true; nxt = false; } var _s = s.split(/(\[.*?\]|(?<=(?:[a-zA-Z\]]|^))(?:\.|\#)[a-zA-Z\_][a-zA-Z0-9\_\-]*)/g).filter(function(r) {return r;}); _s.forEach(function(p) { if(p.match(/^\#/)) { _o.id.push(p.replace(/\#/, "")); } else if(p.match(/^\./)) { _o.class.push(p.replace(/\./, "")); } else if(p.match(/^[a-zA-Z]/)) { _o.tag = p; } else if(p.match(/^\[(.*?)(?:\=(?:\"(.*?)\"|\'(.*?)\')|)\]/)) { var _m = p.match(/^\[(.*?)(?:\=(?:\"(.*?)\"|\'(.*?)\')|)\]/); _o.attr.push({"atr": _m[1], "que": _m[2] ? _m[2] : ""}); } }); a.unshift(_o); }); return a; } }ちなみに、HTMLparserオブジェクトの中身は以下の通り。
treeにはその要素の子要素のリストが、parentにはその要素の親要素への参照が渡されている(図では一番外側のタグのため参照が無い)。下のattrとtagsは検索用の参照のリストが詰まっている。
- 投稿日:2020-04-01T17:19:50+09:00
TypeORM x AuroraDataAPI - タイムゾーン問題への対処
TypeORM x AuroraDataAPI - TIMEZONE問題への対処
ローカルタイムゾーンがJSTだとしたとき、
DBに書き込まれる時刻が9時間未来になる(JSTのまま書かれてしまう)
DBから読み込まれる時刻が9時間過去になる(JST扱いで読まれてくる)問題への対処です。前置き
本記事の解決法では、TypeORMの外側で自力で時刻を補正するため、
ライブラリ(TypeORMとAuroraプラグイン)の仕様が変わったり、バグが治った場合は毒になりえます。
そのへんの影響まで自分で管理してやんよシュババってひとむけです。環境
- Aurora Serverless MySQL 5.6
- time_zone, system_time_zone:
UTC
- TypeORM 0.2.24
- typeorm-aurora-data-api-driver 1.1.8
- Node 10
- timezone(TZ環境変数):
Asia/Tokyo
上記環境 (TypeORM x AuroraDataAPI) での観測ですが、
DBが違う場合でも同じ症状には同じ対策が適用可能なはずです。原因
おそらく、
typeorm-aurora-data-api-driver
に関する、以下のバグです。
- Data型を日付時刻Stringに変換して書き込む処理
- TZ環境変数にもとづいて、出力を補正していない
- 結果、書き込まれる時刻がズレる(JSTなら+9時間されてる)
- 配布物でいうと、
typeorm-aurora-data-api-driver.umd.js
のformatDate()
- リポジトリでいうとどこかは、軽く見たけどわからなかった
- 日付時刻StringをDate型に変換して読み出す処理
- TZ環境変数にもとづいて、入力を補正していない
- 結果、読み出される時刻がズレる(JSTなら-9時間されてる)
- ソースで具体的にどこに該当するかは、ざっくりデバッグしたけどわからなかった。
query()
中のresult補正かけてるあたりのどこかでやるべき、なのかも。結果、JST環境からの読み書きだけ見ると、同じだけズレるので 書き込み前=読み込み前 となっていますが、
CreatedDateColumn() 等でDB側で生成された時刻を読みだした場合ズレたり、
そもそもDB直接覗くとUTCのくせにJST扱いで記録されてたりと、
いろいろ気持ち悪い現象が発生してしまっていました。(補足)原因はわかってるけどコントリビュートしたくない
ざっくり調べた結果、上記原因が解消されれば治るは治るはずなんだけど、
以下の背景からコストが高くなりそうなのでしてません。
issueくらいは投稿してあげてもいいかも。手が空いたらやる。
- それがTypeORM由来なのかプラグイン由来なのか正直わかりきらない
- TypeORMのプラグインにコントリビュートするにはTypeORMの知識も必要である
- プラグインの更新が活発でない(8ヶ月前とか)
対策
@Column()
の option である transformer を使い、
書く直前と読んだ直後に自力で時刻を補正します。
Entity インスタンス自体の時刻には影響しないよう気を使っています。orm-opts.tsimport moment, { Moment } from "moment-timezone"; import { ColumnOptions, EntityMetadata, EntitySchema } from "typeorm"; /** * Date型のoffsetに基づいて読み書きされているので */ const TYPEORM_LOCAL_OFFSET = new Date().getTimezoneOffset(); /** * DBのタイムゾーンは基本UTCの前提とする。 * 異なる場合は、setCorrectOffset()で任意の補正時間をセットする。 */ const DB_OFFSET = moment.tz("UTC").utcOffset(); export class OrmOpts { /** * TypeORM ごしにDatetime型を書く直前、読んだ直後に、この分数だけ時刻をずらす。 * DB時刻がUTCにも関わらず、JSTで(+09:00のまま)書き込まれたりする問題への対処のため。 * * 初期値は 0(UTC) - TZ環境変数のoffset(JSTなら540) */ static CORRECT_OFFSET = DB_OFFSET - TYPEORM_LOCAL_OFFSET; static MOMENT: ColumnOptions = { type: "datetime", transformer: { from: (from: Date) => { if (!from) return from; const fromM = moment(from).add({ minutes: OrmOpts.CORRECT_OFFSET }); return fromM; }, to: (to: Moment) => { if (!to) return to; const toD = to .clone() .subtract({ minutes: OrmOpts.CORRECT_OFFSET }) .toDate(); return toD; } } }; static DATE: ColumnOptions = { type: "datetime", transformer: { from: (from: Date) => { if (!from) return from; const fromM = (OrmOpts.MOMENT.transformer as any).from(from); return fromM.toDate(); }, to: (to: Date) => { if (!to) return to; to = (OrmOpts.MOMENT.transformer as any).to(moment(to)); return to; } } }; }types.tsexport class TestEntity extends BaseEntity { @PrimaryGeneratedColumn("increment") seq: number; @CreateDateColumn(OrmOpts.DATE) createdAt: Date; @CreateDateColumn(OrmOpts.MOMENT) createdAtM: Moment; @Column(OrmOpts.DATE) dateAt: Date; @Column(OrmOpts.MOMENT) dateAtM: Moment; }結果
これで、ローカルのTIMEZONEに関わらず、
正しい時刻(このコードではUTC)でDBに書き込まれるようになります。
読み出すときはローカルTIMEZONEに補正されて読まれるようになります。
- 投稿日:2020-04-01T16:06:27+09:00
Vue.js テンプレート制御ディレクティブ まとめ
はじめに
Vue.jsでTO DOアプリを作るの続きです。
今回はディレクティブを深掘りしていきます。
v-once
- 初回だけテキストバインディングを行う。
- 初回以降は静的なコンテンツとして扱う。
- 描画更新のパフォーマンスを上げたいときに利用できる
var app = new Vue({ el: '#app', data: { message: 'Hello Vue.js!' }, methods: { click(e) { // 文字列を反転するメソッドを定義 this.message = this.message .split('') .reverse() .join('') } } })以下は
v-once
を定義しているので、初回だけテキストバインディングを行っている。初回以降は静的なコンテンツとして扱われるので、ボタンをクリックしてもメソッドが発火しない。
<p v-once> <!-- v-onceを定義 -->> {{ message }} <!-- Hello Vue.js --> </p> <button v-on:click="clickHandler"> 文字が反転 </button>v-pre
- 要素と全ての子要素のコンパイルをスキップする。
- 生のマスタッシュタグが表示される。
<p v-pre> {{ message }} <!-- {{ message }} --> </p>v-html
- プレーンなHTMLを挿入する。
- 指定した要素のinnerHTMLを更新できる。
- 主にサーバーサイドから取得したHTMLを表示したいときに使う。
data
オプションのプロパティにHTMLをセットする。data: { messageHtml: 'Hello <span style="color:red;">Vue.js!</span>' }
v-html
ディレクティブで展開する。<p v-html="messageHtml"></p>※XSS脆弱性を引き起こす恐れがあるため、サービスを利用するユーザーが入力したコンテンツには使用しないこと。
v-cloak
ページを表示開始してから、インスタンスの作成が終わるまでにマスタッシュタグなどのコンパイル前にテンプレートが表示されてしまうのを防ぐ。
<p v-cloak> <!-- v-cloakを定義 --> {{ message }} </p>
v-cloak
に対してdisplay: none
を設定する。[v-cloak] { display: none; }v-text
data
オプションのプロパティをマスタッシュ構文以外で表示したい時。使い道あんまりなさそう。
<p v-text="message"></p> <!-- Hello Vue.js! -->JavaScript式
データバインディング内部では
JavaScript式
を利用する事ができる。data: { message: 'Hello Vue.js!', number: 100, ok: true }<p> {{ number + 1}} <!-- 101 --> {{ message.split('').reverse().join('') }} <!-- !sj.euV olleH --> </p>フロー制御は
三行演算子
を利用する。<p> {{ ok? 'YES' : 'NO' }} <!-- YES --> </p>以下は式では無く文なのでエラーになる。
<p> {{ var x = 1 }} <!-- error --> </p>フィルタ(ローカル)
Vue.jsでは式の終わりにフィルタを追加することができる。
data: { price: 29800 //dataオプションにプロパティを設定 }, filters: { // filterにメソッドを追加 numberFormat(value) { return value.toLocaleString() }
{{ 式 | フィルタ }}
でfilterを実行する<p> {{ price | numberFormat(price) }} <!-- 29,800 --> </p>
v-bind
ディレクティブで使用する場合<input type="text" v-bind:value="price | numberFormat(price)">フィルタ(グローバル)
フィルタはグローバルにも定義できる。
グローバルに定義するには
Vueインスタンス
を生成するよりも前に記述する。// グルーバルフィルタを定義 Vue.filter('numberFormat', function(value) { return value.toLocaleString() }) // Vueインスタンス生成 var app = new Vue({ el: '#app', data: { price: 29800 } })フィルタの連結
フィルタは複数連結できる。
フィルタで加工した返り値に対して、さらにフィルタをかけることができる。
<p> {{ A | filterX(A) | filterY(A) }} </p>フィルタの引数
フィルタで引数を利用する。
例として、長いテキストを省略して
...
を末尾に付与するフィルタを作成する。その際、
表示する文字列
と末尾に付与する文字
を引数で指定できるようにする。
data
オプションに長い文字列を持ったtext
プロパティを用意する。text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt続いてフィルタを定義する。
JavaScriptのメソッド
substring
は第一引数が開始位置、第二引数が抽出する文字数を指定する。以下のフィルタは、引数で
文字列
、抽出する文字数
、連結する文字
を受け取る。Vue.filter('readMore', function(text, length, suffix) { return text.substring(0, length) + suffix })実行してみる。
<p> <!-- textは引数に含まないことに注意 --> {{ text | readMore(10, '...') }} <!-- Lorem ipsu... --> </p><p> {{ text | readMore(5, '***') }} <!-- Lorem*** --> </p>v-bind省略記法
v-bind
は省略記法が存在する。プロジェクト内で統一するのが望ましい。
以下は同じ結果が出力される。
<!-- 完全な構文 --> <a v-bind:href="url"> <!-- 省略記法--> <a :href="url">更新履歴
Vue.jsの基本的な使い方まとめ
Vue.jsでTO DOアプリを作る
Vue.js テンプレート制御ディレクティブ まとめ 今ココ
Vue.js 算出プロパティとメソッドの違い
- 投稿日:2020-04-01T16:03:32+09:00
JavaScript学習 - 関数 -
JavaScriptにおける『関数』
関数そのものの仕組み(入力に対してなんらかの処理を行い、その結果を返す)はJavaと同じ。
JavaScriptでは関数はデータ型の一種
として扱うのがJavaとの大きな違い。・『関数の引数に名前を指定する』
・『複数の戻り値をそれぞれ指定した変数に代入する』 ...etc型推論もあるので、かなり柔軟に関数や変数の設定が出来る。
関数の定義
主に以下の4つ(3つの)の方法がある
・
function命令による定義
・ functionコンストラクターによる定義
・関数リテラルによる定義(無名関数)
・アロー関数による定義
これら4つのうちfunctionコンストラクターは外部から任意のコードが実行できてしまう可能性があるので使用は推奨されていない、原則としてfunction命令・関数リテラルあるいはアロー関数のどれかで定義することになるので実際には3つの方法ということになる。
function命令による定義
関数の定義の最も基本的な方法。
function命令function hoge(hage, ...){ //任意の処理 return piyo }関数の実装といえば基本的にはこれ。
関数の中に関数を実装したり、処理の中で一回限りの処理をする関数を実装したい場合は後述の関数リテラル
やアロー関数
などを使う。関数リテラル・アロー関数
匿名関数
、無名関数
とも呼ばれる。
変数として代入したり、関数の引数や戻り値として利用できるので非常に柔軟なコーディングができるようになる。関数リテラルvar hoge = function(hage, piyo){ return hoge * piyo }アロー関数を利用する事でfunctionキーワードを省略できるのでさらにシンプルに記述できるようになる。
アロー関数var hoge = (hage, piyo) => return hoge * piyo;引数が1つの場合は引数の()も省略することが出来る。
省略する際は引数が0個の場合は省略できないので注意。変数のスコープ
変数がコードのどの部分から参照できるのかを表す概念的なもの。
以下の3種類のスコープがある。
・ローカルスコープ
・グローバルスコープ
・ブロックスコープ
ローカルスコープとptにおける『関数』
関数そのものの仕組み(入力に対してなんらかの処理を行い、その結果を返す)はJavaと同じ。
JavaScriptでは関数はデータ型の一種
として扱うのがJavaとの大きな違い。・『関数の引数に名前を指定する』
・『複数の戻り値をそれぞれ指定した変数に代入する』 ...etc型推論もあるので、かなり柔軟に関数や変数の設定が出来る。
関数の定義
主に以下の4つ(3つの)の方法がある
・
function命令による定義
・ functionコンストラクターによる定義
・関数リテラルによる定義(無名関数)
・アロー関数による定義
これら4つのうちfunctionコンストラクターは外部から任意のコードが実行できてしまう可能性があるので使用は推奨されていない、原則としてfunction命令・関数リテラルあるいはアロー関数のどれかで定義することになるので実際には3つの方法ということになる。
function命令による定義
関数の定義の最も基本的な方法。
function命令function hoge(hage, ...){ //任意の処理 return piyo }関数の実装といえば基本的にはこれ。
関数の中に関数を実装したり、処理の中で一回限りの処理をする関数を実装したい場合は後述の関数リテラル
やアロー関数
などを使う。関数リテラル・アロー関数
匿名関数
、無名関数
とも呼ばれる。
変数として代入したり、関数の引数や戻り値として利用できるので非常に柔軟なコーディングができるようになる。関数リテラルvar hoge = function(hage, piyo){ return hoge * piyo }アロー関数を利用する事でfunctionキーワードを省略できるのでさらにシンプルに記述できるようになる。
アロー関数var hoge = (hage, piyo) => return hoge * piyo;引数が1つの場合は引数の()も省略することが出来る。
省略する際は引数が0個の場合は省略できないので注意。変数のスコープ
変数がコードのどの部分から参照できるのかを表す概念的なもの。
以下の3種類のスコープがある。・
ローカルスコープ
:関数の外で宣言した変数
・グローバルスコープ
:関数の中で宣言した変数
・ブロックスコープ
:if文などの{}内で宣言した変数ローカル・グローバルなどと名がついているので身構えてしまうが基本はJavaと同じである。
ES2015にてブロックスコープが実装されたため、挙動は基本的にJavaと似たような感じになった。引数
主に以下の3つの機能があげられる。
・引数のデフォルト値
・可変長引数
・名前付き引数ES2015までは実装にオブジェクトを必要としたが、ES2015からは実装にあたっての記法が簡略化され、簡素なコードでより柔軟なコーディングが可能になった。
引数のデフォルト値
『仮引数 = デフォルト値』の形式で記述することで仮引数にデフォルトの値を設定できる
デフォルト値の設定function gethoge(hage = 1, piyo = 1){ return hage * piyo / 2 }if文などで条件文気してやる必要がなく、簡単に実装できる。
例外をスローするだけの関数をデフォルト値として利用することで必須の引数を宣言することもできる。
hage = 1, piyo = hoge * 2
のように引数のデフォルトに仮引数の値を利用する事もできる。
仮引数の値を利用する際は自身より後に定義された引数は使えないので注意(上記のケースの場合、hageのデフォルト値にpuyoの値を使おうとするとエラーが起こる)。
また、関数呼び出しの引数にnull
を使うとデフォルト値ではなくnullが代入してしまう点、デフォルト値を設定した引数は引数の末尾に追加しないと思わぬ値が入ってしまうことがある事にも注意。可変長引数の関数
仮引数の前に
...
を入れることで可変長引数となる。
これによって、任意の数の引数を配列としてまとめて受け取る事が出来るようになる可変長引数の定義function hoge(...piyos){ //任意のコード return piyos }配列として受け取るのでlengthやpush/shiftなどのすべての配列操作が可能。
また実引数で...
演算子を利用することで、配列内のすべての数値を展開可能。Math.maxメソッドなど配列の受け取りを許容していないメソッドに有効。名前付きの引数
{プロパティ名 = デフォルト値}
の形で記述する事で、名前付きの引数を簡素に表現する事が出来る。引数に名前を付けて定義function hoge({hage = 1, piyo = 1}){ //任意のコード return piyos }一度オブジェクトリテラル
{}
として渡された引数を分解して個別の値としてアクセスしている、やっていることはフィールドの値を取り出すのに近い
オブジェクトなので複数値を持つこともできる、値を取り出すときはプロパティ名を指定してやると値を取り出せる。呼び出し側のコードを変えることなく取り出す値を変えたりする時に便利。長くなってしまったので今回はここまで
- 投稿日:2020-04-01T15:41:45+09:00
WebUSBでFeliCaのIDmとMIFAREのIDも読み込む
はじめに
WebUSBでFeliCaの一意なIDであるIDmを読む / Qiita
こちらの記事を参考に、FeliCaだけではなく、MIFAREのIDも拾いたい。
いや、むしろ、どちらも拾えるものをということで、公開していただいているgithubをフォークして、書き加えてみましたので、
経緯、やったこと、調べたことをこちらにまとめておこうと思います。結果のソースだけあればいいという人は、完成物をDLしてご利用ください。
注意
それらしく動いてますが、pythonは読んだことないわ、仕様書は難しくて頭痛くなるわで、
サンプルのパケットを解析して、コピペするという作業をしてますので、
追加した内容については、どうして動いているのかということは分かっていません。
使う際は自己責任で。もしくはコメントを頂ければ。完成物
https://github.com/marioninc/webusb-felica
元ネタと同様で、権利フリーです。要件
Google Chrome最新(2020/04/01現在)
PaSori(RC-S380)
MIFARE Classicカード
Windows10(MacOSや、Linuxですと、ドライバーを書き換えず動くようです) (Windowsで)WebUSBでPasoriを扱ってみる / Qiita元ソースとの変更箇所
1.Google Chromeのバージョンアップでの仕様変更対応
最近(2019/03/27)、自分で書いた方法に従って実験してみたのですが、Windowsで動作させる事が出来なくなっていました。
下記の様なエラーが発生して、正常に動作しません。
原因は不明なのですが、変わった事と言えば、ChromeのVersionが上がった位なので、それが何か影響しているのでしょうか…。
後は、Windows 10のUpdateが入ったこと、エディションがHomeからProに変わった事くらいですが、それは関係無いと思っています。
因みに解決策は下記の通り。
ソースコード(html)をローカルに保存して、185行目位にある、protocolCodeの代わりに、productIdを指定します。引用元:(Windowsで)WebUSBでPasoriを扱ってみる | 補足(2019/3/27追記) / Qiita
についての修正2.今回の本題
MIFAREのID取得部分await send(device,[0x00,0x00,0xff,0xff,0xff,0x03,0x00,0xfd,0xd6,0x06,0x00,0x24,0x00]); await receive(device, 6); await receive(device, 13); // <<< 0000ffffff0300fdd707002200 await send(device,[0x00,0x00,0xff,0xff,0xff,0x06,0x00,0xfa,0xd6,0x00,0x02,0x03,0x0f,0x03,0x13,0x00]); await receive(device, 6); await receive(device, 13); // <<< 0000ffffff0300fdd701002800 await send(device, [0x00, 0x00, 0xff, 0xff, 0xff, 0x28, 0x00, 0xd8, 0xd6, 0x02, 0x00, 0x18, 0x01, 0x01, 0x02, 0x01, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x08, 0x08, 0x00, 0x09, 0x00, 0x0a, 0x00, 0x0b, 0x00, 0x0c, 0x00, 0x0e, 0x04, 0x0f, 0x00, 0x10, 0x00, 0x11, 0x00, 0x12, 0x00, 0x13, 0x06, 0x4b, 0x00]); await receive(device, 6); await receive(device, 13); // <<< 0000ffffff0300fdd703002600 await send(device, [0x00,0x00,0xff,0xff,0xff,0x0c,0x00,0xf4,0xd6,0x02,0x01,0x00,0x02,0x00,0x05,0x01,0x00,0x06,0x07,0x07,0x0b,0x00]); await receive(device, 6); await receive(device, 13); // <<< 0000ffffff0300fdd703002600 await send(device, [0x00,0x00,0xff,0xff,0xff,0x05,0x00,0xfb,0xd6,0x04,0x36,0x01,0x26,0xc9,0x00]); await receive(device, 6); await receive(device, 20); // <<< 0000ffffff0900f7d705000000000804001800 await send(device, [0x00,0x00,0xff,0xff,0xff,0x06,0x00,0xfa,0xd6,0x02,0x04,0x01,0x07,0x08,0x14,0x00]); await receive(device, 6); await receive(device, 13); // <<< 0000ffffff0300fdd703002600 await send(device, [0x00,0x00,0xff,0xff,0xff,0x06,0x00,0xfa,0xd6,0x02,0x01,0x00,0x02,0x00,0x25,0x00]); await receive(device, 6); await receive(device, 13); // <<< 0000ffffff0300fdd703002600 await send(device, [0x00,0x00,0xff,0xff,0xff,0x06,0x00,0xfa,0xd6,0x04,0x36,0x01,0x93,0x20,0x3c,0x00]); await receive(device, 6); let idt = (await receive(device, 22)).slice(15, 19); // <<< 0000ffffff0c00f4d705000000000800000000490c00 if (idt.length > 2) { let idtStr = ''; for (let i = 0; i < idt.length; i++) { if (idt[i] < 16) { idtStr += '0'; } idtStr += idt[i].toString(16); } idmMessage.innerText = "Card Type : MIFARE カードのID: " + idtStr; idmMessage.style.display = 'block'; waitingMessage.style.display = 'none'; return; } else { idmMessage.style.display = 'none'; waitingMessage.style.display = 'block'; }の2か所です。
なぜ改造したのか
この度、WEB上で動く勤怠管理システム上で、ICカードタッチで出退勤をしたいという話をいただきました。
Google先生に聞いてみると元ネタの記事が出てきたので、
これで簡単にできそうだぞと、とりあえずPaSoriを用意して、社員証をかざしても、あれ、IDが出ない……このIDカードFeliCa対応のものではなく、MIFARE Classicと呼ばれる規格のもののようで、
使い捨てを前提として、IDが一意ではない(IDが被らないように管理する機関がない)カードらしいのです。
このことが原因で、IDを取得するためのリクエストコマンドも違えば、返ってくる応答も違うという具合で、FeliCa的にはIDmが取得できなかっただけでした。さて、どうしたものかという話
そもそもデバイス関係は専門外で、JIS規格の仕様書は入手しても、不慣れなので読むのが大変。
元ネタの元ネタになっているライブラリはpythonで書かれているようで、pythonも触ったことがない。
nfcpyは、Windows で nfcpy のセットアップ / Qiitaを見ながら、
とりあえず、動くようにはなりましたが、元ネタの記事みたいに、
ライブラリを読み解いて自分のものにはできず、うまく実装までこぎつけられませんでした。ライブラリが読めないなら、パケットを読めばいいじゃない
WEBUSBというものは、直接、パケットを叩くかなり低レイヤーな印象のものです。とどこかの記事で書かれていました。
つまり、意味はちゃんと分かっていなくても、通信さえマネできれば動くということではないか。
そう。ライブラリが読めないなら、直接パケットを読めばいいじゃない。
というわけで、パケットを盗み見るなら、おなじみWireshark
実は、標準の機能だけで、TCPの通信だけではなくUSBのパケットも読めるそうです。そうとなれば、
1. nfcpyのサンプルにある、tagtool.py
を実行して、
2. Wiresharkで監視。
3. 手元にあったSuicaをタッチ。
4. もう一度、tagtool.py
を実行して、
5. Wiresharkで監視。
6. MEFAREの社員証をタッチ。
7. 適当にデータを加工して、比較する。読んだ結果(MIFARE)out // FeliCaのIDmを取得するコマンド 0000ffffff0a00f6d6046e000600ffff0100b300 in // カードがない状態と同じ 0000ffffff0600fad70580000000a400 out 0000ffffff0300fdd606002400 in 0000ffffff0300fdd707002200 out 0000ffffff0600fad60002030f031300 in 0000ffffff0300fdd701002800 out 0000ffffff2800d8d6020018010102010300040005000600… in 0000ffffff0300fdd703002600 out 0000ffffff0c00f4d602010002000501000607070b00 in 0000ffffff0300fdd703002600 out // カードを識別しているコードぽい 0000ffffff0500fbd604360126c900 in //MIFAREを置いているときの状態 0000ffffff0900f7d705000000000804001800 out 0000ffffff0600fad602040107081400 in 0000ffffff0300fdd703002600 out 0000ffffff0600fad602010002002500 in 0000ffffff0300fdd703002600 out // MIFAREのIDを取得するコード 0000ffffff0600fad604360193203c00 in // *がMIFAREのID 0000ffffff0c00f4d7050000000008**********0c00こんな感じになっていました。
out
がRC-380に送信しているパケット
in
がnfcpyに返信されたパケットです。…にっているところは、ログを書き出したときに、長すぎて省略されてしまったものです。
ソースコードに乗せてあります。ここまでの部分では、待機状態でループ処理が走っています。
下から3番目の
0000ffffff0600fad602040107081400
から3つは、Suicaや、タッチしていない状態では、一度も流れていないコマンドでした。
その上のカードを識別しているコードぽい
の部分で、初めて返信パケットに変化があり、そこから分岐がされているようです。
カードを識別しているコードぽい
では、未検出時とSuicaの時は、0000ffffff0600fad70580000000a400
でした。実装
今回は、決済をしたいわけでも、なにかデータを書き込みたいわけでもないので、IDが読めれば十分でした。
ですので、せっかく分岐点を見つけたのですが、FeliCaの読み込みに失敗したら、MIFAREのIDを読み込んでみるという感じで実装しました。
FeliCaのIDmを読んだ直後にMIFAREのIDを読みに行っても、IDは返ってこなかったので、
上の結果のFeliCaのIDmを取得するコマンド
のすぐあとから、コピペして、実装しました。感想など
今回はかなり無理やりな感じがします。よくわかってないけど動いたはよろしくないですね。
実際問題、IDを見るだけならこれでもいいかもしれませんが、読み書きをする場合はちゃんと仕様書を読まないといけないなと。pythonのライブラリを読み解こうとしたり、ICカードの仕様書を読み解こうとしたり、USBのパケットをのぞいたり……
色々なことがやって見れたので面白くはありましたが、ちゃんと作らないと規模が大きくなると使えないなあ。解説など、いただける方がいれば、拝読させていただきます。
使用したもの
python3
nfcpy
libusb-1.0.dll
Wireshark
Zadig参考文献
WebUSBでFeliCaの一意なIDであるIDmを読む / Qiita
(Windowsで)WebUSBでPasoriを扱ってみる / Qiita
WebUSBことはじめ / Qiita
WiresharkでUSBメモリのパケットキャプチャしてみた! / LoT ラブオーティー
Windows で nfcpy のセットアップ / Qiita
- 投稿日:2020-04-01T14:12:23+09:00
Next.js + Firebase Hosting + SSR 環境でBasic認証をかける
概要
Next.js + Firebase Hosting + SSR 環境でBasic認証をかけようという記事。
Next
が持っているメソッドだけでは対応できなかったので、Express
も使用します。また、Next.js + Firebase Hosting + SSR 環境については、
with-firebase-hosting-and-typescript こちらのテンプレートを利用します。
Firebase
を利用する関係上node.js
のバージョンは10系を使用します。
firebase-tools
は最新を使用しましょう。手順
Next.js + Firebase Hosting + SSR 環境のセットアップ
README.md を参考にセットアップしてみてください。
yarn create next-app --example with-firebase-hosting-and-typescript with-firebase-hosting-and-typescript-app
モジュールのインストール
- express
- @types/express
- next-routes
- basic-auth-connect
yarn add express next-routes basic-auth-connectyarn add -D @types/expressエラー対応
Error: > Couldn't find a `pages` directory. Please create one under the project root
2020/04 現在ではデプロイ時に上記エラーが出るため、functionsを修正します。
src/functions/index.tsconst app = next({ dev, conf: { distDir: 'next' } })↓
src/functions/index.tsconst app = next({ dev: false, conf: { distDir: 'next' } })実装
Express
側でベーシック認証を通して、Next
のアプリに流します。src/functions/index.tsimport * as functions from 'firebase-functions'; import next from 'next'; import express from 'express'; /* eslint-disable @typescript-eslint/no-var-requires */ const routes = require('next-routes'); const basicAuth = require('basic-auth-connect'); const USERNAME = 'user'; const PASSWORD = 'password'; const server = express(); const app = next({ dev: false, conf: { distDir: 'next' } }); const handler = routes().getRequestHandler(app); server.use(basicAuth(USERNAME, PASSWORD)); server.get('*', (req, res) => handler(req, res)); export const nextApp = functions.https.onRequest(server);Firebase Project の設定
<project-name-here>
こちらを自身のプロジェクト名に変更します。{ "projects": { "default": "<project-name-here>" } }実行
localで実行
firebase-emulators
を使ってローカルでもBasic認証の確認ができます。Build
yarn preserve
Run
firebase emulators:start
デプロイ
firebase deploy
以上 ☺️
- 投稿日:2020-04-01T13:11:08+09:00
初めてのVue.js備忘録 vol.01
はじめに
今回は、エンジニアになって約半年の私がjavascriptのフレームワークであるVue.jsを使って開発を行ったので、そのVue.jsの基本的な記述について備忘録としてまとめます。
データバインディング
テキストのバインディング
- データのバインディグのもっとも基本的な形 マスタッシュ {{ }}を利用したもの
- 以下のような使い方ができます。
App.vue<template lang="pug"> #app span {{ msg }} //hello </template> <script> export default { data(){ return{ msg:"hello" } } </script>App.vue<template lang="pug"> .app span {{inc}} //numに +1された数字がバインディングされる </template> <script> export default { data(){ return{ num:10 } }, computed:{ inc(){ return this.num + 1 } } }; </script>属性のバインディング
- {{ }}マスタッシュはHTML属性の内部では使えない
- 代わりに v-bindディレクティブを使用する
App.vue<template lang="pug"> #app span(v-bind:class="dynamicClass") {{ msg }} </template> <script> export default { data(){ return{ msg:"hello", dynamicClass:"hoge" } } </script> <style> .hoge{ display:inline-block; } .fuga{ display:none; } </style>dynamicClassの値をhoge⇨fugaに変更すると、spanのクラスが変更され、非表示になります。
親子間のデータの受け渡し
- Vue.jsでの親子間のデータの受け渡しは、『props down, events up』
- 親はプロパティを経由してデータを子に伝える
- 子はイベントを経由して親にメッセージを送る
という認識でいます。
以下のように子コンポーネントでpropsオプションを指定します。
CustomInput.vue<template> ... input(type="text" :placeholder="customPlaceholder" @input="updateValue") ... </template> <script> export default = { props:{ customPlaceholder:{ type:String, default:"" } }, methods: { updateValue (e) { this.$emit('input', e.target.value) this.$emit('change', e.target.value) } } } </script>CustomInput.vuethis.$emit('input', e.target.value)この部分で子コンポーネントからinputに入力されたvalueを親に渡しています。
親コンポーネントでは、子に渡すデータを指定、inputEmailの値が子のコンポーネントに渡されます。
Form.vue<template> custom-input(:customPlaceholder="inputEmail") </template> <script> import CustomInput from "./components/CustomInput" export default = { components:{ CustomInput }, data(){ return{ inputEmail: "メールアドレスを入力してください" } } } </script>だいぶ掻い摘んで書いてみましたが、書き方どうだっけ?的なときのために。
- 投稿日:2020-04-01T09:46:41+09:00
babelの設定ファイルをTypeScriptで書く
表題の通りです。
babel
の設定ファイルといえば.babelrc
babel.config.js
での記述が一般的だと思います。これをbabel.config.ts
に記述できるようにします。1. 必要モジュールのインストール
とりあえず
webpack
前提で必要最低限なものだけインストールします。npm i -D webpack webpack-cli typescript ts-node @babel/core @types/babel__core babel-loader @babel/preset-envほとんど説明するほどのものではないですが、重要なのは
@types/babel__core
です。
ここに設定ファイルで使用する型情報が載っています。2. TypeScriptの設定
tsconfig.json
を書きます。tsconfig.json{ "ts-node": { "compilerOptions": { "module": "commonjs", "target": "es5", }, }, }ここでは
ts-node
の設定だけ行います。3. babelの設定
ここからが本題です。
babel.config.ts
を書きますが、 設定の読み込み方によって書き方も変わってきます。
だいたい次の2通りに分けられるんじゃないでしょうか。
- プロジェクトルートにある
babel
の設定ファイル(.babelrc
babel.config.js
)をbabel
が自動的に読み込むwebpack
から実行環境によって手動(設定ファイルのコード上)で呼び出す設定を変える上記2パターンそれぞれについて説明をしていきます。
3.1. 設定ファイルを自動的に読み込む場合
この場合、
babel.config.ts
は次のように書くことができます。babel.config.tsimport { TransformOptions, ConfigAPI } from '@babel/core' export default function (api: ConfigAPI): TransformOptions { return { presets: [ '@babel/preset-env', ], } }
TransformOptions
とConfigAPI
という見慣れないものが出てきましたが、TransformOptions
はお馴染みの babelの設定ファイルの内容 、ConfigAPI
は babelの設定API のことです。
これを.ts
ではなく.js
で書くと次のようになります(ドキュメントにほぼ同じものが載っていますが)。babel.config.jsmodule.exports = function(api) { return { presets: [ '@babel/preset-env', ], }; }ここまでくればあとは
babel.config.ts
をbabel
に読み込ませるだけですが、babel
はwebpack
のように.ts
の設定ファイルをいい感じに解釈してくれません。webpack.config.ts// ... module: { rules: [ { test: /\.[jt]sx?$/, loader: 'babel-loader', exclude: /node_modules/, options: { configFile: 'babel.config.ts' }, }, // ...このように書いたとしても
babel
はbable.config.ts
をJSONとして読み込もうとするので構文エラーになります。
そこでtsc
コマンドで.js
ファイルにトランスパイルする必要があります。npx tsc babel.config.tsこれで
babel.config.js
が生成されるので設定ファイルを読み込むことができますが、いちいちトランスパイルするのは面倒ですし、babel.config.ts
とbabel.config.js
の2ファイルが並ぶのはあまり気持ちのいいものではないですね。3.2. webpackから実行環境によって設定を変える場合
3.1. の方法だとあまりすっきりできないので、自分はこちらの方法を採用しています。
特にwebpack4
ではwebpack-merge
を使って呼び出す設定ファイルを切り替えるのがメジャーらしい(?)のでこちらの方が合っている気がします。babel.config.tsimport { TransformOptions } from '@babel/core' // 開発環境用の設定 export const dev: TransformOptions = { presets: [ '@babel/preset-env', ], sourceMaps: true, } // 本番環境用の設定 export const prod: TransformOptions = { presets: [ '@babel/preset-env', ], }webpack.config.dev.tsimport { Configuration } from 'webpack' import merge from 'webpack-merge' import config from './webpack.config.common' import { dev as devBabelConfig } from './babel.config' const devConfig: Configuration = merge(config, { mode: 'development', devtool: 'source-map', devServer: { contentBase: path.resolve(__dirname, 'dist'), }, module: { rules: [ { test: /\.[jt]sx?$/, loader: 'babel-loader', exclude: /node_modules/, options: devBabelConfig, }, // ... } export default devConfigwebpack.config.prod.tsimport { Configuration } from 'webpack' import merge from 'webpack-merge' import config from './webpack.config.common' import { prod as prodBabelConfig } from './babel.config' const prodConfig: Configuration = merge(config, { mode: 'production', module: { rules: [ { test: /\.[jt]sx?$/, loader: 'babel-loader', exclude: /node_modules/, options: prodBabelConfig, }, // ... } export default prodConfigおわりに
以上、
babel
の設定ファイルをTypeScript
で書いてみました。
TypeScript
で記述することでタイポなども減らせていいんじゃないでしょうか。
もっといい方法などあれば教えて頂けるとうれしくなります。
- 投稿日:2020-04-01T09:23:24+09:00
JavaScriptの配列操作
配列操作
メソッド
splice(開始する位置, 削除数 [, 追加する要素, ...])
その他操作
const a = [1, 2, 3, 4, 5]; const b = [6, 7, 8, 9, 10]; //配列をなくす方法 const c = [...a, ...b]; //分割代入 const [d, e, f] = a //rest構文 const [g, h, ...i] = a console.log(...a); // 1 2 3 4 5 console.log(c); // [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] console.log(d, e, f); // 1 2 3 console.log(g, h, i); // 1 2 [ 3, 4, 5 ] //値の交換 let [x,y,z] = [1,2,3] console.log(x,y,z); // 1 2 3 [x, y, z] = [y, z, x]; console.log(x, y, z); // 2 3 1 //オブジェクトの取り出し //プロパティ名にないものを指定しようとするとエラーになる const j = {k: 1, l: 2, m: 3, n: 4, o: 5}; const {k, l, ...p} = j; console.log(k, l, p); // 1 2 { m: 3, n: 4, o: 5 }
- 投稿日:2020-04-01T09:23:24+09:00
JavaScriptの配列とオブジェクト操作
配列操作
const a = [1, 2, 3, 4, 5]; const b = [6, 7, 8, 9, 10]; //配列の結合 const c = [...a, ...b]; //分割代入 const [d, e, f] = a //rest構文 const [g, h, ...i] = a console.log(...a); // 1 2 3 4 5 console.log(c); // [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] console.log(d, e, f); // 1 2 3 console.log(g, h, i); // 1 2 [ 3, 4, 5 ] //値の交換 let [x,y,z] = [1,2,3] console.log(x,y,z); // 1 2 3 [x, y, z] = [y, z, x]; console.log(x, y, z); // 2 3 1参照渡したくない場合
const a = [1, 2]; const b = a; const c = [...a]; a[0] = 3; console.log(b); // [3, 2] console.log(c); // [1, 2]配列操作のメソッド
splice(開始する位置, 削除数 [, 追加する要素, ...])
オブジェクト操作
const a = {c: 1, d: 2, e: 3}; //オブジェクトの結合 const b = {...a, f: 4, g: 5}; //分割代入,存在しないキーを指定するとエラーになる const {c, d, ...h} = b; console.log(b); // { c: 1, d: 2, e: 3, f: 4, g: 5 } console.log(c, d, h); // 1 2 { e: 3, f: 4, g: 5 } //キーを取り出す方法 const i = Object.keys(b); console.log(i); // [ 'c', 'd', 'e', 'f', 'g' ] //キーと値を表示 for(let key of i) console.log(`key:${key} value:${b[key]}`); /* key:c value:1 key:d value:2 key:e value:3 key:f value:4 key:g value:5 */
- 投稿日:2020-04-01T09:02:53+09:00
EdgeHTML の browser API 不具合集 (2020年4月)
概要
EdgeHTM エンジン版 Edge の browser/chrome API の不具合の覚え書き。
Chrome (V8 8.0.426 / Blink 80) で普通に動作するかどうかを基準に判断。
Chromium エンジン版 Edge は未検証。
どうせ Chromium に移行するつもりだったからなのか EdgeHTML は色々ガバガバ。
※ browser/chrome API のほぼ全ての非同期関数は、最終引数に関数を渡せばコールバック型で利用できる。また、Chrome でも「Promise を return する事になっているが実はコールバック型でしか使えない関数」が幾つか存在する (具体例要追記)。MDN (英語版しか読んでいないが) ではそれらに関する記述がほぼ皆無なので要注意。検証環境
- 2020年4月時点
- Windows 10 Pro x64
- Microsoft Edge 44.18362.449.0 (EdgeHTML 18.18363 / ChakraCore 1.11.15)
- 下記のモンキーパッチ(?)を適用.
chrome
はbrowser
と同義background.js/content.jsif(/edge/i.test(window.navigator.userAgent)){ window.chrome = window.browser; console.log('EdgeHTML SUCKS!!!'); }Microsoft Edge の長所
ブラウザ拡張機能開発において Edge を使う利点は content script の変更が即時反映される点。ただこの一点に尽きる。
Chrome では拡張機能自体を「更新」しなければ content script の変更が反映されない上、「更新」しても content script や各種リソースの変更が反映されないバグがある為、変更を加える度にブラウザを再起動する必要がある。
これに対して Edge は、content script を変更してから対象URLのページをリロードすれば即座に新しい content script が適用されるのでデバッグが非常に楽。非同期関数は Promise ではなく undefined を返す
見出しの通り。なので、Chrome では then でチェーン出来る関数(メソッド)でも、Edge では 絶対に 直接チェーン出来ない。
本来 Promise を返すことになっているメソッドは、最後の引数にコールバック関数を入れて使う。
(失敗判定はtry...catch
やchrome.runtime.lastError
またはchrome.extension.lastError
などで行う。)background.js// [エラーになる例] chrome.tabs.query({'url':'https://qiita.com/*'}) .then(tabs=>console.log(tabs.length)); //"Error: Invocation of form tabs.query(object) doesn't match definition tabs.query(object queryInfo, function callback)." // [上手くいく例] chrome.tabs.query( {'url':'https://qiita.com/*'}, tabs=>console.log(tabs.length) ); //1background script が cross origin 非対応
例えば background script で WebSocket を開こうとすると「Access-Control-Allow-Headersがないです~ べろべろば~」などのエラーを吐く。
Fetch や XHR は未検証だが同じだろう。
Chrome では問題なく接続できる。
_generated_background_page.html のヘッダーを生成しているのは Edge の拡張機能管理機能なので、恐らく回避は不可能。比較的重要なメソッドが初めから存在しない
chrome.tabs.connect
がない為、background script → content script の port 接続ができない.他にもあったけど忘れたので後日追記.
native messaging が異常に難しい
普通の (Edge 以外の) ブラウザでは比較的容易に 拡張機能 <―> ネイティブアプリ間通信 (native messaging) を実装できる。
いくつかの設定ファイルを用意して少しレジストリを弄るだけなので、テキストエディタさえあればいい。
これに対して Edge で native messaging を実装するには、絶対に最新の Visual Studio を使わなければならない。
Edge では、拡張機能とネイティブアプリの通信は特別なプロトコル (AppService) を用いる。そのプロトコルに対応するアプリケーション (UWP application) は Visual Studio でしか作成できない (Visual Studio でセキュリティートークンを発行しないと AppService を起動できない)。
ネイティブアプリの代わりにローカルサーバーを立てて WebSocket や Fetch で通信しようとしても、前述のように「cross origin access forbidden 」とか言われる。
EdgeHTML 版 Edge での native messaging は潔く諦めた方がいい。
どうしてもやりたいなら Chromium 版 Edge に切り替えよう。
最終決定!「でも」とか「やっぱり」は許さん!二度と試すな!EdgeHTML SUCKS!!!!
- 投稿日:2020-04-01T08:09:57+09:00
Vue.js公式チュートリアルをゆっくり読んでいく2
前置き
前回に引き続き、Vue.jsの公式チュートリアルをゆっくり読んでいきます。
チュートリアルに沿ってはいますが、サンプルはよりわかりやすく単純なものに一部変えているところもあります。前回は主にVue.jsのリアクティブシステムに触れました。今回は、Vue.js上では「ディレクティブ」と呼ばれているVue.js特有の属性を見ていきたいと思います。
Vue.jsのバージョン:v2.6.11
今回のチュートリアル参照箇所
v-bind:要素の紐付け
前回はVueインスタンスのdataプロパティに持たせたデータオブジェクトの内容を、elプロパティと紐付けた「HTML要素の中で」
{{ message }}
という形式で表示させました。<div id="app"> {{ message }} </div> <script> let vm = new Vue({ el: '#app', data: { message: 'Hello Vue!' } }); </script>この「HTML要素の中で」ということですが、HTML要素の属性部分には効くのでしょうか?
次のようにdataオブジェクトのmyFavoriteColorStyleに持たせたcssを#app要素のstyle属性に反映できるかやってみます。<div id="app" style="{{ myFavoriteColorStyle }}"> {{ message }} </div> <script> let vm = new Vue({ el: '#app', data: { message: 'Hello Vue!', myFavoriteColorStyle: 'color:red', } }); </script>style属性は反映されず、警告がでてしまいました。HTML属性部分には効かないようです。
しかし、Vue.jsが親切に警告内でどう書けばいいか教えてくれています。
For example, instead of <div style="{{ val }}">, use <div :style="val">.
これで書き直してみます。<div id="app" :style="myFavoriteColorStyle"> <!-- {{ }} で囲まない --> {{ message }} </div> <script> let vm = new Vue({ el: '#app', data: { message: 'Hello Vue!', myFavoriteColorStyle: 'color:red', } }); </script>今度は警告もでず、スタイルが適用されました。
ここで使用した:style
ですが、正式にはv-bind:style
と書きます。:style
は省略記法です。v-bind属性はVue.js上ではディレクティブと呼ばれており、
v-bind:属性名
とすることで、
その属性に対してデータオブジェクトの内容を紐付けることができます。
もちろんこれもリアクティブなものなので、chromeのデベロッパーツールのコンソールで
vm.myFavoriteColorStyle = "color:blue"
と打つと、すぐにそれが反映されます。v-if:条件分岐
v-ifディレクティブは要素の表示、非表示を切り替えることができます。
<div id="app" v-if="seen"> {{ message }} </div> <script> let vm = new Vue({ el: '#app', data: { message: 'Hello Vue!', seen: true, } }); </script>v-ifディレクティブは厳密には、要素の表示・非表示を切り替えるのではなく、要素を削除・再生成しています。
vm.seen = false
したときにHTML要素を確認してみます。要素の表示・非表示を切り替えるディレクティブは、v-ifの他にv-showもあります。
こちらは、要素のスタイルをdisplay:none
にするだけなので、要素自体は削除されません。
v-ifは要素を削除・再生成する一方、v-showはスタイルを切り替えるだけなので後者のほうが高速に動作します。<div id="app" v-show="seen"> {{ message }} </div> <script> let vm = new Vue({ el: '#app', data: { message: 'Hello Vue!', seen: true, } }); </script>ところでチュートリアルにも記載がありますが、Vue.jsは上記のような要素の更新が行われたときにトランジション効果を
簡単につけることができます。例えばさきほどのv-ifディレクティブの例に、フェード効果をつけてみます。<div id="app"> <!-- トランジション効果をつけたい要素をVue.jsに予め用意されているtransitionタグ(コンポーネント)で囲みます --> <transition name="fade"> <!-- name属性で指定した名前を後述のstyleで使います --> <p v-if="seen">{{ message }}</p> </transition> </div> <style> /* 「transitionタグのname属性で指定した名前 + "-enter"」で要素が追加される直前、 「transitionタグのname属性で指定した名前 + "-leave-to"」で要素が削除された直後を表すクラス名 */ .fade-enter, .fade-leave-to { opacity: 0; /* 透明状態 */ } /* 「transitionタグのname属性で指定した名前 + "enter-active"」で要素の追加中 「transitionタグのname属性で指定した名前 + "leave-active"」で要素の削除中を表すクラス名 */ .fade-enter-active, .fade-leave-active { transition: opacity .5s; /* 0.5秒かけて透明度を変化させる */ } </style> <script> let vm = new Vue({ el: '#app', data: { message: 'Hello Vue!', seen: true, } }); </script>参考:Enter/Leave とトランジション一覧 — Vue.js
v-for:ループ
v-forは配列の内容を取り出して表示してくれます。
<ul id="app"> <li v-for="message in messages"> {{ message }} </li> </ul> <script> let messages = ['Hello','v-for','directive']; let vm = new Vue({ el: '#app', data: { messages: messages, } }); </script>もちろんリアクティブ。
v-on:イベントハンドリング
v-onディレクティブはその要素に対してイベントリスナを設定してくれます。
<button id="app" v-on:click="changeMessage"> {{ message }} </button> <script> let vm = new Vue({ el: '#app', data: { message: 'Hello Vue!', }, methods: { changeMessage: function() { this.message = 'Hello Vue World!' }, } }); </script>ところで、これをVue.jsを使わないで実現するとどうでしょうか。
<button id="app" onclick="changeMessage()"> Hello Vue! </button> <script> function changeMessage() { document.getElementById("app").innerHTML = 'Hello Vue World!'; } </script>Vue.jsを使わなくてももちろんできます。むしろこれくらいならこちらのほうが全然シンプルです。
ではVue.jsを使うメリットは何でしょうか。次の場合を考えてみます。例えば、button要素の中のテキストを赤くしたとします。
<button id="app" onclick="changeMessage()"> <span style="color:red">Hello Vue!</span> </button> <script> function changeMessage() { document.getElementById("app").innerHTML = 'Hello Vue World!'; } </script>せっかく赤くしたテキストがクリックすると黒に戻ってしまいました。
innerHTMLは要素内のHTMLを設定するプロパティなので、テキスト部分だけ変更したい場合は、
例えば次のようにスクリプト部分を修正する必要があります。<button id="app" onclick="changeMessage()"> <span style="color:red">Hello Vue!</span> </button> <script> function changeMessage() { document.querySelector("#app span").innerHTML = 'Hello Vue World!'; } </script>これでうまくいきました。でもこれは、「innerHTMLが要素内のHTMLを設定するプロパティであること」を知っていること、
それを知った上で、要件を満たすために、「querySelectorを使う」「Hello Vue!部分だけを書き換えるためにid="app"要素の
中のspan要素を指定するセレクタを記載する」など、DOMを操作するためのいくつかの追加の知識が必要です。これをVue.jsで実現すると、
<button id="app" v-on:click="changeMessage"> <span style="color:red">{{ message }}</span> </button> <script> let vm = new Vue({ el: '#app', data: { message: 'Hello Vue!', }, methods: { changeMessage: function() { this.message = 'Hello Vue World!' }, } }); </script>スクリプト部分は一切変更せずにできました。これはVue.jsがDOM操作をやってくれるからです。
Vue.jsを使うことで、DOM操作を気にせず、ロジックのみに集中できることがVue.jsのメリットの一つでもあります。今回はここまでです。
- 投稿日:2020-04-01T07:39:07+09:00
自分やチームのLGTMを可視化するツールを作ってみた
はじめに
チームの1年間のいいね数をD3.jsを使ってアニメーションで可視化する
以前この記事で、チームのいいねLGTMをD3を使ったアニメーションで可視化するということをやりました。
今回はQiitaのユーザーIDを入力するだけで、同じような可視化ができるQiitaRacerというものを作ったのでご紹介します。サービス
Qiita Racer
※Github Pagesで公開していますソースもこちらに公開しています。
https://github.com/tonio0720/QiitaRacing使い方
URLにアクセスすると「アクセスの許可を求めています」と出るのでそのまま許可してください。
ユーザーID(最大8個)を入力し、データ取得を実行すると動きます。
※QiitaAPIの制限の都合により、総LGTM数が1000以上のユーザーは抽出できないように制御しています。所感
今回はツールを作って、Github Pagesに公開するということをやってみました。
初めて使いましたが、とても便利ですね。
Qiita APIもCORS制約に引っかかることなく使えたので助かりました。Qiita APIの1時間に1000リクエストの制約は少し不便ですね。
本ツールではユーザーに紐づくすべての記事を取得して、それぞれの記事のLGTM数を取得するということをやっているので、結構APIを消費します。
1ページにおける最大取得件数が100件というのもあり、あまりこういう使い方は向いていないのだなと感じました。
本ツールでは総LGTM数が1000を超えるユーザーは抽出できないように制御しています。可視化においてはReact+AntDesign+D3.jsの合わせ技でやっています。
利用したもの
- React
- Ant Design
- D3.js
- Qiita API
- Github Pages(公開用)
参照したサイト
- https://qiita.com/api/v2/docs
- https://qiita.com/sota_mikami/items/c6038cf13fd84b519a61
- https://qiita.com/tomoyukilabs/items/81698edd5812ff6acb34
- https://qiita.com/nutti/items/688de20382e60286d26d
- https://qiita.com/tonio0720/items/0b9d670389286171af07
- https://qiita.com/tonio0720/items/2548d810e37c442aa540
- 投稿日:2020-04-01T03:25:16+09:00
ムーモ(moomo)の口コミや評判は悪い?実際に使用した方の口コミを調べました
ムーモ(moomo)の口コミや評判は悪い?実際に使用した方の口コミを調べました
実際の所どうなのでしょうか?
詳しい内容はこちらです
- 投稿日:2020-04-01T02:10:21+09:00
JavaScript オブジェクト
はじめに
皆さん、こんばんは?
yuです?
最近、ここ2日間ぐらい、投稿ができませんでした。
理由として、
転職先を、フロントエンドエンジニアになることを目標に置き、
どのような、言語を学ベば良いのか迷っていました。私なりに考え、それは、JavaScript関連だと思いました。
より一層、追求していこうと思いました。これからも、JavaScriptを中心に投稿していきます。
よろしくお願いします?オブジェクトについて、初めていきます。
オブジェクト
まず「オブジェクト」を日本語に訳すと、「物」と出てきます。
本来はそうなのですが、Javascriptでは、「データの集まり」と、
理解しましょう。オブジェクト文を詳しく見ていきましょう↓?↓
const Giants = { sports : "野球",//プロパティ color : "オレンジ",//プロパティ playing : function() { document.write("ボールを投げる。");//メソッド } }上記のようになります。
オブジェクト文には
1 プロパティ
2 メソッド
というものが、入ってます。「プロパティ」とは、オブジェクト内の、データとなります。
特徴などで例えると、分かりやすいと思います。
上記で、例えると、Giantsというチームは、
野球を行い、色はオレンジというデータが入ってます。「メソッド」とは、オブジェクト内の、機能になります。
動きなどで例えると、分かりやすいと思います。
上記で例えると、Giantsというチームの動きは、
ボールを投げるで、これが機能になっています。まとめ
オブジェクトは2つの要素で出来ている。
・プロパティ
・メソッドオブジェクトは、
・人
・団体
などで、例えると、「プロパティ」、「メソッド」との関係性が
分かりやすくなる。本日は以上になります。
ありがとうございました?
- 投稿日:2020-04-01T00:03:55+09:00
asyncとdeferの仕様を誤解していた。
解決したい問題
勤務先のWebサイトで、他社製のスクリプトAの実行が完了する前に、自社製のスクリプトBが実行されるとIE11がクラッシュするバグが見つかりました。具体的には以下のように呼び出しています。
なお、A.jsの呼び出し用タグは他社からの指示で1文字たりとも変更できません。<html> <head> <!-- 自社製スクリプト --> <script src="./B.js"></script> <!-- 他社製スクリプト --> <script src="./A.js" async></script> </head> <body> </body> </html>クラッシュする原因は「B.jsの作りに問題があり、その問題をIE11だけが踏み抜くから」なのですが、A.jsの実行が完了していればクラッシュは回避できるため、B.jsがA.jsの実行を待てば良いと思い、以下のように書換えました。
<html> <head> <!-- 他社製スクリプト --> <script src="./A.js" async></script> <!-- 自社製スクリプト --> <!-- deferはasyncの完了も待ってくれることを期待した --> <script src="./B.js" defer></script> </head> <body> </body> </html>仕様を誤解していた箇所
deferはasyncの完了を待ってくれると勝手に思い込んでいたのですが、クラッシュしたりしなかったりする挙動になってしまい、状況が悪化しました。
WHATWGの規格書を読んでもどうしてもピンと来なかったため、検証用のコードを書くことにします。
挙動さえわかればいいのでコード自体はめちゃくちゃ適当です。色んな人から怒られそうなコードですね。検証用コード
html
<html> <head> <script src="./first.js" async></script> <script src="./second.js" defer></script> </head> <body> </body> </html>asyncで実行するjs (first.js)
var i = 0; for (i = 0; i < 500000000; i++) { } console.log("first.js: " + i);deferで実行するjs (second.js)
console.log("second.js: " + i);検証結果
Chrome 80で検証しました。
どちらもdeferがついていれば想定通りfirst->secondの順で呼び出されますが、asyncとdeferだと必ずasync->deferになるわけではないのですね。
実際に検証した後に <script> タグに async / defer を付けた場合のタイミング - Qiita を読むと大変わかりやすかったです。というか最初からこちらの記事を読んでいれば無駄に検証しなくても良かったのでは…。以下も検証しましたがやはりNGでした。10回に1回位はエラーが出ます。
<html> <head> <script src="./first.js" async></script> </head> <body> <script src="./second.js"></script> </body> </html>ということで、今の所思いつく解決策としては「A.jsの作成元にdeferで呼び出していいか確認する」か、「自社スクリプトB.jsを修正する」のどちらかになりそうです。思い込みは良くないですね。
フロントエンドは初心者なので頑張って勉強していきたいです。