かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

JavaScript Blobオブジェクトでファイルを作成してダウンロードしてみる

A Blob object represents a file-like object of immutable, raw data. Blobs represent data that isn't necessarily in a JavaScript-native format. The File interface is based on Blob, inheriting blob functionality and expanding it to support files on the user's system.
出典: Blob | MDN

JavaScriptで扱える生のデータで、HTML5で追加されたFile APIで追加されていた機能だそうです。 (知らなかった

blob
Can I use... Support tables for HTML5, CSS3, etc

すでに殆どのブラウザで利用可能なようです。(IEも10からOKだった...だと..... クッコロ....

Blobオブジェクトの作り方

var aBlob = new Blob( array[, options]);
  • array は、Blob の中に入れられることになるもの—— ArrayBuffer オブジェクト、ArrayBufferView オブジェクト、Blob オブジェクト、DOMString オブジェクト、またはそういったオブジェクトのうち任意のものを混在させたもの——からなる Array です。DOMString はUTF-8 で符号化されます。
  • options は、以下の二つの属性を指定できるオプショナルな BlobPropertyBag ディクショナリです。
    • type は、 blob の中に入れられることになる配列の中身の MIME タイプを表し、デフォルト値は "" です。
    • endings は、行末文字\n を含む文字列をどのように書き出すべきかを指定するもので、デフォルト値は "transparent" です。endings は、次の二つの値のうちのどちらかです。すなわち、ホスト OS のファイルシステムの慣行に合うように行末文字を変更することを意味する "native"か、変更せずに行末を blob の中にそのまま格納することを意味する "transparent" です。

出典: Blob() コンストラクタ | MDN

ファイルにしたい情報を配列形式で渡して、第二引数でMINIタイプを指定すれば良さそうです。

Blobでファイルを作成してダウンロードさせてみる

chrome v71 のコンソールで実験しました。

const str = "星宮いちご, 霧矢あおい, 紫吹蘭\n大空あかり, 氷上すみれ, 新条ひなき\n";
// Blobでファイルを作成
const file = new Blob([str, "神崎美月, 夏樹みくる"], {
  type: "text/csv;charset=utf-8"
});

// ダウンロード
const a = document.createElement('a');
// ダウンロードされるファイル名
a.download = 'aikatsu.csv';
a.href = URL.createObjectURL(file);
// ダウンロード開始
a.click();

👇 ブラウザからaikatsu.csv がダウンロードされました! aikatsu.csv

星宮いちご, 霧矢あおい, 紫吹蘭
大空あかり, 氷上すみれ, 新条ひなき
神崎美月, 夏樹みくる

第一引数の配列のデータが結合されるようです。
new Blob([ "...新条ひなき", "神崎美月, 夏樹みくる" ]) と結合した時にCSVではなくなる状態にすると、神崎美月, 夏樹みくるはBlobオブジェクトには含まれていてもダウンロードされたファイルでは消えていました。

ファイルの文字コード

上の例では type: "text/csv;charset=utf-8文字コードの指定をしています。

const file = new Blob([str], {
  type: "text/csv;charset=SJIS"
});

でも試してみましたが、ダウンロードされたファイルはUTF-8エンコードされていました。
MDNのサイトの引数の説明にあるようにDOMString はUTF-8 で符号化されるようですから、typeでcharasetは指定する意味は無さそうです。

思った以上に簡単にJavaScriptだけでファイルが生成できてびっくりしました!
知らないことが沢山で色んな所にアンテナ張っておきたいけど、フロント周りだけでもいろんな事がありすぎて遅れを取り戻すキャッチアップしながらだと中々大変です…


[参考]

(仮)MiMiCHeRi アイカツ! (4個入) 食玩・ガム (アイカツ!)

(仮)MiMiCHeRi アイカツ! (4個入) 食玩・ガム (アイカツ!)

クリスマスもやっぱりアイカツ!だね♡

PHP マークダウン変換できるライブラリParsedown使ってみた。

WordPressのカスタムフィールドで作っていたテキストエリアで部分的にボールドにしたいという要望がありました。
運用してる人的にHTML書いてくださいってのはハードル高そうだし、カスタムフィールドをWYSIWYGにするのは工数もかかるし自由に出来すぎるとwordみたいに超でかい文字とか原色みたいな色とかデザインルールに沿わないカオスが生まれそうなので避けたい。

ということで、マークダウン記法で変換させることにしました。
今回は手っ取り早くマークダウン変換できるライブラリを導入して対応することにしました。

Parsedown

composerでインストールでき、高速であるという事で、今回のようなシンプルな要件には良さそうだなと思いParsedownを使うことにしました。

インストール

composerでインストールします

$ composer require erusev/parsedown

WordPressから使う

テーマのfunction.phpに読み込ませます。

<?php // function.php
// パスは適時変更してください
require_once(__ROOT__ . '../vendor/autoload.php');

カスタムフィールドのテキストのマークダウンをHTML変換

<?php // function.php
function get_custom_field_text($postID, $metaKey) {
  $Parsedown = new Parsedown();
  // textareaの改行はそのまま改行にする
  $text = nl2br(esc_html( get_post_meta($postID, $metaKey, true) ));
  // Parsedown->lineでインライン要素のマークダウンをHTML化
  $text = $Parsedown->line($text);
  return $text;
}

はい。簡単にマークダウンが使えるテキストエリアのカスタムフィールドができあがりました!!
今回使ったParsedownはline()メソッドを使えばブロック要素は無視してインライン要素だけマークダウン形式から変換できる様だったので機能を絞る意味でもマッチしていました。
表示速度に関しては、カスタムフィールドたくさん使っている箇所は予めキャッシュ化するようにしていたので分からないですが、簡単に導入して使えるのでとても良いライブラリだと思いました。
また、GitHubでコードを見るとライブラリ自体1つのPHPファイルでとてもシンプルなのでマークダウンの正規表現とかの勉強にもなりそうです!

運用者には「ボールドにしたい箇所を2重のアスタリスク**で囲ってください。例 **ボールドにしたい文字**という感じで伝えました。
偶然マークダウン記法になって事故が起こる可能性は無きにしもあらず…なので
将来的には使用可能な記法を絞れるように、時間あるときにでもライブラリを読んで機能制限できるようにしてみようかなーとか考え中。

ホント、マークダウンのメリットってWordやWYSIWYGみたいに書く人によって文字がめっちゃデカくなったり、自由に色選べるから好き勝手な文字色や背景色で強調されたり、ティラノサウルスの3Dデータをぶっこまれたりしない。
決まったデザイントーンに則った表示になり文章のアウトラインが解りやすくなる。これに尽きると思うんです。
googleドライブで使える文章…ぜひマークダウンのドキュメントもプラグイン無しで作れるようにして欲しい… (もうあります?


[参考]

gulp-connect のリロード処理だけ別タスクにする時のTips

リロードさせる関数があるので、gulpでwatchしている時など好きなタイミングでlocalhostをリロードさせることができるgulp-connectですが、リロードさせるタスクをまとめようとした時にチョットしたハマりどころが合ったのでメモ。

公式に書かれてるやり方

🙆‍♀️リロードされる

const gulp    = require('gulp'),
      stylus  = require('gulp-stylus'),
      connect = require('gulp-connect');

const server = (cb) => {
  connect.server({
    root: webroot,
    livereload: true,
    port: 3000
  });
  cb();
};

const buildCss = () => {
  return gulp.src( files )
    .pipe( stylus() )
    .pipe( gulp.dest( destDir ) )
    .pipe( connect.reload() );
};

const watchCSS = (cb) => {
  gulp.watch([
    `${devDir}/style/**/*.styl`
  ], buildCss);
  cb();
};

gulp.task( 'default', gulp.series(server, watchCSS) );

各タスクにの最後に.pipe( connect.reload() )をつけてリロードさせる方法。
どこでリロードするのかすぐ判るしシンプルでよいのですが、watchで実行するタスクが増えて、最後にリロードさせたいってなった時タスクに変更がある度に最後のタスクにリロードの処理を付け直さなくなるのでリロードの処理だけ別の関数にしたい感はあります。

リロードの処理を別関数にする

gulp-connectのリロードはgulp.src().pipe( connect.reload() )でしか動作しないっぽい

🙅‍♀️リロードされない

const gulp    = require('gulp'),
      connect = require('gulp-connect');

const server = (cb) => { /* 略*/ };

const reload = (cb) => {
  connect.reload();
  cb();
};

const buildCss = () => { /* 略*/ };

const watchCSS = (cb) => {
  gulp.watch([
    `${devDir}/style/**/*.styl`
  ], gulp.series(buildCss, reload));
  cb();
};

gulp.task( 'default', gulp.series(server, watchCSS) );

タスク内でそのままconnect.reload();を呼び出してもリロードされませんでした。

🙆‍♀️ gulp.src().pipe() でならリロードされる

const gulp    = require('gulp'),
      connect = require('gulp-connect');

const server = (cb) => { /* 略*/ };

const reload = () => {
  return gulp.src(webroot).pipe( connect.reload() );
};

const buildCss = () => { /* 略*/ };

const watchCSS = (cb) => {
  gulp.watch([
    `${devDir}/style/**/*.styl`
  ], gulp.series(buildCss, reload));
  cb();
};

gulp.task( 'default', gulp.series(server, watchCSS) );

リロードだけを別タスクにする場合は、この様にgulp.src()に続くpipe()内で実行する必要があるようです。
これでwatchするタスクが増えてもgulp.series内に追加するタスク名を追加するだけで済むようになりました!