diff --git a/addon/components/upf-checkbox.js b/addon/components/upf-checkbox.js index 59f805a75..e6fb99832 100644 --- a/addon/components/upf-checkbox.js +++ b/addon/components/upf-checkbox.js @@ -1,6 +1,11 @@ import Component from '@ember/component'; +import { equal } from '@ember/object/computed'; export default Component.extend({ classNames: ['upf-checkbox'], - classNameBindings: ['hasLabel:upf-checkbox--has-label'] + classNameBindings: [ + 'hasLabel:upf-checkbox--has-label', 'sizeSmall:upf-checkbox--sm' + ], + + sizeSmall: equal('size', 'sm') }); diff --git a/addon/components/upf-table/cell.js b/addon/components/upf-table/cell.js new file mode 100644 index 000000000..a89b0f48c --- /dev/null +++ b/addon/components/upf-table/cell.js @@ -0,0 +1,107 @@ +import Component from '@ember/component'; +import { computed, defineProperty, observer } from '@ember/object'; +import { or } from '@ember/object/computed'; +import { capitalize } from '@ember/string'; +import { isEmpty } from '@ember/utils'; + +const AVAILABLE_RENDERERS = [ + 'text', 'numeric', 'money', 'date' +]; + +export default Component.extend({ + classNames: ['upf-hypertable__cell'], + classNameBindings: [ + 'header:upf-hypertable__cell--header', + 'item.selected:upf-hypertable__cell--selected', + 'loading:upf-hypertable__cell--loading', + '_sorted:upf-hypertable__cell--sorted', + '_filtered:upf-hypertable__cell--filtered', + 'isNumeric:upf-hypertable__cell--numeric', + 'isMoney:upf-hypertable__cell--numeric' + ], + + header: false, + selection: false, + loading: false, + + _sorted: false, + _filtered: false, + _showFiltersPanel: false, + + _sortingIconClass: computed('_sorted', function() { + if (this._sorted) { + let [_, direction] = this.column.sortBy.split(':'); + + return (direction === 'asc') ? 'fa-long-arrow-up' : 'fa-long-arrow-down'; + } + }), + + _typeInferredRenderingComponent: computed('column.type', function() { + if (AVAILABLE_RENDERERS.includes(this.column.type)) { + return `upf-table/cell/renderers/${this.column.type}`; + } + }), + + _renderingComponent: or( + 'column.renderingComponent', '_typeInferredRenderingComponent' + ), + + _filtersRenderingComponent: computed('column.type', function() { + if (AVAILABLE_RENDERERS.includes(this.column.type)) { + return `upf-table/cell/filters-renderers/${this.column.type}`; + } + }), + + _filtersChanged: observer('column.filters.@each', function () { + this.set('_filtered', !isEmpty(this.column.filters)); + }), + + _tableStateListener() { + this.set('_sorted', !isEmpty(this.column.sortBy)); + this.set('_filtered', !isEmpty(this.column.filters)); + }, + + _filtersPanelListener() { + if (this.column.property !== this.manager.applyingFiltersOn) { + this.set('showFiltersPanel', false); + } + }, + + didReceiveAttrs() { + if (this.column) { + AVAILABLE_RENDERERS.forEach((rendererType) => { + defineProperty( + this, + `is${capitalize(rendererType)}`, + computed('column.type', function() { + return this.column.type === rendererType; + }) + ); + }); + + if (this.header) { + this.addObserver( + 'manager.columns.@each.sortBy', + this, + this._tableStateListener + ); + + this.addObserver( + 'manager.applyingFiltersOn', + this, + this._filtersPanelListener + ); + } + + this.set('_sorted', !isEmpty(this.column.sortBy)); + this.set('_filtered', !isEmpty(this.column.filters)); + } + }, + + actions: { + toggleFiltersPanel() { + this.toggleProperty('showFiltersPanel'); + this.set('manager.applyingFiltersOn', this.column.property); + } + } +}); diff --git a/addon/components/upf-table/cell/filters-renderers/date.js b/addon/components/upf-table/cell/filters-renderers/date.js new file mode 100644 index 000000000..89f38bc8c --- /dev/null +++ b/addon/components/upf-table/cell/filters-renderers/date.js @@ -0,0 +1,69 @@ +import Component from '@ember/component'; +import { computed } from '@ember/object'; + +export default Component.extend({ + classNames: ['available-filters'], + + sortingOptions: { + 'Oldest — Newest': 'alphanumerical:asc', + 'Newest — Oldest': 'alphanumerical:desc' + }, + + filteringOptions: { + 'Fixed': 'fixed', + 'Moving': 'moving' + }, + + filterOption: 'moving', // or 'moving' + + currentMovingDateOption: computed('column.filters.@each.value.alias', function() { + let filter = this.column.filters.find((f) => f.type === 'range'); + + return (filter) ? filter.value.alias : null; + }), + + movingDateOptions: { + 'Today': 'today', + 'Yesterday': 'yesterday' + }, + + _buildDateRange(from) { + switch(from) { + case 'today': + return { + alias: 'today', + from: moment().startOf('day').format('X'), + to: moment().endOf('day').format('X') + } + case 'yesterday': + return { + alias: 'yesterday', + from: moment().subtract(1, 'day').startOf('day').format('X'), + to: moment().subtract(1, 'day').endOf('day').format('X') + } + default: + break; + } + }, + + actions: { + sortingOptionChanged(value) { + this.manager.updateSortBy(this.column, value); + }, + + filterOptionChanged(value) { + this.set('filterOption', value); + }, + + selectMovingDate(value) { + this.manager.addFilters( + this.column, 'range', this._buildDateRange(value) + ); + }, + + // Mixin Candidate + clearFilters() { + this.manager.clearFilters(this.column); + } + } +}); diff --git a/addon/components/upf-table/cell/filters-renderers/money.js b/addon/components/upf-table/cell/filters-renderers/money.js new file mode 100644 index 000000000..ac4d3f73a --- /dev/null +++ b/addon/components/upf-table/cell/filters-renderers/money.js @@ -0,0 +1,16 @@ +import Component from '@ember/component'; + +export default Component.extend({ + classNames: ['available-filters'], + + sortingOptions: { + '0 — 9': 'alphanumerical:asc', + '9 — 0': 'alphanumerical:desc' + }, + + actions: { + sortingOptionChanged(value) { + this.manager.updateSortBy(this.column, value); + } + } +}); diff --git a/addon/components/upf-table/cell/filters-renderers/numeric.js b/addon/components/upf-table/cell/filters-renderers/numeric.js new file mode 100644 index 000000000..ac4d3f73a --- /dev/null +++ b/addon/components/upf-table/cell/filters-renderers/numeric.js @@ -0,0 +1,16 @@ +import Component from '@ember/component'; + +export default Component.extend({ + classNames: ['available-filters'], + + sortingOptions: { + '0 — 9': 'alphanumerical:asc', + '9 — 0': 'alphanumerical:desc' + }, + + actions: { + sortingOptionChanged(value) { + this.manager.updateSortBy(this.column, value); + } + } +}); diff --git a/addon/components/upf-table/cell/filters-renderers/text.js b/addon/components/upf-table/cell/filters-renderers/text.js new file mode 100644 index 000000000..f0e94de02 --- /dev/null +++ b/addon/components/upf-table/cell/filters-renderers/text.js @@ -0,0 +1,16 @@ +import Component from '@ember/component'; + +export default Component.extend({ + classNames: ['available-filters'], + + sortingOptions: { + 'A — Z': 'alphanumerical:asc', + 'Z — A': 'alphanumerical:desc' + }, + + actions: { + sortingOptionChanged(value) { + this.manager.updateSortBy(this.column, value); + } + } +}); diff --git a/addon/components/upf-table/cell/renderers/date.js b/addon/components/upf-table/cell/renderers/date.js new file mode 100644 index 000000000..c3b60190b --- /dev/null +++ b/addon/components/upf-table/cell/renderers/date.js @@ -0,0 +1,31 @@ +import Component from '@ember/component'; +import { computed } from '@ember/object'; +import { empty, or } from '@ember/object/computed'; +import { typeOf } from '@ember/utils'; + +import moment from 'moment'; + +export default Component.extend({ + tagName: '', + + value: computed('item', 'column.property', function() { + return this.item.get(this.column.property); + }), + + emptyValue: empty('value'), + + _defaultDateFormat: 'MM/DD/YYYY', + _dateFormat: or('column.date_format', '_defaultDateFormat'), + + _formattedDate: computed('value', '_dateFormat', function() { + let _date; + + if (typeOf(this.value) === 'date') { + _date = moment(this.value); + } else { + _date = moment.unix(this.value) + } + + return _date.format(this._dateFormat); + }) +}); diff --git a/addon/components/upf-table/cell/renderers/money.js b/addon/components/upf-table/cell/renderers/money.js new file mode 100644 index 000000000..2fa97f2b5 --- /dev/null +++ b/addon/components/upf-table/cell/renderers/money.js @@ -0,0 +1,23 @@ +import Component from '@ember/component'; +import { computed } from '@ember/object'; +import { empty } from '@ember/object/computed'; + +export default Component.extend({ + tagName: '', + + currency: computed('column', 'column.currency_key', function() { + if (this.column && !this.column.currency_key) { + throw new Error( + '[upf-table/cell-renderers][money] You are trying to render Money without a currency' + ); + } + + return this.item.get(this.column.currency_key); + }), + + amount: computed('item', 'column.property', function() { + return this.item.get(this.column.property); + }), + + emptyAmount: empty('amount') +}); diff --git a/addon/components/upf-table/cell/renderers/numeric.js b/addon/components/upf-table/cell/renderers/numeric.js new file mode 100644 index 000000000..d8471b335 --- /dev/null +++ b/addon/components/upf-table/cell/renderers/numeric.js @@ -0,0 +1,13 @@ +import Component from '@ember/component'; +import { computed } from '@ember/object'; +import { empty } from '@ember/object/computed'; + +export default Component.extend({ + tagName: '', + + value: computed('item', 'column.property', function() { + return this.item.get(this.column.property); + }), + + emptyValue: empty('value') +}); diff --git a/addon/components/upf-table/cell/renderers/text.js b/addon/components/upf-table/cell/renderers/text.js new file mode 100644 index 000000000..8d9485d5c --- /dev/null +++ b/addon/components/upf-table/cell/renderers/text.js @@ -0,0 +1,11 @@ +import Component from '@ember/component'; +import { computed } from '@ember/object'; +import { empty } from '@ember/object/computed'; + +export default Component.extend({ + value: computed('item', 'column.property', function() { + return this.item.get(this.column.property); + }), + + emptyValue: empty('value') +}); diff --git a/addon/components/upf-table/index.js b/addon/components/upf-table/index.js index fa3dbba05..1e7c45a15 100644 --- a/addon/components/upf-table/index.js +++ b/addon/components/upf-table/index.js @@ -1,64 +1,93 @@ +import { A } from '@ember/array'; import Component from '@ember/component'; -import EmberObject, { observer, computed } from '@ember/object'; -import { filterBy } from '@ember/object/computed'; +import { computed, observer } from '@ember/object'; +import { alias, filterBy } from '@ember/object/computed'; import { run } from '@ember/runloop'; +import { isEmpty } from '@ember/utils'; + +const DEFAULT_OPTIONS = { + features: { + selection: false, + search: false + } +}; export default Component.extend({ - classNames: ['upf-table__container'], - classNameBindings: ['isCompact:upf-table__container--compact'], - - hasSelection: false, - hasPagination: false, - hasSearch: false, - hasPolymorphicColumns: false, - hasToggleableColumns: true, - isCompact: false, - - allRowsSelected: false, - displayedColumnsPanel: false, - isLoading: false, - contentChanging: false, - _contentPlaceholder: new Array(3), + classNames: ['upf-hypertable-container'], + //classNameBindings: ['isCompact:upf-table__container--compact'], + + contextualActions: null, + loadingMore: false, + + /* + * Configuration + * ============= + * + * Define which features of the datatable should be activated. + * + */ + options: DEFAULT_OPTIONS, + + /* + * Event Hooks + * =========== + * + * Actions to be called to react to various events happening on the datatable + * + */ + onColumnsChange: null, + onBottomReached: null, + onSearchQueryChange: null, + + _allRowsSelected: false, + _hasScrollbar: false, + + //hasPagination: false, + //hasPolymorphicColumns: false, + //hasToggleableColumns: true, + //isCompact: false, + + _availableColumnsPanel: false, + _availableColumnsKeyword: '', + //isLoading: false, + //contentChanging: false, + //_contentPlaceholder: new Array(3), _searchQuery: null, - searchInputPlaceholder: 'Search...', - - currentPage: 1, - totalPages: 1, - perPage: 1, - itemTotal: 0, - itemCount: 0, - itemName: '', - - init() { - this._super(); - - this.set('_searchQuery', this.get('searchQuery')); - }, - - _columns: computed('columns', function() { - return this.get('columns').map((column) => { - column.visible = column.visible !== false; - column.sorted = column.sorted === true; - column.editable = column.editable !== false; - column.unhideable = column.unhideable === true; - - return EmberObject.create(column); - }); - }), + //searchInputPlaceholder: 'Search...', + +/* currentPage: 1,*/ + //totalPages: 1, + //perPage: 1, + //itemTotal: 0, + //itemCount: 0, + /*itemName: '',*/ + + _columns: alias('manager.columns'), + _selectedItems: filterBy('collection', 'selected', true), + + _orderedFilteredColumns: computed( + '_columns', + '_availableColumnsKeyword', + function() { + let columns = A(this._columns); + + if (!isEmpty(this._availableColumnsKeyword)) { + let reg = RegExp(this._availableColumnsKeyword, 'i'); + columns = A(columns.filter((x) => reg.test(x.title))); + } - _initiallyDisplayedColumns: filterBy('_columns', 'visible'), - _fullSizeColumnColspan: computed('_initiallyDisplayedColumns', function() { - if (this.get('hasSelection')) { - return this.get('_initiallyDisplayedColumns').length + 1; + return columns.sortBy('title'); } + ), - return this.get('_initiallyDisplayedColumns').length; + _loadingMore: computed('_hasScrollbar', 'loadingMore', function() { + return this.onBottomReached && this._hasScrollbar && this.loadingMore; }), - _selectAllObserver: observer('allRowsSelected', function() { - this.get('collection').map((item) => { - if (this.get('allRowsSelected')) { + _selectAllObserver: observer('_allRowsSelected', function() { + this.get('collection').forEach((item) => { + if (this.get('_allRowsSelected')) { item.set('selected', true); } else { item.set('selected', false); @@ -66,53 +95,98 @@ export default Component.extend({ }); }), - _bubbleSearch: function() { - if (this.performSearch) { - this.performSearch(this.get('_searchQuery')); + _columnsChanged: observer( + '_columns', '_columns.@each.{visible,sortBy,filters}', + function() { + if (this.onColumnsChange) { + this.onColumnsChange(this._columns); + } } - }, + ), - _searchQueryObserver: observer('_searchQuery', function() { - run.debounce(this, this._bubbleSearch, 100); + _selectedItemsChanged: observer('_selectedItems', function() { + if (this.contextualActions) { + let ca = document.querySelector('.contextual-actions'); + + if (this._selectedItems.length > 0) { + ca.classList.remove('contextual-actions--no-animation'); + ca.classList.add('contextual-actions--visible'); + } else { + ca.classList.remove('contextual-actions--visible'); + ca.classList.add('contextual-actions--hidden'); + } + } }), - actions: { - toggleDisplayedColumnVisibilityPanel() { - this.toggleProperty('displayedColumnsPanel'); - }, + _searchQueryObserver: observer('_searchQuery', function() { + run.debounce(this, () => { + if (this.onSearchQueryChange) { + this.onSearchQueryChange(this.get('_searchQuery')); + } + }, 1000); + }), - callOnRowClickCallback(action, record) { - this.onRowClick(record); - }, + didInsertElement() { + let self = this; - didPageChange(page) { - this.sendAction('didPageChange', page); - }, + this.$('.upf-hypertable__table').on('scroll', function() { + let tableHeight = $(this).innerHeight(); + let contentHeight = $('.upf-hypertable')[0].scrollHeight; + let heightScrolled = $(this).scrollTop(); - onClickHeader(column) { - if (!column.get('sortKey')) { - return; - } + self.set('_hasScrollbar', (tableHeight <= contentHeight)); - if (!column.get('sorted')) { - this.get('_columns').forEach((c) => c.set('sorted', false)); - // default direction - column.set('sorted', true); - column.set('direction', 'desc'); - } else { - let direction = column.get('direction') === 'desc' ? 'asc' : 'desc'; - column.set('direction', direction); + if ((heightScrolled + tableHeight) >= contentHeight) { + if (self.onBottomReached) { + self.onBottomReached(); + } } + }); + }, - this.sendAction('didSortColumn', column); - }, - - triggerObjectCreation() { - this.triggerAction({ action: 'handleObjectCreation'}); + actions: { + reorderColumns(x, itemModels, _) { + let _cs = [x[0]].concat(itemModels.concat(x.filter(x => !x.visible))) + _cs.forEach((c, i) => c.set('order', i)) + this.manager.updateColumns(_cs); }, - setContentChanging(contentChanging) { - this.set('contentChanging', contentChanging); + openAvailableColumns() { + this.toggleProperty('_availableColumnsPanel'); } + + //callOnRowClickCallback(action, record) { + //this.onRowClick(record); + //}, + + //didPageChange(page) { + //this.sendAction('didPageChange', page); + /*},*/ + + //onClickHeader(column) { + //if (!column.get('sortKey')) { + //return; + //} + + //if (!column.get('sorted')) { + //this.get('_columns').forEach((c) => c.set('sorted', false)); + //// default direction + //column.set('sorted', true); + //column.set('direction', 'desc'); + //} else { + //let direction = column.get('direction') === 'desc' ? 'asc' : 'desc'; + //column.set('direction', direction); + //} + + //this.sendAction('didSortColumn', column); + /*},*/ + + //triggerObjectCreation() { + //this.triggerAction({ action: 'handleObjectCreation'}); + //}, + + //setContentChanging(contentChanging) { + //this.set('contentChanging', contentChanging); + //} } }); diff --git a/addon/helpers/format-money.js b/addon/helpers/format-money.js index 2f9d3a57b..623869d86 100644 --- a/addon/helpers/format-money.js +++ b/addon/helpers/format-money.js @@ -9,11 +9,8 @@ var _getFormatter = function(currency) { }; var _formatMoney = function(amount, currency) { - if (amount > 0) { - return _getFormatter(currency).format(parseFloat(amount)); - } else { - return '--'; - } + if (isNaN(parseInt(amount))) return amount; + return _getFormatter(currency).format(parseFloat(amount)); }; diff --git a/addon/services/upf-table-state.js b/addon/services/upf-table-state.js new file mode 100644 index 000000000..512b6bd22 --- /dev/null +++ b/addon/services/upf-table-state.js @@ -0,0 +1,57 @@ +import { A } from '@ember/array'; +import Service from '@ember/service'; +import EmberObject from '@ember/object'; +import { isEmpty, typeOf } from '@ember/utils'; + +const Table = EmberObject.extend({ + columns: [], + applyingFiltersOn: null, + + updateColumns(columns) { + this.set('columns', columns.map((column) => { + if (typeOf(column) !== 'instance') { + column = EmberObject.create(column); + } + + column.set('visible', column.visible !== false); + column.set('sortBy', column.sortBy || null); + column.set( + 'filters', + (column.filters || []).map((x) => EmberObject.create(x)) + ); + column.set('type', column.type || 'text'); + + return column; + })); + }, + + updateSortBy(column, sortBy) { + this.columns.forEach((c) => c.set('sortBy', null)); + column.set('sortBy', sortBy); + }, + + addFilters(column, type, value) { + if (isEmpty(column.filters)) { + let f = EmberObject.create({ type, value }) + column.set('filters', [f]); + } else { + column.set('filters', column.filters.map((f) => { + if (f.type === type) { + f.set('value', value); + } + + return f; + })); + } + }, + + clearFilters(column) { + column.set('filters', []); + } +}); + +export default Service.extend({ + createTable() { + return Table.create(); + } +}); diff --git a/app/components/upf-table/cell.js b/app/components/upf-table/cell.js new file mode 100644 index 000000000..763ad29bf --- /dev/null +++ b/app/components/upf-table/cell.js @@ -0,0 +1 @@ +export { default } from 'oss-components/components/upf-table/cell'; diff --git a/app/components/upf-table/cell/filters-renderers/date.js b/app/components/upf-table/cell/filters-renderers/date.js new file mode 100644 index 000000000..aec5d59bc --- /dev/null +++ b/app/components/upf-table/cell/filters-renderers/date.js @@ -0,0 +1 @@ +export { default } from 'oss-components/components/upf-table/cell/filters-renderers/date'; diff --git a/app/components/upf-table/cell/filters-renderers/money.js b/app/components/upf-table/cell/filters-renderers/money.js new file mode 100644 index 000000000..1873f436d --- /dev/null +++ b/app/components/upf-table/cell/filters-renderers/money.js @@ -0,0 +1 @@ +export { default } from 'oss-components/components/upf-table/cell/filters-renderers/money'; diff --git a/app/components/upf-table/cell/filters-renderers/numeric.js b/app/components/upf-table/cell/filters-renderers/numeric.js new file mode 100644 index 000000000..80141e186 --- /dev/null +++ b/app/components/upf-table/cell/filters-renderers/numeric.js @@ -0,0 +1 @@ +export { default } from 'oss-components/components/upf-table/cell/filters-renderers/numeric'; diff --git a/app/components/upf-table/cell/filters-renderers/text.js b/app/components/upf-table/cell/filters-renderers/text.js new file mode 100644 index 000000000..f8da34f64 --- /dev/null +++ b/app/components/upf-table/cell/filters-renderers/text.js @@ -0,0 +1 @@ +export { default } from 'oss-components/components/upf-table/cell/filters-renderers/text'; diff --git a/app/components/upf-table/cell/renderers/date.js b/app/components/upf-table/cell/renderers/date.js new file mode 100644 index 000000000..9498690ea --- /dev/null +++ b/app/components/upf-table/cell/renderers/date.js @@ -0,0 +1 @@ +export { default } from 'oss-components/components/upf-table/cell/renderers/date'; diff --git a/app/components/upf-table/cell/renderers/money.js b/app/components/upf-table/cell/renderers/money.js new file mode 100644 index 000000000..ab154483e --- /dev/null +++ b/app/components/upf-table/cell/renderers/money.js @@ -0,0 +1 @@ +export { default } from 'oss-components/components/upf-table/cell/renderers/money'; diff --git a/app/components/upf-table/cell/renderers/numeric.js b/app/components/upf-table/cell/renderers/numeric.js new file mode 100644 index 000000000..2c10de031 --- /dev/null +++ b/app/components/upf-table/cell/renderers/numeric.js @@ -0,0 +1 @@ +export { default } from 'oss-components/components/upf-table/cell/renderers/numeric'; diff --git a/app/components/upf-table/cell/renderers/text.js b/app/components/upf-table/cell/renderers/text.js new file mode 100644 index 000000000..01b732de1 --- /dev/null +++ b/app/components/upf-table/cell/renderers/text.js @@ -0,0 +1 @@ +export { default } from 'oss-components/components/upf-table/cell/renderers/text'; diff --git a/app/helpers/format-numeric.js b/app/helpers/format-numeric.js index f87b44829..c23a6b436 100644 --- a/app/helpers/format-numeric.js +++ b/app/helpers/format-numeric.js @@ -1,22 +1,5 @@ import Helper from '@ember/component/helper'; -var _getFormatter = function(currency) { - return Intl.NumberFormat(['en-EN', 'fr-FR'], { - style: 'currency', - currency: currency, - minimumFractionDigits: 0, // show decimals only if there are ones - }); -}; - -var _formatMoney = function(amount, currency) { - if (amount > 0) { - return _getFormatter(currency).format(parseFloat(amount)); - } else { - return '--'; - } -}; - - export function formatNumericHelper(params) { let [number] = params; let formatter = Intl.NumberFormat(['en-EN', 'fr-FR'], { @@ -24,11 +7,8 @@ export function formatNumericHelper(params) { minimumFractionDigits: 0, // show decimals only if there are ones }); - if(number > 0) { - return formatter.format(number); - } else { - return '--'; - } + if (isNaN(parseInt(number))) return number; + return formatter.format(number); } export default Helper.helper(formatNumericHelper); diff --git a/app/services/upf-table-state.js b/app/services/upf-table-state.js new file mode 100644 index 000000000..7b92da7b3 --- /dev/null +++ b/app/services/upf-table-state.js @@ -0,0 +1 @@ +export { default } from 'oss-components/services/upf-table-state'; diff --git a/app/styles/oss-components.less b/app/styles/oss-components.less index 253a0cb9f..79c88db6c 100644 --- a/app/styles/oss-components.less +++ b/app/styles/oss-components.less @@ -1,3 +1,410 @@ @import "bootstrap.less"; @import "font-awesome"; @import "upfluence-oss"; + + +@cell-height: 45px; + +.upf-checkbox--sm { + width: 15px; + + .upf-checkbox__fake-checkbox { + width: 15px; + height: 15px; + } + + .upf-checkbox__input:checked + .upf-checkbox__fake-checkbox:after { + top: 6px; + left: 4px; + height: 4px; + width: 7px; + } +} + +.upf-hypertable-container { + border: 1px solid lighten(@upf-gray, 15%); + border-radius: @default-radius; + width: 100%; +} + + +.upf-hypertable { + display: flex; +} + +/* + * Hypertable's Upper Header + * ======================== + * Access to actions allowing the user to modify the current state of the + * datatable. + * + */ +.upf-hypertable__upper-header { + padding: @spacing-xx-sm; + display: flex; + justify-content: space-between; + position: relative; + + .left-side { + display: flex; + flex-grow: 1; + } + + /* + * Search + * ====== + * + */ + .left-side { + input { + max-width: 250px; + } + } + + /* + * Contextual Actions + * =========== + * Contextual actions that show up to act on the selected items + * + */ + .left-side .contextual-actions { + border-left: 1px solid lighten(@upf-gray, 15%); + opacity: 0; + z-index: -1; + animation: none; + + margin-left: @spacing-sm; + padding: 0 @spacing-sm; + + &--no-search-sibling { + border-left: 0; + margin-left: 0; + padding: 0; + } + + &--no-animation { + display: none !important; + } + + &--hidden { + animation: hide-contextual-actions .5s 1; + } + + &--visible { + animation: show-contextual-actions .5s 1; + z-index: 1; + opacity: 1; + display: block; + } + } + + /* + * Available Columns + * ================= + * Access all available columns and toggle their visibility in the table + * + */ + .available-columns { + background-color: #fff; + border: 2px solid lighten(@upf-gray, 15%); + border-radius: @default-radius; + + display: flex; + flex-direction: row; + + position: absolute; + top: 55px; + right: 0; + z-index: 99999; + + .available-columns__categories { + background-color: @upf-gray-light; + border-right: 1px solid lighten(@upf-gray, 15%); + min-width: 200px; + + .field-category { + background-color: #fff; + border-bottom: 1px solid lighten(@upf-gray, 15%); + font-size: 16/@rem; + + display: flex; + align-items: center; + justify-content: space-between; + + padding: @spacing-sm @spacing-xx-sm; + } + } + + .available-columns__fields { + flex-direction: column; + flex-grow: 1; + padding: @spacing-xx-sm; + + .search { + background-color: #fff; + padding: @spacing-xxx-sm; + position: sticky; + + input { + width: 100%; + } + } + + .fields-list { + height: 200px; + max-height: 200px; + overflow: hidden scroll; + + .field { + border-radius: @default-radius; + color: @color-text; + min-width: 270px; + padding: @spacing-xxx-sm; + display: flex; + + &:hover { + background-color: @upf-gray-light; + cursor: pointer; + } + + &.field:not(.visible) { + color: @color-text-lighter; + } + } + } + + ::-webkit-scrollbar { + display: none; + } + } + + &:before, + &:after { + content:''; + position: absolute; + bottom: 100%; + right: 30px; + width: 0; + height: 0; + border-style: solid; + } + + &:after { + border-color: transparent transparent lighten(@upf-gray, 15%) transparent; + border-width: 8px; + } + + &:before { + border-color: transparent transparent lighten(@upf-gray, 15%) transparent; + border-width: 10px; + right: 28.5px + } + } +} + +.upf-hypertable__table { + max-height: 700px; + overflow: auto; +} + +.upf-hypertable__sticky-columns { + position: sticky; + left: 0; + z-index: 9999; + background-color: white; + display: flex; +} + +.upf-hypertable__column { + border-left: 1px solid lighten(@upf-gray, 15%); + + min-width: 200px; + flex-grow: 200; + + opacity: 1; + transition: opacity .5s; + + &:first-child { + border-left: none; + } + + // Animate Columns Drag & Drop + &.is-dragging { + opacity: .75; + } + + &.is-dropping { + opacity: 1; + } +} + +.upf-hypertable__column--selection { + width: 40px; + min-width: 40px; + flex-grow: 40; +} + +.upf-hypertable__cell { + border-bottom: 1px solid lighten(@upf-gray, 15%); + display: flex; + align-items: center; + + height: @cell-height; + max-height: @cell-height; + padding: @spacing-xx-sm; + width: 100%; + + .available-filters { + background-color: #fff; + border: 2px solid lighten(@upf-gray, 15%); + border-radius: @default-radius; + + min-width: 300px; + width: 300px; + + position: absolute; + padding: @spacing-xx-sm; + top: 50px; + + .filters { + padding-top: @spacing-xx-sm; + + .filters__option { + padding: @spacing-xxx-sm; + + &:hover { + background-color: @upf-gray-light; + cursor: pointer; + } + } + + .filters__option--active { + background-color: @upf-gray-light; + } + } + } + + &.upf-hypertable__cell--numeric:not(.upf-hypertable__cell--header) { + justify-content: flex-end; + } +} + +.upf-hypertable__cell--header { + background-color: @upf-gray-light; + color: @upf-primary-rock-blue; + cursor: pointer; + + justify-content: space-between; + position: sticky; + top: 0; + z-index: 9999; + + &.upf-hypertable__cell--sorted { + .fa.fa-long-arrow-up, .fa.fa-long-arrow-down { + color: @color-text-light; + font-weight: bold; + } + } + + &.upf-hypertable__cell--filtered { + .icon-commands .fa.fa-filter { + color: @color-text-light; + display: block; + font-weight: bold; + opacity: 1; + } + } + + & > .column-name { + display: flex; + } + + & > .icon-commands { + color: @color-text-lighter; + + display: flex; + flex-direction: row; + align-items: center; + + i { + opacity: 0; + display: none; + font-size: 16/@rem; + margin-left: @spacing-xx-sm; + } + + i:hover, i:active { + color: @color-text; + cursor: pointer; + } + + i.fa-bars { cursor: move; } + } + + &:hover { + background-color: lighten(@upf-gray, 15%); + + .icon-commands i { + opacity: 1; + display: block; + } + } +} + +.upf-hypertable__cell--selected { + background-color: @upf-gray-light; +} + +.upf-hypertable__cell--loading .skeleton-placeholder { + height: 10px; + animation: skeleton-content 1.2s ease-in-out infinite; + background-color: #eee; + background-image: linear-gradient(90deg, #eee, #f5f5f5, #eee); + background-size: 200px 100%; + background-repeat: no-repeat; + border-radius: @default-radius; + display: inline-block; + line-height: 1; + width: 100%; +} + +/* + * Animations used throughout the datatable + * ======================================= + * + */ +@keyframes show-contextual-actions { + 0% { + display: block; + opacity: 0; + transform: translateX(-50px); + } + + 100% { + opacity: 1; + transform: translateX(0); + } +} + + +@keyframes hide-contextual-actions { + 0% { + opacity: 1; + transform: translateX(0); + } + + 100% { + opacity: 0; + transform: translateX(-50px); + } +} + +@keyframes skeleton-content { + 0% { + background-position: -200px 0; + } + + 100% { + background-position: calc(200px + 100%) 0; + } +} diff --git a/app/templates/components/loading-state.hbs b/app/templates/components/loading-state.hbs index 47d01c29c..53e68d0ef 100644 --- a/app/templates/components/loading-state.hbs +++ b/app/templates/components/loading-state.hbs @@ -1,7 +1,7 @@ -
-
-
-
-
+
+
+
+
+
diff --git a/app/templates/components/upf-table.hbs b/app/templates/components/upf-table.hbs index 30139765a..3a64118db 100644 --- a/app/templates/components/upf-table.hbs +++ b/app/templates/components/upf-table.hbs @@ -1,136 +1,117 @@ - +
+ + + {{#if _availableColumnsPanel}} +
+
+
+
All Fields
+
+
+
+ +
+ -
- {{#each _columns as |column|}} - {{#unless column.unhideable}} -
- {{#upf-checkbox value=column.visible hasLabel=true}} - {{column.title}} - {{/upf-checkbox}} +
+ {{#each _orderedFilteredColumns as |column|}} +
+ {{upf-checkbox value=column.visible size="sm"}} +
{{column.title}}
+
+ {{/each}} +
+
- {{/unless}} - {{/each}} + {{/if}} +
- - - {{#upf-table/row isHeaderRow=true}} - {{#if hasSelection}} - {{#unless contentChanging}} - - {{/unless}} - {{/if}} +
+ {{#sortable-group + direction="x" model=_columns onChange=(action "reorderColumns") + tagName=null + classNames="upf-hypertable" as |group|}} + {{#each _columns as |column index|}} + {{#if (eq index 0)}} +
+ {{#if options.features.selection}} +
+ {{#upf-table/cell header=true selection=true}} + {{upf-checkbox value=_allRowsSelected size="sm"}} + {{/upf-table/cell}} - {{#each _columns as |column|}} - {{#unless (eq column.visible false)}} - {{upf-table/header_cell column=column - click=(action "onClickHeader" column)}} - {{/unless}} - {{/each}} - {{/upf-table/row}} -
+ {{#each collection as |item|}} +
+ {{upf-checkbox value=item.selected size="sm"}} +
+ {{/each}} - - {{#if isLoading}} - - - - {{else}} - {{#if contentChanging}} - {{#each _contentPlaceholder}} - {{#upf-table/row}} - - - {{/upf-table/row}} - {{/each}} - {{else}} - {{#each collection as |item index|}} - {{#upf-table/row ref=item action='callOnRowClickCallback' - hasPolymorphicColumns=hasPolymorphicColumns - onRowClick=onRowClick}} - {{#if hasSelection}} - - {{/if}} + {{#if _loadingMore}} + {{upf-table/cell loading=true}} + {{upf-table/cell loading=true}} + {{upf-table/cell loading=true}} + {{/if}} + + {{/if}} - {{#each _columns as |column|}} - {{#unless (eq column.visible false)}} - {{#upf-table/column ref=column editable=column.editable - classNames=column.additionalClasses}} - {{#if column.component}} - {{component column.component item=item column=column}} - {{else}} - {{#if (eq column.helper "money")}} - {{format-money (get item column.property) (get item column.currency)}} - {{else if (eq column.helper "numeric")}} - {{format-numeric (get item column.property)}} - {{else}} - {{get item column.property}} - {{/if}} - {{/if}} - {{/upf-table/column}} - {{/unless}} +
+ {{upf-table/cell column=column header=true manager=manager}} + + {{#each collection as |item|}} + {{upf-table/cell item=item column=column}} {{/each}} - {{/upf-table/row}} - {{else}} -
- - - {{/each}} - {{#if (and hasPagination isCompact)}} - - - - {{/if}} + {{#if _loadingMore}} + {{upf-table/cell loading=true}} + {{upf-table/cell loading=true}} + {{upf-table/cell loading=true}} + {{/if}} + + {{/if}} - {{/if}} - -
- {{upf-checkbox value=allRowsSelected}} -
- {{loading-state}} -
-
-
-
-
- {{upf-checkbox value=item.selected}} -
- {{yield}} -
- {{upf-table/pagination currentPage=currentPage perPage=perPage - totalPages=totalPages itemTotal=itemTotal - itemCount=itemCount itemName=itemName}} -
+ {{/each}} + + {{#each _columns as |column index|}} + {{#if (and column.visible (gt index 0))}} + {{#sortable-item + classNames="upf-hypertable__column" model=column group=group + handle=".upf-hypertable__cell--header"}} + {{upf-table/cell column=column header=true manager=manager}} + + {{#each collection as |item|}} + {{upf-table/cell item=item column=column}} + {{/each}} + + {{#if _loadingMore}} + {{upf-table/cell loading=true}} + {{upf-table/cell loading=true}} + {{upf-table/cell loading=true}} + {{/if}} + {{/sortable-item}} + {{/if}} + {{/each}} + {{/sortable-group}} +
diff --git a/app/templates/components/upf-table/cell.hbs b/app/templates/components/upf-table/cell.hbs new file mode 100644 index 000000000..d0a1aefc3 --- /dev/null +++ b/app/templates/components/upf-table/cell.hbs @@ -0,0 +1,30 @@ +{{#if header}} + {{#if selection}} + {{yield}} + {{else}} +
+ {{column.title}} + {{#if _sorted }} +   + {{/if}} +
+ +
+ + +
+ + {{#if showFiltersPanel}} + {{component + _filtersRenderingComponent item=item column=column manager=manager}} + {{/if}} + {{/if}} +{{else}} + {{#if loading}} +
+ {{else if (has-block)}} + {{yield}} + {{else}} + {{component _renderingComponent item=item column=column}} + {{/if}} +{{/if}} diff --git a/app/templates/components/upf-table/cell/filters-renderers/date.hbs b/app/templates/components/upf-table/cell/filters-renderers/date.hbs new file mode 100644 index 000000000..25b31fd0a --- /dev/null +++ b/app/templates/components/upf-table/cell/filters-renderers/date.hbs @@ -0,0 +1,41 @@ +{{#input-wrapper}} + +
+ {{#each-in sortingOptions as |label value|}} + {{radio-button + value=value currentValue=column.sortBy label=label + options=sortingOptions onCheck="sortingOptionChanged"}} + {{/each-in}} +
+{{/input-wrapper}} + +
+ + +
+ {{#each-in filteringOptions as |label value|}} + {{radio-button + value=value currentValue=filterOption label=label + options=filteringOptions onCheck="filterOptionChanged"}} + {{/each-in}} +
+ +
+ {{#if (eq filterOption "fixed")}} + Date Range + {{else}} + {{#each-in movingDateOptions as |label value|}} +
+ {{label}} +
+ {{/each-in}} + {{/if}} +
+
+ +
+ +
diff --git a/app/templates/components/upf-table/cell/filters-renderers/money.hbs b/app/templates/components/upf-table/cell/filters-renderers/money.hbs new file mode 100644 index 000000000..6cc0831e5 --- /dev/null +++ b/app/templates/components/upf-table/cell/filters-renderers/money.hbs @@ -0,0 +1,10 @@ +{{#input-wrapper}} + +
+ {{#each-in sortingOptions as |label value|}} + {{radio-button + value=value currentValue=column.sortBy label=label + options=sortingOptions onCheck="sortingOptionChanged"}} + {{/each-in}} +
+{{/input-wrapper}} diff --git a/app/templates/components/upf-table/cell/filters-renderers/numeric.hbs b/app/templates/components/upf-table/cell/filters-renderers/numeric.hbs new file mode 100644 index 000000000..6cc0831e5 --- /dev/null +++ b/app/templates/components/upf-table/cell/filters-renderers/numeric.hbs @@ -0,0 +1,10 @@ +{{#input-wrapper}} + +
+ {{#each-in sortingOptions as |label value|}} + {{radio-button + value=value currentValue=column.sortBy label=label + options=sortingOptions onCheck="sortingOptionChanged"}} + {{/each-in}} +
+{{/input-wrapper}} diff --git a/app/templates/components/upf-table/cell/filters-renderers/text.hbs b/app/templates/components/upf-table/cell/filters-renderers/text.hbs new file mode 100644 index 000000000..6cc0831e5 --- /dev/null +++ b/app/templates/components/upf-table/cell/filters-renderers/text.hbs @@ -0,0 +1,10 @@ +{{#input-wrapper}} + +
+ {{#each-in sortingOptions as |label value|}} + {{radio-button + value=value currentValue=column.sortBy label=label + options=sortingOptions onCheck="sortingOptionChanged"}} + {{/each-in}} +
+{{/input-wrapper}} diff --git a/app/templates/components/upf-table/cell/renderers/date.hbs b/app/templates/components/upf-table/cell/renderers/date.hbs new file mode 100644 index 000000000..bbd9f418f --- /dev/null +++ b/app/templates/components/upf-table/cell/renderers/date.hbs @@ -0,0 +1,5 @@ +{{#if emptyValue}} + — +{{else}} + {{_formattedDate}} +{{/if}} diff --git a/app/templates/components/upf-table/cell/renderers/money.hbs b/app/templates/components/upf-table/cell/renderers/money.hbs new file mode 100644 index 000000000..3897c0dd4 --- /dev/null +++ b/app/templates/components/upf-table/cell/renderers/money.hbs @@ -0,0 +1,5 @@ +{{#if emptyAmount}} + — +{{else}} + {{format-money amount currency}} +{{/if}} diff --git a/app/templates/components/upf-table/cell/renderers/numeric.hbs b/app/templates/components/upf-table/cell/renderers/numeric.hbs new file mode 100644 index 000000000..f6ecf9401 --- /dev/null +++ b/app/templates/components/upf-table/cell/renderers/numeric.hbs @@ -0,0 +1,5 @@ +{{#if emptyValue}} + — +{{else}} + {{format-numeric value}} +{{/if}} diff --git a/app/templates/components/upf-table/cell/renderers/text.hbs b/app/templates/components/upf-table/cell/renderers/text.hbs new file mode 100644 index 000000000..d9fc5d395 --- /dev/null +++ b/app/templates/components/upf-table/cell/renderers/text.hbs @@ -0,0 +1,5 @@ +{{#if emptyValue}} + — +{{else}} + {{get item column.property}} +{{/if}} diff --git a/package.json b/package.json index e6cbb3927..c3ee5bd23 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "test:all": "ember try:each" }, "dependencies": { + "@ember/jquery": "^0.6.1", "babel-plugin-debug-macros": "^0.3.1", "ember-cli-babel": "^7.1.2", "ember-cli-htmlbars": "^3.0.0", @@ -29,6 +30,7 @@ "ember-cli-less": "^1.5.3", "ember-cli-shims": "^1.2.0", "ember-cli-toggle": "^3.0.0", + "ember-sortable": "^1.12.9", "ember-truth-helpers": "^1.3.0", "upfluence-oss": "^1.x" }, @@ -40,6 +42,7 @@ "ember-cli-eslint": "^4.2.3", "ember-cli-ifa": ">= 0.4.1", "ember-cli-inject-live-reload": "^1.8.2", + "ember-cli-moment-shim": "^3.7.1", "ember-cli-sri": "^2.1.1", "ember-cli-template-lint": "^1.0.0-beta.1", "ember-cli-uglify": "^2.1.0", @@ -48,6 +51,7 @@ "ember-faker": "^1.1.1", "ember-load-initializers": "^1.1.0", "ember-maybe-import-regenerator": "^0.1.6", + "ember-moment": "^8.0.0", "ember-qunit": "^3.4.1", "ember-resolver": "^5.0.1", "ember-source": "~3.8.0", diff --git a/tests/dummy/app/components/contextual-actions.js b/tests/dummy/app/components/contextual-actions.js new file mode 100644 index 000000000..8429b51a1 --- /dev/null +++ b/tests/dummy/app/components/contextual-actions.js @@ -0,0 +1,3 @@ +import Component from '@ember/component'; + +export default Component.extend({}) diff --git a/tests/dummy/app/controllers/application.js b/tests/dummy/app/controllers/application.js new file mode 100644 index 000000000..638c058e9 --- /dev/null +++ b/tests/dummy/app/controllers/application.js @@ -0,0 +1,46 @@ +import Controller from '@ember/controller'; +import { inject as service } from '@ember/service'; + +export default Controller.extend({ + plansFetcher: service(), + upfTableState: service(), + + collection: [], + columns: [], + + tableOptions: { + features: { + search: true, + selection: true + } + }, + + init() { + this._super(); + this._fetchPlans(); + }, + + _fetchPlans() { + this.set('refreshing', true); + + this.plansFetcher.fetch().then((data) => { + this.set('collection', data.items); + this.set('tableManager', this.upfTableState.createTable()); + this.tableManager.updateColumns(data.meta.columns); + }).finally(() => { + this.set('refreshing', false); + }); + }, + + actions: { + columnsChanged(layout) { + this.plansFetcher.fetch(layout).then((data) => { + this.set('collection', data.items); + }); + }, + + performSearch(s) { + alert(`Search: ${s}`); + } + } +}); diff --git a/tests/dummy/app/routes/application.js b/tests/dummy/app/routes/application.js new file mode 100644 index 000000000..6c74252aa --- /dev/null +++ b/tests/dummy/app/routes/application.js @@ -0,0 +1,4 @@ +import Route from '@ember/routing/route'; + +export default Route.extend({ +}); diff --git a/tests/dummy/app/services/plans-fetcher.js b/tests/dummy/app/services/plans-fetcher.js new file mode 100644 index 000000000..c36910154 --- /dev/null +++ b/tests/dummy/app/services/plans-fetcher.js @@ -0,0 +1,138 @@ +import { A } from '@ember/array'; +import EmberObject from '@ember/object'; +import Service from '@ember/service'; +import { isEmpty } from '@ember/utils'; + +const MOCK_DATA = A([ + EmberObject.create({ + name: 'Bronze', + price: 99, + currency: 'EUR', + usersCount: 1, + bulkEmailsCount: 0, + selected: false, + data1: 'A', + data2: 1620563995 + }), + EmberObject.create({ + name: 'Silver', + price: 195, + currency: 'EUR', + usersCount: 1, + bulkEmailsCount: 100, + selected: false, + data1: 'B', + data2: 1554800754 + }), + EmberObject.create({ + name: 'Gold', + price: 495, + currency: 'EUR', + usersCount: 1, + bulkEmailsCount: 300, + selected: false, + data1: 'C', + data2: 1365498354 + }), + EmberObject.create({ + name: 'Enterprise', + price: 'Talk to us', + usersCount: 'Custom', + bulkEmailsCount: 'Custom', + selected: false, + data1: 'D', + data2: 1383987954 + }) +]); + +const DEFAULT_COLUMNS = [ + { + title: 'Plan Name', + property: 'name', + type: 'text' + }, + { + title: 'Plan Price', + property: 'price', + type: 'money', + currency_key: 'currency' + }, + { + title: 'Users Count', + property: 'usersCount', + type: 'numeric' + }, + { + title: 'Bulk Emails', + property: 'bulkEmailsCount', + type: 'numeric' + }, + { + title: 'Data 1', + property: 'data1', + type: 'text' + }, + { + title: 'Data 2', + property: 'data2', + type: 'date' + }, + { + title: 'Data 3', + property: 'data3', + type: 'numeric' + }, + { + title: 'Data 4', + property: 'data4', + type: 'numeric' + }, + { + title: 'Data 5', + property: 'data5', + type: 'numeric' + }, + { + title: 'Data 6', + property: 'data6', + type: 'numeric' + }, +]; + +export default Service.extend({ + fetch(columnsLayout) { + return new Promise((resolve, reject) => { + let columns = DEFAULT_COLUMNS; + let data = A( + MOCK_DATA.concat(MOCK_DATA).concat(MOCK_DATA).concat(MOCK_DATA) + ); + + if (columnsLayout) { + columns = columnsLayout; + window.sessionStorage.setItem('columns', JSON.stringify(columns)); + } else if (!columnsLayout && window.sessionStorage.getItem('columns')) { + columns = JSON.parse(window.sessionStorage.getItem('columns')); + } + + let sortedColumn = columns.find((x) => !isEmpty(x.sortBy)); + + if (sortedColumn) { + let [sortType, sortDirection] = sortedColumn.sortBy.split(':'); + data = data.sortBy(sortedColumn.property); + + if (sortDirection === 'desc') { + data.reverse(); + } + } + + setTimeout(() => { + resolve({ + items: data, + meta: { + columns: columns + } + }); + }, 1500) + }); + } +}); diff --git a/tests/dummy/app/styles/app.less b/tests/dummy/app/styles/app.less index 8b1378917..36282f18b 100644 --- a/tests/dummy/app/styles/app.less +++ b/tests/dummy/app/styles/app.less @@ -1 +1 @@ - +@import 'oss-components'; diff --git a/tests/dummy/app/templates/application.hbs b/tests/dummy/app/templates/application.hbs index f0097571c..50e60dd8e 100644 --- a/tests/dummy/app/templates/application.hbs +++ b/tests/dummy/app/templates/application.hbs @@ -1 +1,7 @@ -this is upfluence oss component +
+ {{upf-table + manager=tableManager collection=collection options=tableOptions + onColumnsChange=(action "columnsChanged") + onSearchQueryChange=(action "performSearch") + contextualActions="contextual-actions"}} +
diff --git a/tests/dummy/app/templates/components/contextual-actions.hbs b/tests/dummy/app/templates/components/contextual-actions.hbs new file mode 100644 index 000000000..db44ea3c0 --- /dev/null +++ b/tests/dummy/app/templates/components/contextual-actions.hbs @@ -0,0 +1,3 @@ + diff --git a/tests/dummy/config/optional-features.json b/tests/dummy/config/optional-features.json index b1902623a..21f1dc719 100644 --- a/tests/dummy/config/optional-features.json +++ b/tests/dummy/config/optional-features.json @@ -1,3 +1,3 @@ { - "jquery-integration": false + "jquery-integration": true }