formstrap 0.2.1 → 0.3.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 +4 -4
- data/README.md +1 -0
- data/app/assets/javascripts/formstrap/controllers/nested_preview_controller.js +194 -0
- data/app/assets/javascripts/formstrap/controllers/preview_controller.js +65 -0
- data/app/assets/javascripts/formstrap/controllers/repeater_controller.js +39 -7
- data/app/assets/javascripts/formstrap/index.js +4 -0
- data/app/assets/javascripts/formstrap.js +219 -7
- data/app/assets/stylesheets/formstrap/shared/nested_preview.scss +31 -0
- data/app/assets/stylesheets/formstrap/shared.scss +1 -0
- data/app/assets/stylesheets/formstrap.css +31 -0
- data/app/views/formstrap/_button.html.erb +0 -0
- data/app/views/formstrap/_repeater.html.erb +40 -22
- data/app/views/formstrap/repeater/_row.html.erb +20 -4
- data/app/views/formstrap/shared/_nested_preview.html.erb +35 -0
- data/config/locales/de.yml +5 -0
- data/config/locales/en.yml +1 -5
- data/config/locales/formstrap/de.yml +48 -0
- data/config/locales/formstrap/en.yml +48 -0
- data/config/locales/formstrap/fr.yml +48 -0
- data/config/locales/formstrap/nl.yml +48 -0
- data/config/locales/fr.yml +5 -0
- data/config/locales/nl.yml +1 -5
- data/lib/formstrap/form_builder.rb +15 -0
- data/lib/formstrap/version.rb +1 -1
- data/package.json +1 -1
- metadata +13 -8
- data/config/locales/formstrap/forms/en.yml +0 -25
- data/config/locales/formstrap/forms/nl.yml +0 -25
- data/config/locales/formstrap/media/en.yml +0 -16
- data/config/locales/formstrap/media/nl.yml +0 -16
- data/config/locales/formstrap/thumbnail/en.yml +0 -4
- data/config/locales/formstrap/thumbnail/nl.yml +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2d52dd192e41d84f75bc6be46186bd8a5739ab34f72b8a03a7f57a2518024356
|
4
|
+
data.tar.gz: 21985d08ac6f4837e06385a0de891e0785366322f8504a537f22a89d397903aa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d4e86b30d1f485050c98c72ef426d3590624544e86675b41efe409382595d8f121878b437cf5fdb7ff8dc7aef71dd11c5f689b3bae73d805d1011f6c68cd965b
|
7
|
+
data.tar.gz: 1f04bdc357480984893120ef11166a2c62ade2e80ad793d49814a550f5e35099fd559ca05f57070ce51a166691a1c4c9a916fedd357b29f38c41b563e3c15601
|
data/README.md
CHANGED
@@ -72,6 +72,7 @@ An overview of all the Formstrap / Ruby on Rails form helpers:
|
|
72
72
|
| Textarea | textarea | textarea formstrap: false or text_area |
|
73
73
|
| URL | url | url formstrap: false or url_field |
|
74
74
|
| WYSIWYG * | wysiwyg | N/A |
|
75
|
+
| Repeater | repeater_for | Adds advanced features to fields_for |
|
75
76
|
|
76
77
|
\* Formstrap provides the implementation of these 3rd party libraries, however it is up to the user to provide the
|
77
78
|
correct assets.
|
@@ -0,0 +1,194 @@
|
|
1
|
+
import { Controller } from '@hotwired/stimulus'
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
static get targets () {
|
5
|
+
return ['fields', 'iframeWrapper', 'iframe', 'offcanvas', 'error', 'loader']
|
6
|
+
}
|
7
|
+
|
8
|
+
static get values () {
|
9
|
+
return {
|
10
|
+
url: String
|
11
|
+
}
|
12
|
+
}
|
13
|
+
|
14
|
+
connect () {
|
15
|
+
this.prepareIframe()
|
16
|
+
|
17
|
+
// Resize iFrame after content is loaded
|
18
|
+
this.iframeTarget.addEventListener('load', () => {
|
19
|
+
this.hideLoader()
|
20
|
+
this.resizeIframe()
|
21
|
+
this.autoResizeIframe()
|
22
|
+
})
|
23
|
+
|
24
|
+
// Offcanvas closes
|
25
|
+
this.offcanvasTarget.addEventListener('hide.bs.offcanvas', (event) => {
|
26
|
+
if (!this.update()) {
|
27
|
+
event.preventDefault()
|
28
|
+
}
|
29
|
+
})
|
30
|
+
}
|
31
|
+
|
32
|
+
autoResizeIframe () {
|
33
|
+
// eslint-disable-next-line no-undef
|
34
|
+
const observer = new MutationObserver((mutations) => {
|
35
|
+
mutations.forEach((mutation) => {
|
36
|
+
this.resizeIframe()
|
37
|
+
})
|
38
|
+
})
|
39
|
+
|
40
|
+
// Target the body of the iframe for observing
|
41
|
+
const innerDoc = this.iframeTarget.contentWindow.document
|
42
|
+
observer.observe(innerDoc.body, {
|
43
|
+
childList: true, // Listen for additions/removals of child nodes
|
44
|
+
subtree: true // Listen for changes in the whole subtree
|
45
|
+
})
|
46
|
+
}
|
47
|
+
|
48
|
+
showLoader () {
|
49
|
+
this.loaderTarget.classList.remove('d-none')
|
50
|
+
}
|
51
|
+
|
52
|
+
hideLoader () {
|
53
|
+
this.loaderTarget.classList.add('d-none')
|
54
|
+
}
|
55
|
+
|
56
|
+
showError () {
|
57
|
+
this.errorTarget.classList.remove('d-none')
|
58
|
+
}
|
59
|
+
|
60
|
+
hideError () {
|
61
|
+
this.errorTarget.classList.add('d-none')
|
62
|
+
}
|
63
|
+
|
64
|
+
update () {
|
65
|
+
// Validate fields
|
66
|
+
const isValid = this.validateFields()
|
67
|
+
|
68
|
+
if (isValid) {
|
69
|
+
this.requestPreview()
|
70
|
+
this.hideError()
|
71
|
+
return true
|
72
|
+
} else {
|
73
|
+
this.showError()
|
74
|
+
return false
|
75
|
+
}
|
76
|
+
}
|
77
|
+
|
78
|
+
requestPreview () {
|
79
|
+
// Create an AJAX request
|
80
|
+
// eslint-disable-next-line no-undef
|
81
|
+
const xhr = new XMLHttpRequest()
|
82
|
+
xhr.open('POST', this.urlValue, true)
|
83
|
+
|
84
|
+
// Submit the form data
|
85
|
+
const formData = this.buildFormData()
|
86
|
+
xhr.send(formData)
|
87
|
+
|
88
|
+
// Show loader
|
89
|
+
this.showLoader()
|
90
|
+
|
91
|
+
// Handle the request once it's done
|
92
|
+
xhr.onreadystatechange = () => {
|
93
|
+
if (xhr.readyState === XMLHttpRequest.DONE) {
|
94
|
+
this.hideLoader()
|
95
|
+
this.handleRequest(xhr)
|
96
|
+
}
|
97
|
+
}
|
98
|
+
}
|
99
|
+
|
100
|
+
handleRequest (request) {
|
101
|
+
// Handle the response
|
102
|
+
if (request.status === 200) {
|
103
|
+
this.updatePreview(request.responseText)
|
104
|
+
} else {
|
105
|
+
this.showError()
|
106
|
+
}
|
107
|
+
}
|
108
|
+
|
109
|
+
validateFields () {
|
110
|
+
let allValid = true
|
111
|
+
const fields = this.fieldsTarget
|
112
|
+
const formElements = fields.querySelectorAll('input[name], select[name], textarea[name]')
|
113
|
+
formElements.forEach(function (element) {
|
114
|
+
const isValid = element.reportValidity()
|
115
|
+
if (!isValid) {
|
116
|
+
allValid = false
|
117
|
+
}
|
118
|
+
})
|
119
|
+
return allValid
|
120
|
+
}
|
121
|
+
|
122
|
+
buildFormData () {
|
123
|
+
// Get fields
|
124
|
+
const fields = this.fieldsTarget
|
125
|
+
|
126
|
+
// Build FormData
|
127
|
+
const formData = new FormData()
|
128
|
+
|
129
|
+
// Replace all occurrences of "page[blocks_attributes][0]" with "block"
|
130
|
+
const regex = /\w+\[([^\]]+)s_attributes\]\[\d+\]/g
|
131
|
+
const formElements = fields.querySelectorAll('input[name]:not([name$="[id]"]), select[name]:not([name$="[id]"]), textarea[name]:not([name$="[id]"]), button[name]:not([name$="[id]"])')
|
132
|
+
formElements.forEach(function (element) {
|
133
|
+
const currentName = element.getAttribute('name')
|
134
|
+
const newName = currentName.replace(regex, '$1')
|
135
|
+
formData.append(newName, element.value)
|
136
|
+
})
|
137
|
+
|
138
|
+
// Add authenticity token
|
139
|
+
formData.append('authenticity_token', this.getAuthenticityToken())
|
140
|
+
|
141
|
+
return formData
|
142
|
+
}
|
143
|
+
|
144
|
+
// Prepare the iFrame for rendering
|
145
|
+
// Objective: render the iframe content at the scale of the browser window, but resize it to fit the preview container
|
146
|
+
prepareIframe () {
|
147
|
+
const scaleFactor = this.scaleFactor()
|
148
|
+
const style = `
|
149
|
+
transform: scale(${scaleFactor});
|
150
|
+
opacity: 0;
|
151
|
+
transform-origin: 0 0;
|
152
|
+
width: ${100 / scaleFactor}%;
|
153
|
+
`
|
154
|
+
this.iframeTarget.setAttribute('style', style)
|
155
|
+
}
|
156
|
+
|
157
|
+
// Relative size of the preview container compared to the browser window
|
158
|
+
scaleFactor () {
|
159
|
+
const width = this.iframeWrapperTarget.getBoundingClientRect().width
|
160
|
+
const viewportWidth = window.innerWidth
|
161
|
+
return parseFloat((width / viewportWidth).toFixed(1))
|
162
|
+
}
|
163
|
+
|
164
|
+
// Replace the body of the iframe with the new content
|
165
|
+
updatePreview (html) {
|
166
|
+
this.iframeTarget.contentWindow.document.body.innerHTML = html
|
167
|
+
this.resizeIframe()
|
168
|
+
}
|
169
|
+
|
170
|
+
// Dynamically resize the iFrame to fit its content
|
171
|
+
resizeIframe () {
|
172
|
+
const scaleFactor = this.scaleFactor()
|
173
|
+
const iframeContentHeight = this.iFrameContentHeight()
|
174
|
+
const iframeHeight = iframeContentHeight * scaleFactor
|
175
|
+
|
176
|
+
this.iframeTarget.style.height = `${iframeContentHeight.toFixed()}px`
|
177
|
+
this.iframeTarget.style.opacity = 1
|
178
|
+
this.iframeWrapperTarget.style.height = `${iframeHeight.toFixed()}px`
|
179
|
+
}
|
180
|
+
|
181
|
+
iFrameContentHeight () {
|
182
|
+
const firstElement = this.iframeTarget.contentWindow.document.body.firstElementChild
|
183
|
+
const firstElementStyle = window.getComputedStyle(firstElement)
|
184
|
+
|
185
|
+
const height = firstElement.scrollHeight
|
186
|
+
const margins = parseInt(firstElementStyle.marginTop) + parseInt(firstElementStyle.marginBottom)
|
187
|
+
return height + margins
|
188
|
+
}
|
189
|
+
|
190
|
+
getAuthenticityToken () {
|
191
|
+
const tokenTag = document.querySelector('meta[name="csrf-token"]')
|
192
|
+
return tokenTag.getAttribute('content')
|
193
|
+
}
|
194
|
+
}
|
@@ -0,0 +1,65 @@
|
|
1
|
+
import { Controller } from '@hotwired/stimulus'
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
static values = {
|
5
|
+
url: String
|
6
|
+
}
|
7
|
+
|
8
|
+
connect () {
|
9
|
+
this.button = this.element
|
10
|
+
this.button.addEventListener('click', (event) => {
|
11
|
+
event.preventDefault()
|
12
|
+
this.requestPreview()
|
13
|
+
})
|
14
|
+
}
|
15
|
+
|
16
|
+
requestPreview () {
|
17
|
+
const form = this.buildFakeForm()
|
18
|
+
|
19
|
+
// Insert in DOM
|
20
|
+
document.body.appendChild(form)
|
21
|
+
|
22
|
+
// Submit form
|
23
|
+
form.submit()
|
24
|
+
|
25
|
+
// Remove from DOM
|
26
|
+
document.body.removeChild(form)
|
27
|
+
}
|
28
|
+
|
29
|
+
buildFakeForm () {
|
30
|
+
const form = this.form().cloneNode(true)
|
31
|
+
|
32
|
+
// Empty [id] fields
|
33
|
+
const idInputs = form.querySelectorAll('input[name$="[id]"], select[name$="[id]"], textarea[name$="[id]"], button[name$="[id]"]')
|
34
|
+
idInputs.forEach((input) => {
|
35
|
+
input.value = ''
|
36
|
+
})
|
37
|
+
|
38
|
+
// Set preview action
|
39
|
+
form.setAttribute('action', this.urlValue)
|
40
|
+
|
41
|
+
// Set target to blank
|
42
|
+
form.setAttribute('target', '_blank')
|
43
|
+
|
44
|
+
// Refresh authenticity token
|
45
|
+
const authenticityTokenInput = form.querySelector('input[name="authenticity_token"]')
|
46
|
+
authenticityTokenInput.value = this.getAuthenticityToken()
|
47
|
+
|
48
|
+
// Remove method input if present (to force POST)
|
49
|
+
form.querySelector('input[name="_method"]')?.remove()
|
50
|
+
|
51
|
+
// Ensure POST method
|
52
|
+
form.setAttribute('method', 'POST')
|
53
|
+
|
54
|
+
return form
|
55
|
+
}
|
56
|
+
|
57
|
+
getAuthenticityToken () {
|
58
|
+
const tokenTag = document.querySelector('meta[name="csrf-token"]')
|
59
|
+
return tokenTag.getAttribute('content')
|
60
|
+
}
|
61
|
+
|
62
|
+
form () {
|
63
|
+
return this.button.closest('form')
|
64
|
+
}
|
65
|
+
}
|
@@ -39,7 +39,7 @@ export default class extends Controller {
|
|
39
39
|
|
40
40
|
updatePopupButtonIndices (index) {
|
41
41
|
const popup = document.querySelector(`[data-popup-target="popup"][data-popup-id="repeater-buttons-${this.idValue}"]`)
|
42
|
-
const buttons = popup.querySelectorAll('
|
42
|
+
const buttons = popup.querySelectorAll('[data-popup-target="button"]')
|
43
43
|
buttons.forEach((button) => {
|
44
44
|
button.dataset.rowIndex = index
|
45
45
|
})
|
@@ -52,17 +52,17 @@ export default class extends Controller {
|
|
52
52
|
const rowIndex = button.dataset.rowIndex
|
53
53
|
|
54
54
|
// Prepare html from template
|
55
|
-
|
56
|
-
|
55
|
+
let template = this.getTemplate(templateName).content.cloneNode(true)
|
56
|
+
template = this.replaceIdsWithTimestamps(template)
|
57
57
|
|
58
58
|
// Fallback to last row if no index is set
|
59
59
|
if (rowIndex) {
|
60
60
|
// Insert new row after defined row
|
61
61
|
const row = this.rowTargets[rowIndex]
|
62
|
-
|
62
|
+
this.listTarget.insertBefore(template, row.nextSibling)
|
63
63
|
} else {
|
64
64
|
// Insert before footer
|
65
|
-
this.
|
65
|
+
this.listTarget.insertBefore(template, this.footerTarget)
|
66
66
|
}
|
67
67
|
|
68
68
|
this.resetIndices()
|
@@ -108,8 +108,40 @@ export default class extends Controller {
|
|
108
108
|
}
|
109
109
|
|
110
110
|
replaceIdsWithTimestamps (template) {
|
111
|
-
const
|
112
|
-
|
111
|
+
const pattern = 'rrrrrrrrr'
|
112
|
+
const replacement = new Date().getTime().toString()
|
113
|
+
|
114
|
+
// Replace ids
|
115
|
+
template.querySelectorAll(`input[id*="${pattern}"], select[id*="${pattern}"], textarea[id*="${pattern}"], button[id*="${pattern}"]`).forEach((node) => {
|
116
|
+
const idValue = node.getAttribute('id')
|
117
|
+
node.setAttribute('id', idValue.replace(pattern, replacement))
|
118
|
+
})
|
119
|
+
|
120
|
+
// Replace labels
|
121
|
+
template.querySelectorAll(`label[for*="${pattern}"]`).forEach((node) => {
|
122
|
+
const forValue = node.getAttribute('for')
|
123
|
+
node.setAttribute('for', forValue.replace(pattern, replacement))
|
124
|
+
})
|
125
|
+
|
126
|
+
// Replace names
|
127
|
+
template.querySelectorAll(`input[name*="${pattern}"], select[name*="${pattern}"], textarea[name*="${pattern}"], button[name*="${pattern}"]`).forEach((node) => {
|
128
|
+
const nameValue = node.getAttribute('name')
|
129
|
+
node.setAttribute('name', nameValue.replace(pattern, replacement))
|
130
|
+
})
|
131
|
+
|
132
|
+
// Replace offcanvas targets
|
133
|
+
template.querySelectorAll(`div[data-bs-target="#offcanvas-${pattern}"]`).forEach((node) => {
|
134
|
+
const targetValue = node.getAttribute('data-bs-target')
|
135
|
+
node.setAttribute('data-bs-target', targetValue.replace(pattern, replacement))
|
136
|
+
})
|
137
|
+
|
138
|
+
// Replace offcanvas ids
|
139
|
+
template.querySelectorAll(`.offcanvas[id="offcanvas-${pattern}"]`).forEach((node) => {
|
140
|
+
const idValue = node.getAttribute('id')
|
141
|
+
node.setAttribute('id', idValue.replace(pattern, replacement))
|
142
|
+
})
|
143
|
+
|
144
|
+
return template
|
113
145
|
}
|
114
146
|
|
115
147
|
visibleRowsCount () {
|
@@ -8,7 +8,9 @@ import FlatpickrController from './controllers/flatpickr_controller'
|
|
8
8
|
import InfiniteScrollerController from './controllers/infinite_scroller_controller'
|
9
9
|
import MediaController from './controllers/media_controller'
|
10
10
|
import MediaModalController from './controllers/media_modal_controller'
|
11
|
+
import NestedPreviewController from './controllers/nested_preview_controller'
|
11
12
|
import PopupController from './controllers/popup_controller'
|
13
|
+
import PreviewController from './controllers/preview_controller'
|
12
14
|
import RedactorxController from './controllers/redactorx_controller'
|
13
15
|
import RepeaterController from './controllers/repeater_controller'
|
14
16
|
import SelectController from './controllers/select_controller'
|
@@ -25,7 +27,9 @@ export class Formstrap {
|
|
25
27
|
Stimulus.register('infinite-scroller', InfiniteScrollerController)
|
26
28
|
Stimulus.register('media', MediaController)
|
27
29
|
Stimulus.register('media-modal', MediaModalController)
|
30
|
+
Stimulus.register('nested-preview', NestedPreviewController)
|
28
31
|
Stimulus.register('popup', PopupController)
|
32
|
+
Stimulus.register('preview', PreviewController)
|
29
33
|
Stimulus.register('redactorx', RedactorxController)
|
30
34
|
Stimulus.register('repeater', RepeaterController)
|
31
35
|
Stimulus.register('select', SelectController)
|
@@ -4,6 +4,7 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
5
5
|
var __getProtoOf = Object.getPrototypeOf;
|
6
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
7
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
7
8
|
var __commonJS = (cb, mod) => function __require() {
|
8
9
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
9
10
|
};
|
@@ -19,6 +20,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
19
20
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
20
21
|
mod
|
21
22
|
));
|
23
|
+
var __publicField = (obj, key, value) => {
|
24
|
+
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
25
|
+
return value;
|
26
|
+
};
|
22
27
|
|
23
28
|
// node_modules/tom-select/dist/js/tom-select.complete.js
|
24
29
|
var require_tom_select_complete = __commonJS({
|
@@ -11288,6 +11293,149 @@ var media_modal_controller_default = class extends Controller {
|
|
11288
11293
|
}
|
11289
11294
|
};
|
11290
11295
|
|
11296
|
+
// app/assets/javascripts/formstrap/controllers/nested_preview_controller.js
|
11297
|
+
var nested_preview_controller_default = class extends Controller {
|
11298
|
+
static get targets() {
|
11299
|
+
return ["fields", "iframeWrapper", "iframe", "offcanvas", "error", "loader"];
|
11300
|
+
}
|
11301
|
+
static get values() {
|
11302
|
+
return {
|
11303
|
+
url: String
|
11304
|
+
};
|
11305
|
+
}
|
11306
|
+
connect() {
|
11307
|
+
this.prepareIframe();
|
11308
|
+
this.iframeTarget.addEventListener("load", () => {
|
11309
|
+
this.hideLoader();
|
11310
|
+
this.resizeIframe();
|
11311
|
+
this.autoResizeIframe();
|
11312
|
+
});
|
11313
|
+
this.offcanvasTarget.addEventListener("hide.bs.offcanvas", (event) => {
|
11314
|
+
if (!this.update()) {
|
11315
|
+
event.preventDefault();
|
11316
|
+
}
|
11317
|
+
});
|
11318
|
+
}
|
11319
|
+
autoResizeIframe() {
|
11320
|
+
const observer = new MutationObserver((mutations) => {
|
11321
|
+
mutations.forEach((mutation) => {
|
11322
|
+
this.resizeIframe();
|
11323
|
+
});
|
11324
|
+
});
|
11325
|
+
const innerDoc = this.iframeTarget.contentWindow.document;
|
11326
|
+
observer.observe(innerDoc.body, {
|
11327
|
+
childList: true,
|
11328
|
+
subtree: true
|
11329
|
+
});
|
11330
|
+
}
|
11331
|
+
showLoader() {
|
11332
|
+
this.loaderTarget.classList.remove("d-none");
|
11333
|
+
}
|
11334
|
+
hideLoader() {
|
11335
|
+
this.loaderTarget.classList.add("d-none");
|
11336
|
+
}
|
11337
|
+
showError() {
|
11338
|
+
this.errorTarget.classList.remove("d-none");
|
11339
|
+
}
|
11340
|
+
hideError() {
|
11341
|
+
this.errorTarget.classList.add("d-none");
|
11342
|
+
}
|
11343
|
+
update() {
|
11344
|
+
const isValid = this.validateFields();
|
11345
|
+
if (isValid) {
|
11346
|
+
this.requestPreview();
|
11347
|
+
this.hideError();
|
11348
|
+
return true;
|
11349
|
+
} else {
|
11350
|
+
this.showError();
|
11351
|
+
return false;
|
11352
|
+
}
|
11353
|
+
}
|
11354
|
+
requestPreview() {
|
11355
|
+
const xhr = new XMLHttpRequest();
|
11356
|
+
xhr.open("POST", this.urlValue, true);
|
11357
|
+
const formData = this.buildFormData();
|
11358
|
+
xhr.send(formData);
|
11359
|
+
this.showLoader();
|
11360
|
+
xhr.onreadystatechange = () => {
|
11361
|
+
if (xhr.readyState === XMLHttpRequest.DONE) {
|
11362
|
+
this.hideLoader();
|
11363
|
+
this.handleRequest(xhr);
|
11364
|
+
}
|
11365
|
+
};
|
11366
|
+
}
|
11367
|
+
handleRequest(request) {
|
11368
|
+
if (request.status === 200) {
|
11369
|
+
this.updatePreview(request.responseText);
|
11370
|
+
} else {
|
11371
|
+
this.showError();
|
11372
|
+
}
|
11373
|
+
}
|
11374
|
+
validateFields() {
|
11375
|
+
let allValid = true;
|
11376
|
+
const fields = this.fieldsTarget;
|
11377
|
+
const formElements = fields.querySelectorAll("input[name], select[name], textarea[name]");
|
11378
|
+
formElements.forEach(function(element) {
|
11379
|
+
const isValid = element.reportValidity();
|
11380
|
+
if (!isValid) {
|
11381
|
+
allValid = false;
|
11382
|
+
}
|
11383
|
+
});
|
11384
|
+
return allValid;
|
11385
|
+
}
|
11386
|
+
buildFormData() {
|
11387
|
+
const fields = this.fieldsTarget;
|
11388
|
+
const formData = new FormData();
|
11389
|
+
const regex = /\w+\[([^\]]+)s_attributes\]\[\d+\]/g;
|
11390
|
+
const formElements = fields.querySelectorAll('input[name]:not([name$="[id]"]), select[name]:not([name$="[id]"]), textarea[name]:not([name$="[id]"]), button[name]:not([name$="[id]"])');
|
11391
|
+
formElements.forEach(function(element) {
|
11392
|
+
const currentName = element.getAttribute("name");
|
11393
|
+
const newName = currentName.replace(regex, "$1");
|
11394
|
+
formData.append(newName, element.value);
|
11395
|
+
});
|
11396
|
+
formData.append("authenticity_token", this.getAuthenticityToken());
|
11397
|
+
return formData;
|
11398
|
+
}
|
11399
|
+
prepareIframe() {
|
11400
|
+
const scaleFactor = this.scaleFactor();
|
11401
|
+
const style = `
|
11402
|
+
transform: scale(${scaleFactor});
|
11403
|
+
opacity: 0;
|
11404
|
+
transform-origin: 0 0;
|
11405
|
+
width: ${100 / scaleFactor}%;
|
11406
|
+
`;
|
11407
|
+
this.iframeTarget.setAttribute("style", style);
|
11408
|
+
}
|
11409
|
+
scaleFactor() {
|
11410
|
+
const width = this.iframeWrapperTarget.getBoundingClientRect().width;
|
11411
|
+
const viewportWidth = window.innerWidth;
|
11412
|
+
return parseFloat((width / viewportWidth).toFixed(1));
|
11413
|
+
}
|
11414
|
+
updatePreview(html) {
|
11415
|
+
this.iframeTarget.contentWindow.document.body.innerHTML = html;
|
11416
|
+
this.resizeIframe();
|
11417
|
+
}
|
11418
|
+
resizeIframe() {
|
11419
|
+
const scaleFactor = this.scaleFactor();
|
11420
|
+
const iframeContentHeight = this.iFrameContentHeight();
|
11421
|
+
const iframeHeight = iframeContentHeight * scaleFactor;
|
11422
|
+
this.iframeTarget.style.height = `${iframeContentHeight.toFixed()}px`;
|
11423
|
+
this.iframeTarget.style.opacity = 1;
|
11424
|
+
this.iframeWrapperTarget.style.height = `${iframeHeight.toFixed()}px`;
|
11425
|
+
}
|
11426
|
+
iFrameContentHeight() {
|
11427
|
+
const firstElement = this.iframeTarget.contentWindow.document.body.firstElementChild;
|
11428
|
+
const firstElementStyle = window.getComputedStyle(firstElement);
|
11429
|
+
const height = firstElement.scrollHeight;
|
11430
|
+
const margins = parseInt(firstElementStyle.marginTop) + parseInt(firstElementStyle.marginBottom);
|
11431
|
+
return height + margins;
|
11432
|
+
}
|
11433
|
+
getAuthenticityToken() {
|
11434
|
+
const tokenTag = document.querySelector('meta[name="csrf-token"]');
|
11435
|
+
return tokenTag.getAttribute("content");
|
11436
|
+
}
|
11437
|
+
};
|
11438
|
+
|
11291
11439
|
// node_modules/@popperjs/core/lib/enums.js
|
11292
11440
|
var top = "top";
|
11293
11441
|
var bottom = "bottom";
|
@@ -12917,6 +13065,47 @@ var popup_controller_default = class extends Controller {
|
|
12917
13065
|
}
|
12918
13066
|
};
|
12919
13067
|
|
13068
|
+
// app/assets/javascripts/formstrap/controllers/preview_controller.js
|
13069
|
+
var preview_controller_default = class extends Controller {
|
13070
|
+
connect() {
|
13071
|
+
this.button = this.element;
|
13072
|
+
this.button.addEventListener("click", (event) => {
|
13073
|
+
event.preventDefault();
|
13074
|
+
this.requestPreview();
|
13075
|
+
});
|
13076
|
+
}
|
13077
|
+
requestPreview() {
|
13078
|
+
const form = this.buildFakeForm();
|
13079
|
+
document.body.appendChild(form);
|
13080
|
+
form.submit();
|
13081
|
+
document.body.removeChild(form);
|
13082
|
+
}
|
13083
|
+
buildFakeForm() {
|
13084
|
+
const form = this.form().cloneNode(true);
|
13085
|
+
const idInputs = form.querySelectorAll('input[name$="[id]"], select[name$="[id]"], textarea[name$="[id]"], button[name$="[id]"]');
|
13086
|
+
idInputs.forEach((input) => {
|
13087
|
+
input.value = "";
|
13088
|
+
});
|
13089
|
+
form.setAttribute("action", this.urlValue);
|
13090
|
+
form.setAttribute("target", "_blank");
|
13091
|
+
const authenticityTokenInput = form.querySelector('input[name="authenticity_token"]');
|
13092
|
+
authenticityTokenInput.value = this.getAuthenticityToken();
|
13093
|
+
form.querySelector('input[name="_method"]')?.remove();
|
13094
|
+
form.setAttribute("method", "POST");
|
13095
|
+
return form;
|
13096
|
+
}
|
13097
|
+
getAuthenticityToken() {
|
13098
|
+
const tokenTag = document.querySelector('meta[name="csrf-token"]');
|
13099
|
+
return tokenTag.getAttribute("content");
|
13100
|
+
}
|
13101
|
+
form() {
|
13102
|
+
return this.button.closest("form");
|
13103
|
+
}
|
13104
|
+
};
|
13105
|
+
__publicField(preview_controller_default, "values", {
|
13106
|
+
url: String
|
13107
|
+
});
|
13108
|
+
|
12920
13109
|
// app/assets/javascripts/formstrap/controllers/redactorx_controller.js
|
12921
13110
|
var redactorx_controller_default = class extends Controller {
|
12922
13111
|
connect() {
|
@@ -12980,7 +13169,7 @@ var repeater_controller_default = class extends Controller {
|
|
12980
13169
|
}
|
12981
13170
|
updatePopupButtonIndices(index2) {
|
12982
13171
|
const popup = document.querySelector(`[data-popup-target="popup"][data-popup-id="repeater-buttons-${this.idValue}"]`);
|
12983
|
-
const buttons = popup.querySelectorAll("
|
13172
|
+
const buttons = popup.querySelectorAll('[data-popup-target="button"]');
|
12984
13173
|
buttons.forEach((button) => {
|
12985
13174
|
button.dataset.rowIndex = index2;
|
12986
13175
|
});
|
@@ -12990,13 +13179,13 @@ var repeater_controller_default = class extends Controller {
|
|
12990
13179
|
const button = event.target;
|
12991
13180
|
const templateName = button.dataset.templateName;
|
12992
13181
|
const rowIndex = button.dataset.rowIndex;
|
12993
|
-
|
12994
|
-
|
13182
|
+
let template = this.getTemplate(templateName).content.cloneNode(true);
|
13183
|
+
template = this.replaceIdsWithTimestamps(template);
|
12995
13184
|
if (rowIndex) {
|
12996
13185
|
const row = this.rowTargets[rowIndex];
|
12997
|
-
|
13186
|
+
this.listTarget.insertBefore(template, row.nextSibling);
|
12998
13187
|
} else {
|
12999
|
-
this.
|
13188
|
+
this.listTarget.insertBefore(template, this.footerTarget);
|
13000
13189
|
}
|
13001
13190
|
this.resetIndices();
|
13002
13191
|
this.resetPositions();
|
@@ -13028,8 +13217,29 @@ var repeater_controller_default = class extends Controller {
|
|
13028
13217
|
})[0];
|
13029
13218
|
}
|
13030
13219
|
replaceIdsWithTimestamps(template) {
|
13031
|
-
const
|
13032
|
-
|
13220
|
+
const pattern = "rrrrrrrrr";
|
13221
|
+
const replacement = new Date().getTime().toString();
|
13222
|
+
template.querySelectorAll(`input[id*="${pattern}"], select[id*="${pattern}"], textarea[id*="${pattern}"], button[id*="${pattern}"]`).forEach((node) => {
|
13223
|
+
const idValue = node.getAttribute("id");
|
13224
|
+
node.setAttribute("id", idValue.replace(pattern, replacement));
|
13225
|
+
});
|
13226
|
+
template.querySelectorAll(`label[for*="${pattern}"]`).forEach((node) => {
|
13227
|
+
const forValue = node.getAttribute("for");
|
13228
|
+
node.setAttribute("for", forValue.replace(pattern, replacement));
|
13229
|
+
});
|
13230
|
+
template.querySelectorAll(`input[name*="${pattern}"], select[name*="${pattern}"], textarea[name*="${pattern}"], button[name*="${pattern}"]`).forEach((node) => {
|
13231
|
+
const nameValue = node.getAttribute("name");
|
13232
|
+
node.setAttribute("name", nameValue.replace(pattern, replacement));
|
13233
|
+
});
|
13234
|
+
template.querySelectorAll(`div[data-bs-target="#offcanvas-${pattern}"]`).forEach((node) => {
|
13235
|
+
const targetValue = node.getAttribute("data-bs-target");
|
13236
|
+
node.setAttribute("data-bs-target", targetValue.replace(pattern, replacement));
|
13237
|
+
});
|
13238
|
+
template.querySelectorAll(`.offcanvas[id="offcanvas-${pattern}"]`).forEach((node) => {
|
13239
|
+
const idValue = node.getAttribute("id");
|
13240
|
+
node.setAttribute("id", idValue.replace(pattern, replacement));
|
13241
|
+
});
|
13242
|
+
return template;
|
13033
13243
|
}
|
13034
13244
|
visibleRowsCount() {
|
13035
13245
|
return this.visibleRows().length;
|
@@ -13157,7 +13367,9 @@ var Formstrap = class {
|
|
13157
13367
|
Stimulus.register("infinite-scroller", infinite_scroller_controller_default);
|
13158
13368
|
Stimulus.register("media", media_controller_default);
|
13159
13369
|
Stimulus.register("media-modal", media_modal_controller_default);
|
13370
|
+
Stimulus.register("nested-preview", nested_preview_controller_default);
|
13160
13371
|
Stimulus.register("popup", popup_controller_default);
|
13372
|
+
Stimulus.register("preview", preview_controller_default);
|
13161
13373
|
Stimulus.register("redactorx", redactorx_controller_default);
|
13162
13374
|
Stimulus.register("repeater", repeater_controller_default);
|
13163
13375
|
Stimulus.register("select", select_controller_default);
|
@@ -0,0 +1,31 @@
|
|
1
|
+
.nested-preview-offcanvas, .nested-preview .hiding {
|
2
|
+
display: none !important;
|
3
|
+
|
4
|
+
&.show, &.showing {
|
5
|
+
display: flex !important;
|
6
|
+
}
|
7
|
+
}
|
8
|
+
|
9
|
+
.nested-preview-iframe-wrapper {
|
10
|
+
position: relative;
|
11
|
+
cursor: pointer;
|
12
|
+
|
13
|
+
iframe {
|
14
|
+
width: 100%;
|
15
|
+
}
|
16
|
+
}
|
17
|
+
|
18
|
+
.nested-preview-loader {
|
19
|
+
position: absolute;
|
20
|
+
width: 100%;
|
21
|
+
height: 100%;
|
22
|
+
z-index: 2;
|
23
|
+
top: 0;
|
24
|
+
left: 0;
|
25
|
+
padding: 40px;
|
26
|
+
display: flex;
|
27
|
+
align-items: center;
|
28
|
+
justify-content: center;
|
29
|
+
background: var(--bs-secondary-bg);
|
30
|
+
opacity: 0.5;
|
31
|
+
}
|