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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8243dd5f8e2fdae1537ffab42b56734ebdfe53e054feb92c68a323954c0a63e0
4
- data.tar.gz: 0c5a4a73c1d4c518c2ee71cf36f3ccc882ee106a825d94aef212a639ee0bcb94
3
+ metadata.gz: 98d133e0c15d72418662dbe8a98e296a3c701bc97aa9bec3620fa3f0a346ffec
4
+ data.tar.gz: f50c6dc41f0809c54e6522e14a718b0a6842a419fc3068928a64e156df771408
5
5
  SHA512:
6
- metadata.gz: 6dbd8118d294ff8a863675299ce236cc3bd7f7a749b958b42cd22ac66465c3a5c751433edb93009a87b203d28995e2581b3f3600160d4f6a68e0b78110ab9fbe
7
- data.tar.gz: 90ea13586df35f2db36e0b5f3139c6316ae5be21cf022355c665084a19e5d69663efe329f948bd6287b8deb8c4af76ac4b186a94b73aae5b73c8f6aded3659f9
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.element.dataset.min, 10) || 0
117
+ return parseInt(this.validationInputTarget.dataset.min, 10) || 0
118
118
  }
119
119
 
120
120
  maxActiveItems () {
121
- return parseInt(this.element.dataset.max, 10) || Infinity
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[name], select[name], textarea[name]')
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.iframeTarget.contentWindow.document.body.scrollHeight
172
+ const iframeContentHeight = this.iFrameContentHeight()
157
173
  const iframeHeight = iframeContentHeight * scaleFactor
158
- this.iframeTarget.style.height = iframeContentHeight + 'px'
174
+
175
+ this.iframeTarget.style.height = `${iframeContentHeight.toFixed()}px`
159
176
  this.iframeTarget.style.opacity = 1
160
- this.iframeWrapperTarget.style.height = iframeHeight + 'px'
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.element.dataset.min, 10) || 0;
11038
+ return parseInt(this.validationInputTarget.dataset.min, 10) || 0;
11039
11039
  }
11040
11040
  maxActiveItems() {
11041
- return parseInt(this.element.dataset.max, 10) || Infinity;
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("input[name], select[name], textarea[name]");
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.iframeTarget.contentWindow.document.body.scrollHeight;
11414
+ const iframeContentHeight = this.iFrameContentHeight();
11408
11415
  const iframeHeight = iframeContentHeight * scaleFactor;
11409
- this.iframeTarget.style.height = iframeContentHeight + "px";
11416
+ this.iframeTarget.style.height = `${iframeContentHeight.toFixed()}px`;
11410
11417
  this.iframeTarget.style.opacity = 1;
11411
- this.iframeWrapperTarget.style.height = iframeHeight + "px";
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?('svg')
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 'svg'
8
+ svg = doc.at_css "svg"
9
9
 
10
10
  # for security
11
- doc.search('script').each do |src|
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 @required
112
- (@min.to_i < 1) ? 1 : @min.to_i
109
+ if @min.to_i < 1
110
+ @required ? 1 : 0
113
111
  else
114
- (@min.to_i < 1) ? 0 : @min.to_i
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(name: name, ids: blob_ids, min: min, max: max, mimetype: accept, exclude_models: exclude_models)
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 'svg'
95
+ svg = doc.at_css "svg"
96
96
 
97
97
  # for security
98
- doc.search('script').each do |src|
98
+ doc.search("script").each do |src|
99
99
  src.remove
100
100
  end
101
101
 
@@ -5,6 +5,8 @@
5
5
  class: "formstrap-media-validation",
6
6
  data: {
7
7
  "media-target": "validationInput",
8
+ "min": min,
9
+ "max": max,
8
10
  "min-message": t(".min", count: min),
9
11
  "max-message": t(".max", count: max),
10
12
  } %>
@@ -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 terti">
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
- <% t(".error") %>
21
+ <%= t(".error") %>
22
22
  </div>
23
23
 
24
24
  <!-- Row content -->
@@ -15,7 +15,7 @@ module Formstrap
15
15
  default_options = {
16
16
  data: {
17
17
  controller: "preview",
18
- "preview-url-value": options[:url]
18
+ "preview-url-value": options[:url]
19
19
  },
20
20
  type: nil
21
21
  }
@@ -1,3 +1,3 @@
1
1
  module Formstrap
2
- VERSION = "0.3.0"
2
+ VERSION = "0.3.2"
3
3
  end
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontierdotbe/formstrap",
3
- "version": "0.2.0",
3
+ "version": "0.3.2",
4
4
  "description": "Bootstrap-powered Form Helpers",
5
5
  "module": "app/assets/javascripts/formstrap.js",
6
6
  "main": "app/assets/javascripts/formstrap.js",
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.0
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 00:00:00.000000000 Z
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: