Code Monkey home page Code Monkey logo

fixtable-ember's Introduction

Fixtable-Ember

npm Build Status

This addon provides an Ember-specific wrapper around the the Fixtable library. Fixtable provides an easy way to create data grids with scrollable content and a fixed header/footer. In addition, Fixtable-Ember provides enhanced functionality such as sorting, filtering, and pagination.

See a live demo here: http://purecloudlabs.github.io/fixtable-ember/

(Note: The source for the demo site linked above can be found in this repo's tests/dummy/app directory in the master branch.)

Installation

Getting Started

Navigate to the root directory of the project where you want to install this addon. Then, install this addon in your app using the following commands:

ember install fixtable-ember

This addon creates a new component that you can use in your templates: fixtable-grid

Although it isn't strictly required, we recommend including Bootstrap 3 in your project for optimal display.

Nesting Addons

Ember-Engines

If you are using the fixtable-ember addon within an Ember engine, the fixtable-ember dependency must be under "dependencies" instead of "devDependencies" in the package.json file of your engine. Otherwise, the fixtable-grid component will fail to render.

Basic Usage

Defining Columns

The fixtable-grid component expects to be passed a columns property, which should be a JavaScript array consisting of objects that have the following keys: key, header, and width.

  • key (required) - Unique string identifier for the column.
  • header (optional) - Text to show in the column header. If this isn't passed in, key will be shown in the header.
  • width (optional) - Width of the column. Can be either a number (interpreted as pixels) or a string percentage (formatted like "50%"). If no width is passed in, the column will be sized automatically. Using a combination of pixel and percentage widths is not recommended.
  • hideLabel (optional) - If true, no column header will be shown for this column (even if the header property is specified).

Here's a simple example using just the key:

[
  { key: 'name' },
  { key: 'address' }
]

Here's an example using header and a mixture of pixel widths and automatic sizing:

[
  {
    key: 'name',
    header: 'Name',
    width: 150 // -> 150px
  },
  {
    key: 'address',
    header: 'Street Address'
  }
]

Here's an example using percentage width:

[
  {
    key: 'name',
    width: '40%'
  },
  {
    key: 'address',
    width: '60%'
  }
]

Defining Content

The component expects a content property to be passed in. This should be an array of objects, either a native Array or one of Ember's Array-like objects (Ember.ArrayProxy, DS.ManyArray, or their subclasses).

  • Each object element in the array represents a row of data.
  • Each key/value pair in a row object represents a single cell of data. The key should match a key defined in columns, and the value represents the content for the column/row intersection.
  • If there is no data for a given column, that key/value pair can be omitted from the row object.

For example, this is an example of content corresponding to the name and address columns defined in the examples above.

[
  { name: 'Sherlock Holmes ', address: '221B Baker Street' },
  { name: 'Dudley Dursley', address: '4 Privet Drive' },
  { name: 'Hank Hill', address: '84 Rainey Street' },
  { name: 'Gandalf Stormcrow' }
]

Adding Custom CSS Classes / Setting a Height

The primary feature of the Fixtable library is its ability to allow scrollable content with a fixed header and footer. To enable this feature, all you need to do is pass in a CSS class with a defined height through the fixtableClass property.

The class (or classes, if you pass in a space-delimited class name string) specified in fixtableClass will be added to the Fixtable container element.

Specifying a height for the Fixtable is required for optimal display. If you do not specify a height, the table may appear to "collapse" when there are no data rows. (Note that height does not need to be specified as a fixed number of pixels; percentage-height is also fine as long as the parent element has a size.)

For example:

{{fixtable-grid fixtableClass='restrict-height'}}
.restrict-height {
  height: 500px;
}

At its core, the Fixtable is an HTML table element. If you want to customize the look of the Fixtable by adding CSS classes directly to the table element, you can do that with the tableClass property. For example, we might want to add the table-hover class to add Bootstrap's default styles when you hover over a row in the table.

{{fixtable-grid tableClass='table-hover'}}

By default, Fixtable already adds the table CSS class to the table element. As a result, you do not need to pass table to the tableClass property.

You can also add custom CSS classes directly to the cell (td) elements for a given column. This should be done by specifying a cellClass for the column in the column definition. For example, let's say we want to add the "name" CSS class to every cell in the Name column. Our column definition would need to look like this:

[
  {
    key: 'name',
    header: 'Name',
    cellClass: 'name'
  },
  {
    key: 'address',
    header: 'Street Address'
  }
]

Loading Indicator

If your data is being loaded asynchronously, you can show a loading indicator to make this clear to the user. Loading state can be toggled by binding to the isLoading property:

{{fixtable-grid isLoading=dataIsLoading}}

In this example, dataIsLoading is assumed to be a property on the containing component or controller, indicating whether data has finished loading.

While isLoading is true, a spinning 'loading' indicator will be shown in place of the grid content. When it becomes false, the loading indicator will be replaced with the actual data rows.

Putting it All Together

In the examples above, we are making the following assumptions:

  • We defined column definitions and grid content on our model as columnDefs and dataRows respectively
  • We have a restrict-height CSS class that we want to apply to the Fixtable to limit its height
  • We want to apply the table-hover class to the table element so that rows have a hover style
  • We are indicating whether data has finished loading by binding the component's isLoading property to our dataIsLoading property

With those assumptions, our final markup for the fixtable-grid component ends up looking like this:

{{fixtable-grid columns=model.columnDefs content=model.dataRows fixtableClass='restrict-height' tableClass='table-hover' isLoading=dataIsLoading}}

Advanced Usage

Pagination

By default, the Fixtable grid assumes that its passed-in content represents all of the available content. However, if your content is split into multiple pages, there is built-in support for two different kinds of pagination: client-side and server-side.

Client-Side Pagination

To enable client paging, all you have to do is set the clientPaging property to true. The Fixtable will then assume that all data has been loaded and is present in the content collection, and it will handle pagination logic internally.

{{fixtable-grid columns=model.columnDefs content=model.dataRows clientPaging=true}}

With client paging turned on, the Fixtable will now limit the number of rows that are shown in the table at a time. Beneath the table, there will be a pagination footer that shows the current page and the total number of pages. The footer also lets users go back or forward a page, jump to a specific page, and configure the page size.

The possible options for page size are 25, 50, 100, 250, and 500 -- however, this will be limited based on the number of rows. For example, if there are only 55 rows total, then the possible page sizes will be 25, 50, and 100. There's no need to show the 250 and 500 page size options since they will be functionally identical to a page size of 100.

Server-Side Pagination

Enabling server paging takes a little more setup:

  • First, set the serverPaging property to true.
  • Bind the component's totalRowsOnServer property to a value representing the length of the dataset on the server. (The Fixtable needs this information to calculate how many pages there are.)
  • Attach an action to the component's onReloadContent property. The action function will receive two parameters -- page (the current 1-indexed page number) and pageSize (the current page size) -- and is called whenever either the page or the page size changes.

Although the same pagination footer will be shown as with client-side pagination, the Fixtable will now assume that pagination should not be handled on the client (or at least, not by the Fixtable itself). Instead, it provides a hook named onReloadContent to let the consumer know when the current page or the page size changes. The consumer is expected to use this information to update the bound content of the fixtable-grid component.

Here's some sample markup:

{{fixtable-grid columns=model.columnDefs content=model.pagedDataRows
  isLoading=serverPageIsLoading serverPaging=true totalRowsOnServer=model.totalRows
  onReloadContent=(action 'updatePage') }}

Notice that we've turned on server paging, set the total number of rows, and bound the updatePage action to the component's onReloadContent property. We've also set a property that lets us toggle the loading indicator.

In our controller, we might define updatePage something like this:

actions: {
  updatePage(page, pageSize/*, filters*/) {
    this.set('serverPageIsLoading', true);

    this.store.query('rows', { page, pageSize )
      .then(rows => {
        this.set('serverPageIsLoading', false);
        this.set('model.pagedDataRows', rows);
      });
  }
}

In this example, first we are showing the loading indicator to let the user know that data is loading. Next, we request additional data from the server based on the updated page and pageSize. When we receive the data, we hide the loading indicator and set the new rows into the model property bound to the fixtable-grid component.

Custom Page Size

The default page size is 25 with the following default possible page sizes for the paginated table:

[ 25, 50, 100, 250, 500 ]

The user can switch between page sizes using the pagination footer at the bottom of the table. The page size options shown to the user will be restricted based on the number of data rows (e.g., if there are 53 rows, then the possible page sizes will be 25, 50, or 100) -- the goal is to only show as many page size options as necessary to display all of the data.

If you want to pass in different page size options, you can override the component's pageSize, possiblePageSizes, currentPage properties. The possible page sizes must be listed in ascending order.

Custom Cell Components

You may want to render something more complicated into a cell than just a simple string value. For example, you might want to make each cell link out to another route, or you might want to embed a Handlebars template into a cell. You can do that by creating a custom cell component.

It's very easy to create a custom cell component -- all you have to do is specify a component for the column in the column definitions:

For example, here we have two regular columns and a third "profile" column that will render the link-to-profile component in each cell.

[
  {
    key: 'name',
    header: 'Name',
  },
  {
    key: 'address',
    header: 'Street Address'
  },
  {
    key: 'profile',
    header: 'Profile',
    component: 'link-to-profile'
  }
]

Custom cell components will automatically be provided with three properties:

  • columnDef - the column definition
  • dataRow - the object from the content array represented by this row
  • rowExpanded - a boolean value indicating whether or not the row is expanded (more on this in the Row Expansion section below)

For example, the Handlebars markup for the example link-to-profile component could look like this:

{{#link-to 'profile' dataRow.name}}Go to profile for {{dataRow.name}}{{/link-to}}

This would render a link to the "profile" route in each row, passing the "name" property as a parameter.

Bubbling Actions from Custom Cell components

If you have a custom cell component defined, you may want to allow the consumer of the fixtable-grid to handle actions raised by that cell component. To facilitate that, a bubbleAction function is set on the custom component, which can be called like this from the cell component's JavaScript:

this.bubbleAction(myActionName[, args]);

The action with name myActionName will bubble up to the consumer of the fixtable-grid component. That consumer should set an action handler on the fixtable-grid:

{{fixtable-grid columns=columnDefs content=dataRows myActionName=(action 'myActionHandler')}}

Where myActionName is replaced with the actual name of the action bubbling up, and myActionHandler is some action defined on the parent of the fixtable-grid.

Alternatively, you skip calling this.bubbleAction in the cell component's JavaScript by directly using bubbleAction as an action in the markup of the component:

<button type="button" {{action bubbleAction 'myActionName' optionalArg1 optionalArg2}}>
  Do My Action
</button>

This will directly bubble the action with name myActionName to the parent of the fixtable-grid component, passing along any additional arguments that follow the action name.

Additional Custom Components

Footer Component

You can supply your own footerComponent which will be rendered in place of the default fixtable-footer component. The following properties will be made available to your custom footer:

  • currentPage - integer value of the currently selected page (starting at 1)
  • totalRows - integer value of the total number of rows; if you're using server-side paging, this will be equal to totalRowsOnServer, otherwise it's the number of rows in the current filtered content array
  • totalPages - integer value of the number of available pages given the current pageSize and totalRows
  • selectedDataRows - array of the currently selected row objects
  • pageSize - integer value of the number of items in each page
  • pageSizeOptions - array of integers which can optionally be used to provide the user an affordance for changing pageSize
  • isLoading - boolean value to indicate when a new page is being loaded (it's up to your onReloadContent method to update this value)
  • goToNextPage - action which your component may invoke to increment the currentPage
  • goToPreviousPage - action which your component may invoke to decrement the currentPage
  • bubbleAction - action which your component may invoke to bubble actions up to the consumer of the fixtable-grid (this works the same as custom cell components; see Bubbling Actions from Custom Cell components above)

Empty State Component

You can supply your own emptyStateComponent which will be rendered in place of the default fixtable-empty-state component when the content array is empty. You can also supply an object using the emptyStateComponentValues property with dynamic values to be passed along to your component as values.

The default empty state component simply shows a message, centered vertically and horizontally within Fixtable. This message defaults to "No data available", but you can override it by using the emptyStateComponentValues property to set an object with a nullMessage property. For example:

{{fixtable-grid emptyStateComponentValues={nullMessage: "Nothing to see here!"}}}

Filtering

Fixtable supports filtering the displayed rows, either by search text or by selected option. To set this up, add a filter property to the column definition.

For example, this adds a search-type filter to the name column:

[
  {
    key: 'name',
    header: 'Name',
    filter: {
      type: 'search'
    }
  },
  {
    key: 'address',
    header: 'Street Address'
  }
]

Similar to how pagination is handled, the implementation of filtering depends on whether all of the Fixtable's content is present on the client.

Client-Side Filtering

As long as serverPaging is not set to true, the Fixtable can handle the filtering logic internally. Adding a filter type to the column definition, as described above, will show a filter field in the column header. Typing into the search field (or selecting a filter option) will automatically filter the visible data rows.

Server-Side Filtering

Server-side filtering presents the same user interface as client-side filtering, but the content is not automatically filtered (because the Fixtable cannot assume that all data is present on the client). Server-side filtering is active whenever the serverPaging property is set to true. In other words, if you're using server-side pagination, you also need to use server-side filtering.

Server-side filtering uses the same onReloadContent event as server-side pagination, so you don't need to define a separate function or property. The third parameter passed to the function (after page and pageSize) will be filters, an object where the key/value pairs link column keys to filter text. For example:

{
  name: 'smith',
  address: ''
}

If the value is null or empty, that means that there is no filter applied to that column.

After requesting and receiving the updated data from the server, you should also update the value of totalRowsOnServer so that it reflects the total number of filtered rows on the server. Otherwise, there may be additional "blank" pages in the Fixtable, because the number of pages shown is determined by totalRowsOnServer.

Do note that it's possible for changing the filters to simultaneously change the current page. (The Fixtable automatically jumps to the first page if new filters are applied.) If this happens, the onReloadContent function will still only be invoked a single time. As a result, you should likely always include both filter and pagination information in your server request.

Types of Filters

There are two types of filters: search filters and select filters. Search-type filters discriminate rows based on the text typed into the filter field, excluding any row that does not include the searched-for text as a substring (case-insensitive).

You can specify a search-type filter in the column definitions as follows:

[
  {
    key: 'name',
    header: 'Name',
    filter: {
      type: 'search'
    }
  }
]

Select-type filters discriminate rows based on a discretely selected option, where the row's value for that column must be equal to the selected filter option (again, case-insensitive). A label may be optionally provided for each option to have control over the text shown in the select-type filter dialog. The options for a select-type filter may be specified as follows:

[
  {
    key: 'grade',
    header: 'Letter Grade',
    filter: {
      type: 'select',
      selectOptions: [
        { value: 'A', label: 'The letter A' },
        { value: 'B' },
        { value: 'C', label: 'The letter C' },
        { value: 'D' },
        { value: 'F' },
      ]
    }
  }
]

If you prefer not to specify the possible select options in the column definitions, you can simply set the automaticOptions flag to true in the filter object of the column definition. This will cause the select options to be automatically generated from the values that are present in the data.

[
  {
    key: 'grade',
    header: 'Letter Grade',
    filter: {
      type: 'select',
      automaticOptions: true
    }
  }
]

Automatic filtering should not be used if server-side pagination is turned on, because Fixtable will not be able to determine all of the possible filter options without all of the data loaded onto the client. (Automatic filtering can be used with client-side pagination, however.) Note that the generated options will be unique and case-insensitive.

Filter Debouncing

To avoid rapid and unnecessary filtering as the user types, especially in the case where filtering happens on the server and requires AJAX calls, the Fixtable automatically debounces filtering by 500ms. In other words, the filter will not be applied until 500ms after the user stops typing into a filter field. This can be configured by setting the component's filterDebounce property, if desired. (The property should be set to a number representing the debounce time in milliseconds.)

For example, this lengthens the debounce time to a full second.

{{fixtable-grid columns=model.columnDefs content=model.dataRows filterDebounce=1000}}

Manual Filter Application

By default, the Fixtable will automatically filter itself as the user types into filter fields -- with a short debounce interval, as described above. However, real-time filtering can be disabled entirely. This may be useful in workflows where you want to reduce traffic to the server, or where the user wants to enter filters in multiple fields before seeing the filtered results.

To disable real-time filtering, simply set the realtimeFiltering property to false. The Fixtable will automatically show Apply and Clear buttons that apply or clear the entered filters, respectively.

Filter Placeholder Text

You can specify placeholder text for search- and select-type filters by setting the value of the placeholder property within the filter object of the column definition.

[
  {
    key: 'name',
    header: 'Name',
    filter: {
      type: 'search',
      placeholder: 'name search'
    }
  }
]

If the filter is search-type, the placeholder attribute of the <input> element will be set to the placeholder value. If the filter is select-type, the text of the first <option> of the <select> element will be set to the placeholder value.

Custom Filter Components

You may want to render something other than a search field or select list for the filter. To do so, specify the component to render by providing the name in the component property of the filter object.

For example, given that a component named "checkbox-filter" exists:

[
  {
    key: 'name',
    header: 'Name',
    filter: {
      component: 'checkbox-filter'
    }
  }
]

If both component and type are specified, component will take precedence and type will be ignored.

The components will have access to two properties: columnDef and filter. These properties represent the column definition and the filter value for that column, respectively. Setting the filter property in the component will invoke the onReloadContent action or cause the manual filter buttons to display based on the value of the realtimeFiltering.

{{!-- checkbox-filter.hbs --}}
<div class="checkbox">
  <label>
    {{input type="checkbox" checked=filter}} Check if awesome
  <label>
</div>

If you are using client-side filtering, then you should also provide a custom filter function along with your custom component. Set the custom filter function as the filterFunction property of your column definition's filter object.

When invoked, filterFunction will be passed a data row and the current filter value for that column. It should return true if the given row passes the filter, and false otherwise.

[
  {
    key: 'name',
    header: 'Name',
    filter: {
      component: 'checkbox-filter',
      filterFunction(rowData, filterValue) {
        return rowData.name === 'Roland Deschain';        
      }
    }
  }
]

Filter Caveats

Columns that have a custom cell component can be filtered, but only if the data rows defined in content have values corresponding to the column using the custom cell component. The filtering will be based on the data in content, not on what's rendered by the custom cell component.

Sorting

Fixtable has built-in support for column sorting.

To enable sorting for a given column, just add sortable: true to the column definition.

For example, in the column definitions below, the name column is sortable:

[
  {
    key: 'name',
    header: 'Name',
    sortable: true
  },
  {
    key: 'address',
    header: 'Street Address'
  }
]

Clicking on the header of a sortable column will sort it in ascending order. Clicking on the column header again will toggle ascending/descending. Clicking on another column header will switch to sorting by the new column in ascending order.

To specify a default sort column or sort order, you can bind to the sortBy and sortAscending properties of the fixtable-grid component, respectively. (sortAscending defaults to true, so you can omit this property if that's what you want.) sortBy should be a column key, and sortAscending should be a boolean.

For example, this is how we might sort by ID in descending order:

{{fixtable-grid columns=model.columnDefs content=model.dataRows sortBy=(mut sortKey) sortAscending=(mut ascending)}}

In the owning component or controller:

sortKey: 'id',
ascending: false,

Note that you should use the mut helper to indicate that the fixtable-grid is allowed to mutate the value. Because the Fixtable needs to change these values depending on user actions, you should not use literal string and boolean values for these properties.

Client-Side Sorting

As long as serverPaging is not turned on, Fixtable can handle the sorting logic on its own, under the assumption that all of the table data is already loaded on the client. Specifying the sortable key in the column definition is enough to enable client-side sorting.

There are also some additional features that can be used to customize sorting on the client. By default, sorting is done in lexicographical order, using JavaScript's String.prototype.localeCompare function. If you want to sort using some other means, you can specify a custom sort function using the sortFunction key of the column definition.

For example, this column definition represents an ID column that is sorted numerically.

{
  key: 'id',
  header: 'ID',
  sortable: true,
  sortFunction: (x, y) => x - y
}

When specifying a custom sort function, note that the intended sort direction is assumed to be ascending. When the column is sorted in descending order, the same function will be invoked, but the parameter order will be reversed. The sort function should expect to receive two parameters, corresponding to two cell values for the rows being compared. (In the example above, x and y would be ID values.) In your sort function, don't forget to consider the possibility of null or undefined cell values.

Server-Side Sorting

When server paging is turned on, Fixtable cannot implement sorting on the client because it doesn't have enough information. Instead, the same onReloadContent action used for filtering and pagination will be invoked. The fourth parameter of the passed-in function will be a sortInfo object that contains key and ascending properties. key corresponds to the key of the column that should be sorted, and ascending is a boolean that indicates whether the sort should be ascending or descending. The sortInfo parameter will be null if no sort column is specified.

For example, sortInfo would look like this if we wanted to sort by ID, ascending:

{
  key: 'id',
  ascending: true
}

Similarly to server-side pagination and filtering, the Fixtable will take care of notifying its consumer whenever the table needs to be sorted, but actually sorting the table's bound data is the responsibility of the consumer.

Sorting Caveats

Currently, you can only sort by a single column at a time. (In other words, there is no concept of primary/secondary sort column.)

Also, just like with filtering, sorting is based on what's defined in content, not on the actual markup rendered in the cell. Keep this in mind when using sorting with custom cell components.

External Filters

You may want to implement UI for filtering data outside of Fixtable itself. Perhaps in a sibling component that allows for more complex controls than can comfortably fit in a column header.

To support this use case, Fixtable allows for an externalFilters property to be set. It will then monitor this value for changes and trigger your onReloadContent callback to refresh the table content. The current value of externalFilters will be passed to your callback as the fifth parameter.

Fixtable does not prescribe any particular format for your external filters, so you are free to implement whatever data structure works best for your needs. This, however, means you may need to use notifyPropertyChange() when your external filters are updated, to explicitly notify Fixtable of the change.

Note: serverPaging must be enabled.

onReloadContent

Let's recap onReloadContent, since it can be triggered by several different kinds of content updates. This property of the fixtable-grid should be set to an action on the owning controller or component. The bound action will be called whenever the table is paged, sorted, or filtered, or when any external filters are changed. (This is true regardless of whether client paging or server paging is active, but obviously it's more important for server paging.) The action will be invoked with the following parameters:

  • page (Number) - The current 1-indexed page number.
  • pageSize (Number) - The maximum number of rows shown on a single page.
  • filters (Object) - Object where the key/value pairs map column keys to filter text.
  • sortInfo (Object) - Object with a string key property representing the column to sort by, and a boolean ascending property indicating the sort direction. If the table is unsorted, sortInfo will be null.
  • externalFilters (Object) - The current value of your externalFilters property; this is completely outside of Fixtable's control.

Row Selection

Fixtable has built-in support for selecting rows. If you set the rowSelection property to true, the fixtable-grid will render a leftmost column of checkboxes that can be used to select individual rows. In the header for the checkbox column, there will also be a checkbox that can be used to toggle selection for all of the visible rows.

Selected rows will automatically have the active CSS class applied to them. This will give them a default selected style if you have Bootstrap pulled in, but you can also override those styles as desired.

When rows are selected, any kind of content reload (i.e., anything that would trigger onReloadContent -- sorting, paging, or filtering) will clear the selected rows. (This is true regardless of whether the Fixtable is using client paging, server paging, or no paging at all.)

Consumers should keep track of which rows are selected by subscribing to onSelectionChanged. Note that whenever the onReloadContent handler is called, the onSelectionChanged handler will also be invoked immediately afterwards, informing the listener that the selected rows have been cleared out.

onSelectionChanged should be bound to an action on the owning controller or component. The bound action will be called whenever a row is selected or deselected. It receives the following parameter:

  • selectedDataRows (Object) - The selected data rows.

Row Clicking

For simple row click interaction without showing the row checkbox onRowClick should be bound to an action on the owning controller or component. The bound action will be called whenever the row is clicked. It receives the following parameter:

  • clickedRow (Object) the data object for the row that was clicked.

Row Expansion

Fixtable has built-in support for expandable rows, for cases where you may desire to show more information than can comfortably fit on a single row. When a row is "expanded," a second, full-width table row is displayed beneath the normal row. In this expanded space, Fixtable will render whatever custom component you specify in the expandedRowComponent property. This component will receive the same dataRow binding as any custom cell component in the main row (see Custom Cell Components above).

To allow for toggling rows between expanded and collapsed state, you should include a component in one of your column definitions that toggles the rowExpanded property. Fixtable includes a simple component, fixtable-row-toggle, that you can use for this purpose. You can also make a custom component if you need additional logic or prefer a different design. It's also noteworthy that any custom cell component can read and write the rowExpanded property, so you can toggle or react to this value in multiple columns if needed.

Because "expanded" rows are actually rendered as two table rows, Fixtable takes some steps to ensure you can still style them as desired. For one thing, any border between the two rows will be removed by default to maintain the illusion of a single row (this can, naturally, be overridden in your own CSS). Fixtable also applies some helpful CSS classes to each row element:

  • fixtable-row-primary - the primary, multi-column row (rendered from your column definition)
  • fixtable-row-secondary - the second, full-width column that renders your expandedRowComponent
  • expanded - present on both row elements when the dataRow is expanded
  • hover - present on both row elements when the user's cursor is hovering over either row element (you should use this class rather than relying on the :hover pseudo-class if you want to use hover styles with expanded rows)
  • active - present on both row elements if the row is selected (see Row Selection above)

Debugging

Fixtable-Ember utilizes the core Fixtable library to handle the low-level DOM manipulation required for the fixed-header effect, ensuring Fixtable is styled to match your application's tables, etc. If you notice rendering issues, they're likely to originate in this core library and not Fixtable-Ember itself. If you set the debugMode property to true on your fixtable-grid component, it will enable debug logs on its internal Fixtable object which may be helpful in diagnosing issues.

Development / Contributing

Installation

  • git clone this repository
  • npm install

Running

Running Tests

  • npm test (Runs ember try:testall to test your addon against multiple Ember versions)
  • ember test
  • ember test --server

Building

  • ember build

For more information on using ember-cli, visit http://ember-cli.com/.

fixtable-ember's People

Contributors

aymane08 avatar ember-tomster avatar heymagurany avatar hornetnz avatar inindevevangelists avatar menewman avatar michaelmcauley avatar mraustinmiller avatar pmoaddel avatar rbrcurtis avatar sfanetti avatar smargovsky avatar t-boss avatar toddjordan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fixtable-ember's Issues

Fixtable-grid fails to render if using row selection and data is not set at render

resetSelection: Ember.observer('visibleContent.[]', function fixtableGrid$resetSelection() {
    if (!this.get('rowSelection')) { return; }

    let selectedRows = Ember.Object.create();
    this.set('selectedRows', selectedRows);
    this.get('visibleContent').forEach((row, rowIndex) => {
      selectedRows.set(rowIndex.toString(), false);
    });

This forEach fails ungracefully when data is not set at render time. Setting the data to an empty array fixes this, though this behavior is inconsistent with the same table without row selection.

Add a observed property to allow externally triggered reload

There are always cases when we need to apply filter that either are not displayed in the fixtable-grid, or would make more sense to be triggered through another on-screen or off screen action. However, currently the notifyReloadContent() is only called when the page / filter / sort changes.

As the action of notifyReloadContent exists outside of the componment, we can modify the query call and add all kinds of filter to our hearts content, but we lack a way to trigger the grid reload.

If we can setup a observed property that would call the notifyReloadContent and trigger the grid reload, we can easily set up all kind of custom filters without additional effort from fixtable-ember side. All we need to do is to setup the appropriate filter in the onReloadContent action, and change the observed property to trigger the grid-reload.

Would that sound like a good enough trade off to break the component isolation? All we need is a easy way to trigger reload externally, and this could potentially make it quite a bit more powerful..

Add support for labels on select-type filters

On fixtable-angular, I noticed that for each object in the "selectOptions" field of the filter column, you could optionally add a "label" field. In my application, I need to show the user a string for an option but actually send a filter request with a different value. For instance, I want to show the user options for "true" and "false", but I want the filter request to be for the values 1 and 0 respectively.

Add support for overriding strings

For the sake of localization, we should let consumers easily override any user-facing strings. For now, I think these are the only strings that need to be localized:

"No data available" -> when no rows are loaded
"Page {{X}} of {{Y}}" -> in the pagination footer
"{{X}} items per page" -> in the pagination footer
"Clear" -> on the button that appears after you manually filter

Use dasherized name for get-select-options in fixtable-grid.hbs

Currently can get an error like this:

WARNING: Attempted to lookup "helper:getSelectOptions" which was not found. In previous versions of ember-resolver, a bug would have caused the module at "/helpers/get-select-options" to be returned for this camel case helper name. This has been fixed. Use the dasherized name to resolve the module that would have been returned in previous versions.

Should fix this by dasherizing the name in fixtable-grid.hbs.

Broccoli plugin error on ember-cli v2.13.x

I get the following error when I try to launch my app. Any ideas how to fix it?

The Broccoli Plugin: [SourceMapConcat: Concat: Vendor /assets/vendor.js] failed with:
Error: ENOENT: no such file or directory, open 'C:\workspace\ProjectName\tmp\source_map_concat-input_base_path-SjMvR1OV.tmp\C:\workspace\ProjectName\node_modules\fixtable-ember\node_modules\fixtable\dist\fixtable.js'
    at Error (native)
    at Object.fs.openSync (fs.js:640:18)
    at Object.fs.readFileSync (fs.js:508:33)
    at SourceMap.addFile (C:\workspace\ProjectName\node_modules\fast-sourcemap-concat\lib\source-map.js:75:31)
    at C:\workspace\ProjectName\node_modules\broccoli-concat\concat.js:200:16
    at Array.forEach (native)
    at Concat.<anonymous> (C:\workspace\ProjectName\node_modules\broccoli-concat\concat.js:198:24)
    at C:\workspace\ProjectName\node_modules\fast-sourcemap-concat\lib\source-map.js:393:12
    at initializePromise (C:\workspace\ProjectName\node_modules\rsvp\dist\rsvp.js:589:5)
    at new Promise$1 (C:\workspace\ProjectName\node_modules\rsvp\dist\rsvp.js:1077:33)

The broccoli plugin was instantiated at: 
    at Concat.Plugin (C:\workspace\ProjectName\node_modules\broccoli-plugin\index.js:7:31)
    at new Concat (C:\workspace\ProjectName\node_modules\broccoli-concat\concat.js:38:10)
    at module.exports (C:\workspace\ProjectName\node_modules\broccoli-concat\index.js:26:10)
    at EmberApp._concatFiles (C:\workspace\ProjectName\node_modules\ember-cli\lib\broccoli\ember-app.js:432:12)
    at EmberApp.javascript (C:\workspace\ProjectName\node_modules\ember-cli\lib\broccoli\ember-app.js:1275:14)
    at EmberApp.toArray (C:\workspace\ProjectName\node_modules\ember-cli\lib\broccoli\ember-app.js:1662:12)
    at EmberApp.toTree (C:\workspace\ProjectName\node_modules\ember-cli\lib\broccoli\ember-app.js:1684:38)
    at module.exports (C:\workspace\ProjectName\ember-cli-build.js:27:14)
    at Builder.setupBroccoliBuilder (C:\workspace\ProjectName\node_modules\ember-cli\lib\models\builder.js:56:19)
    at new Builder (C:\workspace\ProjectName\node_modules\ember-cli\lib\models\builder.js:30:10)

Add support for a contextual button toolbar at the top of the table

We likely can't know ahead of time what the buttons actually need to be, so we should allow some amount of user specification. Either they provide a full-blown component that we render into that space, or they provide some "button definitions" and we raise actions/events for them.

Add support for client-side filtering

Filtering should be permitted either with client data only (in which case we can own the filtering) or with a hook to allow server-side filtering. Don't forget to debounce.

Are the installation commands redundant?

Does it suffice to just call ember install without npm install? If so, we can update the README so that it excludes npm install.

It says to do this now:

npm install --save fixtable-ember
ember install fixtable-ember

Allow the user to show/hide columns

Is this possible in the current implementation? Would re-rendering the component after changing the columns object that is passed to the component help?

The functionality could be similar to the models table which provides a dropdown listing all the columns and a checkbox next to each column name.

Add support for custom filter types

Currently we only allow two kinds of filters: search (which is a "contains"-type filter) and select (which is an equality filter). We could also let the user define custom filter types; Fixtable-Angular appears to already have this functionality.

For now, not high priority because I can't think of a good use case (search & select should cover the vast majority of use cases). Just wanted to document this feature since the Angular version has it.

font-awesome not imported in when fixtable-ember is nested in addons > ember-cli 2.12

The error you get when running ember server in an app that includes an addon that uses fixtable-ember is:

ENOENT: no such file or directory, scandir '/Users/omarestrella/Development/orgspan/dummy-app/node_modules/dummy-addon/node_modules/fixtable-ember/node_modules/font-awesome/fonts'

You can recreate the issue by creating a dummy app and dummy addon, installing ember-fixtable in the dummy addon (inside dependencies), putting the addon in the package.json and linking the addon to the app, and running ember server.

There should probably be a distinction between the root app and the parent so that the correct node_modules path is used. For example:

let rootApp = app;
while (rootApp.app) {
  rootApp = rootApp.app;
}

let parent = app;
while (parent.parent) {
  parent = parent.parent;
}

And you can use parent.nodeModulesPath for the correct path and the rootApp.import for importing the CSS that is being imported.

Add support for overriding parts of the template

For example, allow for adding a custom header, a custom footer, a custom loading indicator, or custom in-cell markup. For in-cell markup, it's especially important to allow consumers to specify a {{#link-to}}{{/link-to}} within the cell so that the content can be clickable and point to some other route.

Add a loading indicator

Add support for a loading indicator that can be shown or hidden. Optionally, show the loading indicator by default if there's no data in the grid.

Add support for turning off auto-filtering

Instead, show an "Apply" button that lets you manually apply the filter(s).

Ideally this would only be used if the cost of making requests to the server is expensive for some reason -- otherwise the usability of auto-filtering is much nicer. Filtering is already debounced (by 500ms by default) to help mitigate the pain of making AJAX calls.

Optionally, also add a "Clear" button to match "Apply." Fixtable-Angular doesn't seem to already have this, for whatever reason, but it seems like it would be a nice (and simple) addition.

Row selection is wrong when filters are applied

screen shot 2017-03-07 at 3 07 16 pm

In this screenshot, note that the "Selected" display shows "Sherlock Holmes" even though the actually selected row is "Peggy Hill." It doesn't take a Sherlock to realize that the selection here is indexing into the full, unfiltered set of data rows. We should investigate to see if this is just the consumer (in this case, the dummy app) misusing data it receives from the table, or if it's actually a bug in what the table is giving to the consumer.

Fix deprecation warning about use of didInsertElement hook

From the dev console when a fixtable-grid component is initialized:

DEPRECATION: A property of <dummy@view:-outlet::ember327> was modified inside the didInsertElement hook. You should never change properties on components, services or models during didInsertElement because it causes significant performance degradation. [deprecation id: ember-views.dispatching-modify-property]

I believe this is a result of the following line of code in fixtable-grid.js:
this.set('fixtable', fixtable);

Even if this doesn't have a truly significant performance impact in our use case, we should find another way to do this that doesn't clutter up the console with a deprecation warning.

Add support for tooltips

Perhaps show a tooltip by default in case text gets cut off, or allow the user to specify a custom tooltip. Regular points for title attribute, bonus points for a nicer-looking tooltip.

Update the README for ember-engines

For this addon to work when nested into an ember-engine, the engine's package.json file needs to include the fixtable-ember dependency in "dependencies" instead of "devDependencies." I should add this to the section describing how to add a blueprint when using a nested addon, since it's related.

For select-type filters, get the options dynamically

It'd be nice if a Fixtable could figure out the options for a select-type filter dynamically by running through all of the data rows and seeing what the possible values for that column are, rather than needing to specify the options in the configuration.

This wouldn't work for a data set that's paginated on the server, so presumably we'd still need the ability to specify select options in the config in that case.

Add support for column sorting

Should sorting be limited to data already loaded on the client? Or do we need a hook for sorting all data (including on the web server)? Probably the latter.

install fail file fonts/FontAwesome.otf exists in

home@DESKTOP-6KJ8TSQ c:\keith\emberProject\data_managerment
$ ember build
cleaning up...
Build failed.
The Broccoli Plugin: [BroccoliMergeTrees: TreeMerger (otherAssetTrees)] failed with:
Error: Merge error: file fonts/FontAwesome.otf exists in c:\keith\emberProject\data_managerment\tmp\broccoli_merge_trees-input_base_path-MIcXya4p.tmp\1 and c:\keith\emberProject\data_managerment\tmp\broccoli_merge_trees-input_base_path-MIcXya4p.tmp\2
Pass option { overwrite: true } to mergeTrees in order to have the latter file win.
at BroccoliMergeTrees._mergeRelativePath (c:\keith\emberProject\data_managerment\node_modules\broccoli-merge-trees\index.js:274:17)
at BroccoliMergeTrees._mergeRelativePath (c:\keith\emberProject\data_managerment\node_modules\broccoli-merge-trees\index.js:300:31)
at BroccoliMergeTrees.build (c:\keith\emberProject\data_managerment\node_modules\broccoli-merge-trees\index.js:82:24)
at c:\keith\emberProject\data_managerment\node_modules\broccoli-plugin\read_compat.js:61:34
at tryCatch (c:\keith\emberProject\data_managerment\node_modules\rsvp\dist\rsvp.js:538:12)
at invokeCallback (c:\keith\emberProject\data_managerment\node_modules\rsvp\dist\rsvp.js:553:13)
at publish (c:\keith\emberProject\data_managerment\node_modules\rsvp\dist\rsvp.js:521:7)
at flush (c:\keith\emberProject\data_managerment\node_modules\rsvp\dist\rsvp.js:2373:5)
at _combinedTickCallback (internal/process/next_tick.js:67:7)
at process._tickCallback (internal/process/next_tick.js:98:9)

The broccoli plugin was instantiated at:
at BroccoliMergeTrees.Plugin (c:\keith\emberProject\data_managerment\node_modules\broccoli-plugin\index.js:10:31)
at new BroccoliMergeTrees (c:\keith\emberProject\data_managerment\node_modules\broccoli-merge-trees\index.js:42:10)
at BroccoliMergeTrees (c:\keith\emberProject\data_managerment\node_modules\broccoli-merge-trees\index.js:36:53)
at module.exports (c:\keith\emberProject\data_managerment\node_modules\ember-cli\lib\broccoli\merge-trees.js:8:14)
at EmberApp.otherAssets (c:\keith\emberProject\data_managerment\node_modules\ember-cli\lib\broccoli\ember-app.js:1478:10)
at EmberApp.toArray (c:\keith\emberProject\data_managerment\node_modules\ember-cli\lib\broccoli\ember-app.js:1648:10)
at EmberApp.toTree (c:\keith\emberProject\data_managerment\node_modules\ember-cli\lib\broccoli\ember-app.js:1668:30)
at module.exports (c:\keith\emberProject\data_managerment\ember-cli-build.js:71:14)
at CoreObject.setupBroccoliBuilder (c:\keith\emberProject\data_managerment\node_modules\ember-cli\lib\models\builder.js:72:19)
at CoreObject.init (c:\keith\emberProject\data_managerment\node_modules\ember-cli\lib\models\builder.js:52:10)

Add support for pagination

Ideally, allow jumping to a specific page, paging forward/backward, and configuring page size. We should try to assume as little as possible about how pagination is actually implemented by the consuming app -- they might have all the data loaded onto the client, or (more likely) they might be paginating the response from the web server. Also, they might be tracking pagination info in various ways (e.g., by storing them in the query params).

' Uncaught TypeError: (row[columnKey] || "").toLowerCase is not a function ' When sorting or filtering

Happens to me on latest chrome and firefox, when I am trying to sort or filter records.

Here is my setup:

A simple line in template:
{{fixtable-grid columns=fixTableCols content=reports filterDebounce=1000 }}

The column definition looks like this:

  fixTableCols: [
    {key:'name', header:'Name',  sortable:true, filter:{type:'search'}, component:'fixtable-name-link'},
    {key:'label', header:'Label',sortable:true, filter:{type:'search'} },
    {key:'description', header:'Description',sortable:true,filter:{type:'search'} },
    {key:'reportPath', header:'Path',sortable:true, filter:{type:'search'} },
    {key:'reportSections', header:'Sections',sortable:true,  component:'report-fixtable-section'},
    {key:'parameters', header:'Parameters',sortable:true,  component:'report-fixtable-parameter'},
  ],

and the model looks like this:

  name: attr('string'),
  label: attr('string'),
  description: attr('string'),
  reportPath: attr('string'),
  // relations
  reportSections: DS.hasMany('report-section'),
  parameters: DS.hasMany('parameter'),

I have found out that if I change the sort / filter key part of the code from simple [key] lookup to .get(), the sort and filter works.
(addon/componments/fixtable-grid.js)
Ln 162: var cellData = (row[columnKey] || '').toLowerCase();
change to
Ln 162: var cellData = (row.get(columnKey) || '').toLowerCase();

Could this be due to I have hasMany in the model?
Or it is something else on my setup is quirky?

And, is this the right way to fix this? If yes, I can send in pull request : )

Cannot find module 'broccoli-funnel' and 'broccoli-merge-trees'

I recently upgraded from 0.1.x to the latest stable version and I could not run my ember project because of these missing npm modules. I noticed that they are there in this project's devDependencies, should they be in dependencies instead? index.js requires these modules.

Pager controls should be disabled depending on content

You shouldn't be able to page back if you're on the first page, or page forward if you're on the last page. The pager controls already don't let you navigate to an invalid state, but it'd be nice if there was a visual indication of this as well.

The checkbox column for selecting rows throws off column widths

I just noticed this while looking at the live demo for this addon.

Below, you can see that the checkbox column seems to have the width that the ID column should have, whereas the ID column has the width that the name column should have. It's an off-by-one error.

To be more specific -- In the column definitions, the ID column is defined to be 50px, while the name column is set to 200px. This works correctly in the examples without a checkbox column. But when the checkbox column is present, the checkbox column is 50px and the ID column is 200px.

screen shot 2016-06-22 at 10 43 59 am

Create a way to easily use either the unstyled or the skinned version of Fixtable

There is currently a Fixtable "skin" (i.e., stylesheet primarily focused on colors) that only lives in the dummy test app. We shouldn't force this on users who prefer to use their own styling, but it'd be nice to make it available somehow (through this addon? through a separate fixtable-ember-skin addon?) so that there's a better way to re-use it than simply copy/paste.

Debounce the notification that the current page has changed

For server-side pagination in particular, it is unnecessarily expensive to notify that the page has changed after every keystroke when the user is typing into the "current page" input. This will effectively lead to an AJAX call after every keystroke.

I don't see this as very high priority, although it would be good to fix it. Fixtable-Angular behaves this way too, and the issue is entirely avoided if the user uses the previous / forward pager controls.

An alternative to debouncing would be waiting until focus leaves the input before changing the current page. This can feel a little less responsive (especially if client-side pagination is on, in which case re-paginating after each keystroke is relatively cheap), but the advantage is that we won't make changes until we're pretty confident that the user is done typing.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.