FileUploader.js 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444
  1. define("dojox/form/FileUploader", [
  2. "dojo/_base/kernel",
  3. "dojo/_base/declare",
  4. "dojo/_base/lang",
  5. "dojo/_base/array",
  6. "dojo/_base/connect",
  7. "dojo/_base/window",
  8. "dojo/_base/sniff",
  9. "dojo/query",
  10. "dojo/dom-style",
  11. "dojo/dom-geometry",
  12. "dojo/dom-attr",
  13. "dojo/dom-class",
  14. "dojo/dom-construct",
  15. "dojo/dom-form",
  16. "dojo/_base/config",
  17. "dijit/_base/manager",
  18. "dojo/io/iframe",
  19. "dojo/_base/Color",
  20. "dojo/_base/unload",
  21. "dijit/_Widget",
  22. "dijit/_TemplatedMixin",
  23. "dijit/_Contained",
  24. "dojox/embed/Flash",
  25. "dojox/embed/flashVars",
  26. "dojox/html/styles"
  27. ],function(kernel, declare, lang, array, connect, win, has, query, domStyle, domGeometry, domAttr, domClass, domConstruct, domForm, config, manager, ioIframe, Color, unloadUtils, Widget, TemplatedMixin, Contained, embedFlash, embedFlashVars, htmlStyles){
  28. kernel.deprecated("dojox.form.FileUploader", "Use dojox.form.Uploader", "2.0");
  29. // Usage Notes:
  30. // To center text vertically, use vertical-align:middle;
  31. // which emulates a boxModel button. Using line-height to center text
  32. // can cause height problems in IE6
  33. /*=====
  34. Widget = dijit._Widget;
  35. TemplatedMixin = dijit._TemplatedMixin;
  36. Contained = dijit._Contained;
  37. =====*/
  38. declare("dojox.form.FileUploader", [Widget, TemplatedMixin, Contained], {
  39. // version:
  40. // 1.5 (deprecated)
  41. // summary:
  42. // Handles File Uploading to a server (PHP script included for testing)
  43. //
  44. // FileUploader is now a WIDGET. You do not have to pass a button
  45. // in. Passing a button is still supported until version 1.5 to maintain
  46. // backwards compatibility, but it is not reccomended. Just create your
  47. // uploader like any other widget.
  48. //
  49. // description:
  50. // If the correct version of Flash Player is available (> 9.0) , a SWF
  51. // is used. If Flash Player is not installed or is outdated, a typical
  52. // html fileInput is used. This process can be overridden with
  53. // force:"flash" or force:"html".
  54. //
  55. // FileUploader works with Flash 10.
  56. //
  57. // The button styles are now recreated in Flash, so there is no longer
  58. // using an invisible Flash movie with wmode=transparent. This way the Flash button
  59. // is actually placed inline with the DOM, not floating above it and constantly
  60. // resetting its position. The "Windows Firefox clickable bug" should be fixed (and
  61. // hopefully some Linux problems).
  62. //
  63. // The HTML button is created in a new way and it is now inline as is the
  64. // FLash button. Styling is much easier and more versatile.
  65. //
  66. // Dependencies:
  67. // FileUploader no longer uses FileInput.css. It now uses FileUploader.css
  68. // See requires for JavaScript dependencies.
  69. //
  70. // NEW FEATURES -
  71. // There are a ton of features and fixes in this version.
  72. // Disabled: Can be toggled with widget.attr("disable", true|false)
  73. // Submit: A convenience method has been added for if the uploader is in a form.
  74. // Instead of submitting the form, call uploader.submit(theForm), and the
  75. // Uploader will handle all of the form values and post the data.
  76. // Selected List: If passing the ID of a container, the Uploaders will populate it
  77. // with the selected files.
  78. // Deleting Files: You can now delete pending files.
  79. // Progress Built in: showProgress:true will change the button to a progress
  80. // bar on upload.
  81. // Progress Attach: Passing progressWidgetId will tell the Uploader of a progress
  82. // widget. If the Progress widget is initially hidden, it will change to
  83. // visible and then restored after upload.
  84. // A11Y: The Flash button can be accessed with the TAB key. (The HTML cannot due
  85. // to browser limtations)
  86. // Deferred Uploading: (Flash only) throttles the upload to one file at a time
  87. //
  88. //
  89. // CDN USERS -
  90. // FileUpload now works with the CDN but with limitations. The SWF must
  91. // be from the same domain as the HTML page. 'swfPath' has been exposed
  92. // so that you may link to that file (could of course be the same SWF in
  93. // dojox resource folder). The SWF will *NOT* work from the
  94. // CDN server. This would require a special XML file that would allow
  95. // access to your server, and the logistics to that is impossible.
  96. //
  97. // LIMITATIONS
  98. // - This is not designed to be a part of a form, it contains its own. (See submit())
  99. // - Currently does not in a Dialog box or a Tab where it is not initially visible,
  100. // - The default style inherits font sizes - but a parent container should have a font size
  101. // set somewhere of the results could be inconsistent.
  102. //
  103. // OPERA USERS -
  104. // It works better than the 1.3 version. fileInputs apperantly can't have opacity
  105. // set to zero. The Flash uploader works but files are auto-uploaded. Must be a
  106. // flashVar problem.
  107. //
  108. // Safari Bug note:
  109. // The bug is in the way Safari handles the connection:
  110. // https://bugs.webkit.org/show_bug.cgi?id=5760
  111. // I added this to the virtual host in the Apache conf file, and now it
  112. // works like a charm:
  113. // BrowserMatch Safari nokeepalive
  114. //
  115. swfPath: config.uploaderPath || require.toUrl("dojox/form/resources/fileuploader.swf"),
  116. templateString:'<div><div dojoAttachPoint="progNode"><div dojoAttachPoint="progTextNode"></div></div><div dojoAttachPoint="insideNode" class="uploaderInsideNode"></div></div>',
  117. // uploadUrl: String
  118. // The url targeted for upload. An absolute URL is preferred. Relative URLs are
  119. // changed to absolute.
  120. uploadUrl: "",
  121. //
  122. // isDebug: Boolean
  123. // If true, outputs traces from the SWF to console. What exactly gets passed
  124. // is very relative, and depends upon what traces have been left in the DEFT SWF.
  125. isDebug:false,
  126. //
  127. // devMode: Boolean.
  128. // Re-implemented. devMode increases the logging, adding style tracing from the SWF.
  129. devMode:false,
  130. //
  131. // id: String
  132. // The object id, just like any other widget in Dojo. However, this id
  133. // is also used as a reference for the SWF
  134. // id: "",
  135. //
  136. // baseClass: String
  137. // The name of the class that will style the button in a "normal" state.
  138. // If baseClass is not defined, 'class' will be used.
  139. // NOTE: By default the uploader will be styled like a dijit buttons and
  140. // adhere to the the themes. Tundra, Soria, and Nihilo are supported.
  141. // You can cascade the existing style by using 'class' or 'style'. If you
  142. // overwrite baseClass, you should overwrite the remaing state classes
  143. // that follow) as well.
  144. baseClass:"dojoxUploaderNorm",
  145. //
  146. // hoverClass: String
  147. // The name of the class that will style the button in a "hover" state. A specific
  148. // class should be made to do this. Do not rely on a target like button:hover{...}
  149. hoverClass:"dojoxUploaderHover",
  150. //
  151. // activeClass: String
  152. // The name of the class that will style the button in a "press" state. A specific
  153. // class should be made to do this. Do not rely on a target like button:active{...}
  154. activeClass:"dojoxUploaderActive",
  155. //
  156. // disabledClass: String
  157. // The name of the class that will style the button when its disabled.
  158. disabledClass:"dojoxUploaderDisabled",
  159. //
  160. // force: String
  161. // Use "flash" to always use Flash (and hopefully force the user to download the plugin
  162. // if they don't have it). Use "html" to always use the HTML uploader. An empty string
  163. // (default) will check for the right version of Flash and use HTML if not available.
  164. force:"",
  165. //
  166. // uploaderType: [readonly] String
  167. // Internal. What type of uploader is being used: "flash" or "html"
  168. uploaderType:"",
  169. //
  170. // flashObject: [readonly] dojox.embed.Flash
  171. // The object that creates the SWF embed object. Mostly Internal.
  172. flashObject: null,
  173. //
  174. // flashMovie: [readonly] Function
  175. // The SWF. Mostly Internal.
  176. flashMovie: null,
  177. //
  178. // insideNode: [readonly] HTMLNode
  179. // The div that holds the SWF and form/fileInput
  180. insideNode: null,
  181. //
  182. // deferredUploading: Number (1 - X)
  183. // (Flash only) throttles the upload to a certain amount of files at a time.
  184. // By default, Flash uploads file one at a time to the server, but in parallel.
  185. // Firefox will try to queue all files at once, leading to problems. Set this
  186. // to the amount to upload in parallel at a time.
  187. // Generally, 1 should work fine, but you can experiment with queuing more than
  188. // one at a time.
  189. // This is of course ignored if selectMultipleFiles equals false.
  190. deferredUploading:1,
  191. //
  192. // fileListId: String
  193. // The id of a dom node to be used as a container for the pending file list.
  194. fileListId:"",
  195. //
  196. // uploadOnChange: Boolean
  197. // If true, uploads imediately after a file has been selected. If false,
  198. // waits for upload() to be called.
  199. uploadOnChange: false,
  200. //
  201. // selectMultipleFiles: Boolean
  202. // If true and flash mode, multiple files may be selected from the dialog.
  203. // If html mode, files are not uploaded until upload() is called. The references
  204. // to each file is incremented:uploadedfile0, uploadedfile1, uploadedfile2... etc.
  205. selectMultipleFiles: true,
  206. //
  207. // htmlFieldName: String
  208. // The name of the field of the fileInput that the server is expecting
  209. htmlFieldName:"uploadedfile",
  210. //
  211. // flashFieldName: String
  212. // The name of the field of the flash uploaded files that the server is expecting
  213. flashFieldName:"flashUploadFiles",
  214. //
  215. // fileMask: Array[ Array[Description, FileTypes], Array[...]...]
  216. // (an array, or an array of arrays)
  217. // Restrict file selection to certain file types
  218. // Empty array defaults to "All Files"
  219. // example:
  220. // fileMask = ["Images", "*.jpg;*.jpeg;*.gif;*.png"]
  221. // or
  222. // fileMask = [
  223. // ["Jpeg File", "*.jpg;*.jpeg"],
  224. // ["GIF File", "*.gif"],
  225. // ["PNG File", "*.png"],
  226. // ["All Images", "*.jpg;*.jpeg;*.gif;*.png"],
  227. // ]
  228. // NOTE: MacType is not supported, as it does not work very well.
  229. // fileMask will work on a Mac, but differently than
  230. // Windows.
  231. fileMask: null,
  232. //
  233. // minFlashVersion: Number
  234. // The minimum of version of Flash player to target. 0 would always install Flash, 100
  235. // would never install it. The Flash Player has supported multiple uploads since
  236. // version 8, so it could go as low as that safely.
  237. minFlashVersion:9,
  238. //
  239. // tabIndex: Number|String
  240. // The tab order in the DOM. Only supported by Flash. HTML Uploaders have security
  241. // protection to prevent you from tabbing to the uploader. Stupid.
  242. tabIndex:-1,
  243. //
  244. // showProgress: Boolean
  245. // If true, the button changes to a progress bar during upload.
  246. showProgress:false,
  247. //
  248. // progressMessage: String
  249. // The message shown while the button is changed to a progress bar
  250. progressMessage:"Loading",
  251. //
  252. // progressBackgroundUrl: String|Uri
  253. // The background image to use for the button-progress
  254. progressBackgroundUrl:require.toUrl("dijit/themes/tundra/images/buttonActive.png"),
  255. //
  256. // progressBackgroundColor: String|Number
  257. // The background color to use for the button-progress
  258. progressBackgroundColor:"#ededed",
  259. //
  260. // progressWidgetId:String
  261. // The widget id of a Dijit Progress bar. The Uploader will bind to it and update it
  262. // automatically.
  263. progressWidgetId:"",
  264. //
  265. // skipServerCheck: Boolean
  266. // If true, will not verify that the server was sent the correct format.
  267. // This can be safely set to true. The purpose of the server side check
  268. // is mainly to show the dev if they've implemented the different returns
  269. // correctly.
  270. skipServerCheck:false,
  271. //
  272. // serverTimeout:Number (milliseconds)
  273. // The amount of time given to the uploaded file
  274. // to wait for a server response. After this amount
  275. // of time, the onComplete is fired but with a 'server timeout'
  276. // error in the returned item.
  277. serverTimeout: 5000,
  278. log: function(){
  279. // summary:
  280. // Due to the excessive logging necessary to make this code happen,
  281. // It's easier to turn it on and off here in one place.
  282. // Also helpful if there are multiple uploaders on one page.
  283. if(this.isDebug){
  284. console["log"](Array.prototype.slice.call(arguments).join(" "));
  285. }
  286. },
  287. constructor: function(){
  288. this._subs = [];
  289. },
  290. postMixInProperties: function(){
  291. // internal stuff:
  292. this.fileList = [];
  293. this._cons = [];
  294. this.fileMask = this.fileMask || [];
  295. this.fileInputs = [];
  296. this.fileCount = 0;
  297. this.flashReady = false;
  298. this._disabled = false;
  299. this.force = this.force.toLowerCase(); // Pete FTW.
  300. this.uploaderType = ((embedFlash.available >= this.minFlashVersion || this.force=="flash") && this.force != "html") ? "flash" : "html";
  301. this.deferredUploading = this.deferredUploading===true ? 1 : this.deferredUploading;
  302. this._refNode = this.srcNodeRef;
  303. this.getButtonStyle();
  304. },
  305. startup: function(){
  306. },
  307. postCreate: function(){
  308. this.inherited(arguments);
  309. // internal stuff:
  310. this.setButtonStyle();
  311. var createMethod;
  312. if(this.uploaderType == "flash"){
  313. createMethod = "createFlashUploader";
  314. }else{
  315. this.uploaderType = "html";
  316. createMethod = "createHtmlUploader";
  317. }
  318. this[createMethod]();
  319. if(this.fileListId){
  320. this.connect(dom.byId(this.fileListId), "click", function(evt){
  321. var p = evt.target.parentNode.parentNode.parentNode; // in a table
  322. if(p.id && p.id.indexOf("file_")>-1){
  323. this.removeFile(p.id.split("file_")[1]);
  324. }
  325. });
  326. }
  327. // cleaning up solves memory leak issues in the HTML version
  328. unloadUtils.addOnUnload(this, this.destroy);
  329. },
  330. getHiddenNode: function(/*DomNode*/ node){
  331. // summary:
  332. // Internal.
  333. // If a parent node is styled as display:none,
  334. // returns that node. This node will be temporarilly
  335. // changed to display:block. Note if the node is in
  336. // a widget that has an onShow event, this is
  337. // overridden.
  338. //
  339. if(!node){ return null; }
  340. var hidden = null;
  341. var p = node.parentNode;
  342. while(p && p.tagName.toLowerCase() != "body"){
  343. var d = domStyle.get(p, "display");
  344. if(d == "none"){
  345. hidden = p;
  346. break;
  347. }
  348. p = p.parentNode;
  349. }
  350. return hidden;
  351. },
  352. getButtonStyle: function(){
  353. // summary:
  354. // Internal.
  355. // Get necessary style information from srcRefNode and
  356. // assigned styles
  357. //
  358. // TODO:
  359. // To call this from postCreate....
  360. // could do the style stuff initially, but if hidden they will be bad sizes
  361. // could then redo the sizes
  362. // alt is to create a genuine button and copy THAT instead of how doing now
  363. var refNode = this.srcNodeRef;
  364. this._hiddenNode = this.getHiddenNode(refNode);
  365. if(this._hiddenNode){
  366. domStyle.set(this._hiddenNode, "display", "block");
  367. }
  368. if(!refNode && this.button && this.button.domNode){
  369. // backwards compat for a Dijit button
  370. var isDijitButton = true;
  371. var cls = this.button.domNode.className + " dijitButtonNode";
  372. var txt = this.getText(query(".dijitButtonText", this.button.domNode)[0]);
  373. var domTxt = '<button id="'+this.button.id+'" class="'+cls+'">'+txt+'</button>';
  374. refNode = domConstruct.place(domTxt, this.button.domNode, "after"); /// Pete doesn't like this?
  375. this.srcNodeRef = refNode;
  376. this.button.destroy();
  377. this.baseClass = "dijitButton";
  378. this.hoverClass = "dijitButtonHover";
  379. this.pressClass = "dijitButtonActive";
  380. this.disabledClass = "dijitButtonDisabled";
  381. }else if(!this.srcNodeRef && this.button){
  382. refNode = this.button;
  383. }
  384. if(domAttr.get(refNode, "class")){
  385. this.baseClass += " " + domAttr.get(refNode, "class");
  386. }
  387. domAttr.set(refNode, "class", this.baseClass);
  388. this.norm = this.getStyle(refNode);
  389. this.width = this.norm.w;
  390. this.height = this.norm.h;
  391. if(this.uploaderType == "flash"){
  392. this.over = this.getTempNodeStyle(refNode, this.baseClass+" "+this.hoverClass, isDijitButton);
  393. this.down = this.getTempNodeStyle(refNode, this.baseClass+" "+this.activeClass, isDijitButton);
  394. this.dsbl = this.getTempNodeStyle(refNode, this.baseClass+" "+this.disabledClass, isDijitButton);
  395. this.fhtml = {
  396. cn:this.getText(refNode),
  397. nr:this.norm,
  398. ov:this.over,
  399. dn:this.down,
  400. ds:this.dsbl
  401. };
  402. }else{
  403. this.fhtml = {
  404. cn:this.getText(refNode),
  405. nr:this.norm
  406. }
  407. if(this.norm.va == "middle"){
  408. this.norm.lh = this.norm.h;
  409. }
  410. }
  411. if(this.devMode){
  412. this.log("classes - base:", this.baseClass, " hover:", this.hoverClass, "active:", this.activeClass);
  413. this.log("fhtml:", this.fhtml)
  414. this.log("norm:", this.norm)
  415. this.log("over:", this.over)
  416. this.log("down:", this.down)
  417. }
  418. },
  419. setButtonStyle: function(){
  420. // summary:
  421. // Internal.
  422. // Set up internal dom nodes for button construction.
  423. //
  424. domStyle.set(this.domNode, {
  425. width:this.fhtml.nr.w+"px",
  426. height:(this.fhtml.nr.h)+"px",
  427. padding:"0px",
  428. lineHeight: "normal",
  429. position:"relative"
  430. });
  431. if(this.uploaderType == "html" && this.norm.va == "middle"){
  432. domStyle.set(this.domNode, "lineHeight", this.norm.lh + "px");
  433. }
  434. if(this.showProgress){
  435. this.progTextNode.innerHTML = this.progressMessage;
  436. domStyle.set(this.progTextNode, {
  437. width:this.fhtml.nr.w+"px",
  438. height:(this.fhtml.nr.h+0)+"px",
  439. padding:"0px",
  440. margin:"0px",
  441. left:"0px",
  442. lineHeight:(this.fhtml.nr.h+0)+"px",
  443. position:"absolute"
  444. });
  445. domStyle.set(this.progNode, {
  446. width:this.fhtml.nr.w+"px",
  447. height:(this.fhtml.nr.h+0)+"px",
  448. padding:"0px",
  449. margin:"0px",
  450. left:"0px",
  451. position:"absolute",
  452. display:"none",
  453. backgroundImage:"url("+this.progressBackgroundUrl+")",
  454. backgroundPosition:"bottom",
  455. backgroundRepeat:"repeat-x",
  456. backgroundColor:this.progressBackgroundColor
  457. });
  458. }else{
  459. domConstruct.destroy(this.progNode);
  460. }
  461. domStyle.set(this.insideNode,{
  462. position:"absolute",
  463. top:"0px",
  464. left:"0px",
  465. display:""
  466. });
  467. domClass.add(this.domNode, this.srcNodeRef.className);
  468. if(this.fhtml.nr.d.indexOf("inline")>-1){
  469. domClass.add(this.domNode, "dijitInline");
  470. }
  471. try{
  472. this.insideNode.innerHTML = this.fhtml.cn;
  473. }catch(e){
  474. // You have got to be kidding me. IE does us he favor of checking that
  475. // we aren't inserting the improper type of content with innerHTML into
  476. // an inline element. Alert us with an "Unknown Runtime Error". You can't
  477. // MAKE this stuff up.
  478. //
  479. if(this.uploaderType == "flash"){
  480. this.insideNode = this.insideNode.parentNode.removeChild(this.insideNode);
  481. win.body().appendChild(this.insideNode);
  482. this.insideNode.innerHTML = this.fhtml.cn;
  483. var c = connect.connect(this, "onReady", this, function(){ connect.disconnect(c);
  484. this.insideNode = this.insideNode.parentNode.removeChild(this.insideNode);
  485. this.domNode.appendChild(this.insideNode);
  486. });
  487. }else{
  488. this.insideNode.appendChild(document.createTextNode(this.fhtml.cn));
  489. }
  490. }
  491. if(this._hiddenNode){
  492. domStyle.set(this._hiddenNode, "display", "none");
  493. }
  494. },
  495. /*************************
  496. * Public Events *
  497. *************************/
  498. // The following events are inherited from _Widget and still may be connected:
  499. // onClick
  500. // onMouseUp
  501. // onMouseDown
  502. // onMouseOver
  503. // onMouseOut
  504. onChange: function(dataArray){
  505. // summary:
  506. // stub to connect
  507. // Fires when files are selected
  508. // Event is an array of last files selected
  509. },
  510. onProgress: function(dataArray){
  511. // summary:
  512. // Stub to connect
  513. // Fires as progress returns from SWF
  514. // Event is an array of all files uploading
  515. // Can be connected to for HTML uploader,
  516. // but will not return anything.
  517. },
  518. onComplete: function(dataArray){
  519. // summary:
  520. // stub to connect
  521. // Fires when all files have uploaded
  522. // Event is an array of all files
  523. },
  524. onCancel: function(){
  525. // summary:
  526. // Stub to connect
  527. // Fires when dialog box has been closed
  528. // without a file selection
  529. },
  530. onError: function(/* Object or String */evtObject){
  531. // summary:
  532. // Fires on errors
  533. //
  534. //FIXME: Unsure of a standard form for receiving errors
  535. },
  536. onReady: function(/* dojox.form.FileUploader */ uploader){
  537. // summary:
  538. // Stub - Fired when embedFlash has created the
  539. // Flash object, but it has not necessarilly finished
  540. // downloading, and is ready to be communicated with.
  541. },
  542. onLoad: function(/* dojox.form.FileUploader */ uploader){
  543. // summary:
  544. // Stub - SWF has been downloaded 100%.
  545. },
  546. /*************************
  547. * Public Methods *
  548. *************************/
  549. submit: function(/* form node ? */form){
  550. // summary:
  551. // If FileUploader is in a form, and other data should be sent
  552. // along with the files, use this instead of form submit.
  553. //
  554. var data = form ? domForm.toObject(form) : null;
  555. this.upload(data);
  556. return false; // Boolean
  557. },
  558. upload: function(/*Object ? */data){
  559. // summary:
  560. // When called, begins file upload
  561. // data: Object
  562. // postData to be sent to server
  563. //
  564. if(!this.fileList.length){
  565. return false;
  566. }
  567. if(!this.uploadUrl){
  568. console.warn("uploadUrl not provided. Aborting.");
  569. return false;
  570. }
  571. if(!this.showProgress){
  572. this.set("disabled", true);
  573. }
  574. if(this.progressWidgetId){
  575. var node = manager.byId(this.progressWidgetId).domNode;
  576. if(domStyle.get(node, "display") == "none"){
  577. this.restoreProgDisplay = "none";
  578. domStyle.set(node, "display", "block");
  579. }
  580. if(domStyle.get(node, "visibility") == "hidden"){
  581. this.restoreProgDisplay = "hidden";
  582. domStyle.set(node, "visibility", "visible");
  583. }
  584. }
  585. if(data && !data.target){
  586. this.postData = data;
  587. }
  588. this.log("upload type:", this.uploaderType, " - postData:", this.postData);
  589. for(var i = 0; i < this.fileList.length; i++){
  590. var f = this.fileList[i];
  591. f.bytesLoaded = 0;
  592. f.bytesTotal = f.size || 100000;
  593. f.percent = 0;
  594. }
  595. if(this.uploaderType == "flash"){
  596. this.uploadFlash();
  597. }else{
  598. this.uploadHTML();
  599. }
  600. // prevent form submit
  601. return false;
  602. },
  603. removeFile: function(/*String*/name, /*Boolean*/noListEdit){
  604. // summary:
  605. // Removes a file from the pending file list.
  606. // Removes pending data from the Flash movie
  607. // and fileInputes from the HTML uploader.
  608. // If a file container node is bound, the file
  609. // will also be removed.
  610. // name:String
  611. // The name of the file to be removed. Typically the file name,
  612. // such as: picture01.png
  613. // noListEdit:Boolean
  614. // Internal. If true don't remove files from list.
  615. //
  616. var i;
  617. for(i = 0; i < this.fileList.length; i++){
  618. if(this.fileList[i].name == name){
  619. if(!noListEdit){ // if onComplete, don't do this
  620. this.fileList.splice(i,1);
  621. }
  622. break;
  623. }
  624. }
  625. if(this.uploaderType == "flash"){
  626. this.flashMovie.removeFile(name);
  627. }else if(!noListEdit){
  628. domConstruct.destroy(this.fileInputs[i]);
  629. this.fileInputs.splice(i,1);
  630. this._renumberInputs();
  631. }
  632. if(this.fileListId){
  633. domConstruct.destroy("file_"+name);
  634. }
  635. },
  636. destroy: function(){
  637. // summary:
  638. // Destroys uploader button
  639. if(this.uploaderType == "flash" && !this.flashMovie){
  640. this._cons.push(connect.connect(this, "onLoad", this, "destroy"));
  641. return;
  642. }
  643. array.forEach(this._subs, connect.unsubscribe, dojo);
  644. array.forEach(this._cons, connect.disconnect, dojo);
  645. if(this.scrollConnect){
  646. connect.disconnect(this.scrollConnect);
  647. }
  648. if(this.uploaderType == "flash"){
  649. this.flashObject.destroy();
  650. delete this.flashObject;
  651. }else{
  652. domConstruct.destroy(this._fileInput);
  653. domConstruct.destroy(this._formNode);
  654. }
  655. this.inherited(arguments);
  656. },
  657. /*************************
  658. * Private Events *
  659. *************************/
  660. _displayProgress: function(/*Boolean or Number */display){
  661. // summary:
  662. // Shows and updates the built-in progress bar.
  663. //
  664. if(display === true){
  665. if(this.uploaderType == "flash"){
  666. domStyle.set(this.insideNode,"top", "-2500px");
  667. }else{
  668. domStyle.set(this.insideNode,"display", "none");
  669. }
  670. domStyle.set(this.progNode,"display","");
  671. }else if(display === false){
  672. domStyle.set(this.insideNode,{
  673. display: "",
  674. top: "0"
  675. });
  676. domStyle.set(this.progNode,"display","none");
  677. }else{
  678. var w = display * this.fhtml.nr.w;
  679. domStyle.set(this.progNode, "width", w + "px");
  680. }
  681. },
  682. _animateProgress: function(){
  683. // summary:
  684. // Internal. Animated the built-in progress bar
  685. this._displayProgress(true);
  686. var _uploadDone = false;
  687. var c = connect.connect(this, "_complete", function(){
  688. connect.disconnect(c);
  689. _uploadDone = true;
  690. });
  691. var w = 0;
  692. var interval = setInterval(lang.hitch(this, function(){
  693. w+=5;
  694. if(w>this.fhtml.nr.w){
  695. w = 0;
  696. _uploadDone = true;
  697. }
  698. this._displayProgress(w/this.fhtml.nr.w);
  699. if(_uploadDone){
  700. clearInterval(interval);
  701. setTimeout(lang.hitch(this, function(){
  702. this._displayProgress(false);
  703. }), 500);
  704. }
  705. }),50);
  706. },
  707. _error: function(evt){
  708. //var type = evtObject.type ? evtObject.type.toUpperCase() : "ERROR";
  709. //var msg = evtObject.msg ? evtObject.msg : evtObject;
  710. if(typeof(evt)=="string"){
  711. evt = new Error(evt);
  712. }
  713. this.onError(evt);
  714. },
  715. _addToFileList: function(){
  716. // summary:
  717. // Internal only. If there is a file list, adds a file to it.
  718. // If you need to use a function such as this, connect to
  719. // onChange and update outside of this widget.
  720. //
  721. if(this.fileListId){
  722. var str = '';
  723. array.forEach(this.fileList, function(d){
  724. // have to use tables because of IE. Grumble.
  725. str += '<table id="file_'+d.name+'" class="fileToUpload"><tr><td class="fileToUploadClose"></td><td class="fileToUploadName">'+d.name+'</td><td class="fileToUploadSize">'+(d.size ? Math.ceil(d.size*.001) +"kb" : "")+'</td></tr></table>'
  726. }, this);
  727. dom.byId(this.fileListId).innerHTML = str;
  728. }
  729. },
  730. _change: function(dataArray){
  731. // summary:
  732. // Internal. Updates uploader selection
  733. if(has("ie")){
  734. //IE6 uses the entire path in the name, which isn't terrible, but much different
  735. // than everything else
  736. array.forEach(dataArray, function(f){
  737. f.name = f.name.split("\\")[f.name.split("\\").length-1];
  738. });
  739. }
  740. if(this.selectMultipleFiles){
  741. this.fileList = this.fileList.concat(dataArray);
  742. }else{
  743. if(this.fileList[0]){
  744. this.removeFile(this.fileList[0].name, true);
  745. }
  746. this.fileList = dataArray;
  747. }
  748. this._addToFileList();
  749. this.onChange(dataArray);
  750. if(this.uploadOnChange){
  751. if(this.uploaderType == "html"){
  752. this._buildFileInput();
  753. }
  754. this.upload();
  755. }else if(this.uploaderType == "html" && this.selectMultipleFiles){
  756. this._buildFileInput();
  757. this._connectInput();
  758. }
  759. },
  760. _complete: function(dataArray){
  761. // summary:
  762. // Internal. Handles tasks after files have finished uploading
  763. //
  764. dataArray = lang.isArray(dataArray) ? dataArray : [dataArray];
  765. // Yes. Yes I do have to do three loops here. ugh.
  766. //
  767. // Check if one of the files had an error
  768. array.forEach(dataArray, function(f){
  769. if(f.ERROR){ this._error(f.ERROR); }
  770. }, this);
  771. // Have to be set them all too 100%, because
  772. // onProgress does not always fire
  773. array.forEach(this.fileList, function(f){
  774. f.bytesLoaded = 1;
  775. f.bytesTotal = 1;
  776. f.percent = 100;
  777. this._progress(f);
  778. }, this);
  779. // we're done. remove files.
  780. array.forEach(this.fileList, function(f){
  781. this.removeFile(f.name, true);
  782. }, this);
  783. this.onComplete(dataArray);
  784. this.fileList = [];
  785. this._resetHTML();
  786. this.set("disabled", false);
  787. if(this.restoreProgDisplay){
  788. // using timeout so prog shows on screen for at least a short time
  789. setTimeout(lang.hitch(this, function(){
  790. domStyle.set(manager.byId(this.progressWidgetId).domNode,
  791. this.restoreProgDisplay == "none" ? "display" : "visibility",
  792. this.restoreProgDisplay
  793. );
  794. }), 500);
  795. }
  796. },
  797. _progress: function(dataObject){
  798. // summary:
  799. // Internal. Calculate progress
  800. var total = 0;
  801. var loaded = 0;
  802. for(var i = 0; i < this.fileList.length; i++){
  803. var f = this.fileList[i];
  804. if(f.name == dataObject.name){
  805. f.bytesLoaded = dataObject.bytesLoaded;
  806. f.bytesTotal = dataObject.bytesTotal;
  807. f.percent = Math.ceil(f.bytesLoaded / f.bytesTotal * 100);
  808. this.log(f.name, "percent:", f.percent)
  809. }
  810. loaded += Math.ceil(.001 * f.bytesLoaded);
  811. total += Math.ceil(.001 * f.bytesTotal);
  812. }
  813. var percent = Math.ceil(loaded / total * 100);
  814. if(this.progressWidgetId){
  815. manager.byId(this.progressWidgetId).update({progress:percent+"%"});
  816. }
  817. if(this.showProgress){
  818. this._displayProgress(percent * .01);
  819. }
  820. this.onProgress(this.fileList);
  821. },
  822. _getDisabledAttr: function(){
  823. // summary:
  824. // Internal. To get disabled use: widget.get("disabled");
  825. return this._disabled;
  826. },
  827. _setDisabledAttr: function(disabled){
  828. // summary:
  829. // Internal. To set disabled use: widget.set("disabled", true | false);
  830. if(this._disabled == disabled){ return; }
  831. if(this.uploaderType == "flash"){
  832. if(!this.flashReady){
  833. var _fc = connect.connect(this, "onLoad", this, function(){
  834. connect.disconnect(_fc);
  835. this._setDisabledAttr(disabled);
  836. });
  837. return;
  838. }
  839. this._disabled = disabled;
  840. this.flashMovie.doDisable(disabled);
  841. }else{
  842. this._disabled = disabled;
  843. domStyle.set(this._fileInput, "display", this._disabled ? "none" : "");
  844. }
  845. domClass.toggle(this.domNode, this.disabledClass, disabled);
  846. },
  847. _onFlashBlur: function(){
  848. // summary:
  849. // Internal. Detects when Flash movies reliquishes focus.
  850. // We have to find all the tabIndexes in the doc and figure
  851. // out whom to give focus to next.
  852. this.flashMovie.blur();
  853. if(!this.nextFocusObject && this.tabIndex){
  854. var nodes = query("[tabIndex]");
  855. for(var i = 0; i<nodes.length; i++){
  856. if(nodes[i].tabIndex >= Number(this.tabIndex)+1){
  857. this.nextFocusObject = nodes[i];
  858. break;
  859. }
  860. }
  861. }
  862. this.nextFocusObject.focus();
  863. },
  864. _disconnect: function(){
  865. // summary:
  866. // Internal. Disconnects fileInput in favor of new one.
  867. array.forEach(this._cons, connect.disconnect, dojo);
  868. },
  869. /*************************
  870. * HTML *
  871. *************************/
  872. uploadHTML: function(){
  873. // summary:
  874. // Internal. You could use this, but you should use upload() or submit();
  875. // which can also handle the post data.
  876. //
  877. // NOTE on deferredUploading:
  878. // This is not enabled for HTML. Workaround would be to force
  879. // singleFile uploads.
  880. // TODO:
  881. // Investigate removing fileInputs and resending form
  882. // multiple times adding each fileInput
  883. //
  884. if(this.selectMultipleFiles){
  885. domConstruct.destroy(this._fileInput);
  886. }
  887. this._setHtmlPostData();
  888. if(this.showProgress){
  889. this._animateProgress();
  890. }
  891. var dfd = ioIframe.send({
  892. url: this.uploadUrl.toString(),
  893. form: this._formNode,
  894. handleAs: "json",
  895. error: lang.hitch(this, function(err){
  896. this._error("HTML Upload Error:" + err.message);
  897. }),
  898. load: lang.hitch(this, function(data, ioArgs, widgetRef){
  899. this._complete(data);
  900. })
  901. });
  902. },
  903. createHtmlUploader: function(){
  904. // summary:
  905. // Internal. Fires of methods to build HTML Uploader.
  906. this._buildForm();
  907. this._setFormStyle();
  908. this._buildFileInput();
  909. this._connectInput();
  910. this._styleContent();
  911. domStyle.set(this.insideNode, "visibility", "visible");
  912. this.onReady();
  913. },
  914. _connectInput: function(){
  915. // summary:
  916. // Internal. HTML Uploader connections. These get disconnected
  917. // after upload or if multi upload.
  918. this._disconnect();
  919. this._cons.push(connect.connect(this._fileInput, "mouseover", this, function(evt){
  920. domClass.add(this.domNode, this.hoverClass);
  921. this.onMouseOver(evt);
  922. }));
  923. this._cons.push(connect.connect(this._fileInput, "mouseout", this, function(evt){
  924. setTimeout(lang.hitch(this, function(){
  925. domClass.remove(this.domNode, this.activeClass);
  926. domClass.remove(this.domNode, this.hoverClass);
  927. this.onMouseOut(evt);
  928. this._checkHtmlCancel("off");
  929. }), 0);
  930. }));
  931. this._cons.push(connect.connect(this._fileInput, "mousedown", this, function(evt){
  932. domClass.add(this.domNode, this.activeClass);
  933. domClass.remove(this.domNode, this.hoverClass);
  934. this.onMouseDown(evt);
  935. }));
  936. this._cons.push(connect.connect(this._fileInput, "mouseup", this, function(evt){
  937. domClass.remove(this.domNode, this.activeClass);
  938. this.onMouseUp(evt);
  939. this.onClick(evt);
  940. this._checkHtmlCancel("up");
  941. }));
  942. this._cons.push(connect.connect(this._fileInput, "change", this, function(){
  943. this._checkHtmlCancel("change");
  944. this._change([{
  945. name: this._fileInput.value,
  946. type: "",
  947. size: 0
  948. }]);
  949. }));
  950. if(this.tabIndex>=0){
  951. domAttr.set(this.domNode, "tabIndex", this.tabIndex);
  952. }
  953. },
  954. _checkHtmlCancel: function(mouseType){
  955. // summary:
  956. // Internal. Check if the dialog was opened and canceled without file selection.
  957. if(mouseType == "change"){
  958. this.dialogIsOpen = false;
  959. }
  960. if(mouseType == "up"){
  961. this.dialogIsOpen = true;
  962. }
  963. if(mouseType == "off"){
  964. if(this.dialogIsOpen){
  965. this.onCancel();
  966. }
  967. this.dialogIsOpen = false;
  968. }
  969. },
  970. _styleContent: function(){
  971. // summary:
  972. // Internal.Apply style to node
  973. var o = this.fhtml.nr;
  974. domStyle.set(this.insideNode, {
  975. width:o.w+"px",
  976. height:o.va == "middle"?o.h+"px":"auto",
  977. textAlign:o.ta,
  978. paddingTop:o.p[0]+"px",
  979. paddingRight:o.p[1]+"px",
  980. paddingBottom:o.p[2]+"px",
  981. paddingLeft:o.p[3]+"px"
  982. });
  983. try{
  984. domStyle.set(this.insideNode, "lineHeight", "inherit");
  985. }catch(e){
  986. // There are certain cases where IE refuses to set lineHeight.
  987. // For the life of me I cannot figure out the combination of
  988. // styles that IE doesn't like. Steaming... Pile...
  989. }
  990. },
  991. _resetHTML: function(){
  992. // summary:
  993. // Internal. After upload, this is called to clear the form and build a new
  994. // fileInput.
  995. if(this.uploaderType == "html" && this._formNode){
  996. this.fileInputs = [];
  997. query("*", this._formNode).forEach(function(n){
  998. domConstruct.destroy(n);
  999. });
  1000. this.fileCount = 0;
  1001. this._buildFileInput();
  1002. this._connectInput();
  1003. }
  1004. },
  1005. _buildForm: function(){
  1006. // summary:
  1007. // Build the form that holds the fileInput
  1008. //
  1009. if(this._formNode){ return; }
  1010. if(has("ie") < 9 || (has("ie") && has("quirks"))){
  1011. this._formNode = document.createElement('<form enctype="multipart/form-data" method="post">');
  1012. this._formNode.encoding = "multipart/form-data";
  1013. this._formNode.id = manager.getUniqueId("FileUploaderForm"); // needed for dynamic style
  1014. this.domNode.appendChild(this._formNode);
  1015. }else{
  1016. this._formNode = domConstruct.create('form', {
  1017. enctype:"multipart/form-data",
  1018. method:"post",
  1019. id:manager.getUniqueId("FileUploaderForm")
  1020. }, this.domNode);
  1021. }
  1022. },
  1023. _buildFileInput: function(){
  1024. // summary:
  1025. // Build the fileInput field
  1026. //
  1027. if(this._fileInput){
  1028. this._disconnect();
  1029. // FIXME:
  1030. // Just hiding it which works, but we lose
  1031. // reference to it and can't remove it from
  1032. // the upload list.
  1033. this._fileInput.id = this._fileInput.id + this.fileCount;
  1034. domStyle.set(this._fileInput, "display", "none");
  1035. }
  1036. this._fileInput = document.createElement('input');
  1037. this.fileInputs.push(this._fileInput);
  1038. // server will need to know this variable:
  1039. var nm = this.htmlFieldName;
  1040. var _id = this.id;
  1041. if(this.selectMultipleFiles){
  1042. nm += this.fileCount;
  1043. _id += this.fileCount;
  1044. this.fileCount++;
  1045. }
  1046. domAttr.set(this._fileInput, {
  1047. id:this.id,
  1048. name:nm,
  1049. type:"file"
  1050. });
  1051. domClass.add(this._fileInput, "dijitFileInputReal");
  1052. this._formNode.appendChild(this._fileInput);
  1053. var real = domGeometry.getMarginBox(this._fileInput);
  1054. domStyle.set(this._fileInput, {
  1055. position:"relative",
  1056. left:(this.fhtml.nr.w - real.w) + "px",
  1057. opacity:0
  1058. });
  1059. },
  1060. _renumberInputs: function(){
  1061. if(!this.selectMultipleFiles){ return; }
  1062. var nm;
  1063. this.fileCount = 0;
  1064. array.forEach(this.fileInputs, function(inp){
  1065. nm = this.htmlFieldName + this.fileCount;
  1066. this.fileCount++;
  1067. domAttr.set(inp, "name", nm);
  1068. }, this);
  1069. },
  1070. _setFormStyle: function(){
  1071. // summary:
  1072. // Apply a dynamic style to the form and input
  1073. var size = Math.max(2, Math.max(Math.ceil(this.fhtml.nr.w / 60), Math.ceil(this.fhtml.nr.h / 15)));
  1074. // Now create a style associated with the form ID
  1075. htmlStyles.insertCssRule("#" + this._formNode.id + " input", "font-size:" + size + "em");
  1076. domStyle.set(this.domNode, {
  1077. overflow:"hidden",
  1078. position:"relative"
  1079. });
  1080. domStyle.set(this.insideNode, "position", "absolute");
  1081. },
  1082. _setHtmlPostData: function(){
  1083. // summary:
  1084. // Internal.Apply postData to hidden fields in form
  1085. if(this.postData){
  1086. for(var nm in this.postData){
  1087. domConstruct.create("input", {
  1088. type: "hidden",
  1089. name: nm,
  1090. value: this.postData[nm]
  1091. }, this._formNode);
  1092. }
  1093. }
  1094. },
  1095. /*************************
  1096. * FLASH *
  1097. *************************/
  1098. uploadFlash: function(){
  1099. // summary:
  1100. // Internal. You should use upload() or submit();
  1101. try{
  1102. if(this.showProgress){
  1103. this._displayProgress(true);
  1104. var c = connect.connect(this, "_complete", this, function(){
  1105. connect.disconnect(c);
  1106. this._displayProgress(false);
  1107. });
  1108. }
  1109. var o = {};
  1110. for(var nm in this.postData){
  1111. o[nm] = this.postData[nm];
  1112. }
  1113. this.flashMovie.doUpload(o);
  1114. }catch(err){
  1115. this._error("FileUploader - Sorry, the SWF failed to initialize." + err);
  1116. }
  1117. },
  1118. createFlashUploader: function(){
  1119. // summary:
  1120. // Internal. Creates Flash Uploader
  1121. this.uploadUrl = this.uploadUrl.toString();
  1122. if(this.uploadUrl){
  1123. if(this.uploadUrl.toLowerCase().indexOf("http")<0 && this.uploadUrl.indexOf("/")!=0){
  1124. // Appears to be a relative path. Attempt to
  1125. // convert it to absolute, so it will better
  1126. //target the SWF.
  1127. //
  1128. var loc = window.location.href.split("/");
  1129. loc.pop();
  1130. loc = loc.join("/")+"/";
  1131. this.uploadUrl = loc+this.uploadUrl;
  1132. this.log("SWF Fixed - Relative loc:", loc, " abs loc:", this.uploadUrl);
  1133. }else{
  1134. this.log("SWF URL unmodified:", this.uploadUrl)
  1135. }
  1136. }else{
  1137. console.warn("Warning: no uploadUrl provided.");
  1138. }
  1139. var w = this.fhtml.nr.w;
  1140. var h = this.fhtml.nr.h;
  1141. var args = {
  1142. expressInstall:true,
  1143. path: this.swfPath.uri || this.swfPath,
  1144. width: w,
  1145. height: h,
  1146. allowScriptAccess:"always",
  1147. allowNetworking:"all",
  1148. vars: {
  1149. uploadDataFieldName: this.flashFieldName,
  1150. uploadUrl: this.uploadUrl,
  1151. uploadOnSelect: this.uploadOnChange,
  1152. deferredUploading:this.deferredUploading || 0,
  1153. selectMultipleFiles: this.selectMultipleFiles,
  1154. id: this.id,
  1155. isDebug: this.isDebug,
  1156. devMode:this.devMode,
  1157. flashButton:embedFlashVars.serialize("fh", this.fhtml),
  1158. fileMask:embedFlashVars.serialize("fm", this.fileMask),
  1159. noReturnCheck: this.skipServerCheck,
  1160. serverTimeout:this.serverTimeout
  1161. },
  1162. params: {
  1163. scale:"noscale",
  1164. wmode:"opaque",
  1165. allowScriptAccess:"always",
  1166. allowNetworking:"all"
  1167. }
  1168. };
  1169. this.flashObject = new embedFlash(args, this.insideNode);
  1170. this.flashObject.onError = lang.hitch(function(msg){
  1171. this._error("Flash Error: " + msg);
  1172. });
  1173. this.flashObject.onReady = lang.hitch(this, function(){
  1174. domStyle.set(this.insideNode, "visibility", "visible");
  1175. this.log("FileUploader flash object ready");
  1176. this.onReady(this);
  1177. });
  1178. this.flashObject.onLoad = lang.hitch(this, function(mov){
  1179. this.flashMovie = mov;
  1180. this.flashReady = true;
  1181. this.onLoad(this);
  1182. });
  1183. this._connectFlash();
  1184. },
  1185. _connectFlash: function(){
  1186. // summary:
  1187. // Subscribing to published topics coming from the
  1188. // Flash uploader.
  1189. // description:
  1190. // Sacrificing some readbilty for compactness. this.id
  1191. // will be on the beginning of the topic, so more than
  1192. // one uploader can be on a page and can have unique calls.
  1193. //
  1194. this._doSub("/filesSelected", "_change");
  1195. this._doSub("/filesUploaded", "_complete");
  1196. this._doSub("/filesProgress", "_progress");
  1197. this._doSub("/filesError", "_error");
  1198. this._doSub("/filesCanceled", "onCancel");
  1199. this._doSub("/stageBlur", "_onFlashBlur");
  1200. this._doSub("/up", "onMouseUp");
  1201. this._doSub("/down", "onMouseDown");
  1202. this._doSub("/over", "onMouseOver");
  1203. this._doSub("/out", "onMouseOut");
  1204. this.connect(this.domNode, "focus", function(){
  1205. // TODO: some kind of indicator that the Flash button
  1206. // is in focus
  1207. this.flashMovie.focus();
  1208. this.flashMovie.doFocus();
  1209. });
  1210. if(this.tabIndex>=0){
  1211. domAttr.set(this.domNode, "tabIndex", this.tabIndex);
  1212. }
  1213. },
  1214. _doSub: function(subStr, funcStr){
  1215. // summary:
  1216. // Internal. Shortcut for subscribes to Flash movie
  1217. this._subs.push(connect.subscribe(this.id + subStr, this, funcStr));
  1218. },
  1219. /*************************************
  1220. * DOM INSPECTION METHODS *
  1221. *************************************/
  1222. urlencode: function(url){
  1223. // Using symbols in place of URL chars that will break in Flash serialization.
  1224. if(!url || url == "none"){
  1225. return false;
  1226. }
  1227. return url.replace(/:/g,"||").replace(/\./g,"^^").replace("url(", "").replace(")","").replace(/'/g,"").replace(/"/g,"");
  1228. },
  1229. isButton: function(node){
  1230. // testing if button for styling purposes
  1231. var tn = node.tagName.toLowerCase();
  1232. return tn == "button" || tn == "input";
  1233. },
  1234. getTextStyle: function(node){
  1235. // getting font info
  1236. var o = {};
  1237. o.ff = domStyle.get(node, "fontFamily");
  1238. if(o.ff){
  1239. o.ff = o.ff.replace(", ", ","); // remove spaces. IE in Flash no likee
  1240. o.ff = o.ff.replace(/\"|\'/g, "");
  1241. o.ff = o.ff == "sans-serif" ? "Arial" : o.ff; // Flash doesn't know what sans-serif is
  1242. o.fw = domStyle.get(node, "fontWeight");
  1243. o.fi = domStyle.get(node, "fontStyle");
  1244. o.fs = parseInt(domStyle.get(node, "fontSize"), 10);
  1245. if(domStyle.get(node, "fontSize").indexOf("%") > -1){
  1246. // IE doesn't convert % to px. For god sakes.
  1247. var n = node;
  1248. while(n.tagName){
  1249. if(domStyle.get(n, "fontSize").indexOf("%") == -1){
  1250. o.fs = parseInt(domStyle.get(n, "fontSize"), 10);
  1251. break;
  1252. }
  1253. if(n.tagName.toLowerCase()=="body"){
  1254. // if everyting is %, the the font size is 16px * the %
  1255. o.fs = 16 * .01 * parseInt(domStyle.get(n, "fontSize"), 10);
  1256. }
  1257. n = n.parentNode;
  1258. }
  1259. }
  1260. o.fc = new Color(domStyle.get(node, "color")).toHex();
  1261. o.fc = parseInt(o.fc.substring(1,Infinity),16);
  1262. }
  1263. o.lh = domStyle.get(node, "lineHeight");
  1264. o.ta = domStyle.get(node, "textAlign");
  1265. o.ta = o.ta == "start" || !o.ta ? "left" : o.ta;
  1266. o.va = this.isButton(node) ? "middle" : o.lh == o.h ? "middle" : domStyle.get(node, "verticalAlign");
  1267. return o;
  1268. },
  1269. getText: function(node){
  1270. // Get the text of the button. It's possible to use HTML in the Flash Button,
  1271. // but the results are not spectacular.
  1272. var cn = lang.trim(node.innerHTML);
  1273. if(cn.indexOf("<") >- 1){
  1274. cn = escape(cn);
  1275. }
  1276. return cn;
  1277. },
  1278. getStyle: function(node){
  1279. // getting the style of a node. Using very abbreviated characters which the
  1280. // Flash movie understands.
  1281. var o = {};
  1282. var dim = domGeometry.getContentBox(node);
  1283. var pad = domGeometry.getPadExtents(node);
  1284. o.p = [pad.t, pad.w-pad.l, pad.h-pad.t, pad.l];
  1285. o.w = dim.w + pad.w;
  1286. o.h = dim.h + pad.h;
  1287. o.d = domStyle.get(node, "display");
  1288. var clr = new Color(domStyle.get(node, "backgroundColor"));
  1289. // if no color, Safari sets #000000 and alpha=0 since we don't support alpha,
  1290. // it makes black - make it white
  1291. o.bc = clr.a == 0 ? "#ffffff" : clr.toHex();
  1292. o.bc = parseInt(o.bc.substring(1,Infinity),16);
  1293. var url = this.urlencode(domStyle.get(node, "backgroundImage"));
  1294. if(url){
  1295. o.bi = {
  1296. url:url,
  1297. rp:domStyle.get(node, "backgroundRepeat"),
  1298. pos: escape(domStyle.get(node, "backgroundPosition"))
  1299. };
  1300. if(!o.bi.pos){
  1301. // IE does Xpx and Ypx, not "X% Y%"
  1302. var rx = domStyle.get(node, "backgroundPositionX");
  1303. var ry = domStyle.get(node, "backgroundPositionY");
  1304. rx = (rx == "left") ? "0%" : (rx == "right") ? "100%" : rx;
  1305. ry = (ry == "top") ? "0%" : (ry == "bottom") ? "100%" : ry;
  1306. o.bi.pos = escape(rx+" "+ry);
  1307. }
  1308. }
  1309. return lang.mixin(o, this.getTextStyle(node));
  1310. },
  1311. getTempNodeStyle: function(node, _class, isDijitButton){
  1312. // This sets up a temp node to get the style of the hover, active, and disabled states
  1313. var temp, style;
  1314. if(isDijitButton){
  1315. // backwards compat until dojo 1.5
  1316. temp = domConstruct.place("<"+node.tagName+"><span>"+node.innerHTML+"</span></"+node.tagName+">", node.parentNode); //+" "+_class+"
  1317. var first = temp.firstChild;
  1318. domClass.add(first, node.className);
  1319. domClass.add(temp, _class);
  1320. style = this.getStyle(first);
  1321. }else{
  1322. temp = domConstruct.place("<"+node.tagName+">"+node.innerHTML+"</"+node.tagName+">", node.parentNode);
  1323. domClass.add(temp, node.className);
  1324. domClass.add(temp, _class);
  1325. temp.id = node.id;
  1326. style = this.getStyle(temp);
  1327. }
  1328. // dev note: comment out this line to see what the
  1329. // button states look like to the FileUploader
  1330. domConstruct.destroy(temp);
  1331. return style;
  1332. }
  1333. });
  1334. return dojox.form.FileUploader;
  1335. });