headmin 0.3.3 → 0.4.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.
Files changed (160) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +27 -0
  3. data/.gitignore +14 -0
  4. data/.nvmrc +1 -0
  5. data/CHANGELOG.md +24 -0
  6. data/Gemfile +7 -4
  7. data/Gemfile.lock +171 -3
  8. data/README.md +9 -1
  9. data/Rakefile +1 -7
  10. data/app/assets/javascripts/headmin/config/i18n.js +9 -9
  11. data/app/assets/javascripts/headmin/controllers/autocomplete_controller.js +318 -0
  12. data/app/assets/javascripts/headmin/controllers/blocks_controller.js +74 -79
  13. data/app/assets/javascripts/headmin/controllers/date_range_controller.js +24 -24
  14. data/app/assets/javascripts/headmin/controllers/dropzone_controller.js +23 -25
  15. data/app/assets/javascripts/headmin/controllers/file_preview_controller.js +237 -237
  16. data/app/assets/javascripts/headmin/controllers/filter_controller.js +44 -44
  17. data/app/assets/javascripts/headmin/controllers/filters_controller.js +57 -61
  18. data/app/assets/javascripts/headmin/controllers/flatpickr_controller.js +29 -29
  19. data/app/assets/javascripts/headmin/controllers/hello_controller.js +3 -3
  20. data/app/assets/javascripts/headmin/controllers/notification_controller.js +7 -6
  21. data/app/assets/javascripts/headmin/controllers/popup_controller.js +57 -51
  22. data/app/assets/javascripts/headmin/controllers/redactorx_controller.js +36 -9
  23. data/app/assets/javascripts/headmin/controllers/repeater_controller.js +122 -125
  24. data/app/assets/javascripts/headmin/controllers/select_controller.js +40 -39
  25. data/app/assets/javascripts/headmin/controllers/table_actions_controller.js +88 -77
  26. data/app/assets/javascripts/headmin/controllers/table_controller.js +115 -116
  27. data/app/assets/javascripts/headmin/index.js +38 -35
  28. data/app/assets/javascripts/headmin.js +309 -42
  29. data/app/assets/stylesheets/headmin/forms/autocomplete.scss +21 -0
  30. data/app/assets/stylesheets/headmin/forms/file.scss +46 -0
  31. data/app/assets/stylesheets/headmin/forms/repeater.scss +62 -0
  32. data/app/assets/stylesheets/headmin/forms/search.scss +12 -0
  33. data/app/assets/stylesheets/headmin/forms.scss +11 -0
  34. data/app/assets/stylesheets/headmin/general.scss +14 -0
  35. data/app/assets/stylesheets/headmin/overrides/bootstrap.scss +5 -3
  36. data/app/assets/stylesheets/headmin/overrides/redactorx.scss +74 -0
  37. data/app/assets/stylesheets/headmin/popup.scss +1 -0
  38. data/app/assets/stylesheets/headmin/syntax.scss +36 -349
  39. data/app/assets/stylesheets/headmin/table.scss +1 -1
  40. data/app/assets/stylesheets/headmin/utilities/buttons.scss +19 -0
  41. data/app/assets/stylesheets/headmin/utilities/dropzone.scss +72 -0
  42. data/app/assets/stylesheets/headmin/utilities.scss +2 -68
  43. data/app/assets/stylesheets/headmin.css +209 -205
  44. data/app/assets/stylesheets/headmin.scss +1 -1
  45. data/app/helpers/headmin/admin_helper.rb +0 -1
  46. data/app/helpers/headmin/form_helper.rb +2 -8
  47. data/app/models/concerns/headmin/blockable.rb +1 -1
  48. data/app/models/concerns/headmin/field.rb +1 -0
  49. data/app/models/concerns/headmin/form/autocompletable.rb +38 -0
  50. data/app/models/concerns/headmin/form/hintable.rb +19 -0
  51. data/app/models/concerns/headmin/form/input_groupable.rb +23 -0
  52. data/app/models/concerns/headmin/form/labelable.rb +33 -0
  53. data/app/models/concerns/headmin/form/listable.rb +28 -0
  54. data/app/models/concerns/headmin/form/placeholderable.rb +13 -0
  55. data/app/models/concerns/headmin/form/validatable.rb +40 -0
  56. data/app/models/concerns/headmin/form/wrappable.rb +21 -0
  57. data/app/models/headmin/.DS_Store +0 -0
  58. data/app/models/headmin/blocks_view.rb +15 -0
  59. data/app/models/headmin/form/blocks_view.rb +29 -0
  60. data/app/models/headmin/form/checkbox_view.rb +52 -0
  61. data/app/models/headmin/form/date_range_view.rb +25 -0
  62. data/app/models/headmin/form/date_view.rb +45 -0
  63. data/app/models/headmin/form/email_view.rb +48 -0
  64. data/app/models/headmin/form/file_view.rb +116 -0
  65. data/app/models/headmin/form/flatpickr_range_view.rb +102 -0
  66. data/app/models/headmin/form/flatpickr_view.rb +37 -0
  67. data/app/models/headmin/form/hidden_view.rb +10 -0
  68. data/app/models/headmin/form/hint_view.rb +6 -0
  69. data/app/models/headmin/form/input_group_view.rb +19 -0
  70. data/app/models/headmin/form/label_view.rb +24 -0
  71. data/app/models/headmin/form/number_view.rb +49 -0
  72. data/app/models/headmin/form/password_view.rb +44 -0
  73. data/app/models/headmin/form/redactorx_view.rb +59 -0
  74. data/app/models/headmin/form/search_view.rb +48 -0
  75. data/app/models/headmin/form/select_view.rb +62 -0
  76. data/app/models/headmin/form/switch_view.rb +23 -0
  77. data/app/models/headmin/form/text_view.rb +48 -0
  78. data/app/models/headmin/form/textarea_view.rb +44 -0
  79. data/app/models/headmin/form/url_view.rb +48 -0
  80. data/app/models/headmin/form/wrapper_view.rb +19 -0
  81. data/app/models/headmin/form/wysiwyg_view.rb +17 -0
  82. data/app/models/headmin/{thumbnail.rb → thumbnail_view.rb} +6 -1
  83. data/app/models/view_model.rb +58 -0
  84. data/app/views/headmin/_blocks.html.erb +13 -9
  85. data/app/views/headmin/_heading.html.erb +7 -1
  86. data/app/views/headmin/_thumbnail.html.erb +1 -37
  87. data/app/views/headmin/forms/_autocomplete.html.erb +11 -0
  88. data/app/views/headmin/forms/_blocks.html.erb +16 -17
  89. data/app/views/headmin/forms/_checkbox.html.erb +24 -29
  90. data/app/views/headmin/forms/_datalist.html.erb +3 -0
  91. data/app/views/headmin/forms/_date.html.erb +24 -24
  92. data/app/views/headmin/forms/_date_range.html.erb +19 -21
  93. data/app/views/headmin/forms/_email.html.erb +27 -32
  94. data/app/views/headmin/forms/_errors.html.erb +2 -3
  95. data/app/views/headmin/forms/_file.html.erb +84 -181
  96. data/app/views/headmin/forms/_flatpickr.html.erb +19 -20
  97. data/app/views/headmin/forms/_flatpickr_range.html.erb +28 -37
  98. data/app/views/headmin/forms/_hidden.html.erb +9 -10
  99. data/app/views/headmin/forms/_hint.html.erb +16 -0
  100. data/app/views/headmin/forms/_input_group.html.erb +21 -0
  101. data/app/views/headmin/forms/_label.html.erb +5 -13
  102. data/app/views/headmin/forms/_number.html.erb +23 -35
  103. data/app/views/headmin/forms/_password.html.erb +21 -30
  104. data/app/views/headmin/forms/_redactorx.html.erb +21 -40
  105. data/app/views/headmin/forms/_repeater.html.erb +55 -60
  106. data/app/views/headmin/forms/_search.html.erb +43 -0
  107. data/app/views/headmin/forms/_select.html.erb +24 -49
  108. data/app/views/headmin/forms/_switch.html.erb +29 -0
  109. data/app/views/headmin/forms/_text.html.erb +42 -96
  110. data/app/views/headmin/forms/_textarea.html.erb +21 -32
  111. data/app/views/headmin/forms/_url.html.erb +26 -31
  112. data/app/views/headmin/forms/_validation.html.erb +10 -13
  113. data/app/views/headmin/forms/_wrapper.html.erb +9 -0
  114. data/app/views/headmin/forms/_wysiwyg.html.erb +28 -0
  115. data/app/views/headmin/forms/autocomplete/_item.html.erb +3 -0
  116. data/app/views/headmin/forms/autocomplete/_list.html.erb +3 -0
  117. data/app/views/headmin/forms/fields/_group.html.erb +9 -2
  118. data/app/views/headmin/forms/repeater/_row.html.erb +4 -4
  119. data/app/views/headmin/table/_actions.html.erb +1 -1
  120. data/app/views/headmin/table/actions/_action.html.erb +2 -1
  121. data/app/views/headmin/table/actions/_delete.html.erb +1 -1
  122. data/bin/console +0 -1
  123. data/config/locales/headmin/forms/en.yml +1 -12
  124. data/config/locales/headmin/forms/nl.yml +1 -12
  125. data/esbuild-css.js +18 -18
  126. data/esbuild-js.js +8 -8
  127. data/headmin.gemspec +1 -3
  128. data/lib/generators/templates/controllers/auth/confirmations_controller.rb +0 -2
  129. data/lib/generators/templates/controllers/auth/omniauth_callbacks_controller.rb +0 -2
  130. data/lib/generators/templates/controllers/auth/passwords_controller.rb +0 -2
  131. data/lib/generators/templates/controllers/auth/registrations_controller.rb +0 -2
  132. data/lib/generators/templates/controllers/auth/sessions_controller.rb +0 -2
  133. data/lib/generators/templates/controllers/auth/unlocks_controller.rb +0 -2
  134. data/lib/headmin/version.rb +1 -3
  135. data/lib/headmin.rb +0 -2
  136. data/package-lock.json +5359 -0
  137. data/package.json +13 -6
  138. data/view_model_benchmark.rb +74 -0
  139. data/yarn-error.log +367 -0
  140. data/yarn.lock +1575 -31
  141. metadata +63 -24
  142. data/app/assets/stylesheets/headmin/form.scss +0 -132
  143. data/app/assets/stylesheets/headmin/overrides/redactorx.css +0 -3
  144. data/app/helpers/headmin/documentation_helper.rb +0 -35
  145. data/app/models/headmin/documentation_renderer.rb +0 -32
  146. data/app/models/headmin/form/base.rb +0 -78
  147. data/app/models/headmin/form/text.rb +0 -51
  148. data/app/services/block_service.rb +0 -72
  149. data/app/views/headmin/_card.html.erb +0 -52
  150. data/app/views/headmin/forms/_actions.html.erb +0 -28
  151. data/app/views/headmin/forms/_base.html.erb +0 -114
  152. data/app/views/headmin/forms/_image.html.erb +0 -21
  153. data/app/views/headmin/forms/_video.html.erb +0 -21
  154. data/app/views/headmin/forms/actions/_destroy.html.erb +0 -13
  155. data/app/views/headmin/forms/actions/_save.html.erb +0 -12
  156. data/app/views/headmin/forms/actions/_view.html.erb +0 -15
  157. data/docs/blocks-and-fields.md +0 -54
  158. data/docs/blocks.md +0 -48
  159. data/docs/devise.md +0 -41
  160. data/docs/fields.md +0 -79
@@ -1,64 +1,60 @@
1
- import {Controller} from "@hotwired/stimulus"
1
+ import { Controller } from '@hotwired/stimulus'
2
2
 
3
3
  export default class extends Controller {
4
- static get targets() {
5
- return ["form", "list", "input", "template", "button", "menuItem"]
6
- }
7
-
8
- add(event) {
9
- event.preventDefault()
10
- const name = event.target.dataset.filterName
11
- const button = this.getButtonByName(name)
12
- if (button) {
13
- this.openFilter(button)
14
- } else {
15
- this.createFilter(name)
16
- }
17
- }
18
-
19
- createFilter(name) {
20
- let html = this.getTemplateHTML(name)
21
- html = this.replaceIdsWithTimestamps(html)
22
- this.listTarget.insertAdjacentHTML('beforeend', html)
23
-
24
- // Dispatch an event
25
- this.menuItemTarget.dispatchEvent(new CustomEvent('headmin:reinit', {bubbles: true}))
26
- }
27
-
28
- remove(event) {
29
- const filter = event.currentTarget.closest('.h-filter')
30
- filter.remove()
31
- this.formTarget.submit()
32
- }
33
-
34
- removeAll(event) {
35
- this.listTarget.innerHTML = ""
36
- this.formTarget.submit()
37
- }
38
-
39
- update(event) {
40
- this.formTarget.submit()
41
- }
42
-
43
- getButtonByName(name) {
44
- return this.buttonTargets.find(function (element) {
45
- return element.dataset.filterName === name
46
- })
47
- }
48
-
49
- openFilter(button) {
50
- button.controller.open()
51
- }
52
-
53
- getTemplateHTML(name) {
54
- const template = this.templateTargets.filter((element) => {
55
- return element.dataset.filterName === name
56
- })[0]
57
- return template.innerHTML
58
- }
59
-
60
- replaceIdsWithTimestamps(html) {
61
- const regex = new RegExp('template_id', "g");
62
- return html.replace(regex, new Date().getTime())
63
- }
4
+ static get targets () {
5
+ return ['form', 'list', 'input', 'template', 'button', 'menuItem']
6
+ }
7
+
8
+ add (event) {
9
+ event.preventDefault()
10
+ const name = event.target.dataset.filterName
11
+ const button = this.getButtonByName(name)
12
+ if (button) {
13
+ this.openFilter(button)
14
+ } else {
15
+ this.createFilter(name)
16
+ }
17
+ }
18
+
19
+ createFilter (name) {
20
+ let html = this.getTemplateHTML(name)
21
+ html = this.replaceIdsWithTimestamps(html)
22
+ this.listTarget.insertAdjacentHTML('beforeend', html)
23
+ }
24
+
25
+ remove (event) {
26
+ const filter = event.currentTarget.closest('.h-filter')
27
+ filter.remove()
28
+ this.formTarget.submit()
29
+ }
30
+
31
+ removeAll (event) {
32
+ this.listTarget.innerHTML = ''
33
+ this.formTarget.submit()
34
+ }
35
+
36
+ update (event) {
37
+ this.formTarget.submit()
38
+ }
39
+
40
+ getButtonByName (name) {
41
+ return this.buttonTargets.find(function (element) {
42
+ return element.dataset.filterName === name
43
+ })
44
+ }
45
+
46
+ openFilter (button) {
47
+ button.controller.open()
48
+ }
49
+
50
+ getTemplateHTML (name) {
51
+ const template = this.templateTargets.filter((element) => {
52
+ return element.dataset.filterName === name
53
+ })[0]
54
+ return template.innerHTML
55
+ }
56
+
57
+ replaceIdsWithTimestamps (html) {
58
+ return html.replace(/template_id/g, new Date().getTime())
59
+ }
64
60
  }
@@ -1,39 +1,39 @@
1
- import {Controller} from "@hotwired/stimulus"
2
- import flatpickr from "flatpickr";
3
- import {Dutch} from "flatpickr/dist/esm/l10n/nl.js"
4
- import I18n from "../config/i18n";
1
+ import { Controller } from '@hotwired/stimulus'
2
+ import flatpickr from 'flatpickr'
3
+ import { Dutch } from 'flatpickr/dist/esm/l10n/nl.js'
4
+ import I18n from '../config/i18n'
5
5
 
6
6
  export default class extends Controller {
7
- static get targets() {
8
- return ["input"]
9
- }
7
+ static get targets () {
8
+ return ['input']
9
+ }
10
10
 
11
- connect() {
12
- const options = {...this.defaultOptions(), ...this.options()}
13
- flatpickr(this.inputTarget, options);
14
- }
11
+ connect () {
12
+ const options = { ...this.defaultOptions(), ...this.options() }
13
+ flatpickr(this.inputTarget, options)
14
+ }
15
15
 
16
- options() {
17
- return JSON.parse(this.inputTarget.getAttribute('data-flatpickr-options'))
18
- }
16
+ options () {
17
+ return JSON.parse(this.inputTarget.getAttribute('data-flatpickr'))
18
+ }
19
19
 
20
- defaultOptions() {
21
- return {
22
- allowInput: true,
23
- dateFormat: 'd/m/Y',
24
- locale: this.getLocale(I18n.locale)
25
- }
20
+ defaultOptions () {
21
+ return {
22
+ allowInput: true,
23
+ dateFormat: 'd/m/Y',
24
+ locale: this.getLocale(I18n.locale)
26
25
  }
26
+ }
27
27
 
28
- getLocale(locale) {
29
- const locales = this.locales()
30
- return locales[locale]
31
- }
28
+ getLocale (locale) {
29
+ const locales = this.locales()
30
+ return locales[locale]
31
+ }
32
32
 
33
- locales() {
34
- return {
35
- en: null,
36
- nl: Dutch
37
- }
33
+ locales () {
34
+ return {
35
+ en: null,
36
+ nl: Dutch
38
37
  }
38
+ }
39
39
  }
@@ -1,7 +1,7 @@
1
- import {Controller} from "@hotwired/stimulus"
1
+ import { Controller } from '@hotwired/stimulus'
2
2
 
3
3
  export default class extends Controller {
4
- connect() {
5
- this.element.textContent = "Hello world"
4
+ connect () {
5
+ this.element.textContent = 'Hello world'
6
6
  }
7
7
  }
@@ -1,8 +1,9 @@
1
- import {Controller} from "@hotwired/stimulus"
2
- import {Toast} from "bootstrap";
1
+ import { Controller } from '@hotwired/stimulus'
2
+ import { Toast } from 'bootstrap'
3
3
 
4
4
  export default class extends Controller {
5
- connect() {
6
- new Toast(this.element, {})
7
- }
8
- }
5
+ connect () {
6
+ /* eslint-disable no-new */
7
+ new Toast(this.element, {})
8
+ }
9
+ }
@@ -1,67 +1,73 @@
1
- import {Controller} from "@hotwired/stimulus"
2
- import {createPopper} from '@popperjs/core';
1
+ /* global HTMLInputElement */
2
+ import { Controller } from '@hotwired/stimulus'
3
+ import { createPopper } from '@popperjs/core'
3
4
 
4
5
  export default class extends Controller {
5
- static get targets() {
6
- return ["popup", "button"]
7
- }
6
+ static get targets () {
7
+ return ['popup', 'button']
8
+ }
8
9
 
9
- static get values() {
10
- return {id: String}
11
- }
10
+ static get values () {
11
+ return { id: String }
12
+ }
12
13
 
13
- connect() {
14
- // Clicked outside popup
15
- document.addEventListener('click', (event) => {
16
- this.handleOutsideClick(event)
17
- })
18
- }
14
+ connect () {
15
+ // Clicked outside popup
16
+ document.addEventListener('click', (event) => {
17
+ this.handleOutsideClick(event)
18
+ })
19
+ }
19
20
 
20
- handleOutsideClick(event) {
21
- const inPopup = event.target.closest('[data-popup-target="popup"]') !== null
22
- const inButton = event.target.closest('[data-popup-target="button"]') !== null
23
- const openPopup = document.querySelector('[data-popup-target="popup"]:not(.closed)')
21
+ handleOutsideClick (event) {
22
+ const inPopup = event.target.closest('[data-popup-target="popup"]') !== null
23
+ const inButton = event.target.closest('[data-popup-target="button"]') !== null
24
+ const openPopup = document.querySelector('[data-popup-target="popup"]:not(.closed)')
24
25
 
25
- if(!inButton && !inPopup && openPopup) {
26
- this.closePopup(openPopup)
27
- }
26
+ if (!inButton && !inPopup && openPopup) {
27
+ this.closePopup(openPopup)
28
28
  }
29
+ }
30
+
31
+ open (event) {
32
+ const button = event.target.closest('[data-popup-target="button"]')
33
+ const popup = this.popupById(button.dataset.popupId)
34
+ const passThru = button.dataset.popupPassThru
29
35
 
30
- open(event) {
31
- const button = event.target.closest('[data-popup-target="button"]')
32
- const popup = this.popupById(button.dataset['popupId'])
33
- const passThru = button.dataset['popupPassThru']
36
+ // Open the popup
37
+ createPopper(button, popup)
38
+ this.openPopup(popup)
34
39
 
35
- if (passThru) {
36
- // Pass click event to an element inside the popup
37
- const passThruElement = popup.querySelector(passThru)
38
- passThruElement.click()
40
+ if (passThru) {
41
+ // Pass click event to an element inside the popup
42
+ const passThruElement = popup.querySelector(passThru)
43
+ passThruElement.click()
39
44
 
40
- } else {
41
- // Open the popup
42
- createPopper(button, popup)
43
- this.openPopup(popup)
44
- }
45
+ // Focus and select value if it's an input element
46
+ if (passThruElement instanceof HTMLInputElement) {
47
+ passThruElement.focus()
48
+ passThruElement.select()
49
+ }
45
50
  }
51
+ }
46
52
 
47
- close(event) {
48
- const button = event.target.closest('[data-popup-target="button"]')
49
- const popup = this.popupById(button.dataset['popupId'])
53
+ close (event) {
54
+ const button = event.target.closest('[data-popup-target="button"]')
55
+ const popup = this.popupById(button.dataset.popupId)
50
56
 
51
- this.closePopup(popup)
52
- }
57
+ this.closePopup(popup)
58
+ }
53
59
 
54
- popupById(id) {
55
- return this.popupTargets.find((popup) => {
56
- return popup.dataset['popupId'] === id
57
- })
58
- }
60
+ popupById (id) {
61
+ return this.popupTargets.find((popup) => {
62
+ return popup.dataset.popupId === id
63
+ })
64
+ }
59
65
 
60
- openPopup(popup) {
61
- popup.classList.remove('closed')
62
- }
66
+ openPopup (popup) {
67
+ popup.classList.remove('closed')
68
+ }
63
69
 
64
- closePopup(popup) {
65
- popup.classList.add('closed')
66
- }
67
- }
70
+ closePopup (popup) {
71
+ popup.classList.add('closed')
72
+ }
73
+ }
@@ -1,13 +1,40 @@
1
- import {Controller} from "@hotwired/stimulus"
1
+ /* global RedactorX */
2
+ import { Controller } from '@hotwired/stimulus'
2
3
 
3
4
  export default class extends Controller {
4
- connect() {
5
- if (typeof RedactorX == 'undefined') {
6
- console.error("RedactorX is a paid module and is not included in Headmin. Please purchase it and import it as a JS module")
7
- return false;
8
- }
5
+ connect () {
6
+ this.initRedactor()
7
+ }
8
+
9
+ initRedactor () {
10
+ if (typeof RedactorX === 'undefined') {
11
+ console.error('RedactorX is a paid module and is not included in Headmin. Please purchase it and import it as a JS module')
12
+ return false
13
+ }
9
14
 
10
- const options = JSON.parse(this.element.getAttribute('data-redactor-options'))
11
- RedactorX(this.element, options);
15
+ const defaultOptions = {
16
+ editor: {
17
+ minHeight: '57px'
18
+ },
19
+ subscribe: {
20
+ 'app.start': () => {
21
+ this.stylize()
22
+ }
23
+ }
12
24
  }
13
- }
25
+ const options = JSON.parse(this.element.getAttribute('data-redactor-options'))
26
+ RedactorX(this.element, { ...defaultOptions, ...options })
27
+ }
28
+
29
+ stylize () {
30
+ const container = this.element.nextElementSibling
31
+
32
+ // Copy textarea classes
33
+ const inputClasses = this.element.classList
34
+ container.classList.add(...inputClasses)
35
+
36
+ // Add top margin to input group
37
+ // const inputGroup = container.closest('.input-group')
38
+ // inputGroup.classList.add('mt-4')
39
+ }
40
+ }
@@ -1,139 +1,136 @@
1
- import {Controller} from "@hotwired/stimulus"
2
- import Sortable from "sortablejs";
1
+ import { Controller } from '@hotwired/stimulus'
2
+ import Sortable from 'sortablejs'
3
3
 
4
4
  export default class extends Controller {
5
- static get values() {
6
- return {
7
- id: String
8
- }
5
+ static get values () {
6
+ return {
7
+ id: String
9
8
  }
10
-
11
- static get targets() {
12
- return ["repeater", "footer", "template", "row", "list", "empty", "addButton"]
13
- }
14
-
15
- connect() {
16
- new Sortable(this.listTarget, {
17
- animation: 150,
18
- ghostClass: 'list-group-item-dark',
19
- draggable: '.repeater-row',
20
- handle: '.repeater-row-handle',
21
- onEnd: () => {
22
- this.resetIndices()
23
- this.resetPositions()
24
- }
25
- })
26
-
27
- this.toggleEmpty()
28
- }
29
-
30
- resetButtonIndices(event) {
31
- const row = event.target.closest('.repeater-row')
32
- const index = this.containsRow(row) ? row.dataset.rowIndex : ''
33
- this.updatePopupButtonIndices(index)
34
- }
35
-
36
- containsRow(row) {
37
- return this.rowTargets.includes(row)
38
- }
39
-
40
- updatePopupButtonIndices(index) {
41
- const popup = document.querySelector(`[data-popup-target="popup"][data-popup-id="repeater-buttons-${this.idValue}"]`)
42
- const buttons = popup.querySelectorAll('a')
43
- buttons.forEach((button) => {
44
- button.dataset.rowIndex = index
45
- })
46
- }
47
-
48
- addRow(event) {
49
- event.preventDefault()
50
- const button = event.target
51
- const templateName = button.dataset.templateName
52
- let rowIndex = button.dataset.rowIndex
53
-
54
- // Prepare html from template
55
- const template = this.getTemplate(templateName)
56
- const html = this.replaceIdsWithTimestamps(template)
57
-
58
- // Fallback to last row if no index is set
59
- if (rowIndex) {
60
- // Insert new row after defined row
61
- const row = this.rowTargets[rowIndex]
62
- row.insertAdjacentHTML('afterend', html)
63
- } else {
64
- // Insert before footer
65
- this.footerTarget.insertAdjacentHTML('beforebegin', html)
66
- }
67
-
68
- // Dispatch an event
69
- document.dispatchEvent(new CustomEvent('headmin:reinit', {bubbles: true}))
70
-
71
- this.resetIndices()
72
- this.resetPositions()
73
- this.toggleEmpty()
74
- }
75
-
76
- removeRow(event) {
77
- event.preventDefault()
78
-
79
- const row = event.target.closest(".repeater-row")
80
-
81
- if (row.dataset.newRecord === "true") {
82
- // New records are simply removed from the page
83
- row.remove()
84
- } else {
85
- // Existing records are hidden and flagged for deletion
86
- row.querySelector("input[name*='_destroy']").value = 1
87
- row.style.display = 'none'
88
- }
89
-
9
+ }
10
+
11
+ static get targets () {
12
+ return ['repeater', 'footer', 'template', 'row', 'list', 'empty', 'addButton']
13
+ }
14
+
15
+ connect () {
16
+ Sortable.create(this.listTarget, {
17
+ animation: 150,
18
+ ghostClass: 'list-group-item-dark',
19
+ draggable: '.repeater-row',
20
+ handle: '.repeater-row-handle',
21
+ onEnd: () => {
90
22
  this.resetIndices()
91
23
  this.resetPositions()
92
- this.toggleEmpty()
24
+ }
25
+ })
26
+
27
+ this.toggleEmpty()
28
+ }
29
+
30
+ resetButtonIndices (event) {
31
+ const row = event.target.closest('.repeater-row')
32
+ const index = this.containsRow(row) ? row.dataset.rowIndex : ''
33
+ this.updatePopupButtonIndices(index)
34
+ }
35
+
36
+ containsRow (row) {
37
+ return this.rowTargets.includes(row)
38
+ }
39
+
40
+ updatePopupButtonIndices (index) {
41
+ const popup = document.querySelector(`[data-popup-target="popup"][data-popup-id="repeater-buttons-${this.idValue}"]`)
42
+ const buttons = popup.querySelectorAll('a')
43
+ buttons.forEach((button) => {
44
+ button.dataset.rowIndex = index
45
+ })
46
+ }
47
+
48
+ addRow (event) {
49
+ event.preventDefault()
50
+ const button = event.target
51
+ const templateName = button.dataset.templateName
52
+ const rowIndex = button.dataset.rowIndex
53
+
54
+ // Prepare html from template
55
+ const template = this.getTemplate(templateName)
56
+ const html = this.replaceIdsWithTimestamps(template)
57
+
58
+ // Fallback to last row if no index is set
59
+ if (rowIndex) {
60
+ // Insert new row after defined row
61
+ const row = this.rowTargets[rowIndex]
62
+ row.insertAdjacentHTML('afterend', html)
63
+ } else {
64
+ // Insert before footer
65
+ this.footerTarget.insertAdjacentHTML('beforebegin', html)
93
66
  }
94
67
 
95
- getTemplate(name) {
96
- return this.templateTargets.filter((template) => {
97
- return template.dataset.templateName === name
98
- })[0]
99
- }
68
+ this.resetIndices()
69
+ this.resetPositions()
70
+ this.toggleEmpty()
71
+ }
100
72
 
101
- replaceIdsWithTimestamps(template) {
102
- const regex = new RegExp(template.dataset.templateIdRegex, "g")
103
- return template.innerHTML.replace(regex, new Date().getTime())
104
- }
105
-
106
- visibleRowsCount() {
107
- return this.visibleRows().length
108
- }
73
+ removeRow (event) {
74
+ event.preventDefault()
109
75
 
110
- visibleRows() {
111
- const rows = this.rowTargets
112
- return rows.filter((row) => {
113
- return row.querySelector("input[name*='_destroy']").value !== '1'
114
- })
115
- }
116
-
117
- toggleEmpty() {
118
- if (this.visibleRowsCount() > 0) {
119
- this.emptyTarget.classList.add('invisible')
120
- } else {
121
- this.emptyTarget.classList.remove('invisible')
122
- }
123
- }
76
+ const row = event.target.closest('.repeater-row')
124
77
 
125
- resetPositions() {
126
- this.visibleRows().forEach((row, index) => {
127
- const positionInput = row.querySelector("input[name*='position']")
128
- if (positionInput) {
129
- positionInput.value = index
130
- }
131
- })
78
+ if (row.dataset.newRecord === 'true') {
79
+ // New records are simply removed from the page
80
+ row.remove()
81
+ } else {
82
+ // Existing records are hidden and flagged for deletion
83
+ row.querySelector("input[name*='_destroy']").value = 1
84
+ row.style.display = 'none'
132
85
  }
133
86
 
134
- resetIndices() {
135
- this.visibleRows().forEach((row, index) => {
136
- row.dataset.rowIndex = index
137
- })
87
+ this.resetIndices()
88
+ this.resetPositions()
89
+ this.toggleEmpty()
90
+ }
91
+
92
+ getTemplate (name) {
93
+ return this.templateTargets.filter((template) => {
94
+ return template.dataset.templateName === name
95
+ })[0]
96
+ }
97
+
98
+ replaceIdsWithTimestamps (template) {
99
+ const regex = new RegExp(template.dataset.templateIdRegex, 'g')
100
+ return template.innerHTML.replace(regex, new Date().getTime())
101
+ }
102
+
103
+ visibleRowsCount () {
104
+ return this.visibleRows().length
105
+ }
106
+
107
+ visibleRows () {
108
+ const rows = this.rowTargets
109
+ return rows.filter((row) => {
110
+ return row.querySelector("input[name*='_destroy']").value !== '1'
111
+ })
112
+ }
113
+
114
+ toggleEmpty () {
115
+ if (this.visibleRowsCount() > 0) {
116
+ this.emptyTarget.classList.add('invisible')
117
+ } else {
118
+ this.emptyTarget.classList.remove('invisible')
138
119
  }
120
+ }
121
+
122
+ resetPositions () {
123
+ this.visibleRows().forEach((row, index) => {
124
+ const positionInput = row.querySelector("input[name*='position']")
125
+ if (positionInput) {
126
+ positionInput.value = index
127
+ }
128
+ })
129
+ }
130
+
131
+ resetIndices () {
132
+ this.visibleRows().forEach((row, index) => {
133
+ row.dataset.rowIndex = index
134
+ })
135
+ }
139
136
  }