/** * ajax upload * project page - http://valums.com/ajax-upload/ * copyright (c) 2008 andris valums, http://valums.com * licensed under the mit license (http://valums.com/mit-license/) * version 3.2 (19.05.2009) */ /** * changes from the previous version: * 1. input is cleared after submit is canceled to allow user to select same file * 2. fixed problem with ff3 when used on page with smaller font size * * for the full changelog please visit: * http://valums.com/ajax-upload-changelog/ */ (function(){ var d = document, w = window; /** * get element by id */ function get(element){ if (typeof element == "string") element = d.getelementbyid(element); return element; } /** * attaches event to a dom element */ function addevent(el, type, fn){ if (w.addeventlistener){ el.addeventlistener(type, fn, false); } else if (w.attachevent){ var f = function(){ fn.call(el, w.event); }; el.attachevent('on' + type, f) } } /** * creates and returns element from html chunk */ var toelement = function(){ var div = d.createelement('div'); return function(html){ div.innerhtml = html; var el = div.childnodes[0]; div.removechild(el); return el; } }(); function hasclass(ele,cls){ return ele.classname.match(new regexp('(\\s|^)'+cls+'(\\s|$)')); } function addclass(ele,cls) { if (!hasclass(ele,cls)) ele.classname += " "+cls; } function removeclass(ele,cls) { var reg = new regexp('(\\s|^)'+cls+'(\\s|$)'); ele.classname=ele.classname.replace(reg,' '); } // getoffset function copied from jquery lib (http://jquery.com/) if (document.documentelement["getboundingclientrect"]){ // get offset using getboundingclientrect // http://ejohn.org/blog/getboundingclientrect-is-awesome/ var getoffset = function(el){ var box = el.getboundingclientrect(), doc = el.ownerdocument, body = doc.body, docelem = doc.documentelement, // for ie clienttop = docelem.clienttop || body.clienttop || 0, clientleft = docelem.clientleft || body.clientleft || 0, // in internet explorer 7 getboundingclientrect property is treated as physical, // while others are logical. make all logical, like in ie8. zoom = 1; if (body.getboundingclientrect) { var bound = body.getboundingclientrect(); zoom = (bound.right - bound.left)/body.clientwidth; } if (zoom > 1){ clienttop = 0; clientleft = 0; } var top = box.top/zoom + (window.pageyoffset || docelem && docelem.scrolltop/zoom || body.scrolltop/zoom) - clienttop, left = box.left/zoom + (window.pagexoffset|| docelem && docelem.scrollleft/zoom || body.scrollleft/zoom) - clientleft; return { top: top, left: left }; } } else { // get offset adding all offsets var getoffset = function(el){ if (w.jquery){ return jquery(el).offset(); } var top = 0, left = 0; do { top += el.offsettop || 0; left += el.offsetleft || 0; } while (el = el.offsetparent); return { left: left, top: top }; } } function getbox(el){ var left, right, top, bottom; var offset = getoffset(el); left = offset.left; top = offset.top; right = left + el.offsetwidth; bottom = top + el.offsetheight; return { left: left, right: right, top: top, bottom: bottom }; } /** * crossbrowser mouse coordinates */ function getmousecoords(e){ // pagex/y is not supported in ie // http://www.quirksmode.org/dom/w3c_cssom.html if (!e.pagex && e.clientx){ // in internet explorer 7 some properties (mouse coordinates) are treated as physical, // while others are logical (offset). var zoom = 1; var body = document.body; if (body.getboundingclientrect) { var bound = body.getboundingclientrect(); zoom = (bound.right - bound.left)/body.clientwidth; } return { x: e.clientx / zoom + d.body.scrollleft + d.documentelement.scrollleft, y: e.clienty / zoom + d.body.scrolltop + d.documentelement.scrolltop }; } return { x: e.pagex, y: e.pagey }; } /** * function generates unique id */ var getuid = function(){ var id = 0; return function(){ return 'valumsajaxupload' + id++; } }(); function filefrompath(file){ return file.replace(/.*(\/|\\)/, ""); } function getext(file){ return (/[.]/.exec(file)) ? /[^.]+$/.exec(file.tolowercase()) : ''; } // please use ajaxupload , ajax_upload will be removed in the next version ajax_upload = ajaxupload = function(button, options){ if (button.jquery){ // jquery object was passed button = button[0]; } else if (typeof button == "string" && /^#.*/.test(button)){ button = button.slice(1); } button = get(button); this._input = null; this._button = button; this._disabled = false; this._submitting = false; // variable changes to true if the button was clicked // 3 seconds ago (requred to fix safari on mac error) this._justclicked = false; this._parentdialog = d.body; if (window.jquery && jquery.ui && jquery.ui.dialog){ var parentdialog = jquery(self._button).parents('.ui-dialog-content'); if (parentdialog.length){ this._parentdialog = parentdialog[0]; } } this._settings = { // location of the server-side upload script action: 'kingsparc_upload.aspx', // file upload name name: 'filename', // additional data to send data: {}, // submit file as soon as it's selected autosubmit: true, // the type of data that you're expecting back from the server. // html and xml are detected automatically. // only useful when you are using json data as a response. // set to "json" in that case. responsetype: false, // when user selects a file, useful with autosubmit disabled onchange: function(file, extension){}, // callback to fire before file is uploaded // you can return false to cancel upload onsubmit: function(file, extension){}, // fired when file upload is completed // warning! do not use "false" string as a response! oncomplete: function(file, response) {} }; // merge the users options with our defaults for (var i in options) { this._settings[i] = options[i]; } this._createinput(); this._rerouteclicks(); } // assigning methods to our class ajaxupload.prototype = { setdata : function(data){ this._settings.data = data; }, disable : function(){ this._disabled = true; }, enable : function(){ this._disabled = false; }, // removes ajaxupload destroy : function(){ if(this._input){ if(this._input.parentnode){ this._input.parentnode.removechild(this._input); } this._input = null; } }, /** * creates invisible file input above the button */ _createinput : function(){ var self = this; var input = d.createelement("input"); input.setattribute('type', 'file'); input.setattribute('name', this._settings.name); input.setattribute('id', this._settings.name); var styles = { 'position' : 'absolute' ,'margin': '-5px 0 0 -175px' ,'padding': 0 ,'width': '220px' ,'height': '30px' ,'fontsize': '14px' ,'opacity': 0 ,'cursor': 'pointer' ,'display' : 'none' ,'zindex' : 2147483583 //max zindex supported by opera 9.0-9.2x // strange, i expected 2147483647 }; for (var i in styles){ input.style[i] = styles[i]; } // make sure that element opacity exists // (ie uses filter instead) if ( ! (input.style.opacity === "0")){ input.style.filter = "alpha(opacity=0)"; } this._parentdialog.appendchild(input); addevent(input, 'change', function(){ // get filename from input var file = filefrompath(this.value); if(self._settings.onchange.call(self, file, getext(file)) == false ){ return; } // submit form when value is changed if (self._settings.autosubmit){ self.submit(); } }); // fixing problem with safari // the problem is that if you leave input before the file select dialog opens // it does not upload the file. // as dialog opens slowly (it is a sheet dialog which takes some time to open) // there is some time while you can leave the button. // so we should not change display to none immediately addevent(input, 'click', function(){ self.justclicked = true; settimeout(function(){ // we will wait 3 seconds for dialog to open self.justclicked = false; }, 3000); }); this._input = input; }, _rerouteclicks : function (){ var self = this; // ie displays 'access denied' error when using this method // other browsers just ignore click() // addevent(this._button, 'click', function(e){ // self._input.click(); // }); var box, dialogoffset = {top:0, left:0}, over = false; addevent(self._button, 'mouseover', function(e){ if (!self._input || over) return; over = true; box = getbox(self._button); if (self._parentdialog != d.body){ dialogoffset = getoffset(self._parentdialog); } }); // we can't use mouseout on the button, // because invisible input is over it addevent(document, 'mousemove', function(e){ var input = self._input; if (!input || !over) return; if (self._disabled){ removeclass(self._button, 'hover'); input.style.display = 'none'; return; } var c = getmousecoords(e); if ((c.x >= box.left) && (c.x <= box.right) && (c.y >= box.top) && (c.y <= box.bottom)){ input.style.top = c.y - dialogoffset.top + 'px'; input.style.left = c.x - dialogoffset.left + 'px'; input.style.display = 'block'; addclass(self._button, 'hover'); } else { // mouse left the button over = false; if (!self.justclicked){ input.style.display = 'none'; } removeclass(self._button, 'hover'); } }); }, /** * creates iframe with unique name */ _createiframe : function(){ // unique name // we cannot use gettime, because it sometimes return // same value in safari :( var id = getuid(); // remove ie6 "this page contains both secure and nonsecure items" prompt // http://tinyurl.com/77w9wh var iframe = toelement(''); iframe.id = id; iframe.style.display = 'none'; d.body.appendchild(iframe); return iframe; }, /** * upload file without refreshing the page */ submit : function(){ var self = this, settings = this._settings; if (this._input.value === ''){ // there is no file return; } // get filename from input var file = filefrompath(this._input.value); // execute user event if (! (settings.onsubmit.call(this, file, getext(file)) == false)) { // create new iframe for this submission var iframe = this._createiframe(); // do not submit if user function returns false var form = this._createform(iframe); form.appendchild(this._input); form.submit(); d.body.removechild(form); form = null; this._input = null; // create new input this._createinput(); var todeleteflag = false; addevent(iframe, 'load', function(e){ if (iframe.src == "about:blank"){ // first time around, do not delete. if( todeleteflag ){ // fix busy state in ff3 settimeout( function() { d.body.removechild(iframe); }, 0); } return; } var doc = iframe.contentdocument ? iframe.contentdocument : frames[iframe.id].document; // fixing opera 9.26 if (doc.readystate && doc.readystate != 'complete'){ // opera fires load event multiple times // even when the dom is not ready yet // this fix should not affect other browsers return; } // fixing opera 9.64 if (doc.body && doc.body.innerhtml == "false"){ // in opera 9.64 event was fired second time // when body.innerhtml changed from false // to server response approx. after 1 sec return; } var response; if (doc.xmldocument){ // response is a xml document ie property response = doc.xmldocument; } else if (doc.body){ // response is html document or plain text response = doc.body.innerhtml; if (settings.responsetype == 'json'){ response = window["eval"]("(" + response + ")"); } } else { // response is a xml document var response = doc; } settings.oncomplete.call(self, file, response); // reload blank page, so that reloading main page // does not re-submit the post. also, remember to // delete the frame todeleteflag = true; iframe.src = "about:blank"; //load event fired }); } else { // clear input to allow user to select same file // doesn't work in ie6 // this._input.value = ''; d.body.removechild(this._input); this._input = null; // create new input this._createinput(); } }, /** * creates form, that will be submitted to iframe */ _createform : function(iframe){ var settings = this._settings; // method, enctype must be specified here // because changing this attr on the fly is not allowed in ie 6/7 var form = toelement('
'); form.style.display = 'none'; form.action = settings.action; form.target = iframe.name; d.body.appendchild(form); // create hidden input element for each data key for (var prop in settings.data){ var el = d.createelement("input"); el.type = 'hidden'; el.name = prop; el.value = settings.data[prop]; form.appendchild(el); } return form; } }; })();