Recent Posts

Tags

Site search

January 2012
M T W T F S S
« Sep    
 1
2345678
9101112131415
16171819202122
23242526272829
3031  

Categories

Tags

Blogroll

Last character getting cropped in a static textfield

You have a static textfield, set to Anti-alias for readability, you put on a couple of filters and suddenly the end of your text gets cropped in the middle of a character.

Here’s what it looked like to me:

This doesn’t always happen, it seems to be something to do with the length of the text in your textfield – if it is some magic length then you get truncation. I added some test chars at the end to see if it was a problem with the letter e in this font, but you can make it happen with other letters when the text is a certain length. It’s pretty annoying to come across by accident.

This seems to be a Flash bug to me. So what are your options to work around it?
1. Remove the filters
2. Make the text field selectable
3. Add a space at the beginning of the field.
4. Add a character at the end of the field with Alpha 0%.

Input TextFields – auto select all text

Sometimes you want to have all the text in an input field selected when the user clicks on the field – this makes it easier to enter data quickly on certain forms, particularly if the fields are pre-filled.
But it’s not so easy to do without breaking the standard functionality of the input field. This code hopefully does it without breaking anything, and without compromising on the new functionality.

Put this demo script in Frame 1 of a fresh FLA and give it a whirl:

// Create some text TextFields
  1. var n:Number;
  2. var tf:TextField;
  3. var tfmt:TextFormat = new TextFormat("_sans", 20);
  4. for(n = 0; n < 8; n++){
  5.  tf = new TextField();
  6.  addChild(tf);
  7.  tf.defaultTextFormat = tfmt;
  8.  tf.name = "inp" + (n+1);
  9.  tf.type = TextFieldType.INPUT;
  10.  tf.width = 300;
  11.  tf.height = 30;
  12.  tf.border = true;
  13.  tf.x = 20;
  14.  tf.y = 10 + (n * 35);
  15.  if(n > 2){
  16.   tf.text = "Test text for number " + (n+1);
  17.  }
  18.  
  19.  tf.addEventListener(FocusEvent.FOCUS_IN, handleFocusIn);
  20. }
  21.  
  22. // Listen on the stage for the KEY_FOCUS_CHANGE event so that we can handle
  23. // when the user tabs into a TextField.
  24. addEventListener(FocusEvent.KEY_FOCUS_CHANGE, handleKeyFocusChange);
  25.  
  26. function handleKeyFocusChange($e:FocusEvent):void {
  27.  // If the user tabs to a textfield, cancel the focus in handler so that
  28.  // subsequent mouse clicks in that field DON'T highlight the whole field.
  29.  if($e.relatedObject is TextField){
  30.   var tf:TextField = TextField($e.relatedObject);
  31.   tf.removeEventListener(FocusEvent.FOCUS_IN, handleFocusIn);
  32.   tf.addEventListener(FocusEvent.FOCUS_OUT, handleFocusOut);
  33.  }
  34. }
  35.  
  36. function handleMouseUp($e:MouseEvent):void {
  37.  var tf:TextField = TextField($e.target);
  38.  if(tf.selectionBeginIndex == tf.selectionEndIndex){
  39.   // If the user hasn't dragged to select text already,
  40.   // select the whole text field.
  41.   tf.setSelection(0, tf.text.length);
  42.  }
  43.  tf.removeEventListener(MouseEvent.MOUSE_UP, handleMouseUp);
  44. }
  45. function handleFocusIn($e:Event):void {
  46.  var tf:TextField = TextField($e.target);
  47.  tf.addEventListener(MouseEvent.MOUSE_UP, handleMouseUp);
  48.  tf.addEventListener(FocusEvent.FOCUS_OUT, handleFocusOut);
  49. }
  50. function handleFocusOut($e:Event):void {
  51.  var tf:TextField = TextField($e.target);
  52.  tf.addEventListener(FocusEvent.FOCUS_IN, handleFocusIn);
  53.  tf.removeEventListener(FocusEvent.FOCUS_OUT, handleFocusOut);
  54. }

Basically it works by listening for the FOCUS_IN event on each TextField – when it gets it, it then listens for the MOUSE_UP event that normally follows. When that event happens it checks if a selection has been made (which happens when the user holds and drags to highlight characters in the field), and if not it highlights the whole field. Now since it’s no longer listening for the FOCUS_IN nor the MOUSE_UP events, it lets the user click again in the same field and it won’t highlight it all again – this is important to make it easier for the user to work with the field.
When the user moves away from the field, the FOCUS_OUT event fires, the TextField catches it and starts listening for the FOCUS_IN event again.

All this is fine unless the user uses the keyboard to TAB into the field – if the user tabs into the field, and then uses the mouse to select characters, we don’t want the whole field to highlight again, so to prevent this we have the stage listen out for the FocusEvent.KEY_FOCUS_CHANGE event. When this event fires we check if the object the focus is moving to (the relatedObject property) is a TextField, if it is then we remove the listener on that field for the MOUSE_UP event – this disables the new highlighting feature. To make sure it works again properly when the user moves out again, we start it listening for the FOCUS_OUT event.

Have I typed enough now? :) To see it in action:
Auto Highlighting TextField
and
download the FLA (CS4)

Oh and this all works because of the order the events come in as. When you use the mouse to focus on an Input field it goes like this:
FocusEvent.FOCUS_IN
MouseEvent.MOUSE_UP

when you use the keyboard to tab in:
FocusEvent.KEY_FOCUS_CHANGE
FocusEvent.FOCUS_IN
MouseEvent.MOUSE_UP

If this comes in useful at all, please leave a comment?

AlivePDF and Popup Blockers

I’ve been doing some headscratching and googling the last few days over a problem I had when using http://www.alivepdf.org/ to turn a movieclip into a PDF. The class is great, and gets you a PDF no problem, and when testing in Chrome all was fine, but when I tried it in IE or Firefox, the native popup blockers caused it to stop working.
The problem is that navigateToURL() always triggers the blockers, and AlivePDF uses it when you call the PDF.save method with Method.REMOTE, which is what you do if you want it to create the PDF and open it in a new window.
So how to get around that? Well firstly you need to avoid using navigateToURL(). Some googling turned up this code (found here http://www.blogs.abeazam.com/dev/?p=37):

  1. //set the desired URL here
  2. var url_str:String = "http://www.yoururl.com";
  3. //checks to see if the browser has JS on and
  4. //if ExternalInterface is compatible with the browser
  5. if (ExternalInterface.available) {
  6.   //calls the JS function "window.open" in a new window
  7.   ExternalInterface.call( "window.open", url_str, "_blank" );
  8. } else {
  9.   //the fall back call is "navigateToURL"
  10.   var urlRequest:URLRequest = new URLRequest(url_str);
  11.   navigateToURL(urlRequest,"_blank");
  12. }

But there’s a problem – AlivePDF needs to use a POST request to send the PDF bytearray – that’s not possible with the ExternalInterface.call alternative.

So the solution is to split up the PHP script into two: The first, create_pdf.php receives the bytearray POST and stores it in a session var, all it returns is ok or fail. The second script, fetch_pdf.php takes no parameters, and just outputs a PDF based on whatever’s in the session.
To make that work you need to change the call to PDF.save so it uses Method.LOCAL, and saves the return value, which is the PDF bytearray. Then you send that bytearray via POST to the create_pdf.php script, when that returns you can call the fetch_pdf.php script to actually get it.

But now there’s another problem – if you wait for a COMPLETE event to tell you when create_pdf.php has finished, and then try to load fetch_pdf.php – you’ll still trigger the popup blocker, because now you’re no longer calling the script as a result of a mouse click. So you have to add a little interface and button to the app to allow the user to click to fetch the PDF when it’s ready.

So to sum up, here’s how it goes:

  1. User requests a PDF.
  2. Use PDF.save(Method.LOCAL) and store the return PDF bytearray.
  3. Use URLLoader to send that bytearray to the create_pdf.php script, listen for the COMPLETE event.
  4. When the COMPLETE event triggers, show the user a button to allow them to fetch the PDF.
  5. When the user clicks the button, use the above workaround with ExternalInterface.call to load fetch_pdf-php.

Here are the two PHP scripts.
create_pdf.php

  1. session_start();
  2.  
  3. $_SESSION['pdf_data'] = '';
  4.  
  5. // Receive the data for a PDF, store it in a session so it can be converted by a
  6. // subsequent call to the fetch_pdf.php script.
  7.  
  8. if ( isset ( $GLOBALS["HTTP_RAW_POST_DATA"] )) {
  9.  // get bytearray
  10.  $pdf = $GLOBALS["HTTP_RAW_POST_DATA"];
  11.  $_SESSION['pdf_data'] = $GLOBALS["HTTP_RAW_POST_DATA"];
  12.  echo 'ok';
  13. } else {
  14.  echo 'error';
  15. }

fetch_pdf.php

  1. // Return the PDF that was previously created and stored in the user's session.
  2.  
  3. session_start();
  4.  
  5. //echo "HEADERS SENT? " . (headers_sent($file, $line) ? "YES" : "NO") . ", " . $file . ", line: " . $line;
  6. if ( isset ( $_SESSION['pdf_data'] ) && $_SESSION['pdf_data'] != '' ) {
  7.  header("Cache-Control: public");
  8.  header("Content-Description: File Transfer");
  9.  header("Content-Disposition: attachment; filename=ybs_cover_choices.pdf");
  10.  header("Content-Type: application/pdf");
  11.  header("Content-Transfer-Encoding: binary");
  12.  
  13.  echo $_SESSION['pdf_data'];
  14. } else {
  15.  echo 'An error occured.';
  16. }

Hope that helps someone else.

What am I clicking on?

Sometimes when you have a complicated layout with lots of overlapping clips, masks, transparencies and such, the mouse just doesn’t seem to be hitting what’s directly under it. Your button won’t click. There’s something invisible between the mouse and your button and you can’t figure out what it is.

The thing to do here is to add a listener to the stage. All mouse events end up at the stage eventually, and when they get there they tell us what was clicked:

stage.addEventListener(MouseEvent.CLICK, handleStageClick);
  1. ……….
  2. private function handleStageClick($e:MouseEvent):void {
  3.   var tmpDo:DisplayObject = DisplayObject($e.target);
  4.   var path:String = $e.target.name;
  5.   while (tmpDo.parent && tmpDo.parent.name) {
  6.     path = tmpDo.parent.name + "." + path;
  7.     tmpDo = tmpDo.parent;
  8.   }
  9.   trace("CLICK: " + path);
  10. }

BitmapData – turning the image to greyscale

Here’s a useful tidbit I picked up from here:
http://www.adobe.com/devnet/flash/articles/matrix_transformations_print.html

How to turn a colour bitmapdata into greyscale.

  1. // Turn the object graphics to greyscale.
  2. var matrix:Array = new Array();
  3. matrix = matrix.concat([0.33, 0.33, 0.33, 0, 0]); // red
  4. matrix = matrix.concat([0.33, 0.33, 0.33, 0, 0]); // green
  5. matrix = matrix.concat([0.33, 0.33, 0.33, 0, 0]); // blue
  6. matrix = matrix.concat([0, 0, 0, 1, 0]); // alpha
  7.  
  8. var filter:ColorMatrixFilter = new ColorMatrixFilter(matrix);
  9.  
  10. // Note, bdItem has already been created with an image.
  11. bdItem.applyFilter(bdItem, new Rectangle(0, 0, bdItem.width, bdItem.height), new Point(0, 0), filter);

Spirals in motion

The idea of this blog is to make my Flash work, the portfolio pieces and experiments, more available. My site will soon get a redesign using a Papervision interface, which while cool in many ways will make it a little harder for people to find my content, so this blog should hopefully remedy that. And also provide you with a way to give me feedback on things.

So I’ll start of with a random one… this was always one of my favourite experiments but I’ve yet to find a use for it, so it can kick off the blog :)

Spirals in Motion

Spirals in Motion

As is usual in my experiments, roll your mouse over the screen to interact with the changing patterns.

It was written way back when in AS1, set your background to black and stick this in frame 1:

drawCircle = function (x, y, r) {
  1.  var angleDelta = 45*(Math.PI/180), rx, ry, ex, ey;
  2.  var regDist = r/Math.cos(angleDelta/2);
  3.  var angle = 0;
  4.  this.moveTo(x+r, y);
  5.  for (var i = 0; i < 8; i++) {
  6.    angle += angleDelta;
  7.    rx = x+Math.cos(angle-(angleDelta/2))*(regDist);
  8.    ry = y+Math.sin(angle-(angleDelta/2))*(regDist);
  9.    ex = x+Math.cos(angle)*r;
  10.    ey = y+Math.sin(angle)*r;
  11.    this.curveTo(rx, ry, ex, ey);
  12.  }
  13. }
  14.  
  15. var k = 0.14;
  16. var friction = 0.825;
  17. var cx = Stage.width/2;
  18. var cy = Stage.height/2;
  19. var ox = cx;
  20. var oy = cy;
  21. var numBranches = 3;
  22. var numNodes = 30; // Number of circles per branch
  23. var nodes = [];
  24. // Set up colours
  25. var col = Math.random()*Math.PI*2;
  26. var colInc = 0.1;
  27. // Create the circles
  28. for(n=0, z=1; n < numBranches; n++){
  29.  nodes.push(new Array());
  30.  for(var m=0; m < numNodes; m++, z++){
  31.   nodes[n].push(thisNode =
  32.    this.createEmptyMovieClip("mc_"+z, (m*numBranches)+n));
  33.   // Initally all are white, this is adjusted later
  34.   thisNode.beginFill(0xffffff);
  35.   drawCircle.apply(thisNode, [0, 0, 220 * (1-(m/numNodes))]);
  36.   thisNode.endFill();
  37.   thisNode.thisCol = new Color(thisNode);
  38.  }
  39. }
  40. // Localise Math functions for better performance.
  41. this.sin = Math.sin;
  42. this.cos = Math.cos;
  43. this.abs = Math.abs;
  44. this.createEmptyMovieClip("border", z);
  45. this.border.lineStyle(1, 0xAAAAAA);
  46. this.border.lineTo(Stage.width-1, 0);
  47. this.border.lineTo(Stage.width-1, Stage.height-1);
  48. this.border.lineTo(0, Stage.height-1);
  49. this.border.lineTo(0, 0);
  50. this.onEnterFrame = function(){
  51.  var thisCol, nRatio, r, g, b, rDone, gDone, bDone, rInc, gInc, bInc;
  52.  var dx = ox – cx;
  53.  var dy = oy – cy;
  54.  var xRatio = dx/cx;
  55.  var yRatio = dy/cy;
  56.  var radInc = xRatio * -7;
  57.  var thetaInc = yRatio * 0.05;
  58.  var thetaMod = xRatio * 1.5;
  59.  col += colInc;
  60.  for(var n=0; n < numBranches; n++){
  61.   nRatio = n/numBranches;
  62.   theta = thetaMod + ((Math.PI*2)*nRatio);
  63.   thisCol = col + ((Math.PI*2)*nRatio);
  64.   rDone = this.abs(0×88 + (this.sin(thisCol))*0×77);
  65.   gDone = this.abs(0×88 + (this.sin(1.5 + thisCol))*0×77);
  66.   bDone = this.abs(0×88 + (this.sin(3 + thisCol))*0×77);
  67.   rInc = rDone / numNodes;
  68.   gInc = gDone / numNodes;
  69.   bInc = bDone / numNodes;
  70.   r = g = b = 0;
  71.   for(var m=0, rad=80; m < numNodes; m++){
  72.    theta += thetaInc;
  73.    rad += radInc;
  74.    thisNode = nodes[n][m];
  75.    thisNode._x = cx+(this.cos(theta) * rad);
  76.    thisNode._y = cy+(this.sin(theta) * rad);
  77.    r += rInc;
  78.    g += gInc;
  79.    b += bInc;
  80.    thisNode.thisCol.setRGB(r << 16 | g << 8 | b);
  81.   }
  82.  }
  83.  vx = (vx + ((_xmouse – ox) * k)) * friction;
  84.  vy = (vy + ((_ymouse – oy) * k)) * friction;
  85.  ox += vx;
  86.  oy += vy;
  87. }

3D Carousel Menu