stimulus_rails_datatables 0.5.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bbb9263b496e3deed5f28f35c19530d54cdc311a032da2eeeefb60c0c9dfa07d
4
- data.tar.gz: 613bcfbd3ce0dd842d0bf1a3067b91ddb24cb5d773340726ae2851cd17040a15
3
+ metadata.gz: 6c52d575fdf1e4bf88eb1cdf3b581ed80ec4c6956b1d5f0c3a60b90f99d2e3f2
4
+ data.tar.gz: 9bf301decccb7e7490972346183d73fa25d659088383521ff8b825b6bc83ac5e
5
5
  SHA512:
6
- metadata.gz: 5619dd1eff78003687b588989cad96c353f52309eb184830764a5a42504033a61b5832bfc31fce8ef3fc856af7c4df30ebebc6554c3cfc1bb041b8a8b1c73ca7
7
- data.tar.gz: d24f5d85abafab0b8dd73fd9ca3be0832efad5bdfc60caa98b2f0aa44990390792211432a46124ec6169fc54474787477dcbcb3e3fe282190c9b8d29fe1b6244
6
+ metadata.gz: 61e84f397c85ad598f98b47f7451e454fa6fae7e8e707fee9b9281829feecab1e151be8a387d4ea19cad63ac3d71f3e3c301f756dc2f20f07f9b7948a0f830ea
7
+ data.tar.gz: 780dddf9239013c1fb8444a0d2ffeaab370829cfbffcd0827d23f5545f8e082d3740403b8e4854e52ed0185cf8eccd683decd3eb9d6353e2b8e327ac7113c73d
data/CHANGELOG.md CHANGED
@@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.7.0] - 2026-09-25
9
+
10
+ ### Added
11
+ - `resetPaging` global config option (`window.datatablesConfig` or `setDatatablesConfig`) to go back to the first page when filters change; defaults to `false`
12
+ - `reset_paging` option for the `datatable_for` helper, which overrides the global `resetPaging` value per datatable
13
+
14
+ ## [0.6.0] - 2026-08-03
15
+
16
+ ### Added
17
+ - Tom-Select support for remote filter selects via the `tomselect` option
18
+ - `multiple` option for remote filter selects, sending the selected values as array params (`filters[key][]=`)
19
+ - `set_values` remote option to preselect multiple values
20
+ - Summary label showing the number of selected items on Tom-Select multi-selects
21
+
22
+ ### Changed
23
+ - The `location` filter's barangay select is now a Tom-Select multi-select
24
+ - The filter controller imports `tom-select`, so the host app must pin it in its importmap
25
+
8
26
  ## [0.5.1] - 2026-07-28
9
27
 
10
28
  ### Changed
data/README.md CHANGED
@@ -335,10 +335,25 @@ window.datatablesConfig = {
335
335
  },
336
336
 
337
337
  // Length menu options
338
- lengthMenu: [[10, 25, 50, 100], [10, 25, 50, 100]]
338
+ lengthMenu: [[10, 25, 50, 100], [10, 25, 50, 100]],
339
+
340
+ // Go back to the first page when filters change (default: false)
341
+ resetPaging: true
339
342
  }
340
343
  ```
341
344
 
345
+ ### Resetting Pagination on Filter Change
346
+
347
+ By default, changing a filter reloads the datatable and keeps the current page. Set `resetPaging: true` in `window.datatablesConfig` to jump back to the first page for every datatable, or override it per table with `reset_paging:`:
348
+
349
+ ```ruby
350
+ <%= datatable_for 'users-table', source: users_path, reset_paging: false do |dt| %>
351
+ <% dt.column :name %>
352
+ <% end %>
353
+ ```
354
+
355
+ Leave `reset_paging:` out to use the global value. Restoring saved filters on page load never resets the page, so the page kept by `state_save` is not lost.
356
+
342
357
  ### Available Stimulus Controllers
343
358
 
344
359
  The gem automatically registers the following Stimulus controllers:
@@ -30,7 +30,10 @@ let datatablesConfig = {
30
30
  },
31
31
 
32
32
  // Length menu options
33
- lengthMenu: [[10, 25, 50, 100], [10, 25, 50, 100]]
33
+ lengthMenu: [[10, 25, 50, 100], [10, 25, 50, 100]],
34
+
35
+ // Go back to the first page when filters change (override per table with datatable_for reset_paging:)
36
+ resetPaging: false
34
37
  }
35
38
 
36
39
  // Function to let the app override defaults
@@ -17,7 +17,8 @@ export default class extends Controller {
17
17
  pagingType: { type: String, default: 'simple_numbers' },
18
18
  searching: { type: Boolean, default: true },
19
19
  lengthChange: { type: Boolean, default: true },
20
- responsive: { type: Boolean, default: true }
20
+ responsive: { type: Boolean, default: true },
21
+ resetPaging: Boolean
21
22
  }
22
23
 
23
24
  connect() {
@@ -89,7 +90,8 @@ export default class extends Controller {
89
90
  const config = {
90
91
  language: { ...defaultConfig.language, ...(userConfig.language || {}) },
91
92
  layout: { ...defaultConfig.layout, ...(userConfig.layout || {}) },
92
- lengthMenu: userConfig.lengthMenu || defaultConfig.lengthMenu
93
+ lengthMenu: userConfig.lengthMenu || defaultConfig.lengthMenu,
94
+ resetPaging: userConfig.resetPaging ?? defaultConfig.resetPaging
93
95
  }
94
96
 
95
97
  const responsiveValue = this.responsiveValue
@@ -107,6 +109,9 @@ export default class extends Controller {
107
109
  responsive: this.responsiveValue,
108
110
  language: config.language,
109
111
  layout: config.layout,
112
+ // Not a DataTables option; the filter controller reads it back via datatable.init()
113
+ // Per-table value (datatable_for reset_paging:) wins over the global config
114
+ resetPaging: this.hasResetPagingValue ? this.resetPagingValue : !!config.resetPaging,
110
115
  initComplete: function() {
111
116
  if (responsiveValue === false) {
112
117
  // Add overflow-x only to the table wrapper (not the whole layout) this is alternative of scrollX
@@ -2,6 +2,7 @@
2
2
  // Disabling eslint since this is a library file and may not conform to all rules
3
3
 
4
4
  import { Controller } from '@hotwired/stimulus'
5
+ import TomSelect from 'tom-select'
5
6
 
6
7
  export default class extends Controller {
7
8
  static targets = ['select', 'customFields']
@@ -13,6 +14,45 @@ export default class extends Controller {
13
14
  }
14
15
 
15
16
  connect() {
17
+ this.tomSelects = new Map()
18
+ this.remoteSelects = Array.from(
19
+ this.element.querySelectorAll('select[data-filter-remote-url-value]')
20
+ )
21
+
22
+ this.remoteSelects.forEach(select => {
23
+ if (select.dataset.filterTomselect !== 'true') return
24
+
25
+ const isMultiple = select.multiple
26
+ const ts = new TomSelect(select, {
27
+ maxItems: isMultiple ? null : 1,
28
+ placeholder: select.dataset.filterPlaceholder || 'Select',
29
+ create: false,
30
+ hideSelected: isMultiple ? false : undefined,
31
+ render: isMultiple ? { item: () => '<div class="ts-hidden-item"></div>' } : {}
32
+ })
33
+
34
+ if (isMultiple) {
35
+ const originalOnOptionSelect = ts.onOptionSelect.bind(ts)
36
+ ts.onOptionSelect = (evt, option) => {
37
+ const value = option.dataset.value
38
+ if (ts.items.includes(value)) {
39
+ evt.preventDefault()
40
+ ts.removeItem(value)
41
+ ts.refreshOptions(false)
42
+ } else {
43
+ originalOnOptionSelect(evt, option)
44
+ }
45
+ }
46
+ this.updateSummaryLabel(select, ts)
47
+ ts.on('item_add', () => this.updateSummaryLabel(select, ts))
48
+ ts.on('item_remove', () => this.updateSummaryLabel(select, ts))
49
+ }
50
+
51
+ this.tomSelects.set(select, ts)
52
+
53
+ if (select.dataset.filterDependsOn) ts.disable()
54
+ })
55
+
16
56
  // begin restore; it is async but will dispatch filters:ready when done
17
57
  this.restoreState()
18
58
 
@@ -29,16 +69,50 @@ export default class extends Controller {
29
69
  // trigger datatable reload
30
70
  this.reloadAppDatatable()
31
71
  })
72
+ }
32
73
 
33
- // collect selects for later use
34
- this.selects = Array.from(this.element.querySelectorAll('select[data-filter-remote-url-value]'))
74
+ disconnect() {
75
+ this.tomSelects?.forEach(ts => ts.destroy())
76
+ this.tomSelects?.clear()
77
+ }
35
78
 
36
- // if there are top-level remote selects we want them available for manual changes
37
- this.selects.forEach(select => {
38
- if (select.dataset.filterDependsOn) {
39
- select.disabled = true
40
- }
41
- })
79
+ // --- helpers: single source of truth for reading/writing a field, Tom-Select-aware ---
80
+ getFieldValue(el) {
81
+ const ts = this.tomSelects.get(el)
82
+ return ts ? ts.getValue() : el.value
83
+ }
84
+
85
+ isValueEmpty(value) {
86
+ return Array.isArray(value) ? value.length === 0 : !value
87
+ }
88
+
89
+ setFieldValue(el, value, silent = true) {
90
+ const ts = this.tomSelects.get(el)
91
+ if (ts) ts.setValue(value, silent)
92
+ else el.value = value
93
+ }
94
+
95
+ updateSummaryLabel(select, ts) {
96
+ const count = ts.items.length
97
+ const placeholder = select.dataset.filterPlaceholder || 'Select'
98
+
99
+ let label = ts.control.querySelector('.ts-summary-label')
100
+ if (!label) {
101
+ label = document.createElement('div')
102
+ label.className = 'ts-summary-label'
103
+ ts.control.prepend(label)
104
+ }
105
+
106
+ const hasSelection = count > 0
107
+ label.textContent = hasSelection ? `${count} Barangay${count > 1 ? 's' : ''} selected` : ''
108
+ label.style.display = hasSelection ? 'block' : 'none'
109
+
110
+ if (hasSelection) {
111
+ ts.control_input.style.setProperty('display', 'none', 'important')
112
+ } else {
113
+ ts.control_input.style.removeProperty('display')
114
+ ts.control_input.setAttribute('placeholder', placeholder)
115
+ }
42
116
  }
43
117
 
44
118
  toQuery(obj, prefix) {
@@ -47,7 +121,10 @@ export default class extends Controller {
47
121
  for (const [key, value] of Object.entries(obj)) {
48
122
  const fullKey = prefix ? `${prefix}[${key}]` : key
49
123
 
50
- if (value !== null && typeof value === 'object') {
124
+ if (Array.isArray(value)) {
125
+ value.forEach(v => pairs.push(`${encodeURIComponent(fullKey)}[]=${encodeURIComponent(v)}`))
126
+ }
127
+ else if (value !== null && typeof value === 'object') {
51
128
  pairs.push(this.toQuery(value, fullKey))
52
129
  }
53
130
  else if (value !== '' && value !== null && value !== undefined) {
@@ -59,7 +136,8 @@ export default class extends Controller {
59
136
  }
60
137
 
61
138
  // reloadAppDatatable reloads the datatable with current params
62
- async reloadAppDatatable() {
139
+ // pass resetPaging true/false to force it, otherwise the datatable's resetPaging setting is used
140
+ async reloadAppDatatable(resetPaging) {
63
141
  var id = this.element.dataset.filterDatatableId
64
142
 
65
143
  if (!id) {
@@ -70,17 +148,20 @@ export default class extends Controller {
70
148
  const datatableUrl = datatable.ajax.url().split('?')[0]
71
149
  const params = this.toQuery(this.currentParams())
72
150
 
73
- datatable.ajax.url(`${datatableUrl}?${params}`).load(null, false)
74
- }
151
+ if (typeof resetPaging !== 'boolean') resetPaging = datatable.init()?.resetPaging === true
75
152
 
153
+ datatable.ajax.url(`${datatableUrl}?${params}`).load(null, resetPaging)
154
+ }
76
155
  }
77
156
 
78
157
  // async populate returns when options appended
79
158
  async populate(select) {
159
+ const ts = this.tomSelects.get(select)
80
160
  let url = select.dataset.filterRemoteUrlValue
81
161
  const labelKey = select.dataset.filterLabelKey
82
162
  const valueKey = select.dataset.filterValueKey
83
- const set_value = select.dataset.filterSetValue || ''
163
+ const setValues = (select.dataset.filterSetValues || select.dataset.filterSetValue || '')
164
+ .split(',').map(v => v.trim()).filter(Boolean)
84
165
 
85
166
  url = decodeURIComponent(url).replace(/{(\w+)}/g, (_, key) => {
86
167
  const input = this.element.querySelector(`[data-filter-field-name='${key}']`)
@@ -94,38 +175,54 @@ export default class extends Controller {
94
175
  if (!response.ok) throw new Error(`Failed to fetch ${url}`)
95
176
  const data = await response.json()
96
177
 
97
- this.resetSelect(select, false)
98
- data.forEach(item => {
99
- const option = document.createElement('option')
100
- option.value = item[valueKey]
101
- option.textContent = item[labelKey]
102
-
103
- // If set_value matches, mark as selected
104
- if (set_value && item[valueKey] == set_value) {
105
- option.selected = true
178
+ if (ts) {
179
+ ts.clear(true) // clear current selection (silent — no change event)
180
+ ts.clearOptions() // wipe stale option list
181
+ if (!select.multiple) {
182
+ const placeholder = select.dataset.filterPlaceholder || 'All'
183
+ ts.addOption({ value: '', text: placeholder })
106
184
  }
107
-
108
- select.appendChild(option)
109
- })
110
-
111
- select.disabled = false
185
+ ts.addOptions(data.map(item => ({ value: String(item[valueKey]), text: item[labelKey] })))
186
+ if (setValues.length) this.setFieldValue(select, select.multiple ? setValues : setValues[0])
187
+ ts.enable()
188
+ if (select.multiple) this.updateSummaryLabel(select, ts)
189
+ } else {
190
+ select.innerHTML = ''
191
+
192
+ const placeholder = select.dataset.filterPlaceholder || 'All'
193
+ const blankOption = document.createElement('option')
194
+ blankOption.value = ''
195
+ blankOption.textContent = placeholder
196
+ select.appendChild(blankOption)
197
+
198
+ data.forEach(item => {
199
+ const option = document.createElement('option')
200
+ option.value = item[valueKey]
201
+ option.textContent = item[labelKey]
202
+ if (setValues.includes(String(item[valueKey]))) option.selected = true
203
+ select.appendChild(option)
204
+ })
205
+ select.disabled = false
206
+ }
112
207
  } catch (e) {
113
208
  console.error('[Filter] fetch error:', e)
114
- select.disabled = false
209
+ if (ts) ts.enable()
210
+ else select.disabled = false
115
211
  }
116
212
  }
117
213
 
118
214
  resetSelect(select, disabled = true) {
119
- const placeholder = select.dataset.filterPlaceholder || 'Select'
120
- const option = document.createElement('option')
121
-
122
- option.value = ''
123
- option.textContent = placeholder
124
-
125
- select.innerHTML = ''
126
- select.appendChild(option)
127
- select.value = ''
128
- select.disabled = disabled
215
+ const ts = this.tomSelects.get(select)
216
+ if (ts) {
217
+ ts.clear(true)
218
+ ts.clearOptions()
219
+ disabled ? ts.disable() : ts.enable()
220
+ } else {
221
+ const placeholder = select.dataset.filterPlaceholder || 'Select'
222
+ select.innerHTML = `<option value="">${placeholder}</option>`
223
+ select.value = ''
224
+ select.disabled = disabled
225
+ }
129
226
  }
130
227
 
131
228
  currentParams() {
@@ -133,20 +230,15 @@ export default class extends Controller {
133
230
  const params = {}
134
231
 
135
232
  this.element.querySelectorAll('[data-filter-field-name]').forEach(el => {
136
- if (el.value) params[el.dataset.filterFieldName] = el.value
233
+ const value = this.getFieldValue(el)
234
+ if (!this.isValueEmpty(value)) params[el.dataset.filterFieldName] = value
137
235
  })
138
236
 
139
- const clean = Object.entries(params).reduce((acc, [k, v]) => {
140
- if (v !== '' && v !== null && v !== undefined) acc[k] = v
141
- return acc
142
- }, {})
143
-
144
- return { [rootKey]: clean }
237
+ return { [rootKey]: params }
145
238
  }
146
239
 
147
240
  saveState() {
148
241
  const state = this.currentParams()
149
- // deterministic storage key that datatable can read
150
242
  try {
151
243
  localStorage.setItem(`filterState:${this.filterDtId}`, JSON.stringify(state))
152
244
  } catch (e) {
@@ -176,21 +268,13 @@ export default class extends Controller {
176
268
  if (el && !el.dataset.filterRemoteUrlValue) el.value = value
177
269
  })
178
270
 
179
- // collect remote selects (as an array)
180
- this.selects = Array.from(this.element.querySelectorAll('select[data-filter-remote-url-value]'))
181
-
182
- // find root remote selects (no depends_on)
183
- const roots = this.selects.filter(s => !s.dataset.filterDependsOn)
271
+ const roots = this.remoteSelects.filter(s => !s.dataset.filterDependsOn)
184
272
 
185
- // populate each root -> then recursively populate dependents and restore values
186
273
  await Promise.all(roots.map(async (root) => {
187
274
  await this.populate(root)
188
- // after root options exist, restore saved root value (if any)
189
275
  const sv = savedParams[root.dataset.filterFieldName]
190
- const set_value = root.dataset.filterSetValue || ''
191
-
192
- if (sv && !set_value) root.value = sv
193
- // cascade down children
276
+ const hasSetValue = root.dataset.filterSetValue || root.dataset.filterSetValues
277
+ if (sv && !hasSetValue) this.setFieldValue(root, sv)
194
278
  await this.populateDependents(root, savedParams)
195
279
  }))
196
280
 
@@ -201,52 +285,50 @@ export default class extends Controller {
201
285
  const sd = this.element.querySelector('[data-filter-field-name="start_date"]')
202
286
  if (sd) sd.value = savedParams['start_date']
203
287
  }
204
-
205
288
  if (savedParams['end_date']) {
206
289
  const ed = this.element.querySelector('[data-filter-field-name="end_date"]')
207
290
  if (ed) ed.value = savedParams['end_date']
208
291
  }
209
292
  }
210
293
 
211
- // emit event (use document, bubbles already) so any listener can catch
294
+ // emit event so any listener can catch
212
295
  const payload = this.currentParams()
213
296
  this.element.dispatchEvent(new CustomEvent('filters:ready', { detail: { params: payload }, bubbles: true }))
214
297
 
215
- // also update deterministic storage (in case other code reads it)
216
298
  try {
217
299
  localStorage.setItem(`filterState:${this.filterDtId}`, JSON.stringify(payload))
218
300
  } catch (e) {}
301
+
302
+ // restoring saved filters is not a filter change, so keep the page restored by stateSave
303
+ if (Object.keys(payload[rootKey] || {}).length > 0) {
304
+ this.reloadAppDatatable(false)
305
+ }
219
306
  }
220
307
 
221
308
  // recursively populate children of parent, restore each child's saved value, then recurse
222
309
  async populateDependents(parent, savedParams = {}) {
223
- this.selects = this.selects || Array.from(this.element.querySelectorAll('select[data-filter-remote-url-value]'))
224
310
  const parentKey = parent.dataset.filterFieldName
225
- const children = this.selects.filter(s => s.dataset.filterDependsOn === parentKey)
311
+ const children = this.remoteSelects.filter(s => s.dataset.filterDependsOn === parentKey)
226
312
 
227
313
  for (const child of children) {
228
314
  this.resetSelect(child)
229
315
 
230
- if (!parent.value) {
316
+ const parentValue = this.getFieldValue(parent)
317
+ if (this.isValueEmpty(parentValue)) {
231
318
  await this.populateDependents(child, savedParams)
232
319
  continue
233
320
  }
234
321
 
235
- // populate child using parent's current value substituted by populate()
236
322
  await this.populate(child)
237
- // restore child's saved value if exists
323
+
238
324
  const childSaved = savedParams[child.dataset.filterFieldName]
239
- const set_value = child.dataset.filterSetValue || ''
325
+ const hasSetValue = child.dataset.filterSetValue || child.dataset.filterSetValues
326
+ if (childSaved && !hasSetValue) this.setFieldValue(child, childSaved)
240
327
 
241
- if (childSaved && !set_value) {
242
- child.value = childSaved
243
- }
244
- // recurse deeper
245
328
  await this.populateDependents(child, savedParams)
246
329
  }
247
330
  }
248
331
 
249
- // duration handler (unchanged)
250
332
  durationChanged(event) {
251
333
  const select = event.target
252
334
 
@@ -267,4 +349,4 @@ export default class extends Controller {
267
349
  document.querySelectorAll('.customDurationField').forEach(el => el.remove())
268
350
  }
269
351
  }
270
- }
352
+ }
@@ -8,6 +8,7 @@ module StimulusRailsDatatables
8
8
  length_change = options.fetch(:length_change, true)
9
9
  state_save = options.fetch(:state_save, true)
10
10
  responsive = options.fetch(:responsive, true)
11
+ reset_paging = options[:reset_paging] # nil falls back to the global resetPaging config
11
12
  columns = []
12
13
 
13
14
  capture(DatatableBuilder.new(self, columns), &block)
@@ -22,7 +23,8 @@ module StimulusRailsDatatables
22
23
  datatable_searching_value: searching,
23
24
  datatable_length_change_value: length_change,
24
25
  datatable_state_save_value: state_save,
25
- datatable_responsive_value: responsive
26
+ datatable_responsive_value: responsive,
27
+ datatable_reset_paging_value: reset_paging
26
28
  }
27
29
 
28
30
  content_tag(:div, data: data) do
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+
3
4
  module StimulusRailsDatatables
4
5
  module FilterHelper
5
6
  def filter_for(datatable_id, root_key: 'filters', &)
@@ -39,6 +40,7 @@ module StimulusRailsDatatables
39
40
  else
40
41
  remote = options[:remote] || {}
41
42
  depends_on = options[:depends_on]
43
+ multiple = options[:multiple] || false
42
44
 
43
45
  attrs = {
44
46
  filter_field_name: name,
@@ -47,16 +49,22 @@ module StimulusRailsDatatables
47
49
  filter_value_key: remote[:value],
48
50
  filter_depends_on: depends_on&.to_s,
49
51
  filter_placeholder: remote[:placeholder] || 'Select an option',
50
- filter_set_value: remote[:set_value]
52
+ filter_set_value: remote[:set_value],
53
+ filter_set_values: remote[:set_values],
54
+ filter_tomselect: options[:tomselect]
55
+
51
56
  }
52
57
 
53
58
  select_options = {
54
59
  data: attrs,
55
- class: html_class
60
+ class: html_class,
61
+ multiple: multiple
56
62
  }
57
63
 
58
64
  # Only disable if it depends on another field
59
65
  select_options[:disabled] = true if depends_on.present?
66
+ field_name = "#{field_name}[]" if multiple
67
+
60
68
 
61
69
  @view.select_tag(field_name, @view.options_for_select([]), **select_options)
62
70
  end
@@ -94,6 +102,8 @@ module StimulusRailsDatatables
94
102
  placeholder: 'All Barangays'
95
103
  },
96
104
  depends_on: :city_id,
105
+ multiple: true,
106
+ tomselect: true,
97
107
  class: html_class
98
108
  )
99
109
 
@@ -41,5 +41,8 @@ window.datatablesConfig = {
41
41
  },
42
42
 
43
43
  // Length menu options
44
- lengthMenu: [[10, 25, 50, 100], [10, 25, 50, 100]]
44
+ lengthMenu: [[10, 25, 50, 100], [10, 25, 50, 100]],
45
+
46
+ // Go back to the first page when filters change (override per table with datatable_for reset_paging:)
47
+ resetPaging: false
45
48
  }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StimulusRailsDatatables
4
- VERSION = '0.5.1'
4
+ VERSION = '0.7.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stimulus_rails_datatables
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Den Meralpis