telephony 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (365) hide show
  1. checksums.yaml +15 -0
  2. data/README.md +105 -0
  3. data/Rakefile +39 -0
  4. data/app/assets/fonts/telephony/zest-telephony.eot +0 -0
  5. data/app/assets/fonts/telephony/zest-telephony.svg +73 -0
  6. data/app/assets/fonts/telephony/zest-telephony.ttf +0 -0
  7. data/app/assets/fonts/telephony/zest-telephony.woff +0 -0
  8. data/app/assets/images/telephony/backspace.png +0 -0
  9. data/app/assets/images/telephony/icon-spinner.gif +0 -0
  10. data/app/assets/javascripts/telephony/application.js +12 -0
  11. data/app/assets/javascripts/telephony/collections/agents.js +4 -0
  12. data/app/assets/javascripts/telephony/config.js.erb +11 -0
  13. data/app/assets/javascripts/telephony/models/agent.js +103 -0
  14. data/app/assets/javascripts/telephony/models/conversation.js +226 -0
  15. data/app/assets/javascripts/telephony/models/device.js +30 -0
  16. data/app/assets/javascripts/telephony/models/transfer.js +65 -0
  17. data/app/assets/javascripts/telephony/namespace.js +6 -0
  18. data/app/assets/javascripts/telephony/push.js +80 -0
  19. data/app/assets/javascripts/telephony/vendor/backbone.js +1159 -0
  20. data/app/assets/javascripts/telephony/vendor/chosen.jquery.js +1018 -0
  21. data/app/assets/javascripts/telephony/vendor/jquery.form.js +1117 -0
  22. data/app/assets/javascripts/telephony/vendor/pusher.js +1304 -0
  23. data/app/assets/javascripts/telephony/vendor/test_dependencies.js +18 -0
  24. data/app/assets/javascripts/telephony/vendor/underscore.js +839 -0
  25. data/app/assets/javascripts/telephony/views/agents_view.js +69 -0
  26. data/app/assets/javascripts/telephony/views/application_view.js +78 -0
  27. data/app/assets/javascripts/telephony/views/call_queue_view.js +21 -0
  28. data/app/assets/javascripts/telephony/views/conversation_buttons_view.js +15 -0
  29. data/app/assets/javascripts/telephony/views/conversation_view.js +322 -0
  30. data/app/assets/javascripts/telephony/views/status_view.js +36 -0
  31. data/app/assets/javascripts/telephony/views/transfer_view.js +87 -0
  32. data/app/assets/javascripts/telephony/views/twilio_client_view.js +117 -0
  33. data/app/assets/javascripts/telephony/views/widget_view.js +55 -0
  34. data/app/assets/javascripts/telephony/widget.js +5 -0
  35. data/app/assets/javascripts/templates/telephony/agents_view.jst.ejs +8 -0
  36. data/app/assets/javascripts/templates/telephony/call_queue_view.jst.ejs +2 -0
  37. data/app/assets/javascripts/templates/telephony/conversation_buttons_view.jst.ejs +23 -0
  38. data/app/assets/javascripts/templates/telephony/conversation_view.jst.ejs +10 -0
  39. data/app/assets/javascripts/templates/telephony/status_view.jst.ejs +4 -0
  40. data/app/assets/javascripts/templates/telephony/transfer_view.jst.ejs +38 -0
  41. data/app/assets/javascripts/templates/telephony/twilio_client_view.jst.ejs +10 -0
  42. data/app/assets/stylesheets/telephony/font.css +43 -0
  43. data/app/assets/stylesheets/telephony/reset.css.scss +10 -0
  44. data/app/assets/stylesheets/telephony/widget.css.scss +512 -0
  45. data/app/controllers/telephony/agents_controller.rb +46 -0
  46. data/app/controllers/telephony/application_controller.rb +4 -0
  47. data/app/controllers/telephony/call_centers_controller.rb +9 -0
  48. data/app/controllers/telephony/conversations_controller.rb +74 -0
  49. data/app/controllers/telephony/inbound/conversation_queues_controller.rb +21 -0
  50. data/app/controllers/telephony/playable_listeners_controller.rb +43 -0
  51. data/app/controllers/telephony/providers/twilio/application_controller.rb +26 -0
  52. data/app/controllers/telephony/providers/twilio/calls_controller.rb +130 -0
  53. data/app/controllers/telephony/providers/twilio/inbound_calls_controller.rb +54 -0
  54. data/app/controllers/telephony/providers/twilio/musics_controller.rb +12 -0
  55. data/app/controllers/telephony/providers/twilio/voicemails_controller.rb +29 -0
  56. data/app/controllers/telephony/signals/agents/presences_controller.rb +64 -0
  57. data/app/controllers/telephony/transfers_controller.rb +18 -0
  58. data/app/controllers/telephony/twilio_client_controller.rb +37 -0
  59. data/app/controllers/telephony/voicemails_controller.rb +13 -0
  60. data/app/controllers/telephony/widget_controller.rb +10 -0
  61. data/app/helpers/telephony/application_helper.rb +4 -0
  62. data/app/helpers/telephony/calls_helper.rb +13 -0
  63. data/app/models/telephony/agent.rb +186 -0
  64. data/app/models/telephony/agent_state_machine.rb +54 -0
  65. data/app/models/telephony/base.rb +7 -0
  66. data/app/models/telephony/blacklisted_number.rb +5 -0
  67. data/app/models/telephony/call.rb +94 -0
  68. data/app/models/telephony/call_center.rb +26 -0
  69. data/app/models/telephony/call_state_machine.rb +98 -0
  70. data/app/models/telephony/conversation.rb +273 -0
  71. data/app/models/telephony/conversation_state_machine.rb +109 -0
  72. data/app/models/telephony/conversations_presenter.rb +83 -0
  73. data/app/models/telephony/events.rb +6 -0
  74. data/app/models/telephony/events/answer.rb +6 -0
  75. data/app/models/telephony/events/base.rb +118 -0
  76. data/app/models/telephony/events/busy.rb +6 -0
  77. data/app/models/telephony/events/call_answered.rb +22 -0
  78. data/app/models/telephony/events/call_fail.rb +6 -0
  79. data/app/models/telephony/events/complete_hold.rb +28 -0
  80. data/app/models/telephony/events/complete_one_step_transfer.rb +17 -0
  81. data/app/models/telephony/events/complete_resume.rb +28 -0
  82. data/app/models/telephony/events/complete_two_step_transfer.rb +6 -0
  83. data/app/models/telephony/events/conference.rb +6 -0
  84. data/app/models/telephony/events/connect.rb +22 -0
  85. data/app/models/telephony/events/customer_left_two_step_transfer.rb +6 -0
  86. data/app/models/telephony/events/dial_agent.rb +6 -0
  87. data/app/models/telephony/events/ended.rb +19 -0
  88. data/app/models/telephony/events/enqueue.rb +6 -0
  89. data/app/models/telephony/events/fail_one_step_transfer.rb +9 -0
  90. data/app/models/telephony/events/fail_two_step_transfer.rb +6 -0
  91. data/app/models/telephony/events/initialize_widget.rb +15 -0
  92. data/app/models/telephony/events/initiate_hold.rb +6 -0
  93. data/app/models/telephony/events/initiate_one_step_transfer.rb +6 -0
  94. data/app/models/telephony/events/initiate_resume.rb +6 -0
  95. data/app/models/telephony/events/initiate_two_step_transfer.rb +6 -0
  96. data/app/models/telephony/events/leave_two_step_transfer.rb +17 -0
  97. data/app/models/telephony/events/leave_voicemail.rb +22 -0
  98. data/app/models/telephony/events/no_answer.rb +6 -0
  99. data/app/models/telephony/events/play_closed_greeting.rb +6 -0
  100. data/app/models/telephony/events/play_message.rb +6 -0
  101. data/app/models/telephony/events/reject.rb +6 -0
  102. data/app/models/telephony/events/rona.rb +6 -0
  103. data/app/models/telephony/events/start.rb +17 -0
  104. data/app/models/telephony/events/straight_to_voicemail.rb +6 -0
  105. data/app/models/telephony/events/terminate.rb +6 -0
  106. data/app/models/telephony/events/transfer.rb +35 -0
  107. data/app/models/telephony/inbound_conversation_queue.rb +101 -0
  108. data/app/models/telephony/playable.rb +7 -0
  109. data/app/models/telephony/playable_listener.rb +42 -0
  110. data/app/models/telephony/pusher_event_publisher.rb +26 -0
  111. data/app/models/telephony/recording.rb +4 -0
  112. data/app/models/telephony/voicemail.rb +36 -0
  113. data/app/observers/telephony/agent_observer.rb +8 -0
  114. data/app/observers/telephony/call_observer.rb +15 -0
  115. data/app/observers/telephony/conversation_observer.rb +9 -0
  116. data/app/observers/telephony/event_observer.rb +9 -0
  117. data/app/views/layouts/telephony/application.html.erb +14 -0
  118. data/app/views/telephony/providers/twilio/calls/child_detached.builder +5 -0
  119. data/app/views/telephony/providers/twilio/calls/complete_hold.builder +6 -0
  120. data/app/views/telephony/providers/twilio/calls/connect.builder +13 -0
  121. data/app/views/telephony/providers/twilio/calls/dial.builder +14 -0
  122. data/app/views/telephony/providers/twilio/calls/done.builder +5 -0
  123. data/app/views/telephony/providers/twilio/calls/join_conference.builder +7 -0
  124. data/app/views/telephony/providers/twilio/calls/whisper_tone.builder +3 -0
  125. data/app/views/telephony/providers/twilio/inbound_calls/closed_hours.builder +7 -0
  126. data/app/views/telephony/providers/twilio/inbound_calls/create.builder +8 -0
  127. data/app/views/telephony/providers/twilio/inbound_calls/enqueue.builder +8 -0
  128. data/app/views/telephony/providers/twilio/inbound_calls/reject.builder +5 -0
  129. data/app/views/telephony/providers/twilio/inbound_calls/wait_music.builder +5 -0
  130. data/app/views/telephony/providers/twilio/musics/hold.builder +5 -0
  131. data/app/views/telephony/providers/twilio/voicemails/new.builder +6 -0
  132. data/app/views/telephony/twilio_client/index.html.erb +74 -0
  133. data/app/views/telephony/widget/index.erb +3 -0
  134. data/config/cucumber.yml +8 -0
  135. data/config/database.yml +57 -0
  136. data/config/environment.rb +67 -0
  137. data/config/initializers/pusher.rb +21 -0
  138. data/config/initializers/telephony.rb +44 -0
  139. data/config/routes.rb +94 -0
  140. data/config/wopr.yml.example +38 -0
  141. data/db/migrate/20130806213053_bootstrap_db.rb +105 -0
  142. data/db/migrate/20131009204026_create_telephony_blacklisted_numbers.rb +11 -0
  143. data/lib/agent_generator.rb +24 -0
  144. data/lib/tasks/cucumber.rake +65 -0
  145. data/lib/tasks/jasmine.rake +8 -0
  146. data/lib/tasks/telephony_tasks.rake +4 -0
  147. data/lib/telephony.rb +34 -0
  148. data/lib/telephony/concerns/controllers/twilio_request_verifier.rb +39 -0
  149. data/lib/telephony/conversation_data.rb +67 -0
  150. data/lib/telephony/engine.rb +19 -0
  151. data/lib/telephony/error.rb +8 -0
  152. data/lib/telephony/error/agent_on_a_call.rb +2 -0
  153. data/lib/telephony/error/base.rb +3 -0
  154. data/lib/telephony/error/connection.rb +2 -0
  155. data/lib/telephony/error/not_in_progress.rb +2 -0
  156. data/lib/telephony/error/queue_empty.rb +2 -0
  157. data/lib/telephony/helper.rb +11 -0
  158. data/lib/telephony/jobs/agent_offline.rb +28 -0
  159. data/lib/telephony/jobs/pusher_event.rb +21 -0
  160. data/lib/telephony/providers/twilio_provider.rb +162 -0
  161. data/lib/telephony/version.rb +3 -0
  162. data/spec/controllers/telephony/agents_controller_spec.rb +117 -0
  163. data/spec/controllers/telephony/call_centers_controller_spec.rb +25 -0
  164. data/spec/controllers/telephony/conversations_controller_spec.rb +229 -0
  165. data/spec/controllers/telephony/playable_listeners_controller_spec.rb +138 -0
  166. data/spec/controllers/telephony/providers/twilio/calls_controller_spec.rb +33 -0
  167. data/spec/controllers/telephony/providers/twilio/musics_controller_spec.rb +20 -0
  168. data/spec/controllers/telephony/signals/agents/presences_controller_spec.rb +154 -0
  169. data/spec/controllers/telephony/twilio_client_controller_spec.rb +58 -0
  170. data/spec/controllers/telephony/widget_controller_spec.rb +25 -0
  171. data/spec/dummy/Rakefile +7 -0
  172. data/spec/dummy/app/assets/javascripts/application.js +6 -0
  173. data/spec/dummy/app/assets/javascripts/lib/event_logger.js +82 -0
  174. data/spec/dummy/app/assets/javascripts/vendor/backbone.js +1159 -0
  175. data/spec/dummy/app/assets/javascripts/vendor/chosen.jquery.js +1018 -0
  176. data/spec/dummy/app/assets/javascripts/vendor/jquery.form.js +1117 -0
  177. data/spec/dummy/app/assets/javascripts/vendor/underscore.js +839 -0
  178. data/spec/dummy/app/assets/stylesheets/application.css +23 -0
  179. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  180. data/spec/dummy/app/controllers/widget_host_controller.rb +2 -0
  181. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  182. data/spec/dummy/app/views/layouts/application.html.erb +12 -0
  183. data/spec/dummy/app/views/widget_host/index.html.erb +53 -0
  184. data/spec/dummy/config.ru +4 -0
  185. data/spec/dummy/config/application.rb +61 -0
  186. data/spec/dummy/config/boot.rb +10 -0
  187. data/spec/dummy/config/call_centers.yml +35 -0
  188. data/spec/dummy/config/call_centers.yml.example +35 -0
  189. data/spec/dummy/config/database.yml +56 -0
  190. data/spec/dummy/config/environment.rb +5 -0
  191. data/spec/dummy/config/environments/cucumber.rb +39 -0
  192. data/spec/dummy/config/environments/development.rb +39 -0
  193. data/spec/dummy/config/environments/production.rb +67 -0
  194. data/spec/dummy/config/environments/test.rb +39 -0
  195. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  196. data/spec/dummy/config/initializers/inflections.rb +15 -0
  197. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  198. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  199. data/spec/dummy/config/initializers/session_store.rb +8 -0
  200. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  201. data/spec/dummy/config/locales/en.yml +5 -0
  202. data/spec/dummy/config/pusher.yml +14 -0
  203. data/spec/dummy/config/pusher.yml.example +14 -0
  204. data/spec/dummy/config/routes.rb +5 -0
  205. data/spec/dummy/config/sms_whitelist.json.example +4 -0
  206. data/spec/dummy/config/twilio.yml +57 -0
  207. data/spec/dummy/config/twilio.yml.example +57 -0
  208. data/spec/dummy/db/schema.rb +122 -0
  209. data/spec/dummy/db/seeds.rb +6 -0
  210. data/spec/dummy/log/production.log +156 -0
  211. data/spec/dummy/log/test.log +13812 -0
  212. data/spec/dummy/public/404.html +26 -0
  213. data/spec/dummy/public/422.html +26 -0
  214. data/spec/dummy/public/500.html +25 -0
  215. data/spec/dummy/public/assets/application-628309db7ce8c0abdd91f8f796881599.js +32 -0
  216. data/spec/dummy/public/assets/application-628309db7ce8c0abdd91f8f796881599.js.gz +0 -0
  217. data/spec/dummy/public/assets/application-6c551430f3bdd32ba57c9c41e5f91846.css +24 -0
  218. data/spec/dummy/public/assets/application-6c551430f3bdd32ba57c9c41e5f91846.css.gz +0 -0
  219. data/spec/dummy/public/assets/telephony/backspace-9f5b195e049ed90eb20ecdda6e264b0a.png +0 -0
  220. data/spec/dummy/public/assets/telephony/icon-spinner-ddbae473a70c5425810aeee31d4a8ad7.gif +0 -0
  221. data/spec/dummy/public/assets/telephony/zest-telephony-0e6b13673634da80de9ae09bcca253e3.eot +0 -0
  222. data/spec/dummy/public/assets/telephony/zest-telephony-37f394757ccb11b978f16d9fd32cb3b5.ttf +0 -0
  223. data/spec/dummy/public/assets/telephony/zest-telephony-e7efdbc60c0a1c8404951bc79c7fb3a8.woff +0 -0
  224. data/spec/dummy/public/assets/telephony/zest-telephony-f34287df626936908408f53a33db9e83.svg +73 -0
  225. data/spec/dummy/public/favicon.ico +0 -0
  226. data/spec/dummy/script/rails +6 -0
  227. data/spec/dummy/tmp/cache/assets/C98/020/sprockets%2Fa83f8254d688334ff179984e423f502f +0 -0
  228. data/spec/dummy/tmp/cache/assets/CA4/550/sprockets%2F407f9191fe573f2435c55c017cb0d022 +0 -0
  229. data/spec/dummy/tmp/cache/assets/CAA/6A0/sprockets%2F3265550e2d2c7f5a864ca85d255504c2 +0 -0
  230. data/spec/dummy/tmp/cache/assets/CB0/F60/sprockets%2F462f9efed31e6710732376ae06c27208 +0 -0
  231. data/spec/dummy/tmp/cache/assets/CB6/E20/sprockets%2Fa7092cd9f16917099d754f1380af13e1 +0 -0
  232. data/spec/dummy/tmp/cache/assets/CC7/9F0/sprockets%2F0517100815cd45c43fec1d8510fcb630 +0 -0
  233. data/spec/dummy/tmp/cache/assets/CCA/BF0/sprockets%2F16ea15e0b5d367c34b812612a4207e3e +0 -0
  234. data/spec/dummy/tmp/cache/assets/CD8/370/sprockets%2F357970feca3ac29060c1e3861e2c0953 +0 -0
  235. data/spec/dummy/tmp/cache/assets/CF1/960/sprockets%2F36ef26985821ec702c74dae4af789903 +0 -0
  236. data/spec/dummy/tmp/cache/assets/D07/F30/sprockets%2Fd12862f20df0c9ef7aa2b4216d247079 +0 -0
  237. data/spec/dummy/tmp/cache/assets/D09/BF0/sprockets%2F5c8595efaa8cc5235a09801c60e25a16 +0 -0
  238. data/spec/dummy/tmp/cache/assets/D0D/250/sprockets%2F5e759d66e374910dae5a51b221e6e3a4 +0 -0
  239. data/spec/dummy/tmp/cache/assets/D0D/E30/sprockets%2Fa282879e0788ba133f5bdc211cf134e8 +0 -0
  240. data/spec/dummy/tmp/cache/assets/D11/8E0/sprockets%2F2a193a8af29963f1113bef05788e02ff +0 -0
  241. data/spec/dummy/tmp/cache/assets/D14/270/sprockets%2F7f95a752910ed33d728532f8fabd340d +0 -0
  242. data/spec/dummy/tmp/cache/assets/D17/BE0/sprockets%2F58749d7aea4ca766c2520801f0f96b9b +0 -0
  243. data/spec/dummy/tmp/cache/assets/D18/270/sprockets%2Fbb2d88813b1795a97e9a7c14100eef77 +0 -0
  244. data/spec/dummy/tmp/cache/assets/D22/310/sprockets%2Fbae9148ce1229214dfbb0c425b0054d0 +0 -0
  245. data/spec/dummy/tmp/cache/assets/D30/EB0/sprockets%2F2c668b88beb4c9805d91ed4587f549e7 +0 -0
  246. data/spec/dummy/tmp/cache/assets/D32/A10/sprockets%2F13fe41fee1fe35b49d145bcc06610705 +0 -0
  247. data/spec/dummy/tmp/cache/assets/D3C/690/sprockets%2F681ba175fdd2da8d590b7e624b6135a6 +0 -0
  248. data/spec/dummy/tmp/cache/assets/D44/BC0/sprockets%2F7214f66dd91fdd51f00c3882293f5eea +0 -0
  249. data/spec/dummy/tmp/cache/assets/D46/FA0/sprockets%2F4f9543cd9e68ba3141fd488afb13681b +0 -0
  250. data/spec/dummy/tmp/cache/assets/D4E/1B0/sprockets%2Ff7cbd26ba1d28d48de824f0e94586655 +0 -0
  251. data/spec/dummy/tmp/cache/assets/D51/9A0/sprockets%2Fb6e4cbcf30f6f611671695f79ea846d5 +0 -0
  252. data/spec/dummy/tmp/cache/assets/D55/920/sprockets%2F0a02242aa4c4febcc796419aba103c69 +0 -0
  253. data/spec/dummy/tmp/cache/assets/D5A/EA0/sprockets%2Fd771ace226fc8215a3572e0aa35bb0d6 +0 -0
  254. data/spec/dummy/tmp/cache/assets/D5F/6F0/sprockets%2F155e7bc44f326371cce0ca8bc5b2414e +0 -0
  255. data/spec/dummy/tmp/cache/assets/D6B/250/sprockets%2Fe4eecce7a3371b11038c92f07cb28e67 +0 -0
  256. data/spec/dummy/tmp/cache/assets/D6B/E70/sprockets%2F103a456ae3692e3cca20b7696fdee8f0 +0 -0
  257. data/spec/dummy/tmp/cache/assets/D7F/AD0/sprockets%2Fb1c02d12303b1fe2f981771cbb0c4cbd +0 -0
  258. data/spec/dummy/tmp/cache/assets/D81/D00/sprockets%2F0c68b6d9dce98681a2a710d9fc3f7e39 +0 -0
  259. data/spec/dummy/tmp/cache/assets/D94/4D0/sprockets%2F4608a5bdf27ddf11bcaed1375911e9a2 +0 -0
  260. data/spec/dummy/tmp/cache/assets/D9A/580/sprockets%2F433005b19aaf7ed5cba9c7f8cd30b775 +0 -0
  261. data/spec/dummy/tmp/cache/assets/DA4/270/sprockets%2F860ab3bd30204e02c0a5d82bd4bbabe1 +0 -0
  262. data/spec/dummy/tmp/cache/assets/DA4/480/sprockets%2F53e39285bb57a2ddb6ff856b2eb491da +0 -0
  263. data/spec/dummy/tmp/cache/assets/DA6/3C0/sprockets%2F587c7e35f47442eadaa530acc5e9f5e6 +0 -0
  264. data/spec/dummy/tmp/cache/assets/DAB/DA0/sprockets%2Fb8fb5e8518ed3d38a7f6a8f18a9208aa +0 -0
  265. data/spec/dummy/tmp/cache/assets/DB2/700/sprockets%2Ffa85cb79b94fce1b8344d983ea863d2e +0 -0
  266. data/spec/dummy/tmp/cache/assets/DCC/4F0/sprockets%2Fbc0f3c61e16afb5a285ba8b39846ed8b +0 -0
  267. data/spec/dummy/tmp/cache/assets/DDC/400/sprockets%2Fcffd775d018f68ce5dba1ee0d951a994 +0 -0
  268. data/spec/dummy/tmp/cache/assets/DDC/BE0/sprockets%2F2c0a4d4675a61b014aedbbabb3f071fc +0 -0
  269. data/spec/dummy/tmp/cache/assets/E04/640/sprockets%2F3c5ddf2bbefd9d70f1b506b519cf856e +0 -0
  270. data/spec/dummy/tmp/cache/assets/E04/7F0/sprockets%2F35f1b2ff729a5ec21f6dc5f7d6faa67b +0 -0
  271. data/spec/dummy/tmp/cache/assets/E04/890/sprockets%2F2f5173deea6c795b8fdde723bb4b63af +0 -0
  272. data/spec/dummy/tmp/cache/assets/E07/230/sprockets%2Febd4f2dd708879d593aff008ccb75acc +0 -0
  273. data/spec/dummy/tmp/cache/assets/E17/9D0/sprockets%2F8db2b79f6c4eabe7a9ebe7a7898ec066 +0 -0
  274. data/spec/dummy/tmp/cache/assets/E31/F10/sprockets%2Fcebacf56667da1cc2961b59fd922eafb +0 -0
  275. data/spec/factories/agent.rb +26 -0
  276. data/spec/factories/calls.rb +106 -0
  277. data/spec/factories/conversations.rb +171 -0
  278. data/spec/factories/events.rb +250 -0
  279. data/spec/factories/playable_listeners.rb +6 -0
  280. data/spec/factories/recordings.rb +6 -0
  281. data/spec/factories/voicemails.rb +8 -0
  282. data/spec/fixtures/vcr_cassettes/Authenticating_an_online_user.yml +90 -0
  283. data/spec/fixtures/vcr_cassettes/Creating_a_conversation/creates_a_new_call.yml +42 -0
  284. data/spec/fixtures/vcr_cassettes/Creating_a_conversation/creates_a_new_conversation.yml +42 -0
  285. data/spec/fixtures/vcr_cassettes/Creating_a_conversation/returns_the_conversation_as_JSON.yml +42 -0
  286. data/spec/fixtures/vcr_cassettes/Pusher_channel_as_JSON.yml +90 -0
  287. data/spec/fixtures/vcr_cassettes/Pusher_to_publish_the_event.yml +32 -0
  288. data/spec/fixtures/vcr_cassettes/Reloading_the_widget_during_a_call/_create/during_a_call/after_a_failed_transfer/pushes_the_event_for_the_active_call.yml +1110 -0
  289. data/spec/fixtures/vcr_cassettes/Telephony_ConversationsController/_create/by_default/creates_a_new_call.yml +45 -0
  290. data/spec/fixtures/vcr_cassettes/Telephony_ConversationsController/_create/by_default/creates_a_new_conversation.yml +43 -0
  291. data/spec/fixtures/vcr_cassettes/Telephony_ConversationsController/_create/by_default/returns_the_conversation_as_JSON.yml +44 -0
  292. data/spec/fixtures/vcr_cassettes/Telephony_Providers_TwilioProvider/_call/allows_the_phone_to_ring_for_60_seconds.yml +42 -0
  293. data/spec/fixtures/vcr_cassettes/Telephony_Providers_TwilioProvider/_call/includes_a_status_change_callback_url.yml +42 -0
  294. data/spec/fixtures/vcr_cassettes/Telephony_Providers_TwilioProvider/_call/places_a_call.yml +42 -0
  295. data/spec/fixtures/vcr_cassettes/Telephony_Providers_TwilioProvider/_dial_into_conference/allows_the_phone_to_ring_for_15_seconds.yml +42 -0
  296. data/spec/fixtures/vcr_cassettes/Telephony_Providers_TwilioProvider/_dial_into_conference/places_a_call_to_redirect_the_participant_to_a_conference.yml +42 -0
  297. data/spec/fixtures/vcr_cassettes/Telephony_Providers_TwilioProvider/_hangup/by_default/hangs_up_the_call.yml +42 -0
  298. data/spec/fixtures/vcr_cassettes/buying_number_for_an_area_code.yml +42 -0
  299. data/spec/fixtures/vcr_cassettes/returns_success.yml +90 -0
  300. data/spec/javascripts/helpers/jasmine-jquery.js +546 -0
  301. data/spec/javascripts/helpers/mock-ajax.js +207 -0
  302. data/spec/javascripts/support/jasmine.yml +80 -0
  303. data/spec/javascripts/telephony/models/agent_spec.js +40 -0
  304. data/spec/javascripts/telephony/models/conversation_spec.js +515 -0
  305. data/spec/javascripts/telephony/models/device_spec.js +80 -0
  306. data/spec/javascripts/telephony/models/transfer_spec.js +22 -0
  307. data/spec/javascripts/telephony/push_spec.js +427 -0
  308. data/spec/javascripts/telephony/views/agents_view_spec.js +101 -0
  309. data/spec/javascripts/telephony/views/application_view_spec.js +74 -0
  310. data/spec/javascripts/telephony/views/conversation_view_spec.js +626 -0
  311. data/spec/javascripts/telephony/views/status_view_spec.js +30 -0
  312. data/spec/javascripts/telephony/views/transfer_view_spec.js +187 -0
  313. data/spec/javascripts/telephony/views/twilio_client_view_spec.js +77 -0
  314. data/spec/javascripts/telephony/views/widget_view_spec.js +20 -0
  315. data/spec/lib/telephony/concerns/controllers/twilio_request_verifier_spec.rb +77 -0
  316. data/spec/lib/telephony/conversation_data_spec.rb +342 -0
  317. data/spec/lib/telephony/helper_spec.rb +24 -0
  318. data/spec/lib/telephony/jobs/agent_offline_spec.rb +37 -0
  319. data/spec/lib/telephony/jobs/pusher_event_spec.rb +39 -0
  320. data/spec/lib/telephony/providers/twilio_provider_spec.rb +439 -0
  321. data/spec/lib/telephony_spec.rb +104 -0
  322. data/spec/models/telephony/agent_spec.rb +479 -0
  323. data/spec/models/telephony/agent_state_machine_spec.rb +131 -0
  324. data/spec/models/telephony/call_center_spec.rb +32 -0
  325. data/spec/models/telephony/call_spec.rb +597 -0
  326. data/spec/models/telephony/call_state_machine_spec.rb +472 -0
  327. data/spec/models/telephony/conversation_spec.rb +751 -0
  328. data/spec/models/telephony/conversation_state_machine_spec.rb +387 -0
  329. data/spec/models/telephony/conversations_presenter_spec.rb +40 -0
  330. data/spec/models/telephony/event_spec.rb +716 -0
  331. data/spec/models/telephony/inbound_conversation_queue_spec.rb +243 -0
  332. data/spec/models/telephony/playable_listener_spec.rb +43 -0
  333. data/spec/models/telephony/pusher_event_publisher_spec.rb +36 -0
  334. data/spec/models/telephony/recording_spec.rb +6 -0
  335. data/spec/models/telephony/voicemail_spec.rb +96 -0
  336. data/spec/observers/telephony/agent_observer_spec.rb +21 -0
  337. data/spec/observers/telephony/event_observer_spec.rb +19 -0
  338. data/spec/requests/create_conversation_spec.rb +39 -0
  339. data/spec/requests/dequeue_call_spec.rb +42 -0
  340. data/spec/requests/list_conversations_spec.rb +33 -0
  341. data/spec/requests/presences_spec.rb +51 -0
  342. data/spec/requests/providers/twilio/calls/child_answered_spec.rb +24 -0
  343. data/spec/requests/providers/twilio/calls/child_detached_spec.rb +249 -0
  344. data/spec/requests/providers/twilio/calls/dial_spec.rb +132 -0
  345. data/spec/requests/providers/twilio/calls/done_spec.rb +69 -0
  346. data/spec/requests/providers/twilio/calls/join_conference_spec.rb +56 -0
  347. data/spec/requests/providers/twilio/calls/leave_queue_spec.rb +44 -0
  348. data/spec/requests/providers/twilio/calls/parent_answered_spec.rb +59 -0
  349. data/spec/requests/providers/twilio/inbound/connect_dequeued_call_spec.rb +124 -0
  350. data/spec/requests/providers/twilio/inbound/enqueue_inbound_call_spec.rb +108 -0
  351. data/spec/requests/providers/twilio/inbound/enqueued_call_wait_music_spec.rb +13 -0
  352. data/spec/requests/providers/twilio/voicemails/leave_voicemail_spec.rb +78 -0
  353. data/spec/requests/search_conversations_spec.rb +101 -0
  354. data/spec/requests/transfer_spec.rb +65 -0
  355. data/spec/requests/voicemail_api_spec.rb +58 -0
  356. data/spec/spec_helper.rb +27 -0
  357. data/spec/support/factory_girl.rb +5 -0
  358. data/spec/support/matchers/be_complete_hold.rb +20 -0
  359. data/spec/support/matchers/be_whisper_tone.rb +35 -0
  360. data/spec/support/matchers/join_conference.rb +23 -0
  361. data/spec/support/nokogiri.rb +1 -0
  362. data/spec/support/pusher_helper.rb +24 -0
  363. data/spec/support/vcr.rb +12 -0
  364. data/spec/support/webmock.rb +1 -0
  365. metadata +692 -0
@@ -0,0 +1,1117 @@
1
+ /*!
2
+ * jQuery Form Plugin
3
+ * version: 3.18 (28-SEP-2012)
4
+ * @requires jQuery v1.5 or later
5
+ *
6
+ * Examples and documentation at: http://malsup.com/jquery/form/
7
+ * Project repository: https://github.com/malsup/form
8
+ * Dual licensed under the MIT and GPL licenses:
9
+ * http://malsup.github.com/mit-license.txt
10
+ * http://malsup.github.com/gpl-license-v2.txt
11
+ */
12
+ /*global ActiveXObject alert */
13
+ ;(function($) {
14
+ "use strict";
15
+
16
+ /*
17
+ Usage Note:
18
+ -----------
19
+ Do not use both ajaxSubmit and ajaxForm on the same form. These
20
+ functions are mutually exclusive. Use ajaxSubmit if you want
21
+ to bind your own submit handler to the form. For example,
22
+
23
+ $(document).ready(function() {
24
+ $('#myForm').on('submit', function(e) {
25
+ e.preventDefault(); // <-- important
26
+ $(this).ajaxSubmit({
27
+ target: '#output'
28
+ });
29
+ });
30
+ });
31
+
32
+ Use ajaxForm when you want the plugin to manage all the event binding
33
+ for you. For example,
34
+
35
+ $(document).ready(function() {
36
+ $('#myForm').ajaxForm({
37
+ target: '#output'
38
+ });
39
+ });
40
+
41
+ You can also use ajaxForm with delegation (requires jQuery v1.7+), so the
42
+ form does not have to exist when you invoke ajaxForm:
43
+
44
+ $('#myForm').ajaxForm({
45
+ delegation: true,
46
+ target: '#output'
47
+ });
48
+
49
+ When using ajaxForm, the ajaxSubmit function will be invoked for you
50
+ at the appropriate time.
51
+ */
52
+
53
+ /**
54
+ * Feature detection
55
+ */
56
+ var feature = {};
57
+ feature.fileapi = $("<input type='file'/>").get(0).files !== undefined;
58
+ feature.formdata = window.FormData !== undefined;
59
+
60
+ /**
61
+ * ajaxSubmit() provides a mechanism for immediately submitting
62
+ * an HTML form using AJAX.
63
+ */
64
+ $.fn.ajaxSubmit = function(options) {
65
+ /*jshint scripturl:true */
66
+
67
+ // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
68
+ if (!this.length) {
69
+ log('ajaxSubmit: skipping submit process - no element selected');
70
+ return this;
71
+ }
72
+
73
+ var method, action, url, $form = this;
74
+
75
+ if (typeof options == 'function') {
76
+ options = { success: options };
77
+ }
78
+
79
+ method = this.attr('method');
80
+ action = this.attr('action');
81
+ url = (typeof action === 'string') ? $.trim(action) : '';
82
+ url = url || window.location.href || '';
83
+ if (url) {
84
+ // clean url (don't include hash vaue)
85
+ url = (url.match(/^([^#]+)/)||[])[1];
86
+ }
87
+
88
+ options = $.extend(true, {
89
+ url: url,
90
+ success: $.ajaxSettings.success,
91
+ type: method || 'GET',
92
+ iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank'
93
+ }, options);
94
+
95
+ // hook for manipulating the form data before it is extracted;
96
+ // convenient for use with rich editors like tinyMCE or FCKEditor
97
+ var veto = {};
98
+ this.trigger('form-pre-serialize', [this, options, veto]);
99
+ if (veto.veto) {
100
+ log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
101
+ return this;
102
+ }
103
+
104
+ // provide opportunity to alter form data before it is serialized
105
+ if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
106
+ log('ajaxSubmit: submit aborted via beforeSerialize callback');
107
+ return this;
108
+ }
109
+
110
+ var traditional = options.traditional;
111
+ if ( traditional === undefined ) {
112
+ traditional = $.ajaxSettings.traditional;
113
+ }
114
+
115
+ var elements = [];
116
+ var qx, a = this.formToArray(options.semantic, elements);
117
+ if (options.data) {
118
+ options.extraData = options.data;
119
+ qx = $.param(options.data, traditional);
120
+ }
121
+
122
+ // give pre-submit callback an opportunity to abort the submit
123
+ if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
124
+ log('ajaxSubmit: submit aborted via beforeSubmit callback');
125
+ return this;
126
+ }
127
+
128
+ // fire vetoable 'validate' event
129
+ this.trigger('form-submit-validate', [a, this, options, veto]);
130
+ if (veto.veto) {
131
+ log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
132
+ return this;
133
+ }
134
+
135
+ var q = $.param(a, traditional);
136
+ if (qx) {
137
+ q = ( q ? (q + '&' + qx) : qx );
138
+ }
139
+ if (options.type.toUpperCase() == 'GET') {
140
+ options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
141
+ options.data = null; // data is null for 'get'
142
+ }
143
+ else {
144
+ options.data = q; // data is the query string for 'post'
145
+ }
146
+
147
+ var callbacks = [];
148
+ if (options.resetForm) {
149
+ callbacks.push(function() { $form.resetForm(); });
150
+ }
151
+ if (options.clearForm) {
152
+ callbacks.push(function() { $form.clearForm(options.includeHidden); });
153
+ }
154
+
155
+ // perform a load on the target only if dataType is not provided
156
+ if (!options.dataType && options.target) {
157
+ var oldSuccess = options.success || function(){};
158
+ callbacks.push(function(data) {
159
+ var fn = options.replaceTarget ? 'replaceWith' : 'html';
160
+ $(options.target)[fn](data).each(oldSuccess, arguments);
161
+ });
162
+ }
163
+ else if (options.success) {
164
+ callbacks.push(options.success);
165
+ }
166
+
167
+ options.success = function(data, status, xhr) { // jQuery 1.4+ passes xhr as 3rd arg
168
+ var context = options.context || this ; // jQuery 1.4+ supports scope context
169
+ for (var i=0, max=callbacks.length; i < max; i++) {
170
+ callbacks[i].apply(context, [data, status, xhr || $form, $form]);
171
+ }
172
+ };
173
+
174
+ // are there files to upload?
175
+ var fileInputs = $('input:file:enabled[value]', this); // [value] (issue #113)
176
+ var hasFileInputs = fileInputs.length > 0;
177
+ var mp = 'multipart/form-data';
178
+ var multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);
179
+
180
+ var fileAPI = feature.fileapi && feature.formdata;
181
+ log("fileAPI :" + fileAPI);
182
+ var shouldUseFrame = (hasFileInputs || multipart) && !fileAPI;
183
+
184
+ var jqxhr;
185
+
186
+ // options.iframe allows user to force iframe mode
187
+ // 06-NOV-09: now defaulting to iframe mode if file input is detected
188
+ if (options.iframe !== false && (options.iframe || shouldUseFrame)) {
189
+ // hack to fix Safari hang (thanks to Tim Molendijk for this)
190
+ // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
191
+ if (options.closeKeepAlive) {
192
+ $.get(options.closeKeepAlive, function() {
193
+ jqxhr = fileUploadIframe(a);
194
+ });
195
+ }
196
+ else {
197
+ jqxhr = fileUploadIframe(a);
198
+ }
199
+ }
200
+ else if ((hasFileInputs || multipart) && fileAPI) {
201
+ jqxhr = fileUploadXhr(a);
202
+ }
203
+ else {
204
+ jqxhr = $.ajax(options);
205
+ }
206
+
207
+ $form.removeData('jqxhr').data('jqxhr', jqxhr);
208
+
209
+ // clear element array
210
+ for (var k=0; k < elements.length; k++)
211
+ elements[k] = null;
212
+
213
+ // fire 'notify' event
214
+ this.trigger('form-submit-notify', [this, options]);
215
+ return this;
216
+
217
+ // utility fn for deep serialization
218
+ function deepSerialize(extraData){
219
+ var serialized = $.param(extraData).split('&');
220
+ var len = serialized.length;
221
+ var result = {};
222
+ var i, part;
223
+ for (i=0; i < len; i++) {
224
+ part = serialized[i].split('=');
225
+ result[decodeURIComponent(part[0])] = decodeURIComponent(part[1]);
226
+ }
227
+ return result;
228
+ }
229
+
230
+ // XMLHttpRequest Level 2 file uploads (big hat tip to francois2metz)
231
+ function fileUploadXhr(a) {
232
+ var formdata = new FormData();
233
+
234
+ for (var i=0; i < a.length; i++) {
235
+ formdata.append(a[i].name, a[i].value);
236
+ }
237
+
238
+ if (options.extraData) {
239
+ var serializedData = deepSerialize(options.extraData);
240
+ for (var p in serializedData)
241
+ if (serializedData.hasOwnProperty(p))
242
+ formdata.append(p, serializedData[p]);
243
+ }
244
+
245
+ options.data = null;
246
+
247
+ var s = $.extend(true, {}, $.ajaxSettings, options, {
248
+ contentType: false,
249
+ processData: false,
250
+ cache: false,
251
+ type: method || 'POST'
252
+ });
253
+
254
+ if (options.uploadProgress) {
255
+ // workaround because jqXHR does not expose upload property
256
+ s.xhr = function() {
257
+ var xhr = jQuery.ajaxSettings.xhr();
258
+ if (xhr.upload) {
259
+ xhr.upload.onprogress = function(event) {
260
+ var percent = 0;
261
+ var position = event.loaded || event.position; /*event.position is deprecated*/
262
+ var total = event.total;
263
+ if (event.lengthComputable) {
264
+ percent = Math.ceil(position / total * 100);
265
+ }
266
+ options.uploadProgress(event, position, total, percent);
267
+ };
268
+ }
269
+ return xhr;
270
+ };
271
+ }
272
+
273
+ s.data = null;
274
+ var beforeSend = s.beforeSend;
275
+ s.beforeSend = function(xhr, o) {
276
+ o.data = formdata;
277
+ if(beforeSend)
278
+ beforeSend.call(this, xhr, o);
279
+ };
280
+ return $.ajax(s);
281
+ }
282
+
283
+ // private function for handling file uploads (hat tip to YAHOO!)
284
+ function fileUploadIframe(a) {
285
+ var form = $form[0], el, i, s, g, id, $io, io, xhr, sub, n, timedOut, timeoutHandle;
286
+ var useProp = !!$.fn.prop;
287
+ var deferred = $.Deferred();
288
+
289
+ if ($(':input[name=submit],:input[id=submit]', form).length) {
290
+ // if there is an input with a name or id of 'submit' then we won't be
291
+ // able to invoke the submit fn on the form (at least not x-browser)
292
+ alert('Error: Form elements must not have name or id of "submit".');
293
+ deferred.reject();
294
+ return deferred;
295
+ }
296
+
297
+ if (a) {
298
+ // ensure that every serialized input is still enabled
299
+ for (i=0; i < elements.length; i++) {
300
+ el = $(elements[i]);
301
+ if ( useProp )
302
+ el.prop('disabled', false);
303
+ else
304
+ el.removeAttr('disabled');
305
+ }
306
+ }
307
+
308
+ s = $.extend(true, {}, $.ajaxSettings, options);
309
+ s.context = s.context || s;
310
+ id = 'jqFormIO' + (new Date().getTime());
311
+ if (s.iframeTarget) {
312
+ $io = $(s.iframeTarget);
313
+ n = $io.attr('name');
314
+ if (!n)
315
+ $io.attr('name', id);
316
+ else
317
+ id = n;
318
+ }
319
+ else {
320
+ $io = $('<iframe name="' + id + '" src="'+ s.iframeSrc +'" />');
321
+ $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
322
+ }
323
+ io = $io[0];
324
+
325
+
326
+ xhr = { // mock object
327
+ aborted: 0,
328
+ responseText: null,
329
+ responseXML: null,
330
+ status: 0,
331
+ statusText: 'n/a',
332
+ getAllResponseHeaders: function() {},
333
+ getResponseHeader: function() {},
334
+ setRequestHeader: function() {},
335
+ abort: function(status) {
336
+ var e = (status === 'timeout' ? 'timeout' : 'aborted');
337
+ log('aborting upload... ' + e);
338
+ this.aborted = 1;
339
+ // #214
340
+ if (io.contentWindow.document.execCommand) {
341
+ try { // #214
342
+ io.contentWindow.document.execCommand('Stop');
343
+ } catch(ignore) {}
344
+ }
345
+ $io.attr('src', s.iframeSrc); // abort op in progress
346
+ xhr.error = e;
347
+ if (s.error)
348
+ s.error.call(s.context, xhr, e, status);
349
+ if (g)
350
+ $.event.trigger("ajaxError", [xhr, s, e]);
351
+ if (s.complete)
352
+ s.complete.call(s.context, xhr, e);
353
+ }
354
+ };
355
+
356
+ g = s.global;
357
+ // trigger ajax global events so that activity/block indicators work like normal
358
+ if (g && 0 === $.active++) {
359
+ $.event.trigger("ajaxStart");
360
+ }
361
+ if (g) {
362
+ $.event.trigger("ajaxSend", [xhr, s]);
363
+ }
364
+
365
+ if (s.beforeSend && s.beforeSend.call(s.context, xhr, s) === false) {
366
+ if (s.global) {
367
+ $.active--;
368
+ }
369
+ deferred.reject();
370
+ return deferred;
371
+ }
372
+ if (xhr.aborted) {
373
+ deferred.reject();
374
+ return deferred;
375
+ }
376
+
377
+ // add submitting element to data if we know it
378
+ sub = form.clk;
379
+ if (sub) {
380
+ n = sub.name;
381
+ if (n && !sub.disabled) {
382
+ s.extraData = s.extraData || {};
383
+ s.extraData[n] = sub.value;
384
+ if (sub.type == "image") {
385
+ s.extraData[n+'.x'] = form.clk_x;
386
+ s.extraData[n+'.y'] = form.clk_y;
387
+ }
388
+ }
389
+ }
390
+
391
+ var CLIENT_TIMEOUT_ABORT = 1;
392
+ var SERVER_ABORT = 2;
393
+
394
+ function getDoc(frame) {
395
+ var doc = frame.contentWindow ? frame.contentWindow.document : frame.contentDocument ? frame.contentDocument : frame.document;
396
+ return doc;
397
+ }
398
+
399
+ // Rails CSRF hack (thanks to Yvan Barthelemy)
400
+ var csrf_token = $('meta[name=csrf-token]').attr('content');
401
+ var csrf_param = $('meta[name=csrf-param]').attr('content');
402
+ if (csrf_param && csrf_token) {
403
+ s.extraData = s.extraData || {};
404
+ s.extraData[csrf_param] = csrf_token;
405
+ }
406
+
407
+ // take a breath so that pending repaints get some cpu time before the upload starts
408
+ function doSubmit() {
409
+ // make sure form attrs are set
410
+ var t = $form.attr('target'), a = $form.attr('action');
411
+
412
+ // update form attrs in IE friendly way
413
+ form.setAttribute('target',id);
414
+ if (!method) {
415
+ form.setAttribute('method', 'POST');
416
+ }
417
+ if (a != s.url) {
418
+ form.setAttribute('action', s.url);
419
+ }
420
+
421
+ // ie borks in some cases when setting encoding
422
+ if (! s.skipEncodingOverride && (!method || /post/i.test(method))) {
423
+ $form.attr({
424
+ encoding: 'multipart/form-data',
425
+ enctype: 'multipart/form-data'
426
+ });
427
+ }
428
+
429
+ // support timout
430
+ if (s.timeout) {
431
+ timeoutHandle = setTimeout(function() { timedOut = true; cb(CLIENT_TIMEOUT_ABORT); }, s.timeout);
432
+ }
433
+
434
+ // look for server aborts
435
+ function checkState() {
436
+ try {
437
+ var state = getDoc(io).readyState;
438
+ log('state = ' + state);
439
+ if (state && state.toLowerCase() == 'uninitialized')
440
+ setTimeout(checkState,50);
441
+ }
442
+ catch(e) {
443
+ log('Server abort: ' , e, ' (', e.name, ')');
444
+ cb(SERVER_ABORT);
445
+ if (timeoutHandle)
446
+ clearTimeout(timeoutHandle);
447
+ timeoutHandle = undefined;
448
+ }
449
+ }
450
+
451
+ // add "extra" data to form if provided in options
452
+ var extraInputs = [];
453
+ try {
454
+ if (s.extraData) {
455
+ for (var n in s.extraData) {
456
+ if (s.extraData.hasOwnProperty(n)) {
457
+ // if using the $.param format that allows for multiple values with the same name
458
+ if($.isPlainObject(s.extraData[n]) && s.extraData[n].hasOwnProperty('name') && s.extraData[n].hasOwnProperty('value')) {
459
+ extraInputs.push(
460
+ $('<input type="hidden" name="'+s.extraData[n].name+'">').attr('value',s.extraData[n].value)
461
+ .appendTo(form)[0]);
462
+ } else {
463
+ extraInputs.push(
464
+ $('<input type="hidden" name="'+n+'">').attr('value',s.extraData[n])
465
+ .appendTo(form)[0]);
466
+ }
467
+ }
468
+ }
469
+ }
470
+
471
+ if (!s.iframeTarget) {
472
+ // add iframe to doc and submit the form
473
+ $io.appendTo('body');
474
+ if (io.attachEvent)
475
+ io.attachEvent('onload', cb);
476
+ else
477
+ io.addEventListener('load', cb, false);
478
+ }
479
+ setTimeout(checkState,15);
480
+ form.submit();
481
+ }
482
+ finally {
483
+ // reset attrs and remove "extra" input elements
484
+ form.setAttribute('action',a);
485
+ if(t) {
486
+ form.setAttribute('target', t);
487
+ } else {
488
+ $form.removeAttr('target');
489
+ }
490
+ $(extraInputs).remove();
491
+ }
492
+ }
493
+
494
+ if (s.forceSync) {
495
+ doSubmit();
496
+ }
497
+ else {
498
+ setTimeout(doSubmit, 10); // this lets dom updates render
499
+ }
500
+
501
+ var data, doc, domCheckCount = 50, callbackProcessed;
502
+
503
+ function cb(e) {
504
+ if (xhr.aborted || callbackProcessed) {
505
+ return;
506
+ }
507
+ try {
508
+ doc = getDoc(io);
509
+ }
510
+ catch(ex) {
511
+ log('cannot access response document: ', ex);
512
+ e = SERVER_ABORT;
513
+ }
514
+ if (e === CLIENT_TIMEOUT_ABORT && xhr) {
515
+ xhr.abort('timeout');
516
+ deferred.reject(xhr, 'timeout');
517
+ return;
518
+ }
519
+ else if (e == SERVER_ABORT && xhr) {
520
+ xhr.abort('server abort');
521
+ deferred.reject(xhr, 'error', 'server abort');
522
+ return;
523
+ }
524
+
525
+ if (!doc || doc.location.href == s.iframeSrc) {
526
+ // response not received yet
527
+ if (!timedOut)
528
+ return;
529
+ }
530
+ if (io.detachEvent)
531
+ io.detachEvent('onload', cb);
532
+ else
533
+ io.removeEventListener('load', cb, false);
534
+
535
+ var status = 'success', errMsg;
536
+ try {
537
+ if (timedOut) {
538
+ throw 'timeout';
539
+ }
540
+
541
+ var isXml = s.dataType == 'xml' || doc.XMLDocument || $.isXMLDoc(doc);
542
+ log('isXml='+isXml);
543
+ if (!isXml && window.opera && (doc.body === null || !doc.body.innerHTML)) {
544
+ if (--domCheckCount) {
545
+ // in some browsers (Opera) the iframe DOM is not always traversable when
546
+ // the onload callback fires, so we loop a bit to accommodate
547
+ log('requeing onLoad callback, DOM not available');
548
+ setTimeout(cb, 250);
549
+ return;
550
+ }
551
+ // let this fall through because server response could be an empty document
552
+ //log('Could not access iframe DOM after mutiple tries.');
553
+ //throw 'DOMException: not available';
554
+ }
555
+
556
+ //log('response detected');
557
+ var docRoot = doc.body ? doc.body : doc.documentElement;
558
+ xhr.responseText = docRoot ? docRoot.innerHTML : null;
559
+ xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
560
+ if (isXml)
561
+ s.dataType = 'xml';
562
+ xhr.getResponseHeader = function(header){
563
+ var headers = {'content-type': s.dataType};
564
+ return headers[header];
565
+ };
566
+ // support for XHR 'status' & 'statusText' emulation :
567
+ if (docRoot) {
568
+ xhr.status = Number( docRoot.getAttribute('status') ) || xhr.status;
569
+ xhr.statusText = docRoot.getAttribute('statusText') || xhr.statusText;
570
+ }
571
+
572
+ var dt = (s.dataType || '').toLowerCase();
573
+ var scr = /(json|script|text)/.test(dt);
574
+ if (scr || s.textarea) {
575
+ // see if user embedded response in textarea
576
+ var ta = doc.getElementsByTagName('textarea')[0];
577
+ if (ta) {
578
+ xhr.responseText = ta.value;
579
+ // support for XHR 'status' & 'statusText' emulation :
580
+ xhr.status = Number( ta.getAttribute('status') ) || xhr.status;
581
+ xhr.statusText = ta.getAttribute('statusText') || xhr.statusText;
582
+ }
583
+ else if (scr) {
584
+ // account for browsers injecting pre around json response
585
+ var pre = doc.getElementsByTagName('pre')[0];
586
+ var b = doc.getElementsByTagName('body')[0];
587
+ if (pre) {
588
+ xhr.responseText = pre.textContent ? pre.textContent : pre.innerText;
589
+ }
590
+ else if (b) {
591
+ xhr.responseText = b.textContent ? b.textContent : b.innerText;
592
+ }
593
+ }
594
+ }
595
+ else if (dt == 'xml' && !xhr.responseXML && xhr.responseText) {
596
+ xhr.responseXML = toXml(xhr.responseText);
597
+ }
598
+
599
+ try {
600
+ data = httpData(xhr, dt, s);
601
+ }
602
+ catch (e) {
603
+ status = 'parsererror';
604
+ xhr.error = errMsg = (e || status);
605
+ }
606
+ }
607
+ catch (e) {
608
+ log('error caught: ',e);
609
+ status = 'error';
610
+ xhr.error = errMsg = (e || status);
611
+ }
612
+
613
+ if (xhr.aborted) {
614
+ log('upload aborted');
615
+ status = null;
616
+ }
617
+
618
+ if (xhr.status) { // we've set xhr.status
619
+ status = (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) ? 'success' : 'error';
620
+ }
621
+
622
+ // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
623
+ if (status === 'success') {
624
+ if (s.success)
625
+ s.success.call(s.context, data, 'success', xhr);
626
+ deferred.resolve(xhr.responseText, 'success', xhr);
627
+ if (g)
628
+ $.event.trigger("ajaxSuccess", [xhr, s]);
629
+ }
630
+ else if (status) {
631
+ if (errMsg === undefined)
632
+ errMsg = xhr.statusText;
633
+ if (s.error)
634
+ s.error.call(s.context, xhr, status, errMsg);
635
+ deferred.reject(xhr, 'error', errMsg);
636
+ if (g)
637
+ $.event.trigger("ajaxError", [xhr, s, errMsg]);
638
+ }
639
+
640
+ if (g)
641
+ $.event.trigger("ajaxComplete", [xhr, s]);
642
+
643
+ if (g && ! --$.active) {
644
+ $.event.trigger("ajaxStop");
645
+ }
646
+
647
+ if (s.complete)
648
+ s.complete.call(s.context, xhr, status);
649
+
650
+ callbackProcessed = true;
651
+ if (s.timeout)
652
+ clearTimeout(timeoutHandle);
653
+
654
+ // clean up
655
+ setTimeout(function() {
656
+ if (!s.iframeTarget)
657
+ $io.remove();
658
+ xhr.responseXML = null;
659
+ }, 100);
660
+ }
661
+
662
+ var toXml = $.parseXML || function(s, doc) { // use parseXML if available (jQuery 1.5+)
663
+ if (window.ActiveXObject) {
664
+ doc = new ActiveXObject('Microsoft.XMLDOM');
665
+ doc.async = 'false';
666
+ doc.loadXML(s);
667
+ }
668
+ else {
669
+ doc = (new DOMParser()).parseFromString(s, 'text/xml');
670
+ }
671
+ return (doc && doc.documentElement && doc.documentElement.nodeName != 'parsererror') ? doc : null;
672
+ };
673
+ var parseJSON = $.parseJSON || function(s) {
674
+ /*jslint evil:true */
675
+ return window['eval']('(' + s + ')');
676
+ };
677
+
678
+ var httpData = function( xhr, type, s ) { // mostly lifted from jq1.4.4
679
+
680
+ var ct = xhr.getResponseHeader('content-type') || '',
681
+ xml = type === 'xml' || !type && ct.indexOf('xml') >= 0,
682
+ data = xml ? xhr.responseXML : xhr.responseText;
683
+
684
+ if (xml && data.documentElement.nodeName === 'parsererror') {
685
+ if ($.error)
686
+ $.error('parsererror');
687
+ }
688
+ if (s && s.dataFilter) {
689
+ data = s.dataFilter(data, type);
690
+ }
691
+ if (typeof data === 'string') {
692
+ if (type === 'json' || !type && ct.indexOf('json') >= 0) {
693
+ data = parseJSON(data);
694
+ } else if (type === "script" || !type && ct.indexOf("javascript") >= 0) {
695
+ $.globalEval(data);
696
+ }
697
+ }
698
+ return data;
699
+ };
700
+
701
+ return deferred;
702
+ }
703
+ };
704
+
705
+ /**
706
+ * ajaxForm() provides a mechanism for fully automating form submission.
707
+ *
708
+ * The advantages of using this method instead of ajaxSubmit() are:
709
+ *
710
+ * 1: This method will include coordinates for <input type="image" /> elements (if the element
711
+ * is used to submit the form).
712
+ * 2. This method will include the submit element's name/value data (for the element that was
713
+ * used to submit the form).
714
+ * 3. This method binds the submit() method to the form for you.
715
+ *
716
+ * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely
717
+ * passes the options argument along after properly binding events for submit elements and
718
+ * the form itself.
719
+ */
720
+ $.fn.ajaxForm = function(options) {
721
+ options = options || {};
722
+ options.delegation = options.delegation && $.isFunction($.fn.on);
723
+
724
+ // in jQuery 1.3+ we can fix mistakes with the ready state
725
+ if (!options.delegation && this.length === 0) {
726
+ var o = { s: this.selector, c: this.context };
727
+ if (!$.isReady && o.s) {
728
+ log('DOM not ready, queuing ajaxForm');
729
+ $(function() {
730
+ $(o.s,o.c).ajaxForm(options);
731
+ });
732
+ return this;
733
+ }
734
+ // is your DOM ready? http://docs.jquery.com/Tutorials:Introducing_$(document).ready()
735
+ log('terminating; zero elements found by selector' + ($.isReady ? '' : ' (DOM not ready)'));
736
+ return this;
737
+ }
738
+
739
+ if ( options.delegation ) {
740
+ $(document)
741
+ .off('submit.form-plugin', this.selector, doAjaxSubmit)
742
+ .off('click.form-plugin', this.selector, captureSubmittingElement)
743
+ .on('submit.form-plugin', this.selector, options, doAjaxSubmit)
744
+ .on('click.form-plugin', this.selector, options, captureSubmittingElement);
745
+ return this;
746
+ }
747
+
748
+ return this.ajaxFormUnbind()
749
+ .bind('submit.form-plugin', options, doAjaxSubmit)
750
+ .bind('click.form-plugin', options, captureSubmittingElement);
751
+ };
752
+
753
+ // private event handlers
754
+ function doAjaxSubmit(e) {
755
+ /*jshint validthis:true */
756
+ var options = e.data;
757
+ if (!e.isDefaultPrevented()) { // if event has been canceled, don't proceed
758
+ e.preventDefault();
759
+ $(this).ajaxSubmit(options);
760
+ }
761
+ }
762
+
763
+ function captureSubmittingElement(e) {
764
+ /*jshint validthis:true */
765
+ var target = e.target;
766
+ var $el = $(target);
767
+ if (!($el.is(":submit,input:image"))) {
768
+ // is this a child element of the submit el? (ex: a span within a button)
769
+ var t = $el.closest(':submit');
770
+ if (t.length === 0) {
771
+ return;
772
+ }
773
+ target = t[0];
774
+ }
775
+ var form = this;
776
+ form.clk = target;
777
+ if (target.type == 'image') {
778
+ if (e.offsetX !== undefined) {
779
+ form.clk_x = e.offsetX;
780
+ form.clk_y = e.offsetY;
781
+ } else if (typeof $.fn.offset == 'function') {
782
+ var offset = $el.offset();
783
+ form.clk_x = e.pageX - offset.left;
784
+ form.clk_y = e.pageY - offset.top;
785
+ } else {
786
+ form.clk_x = e.pageX - target.offsetLeft;
787
+ form.clk_y = e.pageY - target.offsetTop;
788
+ }
789
+ }
790
+ // clear form vars
791
+ setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 100);
792
+ }
793
+
794
+
795
+ // ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
796
+ $.fn.ajaxFormUnbind = function() {
797
+ return this.unbind('submit.form-plugin click.form-plugin');
798
+ };
799
+
800
+ /**
801
+ * formToArray() gathers form element data into an array of objects that can
802
+ * be passed to any of the following ajax functions: $.get, $.post, or load.
803
+ * Each object in the array has both a 'name' and 'value' property. An example of
804
+ * an array for a simple login form might be:
805
+ *
806
+ * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
807
+ *
808
+ * It is this array that is passed to pre-submit callback functions provided to the
809
+ * ajaxSubmit() and ajaxForm() methods.
810
+ */
811
+ $.fn.formToArray = function(semantic, elements) {
812
+ var a = [];
813
+ if (this.length === 0) {
814
+ return a;
815
+ }
816
+
817
+ var form = this[0];
818
+ var els = semantic ? form.getElementsByTagName('*') : form.elements;
819
+ if (!els) {
820
+ return a;
821
+ }
822
+
823
+ var i,j,n,v,el,max,jmax;
824
+ for(i=0, max=els.length; i < max; i++) {
825
+ el = els[i];
826
+ n = el.name;
827
+ if (!n) {
828
+ continue;
829
+ }
830
+
831
+ if (semantic && form.clk && el.type == "image") {
832
+ // handle image inputs on the fly when semantic == true
833
+ if(!el.disabled && form.clk == el) {
834
+ a.push({name: n, value: $(el).val(), type: el.type });
835
+ a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
836
+ }
837
+ continue;
838
+ }
839
+
840
+ v = $.fieldValue(el, true);
841
+ if (v && v.constructor == Array) {
842
+ if (elements)
843
+ elements.push(el);
844
+ for(j=0, jmax=v.length; j < jmax; j++) {
845
+ a.push({name: n, value: v[j]});
846
+ }
847
+ }
848
+ else if (feature.fileapi && el.type == 'file' && !el.disabled) {
849
+ if (elements)
850
+ elements.push(el);
851
+ var files = el.files;
852
+ if (files.length) {
853
+ for (j=0; j < files.length; j++) {
854
+ a.push({name: n, value: files[j], type: el.type});
855
+ }
856
+ }
857
+ else {
858
+ // #180
859
+ a.push({ name: n, value: '', type: el.type });
860
+ }
861
+ }
862
+ else if (v !== null && typeof v != 'undefined') {
863
+ if (elements)
864
+ elements.push(el);
865
+ a.push({name: n, value: v, type: el.type, required: el.required});
866
+ }
867
+ }
868
+
869
+ if (!semantic && form.clk) {
870
+ // input type=='image' are not found in elements array! handle it here
871
+ var $input = $(form.clk), input = $input[0];
872
+ n = input.name;
873
+ if (n && !input.disabled && input.type == 'image') {
874
+ a.push({name: n, value: $input.val()});
875
+ a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
876
+ }
877
+ }
878
+ return a;
879
+ };
880
+
881
+ /**
882
+ * Serializes form data into a 'submittable' string. This method will return a string
883
+ * in the format: name1=value1&amp;name2=value2
884
+ */
885
+ $.fn.formSerialize = function(semantic) {
886
+ //hand off to jQuery.param for proper encoding
887
+ return $.param(this.formToArray(semantic));
888
+ };
889
+
890
+ /**
891
+ * Serializes all field elements in the jQuery object into a query string.
892
+ * This method will return a string in the format: name1=value1&amp;name2=value2
893
+ */
894
+ $.fn.fieldSerialize = function(successful) {
895
+ var a = [];
896
+ this.each(function() {
897
+ var n = this.name;
898
+ if (!n) {
899
+ return;
900
+ }
901
+ var v = $.fieldValue(this, successful);
902
+ if (v && v.constructor == Array) {
903
+ for (var i=0,max=v.length; i < max; i++) {
904
+ a.push({name: n, value: v[i]});
905
+ }
906
+ }
907
+ else if (v !== null && typeof v != 'undefined') {
908
+ a.push({name: this.name, value: v});
909
+ }
910
+ });
911
+ //hand off to jQuery.param for proper encoding
912
+ return $.param(a);
913
+ };
914
+
915
+ /**
916
+ * Returns the value(s) of the element in the matched set. For example, consider the following form:
917
+ *
918
+ * <form><fieldset>
919
+ * <input name="A" type="text" />
920
+ * <input name="A" type="text" />
921
+ * <input name="B" type="checkbox" value="B1" />
922
+ * <input name="B" type="checkbox" value="B2"/>
923
+ * <input name="C" type="radio" value="C1" />
924
+ * <input name="C" type="radio" value="C2" />
925
+ * </fieldset></form>
926
+ *
927
+ * var v = $(':text').fieldValue();
928
+ * // if no values are entered into the text inputs
929
+ * v == ['','']
930
+ * // if values entered into the text inputs are 'foo' and 'bar'
931
+ * v == ['foo','bar']
932
+ *
933
+ * var v = $(':checkbox').fieldValue();
934
+ * // if neither checkbox is checked
935
+ * v === undefined
936
+ * // if both checkboxes are checked
937
+ * v == ['B1', 'B2']
938
+ *
939
+ * var v = $(':radio').fieldValue();
940
+ * // if neither radio is checked
941
+ * v === undefined
942
+ * // if first radio is checked
943
+ * v == ['C1']
944
+ *
945
+ * The successful argument controls whether or not the field element must be 'successful'
946
+ * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
947
+ * The default value of the successful argument is true. If this value is false the value(s)
948
+ * for each element is returned.
949
+ *
950
+ * Note: This method *always* returns an array. If no valid value can be determined the
951
+ * array will be empty, otherwise it will contain one or more values.
952
+ */
953
+ $.fn.fieldValue = function(successful) {
954
+ for (var val=[], i=0, max=this.length; i < max; i++) {
955
+ var el = this[i];
956
+ var v = $.fieldValue(el, successful);
957
+ if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length)) {
958
+ continue;
959
+ }
960
+ if (v.constructor == Array)
961
+ $.merge(val, v);
962
+ else
963
+ val.push(v);
964
+ }
965
+ return val;
966
+ };
967
+
968
+ /**
969
+ * Returns the value of the field element.
970
+ */
971
+ $.fieldValue = function(el, successful) {
972
+ var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
973
+ if (successful === undefined) {
974
+ successful = true;
975
+ }
976
+
977
+ if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
978
+ (t == 'checkbox' || t == 'radio') && !el.checked ||
979
+ (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
980
+ tag == 'select' && el.selectedIndex == -1)) {
981
+ return null;
982
+ }
983
+
984
+ if (tag == 'select') {
985
+ var index = el.selectedIndex;
986
+ if (index < 0) {
987
+ return null;
988
+ }
989
+ var a = [], ops = el.options;
990
+ var one = (t == 'select-one');
991
+ var max = (one ? index+1 : ops.length);
992
+ for(var i=(one ? index : 0); i < max; i++) {
993
+ var op = ops[i];
994
+ if (op.selected) {
995
+ var v = op.value;
996
+ if (!v) { // extra pain for IE...
997
+ v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
998
+ }
999
+ if (one) {
1000
+ return v;
1001
+ }
1002
+ a.push(v);
1003
+ }
1004
+ }
1005
+ return a;
1006
+ }
1007
+ return $(el).val();
1008
+ };
1009
+
1010
+ /**
1011
+ * Clears the form data. Takes the following actions on the form's input fields:
1012
+ * - input text fields will have their 'value' property set to the empty string
1013
+ * - select elements will have their 'selectedIndex' property set to -1
1014
+ * - checkbox and radio inputs will have their 'checked' property set to false
1015
+ * - inputs of type submit, button, reset, and hidden will *not* be effected
1016
+ * - button elements will *not* be effected
1017
+ */
1018
+ $.fn.clearForm = function(includeHidden) {
1019
+ return this.each(function() {
1020
+ $('input,select,textarea', this).clearFields(includeHidden);
1021
+ });
1022
+ };
1023
+
1024
+ /**
1025
+ * Clears the selected form elements.
1026
+ */
1027
+ $.fn.clearFields = $.fn.clearInputs = function(includeHidden) {
1028
+ var re = /^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i; // 'hidden' is not in this list
1029
+ return this.each(function() {
1030
+ var t = this.type, tag = this.tagName.toLowerCase();
1031
+ if (re.test(t) || tag == 'textarea') {
1032
+ this.value = '';
1033
+ }
1034
+ else if (t == 'checkbox' || t == 'radio') {
1035
+ this.checked = false;
1036
+ }
1037
+ else if (tag == 'select') {
1038
+ this.selectedIndex = -1;
1039
+ }
1040
+ else if (includeHidden) {
1041
+ // includeHidden can be the value true, or it can be a selector string
1042
+ // indicating a special test; for example:
1043
+ // $('#myForm').clearForm('.special:hidden')
1044
+ // the above would clean hidden inputs that have the class of 'special'
1045
+ if ( (includeHidden === true && /hidden/.test(t)) ||
1046
+ (typeof includeHidden == 'string' && $(this).is(includeHidden)) )
1047
+ this.value = '';
1048
+ }
1049
+ });
1050
+ };
1051
+
1052
+ /**
1053
+ * Resets the form data. Causes all form elements to be reset to their original value.
1054
+ */
1055
+ $.fn.resetForm = function() {
1056
+ return this.each(function() {
1057
+ // guard against an input with the name of 'reset'
1058
+ // note that IE reports the reset function as an 'object'
1059
+ if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType)) {
1060
+ this.reset();
1061
+ }
1062
+ });
1063
+ };
1064
+
1065
+ /**
1066
+ * Enables or disables any matching elements.
1067
+ */
1068
+ $.fn.enable = function(b) {
1069
+ if (b === undefined) {
1070
+ b = true;
1071
+ }
1072
+ return this.each(function() {
1073
+ this.disabled = !b;
1074
+ });
1075
+ };
1076
+
1077
+ /**
1078
+ * Checks/unchecks any matching checkboxes or radio buttons and
1079
+ * selects/deselects and matching option elements.
1080
+ */
1081
+ $.fn.selected = function(select) {
1082
+ if (select === undefined) {
1083
+ select = true;
1084
+ }
1085
+ return this.each(function() {
1086
+ var t = this.type;
1087
+ if (t == 'checkbox' || t == 'radio') {
1088
+ this.checked = select;
1089
+ }
1090
+ else if (this.tagName.toLowerCase() == 'option') {
1091
+ var $sel = $(this).parent('select');
1092
+ if (select && $sel[0] && $sel[0].type == 'select-one') {
1093
+ // deselect all other options
1094
+ $sel.find('option').selected(false);
1095
+ }
1096
+ this.selected = select;
1097
+ }
1098
+ });
1099
+ };
1100
+
1101
+ // expose debug var
1102
+ $.fn.ajaxSubmit.debug = false;
1103
+
1104
+ // helper fn for console logging
1105
+ function log() {
1106
+ if (!$.fn.ajaxSubmit.debug)
1107
+ return;
1108
+ var msg = '[jquery.form] ' + Array.prototype.join.call(arguments,'');
1109
+ if (window.console && window.console.log) {
1110
+ window.console.log(msg);
1111
+ }
1112
+ else if (window.opera && window.opera.postError) {
1113
+ window.opera.postError(msg);
1114
+ }
1115
+ }
1116
+
1117
+ })(jQuery);