jamesej
05-24-2007, 08:39 AM
Hi
I imagine this is not the ideal venue for this proposal but I think the code will be useful to coders to see here and I'd appreciate advice as to how to submit this properly to the OL development process.
The idea is to modify LzFocus to provide new events, onfocusenter and onfocusexit.
The onfocusenter event is fired when focus moves from a view which is not a (non-lexical) child of the event-receiver to a view which is. The onfocusexit event is fired when focus moves from a view which is a (non-lexical) child of the event-receiver to a view which isn't. Any view (or node actually) can receive these events (doesn't have to be focussable itself) simply by declaring <event name="onfocusenter"/> for instance.
This simple structure solves a problem in OL which is very important in component design and is evident when you look at the code for the OL components. The comboboxes for instance are forced to use LzModeManager to determine when focus has moved from a view within the combo box to a view outside it in order to close the drop down at that point. There is also very commonly some kind of complex and inconsistent scheme for passing onblur events up from views inside a component to the parent.
I found it useful to come up with this code because I wanted to use a 'no save' data editing model: to do this you need reliably to know when focus has left a component or a form in order to be able to save the changes at that point. It was simply not possible to do this because the existing components suffered from the lack of a framework like this with the result that focus didn't behave at all predicatably when using them.
It was interesting when writing this code to see how powerful Javascript can be when applied to existing objects in OL. As a matter of interest, it is possible to use the approach here of changing standard OL classes at runtime to even put new attributes and methods onto LFC classes like LzView (you'd make changes to LzView.prototype in a runtime script). This power should be used carefully though or gross inefficiencies, unpredictable results or merely general hackiness could result.
Here's the code:
<library>
<!--- This section adds functionality to the LzFocus service -->
<script>
LzFocus.retainedfocus = null; //Like .lastfocus but retains last focussed view when all focus is lost
LzFocus.eventRecipient = null; //The object the event was raised on (useful to other objects catching the event)
// returns a view's parent for the purposes of determining focus ownership
LzFocus.getFocusParent =
function (v)
{
if (v == null)
return null;
else if (v['focustrap'])
return null;
else if (v instanceof floatinglist)
return v.attachtarget;
else if (v instanceof LzCanvas)
return null;
else if (v['immediateparent'])
return v.immediateparent;
else
return null;
}
// returns whether gp is a grandparent of the child c for the purposes of determining
// whether gp owns the focus of c.
LzFocus.focusChildOf =
function (c, gp)
{
if (c == null || gp == null) return false;
var p = c;
while (true)
{
p = LzFocus.getFocusParent(p);
if (p == null)
return false;
else if (p == gp)
return true;
}
}
</script>
<!-- call this canvas method to remove focus and focus containment from all views - you
might do this in the onbeforeunload DOM event in your host web page via some
browser integration method to be able to take focusexit action on the
OL application being closed or browsed away from -->
<method name="blurAll">
LzFocus.setFocus(null);
this.sendExitEnterEvents(LzFocus.retainedfocus, null);
LzFocus.retainedfocus = null;
</method>
<handler name="onfocus" reference="LzFocus" args="newf">
if (LzFocus.lastfocus != null)
LzFocus.retainedfocus = LzFocus.lastfocus;
if (newf != null)
this.sendExitEnterEvents(LzFocus.retainedfocus, newf);
</handler>
<!-- newf=null means remove focus and focus containment from all views -->
<method name="sendExitEnterEvents" args="lastf, newf">
<![CDATA[
// the following goes up the parent tree from the view losing focus
// to the first event-receiving parent shared with the view gaining focus
// (or the view gaining focus itself if it is event-receiving)
// sending onfocusexit events to all the appropriate intervening parents.
// it then goes up the parent tree from the view gaining focus
// sending onfocusenter events to all the event-receiving parents reached
// before the shared event-receiving parent.
var p = lastf;
var sharedContainer = null;
if (lastf == newf || LzFocus.focusChildOf(newf, lastf) || LzFocus.focusChildOf(lastf, newf))
return;
while (p != null)
{
if (p['onfocusexit'])
{
if (LzFocus.focusChildOf(newf, p))
{
sharedContainer = p;
p = null;
}
else
{
LzFocus.eventRecipient = p;
p.onfocusexit.sendEvent(newf);
LzFocus.eventRecipient = null;
}
}
p = LzFocus.getFocusParent(p);
}
p = newf;
while (p != sharedContainer && p != null)
{
if (p['onfocusenter'])
{
LzFocus.eventRecipient = p;
p.onfocusenter.sendEvent(lastf);
LzFocus.eventRecipient = null;
}
p = LzFocus.getFocusParent(p);
}
]]>
</method>
</library>
I imagine this is not the ideal venue for this proposal but I think the code will be useful to coders to see here and I'd appreciate advice as to how to submit this properly to the OL development process.
The idea is to modify LzFocus to provide new events, onfocusenter and onfocusexit.
The onfocusenter event is fired when focus moves from a view which is not a (non-lexical) child of the event-receiver to a view which is. The onfocusexit event is fired when focus moves from a view which is a (non-lexical) child of the event-receiver to a view which isn't. Any view (or node actually) can receive these events (doesn't have to be focussable itself) simply by declaring <event name="onfocusenter"/> for instance.
This simple structure solves a problem in OL which is very important in component design and is evident when you look at the code for the OL components. The comboboxes for instance are forced to use LzModeManager to determine when focus has moved from a view within the combo box to a view outside it in order to close the drop down at that point. There is also very commonly some kind of complex and inconsistent scheme for passing onblur events up from views inside a component to the parent.
I found it useful to come up with this code because I wanted to use a 'no save' data editing model: to do this you need reliably to know when focus has left a component or a form in order to be able to save the changes at that point. It was simply not possible to do this because the existing components suffered from the lack of a framework like this with the result that focus didn't behave at all predicatably when using them.
It was interesting when writing this code to see how powerful Javascript can be when applied to existing objects in OL. As a matter of interest, it is possible to use the approach here of changing standard OL classes at runtime to even put new attributes and methods onto LFC classes like LzView (you'd make changes to LzView.prototype in a runtime script). This power should be used carefully though or gross inefficiencies, unpredictable results or merely general hackiness could result.
Here's the code:
<library>
<!--- This section adds functionality to the LzFocus service -->
<script>
LzFocus.retainedfocus = null; //Like .lastfocus but retains last focussed view when all focus is lost
LzFocus.eventRecipient = null; //The object the event was raised on (useful to other objects catching the event)
// returns a view's parent for the purposes of determining focus ownership
LzFocus.getFocusParent =
function (v)
{
if (v == null)
return null;
else if (v['focustrap'])
return null;
else if (v instanceof floatinglist)
return v.attachtarget;
else if (v instanceof LzCanvas)
return null;
else if (v['immediateparent'])
return v.immediateparent;
else
return null;
}
// returns whether gp is a grandparent of the child c for the purposes of determining
// whether gp owns the focus of c.
LzFocus.focusChildOf =
function (c, gp)
{
if (c == null || gp == null) return false;
var p = c;
while (true)
{
p = LzFocus.getFocusParent(p);
if (p == null)
return false;
else if (p == gp)
return true;
}
}
</script>
<!-- call this canvas method to remove focus and focus containment from all views - you
might do this in the onbeforeunload DOM event in your host web page via some
browser integration method to be able to take focusexit action on the
OL application being closed or browsed away from -->
<method name="blurAll">
LzFocus.setFocus(null);
this.sendExitEnterEvents(LzFocus.retainedfocus, null);
LzFocus.retainedfocus = null;
</method>
<handler name="onfocus" reference="LzFocus" args="newf">
if (LzFocus.lastfocus != null)
LzFocus.retainedfocus = LzFocus.lastfocus;
if (newf != null)
this.sendExitEnterEvents(LzFocus.retainedfocus, newf);
</handler>
<!-- newf=null means remove focus and focus containment from all views -->
<method name="sendExitEnterEvents" args="lastf, newf">
<![CDATA[
// the following goes up the parent tree from the view losing focus
// to the first event-receiving parent shared with the view gaining focus
// (or the view gaining focus itself if it is event-receiving)
// sending onfocusexit events to all the appropriate intervening parents.
// it then goes up the parent tree from the view gaining focus
// sending onfocusenter events to all the event-receiving parents reached
// before the shared event-receiving parent.
var p = lastf;
var sharedContainer = null;
if (lastf == newf || LzFocus.focusChildOf(newf, lastf) || LzFocus.focusChildOf(lastf, newf))
return;
while (p != null)
{
if (p['onfocusexit'])
{
if (LzFocus.focusChildOf(newf, p))
{
sharedContainer = p;
p = null;
}
else
{
LzFocus.eventRecipient = p;
p.onfocusexit.sendEvent(newf);
LzFocus.eventRecipient = null;
}
}
p = LzFocus.getFocusParent(p);
}
p = newf;
while (p != sharedContainer && p != null)
{
if (p['onfocusenter'])
{
LzFocus.eventRecipient = p;
p.onfocusenter.sendEvent(lastf);
LzFocus.eventRecipient = null;
}
p = LzFocus.getFocusParent(p);
}
]]>
</method>
</library>