ajaxupload.3.5.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  1. /**
  2. * Ajax upload
  3. * Project page - http://valums.com/ajax-upload/
  4. * Copyright (c) 2008 Andris Valums, http://valums.com
  5. * Licensed under the MIT license (http://valums.com/mit-license/)
  6. * Version 3.5 (23.06.2009)
  7. */
  8. /**
  9. * Changes from the previous version:
  10. * 1. Added better JSON handling that allows to use 'application/javascript' as a response
  11. * 2. Added demo for usage with jQuery UI dialog
  12. * 3. Fixed IE "mixed content" issue when used with secure connections
  13. *
  14. * For the full changelog please visit:
  15. * http://valums.com/ajax-upload-changelog/
  16. */
  17. (function(){
  18. var d = document, w = window;
  19. /**
  20. * Get element by id
  21. */
  22. function get(element){
  23. if (typeof element == "string")
  24. element = d.getElementById(element);
  25. return element;
  26. }
  27. /**
  28. * Attaches event to a dom element
  29. */
  30. function addEvent(el, type, fn){
  31. if (w.addEventListener){
  32. el.addEventListener(type, fn, false);
  33. } else if (w.attachEvent){
  34. var f = function(){
  35. fn.call(el, w.event);
  36. };
  37. el.attachEvent('on' + type, f)
  38. }
  39. }
  40. /**
  41. * Creates and returns element from html chunk
  42. */
  43. var toElement = function(){
  44. var div = d.createElement('div');
  45. return function(html){
  46. div.innerHTML = html;
  47. var el = div.childNodes[0];
  48. div.removeChild(el);
  49. return el;
  50. }
  51. }();
  52. function hasClass(ele,cls){
  53. return ele.className.match(new RegExp('(\\s|^)'+cls+'(\\s|$)'));
  54. }
  55. function addClass(ele,cls) {
  56. if (!hasClass(ele,cls)) ele.className += " "+cls;
  57. }
  58. function removeClass(ele,cls) {
  59. var reg = new RegExp('(\\s|^)'+cls+'(\\s|$)');
  60. ele.className=ele.className.replace(reg,' ');
  61. }
  62. // getOffset function copied from jQuery lib (http://jquery.com/)
  63. if (document.documentElement["getBoundingClientRect"]){
  64. // Get Offset using getBoundingClientRect
  65. // http://ejohn.org/blog/getboundingclientrect-is-awesome/
  66. var getOffset = function(el){
  67. var box = el.getBoundingClientRect(),
  68. doc = el.ownerDocument,
  69. body = doc.body,
  70. docElem = doc.documentElement,
  71. // for ie
  72. clientTop = docElem.clientTop || body.clientTop || 0,
  73. clientLeft = docElem.clientLeft || body.clientLeft || 0,
  74. // In Internet Explorer 7 getBoundingClientRect property is treated as physical,
  75. // while others are logical. Make all logical, like in IE8.
  76. zoom = 1;
  77. if (body.getBoundingClientRect) {
  78. var bound = body.getBoundingClientRect();
  79. zoom = (bound.right - bound.left)/body.clientWidth;
  80. }
  81. if (zoom > 1){
  82. clientTop = 0;
  83. clientLeft = 0;
  84. }
  85. var top = box.top/zoom + (window.pageYOffset || docElem && docElem.scrollTop/zoom || body.scrollTop/zoom) - clientTop,
  86. left = box.left/zoom + (window.pageXOffset|| docElem && docElem.scrollLeft/zoom || body.scrollLeft/zoom) - clientLeft;
  87. return {
  88. top: top,
  89. left: left
  90. };
  91. }
  92. } else {
  93. // Get offset adding all offsets
  94. var getOffset = function(el){
  95. if (w.jQuery){
  96. return jQuery(el).offset();
  97. }
  98. var top = 0, left = 0;
  99. do {
  100. top += el.offsetTop || 0;
  101. left += el.offsetLeft || 0;
  102. }
  103. while (el = el.offsetParent);
  104. return {
  105. left: left,
  106. top: top
  107. };
  108. }
  109. }
  110. function getBox(el){
  111. var left, right, top, bottom;
  112. var offset = getOffset(el);
  113. left = offset.left;
  114. top = offset.top;
  115. right = left + el.offsetWidth;
  116. bottom = top + el.offsetHeight;
  117. return {
  118. left: left,
  119. right: right,
  120. top: top,
  121. bottom: bottom
  122. };
  123. }
  124. /**
  125. * Crossbrowser mouse coordinates
  126. */
  127. function getMouseCoords(e){
  128. // pageX/Y is not supported in IE
  129. // http://www.quirksmode.org/dom/w3c_cssom.html
  130. if (!e.pageX && e.clientX){
  131. // In Internet Explorer 7 some properties (mouse coordinates) are treated as physical,
  132. // while others are logical (offset).
  133. var zoom = 1;
  134. var body = document.body;
  135. if (body.getBoundingClientRect) {
  136. var bound = body.getBoundingClientRect();
  137. zoom = (bound.right - bound.left)/body.clientWidth;
  138. }
  139. return {
  140. x: e.clientX / zoom + d.body.scrollLeft + d.documentElement.scrollLeft,
  141. y: e.clientY / zoom + d.body.scrollTop + d.documentElement.scrollTop
  142. };
  143. }
  144. return {
  145. x: e.pageX,
  146. y: e.pageY
  147. };
  148. }
  149. /**
  150. * Function generates unique id
  151. */
  152. var getUID = function(){
  153. var id = 0;
  154. return function(){
  155. return 'ValumsAjaxUpload' + id++;
  156. }
  157. }();
  158. function fileFromPath(file){
  159. return file.replace(/.*(\/|\\)/, "");
  160. }
  161. function getExt(file){
  162. return (/[.]/.exec(file)) ? /[^.]+$/.exec(file.toLowerCase()) : '';
  163. }
  164. // Please use AjaxUpload , Ajax_upload will be removed in the next version
  165. Ajax_upload = AjaxUpload = function(button, options){
  166. if (button.jquery){
  167. // jquery object was passed
  168. button = button[0];
  169. } else if (typeof button == "string" && /^#.*/.test(button)){
  170. button = button.slice(1);
  171. }
  172. button = get(button);
  173. this._input = null;
  174. this._button = button;
  175. this._disabled = false;
  176. this._submitting = false;
  177. // Variable changes to true if the button was clicked
  178. // 3 seconds ago (requred to fix Safari on Mac error)
  179. this._justClicked = false;
  180. this._parentDialog = d.body;
  181. if (window.jQuery && jQuery.ui && jQuery.ui.dialog){
  182. var parentDialog = jQuery(this._button).parents('.ui-dialog');
  183. if (parentDialog.length){
  184. this._parentDialog = parentDialog[0];
  185. }
  186. }
  187. this._settings = {
  188. // Location of the server-side upload script
  189. action: 'upload.php',
  190. // File upload name
  191. name: 'userfile',
  192. // Additional data to send
  193. data: {},
  194. // Submit file as soon as it's selected
  195. autoSubmit: true,
  196. // The type of data that you're expecting back from the server.
  197. // Html and xml are detected automatically.
  198. // Only useful when you are using json data as a response.
  199. // Set to "json" in that case.
  200. responseType: false,
  201. // When user selects a file, useful with autoSubmit disabled
  202. onChange: function(file, extension){},
  203. // Callback to fire before file is uploaded
  204. // You can return false to cancel upload
  205. onSubmit: function(file, extension){},
  206. // Fired when file upload is completed
  207. // WARNING! DO NOT USE "FALSE" STRING AS A RESPONSE!
  208. onComplete: function(file, response) {}
  209. };
  210. // Merge the users options with our defaults
  211. for (var i in options) {
  212. this._settings[i] = options[i];
  213. }
  214. this._createInput();
  215. this._rerouteClicks();
  216. }
  217. // assigning methods to our class
  218. AjaxUpload.prototype = {
  219. setData : function(data){
  220. this._settings.data = data;
  221. },
  222. disable : function(){
  223. this._disabled = true;
  224. },
  225. enable : function(){
  226. this._disabled = false;
  227. },
  228. // removes ajaxupload
  229. destroy : function(){
  230. if(this._input){
  231. if(this._input.parentNode){
  232. this._input.parentNode.removeChild(this._input);
  233. }
  234. this._input = null;
  235. }
  236. },
  237. /**
  238. * Creates invisible file input above the button
  239. */
  240. _createInput : function(){
  241. var self = this;
  242. var input = d.createElement("input");
  243. input.setAttribute('type', 'file');
  244. input.setAttribute('name', this._settings.name);
  245. var styles = {
  246. 'position' : 'absolute'
  247. ,'margin': '-5px 0 0 -175px'
  248. ,'padding': 0
  249. ,'width': '220px'
  250. ,'height': '30px'
  251. ,'fontSize': '14px'
  252. ,'opacity': 0
  253. ,'cursor': 'pointer'
  254. ,'display' : 'none'
  255. ,'zIndex' : 2147483583 //Max zIndex supported by Opera 9.0-9.2x
  256. // Strange, I expected 2147483647
  257. };
  258. for (var i in styles){
  259. input.style[i] = styles[i];
  260. }
  261. // Make sure that element opacity exists
  262. // (IE uses filter instead)
  263. if ( ! (input.style.opacity === "0")){
  264. input.style.filter = "alpha(opacity=0)";
  265. }
  266. this._parentDialog.appendChild(input);
  267. addEvent(input, 'change', function(){
  268. // get filename from input
  269. var file = fileFromPath(this.value);
  270. if(self._settings.onChange.call(self, file, getExt(file)) == false ){
  271. return;
  272. }
  273. // Submit form when value is changed
  274. if (self._settings.autoSubmit){
  275. self.submit();
  276. }
  277. });
  278. // Fixing problem with Safari
  279. // The problem is that if you leave input before the file select dialog opens
  280. // it does not upload the file.
  281. // As dialog opens slowly (it is a sheet dialog which takes some time to open)
  282. // there is some time while you can leave the button.
  283. // So we should not change display to none immediately
  284. addEvent(input, 'click', function(){
  285. self.justClicked = true;
  286. setTimeout(function(){
  287. // we will wait 3 seconds for dialog to open
  288. self.justClicked = false;
  289. }, 3000);
  290. });
  291. this._input = input;
  292. },
  293. _rerouteClicks : function (){
  294. var self = this;
  295. // IE displays 'access denied' error when using this method
  296. // other browsers just ignore click()
  297. // addEvent(this._button, 'click', function(e){
  298. // self._input.click();
  299. // });
  300. var box, dialogOffset = {top:0, left:0}, over = false;
  301. addEvent(self._button, 'mouseover', function(e){
  302. if (!self._input || over) return;
  303. over = true;
  304. box = getBox(self._button);
  305. if (self._parentDialog != d.body){
  306. dialogOffset = getOffset(self._parentDialog);
  307. }
  308. });
  309. // we can't use mouseout on the button,
  310. // because invisible input is over it
  311. addEvent(document, 'mousemove', function(e){
  312. var input = self._input;
  313. if (!input || !over) return;
  314. if (self._disabled){
  315. removeClass(self._button, 'hover');
  316. input.style.display = 'none';
  317. return;
  318. }
  319. var c = getMouseCoords(e);
  320. if ((c.x >= box.left) && (c.x <= box.right) &&
  321. (c.y >= box.top) && (c.y <= box.bottom)){
  322. input.style.top = c.y - dialogOffset.top + 'px';
  323. input.style.left = c.x - dialogOffset.left + 'px';
  324. input.style.display = 'block';
  325. addClass(self._button, 'hover');
  326. } else {
  327. // mouse left the button
  328. over = false;
  329. if (!self.justClicked){
  330. input.style.display = 'none';
  331. }
  332. removeClass(self._button, 'hover');
  333. }
  334. });
  335. },
  336. /**
  337. * Creates iframe with unique name
  338. */
  339. _createIframe : function(){
  340. // unique name
  341. // We cannot use getTime, because it sometimes return
  342. // same value in safari :(
  343. var id = getUID();
  344. // Remove ie6 "This page contains both secure and nonsecure items" prompt
  345. // http://tinyurl.com/77w9wh
  346. var iframe = toElement('<iframe src="javascript:false;" name="' + id + '" />');
  347. iframe.id = id;
  348. iframe.style.display = 'none';
  349. d.body.appendChild(iframe);
  350. return iframe;
  351. },
  352. /**
  353. * Upload file without refreshing the page
  354. */
  355. submit : function(){
  356. var self = this, settings = this._settings;
  357. if (this._input.value === ''){
  358. // there is no file
  359. return;
  360. }
  361. // get filename from input
  362. var file = fileFromPath(this._input.value);
  363. // execute user event
  364. if (! (settings.onSubmit.call(this, file, getExt(file)) == false)) {
  365. // Create new iframe for this submission
  366. var iframe = this._createIframe();
  367. // Do not submit if user function returns false
  368. var form = this._createForm(iframe);
  369. form.appendChild(this._input);
  370. form.submit();
  371. d.body.removeChild(form);
  372. form = null;
  373. this._input = null;
  374. // create new input
  375. this._createInput();
  376. var toDeleteFlag = false;
  377. addEvent(iframe, 'load', function(e){
  378. if (// For Safari
  379. iframe.src == "javascript:'%3Chtml%3E%3C/html%3E';" ||
  380. // For FF, IE
  381. iframe.src == "javascript:'<html></html>';"){
  382. // First time around, do not delete.
  383. if( toDeleteFlag ){
  384. // Fix busy state in FF3
  385. setTimeout( function() {
  386. d.body.removeChild(iframe);
  387. }, 0);
  388. }
  389. return;
  390. }
  391. var doc = iframe.contentDocument ? iframe.contentDocument : frames[iframe.id].document;
  392. // fixing Opera 9.26
  393. if (doc.readyState && doc.readyState != 'complete'){
  394. // Opera fires load event multiple times
  395. // Even when the DOM is not ready yet
  396. // this fix should not affect other browsers
  397. return;
  398. }
  399. // fixing Opera 9.64
  400. if (doc.body && doc.body.innerHTML == "false"){
  401. // In Opera 9.64 event was fired second time
  402. // when body.innerHTML changed from false
  403. // to server response approx. after 1 sec
  404. return;
  405. }
  406. var response;
  407. if (doc.XMLDocument){
  408. // response is a xml document IE property
  409. response = doc.XMLDocument;
  410. } else if (doc.body){
  411. // response is html document or plain text
  412. response = doc.body.innerHTML;
  413. if (settings.responseType && settings.responseType.toLowerCase() == 'json'){
  414. // If the document was sent as 'application/javascript' or
  415. // 'text/javascript', then the browser wraps the text in a <pre>
  416. // tag and performs html encoding on the contents. In this case,
  417. // we need to pull the original text content from the text node's
  418. // nodeValue property to retrieve the unmangled content.
  419. // Note that IE6 only understands text/html
  420. if (doc.body.firstChild && doc.body.firstChild.nodeName.toUpperCase() == 'PRE'){
  421. response = doc.body.firstChild.firstChild.nodeValue;
  422. }
  423. if (response) {
  424. response = window["eval"]("(" + response + ")");
  425. } else {
  426. response = {};
  427. }
  428. }
  429. } else {
  430. // response is a xml document
  431. var response = doc;
  432. }
  433. settings.onComplete.call(self, file, response);
  434. // Reload blank page, so that reloading main page
  435. // does not re-submit the post. Also, remember to
  436. // delete the frame
  437. toDeleteFlag = true;
  438. // Fix IE mixed content issue
  439. iframe.src = "javascript:'<html></html>';";
  440. });
  441. } else {
  442. // clear input to allow user to select same file
  443. // Doesn't work in IE6
  444. // this._input.value = '';
  445. d.body.removeChild(this._input);
  446. this._input = null;
  447. // create new input
  448. this._createInput();
  449. }
  450. },
  451. /**
  452. * Creates form, that will be submitted to iframe
  453. */
  454. _createForm : function(iframe){
  455. var settings = this._settings;
  456. // method, enctype must be specified here
  457. // because changing this attr on the fly is not allowed in IE 6/7
  458. var form = toElement('<form method="post" enctype="multipart/form-data"></form>');
  459. form.style.display = 'none';
  460. form.action = settings.action;
  461. form.target = iframe.name;
  462. d.body.appendChild(form);
  463. // Create hidden input element for each data key
  464. for (var prop in settings.data){
  465. var el = d.createElement("input");
  466. el.type = 'hidden';
  467. el.name = prop;
  468. el.value = settings.data[prop];
  469. form.appendChild(el);
  470. }
  471. return form;
  472. }
  473. };
  474. })();