adminterface 0.2.0 → 0.2.1

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: 4069cfffd19de3ada098678538e0e7ca4f9a840c8477de55fe3612cd6f541572
4
- data.tar.gz: 7454d611f8fd759b502577cd30f1a00ee7f7c6587b2361155998ef1c2e29aff7
3
+ metadata.gz: 7aa4367f85794395d6b92b141c12488c2158b99f0585f4a76f201bb080fb167c
4
+ data.tar.gz: 88a7bb2ecafe63f80145908a0cd4d1289011a71352ee9960f2870a80c91fc08f
5
5
  SHA512:
6
- metadata.gz: d04bd12439c118daedfb64958cdcf0a3a24a077331147bc98376ed0a8df5de9eada6dadb7de0eb066690a2d979c0277a357b42cead3ccf2b4a7e9a4c81bb5972
7
- data.tar.gz: dc004f3881b891829f647c813ec8025459a76be580276b9bcb54ff5eafcd91a35324169b099b0ffb1eb588ea4f34820d41621027bcdff5f3e7509696c81d02f1
6
+ metadata.gz: 92dfa1e53f319043ee355df3d0d70e9888f070727c5828851ee1e10d6e2aa590af464dc7ad75ef871d8e375676dfc68ed4c97decc0fc3f0492868d642f2f2667
7
+ data.tar.gz: 650a23ef8b8f323f6f792fa591aec71d60701c7b5c32d3d2d311cb51ed73a42f9ef8bf0d988498d60aba2cfe0b8a87e82bd8aa7c8e5162b209ac6f72e5d60004
data/README.md CHANGED
@@ -1,7 +1,11 @@
1
1
  # Adminterface <!-- omit in toc -->
2
2
  [![CI](https://github.com/CMDBrew/adminterface/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/CMDBrew/adminterface/actions/workflows/ci.yml)
3
+ [![Maintainability](https://api.codeclimate.com/v1/badges/4dbedfdf21a2d675d2ca/maintainability)](https://codeclimate.com/github/CMDBrew/adminterface/maintainability)
4
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/4dbedfdf21a2d675d2ca/test_coverage)](https://codeclimate.com/github/CMDBrew/adminterface/test_coverage)
5
+ [![gem](https://img.shields.io/gem/v/adminterface?color=%23E9573F&label=rubygems.org)](https://rubygems.org/gems/adminterface)
6
+ [![npm](https://img.shields.io/npm/v/@cmdbrew/adminterface?color=%230273B4&label=npmjs.com)](https://www.npmjs.com/package/@cmdbrew/adminterface)
3
7
 
4
- An add-on that brings Bootstrap 5 and other goodies into ActiveAdmin.
8
+ A gem that brings Bootstrap 5, advanced customizability, and other goodies into ActiveAdmin
5
9
 
6
10
  [![](preview.jpg)](https://adminterface.io)
7
11
 
@@ -26,6 +30,10 @@ An add-on that brings Bootstrap 5 and other goodies into ActiveAdmin.
26
30
  $ rails g active_admin:install --use_webpacker
27
31
  ```
28
32
  4. Install Adminterface
33
+
34
+ > This command will ask for permissions to overwrite files generated by ActiveAdmin. Make sure to review the files carefully and accept them.
35
+ > We recommend you commit your files before running this command to ensure you are aware of the changes made.
36
+
29
37
  ```bash
30
38
  $ rails g adminterface:install
31
39
  ```
@@ -3,7 +3,7 @@ import HeaderToggler from '../lib/header_toggler'
3
3
  document.addEventListener('DOMContentLoaded', () => {
4
4
  const togglerTriggerList = [].slice.call(document.querySelectorAll('[data-aa-header-toggler]'))
5
5
  togglerTriggerList.map((el) => {
6
- const options = JSON.parse(el.dataset.aaHeaderToggler || {})
6
+ const options = JSON.parse(el.dataset.aaHeaderToggler || '{}')
7
7
 
8
8
  return new HeaderToggler(el, options)
9
9
  })
@@ -5,7 +5,7 @@ const initFlatpickr = (element) => {
5
5
  const flatpickrTriggerList = [].slice.call(element.querySelectorAll('[data-aa-flatpickr]'))
6
6
 
7
7
  flatpickrTriggerList.map((el) => {
8
- const options = JSON.parse(el.dataset.aaFlatpickr || {})
8
+ const options = JSON.parse(el.dataset.aaFlatpickr || '{}')
9
9
  const instance = flatpickr(el, options)
10
10
 
11
11
  adminterface.addObserver(el, instance, 'flatpickr')
@@ -4,7 +4,7 @@ const initInputCounter = function (element) {
4
4
  const inputCounterTriggerList = [].slice.call(element.querySelectorAll('[data-aa-input-counter]'))
5
5
 
6
6
  inputCounterTriggerList.map((el) => {
7
- const options = JSON.parse(el.dataset.aaInputCounter || {})
7
+ const options = JSON.parse(el.dataset.aaInputCounter || '{}')
8
8
 
9
9
  return new InputCounter(el, options)
10
10
  })
@@ -4,7 +4,7 @@ import initBootstrap from '../bootstrap'
4
4
  const initPasswordVisibility = function (element) {
5
5
  const visibilityTriggerList = [].slice.call(element.querySelectorAll('[data-aa-password-visibility]'))
6
6
  visibilityTriggerList.map((el) => {
7
- const options = JSON.parse(el.dataset.aaPasswordVisibility || {})
7
+ const options = JSON.parse(el.dataset.aaPasswordVisibility || '{}')
8
8
  const instance = new PasswordVisibility(el, options)
9
9
 
10
10
  initBootstrap(instance.eventTarget)
@@ -5,7 +5,7 @@ const initTomSelect = function (element) {
5
5
  const tomSelectTriggerList = [].slice.call(element.querySelectorAll('[data-aa-tom-select]'))
6
6
 
7
7
  tomSelectTriggerList.map((el) => {
8
- const options = JSON.parse(el.dataset.aaTomSelect || {})
8
+ const options = JSON.parse(el.dataset.aaTomSelect || '{}')
9
9
  const instance = new TomSelect(el, options)
10
10
 
11
11
  adminterface.addObserver(el, instance, instance.constructor.name)
@@ -0,0 +1,5 @@
1
+ <div id="find-me">
2
+ <input type="checkbox" class="toggle_all" checked>
3
+ <input type="checkbox" class="toggle_one" checked>
4
+ <input type="checkbox" class="toggle_two" checked>
5
+ </div>
@@ -0,0 +1,110 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import CheckboxTogglerClass from '../checkbox_toggler'
4
+
5
+ describe('CheckboxToggler', () => {
6
+ const html = fs.readFileSync(path.resolve(__dirname, './checkbox_toggler.html'))
7
+ const container = '#find-me'
8
+ const options = 'options'
9
+
10
+ beforeEach(() => {
11
+ document.documentElement.innerHTML = html.toString()
12
+ })
13
+
14
+ afterEach(() => {
15
+ jest.restoreAllMocks()
16
+ })
17
+
18
+ test('constructor initiates correctly', () => {
19
+ const spyOnInit = jest.spyOn(CheckboxTogglerClass.prototype, '_init')
20
+ const spyOnBind = jest.spyOn(CheckboxTogglerClass.prototype, '_bind')
21
+
22
+ const CheckboxToggler = new CheckboxTogglerClass(container, options)
23
+
24
+ expect(CheckboxToggler.options).toStrictEqual(options)
25
+ expect(CheckboxToggler.container).toStrictEqual(container)
26
+
27
+ expect(spyOnInit).toHaveBeenCalledTimes(1)
28
+ expect(spyOnBind).toHaveBeenCalledTimes(1)
29
+ })
30
+
31
+ test('init throws error if container is null', () => {
32
+ expect(() => { new CheckboxTogglerClass(null, options) }).toThrow( // eslint-disable-line no-new
33
+ 'Container element not found'
34
+ )
35
+ })
36
+
37
+ test('init throws error if toggle checkbox missing', () => {
38
+ const element = document.createElement('div')
39
+ element.id = 'find-me'
40
+ document.documentElement.innerHTML = element.outerHTML
41
+
42
+ expect(() => { new CheckboxTogglerClass(container, options) }).toThrow( // eslint-disable-line no-new
43
+ '"toggle all" checkbox not found'
44
+ )
45
+ })
46
+
47
+ test('init sets $container, $toggleAllCheckbox, $checkboxes', () => {
48
+ const $container = document.querySelector(container)
49
+ const $toggleAllCheckbox = $container.querySelector('.toggle_all')
50
+ const $checkboxes = $container.querySelectorAll('input[type="checkbox"]:not(.toggle_all)')
51
+ const CheckboxToggler = new CheckboxTogglerClass(container, options)
52
+
53
+ expect(CheckboxToggler.$container).toStrictEqual($container)
54
+ expect(CheckboxToggler.$toggleAllCheckbox).toStrictEqual($toggleAllCheckbox)
55
+ expect(CheckboxToggler.$checkboxes).toStrictEqual($checkboxes)
56
+ })
57
+
58
+ test('bind triggers addEventListener', () => {
59
+ const CheckboxToggler = new CheckboxTogglerClass(container, options)
60
+ const spyOnCheckboxesAddEvent = jest.spyOn(
61
+ CheckboxToggler.$checkboxes[0], 'addEventListener'
62
+ )
63
+ const spyOnToggleAllCheckboxAddEvent = jest.spyOn(
64
+ CheckboxToggler.$toggleAllCheckbox, 'addEventListener'
65
+ )
66
+
67
+ CheckboxToggler._bind()
68
+
69
+ expect(spyOnCheckboxesAddEvent).toHaveBeenCalledTimes(1)
70
+ expect(spyOnCheckboxesAddEvent).toHaveBeenCalledWith(
71
+ 'change',
72
+ expect.any(Function)
73
+ )
74
+
75
+ expect(spyOnToggleAllCheckboxAddEvent).toHaveBeenCalledTimes(1)
76
+ expect(spyOnToggleAllCheckboxAddEvent).toHaveBeenCalledWith(
77
+ 'change',
78
+ expect.any(Function)
79
+ )
80
+ })
81
+
82
+ test('didChangeCheckbox sets toggleAllCheckbox checked to true when all checked', () => {
83
+ const CheckboxToggler = new CheckboxTogglerClass(container, options)
84
+ CheckboxToggler._didChangeCheckbox()
85
+
86
+ expect(CheckboxToggler.$toggleAllCheckbox.checked).toBeTruthy()
87
+ expect(CheckboxToggler.$toggleAllCheckbox.indeterminate).toBeFalsy()
88
+ })
89
+
90
+ test('didChangeCheckbox sets toggleAllCheckbox checked to false when some checked', () => {
91
+ const inputElementToggleTwo = document.querySelector('input.toggle_two')
92
+ inputElementToggleTwo.checked = false
93
+
94
+ const CheckboxToggler = new CheckboxTogglerClass(container, options)
95
+ CheckboxToggler._didChangeCheckbox()
96
+
97
+ expect(CheckboxToggler.$toggleAllCheckbox.checked).toBeFalsy()
98
+ expect(CheckboxToggler.$toggleAllCheckbox.indeterminate).toBeTruthy()
99
+ })
100
+
101
+ test('didChangeToggleAllCheckbox', () => {
102
+ const CheckboxToggler = new CheckboxTogglerClass(container, options)
103
+ const setting = CheckboxToggler._didChangeToggleAllCheckbox()
104
+
105
+ expect(setting).toBeTruthy()
106
+ CheckboxToggler.$checkboxes.forEach(el => {
107
+ expect(el.checked).toStrictEqual(setting)
108
+ })
109
+ })
110
+ })
@@ -1,4 +1,3 @@
1
- /* eslint-env jest */
2
1
  import DetachedDropdownClass from '../detached_dropdown'
3
2
 
4
3
  describe('DetachedDropdown', () => {
@@ -1,4 +1,3 @@
1
- /* eslint-env jest */
2
1
  import Filters from '../filters'
3
2
  import * as utilModule from '../utils'
4
3
 
@@ -0,0 +1,14 @@
1
+ <div data-sortable="address">
2
+ <form class="has-many-list">
3
+ <fieldset class="has_many_fields">
4
+ <input type="checkbox" name="[_destroy]" checked>
5
+ <input type="text" name="[address]" value="Tokyo">
6
+ <a class="button has_many_remove"></a>
7
+ <a
8
+ class="button has_many_add"
9
+ data-html="<div></div>"
10
+ data-placeholder="script"
11
+ ></a>
12
+ </fieldset>
13
+ </form>
14
+ </div>
@@ -0,0 +1,224 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import Sortable from 'sortablejs'
4
+ import HasManyClass from '../has_many'
5
+
6
+ jest.mock('sortablejs')
7
+
8
+ describe('HasMany', () => {
9
+ const html = fs.readFileSync(path.resolve(__dirname, './has_many.html'))
10
+ let element
11
+
12
+ const options = {
13
+ list: '.has-many-list',
14
+ item: 'fieldset.has_many_fields',
15
+ addLink: 'a.button.has_many_add',
16
+ removeLink: 'a.button.has_many_remove',
17
+ destroyInput: 'input[name$="[_destroy]"]'
18
+ }
19
+
20
+ beforeEach(() => {
21
+ document.documentElement.innerHTML = html
22
+ element = document.querySelector('div')
23
+ global.adminterface = {
24
+ addObserver: jest.fn()
25
+ }
26
+ })
27
+
28
+ afterEach(() => {
29
+ jest.restoreAllMocks()
30
+ })
31
+
32
+ test('constructor initiates correctly', () => {
33
+ const spyOnBind = jest.spyOn(HasManyClass.prototype, '_bind')
34
+ spyOnBind.mockImplementation(() => {})
35
+
36
+ const HasMany = new HasManyClass(element)
37
+ expect(HasMany.element).toStrictEqual(element)
38
+
39
+ expect(spyOnBind).toHaveBeenCalledTimes(1)
40
+ })
41
+
42
+ test('bind calls bindEvents, initSortable and admininterface', () => {
43
+ const spyOnBindEvents = jest.spyOn(HasManyClass.prototype, '_bindEvents')
44
+ spyOnBindEvents.mockImplementation(() => {})
45
+
46
+ const spyOnInitSortable = jest.spyOn(HasManyClass.prototype, '_initSortable')
47
+ spyOnInitSortable.mockImplementation(() => {})
48
+
49
+ const HasMany = new HasManyClass(element)
50
+
51
+ expect(spyOnBindEvents).toHaveBeenCalledWith(element)
52
+ expect(spyOnInitSortable).toHaveBeenCalledTimes(1)
53
+ expect(global.adminterface.addObserver).toHaveBeenCalledTimes(1)
54
+ expect(global.adminterface.addObserver).toHaveBeenCalledWith(
55
+ element,
56
+ HasMany,
57
+ HasMany.constructor.name,
58
+ []
59
+ )
60
+ })
61
+
62
+ test('initSortable assigns new instance of Sortable to sortable property', () => {
63
+ const spyOnBindEvents = jest.spyOn(HasManyClass.prototype, '_bindEvents')
64
+ spyOnBindEvents.mockImplementation(() => {})
65
+
66
+ const HasMany = new HasManyClass(element)
67
+
68
+ const MockSortableArgument = {
69
+ handle: '.handle',
70
+ animation: 150,
71
+ ghostClass: 'sortable-placeholder',
72
+ dragClass: 'sortable-drag',
73
+ onUpdate: expect.any(Function)
74
+ }
75
+
76
+ expect(Sortable).toHaveBeenCalledTimes(1)
77
+ expect(Sortable).toHaveBeenCalledWith(
78
+ document.querySelector(HasMany.options.list),
79
+ MockSortableArgument
80
+ )
81
+ const sortable = new Sortable(
82
+ document.querySelector(HasMany.options.list),
83
+ MockSortableArgument
84
+ )
85
+ expect(HasMany.sortable.toString()).toStrictEqual(sortable.toString())
86
+ })
87
+
88
+ test('recomputePosition changes sortableInput value', () => {
89
+ const query = 'input[name$="[address]"]'
90
+ const sortableInput = element.querySelector(query)
91
+
92
+ expect(sortableInput.value.length).not.toEqual(0)
93
+
94
+ const HasMany = new HasManyClass(element)
95
+ HasMany._recomputePosition()
96
+
97
+ expect(sortableInput.value.length).toEqual(0)
98
+ })
99
+
100
+ test('bindDestroyEvent called with element and change event added', () => {
101
+ const spyOnBindDestroyEvent = jest.spyOn(
102
+ HasManyClass.prototype,
103
+ '_bindDestroyEvent'
104
+ )
105
+ const destroyInput = document.querySelector(options.destroyInput)
106
+ destroyInput.addEventListener = jest.fn()
107
+
108
+ new HasManyClass(element) // eslint-disable-line no-new
109
+
110
+ expect(spyOnBindDestroyEvent).toHaveBeenCalledTimes(1)
111
+ expect(spyOnBindDestroyEvent).toHaveBeenCalledWith(destroyInput)
112
+
113
+ expect(destroyInput.addEventListener).toHaveBeenCalledTimes(1)
114
+ expect(destroyInput.addEventListener).toHaveBeenCalledWith(
115
+ 'change',
116
+ expect.any(Function)
117
+ )
118
+ })
119
+
120
+ test('bindRemoveEvent called with element and click event added', () => {
121
+ const spyOnBindRemoveEvent = jest.spyOn(
122
+ HasManyClass.prototype,
123
+ '_bindRemoveEvent'
124
+ )
125
+ const removeLinks = document.querySelector(options.removeLink)
126
+ removeLinks.addEventListener = jest.fn()
127
+
128
+ new HasManyClass(element) // eslint-disable-line no-new
129
+
130
+ expect(spyOnBindRemoveEvent).toHaveBeenCalledTimes(1)
131
+ expect(spyOnBindRemoveEvent).toHaveBeenCalledWith(removeLinks)
132
+
133
+ expect(removeLinks.addEventListener).toHaveBeenCalledTimes(1)
134
+ expect(removeLinks.addEventListener).toHaveBeenCalledWith(
135
+ 'click',
136
+ expect.any(Function)
137
+ )
138
+ })
139
+
140
+ test('bindRemoveEventCallBack', () => {
141
+ const mockEvent = {
142
+ preventDefault: () => {},
143
+ target: document.body.querySelector('fieldset')
144
+ }
145
+ const spyOnRecomputePosition = jest.spyOn(
146
+ HasManyClass.prototype,
147
+ '_recomputePosition'
148
+ )
149
+ const HasMany = new HasManyClass(element)
150
+ const spyOnDispatch = jest.spyOn(HasMany.element, 'dispatchEvent')
151
+
152
+ const fieldSet = mockEvent.target.closest(HasMany.options.item)
153
+ fieldSet.remove = jest.fn()
154
+
155
+ const parent = HasMany.element
156
+ const removeBeforeEvent = HasMany.events.removeBefore
157
+ removeBeforeEvent.detail = { fieldSet, parent }
158
+ const removeAfterEvent = HasMany.events.removeAfter
159
+ removeAfterEvent.detail = { fieldSet, parent }
160
+
161
+ const output = HasMany._bindRemoveEventCallBack(mockEvent)
162
+
163
+ expect(spyOnRecomputePosition).toHaveBeenCalledTimes(1)
164
+ expect(spyOnDispatch).toHaveBeenCalledTimes(2)
165
+ expect(spyOnDispatch.mock.calls).toMatchObject([
166
+ [removeBeforeEvent],
167
+ [removeAfterEvent]
168
+ ])
169
+ expect(fieldSet.remove).toHaveBeenCalledTimes(1)
170
+ expect(output).toBeTruthy()
171
+ })
172
+
173
+ test('bindAddEvent called with correctly element and click event added', () => {
174
+ const spyOnBindAddEvent = jest.spyOn(
175
+ HasManyClass.prototype,
176
+ '_bindAddEvent'
177
+ )
178
+
179
+ const addLinks = document.querySelector(options.addLink)
180
+ addLinks.addEventListener = jest.fn()
181
+
182
+ new HasManyClass(element) // eslint-disable-line no-new
183
+
184
+ expect(spyOnBindAddEvent).toHaveBeenCalledTimes(1)
185
+ expect(spyOnBindAddEvent).toHaveBeenCalledWith(addLinks)
186
+
187
+ expect(addLinks.addEventListener).toHaveBeenCalledTimes(1)
188
+ expect(addLinks.addEventListener).toHaveBeenCalledWith(
189
+ 'click',
190
+ expect.any(Function)
191
+ )
192
+ })
193
+
194
+ test('bindAddEventCallBack', () => {
195
+ const mockEvent = {
196
+ preventDefault: () => {},
197
+ target: document.body.querySelector(options.addLink)
198
+ }
199
+ const HasMany = new HasManyClass(element)
200
+ const spyOnDispatchEvent = jest.spyOn(HasMany.element, 'dispatchEvent')
201
+ const $list = HasMany.element.querySelector(HasMany.options.list)
202
+ const spyOnListAppendChild = jest.spyOn($list, 'appendChild')
203
+ const spyOnBindEvents = jest.spyOn(HasMany, '_bindEvents')
204
+ const spyOnRecomputePosition = jest.spyOn(HasMany, '_recomputePosition')
205
+ const beforeAdd = HasMany.events.addBefore
206
+ const parent = HasMany.element
207
+ const datasetHTML = document.createElement('div')
208
+
209
+ const output = HasMany._bindAddEventCallBack(mockEvent)
210
+
211
+ expect(spyOnDispatchEvent).toHaveBeenCalledTimes(2)
212
+ expect(spyOnDispatchEvent.mock.calls).toMatchObject([
213
+ [beforeAdd, [parent]],
214
+ [HasMany.events.addAfter]
215
+ ])
216
+
217
+ expect(spyOnListAppendChild).toHaveBeenCalledTimes(1)
218
+ expect(spyOnListAppendChild).toHaveBeenCalledWith(datasetHTML)
219
+ expect(spyOnBindEvents).toHaveBeenCalledTimes(1)
220
+ expect(spyOnBindEvents).toHaveBeenCalledWith(datasetHTML)
221
+ expect(spyOnRecomputePosition).toHaveBeenCalledTimes(1)
222
+ expect(output).toBeTruthy()
223
+ })
224
+ })
@@ -1,4 +1,3 @@
1
- /* eslint-env jest */
2
1
  import {
3
2
  hasTurbolinks,
4
3
  turbolinksVisit,
@@ -51,54 +51,57 @@ class HasMany {
51
51
  }
52
52
 
53
53
  _bindAddEvent (el) {
54
- el.addEventListener('click', (e) => {
55
- let beforeAdd
56
- const el = e.target
57
- const parent = this.element
54
+ el.addEventListener('click', (e) => this._bindAddEventCallBack(e))
55
+ }
58
56
 
59
- e.preventDefault()
60
- parent.dispatchEvent(beforeAdd = this.events.addBefore, [parent])
57
+ _bindAddEventCallBack (e) {
58
+ let beforeAdd
59
+ const el = e.target
60
+ const parent = this.element
61
+ e.preventDefault()
62
+ parent.dispatchEvent(beforeAdd = this.events.addBefore, [parent])
61
63
 
62
- if (!beforeAdd.defaultPrevented) {
63
- let index = parent.dataset.hasManyIndex || parent.querySelectorAll(this.options.item).length - 1
64
- parent.setAttribute('data-has-many-index', ++index)
64
+ if (!beforeAdd.defaultPrevented) {
65
+ let index = parent.dataset.hasManyIndex || parent.querySelectorAll(this.options.item).length - 1
66
+ parent.setAttribute('data-has-many-index', ++index)
65
67
 
66
- const regex = new RegExp(el.dataset.placeholder, 'g')
67
- const html = el.dataset.html.replaceAll(regex, index)
68
- const newNode = document.createElement('div')
69
- newNode.innerHTML = html
68
+ const regex = new RegExp(el.dataset.placeholder, 'g')
69
+ const html = el.dataset.html.replace(regex, index)
70
+ const newNode = document.createElement('div')
71
+ newNode.innerHTML = html
70
72
 
71
- const fieldset = newNode.firstElementChild
72
- const $list = this.element.querySelector(this.options.list)
73
+ const fieldset = newNode.firstElementChild
74
+ const $list = this.element.querySelector(this.options.list)
73
75
 
74
- $list.appendChild(fieldset)
75
- this._bindEvents(fieldset)
76
- this._recomputePosition()
76
+ $list.appendChild(fieldset)
77
+ this._bindEvents(fieldset)
78
+ this._recomputePosition()
77
79
 
78
- const addAfterEvent = this.events.addAfter
79
- addAfterEvent.detail = { fieldset, parent }
80
- return parent.dispatchEvent(this.events.addAfter)
81
- }
82
- })
80
+ const addAfterEvent = this.events.addAfter
81
+ addAfterEvent.detail = { fieldset, parent }
82
+ return parent.dispatchEvent(this.events.addAfter)
83
+ }
83
84
  }
84
85
 
85
86
  _bindRemoveEvent (el) {
86
- el.addEventListener('click', (e) => {
87
- const el = e.target
88
- const parent = this.element
89
- const fieldset = el.closest(this.options.item)
90
- const removeBeforeEvent = this.events.removeBefore
91
- const removeAfterEvent = this.events.removeAfter
92
-
93
- e.preventDefault()
94
- this._recomputePosition()
87
+ el.addEventListener('click', (e) => this._bindRemoveEventCallBack(e))
88
+ }
95
89
 
96
- removeBeforeEvent.detail = { fieldset, parent }
97
- removeAfterEvent.detail = { fieldset, parent }
98
- parent.dispatchEvent(removeBeforeEvent)
99
- fieldset.remove()
100
- return parent.dispatchEvent(removeAfterEvent)
101
- })
90
+ _bindRemoveEventCallBack (e) {
91
+ const el = e.target
92
+ const parent = this.element
93
+ const fieldset = el.closest(this.options.item)
94
+ const removeBeforeEvent = this.events.removeBefore
95
+ const removeAfterEvent = this.events.removeAfter
96
+
97
+ e.preventDefault()
98
+ this._recomputePosition()
99
+
100
+ removeBeforeEvent.detail = { fieldset, parent }
101
+ removeAfterEvent.detail = { fieldset, parent }
102
+ parent.dispatchEvent(removeBeforeEvent)
103
+ fieldset.remove()
104
+ return parent.dispatchEvent(removeAfterEvent)
102
105
  }
103
106
 
104
107
  _bindEvents (el) {
@@ -1,3 +1,3 @@
1
1
  module Adminterface
2
- VERSION = "0.2.0"
2
+ VERSION = "0.2.1"
3
3
  end
@@ -11,6 +11,10 @@ module Adminterface
11
11
  ActiveRecord::Migration.next_migration_number(next_migration_number)
12
12
  end
13
13
 
14
+ def install_action_text
15
+ rails_command "action_text:install"
16
+ end
17
+
14
18
  def create_initializer
15
19
  template "active_admin_comment_action_text.rb",
16
20
  "config/initializers/active_admin_comment_action_text.rb"
@@ -2,10 +2,10 @@ module Adminterface
2
2
  module Generators
3
3
  class ConfigsGenerator < Rails::Generators::Base
4
4
  desc "Copies config files into a directory for customization"
5
- argument :namespace, type: :string, default: "Admin"
6
-
7
5
  source_root File.expand_path("templates", __dir__)
8
6
 
7
+ argument :namespace, type: :string, default: "Admin"
8
+
9
9
  def copy
10
10
  directory Adminterface::Engine.root.join("lib/adminterface/fixtures"),
11
11
  "config/adminterface/#{namespace.underscore}/"
@@ -4,37 +4,35 @@ module Adminterface
4
4
  desc "Installs Adminterface and generates necessary files and migrations"
5
5
  source_root File.expand_path("templates", __dir__)
6
6
 
7
- def install_rails_addons
8
- invoke "action_text:install"
9
- end
7
+ class_option :skip_comments,
8
+ type: :boolean, default: false,
9
+ desc: "Skip installation of ActionText for comments"
10
+ class_option :skip_examples,
11
+ type: :boolean, default: false,
12
+ desc: "Skip adding sample admin files"
13
+ class_option :use_webpacker,
14
+ type: :boolean, default: true,
15
+ desc: "Install assets with webpacker"
16
+ class_option :version,
17
+ aliases: "-v", type: :string, default: Adminterface::VERSION,
18
+ desc: "Install with a specific version"
10
19
 
11
20
  def install_assets
12
- install_webpacker? do
13
- generate "adminterface:webpacker"
14
- end
15
- end
21
+ return unless options[:use_webpacker]
16
22
 
17
- def install_action_text_for_comments
18
- generate "adminterface:comments"
23
+ generate "adminterface:webpacker -v #{options[:version]}"
19
24
  end
20
25
 
21
- def add_samples
22
- template "dashboard.rb", "app/admin/dashboard.rb"
23
- end
26
+ def install_comments
27
+ return if options[:skip_comments]
24
28
 
25
- private
29
+ generate "adminterface:comments"
30
+ end
26
31
 
27
- def install_webpacker?
28
- return yield if defined?(Webpacker)
32
+ def add_examples
33
+ return if options[:skip_examples]
29
34
 
30
- puts Rainbow("Adminterface requires webpacker:").yellow
31
- if yes?("Install webpacker and continue?")
32
- gem "webpacker"
33
- rails_command "webpacker:install"
34
- yield
35
- else
36
- puts Rainbow("Installation aborted").red
37
- end
35
+ template "dashboard.rb", "app/admin/dashboard.rb"
38
36
  end
39
37
  end
40
38
  end
@@ -2,13 +2,13 @@ module Adminterface
2
2
  module Generators
3
3
  class ViewsGenerator < Rails::Generators::Base
4
4
  desc "Copies view templates for customization"
5
-
6
5
  source_root Adminterface::Engine.root.join("app/views")
7
6
 
8
7
  def copy
9
8
  directory "active_admin", "app/views/active_admin"
10
9
  directory "kaminari/active_admin", "app/views/kaminari/active_admin"
11
- directory "layouts/active_admin_logged_out.html.erb", "app/views/layouts/active_admin_logged_out.html.erb"
10
+ directory "layouts/active_admin_logged_out.html.erb",
11
+ "app/views/layouts/active_admin_logged_out.html.erb"
12
12
  end
13
13
  end
14
14
  end
@@ -2,11 +2,24 @@ module Adminterface
2
2
  module Generators
3
3
  class WebpackerGenerator < Rails::Generators::Base
4
4
  desc "Install Stylesheets and JavaScripts using Webpacker"
5
+ source_root File.expand_path("templates", __dir__)
6
+
5
7
  class_option :version,
6
8
  aliases: "-v", type: :string, default: Adminterface::VERSION,
7
9
  desc: "Install with a specific npm package version"
8
10
 
9
- source_root File.expand_path("templates", __dir__)
11
+ def install_webpacker
12
+ return if webpacker_installed?
13
+
14
+ puts Rainbow("Adminterface requires webpacker:").yellow
15
+ if yes?("Install webpacker and continue?")
16
+ gem "webpacker"
17
+ rails_command "webpacker:install"
18
+ else
19
+ puts Rainbow("Installation aborted").red
20
+ abort
21
+ end
22
+ end
10
23
 
11
24
  def install_packages
12
25
  insert_into_file "config/webpack/environment.js", after: /require\(('|")@rails\/webpacker\1\);?\n/ do
@@ -18,7 +31,7 @@ module Adminterface
18
31
  EOF
19
32
  end
20
33
 
21
- run "yarn add @cmdbrew/adminterface@v#{find_npm_version}"
34
+ run "yarn add @cmdbrew/adminterface@#{npm_version}"
22
35
  end
23
36
 
24
37
  def install_assets
@@ -44,11 +57,12 @@ module Adminterface
44
57
 
45
58
  private
46
59
 
47
- def find_npm_version
48
- version = options[:version]
49
- return version unless version.include?(".rc")
60
+ def webpacker_installed?
61
+ defined?(Webpacker)
62
+ end
50
63
 
51
- version.gsub(".rc", "-rc")
64
+ def npm_version
65
+ options[:version].gsub(".rc", "-rc")
52
66
  end
53
67
  end
54
68
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: adminterface
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - I-Lung Lee
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-15 00:00:00.000000000 Z
11
+ date: 2021-10-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activeadmin
@@ -80,7 +80,8 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0.18'
83
- description: An add-on that brings Bootstrap 5 and other goodies into ActiveAdmin.
83
+ description: A gem that brings Bootstrap 5, advanced customizability, and other goodies
84
+ into ActiveAdmin
84
85
  email:
85
86
  - ilung@hey.com
86
87
  executables: []
@@ -174,8 +175,12 @@ files:
174
175
  - app/javascript/adminterface/initializers/plugins/input_counter.js
175
176
  - app/javascript/adminterface/initializers/plugins/password_visibility.js
176
177
  - app/javascript/adminterface/initializers/plugins/tom_select.js
178
+ - app/javascript/adminterface/lib/__tests__/checkbox_toggler.html
179
+ - app/javascript/adminterface/lib/__tests__/checkbox_toggler.spec.js
177
180
  - app/javascript/adminterface/lib/__tests__/detached_dropdown.spec.js
178
181
  - app/javascript/adminterface/lib/__tests__/filters.spec.js
182
+ - app/javascript/adminterface/lib/__tests__/has_many.html
183
+ - app/javascript/adminterface/lib/__tests__/has_many.spec.js
179
184
  - app/javascript/adminterface/lib/__tests__/utils.spec.js
180
185
  - app/javascript/adminterface/lib/batch_actions.js
181
186
  - app/javascript/adminterface/lib/checkbox_toggler.js
@@ -377,5 +382,5 @@ requirements: []
377
382
  rubygems_version: 3.2.22
378
383
  signing_key:
379
384
  specification_version: 4
380
- summary: An add-on that brings Bootstrap 5 and other goodies into ActiveAdmin.
385
+ summary: Build functional and beautiful web apps faster with ActiveAdmin
381
386
  test_files: []