SPARQLthon/togostanza js
提供:TogoWiki
Ruby 版と JavaScript 版の TogoStanza の入手方法については http://togostanza.org/documents/development.html をご参照下さい。
目次
|
注意
- 現在 ts v.0.0.8以前とv0.0.9以降の記述が混在してます
- v0.0.8以前:Web components v0 と jQuery
- ページ全体でのjQueryの競合に注意が必要
- web components v0 非対応ブラウザ (iOS等) で表示されない
- v0.0.9以降:Web components v1 (jQuery無し)
- jQuery を使っていないので、親要素で jQuery を使っても競合しない
- Stanza 内で jQuery のコードを使う場合には、別途 _header.html で jQuery を読み込む必要がある。その場合は競合に注意
- iOSでも表示できる
- v0.0.8以前:Web components v0 と jQuery
Ruby版との比較
長所
- 習得言語がJavaScriptだけでよい
- 依存環境が少なく環境構築が楽(Ruby版は依存パッケージのインストールが必要)。
- 公開にあたって独自サービスが不要(Ruby版はRackサーバ上で動作し、Apacheの場合にはさらにPhusion Passengerが必要)。
- iframeを使用しないので、SEOでスタンザコンテンツだけが検索にヒットしてしまうことがない。
短所
- SPARQL発行先エンドポイントがCORS対応している必要がある。エンドポイントがCORS対応されているか
- スタンザを埋め込むページ全体でjsの競合に注意する必要がある。 jsの競合, Web Components v1 対応と jQuery 排除
- 高機能な可視化ライブラリで表示が崩れる場合がある描画ライブラリ
- ifameの替わりにWeb Components(ShadowDOM, HTML imports)を使用しているため、対応が完全ではないブラウザ等では Polyfill (webcomponentsjs) 等の代替え技術により表示するので若干の表示崩れがある
- Shadow DOM v0 ブラウザ対応状況
- ts v0.0.8まで
- ブラウザがサポートしないので、ほぼPolyfillで対応
- Shadow DOM v1 ブラウザ対応状況
- ts v0.0.9から
- HTML imports ブラウザ対応状況
- HTML imports は非推奨になり、ブラウザでサポートされなくなるので、 Polyfill で対応 (Chromeは74で HTML imports を削除)
- Polyfill は webcomponentsjs v1.x系のみ。 v2.xでは HTML imports の Polyfill が削除された
- 今後は ES modules, import() に移行?
- ts を ES modules で実装しなおすと、今まで <link rel="import" href="...> としていたところを <script type="module" src="...> と変えなきゃならないので、互換性が無くなる
- そもそも Polyfill 上で HTML imports の代替は ES modules で実装されている
- HTML imports は非推奨になり、ブラウザでサポートされなくなるので、 Polyfill で対応 (Chromeは74で HTML imports を削除)
- Shadow DOM v0 ブラウザ対応状況
サンプルコード
このページに記述しているスタンザ例のコードの一部は以下のリポジトリからダウンロード可能で、動作確認ができます。 サンプルコードのリポジトリ
初めてのts
ドキュメント
スタンザ作成前の確認
スタンザを作成する前に、以下のことを確認しておく必要があります。
エンドポイントがCORS対応されているか
tsはjavascriptでエンドポイントにアクセスするため、tsを動かすWebサーバとエンドポイントのドメインが異なる場合にCross-Origin(CORS)エラーが発生します。
- 確認方法
自分でjavascriptを書いてもいいですが、最も手軽な方法として、d3sparql.jsのデモアプリが利用できます。
d3sparql.js のページ上の [ Table ] -[ d3htmltable ] をクリック。
SPARQL endpoint:の欄に確認したいエンドポイントURLを入力して、必ず値が返ってくる以下のようなクエリを実行
SELECT * { ?s ?p ?o } LIMIT 10
データが表示されればエンドポイントのCORS対応がされています。
表示されない場合、ブラウザのデバッグツールを開いて、'Access-Control-Allow-Origin'というようなエラーメッセージが出ている場合にはエンドポイントの設定が必要です。
Virtuosoの場合には右のサイト等を参考にCORS設定してください。マニュアル(英語) 三嶋さんブログ
Apacheなどのフロントサーバを介している場合にはそちらの設定が必要です。
エンドポイントがCORS対応されていない場合
外部管理のCORS対応されていない Endpoint を togostanza で呼び出したい場合は、SPARQList を立てて、経由することでアクセスできます。下記参照
- SPARQList のSPARQL クエリは SPARQList が動いているサーバー側で処理されるので、CORSエラーは出ません
発行するSPARQLが正しいか(値が返ってくるか)
エラーが発生した場合の切り分けが楽なので、スタンザで使用するクエリが間違いなく返ってくるか、事前に確認しておく方がオススメです。
インストール
tsはバイナリのモジュールをサイトからダウンロードしてPATHを通すだけです。
あとはドキュメントに沿って、一つスタンザを作成してみてtsサーバを立ち上げてみるとインストールされたことが確認できます。
$ mkdir my_provider //作業用ディレクトリを作成 $ cd my_provider $ ts new hello //"hello"という名前の新規スタンザを作成 $ ts server //tsサーバを起動(デフォルトポート8080)
この状態でhttp://localhost:8080/stanza/ にアクセスすると、スタンザリストが見えます。helloをクリックすると、スタンザ(のヘルプページ)が表示されます。
Ctrl + c でtsサーバを停止できます。
スタンザ作成
ドキュメントにあるとおり、ts new <スタンザ名>で作成コマンドを打つと、スタンザ名のディレクトリと中に自動でいくつかファイル群が生成されますので、それらを編集したりファイルを追加したりしてお好みのスタンザを作ります。
ドキュメントの最後にあるSPARQLを使用した例"Issue SPARQL query"のorganism-nameスタンザコードはこちらにあります。organism-name
How To
SPARQL resultデータの加工
stanza.query()を実行すると、SPARQLのresultは仕様で定められたjson(SPARQL 1.1 Query Results JSON Format)形式で返ってきます。
- resultデータをそのまま使用する
- resultデータからbindingsオブジェクトを辿る
- resultデータからstanza.unwrapValueFromBinding()を使用してkey-valueにデータを展開する。※この場合はcolumnの型は失われる
の三通りの扱いができます。
動作確認はサンプルコードで行えますhandling-query-result
- templates/stanza.rq
SELECT * { ?s ?p ?o } LIMIT 2
- index.js
Stanza(function(stanza, params) { var q = stanza.query({ endpoint: "http://integbio.jp/rdf/sparql", template: "stanza.rq", parameters: params }); q.then(function(data) { var raw_data = JSON.stringify(data); var bindings = data.results.bindings; var bindings_json = JSON.stringify(data.results.bindings); var unwrap = stanza.unwrapValueFromBinding(data); var unwrap_json = JSON.stringify(stanza.unwrapValueFromBinding(data)); stanza.render({ template: "stanza.html", parameters: { raw_data: raw_data, bindings: bindings, bindings_json: bindings_json, unwrap: unwrap, unwrap_json: unwrap_json }, }); }); });:
- templates/stanza.html
<style> table { width: 400px; border: 2px #2b2b2b solid; border-collapse: collapse; } th { background-color: #ffd78c; } td, th { border: 2px #2b2b2b solid; } </style> <h3>raw_data</h3> {{raw_data}} <h3>bindings</h3> {{bindings_json}} <table> <th>s</th> <th>p</th> <th>o</th> {{#each bindings}} <tr> <td> {{s.value}} </td> <td> {{p.value}} </td> <td> {{o.value}} </td> {{/each}} </table> <h3>unwrap</h3> {{unwrap_json}} <table> <th>s</th> <th>p</th> <th>o</th> {{#each unwrap}} <tr> <td> {{s}} </td> <td> {{p}} </td> <td> {{o}} </td> {{/each}} </table>
ページのイベントを受け取る
スタンザ描画後に、ユーザのクリックなどのイベントを受け取る方法を紹介します。サンプルコードevent_listener
eventを設定するにはhtmlがレンダリングされた後である必要があるので、stanza.render()と同じ階層に書いてください。
エレメントの取得にはstanza.select()を使用します。
- index.js
Stanza(function(stanza, params) { var q = stanza.query({ endpoint: "http://integbio.jp/rdf/sparql", template: "stanza.rq", parameters: params }); q.then(function(data) { stanza.render({ template: "stanza.html", }); //プルダウンリストを初期値の値にセット $(stanza.select('#current_value')).html(stanza.select('#select1').value); //use plain javascript stanza.select('#button1').addEventListener("click", function(e) { alert('button is clicked!'); }); //use jQuery (ts v0.0.8 以前) $(stanza.select('#select1')).on('change', function(e) { $(stanza.select('#current_value')).empty(); $(stanza.select('#current_value')).html(stanza.select('#select1').value); }); }); });
- templates/stanza.html
<button type="button" id="button1" value="aaa"> Click! </button> <select id='select1'> <option value='value1'>Value1</option> <option value='value2'>Value2</option> </select> <div>Current value: <span id='current_value'></span></div>
複数のSPARQLの実行
複数クエリの順次実行
あるクエリの結果を使って、次のクエリを組み立てて実行する場合、前のクエリの結果を受け取るfunctionで次のクエリを組み立てることになります。
サンプルコードはsequential-queryにあります。
- index.js
Stanza(function(stanza, params) { //一つ目のクエリの実行 var q1 = stanza.query({ endpoint: "http://integbio.jp/rdf/sparql", template: "query1.rq", parameters: params }); //一つ目のクエリの終了イベント q1.then(function(data){ //一つ目のクエリの実行結果を取得して、次のクエリのパラメータにする var first_item = data.results.bindings[0]; var subject_value = first_item.s.value; //二つ目のクエリの実行 var q2 = stanza.query({ endpoint: "http://integbio.jp/rdf/sparql", template: "query2.rq", parameters: { subject: subject_value } }); //二つ目のクエリの終了イベント q2.then(function(data){ var result_list = data.results.bindings; //結果を表示 stanza.render({ template: "stanza.html", parameters: { subject: subject_value, result: result_list } }); }); }); });
- query1.rq
SELECT ?s ?p ?o WHERE { ?s ?p ?o } LIMIT 10
- query2.rq
SELECT ?p ?o WHERE { <{{subject}}> ?p ?o }
複数クエリの並列実行
複数のクエリがお互いに関係せず並列実行できる場合にはv0.0.8 以前はjQueryのwhen()メソッドが、v0.0.9以降はPromiseが使えます。
$.when()を使った場合、then()のfunction引数には発行した数のクエリ結果の引数が渡されますが、実際のSPARQLクエリ結果はこれらの変数の配列[0]に格納されていることに注意してください。
サンプルコードはparallel-queryにあります。
- index.js
- ts v0.0.8以前
Stanza(function(stanza, params) { //コード可読性のために、クエリオブジェクトを変数に定義 var query1 = { endpoint: "http://integbio.jp/rdf/sparql", template: "query1.rq", parameters: params }; var query2 = { endpoint: "http://sparql.uniprot.org/sparql", template: "query2.rq", parameters: params }; //複数クエリの実行 var q = $.when(stanza.query(query1), stanza.query(query2)); //全てのクエリが終了した時点で実行され、発行したクエリの数の引数が渡される q.then(function(data1, data2) { //data1ではなく、data1[0]でSPARQL結果が取得できる var query1_list = data1[0].results.bindings; var query2_list = data2[0].results.bindings; var all_list = query1_list.concat(query2_list); stanza.render({ template: "stanza.html", parameters: { result: all_list } }); }); });
- ts v0.0.9以降
Stanza(function(stanza, params) { // SPARQL query 1 var q1 = stanza.query({ endpoint: "http://exapmle.org/sparql", template: "query1.rq", parameters: params }); // SPARQL query 2 var q2 = stanza.query({ endpoint: "http://example.com/sparql", template: "query2.rq", parameters: params }); var q = Promise.all([q1, q2]); q.then(function([data1, data2]){ // jQuery の $.when() と違い data1[0] ではなく、data1 で SPARQL 結果が取得できる var query1_list = data1.results.bindings; var query2_list = ......略 ....... stanza.render({ template: "stanza.html", parameters: { result: all_list } }); }); });
- query1,rq, query2.rq (どちらも同じ内容)
SELECT * WHERE { ?s ?p ?o } LIMIT 10
SPARQL以外のAPI呼び出し
jQuery
- v0.0.8以前
var q = $.ajax({ url: "http://example.of/api/url", method: 'get', data: { key1: value1, key2: value2 } });
fetch
var q = fetch( "http://example.of/api/url?key1=value1&key2=value2", { method: 'get'} );
SPARQList
- SPARQList で SPARQL 処理を全部やって、結果を受け取ってtemplateのhtmlに渡すだけの index.js
- 'POST' は SPARQList ver. 2018-02-20 以降。それ以前の ver. なら 'GET' を使いましょう
Stanza(function(stanza, params) { var formBody = []; for (var key in params) { if(params[key]) formBody.push(key + "=" + encodeURIComponent(params[key])); } var options = { method: "POST", mode: "cors", body: formBody.join("&"), headers: { "Accept": "application/json", 'Content-Type': 'application/x-www-form-urlencoded' } }; var url = "http://example.of/sparqlist/url"; var q = fetch(url, options).then(res => res.json()); q.then(function(data){ stanza.render({ template: "stanza.html", parameters: { result: data } }); }); });
Style(css)の適用
スタンザに対してStyleを適用する方法を紹介します。
デフォルトでも少しデザインが指定されています。この内容を確認したい場合には、ブラウザのデバッグツールを使用して、スタンザの構成エレメント(
shadow rootの直下)に<style>と<main>があるので、この<style>のcssを参照します。
以下に紹介するStyleの適用は、デフォルトの指定にStyleを追加する方法です。
これらは各スタンザ内だけに適用され、他のスタンザには影響しません。
templatesファイルへの記述
index.jsのrenderで指定したtemplateのhtmlファイルに<style>要素を記述すれば適用されます。
サンプルコードはこちらで確認できます。apply-style
- index.js
stanza.render({ template: "stanza.html", //指定したtemplateファイル parameters: { greeting: "Hello, world!" } });
- stanza.html
<style> table { width: 400px; border: 2px #2b2b2b solid; border-collapse: collapse; } th { background-color: #ffd78c; } td, th { border: 2px #2b2b2b solid; } </style> <table> <tbody> <tr> <th>column</th> </tr> <tr> <td>row 1</td> </tr> <tr> <td>row 2</td> </tr> <tr> <td>row 3</td> </tr> </tbody> </table>
cssファイルの適用
<style>要素ではなく、cssファイルを読み込んで指定したい場合には、次のような方法があります。
サンプルコードはこちらで確認できます。apply-css
表示templateのhtmlファイルに<style>タグを追加して、cssをインポートします。
cssのファイルは公開されている必要があり、URLを絶対パスを指定します。stanzaのassetsに置いた非公開cssもimportはできますが、stanzaの埋め込み先URLからの相対パス指定になってしまうため、うまく適用できない場合がありオススメできません。
- templates/stanza.html
<style> @import url('http://www.coolwebwindow.com/temp/source/simple/sim001_blue/src/css/common.css'); </style> <table> <a href="http://www.coolwebwindow.com/temp/source/simple/sim001_blue/src/css/common.css">Download applied css file</a> </table>
公開されたjsのロード
外部公開されたjsファイルを取り込むには、"_header.html"という名前のファイルを作成して<script>タグを記述してロードしたいjsを指定します。
d3を使用したサンプルコードはd3-sampleにあります。
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
注意点は、指定したjsはグローバル環境に適用されるため、記述したスタンザだけでなく、他のスタンザやスタンザを保有するページ全体に影響することで、お互いが競合する関係にあるjsの場合には、衝突してしまって不具合が出る可能性があります。詳しくはjsの競合も参照してください。
ロードすればindex.jsでd3のオブジェクトを使用できます。
d3の描画対象のelementについては、getElementById('chart')やjQueryの$('#chart')ではelementを拾えないため、stanza.select()を使用します。参照
- index.js
Stanza(function(stanza, params) { stanza.render({ template: "stanza.html" }); //描画用関数 引数に描画対象のelementを指定する var draw = (function(elem){ var margin = {top: 40, right: 20, bottom: 30, left: 40}, width = 800 - margin.left - margin.right, height = 500 - margin.top - margin.bottom; //(中略) //指定されたelementに描画する。 var svg = d3.select(elem).append("svg") //(中略) }); //描画の実行。elementの指定には、stanza.select()を利用する。 draw(stanza.select('#chart')); });
描画する要素をhtmlで指定します。
- templates/stanza.html
<div id="chart"></div>
非公開(独自作成)jsファイルのロード
できません。。。
スタンザのassetsディレクトリに非公開のjsを書いて_header.htmlでロードするように記述すれば出来そうな気がしましたが、スタンザを埋め込む先のURLを起点とした相対パスになってしまうので、うまく動かない場合が多く、オススメできません。
画像の配置
公開されている画像であれば<img>タグのsrcにURLを書けばよいですが、スタンザ独自の画像ファイルを使用したい場合には、assetsディレクトリを作成して画像ファイルを置くと表示templateのhtmlファイルから参照できます。
- assets/message.jpg に画像を配置
- template/stanza.html
<p>{{message}}</p> <img src="./assets/message.jpg">
スタンザの再実行(リロード)
スタンザを一度描画した後にユーザイベント(クリック)等によってコンテンツを修正するにはページのイベント受け取るで対応可能ですが、イベントによって大幅な書き換えが必要で、再度スタンザを読み込んだほうが早い、といったケースでのスタンザ更新方法を紹介します。
スタンザのリロード(技術的にはリロードではありませんが)はスタンザを埋め込んでいるページのスタンザ描画タグ(参照スタンザの埋め込み)を置き換えることで実現できます。
サンプルコードはでredo-stanza確認できます。
- template/stanza.html
Current position: {{start}} - {{end}}<br/> From: <input type="text" id="txt_from" value="{{start}}"> To: <input type="text" id="txt_to" value="{{end}}"> <input type="button" id="btn_update" value="update stanza!">
- index.js
Stanza(function(stanza, params) { stanza.render({ template: "stanza.html", parameters: { start: params.start_pos, end: params.end_pos } }); //ボタン押下時 $(stanza.select("#btn_update")).on('click', function(){ //画面で指定された値を取得 var from = stanza.select("#txt_from").value; var to = stanza.select("#txt_to").value; //値を引数に指定したタグのhtmlテキストを組み立てる var stanza_elem = '<togostanza-redo-stanza start_pos="' + from + '" end_pos="' + to + '"></togostanza-redo-stanza>' //現在の表示スタンザを置き換える var current_elem = $("togostanza-redo-stanza");//現在のスタンザエレメント idで検索してもよい current_elem.before(stanza_elem);//新しいスタンザエレメントの挿入 current_elem.remove();//古いスタンザエレメントの削除 }); });
注意!! スタンザタグの属性(attribute)を入れ替えたいだけなので、[エレメント].setAttribute('start_pos', from) と記述してもいいように思うかもしれませんが、属性が複数ある場合には挙動がおかしくなるケースがありますのでこの方法は避けるべきです(属性を一つしかとらないスタンザであれば問題無し)。
詳しくはこちらも参照してください。スタンザタグの属性値を複数変更すると、更新イベントが複数回発生する
スタンザ同士の連携
スタンザは単体で動作することを基本として設計されていますが、”あるスタンザでのイベントを機に別のスタンザの内容を操作したい”といった場合には、スタンザの再実行(リロード)と同じ方法で、操作したいスタンザのタグを入れ替えて相手のスタンザを更新(実質的リロード)させることで実現できます。
この方法以外にはスタンザ間でメッセージを送り合うようなI/Fは用意されていません。
スタンザの入れ子
_header.html で呼びたいStanzaを '<link rel="import" href="stanzaのurl">' でImportしておくだけ
スタンザの公開方法
公開コンテンツの生成
公開用コンテンツを生成するには、スタンザプロバイダのディレクトリで次のコマンドを打ちます。
$ ts build
すると、ディレクトリ配下に"dist"という名前のディレクトリが作成され、その中に各スタンザのコンテンツが作られます。
$ ts serverコマンドでもdistディレクトリは作成されますが、デバッグモードのコンテンツが出力されるため、公開する場合にはts buildコマンドを使ってください
- buildによって作成されるディレクトリ
--dist |---stanza |---index.html |---assets |---metadata.json |---apply-css(スタンザディレクトリ) | |---help.html | |---index.html | |---metadata.json | |---(以下各スタンザディレクトリ) |---
スタンザの埋め込み
ts buildで生成されるコンテンツは、html,json等の静的コンテンツですので、このままApache等のWebサーバのドキュメントディレクトリに配置しても動作します。
任意のスタンザを埋め込んだページを作成するには各stanzaのhelp.htmlが参考になります。
具体的には<link>タグでインポートしたいstanzaの場所を指し、埋め込みたい場所に<togostanza-[スタンザ名]>というタグを記述すると、スタンザが表示されます
- organism-nameとapply-styleの二つのスタンザを埋め込む例
<!DOCTYPE html> <html> <head> <script src="../assets/components/webcomponentsjs/webcomponents.min.js"></script> // ts v0.0.8 以前 <script src="https://cdn.jsdelivr.net/npm/@webcomponents/webcomponentsjs@1.3.0/webcomponents-loader.js" crossorigin></script> // ts v0.0.9 以降 <link rel="import" href="../organism-name/">//埋め込みたいスタンザのディレクトリを指定。ディレクトリ後のスラッシュを忘れると Chrome 以外では動かない <link rel="import" href="../apply-style/"> //複数ある場合には列挙する <link rel="stylesheet" href="../assets/css/ts.css"> <title>Hello Example</title> </head> <body> <togostanza-organism-name tax_id="1148"></togostanza-organism-name> //スタンザの埋め込み。togostanza-[スタンザ名]というタグを記載する。 属性で引数を指定する <togostanza-apply-style></togostanza-apply-style> //他のスタンザの埋め込み </body>
SSL化 (https) 準備
- 自分で管理していて、将来的に全てSSL化する予定なら index.js 内の endpoint や api の URL の protocol 部分を自動取得しておくと便利
var endpoint = document.location.protocol + "//example.org/endpoint"; // 現在アクセスしているプロトコルを返す var res = fetch(endpoint, oprtions);
トラブルシューティング
ts server(build)が動かない
コマンドを打つディレクトリが不適切
$ ts server no stanzas available under /current/your/directory/
カレントディレクトリを移動してコマンドを打ち直すか、または-stanza-base-dirパラメータでスタンザのディレクトリを指定できます。
$ ts server -stanza-base-dir /your/stanza/provider/directory/
ポートがすでに使用されている(ts serverコマンドのみ)
$ ts server ・・・・・ listen tcp :8080: bind: address already in use
デフォルト8080以外を使用したい場合は-portパラメータで指定できます。
$ ts server -port 9090
invalidというメッセージが出る
$ ts server 2016/02/26 15:02:04 loading stanza /Users/yoko/DBCLS/ts_samples/redo-stanza 2016/02/26 15:02:04 invalid character '1' after object key:value pair
metadata.jsonのjsonが不正な場合にエラーが出ることがあります。よくある間違いは、"stanza:usage"のタグ属性のダブルクォーテーションのエスケープ(バックスラッシュ)忘れです。
誤) "stanza:usage": "<togostanza-redo-stanza start_pos="1" end_pos="50"></togostanza-redo-stanza>", 正) "stanza:usage": "<togostanza-redo-stanza start_pos=\"1\" end_pos=\"50\"></togostanza-redo-stanza>",
また、配列の最後の要素にカンマがあってもtsではエラーになります。
"stanza:parameter": [ { ・・・・・ }, { ・・・・・ }, //このカンマはNG!! ],
スタンザが表示されない
なにはなくとも、ブラウザのデバッグツールでエラーが出ていないか確認します。
ts serverで起動している場合にはかなり詳細にログが出ています。発行されたSPARQLや戻り値、テンプレートから生成されたHTML文字列など。(ts buildの場合にはほとんどログはでない)
SPARQLのエラー
エンドポイントのCORSが未対応
エラーメッセージの中に'Access-Control-Allow-Origin' という文字列があった場合には、jsからのアクセスをエンドポイントが拒否しています。
スタンザ作成前の確認の確認でエンドポイントが使用できるか、クエリが間違えていないかを確認しましょう。
SPARQLの結果がエラー
サーバが落ちていたり、文法エラーであったりその他の理由でエンドポイントから結果が正しく取得できないといった場合には、通常はHTTPステータスはエラーで返されます。
エラーハンドリングしたい場合には、通常のjQueryでのAjax処理のようにdone()メソッドの替わりに、doneとfailを使います。
var q = stanza.query({ ・・・・・ }); q.done(function(data) { //成功時 console.log("done"); console.log(data); }).fail(function(data) { //エラー発生時 console.log("fail"); console.log(data); });
jsの記述エラー
基本的には一般のjavascriptと同じようにエラーが出ますので、メッセージに従って修正します。
ただし、デバッグツールが示すエラー発生の行番号とindex.js等のファイルの行番号は一致しません。(エラー発生箇所周辺のコードを表示してくれるようなデバッグツールの場合にはあまり問題ありません)
デバッグツールが実際にどこのファイルの行数を指しているかというと、tsコマンド(server/build)で生成されたコンテンツファイルになります。
- [スタンザディレクトリ]/[スタンザ名]/index.js(実際に修正すべき箇所)
13 stanza.render({ 14 ・・・・・ 15 }; //ここのカッコとじが抜けている
- [スタンザディレクトリ]/dist/stanza/[スタンザ名]/index.html (ビルド後のコンテンツで、デバッグツールが指す行番号はこのファイルの行番号)
203 stanza.render({ 204 ・・・・・ 205 }; //ここのカッコとじが抜けている。しかしこのファイルを直しても意味はない(再コンパイル)
テンプレートファイル(handlebars)の記述エラー
エラーを起こしている箇所にhandlebarsという書かれていた場合は、テンプレートファイル(HTML(*.html)やSPARQL(*.rq))の記述に不備がある場合があります。 たとえばテンプレートファイルを閉じ忘れると次のようなエラーメッセージになります(Chromeのデバッグツール)
正) {{raw_data}} 誤){{raw_data} //閉じかっこがたりない。 エラーメッセージ handlebars.min.js:27 Uncaught Error: Parse error on line 16: ...data</h3>{{raw_data}<h3>bindings</h3> ----------------------^
js ライブラリが動かない
あるjsライブラリが動かない、描画が崩れるといった場合には、jsの競合やライブラリがShadowDOMに対応していない描画ライブラリといった原因が考えられます。
jQueryプラグインとして作成されたライブラリ
以下のようなメッセージが出る場合にはライブラリがjQueryを拡張していることが原因と考えられます。(ライブラリ読み込み後にスタンザ側でjQueryをロードし直すので、上書き分がなかったことになる)
Uncaught TypeError: $(...).XXXXX is not a function
注意事項
jsの競合 (ts v0.0.8以前)
各スタンザがjQueryなどのjsライブラリをページグローバルな環境に読み込むため、jsの競合(上書きによる不整合)が発生するケースがある。
影響範囲スタンザを埋め込んだページ全体になる
- jQueryのプラグインは基本的には使用できない, Web Components v1 対応と jQuery 排除
bootstrap(一部動作はする)やhighcharts等...
- スタンザ同士で変数名(グローバル変数)やライブラリ名(違い内容で同名)が衝突をしないように気をつける
描画ライブラリ
ShadowDOMに未対応(想定していない)描画ライブラリでは、正しく描画できない、表示位置がズレる等の不具合が出ることがある。
例えば、ライブラリが内部でウェブページの左上の起点を探す際に<body>タグを探して位置決めしている場合があり、Shadow DOM(スタンザ)内には<body>タグが存在しないため、スタンザを内包するWebページの<body>タグを見てしまい、位置ズレが発生するというケースがある。
描画の不具合を確認したツール(2016年1月現在)
- plotly.js (軸タイトル等の表示位置がズレてしまう)
- 3Dmol.js (Styleでbodyタグからの絶対位置を用いている箇所があったため、コードを修正して使用)
- chartjs(よく分からないが描画されない)
ただし、D3.js等の低機能なチャートライブラリの場合には問題なく動作する。また、<body>タグやDocumentのルートを意識していないライブラリについても使用できると思われる(NVDは動きそう)
Web Components v1 対応と jQuery 排除
- 注意:ts v0.0.9 以降は下記に対応済み
- Web Components
- 現在の JS 版 stanza は Web Components v0
- 今後は v1 が普及予定
- iOS のブラウザは v1 のみ対応なので表示すらできない
- jQuery
- jQuery, bootstrap はまだまだサイトで使われてるので競合すると困る
v1対応
- ソースの stanza/data/help.html, provider/provider.go(ts build で生成される dist/stanza/stanza-name/help.html)で webcomponentjs 変更
- 現在の v0: webcomponents.min.js ver. 0.5.5
- v1: https://github.com/webcomponents/webcomponentsjs の webcomponents-loader.js + webcomponents-******.js
- 読み込むのは webcomponents-loader.js だけで、他は同じディレクトリに入れるために provider/provider.go に追記
- ソースの stanza/data/stanza.js(ts build で生成される dist/stanza/stanza-name/index.js) を書き換え
/* v0 の部分をコメントアウト proto.createdCallback = function() { var shadow = this.createShadowRoot(); var style = document.createElement("style"); style.appendChild(document.createTextNode(descriptor.stylesheet)); shadow.appendChild(style); var main = document.createElement("main"); shadow.appendChild(main); update(this); }; proto.attributeChangedCallback = function(attrName, oldVal, newVal) { var found = false; descriptor.parameters.forEach(function(key) { if (attrName == key) { found = true; } }); if (found) { update(this); } }; document.registerElement(descriptor.elementName, { prototype: proto }); */
// v1 ぽく書き換え class StanzaElement extends HTMLElement { constructor() { super(); var shadow = this.attachShadow({ mode: 'open' }); var style = document.createElement("style"); style.appendChild(document.createTextNode(descriptor.stylesheet)); shadow.appendChild(style); var main = document.createElement("main"); shadow.appendChild(main); update(this); } attributeChangedCallback(attrName, oldVal, newVal) { var found = false; descriptor.parameters.forEach(function(key) { if (attrName == key) { found = true; } }); if (found) { update(this); } } } if ('customElements' in window && !window.customElements.get(descriptor.elementName)) { window.customElements.define(descriptor.elementName, StanzaElement); }
- ts build したものは stanza更新したりすると、自動で書き換わってもとに戻るので、動作確認にしか使えません
jQuery排除
- ついでに競合すると困りやすい jQuery 排除
- ソースの stanza/data/index.html で jQuery 呼び出しを削除
- ソースの stanza/data/stanza.js 書き換え
// jQuery --------------------------- /* var p = $.ajax({ method: method, url: params.endpoint, headers: { "Accept": "application/sparql-results+json" }, data: { query: query } }); */ // Native (Fetch API)---------------- var p = fetch(params.endpoint + '?query=' + query, { method: method, mode: 'cors', headers: { "Accept": "application/sparql-results+json" } }).then(res => res.json()); //-----------------------------------
// jQuery----------------- /* $(selector, element.shadowRoot).html(htmlFragment); */ // Native----------------- element.shadowRoot.querySelector(selector).innerHTML = htmlFragment; //------------------------
- 各 stanza の index.js で jQuery コードを使っている場合は _header.html で jQuery を読み込む必要があります
- index.js で jQuery を使わずコードする場合
- 複数クエリ、APIの並列実行の例
Stanza(function(stanza, params) { // SPARQL query 1 var q1 = stanza.query({ endpoint: "http://exapmle.org/sparql", template: "query1.rq", parameters: params }); // SPARQL query 2 var q2 = stanza.query({ endpoint: "http://example.com/sparql", template: "query2.rq", parameters: params }); // other API var q3 = fetch( "http://example.of/api/url?param=val", { method: 'get', mode: 'cors', }).then(res => res.json()); // プレーンテキストで受けるなら res.text() var q = Promise.all([q1, q2, q3]); q.then(function([data1, data2, data3]){ // jQuery の $.when() と違い data1[0] ではなく、data1 で SPARQL 結果が取得できる var query1_list = data1.results.bindings; var query2_list = ...... ....... stanza.render({ template: "stanza.html", parameters: { result: all_list } }); }); });
テスト用バイナリ、ソース
- 上記を実装してみたバイナリ (ts v0.0.8mod)
- linux_amd64 (for linux)
- darwin_amd64 (for mac)
- webcomponentsjs v1.0.14
- handlebars v4.0.10
- 親要素にはみ出る要らない css 削除
- github branch