enki-engine 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (206) hide show
  1. data/.gitignore +13 -0
  2. data/.rspec +1 -0
  3. data/.travis.yml +3 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE +284 -0
  6. data/README.textile +112 -0
  7. data/Rakefile +13 -0
  8. data/TODO.textile +8 -0
  9. data/app/assets/images/admin/flash_bg.gif +0 -0
  10. data/app/assets/images/admin/future_bg.png +0 -0
  11. data/app/assets/images/admin/gray_bg.gif +0 -0
  12. data/app/assets/images/admin/new_button.png +0 -0
  13. data/app/assets/images/admin/silver_bg.gif +0 -0
  14. data/app/assets/images/admin/submit_bg.gif +0 -0
  15. data/app/assets/images/admin/subnav_bg.gif +0 -0
  16. data/app/assets/images/openid_icon.png +0 -0
  17. data/app/assets/images/rails.png +0 -0
  18. data/app/assets/images/silk/arrow_undo.png +0 -0
  19. data/app/assets/images/silk/delete.png +0 -0
  20. data/app/assets/images/silk/pencil.png +0 -0
  21. data/app/assets/javascripts/admin/actions.js +18 -0
  22. data/app/assets/javascripts/admin/comments.js +3 -0
  23. data/app/assets/javascripts/admin/common.js +109 -0
  24. data/app/assets/javascripts/admin/dashboard.js +33 -0
  25. data/app/assets/javascripts/admin/edit-preview.js +40 -0
  26. data/app/assets/javascripts/admin/pages.js +1 -0
  27. data/app/assets/javascripts/admin/posts.js +1 -0
  28. data/app/assets/javascripts/admin/shortcut.js +223 -0
  29. data/app/assets/javascripts/admin.js +17 -0
  30. data/app/assets/javascripts/application.js +15 -0
  31. data/app/assets/javascripts/common.js +22 -0
  32. data/app/assets/javascripts/enki.js +13 -0
  33. data/app/assets/javascripts/live-comment-preview.js +36 -0
  34. data/app/assets/stylesheets/admin.css +304 -0
  35. data/app/assets/stylesheets/application.css.scss +486 -0
  36. data/app/assets/stylesheets/humanmsg.css +112 -0
  37. data/app/assets/stylesheets/login.css +65 -0
  38. data/app/controllers/enki/admin/base_controller.rb +15 -0
  39. data/app/controllers/enki/admin/comments_controller.rb +52 -0
  40. data/app/controllers/enki/admin/dashboard_controller.rb +12 -0
  41. data/app/controllers/enki/admin/health_controller.rb +20 -0
  42. data/app/controllers/enki/admin/pages_controller.rb +97 -0
  43. data/app/controllers/enki/admin/posts_controller.rb +104 -0
  44. data/app/controllers/enki/admin/undo_items_controller.rb +43 -0
  45. data/app/controllers/enki/application_controller.rb +21 -0
  46. data/app/controllers/enki/archives_controller.rb +7 -0
  47. data/app/controllers/enki/base_controller.rb +5 -0
  48. data/app/controllers/enki/comments_controller.rb +43 -0
  49. data/app/controllers/enki/pages_controller.rb +7 -0
  50. data/app/controllers/enki/posts_controller.rb +27 -0
  51. data/app/helpers/enki/admin/navigation_helper.rb +10 -0
  52. data/app/helpers/enki/application_helper.rb +35 -0
  53. data/app/helpers/enki/date_helper.rb +15 -0
  54. data/app/helpers/enki/form_helper.rb +7 -0
  55. data/app/helpers/enki/host_helper.rb +19 -0
  56. data/app/helpers/enki/navigation_helper.rb +23 -0
  57. data/app/helpers/enki/page_title_helper.rb +33 -0
  58. data/app/helpers/enki/posts_helper.rb +9 -0
  59. data/app/helpers/enki/tag_helper.rb +7 -0
  60. data/app/helpers/enki/url_helper.rb +25 -0
  61. data/app/models/enki/base/post.rb +153 -0
  62. data/app/models/enki/comment.rb +75 -0
  63. data/app/models/enki/comment_activity.rb +33 -0
  64. data/app/models/enki/delete_comment_undo.rb +33 -0
  65. data/app/models/enki/delete_page_undo.rb +32 -0
  66. data/app/models/enki/delete_post_undo.rb +36 -0
  67. data/app/models/enki/page.rb +42 -0
  68. data/app/models/enki/post.rb +4 -0
  69. data/app/models/enki/stats.rb +19 -0
  70. data/app/models/enki/tag.rb +19 -0
  71. data/app/models/enki/tagging.rb +7 -0
  72. data/app/models/enki/undo_item.rb +10 -0
  73. data/app/views/enki/admin/comments/_comment.html.erb +12 -0
  74. data/app/views/enki/admin/comments/index.html.erb +30 -0
  75. data/app/views/enki/admin/comments/show.html.erb +9 -0
  76. data/app/views/enki/admin/dashboard/show.html.erb +61 -0
  77. data/app/views/enki/admin/health/index.html.erb +3 -0
  78. data/app/views/enki/admin/pages/_form.html.erb +3 -0
  79. data/app/views/enki/admin/pages/_page.html.erb +12 -0
  80. data/app/views/enki/admin/pages/index.html.erb +25 -0
  81. data/app/views/enki/admin/pages/new.html.erb +8 -0
  82. data/app/views/enki/admin/pages/show.html.erb +8 -0
  83. data/app/views/enki/admin/posts/_form.html.erb +11 -0
  84. data/app/views/enki/admin/posts/_post.html.erb +12 -0
  85. data/app/views/enki/admin/posts/_taggings_form.html.erb +4 -0
  86. data/app/views/enki/admin/posts/_upload_form.html.erb +0 -0
  87. data/app/views/enki/admin/posts/index.html.erb +25 -0
  88. data/app/views/enki/admin/posts/new.html.erb +8 -0
  89. data/app/views/enki/admin/posts/show.html.erb +8 -0
  90. data/app/views/enki/admin/undo_items/index.html.erb +24 -0
  91. data/app/views/enki/archives/index.html.erb +17 -0
  92. data/app/views/enki/comments/_comment.html.erb +4 -0
  93. data/app/views/enki/pages/_page.html.erb +3 -0
  94. data/app/views/enki/pages/show.html.erb +5 -0
  95. data/app/views/enki/posts/_post.html.erb +13 -0
  96. data/app/views/enki/posts/index.atom.builder +27 -0
  97. data/app/views/enki/posts/index.html.erb +15 -0
  98. data/app/views/enki/posts/show.html.erb +37 -0
  99. data/autotest/discover.rb +2 -0
  100. data/config/cucumber.yml +8 -0
  101. data/config/enki.yml.sample +16 -0
  102. data/config/initializers/enki_ext.rb +3 -0
  103. data/config/initializers/set_chronic_timezone.rb +1 -0
  104. data/config/initializers/verification.rb +135 -0
  105. data/config/routes.rb +34 -0
  106. data/db/migrate/20110709024316_initialize_db.rb +97 -0
  107. data/db/seeds.rb +8 -0
  108. data/enki-engine.gemspec +47 -0
  109. data/features/admin_dashboard.feature +10 -0
  110. data/features/admin_health.feature +10 -0
  111. data/features/admin_undo.feature +20 -0
  112. data/features/browsing.feature +16 -0
  113. data/features/step_definitions/admin.rb +27 -0
  114. data/features/step_definitions/browsing.rb +3 -0
  115. data/features/step_definitions/posts.rb +11 -0
  116. data/features/step_definitions/web_steps.rb +1 -0
  117. data/features/support/env.rb +59 -0
  118. data/features/support/paths.rb +35 -0
  119. data/features/support/selectors.rb +39 -0
  120. data/lib/core_extensions/object.rb +9 -0
  121. data/lib/core_extensions/string.rb +22 -0
  122. data/lib/enki/config.rb +44 -0
  123. data/lib/enki/engine.rb +19 -0
  124. data/lib/enki/html5_tags.rb +8 -0
  125. data/lib/enki/pagination_shim.rb +25 -0
  126. data/lib/enki/version.rb +3 -0
  127. data/lib/enki.rb +14 -0
  128. data/lib/enki_formatter.rb +11 -0
  129. data/lib/tag_list.rb +2 -0
  130. data/lib/tags_helper.rb +13 -0
  131. data/lib/tasks/cucumber.rake +65 -0
  132. data/lib/tasks/enki.rake +29 -0
  133. data/lib/undo_failed.rb +2 -0
  134. data/script/cucumber +10 -0
  135. data/spec/controllers/admin/comments_controller_spec.rb +140 -0
  136. data/spec/controllers/admin/dashboard_controller_spec.rb +47 -0
  137. data/spec/controllers/admin/health_controller_spec.rb +49 -0
  138. data/spec/controllers/admin/pages_controller_spec.rb +136 -0
  139. data/spec/controllers/admin/posts_controller_spec.rb +183 -0
  140. data/spec/controllers/admin/undo_items_controller_spec.rb +93 -0
  141. data/spec/controllers/archives_controller_spec.rb +37 -0
  142. data/spec/controllers/comments_controller_spec.rb +126 -0
  143. data/spec/controllers/pages_controller_spec.rb +46 -0
  144. data/spec/controllers/posts_controller_spec.rb +168 -0
  145. data/spec/dummy/Gemfile +5 -0
  146. data/spec/dummy/Rakefile +7 -0
  147. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  148. data/spec/dummy/config/application.rb +34 -0
  149. data/spec/dummy/config/boot.rb +10 -0
  150. data/spec/dummy/config/database.yml +6 -0
  151. data/spec/dummy/config/enki.yml +20 -0
  152. data/spec/dummy/config/environment.rb +5 -0
  153. data/spec/dummy/config/environments/development.rb +34 -0
  154. data/spec/dummy/config/environments/test.rb +32 -0
  155. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  156. data/spec/dummy/config/initializers/session_store.rb +8 -0
  157. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  158. data/spec/dummy/config/locales/en.yml +5 -0
  159. data/spec/dummy/config/routes.rb +14 -0
  160. data/spec/dummy/config.ru +6 -0
  161. data/spec/dummy/script/rails +6 -0
  162. data/spec/factories/factories.rb +36 -0
  163. data/spec/helpers/page_title_helper_spec.rb +54 -0
  164. data/spec/helpers/url_helper_spec.rb +23 -0
  165. data/spec/lib/slugorize_spec.rb +44 -0
  166. data/spec/models/comment_activity_spec.rb +60 -0
  167. data/spec/models/comment_spec.rb +125 -0
  168. data/spec/models/delete_comment_undo_spec.rb +52 -0
  169. data/spec/models/delete_post_undo_spec.rb +18 -0
  170. data/spec/models/page_spec.rb +75 -0
  171. data/spec/models/post_spec.rb +257 -0
  172. data/spec/models/stats_spec.rb +28 -0
  173. data/spec/models/tag_spec.rb +13 -0
  174. data/spec/models/tagging_spec.rb +30 -0
  175. data/spec/rcov.opts +2 -0
  176. data/spec/routing/admin/pages_routing_spec.rb +29 -0
  177. data/spec/routing/archives_routing_spec.rb +9 -0
  178. data/spec/routing/comments_routing_spec.rb +17 -0
  179. data/spec/routing/pages_routing_spec.rb +9 -0
  180. data/spec/routing/posts_routing_spec.rb +26 -0
  181. data/spec/spec.opts +4 -0
  182. data/spec/spec_helper.rb +60 -0
  183. data/spec/support/be_valid_html5.rb +150 -0
  184. data/spec/support/be_valid_xhtml.rb +148 -0
  185. data/spec/support/routes_override_helper.rb +12 -0
  186. data/spec/views/admin/comments/index.html_spec.rb +26 -0
  187. data/spec/views/admin/comments/show.html_spec.rb +28 -0
  188. data/spec/views/admin/dashboard/show.html_spec.rb +37 -0
  189. data/spec/views/admin/pages/index.html_spec.rb +23 -0
  190. data/spec/views/admin/pages/new.html_spec.rb +16 -0
  191. data/spec/views/admin/pages/show.html_spec.rb +16 -0
  192. data/spec/views/admin/posts/index.html_spec.rb +24 -0
  193. data/spec/views/admin/posts/new.html_spec.rb +16 -0
  194. data/spec/views/admin/posts/show.html_spec.rb +16 -0
  195. data/spec/views/admin/undo_items/index.html_spec.rb +19 -0
  196. data/spec/views/archives/index.html_spec.rb +34 -0
  197. data/spec/views/pages/show.html_spec.rb +23 -0
  198. data/spec/views/posts/index.atom.builder_spec.rb +36 -0
  199. data/spec/views/posts/index.html_spec.rb +39 -0
  200. data/spec/views/posts/show.html_spec.rb +49 -0
  201. data/vendor/assets/javascripts/humanmsg.js +86 -0
  202. data/vendor/assets/javascripts/jquery.easing.1.3.js +205 -0
  203. data/vendor/assets/javascripts/jquery.form.js +869 -0
  204. data/vendor/assets/javascripts/jquery.jfeed.js +143 -0
  205. data/vendor/assets/javascripts/jquery.livequery.js +250 -0
  206. metadata +464 -0
@@ -0,0 +1,869 @@
1
+ /*
2
+ * jQuery Form Plugin
3
+ * version: 2.07 (03/04/2008)
4
+ * @requires jQuery v1.2.2 or later
5
+ *
6
+ * Examples at: http://malsup.com/jquery/form/
7
+ * Dual licensed under the MIT and GPL licenses:
8
+ * http://www.opensource.org/licenses/mit-license.php
9
+ * http://www.gnu.org/licenses/gpl.html
10
+ *
11
+ * Revision: $Id$
12
+ */
13
+ (function($) {
14
+ /**
15
+ * ajaxSubmit() provides a mechanism for submitting an HTML form using AJAX.
16
+ *
17
+ * ajaxSubmit accepts a single argument which can be either a success callback function
18
+ * or an options Object. If a function is provided it will be invoked upon successful
19
+ * completion of the submit and will be passed the response from the server.
20
+ * If an options Object is provided, the following attributes are supported:
21
+ *
22
+ * target: Identifies the element(s) in the page to be updated with the server response.
23
+ * This value may be specified as a jQuery selection string, a jQuery object,
24
+ * or a DOM element.
25
+ * default value: null
26
+ *
27
+ * url: URL to which the form data will be submitted.
28
+ * default value: value of form's 'action' attribute
29
+ *
30
+ * type: The method in which the form data should be submitted, 'GET' or 'POST'.
31
+ * default value: value of form's 'method' attribute (or 'GET' if none found)
32
+ *
33
+ * data: Additional data to add to the request, specified as key/value pairs (see $.ajax).
34
+ *
35
+ * beforeSubmit: Callback method to be invoked before the form is submitted.
36
+ * default value: null
37
+ *
38
+ * success: Callback method to be invoked after the form has been successfully submitted
39
+ * and the response has been returned from the server
40
+ * default value: null
41
+ *
42
+ * dataType: Expected dataType of the response. One of: null, 'xml', 'script', or 'json'
43
+ * default value: null
44
+ *
45
+ * semantic: Boolean flag indicating whether data must be submitted in semantic order (slower).
46
+ * default value: false
47
+ *
48
+ * resetForm: Boolean flag indicating whether the form should be reset if the submit is successful
49
+ *
50
+ * clearForm: Boolean flag indicating whether the form should be cleared if the submit is successful
51
+ *
52
+ *
53
+ * The 'beforeSubmit' callback can be provided as a hook for running pre-submit logic or for
54
+ * validating the form data. If the 'beforeSubmit' callback returns false then the form will
55
+ * not be submitted. The 'beforeSubmit' callback is invoked with three arguments: the form data
56
+ * in array format, the jQuery object, and the options object passed into ajaxSubmit.
57
+ * The form data array takes the following form:
58
+ *
59
+ * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
60
+ *
61
+ * If a 'success' callback method is provided it is invoked after the response has been returned
62
+ * from the server. It is passed the responseText or responseXML value (depending on dataType).
63
+ * See jQuery.ajax for further details.
64
+ *
65
+ *
66
+ * The dataType option provides a means for specifying how the server response should be handled.
67
+ * This maps directly to the jQuery.httpData method. The following values are supported:
68
+ *
69
+ * 'xml': if dataType == 'xml' the server response is treated as XML and the 'success'
70
+ * callback method, if specified, will be passed the responseXML value
71
+ * 'json': if dataType == 'json' the server response will be evaluted and passed to
72
+ * the 'success' callback, if specified
73
+ * 'script': if dataType == 'script' the server response is evaluated in the global context
74
+ *
75
+ *
76
+ * Note that it does not make sense to use both the 'target' and 'dataType' options. If both
77
+ * are provided the target will be ignored.
78
+ *
79
+ * The semantic argument can be used to force form serialization in semantic order.
80
+ * This is normally true anyway, unless the form contains input elements of type='image'.
81
+ * If your form must be submitted with name/value pairs in semantic order and your form
82
+ * contains an input of type='image" then pass true for this arg, otherwise pass false
83
+ * (or nothing) to avoid the overhead for this logic.
84
+ *
85
+ *
86
+ * When used on its own, ajaxSubmit() is typically bound to a form's submit event like this:
87
+ *
88
+ * $("#form-id").submit(function() {
89
+ * $(this).ajaxSubmit(options);
90
+ * return false; // cancel conventional submit
91
+ * });
92
+ *
93
+ * When using ajaxForm(), however, this is done for you.
94
+ *
95
+ * @example
96
+ * $('#myForm').ajaxSubmit(function(data) {
97
+ * alert('Form submit succeeded! Server returned: ' + data);
98
+ * });
99
+ * @desc Submit form and alert server response
100
+ *
101
+ *
102
+ * @example
103
+ * var options = {
104
+ * target: '#myTargetDiv'
105
+ * };
106
+ * $('#myForm').ajaxSubmit(options);
107
+ * @desc Submit form and update page element with server response
108
+ *
109
+ *
110
+ * @example
111
+ * var options = {
112
+ * success: function(responseText) {
113
+ * alert(responseText);
114
+ * }
115
+ * };
116
+ * $('#myForm').ajaxSubmit(options);
117
+ * @desc Submit form and alert the server response
118
+ *
119
+ *
120
+ * @example
121
+ * var options = {
122
+ * beforeSubmit: function(formArray, jqForm) {
123
+ * if (formArray.length == 0) {
124
+ * alert('Please enter data.');
125
+ * return false;
126
+ * }
127
+ * }
128
+ * };
129
+ * $('#myForm').ajaxSubmit(options);
130
+ * @desc Pre-submit validation which aborts the submit operation if form data is empty
131
+ *
132
+ *
133
+ * @example
134
+ * var options = {
135
+ * url: myJsonUrl.php,
136
+ * dataType: 'json',
137
+ * success: function(data) {
138
+ * // 'data' is an object representing the the evaluated json data
139
+ * }
140
+ * };
141
+ * $('#myForm').ajaxSubmit(options);
142
+ * @desc json data returned and evaluated
143
+ *
144
+ *
145
+ * @example
146
+ * var options = {
147
+ * url: myXmlUrl.php,
148
+ * dataType: 'xml',
149
+ * success: function(responseXML) {
150
+ * // responseXML is XML document object
151
+ * var data = $('myElement', responseXML).text();
152
+ * }
153
+ * };
154
+ * $('#myForm').ajaxSubmit(options);
155
+ * @desc XML data returned from server
156
+ *
157
+ *
158
+ * @example
159
+ * var options = {
160
+ * resetForm: true
161
+ * };
162
+ * $('#myForm').ajaxSubmit(options);
163
+ * @desc submit form and reset it if successful
164
+ *
165
+ * @example
166
+ * $('#myForm).submit(function() {
167
+ * $(this).ajaxSubmit();
168
+ * return false;
169
+ * });
170
+ * @desc Bind form's submit event to use ajaxSubmit
171
+ *
172
+ *
173
+ * @name ajaxSubmit
174
+ * @type jQuery
175
+ * @param options object literal containing options which control the form submission process
176
+ * @cat Plugins/Form
177
+ * @return jQuery
178
+ */
179
+ $.fn.ajaxSubmit = function(options) {
180
+ if (typeof options == 'function')
181
+ options = { success: options };
182
+
183
+ options = $.extend({
184
+ url: this.attr('action') || window.location.toString(),
185
+ type: this.attr('method') || 'GET'
186
+ }, options || {});
187
+
188
+ // hook for manipulating the form data before it is extracted;
189
+ // convenient for use with rich editors like tinyMCE or FCKEditor
190
+ var veto = {};
191
+ this.trigger('form-pre-serialize', [this, options, veto]);
192
+ if (veto.veto) return this;
193
+
194
+ var a = this.formToArray(options.semantic);
195
+ if (options.data) {
196
+ options.extraData = options.data;
197
+ for (var n in options.data)
198
+ a.push( { name: n, value: options.data[n] } );
199
+ }
200
+
201
+ // give pre-submit callback an opportunity to abort the submit
202
+ if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) return this;
203
+
204
+ // fire vetoable 'validate' event
205
+ this.trigger('form-submit-validate', [a, this, options, veto]);
206
+ if (veto.veto) return this;
207
+
208
+ var q = $.param(a);
209
+
210
+ if (options.type.toUpperCase() == 'GET') {
211
+ options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
212
+ options.data = null; // data is null for 'get'
213
+ }
214
+ else
215
+ options.data = q; // data is the query string for 'post'
216
+
217
+ var $form = this, callbacks = [];
218
+ if (options.resetForm) callbacks.push(function() { $form.resetForm(); });
219
+ if (options.clearForm) callbacks.push(function() { $form.clearForm(); });
220
+
221
+ // perform a load on the target only if dataType is not provided
222
+ if (!options.dataType && options.target) {
223
+ var oldSuccess = options.success || function(){};
224
+ callbacks.push(function(data) {
225
+ $(options.target).html(data).each(oldSuccess, arguments);
226
+ });
227
+ }
228
+ else if (options.success)
229
+ callbacks.push(options.success);
230
+
231
+ options.success = function(data, status) {
232
+ for (var i=0, max=callbacks.length; i < max; i++)
233
+ callbacks[i](data, status, $form);
234
+ };
235
+
236
+ // are there files to upload?
237
+ var files = $('input:file', this).fieldValue();
238
+ var found = false;
239
+ for (var j=0; j < files.length; j++)
240
+ if (files[j])
241
+ found = true;
242
+
243
+ // options.iframe allows user to force iframe mode
244
+ if (options.iframe || found) {
245
+ // hack to fix Safari hang (thanks to Tim Molendijk for this)
246
+ // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
247
+ if ($.browser.safari && options.closeKeepAlive)
248
+ $.get(options.closeKeepAlive, fileUpload);
249
+ else
250
+ fileUpload();
251
+ }
252
+ else
253
+ $.ajax(options);
254
+
255
+ // fire 'notify' event
256
+ this.trigger('form-submit-notify', [this, options]);
257
+ return this;
258
+
259
+
260
+ // private function for handling file uploads (hat tip to YAHOO!)
261
+ function fileUpload() {
262
+ var form = $form[0];
263
+ var opts = $.extend({}, $.ajaxSettings, options);
264
+
265
+ var id = 'jqFormIO' + (new Date().getTime());
266
+ var $io = $('<iframe id="' + id + '" name="' + id + '" />');
267
+ var io = $io[0];
268
+ var op8 = $.browser.opera && window.opera.version() < 9;
269
+ if ($.browser.msie || op8) io.src = 'javascript:false;document.write("");';
270
+ $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
271
+
272
+ var xhr = { // mock object
273
+ responseText: null,
274
+ responseXML: null,
275
+ status: 0,
276
+ statusText: 'n/a',
277
+ getAllResponseHeaders: function() {},
278
+ getResponseHeader: function() {},
279
+ setRequestHeader: function() {}
280
+ };
281
+
282
+ var g = opts.global;
283
+ // trigger ajax global events so that activity/block indicators work like normal
284
+ if (g && ! $.active++) $.event.trigger("ajaxStart");
285
+ if (g) $.event.trigger("ajaxSend", [xhr, opts]);
286
+
287
+ var cbInvoked = 0;
288
+ var timedOut = 0;
289
+
290
+ // take a breath so that pending repaints get some cpu time before the upload starts
291
+ setTimeout(function() {
292
+ // make sure form attrs are set
293
+ var t = $form.attr('target'), a = $form.attr('action');
294
+ $form.attr({
295
+ target: id,
296
+ encoding: 'multipart/form-data',
297
+ enctype: 'multipart/form-data',
298
+ method: 'POST',
299
+ action: opts.url
300
+ });
301
+
302
+ // support timout
303
+ if (opts.timeout)
304
+ setTimeout(function() { timedOut = true; cb(); }, opts.timeout);
305
+
306
+ // add "extra" data to form if provided in options
307
+ var extraInputs = [];
308
+ try {
309
+ if (options.extraData)
310
+ for (var n in options.extraData)
311
+ extraInputs.push(
312
+ $('<input type="hidden" name="'+n+'" value="'+options.extraData[n]+'" />')
313
+ .appendTo(form)[0]);
314
+
315
+ // add iframe to doc and submit the form
316
+ $io.appendTo('body');
317
+ io.attachEvent ? io.attachEvent('onload', cb) : io.addEventListener('load', cb, false);
318
+ form.submit();
319
+ }
320
+ finally {
321
+ // reset attrs and remove "extra" input elements
322
+ $form.attr('action', a);
323
+ t ? $form.attr('target', t) : $form.removeAttr('target');
324
+ $(extraInputs).remove();
325
+ }
326
+ }, 10);
327
+
328
+ function cb() {
329
+ if (cbInvoked++) return;
330
+
331
+ io.detachEvent ? io.detachEvent('onload', cb) : io.removeEventListener('load', cb, false);
332
+
333
+ var ok = true;
334
+ try {
335
+ if (timedOut) throw 'timeout';
336
+ // extract the server response from the iframe
337
+ var data, doc;
338
+ doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document;
339
+ xhr.responseText = doc.body ? doc.body.innerHTML : null;
340
+ xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
341
+ xhr.getResponseHeader = function(header){
342
+ var headers = {'content-type': opts.dataType};
343
+ return headers[header];
344
+ };
345
+
346
+ if (opts.dataType == 'json' || opts.dataType == 'script') {
347
+ var ta = doc.getElementsByTagName('textarea')[0];
348
+ xhr.responseText = ta ? ta.value : xhr.responseText;
349
+ }
350
+ else if (opts.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) {
351
+ xhr.responseXML = toXml(xhr.responseText);
352
+ }
353
+ data = $.httpData(xhr, opts.dataType);
354
+ }
355
+ catch(e){
356
+ ok = false;
357
+ $.handleError(opts, xhr, 'error', e);
358
+ }
359
+
360
+ // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
361
+ if (ok) {
362
+ opts.success(data, 'success');
363
+ if (g) $.event.trigger("ajaxSuccess", [xhr, opts]);
364
+ }
365
+ if (g) $.event.trigger("ajaxComplete", [xhr, opts]);
366
+ if (g && ! --$.active) $.event.trigger("ajaxStop");
367
+ if (opts.complete) opts.complete(xhr, ok ? 'success' : 'error');
368
+
369
+ // clean up
370
+ setTimeout(function() {
371
+ $io.remove();
372
+ xhr.responseXML = null;
373
+ }, 100);
374
+ };
375
+
376
+ function toXml(s, doc) {
377
+ if (window.ActiveXObject) {
378
+ doc = new ActiveXObject('Microsoft.XMLDOM');
379
+ doc.async = 'false';
380
+ doc.loadXML(s);
381
+ }
382
+ else
383
+ doc = (new DOMParser()).parseFromString(s, 'text/xml');
384
+ return (doc && doc.documentElement && doc.documentElement.tagName != 'parsererror') ? doc : null;
385
+ };
386
+ };
387
+ };
388
+
389
+ /**
390
+ * ajaxForm() provides a mechanism for fully automating form submission.
391
+ *
392
+ * The advantages of using this method instead of ajaxSubmit() are:
393
+ *
394
+ * 1: This method will include coordinates for <input type="image" /> elements (if the element
395
+ * is used to submit the form).
396
+ * 2. This method will include the submit element's name/value data (for the element that was
397
+ * used to submit the form).
398
+ * 3. This method binds the submit() method to the form for you.
399
+ *
400
+ * Note that for accurate x/y coordinates of image submit elements in all browsers
401
+ * you need to also use the "dimensions" plugin (this method will auto-detect its presence).
402
+ *
403
+ * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely
404
+ * passes the options argument along after properly binding events for submit elements and
405
+ * the form itself. See ajaxSubmit for a full description of the options argument.
406
+ *
407
+ *
408
+ * @example
409
+ * var options = {
410
+ * target: '#myTargetDiv'
411
+ * };
412
+ * $('#myForm').ajaxSForm(options);
413
+ * @desc Bind form's submit event so that 'myTargetDiv' is updated with the server response
414
+ * when the form is submitted.
415
+ *
416
+ *
417
+ * @example
418
+ * var options = {
419
+ * success: function(responseText) {
420
+ * alert(responseText);
421
+ * }
422
+ * };
423
+ * $('#myForm').ajaxSubmit(options);
424
+ * @desc Bind form's submit event so that server response is alerted after the form is submitted.
425
+ *
426
+ *
427
+ * @example
428
+ * var options = {
429
+ * beforeSubmit: function(formArray, jqForm) {
430
+ * if (formArray.length == 0) {
431
+ * alert('Please enter data.');
432
+ * return false;
433
+ * }
434
+ * }
435
+ * };
436
+ * $('#myForm').ajaxSubmit(options);
437
+ * @desc Bind form's submit event so that pre-submit callback is invoked before the form
438
+ * is submitted.
439
+ *
440
+ *
441
+ * @name ajaxForm
442
+ * @param options object literal containing options which control the form submission process
443
+ * @return jQuery
444
+ * @cat Plugins/Form
445
+ * @type jQuery
446
+ */
447
+ $.fn.ajaxForm = function(options) {
448
+ return this.ajaxFormUnbind().bind('submit.form-plugin',function() {
449
+ $(this).ajaxSubmit(options);
450
+ return false;
451
+ }).each(function() {
452
+ // store options in hash
453
+ $(":submit,input:image", this).bind('click.form-plugin',function(e) {
454
+ var $form = this.form;
455
+ $form.clk = this;
456
+ if (this.type == 'image') {
457
+ if (e.offsetX != undefined) {
458
+ $form.clk_x = e.offsetX;
459
+ $form.clk_y = e.offsetY;
460
+ } else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin
461
+ var offset = $(this).offset();
462
+ $form.clk_x = e.pageX - offset.left;
463
+ $form.clk_y = e.pageY - offset.top;
464
+ } else {
465
+ $form.clk_x = e.pageX - this.offsetLeft;
466
+ $form.clk_y = e.pageY - this.offsetTop;
467
+ }
468
+ }
469
+ // clear form vars
470
+ setTimeout(function() { $form.clk = $form.clk_x = $form.clk_y = null; }, 10);
471
+ });
472
+ });
473
+ };
474
+
475
+
476
+ /**
477
+ * ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
478
+ *
479
+ * @name ajaxFormUnbind
480
+ * @return jQuery
481
+ * @cat Plugins/Form
482
+ * @type jQuery
483
+ */
484
+ $.fn.ajaxFormUnbind = function() {
485
+ this.unbind('submit.form-plugin');
486
+ return this.each(function() {
487
+ $(":submit,input:image", this).unbind('click.form-plugin');
488
+ });
489
+
490
+ };
491
+
492
+ /**
493
+ * formToArray() gathers form element data into an array of objects that can
494
+ * be passed to any of the following ajax functions: $.get, $.post, or load.
495
+ * Each object in the array has both a 'name' and 'value' property. An example of
496
+ * an array for a simple login form might be:
497
+ *
498
+ * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
499
+ *
500
+ * It is this array that is passed to pre-submit callback functions provided to the
501
+ * ajaxSubmit() and ajaxForm() methods.
502
+ *
503
+ * The semantic argument can be used to force form serialization in semantic order.
504
+ * This is normally true anyway, unless the form contains input elements of type='image'.
505
+ * If your form must be submitted with name/value pairs in semantic order and your form
506
+ * contains an input of type='image" then pass true for this arg, otherwise pass false
507
+ * (or nothing) to avoid the overhead for this logic.
508
+ *
509
+ * @example var data = $("#myForm").formToArray();
510
+ * $.post( "myscript.cgi", data );
511
+ * @desc Collect all the data from a form and submit it to the server.
512
+ *
513
+ * @name formToArray
514
+ * @param semantic true if serialization must maintain strict semantic ordering of elements (slower)
515
+ * @type Array<Object>
516
+ * @cat Plugins/Form
517
+ */
518
+ $.fn.formToArray = function(semantic) {
519
+ var a = [];
520
+ if (this.length == 0) return a;
521
+
522
+ var form = this[0];
523
+ var els = semantic ? form.getElementsByTagName('*') : form.elements;
524
+ if (!els) return a;
525
+ for(var i=0, max=els.length; i < max; i++) {
526
+ var el = els[i];
527
+ var n = el.name;
528
+ if (!n) continue;
529
+
530
+ if (semantic && form.clk && el.type == "image") {
531
+ // handle image inputs on the fly when semantic == true
532
+ if(!el.disabled && form.clk == el)
533
+ a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
534
+ continue;
535
+ }
536
+
537
+ var v = $.fieldValue(el, true);
538
+ if (v && v.constructor == Array) {
539
+ for(var j=0, jmax=v.length; j < jmax; j++)
540
+ a.push({name: n, value: v[j]});
541
+ }
542
+ else if (v !== null && typeof v != 'undefined')
543
+ a.push({name: n, value: v});
544
+ }
545
+
546
+ if (!semantic && form.clk) {
547
+ // input type=='image' are not found in elements array! handle them here
548
+ var inputs = form.getElementsByTagName("input");
549
+ for(var i=0, max=inputs.length; i < max; i++) {
550
+ var input = inputs[i];
551
+ var n = input.name;
552
+ if(n && !input.disabled && input.type == "image" && form.clk == input)
553
+ a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
554
+ }
555
+ }
556
+ return a;
557
+ };
558
+
559
+
560
+ /**
561
+ * Serializes form data into a 'submittable' string. This method will return a string
562
+ * in the format: name1=value1&amp;name2=value2
563
+ *
564
+ * The semantic argument can be used to force form serialization in semantic order.
565
+ * If your form must be submitted with name/value pairs in semantic order then pass
566
+ * true for this arg, otherwise pass false (or nothing) to avoid the overhead for
567
+ * this logic (which can be significant for very large forms).
568
+ *
569
+ * @example var data = $("#myForm").formSerialize();
570
+ * $.ajax('POST', "myscript.cgi", data);
571
+ * @desc Collect all the data from a form into a single string
572
+ *
573
+ * @name formSerialize
574
+ * @param semantic true if serialization must maintain strict semantic ordering of elements (slower)
575
+ * @type String
576
+ * @cat Plugins/Form
577
+ */
578
+ $.fn.formSerialize = function(semantic) {
579
+ //hand off to jQuery.param for proper encoding
580
+ return $.param(this.formToArray(semantic));
581
+ };
582
+
583
+
584
+ /**
585
+ * Serializes all field elements in the jQuery object into a query string.
586
+ * This method will return a string in the format: name1=value1&amp;name2=value2
587
+ *
588
+ * The successful argument controls whether or not serialization is limited to
589
+ * 'successful' controls (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
590
+ * The default value of the successful argument is true.
591
+ *
592
+ * @example var data = $("input").fieldSerialize();
593
+ * @desc Collect the data from all successful input elements into a query string
594
+ *
595
+ * @example var data = $(":radio").fieldSerialize();
596
+ * @desc Collect the data from all successful radio input elements into a query string
597
+ *
598
+ * @example var data = $("#myForm :checkbox").fieldSerialize();
599
+ * @desc Collect the data from all successful checkbox input elements in myForm into a query string
600
+ *
601
+ * @example var data = $("#myForm :checkbox").fieldSerialize(false);
602
+ * @desc Collect the data from all checkbox elements in myForm (even the unchecked ones) into a query string
603
+ *
604
+ * @example var data = $(":input").fieldSerialize();
605
+ * @desc Collect the data from all successful input, select, textarea and button elements into a query string
606
+ *
607
+ * @name fieldSerialize
608
+ * @param successful true if only successful controls should be serialized (default is true)
609
+ * @type String
610
+ * @cat Plugins/Form
611
+ */
612
+ $.fn.fieldSerialize = function(successful) {
613
+ var a = [];
614
+ this.each(function() {
615
+ var n = this.name;
616
+ if (!n) return;
617
+ var v = $.fieldValue(this, successful);
618
+ if (v && v.constructor == Array) {
619
+ for (var i=0,max=v.length; i < max; i++)
620
+ a.push({name: n, value: v[i]});
621
+ }
622
+ else if (v !== null && typeof v != 'undefined')
623
+ a.push({name: this.name, value: v});
624
+ });
625
+ //hand off to jQuery.param for proper encoding
626
+ return $.param(a);
627
+ };
628
+
629
+
630
+ /**
631
+ * Returns the value(s) of the element in the matched set. For example, consider the following form:
632
+ *
633
+ * <form><fieldset>
634
+ * <input name="A" type="text" />
635
+ * <input name="A" type="text" />
636
+ * <input name="B" type="checkbox" value="B1" />
637
+ * <input name="B" type="checkbox" value="B2"/>
638
+ * <input name="C" type="radio" value="C1" />
639
+ * <input name="C" type="radio" value="C2" />
640
+ * </fieldset></form>
641
+ *
642
+ * var v = $(':text').fieldValue();
643
+ * // if no values are entered into the text inputs
644
+ * v == ['','']
645
+ * // if values entered into the text inputs are 'foo' and 'bar'
646
+ * v == ['foo','bar']
647
+ *
648
+ * var v = $(':checkbox').fieldValue();
649
+ * // if neither checkbox is checked
650
+ * v === undefined
651
+ * // if both checkboxes are checked
652
+ * v == ['B1', 'B2']
653
+ *
654
+ * var v = $(':radio').fieldValue();
655
+ * // if neither radio is checked
656
+ * v === undefined
657
+ * // if first radio is checked
658
+ * v == ['C1']
659
+ *
660
+ * The successful argument controls whether or not the field element must be 'successful'
661
+ * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
662
+ * The default value of the successful argument is true. If this value is false the value(s)
663
+ * for each element is returned.
664
+ *
665
+ * Note: This method *always* returns an array. If no valid value can be determined the
666
+ * array will be empty, otherwise it will contain one or more values.
667
+ *
668
+ * @example var data = $("#myPasswordElement").fieldValue();
669
+ * alert(data[0]);
670
+ * @desc Alerts the current value of the myPasswordElement element
671
+ *
672
+ * @example var data = $("#myForm :input").fieldValue();
673
+ * @desc Get the value(s) of the form elements in myForm
674
+ *
675
+ * @example var data = $("#myForm :checkbox").fieldValue();
676
+ * @desc Get the value(s) for the successful checkbox element(s) in the jQuery object.
677
+ *
678
+ * @example var data = $("#mySingleSelect").fieldValue();
679
+ * @desc Get the value(s) of the select control
680
+ *
681
+ * @example var data = $(':text').fieldValue();
682
+ * @desc Get the value(s) of the text input or textarea elements
683
+ *
684
+ * @example var data = $("#myMultiSelect").fieldValue();
685
+ * @desc Get the values for the select-multiple control
686
+ *
687
+ * @name fieldValue
688
+ * @param Boolean successful true if only the values for successful controls should be returned (default is true)
689
+ * @type Array<String>
690
+ * @cat Plugins/Form
691
+ */
692
+ $.fn.fieldValue = function(successful) {
693
+ for (var val=[], i=0, max=this.length; i < max; i++) {
694
+ var el = this[i];
695
+ var v = $.fieldValue(el, successful);
696
+ if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length))
697
+ continue;
698
+ v.constructor == Array ? $.merge(val, v) : val.push(v);
699
+ }
700
+ return val;
701
+ };
702
+
703
+ /**
704
+ * Returns the value of the field element.
705
+ *
706
+ * The successful argument controls whether or not the field element must be 'successful'
707
+ * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
708
+ * The default value of the successful argument is true. If the given element is not
709
+ * successful and the successful arg is not false then the returned value will be null.
710
+ *
711
+ * Note: If the successful flag is true (default) but the element is not successful, the return will be null
712
+ * Note: The value returned for a successful select-multiple element will always be an array.
713
+ * Note: If the element has no value the return value will be undefined.
714
+ *
715
+ * @example var data = jQuery.fieldValue($("#myPasswordElement")[0]);
716
+ * @desc Gets the current value of the myPasswordElement element
717
+ *
718
+ * @name fieldValue
719
+ * @param Element el The DOM element for which the value will be returned
720
+ * @param Boolean successful true if value returned must be for a successful controls (default is true)
721
+ * @type String or Array<String> or null or undefined
722
+ * @cat Plugins/Form
723
+ */
724
+ $.fieldValue = function(el, successful) {
725
+ var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
726
+ if (typeof successful == 'undefined') successful = true;
727
+
728
+ if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
729
+ (t == 'checkbox' || t == 'radio') && !el.checked ||
730
+ (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
731
+ tag == 'select' && el.selectedIndex == -1))
732
+ return null;
733
+
734
+ if (tag == 'select') {
735
+ var index = el.selectedIndex;
736
+ if (index < 0) return null;
737
+ var a = [], ops = el.options;
738
+ var one = (t == 'select-one');
739
+ var max = (one ? index+1 : ops.length);
740
+ for(var i=(one ? index : 0); i < max; i++) {
741
+ var op = ops[i];
742
+ if (op.selected) {
743
+ // extra pain for IE...
744
+ var v = $.browser.msie && !(op.attributes['value'].specified) ? op.text : op.value;
745
+ if (one) return v;
746
+ a.push(v);
747
+ }
748
+ }
749
+ return a;
750
+ }
751
+ return el.value;
752
+ };
753
+
754
+
755
+ /**
756
+ * Clears the form data. Takes the following actions on the form's input fields:
757
+ * - input text fields will have their 'value' property set to the empty string
758
+ * - select elements will have their 'selectedIndex' property set to -1
759
+ * - checkbox and radio inputs will have their 'checked' property set to false
760
+ * - inputs of type submit, button, reset, and hidden will *not* be effected
761
+ * - button elements will *not* be effected
762
+ *
763
+ * @example $('form').clearForm();
764
+ * @desc Clears all forms on the page.
765
+ *
766
+ * @name clearForm
767
+ * @type jQuery
768
+ * @cat Plugins/Form
769
+ */
770
+ $.fn.clearForm = function() {
771
+ return this.each(function() {
772
+ $('input,select,textarea', this).clearFields();
773
+ });
774
+ };
775
+
776
+ /**
777
+ * Clears the selected form elements. Takes the following actions on the matched elements:
778
+ * - input text fields will have their 'value' property set to the empty string
779
+ * - select elements will have their 'selectedIndex' property set to -1
780
+ * - checkbox and radio inputs will have their 'checked' property set to false
781
+ * - inputs of type submit, button, reset, and hidden will *not* be effected
782
+ * - button elements will *not* be effected
783
+ *
784
+ * @example $('.myInputs').clearFields();
785
+ * @desc Clears all inputs with class myInputs
786
+ *
787
+ * @name clearFields
788
+ * @type jQuery
789
+ * @cat Plugins/Form
790
+ */
791
+ $.fn.clearFields = $.fn.clearInputs = function() {
792
+ return this.each(function() {
793
+ var t = this.type, tag = this.tagName.toLowerCase();
794
+ if (t == 'text' || t == 'password' || tag == 'textarea')
795
+ this.value = '';
796
+ else if (t == 'checkbox' || t == 'radio')
797
+ this.checked = false;
798
+ else if (tag == 'select')
799
+ this.selectedIndex = -1;
800
+ });
801
+ };
802
+
803
+
804
+ /**
805
+ * Resets the form data. Causes all form elements to be reset to their original value.
806
+ *
807
+ * @example $('form').resetForm();
808
+ * @desc Resets all forms on the page.
809
+ *
810
+ * @name resetForm
811
+ * @type jQuery
812
+ * @cat Plugins/Form
813
+ */
814
+ $.fn.resetForm = function() {
815
+ return this.each(function() {
816
+ // guard against an input with the name of 'reset'
817
+ // note that IE reports the reset function as an 'object'
818
+ if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType))
819
+ this.reset();
820
+ });
821
+ };
822
+
823
+
824
+ /**
825
+ * Enables or disables any matching elements.
826
+ *
827
+ * @example $(':radio').enabled(false);
828
+ * @desc Disables all radio buttons
829
+ *
830
+ * @name select
831
+ * @type jQuery
832
+ * @cat Plugins/Form
833
+ */
834
+ $.fn.enable = function(b) {
835
+ if (b == undefined) b = true;
836
+ return this.each(function() {
837
+ this.disabled = !b
838
+ });
839
+ };
840
+
841
+ /**
842
+ * Checks/unchecks any matching checkboxes or radio buttons and
843
+ * selects/deselects and matching option elements.
844
+ *
845
+ * @example $(':checkbox').select();
846
+ * @desc Checks all checkboxes
847
+ *
848
+ * @name select
849
+ * @type jQuery
850
+ * @cat Plugins/Form
851
+ */
852
+ $.fn.select = function(select) {
853
+ if (select == undefined) select = true;
854
+ return this.each(function() {
855
+ var t = this.type;
856
+ if (t == 'checkbox' || t == 'radio')
857
+ this.checked = select;
858
+ else if (this.tagName.toLowerCase() == 'option') {
859
+ var $sel = $(this).parent('select');
860
+ if (select && $sel[0] && $sel[0].type == 'select-one') {
861
+ // deselect all other options
862
+ $sel.find('option').select(false);
863
+ }
864
+ this.selected = select;
865
+ }
866
+ });
867
+ };
868
+
869
+ })(jQuery);