@@ -13,7 +13,7 @@
*/
package org.orbeon.oxf.xforms.control

import controls.{XXFormsRootControl, XFormsRepeatControl, XFormsCaseControl, XXFormsDialogControl}
import controls._
import org.orbeon.oxf.xforms.event.events.{DOMFocusOutEvent,DOMFocusInEvent}
import org.orbeon.oxf.xforms.control.Controls.AncestorIterator
import org.orbeon.oxf.xforms.XFormsContainingDocument
@@ -28,7 +28,7 @@ import org.orbeon.oxf.xforms.event.XFormsEvents.{DOM_FOCUS_OUT, DOM_FOCUS_IN}
object Focus {

// Focus on the given control and dispatch appropriate focus events
def focusWithEvents(control: XFormsControl ): Boolean = {
def focusWithEvents(control: XFormsControl): Boolean = {

// Continue only if control is focusable
if (! isFocusable(control))
@@ -47,8 +47,8 @@ object Focus {
doc.getControls.setFocusedControl(control)

// Ancestor-or-self chains from root to leaf
val previousChain = previousOption.toList flatMap (containersAndSelf(_))
val currentChain = containersAndSelf(control)
val previousChain = previousOption.toList flatMap (containersAndSelf(_).reverse)
val currentChain = containersAndSelf(control).reverse

// Number of common ancestor containers, if any
val commonPrefix = previousChain zip currentChain prefixLength
@@ -66,7 +66,33 @@ object Focus {
}

// Updated focus based on a previously focused control
def updateFocus(focusedBefore: XFormsControl ) =
def updateFocusWithEvents(focusedBefore: XFormsControl, repeat: Option[XFormsRepeatControl] = None) = repeat match {
case Some(repeat)
// Update the focus based on a previously focused control for which focus out events up to a repeat have
// already been dispatched when a repeat iteration has been removed

val doc = focusedBefore.containingDocument

// Do as if focus hadn't been removed yet, as updateFocus expects it
doc.getControls.setFocusedControl(focusedBefore)

// Called if the focus is fully removed
// Focus out events have been dispatched up to the iteration already, so just dispatch from the repeat to the root
def removeFocus() =
containersAndSelf(repeat) foreach (focusOut(_))

// Called if the focus is changing to a new control
// This will dispatch focus events from the new repeat iteration to the control
def focus(control: XFormsControl) =
setFocusPartially(control, Some(repeat))

updateFocus(focusedBefore, removeFocus _, focus)
case None
updateFocus(focusedBefore, () removeFocus(focusedBefore.containingDocument), focusWithEvents(_))
}

// Updated focus based on a previously focused control
private def updateFocus(focusedBefore: XFormsControl, onRemoveFocus: () Any, onFocus: XFormsControl Any) =
if (focusedBefore ne null) {
// There was a control with focus before

@@ -93,29 +119,34 @@ object Focus {
case None
// Cannot find a reference to the control anymore
// Control might be a ghost that has been removed from the tree (iteration removed)
removeFocus(doc)
onRemoveFocus()

case Some(newReference) if ! isFocusable(newReference)
// New reference exists, but is not focusable
removeFocus(doc)
onRemoveFocus()

case Some(newReference) if newReference ne focusedBefore
// Control exists and is focusable, and is not the same as the original control

// This covers the case where repeat indexes have been updated
// Here we move the focus to the new control
focusWithEvents(newReference)
onFocus(newReference)

case _
// Control exists, is focusable, and is the same as before, so we do nothing!
}
} else {
// Either there is no focus now, or the focus is different. Either way, the change must have been done
// via xforms-focus, which means that events have already been dispatched. So here we do nothing.
// The focus is different or has been removed
//
// - if different, the change must have been done via xforms-focus
// - if removed, the change might have been done in XFormsRepeatControl upon removing an iteration
//
// Either way events must have already been dispatched, so here we do nothing.
}
} else {
// Whether there was focus before or not, or whether there is focus now or not the change must have been
// done via xforms-focus, which means that events have already been dispatched. So here we do nothing.
// There was no focus before. If there is focus now, the change must have been done via xforms-focus, which
// means that events have already been dispatched. If there is no focus now, nothing has changed. So here we
// do nothing.
}

// Whether focus is currently within the given container
@@ -125,20 +156,54 @@ object Focus {
case _ false
}

// Completely remove the focus
def removeFocus(doc: XFormsContainingDocument) {
private def isNotBoundary(control: XFormsControl, boundary: Option[XFormsContainerControl]) =
boundary.isEmpty || (control ne boundary.get)

// Partially remove the focus until the given boundary if any
// The boundary is used by XFormsRepeatControl when an iteration is removed if the focus is within that iteration
def removeFocusPartially(doc: XFormsContainingDocument, boundary: Option[XFormsContainerControl]) {

// Dispatch DOMFocusOut events to the given control and to its container ancestors
def dispatchFocusOuts(control: XFormsControl) =
(containersAndSelf(control) takeWhile (isNotBoundary(_, boundary))) foreach (focusOut(_))

// Dispatch focus out events if needed
Option(doc.getControls.getFocusedControl) foreach { focused
doc.getControls.clearFocusedControl()
dispatchFocusOuts(focused)
}
}

// Dispatch DOMFocusOut events to the given focusable control and to all its container ancestors
def dispatchFocusOutUponNonRelevance(control: XFormsControl) =
(containersAndSelf(control) reverse) foreach (focusOut(_))
// Partially set the focus to the control, dispatching events from the given boundary if any
def setFocusPartially(control: XFormsControl, boundary: Option[XFormsContainerControl]) {
val doc = control.containingDocument

// Read previous control
Option(doc.getControls.getFocusedControl) foreach { focusedBefore
// Forget focus
doc.getControls.setFocusedControl(null)
// Dispatch focus out events to the control and its ancestors
dispatchFocusOutUponNonRelevance(focusedBefore)
}
// Remember first that the new control has focus
doc.getControls.setFocusedControl(control)

// Dispatch DOMFocusOut events to the given control and to its container ancestors
def dispatchFocusIns(control: XFormsControl) =
(containersAndSelf(control) takeWhile (isNotBoundary(_, boundary)) reverse) foreach (focusIn(_))

// Dispatch focus in events
dispatchFocusIns(control)
}

// Remove the focus entirely and dispatch the appropriate events
def removeFocus(doc: XFormsContainingDocument) =
removeFocusPartially(doc, boundary = None)

// Whether focus is currently in the given container
def isFocusInContainer(container: XFormsContainerControl) = {
val doc = container.containingDocument

// Focused control if any
val previousOption = Option(doc.getControls.getFocusedControl)

// True iif the currently focused control is a descendant of the container
previousOption map
(focused containers(focused) exists (_ eq container)) getOrElse
false
}

// Find boundaries for event dispatch
@@ -231,7 +296,7 @@ object Focus {
new AncestorIterator(control.parent) collect
{ case container: XFormsContainerControl container } toList

// Ancestor controls and control from root to leaf excepting the root control
// Ancestor controls and control from leaf to root excepting the root control
private def containersAndSelf(control: XFormsControl) =
control :: containers(control).init reverse
control :: containers(control).init
}
@@ -248,8 +248,6 @@ class XFormsRepeatControl(container: XBLContainer, parent: XFormsControl, elemen
// Get old nodeset
val oldRepeatNodeset = getBindingContext.getNodeset

val focusedBefore = containingDocument.getControls.getFocusedControl

// Set new binding context on the repeat control
locally {
// NOTE: here we just reevaluate against the parent; maybe we should reevaluate all the way down
@@ -270,7 +268,9 @@ class XFormsRepeatControl(container: XBLContainer, parent: XFormsControl, elemen
if (! Controls.compareNodesets(oldRepeatNodeset, getBindingContext.getNodeset)) {
// Update iterationsInitialStateIfNeeded()

val newIterations = updateIterations(oldRepeatNodeset, insertedNodeInfos, isInsertDelete = true)
val focusedBefore = containingDocument.getControls.getFocusedControl

val (newIterations, partialFocusRepeatOption) = updateIterations(oldRepeatNodeset, insertedNodeInfos, isInsertDelete = true)

// Evaluate all controls and then dispatches creation events
val currentControlTree = containingDocument.getControls.getCurrentControlTree
@@ -279,10 +279,10 @@ class XFormsRepeatControl(container: XBLContainer, parent: XFormsControl, elemen

// This will dispatch xforms-enabled/xforms-disabled/xxforms-nodeset-changed/xxforms-index-changed events if needed
containingDocument.getControls.getCurrentControlTree.dispatchRefreshEvents(Collections.singletonList(getEffectiveId))
}

// Handle focus changes
Focus.updateFocus(focusedBefore)
// Handle focus changes
Focus.updateFocusWithEvents(focusedBefore, partialFocusRepeatOption)
}
}

/**
@@ -296,7 +296,8 @@ class XFormsRepeatControl(container: XBLContainer, parent: XFormsControl, elemen
* @param insertedItems items just inserted by xforms:insert if any, or null
* @return new iterations if any, or an empty list
*/
def updateIterations(oldRepeatItems: JList[Item], insertedItems: JList[Item], isInsertDelete: Boolean): Seq[XFormsRepeatIterationControl] = {
def updateIterations(oldRepeatItems: JList[Item], insertedItems: JList[Item], isInsertDelete: Boolean):
(Seq[XFormsRepeatIterationControl], Option[XFormsRepeatControl]) = {

// NOTE: The following assumes the nodesets have changed

@@ -314,9 +315,12 @@ class XFormsRepeatControl(container: XBLContainer, parent: XFormsControl, elemen
val oldRepeatIndex = getIndex// 1-based
var updated = false

val (newIterations, movedIterationsOldPositions, movedIterationsNewPositions) =
val (newIterations, movedIterationsOldPositions, movedIterationsNewPositions, partialFocusRepeatOption) =
if (! newRepeatNodeset.isEmpty) {

// This may be set to this repeat or to a nested repeat if focus was within a removed iteration
var partialFocusRepeatOption: Option[XFormsRepeatControl] = None

// For each new node, what its old index was, -1 if it was not there
val oldIndexes = findNodeIndexes(newRepeatNodeset, oldRepeatItems)

@@ -334,6 +338,15 @@ class XFormsRepeatControl(container: XBLContainer, parent: XFormsControl, elemen
val movedOrRemovedIteration = oldChildren(i)
if (isRemoved) {
withDebug("removing iteration", Seq("id" getEffectiveId, "index" (i + 1).toString)) {

// If focused control is in removed iteration, remember this repeat and partially remove
// focus before deindexing the iteration. The idea here is that we don't want to dispatch
// events to controls that have been removed from the index. So we dispatch all the
// possible focus out events here.
if (partialFocusRepeatOption.isEmpty && Focus.isFocusInContainer(movedOrRemovedIteration)) {
partialFocusRepeatOption = Some(XFormsRepeatControl.this)
Focus.removeFocusPartially(containingDocument, boundary = partialFocusRepeatOption)
}

// Dispatch destruction events
currentControlTree.dispatchDestructionEventsForRemovedContainer(movedOrRemovedIteration, true)
@@ -434,6 +447,16 @@ class XFormsRepeatControl(container: XBLContainer, parent: XFormsControl, elemen

val existingIteration = oldChildren(currentOldIndex)
val newIterationOldIndex = existingIteration.iterationIndex

def updateBindingsIfNeeded() {
// NOTE: We used to only update the binding on the iteration itself
if (isInsertDelete) {
val updater = Controls.updateBindings(existingIteration)
if (partialFocusRepeatOption.isEmpty && updater.partialFocusRepeat.isDefined)
partialFocusRepeatOption = updater.partialFocusRepeat
}
}

if (newIterationOldIndex != repeatIndex) {
// Iteration index changed
debug("moving iteration", Seq("id" getEffectiveId, "old index" newIterationOldIndex.toString, "new index" repeatIndex.toString))
@@ -442,9 +465,7 @@ class XFormsRepeatControl(container: XBLContainer, parent: XFormsControl, elemen
existingIteration.setIterationIndex(repeatIndex)

// Update iteration bindings
// NOTE: We used to only update the binding on the iteration itself
if (isInsertDelete)
Controls.updateBindings(existingIteration)
updateBindingsIfNeeded()

// Index iteration
currentControlTree.indexSubtree(existingIteration, true)
@@ -457,9 +478,7 @@ class XFormsRepeatControl(container: XBLContainer, parent: XFormsControl, elemen
// Iteration index stayed the same

// Update iteration bindings
// NOTE: We used to only update the binding on the iteration itself
if (isInsertDelete)
Controls.updateBindings(existingIteration)
updateBindingsIfNeeded()
}

// Add existing iteration
@@ -470,10 +489,14 @@ class XFormsRepeatControl(container: XBLContainer, parent: XFormsControl, elemen
// Set the new children iterations
setChildren(newChildren)

(newIterations, movedIterationsOldPositions, movedIterationsNewPositions)
(newIterations, movedIterationsOldPositions, movedIterationsNewPositions, partialFocusRepeatOption)
} else {
// New repeat nodeset is now empty

// If focused control is in removed iterations, remove focus first
if (Focus.isFocusInContainer(XFormsRepeatControl.this))
Focus.removeFocus(containingDocument)

// Remove control information for iterations that disappear
for (removedIteration children) {

@@ -492,7 +515,7 @@ class XFormsRepeatControl(container: XBLContainer, parent: XFormsControl, elemen
clearChildren()
setIndexInternal(0)

(Seq.empty, Collections.emptyList[java.lang.Integer], Collections.emptyList[java.lang.Integer])
(Seq.empty, Collections.emptyList[java.lang.Integer], Collections.emptyList[java.lang.Integer], None)
}

if (updated || oldRepeatIndex != getIndex) {
@@ -509,7 +532,7 @@ class XFormsRepeatControl(container: XBLContainer, parent: XFormsControl, elemen
else
refreshInfo = null

newIterations
(newIterations, partialFocusRepeatOption)
}

def dispatchRefreshEvents() {