pakunok 0.0.1

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