coupler 0.0.1-java

Sign up to get free protection for your applications and to get access to all the features.
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
+ })();