Contains the Concourse pipeline definition for building a line-server container
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1291 lines
135 KiB

  1. /*! jQuery v1.11.3 | (c) 2005, 2015 jQuery Foundation, Inc. | jquery.org/license */
  2. !function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l="1.11.3",m=function(a,b){return new m.fn.init(a,b)},n=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,o=/^-ms-/,p=/-([\da-z])/gi,q=function(a,b){return b.toUpperCase()};m.fn=m.prototype={jquery:l,constructor:m,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=m.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return m.each(this,a,b)},map:function(a){return this.pushStack(m.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},m.extend=m.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||m.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(m.isPlainObject(c)||(b=m.isArray(c)))?(b?(b=!1,f=a&&m.isArray(a)?a:[]):f=a&&m.isPlainObject(a)?a:{},g[d]=m.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},m.extend({expando:"jQuery"+(l+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===m.type(a)},isArray:Array.isArray||function(a){return"array"===m.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return!m.isArray(a)&&a-parseFloat(a)+1>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==m.type(a)||a.nodeType||m.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(k.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&m.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(o,"ms-").replace(p,q)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=r(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(n,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(r(Object(a))?m.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=r(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),m.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||m.guid++,e):void 0},now:function(){return+new Date},support:k}),m.each(
  3. return!0}function Q(a,b,d,e){if(m.acceptData(a)){var f,g,h=m.expando,i=a.nodeType,j=i?m.cache:a,k=i?a[h]:a[h]&&h;if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||m.guid++:h),j[k]||(j[k]=i?{}:{toJSON:m.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=m.extend(j[k],b):j[k].data=m.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[m.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[m.camelCase(b)])):f=g,f}}function R(a,b,c){if(m.acceptData(a)){var d,e,f=a.nodeType,g=f?m.cache:a,h=f?a[m.expando]:m.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){m.isArray(b)?b=b.concat(m.map(b,m.camelCase)):b in d?b=[b]:(b=m.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!P(d):!m.isEmptyObject(d))return}(c||(delete g[h].data,P(g[h])))&&(f?m.cleanData([a],!0):k.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}m.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?m.cache[a[m.expando]]:a[m.expando],!!a&&!P(a)},data:function(a,b,c){return Q(a,b,c)},removeData:function(a,b){return R(a,b)},_data:function(a,b,c){return Q(a,b,c,!0)},_removeData:function(a,b){return R(a,b,!0)}}),m.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=m.data(f),1===f.nodeType&&!m._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=m.camelCase(d.slice(5)),O(f,d,e[d])));m._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){m.data(this,a)}):arguments.length>1?this.each(function(){m.data(this,a,b)}):f?O(f,a,m.data(f,a)):void 0},removeData:function(a){return this.each(function(){m.removeData(this,a)})}}),m.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=m._data(a,b),c&&(!d||m.isArray(c)?d=m._data(a,b,m.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=m.queue(a,b),d=c.length,e=c.shift(),f=m._queueHooks(a,b),g=function(){m.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return m._data(a,c)||m._data(a,c,{empty:m.Callbacks("once memory").add(function(){m._removeData(a,b+"queue"),m._removeData(a,c)})})}}),m.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length<c?m.queue(this[0],a):void 0===b?this:this.each(function(){var c=m.queue(this,a,b);m._queueHooks(this,a),"fx"===a&&"inprogress"!==c[0]&&m.dequeue(this,a)})},dequeue:function(a){return this.each(function(){m.dequeue(this,a)})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,b){var c,d=1,e=m.Deferred(),f=this,g=this.length,h=function(){--d||e.resolveWith(f,[f])};"string"!=typeof a&&(b=a,a=void 0),a=a||"fx";while(g--)c=m._data(f[g],a+"queueHooks"),c&&c.empty&&(d++,c.empty.add(h));return h(),e.promise(b)}});var S=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=["Top","Right","Bottom","Left"],U=function(a,b){return a=b||a,"none"===m.css(a,"display")||!m.contains(a.ownerDocument,a)},V=m.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===m.type(c)){e=!0;for(h in c)m.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,m.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(m(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},W=/^(?:checkbox|radio)$/i;!function(){var a=y.createElement("input"),b=y.createElement("div"),c=y.createDocumentFragment();if(b.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",k.leadingWhitespace=3===b.firstChild.nodeType,k.tbody=!b.getElementsByTagName("tbody").length,k.htmlSerialize=!!b.getElementsByTagName("link").length,k.html5Clone="<:nav></:nav>"!==y.createElement("nav").cloneNode(!0).outerHTML,a.type="checkbox",a.checked=!0,c.appendChild(a),k.appendChecked=a.checked,b.innerHTML="<textarea>x</tex
  4. return new Za.prototype.init(a,b,c,d,e)}m.Tween=Za,Za.prototype={constructor:Za,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(m.cssNumber[c]?"":"px")},cur:function(){var a=Za.propHooks[this.prop];return a&&a.get?a.get(this):Za.propHooks._default.get(this)},run:function(a){var b,c=Za.propHooks[this.prop];return this.options.duration?this.pos=b=m.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Za.propHooks._default.set(this),this}},Za.prototype.init.prototype=Za.prototype,Za.propHooks={_default:{get:function(a){var b;return null==a.elem[a.prop]||a.elem.style&&null!=a.elem.style[a.prop]?(b=m.css(a.elem,a.prop,""),b&&"auto"!==b?b:0):a.elem[a.prop]},set:function(a){m.fx.step[a.prop]?m.fx.step[a.prop](a):a.elem.style&&(null!=a.elem.style[m.cssProps[a.prop]]||m.cssHooks[a.prop])?m.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},Za.propHooks.scrollTop=Za.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},m.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},m.fx=Za.prototype.init,m.fx.step={};var $a,_a,ab=/^(?:toggle|show|hide)$/,bb=new RegExp("^(?:([+-])=|)("+S+")([a-z%]*)$","i"),cb=/queueHooks$/,db=[ib],eb={"*":[function(a,b){var c=this.createTween(a,b),d=c.cur(),e=bb.exec(b),f=e&&e[3]||(m.cssNumber[a]?"":"px"),g=(m.cssNumber[a]||"px"!==f&&+d)&&bb.exec(m.css(c.elem,a)),h=1,i=20;if(g&&g[3]!==f){f=f||g[3],e=e||[],g=+d||1;do h=h||".5",g/=h,m.style(c.elem,a,g+f);while(h!==(h=c.cur()/d)&&1!==h&&--i)}return e&&(g=c.start=+g||+d||0,c.unit=f,c.end=e[1]?g+(e[1]+1)*e[2]:+e[2]),c}]};function fb(){return setTimeout(function(){$a=void 0}),$a=m.now()}function gb(a,b){var c,d={height:a},e=0;for(b=b?1:0;4>e;e+=2-b)c=T[e],d["margin"+c]=d["padding"+c]=a;return b&&(d.opacity=d.width=a),d}function hb(a,b,c){for(var d,e=(eb[b]||[]).concat(eb["*"]),f=0,g=e.length;g>f;f++)if(d=e[f].call(c,b,a))return d}function ib(a,b,c){var d,e,f,g,h,i,j,l,n=this,o={},p=a.style,q=a.nodeType&&U(a),r=m._data(a,"fxshow");c.queue||(h=m._queueHooks(a,"fx"),null==h.unqueued&&(h.unqueued=0,i=h.empty.fire,h.empty.fire=function(){h.unqueued||i()}),h.unqueued++,n.always(function(){n.always(function(){h.unqueued--,m.queue(a,"fx").length||h.empty.fire()})})),1===a.nodeType&&("height"in b||"width"in b)&&(c.overflow=[p.overflow,p.overflowX,p.overflowY],j=m.css(a,"display"),l="none"===j?m._data(a,"olddisplay")||Fa(a.nodeName):j,"inline"===l&&"none"===m.css(a,"float")&&(k.inlineBlockNeedsLayout&&"inline"!==Fa(a.nodeName)?p.zoom=1:p.display="inline-block")),c.overflow&&(p.overflow="hidden",k.shrinkWrapBlocks()||n.always(function(){p.overflow=c.overflow[0],p.overflowX=c.overflow[1],p.overflowY=c.overflow[2]}));for(d in b)if(e=b[d],ab.exec(e)){if(delete b[d],f=f||"toggle"===e,e===(q?"hide":"show")){if("show"!==e||!r||void 0===r[d])continue;q=!0}o[d]=r&&r[d]||m.style(a,d)}else j=void 0;if(m.isEmptyObject(o))"inline"===("none"===j?Fa(a.nodeName):j)&&(p.display=j);else{r?"hidden"in r&&(q=r.hidden):r=m._data(a,"fxshow",{}),f&&(r.hidden=!q),q?m(a).show():n.done(function(){m(a).hide()}),n.done(function(){var b;m._removeData(a,"fxshow");for(b in o)m.style(a,b,o[b])});for(d in o)g=hb(q?r[d]:0,d,n),d in r||(r[d]=g.start,q&&(g.end=g.start,g.start="width"===d||"height"===d?1:0))}}function jb(a,b){var c,d,e,f,g;for(c in a)if(d=m.camelCase(c),e=b[d],f=a[c],m.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=m.cssHooks[d],g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}function kb(a,b,c){var d,e,f=0,g=db.length,h=m.Deferred().always(function(){delete i.elem}),i=function(){if(e)return!1;for(var b=$a||fb(),c=Math.max(0,j.startTime+j.duration-b),d=c/j.duration||0,f=1-d,g=0,i=j.tweens.length;i>g;g++)j.tweens[g].run(f);return h.notifyWith(a,[j,f,c]),1>f&&i?c:(h.resolveWith(a,[j]),!1)},j=h
  5. /**
  6. * http://github.com/valums/file-uploader
  7. *
  8. * Multiple file upload component with progress-bar, drag-and-drop.
  9. * © 2010 Andrew Valums ( andrew(at)valums.com )
  10. *
  11. * Licensed under GNU GPL 2 or later and GNU LGPL 2 or later, see license.txt.
  12. */
  13. //
  14. // Helper functions
  15. //
  16. var qq = qq || {};
  17. /**
  18. * Adds all missing properties from second obj to first obj
  19. */
  20. qq.extend = function(first, second){
  21. for (var prop in second){
  22. first[prop] = second[prop];
  23. }
  24. };
  25. /**
  26. * Searches for a given element in the array, returns -1 if it is not present.
  27. * @param {Number} [from] The index at which to begin the search
  28. */
  29. qq.indexOf = function(arr, elt, from){
  30. if (arr.indexOf) return arr.indexOf(elt, from);
  31. from = from || 0;
  32. var len = arr.length;
  33. if (from < 0) from += len;
  34. for (; from < len; from++){
  35. if (from in arr && arr[from] === elt){
  36. return from;
  37. }
  38. }
  39. return -1;
  40. };
  41. qq.getUniqueId = (function(){
  42. var id = 0;
  43. return function(){ return id++; };
  44. })();
  45. //
  46. // Events
  47. qq.attach = function(element, type, fn){
  48. if (element.addEventListener){
  49. element.addEventListener(type, fn, false);
  50. } else if (element.attachEvent){
  51. element.attachEvent('on' + type, fn);
  52. }
  53. };
  54. qq.detach = function(element, type, fn){
  55. if (element.removeEventListener){
  56. element.removeEventListener(type, fn, false);
  57. } else if (element.attachEvent){
  58. element.detachEvent('on' + type, fn);
  59. }
  60. };
  61. qq.preventDefault = function(e){
  62. if (e.preventDefault){
  63. e.preventDefault();
  64. } else{
  65. e.returnValue = false;
  66. }
  67. };
  68. //
  69. // Node manipulations
  70. /**
  71. * Insert node a before node b.
  72. */
  73. qq.insertBefore = function(a, b){
  74. b.parentNode.insertBefore(a, b);
  75. };
  76. qq.remove = function(element){
  77. element.parentNode.removeChild(element);
  78. };
  79. qq.contains = function(parent, descendant){
  80. // compareposition returns false in this case
  81. if (parent == descendant) return true;
  82. if (parent.contains){
  83. return parent.contains(descendant);
  84. } else {
  85. return !!(descendant.compareDocumentPosition(parent) & 8);
  86. }
  87. };
  88. /**
  89. * Creates and returns element from html string
  90. * Uses innerHTML to create an element
  91. */
  92. qq.toElement = (function(){
  93. var div = document.createElement('div');
  94. return function(html){
  95. div.innerHTML = html;
  96. var element = div.firstChild;
  97. div.removeChild(element);
  98. return element;
  99. };
  100. })();
  101. //
  102. // Node properties and attributes
  103. /**
  104. * Sets styles for an element.
  105. * Fixes opacity in IE6-8.
  106. */
  107. qq.css = function(element, styles){
  108. if (styles.opacity != null){
  109. if (typeof element.style.opacity != 'string' && typeof(element.filters) != 'undefined'){
  110. styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
  111. }
  112. }
  113. qq.extend(element.style, styles);
  114. };
  115. qq.hasClass = function(element, name){
  116. var re = new RegExp('(^| )' + name + '( |$)');
  117. return re.test(element.className);
  118. };
  119. qq.addClass = function(element, name){
  120. if (!qq.hasClass(element, name)){
  121. element.className += ' ' + name;
  122. }
  123. };
  124. qq.removeClass = function(element, name){
  125. var re = new RegExp('(^| )' + name + '( |$)');
  126. element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
  127. };
  128. qq.setText = function(element, text){
  129. element.innerText = text;
  130. element.textContent = text;
  131. };
  132. //
  133. // Selecting elements
  134. qq.children = function(element){
  135. var children = [],
  136. child = element.firstChild;
  137. while (child){
  138. if (child.nodeType == 1){
  139. children.push(child);
  140. }
  141. child = child.nextSibling;
  142. }
  143. return children;
  144. };
  145. qq.getByClass = function(element, className){
  146. if (element.querySelectorAll){
  147. return element.querySelectorAll('.' + className);
  148. }
  149. var result = [];
  150. var candidates = element.getElementsByTagName("*");
  151. var len = candidates.length;
  152. for (var i = 0; i < len; i++){
  153. if (qq.hasClass(candidates[i], className)){
  154. result.push(candidates[i]);
  155. }
  156. }
  157. return result;
  158. };
  159. /**
  160. * obj2url() takes a json-object as argument and generates
  161. * a querystring. pretty much like jQuery.param()
  162. *
  163. * how to use:
  164. *
  165. * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
  166. *
  167. * will result in:
  168. *
  169. * `http://any.url/upload?otherParam=value&a=b&c=d`
  170. *
  171. * @param Object JSON-Object
  172. * @param String current querystring-part
  173. * @return String encoded querystring
  174. */
  175. qq.obj2url = function(obj, temp, prefixDone){
  176. var uristrings = [],
  177. prefix = '&',
  178. add = function(nextObj, i){
  179. var nextTemp = temp
  180. ? (/\[\]$/.test(temp)) // prevent double-encoding
  181. ? temp
  182. : temp+'['+i+']'
  183. : i;
  184. if ((nextTemp != 'undefined') && (i != 'undefined')) {
  185. uristrings.push(
  186. (typeof nextObj === 'object')
  187. ? qq.obj2url(nextObj, nextTemp, true)
  188. : (Object.prototype.toString.call(nextObj) === '[object Function]')
  189. ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
  190. : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
  191. );
  192. }
  193. };
  194. if (!prefixDone && temp) {
  195. prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
  196. uristrings.push(temp);
  197. uristrings.push(qq.obj2url(obj));
  198. } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj != 'undefined') ) {
  199. // we wont use a for-in-loop on an array (performance)
  200. for (var i = 0, len = obj.length; i < len; ++i){
  201. add(obj[i], i);
  202. }
  203. } else if ((typeof obj != 'undefined') && (obj !== null) && (typeof obj === "object")){
  204. // for anything else but a scalar, we will use for-in-loop
  205. for (var i in obj){
  206. add(obj[i], i);
  207. }
  208. } else {
  209. uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
  210. }
  211. return uristrings.join(prefix)
  212. .replace(/^&/, '')
  213. .replace(/%20/g, '+');
  214. };
  215. //
  216. //
  217. // Uploader Classes
  218. //
  219. //
  220. var qq = qq || {};
  221. /**
  222. * Creates upload button, validates upload, but doesn't create file list or dd.
  223. */
  224. qq.FileUploaderBasic = function(o){
  225. this._options = {
  226. // set to true to see the server response
  227. debug: false,
  228. action: '/upload',
  229. params: {},
  230. button: null,
  231. multiple: true,
  232. maxConnections: 3,
  233. // validation
  234. allowedExtensions: [],
  235. sizeLimit: 2147483648,
  236. minSizeLimit: 0,
  237. // events
  238. // return false to cancel submit
  239. onSubmit: function(id, fileName){ document.getElementById("toggleme").style.display = "block";},
  240. onProgress: function(id, fileName, loaded, total){},
  241. onComplete: function(id, fileName, responseJSON){},
  242. onCancel: function(id, fileName){},
  243. // messages
  244. messages: {
  245. typeError: "{file} has invalid extension. Only {extensions} are allowed.",
  246. //sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
  247. sizeError: "test",
  248. minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
  249. emptyError: "{file} is empty, please select files again without it.",
  250. onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
  251. },
  252. showMessage: function(message){
  253. if (message == "test") {
  254. if (confirm("Uploading a large file, redirecting to a stable uploader. \nCancel if you currently have files already uploaded.")) {
  255. document.getElementById("html5").style.display = "none";
  256. document.getElementById("flash").style.display = "block";
  257. //window.location = "/stable";
  258. }
  259. }
  260. else {
  261. alert(message);
  262. }
  263. }
  264. };
  265. qq.extend(this._options, o);
  266. // number of files being uploaded
  267. this._filesInProgress = 0;
  268. this._handler = this._createUploadHandler();
  269. if (this._options.button){
  270. this._button = this._createUploadButton(this._options.button);
  271. }
  272. this._preventLeaveInProgress();
  273. };
  274. qq.FileUploaderBasic.prototype = {
  275. setParams: function(params){
  276. this._options.params = params;
  277. },
  278. getInProgress: function(){
  279. return this._filesInProgress;
  280. },
  281. _createUploadButton: function(element){
  282. var self = this;
  283. return new qq.UploadButton({
  284. element: element,
  285. multiple: this._options.multiple && qq.UploadHandlerXhr.isSupported(),
  286. onChange: function(input){
  287. self._onInputChange(input);
  288. }
  289. });
  290. },
  291. _createUploadHandler: function(){
  292. var self = this,
  293. handlerClass;
  294. if(qq.UploadHandlerXhr.isSupported()){
  295. handlerClass = 'UploadHandlerXhr';
  296. } else {
  297. handlerClass = 'UploadHandlerForm';
  298. }
  299. var handler = new qq[handlerClass]({
  300. debug: this._options.debug,
  301. action: this._options.action,
  302. maxConnections: this._options.maxConnections,
  303. onProgress: function(id, fileName, loaded, total){
  304. self._onProgress(id, fileName, loaded, total);
  305. self._options.onProgress(id, fileName, loaded, total);
  306. },
  307. onComplete: function(id, fileName, result){
  308. self._onComplete(id, fileName, result);
  309. self._options.onComplete(id, fileName, result);
  310. },
  311. onCancel: function(id, fileName){
  312. self._onCancel(id, fileName);
  313. self._options.onCancel(id, fileName);
  314. }
  315. });
  316. return handler;
  317. },
  318. _preventLeaveInProgress: function(){
  319. var self = this;
  320. qq.attach(window, 'beforeunload', function(e){
  321. if (!self._filesInProgress){return;}
  322. var e = e || window.event;
  323. // for ie, ff
  324. e.returnValue = self._options.messages.onLeave;
  325. // for webkit
  326. return self._options.messages.onLeave;
  327. });
  328. },
  329. _onSubmit: function(id, fileName){
  330. this._filesInProgress++;
  331. },
  332. _onProgress: function(id, fileName, loaded, total){
  333. },
  334. _onComplete: function(id, fileName, result){
  335. this._filesInProgress--;
  336. if (result.error){
  337. this._options.showMessage(result.error);
  338. }
  339. },
  340. _onCancel: function(id, fileName){
  341. this._filesInProgress--;
  342. },
  343. _onInputChange: function(input){
  344. if (this._handler instanceof qq.UploadHandlerXhr){
  345. this._uploadFileList(input.files);
  346. } else {
  347. if (this._validateFile(input)){
  348. this._uploadFile(input);
  349. }
  350. }
  351. this._button.reset();
  352. },
  353. _uploadFileList: function(files){
  354. for (var i=0; i<files.length; i++){
  355. if ( !this._validateFile(files[i])){
  356. return;
  357. }
  358. }
  359. for (var i=0; i<files.length; i++){
  360. this._uploadFile(files[i]);
  361. }
  362. },
  363. _uploadFile: function(fileContainer){
  364. var id = this._handler.add(fileContainer);
  365. var fileName = this._handler.getName(id);
  366. if (this._options.onSubmit(id, fileName) !== false){
  367. this._onSubmit(id, fileName);
  368. this._handler.upload(id, this._options.params);
  369. }
  370. },
  371. _validateFile: function(file){
  372. var name, size;
  373. if (file.value){
  374. // it is a file input
  375. // get input value and remove path to normalize
  376. name = file.value.replace(/.*(\/|\\)/, "");
  377. } else {
  378. // fix missing properties in Safari
  379. name = file.fileName != null ? file.fileName : file.name;
  380. size = file.fileSize != null ? file.fileSize : file.size;
  381. }
  382. if (! this._isAllowedExtension(name)){
  383. this._error('typeError', name);
  384. return false;
  385. } else if (size === 0){
  386. this._error('emptyError', name);
  387. return false;
  388. } else if (size && this._options.sizeLimit && size > this._options.sizeLimit){
  389. this._error('sizeError', name);
  390. return false;
  391. } else if (size && size < this._options.minSizeLimit){
  392. this._error('minSizeError', name);
  393. return false;
  394. }
  395. return true;
  396. },
  397. _error: function(code, fileName){
  398. var message = this._options.messages[code];
  399. function r(name, replacement){ message = message.replace(name, replacement); }
  400. r('{file}', this._formatFileName(fileName));
  401. r('{extensions}', this._options.allowedExtensions.join(', '));
  402. r('{sizeLimit}', this._formatSize(this._options.sizeLimit));
  403. r('{minSizeLimit}', this._formatSize(this._options.minSizeLimit));
  404. this._options.showMessage(message);
  405. },
  406. _formatFileName: function(name){
  407. if (name.length > 33){
  408. name = name.slice(0, 19) + '...' + name.slice(-13);
  409. }
  410. return name;
  411. },
  412. _isAllowedExtension: function(fileName){
  413. var ext = (-1 !== fileName.indexOf('.')) ? fileName.replace(/.*[.]/, '').toLowerCase() : '';
  414. var allowed = this._options.allowedExtensions;
  415. if (!allowed.length){return true;}
  416. for (var i=0; i<allowed.length; i++){
  417. if (allowed[i].toLowerCase() == ext){ return true;}
  418. }
  419. return false;
  420. },
  421. _formatSize: function(bytes){
  422. var i = -1;
  423. do {
  424. bytes = bytes / 1024;
  425. i++;
  426. } while (bytes > 99);
  427. return Math.max(bytes, 0.1).toFixed(1) + ['kB', 'MB', 'GB', 'TB', 'PB', 'EB'][i];
  428. }
  429. };
  430. /**
  431. * Class that creates upload widget with drag-and-drop and file list
  432. * @inherits qq.FileUploaderBasic
  433. */
  434. qq.FileUploader = function(o){
  435. // call parent constructor
  436. qq.FileUploaderBasic.apply(this, arguments);
  437. // additional options
  438. qq.extend(this._options, {
  439. element: null,
  440. // if set, will be used instead of qq-upload-list in template
  441. listElement: null,
  442. template: '<div class="qq-uploader">' +
  443. '<div class="qq-upload-drop-area"><span>Drop files here to upload</span></div>' +
  444. '<div class="qq-upload-button">Click or Drop file(s)</div>' +
  445. '<div id="toggleme" class="qq-upload-list"></div>' +
  446. '</div>',
  447. // template for one item in file list
  448. fileTemplate: '<li>' +
  449. '<span class="qq-upload-file"></span>' +
  450. '<span class="qq-upload-spinner"></span>' +
  451. '<span class="qq-upload-size"></span>' +
  452. '<a class="qq-upload-cancel" href="#">Cancel</a>' +
  453. '<span class="qq-upload-failed-text">Failed</span>' +
  454. '</li>',
  455. classes: {
  456. // used to get elements from templates
  457. button: 'qq-upload-button',
  458. drop: 'qq-upload-drop-area',
  459. dropActive: 'qq-upload-drop-area-active',
  460. list: 'qq-upload-list',
  461. file: 'qq-upload-file',
  462. spinner: 'qq-upload-spinner',
  463. size: 'qq-upload-size',
  464. cancel: 'qq-upload-cancel',
  465. // added to list item when upload completes
  466. // used in css to hide progress spinner
  467. success: 'qq-upload-success',
  468. fail: 'qq-upload-fail'
  469. }
  470. });
  471. // overwrite options with user supplied
  472. qq.extend(this._options, o);
  473. this._element = this._options.element;
  474. this._element.innerHTML = this._options.template;
  475. this._listElement = this._options.listElement || this._find(this._element, 'list');
  476. this._classes = this._options.classes;
  477. this._button = this._createUploadButton(this._find(this._element, 'button'));
  478. this._bindCancelEvent();
  479. this._setupDragDrop();
  480. };
  481. // inherit from Basic Uploader
  482. qq.extend(qq.FileUploader.prototype, qq.FileUploaderBasic.prototype);
  483. qq.extend(qq.FileUploader.prototype, {
  484. /**
  485. * Gets one of the elements listed in this._options.classes
  486. **/
  487. _find: function(parent, type){
  488. var element = qq.getByClass(parent, this._options.classes[type])[0];
  489. if (!element){
  490. throw new Error('element not found ' + type);
  491. }
  492. return element;
  493. },
  494. _setupDragDrop: function(){
  495. var self = this,
  496. dropArea = this._find(this._element, 'drop');
  497. var dz = new qq.UploadDropZone({
  498. element: dropArea,
  499. onEnter: function(e){
  500. qq.addClass(dropArea, self._classes.dropActive);
  501. e.stopPropagation();
  502. },
  503. onLeave: function(e){
  504. e.stopPropagation();
  505. },
  506. onLeaveNotDescendants: function(e){
  507. qq.removeClass(dropArea, self._classes.dropActive);
  508. },
  509. onDrop: function(e){
  510. dropArea.style.display = 'none';
  511. qq.removeClass(dropArea, self._classes.dropActive);
  512. self._uploadFileList(e.dataTransfer.files);
  513. }
  514. });
  515. dropArea.style.display = 'none';
  516. qq.attach(document, 'dragenter', function(e){
  517. if (!dz._isValidFileDrag(e)) return;
  518. dropArea.style.display = 'block';
  519. });
  520. qq.attach(document, 'dragleave', function(e){
  521. if (!dz._isValidFileDrag(e)) return;
  522. var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
  523. // only fire when leaving document out
  524. if ( ! relatedTarget || relatedTarget.nodeName == "HTML"){
  525. dropArea.style.display = 'none';
  526. }
  527. });
  528. },
  529. _onSubmit: function(id, fileName){
  530. qq.FileUploaderBasic.prototype._onSubmit.apply(this, arguments);
  531. this._addToList(id, fileName);
  532. },
  533. _onProgress: function(id, fileName, loaded, total){
  534. qq.FileUploaderBasic.prototype._onProgress.apply(this, arguments);
  535. var item = this._getItemByFileId(id);
  536. var size = this._find(item, 'size');
  537. size.style.display = 'inline';
  538. var text;
  539. if (loaded != total){
  540. text = Math.round(loaded / total * 100) + '% from ' + this._formatSize(total);
  541. } else {
  542. text = this._formatSize(total);
  543. }
  544. qq.setText(size, text);
  545. },
  546. _onComplete: function(id, fileName, result){
  547. qq.FileUploaderBasic.prototype._onComplete.apply(this, arguments);
  548. // mark completed
  549. var item = this._getItemByFileId(id);
  550. qq.remove(this._find(item, 'cancel'));
  551. qq.remove(this._find(item, 'spinner'));
  552. if (result){
  553. qq.addClass(item, this._classes.success);
  554. item.innerHTML = '<a target="_blank" href="' + result.url + '">' + result.url + '</a>&nbsp;<br />'
  555. } else {
  556. qq.addClass(item, this._classes.fail);
  557. }
  558. },
  559. _addToList: function(id, fileName){
  560. var item = qq.toElement(this._options.fileTemplate);
  561. item.qqFileId = id;
  562. var fileElement = this._find(item, 'file');
  563. qq.setText(fileElement, this._formatFileName(fileName));
  564. this._find(item, 'size').style.display = 'none';
  565. this._listElement.appendChild(item);
  566. },
  567. _getItemByFileId: function(id){
  568. var item = this._listElement.firstChild;
  569. // there can't be txt nodes in dynamically created list
  570. // and we can use nextSibling
  571. while (item){
  572. if (item.qqFileId == id) return item;
  573. item = item.nextSibling;
  574. }
  575. },
  576. /**
  577. * delegate click event for cancel link
  578. **/
  579. _bindCancelEvent: function(){
  580. var self = this,
  581. list = this._listElement;
  582. qq.attach(list, 'click', function(e){
  583. e = e || window.event;
  584. var target = e.target || e.srcElement;
  585. if (qq.hasClass(target, self._classes.cancel)){
  586. qq.preventDefault(e);
  587. var item = target.parentNode;
  588. self._handler.cancel(item.qqFileId);
  589. qq.remove(item);
  590. }
  591. });
  592. }
  593. });
  594. qq.UploadDropZone = function(o){
  595. this._options = {
  596. element: null,
  597. onEnter: function(e){},
  598. onLeave: function(e){},
  599. // is not fired when leaving element by hovering descendants
  600. onLeaveNotDescendants: function(e){},
  601. onDrop: function(e){}
  602. };
  603. qq.extend(this._options, o);
  604. this._element = this._options.element;
  605. this._disableDropOutside();
  606. this._attachEvents();
  607. };
  608. qq.UploadDropZone.prototype = {
  609. _disableDropOutside: function(e){
  610. // run only once for all instances
  611. if (!qq.UploadDropZone.dropOutsideDisabled ){
  612. qq.attach(document, 'dragover', function(e){
  613. if (e.dataTransfer){
  614. e.dataTransfer.dropEffect = 'none';
  615. e.preventDefault();
  616. }
  617. });
  618. qq.UploadDropZone.dropOutsideDisabled = true;
  619. }
  620. },
  621. _attachEvents: function(){
  622. var self = this;
  623. qq.attach(self._element, 'dragover', function(e){
  624. if (!self._isValidFileDrag(e)) return;
  625. var effect = e.dataTransfer.effectAllowed;
  626. if (effect == 'move' || effect == 'linkMove'){
  627. e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
  628. } else {
  629. e.dataTransfer.dropEffect = 'copy'; // for Chrome
  630. }
  631. e.stopPropagation();
  632. e.preventDefault();
  633. });
  634. qq.attach(self._element, 'dragenter', function(e){
  635. if (!self._isValidFileDrag(e)) return;
  636. self._options.onEnter(e);
  637. });
  638. qq.attach(self._element, 'dragleave', function(e){
  639. if (!self._isValidFileDrag(e)) return;
  640. self._options.onLeave(e);
  641. var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
  642. // do not fire when moving a mouse over a descendant
  643. if (qq.contains(this, relatedTarget)) return;
  644. self._options.onLeaveNotDescendants(e);
  645. });
  646. qq.attach(self._element, 'drop', function(e){
  647. if (!self._isValidFileDrag(e)) return;
  648. e.preventDefault();
  649. self._options.onDrop(e);
  650. });
  651. },
  652. _isValidFileDrag: function(e){
  653. var dt = e.dataTransfer,
  654. // do not check dt.types.contains in webkit, because it crashes safari 4
  655. isWebkit = navigator.userAgent.indexOf("AppleWebKit") > -1;
  656. // dt.effectAllowed is none in Safari 5
  657. // dt.types.contains check is for firefox
  658. return dt && dt.effectAllowed != 'none' &&
  659. (dt.files || (!isWebkit && dt.types.contains && dt.types.contains('Files')));
  660. }
  661. };
  662. qq.UploadButton = function(o){
  663. this._options = {
  664. element: null,
  665. // if set to true adds multiple attribute to file input
  666. multiple: false,
  667. // name attribute of file input
  668. name: 'file',
  669. onChange: function(input){},
  670. hoverClass: 'qq-upload-button-hover',
  671. focusClass: 'qq-upload-button-focus'
  672. };
  673. qq.extend(this._options, o);
  674. this._element = this._options.element;
  675. // make button suitable container for input
  676. qq.css(this._element, {
  677. position: 'relative',
  678. overflow: 'hidden',
  679. // Make sure browse button is in the right side
  680. // in Internet Explorer
  681. direction: 'ltr'
  682. });
  683. this._input = this._createInput();
  684. };
  685. qq.UploadButton.prototype = {
  686. /* returns file input element */
  687. getInput: function(){
  688. return this._input;
  689. },
  690. /* cleans/recreates the file input */
  691. reset: function(){
  692. if (this._input.parentNode){
  693. qq.remove(this._input);
  694. }
  695. qq.removeClass(this._element, this._options.focusClass);
  696. this._input = this._createInput();
  697. },
  698. _createInput: function(){
  699. var input = document.createElement("input");
  700. if (this._options.multiple){
  701. input.setAttribute("multiple", "multiple");
  702. }
  703. input.setAttribute("type", "file");
  704. input.setAttribute("name", this._options.name);
  705. qq.css(input, {
  706. position: 'absolute',
  707. // in Opera only 'browse' button
  708. // is clickable and it is located at
  709. // the right side of the input
  710. right: 0,
  711. top: 0,
  712. fontFamily: 'Arial',
  713. // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
  714. fontSize: '118px',
  715. margin: 0,
  716. padding: 0,
  717. cursor: 'pointer',
  718. opacity: 0
  719. });
  720. this._element.appendChild(input);
  721. var self = this;
  722. qq.attach(input, 'change', function(){
  723. self._options.onChange(input);
  724. });
  725. qq.attach(input, 'mouseover', function(){
  726. qq.addClass(self._element, self._options.hoverClass);
  727. });
  728. qq.attach(input, 'mouseout', function(){
  729. qq.removeClass(self._element, self._options.hoverClass);
  730. });
  731. qq.attach(input, 'focus', function(){
  732. qq.addClass(self._element, self._options.focusClass);
  733. });
  734. qq.attach(input, 'blur', function(){
  735. qq.removeClass(self._element, self._options.focusClass);
  736. });
  737. // IE and Opera, unfortunately have 2 tab stops on file input
  738. // which is unacceptable in our case, disable keyboard access
  739. if (window.attachEvent){
  740. // it is IE or Opera
  741. input.setAttribute('tabIndex', "-1");
  742. }
  743. return input;
  744. }
  745. };
  746. /**
  747. * Class for uploading files, uploading itself is handled by child classes
  748. */
  749. qq.UploadHandlerAbstract = function(o){
  750. this._options = {
  751. debug: false,
  752. action: '/testest',
  753. // maximum number of concurrent uploads
  754. maxConnections: 999,
  755. onProgress: function(id, fileName, loaded, total){},
  756. onComplete: function(id, fileName, response){},
  757. onCancel: function(id, fileName){}
  758. };
  759. qq.extend(this._options, o);
  760. this._queue = [];
  761. // params for files in queue
  762. this._params = [];
  763. };
  764. qq.UploadHandlerAbstract.prototype = {
  765. log: function(str){
  766. if (this._options.debug && window.console) console.log('[uploader] ' + str);
  767. },
  768. /**
  769. * Adds file or file input to the queue
  770. * @returns id
  771. **/
  772. add: function(file){},
  773. /**
  774. * Sends the file identified by id and additional query params to the server
  775. */
  776. upload: function(id, params){
  777. var len = this._queue.push(id);
  778. var copy = {};
  779. qq.extend(copy, params);
  780. this._params[id] = copy;
  781. // if too many active uploads, wait...
  782. if (len <= this._options.maxConnections){
  783. this._upload(id, this._params[id]);
  784. }
  785. },
  786. /**
  787. * Cancels file upload by id
  788. */
  789. cancel: function(id){
  790. this._cancel(id);
  791. this._dequeue(id);
  792. },
  793. /**
  794. * Cancells all uploads
  795. */
  796. cancelAll: function(){
  797. for (var i=0; i<this._queue.length; i++){
  798. this._cancel(this._queue[i]);
  799. }
  800. this._queue = [];
  801. },
  802. /**
  803. * Returns name of the file identified by id
  804. */
  805. getName: function(id){},
  806. /**
  807. * Returns size of the file identified by id
  808. */
  809. getSize: function(id){},
  810. /**
  811. * Returns id of files being uploaded or
  812. * waiting for their turn
  813. */
  814. getQueue: function(){
  815. return this._queue;
  816. },
  817. /**
  818. * Actual upload method
  819. */
  820. _upload: function(id){},
  821. /**
  822. * Actual cancel method
  823. */
  824. _cancel: function(id){},
  825. /**
  826. * Removes element from queue, starts upload of next
  827. */
  828. _dequeue: function(id){
  829. var i = qq.indexOf(this._queue, id);
  830. this._queue.splice(i, 1);
  831. var max = this._options.maxConnections;
  832. if (this._queue.length >= max && i < max){
  833. var nextId = this._queue[max-1];
  834. this._upload(nextId, this._params[nextId]);
  835. }
  836. }
  837. };
  838. /**
  839. * Class for uploading files using form and iframe
  840. * @inherits qq.UploadHandlerAbstract
  841. */
  842. qq.UploadHandlerForm = function(o){
  843. qq.UploadHandlerAbstract.apply(this, arguments);
  844. this._inputs = {};
  845. };
  846. // @inherits qq.UploadHandlerAbstract
  847. qq.extend(qq.UploadHandlerForm.prototype, qq.UploadHandlerAbstract.prototype);
  848. qq.extend(qq.UploadHandlerForm.prototype, {
  849. add: function(fileInput){
  850. fileInput.setAttribute('name', 'qqfile');
  851. var id = 'qq-upload-handler-iframe' + qq.getUniqueId();
  852. this._inputs[id] = fileInput;
  853. // remove file input from DOM
  854. if (fileInput.parentNode){
  855. qq.remove(fileInput);
  856. }
  857. return id;
  858. },
  859. getName: function(id){
  860. // get input value and remove path to normalize
  861. return this._inputs[id].value.replace(/.*(\/|\\)/, "");
  862. },
  863. _cancel: function(id){
  864. this._options.onCancel(id, this.getName(id));
  865. delete this._inputs[id];
  866. var iframe = document.getElementById(id);
  867. if (iframe){
  868. // to cancel request set src to something else
  869. // we use src="javascript:false;" because it doesn't
  870. // trigger ie6 prompt on https
  871. iframe.setAttribute('src', 'javascript:false;');
  872. qq.remove(iframe);
  873. }
  874. },
  875. _upload: function(id, params){
  876. var input = this._inputs[id];
  877. if (!input){
  878. throw new Error('file with passed id was not added, or already uploaded or cancelled');
  879. }
  880. var fileName = this.getName(id);
  881. var iframe = this._createIframe(id);
  882. var form = this._createForm(iframe, params);
  883. form.appendChild(input);
  884. var self = this;
  885. this._attachLoadEvent(iframe, function(){
  886. self.log('iframe loaded');
  887. var response = self._getIframeContentJSON(iframe);
  888. self._options.onComplete(id, fileName, response);
  889. self._dequeue(id);
  890. delete self._inputs[id];
  891. // timeout added to fix busy state in FF3.6
  892. setTimeout(function(){
  893. qq.remove(iframe);
  894. }, 1);
  895. });
  896. form.submit();
  897. qq.remove(form);
  898. return id;
  899. },
  900. _attachLoadEvent: function(iframe, callback){
  901. qq.attach(iframe, 'load', function(){
  902. // when we remove iframe from dom
  903. // the request stops, but in IE load
  904. // event fires
  905. if (!iframe.parentNode){
  906. return;
  907. }
  908. // fixing Opera 10.53
  909. if (iframe.contentDocument &&
  910. iframe.contentDocument.body &&
  911. iframe.contentDocument.body.innerHTML == "false"){
  912. // In Opera event is fired second time
  913. // when body.innerHTML changed from false
  914. // to server response approx. after 1 sec
  915. // when we upload file with iframe
  916. return;
  917. }
  918. callback();
  919. });
  920. },
  921. /**
  922. * Returns json object received by iframe from server.
  923. */
  924. _getIframeContentJSON: function(iframe){
  925. // iframe.contentWindow.document - for IE<7
  926. var doc = iframe.contentDocument ? iframe.contentDocument: iframe.contentWindow.document,
  927. response;
  928. this.log("converting iframe's innerHTML to JSON");
  929. this.log("innerHTML = " + doc.body.innerHTML);
  930. try {
  931. response = eval("(" + doc.body.innerHTML + ")");
  932. } catch(err){
  933. response = {};
  934. }
  935. return response;
  936. },
  937. /**
  938. * Creates iframe with unique name
  939. */
  940. _createIframe: function(id){
  941. // We can't use following code as the name attribute
  942. // won't be properly registered in IE6, and new window
  943. // on form submit will open
  944. // var iframe = document.createElement('iframe');
  945. // iframe.setAttribute('name', id);
  946. var iframe = qq.toElement('<iframe src="javascript:false;" name="' + id + '" />');
  947. // src="javascript:false;" removes ie6 prompt on https
  948. iframe.setAttribute('id', id);
  949. iframe.style.display = 'none';
  950. document.body.appendChild(iframe);
  951. return iframe;
  952. },
  953. /**
  954. * Creates form, that will be submitted to iframe
  955. */
  956. _createForm: function(iframe, params){
  957. // We can't use the following code in IE6
  958. // var form = document.createElement('form');
  959. // form.setAttribute('method', 'post');
  960. // form.setAttribute('enctype', 'multipart/form-data');
  961. // Because in this case file won't be attached to request
  962. var form = qq.toElement('<form method="post" enctype="multipart/form-data"></form>');
  963. var queryString = qq.obj2url(params, this._options.action);
  964. form.setAttribute('action', queryString);
  965. form.setAttribute('target', iframe.name);
  966. form.style.display = 'none';
  967. document.body.appendChild(form);
  968. return form;
  969. }
  970. });
  971. /**
  972. * Class for uploading files using xhr
  973. * @inherits qq.UploadHandlerAbstract
  974. */
  975. qq.UploadHandlerXhr = function(o){
  976. qq.UploadHandlerAbstract.apply(this, arguments);
  977. this._files = [];
  978. this._xhrs = [];
  979. // current loaded size in bytes for each file
  980. this._loaded = [];
  981. };
  982. // static method
  983. qq.UploadHandlerXhr.isSupported = function(){
  984. var input = document.createElement('input');
  985. input.type = 'file';
  986. return (
  987. 'multiple' in input &&
  988. typeof File != "undefined" &&
  989. typeof (new XMLHttpRequest()).upload != "undefined" );
  990. };
  991. // @inherits qq.UploadHandlerAbstract
  992. qq.extend(qq.UploadHandlerXhr.prototype, qq.UploadHandlerAbstract.prototype)
  993. qq.extend(qq.UploadHandlerXhr.prototype, {
  994. /**
  995. * Adds file to the queue
  996. * Returns id to use with upload, cancel
  997. **/
  998. add: function(file){
  999. if (!(file instanceof File)){
  1000. throw new Error('Passed obj in not a File (in qq.UploadHandlerXhr)');
  1001. }
  1002. return this._files.push(file) - 1;
  1003. },
  1004. getName: function(id){
  1005. var file = this._files[id];
  1006. // fix missing name in Safari 4
  1007. return file.fileName != null ? file.fileName : file.name;
  1008. },
  1009. getSize: function(id){
  1010. var file = this._files[id];
  1011. return file.fileSize != null ? file.fileSize : file.size;
  1012. },
  1013. /**
  1014. * Returns uploaded bytes for file identified by id
  1015. */
  1016. getLoaded: function(id){
  1017. return this._loaded[id] || 0;
  1018. },
  1019. /**
  1020. * Sends the file identified by id and additional query params to the server
  1021. * @param {Object} params name-value string pairs
  1022. */
  1023. _upload: function(id, params){
  1024. var file = this._files[id],
  1025. name = this.getName(id),
  1026. size = this.getSize(id);
  1027. this._loaded[id] = 0;
  1028. var xhr = this._xhrs[id] = new XMLHttpRequest();
  1029. var self = this;
  1030. xhr.upload.onprogress = function(e){
  1031. if (e.lengthComputable){
  1032. self._loaded[id] = e.loaded;
  1033. self._options.onProgress(id, name, e.loaded, e.total);
  1034. }
  1035. };
  1036. xhr.onreadystatechange = function(){
  1037. if (xhr.readyState == 4){
  1038. self._onComplete(id, xhr);
  1039. }
  1040. };
  1041. // build query string
  1042. params = params || {};
  1043. params['qqfile'] = name;
  1044. var queryString = qq.obj2url(params, this._options.action);
  1045. xhr.open("POST", queryString, true);
  1046. xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
  1047. xhr.setRequestHeader("X-File-Name", encodeURIComponent(name));
  1048. xhr.setRequestHeader("Content-Type", "application/octet-stream");
  1049. xhr.setRequestHeader("Accept", "application/json");
  1050. xhr.send(file);
  1051. },
  1052. _onComplete: function(id, xhr){
  1053. // the request was aborted/cancelled
  1054. if (!this._files[id]) return;
  1055. var name = this.getName(id);
  1056. var size = this.getSize(id);
  1057. this._options.onProgress(id, name, size, size);
  1058. if (xhr.status == 200){
  1059. this.log("xhr - server response received");
  1060. this.log("responseText = " + xhr.responseText);
  1061. var response;
  1062. try {
  1063. response = eval("(" + xhr.responseText + ")");
  1064. } catch(err){
  1065. response = {};
  1066. }
  1067. this._options.onComplete(id, name, response);
  1068. } else {
  1069. this._options.onComplete(id, name, {});
  1070. }
  1071. this._files[id] = null;
  1072. this._xhrs[id] = null;
  1073. this._dequeue(id);
  1074. },
  1075. _cancel: function(id){
  1076. this._options.onCancel(id, this.getName(id));
  1077. this._files[id] = null;
  1078. if (this._xhrs[id]){
  1079. this._xhrs[id].abort();
  1080. this._xhrs[id] = null;
  1081. }
  1082. }
  1083. });
  1084. var uploader = new qq.FileUploader({
  1085. element: document.getElementById('file-uploader'),
  1086. action: '/upload',
  1087. params: {
  1088. expires: $('#expires').val(),
  1089. randomize: true,
  1090. },
  1091. messages: {
  1092. typeError: "{file} has invalid extension. Only {extensions} are allowed.",
  1093. sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
  1094. minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
  1095. emptyError: "{file} is empty, please select files again without it.",
  1096. onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
  1097. },
  1098. showMessage: function(message){
  1099. alert(message);
  1100. },
  1101. });