From a67ae9fac65fb3503cf350117556277383360ea2 Mon Sep 17 00:00:00 2001 From: Dr Nic Williams Date: Sat, 26 Apr 2008 12:25:25 +1000 Subject: [PATCH] initial import --- History.txt | 4 + License.txt | 20 + README.txt | 13 + Rakefile | 121 + config/javascript_test_autotest.yml | 15 + lib/jstest.rb | 382 +++ lib/protodoc.rb | 36 + script/destroy | 14 + script/generate | 14 + script/js_autotest | 1 + script/rstakeout | 98 + src/HEADER | 8 + src/ext/jquery.ajax_cache.js | 28 + src/ext/jquery.form.js | 869 ++++++ src/ext/jquery.jcache.js | 137 + src/ext/jquery.js | 3408 +++++++++++++++++++++ src/ext/jquery.livequery.js | 250 ++ src/ext/jquery.template.js | 1 + src/github_badges.js | 7 + src/user_badge.js | 36 + tasks/deploy.rake | 29 + tasks/environment.rake | 7 + tasks/javascript_test_autotest_tasks.rake | 45 + test/assets/jsunittest.js | 964 ++++++ test/assets/unittest.css | 50 + test/unit/user_badge_test.html | 73 + 26 files changed, 6630 insertions(+) create mode 100644 History.txt create mode 100644 License.txt create mode 100644 README.txt create mode 100644 Rakefile create mode 100644 config/javascript_test_autotest.yml create mode 100644 lib/jstest.rb create mode 100644 lib/protodoc.rb create mode 100755 script/destroy create mode 100755 script/generate create mode 100755 script/js_autotest create mode 100755 script/rstakeout create mode 100644 src/HEADER create mode 100644 src/ext/jquery.ajax_cache.js create mode 100644 src/ext/jquery.form.js create mode 100644 src/ext/jquery.jcache.js create mode 100644 src/ext/jquery.js create mode 100644 src/ext/jquery.livequery.js create mode 100644 src/ext/jquery.template.js create mode 100644 src/github_badges.js create mode 100644 src/user_badge.js create mode 100644 tasks/deploy.rake create mode 100644 tasks/environment.rake create mode 100644 tasks/javascript_test_autotest_tasks.rake create mode 100644 test/assets/jsunittest.js create mode 100644 test/assets/unittest.css create mode 100644 test/unit/user_badge_test.html diff --git a/History.txt b/History.txt new file mode 100644 index 0000000..472bd0a --- /dev/null +++ b/History.txt @@ -0,0 +1,4 @@ +== 0.0.1 2008-04-26 + +* 1 major enhancement: + * Initial release diff --git a/License.txt b/License.txt new file mode 100644 index 0000000..5218ebd --- /dev/null +++ b/License.txt @@ -0,0 +1,20 @@ +Copyright (c) 2008 Dr Nic Williams + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..59d14fa --- /dev/null +++ b/README.txt @@ -0,0 +1,13 @@ +Github Badges + +Description: + This JavaScript project ... + +Example: + ... + +More information: + http://NOTE-ENTER-URL.com + +Author: + Dr Nic Williams, drnicwilliams@gmail.com \ No newline at end of file diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..db1bf3f --- /dev/null +++ b/Rakefile @@ -0,0 +1,121 @@ +require 'rubygems' +begin + require 'rake' +rescue LoadError + puts 'This script should only be accessed via the "rake" command.' + puts 'Installation: gem install rake -y' + exit +end +require 'rake' +require 'rake/clean' +require 'rake/packagetask' + +$:.unshift File.dirname(__FILE__) + "/lib" + +APP_VERSION = '0.0.1' +APP_NAME = 'github_badges' +RUBYFORGE_PROJECT = APP_NAME +APP_FILE_NAME= "#{APP_NAME}.js" + +APP_ROOT = File.expand_path(File.dirname(__FILE__)) +APP_SRC_DIR = File.join(APP_ROOT, 'src') +APP_DIST_DIR = File.join(APP_ROOT, 'dist') +APP_PKG_DIR = File.join(APP_ROOT, 'pkg') + + +unless ENV['rakefile_just_config'] + +task :default => [:dist, :package, :clean_package_source] + +desc "Builds the distribution" +task :dist do + $:.unshift File.join(APP_ROOT, 'lib') + require 'protodoc' + require 'fileutils' + FileUtils.mkdir_p APP_DIST_DIR + + Dir.chdir(APP_SRC_DIR) do + File.open(File.join(APP_DIST_DIR, APP_FILE_NAME), 'w+') do |dist| + dist << Protodoc::Preprocessor.new(APP_FILE_NAME) + end + end + Dir.chdir(APP_DIST_DIR) do + FileUtils.copy_file APP_FILE_NAME, "#{APP_NAME}-#{APP_VERSION}.js" + end + if File.directory?("website") + FileUtils.mkdir_p "website/dist" + FileUtils.copy_file "dist/#{APP_FILE_NAME}", "website/dist/#{APP_FILE_NAME}" + FileUtils.copy_file "dist/#{APP_FILE_NAME}", "website/dist/#{APP_NAME}-#{APP_VERSION}.js" + end +end + +Rake::PackageTask.new(APP_NAME, APP_VERSION) do |package| + package.need_tar_gz = true + package.package_dir = APP_PKG_DIR + package.package_files.include( + '[A-Z]*', + 'config/*.sample', + "dist/#{APP_FILE_NAME}", + 'lib/**', + 'src/**', + 'script/**', + 'tasks/**', + 'test/**', + 'website/**' + ) +end + +desc "Builds the distribution, runs the JavaScript unit + functional tests and collects their results." +task :test => [:dist, :test_units, :test_functionals] + +require 'jstest' +desc "Runs all the JavaScript unit tests and collects the results" +JavaScriptTestTask.new(:test_units, 4711) do |t| + testcases = ENV['TESTCASES'] + tests_to_run = ENV['TESTS'] && ENV['TESTS'].split(',') + browsers_to_test = ENV['BROWSERS'] && ENV['BROWSERS'].split(',') + + t.mount("/dist") + t.mount("/src") + t.mount("/test") + + Dir["test/unit/*_test.html"].sort.each do |test_file| + tests = testcases ? { :url => "/#{test_file}", :testcases => testcases } : "/#{test_file}" + test_filename = test_file[/.*\/(.+?)\.html/, 1] + t.run(tests) unless tests_to_run && !tests_to_run.include?(test_filename) + end + + %w( safari firefox ie konqueror opera ).each do |browser| + t.browser(browser.to_sym) unless browsers_to_test && !browsers_to_test.include?(browser) + end +end + +desc "Runs all the JavaScript functional tests and collects the results" +JavaScriptTestTask.new(:test_functionals, 4712) do |t| + testcases = ENV['TESTCASES'] + tests_to_run = ENV['TESTS'] && ENV['TESTS'].split(',') + browsers_to_test = ENV['BROWSERS'] && ENV['BROWSERS'].split(',') + + t.mount("/dist") + t.mount("/src") + t.mount("/test") + + Dir["test/functional/*_test.html"].sort.each do |test_file| + tests = testcases ? { :url => "/#{test_file}", :testcases => testcases } : "/#{test_file}" + test_filename = test_file[/.*\/(.+?)\.html/, 1] + t.run(tests) unless tests_to_run && !tests_to_run.include?(test_filename) + end + + %w( safari firefox ie konqueror opera ).each do |browser| + t.browser(browser.to_sym) unless browsers_to_test && !browsers_to_test.include?(browser) + end +end + + +task :clean_package_source do + rm_rf File.join(APP_PKG_DIR, "#{APP_NAME}-#{APP_VERSION}") +end + +Dir['tasks/**/*.rake'].each { |rake| load rake } + +end \ No newline at end of file diff --git a/config/javascript_test_autotest.yml b/config/javascript_test_autotest.yml new file mode 100644 index 0000000..6fbd2b3 --- /dev/null +++ b/config/javascript_test_autotest.yml @@ -0,0 +1,15 @@ +# This file has been copied here by the javascript_test_autotest plugin. +# Comment/uncomment the browsers you wish to autotest with. +# Same schema as per selenium-on-rails plugin, which is nice. +browsers: + # Windows + # firefox: 'c:\Program Files\Mozilla Firefox\firefox.exe' + # ie: 'c:\Program Files\Internet Explorer\iexplore.exe' + + # Mac OS X + # firefox: '/Applications/Firefox.app/Contents/MacOS/firefox-bin' + safari: '/Applications/Safari.app/Contents/MacOS/Safari' + + # Unix + # ? + # ? \ No newline at end of file diff --git a/lib/jstest.rb b/lib/jstest.rb new file mode 100644 index 0000000..dfec0d2 --- /dev/null +++ b/lib/jstest.rb @@ -0,0 +1,382 @@ +require 'rake/tasklib' +require 'thread' +require 'webrick' +require 'fileutils' +include FileUtils + +class Browser + def supported?; true; end + def setup ; end + def open(url) ; end + def teardown ; end + + def host + require 'rbconfig' + Config::CONFIG['host'] + end + + def macos? + host.include?('darwin') + end + + def windows? + host.include?('mswin') + end + + def linux? + host.include?('linux') + end + + def applescript(script) + raise "Can't run AppleScript on #{host}" unless macos? + system "osascript -e '#{script}' 2>&1 >/dev/null" + end +end + +class FirefoxBrowser < Browser + def initialize(path=File.join(ENV['ProgramFiles'] || 'c:\Program Files', '\Mozilla Firefox\firefox.exe')) + @path = path + end + + def visit(url) + system("open -a Firefox '#{url}'") if macos? + system("#{@path} #{url}") if windows? + system("firefox #{url}") if linux? + end + + def to_s + "Firefox" + end +end + +class SafariBrowser < Browser + def supported? + macos? + end + + def setup + applescript('tell application "Safari" to make new document') + end + + def visit(url) + applescript('tell application "Safari" to set URL of front document to "' + url + '"') + end + + def teardown + #applescript('tell application "Safari" to close front document') + end + + def to_s + "Safari" + end +end + +class IEBrowser < Browser + def setup + require 'win32ole' if windows? + end + + def supported? + windows? + end + + def visit(url) + if windows? + ie = WIN32OLE.new('InternetExplorer.Application') + ie.visible = true + ie.Navigate(url) + while ie.ReadyState != 4 do + sleep(1) + end + end + end + + def to_s + "Internet Explorer" + end +end + +class KonquerorBrowser < Browser + @@configDir = File.join((ENV['HOME'] || ''), '.kde', 'share', 'config') + @@globalConfig = File.join(@@configDir, 'kdeglobals') + @@konquerorConfig = File.join(@@configDir, 'konquerorrc') + + def supported? + linux? + end + + # Forces KDE's default browser to be Konqueror during the tests, and forces + # Konqueror to open external URL requests in new tabs instead of a new + # window. + def setup + cd @@configDir, :verbose => false do + copy @@globalConfig, "#{@@globalConfig}.bak", :preserve => true, :verbose => false + copy @@konquerorConfig, "#{@@konquerorConfig}.bak", :preserve => true, :verbose => false + # Too lazy to write it in Ruby... Is sed dependency so bad? + system "sed -ri /^BrowserApplication=/d '#{@@globalConfig}'" + system "sed -ri /^KonquerorTabforExternalURL=/s:false:true: '#{@@konquerorConfig}'" + end + end + + def teardown + cd @@configDir, :verbose => false do + copy "#{@@globalConfig}.bak", @@globalConfig, :preserve => true, :verbose => false + copy "#{@@konquerorConfig}.bak", @@konquerorConfig, :preserve => true, :verbose => false + end + end + + def visit(url) + system("kfmclient openURL #{url}") + end + + def to_s + "Konqueror" + end +end + +class OperaBrowser < Browser + def initialize(path='c:\Program Files\Opera\Opera.exe') + @path = path + end + + def setup + if windows? + puts %{ + MAJOR ANNOYANCE on Windows. + You have to shut down Opera manually after each test + for the script to proceed. + Any suggestions on fixing this is GREATLY appreciated! + Thank you for your understanding. + } + end + end + + def visit(url) + applescript('tell application "Opera" to GetURL "' + url + '"') if macos? + system("#{@path} #{url}") if windows? + system("opera #{url}") if linux? + end + + def to_s + "Opera" + end +end + +# shut up, webrick :-) +class ::WEBrick::HTTPServer + def access_log(config, req, res) + # nop + end +end + +class ::WEBrick::BasicLog + def log(level, data) + # nop + end +end + +class WEBrick::HTTPResponse + alias send send_response + def send_response(socket) + send(socket) unless fail_silently? + end + + def fail_silently? + @fail_silently + end + + def fail_silently + @fail_silently = true + end +end + +class WEBrick::HTTPRequest + def to_json + headers = [] + each { |k, v| headers.push "#{k.inspect}: #{v.inspect}" } + headers = "{" << headers.join(', ') << "}" + %({ "headers": #{headers}, "body": #{body.inspect}, "method": #{request_method.inspect} }) + end +end + +class WEBrick::HTTPServlet::AbstractServlet + def prevent_caching(res) + res['ETag'] = nil + res['Last-Modified'] = Time.now + 100**4 + res['Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0' + res['Pragma'] = 'no-cache' + res['Expires'] = Time.now - 100**4 + end +end + +class BasicServlet < WEBrick::HTTPServlet::AbstractServlet + def do_GET(req, res) + prevent_caching(res) + res['Content-Type'] = "text/plain" + + req.query.each do |k, v| + res[k] = v unless k == 'responseBody' + end + res.body = req.query["responseBody"] + + raise WEBrick::HTTPStatus::OK + end + + def do_POST(req, res) + do_GET(req, res) + end +end + +class SlowServlet < BasicServlet + def do_GET(req, res) + sleep(2) + super + end +end + +class DownServlet < BasicServlet + def do_GET(req, res) + res.fail_silently + end +end + +class InspectionServlet < BasicServlet + def do_GET(req, res) + prevent_caching(res) + res['Content-Type'] = "application/json" + res.body = req.to_json + raise WEBrick::HTTPStatus::OK + end +end + +class NonCachingFileHandler < WEBrick::HTTPServlet::FileHandler + def do_GET(req, res) + super + set_default_content_type(res, req.path) + prevent_caching(res) + end + + def set_default_content_type(res, path) + res['Content-Type'] = case path + when /\.js$/ then 'text/javascript' + when /\.html$/ then 'text/html' + when /\.css$/ then 'text/css' + else 'text/plain' + end + end +end + +class JavaScriptTestTask < ::Rake::TaskLib + + def initialize(name=:test, port=4711) + @name = name + @tests = [] + @browsers = [] + @port = port + @queue = Queue.new + + @server = WEBrick::HTTPServer.new(:Port => @port) # TODO: make port configurable + @server.mount_proc("/results") do |req, res| + @queue.push({ + :tests => req.query['tests'].to_i, + :assertions => req.query['assertions'].to_i, + :failures => req.query['failures'].to_i, + :errors => req.query['errors'].to_i + }) + res.body = "OK" + end + @server.mount("/response", BasicServlet) + @server.mount("/slow", SlowServlet) + @server.mount("/down", DownServlet) + @server.mount("/inspect", InspectionServlet) + yield self if block_given? + define + end + + def define + task @name do + trap("INT") { @server.shutdown } + t = Thread.new { @server.start } + + # run all combinations of browsers and tests + @browsers.each do |browser| + if browser.supported? + t0 = Time.now + results = {:tests => 0, :assertions => 0, :failures => 0, :errors => 0} + errors = [] + failures = [] + browser.setup + puts "\nStarted tests in #{browser}" + @tests.each do |test| + params = "resultsURL=http://localhost:#{@port}/results&t=" + ("%.6f" % Time.now.to_f) + if test.is_a?(Hash) + params << "&tests=#{test[:testcases]}" if test[:testcases] + test = test[:url] + end + browser.visit("http://localhost:#{@port}#{test}?#{params}") + + result = @queue.pop + result.each { |k, v| results[k] += v } + value = "." + + if result[:failures] > 0 + value = "F" + failures.push(test) + end + + if result[:errors] > 0 + value = "E" + errors.push(test) + end + + print value + end + + puts "\nFinished in #{(Time.now - t0).round.to_s} seconds." + puts " Failures: #{failures.join(', ')}" unless failures.empty? + puts " Errors: #{errors.join(', ')}" unless errors.empty? + puts "#{results[:tests]} tests, #{results[:assertions]} assertions, #{results[:failures]} failures, #{results[:errors]} errors" + browser.teardown + else + puts "\nSkipping #{browser}, not supported on this OS" + end + end + + @server.shutdown + t.join + end + end + + def mount(path, dir=nil) + dir = Dir.pwd + path unless dir + + # don't cache anything in our tests + @server.mount(path, NonCachingFileHandler, dir) + end + + # test should be specified as a url or as a hash of the form + # {:url => "url", :testcases => "testFoo,testBar"} + def run(test) + @tests< Only watches Ruby files one directory down (no quotes) +# +# rstakeout 'rake test:recent' '**/*.rb' +# => Watches all Ruby files in all directories and subdirectories +# +# Modified (with permission) by Geoffrey Grosenbach to call growlnotify for +# rspec and Test::Unit output. +# +# See the PeepCode screencast on rSpec or other blog articles for instructions on +# setting up growlnotify. + +def growl(title, msg, img, pri=0, sticky="") + system "growlnotify -n autotest --image ~/.autotest_images/#{img} -p #{pri} -m #{msg.inspect} #{title} #{sticky}" +end + +def self.growl_fail(output) + growl "FAIL", "#{output}", "fail.png", 2 +end + +def self.growl_pass(output) + growl "Pass", "#{output}", "pass.png" +end + +command = ARGV.shift +files = {} + +ARGV.each do |arg| + Dir[arg].each { |file| + files[file] = File.mtime(file) + } +end + +puts "Watching #{files.keys.join(', ')}\n\nFiles: #{files.keys.length}" + +trap('INT') do + puts "\nQuitting..." + exit +end + + +loop do + + sleep 1 + + changed_file, last_changed = files.find { |file, last_changed| + File.mtime(file) > last_changed + } + + if changed_file + files[changed_file] = File.mtime(changed_file) + puts "=> #{changed_file} changed, running #{command}" + results = `#{command}` + puts results + + if results.include? 'tests' + output = results.slice(/(\d+)\s+tests?,\s*(\d+)\s+assertions?,\s*(\d+)\s+failures?(,\s*(\d+)\s+errors)?/) + if output + $~[3].to_i + $~[5].to_i > 0 ? growl_fail(output) : growl_pass(output) + end + else + output = results.slice(/(\d+)\s+examples?,\s*(\d+)\s+failures?(,\s*(\d+)\s+not implemented)?/) + if output + $~[2].to_i > 0 ? growl_fail(output) : growl_pass(output) + end + end + # TODO Generic growl notification for other actions + + puts "=> done" + end + +end diff --git a/src/HEADER b/src/HEADER new file mode 100644 index 0000000..dc54d5f --- /dev/null +++ b/src/HEADER @@ -0,0 +1,8 @@ +/* Github Badges, version <%= APP_VERSION %> + * (c) 2008 Dr Nic Williams + * + * Github Badges is freely distributable under + * the terms of an MIT-style license. + * For details, see the web site: http://NOTE-ENTER-URL.com + * + *--------------------------------------------------------------------------*/ \ No newline at end of file diff --git a/src/ext/jquery.ajax_cache.js b/src/ext/jquery.ajax_cache.js new file mode 100644 index 0000000..aa64262 --- /dev/null +++ b/src/ext/jquery.ajax_cache.js @@ -0,0 +1,28 @@ +/* Copyright (c) 2007 Dr Nic Williams (drnicwilliams@gmail.com || http://drnicwilliams.com) + * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license. + * + * Version: @VERSION + * Requires jQuery 1.2.3+ + * Docs: http://docs.jquery.com/Plugins/ajax_cache + * Dependencies: jCache 1.0.2 - http://plugins.jquery.com/project/jCache + * + * Description: Provides a $.getCachedJSON method that has the same API as the standard $.getJSON + * but the url+args=>json is cached on the page. Subsequent calls to same url+args will return the + * original result without an $.ajax call being made. + */ + +(function($) { + this.version = '0.1'; + + $.getCachedJSON = function(url, args, success) { + if (url) { + var full_url = url + (args ? "" : "?" + $.param(args)); + var json = $.jCache.getItem(full_url); + } + if (!json) $.getJSON(url, args, function(json) { + $.jCache.setItem(full_url, json); + success(json); + }); + else success(json); + }; +})(jQuery); \ No newline at end of file diff --git a/src/ext/jquery.form.js b/src/ext/jquery.form.js new file mode 100644 index 0000000..1c3284b --- /dev/null +++ b/src/ext/jquery.form.js @@ -0,0 +1,869 @@ +/* + * jQuery Form Plugin + * version: 2.07 (03/04/2008) + * @requires jQuery v1.2.2 or later + * + * Examples at: http://malsup.com/jquery/form/ + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + * Revision: $Id$ + */ + (function($) { +/** + * ajaxSubmit() provides a mechanism for submitting an HTML form using AJAX. + * + * ajaxSubmit accepts a single argument which can be either a success callback function + * or an options Object. If a function is provided it will be invoked upon successful + * completion of the submit and will be passed the response from the server. + * If an options Object is provided, the following attributes are supported: + * + * target: Identifies the element(s) in the page to be updated with the server response. + * This value may be specified as a jQuery selection string, a jQuery object, + * or a DOM element. + * default value: null + * + * url: URL to which the form data will be submitted. + * default value: value of form's 'action' attribute + * + * type: The method in which the form data should be submitted, 'GET' or 'POST'. + * default value: value of form's 'method' attribute (or 'GET' if none found) + * + * data: Additional data to add to the request, specified as key/value pairs (see $.ajax). + * + * beforeSubmit: Callback method to be invoked before the form is submitted. + * default value: null + * + * success: Callback method to be invoked after the form has been successfully submitted + * and the response has been returned from the server + * default value: null + * + * dataType: Expected dataType of the response. One of: null, 'xml', 'script', or 'json' + * default value: null + * + * semantic: Boolean flag indicating whether data must be submitted in semantic order (slower). + * default value: false + * + * resetForm: Boolean flag indicating whether the form should be reset if the submit is successful + * + * clearForm: Boolean flag indicating whether the form should be cleared if the submit is successful + * + * + * The 'beforeSubmit' callback can be provided as a hook for running pre-submit logic or for + * validating the form data. If the 'beforeSubmit' callback returns false then the form will + * not be submitted. The 'beforeSubmit' callback is invoked with three arguments: the form data + * in array format, the jQuery object, and the options object passed into ajaxSubmit. + * The form data array takes the following form: + * + * [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ] + * + * If a 'success' callback method is provided it is invoked after the response has been returned + * from the server. It is passed the responseText or responseXML value (depending on dataType). + * See jQuery.ajax for further details. + * + * + * The dataType option provides a means for specifying how the server response should be handled. + * This maps directly to the jQuery.httpData method. The following values are supported: + * + * 'xml': if dataType == 'xml' the server response is treated as XML and the 'success' + * callback method, if specified, will be passed the responseXML value + * 'json': if dataType == 'json' the server response will be evaluted and passed to + * the 'success' callback, if specified + * 'script': if dataType == 'script' the server response is evaluated in the global context + * + * + * Note that it does not make sense to use both the 'target' and 'dataType' options. If both + * are provided the target will be ignored. + * + * The semantic argument can be used to force form serialization in semantic order. + * This is normally true anyway, unless the form contains input elements of type='image'. + * If your form must be submitted with name/value pairs in semantic order and your form + * contains an input of type='image" then pass true for this arg, otherwise pass false + * (or nothing) to avoid the overhead for this logic. + * + * + * When used on its own, ajaxSubmit() is typically bound to a form's submit event like this: + * + * $("#form-id").submit(function() { + * $(this).ajaxSubmit(options); + * return false; // cancel conventional submit + * }); + * + * When using ajaxForm(), however, this is done for you. + * + * @example + * $('#myForm').ajaxSubmit(function(data) { + * alert('Form submit succeeded! Server returned: ' + data); + * }); + * @desc Submit form and alert server response + * + * + * @example + * var options = { + * target: '#myTargetDiv' + * }; + * $('#myForm').ajaxSubmit(options); + * @desc Submit form and update page element with server response + * + * + * @example + * var options = { + * success: function(responseText) { + * alert(responseText); + * } + * }; + * $('#myForm').ajaxSubmit(options); + * @desc Submit form and alert the server response + * + * + * @example + * var options = { + * beforeSubmit: function(formArray, jqForm) { + * if (formArray.length == 0) { + * alert('Please enter data.'); + * return false; + * } + * } + * }; + * $('#myForm').ajaxSubmit(options); + * @desc Pre-submit validation which aborts the submit operation if form data is empty + * + * + * @example + * var options = { + * url: myJsonUrl.php, + * dataType: 'json', + * success: function(data) { + * // 'data' is an object representing the the evaluated json data + * } + * }; + * $('#myForm').ajaxSubmit(options); + * @desc json data returned and evaluated + * + * + * @example + * var options = { + * url: myXmlUrl.php, + * dataType: 'xml', + * success: function(responseXML) { + * // responseXML is XML document object + * var data = $('myElement', responseXML).text(); + * } + * }; + * $('#myForm').ajaxSubmit(options); + * @desc XML data returned from server + * + * + * @example + * var options = { + * resetForm: true + * }; + * $('#myForm').ajaxSubmit(options); + * @desc submit form and reset it if successful + * + * @example + * $('#myForm).submit(function() { + * $(this).ajaxSubmit(); + * return false; + * }); + * @desc Bind form's submit event to use ajaxSubmit + * + * + * @name ajaxSubmit + * @type jQuery + * @param options object literal containing options which control the form submission process + * @cat Plugins/Form + * @return jQuery + */ +$.fn.ajaxSubmit = function(options) { + if (typeof options == 'function') + options = { success: options }; + + options = $.extend({ + url: this.attr('action') || window.location.toString(), + type: this.attr('method') || 'GET' + }, options || {}); + + // hook for manipulating the form data before it is extracted; + // convenient for use with rich editors like tinyMCE or FCKEditor + var veto = {}; + this.trigger('form-pre-serialize', [this, options, veto]); + if (veto.veto) return this; + + var a = this.formToArray(options.semantic); + if (options.data) { + options.extraData = options.data; + for (var n in options.data) + a.push( { name: n, value: options.data[n] } ); + } + + // give pre-submit callback an opportunity to abort the submit + if (options.beforeSubmit && options.beforeSubmit(a, this, options) === false) return this; + + // fire vetoable 'validate' event + this.trigger('form-submit-validate', [a, this, options, veto]); + if (veto.veto) return this; + + var q = $.param(a); + + if (options.type.toUpperCase() == 'GET') { + options.url += (options.url.indexOf('?') >= 0 ? '&' : '?') + q; + options.data = null; // data is null for 'get' + } + else + options.data = q; // data is the query string for 'post' + + var $form = this, callbacks = []; + if (options.resetForm) callbacks.push(function() { $form.resetForm(); }); + if (options.clearForm) callbacks.push(function() { $form.clearForm(); }); + + // perform a load on the target only if dataType is not provided + if (!options.dataType && options.target) { + var oldSuccess = options.success || function(){}; + callbacks.push(function(data) { + $(options.target).html(data).each(oldSuccess, arguments); + }); + } + else if (options.success) + callbacks.push(options.success); + + options.success = function(data, status) { + for (var i=0, max=callbacks.length; i < max; i++) + callbacks[i](data, status, $form); + }; + + // are there files to upload? + var files = $('input:file', this).fieldValue(); + var found = false; + for (var j=0; j < files.length; j++) + if (files[j]) + found = true; + + // options.iframe allows user to force iframe mode + if (options.iframe || found) { + // hack to fix Safari hang (thanks to Tim Molendijk for this) + // see: http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d + if ($.browser.safari && options.closeKeepAlive) + $.get(options.closeKeepAlive, fileUpload); + else + fileUpload(); + } + else + $.ajax(options); + + // fire 'notify' event + this.trigger('form-submit-notify', [this, options]); + return this; + + + // private function for handling file uploads (hat tip to YAHOO!) + function fileUpload() { + var form = $form[0]; + var opts = $.extend({}, $.ajaxSettings, options); + + var id = 'jqFormIO' + (new Date().getTime()); + var $io = $('