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

Widget Properties – How they’re used

Last week we looked at how Widget Properties work. This week we’ll see how to use them, and I’ll show you what Widget Properties process I use in my widgets.

How to Write Widget Properties

As we learnt last week, Widget Properties can only be created in the Properties Dialog mode, and the saveProperties() Template Method is usually the best place to do this as it is called just before Captivate requests the Widget Properties from the widget.

To create a new Widget Property, you must access the Widget Properties object (through the ‘properties’ property) and create a new property on it. I know, I know, there was an awful lot of ‘properties’ in the last sentence. It sounds complicated, and to a certain extent the concept can be, but the code is dead simple.

I’ll refer to Widget Properties as WP from now on, hopefully that will cut down the propertying

The property on the WP object will hold the data you save to it. Though initially that property will not exist on the WP object, once you create it, it will still be there when you check the WPs again.

Enough talk. Time for examples! Say the user had writen something in a text field in your interface, and you wanted to save this data to a WP called asdfasdf. Here is how you’d create that property.

1
properties.asdfasdf = textField.text;

I can assure you that asdfasdf is not an existing property on the WP object. In fact, I’d be pretty surprised if it’s an existing property on any object. However, just by assigning it a value, you create it.

Some of you may be raising your hands and asking about the good old 1119 error. You know the one you get when you misspell a property and the Flash compiler crashes the party saying:

Access of possibly undefined property asdfasdf through a reference with static type ClassName.

Well it’s true, this code usually would lead to that error. However, the WP object is special. It’s not one of those ‘static types’ it’s a ‘dynamic type’. Dynamic objects allow you to create properties on them that don’t naturally exist. Some dynamic objects you would have used before would be Arrays, Objects, and MovieClips.

There is no limit to how many properties you can create on a dynamic object. So you can create as many WPs as you like.

1
2
3
4
5
6
7
8
override protected function saveProperties():void
{
    properties.textFieldText = textField.text;
    properties.aNumberProperty = 72;
    properties.aStringProperty = "Greetings World";
    properties.aBooleanProperty = true;
    properties.anArrayProperty = [1,2,3];
}

WPs can only hold certain data types. Make sure you only save those data types to the object. If you tried saving a reference to the textField as a Widget Property instead of the textField’s text, that’s not going to work. Your whole WPs will probably fail as a result.

1
2
3
4
override protected function saveProperties():void
{
    properties.myDynamicTextHoldingProperty = textField; // This won't end well
}

These are the types of data that you can save as Widget Properties.

  • Objects,
  • Strings,
  • Numbers,
  • Booleans (true and false),
  • Arrays, and
  • Null.

Also, the items inside the Arrays and Objects must not fall outside the above list.

How do I Access Widget Properties?

Once a Widget Property has been created, it can be accessed in any of the other modes that receive Widget Properties (see last week’s post)

So say we were in the enterPropertiesDialog() Template Method and wanted to set our textField back to what it was in the previous Properties Dialog session, then you’d set textField’s text equal to the WP you stored its data in, like so:

1
2
3
4
override protected function enterPropertiesDialog():void
{
    textField.text = properties.textFieldText;
}

The Widget King Widget Properties Method

The problem with WPs is that they don’t come with defaults. When the Captivate Author opens up the Widget Properties interface for the first time, the WP object will be blank. In this case you need defaults set up. I like to have a variable for each of my WPs that holds its default value, then set my interface element equal to that variable. So for example, if we were using the textField example above, then the code would look like this.

1
2
3
4
5
6
private var textFieldDefaultText:String = "Default Text";

override protected function enterPropertiesDialog():void
{
    textField.text = textFieldDefaultText;
}

When the Widget’s Properties Dialog opens up, the textField will be given the default text. However, before I set up the textField, I’ll insert a call to a function called checkProperties(), then go down and define that function.

1
2
3
4
5
6
7
8
9
10
11
12
private var textFieldDefaultText:String = "Default Text";

override protected function enterPropertiesDialog():void
{
    checkProperties();
    textField.text = textFieldDefaultText;
}

private function checkProperties():void
{
   
}

There I will put in an if statement that checks if the setBefore WP equals true. If so, then I’ll set the textFieldDefaultText variable equal to the textField’s Widget Property.

1
2
3
4
5
6
7
8
private function checkProperties():void
{
    if (properties.setBefore == true) {

        textFieldDefaultText = properties.textFieldText;

    }
}

Then in my saveProperties() Template Method, I’ll extract the data from my interface and save it to WPs. Then at the end I’ll set the setBefore WP to true. So all up the code would look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private var textFieldDefaultText:String = "Default Text";

override protected function enterPropertiesDialog():void
{
    checkProperties();
    textField.text = textFieldDefaultText;
}

private function checkProperties():void
{
    if (properties.setBefore == true) {

        textFieldDefaultText = properties.textFieldText;

    }
}

override protected function saveProperties():void
{
    properties.textFieldText = textField.text;
    properties.setBefore = true;
}

Let’s run through the process and see how it works. The first time around, there is no setBefore property, so the if statement comes back false and the textFieldDefaultText property stays at its default value. When the properties are saved, the setBefore property is created with a true value. That means for every time afterwards the if statement returns true and the default variable’s value is swapped out with the saved property’s value. Then when that variable is applied to the interface, it’s the saved value, not the default value, which is assigned.

To add another property, create another default variable, set its associated interface element equal to that variable in enterPropertiesDialog(), associate the variable with its WP in the checkProperties() method, and save the interface element’s value to the WP in the saveProperties() Template Method.

This gives you a water tight properties system. I keep the code in the checkProperties() method separate so I can use it in other Widget Modes.

There are many different ways to handle Widget Properties. If you have your own particular method, by all means feel free to share it with us.

Posted in Basics, Theory, Tips, Widget Properties, Widgets | Tagged , , , , | 5 Comments

Sign up for the Widget eSeminars!

We had a lot of fun delivering the Widget Basics eSeminar a while ago that we decided to come back and do some more!

First up, on November 17th Rod and Allen will be presenting an eSeminar on how to use the Drag and Drop Lite Question Widget. They’ll be demonstrating how to make a flow chart drag and drop question. You can see some examples of a few of the questions you can make with the Drag and Drop widget here.

[Edit: Click here to view the eSeminar]

Second is the follow up to the Widget basics eSeminar. We’ll be building on top of the widget from that seminar and making it into a widget that swaps the mouse cursor out with an image defined by the Captivate Author. Should be very cool, and useful to your courses. Allen and I will be presenting this eSeminar on the 1st of December.

[Edit: Click here to view the eSeminar]

At the end of each seminar we’ll be taking questions. So if you have any issues or requests be sure to attend and let us know.

Both seminars will be recorded and made available afterwards.

See you there!

Posted in Basics, eSeminars, Widgets | Tagged , , , , | 4 Comments

Widget Properties – How they work

Widget Properties! They are a wonderful thing. You could argue they are what separates Widgets from regular Animations. However, their very nature is mysterious. Where do they come from? Where do they go? When do they have lunch?

I’m not a developer of Captivate (sigh) so I can’t tell you with any absoluteness what happens to Widget Properties when they get sucked into Captivate. I can, however, tell you their life cycle as far as widgets go.

First of all though, let’s look at some basic questions.

Why do we need Widget Properties?

There are many different places that the widget swf finds itself in inside of Captivate (called Widget Modes). There is Runtime Mode, when the widget is in the published Captivate Movie and needs to be a fully functioning tool. There is Stage Mode, when the Captivate Author is editing the slide that the widget is on, and the widget needs to display a preview of what it will look like at Runtime. Then if your widget any settings, it needs to create an interface to display in the Properties Dialog’s Mode as well. A rule that Captivate follows with regards to widgets and their modes is that they never touch. You will never have the widget swf load in the Properties Dialog, then jump to the Stage preview, and then find itself being published into Runtime, or any variant thereof. What happens is the swf will load in the Properties Dialog, then reload on the Stage, and reload again at Runtime. Think of it like if you were filling out a form on the web. If you refresh the page, the form will be loaded again, and will wiped clean of anything we wrote in it. Same with widgets. When the widget swf moves from one mode to another, it will be reloaded and have no memory of what happened to it in another mode.

This leads to a major issue. How do you save any information gathered in this widget mode that might be of use in another? For example: Say we had a widget that drew a square on stage, and in the widget properties you could customise the colour of that square. If the user specifies that he wants it to be red, how do we save that information so that the other modes will know to draw a red square?

Answer: Widget Properties

What are Widget Properties?

Widget Properties are an object that Captivate holds for our widget. The widget can write information to this object (such as what colour the square should be) and Captivate will save it. When the widget swf is reloaded into a new location, Captivate will give the widget the Properties object so that it can read any information that it has set previously.

How do Widget Properties Work?

Widget Properties go through quite a life cycle. This gives me an excellent opportunity to pull out a FLOW CHART!

Lets start at the beginning.

1. Where do Widget Properties come from?

Widget Properties start their life in the Properties Dialog. The place where you should stick the settings interface for the widget.

Ah yes, but why do I keep saying that you should stick your widget’s interface here?

When the Captivate Author clicks OK the Properties Dialog will close down. Before closing down however, Captivate will request the widget give him its Widget Properties object. Sort of like if you were on a sinking ship and then these pirates came along and told you to throw your valuables at them before going down.

In WidgetFactory terms, this handing over of Widget Properties happens just after the saveProperties() Template Method is called. So saveProperties() is an excellent place to extract the information in the widget’s interface, and place them into Widget Properties, before the whole widget gets closed down.

FlowChart1_web

(Yes, I built the flow chart in Captivate. I knew that software must have been good for something :P )

This, ladies and gentlemen, is the birth place of all Widget Properties. This is the only place where Captivate receives properties from the widget. After that it’s all give give give. You can’t change the Properties when the widget is on Stage or at Runtime. So this is the ONLY place you can allow the user the change the settings of the widget.

2. Captivate sends the Widget Properties out.

Now that we’ve saved some Widget Properties in Captivate, Captivate will send them back to the widget at the start of a new Stage Mode, Runtime Mode,  or the Properties Dialog Preview in Captivate 4 (See the enterPropertiesDialogPreview() Template Method under the StaticWidget class in the documentation).

The Widget’s Properties are also sent back to the widget when it enters the Properties Dialog. Why is this? Take an example of what would happen if they weren’t given to the Properties Dialog. Say we were building that widget I mentioned above that drew a square and allowed the Captivate Author to define what colour it would be. Let’s say that the square had a default colour of blue. However, the Captivate Author enters the Properties Dialog and changes the Widget Property that controls the colour of the square from blue to red. So they close down the Properties Dialog and the Stage preview updates to show a red square. But then if the Captivate Author opens up the Properties Dialog again to change more settings, the widget will show the default settings again. In other words, the Captivate Author will open up the dialog again, see the square’s colour has been set back to the default blue, judge our widget as being buggy, and it out.

So Widget Properties are given to the Properties Dialog again, so that the widget can update its interface to hold the same values as when the Captivate Author last left it.

Let’s make what’s sending and what’s recieving clear:

  • Stage mode RECIEVES Widget Properties at its start.
  • Properties Dialog Preview RECIEVES Widget Properties at its start.
  • Properties Dialog RECIEVES Widget Properties at its start.
  • Properties Dialog SENDS Widget Properties to Captivate at its end (assuming the Captivate Author clicked OK and not Cancel)

FlowChart2_web

Where’s Runtime? Well technically that should be on the list so I’ll add it.

  • Runtime mode RECIEVES Widget Properties at its start.

However, it’s not quite as simple as that.

3. Covert Runtime Properties

When the widget is inside Captivate, Captivate sends and recieves the Widget Properties in an Object form. For some reason I do not fully understand (again, not a Captivate Developer) at Runtime the Widget Properties object is transcribed into XML, and that’s how the Widget receives them.

SPLOT!

It requires quite a bit of time to learn the structure of the XML and how to weed your way through it to get to the properties you want. A lot of trial and erroring there. Not fun.

You can see this XML for your own if you like. It’s available under the widgetPropertiesRawData property as a String. But you need not worry about searching through an XML jungle no more.

Thankfully, WidgetFactory runs through that XML and formats it back into the classic object we are used to dealing with inside of Captivate. This saves a LOT of time and headache I can tell you. It’s probably the main reason why WidgetFactory was built in the first place.

So let’s look at the completed flow chart why don’t we?

FlowChart3_web

So there you go. Everything I know about how Widget Properties work. Not a whole lot about how to use them, but next week I will be sharing the Widget Properties workflow I use in my Widgets.

See you then!

Posted in Basics, Theory, Widget Properties | Tagged , , , , , , | 2 Comments

Building Widgets in Flash

In a previous post I showed you how to set up WidgetFactory in Flash. But now we will see how to create a widget in Flash.

In this post we’ll make a simple Static Widget. When I mean simple, I mean just that. Captivate will recognise it as a widget and that’s about all.

Note: The information in this post is taken heavily from the Widget Basics eSeminar. For the sake of ease, it has been formatted into a blog post that you can refer to on the fly. This process will also work with Flash versions CS3 and CS4, while the eSeminar was done with some new CS5 features.

Step 1: Open Flash

Step 2: Create an ActionScript 3.0 Flash Document: A widget is essentially an swf. So we need something to create that swf. That’s what a Flash Document does.

You can create an ActionScript 3.0 Flash Document by choosing that option from the splash screen…

SplashScreen

…or by going to File > New, selecting the ActionScript 3.0 Flash Document option, and clicking OK.

FileNew

You now have a blank Flash Document. Save it by going to File > Save and call it something like: MyFirstWidget.

Step 3: Create an ActionScript file: If you’ve used the Widget Template before, then you know that you can write code in the Flash Document. However, to use WidgetFactory, you need to write your code in ‘classes’ (more on that in a bit), and classes can only be written in ActionScript Files.

To create a new ActionScript File, go to File > New, choose ActionScript File and click OK.

Flash will open up a blank ActionScript file. Save this file to the same location as the Flash Document, and give it a name of MyFirstWidget.

Step 4: Create a Class: Like I said, WidgetFactory widgets need to be written as classes. But what are those? Without knowing it, you’ve probably already come into contact with classes. MovieClip is a class, Buttons are a class, Bitmaps are a class. Just about everything seen in Flash are classes.

So a class is an object that serves a particular purpose. The class we’re going to write serves the purpose of being a basic widget. So with that in mind, let’s write that class.

The first thing you need to write is a package declaration.

1
2
3
package {

}

This lets flash know where the ActionScript file is located in relation to the Flash Document. Seeing as we’ve saved both files to the same folder, there is no need to write anything else in the package statement.

Next we write the following between the package braces.

1
2
3
4
5
6
7
package {

   public class MyFirstWidget {

   }

}

The package statement defined where this file is, this line defines what the file is – a class by the name of MyFirstWidget. As a rule, your class must always be the same name as the ActionScript File. The ‘public’ at the start makes this class available anywhere.

Currently, this class doesn’t do anything. In order to change this mild mannered ActionScript class into a widget, we need to make it extend a class that already works as a widget, such as the StaticWidget class in the WidgetFactory API. To do this, write the following.

1
2
3
4
5
6
7
package {

   public class MyFirstWidget extends StaticWidget {

   }

}

We add two words to the class declaration: ‘extends StaticWidget’. Now that the MyFirstWidget class extends the StaticWidget class, it has all the abilities of the StaticWidget class. This means that Captivate will pick up our swf as being a widget.

However, the MyFirstWidget does not have access to the StaticWidget class right off the bat. It first needs to import it. Sort of like how if we wanted to read a book, we’d need to check it out of the library first. To import the StaticWidget class, write ‘import widgetfactory.StaticWidget;’ after the package declaration.

1
2
3
4
5
6
7
8
9
package {

import widgetfactory.StaticWidget;

   public class MyFirstWidget extends StaticWidget {

   }

}

At the moment, this class is a clone of the StaticWidget class, and that’s a bit dull. To start adding our own characteristics to the widget, we need it to run some code. In classes you write your code inside functions. A function is a block of code that will execute when we tell it to. If we write a function with the same name as the class, then that function will be called right when the class is created. This is called a constructor function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package {

import widgetfactory.StaticWidget;

   public class MyFirstWidget extends StaticWidget {

      public function MyFirstWidget ():void {

         trace("Hello World!");

      }

   }

}

The one line of code in the constructor function sends the message “Hello World!” to the output panel in Flash. Well it will, once we’ve done the next step.

Step 5: Link the Flash Document to the ActionScript file: To make the MyFirstWidget class the controller of the MyFirstWidget Flash Document go to the Flash Document and open the Properties panel. If nothing in the document is selected you should see the following.

PropertiesPanel

Under the publish accordion, there is a field called Class. This is where you put the name of the class that will control the document (known as the Document Class). So to link this Flash Document to the MyFirstWidget class, just type MyFirstWidget into the Class field.

 ClassWritten

FYI: It is not essential that the Flash Document and the ActionScript file have the same name.

Step 6: Publish the Flash Document: Now that everything is wired up, we’re ready to publish. To do this, go to Control > Test Movie (then Test in CS5), or press Ctrl + Enter.

Flash Player will pop up showing us our widget, and in Flash output window will pop up with the ”Hello World!” message we told it to show.

Step 7: Import the Widget into Captivate: Now switch over to a Captivate Project. Import the widget by going to Insert > Widget, then navigate down to where you saved the Flash and ActionScript files. There should be an swf file by the name of MyFirstWidget there as well. This is the file we just published, and it is the file we want to import. Select it and hit Open.

If everything is done correctly, Captivate will recognise the swf as a widget and display its Properties Dialog.

Step 6: Continue Building: Congratulations! You have now created your first widget. What it becomes, what purpose it serves, is up to you now.

You can continue writing code in the ActionScript file, and adding visual elements to the Flash Document, to build onto your widget. Captivate is your oyster.

Creating Other Widget Types

Now the above is fine as long as you wanted to create a Static Widget. But by now you’re probably saying: “Hey! What if I want to make a Question Widget, or an Interactive Widget. Whatever those are.”

Well luckily it’s not that hard to switch widget types. All you need to do to change the Static Widget above into an Interactive Widget is replace where ever it says StaticWidget with InteractiveWidget.

Update: For more information on how to write Interactive Widgets see this post.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package {

import widgetfactory.InteractiveWidget;

   public class MyFirstWidget extends InteractiveWidget {

      public function MyFirstWidget ():void {

         trace("Hello World!");

      }

   }

}

The same goes for QuestionWidget.

Update: For more information on how to write a Question Widget see this post.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package {

import widgetfactory.QuestionWidget;

   public class MyFirstWidget extends QuestionWidget {

      public function MyFirstWidget ():void {

         trace("Hello World!");

      }

   }

}

What’s next?

From here, I would recommend learning how to use Template Methods. Then after that, Widget Properties, which I may or may not be blogging about next week. Smile

Posted in Basics, Static Widgets, Widgets | Tagged , , , , , | 16 Comments

Accessing Captivate User Variables – Part 2

In last week’s post we saw how to access a Captivate Variable using cpVariables. This week, we’ll see how we can allow the Captivate Author to choose which variable the widget will access. To do this, we must venture into the mysterious world of SQUARE BRACES [][][][][][]!!!!!

Now you’ve probably used [] before with Arrays. However, the [] can also be used to access an objects properties or methods. Much like what ’.’ is used for.

For example, if we wanted to change a Display Object’s x location. We would type out the name of that object, then use ‘.’ to access the object’s x property.

1
displayObject.x = 100;

This is known as Dot Syntax! Nice and simple. However, we could also access the Display Object’s x property using [] instead. Here’s how:

1
displayObject["x"] = 100;

This will result in the exact same action as the first example. So the formula for dot syntax was: Object DOT Property. For square brace syntax the formula is: Object BRACE String-Of-Property BRACE. Writing a String of the property inbetween the [] is extremely important. If you wrote:

1
displayObject[x] = 100; // This won't work

That code is not going to work out for you. So remember your quotations!

Let’s try this with a few other properties.

1
2
3
4
5
6
displayObject["x"] = 100;
displayObject["y"] = 100;
displayObject["alpha"] = 0.5;
displayObject["graphics"].beginFill(0xFF0000);
displayObject["graphics"]["drawCircle"](10,10,10);
displayObject["startDrag"]();

Take a look at the third line. You can use dot syntax after a [] to access a sub-property of the []‘s property. Or you could do the same by piling [] on top of [] like with the fourth line. You can also use [] to call methods, as shown in the fourth and fifth lines. Just remember, when calling a method with [], you still need to put the () afterwards.

This is all nice to know, but [] requires a lot more typing than dot syntax does. And to be honest, it doesn’t give you anything that dots don’t. Where []s become powerful, is when they’re used in conjunction with variables. Strings can be saved to variables, which means you can do this:

1
2
var propertyName:String = "x";
displayObject[propertyName] = 100;

This changes the Display Object’s x location to 100, just like the first example. So which property this code will edit, depends on the value of the propertyName variable. This means if we made a change to the propertyName variable, then we’d set a different property to 100. For example:

1
2
3
4
5
6
7
var propertyName:String = "x";

if (displayObject.x &gt;= 100) {
    propertyName = "y";
}

displayObject[propertyName] = 100;

So with this code, if the display object’s x position is under 100, then we’ll make it jump to 100. However, if it is over 100, then we’ll make it jump to 100 on the y axis instead.

Here’s the point: [] = dynamic coding.

Let’s try a demo! Below you can edit the red circle by typing in the name of the property you want to change, then the value you want to assign it, and clicking the execute button.

Get Adobe Flash player

So the text field where you type the property name is called property_txt, and the text field where you type the value is called value_txt. See if you can work out how this code works:

1
circle[property_txt.text] = value_txt.text;

We plug the text in the property_txt input field in between the braces to access the property by the name of what’s written there, and then set it equal to the text in the value_txt text field.

There you go! How to dynamically access object properties!

What does this have to do with Captivate Variables?

If you remember from last time, you access all Captivate Variables (System and User) from the cpVariables object. The []s work just as well with cpVariables as they do with any other object. Remember last time we accessed the cpInfoCurrentSlide Captivate System Variable using dot syntax?

1
cpVariables.cpInfoCurrentSlide;

How do you think we’d access it with []?

1
cpVariables["cpInfoCurrentSlide"];

So once again, that string could be saved to a variable and then stuck in between the [].

1
2
var captivateVariable:String = "cpInfoCurrentSlide";
cpVariables[captivateVariable];

OR that string could be saved in a Widget Property and then stuck between the [].

1
cpVariables[properties.captivateVariable];

You may already see where this is going. You can set up your Properties Dialog Interface with one of those Input Text Fields shown in the demo above, save its value to a Widget Property, and then use that Widget Property to access the variable at runtime

DING!

The Captivate Author can now customise the widget to access the variables THEY created.

And now would be an excellent time for me to plug the eSeminar I did a couple months back that shows you how to create an interface that saves text from a Text Input Field to a Widget Property!

Okay, so how about we put one of these interfaces into the Variable Formatter widget we made last week.

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
package  {
   
    import fl.controls.TextInput;
    import flash.text.TextField;
    import flash.text.TextFieldAutoSize;
    import widgetfactory.StaticWidget
   
    public class FormatVariableCase extends StaticWidget {
       
        // The field where the Captivate Author will type the name of their User Variable.
        private var variableName_txt:TextInput;
       
        override protected function enterPropertiesDialog():void
        {
            // The message we will display to the Captivate Author
            var title_txt:TextField = new TextField();
            addChild(title_txt);
            title_txt.text = "Type the name of the User Variable you'd like formatted";
            title_txt.autoSize = TextFieldAutoSize.CENTER;
            title_txt.y = 10;
            // Move it to the center of the stage.
            title_txt.x = stage.stageWidth / 2 - title_txt.width / 2;
           
            // The field where the Captivate Author will type the name of their User Variable
            variableName_txt = new TextInput();
            addChild(variableName_txt);
            // Make the input field appear 10 pixels below the text.
            variableName_txt.y = title_txt.y + title_txt.height + 10;
            // Make the input field as wide as the text shown above it.
            variableName_txt.width = title_txt.width;
            // Move the input field to the center of the stage.
            variableName_txt.x = stage.stageWidth / 2 - variableName_txt.width / 2;
           
            // If the Captivat Author has already defined a variable in a previous session...
            if (properties.variableName != undefined) {
                // Make that the default text of the input field.
                variableName_txt.text = properties.variableName;
            }
        }
       
        override protected function saveProperties():void
        {
            // Save the variable name to properties.
            properties.variableName = variableName_txt.text;
        }
       
        // All variable editing happens at runtime.
        // As soon as this widget is set to appear on stage,
        // it will make the edits to the User Variable.
        override protected function enterRuntime():void
        {
            // Read the variable that the Captivate Author designated
            var stringToEdit:String = cpVariables[properties.variableName];
           
            // Lets say it has a value of: jaSOn
           
            // Makes the whole string lower case.
            // Example would now be: jason
            stringToEdit = stringToEdit.toLowerCase();
           
            // Extracts the first letter and saves it
            // In our example that would be: j
            var firstLetter:String = stringToEdit.charAt(0);
            // Make first letter upper case.
            // Example: J
            firstLetter = firstLetter.toUpperCase();
           
            // Cuts out the first letter from the string.
            // Example: ason
            stringToEdit = stringToEdit.slice(1, stringToEdit.length);
            // Add the upper cased first letter to the rest of the rest of the word
            // Example: Jason
            stringToEdit = firstLetter + stringToEdit;
           
            // And we're done! Assign our working string back to the Captivate Author's designated Variable.
            cpVariables[properties.variableName] = stringToEdit;
        }
    }
}

Download the widget and try for yourself!

Would you believe I still have more to say on the subject of Captivate Variables? Yes, prepare yourself for an impending Captivate Variables Part 3! Which may or may not be coming next week. Sort of depends on what I feel about writing about.

Posted in Captivate Variables, Widgets | Tagged , , , , , , | 4 Comments

Accessing Captivate User Variables – Part 1

By far the most common question I get asked is: “How can I get my Widget to access Captivate User Variables?”

A decent question, and in truth, not all that hard to answer. But I LOOOOOOOVE to reply to people saying: “You know what, I already wrote a blog post about that which goes into a lot more detail then whatever answer I can scrounge up in the next five minutes.”

Send button – Mash.

I’m betting about half of you would have just read an e-mail along those lines before starting on this post. Good times. Good times.

I didn’t get this e-mail, and what are ‘Captivate User Variables’?

Captivate User Variables are part of Advanced Actions. Advanced Actions is(/are?) Captivate’s native scripting. At time of writing, Advanced Actions are relatively limited. They’re mostly use to hide or show objects, or some basic ‘if else’ stuff, or to change the values of variables. Variables in Advanced Actions are divided into two groups: System Variables, and User Variables.

System Variables are made by Captivate. Most of them are for information purposes only, like cpInfoCourseName tells you what the name of the course is, and cpInfoCurrentSlide tells you what number slide is currently being viewed. Then there are others that allow you to control the movie, like how assigning a number to cpCmndGotoSlide will make the movie jump to that number slide (minus 1, as it’s zero based), and by giving cpCmndCC a 1 or a 0, you can open or close the Close Captioning box. But as I said. these variables are created by Captivate. So you can’t add any more System Variables to the list.

User Variables however, you can create as many as you want to. You can do this in Captivate by going to Project > Variables. These variables aren’t tied into functions of the Captivate Movie. Usually they’re used to hold information about the person doing the course. The typical example used to demonstrate User Variables is where the user will write their name in a text entry field somewhere at the start of the course, and for the rest of the course their name jumps up in all sorts of places.

Variables

Fine and dandy. However, there are some situations that you run into where Advanced Actions just don’t cut it. For example: What if you recorded the name of the person doing the course, and instead of them being civil and writing: Jason, they write : jAsOn? How can we make sure the capitalisation is correct? Well in ActionScript that’s easy, you use the toLowerCase() method to make the whole text lower case, then bump up the case of the first letter. Advanced Actions however don’t have this function. So if you wanted to do this, you’d need a widget to access that Captivate Variable and make the edit for you.

Okay, so how do we access a User Variable?

Both System and User Variables can be accessed in the same place, cpVariables. So, to access that cpInfoCurrentSlide System Information Variable, you’d write the following:

1
cpVariables.cpInfoCurrentSlide;

And remember how assigning a number to cpCmndGotoSlide would make the movie jump to said slide? Well you can do that too.

1
cpVariables.cpCmndGotoSlide  = 1;

That will make the movie jump to the second slide. Zero base, got to love it.

How about User Variables? Well, more of the same. cpVariables dot variable name. Say we wanted to access that User Variable (wonderfulUserVariable) in the image above. Here’s how we’d do that

1
cpVariables.wonderfulUserVariable;

Can you change the value of User Variables?

Sure! Just assign it a value.

1
cpVariables.wonderfulUserVariable = "Something else!"

Check out the movie below. First slide shows you the value of the User Variable (via a Caption). Next slide there’s a widget where you can give the variable a new name, then the third slide has a Caption that shows you the variable again. See any difference?

Get Adobe Flash player

So it works. But that’s not doing anything that a normal Text Entry Box in Captivate couldn’t do. What about the example I mentioned earlier about correcting the capitalisation of the person’s name? Well assuming that the User Variable: ‘firstName’ is holding the name, then this widget will work for you.

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
package  {
    import widgetfactory.StaticWidget
    public class FormatVariableCase extends StaticWidget {
        // All variable editing happens at runtime.
        // As soon as this widget is set to appear on stage,
        // it will make the edits to the User Variable.
        override protected function enterRuntime():void
        {
            // Get what's currently in the firstName User Variable.
            // Lets say it has a value of: jaSOn
            var stringToEdit:String = cpVariables.firstName;
            // Makes the whole string lower case.
            // Example would now be: jason
            stringToEdit = stringToEdit.toLowerCase();
            // Extracts the first letter and saves it
            // In our example that would be: j
            var firstLetter:String = stringToEdit.charAt(0);
            // Make first letter upper case.
            // Example: J
            firstLetter = firstLetter.toUpperCase();
            // Cuts out the first letter from the string.
            // Example: ason
            stringToEdit = stringToEdit.slice(1, stringToEdit.length);
            // Add the upper cased first letter to the rest of the rest of the word
            // Example: Jason
            stringToEdit = firstLetter + stringToEdit;
            // And we're done! Assign our working string back to the User Variable.
            cpVariables.firstName = stringToEdit;
        }
    }
}

Now see it in action. Do whatever crazy capitalization you like to your name and see how it comes out in the caption on the next slide.

Get Adobe Flash player

Download the widget here.

Pretty cool huh? But also limiting, because the name of the User Variable is hard coded into the widget. What if we wanted to change the capitalisation of the lastName variable as well? Wouldn’t it be better if the user could choose the variable they want edited? Tune in next week to find out how to use Widget Properties in conjunction with cpVariable to do that!

[Edit: Click here to read part 2]

Posted in Basics, Captivate Variables, Theory, Widgets | Tagged , | 5 Comments

Slip Sliding Away

According to the ‘What should I write about next’ poll, it seems you’d like to learn some ‘Advanced’ widgetry. Well in this post we will. But it’s not really ‘Advanced advanced’. That is to say, not overly complicated, or hard to get your head around. Oh no. As advanced goes this is pretty easy. It’s the many ways you can apply this feature that makes it advanced.

I’ve probably toted just about every feature I’ve written about as being ‘the most powerful widget feature ever!’ (I mean would you really read an article that started: “This feature is kinda soggy and flat, but here’s how it works anyway”) This one however, is the real deal. Seriously. In the future I think at least half of the widgets worth their salt will use this feature in some form or another.

What is it?

1
getSlideObjectByName();

Oh be afraid. Be very afraid.

What does this do?

It allows you to get a reference to an object on the Captivate Slide (like a caption, highlight box, image, shape, click area, so on). What good is that? Well, once you have that ‘reference’ you can do pretty much anything you like to that object. Move it, scale it, rotate it, tween it, filter it, (cough) drag it. You could even make a full physics simulation with captions and highlight boxes bouncing around the stage.

“Sounds like a blast” You say. Well, maybe not out loud, because people will look at you weird for talking to your computer.

“BUT” You add, “What do I have to do to get this ‘reference’?”

Easy. You just need to know that object’s name.

“How would I know that objects name? We’ve never been introduced.”

Quite easy my friend. Inside Captivate select the object, and go to the Properties panel. You will see its name listed under “Item Name”.

ItemName2BINGO! If we want to access this text caption at runtime, all we need to do is pass the string “manicTextCaption” into the getSlideObjectByName() method, and it will return us the reference to that caption.

1
getSlideObjectByName("manicTextCaption");

To make things easier for us, we’ll store that reference in a variable for safe keeping. A Sprite or MovieClip variable should see you right.

1
var soonToBeRotatedCaption:Sprite = getSlideObjectByName("manicTextCaption");

So, all together, if we wanted to make a widget that rotated the manicTextCaption –90 degrees, here’s what it would look like.

1
2
3
4
5
6
7
8
9
10
11
12
13
package  {

    import flash.display.Sprite;
    import widgetfactory.StaticWidget;

    public class ManicTextFieldRotator extends StaticWidget {
        override protected function enterRuntime():void
        {
            var soonToBeRotatedCaption:Sprite = getSlideObjectByName("manicTextCaption");
            soonToBeRotatedCaption.rotation = -90;
        }
    }
}

CaptionRotated 

(Note: In Captivate, the 0,0 point is in the top left of every object, not the centre. So when you rotate an object, it will pivot around the object’s top left corner, not its centre.)

Now if you’re selling your widget to a wide audience then you’re not going to know what names they give their slide objects. But as you saw, the name is just a string value. So what you can do is provide an input text field in the Properties Dialog interface where the Captivate Author can type the name they gave the object. The widget saves that name to properties, and then plugs that property into the getSlideObjectByName() method at runtime.

And the best part is that I’ve already done a tutorial on creating that type of interface in the eSeminar I did with Allen Partridge. So I don’t have to show you how to do that here!

Why is this so great?

Because I hate writing the same thing over and over again.

Oh, you were talking about the getSlideObject thing-o weren’t you?

Because this means the Properties Dialog is not the only place where you can customise the widget. Captivate becomes the widget’s design environment. Do you remember when widgets first came out Adobe said: “If you want to customise the appearance of the widget. Open up its fla file and start breaking stuff!” With this feature employed, if the Captivate Author wanted to provide a custom background to their Course Complete certificate widget, they’d just drag said image onto the same slide as the widget and type its name into the Properties Dialog interface! The Captivate Author could provide images the up, over, and down state of a button widget (pretty savvy he would be too). Why not go all out!? A fully customisable playbar widget! AND THEN WE SHALL TAKE OVER THE WORLD!!!!

MUAHAHAHAHAHAAAHAHAH!

See. Powerful.

Currently, getSlideObjectByName() will only return a reference to an object that is on the same slide as the widget. In the next version of WidgetFactory (5.1), getSlideObjectByName() will have a second parameter that will take the number, or label, of the slide you’re trying to find the object on. This will be an optional parameter, and will default to the widget’s slide.

Before I leave off, I’d like to give you another cool example widget. 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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package  {

    import flash.display.Sprite;
    import flash.text.TextField;
    import flash.text.TextFieldAutoSize;
    import flash.events.MouseEvent;
    import flash.filters.GlowFilter;
    import fl.controls.TextInput;
    import fl.controls.Button;
    import widgetfactory.StaticWidget;

    public class RuntimeDragger extends StaticWidget {

        // The space between the edges
        private const PADDING:int = 5;

        // UI
        private var message:TextField;
        private var textInput:TextInput;

        // The object on slide that we are currently interacting with.
        private var activeSlideObject:Sprite;

        // The starting location of the active object
        private var orgX:Number;
        private var orgY:Number;

        override protected function enterRuntime():void
        {
            ///// Create feed back text field
            message = new TextField();
            addChild(message);
            message.autoSize = TextFieldAutoSize.CENTER;
            message.text = "Write the name of the object you want to access.";
            message.x = PADDING;
            message.y = PADDING;

            ///// Create field to write slide object name
            textInput = new TextInput();
            addChild(textInput);
            textInput.width = message.width;
            textInput.x = PADDING;
            // Position under message text field
            textInput.y = message.y + message.height + 5;
            // Default to choosing the highlight box
            textInput.text = "highlightBox";

            ///// Button to click to start dragging a different object
            var startDragButton:Button = new Button();
            addChild(startDragButton);
            startDragButton.width = textInput.width / 2;
            // Centered to text input field
            startDragButton.x = textInput.width / 2 - startDragButton.width / 2 + PADDING;
            // Position under text input field
            startDragButton.y = textInput.y + textInput.height + 5;
            startDragButton.label = "Start Dragging";
            startDragButton.addEventListener(MouseEvent.CLICK, onButtonClick);

            // Simulate a click to start default behaviour.
            onButtonClick();
        }

        private function onButtonClick(e:MouseEvent = null):void
        {
            if (activeSlideObject != null) { // If we have been dragging something before the new object
                // Return it to its starting position
                activeSlideObject.x = orgX;
                activeSlideObject.y = orgY;

                // Stop dragging behaviour.
                activeSlideObject.removeEventListener(MouseEvent.MOUSE_DOWN, onDown);
                onUp();

                // Remove indicators that you can drag this object.
                activeSlideObject.filters = [];
                activeSlideObject.mouseEnabled = false;
            }

            // The object that we've written in the text input field will become draggable
            activeSlideObject = getSlideObjectByName(textInput.text);

            if (activeSlideObject == null) { // If the user didn't write the name correctly
                message.text = "Could not find an object by the name of: " + textInput.text;
            } else { // User did write the name correctly

                // Reset message
                message.text = "Write the name of the object you want to access.";

                // Start dragging behaviour
                activeSlideObject.addEventListener(MouseEvent.MOUSE_DOWN, onDown);

                // Save new object's starting position.
                orgX = activeSlideObject.x;
                orgY = activeSlideObject.y;

                // Create visual indicators that you can now drag this object.
                activeSlideObject.filters = [new GlowFilter(0xFF0000)];
                activeSlideObject.mouseEnabled = true;

            }
        }

        // On mouse down start dragging.
        private function onDown(e:MouseEvent):void
        {
            activeSlideObject.startDrag();
            stage.addEventListener(MouseEvent.MOUSE_UP, onUp);
        }

        // On mouse up stop dragging.
        private function onUp(e:MouseEvent = null):void
        {
            activeSlideObject.stopDrag();
            stage.removeEventListener(MouseEvent.MOUSE_UP, onUp);
        }
    }
}

Write the name of one of the objects on slide in the input field and click the ‘Start Dragging’ button to… Well… Start dragging the object around. The name of the button is: ‘button’ the name of the highlight box is: ‘highlightBox’, the name of the caption is… Guess.

Get Adobe Flash player

Download the widget here!

Challenge: Can you drag the widget?

Posted in Widgets | Tagged , , , , , , | 14 Comments