Monday, May 18, 2009

AutoScrolling Flex Form.

At work we have an extensive library for our internal ERP application. A component therein is our Form component. It does a lot of little things such as adding default keyListeners for regularly used form shortcuts, or a getFormValues that loops through all children (and all children of childrenContainers ad infinitum), returning a class that we use for most RemoteObject calls.

Last week I added an autoScroll property to that internal form, using code from Daryl Bantarri's AutoScroll utility class (a class that has a single static method). I had a slight problem with it that required a slight edit or two (breaking the method into a few more manageable functions). (and, of course, my edits had to conform to our internal architectural standards and naming conventions)... so if the "doFunctionName," "canRuleName" or redundant returns in the rule are annoying -- that's my fault --> I wrote a lot of those ridiculous rules over here.

Also, in the comments of Daryl's post, some talked of removing the updateComplete listener. We add a lot of form components dynamically to forms, so listening for anything after an updateDisplayList, etc., is a necessity for our framework. Feel free to get rid of that updateComplete listener if need be, especially if your intended use is to hard-code your components in the form.

A shout out to Patrick who is part owner of Webapper... fellow Northern Coloradan whom I've met a small handful of times at random local tech meetups.


private var _autoScroll : Boolean = false;
public function get autoScroll():Boolean
{
return _autoScroll;
}
public function set autoScroll(value:Boolean):void
{
_autoScroll = value;
if(value)
activateAutoscrollListeners();
}



private function activateAutoscrollListeners():void
{
var container : DisplayObjectContainer = this.parent;
if(container)
{
container.addEventListener(FlexEvent.UPDATE_COMPLETE, doAutoScroll);
container.addEventListener(FocusEvent.FOCUS_IN, doAutoScroll);
}
}


also, on creationComplete (or earlier, since we only need the this.parent in activeAutoScrollLIsteners()):

if(autoScroll)
activateAutoscrollListeners();



private function canAutoScroll(container:Container, focusObject:DisplayObject):Boolean
{
if(!focusObject)
return false;

if(!container)
return false;

if(!container.verticalScrollBar)
return false;

if(!this.contains(focusObject))
return false;

if(focusObject == this)
return false;

return true;
}

/**
* doAutoScroll is the eventListener for the parent container's updateComplete and focusIn. if(canAutoScroll), this method calls
* completeAutoScroll and updates the parent container's verticalScrollPosition.
*
*/
private function doAutoScroll(event:Event):void
{
var container : Container = Container(event.currentTarget);
//Don't want displayObject.Y of event.target. getFocus() is more accurate (i.e., mx.controls.combobox).
var focusObject : DisplayObject = DisplayObject(container.focusManager.getFocus());

if(canAutoScroll(container,focusObject))
{
var itemParent : DisplayObjectContainer = focusObject.parent;
var focusObjectTopY : int = (focusObject.parent == this) ? focusObject.y : addTotalYoffSets(focusObject);
if(container.verticalScrollPosition != focusObjectTopY)
{
completeAutoScroll(container, focusObjectTopY, focusObject);
}
}
}

private function addTotalYoffSets(focusObject:DisplayObject):int
{
var parentContainer : DisplayObjectContainer = focusObject.parent;
var focusObjectTopY : int = focusObject.y ;

while(parentContainer != this.parent) {
focusObjectTopY += parentContainer.y;
parentContainer = parentContainer.parent;
}

return focusObjectTopY;
}

private function completeAutoScroll(container:Container, focusObjectTopY:int, focusObject:DisplayObject):void
{
var focusObjectBottomY : int = focusObjectTopY + focusObject.height;
var lastVisibleY : int = container.height + container.verticalScrollPosition;

if(container.horizontalScrollBar)
lastVisibleY -= container.horizontalScrollBar.height;


if(focusObjectTopY < container.verticalScrollPosition)
{
container.verticalScrollPosition = Math.max( 0, focusObjectTopY - 5 );
}
else if(focusObjectBottomY > lastVisibleY)
{
//scroll down. +5 pixels for good measure.
var newPosition : int = Math.min( container.verticalScrollBar.maxScrollPosition, (container.verticalScrollPosition + (focusObjectBottomY - lastVisibleY)) );
container.verticalScrollPosition = newPosition + 5;
}

}

Monday, May 11, 2009

A Note on SAP / Flex and iPerspective

Ridiculously long time since a post, but, for anyone who knew I went, my trip to Palo Alto wasn't what I had expected. I thought I'd be writing an interface to parse the SAP metadata used to render an SAP screen and render it in a Flex application.

It appears as though it had already been done... not sure if it was/is an open source project, but one thing that did come out of my own experimentation is a little interface called iPerspective.

iPerspective: In the app I was building it seemed to make sense to (instead of manage components' interactivity through states) dispatch a PerspectiveEvent (listened for in the App and pushed back down to all registered components), changing the look of the ApplicationControlBar, and anything else that is a mx.core.container that implements iPerspective.

I'll work with it next week if/when I have time -- if it makes sense in practice, maybe I'll open source it.
Anyway, no real stories. Hope all is well out there.