20190127のJavaScriptに関する記事は16件です。

【翻訳】Aureliaトップページ翻訳

記事要約

Aureliaを学習するにあたって、Aurelia公式サイトトップページの “Why Aurelia?” の部分を翻訳しました。(2019/1/27時点)
なるべく直訳にするようにしましたが、直訳だとどうしてもわかりづらい部分はつながりの良い文章に直しています。

JS初心者向けの記事です。
単語には、参考になるリンクをつけました。外部サイトに飛ぶ場合もあります。

以下、翻訳です。

Aureliaを選ぶ理由(Why Aurelia?)

次世代のアプリケーションを作る人々に加わろう

学習しやすい(Easy to Learn)

規約に基づいた特殊すぎないスタイルによって、Aureliaは、vanilla JavaScriptTypeScriptを使ったコンポーネントを作成する機能を与えることのできる唯一のフレームワークになる。もしあなたがmodern JSやHTMLを知っているなら、最も複雑なアプリケーションを作るためにでさえ、学ぶ必要のあることは少ししかない。

高性能(High Performance)

Aureliaの核となるのは、高性能であること、リアクティブシステム、そして、他のフレームワークと仮想DOMをほこりにまみれさせて取り残してしまうような方法で、DOMの更新をまとめて処理する能力があること。UIがどれほど複雑であっても、一貫性と拡張性のある性能であることを体験してほしい。

大規模なエコシステム(Extensive Ecosystem)

コアチームによる公式プラグインとして、stateの管理に、国際化やバリデーションがある。
開発を向上させるためのオプショナルツールとして、CLIVS Codeのプラグイン、そしてChromeのデバッガーがある。Aureliaは単なるフレームワークではなく、迅速な成果を約束する強力なプラットフォームエコシステムとして設計されているのである。

リアクティブな関連付け(Reactive Binding)

Aureliaは、あらゆるオブジェクトに対する、強力でリアクティブな関連付けを可能にする。適応性のある(adaptive)な技術を使うことによって、Aureliaは、あなたの作成したモデル内の各プロパティを監視するのに最も効果的な方法を選択し、あなたの作成したUIとstateを自動的に最高クラスのパフォーマンスにする。

テストが簡単(Simple Testing)

モダンなJSモジュールと特殊すぎない方法を兼ね備えることによって、Aureliaはvanilla JSのテストと同じくらい、ユニットテストを簡単にした。結合テストを書く必要性がある?強力なDIコンテナとテストライブラリが、結合テストを素早く簡単にしてくれる。あなたは、非常に保守性が高く、より長続きするアプリケーションの恩恵を受けることになる。

対応していない拡張性(Unmatched Extensibility)

この業界(industry)内で、Aureliaの拡張性が応じることの出来ないものはない。カスタムした要素を作成する、既存の要素に対してカスタムした属性を付け加える、テンプレートの生成を操作する、テンプレートの構文をカスタマイズする、リアクティブな関連付けに新しい型を作成する、DIを拡張するなど、あなたが思いつく限りのことは何でもできる。

規約のある楽なコーディング(Effortless Coding with Conventions)

コーディング規約の存在によって、あなたはあなたが書くコードとビジネスロジックに集中することができる。フレームワークをアプリケーションの中心にしてしまうのではなく、あなたの書いたコードをアプリケーションの中心に位置づける唯一のフレームワークという点において、Aureliaは現在際立った存在である。コーディング規約は嫌い?なら、あなた自身の規約をプラグインとして追加したり、元のコーディング規約を全て外したりすればよい。あなたはどのようにでも出来る。

ルーティング、コンポジションプログレッシブエンハンスメント(Routing, Composition & Progressive Enhancement)

Aureliaの、拡張可能なパイプライン子ルーターを持つ、先進的なクライアントサイドルーターを活用すべきである。ルーターが必要ではなくとも、ダイナミックな、データ駆動型のUIコンポジションは必要ではないか?我々もそうである。アプリケーションが古い?Aureliaのプログレッシブエンハンスメントとスクリプトタグのビルドによって、簡単にAureliaを追加して使うことができる。

あとがき

JavaScriptにきちんと触れたことがないので、翻訳の過程で知らない単語を調べることが勉強になりました。
「unobtrusive」の訳が難しかったです。直訳だと「目立たない」「出しゃばらない」という訳になりますね。
「特殊すぎない方法を使った〜」みたいな意味で使われているのだと思うので、「特殊すぎない」という訳にしました。
「industry」も散々迷ったんですが、とりあえず「業界」にしました。(業界の範囲をうまく表す言葉を思いつきませんでした。)
あと、カタカナにするしかない部分についてはどうしようもなかったですね。

「ecosystem(エコシステム)」という言葉については、正確にどのような意味で使われているのかよく分からなかったです。
こっちの記事(Aurelia Beta Week - Day 4 - Ecosystem - Blog | Aurelia)を訳すと分かりそうなので、またぼちぼち訳します。

次はAureliaのチュートリアルをやってみようと思います。

参考サイトまとめ

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

【実録】Electronアプリを作れるようになるまでの2ヶ月

この記事はElectronに挑戦する人のために書きます。

とある組み込みエンジニアです。
お仕事ではC言語を使い、エクセルの資料やチェックリストに囲まれてる。そんな感じです。
他人のスピーチ動画を見るが好きです。

まとめ

  1. ひょんなことからElectronでアプリを作ろうと思った(先輩の勧め)
  2. 動画を見て、手を動かした(Udemy, YouTube, ドットインストール)
  3. 思い出せるように、記録を書いた(Dropbox, Qiita)
  4. 成果や過程を、仲間にたびたび公開した(Slack)
  5. Electronアプリを作れた

個人的勝利の方程式 = 動画(Udemy, YouTubeなど) + メモサービス(Dropbox, Qiitaなど) + Chat(Slackなど)

まとめはこんな感じ。
コード等はありませんが、私がElectronのアプリ作成にチャレンジした実録が誰かの役に立てばいいなと思います。

黎明編

2018年11月末頃。社内のChatにて。

私「例えばプライベートでWindows Form +C#でアプリを作っても、家のMacで動かない。クロスプラットフォームアプリを作るなら、Xamarinなの?Electronなの?」
先輩「Electron + React + Redux楽しいです。」
私「へー。じゃあ、やるか!」

この時の私は、
HTML, CSS, JavaScriptは未経験。
Electronは、AtomやVisual Studio Codeとかで使われているやつという認識。
ReactとReduxは、知りません。
英語は、TOEIC300点台?
んー、ポケモンに例えるとコイキング:fish:って感じですかね!

一方、先輩は社外の公式のカンファレンスにも登壇したことのある人。
公式のカンファレンスに登壇したことのあるとか...強い。主人公すぎる。
ポケモンに例えると600族のガブリアス:dragon_face:でしょうか。
ガブ先輩が言うならきっと正しいのだろうと思い、そのうちElectronでアプリを作ろうと思った。
当時、Windows Formで作っていたのは、自作のポモドーロタイマーでした。

そんな時、プライベートで使っているSlackに一つの投稿があった。

obiwan師匠「udemy black friday sale」

Udemy修行編

Udemy

オンライン学習サービスは、「ドットインストール」や「Progate」とかなら知っていた。やったことないけど。「Udemy」は存在すら知らなかった、すまんな、Udemy!
簡単にNetで調べてみると、Udemyは頻繁にセールをやっていて、セールで買うのが定石らしい。
この点については、正直うーんどうなんだろうとは思います。当然、Netにもネガティブな意見はあるようです。

obiwan師匠が教えてくれた「black friday sale」では、下限っぽい価格で各コースを購入できるようになっていました。
なので、人柱的にいくつか購入してみました。

その中で、まずGitのコースをやってみました。
もう怖くないGit!チーム開発で必要なGitを完全マスター

なぜなら、プライベートで開発するためにソースコードを管理する仕組みが必要だと思ったからです。
ちなみに、現在会社ではGitもGitHubも使っていません。

1つ目のGitのコース

ヨカッタ\(^o^)/。
家から一歩も出ないで講師が体系的に教えてくれる、便利な時代になったなー。
講師を雇って直接教えてもらうことに比べれば、動画は安いもんである。
とりあえず、もう怖くないわ。Git。

いやでも待てよ?落ち着け。
世の中に知の高速道路・学習の高速道路という言葉があるけれど、もうこれ「知のリニアモーターカー」じゃね?
え?高速よりすっごく早いよ??
これから将来、自分よりも若い人たちがこれに乗ってくるの???
やっぱ怖いわ。((((;゚Д゚))))ガクブル

Dropbox Paper

Udemyのコースで学んだことは、後でざっと見返せるように「Dropbox Paper」に書き留めました。

Udemyの「動画で見ることができる」という点は、分かりやすいというメリットでもあり、一覧性が悪いというデメリットでもある。私はそう思いました。
そのデメリットを補うために、QiitaやDropbox Paperなどのメモサービスを使うのはとても有効だと思います。

人それぞれ好みや環境が異なると思うので、自分に合うサービスを探すと良いでしょう。
自分用メモができるクラウドサービスを比較してみた

他にも、最近知りましたがQrunchというサービスもあるみたいですね。
もっと気軽にアウトプットできる技術ブログサービス「Qrunch(クランチ)」をリリースした【個人開発】

Dropboxの創業者 Drew Houston

Dropboxが話に挙がったので、参考までにYouTubeへのリンクを置いておきます。
私がスピーチ動画好きだからです。
Dropboxの創業者のMITでの講演の動画です。
「勉強して準備万端になってから、何かを始めようと思っている人」は見た方が良いかもしれません。私自身ですね。

【感動】あなたの人生はあと何日?20代MIT卒経営者の心に響くスピーチ日本語字幕

2つ目のElectronのコース

Electron for Desktop Apps: The Complete Developer's Guide

結論としては、これもヨカッタ\(^o^)/
Udemyで『「BEST SELLER」のようなバッヂを持っているコース』や、『4.5以上くらいのスターを持ってる、かつ、レビュー数・受講者数が多いコース』は、それなりに信頼できそうな気がしてきました。

言語は英語です。
「スターとレビュー数の多い英語コンテンツ」と「スターとレビュー数の少ない日本語コンテンツ」とを天秤にかけて前者を選びました。
最初、完遂できるかどうか不安でした。私には、字幕とイラストとCodeが頼りでした。
ちなみに、字幕は自動翻訳のため精度はソレナリ。なのでちょっと時間はかかりました。

しかし、時間がかかっても継続できたのは理由があります。
それは、先程も登場したSlackです。

Slack

こんな風に、Slack上でUdemyの進捗・成果報告をしていました。
画像の左にちっちゃく写ってる:rocket:(rocketのアイコン)は、obiwan師匠が飛ばしてくれています。
このように、一人っきりの学習ではなく、誰かにゆるく見守ってもらえていました。
なので、途中で挫折はしませんでした。感謝です。
Slack.png

海外出張時や帰省時に、荷物が少なくて済む

以前、長期で家を離れる際には「本」を持って行き学習していました。
しかし、「動画」で学習する場合は、スマートデバイスにダウンロードさえしておけば、どこでだって学習できました。
荷物が少なくすむので、これはホントに楽ちんだなと思いました。

Electronのコースを終えて

このElectronのコースのおかげで、いい感じのHTMLとCSSとJavaScriptさえ用意できれば、Electronアプリを作れるようになりました。

ん?ちょっと待てよ。
私ってばHTML, CSS, JavaScriptは未経験\(^o^)/

でも、もうUdemyのセール、終わってる。
ならば、無料で学習できるところを探そうか。

無料学習編

ドットインストール

私が作ろう思っているのは、自作のポモドーロタイマーでした。
カウントダウンするタイマーのサンプルさえあれば、多少は自力で改良できる気がしました。
ですので、Webで探しました。

ありました。ドットインストールに。
JavaScriptでカウントダウンタイマーを作ろう (全12回)

レッスン一覧を見ると、この次の講座からPREMIUMってなってます。
つまり、ギリギリFreeでした。
プログラミングのレッスン一覧

もはや、私のためにドットインストールさんがカウントダウンタイマーの講座をFreeにしておいてくれたとしか思えません。
Thank you ドットインストール.

カウントダウンタイマーの講座の成果物を、自力で変えたりして遊びはじめました。

  • Electron化してみたり
  • CSSで見た目を変更したり
  • ポモドーロタイマーらしく、指定の時間からカウントダウンするように変更したり

サンプルを元に、自力で変更してみるのはとても有効ですね。
この時期から、サンプルをいじりつつ徐々にポモドーロタイマーもどきの形に変えていく作業を続けました。

YouTube

2つ目のElectronのコースを受講した時に、1つ気づいたことがあります。
それは「Udemyで講師をしている人がYouTubeにChannelを開設している場合がある」ということでした。
外国人講師のプロフィール欄にYouTubeのリンクがあったので、気が付きました。

仮説ですが、YouTubeに英語圏向けの学習コンテンツを供給すると一定の収入が得られる、または、いい宣伝になるのかもしれませんね。パイがでかいので。
実際、外国人講師がYouTubeやってる率は、日本人講師より高いように感じました。

まー詳しくは分かりませんが、これを利用しない手はないなと思いました。
以下の条件で、捜索を開始しました。

  • Udemyで『「BEST SELLER」のようなバッヂ付きのコース』を持っている
  • Udemyでのコースの評価が高い
  • YouTubeでの登録者数が多い
  • YouTubeでのコンテンツが多い

結果、以下の動画を見つけて、実際に手を動かしてみました。
Web Development In 2019 - A Practical Guide
HTML Crash Course For Absolute Beginners
CSS Crash Course For Absolute Beginners
CSS Grid Layout Crash Course
Flexbox CSS In 20 Minutes
もしより良いChannelや動画を知ってるよって方がいらっしゃったら、教えてください

これで、
「HTMLとCSS、なんもわかんない/(^o^)\」から、
「HTMLとCSS、雰囲気はわかった\(^o^)/」くらいにはなりました。

結果、ポモドーロタイマーもどきに対しては、Flexboxを適用してみるとか、ボタンを角丸にしてみるとかやりました。

GitHubやElectron公式Webpage

ElectronのGitHubリポジトリにも参考になりました。
https://github.com/electron/electron

特に、Resources for learning Electronの中にあるリンクが参考になりました。

  • Electron Documentation
    このDocs以外にも、Electron公式には「Apps」のページがあります。
    ここで「pomodoro」と検索すると、いくつかヒットします。
    https://electronjs.org/apps?q=pomodoro
    他の人が公開しているElectron製ポモドーロタイマーを、実際に知り触ることができます。

  • Electron Fiddle
    簡単にElectronでHello Worldしたり、Codeをお試しすることができる。

  • Electron API Demos
    ElectronにどのようなAPIがあるのか、実際に動かせるdemoとcodeをみることができる

そんな模索をしている最中に、あるニュースが飛び込んできました。

GitHubプライベートリポジトリ無料公開

GitHub、無料ユーザーもプライベートリポジトリを使い放題に

Gitのコースで学んだコマンドをVisual Studio CodeのTerminalに打ち込んで、
溜まっていた成果物をあらかたプライベートリポジトリに格納しました。その日の内に。

ちょうどGitHubにプライベートリポジトリが欲しいなーと思っていたので、
もはや、私のためにNat FriedmanさんがにプライベートリポジトリをFreeにしてくれたとしか思えません(ジョークです)
Thank you Nat.

Electronアプリ制作編

他の人のElectron製ポモドーロタイマーを触ってみた経験を生かして、自分はどういうものを作ろうかを考えました。
そして、現在の自分の力量もふまえつつ、達成したい機能を考えました。

できました

PomodoroTimer1.gif

Feature

  • TrayアプリではなくWindowアプリ
    とりあえず、Windowアプリにしました。
    Macで開発していますが、メインの使用はWindowsを想定しています。
    いずれMac用にTray modeも作りたいです。

  • ショートカットキーで操作できる

    • Toggle Preferences: Cmd+,
    • Start/Stop: Cmd+S
    • Work/Break: Cmd+D
  • 起動時のWindowを表示を、前回の位置とサイズにする
    electron-storeを使わせていただきました。
    https://github.com/sindresorhus/electron-store
    JavaScript界隈は、いろいろ便利なライブラリがありそうですね。

  • 日付更新時にインターバルをリセットする機能
    前回起動時の日付と今回の起動時の日付とを比べて、インターバル(右側の数字)をリセットをします。

などなど。
あえて、休憩時間を0分にして作業だけの設定もできます。
これを私と師匠とでは「HELLモード」とよんでいます。

これから

そういえばガブ先輩は、

:dragon_face:「Electron + React + Redux楽しいです。」

って言ってたな:fish:
Electron版ポモドーロタイマーづくりが一段落ついたので、Reactを触ってみるのもいいかもしれない。
JavaScriptもまだ勉強途中です。

私が伝えたかったこと

正直、この記事を書き始めたきっかけは以下のイベントです。
【Qiita×Udemy特別企画】学習応援イベント開催中(参加締切:2/28まで)

けれど、1月21日の深夜に見たジャバ・ザ・ハットリさんのBlog記事がとても印象に残りました。
ブログは独自ドメインにしておけ。あとで後悔するぞ、私のように
お布団に入りながら、もしかしたら私も「都合のいい」存在なのかもしれないなと思いました。

独自ドメインはありません。まだやり方も分かりません。
だからせめて、イベントとは関係なく、実体験を元に自由に書いてみました。
伝えたいのは、要するに、現在は様々な学習手段があってすぐ始められるんだなって言うことです。そして、どのツールやサービスを選ぶのかというのは、みなさんの自由だということです。
書きたかったのは「Udemy学習応援」ではなく、「みんなの学習応援」記事でした。
その試みに成功していることを願います。

Thank you for your reading.
最後まで読んでくれてアリガトー\(^o^)/

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

【Windows】pdfmakeでカスタムフォントを使用したPDFファイル生成

やりたいこと

pdfmakeでカスタムフォントを使用したPDF生成を行う

pdfmakeとは

Client/server side PDF printing in pure JavaScript

JavaScriptでクライアント側、サーバ側でのPDF生成ができるモジュールと理解しています。

利用する際に必要なツール

  1. Git
    • v2.19.0 を利用しています
  2. nodist
    • v0.8.8 を利用しています
    • nodist はWindows環境で Node.js を利用するためにインストールしています

クライアント側でPDF作成できるかチェック

pdfmakeモジュールを取得

  1. 作成した作業ディレクトリにエクスプローラでアクセス
    • 今回は D:\pdfmakeTest を作業フォルダとします
  2. パス表示部分をクリックし cmd と入力
    • 現在のディレクトリをカレントにしたコマンドプロンプトが立ち上がります
  3. git clone https://github.com/bpampuch/pdfmake.git でモジュールを取得
    • モジュール取得が完了すると、作業フォルダ直下に pdfmake というフォルダが生成されます

pdfmakeモジュールの動作確認

作業フォルダにテスト表示用のJSファイル、HTMLファイルを作成してPDFが出力されるか確認します。
(クライアントのJavaScriptだけでPDF作成(2)を参考にさせていただき、確認環境を作成しました)
作業フォルダの構造は以下です。

pdfmakeTest
 L pdfmake            <- 上記でダウンロードした pdfmake のモジュールディレクトリ
 L pdfmakeTest_01.js  <- pdfmake のメソッド呼び出し用JSファイル
 L check_01.html      <- pdfmake 確認用HTMLファイル

ソースコード

pdfmakeTest_01.js

// 連想配列で content にPDF出力用のデータを設定
var pdfContent = {content: "This is pdfmake test. これはpdfmakeのテストです。"};

// pdfmakeで content を設定した変数を引数としてPDF生成
pdfMake.createPdf(pdfContent).open();

check_01.html

<!DOCTYPE html>
<html lang="ja">

  <!-- ヘッダー -->
  <head>

    <title>pdfmakeTest</title>

    <!-- ↓↓↓ pdfmake用JSファイル呼び出し ↓↓↓ -->

    <!-- pdfmakeを利用するJSファイル(モジュールのBuildフォルダ内のJSファイル) -->
    <script type="text/javascript" src="./pdfmake/build/pdfmake.min.js"></script>
    <!-- pdf生成に利用するフォントJSファイル(モジュールのBuildフォルダ内のJSファイル) -->
    <script type="text/javascript" src="./pdfmake/build/vfs_fonts.js"></script>

    <!-- ↑↑↑ pdfmake用JSファイル呼び出し ↑↑↑ -->

    <!-- pdfmake 呼び出し用JSファイル -->
    <script type="text/javascript" src="./pdfmakeTest_01.js"></script>

  </head>

  <body>
    <!-- 何も書かない -->
  </body>
</html>

PDF生成確認

ブラウザに GoogleChrome を利用して動作を確認します。
※ 生成結果はポップアップで表示されますので、ポップアップブロックを許可してください

↓ PDFが生成されることは確認しましたが、日本語はフォントを変えないと表示されないようです。
キャプチャ.PNG

pdfmakeで利用するフォントの変更

pdfmakeで日本語が表示できるようにフォントのJSファイル(vfs_fonts.js)を更新します。

フォントの入手

環境を問わず、日本語を表示できるIPAフォントを利用させていただきます。

IPAフォントは、誰でも無償で利用できる、高品位を目指した日本語フォントです。OSI認定の“IPAフォントライセンス”と、フォントのデファクトスタンダードであるOpenTypeフォントフォーマットを採用することで、プラットフォームの種類を問わず、多様な情報機器で共通に利用することができ、どの環境の下でも同じ形状の高品質な文字の表示・印刷を可能にします。本プロジェクトでは、IPAフォントの品質向上を図るとともに、各プラットフォームでの日本語文字表示に関する情報交換の場となることを目指しています。

  1. 公式サイトのIPA Fonts/IPAex Fonts 4書体パック_IPAフォント(Ver.003.03)からフォントファイルをダウンロード
  2. zipファイルを展開
    • ipagp.tff(等幅ゴシック), ipam.tff(等幅明朝) のファイルを利用します

フォント情報の更新

CUSTOM FONTS (CLIENT-SIDE)を参考にフォント情報を更新します。

フォントファイルの入れ替え

pdfmakeTest\pdfmake\examples\fontsディレクトリ内のファイル(Roboto-xxx.ttfの4ファイル、sampleimage.jpg)を削除し、IPAフォントファイル(ipagp.ttf, ipamp.ttf)を格納します。

vfs_fonts.js の更新

以下の手順でフォント情報を更新します。

  1. pdfmakeディレクトリに移動し、Node.jsモジュールを更新
    • npm install (ズラズラっとメッセージが出て更新が行われます)
  2. 上記でインストールしたpdfmake\node_modulesgulpを利用したvfs_fonts.jsの更新
    • pdfmakeTest\pdfmake\node_modules\.bin\gulp.cmd buildFonts を入力しvfs_fonts.jsを更新します
  3. フォント情報をpdfmakeに反映
    • pdfmakeのフォルダでnpm run buildを行い、モジュールをビルドしなおします

vfs_fonts.js の確認

テキストエディタでファイルを確認し、ipagp.ttfという記述があるかを確認します。
キャプチャ2.PNG

カスタムフォントが正常に利用できるかチェック

利用フォントを設定したJSファイルの作成

作業フォルダのテスト表示用のJSファイル、HTMLファイルを更新し、日本語のPDFが出力されるか確認します。
(クライアントのJavaScriptだけでPDF作成(6-日本語フォントテスト)を参考にさせていただき、確認環境を作成しました)
作業フォルダの構造は以下です。

pdfmakeTest
 L pdfmake            <- 上記でダウンロードした pdfmake のモジュールディレクトリ
 L pdfmakeTest_02.js  <- pdfmake のメソッド呼び出し用JSファイル
 L check_02.html      <- pdfmake 確認用HTMLファイル

ソースコード

pdfmakeTest_02.js

先ほど更新したフォントを利用フォントとして設定し、表示する文字列に対して利用フォントを設定しています。

// フォント情報として、ゴシックと明朝を定義
pdfMake.fonts = {
    IPAgothic: {
        normal:      'ipagp.ttf',
        bold:        'ipagp.ttf',
        italics:     'ipagp.ttf',
        bolditalics: 'ipagp.ttf'
   },
    IPAmincho: {
        normal:      'ipamp.ttf',
        bold:        'ipamp.ttf',
        italics:     'ipamp.ttf',
        bolditalics: 'ipamp.ttf'
   }
}

// JSON形式で content にPDF出力用のデータを設定
var pdfContent = {
    content: [
        { text: "This is pdfmake test."},
        { text: "これは等幅ゴシック体(ipagp.tff)のテストです。", font: "IPAmincho"},
        { text: "これは等幅明朝体(ipamp.tff)のテストです。", font: "IPAgothic"}
   ],
   // デフォルトフォントを等幅ゴシック体に変更
   defaultStyle: {
       font: "IPAgothic"
   }
}

// pdfmakeで content を設定した変数を引数としてPDF生成
pdfMake.createPdf(pdfContent).open();

check_02.html

呼び出すJSファイルを変更しただけです。

<!DOCTYPE html>
<html lang="ja">

  <!-- ヘッダー -->
  <head>

    <title>pdfmakeTest</title>

    <!-- ↓↓↓ pdfmake用JSファイル呼び出し ↓↓↓ -->

    <!-- pdfmakeを利用するJSファイル(モジュールのBuildフォルダ内のJSファイル) -->
    <script type="text/javascript" src="./pdfmake/build/pdfmake.min.js"></script>
    <!-- pdf生成に利用するフォントJSファイル(モジュールのBuildフォルダ内のJSファイル) -->
    <script type="text/javascript" src="./pdfmake/build/vfs_fonts.js"></script>

    <!-- ↑↑↑ pdfmake用JSファイル呼び出し ↑↑↑ -->

    <!-- pdfmake 呼び出し用JSファイル -->
    <script type="text/javascript" src="./pdfmakeTest_02.js"></script>

  </head>

  <body>
    <!-- 何も書かない -->
  </body>
</html>

日本語が表示されているか確認

できました!
3.PNG

ハマったところ

カスタムフォント定義ファイルの更新

問題

grunt を利用してカスタムフォント更新する方法がわかりませんでした。

解決

pdfmakeのイシュー(Grunt #794)を確認し、gulpを利用することに気づきました!
(というよりも、公式のドキュメントにはgulpが記載されているので、最初にそれを見れば解決していました……。)

vfs_fonts.js ファイルの内容を pdfmake に反映させる

問題

vfs_fonts.jsを更新しただけでは、Uncaught File 'ipagp.ttf' not found in virtual file system というエラーが発生し、フォント情報が pdfmake.min.js から読めないようでした。

解決

npm run build で再度ビルドを行うことで、モジュール設定の更新が行われ、正常に表示することができました!

参考サイト

pdfmake

bpampuch/pdfmake
Grunt #794
PDFMAKE

カスタムフォント

クライアントのJavaScriptだけでPDF作成(2)
クライアントのJavaScriptだけでPDF作成(5-フォント作成)
クライアントのJavaScriptだけでPDF作成(6-日本語フォントテスト)

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

JavaScriptで不動点演算子を改造してスタックオーバーフローを回避したメモ化再帰を書く

次のfibRはフィボナッチ数を求める再帰関数を素朴に実装したものだよ!

const fibR = n => (n === 0 || n === 1) ? n : fibR(n - 1) + fibR(n - 2);

謎の関数fixを使って、ほとんど同じようにフィボナッチ数を求める再帰関数fibMを定義してみるよ!!

const fibM = fix(f => n => (n === 0 || n === 1) ? n : f(n - 1) + f(n - 2));

あら不思議!!!fibRに比べてfibMってば爆速!!!!!!

もしかして、竹内関数も早くなる???

const taraiR = (x, y, z) => x <= y ? y : taraiR(taraiR(x - 1, y , z), taraiR(y - 1, z, x), taraiR(z - 1, x, y));
const taraiM = fix(t => (x, y, z) => x <= y ? y : t(t(x - 1, y , z), t(y - 1, z, x), t(z - 1, x, y)));

やっぱり、taraiRに比べてfixを使ったtaraiMってば爆速!!!
しかも、taraiR(10000, 0, 0)はスタックオーバーフローを起こしちゃうのに、taraiM(10000, 0, 0)taraiM(100000, 0, 0)もちゃんと動く!!!スタックオーバーフローが起こらないなんてすごい!!!!!!!!

・・・といった感動を与えてくれるfixを実装してみます。

とりあえずfixの実装を見てくれ

うん。事前知識無しに読めたもんじゃない。

試しに動かしてみたい人はコピペしてくれ。ただ、普段JavaScriptを使ってないので手元Chromeでしか動作確認してないんだ。君のところの環境で動かなかったらごめん。

const fix = f => (...args) => {
  const stack = [args];
  const memo = new Map;

  const rec = rec => f((...args) => {
    stack.push(args);

    const key = JSON.stringify(args);
    if (! memo.has(key)) {
      memo.set(key, rec(rec)(...args));
    }

    stack.pop();
    return memo.get(key);
  });

  const errKey = new Set;
  while (stack.length > 0) {
    try {
      rec(rec)(...stack.pop());
    } catch (e) {
      // 実行環境によってスタックオーバーフローか否かの判定を変えないと動かない
      if (e.name !== "RangeError" || e.message !== "Maximum call stack size exceeded") {
        throw e;
      }

      const eKey = JSON.stringify(stack[stack.length - 1]);
      if (errKey.has(eKey)) {
        throw e;
      }
      errKey.add(eKey);
    }
  }

  return rec(rec)(...args);
};

不動点演算子

世の中には不動点演算子というものがあって。不動点演算子を使うとアロー関数式(or 無名関数)だけで再帰関数が作れることが知られている。

例えば、Zコンビネータと呼ばれる不動点演算子は次のように実装できる。

f => (x => f(y => x(x)(y)))(x => f(y => x(x)(y)));

このZコンビネータを使うと、階乗の再帰関数を次のように実装できる。コンソールで試すとわかるが、マジで動く。

(f => (x => f(y => x(x)(y)))(x => f(y => x(x)(y))))(f => n => n < 1 ? 1 : n * f(n - 1));

fixの導出

Zコンビネータを変形して、fixを導出する。

Zコンビネータを読みやすくする

Zコンビネータはそのままだと何が何だかわからないので、少し見やすくする。

f => (x => f(y => x(x)(y)))(x => f(y => x(x)(y)));

まず、x => f(y => x(x)(y))が重複しているので、変数に格納することで冗長性を排除する。

f => {
  const x = x => f(y => x(x)(y));
  return x(x);
};

変数名がわかりづらいので変更する。x(x)が再帰関数に相当するのでxrec(recursive)に変更する。yが再帰関数の引数に相当するのでarg(argument)に変更する。

f => {
  const rec = rec => f(arg => rec(rec)(arg));
  return rec(rec);
};

可変長の引数に対応する

竹内関数のように再帰関数の引数は1つとは限らない。再帰関数の引数が何個であっても対応できるよう、arg...args(Restパラメータ、Spread構文)に変更する。

f => {
  const rec = rec => f((...args) => rec(rec)(...args));
  return rec(rec);
};

メモ化する

次はメモ化を目指す。とりあえずメモ用の変数memoを定義しておく。

f => {
  const memo = new Map;
  const rec = rec => f((...args) => rec(rec)(...args));
  return rec(rec);
};

メモ化のキーとしてJSON文字列可したargsを使うことにする。(注:JSON文字列可すると等価判定が狂う値が適用された場合、正常にメモ化できない。また、JSON文字列可に時間がかかる値が適用された場合、動作が遅くなる。)

f => {
  const memo = new Map;

  const rec = rec => f((...args) => {
    const key = JSON.stringify(args); 
    return rec(rec)(...args);
  });

  return rec(rec);
};

未計算であれば計算結果をメモし、メモした値を返却するよう変形する。

f => {
  const memo = new Map;

  const rec = rec => f((...args) => {
    const key = JSON.stringify(args); 
    if (! memo.has(key)) {
      memo.set(key, rec(rec)(...args));
    }

    return memo.get(key);
  });

  return rec(rec);
};

これでメモ化再帰を定義できるようになった。

スタックを管理する

実行エンジンが面倒を見てくれるコールスタックがあふれてしまうのであれば、自前で管理する他ない。ということで、stackを用意する。

f => {
  const stack = [];
  const memo = new Map;

  const rec = rec => f((...args) => {
    const key = JSON.stringify(args); 
    if (! memo.has(key)) {
      memo.set(key, rec(rec)(...args));
    }

    return memo.get(key);
  });

  return rec(rec);
};

再帰関数においてstackに積むべき情報は引数のみなので、計算を開始する前に引数をpushし、計算が完了した後に引数をpopしておく。

f => {
  const stack = [];
  const memo = new Map;

  const rec = rec => f((...args) => {
    stack.push(args);

    const key = JSON.stringify(args); 
    if (! memo.has(key)) {
      memo.set(key, rec(rec)(...args));
    }

    stack.pop();
    return memo.get(key);
  });

  return rec(rec);
};

スタックオーバーフローを回避する

テキトーに (() => {let f = () => -f(); f()})() とか動かせばスタックオーバーフローのエラーメッセージが見れるので、それを元にその環境での判定式が書ける。
下記は、Chromeでの判定例。

f => (...args) => {
  const stack = [];
  const memo = new Map;

  const rec = rec => f((...args) => {
    stack.push(args);

    const key = JSON.stringify(args); 
    if (! memo.has(key)) {
      memo.set(key, rec(rec)(...args));
    }

    stack.pop();
    return memo.get(key);
  });

  try {
    return rec(rec)(...args);
  } catch (e) {
    // 実行環境によってスタックオーバーフローか否かの判定を変えないと動かない
    if (e.name !== "RangeError" || e.message !== "Maximum call stack size exceeded") {
      throw e; // スタックオーバーフロー以外のエラー
    }

    return "スタックオーバーフロー!!!";
  }
};

さて、スタックオーバーフローを捕獲できたので、あとはスタックオーバーフローが起きたあとに自前のstackを使って計算を続行させれば良い。

f => (...args) => {
  const stack = [args];
  const memo = new Map;

  const rec = rec => f((...args) => {
    stack.push(args);

    const key = JSON.stringify(args); 
    if (! memo.has(key)) {
      memo.set(key, rec(rec)(...args));
    }

    stack.pop();
    return memo.get(key);
  });

  while(stack.length > 0) {
    try {
      rec(rec)(...stack.pop());
    } catch (e) {
      // 実行環境によってスタックオーバーフローか否かの判定を変えないと動かない
      if (e.name !== "RangeError" || e.message !== "Maximum call stack size exceeded") {
        throw e;
      }
    }
  }

  return rec(rec)(...args);
};

最後にfix(f => () => f())のように、同じ引数で2回以上スタックオーバーフローを起こす場合は諦めてスタックオーバーフローの例外を再スローしておく。

f => (...args) => {
  const stack = [args];
  const memo = new Map;

  const rec = rec => f((...args) => {
    stack.push(args);

    const key = JSON.stringify(args); 
    if (! memo.has(key)) {
      memo.set(key, rec(rec)(...args));
    }

    stack.pop();
    return memo.get(key);
  });

  const errKey = new Set;
  while (stack.length > 0) {
    try {
      rec(rec)(...stack.pop());
    } catch (e) {
      // 実行環境によってスタックオーバーフローか否かの判定を変えないと動かない
      if (e.name !== "RangeError" || e.message !== "Maximum call stack size exceeded") {
        throw e;
      }

      const eKey = JSON.stringify(stack[stack.length - 1]);
      if (errKey.has(eKey)) {
        throw e;
      }
      errKey.add(eKey);
    }
  }

  return rec(rec)(...args);
};

以上、導出完了。

おわり

まぁ、実用性は皆無ですが、面白いところもあるので愛でてやってくだせぇ。

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

WebページにGoogle Map埋め込む[Google Maps API]

Google Maps APIを使えば場所や経路表示を埋め込める

店や会社のアクセスなどで使われているよく見るMapです。
昔はAPIキーを使わなくても表示できたようですが、今はAPIキーが必要です。
通常のサイト程度なら課金されることはないとのことですが、読み込み回数が一定以上になると課金されます。

ただ、制限をかけられますので設定をミスしなければ課金されなので安心して使いましょう。
image.png

登録して使い始める

APIキーを取得するために、まずはGoogle Cloud Platform

Google Cloud

「コンソール」からでもいいみたいですが「使ってみる」クリックして
image.png

Mapを使いますので☑
image.png

プロジェクトは何でもいいですが名前を決めましょう。
image.png

有効化されていなかったら

左上のメニュー → APIとサービス → ダッシュボード
image.png

有効化します。
image.png

webアプリに使うのでjavascript使うことにします。
map4.PNG

こんな画面なる
image.png

APIを使えるようにする

認証情報を作成して使えるようにしていきます。
image.png

APIキー選択
image.png

自分のキーが出ます。閉じないで「キーを制限」クリック
image.png

不正に使われないように制限を付ける

httpリファラーで制限を付けます。
使用するサイトのURL入れる。*はワイルドカード。www.やrootパス以下に適用します。
開発環境ならlocalhostなど。

http://*.example.com/*(http の場合)
https://*.example.com/*(https の場合)
http://localhost/* とか http://*.localhost/*

複数登録できるので開発環境と本番環境登録しておくとよい思います。
image.png

APIの制限もしておきます。

今回はまだJavaScriptしか使っていないので1項目しかなかったですが、複数あった場合は使うものだけ選択します。
image.png

割り当て上限を設定する
APIサービス → 下のほうにある Maps JavaScript API → 割り当て
image.png

記事作成現在、一か月の読み込み回数が28500回なので28500 / 30 = 950

なので900とか800とか入れておきます。そうすれば課金はされません。
(回数は確認しておきましょう。ちゃんと設定していれば上限を超える方が難しいはずです。)

課金登録

ところで、課金設定していないとこんな画面が出ます。
面倒ですがクレジットの登録が必要です。設定ができていれば課金されることはないですので設定してしまいましょう。
image.png

マップを表示させる

サンプルコードを何も書いていないページにコピペすればこんな感じで最初は表示されると思います。
image.png

サンプルコード1
サンプルコード2

見たい場所を指定

latとlng:数字を入れれば特定の場所に合わせることができます。緯度と経度です。
ツールを使って数字を調べて入力すればOK

zoom:大きくなるほどズーム。1が世界地図。20が建物レベル。調整しましょう。
経度と緯度を調べる

該当箇所を変更すれば好きな場所にズームできます。

center: {lat: -34.397, lng: 150.644},
zoom: 8

その他のサンプル

Maps Javascript API 以外もありますが経路など表示できるようです。
サンプル:経路
image.png

英語ですが他にも様々なパターンがあります。サイドバーから確認して使ってみるといいと思います。
image.png

何個も使う場合キーのアクセス回数制限設定に気をつけましょう。

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

PayPal のREST APIで、ログインやEC決済を試してみる

PayPalは、EC向けにいろんなツール類を用意してくれているのですが、ドキュメントが散在してわかりにくかったり、古いものから新しいものまでごちゃまぜになっている印象があります。

そんな中で、Node.jsの勉強のためもあり、REST APIを使ってみたいと思います。
割と今風で、マルチプラットフォームに対応できそうなので、これを選びました。
(これに慣れたら、最新のBraintreeに移行したいと思っています。)

(情報源)
PayPal Developer
 https://developer.paypal.com/

PayPal API Reference
 https://developer.paypal.com/docs/api/overview/

node.js SDK for PayPal RESTful APIs
 https://github.com/paypal/PayPal-node-SDK

Connect With PayPal
 https://developer.paypal.com/docs/integration/direct/identity/

他の決済サーバと同じように、自由にいじれるSandboxがあるので、気兼ねなく試してみましょう。

毎度のことですが、Swaggerを使ったRESTfulサーバを立ち上げます。
以下ご参考まで。

SwaggerでRESTful環境を構築する

PayPal Developerアカウントの作成

まずは、以下のページからアカウントを作成しましょう。

PayPal Developer
 https://developer.paypal.com/

image.png

まず見ていただきたいのが、Sandbox Accountです。
左側のナビゲータから選択します。

image.png

あらかじめ2つのアカウントができているかと思います。

 XXXXX-facilitator@YYYYY.yyy BUSINESS
 XXXXX-buyer@YYYYY.yyy PERSONAL

これらアカウントは、PayPal Sandboxの中だけのアカウントですので、メールアドレスは実在しませんし、本番PayPalには使えません。
XXXXX-buyer@YYYYY.yyy は、あとでログインに使うので、パスワードを変えておきましょう。

REST APIを使うための準備

いくつかの種類がありますが、今回使うのは、REST API appsです。
「Create App」ボタンを押下します。

App Nameには、適当な名前を付けます。例えば、「TestApp」とします。
Sandbox developer account には、BUSINESSのアカウントを指定するのですが、今回は、あらかじめ作られていた XXXXX-facilitator@YYYYY.yyy を選択します。

image.png

そうすると、Client IDやSecretが表示されます。
後で使うので、覚えておきます。

image.png

また、右上のところで、SandboxとLiveを選択できるのですが、当然ながら試作中なので、Sandboxにしておきます。

RESTfulサーバを立ち上げる

では早速、サーバを立ち上げます。

2つのエンドポイントを使います。
Swagger定義を示します。

swagger.yaml
  /paypal-create:
    get:
      x-swagger-router-controller: routing
      operationId: paypal-create
      responses:
        200:
          description: Success
          schema:
            type: object

  /paypal-redirect:
    get:
      x-swagger-router-controller: routing
      operationId: paypal-redirect
      responses:
        200:
          description: Success
          schema:
            type: object

/paypal-create
 決済を開始します。品目や金額を指定します。例として、100円のお買い物です。
 その後、PayPalがユーザからApproveをもらうために、画面を表示してくれます。

/paypal-redirect
 PayPalの画面でユーザが承認してくれると、このエンドポイントが呼び出されます。
 Approveをもらったので、これで決済を完了させます。
 完了後、ユーザにPaymentIdなど、もろもろ返してあげています。

利用するnpmモジュールは以下の通りです。

  • paypal-rest-sdk
  • node-fetch
  • uuid

PayPalから、paypal-rest-sdkというモジュールを提供していただいているので、だいぶ楽になりました。

(エンドポイント「/paypal-token」もありますが、これは後程説明します。)

index.js
var paypal = require('paypal-rest-sdk');

const base_url = 【RESTfulサーバを立ち上げているURL】;

var Response = require('../../helpers/response');
var Redirect = require('../../helpers/redirect');
var fetch = require('node-fetch');
const { URLSearchParams } = require('url');
const uuidv4 = require('uuid/v4');

const PAYPAL_CLIENT_ID = process.env.PAYPAL_CLIENT_ID || 【REST API appsのClient ID】;
const PAYPAL_CLIENT_SECRET = process.env.PAYPAL_CLIENT_SECRET || 【REST API appsのSecret】;

paypal.configure({
    'mode': 'sandbox', //sandbox or live
    'client_id': PAYPAL_CLIENT_ID,
    'client_secret': PAYPAL_CLIENT_SECRET,
    'headers' : {
        'custom': 'header'
    }
});

exports.handler = (event, context, callback) => {
    if( event.path == '/paypal-token'){
        var body = JSON.parse(event.body);
        var code = body.code;

        var url = 'https://api.sandbox.paypal.com/v1/oauth2/token';
        var params = {
            grant_type: 'authorization_code',
            code: code
        };

        return do_post_form_basic(url, params, PAYPAL_CLIENT_ID, PAYPAL_CLIENT_SECRET )
        .then(json =>{
            console.log(json);
            if( json.error )
                throw json.error;
            return do_get_token('https://api.sandbox.paypal.com/v1/identity/oauth2/userinfo', { schema: 'paypalv1.1'}, json.access_token);
        })
        .then(json =>{
            console.log(json);
            if( json.error )
                throw json.error;
            return new Response( json );
        })
        .catch(error =>{
            return new Response().set_error(error);
        })
    }else if( event.path == '/paypal-create'){
        var orderid = uuidv4();

        var create_payment_json = {
            "intent": "authorize",
            "payer": {
                "payment_method": "paypal"
            },
            "redirect_urls": {
                "return_url": base_url + "/paypal-redirect",
                "cancel_url": base_url + "/paypal/index.html"
            },
            "transactions": [{
                "custom": JSON.stringify({ orderid: orderid, amount: 100 } ),
                "item_list": {
                    "items": [{
                        "name": "item",
                        "price": "100",
                        "currency": "JPY",
                        "quantity": 1
                    }]
                },
                "amount": {
                    "currency": "JPY",
                    "total": "100"
                },
                "description": "This is the payment description."
            }]
        };

        return new Promise((resolve, reject) =>{
            paypal.payment.create(create_payment_json, (error, payment) => {
                if (error) {
                    console.log(error.response);
                    return reject(error);
                }
                console.log(payment);

                for (var index = 0; index < payment.links.length; index++) {
                    if (payment.links[index].rel === 'approval_url')
                        return resolve( new Redirect(payment.links[index].href) );
                }

                return reject('approval_url not found');
            });
        });
    }else if( event.path == '/paypal-redirect'){
        var qs = event.queryStringParameters;
        console.log(qs);

        var payerid = event.queryStringParameters.PayerID;
        var paymentid = event.queryStringParameters.paymentId;

        var execute_payment_json = {
            "payer_id": payerid,
            "transactions": [{
                "amount": {
                    "currency": "JPY",
                    "total": "100"
                }
            }]
        };

        return new Promise((resolve, reject) =>{
            paypal.payment.execute(paymentid, execute_payment_json, (error, payment) => {
                if (error) {
                    console.log(error.response);
                    return reject(error);
                }
                console.log(payment);

                for( var i = 0 ; i < payment.transactions.length ; i++ ){
                    for( var j = 0 ; j < payment.transactions[i].related_resources.length ; j++ ){
                        if( payment.transactions[i].related_resources[j].authorization ){
                            var authorization_id = payment.transactions[i].related_resources[j].authorization.id;
                            console.log('authorization_id=' + authorization_id);
                        }
                    }
                }

                var params = new URLSearchParams();
                for( var key in qs )
                    params.set(key, qs[key] );

                return resolve( new Redirect(base_url + '/paypal/index.html?' + params.toString() ));
            });
        });
    }
};

function do_get_token(url, qs, token){
    var params = new URLSearchParams();
    for( var key in qs )
        params.set(key, qs[key] );

    return fetch(url + '?' + params.toString(), {
        method : 'GET',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization' : 'Bearer ' + token }
    })
    .then((response) => {
        return response.json();
    });
}

function do_post_form_basic(url, qs, client_id, client_secret){
    var params = new URLSearchParams();
    for( var key in qs )
        params.set(key, qs[key] );

    const headers = { 
        "Content-Type" : "application/x-www-form-urlencoded",
        "Authorization" : "Basic " + new Buffer(client_id + ':' + client_secret).toString('base64')
    };

    return fetch(url, {
        method : 'POST',
        body : params,
        headers: headers
    })
    .then((response) => {
        return response.json();
    });
}

ユーティリティも示しておきます。

redirect.js
class Redirect{
    constructor(url){
        this.statusCode = 303;
        this.headers = {'Location' : url};
        this.body = null;
    }
}
response.js
module.exports = Redirect;

class Response{
    constructor(context){
        this.statusCode = 200;
        this.headers = {'Access-Control-Allow-Origin' : '*'};
        if( context )
            this.set_body(context);
        else
            this.body = "";
    }

    set_error(error){
        this.body = JSON.stringify({"err": error});
        return this;
    }

    set_body(content){
        this.body = JSON.stringify(content);        
        return this;
    }

    get_body(){
        return JSON.parse(this.body);
    }
}

module.exports = Response;

環境に合わせて、以下を変更してください。

【REST API appsのClient ID】
【REST API appsのSecret】
【RESTfulサーバを立ち上げているURL】

また、

 "return_url": base_url + "/paypal-redirect"

の部分で、ユーザによる同意の結果のリダイレクト先を指定しています。
また、以下の部分で、決済完了後のリダイレクト先を指定しています。

 return resolve( new Redirect(base_url + '/paypal/index.html?' + params.toString() ));

クライアント側のHTMLページも示します。

/paypal/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <meta http-equiv="Content-Security-Policy" content="default-src * data: gap: https://ssl.gstatic.com 'unsafe-eval' 'unsafe-inline'; style-src * 'unsafe-inline'; media-src *; img-src * data: content: blob:;">
  <meta name="format-detection" content="telephone=no">
  <meta name="msapplication-tap-highlight" content="no">
  <meta name="apple-mobile-web-app-capable" content="yes" />
  <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">

  <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
  <!-- Latest compiled and minified CSS -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
  <!-- Optional theme -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
  <!-- Latest compiled and minified JavaScript -->
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>

  <title>PayPal 連携 テスト</title>

  <script src="js/vue_utils.js"></script>
  <script src="https://unpkg.com/vue"></script>
</head>
<body>
    <div id="top" class="container">
        <h1>PayPal 連携 テスト</h1>
        <br>
        <a class="btn btn-primary" href="【RESTfulサーバを立ち上げたURL】/paypal-create">Payment</a>
        <br>
    </div>

    <script src="js/start.js"></script>
</body>
start.js
'use strict';

const base_url = 【RESTfulサーバを立ち上げたURL】;
const PAYPAL_CLIENT_ID = 【REST API appsのClient ID】;

var vue_options = {
    el: "#top",
    data: {
    },
    computed: {
    },
    methods: {
    },
    created: function(){
    },
    mounted: function(){
        proc_load();

        if( searchs.paymentId ){
            history.replaceState(null, null, '.');
            alert('決済が完了しました。');
        }
    }
};
var vue = new Vue( vue_options );
vue_utils.js
var hashs = {};
var searchs = {};

function proc_load() {
  hashs = parse_url_vars(location.hash);
  searchs = parse_url_vars(location.search);
}

function parse_url_vars(param){
  if( param.length < 1 )
      return {};

  var hash = param;
  if( hash.slice(0, 1) == '#' || hash.slice(0, 1) == '?' )
      hash = hash.slice(1);
  var hashs  = hash.split('&');
  var vars = {};
  for( var i = 0 ; i < hashs.length ; i++ ){
      var array = hashs[i].split('=');
      vars[array[0]] = array[1];
  }

  return vars;
}

function vue_add_methods(options, funcs){
    for(var func in funcs){
        options.methods[func] = funcs[func];
    }
}
function vue_add_computed(options, funcs){
    for(var func in funcs){
        options.computed[func] = funcs[func];
    }
}

以下に示すように、決済が完了して元のページに戻ってくると、paymentIdが指定されてくるので、それを受けてダイアログ表示するようにしています。

    if( searchs.paymentId ){
        history.replaceState(null, null, '.');
        alert('決済が完了しました。');
    }

それではRESTfulサーバを立ち上げてみましょう。

image.png

Paymentボタンを押下します。
そうすると、ログイン画面が表示されます。
Personalアカウントでログインします。自動で作られるXXXXX-buyer@YYYYY.yyy がありました。

image.png

今度は、品目と価格の確認画面に変わります。
内容を確認したら「同意して続行」ボタンを押下します。

image.png

めでたく最初のページに戻ってきて、ダイアログが表示できました。

image.png

PayPalアカウントでのログイン機能の追加

ついでに、PayPalアカウントでのログイン機能を追加してみます。
クライアント側に少しコードを追加します。

/paypal/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <meta http-equiv="Content-Security-Policy" content="default-src * data: gap: https://ssl.gstatic.com 'unsafe-eval' 'unsafe-inline'; style-src * 'unsafe-inline'; media-src *; img-src * data: content: blob:;">
  <meta name="format-detection" content="telephone=no">
  <meta name="msapplication-tap-highlight" content="no">
  <meta name="apple-mobile-web-app-capable" content="yes" />
  <meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">

  <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
  <!-- Latest compiled and minified CSS -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
  <!-- Optional theme -->
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
  <!-- Latest compiled and minified JavaScript -->
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>

  <title>PayPal 連携 テスト</title>

  <script src="js/vue_utils.js"></script>
  <script src="https://unpkg.com/vue"></script>
</head>
<body>
    <div id="top" class="container">
        <h1>PayPal 連携 テスト</h1>
        <br>
        <span id='cwppButton'></span>
        <br>
        <br>
        <a class="btn btn-primary" href="【RESTfulサーバを立ち上げたURL】paypal-create">Payment</a>
        <br>
    </div>

    <script src="js/start.js"></script>
    <script src='https://www.paypalobjects.com/js/external/connect/api.js'></script>
    <script>
        paypal.use( ['login'], function (login) {
            login.render ({
            "appid": PAYPAL_CLIENT_ID,
            "authend":"sandbox",
            "scopes":"openid profile",
            "containerid":"cwppButton",
            "locale":"ja-jp",
            "buttonType":"CWP",
            "buttonSize":"lg",
            "returnurl": base_url + "/paypal/"
            });
        });
    </script>
</body>

以下の部分が、PayPalアカウントでのログインボタンになります。

<span id='cwppButton'></span>

各種設定は、以下の部分で行います。

 paypal.use( ['login'], function (login)

containeridの指定の部分で指定したIDのspanの部分に、ボタンを描画してくれます。
ログインのために、PayPalが提供する画面に遷移したのち、ログイン完了後にまた戻ってきます。戻ってくる先は、「returnurl」の部分に指定します。

スクリプトの本体は、以下です。

 <script src='https://www.paypalobjects.com/js/external/connect/api.js'>

PayPalからログイン完了後に戻ってくると、codeが指定されています。
これは認可コードであって、これをアクセストークンに変換する必要があります。
そのために、いったんRESTfulサーバに処理を移す必要があり、それが、以下の部分です。

    if( searchs.code ){

エンドポイント「/paypal-token」を呼んでいます。
呼び出しが成功すると、PayPalアカウント名が書かれたダイアログを表示するようにしています。

start.js
'use strict';

const base_url = 【RESTfulサーバを立ち上げたURL】;
const PAYPAL_CLIENT_ID = 【REST API appsのClient ID】;

var vue_options = {
    el: "#top",
    data: {
    },
    computed: {
    },
    methods: {
    },
    created: function(){
    },
    mounted: function(){
        proc_load();

        if( searchs.paymentId ){
            history.replaceState(null, null, '.');
            alert('決済が完了しました。');
        }
        if( searchs.code ){
            history.replaceState(null, null, '.');

            do_post(base_url + '/paypal-token', { code: searchs.code} )
            .then(json =>{
                console.log(json);
                alert(json.name + 'さん、こんにちは');
            });
        }
    }
};
var vue = new Vue( vue_options );

function do_post(url, body){
    console.log('do_post: ' + url);

    const headers = new Headers( { "Content-Type" : "application/json; charset=utf-8" } );

    return fetch(url, {
        method : 'POST',
        body : JSON.stringify(body),
        headers: headers
    })
    .then((response) => {
        return response.json();
    });
}

エンドポイント、/paypal-tokenのサーバ部分は、すでに実装を示してました。
世の中で一般的なOAuth2やOpenID Connectとほぼ同じなので、なじみがあるかと思います。

swagger.yaml
  /paypal-token:
    post:
      x-swagger-router-controller: routing
      operationId: paypal-token
      parameters:
        - in: body
          name: body
          schema:
            type: object
      responses:
        200:
          description: Success
          schema:
            type: object

それでは、もう一度、RESTfulサーバを再起動して、ブラウザからアクセスしてみます。

image.png

PayPalのボタンが増えているのがわかると思います。
PERSONALのアカウントでログインします。

image.png

PayPalのログインアカウント名が書かれたダイアログが表示されたかと思います。成功です。

image.png

感想

PayPalのドキュメント類は、すごく見にくかったのですが、REST APIに絞って熟読することで、なんとなく動作させることができました。
今後は、これを足掛かりに、他の機能も見ていこうと思います。

以上

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

C#でJSONな記事補足

うーん

参考にはなるけれどちょっと雑

C#:API呼び出し
https://blog.pie001.com/entry/2018/01/16/165318

これを実装ために追加すべきもの

nuget→DLL参照→usingの順で追加

nuget

・Newtonsoft.Json

Newtonsoft.Json
https://www.nuget.org/packages/Newtonsoft.Json/

DLL参照

以下手順
1.ソリューションエクスプローラー内の参照を右クリック
2.参照の追加(R)...
3.左側のアセンブリから
4.System.Web.Extensionsにチェック
5.OK

using

using System.Net;
using System.IO;
using System.Web.Script.Serialization;
using Newtonsoft.Json.Linq;

JSONパラメータの書き方

シリアライズするSerialize()メソッドを使った書き方

C#
var jsonParameter = new JavaScriptSerializer().Serialize(new
{
    name = "Hoge-San",
    email = "hogehoge@hoge.com",
    password = "h0gew0rd",
    detail_info = new
    {
        info1 = "hoge1",
        info2 = "hoge2"
    },
    hoge = "hogehoge",
});

このように記述することで、

JSON
{
    "name": "Hoge-San",
    "email": "hogehoge@hoge.com",
    "password": "h0gew0rd",
    "detail_info": {
        "info1": "hoge1",
        "info2": "hoge2"
    },
    "hoge":"hogehoge"
}

のようなJSONが生成され、jsonParameterに格納されます。

実際は改行・スペースなしの1行のstring型になるので、格納される正しい中身は、

jsonParameterの中身
{"name":"Hoge-San","email":"hogehoge@hoge.com","password":"h0gew0rd","detail_info":{"info1":"hoge1","info2":"hoge2"},"hoge":"hogehoge"}

のようになります。

おわり

記事にするならこれくらいはあった方が初心者にも熟練者にも親切だよね。

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

Date Range Pickerでnull(空白)許容する

DateRangePickerでnullを許容したくてなかなかできずハマった。

nullを許容する場合
autoUpdateInput: false
をオプション指定すればnullになるが、日付を指定した場合に反映されない。
コールバック関数で指定してあげれば反映される。

$(function () {
    $(function () {
        "use strict";

        moment.locale("ja");
        $('#selector').daterangepicker({
            singleDatePicker: true,
            calender_style: "picker_3",
            format: 'YYYY/MM/DD',
            //初期値NULLにする&NULL許容する
            autoUpdateInput: false,
            locale: {
                cancelLabel: 'Clear'
            },
        },
            //autoUpdateInputが入っていると日付指定できなくなるのでcallback関数で指定する
            function (date) {
                $('#selector').val(date.format('YYYY/MM/DD'));
            }
        );
    });
});

参考
http://www.daterangepicker.com/#ex5

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

Node.jsで定期実行メモ

毎回忘れるので自分用メモです。
Node Cronを使います。

準備

$ npm init -y
$ npm i node-cron

Cronの書式

https://www.npmjs.com/package/node-cron#cron-syntax

 # ┌────────────── second (optional)
 # │ ┌──────────── minute
 # │ │ ┌────────── hour
 # │ │ │ ┌──────── day of month
 # │ │ │ │ ┌────── month
 # │ │ │ │ │ ┌──── day of week
 # │ │ │ │ │ │
 # │ │ │ │ │ │
 # * * * * * *
  • 6段階あって左から秒,分,時間,日,月,曜日
  • 秒はオプショナルなので5段階設定しておけば動きはします。

らしいです。

試す

公式サンプルが分単位のサンプルばかりで試しづらいので基本は秒単位の試し方を書いてます。

毎秒実行

  • cron.schedule()の*が6つ
app.js
'use strict';

const cron = require('node-cron');
cron.schedule('* * * * * *', () => console.log('毎秒実行'));

毎分実行

  • cron.schedule()の*が5つ
  • 毎秒実行よりも*が少ない
app.js
'use strict';

const cron = require('node-cron');
cron.schedule('* * * * *', () => console.log('毎分実行'));

毎分の1秒、10秒、30秒、55秒に実行

app.js
'use strict';

const cron = require('node-cron');
cron.schedule('1,10,30,55 * * * *', () => console.log('毎分1秒,10秒,30秒,55秒に実行'));

毎分の1秒~10秒に実行

  • 1-10みたいな記述が出来ます。
app.js
'use strict';

const cron = require('node-cron');
cron.schedule('1-10 * * * * *', () => console.log('毎分1秒~10秒に実行'));

実行すると、毎分10回実行されますね。

$ node app.js
毎分1秒~10秒に実行
毎分1秒~10秒に実行
毎分1秒~10秒に実行
毎分1秒~10秒に実行
毎分1秒~10秒に実行
毎分1秒~10秒に実行
毎分1秒~10秒に実行
毎分1秒~10秒に実行
毎分1秒~10秒に実行
毎分1秒~10秒に実行

3秒ごとに実行

  • */3とすると3秒ごととか3分ごとみたいな指定です。
  • */10だと10分ごと
app.js
'use strict';

const cron = require('node-cron');
cron.schedule('*/3 * * * * *', () => console.log('3秒ごとに実行'));

毎日0時,6時,12時,18時に実行

結構使いそうな間隔

app.js
'use strict';

const cron = require('node-cron');
cron.schedule('0 0 0,6,12,18 * * *', () => console.log('毎日0時,6時,12時,18時に実行'));

3時間おきの指定時間(3,6,9,12,15,18,21)実行

'use strict';

const cron = require('node-cron');
cron.schedule('0 0 0,3,6,9,12,15,18,21, * * *', () => console.log('3時間おきの指定時間実行'));

3時間おき実行

'use strict';

const cron = require('node-cron');
cron.schedule('0 0 */3 * * *', () => console.log('3時間おきの実行'));

23時59分59秒に実行したい

app.js
'use strict';

const cron = require('node-cron');
cron.schedule('59 59 23 * * *', () => console.log('毎日23時59分59秒に実行'));

できなかったネタ

24時は指定できない

毎日24時に実行したくて、こんな指定をすると怒られます。

app.js
'use strict';

const cron = require('node-cron');
cron.schedule('0 0 24 * * *', () => console.log('毎日24時に実行'));
$ node app.js

/Users/n0bisuke/dotstudio/playground/cron-test/node_modules/node-cron/src/pattern-validation.js:54
      throw patterns[2] + ' is a invalid expression for hour';
      ^
24 is a invalid expression for hour

1日は0:00~23:59になるので24時ってのはほんとはダメなんですね。

毎日24時に実行させたい場合は0を指定しましょう。

app.js
'use strict';

const cron = require('node-cron');
cron.schedule('0 0 0 * * *', () => console.log('毎日24時に実行'));

60分や60秒も指定できない

24時同様に60分や60分も指定できません。

app.js
'use strict';

const cron = require('node-cron');
cron.schedule('60 60 0 * * *', () => console.log('60秒や60分の指定'));

怒られますね。

$ node app.js

/Users/n0bisuke/dotstudio/playground/cron-test/node_modules/node-cron/src/pattern-validation.js:50
      throw patterns[1] + ' is a invalid expression for minute';
      ^
60 is a invalid expression for minute

所感

地味に24H指定してハマってたので気をつけましょう笑

随時サンプルアップデートしたい

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

イベントハンドラーの活用例

まえおき

今更なのですが、jQuery大好きなので、jQuery Advent Calendar 2013に参加したく、記事を書きました。ここ数年の間に便利なjavascriptフレームワークもたくさん出てきてjQueryは影が薄くなっているようですが、数多くの使い勝手の良いCSSフレームワークやCMSにはがっちり組み込まれているので、まだまだ必要な技術なんだなと思います。
今もBootstrap4を使ったコンテンツの開発をしているので、相変わらずjQueryは触り続けています。
今回は過去実装したイベントハンドラを使った小技(?)を書かせていただければと思っております。

背景

画像表示をする際に画像が全て揃っているかどうかわからない環境で画像がない時にはデフォルトの画像を表示したいときに実装した方法です。

サンプル

See the Pen error sample by tomo (@tomoka) on CodePen.

説明

errorEvent

今回の処理にerrorEventを使いました。
errorEventが発火する状態には色々な状態があります。その中で、画像がない時に404エラーが帰ってきます。

参考:HTMLImageElement
https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement#Errors

よって画像がない時は"Null"が帰ってきて自動的にエラーを取得できるので、404エラーが起こった時のみ、画像を差し替えるように設定しました。

実装方法

実装方法はイベントハンドラーであれば自動的に帰ってくるのでjQueryのon()に実装できます。

参考:.on()
http://api.jquery.com/on/

$(".itemPhoto").on("error",function(){
  console.log("画像がありません");
})

404エラーを出すsrcを持つimgにイベントを付けます。
もちろん、通常のイベントなので、同じように設定できます。
書き方は、いつもと一緒。

あとがき

まだまだjQuery書いてます。

イベントつながりで、最近、とても便利そうなjQuery Pluginを見つけました。
https://hammerjs.github.io/
APIに無いイベントの取得で過去にiOSとAndroidの検知の違いで苦労したことがあるため、Swipeを使いやすい形で実装できることに感動しました。(3本指のスワイプジェスチャー(MozSwipeGesture)ではなく1本指のSwipeに対応。)

書き方もこんな感じ。
http://hammerjs.github.io/getting-started/

var hammertime = new Hammer(myElement, myOptions);
hammertime.on('pan', function(ev) {
    console.log(ev);
});

使えるようになるイベントも豊富。
http://hammerjs.github.io/touch-action/

まだまだ、見つけたばかりで、documentの読み込みもできていなし理解もしていないので、実装予定があるので、実装してみたら報告したいと思います。
最後まで読んでいただきありがとうございました。

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

連想配列の配列からキーごとの配列を抜き出す

こういうAPIの返りを

APIの返り値
const apiReturn = [
  {
     id: "3fa85f64-5717-4562-b3fc-2c963f66afa1",
     name: 'taro',
   },
   {
     id: "3fa85f64-5717-4562-b3fc-2c963f66afa2",
     name: 'jiro',
   },
   {
     id: "3fa85f64-5717-4562-b3fc-2c963f66afa3",
     name: 'saburo',
   }
];

こんな風にキーごとに配列にしたかったので

キーごとの配列
[ '3fa85f64-5717-4562-b3fc-2c963f66afa1',
  '3fa85f64-5717-4562-b3fc-2c963f66afa2',
  '3fa85f64-5717-4562-b3fc-2c963f66afa3' ]

[ 'taro', 'jiro', 'saburo' ]

こうした

snippet.js
const apiReturn = [
  {
     id: "3fa85f64-5717-4562-b3fc-2c963f66afa1",
     name: 'taro',
   },
   {
     id: "3fa85f64-5717-4562-b3fc-2c963f66afa2",
     name: 'jiro',
   },
   {
     id: "3fa85f64-5717-4562-b3fc-2c963f66afa3",
     name: 'saburo',
   }
];

const ids = apiReturn.map(x => x.id);
const names = apiReturn.map(x => x.name);

console.log(ids)
console.log(names)

Array.prototype.map() は配列を返すんだな

なんでこんなことがしたかったかというと.... つづく。

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

DataTablesで選択されている行/選択されていない行のデータを取得する方法

DataTablesはjavascriptで簡単にテーブルを作成することのできるライブラリ(MITライセンス)です。Ajaxとの相性も良くとても簡単にテーブルを作成でき、表のデータの取得も簡単にできます。plug-inのselectを使用すれば、全ての行のテーブル情報や選択されているテーブルの情報は取得できるのですが、選択されていないテーブルの情報が取得できませんでした。今回は選択されていないテーブルの情報をどのように取得したかを記載しようと思います。もし、いい方法があればご教示いただきたいです。

環境

DataTablesのversionは1.10.19を用いています。
Plug-inのselectのversionは1.2.7です。

Table表示

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>DataTables</title>
    <!-- DataTablesで必要なCSSファイル -->
    <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.19/css/jquery.dataTables.min.css">
    <!-- DataTablesでselect(チェックマーク)に必要なCSSファイル -->
    <link type="text/css" rel="stylesheet" href="https://cdn.datatables.net/select/1.2.7/css/select.dataTables.min.css">
    <!-- DataTablesで必要なjsファイル -->
    <script src="https://code.jquery.com/jquery-3.3.1.js"></script>
    <script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/1.10.19/js/jquery.dataTables.min.js"></script>
    <!-- DataTablesでselectに必要なCSSファイル -->
    <script type="text/javascript" charset="utf8" src="https://cdn.datatables.net/select/1.2.7/js/dataTables.select.min.js"></script>

    <script src="./index.js"></script>
</head>
<body>
    <div id="show-datatables"></div>
</body>
</html>
index.js
var table;

$(function() {
    document.getElementById('show-datatables').innerHTML = "<table id='show-datatablesTable' class='display nowrap' style='width:100%'></table>";
    table = $('#show-datatablesTable').DataTable({
         "columnDefs": columnDefs,
         "data" : data,
         "scrollX": true,        
         "select": {
            "style": "multi", // 単一選択の場合はOS 複数選択の場合がmulti
            "selector": "td:nth-child(2)", // 選択したい列 今回は2列目をクリックすることでチェックマークをつけることができる
            "blurable": false, // table外をクリックしたときに、チェックマークが反応するかどうか
          }
       });
});

var columnDefs= [
    {targets: 0, title: "No", data: "no"},
    {targets: 1, title: "チェック", defaultContent: "", "className": "select-checkbox"},
    {targets: 2, title: "Name", data: "name"},
    {targets: 3, title: "Age", data: "age"},
    {targets: 4, title: "Address", data: "address"},
    {targets: 5, title: "Phone Number", data: "number"}
  ];

var data = [{
        "no": 1,
        "name": "田中",
        "age": 18,
        "address": "東京",
        "number": "08011111111"
    },
    {
        "no": 2,
        "name": "佐藤",
        "age": 20,
        "address": "神奈川",
        "number": "08022222222"
    },
    {
        "no": 3,
        "name": "鈴木",
        "age": 22,
        "address": "埼玉",
        "number": "08033333333"
    },
    {
        "no": 4,
        "name": "前田",
        "age": 25,
        "address": "群馬",
        "number": "08044444444"
    },
    {
        "no": 5,
        "name": "小島",
        "age": 30,
        "address": "栃木",
        "number": "08055555555"
    }
]

土台となるTableの表示です。下記のようなTableが表示されます。
スクリーンショット 2019-01-27 13.58.01.png

選択された行のデータの表示方法

選択されている行のデータ取得はDataDablesの方で用意されています。

//全ての行のデータ
table.rows().data();
// 選択されている行のデータ
table.rows('.selected').data();

コンソール上でデータが取れていることが確認できます。
スクリーンショット 2019-01-27 14.05.53.png
しかし、上記のように選択されていない行のデータをDataTables用意されているものでは取得できませんでしたので、工夫させていただきました。

選択されていない行のデータの表示方法

function()内に下記を追加します。

index.js
for (var i = 0; i < table.rows().data().length; i++) {
          // チェック選択されていない行にはクラスを追加しておく
          $(table.row(i).node()).addClass('deselect');
        }
       table
        .on( 'select', function ( e, dt, type, indexes ) {
            $(table.row(indexes).node()).removeClass('deselect');
        } )
        .on( 'deselect', function ( e, dt, type, indexes ) {
            $(table.row(indexes).node()).addClass('deselect');
        } );

最初のfor文で初期表示時に全ての行に.deselectクラスを追加しています。
選択した時と、選択解除した時のイベントを取得できるのでそのときに選択した時は.deselectクラスの解除。選択を外したときに.deselectクラスを追加しています。
このようにすることで

// 選択されていない行のデータ
table.rows('.deselected').data();

スクリーンショット 2019-01-27 14.14.21.png

選択されていないデータの取得ができるようになります。
選択されていない行のデータが必要になるケースはなかなかないと思いますが、このようにすることでなんとか取得できました。

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

僕はArrow functionが読めない

はじめに

RubyからJavascriptに手を広げている時、Rubyに比べてJSはコードが読みにくいと感じた。
特に Arrow function(無名関数) についてはしっくり理解できるまで時間がかかったので、同じ道を通るかもしれない人に向けて記録を残しておこうと思う。 また、関数で頻繁に使用されている、省略記法が基礎が出来ていない状態で障害になっていたと感じる。


対象となる読者

Javascriptを初めたばかりの方


Arrow functionの利点

1. 短く記述出来る

function(){...} vs. () => {...}

2. 関数名を記述しなくて良い

Arrow functionは Anonymous である。

Anonymous functions usually don't have a named identifier

1については、メリットが理解しやすいが、2については文章を見ても直ぐにはしっくり来なかった。
2について具体例を出して考えていく。

function blastoff() {
  console.log("3...2...1..blastoff!");
}

blastoff();
結果
=> 3...2...1..blastoff!

ここでは関数名に blastoff という固有の名前がつけられている。
次は、1秒後にconsoleの中身が返ってくるように setTimeout()メソッド を例に考える。

setTimeout(function() {
  console.log("3...2...1..blastoff!");
}, 1000)
結果
=> 3...2...1..blastoff!

上記の例では、 blastoff の名称を用いた関数の呼び出しは行なっていないが問題なく動作する。
Arrow function を用いると以下のように記述出来る。

setTimeout(() => {
  console.log("3...2...1..blastoff!");
}, 1000)

結果は同じである。
もし、無名関数ではなく、固有の名前を与えたいなら以下のように記述してあげれば良い。

const blastoff = () => {
  console.log("3...2...1..blastoff!");
}

blastoff()
結果
=> 3...2...1..blastoff!

次に、配列の数字に1を加えるメソッドを例に考える.

let points = [10, 11, 12]

let addOne = function(element) {
  return element + 1;
}

points = points.map(addOne);

console.log(points)
結果
=> [11, 12, 13]

上記の例は Arrow function を用いると以下のように記述出来る。

let points = [10, 11, 12]

let addOne = () => {
  return element + 1;
}

points = points.map(addOne);

mapメソッドの引数に addOneの中身を記述すれば、更に短く記述が出来る。

let points = [10, 11, 12]

points = points.map((element) => {
  return element + 1;
})

Arrow functionに渡す 引数 が1つの場合は、 () を省略出来る。

let points = [10, 11, 12]

points = points.map(element => {
  return element + 1;
})

Arrow functionのbodyが1つのreturn文の時は {} 及び return を省略できる。

let points = [10, 11, 12]

points = points.map(element => element + 1)

同じ表現を省略してかなり短く記述することが可能だ。

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

freecodecampのWrite Higher Order Arrow Functionsを解いたメモ

 実際の問題へのリンク

https://learn.freecodecamp.org/javascript-algorithms-and-data-structures/es6/write-higher-order-arrow-functions

 問題概要

与えられた配列から正の整数を取り出し、取り出された正の整数で自乗された(squared)配列を新たに作成する。

 自分の回答

javascript
 const realNumberArray = [4, 5.6, -9.8, 3.14, 42, 6, 8.34, -2];
 const squareList = (arr) => {
   "use strict";
   // change code below this line
   const squaredIntegers = arr.filter((num) => num > 0 && Number.isInteger(num) ).map(filteredNum => filteredNum ** 2);
   // change code above this line
   return squaredIntegers;
 };
 // test your code
 const squaredIntegers = squareList(realNumberArray);
 console.log(squaredIntegers);

 解説

引数arrが与えられた配列で、まずこの配列からfilter()を使用し正の整数を取り出す。
numに配列の各値が入る。このときnumの変数名は自由につけてOK。
Number.isInteger(値) で整数を取り出す。
整数には負の数も含まれるため 値 > 0 で正の整数に絞り込む。

javascript
 const squaredIntegers = arr.filter((num) => num > 0 && Number.isInteger(num) );

ここまでで正の整数は取り出せた。
次は取り出した値で自乗を行いつつ、その結果を配列として生成する。
map()を使用して新たな配列を生成。
filteredNumに配列の各値が入ってくる。
「なにかしらの処理」の部分に今回でいうと自乗を行うコードを書く。

javascript
 const squaredIntegers = arr.map(filteredNum => なにかしらの処理);

べき乗の計算は ** を使う。 (Math.pow() もOK)
今回の問題では、取り出した値の2乗を行えばOK。

javascript
 const squaredIntegers = arr.map(filteredNum => filteredNum ** 2);

const squaredIntegers = arr.filter((num) => num > 0 && Number.isInteger(num) );

const squaredIntegers = arr.map(filteredNum => filteredNum ** 2);
をがっちゃんこ。

javascript
 const squaredIntegers = arr.filter((num) => num > 0 && Number.isInteger(num)  ).map(filteredNum => filteredNum ** 2);

以上。

 参考にしたもの

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

ElectronでFileのOpenとSave

Electronでファイル操作する必要があったのでOpenとSaveの方法をメモ。

dialogの制御方法が知りたいのが主目的なので、ロジックはなし。
csvを読み込み表示。そのままsaveする。

仕様

下記のような感じ。

  • openボタンを押すとファイオープンし、textareaに表示。
  • saveでtextarea内の内容をfileにSave。

スクリーンショット 2019-01-27 7.14.03.png

textarea内を変更しても内容はファイルには反映されない。それをやるにはVueとかReact使う。

実装

package.json

エンドポイントはmain.jsとしました。

package.json
{
  "name": "openclose",
  "version": "1.0.0",
  "description": "sample",
  "main": "main.js",
  "scripts": {
    "start": "electron ."
  },
  "devDependencies": {
    "electron": "^4.0.2"
  }
}

main.js

ElectronのQuick Startのmain.jsとほぼ一緒。
起動時の表示としてindex.htmlを呼び出している。

main.js
const { app, BrowserWindow } = require('electron')

let win

function createWindow() {

    //ウインドウの作成
    win = new BrowserWindow({ width: 800, height: 400 })

    //ウインドウに表示する内容
    win.loadFile('index.html')

    //デバッグ画面表示
    // win.webContents.openDevTools()

    //このウインドウが閉じられたときの処理
    win.on('closed', () => {
        win = null
    })
}

//アプリが初期化されたとき(起動されたとき)
app.on('ready', () => {
    createWindow()
})

//全ウインドウが閉じられたとき
app.on('window-all-closed', () => {
    if (process.platform !== 'darwin') {
        app.quit()
    }
})

//アクティブになったとき(MacだとDockがクリックされたとき)
app.on('activate', () => {
    if (win === null) {
        createWindow()
    }
})

index.html

画面要素を配置。ロジックを実装するindex.jsとstyles.cssを呼び出している。

index.html
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>sample</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <h3>File Open/Save</h3>
    <button id="openFile">Open</button>
    <button id="saveFile">Save</button>
    <br>
    <textarea id="preview"></textarea>
    <script src="index.js"></script>
</body>
</html>

index.js

ここにいろいろ書く。やってることは、

  • OpenDialogで開くファイル名(Path)を取得し、その情報を利用してファイルをオープン。
  • SaveDialogで保存するファイル名(Path)を取得して、その情報を利用してファイルをSave。
index.js
const fs = require('fs');
const { BrowserWindow, dialog } = require('electron').remote;

//html内の要素取得とリスナーの設定
document.querySelector("#openFile").addEventListener('click', () => {
    openFile();
})

document.querySelector("#saveFile").addEventListener('click', () => {
    saveFile();
})

const preview = document.getElementById('preview');

//openFileボタンが押されたとき(ファイル名取得まで)
function openFile() {
    const win = BrowserWindow.getFocusedWindow();
    dialog.showOpenDialog(
        win,
        {
            properties: ['openFile'],
            filters: [
                {
                    name: 'Document',
                    extensions: ['csv', 'txt']
                }
            ]
        },
        (fileNames) => {
            if (fileNames) {
                // alert(fileNames[0]);
                readFile(fileNames[0]); //複数選択の可能性もあるので配列となる。
            }
        }
    )
}

//指定したファイルを読み込む
function readFile(path) {
    fs.readFile(path, (error, data) => {
        if (error != null) {
            alert("file open error.");
            return;
        }
        preview.textContent = data.toString();
    })
}

//saveFileボタンが押されたとき
function saveFile() {
    const win = BrowserWindow.getFocusedWindow();
    dialog.showSaveDialog(
        win,
        {
            properties: ['openFile'],
            filters: [
                {
                    name: 'Documents',
                    extensions: ['csv', 'txt']
                }
            ]
        },
        (fileName) => {
            if (fileName) {
                const data = preview.textContent;
                console.log(data);
                writeFile(fileName, data);
            }
        }
    )
}

//fileを保存(Pathと内容を指定)
function writeFile(path, data) {
    fs.writeFile(path, data, (error) => {
        if (error != null) {
            alert("save error.");
            return;
        }
    })
}

styles.css

まあ、おまけ程度ですが、いちおう。

styles.css
#preview{
    margin-top: 20px;
    width: 300px;
    height: 100px;
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

typescript-eslintのversion1.x系がリリースされたので使ってみる

typescript-eslintのVersion 1.X系がリリースされましたので、サンプルアプリを使って試してみたいと思います。

typescript-eslintについて

typescript-eslint は、 @mysticatea さんの記事でもあったように、ESLintチームがTypeScriptのサポートする宣言があり、 1月20日にVersion 1.0.0がリリースされました。さらに3日後にはVersion1.1.0がリリースされています。

現在、TypeScriptが利用されているプロジェクトは、TypeScriptの普及により多くなってきたこともあり、後々TSLintからESLintへの移行が必須になるかと思います(しばらくは大丈夫だと思いますが...)。

そこで、今回は、TypeScript + React のサンプルアプリでtypescript-eslintを試してみたいと思います。また、既存のtslint.jsonを利用したサンプルも試してみたいと思います。

ESLintの導入

create-react-app を利用して、任意のサンプルを作ります。

typescript-eslint $ create-react-app typescript-eslint-react-sample --scripts-version=react-scripts-ts

サンプルアプリに移動して、以下のパッケージをインストールします。

  • eslint
  • @typescript-eslint/parser
  • @typescript-eslint/typescript-estree
  • @typescript-eslint/eslint-plugin
パッケージインストール
typescript-eslint $ yarn add eslint @typescript-eslint/parser @typescript-eslint/typescript-estree @typescript-eslint/eslint-plugin -D

ESLintの設定

eslintの必要最低限の設定をしていきます。

.eslintrc.json
{
  "root": true,
  "plugins": ["@typescript-eslint"],
  "parser": "@typescript-eslint/parser",
  "env": {
    "es6": true,
    "browser": true
  },
  "rules": {
    "no-console": "error" // console.logなどのコンソール出力に対してエラーを出す設定
  },
  "parserOptions": {
    "sourceType": "module",
    "ecmaFeatures": {
      "jsx": true
    }
  }
}
package.json
{
  // ...
  "scripts": {
    "eslint": "eslint -c ./.eslintrc.json 'src/**/*.{ts,tsx}'"
  },
  // ...
}

上記、設定が完了したら、コンソールに対するエラーが出るか確認してみます。

1____w_1_0_q_t_typescript-eslint-react-sample__fish_.png

servicework用のtsが引っかかっているのが確認できましたので、最低限の設定に問題がないことを確認できました。

tslintの設定を利用する

既存のプロジェクトでTSLintを利用している場合、その設定をそのまま利用したくなる場合があるかと思います。今回は、tslint.jsonがある場合を想定して試してみたいと思います。

まず、以下のパッケージを追加します。

  • @typescript-eslint/eslint-plugin-tslin

次に、 .eslintrc.json の設定を変更します。

.eslintrc.json
{
  "root": true,
  "plugins": ["@typescript-eslint", "@typescript-eslint/tslint"], // <- 追加
  // ..
  "rules": { // <- 追加(先ほどのconsoleのルールは削除して、このルールだけにします)
    "@typescript-eslint/tslint/config": [
      "warn",
      {
        "lintFile": "./tslint.json"
      }
    ]
  },
  "parserOptions": {
    "sourceType": "module",
    "ecmaFeatures": {
      "jsx": true
    },
    "project": "./tsconfig.json"// <- 追加
  }
}

次に、 tslint.json を変更します。今回は、 var キーワードの使用に対するチェックを追加します。

tslint.json
{
  "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"],
  "linterOptions": {
    "exclude": [
      "config/**/*.js",
      "node_modules/**/*.ts",
      "coverage/lcov-report/*.js"
    ]
  },
  "rules": { // <-追加
    "no-var-keyword": true
  }
}

次に、適当なファイルを修正します。今回は適当な関数を追加して、その中で var を宣言しています。

App.tsx
// ..
class App extends React.Component {
  public fn = () => { // <- 追加
    var arg = "aaa";
    return arg.toUpperCase();
  };
  public render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className="App-title">Welcome to React</h1>
        </header>
        <p className="App-intro">
          To get started, edit <code>src/App.tsx</code> and save to reload.
        </p>
        {this.fn()} // <- 追加
      </div>
    );
  }
}
// ..

最後に、 yarn eslint で追加したルールが機能しているか確認します。

1____w_1_0_q_t_typescript-eslint-react-sample__fish_.png

上がESLintの実行結果で、下が、TSLintの実行結果です。
同じエラーが出ていることが確認できたので、正常にtslint.jsonが機能していることが確認できました。

最後に

今回は必要最低限の設定のみで実践しましたが、ルールを整備したり、オリジナルのルールを追加する必要があります。が、導入自体のハードルはそんなに高くないように感じました。

今回作成したリポジトリは、こちらです。

ESLint、TSLintに対するご指摘などがありましたら、ご連絡ください。

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