Close
Angular React Web Components Blazor
Open Source

Web Components Query Builder の概要

Ignite UI for Web Components Query Builder は、指定したデータ セットに対する複雑なデータ フィルタリング クエリを構築できる豊富な UI を提供します。このコンポーネントでは、式ツリーを作成し、各フィールドのデータ型に応じたエディターと条件リストを使用して、式の間に AND / OR 条件を指定できます。作成した式ツリーは、その後バックエンドでサポートされる形式のクエリへ簡単に変換できます。

Web Components Query Builder の使用を開始するには

QueryBuilder の使用を開始するには、まず次のコマンドを実行して Ignite UI for Web Components パッケージをインストールします:

npm install igniteui-webcomponents igniteui-webcomponents-grids

プロジェクト構成に応じて、対応するスタイルも参照する必要があります。

import 'igniteui-webcomponents-grids/grids/themes/light/bootstrap.css';

Web Components Query Builder の使用

初期の式ツリーが設定されていない場合は、まずエンティティと、クエリが返す対象フィールドを選択します。その後、条件またはサブグループを追加できます。

条件を追加するには、フィールド、フィールドのデータ型に基づく演算子、そして演算子が単項でない場合は値を選択します。In および Not In 演算子を使用すると、単に値を指定する代わりに、別のエンティティに対する条件を持つ内部クエリを作成できます。条件を確定すると、その条件情報を含むチップが表示されます。チップをクリックまたはホバーすると、その条件を編集したり、直後に別の条件やグループを追加したりできます。

各グループの上にある AND または OR ボタンをクリックすると、グループの種類を変更したり、内部の条件をグループ解除したりするためのメニューが開きます。

各条件は特定のエンティティの特定フィールドに関連付けられているため、エンティティを変更すると、事前に設定された条件およびグループはすべてリセットされます。

このコンポーネントは、Entities プロパティに、エンティティ名とそのフィールド配列を記述した配列を設定することで使用できます。各フィールドは、名前とデータ型によって定義されます。フィールドを選択すると、データ型に応じた対応する演算子が自動的に割り当てられます。 また Query Builder には ExpressionTree プロパティがあります。これを使用すると、コントロールの初期状態を設定したり、ユーザーが指定したフィルタリング ロジックにアクセスしたりできます。

<igc-query-builder id="queryBuilder">
</igc-query-builder>
private queryBuilder: IgcQueryBuilderComponent;
public entities: any[] = [];
public ordersFields: any[] = [];
public expressionTree!: IgcExpressionTree;

constructor() {
  this.queryBuilder = document.getElementById('queryBuilder') as IgcQueryBuilderComponent;
  this.initFields();
}

private initFields(): void {
  this.ordersFields = [
    { field: 'orderId', dataType: 'number' },
    { field: 'customerId', dataType: 'string' },
    { field: 'orderDate', dataType: 'date' }
  ];

  this.entities = [
    { name: 'Orders', fields: this.ordersFields }
  ];

  const tree = new IgcFilteringExpressionsTree();
  tree.operator = FilteringLogic.And;
  tree.entity = 'Orders';

  this.expressionTree = tree;
  this.queryBuilder.entities = this.entities;
  this.queryBuilder.expressionTree = this.expressionTree;
}

IgcExpressionTree はバインド可能なプロパティです。つまり、ExpressionTreeChange イベントを購読することで、エンドユーザーが条件を作成、編集、削除して UI を変更したときに通知を受け取ることができます。

this.queryBuilder.addEventListener('expressionTreeChange', (e: CustomEvent<IgcExpressionTree>) => {
  this.expressionTree = e.detail;
  this.onExpressionTreeChange();
});

式のドラッグ

条件チップは、マウスのドラッグ アンド ドロップまたはキーボードによる並べ替えで簡単に位置を変更できます。これにより、ユーザーはクエリ ロジックを動的に調整できます。

  • チップをドラッグしても、変更されるのは位置のみであり、条件や内容自体は変更されません。
  • チップはグループおよびサブグループ間でもドラッグできます。たとえば、式のグループ化やグループ解除はこのドラッグ機能によって実現できます。 既存の条件をグループ化するには、まず「グループ追加」ボタンで新しいグループを追加します。その後、必要な式をドラッグしてそのグループへ移動できます。グループ解除するには、すべての条件を現在のグループ外へドラッグします。最後の条件が移動されると、そのグループは削除されます。

あるクエリ ツリーのチップを別のクエリ ツリーへドラッグすることはできません。たとえば、親クエリから内部クエリへ、またはその逆への移動はできません。

Animated Example of Query Builder Drag and Drop using the Mouse

キーボード操作

キー操作

  • Tab / Shift + Tab - 次 / 前のチップ、ドラッグ インジケーター、削除ボタン、「式を追加」ボタンへ移動します。
  • Arrow Down/Arrow Up - チップのドラッグ インジケーターにフォーカスがある場合、チップを上下に移動できます。
  • Space / Enter - フォーカスされた式が編集モードに入ります。チップの移動中であれば、新しい位置を確定します。
  • Esc - チップの並べ替えをキャンセルし、元の位置に戻します。

キーボードによる並べ替えは、マウスのドラッグ アンド ドロップと同じ機能を提供します。チップを移動した後、ユーザーは新しい位置を確定するか、並べ替えをキャンセルする必要があります。

Animated Example of Keyboard Drag and Drop Using the Ignite UI for Angular Query Builder

テンプレート

Ignite UI for Web Components Query Builder では、コンポーネントのヘッダーおよび検索値に対してテンプレートを定義できます:

ヘッダー テンプレート

既定では、QueryBuilder のヘッダーは表示されません。ヘッダーを定義するには、QueryBuilderHeader コンポーネントを Query Builder 内に追加する必要があります。

<igc-query-builder id="queryBuilder">
  <igc-query-builder-header title="My Query Builder">
  </igc-query-builder-header>
</igc-query-builder>

検索値テンプレート

条件の検索値は、SearchValueTemplate プロパティに lit-html テンプレートを返す関数を設定することでテンプレート化できます。

SearchValueTemplate を使用する場合は、エンティティ内のすべてのフィールド型に対するテンプレートを提供する必要があります。そうしないと Query Builder は正しく動作しません。特定のカスタム テンプレートでカバーされないフィールドや条件を処理する既定 / フォールバック テンプレートを必ず実装してください。これがないと、ユーザーはそれらのフィールドの条件を編集できません。

constructor() {
  this.queryBuilder = document.getElementById('queryBuilder') as IgcQueryBuilderComponent;
  this.queryBuilder.searchValueTemplate = (ctx) => this.buildSearchValueTemplate(ctx);
}

private buildSearchValueTemplate(ctx: IgcQueryBuilderSearchValueContext) {
  const field = ctx.selectedField?.field;
  const condition = ctx.selectedCondition;
  const matchesEqualityCondition = condition === 'equals' || condition === 'doesNotEqual';

  if (!ctx.implicit) {
      ctx.implicit = { value: null };
  }

  // Custom template for Region field
  if (field === 'Region' && matchesEqualityCondition) {
      return this.buildRegionSelect(ctx);
  }

  // Custom template for OrderStatus field
  if (field === 'OrderStatus' && matchesEqualityCondition) {
      return this.buildStatusRadios(ctx);
  }

  // Custom template for date fields
  if (ctx.selectedField?.dataType === 'date') {
      return this.buildDatePicker(ctx);
  }

  // Custom template for time fields
  if (ctx.selectedField?.dataType === 'time') {
      return this.buildTimeInput(ctx);
  }

  // Default template for all other fields (string, number, boolean, etc.)
  // This ensures all fields have a functioning editor
  return this.buildDefaultInput(ctx);
}

以下は、各エディター タイプごとに 1 つのテンプレート例を示したものです:

Region Select の例:

// Field definition
{ field: 'Region', dataType: 'string' }

// Template
private buildRegionSelect(ctx: IgcQueryBuilderSearchValueContext) {
  const currentValue = ctx?.implicit?.value?.value ?? '';

  return html`
    <igc-select
      placeholder="Region"
      .value=${currentValue}
      @igcChange=${(event: CustomEvent<{ value: string }>) => {
        const region = this.regionOptions.find(o => o.value === event.detail.value);
        ctx.implicit.value = region ?? null;
      }}>
      ${this.regionOptions.map(option => html`
        <igc-select-item value=${option.value}>${option.text}</igc-select-item>
      `)}
    </igc-select>
  `;
}

Status Radio Group の例:

// Field definition
{ field: 'OrderStatus', dataType: 'number' }

// Template
private buildStatusRadios(ctx: IgcQueryBuilderSearchValueContext) {
  const currentValue = ctx.implicit?.value?.toString() ?? '';

  return html`
    <igc-radio-group
      .alignment=${"horizontal"}
      .value=${currentValue}
      @igcChange=${(event: CustomEvent<{ value: string }>) => {
        ctx.implicit.value = Number(event.detail.value);
      }}>
      ${this.statusOptions.map(option => html`
        <igc-radio
          name="status"
          value=${option.value}
          ?checked=${option.value.toString() === currentValue}>
          ${option.text}
        </igc-radio>
      `)}
    </igc-radio-group>
  `;
}

Date Picker の例:

// Field definition
{ field: 'OrderDate', dataType: 'date' }

// Template
private buildDatePicker(ctx: IgcQueryBuilderSearchValueContext) {
  const currentValue = ctx.implicit?.value instanceof Date
    ? ctx.implicit.value
    : ctx.implicit?.value ? new Date(ctx.implicit.value) : null;

  const allowedConditions = ['equals', 'doesNotEqual', 'before', 'after'];
  const isEnabled = allowedConditions.includes(ctx.selectedCondition ?? '');

  return html`
    <igc-date-picker
      .value=${currentValue}
      .disabled=${!isEnabled}
      @igcChange=${(event: CustomEvent) => {
        ctx.implicit.value = event.detail;
      }}>
    </igc-date-picker>
  `;
}

Time Input の例:

// Field definition
{ field: 'RequiredTime', dataType: 'time' }

// Template
private buildTimeInput(ctx: IgcQueryBuilderSearchValueContext) {
  const currentValue = this.normalizeTimeValue(ctx.implicit?.value);
  const allowedConditions = ['at', 'not_at', 'at_before', 'at_after', 'before', 'after'];
  const isDisabled = !allowedConditions.includes(ctx.selectedCondition ?? '');

  return html`
    <igc-date-time-input
      .inputFormat=${"hh:mm tt"}
      .value=${currentValue}
      .disabled=${isDisabled}
      @igcChange=${(event: CustomEvent) => {
        ctx.implicit.value = event.currentTarget.value;
      }}>
      <igc-icon slot="prefix" name="clock" collection="material"></igc-icon>
    </igc-date-time-input>
  `;
}

Default Input テンプレートの例:

// Field definitions for string, number, and boolean types
{ field: 'ShipCountry', dataType: 'string' }
{ field: 'OrderID', dataType: 'number' }
{ field: 'IsRushOrder', dataType: 'boolean' }

// Template that handles all these types
private buildDefaultInput(ctx: IgcQueryBuilderSearchValueContext) {
  const dataType = ctx.selectedField?.dataType;
  const isNumber = dataType === 'number';
  const isBoolean = dataType === 'boolean';
  
  const placeholder = ctx.selectedCondition === 'inQuery' || ctx.selectedCondition === 'notInQuery'
    ? 'Sub-query results'
    : 'Value';

  const inputValue = ctx.implicit?.value ?? '';
  const disabledConditions = ['empty', 'notEmpty', 'null', 'notNull', 'inQuery', 'notInQuery'];
  const isDisabled = isBoolean || !ctx.selectedField || disabledConditions.includes(ctx.selectedCondition ?? '');

  return html`
    <igc-input 
      .value=${inputValue}
      ?disabled=${isDisabled}
      placeholder=${placeholder}
      type=${isNumber ? 'number' : 'text'}
      @input=${(event: Event) => {
        const target = event.target as HTMLInputElement;
        ctx.implicit.value = isNumber
          ? target.value === '' ? null : Number(target.value)
          : target.value;
      }}>
    </igc-input>
  `;
}

フォーマッター

条件が編集モードではないときにチップ内へ表示される検索値の見た目を変更するには、fields 配列に formatter 関数を設定します。検索値には次のように value 引数からアクセスできます:

this.ordersFields = [
  { field: 'OrderID', dataType: 'number' },
  { field: 'ShipCountry', dataType: 'string' },
  {
    field: 'OrderDate',
    dataType: 'date',
    formatter: (value: any) => value.toLocaleDateString(this.queryBuilder?.locale, { 
      month: 'short', 
      day: 'numeric', 
      year: 'numeric' 
    })
  },
  {
    field: 'Region',
    dataType: 'string',
    formatter: (value: any) => value?.text ?? value?.value ?? value
  }
];

デモ

この例では、Web Components Query Builder コンポーネントのヘッダーおよび検索値に対するテンプレート機能と formatter 機能を確認できます。

API リファレンス

QueryBuilder QueryBuilderHeader

その他のリソース

コミュニティは活発で、新しいアイデアをいつでも歓迎しています。