In the previous post, I have tried to play around with the width of the drop down popup of the Flex DropDownList. An interesting observation is that the drop down is always aligned to the left edge of the open button of the DropDownList. Now it could be a requirement to open it aligned to the right edge or even centered with respect to the open button. So how to achieve that?
For this we need to dig into the DropDownListSkin. We can see that the component opens the drop down popup using a PopUpAnchor which is aligned with left = 0. Now this causes, the popup to stick to the left edge. So, in order to right align it we need to clear the left constraint and make right = 0. For clearing the left constraint, all that we have to do is to set it to undefined!
Now, it works fine if we have fixed width for the drop down but in a previous post, we have seen that we can have the width of the drop down to auto expand as well. This auto expansion breaks our alignment and the popup does not remain aligned to the right edge.
A simple solution that I have employed is to keep track of the popup width by using Binding and whenever the width changes, I just re-position the popup. The same method can be used to position the popup in the center. All you have to do is to calculate the left/right constraint position from where the popup needs to open up.
A sample application showing both the auto expanding and the fixed width DropDownList is shown below. Three buttons are provided to change the alignment of the drop down on the fly. You can also see the behavior of the lists when they are placed near the edges of the screen as well.
Since the drop down uses PopUpAnchor, it has a built in functionality which does not allow the popup to go out of bounds of the screen and that works perfectly well for us!
So here is my CustomDropDownList, which can be used to position the drop down to either align with the left edge, right edge or be centrally aligned.
package com.codedebugged.dropdown.alignment
{
import mx.binding.utils.BindingUtils;
import mx.binding.utils.ChangeWatcher;
import mx.events.FlexEvent;
import spark.components.DropDownList;
import spark.components.PopUpAnchor;
public class CustomDropDownList extends DropDownList
{
public static const JUSTIFIED : String = "JUSTIFIED";
public static const LEFT_EDGE_ALIGNED : String = "LEFT_EDGE_ALIGNED";
public static const RIGHT_EDGE_ALIGNED : String = "RIGHT_EDGE_ALIGNED";
public function CustomDropDownList()
{
super();
setStyle( "skinClass", CustomDropDownListSkin );
}
[SkinPart]
public var popUp : PopUpAnchor;
private var _changeWatcher : ChangeWatcher;
private var _dropDownWidth : Number = NaN;
private var _popupPosition : String = LEFT_EDGE_ALIGNED;
private var dropDownWidthChanged : Boolean;
private var popupPositionChanged : Boolean;
[Bindable]
public function get dropDownWidth() : Number
{
return _dropDownWidth;
}
public function set dropDownWidth( value : Number ) : void
{
if (_dropDownWidth != value)
{
_dropDownWidth = value;
dropDownWidthChanged = true;
invalidateProperties();
}
}
[Inspectable( enumeration = "LEFT_EDGE_ALIGNED,JUSTIFIED,RIGHT_EDGE_ALIGNED" )]
[Bindable]
public function get popupPosition() : String
{
return _popupPosition;
}
public function set popupPosition( value : String ) : void
{
if (_popupPosition != value)
{
_popupPosition = value;
popupPositionChanged = true;
invalidateDisplayList();
}
}
override protected function commitProperties() : void
{
super.commitProperties();
if (dropDownWidthChanged)
{
dropDownWidthChanged = false;
setDropDownWidth();
}
}
override protected function partAdded( partName : String, instance : Object ) : void
{
super.partAdded( partName, instance );
if (instance == popUp)
{
popUp.addEventListener( FlexEvent.CREATION_COMPLETE, handlePopupCreation );
}
else if (instance == dropDown)
{
setDropDownWidth();
}
}
override protected function partRemoved( partName : String, instance : Object ) : void
{
super.partRemoved( partName, instance );
if (instance == popUp)
{
popUp.removeEventListener( FlexEvent.CREATION_COMPLETE, handlePopupCreation );
if (_changeWatcher)
{
_changeWatcher.unwatch();
_changeWatcher = null;
}
}
}
override protected function updateDisplayList( unscaledWidth : Number, unscaledHeight : Number ) : void
{
super.updateDisplayList( unscaledWidth, unscaledHeight );
if (popupPositionChanged)
{
popupPositionChanged = false;
setPopupPosition();
}
}
private function handlePopupCreation( event : FlexEvent ) : void
{
setPopupPosition();
}
private function justifyPopup() : void
{
if (popUp && popUp.popUp)
{
popUp.left = (this.width - popUp.popUp.width) / 2;
}
}
private function leftifyPopup() : void
{
if (popUp && popUp.popUp)
{
popUp.left = (this.width - popUp.popUp.width);
}
}
private function refreshPopup( obj : Object = null ) : void
{
if (popUp)
{
if (popupPosition == JUSTIFIED)
{
justifyPopup();
}
else if (popupPosition == RIGHT_EDGE_ALIGNED)
{
leftifyPopup();
}
popUp.updatePopUpTransform();
}
}
private function setDropDownWidth() : void
{
if (dropDown && !isNaN( dropDownWidth ))
{
dropDown.width = dropDownWidth;
}
}
private function setPopupPosition( object : Object = null ) : void
{
if (popUp)
{
if (!_changeWatcher)
{
_changeWatcher = BindingUtils.bindSetter( refreshPopup, popUp, [ "popUp", "width" ], false, true );
}
if (popupPosition == LEFT_EDGE_ALIGNED)
{
popUp.right = 0;
popUp.left = 0;
}
else if (popupPosition == JUSTIFIED)
{
justifyPopup();
popUp.right = undefined;
}
else if (popupPosition == RIGHT_EDGE_ALIGNED)
{
leftifyPopup();
popUp.right = undefined;
}
}
}
}
}
For this we need to dig into the DropDownListSkin. We can see that the component opens the drop down popup using a PopUpAnchor which is aligned with left = 0. Now this causes, the popup to stick to the left edge. So, in order to right align it we need to clear the left constraint and make right = 0. For clearing the left constraint, all that we have to do is to set it to undefined!
Now, it works fine if we have fixed width for the drop down but in a previous post, we have seen that we can have the width of the drop down to auto expand as well. This auto expansion breaks our alignment and the popup does not remain aligned to the right edge.
A simple solution that I have employed is to keep track of the popup width by using Binding and whenever the width changes, I just re-position the popup. The same method can be used to position the popup in the center. All you have to do is to calculate the left/right constraint position from where the popup needs to open up.
A sample application showing both the auto expanding and the fixed width DropDownList is shown below. Three buttons are provided to change the alignment of the drop down on the fly. You can also see the behavior of the lists when they are placed near the edges of the screen as well.
Since the drop down uses PopUpAnchor, it has a built in functionality which does not allow the popup to go out of bounds of the screen and that works perfectly well for us!
So here is my CustomDropDownList, which can be used to position the drop down to either align with the left edge, right edge or be centrally aligned.
package com.codedebugged.dropdown.alignment
{
import mx.binding.utils.BindingUtils;
import mx.binding.utils.ChangeWatcher;
import mx.events.FlexEvent;
import spark.components.DropDownList;
import spark.components.PopUpAnchor;
public class CustomDropDownList extends DropDownList
{
public static const JUSTIFIED : String = "JUSTIFIED";
public static const LEFT_EDGE_ALIGNED : String = "LEFT_EDGE_ALIGNED";
public static const RIGHT_EDGE_ALIGNED : String = "RIGHT_EDGE_ALIGNED";
public function CustomDropDownList()
{
super();
setStyle( "skinClass", CustomDropDownListSkin );
}
[SkinPart]
public var popUp : PopUpAnchor;
private var _changeWatcher : ChangeWatcher;
private var _dropDownWidth : Number = NaN;
private var _popupPosition : String = LEFT_EDGE_ALIGNED;
private var dropDownWidthChanged : Boolean;
private var popupPositionChanged : Boolean;
[Bindable]
public function get dropDownWidth() : Number
{
return _dropDownWidth;
}
public function set dropDownWidth( value : Number ) : void
{
if (_dropDownWidth != value)
{
_dropDownWidth = value;
dropDownWidthChanged = true;
invalidateProperties();
}
}
[Inspectable( enumeration = "LEFT_EDGE_ALIGNED,JUSTIFIED,RIGHT_EDGE_ALIGNED" )]
[Bindable]
public function get popupPosition() : String
{
return _popupPosition;
}
public function set popupPosition( value : String ) : void
{
if (_popupPosition != value)
{
_popupPosition = value;
popupPositionChanged = true;
invalidateDisplayList();
}
}
override protected function commitProperties() : void
{
super.commitProperties();
if (dropDownWidthChanged)
{
dropDownWidthChanged = false;
setDropDownWidth();
}
}
override protected function partAdded( partName : String, instance : Object ) : void
{
super.partAdded( partName, instance );
if (instance == popUp)
{
popUp.addEventListener( FlexEvent.CREATION_COMPLETE, handlePopupCreation );
}
else if (instance == dropDown)
{
setDropDownWidth();
}
}
override protected function partRemoved( partName : String, instance : Object ) : void
{
super.partRemoved( partName, instance );
if (instance == popUp)
{
popUp.removeEventListener( FlexEvent.CREATION_COMPLETE, handlePopupCreation );
if (_changeWatcher)
{
_changeWatcher.unwatch();
_changeWatcher = null;
}
}
}
override protected function updateDisplayList( unscaledWidth : Number, unscaledHeight : Number ) : void
{
super.updateDisplayList( unscaledWidth, unscaledHeight );
if (popupPositionChanged)
{
popupPositionChanged = false;
setPopupPosition();
}
}
private function handlePopupCreation( event : FlexEvent ) : void
{
setPopupPosition();
}
private function justifyPopup() : void
{
if (popUp && popUp.popUp)
{
popUp.left = (this.width - popUp.popUp.width) / 2;
}
}
private function leftifyPopup() : void
{
if (popUp && popUp.popUp)
{
popUp.left = (this.width - popUp.popUp.width);
}
}
private function refreshPopup( obj : Object = null ) : void
{
if (popUp)
{
if (popupPosition == JUSTIFIED)
{
justifyPopup();
}
else if (popupPosition == RIGHT_EDGE_ALIGNED)
{
leftifyPopup();
}
popUp.updatePopUpTransform();
}
}
private function setDropDownWidth() : void
{
if (dropDown && !isNaN( dropDownWidth ))
{
dropDown.width = dropDownWidth;
}
}
private function setPopupPosition( object : Object = null ) : void
{
if (popUp)
{
if (!_changeWatcher)
{
_changeWatcher = BindingUtils.bindSetter( refreshPopup, popUp, [ "popUp", "width" ], false, true );
}
if (popupPosition == LEFT_EDGE_ALIGNED)
{
popUp.right = 0;
popUp.left = 0;
}
else if (popupPosition == JUSTIFIED)
{
justifyPopup();
popUp.right = undefined;
}
else if (popupPosition == RIGHT_EDGE_ALIGNED)
{
leftifyPopup();
popUp.right = undefined;
}
}
}
}
}
A small modification is required to the skin as well for which I have created my CustomDropDownListSkin which has this minor modification :
<s:PopUpAnchor id="popUp"
displayPopUp.normal="false"
displayPopUp.open="true"
includeIn="open"
bottom="0"
popUpPosition="below"
itemDestructionPolicy="never"
top="{height}"
popUpWidthMatchesAnchorWidth="false">
The component provides a popupPosition property which can be set to LEFT_EDGE_ALIGNED, RIGHT_EDGE_ALIGNED or JUSTIFIED depending on the your requirement. Easy as that!
The same trick can be used to achieve any kind of custom alignment as well. All you have to figure out is the starting position of your popup and position the popupAnchor to it.
Download the source code.