/*
Copyright (c) 2004-2006, The Dojo Foundation
All Rights Reserved.
Licensed under the Academic Free License version 2.1 or above OR the
modified BSD license. For more information on Dojo licensing, see:
http://dojotoolkit.org/community/licensing.shtml
*/
dojo.provide("dojo.flash");
dojo.require("dojo.string.*");
dojo.require("dojo.uri.*");
/**
The goal of dojo.flash is to make it easy to extend Flash's capabilities
into an AJAX/DHTML environment. Robust, performant, reliable
JavaScript/Flash communication is harder than most realize when they
delve into the topic, especially if you want it
to work on Internet Explorer, Firefox, and Safari, and to be able to
push around hundreds of K of information quickly. Dojo.flash makes it
possible to support these platforms; you have to jump through a few
hoops to get its capabilites, but if you are a library writer
who wants to bring Flash's storage or streaming sockets ability into
DHTML, for example, then dojo.flash is perfect for you.
Dojo.flash provides an easy object for interacting with the Flash plugin.
This object provides methods to determine the current version of the Flash
plugin (dojo.flash.info); execute Flash instance methods
independent of the Flash version
being used (dojo.flash.comm); write out the necessary markup to
dynamically insert a Flash object into the page (dojo.flash.Embed; and
do dynamic installation and upgrading of the current Flash plugin in
use (dojo.flash.Install).
To use dojo.flash, you must first wait until Flash is finished loading
and initializing before you attempt communication or interaction.
To know when Flash is finished use dojo.event.connect:
dojo.event.connect(dojo.flash, "loaded", myInstance, "myCallback");
Then, while the page is still loading provide the file name
and the major version of Flash that will be used for Flash/JavaScript
communication (see "Flash Communication" below for information on the
different kinds of Flash/JavaScript communication supported and how they
depend on the version of Flash installed):
dojo.flash.setSwf({flash6: "src/storage/storage_flash6.swf",
flash8: "src/storage/storage_flash8.swf"});
This will cause dojo.flash to pick the best way of communicating
between Flash and JavaScript based on the platform.
If no SWF files are specified, then Flash is not initialized.
Your Flash must use DojoExternalInterface to expose Flash methods and
to call JavaScript; see "Flash Communication" below for details.
setSwf can take an optional 'visible' attribute to control whether
the Flash object is visible or not on the page; the default is visible:
dojo.flash.setSwf({flash6: "src/storage/storage_flash6.swf",
flash8: "src/storage/storage_flash8.swf",
visible: false});
Once finished, you can query Flash version information:
dojo.flash.info.version
Or can communicate with Flash methods that were exposed:
var results = dojo.flash.comm.sayHello("Some Message");
Only string values are currently supported for both arguments and
for return results. Everything will be cast to a string on both
the JavaScript and Flash sides.
-------------------
Flash Communication
-------------------
dojo.flash allows Flash/JavaScript communication in
a way that can pass large amounts of data back and forth reliably and
very fast. The dojo.flash
framework encapsulates the specific way in which this communication occurs,
presenting a common interface to JavaScript irrespective of the underlying
Flash version.
There are currently three major ways to do Flash/JavaScript communication
in the Flash community:
1) Flash 6+ - Uses Flash methods, such as SetVariable and TCallLabel,
and the fscommand handler to do communication. Strengths: Very fast,
mature, and can send extremely large amounts of data; can do
synchronous method calls. Problems: Does not work on Safari; works on
Firefox/Mac OS X only if Flash 8 plugin is installed; cryptic to work with.
2) Flash 8+ - Uses ExternalInterface, which provides a way for Flash
methods to register themselves for callbacks from JavaScript, and a way
for Flash to call JavaScript. Strengths: Works on Safari; elegant to
work with; can do synchronous method calls. Problems: Extremely buggy
(fails if there are new lines in the data, for example); performance
degrades drastically in O(n^2) time as data grows; locks up the browser while
it is communicating; does not work in Internet Explorer if Flash
object is dynamically added to page with document.writeln, DOM methods,
or innerHTML.
3) Flash 6+ - Uses two seperate Flash applets, one that we
create over and over, passing input data into it using the PARAM tag,
which then uses a Flash LocalConnection to pass the data to the main Flash
applet; communication back to Flash is accomplished using a getURL
call with a javascript protocol handler, such as "javascript:myMethod()".
Strengths: the most cross browser, cross platform pre-Flash 8 method
of Flash communication known; works on Safari. Problems: Timing issues;
clunky and complicated; slow; can only send very small amounts of
data (several K); all method calls are asynchronous.
dojo.flash.comm uses only the first two methods. This framework
was created primarily for dojo.storage, which needs to pass very large
amounts of data synchronously and reliably across the Flash/JavaScript
boundary. We use the first method, the Flash 6 method, on all platforms
that support it, while using the Flash 8 ExternalInterface method
only on Safari with some special code to help correct ExternalInterface's
bugs.
Since dojo.flash needs to have two versions of the Flash
file it wants to generate, a Flash 6 and a Flash 8 version to gain
true cross-browser compatibility, several tools are provided to ease
development on the Flash side.
In your Flash file, if you want to expose Flash methods that can be
called, use the DojoExternalInterface class to register methods. This
class is an exact API clone of the standard ExternalInterface class, but
can work in Flash 6+ browsers. Under the covers it uses the best
mechanism to do communication:
class HelloWorld{
function HelloWorld(){
// Initialize the DojoExternalInterface class
DojoExternalInterface.initialize();
// Expose your methods
DojoExternalInterface.addCallback("sayHello", this, this.sayHello);
// Tell JavaScript that you are ready to have method calls
DojoExternalInterface.loaded();
// Call some JavaScript
var resultsReady = function(results){
trace("Received the following results from JavaScript: " + results);
}
DojoExternalInterface.call("someJavaScriptMethod", resultsReady,
someParameter);
}
function sayHello(){ ... }
static main(){ ... }
}
DojoExternalInterface adds two new functions to the ExternalInterface
API: initialize() and loaded(). initialize() must be called before
any addCallback() or call() methods are run, and loaded() must be
called after you are finished adding your callbacks. Calling loaded()
will fire the dojo.flash.loaded() event, so that JavaScript can know that
Flash has finished loading and adding its callbacks, and can begin to
interact with the Flash file.
To generate your SWF files, use the ant task
"buildFlash". You must have the open source Motion Twin ActionScript
compiler (mtasc) installed and in your path to use the "buildFlash"
ant task; download and install mtasc from http://www.mtasc.org/.
buildFlash usage:
ant buildFlash -Ddojo.flash.file=../tests/flash/HelloWorld.as
where "dojo.flash.file" is the relative path to your Flash
ActionScript file.
This will generate two SWF files, one ending in _flash6.swf and the other
ending in _flash8.swf in the same directory as your ActionScript method:
HelloWorld_flash6.swf
HelloWorld_flash8.swf
Initialize dojo.flash with the filename and Flash communication version to
use during page load; see the documentation for dojo.flash for details:
dojo.flash.setSwf({flash6: "tests/flash/HelloWorld_flash6.swf",
flash8: "tests/flash/HelloWorld_flash8.swf"});
Now, your Flash methods can be called from JavaScript as if they are native
Flash methods, mirrored exactly on the JavaScript side:
dojo.flash.comm.sayHello();
Only Strings are supported being passed back and forth currently.
JavaScript to Flash communication is synchronous; i.e., results are returned
directly from the method call:
var results = dojo.flash.comm.sayHello();
Flash to JavaScript communication is asynchronous due to limitations in
the underlying technologies; you must use a results callback to handle
results returned by JavaScript in your Flash AS files:
var resultsReady = function(results){
trace("Received the following results from JavaScript: " + results);
}
DojoExternalInterface.call("someJavaScriptMethod", resultsReady);
-------------------
Notes
-------------------
If you have both Flash 6 and Flash 8 versions of your file:
dojo.flash.setSwf({flash6: "tests/flash/HelloWorld_flash6.swf",
flash8: "tests/flash/HelloWorld_flash8.swf"});
but want to force the browser to use a certain version of Flash for
all platforms (for testing, for example), use the djConfig
variable 'forceFlashComm' with the version number to force:
var djConfig = { forceFlashComm: 6 };
Two values are currently supported, 6 and 8, for the two styles of
communication described above. Just because you force dojo.flash
to use a particular communication style is no guarantee that it will
work; for example, Flash 8 communication doesn't work in Internet
Explorer due to bugs in Flash, and Flash 6 communication does not work
in Safari. It is best to let dojo.flash determine the best communication
mechanism, and to use the value above only for debugging the dojo.flash
framework itself.
Also note that dojo.flash can currently only work with one Flash object
on the page; it and the API do not yet support multiple Flash objects on
the same page.
We use some special tricks to get decent, linear performance
out of Flash 8's ExternalInterface on Safari; see the blog
post
http://codinginparadise.org/weblog/2006/02/how-to-speed-up-flash-8s.html
for details.
Your code can detect whether the Flash player is installing or having
its version revved in two ways. First, if dojo.flash detects that
Flash installation needs to occur, it sets dojo.flash.info.installing
to true. Second, you can detect if installation is necessary with the
following callback:
dojo.event.connect(dojo.flash, "installing", myInstance, "myCallback");
You can use this callback to delay further actions that might need Flash;
when installation is finished the full page will be refreshed and the
user will be placed back on your page with Flash installed.
Two utility methods exist if you want to add loading and installing
listeners without creating dependencies on dojo.event; these are
'addLoadingListener' and 'addInstallingListener'.
-------------------
Todo/Known Issues
-------------------
There are several tasks I was not able to do, or did not need to fix
to get dojo.storage out:
* When using Flash 8 communication, Flash method calls to JavaScript
are not working properly; serialization might also be broken for certain
invalid characters when it is Flash invoking JavaScript methods.
The Flash side needs to have more sophisticated serialization/
deserialization mechanisms like JavaScript currently has. The
test_flash2.html unit tests should also be updated to have much more
sophisticated Flash to JavaScript unit tests, including large
amounts of data.
* On Internet Explorer, after doing a basic install, the page is
not refreshed or does not detect that Flash is now available. The way
to fix this is to create a custom small Flash file that is pointed to
during installation; when it is finished loading, it does a callback
that says that Flash installation is complete on IE, and we can proceed
to initialize the dojo.flash subsystem.
@author Brad Neuberg, bkn3@columbia.edu
*/
dojo.flash = {
flash6_version: null,
flash8_version: null,
ready: false,
_visible: true,
_loadedListeners: new Array(),
_installingListeners: new Array(),
/** Sets the SWF files and versions we are using. */
setSwf: function(fileInfo){
//dojo.debug("setSwf");
if(fileInfo == null || dojo.lang.isUndefined(fileInfo)){
return;
}
if(fileInfo.flash6 != null && !dojo.lang.isUndefined(fileInfo.flash6)){
this.flash6_version = fileInfo.flash6;
}
if(fileInfo.flash8 != null && !dojo.lang.isUndefined(fileInfo.flash8)){
this.flash8_version = fileInfo.flash8;
}
if(!dojo.lang.isUndefined(fileInfo.visible)){
this._visible = fileInfo.visible;
}
// initialize ourselves
this._initialize();
},
/** Returns whether we are using Flash 6 for communication on this platform. */
useFlash6: function(){
if(this.flash6_version == null){
return false;
}else if (this.flash6_version != null && dojo.flash.info.commVersion == 6){
// if we have a flash 6 version of this SWF, and this browser supports
// communicating using Flash 6 features...
return true;
}else{
return false;
}
},
/** Returns whether we are using Flash 8 for communication on this platform. */
useFlash8: function(){
if(this.flash8_version == null){
return false;
}else if (this.flash8_version != null && dojo.flash.info.commVersion == 8){
// if we have a flash 8 version of this SWF, and this browser supports
// communicating using Flash 8 features...
return true;
}else{
return false;
}
},
/** Adds a listener to know when Flash is finished loading.
Useful if you don't want a dependency on dojo.event. */
addLoadedListener: function(listener){
this._loadedListeners.push(listener);
},
/** Adds a listener to know if Flash is being installed.
Useful if you don't want a dependency on dojo.event. */
addInstallingListener: function(listener){
this._installingListeners.push(listener);
},
/**
A callback when the Flash subsystem is finished loading and can be
worked with. To be notified when Flash is finished loading, connect
your callback to this method using the following:
dojo.event.connect(dojo.flash, "loaded", myInstance, "myCallback");
*/
loaded: function(){
//dojo.debug("dojo.flash.loaded");
dojo.flash.ready = true;
if(dojo.flash._loadedListeners.length > 0){
for(var i = 0;i < dojo.flash._loadedListeners.length; i++){
dojo.flash._loadedListeners[i].call(null);
}
}
},
/**
A callback to know if Flash is currently being installed or
having its version revved. To be notified if Flash is installing, connect
your callback to this method using the following:
dojo.event.connect(dojo.flash, "installing", myInstance, "myCallback");
*/
installing: function(){
//dojo.debug("installing");
if(dojo.flash._installingListeners.length > 0){
for(var i = 0; i < dojo.flash._installingListeners.length; i++){
dojo.flash._installingListeners[i].call(null);
}
}
},
/** Initializes dojo.flash. */
_initialize: function(){
//dojo.debug("dojo.flash._initialize");
// see if we need to rev or install Flash on this platform
var installer = new dojo.flash.Install();
dojo.flash.installer = installer;
if(installer.needed() == true){
installer.install();
}else{
//dojo.debug("Writing object out");
// write the flash object into the page
dojo.flash.obj = new dojo.flash.Embed(this._visible);
dojo.flash.obj.write(dojo.flash.info.commVersion);
// initialize the way we do Flash/JavaScript communication
dojo.flash.comm = new dojo.flash.Communicator();
}
}
};
/**
A class that helps us determine whether Flash is available,
it's major and minor versions, and what Flash version features should
be used for Flash/JavaScript communication. Parts of this code
are adapted from the automatic Flash plugin detection code autogenerated
by the Macromedia Flash 8 authoring environment.
An instance of this class can be accessed on dojo.flash.info after
the page is finished loading.
This constructor must be called before the page is finished loading.
*/
dojo.flash.Info = function(){
// Visual basic helper required to detect Flash Player ActiveX control
// version information on Internet Explorer
if(dojo.render.html.ie){
document.writeln('');
}
this._detectVersion();
this._detectCommunicationVersion();
}
dojo.flash.Info.prototype = {
/** The full version string, such as "8r22". */
version: -1,
/**
The major, minor, and revisions of the plugin. For example, if the
plugin is 8r22, then the major version is 8, the minor version is 0,
and the revision is 22.
*/
versionMajor: -1,
versionMinor: -1,
versionRevision: -1,
/** Whether this platform has Flash already installed. */
capable: false,
/**
The major version number for how our Flash and JavaScript communicate.
This can currently be the following values:
6 - We use a combination of the Flash plugin methods, such as SetVariable
and TCallLabel, along with fscommands, to do communication.
8 - We use the ExternalInterface API.
-1 - For some reason neither method is supported, and no communication
is possible.
*/
commVersion: 6,
/** Set if we are in the middle of a Flash installation session. */
installing: false,
/**
Asserts that this environment has the given major, minor, and revision
numbers for the Flash player. Returns true if the player is equal
or above the given version, false otherwise.
Example: To test for Flash Player 7r14:
dojo.flash.info.isVersionOrAbove(7, 0, 14)
*/
isVersionOrAbove: function(reqMajorVer, reqMinorVer, reqVer){
// make the revision a decimal (i.e. transform revision 14 into
// 0.14
reqVer = parseFloat("." + reqVer);
if(this.versionMajor >= reqMajorVer && this.versionMinor >= reqMinorVer
&& this.versionRevision >= reqVer){
return true;
}else{
return false;
}
},
_detectVersion: function(){
var versionStr;
// loop backwards through the versions until we find the newest version
for(var testVersion = 25; testVersion > 0; testVersion--){
if(dojo.render.html.ie){
versionStr = VBGetSwfVer(testVersion);
}else{
versionStr = this._JSFlashInfo(testVersion);
}
if(versionStr == -1 ){
this.capable = false;
return;
}else if(versionStr != 0){
var versionArray;
if(dojo.render.html.ie){
var tempArray = versionStr.split(" ");
var tempString = tempArray[1];
versionArray = tempString.split(",");
}else{
versionArray = versionStr.split(".");
}
this.versionMajor = versionArray[0];
this.versionMinor = versionArray[1];
this.versionRevision = versionArray[2];
// 7.0r24 == 7.24
var versionString = this.versionMajor + "." + this.versionRevision;
this.version = parseFloat(versionString);
this.capable = true;
break;
}
}
},
/**
JavaScript helper required to detect Flash Player PlugIn version
information. Internet Explorer uses a corresponding Visual Basic
version to interact with the Flash ActiveX control.
*/
_JSFlashInfo: function(testVersion){
// NS/Opera version >= 3 check for Flash plugin in plugin array
if(navigator.plugins != null && navigator.plugins.length > 0){
if(navigator.plugins["Shockwave Flash 2.0"] ||
navigator.plugins["Shockwave Flash"]){
var swVer2 = navigator.plugins["Shockwave Flash 2.0"] ? " 2.0" : "";
var flashDescription = navigator.plugins["Shockwave Flash" + swVer2].description;
var descArray = flashDescription.split(" ");
var tempArrayMajor = descArray[2].split(".");
var versionMajor = tempArrayMajor[0];
var versionMinor = tempArrayMajor[1];
if(descArray[3] != ""){
var tempArrayMinor = descArray[3].split("r");
}else{
var tempArrayMinor = descArray[4].split("r");
}
var versionRevision = tempArrayMinor[1] > 0 ? tempArrayMinor[1] : 0;
var version = versionMajor + "." + versionMinor + "."
+ versionRevision;
return version;
}
}
return -1;
},
/**
Detects the mechanisms that should be used for Flash/JavaScript
communication, setting 'commVersion' to either 6 or 8. If the value is
6, we use Flash Plugin 6+ features, such as GetVariable, TCallLabel,
and fscommand, to do Flash/JavaScript communication; if the value is
8, we use the ExternalInterface API for communication.
*/
_detectCommunicationVersion: function(){
if(this.capable == false){
this.commVersion = null;
return;
}
// detect if the user has over-ridden the default flash version
if (typeof djConfig["forceFlashComm"] != "undefined" &&
typeof djConfig["forceFlashComm"] != null){
this.commVersion = djConfig["forceFlashComm"];
return;
}
// we prefer Flash 6 features over Flash 8, because they are much faster
// and much less buggy
// at this point, we don't have a flash file to detect features on,
// so we need to instead look at the browser environment we are in
if(dojo.render.html.safari == true || dojo.render.html.opera == true){
this.commVersion = 8;
}else{
this.commVersion = 6;
}
}
};
/** A class that is used to write out the Flash object into the page. */
dojo.flash.Embed = function(visible){
this._visible = visible;
}
dojo.flash.Embed.prototype = {
/**
The width of this Flash applet. The default is the minimal width
necessary to show the Flash settings dialog.
*/
width: 215,
/**
The height of this Flash applet. The default is the minimal height
necessary to show the Flash settings dialog.
*/
height: 138,
/** The id of the Flash object. */
id: "flashObject",
/** Controls whether this is a visible Flash applet or not. */
_visible: true,
/**
Writes the Flash into the page. This must be called before the page
is finished loading.
@param flashVer The Flash version to write.
@param doExpressInstall Whether to write out Express Install
information. Optional value; defaults to false.
*/
write: function(flashVer, doExpressInstall){
//dojo.debug("write");
if(dojo.lang.isUndefined(doExpressInstall)){
doExpressInstall = false;
}
// determine our container div's styling
var containerStyle = new dojo.string.Builder();
containerStyle.append("width: " + this.width + "px; ");
containerStyle.append("height: " + this.height + "px; ");
if(this._visible == false){
containerStyle.append("position: absolute; ");
containerStyle.append("z-index: 10000; ");
containerStyle.append("top: -1000px; ");
containerStyle.append("left: -1000px; ");
}
containerStyle = containerStyle.toString();
// figure out the SWF file to get and how to write out the correct HTML
// for this Flash version
var objectHTML;
var swfloc;
// Flash 6
if(flashVer == 6){
swfloc = dojo.flash.flash6_version;
var dojoPath = djConfig.baseRelativePath;
swfloc = swfloc + "?baseRelativePath=" + escape(dojoPath);
objectHTML =
'