You read correctly. It’s finally here. WidgetFactory 6! Now with logo:
Click here to Download WidgetFactory 6
WidgetFactory 6 is once again a ground up recoding of the framework, to make all the behind the scenes stuff neat and sparkly. So what’s new in WidgetFactory 6?
Captivate 6 Support
Big surprise huh? Widgets now run properly in Captivate 6, and there are tools to help you use the new CP6 features, such as the awesome library access tools. In future blogs I’ll go into how to do this, and I really mean it this time. There will be blogs. In order to push out Captivate 6, I haven’t been able to complete the documentation. So I’ll be spending a little of each work day documenting the API and I’ll try to reformat that information into blog posts. So if things go to plan that will be three blog posts per week. Reading that back just now… it’s sounds like a lot… Well we’ll see how we go.
New Widget Building Technique
The StaticWidget, InteractiveWidget, and QuestionWidget classes have been our friends for a long time, and they shall continue to be. However, as I’ve been using WidgetFactory to build larger and larger projects (such as the stunningly complex Drag and Drop Interactive widget) I’ve found that compacting the code code for each widget mode into one actionscript class doesn’t lend itself well to big projects. So I’ve included an optional new method of building widgets in WidgetFactory 6 which separates the widget modes into separate classes.
How does it work?
Well instead of extending the StaticWidget, InteractiveWidget or QuestionWidget classes, you can now extend the Widget class.
1 2 3 4 5 6 7 8 9 | package { import widgetfactory.Widget; public class MyWidget extends Widget { } } |
The Widget class has a template method called createWidgetModes(). We’ll need to extend that.
1 2 3 4 5 6 7 8 9 10 11 12 13 | package { import widgetfactory.Widget; public class MyWidget extends Widget { override protected function createWidgetModes():void { } } } |
So what do we do inside this template method? I’m glad you asked, it’s very important. In fact, so important that we’ll talk about something else first and come back to it.
Every Widget Mode now has a class that manages it. These classes are called:
- StageMode
- WidgetPropertiesDialogMode
- WidgetPropertiesDialogPreviewMode
- WidgetPanelPreviewMode
- StaticRuntimeMode
- InteractiveRuntimeMode
- QuestionRuntimeMode
- ReviewMode (New! And to be explained in another post)
These are all under the widgetfactory.modes package. So if you want to import say the StageMode class, you’d do it like this:
1 | import widgetfactory.modes.StageMode; |
So if we want something to happen in Stage mode, we have to create a new ActionScript file and extend the StageMode class like so:
1 2 3 4 5 6 7 8 9 | package { import widgetfactory.modes.StageMode; public class MyStageMode extends StageMode { } } |
Every Widget Mode class has a template method called initiate(). When the widget mode becomes active, WidgetFactory will call the initiate() method for that widget mode. So when Stage Mode becomes active, WidgetFactory will call the initiate() function for our MyStageMode class. So we’ll override the initiate() template method and insert our own code into it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | package { import widgetfactory.modes.StageMode; public class MyStageMode extends StageMode { override protected function initiate():void { graphics.beginFill(0xFF0000); graphics.drawCircle(100,100,100); graphics.endFill(); } } } |
Now when Stage mode becomes active, this class will draw a red circle, and you’ll see that on the Captivate Stage.
Except, at the moment it won’t do that, because there is one last thing we need to do.
So let’s go back to the MyWidget class and that curious createWidgetModes() template method. As the name hints, this is where you create widget modes. How do we do that? Just like how you’d create any other object in ActionScript, you use the ‘new’ keyword, followed by the name of the class and () to finish it off.
So to create a new instance of our MyStageMode class, we’d write it like this:
1 | new MyStageMode(); |
For some of you this code may look a little strange. Perhaps you’re used to seeing something like this:
1 | var stageMode:MyStageMode = new MyStageMode(); |
The above line of code is doing two things. It’s creating an instance of the MyStageMode() class, and it’s then saving it to a variable.
However, when we’re creating widget modes in the createWidgetModes() template method we should not save the widget mode to a variable. The reasons for this are a little too involved to get into now, but they do exist.
So all up the code of the MyWidget class would look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | package { import widgetfactory.Widget; public class MyWidget extends Widget { override protected function createWidgetModes():void { new MyStageMode(); } } } |
Try that, publish it, pull it into Captivate (it doesn’t matter which version) and you’ll see that glorious red circle appear on the stage. To create the other widget modes, just create more classes and have them extend the other widget mode classes. Want to make a Static Widget? Have your runtime mode extend the StaticRuntimeMode class. For Interactive widgets extend the InteractiveRuntimeMode class. For Question Widgets, the QuestionRuntimeMode class. All the runtime mode classes have the template methods, functions and properties you’ve used before with the StaticWidget, InteractiveWidget, and QuestionWidget classes, so they should be quite familiar.
Now if this all sound too complex for you, then don’t worry. The ‘classic’ widget classes are all still available and work just as well. However, if you’re gearing up to build a massive widget, then it is worth taking the time to try this new way out
Minor Changes
A number of minor changes have been made to the framework as well. Certain classes have been shifted to make the structure more manageable, so when you republish your widget it may complain for a second that it can’t find a certain class, but don’t worry they’re all still there, they’ve just been moved to another folder, do a search for it and everything will become clear.
Now there are two potential ‘gotchas’ when upgrading to Captivate 6. First of all, if any of your widgets use the cpVariables property to access something on the Captivate Main Timeline, then that’s not going to work for Captivate 6. In Captivate 6, you can ONLY use the cpVariables property to access Captivate system and user variables. If you need to access something on the Captivate Main Timeline as before, please use the captivateMainTimeline property.
The second gotcha has to do with how Captivate 6 now only load widgets when they enter runtime, and then unloads them right when they exit runtime. For more information on this, please see this post.
Aside from that, I’d ask that you all enjoy yourselves with this. There are a lot of new toys to play with. I hope you have as much fun with it as I have.




hey there,
nice work! unfortunately i fell in the first pitfall just two minutes in.
the example above won’t work for me. the solution was to implement a WidgetPropertiesDialogMode, without it no luck.
maybe you could add in your article you have to implement certain modes for it to work. or is it something on my side?
cheers
Hey Kris,
Yes I believe that is a problem with WidgetFactory. I structured WidgetFactory so if the developer didn’t define a WidgetPropertiesDialogMode that it wouldn’t display the Widget Properties Dialog in Captivate. Unfortunately that has seemed to have caused a problem with the widget running at runtime. I will look at this on Monday and get a fix out then. In the meantime everything is fine as long as you create a WidgetPropertiesDialogMode.
Tristan,
Hey Kris,
In this draganddrop untill user sumbmit all drag int targer the slide allow to next slide
can you give any example
I’m sorry, I don’t really understand what you’re asking.
Hi Tristan,
I followed your fab tutorials to create a navigation bar widget that would replace the default Captivate playbar. It works just great in CP5.5 but not in 6. I have had a look at the code but not sure where I am going wrong? I’m thinking it might have something to do with using the cpVariables property to access something on the Captivate Main Timeline, but can’t figure out how I go about fixing it.
Here is my code. Can you see anything glaringly obvious?
package {
import widgetfactory.StaticWidget;
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.events.Event;
import flash.ui.Mouse;
import flash.display.MovieClip;
import flash.net.URLRequest;
import flash.net.navigateToURL;
import flash.external.ExternalInterface;
public class NavigationBarWidget_v3 extends StaticWidget {
var captivateObject:Sprite;
var resources:int;
var glossary:int;
var accessibility:int;
var help:String;
public function NavigationBarWidget_v3() {
this.myProperties.visible = false;
this.myBackground.visible = false;
this.audioOff.visible = false;
}
override protected function saveProperties():void
{
properties.resourcesText = myProperties.textBox1.text;
properties.glossaryText = myProperties.textBox2.text;
properties.accessibilityText = myProperties.textBox3.text;
properties.helpText = myProperties.textBox4.text;
}
override protected function enterStage():void
{
this.myBackground.visible = true;
this.myProperties.visible = false;
this.audioOff.visible = false;
myProperties.textBox1.text = properties.resourcesText;
myProperties.textBox2.text = properties.glossaryText;
myProperties.textBox3.text = properties.accessibilityText;
myProperties.textBox4.text = properties.helpText;
}
override protected function enterPropertiesDialog():void
{
this.myProperties.visible = true;
this.myButtons.visible = false;
this.myBackground.visible = false;
this.audioOff.visible = false;
myProperties.textBox1.text = properties.resourcesText;
myProperties.textBox2.text = properties.glossaryText;
myProperties.textBox3.text = properties.accessibilityText;
myProperties.textBox4.text = properties.helpText;
}
override protected function enterRuntime():void
{
this.myButtons.visible = true;
this.myProperties.visible = false;
this.myBackground.visible = false;
this.audioOff.visible = false;
myProperties.textBox1.text = properties.resourcesText;
myProperties.textBox2.text = properties.glossaryText;
myProperties.textBox3.text = properties.glossaryText;
myProperties.textBox4.text = properties.helpText;
resources = properties.resourcesText – 1;
glossary = properties.glossaryText – 1;
accessibility = properties.accessibilityText – 1;
help = properties.helpText;
myButtons.resourcesButton.addEventListener(MouseEvent.CLICK, onResourcesButtonClick);
myButtons.glossaryButton.addEventListener(MouseEvent.CLICK, onGlossaryButtonClick);
myButtons.audioButton.addEventListener(MouseEvent.CLICK, onAudioButtonClick);
myButtons.menuButton.addEventListener(MouseEvent.CLICK, onMenuButtonClick);
myButtons.accessibilityButton.addEventListener(MouseEvent.CLICK, onAccessibilityButtonClick);
myButtons.homeButton.addEventListener(MouseEvent.CLICK, onHomeButtonClick);
myButtons.exitButton.addEventListener(MouseEvent.CLICK, onExitButtonClick);
myButtons.helpButton.addEventListener(MouseEvent.CLICK, onHelpButtonClick);
}
public function onResourcesButtonClick (e:MouseEvent):void
{
var myRoot:MovieClip = MovieClip(root);
var mainmov:MovieClip = MovieClip(myRoot.parent.root);
mainmov.cpCmndGotoSlide = resources;
}
private function onGlossaryButtonClick (e:MouseEvent):void
{
var myRoot:MovieClip = MovieClip(root);
var mainmov:MovieClip = MovieClip(myRoot.parent.root);
mainmov.cpCmndGotoSlide = glossary;
}
private function onAudioButtonClick (e:MouseEvent):void
{
var myRoot:MovieClip = MovieClip(root);
var mainmov:MovieClip = MovieClip(myRoot.parent.root);
if (mainmov.cpCmndMute == 0){
mainmov.cpCmndMute = 1;
this.audioOff.visible = true;
} else {
mainmov.cpCmndMute = 0;
this.audioOff.visible = false;
}
}
private function onMenuButtonClick (e:MouseEvent):void
{
var openMenu:MouseEvent = new MouseEvent(MouseEvent.MOUSE_UP);
cpVariables.ShowHideTocRight_mc.dispatchEvent(openMenu);
}
private function onAccessibilityButtonClick (e:MouseEvent):void
{
var myRoot:MovieClip = MovieClip(root);
var mainmov:MovieClip = MovieClip(myRoot.parent.root);
mainmov.cpCmndGotoSlide = accessibility;
}
private function onHomeButtonClick (e:MouseEvent):void
{
var myRoot:MovieClip = MovieClip(root);
var mainmov:MovieClip = MovieClip(myRoot.parent.root);
mainmov.cpCmndGotoSlide = 0;
}
private function onExitButtonClick (e:MouseEvent):void
{
ExternalInterface.call(“window.close()”);
}
private function onHelpButtonClick (e:MouseEvent):void
{
var url:String = help;
var request:URLRequest = new URLRequest(url);
navigateToURL(request, ‘_blank’);
}
}
}
Cheers,
Andrew
P.S I created the navigation bar widget using an older version of widget factory, not widget factory 6.
Hi Andrew,
I would seriously recommend that you upgrade your widget to WidgetFactory 6 as WidgetFactory 5 does not work in Captivate 6. The StaticWidget, InteractiveWidget, and QuestionWidget classes are all still in WidgetFactory 6, so you won’t have to rebuild your widget.
Tristan,
OK, so I have upgraded to WidgetFactory 6, but my widget still wouldn’t work, so I am now attempting to recreate the widget using your WidgetFactory 6 example widget. I think (hoping) that I have placed the relevant code from my old widget into the correct class files for each mode, but I am getting ’1120: Access of undefined property’ errors trying to reference movieclip instances on the stage. Any ideas why?
Andrew, I think it would be easier to use the widget as you posted it above and just replace wherever you use and access ‘mainmov’ like this…
var myRoot:MovieClip = MovieClip(root);
var mainmov:MovieClip = MovieClip(myRoot.parent.root);
…with the cpVariables property. So to access cpCmndGotoSlide, you’d just type: cpVariables.cpCmndGotoSlide;
As for the widget mode classes, the problem is you’re trying to access the elements on the stage from the widget mode classes, and those widget modes are their own movieclips that are added as children of the widget. To access an object on your Flash Main Timeline, try accessing them through the ‘parent’ property in the widget mode.
Even if you use the widget mode method, you’ll still need to access the Captivate Variables through the cpVariables property.
Hi Tristan,
Can you give me an example of accessing them through the ‘parent’ property?
I have replaced the use of ‘mainmov’ with cpVariables but still can’t get my widget to work, even though I don’t get any errors. When I import my widget into CP6 and preview the project none of the buttons work. It also doesn’t retain any of the info I add in properties dialogue. Here’s how my code looks now:
package {
import widgetfactory.StaticWidget;
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.events.Event;
import flash.ui.Mouse;
import flash.display.MovieClip;
import flash.net.URLRequest;
import flash.net.navigateToURL;
import flash.external.ExternalInterface;
public class NavigationBarWidget_CP6_v1 extends StaticWidget {
var captivateObject:Sprite;
var resources:int;
var glossary:int;
var accessibility:int;
var help:String;
public function NavigationBarWidget_CP6_v1() {
this.myProperties.visible = false;
this.myBackground.visible = false;
this.audioOff.visible = false;
}
override protected function saveProperties():void
{
properties.resourcesText = myProperties.textBox1.text;
properties.glossaryText = myProperties.textBox2.text;
properties.accessibilityText = myProperties.textBox3.text;
properties.helpText = myProperties.textBox4.text;
}
override protected function enterStage():void
{
this.myBackground.visible = true;
this.myProperties.visible = false;
this.audioOff.visible = false;
myProperties.textBox1.text = properties.resourcesText;
myProperties.textBox2.text = properties.glossaryText;
myProperties.textBox3.text = properties.accessibilityText;
myProperties.textBox4.text = properties.helpText;
}
override protected function enterPropertiesDialog():void
{
this.myProperties.visible = true;
this.myButtons.visible = false;
this.myBackground.visible = false;
this.audioOff.visible = false;
myProperties.textBox1.text = properties.resourcesText;
myProperties.textBox2.text = properties.glossaryText;
myProperties.textBox3.text = properties.accessibilityText;
myProperties.textBox4.text = properties.helpText;
}
override protected function enterRuntime():void
{
this.myButtons.visible = true;
this.myProperties.visible = false;
this.myBackground.visible = false;
this.audioOff.visible = false;
myProperties.textBox1.text = properties.resourcesText;
myProperties.textBox2.text = properties.glossaryText;
myProperties.textBox3.text = properties.glossaryText;
myProperties.textBox4.text = properties.helpText;
resources = properties.resourcesText – 1;
glossary = properties.glossaryText – 1;
accessibility = properties.accessibilityText – 1;
help = properties.helpText;
myButtons.resourcesButton.addEventListener(MouseEvent.CLICK, onResourcesButtonClick);
myButtons.glossaryButton.addEventListener(MouseEvent.CLICK, onGlossaryButtonClick);
myButtons.audioButton.addEventListener(MouseEvent.CLICK, onAudioButtonClick);
myButtons.menuButton.addEventListener(MouseEvent.CLICK, onMenuButtonClick);
myButtons.accessibilityButton.addEventListener(MouseEvent.CLICK, onAccessibilityButtonClick);
myButtons.homeButton.addEventListener(MouseEvent.CLICK, onHomeButtonClick);
myButtons.exitButton.addEventListener(MouseEvent.CLICK, onExitButtonClick);
myButtons.helpButton.addEventListener(MouseEvent.CLICK, onHelpButtonClick);
}
public function onResourcesButtonClick (e:MouseEvent):void
{
//var myRoot:MovieClip = MovieClip(root);
//var mainmov:MovieClip = MovieClip(myRoot.parent.root);
//mainmov.cpCmndGotoSlide = resources;
cpVariables.cpCmndGotoSlide = resources;
}
private function onGlossaryButtonClick (e:MouseEvent):void
{
//var myRoot:MovieClip = MovieClip(root);
//var mainmov:MovieClip = MovieClip(myRoot.parent.root);
//mainmov.cpCmndGotoSlide = glossary;
cpVariables.cpCmndGotoSlide = glossary;
}
private function onAudioButtonClick (e:MouseEvent):void
{
/*var myRoot:MovieClip = MovieClip(root);
var mainmov:MovieClip = MovieClip(myRoot.parent.root);
//mainmov.cpCmndMute = 1;
if (mainmov.cpCmndMute == 0){
mainmov.cpCmndMute = 1;
this.audioOff.visible = true;
} else {
mainmov.cpCmndMute = 0;
this.audioOff.visible = false;
}*/
if (cpVariables.cpCmndMute == 0){
cpVariables.cpCmndMute = 1;
this.audioOff.visible = true;
} else {
cpVariables.cpCmndMute = 0;
this.audioOff.visible = false;
}
}
private function onMenuButtonClick (e:MouseEvent):void
{
var openMenu:MouseEvent = new MouseEvent(MouseEvent.MOUSE_UP);
//cpVariables.ShowHideTocRight_mc.dispatchEvent(openMenu);
captivateMainTimeline.ShowHideTocRight_mc.dispatchEvent(openMenu);
}
private function onAccessibilityButtonClick (e:MouseEvent):void
{
//var myRoot:MovieClip = MovieClip(root);
//var mainmov:MovieClip = MovieClip(myRoot.parent.root);
//mainmov.cpCmndGotoSlide = accessibility;
cpVariables.cpCmndGotoSlide = accessibility;
}
private function onHomeButtonClick (e:MouseEvent):void
{
//var myRoot:MovieClip = MovieClip(root);
//var mainmov:MovieClip = MovieClip(myRoot.parent.root);
//mainmov.cpCmndGotoSlide = 0;
cpVariables.cpCmndGotoSlide = 0;
}
private function onExitButtonClick (e:MouseEvent):void
{
//var myRoot:MovieClip = MovieClip(root);
//var mainmov:MovieClip = MovieClip(myRoot.parent.root);
//mainmov.rdcmndExit = 1;
//ExternalInterface.call(“window.close()”);
cpVariables.rdcmndExit = 1;
}
private function onHelpButtonClick (e:MouseEvent):void
{
var url:String = help;
var request:URLRequest = new URLRequest(url);
navigateToURL(request, ‘_blank’);
}
}
}
If you import this widget into Captivate 5 or 5.5 does it work?
Yes, it seems to work fine in CP5.5
Hi Andrew,
I believe the problem with your widget is that you need to set up default Widget Properties for your Widget Properties Dialog interface. This article here discusses default properties in detail: http://www.infosemantics.com.au/widgetking/2010/11/widget-properties-how-theyre-used/ (Scroll down to the The Widget King Widget Properties Method heading).
This should be a rather basic change. You should only need to change your enterPropertiesDialog() function to this:
override protected function enterPropertiesDialog():void
{ //enterPropertiesDialog = keeps the info when you go back into widget properties
if (!properties.setBefore) {
properties.resourcesText = “0″;
properties.glossaryText = “0″;
properties.accessibilityText = “0″;
properties.helpText = “http://www.google.com”;
properties.setBefore = true;
}
this.myProperties.visible = true;
this.myButtons.visible = false;
this.myBackground.visible = false;
this.audioOff.visible = false;
myProperties.textBox1.text = properties.resourcesText;
myProperties.textBox2.text = properties.glossaryText;
myProperties.textBox3.text = properties.accessibilityText;
myProperties.textBox4.text = properties.helpText;
}
Tristan,
This is something I have unable to sort out so here’s hoping you can help!
I have a variable in Captivate 5.5 called “stat” and set it to 0, I then insert a flash animation into cap with this code below:
var UserStatus:String = String(ExternalInterface.call(‘g_objAPI.LMSGetValue’,'cmi.core.lesson_status’));
if (UserStatus==”passed”){
var myRoot:MovieClip = MovieClip(root);
var mainmov:MovieClip = MovieClip(myRoot.parent.root);
mainmov.stat=1
}
Then what I do is use this stat=1 (if passed) to inform the user that they have “passed” when they revisit the module again and I can add some extra graphic stuff. The client loves it. I want to use captivate 6, but this code dosnt work as adobe have changed things on main timeline.
Question is can I still do the same thing (insert a flash anmiation with some code) but change this code to something else:)
var myRoot:MovieClip = MovieClip(root);
var mainmov:MovieClip = MovieClip(myRoot.parent.root);
or
Is this now impossible to add a custom var in captivate and change with a swf
Regards
WL
Hi WL,
Try using this code to your swf and see if it works. The if statement down the bottom is where the ‘stat’ variable is changed.
import flash.display.DisplayObjectContainer;
function getCaptivateVariables(checkObject:DisplayObjectContainer):Object
{
if (checkObject.hasOwnProperty(“getMovieProps”)) {
var movieProps:Object = Object(checkObject.getMovieProps();
return movieProps.variablesHandle;
} else if (checkObject != stage) {
return getCaptivateVariables(checkObject.parent);
}
return null;
}
var captivateVariables:Object = getCaptivateVariables(this);
if (captivateVariables) {
// Use the captivateVariables object to access captivate variables.
// For example: captivateVariables.cpInfoCurrentFrame;
// Or: captivateVariables.myCustomVariableName;
captivateVariables.stat = 1;
}
Hi Tristan,
thanks so much for this. I understand what the code does too.
I’m getting a compile error in flash with this line:-
var movieProps:Object = Object(checkObject.getMovieProps());
(….call to undefined method getMovieProps….)
spent a good while trying to fix this morning, but not getting anywhere
regards
WL
Try moving the ) brace back so that it only contains ‘checkObject’:
Object(checkObject)
All together it looks like this:
var movieProps:Object = Object(checkObject).getMovieProps();
Tristan Great stuff
thanks for helping with this
So what we are doing is checking captivate timeline to see if there is a variable called stat if there is we can change it
Does this look ok to you re setting the stat variable, I haven’t got Captivate 6 at home only at work.
regards
WL
import flash.display.DisplayObjectContainer;
var UserStatus:String = String(ExternalInterface.call(“g_objAPI.LMSGetValue”,”cmi.core.lesson_status”));
if (UserStatus==”passed”){
captivateVariables.stat = 1
}
function getCaptivateVariables(checkObject:DisplayObjectContainer):Object
{
if (checkObject.hasOwnProperty(“getMovieProps”))
{
var movieProps:Object = Object(checkObject).getMovieProps();
return movieProps.variablesHandle;
}
else if (checkObject != stage)
{
return getCaptivateVariables(checkObject.parent);
}
return null;
}
var captivateVariables:Object = getCaptivateVariables(this);
if (captivateVariables)
{
trace(“true”)
}
Yes that code looks okay to me.
In Captivate 6, Adobe has moved reading and writing Captivate Variables from the Captivate Main Timeline. The getCaptivateVariables() function locates the object in Captivate 6 that holds the Captivate Variables and sends it back to you.