|
CreateStaticWidget
Article on how to create a Captivate Static Widget using CpGears
IntroductionCreating a static Widget with CpGears is quite simple ... once you know how and if you're using the proper tools. I always develop my widgets in FlashDevelop. It's an amazing tool and the best part is that it's free. You can download the complete code for this article, in a FlashDevelop project, in the downloads section. Setting up a FlashDevelop projectOpen up FlashDevelop, select the "Project" menu and click on "New Project..." You should now be presented with the New Project dialog. Select a folder location and create your project as per the image below.
Clicking on the OK button should create a folder called StaticWidget which includes the FlashDevelop project files. Next, in StaticWidget directory, create a folder called lib and copy the CpGears.swc component that can also be found in the "downloads section". Go back to FlashDevelop and from the Project view, open up the newly created lib folder and select the CpGears.swc component. Right-click and select Add to library. This will cause FlashDevelop to automatically propose CpGears classes, methods and properties when you type in text.
Now, we are going to prepare the folder structure (also called package structure) for our widget. In the StaticWidget folder, create the following hierarchy of folders:
Creating the Widget classThe project is now well under way and we can start creating the class files that will compose our Static Widget. Start by creating a class file called DemoStaticWidget that derives from CpGears' StaticWidget class. Make sure that the newly created class file is located in the demo folder. package demo {
import cpgears.StaticWidget;
public class DemoStaticWidget extends StaticWidget {
public function DemoStaticWidget() {
}
}
}Creating the Flash (fla) fileIn order to compile our widget and create assets for it, we need to create a .fla file. Open up Flash, create a new AS3 file and save it in our StaticWidget folder under the name DemoStaticWidget.fla. To link the .fla file with our widget class, we just need to type in the name of our widget class in the Document Class field in the properties tab. While we're at it, let's resize the stage to 300 px by 200 px.
At this point, if we were to test the movie in flash, we would get compiler errors. This is normal since flash isn't aware of CpGears for the moment. We will correct the situation by telling it where it can be found. Remember that lib folder we created at the beginning? We just need to tell flash its path. To do so, open up Flash's Publish Settings dialog and open the Actionscript Settings dialog. Chose the Library Path tab and add a new row. Type in lib and close the dialogs.
Congratulations! You just created your first Static Widget using CpGears. You should be able to publish the swf and load it up in Captivate as a widget. However, if you do, you will find that the widget doesn't do much. It opens up an empty dialog and doesn't display anything on the stage or during runtime. So keep on reading! Creating the widget viewsOne of the most powerful feature of CpGears is that it manages the widget views automatically for you. A widget can have the following views:
If you tell CpGears what those views are, it will manage them for you. Edit ViewLets start by creating the Edit View. The first thing to do is to create a class that will manage the Edit View. In FlashDevelop, create in the .\demo\views folder the following class. package demo.views {
import cpgears.views.WidgetView;
public class EditView extends WidgetView {
public function EditView() {
super();
}
}
}Now lets go back to flash and create a MovieClip that will physically represent the Edit View. Create a new Symbol that you will name EditView and for which you are going to set the linkage properties as per the picture below.
Open up this symbol in Flash and add a Static TextField with the text My Widget Name as well as a text input named widgetName_ti. It should look like this:
From this moment on, your movieclip representation of the edit view should be connected to the actionscript class so we can control what happens in the movieclip directly from the class itself! That's neat but how do we get CpGears to display it? With one simple line of code. Go back to your DemoStaticWidget class and add this line in the constructor: package demo {
import cpgears.StaticWidget;
import cpgears.views.manager.SimpleViewManager;
import demo.views.EditView;
/**
* ...
* @author Whyves
*/
public class DemoStaticWidget extends StaticWidget {
public function DemoStaticWidget() {
super(null, new SimpleViewManager(new EditView(), null, null, null));
}
}
}What we did was to define one View Manager and give it the views that it will manage, in this case, the EditView class that we previously created. The view manager will take care of displaying the proper view at the right time. The class SimpleViewManager is the manager that you should be using most of the time. It will work for a static or interactive widget. The SimpleViewManager will eventually manage all four views (Preview, Edit View, Stage View, and Runtime View). For now, we just defined the EditView. Now, give this a try: Compile the swf in Flash and upload it in Captivate. Captivate should present you this dialog when the widget loads:
That doesn't look too good right? It's much bigger than what we created and it is truncated. The problem here is that Captivate tries to fit the widget' Edit View in it's default window size. To solve this, we need to do two things:
First, the Stage scale and align modes. For this, we need to go back to our EditView class and override the default behavior for the modes. CpGears allows you to specify the Stage Scale and Align mode of every view. package demo.views {
import cpgears.views.WidgetView;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
public class EditView extends WidgetView {
public function EditView() {
super();
}
override public function get stageScaleMode():String {
return StageScaleMode.NO_SCALE;
}
override public function get stageAlign():String {
return StageAlign.TOP_LEFT;
}
}
}This will prevent Captivate from scaling the view and will position its reference in the top left corner. This is what we should be getting:
Now, on to resizing the dialog. For this, we need to go back to the DemoStaticWidget class and force the dialog size by adding those two lines in the constructor: propertiesDialog.width = 300; propertiesDialog.height = 200; Our EditView has a size of 300px by 200px so we resize the dialog to this dimension. Finally, in Captivate, we get:
We will leave the Edit view like this for the moment and proceed to create the runtime version of our widget. Runtime ViewThe creation of the Runtime View is very similar to the creation of the Edit View. We need to create a class, a movieclip and link them together. The only major difference is that the class doesn't extend from WidgetView but rather from RuntimeWidgetView. Create the following class in the .\demo\views folder: package demo.views {
import cpgears.views.RuntimeWidgetView;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
public class RuntimeView extends RuntimeWidgetView {
public function RuntimeView() {
super();
}
override public function get stageScaleMode():String {
return StageScaleMode.NO_SCALE;
}
override public function get stageAlign():String {
return StageAlign.TOP_LEFT;
}
}
}Now, in Flash, create a new Symbol for the RuntimeView and link it to the class that we just created.
Lets add a button to our runtime view and lets name it widget_btn.
And for the last step ... lets make our View Manager aware of the new Runtime view. Nothing is more simple, lets go back to our DemoStaticWidget class and add our new view to the SimpleViewManager. super(null, new SimpleViewManager(new EditView(), new RuntimeView(), null, null)); What if we want the stage to show the same view as the Runtime View? Just ask the SimpleViewManager to do it for you! super(null, new SimpleViewManager(new EditView(), new RuntimeView(), new RuntimeView(), null)); Compile this and upload it in Captivate. You will now see a button on the Stage and when you're previewing the lesson. Widget PropertiesUp to this point, the two views have no way of sharing information. The Edit View has no means of telling the Runtime View how to configure itself. This functionality is accomplished by the widget properties object. In the demo folder, create the following class: package demo {
import cpgears.properties.WidgetPropertiesBase;
import flash.utils.getQualifiedClassName;
/**
* ...
* @author Whyves
*/
public class StaticWidgetProperties extends WidgetPropertiesBase {
public function StaticWidgetProperties() {
super(getQualifiedClassName(this));
}
}
}For the time being, we will ignore what happens in the super() function. We will cover that in a different article. The important thing to know is how we connect this class in CpGears. Again, nothing is more simple. We just go back to the DemoStaticWidget class and replace this line in the constructor: super(new StaticWidgetProperties(), new SimpleViewManager(new EditView(), new RuntimeView(), new RuntimeView(), null)); From this point on, your properties object is now part of the CpGears workflow and will be provided to you in every view. Before we go any further with the views, let add a property called widgetName that will be used to configure our views. Let us also initialize it to a default value of My CpGears Widget. package demo {
import cpgears.properties.WidgetPropertiesBase;
import flash.utils.getQualifiedClassName;
public class StaticWidgetProperties extends WidgetPropertiesBase {
public function StaticWidgetProperties() {
super(getQualifiedClassName(this));
}
override protected function setDefaultValues():void {
widgetName = "My CpGears Widget";
}
public function get widgetName():String {
return getProperty("widgetName") as String;
}
public function set widgetName(value:String):void {
setProperty("widgetName", value);
}
}
}There is a lot of black magic happening here and for which an explanation must be given. First, by defining the property widgetName this way, we will get compile time errors if we try to use a property that doesn't exist or if we make a typo in the name. Take note that the name used when calling the getProperty() or setProperty() functions must be the exact same name as the property itself. Finally, by overriding the setDefaultValues() function, we get to set the default value of any property. Using Widget Properties in the Edit ViewIn our edit view, we will want the text input to receive the name of our widget. We will also want to store that name in the properties object so Captivate can send it to the Runtime View when the presentation is run. So, we will go back to our EditView class and add a few lines of code. First, lets strongly cast the properties object into its real type. This will allow us to get compile time errors as well as having our IDE (FlashDevelop) propose properties to us. private function get properties():StaticWidgetProperties {
return widgetProperties as StaticWidgetProperties;
}Next, the workflow for a view is to call initialize() when the view is being displayed and call finalize() when the view is to be removed. So, when the view is initialized, we want to copy the value of the widgetName property in the input text and when the view is removed, to copy the content of the input text in the widgetName property so Captivate can store it. public var widgetName_ti:TextInput;
override public function initialize():void {
widgetName_ti.text = properties.widgetName;
}
override public function finalize():void {
properties.widgetName = widgetName_ti.text;
}
Now, compile the swf and upload it in Captivate. You will see that the text input is populated with My CpGears Widget. If you change this value for any other text and click on the OK button, your new text will be persisted by Captivate and eventually given back to the Runtime view. Using Widget Properties in the Runtime ViewThe final part of our article will be to use the widgetName property to customize the Runtime View. What we will do is very simple; we will change the button label with the widgetName property. Lets start by editing the RuntimeView class. Lets type cast the properties object just like we did in the EditView. private function get properties():StaticWidgetProperties {
return widgetProperties as StaticWidgetProperties;
}Next, lets capitalize on the views' workflow and change the label of the button in the initialize() function: public var widget_btn:Button;
override public function initialize():void {
widget_btn.label = properties.widgetName;
}Compile the swf and upload it in Captivate. At Runtime and when previewed on the Stage, the button should show the name that you wrote in the EditView.
Et voilĂ , you have a functional Static Widget based on CpGears with a minimal set of code. |
Hi there,
Thanks so much for this - I have been looking for something on creating widgets for ages and this is perfect.
However... I can't get your demo to work in Flash CS5. When I load DemoStaticWidget?.fla and CTRL-ENTER it to create the .swf, I get the following compiler errors: -
DemoStaticWidget?.as, Line 11 - 1017: The definition of the base class StaticWidget? was not found.
DemoStaticWidget?.as, Line 1 - 5000: The class 'demo.DemoStaticWidget?' must subclass 'flash.display.MovieClip?' since it is linked to a library symbol of that type.
That last error repeats 5 more times, for EditView?.as, Line 1, RunTimeView?.as, Line 1, and ComponentShim? (Compiled Clip), Line 1 (3 times.)
I have basic knowledge of ActionScript? and am teaching myself Flash at the moment and would really like to make use of cpGears in my Captivate lessons.
Would appreciate any help - I imagine it's something I've not linked properly or whatever, but the demo files are untouched from the downloaded package you provide so they should work?
Thanks,
Jon
Hi Jon,
The example was created using Flash CS4. Try this out: in the .fla file, access the "Advanced ActionScript? 3.0 Settings" dialog and select the "Library Path" tab. There, replace the "lib" folder with "./lib". That should be working. It seems that there's a small difference at how CS5 handles the folder paths.
And thanks for the comment!
Yves
Ah... fantastic! I thought it'd be something simple like that - it's working fine now. :-)
Thanks again - I would imagine the majority of 'average' Captivate users would never need something like cpGears, but I for one really appreciate the work you've put into it.
Cheers Yves!
Jon
Ooh - cheeky, I know, but is there any chance you'll be planning a tutorial on creating a question widget at some point? :-D
Hi Jon,
Glad it's working now. I do hope that a lot of Widget_ers will give CpGears? a spin. As for the tutorial on question widget: It is noted! However, I think that there might be a little bit more work to do on the QuestionWidget? class. Could be a good way to get feedback!
Yves
I can not express how happy I am with CpGears?. I have my first simple widget working! I am still in the baby steps stage so mine just runs a flash created drag and drop with feedback. No captivate communication yet.
My question is I want my widget to reset and play again if a learner moves back to the widget slide. Right now if I do the widget drag and drop get my feedback and move to the next captivate slide, if I return to my widget slide all I see is my feedback from the first time I did it. Thank you.
Hi Walter,
Thanks for trying out CpGears? and glad that you like it! You can reset the widget functionality by listening to its visibility event. I have written an article on this:
http://code.google.com/p/cpgears/wiki/CPGearsEvents
Give it a try!
Yves
Good day Yves,
I have looked at the CPGearsEvents and I guess I am missing something. In that example you are changing the visibility of either the widget or the playbar all in the same captivate slide. For what I want to do which is when ever I re enter the captivate slide which my widget is on I want to re initialize() it. So would this be like... Using the Monitoring Captivate Events when "CPSlideEnterEvent" then "initialize()"? Or am I way off base?
Hi Walter,
You just have to register to the Visibility event and do something like this:
import cpgears.movie.widget.events.WidgetEvent; import cpgears.movie.widget.events.WidgetVisibilityEvent; override public function initialize():void { movie.widget.addEventListener(WidgetEvent.VISIBILITY_CHANGED, onWidgetVisibilityChanged); } private function onWidgetVisibilityChanged(event:WidgetVisibilityEvent):void { resetWidget(); } private function resetWidget():void { <ADD YOUR CODE HERE> }And in the resetWidget() function, put all your internal logic to reset your widget.
Hope that helped!
Yves