Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature] Compound Filters (input & date) #32

Merged
merged 9 commits into from
Mar 20, 2018
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ bower_components
npm-debug.log
package-lock.json
yarn.lock
yarn-error.log
1 change: 1 addition & 0 deletions aurelia-slickgrid/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ node_modules

# Compiled files
scripts
yarn-error.log
6 changes: 6 additions & 0 deletions aurelia-slickgrid/assets/i18n/en/aurelia-slickgrid.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,24 @@
"CLEAR_ALL_FILTERS": "Clear All Filters",
"COLUMNS": "Columns",
"COMMANDS": "Commands",
"CONTAINS": "Contains",
"ENDS_WITH": "Ends With",
"EQUALS": "Equals",
"EXPORT_TO_CSV": "Export in CSV format",
"EXPORT_TO_TAB_DELIMITED": "Export in Text format (Tab delimited)",
"FROM_TO_OF_TOTAL_ITEMS": "{{from}}-{{to}} of {{totalItems}} items",
"FORCE_FIT_COLUMNS": "Force fit columns",
"GROUP_BY": "Group by",
"IN_COLLECTION_SEPERATED_BY_COMMA": "Search items in a collection, must be separated by a comma (a,b)",
"ITEMS": "Items",
"ITEMS_PER_PAGE": "items per page",
"NOT_IN_COLLECTION_SEPERATED_BY_COMMA": "Search items not in a collection, must be separated by a comma (a,b)",
"OF": "of",
"PAGE": "Page",
"PAGE_X_OF_Y": "page {{x}} of {{y}}",
"REFRESH_DATASET": "Refresh Dataset",
"SELECT_ALL": "Select All",
"STARTS_WITH": "Starts With",
"SYNCHRONOUS_RESIZE": "Synchronous resize",
"TOGGLE_FILTER_ROW": "Toggle Filter Row",
"X_OF_Y_SELECTED": "# of % selected",
Expand Down
6 changes: 6 additions & 0 deletions aurelia-slickgrid/assets/i18n/fr/aurelia-slickgrid.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,24 @@
"CLEAR_ALL_FILTERS": "Effacer tous les filtres",
"COLUMNS": "Colonnes",
"COMMANDS": "Commandes",
"CONTAINS": "Contient",
"ENDS_WITH": "Se termine par",
"EQUALS": "Égale",
"EXPORT_TO_CSV": "Exporter en format CSV",
"EXPORT_TO_TAB_DELIMITED": "Exporter en format texte (délimité par tabulation)",
"FROM_TO_OF_TOTAL_ITEMS": "{{from}}-{{to}} de {{totalItems}} éléments",
"FORCE_FIT_COLUMNS": "Ajustement forcé des colonnes",
"GROUP_BY": "Grouper par",
"IN_COLLECTION_SEPERATED_BY_COMMA": "Recherche incluant certain éléments d'une collection, doit être séparé par une virgule (a,b)",
"ITEMS": "Éléments",
"ITEMS_PER_PAGE": "éléments par page",
"NOT_IN_COLLECTION_SEPERATED_BY_COMMA": "Recherche excluant certain éléments d'une collection, doit être séparé par une virgule (a,b)",
"OF": "de",
"PAGE": "Page",
"PAGE_X_OF_Y": "page {{x}} de {{y}}",
"REFRESH_DATASET": "Rafraîchir les données",
"SELECT_ALL": "Sélectionner tout",
"STARTS_WITH": "Commence par",
"SYNCHRONOUS_RESIZE": "Redimension synchrone",
"TOGGLE_FILTER_ROW": "Basculer la ligne des filtres",
"X_OF_Y_SELECTED": "# de % sélectionné",
Expand Down
7 changes: 5 additions & 2 deletions aurelia-slickgrid/src/aurelia-slickgrid/editors/dateEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ export class DateEditor implements Editor {
this.defaultDate = this.args.item[this.args.column.field] || null;
const inputFormat = mapFlatpickrDateFormatWithFieldType(this.args.column.type || FieldType.dateIso);
const outputFormat = mapFlatpickrDateFormatWithFieldType(this.args.column.outputType || FieldType.dateUtc);
const currentLocale = this.getCurrentLocale(this.args.column, gridOptions);
let currentLocale = this.getCurrentLocale(this.args.column, gridOptions);
if (currentLocale.length > 2) {
currentLocale = currentLocale.substring(0, 2);
}

const pickerOptions: any = {
defaultDate: this.defaultDate,
Expand All @@ -43,7 +46,7 @@ export class DateEditor implements Editor {
}

getCurrentLocale(columnDef: Column, gridOptions: GridOption) {
const params = columnDef.params || {};
const params = gridOptions.params || columnDef.params || {};
if (params.i18n && params.i18n instanceof I18N) {
return params.i18n.getLocale();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@ import { testFilterCondition } from './filterUtilities';
import * as moment from 'moment';

export const dateUtcFilterCondition: FilterCondition = (options: FilterConditionOption) => {
if (!options.filterSearchType) {
throw new Error('Date UTC filter is a special case and requires a filterSearchType to be provided in the column option, for example: { filterable: true, type: FieldType.dateUtc, filterSearchType: FieldType.dateIso }');
}

const searchDateFormat = mapMomentDateFormatWithFieldType(options.filterSearchType);
const searchDateFormat = mapMomentDateFormatWithFieldType(options.filterSearchType || options.fieldType);
if (!moment(options.cellValue, moment.ISO_8601).isValid() || !moment(options.searchTerm, searchDateFormat, true).isValid()) {
return true;
}
Expand Down
242 changes: 242 additions & 0 deletions aurelia-slickgrid/src/aurelia-slickgrid/filters/compoundDateFilter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
import { inject } from 'aurelia-framework';
import { I18N } from 'aurelia-i18n';
import { mapFlatpickrDateFormatWithFieldType } from '../services/utilities';
import {
Column,
FieldType,
Filter,
FilterArguments,
FilterCallback,
FilterType,
GridOption,
OperatorString,
OperatorType,
SearchTerm
} from './../models/index';
import * as flatpickr from 'flatpickr';
import * as $ from 'jquery';

@inject(I18N)
export class CompoundDateFilter implements Filter {
private $filterElm: any;
private $filterInputElm: any;
private $selectOperatorElm: any;
private _currentValue: string;
flatInstance: any;
grid: any;
gridOptions: GridOption;
operator: OperatorType | OperatorString | undefined;
searchTerm: SearchTerm | undefined;
columnDef: Column;
callback: FilterCallback;
filterType = FilterType.compoundDate;

constructor(private i18n: I18N) { }

/**
* Initialize the Filter
*/
init(args: FilterArguments) {
if (args) {
this.grid = args.grid;
this.callback = args.callback;
this.columnDef = args.columnDef;
this.operator = args.operator || '';
this.searchTerm = args.searchTerm;
if (this.grid && typeof this.grid.getOptions === 'function') {
this.gridOptions = this.grid.getOptions();
}

// step 1, create the DOM Element of the filter which contain the compound Operator+Input
// and initialize it if searchTerm is filled
this.$filterElm = this.createDomElement();

// step 3, subscribe to the keyup event and run the callback when that happens
// also add/remove "filled" class for styling purposes
this.$filterInputElm.keyup((e: any) => {
this.onTriggerEvent(e);
});
this.$selectOperatorElm.change((e: any) => {
this.onTriggerEvent(e);
});
}
}

/**
* Clear the filter value
*/
clear(triggerFilterKeyup = true) {
if (this.flatInstance && this.$selectOperatorElm) {
this.$selectOperatorElm.val(0);
this.flatInstance.clear();
}
}

/**
* destroy the filter
*/
destroy() {
if (this.$filterElm) {
this.$filterElm.off('keyup').remove();
}
}

/**
* Set value(s) on the DOM element
*/
setValues(values: SearchTerm) {
if (values) {
this.flatInstance.setDate(values);
}
}

//
// private functions
// ------------------

private buildDatePickerInput(searchTerm: SearchTerm) {
const inputFormat = mapFlatpickrDateFormatWithFieldType(this.columnDef.type || FieldType.dateIso);
const outputFormat = mapFlatpickrDateFormatWithFieldType(this.columnDef.outputType || this.columnDef.type || FieldType.dateUtc);
let currentLocale = this.getCurrentLocale(this.columnDef, this.gridOptions) || '';
if (currentLocale.length > 2) {
currentLocale = currentLocale.substring(0, 2);
}

const pickerOptions: any = {
defaultDate: searchTerm || '',
altInput: true,
altFormat: outputFormat,
dateFormat: inputFormat,
wrap: true,
closeOnSelect: true,
locale: (currentLocale !== 'en') ? this.loadFlatpickrLocale(currentLocale) : 'en',
onChange: (selectedDates: any[] | any, dateStr: string, instance: any) => {
this._currentValue = dateStr;
this.onTriggerEvent(undefined);
},
};

// add the time picker when format is UTC (Z) or has the 'h' (meaning hours)
if (outputFormat === 'Z' || outputFormat.includes('h')) {
pickerOptions.enableTime = true;
}

const placeholder = (this.gridOptions) ? (this.gridOptions.defaultFilterPlaceholder || '') : '';
const $filterInputElm: any = $(`<div class=flatpickr><input type="text" class="form-control" data-input placeholder="${placeholder}"></div>`);
this.flatInstance = (flatpickr && $filterInputElm[0] && typeof $filterInputElm[0].flatpickr === 'function') ? $filterInputElm[0].flatpickr(pickerOptions) : null;
return $filterInputElm;
}

private buildSelectOperatorHtmlString() {
const optionValues = this.getOptionValues();
let optionValueString = '';
optionValues.forEach((option) => {
optionValueString += `<option value="${option.operator}" title="${option.description}">${option.operator}</option>`;
});

return `<select class="form-control">${optionValueString}</select>`;
}

private getOptionValues(): { operator: OperatorString, description: string }[] {
return [
{ operator: '', description: '' },
{ operator: '=', description: '' },
{ operator: '<', description: '' },
{ operator: '<=', description: '' },
{ operator: '>', description: '' },
{ operator: '>=', description: '' },
{ operator: '<>', description: '' }
];
}

/**
* Create the DOM element
*/
private createDomElement() {
const $headerElm = this.grid.getHeaderRowColumn(this.columnDef.id);
$($headerElm).empty();

const searchTerm = (this.searchTerm || '') as string;
if (searchTerm) {
this._currentValue = searchTerm;
}

// create the DOM Select dropdown for the Operator
this.$selectOperatorElm = $(this.buildSelectOperatorHtmlString());
this.$filterInputElm = this.buildDatePickerInput(searchTerm);
const $filterContainerElm = $(`<div class="form-group search-filter"></div>`);
const $containerInputGroup = $(`<div class="input-group flatpickr"></div>`);
const $operatorInputGroupAddon = $(`<div class="input-group-addon operator"></div>`);

/* the DOM element final structure will be
<div class="input-group">
<div class="input-group-addon operator">
<select class="form-control"></select>
</div>
<div class=flatpickr>
<input type="text" class="form-control" data-input>
</div>
</div>
*/
$operatorInputGroupAddon.append(this.$selectOperatorElm);
$containerInputGroup.append($operatorInputGroupAddon);
$containerInputGroup.append(this.$filterInputElm);

// create the DOM element & add an ID and filter class
$filterContainerElm.append($containerInputGroup);
$filterContainerElm.attr('id', `filter-${this.columnDef.id}`);
this.$filterInputElm.data('columnId', this.columnDef.id);

if (this.operator) {
this.$selectOperatorElm.val(this.operator);
}

// if there's a search term, we will add the "filled" class for styling purposes
if (this.searchTerm) {
$filterContainerElm.addClass('filled');
}

// append the new DOM element to the header row
if ($filterContainerElm && typeof $filterContainerElm.appendTo === 'function') {
$filterContainerElm.appendTo($headerElm);
}

return $filterContainerElm;
}

private getCurrentLocale(columnDef: Column, gridOptions: GridOption) {
const params = gridOptions.params || columnDef.params || {};
if (params.i18n && params.i18n instanceof I18N) {
return params.i18n.getLocale();
}

return 'en';
}

private loadFlatpickrLocale(locale: string) {
// change locale if needed, Flatpickr reference: https://chmln.github.io/flatpickr/localization/
if (locale !== 'en') {
const localeDefault: any = require(`flatpickr/dist/l10n/${locale}.js`).default;
return (localeDefault && localeDefault[locale]) ? localeDefault[locale] : 'en';
}
return 'en';
}

private onTriggerEvent(e: Event | undefined) {
const selectedOperator = this.$selectOperatorElm.find('option:selected').text();
(this._currentValue) ? this.$filterElm.addClass('filled') : this.$filterElm.removeClass('filled');
this.callback(e, { columnDef: this.columnDef, searchTerm: this._currentValue, operator: selectedOperator || '=' });
}

private hide() {
if (this.flatInstance && typeof this.flatInstance.close === 'function') {
this.flatInstance.close();
}
}

private show() {
if (this.flatInstance && typeof this.flatInstance.open === 'function') {
this.flatInstance.open();
}
}
}
Loading