personal weblog of a tech addict
AIR
Using a Custom AdvancedDataGridHeaderRenderer to Display a Custom Image
Feb 9th
You decide to use an AdvancedDataGrid to display your data and for one of the columns you would prefer to use an icon instead of a text label to indicate the kind of data this column represents.
First step is building the renderer. You can use either MXML or ActionScript but for this example we’ll use ActionScript.
package labs.otuome.ui.renderers { import mx.controls.Button; import mx.controls.advancedDataGridClasses.AdvancedDataGridHeaderRenderer; /** * Custom header renderer for displaying * a graphical image in the column header. * @author Hasan Otuome */ public class StatusHeaderRenderer extends AdvancedDataGridHeaderRenderer { ///////////////////////////////////////////////////////////////////////////////////////// // PRIVATE PROPERTIES ///////////////////////////////////////////////////////////////////////////////////////// private var _btn:Button; private const LEFT_PADDING:int = 12; ///////////////////////////////////////////////////////////////////////////////////////// // PUBLIC PROPERTIES ///////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////// // PUBLIC METHODS ///////////////////////////////////////////////////////////////////////////////////////// /** * Constructor */ public function StatusHeaderRenderer() { super(); } ///////////////////////////////////////////////////////////////////////////////////////// // OVERRIDES ///////////////////////////////////////////////////////////////////////////////////////// /** * Override to add the custom component. */ override protected function createChildren():void { super.createChildren(); _btn = new Button(); _btn.width = 16; _btn.height = 16; _btn.setStyle( 'skin', StatusMarkerHeaderIconSkin ); addChild( _btn ); } /** * Override to layout the children * @param unscaledWidth * @param unscaledHeight */ override protected function updateDisplayList( unscaledWidth:Number, unscaledHeight:Number ):void { _btn.x = LEFT_PADDING; super.updateDisplayList( unscaledWidth, unscaledHeight ); } ///////////////////////////////////////////////////////////////////////////////////////// // PRIVATE METHODS ///////////////////////////////////////////////////////////////////////////////////////// } }
Here we’ve added a 16×16 button with a custom skin that will serve as our column header and we adjust the position of the custom skin so that it displays where we’d like it to.
Now, we can provide this class name to our AdvancedDataGridColumn that we wish to customize. We do this by assigning our custom class to the headerRenderer property of the AdvancedDataGridColumn. This property can be set via MXML or ActionScript. I’ll show an example in ActionScript as that approach is slightly more involved.
import mx.core.ClassFactory; myADGColumn.headerRenderer = new ClassFactory( StatusHeaderRenderer );
Here we’ve used ClassFactory to get an instance of our custom renderer and we’ve assigned that instance to our chosen data grid column. Now, all that’s left to do is compile our application to see the result.
Joining the Adobe Community Professionals for 2010
Jan 18th
Formerly the Adobe Community Experts program, this is a collective of awesome individuals who exhibit expertise in various Adobe product areas from Acrobat to Photoshop, Flash, Cold Fusion, Flex, AIR and many, many more. Comprised of community leaders, this group has a worldwide presence and expounds the virtues of Adobe to the masses through the sharing of their time and expertise.
I’m truly honored to have been chosen as a member of this select group and look forward to expanding my community efforts throughout this new year. Having missed FlashCamp Brasil this past weekend, this revelation makes the trip to FITC Amsterdam that much more rewarding. See you there!!
Introducing Opticon Runtime Debugger
Jan 9th
![]()
Opticon is a runtime debugging console useful for Flash/Flex developers. It’s simple to start using in your projects. To use, follow these steps after installing the application:
1. Launch the application
2. Click the Settings button
3. Click on the SWC icon to add the OpticonConnector to your project’s build path
4. Replace your trace statements with one of the following:
- a) Opticon.log(‘message to display’)
- b) Opticon.warning(‘warning to display’)
- c) Opticon.error(‘error to display’)
Download available from:
Adobe AIR Marketplace
RIAForge
Some might ask why? This idea first appeared when AIR was Apollo and was born out of necessity (I always forget some trace statements during cleanup). Could I use global find and replace? Sure, when I’m developing with Eclipse-based IDEs but that’s not always the case.
Why not use one of the other great tools out there? After 2 years in the shadows, it’s ready to be shared with the world and, because I have a vision of where I’d like to take this so hopefully you’ll come along for the ride.
Where can I submit any bugs I find? I’ll have the bug reporting mechanism in place shortly. You’ll be notified via the application or you can subscribe to this post to be notified once that goes live.
My version doesn’t include the connector. What can I do? This happened as a result of a build error. Visit this link to download the connector Get Opticon Connector.
Double-headed Arrows with GraphicsUtil
Dec 7th
I recently ran across this awesome utility class, GraphicsUtil, created by Noel Billig. It allows the ability to draw lines with an arrow on one end using the drawing API in AS3. This proved useful for a project I was working on. In addition to its default behavior however, I needed the utility class to provide the ability to draw lines with arrows on both ends. Well thanks to Noel’s clean code, I was able to easily modify the base class to do just that by adding the following static method:
/** * Draws a double-headed arrow. Pass in ArrowStyle * objects for both arrows to override the default settings. * @param graphics * @param start * @param end * @param startStyle * @param endStyle */ public static function drawArrows( graphics:Graphics, start:Point,end:Point, startStyle:Object=null, endStyle:Object=null ):void { // variables used for arrow 1 var startArrowStyle:ArrowStyle; var startHalfWidth:Number; var vect1:Point; var startNorm:Point; var start1:Point; var start2:Point; var end1:Point; var end2:Point; var startHeadPnt:Point; var startHeadPntNorm:Point; var startEdge1:Point; var startEdge2:Point; var startShaftCenter:Point; var startInter1:Point; var startInter2:Point; var startEdgeCenter:Point; var startEdgeNorm:Point; var startEdgeCntrl1:Point; var startEdgeCntrl2:Point; // variables used for arrow 2 var endArrowStyle:ArrowStyle; var endHalfWidth:Number; var vect2:Point; var endNorm:Point; var end3:Point; var end4:Point; var start3:Point; var start4:Point; var endHeadPnt:Point; var endHeadPntNorm:Point; var endEdge1:Point; var endEdge2:Point; var endShaftCenter:Point; var endInter1:Point; var endInter2:Point; var endEdgeCenter:Point; var endEdgeNorm:Point; var endEdgeCntrl1:Point; var endEdgeCntrl2:Point; if (start.equals(end)) return; ///////////////////////////////// start arrow config \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ if (startStyle == null) { startArrowStyle = new ArrowStyle(); } else if (startStyle is ArrowStyle) { startArrowStyle = startStyle as ArrowStyle; } else { startArrowStyle = new ArrowStyle( startStyle ); } vect1 = end.subtract( start ); startHalfWidth = (startArrowStyle.headWidth != -1) ? startArrowStyle.headWidth / 2 : startArrowStyle.headLength / 2; //Figure out the line start/end points startNorm = new Point( vect1.y, -vect1.x ); startNorm.normalize( startArrowStyle.shaftThickness/ 2 ); start1 = start.add( startNorm ); start2 = start.subtract( startNorm ); end1 = end.add( startNorm ); end2 = end.subtract( startNorm ); //figure out where the arrow head starts startHeadPnt = vect1.clone(); startHeadPnt.normalize( startHeadPnt.length - startArrowStyle.headLength ); startHeadPnt = startHeadPnt.add( start ); //calculate the arrowhead corners startHeadPntNorm = startNorm.clone(); startHeadPntNorm.normalize( startHalfWidth ); startEdge1 = startHeadPnt.add( startHeadPntNorm ); startEdge2 = startHeadPnt.subtract( startHeadPntNorm ); //Figure out where the arrow connects the the shaft, then calc the intersections startShaftCenter = Point.interpolate( end, startHeadPnt, startArrowStyle.shaftPosition ); startInter1 = GeomUtil.getLineIntersection( start1, end1, startShaftCenter, startEdge1 ); startInter2 = GeomUtil.getLineIntersection( start2, end2, startShaftCenter, startEdge2 ); //Figure out the control points startEdgeCenter = Point.interpolate( end, startHeadPnt, startArrowStyle.edgeControlPosition ); startEdgeNorm = startNorm.clone(); startEdgeNorm.normalize( startHalfWidth * startArrowStyle.edgeControlSize ); startEdgeCntrl1 = startEdgeCenter.add( startEdgeNorm ); startEdgeCntrl2 = startEdgeCenter.subtract( startEdgeNorm ); ///////////////////////////////// end arrow config \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ if (endStyle == null) { endArrowStyle = new ArrowStyle(); } else if (endStyle is ArrowStyle) { endArrowStyle = endStyle as ArrowStyle; } else { endArrowStyle = new ArrowStyle( endStyle ); } vect2 = start.subtract( end ); endHalfWidth = (endArrowStyle.headWidth != -1) ? endArrowStyle.headWidth / 2 : endArrowStyle.headLength / 2; //Figure out the line start/end points endNorm = new Point( vect2.y, -vect2.x ); endNorm.normalize( endArrowStyle.shaftThickness / 2 ); start3 = start.add( endNorm ); start4 = start.subtract( endNorm ); end3 = end.add( endNorm ); end4 = end.subtract( endNorm ); //figure out where the arrow head starts endHeadPnt = vect2.clone(); endHeadPnt.normalize( endHeadPnt.length - endArrowStyle.headLength ); endHeadPnt = endHeadPnt.add( end ); //calculate the arrowhead corners endHeadPntNorm = endNorm.clone(); endHeadPntNorm.normalize( endHalfWidth ); endEdge1 = endHeadPnt.add( endHeadPntNorm ); endEdge2 = endHeadPnt.subtract( endHeadPntNorm ); //Figure out where the arrow connects the the shaft, then calc the intersections endShaftCenter = Point.interpolate( start, endHeadPnt, endArrowStyle.shaftPosition ); endInter1 = GeomUtil.getLineIntersection( end3, start3, endShaftCenter, endEdge1 ); endInter2 = GeomUtil.getLineIntersection( end4, start4, endShaftCenter, endEdge2 ); //Figure out the control points endEdgeCenter = Point.interpolate( start, endHeadPnt, endArrowStyle.edgeControlPosition ); endEdgeNorm = endNorm.clone(); endEdgeNorm.normalize( endHalfWidth * endArrowStyle.edgeControlSize ); endEdgeCntrl1 = endEdgeCenter.add( endEdgeNorm ); endEdgeCntrl2 = endEdgeCenter.subtract( endEdgeNorm ); ///////////////////////////////// draw the graphics \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ // draw 1st arrow graphics.moveTo( start1.x, start2.y ); graphics.lineTo( startInter1.x, startInter1.y ); graphics.lineTo( startEdge1.x, startEdge1.y ); graphics.curveTo( startEdgeCntrl1.x, startEdgeCntrl1.y, end.x, end.y ); graphics.curveTo( startEdgeCntrl2.x, startEdgeCntrl2.y, startEdge2.x, startEdge2.y ); graphics.lineTo( startInter2.x, startInter2.y ); graphics.lineTo( start2.x, start2.y ); graphics.lineTo( start1.x, start1.y ); // draw 2nd arrow graphics.moveTo( end3.x, end4.y ); graphics.lineTo( endInter1.x, endInter1.y ); graphics.lineTo( endEdge1.x, endEdge1.y ); graphics.curveTo( endEdgeCntrl1.x, endEdgeCntrl1.y, start.x, start.y ); graphics.curveTo( endEdgeCntrl2.x, endEdgeCntrl2.y, endEdge2.x, endEdge2.y ); graphics.lineTo( endInter2.x, endInter2.y ); graphics.lineTo( end4.x, end4.y ); graphics.lineTo( end3.x, end3.y ); }
Those are the basics. It can be tweaked to taste, etc. Kudos to Noel for the such a useful class…
Overlaying the Flex SDK with the AIR 2.0 SDK on OSX
Dec 4th
Since I develop with multiple IDEs, I maintain the latest releases of the Flex and AIR SDKs in a centralized location outside of their default install locations. This made enabling the AIR 2.0 SDK simple for me as all I had to do was the following:
1. Duplicate my Flex 3 SDK path
2. Rename the duplicate to flex_sdk_3.x_air_sdk_2.x
3. Download the latest AIR 2.0 SDK (currently AIR20_mac_sdk_120209.tbz2)
4. Place the AIR SDK zip in the root of the new Flex SDK directory
/flex_sdk_3.x_air_sdk_2.x/AIR20_mac_sdk_120209.tbz2
5. Open up Terminal in the new Flex SDK directory and execute the following command:
tar -xjf AIR20_mac_sdk_120209.tbz2
6. Open Flex Builder and add the new SDK -> Flex Builder > Preferences > Installed Flex SDKs > Add
Now, when I want to create an AIR app that targets the 2.0 SDK all I have to do is
1. Update the project compiler settings to use the new SDK -> Project > Properties > Flex Compiler > Use a Specific SDK
2. Update the application descriptor file to use the AIR 2.0 namespace
<application xmlns="http://ns.adobe.com/air/application/2.0beta">
All of the above steps can be repeated for the Flex 4 SDK as well.
For those interested, these are the items that were updated and added to the SDK for 2.0:
/bin/adl
/bin/adt
/frameworks/libs/air
/frameworks/projects/air
/lib/nai
/lib/adt.jar
/runtimes/air
/samples
/templates/air
/AIR SDK Readme.txt
/SDK license.pdf
CAVEATS:
- There’s a new certificate process started in AIR 1.5.3 that may disrupt your workflow if you’re creating signed applications.
- If you want to utilize the new native process features, you’ll have to implement multi-platform builds (native installers for Win, Mac and Linux) and these must occur on the target platform (ie, can only create DMGs on Mac, etc)
AS3: LinearGradientUtil Class
Nov 20th
Once again, I found myself needing a utility class that would let me stop writing the same code block over and over again. And so, the LinearGradientUtil class was born.
Previously, I’d have something like the following in a view class:
/** * Draws the gradient background for * this component on initialize and resize. */ private function _createGradientBackground():void { var fillType:String = GradientType.LINEAR; var colors:Array = [ 0xe9e9e9, 0xc7c7c7 ]; var alphas:Array = [ 1, 1 ]; var ratios:Array = [ 0, 255 ]; var matrix:Matrix = new Matrix(); matrix.createGradientBox( width, 34, Math.PI/2, 0, 0 ); var spreadMethod:String = SpreadMethod.PAD; var g:Graphics = this.graphics; g.clear(); g.beginGradientFill( fillType, colors, alphas, ratios, matrix, spreadMethod ); g.drawRect( 0, 0, width, height ); g.endFill(); }
This would be called after INITIALIZE and RESIZE events. Writing those lines over and over again prompted me to devise a more elegant solution:
package labs.otuome.fl.graphics { import flash.display.GradientType; import flash.display.Graphics; import flash.display.SpreadMethod; import flash.display.Sprite; import flash.geom.Matrix; /** * Util class to create linear gradient fills. * @author Hasan Otuome */ public class LinearGradientUtil { /////////////////////////////////////////////////////////////////////////////////////////////// // PRIVATE PROPERTIES /////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////// // PUBLIC PROPERTIES /////////////////////////////////////////////////////////////////////////////////////////////// public static const HORIZONTAL:String = 'horizontalLinearGradient'; public static const VERTICAL:String = 'verticalLinearGradient'; /////////////////////////////////////////////////////////////////////////////////////////////// // PUBLIC METHODS /////////////////////////////////////////////////////////////////////////////////////////////// /** * Draws the linear gradient to the .graphics * property of the specified component. Defaults * to a Web 2.0 style gray gradient. * * @param target The display object to apply the gradient to. * @param size For a vertical gradient this is the * height of the gradient. For a horizontal gradient, * this is the width of the gradient. * @param primaryColor The top or left-most color depending on orientation. * @param secondaryColor The bottom or right-most color depending on orientation. * @param primaryAlpha The top or left-most color depending on orientation. * @param secondaryAlpha The bottom or right-most alpha depending on orientation. * @param primaryRatio The ratio of the top or left-most color depending on orientation. * @param secondaryRatio The ratio of the bottom or right-most color depending on orientation. * @param orientation either horizontal or vertical. defaults to vertical. * @param invert Toggle that switches what the method considers primary and secondary. */ public static function createGradientBackground( target:Sprite, size:uint=34, cornerRadius:uint=0, primaryColor:uint=0xe9e9e9, secondaryColor:uint=0xc7c7c7, primaryAlpha:uint=1, secondaryAlpha:uint=1, primaryRatio:uint=0, secondaryRatio:uint=255, orientation:String=LinearGradientUtil.HORIZONTAL, invert:Boolean=false ):void { var fillType:String; var colors:Array; var alphas:Array; var ratios:Array; var matrix:Matrix; var spreadMethod:String; var g:Graphics; fillType = GradientType.LINEAR; colors = _createColorsArray( primaryColor, secondaryColor, invert ); alphas = _createAlphasArray( primaryAlpha, secondaryAlpha, invert ); ratios = _createRatiosArray( primaryRatio, secondaryRatio, invert ); matrix = new Matrix(); switch (orientation) { case LinearGradientUtil.HORIZONTAL: matrix.createGradientBox( size, target.height, Math.PI/2, 0, 0 ); break; case LinearGradientUtil.VERTICAL: matrix.createGradientBox( target.width, size, Math.PI/2, 0, 0 ); break; } spreadMethod = SpreadMethod.PAD; g = target.graphics; g.clear(); g.beginGradientFill( fillType, colors, alphas, ratios, matrix, spreadMethod ); if (cornerRadius > 0) { g.drawRoundRect( 0, 0, target.width, target.height, cornerRadius ); } else { g.drawRect( 0, 0, target.width, target.height ); } g.endFill(); } /////////////////////////////////////////////////////////////////////////////////////////////// // OVERRIDES /////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////// // PRIVATE METHODS /////////////////////////////////////////////////////////////////////////////////////////////// /** * Creates the colors array required by the main method. * @param colorOne * @param colorTwo * @param inverted * @return The gradient colors array. */ private static function _createColorsArray( colorOne:uint, colorTwo:uint, inverted:Boolean=false ):Array { var colors:Array; if (!inverted) { colors = [ colorOne, colorTwo ]; } else { colors = [ colorTwo, colorOne ]; } return colors; } /** * Creates the alphas array required by the main method. * @param colorOneAlpha * @param colorTwoAlpha * @param inverted * @return The gradient alphas array. */ private static function _createAlphasArray( colorOneAlpha:uint, colorTwoAlpha:uint, inverted:Boolean=false ):Array { var alphas:Array; if (!inverted) { alphas = [ colorOneAlpha, colorTwoAlpha ]; } else { alphas = [ colorTwoAlpha, colorOneAlpha ]; } return alphas; } /** * Creates the ratios array required by the main method. * @param colorOneRatio * @param colorTwoRatio * @param inverted * @return The gradient ratios array. */ private static function _createRatiosArray( colorOneRatio:uint, colorTwoRatio:uint, inverted:Boolean=false ):Array { var ratios:Array; if (!inverted) { ratios = [ colorOneRatio, colorTwoRatio ]; } else { ratios = [ colorTwoRatio, colorOneRatio ]; } return ratios; } } }
Now, I can just issue the command like so:
/** * Draws the gradient background for * this component on initialize and resize. */ private function _createGradientBackground():void { // LinearGradientUtil.createGradientBackground( this, this.height, getStyle('cornerRadius'), 0xfdfeff, 0xe9e9e9 ); // changing color scheme // LinearGradientUtil.createGradientBackground( this ); // using the default color scheme }
Less code + rich functionality = happy developer!!
ZamfBrowser – ZendAMF Service Browser
Nov 13th
One of my colleagues at Almer/Blank, Omar Gonzalez, just released a very promising project into the open source community today, ZamfBrowser. This AIR application is a lifeline for all the developers who have embraced ZendAMF since its release yet have longed for that familiar service browser paradigm we grew used to with AMFPHP. Well, wait no longer. ZamfBrowser performs server introspection to give you access to your ZendAMF services and it even features test retention by remembering the last arguments used for method testing between sessions. This is definitely an application worth a look if you plan on doing any AMF development.
Using the Flex IViewCursor to Manage an ArrayCollection
Nov 12th
One of the nice things about the Flex framework is the various utility classes that make it so much easier for you to accomplish your development tasks than you’d be able to without them. One such class, in my opinion, is mx.collections.IViewCursor. What this class does is to define an interface for enumerating a collection view (ie, ArrayCollection) both forwards and backwards. Using this construct, you can avoid having to use for.. loops to examine the collection.
Here’s a quick example of a fictitious auto maker that’s tasked us to update some info related to one of its auto dealers after receiving the quarterly sales report:
var carDealers:ArrayCollection; var dealerCursor:IViewCursor; var vehicleCursor:IViewCursor; var affectedDealer:CarDealer; var targetVehicle:Car; var targetVehicleIndex:Number; var dealerIdFromSale:String = 'XXXXXXXX-XXXX-XXXX-XXXXXXXXXXX'; var vinNumberFromSale:String = 'XXXXXXXXXXXXXXXXX'; dealerCursor = carDealers.createCursor(); // iterate through the list of dealers while (!dealerCursor.afterLast) { if (dealerCursor.current.dealerId == dealerIdFromSale) { affectedDealer = CarDealer( dealerCursor.current ); // create a cursor to iterate over the dealer's inventory vehicleCursor = affectedDealer.inventory.createCursor(); // iterate over the inventory while (!vehicleCursor.afterLast) { if (vehicleCursor.current.VIN == vinNumberFromSale) { // a match was found so now we need the // index of this vehicle in the ArrayCollection targetVehicle = Car( vehicleCursor.current ); targetVehicleIndex = affectedDealer.inventory.getItemIndex( targetVehicle ); // since the sale was successful, we can safely // remove the vehicle from this dealer's inventory affectedDealer.inventory.removeItemAt( targetVehicleIndex ); } vehicleCursor.moveNext(); } } dealerCursor.moveNext(); }
In a few lines of code, we’re able to create some very readable and manageable logic to complete the task of updating the inventory. And, you may notice that we also iterated over not one, but two ArrayCollection instances with our mighty IViewCursor. One additional thing to note is the use of the moveNext() method. It’s important that you instruct the cursor that it’s OK to advance even though you’ve found what you’re looking for; otherwise, plan on some serious hang time. I mean it is still a while..loop after all!
Cool Features in Flash Builder 4
Nov 4th

I was recently working on a project in the latest beta release of the forthcoming Flash Builder 4 and wanted to point out the new contextual help feature. What’s nice about this feature is that I had just added the highlighted event to the event class and was busy implementing it in a view class and because of FB4’s new code-hinting muscle I was able to see everything that I had documented in the event class concerning the event without having to have the event class open.
This is definitely a much appreciated usability enhancement. Now, if we could just get editable code (not file) templates (think FDT, Zend Studio, etc), FB4 would be that much closer to being ready for prime time…:D
AMFPHP Fatal Errors After PHP 5.3 Upgrade (Part 2)
Oct 16th
As promised in Part 1, I’m documenting yet another issue that must be addressed in your AMFPHP installation upon upgrading to PHP 5.3. This issue stems from the usage of the [code lang="php"]eregi_replace[/code] function that has been deprecated in PHP 5.3. If you run into a fatal error in your AMFPHP application with the message [code]Function eregi_replace() is deprecated[/code], you have (2) options:
- Modify your PHP configuration to disable the warnings
- Replace the deprecated code with the new, recommended equivalent
I’d advise against #1 since you will also lose warning and error notices that could be helpful to you during development. And, by choosing #2 you will be bringing the AMFPHP code into compliance with a change that is also backwards-compatible with previous versions of PHP since the replacement function you’re going to use has been around since PHP 4.
So, to update your AMFPHP source, you need to modify [code]MethodTable.php[/code] which can be found @ [code]/path/to/amfphp/core/shared/util/MethodTable.php[/code]. Open up this file in your favorite text editor (ie, TextMate
) and jump to line 505. Once there, you need to replace these three lines:
$comment = eregi_replace(”\n[ \t]+”, “\n”, trim($comment)); $comment = str_replace(”\n”, “\\n”, trim($comment)); $comment = eregi_replace(”[\t ]+”, ” “, trim($comment));
with these equivalent lines of code:
$comment = preg_replace(”'\n[ \t]+'U”, “\n”, trim($comment)); $comment = str_replace(”\n”, “\\n”, trim($comment)); $comment = preg_replace(”'[\t ]+'U”, ” “, trim($comment));
Save and close the file, fire up your service browser and you should now be good to go with no more fatal errors in your AMFPHP applications produced by this deprecated function. Happy coding!


