formstrap 0.3.0 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/javascripts/formstrap/controllers/media_controller.js +5 -11
- data/app/assets/javascripts/formstrap/controllers/nested_preview_controller.js +32 -6
- data/app/assets/javascripts/formstrap/controllers/repeater_controller.js +6 -0
- data/app/assets/javascripts/formstrap.js +34 -16
- data/app/helpers/application_helper.rb +4 -4
- data/app/models/formstrap/media_view.rb +12 -7
- data/app/models/formstrap/thumbnail_view.rb +2 -2
- data/app/views/formstrap/media/_validation.html.erb +2 -0
- data/app/views/formstrap/shared/_nested_preview.html.erb +2 -2
- data/lib/formstrap/form_builder.rb +1 -1
- data/lib/formstrap/version.rb +1 -1
- data/package.json +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 98d133e0c15d72418662dbe8a98e296a3c701bc97aa9bec3620fa3f0a346ffec
|
4
|
+
data.tar.gz: f50c6dc41f0809c54e6522e14a718b0a6842a419fc3068928a64e156df771408
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f540f4053664dc2095dd165f5e4d6c2c5a7257a7fe810bed486834b3477d5c0888ca4ee9bfc5f0e9ac8685d86e0f97039e5bde5795013a1e80350246580a35d3
|
7
|
+
data.tar.gz: 7318bdcee8bb2ac4a84a079374fb15b8385e1032186e8ce14cbe00b449b7d33e2f012b887ad5fa35773eebf9ad718a58c5cacb43188300dc59f27486804882b0
|
@@ -114,11 +114,11 @@ export default class extends Controller {
|
|
114
114
|
}
|
115
115
|
|
116
116
|
minActiveItems () {
|
117
|
-
return parseInt(this.
|
117
|
+
return parseInt(this.validationInputTarget.dataset.min, 10) || 0
|
118
118
|
}
|
119
119
|
|
120
120
|
maxActiveItems () {
|
121
|
-
return parseInt(this.
|
121
|
+
return parseInt(this.validationInputTarget.dataset.max, 10) || Infinity
|
122
122
|
}
|
123
123
|
|
124
124
|
resetPositions () {
|
@@ -131,13 +131,7 @@ export default class extends Controller {
|
|
131
131
|
}
|
132
132
|
|
133
133
|
addNewItems (items) {
|
134
|
-
const itemTargetIds = this.itemTargets.map((i) => { return parseInt(i.querySelectorAll('input')[1].value) })
|
135
134
|
items.forEach((item) => {
|
136
|
-
if (itemTargetIds.includes(item.blobId)) {
|
137
|
-
// Do not add this item (as it is already present)
|
138
|
-
return
|
139
|
-
}
|
140
|
-
|
141
135
|
this.addItem(item)
|
142
136
|
})
|
143
137
|
}
|
@@ -211,7 +205,7 @@ export default class extends Controller {
|
|
211
205
|
const returnedBlobIds = elements.map((e) => { return e.blobId })
|
212
206
|
|
213
207
|
items.forEach((item) => {
|
214
|
-
const blobId = parseInt(item.querySelectorAll('input')[1].value)
|
208
|
+
const blobId = parseInt(item.querySelectorAll('input')[1].value, 10)
|
215
209
|
if (returnedBlobIds.includes(blobId)) {
|
216
210
|
// Do not delete this one
|
217
211
|
return
|
@@ -237,7 +231,7 @@ export default class extends Controller {
|
|
237
231
|
|
238
232
|
itemByBlobId (blobId) {
|
239
233
|
return this.itemTargets.find((item) => {
|
240
|
-
return item.querySelector('input[name*=\'blob_id\']').value === blobId
|
234
|
+
return parseInt(item.querySelector('input[name*=\'blob_id\']').value, 10) === blobId
|
241
235
|
})
|
242
236
|
}
|
243
237
|
|
@@ -249,7 +243,7 @@ export default class extends Controller {
|
|
249
243
|
|
250
244
|
activeIds () {
|
251
245
|
return this.activeItems().map((item) => {
|
252
|
-
return item.querySelector('input[name$=\'[blob_id]\']').value
|
246
|
+
return parseInt(item.querySelector('input[name$=\'[blob_id]\']').value, 10)
|
253
247
|
})
|
254
248
|
}
|
255
249
|
}
|
@@ -1,3 +1,4 @@
|
|
1
|
+
/* global XMLHttpRequest, MutationObserver */
|
1
2
|
import { Controller } from '@hotwired/stimulus'
|
2
3
|
|
3
4
|
export default class extends Controller {
|
@@ -18,6 +19,7 @@ export default class extends Controller {
|
|
18
19
|
this.iframeTarget.addEventListener('load', () => {
|
19
20
|
this.hideLoader()
|
20
21
|
this.resizeIframe()
|
22
|
+
this.autoResizeIframe()
|
21
23
|
})
|
22
24
|
|
23
25
|
// Offcanvas closes
|
@@ -28,6 +30,21 @@ export default class extends Controller {
|
|
28
30
|
})
|
29
31
|
}
|
30
32
|
|
33
|
+
autoResizeIframe () {
|
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
|
+
|
31
48
|
showLoader () {
|
32
49
|
this.loaderTarget.classList.remove('d-none')
|
33
50
|
}
|
@@ -60,7 +77,6 @@ export default class extends Controller {
|
|
60
77
|
|
61
78
|
requestPreview () {
|
62
79
|
// Create an AJAX request
|
63
|
-
// eslint-disable-next-line no-undef
|
64
80
|
const xhr = new XMLHttpRequest()
|
65
81
|
xhr.open('POST', this.urlValue, true)
|
66
82
|
|
@@ -92,7 +108,7 @@ export default class extends Controller {
|
|
92
108
|
validateFields () {
|
93
109
|
let allValid = true
|
94
110
|
const fields = this.fieldsTarget
|
95
|
-
const formElements = fields.querySelectorAll('input[
|
111
|
+
const formElements = fields.querySelectorAll('input:not([type="hidden"]), select[name], textarea[name]')
|
96
112
|
formElements.forEach(function (element) {
|
97
113
|
const isValid = element.reportValidity()
|
98
114
|
if (!isValid) {
|
@@ -141,7 +157,7 @@ export default class extends Controller {
|
|
141
157
|
scaleFactor () {
|
142
158
|
const width = this.iframeWrapperTarget.getBoundingClientRect().width
|
143
159
|
const viewportWidth = window.innerWidth
|
144
|
-
return (width / viewportWidth).toFixed(1)
|
160
|
+
return parseFloat((width / viewportWidth).toFixed(1))
|
145
161
|
}
|
146
162
|
|
147
163
|
// Replace the body of the iframe with the new content
|
@@ -153,11 +169,21 @@ export default class extends Controller {
|
|
153
169
|
// Dynamically resize the iFrame to fit its content
|
154
170
|
resizeIframe () {
|
155
171
|
const scaleFactor = this.scaleFactor()
|
156
|
-
const iframeContentHeight = this.
|
172
|
+
const iframeContentHeight = this.iFrameContentHeight()
|
157
173
|
const iframeHeight = iframeContentHeight * scaleFactor
|
158
|
-
|
174
|
+
|
175
|
+
this.iframeTarget.style.height = `${iframeContentHeight.toFixed()}px`
|
159
176
|
this.iframeTarget.style.opacity = 1
|
160
|
-
this.iframeWrapperTarget.style.height = iframeHeight
|
177
|
+
this.iframeWrapperTarget.style.height = `${iframeHeight.toFixed()}px`
|
178
|
+
}
|
179
|
+
|
180
|
+
iFrameContentHeight () {
|
181
|
+
const firstElement = this.iframeTarget.contentWindow.document.body.firstElementChild
|
182
|
+
const firstElementStyle = window.getComputedStyle(firstElement)
|
183
|
+
|
184
|
+
const height = firstElement.scrollHeight
|
185
|
+
const margins = parseInt(firstElementStyle.marginTop) + parseInt(firstElementStyle.marginBottom)
|
186
|
+
return height + margins
|
161
187
|
}
|
162
188
|
|
163
189
|
getAuthenticityToken () {
|
@@ -110,6 +110,7 @@ export default class extends Controller {
|
|
110
110
|
replaceIdsWithTimestamps (template) {
|
111
111
|
const pattern = 'rrrrrrrrr'
|
112
112
|
const replacement = new Date().getTime().toString()
|
113
|
+
const regex = new RegExp(pattern, 'g')
|
113
114
|
|
114
115
|
// Replace ids
|
115
116
|
template.querySelectorAll(`input[id*="${pattern}"], select[id*="${pattern}"], textarea[id*="${pattern}"], button[id*="${pattern}"]`).forEach((node) => {
|
@@ -117,6 +118,11 @@ export default class extends Controller {
|
|
117
118
|
node.setAttribute('id', idValue.replace(pattern, replacement))
|
118
119
|
})
|
119
120
|
|
121
|
+
// Search and replace pattern in templates
|
122
|
+
template.querySelectorAll('template').forEach((node) => {
|
123
|
+
node.innerHTML = node.innerHTML.replace(regex, replacement)
|
124
|
+
})
|
125
|
+
|
120
126
|
// Replace labels
|
121
127
|
template.querySelectorAll(`label[for*="${pattern}"]`).forEach((node) => {
|
122
128
|
const forValue = node.getAttribute('for')
|
@@ -11035,10 +11035,10 @@ var media_controller_default = class extends Controller {
|
|
11035
11035
|
}
|
11036
11036
|
}
|
11037
11037
|
minActiveItems() {
|
11038
|
-
return parseInt(this.
|
11038
|
+
return parseInt(this.validationInputTarget.dataset.min, 10) || 0;
|
11039
11039
|
}
|
11040
11040
|
maxActiveItems() {
|
11041
|
-
return parseInt(this.
|
11041
|
+
return parseInt(this.validationInputTarget.dataset.max, 10) || Infinity;
|
11042
11042
|
}
|
11043
11043
|
resetPositions() {
|
11044
11044
|
this.activeItems().forEach((item, index2) => {
|
@@ -11049,13 +11049,7 @@ var media_controller_default = class extends Controller {
|
|
11049
11049
|
});
|
11050
11050
|
}
|
11051
11051
|
addNewItems(items) {
|
11052
|
-
const itemTargetIds = this.itemTargets.map((i) => {
|
11053
|
-
return parseInt(i.querySelectorAll("input")[1].value);
|
11054
|
-
});
|
11055
11052
|
items.forEach((item) => {
|
11056
|
-
if (itemTargetIds.includes(item.blobId)) {
|
11057
|
-
return;
|
11058
|
-
}
|
11059
11053
|
this.addItem(item);
|
11060
11054
|
});
|
11061
11055
|
}
|
@@ -11112,7 +11106,7 @@ var media_controller_default = class extends Controller {
|
|
11112
11106
|
return e.blobId;
|
11113
11107
|
});
|
11114
11108
|
items.forEach((item) => {
|
11115
|
-
const blobId = parseInt(item.querySelectorAll("input")[1].value);
|
11109
|
+
const blobId = parseInt(item.querySelectorAll("input")[1].value, 10);
|
11116
11110
|
if (returnedBlobIds.includes(blobId)) {
|
11117
11111
|
return;
|
11118
11112
|
}
|
@@ -11128,7 +11122,7 @@ var media_controller_default = class extends Controller {
|
|
11128
11122
|
}
|
11129
11123
|
itemByBlobId(blobId) {
|
11130
11124
|
return this.itemTargets.find((item) => {
|
11131
|
-
return item.querySelector("input[name*='blob_id']").value === blobId;
|
11125
|
+
return parseInt(item.querySelector("input[name*='blob_id']").value, 10) === blobId;
|
11132
11126
|
});
|
11133
11127
|
}
|
11134
11128
|
activeItems() {
|
@@ -11138,7 +11132,7 @@ var media_controller_default = class extends Controller {
|
|
11138
11132
|
}
|
11139
11133
|
activeIds() {
|
11140
11134
|
return this.activeItems().map((item) => {
|
11141
|
-
return item.querySelector("input[name$='[blob_id]']").value;
|
11135
|
+
return parseInt(item.querySelector("input[name$='[blob_id]']").value, 10);
|
11142
11136
|
});
|
11143
11137
|
}
|
11144
11138
|
};
|
@@ -11308,6 +11302,7 @@ var nested_preview_controller_default = class extends Controller {
|
|
11308
11302
|
this.iframeTarget.addEventListener("load", () => {
|
11309
11303
|
this.hideLoader();
|
11310
11304
|
this.resizeIframe();
|
11305
|
+
this.autoResizeIframe();
|
11311
11306
|
});
|
11312
11307
|
this.offcanvasTarget.addEventListener("hide.bs.offcanvas", (event) => {
|
11313
11308
|
if (!this.update()) {
|
@@ -11315,6 +11310,18 @@ var nested_preview_controller_default = class extends Controller {
|
|
11315
11310
|
}
|
11316
11311
|
});
|
11317
11312
|
}
|
11313
|
+
autoResizeIframe() {
|
11314
|
+
const observer = new MutationObserver((mutations) => {
|
11315
|
+
mutations.forEach((mutation) => {
|
11316
|
+
this.resizeIframe();
|
11317
|
+
});
|
11318
|
+
});
|
11319
|
+
const innerDoc = this.iframeTarget.contentWindow.document;
|
11320
|
+
observer.observe(innerDoc.body, {
|
11321
|
+
childList: true,
|
11322
|
+
subtree: true
|
11323
|
+
});
|
11324
|
+
}
|
11318
11325
|
showLoader() {
|
11319
11326
|
this.loaderTarget.classList.remove("d-none");
|
11320
11327
|
}
|
@@ -11361,7 +11368,7 @@ var nested_preview_controller_default = class extends Controller {
|
|
11361
11368
|
validateFields() {
|
11362
11369
|
let allValid = true;
|
11363
11370
|
const fields = this.fieldsTarget;
|
11364
|
-
const formElements = fields.querySelectorAll(
|
11371
|
+
const formElements = fields.querySelectorAll('input:not([type="hidden"]), select[name], textarea[name]');
|
11365
11372
|
formElements.forEach(function(element) {
|
11366
11373
|
const isValid = element.reportValidity();
|
11367
11374
|
if (!isValid) {
|
@@ -11396,7 +11403,7 @@ var nested_preview_controller_default = class extends Controller {
|
|
11396
11403
|
scaleFactor() {
|
11397
11404
|
const width = this.iframeWrapperTarget.getBoundingClientRect().width;
|
11398
11405
|
const viewportWidth = window.innerWidth;
|
11399
|
-
return (width / viewportWidth).toFixed(1);
|
11406
|
+
return parseFloat((width / viewportWidth).toFixed(1));
|
11400
11407
|
}
|
11401
11408
|
updatePreview(html) {
|
11402
11409
|
this.iframeTarget.contentWindow.document.body.innerHTML = html;
|
@@ -11404,11 +11411,18 @@ var nested_preview_controller_default = class extends Controller {
|
|
11404
11411
|
}
|
11405
11412
|
resizeIframe() {
|
11406
11413
|
const scaleFactor = this.scaleFactor();
|
11407
|
-
const iframeContentHeight = this.
|
11414
|
+
const iframeContentHeight = this.iFrameContentHeight();
|
11408
11415
|
const iframeHeight = iframeContentHeight * scaleFactor;
|
11409
|
-
this.iframeTarget.style.height = iframeContentHeight
|
11416
|
+
this.iframeTarget.style.height = `${iframeContentHeight.toFixed()}px`;
|
11410
11417
|
this.iframeTarget.style.opacity = 1;
|
11411
|
-
this.iframeWrapperTarget.style.height = iframeHeight
|
11418
|
+
this.iframeWrapperTarget.style.height = `${iframeHeight.toFixed()}px`;
|
11419
|
+
}
|
11420
|
+
iFrameContentHeight() {
|
11421
|
+
const firstElement = this.iframeTarget.contentWindow.document.body.firstElementChild;
|
11422
|
+
const firstElementStyle = window.getComputedStyle(firstElement);
|
11423
|
+
const height = firstElement.scrollHeight;
|
11424
|
+
const margins = parseInt(firstElementStyle.marginTop) + parseInt(firstElementStyle.marginBottom);
|
11425
|
+
return height + margins;
|
11412
11426
|
}
|
11413
11427
|
getAuthenticityToken() {
|
11414
11428
|
const tokenTag = document.querySelector('meta[name="csrf-token"]');
|
@@ -13199,10 +13213,14 @@ var repeater_controller_default = class extends Controller {
|
|
13199
13213
|
replaceIdsWithTimestamps(template) {
|
13200
13214
|
const pattern = "rrrrrrrrr";
|
13201
13215
|
const replacement = new Date().getTime().toString();
|
13216
|
+
const regex = new RegExp(pattern, "g");
|
13202
13217
|
template.querySelectorAll(`input[id*="${pattern}"], select[id*="${pattern}"], textarea[id*="${pattern}"], button[id*="${pattern}"]`).forEach((node) => {
|
13203
13218
|
const idValue = node.getAttribute("id");
|
13204
13219
|
node.setAttribute("id", idValue.replace(pattern, replacement));
|
13205
13220
|
});
|
13221
|
+
template.querySelectorAll("template").forEach((node) => {
|
13222
|
+
node.innerHTML = node.innerHTML.replace(regex, replacement);
|
13223
|
+
});
|
13206
13224
|
template.querySelectorAll(`label[for*="${pattern}"]`).forEach((node) => {
|
13207
13225
|
const forValue = node.getAttribute("for");
|
13208
13226
|
node.setAttribute("for", forValue.replace(pattern, replacement));
|
@@ -1,14 +1,14 @@
|
|
1
1
|
class ApplicationHelper
|
2
2
|
def show_svg(attachment, options = {})
|
3
|
-
return if !attachment.content_type.include?(
|
3
|
+
return if !attachment.content_type.include?("svg")
|
4
4
|
|
5
5
|
attachment.open do |file|
|
6
6
|
content = file.read
|
7
7
|
doc = Nokogiri::HTML::DocumentFragment.parse content
|
8
|
-
svg = doc.at_css
|
8
|
+
svg = doc.at_css "svg"
|
9
9
|
|
10
10
|
# for security
|
11
|
-
doc.search(
|
11
|
+
doc.search("script").each do |src|
|
12
12
|
src.remove
|
13
13
|
end
|
14
14
|
|
@@ -17,4 +17,4 @@ class ApplicationHelper
|
|
17
17
|
doc.to_html.html_safe
|
18
18
|
end
|
19
19
|
end
|
20
|
-
end
|
20
|
+
end
|
@@ -19,8 +19,6 @@ module Formstrap
|
|
19
19
|
data: {
|
20
20
|
controller: "media",
|
21
21
|
name: name,
|
22
|
-
min: min,
|
23
|
-
max: max,
|
24
22
|
sort: sort,
|
25
23
|
accept: accept,
|
26
24
|
required: required.nil? ? 0 : required
|
@@ -31,7 +29,7 @@ module Formstrap
|
|
31
29
|
def item_options
|
32
30
|
options = {
|
33
31
|
sort: sort,
|
34
|
-
url: modal_url
|
32
|
+
url: modal_url
|
35
33
|
}
|
36
34
|
|
37
35
|
# Don't pass width or height if it was not defined
|
@@ -108,10 +106,10 @@ module Formstrap
|
|
108
106
|
end
|
109
107
|
|
110
108
|
def min
|
111
|
-
if @
|
112
|
-
|
109
|
+
if @min.to_i < 1
|
110
|
+
@required ? 1 : 0
|
113
111
|
else
|
114
|
-
|
112
|
+
@min.to_i
|
115
113
|
end
|
116
114
|
end
|
117
115
|
|
@@ -124,7 +122,14 @@ module Formstrap
|
|
124
122
|
end
|
125
123
|
|
126
124
|
def modal_url
|
127
|
-
formstrap_media_path(
|
125
|
+
formstrap_media_path(
|
126
|
+
name: name,
|
127
|
+
ids: blob_ids,
|
128
|
+
min: min,
|
129
|
+
max: max,
|
130
|
+
mimetype: accept,
|
131
|
+
exclude_models: exclude_models
|
132
|
+
)
|
128
133
|
end
|
129
134
|
|
130
135
|
def edit_modal_url(attachment)
|
@@ -92,10 +92,10 @@ module Formstrap
|
|
92
92
|
blob.open do |file|
|
93
93
|
content = file.read
|
94
94
|
doc = Nokogiri::HTML::DocumentFragment.parse content
|
95
|
-
svg = doc.at_css
|
95
|
+
svg = doc.at_css "svg"
|
96
96
|
|
97
97
|
# for security
|
98
|
-
doc.search(
|
98
|
+
doc.search("script").each do |src|
|
99
99
|
src.remove
|
100
100
|
end
|
101
101
|
|
@@ -2,7 +2,7 @@
|
|
2
2
|
<!-- Preview placeholder -->
|
3
3
|
<div class="nested-preview-iframe-wrapper position-relative" role="button" data-nested-preview-target="iframeWrapper" data-bs-toggle="offcanvas" data-bs-target="#offcanvas-<%= form.options[:child_index] %>" aria-controls="offcanvasRight" data-turbo-cache="false">
|
4
4
|
<iframe src="<%= url %>" class="pe-none" data-nested-preview-target="iframe"></iframe>
|
5
|
-
<div data-nested-preview-target="loader" class="nested-preview-loader
|
5
|
+
<div data-nested-preview-target="loader" class="nested-preview-loader">
|
6
6
|
<div class="spinner-grow text-secondary" role="status">
|
7
7
|
<span class="visually-hidden">Loading...</span>
|
8
8
|
</div>
|
@@ -18,7 +18,7 @@
|
|
18
18
|
<div class="offcanvas-body">
|
19
19
|
|
20
20
|
<div class="alert alert-danger d-none" data-nested-preview-target="error">
|
21
|
-
|
21
|
+
<%= t(".error") %>
|
22
22
|
</div>
|
23
23
|
|
24
24
|
<!-- Row content -->
|
data/lib/formstrap/version.rb
CHANGED
data/package.json
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: formstrap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jef Vlamings
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-03-
|
11
|
+
date: 2024-03-20 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: An extensive Bootstrap form library to power your Ruby On Rails application.
|
14
14
|
email:
|