formstrap 0.3.1 → 0.3.3
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 +17 -6
- data/app/assets/javascripts/formstrap/controllers/repeater_controller.js +6 -0
- data/app/assets/javascripts/formstrap.js +23 -15
- 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/lib/formstrap/form_builder.rb +1 -1
- data/lib/formstrap/version.rb +1 -1
- data/package.json +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dc7c89e02028fbd50ff04f97b36ad9ef3bcbc3a1e270d025a664a721a9755e7d
|
4
|
+
data.tar.gz: 2375b43a0db037bcb58be9bac12d47079d7f4e5baaa03b08a5ea839053a3aaad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c888b852f01b98134dae97d0a5573ac695c4525020a9fef792af903b9c7a894c5b85dce0172633246e78a0e5ed2825c89659ecf2ff2a663f5aea9b10d844002b
|
7
|
+
data.tar.gz: 30ae20de2ddf908f64a82ca167bbc62fa6c0c6b744403e48ff053a9b6e1f293a323f3070947f3d29dd5e4509ef7cee20d42ec9db18d76adb53c8eddb79ee65aa
|
@@ -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 {
|
@@ -30,7 +31,6 @@ export default class extends Controller {
|
|
30
31
|
}
|
31
32
|
|
32
33
|
autoResizeIframe () {
|
33
|
-
// eslint-disable-next-line no-undef
|
34
34
|
const observer = new MutationObserver((mutations) => {
|
35
35
|
mutations.forEach((mutation) => {
|
36
36
|
this.resizeIframe()
|
@@ -77,7 +77,6 @@ export default class extends Controller {
|
|
77
77
|
|
78
78
|
requestPreview () {
|
79
79
|
// Create an AJAX request
|
80
|
-
// eslint-disable-next-line no-undef
|
81
80
|
const xhr = new XMLHttpRequest()
|
82
81
|
xhr.open('POST', this.urlValue, true)
|
83
82
|
|
@@ -109,7 +108,7 @@ export default class extends Controller {
|
|
109
108
|
validateFields () {
|
110
109
|
let allValid = true
|
111
110
|
const fields = this.fieldsTarget
|
112
|
-
const formElements = fields.querySelectorAll('input[
|
111
|
+
const formElements = fields.querySelectorAll('input:not([type="hidden"]), select[name], textarea[name]')
|
113
112
|
formElements.forEach(function (element) {
|
114
113
|
const isValid = element.reportValidity()
|
115
114
|
if (!isValid) {
|
@@ -127,12 +126,15 @@ export default class extends Controller {
|
|
127
126
|
const formData = new FormData()
|
128
127
|
|
129
128
|
// Replace all occurrences of "page[blocks_attributes][0]" with "block"
|
130
|
-
const regex = /\w+\[([^\]]+)s_attributes
|
129
|
+
const regex = /\w+\[([^\]]+)s_attributes]\[\d+]/g
|
131
130
|
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(
|
131
|
+
formElements.forEach((element) => {
|
133
132
|
const currentName = element.getAttribute('name')
|
134
133
|
const newName = currentName.replace(regex, '$1')
|
135
|
-
|
134
|
+
const values = this.readValues(element)
|
135
|
+
values.forEach((value) => {
|
136
|
+
formData.append(newName, value)
|
137
|
+
})
|
136
138
|
})
|
137
139
|
|
138
140
|
// Add authenticity token
|
@@ -141,6 +143,15 @@ export default class extends Controller {
|
|
141
143
|
return formData
|
142
144
|
}
|
143
145
|
|
146
|
+
readValues (element) {
|
147
|
+
// Check if the element is a select with multiple selection
|
148
|
+
if (element.tagName.toLowerCase() === 'select' && element.multiple) {
|
149
|
+
return [...element.selectedOptions].map(option => option.value)
|
150
|
+
} else {
|
151
|
+
return [element.value]
|
152
|
+
}
|
153
|
+
}
|
154
|
+
|
144
155
|
// Prepare the iFrame for rendering
|
145
156
|
// Objective: render the iframe content at the scale of the browser window, but resize it to fit the preview container
|
146
157
|
prepareIframe () {
|
@@ -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
|
};
|
@@ -11374,7 +11368,7 @@ var nested_preview_controller_default = class extends Controller {
|
|
11374
11368
|
validateFields() {
|
11375
11369
|
let allValid = true;
|
11376
11370
|
const fields = this.fieldsTarget;
|
11377
|
-
const formElements = fields.querySelectorAll(
|
11371
|
+
const formElements = fields.querySelectorAll('input:not([type="hidden"]), select[name], textarea[name]');
|
11378
11372
|
formElements.forEach(function(element) {
|
11379
11373
|
const isValid = element.reportValidity();
|
11380
11374
|
if (!isValid) {
|
@@ -11386,16 +11380,26 @@ var nested_preview_controller_default = class extends Controller {
|
|
11386
11380
|
buildFormData() {
|
11387
11381
|
const fields = this.fieldsTarget;
|
11388
11382
|
const formData = new FormData();
|
11389
|
-
const regex = /\w+\[([^\]]+)s_attributes
|
11383
|
+
const regex = /\w+\[([^\]]+)s_attributes]\[\d+]/g;
|
11390
11384
|
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(
|
11385
|
+
formElements.forEach((element) => {
|
11392
11386
|
const currentName = element.getAttribute("name");
|
11393
11387
|
const newName = currentName.replace(regex, "$1");
|
11394
|
-
|
11388
|
+
const values = this.readValues(element);
|
11389
|
+
values.forEach((value) => {
|
11390
|
+
formData.append(newName, value);
|
11391
|
+
});
|
11395
11392
|
});
|
11396
11393
|
formData.append("authenticity_token", this.getAuthenticityToken());
|
11397
11394
|
return formData;
|
11398
11395
|
}
|
11396
|
+
readValues(element) {
|
11397
|
+
if (element.tagName.toLowerCase() === "select" && element.multiple) {
|
11398
|
+
return [...element.selectedOptions].map((option2) => option2.value);
|
11399
|
+
} else {
|
11400
|
+
return [element.value];
|
11401
|
+
}
|
11402
|
+
}
|
11399
11403
|
prepareIframe() {
|
11400
11404
|
const scaleFactor = this.scaleFactor();
|
11401
11405
|
const style = `
|
@@ -13219,10 +13223,14 @@ var repeater_controller_default = class extends Controller {
|
|
13219
13223
|
replaceIdsWithTimestamps(template) {
|
13220
13224
|
const pattern = "rrrrrrrrr";
|
13221
13225
|
const replacement = new Date().getTime().toString();
|
13226
|
+
const regex = new RegExp(pattern, "g");
|
13222
13227
|
template.querySelectorAll(`input[id*="${pattern}"], select[id*="${pattern}"], textarea[id*="${pattern}"], button[id*="${pattern}"]`).forEach((node) => {
|
13223
13228
|
const idValue = node.getAttribute("id");
|
13224
13229
|
node.setAttribute("id", idValue.replace(pattern, replacement));
|
13225
13230
|
});
|
13231
|
+
template.querySelectorAll("template").forEach((node) => {
|
13232
|
+
node.innerHTML = node.innerHTML.replace(regex, replacement);
|
13233
|
+
});
|
13226
13234
|
template.querySelectorAll(`label[for*="${pattern}"]`).forEach((node) => {
|
13227
13235
|
const forValue = node.getAttribute("for");
|
13228
13236
|
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
|
|
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.3
|
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-
|
11
|
+
date: 2024-04-02 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:
|
@@ -211,7 +211,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
211
211
|
- !ruby/object:Gem::Version
|
212
212
|
version: '0'
|
213
213
|
requirements: []
|
214
|
-
rubygems_version: 3.
|
214
|
+
rubygems_version: 3.5.6
|
215
215
|
signing_key:
|
216
216
|
specification_version: 4
|
217
217
|
summary: Bootstrap-powered Form Helpers
|