Skip to content

Data / Schema / View モデル

Extableはテーブル設定をData / Schema / Viewの3つの関心事に分離します。この分離により、マルチユーザー協調、予測可能な状態管理、クリーンなアーキテクチャが実現します。

主な設計原則

項目DataSchemaView
所有者アプリケーションアプリケーションユーザー
変更可能か可能(setData()不可(初期化時に固定)可能(フィルター、ソート、列)
他ユーザーに影響あり(全ユーザーに同じデータ)あり(全ユーザーに同じ列)なし(各ユーザー固有)
永続化先バックエンドDBアプリコードlocalStorage / URLクエリ / セッション
新しい従業員行の追加給与列のリネーム給与列を非表示にする

Data: 真のデータ

Dataは実データです。単純な配列(行)として表現します。

typescript
const data = [
  { id: 'emp-001', name: 'Alice', department: 'Engineering', salary: 95000, active: true },
  { id: 'emp-002', name: 'Bob', department: 'Sales', salary: 72000, active: true },
  { id: 'emp-003', name: 'Carol', department: 'Marketing', salary: 68000, active: false }
];

対応データ型

Extableは次の型を標準で扱えます。

  • string: 文字列(長さや正規表現の検証)
  • number: 数値(精度、スケール、符号制約)
  • boolean: 真偽値(checkbox、yes/noなどの表示)
  • date: 日付(YYYY-MM-DD)
  • time: 時刻(HH:mm:ss)
  • datetime: 日付+時刻のタイムスタンプ
  • enum: 固定の選択肢
  • tags: 1セルに複数タグ
  • button: クリック可能なアクションセル
  • link: URLへ遷移するリンクセル

Dataの設定と更新

テーブル生成時に初期データを渡します。

typescript
const table = new ExtableCore({
  root: document.getElementById('table'),
  defaultData: data,  // 初期データ
  schema: schema,
  defaultView: view
});

setData()でいつでもデータを更新できます。

typescript
// APIから最新データを取得
const freshData = await fetch('/api/employees').then(r => r.json());

// テーブルを更新
table.setData(freshData);

setData()はデータ配列を丸ごと置き換え、再描画を行います。

typescript
// データ変更を監視
table.onTableState(({ data, view, schema }) => {
  console.log('Data updated:', data);
});

// データを更新
table.setData(newEmployeeList);

データ検証

データは初期化や更新時にスキーマと照合され、無効な値はExcelのような赤枠で示されます。

Schema: 変更不可の列定義

Schemaは列の構造と挙動を定義します。Excelと異なり、初期化時に固定され、エンドユーザーは変更できません。

typescript
const schema = {
  columns: [
    {
      key: 'id',
      type: 'string',
      header: 'Employee ID',
      readonly: true,  // ユーザーは編集不可
    },
    {
      key: 'name',
      type: 'string',
      header: 'Full Name',
      nullable: false,  // 必須項目
    },
    {
      key: 'department',
      type: 'enum',
      header: 'Department',
      enum: { options: ['Engineering', 'Sales', 'Marketing', 'HR'] },
    },
    {
      key: 'salary',
      type: 'number',
      header: 'Salary (USD)',
      format: { precision: 2 },
      style: { align: 'right' },
      conditionalStyle: (row) => {
        if (row.salary > 90000) return { backgroundColor: '#d4edda' };  // 高額給与は緑
        return null;
      }
    },
    {
      key: 'active',
      type: 'boolean',
      header: 'Active',
      format: 'checkbox'
    },
    {
      key: 'notes',
      type: 'string',
      header: 'Notes',
      wrapText: true  // この列で折り返しを有効化
    },
    {
      key: 'fullCompensation',
      type: 'number',
      header: 'Full Compensation',
      readonly: true,
      formula: (row) => row.salary * 1.25  // 計算列
    }
  ]
};

Schemaの機能

  • 型安全: 期待される型を保証
  • バリデーション: 文字数、正規表現、数値の最小/最大、nullable制約
  • フォーマット: 数値精度、日付形式、boolean表示
  • スタイル: 文字色、背景、太字/斜体、装飾(下線、取り消し線)
  • 折り返し: 列ごとの折り返し(wrapText
  • 条件付き書式: 値に応じた動的スタイル(関数で定義)
  • readonly列: IDや計算列などの編集防止
  • 計算列: JavaScript関数で定義する数式列(ユーザーは編集不可)

Readonly / Disabled マトリクス

列タイプSchema readonlyConditional { readonly: true }Schema disabledConditional { disabled: true }Notes
button常にreadonly(変更不可)非対応対応対応disabledはreadonlyのグレー表示で操作不可。
link常にreadonly(変更不可)非対応対応対応disabledはreadonlyのグレー表示で操作不可。
formula常にreadonly(変更不可)非対応非対応非対応conditionalのreadonly/disabledは無視。
boolean/number/date/time/datetime対応対応非対応非対応readonlyはschemaかconditionalStyleで設定。
string/enum/tags対応対応非対応非対応readonlyはschemaかconditionalStyleで設定。

disabledはbutton/linkのみstyle.disabledまたはconditionalStyleで設定します。

なぜ固定スキーマなのか

  • データ整合性: アプリが構造を保証し、予期しない変更がない
  • バージョン管理: スキーマ変更はアプリと共に配布される
  • 協調性: すべてのユーザーが同じ列構成を共有
  • 予測可能なAPI: 列と制約を開発者が制御

詳細なスキーマ例と設定はデータフォーマットガイドを参照してください。

View: ユーザーごとの表示・整理

Viewはユーザーの作業空間です。各ユーザーが独自のビュー状態を持ち、共有データには影響しません。

typescript
const view = {
  // 列の表示と順序
  columnOrder: ['id', 'name', 'active', 'department'],  // 'salary'と'fullCompensation'を非表示
  
  // ソート
  sortOrder: [
    { key: 'active', direction: 'desc' },  // アクティブを先頭に
    { key: 'name', direction: 'asc' }     // 次にアルファベット順
  ],
  
  // フィルター
  filters: {
    department: ['Engineering', 'Sales'],  // 対象部署のみ表示
    active: [true]                         // アクティブのみ表示
  }
};

Viewの構成要素

  • 列の順序と可視性: どの列を表示するか、どの順で表示するか
  • ソート順: 昇順/降順を含む並び順
  • フィルター: 列の値で行を絞り込む(縦方向のみ)

ユーザーごとの永続化

Viewは通常ユーザーのセッションローカルです。次のようにlocalStorageやバックエンドに保存できます。

typescript
// View変更時に保存
table.onTableState(({ view }) => {
  localStorage.setItem('my-table-view', JSON.stringify(view));
});

// ページ読み込み時に復元
const savedView = localStorage.getItem('my-table-view');
const restoredView = savedView ? JSON.parse(savedView) : defaultView;

const table = new ExtableCore({
  root: document.getElementById('table'),
  defaultData: data,
  schema: schema,
  defaultView: restoredView  // 保存済みビューを使用
});

View永続化の利点

  • ソートを維持: リロード後もソート順が残る
  • フィルターを記憶: 絞り込み状態を保持
  • 列の好み: 可視列設定を一度行えば継続
  • タブごとの独立性: ブラウザタブ間で独自のビューを持てる

次のステップ

Apache 2.0 Licenseで公開