Truncate Spark Label in the middle and showTruncationTip

Found a little handy customization of the Spark Label control that truncates the label in the middle with a (…). However it didn’t show the full label text on roll over when setting the showTruncationTip=”true” property of the control. I added a few little lines of code to fix it.

The code stores the original value in a variable called “_trueText”, so that just needs to be the value you pass to the toolTip when showTruncationTip=”true”. You need to override the mx_internal function to accomplish this.

override mx_internal function setIsTruncated(value:Boolean):void
		{
			if (_isTruncated != value)
			{
				_isTruncated = value;
				if (showTruncationTip)
					toolTip = _isTruncated ? _trueText : null;
				dispatchEvent(new Event("isTruncatedChanged"));
			}
		}

Then you need to add one little bit of code in the truncateTextMiddle method that will tell the control that the text is indeed truncated, setIsTruncated(true); should be at the end of that method around line 153 in the code snippet below:

	public function truncateTextMiddle(fullText:String, widthToTruncate:Number) : String
		{
			if (!(fullText) || fullText.length < 3 || !this.parent)
			{
				// skip any truncating if no styles (no parent),
				// or text is too small
				return fullText;
			}
			
			// add paddings for some font oversize issues
			var paddingWidth:Number =
				UITextField.mx_internal::TEXT_WIDTH_PADDING +
				this.getStyle("paddingLeft") + this.getStyle("paddingRight");
			
			// Skip if width is too small
			if (widthToTruncate < paddingWidth + 10) return fullText;
			
			// Prepare measurement object
			// We create new TextField, and copy styles for it from this object
			// We cannot re-use internal original text field instance because
			// it will cause event firing in process of text measurement
			var measurementField:TextField = new TextField();
			
			// Clear so measured text will not get old styles.
			measurementField.text = "";
			
			// Copy styles into TextField
			var textStyles:UITextFormat = this.determineTextFormatFromStyles();
			measurementField.defaultTextFormat = textStyles;
			var sm:ISystemManager = this.systemManager;
			if (textStyles.font)
			{
				measurementField.embedFonts = sm != null && sm.isFontFaceEmbedded(textStyles);
			}
			else
			{
				measurementField.embedFonts = false;
			}
			if (textStyles.antiAliasType) {
				measurementField.antiAliasType = textStyles.antiAliasType;
			}
			if (textStyles.gridFitType) {
				measurementField.gridFitType = textStyles.gridFitType;
			}
			if (!isNaN(textStyles.sharpness)) {
				measurementField.sharpness = textStyles.sharpness;
			}
			if (!isNaN(textStyles.thickness)) {
				measurementField.thickness = textStyles.thickness;
			}
			
			// Perform initial measure of text and check if need truncating at all
			
			// To measure text, we set it to measurement text field
			// and get line metrics for first line
			measurementField.text = fullText;
			var fullTextWidth:Number = measurementField.getLineMetrics(0).width + paddingWidth;
			if(fullTextWidth > widthToTruncate){
				// get width of ...
				measurementField.text = "...";
				var dotsWidth:Number = measurementField.getLineMetrics(0).width;
				
				// Find out what is the half of truncated text without ...
				var halfWidth : Number = (widthToTruncate - paddingWidth - dotsWidth) / 2;
				
				// Make a rough estimate of how much chars we need to cut out
				// This saves steps of character-by-character preocessing
				measurementField.text = "s";
				var charWidth:Number = measurementField.getLineMetrics(0).width;
				var charsToTruncate:int = Math.round(
					((fullTextWidth - paddingWidth) / 2 - halfWidth) /
					charWidth) + 2;
				
				// allow some distortion to account fractional widths part
				halfWidth = halfWidth - 0.5;
				
				// Below algorithm makes rough middle-truncating
				// Then it is corrected by adding or removing
				// characters for each part until reach required
				// width for each half. Algorith does checks
				// (min max and loop ciodnitions) so that string
				// cannot be less then one character for each half
				
				// see if right part of text approximately fits into half width
				var rightPart:String;
				var widthWithNextChar:Number;
				
				var len:int = fullText.length;
				var currLoc:int = Math.min(len/2 + charsToTruncate + 1, len-1);
				measurementField.text = fullText.substr(currLoc);
				var rightPartWidth:Number = measurementField.getLineMetrics(0).width;
				
				if (rightPartWidth > halfWidth) {
					// throw away characters until fits
					currLoc++;
					while (rightPartWidth > halfWidth && currLoc < len) {
						measurementField.text = fullText.charAt(currLoc);
						rightPartWidth -= measurementField.getLineMetrics(0).width;
						currLoc++;
					}
					rightPart = fullText.substr(currLoc - 1);
				} else {
					// try to add characters one-by-one and
					// see if it still fits
					widthWithNextChar = 0;
					do {
						currLoc--;
						rightPartWidth += widthWithNextChar;
						measurementField.text = fullText.charAt(currLoc);
						widthWithNextChar = measurementField.getLineMetrics(0).width;
					} while (rightPartWidth + widthWithNextChar <= halfWidth && currLoc > 0);
					rightPart = fullText.substr(currLoc + 1);
				}
				
				// Do the same with left part, but compare overall string
				// Overall is needed because character-by character
				// would not give us correct total width of string -
				// somehow overall text is measured with sapcers etc. and
				// also there are rounding issues.
				// This way, and by putting left part calculating as last, we allow
				// left part might be larger (may become more than half).
				
				// allow some distortion in widths fractions
				widthToTruncate = widthToTruncate - 0.5 - paddingWidth;
				
				currLoc = Math.max(len/2 - charsToTruncate, 1);
				measurementField.text = fullText.substr(0, currLoc) +
					"..." + rightPart;
				var truncatedWidth:Number = measurementField.getLineMetrics(0).width;
				if (truncatedWidth > widthToTruncate) {
					// throw away characters until fits
					currLoc--;
					while (truncatedWidth > widthToTruncate && currLoc > 0) {
						measurementField.text = fullText.substr(0, currLoc) +
							"..." + rightPart;
						truncatedWidth = measurementField.getLineMetrics(0).width;
						currLoc--;
					}
					currLoc++;
				} else {
					// try to add characters one-by-one and
					// see if it still fits
					do {
						currLoc++;
						measurementField.text = fullText.substr(0, currLoc) +
							"..." + rightPart;
						widthWithNextChar = measurementField.getLineMetrics(0).width;
					} while (widthWithNextChar <= widthToTruncate &&
						currLoc < len-1);
					currLoc--;
				}

				setIsTruncated(true);
				
				return fullText.substr(0, Math.max(currLoc,1)) +
					"..." + rightPart;
			}
			return fullText;
			
		}

The original post can be found here.

-Mr

Truncating HTML Text

With Flex truncating text within a Label control is easily done by setting the property “truncateToFit” to true. This parameter doesn’t do much when using the Text or TextArea controls in Flex. To truncate text in these controls you would have to build your own function to count characters and add the ellipses. There is one good example of creating your own custom Text control that truncates the text in the same manner as the Label control based on size of the control.
Continue reading