Skip to content

Commit 6d62d1f

Browse files
committed
Fix #6923: properly initialize subsequently embedded forms
1 parent 095b7aa commit 6d62d1f

File tree

1 file changed

+115
-116
lines changed

1 file changed

+115
-116
lines changed

form-runner-web/src/main/scala/org/orbeon/fr/FormRunnerApp.scala

+115-116
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
package org.orbeon.fr
1515

1616
import org.orbeon.facades.{Ladda, ResizeObserver}
17-
import org.orbeon.web.DomEventNames
17+
import org.orbeon.web.{DomEventNames, DomSupport}
1818
import org.orbeon.web.DomSupport.*
1919
import org.orbeon.xbl
2020
import org.orbeon.xforms.*
@@ -26,6 +26,7 @@ import org.scalajs.dom.*
2626
import scala.scalajs.js
2727
import scala.scalajs.js.Dynamic.global as g
2828
import scala.scalajs.js.timers
29+
import org.scalajs.macrotaskexecutor.MacrotaskExecutor.Implicits.*
2930

3031

3132
// Scala.js starting point for Form Runner
@@ -76,141 +77,139 @@ object FormRunnerApp extends App {
7677
xbl.ClipboardCopy
7778
xbl.Trigger
7879

79-
// TODO: with embedding, unobserve when the form is destroyed
80-
Events.orbeonLoadedEvent.subscribe(() => {
81-
82-
// Add `scroll-padding-top` and `scroll-padding-bottom` to prevent the focused form field from being below the top navbar or button bar
83-
def addScrollPadding(htmlElement: html.Element, cssClass: String): Unit = {
84-
val position = window.getComputedStyle(htmlElement).position
85-
if (position == "fixed" || position == "sticky") {
86-
val resizeObserver = new ResizeObserver(() => {
87-
val documentElement = document.documentElementT
88-
val scrollPaddingWithMargin = htmlElement.clientHeight + 5;
89-
documentElement.style.setProperty(cssClass, s"${scrollPaddingWithMargin}px")
90-
})
91-
resizeObserver.observe(htmlElement)
92-
}
93-
}
94-
95-
document.querySelectorOpt(".orbeon .navbar-fixed-top").foreach(addScrollPadding(_, "scroll-padding-top"))
96-
document.querySelectorOpt(".orbeon .fr-buttons" ).foreach(addScrollPadding(_, "scroll-padding-bottom"))
80+
DomSupport.atLeastDomReadyStateF(document, DomSupport.DomReadyState.Interactive) foreach { _ =>
81+
DomSupport.onElementFoundOrAdded(document.body, ".orbeon .navbar-fixed-top" , addScrollPadding(_, "scroll-padding-top"))
82+
DomSupport.onElementFoundOrAdded(document.body, ".orbeon .fr-buttons" , addScrollPadding(_, "scroll-padding-bottom"))
83+
DomSupport.onElementFoundOrAdded(document.body, ".orbeon .fr-session-expiration-dialog", initSessionExpirationDialog)
84+
}
85+
}
9786

98-
initSessionExpirationDialog()
99-
})
87+
// Add `scroll-padding-top` and `scroll-padding-bottom` to prevent the focused form field from being
88+
// below the top navbar or button bar
89+
private def addScrollPadding(htmlElement: html.Element, cssClass: String): Unit = {
90+
val position = window.getComputedStyle(htmlElement).position
91+
if (position == "fixed" || position == "sticky") {
92+
val resizeObserver = new ResizeObserver(() => {
93+
val documentElement = document.documentElementT
94+
val scrollPaddingWithMargin = htmlElement.clientHeight + 5;
95+
documentElement.style.setProperty(cssClass, s"${scrollPaddingWithMargin}px")
96+
})
97+
resizeObserver.observe(htmlElement)
98+
}
10099
}
101100

102-
private def initSessionExpirationDialog(): Unit = {
103-
document.querySelectorOpt(".fr-session-expiration-dialog").collect { case dialog: HTMLDialogElement =>
104-
105-
// Detecting whether the dialog is shown or not by retrieving its CSS classes is not reliable when aboutToExpire
106-
// is called multiple times in a row (e.g. locally and because of a message from another page), so we keep track
107-
// of it ourselves.
108-
var dialogShown: Boolean = false
109-
var didExpireTimerOpt: Option[timers.SetTimeoutHandle] = None
110-
111-
if (dialog.classList.contains("fr-feature-enabled")) {
112-
// Remove XForms-level dialog we're replacing
113-
dom.document.querySelectorAllT(s".xforms-login-detected-dialog").foreach(_.remove())
114-
115-
val renewButton = dialog.querySelectorT(".fr-renew-button")
116-
GlobalEventListenerSupport.addListener(renewButton, DomEventNames.Click, (_: dom.EventTarget) => {
117-
renewSession()
118-
// Since we sent a heartbeat when the session was renewed, the local newest event time has been updated and
119-
// will be broadcast to other pages.
120-
Session.updateWithLocalNewestEventTime()
121-
})
101+
private def initSessionExpirationDialog(dialogElem: html.Element): Unit = {
102+
val dialog = dialogElem.asInstanceOf[HTMLDialogElement]
103+
org.scalajs.dom.console.log(dialog)
104+
105+
// Detecting whether the dialog is shown or not by retrieving its CSS classes is not reliable when aboutToExpire
106+
// is called multiple times in a row (e.g. locally and because of a message from another page), so we keep track
107+
// of it ourselves.
108+
var dialogShown: Boolean = false
109+
var didExpireTimerOpt: Option[timers.SetTimeoutHandle] = None
110+
111+
if (dialog.classList.contains("fr-feature-enabled")) {
112+
// Remove XForms-level dialog we're replacing
113+
dom.document.querySelectorAllT(s".xforms-login-detected-dialog").foreach(_.remove())
114+
115+
val renewButton = dialog.querySelectorT(".fr-renew-button")
116+
GlobalEventListenerSupport.addListener(renewButton, DomEventNames.Click, (_: dom.EventTarget) => {
117+
renewSession()
118+
// Since we sent a heartbeat when the session was renewed, the local newest event time has been updated and
119+
// will be broadcast to other pages.
120+
Session.updateWithLocalNewestEventTime()
121+
})
122+
123+
val reloadButton = dialog.querySelectorT(".fr-reload-button")
124+
val laddaReloadButton = Ladda.create(reloadButton)
125+
GlobalEventListenerSupport.addListener(
126+
target = reloadButton,
127+
name = DomEventNames.Click,
128+
fn = (_: dom.EventTarget) => {
129+
laddaReloadButton.start()
130+
dom.window.location.href = dom.window.location.href
131+
}
132+
)
122133

123-
val reloadButton = dialog.querySelectorT(".fr-reload-button")
124-
val laddaReloadButton = Ladda.create(reloadButton)
125-
GlobalEventListenerSupport.addListener(
126-
target = reloadButton,
127-
name = DomEventNames.Click,
128-
fn = (_: dom.EventTarget) => {
129-
laddaReloadButton.start()
130-
dom.window.location.href = dom.window.location.href
131-
}
132-
)
133-
134-
Session.addSessionUpdateListener(sessionUpdate)
135-
}
134+
Session.addSessionUpdateListener(sessionUpdate)
135+
}
136136

137-
def sessionUpdate(sessionUpdate: SessionUpdate): Unit =
138-
if (! sessionUpdate.sessionHeartbeatEnabled) {
139-
sessionUpdate.sessionStatus match {
140-
case Session.SessionActive if dialogShown && ! Session.expired =>
141-
renewSession()
137+
def sessionUpdate(sessionUpdate: SessionUpdate): Unit =
138+
if (! sessionUpdate.sessionHeartbeatEnabled) {
139+
sessionUpdate.sessionStatus match {
140+
case Session.SessionActive if dialogShown && ! Session.expired =>
141+
renewSession()
142142

143-
case Session.SessionAboutToExpire =>
144-
aboutToExpire(sessionUpdate.approxSessionExpiredTimeMillis)
143+
case Session.SessionAboutToExpire =>
144+
aboutToExpire(sessionUpdate.approxSessionExpiredTimeMillis)
145145

146-
case Session.SessionExpired =>
147-
sessionExpired()
146+
case Session.SessionExpired =>
147+
sessionExpired()
148148

149-
case _ =>
150-
// Nothing to do
151-
}
149+
case _ =>
150+
// Nothing to do
152151
}
152+
}
153153

154-
def aboutToExpire(approxSessionExpiredTimeMillis: Long): Unit =
155-
if (! dialogShown) {
156-
AjaxClient.pause()
157-
updateDialog()
158-
showDialog()
159-
160-
val timeToExpiration = approxSessionExpiredTimeMillis - System.currentTimeMillis()
161-
didExpireTimerOpt.foreach(timers.clearTimeout)
162-
didExpireTimerOpt = Some(timers.setTimeout(timeToExpiration.toDouble) {
163-
Session.sessionHasExpired()
164-
updateDialog()
165-
})
166-
}
154+
def aboutToExpire(approxSessionExpiredTimeMillis: Long): Unit =
155+
if (! dialogShown) {
156+
AjaxClient.pause()
157+
updateDialog()
158+
showDialog()
167159

168-
def sessionExpired(): Unit =
169-
if (! dialogShown) {
170-
AjaxClient.pause()
171-
updateDialog()
172-
showDialog()
173-
} else {
160+
val timeToExpiration = approxSessionExpiredTimeMillis - System.currentTimeMillis()
161+
didExpireTimerOpt.foreach(timers.clearTimeout)
162+
didExpireTimerOpt = Some(timers.setTimeout(timeToExpiration.toDouble) {
163+
Session.sessionHasExpired()
174164
updateDialog()
175-
}
165+
})
166+
}
176167

177-
def updateDialog(): Unit = {
178-
val headerContent = dialog.querySelectorAllT(".xxforms-dialog-head .xforms-output")
179-
val bodyContent = dialog.querySelectorAllT(".xxforms-dialog-body .xforms-mediatype-text-html")
180-
val renewButton = dialog.querySelectorT (".fr-renew-button")
181-
val reloadButton = dialog.querySelectorT (".fr-reload-button")
168+
def sessionExpired(): Unit =
169+
if (! dialogShown) {
170+
AjaxClient.pause()
171+
updateDialog()
172+
showDialog()
173+
} else {
174+
updateDialog()
175+
}
182176

183-
val visibleWhenExpiring = List(headerContent.head, bodyContent.head, renewButton)
184-
val visibleWhenExpired = List(headerContent.last, bodyContent.last, reloadButton)
177+
def updateDialog(): Unit = {
178+
val headerContent = dialog.querySelectorAllT(".xxforms-dialog-head .xforms-output")
179+
val bodyContent = dialog.querySelectorAllT(".xxforms-dialog-body .xforms-mediatype-text-html")
180+
val renewButton = dialog.querySelectorT (".fr-renew-button")
181+
val reloadButton = dialog.querySelectorT (".fr-reload-button")
185182

186-
def setDisplay(elements: List[html.Element], display: String): Unit =
187-
elements.foreach(_.style.display = display)
183+
val visibleWhenExpiring = List(headerContent.head, bodyContent.head, renewButton)
184+
val visibleWhenExpired = List(headerContent.last, bodyContent.last, reloadButton)
188185

189-
val (toShow, toHide) = if (Session.expired)
190-
(visibleWhenExpired , visibleWhenExpiring) else
191-
(visibleWhenExpiring, visibleWhenExpired )
186+
def setDisplay(elements: List[html.Element], display: String): Unit =
187+
elements.foreach(_.style.display = display)
192188

193-
setDisplay(toShow, "")
194-
setDisplay(toHide, "none")
195-
}
189+
val (toShow, toHide) = if (Session.expired)
190+
(visibleWhenExpired , visibleWhenExpiring) else
191+
(visibleWhenExpiring, visibleWhenExpired )
196192

197-
def renewSession(): Unit = {
198-
didExpireTimerOpt.foreach(timers.clearTimeout)
199-
didExpireTimerOpt = None
200-
AjaxClient.sendHeartBeat()
201-
AjaxClient.unpause()
202-
hideDialog()
203-
}
193+
setDisplay(toShow, "")
194+
setDisplay(toHide, "none")
195+
}
204196

205-
def showDialog(): Unit = {
206-
dialog.showModal()
207-
dialogShown = true
208-
}
197+
def renewSession(): Unit = {
198+
didExpireTimerOpt.foreach(timers.clearTimeout)
199+
didExpireTimerOpt = None
200+
AjaxClient.sendHeartBeat()
201+
AjaxClient.unpause()
202+
hideDialog()
203+
}
209204

210-
def hideDialog(): Unit = {
211-
dialog.close()
212-
dialogShown = false
213-
}
205+
def showDialog(): Unit = {
206+
dialog.showModal()
207+
dialogShown = true
208+
}
209+
210+
def hideDialog(): Unit = {
211+
dialog.close()
212+
dialogShown = false
214213
}
215214
}
216215

0 commit comments

Comments
 (0)