0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Wails+Webix を使ってみたの続き

Last updated at Posted at 2025-01-13

はじめに

デスクトップアプリを作るための開発環境 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 プログラムに以下のコードを追加します。

app.go
import (
    "database/sql"

    _ "github.com/mattn/go-sqlite3"
)

wails build ないし wails dev すると、go mod tidy されて、プロジェクトの go.mod ファイルが更新され、開発環境の go ディレクトリに必要なパッケージがインストールされます。

アプリの起動時にデータベースを作成する

実行ファイルと同じフォルダにデータファイル data.db を作ることにします。さらに、テーブル list を作成します。それにデータを追加しておきます。↓

app.go
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() をフロントエンドのプログラムで呼出します。↓

main.js
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 コンポーネントを画面に表示します。

main.js
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 コンポーネントにデータをセットして表示します。

サンプル①.png

データを取得するメソッドを用意する

SQLite からデータを取得するメソッドを用意します。

app.go
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 にデータをセットする

これをフロントエンドのプログラムで呼出します。

main.js
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() を呼出してはまずいでしょう。

main.js
    (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 プロパティを指定すると、セル単位でインライン編集できるようになります。

main.js
        {   
            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

さらに、データの追加と削除できるように入力フォームを用意しましょう。

サンプル②.png

datatable にデータを追加する

add メソッドを使って datatable に追加できます。

まず、入力フォームを追加します。「追加」ボタンを用意しています。

main.js
        { 
            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 から行を削除できます。

まず、入力フォームに「削除」ボタンを追加します。

main.js
        { 
            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);
}

選択した行を削除するので、datatableselect:'row' を指定して行単位で選択できるようにしておきます。↓

        {   
            view:'datatable',
            id:"list",
            (中略)
            select:`row`,
        },

参考 https://snippet.webix.com/247a9541

データの変更を受けるメソッドを用意する①

データの追加を受けるメソッドを用意します。指定されたデータ item をテーブル list に追加します。

app.go
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 イベントが発生します。

main.js
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 に更新します。

app.go
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 イベントが発生します。

main.js
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 から削除します。

app.go
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 イベントが発生します。

main.js
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 を使ってみる③

新規追加の空行を追加する

一覧表示の最終に空行を表示してデータを入力して追加できると便利ですね。

サンプル③.png

onBeforeRender イベントで空行を追加します。

main.js
        {   
            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 を使って判定します。

main.js
    $$('list').attachEvent('onBeforeAdd', function(id, data){
        // 何もしない
    });
    
    $$('list').attachEvent('onDataUpdate', function(id, data){
        if (!canAdd(id)) {
            return;
        }
        // ここでデータ更新するメソッドを呼出
    });

データ更新するメソッドを呼出しますが、この時点で追加か更新か判別して処理できません。↑

データの変更を受けるメソッドを用意する②

id を使ってサーバ側で登録済か確認して、未登録なら追加、そうでなければ更新の処理するのがいいでしょう。

app.go
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 なら一回のクエリで書くことができます。↑

これをフロントエンドのプログラムで呼出します。↓

main.js
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)
        });
    });
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?