diff options
Diffstat (limited to 'js/dojo/dojox/form/FileUploader.js')
| -rw-r--r-- | js/dojo/dojox/form/FileUploader.js | 1445 |
1 files changed, 1445 insertions, 0 deletions
diff --git a/js/dojo/dojox/form/FileUploader.js b/js/dojo/dojox/form/FileUploader.js new file mode 100644 index 0000000..fad850e --- /dev/null +++ b/js/dojo/dojox/form/FileUploader.js @@ -0,0 +1,1445 @@ +//>>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:'<div><div dojoAttachPoint="progNode"><div dojoAttachPoint="progTextNode"></div></div><div dojoAttachPoint="insideNode" class="uploaderInsideNode"></div></div>', + + // 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 = '<button id="'+this.button.id+'" class="'+cls+'">'+txt+'</button>'; + 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 += '<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>' + }, 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<nodes.length; i++){ + if(nodes[i].tabIndex >= 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('<form enctype="multipart/form-data" method="post">'); + 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+"><span>"+node.innerHTML+"</span></"+node.tagName+">", 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.tagName+">", 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; +}); |
