Wednesday, November 26, 2008

A slightly more thorough introduction to Smartypants-IOC

I'm writing up some info on Smartypants-IOC which will hopefully make it into @dhanji's upcoming book on Dependency Injection, so I figured I'd kill two birds with one stone, and finally update my damned blog while I'm at it.

First I'll introduce a couple of key concepts:

The class+name key

Whether you're telling Smartypants what to provide, or sitting around with your hand out asking for a dependency, the key is the same. A combination of a class and a name. You need to specify the class, but the name is optional.

Injection annotations

How do you actually request a field be injected, you ask? In most circumstances you just need to annotate your fields with some ActionScript metadata. If you just want to request an instance of IServiceInterface, the syntax is simple:

[Inject]
public var myService : IServiceInterface;

As I mentioned above, the injector works using a compound key of both Class and Name. Let's say you want to look up a particular String, rather than just anything. For a WSDL URL, or something along those lines:

[Inject(name="mainWSDL")]
public var myServiceWSDL : String;

These fields will be injected into your object when it is constructed for you by Smartypants-IOC, or when you call injector.injectInto(myInstance).

We also have live injections, which when coupled with live rules behave like the Flex SDK's data binding functionality:

//This will be set whenever the source changes
[Inject(name="userName",live)]
public var currentUsername : String;

Injector Rules

Smartypants injector rules are akin to Guice's bindings. We simply use another term to avoid confusion with the Flex SDK's data-binding mechanism. You just tell the injector what you'd like it to do. Here's a couple of examples:

//Simple singleton rules:
injector.newRule().whenAskedFor(String).named("wsdl").useInstance("http://www.server.com/soap/service.wsdl");
injector.newRule().whenAskedFor(IServiceInterface).useSingletonOf(MyServiceImpl);
injector.newRule().whenAskedFor(MyConcreteClass).useSingleton();

//"Live" rules act just like <mx:Binding>
injector.newRule().whenAskedFor(String).named("userId").useBindableProperty(this, "userId");

But how do I kickstart the whole thing?

Good question! Two ways. First, in an MXML component:

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:smartypants="http://net.expantra.smartypants/2008">

    <smartypants:RequestInjection/>

    <!-- ...Regular code and MXML... -->

</mx:Canvas>

And in ActionScript it's even simpler:

var injector : Injector = Smartypants.getOrCreateInjectorFor(this);
injector.injectInto(this);

Hopefully this gives a little more insight into the style and ideas behind Smartypants-IOC. Be sure to check it out on the Google Code site, and I'd love to hear your thoughts and ideas for the future!

Friday, November 7, 2008

DSLs in MXML

I like DSLs, and god-help me I like MXML. Mainly for the auto-complete features we get from Builder. I've recently built a new application framework at Pathways (my 9-5), which uses an MXML file as the core of the application to map events, services, actions(think commands), etc. This has got me thinking I'd like to have a tool that'll let me define an MXML DSL and have it spit out a bunch of AS3 classes to implement it. I'd like to hear from anybody who's got any thoughts on the idea, or things they'd like to see included. I probably won't get started on it just yet, but it's floating around in my head with the rest of the crazy ideas waiting to escape into code.

Thursday, November 6, 2008

Free Book! Giggity!

I forgot to brag! About a week or so ago, I received my free copy of Flex 3 Cookbook in the mail, courtesy of the Flex team @ Adobe! An unnecessary but welcome reward for getting 3 SDK patches accepted!

Cookbook!

Anyway, enough bragging, and a big thanks to Matt and the rest of the Flex team for building the badass platform we use every day!

Wednesday, November 5, 2008

Skinning Flex apps with Degrafa

I've been a lazy bastich, but here's the promised post on skinning with Degrafa. Note: this is based on Degrafa Beta 2. Beta 3 is expected rather soon, and there'll be a couple of changes such as the new namespace etc.

OK, for this example, we're going to skin a simple little <mx:Box> to dress it up some. Here's our MXML for the component we'd like to skin:

<mx:Canvas styleName="skinnedBox" width="250" horizontalCenter="0" verticalCenter="0">
    <mx:Text width="100%">
        <mx:text>
            Aliquam et nisl vel ligula consectetuer suscipit. Morbi euismod enim eget neque. Donec sagittis
            massa. Vestibulum quis augue sit amet ipsum laoreet pretium. Nulla facilisi. Duis tincidunt,
            felis et luctus placerat, ipsum libero vestibulum sem, vitae elementum wisi ipsum a metus.
            Nulla a enim sed dui hendrerit lobortis. Donec lacinia vulputate magna. Vivamus suscipit lectus
            consectetuer adipiscing elit.
        </mx:text>
    </mx:Text>
</mx:Canvas>

And a simple "before" CSS:

/* CSS file */
.skinnedBox
{
    border-style:solid;
    border-thickness:1;
    border-color:black;
    background-color:#cccccc;
}

Giving us this:

And we're going to skin it so it looks like this:

If we were skinning a Button, we'd use things like up-skin, down-skin, etc. But, we're just skinning a box, so we'll use border-skin:

/* CSS file */
.skinnedBox
{
    border-skin:ClassReference("skinningInDegrafa.ShinySkin");
}

We want to specify the borderMetrics of our skin (to add the padding), so we'll subclass <degrafa:GraphicRectangularBorderSkin>:

<?xml version="1.0" encoding="utf-8"?>
<degrafa:GraphicRectangularBorderSkin xmlns:mx="http://www.adobe.com/2006/mxml" width="400" height="300" xmlns:degrafa="http://www.degrafa.com/2007">

    <mx:Script>
        <![CDATA[
            import mx.core.EdgeMetrics;

We need to keep a copy of the width and height which is set by the framework. The actual height and width are set on updateDisplayList, which is as good a place as any to take note of them, and we'll have less unnecessary updates that way:

            [Bindable]
            private var skinWidth : Number = 0;

            [Bindable]
            private var skinHeight : Number = 0;

            private var edges : EdgeMetrics = new EdgeMetrics(20, 5, 20, 5);

            override protected function updateDisplayList(unscaledWidth : Number, unscaledHeight : Number) : void
            {
                skinWidth = unscaledWidth;
                skinHeight = unscaledHeight;
                super.updateDisplayList(unscaledWidth, unscaledHeight);
            }

            override public function get borderMetrics() : EdgeMetrics
            {
                return edges;
            }

        ]]>
    </mx:Script>

Below is the exciting part that defines the shapes and fills! A how-to on Degrafa in general is out of scope for this post, so see the Degrafa site for documentation and more examples.

    <degrafa:fills>
        <degrafa:LinearGradientFill angle="90" id="f">
            <degrafa:GradientStop ratio="0" color="#dde4ee"/>
            <degrafa:GradientStop ratio="1" color="#aaa0bb"/>
        </degrafa:LinearGradientFill>

        <degrafa:LinearGradientFill angle="90" id="f2">
            <degrafa:GradientStop ratio="0.3" alpha="0.02" color="#ffffff"/>
            <degrafa:GradientStop ratio="1" alpha="0.3" color="#ffffff"/>
        </degrafa:LinearGradientFill>
    </degrafa:fills>

    <degrafa:geometry>

        <degrafa:Path fill="{f}">
            <degrafa:MoveTo x="0" y="{ skinHeight / 2 }"/>
            <degrafa:CubicBezierTo x="20" y="0" cx="0" cy="{skinHeight * 0.25}" cx1="1" cy1="1"/>
            <degrafa:LineTo x="{ skinWidth - 20}" y="0"/>
            <degrafa:CubicBezierTo x="{ skinWidth }" y="{ skinHeight / 2 }" cx="{ skinWidth - 1}" cy="1" cx1="{ skinWidth }" cy1="{ skinHeight * 0.25 }"/>
            <degrafa:CubicBezierTo x="{ skinWidth - 20 }" y="{ skinHeight }" cx="{ skinWidth }" cy="{ skinHeight * 0.75 }" cx1="{ skinWidth - 1 }" cy1="{ skinHeight - 1 }"/>
            <degrafa:LineTo x="20" y="{ skinHeight }"/>
            <degrafa:CubicBezierTo x="0" y="{ skinHeight / 2 }" cx="1" cy="{ skinHeight - 1 }" cx1="0" cy1="{ skinHeight * 0.75 }"/>
            <degrafa:ClosePath/>
        </degrafa:Path>

        <degrafa:Path fill="{f2}">
            <degrafa:MoveTo x="10" y="{ skinHeight / 2 }"/>
            <degrafa:CubicBezierTo x="20" y="0" cx="0" cy="{skinHeight * 0.25}" cx1="1" cy1="1"/>
            <degrafa:LineTo x="{ skinWidth - 20}" y="0"/>
            <degrafa:CubicBezierTo x="{ skinWidth - 10 }" y="{ skinHeight / 2 }" cx="{ skinWidth - 1}" cy="1" cx1="{ skinWidth }" cy1="{ skinHeight * 0.25 }"/>
            <degrafa:CubicBezierTo x="10" y="{ skinHeight / 2 }" cx="{ skinWidth * 0.75 }" cy="{ skinHeight * 0.4}" cx1="{ skinWidth * 0.25 }" cy1="{ skinHeight * 0.4}"/>
            <degrafa:ClosePath/>
        </degrafa:Path>

    </degrafa:geometry>

    <degrafa:filters>
        <mx:GlowFilter inner="true" color="#000000" blurX="3" blurY="3" alpha="0.3"/>
        <mx:DropShadowFilter alpha="0.3" angle="90" distance="5" blurX="8" blurY="8"/>
    </degrafa:filters>

</degrafa:GraphicRectangularBorderSkin>

And that's a pretty simple example of skinning with Degrafa! Here's the complete example (with view-source enabled).

This is

  • Tales of Flex
  • From Brisbane, Australia
  • Opinions on Flex development
  • Tips and FAQs
  • Shameless self-promotion

I am

  • Twitterer
  • Flexcoder
  • Maroon
  • Designer
  • Java lover
  • That loud-mouthed Aussie yob
  • Blogger
  • Problem solver
  • Contributor
  • Cricket Fan
  • Lousy photographer
  • Great cook

I read



Subscribe via RSS to receive updates!