diff --git a/README.md b/README.md index 1ac7da2..7639c54 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Data table for EmberJS ### Add basic Ember Data Table -Find an adaptation of Ember Data Table for the design framework of your choice or implement a custom variant for your application. This tutorial uses `RawDataTable`. +Find an adaptation of Ember Data Table for the design framework of your choice or implement a custom variant for your application. This tutorial uses `RawDataTable`. Alternatively look at the examples in [the dummy app](/addon/tests/dummy/app). Generate a route for products first: @@ -22,38 +22,86 @@ The tutorial assumes a model exists with `label` and `price` which you can gener ember g model product label:string price:number ``` -Next you'll fetch content from the back-end using standard model hooks and query parameters. Extending from the provided Route and Controller is the shortest form. - +Next you'll fetch content from the back-end using standard model hooks and query parameters. For the route stored in `/app/routes/products/index.js` write: ```javascript -import DataTableRoute from 'ember-data-table/route'; +import { inject as service } from '@ember/service'; +import { action } from '@ember/object'; +import Route from '@ember/routing/route'; + +export default class DataTableRoute extends Route { + @service store; -export default class ProductsIndexRoute extends DataTableRoute { modelName = 'product'; + + queryParams = { + filter: { refreshModel: true }, + page: { refreshModel: true }, + size: { refreshModel: true }, + sort: { refreshModel: true }, + }; + + model(params) { + const options = { + sort: params.sort, + page: { + number: params.page, + size: params.size, + }, + }; + if (params.filter) { + options['filter'] = params.filter; + } + return this.store.query(this.modelName, options); + } + + @action + loading(transition) { + // eslint-disable-next-line ember/no-controller-access-in-routes + let controller = this.controllerFor(this.routeName); + + if(controller) { + controller.isLoadingModel = true; + + transition.finally(function () { + controller.isLoadingModel = false; + }); + } + + return true; // bubble the loading event + } } ``` For the controller stored in `/app/controllers/product/index.js` write: ```javascript -import DataTableController from 'ember-data-table/controller'; +import { tracked } from '@glimmer/tracking'; +import Controller from '@ember/controller'; -export default class ProductsIndexController extends DataTableController {} +export default class DataTableController extends Controller { + queryParams = ['size', 'page', 'filter', 'sort']; + + @tracked size = 10; + @tracked page = 0; + @tracked filter = ''; + @tracked sort = ''; + @tracked isLoadingModel = false; +} ``` These steps are the same for any Ember Data Table flavour, the following visualizes `RawDataTable`: ```hbs +``` + ### Overwrite the header labels Supply column headers by adding extra properties to the fields attribute, split by a colon. A single `_` gets replaced by a space and two underscores get replaced by a single underscore -``` +```hbs ``` +Alternatively, pass the field (key `attribute`) and header (key `label`) via an object. This is only possible when passing the fields via an array. +```hbs + +``` + + ## Discussions ### Why one big template file @@ -150,6 +221,9 @@ The default implementation will be used most often, but the end-user receives an ## Reference +### Serializer +Ember Data Table expects meta (pagination) information in a specific format. The provided [serializer](/addon/serializer.js) can be used in case of a JSONAPI, or it can be used as an inspiration for a custom serializer. + ### Arguments to Ember Data Table These arguments should be supported by specific design implementations too. @@ -158,65 +232,68 @@ These arguments should be supported by specific design implementations too. The passing of data from route and controller, and moving data back up. -- `@content` :: Data to be rendered. In case this has a `meta` +- `@content` :: Data (an array of items) to be rendered. In case this has a `meta` property, this is used as default to derive amount of results and back-end pagination offset. -- `@page` and `@updatePage` :: Indicates the current page number and the +- `@page` and `@updatePage` :: Set the current page number and the function called to update the current page. -- `@size` and `@updatePageSize` :: Indicates the current page size and +- `@size` and `@updatePageSize` :: Set the current page size and the function called to update the current page size. -- `@sort` and `@updateSort` :: Returns current sorting for data table - and a function to update the sorting. -- `@filter` and `@updateFilter` :: Supplies the user filter string and - the function to call for updating that string. +- `@sort` and `@updateSort` :: Set the current sorting for data table + and the function called to update the sorting. +- `@filter` and `@updateFilter` :: Set the user search string and + the function called to update that string. - `@total` :: The total amount of results across all pages. If not set, `@meta.count` or `@content.meta.count` is tried. -- `@isLoading` :: Truthy if the Data Table is currently loading data. +- `@isLoading` :: Whether to show the Data Table in its loading state. - `@meta` :: Meta may be provided in `@content.meta` or it may be - provided in a separate property. If supplied, it may be used to + provided in this property. If supplied, it may be used to determine the back-end pagination offset from - `@meta.links.first.number` (often `0` but sometimes `1`) and - amount of results as alternative to `@total` from `@meta.count`. + `@meta.links.first.number` (often `0` but sometimes `1`), pagination in + `@meta.pagination` and + amount of results from `@meta.count` as an alternative to `@total`. #### Ember Data Table visualization configuration How to show different things in Ember Data Table -- `@fields` :: List of fields to render with extra options. The fields are split by spaces. Splitting a field with a colon (`:`) makes the first element be the attribute and the second be the label. Use an `_` to render a space in the label. E.g.: `@fields="label:Name priceInEuros:Euro_price"`. -- `@sortableFields` :: List of fields by which the user may sort. - Fields should use the attribute names of `@fields` and are split by - spaces. By default all fields are sortable. Set to an empty list to - disable sorting. +- `@fields` :: Array of objects/strings or space-separated string of fields to render (in given order) with extra options. Each field can consists of two parts, split by a colon (`:`) for string syntax. The first part is the attribute (key `attribute`, in string syntax `_` are rendered as spaces), the second an optional label (key `label`). If no label is provided, the attribute is used as the label. E.g.: `@fields="label:Name priceInEuros:Euro_price available"` or `@fields={{array "label:Name" (hash attribute="priceInEuros" label="Euro price") "available"}}`. +- `@sortableFields` :: Array or space-separated string of fields by which the user may sort. + Fields should use the attribute names of `@fields`. By default all fields are sortable. Set to an empty list or empty string to disable sorting. - `@noDataMessage` :: Custom message to show when no data is available. The `:no-data-message` block can be used as an alternative to provide styling. - `@enableSearch` :: Set to false to disable search in the table. +- `@searchPlaceholder` :: Custom placeholder text for the search input box. Defaults to 'Search input'. - `@enableLineNumbers` :: Set to truthy to show line numbers in the table. -- `@links` :: Each row may contain a number of links. Different links - are split by a space in the configuration. Each link consists of one - to three parts split by a colon. The first part is the route, the - second is the label, the third is an icon to use instead of the label - if supported (screen readers should see the label still). E.g.: - `@links="products.edit:edit:pencil products.show:open:file-earmark-richtext"`. +- `@sizes` :: Array or space-separated string of page size choices that should be shown in the pagination block. Defaults to `[5, 10, 25, 50, 100]`. Set to an empty list or empty string to hide. +- `@links` :: Array of objects/strings or a space-separated string of links with extra options. + Each row may contain a number of clickable links rendered in a separate column. Each link consists of one + to three parts, split by a colon for string syntax. The first part is the route (key `route`), the + second is the label (key `label`, in string syntax `_` are rendered as spaces), the third is an icon (key `icon`) to use instead of the label + if supported (screen readers should see the label still). E.g. (both equivalent): + `@links="products.edit:edit:pencil products.show:open:file-earmark-richtext"` + `@links={{array (hash route="products.edit" label="edit" icon="pencil") "products.show:open:file-earmark-richtext"}}`. Note that only the route is required in which case the label is - derived and no icon is shown. The link by default receives the `id` - of the item but this is configurable using the `@linksModelProperty` + derived and no icon is shown. By default the link receives the `id` + of the item, but is configurable using the `@linksModelProperty` attribute (see below). -- `@customHeaders` :: List of attributes for which a custom header will - be rendered through the `:data-header` named block. Each of the - attributes mentioned here won't render the default header but will - instead dispatch to the named block. Check which attribute is being - rendered in the named block to render the right label. Verify in the - implementation you override which actions are set on the columns how - to support sorting if needed. +- `@customHeaders` :: An object (hash) or an array/space-separated string of attributes. + When passing an array/space-separated string, the attributes will be rendered through the `:data-header` named block, instead of rendering the default header. + When passing an object, set as key the attribute and value the component to use for rendering. The component will receive in `@header` the same hash given to the `:data-header` block. If value is empty, the attribute will be rendered through the `:data-header` named block. + For the `:data-header` named block, check which attribute is being rendered to render the right label. + Check in the implementation you override how sorting is supported, if sorting is needed for this header. ```hbs <:data-header as |header|> {{#if (eq header.attribute "label")}} @@ -228,16 +305,17 @@ How to show different things in Ember Data Table ``` -- `@customFields` :: List of attributes for which the fields will - receive a custom rendering. This will render the individual cell - values based on the `:data-cell` custom block. You may use the - attribute name to verify which attribute the custom block is rendering - for. +- `@customFields` :: An object (hash) or an array/space-separated string of attributes. + When passing an array/space-separated string, the attributes will be rendered through the `:data-cell` named block. + When passing an object, set as key the attribute and value the component to use for rendering. The component will receive in `@cell` the same hash given to the `:data-cell` block. If value is empty, the attribute will be rendered through the `:data-cell` named block. + For the `:data-cell` named block, use the attribute name to verify which attribute the custom block is rendering for. ```hbs <:data-cell as |cell|> {{#if (eq cell.attribute "label")}} @@ -253,34 +331,35 @@ How to show different things in Ember Data Table - `@autoSearch` :: If truthy, search is automatically triggered without explicitly pressing search. If a number is provided, this is - the time in milliseconds before sending the request. If no number is - supplied a default is used. -- `@hasMenu` :: If not truthy, the component will show the supplied + the time in milliseconds to wait for input before sending the request (input douncing). + If not set, autosearch is enabled with a default wait of 2000ms. +- `@showMenu` :: If false, the component will hide the supplied menu. This allows controlling whether the menu should be shown dynamically. The menu may contain actions which act on the current selection. - `@enableSelection` :: Whether items should be selectable. Items are selectable across pages and may be acted on using the `:selection-menu-actions` or `:selection-menu` named blocks. +- `@initialSelection` :: The selection to use as long as the user has not changed the selection yet. +- `@selectionProperty` :: By default equality will be checked by direct comparison of objects, which works for e.g. ember-data records. If a specific property should be used for comparison (e.g. `uuid` when using mu-search), a property name or path can be supplied. - `@linksModelProperty` :: When a link is clicked the row must supply information to the link to indicate which item was clicked. By - default the `id` property is used but another attribute may be + default the `id` property is used, but another property name or path may be supplied if desired (such as `uuid` when using mu-search). An empty string will provide the full object. -- `@attributeToSortParams` :: Function which translates an attribute to - its sort parameters. The sort parameters is currently a hash which - contains a key (default `'asc'` and `'desc'` to indicate sorting up - and down) and the corresponding sort key which should be sent out of - Ember Data Table (and used in the sort hash to the back-end). More - options than `'asc'` and `'desc'` can be provided if the back-end - understands different sorting strategies. -- `@onClickRow` :: Action to be triggered when the row is clicked. This - is an alternative for the row link but it triggers an action rather - than following a route. +- `@attributeToSortParams` :: Function which translates an attribute name to + its sort parameters. The sort parameters are a hash + with key the sort name to use in the table and value the corresponding sort key to sent out of Ember Data Table (and used in the sort hash to the back-end via `updateSort`). + By default, for input `attributeName`, it returns `{ 'asc': 'attribute-name', 'desc': '-attribute-name'}`. + More options can be provided if the back-end understands different sorting strategies. - `@rowLink` :: Link to be used when users click on the full row. This is an easier click target for users than an icon on the side. Ideally - the that target is provided too. `@onClickRow` may be provided to + that target is provided too. `@onClickRow` may be provided to call a function instead but this is less accessible. +- `@onClickRow` :: Callback to be triggered when the row is clicked. + Receives the clicked item (from `@content`) as its first argument. This + is an alternative for the row link but it triggers an action rather + than following a route. - `@rowLinkModelProperty` :: When `@rowLink` is used, the `id` property of the model rendered in the row will be supplied to the link. The property may be overridden by this property. Set to `uuid` when using @@ -289,39 +368,40 @@ How to show different things in Ember Data Table #### Overriding Ember Data Table parts using named blocks -Various named blocks are offered, check your Ember Data Table design implementation to see which part needs to be overridden. A list is provided here for reference. - -- `search` :: Overrides the full search component. Receives a search hash with properties: - - `filter` :: User's filter - - `placeholder` :: Placeholder for the text search - - `autoSearch` :: Value for autoSearch as supplied by the user - (subject to change) - - `submitForm` :: Action which can be used to submit the search form - and trigger search update - - `handleAutoInput` :: Action which can handle auto input by - debouncing and updating the search string - - `handleDirectInput` :: Action which handles the event where a user - types, gets value from `event.target.value`. - -- `menu` :: Overrides the full menu rendering. Receives three positional arguments: +Various named blocks are offered, check your Ember Data Table design implementation to see which part needs to be overridden. A list is provided here for reference as used in `raw-data-table.hbs`. + +- `:search` :: Overrides the full search block. Receives a hash containing: + - `filter` :: User's filter string + - `placeholder` :: Placeholder for the text search input + - `autoSearch` :: Value for autoSearch as supplied by the user (boolean or number). + - `submitSearch` :: Action which can be used to trigger a search string update (`@updateFilter`) + - `handleInput` :: Action which expects and event (with value in `event.target.value`) + and updates the search string immediately or after a debounce time, depending on `@autoSearch` value. + - `handleAutoInput` :: Like `handleInput`, but always uses a debounce time (even if `@autoSearch` is falsy). + - `handleDirectInput` :: Like `handleInput`, but always updates immediately (ignoring `@autoSearch` value). + + +- `:menu` :: Overrides the full menu block. Receives a hash containing: - `General` :: Component with information about the General menu which is rendered when nothing is selected. The block given to General - receives an argument which should be passed to `:general-menu`. + receives one block parameter which should be passed to `:general-menu`. + See `:general-menu` for the parameter details of `General`. - `Selected` :: Component with information on handling selected items. - The block given to Selected receives `selected` which should be - passed to `:selection-menu`. + The block given to Selected receives one block parameter which should be + passed to `:selection-menu`. + See `:selection-menu` for the parameter details of `Selected`. + - `enableSelection` :: Whether selection is enabled. -- `general-menu` :: Implements the menu with actions which is shown when - no items are selected. Receives a hash with two items: +- `:general-menu` :: Implements the menu with actions which is shown when + no items are selected. Receives a hash containing: - `dataTable` :: The main DataTable object on which actions can be called. - `selectionIsEmpty` :: Whether items are currently selected or not. -- `selection-menu` :: This menu is rendered only when items have been +- `:selection-menu` :: This menu is rendered only when items have been selected. It's the main wrapper which contains `:selection-menu-actions` (which you'd likely want to override - instead) as well as some visual information on the selected items. It - receives a hash with four elements: + instead) as well as some visual information on the selected items. Receives a hash containing: - `selectionIsEmpty` :: Whether the selection is currently empty. - `selectionCount` :: The amount of items which are selected at this point. - `clearSelection` :: An action to clear the whole selection. @@ -329,109 +409,102 @@ Various named blocks are offered, check your Ember Data Table design implementat - `dataTable` :: The DataTable object. - `selection-menu-actions` :: Contains the actions which can be applied - to a selection. This is likely custom for each use of the Ember Data + to a selection, rendered at the same time as `:selection-menu`. + This is likely custom for each use of the Ember Data Table (versus the template). Receives the same argument as `:selection-menu`. -- `content` :: This block is the full table but without search, actions - or pagination. It must render the table tag and everything in it. It - receives a hash with three elements. +- `:content` :: This block is the full table but without search, menu actions + or pagination. It must render the table tag and everything in it. + Receives a hash containing: - `Header` :: The Header logical component which contains information - to render the header row. Supplying a block to Header will yield - with the content for the `:header` named block. + to render the header row. Has the same block parameter hash as `:full-header` below. - `Body` :: The Body logical component which contains information to - render each of the body rows. Supplying a block to Body will yield - with the content for the `:body` named block. + render each of the body rows. Has the same block parameter hash as `:body` below. - `dataTable` :: The DataTable object. -- `full-header` :: This block should render the `` with the header row - inside of it. Receives a hash with the following items: - - `enableSelection` :: Whether or not selection is enabled. - - `enableLineNumbers` :: Whether or not line numbers are enabled. - - `sort` :: Sort parameter. - - `updateSort` :: Function to update sorting. +- `:full-header` :: This block should render the `` with the header row + inside of it. Receives a hash containing: + - `enableSelection` :: Whether selection is enabled. + - `enableLineNumbers` :: Whether line numbers are enabled. + - `sort` :: Current sort parameter. + - `updateSort` :: Function to update sorting (see `@updateSort`). - `hasLinks` :: Whether custom links are provided for this table (as per the `@links` argument to DataTable). - `customHeaders` :: Headers which should be rendered in a custom way - as an array or strings. - - `fields` :: A complex fields object containing the information about - each column to be rendered: + as an array of strings. + - `fields` :: An array of complex fields object containing the information about + each data column to be rendered: - `attribute` :: the attribute to be rendered - `label` :: the label of the header - `isSortable` :: whether this column is sortable or not - `sortParameters` :: hash which indicates in which ways this field - can be sorted (ascending, descending, something else). See + can be sorted (ascending, descending, something else). See output of `@attributeToSortParams`. - `hasCustomHeader` :: whether this column has a custom header or - not (meaning we should render it through the `:data-header` - named block) + not (meaning it should be rendered through the `:data-header` + named block). - `isCustom` :: whether the field rendering should be custom or not (meaning data cells should be rendered through `:data-cell`). + - `customFieldComponent` :: Available if the field rendering should use this custom component for rendering. + - `customHeaderComponent` :: Available if the column header should use this custom component for rendering. - `dataHeadersInfo` :: information for the data headers. Supplied to `:data-headers` named block. - `ThSortable` :: Contextual component. When calling this component - `@field` must be supplied (to generate info for a given field when + `@field` must be supplied (to generate info for the specific field when looping over `header.fields`) and `@hasCustomBlock` which should - indicate whether a `:data-header` is given. Supplying a block to - ThSortable will yield with the content for the `:data-header` - named block. The aforementioned content also has a + indicate whether a `:data-header` block is given. Has the same block parameter hash as `:data-header` below. This block parameter contains `renderCustomBlock` which can be used to detect whether a custom block should be rendered for this block or not. -- `data-headers` :: This is inside the `` of the `` and +- `:data-headers` :: This is inside the `` of the `` and should render all headers for the attributes. Thus ignoring the - headers for selection, numbers and actions. It receives a hash - containing the following elements: - - `fields` :: The fields to be rendered (see `fields` above for all - the attributes). + headers for selection, numbers and actions. Receives a hash + containing: + - `fields` :: The fields to be rendered (same `fields` as `:full-header`). - `customHeaders` :: Headers which should be rendered in a custom way - as an array or strings. + as an array of strings. - `sort` :: Sort parameter. - - `updateSort` :: Function to update sorting. -- `data-header` :: Renders a custom header which should handle sorting - etc. Receives a hash with the following elements: + - `updateSort` :: Function to update sorting (see `@updateSort`). +- `:data-header` :: Renders a custom header for headers specified in `@customHeaders`, which should handle sorting etc. Receives a hash containing: - `label` :: Label of the header. - `attribute` :: Attribute which will be rendered in this column. - `isSortable` :: Whether this column is sortable or not. - `isSorted` :: Whether sorting is applied to this header or not. - `toggleSort` :: Action which switches to the next sorting method - (e.g.: from `'asc'` to `'desc'` or from `'desc'` to nothing). + (e.g.: from `'asc'` to `'desc'` or from `'desc'` to nothing by default). - `nextSort` :: Next way of sorting. This is clear for - `["asc","desc",""]` but users may have provided other sorting - methods through `@attributeToSortParams`. - - `isAscending` :: Are we sorting ascending now? - - `isDescending` :: Are we sorting descending now? - - `sortDirection` :: What's the key on which we're sorting now (e.g.: `"desc"`) - - `renderCustomBlock` :: Should a custom block be rendered for this data header? - - `isCustom` :: Is the header explicitly marked to render custom? - - `hasCustomHeaders` :: Are there any custom headers to be rendered? - -- `actions-header` :: Header which will contain all actions. Receives no arguments. - -- `body` :: Renders the full body of the table, including the `` + `['asc','desc','']` but users may have provided other sorting + methods through `@attributeToSortParams`. The order is always alphabetically. + - `isAscending` :: Whether the current sorting is ascending (`'asc'`). + - `isDescending` :: Whether the current sorting is descending (`'desc'`). + - `sortDirection` :: What's the key on which we're sorting now (e.g.: `'desc'`) + - `renderCustomBlock` :: Whether a custom block should be rendered for this data header. + - `isCustom` :: Truthy if the header is explicitly marked to render custom. + - `hasCustomHeaders` :: Truthy if there are any custom headers to be rendered. + +- `:actions-header` :: Header which will contain all actions. Receives no arguments. + +- `:body` :: This block renders the full body of the table, and should include the `` tag. Receives a hash containing: - - `isLoading` :: Is the data being loaded at this point? Probably - need to render `:body-loading` named block then. - - `content` :: The actual content of this Data Table. - - `offset` :: The index of the first element in this data table. - - `wrappedItems` :: Rows of the data table in a way through which they - can be selected. + - `isLoading` :: Whether the data is being loaded. + Need to render `:body-loading` named block then. + - `content` :: The actual content of this Data Table (all items). + - `offset` :: The absolute index of the first element of the current page. - `enableLineNumbers` :: Whether line numbers are enabled or not. - `hasClickRowAction` :: Whether something needs to happen when the row is clicked. Either because there is an `@onClickRow` or because there is a `@rowLink`. - - `onClickRow` :: Action to be called when user clicked on a row, if - supplied by user of this Data Table. - `toggleSelected` :: Action which allows to toggle the selection - state of the current row. Should receive the an element from - `wrappedItems` as first element and the event that caused it (will - check `event.target.fetched`) as second argument. + state of the current row. Should receive the item to toggle from + `content` as first element and the event that caused it (will + check `event.target.checked`) as second argument. - `selection` :: Currently selected items. - `enableSelection` :: Whether selection of items is enabled. - `linkedRoutes` :: Array of objects describing each of the routes which should be linked as custom links per row. Each item is a hash with the following elements: - `route` :: The route to which we should link. - - `label` :: The human-readable label for the route if supplied. + - `label` :: The human-readable label for the route, if supplied. - `icon` :: The icon which should be rendered for the link, if supplied. - `linksModelProperty` :: The property of the model which should be supplied to the route (e.g.: `id` for the id or `""` if the whole @@ -447,88 +520,80 @@ Various named blocks are offered, check your Ember Data Table design implementat rendered. See `fields` higher up. - `Row` :: Contextual component handling the logic of an individual row. This has to be called for each row in the visible table and it - should receive `@wrapper` for the element of `wrappedItems` we're - rendering here, as well as the `@index` for the index we're looping - over here. The `@index` is a local index for this rendering - regardless of the page, so you can use `{{#each body.wrappedItems as - |wrapper index|}}...{{/each}}`. -- `body-loading` :: Renders a custom body loading message supplied in - this invocation of Ember Data Table. -- `row` :: Renders an individual row, including the `` tag. This is - the row with both the data elements as well as with the meta elements - such as selection of items and links. Receives a hash with the - following elements: - - `wrapper` :: An object containing the item and the selection status. + should receive in `@item` the element of `content` we're + rendering here, as well as the index we're looping + over here in `@index`. The `@index` is a local index for this rendering + regardless of the page, so you can use `{{#each body.content as + |item index|}}...{{/each}}`. +- `:body-loading` :: Block to show a custom loading message block. +- `:row` :: Renders an individual row, including the `` tag. This is + the row with both the data columns as well as the meta columns + such as selection of items and links. Receives a hash containing: - `item` :: Actual item to be rendered in this row. - `enableLineNumbers` :: See above. - `lineNumber` :: See above. - `enableSelection` :: See above. - - `selected` :: Whether this row is selected or not. - - `isSelected` :: Whether this item is selected or not (same as - selected). - - `toggleSelected` :: See above. + - `isSelected` :: Whether this item is selected or not. + - `toggleSelected` :: See above, but the row item is already passed. - `hasClickRowAction` :: See above. - - `onClickRow` :: See above. + - `rowClicked` :: Function to be called when user clicked on this row. - `linkedRoutes` :: A copy of `linkedRoutes` as mentioned above but adding the `model` key which contains the specific model to supply to the linked route for this row (e.g.: the `id`, `uuid` or the full `item`) + - `rowLink` :: The route which should be used when users click on the row itself. + - `rowLinkModel` :: Model to supply to the route specified by `rowLink` for this specific row. - `fields` :: See above. - `DataCells` :: Contextual component which provides information for - rendering the data cells of a row. Supplying a block to DataCells - will yield a block which is used for rendering the `:dataCells` named - block. -- `data-cells` :: Renders all the cells containing real data in a row. - This includes selection of the row and links. Receives a hash with - the following elements: - - `fields` :: See above. - - `firstColumn` :: The field of the first column to be rendered. Good + rendering the data cells of a row. Has the same block parameter hash as `:data-cells` below. +- `:data-cells` :: Renders all the cells containing real data (fields) in a row. + This excludes cells for meta columns (like selection and links). Receives a hash containing: + - `fields` :: All fields to be rendered. See above. + - `firstColumnField` :: The field of the first column to be rendered. Good for designs where the first column should receive different styling. - - `otherColumns` :: The fields of all columns but the first one to be + - `otherColumnFields` :: The fields of all columns but the first one to be rendered. Good for designs where the first column should receive different styling. - - `wrapper` :: See above. - `item` :: See above. - `rowLink` :: See above. - - `rowLinkModel` :: Model to supply to the route specified by `rowLink` for this specific row. # =@wrapper.rowLinkModel - - `fields` :: See above. + - `rowLinkModel` :: See above. + - `rowClicked` :: See above. - `DataCell` :: Contextual component which provides information for rendering an individual cell. Should receive `@column` with the field to render and `@hasCustomBlock` with `{{has-block "data-cell"}}` so we know whether a custom block was provided for the `data-cell` named slot. -- `data-cell` :: Renders a custom data cell regardless of whether it's - first or any other. Receives a hash with the following elements: - - `firstColumn` :: See above. - - `otherColumns` :: See above. +- `:data-cell` :: Renders a custom data cell regardless of whether it's + first or any other. Receives a hash containing: + - `fields` :: See above. + - `firstColumnField` :: See above. + - `otherColumnFields` :: See above. - `item` :: See above. - `rowLink` :: See above. - `rowLinkModel` :: See above. + - `rowClicked` :: See above. - `label` :: See above. - - `fields` :: See above. - - `isCustom` :: Is the cell explicitly marked to render custom? - - `hasCustomFields` :: Whether there are custom fields to be - rendered. + - `isCustom` :: Wether this cell is explicitly marked to render custom. + - `hasCustomFields` :: Whether there are any cells that are marked to render custom. - `attribute` :: The attribute which will be rendered. - `renderCustomBlock` :: Whether a custom block should be rendered - for this field. This is the named slot `:data-cell`. - - `value` :: The value which should be rendered. -- `first-data-cell` :: In designs which care about the first data cell + for this field. This block is the named slot `:data-cell`. + - `value` :: The data value which should be rendered. +- `:first-data-cell` :: In designs which care about the first data cell versus the others, this will render a custom design for the first data - column of the table. Receives the same arguments as `data-cell`. -- `rest-data-cell` :: In designs which care about the first data cell + column of the table. Receives the same block parameter hash as `:data-cell`. +- `:rest-data-cell` :: In designs which care about the first data cell versus the others, this will render a custom design for the other data - columns of the table. Receives the same arguments as `data-cell`. -- `actions` :: Renders the links next to each row specified through - `@links`. Receives the same arguments as `row`. -- `no-data-message` :: Rendered when no data was available in the data + columns of the table. Receives the block parameter hash as `:data-cell`. +- `:actions` :: Renders the links next to each row specified through + `@links`. Receives the same arguments as `:row`. +- `:no-data-message` :: Rendered when no data was available in the data cell. When no styling is needed, `@noDataMessage` can be used instead. -- `pagination` :: Renders everything needed to handle pagination. - Receives a hash with the following elements: - - `startItem` :: Number of the first item rendered on this page. - - `endItem` :: Number of the last item rendered on this page. +- `:pagination` :: This block contains everything needed to handle pagination. + Receives a hash containing: + - `startIndex` :: Absolute index of the first item rendered on this page. + - `endIndex` :: Absolute index of the last item rendered on this page. - `total` :: Total amount of items on all pages of this table. - `hasTotal` :: Whether the total amount of items is known. - `pageSize` :: Amount of items per page (though the last page may have fewer items). @@ -545,7 +610,7 @@ Various named blocks are offered, check your Ember Data Table design implementat - followed by up to three pages after the current page number, - followed by 'more' if empty spots follow, - followed by the last page number. - - `sizeOptions` :: The different sizes (as an array) for pages of this Data Table. + - `sizeOptions` :: The different sizes (as an array of numbers) for pages of this Data Table. `null` if size should not be changeable (defined by `@sizes`). - `firstPage` :: The first page number in this Data Table. - `lastPage` :: The last page number in this Data Table. - `nextPage` :: The next page number in this view, `undefined` if this @@ -553,11 +618,11 @@ Various named blocks are offered, check your Ember Data Table design implementat - `previousPage` :: The previous page number in this view, `undefined` if this is the first page. - `updatePage` :: Function which takes a back-end page number and - updates it (this is the raw function supplied to `DataTable`. - - `humanPage` :: Thu current page in human form. - - `updateHumanPage` :: Updates the human page number (this will call + updates it (this is the raw function supplied to `DataTable`). + - `humanPage` :: The current page in human form. + - `updateHumanPage` :: Updates the human page number. This will call `updatePage` after mapping the human page number through the back-end - page number offset). + page number offset. All page numbers defined here are human page numbers. - `selectSizeOption` :: Selects a new size option, takes `event` as input and gets the new value from `event.target.value`. - `setSizeOption` :: Selects a new size, takes the `size` as either @@ -574,3 +639,6 @@ Various named blocks are offered, check your Ember Data Table design implementat contain page links. - `backendPageOffset` :: The current back-end page offset (either calculated or guessed). + +## Development +There is a [dummy app](/addon/tests/dummy/app) for example configurations to test changes. The dummy app can be run via `npm start`. \ No newline at end of file diff --git a/addon/components/data-table.hbs b/addon/components/data-table.hbs index c401c1e..09a85a9 100644 --- a/addon/components/data-table.hbs +++ b/addon/components/data-table.hbs @@ -12,6 +12,7 @@ content=@content noDataMessage=this.noDataMessage enableSelection=@enableSelection + selectionProperty=@selectionProperty enableLineNumbers=@enableLineNumbers onClickRow=@onClickRow sort=this.sort diff --git a/addon/components/data-table.js b/addon/components/data-table.js index ea157d3..077d925 100644 --- a/addon/components/data-table.js +++ b/addon/components/data-table.js @@ -1,36 +1,35 @@ import { action } from '@ember/object'; import { tracked } from '@glimmer/tracking'; +import { isEmpty } from '@ember/utils'; import Component from '@glimmer/component'; import { typeOf } from '@ember/utils'; -import { toComponentSpecifications, splitDefinitions } from "../utils/string-specification-helpers"; +import { toComponentSpecifications, definitionsToArray } from "../utils/string-specification-helpers"; import attributeToSortParams from "../utils/attribute-to-sort-params"; +import get from '../utils/get'; +const DEFAULT_DEBOUNCE_TIME = 2000; export default class DataTable extends Component { @tracked _selection = undefined; get filter() { - return this.args.filter !== undefined - ? this.args.filter - : this.args.view?.filter; + return this.args.filter; } get sort() { - return this.args.sort !== undefined - ? this.args.sort - : this.args.view?.sort; + return this.args.sort; } get selection() { - if (this._selection === undefined && this.args.selection === undefined) + if (this._selection === undefined && this.args.initialSelection === undefined) return []; else if (this._selection !== undefined) return this._selection; else - return this.args.selection; + return this.args.initialSelection; } set selection(newSelection) { - this._selection = newSelection; // also trigers dependent properties + this._selection = newSelection; // also triggers dependent properties } get noDataMessage() { @@ -40,24 +39,19 @@ export default class DataTable extends Component { } get isLoading() { - return this.args.isLoading !== undefined - ? this.args.isLoading - : this.args.view?.isLoading; + return this.args.isLoading; } /** * Calculates the search debounce time. * - * If the user supplies searchDebounceTime, that is what we should - * use. A shorthand form is supported in which the user supplies a + * A shorthand form is supported in which the user supplies a * number to autoSearch in which case we use that. This would not * work with 0 (which is a strange debounce time in itself) so this * option exists for now. */ get searchDebounceTime() { - return this.args.searchDebounceTime === undefined - ? isNaN(this.args.autoSearch) ? 2000 : this.args.autoSearch - : this.args.searchDebounceTime; + return isNaN(this.args.autoSearch) ? DEFAULT_DEBOUNCE_TIME : this.args.autoSearch } get enableSelection() { @@ -68,31 +62,24 @@ export default class DataTable extends Component { return this.selection.length === 0; } - get enableSizes() { - return this.args.enableSizes === undefined ? true : this.args.enableSizes; - } - get page() { - const page = this.args.page !== undefined - ? this.args.page - : this.args.view?.page; - return page || 0; + return this.args.page || 0; } get size() { - if ( this.args.size ) - return this.args.size; - else if ( this.args.view?.size ) - return this.args.view.size; - else - return 5; + return this.args.size || 5; } get sizeOptions() { - if (!this.enableSizes) { + const sizeOptions = + this.args.sizes === undefined + ? [5, 10, 25, 50, 100] + : definitionsToArray(this.args.sizes).map((nrOrStr) => + parseInt(nrOrStr) + ); + if (isEmpty(sizeOptions)) { return null; } else { - const sizeOptions = this.args.sizes || [5, 10, 25, 50, 100]; if (!sizeOptions.includes(this.size) && this.size) { sizeOptions.push(this.size); } @@ -124,13 +111,10 @@ export default class DataTable extends Component { } get fieldsWithMeta() { - const fields = this.args.fields; - - if (typeOf(fields) === 'string') { - return toComponentSpecifications(fields, [{raw: "attribute"},{name: "label", default: "attribute"}]); - } else { - return fields || []; - } + return toComponentSpecifications(this.args.fields, [ + { raw: 'attribute' }, + { name: 'label', default: 'attribute' }, + ]); } attributeToSortParams(attribute) { @@ -156,22 +140,44 @@ export default class DataTable extends Component { hasCustomHeader: hasCustomHeader || this.customHeaders.includes(attribute), isCustom: isCustom - || this.customFields.includes(attribute) + || this.customFields.includes(attribute), + customFieldComponent: this.customFieldComponents[attribute] || null, + customHeaderComponent: this.customHeaderComponents[attribute] || null })); } get customHeaders() { - return splitDefinitions(this.args.customHeaders); + const headers = this.args.customHeaders; + if(typeOf(headers) === "object") { + return Object.keys(headers).filter(attr => isEmpty(headers[attr])); + } else { + return definitionsToArray(headers); + } } get customFields() { - return splitDefinitions(this.args.customFields); + const fields = this.args.customFields; + if(typeOf(fields) === "object") { + return Object.keys(fields).filter(attr => isEmpty(fields[attr])); + } else { + return definitionsToArray(fields); + } + } + + get customFieldComponents() { + const fields = this.args.customFields; + return typeOf(fields) === "object"? fields : {}; + } + + get customHeaderComponents() { + const headers = this.args.customHeaders; + return typeOf(headers) === "object"? headers : {}; } get sortableFields() { const sortableFields = this.args.sortableFields; if (sortableFields || sortableFields === "") - return splitDefinitions(sortableFields); + return definitionsToArray(sortableFields); else // default: all fields are sortable return null; @@ -185,10 +191,7 @@ export default class DataTable extends Component { @action updatePageSize(size) { - const updater = this.args.updatePageSize !== undefined - ? this.args.updatePageSize - : this.args.view?.updatePageSize; - + const updater = this.args.updatePageSize; if( !updater ) { console.error(`Could not update page size to ${size} because @updatePageSize was not supplied to data table`); } else { @@ -199,7 +202,7 @@ export default class DataTable extends Component { @action updateFilter(filter) { - const updater = this.args.updateFilter || this.args.view?.updateFilter; + const updater = this.args.updateFilter; if( !updater ) { console.error(`Could not update filter to '${filter}' because @updateFilter was not supplied to data table`); @@ -211,10 +214,7 @@ export default class DataTable extends Component { @action updateSort(sort) { - const updater = this.args.updateSort !== undefined - ? this.args.updateSort - : this.args.view?.updateSort; - + const updater = this.args.updateSort; if( !updater ) { console.error(`Could not update sorting to '${sort}' because @updateSort was not supplied to data table`); } else { @@ -225,10 +225,7 @@ export default class DataTable extends Component { @action updatePage(page) { - const updater = this.args.updatePage !== undefined - ? this.args.updatePage - : this.args.view?.updatePage; - + const updater = this.args.updatePage; if( !updater ) { console.error(`Could not update page to ${page} because @updatePage was not supplied to data table`); } else { @@ -238,11 +235,13 @@ export default class DataTable extends Component { @action addItemToSelection(item) { - this.selection = [...new Set([item, ...this.selection])]; + this.removeItemFromSelection(item); // in case the item was already selected + this.selection = [...this.selection, item]; // create new array to trigger setter if `selection` } @action removeItemFromSelection(item) { - this.selection = this.selection.filter((x) => x !== item); + const byPath = this.args.selectionProperty; + this.selection = this.selection.filter((x) => get(x, byPath) !== get(item, byPath)); } @action clearSelection() { diff --git a/addon/components/data-table/data-cell.hbs b/addon/components/data-table/data-cell.hbs index 877b297..ac39cb8 100644 --- a/addon/components/data-table/data-cell.hbs +++ b/addon/components/data-table/data-cell.hbs @@ -1,14 +1,15 @@ {{!-- Used in: data-table/data-cells --}} {{yield (hash - firstColumn=@firstColumn - otherColumns=@otherColumns - item=@wrapper.item - rowLink=@wrapper.rowLink - rowLinkModel=@wrapper.rowLinkModel + firstColumnField=@firstColumnField + otherColumnFields=@otherColumnFields + item=@item + rowLink=@rowLink + rowLinkModel=@rowLinkModel + rowClicked=@rowClicked label=@column.label fields=@fields isCustom=this.isCustom hasCustomFields=this.hasCustomFields attribute=@column.attribute renderCustomBlock=this.renderCustomBlock - value=(get @wrapper.item @column.attribute))}} \ No newline at end of file + value=(get @item @column.attribute))}} \ No newline at end of file diff --git a/addon/components/data-table/data-cells.hbs b/addon/components/data-table/data-cells.hbs index a0bfebb..106b08e 100644 --- a/addon/components/data-table/data-cells.hbs +++ b/addon/components/data-table/data-cells.hbs @@ -1,16 +1,18 @@ {{!-- Used in: data-table/row --}} {{yield (hash - fields=@dataTable.fields - firstColumn=this.firstColumn - otherColumns=this.otherColumns - wrapper=@wrapper - item=@wrapper.item - rowLink=@wrapper.rowLink - rowLinkModel=@wrapper.rowLinkModel + firstColumnField=this.firstColumnField + otherColumnFields=this.otherColumnFields + item=@item + rowLink=@rowLink + rowLinkModel=@rowLinkModel + rowClicked=@rowClicked fields=@fields DataCell=(component "data-table/data-cell" - firstColumn=this.firstColumn - otherColumns=this.otherColumns - wrapper=@wrapper + firstColumnField=this.firstColumnField + otherColumnFields=this.otherColumnFields + item=@item + rowLink=@rowLink + rowLinkModel=@rowLinkModel + rowClicked=@rowClicked fields=@fields))}} diff --git a/addon/components/data-table/data-cells.js b/addon/components/data-table/data-cells.js index 3b607d1..fc515d9 100644 --- a/addon/components/data-table/data-cells.js +++ b/addon/components/data-table/data-cells.js @@ -1,11 +1,11 @@ import Component from '@glimmer/component'; export default class DataTableDataCellsComponent extends Component { - get firstColumn() { + get firstColumnField() { return this.args.fields?.[0] || null; } - get otherColumns() { + get otherColumnFields() { if (this.args.fields?.length) { let [, ...fields] = this.args.fields; return fields; diff --git a/addon/components/data-table/data-table-content-body.hbs b/addon/components/data-table/data-table-content-body.hbs index 5891161..facc21a 100644 --- a/addon/components/data-table/data-table-content-body.hbs +++ b/addon/components/data-table/data-table-content-body.hbs @@ -3,10 +3,8 @@ isLoading=@dataTable.isLoading content=@content offset=this.offset - wrappedItems=this.wrappedItems enableLineNumbers=@enableLineNumbers hasClickRowAction=(and (or @onClickRow @rowLink) true) - onClickRow=@onClickRow toggleSelected=this.updateSelection selection=@dataTable.selection enableSelection=@enableSelection @@ -19,10 +17,13 @@ dataTable=@dataTable enableLineNumbers=@enableLineNumbers enableSelection=@enableSelection + selectionProperty=@selectionProperty selection=@dataTable.selection offset=this.offset hasClickRowAction=(and (or @onClickRow @rowLink) true) - onClickRow=this.onClickRow + onClickRow=@onClickRow linkedRoutes=@linkedRoutes + rowLink=@rowLink + rowLinkModelProperty=@rowLinkModelProperty fields=@fields toggleSelected=this.updateSelection))}} diff --git a/addon/components/data-table/data-table-content-body.js b/addon/components/data-table/data-table-content-body.js index 3b27459..7d6da01 100644 --- a/addon/components/data-table/data-table-content-body.js +++ b/addon/components/data-table/data-table-content-body.js @@ -1,11 +1,7 @@ -import { cached } from '@glimmer/tracking'; -import { get } from '@ember/object'; -import { inject as service } from '@ember/service'; import { action } from '@ember/object'; import Component from '@glimmer/component'; export default class DataTableContentBodyComponent extends Component { - @service router; get offset() { var offset = 1; //to avoid having 0. row @@ -17,40 +13,11 @@ export default class DataTableContentBodyComponent extends Component { return offset; } - @cached - get wrappedItems() { - const selection = this.args.dataTable.selection || []; // TODO: should the dataTable ensure this is an array? - const content = this.args.content; - return content.map((item) => { - return { - item: item, - isSelected: selection.includes(item), - rowLink: this.args.rowLink, - rowLinkModel: this.rowLinkModel(item) - }; - }); - } - - rowLinkModel(row) { - return this.args.rowLinkModelProperty - ? get(row, this.args.rowLinkModelProperty) - : row; - } - @action - updateSelection(selectedWrapper, event) { + updateSelection(item, event) { if( event.target.checked ) - this.args.dataTable.addItemToSelection(selectedWrapper.item); + this.args.dataTable.addItemToSelection(item); else - this.args.dataTable.removeItemFromSelection(selectedWrapper.item); - } - - @action - onClickRow(row) { - if ( this.args.onClickRow ) { - this.args.onClickRow(...arguments); - } else if ( this.args.rowLink ) { - this.router.transitionTo( this.args.rowLink, this.rowLinkModel(row) ); - } + this.args.dataTable.removeItemFromSelection(item); } } diff --git a/addon/components/data-table/data-table-content.hbs b/addon/components/data-table/data-table-content.hbs index 38570f9..8a86e20 100644 --- a/addon/components/data-table/data-table-content.hbs +++ b/addon/components/data-table/data-table-content.hbs @@ -13,6 +13,7 @@ Body=(component "data-table/data-table-content-body" content=@content enableSelection=@enableSelection + selectionProperty=@selectionProperty enableLineNumbers=@enableLineNumbers noDataMessage=@noDataMessage onClickRow=@onClickRow diff --git a/addon/components/data-table/data-table-content.js b/addon/components/data-table/data-table-content.js index b197317..d84776a 100644 --- a/addon/components/data-table/data-table-content.js +++ b/addon/components/data-table/data-table-content.js @@ -19,6 +19,9 @@ export default class DataTableContentComponent extends Component { * * Behaviour for `___` is undefined. * + * Can pass a space-separated string or an array. + * The array can already contain an object with the transformed link + * * Yields an array of objects to represent the linked routes. * [ { route: "products.show", label: "Show product", icon: "show-icon" } ] */ diff --git a/addon/components/data-table/data-table-menu.hbs b/addon/components/data-table/data-table-menu.hbs index 635a3ff..76c6928 100644 --- a/addon/components/data-table/data-table-menu.hbs +++ b/addon/components/data-table/data-table-menu.hbs @@ -3,5 +3,7 @@ (component "data-table/data-table-menu-general" dataTable=@dataTable) (component "data-table/data-table-menu-selected" dataTable=@dataTable) as |general selected|}} - {{yield general selected @dataTable.enableSelection}} + {{yield + (hash General=general Selected=selected enableSelection=@dataTable.enableSelection) + }} {{/let}} \ No newline at end of file diff --git a/addon/components/data-table/number-pagination.hbs b/addon/components/data-table/number-pagination.hbs index 769b7e3..6f00637 100644 --- a/addon/components/data-table/number-pagination.hbs +++ b/addon/components/data-table/number-pagination.hbs @@ -1,7 +1,7 @@ {{!-- Used in: data-table.hbs --}} {{yield (hash - startItem=this.startItem - endItem=this.endItem + startIndex=this.startIndex + endIndex=this.endIndex total=this.total hasTotal=this.hasTotal pageSize=@size diff --git a/addon/components/data-table/number-pagination.js b/addon/components/data-table/number-pagination.js index 61516b9..8119b0c 100644 --- a/addon/components/data-table/number-pagination.js +++ b/addon/components/data-table/number-pagination.js @@ -126,7 +126,7 @@ export default class NumberPaginationComponent extends Component { return this.lastPage > this.firstPage; } - get startItem() { + get startIndex() { // note, you might want to use this.args.page instead, but given // that comes from the backend, it's *not* guaranteed to be // zero-based either. @@ -137,7 +137,7 @@ export default class NumberPaginationComponent extends Component { return zeroToHumanBased(this.args.size * humanToZeroBased( this.humanPage )); } - get endItem() { + get endIndex() { // this one is exactly the same number as humanPageOffset yet it has // a different meaning. When summing up lists, it's effectively // removing one regardless of the offset. @@ -145,7 +145,7 @@ export default class NumberPaginationComponent extends Component { // human probably expects to see 0-0 when no items exist. return 0; else - return this.startItem - 1 + this.args.itemsOnCurrentPage; + return this.startIndex - 1 + this.args.itemsOnCurrentPage; } get numberOfPages() { diff --git a/addon/components/data-table/row.hbs b/addon/components/data-table/row.hbs index 60c2bc8..1d6abe2 100644 --- a/addon/components/data-table/row.hbs +++ b/addon/components/data-table/row.hbs @@ -1,21 +1,24 @@ {{!-- Used in: data-table/data-table-content-body --}} -{{!-- TODO: do we want both selected and isSelected? --}} +{{!-- @item and @index come from consumer's data-table.hbs implementation --}} {{yield (hash - wrapper=@wrapper - item=@wrapper.item + item=@item enableLineNumbers=@enableLineNumbers lineNumber=(add @index @offset) enableSelection=@enableSelection - hasClickRowAction=@hasClickRowAction - onClickRow=(fn @onClickRow @wrapper.item) - isSelected=(includes @wrapper.item @selection) - selected=(includes @wrapper.item @selection) - toggleSelected=(fn @toggleSelected @wrapper) + isSelected=(includes-by @selection @item @selectionProperty) + toggleSelected=(fn @toggleSelected @item) linkedRoutes=this.linkedRoutes + rowLink=@rowLink + rowLinkModel=this.rowLinkModel + hasClickRowAction=@hasClickRowAction + rowClicked=this.rowClicked fields=@fields DataCells=(component "data-table/data-cells" fields=@fields - wrapper=@wrapper + item=@item + rowLink=@rowLink + rowLinkModel=this.rowLinkModel + rowClicked=this.rowClicked linkedRoutes=this.linkedRoutes dataTable=@dataTable))}} \ No newline at end of file diff --git a/addon/components/data-table/row.js b/addon/components/data-table/row.js index 6428da7..d4689c6 100644 --- a/addon/components/data-table/row.js +++ b/addon/components/data-table/row.js @@ -1,10 +1,14 @@ import { get } from '@ember/object'; import Component from '@glimmer/component'; +import { action } from '@ember/object'; +import { inject as service } from '@ember/service'; export default class DataTableRowComponent extends Component { + @service router; + get linkedRoutes() { return this.args.linkedRoutes.map( (linkedRoute) => { - const model = this.args.wrapper.item; + const model = this.args.item; return Object.assign( { model: linkedRoute.linksModelProperty ? get(model, linkedRoute.linksModelProperty) @@ -12,4 +16,20 @@ export default class DataTableRowComponent extends Component { }, linkedRoute ); } ); } + + get rowLinkModel() { + const { item, rowLinkModelProperty } = this.args; + return rowLinkModelProperty + ? get(item, rowLinkModelProperty) + : item; + } + + @action + rowClicked() { + if ( this.args.onClickRow ) { + this.args.onClickRow(...arguments); + } else if ( this.args.rowLink ) { + this.router.transitionTo( this.args.rowLink, this.rowLinkModel ); + } + } } diff --git a/addon/components/data-table/text-search.hbs b/addon/components/data-table/text-search.hbs index a204a12..2c1235e 100644 --- a/addon/components/data-table/text-search.hbs +++ b/addon/components/data-table/text-search.hbs @@ -4,5 +4,6 @@ placeholder=@placeholder autoSearch=@autoSearch submitForm=this.submitForm + handleInput=this.handleInput handleAutoInput=this.handleAutoInput handleDirectInput=this.handleDirectInput)}} diff --git a/addon/components/data-table/text-search.js b/addon/components/data-table/text-search.js index 8000fa0..aa8829b 100644 --- a/addon/components/data-table/text-search.js +++ b/addon/components/data-table/text-search.js @@ -13,6 +13,14 @@ export default class TextSearchComponent extends Component { this.autoDebouncePid = debounce(this, this.submitCurrent, this.args.searchDebounceTime); } + @action + handleInput(event) { + this.enteredValue = event.target.value; + if(this.args.autoSearch !== false) { + this.autoDebouncePid = debounce(this, this.submitCurrent, this.args.searchDebounceTime); + } + } + submitCurrent() { if (!this.isDestroying && !this.isDestroyed) { this.args.updateFilter(this.enteredValue); diff --git a/addon/components/raw-data-table.hbs b/addon/components/raw-data-table.hbs index 35deedc..2a99554 100644 --- a/addon/components/raw-data-table.hbs +++ b/addon/components/raw-data-table.hbs @@ -3,16 +3,18 @@ @content={{@content}} @fields={{@fields}} @autoSearch={{@autoSearch}} - @view={{@view}} @page={{@page}} @size={{@size}} + @sizes={{@sizes}} @total={{@total}} @sort={{@sort}} @filter={{@filter}} @meta={{@meta}} - @hasMenu={{@hasMenu}} + @showMenu={{@showMenu}} @enableSearch={{@enableSearch}} @enableSelection={{@enableSelection}} + @initialSelection={{@initialSelection}} + @selectionProperty={{@selectionProperty}} @noDataMessage={{@noDataMessage}} @isLoading={{@isLoading}} @enableLineNumbers={{@enableLineNumbers}} @@ -27,6 +29,7 @@ @rowLinkModelProperty={{@rowLinkModelProperty}} @customHeaders={{@customHeaders}} @customFields={{@customFields}} + @customFieldComponents={{@customFieldComponents}} @sortableFields={{@sortableFields}} @attributeToSortParams={{@attributeToSortParams}} as |dt|> @@ -43,18 +46,18 @@ {{on "submit" search.submitForm}} class="data-table-search"> {{#if search.autoSearch}} -