WidgetFactory 5.1 Released!

Celebration!

Yes fellow widget builders, there be a new version of WidgetFactory for us to sink our… keyboards… into…

It’s a release candidate. Which means I’m pretty sure there are no serious bugs, but I’m covered if there are.

Click here to get WidgetFactory 5.1 Release Candidate 1!
5.1 has been in the works for a long time and it took a lot of effort and hard work and blah blah blah bla- FEATURES!

New Features

Almost Unlimited Slide Access

Previously, WidgetFactory allowed you access to the slide the widget was located on. Now however, widgets can now access ANY slide in the Captivate Movie.

There is one limitation though: The slide must have been loaded by Captivate. So if your widget is on slide 1 and is trying to access slide 500, that slide may not exist yet. However, as soon as that slide has been created, it will be made available to you, and in Captivate 5 they tend to load very fast.

With that in mind, let’s take a look at the new tools:

getSlide(slide number or label)

Returns the slide (a MovieClip) with the number or label that you pass in. Yes that’s right, you can search by slide label! But only in Captivate 5 and above.

1
2
3
var slideOfInterest = getSlide("My Slide Label");
// slideOfInterest now holds the slide with the label of My Slide Label,
// assuming there is such a slide.

gotoSlide(slide number or label)

Makes the move jump to the slide with the number or label that you pass in. Slide labels can only be used with Captivate 5. BTW: The slide loading limitation mentioned above does not apply to this method.

1
2
gotoSlide(6);
// The Captivate movie will move to slide six.

currentSlide

This property gives you access the slide that the user is currently watching. Useful if you’ve got a widget which is set to display for rest of project (more on that later).

getSlideObjectByName(object name, slide)

This method has been in WidgetFactory since Version 1.2. By passing a string into getSlideObjectByName() you could (and still can) access any object on the widget’s slide. However, now there’s an optional second parameter which defines which slide the object will be retrieved from. This is done by (you guessed it) passing in that slide’s number or slide label. Once again: slide labels + Captivate 4 = no-no.

1
2
3
var slideObjectOfInterest:Sprite = getSlideObjectByName("HipHighlightBox", 3);
// slideObjectOfInterest now holds the highlight box called HipHighlightBox located on slide 3.
// assuming such an object (or slide) exists.

Passing in the slide’s MovieClip would also work. So if you plug currentSlide into the second parameter, the search will be directed to the slide being currently viewed.

Remember, this is an optional parameter. If you leave it out, the search will default to the widget’s slide.

Widget Information

Ever wanted to know whether the Captivate Author attached a sound to your widget? Or whether the widget has a fade in or fade out animation? Or how many failure criterias the Captivate Author has allowed for your Interactive Widget?

Well the new widgetInfo property (available on each widget class) holds a whole slew of information on how the widget has been set up. Here are the various properties and information you can access on widgetInfo.

Frame Numbers

  • widgetInfo.startFrame
  • widgetInfo.endFrame
  • widgetInfo.hasFadeIn
  • widgetInfo.hasFadeOut
  • widgetInfo.totalFadeInFrames
  • widgetInfo.totalFadeOutFrames
  • widgetInfo.fadeInEndFrame
  • widgetInfo.fadeOutStartFrame

Interactive Widgets

  • widgetInfo.pauseFrame
  • widgetInfo.pauseForCaptions
  • widgetInfo.maxAttempts
  • widgetInfo.continueAfterSuccess
  • widgetInfo.continueAfterFailure

Widget SWF’s Original Size

  • widgetInfo.originalWidth
  • widgetInfo.originalHeight

Misc

  • widgetInfo.hasInitialVisibility
  • widgetInfo.isSynchronizedWithProject
  • widgetInfo.widgetSlideNumber
  • widgetInfo.hasAudioAttached
  • widgetInfo.initialAlpha
  • widgetInfo.isDisplayedForRestOfProject

For more information on each of these tidbits of information, see the WidgetInfo class in documentation.

Various Other Features

isInAggregator

Sometimes widgets that work in normal Captivate don’t work so well when the Captivate Movie is included in an Aggregated Project. Especially if the widget plays around with the TOC, Closed Captioning, or Playbar.

This property will be true if the widget is inside an aggregator. Using this in conjunction with an if statement, you can change how the widget works when it’s inside an aggregator.

isFirstRuntime

I promised to add this to the next version of WidgetFactory, and so I did. When runtime is first dispatched, it will be true. Whereas after that, it will be false. This post explains why this is useful.

quizData

Now you can access information on the quiz setup when responding to the WidgetEvent.INTERACTIVE_ITEM_SUBMITTED event, or the QuestionWidgetEvent.QUESTION_SUBMITTED event, through the event object’s quizData property.

1
2
3
4
5
6
addEventListener(QuestionWidgetEvent.QUESTION_SUBMITTED, onQuestionSubmitted);

private function onQuestionSubmitted(e:QuestionWidgetEvent):void
{
    e.quizData;
}

Here’s a list of all the information you can access:

  • quizData.slideNumber
  • quizData.questionNumber
  • quizData.questionType
  • quizData.questionAnswered
  • quizData.questionAnsweredCorrectly
  • quizData.questionMaxScore
  • quizData.questionScore
  • quizData.questionMaxAttempts
  • quizData.questionAttempts
  • quizData.interactionID

For more information on this, see the documentation under the QuizData class.

WidgetPropertiesRawData

If anyone has been using the widgetPropertiesRawData property (which I seriously doubt), be advised that has now been moved to the new WidgetInfo class and renamed to widgetPropertiesXML.

enterMovie()

The enterMovie() Template Method now works consistently in Captivate 4.

Bug Fixes

Along with the new, there is also the repairing of the old. If you’ve come astray of any of these little insects, be reassured they’ve now been extracted:

  • Widgets no longer prevent the Captivate Movie from resizing. As noted here.
  • Static Widgets now work when set to display for rest of project.
  • In CP4, closing down the Widget Properties dialog without going to the Widget Parameters tab no longer results in the Widget Properties being forgotten.
  • Corrected misspelt parameter in the QuestionWidgetEvent class.
  • triggerSubmitProcess() no longer requires the submit button to be on stage to work.
  • Various behind the scenes clean up.

I hope that’s given you some new toys to play with. Be warned, I have much more planed… SO MUCH!

If you have any feature requests, please let me know and I’ll see what I can do to include them.

Until next time, I bid thee farewidget!

Posted in WidgetFactory Builds, Widgets | Tagged , , , , , | 4 Comments

DIY Widget Panel Preview Construction

The two main Widget Modes are Runtime and the Properties Dialog. These are essential to have a working widget. However, they are not the only widget modes. So in this post, we’re going to look at one of the more obscure widget modes.

The Widget Panel Preview.

WidgetPanel

What is it?

The Widget Panel (found under the Window > Widget menu in Captivate) lists the widgets in the Captivate Gallery folder. To find this folder, go to Program Files and into Adobe\Adobe Captivate 5\Gallery\Widgets. If you insert your widget here, it will appear in the list next time it is refreshed.

When you select one of the widgets, the window above the list will display a ‘preview’ of the widget. This preview could range anything from a splash screen, to a detailed tutorial, or maybe even a help system. Either way, what’s actually being shown here is the widget swf. Which means we need to program the screen ourselves.

How do we build it?

To make something happen in the preview window, write it in the enterWidgetPanelPreview() Template Method.

1
2
3
4
override protected function enterWidgetPanelPreview():void
{
   
}

When WidgetFactory detects that the widget swf is being displayed in the widget panel, it will call this function and thus the code inside it will run.

For me I like to keep my preview screen simple. I usually have the widget’s name up top, a logo in the middle, and a link to the company website down the bottom. All told, that code looks similar to this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
override protected function enterWidgetPanelPreview():void
{
    ////// The Title text field.
    var title:TextField = createTextField("Widget Panel Testing Widget", 16, true);
    title.x = stage.stageWidth / 2 - title.width / 2;
    title.y = 25;
          
    ////// Logo graphic
    var logo:Sprite = new Sprite();
          
    // Usually we wouldn't draw our own logo. But we will for this example.
    logo.graphics.beginFill(0xFF0000); // Fill colour
    logo.graphics.lineStyle(3, 0x000000, 1); // Line thickness, colour, alpha
    // Draws a 100x100 rectangle in the centre of the Sprite
    logo.graphics.drawRect( -50, -50, 100, 100);
    logo.graphics.endFill();
    addChild(logo);
           
    logo.x = stage.stageWidth / 2;
    logo.y = 125;
           
           
    ////// The Company Link text field.
    var link:TextField = createTextField("By the Widget King", 12, false, "<a href="http://www.infosemantics.com.au/widgetking/&quot;);">http://www.infosemantics.com.au/widgetking/");</a>
    link.x = stage.stageWidth / 2 - link.width / 2;
    link.y = 200;
           
}
       
// This function takes care for the usual text field operations.
private function createTextField(text:String, size:int, bold:Boolean, link:String = ''):TextField
{
    var tf:TextField = new TextField();
    addChild(tf);
           
    tf.defaultTextFormat = new TextFormat("Verdana", size, 0x000000, bold, null, null, link);
           
    tf.text = text;
    tf.autoSize = TextFieldAutoSize.CENTER;
           
    return tf;
}

The above code yields this Widget Panel preview screen.

PanelTest

As far as looks go, it’s okay. However, there is one BIG problem. The Widget Panel is resizable. And if you resize the window, this screen will no longer be laid out correctly.

Working with Resizable Windows

To get our screen to resize correctly, we use much the same method as any other resizable swf window. First, you set the stage’s scale mode to NO_SCALE; which WidgetFactory already does for you. NO_SCALE means flash won’t try and resize the swf by itself, instead it will dispatch an Event.RESIZE event each time the stage size changes so that we can listen for it and deal with the resizing.

For more information about working with events, see this post.

1
stage.addEventListener(Event.RESIZE, onStageResize);

Note: This event is dispatched from the stage.

Now inside the handler (the onStageResize function passed in above) we reposition the elements (the tile, logo, and link) to align to the new stage size. So we move all the code that positions the on screen elements from enterWidgetPanelPreview() into the onStageResize() function.

1
2
3
4
5
6
7
8
9
10
11
private function onStageResize(e:Event):void
{
    title.x = stage.stageWidth / 2 - title.width / 2;
    title.y = 25;
           
    logo.x = stage.stageWidth / 2;
    logo.y = 125;
           
    link.x = stage.stageWidth / 2 - link.width / 2;
    link.y = 200;
}

If you’re following along, then you’re going to have to make the title, logo, and link objects  into class variables so that they can be accessed outside the enterWidgetPanelPreview() function.

Because we’ve horizontally position the objects according to the stage’s width, the screen will look find if we resize the preview horizontally. However, because all the y (vertical) locations have been hard coded, the elements will always stay at the same height despite the stage’s size. So the lesson is: When you’re dealing with resizable windows, always position your elements according to the stage width and height.

Let’s make it so that the title is positioned at an eighth of the stage height, the logo at half the stage height (putting it in the centre), and the link at seven eigths of the stage height to balance out the title.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private function onStageResize(e:Event = null):void
{
    // Title is 1 eigth down the stage.
    title.x = stage.stageWidth / 2 - title.width / 2;
    title.y = stage.stageHeight / 8;
           
    // Logo is halfway down the stage.
    logo.x = stage.stageWidth / 2;
    logo.y = stage.stageHeight / 2;
           
    // Link is seven eigths down the stage.
    link.x = stage.stageWidth / 2 - link.width / 2;
    link.y = stage.stageHeight - stage.stageHeight / 8;
}

Now when you resize the Widget Panel’s Preview, the layout remains constant.

One more thing. Seeing as the positioning code has been moved to the onStageResize() function, the objects aren’t aligned until the screen is resized. This means before you resize the stage you see this:

 unaligned

To position the objects without duplicating the positioning code, we’ll call the onStageResize function during the initial set up.

1
2
3
4
5
6
override protected function enterWidgetPanelPreview():void
{
    ///.... Other stuff in this function.
           
    onStageResize();
}

onStageResize() has been built to respond to an event, and is therefore expecting an event object to be passed into it.

1
private function onStageResize(e:Event):void

The (e:Event) means an error would be caused if we tried to call onStageResize() without passing in an event object. To get around this, we give the e parameter a default value; making passing something in optional.

1
private function onStageResize(e:Event = null):void

Here’s the complete code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package  {
   
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.text.TextFieldAutoSize;
    import flash.text.TextField;
    import flash.text.TextFormat;
    import widgetfactory.StaticWidget;
   
    public class TestWidgetPanelPreview extends StaticWidget {
       
        private var title:TextField;
        private var logo:Sprite;
        private var link:TextField;
       
        public function TestWidgetPanelPreview() {
           
            ///// Uncomment next line to test the preview screen.
            //enterWidgetPanelPreview();
        }
       
        override protected function enterWidgetPanelPreview():void
        {
            ////// The Title text field.
            title = createTextField("Widget Panel Testing Widget", 16, true);
           
            ////// Logo graphic
            logo = new Sprite();
           
            // Usually we wouldn't draw our own logo. But we will for this example.
            logo.graphics.beginFill(0xFF0000); // Fill colour
            logo.graphics.lineStyle(3, 0x000000, 1); // Line thickness, colour, alpha
            // Draws a 100x100 rectangle in the centre of the spirte
            logo.graphics.drawRect( -50, -50, 100, 100);
            logo.graphics.endFill();
            addChild(logo);
           
           
            ////// The Company Link text field.
            link = createTextField("By the Widget King", 12, false, "<a href="http://www.infosemantics.com.au/widgetking/&quot;);">http://www.infosemantics.com.au/widgetking/");</a>
           
            stage.addEventListener(Event.RESIZE, onStageResize);
           
            onStageResize();
        }
       
        private function onStageResize(e:Event = null):void
        {
            // Title is 1 eigth down the stage.
            title.x = stage.stageWidth / 2 - title.width / 2;
            title.y = stage.stageHeight / 8;
           
            // Logo is halfway down the stage.
            logo.x = stage.stageWidth / 2;
            logo.y = stage.stageHeight / 2;
           
            // Link is seven eigths down the stage.
            link.x = stage.stageWidth / 2 - link.width / 2;
            link.y = stage.stageHeight - stage.stageHeight / 8;
        }
       
        // This function creates a formatted text field to allow us to make the text above quicker.
        private function createTextField(text:String, size:int, bold:Boolean, link:String = ''):TextField
        {
            var tf:TextField = new TextField();
            addChild(tf);
           
            tf.defaultTextFormat = new TextFormat("Verdana", size, 0x000000, bold, null, null, link);
           
            tf.text = text;
            tf.autoSize = TextFieldAutoSize.CENTER;
           
            return tf;
        }
       
    }
   
}

Click here to download the widget files.

On a final note and warning, Captivate 5′s Widget Panel Preview screen is a little unpredictable. Sometimes it works, other times doesn’t show up at all. Usually when I want to see if my interface is working I test it in Captivate 4.

Posted in Widgets | Tagged , , | Leave a comment

Debugging Widgets Inside Your IDE

Last week we discussed a few ways that you can debug your widget from inside Captivate. This week I’ll show you how to trick your widget into believing it’s in Captivate while still testing in your IDE.

To Trick A Widget

In pretty much every post I’ve written so far, I’ve linked back to one of the first articles I ever wrote – the Template Methods post. I’ve linked back to it so often, that my blogging software now makes the link automatically when I type ‘Template Methods’ (It’s becoming sentient!), with good reason too.

Template Methods are the most essential feature in WidgetFactory. Without them, each Widget Mode would do the same thing and widgets would be pointless. With Template Methods employed, all your mode code is segregated nicely into individual methods to be called when needed.

Now don’t let the word ‘Template’ fool you into thinking there’s something special about these methods (or functions). They’re just the same as any other function in your program, and you can call them yourself just the same.

So what would happen if instead of Captivate calling the Template Method, we called it instead? Let’s say, in our constructor, like below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package
{
    import fl.controls.TextInput;
    import widgetfactory.StaticWidget;
   
    public class TestingWidget extends StaticWidget
    {

        private var textField:TextInput;

        public function TestingWidget()
        {
            ///////////// THE IMPORTANT BIT
            enterPropertiesDialog();
        }
       
        override protected function enterPropertiesDialog():void
        {
            // Building the Properties Dialog interface.
            // Of sorts
            textField= new TextInput();
            addChild(textField);
           
            textField.width = 250;
            textField.text = "Seriously... I'm a Properties Interface."
           
            // Centre text field.
            textField.x = stage.stageWidth / 2 - textField.width / 2;
            textField.y = stage.stageHeight / 2 - textField.height / 2;
        }
    }
}

Then when we publish the swf, we’d get…

TrickPropertiesInterface

This kicks the widget’s Properties Dialog code off even though it’s not inside Captivate at all. Which means you can test how your Properties Dialog interface is going to act, without having to leave your IDE. Which means you can use all your IDE’s debugging tools, including trace!

You can do this for any of the other widget modes as well, and it works remarkably well for the Widget Panel Preview (enterWidgetPanelPreview()), and even Stage mode (enterStage()). Where you start running into difficulties is with Runtime.

If your widget’s actions are relatively internal, then you could probably test it successfully in the IDE. However, if your widget interacts with the Captivate Movie at all, in say: grabbing slide objects, reading Captivate Variables, jumping to another slide, then the only way to reliably test it is to bring it into Captivate.

About now some of you may be raising your hands and asking: “What about Widget Properties?” Well that’s a good point. Both the Properties Dialog and Stage modes rely heavily on Widget Properties. Fortunately there is a way to get around that. Fake ‘em!

How to Forge Widget Properties

This is quite similar to The Widget King Widget Properties Method I wrote about in another post. I’ll try and sum up in a few sentences what it took me that whole post to explain.

The first time the user enters the Properties Dialog, there are no proir properties to populate the interface. In this case you have to set up some default properties. We have essentially the same problem here and can use the same method to solve it.

Let’s take the widget example above and make some changes. Let’s save and text written in the textField to a Widget Property.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package
{
    import fl.controls.TextInput;
    import widgetfactory.StaticWidget;
   
    public class TestingWidget extends StaticWidget
    {
        private var textField:TextInput;
       
        public function TestingWidget()
        {
            ///////////// THE IMPORTANT BIT
            enterPropertiesDialog();
        }
       
        override protected function enterPropertiesDialog():void
        {
            if (properties.text == undefined) {
                properties.text = "Default Text";
            }
           
            // Building the Properties Dialog interface.
            // Of sorts
            textField = new TextInput();
            addChild(textField);
           
            textField.width = 250;
            textField.text = properties.text;
           
            // Centre text field.
            textField.x = stage.stageWidth / 2 - textField.width / 2;
            textField.y = stage.stageHeight / 2 - textField.height / 2;
        }
       
        override protected function saveProperties():void
        {
            properties.text = textField.text;
        }
    }
}

To do that we’ve had to make a number of changes, but most important for us is this if statement:

1
2
3
if (properties.text == undefined) {
    properties.text = "Default Text";
}

This if statement checks if the Widget Properties had been set before. If they haven’t, then we give the property a default value which is then applied to the textfield latter. However, this if statement will evaluate to true when testing in your IDE also. So you’ll see the default text come up.

All fine and dandy, but what if you wanted to test a certain combination of properties without messing with your defaults? In that case set those properties up in your constructor before you call enterPropertiesDialog(). In that case the if statement will evaluate to false and wont set up the defaults.

1
2
3
4
5
public function TestingWidget()
{
    properties.text = "Not default text. HA-HA!";
    enterPropertiesDialog();
}

If you have a large set of properties though, it might be better to extract this operation into a function of its own. Then when you come to releasing your widget, you only have to comment out one line.

Bare in mind this method will work just as well with any of the other Widget Modes.

Faking Template Methods for Question Widgets

Question Widgets have some additional Template Methods. Most notably the submit() method, which is called when the audience submits the question, and the clearAnswers() method, which is called when the audience clicks the clear button.

SubmitAndClear

But what if you’re testing your question in the IDE and you want to see if your submit process, or question clearing, is working correctly?

In that case, you can add a temporary button to the stage, listen for when it’s clicked, and when it is call the submit() or clearAnswers() template method. In code, it looks like this:

1
2
3
4
5
6
7
8
9
10
11
var button:Button = new Button();
addChild(button);
           
button.label = "Submit";
           
button.addEventListener(MouseEvent.CLICK, onButtonClick);
       
private function onButtonClick(e:MouseEvent):void
{
    submit();
}

You could also jerry rig something like this to trigger the saveProperties() Template Method, and then trace out the properties object to see if your properties are being extracted correctly.

The other advantage of this method, besides being able to use the beloved trace, is that it saves a lot of time. Rather then having to pull your widget into Captivate every time you make a change, just publish the movie and there you go!

So in short: Call a Template Method and everyone’s happy!

Posted in Debugging, Question Widgets, Tips, Widget Properties | Tagged , , , | 2 Comments

Debugging – A Widget Debacle

No matter what you’re programming, be it a widget, a desktop app, or anything else, you’re going to need to debug it. Yes, it seems no matter how small the program them bugs will find their way into some crevice. Widgets can be particularly susceptible as there are a number of ‘gotchas’ that result from unique Captivate situations.

So you definitely need your debugging tools at the ready when building widgets.

The problem is, the nature of Captivate makes debugging a bit of a debacle.

Let Me Explain

The most debugging basic tool is trace(); which sends a message to the debugging program. So if you wrote…

1
trace("Hello World");

…you’d get:

OutputPanel

As long as you’re testing with an IDE (like Flash Builder, Flash Pro, Flash Develop, FDT) then you’ll have a panel, like the one above, which will receive your traces.

So what’s so great about tracing? Well it’s very useful to see if your if statements was successful…

1
2
3
if (this == that) {
    trace("This does equal That");
}

…or to find the current value of a variable…

1
2
3
4
5
6
7
var startingNumber:Number = 63;
var multiplier:Number = 7;

startingNumber = startingNumber * multiplier;

// What does startingNumber equal now?
trace(startingNumber); // 441

…or to see if/when your function has been called…

1
2
3
private function usefulFunction ():void {
    trace("UsefulFunction has been called");
}

…or if you want to know what type an object is…

1
2
var slideObject = getSlideObjectByName("SlideObjectName");
trace(slideObject);

With widgets however, most of the time you have to test them inside Captivate, which is OUTSIDE your IDE and far away from the handy trace panel.

So if you were in the properties dialog and wanted to trace a message…

1
2
3
override protected function enterPropertiesDialog():void {
    trace("Hello World!");
}

…you’d get…

 WidgetProperties

…nothing.

This makes debugging difficult. However, we are not doomed to spurn the ‘trace’ ability forever for there are a number of alternatives to the trace() method. I shall now list some of them in reverse order as to which I feel are the best.

Flash Debug Player

This is a developer version of the Flash Player that is supposed to write your traces to a text file somewhere on your computer. I vaguely remember someone saying that it can also display them on screen somewhere (though I’ve never been able to make it do so).

This is the method recommended in the Captivate help, but I’ve always had trouble installing the debug player, and I really want to see my traces live, not have to dig them out of some obscure directory.

So… Not for me…

DIY Output Panel

You could create a text field, add it to the stage, and direct your traces to it instead. The problem is it can be very time consuming to program a whole tracer interface.

I’m looking into building a tracing field much like this into WidgetFactory. But for current days I recommend you go with one of the next options.

Little Circles

In comparison to many other languages, drawing stuff in ActionScript is very easy. Drawing a circle can be a nice visual hint as to when something has happened. What’s more, you don’t even have to create an object for it, you can scribble it directly on to the widget itself. Like so:

1
2
3
graphics.beginFill(0xFF0000);
graphics.drawCircle(10, 10, 10);
graphics.endFill();

If you’re not familiar with drawing things in ActionScript, here’s a quick explanation of the above. graphics.beginFill() takes the hex colour (in the above case: red) of what we’re about to draw. The first two parameters of graphics.drawCircle() define the x and y location respectively of where the circle’s centre will be drawn, while the third parameter is the radius of the circle (So the above would draw a 20 pixel wide circle in the top left corner of the widget). Lastly, graphics.endFill() stops drawing with the colour from graphics.beginfill(). Cleaning the paintbrush of its colour so to speak.

Circle

This is a rather quick and dirty way of testing stuff. Where it really shines is when you’re trying to work out how many times something is happening. You can do this by changing graphics.drawCircle() to give the circle a random position.

1
graphics.drawCircle(Math.random() * 300, Math.random() * 300, 10);

Circles

This will make the circle appear randomly in the top 300 square pixels of the widget. So if the code was called six times, you’d see six circles. If you need to test a second section of code this way, then you can draw those circles with a different colour.

Use a 3rd Party Debugger

There are quite a number of AIR apps that can connect to your swf and help debug your code. My favourite is DeMonster Debugger. You can download it here, and I suggest you watch this tutorial here.

It has a few annoyances. For example: it is a little processor intensive, and it adds a good 10k to your widget’s file size; so before you release your widget makes sure you remove all references to it.

What makes DeMonster Debugger so great, is that it not only traces strings (letters and words):

1
MonsterDebugger.trace(this, "Hello World");

But objects as well.

So if you want to know how your properties look at runtime you can, by tracing them:

1
MonsterDebugger.trace(this, properties);

Trace

This also makes DeMonster Debugger useful for exploring the inner workings of Captivate. Do you want to know how slides work? Trace widgetSlide. Do you want to see a complete list of all Captivate Variables? Trace cpVariables.

If a little Captivate spelunking is your aim, then I recommend you check out Whyve’s CPXray widget. It works in conjunction with DeMonster Debugger, and quickly allows you to explore any slide of the movie with it.

So there are a few different ways for you to extract the arachnids from your widgets. I do, however, have one other hot debugging tip. It is in fact so hot that it will need it’s own separate post…

Posted in Debugging, Widgets | Tagged , , , , , | 2 Comments

Static Widgets vs. Interactive Widgets

StaticVsInteractive

There seems to be a bit of confusion around about what exactly makes an Interactive Widget ‘Interactive’ and a Static Widget ‘Static’.

Many people seem to believe that Static Widgets have no interaction with the audience (no mouse, keyboard, controller, or microphone input), whereas Interactive Widgets do. Which is logical because ‘Static’ means no movement and ‘Interactive’ means… Well it doesn’t literally mean movement, but it carries the idea of it.

However, unlike what the names suggest, the difference between Interactive and Static widgets has nothing to do with user interaction or movement.

What is a Widget?

Speaking purely on a technical basis, a widget is a swf. Anything a swf can do, any widget can do. This includes all those cool user interactions that you see in those flash games you secretly play at work.

When the widgets are brought into Captivate, no changes are made to them. Captivate doesn’t somehow turn off interactivity for Static Widgets. So as far as user interaction goes, Static Widgets are just as responsive as Interactive Widgets.

So then… What’s an Interactive Widget?

An Interactive Widget as a Widget with Interactive Object abilities.

Wha?

Okay let me put that another way. What’s the difference between a Highlight Box and a Click Box?

Before you say ‘Clicking’ I’ll have you know that Highlight Boxes are more than happy to be clicked. Don’t believe me? Then <plug type=’shameless’> try out our Event Handler widget</plug>.

The main two things that set click boxes appart are the Action and Reporting menus.

ActionsAndReporting

The Action menu has two drop downs that allow you to say what happens after the click box is successful (when the audience clicks inside the box) or after it has finally failed (when the audience clicks outside the box). The Reporting menu allows you to add the results of the click box to the quiz.

Click boxes are not the only Captivate object to have these menus. Text Entry Boxes, and Buttons also make use of them.

Now here’s the interesting thing. If you go to the main page of the Captivate 5 help, you’ll see a section for ‘Interactive Objects’. If you expand it, what do you see?

InteractiveObjects

Yep. Those three objects that share the Action and Reporting menus are known as ‘Interactive Objects’. And wouldn’t you know it, Interactive Widgets have those menus too!

So that’s what I mean when I say that Interactive Widgets are regular widgets with Interactive Object abilities. In another post I’ll go into how you can harness the Interactive Object stuff for your own widgets. If you can’t wait, then look up the InteractiveWidget class in the WidgetFactory ASDoc.

So then what does a Static Widget do?

Here’s the thing. Although there is a (sort of) justifiable reason why Interactive Widgets are called Interactive Widgets, I have yet to find out what’s so Static about Static Widgets. As already mentioned above, they respond just as well to user input as any other widget.

Probably a better name for them would be ‘Regular’, or ‘Normal’, or ‘Generic’, or perhaps just ditch the prefix altogether and call them ‘Widgets’. Because that’s what they are; a plain widget. Widgets without the bells and whistles. Static Widgets are the basis for Interactive and Question Widgets. Quite literally. The Widget Template for Interactive and Question Widgets is Static Widget’s template with extra code tacked on the end.

The above may make them sound a bit bland. But the beauty of ‘Static’ Widgets is that they have no predefined purpose. While Interactive Widgets live only to trigger the success or failure action, and Question Widgets only exist so as to mark a question as correct or incorrect. Static Widgets could be anything. They are not required to answer back to Captivate. They are free as the wind blows. This is why about 90% of the widgets in the Widget Treasury are Static.

So let’s give Static Widgets a round of applause. Far from being dull and non-interactive, they are in fact the quiet ruler of the Widget Types.

So I hoped that has cleared up some things for you. I’m going to go find a thesaurus now.

Posted in Basics, Interactive Widgets, Static Widgets | Tagged , , | 8 Comments

Question Widgets – Part 4

The Review Area.

ReviewArea

Some people love it, most people hate it, either way it’s an aspect of Question Widgets, and Question Widgets are what I’m teaching.

What is the Review Area?

In theory, the Review Area is supposed to supply the audience feedback on how they answered the question. When they answer it correctly, the Review Area has the stock response of ‘You answered this correctly!’ It’s when the audience gets the question wrong where things get complicated. The general consensus is that there is only one correct answer and many wrong answers. Not that this is true in real life, but that’s how it works in Captivate. So although you can only have one message for when the answer is correct, which is controlled in the Review Area’s properties…

ReviewFeedback

…You could have potentially unlimited messages for each way the audience can get the question wrong. So Captivate allows the widget to take control of what feedback will be given if the question is incorrect.

This feedback is controlled by WidgetFactory’s Answer objects.

The Review Area and Question Widgets

Here’s the theory: You create an answer object to represent how the audience answered the question. These answers, once created, are added to an answer list. Then after the question has been submitted, Captivate takes the answer list and displays it in the review area.

An answer object has three properties, each of which are Strings:

  • answerID: Something to identify this answer object when it is dealt with by compareAnswers (See next section)
  • chosenAnswer: What will appear in the ‘Your Answer’ section of the Review Area.
  • correctAnswer: What will appear in the ‘Correct Answer’ section of the Review Area.

So here’s what you do:

  1. Import the Answer class: import widgetfactory.Answer;
  2. Create an Answer object: var answer:Answer = new Answer();
  3. The constructor for the Answer class, the () in the new Answer()bit, takes three parameters. These parameters correspond to the three properties above: answerID, chosenAnswer, and correctAnswer. answerID is required, the others are optional. So at the very least, you would have to have something like: new Answer(“answerIdentity”);
  4. If you don’t define chosenAnswer and correctAnswer in the step above, then use the answer object’s properties to assign them a value. For example: answer.correctAnswer = “Red”; answer.chosenAnswer = “Blue”;
  5. Add the answer to the answer list with QuestionWidget’s addAnswer() method: addAnswer(answer).

Let’s see how that looks in an example.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package
{
    import widgetfactory.Answer;
    import widgetfactory.QuestionWidget;
   
    public class ReviewAreaExampleOne extends QuestionWidget
    {
       
        override protected function submit():void
        {
            var answer:Answer = new Answer("answer1");
            answer.chosenAnswer = "User's Answer";
            answer.correctAnswer = "Correct Answer";
           
            // You could also write:
            // var answer:Answer = new Answer("answer1","User's Answer", "Correct Answer");
            // That would give you the exact same object.
           
            addAnswer(answer);
           
        }
    }
}

Get Adobe Flash player

Click here to download this widget.Have a play around with the chosenAnswer and correctAnswer values and see how they come up in the Review Area.

You may be wondering what happens if you add multiple answers to the answer list.

1
2
3
4
5
6
7
override protected function submit():void {
   var answer1:Answer =  new Answer("answer1","User's Answer", "Correct Answer");
   var answer2:Answer = new Answer("answer2", "User's Other Answer", "Other Correct answer");
           
   addAnswer(answer1);
   addAnswer(answer2);
}

In that case, all answers are displayed in the Review Area separated by comas.

ReviewArea2

Note that Answers have no effect on how the question is scored. If the correctAnswer and chosenAnswer properties don’t match, it’s not going to score the question as incorrect. Scoring is ONLY handled by isCorrectAnswer.

For some Question Widgets, just a static response like “You got it wrong dude” will be fine. With other ones you may have a chance to give valuable feedback. For example, say you built a Question Widget that was a hangman game. If during the game you recorded what letters the audience guessed, then you could assign them to chosenAnswer, while correctAnswer held what the correct letters would have been.

Although this works quite well for hangman, as it is a text based game, it would be much more difficult to give meaningful feedback for a certain Drag and Drop widget.

There is however, another way Answers could come in useful.

Advanced Question Reviewing

Answers work in tandem with the compareAnswers Template Method. Let’s meet that method.

1
2
3
4
override protected function compareAnswers(answerID:String, chosenAnswer:String, correctAnswer:String):void
{
           
}

You may recognize some of those parameters. Yep, they’re the same as Answer class’ properties. When the audience reviews the quiz, compareAnswers() is called for each answer in the answer list. With this information about how the audience answered the question, the widget could provide some visual feedback to show how the question should have been answered. For example, what if we added some code to the Question Widget we made in Part 1 to make the button we pressed glow when we review the question.

All we need to do is add a few lines to the submit method and add the compareAnswers method.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
override protected function submit():void
{
    // Create the answer.
    var answer:Answer = new Answer("AnswerID");
    // The correct answer is always going to be the same.
    answer.correctAnswer = "CorrectButton";
           
    // Unless otherwise stated, the question is complete.
    isCompleteAnswer = true;
           
    // If the correctAnswerButton is selected...
    if (correctAnswerButton.selected == true) {
        // ...then the question is answered correctly
        isCorrectAnswer = true;
               
        // Set the chosen answer to be the same as the correct answer.
        answer.chosenAnswer = answer.correctAnswer;
               
    // if the incorrectAnswerButton is selected...
    } else if (incorrectAnswerButton.selected == true) {
        // ...then the question is answered incorrectly
        isCorrectAnswer = false;
               
        // Set the chosen answer to be the incorrect button.
       answer.chosenAnswer = "IncorrectButton";
               
    // If no buttons are selected...
    } else {
        // ...then the question hasn't been answered at all
        isCompleteAnswer = false;
    }
           
    // Add our answer to the answer list.
    addAnswer(answer);
}
       
// Review quiz time
override protected function compareAnswers(answerID:String, chosenAnswer:String, correctAnswer:String):void
{
    // If the question was answered correctly...
    if (chosenAnswer == correctAnswer) {
        // ...make the correctAnswerButton glow green.
        correctAnswerButton.filters = [new GlowFilter(0x00FF00,1,6,6,2,3)];
               
    // If the question was answered incorrectly...
    } else {
        // ...make the incorrectAnswerButton glow red.
        incorrectAnswerButton.filters = [new GlowFilter(0xFF0000,1,6,6,2,3)];
    }
}

Get Adobe Flash player

Click here to download this widget.

Of course, in this case it would probably be easier to check the isCorrectAnswer property to see how the question was answered, but don’t rain on my parade.

You can see how if you had multiple answers and just the chosenAnswer and correctAnswer properties to work with in compareAnswers, it could become difficult to determine which answer you’re currently dealing with. This is why we have the answerID.

Sometimes you may just want to use this Template Method to run code when the question is being reviewed. However, compareAnswers() will not be called without any answers to compare! So if make sure you have at least one answer in the answer list.

Here ends the Question Widget Saga. That is unless anyone has any further questions on Questions?

Posted in Question Widgets, Widgets | Tagged , , , , , | 18 Comments

Question Widgets – Part 3

EraserNo one’s perfect. We all get it wrong occasionally. However at some point, I think this happened soon after pencils came along, people started to expect that they would be able to redo their answer if they got it wrong. Thus the eraser was born. It morphed into a number of forms until becoming the clear button we know and love today.

For Question Widgets, when the audience clicks the clear button, it should return to an unanswered state.

How do we know when to clear the question?

You may be able to guess this for yourself by now, but there’s a Template Method for that – clearAnswers(). clearAnswers() is called whenever the question’s clear button is clicked.

Like enableAnswers() and disableAnswers(), clearAnswers() is a public function, not protected.

1
2
3
4
override public function clearAnswers():void
{
     
}

How do we clear the question?

You see the hard part is every question is different. For a multiple choice question, clearing would be deselecting each answer. For a drag and drop question, clearing would mean returning all the drag objects back to their starting position. For a crossword puzzle, clearing would mean removing all the letters from the crossword boxes. For each question type, the clearing process is different.

Let’s look at last post’s widget for an example of clearing a question. To reset that question, all you need to do is return the circle to its starting point.

1
2
3
4
5
6
override public function clearAnswers():void
{
     // Snap the circle back to the top left point.
     circle.x = 0;
     circle.y = 0;
}

So that’s not that painful. Actually, once you’ve worked out how to clear the question, the code behind it is usually not painful at all. I’m writing this sentence because I feel I should say more on this subject.

Here are some tips for clearing your question:

  • Get familiar with Arrays. Quite often your question will involve multiple objects, so you will need to record where each object was at the start of the question in the array. Then when you clear the question, read the array back and apply the values to your objects.
  • Template Methods aren’t just called by WidgetFactory, you can also call them yourself. In the last post I mentioned how you may need to use enableAnswers() to reset the question as well as add interactivity. Instead of writing all that clearing code in enableAnswers(), just call clearAnswers() again.
  • Usually when you clear the answer, everything just snaps back to its original state. However there is nothing that says the reset has to be instantaneous. If you’ve got a crossword puzzle widget, why not have the letters fade gradually from the puzzle? If you’ve got a drag and drop question, why not have the drag objects tween back to their starting position? I did that with TweenLite for last week’s Simple Circle question.

We’re drawing to the end of the Question Widget saga. We know how to score our question, we know how to enable or disable our question, and we now know how to clear our question. In the next post, we’ll look at how to control what comes up in our question’s Review Area.

Posted in Question Widgets, Widgets | Tagged , , | 1 Comment

Question Widgets – Part 2

YinYangLast week we looked at what it takes to score a Question Widget. While this is really the only thing you need to know in order to make a ‘working’ Question Widget, there’s a lot more involved in making a complete Question Widget.

What’s a ‘complete Question Widget’ you ask? That would be a question widget that for all intents and purposes works just like a native Captivate Question type. A widget that when Captivate says: “Jump!” It knows exactly how high. And the first step to building one of these, is to have your question enable and disable at the right times.

When is a Question Enabled and When is it Disabled?

A Question is enabled when the user should interact with it; it is disabled when they shouldn’t.

So when the audience is in the process of answering the question, the question is Enabled.

After the user clicks the submit button (or the widget submits the question using the triggerSubmitProcess() method) there is a slight pause before moving to the next slide to give the audience time to look over how they answered the question and what the success or failure caption says. During this period the audience can’t interact with the question. Which is good, because if they did it might make them wonder if what they just did changed how question was answered. So at this point the question is Disabled.

When the audience reviews the quiz, once again we don’t want them to think that they can change their answer. So the question is Disabled.

Then again, sometimes the audience is allowed the opportunity to retake the quiz. In this case the question must be reset and the audience allowed to answer again. So now the question is Enabled.

As you can see, working out when your question needs to be enabled or disabled can be quite mind bending. I’m not entirely sure if I’ve even noted every enabling/disabling circumstance above. The good news is, you don’t need to know exactly when the question is being enabled or disabled, because the good developers at Adobe have already sorted that out for us with two Template Methods. enableAnswers() and disableAnswers().

enableAnswers() is called whenever the question should be enabled, disableAnswers() is called whenever it should be disabled. These two functions compliment each other like yin and yang. Theoretically if you call enableAnswers() then disableAnswers() right afterward, they should cancel each other out. Or, from a programming standpoint, enableAnswers() is where you add your mouse or keyboard listeners, and disableAnswers() is where you remove them.

So where does enterRuntime() come into the mix? You should use enterRuntime() to add the question’s visual elements to the stage (or, alternatively, grab them off the slide) and store them in variables or arrays or whatever you’re using. But you don’t add your event listeners there, that’s what enableAnswers() is for. Depending on the question, you might set dispatchRuntimeAfterRewind to false, because enableAnswers() can reset the question when it’s rewound.

Show me the code

enableAnswers() and disableAnswers() are used just like any other Template Methods. That is aside from one thing: They are public, not protected like most methods.

1
2
3
4
5
6
7
8
9
override public function enableAnswers():void
{
    
}
       
override public function disableAnswers():void
{
    
}

I know, I know, it’s inconsistent and user unfriendly, but these methods are called directly from Captivate and it can only do that if they’re public.

Time for an example! Let’s say you had a question widget where all you did was drag around a circle. You’d need to make sure you could only drag the circle when the question is enabled. Here’s how you’d build that question.

Note: Because we’re focusing on enabling and disabling, I have made it so that this question will always be answered incorrectly. This means we can see if it still works when retaking the quiz.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package

{
    import flash.display.Sprite;
    import widgetfactory.QuestionWidget;
    import flash.events.MouseEvent
   
    public class SimpleCircleQuestion extends QuestionWidget
    {
        // The circle we're going to drag around screen.
        private var circle:Sprite;
       
        override protected function enterRuntime():void
        {
            // Create circle.
            circle = new Sprite();
            addChild(circle);
           
            // Draw the circle.
            circle.graphics.beginFill(0xFF0000);
            circle.graphics.drawCircle(0, 0, 25);
            circle.graphics.endFill();
           
            // Make sure we don't draw the circle again if the user rewinds the movie.
            dispatchRuntimeAfterRewind = false;
           
            // The question will always answer as incorrect so that we can test with retake quiz.
            isCorrectAnswer = false;
        }
       
        // Captivate wants the interactivity on.
        override public function enableAnswers():void
        {
            circle.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
            circle.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
           

            // Make hand cursor appear when rolling over the circle.
            circle.buttonMode = true;
            // Make sure the circle is back in its starting position.
            circle.x = 0;
            circle.y = 0;
        }
       
        // When the user mouses down on the circle, it will start dragging.
        private function onMouseDown(e:MouseEvent):void
        {
            circle.startDrag();
        }
       
        // When the user lets go of the circle, it will stop dragging.
        private function onMouseUp(e:MouseEvent):void
        {
            circle.stopDrag();
        }
       
        // Captivate wants the interactivity off.
        override public function disableAnswers():void
        {
            circle.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
            circle.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp);

            // Stop the hand cursor appearing.
           circle.buttonMode = false;
        }
       
    }
   
}

Notice how:

  • enterRuntime() set up the circle but did not start the interactivity.
  • enableAnswers() added two event listeners.
  • disableAnswers() removed the same two event listeners. Cancelling out enableAnswers().
  • enableAnswers() also made sure the circle was at its starting point in the top left corner. This is because enableAnswers() is called when the user retakes the question. If those two lines of code weren’t there, then the circle would be lying where the audience put it the first time they answered the question. So enableAnswers() not only has to add the interactivity, it also has to reset the question.

Take a look at what the widget does below. Notice when you can drag the circle and when you can’t. You’ll see that between enableAnswers() and disableAnswers() all circumstances are taken care of.

Get Adobe Flash player

Download the widget here.

So the widget is enabling and disabling correctly. You may have noticed though, if you click the clear button the question doesn’t reset.

In the next post we’ll look at how to react when the audience clears the question.

Posted in Question Widgets, Widgets | Tagged , , , , | 1 Comment

Beyond the Basics Widget eSeminar

Just this Wednesday Allen Partridge and I did another widget eSeminar. In this one we built a widget that (with a bit of polishing) you would actually use in one of your courses - a mouse replacer widget!

To do that, we took the widget we built in the first eSeminar, and expanded it to access a slide object, and then make that slide object follow the mouse around. It was a lot of fun, and a very good way to get up to speed on building widgets (especially if you don’t have the patience to read blog posts (like me)).

Still not convinced? Then I encourage you to see how we manage to keep the seminar going when A: My phone starts beeping to inform me that I’m inches away from a dead battery, and B: Allen’s microphone breaks.

Before barreling off to see the most recent eSeminar, I suggest you have a squiz at the first one to get up to speed: http://bit.ly/aYnFah”

Once you’ve viewed that, by all means, venture into the wonderful world of widget <insert another word that starts with ‘w’ here>: http://bit.ly/dUp5zM

Posted in eSeminars, Widgets | Tagged , , , | 1 Comment

Question Widgets – Part 1

Question Widgets! Everybody loves Question Widgets! Over the next couple of weeks I’ll be writing a series of posts to help you get up and building Question Widgets.

In this post, we’ll look at the basics of how to create a Question Widget, and how to get it to answer correctly, incorrectly, or as incomplete. After you know how to do these things, everything else is making sure the widget works smoothly with the Captivate Movie.

Let’s meet the beast.

What is a Question Widget?

A Question Widget is a widget that Captivate treats as a Question Type. Captivate has native Question Types such as: True or False, Multiple Choice, Fill in the Blank, and many many more. However, if you wanted to create a Crossword Puzzle question or a Drag and Drop question, then Captivate doesn’t have those Question Types. Question Widgets mean you can create your own Crossword Puzzle widget and it would work just like it was a native Captivate Question Type (provided you put it together right).

The good news is, if you already know how to build a Static Widget, then you have most of the knowledge needed to build a Question Widget. All the properties and methods that you use in Static Widgets are used in Question Widgets as well. Question Widgets also have additional properties and methods that are used in unique Question Widgety situations. You can see a list of them in the documentation right now (Select Question Widget under All Classes).

An important difference between Question Widgets and other widget types is this: When you insert a Static or Interactive widget into a Captivate Movie, they are added to the current slide as an element. This means you can change their timing and depth on the timeline. However, when you insert a Question Widget, a whole new slide is created for it. In fact, the Question Widget is considered as being part of that slide. So there are no timing or depth controls for the widget.

How do I make a Question Widget?

It’s as easy as making a Static Widget. If you don’t know how to do that, then read this post on how to create a Static Widget in Flash. You can change the Static Widget in that post to a Question Widget by swaping the StaticWidget class with the QuestionWidget class so that the widget looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
package
{
    import widgetfactory.QuestionWidget;
   
    public class MyQuestionWidget extends QuestionWidget
    {
       
        public function MyQuestionWidget()
        {
           
        }
    }
}

How do I score a Question Widget?

In the end, a Question Widget comes down to one thing: How did the audience answer the question?

Did they get it right? Did they get it wrong? Or did they flat out not answer it?

When the question is submitted (either by the audience clicking the question’s submit button, or the widget submitting itself with the triggerSubmitProcess() method) Captivate will demand from the widget how the audience answered its question. The answer that the widget gives Captivate is handled by two properties: isCompleteAnswer, and isCorrectAnswer.

isCompleteAnswer tells Captivate IF the audience has answered the question. If isCompleteAnswer is set to false, then Captivate will display the ‘You must answer the question before continuing’ caption, and give the audience another chance to answer the question. If isCompleteAnswer is set to true, then Captivate will recoginise the question as having an answer, and move on to the next step to try and establish what that answer is.

isCorrectAnswer tells Captivate HOW the audience has answered the question. If isCorrectAnswer is set to true, then the question will be picked up as being correct. If it’s set to false, then the question will be marked as incorrect.

By default, isCompleteAnswer is set to true, and isCorrectAnswer is set to false.

Let’s take a look at a really basic Question Widget.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package
{
    import widgetfactory.QuestionWidget;
   
    public class MyQuestionWidget extends QuestionWidget
    {
       
        public function MyQuestionWidget()
        {
            isCompleteAnswer = true;
            isCorrectAnswer = true;
        }
    }
}

This question will always be picked up as being correct. Try playing around with the values of isCompleteAnswer and isCorrectAnswer in your own time. See how the different combinations change the result of the Question.

Of course, the answer of the above widget is set in stone as soon as it is published. In a working Question Widget, the answer would be uncertain until the audience clicks the submit button. So ideally what should happen is after the question is submitted, the widget checks itself to see if it was answered correct, incorrect, or incomplete. But how do we do that? How do we get that checking code to run when the audience submits the question?

Well, lucky for us, QuestionWidget has a unique Template Method called submit. Here’s how you override it.

1
2
3
4
override protected function submit():void
{

}

Any code that you put in the submit method will be executed between when the user clicks the submit button and Captivate requests the results of the isCompleteAnswer and isCorrectAnswer properties. That makes it the perfect time to evaluate the answer of your question.

So now that we know a little about Question Widgets, let’s build a working one. The Question Widget below creates two toggling buttons, one called ‘correctAnswerButton’ and one called ‘incorrectAnswerButton’. When the question is submitted, it will be received as correct if the correctAnswerButton is selected, or incorrect if the incorrectAnswerButton is selected. Alternatively, if neither button is selected, then the question will be marked incomplete.

Here’s the code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package
{
    import fl.controls.Button;
    import flash.events.MouseEvent;
    import widgetfactory.QuestionWidget;
   
    public class SimpleButtonQuestion extends QuestionWidget
    {
       
        private var correctAnswerButton:Button;
        private var incorrectAnswerButton:Button;
       
        override protected function enterRuntime():void
        {
            // When this button is selected, the question will be recieved as correct
            correctAnswerButton = new Button();
            addChild(correctAnswerButton);
           
            // Change the text on the button
            correctAnswerButton.label = "Answer Correct";
            // Make into a toggle button
            correctAnswerButton.toggle = true;
            // Put in the top left corner (where it is by default anyway)
            correctAnswerButton.x = 0;
            correctAnswerButton.y = 0;
            // Call the onButtonClick function when this button is clicked
            correctAnswerButton.addEventListener(MouseEvent.CLICK, onButtonClick);
           
            // When this button is selected, the question will be recieved as incorrect
            incorrectAnswerButton = new Button();
            addChild(incorrectAnswerButton);
           
            // Change the button's text
            incorrectAnswerButton.label = "Answer Incorrect";
            // Make it toggle
            incorrectAnswerButton.toggle = true;
            // Shift it to be to the right of the correctAnswerButton
            incorrectAnswerButton.x = correctAnswerButton.width + 10;
            incorrectAnswerButton.y = 0;
            // Call the onButtonClick function when this button is clicked also
            incorrectAnswerButton.addEventListener(MouseEvent.CLICK, onButtonClick);
           
            // This stops the buttons being created again when the movie is rewound
            dispatchRuntimeAfterRewind = false;
        }
       
        private function onButtonClick(e:MouseEvent):void
        {
            // If the user has just clicked on the correctAnswerButton...
            if (e.target == correctAnswerButton) {
                // ...Then we want to toggle the incorrectAnswerButton off
                incorrectAnswerButton.selected = false;
               
            // If the user hasn't click the correctAnswerButton...
            // ...then they must have clicked the incorrectAnswerButton
            } else {
                // In which case we want to toggle the correctAnswerButton off.
                correctAnswerButton.selected = false;
            }
        }
       
        // The user has just click the submit button,
        // Captivate is just about to check the isCompleteAnswer
        // and isCorrectAnswer properties
        override protected function submit():void
        {
            // Unless otherwise stated, the question is complete.
            isCompleteAnswer = true;
           
            // If the correctAnswerButton is selected...
            if (correctAnswerButton.selected == true) {
                // ...then the question is answered correctly
                isCorrectAnswer = true;
               
            // if the incorrectAnswerButton is selected...
            } else if (incorrectAnswerButton.selected == true) {
                // ...then the question is answered incorrectly
                isCorrectAnswer = false;
               
            // If no buttons are selected...
            } else {
                // ...then the question hasn't been answered at all
                isCompleteAnswer = false;
            }
        }
    }
}

Get Adobe Flash player

Click here to download the widget!

This isn’t a flawless widget. Notice how after you’ve answered it, you can still interact with the buttons (although that does not change the result of the question). In the next post of this series, we’ll look at how to enable and disable the questions in synch with the status of the Captivate Movie.

Posted in Basics, Question Widgets, Widgets | Tagged , , , | 6 Comments