PageRenderTime 50ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/org.hxzon.tapestry5/org/hxzon/tapestry5/components/ajaxupload.js

https://gitlab.com/mstar-hxzon/mstar-mybatis-hxzon
JavaScript | 1664 lines | 1243 code | 191 blank | 230 comment | 161 complexity | af87057368d33ce32946e4077f77d933 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. /**
  2. * http://github.com/valums/file-uploader
  3. *
  4. * Multiple file upload component with progress-bar, drag-and-drop. Š 2010
  5. * Andrew Valums ( andrew(at)valums.com )
  6. *
  7. * Licensed under GNU GPL 2 or later and GNU LGPL 2 or later, see license.txt.
  8. */
  9. //
  10. // Helper functions
  11. //
  12. var qq = qq || {};
  13. /**
  14. * Adds all missing properties from second obj to first obj
  15. */
  16. qq.extend = function(first, second)
  17. {
  18. for ( var prop in second)
  19. {
  20. first[prop] = second[prop];
  21. }
  22. };
  23. /**
  24. * Searches for a given element in the array, returns -1 if it is not present.
  25. *
  26. * @param {Number}
  27. * [from] The index at which to begin the search
  28. */
  29. qq.indexOf = function(arr, elt, from)
  30. {
  31. if (arr.indexOf)
  32. return arr.indexOf(elt, from);
  33. from = from || 0;
  34. var len = arr.length;
  35. if (from < 0)
  36. from += len;
  37. for (; from < len; from++)
  38. {
  39. if (from in arr && arr[from] === elt)
  40. {
  41. return from;
  42. }
  43. }
  44. return -1;
  45. };
  46. qq.getUniqueId = (function()
  47. {
  48. var id = 0;
  49. return function()
  50. {
  51. return id++;
  52. };
  53. })();
  54. //
  55. // Events
  56. qq.attach = function(element, type, fn)
  57. {
  58. if (element.addEventListener)
  59. {
  60. element.addEventListener(type, fn, false);
  61. }
  62. else if (element.attachEvent)
  63. {
  64. element.attachEvent('on' + type, fn);
  65. }
  66. };
  67. qq.detach = function(element, type, fn)
  68. {
  69. if (element.removeEventListener)
  70. {
  71. element.removeEventListener(type, fn, false);
  72. }
  73. else if (element.attachEvent)
  74. {
  75. element.detachEvent('on' + type, fn);
  76. }
  77. };
  78. qq.preventDefault = function(e)
  79. {
  80. if (e.preventDefault)
  81. {
  82. e.preventDefault();
  83. }
  84. else
  85. {
  86. e.returnValue = false;
  87. }
  88. };
  89. //
  90. // Node manipulations
  91. /**
  92. * Insert node a before node b.
  93. */
  94. qq.insertBefore = function(a, b)
  95. {
  96. b.parentNode.insertBefore(a, b);
  97. };
  98. qq.remove = function(element)
  99. {
  100. element.parentNode.removeChild(element);
  101. };
  102. qq.contains = function(parent, descendant)
  103. {
  104. // compareposition returns false in this case
  105. if (parent == descendant)
  106. return true;
  107. if (parent.contains)
  108. {
  109. return parent.contains(descendant);
  110. }
  111. else
  112. {
  113. return !!(descendant.compareDocumentPosition(parent) & 8);
  114. }
  115. };
  116. /**
  117. * Creates and returns element from html string Uses innerHTML to create an
  118. * element
  119. */
  120. qq.toElement = (function()
  121. {
  122. var div = document.createElement('div');
  123. return function(html)
  124. {
  125. div.innerHTML = html;
  126. var element = div.firstChild;
  127. div.removeChild(element);
  128. return element;
  129. };
  130. })();
  131. //
  132. // Node properties and attributes
  133. /**
  134. * Sets styles for an element. Fixes opacity in IE6-8.
  135. */
  136. qq.css =
  137. function(element, styles)
  138. {
  139. if (styles.opacity != null)
  140. {
  141. if (typeof element.style.opacity != 'string'
  142. && typeof (element.filters) != 'undefined')
  143. {
  144. styles.filter =
  145. 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
  146. }
  147. }
  148. qq.extend(element.style, styles);
  149. };
  150. qq.hasClass = function(element, name)
  151. {
  152. var re = new RegExp('(^| )' + name + '( |$)');
  153. return re.test(element.className);
  154. };
  155. qq.addClass = function(element, name)
  156. {
  157. if (!qq.hasClass(element, name))
  158. {
  159. element.className += ' ' + name;
  160. }
  161. };
  162. qq.removeClass =
  163. function(element, name)
  164. {
  165. var re = new RegExp('(^| )' + name + '( |$)');
  166. element.className =
  167. element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
  168. };
  169. qq.setText = function(element, text)
  170. {
  171. element.innerText = text;
  172. element.textContent = text;
  173. };
  174. //
  175. // Selecting elements
  176. qq.children = function(element)
  177. {
  178. var children = [], child = element.firstChild;
  179. while (child)
  180. {
  181. if (child.nodeType == 1)
  182. {
  183. children.push(child);
  184. }
  185. child = child.nextSibling;
  186. }
  187. return children;
  188. };
  189. qq.getByClass = function(element, className)
  190. {
  191. if (element.querySelectorAll)
  192. {
  193. return element.querySelectorAll('.' + className);
  194. }
  195. var result = [];
  196. var candidates = element.getElementsByTagName("*");
  197. var len = candidates.length;
  198. for ( var i = 0; i < len; i++)
  199. {
  200. if (qq.hasClass(candidates[i], className))
  201. {
  202. result.push(candidates[i]);
  203. }
  204. }
  205. return result;
  206. };
  207. /**
  208. * obj2url() takes a json-object as argument and generates a querystring. pretty
  209. * much like jQuery.param()
  210. *
  211. * how to use:
  212. *
  213. * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
  214. *
  215. * will result in:
  216. *
  217. * `http://any.url/upload?otherParam=value&a=b&c=d`
  218. *
  219. * @param Object
  220. * JSON-Object
  221. * @param String
  222. * current querystring-part
  223. * @return String encoded querystring
  224. */
  225. qq.obj2url =
  226. function(obj, temp, prefixDone)
  227. {
  228. var uristrings = [], prefix = '&', add =
  229. function(nextObj, i)
  230. {
  231. var nextTemp = temp ? (/\[\]$/.test(temp)) // prevent
  232. // double-encoding
  233. ? temp : temp + '[' + i + ']' : i;
  234. if ((nextTemp != 'undefined') && (i != 'undefined'))
  235. {
  236. uristrings
  237. .push((typeof nextObj === 'object')
  238. ? qq.obj2url(nextObj, nextTemp, true)
  239. : (Object.prototype.toString.call(nextObj) === '[object Function]')
  240. ? encodeURIComponent(nextTemp)
  241. + '='
  242. + encodeURIComponent(nextObj())
  243. : encodeURIComponent(nextTemp)
  244. + '='
  245. + encodeURIComponent(nextObj));
  246. }
  247. };
  248. if (!prefixDone && temp)
  249. {
  250. prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
  251. uristrings.push(temp);
  252. uristrings.push(qq.obj2url(obj));
  253. }
  254. else if ((Object.prototype.toString.call(obj) === '[object Array]')
  255. && (typeof obj != 'undefined'))
  256. {
  257. // we wont use a for-in-loop on an array (performance)
  258. for ( var i = 0, len = obj.length; i < len; ++i)
  259. {
  260. add(obj[i], i);
  261. }
  262. }
  263. else if ((typeof obj != 'undefined')
  264. && (obj !== null)
  265. && (typeof obj === "object"))
  266. {
  267. // for anything else but a scalar, we will use for-in-loop
  268. for ( var i in obj)
  269. {
  270. add(obj[i], i);
  271. }
  272. }
  273. else
  274. {
  275. uristrings.push(encodeURIComponent(temp)
  276. + '='
  277. + encodeURIComponent(obj));
  278. }
  279. return uristrings.join(prefix).replace(/^&/, '').replace(/%20/g, '+');
  280. };
  281. //
  282. //
  283. // Uploader Classes
  284. //
  285. //
  286. var qq = qq || {};
  287. /**
  288. * Creates upload button, validates upload, but doesn't create file list or dd.
  289. */
  290. qq.FileUploaderBasic =
  291. function(o)
  292. {
  293. this._options =
  294. {
  295. // set to true to see the server response
  296. debug : false,
  297. action : '/server/upload',
  298. params : {},
  299. button : null,
  300. multiple : true,
  301. uploadText:"Upload",
  302. maxConnections : 3,
  303. // validation
  304. allowedExtensions : [],
  305. sizeLimit : 0,
  306. maxFiles: 999,
  307. minSizeLimit : 0,
  308. // events
  309. // return false to cancel submit
  310. onSubmit : function(id, fileName)
  311. {
  312. },
  313. onProgress : function(id, fileName, loaded, total)
  314. {
  315. },
  316. onComplete : function(id, fileName, responseJSON)
  317. {
  318. },
  319. onCancel : function(id, fileName)
  320. {
  321. Tapestry.ajaxRequest(this.cancelLink + '/' + fileName, {method:'get'});
  322. },
  323. onRemove : function(serverIndex)
  324. {
  325. var url;
  326. if(this.removeLink.indexOf("?") == -1)
  327. {
  328. url = this.removeLink + "?serverIndex=" + serverIndex;
  329. }
  330. else
  331. {
  332. url = this.removeLink + "&serverIndex=" + serverIndex;
  333. }
  334. Tapestry.ajaxRequest(url, {method:'get'});
  335. },
  336. // messages
  337. messages : {
  338. typeError : "{file} has invalid extension. Only {extensions} are allowed.",
  339. sizeError : "{file} is too large, maximum file size is {sizeLimit}.",
  340. minSizeError : "{file} is too small, minimum file size is {minSizeLimit}.",
  341. emptyError : "{file} is empty, please select files again without it.",
  342. onLeave : "The files are being uploaded, if you leave now the upload will be cancelled.",
  343. maxFilesError:"Cannot upload file. Only {maxFiles} files can be uploaded"
  344. },
  345. showMessage : function(message)
  346. {
  347. alert(message);
  348. }
  349. };
  350. qq.extend(this._options, o);
  351. // number of files being uploaded
  352. this._filesInProgress = 0;
  353. this._handler = this._createUploadHandler();
  354. if (this._options.button)
  355. {
  356. this._button = this._createUploadButton(this._options.button);
  357. }
  358. this._preventLeaveInProgress();
  359. };
  360. qq.FileUploaderBasic.prototype =
  361. {
  362. setParams : function(params)
  363. {
  364. this._options.params = params;
  365. },
  366. getInProgress : function()
  367. {
  368. return this._filesInProgress;
  369. },
  370. _createUploadButton : function(element)
  371. {
  372. var self = this;
  373. return new qq.UploadButton({
  374. element : element,
  375. multiple : this._options.multiple
  376. && qq.UploadHandlerXhr.isSupported(),
  377. name:this._options.name,
  378. id:this._options.id,
  379. onChange : function(input)
  380. {
  381. self._onInputChange(input);
  382. }
  383. });
  384. },
  385. _createUploadHandler : function()
  386. {
  387. var self = this, handlerClass;
  388. if (qq.UploadHandlerXhr.isSupported())
  389. {
  390. handlerClass = 'UploadHandlerXhr';
  391. }
  392. else
  393. {
  394. handlerClass = 'UploadHandlerForm';
  395. }
  396. var handler = new qq[handlerClass]({
  397. debug : this._options.debug,
  398. action : this._options.action,
  399. maxConnections : this._options.maxConnections,
  400. onProgress : function(id, fileName, loaded, total)
  401. {
  402. self._onProgress(id, fileName, loaded, total);
  403. self._options.onProgress(id, fileName, loaded, total);
  404. },
  405. onComplete : function(id, fileName, result)
  406. {
  407. self._onComplete(id, fileName, result);
  408. self._options.onComplete(id, fileName, result);
  409. },
  410. onCancel : function(id, fileName)
  411. {
  412. self._onCancel(id, fileName);
  413. self._options.onCancel(id, fileName);
  414. },
  415. onRemove : function(serverIndex)
  416. {
  417. self._options.onRemove(serverIndex);
  418. }
  419. });
  420. return handler;
  421. },
  422. _preventLeaveInProgress : function()
  423. {
  424. var self = this;
  425. qq.attach(window, 'beforeunload', function(e)
  426. {
  427. if (!self._filesInProgress)
  428. {
  429. return;
  430. }
  431. var e = e || window.event;
  432. // for ie, ff
  433. e.returnValue = self._options.messages.onLeave;
  434. // for webkit
  435. return self._options.messages.onLeave;
  436. });
  437. },
  438. _onSubmit : function(id, fileName)
  439. {
  440. this._filesInProgress++;
  441. },
  442. _onProgress : function(id, fileName, loaded, total)
  443. {
  444. },
  445. _onComplete : function(id, fileName, result)
  446. {
  447. this._filesInProgress--;
  448. if (result.error)
  449. {
  450. this._options.showMessage(result.error);
  451. }
  452. },
  453. _onCancel : function(id, fileName)
  454. {
  455. this._filesInProgress--;
  456. },
  457. _onInputChange : function(input)
  458. {
  459. if (this._handler instanceof qq.UploadHandlerXhr)
  460. {
  461. this._uploadFileList(input.files);
  462. }
  463. else
  464. {
  465. if (this._validateFile(input))
  466. {
  467. this._uploadFile(input);
  468. }
  469. }
  470. this._button.reset();
  471. },
  472. _uploadFileList : function(files)
  473. {
  474. for ( var i = 0; i < files.length; i++)
  475. {
  476. if (!this._validateFile(files[i]))
  477. {
  478. return;
  479. }
  480. }
  481. for ( var i = 0; i < files.length; i++)
  482. {
  483. this._uploadFile(files[i]);
  484. }
  485. },
  486. _uploadFile : function(fileContainer)
  487. {
  488. var id = this._handler.add(fileContainer);
  489. var fileName = this._handler.getName(id);
  490. if(this._listElement.children.length == this._options.maxFiles)
  491. {
  492. this._error('maxFilesError', fileName);
  493. return;
  494. }
  495. if (this._options.onSubmit(id, fileName) !== false)
  496. {
  497. this._onSubmit(id, fileName);
  498. this._handler.upload(id, this._options.params);
  499. }
  500. },
  501. _validateFile : function(file)
  502. {
  503. var name, size;
  504. if (file.value)
  505. {
  506. // it is a file input
  507. // get input value and remove path to normalize
  508. name = file.value.replace(/.*(\/|\\)/, "");
  509. }
  510. else
  511. {
  512. // fix missing properties in Safari
  513. name = file.fileName != null ? file.fileName : file.name;
  514. size = file.fileSize != null ? file.fileSize : file.size;
  515. }
  516. if (!this._isAllowedExtension(name))
  517. {
  518. this._error('typeError', name);
  519. return false;
  520. }
  521. else if (size === 0)
  522. {
  523. this._error('emptyError', name);
  524. return false;
  525. }
  526. else if (size
  527. && this._options.sizeLimit
  528. && size > this._options.sizeLimit)
  529. {
  530. this._error('sizeError', name);
  531. return false;
  532. }
  533. else if (size && size < this._options.minSizeLimit)
  534. {
  535. this._error('minSizeError', name);
  536. return false;
  537. }
  538. return true;
  539. },
  540. _error : function(code, fileName)
  541. {
  542. var message = this._options.messages[code];
  543. function r(name, replacement)
  544. {
  545. message = message.replace(name, replacement);
  546. }
  547. r('{file}', this._formatFileName(fileName));
  548. r('{extensions}', this._options.allowedExtensions.join(', '));
  549. r('{sizeLimit}', this._formatSize(this._options.sizeLimit));
  550. r('{minSizeLimit}', this._formatSize(this._options.minSizeLimit));
  551. r('{maxFiles}', this._options.maxFiles);
  552. this._options.showMessage(message);
  553. },
  554. _formatFileName : function(name)
  555. {
  556. if (name.length > 33)
  557. {
  558. name = name.slice(0, 19) + '...' + name.slice(-13);
  559. }
  560. return name;
  561. },
  562. _isAllowedExtension : function(fileName)
  563. {
  564. var ext =
  565. (-1 !== fileName.indexOf('.')) ? fileName.replace(/.*[.]/,
  566. '').toLowerCase() : '';
  567. var allowed = this._options.allowedExtensions;
  568. if (!allowed.length)
  569. {
  570. return true;
  571. }
  572. for ( var i = 0; i < allowed.length; i++)
  573. {
  574. if (allowed[i].toLowerCase() == ext)
  575. {
  576. return true;
  577. }
  578. }
  579. return false;
  580. },
  581. _formatSize : function(bytes)
  582. {
  583. var i = -1;
  584. do
  585. {
  586. bytes = bytes / 1024;
  587. i++;
  588. }
  589. while (bytes > 99);
  590. return Math.max(bytes, 0.1).toFixed(1)
  591. + [ 'kB', 'MB', 'GB', 'TB', 'PB', 'EB' ][i];
  592. }
  593. };
  594. /**
  595. * Class that creates upload widget with drag-and-drop and file list
  596. *
  597. * @inherits qq.FileUploaderBasic
  598. */
  599. qq.FileUploader =
  600. function(o)
  601. {
  602. // call parent constructor
  603. qq.FileUploaderBasic.apply(this, arguments);
  604. // additional options
  605. qq
  606. .extend(
  607. this._options,
  608. {
  609. element : null,
  610. // if set, will be used instead of qq-upload-list in
  611. // template
  612. listElement : null,
  613. template : '<div class="qq-uploader">'
  614. + '<div class="qq-upload-drop-area"><span>'+ this._options.dropText + '</span></div>'
  615. + '<div class="qq-upload-button">' + this._options.uploadText + '</div>'
  616. + '<ul class="qq-upload-list"></ul>'
  617. + '</div>',
  618. // template for one item in file list
  619. fileTemplate : '<li>'
  620. + '<span class="qq-upload-file"></span>'
  621. + '<span class="qq-upload-spinner"></span>'
  622. + '<span class="qq-upload-size"></span>'
  623. + '<a class="qq-upload-cancel" href="#">'+ this._options.cancelText + '</a>'
  624. + '<a class="qq-upload-remove" href="#">'+ this._options.removeText + '</a>'
  625. + '<span class="qq-upload-failed-text">'+ this._options.failedText + '</span>'
  626. + '</li>',
  627. classes : {
  628. // used to get elements from templates
  629. button : 'qq-upload-button',
  630. drop : 'qq-upload-drop-area',
  631. dropActive : 'qq-upload-drop-area-active',
  632. list : 'qq-upload-list',
  633. file : 'qq-upload-file',
  634. spinner : 'qq-upload-spinner',
  635. size : 'qq-upload-size',
  636. cancel : 'qq-upload-cancel',
  637. remove : 'qq-upload-remove',
  638. // added to list item when upload completes
  639. // used in css to hide progress spinner
  640. success : 'qq-upload-success',
  641. fail : 'qq-upload-fail'
  642. }
  643. });
  644. // overwrite options with user supplied
  645. qq.extend(this._options, o);
  646. this._element = this._options.element;
  647. this._element.innerHTML = this._options.template;
  648. this._listElement =
  649. this._options.listElement || this._find(this._element, 'list');
  650. this._classes = this._options.classes;
  651. this._button =
  652. this._createUploadButton(this._find(this._element, 'button'));
  653. this._bindCancelEvent();
  654. this._setupDragDrop();
  655. this._addInitFiles(this._listElement.id);
  656. };
  657. // inherit from Basic Uploader
  658. qq.extend(qq.FileUploader.prototype, qq.FileUploaderBasic.prototype);
  659. qq.extend(qq.FileUploader.prototype, {
  660. /**
  661. * Gets one of the elements listed in this._options.classes
  662. */
  663. _find : function(parent, type)
  664. {
  665. var element = qq.getByClass(parent, this._options.classes[type])[0];
  666. if (!element)
  667. {
  668. throw new Error('element not found ' + type);
  669. }
  670. return element;
  671. },
  672. _setupDragDrop : function()
  673. {
  674. var self = this, dropArea = this._find(this._element, 'drop');
  675. var dz = new qq.UploadDropZone({
  676. element : dropArea,
  677. onEnter : function(e)
  678. {
  679. qq.addClass(dropArea, self._classes.dropActive);
  680. e.stopPropagation();
  681. },
  682. onLeave : function(e)
  683. {
  684. e.stopPropagation();
  685. },
  686. onLeaveNotDescendants : function(e)
  687. {
  688. qq.removeClass(dropArea, self._classes.dropActive);
  689. },
  690. onDrop : function(e)
  691. {
  692. dropArea.style.display = 'none';
  693. qq.removeClass(dropArea, self._classes.dropActive);
  694. self._uploadFileList(e.dataTransfer.files);
  695. }
  696. });
  697. dropArea.style.display = 'none';
  698. qq.attach(document, 'dragenter', function(e)
  699. {
  700. if (!dz._isValidFileDrag(e))
  701. return;
  702. dropArea.style.display = 'block';
  703. });
  704. qq.attach(document, 'dragleave',
  705. function(e)
  706. {
  707. if (!dz._isValidFileDrag(e))
  708. return;
  709. var relatedTarget =
  710. document.elementFromPoint(e.clientX, e.clientY);
  711. // only fire when leaving document out
  712. if (!relatedTarget || relatedTarget.nodeName == "HTML")
  713. {
  714. dropArea.style.display = 'none';
  715. }
  716. });
  717. },
  718. _onSubmit : function(id, fileName)
  719. {
  720. qq.FileUploaderBasic.prototype._onSubmit.apply(this, arguments);
  721. this._addToList(id, fileName);
  722. },
  723. _onProgress : function(id, fileName, loaded, total)
  724. {
  725. qq.FileUploaderBasic.prototype._onProgress.apply(this, arguments);
  726. var item = this._getItemByFileId(id);
  727. var size = this._find(item, 'size');
  728. size.style.display = 'inline';
  729. var text;
  730. if (loaded != total)
  731. {
  732. text =
  733. Math.round(loaded / total * 100)
  734. + '% / '
  735. + this._formatSize(total);
  736. }
  737. else
  738. {
  739. text = this._formatSize(total);
  740. }
  741. qq.setText(size, text);
  742. },
  743. _onComplete : function(id, fileName, result)
  744. {
  745. qq.FileUploaderBasic.prototype._onComplete.apply(this, arguments);
  746. // mark completed
  747. var item = this._getItemByFileId(id);
  748. qq.remove(this._find(item, 'cancel'));
  749. qq.remove(this._find(item, 'spinner'));
  750. if (result.success)
  751. {
  752. qq.addClass(item, this._classes.success);
  753. this._find(item, 'remove').style.display = "inline";
  754. item.serverIndex = result.serverIndex;
  755. }
  756. else
  757. {
  758. qq.addClass(item, this._classes.fail);
  759. }
  760. },
  761. _addToList : function(id, fileName, serverIndex)
  762. {
  763. var item = qq.toElement(this._options.fileTemplate);
  764. item.qqFileId = id;
  765. var fileElement = this._find(item, 'file');
  766. qq.setText(fileElement, this._formatFileName(fileName));
  767. this._find(item, 'size').style.display = 'none';
  768. if (typeof (serverIndex) != "undefined")
  769. {
  770. item.serverIndex = serverIndex;
  771. this._find(item, 'cancel').style.display = 'none';
  772. this._find(item, 'spinner').style.display = 'none';
  773. }
  774. else
  775. {
  776. this._find(item, 'remove').style.display = 'none';
  777. }
  778. this._listElement.appendChild(item);
  779. this._setSizeField();
  780. },
  781. _setSizeField : function()
  782. {
  783. if(this._options.sizeField && document.getElementById(this._options.sizeField))
  784. {
  785. document.getElementById(this._options.sizeField).value =
  786. this._listElement.children.length == 0 ? null: this._listElement.children.length;
  787. }
  788. },
  789. _getItemByFileId : function(id)
  790. {
  791. var item = this._listElement.firstChild;
  792. // there can't be txt nodes in dynamically created list
  793. // and we can use nextSibling
  794. while (item)
  795. {
  796. if (item.qqFileId == id)
  797. return item;
  798. item = item.nextSibling;
  799. }
  800. },
  801. _addInitFiles : function(id)
  802. {
  803. var self = this;
  804. Tapestry.ajaxRequest(this._options.initializeUploadsLink, function(response){
  805. if(response.responseJSON)
  806. {
  807. for ( var i = 0; i < response.responseJSON.uploads.size(); ++i)
  808. {
  809. var file = response.responseJSON.uploads[i];
  810. self._addToList(id, file.fileName, file.serverIndex);
  811. }
  812. }
  813. });
  814. },
  815. /**
  816. * delegate click event for cancel link
  817. */
  818. _bindCancelEvent : function()
  819. {
  820. var self = this, list = this._listElement;
  821. qq.attach(list, 'click', function(e)
  822. {
  823. e = e || window.event;
  824. var target = e.target || e.srcElement;
  825. if (qq.hasClass(target, self._classes.cancel))
  826. {
  827. qq.preventDefault(e);
  828. var item = target.parentNode;
  829. self._handler.cancel(item.qqFileId);
  830. qq.remove(item);
  831. self._setSizeField();
  832. }
  833. else if (qq.hasClass(target, self._classes.remove))
  834. {
  835. qq.preventDefault(e);
  836. var item = target.parentNode;
  837. self._handler._options.onRemove(item.serverIndex);
  838. qq.remove(item);
  839. self._setSizeField();
  840. }
  841. });
  842. }
  843. });
  844. qq.UploadDropZone = function(o)
  845. {
  846. this._options = {
  847. element : null,
  848. onEnter : function(e)
  849. {
  850. },
  851. onLeave : function(e)
  852. {
  853. },
  854. // is not fired when leaving element by hovering descendants
  855. onLeaveNotDescendants : function(e)
  856. {
  857. },
  858. onDrop : function(e)
  859. {
  860. }
  861. };
  862. qq.extend(this._options, o);
  863. this._element = this._options.element;
  864. this._disableDropOutside();
  865. this._attachEvents();
  866. };
  867. qq.UploadDropZone.prototype =
  868. {
  869. _disableDropOutside : function(e)
  870. {
  871. // run only once for all instances
  872. if (!qq.UploadDropZone.dropOutsideDisabled)
  873. {
  874. qq.attach(document, 'dragover', function(e)
  875. {
  876. if (e.dataTransfer)
  877. {
  878. e.dataTransfer.dropEffect = 'none';
  879. e.preventDefault();
  880. }
  881. });
  882. qq.UploadDropZone.dropOutsideDisabled = true;
  883. }
  884. },
  885. _attachEvents : function()
  886. {
  887. var self = this;
  888. qq.attach(self._element, 'dragover', function(e)
  889. {
  890. if (!self._isValidFileDrag(e))
  891. return;
  892. var effect = e.dataTransfer.effectAllowed;
  893. if (effect == 'move' || effect == 'linkMove')
  894. {
  895. e.dataTransfer.dropEffect = 'move'; // for FF (only move
  896. // allowed)
  897. }
  898. else
  899. {
  900. e.dataTransfer.dropEffect = 'copy'; // for Chrome
  901. }
  902. e.stopPropagation();
  903. e.preventDefault();
  904. });
  905. qq.attach(self._element, 'dragenter', function(e)
  906. {
  907. if (!self._isValidFileDrag(e))
  908. return;
  909. self._options.onEnter(e);
  910. });
  911. qq.attach(self._element, 'dragleave', function(e)
  912. {
  913. if (!self._isValidFileDrag(e))
  914. return;
  915. self._options.onLeave(e);
  916. var relatedTarget =
  917. document.elementFromPoint(e.clientX, e.clientY);
  918. // do not fire when moving a mouse over a descendant
  919. if (qq.contains(this, relatedTarget))
  920. return;
  921. self._options.onLeaveNotDescendants(e);
  922. });
  923. qq.attach(self._element, 'drop', function(e)
  924. {
  925. if (!self._isValidFileDrag(e))
  926. return;
  927. e.preventDefault();
  928. self._options.onDrop(e);
  929. });
  930. },
  931. _isValidFileDrag : function(e)
  932. {
  933. var dt = e.dataTransfer,
  934. // do not check dt.types.contains in webkit, because it crashes
  935. // safari 4
  936. isWebkit = navigator.userAgent.indexOf("AppleWebKit") > -1;
  937. // dt.effectAllowed is none in Safari 5
  938. // dt.types.contains check is for firefox
  939. return dt
  940. && dt.effectAllowed != 'none'
  941. && (dt.files || (!isWebkit && dt.types.contains && dt.types
  942. .contains('Files')));
  943. }
  944. };
  945. qq.UploadButton = function(o)
  946. {
  947. this._options = {
  948. element : null,
  949. // if set to true adds multiple attribute to file input
  950. multiple : false,
  951. // name attribute of file input
  952. name : 'file',
  953. onChange : function(input)
  954. {
  955. },
  956. hoverClass : 'qq-upload-button-hover',
  957. focusClass : 'qq-upload-button-focus'
  958. };
  959. qq.extend(this._options, o);
  960. this._element = this._options.element;
  961. // make button suitable container for input
  962. qq.css(this._element, {
  963. position : 'relative',
  964. overflow : 'hidden',
  965. // Make sure browse button is in the right side
  966. // in Internet Explorer
  967. direction : 'ltr'
  968. });
  969. this._input = this._createInput();
  970. };
  971. qq.UploadButton.prototype = {
  972. /* returns file input element */
  973. getInput : function()
  974. {
  975. return this._input;
  976. },
  977. /* cleans/recreates the file input */
  978. reset : function()
  979. {
  980. if (this._input.parentNode)
  981. {
  982. qq.remove(this._input);
  983. }
  984. qq.removeClass(this._element, this._options.focusClass);
  985. this._input = this._createInput();
  986. },
  987. _createInput : function()
  988. {
  989. var input = document.createElement("input");
  990. if (this._options.multiple)
  991. {
  992. input.setAttribute("multiple", "multiple");
  993. }
  994. input.setAttribute("type", "file");
  995. input.setAttribute("name", this._options.name);
  996. if (this._options.id)
  997. {
  998. input.id = this._options.id;
  999. }
  1000. qq.css(input, {
  1001. position : 'absolute',
  1002. // in Opera only 'browse' button
  1003. // is clickable and it is located at
  1004. // the right side of the input
  1005. right : 0,
  1006. top : 0,
  1007. fontFamily : 'Arial',
  1008. // 4 persons reported this, the max values that worked for them were
  1009. // 243, 236, 236, 118
  1010. fontSize : '118px',
  1011. margin : 0,
  1012. padding : 0,
  1013. cursor : 'pointer',
  1014. opacity : 0
  1015. });
  1016. this._element.appendChild(input);
  1017. var self = this;
  1018. qq.attach(input, 'change', function()
  1019. {
  1020. self._options.onChange(input);
  1021. });
  1022. qq.attach(input, 'mouseover', function()
  1023. {
  1024. qq.addClass(self._element, self._options.hoverClass);
  1025. });
  1026. qq.attach(input, 'mouseout', function()
  1027. {
  1028. qq.removeClass(self._element, self._options.hoverClass);
  1029. });
  1030. qq.attach(input, 'focus', function()
  1031. {
  1032. qq.addClass(self._element, self._options.focusClass);
  1033. });
  1034. qq.attach(input, 'blur', function()
  1035. {
  1036. qq.removeClass(self._element, self._options.focusClass);
  1037. });
  1038. // IE and Opera, unfortunately have 2 tab stops on file input
  1039. // which is unacceptable in our case, disable keyboard access
  1040. if (window.attachEvent)
  1041. {
  1042. // it is IE or Opera
  1043. input.setAttribute('tabIndex', "-1");
  1044. }
  1045. return input;
  1046. }
  1047. };
  1048. /**
  1049. * Class for uploading files, uploading itself is handled by child classes
  1050. */
  1051. qq.UploadHandlerAbstract = function(o)
  1052. {
  1053. this._options = {
  1054. debug : false,
  1055. action : '/upload.php',
  1056. // maximum number of concurrent uploads
  1057. maxConnections : 999,
  1058. onProgress : function(id, fileName, loaded, total)
  1059. {
  1060. },
  1061. onComplete : function(id, fileName, response)
  1062. {
  1063. },
  1064. onCancel : function(id, fileName)
  1065. {
  1066. },
  1067. onRemove : function(serverIndex)
  1068. {
  1069. }
  1070. };
  1071. qq.extend(this._options, o);
  1072. this._queue = [];
  1073. // params for files in queue
  1074. this._params = [];
  1075. };
  1076. qq.UploadHandlerAbstract.prototype = {
  1077. log : function(str)
  1078. {
  1079. if (this._options.debug && window.console)
  1080. console.log('[uploader] ' + str);
  1081. },
  1082. /**
  1083. * Adds file or file input to the queue
  1084. *
  1085. * @returns id
  1086. */
  1087. add : function(file)
  1088. {
  1089. },
  1090. /**
  1091. * Sends the file identified by id and additional query params to the server
  1092. */
  1093. upload : function(id, params)
  1094. {
  1095. var len = this._queue.push(id);
  1096. var copy = {};
  1097. qq.extend(copy, params);
  1098. this._params[id] = copy;
  1099. // if too many active uploads, wait...
  1100. if (len <= this._options.maxConnections)
  1101. {
  1102. this._upload(id, this._params[id]);
  1103. }
  1104. },
  1105. /**
  1106. * Cancels file upload by id
  1107. */
  1108. cancel : function(id)
  1109. {
  1110. this._cancel(id);
  1111. this._dequeue(id);
  1112. },
  1113. /**
  1114. * Cancells all uploads
  1115. */
  1116. cancelAll : function()
  1117. {
  1118. for ( var i = 0; i < this._queue.length; i++)
  1119. {
  1120. this._cancel(this._queue[i]);
  1121. }
  1122. this._queue = [];
  1123. },
  1124. /**
  1125. * Returns name of the file identified by id
  1126. */
  1127. getName : function(id)
  1128. {
  1129. },
  1130. /**
  1131. * Returns size of the file identified by id
  1132. */
  1133. getSize : function(id)
  1134. {
  1135. },
  1136. /**
  1137. * Returns id of files being uploaded or waiting for their turn
  1138. */
  1139. getQueue : function()
  1140. {
  1141. return this._queue;
  1142. },
  1143. /**
  1144. * Actual upload method
  1145. */
  1146. _upload : function(id)
  1147. {
  1148. },
  1149. /**
  1150. * Actual cancel method
  1151. */
  1152. _cancel : function(id)
  1153. {
  1154. },
  1155. /**
  1156. * Removes element from queue, starts upload of next
  1157. */
  1158. _dequeue : function(id)
  1159. {
  1160. var i = qq.indexOf(this._queue, id);
  1161. this._queue.splice(i, 1);
  1162. var max = this._options.maxConnections;
  1163. if (this._queue.length >= max && i < max)
  1164. {
  1165. var nextId = this._queue[max - 1];
  1166. this._upload(nextId, this._params[nextId]);
  1167. }
  1168. }
  1169. };
  1170. /**
  1171. * Class for uploading files using form and iframe
  1172. *
  1173. * @inherits qq.UploadHandlerAbstract
  1174. */
  1175. qq.UploadHandlerForm = function(o)
  1176. {
  1177. qq.UploadHandlerAbstract.apply(this, arguments);
  1178. this._inputs = {};
  1179. };
  1180. // @inherits qq.UploadHandlerAbstract
  1181. qq.extend(qq.UploadHandlerForm.prototype, qq.UploadHandlerAbstract.prototype);
  1182. qq
  1183. .extend(
  1184. qq.UploadHandlerForm.prototype,
  1185. {
  1186. add : function(fileInput)
  1187. {
  1188. fileInput.setAttribute('name', 'qqfile');
  1189. var id = 'qq-upload-handler-iframe' + qq.getUniqueId();
  1190. this._inputs[id] = fileInput;
  1191. // remove file input from DOM
  1192. if (fileInput.parentNode)
  1193. {
  1194. qq.remove(fileInput);
  1195. }
  1196. return id;
  1197. },
  1198. getName : function(id)
  1199. {
  1200. // get input value and remove path to normalize
  1201. return this._inputs[id].value.replace(/.*(\/|\\)/, "");
  1202. },
  1203. _cancel : function(id)
  1204. {
  1205. this._options.onCancel(id, this.getName(id));
  1206. delete this._inputs[id];
  1207. var iframe = document.getElementById(id);
  1208. if (iframe)
  1209. {
  1210. // to cancel request set src to something else
  1211. // we use src="javascript:false;" because it doesn't
  1212. // trigger ie6 prompt on https
  1213. iframe.setAttribute('src', 'javascript:false;');
  1214. qq.remove(iframe);
  1215. }
  1216. },
  1217. _upload : function(id, params)
  1218. {
  1219. var input = this._inputs[id];
  1220. if (!input)
  1221. {
  1222. throw new Error(
  1223. 'file with passed id was not added, or already uploaded or cancelled');
  1224. }
  1225. var fileName = this.getName(id);
  1226. var iframe = this._createIframe(id);
  1227. var form = this._createForm(iframe, params);
  1228. form.appendChild(input);
  1229. var self = this;
  1230. this._attachLoadEvent(iframe, function()
  1231. {
  1232. self.log('iframe loaded');
  1233. var response = self._getIframeContentJSON(iframe);
  1234. self._options.onComplete(id, fileName, response);
  1235. self._dequeue(id);
  1236. delete self._inputs[id];
  1237. // timeout added to fix busy state in FF3.6
  1238. setTimeout(function()
  1239. {
  1240. qq.remove(iframe);
  1241. }, 1);
  1242. });
  1243. form.submit();
  1244. qq.remove(form);
  1245. return id;
  1246. },
  1247. _attachLoadEvent : function(iframe, callback)
  1248. {
  1249. qq
  1250. .attach(
  1251. iframe,
  1252. 'load',
  1253. function()
  1254. {
  1255. // when we remove iframe from dom
  1256. // the request stops, but in IE load
  1257. // event fires
  1258. if (!iframe.parentNode)
  1259. {
  1260. return;
  1261. }
  1262. // fixing Opera 10.53
  1263. if (iframe.contentDocument
  1264. && iframe.contentDocument.body
  1265. && iframe.contentDocument.body.innerHTML == "false")
  1266. {
  1267. // In Opera event is fired second time
  1268. // when body.innerHTML changed from false
  1269. // to server response approx. after 1 sec
  1270. // when we upload file with iframe
  1271. return;
  1272. }
  1273. callback();
  1274. });
  1275. },
  1276. /**
  1277. * Returns json object received by iframe from server.
  1278. */
  1279. _getIframeContentJSON : function(iframe)
  1280. {
  1281. // iframe.contentWindow.document - for IE<7
  1282. var doc =
  1283. iframe.contentDocument
  1284. ? iframe.contentDocument
  1285. : iframe.contentWindow.document, response;
  1286. this.log("converting iframe's innerHTML to JSON");
  1287. this.log("innerHTML = " + doc.body.innerHTML);
  1288. try
  1289. {
  1290. response = eval("(" + doc.body.innerHTML + ")");
  1291. }
  1292. catch (err)
  1293. {
  1294. response = {};
  1295. }
  1296. return response;
  1297. },
  1298. /**
  1299. * Creates iframe with unique name
  1300. */
  1301. _createIframe : function(id)
  1302. {
  1303. // We can't use following code as the name attribute
  1304. // won't be properly registered in IE6, and new window
  1305. // on form submit will open
  1306. // var iframe = document.createElement('iframe');
  1307. // iframe.setAttribute('name', id);
  1308. var iframe =
  1309. qq
  1310. .toElement('<iframe src="javascript:false;" name="'
  1311. + id
  1312. + '" />');
  1313. // src="javascript:false;" removes ie6 prompt on https
  1314. iframe.setAttribute('id', id);
  1315. iframe.style.display = 'none';
  1316. document.body.appendChild(iframe);
  1317. return iframe;
  1318. },
  1319. /**
  1320. * Creates form, that will be submitted to iframe
  1321. */
  1322. _createForm : function(iframe, params)
  1323. {
  1324. // We can't use the following code in IE6
  1325. // var form = document.createElement('form');
  1326. // form.setAttribute('method', 'post');
  1327. // form.setAttribute('enctype', 'multipart/form-data');
  1328. // Because in this case file won't be attached to request
  1329. var form =
  1330. qq
  1331. .toElement('<form method="post" enctype="multipart/form-data"></form>');
  1332. var queryString = qq.obj2url(params, this._options.action);
  1333. form.setAttribute('action', queryString);
  1334. form.setAttribute('target', iframe.name);
  1335. form.style.display = 'none';
  1336. document.body.appendChild(form);
  1337. return form;
  1338. }
  1339. });
  1340. /**
  1341. * Class for uploading files using xhr
  1342. *
  1343. * @inherits qq.UploadHandlerAbstract
  1344. */
  1345. qq.UploadHandlerXhr = function(o)
  1346. {
  1347. qq.UploadHandlerAbstract.apply(this, arguments);
  1348. this._files = [];
  1349. this._xhrs = [];
  1350. // current loaded size in bytes for each file
  1351. this._loaded = [];
  1352. };
  1353. // static method
  1354. qq.UploadHandlerXhr.isSupported =
  1355. function()
  1356. {
  1357. var input = document.createElement('input');
  1358. input.type = 'file';
  1359. return ('multiple' in input && typeof File != "undefined" && typeof (new XMLHttpRequest()).upload != "undefined");
  1360. };
  1361. // @inherits qq.UploadHandlerAbstract
  1362. qq.extend(qq.UploadHandlerXhr.prototype, qq.UploadHandlerAbstract.prototype)
  1363. qq.extend(qq.UploadHandlerXhr.prototype, {
  1364. /**
  1365. * Adds file to the queue Returns id to use with upload, cancel
  1366. */
  1367. add : function(file)
  1368. {
  1369. if (!(file instanceof File))
  1370. {
  1371. throw new Error('Passed obj in not a File (in qq.UploadHandlerXhr)');
  1372. }
  1373. return this._files.push(file) - 1;
  1374. },
  1375. getName : function(id)
  1376. {
  1377. var file = this._files[id];
  1378. // fix missing name in Safari 4
  1379. return file.fileName != null ? file.fileName : file.name;
  1380. },
  1381. getSize : function(id)
  1382. {
  1383. var file = this._files[id];
  1384. return file.fileSize != null ? file.fileSize : file.size;
  1385. },
  1386. /**
  1387. * Returns uploaded bytes for file identified by id
  1388. */
  1389. getLoaded : function(id)
  1390. {
  1391. return this._loaded[id] || 0;
  1392. },
  1393. /**
  1394. * Sends the file identified by id and additional query params to the
  1395. * server
  1396. *
  1397. * @param {Object}
  1398. * params name-value string pairs
  1399. */
  1400. _upload : function(id, params)
  1401. {
  1402. var file = this._files[id], name = this.getName(id), size =
  1403. this.getSize(id);
  1404. this._loaded[id] = 0;
  1405. var xhr = this._xhrs[id] = new XMLHttpRequest();
  1406. var self = this;
  1407. xhr.upload.onprogress = function(e)
  1408. {
  1409. if (e.lengthComputable)
  1410. {
  1411. self._loaded[id] = e.loaded;
  1412. self._options.onProgress(id, name, e.loaded, e.total);
  1413. }
  1414. };
  1415. xhr.onreadystatechange = function()
  1416. {
  1417. if (xhr.readyState == 4)
  1418. {
  1419. self._onComplete(id, xhr);
  1420. }
  1421. };
  1422. // build query string
  1423. params = params || {};
  1424. params['qqfile'] = name;
  1425. var queryString = qq.obj2url(params, this._options.action);
  1426. xhr.open("POST", queryString, true);
  1427. xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
  1428. xhr.setRequestHeader("X-File-Name", encodeURIComponent(name));
  1429. xhr.setRequestHeader("Content-Type", "application/octet-stream");
  1430. xhr.send(file);
  1431. },
  1432. _onComplete : function(id, xhr)
  1433. {
  1434. // the request was aborted/cancelled
  1435. if (!this._files[id])
  1436. return;
  1437. var name = this.getName(id);
  1438. var size = this.getSize(id);
  1439. this._options.onProgress(id, name, size, size);
  1440. if (xhr.status == 200)
  1441. {
  1442. this.log("xhr - server response received");
  1443. this.log("responseText = " + xhr.responseText);
  1444. var response;
  1445. try
  1446. {
  1447. response = eval("(" + xhr.responseText + ")");

Large files files are truncated, but you can click here to view the full file