Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Add AMD support to Flash event dispatch #111

Closed
wants to merge 4 commits into from

5 participants

@chrisbreiding

This adds support for AMD on the Flash side, fixing issue #101. It also adds documentation detailing some of the consequences. Please see issue #101 for more information.

@CMTegner

This is a pretty long line. I suggest breaking it up over multiple lines to aid readability.

Do you have a preference between these 2 options, or perhaps a better way to break up the long string?

Option 1:

private var wrappedDispatch:String = '(function (event, args) {';
                  wrappedDispatch +=   'require(["ZeroClipboard"], function (ZeroClipboard) {';
                  wrappedDispatch +=     'ZeroClipboard.dispatch(event, args);';
                  wrappedDispatch +=   '});';
                  wrappedDispatch += '})';

Option 2:

private var wrappedDispatch:String = ( <![CDATA[
  (function (event, args) {
    require(["ZeroClipboard"], function (ZeroClipboard) {
      ZeroClipboard.dispatch(event, args);
    });
  })
]]> ).toString();

Then:

private var externalDispatch:String = isAMD() ? wrappedDispatch : 'ZeroClipboard.dispatch';

After some testing it looks like option 1 would actually have to be wrapped in a function itself, so the options would be:

Option 1:

private function wrappedDispatch(): String {
  var wrappedDispatch:String = '(function (event, args) {';
            wrappedDispatch +=   'require(["ZeroClipboard"], function (ZeroClipboard) {';
            wrappedDispatch +=     'ZeroClipboard.dispatch(event, args);';
            wrappedDispatch +=   '});';
            wrappedDispatch += '})';
  return wrappedDispatch;
}

private var externalDispatch:String = isAMD() ? wrappedDispatch() : 'ZeroClipboard.dispatch';

Option 2:

private var wrappedDispatch:String = ( <![CDATA[
  (function (event, args) {
    require(["ZeroClipboard"], function (ZeroClipboard) {
      ZeroClipboard.dispatch(event, args);
    });
  })
]]> ).toString();

private var externalDispatch:String = isAMD() ? wrappedDispatch : 'ZeroClipboard.dispatch';

Even though I'm not a huge fan of CDATA sections, I think option #2 looks cleaner.

@JamesMGreene

Hey @jrburke @unscriptable, could you guys offer an AMD consult?

Please take a quick peek at this PR and its related issue (#101) and let me know if there are any other options we could explore for AMD users. I'm never wild about forcing module names on consumers but forcing them to tack an object onto the global object is also unfortunate.

@JamesMGreene

What about having AMD users set an additional config value (i.e. amdModuleName) on the ZeroClipboard object that would be passed into the Flash object? That way we could optionally avoid the need to check for the existence of AMD from Flash (although your check is very clever!) as we can put that burden on the users. More importantly, the users can keep their module names as whatever they want, so long as their ZeroClipboard module configuration has some name. Then in the ActionScript changes for this PR, we would just update Line 209 to utilize the passed value for amdModuleName instead of hard-coding it to "ZeroClipboard".

Example usage for an AMD user:

require(['ZeroClipboardOrWhatever'], function(ZeroClipboard) {
    ZeroClipboard.setDefaults({
        moviePath: "lib/ZeroClipboard.swf",
        amdModuleName: "ZeroClipboardOrWhatever"
    });
    var clip = new ZeroClipboard(document.getElementById('copy-button'));
    clip.on('load', function () {
        // yay, this works now!
    });
});

cc: @jrburke @unscriptable — Are there any other AMD config options that might work better here that I'm forgetting...?

docs/instructions.md
@@ -461,6 +461,20 @@ Here is a complete example which exercises every option and event handler:
</html>
```
+## AMD
+
+If using AMD, such as RequireJS, note that the SWF file relies on a hard-coded path of 'ZeroClipboard' defined for ZeroClipboard.js. Either put ZeroClipboard.js in the root of your RequireJS base path or configure the path like so:

It seems this doc section should be included regardless of whether the swf relies on the AMD module's ID or not. This paths config is likely needed unless the ZeroClipboard is placed at baseUrl, irregardless of the swf. Hmmm... I guess some folks would just load the module via its path instead of its ID. Still, I think this section should probably stay in the docs (and not imply that it's needed for the swf) even if we find a way to not hard-code the ID inside the swf.

Also: remove "hard-coded" in the previous sentence. It's confusing, imho, and took me a while to figure out what you were saying. (All config is hard-coded :) .) The "hard-coded" below makes perfect sense to me.

Any interest in mentioning other AMD loaders? :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
src/flash/ZeroClipboard.as
((7 lines not shown))
+ // determines if AMD is being used
+ //
+ // returns boolean
+ private function isAMD(): Boolean {
+ return ExternalInterface.call( '(function () { return typeof define === "function" && define.amd; })');
+ }
+
+ // amdWrappedDispatch
+ //
+ // string javascript function used to call ZeroClipboard.dispatch with AMD
+ //
+ // returns string
+ private function amdWrappedDispatch(): String {
+ return ( <![CDATA[
+ (function (event, args) {
+ require(["ZeroClipboard"], function (ZeroClipboard) {

Two issues here:

  1. Global require. The global require is an optional global in AMD. curl.js, for instance, does not declare it by default (which has eliminated tons of newb confusion). Also: developers can change the name of AMD globals (define, curl, require, etc.).

  2. This is tricky since it is an async call. Isn't there a chance in IE that ZeroClipboard.dispatch() could get called before the async require() returns?

I have no knowledge of how flash/ActionScript and browser/JavaScript interact, but I imagine there's a way to use define instead of require here. Something like this?

// execute this "named module". Note: the name is arbitrary, but should be unique / "namespaced".
define('ZeroClipboard/dispatch-capture', ['ZeroConfig'], function (ZC) { /* is there a way for ActionScript to capture ZC.dispatch here? */ });

This doesn't solve the async issue, but does mostly solve the global naming issue. Devs who want to change the name of define are accustomed to having to change source code, so they won't complain much, I imagine.

Alternatively: could we pass the name of the global define or require into the swf as a parameter???

If we could use @JamesMGreene's parameter-passing solution and add an additional parameter to name the global loader function, that would work, I believe.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@unscriptable

Ah, @JamesMGreene, I saw your suggestion after I commented on the PR. :) I guess my idea of stuffing a parameter into the swf at load time could be also be done using your method.

Btw, I think you mean define instead of require:

define(['ZeroClipboardIDOrFullPath'], function(ZeroClipboard) {
    ZeroClipboard.setDefaults({
        moviePath: "lib/ZeroClipboard.swf",
        amdModuleName: "ZeroClipboardIDOrFullPath",
        amdLoaderName: "curl" // or "requirejs" or "mynamespace.load"
    });
    var clip = new ZeroClipboard(document.getElementById('copy-button'));
    clip.on('load', function () {
        // yay, this works now!
    });
});

From this code snippet, it also seems like the async issue is not a problem. My misunderstanding. Well, that is true if users don't expect the code inside your example "load" handler to execute sync since it will likely execute async.

@chrisbreiding

My latest commits add the ability to configure the AMD path/id and loader function as per the suggestions of @JamesMGreene and @unscriptable. Awesome suggestions, by the way, since they deal with the main drawback of this approach and also add better support for other AMD libraries (I was myopically only considering RequireJS).

This makes amdModuleName a required configuration option if a user is using AMD. By default, the code assumes a global require function, but that can be configured with the amdLoaderName option if curl or another library is being used.

I'm passing the configuration options to the ActionScript through the flashvars. Please advise if there is a better way to do that.

@JamesMGreene

Btw, I think you mean define instead of require:

define(['ZeroClipboardIDOrFullPath'], function(ZeroClipboard) {
ZeroClipboard.setDefaults({
moviePath: "lib/ZeroClipboard.swf",
amdModuleName: "ZeroClipboardIDOrFullPath",
amdLoaderName: "curl" // or "requirejs" or "mynamespace.load"
});
var clip = new ZeroClipboard(document.getElementById('copy-button'));
clip.on('load', function () {
// yay, this works now!
});
});
From this code snippet, it also seems like the async issue is not a problem. My misunderstanding. Well, that is true if users don't expect the code inside your example "load" handler to execute sync since it will likely execute async.

@unscriptable Is there any overhead from generating a new anonymous module with define for every callback? That's why I had used the [non-standard] require instead.

@unscriptable

Hey @chrisbreiding, thanks for mentioning curl.js! <3 <3 Minor request: can you change it to say "curl.js"? We refer to it that way for better googling and differentiation from the Unix utility. :) Thanks -- John

@unscriptable

lgtm! :)

@unscriptable

Is there any overhead from generating a new anonymous module with define for every callback?

Hey @JamesMGreene, I don't understand. All AMD modules use a define(). If a dev is using ZeroClipboard in AMD, they'll be doing it inside a module -- in other words, inside a define(). I just changed your code to look like a module.

The way you wrote it (with require()), it is just executing global code in a callback after ensuring ZeroClipboard is loaded. This is not the right way to write AMD. That's more like LABjs. Devs should be thinking and writing modules (whether AMD or not), not global-ish code. :) IMHO

-- John

@JamesMGreene

@unscriptable I'm pretty AMD savvy so I think we're not on the same page with regard to execution contexts. I'll post more of an execution flow tonight to try to clarify.

@JamesMGreene

AMD Execution Flow for ZeroClipboard

index.html

Import RequireJS (or any other AMD loader) and kick it all off:

<!DOCTYPE html>
<html>
    <head>
        <title>ZeroClipboard AMD Demo</title>
        <!-- data-main attribute tells require.js to load scripts/main.js after require.js loads. -->
        <script data-main="scripts/main" src="scripts/require.js"></script>
    </head>
    <body>
        <h1>Demo, OMG!</h1>
    </body>
</html>

main.js

"main.js" will create some AMD module ("myMainModule") with a dependency on ZeroClipboard (which is exposed as an AMD module if there is an AMD loader present on the page), update the ZC config, create a ZC client instance, and attach an event listener callback:

define('myMainModule', ['lib/zc/ZeroClipboard'], function(ZeroClipboard) {
  ZeroClipboard.setDefaults({
    moviePath: 'lib/zc/ZeroClipboard.swf',
    amdModuleName: 'lib/zc/ZeroClipboard'
  });
  var clip = new ZeroClipboard(document.getElementById('copy-button'));
  clip.on('complete', function(data) {
    // Yay, JS event listener is attached... BUT it's not quite that easy when Flash is involved!
    console.log('Injected into the clipboard: ' + data.text);
  });
});

ZeroClibpoard.swf / ZeroClipboard.as

After the user tries to click on the HTML button layered beneath the associated ZC client instance (a transparent Flash object), the "ZeroClibpoard.swf" will inject developer-configured text data into the user's clipboard. When successfully inserted, the Flash object will then issue an ExternalInterface.call command to tell the JS-side ZC dispatcher (in the non-AMD scenario, the dispatcher is a globally available ZeroClipboard.dispatch method) that the action was successfully completed. Due to the limitations of JS-to-Flash and Flash-to-JS interoperability (and herein lies the original issue), ExternalInterface.call is actually nothing more than a thinly veiled window.eval executed in the global scope... but in an AMD scope, the ZeroClipboard object is not globally available to allow its dispatch method to be invoked via ExternalInterface.call!

This dilemma brings us to where you and I are currently not on the same page [yet]. Now, to make this all hook fluidly together in an AMD application, the Flash object must be able to get in touch with the ZeroClibpoard.dispatch method from the global scope. So, we can either utilize a non-module-creating function like RequireJS's global require but, as you noted, that is not a part of the AMD standard and therefore should be avoided. As the only global function that is required by the AMD standard is define, it seems that we should follow your advice and wrap the script contents of every ExternalInterface.call invocation (originally just a plain ole ZeroClipboard.dispatch) to include that, e.g. ExternalInterface.call (a.k.a. window.eval):

(function(event, args) {
  // Remember: 'lib/zc/ZeroClipboard' == `amdModuleName` FlashVars item from "main.js"
  define(['lib/zc/ZeroClipboard'], function(ZeroClipboard) {
    ZeroClipboard.dispatch(event, args);
  });
})('complete', { text: 'blah' });

This means that, when using AMD, we will be creating a new anonymous module (as seen immediately above) to wrap the event dispatching, which fire every time a user clicks on a ZeroClipboard client object to inject text into their clipboard.

And so, my last question to you (@unscriptable) was:

Is there any overhead from generating a new anonymous module with define for every callback?

Does my question make sense now?

P.S. If we use the global define method, as I think we must to be safe, then we can also drop the amdLoaderName FlashVars item.

@JamesMGreene

@unscriptable Can you please respond when you get a chance? Thanks!

@jrburke Would really love feedback and/or other options/ideas from you on how to make this work as well. Thanks!
~~ James Greene, your JSConf 2012 lunch-time-acquaintance

@unscriptable

Hey @JamesMGreene!

Generating anonymous module on every callback is not a good idea. :) When I said, "Btw, I think you mean define instead of require:", I was referring to your code snippet in this comment which you later corrected in your awesomely detailed later comment.

The solution you and @chrisbreiding have now (configurable loader name with default == "require") looks fine to me!

-- John

@JamesMGreene

Posted a message to the RequireJS mailing list in the hopes of getting feedback from @jrburke's RequireJS camp: https://groups.google.com/d/topic/requirejs/FntaEC55JMg/discussion

@JamesMGreene
Owner

Finally talked to @jrburke (on IRC) and he [unsurprisingly] agrees with @unscriptable's final assessment:

  • Yes, use the global require instead of define.
  • Yes, use a configurable name for AMDjs libs that use a function name other than require, e.g. curl.

Updated Flash callback to emit events

(function(event, args) {
  require(['lib/zc/ZeroClipboard'], function(ZeroClipboard) {
    ZeroClipboard.dispatch(event, args);
  });
})('complete', { text: 'blah' });

Notes:

  • 'lib/zc/ZeroClipboard' == amdModuleName FlashVars item from "main.js"
  • require == amdLoaderName FlashVars item, to be added from "main.js" (as in earlier comments)

Remaining discussion

I would like to come up with a clearer name for the amdLoaderName FlashVars item, though, e.g. amdRequireName, amdRequireFuncName, amdRequireFunctionName, amdGlobalRequireName, amdGlobalRequireFuncName, amdGlobalRequireFunctionName, etc.

The AMDjs spec does refer to this optional function as "the global require function". Although it's technically optional, it's realistically required in one form or another as the function typically serves the role of the AMD loader's global entry point for kicking off the page's module loading chains.

Various AMD loader implementation names:

  • RequireJS: require/requirejs
  • curl.js: curl
  • needs: require
  • lsjs: require/lsjs
  • Inject: require/Inject.require
@cmcculloh

I'd love to implement this into my Backbone/require project... Any more progress on this?

How about just requireLoader? "name" seems sort of redundant (almost like calling a variable fooVar or varFoo). "Require" might be more recognizable to the masses (who don't care/know about AMD theory) but either way requireLoader or amdLoader is immediately comprehensible, "Oh, that must be the var that holds the loader for the require/amd library". Adding "name" to the end doesn't really get you much more comprehension. "loader" implies it is a function, so, no need to add "Func" in there.

Looking forward to seeing this implemented. :)

@JamesMGreene JamesMGreene was assigned
@JamesMGreene
Owner

@cmcculloh: I was proposing tacking the "Name" suffix on as I'm worried users who aren't so familiar with the implementation details here might try to pass the require function rather than its global identifier name (e.g. "require"). I'm OK with omitting the "Name" suffix... I'll just add a typeof check to ensure that the value passed in is a string.

@JamesMGreene
Owner

I'll try to get this implemented in the next few days.

@cmcculloh
@JamesMGreene JamesMGreene referenced this pull request from a commit in JamesMGreene/zeroclipboard
@JamesMGreene JamesMGreene Completing AMD support. Fixes #98. Fixes #99. Closes #111. afe8f4e
@JamesMGreene JamesMGreene referenced this pull request from a commit in JamesMGreene/zeroclipboard
@JamesMGreene JamesMGreene Completing AMD support. Fixes #98. Fixes #99. Closes #111. 7e8a99e
@JamesMGreene JamesMGreene referenced this pull request from a commit in JamesMGreene/zeroclipboard
@JamesMGreene JamesMGreene Completing AMD support. Fixes #98. Fixes #99. Closes #111. 148d0e9
@JamesMGreene JamesMGreene referenced this pull request
Closed

Completing AMD support #145

@JamesMGreene JamesMGreene referenced this pull request from a commit in JamesMGreene/zeroclipboard
@JamesMGreene JamesMGreene Completing AMD support. Fixes #98. Fixes #99. Closes #111. 837fe0c
@JamesMGreene
Owner

Replaced by PR #145.

PR #144 adds an AMD-based demo page, too.

@JamesMGreene JamesMGreene referenced this pull request from a commit in JamesMGreene/zeroclipboard
@JamesMGreene JamesMGreene Completing AMD support. Fixes #98. Fixes #99. Closes #101. Closes #111. 2b6f76e
@JamesMGreene JamesMGreene referenced this pull request from a commit in JamesMGreene/zeroclipboard
@JamesMGreene JamesMGreene Completing AMD support. Fixes #98. Fixes #99. Fixes #101. Closes #111. 88f31ff
@JamesMGreene JamesMGreene referenced this pull request from a commit in JamesMGreene/zeroclipboard
@JamesMGreene JamesMGreene Completing AMD support. Fixes #98. Fixes #99. Fixes #101. Closes #111. 28e0262
@JamesMGreene JamesMGreene referenced this pull request from a commit in JamesMGreene/zeroclipboard
@JamesMGreene JamesMGreene Completing AMD support. Fixes #98. Fixes #99. Fixes #101. Closes #111. 9579e4c
@JamesMGreene JamesMGreene referenced this pull request from a commit in JamesMGreene/zeroclipboard
@JamesMGreene JamesMGreene Completing AMD support. Fixes #98. Fixes #99. Fixes #101. Closes #111. 33f3d66
@JamesMGreene JamesMGreene referenced this pull request from a commit in JamesMGreene/zeroclipboard
@JamesMGreene JamesMGreene Completing AMD support. Fixes #98. Fixes #99. Fixes #101. Closes #111.
Closes #145.
ea5f231
@JamesMGreene
Owner

FYI: as of ZeroClipboard v1.2.0-beta.2, you no longer need to set any special configuration options for AMDJS (or CommonJS) support!

@unscriptable

woohoo!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 3, 2013
  1. @chrisbreiding
Commits on Mar 6, 2013
  1. @chrisbreiding
Commits on Mar 10, 2013
  1. @chrisbreiding
  2. @chrisbreiding

    update AMD documentation

    chrisbreiding authored
This page is out of date. Refresh to see the latest.
View
8 ZeroClipboard.js
@@ -128,6 +128,10 @@
str.push("trustedDomain=" + options.trustedDomains.join(","));
}
}
+ if (options.amdModuleName) {
+ str.push("amdModuleName=" + options.amdModuleName);
+ str.push("amdLoaderName=" + options.amdLoaderName);
+ }
return str.join("&");
};
var _inArray = function(elem, array) {
@@ -187,7 +191,9 @@
text: null,
hoverClass: "zeroclipboard-is-hover",
activeClass: "zeroclipboard-is-active",
- allowScriptAccess: "sameDomain"
+ allowScriptAccess: "sameDomain",
+ amdModuleName: null,
+ amdLoaderName: "require"
};
ZeroClipboard.setDefaults = function(options) {
for (var ko in options) _defaults[ko] = options[ko];
View
2  ZeroClipboard.min.js
@@ -5,4 +5,4 @@
* Released under the MIT license
* http://jonrohan.github.com/ZeroClipboard/
* v1.1.7
- */(function(){"use strict";var a=function(a,b){var c=a.style[b];a.currentStyle?c=a.currentStyle[b]:window.getComputedStyle&&(c=document.defaultView.getComputedStyle(a,null).getPropertyValue(b));if(c=="auto"&&b=="cursor"){var d=["a"];for(var e=0;e<d.length;e++)if(a.tagName.toLowerCase()==d[e])return"pointer"}return c},b=function(a){if(!l.prototype._singleton)return;a||(a=window.event);var b;this!==window?b=this:a.target?b=a.target:a.srcElement&&(b=a.srcElement),l.prototype._singleton.setCurrent(b)},c=function(a,b,c){a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent&&a.attachEvent("on"+b,c)},d=function(a,b,c){a.removeEventListener?a.removeEventListener(b,c,!1):a.detachEvent&&a.detachEvent("on"+b,c)},e=function(a,b){if(a.addClass)return a.addClass(b),a;if(b&&typeof b=="string"){var c=(b||"").split(/\s+/);if(a.nodeType===1)if(!a.className)a.className=b;else{var d=" "+a.className+" ",e=a.className;for(var f=0,g=c.length;f<g;f++)d.indexOf(" "+c[f]+" ")<0&&(e+=" "+c[f]);a.className=e.replace(/^\s+|\s+$/g,"")}}return a},f=function(a,b){if(a.removeClass)return a.removeClass(b),a;if(b&&typeof b=="string"||b===undefined){var c=(b||"").split(/\s+/);if(a.nodeType===1&&a.className)if(b){var d=(" "+a.className+" ").replace(/[\n\t]/g," ");for(var e=0,f=c.length;e<f;e++)d=d.replace(" "+c[e]+" "," ");a.className=d.replace(/^\s+|\s+$/g,"")}else a.className=""}return a},g=function(b){var c={left:0,top:0,width:b.width||b.offsetWidth||0,height:b.height||b.offsetHeight||0,zIndex:9999},d=a(b,"zIndex");d&&d!="auto"&&(c.zIndex=parseInt(d,10));while(b){var e=parseInt(a(b,"borderLeftWidth"),10),f=parseInt(a(b,"borderTopWidth"),10);c.left+=isNaN(b.offsetLeft)?0:b.offsetLeft,c.left+=isNaN(e)?0:e,c.top+=isNaN(b.offsetTop)?0:b.offsetTop,c.top+=isNaN(f)?0:f,b=b.offsetParent}return c},h=function(a){return(a.indexOf("?")>=0?"&":"?")+"nocache="+(new Date).getTime()},i=function(a){var b=[];return a.trustedDomains&&(typeof a.trustedDomains=="string"?b.push("trustedDomain="+a.trustedDomains):b.push("trustedDomain="+a.trustedDomains.join(","))),b.join("&")},j=function(a,b){if(b.indexOf)return b.indexOf(a);for(var c=0,d=b.length;c<d;c++)if(b[c]===a)return c;return-1},k=function(a){if(typeof a=="string")throw new TypeError("ZeroClipboard doesn't accept query strings.");return a.length?a:[a]},l=function(a,b){a&&(l.prototype._singleton||this).glue(a);if(l.prototype._singleton)return l.prototype._singleton;l.prototype._singleton=this,this.options={};for(var c in o)this.options[c]=o[c];for(var d in b)this.options[d]=b[d];this.handlers={},l.detectFlashSupport()&&p()},m,n=[];l.prototype.setCurrent=function(b){m=b,this.reposition(),b.getAttribute("title")&&this.setTitle(b.getAttribute("title")),this.setHandCursor(a(b,"cursor")=="pointer")},l.prototype.setText=function(a){a&&a!==""&&(this.options.text=a,this.ready()&&this.flashBridge.setText(a))},l.prototype.setTitle=function(a){a&&a!==""&&this.htmlBridge.setAttribute("title",a)},l.prototype.setSize=function(a,b){this.ready()&&this.flashBridge.setSize(a,b)},l.prototype.setHandCursor=function(a){this.ready()&&this.flashBridge.setHandCursor(a)},l.version="1.1.7";var o={moviePath:"ZeroClipboard.swf",trustedDomains:null,text:null,hoverClass:"zeroclipboard-is-hover",activeClass:"zeroclipboard-is-active",allowScriptAccess:"sameDomain"};l.setDefaults=function(a){for(var b in a)o[b]=a[b]},l.destroy=function(){l.prototype._singleton.unglue(n);var a=l.prototype._singleton.htmlBridge;a.parentNode.removeChild(a),delete l.prototype._singleton},l.detectFlashSupport=function(){var a=!1;try{new ActiveXObject("ShockwaveFlash.ShockwaveFlash")&&(a=!0)}catch(b){navigator.mimeTypes["application/x-shockwave-flash"]&&(a=!0)}return a};var p=function(){var a=l.prototype._singleton,b=document.getElementById("global-zeroclipboard-html-bridge");if(!b){var c=' <object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" id="global-zeroclipboard-flash-bridge" width="100%" height="100%"> <param name="movie" value="'+a.options.moviePath+h(a.options.moviePath)+'"/> <param name="allowScriptAccess" value="'+a.options.allowScriptAccess+'"/> <param name="scale" value="exactfit"/> <param name="loop" value="false"/> <param name="menu" value="false"/> <param name="quality" value="best" /> <param name="bgcolor" value="#ffffff"/> <param name="wmode" value="transparent"/> <param name="flashvars" value="'+i(a.options)+'"/> <embed src="'+a.options.moviePath+h(a.options.moviePath)+'" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="100%" height="100%" name="global-zeroclipboard-flash-bridge" allowScriptAccess="always" allowFullScreen="false" type="application/x-shockwave-flash" wmode="transparent" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="'+i(a.options)+'" scale="exactfit"> </embed> </object>';b=document.createElement("div"),b.id="global-zeroclipboard-html-bridge",b.setAttribute("class","global-zeroclipboard-container"),b.setAttribute("data-clipboard-ready",!1),b.style.position="absolute",b.style.left="-9999px",b.style.top="-9999px",b.style.width="15px",b.style.height="15px",b.style.zIndex="9999",b.innerHTML=c,document.body.appendChild(b)}a.htmlBridge=b,a.flashBridge=document["global-zeroclipboard-flash-bridge"]||b.children[0].lastElementChild};l.prototype.resetBridge=function(){this.htmlBridge.style.left="-9999px",this.htmlBridge.style.top="-9999px",this.htmlBridge.removeAttribute("title"),this.htmlBridge.removeAttribute("data-clipboard-text"),f(m,this.options.activeClass),m=null,this.options.text=null},l.prototype.ready=function(){var a=this.htmlBridge.getAttribute("data-clipboard-ready");return a==="true"||a===!0},l.prototype.reposition=function(){if(!m)return!1;var a=g(m);this.htmlBridge.style.top=a.top+"px",this.htmlBridge.style.left=a.left+"px",this.htmlBridge.style.width=a.width+"px",this.htmlBridge.style.height=a.height+"px",this.htmlBridge.style.zIndex=a.zIndex+1,this.setSize(a.width,a.height)},l.dispatch=function(a,b){l.prototype._singleton.receiveEvent(a,b)},l.prototype.on=function(a,b){var c=a.toString().split(/\s/g);for(var d=0;d<c.length;d++)a=c[d].toLowerCase().replace(/^on/,""),this.handlers[a]||(this.handlers[a]=b);this.handlers.noflash&&!l.detectFlashSupport()&&this.receiveEvent("onNoFlash",null)},l.prototype.addEventListener=l.prototype.on,l.prototype.off=function(a,b){var c=a.toString().split(/\s/g);for(var d=0;d<c.length;d++){a=c[d].toLowerCase().replace(/^on/,"");for(var e in this.handlers)e===a&&this.handlers[e]===b&&delete this.handlers[e]}},l.prototype.removeEventListener=l.prototype.off,l.prototype.receiveEvent=function(a,b){a=a.toString().toLowerCase().replace(/^on/,"");var c=m;switch(a){case"load":if(b&&parseFloat(b.flashVersion.replace(",",".").replace(/[^0-9\.]/gi,""))<10){this.receiveEvent("onWrongFlash",{flashVersion:b.flashVersion});return}this.htmlBridge.setAttribute("data-clipboard-ready",!0);break;case"mouseover":e(c,this.options.hoverClass);break;case"mouseout":f(c,this.options.hoverClass),this.resetBridge();break;case"mousedown":e(c,this.options.activeClass);break;case"mouseup":f(c,this.options.activeClass);break;case"datarequested":var d=c.getAttribute("data-clipboard-target"),g=d?document.getElementById(d):null;if(g){var h=g.value||g.textContent||g.innerText;h&&this.setText(h)}else{var i=c.getAttribute("data-clipboard-text");i&&this.setText(i)}break;case"complete":this.options.text=null}if(this.handlers[a]){var j=this.handlers[a];typeof j=="function"?j.call(c,this,b):typeof j=="string"&&window[j].call(c,this,b)}},l.prototype.glue=function(a){a=k(a);for(var d=0;d<a.length;d++)j(a[d],n)==-1&&(n.push(a[d]),c(a[d],"mouseover",b))},l.prototype.unglue=function(a){a=k(a);for(var c=0;c<a.length;c++){d(a[c],"mouseover",b);var e=j(a[c],n);e!=-1&&n.splice(e,1)}},typeof module!="undefined"?module.exports=l:typeof define=="function"&&define.amd?define(function(){return l}):window.ZeroClipboard=l})();
+ */(function(){"use strict";var a=function(a,b){var c=a.style[b];a.currentStyle?c=a.currentStyle[b]:window.getComputedStyle&&(c=document.defaultView.getComputedStyle(a,null).getPropertyValue(b));if(c=="auto"&&b=="cursor"){var d=["a"];for(var e=0;e<d.length;e++)if(a.tagName.toLowerCase()==d[e])return"pointer"}return c},b=function(a){if(!l.prototype._singleton)return;a||(a=window.event);var b;this!==window?b=this:a.target?b=a.target:a.srcElement&&(b=a.srcElement),l.prototype._singleton.setCurrent(b)},c=function(a,b,c){a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent&&a.attachEvent("on"+b,c)},d=function(a,b,c){a.removeEventListener?a.removeEventListener(b,c,!1):a.detachEvent&&a.detachEvent("on"+b,c)},e=function(a,b){if(a.addClass)return a.addClass(b),a;if(b&&typeof b=="string"){var c=(b||"").split(/\s+/);if(a.nodeType===1)if(!a.className)a.className=b;else{var d=" "+a.className+" ",e=a.className;for(var f=0,g=c.length;f<g;f++)d.indexOf(" "+c[f]+" ")<0&&(e+=" "+c[f]);a.className=e.replace(/^\s+|\s+$/g,"")}}return a},f=function(a,b){if(a.removeClass)return a.removeClass(b),a;if(b&&typeof b=="string"||b===undefined){var c=(b||"").split(/\s+/);if(a.nodeType===1&&a.className)if(b){var d=(" "+a.className+" ").replace(/[\n\t]/g," ");for(var e=0,f=c.length;e<f;e++)d=d.replace(" "+c[e]+" "," ");a.className=d.replace(/^\s+|\s+$/g,"")}else a.className=""}return a},g=function(b){var c={left:0,top:0,width:b.width||b.offsetWidth||0,height:b.height||b.offsetHeight||0,zIndex:9999},d=a(b,"zIndex");d&&d!="auto"&&(c.zIndex=parseInt(d,10));while(b){var e=parseInt(a(b,"borderLeftWidth"),10),f=parseInt(a(b,"borderTopWidth"),10);c.left+=isNaN(b.offsetLeft)?0:b.offsetLeft,c.left+=isNaN(e)?0:e,c.top+=isNaN(b.offsetTop)?0:b.offsetTop,c.top+=isNaN(f)?0:f,b=b.offsetParent}return c},h=function(a){return(a.indexOf("?")>=0?"&":"?")+"nocache="+(new Date).getTime()},i=function(a){var b=[];return a.trustedDomains&&(typeof a.trustedDomains=="string"?b.push("trustedDomain="+a.trustedDomains):b.push("trustedDomain="+a.trustedDomains.join(","))),a.amdModuleName&&(b.push("amdModuleName="+a.amdModuleName),b.push("amdLoaderName="+a.amdLoaderName)),b.join("&")},j=function(a,b){if(b.indexOf)return b.indexOf(a);for(var c=0,d=b.length;c<d;c++)if(b[c]===a)return c;return-1},k=function(a){if(typeof a=="string")throw new TypeError("ZeroClipboard doesn't accept query strings.");return a.length?a:[a]},l=function(a,b){a&&(l.prototype._singleton||this).glue(a);if(l.prototype._singleton)return l.prototype._singleton;l.prototype._singleton=this,this.options={};for(var c in o)this.options[c]=o[c];for(var d in b)this.options[d]=b[d];this.handlers={},l.detectFlashSupport()&&p()},m,n=[];l.prototype.setCurrent=function(b){m=b,this.reposition(),b.getAttribute("title")&&this.setTitle(b.getAttribute("title")),this.setHandCursor(a(b,"cursor")=="pointer")},l.prototype.setText=function(a){a&&a!==""&&(this.options.text=a,this.ready()&&this.flashBridge.setText(a))},l.prototype.setTitle=function(a){a&&a!==""&&this.htmlBridge.setAttribute("title",a)},l.prototype.setSize=function(a,b){this.ready()&&this.flashBridge.setSize(a,b)},l.prototype.setHandCursor=function(a){this.ready()&&this.flashBridge.setHandCursor(a)},l.version="1.1.7";var o={moviePath:"ZeroClipboard.swf",trustedDomains:null,text:null,hoverClass:"zeroclipboard-is-hover",activeClass:"zeroclipboard-is-active",allowScriptAccess:"sameDomain",amdModuleName:null,amdLoaderName:"require"};l.setDefaults=function(a){for(var b in a)o[b]=a[b]},l.destroy=function(){l.prototype._singleton.unglue(n);var a=l.prototype._singleton.htmlBridge;a.parentNode.removeChild(a),delete l.prototype._singleton},l.detectFlashSupport=function(){var a=!1;try{new ActiveXObject("ShockwaveFlash.ShockwaveFlash")&&(a=!0)}catch(b){navigator.mimeTypes["application/x-shockwave-flash"]&&(a=!0)}return a};var p=function(){var a=l.prototype._singleton,b=document.getElementById("global-zeroclipboard-html-bridge");if(!b){var c=' <object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" id="global-zeroclipboard-flash-bridge" width="100%" height="100%"> <param name="movie" value="'+a.options.moviePath+h(a.options.moviePath)+'"/> <param name="allowScriptAccess" value="'+a.options.allowScriptAccess+'"/> <param name="scale" value="exactfit"/> <param name="loop" value="false"/> <param name="menu" value="false"/> <param name="quality" value="best" /> <param name="bgcolor" value="#ffffff"/> <param name="wmode" value="transparent"/> <param name="flashvars" value="'+i(a.options)+'"/> <embed src="'+a.options.moviePath+h(a.options.moviePath)+'" loop="false" menu="false" quality="best" bgcolor="#ffffff" width="100%" height="100%" name="global-zeroclipboard-flash-bridge" allowScriptAccess="always" allowFullScreen="false" type="application/x-shockwave-flash" wmode="transparent" pluginspage="http://www.macromedia.com/go/getflashplayer" flashvars="'+i(a.options)+'" scale="exactfit"> </embed> </object>';b=document.createElement("div"),b.id="global-zeroclipboard-html-bridge",b.setAttribute("class","global-zeroclipboard-container"),b.setAttribute("data-clipboard-ready",!1),b.style.position="absolute",b.style.left="-9999px",b.style.top="-9999px",b.style.width="15px",b.style.height="15px",b.style.zIndex="9999",b.innerHTML=c,document.body.appendChild(b)}a.htmlBridge=b,a.flashBridge=document["global-zeroclipboard-flash-bridge"]||b.children[0].lastElementChild};l.prototype.resetBridge=function(){this.htmlBridge.style.left="-9999px",this.htmlBridge.style.top="-9999px",this.htmlBridge.removeAttribute("title"),this.htmlBridge.removeAttribute("data-clipboard-text"),f(m,this.options.activeClass),m=null,this.options.text=null},l.prototype.ready=function(){var a=this.htmlBridge.getAttribute("data-clipboard-ready");return a==="true"||a===!0},l.prototype.reposition=function(){if(!m)return!1;var a=g(m);this.htmlBridge.style.top=a.top+"px",this.htmlBridge.style.left=a.left+"px",this.htmlBridge.style.width=a.width+"px",this.htmlBridge.style.height=a.height+"px",this.htmlBridge.style.zIndex=a.zIndex+1,this.setSize(a.width,a.height)},l.dispatch=function(a,b){l.prototype._singleton.receiveEvent(a,b)},l.prototype.on=function(a,b){var c=a.toString().split(/\s/g);for(var d=0;d<c.length;d++)a=c[d].toLowerCase().replace(/^on/,""),this.handlers[a]||(this.handlers[a]=b);this.handlers.noflash&&!l.detectFlashSupport()&&this.receiveEvent("onNoFlash",null)},l.prototype.addEventListener=l.prototype.on,l.prototype.off=function(a,b){var c=a.toString().split(/\s/g);for(var d=0;d<c.length;d++){a=c[d].toLowerCase().replace(/^on/,"");for(var e in this.handlers)e===a&&this.handlers[e]===b&&delete this.handlers[e]}},l.prototype.removeEventListener=l.prototype.off,l.prototype.receiveEvent=function(a,b){a=a.toString().toLowerCase().replace(/^on/,"");var c=m;switch(a){case"load":if(b&&parseFloat(b.flashVersion.replace(",",".").replace(/[^0-9\.]/gi,""))<10){this.receiveEvent("onWrongFlash",{flashVersion:b.flashVersion});return}this.htmlBridge.setAttribute("data-clipboard-ready",!0);break;case"mouseover":e(c,this.options.hoverClass);break;case"mouseout":f(c,this.options.hoverClass),this.resetBridge();break;case"mousedown":e(c,this.options.activeClass);break;case"mouseup":f(c,this.options.activeClass);break;case"datarequested":var d=c.getAttribute("data-clipboard-target"),g=d?document.getElementById(d):null;if(g){var h=g.value||g.textContent||g.innerText;h&&this.setText(h)}else{var i=c.getAttribute("data-clipboard-text");i&&this.setText(i)}break;case"complete":this.options.text=null}if(this.handlers[a]){var j=this.handlers[a];typeof j=="function"?j.call(c,this,b):typeof j=="string"&&window[j].call(c,this,b)}},l.prototype.glue=function(a){a=k(a);for(var d=0;d<a.length;d++)j(a[d],n)==-1&&(n.push(a[d]),c(a[d],"mouseover",b))},l.prototype.unglue=function(a){a=k(a);for(var c=0;c<a.length;c++){d(a[c],"mouseover",b);var e=j(a[c],n);e!=-1&&n.splice(e,1)}},typeof module!="undefined"?module.exports=l:typeof define=="function"&&define.amd?define(function(){return l}):window.ZeroClipboard=l})();
View
BIN  ZeroClipboard.swf
Binary file not shown
View
27 docs/instructions.md
@@ -50,7 +50,9 @@ var _defaults = {
trustedDomains: undefined, // Domains that we should trust (single string or array of strings)
hoverClass: "zeroclipboard-is-hover", // The class used to hover over the object
activeClass: "zeroclipboard-is-active", // The class used to set object active
- allowScriptAccess: "sameDomain" // SWF outbound scripting policy
+ allowScriptAccess: "sameDomain", // SWF outbound scripting policy
+ amdModuleName: null, // AMD module ID or path
+ amdLoaderName: "require" // AMD loader function name
};
```
You can override the defaults using `ZeroClipboard.setDefaults({ moviePath: "new/path" })` before you create any clients.
@@ -461,6 +463,29 @@ Here is a complete example which exercises every option and event handler:
</html>
```
+## AMD
+
+If using AMD, with a library such as RequireJS or curl, you must configure ZeroClipboard with the `amdModuleName` set to the path of the ZeroClipboard JavaScript file. For example:
+
+```
+define(['path/to/zero-clipboard'], function (ZeroClipboard) {
+ ZeroClipboard.setDefaults({
+ amdModuleName: 'path/to/zero-clipboard'
+ });
+});
+```
+
+By default, ZeroClipboard expects a global function `require` to exist, but if you are using an AMD library that uses a different global for requiring files, you can set it with the `amdLoaderName` option, like so:
+
+```
+define(['path/to/zero-clipboard'], function (ZeroClipboard) {
+ ZeroClipboard.setDefaults({
+ amdModuleName: 'path/to/zero-clipboard',
+ amdLoaderName: 'curl'
+ });
+});
+```
+
## Browser Support
Works in IE8+. Works in IE7 but requires Sizzle/jQuery. (And of course works in all of the other browsers.)
View
34 src/flash/ZeroClipboard.as
@@ -22,6 +22,9 @@ package {
// The text in the clipboard
private var clipText:String = "";
+ // function through which JavaScript events are dispatched
+ private var externalDispatch:String = 'ZeroClipboard.dispatch';
+
// constructor, setup event listeners and external interfaces
public function ZeroClipboard() {
@@ -37,6 +40,10 @@ package {
flash.system.Security.allowDomain(flashvars.trustedDomain.split("\\").join("\\\\"));
}
+ if (flashvars.amdModuleName) {
+ externalDispatch = amdWrappedDispatch(flashvars.amdModuleName, flashvars.amdLoaderName);
+ }
+
// invisible button covers entire stage
button = new Sprite();
button.buttonMode = true;
@@ -59,7 +66,7 @@ package {
ExternalInterface.addCallback("setSize", setSize);
// signal to the browser that we are ready
- ExternalInterface.call( 'ZeroClipboard.dispatch', 'load', metaData());
+ ExternalInterface.call( externalDispatch, 'load', metaData());
}
// mouseClick
@@ -77,7 +84,7 @@ package {
flash.system.System.setClipboard(clipText);
// signal to the page it is done
- ExternalInterface.call( 'ZeroClipboard.dispatch', 'complete', metaData(event, {
+ ExternalInterface.call( externalDispatch, 'complete', metaData(event, {
text: clipText.split("\\").join("\\\\")
}));
@@ -91,7 +98,7 @@ package {
//
// returns nothing
private function mouseOver(event:MouseEvent): void {
- ExternalInterface.call( 'ZeroClipboard.dispatch', 'mouseOver', metaData(event) );
+ ExternalInterface.call( externalDispatch, 'mouseOver', metaData(event) );
}
// mouseOut
@@ -100,7 +107,7 @@ package {
//
// returns nothing
private function mouseOut(event:MouseEvent): void {
- ExternalInterface.call( 'ZeroClipboard.dispatch', 'mouseOut', metaData(event) );
+ ExternalInterface.call( externalDispatch, 'mouseOut', metaData(event) );
}
// mouseDown
@@ -109,13 +116,13 @@ package {
//
// returns nothing
private function mouseDown(event:MouseEvent): void {
- ExternalInterface.call( 'ZeroClipboard.dispatch', 'mouseDown', metaData(event) );
+ ExternalInterface.call( externalDispatch, 'mouseDown', metaData(event) );
// if the clipText hasn't been set
if (!clipText) {
// request data from the page
- ExternalInterface.call( 'ZeroClipboard.dispatch', 'dataRequested', metaData(event) );
+ ExternalInterface.call( externalDispatch, 'dataRequested', metaData(event) );
}
}
@@ -125,7 +132,7 @@ package {
//
// returns nothing
private function mouseUp(event:MouseEvent): void {
- ExternalInterface.call( 'ZeroClipboard.dispatch', 'mouseUp', metaData(event) );
+ ExternalInterface.call( externalDispatch, 'mouseUp', metaData(event) );
}
// metaData
@@ -185,5 +192,18 @@ package {
button.width = width;
button.height = height;
}
+
+ // amdWrappedDispatch
+ //
+ // string javascript function used to call ZeroClipboard.dispatch with AMD
+ //
+ // returns string
+ private function amdWrappedDispatch(amdModuleName:String, amdLoaderName:String): String {
+ return '(function (event, args) {' +
+ amdLoaderName + '(["' + amdModuleName + '"], function (ZeroClipboard) { \
+ ZeroClipboard.dispatch(event, args); \
+ }); \
+ })';
+ }
}
}
View
4 src/javascript/ZeroClipboard/core.js
@@ -6,7 +6,9 @@ var _defaults = {
text: null, // The text to be copied
hoverClass: "zeroclipboard-is-hover", // The class used to hover over the object
activeClass: "zeroclipboard-is-active", // The class used to set object active
- allowScriptAccess: "sameDomain" // SWF outbound scripting policy
+ allowScriptAccess: "sameDomain", // SWF outbound scripting policy
+ amdModuleName: null, // AMD module ID or path
+ amdLoaderName: "require" // AMD loader function name
};
/*
View
6 src/javascript/ZeroClipboard/utils.js
@@ -209,6 +209,12 @@ var _vars = function (options) {
}
}
+ // if amdModuleName is set
+ if (options.amdModuleName) {
+ str.push("amdModuleName=" + options.amdModuleName);
+ str.push("amdLoaderName=" + options.amdLoaderName);
+ }
+
// join the str by &
return str.join("&");
};
Something went wrong with that request. Please try again.