はじめに
デスクトップアプリを作るための開発環境 Wails と、ウェブアプリを作るためのライブラリ Webix を使ってみました。
Wails+Webix を使ってみた #Wails - Qiita
Webix のコンポーネントは多くの種類が用意されていますが特に、データを一覧表示する datatable
コンポーネントは便利です。
Webix を使ってみたの続き #JavaScript - Qiita
これを Wails アプリで使ってみたいと思います。
Wails+SQLite を使ってみる
アプリのデータは SQLite データベースに記録するようにしたいと思います。
Go の SQLite パッケージを組込する
Wails の Go プログラムで SQLite データベースを利用できるよう用意します。
Go 言語で SQLite を使う(Windows 向けの紹介) | text.Baldanders.info
Go 言語で SQLite3 #Mac - Qiita
Wails の Go プログラムに以下のコードを追加します。
import (
"database/sql"
_ "github.com/mattn/go-sqlite3"
)
wails build
ないし wails dev
すると、go mod tidy
されて、プロジェクトの go.mod
ファイルが更新され、開発環境の go
ディレクトリに必要なパッケージがインストールされます。
アプリの起動時にデータベースを作成する
実行ファイルと同じフォルダにデータファイル data.db
を作ることにします。さらに、テーブル list
を作成します。それにデータを追加しておきます。↓
var dbfile string
func init() {
path, _ := os.Executable()
dbfile = filepath.Join(filepath.Dir(path), "data.db")
}
func (a *App) InitDb() error {
db, _ := sql.Open("sqlite3", dbfile)
defer db.Close()
_, err := db.Exec(`CREATE TABLE IF NOT EXISTS list (
id STRING, title STRING, year INT, votes INT, rank INT,
PRIMARY KEY(id)
)`)
if err != nil {
return err
}
_, err = db.Exec(`INSERT INTO list (id, title, year, votes) VALUES (
1, "The Shawshank Redemption", 1994, 678790
)
ON CONFLICT (id) DO NOTHING
;
INSERT INTO list (id, title, year, votes) VALUES (
2, "The Godfather", 1972, 511495
)
ON CONFLICT (id) DO NOTHING`)
if err != nil {
return err
}
return nil
}
全て init()
に書いてもよさそうですが、処理でエラー発生したときにフロントエンドのプログラムでメッセージ表示したいでしょう。InitDb()
をフロントエンドのプログラムで呼出します。↓
import {InitDb} from '../wailsjs/go/main/App';
webix.ready(function(){
InitDb()
.then(function(){
webix.message("Database initialized.");
})
.catch(function(reason){
webix.message("Database init failed: " + reason);
})
Webix の datatable を使ってみる①
datatable コンポーネントを記述する
まず、datatable
コンポーネントを画面に表示します。
webix.ui({
rows:[
{
view:'datatable',
id:"list",
columns:[
{ id:'rank', header:"", width:30 },
{ id:'title', header:"Title", width:300 },
{ id:'year', header:"Released", width:80 },
{ id:'votes', header:"Votes", width:100 }
],
},
]
});
この datatable
コンポーネントにデータをセットして表示します。
データを取得するメソッドを用意する
SQLite からデータを取得するメソッドを用意します。
type Item struct {
Id string `json:"id"`
Title string `json:"title"`
Year int `json:"year"`
Votes int `json:"votes"`
}
func (a *App) GetItems() ([]Item, error) {
db, _ := sql.Open("sqlite3", dbfile)
defer db.Close()
rows, _ := db.Query(`SELECT id, title, year, votes FROM list`)
defer rows.Close()
var items []Item
for rows.Next() {
var item Item
err := rows.Scan(&item.Id, &item.Title, &item.Year, &item.Votes)
if err != nil {
return []Item{}, err
}
items = append(items, item)
}
return items, nil
}
テーブル list
に記録されている全件を取得します。↑
datatable にデータをセットする
これをフロントエンドのプログラムで呼出します。
import {InitDb, GetItems} from '../wailsjs/go/main/App';
webix.ready(function(){
(中略)
GetItems()
.then(function(result){
$$('list').parse(result);
})
.catch(function(reason){
webix.message(reason);
});
parse
メソッドを使って datatable
にデータをセットします。↑
参考 https://snippet.webix.com/598f77de
データベースの準備する InitDb()
が完了する前に、データを取得する処理 GetItems()
を呼出してはまずいでしょう。
(async function(){
await InitDb()
.then(function(){
webix.message("Database initialized.");
})
.catch(function(reason){
webix.message("Database init failed: " + reason);
});
await GetItems()
.then(function(result){
$$('list').parse(result);
})
.catch(function(reason){
webix.message("Load failed: " + reason);
});
})();
Webix の datatable を使ってみる②
datatable を編集可能にする
editable
プロパティを指定すると、セル単位でインライン編集できるようになります。
{
view:'datatable',
id:"list",
(中略)
columns:[
{ id:'rank', header:"", width:30 },
{ id:'title', header:"Title", width:300, editor:'text' },
{ id:'year', header:"Released", width:80, editor:'text' },
{ id:'votes', header:"Votes", width:100, editor:'text' }
],
editable:true,
},
参考 https://snippet.webix.com/f1edcadc
さらに、データの追加と削除できるように入力フォームを用意しましょう。
datatable にデータを追加する
add
メソッドを使って datatable
に追加できます。
まず、入力フォームを追加します。「追加」ボタンを用意しています。
{
view:'datatable',
id:"list",
(中略)
},
{
view:'form',
id:"input",
elements:[
{ view:'text', name:"title", value:"New Movie",inputWidth:200 },
{ view:'text', name:"year", value:"2025", inputWidth:200 },
{ view:"text", name:"votes", value:"999999", inputWidth:200 },
{ cols:[
{ view:'button', width:100, value:"Add", click:addData },
] }
]
}
「追加」ボタンをクリックすると関数 addData
を実行するようにしています。ここで datatable
に行を追加します。↓
function addData(){
var values = $$('input').getValues();
$$('list').add({
title: values['title'],
year: values['year']
});
}
参考 https://snippet.webix.com/247a9541
datatable にデータを削除する
remove
メソッドを使って datatable
から行を削除できます。
まず、入力フォームに「削除」ボタンを追加します。
{
view:'datatable',
id:"list",
(中略)
select:'row',
},
{
view:'form',
(中略)
{ view:"button", width:160, value:"Remove selected", click:removeData },
] }
]
}
(中略)
「追加」ボタンをクリックすると関数 removeData
を実行するようにしています。ここで datatable
から行を削除します。
function removeData(){
var id = $$('list').getSelectedId()
if (!id) {
return;
}
$$('list').remove(id);
}
選択した行を削除するので、datatable
に select:'row'
を指定して行単位で選択できるようにしておきます。↓
{
view:'datatable',
id:"list",
(中略)
select:`row`,
},
参考 https://snippet.webix.com/247a9541
データの変更を受けるメソッドを用意する①
データの追加を受けるメソッドを用意します。指定されたデータ item
をテーブル list
に追加します。
func (a *App) InsertItem(item map[string]interface{}) error {
db, _ := sql.Open("sqlite3", dbfile)
defer db.Close()
_, err := db.Exec(`INSERT INTO list (id, title, year, votes) VALUES (?, ?, ?, ?)`,
item["id"], item["title"], item["year"], item["votes"])
if err != nil {
return err
}
return nil
}
データを変更するメソッドを呼出する①
データが変更されたイベントを受けて、上記のメソッドをフロントエンドのプログラムで呼出します。
datatable
に行が追加されるタイミングで onBeforeAdd
イベントが発生します。
import {InitDb, GetItems, InsertItem} from '../wailsjs/go/main/App';
(中略)
$$('list').attachEvent('onBeforeAdd', function(id, data, index){
InsertItem(data)
.then(function(){
webix.message("Inserted.")
})
.catch(function(reason){
webix.message(reason)
});
});
データの変更を受けるメソッドを用意する②
データの変更を受けるメソッドを用意します。指定されたデータ item
をテーブル list
に更新します。
func (a *App) UpdateItem(item map[string]interface{}) error {
db, _ := sql.Open("sqlite3", dbfile)
defer db.Close()
_, err := db.Exec(`UPDATE list SET title = ?, year = ?, votes = ? WHERE id = ?`,
item["title"], item["year"], item["votes"], item["id"])
if err != nil {
return err
}
return nil
}
データを変更するメソッドを呼出する②
データが変更されたイベントを受けて、上記のメソッドをフロントエンドのプログラムで呼出します。
datatable
で表示の内容が変更されると onDataUpdate
イベントが発生します。
import {InitDb, GetItems, InsertItem, UpdateItem} from '../wailsjs/go/main/App';
(中略)
$$('list').attachEvent('onDataUpdate', function(id, data, old){
UpdateItem(data)
.then(function(){
webix.message("Updated.")
})
.catch(function(reason){
webix.message(reason)
});
});
データの変更を受けるメソッドを用意する③
データの変更を受けるメソッドを用意します。指定されたデータ item
をテーブル list
から削除します。
func (a *App) DeleteItem(item map[string]interface{}) error {
db, _ := sql.Open("sqlite3", dbfile)
defer db.Close()
_, err := db.Exec(`DELETE FROM list WHERE id = ?`, item["id"])
if err != nil {
return err
}
return nil
}
データを変更するメソッドを呼出する③
データが変更されたイベントを受けて、上記のメソッドをフロントエンドのプログラムで呼出します。
datatable
で行が削除されるタイミングで onBeforeDelete
イベントが発生します。
import {InitDb, GetItems, InsertItem, UpdateItem, DeleteItem} from '../wailsjs/go/main/App';
(中略)
$$('list').attachEvent('onBeforeDelete', function(id){
var dt = this;
DeleteItem(dt.getItem(id))
.then(function(){
webix.message("Deleted.");
})
.catch(function(reason){
dt.undo();
webix.message(reason);
});
});
DaleteItem
メソッドが失敗したときは datatable
コンポーネントで行の削除をアンドゥします。↑
アンドゥできるように datatable
コンポーネントに指定しておきます。↓
{
view:'datatable',
id:"list",
(中略)
undo:true,
},
Webix の datatable を使ってみる③
新規追加の空行を追加する
一覧表示の最終に空行を表示してデータを入力して追加できると便利ですね。
onBeforeRender
イベントで空行を追加します。
{
view:'datatable',
id:"list",
columns:[
{ id:'no', header:"", width:30 },
{ id:'title', header:"Title", width:300, editor:'text' },
{ id:'year', header:"Released", width:80, editor:'text' },
{ id:'votes', header:"Votes", width:100, editor:'text' }
],
editable:true,
},
(中略)
$$('list').attachEvent('onBeforeRender', function(){
var dt = this;
if (dt.count() == 0){
dt.add({});
}
if (canAdd(dt.getLastId())) {
dt.add({});
}
dt.data.each(function(obj, i){
obj['no'] = i + 1;
});
});
先頭の列を「no」にして、連番を表示するようにしています。↑
追加していいか判定する
上記のコードで canAdd
関数を用意して使っています。指定された行の、全てのセルが空でないか確認して、空がなければ行の追加を可能と判断します。↓
function canAdd(id) {
return $$('list').getColumns(true).every(function(obj){
return ($$('list').getItem(id)[obj.id])
});
}
データを変更するメソッドを呼出する②
onBeforeAdd
イベントが発生する空行を追加した時点では全ての項目は空なので、ここで記録する処理しても意味ありません。
onDataUpdate
イベントでも全ての項目がセットされていないと、処理してはだめでしょう。canAdd
を使って判定します。
$$('list').attachEvent('onBeforeAdd', function(id, data){
// 何もしない
});
$$('list').attachEvent('onDataUpdate', function(id, data){
if (!canAdd(id)) {
return;
}
// ここでデータ更新するメソッドを呼出
});
データ更新するメソッドを呼出しますが、この時点で追加か更新か判別して処理できません。↑
データの変更を受けるメソッドを用意する②
id
を使ってサーバ側で登録済か確認して、未登録なら追加、そうでなければ更新の処理するのがいいでしょう。
func (a *App) UpsertItem(item map[string]interface{}) error {
db, _ := sql.Open("sqlite3", dbfile)
defer db.Close()
_, err := db.Exec(`INSERT INTO list (id, title, year, votes) VALUES ($1, $2, $3, $4)
ON CONFLICT(id)
DO UPDATE SET title = $2, year = $3, votes = $4`,
item["id"], item["title"], item["year"], item["votes"])
if err != nil {
return err
}
return nil
}
いわゆる「UPSERT」処理ですが、SQLite なら一回のクエリで書くことができます。↑
これをフロントエンドのプログラムで呼出します。↓
import {InitDb, GetItems, UpsertItem, DeleteItem} from '../wailsjs/go/main/App';
(中略)
$$('list').attachEvent('onDataUpdate', function(id, data){
if (!canAdd(id)) {
return;
}
UpsertItem(data)
.then(function(){
webix.message("Upserted.")
})
.catch(function(reason){
webix.message(reason)
});
});