//>>built define("dojox/form/FileUploader", [ "dojo/_base/kernel", "dojo/_base/declare", "dojo/_base/lang", "dojo/_base/array", "dojo/_base/connect", "dojo/_base/window", "dojo/_base/sniff", "dojo/query", "dojo/dom-style", "dojo/dom-geometry", "dojo/dom-attr", "dojo/dom-class", "dojo/dom-construct", "dojo/dom-form", "dojo/_base/config", "dijit/_base/manager", "dojo/io/iframe", "dojo/_base/Color", "dojo/_base/unload", "dijit/_Widget", "dijit/_TemplatedMixin", "dijit/_Contained", "dojox/embed/Flash", "dojox/embed/flashVars", "dojox/html/styles" ],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){ kernel.deprecated("dojox.form.FileUploader", "Use dojox.form.Uploader", "2.0"); // Usage Notes: // To center text vertically, use vertical-align:middle; // which emulates a boxModel button. Using line-height to center text // can cause height problems in IE6 /*===== Widget = dijit._Widget; TemplatedMixin = dijit._TemplatedMixin; Contained = dijit._Contained; =====*/ declare("dojox.form.FileUploader", [Widget, TemplatedMixin, Contained], { // version: // 1.5 (deprecated) // summary: // Handles File Uploading to a server (PHP script included for testing) // // FileUploader is now a WIDGET. You do not have to pass a button // in. Passing a button is still supported until version 1.5 to maintain // backwards compatibility, but it is not reccomended. Just create your // uploader like any other widget. // // description: // If the correct version of Flash Player is available (> 9.0) , a SWF // is used. If Flash Player is not installed or is outdated, a typical // html fileInput is used. This process can be overridden with // force:"flash" or force:"html". // // FileUploader works with Flash 10. // // The button styles are now recreated in Flash, so there is no longer // using an invisible Flash movie with wmode=transparent. This way the Flash button // is actually placed inline with the DOM, not floating above it and constantly // resetting its position. The "Windows Firefox clickable bug" should be fixed (and // hopefully some Linux problems). // // The HTML button is created in a new way and it is now inline as is the // FLash button. Styling is much easier and more versatile. // // Dependencies: // FileUploader no longer uses FileInput.css. It now uses FileUploader.css // See requires for JavaScript dependencies. // // NEW FEATURES - // There are a ton of features and fixes in this version. // Disabled: Can be toggled with widget.attr("disable", true|false) // Submit: A convenience method has been added for if the uploader is in a form. // Instead of submitting the form, call uploader.submit(theForm), and the // Uploader will handle all of the form values and post the data. // Selected List: If passing the ID of a container, the Uploaders will populate it // with the selected files. // Deleting Files: You can now delete pending files. // Progress Built in: showProgress:true will change the button to a progress // bar on upload. // Progress Attach: Passing progressWidgetId will tell the Uploader of a progress // widget. If the Progress widget is initially hidden, it will change to // visible and then restored after upload. // A11Y: The Flash button can be accessed with the TAB key. (The HTML cannot due // to browser limtations) // Deferred Uploading: (Flash only) throttles the upload to one file at a time // // // CDN USERS - // FileUpload now works with the CDN but with limitations. The SWF must // be from the same domain as the HTML page. 'swfPath' has been exposed // so that you may link to that file (could of course be the same SWF in // dojox resource folder). The SWF will *NOT* work from the // CDN server. This would require a special XML file that would allow // access to your server, and the logistics to that is impossible. // // LIMITATIONS // - This is not designed to be a part of a form, it contains its own. (See submit()) // - Currently does not in a Dialog box or a Tab where it is not initially visible, // - The default style inherits font sizes - but a parent container should have a font size // set somewhere of the results could be inconsistent. // // OPERA USERS - // It works better than the 1.3 version. fileInputs apperantly can't have opacity // set to zero. The Flash uploader works but files are auto-uploaded. Must be a // flashVar problem. // // Safari Bug note: // The bug is in the way Safari handles the connection: // https://bugs.webkit.org/show_bug.cgi?id=5760 // I added this to the virtual host in the Apache conf file, and now it // works like a charm: // BrowserMatch Safari nokeepalive // swfPath: config.uploaderPath || require.toUrl("dojox/form/resources/fileuploader.swf"), templateString:'
', // uploadUrl: String // The url targeted for upload. An absolute URL is preferred. Relative URLs are // changed to absolute. uploadUrl: "", // // isDebug: Boolean // If true, outputs traces from the SWF to console. What exactly gets passed // is very relative, and depends upon what traces have been left in the DEFT SWF. isDebug:false, // // devMode: Boolean. // Re-implemented. devMode increases the logging, adding style tracing from the SWF. devMode:false, // // id: String // The object id, just like any other widget in Dojo. However, this id // is also used as a reference for the SWF // id: "", // // baseClass: String // The name of the class that will style the button in a "normal" state. // If baseClass is not defined, 'class' will be used. // NOTE: By default the uploader will be styled like a dijit buttons and // adhere to the the themes. Tundra, Soria, and Nihilo are supported. // You can cascade the existing style by using 'class' or 'style'. If you // overwrite baseClass, you should overwrite the remaing state classes // that follow) as well. baseClass:"dojoxUploaderNorm", // // hoverClass: String // The name of the class that will style the button in a "hover" state. A specific // class should be made to do this. Do not rely on a target like button:hover{...} hoverClass:"dojoxUploaderHover", // // activeClass: String // The name of the class that will style the button in a "press" state. A specific // class should be made to do this. Do not rely on a target like button:active{...} activeClass:"dojoxUploaderActive", // // disabledClass: String // The name of the class that will style the button when its disabled. disabledClass:"dojoxUploaderDisabled", // // force: String // Use "flash" to always use Flash (and hopefully force the user to download the plugin // if they don't have it). Use "html" to always use the HTML uploader. An empty string // (default) will check for the right version of Flash and use HTML if not available. force:"", // // uploaderType: [readonly] String // Internal. What type of uploader is being used: "flash" or "html" uploaderType:"", // // flashObject: [readonly] dojox.embed.Flash // The object that creates the SWF embed object. Mostly Internal. flashObject: null, // // flashMovie: [readonly] Function // The SWF. Mostly Internal. flashMovie: null, // // insideNode: [readonly] HTMLNode // The div that holds the SWF and form/fileInput insideNode: null, // // deferredUploading: Number (1 - X) // (Flash only) throttles the upload to a certain amount of files at a time. // By default, Flash uploads file one at a time to the server, but in parallel. // Firefox will try to queue all files at once, leading to problems. Set this // to the amount to upload in parallel at a time. // Generally, 1 should work fine, but you can experiment with queuing more than // one at a time. // This is of course ignored if selectMultipleFiles equals false. deferredUploading:1, // // fileListId: String // The id of a dom node to be used as a container for the pending file list. fileListId:"", // // uploadOnChange: Boolean // If true, uploads imediately after a file has been selected. If false, // waits for upload() to be called. uploadOnChange: false, // // selectMultipleFiles: Boolean // If true and flash mode, multiple files may be selected from the dialog. // If html mode, files are not uploaded until upload() is called. The references // to each file is incremented:uploadedfile0, uploadedfile1, uploadedfile2... etc. selectMultipleFiles: true, // // htmlFieldName: String // The name of the field of the fileInput that the server is expecting htmlFieldName:"uploadedfile", // // flashFieldName: String // The name of the field of the flash uploaded files that the server is expecting flashFieldName:"flashUploadFiles", // // fileMask: Array[ Array[Description, FileTypes], Array[...]...] // (an array, or an array of arrays) // Restrict file selection to certain file types // Empty array defaults to "All Files" // example: // fileMask = ["Images", "*.jpg;*.jpeg;*.gif;*.png"] // or // fileMask = [ // ["Jpeg File", "*.jpg;*.jpeg"], // ["GIF File", "*.gif"], // ["PNG File", "*.png"], // ["All Images", "*.jpg;*.jpeg;*.gif;*.png"], // ] // NOTE: MacType is not supported, as it does not work very well. // fileMask will work on a Mac, but differently than // Windows. fileMask: null, // // minFlashVersion: Number // The minimum of version of Flash player to target. 0 would always install Flash, 100 // would never install it. The Flash Player has supported multiple uploads since // version 8, so it could go as low as that safely. minFlashVersion:9, // // tabIndex: Number|String // The tab order in the DOM. Only supported by Flash. HTML Uploaders have security // protection to prevent you from tabbing to the uploader. Stupid. tabIndex:-1, // // showProgress: Boolean // If true, the button changes to a progress bar during upload. showProgress:false, // // progressMessage: String // The message shown while the button is changed to a progress bar progressMessage:"Loading", // // progressBackgroundUrl: String|Uri // The background image to use for the button-progress progressBackgroundUrl:require.toUrl("dijit/themes/tundra/images/buttonActive.png"), // // progressBackgroundColor: String|Number // The background color to use for the button-progress progressBackgroundColor:"#ededed", // // progressWidgetId:String // The widget id of a Dijit Progress bar. The Uploader will bind to it and update it // automatically. progressWidgetId:"", // // skipServerCheck: Boolean // If true, will not verify that the server was sent the correct format. // This can be safely set to true. The purpose of the server side check // is mainly to show the dev if they've implemented the different returns // correctly. skipServerCheck:false, // // serverTimeout:Number (milliseconds) // The amount of time given to the uploaded file // to wait for a server response. After this amount // of time, the onComplete is fired but with a 'server timeout' // error in the returned item. serverTimeout: 5000, log: function(){ // summary: // Due to the excessive logging necessary to make this code happen, // It's easier to turn it on and off here in one place. // Also helpful if there are multiple uploaders on one page. if(this.isDebug){ console["log"](Array.prototype.slice.call(arguments).join(" ")); } }, constructor: function(){ this._subs = []; }, postMixInProperties: function(){ // internal stuff: this.fileList = []; this._cons = []; this.fileMask = this.fileMask || []; this.fileInputs = []; this.fileCount = 0; this.flashReady = false; this._disabled = false; this.force = this.force.toLowerCase(); // Pete FTW. this.uploaderType = ((embedFlash.available >= this.minFlashVersion || this.force=="flash") && this.force != "html") ? "flash" : "html"; this.deferredUploading = this.deferredUploading===true ? 1 : this.deferredUploading; this._refNode = this.srcNodeRef; this.getButtonStyle(); }, startup: function(){ }, postCreate: function(){ this.inherited(arguments); // internal stuff: this.setButtonStyle(); var createMethod; if(this.uploaderType == "flash"){ createMethod = "createFlashUploader"; }else{ this.uploaderType = "html"; createMethod = "createHtmlUploader"; } this[createMethod](); if(this.fileListId){ this.connect(dom.byId(this.fileListId), "click", function(evt){ var p = evt.target.parentNode.parentNode.parentNode; // in a table if(p.id && p.id.indexOf("file_")>-1){ this.removeFile(p.id.split("file_")[1]); } }); } // cleaning up solves memory leak issues in the HTML version unloadUtils.addOnUnload(this, this.destroy); }, getHiddenNode: function(/*DomNode*/ node){ // summary: // Internal. // If a parent node is styled as display:none, // returns that node. This node will be temporarilly // changed to display:block. Note if the node is in // a widget that has an onShow event, this is // overridden. // if(!node){ return null; } var hidden = null; var p = node.parentNode; while(p && p.tagName.toLowerCase() != "body"){ var d = domStyle.get(p, "display"); if(d == "none"){ hidden = p; break; } p = p.parentNode; } return hidden; }, getButtonStyle: function(){ // summary: // Internal. // Get necessary style information from srcRefNode and // assigned styles // // TODO: // To call this from postCreate.... // could do the style stuff initially, but if hidden they will be bad sizes // could then redo the sizes // alt is to create a genuine button and copy THAT instead of how doing now var refNode = this.srcNodeRef; this._hiddenNode = this.getHiddenNode(refNode); if(this._hiddenNode){ domStyle.set(this._hiddenNode, "display", "block"); } if(!refNode && this.button && this.button.domNode){ // backwards compat for a Dijit button var isDijitButton = true; var cls = this.button.domNode.className + " dijitButtonNode"; var txt = this.getText(query(".dijitButtonText", this.button.domNode)[0]); var domTxt = ''; refNode = domConstruct.place(domTxt, this.button.domNode, "after"); /// Pete doesn't like this? this.srcNodeRef = refNode; this.button.destroy(); this.baseClass = "dijitButton"; this.hoverClass = "dijitButtonHover"; this.pressClass = "dijitButtonActive"; this.disabledClass = "dijitButtonDisabled"; }else if(!this.srcNodeRef && this.button){ refNode = this.button; } if(domAttr.get(refNode, "class")){ this.baseClass += " " + domAttr.get(refNode, "class"); } domAttr.set(refNode, "class", this.baseClass); this.norm = this.getStyle(refNode); this.width = this.norm.w; this.height = this.norm.h; if(this.uploaderType == "flash"){ this.over = this.getTempNodeStyle(refNode, this.baseClass+" "+this.hoverClass, isDijitButton); this.down = this.getTempNodeStyle(refNode, this.baseClass+" "+this.activeClass, isDijitButton); this.dsbl = this.getTempNodeStyle(refNode, this.baseClass+" "+this.disabledClass, isDijitButton); this.fhtml = { cn:this.getText(refNode), nr:this.norm, ov:this.over, dn:this.down, ds:this.dsbl }; }else{ this.fhtml = { cn:this.getText(refNode), nr:this.norm } if(this.norm.va == "middle"){ this.norm.lh = this.norm.h; } } if(this.devMode){ this.log("classes - base:", this.baseClass, " hover:", this.hoverClass, "active:", this.activeClass); this.log("fhtml:", this.fhtml) this.log("norm:", this.norm) this.log("over:", this.over) this.log("down:", this.down) } }, setButtonStyle: function(){ // summary: // Internal. // Set up internal dom nodes for button construction. // domStyle.set(this.domNode, { width:this.fhtml.nr.w+"px", height:(this.fhtml.nr.h)+"px", padding:"0px", lineHeight: "normal", position:"relative" }); if(this.uploaderType == "html" && this.norm.va == "middle"){ domStyle.set(this.domNode, "lineHeight", this.norm.lh + "px"); } if(this.showProgress){ this.progTextNode.innerHTML = this.progressMessage; domStyle.set(this.progTextNode, { width:this.fhtml.nr.w+"px", height:(this.fhtml.nr.h+0)+"px", padding:"0px", margin:"0px", left:"0px", lineHeight:(this.fhtml.nr.h+0)+"px", position:"absolute" }); domStyle.set(this.progNode, { width:this.fhtml.nr.w+"px", height:(this.fhtml.nr.h+0)+"px", padding:"0px", margin:"0px", left:"0px", position:"absolute", display:"none", backgroundImage:"url("+this.progressBackgroundUrl+")", backgroundPosition:"bottom", backgroundRepeat:"repeat-x", backgroundColor:this.progressBackgroundColor }); }else{ domConstruct.destroy(this.progNode); } domStyle.set(this.insideNode,{ position:"absolute", top:"0px", left:"0px", display:"" }); domClass.add(this.domNode, this.srcNodeRef.className); if(this.fhtml.nr.d.indexOf("inline")>-1){ domClass.add(this.domNode, "dijitInline"); } try{ this.insideNode.innerHTML = this.fhtml.cn; }catch(e){ // You have got to be kidding me. IE does us he favor of checking that // we aren't inserting the improper type of content with innerHTML into // an inline element. Alert us with an "Unknown Runtime Error". You can't // MAKE this stuff up. // if(this.uploaderType == "flash"){ this.insideNode = this.insideNode.parentNode.removeChild(this.insideNode); win.body().appendChild(this.insideNode); this.insideNode.innerHTML = this.fhtml.cn; var c = connect.connect(this, "onReady", this, function(){ connect.disconnect(c); this.insideNode = this.insideNode.parentNode.removeChild(this.insideNode); this.domNode.appendChild(this.insideNode); }); }else{ this.insideNode.appendChild(document.createTextNode(this.fhtml.cn)); } } if(this._hiddenNode){ domStyle.set(this._hiddenNode, "display", "none"); } }, /************************* * Public Events * *************************/ // The following events are inherited from _Widget and still may be connected: // onClick // onMouseUp // onMouseDown // onMouseOver // onMouseOut onChange: function(dataArray){ // summary: // stub to connect // Fires when files are selected // Event is an array of last files selected }, onProgress: function(dataArray){ // summary: // Stub to connect // Fires as progress returns from SWF // Event is an array of all files uploading // Can be connected to for HTML uploader, // but will not return anything. }, onComplete: function(dataArray){ // summary: // stub to connect // Fires when all files have uploaded // Event is an array of all files }, onCancel: function(){ // summary: // Stub to connect // Fires when dialog box has been closed // without a file selection }, onError: function(/* Object or String */evtObject){ // summary: // Fires on errors // //FIXME: Unsure of a standard form for receiving errors }, onReady: function(/* dojox.form.FileUploader */ uploader){ // summary: // Stub - Fired when embedFlash has created the // Flash object, but it has not necessarilly finished // downloading, and is ready to be communicated with. }, onLoad: function(/* dojox.form.FileUploader */ uploader){ // summary: // Stub - SWF has been downloaded 100%. }, /************************* * Public Methods * *************************/ submit: function(/* form node ? */form){ // summary: // If FileUploader is in a form, and other data should be sent // along with the files, use this instead of form submit. // var data = form ? domForm.toObject(form) : null; this.upload(data); return false; // Boolean }, upload: function(/*Object ? */data){ // summary: // When called, begins file upload // data: Object // postData to be sent to server // if(!this.fileList.length){ return false; } if(!this.uploadUrl){ console.warn("uploadUrl not provided. Aborting."); return false; } if(!this.showProgress){ this.set("disabled", true); } if(this.progressWidgetId){ var node = manager.byId(this.progressWidgetId).domNode; if(domStyle.get(node, "display") == "none"){ this.restoreProgDisplay = "none"; domStyle.set(node, "display", "block"); } if(domStyle.get(node, "visibility") == "hidden"){ this.restoreProgDisplay = "hidden"; domStyle.set(node, "visibility", "visible"); } } if(data && !data.target){ this.postData = data; } this.log("upload type:", this.uploaderType, " - postData:", this.postData); for(var i = 0; i < this.fileList.length; i++){ var f = this.fileList[i]; f.bytesLoaded = 0; f.bytesTotal = f.size || 100000; f.percent = 0; } if(this.uploaderType == "flash"){ this.uploadFlash(); }else{ this.uploadHTML(); } // prevent form submit return false; }, removeFile: function(/*String*/name, /*Boolean*/noListEdit){ // summary: // Removes a file from the pending file list. // Removes pending data from the Flash movie // and fileInputes from the HTML uploader. // If a file container node is bound, the file // will also be removed. // name:String // The name of the file to be removed. Typically the file name, // such as: picture01.png // noListEdit:Boolean // Internal. If true don't remove files from list. // var i; for(i = 0; i < this.fileList.length; i++){ if(this.fileList[i].name == name){ if(!noListEdit){ // if onComplete, don't do this this.fileList.splice(i,1); } break; } } if(this.uploaderType == "flash"){ this.flashMovie.removeFile(name); }else if(!noListEdit){ domConstruct.destroy(this.fileInputs[i]); this.fileInputs.splice(i,1); this._renumberInputs(); } if(this.fileListId){ domConstruct.destroy("file_"+name); } }, destroy: function(){ // summary: // Destroys uploader button if(this.uploaderType == "flash" && !this.flashMovie){ this._cons.push(connect.connect(this, "onLoad", this, "destroy")); return; } array.forEach(this._subs, connect.unsubscribe, dojo); array.forEach(this._cons, connect.disconnect, dojo); if(this.scrollConnect){ connect.disconnect(this.scrollConnect); } if(this.uploaderType == "flash"){ this.flashObject.destroy(); delete this.flashObject; }else{ domConstruct.destroy(this._fileInput); domConstruct.destroy(this._formNode); } this.inherited(arguments); }, /************************* * Private Events * *************************/ _displayProgress: function(/*Boolean or Number */display){ // summary: // Shows and updates the built-in progress bar. // if(display === true){ if(this.uploaderType == "flash"){ domStyle.set(this.insideNode,"top", "-2500px"); }else{ domStyle.set(this.insideNode,"display", "none"); } domStyle.set(this.progNode,"display",""); }else if(display === false){ domStyle.set(this.insideNode,{ display: "", top: "0" }); domStyle.set(this.progNode,"display","none"); }else{ var w = display * this.fhtml.nr.w; domStyle.set(this.progNode, "width", w + "px"); } }, _animateProgress: function(){ // summary: // Internal. Animated the built-in progress bar this._displayProgress(true); var _uploadDone = false; var c = connect.connect(this, "_complete", function(){ connect.disconnect(c); _uploadDone = true; }); var w = 0; var interval = setInterval(lang.hitch(this, function(){ w+=5; if(w>this.fhtml.nr.w){ w = 0; _uploadDone = true; } this._displayProgress(w/this.fhtml.nr.w); if(_uploadDone){ clearInterval(interval); setTimeout(lang.hitch(this, function(){ this._displayProgress(false); }), 500); } }),50); }, _error: function(evt){ //var type = evtObject.type ? evtObject.type.toUpperCase() : "ERROR"; //var msg = evtObject.msg ? evtObject.msg : evtObject; if(typeof(evt)=="string"){ evt = new Error(evt); } this.onError(evt); }, _addToFileList: function(){ // summary: // Internal only. If there is a file list, adds a file to it. // If you need to use a function such as this, connect to // onChange and update outside of this widget. // if(this.fileListId){ var str = ''; array.forEach(this.fileList, function(d){ // have to use tables because of IE. Grumble. str += '
'+d.name+''+(d.size ? Math.ceil(d.size*.001) +"kb" : "")+'
' }, this); dom.byId(this.fileListId).innerHTML = str; } }, _change: function(dataArray){ // summary: // Internal. Updates uploader selection if(has("ie")){ //IE6 uses the entire path in the name, which isn't terrible, but much different // than everything else array.forEach(dataArray, function(f){ f.name = f.name.split("\\")[f.name.split("\\").length-1]; }); } if(this.selectMultipleFiles){ this.fileList = this.fileList.concat(dataArray); }else{ if(this.fileList[0]){ this.removeFile(this.fileList[0].name, true); } this.fileList = dataArray; } this._addToFileList(); this.onChange(dataArray); if(this.uploadOnChange){ if(this.uploaderType == "html"){ this._buildFileInput(); } this.upload(); }else if(this.uploaderType == "html" && this.selectMultipleFiles){ this._buildFileInput(); this._connectInput(); } }, _complete: function(dataArray){ // summary: // Internal. Handles tasks after files have finished uploading // dataArray = lang.isArray(dataArray) ? dataArray : [dataArray]; // Yes. Yes I do have to do three loops here. ugh. // // Check if one of the files had an error array.forEach(dataArray, function(f){ if(f.ERROR){ this._error(f.ERROR); } }, this); // Have to be set them all too 100%, because // onProgress does not always fire array.forEach(this.fileList, function(f){ f.bytesLoaded = 1; f.bytesTotal = 1; f.percent = 100; this._progress(f); }, this); // we're done. remove files. array.forEach(this.fileList, function(f){ this.removeFile(f.name, true); }, this); this.onComplete(dataArray); this.fileList = []; this._resetHTML(); this.set("disabled", false); if(this.restoreProgDisplay){ // using timeout so prog shows on screen for at least a short time setTimeout(lang.hitch(this, function(){ domStyle.set(manager.byId(this.progressWidgetId).domNode, this.restoreProgDisplay == "none" ? "display" : "visibility", this.restoreProgDisplay ); }), 500); } }, _progress: function(dataObject){ // summary: // Internal. Calculate progress var total = 0; var loaded = 0; for(var i = 0; i < this.fileList.length; i++){ var f = this.fileList[i]; if(f.name == dataObject.name){ f.bytesLoaded = dataObject.bytesLoaded; f.bytesTotal = dataObject.bytesTotal; f.percent = Math.ceil(f.bytesLoaded / f.bytesTotal * 100); this.log(f.name, "percent:", f.percent) } loaded += Math.ceil(.001 * f.bytesLoaded); total += Math.ceil(.001 * f.bytesTotal); } var percent = Math.ceil(loaded / total * 100); if(this.progressWidgetId){ manager.byId(this.progressWidgetId).update({progress:percent+"%"}); } if(this.showProgress){ this._displayProgress(percent * .01); } this.onProgress(this.fileList); }, _getDisabledAttr: function(){ // summary: // Internal. To get disabled use: widget.get("disabled"); return this._disabled; }, _setDisabledAttr: function(disabled){ // summary: // Internal. To set disabled use: widget.set("disabled", true | false); if(this._disabled == disabled){ return; } if(this.uploaderType == "flash"){ if(!this.flashReady){ var _fc = connect.connect(this, "onLoad", this, function(){ connect.disconnect(_fc); this._setDisabledAttr(disabled); }); return; } this._disabled = disabled; this.flashMovie.doDisable(disabled); }else{ this._disabled = disabled; domStyle.set(this._fileInput, "display", this._disabled ? "none" : ""); } domClass.toggle(this.domNode, this.disabledClass, disabled); }, _onFlashBlur: function(){ // summary: // Internal. Detects when Flash movies reliquishes focus. // We have to find all the tabIndexes in the doc and figure // out whom to give focus to next. this.flashMovie.blur(); if(!this.nextFocusObject && this.tabIndex){ var nodes = query("[tabIndex]"); for(var i = 0; i= Number(this.tabIndex)+1){ this.nextFocusObject = nodes[i]; break; } } } this.nextFocusObject.focus(); }, _disconnect: function(){ // summary: // Internal. Disconnects fileInput in favor of new one. array.forEach(this._cons, connect.disconnect, dojo); }, /************************* * HTML * *************************/ uploadHTML: function(){ // summary: // Internal. You could use this, but you should use upload() or submit(); // which can also handle the post data. // // NOTE on deferredUploading: // This is not enabled for HTML. Workaround would be to force // singleFile uploads. // TODO: // Investigate removing fileInputs and resending form // multiple times adding each fileInput // if(this.selectMultipleFiles){ domConstruct.destroy(this._fileInput); } this._setHtmlPostData(); if(this.showProgress){ this._animateProgress(); } var dfd = ioIframe.send({ url: this.uploadUrl.toString(), form: this._formNode, handleAs: "json", error: lang.hitch(this, function(err){ this._error("HTML Upload Error:" + err.message); }), load: lang.hitch(this, function(data, ioArgs, widgetRef){ this._complete(data); }) }); }, createHtmlUploader: function(){ // summary: // Internal. Fires of methods to build HTML Uploader. this._buildForm(); this._setFormStyle(); this._buildFileInput(); this._connectInput(); this._styleContent(); domStyle.set(this.insideNode, "visibility", "visible"); this.onReady(); }, _connectInput: function(){ // summary: // Internal. HTML Uploader connections. These get disconnected // after upload or if multi upload. this._disconnect(); this._cons.push(connect.connect(this._fileInput, "mouseover", this, function(evt){ domClass.add(this.domNode, this.hoverClass); this.onMouseOver(evt); })); this._cons.push(connect.connect(this._fileInput, "mouseout", this, function(evt){ setTimeout(lang.hitch(this, function(){ domClass.remove(this.domNode, this.activeClass); domClass.remove(this.domNode, this.hoverClass); this.onMouseOut(evt); this._checkHtmlCancel("off"); }), 0); })); this._cons.push(connect.connect(this._fileInput, "mousedown", this, function(evt){ domClass.add(this.domNode, this.activeClass); domClass.remove(this.domNode, this.hoverClass); this.onMouseDown(evt); })); this._cons.push(connect.connect(this._fileInput, "mouseup", this, function(evt){ domClass.remove(this.domNode, this.activeClass); this.onMouseUp(evt); this.onClick(evt); this._checkHtmlCancel("up"); })); this._cons.push(connect.connect(this._fileInput, "change", this, function(){ this._checkHtmlCancel("change"); this._change([{ name: this._fileInput.value, type: "", size: 0 }]); })); if(this.tabIndex>=0){ domAttr.set(this.domNode, "tabIndex", this.tabIndex); } }, _checkHtmlCancel: function(mouseType){ // summary: // Internal. Check if the dialog was opened and canceled without file selection. if(mouseType == "change"){ this.dialogIsOpen = false; } if(mouseType == "up"){ this.dialogIsOpen = true; } if(mouseType == "off"){ if(this.dialogIsOpen){ this.onCancel(); } this.dialogIsOpen = false; } }, _styleContent: function(){ // summary: // Internal.Apply style to node var o = this.fhtml.nr; domStyle.set(this.insideNode, { width:o.w+"px", height:o.va == "middle"?o.h+"px":"auto", textAlign:o.ta, paddingTop:o.p[0]+"px", paddingRight:o.p[1]+"px", paddingBottom:o.p[2]+"px", paddingLeft:o.p[3]+"px" }); try{ domStyle.set(this.insideNode, "lineHeight", "inherit"); }catch(e){ // There are certain cases where IE refuses to set lineHeight. // For the life of me I cannot figure out the combination of // styles that IE doesn't like. Steaming... Pile... } }, _resetHTML: function(){ // summary: // Internal. After upload, this is called to clear the form and build a new // fileInput. if(this.uploaderType == "html" && this._formNode){ this.fileInputs = []; query("*", this._formNode).forEach(function(n){ domConstruct.destroy(n); }); this.fileCount = 0; this._buildFileInput(); this._connectInput(); } }, _buildForm: function(){ // summary: // Build the form that holds the fileInput // if(this._formNode){ return; } if(has("ie") < 9 || (has("ie") && has("quirks"))){ this._formNode = document.createElement('
'); this._formNode.encoding = "multipart/form-data"; this._formNode.id = manager.getUniqueId("FileUploaderForm"); // needed for dynamic style this.domNode.appendChild(this._formNode); }else{ this._formNode = domConstruct.create('form', { enctype:"multipart/form-data", method:"post", id:manager.getUniqueId("FileUploaderForm") }, this.domNode); } }, _buildFileInput: function(){ // summary: // Build the fileInput field // if(this._fileInput){ this._disconnect(); // FIXME: // Just hiding it which works, but we lose // reference to it and can't remove it from // the upload list. this._fileInput.id = this._fileInput.id + this.fileCount; domStyle.set(this._fileInput, "display", "none"); } this._fileInput = document.createElement('input'); this.fileInputs.push(this._fileInput); // server will need to know this variable: var nm = this.htmlFieldName; var _id = this.id; if(this.selectMultipleFiles){ nm += this.fileCount; _id += this.fileCount; this.fileCount++; } domAttr.set(this._fileInput, { id:this.id, name:nm, type:"file" }); domClass.add(this._fileInput, "dijitFileInputReal"); this._formNode.appendChild(this._fileInput); var real = domGeometry.getMarginBox(this._fileInput); domStyle.set(this._fileInput, { position:"relative", left:(this.fhtml.nr.w - real.w) + "px", opacity:0 }); }, _renumberInputs: function(){ if(!this.selectMultipleFiles){ return; } var nm; this.fileCount = 0; array.forEach(this.fileInputs, function(inp){ nm = this.htmlFieldName + this.fileCount; this.fileCount++; domAttr.set(inp, "name", nm); }, this); }, _setFormStyle: function(){ // summary: // Apply a dynamic style to the form and input var size = Math.max(2, Math.max(Math.ceil(this.fhtml.nr.w / 60), Math.ceil(this.fhtml.nr.h / 15))); // Now create a style associated with the form ID htmlStyles.insertCssRule("#" + this._formNode.id + " input", "font-size:" + size + "em"); domStyle.set(this.domNode, { overflow:"hidden", position:"relative" }); domStyle.set(this.insideNode, "position", "absolute"); }, _setHtmlPostData: function(){ // summary: // Internal.Apply postData to hidden fields in form if(this.postData){ for(var nm in this.postData){ domConstruct.create("input", { type: "hidden", name: nm, value: this.postData[nm] }, this._formNode); } } }, /************************* * FLASH * *************************/ uploadFlash: function(){ // summary: // Internal. You should use upload() or submit(); try{ if(this.showProgress){ this._displayProgress(true); var c = connect.connect(this, "_complete", this, function(){ connect.disconnect(c); this._displayProgress(false); }); } var o = {}; for(var nm in this.postData){ o[nm] = this.postData[nm]; } this.flashMovie.doUpload(o); }catch(err){ this._error("FileUploader - Sorry, the SWF failed to initialize." + err); } }, createFlashUploader: function(){ // summary: // Internal. Creates Flash Uploader this.uploadUrl = this.uploadUrl.toString(); if(this.uploadUrl){ if(this.uploadUrl.toLowerCase().indexOf("http")<0 && this.uploadUrl.indexOf("/")!=0){ // Appears to be a relative path. Attempt to // convert it to absolute, so it will better //target the SWF. // var loc = window.location.href.split("/"); loc.pop(); loc = loc.join("/")+"/"; this.uploadUrl = loc+this.uploadUrl; this.log("SWF Fixed - Relative loc:", loc, " abs loc:", this.uploadUrl); }else{ this.log("SWF URL unmodified:", this.uploadUrl) } }else{ console.warn("Warning: no uploadUrl provided."); } var w = this.fhtml.nr.w; var h = this.fhtml.nr.h; var args = { expressInstall:true, path: this.swfPath.uri || this.swfPath, width: w, height: h, allowScriptAccess:"always", allowNetworking:"all", vars: { uploadDataFieldName: this.flashFieldName, uploadUrl: this.uploadUrl, uploadOnSelect: this.uploadOnChange, deferredUploading:this.deferredUploading || 0, selectMultipleFiles: this.selectMultipleFiles, id: this.id, isDebug: this.isDebug, devMode:this.devMode, flashButton:embedFlashVars.serialize("fh", this.fhtml), fileMask:embedFlashVars.serialize("fm", this.fileMask), noReturnCheck: this.skipServerCheck, serverTimeout:this.serverTimeout }, params: { scale:"noscale", wmode:"opaque", allowScriptAccess:"always", allowNetworking:"all" } }; this.flashObject = new embedFlash(args, this.insideNode); this.flashObject.onError = lang.hitch(function(msg){ this._error("Flash Error: " + msg); }); this.flashObject.onReady = lang.hitch(this, function(){ domStyle.set(this.insideNode, "visibility", "visible"); this.log("FileUploader flash object ready"); this.onReady(this); }); this.flashObject.onLoad = lang.hitch(this, function(mov){ this.flashMovie = mov; this.flashReady = true; this.onLoad(this); }); this._connectFlash(); }, _connectFlash: function(){ // summary: // Subscribing to published topics coming from the // Flash uploader. // description: // Sacrificing some readbilty for compactness. this.id // will be on the beginning of the topic, so more than // one uploader can be on a page and can have unique calls. // this._doSub("/filesSelected", "_change"); this._doSub("/filesUploaded", "_complete"); this._doSub("/filesProgress", "_progress"); this._doSub("/filesError", "_error"); this._doSub("/filesCanceled", "onCancel"); this._doSub("/stageBlur", "_onFlashBlur"); this._doSub("/up", "onMouseUp"); this._doSub("/down", "onMouseDown"); this._doSub("/over", "onMouseOver"); this._doSub("/out", "onMouseOut"); this.connect(this.domNode, "focus", function(){ // TODO: some kind of indicator that the Flash button // is in focus this.flashMovie.focus(); this.flashMovie.doFocus(); }); if(this.tabIndex>=0){ domAttr.set(this.domNode, "tabIndex", this.tabIndex); } }, _doSub: function(subStr, funcStr){ // summary: // Internal. Shortcut for subscribes to Flash movie this._subs.push(connect.subscribe(this.id + subStr, this, funcStr)); }, /************************************* * DOM INSPECTION METHODS * *************************************/ urlencode: function(url){ // Using symbols in place of URL chars that will break in Flash serialization. if(!url || url == "none"){ return false; } return url.replace(/:/g,"||").replace(/\./g,"^^").replace("url(", "").replace(")","").replace(/'/g,"").replace(/"/g,""); }, isButton: function(node){ // testing if button for styling purposes var tn = node.tagName.toLowerCase(); return tn == "button" || tn == "input"; }, getTextStyle: function(node){ // getting font info var o = {}; o.ff = domStyle.get(node, "fontFamily"); if(o.ff){ o.ff = o.ff.replace(", ", ","); // remove spaces. IE in Flash no likee o.ff = o.ff.replace(/\"|\'/g, ""); o.ff = o.ff == "sans-serif" ? "Arial" : o.ff; // Flash doesn't know what sans-serif is o.fw = domStyle.get(node, "fontWeight"); o.fi = domStyle.get(node, "fontStyle"); o.fs = parseInt(domStyle.get(node, "fontSize"), 10); if(domStyle.get(node, "fontSize").indexOf("%") > -1){ // IE doesn't convert % to px. For god sakes. var n = node; while(n.tagName){ if(domStyle.get(n, "fontSize").indexOf("%") == -1){ o.fs = parseInt(domStyle.get(n, "fontSize"), 10); break; } if(n.tagName.toLowerCase()=="body"){ // if everyting is %, the the font size is 16px * the % o.fs = 16 * .01 * parseInt(domStyle.get(n, "fontSize"), 10); } n = n.parentNode; } } o.fc = new Color(domStyle.get(node, "color")).toHex(); o.fc = parseInt(o.fc.substring(1,Infinity),16); } o.lh = domStyle.get(node, "lineHeight"); o.ta = domStyle.get(node, "textAlign"); o.ta = o.ta == "start" || !o.ta ? "left" : o.ta; o.va = this.isButton(node) ? "middle" : o.lh == o.h ? "middle" : domStyle.get(node, "verticalAlign"); return o; }, getText: function(node){ // Get the text of the button. It's possible to use HTML in the Flash Button, // but the results are not spectacular. var cn = lang.trim(node.innerHTML); if(cn.indexOf("<") >- 1){ cn = escape(cn); } return cn; }, getStyle: function(node){ // getting the style of a node. Using very abbreviated characters which the // Flash movie understands. var o = {}; var dim = domGeometry.getContentBox(node); var pad = domGeometry.getPadExtents(node); o.p = [pad.t, pad.w-pad.l, pad.h-pad.t, pad.l]; o.w = dim.w + pad.w; o.h = dim.h + pad.h; o.d = domStyle.get(node, "display"); var clr = new Color(domStyle.get(node, "backgroundColor")); // if no color, Safari sets #000000 and alpha=0 since we don't support alpha, // it makes black - make it white o.bc = clr.a == 0 ? "#ffffff" : clr.toHex(); o.bc = parseInt(o.bc.substring(1,Infinity),16); var url = this.urlencode(domStyle.get(node, "backgroundImage")); if(url){ o.bi = { url:url, rp:domStyle.get(node, "backgroundRepeat"), pos: escape(domStyle.get(node, "backgroundPosition")) }; if(!o.bi.pos){ // IE does Xpx and Ypx, not "X% Y%" var rx = domStyle.get(node, "backgroundPositionX"); var ry = domStyle.get(node, "backgroundPositionY"); rx = (rx == "left") ? "0%" : (rx == "right") ? "100%" : rx; ry = (ry == "top") ? "0%" : (ry == "bottom") ? "100%" : ry; o.bi.pos = escape(rx+" "+ry); } } return lang.mixin(o, this.getTextStyle(node)); }, getTempNodeStyle: function(node, _class, isDijitButton){ // This sets up a temp node to get the style of the hover, active, and disabled states var temp, style; if(isDijitButton){ // backwards compat until dojo 1.5 temp = domConstruct.place("<"+node.tagName+">"+node.innerHTML+"", node.parentNode); //+" "+_class+" var first = temp.firstChild; domClass.add(first, node.className); domClass.add(temp, _class); style = this.getStyle(first); }else{ temp = domConstruct.place("<"+node.tagName+">"+node.innerHTML+"", node.parentNode); domClass.add(temp, node.className); domClass.add(temp, _class); temp.id = node.id; style = this.getStyle(temp); } // dev note: comment out this line to see what the // button states look like to the FileUploader domConstruct.destroy(temp); return style; } }); return dojox.form.FileUploader; });