koda 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. data/lib/helpers/app_helper.rb +40 -0
  2. data/lib/helpers/koda_helper.rb +105 -0
  3. data/lib/helpers/view_helper.rb +146 -0
  4. data/lib/koda.rb +123 -0
  5. data/lib/models/hash.rb +29 -0
  6. data/lib/models/mongo_collection.rb +144 -0
  7. data/lib/models/mongo_config.rb +22 -0
  8. data/lib/models/mongo_database.rb +136 -0
  9. data/lib/models/mongo_document.rb +124 -0
  10. data/lib/models/mongo_grid.rb +85 -0
  11. data/lib/models/mongo_media.rb +55 -0
  12. data/lib/models/user_access_provider.rb +135 -0
  13. data/lib/models/user_context.rb +19 -0
  14. data/lib/routes/koda_api.rb +392 -0
  15. data/lib/routes/koda_site.rb +57 -0
  16. data/lib/views/console.erb +139 -0
  17. data/lib/views/explorer.erb +119 -0
  18. data/lib/views/login.erb +77 -0
  19. data/lib/views/not_allowed.erb +75 -0
  20. data/public/koda/Help/DataTypes.txt +82 -0
  21. data/public/koda/Help/KodaType_Template.js +48 -0
  22. data/public/koda/apple-touch-icon-114x114-precomposed.png +0 -0
  23. data/public/koda/apple-touch-icon-144x144-precomposed.png +0 -0
  24. data/public/koda/apple-touch-icon-57x57-precomposed.png +0 -0
  25. data/public/koda/apple-touch-icon-72x72-precomposed.png +0 -0
  26. data/public/koda/apple-touch-icon-precomposed.png +0 -0
  27. data/public/koda/apple-touch-icon.png +0 -0
  28. data/public/koda/css/bootstrap-responsive.css +1058 -0
  29. data/public/koda/css/bootstrap.css +5774 -0
  30. data/public/koda/css/main.css +288 -0
  31. data/public/koda/favicon.ico +0 -0
  32. data/public/koda/fonts/angelina-webfont.eot +0 -0
  33. data/public/koda/fonts/angelina-webfont.svg +228 -0
  34. data/public/koda/fonts/angelina-webfont.ttf +0 -0
  35. data/public/koda/fonts/angelina-webfont.woff +0 -0
  36. data/public/koda/fonts/coolvetica_rg-webfont.eot +0 -0
  37. data/public/koda/fonts/coolvetica_rg-webfont.svg +232 -0
  38. data/public/koda/fonts/coolvetica_rg-webfont.ttf +0 -0
  39. data/public/koda/fonts/coolvetica_rg-webfont.woff +0 -0
  40. data/public/koda/fonts/ladyic__-webfont.eot +0 -0
  41. data/public/koda/fonts/ladyic__-webfont.svg +257 -0
  42. data/public/koda/fonts/ladyic__-webfont.ttf +0 -0
  43. data/public/koda/fonts/ladyic__-webfont.woff +0 -0
  44. data/public/koda/images/ajax-loader.gif +0 -0
  45. data/public/koda/images/back.png +0 -0
  46. data/public/koda/images/bg-table-thead.png +0 -0
  47. data/public/koda/images/big_folder.png +0 -0
  48. data/public/koda/images/box_file.png +0 -0
  49. data/public/koda/images/car_add.png +0 -0
  50. data/public/koda/images/compress.png +0 -0
  51. data/public/koda/images/database_table.png +0 -0
  52. data/public/koda/images/feed_add.png +0 -0
  53. data/public/koda/images/feed_link.png +0 -0
  54. data/public/koda/images/file.png +0 -0
  55. data/public/koda/images/folder.png +0 -0
  56. data/public/koda/images/folder_image.png +0 -0
  57. data/public/koda/images/glyphicons-halflings-white.png +0 -0
  58. data/public/koda/images/glyphicons-halflings.png +0 -0
  59. data/public/koda/images/group_add.png +0 -0
  60. data/public/koda/images/group_key.png +0 -0
  61. data/public/koda/images/image_add.png +0 -0
  62. data/public/koda/images/layout_add.png +0 -0
  63. data/public/koda/images/package.png +0 -0
  64. data/public/koda/images/page_white_edit.png +0 -0
  65. data/public/koda/images/page_white_text.png +0 -0
  66. data/public/koda/images/photo_add.png +0 -0
  67. data/public/koda/images/toggle-collapse-dark.png +0 -0
  68. data/public/koda/images/toggle-collapse-light.png +0 -0
  69. data/public/koda/images/toggle-expand-dark.png +0 -0
  70. data/public/koda/images/toggle-expand-light.png +0 -0
  71. data/public/koda/images/twitter.png +0 -0
  72. data/public/koda/koda-editors/KodaEditor.js +843 -0
  73. data/public/koda/koda-editors/collection-editor.html +56 -0
  74. data/public/koda/koda-editors/generic-editor.css +112 -0
  75. data/public/koda/koda-editors/generic-editor.html +74 -0
  76. data/public/koda/koda-editors/koda-editor.css +72 -0
  77. data/public/koda/koda-editors/twitterfeed-editor.html +90 -0
  78. data/public/koda/koda-types/_builtin_registration.json +62 -0
  79. data/public/koda/koda-types/koda-access.json +49 -0
  80. data/public/koda/koda-types/koda-collection.json +12 -0
  81. data/public/koda/koda-types/koda-generictext.json +50 -0
  82. data/public/koda/koda-types/koda-media.json +58 -0
  83. data/public/koda/koda-types/koda-twitterfeed.json +79 -0
  84. data/public/koda/koda-types/koda-user.json +71 -0
  85. data/public/koda/nicEditorIcons.gif +0 -0
  86. data/public/koda/scripts/Koda.js +1200 -0
  87. data/public/koda/scripts/lib/DOMAssistant.js +4 -0
  88. data/public/koda/scripts/lib/modernizr.js +4 -0
  89. data/public/koda/scripts/lib/respond.js +2 -0
  90. data/public/koda/scripts/lib/selectivizr.js +5 -0
  91. data/public/koda/scripts/plugins/bootstrap.js +2027 -0
  92. data/public/koda/scripts/plugins/box.js +8 -0
  93. data/public/koda/scripts/plugins/fancybox/blank.gif +0 -0
  94. data/public/koda/scripts/plugins/fancybox/fancy_close.png +0 -0
  95. data/public/koda/scripts/plugins/fancybox/fancy_loading.png +0 -0
  96. data/public/koda/scripts/plugins/fancybox/fancy_nav_left.png +0 -0
  97. data/public/koda/scripts/plugins/fancybox/fancy_nav_right.png +0 -0
  98. data/public/koda/scripts/plugins/fancybox/fancy_shadow_e.png +0 -0
  99. data/public/koda/scripts/plugins/fancybox/fancy_shadow_n.png +0 -0
  100. data/public/koda/scripts/plugins/fancybox/fancy_shadow_ne.png +0 -0
  101. data/public/koda/scripts/plugins/fancybox/fancy_shadow_nw.png +0 -0
  102. data/public/koda/scripts/plugins/fancybox/fancy_shadow_s.png +0 -0
  103. data/public/koda/scripts/plugins/fancybox/fancy_shadow_se.png +0 -0
  104. data/public/koda/scripts/plugins/fancybox/fancy_shadow_sw.png +0 -0
  105. data/public/koda/scripts/plugins/fancybox/fancy_shadow_w.png +0 -0
  106. data/public/koda/scripts/plugins/fancybox/fancy_title_left.png +0 -0
  107. data/public/koda/scripts/plugins/fancybox/fancy_title_main.png +0 -0
  108. data/public/koda/scripts/plugins/fancybox/fancy_title_over.png +0 -0
  109. data/public/koda/scripts/plugins/fancybox/fancy_title_right.png +0 -0
  110. data/public/koda/scripts/plugins/fancybox/fancybox-x.png +0 -0
  111. data/public/koda/scripts/plugins/fancybox/fancybox-y.png +0 -0
  112. data/public/koda/scripts/plugins/fancybox/fancybox.png +0 -0
  113. data/public/koda/scripts/plugins/fancybox/jquery.easing-1.3.pack.js +72 -0
  114. data/public/koda/scripts/plugins/fancybox/jquery.fancybox-1.3.4.css +359 -0
  115. data/public/koda/scripts/plugins/fancybox/jquery.fancybox-1.3.4.js +1155 -0
  116. data/public/koda/scripts/plugins/fancybox/jquery.fancybox-1.3.4.pack.js +46 -0
  117. data/public/koda/scripts/plugins/fancybox/jquery.mousewheel-3.0.4.pack.js +14 -0
  118. data/public/koda/scripts/plugins/fileuploader.js +1527 -0
  119. data/public/koda/scripts/plugins/jquery-class.js +7 -0
  120. data/public/koda/scripts/plugins/jquery.contextmenu/images/cut.png +0 -0
  121. data/public/koda/scripts/plugins/jquery.contextmenu/images/door.png +0 -0
  122. data/public/koda/scripts/plugins/jquery.contextmenu/images/page_white_copy.png +0 -0
  123. data/public/koda/scripts/plugins/jquery.contextmenu/images/page_white_delete.png +0 -0
  124. data/public/koda/scripts/plugins/jquery.contextmenu/images/page_white_edit.png +0 -0
  125. data/public/koda/scripts/plugins/jquery.contextmenu/images/page_white_paste.png +0 -0
  126. data/public/koda/scripts/plugins/jquery.contextmenu/jquery.contextMenu.css +63 -0
  127. data/public/koda/scripts/plugins/jquery.contextmenu/jquery.contextMenu.js +211 -0
  128. data/public/koda/scripts/plugins/jquery.js +2 -0
  129. data/public/koda/scripts/plugins/json2.js +277 -0
  130. data/public/koda/scripts/plugins/modernizr.js +2 -0
  131. data/public/koda/scripts/plugins/nicEdit.js +183 -0
  132. data/public/koda/scripts/plugins/qunit.js +1448 -0
  133. data/public/koda/scripts/plugins/spin.js +2 -0
  134. data/public/koda/scripts/plugins/uploader/README.md +77 -0
  135. data/public/koda/scripts/plugins/uploader/README.txt +89 -0
  136. data/public/koda/scripts/plugins/uploader/example/application.js +20 -0
  137. data/public/koda/scripts/plugins/uploader/example/index.html +109 -0
  138. data/public/koda/scripts/plugins/uploader/example/style.css +22 -0
  139. data/public/koda/scripts/plugins/uploader/example/upload.php +313 -0
  140. data/public/koda/scripts/plugins/uploader/jquery.fileupload-ui.css +100 -0
  141. data/public/koda/scripts/plugins/uploader/jquery.fileupload-ui.js +642 -0
  142. data/public/koda/scripts/plugins/uploader/jquery.fileupload.js +711 -0
  143. data/public/koda/scripts/plugins/uploader/jquery.iframe-transport.js +133 -0
  144. data/public/koda/scripts/plugins/uploader/pbar-ani.gif +0 -0
  145. data/public/koda/scripts/plugins/uploader/tests/index.html +115 -0
  146. data/public/koda/scripts/plugins/uploader/tests/tests.js +1008 -0
  147. data/public/koda/scripts/require.js +32 -0
  148. data/public/koda/scripts/specs.js +12 -0
  149. data/public/koda/scripts/specs/cd-command-spec.js +51 -0
  150. data/public/koda/scripts/specs/controller-spec.js +95 -0
  151. data/public/koda/scripts/specs/doubles/mock-command.js +21 -0
  152. data/public/koda/scripts/specs/doubles/mock-jamservice.js +33 -0
  153. data/public/koda/scripts/specs/doubles/mock-prompt.js +30 -0
  154. data/public/koda/scripts/specs/doubles/uiobject-double.js +15 -0
  155. data/public/koda/scripts/specs/edit-command-spec.js +76 -0
  156. data/public/koda/scripts/specs/ls-command-spec.js +61 -0
  157. data/public/koda/scripts/specs/mkdir-command-spec.js +40 -0
  158. data/public/koda/scripts/specs/peek-command-spec.js +24 -0
  159. data/public/koda/scripts/specs/remove-command-spec.js +37 -0
  160. data/public/koda/scripts/specs/service-spec.js +85 -0
  161. metadata +402 -0
@@ -0,0 +1,46 @@
1
+ /*
2
+ * FancyBox - jQuery Plugin
3
+ * Simple and fancy lightbox alternative
4
+ *
5
+ * Examples and documentation at: http://fancybox.net
6
+ *
7
+ * Copyright (c) 2008 - 2010 Janis Skarnelis
8
+ * That said, it is hardly a one-person project. Many people have submitted bugs, code, and offered their advice freely. Their support is greatly appreciated.
9
+ *
10
+ * Version: 1.3.4 (11/11/2010)
11
+ * Requires: jQuery v1.3+
12
+ *
13
+ * Dual licensed under the MIT and GPL licenses:
14
+ * http://www.opensource.org/licenses/mit-license.php
15
+ * http://www.gnu.org/licenses/gpl.html
16
+ */
17
+
18
+ ;(function(b){var m,t,u,f,D,j,E,n,z,A,q=0,e={},o=[],p=0,d={},l=[],G=null,v=new Image,J=/\.(jpg|gif|png|bmp|jpeg)(.*)?$/i,W=/[^\.]\.(swf)\s*$/i,K,L=1,y=0,s="",r,i,h=false,B=b.extend(b("<div/>")[0],{prop:0}),M=b.browser.msie&&b.browser.version<7&&!window.XMLHttpRequest,N=function(){t.hide();v.onerror=v.onload=null;G&&G.abort();m.empty()},O=function(){if(false===e.onError(o,q,e)){t.hide();h=false}else{e.titleShow=false;e.width="auto";e.height="auto";m.html('<p id="fancybox-error">The requested content cannot be loaded.<br />Please try again later.</p>');
19
+ F()}},I=function(){var a=o[q],c,g,k,C,P,w;N();e=b.extend({},b.fn.fancybox.defaults,typeof b(a).data("fancybox")=="undefined"?e:b(a).data("fancybox"));w=e.onStart(o,q,e);if(w===false)h=false;else{if(typeof w=="object")e=b.extend(e,w);k=e.title||(a.nodeName?b(a).attr("title"):a.title)||"";if(a.nodeName&&!e.orig)e.orig=b(a).children("img:first").length?b(a).children("img:first"):b(a);if(k===""&&e.orig&&e.titleFromAlt)k=e.orig.attr("alt");c=e.href||(a.nodeName?b(a).attr("href"):a.href)||null;if(/^(?:javascript)/i.test(c)||
20
+ c=="#")c=null;if(e.type){g=e.type;if(!c)c=e.content}else if(e.content)g="html";else if(c)g=c.match(J)?"image":c.match(W)?"swf":b(a).hasClass("iframe")?"iframe":c.indexOf("#")===0?"inline":"ajax";if(g){if(g=="inline"){a=c.substr(c.indexOf("#"));g=b(a).length>0?"inline":"ajax"}e.type=g;e.href=c;e.title=k;if(e.autoDimensions)if(e.type=="html"||e.type=="inline"||e.type=="ajax"){e.width="auto";e.height="auto"}else e.autoDimensions=false;if(e.modal){e.overlayShow=true;e.hideOnOverlayClick=false;e.hideOnContentClick=
21
+ false;e.enableEscapeButton=false;e.showCloseButton=false}e.padding=parseInt(e.padding,10);e.margin=parseInt(e.margin,10);m.css("padding",e.padding+e.margin);b(".fancybox-inline-tmp").unbind("fancybox-cancel").bind("fancybox-change",function(){b(this).replaceWith(j.children())});switch(g){case "html":m.html(e.content);F();break;case "inline":if(b(a).parent().is("#fancybox-content")===true){h=false;break}b('<div class="fancybox-inline-tmp" />').hide().insertBefore(b(a)).bind("fancybox-cleanup",function(){b(this).replaceWith(j.children())}).bind("fancybox-cancel",
22
+ function(){b(this).replaceWith(m.children())});b(a).appendTo(m);F();break;case "image":h=false;b.fancybox.showActivity();v=new Image;v.onerror=function(){O()};v.onload=function(){h=true;v.onerror=v.onload=null;e.width=v.width;e.height=v.height;b("<img />").attr({id:"fancybox-img",src:v.src,alt:e.title}).appendTo(m);Q()};v.src=c;break;case "swf":e.scrolling="no";C='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="'+e.width+'" height="'+e.height+'"><param name="movie" value="'+c+
23
+ '"></param>';P="";b.each(e.swf,function(x,H){C+='<param name="'+x+'" value="'+H+'"></param>';P+=" "+x+'="'+H+'"'});C+='<embed src="'+c+'" type="application/x-shockwave-flash" width="'+e.width+'" height="'+e.height+'"'+P+"></embed></object>";m.html(C);F();break;case "ajax":h=false;b.fancybox.showActivity();e.ajax.win=e.ajax.success;G=b.ajax(b.extend({},e.ajax,{url:c,data:e.ajax.data||{},error:function(x){x.status>0&&O()},success:function(x,H,R){if((typeof R=="object"?R:G).status==200){if(typeof e.ajax.win==
24
+ "function"){w=e.ajax.win(c,x,H,R);if(w===false){t.hide();return}else if(typeof w=="string"||typeof w=="object")x=w}m.html(x);F()}}}));break;case "iframe":Q()}}else O()}},F=function(){var a=e.width,c=e.height;a=a.toString().indexOf("%")>-1?parseInt((b(window).width()-e.margin*2)*parseFloat(a)/100,10)+"px":a=="auto"?"auto":a+"px";c=c.toString().indexOf("%")>-1?parseInt((b(window).height()-e.margin*2)*parseFloat(c)/100,10)+"px":c=="auto"?"auto":c+"px";m.wrapInner('<div style="width:'+a+";height:"+c+
25
+ ";overflow: "+(e.scrolling=="auto"?"auto":e.scrolling=="yes"?"scroll":"hidden")+';position:relative;"></div>');e.width=m.width();e.height=m.height();Q()},Q=function(){var a,c;t.hide();if(f.is(":visible")&&false===d.onCleanup(l,p,d)){b.event.trigger("fancybox-cancel");h=false}else{h=true;b(j.add(u)).unbind();b(window).unbind("resize.fb scroll.fb");b(document).unbind("keydown.fb");f.is(":visible")&&d.titlePosition!=="outside"&&f.css("height",f.height());l=o;p=q;d=e;if(d.overlayShow){u.css({"background-color":d.overlayColor,
26
+ opacity:d.overlayOpacity,cursor:d.hideOnOverlayClick?"pointer":"auto",height:b(document).height()});if(!u.is(":visible")){M&&b("select:not(#fancybox-tmp select)").filter(function(){return this.style.visibility!=="hidden"}).css({visibility:"hidden"}).one("fancybox-cleanup",function(){this.style.visibility="inherit"});u.show()}}else u.hide();i=X();s=d.title||"";y=0;n.empty().removeAttr("style").removeClass();if(d.titleShow!==false){if(b.isFunction(d.titleFormat))a=d.titleFormat(s,l,p,d);else a=s&&s.length?
27
+ d.titlePosition=="float"?'<table id="fancybox-title-float-wrap" cellpadding="0" cellspacing="0"><tr><td id="fancybox-title-float-left"></td><td id="fancybox-title-float-main">'+s+'</td><td id="fancybox-title-float-right"></td></tr></table>':'<div id="fancybox-title-'+d.titlePosition+'">'+s+"</div>":false;s=a;if(!(!s||s==="")){n.addClass("fancybox-title-"+d.titlePosition).html(s).appendTo("body").show();switch(d.titlePosition){case "inside":n.css({width:i.width-d.padding*2,marginLeft:d.padding,marginRight:d.padding});
28
+ y=n.outerHeight(true);n.appendTo(D);i.height+=y;break;case "over":n.css({marginLeft:d.padding,width:i.width-d.padding*2,bottom:d.padding}).appendTo(D);break;case "float":n.css("left",parseInt((n.width()-i.width-40)/2,10)*-1).appendTo(f);break;default:n.css({width:i.width-d.padding*2,paddingLeft:d.padding,paddingRight:d.padding}).appendTo(f)}}}n.hide();if(f.is(":visible")){b(E.add(z).add(A)).hide();a=f.position();r={top:a.top,left:a.left,width:f.width(),height:f.height()};c=r.width==i.width&&r.height==
29
+ i.height;j.fadeTo(d.changeFade,0.3,function(){var g=function(){j.html(m.contents()).fadeTo(d.changeFade,1,S)};b.event.trigger("fancybox-change");j.empty().removeAttr("filter").css({"border-width":d.padding,width:i.width-d.padding*2,height:e.autoDimensions?"auto":i.height-y-d.padding*2});if(c)g();else{B.prop=0;b(B).animate({prop:1},{duration:d.changeSpeed,easing:d.easingChange,step:T,complete:g})}})}else{f.removeAttr("style");j.css("border-width",d.padding);if(d.transitionIn=="elastic"){r=V();j.html(m.contents());
30
+ f.show();if(d.opacity)i.opacity=0;B.prop=0;b(B).animate({prop:1},{duration:d.speedIn,easing:d.easingIn,step:T,complete:S})}else{d.titlePosition=="inside"&&y>0&&n.show();j.css({width:i.width-d.padding*2,height:e.autoDimensions?"auto":i.height-y-d.padding*2}).html(m.contents());f.css(i).fadeIn(d.transitionIn=="none"?0:d.speedIn,S)}}}},Y=function(){if(d.enableEscapeButton||d.enableKeyboardNav)b(document).bind("keydown.fb",function(a){if(a.keyCode==27&&d.enableEscapeButton){a.preventDefault();b.fancybox.close()}else if((a.keyCode==
31
+ 37||a.keyCode==39)&&d.enableKeyboardNav&&a.target.tagName!=="INPUT"&&a.target.tagName!=="TEXTAREA"&&a.target.tagName!=="SELECT"){a.preventDefault();b.fancybox[a.keyCode==37?"prev":"next"]()}});if(d.showNavArrows){if(d.cyclic&&l.length>1||p!==0)z.show();if(d.cyclic&&l.length>1||p!=l.length-1)A.show()}else{z.hide();A.hide()}},S=function(){if(!b.support.opacity){j.get(0).style.removeAttribute("filter");f.get(0).style.removeAttribute("filter")}e.autoDimensions&&j.css("height","auto");f.css("height","auto");
32
+ s&&s.length&&n.show();d.showCloseButton&&E.show();Y();d.hideOnContentClick&&j.bind("click",b.fancybox.close);d.hideOnOverlayClick&&u.bind("click",b.fancybox.close);b(window).bind("resize.fb",b.fancybox.resize);d.centerOnScroll&&b(window).bind("scroll.fb",b.fancybox.center);if(d.type=="iframe")b('<iframe id="fancybox-frame" name="fancybox-frame'+(new Date).getTime()+'" frameborder="0" hspace="0" '+(b.browser.msie?'allowtransparency="true""':"")+' scrolling="'+e.scrolling+'" src="'+d.href+'"></iframe>').appendTo(j);
33
+ f.show();h=false;b.fancybox.center();d.onComplete(l,p,d);var a,c;if(l.length-1>p){a=l[p+1].href;if(typeof a!=="undefined"&&a.match(J)){c=new Image;c.src=a}}if(p>0){a=l[p-1].href;if(typeof a!=="undefined"&&a.match(J)){c=new Image;c.src=a}}},T=function(a){var c={width:parseInt(r.width+(i.width-r.width)*a,10),height:parseInt(r.height+(i.height-r.height)*a,10),top:parseInt(r.top+(i.top-r.top)*a,10),left:parseInt(r.left+(i.left-r.left)*a,10)};if(typeof i.opacity!=="undefined")c.opacity=a<0.5?0.5:a;f.css(c);
34
+ j.css({width:c.width-d.padding*2,height:c.height-y*a-d.padding*2})},U=function(){return[b(window).width()-d.margin*2,b(window).height()-d.margin*2,b(document).scrollLeft()+d.margin,b(document).scrollTop()+d.margin]},X=function(){var a=U(),c={},g=d.autoScale,k=d.padding*2;c.width=d.width.toString().indexOf("%")>-1?parseInt(a[0]*parseFloat(d.width)/100,10):d.width+k;c.height=d.height.toString().indexOf("%")>-1?parseInt(a[1]*parseFloat(d.height)/100,10):d.height+k;if(g&&(c.width>a[0]||c.height>a[1]))if(e.type==
35
+ "image"||e.type=="swf"){g=d.width/d.height;if(c.width>a[0]){c.width=a[0];c.height=parseInt((c.width-k)/g+k,10)}if(c.height>a[1]){c.height=a[1];c.width=parseInt((c.height-k)*g+k,10)}}else{c.width=Math.min(c.width,a[0]);c.height=Math.min(c.height,a[1])}c.top=parseInt(Math.max(a[3]-20,a[3]+(a[1]-c.height-40)*0.5),10);c.left=parseInt(Math.max(a[2]-20,a[2]+(a[0]-c.width-40)*0.5),10);return c},V=function(){var a=e.orig?b(e.orig):false,c={};if(a&&a.length){c=a.offset();c.top+=parseInt(a.css("paddingTop"),
36
+ 10)||0;c.left+=parseInt(a.css("paddingLeft"),10)||0;c.top+=parseInt(a.css("border-top-width"),10)||0;c.left+=parseInt(a.css("border-left-width"),10)||0;c.width=a.width();c.height=a.height();c={width:c.width+d.padding*2,height:c.height+d.padding*2,top:c.top-d.padding-20,left:c.left-d.padding-20}}else{a=U();c={width:d.padding*2,height:d.padding*2,top:parseInt(a[3]+a[1]*0.5,10),left:parseInt(a[2]+a[0]*0.5,10)}}return c},Z=function(){if(t.is(":visible")){b("div",t).css("top",L*-40+"px");L=(L+1)%12}else clearInterval(K)};
37
+ b.fn.fancybox=function(a){if(!b(this).length)return this;b(this).data("fancybox",b.extend({},a,b.metadata?b(this).metadata():{})).unbind("click.fb").bind("click.fb",function(c){c.preventDefault();if(!h){h=true;b(this).blur();o=[];q=0;c=b(this).attr("rel")||"";if(!c||c==""||c==="nofollow")o.push(this);else{o=b("a[rel="+c+"], area[rel="+c+"]");q=o.index(this)}I()}});return this};b.fancybox=function(a,c){var g;if(!h){h=true;g=typeof c!=="undefined"?c:{};o=[];q=parseInt(g.index,10)||0;if(b.isArray(a)){for(var k=
38
+ 0,C=a.length;k<C;k++)if(typeof a[k]=="object")b(a[k]).data("fancybox",b.extend({},g,a[k]));else a[k]=b({}).data("fancybox",b.extend({content:a[k]},g));o=jQuery.merge(o,a)}else{if(typeof a=="object")b(a).data("fancybox",b.extend({},g,a));else a=b({}).data("fancybox",b.extend({content:a},g));o.push(a)}if(q>o.length||q<0)q=0;I()}};b.fancybox.showActivity=function(){clearInterval(K);t.show();K=setInterval(Z,66)};b.fancybox.hideActivity=function(){t.hide()};b.fancybox.next=function(){return b.fancybox.pos(p+
39
+ 1)};b.fancybox.prev=function(){return b.fancybox.pos(p-1)};b.fancybox.pos=function(a){if(!h){a=parseInt(a);o=l;if(a>-1&&a<l.length){q=a;I()}else if(d.cyclic&&l.length>1){q=a>=l.length?0:l.length-1;I()}}};b.fancybox.cancel=function(){if(!h){h=true;b.event.trigger("fancybox-cancel");N();e.onCancel(o,q,e);h=false}};b.fancybox.close=function(){function a(){u.fadeOut("fast");n.empty().hide();f.hide();b.event.trigger("fancybox-cleanup");j.empty();d.onClosed(l,p,d);l=e=[];p=q=0;d=e={};h=false}if(!(h||f.is(":hidden"))){h=
40
+ true;if(d&&false===d.onCleanup(l,p,d))h=false;else{N();b(E.add(z).add(A)).hide();b(j.add(u)).unbind();b(window).unbind("resize.fb scroll.fb");b(document).unbind("keydown.fb");j.find("iframe").attr("src",M&&/^https/i.test(window.location.href||"")?"javascript:void(false)":"about:blank");d.titlePosition!=="inside"&&n.empty();f.stop();if(d.transitionOut=="elastic"){r=V();var c=f.position();i={top:c.top,left:c.left,width:f.width(),height:f.height()};if(d.opacity)i.opacity=1;n.empty().hide();B.prop=1;
41
+ b(B).animate({prop:0},{duration:d.speedOut,easing:d.easingOut,step:T,complete:a})}else f.fadeOut(d.transitionOut=="none"?0:d.speedOut,a)}}};b.fancybox.resize=function(){u.is(":visible")&&u.css("height",b(document).height());b.fancybox.center(true)};b.fancybox.center=function(a){var c,g;if(!h){g=a===true?1:0;c=U();!g&&(f.width()>c[0]||f.height()>c[1])||f.stop().animate({top:parseInt(Math.max(c[3]-20,c[3]+(c[1]-j.height()-40)*0.5-d.padding)),left:parseInt(Math.max(c[2]-20,c[2]+(c[0]-j.width()-40)*0.5-
42
+ d.padding))},typeof a=="number"?a:200)}};b.fancybox.init=function(){if(!b("#fancybox-wrap").length){b("body").append(m=b('<div id="fancybox-tmp"></div>'),t=b('<div id="fancybox-loading"><div></div></div>'),u=b('<div id="fancybox-overlay"></div>'),f=b('<div id="fancybox-wrap"></div>'));D=b('<div id="fancybox-outer"></div>').append('<div class="fancybox-bg" id="fancybox-bg-n"></div><div class="fancybox-bg" id="fancybox-bg-ne"></div><div class="fancybox-bg" id="fancybox-bg-e"></div><div class="fancybox-bg" id="fancybox-bg-se"></div><div class="fancybox-bg" id="fancybox-bg-s"></div><div class="fancybox-bg" id="fancybox-bg-sw"></div><div class="fancybox-bg" id="fancybox-bg-w"></div><div class="fancybox-bg" id="fancybox-bg-nw"></div>').appendTo(f);
43
+ D.append(j=b('<div id="fancybox-content"></div>'),E=b('<a id="fancybox-close"></a>'),n=b('<div id="fancybox-title"></div>'),z=b('<a href="javascript:;" id="fancybox-left"><span class="fancy-ico" id="fancybox-left-ico"></span></a>'),A=b('<a href="javascript:;" id="fancybox-right"><span class="fancy-ico" id="fancybox-right-ico"></span></a>'));E.click(b.fancybox.close);t.click(b.fancybox.cancel);z.click(function(a){a.preventDefault();b.fancybox.prev()});A.click(function(a){a.preventDefault();b.fancybox.next()});
44
+ b.fn.mousewheel&&f.bind("mousewheel.fb",function(a,c){if(h)a.preventDefault();else if(b(a.target).get(0).clientHeight==0||b(a.target).get(0).scrollHeight===b(a.target).get(0).clientHeight){a.preventDefault();b.fancybox[c>0?"prev":"next"]()}});b.support.opacity||f.addClass("fancybox-ie");if(M){t.addClass("fancybox-ie6");f.addClass("fancybox-ie6");b('<iframe id="fancybox-hide-sel-frame" src="'+(/^https/i.test(window.location.href||"")?"javascript:void(false)":"about:blank")+'" scrolling="no" border="0" frameborder="0" tabindex="-1"></iframe>').prependTo(D)}}};
45
+ b.fn.fancybox.defaults={padding:10,margin:40,opacity:false,modal:false,cyclic:false,scrolling:"auto",width:560,height:340,autoScale:true,autoDimensions:true,centerOnScroll:false,ajax:{},swf:{wmode:"transparent"},hideOnOverlayClick:true,hideOnContentClick:false,overlayShow:true,overlayOpacity:0.7,overlayColor:"#777",titleShow:true,titlePosition:"float",titleFormat:null,titleFromAlt:false,transitionIn:"fade",transitionOut:"fade",speedIn:300,speedOut:300,changeSpeed:300,changeFade:"fast",easingIn:"swing",
46
+ easingOut:"swing",showCloseButton:true,showNavArrows:true,enableEscapeButton:true,enableKeyboardNav:true,onStart:function(){},onCancel:function(){},onComplete:function(){},onCleanup:function(){},onClosed:function(){},onError:function(){}};b(document).ready(function(){b.fancybox.init()})})(jQuery);
@@ -0,0 +1,14 @@
1
+ /*! Copyright (c) 2010 Brandon Aaron (http://brandonaaron.net)
2
+ * Licensed under the MIT License (LICENSE.txt).
3
+ *
4
+ * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
5
+ * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
6
+ * Thanks to: Seamus Leahy for adding deltaX and deltaY
7
+ *
8
+ * Version: 3.0.4
9
+ *
10
+ * Requires: 1.2.2+
11
+ */
12
+
13
+ (function(d){function g(a){var b=a||window.event,i=[].slice.call(arguments,1),c=0,h=0,e=0;a=d.event.fix(b);a.type="mousewheel";if(a.wheelDelta)c=a.wheelDelta/120;if(a.detail)c=-a.detail/3;e=c;if(b.axis!==undefined&&b.axis===b.HORIZONTAL_AXIS){e=0;h=-1*c}if(b.wheelDeltaY!==undefined)e=b.wheelDeltaY/120;if(b.wheelDeltaX!==undefined)h=-1*b.wheelDeltaX/120;i.unshift(a,c,h,e);return d.event.handle.apply(this,i)}var f=["DOMMouseScroll","mousewheel"];d.event.special.mousewheel={setup:function(){if(this.addEventListener)for(var a=
14
+ f.length;a;)this.addEventListener(f[--a],g,false);else this.onmousewheel=g},teardown:function(){if(this.removeEventListener)for(var a=f.length;a;)this.removeEventListener(f[--a],g,false);else this.onmousewheel=null}};d.fn.extend({mousewheel:function(a){return a?this.bind("mousewheel",a):this.trigger("mousewheel")},unmousewheel:function(a){return this.unbind("mousewheel",a)}})})(jQuery);
@@ -0,0 +1,1527 @@
1
+ /**
2
+ * http://github.com/Valums-File-Uploader/file-uploader
3
+ *
4
+ * Multiple file upload component with progress-bar, drag-and-drop.
5
+ *
6
+ * Have ideas for improving this JS for the general community?
7
+ * Submit your changes at: https://github.com/Valums-File-Uploader/file-uploader
8
+ *
9
+ * VERSION 2.0 beta
10
+ * Original version 1.0 © 2010 Andrew Valums ( andrew(at)valums.com )
11
+ *
12
+ * Licensed under MIT license, GNU GPL 2 or later, GNU LGPL 2 or later, see license.txt.
13
+ */
14
+
15
+ //
16
+ // Helper functions
17
+ //
18
+
19
+ var qq = qq || {};
20
+
21
+ /**
22
+ * Adds all missing properties from second obj to first obj
23
+ */
24
+ qq.extend = function(first, second){
25
+ for (var prop in second){
26
+ first[prop] = second[prop];
27
+ }
28
+ };
29
+
30
+ /**
31
+ * Searches for a given element in the array, returns -1 if it is not present.
32
+ * @param {Number} [from] The index at which to begin the search
33
+ */
34
+ qq.indexOf = function(arr, elt, from){
35
+ if (arr.indexOf) return arr.indexOf(elt, from);
36
+
37
+ from = from || 0;
38
+ var len = arr.length;
39
+
40
+ if (from < 0) from += len;
41
+
42
+ for (; from < len; from++){
43
+ if (from in arr && arr[from] === elt){
44
+ return from;
45
+ }
46
+ }
47
+ return -1;
48
+ };
49
+
50
+ qq.getUniqueId = (function(){
51
+ var id = 0;
52
+ return function(){ return id++; };
53
+ })();
54
+
55
+ //
56
+ // Browsers and platforms detection
57
+
58
+ qq.ie = function(){ return navigator.userAgent.indexOf('MSIE') != -1; }
59
+ qq.safari = function(){ return navigator.vendor != undefined && navigator.vendor.indexOf("Apple") != -1; }
60
+ qq.chrome = function(){ return navigator.vendor != undefined && navigator.vendor.indexOf('Google') != -1; }
61
+ qq.firefox = function(){ return (navigator.userAgent.indexOf('Mozilla') != -1 && navigator.vendor != undefined && navigator.vendor == ''); }
62
+ qq.windows = function(){ return navigator.platform == "Win32"; }
63
+
64
+ //
65
+ // Events
66
+
67
+ /** Returns the function which detaches attached event */
68
+ qq.attach = function(element, type, fn){
69
+ if (element.addEventListener){
70
+ element.addEventListener(type, fn, false);
71
+ } else if (element.attachEvent){
72
+ element.attachEvent('on' + type, fn);
73
+ }
74
+ return function() {
75
+ qq.detach(element, type, fn)
76
+ }
77
+ };
78
+ qq.detach = function(element, type, fn){
79
+ if (element.removeEventListener){
80
+ element.removeEventListener(type, fn, false);
81
+ } else if (element.attachEvent){
82
+ element.detachEvent('on' + type, fn);
83
+ }
84
+ };
85
+
86
+ qq.preventDefault = function(e){
87
+ if (e.preventDefault){
88
+ e.preventDefault();
89
+ } else{
90
+ e.returnValue = false;
91
+ }
92
+ };
93
+
94
+ //
95
+ // Node manipulations
96
+
97
+ /**
98
+ * Insert node a before node b.
99
+ */
100
+ qq.insertBefore = function(a, b){
101
+ b.parentNode.insertBefore(a, b);
102
+ };
103
+ qq.remove = function(element){
104
+ element.parentNode.removeChild(element);
105
+ };
106
+
107
+ qq.contains = function(parent, descendant){
108
+ // compareposition returns false in this case
109
+ if (parent == descendant) return true;
110
+
111
+ if (parent.contains){
112
+ return parent.contains(descendant);
113
+ } else {
114
+ return !!(descendant.compareDocumentPosition(parent) & 8);
115
+ }
116
+ };
117
+
118
+ /**
119
+ * Creates and returns element from html string
120
+ * Uses innerHTML to create an element
121
+ */
122
+ qq.toElement = (function(){
123
+ var div = document.createElement('div');
124
+ return function(html){
125
+ div.innerHTML = html;
126
+ var element = div.firstChild;
127
+ div.removeChild(element);
128
+ return element;
129
+ };
130
+ })();
131
+
132
+ //
133
+ // Node properties and attributes
134
+
135
+ /**
136
+ * Sets styles for an element.
137
+ * Fixes opacity in IE6-8.
138
+ */
139
+ qq.css = function(element, styles){
140
+ if (styles.opacity != null){
141
+ if (typeof element.style.opacity != 'string' && typeof(element.filters) != 'undefined'){
142
+ styles.filter = 'alpha(opacity=' + Math.round(100 * styles.opacity) + ')';
143
+ }
144
+ }
145
+ qq.extend(element.style, styles);
146
+ };
147
+ qq.hasClass = function(element, name){
148
+ var re = new RegExp('(^| )' + name + '( |$)');
149
+ return re.test(element.className);
150
+ };
151
+ qq.addClass = function(element, name){
152
+ if (!qq.hasClass(element, name)){
153
+ element.className += ' ' + name;
154
+ }
155
+ };
156
+ qq.removeClass = function(element, name){
157
+ var re = new RegExp('(^| )' + name + '( |$)');
158
+ element.className = element.className.replace(re, ' ').replace(/^\s+|\s+$/g, "");
159
+ };
160
+ qq.setText = function(element, text){
161
+ element.innerText = text;
162
+ element.textContent = text;
163
+ };
164
+
165
+ //
166
+ // Selecting elements
167
+
168
+ qq.children = function(element){
169
+ var children = [],
170
+ child = element.firstChild;
171
+
172
+ while (child){
173
+ if (child.nodeType == 1){
174
+ children.push(child);
175
+ }
176
+ child = child.nextSibling;
177
+ }
178
+
179
+ return children;
180
+ };
181
+
182
+ qq.getByClass = function(element, className){
183
+ if (element.querySelectorAll){
184
+ return element.querySelectorAll('.' + className);
185
+ }
186
+
187
+ var result = [];
188
+ var candidates = element.getElementsByTagName("*");
189
+ var len = candidates.length;
190
+
191
+ for (var i = 0; i < len; i++){
192
+ if (qq.hasClass(candidates[i], className)){
193
+ result.push(candidates[i]);
194
+ }
195
+ }
196
+ return result;
197
+ };
198
+
199
+ /**
200
+ * obj2url() takes a json-object as argument and generates
201
+ * a querystring. pretty much like jQuery.param()
202
+ *
203
+ * how to use:
204
+ *
205
+ * `qq.obj2url({a:'b',c:'d'},'http://any.url/upload?otherParam=value');`
206
+ *
207
+ * will result in:
208
+ *
209
+ * `http://any.url/upload?otherParam=value&a=b&c=d`
210
+ *
211
+ * @param Object JSON-Object
212
+ * @param String current querystring-part
213
+ * @return String encoded querystring
214
+ */
215
+ qq.obj2url = function(obj, temp, prefixDone){
216
+ var uristrings = [],
217
+ prefix = '&',
218
+ add = function(nextObj, i){
219
+ var nextTemp = temp
220
+ ? (/\[\]$/.test(temp)) // prevent double-encoding
221
+ ? temp
222
+ : temp+'['+i+']'
223
+ : i;
224
+ if ((nextTemp != 'undefined') && (i != 'undefined')) {
225
+ uristrings.push(
226
+ (typeof nextObj === 'object')
227
+ ? qq.obj2url(nextObj, nextTemp, true)
228
+ : (Object.prototype.toString.call(nextObj) === '[object Function]')
229
+ ? encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj())
230
+ : encodeURIComponent(nextTemp) + '=' + encodeURIComponent(nextObj)
231
+ );
232
+ }
233
+ };
234
+
235
+ if (!prefixDone && temp) {
236
+ prefix = (/\?/.test(temp)) ? (/\?$/.test(temp)) ? '' : '&' : '?';
237
+ uristrings.push(temp);
238
+ uristrings.push(qq.obj2url(obj));
239
+ } else if ((Object.prototype.toString.call(obj) === '[object Array]') && (typeof obj != 'undefined') ) {
240
+ // we wont use a for-in-loop on an array (performance)
241
+ for (var i = 0, len = obj.length; i < len; ++i){
242
+ add(obj[i], i);
243
+ }
244
+ } else if ((typeof obj != 'undefined') && (obj !== null) && (typeof obj === "object")){
245
+ // for anything else but a scalar, we will use for-in-loop
246
+ for (var i in obj){
247
+ add(obj[i], i);
248
+ }
249
+ } else {
250
+ uristrings.push(encodeURIComponent(temp) + '=' + encodeURIComponent(obj));
251
+ }
252
+
253
+ return uristrings.join(prefix)
254
+ .replace(/^&/, '')
255
+ .replace(/%20/g, '+');
256
+ };
257
+
258
+ //
259
+ //
260
+ // Uploader Classes
261
+ //
262
+ //
263
+
264
+ var qq = qq || {};
265
+
266
+ /**
267
+ * Creates upload button, validates upload, but doesn't create file list or dd.
268
+ */
269
+ qq.FileUploaderBasic = function(o){
270
+ var that = this;
271
+ this._options = {
272
+ // set to true to see the server response
273
+ debug: false,
274
+ action: '/server/upload',
275
+ params: {},
276
+ customHeaders: {},
277
+ button: null,
278
+ multiple: true,
279
+ maxConnections: 3,
280
+ disableCancelForFormUploads: false,
281
+ autoUpload: true,
282
+ // validation
283
+ allowedExtensions: [],
284
+ acceptFiles: null, // comma separated string of mime-types for browser to display in browse dialog
285
+ sizeLimit: 0,
286
+ minSizeLimit: 0,
287
+ // events
288
+ // return false to cancel submit
289
+ onSubmit: function(id, fileName){},
290
+ onComplete: function(id, fileName, responseJSON){},
291
+ onCancel: function(id, fileName){},
292
+ onUpload: function(id, fileName, xhr){},
293
+ onProgress: function(id, fileName, loaded, total){},
294
+ onError: function(id, fileName, xhr) {},
295
+ // messages
296
+ messages: {
297
+ typeError: "{file} has an invalid extension. Only {extensions} {isAre} allowed.",
298
+ sizeError: "{file} is too large, maximum file size is {sizeLimit}.",
299
+ minSizeError: "{file} is too small, minimum file size is {minSizeLimit}.",
300
+ emptyError: "{file} is empty, please select files again without it.",
301
+ noFilesError: "No files to upload.",
302
+ onLeave: "The files are being uploaded, if you leave now the upload will be cancelled."
303
+ },
304
+ showMessage: function(message){
305
+ alert(message);
306
+ },
307
+ inputName: 'qqfile'
308
+ };
309
+ qq.extend(this._options, o);
310
+ qq.extend(this, qq.DisposeSupport);
311
+
312
+ // number of files being uploaded
313
+ this._filesInProgress = 0;
314
+
315
+ this._storedFileIds = [];
316
+
317
+ this._handler = this._createUploadHandler();
318
+
319
+ if (this._options.button){
320
+ this._button = this._createUploadButton(this._options.button);
321
+ }
322
+
323
+ this._preventLeaveInProgress();
324
+ };
325
+
326
+ qq.FileUploaderBasic.prototype = {
327
+ setParams: function(params){
328
+ this._options.params = params;
329
+ },
330
+ getInProgress: function(){
331
+ return this._filesInProgress;
332
+ },
333
+ uploadStoredFiles: function(){
334
+ for (var i = 0; i < this._storedFileIds.length; i++) {
335
+ this._filesInProgress++;
336
+ this._handler.upload(this._storedFileIds[i], this._options.params);
337
+ }
338
+ },
339
+ clearStoredFiles: function(){
340
+ this._storedFileIds = [];
341
+ },
342
+ _createUploadButton: function(element){
343
+ var self = this;
344
+
345
+ var button = new qq.UploadButton({
346
+ element: element,
347
+ multiple: this._options.multiple && qq.UploadHandlerXhr.isSupported(),
348
+ acceptFiles: this._options.acceptFiles,
349
+ onChange: function(input){
350
+ self._onInputChange(input);
351
+ }
352
+ });
353
+
354
+ this.addDisposer(function() { button.dispose(); });
355
+ return button;
356
+ },
357
+ _createUploadHandler: function(){
358
+ var self = this,
359
+ handlerClass;
360
+
361
+ if(qq.UploadHandlerXhr.isSupported()){
362
+ handlerClass = 'UploadHandlerXhr';
363
+ } else {
364
+ handlerClass = 'UploadHandlerForm';
365
+ }
366
+
367
+ var handler = new qq[handlerClass]({
368
+ debug: this._options.debug,
369
+ action: this._options.action,
370
+ encoding: this._options.encoding,
371
+ maxConnections: this._options.maxConnections,
372
+ customHeaders: this._options.customHeaders,
373
+ inputName: this._options.inputName,
374
+ demoMode: this._options.demoMode,
375
+ onProgress: function(id, fileName, loaded, total){
376
+ self._onProgress(id, fileName, loaded, total);
377
+ self._options.onProgress(id, fileName, loaded, total);
378
+ },
379
+ onComplete: function(id, fileName, result){
380
+ self._onComplete(id, fileName, result);
381
+ self._options.onComplete(id, fileName, result);
382
+ },
383
+ onCancel: function(id, fileName){
384
+ var indexToRemove = qq.indexOf(self._storedFileIds, id);
385
+ if (indexToRemove >= 0) {
386
+ self._storedFileIds.splice(indexToRemove, 1);
387
+ }
388
+
389
+ self._onCancel(id, fileName);
390
+ self._options.onCancel(id, fileName);
391
+ },
392
+ onError: self._options.onError,
393
+ onUpload: function(id, fileName, xhr){
394
+ self._onUpload(id, fileName, xhr);
395
+ self._options.onUpload(id, fileName, xhr);
396
+ }
397
+ });
398
+
399
+ return handler;
400
+ },
401
+ _preventLeaveInProgress: function(){
402
+ var self = this;
403
+
404
+ this._attach(window, 'beforeunload', function(e){
405
+ if (!self._filesInProgress){return;}
406
+
407
+ var e = e || window.event;
408
+ // for ie, ff
409
+ e.returnValue = self._options.messages.onLeave;
410
+ // for webkit
411
+ return self._options.messages.onLeave;
412
+ });
413
+ },
414
+ _onSubmit: function(id, fileName){
415
+ if (this._options.autoUpload) {
416
+ this._filesInProgress++;
417
+ }
418
+ },
419
+ _onProgress: function(id, fileName, loaded, total){
420
+ },
421
+ _onComplete: function(id, fileName, result){
422
+ var indexToRemove = qq.indexOf(this._storedFileIds, id);
423
+ if (indexToRemove >= 0) {
424
+ this._storedFileIds.splice(indexToRemove, 1);
425
+ }
426
+
427
+ this._filesInProgress--;
428
+ if (result.error){
429
+ this._options.showMessage(result.error);
430
+ }
431
+ },
432
+ _onCancel: function(id, fileName){
433
+ if (this._options.autoUpload) {
434
+ this._filesInProgress--;
435
+ }
436
+ },
437
+ _onUpload: function(id, fileName, xhr){
438
+ },
439
+ _onInputChange: function(input){
440
+ if (this._handler instanceof qq.UploadHandlerXhr){
441
+ this._uploadFileList(input.files);
442
+ } else {
443
+ if (this._validateFile(input)){
444
+ this._uploadFile(input);
445
+ }
446
+ }
447
+ this._button.reset();
448
+ },
449
+ _uploadFileList: function(files){
450
+ if (files.length > 0) {
451
+ for (var i=0; i<files.length; i++){
452
+ if ( !this._validateFile(files[i])){
453
+ return;
454
+ }
455
+ }
456
+
457
+ for (var i=0; i<files.length; i++){
458
+ this._uploadFile(files[i]);
459
+ }
460
+ }
461
+ else {
462
+ this._error('noFilesError', "");
463
+ }
464
+ },
465
+ _uploadFile: function(fileContainer){
466
+ var id = this._handler.add(fileContainer);
467
+ var fileName = this._handler.getName(id);
468
+
469
+ if (this._options.onSubmit(id, fileName) !== false){
470
+ this._onSubmit(id, fileName);
471
+ if (this._options.autoUpload) {
472
+ this._handler.upload(id, this._options.params);
473
+ }
474
+ else {
475
+ this._storeFileForLater(id);
476
+ }
477
+ }
478
+ },
479
+ _storeFileForLater: function(id) {
480
+ this._storedFileIds.push(id);
481
+ },
482
+ _validateFile: function(file){
483
+ var name, size;
484
+
485
+ if (file.value){
486
+ // it is a file input
487
+ // get input value and remove path to normalize
488
+ name = file.value.replace(/.*(\/|\\)/, "");
489
+ } else {
490
+ // fix missing properties in Safari 4 and firefox 11.0a2
491
+ name = (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
492
+ size = (file.fileSize !== null && file.fileSize !== undefined) ? file.fileSize : file.size;
493
+ }
494
+
495
+ if (! this._isAllowedExtension(name)){
496
+ this._error('typeError', name);
497
+ return false;
498
+
499
+ } else if (size === 0){
500
+ this._error('emptyError', name);
501
+ return false;
502
+
503
+ } else if (size && this._options.sizeLimit && size > this._options.sizeLimit){
504
+ this._error('sizeError', name);
505
+ return false;
506
+
507
+ } else if (size && size < this._options.minSizeLimit){
508
+ this._error('minSizeError', name);
509
+ return false;
510
+ }
511
+
512
+ return true;
513
+ },
514
+ _error: function(code, fileName){
515
+ var message = this._options.messages[code];
516
+ function r(name, replacement){ message = message.replace(name, replacement); }
517
+
518
+ var extensions = this._options.allowedExtensions.join(', ');
519
+
520
+ r('{file}', this._formatFileName(fileName));
521
+ r('{extensions}', extensions);
522
+ r('{sizeLimit}', this._formatSize(this._options.sizeLimit));
523
+ r('{minSizeLimit}', this._formatSize(this._options.minSizeLimit));
524
+ r('{isAre}', extensions.indexOf(",") != -1 ? "are" : "is");
525
+
526
+ this._options.showMessage(message);
527
+ },
528
+ _formatFileName: function(name){
529
+ if (name.length > 33){
530
+ name = name.slice(0, 19) + '...' + name.slice(-13);
531
+ }
532
+ return name;
533
+ },
534
+ _isAllowedExtension: function(fileName){
535
+ var ext = (-1 !== fileName.indexOf('.'))
536
+ ? fileName.replace(/.*[.]/, '').toLowerCase()
537
+ : '';
538
+ var allowed = this._options.allowedExtensions;
539
+
540
+ if (!allowed.length){return true;}
541
+
542
+ for (var i=0; i<allowed.length; i++){
543
+ if (allowed[i].toLowerCase() == ext){ return true;}
544
+ }
545
+
546
+ return false;
547
+ },
548
+ _formatSize: function(bytes){
549
+ var i = -1;
550
+ do {
551
+ bytes = bytes / 1024;
552
+ i++;
553
+ } while (bytes > 99);
554
+
555
+ return Math.max(bytes, 0.1).toFixed(1) + ['kB', 'MB', 'GB', 'TB', 'PB', 'EB'][i];
556
+ }
557
+ };
558
+
559
+
560
+ /**
561
+ * Class that creates upload widget with drag-and-drop and file list
562
+ * @inherits qq.FileUploaderBasic
563
+ */
564
+ qq.FileUploader = function(o){
565
+ // call parent constructor
566
+ qq.FileUploaderBasic.apply(this, arguments);
567
+
568
+ // additional options
569
+ qq.extend(this._options, {
570
+ element: null,
571
+ // if set, will be used instead of qq-upload-list in template
572
+ listElement: null,
573
+ dragText: 'Drop files here to upload',
574
+ extraDropzones : [],
575
+ uploadButtonText: 'Upload a file',
576
+ cancelButtonText: 'Cancel',
577
+ failUploadText: 'Upload failed',
578
+
579
+ multipleFileDropNotAllowedMessage: "You may only drop one file",
580
+
581
+ template: '<div class="qq-uploader">' +
582
+ '<div class="qq-upload-drop-area"><span>{dragText}</span></div>' +
583
+ '<div class="qq-upload-button">{uploadButtonText}</div>' +
584
+ '<ul class="qq-upload-list"></ul>' +
585
+ '</div>',
586
+
587
+ // template for one item in file list
588
+ fileTemplate: '<li>' +
589
+ '<span class="qq-progress-bar"></span>' +
590
+ '<span class="qq-upload-spinner"></span>' +
591
+ '<span class="qq-upload-finished"></span>' +
592
+ '<span class="qq-upload-file"></span>' +
593
+ '<span class="qq-upload-size"></span>' +
594
+ '<a class="qq-upload-cancel" href="#">{cancelButtonText}</a>' +
595
+ '<span class="qq-upload-failed-text">{failUploadtext}</span>' +
596
+ '</li>',
597
+
598
+ classes: {
599
+ // used to get elements from templates
600
+ button: 'qq-upload-button',
601
+ drop: 'qq-upload-drop-area',
602
+ dropActive: 'qq-upload-drop-area-active',
603
+ dropDisabled: 'qq-upload-drop-area-disabled',
604
+ list: 'qq-upload-list',
605
+ progressBar: 'qq-progress-bar',
606
+ file: 'qq-upload-file',
607
+ spinner: 'qq-upload-spinner',
608
+ finished: 'qq-upload-finished',
609
+ size: 'qq-upload-size',
610
+ cancel: 'qq-upload-cancel',
611
+
612
+ // added to list item <li> when upload completes
613
+ // used in css to hide progress spinner
614
+ success: 'qq-upload-success',
615
+ fail: 'qq-upload-fail',
616
+
617
+ successIcon: null,
618
+ failIcon: null
619
+ }
620
+ });
621
+ // overwrite options with user supplied
622
+ qq.extend(this._options, o);
623
+
624
+ this._options.messages.tooManyFilesError = this._options.multipleFileDropNotAllowedMessage;
625
+
626
+ // overwrite the upload button text if any
627
+ // same for the Cancel button and Fail message text
628
+ this._options.template = this._options.template.replace(/\{dragText\}/g, this._options.dragText);
629
+ this._options.template = this._options.template.replace(/\{uploadButtonText\}/g, this._options.uploadButtonText);
630
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{cancelButtonText\}/g, this._options.cancelButtonText);
631
+ this._options.fileTemplate = this._options.fileTemplate.replace(/\{failUploadtext\}/g, this._options.failUploadText);
632
+
633
+ this._element = this._options.element;
634
+ this._element.innerHTML = this._options.template;
635
+ this._listElement = this._options.listElement || this._find(this._element, 'list');
636
+
637
+ this._classes = this._options.classes;
638
+
639
+ this._button = this._createUploadButton(this._find(this._element, 'button'));
640
+
641
+ this._bindCancelEvent();
642
+ this._setupDragDrop();
643
+ };
644
+
645
+ // inherit from Basic Uploader
646
+ qq.extend(qq.FileUploader.prototype, qq.FileUploaderBasic.prototype);
647
+
648
+ qq.extend(qq.FileUploader.prototype, {
649
+ clearStoredFiles: function() {
650
+ qq.FileUploaderBasic.prototype.clearStoredFiles.apply(this, arguments);
651
+ this._listElement.innerHTML = "";
652
+ },
653
+ addExtraDropzone: function(element){
654
+ this._setupExtraDropzone(element);
655
+ },
656
+ removeExtraDropzone: function(element){
657
+ var dzs = this._options.extraDropzones;
658
+ for(var i in dzs) if (dzs[i] === element) return this._options.extraDropzones.splice(i,1);
659
+ },
660
+ _leaving_document_out: function(e){
661
+ return ((qq.chrome() || (qq.safari() && qq.windows())) && e.clientX == 0 && e.clientY == 0) // null coords for Chrome and Safari Windows
662
+ || (qq.firefox() && !e.relatedTarget); // null e.relatedTarget for Firefox
663
+ },
664
+ _storeFileForLater: function(id) {
665
+ qq.FileUploaderBasic.prototype._storeFileForLater.apply(this, arguments);
666
+ var item = this._getItemByFileId(id);
667
+ this._find(item, 'spinner').style.display = "none";
668
+ },
669
+ /**
670
+ * Gets one of the elements listed in this._options.classes
671
+ **/
672
+ _find: function(parent, type){
673
+ var element = qq.getByClass(parent, this._options.classes[type])[0];
674
+ if (!element){
675
+ throw new Error('element not found ' + type);
676
+ }
677
+
678
+ return element;
679
+ },
680
+ _setupExtraDropzone: function(element){
681
+ this._options.extraDropzones.push(element);
682
+ this._setupDropzone(element);
683
+ },
684
+ _setupDropzone: function(dropArea){
685
+ var self = this;
686
+
687
+ var dz = new qq.UploadDropZone({
688
+ element: dropArea,
689
+ onEnter: function(e){
690
+ qq.addClass(dropArea, self._classes.dropActive);
691
+ e.stopPropagation();
692
+ },
693
+ onLeave: function(e){
694
+ //e.stopPropagation();
695
+ },
696
+ onLeaveNotDescendants: function(e){
697
+ qq.removeClass(dropArea, self._classes.dropActive);
698
+ },
699
+ onDrop: function(e){
700
+ dropArea.style.display = 'none';
701
+ qq.removeClass(dropArea, self._classes.dropActive);
702
+ if (e.dataTransfer.files.length > 1 && !self._options.multiple) {
703
+ self._error('tooManyFilesError', "");
704
+ }
705
+ else {
706
+ self._uploadFileList(e.dataTransfer.files);
707
+ }
708
+ }
709
+ });
710
+
711
+ this.addDisposer(function() { dz.dispose(); });
712
+
713
+ dropArea.style.display = 'none';
714
+ },
715
+ _setupDragDrop: function(){
716
+ var dropArea = this._find(this._element, 'drop');
717
+ var self = this;
718
+ this._options.extraDropzones.push(dropArea);
719
+
720
+ var dropzones = this._options.extraDropzones;
721
+ var i;
722
+ for (i=0; i < dropzones.length; i++){
723
+ this._setupDropzone(dropzones[i]);
724
+ }
725
+
726
+ // IE <= 9 does not support the File API used for drag+drop uploads
727
+ // Any volunteers to enable & test this for IE10?
728
+ if (!qq.ie()) {
729
+ this._attach(document, 'dragenter', function(e){
730
+ if (qq.hasClass(dropArea, self._classes.dropDisabled)) return;
731
+
732
+ dropArea.style.display = 'block';
733
+ for (i=0; i < dropzones.length; i++){ dropzones[i].style.display = 'block'; }
734
+
735
+ });
736
+ }
737
+ this._attach(document, 'dragleave', function(e){
738
+ var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
739
+ // only fire when leaving document out
740
+ if (qq.FileUploader.prototype._leaving_document_out(e)) {
741
+ for (i=0; i < dropzones.length; i++){ dropzones[i].style.display = 'none'; }
742
+ }
743
+ });
744
+ qq.attach(document, 'drop', function(e){
745
+ for (i=0; i < dropzones.length; i++){ dropzones[i].style.display = 'none'; }
746
+ e.preventDefault();
747
+ });
748
+ },
749
+ _onSubmit: function(id, fileName){
750
+ qq.FileUploaderBasic.prototype._onSubmit.apply(this, arguments);
751
+ this._addToList(id, fileName);
752
+ },
753
+ // Update the progress bar & percentage as the file is uploaded
754
+ _onProgress: function(id, fileName, loaded, total){
755
+ qq.FileUploaderBasic.prototype._onProgress.apply(this, arguments);
756
+
757
+ var item = this._getItemByFileId(id);
758
+
759
+ if (loaded === total) {
760
+ var cancelLink = this._find(item, 'cancel');
761
+ cancelLink.style.display = 'none';
762
+ }
763
+
764
+ var size = this._find(item, 'size');
765
+ size.style.display = 'inline';
766
+
767
+ var text;
768
+ var percent = Math.round(loaded / total * 100);
769
+
770
+ if (loaded != total) {
771
+ // If still uploading, display percentage
772
+ text = percent + '% from ' + this._formatSize(total);
773
+ } else {
774
+ // If complete, just display final size
775
+ text = this._formatSize(total);
776
+ }
777
+
778
+ // Update progress bar <span> tag
779
+ this._find(item, 'progressBar').style.width = percent + '%';
780
+
781
+ qq.setText(size, text);
782
+ },
783
+ _onComplete: function(id, fileName, result){
784
+
785
+ qq.FileUploaderBasic.prototype._onComplete.apply(this, arguments);
786
+
787
+ // mark completed
788
+ var item = this._getItemByFileId(id);
789
+ if (!this._options.disableCancelForFormUploads || qq.UploadHandlerXhr.isSupported()) {
790
+ qq.remove(this._find(item, 'cancel'));
791
+ }
792
+ qq.remove(this._find(item, 'spinner'));
793
+
794
+ if (result.success){
795
+ qq.addClass(item, this._classes.success);
796
+ if (this._classes.successIcon) {
797
+ this._find(item, 'finished').style.display = "inline-block";
798
+ qq.addClass(item, this._classes.successIcon)
799
+ }
800
+ } else {
801
+ var errorReason = result.reason ? result.reason : "Unknown error";
802
+ qq.addClass(item, this._classes.fail);
803
+ if (this._classes.failIcon) {
804
+ this._find(item, 'finished').style.display = "inline-block";
805
+ qq.addClass(item, this._classes.failIcon)
806
+ }
807
+ item.title = errorReason;
808
+ }
809
+ },
810
+ _onUpload: function(id, fileName, xhr){
811
+ qq.FileUploaderBasic.prototype._onUpload.apply(this, arguments);
812
+
813
+ var item = this._getItemByFileId(id);
814
+ var spinnerEl = this._find(item, 'spinner');
815
+ if (spinnerEl.style.display == "none") {
816
+ spinnerEl.style.display = "inline-block";
817
+ }
818
+ },
819
+ _addToList: function(id, fileName){
820
+ var item = qq.toElement(this._options.fileTemplate);
821
+ if (this._options.disableCancelForFormUploads && !qq.UploadHandlerXhr.isSupported()) {
822
+ var cancelLink = this._find(item, 'cancel');
823
+ qq.remove(cancelLink);
824
+ }
825
+ item.qqFileId = id;
826
+
827
+ var fileElement = this._find(item, 'file');
828
+ qq.setText(fileElement, this._formatFileName(fileName));
829
+ this._find(item, 'size').style.display = 'none';
830
+ if (!this._options.multiple) this._clearList();
831
+ this._listElement.appendChild(item);
832
+ },
833
+ _clearList: function(){
834
+ this._listElement.innerHTML = '';
835
+ this.clearStoredFiles();
836
+ },
837
+ _getItemByFileId: function(id){
838
+ var item = this._listElement.firstChild;
839
+
840
+ // there can't be txt nodes in dynamically created list
841
+ // and we can use nextSibling
842
+ while (item){
843
+ if (item.qqFileId == id) return item;
844
+ item = item.nextSibling;
845
+ }
846
+ },
847
+ /**
848
+ * delegate click event for cancel link
849
+ **/
850
+ _bindCancelEvent: function(){
851
+ var self = this,
852
+ list = this._listElement;
853
+
854
+ this._attach(list, 'click', function(e){
855
+ e = e || window.event;
856
+ var target = e.target || e.srcElement;
857
+
858
+ if (qq.hasClass(target, self._classes.cancel)){
859
+ qq.preventDefault(e);
860
+
861
+ var item = target.parentNode;
862
+ self._handler.cancel(item.qqFileId);
863
+ qq.remove(item);
864
+ }
865
+ });
866
+ }
867
+ });
868
+
869
+ qq.UploadDropZone = function(o){
870
+ this._options = {
871
+ element: null,
872
+ onEnter: function(e){},
873
+ onLeave: function(e){},
874
+ // is not fired when leaving element by hovering descendants
875
+ onLeaveNotDescendants: function(e){},
876
+ onDrop: function(e){}
877
+ };
878
+ qq.extend(this._options, o);
879
+ qq.extend(this, qq.DisposeSupport);
880
+
881
+ this._element = this._options.element;
882
+
883
+ this._disableDropOutside();
884
+ this._attachEvents();
885
+ };
886
+
887
+ qq.UploadDropZone.prototype = {
888
+ _dragover_should_be_canceled: function(){
889
+ return qq.safari() || (qq.firefox() && qq.windows());
890
+ },
891
+ _disableDropOutside: function(e){
892
+ // run only once for all instances
893
+ if (!qq.UploadDropZone.dropOutsideDisabled ){
894
+
895
+ // for these cases we need to catch onDrop to reset dropArea
896
+ if (this._dragover_should_be_canceled){
897
+ qq.attach(document, 'dragover', function(e){
898
+ e.preventDefault();
899
+ });
900
+ } else {
901
+ qq.attach(document, 'dragover', function(e){
902
+ if (e.dataTransfer){
903
+ e.dataTransfer.dropEffect = 'none';
904
+ e.preventDefault();
905
+ }
906
+ });
907
+ }
908
+
909
+ qq.UploadDropZone.dropOutsideDisabled = true;
910
+ }
911
+ },
912
+ _attachEvents: function(){
913
+ var self = this;
914
+
915
+ self._attach(self._element, 'dragover', function(e){
916
+ if (!self._isValidFileDrag(e)) return;
917
+
918
+ var effect = qq.ie() ? null : e.dataTransfer.effectAllowed;
919
+ if (effect == 'move' || effect == 'linkMove'){
920
+ e.dataTransfer.dropEffect = 'move'; // for FF (only move allowed)
921
+ } else {
922
+ e.dataTransfer.dropEffect = 'copy'; // for Chrome
923
+ }
924
+
925
+ e.stopPropagation();
926
+ e.preventDefault();
927
+ });
928
+
929
+ self._attach(self._element, 'dragenter', function(e){
930
+ if (!self._isValidFileDrag(e)) return;
931
+
932
+ self._options.onEnter(e);
933
+ });
934
+
935
+ self._attach(self._element, 'dragleave', function(e){
936
+ if (!self._isValidFileDrag(e)) return;
937
+
938
+ self._options.onLeave(e);
939
+
940
+ var relatedTarget = document.elementFromPoint(e.clientX, e.clientY);
941
+ // do not fire when moving a mouse over a descendant
942
+ if (qq.contains(this, relatedTarget)) return;
943
+
944
+ self._options.onLeaveNotDescendants(e);
945
+ });
946
+
947
+ self._attach(self._element, 'drop', function(e){
948
+ if (!self._isValidFileDrag(e)) return;
949
+
950
+ e.preventDefault();
951
+ self._options.onDrop(e);
952
+ });
953
+ },
954
+ _isValidFileDrag: function(e){
955
+ // e.dataTransfer currently causing IE errors
956
+ // IE9 does NOT support file API, so drag-and-drop is not possible
957
+ // IE10 should work, but currently has not been tested - any volunteers?
958
+ if (qq.ie()) return false;
959
+
960
+ var dt = e.dataTransfer,
961
+ // do not check dt.types.contains in webkit, because it crashes safari 4
962
+ isSafari = qq.safari();
963
+
964
+ // dt.effectAllowed is none in Safari 5
965
+ // dt.types.contains check is for firefox
966
+ return dt && dt.effectAllowed != 'none' &&
967
+ (dt.files || (!isSafari && dt.types.contains && dt.types.contains('Files')));
968
+
969
+ }
970
+ };
971
+
972
+ qq.UploadButton = function(o){
973
+ this._options = {
974
+ element: null,
975
+ // if set to true adds multiple attribute to file input
976
+ multiple: false,
977
+ acceptFiles: null,
978
+ // name attribute of file input
979
+ name: 'file',
980
+ onChange: function(input){},
981
+ hoverClass: 'qq-upload-button-hover',
982
+ focusClass: 'qq-upload-button-focus'
983
+ };
984
+
985
+ qq.extend(this._options, o);
986
+ qq.extend(this, qq.DisposeSupport);
987
+
988
+ this._element = this._options.element;
989
+
990
+ // make button suitable container for input
991
+ qq.css(this._element, {
992
+ position: 'relative',
993
+ overflow: 'hidden',
994
+ // Make sure browse button is in the right side
995
+ // in Internet Explorer
996
+ direction: 'ltr'
997
+ });
998
+
999
+ this._input = this._createInput();
1000
+ };
1001
+
1002
+ qq.UploadButton.prototype = {
1003
+ /* returns file input element */
1004
+ getInput: function(){
1005
+ return this._input;
1006
+ },
1007
+ /* cleans/recreates the file input */
1008
+ reset: function(){
1009
+ if (this._input.parentNode){
1010
+ qq.remove(this._input);
1011
+ }
1012
+
1013
+ qq.removeClass(this._element, this._options.focusClass);
1014
+ this._input = this._createInput();
1015
+ },
1016
+ _createInput: function(){
1017
+ var input = document.createElement("input");
1018
+
1019
+ if (this._options.multiple){
1020
+ input.setAttribute("multiple", "multiple");
1021
+ }
1022
+
1023
+ if (this._options.acceptFiles) input.setAttribute("accept", this._options.acceptFiles);
1024
+
1025
+ input.setAttribute("type", "file");
1026
+ input.setAttribute("name", this._options.name);
1027
+
1028
+ qq.css(input, {
1029
+ position: 'absolute',
1030
+ // in Opera only 'browse' button
1031
+ // is clickable and it is located at
1032
+ // the right side of the input
1033
+ right: 0,
1034
+ top: 0,
1035
+ fontFamily: 'Arial',
1036
+ // 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
1037
+ fontSize: '118px',
1038
+ margin: 0,
1039
+ padding: 0,
1040
+ cursor: 'pointer',
1041
+ opacity: 0
1042
+ });
1043
+
1044
+ this._element.appendChild(input);
1045
+
1046
+ var self = this;
1047
+ this._attach(input, 'change', function(){
1048
+ self._options.onChange(input);
1049
+ });
1050
+
1051
+ this._attach(input, 'mouseover', function(){
1052
+ qq.addClass(self._element, self._options.hoverClass);
1053
+ });
1054
+ this._attach(input, 'mouseout', function(){
1055
+ qq.removeClass(self._element, self._options.hoverClass);
1056
+ });
1057
+ this._attach(input, 'focus', function(){
1058
+ qq.addClass(self._element, self._options.focusClass);
1059
+ });
1060
+ this._attach(input, 'blur', function(){
1061
+ qq.removeClass(self._element, self._options.focusClass);
1062
+ });
1063
+
1064
+ // IE and Opera, unfortunately have 2 tab stops on file input
1065
+ // which is unacceptable in our case, disable keyboard access
1066
+ if (window.attachEvent){
1067
+ // it is IE or Opera
1068
+ input.setAttribute('tabIndex', "-1");
1069
+ }
1070
+
1071
+ return input;
1072
+ }
1073
+ };
1074
+
1075
+ /**
1076
+ * Class for uploading files, uploading itself is handled by child classes
1077
+ */
1078
+ qq.UploadHandlerAbstract = function(o){
1079
+ // Default options, can be overridden by the user
1080
+ this._options = {
1081
+ debug: false,
1082
+ action: '/upload.php',
1083
+ // maximum number of concurrent uploads
1084
+ maxConnections: 999,
1085
+ onProgress: function(id, fileName, loaded, total){},
1086
+ onComplete: function(id, fileName, response){},
1087
+ onCancel: function(id, fileName){},
1088
+ onUpload: function(id, fileName, xhr){}
1089
+ };
1090
+ qq.extend(this._options, o);
1091
+
1092
+ this._queue = [];
1093
+ // params for files in queue
1094
+ this._params = [];
1095
+ };
1096
+ qq.UploadHandlerAbstract.prototype = {
1097
+ log: function(str){
1098
+ if (this._options.debug && window.console) console.log('[uploader] ' + str);
1099
+ },
1100
+ /**
1101
+ * Adds file or file input to the queue
1102
+ * @returns id
1103
+ **/
1104
+ add: function(file){},
1105
+ /**
1106
+ * Sends the file identified by id and additional query params to the server
1107
+ */
1108
+ upload: function(id, params){
1109
+ var len = this._queue.push(id);
1110
+
1111
+ var copy = {};
1112
+ qq.extend(copy, params);
1113
+ this._params[id] = copy;
1114
+
1115
+ // if too many active uploads, wait...
1116
+ if (len <= this._options.maxConnections){
1117
+ this._upload(id, this._params[id]);
1118
+ }
1119
+ },
1120
+ /**
1121
+ * Cancels file upload by id
1122
+ */
1123
+ cancel: function(id){
1124
+ this._cancel(id);
1125
+ this._dequeue(id);
1126
+ },
1127
+ /**
1128
+ * Cancells all uploads
1129
+ */
1130
+ cancelAll: function(){
1131
+ for (var i=0; i<this._queue.length; i++){
1132
+ this._cancel(this._queue[i]);
1133
+ }
1134
+ this._queue = [];
1135
+ },
1136
+ /**
1137
+ * Returns name of the file identified by id
1138
+ */
1139
+ getName: function(id){},
1140
+ /**
1141
+ * Returns size of the file identified by id
1142
+ */
1143
+ getSize: function(id){},
1144
+ /**
1145
+ * Returns id of files being uploaded or
1146
+ * waiting for their turn
1147
+ */
1148
+ getQueue: function(){
1149
+ return this._queue;
1150
+ },
1151
+ /**
1152
+ * Actual upload method
1153
+ */
1154
+ _upload: function(id){},
1155
+ /**
1156
+ * Actual cancel method
1157
+ */
1158
+ _cancel: function(id){},
1159
+ /**
1160
+ * Removes element from queue, starts upload of next
1161
+ */
1162
+ _dequeue: function(id){
1163
+ var i = qq.indexOf(this._queue, id);
1164
+ this._queue.splice(i, 1);
1165
+
1166
+ var max = this._options.maxConnections;
1167
+
1168
+ if (this._queue.length >= max && i < max){
1169
+ var nextId = this._queue[max-1];
1170
+ this._upload(nextId, this._params[nextId]);
1171
+ }
1172
+ }
1173
+ };
1174
+
1175
+ /**
1176
+ * Class for uploading files using form and iframe
1177
+ * @inherits qq.UploadHandlerAbstract
1178
+ */
1179
+ qq.UploadHandlerForm = function(o){
1180
+ qq.UploadHandlerAbstract.apply(this, arguments);
1181
+
1182
+ this._inputs = {};
1183
+ this._detach_load_events = {};
1184
+ };
1185
+ // @inherits qq.UploadHandlerAbstract
1186
+ qq.extend(qq.UploadHandlerForm.prototype, qq.UploadHandlerAbstract.prototype);
1187
+
1188
+ qq.extend(qq.UploadHandlerForm.prototype, {
1189
+ add: function(fileInput){
1190
+ fileInput.setAttribute('name', this._options.inputName);
1191
+ var id = 'qq-upload-handler-iframe' + qq.getUniqueId();
1192
+
1193
+ this._inputs[id] = fileInput;
1194
+
1195
+ // remove file input from DOM
1196
+ if (fileInput.parentNode){
1197
+ qq.remove(fileInput);
1198
+ }
1199
+
1200
+ return id;
1201
+ },
1202
+ getName: function(id){
1203
+ // get input value and remove path to normalize
1204
+ return this._inputs[id].value.replace(/.*(\/|\\)/, "");
1205
+ },
1206
+ _cancel: function(id){
1207
+ this._options.onCancel(id, this.getName(id));
1208
+
1209
+ delete this._inputs[id];
1210
+ delete this._detach_load_events[id];
1211
+
1212
+ var iframe = document.getElementById(id);
1213
+ if (iframe){
1214
+ // to cancel request set src to something else
1215
+ // we use src="javascript:false;" because it doesn't
1216
+ // trigger ie6 prompt on https
1217
+ iframe.setAttribute('src', 'javascript:false;');
1218
+
1219
+ qq.remove(iframe);
1220
+ }
1221
+ },
1222
+ _upload: function(id, params){
1223
+ this._options.onUpload(id, this.getName(id), false);
1224
+ var input = this._inputs[id];
1225
+
1226
+ if (!input){
1227
+ throw new Error('file with passed id was not added, or already uploaded or cancelled');
1228
+ }
1229
+
1230
+ var fileName = this.getName(id);
1231
+
1232
+ var iframe = this._createIframe(id);
1233
+ var form = this._createForm(iframe, params);
1234
+ form.appendChild(input);
1235
+
1236
+ var self = this;
1237
+ this._attachLoadEvent(iframe, function(){
1238
+ self.log('iframe loaded');
1239
+
1240
+ var response = self._getIframeContentJSON(iframe);
1241
+
1242
+ self._options.onComplete(id, fileName, response);
1243
+ self._dequeue(id);
1244
+
1245
+ delete self._inputs[id];
1246
+ // timeout added to fix busy state in FF3.6
1247
+ setTimeout(function(){
1248
+ self._detach_load_events[id]();
1249
+ delete self._detach_load_events[id];
1250
+ qq.remove(iframe);
1251
+ }, 1);
1252
+ });
1253
+
1254
+ form.submit();
1255
+ qq.remove(form);
1256
+
1257
+ return id;
1258
+ },
1259
+ _attachLoadEvent: function(iframe, callback){
1260
+ this._detach_load_events[iframe.id] = qq.attach(iframe, 'load', function(){
1261
+ // when we remove iframe from dom
1262
+ // the request stops, but in IE load
1263
+ // event fires
1264
+ if (!iframe.parentNode){
1265
+ return;
1266
+ }
1267
+
1268
+ try {
1269
+ // fixing Opera 10.53
1270
+ if (iframe.contentDocument &&
1271
+ iframe.contentDocument.body &&
1272
+ iframe.contentDocument.body.innerHTML == "false"){
1273
+ // In Opera event is fired second time
1274
+ // when body.innerHTML changed from false
1275
+ // to server response approx. after 1 sec
1276
+ // when we upload file with iframe
1277
+ return;
1278
+ }
1279
+ }
1280
+ catch (error) {
1281
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
1282
+ }
1283
+
1284
+ callback();
1285
+ });
1286
+ },
1287
+ /**
1288
+ * Returns json object received by iframe from server.
1289
+ */
1290
+ _getIframeContentJSON: function(iframe){
1291
+ //IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
1292
+ try {
1293
+ // iframe.contentWindow.document - for IE<7
1294
+ var doc = iframe.contentDocument ? iframe.contentDocument: iframe.contentWindow.document,
1295
+ response;
1296
+
1297
+ var innerHTML = doc.body.innerHTML;
1298
+ this.log("converting iframe's innerHTML to JSON");
1299
+ this.log("innerHTML = " + innerHTML);
1300
+ //plain text response may be wrapped in <pre> tag
1301
+ if (innerHTML.slice(0, 5).toLowerCase() == '<pre>' && innerHTML.slice(-6).toLowerCase() == '</pre>') {
1302
+ innerHTML = doc.body.firstChild.firstChild.nodeValue;
1303
+ }
1304
+
1305
+ response = eval("(" + innerHTML + ")");
1306
+ } catch(err){
1307
+ response = {success: true};
1308
+ }
1309
+
1310
+ return response;
1311
+ },
1312
+ /**
1313
+ * Creates iframe with unique name
1314
+ */
1315
+ _createIframe: function(id){
1316
+ // We can't use following code as the name attribute
1317
+ // won't be properly registered in IE6, and new window
1318
+ // on form submit will open
1319
+ // var iframe = document.createElement('iframe');
1320
+ // iframe.setAttribute('name', id);
1321
+
1322
+ var iframe = qq.toElement('<iframe src="javascript:false;" name="' + id + '" />');
1323
+ // src="javascript:false;" removes ie6 prompt on https
1324
+
1325
+ iframe.setAttribute('id', id);
1326
+
1327
+ iframe.style.display = 'none';
1328
+ document.body.appendChild(iframe);
1329
+
1330
+ return iframe;
1331
+ },
1332
+ /**
1333
+ * Creates form, that will be submitted to iframe
1334
+ */
1335
+ _createForm: function(iframe, params){
1336
+ // We can't use the following code in IE6
1337
+ // var form = document.createElement('form');
1338
+ // form.setAttribute('method', 'POST');
1339
+ // form.setAttribute('enctype', 'multipart/form-data');
1340
+ // Because in this case file won't be attached to request
1341
+ var protocol = this._options.demoMode ? "GET" : "POST"
1342
+ var form = qq.toElement('<form method="' + protocol + '" enctype="multipart/form-data"></form>');
1343
+
1344
+ var queryString = qq.obj2url(params, this._options.action);
1345
+
1346
+ form.setAttribute('action', queryString);
1347
+ form.setAttribute('target', iframe.name);
1348
+ form.style.display = 'none';
1349
+ document.body.appendChild(form);
1350
+
1351
+ return form;
1352
+ }
1353
+ });
1354
+
1355
+ /**
1356
+ * Class for uploading files using xhr
1357
+ * @inherits qq.UploadHandlerAbstract
1358
+ */
1359
+ qq.UploadHandlerXhr = function(o){
1360
+ qq.UploadHandlerAbstract.apply(this, arguments);
1361
+
1362
+ this._files = [];
1363
+ this._xhrs = [];
1364
+
1365
+ // current loaded size in bytes for each file
1366
+ this._loaded = [];
1367
+ };
1368
+
1369
+ // static method
1370
+ qq.UploadHandlerXhr.isSupported = function(){
1371
+ var input = document.createElement('input');
1372
+ input.type = 'file';
1373
+
1374
+ return (
1375
+ 'multiple' in input &&
1376
+ typeof File != "undefined" &&
1377
+ typeof FormData != "undefined" &&
1378
+ typeof (new XMLHttpRequest()).upload != "undefined" );
1379
+ };
1380
+
1381
+ // @inherits qq.UploadHandlerAbstract
1382
+ qq.extend(qq.UploadHandlerXhr.prototype, qq.UploadHandlerAbstract.prototype)
1383
+
1384
+ qq.extend(qq.UploadHandlerXhr.prototype, {
1385
+ /**
1386
+ * Adds file to the queue
1387
+ * Returns id to use with upload, cancel
1388
+ **/
1389
+ add: function(file){
1390
+ if (!(file instanceof File)){
1391
+ throw new Error('Passed obj in not a File (in qq.UploadHandlerXhr)');
1392
+ }
1393
+
1394
+ return this._files.push(file) - 1;
1395
+ },
1396
+ getName: function(id){
1397
+ var file = this._files[id];
1398
+ // fix missing name in Safari 4
1399
+ //NOTE: fixed missing name firefox 11.0a2 file.fileName is actually undefined
1400
+ return (file.fileName !== null && file.fileName !== undefined) ? file.fileName : file.name;
1401
+ },
1402
+ getSize: function(id){
1403
+ var file = this._files[id];
1404
+ return file.fileSize != null ? file.fileSize : file.size;
1405
+ },
1406
+ /**
1407
+ * Returns uploaded bytes for file identified by id
1408
+ */
1409
+ getLoaded: function(id){
1410
+ return this._loaded[id] || 0;
1411
+ },
1412
+ /**
1413
+ * Sends the file identified by id and additional query params to the server
1414
+ * @param {Object} params name-value string pairs
1415
+ */
1416
+ _upload: function(id, params){
1417
+ this._options.onUpload(id, this.getName(id), true);
1418
+
1419
+ var file = this._files[id],
1420
+ name = this.getName(id),
1421
+ size = this.getSize(id);
1422
+
1423
+ this._loaded[id] = 0;
1424
+
1425
+ var xhr = this._xhrs[id] = new XMLHttpRequest();
1426
+ var self = this;
1427
+
1428
+ xhr.upload.onprogress = function(e){
1429
+ if (e.lengthComputable){
1430
+ self._loaded[id] = e.loaded;
1431
+ self._options.onProgress(id, name, e.loaded, e.total);
1432
+ }
1433
+ };
1434
+
1435
+ xhr.onreadystatechange = function(){
1436
+ if (xhr.readyState == 4){
1437
+ self._onComplete(id, xhr);
1438
+ }
1439
+ };
1440
+
1441
+ // build query string
1442
+ params = params || {};
1443
+ params[this._options.inputName] = name;
1444
+ var queryString = qq.obj2url(params, this._options.action);
1445
+
1446
+ var protocol = this._options.demoMode ? "GET" : "POST";
1447
+ xhr.open(protocol, queryString, true);
1448
+ xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
1449
+ xhr.setRequestHeader("X-File-Name", encodeURIComponent(name));
1450
+ if (this._options.encoding == 'multipart') {
1451
+ var formData = new FormData();
1452
+ formData.append(this._options.inputName, file);
1453
+ file = formData;
1454
+ } else {
1455
+ xhr.setRequestHeader("Content-Type", "application/octet-stream");
1456
+ //NOTE: return mime type in xhr works on chrome 16.0.9 firefox 11.0a2
1457
+ xhr.setRequestHeader("X-Mime-Type",file.type );
1458
+ }
1459
+ for (key in this._options.customHeaders){
1460
+ xhr.setRequestHeader(key, this._options.customHeaders[key]);
1461
+ };
1462
+ xhr.send(file);
1463
+ },
1464
+ _onComplete: function(id, xhr){
1465
+ // the request was aborted/cancelled
1466
+ if (!this._files[id]) return;
1467
+ var name = this.getName(id);
1468
+ var size = this.getSize(id);
1469
+
1470
+ this._options.onProgress(id, name, size, size);
1471
+ if (xhr.status == 200){
1472
+ this.log("xhr - server response received");
1473
+ this.log("responseText = " + xhr.responseText);
1474
+
1475
+ var response;
1476
+ try {
1477
+ response = eval("(" + xhr.responseText + ')');
1478
+ } catch(err){
1479
+ response = {};
1480
+ }
1481
+
1482
+ this._options.onComplete(id, name, response);
1483
+
1484
+ } else {
1485
+ this._options.onError(id, name, xhr);
1486
+ this._options.onComplete(id, name, {});
1487
+ }
1488
+
1489
+ this._xhrs[id] = null;
1490
+ this._dequeue(id);
1491
+ },
1492
+ _cancel: function(id){
1493
+ this._options.onCancel(id, this.getName(id));
1494
+
1495
+ this._files[id] = null;
1496
+
1497
+ if (this._xhrs[id]){
1498
+ this._xhrs[id].abort();
1499
+ this._xhrs[id] = null;
1500
+ }
1501
+ }
1502
+ });
1503
+
1504
+ /**
1505
+ * A generic module which supports object disposing in dispose() method.
1506
+ * */
1507
+ qq.DisposeSupport = {
1508
+ _disposers: [],
1509
+
1510
+ /** Run all registered disposers */
1511
+ dispose: function() {
1512
+ var disposer;
1513
+ while (disposer = this._disposers.shift()) {
1514
+ disposer();
1515
+ }
1516
+ },
1517
+
1518
+ /** Add disposer to the collection */
1519
+ addDisposer: function(disposeFunction) {
1520
+ this._disposers.push(disposeFunction);
1521
+ },
1522
+
1523
+ /** Attach event handler and register de-attacher as a disposer */
1524
+ _attach: function() {
1525
+ this.addDisposer(qq.attach.apply(this, arguments));
1526
+ }
1527
+ };