Skip to content

Commit 771fbf7

Browse files
committed
v0.3.0 ~ Added checkbox filter, patched filter picker component, new table context methods, patches
1 parent 551a429 commit 771fbf7

File tree

20 files changed

+256
-169
lines changed

20 files changed

+256
-169
lines changed

addon/components/dropdown-button.hbs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
{{did-insert this.onInsert}}
1616
as |dd|
1717
>
18-
<dd.Trigger class={{@triggerClass}} {{did-insert this.onTriggerInsert}} {{did-update this.onArgsChanged @disabled @visible @permission}}>
18+
<dd.Trigger class={{@triggerClass}} {{did-insert this.onTriggerInsert}} {{did-update this.onArgsChanged @disabled @visible @permission @buttonComponentArgs}}>
1919
{{#if @buttonComponent}}
2020
{{component
2121
@buttonComponent

addon/components/dropdown-button.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@ export default class DropdownButtonComponent extends Component {
7272
this._onInsertFired = true;
7373
}
7474

75-
@action onArgsChanged(el, [disabled = false, visible = true, permission = null]) {
75+
@action onArgsChanged(el, [disabled = false, visible = true, permission = null, buttonComponentArgs = {}]) {
76+
this.buttonComponentArgs = buttonComponentArgs;
7677
this.visible = visible;
7778
this.disabled = disabled;
7879
if (!disabled && permission) {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<div class="filter-checkbox">
2+
<Checkbox @value={{this.value}} @label={{or @filter.filterLabel @filter.label}} @onToggle={{this.onChange}} @alignItems="center" @labelClass="mb-0i" />
3+
</div>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import Component from '@glimmer/component';
2+
import { tracked } from '@glimmer/tracking';
3+
import { action } from '@ember/object';
4+
import toBoolean from '@fleetbase/ember-core/utils/to-boolean';
5+
6+
export default class FilterCheckboxComponent extends Component {
7+
@tracked value = false;
8+
9+
constructor(owner, { value = false }) {
10+
super(...arguments);
11+
this.value = toBoolean(value);
12+
}
13+
14+
@action onChange(checked) {
15+
const { onChange, filter } = this.args;
16+
17+
this.value = checked;
18+
19+
if (typeof onChange === 'function') {
20+
onChange(filter, checked);
21+
}
22+
}
23+
}

addon/components/filters-picker.hbs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@
2929
<div class="grid grid-cols-3 lg:grid-cols-3 sm:grid-cols-1 gap-2 w-full mb-4">
3030
{{#each this.filters as |filter|}}
3131
<div class="filter-component">
32-
<label class="filter-component-label">{{or filter.filterLabel filter.label}}</label>
32+
{{#unless filter.noFilterLabel}}
33+
<label class="filter-component-label">{{or filter.filterLabel filter.label}}</label>
34+
{{/unless}}
3335
{{component
3436
filter.filterComponent
3537
value=filter.filterValue

addon/components/filters-picker.js

Lines changed: 79 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -1,183 +1,116 @@
11
import Component from '@glimmer/component';
2-
import { tracked } from '@glimmer/tracking';
32
import { inject as service } from '@ember/service';
4-
import { set, action } from '@ember/object';
5-
import { filter, gt } from '@ember/object/computed';
3+
import { tracked } from '@glimmer/tracking';
4+
import { action } from '@ember/object';
65
import { isArray } from '@ember/array';
7-
import { later } from '@ember/runloop';
86
import getUrlParam from '../utils/get-url-param';
97

108
export default class FiltersPickerComponent extends Component {
11-
/**
12-
* Inject router to handle param changes via `transitionTo`
13-
*
14-
* @memberof FiltersPickerComponent
15-
*/
169
@service hostRouter;
17-
18-
/**
19-
* Array of filters created from columns argument.
20-
*
21-
* @memberof FiltersPickerComponent
22-
*/
2310
@tracked filters = [];
2411

25-
/**
26-
* Filters which are active and should be applied.
27-
*
28-
* @memberof FiltersPickerComponent
29-
*/
30-
@filter('filters.@each.isFilterActive', (filter) => filter.isFilterActive === true) activeFilters;
31-
32-
/**
33-
* Computed property that determines if any filters are set.
34-
*
35-
* @memberof FiltersPickerComponent
36-
*/
37-
@gt('activeFilters.length', 0) hasFilters;
38-
39-
/**
40-
* Creates an instance of FiltersPickerComponent.
41-
* @memberof FiltersPickerComponent
42-
*/
12+
get activeFilters() {
13+
return this.filters.filter((f) => f.isFilterActive);
14+
}
15+
16+
get hasFilters() {
17+
return this.activeFilters.length > 0;
18+
}
19+
4320
constructor() {
4421
super(...arguments);
45-
this.updateFilters();
22+
23+
this.#rebuildFilters(); // initial state
24+
25+
// Refresh whenever the route (→ query-params) changes
26+
this._routeHandler = () => this.#rebuildFilters();
27+
this.hostRouter.on('routeDidChange', this._routeHandler);
4628
}
4729

48-
/**
49-
* Creates and updates filters via map
50-
*
51-
* @param {null|Function} onColumn
52-
* @memberof FiltersPickerComponent
53-
*/
54-
@action updateFilters(onColumn) {
55-
this.filters = this.args.columns
56-
.filter((column) => column.filterable)
57-
.map((column, trueIndex) => {
58-
// add true index to column
59-
column = { ...column, trueIndex };
60-
61-
// set the column param
62-
column.param = column.filterParam ?? column.valuePath;
63-
64-
// get the active param if any and update filter
65-
const activeParam = getUrlParam(column.param);
66-
67-
// update if an activeParam exists
68-
if (activeParam) {
69-
column.isFilterActive = true;
70-
71-
if (isArray(activeParam) && activeParam.length === 0) {
72-
column.isFilterActive = false;
73-
}
74-
75-
column.filterValue = activeParam;
76-
}
30+
willDestroy() {
31+
super.willDestroy(...arguments);
32+
this.hostRouter.off('routeDidChange', this._routeHandler);
33+
}
34+
35+
#readUrlValue(param) {
36+
const raw = getUrlParam(param); // string | string[] | undefined
37+
if (isArray(raw)) {
38+
return raw.length ? raw : undefined;
39+
}
40+
return raw === '' ? undefined : raw;
41+
}
42+
43+
#rebuildFilters(onColumn) {
44+
const cols = this.args.columns ?? [];
45+
46+
this.filters = cols
47+
.filter((c) => c.filterable)
48+
.map((column, index) => {
49+
const param = column.filterParam ?? column.valuePath;
50+
const value = this.#readUrlValue(param);
51+
const active = value != null; // null & undefined only
52+
53+
const filterCol = {
54+
...column,
55+
trueIndex: index,
56+
param,
57+
filterValue: value,
58+
isFilterActive: active,
59+
};
7760

78-
// callback to modify column from hook
7961
if (typeof onColumn === 'function') {
80-
onColumn(column, trueIndex, activeParam);
62+
onColumn(filterCol, index, value);
8163
}
8264

83-
return column;
65+
return filterCol;
8466
});
8567

8668
return this;
8769
}
8870

89-
/**
90-
* Triggers the apply callback for the filters picker.
91-
*
92-
* @memberof FiltersPickerComponent
93-
*/
9471
@action applyFilters() {
95-
const { onApply } = this.args;
96-
97-
// run `onApply()` callback
98-
if (typeof onApply === 'function') {
99-
onApply();
72+
if (typeof this.args.onApply === 'function') {
73+
this.args.onApply();
10074
}
101-
102-
// manually run update filters after apply with slight 300ms delay to update activeFilters
103-
later(
104-
this,
105-
() => {
106-
this.updateFilters();
107-
},
108-
150
109-
);
11075
}
11176

112-
/**
113-
* Updates an individual filter/column value.
114-
*
115-
* @param {String} key
116-
* @param {*} value
117-
* @memberof FiltersPickerComponent
118-
*/
11977
@action updateFilterValue({ param }, value) {
120-
const { onChange } = this.args;
121-
122-
// run `onChange()` callback
123-
if (typeof onChange === 'function') {
124-
onChange(param, value);
78+
if (typeof this.args.onChange === 'function') {
79+
this.args.onChange(param, value);
12580
}
12681
}
12782

128-
/**
129-
* Callback to clear a single filter/column value.
130-
*
131-
* @param {String} key
132-
* @memberof FiltersPickerComponent
133-
*/
13483
@action clearFilterValue({ param }) {
135-
const { onFilterClear } = this.args;
136-
137-
// update filters
138-
this.updateFilters((column) => {
139-
if (column.param !== param) {
140-
return;
141-
}
142-
143-
// clear column values
144-
set(column, 'filterValue', undefined);
145-
set(column, 'isFilterActive', false);
146-
});
147-
148-
// run `onFilterClear()` callback
149-
if (typeof onFilterClear === 'function') {
150-
onFilterClear(param);
84+
if (typeof this.args.onFilterClear === 'function') {
85+
this.args.onFilterClear(param);
15186
}
15287
}
15388

154-
/**
155-
* Used to clear all filter/column values and URL params.
156-
*
157-
* @memberof FiltersPickerComponent
158-
*/
159-
@action clearFilters() {
160-
const { onClear } = this.args;
161-
const currentRouteName = this.hostRouter.currentRouteName;
162-
const currentQueryParams = { ...this.hostRouter.currentRoute.queryParams };
163-
164-
// update filters
165-
this.updateFilters((column) => {
166-
const paramKey = column.filterParam ?? column.valuePath;
167-
delete currentQueryParams[paramKey];
168-
delete currentQueryParams[`${paramKey}[]`];
169-
170-
// reset column values
171-
set(column, 'filterValue', undefined);
172-
set(column, 'isFilterActive', false);
173-
});
174-
175-
// transition to cleared params with router service
176-
this.hostRouter.transitionTo(currentRouteName, { queryParams: currentQueryParams });
177-
178-
// run `onClear()` callback
179-
if (typeof onClear === 'function') {
180-
onClear(...arguments);
89+
@action async clearFilters(...args) {
90+
if (typeof this.args.onClear === 'function') {
91+
this.args.onClear(...args);
92+
}
93+
94+
// Build a clean query-param bag
95+
const qp = { ...this.hostRouter.currentRoute.queryParams };
96+
(this.args.columns ?? [])
97+
.filter((c) => c.filterable)
98+
.forEach((c) => {
99+
const key = c.filterParam ?? c.valuePath;
100+
delete qp[key];
101+
delete qp[`${key}[]`];
102+
});
103+
104+
// Transition – routeDidChange listener will rebuild afterwards
105+
try {
106+
await this.hostRouter.transitionTo(this.hostRouter.currentRouteName, {
107+
queryParams: qp,
108+
});
109+
} catch (error) {
110+
// Ignore only the "transition aborted" case
111+
if (error?.name !== 'TransitionAborted') {
112+
throw error; // real error → rethrow
113+
}
181114
}
182115
}
183116
}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
<Button @type={{@type}} @text={{or @text "Filters"}} @icon={{or @icon "filter"}} @size={{or @size "xs"}} @wrapperClass={{@wrapperClass}} ...attributes>
2-
{{#if @buttonComponentArgs.activeFilters.length}}
1+
<Button @type={{@type}} @text={{or @text "Filters"}} @icon={{or @icon "filter"}} @size={{or @size "xs"}} @wrapperClass={{@wrapperClass}} {{did-update this.handleComponentArgsUpdate @buttonComponentArgs}} ...attributes>
2+
{{#if this.buttonComponentArgs.activeFilters.length}}
33
<div class="box-divider">
4-
<span class="font-semibold text-sky-500">{{@buttonComponentArgs.activeFilters.length}}</span>
4+
<span class="font-semibold text-sky-500">{{this.buttonComponentArgs.activeFilters.length}}</span>
55
</div>
66
{{/if}}
77
</Button>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import Component from '@glimmer/component';
2+
import { tracked } from '@glimmer/tracking';
3+
import { action } from '@ember/object';
4+
5+
export default class FiltersPickerButtonComponent extends Component {
6+
@tracked buttonComponentArgs = {};
7+
8+
constructor(owner, { buttonComponentArgs = {} }) {
9+
super(...arguments);
10+
this.buttonComponentArgs = buttonComponentArgs;
11+
}
12+
13+
@action handleComponentArgsUpdate(el, [buttonComponentArgs = {}]) {
14+
this.buttonComponentArgs = buttonComponentArgs;
15+
}
16+
}

addon/components/modals/bulk-action-model.hbs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
{{pluralize @options.modelName}}?
2323
{{/if}}
2424
</h3>
25-
<div class="mt-2">
25+
<div class="mt-0.5">
2626
<p class="text-sm leading-5 text-gray-500 dark:text-gray-50">
2727
{{#if @options.message}}
2828
{{@options.message}}
@@ -36,14 +36,27 @@
3636
</div>
3737
</div>
3838
</div>
39-
<ul class="list-scroll-box rounded shadow border border-gray-300 bg-gray-100 dark:bg-gray-800">
39+
<div>{{yield @options}}</div>
40+
<ul class="list-scroll-box rounded shadow border border-gray-300 bg-gray-100 dark:bg-gray-800 {{@options.listScrollBoxClass}}">
4041
{{#each @options.selected as |selected|}}
41-
<li>
42-
<span class="text-sm dark:text-gray-100">{{n-a (get selected @options.modelNamePath)}}</span>
42+
<li id={{selected.id}} data-public-id={{selected.public_id}}>
43+
<div>
44+
{{#if @options.modelNameRenderComponent}}
45+
{{component @options.modalNameRenderComponent options=@options}}
46+
{{else}}
47+
{{#if @options.resolveModelName}}
48+
<span class="text-sm dark:text-gray-100">{{n-a selected.list_resolved_name}}</span>
49+
{{else}}
50+
<span class="text-sm dark:text-gray-100">{{n-a (get selected @options.modelNamePath)}}</span>
51+
{{/if}}
52+
{{/if}}
53+
</div>
4354

44-
<a href="javascript:;" {{on "click" (fn @options.remove selected)}} class="my-1">
45-
<FaIcon @icon="times-circle" />
46-
</a>
55+
<div>
56+
<a href="javascript:;" {{on "click" (fn @options.remove selected)}} class="my-1">
57+
<FaIcon @icon="times-circle" />
58+
</a>
59+
</div>
4760
</li>
4861
{{/each}}
4962
</ul>

0 commit comments

Comments
 (0)