coupler 0.0.1-java

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 (258) hide show
  1. data/.document +5 -0
  2. data/.gitmodules +3 -0
  3. data/.rvmrc +1 -0
  4. data/.vimrc +40 -0
  5. data/Gemfile +27 -0
  6. data/Gemfile.lock +71 -0
  7. data/LICENSE +20 -0
  8. data/NOTES +6 -0
  9. data/README.rdoc +18 -0
  10. data/Rakefile +42 -0
  11. data/TODO +11 -0
  12. data/VERSION +1 -0
  13. data/bin/coupler +7 -0
  14. data/db/.gitignore +6 -0
  15. data/db/migrate/001_initial_schema.rb +166 -0
  16. data/db/migrate/002_stub.rb +4 -0
  17. data/db/migrate/003_stub.rb +4 -0
  18. data/db/migrate/004_create_comparisons.rb +28 -0
  19. data/db/migrate/005_move_database_name.rb +19 -0
  20. data/db/migrate/006_upgrade_comparisons.rb +34 -0
  21. data/db/migrate/007_add_which_to_comparisons.rb +23 -0
  22. data/db/migrate/008_add_result_field_to_transformations.rb +33 -0
  23. data/db/migrate/009_add_generated_flag_to_fields.rb +13 -0
  24. data/db/migrate/010_create_imports.rb +24 -0
  25. data/db/migrate/011_add_primary_key_type.rb +13 -0
  26. data/db/migrate/012_add_transformed_with_to_resources.rb +13 -0
  27. data/db/migrate/013_add_run_count_to_scenarios.rb +13 -0
  28. data/db/migrate/014_add_last_accessed_at_to_some_tables.rb +13 -0
  29. data/db/migrate/015_add_run_number_to_results.rb +15 -0
  30. data/db/migrate/016_fix_scenario_run_count.rb +27 -0
  31. data/db/migrate/017_rename_comparison_columns.rb +14 -0
  32. data/db/migrate/018_fix_scenario_linkage_type.rb +8 -0
  33. data/db/migrate/019_add_columns_to_imports.rb +24 -0
  34. data/db/migrate/020_rename_import_columns.rb +12 -0
  35. data/db/migrate/021_add_fields_to_connections.rb +15 -0
  36. data/db/migrate/022_remove_database_name_from_resources.rb +11 -0
  37. data/features/connections.feature +28 -0
  38. data/features/matchers.feature +35 -0
  39. data/features/projects.feature +11 -0
  40. data/features/resources.feature +62 -0
  41. data/features/scenarios.feature +45 -0
  42. data/features/step_definitions/coupler_steps.rb +145 -0
  43. data/features/step_definitions/matchers_steps.rb +26 -0
  44. data/features/step_definitions/resources_steps.rb +12 -0
  45. data/features/step_definitions/scenarios_steps.rb +7 -0
  46. data/features/step_definitions/transformations_steps.rb +3 -0
  47. data/features/support/env.rb +128 -0
  48. data/features/transformations.feature +22 -0
  49. data/features/wizard.feature +10 -0
  50. data/gfx/coupler-header.svg +213 -0
  51. data/gfx/coupler-sidebar.svg +656 -0
  52. data/gfx/coupler.svg +184 -0
  53. data/gfx/icon.svg +75 -0
  54. data/lib/coupler/base.rb +63 -0
  55. data/lib/coupler/config.rb +128 -0
  56. data/lib/coupler/data_uploader.rb +20 -0
  57. data/lib/coupler/database.rb +31 -0
  58. data/lib/coupler/extensions/connections.rb +57 -0
  59. data/lib/coupler/extensions/exceptions.rb +58 -0
  60. data/lib/coupler/extensions/imports.rb +43 -0
  61. data/lib/coupler/extensions/jobs.rb +21 -0
  62. data/lib/coupler/extensions/matchers.rb +64 -0
  63. data/lib/coupler/extensions/projects.rb +62 -0
  64. data/lib/coupler/extensions/resources.rb +89 -0
  65. data/lib/coupler/extensions/results.rb +100 -0
  66. data/lib/coupler/extensions/scenarios.rb +50 -0
  67. data/lib/coupler/extensions/transformations.rb +70 -0
  68. data/lib/coupler/extensions/transformers.rb +58 -0
  69. data/lib/coupler/extensions.rb +16 -0
  70. data/lib/coupler/helpers.rb +121 -0
  71. data/lib/coupler/import_buffer.rb +48 -0
  72. data/lib/coupler/logger.rb +16 -0
  73. data/lib/coupler/models/common_model.rb +104 -0
  74. data/lib/coupler/models/comparison.rb +166 -0
  75. data/lib/coupler/models/connection.rb +59 -0
  76. data/lib/coupler/models/field.rb +55 -0
  77. data/lib/coupler/models/import.rb +238 -0
  78. data/lib/coupler/models/job.rb +42 -0
  79. data/lib/coupler/models/jobify.rb +17 -0
  80. data/lib/coupler/models/matcher.rb +36 -0
  81. data/lib/coupler/models/project.rb +40 -0
  82. data/lib/coupler/models/resource.rb +287 -0
  83. data/lib/coupler/models/result.rb +92 -0
  84. data/lib/coupler/models/scenario/runner.rb +357 -0
  85. data/lib/coupler/models/scenario.rb +115 -0
  86. data/lib/coupler/models/transformation.rb +117 -0
  87. data/lib/coupler/models/transformer/runner.rb +28 -0
  88. data/lib/coupler/models/transformer.rb +110 -0
  89. data/lib/coupler/models.rb +30 -0
  90. data/lib/coupler/runner.rb +76 -0
  91. data/lib/coupler/scheduler.rb +56 -0
  92. data/lib/coupler.rb +34 -0
  93. data/log/.gitignore +1 -0
  94. data/misc/README +5 -0
  95. data/misc/jruby-json.license +57 -0
  96. data/misc/rack-flash.license +22 -0
  97. data/script/dbconsole.rb +5 -0
  98. data/src/edu/vanderbilt/coupler/Main.java +116 -0
  99. data/src/edu/vanderbilt/coupler/jruby.properties +1 -0
  100. data/tasks/annotations.rake +84 -0
  101. data/tasks/db.rake +120 -0
  102. data/tasks/environment.rake +12 -0
  103. data/tasks/jeweler.rake +43 -0
  104. data/tasks/package.rake +58 -0
  105. data/tasks/rdoc.rake +13 -0
  106. data/tasks/test.rake +63 -0
  107. data/tasks/vendor.rake +43 -0
  108. data/test/README.txt +6 -0
  109. data/test/config.yml +9 -0
  110. data/test/coupler/models/test_import.rb +221 -0
  111. data/test/factories.rb +91 -0
  112. data/test/fixtures/duplicate-keys.csv +5 -0
  113. data/test/fixtures/no-headers.csv +50 -0
  114. data/test/fixtures/people.csv +51 -0
  115. data/test/fixtures/varying-row-size.csv +4 -0
  116. data/test/helper.rb +156 -0
  117. data/test/integration/extensions/test_connections.rb +80 -0
  118. data/test/integration/extensions/test_imports.rb +94 -0
  119. data/test/integration/extensions/test_jobs.rb +52 -0
  120. data/test/integration/extensions/test_matchers.rb +134 -0
  121. data/test/integration/extensions/test_projects.rb +82 -0
  122. data/test/integration/extensions/test_resources.rb +150 -0
  123. data/test/integration/extensions/test_results.rb +89 -0
  124. data/test/integration/extensions/test_scenarios.rb +88 -0
  125. data/test/integration/extensions/test_transformations.rb +113 -0
  126. data/test/integration/extensions/test_transformers.rb +80 -0
  127. data/test/integration/test_field.rb +45 -0
  128. data/test/integration/test_import.rb +78 -0
  129. data/test/integration/test_running_scenarios.rb +379 -0
  130. data/test/integration/test_transformation.rb +56 -0
  131. data/test/integration/test_transforming.rb +154 -0
  132. data/test/table_sets.rb +76 -0
  133. data/test/unit/models/test_common_model.rb +130 -0
  134. data/test/unit/models/test_comparison.rb +619 -0
  135. data/test/unit/models/test_connection.rb +115 -0
  136. data/test/unit/models/test_field.rb +99 -0
  137. data/test/unit/models/test_import.rb +130 -0
  138. data/test/unit/models/test_job.rb +115 -0
  139. data/test/unit/models/test_matcher.rb +82 -0
  140. data/test/unit/models/test_project.rb +102 -0
  141. data/test/unit/models/test_resource.rb +564 -0
  142. data/test/unit/models/test_result.rb +90 -0
  143. data/test/unit/models/test_scenario.rb +199 -0
  144. data/test/unit/models/test_transformation.rb +193 -0
  145. data/test/unit/models/test_transformer.rb +188 -0
  146. data/test/unit/test_base.rb +60 -0
  147. data/test/unit/test_data_uploader.rb +27 -0
  148. data/test/unit/test_database.rb +23 -0
  149. data/test/unit/test_helpers.rb +58 -0
  150. data/test/unit/test_logger.rb +10 -0
  151. data/test/unit/test_models.rb +12 -0
  152. data/test/unit/test_runner.rb +76 -0
  153. data/test/unit/test_scheduler.rb +66 -0
  154. data/uploads/.gitignore +2 -0
  155. data/vendor/java/.gitignore +5 -0
  156. data/webroot/public/css/960.css +1 -0
  157. data/webroot/public/css/dataTables.css +1057 -0
  158. data/webroot/public/css/jquery-ui.css +572 -0
  159. data/webroot/public/css/jquery.treeview.css +68 -0
  160. data/webroot/public/css/reset.css +1 -0
  161. data/webroot/public/css/style.css +504 -0
  162. data/webroot/public/css/text.css +1 -0
  163. data/webroot/public/favicon.ico +0 -0
  164. data/webroot/public/images/12_col.gif +0 -0
  165. data/webroot/public/images/16_col.gif +0 -0
  166. data/webroot/public/images/add.png +0 -0
  167. data/webroot/public/images/ajax-loader.gif +0 -0
  168. data/webroot/public/images/cog.png +0 -0
  169. data/webroot/public/images/coupler.png +0 -0
  170. data/webroot/public/images/foo.png +0 -0
  171. data/webroot/public/images/hammer.png +0 -0
  172. data/webroot/public/images/header.png +0 -0
  173. data/webroot/public/images/home.gif +0 -0
  174. data/webroot/public/images/jobs.gif +0 -0
  175. data/webroot/public/images/sidebar-bottom.png +0 -0
  176. data/webroot/public/images/sidebar.png +0 -0
  177. data/webroot/public/images/treeview-default-line.gif +0 -0
  178. data/webroot/public/images/treeview-default.gif +0 -0
  179. data/webroot/public/images/ui-anim_basic_16x16.gif +0 -0
  180. data/webroot/public/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  181. data/webroot/public/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  182. data/webroot/public/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  183. data/webroot/public/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  184. data/webroot/public/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  185. data/webroot/public/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  186. data/webroot/public/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  187. data/webroot/public/images/ui-bg_highlight-hard_30_565356_1x100.png +0 -0
  188. data/webroot/public/images/ui-bg_highlight-hard_75_888588_1x100.png +0 -0
  189. data/webroot/public/images/ui-bg_highlight-soft_30_6e3b3a_1x100.png +0 -0
  190. data/webroot/public/images/ui-bg_highlight-soft_35_8e8b8e_1x100.png +0 -0
  191. data/webroot/public/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  192. data/webroot/public/images/ui-icons_222222_256x240.png +0 -0
  193. data/webroot/public/images/ui-icons_2e83ff_256x240.png +0 -0
  194. data/webroot/public/images/ui-icons_454545_256x240.png +0 -0
  195. data/webroot/public/images/ui-icons_888888_256x240.png +0 -0
  196. data/webroot/public/images/ui-icons_cd0a0a_256x240.png +0 -0
  197. data/webroot/public/images/ui-icons_ffffff_256x240.png +0 -0
  198. data/webroot/public/js/ajaxupload.js +673 -0
  199. data/webroot/public/js/application.js +40 -0
  200. data/webroot/public/js/jquery-ui.combobox.js +98 -0
  201. data/webroot/public/js/jquery-ui.js +9867 -0
  202. data/webroot/public/js/jquery-ui.min.js +559 -0
  203. data/webroot/public/js/jquery.dataTables.min.js +587 -0
  204. data/webroot/public/js/jquery.min.js +154 -0
  205. data/webroot/public/js/jquery.timeago.js +140 -0
  206. data/webroot/public/js/jquery.tooltip.min.js +19 -0
  207. data/webroot/public/js/jquery.treeview.min.js +15 -0
  208. data/webroot/public/js/resource.js +11 -0
  209. data/webroot/public/js/results.js +56 -0
  210. data/webroot/public/js/transformations.js +95 -0
  211. data/webroot/views/connections/index.erb +5 -0
  212. data/webroot/views/connections/list.erb +34 -0
  213. data/webroot/views/connections/new.erb +55 -0
  214. data/webroot/views/connections/show.erb +36 -0
  215. data/webroot/views/imports/edit.erb +60 -0
  216. data/webroot/views/imports/form.erb +81 -0
  217. data/webroot/views/imports/new.erb +89 -0
  218. data/webroot/views/index.erb +12 -0
  219. data/webroot/views/jobs/index.erb +7 -0
  220. data/webroot/views/jobs/list.erb +24 -0
  221. data/webroot/views/layout.erb +38 -0
  222. data/webroot/views/matchers/form.erb +250 -0
  223. data/webroot/views/matchers/list.erb +32 -0
  224. data/webroot/views/projects/form.erb +14 -0
  225. data/webroot/views/projects/index.erb +96 -0
  226. data/webroot/views/projects/show.erb +24 -0
  227. data/webroot/views/resources/edit.erb +88 -0
  228. data/webroot/views/resources/index.erb +5 -0
  229. data/webroot/views/resources/list.erb +27 -0
  230. data/webroot/views/resources/new.erb +121 -0
  231. data/webroot/views/resources/show.erb +86 -0
  232. data/webroot/views/resources/transform.erb +2 -0
  233. data/webroot/views/results/csv.erb +12 -0
  234. data/webroot/views/results/details.erb +15 -0
  235. data/webroot/views/results/index.erb +2 -0
  236. data/webroot/views/results/list.erb +22 -0
  237. data/webroot/views/results/record.erb +24 -0
  238. data/webroot/views/results/show.erb +68 -0
  239. data/webroot/views/scenarios/index.erb +5 -0
  240. data/webroot/views/scenarios/list.erb +20 -0
  241. data/webroot/views/scenarios/new.erb +99 -0
  242. data/webroot/views/scenarios/run.erb +2 -0
  243. data/webroot/views/scenarios/show.erb +50 -0
  244. data/webroot/views/sidebar.erb +106 -0
  245. data/webroot/views/transformations/create.erb +115 -0
  246. data/webroot/views/transformations/for.erb +16 -0
  247. data/webroot/views/transformations/index.erb +2 -0
  248. data/webroot/views/transformations/list.erb +29 -0
  249. data/webroot/views/transformations/new.erb +126 -0
  250. data/webroot/views/transformations/preview.erb +46 -0
  251. data/webroot/views/transformers/edit.erb +6 -0
  252. data/webroot/views/transformers/form.erb +58 -0
  253. data/webroot/views/transformers/index.erb +2 -0
  254. data/webroot/views/transformers/list.erb +25 -0
  255. data/webroot/views/transformers/new.erb +5 -0
  256. data/webroot/views/transformers/preview.erb +23 -0
  257. data/webroot/views/transformers/show.erb +0 -0
  258. metadata +558 -0
@@ -0,0 +1,673 @@
1
+ /**
2
+ * AJAX Upload ( http://valums.com/ajax-upload/ )
3
+ * Copyright (c) Andris Valums
4
+ * Licensed under the MIT license ( http://valums.com/mit-license/ )
5
+ * Thanks to Gary Haran, David Mark, Corey Burns and others for contributions
6
+ */
7
+ (function () {
8
+ /* global window */
9
+ /* jslint browser: true, devel: true, undef: true, nomen: true, bitwise: true, regexp: true, newcap: true, immed: true */
10
+
11
+ /**
12
+ * Wrapper for FireBug's console.log
13
+ */
14
+ function log(){
15
+ if (typeof(console) != 'undefined' && typeof(console.log) == 'function'){
16
+ Array.prototype.unshift.call(arguments, '[Ajax Upload]');
17
+ console.log( Array.prototype.join.call(arguments, ' '));
18
+ }
19
+ }
20
+
21
+ /**
22
+ * Attaches event to a dom element.
23
+ * @param {Element} el
24
+ * @param type event name
25
+ * @param fn callback This refers to the passed element
26
+ */
27
+ function addEvent(el, type, fn){
28
+ if (el.addEventListener) {
29
+ el.addEventListener(type, fn, false);
30
+ } else if (el.attachEvent) {
31
+ el.attachEvent('on' + type, function(){
32
+ fn.call(el);
33
+ });
34
+ } else {
35
+ throw new Error('not supported or DOM not loaded');
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Attaches resize event to a window, limiting
41
+ * number of event fired. Fires only when encounteres
42
+ * delay of 100 after series of events.
43
+ *
44
+ * Some browsers fire event multiple times when resizing
45
+ * http://www.quirksmode.org/dom/events/resize.html
46
+ *
47
+ * @param fn callback This refers to the passed element
48
+ */
49
+ function addResizeEvent(fn){
50
+ var timeout;
51
+
52
+ addEvent(window, 'resize', function(){
53
+ if (timeout){
54
+ clearTimeout(timeout);
55
+ }
56
+ timeout = setTimeout(fn, 100);
57
+ });
58
+ }
59
+
60
+ // Needs more testing, will be rewriten for next version
61
+ // getOffset function copied from jQuery lib (http://jquery.com/)
62
+ if (document.documentElement.getBoundingClientRect){
63
+ // Get Offset using getBoundingClientRect
64
+ // http://ejohn.org/blog/getboundingclientrect-is-awesome/
65
+ var getOffset = function(el){
66
+ var box = el.getBoundingClientRect();
67
+ var doc = el.ownerDocument;
68
+ var body = doc.body;
69
+ var docElem = doc.documentElement; // for ie
70
+ var clientTop = docElem.clientTop || body.clientTop || 0;
71
+ var clientLeft = docElem.clientLeft || body.clientLeft || 0;
72
+
73
+ // In Internet Explorer 7 getBoundingClientRect property is treated as physical,
74
+ // while others are logical. Make all logical, like in IE8.
75
+ var zoom = 1;
76
+ if (body.getBoundingClientRect) {
77
+ var bound = body.getBoundingClientRect();
78
+ zoom = (bound.right - bound.left) / body.clientWidth;
79
+ }
80
+
81
+ if (zoom > 1) {
82
+ clientTop = 0;
83
+ clientLeft = 0;
84
+ }
85
+
86
+ var top = box.top / zoom + (window.pageYOffset || docElem && docElem.scrollTop / zoom || body.scrollTop / zoom) - clientTop, left = box.left / zoom + (window.pageXOffset || docElem && docElem.scrollLeft / zoom || body.scrollLeft / zoom) - clientLeft;
87
+
88
+ return {
89
+ top: top,
90
+ left: left
91
+ };
92
+ };
93
+ } else {
94
+ // Get offset adding all offsets
95
+ var getOffset = function(el){
96
+ var top = 0, left = 0;
97
+ do {
98
+ top += el.offsetTop || 0;
99
+ left += el.offsetLeft || 0;
100
+ el = el.offsetParent;
101
+ } while (el);
102
+
103
+ return {
104
+ left: left,
105
+ top: top
106
+ };
107
+ };
108
+ }
109
+
110
+ /**
111
+ * Returns left, top, right and bottom properties describing the border-box,
112
+ * in pixels, with the top-left relative to the body
113
+ * @param {Element} el
114
+ * @return {Object} Contains left, top, right,bottom
115
+ */
116
+ function getBox(el){
117
+ var left, right, top, bottom;
118
+ var offset = getOffset(el);
119
+ left = offset.left;
120
+ top = offset.top;
121
+
122
+ right = left + el.offsetWidth;
123
+ bottom = top + el.offsetHeight;
124
+
125
+ return {
126
+ left: left,
127
+ right: right,
128
+ top: top,
129
+ bottom: bottom
130
+ };
131
+ }
132
+
133
+ /**
134
+ * Helper that takes object literal
135
+ * and add all properties to element.style
136
+ * @param {Element} el
137
+ * @param {Object} styles
138
+ */
139
+ function addStyles(el, styles){
140
+ for (var name in styles) {
141
+ if (styles.hasOwnProperty(name)) {
142
+ el.style[name] = styles[name];
143
+ }
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Function places an absolutely positioned
149
+ * element on top of the specified element
150
+ * copying position and dimentions.
151
+ * @param {Element} from
152
+ * @param {Element} to
153
+ */
154
+ function copyLayout(from, to){
155
+ var box = getBox(from);
156
+
157
+ addStyles(to, {
158
+ position: 'absolute',
159
+ left : box.left + 'px',
160
+ top : box.top + 'px',
161
+ width : from.offsetWidth + 'px',
162
+ height : from.offsetHeight + 'px'
163
+ });
164
+ }
165
+
166
+ /**
167
+ * Creates and returns element from html chunk
168
+ * Uses innerHTML to create an element
169
+ */
170
+ var toElement = (function(){
171
+ var div = document.createElement('div');
172
+ return function(html){
173
+ div.innerHTML = html;
174
+ var el = div.firstChild;
175
+ return div.removeChild(el);
176
+ };
177
+ })();
178
+
179
+ /**
180
+ * Function generates unique id
181
+ * @return unique id
182
+ */
183
+ var getUID = (function(){
184
+ var id = 0;
185
+ return function(){
186
+ return 'ValumsAjaxUpload' + id++;
187
+ };
188
+ })();
189
+
190
+ /**
191
+ * Get file name from path
192
+ * @param {String} file path to file
193
+ * @return filename
194
+ */
195
+ function fileFromPath(file){
196
+ return file.replace(/.*(\/|\\)/, "");
197
+ }
198
+
199
+ /**
200
+ * Get file extension lowercase
201
+ * @param {String} file name
202
+ * @return file extenstion
203
+ */
204
+ function getExt(file){
205
+ return (-1 !== file.indexOf('.')) ? file.replace(/.*[.]/, '') : '';
206
+ }
207
+
208
+ function hasClass(el, name){
209
+ var re = new RegExp('\\b' + name + '\\b');
210
+ return re.test(el.className);
211
+ }
212
+ function addClass(el, name){
213
+ if ( ! hasClass(el, name)){
214
+ el.className += ' ' + name;
215
+ }
216
+ }
217
+ function removeClass(el, name){
218
+ var re = new RegExp('\\b' + name + '\\b');
219
+ el.className = el.className.replace(re, '');
220
+ }
221
+
222
+ function removeNode(el){
223
+ el.parentNode.removeChild(el);
224
+ }
225
+
226
+ /**
227
+ * Easy styling and uploading
228
+ * @constructor
229
+ * @param button An element you want convert to
230
+ * upload button. Tested dimentions up to 500x500px
231
+ * @param {Object} options See defaults below.
232
+ */
233
+ window.AjaxUpload = function(button, options){
234
+ this._settings = {
235
+ // Location of the server-side upload script
236
+ action: 'upload.php',
237
+ // File upload name
238
+ name: 'userfile',
239
+ // Additional data to send
240
+ data: {},
241
+ // Submit file as soon as it's selected
242
+ autoSubmit: true,
243
+ // The type of data that you're expecting back from the server.
244
+ // html and xml are detected automatically.
245
+ // Only useful when you are using json data as a response.
246
+ // Set to "json" in that case.
247
+ responseType: false,
248
+ // Class applied to button when mouse is hovered
249
+ hoverClass: 'hover',
250
+ // Class applied to button when AU is disabled
251
+ disabledClass: 'disabled',
252
+ // When user selects a file, useful with autoSubmit disabled
253
+ // You can return false to cancel upload
254
+ onChange: function(file, extension){
255
+ },
256
+ // Callback to fire before file is uploaded
257
+ // You can return false to cancel upload
258
+ onSubmit: function(file, extension){
259
+ },
260
+ // Fired when file upload is completed
261
+ // WARNING! DO NOT USE "FALSE" STRING AS A RESPONSE!
262
+ onComplete: function(file, response){
263
+ }
264
+ };
265
+
266
+ // Merge the users options with our defaults
267
+ for (var i in options) {
268
+ if (options.hasOwnProperty(i)){
269
+ this._settings[i] = options[i];
270
+ }
271
+ }
272
+
273
+ // button isn't necessary a dom element
274
+ if (button.jquery){
275
+ // jQuery object was passed
276
+ button = button[0];
277
+ } else if (typeof button == "string") {
278
+ if (/^#.*/.test(button)){
279
+ // If jQuery user passes #elementId don't break it
280
+ button = button.slice(1);
281
+ }
282
+
283
+ button = document.getElementById(button);
284
+ }
285
+
286
+ if ( ! button || button.nodeType !== 1){
287
+ throw new Error("Please make sure that you're passing a valid element");
288
+ }
289
+
290
+ if ( button.nodeName.toUpperCase() == 'A'){
291
+ // disable link
292
+ addEvent(button, 'click', function(e){
293
+ if (e && e.preventDefault){
294
+ e.preventDefault();
295
+ } else if (window.event){
296
+ window.event.returnValue = false;
297
+ }
298
+ });
299
+ }
300
+
301
+ // DOM element
302
+ this._button = button;
303
+ // DOM element
304
+ this._input = null;
305
+ // If disabled clicking on button won't do anything
306
+ this._disabled = false;
307
+
308
+ // if the button was disabled before refresh if will remain
309
+ // disabled in FireFox, let's fix it
310
+ this.enable();
311
+
312
+ this._rerouteClicks();
313
+ };
314
+
315
+ // assigning methods to our class
316
+ AjaxUpload.prototype = {
317
+ setData: function(data){
318
+ this._settings.data = data;
319
+ },
320
+ disable: function(){
321
+ addClass(this._button, this._settings.disabledClass);
322
+ this._disabled = true;
323
+
324
+ var nodeName = this._button.nodeName.toUpperCase();
325
+ if (nodeName == 'INPUT' || nodeName == 'BUTTON'){
326
+ this._button.setAttribute('disabled', 'disabled');
327
+ }
328
+
329
+ // hide input
330
+ if (this._input){
331
+ // We use visibility instead of display to fix problem with Safari 4
332
+ // The problem is that the value of input doesn't change if it
333
+ // has display none when user selects a file
334
+ this._input.parentNode.style.visibility = 'hidden';
335
+ }
336
+ },
337
+ enable: function(){
338
+ removeClass(this._button, this._settings.disabledClass);
339
+ this._button.removeAttribute('disabled');
340
+ this._disabled = false;
341
+
342
+ },
343
+ /**
344
+ * Creates invisible file input
345
+ * that will hover above the button
346
+ * <div><input type='file' /></div>
347
+ */
348
+ _createInput: function(){
349
+ var self = this;
350
+
351
+ var input = document.createElement("input");
352
+ input.setAttribute('type', 'file');
353
+ input.setAttribute('name', this._settings.name);
354
+
355
+ addStyles(input, {
356
+ 'position' : 'absolute',
357
+ // in Opera only 'browse' button
358
+ // is clickable and it is located at
359
+ // the right side of the input
360
+ 'right' : 0,
361
+ 'margin' : 0,
362
+ 'padding' : 0,
363
+ 'fontSize' : '480px',
364
+ 'cursor' : 'pointer'
365
+ });
366
+
367
+ var div = document.createElement("div");
368
+ addStyles(div, {
369
+ 'display' : 'block',
370
+ 'position' : 'absolute',
371
+ 'overflow' : 'hidden',
372
+ 'margin' : 0,
373
+ 'padding' : 0,
374
+ 'opacity' : 0,
375
+ // Make sure browse button is in the right side
376
+ // in Internet Explorer
377
+ 'direction' : 'ltr',
378
+ //Max zIndex supported by Opera 9.0-9.2
379
+ 'zIndex': 2147483583
380
+ });
381
+
382
+ // Make sure that element opacity exists.
383
+ // Otherwise use IE filter
384
+ if ( div.style.opacity !== "0") {
385
+ if (typeof(div.filters) == 'undefined'){
386
+ throw new Error('Opacity not supported by the browser');
387
+ }
388
+ div.style.filter = "alpha(opacity=0)";
389
+ }
390
+
391
+ addEvent(input, 'change', function(){
392
+
393
+ if ( ! input || input.value === ''){
394
+ return;
395
+ }
396
+
397
+ // Get filename from input, required
398
+ // as some browsers have path instead of it
399
+ var file = fileFromPath(input.value);
400
+
401
+ if (false === self._settings.onChange.call(self, file, getExt(file))){
402
+ self._clearInput();
403
+ return;
404
+ }
405
+
406
+ // Submit form when value is changed
407
+ if (self._settings.autoSubmit) {
408
+ self.submit();
409
+ }
410
+ });
411
+
412
+ addEvent(input, 'mouseover', function(){
413
+ addClass(self._button, self._settings.hoverClass);
414
+ });
415
+
416
+ addEvent(input, 'mouseout', function(){
417
+ removeClass(self._button, self._settings.hoverClass);
418
+
419
+ // We use visibility instead of display to fix problem with Safari 4
420
+ // The problem is that the value of input doesn't change if it
421
+ // has display none when user selects a file
422
+ input.parentNode.style.visibility = 'hidden';
423
+
424
+ });
425
+
426
+ div.appendChild(input);
427
+ document.body.appendChild(div);
428
+
429
+ this._input = input;
430
+ },
431
+ _clearInput : function(){
432
+ if (!this._input){
433
+ return;
434
+ }
435
+
436
+ // this._input.value = ''; Doesn't work in IE6
437
+ removeNode(this._input.parentNode);
438
+ this._input = null;
439
+ this._createInput();
440
+
441
+ removeClass(this._button, this._settings.hoverClass);
442
+ },
443
+ /**
444
+ * Function makes sure that when user clicks upload button,
445
+ * the this._input is clicked instead
446
+ */
447
+ _rerouteClicks: function(){
448
+ var self = this;
449
+
450
+ // IE will later display 'access denied' error
451
+ // if you use using self._input.click()
452
+ // other browsers just ignore click()
453
+
454
+ addEvent(self._button, 'mouseover', function(){
455
+ if (self._disabled){
456
+ return;
457
+ }
458
+
459
+ if ( ! self._input){
460
+ self._createInput();
461
+ }
462
+
463
+ var div = self._input.parentNode;
464
+ copyLayout(self._button, div);
465
+ div.style.visibility = 'visible';
466
+
467
+ });
468
+
469
+
470
+ // commented because we now hide input on mouseleave
471
+ /**
472
+ * When the window is resized the elements
473
+ * can be misaligned if button position depends
474
+ * on window size
475
+ */
476
+ //addResizeEvent(function(){
477
+ // if (self._input){
478
+ // copyLayout(self._button, self._input.parentNode);
479
+ // }
480
+ //});
481
+
482
+ },
483
+ /**
484
+ * Creates iframe with unique name
485
+ * @return {Element} iframe
486
+ */
487
+ _createIframe: function(){
488
+ // We can't use getTime, because it sometimes return
489
+ // same value in safari :(
490
+ var id = getUID();
491
+
492
+ // We can't use following code as the name attribute
493
+ // won't be properly registered in IE6, and new window
494
+ // on form submit will open
495
+ // var iframe = document.createElement('iframe');
496
+ // iframe.setAttribute('name', id);
497
+
498
+ var iframe = toElement('<iframe src="javascript:false;" name="' + id + '" />');
499
+ // src="javascript:false; was added
500
+ // because it possibly removes ie6 prompt
501
+ // "This page contains both secure and nonsecure items"
502
+ // Anyway, it doesn't do any harm.
503
+ iframe.setAttribute('id', id);
504
+
505
+ iframe.style.display = 'none';
506
+ document.body.appendChild(iframe);
507
+
508
+ return iframe;
509
+ },
510
+ /**
511
+ * Creates form, that will be submitted to iframe
512
+ * @param {Element} iframe Where to submit
513
+ * @return {Element} form
514
+ */
515
+ _createForm: function(iframe){
516
+ var settings = this._settings;
517
+
518
+ // We can't use the following code in IE6
519
+ // var form = document.createElement('form');
520
+ // form.setAttribute('method', 'post');
521
+ // form.setAttribute('enctype', 'multipart/form-data');
522
+ // Because in this case file won't be attached to request
523
+ var form = toElement('<form method="post" enctype="multipart/form-data"></form>');
524
+
525
+ form.setAttribute('action', settings.action);
526
+ form.setAttribute('target', iframe.name);
527
+ form.style.display = 'none';
528
+ document.body.appendChild(form);
529
+
530
+ // Create hidden input element for each data key
531
+ for (var prop in settings.data) {
532
+ if (settings.data.hasOwnProperty(prop)){
533
+ var el = document.createElement("input");
534
+ el.setAttribute('type', 'hidden');
535
+ el.setAttribute('name', prop);
536
+ el.setAttribute('value', settings.data[prop]);
537
+ form.appendChild(el);
538
+ }
539
+ }
540
+ return form;
541
+ },
542
+ /**
543
+ * Gets response from iframe and fires onComplete event when ready
544
+ * @param iframe
545
+ * @param file Filename to use in onComplete callback
546
+ */
547
+ _getResponse : function(iframe, file){
548
+ // getting response
549
+ var toDeleteFlag = false, self = this, settings = this._settings;
550
+
551
+ addEvent(iframe, 'load', function(){
552
+
553
+ if (// For Safari
554
+ iframe.src == "javascript:'%3Chtml%3E%3C/html%3E';" ||
555
+ // For FF, IE
556
+ iframe.src == "javascript:'<html></html>';"){
557
+ // First time around, do not delete.
558
+ // We reload to blank page, so that reloading main page
559
+ // does not re-submit the post.
560
+
561
+ if (toDeleteFlag) {
562
+ // Fix busy state in FF3
563
+ setTimeout(function(){
564
+ removeNode(iframe);
565
+ }, 0);
566
+ }
567
+
568
+ return;
569
+ }
570
+
571
+ var doc = iframe.contentDocument ? iframe.contentDocument : window.frames[iframe.id].document;
572
+
573
+ // fixing Opera 9.26,10.00
574
+ if (doc.readyState && doc.readyState != 'complete') {
575
+ // Opera fires load event multiple times
576
+ // Even when the DOM is not ready yet
577
+ // this fix should not affect other browsers
578
+ return;
579
+ }
580
+
581
+ // fixing Opera 9.64
582
+ if (doc.body && doc.body.innerHTML == "false") {
583
+ // In Opera 9.64 event was fired second time
584
+ // when body.innerHTML changed from false
585
+ // to server response approx. after 1 sec
586
+ return;
587
+ }
588
+
589
+ var response;
590
+
591
+ if (doc.XMLDocument) {
592
+ // response is a xml document Internet Explorer property
593
+ response = doc.XMLDocument;
594
+ } else if (doc.body){
595
+ // response is html document or plain text
596
+ response = doc.body.innerHTML;
597
+
598
+ if (settings.responseType && settings.responseType.toLowerCase() == 'json') {
599
+ // If the document was sent as 'application/javascript' or
600
+ // 'text/javascript', then the browser wraps the text in a <pre>
601
+ // tag and performs html encoding on the contents. In this case,
602
+ // we need to pull the original text content from the text node's
603
+ // nodeValue property to retrieve the unmangled content.
604
+ // Note that IE6 only understands text/html
605
+ if (doc.body.firstChild && doc.body.firstChild.nodeName.toUpperCase() == 'PRE') {
606
+ response = doc.body.firstChild.firstChild.nodeValue;
607
+ }
608
+
609
+ if (response) {
610
+ response = eval("(" + response + ")");
611
+ } else {
612
+ response = {};
613
+ }
614
+ }
615
+ } else {
616
+ // response is a xml document
617
+ response = doc;
618
+ }
619
+
620
+ settings.onComplete.call(self, file, response);
621
+
622
+ // Reload blank page, so that reloading main page
623
+ // does not re-submit the post. Also, remember to
624
+ // delete the frame
625
+ toDeleteFlag = true;
626
+
627
+ // Fix IE mixed content issue
628
+ iframe.src = "javascript:'<html></html>';";
629
+ });
630
+ },
631
+ /**
632
+ * Upload file contained in this._input
633
+ */
634
+ submit: function(){
635
+ var self = this, settings = this._settings;
636
+
637
+ if ( ! this._input || this._input.value === ''){
638
+ return;
639
+ }
640
+
641
+ var file = fileFromPath(this._input.value);
642
+
643
+ // user returned false to cancel upload
644
+ if (false === settings.onSubmit.call(this, file, getExt(file))){
645
+ this._clearInput();
646
+ return;
647
+ }
648
+
649
+ // sending request
650
+ var iframe = this._createIframe();
651
+ var form = this._createForm(iframe);
652
+
653
+ // assuming following structure
654
+ // div -> input type='file'
655
+ removeNode(this._input.parentNode);
656
+ removeClass(self._button, self._settings.hoverClass);
657
+
658
+ form.appendChild(this._input);
659
+
660
+ form.submit();
661
+
662
+ // request set, clean up
663
+ removeNode(form); form = null;
664
+ removeNode(this._input); this._input = null;
665
+
666
+ // Get response from iframe and fire onComplete event when ready
667
+ this._getResponse(iframe, file);
668
+
669
+ // get ready for next request
670
+ this._createInput();
671
+ }
672
+ };
673
+ })();