RDFデータベースをSPARQLと呼ばれるクエリ言語で検索できるWeb API「SPARQLエンドポイント」を公開するオープンデータサイトが行政を中心に増えています。例えば、
これらのサイトの多くは、Webブラウザ上でSPARQLクエリを実行して、表形式で検索結果を見られるようになっています。
しかし、Web APIなので本来は仕様に基づいた形式でエスケープ処理をしたSPARQLクエリをGETもしくはPOSTしなけれならないので、上記のような気の利いたページを公開しないSPARQLエンドポイントもあります。
そこでだいぶ前にWebブラウザ上でSPARQLエンドポイントへ簡単にSPARQLクエリを実行できて、その結果を見やすい表形式で表示するWebアプリをjQueryで作りました。
JavaScriptによるSPARQL利用サンプル(クエリ検索アプリ)
https://github.com/uedayou/simple-sparqlsearch-js
かなり古いアプリですが、今でも問題なく動きます。ただ、今見るとコードの可読性が悪いなとおもいます。とても短いコードでもあるので、jQueryを使わずに書き直すことにしました。
Vue.js で書き直し
今回は Vue.js を使うことにしました。React, AngularでもWebアプリを書いたことがありますが、今回はできるだけコード量を減らして、Webpack等でのビルドを行わない形にしたかったので、Vue.jsを使いました。
Vue.jsによるSPARQLエンドポイント検索アプリ
https://github.com/uedayou/simple-sparqlsearch-vue
機能としては全く同じですが、検索した結果をJSONファイルでダウンロードできるようにしました。
検索結果が表示されているときに、ダウンロードボタンを押すとJSONファイルがダウンロードできます。
Vue.js vs jQuery
Vue.js で書き直してみて、jQueryのコードよりも個人的には格段に可読性が上がったと思います。
jQueryは、DOM操作をコード内で行わないといけなかったのが、Vue.jsだとDOMにデータをバインディングできるので書き直すのも楽にできました。
Vue.use(VueLoading);
Vue.component('loading', VueLoading);
var app = new Vue({
el: '#app',
data: {
query: 'select * where {?s ?p ?o} LIMIT 10',
results: {
data: null,
head: [],
body: [],
},
},
methods: {
doSearch: function() {
var loader = this.$loading.show();
var that = this;
axios.get(
endpoint+"?query="+encodeURIComponent(this.query),
{ headers: {'Accept': 'application/sparql-results+json'} })
.then(function(res) {
that.results.data = res.data;
that.results.head = res.data.head.vars;
that.results.body = res.data.results.bindings;
})
.catch(function(error) {
console.log(error);
alert("Error!");
})
.then(function() {
loader.hide();
});
},
downloadData: function() {
var filename = "results.json";
var a = document.createElement('a');
var uriContent = 'data:application/octet-stream,'+encodeURIComponent(JSON.stringify(this.results.data));
a.setAttribute('href', uriContent);
a.setAttribute('download', filename);
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
}
})
(function(){
$.fn.modal.defaults.spinner = $.fn.modalmanager.defaults.spinner = '<div class="loading-spinner" style="width: 200px; margin-left: -100px;"><div class="progress progress-striped active"><div class="progress-bar" style="width: 100%;"></div></div></div>';
$('#find_query').click(function(){
$('body').modalmanager('loading').find('.modal-scrollable').off('click.modalmanager');
qr = sendQuery(endpoint,encodeURIComponent($('#query_area').val().replace(/[\n\r]/g,"")));
qr.fail(
function (xhr, textStatus, thrownError) {
$('body').modalmanager('removeLoading');
alert("Error: A '" + textStatus+ "' occurred.");
}
);
qr.done(
function (d) {
$('body').modalmanager('removeLoading');
$('body').removeClass('modal-open');
result_table(d.results.bindings);
}
);
});
$('#result_div').hide();
}());
var result_table = function(data){
var result_div = $('#result_div');
var table = $('#result_list')[0];
if (table == undefined) {
result_div.append($('<table></table>').attr({
'id' : 'result_list',
'class' : 'table'
}));
table = $('#result_list')[0];
}
while (table.rows.length > 0) { table.deleteRow(0); }
if (data instanceof Array) {
result_div.show();
var header = table.createTHead();
var headerRow = header.insertRow(0);
id = 1;
for (var d = 0; d < data.length; d++) {
var row1 = table.insertRow(d + 1);
if (d == 0) {
for ( var key in data[0]) {
var th = document.createElement('th');
var label = key;
th.innerHTML = key;
headerRow.appendChild(th);
}
}
var i = 0;
for ( var key in data[d]) {
var cell = row1.insertCell(i++);
var value = data[d][key];
if (value.value != undefined){value = value.value;}
if (value == null) {value = '';}
var link = true;
if (link) {
if (value != null && value.indexOf("http://") == 0) {
value = '<a href="'+value+'" target="_blank">'+value+'</a>';
}
}
cell.innerHTML = value;
}
}
}
};