この記事は・・・
- Google謹製のBlocklyのお話。
- Blocklyはブロックプログラミングのためのフレームワークで拡張可能。
- そもそもScratch3.0がBlcoklyベース
- BlocklyでWebSocketなんか使えたら夢が広がるよね。
- 小学生が、ネコ動かすだけじゃなく、通信プログラム書いたりできるよね。
- Blocklyの拡張を勉強するために習作としてWebSocketブロックを作ろう!
- ついでに、Blocklyを拡張したい人向けにHow-toを残しておこう・・・・
と、そんな意気込みで書き始めたら記事が長くなってしまいました。
そこで記事を2回に分ける事にしました・・・・
- その1:カスタムブロックの作り方(この記事)
- その2:WebSocket通信の実現 (未完成)
Blocklyとは
BlocklyはGoogleが開発している、拡張可能なブロックプログラミングフレームワークです。基本的な命令はあるので、Play Groundでプログラミングして遊ぶ事も出来ますが、特定の環境・言語に依存した機能は一切持っていないため、これ単体では意味のある事は出来ません。つまり、Scratchのようにネコを動かす事は出来ないんです。
では、何のためのツールか?というと、Scratchのような独自のブロックプログラミング環境を構築したい人のうち、「でも、ブロックのデザインや操作のロジックを組み立てるのはメンドクサイから、何かツールでちゃちやっと出来ないかな?」という人向けの便利ツールです。事実Scrath3.0はBlocklyベースだそうですね。
世の小学生はScratchを直接使うでしょうし、独自のブロックプログラミング環境を子供にプレゼントするお父さんなんてそうそう居ないでしょうから、Blocklyを直接使うニーズなんて、あまり無いのかもしれませんが、BlocklyはJavaScriptで簡単に拡張可能である事を謳っていますので、習作として何か作って見たくなりますよね。
習作なんで、拡張の方法が理解できれば何でも良いのですが、ネコを走らす機能を作っても二番煎じですので、何かすこし意味のある機能を作りましょう。という事でWebSocketです。
以前、サイドワークで小学生向けのScratch講座を手伝ったのですが、そこで感じたのは、結局、プログラミングというよりは、ちょっとインタラクティブな紙芝居になってしまうケースがあまりにも多いなという事でした。小学校でプログラミングの授業か始まりますが、複数の生徒たちが一斉にプログラミングをするのですから、通信機能ぐらいあったって良いと思う訳です。Nintendo Switchだってネットワーク対戦できる訳で、彼らが最初に触るプログラミング環境に、友達のプログラムと簡単に通信できる機能があったとしても、決して「理解できない機能」という事はないはずです。
Blocklyはブラウザ上でプログラミングと実行が出来るツールです。ブラウザ上で通信と言えば、WebSocketですよね。ところがこのWebSocket、いや、WebSocketに限らず、最近のJavaScript系のツールは、非同期実行とコールバック関数の嵐です。
何が問題かといえば、ブロックプログラミングの殆どが、非同期で起こるイベント処理に、文法レベル(いやブロックレベルと言うべきか?)で対応していないのです。
例えばWebSocketはインスタンスを作った後に、各種ハンドラに関数を登録しておきます。
//インスタンスの作成
const ws= new WebSocket('ws://foo')
//メッセージ受信の際に実行される関数を上書きして独自コードを入れ込む
ws.onmessage = (event)=>{
console.log(event.data)
}
最近のJavaScriptの書き方は殆どがこんな感じで、コールバックが多階層入れ子になる事が問題にもなり、コールバック地獄などと呼ばれていますが、node-asyncやPromise等が出てきて、入れ子のコールバックをシンプルに表現できるようになりました。
ところが、ブロックプログラミングでは、そもそもコールバック関数を登録して・・・というような事がブロックレベルで対応していません。(Scratchの場合は、「〇〇が押されたら」というUIイベントに対する処理は書けますけど・・・)
という訳で、ちょっとその辺の工夫が、Blocklyの習作としては丁度良いかなと考えました。
完成品
完成した作品はGitHubとnpm上に置いてあります。WebSocketによるエコーサーバーと、今回作ったWebSocketブロックが使えるブロックプログラミング環境が含まれています。
- https://github.com/abarth500/Blockly-with-WebSocket
- https://www.npmjs.com/package/blockly-with-websocket
試しに実行したければ、以下のコマンドで一発でWebSocketエコーサーバーが起動します。(node.js環境が必要です)
npx blockly-with-websocket ja
起動したらブラウザを立ち上げて http://localhost:8080/ へ繋ぐと、Blocklyのプログラミング画面が表示されます。ここでWebSocketでエコーのクライアントを実装して、実行する事が出来ます。(サンプルコードはGitHubに画像で貼り付けてあります。)
なお、記事が長くなりWebSocketの話は、次回にしましたので、上記コードはこの記事では使いません。この記事では、一般論としてのカスタムブロックの作り方を説明しています。この記事で説明しているコードは上記とは別のGitHubリポジトリに置いています。ただし、公開しているコードは、このページの遥か下まで読み進めてできあがる上がる完成品で、記事自体は、どうやってそれを作るかをチュートリアル形式で段階を追って説明していますので、まずは記事を読み進めてください。
- このチュートリアルで作るコード
カスタムブロックの作成
では、まずWebSocketのブロックを作るという前に、一般論としてどのようにカスタムブロック(ユーザが独自に定義したブロック)を作るのかを説明します。
Blockly Developer Tools
Blocklyではブロックの定義や機能の実装などは、XMLやJSON、JavaScritpでできますので、基本的にはリファレンスを読んで、メモ帳か何かで実装していけばよいのですが、Blocklyでは簡単にブロックの定義を行うためのツールBlockly Developer Toolsを用意しています。
このツールを使うとBlocklyのカスタムブロックの作成がBlocklyでできます。なので、このツール自体、Blocklyの配布物のdemoフォルダにも入っています。
画面は大きく四つの部分に分かれています。左側がブロック定義を実装する画面、右側が上から順に、作ったブロックの***プレビュー (Preview)***と、ブロック定義 (Block Definition)、***動作の定義 (Generator stub)***です。
ブロック定義はJSONかJavaScriptから選べます。また動作の定義はJavaScript以外にも各種言語が選べます。ブラウザ上で動作させるならJavaScript一択だと思いますが、Blockly自体は、言語非依存で、ブロックで作られたプログラムを様々な言語にエクスポートする事ができるように設計されています。そうそう、言語非依存と言えば、Blocklyはプログラミング言語だけでなく、自然言語にも非依存であり、様々な言語に対応しています。プレビュー (Preview)の所にあるLTRをRTL**にすれば、アラビア語のような、右から左に書く言語でのプレビューもできます。
ブロックの定義
初期画面ではブロックの定義として
- name
- inputs
- input形式
- 他ブロックとの接続形式
- tooltips
- help url
- colour
の項目があります。それらを定義する事でカスタムブロックを作っていきます。
name
これはブロックの識別に使われるIDで、他と被らない名前を付けましょう。後で、ブロックが増えた時に、そのブロックが何なのかが分かる名前にするとよいでしょう。
inputsとfield
このブロックに対する入力を定義します。「入力 - input」とありますが、ブロックの見てくれ全般の定義をここで行います。inputに入れ込むブロックは左側ツールバーのinputにまとまっています。
ブロックは三種類、値とるvalue input、構文をとるstatement input、何の機能もなさそうなdummy inputです。このinputにはfieldsを入れ子にする事が出来ます。
HTMLを書いたことのある方でしたら、inputというと、<form>
を連想すると思いますが、それと似ています。
では、実際に組み合わせてみましょう。三種類のinputに適当なfieldをくっつけた例を以下に示します。
このようにvalue input、statement input、dummy inputはそれぞれ、異なるブロックの外形になります。前者の二つは他のブロックを繋げる事が出来ます。
ではinputsの下にある***[automatic] inputs***を変えると何が起こるのでしょうか。このセレクトは三つの値を取る事が出来ます。
- automatic
- external
- inline
これをinlineに変えると、ブロックのプレビューは以下のように変化します。
この例だと、些細な違いしかありませんが、value inputが複数ある場合、externalだと接続点は縦に並びますが、inlineだと横に並びます。機能の違いはありませんが、前後の文字との座りの良さで決めると良いでしょう。
connection
すでに、Blocklyのブロックには二つの形がある事に気づいていると思います。1つは、上と下に凸凹があるタイプ、もう一つは左側にジグソーパズルのような突起があるタイプです。
前者が構文ブロックで、なんらかの処理を行うブロックになります。後者は値ブロックで、なんらかの値を返すブロックです。ここで、何らかの値や何らかの処理は、みなさんがJavaScriptで定義する訳で、値ブロックの中で何らかの処理を行ったりといった事は自由自在です。その前提の元、構文ブロックはプロシージャそのもので、値ブロックは式の右辺にくる値だと理解しておくと良いでしょう。
さて、今作ったブロックのプレビューを見てみると、この二つのどちらでも無い事に気づきます。突起が上下にも左にもありません。このままでは、他のブロックにくっつける事ができません。
このブロックを構文ブロック(突起が上下)にするのか、値ブロック(突起が左)にするのかは、[no connection]というセレクトで指定できます。ブロックの性質に応じて指定しましょう。
type
すでに気づいたかもしれませんが、接続タイプを← left outputを指定した時に、ブロック定義にoutput typeという項目が現れます。
このtypeに接続できるブロックはツールバーのtypeのカテゴリにあります。
これをoutput typeに接続する事で、このブロックがどのような型のデータを吐き出すのかを定義できます。デフォルトはanyで、型付けはされていません。Stringにすれば、この値ブロックは文字列を返すという事を宣言でききます。
これが何の役に立つのでしょうか?それを考えるには、inputの方にもtypeがあった事を思い出してください。
例えばvalue inputのtypeにstringを設定したとします。この場合、その接続点にくっつけられるブロックはoutput typeにstringをとる値ブロックのみに制限されます。Blocklyが翻訳され実行される言語(JavaScript等)は、基本的には型付けが弱い言語ですが、Blocklyはブロックを接続する制約として、最低限の型チェックの機能を持っている訳です。
Colour
なぜ英国英語の綴りなのかは分かりませんがブロックの色の事です。
色はHSV系で色相だけコントロールできるようになっています。元々用意されているブロックは以下の様にカテゴリが分けられており、それぞれシンボルカラーを持っています。自分で作るブロックが、それらどれかのカテゴリに入るなら、同じ色にすると良いでしょうし、それとは別に、独自のカテゴリとしてまとめ長ければ、デフォルトで使われていない色にすると良いでしょう。
ブロック定義
ビジュアルプログラミングの最大の懸念点は、プログラムが絵だけど、どーやって保存するの?って所だと思います。Blocklyではブロック定義は、JSON形式、あるいはJavaScript形式でエクスポートする事が出来ます。例えば、上記のブロックの定義は以下のようなJSON形式で表されます。
{
"type": "my_block",
"message0": "値ブロックを連結できます %1 構文を入れ子にできます %2 定数入力に使います %3 %4 %5",
"args0": [
{
"type": "input_value",
"name": "value_block"
},
{
"type": "input_statement",
"name": "statement_blocks"
},
{
"type": "field_checkbox",
"name": "input_check",
"checked": true
},
{
"type": "field_input",
"name": "input_text",
"text": "default"
},
{
"type": "field_number",
"name": "input_num",
"value": 0,
"min": 0,
"max": 5
}
],
"output": null,
"colour": 210,
"tooltip": "",
"helpUrl": ""
}
このJSONファイルを保存しておけば、別のマシンや環境から呼び出して使う事が出来ます。BlocklyではこのJSON形式の他、JavaScript形式でもエクスポートする事が出来ます。どっちが良いかは分かりませんが、ファイルとして保存して置く場合や、使うときに一部を書き換えたりしたい場合はJSONの方が使い勝手が良いでしょうし、単にプログラム上に展開したいだけならJavaScirpt形式の方が<srcipt>
で読み込むだけですので、手間が省けます。
動作の定義
ブロックは前節で説明したみてくれの定義の他にもうひとつ、動作の定義があります。前者はプログラミング言語非依存の部分で、後者が依存する部分です。定義が二ヵ所に分かれてちょっとややこしいのですが、この仕組みによって、言語に依存する部分をなるべく小さくするようにしています。
この部分は、エクスポートする言語毎につくる必要があります。もし作りたいシステムがJavaScriptだけで良いやという事でしたらJavaScriptのコードだけかけば良いですし、Pythonもという事なら、Pythonのコードも書かなければなりません。
- JavaScript定義の書き出し
Blockly.JavaScript['my_block'] = function(block) {
var value_value_block = Blockly.JavaScript.valueToCode(block, 'value_block', Blockly.JavaScript.ORDER_ATOMIC);
var statements_statement_blocks = Blockly.JavaScript.statementToCode(block, 'statement_blocks');
var checkbox_input_check = block.getFieldValue('input_check') == 'TRUE';
var text_input_text = block.getFieldValue('input_text');
var number_input_num = block.getFieldValue('input_num');
- Python定義の書き出し
Blockly.Python['my_block'] = function(block) {
var value_value_block = Blockly.Python.valueToCode(block, 'value_block', Blockly.Python.ORDER_ATOMIC);
var statements_statement_blocks = Blockly.Python.statementToCode(block, 'statement_blocks');
var checkbox_input_check = block.getFieldValue('input_check') == 'TRUE';
var text_input_text = block.getFieldValue('input_text');
var number_input_num = block.getFieldValue('input_num');
あれ?
両方JavaScriptじゃんと思われるかもしれませんが、その両方JavaScriptで正しいです。BlocklyはHTML+JavaScriptで全て動作します。あくまでこれはエクスポートする部分であり、エクスポートされるコードはPythonですが、エクスポート機能のコードはJavaScriptで実装されています。当然といえば当然ですね。
では、それに続く行を見てみましょう。
// TODO: Assemble Python into code variable.
var code = '...';
この***...の部分に、JavaScriptならJavaScriptのコード、PythonならPythonのコードを文字列として***書いていくのです。
そう、Blocklyの本質は、文字列で書かれたプログラムを切った貼ったする事なんです。
では、二つの数を取り平均を返すブロックを考えてみましょう。定義は以下のようになります。
練習:平均値出力カスタムブロックを作る
では、実際に Blockly Developer Tools を使ってカスタムブロックを作ってみましょう。画面は3つのタブ、Block Factory、Block Exporter、Workspace Factoryに分かれています。それぞれの機能を簡単に理解しておきましょう。
タブ | 説明 | |
---|---|---|
1 | Block Factory | カスタムブロックの定義 |
2 | Block Exporter | カスタムブロック定義のダウンロード |
3 | Workspace Factory | プログラミング画面の設定と定義のダウンロード |
では、Block Factoryタブで以下の仕様のブロックを定義してみましょう
- 2つの数値を入力にとり平均値を算出し、それを標準出力に出す
このプログラムを実現するために、2つのカスタムブロックを作りましょう
- 2つの数値の平均値を返す値ブロック
- 与えられた値を標準出力に出力する構文ブロック
値ブロックの定義
では、まず平均値を算出するブロックを作りましょう。ブロックにどんな説明を載せるかは、個人のセンス次第ですが、以下のように作ってしまうと、あまりよろしくないかもしれません。
普段、普通のプログラミング言語やエクセル関数等を触っている人であれば、簡潔で分かりやすいのですが、ブロックプログラミング的か?と言われると、違うような気がします。では次のはどうでしょうか?
これならScratch大好き小学生でもすんなり理解できそうですね。ともあれ、この部分はデザインとかセンスの話で、ルールではありません。
このブロックの定義は次の通りです。
このブロックは二つの値ブロックの接続点を持っています。それぞれNumber型が接続できるようになっています。型を決めておく事により、その接続点には、Number型のoutput typeを持つ値ブロックしか接続できなくなります。ユーザがString型のブロックをくっ付けようとしても、繋がりません。
定義が出来たらsaveボタンもしくはupdateボタンを忘れずに押しておきましょう。ブラウザのCOOKIEに保存されるようです。(ですので、保存性はあまりあてにならないかも)
さぁこのブロックの動作を定義しましょう。まずはGenerator stubに表示されているスケルトンコードを見ていきましょう。
Blockly.JavaScript['average'] = function(block) {
var value_v1 = Blockly.JavaScript.valueToCode(block, 'v1', Blockly.JavaScript.ORDER_ATOMIC);
var value_v2 = Blockly.JavaScript.valueToCode(block, 'v2', Blockly.JavaScript.ORDER_ATOMIC);
// TODO: Assemble JavaScript into code variable.
var code = '...';
// TODO: Change ORDER_NONE to the correct strength.
return [code, Blockly.JavaScript.ORDER_NONE];
};
Blockly Developer Toolsの画面上では、スケルトンコードを閲覧するだけで、改変する事は出来ません、後で、jsファイルにエクスポートしたものを修正する事になります。エクスポートの手順は後述するとして、ここでは、まず動作コードについて考えてみましょう。
二つの接続点に繋がれる値は、このコード内ではそれぞれvalue_v1、value_v2という変数に格納されます。平均ですから、そられを足して2で割ればよいわけですが、そのコードをvar code = '...';
に書いていきましょう。
var code = (value_v1 + value_v2)/2;
これは間違いです。なぜだか分かりますか?codeにはコードを文字列として入れなければなりません。では次のコードはどうでしょうか?
var code = '(value_v1 + value_v2)/2';
これも間違いです。変数はこの時点で展開してやらなければなりません。では正解は何でしょうか?
var code = '(' + value_v1 + '+' + value_v2 + ')/2';
ちょっとややこしいですが、こんな感じで書きます。もしあなたが、かつてPerlやPHPで動的に変化するJavaScriptプログラム付きのHTMLを吐き出すコードを書いていたら懐かしいと感じるかもしれません。
構文ブロックの定義
次は、与えられた値を標準出力に出力する構文ブロックを作ってみましょう。
値を1つ取るブロックで以下のように定義できます。
動作定義コードは次のようになります。(繰り返しになりますが、スケルトンコードを書き換えるのは、次の定義をダウンロードした後になります)
Blockly.JavaScript['console_log'] = function(block) {
var value_msg = Blockly.JavaScript.valueToCode(block, 'msg', Blockly.JavaScript.ORDER_ATOMIC);
// TODO: Assemble JavaScript into code variable.
var code = 'console.log(' + value_msg + ');';
return code;
};
さて、これで、平均値を出力する為の2つのブロックが完成しました。このブロックを使った、みなさん独自のブロックプログラミング環境を作ってみましょう。
繰り返しになりますが、Blocklyはブロックプログラミングをする環境ではありません。それに対してScratchはブロックプログラミングをする環境です。Scratch3.0はBlocklyが元になっています。つまり、BlocklyはScratch3.0をぶちのめすような、新たなブロックプログラミングツールを作るためのツールです。(もしくはScratch3.0をHackするにも使えるかもしれませんね。)
Block Exporter
さて、話が多少前後しましたが、カスタムブロックの定義をダウンロードしましょう。Block Exporterタブに切り替えると、3分割された画面が表示されます。
-
Block Selector
- これまでに作ったカスタムブロックのリストです。
-
Export Settings
- エクスポートする定義ファイルのファイル名等を指定します。
-
Export Preview
- エクスポートされる定義ファイルのプレビューです。
それでは、Block Selectorで、平均値出力の為に作った2つのブロックを選択しましょう。次にExport Settingsの***Block Definitions(s)***と、***Generator Stub(s)***に適当なファイル名を入れます。ここでは次のようにしています。
ファイル名 | |
---|---|
Block Definitions(s) | average_blcok.js |
Generator Stub(s) | average.js |
ファイル名を指定したらExportボタンを押せば、2つのファイルがダウンロードされますので、適当なフォルダをつくって保存しましょう。
前の節で説明しましたが、動作定義の実装を行わなければなりません、average.jsを適当なエディタで開いて、codeの部分を書き換えましょう。
var code = '(' + value_v1 + '+' + value_v2 + ')/2';
var code = 'console.log(' + value_msg + ');';
Workspace Factory
さて、最後のタブWorkspace Factoryに進みましょう。これまでに、カスタムブロックを作成して、その定義ファイルをダウンロードしました。次に、そのカスタムブロックを含んだ、ブロックプログラミング画面を作りましょう。
画面右のPreviewにありますが、ブロックプログラミングの画面はScratchでもおなじみですが、左側に使えるブロックのリスト(ツールボックス - 灰色の部分)、右側がプログラミング領域(ワークスペース - 白い部分)に分かれています。まずはツールボックスを作っていきましょう。
画面中央のアイコン[+]ボタンをクリックしてNew Categoryを選び、新しいカテゴリを作ります。名前を入れろと出てきますので、ここでは仮にMy Blocksとしましょう。(本来ならば、機能別にブロックのカテゴリを分けるべきです。)
また、[Edit Categry]ボタンをクリックすると色(Colour)の設定が出来ます。
さて、このカテゴリに所属させるブロックを選びます。Edit側のツールボックスにあるBlock libraryカテゴリに、全てのカスタムブロックが入っていますので、そのうち、使いたい物をワークスペースの方にドラックします。この時デフォルトで組み合わせて表示したいものは、組み合わせても構いませんが、ここでは、平均値算出ブロックと標準出力ブロックの2つを組み合わせずに登録します。
次に、セパレーターを加えてみます。また[+]をクリックして、今度はSeparatorを選びます。My Blocksの直下に区切り線が引かれました。
これだけですと、カスタムブロック2つしかないワークスペースですので、予め用意されている、基本的なブロックも追加しましょう。[+]をクリックして、Standard Toolboxを選びます。そうすると、一気に8個のカテゴリが登録されます。
さて、次にワークスペースをデザインしていきます。画面左側Editの所にタブが2つあり、今はToolboxという方が選ばれていますね?
これをWorkspaceに切り替えましょう。画面中央に沢山のチェックボックスが表示されます。
基本的にはデフォルトのままでも良いのですが、Zoomはチェックしておいた方が良いかもしれません。ブロックの拡大縮小が出来るようになります。後は好みですがGridもチェックすると、ワークスペースが方眼紙みたいになります。結果はPreviewにすぐさま反映されますので、それをチェックしながら調整してください。またPreviewのMy Blocksをクリックして、きちんとカスタムブロックが登録されている事も確認しましょう。
なお、この画面にはもう一つの機能があります。それは、デフォルトでワークスペースに表示して置くブロックの指定です。
Edit側のタブがWorkspaceになっている事を確認して、Block Libraryから平均値算出の値ブロックをワークスペースに配置しましょう。
こうするとプレビューの方にも、反映されます。
何か、ユーザにデフォルトで提示したいブロックがある場合に使える機能です。
さて、ツールボックスとワークスペースをデザインしましたので、その定義ファイルをダウンロードしましょう。画面上部にある[Export]ボタンをクリックしてAllを選びます。
ファイルが3つ順番にダウンロードされます。保存するファイル名を聞かれますが、デフォルトのままで良いでしょう。さて、これで全ての定義ファイルがダウンロードできました。
ブロックプログラミング画面の作成
ここまでの手順で定義ファイルが全て揃いましたが、肝心のHTMLファイルがありません。Scratchのように、ユーザはブラウザ上でブロックプログラミングをしますので、その画面はHTMLページとして書かなければなりません。最後にそれを作ってみましょう。
大まかな手順としてはHTMLファイルを作って、そこに、これまでにダウンロードしたファイル群を読み込んでいきます。ブロック定義に関してはaverage.jsとaverage_blcok.jsですので<script>
で読み込めばOKです。workspace.jsはツールボックスやワークスペースを画面上に配置するメインのプログラムですので、お好みに応じて<script>
で読み込むか、HTMLファイルの中にコピペ移植すれば良いでしょう。
問題はXMLファイルです。画像・動画・JavaScript・CSSはタグを使って簡単に読み込めますが、XMLファイルはできません。Ajaxを使ったりと、すこし手間がかかります。それが原因かどうかは分かりませんが、 公式ページでは、「XMLファイルはHTMLのどこかに直接埋め込んどいてね」 と書いてあります。
私も、埋め込んで作っていましたが、この定義、非常に長くなって、メンテナンス性が悪い上、Blockly Developer Toolsからエクスポートしたそのままの状態で使えるものですので、何とか、外部ファイルとしておきたい。
という事で、Ajaxよりも最近の規格であるFetch APIを使って読み込んでみようと思います。
index.htmlの実装
では、まずindex.htmlを作ってみましょう。ブロック定義・ワークスペース定義・ツールボックス定義ファイルのあるフォルダに、index.htmlを作ります。エディタは何でも構いませんが、Fetch APIをテストするのにWebサーバが必要になります。 Microsoft Visual Studio Code なら、ボタン一発でWebサーバが立ち上がりますのでオススメです。
まずはHTMLの雛形を作りましょう。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My Custom Blocks - Blockly</title>
</head>
<body>
</body>
</html>
まぁ誰が作ってもこんな感じですね。
HTMLとして必須なのは、ユニークなIDの付いた<div>
です。その<div>
の中にツールボックスとワークスペースが表示されます。この記事はあくまで習作の解説という事で、デザインなんてどうでもよいのですが、それでもサクッと綺麗なページが作れるBootstrapを使ってデザインしましょう。ページを左右で分割して、左側にツールボックスとワークスペース、右側はプログラムの実行ボタンと、確認の為のJavaScriptコードを表示するボタンを作りましょう。(それら機能の実装は後で説明します。)
下記に <body>
内のサンプルコードを示します。
<div class="container">
<div class="row">
<div class="col-8" id="blocklyDiv"></div>
<div class="col-4">
<button id="showCode" class="btn btn-success">JavaScriptを表示する</button>
<button id="execCode" class="btn btn-primary">実行する</button>
<pre id='jsCode'></pre>
</div>
</div>
</div>
<div>
のid属性に設定した値は後々使いますので覚えておきましょう。特にプログラミング画面のid(blocklyDiv
)は重要です。
次に、<head>
タグも作っていきましょう。まずはBootstrap関連から。CSSファイルとJavaScriptファイルをリンクします。JavaScriptの方は、 Bootstrap Native の方に差し替えています。jQueryフリーのバージョンで、ほぼ普通のBootstrapと互換性があります。
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS"
crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap.native/2.0.15/bootstrap-native-v4.min.js"></script>
いずれもCDN (Content Delivery Network) から読み込みますので、何かをダウンロードしたりする事は不要です。
次にBlockly関連のファイルをリンクします。まずは基本機能と基本ブロックの定義をリンクしましょう。
<script src="https://cdn.jsdelivr.net/gh/google/blockly@latest/blockly_compressed.js"></script>
<script src="https://cdn.jsdelivr.net/gh/google/blockly@latest/blocks_compressed.js"></script>
次に、ブロックプログラミングのエクスポート先の言語に関連したJavaScriptファイルもリンクします。今回はJavaScriptのみなので以下のファイルをリンクするだけでOKです。ちなみに、他には、python_compressed.js、php_compressed.js、dart_compressed.js、lua_compressed.jsがあります。他の言語にもエクスポートする際は、必要に応じてリンクを加えてください。もちろんカスタムブロックの動作定義も、複数言語に対応しなければなりません。
<script src="https://cdn.jsdelivr.net/gh/google/blockly@latest/javascript_compressed.js"></script>
次も言語の設定なのですが、こちらは自然言語の方です。様は日本語・英語というような切り替えです。Scratchも沢山の言語に対応していますが、Blocklyも多くの言語に対応しています。今回は日本語ですので以下のようになります。
<script src="https://cdn.jsdelivr.net/gh/google/blockly@latest/msg/js/ja.js"></script>
もし、バスク語版を作りたければ、URLを変えるだけです。
<script src="https://cdn.jsdelivr.net/gh/google/blockly@latest/msg/js/eu.js"></script>
次に、カスタムブロックの定義ファイルをリンクします。
<script src="./average_block.js"></script>
<script src="./average.js"></script>
さらに、残るファイルworkspace.jsをリンクしましょう。このJavaScriptファイルがメインですので、他のJavaScriptファイルやHTMLファイルが読み込んだ後に実行するようにdefer属性を付けておきましょう。
<script src="workspace.js" defer></script>
最後に、<div>
タグの高さをCSSで規定しておきましょう。とりあえずは画面いっぱいに表示したいので、以下の様なCSSをページに追加しておきます。
<style>
#blocklyDiv {
height: 100vh;
}
</style>
これでHTMLは完成です。では早速これをブラウザで見てみましょう。ローカルファイルとして直接ブラウザで開いても(この時点では)問題ありませんが、折角ですのでWebサーバ経由で見てみましょう。
Visual Studio Codeを使っている場合、Webサーバの立ち上げは簡単です。HTMLファイルのあるフォルダを開いた状態で、画面下の方にあるGo Liveというボタンをクリックすルだけで、WebサーバとWebブラウザが立ち上がります。
なお、Visual Studio Code使っていない場合で、ご使用のエディタに同様なWebサーバ機能が無い場合は、Chromeの拡張機能を使う手があります。
workspace.js
さて、エラーがでてしまいましたが、実はworkspace.jsは、この時点では、まだ未完成です。ではエラーの出ている36行目を見てみましょう。
/* Inject your workspace */
var workspace = Blockly.inject(/* TODO: Add ID of div to inject Blockly into */, options);
なにやらToDoが潜んでいました。ここの部分に<div>
のIDを入れましょう。
/* Inject your workspace */
var workspace = Blockly.inject('blocklyDiv', options);
さらにHTMLタグではリンクできないXMLファイルもJavaScriptのFetch APIを使ってページ内に読み込むようにしましょう。
Fetch APIの解説は MDNにあります
。
そこで例示されているコードを見てみましょう。
fetch('http://example.com/movies.json')
.then(function(response) {
return response.json();
})
.then(function(myJson) {
console.log(JSON.stringify(myJson));
});
なるほど。通信は非同期で行われますが、Promiseに従って、thenで繋いでいくことで、順序処理を書けるようになっています。
では、こんな戦略でソースコードを作っていきましょう。
Promise.all(
//ToDo: 全てのXMLファイルの読み込み
).then(
//ToDo: XMLをDOMツリーに加える
).then(
//元々workspace.jsにあった処理をここへ
);
ではまずXMLファイルを読み込む部分を作りましょう。読み込みたいXMLファイルを羅列して、それぞれに対して同じ処理をする訳ですからArray.map()
を使うと良いでしょう。
Promise.all(
["workspace.xml", "toolbox.xml"].map(async file => {
return fetch(file).then(
(res) => {
return res.text();
}
);
})
)
XMLファイルのパスは配列に入れておき、それをmapします。反復処理の部分は、XMLファイルのパスがfile変数として渡されますので、fetch(file)
として、そのパスのファイルを持ってきます。レスポンスがresに格納されて帰ってきますので、その中のbody部分、すなわちXML文書をres.text()
で抜き出して返します。なお、このres.text()
はPremiseですので、map()
はXMLファイル数分のPromiseの配列を返します。promise.all()
は複数のPromiseを入力にとり、全て解決している場合、単一のPromiseを返します。
つまり、XMLファイルが全て読み終えた段階で、最初のthen()
に処理が移ります。XMLファイルが読み終わりましたので、最初のthen()
では、XMLをHTMLのDOM(<body>
の直下)にくっつけます。
then((xmls) => {
xmls.forEach((xml) => {
var parser = new DOMParser();
var doc = parser.parseFromString(xml, "application/xml");
document.body.appendChild(doc.documentElement);
});
})
これで、XMLファイルをJavaScriptから参照できるようになったので、あらかじめworkspace.jsのコードを二番目のthen()
に入れましょう。
長くなるので省きますが、完成したworkspace.jsのコードはGISTに置いたので見てください。
さぁ、これでもう一度、ブラウザでindex.htmlを見てください。なお、かならずWebサーバ経由で見てください。Visual Studio Codeのgo liveを使っている場合は http://127.0.0.1:5500/index.html です。
成功すれば以下の様なWebページが表示されます。
はじめてのブロックブログラミング
さて、できた事とできていない事をまとめておきましょう。
- できた事
- カスタムブロックの定義(構文ブロック・値ブロック)
- ツールバーの定義
- ワークスベースの定義
- 定義ファイルの読み込み
- プログラミング環境の構築
- できていない事
- ブロックプログラムをJavaScriptにエクスポート
- ブロックプログラムの実行
では、一つずつ潰していきましょう。まずはワークスペースに作ったブロックからJavaScriptコードを吐き出す部分です。
JavaScriptコードのエクスポート
ワークスペースはworkspace.js内でworkspace
変数で保持しています。これをJavaScriptコード(の文字列)に変換するには、workspaceToCode
関数を使います。
Blockly.JavaScript.workspaceToCode(workspace);
ここで得られたコードをindex.htmlで定義している<pre>
タグに突っ込めば、画面にJavaScriptコードを表示できます。コードはworkspace.jsの二番目のthen()
の中の末尾に加えていきましょう。
const pre = document.getElementById('jsCode');
pre.innerHTML = Blockly.JavaScript.workspaceToCode(workspace);
なお、もう一つ、無限ループの取り扱いを先に指定しておかなければいけないようです(未検証)。とりあえず以下のようにしてお来ましょう。
Blockly.JavaScript.INFINITE_LOOP_TRAP = null;
余談ですが、公式ドキュメントでは次のようなコードを推奨しています。( 詳しくこちらを参照 )
window.LoopTrap = 1000;
Blockly.JavaScript.INFINITE_LOOP_TRAP = 'if(--window.LoopTrap == 0) throw "Infinite loop.";\n';
var code = Blockly.JavaScript.workspaceToCode(workspace);
グローバルスコープでカウンターを用意して、ループの中でカウンターを減らしていき0になったら、エラーを吐いて止まるという仕組みの様で、おそらくINFINITE_LOOP_TRAP
は、ループの中に自動的に展開されるコード断片ではないでしょうか(未検証)。1000で十分なのかどうかはよくわかりませんが、実際に何かを作る時は、考慮すべきかもしれません。
さて、最後に、ここで作ったJavaScriptのソースコード表示機能を、ボタンに割り当てます。index.htmlにはid="showCode"
というボタンがあるので、それに割り当てましょう。全体のコードを以下に示します。
function showCode() {
Blockly.JavaScript.INFINITE_LOOP_TRAP = null;
const pre = document.getElementById('jsCode');
pre.innerHTML = Blockly.JavaScript.workspaceToCode(workspace);
}
document.getElementById('showCode').addEventListener('click', showCode, false);
では、もう一度Webブラウザに戻って、ページを更新してみてください。ワークスペースには、何も接続されていない平均算出の値ブロックが一つ表示されています。
では、この状態で[JavaScriptを表示する]をクリックしてみましょう。その下に以下の様に表示されたと思います。
(+)/2
Blocklyは文字列の切った貼ったをするだけなので、もし接続点になにもくっついていない場合、空文字、nullとして評価されるようです。(undefinedでエラーも出ません)
本来なら、必須フィールド等の設定があると便利なのですが、Blockly Developer Toolsではそのような設定は出来ないようです。ただ、 Blockly.Fieldクラスのコンストラクタの引数にopt_validator
というのがあるので、自分で値の検証をする事が出来るのかもしれません(未検証)。
では、数値を接続点に入れてみましょう。数値はMathカテゴリにあります。ただ単に数値を入れるだけだと面白くありませんので、数値を吐き出すブロックでしたら他のブロックを接続しても構いません。例えばここでは平方根のブロックも接続してみます。
これに対して[JavaScriptを表示する]をクリックすると次のコードが表示されます。
(15+(Math.sqrt(9)))/2;
やっと式らしくなってきました。ただし、まだこれでは『プログラム』にはなっていません。最後にこの値を標準出力に出力しましょう。
My Blocksカテゴリから標準出力ブロックを引き出して、平均値ブロックに接続してください。
さぁ、これでJavaScriptコードを見てみましょう。
console.log(((15+(Math.sqrt(9)))/2))
やっと、実行できそうなコードが出てきました。ただ、実際にはQiitaのようにソースコードに色付けされないので、白地に黒字の味気ないソースコードが表示されました。
Syntax Highlighter
完全に脱線ですが、ソースコードの色付けもやってみましょう。ソースコードの色付けはSyntax Highlighterと呼ばれる機能で、ここではその中でもJavaScriptから使いやすいhighlighter.jsを使ってみましょう。更新するファイルはindex.htmlとworkspace.jsです。
まずはindex.htmlの<head>
で、CSSとJavaScriptを読み込んで起動しましょう。<title>
のすく下に以下の3行を足します。
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/styles/atom-one-dark.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
CSSファイルを差し替えると、コラースキームを変える事が出来ます。対応言語とカラースキームのリストは以下のデモページとCDNのページを見てください。
- デモページ:https://highlightjs.org/static/demo/
- CSSファイルURL一覧:https://cdnjs.com/libraries/highlight.js/
また、<pre>
もすこし改良しましょう。<pre>
の中に<code>
を加えます。id='jsCode'
も<code>
の方に付け替えます。
<pre><code class="JavaSciprt" id='jsCode'></code></pre>
次にworkspace.jsを修正します。showCode()
関数の末尾に以下のコードを付け加えてください。
hljs.highlightBlock(pre);
コードの色付けは静的に行われるため<pre>
を書き換えたタイミングでhljs.highlightBlock()
関数を呼んで、明示的に色付けをしてやらなければなりません。
さぁ、これで次のような色付けされたJavaScriptコードが表示されるようになったと思います。
プログラムの実行
ついに、プログラムを実行できるところまでやってきました。今回の実行言語はJavaScriptで、さらに前節で、ブロックをJavaScriptコードの文字列に変換する機能が完成しました。後は単にそのJavaScriptコードをeval()
関数に渡して、実行してもらうだけです。では、実行の機能を[実行する]ボタンに割り当てましょう。
function execCode() {
Blockly.JavaScript.INFINITE_LOOP_TRAP = null;
try {
eval(Blockly.JavaScript.workspaceToCode(workspace));
} catch (e) {
alert(e);
}
}
document.getElementById('execCode').addEventListener('click', execCode, false);
さぁ、Webブラウザで[実行する]ボタンを押してみましょう。
どうですか?標準出力に9と出力されたでしょうか?こんだけ長いページを読んできた末の成果物としてかかなり地味ですが、二つの数値をとって平均値を標準出力に出すカスタムブロックがきちんと動作しました。
折角ですから、元々あるブロックとも組み合わせてみましょう。
平均値を変数に代入しておき、それを標準出力に出力すると共に、元々あるアラートで表示する機能を使って、表示しています。
この様なアラートが出てきましたか?
今回はここまでにしておきます。長い記事ですので、ここまで読んでくださった方、大変ありがとうございます。私自身あまりにも記事が長すぎで息切れししております。この記事が、MITのScratchをぶちのめす何かをつくる一助になれば幸いです。
この記事のまとめ
WebSocketの実装について解説しようと思っていましたが、全然そこまでたどり着きませんでした。まずはBlocklyでカスタムブロックをどのように作るのか、そしてそれを使ったブロックプログラミング環境をどう作るのかの説明は、一通りできたと思います。
次回は、WebSocketをブロックとして実装する所の解説をします。