Skip to content

Instantly share code, notes, and snippets.

@davestewart
Last active December 21, 2015 05:48
Show Gist options
  • Save davestewart/6259113 to your computer and use it in GitHub Desktop.
Save davestewart/6259113 to your computer and use it in GitHub Desktop.
package text
{
import flash.events.Event;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;
/**
* Class to manage a TextField instance and reduce its fontsize to ensure it stays within its original dimensions
* @author Dave Stewart
*/
public class TextFieldController
{
// ---------------------------------------------------------------------------------------------------------------------
// { region: variables
// properties
protected var tf :TextField;
protected var maxHeight :int;
protected var format :TextFormat;
protected var size :Number;
protected var prop :String;
// ---------------------------------------------------------------------------------------------------------------------
// { region: instantiation
public static function create(tf:TextField):TextFieldController
{
return new TextFieldController(tf);
}
public function TextFieldController(tf:TextField)
{
this.tf = tf;
initialize();
}
protected function initialize():void
{
tf.addEventListener(Event.CHANGE, onTextFieldChange);
tf.wordWrap = true;
maxHeight = tf.height;
format = tf.getTextFormat();
size = Number(format.size);
// gets over a bug with rotated textfields
prop = tf.rotation > 45 ? 'width' : 'height';
}
// ---------------------------------------------------------------------------------------------------------------------
// { region: accessors
public function set text(value:String):void
{
tf.text = value.replace(/(^\s*|\s*$)/g, '');
fit();
}
// ---------------------------------------------------------------------------------------------------------------------
// { region: protected methods
protected function fit():void
{
tf.autoSize = flash.text.TextFieldAutoSize.LEFT;
size = int(format.size);
tf.setTextFormat(format);
if(tf[prop] >= maxHeight && size > 5)
{
size --;
format.size = size;
fit();
}
}
// ---------------------------------------------------------------------------------------------------------------------
// { region: handlers
protected function onTextFieldChange(event:Event):void
{
fit();
}
// ---------------------------------------------------------------------------------------------------------------------
// { region: utilities
}
}
@ThomasBurleson
Copy link

I like your formatting and style above. Really nice.

While you said AS3, I thought [for some reason] that you were dealing with Flex UIComponent subclasses which force developers to consider invalidation phases. Obviously TextField is a flash.text component not a UIComponent. So the suggestions I had prepared are not appropriate.

I like this class and the way the controller dynamically, auto-adjusts the font size as the # of chars change; so the text always 'fills' the bounds.

Suggestions:

  1. Change public function initialize() to public function attach( tf : TextField ):void. This would allow you to reuse/reattach the TextFieldController instance for any desired TextField.
  2. initialize() does not immediately autoFit. Is that desired effect?
  3. You are not listening for changes to the bounds. With such listeners fit( ) could be called to auto adjust the fontsize as the bounds change.
  1. When txController.text = "some text..." does the Event.CHANGE not get fired after line 55? If so, why did you include line 56 ?

Love your above work.
Thanks for the review opportunity.
Wish I had known about you 1 year ago... could have used you on the SiriusXM project.

Best,
Thomas Burleson

@ThomasBurleson
Copy link

Final comment ;-).

Your class has the public methods above the protected. I love that.
Would it not make more syntactic sense to move the protected variables below the protected methods ?

@davestewart
Copy link
Author

Hey Thomas,

Thanks for the feedback!

Actually this was something that was thrown together for a hack weekend, and for a very specific implementation so it could certainly be improved!

No reason for the initialize method to be public() (in fact, I'm not sure why it is!) and as you said, adding an attach method is a really nice idea. There could also be some other range parameter improvements, and more testing in other situations.

The overall formatting is something I stick to for any class I create, and comes from a template that I've refined over the years. The region: markers also make those regions show up in the FlashDevelop outline panel, which is really useful on bigger classes.

It's actually missing one region "public methods" which always comes after "instantiation" and before "accessors"! I'd never thought about placing protected variables above the protected methods region, but it's an interesting idea. I may take a look at that if my OCD tendencies don't get in the way!

And, as I said in my tiny tweet, the recursive nature of the solution was to solve the problem of the textfield not reporting its correct height when being changed in a loop. A nice nugget of info which I've filed away for future use.

I checked your blog as well mate. Some good stuff in there!

I'm always available for bouncing ideas off BTW :)

Cheers for now,
Dave

@davestewart
Copy link
Author

Actually, thinking about making this class more useful, a static implementation that monitored multiple textfields, either using internal lists, or an array of TextFieldController instances would be a neater solution:

TextFieldController.create(tf);

Have added that...

@ThomasBurleson
Copy link

Here is the styling I often use:

package text 
{
  import flash.events.Event;
  import flash.text.TextField;
  import flash.text.TextFieldAutoSize;
  import flash.text.TextFormat;

  /**
   * TextFieldController 
   *
   * Class to manage a TextField instance and reduce its fontsize to ensure 
   * it stays within its original dimensions/bounds
   *
   * @author Dave Stewart
   * @blog   http://www.davestewart.co.uk
   *
   * @date   August, 2013
   *
   */
  public class TextFieldController
  {

    // **************************************************
    // Public Static Methods
    // **************************************************

    public static function create(tf:TextField):TextFieldController
    {
      return new TextFieldController(tf);
    }

    // **************************************************
    // Public Properties
    // **************************************************

    /**
     * Mutator for the textField's `text` property
     * Force clears all prepending and trailing spaces
     */
    public function set text(value:String):void
    {
      tf.text  = value.replace(/(^\s*|\s*$)/g, '');
      fit();
    }


    // **************************************************
    // Constructor
    // **************************************************

    public function TextFieldController(tf:TextField) 
    {
      this.tf = tf;
      initialize();
    }


    // **************************************************
    // Protected Methods
    // **************************************************

    /**
    * Attach change handler to watch for text changes 
    */
    protected function initialize():void 
    {
      if ( !tf ) return;

      tf.addEventListener(Event.CHANGE, onTextFieldChange);
      tf.wordWrap   = true;

      maxHeight     = tf.height;
      format        = tf.getTextFormat();
      size          = Number(format.size);

      // gets over a bug with rotated textfields
      prop          = (tf.rotation > 45) ? 'width' : 'height';

    }

    /**
     * Recursively auto-change the fontsize until
     * the resulting total text width fits the TextField bounds
     */
    protected function fit():void
    {
      tf.autoSize = flash.text.TextFieldAutoSize.LEFT;
      size    = int(format.size);
      tf.setTextFormat(format);

      if(tf[prop] >= maxHeight && size > 5)
      {
        size --;
        format.size = size;
        fit();
      }
    }

    // **************************************************
    // Protected EventHandlers
    // **************************************************


    /**
     * Auto-resize font when the text content changes
     */
    protected function onTextFieldChange(event:Event):void
    {
      fit();
    }


    // **************************************************
    // Protected Properties
    // **************************************************

    protected var tf        :TextField;
    protected var maxHeight :int;
    protected var format    :TextFormat;
    protected var size      :Number;

    protected var prop      :String;


  }

}

@ThomasBurleson
Copy link

Also I think you have a potential bug:

You cache the maxHeight but do not account for rotation; which may require you to catch the maxWidth.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment