logo dile-components dile-components

dile-crud

The dile-crud component is the primary tool for building the CRUD system, combining various components from this library to implement the full functionality of a create, read, update, and delete (CRUD) system.

Additionally, dile-crud also offers batch operation management through the definition of action components.

Installation

npm i @dile/crud

Usage

Import the dile-crud component.

import '@dile/crud/components/crud/crud.js';

Use the component.

<dile-crud
  .config=${this.config}
></dile-crud>

Properties

Methods

Events

This component is based on elements such as dile-crud-list, dile-crud-insert, dile-crud-update, dile-crud-item-delete, and many others. Therefore, all events documented in those components can be listened to in dile-crud. Please refer to the mentioned components for information on those events.

Specific events of dile-crud:

CRUD component CSS Custom Properties

It is possible to customize the appearance of CRUD components using Custom CSS Properties. This component uses elements such as dile-button and dile-input-search, and therefore supports the custom CSS properties documented for those components. It also uses CSS custom properties that are consistently applied across the entire component catalog, some of which are detailed on the theming page.

Additionally, there are specific custom properties for dile-crud, which are detailed in the following table:

Custom property Description Default
--filter-list-margin Margin of the filter list container 0 0.5rem
--filter-list-gap Gap between filter list items 0.5rem
--dile-crud-h1-font-size Font size for h1 headings in CRUD forms 1.5rem
--dile-crud-h1-font-weight Font weight for h1 headings in CRUD forms bold
--dile-crud-h1-margin Margin for h1 headings in CRUD forms 0 0 1rem 0
--dile-crud-insert-button-padding-y Vertical padding for the insert button 0.4rem
--dile-crud-insert-button-padding-x Horizontal padding for the insert button 0.5rem
--dile-crud-insert-button-font-size Font size for the insert button 1rem
--dile-crud-action-color Color for action buttons and icons #888
--dile-on-crud-action-color Text/foreground color for action buttons #fff
--dile-crud-order-switch-icon-color Icon color for the order-switch component in sort form var(--dile-secondary-color, #888)
--dile-crud-filters-label-font-size Font size for filter overlay and labels (affects entire filters popup) var(--dile-input-label-font-size, 1em)
--dile-crud-filters-select-font-size Font size for select fields in filters var(--dile-select-font-size, 0.875em)
--dile-crud-filters-input-padding Padding for input/select fields in filters var(--dile-input-padding, 5px)
--dile-crud-filters-label-margin-bottom Margin bottom for filter labels var(--dile-input-label-margin-bottom, 4px)
--dile-crud-filters-field-margin Margin for filter fields (checkboxes and other elements) 0.4rem 0
--dile-crud-filters-inline-margin Margin of the card wrapping the always-visible filters form 0.5rem 0
--dile-crud-filters-inline-background-color Background color of the always-visible filters card var(--dile-very-light-color, #f5f5f5)
--dile-crud-filters-inline-border Border of the always-visible filters card none
--dile-crud-filters-inline-gap Gap between filter fields in the always-visible filters grid (from 500px viewport width) 1rem
--dile-crud-filters-inline-columns-medium Grid columns for the always-visible filters form on viewports from 500px wide 1fr 1fr
--dile-crud-filters-inline-columns-large Grid columns for the always-visible filters form on viewports from 1200px wide 1fr 1fr 1fr
--dile-crud-single-action-background-color Background color of the single action dispatcher button var(--dile-primary-color, #7BB93D)
--dile-crud-single-action-text-color Text color of the single action dispatcher button var(--dile-on-primary-color, #fff)
--dile-crud-single-action-border-color Border color of the single action dispatcher button var(--dile-primary-dark-color, #12354d)
--dile-crud-single-action-hover-background-color Background color of the single action dispatcher button on hover var(--dile-primary-light-color, #f3f3ae)
--dile-crud-single-action-hover-text-color Text color of the single action dispatcher button on hover var(--dile-on-primary-light-color, #303030)
--dile-crud-single-action-hover-border-color Border color of the single action dispatcher button on hover var(--dile-primary-color, #666666)
--dile-crud-direct-action-background-color Background color of direct single action buttons var(--dile-button-background-color, var(--dile-primary-color, #7BB93D))
--dile-crud-direct-action-text-color Text color of direct single action buttons var(--dile-button-text-color, var(--dile-on-primary-color, #fff))
--dile-crud-direct-action-border-color Border color of direct single action buttons var(--dile-button-border-color, var(--dile-primary-dark-color, #12354d))
--dile-crud-direct-action-hover-background-color Background color of direct single action buttons on hover var(--dile-button-hover-background-color, var(--dile-primary-light-color, #f3f3ae))
--dile-crud-direct-action-hover-text-color Text color of direct single action buttons on hover var(--dile-button-hover-text-color, var(--dile-on-primary-light-color, #303030))
--dile-crud-direct-action-hover-border-color Border color of direct single action buttons on hover var(--dile-button-hover-border-color, var(--dile-primary-color, #666666))
--dile-crud-direct-action-font-size Font size of direct single action buttons var(--dile-button-font-size, 1rem)
--dile-crud-direct-action-text-transform Text transform of direct single action buttons var(--dile-button-text-transform, none)
--dile-crud-direct-action-font-weight Font weight of direct single action buttons var(--dile-button-font-weight, bold)

Generating CRUD Components for Entities with the CLI

You can use the Dile Components CLI to create the scaffolding for CRUD components that are already configured for use with dile-crud. To do this, run the following command:

dile g-crud post/post-crud

This command requires that the resource configuration file has been generated previously, which can be done with the CLI command g-resource-config. You can find more information on the resource configuration page.

You can get the help with the complete options for this command by running:

dile g-crud --help

Configuration

Please refer to the general documentation on the CRUD library to find the established mechanisms for configuring the dile-crud component.

belongTo and relationId configuration

These fields facilitate the implementation of a type of filtering on the items that a dile-crud component will allow you to manage. For example, if you are trying to view invoices for the customer with id=10, belongsTo would be set to "customer" and relationId would be set to "10."

This is useful, for example, for managing a specific record on an admin page, like those that can be created using the dile-crud-single component. For instance, when viewing the details of a country, you could use a dile-crud component configured with belongsTo and relationId to manage all the states within that country directly from the dile-crud-single component.

Note that these configurations must be supported by the backend. The listing component will handle sending the necessary query strings to the backend, ensuring that the records delivered for the listings in this dile-crud component are properly filtered.

Unpaginated Crud Example

For the correct functioning of the dile-crud component, a configuration object is required. Depending on the functionalities requested through the configuration object, various additional components from the CRUD library may also be needed.

Configuration object

Find the guides for creating the configuration object on the resource configuration page.

<script type="module">
import { html } from 'lit';
import { CrudConfigBuilder } from '@dile/crud/lib/CrudConfigBuilder';
import '@dile/ui/components/pages/pages';

// For the correct functioning of this declaration in the demo system, we have defined the variable with the configuration object globally. Normally, it would be created in a module and exported.
window.countryConfig = new CrudConfigBuilder('https://timer.escuelait.com/api/countries', {
  templates: {
    item: (country) => html`<demo-country-item .country=${country}></demo-country-item>`,
    insertForm: () => html`<demo-country-form id="insertform"></demo-country-form>`,
    updateForm: () => html`<demo-country-form id="updateform"></demo-country-form>`,
    help: () => html`<p>This is the help provided to the countries resource.</p>`,
    detail: (country) => html`<demo-country-detail .country="${country}"></demo-country-detail>`,
    relations: (country) => html`<demo-country-relations .country=${country}></demo-country-relations>`,
    formSingleActions: (actionName, country) => html`
        <dile-pages attrForSelected="action" selected="${actionName}">
            <demo-set-europe-as-continent-action action="SetEurope" .country=${country}></demo-set-europe-as-continent-action>
            <demo-set-asia-as-continent-action action="SetAsia" .country=${country}></demo-set-asia-as-continent-action>
        </dile-pages>
    `,
  },
  customization: {
    disablePagination: true,
    disableHelp: false,
    disableKeywordSearch: false,
    disableSort: false,
    disableFilter: false,
  },
  actions: {
    single: [
      {
        name: "SetEurope",
        label: "Set Europe as continent"
      },
      {
        name: "SetAsia",
        label: "Set Asia as continent"
      },
    ]
  },
  labels: {
    helpTitle: 'Country help',
  },
  sort: {
    options: [
      {
        name: 'name',
        label: 'Name',
        direction: 'desc'
      },
    ],
    initialSortField: 'name',
  },
  availableFilters: [
      {
        name: 'continent',
        label: 'Continent',
        active: false,
        value: false,
        type: 'select',
        options: [
            {
                value: 'Europe',
                label: 'Europe'
            },
            {
                value: 'Africa',
                label: 'Africa'
            },
            {
              value: 'Asia',
              label: 'Asia'
          },
        ]
      },
    ],
});
</script>

Item component

The item component serves as a template to display each of the elements in the list.

<script type="module">
import { LitElement, html, css } from 'lit';

export class DemoCountryItem extends LitElement {
  static styles = [
    css`
      :host {
        display: block;
        color: var(--dile-on-background-color, #303030);
      }
      span {
        font-style: italic;
        font-size: 0.9rem;
      }
    `
  ];

  static get properties() {
    return {
      country: { type: Object }
    };
  }

  render() {
    return html`
      ${this.country.name} - 
      <span @click=${this.dispatchContinent}>${this.country.continent}</span>
    `;
  }

  dispatchContinent() {
    this.dispatchEvent(new CustomEvent('continent-event', { 
      bubbles: true,
      composed: true,
      detail: this.country
    }));
  }
}
customElements.define('demo-country-item', DemoCountryItem);
</script>

Resource form component

The resource form provides the necessary fields for adding new elements to the resource. In this example, the same form is used for both insertions and edits, but it is possible to have a different form for each operation.

Instructions on how to create these forms can be found in the dile-crud-insert and dile-ajax-form component documentation.

<script type="module">
import { LitElement, html, css } from 'lit';
import '@dile/ui/components/input/input.js';
import '@dile/ui/components/select/select.js';
import { DileForm } from '@dile/ui/mixins/form'

export class DemoCountryForm extends DileForm(LitElement) {
  static styles = [
    css`
      :host {
        display: block;
      }
    `
  ];

  render() {
    return html`
      <dile-input label="Country name" name="name" id="name" hideErrorOnInput></dile-input>
      <dile-input label="Slug" name="slug" id="slug" hideErrorOnInput></dile-input>
      <dile-select name="continent" id="continent" label="Continent" hideErrorOnInput>
        <select slot="select">
          <option value="">Select...</option>
          <option value="Europe">Europa</option>
          <option value="South America">América del Sur</option>
          <option value="North America">Norte América</option>
          <option value="Asia">Asia</option>
          <option value="Africa">Africa</option>
          <option value="Oceania">Oceania</option>
        </select>
      </dile-select>
    `;
  }
}
customElements.define('demo-country-form', DemoCountryForm);
</script>
<demo-country-form></demo-country-form>

Crud component

<script type="module">
import { LitElement, html, css } from 'lit';
import '@dile/crud/components/crud/crud';

export class DemoCountryCrud extends LitElement {
  static styles = [
    css`
      :host {
        display: block;
      }
    `
  ];

  static get properties() {
    return {
      config: { type: Object },
    };
  }

  constructor() {
    super();
    this.config = window.countryConfig.getConfig();
  }

  render() {
    return html`
      <dile-crud
        .config="${this.config}"
      ></dile-crud>
    `;
  }
}
customElements.define('demo-country-crud', DemoCountryCrud);
</script>
<demo-country-crud></demo-country-crud>

Paginated Crud Example

Configuration object

Find the guides for creating the configuration object on the resource configuration page.

<script type="module">
import { html } from 'lit';
import { CrudConfigBuilder } from '@dile/crud/lib/CrudConfigBuilder';
import { ResponseApiAdapter } from '@dile/crud/lib/ResponseApiAdapter';

class BoardGameResponseApiAdapter extends ResponseApiAdapter {
  getElementList() {
    return this.response.data.result.data;
  }
}
// For the correct functioning of this declaration in the demo system, we have defined the variable with the configuration object globally. Normally, it would be created in a module and exported.
window.boardGameConfig = new CrudConfigBuilder('https://timer.escuelait.com/api/board-games', {
  customization: {
    hideCountSummary: false,
    hideCheckboxSelection: false,
    disablePagination: false,
    disableHelp: true,
    disableKeywordSearch: false,
    disableSort: false,
    disableFilter: false,
  },
  sort: {
    options: [
      {
        name: 'name',
        label: 'Name',
        direction: 'asc'
      },
      {
        name: 'year',
        label: 'Year',
        direction: 'desc'
      },
    ],
    initialSortField: 'year',
  },
  availableFilters: [
    {
      name: 'essential',
      label: 'Is essential',
      active: false,
      value: false,
      type: 'boolean',
    },
  ],
  responseAdapter: new BoardGameResponseApiAdapter(),
  actions: {
    list: [
      {
        label: 'Delete board games',
        name: 'DeleteAction'
      },
      {
        label: 'Change Essential',
        name: 'DemoChangeEssentialAction'
      },
      {
        label: 'Change Name',
        name: 'DemoChangeNameAction'
      },
    ],
  },
  templates: {
    item: (boardGame) => html`<demo-board-game-item .boardGame=${boardGame}></demo-board-game-item>`,
    insertForm: (belongsTo, relationId) => html`<demo-board-game-form id="insertform" belongsTo="${belongsTo}" relationId="${relationId}"></demo-board-game-form>`,
    updateForm: () => html`<demo-board-game-form id="updateform"></demo-board-game-form>`,
    formActions: (actionName, actionIds) => html`
        <dile-pages attrForSelected="action" selected="${actionName}">
            <dile-crud-delete-action action="DeleteAction"></dile-crud-delete-action>
            <demo-change-essential-action action="DemoChangeEssentialAction" .actionIds=${actionIds}></demo-change-essential-action>
            <demo-change-name-action action="DemoChangeNameAction" .actionIds=${actionIds}></demo-change-name-action>
        </dile-pages>
    `,
  },
});
</script>

Item component

The item component serves as a template to display each of the elements in the list.

<script type="module">
import { LitElement, html, css } from 'lit';

export class DemoBoardGameItem extends LitElement {
  static styles = [
    css`
      :host {
        display: block;
        color: var(--dile-on-background-color, #303030);
      }
    `
  ];

  static get properties() {
    return {
      boardGame: { type: Object }
    };
  }

  render() {
    return html`
      ${this.boardGame.name} - (${this.boardGame.year})
    `;
  }
}
customElements.define('demo-board-game-item', DemoBoardGameItem);
</script>

Resource form component

The resource form provides the necessary fields for adding new elements to the resource. In this case, the same form is used for both insertions and edits, but it is possible to have a different form for each operation.

Instructions on how to create these forms can be found in the dile-crud-insert component documentation and dile-ajax-form.

<script type="module">
import { LitElement, html, css } from 'lit';
import '@dile/ui/components/input/input.js';
import '@dile/ui/components/input/input-integer.js';
import '@dile/ui/components/checkbox/checkbox.js';
import '@dile/crud/components/ajax-select-crud/ajax-select-crud';

import { DileForm } from '@dile/ui/mixins/form'

export class DemoBoardGamesForm extends DileForm(LitElement) {
  static styles = [
    css`
      :host {
        display: block;
      }
    `
  ];

  static get properties() {
    return {
      belongsTo: { type: String },
      relationId: { type: String },
    };
  }

  firstUpdated() {
    if(this.belongsTo == "country" && this.relationId) {
      this.shadowRoot.getElementById('countryselect').value = this.relationId;
    }
  }

  render() {
    return html`
      <dile-input label="Nombre" name="name" id="name" hideErrorOnInput></dile-input>
      <dile-input label="Slug" name="slug" id="slug" hideErrorOnInput></dile-input>
      <dile-input-integer name="year" label="Year" hideErrorOnInput id="year"></dile-input-integer>
      <dile-ajax-select-crud
          id="countryselect"
          idProperty="id"
          name="country_id"
          label="País"
          endpoint="https://timer.escuelait.com/api/countries" 
          queryStringVariable="keyword"
          placeholder="Buscar país"
          resultDataProperty="data"
          displayProperty="name"
          selectDefaultPlaceholder="Seleccionar país..."
      ></dile-ajax-select-crud>
      <p><dile-checkbox name="essential">Essential</dile-checkbox></p>
    `;
  }
}
customElements.define('demo-board-game-form', DemoBoardGamesForm);
</script>
<demo-board-game-form></demo-board-game-form>

Action component

For the purposes of this CRUD component demo, we will include a custom batch action.

You can find more information about actions in the actions section of the CRUD documentation.

Change Essentian Action

<script type="module">
import { LitElement, html, css } from 'lit';
import { DileForm } from '@dile/ui/mixins/form';
import '@dile/ui/components/select/select.js';

export class DemoChangeEssentialAction extends DileForm(LitElement) {
  static styles = [
    css`
      :host {
        display: block;
      }
    `
  ];

  static get properties() {
    return {
      actionIds: { type: Array }
    };
  }

  constructor() {
    super();
    this.actionIds = [];
  }

  render() {
    return html`
      <p>Change essential game state of ${this.actionIds.length} elements.</p>
      <dile-select name="essential">
        <select slot="select">
          <option value="0">Not Essential</option>
          <option value="1">Essential</option>
        </select>
      </dile-select>
    `;
  }
}
customElements.define('demo-change-essential-action', DemoChangeEssentialAction);
</script>

Change Name Action

<script type="module">
import { LitElement, html, css } from 'lit';
import { DileForm } from '@dile/ui/mixins/form';
import '@dile/ui/components/input/input.js';

export class DemoChangeNameAction extends DileForm(LitElement) {
  static styles = [
    css`
      :host {
        display: block;
      }
    `
  ];

  static get properties() {
    return {
      actionIds: { type: Array }
    };
  }

  constructor() {
    super();
    this.actionIds = [];
  }

  render() {
    return html`
      <p>Please inform a new name for the element:</p>
      <dile-input name="name"></dile-input>
    `;
  }
}
customElements.define('demo-change-name-action', DemoChangeNameAction);
</script>

Crud component

<script type="module">
import { LitElement, html, css } from 'lit';

class DemoBoardGameCrud extends LitElement {
  static styles = [
    css`
      :host {
        display: block;
      }
    `
  ];

  static get properties() {
    return {
      config: { type: Object },
    };
  }

  constructor() {
    super();
    this.config = window.boardGameConfig.getConfig();
    this.config.customization.hideCheckboxSelection = false;
  }

  render() {
    return html`
      <dile-crud
        .config="${this.config}"
      ></dile-crud>
    `;
  }
}
customElements.define('demo-board-game-crud', DemoBoardGameCrud);
</script>
<demo-board-game-crud></demo-board-game-crud>

Always Visible Filters

By default, filters are hidden behind a button that opens an overlay panel (see the dile-crud-filters component used in the examples above). Setting the filtersAlwaysVisible property to true renders the filters form directly below the nav actions bar instead, using the dile-crud-filters-inline component internally.

This is useful when filters are a primary part of the workflow and you want them visible at all times, without requiring an extra click to open them.

<script type="module">
import { LitElement, html, css } from 'lit';

class DemoBoardGameCrudFiltersAlwaysVisible extends LitElement {
  static styles = [
    css`
      :host {
        display: block;
      }
    `
  ];

  static get properties() {
    return {
      config: { type: Object },
    };
  }

  constructor() {
    super();
    this.config = window.boardGameConfig.getConfig();
  }

  render() {
    return html`
      <dile-crud
        filtersAlwaysVisible
        .config="${this.config}"
      ></dile-crud>
    `;
  }
}
customElements.define('demo-board-game-crud-filters-always-visible', DemoBoardGameCrudFiltersAlwaysVisible);
</script>
<demo-board-game-crud-filters-always-visible></demo-board-game-crud-filters-always-visible>

You can customize the appearance of the filters card and its grid layout with the --dile-crud-filters-inline-* custom properties documented in the CSS Custom Properties table above.

Single Action Dispatcher

When a resource only needs a single batch action (for example, just deleting the selected items), opening the actions dropdown menu to pick from a list of one is unnecessary friction. Setting singleActionDispatcher to the name of an action renders a direct button for that action instead, skipping the dropdown selection step entirely.

The action definition (label, destructive, etc.) is looked up by name in config.actions.list, using the same action object shape documented on the actions configuration page. The button is only shown once at least one item is selected via checkboxes, exactly like the standard batch actions menu.

<script type="module">
import { LitElement, html, css } from 'lit';

class DemoBoardGameCrudSingleAction extends LitElement {
  static styles = [
    css`
      :host {
        display: block;
      }
    `
  ];

  static get properties() {
    return {
      config: { type: Object },
    };
  }

  constructor() {
    super();
    this.config = window.boardGameConfig.getConfig();
    this.config.customization.hideCheckboxSelection = false;
    this.config.actions.single = [
      {
        label: 'Delete board games',
        name: 'DeleteAction',
        destructive: true,
      },
    ];
  }

  render() {
    return html`
      <dile-crud
        singleActionDispatcher="DeleteAction"
        .config="${this.config}"
      ></dile-crud>
    `;
  }
}
customElements.define('demo-board-game-crud-single-action', DemoBoardGameCrudSingleAction);
</script>
<demo-board-game-crud-single-action></demo-board-game-crud-single-action>

Note that config.templates.formActions is reused as-is for the single action dispatcher (it receives the action name and the array of selected item ids, just like batch actions do). Only the action metadata (label, destructive) is sourced from actions.single instead of actions.list.

You can customize the button's colors, including its hover state, with the --dile-crud-single-action-* custom properties documented in the CSS Custom Properties table above.