drogus-gadgeteer 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/LICENSE ADDED
@@ -0,0 +1 @@
1
+ Copyright (c) 2009 László Bácsi, released under the MIT license
@@ -0,0 +1,49 @@
1
+ Gadgeteer simplifies OpenSocial Gadget development by giving you helpers you can use in your Rails application to verify Signed Requests and access OpenSocial data.
2
+
3
+ = Requirements
4
+
5
+ Gadgeteer requires the {oauth gem}[http://github.com/pelle/oauth/tree/master] (0.2.7+).
6
+
7
+ *Note*: the current version of the oauth gem doesn't comply completely with the OAuth standard, and also doesn't work with Rails 2.3</tt>. You can use {lackac's fork}[http://github.com/lackac/oauth/tree/master], until the fixes are merged in.
8
+
9
+ = Usage
10
+
11
+ You can configure the secrets and public keys used by your application two ways.
12
+
13
+ For consumer secrets you can put your consumer key/secret pairs into <tt>config/oauth_secrets.yml</tt>:
14
+
15
+ key: secret
16
+
17
+ or you could setup those in your ApplicationController:
18
+
19
+ class ApplicationController < ActionController::Base
20
+
21
+ oauth_secrets['key'] = 'secret'
22
+
23
+ end
24
+
25
+ For public keys you can put the certificates into <tt>config/certs</tt> with <tt>.cert</tt> extension, or you could setup the public keys in your ApplicationController by creating a <tt>OpenSSL::PKey::RSA</tt> object and adding it to the <tt>public_keys</tt> hash:
26
+
27
+ class ApplicationController < ActionController::Base
28
+
29
+ public_keys['example.com'] = OpenSSL::PKey::RSA.new(OpenSSL::X509::Certificate.new(CERT).public_key)
30
+
31
+ end
32
+
33
+ You can use the <tt>verify_signature</tt> method as a before_filter in your controllers to make sure the signed requests are correct:
34
+
35
+ class SecretNotesController < ActionController::Base
36
+
37
+ before_filter :verify_signature
38
+
39
+ end
40
+
41
+ The correct secret or public key will be used for verification based on the current request. If the <tt>xoauth_signature_publickey</tt> parameter is set, the corresponding public key will be used. Otherwise the consumer secret connected to the key found in the <tt>oauth_consumer_key</tt> parameter will be used. The singature will be verified based on this key/secret pair and the singature method set in the parameters.
42
+
43
+ If there are OpenSocial related request parameters, you can access them with the <tt>open_social</tt> method:
44
+
45
+ def index
46
+ @secret_notes = SecretNote.find_by_profile_id(open_social[:viewer_id])
47
+ end
48
+
49
+ Copyright (c) 2009 László Bácsi, released under the MIT license
@@ -0,0 +1,42 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+ require 'rcov/rcovtask'
5
+
6
+ begin
7
+ require 'jeweler'
8
+ Jeweler::Tasks.new do |s|
9
+ s.name = "gadgeteer"
10
+ s.summary = %Q{Making it easy to develop OpenSocial gadgets with Rails or Sinatra.}
11
+ s.email = "bacsi.laszlo@virgo.hu"
12
+ s.homepage = "http://github.com/virgo/gadgeteer"
13
+ s.description = "Making it easy to develop OpenSocial gadgets with Rails or Sinatra."
14
+ s.authors = ["Laszlo Bacsi"]
15
+ s.executables = ["gadgeteer"]
16
+ s.files = FileList['lib/**/*.rb', 'bin/*', '[A-Z]*', 'test/**/*', 'templates/*', 'javascripts/*.js', 'rails/*'].to_a
17
+ end
18
+ rescue LoadError
19
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
20
+ end
21
+
22
+ Rake::TestTask.new do |t|
23
+ t.libs << 'lib'
24
+ t.pattern = 'test/**/*_test.rb'
25
+ t.verbose = false
26
+ end
27
+
28
+ Rake::RDocTask.new do |rdoc|
29
+ rdoc.rdoc_dir = 'rdoc'
30
+ rdoc.title = 'gadgeteer'
31
+ rdoc.options << '--line-numbers' << '--inline-source'
32
+ rdoc.rdoc_files.include('README*')
33
+ rdoc.rdoc_files.include('lib/**/*.rb')
34
+ end
35
+
36
+ Rcov::RcovTask.new do |t|
37
+ t.libs << 'test'
38
+ t.test_files = FileList['test/**/*_test.rb']
39
+ t.verbose = true
40
+ end
41
+
42
+ task :default => :rcov
@@ -0,0 +1,4 @@
1
+ ---
2
+ :patch: 0
3
+ :major: 0
4
+ :minor: 3
@@ -0,0 +1,229 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $usage = <<USAGE
4
+ == Synopsis
5
+ This script generates some files for you to start coding your gadget with.
6
+
7
+ == Examples
8
+ For a Rails project:
9
+ gadgeteer --rails Gadget
10
+
11
+ For a Sinatra project:
12
+ gadgeteer --sinatra Gadget
13
+
14
+ == Usage
15
+ gadgeteer <--rails|--sinatra> [options] ModelName
16
+
17
+ For help use: gadgeteer -h
18
+
19
+ == Options
20
+ -h, --help Displays help message
21
+ -V, --version Display the version, then exit
22
+ -q, --quiet Output as little as possible, overrides verbose
23
+ -v, --verbose Verbose output
24
+ -r, --rails Generates files for a Rails app
25
+ -s, --sinatra Generates files for a Sinatra app
26
+ -f, --force Overwrite existing files
27
+ -a, --author me Name of the author (for gadget.xml)
28
+ -e, --email i@me.us Email address of the author (for gadget.xml)
29
+
30
+ == Author
31
+ László Bácsi
32
+
33
+ == Copyright
34
+ Copyright (c) 2009 László Bácsi. Licensed under the MIT License:
35
+ http://www.opensource.org/licenses/mit-license.php
36
+ USAGE
37
+
38
+ require 'optparse'
39
+ require 'rdoc/usage'
40
+ require 'ostruct'
41
+ require 'date'
42
+
43
+ require 'erb'
44
+ require 'yaml'
45
+ require 'activesupport'
46
+
47
+ module Gadgeteer
48
+ class App
49
+ VERSION_HASH = YAML.load_file(File.join(File.dirname(__FILE__), '..', 'VERSION.yml'))
50
+ VERSION = "#{VERSION_HASH[:major]}.#{VERSION_HASH[:minor]}.#{VERSION_HASH[:patch]}"
51
+
52
+ MAPPING = {
53
+ :rails => {
54
+ :files => {
55
+ "gadget.yml" => "config/:singular.yml",
56
+ "gadget.rb" => "app/models/:singular.rb",
57
+ "gadget.haml" => "app/views/:plural/show.xml.haml",
58
+ "canvas.haml" => "app/views/:plural/_canvas.html.haml",
59
+ "gadget.js" => "public/javascripts/:singular.js",
60
+ "gadgets_controller.rb" => "app/controllers/:plural_controller.rb"
61
+ },
62
+ :help => <<-HELP
63
+ Add this to your Rails application:
64
+
65
+ # config/environment.rb
66
+ config.gem "gadgeteer"
67
+ config.gem "haml"
68
+
69
+ # config/routes.rb
70
+ map.resource ::singular
71
+ HELP
72
+ },
73
+ :sinatra => {
74
+ :files => {
75
+ "gadget.yml" => "config/:singular.yml",
76
+ "gadget.rb" => ":singular.rb",
77
+ "gadget.haml" => "views/:singular.haml",
78
+ "canvas.haml" => "views/canvas.haml",
79
+ "gadget.js" => "public/javascripts/:singular.js"
80
+ },
81
+ :help => <<-HELP
82
+ Add this to your Sinatra application:
83
+
84
+ require 'sinatra/gadgeteer'
85
+ require ':singular'
86
+
87
+ get '/:singular.xml' do
88
+ haml ::singular
89
+ end
90
+ HELP
91
+ }
92
+ }
93
+
94
+ attr_reader :options
95
+
96
+ def initialize(arguments, stdin)
97
+ @arguments = arguments
98
+ @stdin = stdin
99
+
100
+ # Set defaults
101
+ @options = OpenStruct.new
102
+ @options.verbose = true
103
+ @options.quiet = false
104
+ @options.author = "Calvin"
105
+ @options.email = "calvin@example.com"
106
+ end
107
+
108
+ # Parse options, check arguments, then process the command
109
+ def run
110
+ if parsed_options? && arguments_valid?
111
+ process_arguments
112
+ process_command
113
+ else
114
+ output_usage
115
+ end
116
+ end
117
+
118
+ protected
119
+
120
+ def parsed_options?
121
+ # Specify options
122
+ opts = OptionParser.new
123
+ opts.on('-V', '--version') { output_version ; exit 0 }
124
+ opts.on('-h', '--help') { output_help }
125
+ opts.on('-v', '--verbose') { @options.verbose = true }
126
+ opts.on('-q', '--quiet') { @options.quiet = true }
127
+ opts.on('-r', '--rails') { @options.rails = true }
128
+ opts.on('-s', '--sinatra') { @options.sinatra = true }
129
+ opts.on('-f', '--force') { @options.force = true }
130
+ opts.on('-a', '--author AUTHOR') { |x| @options.author = x }
131
+ opts.on('-e', '--email EMAIL') { |x| @options.email = x }
132
+
133
+ opts.parse!(@arguments) rescue return false
134
+
135
+ process_options
136
+ end
137
+
138
+ # Performs post-parse processing on options
139
+ def process_options
140
+ @options.verbose = false if @options.quiet
141
+ @options.rails ^ @options.sinatra
142
+ end
143
+
144
+ # True if required arguments were provided
145
+ def arguments_valid?
146
+ true if @arguments.length == 1
147
+ end
148
+
149
+ # Setup the arguments
150
+ def process_arguments
151
+ @options.model = @arguments.first.classify
152
+ @options.title = @options.model.underscore.humanize
153
+ @options.singular = @options.model.underscore
154
+ @options.plural = @options.model.tableize
155
+ end
156
+
157
+ def output_help
158
+ output_version
159
+ usage_and_exit! #exits app
160
+ end
161
+
162
+ def output_usage
163
+ usage_and_exit! # gets usage from comments above
164
+ end
165
+
166
+ # Shamefully stolen from RDoc#usage. We cannot use RDoc::usage because
167
+ # RubyGems will call this from a wrapper, and #usage is hardcoded to look
168
+ # at the top-level file instead of the current one. I have changed this
169
+ # code to instead just parse a string.
170
+ def usage_and_exit!
171
+ markup = SM::SimpleMarkup.new
172
+ flow_convertor = SM::ToFlow.new
173
+
174
+ flow = markup.convert($usage, flow_convertor)
175
+
176
+ options = RI::Options.instance
177
+ formatter = options.formatter.new(options, "")
178
+ formatter.display_flow(flow)
179
+
180
+ exit(0)
181
+ end
182
+
183
+ def output_version
184
+ puts "#{$0} version #{VERSION}"
185
+ end
186
+
187
+ def process_command
188
+ key = @options.rails ? :rails : :sinatra
189
+ file_mapping = MAPPING[key][:files]
190
+ help = MAPPING[key][:help]
191
+
192
+ # Templates
193
+ file_mapping.each do |filename, target|
194
+ template = File.join(File.dirname(__FILE__), '..', 'templates', filename)
195
+ target = target.gsub(':singular', @options.singular).gsub(':plural', @options.plural)
196
+ if File.exists?(target) and not @options.force
197
+ puts "Skipping existing file #{target}" if @options.verbose
198
+ else
199
+ puts "Writing file #{target}" if @options.verbose
200
+ FileUtils.mkdir_p(File.dirname(target))
201
+ File.open(target, "w") do |f|
202
+ f.write(ERB.new(File.read(template), nil, "<>").result(binding))
203
+ end
204
+ end
205
+ end
206
+
207
+ # Javascript files
208
+ Dir[File.join(File.dirname(__FILE__), '..', 'javascripts', '*.js')].each do |jsfile|
209
+ filename = File.basename(jsfile)
210
+ target = "public/javascripts/#{filename}"
211
+ if File.exists?(target) and not @options.force
212
+ puts "Skipping existing file #{target}" if @options.verbose
213
+ else
214
+ puts "Writing file #{target}" if @options.verbose
215
+ FileUtils.mkdir_p(File.dirname(target))
216
+ FileUtils.cp jsfile, target
217
+ end
218
+ end
219
+
220
+ # Help
221
+ puts
222
+ puts(help.gsub(':singular', @options.singular).gsub(':plural', @options.plural))
223
+ end
224
+ end
225
+ end
226
+
227
+ # Create and run the application
228
+ app = Gadgeteer::App.new(ARGV, STDIN)
229
+ app.run
@@ -0,0 +1,643 @@
1
+ /*
2
+ * jQuery Form Plugin
3
+ * version: 2.28 (10-MAY-2009)
4
+ * @requires jQuery v1.2.2 or later
5
+ *
6
+ * Examples and documentation 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
+ ;(function($) {
12
+
13
+ /*
14
+ Usage Note:
15
+ -----------
16
+ Do not use both ajaxSubmit and ajaxForm on the same form. These
17
+ functions are intended to be exclusive. Use ajaxSubmit if you want
18
+ to bind your own submit handler to the form. For example,
19
+
20
+ $(document).ready(function() {
21
+ $('#myForm').bind('submit', function() {
22
+ $(this).ajaxSubmit({
23
+ target: '#output'
24
+ });
25
+ return false; // <-- important!
26
+ });
27
+ });
28
+
29
+ Use ajaxForm when you want the plugin to manage all the event binding
30
+ for you. For example,
31
+
32
+ $(document).ready(function() {
33
+ $('#myForm').ajaxForm({
34
+ target: '#output'
35
+ });
36
+ });
37
+
38
+ When using ajaxForm, the ajaxSubmit function will be invoked for you
39
+ at the appropriate time.
40
+ */
41
+
42
+ /**
43
+ * ajaxSubmit() provides a mechanism for immediately submitting
44
+ * an HTML form using AJAX.
45
+ */
46
+ $.fn.ajaxSubmit = function(options) {
47
+ // fast fail if nothing selected (http://dev.jquery.com/ticket/2752)
48
+ if (!this.length) {
49
+ log('ajaxSubmit: skipping submit process - no element selected');
50
+ return this;
51
+ }
52
+
53
+ if (typeof options == 'function')
54
+ options = { success: options };
55
+
56
+ var url = $.trim(this.attr('action'));
57
+ if (url) {
58
+ // clean url (don't include hash vaue)
59
+ url = (url.match(/^([^#]+)/)||[])[1];
60
+ }
61
+ url = url || window.location.href || ''
62
+
63
+ options = $.extend({
64
+ url: url,
65
+ type: this.attr('method') || 'GET'
66
+ }, options || {});
67
+
68
+ // hook for manipulating the form data before it is extracted;
69
+ // convenient for use with rich editors like tinyMCE or FCKEditor
70
+ var veto = {};
71
+ this.trigger('form-pre-serialize', [this, options, veto]);
72
+ if (veto.veto) {
73
+ log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
74
+ return this;
75
+ }
76
+
77
+ // provide opportunity to alter form data before it is serialized
78
+ if (options.beforeSerialize && options.beforeSerialize(this, options) === false) {
79
+ log('ajaxSubmit: submit aborted via beforeSerialize callback');
80
+ return this;
81
+ }
82
+
83
+ var a = this.formToArray(options.semantic);
84
+ if (options.data) {
85
+ options.extraData = options.data;
86
+ for (var n in options.data) {
87
+ if(options.data[n] instanceof Array) {
88
+ for (var k in options.data[n])
89
+ a.push( { name: n, value: options.data[n][k] } );
90
+ }
91
+ else
92
+ a.push( { name: n, value: options.data[n] } );
93
+ }
94
+ }
95
+
96
+ // give pre-submit callback an opportunity to abort the submit
97
+ if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) {
98
+ log('ajaxSubmit: submit aborted via beforeSubmit callback');
99
+ return this;
100
+ }
101
+
102
+ // fire vetoable 'validate' event
103
+ this.trigger('form-submit-validate', [a, this, options, veto]);
104
+ if (veto.veto) {
105
+ log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
106
+ return this;
107
+ }
108
+
109
+ var q = $.param(a);
110
+
111
+ if (options.type.toUpperCase() == 'GET') {
112
+ options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q;
113
+ options.data = null; // data is null for 'get'
114
+ }
115
+ else
116
+ options.data = q; // data is the query string for 'post'
117
+
118
+ var $form = this, callbacks = [];
119
+ if (options.resetForm) callbacks.push(function() { $form.resetForm(); });
120
+ if (options.clearForm) callbacks.push(function() { $form.clearForm(); });
121
+
122
+ // perform a load on the target only if dataType is not provided
123
+ if (!options.dataType && options.target) {
124
+ var oldSuccess = options.success || function(){};
125
+ callbacks.push(function(data) {
126
+ $(options.target).html(data).each(oldSuccess, arguments);
127
+ });
128
+ }
129
+ else if (options.success)
130
+ callbacks.push(options.success);
131
+
132
+ options.success = function(data, status) {
133
+ for (var i=0, max=callbacks.length; i < max; i++)
134
+ callbacks[i].apply(options, [data, status, $form]);
135
+ };
136
+
137
+ // are there files to upload?
138
+ var files = $('input:file', this).fieldValue();
139
+ var found = false;
140
+ for (var j=0; j < files.length; j++)
141
+ if (files[j])
142
+ found = true;
143
+
144
+ var multipart = false;
145
+ // var mp = 'multipart/form-data';
146
+ // multipart = ($form.attr('enctype') == mp || $form.attr('encoding') == mp);
147
+
148
+ // options.iframe allows user to force iframe mode
149
+ if (options.iframe || found || multipart) {
150
+ // hack to fix Safari hang (thanks to Tim Molendijk for this)
151
+ // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
152
+ if (options.closeKeepAlive)
153
+ $.get(options.closeKeepAlive, fileUpload);
154
+ else
155
+ fileUpload();
156
+ }
157
+ else
158
+ $.ajax(options);
159
+
160
+ // fire 'notify' event
161
+ this.trigger('form-submit-notify', [this, options]);
162
+ return this;
163
+
164
+
165
+ // private function for handling file uploads (hat tip to YAHOO!)
166
+ function fileUpload() {
167
+ var form = $form[0];
168
+
169
+ if ($(':input[name=submit]', form).length) {
170
+ alert('Error: Form elements must not be named "submit".');
171
+ return;
172
+ }
173
+
174
+ var opts = $.extend({}, $.ajaxSettings, options);
175
+ var s = $.extend(true, {}, $.extend(true, {}, $.ajaxSettings), opts);
176
+
177
+ var id = 'jqFormIO' + (new Date().getTime());
178
+ var $io = $('<iframe id="' + id + '" name="' + id + '" src="about:blank" />');
179
+ var io = $io[0];
180
+
181
+ $io.css({ position: 'absolute', top: '-1000px', left: '-1000px' });
182
+
183
+ var xhr = { // mock object
184
+ aborted: 0,
185
+ responseText: null,
186
+ responseXML: null,
187
+ status: 0,
188
+ statusText: 'n/a',
189
+ getAllResponseHeaders: function() {},
190
+ getResponseHeader: function() {},
191
+ setRequestHeader: function() {},
192
+ abort: function() {
193
+ this.aborted = 1;
194
+ $io.attr('src','about:blank'); // abort op in progress
195
+ }
196
+ };
197
+
198
+ var g = opts.global;
199
+ // trigger ajax global events so that activity/block indicators work like normal
200
+ if (g && ! $.active++) $.event.trigger("ajaxStart");
201
+ if (g) $.event.trigger("ajaxSend", [xhr, opts]);
202
+
203
+ if (s.beforeSend && s.beforeSend(xhr, s) === false) {
204
+ s.global && $.active--;
205
+ return;
206
+ }
207
+ if (xhr.aborted)
208
+ return;
209
+
210
+ var cbInvoked = 0;
211
+ var timedOut = 0;
212
+
213
+ // add submitting element to data if we know it
214
+ var sub = form.clk;
215
+ if (sub) {
216
+ var n = sub.name;
217
+ if (n && !sub.disabled) {
218
+ options.extraData = options.extraData || {};
219
+ options.extraData[n] = sub.value;
220
+ if (sub.type == "image") {
221
+ options.extraData[name+'.x'] = form.clk_x;
222
+ options.extraData[name+'.y'] = form.clk_y;
223
+ }
224
+ }
225
+ }
226
+
227
+ // take a breath so that pending repaints get some cpu time before the upload starts
228
+ setTimeout(function() {
229
+ // make sure form attrs are set
230
+ var t = $form.attr('target'), a = $form.attr('action');
231
+
232
+ // update form attrs in IE friendly way
233
+ form.setAttribute('target',id);
234
+ if (form.getAttribute('method') != 'POST')
235
+ form.setAttribute('method', 'POST');
236
+ if (form.getAttribute('action') != opts.url)
237
+ form.setAttribute('action', opts.url);
238
+
239
+ // ie borks in some cases when setting encoding
240
+ if (! options.skipEncodingOverride) {
241
+ $form.attr({
242
+ encoding: 'multipart/form-data',
243
+ enctype: 'multipart/form-data'
244
+ });
245
+ }
246
+
247
+ // support timout
248
+ if (opts.timeout)
249
+ setTimeout(function() { timedOut = true; cb(); }, opts.timeout);
250
+
251
+ // add "extra" data to form if provided in options
252
+ var extraInputs = [];
253
+ try {
254
+ if (options.extraData)
255
+ for (var n in options.extraData)
256
+ extraInputs.push(
257
+ $('<input type="hidden" name="'+n+'" value="'+options.extraData[n]+'" />')
258
+ .appendTo(form)[0]);
259
+
260
+ // add iframe to doc and submit the form
261
+ $io.appendTo('body');
262
+ io.attachEvent ? io.attachEvent('onload', cb) : io.addEventListener('load', cb, false);
263
+ form.submit();
264
+ }
265
+ finally {
266
+ // reset attrs and remove "extra" input elements
267
+ form.setAttribute('action',a);
268
+ t ? form.setAttribute('target', t) : $form.removeAttr('target');
269
+ $(extraInputs).remove();
270
+ }
271
+ }, 10);
272
+
273
+ var nullCheckFlag = 0;
274
+
275
+ function cb() {
276
+ if (cbInvoked++) return;
277
+
278
+ io.detachEvent ? io.detachEvent('onload', cb) : io.removeEventListener('load', cb, false);
279
+
280
+ var ok = true;
281
+ try {
282
+ if (timedOut) throw 'timeout';
283
+ // extract the server response from the iframe
284
+ var data, doc;
285
+
286
+ doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document;
287
+
288
+ if ((doc.body == null || doc.body.innerHTML == '') && !nullCheckFlag) {
289
+ // in some browsers (cough, Opera 9.2.x) the iframe DOM is not always traversable when
290
+ // the onload callback fires, so we give them a 2nd chance
291
+ nullCheckFlag = 1;
292
+ cbInvoked--;
293
+ setTimeout(cb, 100);
294
+ return;
295
+ }
296
+
297
+ xhr.responseText = doc.body ? doc.body.innerHTML : null;
298
+ xhr.responseXML = doc.XMLDocument ? doc.XMLDocument : doc;
299
+ xhr.getResponseHeader = function(header){
300
+ var headers = {'content-type': opts.dataType};
301
+ return headers[header];
302
+ };
303
+
304
+ if (opts.dataType == 'json' || opts.dataType == 'script') {
305
+ var ta = doc.getElementsByTagName('textarea')[0];
306
+ xhr.responseText = ta ? ta.value : xhr.responseText;
307
+ }
308
+ else if (opts.dataType == 'xml' && !xhr.responseXML && xhr.responseText != null) {
309
+ xhr.responseXML = toXml(xhr.responseText);
310
+ }
311
+ data = $.httpData(xhr, opts.dataType);
312
+ }
313
+ catch(e){
314
+ ok = false;
315
+ $.handleError(opts, xhr, 'error', e);
316
+ }
317
+
318
+ // ordering of these callbacks/triggers is odd, but that's how $.ajax does it
319
+ if (ok) {
320
+ opts.success(data, 'success');
321
+ if (g) $.event.trigger("ajaxSuccess", [xhr, opts]);
322
+ }
323
+ if (g) $.event.trigger("ajaxComplete", [xhr, opts]);
324
+ if (g && ! --$.active) $.event.trigger("ajaxStop");
325
+ if (opts.complete) opts.complete(xhr, ok ? 'success' : 'error');
326
+
327
+ // clean up
328
+ setTimeout(function() {
329
+ $io.remove();
330
+ xhr.responseXML = null;
331
+ }, 100);
332
+ };
333
+
334
+ function toXml(s, doc) {
335
+ if (window.ActiveXObject) {
336
+ doc = new ActiveXObject('Microsoft.XMLDOM');
337
+ doc.async = 'false';
338
+ doc.loadXML(s);
339
+ }
340
+ else
341
+ doc = (new DOMParser()).parseFromString(s, 'text/xml');
342
+ return (doc && doc.documentElement && doc.documentElement.tagName != 'parsererror') ? doc : null;
343
+ };
344
+ };
345
+ };
346
+
347
+ /**
348
+ * ajaxForm() provides a mechanism for fully automating form submission.
349
+ *
350
+ * The advantages of using this method instead of ajaxSubmit() are:
351
+ *
352
+ * 1: This method will include coordinates for <input type="image" /> elements (if the element
353
+ * is used to submit the form).
354
+ * 2. This method will include the submit element's name/value data (for the element that was
355
+ * used to submit the form).
356
+ * 3. This method binds the submit() method to the form for you.
357
+ *
358
+ * The options argument for ajaxForm works exactly as it does for ajaxSubmit. ajaxForm merely
359
+ * passes the options argument along after properly binding events for submit elements and
360
+ * the form itself.
361
+ */
362
+ $.fn.ajaxForm = function(options) {
363
+ return this.ajaxFormUnbind().bind('submit.form-plugin',function() {
364
+ $(this).ajaxSubmit(options);
365
+ return false;
366
+ }).each(function() {
367
+ // store options in hash
368
+ $(":submit,input:image", this).bind('click.form-plugin',function(e) {
369
+ var form = this.form;
370
+ form.clk = this;
371
+ if (this.type == 'image') {
372
+ if (e.offsetX != undefined) {
373
+ form.clk_x = e.offsetX;
374
+ form.clk_y = e.offsetY;
375
+ } else if (typeof $.fn.offset == 'function') { // try to use dimensions plugin
376
+ var offset = $(this).offset();
377
+ form.clk_x = e.pageX - offset.left;
378
+ form.clk_y = e.pageY - offset.top;
379
+ } else {
380
+ form.clk_x = e.pageX - this.offsetLeft;
381
+ form.clk_y = e.pageY - this.offsetTop;
382
+ }
383
+ }
384
+ // clear form vars
385
+ setTimeout(function() { form.clk = form.clk_x = form.clk_y = null; }, 10);
386
+ });
387
+ });
388
+ };
389
+
390
+ // ajaxFormUnbind unbinds the event handlers that were bound by ajaxForm
391
+ $.fn.ajaxFormUnbind = function() {
392
+ this.unbind('submit.form-plugin');
393
+ return this.each(function() {
394
+ $(":submit,input:image", this).unbind('click.form-plugin');
395
+ });
396
+
397
+ };
398
+
399
+ /**
400
+ * formToArray() gathers form element data into an array of objects that can
401
+ * be passed to any of the following ajax functions: $.get, $.post, or load.
402
+ * Each object in the array has both a 'name' and 'value' property. An example of
403
+ * an array for a simple login form might be:
404
+ *
405
+ * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ]
406
+ *
407
+ * It is this array that is passed to pre-submit callback functions provided to the
408
+ * ajaxSubmit() and ajaxForm() methods.
409
+ */
410
+ $.fn.formToArray = function(semantic) {
411
+ var a = [];
412
+ if (this.length == 0) return a;
413
+
414
+ var form = this[0];
415
+ var els = semantic ? form.getElementsByTagName('*') : form.elements;
416
+ if (!els) return a;
417
+ for(var i=0, max=els.length; i < max; i++) {
418
+ var el = els[i];
419
+ var n = el.name;
420
+ if (!n) continue;
421
+
422
+ if (semantic && form.clk && el.type == "image") {
423
+ // handle image inputs on the fly when semantic == true
424
+ if(!el.disabled && form.clk == el) {
425
+ a.push({name: n, value: $(el).val()});
426
+ a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
427
+ }
428
+ continue;
429
+ }
430
+
431
+ var v = $.fieldValue(el, true);
432
+ if (v && v.constructor == Array) {
433
+ for(var j=0, jmax=v.length; j < jmax; j++)
434
+ a.push({name: n, value: v[j]});
435
+ }
436
+ else if (v !== null && typeof v != 'undefined')
437
+ a.push({name: n, value: v});
438
+ }
439
+
440
+ if (!semantic && form.clk) {
441
+ // input type=='image' are not found in elements array! handle it here
442
+ var $input = $(form.clk), input = $input[0], n = input.name;
443
+ if (n && !input.disabled && input.type == 'image') {
444
+ a.push({name: n, value: $input.val()});
445
+ a.push({name: n+'.x', value: form.clk_x}, {name: n+'.y', value: form.clk_y});
446
+ }
447
+ }
448
+ return a;
449
+ };
450
+
451
+ /**
452
+ * Serializes form data into a 'submittable' string. This method will return a string
453
+ * in the format: name1=value1&amp;name2=value2
454
+ */
455
+ $.fn.formSerialize = function(semantic) {
456
+ //hand off to jQuery.param for proper encoding
457
+ return $.param(this.formToArray(semantic));
458
+ };
459
+
460
+ /**
461
+ * Serializes all field elements in the jQuery object into a query string.
462
+ * This method will return a string in the format: name1=value1&amp;name2=value2
463
+ */
464
+ $.fn.fieldSerialize = function(successful) {
465
+ var a = [];
466
+ this.each(function() {
467
+ var n = this.name;
468
+ if (!n) return;
469
+ var v = $.fieldValue(this, successful);
470
+ if (v && v.constructor == Array) {
471
+ for (var i=0,max=v.length; i < max; i++)
472
+ a.push({name: n, value: v[i]});
473
+ }
474
+ else if (v !== null && typeof v != 'undefined')
475
+ a.push({name: this.name, value: v});
476
+ });
477
+ //hand off to jQuery.param for proper encoding
478
+ return $.param(a);
479
+ };
480
+
481
+ /**
482
+ * Returns the value(s) of the element in the matched set. For example, consider the following form:
483
+ *
484
+ * <form><fieldset>
485
+ * <input name="A" type="text" />
486
+ * <input name="A" type="text" />
487
+ * <input name="B" type="checkbox" value="B1" />
488
+ * <input name="B" type="checkbox" value="B2"/>
489
+ * <input name="C" type="radio" value="C1" />
490
+ * <input name="C" type="radio" value="C2" />
491
+ * </fieldset></form>
492
+ *
493
+ * var v = $(':text').fieldValue();
494
+ * // if no values are entered into the text inputs
495
+ * v == ['','']
496
+ * // if values entered into the text inputs are 'foo' and 'bar'
497
+ * v == ['foo','bar']
498
+ *
499
+ * var v = $(':checkbox').fieldValue();
500
+ * // if neither checkbox is checked
501
+ * v === undefined
502
+ * // if both checkboxes are checked
503
+ * v == ['B1', 'B2']
504
+ *
505
+ * var v = $(':radio').fieldValue();
506
+ * // if neither radio is checked
507
+ * v === undefined
508
+ * // if first radio is checked
509
+ * v == ['C1']
510
+ *
511
+ * The successful argument controls whether or not the field element must be 'successful'
512
+ * (per http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
513
+ * The default value of the successful argument is true. If this value is false the value(s)
514
+ * for each element is returned.
515
+ *
516
+ * Note: This method *always* returns an array. If no valid value can be determined the
517
+ * array will be empty, otherwise it will contain one or more values.
518
+ */
519
+ $.fn.fieldValue = function(successful) {
520
+ for (var val=[], i=0, max=this.length; i < max; i++) {
521
+ var el = this[i];
522
+ var v = $.fieldValue(el, successful);
523
+ if (v === null || typeof v == 'undefined' || (v.constructor == Array && !v.length))
524
+ continue;
525
+ v.constructor == Array ? $.merge(val, v) : val.push(v);
526
+ }
527
+ return val;
528
+ };
529
+
530
+ /**
531
+ * Returns the value of the field element.
532
+ */
533
+ $.fieldValue = function(el, successful) {
534
+ var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
535
+ if (typeof successful == 'undefined') successful = true;
536
+
537
+ if (successful && (!n || el.disabled || t == 'reset' || t == 'button' ||
538
+ (t == 'checkbox' || t == 'radio') && !el.checked ||
539
+ (t == 'submit' || t == 'image') && el.form && el.form.clk != el ||
540
+ tag == 'select' && el.selectedIndex == -1))
541
+ return null;
542
+
543
+ if (tag == 'select') {
544
+ var index = el.selectedIndex;
545
+ if (index < 0) return null;
546
+ var a = [], ops = el.options;
547
+ var one = (t == 'select-one');
548
+ var max = (one ? index+1 : ops.length);
549
+ for(var i=(one ? index : 0); i < max; i++) {
550
+ var op = ops[i];
551
+ if (op.selected) {
552
+ var v = op.value;
553
+ if (!v) // extra pain for IE...
554
+ v = (op.attributes && op.attributes['value'] && !(op.attributes['value'].specified)) ? op.text : op.value;
555
+ if (one) return v;
556
+ a.push(v);
557
+ }
558
+ }
559
+ return a;
560
+ }
561
+ return el.value;
562
+ };
563
+
564
+ /**
565
+ * Clears the form data. Takes the following actions on the form's input fields:
566
+ * - input text fields will have their 'value' property set to the empty string
567
+ * - select elements will have their 'selectedIndex' property set to -1
568
+ * - checkbox and radio inputs will have their 'checked' property set to false
569
+ * - inputs of type submit, button, reset, and hidden will *not* be effected
570
+ * - button elements will *not* be effected
571
+ */
572
+ $.fn.clearForm = function() {
573
+ return this.each(function() {
574
+ $('input,select,textarea', this).clearFields();
575
+ });
576
+ };
577
+
578
+ /**
579
+ * Clears the selected form elements.
580
+ */
581
+ $.fn.clearFields = $.fn.clearInputs = function() {
582
+ return this.each(function() {
583
+ var t = this.type, tag = this.tagName.toLowerCase();
584
+ if (t == 'text' || t == 'password' || tag == 'textarea')
585
+ this.value = '';
586
+ else if (t == 'checkbox' || t == 'radio')
587
+ this.checked = false;
588
+ else if (tag == 'select')
589
+ this.selectedIndex = -1;
590
+ });
591
+ };
592
+
593
+ /**
594
+ * Resets the form data. Causes all form elements to be reset to their original value.
595
+ */
596
+ $.fn.resetForm = function() {
597
+ return this.each(function() {
598
+ // guard against an input with the name of 'reset'
599
+ // note that IE reports the reset function as an 'object'
600
+ if (typeof this.reset == 'function' || (typeof this.reset == 'object' && !this.reset.nodeType))
601
+ this.reset();
602
+ });
603
+ };
604
+
605
+ /**
606
+ * Enables or disables any matching elements.
607
+ */
608
+ $.fn.enable = function(b) {
609
+ if (b == undefined) b = true;
610
+ return this.each(function() {
611
+ this.disabled = !b;
612
+ });
613
+ };
614
+
615
+ /**
616
+ * Checks/unchecks any matching checkboxes or radio buttons and
617
+ * selects/deselects and matching option elements.
618
+ */
619
+ $.fn.selected = function(select) {
620
+ if (select == undefined) select = true;
621
+ return this.each(function() {
622
+ var t = this.type;
623
+ if (t == 'checkbox' || t == 'radio')
624
+ this.checked = select;
625
+ else if (this.tagName.toLowerCase() == 'option') {
626
+ var $sel = $(this).parent('select');
627
+ if (select && $sel[0] && $sel[0].type == 'select-one') {
628
+ // deselect all other options
629
+ $sel.find('option').selected(false);
630
+ }
631
+ this.selected = select;
632
+ }
633
+ });
634
+ };
635
+
636
+ // helper fn for console logging
637
+ // set $.fn.ajaxSubmit.debug to true to enable debug logging
638
+ function log() {
639
+ if ($.fn.ajaxSubmit.debug && window.console && window.console.log)
640
+ window.console.log('[jquery.form] ' + Array.prototype.join.call(arguments,''));
641
+ };
642
+
643
+ })(jQuery);