drogus-gadgeteer 0.3.0

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