20200516のJavaScriptに関する記事は30件です。

Expressでcssとjavascriptを読み込む方法

1. ディレクトリ構成

Expressでプロジェクトを作成するとディレクトリは以下のような構成になります。

スクリーンショット 2020-05-16 22.33.17.png

詳細は今後に書くとして、cssとjavascriptをテンプレートエンジンへ適用させたい時は以下の手順を行ってください。

2. css作成

publicフォルダ直下のstylesheetsフォルダにcssファイルを作成してください。

3. javascript作成

publicフォルダ直下のjavascriptsフォルダにjsファイルを作成してください。
javascriptの場合はAPIの作成だったりルーティングの兼ね合いがありますので、
その点を考慮した実装としておいてください。

4.適用

viewsフォルダに作成した(プロジェクト作成時は1ファイルのみ作成されている)テンプレートエンジンのファイルに読み込みをさせていきます。

4-1. cssの場合

おなじみ、linkタグを使いましょう
e.g.
<link rel='stylesheet' href='/stylesheets/style.css' />
publicフォルダ直下のstylesheetsフォルダのstyle.cssファイルなので良いですね?

4-2. javascriptの場合

こちらもおなじみ、scriptタグを使いましょう
<script type="text/javascript" src='/javascripts/test.js'></script>
こちらも問題ないですね?

5. 最後に

まだ終わりません。
生htmlと違い、タグで読み込むだけでは表示ができません。
どうしたら良いかと言うと、ディレクトリ構成の中にある、app.jsファイルに1行書き込みます。
app.jsはルーティングを定義したりしていますので、全体のコントローラと思ってください。
そこで以下を追加します。
app.use(express.static('public'));
若しくは
app.use(express.static(path.join(__dirname, 'public')));
__dirnameは現在実行されているファイルの絶対パスが入っています。
これで表示ができると思います。

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

Kinx ライブラリ - DateTime

DateTime

はじめに

「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。言語はライブラリが命。ということでライブラリの使い方編。

今回は DateTime です。Range でも使えるようにしました。

使い方

using DateTime

DateTime ライブラリは標準組み込みではないため、using ディレクティブを使用して明示的に読み込む。

using DateTime;

インスタンス化

インスタンス化は基本的には DateTime オブジェクトを new する方法で行う。

  • new DateTime() ... 現在時刻でインスタンス化
  • new DateTime(dateString) ... 文字列をパースしてインスタンス化
  • new DateTime(Unixtime) ... UNIXエポックの時刻からインスタンス化
  • new DateTime(year, month, day[, hour, minute, second]) ... 日時情報を個別に指定してインスタンス化

ただし、以下でも可能(内部で new して返しているだけ)。好きなものを使ってください。

  • DateTime.parse(...)
  • DateTime(...)

尚、dateString は以下のような書式を解釈する。

  • "2020-01-01""2020-1-1"
  • "2020/01/01""2020/1/1"
  • "2020-01-01T10:00:05""2020-1-01T10:0:5"
  • "2020/01/01 10:00:05""2020/1/01 10:0:5"

メソッド

DateTime オブジェクトには以下のメソッドがある。

メソッド 動作概要
isLeapYear() うるう年であれば true を返す
unixtime() 現在日時の Unix エポック時間を返す
datetime() 現在日時を表すオブジェクトを返す
year() 現在日時の「年」
month() 現在日時の「月」
day() 現在日時の「日」
hour() 現在日時の「時」
minute() 現在日時の「分」
second() 現在日時の「秒」
weekday() 現在日時の「週」(0: 日曜, 1: 月曜, ..., 6: 土曜)
isSunday() 日曜日であれば true を返す
isMonday() 月曜日であれば true を返す
isTuesday() 火曜日であれば true を返す
isWednesday() 水曜日であれば true を返す
isThursday() 木曜日であれば true を返す
isFriday() 金曜日であれば true を返す
isSaturday() 土曜日であれば true を返す
clone() 日時オブジェクトのコピーを返す
addDay(day) 日時オブジェクトを day 日進める(破壊的)
subDay(day) 日時オブジェクトを day 日戻す(破壊的)
addMonth(month) 日時オブジェクトを month か月進める(破壊的)
subMonth(month) 日時オブジェクトを month か月戻す(破壊的)
next() 次の日を表す新たな日時オブジェクトを返す
+(day) day 日後を表す新たな日時オブジェクトを返す
-(day) day 日前を表す新たな日時オブジェクトを返す
>>(month) month か月後を表す新たな日時オブジェクトを返す
<<(month) month か月前を表す新たな日時オブジェクトを返す
<=>(dt) 0: 日時が同じ、-1: dt のほうが後の日時、1: dt のほうが以前の日時
format(fmtString) fmtString のフォーマットに従ってフォーマットする。サポートするフォーマットは以下の通り。
%YYYY%:4桁の年、%YY%:2桁の年
%MM%:2桁の月、%M%:月
%DD%:2桁の日、%D%:日
%hh%:2桁の時、%h%:時
%mm%:2桁の分、%m%:分
%ss%:2桁の秒、%s%:秒

月末

<<>> で月を移動した場合、対応する月に同じ日が存在しない時は代わりにその月の末日が使われる。

using DateTime;
System.println(DateTime("2001-3-28") << 1);  // 2001/02/28 00:00:00
System.println(DateTime("2001-3-31") << 1);  // 2001/02/28 00:00:00

このことは以下のように、もしかすると予期しない振る舞いをするかもしれない(Ruby と一緒)。

using DateTime;
System.println(DateTime("2001-1-31") >> 2);        // 2001/03/31 00:00:00
System.println(DateTime("2001-1-31") >> 1 >> 1);   // 2001/03/28 00:00:00
System.println(DateTime("2001-1-31") >> 1 >> -1);  // 2001/01/28 00:00:00

Range

Range で使えるようにするには、next メソッドと <=> メソッドを定義しておけば良い。なので、DateTime オブジェクトは Range で使用できる。

using DateTime;
(DateTime(2020,1,1)..DateTime(2020,1,10))
    .each(&(d) => System.println(d));

.. なので最後の日が含まれる。... の場合は最後の日は含まれない。

2020/01/01 00:00:00
2020/01/02 00:00:00
2020/01/03 00:00:00
2020/01/04 00:00:00
2020/01/05 00:00:00
2020/01/06 00:00:00
2020/01/07 00:00:00
2020/01/08 00:00:00
2020/01/09 00:00:00
2020/01/10 00:00:00

Range で使えるので for-in でもそのままいける。

using DateTime;
for (var d in DateTime(2020,1,1)...DateTime(2020,1,10)) {
    System.println(d);
}

最終日を含まないループ。

2020/01/01 00:00:00
2020/01/02 00:00:00
2020/01/03 00:00:00
2020/01/04 00:00:00
2020/01/05 00:00:00
2020/01/06 00:00:00
2020/01/07 00:00:00
2020/01/08 00:00:00
2020/01/09 00:00:00

おわりに

作り始めてから約半年。色々できるようになってきましたねー。ライブラリを充実させて、何かしらのアプリを作れるようになることが次の目標ですかね。ニッチな用途でのアプリをサクッと作れる、とかできるとどこかに居場所ができるかもしれない。

ではまた次回。

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

Wixでjavascript sessionやarrayを使ってみる

コードをもうちょっとだけきれいにしてみた。

先週こちらの記事を書いたんですけど、もうちょっとコードを使いやすくしたので、公開しておきます。
以前は違う販売日を違うページにしていたのですが、今回、WixのSessionという機能(ユーザーがページを開いている間、そのユーザーのデータを保存する機能)を使って、販売日を記録しておき、希望日の在庫だけを見せる、という仕組みです。コードは汚いのですが、忘備録と、誰かの参考になればと思って、上げておきます。

やりたいこと

さて、ポストコード販売ページはこんな感じで、ポストコードを打つことでjavascriptが文字の置き換えやボタンの置き換えを行う、というのがやりたかったことです。
Screen Shot 2020-05-16 at 21.55.33.png
これにポストコードを入れると、
Screen Shot 2020-05-16 at 21.55.42.png
こういうボタンが出てきたり、
Screen Shot 2020-05-16 at 21.56.04.png
ゴメンネっていうコメントが出たりします。デリバリーの日付は複数あるので、この日付をSessionに記録します。そうすると、飛んだ先のページで日付を元にオンラインショップを構成します。また、いいやり方かどうかはわからないんですが、一応前日の5時をすぎるとオーダーできなくなる仕組みも実装しました。ホントはGitで管理すればいいんですけど、多分これGitできない仕様なので。。何か方法をご存知の方はお知らせくださいー

コードの解説。

汚いですが。
まずsessionをインポートします。'wix-storage'にセッションデータが記録されているようです。

import {session} from 'wix-storage';
import wixWindow from 'wix-window';

非常に汚いんですが、こういう方法で今回は配送日を記録します。
データベース接続すると、この辺はきれいになりそうです。
その辺は今回は飛ばしました。

const deliveryDateDay=[20,21,22,23]
const deliveryMonth=[4,4,4,4] //(0 for Jan, 11 for Dec)
const deliveryDate=["20May","21May","22May","23May"];
const listmonths=["Jan","Feb","Mar","Apr","May","June","July","Aug","Oct","Nov","Dec"];
const currentDate=today.getUTCDate()+listmonths[today.getUTCMonth()];
const deliveryPostcodeWed=["TW8","TW9","TW10","TW1","TW2"];
const deliveryPostcodeThu=["SW19","SW18"];
const deliveryPostcodeFri=["W6","W8","W10","W11","W14"];
const deliveryDay=["Wed","Thu","Fri","Sat"];
const deliveryPostcodes=[deliveryPostcodeWed,deliveryPostcodeThu,deliveryPostcodeFri];

JavascriptのDate機能を使って、現在の日付と時間を取り込み、水曜から土曜の各日付に対して、日付をすぎたかどうかを判定する部分です。ちょっとこれは月跨ぐとバグるコードなので、関数をちゃんと作るときに、月跨いでもいいコードに書き換えることにします。

const today = new Date();

//日付を超えたかどうか
var isPassed=[false,false,false,false];

//各日付に対して、月、日と時間が超えてないかどうかを確認します。
for (var i = 0; i < 4; i++){
  console.log(deliveryDateDay[i],today.getUTCDate(),deliveryMonth[i],today.getUTCMonth(),today.getUTCHours());
  if (deliveryDateDay[i]<=today.getUTCDate() && deliveryMonth[i]<=today.getUTCMonth() &&  16<=today.getUTCHours() ){
    isPassed[i]=true;
  }
}

セッションにこの情報を保存します。

//console.log(isPassed);
session.setItem("listDeliveryDate",deliveryDate);
session.setItem("listDeliveryDay",deliveryDay);
session.setItem("listIsPassed",isPassed);

ポストコードの関数などは、前回の記事にあるので省略。
JavascriptのArrayを使って、前回の関数iconButton3_onClickをチョコチョコ書き換えます。

export function iconButton3_onClick(event){
  let value = $w("#input1").value;

//ポストコードがちゃんと入力されれば実行
  if( isValidPostcode(value)) 
  {
    var postcode=formatPostcode(value);
    var areacodeslist=postcode.split(" ");
    var areacodes=areacodeslist[0];
    session.setItem("POSTCODE", postcode);
    session.setItem("AREACODE", areacodes);
    var deliveryList=[(deliveryPostcodeWed.indexOf(areacodes)>-1),(deliveryPostcodeThu.indexOf(areacodes)>-1),(deliveryPostcodeFri.indexOf(areacodes)>-1)]
    var isDeliverable = false;
    //console.log(deliveryList,isDeliverable);

//配達可能な三日間のArrayをforで巡回します。
    for (i = 0; i < 3; i++) {
//配達可能なとき
      if (deliveryList[i] && isDeliverable===false && isPassed[i]===false){
        //$w("#text26").text="Sorry! Preorder is over! Please come back next week!";
        $w("#iconButton1").label = "Order Delivery on "+deliveryDate[i]+" "+deliveryDay[i];
        $w("#iconButton1").link = "/shoppingpage";
        $w("#iconButton1").show();
        session.setItem("Date", deliveryDate[i]);
        session.setItem("Day", deliveryDay[i]);
        $w("#text26").text="Great news! We are delivering to your area, "+deliveryPostcodes[i]+" on "+deliveryDate[i]+".  Please tell your friends :)";
        isDeliverable = true;
      }
//配達できるけど、時間を過ぎてしまったとき
      else if (deliveryList[i] && isDeliverable===false && isPassed[i]===true) {
        $w("#iconButton1").label = "Order Shop Collection";
        $w("#iconButton1").link = "/choosedate";
        $w("#iconButton1").show();
        $w("#text26").text="Sorry! We are delivering to your area, "+deliveryPostcodes[i]+" on "+deliveryDay[i] +" but preorder is over this week. Please wait until next week or you can always use click and collect service from the shop!";
        isDeliverable = true;

      }
//そのポストコードに配達できないとき
      else if (isDeliverable===false){
        $w("#iconButton1").label = "Order Shop Collection";
        $w("#iconButton1").link = "/choosedate";
        $w("#iconButton1").show();
        $w("#text26").text="We are very sorry, but at the moment there are no scheduled deliveries for your area in the next few weeks. You can still collect from the store using our click and collect service. Simply choose your order online, select a pick up date and we’ll see you then :)";  //or else do this other thing
      }
    }
  }
//ポストコードが判定できなかったとき
  else{
    $w("#text26").text="Sorry! We can't recognize your postcode......";  //or else do this other thing
  }
}

こんな感じです。飛んだ先のページでは、こんな感じでデータを取得できます。ただし、sessionの中のarrayはテキストになっているので、arrayに戻したり上げたりすることが必要かもしれません。

import {session} from 'wix-storage';

let postcode=session.getItem("POSTCODE");
let areacode=session.getItem("AREACODE");
let deliveryDate=session.getItem("Date");
let deliveryDay=session.getItem("Day");

またそのうちまとめてコードをあげようと思いますが、今日はこの辺にしておきます。

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

KEN_ALL.CSVをEmEditorでゴニョゴニョしてみる

機能

  • KEN_ALL.CSV(日本郵便で公開されている、郵便番号データ)を元に、町域名(半角カタカナ/漢字)部分をスッキリさせる(複数行にわたる括弧部分を削除とか、"以下を除く"等を削除)
  • 変換後のファイルを出力する(必要な列のみ出力するようにすることも簡単な修正で可能)

実行環境

-Windows10
-EmEditor Professional (64-bit) Version 19.8.5 にて確認

使い方

  1. 日本郵便の郵便番号データダウンロードページで公開されている郵便番号データ(全国一括)を取得し、"KEN_ALL.CSV"ファイルを準備する(*1)
  2. EmEditorで"KEN_ALL.CSV"ファイルを開き、下記マクロを実行する。
  3. KEN_ALL.CSVと同じディレクトリにKEN_ALL_cnv.txtファイルが作成される
  4. (KEN_ALL_LOG.txtファイルも出力される)

(*1)「読み仮名データの促音・拗音を小書きで表記しないもの」と「読み仮名データの促音・拗音を小書きで表記するもの」と2種類になっているが、"・・・小書きで表記するもの"でテストしています。変換処理の正規表現を変えることにより、"・・・小書きで表記しないもの"でも使えると思われます

マクロ

郵便番号データ変換_v2.jsee
var startTime = new Date();
/*
□マクロ名:郵便番号データ変換.jsee
□バージョン:2.0
 2020 05 16 クラスで実装
 
■開くファイル
 郵便番号データダウンロードから取得した住所の郵便番号(CSV形式)
 (ken_all.zipを展開したKEN_ALL.CSV。シフトJIS、改行コードは0x0D,0x0A)
 https://www.post.japanpost.jp/zipcode/dl/kogaki-zip.html
■作成ファイル
 レイアウト同じで町域名の内容を簡略化したファイル(ファイル名=拡張子の前に"_cnv"を追加したテキストファイル)
■手順(例)
 KEN_ALL.CSVを開く
 郵便番号データ変換_v2.jseeを実行
 同一ディレクトリに変換後のファイル(UTF-8)が作成される
*/

//■■■■■■■■■■■■■■■■■■■■■■■■■■■
//クラス定義
//■■■■■■■■■■■■■■■■■■■■■■■■■■■

//■■■■■■■■■■■■■■■■■■■■■■■■■■■
//■■ ログ情報クラス
//■■■■■■■■■■■■■■■■■■■■■■■■■■■
//■■■■■■■■■■■■■■■■■■■■■■■■■■■
//【名称】ログ情報:コンストラクタ
//【引数】なし
//【返却】なし
//【処理】ログ情報を管理する配列を作成する
//■■■■■■■■■■■■■■■■■■■■■■■■■■■
Log = function(){
    OutputBar.writeln("○Log.コンストラクタ(ログ情報:start)");
    //コンストラクタ
    this.arrLog = [];
    OutputBar.writeln("○Log.コンストラクタ(ログ情報:end)");
};

//■■■■■■■■■■■■■■■■■■■■■■■■■■■
//【名称】ログ情報:追加
//【引数】ログ種類、郵便番号、ログ内容
//【返却】なし
//【処理】this.arrLogにログ情報を追加する
//■■■■■■■■■■■■■■■■■■■■■■■■■■■
Log.prototype.push = function(
    logType,//ログ種別
    postalCode,//郵便番号
    logContent//ログ内容
    ){
//  OutputBar.writeln("○Log.push(ログ情報.追加:start)");
    tmpArr = [];
    tmpArr[0] = logType;//ログ種別
    tmpArr[1] = postalCode;//郵便番号
    tmpArr[2] = logContent;//ログ内容
    this.arrLog.push(tmpArr);
//  OutputBar.writeln("○Log.push(ログ情報.追加:end)");
};

//■■■■■■■■■■■■■■■■■■■■■■■■■■■
//【名称】ログ情報:OutputBar出力
//【引数】なし
//【返却】なし(OutputBarに出力するのみなので)
//【処理】OutputBarにログ情報を出力する
//■■■■■■■■■■■■■■■■■■■■■■■■■■■
Log.prototype.toString = function(){
    OutputBar.writeln("○Log.toString(ログ情報.OutputBar出力:start)");
    for (var i = 0; i < this.arrLog.length; i++){
        OutputBar.writeln("this.arrLog[" + i + "]="
            + this.arrLog[i][0] + ","
            + this.arrLog[i][1] + ","
            + this.arrLog[i][2]
        );
    }
    OutputBar.writeln("○Log.toString(ログ情報.OutputBar出力:end)");
};

//■■■■■■■■■■■■■■■■■■■■■■■■■■■
//【名称】ログ情報:書き出し
//【引数】出力ファイルの完全パス+ファイル名
//【返却】なし
//【処理】ログ情報をまとめたthis.arrLogをファイルに書き出す
//■■■■■■■■■■■■■■■■■■■■■■■■■■■
Log.prototype.fWrite = function(sLogFilePathName){
    OutputBar.writeln("○Log.fWrite(ログ情報.書き出し:start)");
    sLog = '';
    for (var i = 0; i < this.arrLog.length; i++){
        sLog +=  this.arrLog[i][0] + ","
            + this.arrLog[i][1] + ","
            + this.arrLog[i][2] + "\r\n";
    }
    editor.NewFile();//新規にファイルを作成
    docLog = editor.ActiveDocument;//現在開いている Document オブジェクトを返します。
    docLog.write( sLog );//現在のカーソル位置に文字列を挿入、または上書き
    docLog.Encoding = eeEncodingUTF8;//次に保存する時に使用されるエンコードを設定(UTF-8)
    docLog.UnicodeSignature = false;//次に保存する時に Unicode サイン (BOM) を付けない
    docLog.Save( sLogFilePathName );//文書を保存します。
    editor.ExecuteCommandByID(4120);//docNoWriteを保存しないで閉じます。■DELETE■
    OutputBar.writeln("○Log.fWrite(ログ情報.書き出し:end)");
};

//■■■■■■■■■■■■■■■■■■■■■■■■■■■
//■■ 郵便番号管理クラス
//■■■■■■■■■■■■■■■■■■■■■■■■■■■
//■■■■■■■■■■■■■■■■■■■■■■■■■■■
//【名称】郵便番号管理:コンストラクタ
//【引数】なし
//【返却】なし
//【処理】郵便番号管理を管理するdocumentオブジェクトを作成する
//■■■■■■■■■■■■■■■■■■■■■■■■■■■
PostalCodeManage = function(docInFile,log){
    OutputBar.writeln("○PostalCodeManage.コンストラクタ(郵便番号管理:start)");

    docInFile.ExtractColumns(":1,:2,:3,:4,:5,:6,:7,:8,:9,:10,:11,:12,:13,:14,:15");
    this.docPCI = editor.ActiveDocument;

    docInFile.Activate();//文書をアクティブにします。
    editor.ExecuteCommandByID(4120);//ファイルを保存しないで閉じます

    this.log = log;
    OutputBar.writeln("○PostalCodeManage.コンストラクタ(郵便番号管理:end)");
};

//■■■■■■■■■■■■■■■■■■■■■■■■■■■
//【名称】郵便番号管理:書き出し
//【引数】なし
//【返却】なし
//【処理】this.docPCIを書き出す
//【めも】fn=ファイル名、celn=書き出す列
//■■■■■■■■■■■■■■■■■■■■■■■■■■■
PostalCodeManage.prototype.fWrite = function(fn,celn){
OutputBar.writeln("○PostalCodeManage.fWrite(郵便番号管理.書き出し:start)");

//●書き出し
    this.docPCI.ExtractColumns(celn);
    this.docPCI.Activate();//文書をアクティブにします。
    editor.ExecuteCommandByID(4120);//ファイルを保存しないで閉じます

    this.docWrite = editor.ActiveDocument;
    this.docWrite.Encoding = eeEncodingUTF8;//保存時のエンコード:UTF-8
    this.docWrite.UnicodeSignature = false;//保存時に(BOM)を付けない
    this.docWrite.Save( fn );//文書を保存
    this.docWrite.close();//文書を閉じる

OutputBar.writeln("○PostalCodeManage.fWrite(郵便番号管理.書き出し:emd)");
};

//■■■■■■■■■■■■■■■■■■■■■■■■■■■
//【名称】郵便番号管理:チェック1
//【引数】なし
//【返却】なし
//【処理】this.docPCIの町域名(漢字)の重複を削除
//【めも】
//■■■■■■■■■■■■■■■■■■■■■■■■■■■
PostalCodeManage.prototype.cnv1a = function(){
    OutputBar.writeln("○PostalCodeManage.cnv1(郵便番号管理.チェック1:start)");

//■町域の複数行に渡るかっこ書きを削除
//郵便番号の列を取得
this.docPCI.selection.SetActivePoint( eePosCellLogical, 3, 1 );
editor.ExecuteCommandByID(4461);//CSV文書で現在の列をヘディング無しで箱型選択モードで選択
sTmp = "\r\n" + this.docPCI.selection.Text;
aPostalCode = new Array();
aPostalCode = sTmp.split("\r\n");

//町域の列を取得
//this.docPCI.Activate();//文書をアクティブにします。
this.docPCI.selection.SetActivePoint( eePosCellLogical, 9, 1 );
editor.ExecuteCommandByID(4461);//CSV文書で現在の列をヘディング無しで箱型選択モードで選択
sTmp = "\r\n" + this.docPCI.selection.Text;//値を[1]以降に格納
aTyou = new Array();
aTyou = sTmp.split("\r\n");
sTmp = null;

this.docPCI.selection.Collapse();//選択状態をキャンセルします。
OutputBar.writeln("aPostalCode.length=" + aPostalCode.length);
OutputBar.writeln("aTyou.length=" + aTyou.length);

//最終行から開始行へ向かって処理を行う
fOkikae = false;//フラグ置き換えモード:終わり
re1 = new RegExp("^\"[^(]*)\"$");//'('が無く、右端の')'を検索(両端は'"')
re2 = new RegExp("^(\".+)(([^)]*)(\")");//'('があり')'が無い行を検索($1:開く括弧の前、$3:'"')
for(i = aTyou.length - 1; i >= 1; i--){
    if( ( aPostalCode[i] == aPostalCode[i-1] ) || fOkikae){//郵便番号が同じor置き換えモードの場合
        this.docPCI.selection.SetActivePoint( eePosCellLogical, 9, i );
        if( fOkikae ){
            if(aTyou[i].match(re2)){//'('を検索
                this.log.push('i',this.docPCI.GetCell( i, 3, eeCellIncludeNone ),RegExp.$1 + RegExp.$3);
                this.docPCI.SetCell( i , 9, RegExp.$1 + RegExp.$3, eeDontQuote );//"("以降の部分を削除
                fOkikae = false;//フラグ置き換えモード:終わり
            }else{
                //'('の行と')'の行の間の行(行をブックマーク)
                this.docPCI.selection.SelectLine();
                this.docPCI.selection.SetBookmark();
            }
        } else {
            if(aTyou[i].match(re1)){//')'を検索
                this.docPCI.selection.SelectLine();
                this.docPCI.selection.SetBookmark();
                fOkikae = true;//フラグ置き換えモード:開始
            }
        }
    }
}

editor.ExecuteCommandByID(4589);//この文書のすべてのブックマークされた行を削除

OutputBar.writeln("○PostalCodeManage.cnv1(郵便番号管理.チェック1:end)");
};

//■■■■■■■■■■■■■■■■■■■■■■■■■■■
//【名称】郵便番号管理:チェック2
//【引数】なし
//【返却】なし
//【処理】this.docPCIの町域名(漢字)の置き換え
//【めも】
//■■■■■■■■■■■■■■■■■■■■■■■■■■■
PostalCodeManage.prototype.cnv2 = function(){
    OutputBar.writeln("○PostalCodeManage.cnv2(郵便番号管理.チェック2:start)");

//■町域の文字列を置き換え
this.docPCI.selection.SetActivePoint( eePosCellLogical, 9, 1 );//カーソル位置を設定
editor.ExecuteCommandByID(4461);//CSV文書で現在の列をヘディング無しで箱型選択モードで選択します。

//★文字列決め打ちで削除
this.docPCI.selection.Replace("以下に掲載がない場合","",eeFindReplaceSelOnly | eeReplaceAll,0);
this.docPCI.selection.Replace("(\".*)((その他|丁目|番地|次のビルを除く|地階・階層不明|.*[、~].*|.*以上|.*以下))(\")","\\1\\3",eeFindReplaceSelOnly | eeReplaceAll | eeFindReplaceRegExp,0);

//★名駅ミッドランドスクエア(高層棟)(地階・階層不明)
this.docPCI.selection.Replace("(\".*)(高層棟)(.*\")","\\1\\2",eeFindReplaceSelOnly | eeReplaceAll | eeFindReplaceRegExp,0);

//★三田市の次に番地がくる場合
this.docPCI.selection.Replace("\".*の次に番地がくる場合\"","\"\"",eeFindReplaceSelOnly | eeReplaceAll | eeFindReplaceRegExp,0);

//★土樋(1丁目「11を除く」) => 土樋(1丁目)
//★切畑(長尾山「その他」) => 切畑(長尾山)
this.docPCI.selection.Replace("(\".*(.*)「(.*を除く|その他)」()\")","\\1\\3",eeFindReplaceSelOnly | eeReplaceAll | eeFindReplaceRegExp,0);

//★音江町(国見その他) => 音江町(国見)
this.docPCI.selection.Replace("(\".*(.*)その他()\")","\\1\\2",eeFindReplaceSelOnly | eeReplaceAll | eeFindReplaceRegExp,0);

//★花田町官有地(無番地を除く)
//★芦田町福田(376-10を除く)
//★津島町下畑地(乙を除く)
this.docPCI.selection.Replace("(\".*)(.*を除く)(\")","\\1\\2",eeFindReplaceSelOnly | eeReplaceAll | eeFindReplaceRegExp,0);

//★厚内(全域) => 厚内
this.docPCI.selection.Replace("(\".*)(全域)(\")","\\1\\2",eeFindReplaceSelOnly | eeReplaceAll | eeFindReplaceRegExp,0);

//■町域のかっこ"(" => " "、")" => ""へ
document.selection.Replace("(\".*)((.*))(\")","\\1 \\2\\3",eeFindReplaceSelOnly | eeReplaceAll | eeFindReplaceRegExp,0);

OutputBar.writeln("○PostalCodeManage.cnv2(郵便番号管理.チェック2:end)");
};

//■■■■■■■■■■■■■■■■■■■■■■■■■■■
//【名称】郵便番号管理:チェック3
//【引数】なし
//【返却】なし
//【処理】this.docPCIの町域名(半角カタカナ)の置き換え
//【めも】
//■■■■■■■■■■■■■■■■■■■■■■■■■■■
PostalCodeManage.prototype.cnv3 = function(){
    OutputBar.writeln("○PostalCodeManage.cnv3(郵便番号管理.チェック3:start)");

//■町域の文字列を置き換え
this.docPCI.selection.SetActivePoint( eePosCellLogical, 6, 1 );//カーソル位置を設定
editor.ExecuteCommandByID(4461);//CSV文書で現在の列をヘディング無しで箱型選択モードで選択します。

//★開き括弧のみの場合、括弧以降を削除
re2 = new RegExp("^(\".+)\\(([^\\)]*)(\")");//'('があり')'が無い行を検索($1:開く括弧の前、$3:'"')
nLine = this.docPCI.GetLines();
for(i = 1; i < nLine ; i++){
    if(this.docPCI.GetCell( i, 6, eeCellIncludeQuotes ).match(re2)){//'('を検索
        this.docPCI.SetCell( i , 6, RegExp.$1 + RegExp.$3, eeDontQuote );//"("以降の部分を削除
    }
}

//★文字列決め打ちで削除
this.docPCI.selection.Replace("イカニケイサイガナイバアイ","",eeFindReplaceSelOnly | eeReplaceAll,0);
this.docPCI.selection.Replace("(\".*)\\((ソノタ|チョウメ|バンチ|ツギノビルヲノゾク|チカイ・カイソウフメイ|.*[、\\-].*|.*イジョウ|.*イカ)\\)(\")","\\1\\3",eeFindReplaceSelOnly | eeReplaceAll | eeFindReplaceRegExp,0);

//★名駅ミッドランドスクエア(高層棟)
this.docPCI.selection.Replace("(\".*)\\(コウソウトウ\\)(.*\")","\\1\\2",eeFindReplaceSelOnly | eeReplaceAll | eeFindReplaceRegExp,0);

//★三田市の次に番地がくる場合
this.docPCI.selection.Replace("\".*ノツギニバンチガクルバアイ\"","\"\"",eeFindReplaceSelOnly | eeReplaceAll | eeFindReplaceRegExp,0);

//★ツチトイ(1チョウメ<11ヲノゾク>) => ツチトイ(1チョウメ)
//★キリハタ(ナガオサン<ソノタ>) => キリハタ(ナガオサン)
this.docPCI.selection.Replace("(\".*\\(.*)<(.*ヲノゾク|ソノタ)>(\\)\")","\\1\\3",eeFindReplaceSelOnly | eeReplaceAll | eeFindReplaceRegExp,0);

//★オトエチョウ(クニミソノタ) => オトエチョウ(クニミ)
this.docPCI.selection.Replace("(\".*\\(.*)ソノタ(\\)\")","\\1\\2",eeFindReplaceSelOnly | eeReplaceAll | eeFindReplaceRegExp,0);

//★ハナダチョウカンユウチ(ムバンチヲノゾク)
//★アシダチョウフクダ(376-10ヲノゾク)
//★ツシマチョウシモハタジ(オツヲノゾク)
this.docPCI.selection.Replace("(\".*)\\(.*ヲノゾク\\)(\")","\\1\\2",eeFindReplaceSelOnly | eeReplaceAll | eeFindReplaceRegExp,0);

//★アツナイ(ゼンイキ) => アツナイ
this.docPCI.selection.Replace("(\".*)\\(ゼンイキ\\)(\")","\\1\\2",eeFindReplaceSelOnly | eeReplaceAll | eeFindReplaceRegExp,0);

//■町域(半角カタカナ)のかっこ"(" => " "、")" => ""へ
document.selection.Replace("(\".*)\\((.*)\\)(\")","\\1 \\2\\3",eeFindReplaceSelOnly | eeReplaceAll | eeFindReplaceRegExp,0);

OutputBar.writeln("○PostalCodeManage.cnv3(郵便番号管理.チェック3:end)");
};

//■■■■■■■■■■■■■■■■■■■■■■■■■■■
//処理開始
//■■■■■■■■■■■■■■■■■■■■■■■■■■■

var startTime = new Date();
OutputBar.Visible = true;
OutputBar.Clear();
OutputBar.writeln("○(処理:start)");
Redraw = false;//ウィンドウの再描画:しない

docInFile = editor.ActiveDocument;
//文書を含むディレクトリのパスをファイル名を付けないで取得します。
sInFilePath = docInFile.Path;
//文書のファイル名をパスを付けないで取得します。
aInFileName = docInFile.Name.split(".");
//変換後書き出すファイル名
sOutFilePathName = sInFilePath + "\\" + aInFileName[0] + "_cnv.txt";
//ログ用ファイル名を設定
sLogFilePathName = sInFilePath + "\\" + aInFileName[0] + "_LOG.txt";

//CSV モードで列の数を取得します。文書が CSV モードでない場合は、0 を返します。
columnN = docInFile.GetColumns();
if(columnN != 15){
    alert( "列数エラー。列数は、" + columnN + "です。" );
    Quit();
}
//●ログ管理オブジェクトを作成
var log = new Log();
//●郵便番号管理オブジェクトを作成
var pcManage = new PostalCodeManage(docInFile,log);
//●町域名が複数行に渡る場合の処理
pcManage.cnv1a();//配列使用版
//pcManage.cnv1b();//match使用版
//●町域名(漢字)の置き換え
pcManage.cnv2();
//●町域名(半角カタカナ)の置き換え
pcManage.cnv3();

//●変換後のファイルを書き出す
pcManage.fWrite(sOutFilePathName,":1,:2,:3,:4,:5,:6,:7,:8,:9,:10,:11,:12,:13,:14,:15");
//pcManage.fWrite(sOutFilePathName,":3,:7,:8,:9"); //必要な列のみ指定する場合

//●ログファイル書き出し
log.fWrite(sLogFilePathName);

var endTime = new Date();
Redraw = true;//ウィンドウの再描画:する
alert("処理終了:" + (endTime - startTime)/1000 + "sec");
Quit();

補足

  • 町域名(半角カタカナ)を利用しないのであれば、"pcManage.cnv3();"の処理は不要です。
  • ログファイル部分の処理は変換処理には関係ありませんので、削除しても問題ありません。
  • 出力ファイルの文字コードや改行コードなどを変更する場合は、"pcManage.fWrite()"を変更すればよいです。
  • EmEditorでのcsvファイル操作のメモ書きとして

参考情報

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

【API】郵便番号検索APIを使ってみた

APIを使用したアプリを作ってみる

背景

  • APIを使用したアプリケーションが増えてきている
  • APIを使用する、作るエンジニアの仕事も増えている
  • APIを扱う技術の需要が増えそう

目次

0.環境確認

  • OS: Windows10 home
  • IDE : Eclipse(Photon 4.8)
  • ビルドツール : Gradle
  • サーバーサイド言語 : Java(1.8)
  • JavaScript ライブラリ : jQuery(3.3.1)
  • テンプレートエンジン : thymeleaf

1.APIの確認

  • 以下のサイトからAPIの使用を確認します。
    https://zip-cloud.appspot.com/doc/api

  • 仕様を確認します。

    パラメータ名 項目名 必須 備考
    zipcode 郵便番号 7桁の数字。ハイフン付きでも可。完全一致検索。
    callback コールバック関数名 - JSONPとして出力する際のコールバック関数名。UTF-8でURLエンコードした文字列。
    limit 最大件数 - 同一の郵便番号で複数件のデータが存在する場合に返される件数の上限値(数字) ※デフォルト:20
    • レスポンスパラメータ
    フィールド名 項目名 備考
    status ステータス 正常時は 200、エラー発生時にはエラーコードが返される
    message メッセージ エラー発生時に、エラーの内容が返される。
    results zipcode(郵便番号)
    prefcode(都道府県コード)
    address1(都道府県名)
    address2(市区町村名)
    address3(町域名)
    kana1(都道府県名カナ)
    kana2 (市区町村名カナ)
    kana3(町域名カナ)
    複数の場合、配列となる
    • (例)郵便番号「7830060」で検索する場合
      • リクエストURL https://zip-cloud.appspot.com/api/search?zipcode=7830060
      • レスポンス

        { "message": null, "results": [ { "address1": "北海道", "address2": "美唄市", "address3": "上美唄町協和", "kana1": "ホッカイドウ", "kana2": "ビバイシ", "kana3": "カミビバイチョウキョウワ", "prefcode": "1", "zipcode": "0790177" }, { "address1": "北海道", "address2": "美唄市", "address3": "上美唄町南", "kana1": "ホッカイドウ", "kana2": "ビバイシ", "kana3": "カミビバイチョウミナミ", "prefcode": "1", "zipcode": "0790177" } ], "status": 200 }

2.プロジェクトの作成

別の記事で詳細に紹介しているので、そちらを参照ください。
GradleのSpringBootプロジェクトを作成する

3.サーバーエンドの実装

  • build.gradle
build.gradle
//中略
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    compile("com.fasterxml.jackson.core:jackson-databind")
}


  • Controllerクラス
FrontController.java
@Controller
public class FrontController {

    @Autowired
    private FrontService frontService;

    @RequestMapping({ "/", "/index" })
    public String index() {
        return "index";
    }
    @ResponseBody
    @RequestMapping(value = "/getAddress" ,method = RequestMethod.POST, produces="application/json;charset=UTF-8")
    public String getAddress(@RequestBody(required = false) AddressForm addressForm) {
        return frontService.getAddress(addressForm.getZipcode());
    }
}
  • Serviceクラス
FrontService.java
public interface FrontService {
    public String getAddress(String zipCode);
}
FrontServiceImpl.java
@Service
public class FrontServiceImpl implements FrontService {

    /** 郵便番号検索API リクエストURL */
    private static final String URL = "https://zip-cloud.appspot.com/api/search?zipcode={zipcode}";

    @Override
    public String getAddress(String zipCode) {
        String zipCodeJson = restTemplate.getForObject(URL, String.class, zipCode);
        return zipCodeJson;
    }
}
  • formクラス
AddressForm.java
@Data
public class AddressForm {

    /** 郵便番号 */
    String zipcode;
}

4.フロントエンドの実装

  • html
index.html
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>address</title>
        <script type="text/javascript" th:src="@{/jquery/jquery-3.3.1.js}"></script>
        <script th:src="@{/js/index.js}"></script>
    </head>
    <body>
        <form name="getAddress">
            <input id="zipcode" type="text">
            <button type="button" id="getAddressBtn">住所取得</button>
            <div id="dispAddress"></div>
        </form>
    </body>
</html>
  • JavaScript
index.js
$(function() {
    $('#getAddressBtn').on('click', function() {
        var params = {
                "zipcode" : $('#zipcode').val()
        };
        $.ajax({
            url : 'getAddress',
            type: 'POST',
            contentType: "application/json",
            data: JSON.stringify(params),
            dataType : 'json',
            async: false,
            success: function (data) {
                $("#dispAddress").empty();
                var dispAddress = document.getElementById("dispAddress");
                var table = document.createElement("table");
                table.setAttribute("border","2");
                table.setAttribute("cellpadding","15");
                table.setAttribute("style","margin :15px");

                $(data.results).each(function(index, result){
                    table.appendChild(createRow("郵便番号",result.zipcode));
                    table.appendChild(createRow("都道府県コード",result.prefcode));
                    table.appendChild(createRow("都道府県名",result.address1));
                    table.appendChild(createRow("市区町村名",result.address2));
                    table.appendChild(createRow("町域名",result.address3));
                    table.appendChild(createRow("都道府県名カナ",result.kana1));
                    table.appendChild(createRow("市区町村名カナ",result.kana2));
                    table.appendChild(createRow("町域名カナ",result.kana3));
                });
                dispAddress.appendChild(table);
            }
        });
    });
});

function createRow(header , value){
    var tr = document.createElement("tr");
    var th = document.createElement("th");
    th.append(header);
    var td = document.createElement("td");
    td.append(value);
    tr.appendChild(th);
    tr.appendChild(td);
    return tr;
}

5.動作確認

  • ブラウザで「localhost:8080」にアクセス
    ブラウザ起動.png

  • 「100-0001」(皇居の郵便番号)を入力して住所取得ボタン押下
    ブラウザ起動2.png

  • 住所が表示された。

6.まとめ

  • APIを使用するのは簡単(認証機能付きはもう少し難しい)
  • 他APIからデータ活用できる
  • いろんなAPIを組み合わせて新しいサービスを作れそう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初級編】JavaScript if文についてその2

前回の記事
https://qiita.com/furukouji/items/8d6bdad9d0c2fce011db

? 簡単な条件記入例(サラッと書きます)

const price = 99;

if (price >= 100){      //priceが100以上であるとき実行する
 console.log('値段は'+price+'円です');
};

if (price <= 100){     //priceが100以下であるとき
 console.log('お金が足りません。');
};

if (price != 100){       //!は否定形です。100ではないときコードを実行します。 また!==とすることで型の判定もできるので基本的にはこちらを使いましょう
 console.log('値段は'+price+'円です');
};

AND,OR,三項演算子について

AND = 条件を両方満たす場合実行する。コード上は  && と書くと前述と同義となります
OR  = どちらかの条件を満たす場合実行する。コード上は || と書きます。(フォントの関係で斜めになってます)
ということでコードを書いていきましょう。

const signal_1 = 'red'
const signal_2 = 'yellow'

if(signal_1 ==='red' && signal_2'yellow'){
 console.log('赤と黄色です。');
}

if(signal_1'red' || signal_2 'yellow'){
 console.log('赤か黄色です');
}

AND,ORは基本的には記号が違うだけで使い方は変わりません!!

三項演算子について書いてきます。
三項演算子の基本の形は

条件 ? 真 : 偽 

このような形になります。文字だけだと意味不明なので、実際にコードを書いていきます。

const point = 100 ;

const player = point > 100 ? 'good' : 'not good';  //pointが100より大きかったらgoodが低ければ not good が値として返ります。

console.log(player);

このように一文でスッキリと書くことができるのでおすすめです!

今回は短いですがこのへんで!

? まとめ

  • AND = && OR = ||
  • 三項演算子の基本形は 条件 ? 真: 偽
  • 三項演算子のメリットは一文でスッキリ書ける

? 次回予告

次回は、内容未定です!
for文の内容にしようかと思っていますが
Node.jsの内容になるかもしれません!

*記事を見ていただいてありがとうございます。
ご指摘有りましたら編集リクエストまでドシドシお願いいたします!
*

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

DenoはNode.jsとどこが違うのか超ざっくりまとめ?

deno-logo.png

2020年5月13日、ついにver1.0がリリースされたDeno
Node.jsとの違いをざっくりと解説します。

※以下、本記事の内容はDeno公式マニュアルの記述に基づきます

そもそもDenoって何なの?

DenoはJavaScriptおよびTypeScriptの実行環境です。
V8, Rust, およびTokioによって作られています。

え、Node.jsがあるじゃん?

Node.jsは2009年に誕生し、すでに10年を超える歴史を有しています。

たとえばNode.jsが生まれたとき、この世にPromiseはありませんでした。
2012年にはTypeScriptが生まれました。

この10年で、JavaScriptを取り巻く環境は大きく変わってしまったのです。

そして2020年。
Node.jsの問題を解決し、よりよい開発環境を提供するため、Node.jsの開発者であるライアン・ダールらの手によって生まれたのがDenoなのです(DenoはNodeのアナグラム)。

DenoはNode.jsとどこが違うのか

npmを使いません

モジュールは任意のURLまたはファイルパスからダウンロードされます。

import {hoge} from "fuga";

というNode.jsでの記述形式は、指定されたモジュールがどこにあるのか特定できないという問題を引き起こしていました。

fuga モジュールはNPMサーバにあるのかもしれないし、ローカルのnode_modulesフォルダにあるのかもしれません。

import {hoge} from "https://your.domain.com/fuga.ts";

とすることで、fugaライブラリの場所は一意に定まります。

またこれは同時に、package.jsonに相当するしかけを必要としないことをも意味します。

URLやファイルパスがそのままバージョンの指定にもなっていれば、わざわざ別ファイルでその依存性情報まで管理する必要はないからです。

TypeScriptを標準でサポートします

追加のライブラリをインストールすることなく、TypeScriptをコンパイルしてくれます。

コンパイルは自動的に行われるので、コマンドを叩く必要もありません。

セキュアです

明示的に許可されない限り、denoはネットワーク接続もファイル読み取りもできません。

例として、標準ライブラリとして公開されているcurl.tsでURLへのアクセスを試してみるとエラーが返ります。

$> deno run https://deno.land/std/examples/curl.ts https://example.com
error: Uncaught PermissionDenied: network access to "https://example.com/", run again with the --allow-net flag

接続許可するドメインを--allow-netフラグに設定することで初めて、URLへのアクセスが可能になります。

deno run --allow-net=example.com https://deno.land/std/examples/curl.ts https://example.com

よくわからないNPMパッケージを走らせたら、変なURLに接続されたりファイルシステムを触られたりするのではないか……という恐怖ともこれでおさらばです。

詳細: https://deno.land/manual/getting_started/permissions

標準テストランナーがあります

Deno.testを使えば、ライブラリをインストールすることなくテストを実行できます。

Deno.test("Hello deno test", () => {
  const foo: string = "Hello";
  if (foo !== "Hello") {
    throw ("foo should be Hello.");
  }
});

アサーションライブラリも用意されています。

import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
Deno.test("Hello assertion", () => {
  const foo: string = "Hello";
  assertEquals(foo, "Hello");
});

実行するときは deno test [files] コマンドを叩くだけ。かんたん!

詳細: https://deno.land/manual/testing

非同期のアクションはすべてPromiseを返します

Webサーバを記述する場合、Node.jsではこんな感じでしたが

var http = require('http');

http.createServer(function (req, res) {
  res.write('Hello World!');
  res.end();
}).listen(8080);

出典: https://www.w3schools.com/nodejs/nodejs_http.asp

Denoではawaitを使ってこんな感じになります。

import { serve } from "https://deno.land/std/http/server.ts";
const s = serve({ port: 8080 });
for await (const req of s) {
  req.respond({ body: "Hello World!\n" });
}

出典: https://deno.land/std/http

予期せぬエラーが発生した場合は"常に"処理を終了します

Uncaught Exceptions in Nodeなどの記事に見られるように、Node.jsでは捕捉できないエラーをどう扱うかは開発者に任せられていました。

処理を終了させてもいいし、もみ消して続行させることもできました。

Denoでは常に処理が終了します。常にです。

おまけ

Node.jsが抱える問題点、およびDenoが目指すゴールについては、ライアン・ダール自らによる以下のプレゼンテーションを見るとわかりやすいです。
Node.jsに関する10の反省点 Ryan Dahl - JSConf EU 2018

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

JavaScriptにおける配列リテラルとコンストラクターの違いについて[小ネタ]

0.はじめに

こんにちは。
エンジニアしている @gkzz です。

JavaScriptのお勉強をしているのですが、Google JavaScript Style Guideを読んでいて気になった箇所がありました。

5.2.2 Do not use the variadic Array constructor
The constructor is error-prone if arguments are added or removed. >Use a literal instead.

Disallowed:

const a1 = new Array(x1, x2, x3);
const a2 = new Array(x1, x2);
const a3 = new Array(x1);
const a4 = new Array();

そういえば、[]とnew Array()。
違いってあるのだろうか。
気になってしまったので、早速調べて、まとめました。

1.非推奨のArrayコンストラクターを使った場合

let data2 = new Array(10);

console.log("data2: " + data2);
console.log("data2[0]: " + data2[0]);
console.log("data2.length: " + data2.length);

console.log("#######################");

data2[0] = 10;

console.log("data2: " + data2);
console.log("data2[0]: " + data2[0]);
console.log("data2.length: " + data2.length);
data2: ,,,,,,,,,
data2[0]: undefined
data2.length: 10
#######################
data2: 10,,,,,,,,,
data2[0]: 10
data2.length: 10

ポイント

  • new Array(10)とは、lengthが10である配列を定義
    • ひとつひとつの要素はカラであり、undefinedとなる
    • 要素を定義する場合、data2[0] = 10;とする必要がある
  • 一方、new Array("10")と文字列の場合、lengthが1である配列を定義
    • data[0]の値は文字列の10
  • 「new Array()の使いどころ」にてコンストラクターの使いどころについて後述

2.推奨されている配列の書き方

左記のGoogle JavaScript Style Guideには、推奨された書き方も紹介されています。

Instead, write
const a1 = [x1, x2, x3];
const a2 = [x1, x2];
const a3 = [x1];
const a4 = [];

先ほどと同様、出力結果も確認してみましょう。

let data1 = [10];

console.log("data1: " + data1);
console.log("data1[0]: " + data1[0]);
console.log("data1.length: " + data1.length);

console.log("#######################");

data1[0] = 1;

console.log("data1: " + data1);
console.log("data1[0]: " + data1[0]);
console.log("data1.length: " + data1.length);
data1: 10
data1[0]: 10
data1.length: 1
#######################
data1: 1
data1[0]: 1
data1.length: 1

ポイント

  • let data = [10]とは、lengthが1であり、data[0]の値は10ことを定義
    • data1[0]がundefinedではなく、格納した値が入っている

3.new Array()の使いどころ

new Array()の使いどころが気になったので、調べたところ、下記の記事が参考になったので、引用させていただきます。

一つずつ要素を入れて配列長をじわじわ伸ばしていくより、最初にまとめて確保しておいた方が軽いという理屈だ。
古いJavaScriptエンジンでは有効なテクニックだったのだが、エンジンの進歩により、逆転現象が起きている。V8で測定すると[]を使った方が2倍ほど速かった。

というわけで、 今の時代、タイプ数の多い new Array をあえて使う必要はない。

出所:new Array()と[]の違い

[]とnew Array()。
配列を扱いたい場合、[]でOKということですよね。

p.s.JavaScriptにおいてメモリが解放されるタイミングはいつなのか

メモリはいつ開放されるのか?という点も気になったので、こちらについても私の理解と参考になった箇所を書き留めておきます。

  • メモリが開放されるタイミングは分からない
  • ある領域にオブジェクトが管理されていてだれからも参照されなくなったら回収されると理解すればよさそう

JavaScript(ECMAScript)の仕様では、いつメモリが解放されるのかは定められていません。GC(ガベージコレクション)がいつ動作するのか、また、そのGCで回収されるのかはJavaScriptエンジンによって異なり、一概には言えません。
(中略)
ある領域にオブジェクトが管理されていてだれからも参照されなくなったら回収されるというのが肝心な点なのでそこだけぶれなければOKじゃないでしょうか。

出所:javascriptの変数のメモリへの割当について

んー。。

P.P.S. Twitterもやってるのでフォローしていただけると泣いて喜びます:)

@gkzvoice

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

GitHubでindex.jsだけのライブラリーを使った学習法

はじめに

ある程度プログラミングできるようになったけど、レベルを上げるためにどうやって勉強したらいいのかわからない人向けの勉強法です。

何か困った時、プログラマーならまずライブラリーを探すかと思います。

探したライブラリーのGitHubにコードを見に行った時に⭐️がゼロだったらスルーしてしまうかもしれないですが それは非常にもったいないです。

具体的にどういう風にもったいないか説明します。

ある日のこと

.svgなファイルをrequireしたらどうなるのかなって思ってreuqireしたらエラーになりました。

$ node
Welcome to Node.js v12.7.0.
Type ".help" for more information.
> var sprites = require('./src/assets/svg-sprites.svg')
Thrown:
/Users/yukihirop/JavaScriptProjects/sample/src/assets/svg-sprites.svg:1
<svg width="0" height="0" class="hidden" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
^

SyntaxError: Unexpected token <
>

なんか読み込めるようにするライブラリーないか探したところ、 inline-svg-registerというものを見つけたのですが、?がゼロでした。(今は一個あります。色々勉強になったので私がつけました。)

image.png

「星ないのかー...」って思ったのですが、使い方はシンプルなので使ってみました。

$ yarn -D add inline-svg-register
$ node
Welcome to Node.js v12.7.0.
Type ".help" for more information.
> var unhook = require("inline-svg-register")
undefined
> var sprites = require("./src/assets/svg-sprites.svg")
undefined
> sprites
'<svg width="0" height="0" class="hidden" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <defs> <!-- yukihirop -->

エラーも起きずに文字列で出力されるようになりました。

知識の獲得

すぐに仕組みが気になりました。 100行程度の index.js しかないしコード見てみるかと思って見たら、ラッキーな事に babel-register のコードを参考にして作ったとあったので読んで見ました。

そこで以下の知識を得ました。

  • nodeの requirerequire.extensions という関数を使って、拡張子で判定してコンパイルできる。
    • だが、require.extensions非推奨
    • 非推奨どうしたらいいのかstackoverflowを見たのですが、ピシャッとくる回答がない。

するとすぐに次のような興味が湧きました。

現在のbabel-register では当然非推奨の対応をしてあるだろう。もしかしたら、babel-register での非推奨対応のPRを見つけたら勉強になるかもしれないぞと思って、babel-registerのコードのコードを見に行きました。

?実際にbabel-registerのコードが差し替えられた時のPRはこちら

すると現在では、require.extensions が使われておらず、pirates というものが使われている事を知りました。

pirates って何だろうって思ってコードを見に行ったらこれまたラッキーな事に index.js しかなく100行程度だったので読んでみました。

そこで以下の知識を得ました。

  • nodeのビルトインModuleのModule._extensions をうまく使って拡張子で判定してコンパイルできるようにしている事を知りました。
    • ビルトインモジュールに機能拡張しているライブラリーを見たのは初めてでした。新鮮でした。

使い方が簡単だったので、すぐに inline-svg-register のコードに還元してやろうと思いました。

知識の還元

得た知識はすぐに使ったほうが定着がいいと思ったのですぐに知識の還元を行いました。

  • inline-svg-registerpirates を使うようにしてPRを送りました。
  • stackoverflowに回答を書きました。こちら

まとめ

svgファイルをrequireで読み込んで見たい」って思っただけで思わぬ収穫がありました。

知識獲得フェーズ

  • babel-register というコードが何をするのか想像ができた。
  • require.extensions という関数で拡張子毎にコンパイルを変えれるが非推奨である。
  • require.extensions の代わりに pirates というライブラリーが使われているようである。

知識還元フェーズ

  • 知見を得るきっかけになった inline-svg-registerPRを送った。
  • require.extensions の代わりに何を使えばいいか困っている人に回答を書いた。
  • この記事を書いた。

いつもこんなラッキーが続くとは思いませんが、たまには index.js だけのライブラリーを読んでみるもの面白いかなって思います。

以上です。

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

Youtubeの好きな動画の好きな部分だけをリスト再生するWebページの作り方

はじめまして。ひょんなことからVtuberにハマり、色々な物を書いている夕星(ゆうづつ)と申します。
この度、推しのプレイリストが欲しいなぁという欲望の結果、簡素ながらもそれなりに動く物が出来たので備忘録を兼ねて記します。

まず完成品がこちらになります。
加賀美ハヤト「ロックコンピ」プレイヤー|夕星の長い話置き場
(にじさんじ所属Vtuber「加賀美ハヤト」の配信から楽曲を抜粋したリストです)

概要

構成は静的HTML+ピュアJavascript(Youtube Iframe API使用)。
ネットに繋がってさえいればローカルでも動作しますので、自分用かWebサイトに載せて仲間内で楽しむ等の用途が想定されます。
なおAPIの利用規約はご自身で最新版をご確認ください。(基本は非営利利用限定の様子?)

動画の再生数は本家にカウントされ、広告も再生されます。
「切り抜き動画では本家に還元されない」「プレイリストだと動画丸ごとしか再生出来ない」「作業BGMとして良い場面だけリピート再生したい」という悩みやニーズに応えてます。
(あと上手くやるとスマホでバックグラウンド再生出来たりもします)

ソースコード

ほぼ最小構成が以下です(リストが必要無いなら更に削れます)。デザイン等はご自由に調整ください。
かなり雑でDOM構成もやっつけなので、実際に使用する場合はご利用中の環境に合わせて適宜編集推奨です。

少し長いので折り畳んでいます
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>ページタイトル</title>

    <style type="text/css">
      #playlist {
        background-color: black;
        color: #aaaaaa;
      }
      .playparts {
        padding: 10px;
        cursor: pointer;
      }
      .playparts:not(:last-child) {
        border-bottom: 1px #aaa solid;
      }
      .songtitle {
        color:white;
      }
    </style>
  </head>
  <body>
    <div id="player"></div>
    <div id="playlist"></div>

    <script>
      // 動画リスト
      var playList = [
        {videoId:'',startSeconds:0, endSeconds:0, title:'', artist:''},
      ];

      // Iframe APIの準備
      var tag = document.createElement('script');
      tag.src = "https://www.youtube.com/iframe_api";
      var firstScriptTag = document.getElementsByTagName('script')[0];
      firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

      // プレイヤーの設定
      var player;
      function onYouTubeIframeAPIReady() {
        player = new YT.Player('player', {
          height: '360',
          width: '640',
          events: {
            'onReady': onPlayerReady,
            'onStateChange': onPlayerStateChange
          }
        });
      }

      // プレイヤーの準備完了後の処理(最初の動画のセット)
      var isReady = false;
      var nowPlaying = 0;
      var isNextVideo = false;
      function onPlayerReady(event) {
        player.cueVideoById(playList[nowPlaying]);
        document.getElementById("song"+nowPlaying).style.color = 'red';
        isReady = true;
      }

      // プレイヤーの状態が変化した時の処理(次の動画へ進む)
      function onPlayerStateChange(event) {
        if (event.data == YT.PlayerState.ENDED && !isNextVideo) {
          document.getElementById("song"+nowPlaying).style.color = 'white';
          nowPlaying += 1;
          if (playList.length <= nowPlaying) nowPlaying = 0;
          isNextVideo = true;
          player.loadVideoById(playList[nowPlaying]);
          document.getElementById("song"+nowPlaying).style.color = 'red';
        } else if (event.data == YT.PlayerState.PLAYING && isNextVideo) {
          isNextVideo = false;
        }
      }

      // リストの表示
      function showList () {
        var parent = document.getElementById("playlist");
        for (let i = 0; i < playList.length; i++) {
          // 動画の時間計算
          var min = Math.floor((playList[i].endSeconds - playList[i].startSeconds) / 60);
          var sec = ( '00' + (playList[i].endSeconds - playList[i].startSeconds) % 60 ).slice( -2 );

          // 要素を記述
          var parts = document.createElement("div");
          parent.appendChild(parts);
          var html = '<p><span class="songtitle" id="song'+i+'">';
          html += playList[i].title;
          html += '</span><span class="time">('+min+':'+sec+')</span></p><p class="artist">';
          html += playList[i].artist;
          html += '</p>';
          parts.innerHTML += html;

          parts.classList.add("playparts")
          parts.addEventListener("click", () => { pushList(i+""); }, false);
        }
      }
      showList();

      // リストをクリックされた時の処理
      function pushList (id) {
        if (!isReady) return;
        document.getElementById("song"+nowPlaying).style.color = 'white';
        nowPlaying = Number(id);
        player.loadVideoById(playList[nowPlaying]);
        document.getElementById("song"+nowPlaying).style.color = 'red';
      }
    </script>
  </body>
</html>

動画リストの設定

playListの中にオブジェクトを突っ込みます。
videoId, startSeconds, endSeconds はAPIで必要な情報なのでプロパティ名変更不可。
それ以外はリストで表示する為の情報なのでお好きに変更可能です。

プロパティ名 内容
videoId 動画のID。動画のURLに含まれています。(もしURLが「https://youtu.be/fGvYwVW38mc」なら「fGvYwVW38mc」がIDです)
startSeconds 開始地点の秒数。
endSeconds 終了時点の秒数。動画まるごとの場合は「0」指定でも動きます。

以下、設定例です。

var playList = [
  {videoId:'fGvYwVW38mc',startSeconds:0, endSeconds:262, title:'WITHIN', artist:'加賀美ハヤト'},
  {videoId:'SSY5z5vUgHA',startSeconds:462, endSeconds:616, title:'どうにもとまらない', artist:'9mm Parabellum Bullet(山本リンダ)'},
  {videoId:'Wq8i7lJVYGw',startSeconds:1062,endSeconds:1330, title:'ソラニン', artist:'ASIAN KUNG-FU GENERATION'},
  {videoId:'MpBnxbMzaXA',startSeconds:0,endSeconds:260, title:'シュガーソングとビターステップ(with SMC組)', artist:'UNISON SQUARE GARDEN'}
];

その他技術的な話

基本的に全部公式リファレンスに書いてあります。というか半分ここのコピペです。
iframe 組み込みの YouTube Player API リファレンス | YouTube IFrame Player API

但し動画のデータがオブジェクト型でないとendSecondsが使えません。上記ページのコピペだと最初の動画をオブジェクト型で渡せませんでした。
その為にnew YT.Player時点では動画を指定せず、onPlayerReadycueVideoByIdを使っています。
今回は開始時にワンクリック挟みたかったのでわざとcueVideoByIdとしていますが、loadVideoByIdにすると勝手に再生が始まります。

最後に

分かってしまえばとても手軽に使えますので、自分の推しのも作りたい!と思った方はご参考にして頂けるととても嬉しいです。
Twitterで「使ったよ!」と一声頂けたら喜び勇んで見に行きます)

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

TypeScriptで学ぶデザインパターン〜Memento編〜

対象読者

  • デザインパターンを学習あるいは復習したい方
  • TypeScriptが既に読めるあるいは気合いで読める方
    • いずれかのオブジェクト指向言語を知っている方は気合いで読めると思います
  • UMLが既に読めるあるいは気合いで読める方

環境

  • OS: macOS Mojave
  • Node.js: v12.7.0
  • npm: 6.14.3
  • TypeScript: Version 3.8.3

本シリーズ記事一覧(随時更新)

Mementoパターンとは

オブジェクトのある時点における状態の保存や復元を可能にするためのパターンです。

DBのリストアやundoのようなイメージをしておくとわかりやすいと思います。

サンプルコード

Mementoパターンで作られたクラス群がどんなものになるのか確認していきましょう。

今回は、題材として"undoができるエディタ"を想定します。GitHubにも公開しています。

modules/Memento.ts

エディターの状態を表現するクラスです。

Memento.ts
export default class Memento {
  protected text: string;

  protected constructor(text: string) {
    this.text = text;
  }

  public getText(): string {
    return this.text;
  }
}

全てのクラスから状態を操作できないようにするためgetText以外はprotectedになっています。

modules/Editor.ts

エディターを表現するクラスです。Mementoを継承しています。

Editor.ts
import Memento from "./Memento";

export default class Editor extends Memento {
  constructor(text: string) {
    super(text);
  }

  setText(text: string): void {
    this.text = text;
  }

  createMemento(): Memento {
    const memento: Memento = new Memento(this.text);

    return memento;
  }

  restoreMemento(memento: Memento): void {
    this.text = memento.getText();
  }
}

setTextではエディターでいうところの文字を入力している状態をイメージしてください。
createMementoでは呼び出し時点におけるエディターの状態をMementoに保存しています。
restoreMementoではMementoが保持している状態で上書きしてundoを実現します。

Main.ts

どの時点のエディターの状態を保持するか等の指示をエディターに出す処理が書かれています。

Main.ts
import Editor from "./modules/Editor";
import Memento from "./modules/Memento";

const editor: Editor = new Editor('こんにちは');
const memento: Memento = editor.createMemento();
console.log(editor.getText());

editor.setText('こんにちは!!!');
console.log(editor.getText());

editor.restoreMemento(memento);
console.log(editor.getText());

まず、"こんにちは"とエディターに(擬似的に)入力します。次にこの状態でMementoを作成して保存します。
次に、"こんにちは!!!"とエディターに入力します。当然エディアーには"こんにちは!!!"と(擬似的に)表示されています。
最後に、restoreMementoを呼び出して"こんにちは"という状態に戻します。

クラス図

ここまでMementoパターンで作られたクラス群を1つずつ確認してきました。次にクラス図を示します。Mementoパターンの全体像を整理するのにお役立てください。

Memento.png

  • Memento: サンプルコードではMementoクラスが対応
  • Originator: サンプルコードではEditorクラスが対応
  • Caretaker: サンプルコードではMainが対応

LucidChartを使用して作成

2点補足をします。

1点目はMementoのgetProtectedInfogetPublicInfoについてです。MementoはCaretakerから一部メソッドを隠蔽しないといけません。たとえば、Mementoの状態を自由自在に変更できてしまうと困ります。以前の状態を保持しておきたいのにその以前の状態がどこからでも操作できてしまうと以前の状態とは言えない状態になってしまうからです。そういう意味で、Mementoに値を設定する処理(サンプルコードだとconstructor)はOriginatorからしか行えないようになっています。そういった処理はgetProtectedInfoとしてカプセル化します。一方で、Caretakerからアクセスされても問題ない処理(サンプルコードだとgetText)はpublicで実装しても問題ありません。そういった処理はgetPublicInfoとして定義します。

2点目はサンプルコードとクラス図の差異です。サンプルコードではMementoをEditorは継承しています。一方で、クラス図では継承という関係にはなっていません。これには言語の違いが背景にあります。たとえばJavaでは継承を使わずとも「あるパッケージ内からアクセス可能」というアクセス制御が可能です。こういったアクセス制御が可能であることが意味することは、getProtectedInfogetPublicInfoを継承を用いずとも分けることができるのです(クラス図通りになる)。1点目であげたアクセス制御は本デザインパターンにとって非常に重要なのでこれが実現できないと困ります。一方で、TypeScriptではJavaであげたように「あるクラスからしかアクセスさせない」といったアクセス制御ができません(※1)。そのため、継承を用いることによってアクセス制御を実現しているのです。

(※1)
十分調査したつもりではありますがもし私が誤解していることがあったら修正いたします。

解説

最後に、このデザインパターンの存在意義を考えます。

クラス図のところでほとんど解説してしまったのですが、カプセル化を実現しつつ保存や複製が可能になるというメリットがあります。ある状態を保存して復元しようとしたら対象クラスの様々なメソッドがpublicになってしまって、カプセル化の破壊を引き起こしてしまうことがあります。それを避けつつ復元ができるようになるのです。

補足

サンプルコードの実行方法はこちらと同様です。

参考

あとがたり

これ考えた人すごいな。

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

Vue.jsでアップロードされた画像の中心から正方形にくり抜いてexif情報処理してFirebaseStorageにアップする

自分用
詰め込みすぎだけど色々なアプリで使うため

<input type="file" accept="image/*" style="display:none;" @change="upload"/>
<canvas id="canvas" hidden></canvas>
    upload(ev) {
      const TRIM_SIZE = 512
      let blob = null
      const storageRef = firebase.storage().ref()
      if (!ev) return
      const file = ev.target.files[0]
      if (file.type !== 'image/jpeg' && file.type !== 'image/png') {
        return
      }
      const image = new Image()
      const reader = new FileReader()
      reader.onload = e => {
        image.onload = () => {
          let width, height, xOffset, yOffset
          if (image.width > image.height) {
            height = TRIM_SIZE
            width = image.width * (TRIM_SIZE / image.height)
            xOffset = -(width - TRIM_SIZE) / 2
            yOffset = 0
          } else {
            width = TRIM_SIZE
            height = image.height * (TRIM_SIZE / image.width)
            yOffset = -(height - TRIM_SIZE) / 2
            xOffset = 0
          }
          const canvas = $('#canvas')
            .attr('width', TRIM_SIZE)
            .attr('height', TRIM_SIZE)
          const ctx = canvas[0].getContext('2d')
          ctx.clearRect(0, 0, width, height)
          let orientation = ''
          EXIF.getData(file, () => {
            orientation = file.exifdata.Orientation
            switch (orientation) {
              case 2:
                ctx.transform(-1, 0, 0, 1, width, 0)
                break
              case 3:
                ctx.transform(-1, 0, 0, -1, width, height)
                break
              case 4:
                ctx.transform(1, 0, 0, -1, 0, height)
                break
              case 5:
                ctx.transform(0, 1, 1, 0, 0, 0)
                break
              case 6:
                ctx.transform(0, 1, -1, 0, height, 0)
                break
              case 7:
                ctx.transform(0, -1, -1, 0, height, width)
                break
              case 8:
                ctx.transform(0, -1, 1, 0, 0, width)
                break
              default:
                break
            }
            ctx.drawImage(image, xOffset, yOffset, width, height)
            const base64 = canvas.get(0).toDataURL('image/jpeg')
            const bin = atob(base64.split('base64,')[1])
            const len = bin.length
            const barr = new Uint8Array(len)
            let i = 0
            while (i < len) {
              barr[i] = bin.charCodeAt(i)
              i += 1
            }
            blob = new Blob([barr], { type: 'image/jpeg' })
            ctx.clearRect(0, 0, width, height)
            const uploadRef = storageRef.child(//パス
            )
            uploadRef.put(blob).then(() => {
              uploadRef.getDownloadURL().then(url => {
                //URLをどうにかする処理
              })
            })
          })
        }
        image.src = e.target.result
      }
      reader.readAsDataURL(file)
    },
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.js文法チートシート

Vue.jsとは

Vue.jsはUI構築のためのJavaScriptプログレッシブフレームワークのこと
Vue.js公式サイト

基本的な書き方

HTML
<div id="app">
  <!-- Vue.js適用範囲 -->
</div>
Vue
var vm = new Vue({
  el: '#app'
});

オプション

el

Vue.jsの機能を適用するDOMの指定


See the Pen
Vue.js_el
by engineerhikaru (@engineerhikaru)
on CodePen.


data

データを保持する(データの変数化)。{{ 変数名 }}で出力可能


See the Pen
Vue.js_data
by engineerhikaru (@engineerhikaru)
on CodePen.


methods

v-onのイベントハンドラとして使用


See the Pen
Vue.js_ methods
by engineerhikaru (@engineerhikaru)
on CodePen.


computed

算出プロパティ。データの自動更新をし、計算結果はキャッシュに保存。getでは必ず戻り値が必要


See the Pen
Vue.js_ computed
by engineerhikaru (@engineerhikaru)
on CodePen.


watch

監視プロパティ。特定のプロパティの値の変更を監視し、変更時に設定した関数を実行


See the Pen
Vue.js_watch
by engineerhikaru (@engineerhikaru)
on CodePen.


template

文字列,HTMLコードをテンプレート化する。コンポーネント機能と共に使用


See the Pen
Vue.js_template
by engineerhikaru (@engineerhikaru)
on CodePen.


props

コンポーネント機能でデータを参照する


See the Pen
Vue.js_props
by engineerhikaru (@engineerhikaru)
on CodePen.


created

インスタンスが作成された後(DOMは生成されていない)に実行するオプション


See the Pen
Vue.js_created
by engineerhikaru (@engineerhikaru)
on CodePen.


mounted

DOMが生成された後に実行するオプション


See the Pen
Vue.js_mounted
by engineerhikaru (@engineerhikaru)
on CodePen.


ディレクティブ

v-text

テキストデータを出力


See the Pen
Vue.js_v-text
by engineerhikaru (@engineerhikaru)
on CodePen.


v-html

HTMLデータを出力


See the Pen
Vue.js_v-html
by engineerhikaru (@engineerhikaru)
on CodePen.


v-show

条件分岐(createdフックは最初のみ実行)


See the Pen
Vue.js_v-show
by engineerhikaru (@engineerhikaru)
on CodePen.


v-if, v-if-else, v-else

条件分岐


See the Pen
Vue.js_v-if
by engineerhikaru (@engineerhikaru)
on CodePen.


v-for

ループする


See the Pen
Vue.js_v-for
by engineerhikaru (@engineerhikaru)
on CodePen.


v-on, @

イベントを処理する


See the Pen
Vue.js_v-on
by engineerhikaru (@engineerhikaru)
on CodePen.


v-bind, :

HTMLの属性を指定する


See the Pen
Vue.js_v-bind
by engineerhikaru (@engineerhikaru)
on CodePen.


v-model

入力データと指定したデータをリンクする


See the Pen
Vue.js_v-model
by engineerhikaru (@engineerhikaru)
on CodePen.


v-pre

{{}}をそのまま表示する


See the Pen
Vue.js_v-pre
by engineerhikaru (@engineerhikaru)
on CodePen.


v-cloak

{{}}が表示されるのを防ぐ(最初に表示される事がある)


See the Pen
Vue.js_v-cloak
by engineerhikaru (@engineerhikaru)
on CodePen.


v-once

レンダリングを一度だけ実行(データの変更を適用しない)


See the Pen
Vue.js_v-once
by engineerhikaru (@engineerhikaru)
on CodePen.


グローバル設定

silent

警告,エラーメッセージの表示,非表示を指定する

Vue
Vue.config.silent = true;

この内容について

この内容は、私が運営しているサイトに、より見やすく掲載しているので、よければそちらもご活用ください。
Vue.jsチートシート | コレワカ

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

【超基礎】PythonとJavaとJavaScriptを比較します(変数, if文, while文, for文)

目的

  • 今週から本配属だった
  • 結局Javaを勉強することになった
  • 記憶が新しいうちにJavaとPythonでちがったことまとめておこう
  • ついでに少し勉強したことがあるJavaScriptも引っ張り出して比較しよう

この記事で比較する内容

  • 変数
  • if文
  • while文
  • for文

変数

Python

  • データ型を宣言する必要はありません
    • 変数の中身によって勝手に型を指定してくれます
  • セミコロンもいりません
asobi.py
py_str = "Python"
py_int = 23
py_float = 23.5

# データ型確認
print(type(py_str))
print(type(py_int))
print(type(py_float))
コンソール
<class 'str'>
<class 'int'>
<class 'float'>
  • 変数のデータ型を書きかえることもできます
asobi.py
py_str = "Python"
py_int = 23

print(type(py_str))

py_str = 10
print(type(py_str))

py_cal = py_str * py_int
print(py_cal)
コンソール
<class 'str'>
<class 'int'>
230

Java

  • データ型を宣言しなければなりません
    • float型もありますが、主にdouble型を使うようです
    • 直接的にデータ型を確認する方法は見つかりませんでした(instanceofというメソッドがあるようですが、intやdoubleには使えません)
    • Javaのデータ型についてはこちらのサイトがわかりやすいです
  • セミコロンを忘れずに
asobi.java
public class asobi {
    public static void main(String[] args) {
        String javaStr = "Java";
        int javaInt = 23;
        double javaFloat = 23.5;
    }
}

JavaScript

  • データ型の宣言は必要ありませんが、変数名の前にvarをつける必要があります
    • int型やfloat型ではなくどちらもnumber型になるんですね…
  • セミコロンもいります
asobi.html
<html>
<script>
var jsStr = "JavaScript";
var jsInt = 23;
var jsFloat = 23.5;

// データ型確認
console.log(typeof(jsStr));
console.log(typeof(jsInt));
console.log(typeof(jsFloat));
</script>
</html>
コンソール
string
number
number

if文

Python

  • if, elif, elseが使われます
  • 条件の末尾に:を付けます
  • 実行する文のインデントは必須です
asobi.py
age = 23

if age >= 65:
    print("定年です")
elif age >= 20:
    print("成人です")
else:
    print("未成年です")

Java

  • if, else if, elseが使われます
  • それぞれの条件を(), 実行する文を{}で囲います
asobi.java
public class asobi {
    public static void main(String[] args) {
        int age = 23;

        if (age >= 65) {
            System.out.println("定年です");
        }else if (age >= 20) {
            System.out.println("成人です");
        }else{
            System.out.println("未成年です");
        }
    }
}

JavaScript

  • if, else if, elseが使われます
  • それぞれの条件を(), 実行する文を{}で囲います
  • if文の形自体はJavaと同じですね
asobi.html
<html>
<script>
var age = 23;

if (age >= 65) {
    console.log("定年です");
}else if (age >= 20){
    console.log("成人です");
}else{
    console.log("未成年です");
}
</script>
</html>

while文

  • それぞれの言語で以下のようにコンソール表示させてみましょう
コンソール
0です
1です
2です
3です
4です

Python

  • int型であるiと文字列をそのままつなげることができないので、iをstr型にします
asobi.py
i = 0

while i < 5:
    print(str(i) + "です")
    i += 1

Java

  • 例によって条件を()、実行文を{}で囲います
  • インクリメントでi++という形を使うことができます
asobi.java
public class asobi {
    public static void main(String[] args) {
        int i = 0;
        while (i < 5){
            System.out.println(i + "です");
            i++;
        }
    }
}

JavaScript

  • 例によってwhile文の形自体はJavaと同じです
asobi.html
<html>
<script>
var i = 0;

while (i < 5){
    console.log(i + "です");
    i++;
}
</script>
</html>

for文

  • それぞれの言語で以下のようにコンソール表示させてみましょう
コンソール
0です
1です
2です
3です
4です

Python

  • iが0から4までの5回の範囲で繰り返されます
asobi.py
for i in range(5):
    print(str(i) + "です")

Java

  • for文の繰り返し条件が(初期値; 範囲; 増減)で表されます
    • 初期値を格納する変数はデータ型を宣言する必要があります
asobi.java
public class asobi {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++){
            System.out.println(i + "です");
        }
    }
}

JavaScript

  • 例によってfor文の形自体はJavaと同じです
    • 初期値を格納する変数名の前にはvarを付ける必要があります
asobi.html
<html>
<script>
for (var i = 0; i < 5; i++){
    console.log(i + "です");
}
</script>
</html>

感想

  • 複数言語を勉強するとき、比較しながら勉強すると覚えやすい
  • JavaとJavaScript、インドとインドネシア並にちがうといいつつ、書き方結構似てるんだな
  • やっぱりPythonがいちばん書きやすい!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsで東京都の今日の天気を表示するWebサイトを作るコピペサンプル

簡単なサンプルです。

Vue.jsとaxiosで簡単なデータ連携をします。

作るもの

こんな感じでボタンを押すと今日の天気を表示するWebサイトを作ります。

使うAPI

OpenWhethermapのAPIを作います。無料である程度(雑)使えます。

https://openweathermap.org/api

APIキーを取得しましょう。

ログイン後ここにアクセスするとキーが表示されます。

コピペ用コード

一箇所だけ、変更が必要です。
appid=ここにOpenWeathermapのAPIキーを指定と書いているところに自身で取得したAPIキーを指定しましょう。

index.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>
</head>
<body>
    <div id="app">
        <p>今日の天気は{{ weather }}で、気温は{{ temp }}度です。</p>
        <button v-on:click="getData()">今日の東京の天気をAPIで取得!</button>

        <p>
            <a href="https://openweathermap.org/api">openweathermapから取得しています。</a>
        </p>
    </div>

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script>
        var app = new Vue({
            el: '#app',
            data: {
                weather: 'xxxx',
                temp: 'yyyy'
            },

            methods: {
                getData: async function(){
                    const URL = `https://api.openweathermap.org/data/2.5/weather?id=1850147&units=metric&appid=ここにOpenWeathermapのAPIキーを指定`;
                    const response = await axios.get(URL);
                    this.weather = response.data.weather[0].main;
                    this.temp = response.data.main.temp;
                    // console.log(response.data);
                },
            },

            // mounted: function(){

            // }
        })
    </script>
</body>
</html>

試す。

ローカルのindex.htmlをブラウザで開いたり、live severやどこかにデプロイしたりして挙動確認をしましょう。

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

javascriptメモ

概要

javascript初めてなので、公式?を見ながら基本文法の確認した。

保存

//モジュール化されたJavaScriptファイル
const { NearestScanner } = require('@toio/scanner')

// 制御フロー
// https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Loops_and_iteration
function test_1a(){
let x = 0;
console.log(ループ開始前のxの値: ${x});
while (x < 10) {
console.log(x);
x += 1;
}
console.log(ループ終了後のxの値: ${x});

for (let step = 0; step < 5; step++) {
// 値が 0 から 4 まで計 5 回実行される
console.log('一歩西に歩く');
}
}

function test_1b(){

fruittype ='Apples'
switch (fruittype) {
case 'Oranges':
console.log('Oranges are $0.59 a pound.');
break;
case 'Apples':
console.log('Apples are $0.32 a pound.');
break;
default:
console.log(Sorry, we are out of ${fruittype}.);
}
console.log("Is there anything else you'd like?");

}

function test_1c()
{
let condition = true
let t_val = 10
if (condition) {
t_val = 1
}else if (condition == false) {
t_val = 2
}else {
t_val = 3
}
}

function test_1d()
{
let x = 0;
let z = 0;

labelCancelLoops:

while (true) {
console.log('外側のループ: ' + x);
x += 1;
z = 1;
while (true) {
console.log('内側のループ: ' + z);
z += 1;
if (z === 3 && x === 3) {
break labelCancelLoops;
} else if (z === 3) {
break;
}
}
}

}

function test_1(){
test_1a()
console.log('------------------------------');
test_1b()
console.log('------------------------------');
test_1c()
console.log('------------------------------');
test_1d()
console.log('------------------------------');

}

//https://www.sejuku.net/blog/61660
//【JavaScript入門】exitの代わりにtry catchでプログラムを終了する
function test2(){

const flag = true;
try {
if (flag) {
throw new Error('終了します');
}
console.log('実行されないコード');
} catch (e) {
console.log(e.message);
}
}

//--------------------------------------------
//変数

//構造体の代わり
function Position(x, y) {
this.x = x;
this.y = y;
}

/*
複数行のコメントアウト:入れ子のコメントは できない。

JavaScript は大文字と小文字を区別し、また Unicode 文字セットを使用しています。

文が単独の行で書かれている場合、文の後にセミコロンは必要ではありません。
*/
function test_3a(){
// strict モードも存在する
var val1 = 1
const flag = true
const val100 = 100

let local_val = 100; //ブロックスコープのローカル変数を宣言し

let szName ="APPLE"
let szNameKANJI ="漢字UTFらしい"
console.log(szNameKANJI);

//配列 
const arr = [3, 5, 7];
// inとofの違い
for (let i in arr) {
console.log(i); // "0", "1", "2", "foo" が出力される
}
for (let i of arr) {
console.log(i); // 3, 5, 7 が出力される
}
//配列
var array = [];
array.push('hoge')
array.push('fuga')

//DICT,連想配列
var mycar = {make: "Honda", model: "Accord", year: 1998};
console.log(mycar);
console.log(mycar['make'] +" "+ mycar['model'] +" "+ mycar['year'] );

var obj = {};
obj['hoge'] = 'moge';
console.log(obj['hoge']);

// //構造体
console.log("構造体") // Position {x: 0, y: 1}

//JavaScriptには俗に「構造体」と呼ばれるものはありません。(ないよね?)
console.log(new Position(0, 1)); // Position {x: 0, y: 1}
let t_pos = new Position(9, 99)
console.log(t_pos.x)

//console.log(Position(0, 1)); // undefined

}

function test_3(){
test_3a()
}

//--------------------------------------------

// 他の関数は非 Strict Mode になる。
// 'use strict';
//
/* Strict Mode 関数 */
function strict() {
// 関数レベル strict mode 文法
'use strict';
function nested() { return "ここでも Strict Mode."; }
return "Strict Mode 関数 " + nested();
}

function test_4(){
let result = strict()
console.log(result)
//notStrict()
}

//--------------------------------------------
function square(number) {
return number * number;
}

function test_5a(val) {
val = val + 1
}
function test_5b(arr) {
arr[0] = 99
}

function test_5c(theObject) {
theObject.make = "Toyota";
}

//関数呼び出し

function test_5b(){
var myFunc;
//local関数定義
myFunc = function(theObject) {
theObject = "Toyota"
return theObject
}

console.log(myFunc());

}

function loop(x) {
if (x >= 10) // "x >= 10" が終了条件 ("!(x < 10)" と同等)
return x;
// 何らかの処理を行う
return loop(x + 1); // 再帰呼出し
}

function multiply(a, b = 1) {
return a*b;
}

// https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Functions
function Person() {
var self = this; // self の代わりに that を選ぶ人もいます。
// どちらか一方を選び、そちらだけを使うようにしましょう。
self.age = 0;

// タイマー処理みたい
setInterval(function growUp() {
// このコールバックは、その値が期待通りのオブジェクトを指す
// 変数 self を参照している。
self.age++;
console.log(self.age )

}, 1000);
}

function test_5a(){
console.log("square " + square(2))

// プリミティブなパラメータ(数値など)は値渡しで関数に渡されます。
let t_val = 2
test_5a(t_val)
console.log("引数の参照、値渡しの確認: 2を関数内で+1してreturn -> " + t_val)

const arr = [3, 5, 7];
console.log("関数呼び出し前" + arr)
test_5b(arr)
console.log("関数で配列の値を変更" + arr)

// デフォルト引数
multiply(5); // 5

}

function test_5c(){

var p = new Person();
//p.setInterval()
console.log("new Person " + p.age )

}

// 関数呼び出し
function test_5(){
test_5b()

t_x = loop(0) // 再帰
console.log("再帰の結果 " + t_x)

test_5c()

}

//--------------------------------------------
// 数の表現、NULL
function JSClock() {
var time = new Date();
var hour = time.getHours();
var minute = time.getMinutes();
var second = time.getSeconds();
var temp = '' + ((hour > 12) ? hour - 12 : hour);
if (hour == 0)
temp = '12';
temp += ((minute < 10) ? ':0' : ':') + minute;
temp += ((second < 10) ? ':0' : ':') + second;
temp += (hour >= 12) ? ' P.M.' : ' A.M.';
return temp;
}

function test_6a()
{
//1E3 // 1000
//2e6 // 2000000
//0.1e2 // 10

let val = 0XA
console.log( val )
val = 1E3
console.log( val )
val = 0.1e2 //
console.log( val )
val = 2e3 // 2x 10^3
console.log( val )

// MAX_MIN
var biggestNum = Number.MAX_VALUE;
var smallestNum = Number.MIN_VALUE;
var infiniteNum = Number.POSITIVE_INFINITY;
var negInfiniteNum = Number.NEGATIVE_INFINITY;
var notANum = Number.NaN;
console.log( biggestNum )

// 文字列から数字
val = Number.parseInt("5155")
console.log( val )

//deg=rad∗(180/π)
//rad = deg * PI / 180

console.log( Math.PI )

let rad_val = 0
rad_val = 90 * Math.PI / 180
console.log( Math.sin(rad_val) )

//日付

var Xmas95 = new Date("December 25, 1995");
var today = new Date();
console.log( today )

}

function test_6()
{
test_6a()
console.log( JSClock() )
}

function test_7a()
{
var arr = [42]; // 42という数の要素を
// 1個だけ持つ配列が作られる。

var arr = Array(42); // 要素がなく、arr.length が
// 42に設定された配列が作られる。
// 以下のコードと同様。
var arr = [];
arr.length = 42;

let myVar = "moge"
var myArray = new Array('Hello', myVar, 3.14159);
console.log( myArray )

var cats = [];
cats[30] = ['Dusty'];
console.log("length")
console.log(cats.length); // 31

}

// 多次元配列
function test_7b()
{
var a = new Array(4);
for (i = 0; i < 4; i++) {
a[i] = new Array(4);
for (j = 0; j < 4; j++) {
//a[i][j] = '[' + i + ', ' + j + ']';
a[i][j] = i*4+j
}
}
console.log(a); // 31

}

//https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Indexed_collections
//型付き配列
function test_7c()
{

}

function test_7()
{

test_7a()
test_7b()
test_7c()
}

// map
function test_8a()
{
var sayings = new Map();
sayings.set('dog', 'woof');
sayings.set('cat', 'meow');
sayings.set('elephant', 'toot');
sayings.size; // 3
sayings.get('fox'); // undefined
sayings.has('bird'); // false
sayings.delete('dog');
sayings.has('dog'); // false

for (var [key, value] of sayings) {
console.log(key + ' goes ' + value);
}
// "cat goes meow"
// "elephant goes toot"

sayings.clear();
sayings.size; // 0

}
function test_8()
{

test_8a()
}

//プロパティの列挙
function showProps(obj, objName) {
var result = '';
for (var i in obj) {
// obj.hasOwnProperty() はオブジェクトのプロトタイプチェーンからプロパティを絞り込むために使用しています
if (obj.hasOwnProperty(i)) {
//result += objName + '.' + i + ' = ' + obj[i] + '\n';
result += i + ",";
}
}
return result;
}
// Object型
function test_9a()
{
var myCar = new Object();
myCar.make = 'Ford';
myCar.model = 'Mustang';
myCar.year = 1969;
//console.log(myCar)

let result = showProps(myCar, "Mustang");
console.log("--")
console.log(result)

}
//コンストラクタ関数
function Car(make, model, year) {
this.make = make;
this.model = model;
this.year = year;
}

function test_9b()
{
var mycar = new Car('Eagle', 'Talon TSi', 1993);
console.log(mycar)
}

function test_9c()
{
console.log("------------- test_9c -------------")

function Employee() {
this.name = '';
this.dept = 'general';
}

function Manager() {
Employee.call(this);
this.reports = [];
}
Manager.prototype = Object.create(Employee.prototype);
Manager.prototype.constructor = Manager;

function WorkerBee() {
Employee.call(this);
this.projects = ["A", "B", "C"];
}
WorkerBee.prototype = Object.create(Employee.prototype);
WorkerBee.prototype.constructor = WorkerBee;

var jim = new Employee;
var bee = new WorkerBee;
var boss = new Manager;

console.log(jim)
console.log(bee)
console.log(boss)

}

//クラスとはちがうのか -> 「JavaScript は、クラスではなく、プロトタイプに基づいたオブジェクトベースの言語です」
function test_9()
{
try {
test_9a()
test_9b()
test_9c()

console.log("------------- end of test9 -------------")

let val = 1
val = val / 0  //例外発生しないみたい。
throw new Error('終了します');

} catch (e) {
console.log("in catch");
console.log(e.message);
}
}
async function main() {
console.log(in main -----------------)
test_9()
console.log(out main ---------------------)
}

main()

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

GitHubスター数ランキング上位プロジェクトの一口解説 2020年5月

GitHubRanking

ふと思い立って、2020年5月16日時点のGitHubのスター数上位レポジトリを出したところ、結構知らないレポジトリが多かったので軽く調べてみました。

何か気づいたことがあれば修正依頼していただけると幸いです。


1位 freeCodeCamp/freeCodeCamp JavaScript star 310,728

無料のコードキャンプ
フルスタックWeb開発者になるためのコースが揃っている。

Learn to code at home | freeCodeCamp.org

2位 996icu/996.ICU Rust star 249,552

中国の労働問題を訴えるOSS活動

最終的に集中治療室(ICU)送りになるリスクを伴いながら午前9時から午後9時まで、週6日働く

中国の長時間労働にスタートアップが反撃 | TechCrunch Japan

3位 vuejs/vue JavaScript star 164,049

SPAを作るためのJavaScriptライブラリ/フレームワーク

TypeScriptとの相性の悪さが指摘されがち。

アリババが支援しているので中国での人気が高い。
中国でも求人数は React > Vue

4位 facebook/react JavaScript star 148,705

Hooksという機能が2019年に追加された。SPAのデファクトスタンダード

NPMでのダウンロード数ではSPA御三家で圧倒的一位

image.png

ReactはウェブやHTMLとは特に関係のないライブラリです - Qiita

Reactはウェブ向けライブラリとして登場した
なんかReact Nativeとかいうのも出てきた
React NativeとうまくやるためにReact自体は2015年にウェブから完全に独立した
ウェブ向け機能はreact-domってライブラリがやるようになった
Reactに残った機能はコンポーネントのツリー管理とか差分検出システムぐらい
ウェブとかHTMLに依存してないからなんでも作れるようになった

React使っている人の方が強そう(小並感)

ReactとVueのどちらを選ぶか - Qiita

5位 EbookFoundation/free-programming-books - star 147,578

無料で利用できる学習リソースまとめ。GitHub Pagesで公開されている。

日本語リソースはこれ:Index | free-programming-books-ja

6位 tensorflow/tensorflow C++ star 144,362

Google開発の機械学習・ディープラーニング用ライブラリ

ブラウザで動くTensorFlow.jsもある

7位 twbs/bootstrap JavaScript star 140,787

世界で最も人気のあるCSSフレームワーク

Bootstrap 5のリリースはもうすぐみたい!注目の新機能、jQueryは削除、IE10のサポートは終了へ | コリス

8位 sindresorhus/awesome - star 133,119

役に立つ情報や素晴らしいライブラリのリスト。
フレームワークやライブラリの候補を洗い出す作業を短縮できる。
フォーマットが決まっているので二次利用しやすい。

9位 getify/You-Dont-Know-JS - star 121,215

JavaScriptのテキスト
JavaScriptのレベル別書籍のまとめ

10位 jwasham/coding-interview-university - star 115,174

コーディング面接に必要な知識と学習リソースへのリンクのリスト

完全習得すればAmazonで働けるかも -> I've Been Acquired by Amazon

10位以下で気になったもの

19位 d3/d3 JavaScript star 91,500

データドリブンでインタラクティブなSVGグラフを描画するJavaScriptライブラリ
Pythonの可視化ツールにも使われていることが多い(Plotly、Bokehとか)

12位 kamranahmedse/developer-roadmap - star 108,176

毎年1月ごろに更新される、Web開発者向けロードマップ
Web技術・ツールを俯瞰できるのでサラッと読むと面白い

バックエンドエンジニアのロードマップ in2020 - Qiita
私からあなたへ 一人前のJavaエンジニアになるためのロードマップを送ろう - Qiita

まとめ系

中国すごい

  • 14位 CyC2018/CS-Notes Java star 101,029
    • 技術面接対策用のテキストサイト(中国語)
  • 22位 jackfrued/Python-100-Days Jupyter
    • Pythonを習得するための100日分の教材とソースコード(中国語)
  • 26位 Snailclimb/JavaGuide Java star 78,774
    • Javaの学習教材(中国語)

中国語しか対応してなくてもこのスター数。総人口10億人越えのパワーをみた。

多言語対応教材(日本語もあり)

  • 17位 donnemartin/system-design-primer Python star 95,326
    • システム設計の学習資料。圧倒的な量。親切にも暗記カード付き。
  • 28位 jlevy/the-art-of-command-line - star 75,368
    • コマンドラインを使いこなすためのさまざまな情報やTipsをまとめたGitHub上のドキュメント

クロスプラットフォーム

いつの間にかFlutterがRNを抜いていたんですね。

ReactはウェブやHTMLとは特に関係のないライブラリです - Qiita


全体ランキング

Rank URL Lang Star
1 freeCodeCamp/freeCodeCamp JavaScript 310,728
2 996icu/996.ICU Rust 249,552
3 vuejs/vue JavaScript 164,048
4 facebook/react JavaScript 148,705
5 EbookFoundation/free-programming-books - 147,575
6 tensorflow/tensorflow C++ 144,361
7 twbs/bootstrap JavaScript 140,787
8 sindresorhus/awesome - 133,119
9 getify/You-Dont-Know-JS - 121,214
10 jwasham/coding-interview-university - 115,163
11 ohmyzsh/ohmyzsh Shell 109,777
12 kamranahmedse/developer-roadmap - 108,173
13 github/gitignore - 101,595
14 CyC2018/CS-Notes Java 101,026
15 microsoft/vscode TypeScript 96,371
16 airbnb/javascript JavaScript 95,412
17 donnemartin/system-design-primer Python 95,325
18 flutter/flutter Dart 92,483
19 d3/d3 JavaScript 91,499
20 torvalds/linux C 90,998
21 facebook/react-native JavaScript 87,215
22 jackfrued/Python-100-Days Jupyter 85,682
23 electron/electron C++ 82,688
24 vinta/awesome-python Python 82,453
25 public-apis/public-apis Python 82,041
26 Snailclimb/JavaGuide Java 78,772
27 facebook/create-react-app JavaScript 78,697
28 jlevy/the-art-of-command-line - 75,368
29 axios/axios JavaScript 73,248
30 golang/go Go 72,543
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

非同期通信の初回挙動不良の対処方法

なんで動かないの!?

アプリ作成をしていてjavaScriptで非同期やインクリメンタルサーチをしたけど、なぜか初回の挙動だけがうまくいってくれない、、!なんてなことないですか??

今回は同じ事象で困っている人のために解消方法を紹介します。

turbolinksを停止しよう

turbolinksとはgemとしてRailsアプリケーションに導入されている機能です。
今回の挙動の動作不良は手作業で作成したAjaxとturbolinksが競合してしまい、うまく作動しない可能性が考えられます。

1.Gemfileからturbolinksの部分をコメントアウトする

gem 'turbolinks', '~> 5'
< --- コメントアウトしましょう --- >
# gem 'turbolinks', '~> 5'

bundleinstallも忘れず実行する

$ bundle install

2.application.html.hamlからturbolinksの関連部分を削除する

application.html.haml
!!!
%html
  %head
    %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/
    %title PracticeApp
    = csrf_meta_tags

    / 修正前 
    = stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload'  このオプションを消す
    = javascript_include_tag 'application', 'data-turbolinks-track': 'reload'  このオプションを消す

    / 修正後
    = stylesheet_link_tag    'application', media: 'all'
    = javascript_include_tag 'application'
  %body
    = render "layouts/notifications"
    = yield

3.application.jsからturbolinksの関連部分を削除する

//= require jquery
//= require jquery_ujs
//= require turbolinks  <--- こいつを消してあげる
//= require_tree .

以上

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

テンプレートリテラル記法を用いるとデプロイ時にエラーが出る

背景

個人アプリをデプロイしようとしたが、エラーが出てうまくいかない。。。
javaScriptでテンプレートリテラル記法を用いた記述をするとどうやらエラーが出るみたい。

解決策

修正前
config/environments/production.rb
config.assets.js_compressor = :uglifier
修正後
config/environments/production.rb
# config.assets.js_compressor = :uglifier

Uglifierというgemがあり、これはJavaScriptを軽量化するためのものです。
今回のアプリ中では、JavaScriptで使用しているテンプレートリテラル記法(`)に対応していません。
そのため、デプロイ時にエラーの原因となります。
上記部分をコメントアウトすることで解決します。

以上

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

javascriptでradioボタンにハマった話

結論

radioボタンのプロパティは最初にtypeを設定しよう。

経緯

javascriptではcreateElementを使ってコンテンツを作る事はよくあると思う。
例えばこんなの

radio.js
    //フォームを生成
    var eForm1 = document.createElement('form');

    //ラジオボタンを生成
    var eRadio1 = document.createElement('input');
    eRadio1.value = '';
    eRadio1.type = 'radio';
    eRadio1.name = 'gender';
    eRadio1.checked = true;

    var eRadio2 = document.createElement('input');
    eRadio2.value = '';
    eRadio2.type = 'radio';
    eRadio2.name = 'gender';

    var eRadio3 = document.createElement('input');
    eRadio3.value = 'その他';
    eRadio3.type = 'radio';
    eRadio3.name = 'gender';

    eForm1.appendChild(eRadio1);
    eForm1.appendChild(eRadio2);
    eForm1.appendChild(eRadio3);
    document.body.appendChild(eForm1);

次のようにするとチェックを入れた部分の内容(value)を取得できる。

    //elementsを取得
    var elements = document.getElementsByName( "gender" ) ;

    //valueを取得
    for ( var output = "", i = elements.length; i--; ) {
        if ( elements[i].checked ) {
            var output = elements[i].value ;
            break ;
        }
    }
    alert(output);

firefoxやchromeでは以下のように表示される。
chrome.png

しかしIE11では上手く動かない。
ie.png

性別がonでは困る。

ナゼだろう?

しばらく試行錯誤してtypeの位置が悪い事が分かった。
検証コードを書いて詳しく調べるとtypeを設定する前にvalueを設定するとvalueが無視される事が分かった。

radio1.js
function init(){
    //フォームを作る
    var eForm = document.createElement('form');

    //vtnc
    var eRadio = document.createElement('input');
    eRadio.value = 'vtnc';
    eRadio.type = 'radio';
    eRadio.name = 'vtnc';
    eRadio.checked = true;
    eForm.appendChild(eRadio);

    //vtcn
    var eRadio = document.createElement('input');
    eRadio.value = 'vtcn';
    eRadio.type = 'radio';
    eRadio.checked = true;
    eRadio.name = 'vtcn';
    eForm.appendChild(eRadio);

    //vntc
    var eRadio = document.createElement('input');
    eRadio.value = 'vntc';
    eRadio.name = 'vntc';
    eRadio.type = 'radio';
    eRadio.checked = true;
    eForm.appendChild(eRadio);

    //vnct
    var eRadio = document.createElement('input');
    eRadio.value = 'vnct';
    eRadio.name = 'vnct';
    eRadio.checked = true;
    eRadio.type = 'radio';
    eForm.appendChild(eRadio);

    //vctn
    var eRadio = document.createElement('input');
    eRadio.value = 'vctn';
    eRadio.checked = true;
    eRadio.type = 'radio';
    eRadio.name = 'vctn';
    eForm.appendChild(eRadio);

    //vcnt
    var eRadio = document.createElement('input');
    eRadio.value = 'vcnt';
    eRadio.checked = true;
    eRadio.name = 'vcnt';
    eRadio.type = 'radio';
    eForm.appendChild(eRadio);

    //tvnc
    var eRadio = document.createElement('input');
    eRadio.type = 'radio';
    eRadio.value = 'tvnc';
    eRadio.name = 'tvnc';
    eRadio.checked = true;
    eForm.appendChild(eRadio);

    //tvcn
    var eRadio = document.createElement('input');
    eRadio.type = 'radio';
    eRadio.value = 'tvcn';
    eRadio.checked = true;
    eRadio.name = 'tvcn';
    eForm.appendChild(eRadio);

    //tncv
    var eRadio = document.createElement('input');
    eRadio.type = 'radio';
    eRadio.name = 'tncv';
    eRadio.checked = true;
    eRadio.value = 'tncv';
    eForm.appendChild(eRadio);

    //tnvc
    var eRadio = document.createElement('input');
    eRadio.type = 'radio';
    eRadio.name = 'tnvc';
    eRadio.value = 'tnvc';
    eRadio.checked = true;
    eForm.appendChild(eRadio);

    //tcvn
    var eRadio = document.createElement('input');
    eRadio.type = 'radio';
    eRadio.checked = true;
    eRadio.value = 'tcvn';
    eRadio.name = 'tcvn';
    eForm.appendChild(eRadio);

    //tcnv
    var eRadio = document.createElement('input');
    eRadio.type = 'radio';
    eRadio.checked = true;
    eRadio.name = 'tcnv';
    eRadio.value = 'tcnv';
    eForm.appendChild(eRadio);

    //nvtc
    var eRadio = document.createElement('input');
    eRadio.name = 'nvtc';
    eRadio.value = 'nvtc';
    eRadio.type = 'radio';
    eRadio.checked = true;
    eForm.appendChild(eRadio);

    //nvct
    var eRadio = document.createElement('input');
    eRadio.name = 'nvct';
    eRadio.value = 'nvct';
    eRadio.checked = true;
    eRadio.type = 'radio';
    eForm.appendChild(eRadio);

    //ntcv
    var eRadio = document.createElement('input');
    eRadio.name = 'ntcv';
    eRadio.type = 'radio';
    eRadio.checked = true;
    eRadio.value = 'ntcv';
    eForm.appendChild(eRadio);

    //ntvc
    var eRadio = document.createElement('input');
    eRadio.name = 'ntvc';
    eRadio.type = 'radio';
    eRadio.value = 'ntvc';
    eRadio.checked = true;
    eForm.appendChild(eRadio);

    //ncvt
    var eRadio = document.createElement('input');
    eRadio.name = 'ncvt';
    eRadio.checked = true;
    eRadio.value = 'ncvt';
    eRadio.type = 'radio';
    eForm.appendChild(eRadio);

    //nctv
    var eRadio = document.createElement('input');
    eRadio.name = 'nctv';
    eRadio.checked = true;
    eRadio.type = 'radio';
    eRadio.value = 'nctv';
    eForm.appendChild(eRadio);

    //cvtn
    var eRadio = document.createElement('input');
    eRadio.checked = true;
    eRadio.value = 'cvtn';
    eRadio.type = 'radio';
    eRadio.name = 'cvtn';
    eForm.appendChild(eRadio);

    //cvnt
    var eRadio = document.createElement('input');
    eRadio.checked = true;
    eRadio.value = 'cvnt';
    eRadio.name = 'cvnt';
    eRadio.type = 'radio';
    eForm.appendChild(eRadio);

    //ctvn
    var eRadio = document.createElement('input');
    eRadio.checked = true;
    eRadio.type = 'radio';
    eRadio.value = 'ctvn';
    eRadio.name = 'ctvn';
    eForm.appendChild(eRadio);

    //ctnv
    var eRadio = document.createElement('input');
    eRadio.checked = true;
    eRadio.type = 'radio';
    eRadio.name = 'ctnv';
    eRadio.value = 'ctnv';
    eForm.appendChild(eRadio);

    //cnvt
    var eRadio = document.createElement('input');
    eRadio.checked = true;
    eRadio.name = 'cnvt';
    eRadio.value = 'cnvt';
    eRadio.type = 'radio';
    eForm.appendChild(eRadio);

    //cntv
    var eRadio = document.createElement('input');
    eRadio.checked = true;
    eRadio.name = 'cntv';
    eRadio.type = 'radio';
    eRadio.value = 'cntv';
    eForm.appendChild(eRadio);

    document.body.appendChild(eForm);

    echoRadio('vtnc');
    echoRadio('vtcn');
    echoRadio('vntc');
    echoRadio('vnct');
    echoRadio('vctn');
    echoRadio('vcnt');
    echoRadio('tvnc');
    echoRadio('tvcn');
    echoRadio('tncv');
    echoRadio('tnvc');
    echoRadio('tcvn');
    echoRadio('tcnv');
    echoRadio('nvtc');
    echoRadio('nvct');
    echoRadio('ntcv');
    echoRadio('ntvc');
    echoRadio('ncvt');
    echoRadio('nctv');
    echoRadio('cvtn');
    echoRadio('cvnt');
    echoRadio('ctvn');
    echoRadio('ctnv');
    echoRadio('cnvt');
    echoRadio('cntv');
}

function echoRadio(elementName){
    //要素を取得
    var elements = document.getElementsByName( elementName ) ;
    //内容を取得
    var output;
    for ( output = "", i = elements.length; i--; ) {
        if ( elements[i].checked ) {
            output = elements[i].value ;
            break ;
        }
    }
    //内容を出力
    document.body.insertAdjacentHTML ( 'beforeend', elementName + ' = '+ output + '<br>');
}

window.addEventListener('load',init,false);

radioボタン以外はどうだろうね?要検証。

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

Denoのマニュアルで気になった所を試してみる

Deno

公式ページ

最近よく目にするので、何はともあれ触ってみようかという事で
マニュアルを元にザッと気になる所を試して見たいと思います。

:computer:環境構築

Denoの公式Docker(?)を見つけたのでそちらで環境を構築して見たいと思います。
https://hub.docker.com/r/hayd/deno

ワンランナーでさくっと試してみます。

$ docker run --rm -it hayd/alpine-deno run https://deno.land/std/examples/welcome.ts
Welcome to Deno ?

:thumbsup:

:pencil: 色々試してみる

簡単なサンプル

公式にも載っていた以下サンプルをローカルに server.ts として作成し、起動させてみます。

import { serve } from "https://deno.land/std@0.50.0/http/server.ts";
const s = serve({ port: 8000 });
console.log("http://localhost:8000/");
for await (const req of s) {
  req.respond({ body: "Hello World\n" });
}
$ docker run --rm -it --init -p 8000:8000 -v $PWD:/app hayd/alpine-deno run /app/server.ts
...
error: Uncaught PermissionDenied: network access to "0.0.0.0:8000", run again with the --allow-net flag

Premissionが無いと怒られました。Denoはデフォルトでファイル、ネットワーク、環境変数等のアクセスが
許可されてないので、--allow-net を付けて再度実行してみます。

$ docker run --rm -it --init -p 8000:8000 -v $PWD:/app hayd/alpine-deno run --allow-net /app/server.ts

今度は実行でき http://localhost:8000/ にアクセスするとちゃんと Hello World が返ってきました :sparkles:

ファイル読み込み

こちら を試して見たいと思います。
cat.ts として以下を作成します。

const filenames = Deno.args;
for (const filename of filenames) {
  const file = await Deno.open(filename);
  await Deno.copy(file, Deno.stdout);
  file.close();
}

読み込み用のファイル sample.txt を以下の内容で作成。

Deno is a simple, modern and secure runtime for JavaScript and TypeScript that uses V8 and is built in Rust.

デフォルトだとファイル読み込みもPremissionが無いので --allow-read を付けて実行します。

$ docker run --rm -it --init -v $PWD:/app hayd/alpine-deno run --allow-read /app/cat.ts /app/sample.txt
> Deno is a simple, modern and secure runtime for JavaScript and TypeScript that uses V8 and is built in Rust.

ちゃんと読み込めてそうです :sparkles:

dependency inspectorformatter

Denoには dependency inspectorformatter がビルドインされているらしいので試してみます。

  • dependency inspector

    $ docker run --rm -it --init hayd/alpine-deno info
    DENO_DIR location: "/deno-dir/"
    Remote modules cache: "/deno-dir/deps"
    TypeScript compiler cache: "/deno-dir/gen"
    

    remoteから取得したmoduleとTypescriptコンパイル時のキャッシュ場所を教えてくれてます :sparkles:

  • formatter

    $ docker run --rm -it --init -v $PWD:/app hayd/alpine-deno fmt
    

テスト

こちらもビルドインでtest runnerが入っているらしいです。至れり尽くせり :sparkles:
https://deno.land/manual/testing

simple.test.ts
import { assertEquals } from "https://deno.land/std/testing/asserts.ts";
Deno.test("simple", () => {
  const x = 1 + 2;
  assertEquals(x, 3);
});

実行!

$ docker run --rm -it --init -v $PWD:/app hayd/alpine-deno test /app/simple.test.ts
running 1 tests
test simple ... ok (3ms)

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (4ms)

Debugger

https://deno.land/manual/tools/debugger
V8 Inspector Protocolをサポートしている

先ほどのファイル読み込みのサンプルをDebugして見たいと思います。
--inspect-brk を付けて最初の1行目でブレークする様にします。

$ docker run --rm -it --init -p 9229:9229 -v $PWD:/app hayd/alpine-deno run --inspect-brk=0.0.0.0:9229 --allow-read /app/cat.ts /app/sample.txt
Debugger listening on ws://127.0.0.1:9229/ws/xxxxx

Chromeで chrome://inspect にアクセスして

↑のinspectをクリックすると

デバッグできました :sparkles:

また、試せては無いですが、VSCode上でもデバッグ可能らしいです。
https://deno.land/manual/tools/debugger#vscode

VSCodeの拡張機能

Deno - Visual Studio Marketplace

:bomb: バッドノウハウ

  • Debugger使用時にchromeでinspectが表示されない :warning:
    • docker使用する場合だと --inspect-brk だけではダメで --inspect-brk=0.0.0.0:9229 と指定しないといけない

:sparkles: 感想

  • ビルドインで色々入っているので便利
  • 外部パッケージをURLでインポートできるのはサクッと試せるので便利
  • いきなりNodeのプロジェクトをDenoに置き換えるのは厳しそうだが、新規のプロジェクトや単独の小さなモジュールとかで使ってみたい

:link: 参考になったURL

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

javascriptのthisとbind、apply、callの使い方

参考

thisの違いはこちらが分かりやすい。
JavaScriptの「this」は「4種類」??

bind()
Function.prototype.bind()

自分なりのまとめ

javascriptのfunction内のthisが示すものはは関数の呼び出し方によって異なる。
関数呼び出し時にthisを明示するために用いるのが下記の関数

  • call
  • apply
  • bind

call、apply

関数呼び出し時にthisを指定する。
callとapplyの違いは引数の取り扱いが違うくらい。

bind

関数のthisに予め任意のものを指定した関数を返す。
bindを使って受け取った関数はthisが固定されているためcallとapplyで呼び出し時に上書きすることは不可能。

確認

https://paiza.io/projects/ZJir-GpI4yZHafpMuZMmcQ?locale=en-us

process.stdin.resume();
process.stdin.setEncoding('utf8');
// Your code here!
value = 'global';

var myObject = {
  value: 'myObject',
  show: function() {
    console.log(this.value);
    return this.value;
  }
}
myObject.show(); // myObject

var show = myObject.show;
show(); // global
show.call(myObject); // myObject
show.apply(myObject); // myObject

show = myObject.show.bind(myObject);
show(); // myObject
show.call(global); //  myObject bindしたため無効
show.apply(global); // myObject bindしたため無効

show = myObject.show.bind(global);
show(); // global
show.call(myObject); // global bindしたため無効
show.apply(myObject); // global bindしたため無効
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】remote:true形式でAjax通信を行う(ブックマーク機能のajax化)

Ajaxとは

Ajaxとは、Webブラウザ上で非同期通信を行い、ページ全体の再読み込み無しにページを更新する方法のことです。

同期通信について

同期通信では、クライアントはwebページ全体の情報(HTMLとそれに紐づくcss,js,imageなどのアセット)をサーバーから受け取って、ページを一から作り直します。
例えばページの一部を変更するだけなのに、他の部分も組み立て直すってことはその分ページの表示に時間がかかっちゃいます。(サーバー側の処理を待つことになる)

しかも、このリクエスト〜レスポンスの処理を行っている間は、他の処理を行わずにサーバーからレスポンスが返ってくるのを待ち続ける必要があります(よくあるのが画面が真っ白になって何もできない状態)。

そこでAjaxのような非同期通信を使用すれば、ページ遷移無しに、高速で更新処理を行い、尚且つ、リクエスト〜レスポンスの処理を行っている間も他の処理が行えます

非同期通信の方法は2種類

この便利なAjaxによる非同期通信を行う方法としては、
①remote:true形式
②ajax関数を使った形式

の2パターンが存在しますが、今回はremote:true形式について以下に記していきます。

仕組みだけ知りたいよって方は、コードの説明は読み飛ばしちゃっても大丈夫です。

コードの説明

今回作るもの

掲示板のブックマーク(いいね)ボタンを押した時に、ブックマークの登録、解除を行うという仕組みをajax化させていきます。

ルーティングの設定

ブックマークの登録、削除を行うために必要なルーティングの設定を行う。

config/routes.rb
resources :boards do
  resources :bookmarks, only: %i[create destroy], shallow: true
end

モデルの設定

モデルでUsersテーブル、Boardsテーブル、Bookmarksテーブルの関連付を行う。

簡素なER図を書くとこんな感じ。
スクリーンショット 2020-05-16 14.13.29.png

app/models/user.rb
class User < ApplicationRecord
  has_many :boards, dependent: :destroy
  has_many :bookmarks, dependent: :destroy
  has_many :bookmark_boards, through: :bookmarks, source: :board

# ブックマーク関連のインスタンスメソッド
  # ブックマークをする
  def bookmark(board)
    bookmark_boards << board
  end

  #  ブックマークを解除する
  def unbookmark(board)
    bookmark_boards.destroy(board)
  end

  # ブックマークをしているかどうかを判定する
  def bookmark?(board)
    bookmarks.where(board_id: board.id).exists?
  end
end
app/models/board.rb
class Board < ApplicationRecord
  belongs_to :user
  has_many :bookmarks, dependent: :destroy
end
app/models/bookmark.rb
class Bookmark < ApplicationRecord
  belongs_to :user
  belongs_to :board

  validates :user_id, uniqueness: { scope: :board_id }
end

コントローラの設定

AjaxによるHTTP通信を行うには、formにremote:trueオプションを設定する必要がある。

  • form_withメソッドでAjax通信を利用しない場合(local: trueオプション)
    bookmarksコントローラのcreateアクション実行の際に、bookmarks/create.html.erbというファイルをレンダリングしようとするため、別のページへリダイレクトさせていた。

  • Ajax通信を利用する場合(remote: trueオプション)
    remote: trueの記述によって、AjaxでHTTPリクエストを送信するように設定される。
    更に、html.erbファイルではなくjs.erbファイルをレンダリングしてくれる。そして、このjs.erbファイルをjsのコードに変換した文字列が、レスポンスボディとしてブラウザに返される(詳細は後述)。

app/controllers/bookmarks_controller.rb
class BookmarksController < ApplicationController
  # js.erbファイルで変数を使用するため、インスタンス変数を設定
  def create
    @board = Board.find(params[:board_id])
    current_user.bookmark(@board)
  end

  def destroy
    @board = current_user.bookmark_boards.find(params[:id])
    current_user.unbookmark(@board)
  end
end

ブックマークボタンを切り替えるためのビュー

bookmarks/_bookmark_area.html.erbファイルで、ログイン中のユーザーが掲示板をブックマークしているかどうかによって呼び出すテンプレートを分ける。

  • ブックマークしていない場合は_bookmark.html.erbを呼び出す。
    • ブックマークボタンは色無しの状態
    • ブックマークする機能
  • ブックマークしている場合は_unbookmark.html.erbを呼び出す。
    • ブックマークボタンは色付きの状態
    • ブックマークを削除する機能
app/views/bookmarks/_bookmark_area.html.erb
<% if current_user.bookmark?(board) %>
  <%= render 'bookmarks/unbookmark', { board: board } %>
<% else %>
  <%= render 'bookmarks/bookmark', { board: board } %>
<% end %>

ブックマークしていない場合のボタンを実装

_bookmark.html.erbファイルを作成
- ブックマークするので、HTTPメソッドはpost。対応するコントローラがcreate.js.erbを呼び出す。
- id属性を付与(どのボタンをクリックしたか判別するため、各レコードのidを使用し、一意性を保つ)
- remote: trueオプションを付与。

app/views/bookmarks/_bookmark.html.erb
<%= link_to board_bookmarks_path(board),
            id: "js-bookmark-button-for-board-#{board.id}",
            method: :post,
            remote: true do %>
  <%= icon 'far', 'star' %>
<% end %>

ブックマークしている場合のボタンを実装

_unbookmark.html.erbファイルを作成
- ブックマークを削除するので、HTTPメソッドはdeletedestroy.js.erbを呼び出す。
- id属性を付与。
- remote: trueオプションを付与。

app/views/bookmarks/_unbookmark.html.erb
<%= link_to bookmark_path(board),
          id: "js-bookmark-button-for-board-#{board.id}",
          method: :delete,
          remote: true do %>
  <%= icon 'fas', 'star' %>
<% end %>

js.erbファイルを作成

js.erbファイルは以下2つの記述が可能。

1. jsの処理
2. rubyの記述(erbファイルだから)

以下のjs.erbファイルによって、画面上に表示するブックマークボタンをAjax通信で切り替えられるようにします。

create.js.erbファイルを作成】
create.js.erbでhtml()メソッドを用い、指定したセレクタのhtml部分(指定したid属性を持つ部分)を置き換える。_unbookmark.html.erbに置き換えるよう記述。

app/views/bookmarks/create.js.erb
$("#btn-bookmark-<%= @board.id %>").html("<%= j(render('boards/unbookmark', board: @board)) %>");

destroy.js.erbファイルを作成】
create.js.erbと逆の内容を記述する。

app/views/bookmarks/destroy.js.erb
$("#btn-bookmark-<%= @board.id %>").html("<%= j(render('boards/bookmark', board: @board)) %>");

ここまでがコードの細かい話!!

ブックマークボタンを押した時のHTTPレスポンスについて

上記の実装によって、なぜブックマークボタンをAjax通信で切り替えられるのか、その仕組みについて以下で説明します。
先に結論を述べると、それはサーバーからレスポンスボディとしてJavaScriptのコードを返し、そのコードに対する処理をクライアント側が実行してくれているからです。

HTTPレスポンスの中身とクライアントの処理は?

  • ブックマークボタンを押した時のHTTPレスポンスの中身はどうなっているのか?
  • それに対してクライアント(ブラウザ)側はどのような処理を行うのか?
    の2点を押さえれば、ブックマークボタンをAjax通信で切り替えられる仕組みを理解できるはずです。
ブックマークボタンを押した時のHTTPレスポンスの中身は?

erbファイルをJS形式のコード(この段階ではただの文字列!)に変換したものが、レスポンスボディとしてクライアントに返されます。

つまり、erbファイルをそのままクライアントに返すのではなく、サーバー側でjs.erbファイルのrubyの記述(j renderとか@boardとか)を事前に実行し、HTMLのコードとして展開した結果を、クライアント側に返しているのです。
一言で表すなら、クライアント側が読める内容に変換してから返している、ということです。

レスポンスに対するクライアント側の処理

これに対し、クライアントはサーバーから返ってきたレスポンスボディを見て、「これはjs形式のものだな」と判断し、そこでようやくレスポンスボディの文字列に対してJavaScriptを実行してくれる、といった感じです。

検証ツールのネットワークタブを確認

  • HTTPレスポンスのContent-Type(どういうコンテンツの種類か)が text/javascriptになっている。
    →ajax通信に設定しているから、RailsがJS形式でレスポンスを送ってくれている。
    →クライアント側はContent-typeを見て「JSで処理するんだな」と判断している。

スクリーンショット 2020-05-03 22.11.16.png

  • レスポンスボディにjs形式のコードが入っている。
    スクリーンショット 2020-05-03 22.10.46.png

  • レスポンスボディの詳細

$("#js-bookmark-button-for-board-152").replaceWith("<a id=\"js-bookmark-button-for-bo
ard-152\" data-remote=\"true\" rel=\"nofollow\" data-method=\"delete\" href=\"/bookma
rks/152\">\n  <i class=\"fas fa-star\"><\/i>\n<\/a>");

おわりに

以上でremote:true形式でAjax通信を行う方法の説明を追えます。
なにか説明部分について誤りがございましたら、ご指摘頂きたく思います。

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

高階関数とコールバック関数

この記事はコールバック関数の以下の内容について解説します。

  • 高階関数とは
  • コールバック関数とは
  • コールバック関数の例
  • forEachメソッドをコールバック関数で実装してみる
  • まとめ

高階関数

コールバック関数に入る前に、高階関数について説明します。
高階関数とは、関数を引数にとる関数の事を指します。
以下が例です。

sample
function hello(){
 console.log('hello');
}

function hai(callback){
 callback();
}

hi(hello);

hi関数はhello関数を引数としているので、高階関数となります。

上の例がどのような流れなのかについて説明します。

  1. hi(hello);により、hi関数が実行
  2. hi関数内のcallback();より、hello関数が実行
  3. hello関数内のconsole.log('hello');が実行
  4. hi関数に戻るが何も処理がないのでそのまま終了

次にコールバック関数について説明します。

コールバック関数

コールバック関数とは、高階関数に引数として渡された関数の事を指します。
上の例ですと、hello関数が該当します。

コールバック関数の例

ここまでコールバック関数について、ざっくりとした説明をしました。
上記の内容だけでもコールバック関数が利用できるようにはなりますが、JavaScriptには関数宣言、関数式、アロー関数があるためコールバック関数も上記の書き方以外にも書き方はあります。
更にコールバック関数は引数をとることができます。関数なので当たり前なのですが、ここら辺で躓く人が多いと思います。
そこで、ここからは様々なコールバック関数の書き方を例としてあげ、コールバック関数への理解を深めていきたいと思います。

例1 関数宣言以外でコールバック関数をコーディング

コールバック関数は関数なので、勿論関数式でもアロー関数でも定義することができます。
以下は、高階関数を関数式で、コールバック関数をアロー関数で定義した例です。
因みに関数宣言、関数式、アロー関数が分からないという方は、こちらへどうぞ

const func1 = function(callback) {
    console.log('例1 一番最初に実行される');
    callback();
    console.log('callbackが呼ばれた後に実行される');
};
// func1に引数として呼ばれている側なので、callback1関数はコールバック関数
const callback1 = () => {
    console.log('コールバック1');
};

func1(callback1);

func1(callback1)でcallback1関数を引数としたfunc1関数を実行しているので、func1関数は高階関数、callback1関数はコールバック関数となります。

この例を実行した結果は、実行結果1のようになります。

exam1.png
実行結果1

例2 引数をもつコールバック関数をコーディング

コールバック関数は関数なので、勿論引数をもつことができます。
以下は、引数をもつコールバック関数の例です。

const msg = 'コールバック2';
const func2 = function (msg, callback) {
    console.log('例2 一番最初に実行される');
    callback(msg);
    console.log('callbackが呼ばれた後に実行される');
};
const callback2 = function (message) {
    console.log(message);
};

func2(msg, callback2);

この例で行われている処理の流れを説明します。

  1. 文字列(msg)と関数式2つ(func2, callback2)を定義しています。この時点では定義しているだけですので、実行しても何も出力されません。

  2. func2(msg,callback)にて、func2関数が呼び出されています。引数は、最初に定義した文字列(msg)とコールバック関数です。

  3. func2関数が呼び出されたので、func2関数内の一番最初にコーディングされたconsole.log(例2 一番最初に実行される');が実行されます。

  4. 次にcallback(msg)が実行されます。callbackにはcallback2関数が代入されているので、ここでの処理はmsgを引数に持つcallback2関数が呼び出されている事になります。

  5. 4.でcallback2関数が呼び出されたので、callback2関数内の一番最初にコーディングされたconsole.log(message);を実行します。

  6. callback2関数内には他に処理がないので、終了となります。

  7. callback(msg);が終了したので、func2関数内の次の処理にうつります。console.log('callbackが呼ばれた後に実行される');が実行されます。

  8. func2関数内には他に処理がないので、終了となります。

よって、実行結果は、以下の実行結果2となります。
exam2.png
実行結果2

例3 高階関数を呼び出すときにコールバック関数をコーディング

コールバック関数には他にもコーディングの仕方はたくさんありますが、ここでは最後に理解しにくい例をあげます。
以下、その例です。

const msg = 'コールバック3';
const func3 = function (msg, callback) {
    console.log('一番最初に実行される');
    callback(msg);
    console.log('callbackが呼ばれた後に実行される');
};

func3(msg, (message) => {
    console.log(message);
});

func3関数は高階関数です。コールバック関数はどこへいったかといいますと、
(message) => {
console.log(message);
}
がコールバック関数となっています。
例2を省略してコーディングしたものですね。省略しただけで処理の流れは同じです。
そのため、実行結果は以下の実行結果3です。
exam3.png
実行結果3

forEachメソッドをコールバック関数で実装

JavaScriptにはforEachメソッドがあります。
forEachメソッドは配列の中の要素を一つ一つ実行します。これと同じことをするものをコールバック関数で実装していきます。
forEachについて知らない、もっと詳しく知りたいという方は、こちらへどうぞ。

const array = ['コールバック関数', '', 'forEachメソッドを', '作ってみる'];

const forEach = function (array, callback) {
    for (let i = 0; i < array.length; i++) {
        callback(array[i], i);
    }
};
forEach(array, function(item, index) {
    console.log(`コールバック関数の実行${index}回目`, item);
});

コード内容について説明します。
1. 最初にforEachメソッドで使うための配列を用意します。
2. 次に高階関数(forEach())をコーディングしています。高階関数ではforEachメソッドの配列の要素を一つ一つ実行する、というところをfor文を使うことで実装しています。for文で配列の各要素とインデックス番号を引数とした関数を呼び出しています。
3. コールバック関数(function(item, index))を実行し、処理が全て終了すると、for文に戻ります。
4. for文の中は他に何も処理がないので、for文の外へ出ます。for文後も何も処理がないので処理は終了となります。

このコードの実行結果は以下の実行結果4になります。
forEach.png
実行結果4

まとめ

今回はコールバック関数の以下の内容について説明しました。

  • 高階関数とは、関数の引数をもつ関数のこと
  • コールバック関数とは、高階関数に引数として渡される関数のこと
  • コールバック関数の例を3つ紹介
  • forEachメソッドをコールバック関数で実装
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ニコ生チャット取得

コメントビューア用。
OAuthが要るようなAPIは使わず、未登録で取得できるコメントを取得。
あくまで執筆時点の情報です。仕様が変わる可能性もあるのであしからず。

前提として、ニコ生チャット取得はWebSocketの接続を2本行う必要があることに留意。

コミュニティIDを元に、配信ページを取得

  • https://live2.nicovideo.jp/watch/{コミュニティID}
    • 例: https://live2.nicovideo.jp/watch/co4930985

ページに埋め込まれた放送情報を取得する

    const embeddedData = JSON.parse(document.getElementById("embedded-data").getAttribute("data-props"));
  • 以下の場合は配信が終了してる
    • embeddedData.program.status === 'ENDED'
    • embeddedData.program.endTime < 現在時刻
    const broadcastId = embeddedData.program.broadcastId  || embeddedData.program.reliveProgramId;
    const audienceToken = embeddedData.player.audienceToken;
    const frontendId = embeddedData.site.frontendId;

スレッドIDを取得する

WebSocketで以下に接続

const url = `wss://a.live2.nicovideo.jp/unama/wsapi/v2/watch/${broadcastId}?audience_token=${audienceToken}&frontend_id=${frontendId}`

メッセージのやり取りは以下の通り。簡単のためにJSONで表記しているが、実際は文字列化して送受信される。

send
{
  "type": "startWatching",
  "data": { 
    "stream": { "quality": "high", "protocol": "hls", "latency": "low", "chasePlay": false },
    "room": { "protocol": "webSocket", "commentable": true },
    "reconnect": false
  },
}
send
{ 
  "type": "getAkashic",
  "data": { "chasePlay": false } 
}
receive
{
    "type":"room",
    "data":{
      isFirst: boolean,
      messageServer: {
        type: string,
        /** コメントサーバのWebSocketURL */
        uri: string,
      },
      name: string,
      /** スレッドID */
      threadId: string,
      waybackkey: string,
    }
}
  • ここで抜き出すべき要素は以下。
    • data.messageServer.uri
    • data.threadId

チャットサーバに接続してスレッドIDを送信

先程取得したコメントサーバのURLにWebSocket接続。
WebSocketのリクエストヘッダの指定が必要。

    const ws = new WebSocket(wsUrl, 'niconama', {
      headers: {
        'Sec-WebSocket-Extensions': 'permessage-deflate; client_max_window_bits',
        'Sec-WebSocket-Protocol': 'msg.nicovideo.jp#json',
      },
    });

以下WebSocketでの送受信。

send
[
    {
        "ping": {
            "content": "rs:0"
        }
    },
    {
        "ping": {
            "content": "ps:0"
        }
    },
    {
        "thread": {
            "thread": "★スレッドID★",
            "version": "20061206",
            "user_id": "guest",
            "res_from": -150,
            "with_global": 1,
            "scores": 1,
            "nicoru": 0
        }
    },
    {
        "ping": {
            "content": "pf:0"
        }
    },
    {
        "ping": {
            "content": "rf:0"
        }
    }
]

チャット情報の受信

コメントサーバのWebSocketにスレッドIDを送信したら、コメント情報が返ってくる。

receive
開いた時点までのチャット情報がドカッと来る。
obj.ping.content: 'rf:0'のものを受信したらおしまい。

以降は下記のchatオブジェクトを含んだメッセージを受信し続ける。

receive
{
  chat: {
    anonymity?: number;
    /** チャットコメント */
    content: string;
    /** Dateを数値化したやつ */
    date: number;
    date_usec: number;
    /** 匿名の時は184が入ってる */
    mail?: string;
    /** コメント番号 */
    no: number;
    /**
     *  謎のフラグ
     * - 1: プレミアム会員
     * - 3: 配信者自身
     */
    premium?: number;
    score?: number;
    thread: string;
    user_id: string;
    vpos: number;
  };
}

また、定期的にWebSocketに対してpingを打つ必要がある。特にメッセージを含める必要はない。
これをしないと途中でメッセージが来なくなる。
JavaScriptなら、WebSocketインスタンスが持っているpingをsetInterval実行するだけでOK。

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

JavaScript 学習記録 #1 Dateオブジェクト

わかりやすく日時を表示する方法:point_up:コードの書き方

①プログラムを書く準備

date.js
<section>
    <p>最終アクセス日時:<span id="time"></span></p>
</section>

最終アクセス日時:

②「年/月/日 時:分」のかたちで表示
 そのためには「年、月、日、時、日、分」を個別に取得する必要がある。

date.js
<script>
'use strict';

const now = new Date();
const year = now.getFullYear();
const month = now.getMonth();
const date = now.getDate();
const hour = now.getHours();
const min = now.getMinutes();

const output = `${year}/${month + 1}/${date} ${hour}:${min}`;
document.getElementById('time').textContent = output;
</script>

③Dateオブジェクト
 日時を扱うためのオブジェクトであり、次のようなことができる
  1.現在日時を取得する
  2.過去や未来の日時を設定する
  3.日時の計算をする

 :point_right:Dateオブジェクトは初期化(インスタンス化)する必要がある
   new Date();
  ※いくつかのオブジェクトは、newを使って初期化してから使う
 :point_right:月を取得するgetMonthメソッドは「実際の月-1」の数字が取得されるため、+1を忘れずに
  20200516_200516_0002.jpg

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

【Rails】テーブルを分けて複数の画像をアップロードする

複数枚の写真を一度に保存する機能の実装において、
【itemテーブル】と【item_imageテーブル】の二つに
分けて複数枚の写真を保存する機能を実装した際の手順をまとめたのでご紹介します。

完成形

○ 商品写真は最大で10枚まで投稿可能。
○ 一つのファイルフィールドに一つの写真でアップロードしていく。
○ 5枚投稿時点で下段に切り替わる。
Image from Gyazo

1. HTMLの用意

sample.haml
~ 省略 ~

.image-container
  .image-container_box
    .form-title
      %span.box-form-explanaion
        商品画像
      %span.indispensable
        必須
    %p.image-container_box_message
      最大10枚までアップロードできます
    .image-container_box_alart-10
      ※ 1枚目は必須です
  .image-container_unit-man    【写真が順にプレビューされていく箱】
    .item-image-container__unit.preview-0  【写真一枚が入る箱 ※投稿ごとに生成されていく】
      = f.fields_for :item_images do |i|
        .image-container__unit--guide
          = i.file_field :image, class: 'img-man', id:"image-label-0",type: 'file'
          .have-image
            %i.fas.fa-camera

~ 省略 ~

◉【写真一枚が入る箱】に2つクラスがあるのは、2枚目以降では毎回クラス名を
変えていくためです。
◉この仕様では、1つのinputに写真は1つなので、multiple属性は付けていません。

2. CSSの用意

sample.scss
  .image-container {
    padding: 40px;
    border-bottom: solid 1px rgb(235, 235, 235);
    &_box {
      &_message {
        height: 19px;
        margin: 16px 0 5px;
        font-size: 14px;
        line-height: 1.4em;
        display: block;
      }
      &_alart-10 {
        margin-bottom: 4px;
        font-size: 14px;
        font-weight: bold;
        color:red;
      }
    }
    &_unit-man {
      min-height: 152px;
      display: flex;
      flex-direction: row;
      flex-wrap: wrap;
      .item-image-container__unit {
        align-content: center;
        align-items: center;
        background-color: rgb(245, 245, 245);
        display: flex;
        justify-content: flex-start;
        height: 150px;
        width: 118px;
        margin-left: 5px;
        margin-bottom: 2px;
        justify-content: center;
        position: relative;
        border-width: 1px;
        border-style: dashed;
        border-color: rgb(204, 204, 204);
        border-image: initial;

        .have-image {
          position: absolute;
            left: 32px;
            top: 40px;
          z-index: 0;
          cursor: pointer;
          font-size: 16px;
          .fas.fa-camera {
            margin-left: 16px;
            font-size: 1.2rem;
          }
          .fas.fa-camera:hover {
            cursor: pointer;
            transform: scale(1.3, 1.3);
            transition: all 0.1s ease 0s;
          }
        }
        .item-image-container__unit__image {
          z-index: 1;
          height: 145px;
          width: 100%;
          margin: 0 3px;
          background-color: #ffffff;
          position: relative;
          img {
            width: 100%;
            height: auto;
          }
          .image-option__list--tag {
            width: 100%;
            background-color: lightblue;
            text-align: center;
            position: absolute;
              bottom: 0;
              left: 0;
          }
          .image-option__list--tag:hover {
            cursor: pointer;
            transform: scale(1.0, 1.0);
            transition: all 0.1s ease 0s;
            background-color: #ea352d;
            color:#ffffff;
          }

        }
      }
      .item-image-container__unit {
        input{
          display: none;
        }
      }
    }
  }

[flex-direction: row;]と[flex-wrap: wrap;]により、
写真が既定の幅まで投稿されたら下段に折り返してくれます。

3. JSでプレビューさせる

sample.js
$(function(){


~ 省略 ~


  var dataBox = new DataTransfer();  //データ用の箱を作る
  $(document).on('change', '.img-man', function(){    //inputの中身が変化したら発火する
    $.each(this.files, function(i, file){
      var fileReader = new FileReader();
      dataBox.items.add(file)    
      fileReader.readAsDataURL(file);     //ファイルの読み込み
      fileReader.onloadend = function() {     //読み込み完了すると発火
        var num = $('.item-image-container__unit').length  //写真の枚数をnumに代入
        var src = fileReader.result   //写真のデータをsrcに代入
        var html =  `<div class="item-image-container__unit__image">
                        <img src="${src}">
                      <div class="image-option__list--tag btn-${num}">削除</div>
                    </div>`

        var field = `<div class="item-image-container__unit preview-${num}">
                      <div class="image-container__unit--guide">
                        <label for="image-label-${num}">
                          <input class="img-man" id="image-label-${num}" type="file" name="item[item_images_attributes][${num}][image]">
                          <div class="have-image">
                            <i class="fas fa-camera"></i>
                          </div>
                        </label>
                      </div>
                    </div>`
        $(html).appendTo(`.preview-${num - 1}`)  //枚数で該当するクラスに写真を追加する
                      
        if (num < 10 ) {     //10枚分まで新しいinputの生成を行う
          $(field).appendTo('.image-container_unit-man')
        }
      };
    });
  });
  //削除機能 
  $(document).on("click", ".image-option__list--tag", function(){  //削除ボタンクリックで発火
    var num = $('.item-image-container__unit').length
    var field_2 = `<div class="item-image-container__unit preview-0">
                    <div class="image-container__unit--guide">
                      <label for="image-label">
                        <input class="img-man" id="image-label-0" type="file">
                        <div class="have-image">
                          <i class="fas fa-camera"></i>
                        </div>
                      </label>
                    </div>
                  </div>`
    $(this).parent().parent().remove();  //写真が入っている箱ごと削除
    $(".item-image-container__unit").removeClass(`preview-${num - 1}`)
    $(".item-image-container__unit").addClass(`preview-${num - 2}`)
    if(num == 1) {   //全部削除した時に新たに1つフィールドを生成する
      $(field_2).appendTo('.image-container_unit-man')
    }
  });

~ 省略 ~

◉変数【num】は、写真の読み込みごとに代入され、その度に横に生成される
【preview-${num}】クラスが書き換わって横に生成されるようになっています。

◉ 下部の10枚制限の記述によって、10枚までアップされるとinputの生成が止まります。
また、multiple属性が付いていないので写真の一括選択はできなくなります。

◉削除機能に関しても、クラス名の書き換えを行う必要があります。


登録枚数に関しては、モデルにも別でバリーデーションは書いてあります。

sample.rb
  def  item_images_number
    errors.add(:item_images, "は10枚までです") if item_images.size > 10
  end

以上で終了です。
ご覧いただきありがとうございました。

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

toio を Web Bluetooth API で制御(「通知・読み出し・書き込み」を行う)

はじめに

この記事は、Web Bluetooth API を使ってロボットトイ「toio」を制御した際の過程・プログラムをメモしたものです。
具体的には以下の 4種類の処理・制御を試しました。

  • 通知の ON/OFF(モーションセンサーの値を受け取る)
  • デバイスの切断・再接続
  • 値の書き込み(ランプの点灯の制御)
  • 値の読み出し(読み取りセンサーの情報を受け取る)

制御に利用する情報は、以下の公開情報から得ています。
 ●通信概要 · toio™コア キューブ 技術仕様
  https://toio.github.io/toio-spec/docs/ble_communication_overview.html

以前試した内容

Web Bluetooth API による toio の制御は、以前も利用したことがありました。
その時に行った内容は、2台の toio に同時に接続してモーターの制御を行ったりするもの等で、試した内容は記事に書きました。

このとき、値の読み出しや通知の停止・再開といった内容を試してなかったため、今回はそれらを含めて通信周りを一通り試してみます。

参考にしたサイト

今回、下記にたくさんあるサンプルの中の 4つを参照して、参照したソースコードの中の不要な部分を削ったり、toio用の処理に合わない部分を書きかえたり、ということをやりました。
 ●samples/web-bluetooth at gh-pages · GoogleChrome/samples
  https://github.com/GoogleChrome/samples/tree/gh-pages/web-bluetooth

参照したソースは以下の4つです。

作成した 4つのソースコード

以下で、toio を制御するためのソースコードを掲載します。
この記事では、ソースコードに関する補足は割愛しています。また、全てのソースコードでCSSフレームワークの「Bulma」を読み込んでいます。

通知の ON/OFF

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Web Bluetooth API で通知</title>
<link rel="stylesheet" href="./bulma.min.css">
</head>
<body>
  <section class="section">
    <div class="container">
      <h1 class="title">
        操作用ボタン1
      </h1>
      <div class="buttons">
        <button class="button is-success is-light" type="button" onclick="onStartButtonClick()">接続+通知ON</button>
        <button class="button is-danger is-light"  type="button" onclick="onStopButtonClick()">通知OFF</button>
        <button class="button is-info is-light" type="button" onclick="onStartNotificationsButtonClick()">通知ON</button>
      </div>
    </div>
</section>

<script>
const TOIO_SERVICE_UUID          = '10b20100-5b3b-4571-9508-cf3efcd7bbae';
const MOTION_CHARACTERISTIC_UUID = '10b20106-5b3b-4571-9508-cf3efcd7bbae';

let myCharacteristic;

async function onStartButtonClick() {
  let serviceUuid = TOIO_SERVICE_UUID;
  let characteristicUuid = MOTION_CHARACTERISTIC_UUID;

  try {
    console.log('Requesting Bluetooth Device...');
    const device = await navigator.bluetooth.requestDevice({
        filters: [{services: [serviceUuid]}]});
    console.log('Connecting to GATT Server...');
    const server = await device.gatt.connect();
    console.log('Getting Service...');
    const service = await server.getPrimaryService(serviceUuid);
    console.log('Getting Characteristic...');
    myCharacteristic = await service.getCharacteristic(characteristicUuid);
    await myCharacteristic.startNotifications();
    console.log('> Notifications started');
    myCharacteristic.addEventListener('characteristicvaluechanged',
        handleNotifications);
  } catch(error) {
    console.log('Argh! ' + error);
  }
}

async function onStopButtonClick() {
  if (myCharacteristic) {
    try {
      await myCharacteristic.stopNotifications();
      console.log('> Notifications stopped');
      myCharacteristic.removeEventListener('characteristicvaluechanged',
          handleNotifications);
    } catch(error) {
      console.log('Argh! ' + error);
    }
  }
}

async function onStartNotificationsButtonClick() {
  try {
    console.log('Starting Notifications...');
    await myCharacteristic.startNotifications();
    myCharacteristic.addEventListener('characteristicvaluechanged',
        handleNotifications);
    console.log('> Notifications started');
  } catch(error) {
    log('Argh! ' + error);
  }
}

function handleNotifications(event) {
  let value = event.target.value;
  let a = [];
  for (let i = 0; i < value.byteLength; i++) {
    a.push('0x' + ('00' + value.getUint8(i).toString(16)).slice(-2));
  }
  console.log('> ' + a.join(' '));
}

</script>
</body>
</html>

デバイスの切断・再接続

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Web Bluetooth API で接続・切断</title>
<link rel="stylesheet" href="./bulma.min.css">
</head>
<body>
  <section class="section">
    <div class="container">
      <h1 class="title">
        操作用ボタン1
      </h1>
      <div class="buttons">
        <button class="button is-success is-light" type="button" onclick="onStarButtonClick()">接続</button>
        <button class="button is-light"            type="button" onclick="onReadButtonClick()">読み込み</button>
      </div>
    </div>
</section>

<script>
const TOIO_SERVICE_UUID          = '10b20100-5b3b-4571-9508-cf3efcd7bbae';
const ID_SENSOR_CHARACTERISTICS_UUID = '10b20101-5b3b-4571-9508-cf3efcd7bbae';

let bluetoothDevice;
let idSensorCharacteristic;

async function onStarButtonClick() {
  try {
    if (!bluetoothDevice) {
      await requestDevice();
    }
  } catch(error) {
    console.log('Argh! ' + error);
  }
}

async function requestDevice() {
  let serviceUuid = TOIO_SERVICE_UUID;
  let characteristicUuid = ID_SENSOR_CHARACTERISTICS_UUID;

  try {
    console.log('Requesting Bluetooth Device...');
    bluetoothDevice = await navigator.bluetooth.requestDevice({
          filters: [{services: [serviceUuid]}]});
    console.log('Connecting to GATT Server...');
    const server = await bluetoothDevice.gatt.connect();
    console.log('Getting Service...');
    const service = await server.getPrimaryService(serviceUuid);
    console.log('Getting Characteristic...');
    idSensorCharacteristic = await service.getCharacteristic(characteristicUuid);
  } catch(error) {
    console.log('Argh! ' + error);
  }
}

async function onReadButtonClick() {
  if (idSensorCharacteristic) {
    try {
      console.log('Reading ...');
      await idSensorCharacteristic.readValue().then(value => {
        let a = [];
        for (let i = 0; i < value.byteLength; i++) {
          a.push('0x' + ('00' + value.getUint8(i).toString(16)).slice(-2));
        }
        console.log('> ' + a.join(' '));
      });
    } catch(error) {
      console.log('Argh! ' + error);
    }
  }
}

</script>
</body>
</html>

値の書き込み

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Web Bluetooth API で書き込み</title>
<link rel="stylesheet" href="./bulma.min.css">
</head>
<body>
  <section class="section">
    <div class="container">
      <h1 class="title">
        操作用ボタン1
      </h1>
      <div class="buttons">
        <button class="button is-success is-light" type="button" onclick="onReadButtonClick()">接続</button>
        <button class="button is-info is-light" type="button" onclick="onWriteButtonClick()">書き込み</button>
      </div>
    </div>
</section>

<script>
const TOIO_SERVICE_UUID          = '10b20100-5b3b-4571-9508-cf3efcd7bbae';
const LIGHT_CHARACTERISTICS_UUID = '10b20103-5b3b-4571-9508-cf3efcd7bbae';
const light_buf = new Uint8Array([ 0x03, 0x00, 0x01, 0x01, 0x00, 0xFF, 0x00 ]);

let myDescriptor;
let characteristic;

async function onReadButtonClick() {
  let serviceUuid = TOIO_SERVICE_UUID;
  let characteristicUuid = LIGHT_CHARACTERISTICS_UUID;

  try {
    console.log('Requesting Bluetooth Device...');
    const device = await navigator.bluetooth.requestDevice({
        filters: [{services: [serviceUuid]}]});
    console.log('Connecting to GATT Server...');
    const server = await device.gatt.connect();
    console.log('Getting Service...');
    const service = await server.getPrimaryService(serviceUuid);
    console.log('Getting Characteristic...');
    characteristic = await service.getCharacteristic(characteristicUuid);

    console.log('Getting Descriptor...');
    myDescriptor = await characteristic.getDescriptor('gatt.characteristic_user_description');
    const value = await myDescriptor.readValue();
    let decoder = new TextDecoder('utf-8');
    console.log('> Characteristic User Description: ' + decoder.decode(value));
  } catch(error) {
    console.log('Argh! ' + error);
  }
}

async function onWriteButtonClick() {
  if (!characteristic) {
    return;
  }
  let value = light_buf;
  try {
    await characteristic.writeValue(value);
  } catch(error) {
    console.log('Argh! ' + error);
  }
}

</script>
</body>
</html>

値の読み出し

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Web Bluetooth API で読み込み</title>
<link rel="stylesheet" href="./bulma.min.css">
</head>
<body>
  <section class="section">
    <div class="container">
      <h1 class="title">
        操作用ボタン1
      </h1>
      <div class="buttons">
        <button class="button is-success is-light" type="button" onclick="onStarButtonClick()">接続</button>
        <button class="button is-light"            type="button" onclick="onReadButtonClick()">読み込み</button>
      </div>
    </div>
</section>

<script>
const TOIO_SERVICE_UUID          = '10b20100-5b3b-4571-9508-cf3efcd7bbae';
const ID_SENSOR_CHARACTERISTICS_UUID = '10b20101-5b3b-4571-9508-cf3efcd7bbae';

let bluetoothDevice;
let idSensorCharacteristic;

async function onStarButtonClick() {
  try {
    if (!bluetoothDevice) {
      await requestDevice();
    }
  } catch(error) {
    console.log('Argh! ' + error);
  }
}

async function requestDevice() {
  let serviceUuid = TOIO_SERVICE_UUID;
  let characteristicUuid = ID_SENSOR_CHARACTERISTICS_UUID;

  try {
    console.log('Requesting Bluetooth Device...');
    bluetoothDevice = await navigator.bluetooth.requestDevice({
          filters: [{services: [serviceUuid]}]});
    console.log('Connecting to GATT Server...');
    const server = await bluetoothDevice.gatt.connect();
    console.log('Getting Service...');
    const service = await server.getPrimaryService(serviceUuid);
    console.log('Getting Characteristic...');
    idSensorCharacteristic = await service.getCharacteristic(characteristicUuid);
  } catch(error) {
    console.log('Argh! ' + error);
  }
}

async function onReadButtonClick() {
  if (idSensorCharacteristic) {
    try {
      console.log('Reading ...');
      await idSensorCharacteristic.readValue().then(value => {
        let a = [];
        for (let i = 0; i < value.byteLength; i++) {
          a.push('0x' + ('00' + value.getUint8(i).toString(16)).slice(-2));
        }
        console.log('> ' + a.join(' '));
      });
    } catch(error) {
      console.log('Argh! ' + error);
    }
  }
}

</script>
</body>
</html>

まとめ

無事に、Web Bluetooth API を使っての値のやりとり(通知、書き込み、読み出し)を行うことができました。
この後は、センサー等の値の読み出しで取得した結果や、通知されたセンサー等の値の変化によって、toio の挙動を変えたりするようなプログラムを作ってみようと思います。

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

Firebase Local Emulator SuiteのFirestoreに外部のスクリプトからアクセスしてモックデータを突っ込む

こちらに非常に良い記事があります。まだ読んでおられない方は是非!

環境変数のFIRESTORE_EMULATOR_HOSTに適切な値を設定することで外部のスクリプトからエミュレーターにアクセスできます。(他言語のSDKでも同様だそうです)

import { Firestore } from '@google-cloud/firestore';

Object.assign(process.env, {
  GCLOUD_PROJECT: '[プロジェクトID]', 
  FIRESTORE_EMULATOR_HOST: '0.0.0.0:[Firestoreのポート番号]'
});

const firestore = new Firestore();

(async () => {
  // admin権限が必要な操作も可能
  console.log(await firestore.listCollections());
})();

なお、GCLOUD_PROJECTに関しては環境変数を使わなくても適当なメソッドで代替できます。実際に存在するプロジェクトIDである必要はないことに注意です!Firestoreのエミュレータは並列実行しても1インスタンスなので、プロジェクトIDが被ると共通の領域が使用されるようになります。

これに気づくまでに数時間を要しましたが、冒頭にも書いた通り、既に偉大な先人の方が記事を書かれていたようです。こちらの記事は十分に読み込んだつもりになっていましたがまだまだ甘かった・・・。

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

EJSで「moment」というライブラリーを使用する方法

ウェブアプリを開発する際にEJSページで時間のフォーマットを指定したい場合はどうすればいいでしょうか?
ネット上で検索してみたが、いろいろな方法が出た。
でも、やってみた後で「moment」ライブラリーの使い方がいいと思う
じゃ、説明いたします

分かりやすいためにSails.jsフレームワークを使う

最初、「moment」をプロジェックトにインストールする

npm install moment

Sails.jsのアクションファイルでmomentを呼び出します

  fn: async function () {
    var postId = this.req.param('id');
    var postInfo = await Post.findOne({ id: postId });
    return { post: postInfo, moment: momentInfo };
  }

ESJページにmoment変数を渡すためにreturn { moment: momentInfo };を使う
そうした後でESJページでmoment変数がある

最後、moment変数をESJページで呼び出します

 作成日 <%= moment(post.createdAt).format('YYYY/MM/DD') %>

結果は「作成日 2020/05/15」をブラウザで表示される
※「post.createdAt」はデータベースに「1589531502042」を格納する。

以上です

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