package { import flash.events.Event; import flash.events.KeyboardEvent; import flash.events.MouseEvent; import flash.ui.Keyboard; import mx.controls.TextInput; import mx.events.FlexEvent; /** * @author Steven Peeters * Adobe Certified Instructor * steven@multimediacollege.be * * This class extends the basic TextInput class to add a * currency label at the front or the back of the value. * This currency label doens't have to be 1 single character * like '€' or '$'. It can be as long as you like, so for * example, you can use 'EUR' or 'USD'... * * You can also specify the precision and it will automatically * be made visible in the component. */ public class CurrencyInput extends TextInput { // Some constants to make life easier. public static const PRE:String = "PRE"; public static const POST:String = "POST"; // the internal variables private var _currencySymbol:String; private var _symbolPosition:String; private var _precision:Number; /** * The constructor. Here we set the defaults for the component * and attach the event listeners. */ public function CurrencyInput() { super(); this.restrict = "0-9.,"; _currencySymbol = ""; // Set a default precision of 2 decimals. _precision = 2; addEventListener(Event.CHANGE, changeHandler); addEventListener(FlexEvent.CREATION_COMPLETE, creationCompleteHandler); addEventListener(MouseEvent.CLICK,reposition,true); } /** * @param value The precision to set * * This method will be used to set the precision for the component */ public function set precision(value:Number):void { _precision = value; } /** * @return Number The precision * * This method will return the precision that was set for the component */ public function get precision():Number { return _precision; } /** * @param value The currency symbol to set * * This method will be used to set the currency symbol for the component. */ public function set currencySymbol(value:String):void { _currencySymbol = value; //super.restrict = super.restrict + value; } /** * @return String The currency symbol * * This method will return the currency symbol that was set for the component */ public function get currencySymbol():String { return _currencySymbol; } /** * @param value The position where to put the currency symbol * * This method will be used to set the position to where the * currency symbol should be located in view of the value. For * convenience, I've made it inspectable, so code completion * should show you the possible values in MXML code. */ [Inspectable(enumeration = "PRE,POST")] public function set symbolPosition(value:String):void { _symbolPosition = value; } /** * @return Number The position where the currency sybol is placed * * This method will return the position where the currency sybol is placed */ public function get symbolPosition():String { return _symbolPosition; } /** * @param value The actual value to set. * * This method will render the currency formatted string according * to the specified properties. */ override public function set text(value:String):void { if(value != null) { var theText:String = String(value); var arr:Array = theText.split("."); var tmp:String; // Regain the decimal value by stripping the currency symbol if(theText.indexOf(_currencySymbol) > -1) { switch(_symbolPosition){ case PRE: theText = theText.slice(_currencySymbol.length); break; case POST: theText = theText.slice(0,theText.length - _currencySymbol.length); break; } } // The value was split up using the '.', but in some cases, this should // be the ',' as a decimal separator. If not, then convert it to a comma // anyway. if(arr.length == 1) { arr = theText.split(","); if(arr.length == 1) { if(!isNaN(Number(theText))) { tmp = theText; } else { tmp = ""; } } else { if(!isNaN(Number(arr[0])) && !isNaN(Number(arr[1]))) { tmp = theText; } else { tmp = ""; } } } else { if(!isNaN(Number(arr[0])) && !isNaN(Number(arr[1]))) { tmp = arr[0] + "," + arr[1]; } else { tmp = ""; } } // Set the decimal string again, but this time with the comma // as a decimal sign super.text = tmp; } else { super.text = ""; } // If a currency symbl was specified, we attach it at the proper position. if((_currencySymbol != null) && (_currencySymbol.length > 0)) { switch(_symbolPosition){ case PRE: super.text = _currencySymbol + super.text; break; case POST: super.text = super.text + _currencySymbol; break; } } } /** * @return String The textual value of the component * * This method will simply return the textual value of the component, * including any decimal signs if there is a precision specified. */ override public function get text():String { return super.text; } /** * @param event The event object that is dispatched by the Flex framework * * This method is executed when the component has been initialised completely. * Here I set the initial value and attach the currency symbol if it is * defined and a position for it has been specified. */ private function creationCompleteHandler(event:FlexEvent):void { // When a precision has been specified, adjust the diplayed // value with a decimal sign. if(_precision > -1) { super.text = "0,"; for(var i:int = 0; i < _precision; i++) { super.text = super.text + "0"; } } // Attach the currency symbol at the proper position if // the symbol has been specified. if((_currencySymbol != null) && (_currencySymbol.length > 0)) { switch(_symbolPosition){ case PRE: super.text = _currencySymbol + super.text; break; case POST: super.text = super.text + _currencySymbol; break; } } } /** * @param event The event object that is dispatched by the Flex framework * * This method is executed whenever the value of the component changes. Here * we determine that the decimal positions that were specified are always * visible. If they are not filled out by the user, we replace them by zeroes. */ private function changeHandler(event:Event):void { var i:int; var arr:Array; // Regain the numeric value from the component by stripping off // the currency symbol if one was specified. if((_currencySymbol != null) && (_currencySymbol.length > 0)){ switch(_symbolPosition){ case PRE: i = 0; while(_currencySymbol.indexOf(super.text.charAt(i++)) > -1); super.text = super.text.slice(--i); break; case POST: i = super.text.length; while(_currencySymbol.indexOf(super.text.charAt(--i)) > -1); super.text = super.text.slice(0,++i); break; } } // Eliminate duplicate comma characters, because the value here // is already the changed value. if(super.text.indexOf(",") != super.text.lastIndexOf(",")) { // If the text already contains a comma, then the new value // contains 2 comma's for now. We must then eliminate one // of them, depending on the cursor position. var begin:Number = selectionBeginIndex; arr = super.text.split(","); if(begin == super.text.indexOf(",") + 1) { super.text = arr[0] + arr[1] + "," + arr[2]; } else { super.text = arr[0] + "," + arr[1] + arr[2]; } } else { // If the user typed a decimal point, we have to replace // it by a comma, but only if there wasn't already a // comma present in the text. if(super.text.indexOf(".") > -1) { arr = super.text.split("."); if(super.text.indexOf(",") > -1) { super.text = arr[0] + arr[1]; } else { super.text = arr[0] + "," + arr[1]; } } } // Now we possibly have to limit the decimal positions // depending on the value of the property 'precision'. arr = super.text.split(","); if(arr.length > 1) { // We have a comma in the text. if((arr[1] as String).length > _precision) { super.text = arr[0] + "," + (arr[1] as String).substr(0,_precision); } else { for(i = (arr[1] as String).length; i < _precision; i++) { super.text = text + "0"; } } } else { super.text = super.text + ","; for(i = 0; i < _precision; i++) { super.text = super.text + "0"; } } // If the first character of the text is the decimal point, // we have to make it a zero. if(super.text.indexOf(",") == 0) { super.text = "0" + super.text; } // Attach the currency symbol at the proper position again // if one is specified. if((_currencySymbol != null) && (_currencySymbol.length > 0)) { switch(_symbolPosition){ case PRE: super.text = _currencySymbol + super.text; break; case POST: super.text = super.text + _currencySymbol; break; } } } /** * @param event The event object that is dispatched by the Flex framework * * This method is executed upon each an every keystroke. Here is determined * what to do with specific keys like LEFT, RIGHT, HOME and END. The DELETE * and BACKSPACE are also handled separately, because we cannot delete the * currency symbol for example. * If the user types a number and a comma is encountered, it is skipped so * the user doesn't have to worry at all about typing the decimal sign. */ override protected function keyDownHandler(event:KeyboardEvent):void { var pos:Number; var str:String = super.text.substring(selectionBeginIndex, selectionEndIndex); if(event.keyCode == Keyboard.BACKSPACE) { if((_symbolPosition == PRE) && (super.text.substring(0, selectionBeginIndex) == _currencySymbol)) { // Do nothing setSelection(selectionBeginIndex, selectionBeginIndex); event.preventDefault(); } else { if(str.length == 0) { if(selectionBeginIndex > 0) { // Normal cursor position; no selection in text if(super.text.charAt(selectionBeginIndex - 1) == ",") { textField.setSelection(selectionBeginIndex - 1, selectionBeginIndex - 1); } if(super.text.indexOf(",") < selectionBeginIndex) { super.text = super.text.substring(0, selectionBeginIndex) + "0" + super.text.substr(selectionBeginIndex); super.textField.text = super.text; } } } else { // A part of the text is selected. Here we must // determine whether or not the comma is within // the selection. pos = super.text.indexOf(",") if(pos > -1) { while(++pos < selectionEndIndex) { super.text = super.text.substring(0, pos) + "0" + super.text.substr(pos + 1); super.textField.text = super.text; } if(selectionBeginIndex <= super.text.indexOf(",")) { textField.setSelection(selectionBeginIndex, super.text.indexOf(",")); } } super.keyDownHandler(event); } } } else if(event.keyCode == Keyboard.DELETE) { if((_symbolPosition == POST) && (super.text.substr(selectionBeginIndex) == _currencySymbol)) { // Do nothing setSelection(selectionBeginIndex, selectionBeginIndex); event.preventDefault(); } else { if(str.length == 0) { if(selectionEndIndex < this.length) { // Normal cursor position; no selection in text if(super.text.charAt(selectionBeginIndex) == ",") { textField.setSelection(selectionBeginIndex + 1, selectionBeginIndex + 1); } if(super.text.indexOf(",") < selectionBeginIndex) { super.text = super.text.substring(0, selectionBeginIndex) + "0" + super.text.substr(selectionBeginIndex); super.textField.text = super.text; textField.setSelection(selectionBeginIndex + 1, selectionBeginIndex + 1); } } } else { // A part of the text is selected. Here we must // determine whether or not the comma is within // the selection. pos = super.text.indexOf(",") if(pos > -1) { while(++pos < selectionEndIndex) { super.text = super.text.substring(0, pos) + "0" + super.text.substr(pos + 1); super.textField.text = super.text; } if(selectionBeginIndex <= super.text.indexOf(",")) { textField.setSelection(selectionBeginIndex, super.text.indexOf(",")); } } super.keyDownHandler(event); } } } else if(event.keyCode == Keyboard.LEFT) { if((_symbolPosition == PRE) && (super.text.substring(0, selectionBeginIndex) == _currencySymbol)) { setSelection(selectionBeginIndex, selectionBeginIndex); event.preventDefault(); } } else if(event.keyCode == Keyboard.RIGHT) { if((_symbolPosition == POST) && (super.text.substr(selectionBeginIndex) == _currencySymbol)) { setSelection(selectionBeginIndex, selectionBeginIndex); event.preventDefault(); } } else if((event.keyCode == Keyboard.END) ||(event.keyCode == Keyboard.DOWN) ||(event.keyCode == Keyboard.PAGE_DOWN)) { if(_symbolPosition == POST) { pos = super.text.length - _currencySymbol.length; setSelection(pos, pos); event.preventDefault(); } } else if((event.keyCode == Keyboard.HOME) || (event.keyCode == Keyboard.UP) || (event.keyCode == Keyboard.PAGE_UP)) { if(_symbolPosition == PRE) { pos = _currencySymbol.length; setSelection(pos, pos); event.preventDefault(); } } pos = super.text.indexOf(",") if((event.charCode > 47) && (event.charCode < 58)) { if(pos > -1) { if(pos < selectionBeginIndex) { // Numeric character must overwrite the places behind // the decimal point. super.text = super.text.substring(0, selectionBeginIndex) + super.text.substr(selectionBeginIndex + 1); super.textField.text = super.text; } else if(pos >= selectionBeginIndex) { // If the part before the decimal point is zero // the first numeric character typed there will // replace that zero. var i:int = 0; if(_symbolPosition == PRE) { i = _currencySymbol.length; } if(Number(super.text.substring(i,pos)) == 0) { super.text = super.text.substr(pos); super.textField.text = super.text; if(_symbolPosition == PRE) { super.textField.text = _currencySymbol + super.text; super.textField.setSelection(i,i); } else { super.textField.setSelection(0,0); } } } } } // if the user types a dot or comma and the cursor is // still before the decimal sign, then the cursor just // jumps to the first position after the decimal sign. if((event.charCode == 44) || (event.charCode == 46)) { if((pos > selectionBeginIndex) && (selectionBeginIndex == selectionEndIndex)) { super.textField.setSelection(pos, pos); } } } /** * @param event The event object that is dispatched by the Flex framework * * This method is executed whenever the user clicks within the component. * I then possibly reposition the cursor to make sure it is positioned * at the beginning of the editable part. */ private function reposition(event:MouseEvent):void { if((_currencySymbol != null) && (_currencySymbol.length > 0)) { var p:Number = this.selectionBeginIndex; if((_symbolPosition == PRE) && (this.selectionBeginIndex < _currencySymbol.length)) { this.setSelection(_currencySymbol.length, _currencySymbol.length); } } } } }