Skip to content

Commit

Permalink
xxforms-visited/unvisited events
Browse files Browse the repository at this point in the history
- add VisitableTrait, implemented by container and focusable controls
- move isFocusable to control
- move dispatchCreation/Destruction/ChangeEvents to controls
- remove "isAllowSending" optimizations (they don't kick in for FR anyway)
  • Loading branch information
ebruchez committed Nov 27, 2012
1 parent 77a888f commit f6c02b4
Show file tree
Hide file tree
Showing 20 changed files with 278 additions and 286 deletions.
180 changes: 6 additions & 174 deletions src/java/org/orbeon/oxf/xforms/ControlTree.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@
import org.orbeon.oxf.xforms.control.*;
import org.orbeon.oxf.xforms.control.controls.XFormsRepeatControl;
import org.orbeon.oxf.xforms.control.controls.XFormsRepeatIterationControl;
import org.orbeon.oxf.xforms.event.XFormsEvents;
import org.orbeon.oxf.xforms.event.Dispatch;
import org.orbeon.oxf.xforms.event.events.*;
import org.orbeon.oxf.xforms.xbl.XBLContainer;
import org.orbeon.oxf.xforms.event.Dispatch;

import java.util.*;

Expand All @@ -31,14 +30,6 @@
*/
public class ControlTree implements ExternalCopyable {

private boolean isAllowSendingValueChangedEvents;
private boolean isAllowSendingRequiredEvents;
private boolean isAllowSendingRelevantEvents;
private boolean isAllowSendingReadonlyEvents;
private boolean isAllowSendingValidEvents;
private boolean isAllowSendingRefreshEvents;
private boolean isAllowSendingIterationMovedEvents;

private final IndentedLogger indentedLogger;

// Top-level controls
Expand All @@ -55,32 +46,6 @@ public class ControlTree implements ExternalCopyable {
public ControlTree(XFormsContainingDocument containingDocument, IndentedLogger indentedLogger) {
this.indentedLogger = indentedLogger;
this.controlIndex = new ControlIndex(containingDocument.getStaticState().isNoscript());
updateAllowedEvents(containingDocument);
}

public void updateAllowedEvents(XFormsContainingDocument containingDocument) {
// Obtain global information about event handlers. This is a rough optimization so we can avoid sending certain
// types of events below. Mostly useful for simple forms!

final StaticStateGlobalOps ops = containingDocument.getStaticOps();

isAllowSendingValueChangedEvents = ops.hasHandlerForEvent(XFormsEvents.XFORMS_VALUE_CHANGED);
isAllowSendingRequiredEvents = ops.hasHandlerForEvent(XFormsEvents.XFORMS_REQUIRED) || ops.hasHandlerForEvent(XFormsEvents.XFORMS_OPTIONAL);
isAllowSendingRelevantEvents = ops.hasHandlerForEvent(XFormsEvents.XFORMS_ENABLED) || ops.hasHandlerForEvent(XFormsEvents.XFORMS_DISABLED);
isAllowSendingReadonlyEvents = ops.hasHandlerForEvent(XFormsEvents.XFORMS_READONLY) || ops.hasHandlerForEvent(XFormsEvents.XFORMS_READWRITE);
isAllowSendingValidEvents = ops.hasHandlerForEvent(XFormsEvents.XFORMS_VALID) || ops.hasHandlerForEvent(XFormsEvents.XFORMS_INVALID);
isAllowSendingIterationMovedEvents = ops.hasHandlerForEvent(XFormsEvents.XXFORMS_ITERATION_MOVED);
final boolean isAllowSendingNodesetChangedEvent = ops.hasHandlerForEvent(XFormsEvents.XXFORMS_NODESET_CHANGED);
final boolean isAllowSendingIndexChangedEvent = ops.hasHandlerForEvent(XFormsEvents.XXFORMS_INDEX_CHANGED);

isAllowSendingRefreshEvents = isAllowSendingValueChangedEvents
|| isAllowSendingRequiredEvents
|| isAllowSendingRelevantEvents
|| isAllowSendingReadonlyEvents
|| isAllowSendingValidEvents
|| isAllowSendingIterationMovedEvents
|| isAllowSendingNodesetChangedEvent
|| isAllowSendingIndexChangedEvent;
}

/**
Expand Down Expand Up @@ -113,10 +78,6 @@ public void initialize(XFormsContainingDocument containingDocument) {
);
}

public boolean isAllowSendingRefreshEvents() {
return isAllowSendingRefreshEvents;
}

public void dispatchRefreshEvents(Collection<String> controlsEffectiveIds) {
indentedLogger.startHandleOperation("controls", "dispatching refresh events");
for (final String controlEffectiveId: controlsEffectiveIds) {
Expand All @@ -134,49 +95,13 @@ private void dispatchRefreshEvents(XFormsControl control) {

if (newRelevantState && !oldRelevantState) {
// Control has become relevant
dispatchCreationEvents(control);
control.dispatchCreationEvents();
} else if (!newRelevantState && oldRelevantState) {
// Control has become non-relevant
dispatchDestructionEvents(control);
control.dispatchDestructionEvents();
} else if (newRelevantState) {
// Control was and is relevant
dispatchChangeEvents(control);
}
}
}

private void dispatchCreationEvents(XFormsControl control) {
if (XFormsControl.controlSupportsRefreshEvents(control)) {
if (control.isRelevant()) {
// Commit current control state
control.commitCurrentUIState();

// Dispatch xforms-enabled if needed
if (isAllowSendingRelevantEvents) {
Dispatch.dispatchEvent(new XFormsEnabledEvent(control));
}
if (control instanceof XFormsSingleNodeControl) {
final XFormsSingleNodeControl singleNodeControl = (XFormsSingleNodeControl) control;
// Dispatch events only if the MIP value is different from the default

// Dispatch xforms-required if needed
// TODO: must reacquire control and test for relevance again
if (isAllowSendingRequiredEvents && singleNodeControl.isRequired()) {
Dispatch.dispatchEvent(new XFormsRequiredEvent(singleNodeControl));
}

// Dispatch xforms-readonly if needed
// TODO: must reacquire control and test for relevance again
if (isAllowSendingReadonlyEvents && singleNodeControl.isReadonly()) {
Dispatch.dispatchEvent(new XFormsReadonlyEvent(singleNodeControl));
}

// Dispatch xforms-invalid if needed
// TODO: must reacquire control and test for relevance again
if (isAllowSendingValidEvents && !singleNodeControl.isValid()) {
Dispatch.dispatchEvent(new XFormsInvalidEvent(singleNodeControl));
}
}
control.dispatchChangeEvents();
}
}
}
Expand Down Expand Up @@ -207,102 +132,13 @@ public void endVisitControl(XFormsControl control) {
for (final String effectiveId : controlsEffectiveIds) {
final XFormsControl control = controlIndex.getControl(effectiveId);
// Directly call destruction events as we know the iteration is going away
dispatchDestructionEvents(control);
if (XFormsControl.controlSupportsRefreshEvents(control))
control.dispatchDestructionEvents();
}

indentedLogger.endHandleOperation("controls", Integer.toString(controlsEffectiveIds.size()));
}

private void dispatchDestructionEvents(XFormsControl control) {
if (XFormsControl.controlSupportsRefreshEvents(control)) {

// Don't test for relevance here
// o In iteration removal case, control is still relevant
// o In refresh case, control is non-relevant

// Dispatch xforms-disabled if needed
if (isAllowSendingRelevantEvents) {
Dispatch.dispatchEvent(new XFormsDisabledEvent(control));
}

// TODO: if control *becomes* non-relevant and value changed, arguably we should dispatch the value-changed event
}
}

private void dispatchChangeEvents(XFormsControl control) {

// NOTE: For the purpose of dispatching value change and MIP events, we used to make a
// distinction between value controls and plain single-node controls. However it seems that it is
// still reasonable to dispatch those events to xf:group, xf:switch, and even repeat
// iterations if they are bound.

if (XFormsControl.controlSupportsRefreshEvents(control)) {
if (control.isRelevant()) {
// TODO: implement dispatchRefreshEvents() on all controls instead of using if ()
if (control instanceof XFormsSingleNodeControl) {

final XFormsSingleNodeControl singleNodeControl = (XFormsSingleNodeControl) control;

// xforms-value-changed
if (isAllowSendingValueChangedEvents && singleNodeControl.isValueChanged()) {
Dispatch.dispatchEvent(new XFormsValueChangeEvent(singleNodeControl));
}

// Dispatch moved xxforms-iteration-changed if needed
if (isAllowSendingIterationMovedEvents
&& control.previousEffectiveIdCommit() != control.getEffectiveId()
&& control.container().getPartAnalysis().observerHasHandlerForEvent(control.getPrefixedId(), XFormsEvents.XXFORMS_ITERATION_MOVED)) {
Dispatch.dispatchEvent(new XXFormsIterationMovedEvent(control));
}

// Dispatch events only if the MIP value is different from the previous value

// TODO: must reacquire control and test for relevance again
if (isAllowSendingValidEvents) {
final boolean previousValidState = singleNodeControl.wasValid();
final boolean newValidState = singleNodeControl.isValid();

if (previousValidState != newValidState) {
if (newValidState) {
Dispatch.dispatchEvent(new XFormsValidEvent(singleNodeControl));
} else {
Dispatch.dispatchEvent(new XFormsInvalidEvent(singleNodeControl));
}
}
}
// TODO: must reacquire control and test for relevance again
if (isAllowSendingRequiredEvents) {
final boolean previousRequiredState = singleNodeControl.wasRequired();
final boolean newRequiredState = singleNodeControl.isRequired();

if (previousRequiredState != newRequiredState) {
if (newRequiredState) {
Dispatch.dispatchEvent(new XFormsRequiredEvent(singleNodeControl));
} else {
Dispatch.dispatchEvent(new XFormsOptionalEvent(singleNodeControl));
}
}
}
// TODO: must reacquire control and test for relevance again
if (isAllowSendingReadonlyEvents) {
final boolean previousReadonlyState = singleNodeControl.wasReadonly();
final boolean newReadonlyState = singleNodeControl.isReadonly();

if (previousReadonlyState != newReadonlyState) {
if (newReadonlyState) {
Dispatch.dispatchEvent(new XFormsReadonlyEvent(singleNodeControl));
} else {
Dispatch.dispatchEvent(new XFormsReadwriteEvent(singleNodeControl));
}
}
}
} else if (control instanceof XFormsRepeatControl) {
((XFormsRepeatControl) control).dispatchRefreshEvents();
}
}
}
}

public Object getBackCopy() {

// Clone this
Expand Down Expand Up @@ -434,10 +270,6 @@ public List<XFormsControl> getChildren() {
return root != null ? root.childrenJava() : Collections.<XFormsControl>emptyList();
}

public Map<String, XFormsControl> getEffectiveIdsToControls() {
return controlIndex.getEffectiveIdsToControls();
}

public XFormsControl getControl(String effectiveId) {
// Delegate
return controlIndex.getControl(effectiveId);
Expand Down
38 changes: 13 additions & 25 deletions src/java/org/orbeon/oxf/xforms/XFormsControls.java
Original file line number Diff line number Diff line change
Expand Up @@ -314,27 +314,17 @@ public void doRefresh(XBLContainer container) {
// Update control bindings
final Controls.BindingUpdater updater = updateControlBindings();

if (currentControlTree.isAllowSendingRefreshEvents()) {
// There are potentially event handlers for UI events, so do the whole processing
// There are potentially event handlers for UI events, so do the whole processing

// Gather controls to which to dispatch refresh events
final List<String> controlsEffectiveIds = gatherControlsForRefresh();
// Gather controls to which to dispatch refresh events
final List<String> controlsEffectiveIds = gatherControlsForRefresh();

// "Actions that directly invoke rebuild, recalculate, revalidate, or refresh always have an immediate
// effect, and clear the corresponding flag."
refreshDone();
// "Actions that directly invoke rebuild, recalculate, revalidate, or refresh always have an immediate
// effect, and clear the corresponding flag."
refreshDone();

// Dispatch events
currentControlTree.dispatchRefreshEvents(controlsEffectiveIds);

} else {
// No UI events to send because there is no event handlers for any of them
indentedLogger.logDebug("controls", "refresh skipping sending of UI events because no listener was found", "container id", container.getEffectiveId());

// "Actions that directly invoke rebuild, recalculate, revalidate, or refresh always have an immediate
// effect, and clear the corresponding flag."
refreshDone();
}
// Dispatch events
currentControlTree.dispatchRefreshEvents(controlsEffectiveIds);

// Handle focus changes
Focus.updateFocusWithEvents(focusedBefore, updater.partialFocusRepeat());
Expand Down Expand Up @@ -408,15 +398,13 @@ public void doPartialRefresh(XFormsContainerControl containerControl) {
// Update bindings starting at the container control
final Controls.BindingUpdater updater = updateSubtreeBindings(containerControl);

if (currentControlTree.isAllowSendingRefreshEvents()) {
// There are potentially event handlers for UI events, so do the whole processing
// There are potentially event handlers for UI events, so do the whole processing

// Gather controls to which to dispatch refresh events
final List<String> eventsToDispatch = gatherControlsForRefresh(containerControl);
// Gather controls to which to dispatch refresh events
final List<String> eventsToDispatch = gatherControlsForRefresh(containerControl);

// Dispatch events
currentControlTree.dispatchRefreshEvents(eventsToDispatch);
}
// Dispatch events
currentControlTree.dispatchRefreshEvents(eventsToDispatch);

// Handle focus changes
Focus.updateFocusWithEvents(focusedBefore, updater.partialFocusRepeat());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,14 @@
import org.dom4j.Element;
import org.dom4j.QName;
import org.orbeon.oxf.xforms.XFormsConstants;
import org.orbeon.oxf.xforms.control.FocusableTrait;
import org.orbeon.oxf.xforms.control.XFormsControl;
import org.orbeon.oxf.xforms.control.XFormsValueControlBase;
import org.orbeon.oxf.xforms.control.XFormsValueFocusableControlBase;
import org.orbeon.oxf.xforms.xbl.XBLContainer;

/**
* Represents an xf:secret control.
*/
public class XFormsSecretControl extends XFormsValueControlBase implements FocusableTrait {
public class XFormsSecretControl extends XFormsValueFocusableControlBase { // TODO: move to Scala

// List of attributes to handle as AVTs
private static final QName[] EXTENSION_ATTRIBUTES = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,8 @@
import org.orbeon.oxf.util.IndentedLogger;
import org.orbeon.oxf.xforms.XFormsConstants;
import org.orbeon.oxf.xforms.XFormsUtils;
import org.orbeon.oxf.xforms.control.FocusableTrait;
import org.orbeon.oxf.xforms.control.XFormsControl;
import org.orbeon.oxf.xforms.control.XFormsValueControlBase;
import org.orbeon.oxf.xforms.control.XFormsValueFocusableControlBase;
import org.orbeon.oxf.xforms.xbl.XBLContainer;
import org.orbeon.oxf.xml.XMLUtils;
import org.orbeon.oxf.xml.dom4j.Dom4jUtils;
Expand All @@ -30,7 +29,7 @@
/**
* Represents an xf:textarea control.
*/
public class XFormsTextareaControl extends XFormsValueControlBase implements FocusableTrait {
public class XFormsTextareaControl extends XFormsValueFocusableControlBase { // TODO: move to Scala

// List of attributes to handle as AVTs
private static final QName[] EXTENSION_ATTRIBUTES = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,16 @@
package org.orbeon.oxf.xforms.control.controls;

import org.dom4j.Element;
import org.orbeon.oxf.xforms.control.FocusableTrait;
import org.orbeon.oxf.xforms.control.XFormsControl;
import org.orbeon.oxf.xforms.control.XFormsSingleNodeControl;
import org.orbeon.oxf.xforms.control.XFormsSingleNodeFocusableControlBase;
import org.orbeon.oxf.xforms.xbl.XBLContainer;

/**
* Represents an xf:trigger control.
*
* TODO: Use inheritance/interface to make this a single-node control that doesn't hold a value.
*/
public class XFormsTriggerControl extends XFormsSingleNodeControl implements FocusableTrait {
public class XFormsTriggerControl extends XFormsSingleNodeFocusableControlBase { // TODO: move to Scala
public XFormsTriggerControl(XBLContainer container, XFormsControl parent, Element element, String id) {
super(container, parent, element, id);
}
Expand Down
3 changes: 3 additions & 0 deletions src/java/org/orbeon/oxf/xforms/event/XFormsEvents.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ public class XFormsEvents {
public static final String XFORMS_OPTIONAL = "xforms-optional";
public static final String XFORMS_READWRITE = "xforms-readwrite";
public static final String XFORMS_READONLY = "xforms-readonly";
public static final String XXFORMS_VISITED = "xxforms-visited";
public static final String XXFORMS_UNVISITED = "xxforms-unvisited";

public static final String XFORMS_ENABLED = "xforms-enabled";
public static final String XFORMS_DISABLED = "xforms-disabled";

Expand Down
Loading

0 comments on commit f6c02b4

Please sign in to comment.