1. 程式人生 > >layui tree treegrid javascript js treetable 前端 jquery form

layui tree treegrid javascript js treetable 前端 jquery form

最近接了個活,被狠狠的坑了一把,也沒有什麼收穫。接近尾聲,把唯一的一點成果發出來。

新專案,沒有完整的前端框架,選擇了layui做為後臺管理系統的前端js框架。layui挺好的(尤其是layui.use宣告式的元件載入,要比提前大量的js檔案引入優雅的多),但是畢竟是新的框架,可用的東西太少了,很多封裝的元件也比較簡單,適應不了後臺複雜的展示功能。

新功能有個樹表的展示,時間太緊了,常用的treetable做出來也與整個系統的頁面風格不統一,無奈只能自己寫個treetable的展示頁了。

效果圖:


主要方法:

openChild(elem,id)   節點開啟關閉    

addChild(item,tr)   新增節點     

 	 addChildCity(id,$(tr).next()) 新增節點後的回撥,自動把建立的節點新增到表格中
	 del(id,$that)  刪除節點
 
 

整個過程中沒有重新整理操作,全部jquery實現table的變動。

        相對treetable元件,treetable是一行一個table組織起來的,這個treetable是整個一個表格。

        程式碼還沒有整理封裝,可以快速替換自己的樣式和model。

layui的所有程式碼都封裝layui.use中,外部不可見的,如果想外部呼叫layui的方法,可以先宣告,在layui中再定義方法實現。例如:

var addChildCity = null; var del = null; 兩個方法。

還有比較坑的是layui.js載入元件路徑的宣告,開始使用的時候就是不能準確載入元件,無奈寫死了請求路徑方才可以。

		var p = n.createElement("script"), h = (a[m] ?  "/static/plugins/layui/lay/" : o.base(layui.js 76行)
	具體原因未找到,可能是自己使用的不太對吧。

還是要宣告一下,如果使用layui,一定要確定自己有封裝元件的能力和充足的時間,否則還是用功能完整的extjs吧,急就章趕出來的程式碼,實在不敢恭維。

過兩天封裝元件程式碼.......................................................................................

<!DOCTYPE html>
<html>
<body>
	<div style="margin: 0px; background-color: white;">
		<blockquote class="layui-elem-quote" style="float: right">
			<a href="javascript:" class="layui-btn layui-btn-small" id="add">
				<i class="layui-icon"></i> 新增根節點
			</a>
		</blockquote>
		<div class="layui-form">
			<table class="layui-table admin-table">
				<thead>
					<tr>
						<th>名稱</th>
						<th>簡稱</th>
						<th>全拼</th>
						<th>簡拼</th>
						<th>編碼</th>
						<th>是否主要</th>
						<th>對映編碼</th>
						<th>序號</th>
						<th>級別</th>
						<th>操作</th>
					</tr>
				</thead>
				<tbody id="content">
				</tbody>
			</table>
		</div>
	</div>
	<!--模板-->
	<script type="text/html" id="tpl">
			{{# layui.each(d.list, function(index, item){ }}
			<tr data-parent="{{item.parentId}}" data-id="{{item.id}}" data-name="{{ item.name }}" data-havChild="{{ item.havChild }}" data-spread="false">
				<td data-id="{{ item.id }}" >
					{{# if(item.havChild&&item.havChild>0){ }}
						<a href="javascript:" onclick="openChild(this,'{{item.id}}');">
							<i class="layui-icon layui-tree-spread">+</i>
						</a>
						<cite>{{ item.name }}</cite></a>
					{{# }else{ }}
						<span class="layui-icon layui-tree-leaf">  </span>
						<cite>{{ item.name }}</cite>
					{{# } }}
			<!--開啟樣式 <a href="javascript:;"><i class="layui-icon layui-tree-spread"></i>
					<i class="layui-icon layui-tree-branch"></i>
					<cite>{{ item.name }}</cite></a>-->
				</td>
				<td>{{ item.abbrName }}</td>
				<td>{{ item.fullSpell }}</td>
				<td>{{ item.abbrSpell }}</td>
				<td>{{ item.cityCode }}</td>
				<td>{{# if(item.isMainCity&&item.isMainCity==1){ }}
					{{ "是" }}
					{{# }else{ }}
					{{ "否" }}
					{{# } }}
				</td>
				<td>{{ item.mappingCode }}</td>
				<td>{{ item.sortNo==null?"":item.sortNo }}</td>
				<td>{{ item.regionLevel }}</td>
				<td>
					<a href="javascript:;" data-id="{{ item.id }}" data-opt="edit" class="layui-btn layui-btn-mini">新增子城市</a>
					<a href="javascript:;" data-id="{{ item.id }}" data-opt="del" class="layui-btn layui-btn-danger layui-btn-mini">刪除</a>
				</td><!---->
			</tr>
			{{# }); }}
			
		</script>
	<script type="text/javascript">
			layui.config({
				base: '<%=contextPath%>/static/js/'
			});
			var $ = layui.jquery;
			var addChildCity = null;
			var del = null;
			var layerTips = null;
			
			
			function spread(tr){
				var after = $(tr).next();
				if(after){
					var id = tr.attr("data-id");
	    			var parentId = after.attr("data-parent");
	       			while(id==parentId){
	       				//after.hide();
	       				if("true"==(tr.attr("data-spread"))){
	       					after.show(); 
	       				}
	       				after = spread(after);
	       				parentId = after.attr("data-parent");
	       			}
	       			return after;
				}
    		}
			
			function closed(tr){
				var after = $(tr).next();
				if(after){
					var id = $(tr).attr("data-id");
	    			var parentId = after.attr("data-parent");
	       			while(id==parentId){
	       				//after.hide();
	       				after.hide(); 
	    				after = closed(after);
	        			parentId = after.attr("data-parent");
	       			}
	       			return after;
				}
    		}
			
			function openChild(elem,id){
				var tr = $(elem).parent().parent();
        		if("false"==($(tr).attr("data-spread"))){
        			$(tr).attr("data-spread","true");
        			$(elem).empty();
        			$(elem).prepend('<i class="layui-icon layui-tree-spread">-</i>');
        			
        			var after = $(tr).next();
        			var parentId = after.attr("data-parent");
        			if(id==parentId){
        				spread($(tr));

        			}else{
        				$.ajax({
        	                type: "GET",
        	                url: "<%=servicePath%>/city/",
        	                dataType : 'json',
        	                data:{parentId:id},
        	                success: function (obj) {
        	                    if(obj.msg){
        	                    	layer.alert(data.msg);
        	                    }else{
        	                      	layui.each(obj, function(index, item){
        	                    		var html = '<tr data-parent="'+item.parentId+'" data-havChild="'+item.havChild+'" data-id="'+item.id+'"  data-name="'+item.name+'" data-spread="false"><td  data-id="'+item.id+'" >';
        	                    		if(item.havChild&&item.havChild>0){
        	                    			html = html +'<a href="javascript:" onclick="openChild(this,\''+item.id+'\');">'+'<i class="layui-icon layui-tree-spread">+</i></a>';
        	                    		}else{
        	                    			html = html + '<span width="'+60*(item.level-1)+'px;">  </span>';
        	                    		}
        	                    		var isMainCity = "否";
                                        if (item.isMainCity && item.isMainCity == 1) {
                                            isMainCity = "是"
                                        }
                                        html = html+'<cite>'+item.name+'</cite></a></td><td>'+item.abbrName +'</td><td>'+item.fullSpell +'</td><td>'+item.abbrSpell
        	                    			+'</td><td>'+item.cityCode +'</td><td>'+ isMainCity +'</td><td>'+item.mappingCode +'</td><td>'+item.sortNo 
        	                    			+'</td><td>'+item.regionLevel +'</td>'
        	                    			+'<td><a href="javascript:;" data-id="'+item.id+'" data-opt="edit" class="layui-btn layui-btn-mini">新增子城市</a>'
        	            					+'<a href="javascript:;" data-id="'+item.id+'" data-opt="del" class="layui-btn layui-btn-danger layui-btn-mini">刪除</a></td>';
        	            				
        	            				$(tr).after(html);
        	                    		//繫結所有編輯按鈕事件
        	                    		//console.log($(tr).next().prop("outerHTML"));
        	                    		//console.log($(tr).next().children('td:last-child').prop("outerHTML"));
        	                    		var id = $(tr).next().data('id');
        	                    		
        	                    		$(tr).next().children('td:last-child').children('a[data-opt=edit]').on('click', function() {
	        	           					 addChildCity(id,$(tr).next());
	        	           				});
        	                    		var name =  $(tr).next().data('name');
        	                    		$(tr).next().children('td:last-child').children('a[data-opt=del]').on('click', function() {
	        	                               layerTips.confirm('確定要刪除[ <span style="color:red;">' + name + '</span> ] ?', { icon: 3, title: '系統提示' }, function (index) {
	        	                                   del(id,$(tr).next());
	        	                               });
	        	           				}); 
    	    						});
        	                    }
        	                },
        	                error: function(data) {
        	                	layer.alert(data.msg);
        	                }
        	            });
        			}
        		}else{
        			$(tr).attr("data-spread","false");
        			$(elem).empty();
        			$(elem).prepend('<i class="layui-icon layui-tree-spread">+</i>');
        			closed($(tr));
        		}
			}
			
			//後期程式碼整合
			function addChild(item,tr){
				var html = '<tr data-parent="'+item.parentId+'" data-havChild="'+item.havChild+'" data-id="'+item.id+'"  data-name="'+item.name+'" data-spread="false"><td  data-id="'+item.id+'" >';
        		if(item.havChild&&item.havChild>0){
        			html = html +'<a href="javascript:" onclick="openChild(this,\''+item.id+'\');">'+'<i class="layui-icon layui-tree-spread">+</i></a>';
        		}else{
        			html = html + '<span width="'+60*(item.level-1)+'px;">  </span>';
        		}
        		var isMainCity = "否";
                if (item.isMainCity && item.isMainCity == 1) {
                    isMainCity = "是"
                }
                html = html+'<cite>'+item.name+'</cite></a></td><td>'+item.abbrName +'</td><td>'+item.fullSpell +'</td><td>'+item.abbrSpell
        			+'</td><td>'+item.cityCode +'</td><td>'+ isMainCity +'</td><td>'+item.mappingCode +'</td><td>'+item.sortNo 
        			+'</td><td>'+item.regionLevel +'</td>'
        			+'<td><a href="javascript:;" data-id="'+item.id+'" data-opt="edit" class="layui-btn layui-btn-mini">新增子城市</a>'
					+'<a href="javascript:;" data-id="'+item.id+'" data-opt="del" class="layui-btn layui-btn-danger layui-btn-mini">刪除</a></td>';
				
				$(tr).after(html);
				$(tr).attr("data-spread","true");
				var td = $(tr).children('td:first-child');
				var span = td.children('span:first-child')
				span.after('<a href="javascript:" onclick="openChild(this,\''+$(tr).data('id')+'\');">'+'<i class="layui-icon layui-tree-spread">-</i></a>');
        		//繫結所有編輯按鈕事件
        		console.log($(tr).next().prop("outerHTML"));
        		console.log($(tr).next().children('td:last-child').prop("outerHTML"));
        		var id = $(tr).next().data('id');
				$(tr).next().children('td:last-child').children('a[data-opt=edit]').on('click', function() {
					 addChildCity(id,$(tr).next());
				});
				$(tr).next().children('td:last-child').children('a[data-opt=del]').on('click', function() {
                    layerTips.confirm('確定要刪除[ <span style="color:red;">' + name + '</span> ] ?', { icon: 3, title: '系統提示' }, function (index) {
                        del(id,$(tr).next());
                    });
				});
			}
			
			layui.use(['jquery','paging', 'form','jquery_form'], function() {
				 	$ = layui.jquery_form(layui.jquery);
				 	layerTips = parent.layer === undefined ? layui.layer : parent.layer; //獲取父視窗的layer物件
					var paging = layui.paging(),
					layer = layui.layer, //獲取當前視窗的layer物件
					form = layui.form();

                paging.init({
                    openWait: true,
                    url: '<%=servicePath%>/city/',
					elem: '#content', //內容容器
					params: { //傳送到服務端的引數
						level:1
					},
					type: 'GET',
					tempElem: '#tpl', //模組容器
					paged:false,
					complate: function() { //完成的回撥
						//繫結所有編輯按鈕事件						
						$('#content').children('tr').each(function() {
							var $that = $(this);
							var id = $that.data('id');
							var name = $that.data('name');
							$that.children('td:last-child').children('a[data-opt=edit]').on('click', function() {
	                        	addChildCity(id,$that);
							});
							$that.children('td:last-child').children('a[data-opt=del]').on('click', function() {
	                            layerTips.confirm('確定要刪除[ <span style="color:red;">' + name + '</span> ] ?', { icon: 3, title: '系統提示' }, function (index) {
	                                del(id,$that);
	                            });
							}); 
						});
					}
				});

				var addBoxIndex = -1;
				$('#add').on('click', function() {
					if(addBoxIndex !== -1) return;
					//本表單通過ajax載入 --以模板的形式,當然你也可以直接寫在頁面上讀取
					$.get('<%=contextPath%>/cityPage/toAddCity?level=1', null, function(form) {
						addBoxIndex = layer.open({
							type: 1,
							title: '新增根城市',
							content: form,
							btn: ['儲存', '取消'],
							shade: false,
							offset: ['100px', '30%'],
							area: ['600px', '400px'],
							zIndex: 19950924,
							maxmin: true,
							yes: function(index) {
								//觸發表單的提交事件
								$('form.layui-form').find('button[lay-filter=citySubmit]').click();
							},
							full: function(elem) {
								var win = window.top === window.self ? window : parent.window;
								$(win).on('resize', function() {
									var $this = $(this);
									elem.width($this.width()).height($this.height()).css({
										top: 0,
										left: 0
									});
									elem.children('div.layui-layer-content').height($this.height() - 95);
								});
							},
							success: function(layero, index) {
								//彈出視窗成功後渲染表單
								var form = layui.form();
								form.render();
								form.on('submit(citySubmit)', function(data) {
									$("#dictCityForm").ajaxSubmit({
									 	type:'post',      
						                success:function(data)
						                {   //成功執行的方法
						                	if(data.msg){
						                		layer.alert(data.msg);
						                	}else{
						                		addChild(data,$('#content').children('tr:last'));
						                	}
						                	//btable.get();
						                	layer.close(index);
						                }     
									});
									return false; //阻止表單跳轉。如果需要表單跳轉,去掉這段即可。									
								});
							},
							end: function() {
								addBoxIndex = -1;
							}
						});
					});
				});

				addChildCity = function(parentId,tr) {
					if(addBoxIndex !== -1) return;
					var that = this;
                    $.get('<%=contextPath%>/cityPage/toAddCity?parentId='+parentId, null, function(form) {
                        addBoxIndex = layer.open({
                            type: 1,
                            title: '新增子',
                            content: form,
                            btn: ['儲存', '取消'],
                            shade: false,
                            offset: ['100px', '30%'],
                            area: ['600px', '400px'],
                            zIndex: 19950924,
                            maxmin: true,
                            yes: function(index) {
                                //觸發表單的提交事件
                                $('form.layui-form').find('button[lay-filter=citySubmit]').click();
                            },
                            full: function(elem) {
                                var win = window.top === window.self ? window : parent.window;
                                $(win).on('resize', function() {
                                    var $this = $(this);
                                    elem.width($this.width()).height($this.height()).css({
                                        top: 0,
                                        left: 0
                                    });
                                    elem.children('div.layui-layer-content').height($this.height() - 95);
                                });
                            },
                            success: function(layero, index) {
                                //彈出視窗成功後渲染表單
                                var form = layui.form();
                                form.render();
                                form.on('submit(citySubmit)', function(data) {
                                	$("#dictCityForm").ajaxSubmit({
									 	type:'post',      
						                success:function(data)
						                {   //成功執行的方法
						                	if(data.msg){
						                		layer.alert(data.msg);
						                	}else{
						                		addChild(data, tr);
						                	}
						                	//btable.get();
						                	layer.close(index);
						                }     
									});
                                    return false; //阻止表單跳轉。如果需要表單跳轉,去掉這段即可。
                                });
                            },
                            end: function() {
                                addBoxIndex = -1;
                            }
                        });
                    });
				};
			del = function(id,tr){
				$.ajax({
	                type: "POST",
	                url: "<%=servicePath%>/city/delete/"+id,
	                success: function (obj) {
	                    if(obj.msg){
	                    	layer.alert(data.msg);
	                    	return false;
	                    }else{
	                    	tr.remove();
                            layerTips.msg('刪除成功.');
	                    }
	                },
	                error: function(data) {
	                	layer.alert(data.msg);
	                	return false;
	                }
	            });
			};
		});
		</script>
</body>

</html>
再附上一個layui可以使用的jquery-form元件(網上很多如何把jquery元件轉成layui的說明,這個是轉成功的):

/*
 * ! jQuery Form Plugin version: 3.50.0-2014.02.05 Requires jQuery v1.5 or later
 * Copyright (c) 2013 M. Alsup Examples and documentation at:
 * http://malsup.com/jquery/form/ Project repository:
 * https://github.com/malsup/form Dual licensed under the MIT and GPL licenses.
 * https://github.com/malsup/form#copyright-and-license
 */
/* global ActiveXObject */

// AMD support
layui.define(function(exports) {
	function jquery_form(jQuery) {
		// 外掛程式碼區
		(function(factory) {
			"use strict";
			if (typeof define === 'function' && define.amd) {
				// using AMD; register as anon module
				define(['jquery'], factory);
			} else {
				// no AMD; invoke directly
				factory((typeof(jQuery) != 'undefined') ? jQuery : window.Zepto);
			}
		}

		(function($) {
			"use strict";

			/*
			 * Usage Note: ----------- Do not use both ajaxSubmit and ajaxForm
			 * on the same form. These functions are mutually exclusive. Use
			 * ajaxSubmit if you want to bind your own submit handler to the
			 * form. For example,
			 * 
			 * $(document).ready(function() { $('#myForm').on('submit',
			 * function(e) { e.preventDefault(); // <-- important
			 * $(this).ajaxSubmit({ target: '#output' }); }); });
			 * 
			 * Use ajaxForm when you want the plugin to manage all the event
			 * binding for you. For example,
			 * 
			 * $(document).ready(function() { $('#myForm').ajaxForm({ target:
			 * '#output' }); });
			 * 
			 * You can also use ajaxForm with delegation (requires jQuery
			 * v1.7+), so the form does not have to exist when you invoke
			 * ajaxForm:
			 * 
			 * $('#myForm').ajaxForm({ delegation: true, target: '#output' });
			 * 
			 * When using ajaxForm, the ajaxSubmit function will be invoked for
			 * you at the appropriate time.
			 */

			/**
			 * Feature detection
			 */
			var feature = {};
			feature.fileapi = $("<input type='file'/>").get(0).files !== undefined;
			feature.formdata = window.FormData !== undefined;

			var hasProp = !!$.fn.prop;

			// attr2 uses prop when it can but checks the return type for
			// an expected string. this accounts for the case where a form
			// contains inputs with names like "action" or "method"; in those
			// cases "prop" returns the element
			$.fn.attr2 = function() {
				if (!hasProp) {
					return this.attr.apply(this, arguments);
				}
				var val = this.prop.apply(this, arguments);
				if ((val && val.jquery) || typeof val === 'string') {
					return val;
				}
				return this.attr.apply(this, arguments);
			};

			/**
			 * ajaxSubmit() provides a mechanism for immediately submitting an
			 * HTML form using AJAX.
			 */
			$.fn.ajaxSubmit = function(options) {
				/* jshint scripturl:true */

				// fast fail if nothing selected
				// (http://dev.jquery.com/ticket/2752)
				if (!this.length) {
					log('ajaxSubmit: skipping submit process - no element selected');
					return this;
				}

				var method, action, url, $form = this;

				if (typeof options == 'function') {
					options = {
						success : options
					};
				} else if (options === undefined) {
					options = {};
				}

				method = options.type || this.attr2('method');
				action = options.url || this.attr2('action');

				url = (typeof action === 'string') ? $.trim(action) : '';
				url = url || window.location.href || '';
				if (url) {
					// clean url (don't include hash vaue)
					url = (url.match(/^([^#]+)/) || [])[1];
				}

				options = $.extend(true, {
							url : url,
							success : $.ajaxSettings.success,
							type : method || $.ajaxSettings.type,
							iframeSrc : /^https/i.test(window.location.href
									|| '') ? 'javascript:false' : 'about:blank'
						}, 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) {
					log('ajaxSubmit: submit vetoed via form-pre-serialize trigger');
					return this;
				}

				// provide opportunity to alter form data before it is
				// serialized
				if (options.beforeSerialize
						&& options.beforeSerialize(this, options) === false) {
					log('ajaxSubmit: submit aborted via beforeSerialize callback');
					return this;
				}

				var traditional = options.traditional;
				if (traditional === undefined) {
					traditional = $.ajaxSettings.traditional;
				}

				var elements = [];
				var qx, a = this.formToArray(options.semantic, elements);
				if (options.data) {
					options.extraData = options.data;
					qx = $.param(options.data, traditional);
				}

				// give pre-submit callback an opportunity to abort the submit
				if (options.beforeSubmit
						&& options.beforeSubmit(a, this, options) === false) {
					log('ajaxSubmit: submit aborted via beforeSubmit callback');
					return this;
				}

				// fire vetoable 'validate' event
				this.trigger('form-submit-validate', [a, this, options, veto]);
				if (veto.veto) {
					log('ajaxSubmit: submit vetoed via form-submit-validate trigger');
					return this;
				}

				var q = $.param(a, traditional);
				if (qx) {
					q = (q ? (q + '&' + qx) : qx);
				}
				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 callbacks = [];
				if (options.resetForm) {
					callbacks.push(function() {
								$form.resetForm();
							});
				}
				if (options.clearForm) {
					callbacks.push(function() {
								$form.clearForm(options.includeHidden);
							});
				}

				// 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) {
						var fn = options.replaceTarget ? 'replaceWith' : 'html';
						$(options.target)[fn](data).each(oldSuccess, arguments);
					});
				} else if (options.success) {
					callbacks.push(options.success);
				}

				options.success = function(data, status, xhr) { // jQuery 1.4+
																// passes xhr as
																// 3rd arg
					var context = options.context || this; // jQuery 1.4+
															// supports scope
															// context
					for (var i = 0, max = callbacks.length; i < max; i++) {
						callbacks[i].apply(context, [data, status,
										xhr || $form, $form]);
					}
				};

				if (options.error) {
					var oldError = options.error;
					options.error = function(xhr, status, error) {
						var context = options.context || this;
						oldError.apply(context, [xhr, status, error, $form]);
					};
				}

				if (options.complete) {
					var oldComplete = options.complete;
					options.complete = function(xhr, status) {
						var context = options.context || this;
						oldComplete.apply(context, [xhr, status, $form]);
					};
				}

				// are there files to upload?

				// [value] (issue #113), also see comment:
				// https://github.com/malsup/form/commit/588306aedba1de01388032d5f42a60159eea9228#commitcomment-2180219
				var fileInputs = $('input[type=file]:enabled', this).filter(
						function() {
							return $(this).val() !== '';
						});

				var hasFileInputs = fileInputs.length > 0;
				var mp = 'multipart/form-data';
				var multipart = ($form.attr('enctype') == mp || $form
						.attr('encoding') == mp);

				var fileAPI = feature.fileapi && feature.formdata;
				log("fileAPI :" + fileAPI);
				var shouldUseFrame = (hasFileInputs || multipart) && !fileAPI;

				var jqxhr;

				// options.iframe allows user to force iframe mode
				// 06-NOV-09: now defaulting to iframe mode if file input is
				// detected
				if (options.iframe !== false
						&& (options.iframe || shouldUseFrame)) {
					// hack to fix Safari hang (thanks to Tim Molendijk for
					// this)
					// see:
					// http://groups.google.com/group/jquery-dev/browse_thread/thread/36395b7ab510dd5d
					if (options.closeKeepAlive) {
						$.get(options.closeKeepAlive, function() {
									jqxhr = fileUploadIframe(a);
								});
					} else {
						jqxhr = fileUploadIframe(a);
					}
				} else if ((hasFileInputs || multipart) && fileAPI) {
					jqxhr = fileUploadXhr(a);
				} else {
					jqxhr = $.ajax(options);
				}

				$form.removeData('jqxhr').data('jqxhr', jqxhr);

				// clear element array
				for (var k = 0; k < elements.length; k++) {
					elements[k] = null;
				}

				// fire 'notify' event
				this.trigger('form-submit-notify', [this, options]);
				return this;

				// utility fn for deep serialization
				function deepSerialize(extraData) {
					var serialized = $.param(extraData, options.traditional)
							.split('&');
					var len = serialized.length;
					var result = [];
					var i, part;
					for (i = 0; i < len; i++) {
						// #252; undo param space replacement
						serialized[i] = serialized[i].replace(/\+/g, ' ');
						part = serialized[i].split('=');
						// #278; use array instead of object storage, favoring
						// array serializations
						result.push([decodeURIComponent(part[0]),
								decodeURIComponent(part[1])]);
					}
					return result;
				}

				// XMLHttpRequest Level 2 file uploads (big hat tip to
				// francois2metz)
				function fileUploadXhr(a) {
					var formdata = new FormData();

					for (var i = 0; i < a.length; i++) {
						formdata.append(a[i].name, a[i].value);
					}

					if (options.extraData) {
						var serializedData = deepSerialize(options.extraData);
						for (i = 0; i < serializedData.length; i++) {
							if (serializedData[i]) {
								formdata.append(serializedData[i][0],
										serializedData[i][1]);
							}
						}
					}

					options.data = null;

					var s = $.extend(true, {}, $.ajaxSettings, options, {
								contentType : false,
								processData : false,
								cache : false,
								type : method || 'POST'
							});

					if (options.uploadProgress) {
						// workaround because jqXHR does not expose upload
						// property
						s.xhr = function() {
							var xhr = $.ajaxSettings.xhr();
							if (xhr.upload) {
								xhr.upload.addEventListener('progress',
										function(event) {
											var percent = 0;
											var position = event.loaded
													|| event.position; /*
																		 * event.position
																		 * is
																		 * deprecated
																		 */
											var total = event.total;
											if (event.lengthComputable) {
												percent = Math.ceil(position
														/ total * 100);
											}
											options.uploadProgress(event,
													position, total, percent);
										}, false);
							}
							return xhr;
						};
					}

					s.data = null;
					var beforeSend = s.beforeSend;
					s.beforeSend = function(xhr, o) {
						// Send FormData() provided by user
						if (options.formData) {
							o.data = options.formData;
						} else {
							o.data = formdata;
						}
						if (beforeSend) {
							beforeSend.call(this, xhr, o);
						}
					};
					return $.ajax(s);
				}

				// private function for handling file uploads (hat tip to
				// YAHOO!)
				function fileUploadIframe(a) {
					var form = $form[0], el, i, s, g, id, $io, io, xhr, sub, n, timedOut, timeoutHandle;
					var deferred = $.Deferred();

					// #341
					deferred.abort = function(status) {
						xhr.abort(status);
					};

					if (a) {
						// ensure that every serialized input is still enabled
						for (i = 0; i < elements.length; i++) {
							el = $(elements[i]);
							if (hasProp) {
								el.prop('disabled', false);
							} else {
								el.removeAttr('disabled');
							}
						}
					}

					s = $.extend(true, {}, $.ajaxSettings, options);
					s.context = s.context || s;
					id = 'jqFormIO' + (new Date().getTime());
					if (s.iframeTarget) {
						$io = $(s.iframeTarget);
						n = $io.attr2('name');
						if (!n) {
							$io.attr2('name', id);
						} else {
							id = n;
						}
					} else {
						$io = $('<iframe name="' + id + '" src="' + s.iframeSrc
								+ '" />');
						$io.css({
									position : 'absolute',
									top : '-1000px',
									left : '-1000px'
								});
					}
					io = $io[0];

					xhr = { // mock object
						aborted : 0,
						responseText : null,
						responseXML : null,
						status : 0,
						statusText : 'n/a',
						getAllResponseHeaders : function() {
						},
						getResponseHeader : function() {
						},
						setRequestHeader : function() {
						},
						abort : function(status) {
							var e = (status === 'timeout'
									? 'timeout'
									: 'aborted');
							log('aborting upload... ' + e);
							this.aborted = 1;

							try { // #214, #257
								if (io.contentWindow.document.execCommand) {
									io.contentWindow.document
											.execCommand('Stop');
								}
							} catch (ignore) {
							}

							$io.attr('src', s.iframeSrc); // abort op in
															// progress
							xhr.error = e;
							if (s.error) {
								s.error.call(s.context, xhr, e, status);
							}
							if (g) {
								$.event.trigger("ajaxError", [xhr, s, e]);
							}
							if (s.complete) {
								s.complete.call(s.context, xhr, e);
							}
						}
					};

					g = s.global;
					// trigger ajax global events so that activity/block
					// indicators work like normal
					if (g && 0 === $.active++) {
						$.event.trigger("ajaxStart");
					}
					if (g) {
						$.event.trigger("ajaxSend", [xhr, s]);
					}

					if (s.beforeSend
							&& s.beforeSend.call(s.context, xhr, s) === false) {
						if (s.global) {
							$.active--;
						}
						deferred.reject();
						return deferred;
					}
					if (xhr.aborted) {
						deferred.reject();
						return deferred;
					}

					// add submitting element to data if we know it
					sub = form.clk;
					if (sub) {
						n = sub.name;
						if (n && !sub.disabled) {
							s.extraData = s.extraData || {};
							s.extraData[n] = sub.value;
							if (sub.type == "image") {
								s.extraData[n + '.x'] = form.clk_x;
								s.extraData[n + '.y'] = form.clk_y;
							}
						}
					}

					var CLIENT_TIMEOUT_ABORT = 1;
					var SERVER_ABORT = 2;

					function getDoc(frame) {
						/*
						 * it looks like contentWindow or contentDocument do not
						 * carry the protocol property in ie8, when running
						 * under ssl frame.document is the only valid response
						 * document, since the protocol is know but not on the
						 * other two objects. strange? "Same origin policy"
						 * http://en.wikipedia.org/wiki/Same_origin_policy
						 */

						var doc = null;

						// IE8 cascading access check
						try {
							if (frame.contentWindow) {
								doc = frame.contentWindow.document;
							}
						} catch (err) {
							// IE8 access denied under ssl & missing protocol
							log('cannot get iframe.contentWindow document: '
									+ err);
						}

						if (doc) { // successful getting content
							return doc;
						}

						try { // simply checking may throw in ie8 under ssl or
								// mismatched protocol
							doc = frame.contentDocument
									? frame.contentDocument
									: frame.document;
						} catch (err) {
							// last attempt
							log('cannot get iframe.contentDocument: ' + err);
							doc = frame.document;
						}
						return doc;
					}

					// Rails CSRF hack (thanks to Yvan Barthelemy)
					var csrf_token = $('meta[name=csrf-token]').attr('content');
					var csrf_param = $('meta[name=csrf-param]').attr('content');
					if (csrf_param && csrf_token) {
						s.extraData = s.extraData || {};
						s.extraData[csrf_param] = csrf_token;
					}

					// take a breath so that pending repaints get some cpu time
					// before the upload starts
					function doSubmit() {
						// make sure form attrs are set
						var t = $form.attr2('target'), a = $form
								.attr2('action'), mp = 'multipart/form-data', et = $form
								.attr('enctype')
								|| $form.attr('encoding') || mp;

						// update form attrs in IE friendly way
						form.setAttribute('target', id);
						if (!method || /post/i.test(method)) {
							form.setAttribute('method', 'POST');
						}
						if (a != s.url) {
							form.setAttribute('action', s.url);
						}

						// ie borks in some cases when setting encoding
						if (!s.skipEncodingOverride
								&& (!method || /post/i.test(method))) {
							$form.attr({
										encoding : 'multipart/form-data',
										enctype : 'multipart/form-data'
									});
						}

						// support timout
						if (s.timeout) {
							timeoutHandle = setTimeout(function() {
										timedOut = true;
										cb(CLIENT_TIMEOUT_ABORT);
									}, s.timeout);
						}

						// look for server aborts
						function checkState() {
							try {
								var state = getDoc(io).readyState;
								log('state = ' + state);
								if (state
										&& state.toLowerCase() == 'uninitialized') {
									setTimeout(checkState, 50);
								}
							} catch (e) {
								log('Server abort: ', e, ' (', e.name, ')');
								cb(SERVER_ABORT);
								if (timeoutHandle) {
									clearTimeout(timeoutHandle);
								}
								timeoutHandle = undefined;
							}
						}

						// add "extra" data to form if provided in options
						var extraInputs = [];
						try {
							if (s.extraData) {
								for (var n in s.extraData) {
									if (s.extraData.hasOwnProperty(n)) {
										// if using the $.param format that
										// allows for multiple values with the
										// same name
										if ($.isPlainObject(s.extraData[n])
												&& s.extraData[n]
														.hasOwnProperty('name')
												&& s.extraData[n]
														.hasOwnProperty('value')) {
											extraInputs
													.push(	$('<input type="hidden" name="'
																	+ s.extraData[n].name
																	+ '">')
																	.val(s.extraData[n].value)
																	.appendTo(form)[0]);
										} else {
											extraInputs
													.push(	$('<input type="hidden" name="'
																	+ n + '">')
																	.val(s.extraData[n])
																	.appendTo(form)[0]);
										}
									}
								}
							}

							if (!s.iframeTarget) {
								// add iframe to doc and submit the form
								$io.appendTo('body');
							}
							if (io.attachEvent) {
								io.attachEvent('onload', cb);
							} else {
								io.addEventListener('load', cb, false);
							}
							setTimeout(checkState, 15);

							try {
								form.submit();
							} catch (err) {
								// just in case form has element with name/id of
								// 'submit'
								var submitFn = document.createElement('form').submit;
								submitFn.apply(form);
							}
						} finally {
							// reset attrs and remove "extra" input elements
							form.setAttribute('action', a);
							form.setAttribute('enctype', et); // #380
							if (t) {
								form.setAttribute('target', t);
							} else {
								$form.removeAttr('target');
							}
							$(extraInputs).remove();
						}
					}

					if (s.forceSync) {
						doSubmit();
					} else {
						setTimeout(doSubmit, 10); // this lets dom updates
													// render
					}

					var data, doc, domCheckCount = 50, callbackProcessed;

					function cb(e) {
						if (xhr.aborted || callbackProcessed) {
							return;
						}

						doc = getDoc(io);
						if (!doc) {
							log('cannot access response document');
							e = SERVER_ABORT;
						}
						if (e === CLIENT_TIMEOUT_ABORT && xhr) {
							xhr.abort('timeout');
							deferred.reject(xhr, 'timeout');
							return;
						} else if (e == SERVER_ABORT && xhr) {
							xhr.abort('server abort');
							deferred.reject(xhr, 'error', 'server abort');
							return;
						}

						if (!doc || doc.location.href == s.iframeSrc) {
							// response not received yet
							if (!timedOut) {
								return;
							}
						}
						if (io.detachEvent) {
							io.detachEvent('onload', cb);
						} else {
							io.removeEventListener('load', cb, false);
						}

						var status = 'success', errMsg;
						try {
							if (timedOut) {
								throw 'timeout';
							}

							var isXml = s.dataType == 'xml' || doc.XMLDocument
									|| $.isXMLDoc(doc);
							log('isXml=' + isXml);
							if (!isXml
									&& window.opera
									&& (doc.body === null || !doc.body.innerHTML)) {
								if (--domCheckCount) {
									// in some browsers (Opera) the iframe DOM
									// is not always traversable when
									// the onload callback fires, so we loop a
									// bit to accommodate
									log('requeing onLoad callback, DOM not available');
									setTimeout(cb, 250);
									return;
								}
								// let this fall through because server response
								// could be an empty document
								// log('Could not access iframe DOM after
								// mutiple tries.');
								// throw 'DOMException: not available';
							}

							// log('response detected');
							var docRoot = doc.body
									? doc.body
									: doc.documentElement;
							xhr.responseText = docRoot
									? docRoot.innerHTML
									: null;
							xhr.responseXML = doc.XMLDocument
									? doc.XMLDocument
									: doc;
							if (isXml) {
								s.dataType = 'xml';
							}
							xhr.getResponseHeader = function(header) {
								var headers = {
									'content-type' : s.dataType
								};
								return headers[header.toLowerCase()];
							};
							// support for XHR 'status' & 'statusText' emulation
							// :
							if (docRoot) {
								xhr.status = Number(docRoot
										.getAttribute('status'))
										|| xhr.status;
								xhr.statusText = docRoot
										.getAttribute('statusText')
										|| xhr.statusText;
							}

							var dt = (s.dataType || '').toLowerCase();
							var scr = /(json|script|text)/.test(dt);
							if (scr || s.textarea) {
								// see if user embedded response in textarea
								var ta = doc.getElementsByTagName('textarea')[0];
								if (ta) {
									xhr.responseText = ta.value;
									// support for XHR 'status' & 'statusText'
									// emulation :
									xhr.status = Number(ta
											.getAttribute('status'))
											|| xhr.status;
									xhr.statusText = ta
											.getAttribute('statusText')
											|| xhr.statusText;
								} else if (scr) {
									// account for browsers injecting pre around
									// json response
									var pre = doc.getElementsByTagName('pre')[0];
									var b = doc.getElementsByTagName('body')[0];
									if (pre) {
										xhr.responseText = pre.textContent
												? pre.textContent
												: pre.innerText;
									} else if (b) {
										xhr.responseText = b.textContent
												? b.textContent
												: b.innerText;
									}
								}
							} else if (dt == 'xml' && !xhr.responseXML
									&& xhr.responseText) {
								xhr.responseXML = toXml(xhr.responseText);
							}

							try {
								data = httpData(xhr, dt, s);
							} catch (err) {
								status = 'parsererror';
								xhr.error = errMsg = (err || status);
							}
						} catch (err) {
							log('error caught: ', err);
							status = 'error';
							xhr.error = errMsg = (err || status);
						}

						if (xhr.aborted) {
							log('upload aborted');
							status = null;
						}

						if (xhr.status) { // we've set xhr.status
							status = (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304)
									? 'success'
									: 'error';
						}

						// ordering of these callbacks/triggers is odd, but
						// that's how $.ajax does it
						if (status === 'success') {
							if (s.success) {
								s.success.call(s.context, data, 'success', xhr);
							}
							deferred.resolve(xhr.responseText, 'success', xhr);
							if (g) {
								$.event.trigger("ajaxSuccess", [xhr, s]);
							}
						} else if (status) {
							if (errMsg === undefined) {
								errMsg = xhr.statusText;
							}
							if (s.error) {
								s.error.call(s.context, xhr, status, errMsg);
							}
							deferred.reject(xhr, 'error', errMsg);
							if (g) {
								$.event.trigger("ajaxError", [xhr, s, errMsg]);
							}
						}

						if (g) {
							$.event.trigger("ajaxComplete", [xhr, s]);
						}

						if (g && !--$.active) {
							$.event.trigger("ajaxStop");
						}

						if (s.complete) {
							s.complete.call(s.context, xhr, status);
						}

						callbackProcessed = true;
						if (s.timeout) {
							clearTimeout(timeoutHandle);
						}

						// clean up
						setTimeout(function() {
									if (!s.iframeTarget) {
										$io.remove();
									} else { // adding else to clean up
												// existing iframe response.
										$io.attr('src', s.iframeSrc);
									}
									xhr.responseXML = null;
								}, 100);
					}

					var toXml = $.parseXML || function(s, doc) { // use
																	// parseXML
																	// if
																	// available
																	// (jQuery
																	// 1.5+)
								if (window.ActiveXObject) {
									doc = new ActiveXObject('Microsoft.XMLDOM');
									doc.async = 'false';
									doc.loadXML(s);
								} else {
									doc = (new DOMParser()).parseFromString(s,
											'text/xml');
								}
								return (doc && doc.documentElement && doc.documentElement.nodeName != 'parsererror')
										? doc
										: null;
							};
					var parseJSON = $.parseJSON || function(s) {
						/* jslint evil:true */
						return window['eval']('(' + s + ')');
					};

					var httpData = function(xhr, type, s) { // mostly lifted
															// from jq1.4.4

						var ct = xhr.getResponseHeader('content-type') || '', xml = type === 'xml'
								|| !type && ct.indexOf('xml') >= 0, data = xml
								? xhr.responseXML
								: xhr.responseText;

						if (xml
								&& data.documentElement.nodeName === 'parsererror') {
							if ($.error) {
								$.error('parsererror');
							}
						}
						if (s && s.dataFilter) {
							data = s.dataFilter(data, type);
						}
						if (typeof data === 'string') {
							if (type === 'json' || !type
									&& ct.indexOf('json') >= 0) {
								data = parseJSON(data);
							} else if (type === "script" || !type
									&& ct.indexOf("javascript") >= 0) {
								$.globalEval(data);
							}
						}
						return data;
					};

					return deferred;
				}
			};

			/**
			 * ajaxForm() provides a mechanism for fully automating form
			 * submission.
			 * 
			 * The advantages of using this method instead of ajaxSubmit() are:
			 * 
			 * 1: This method will include coordinates for <input type="image" />
			 * elements (if the element is used to submit the form). 2. This
			 * method will include the submit element's name/value data (for the
			 * element that was used to submit the form). 3. This method binds
			 * the submit() method to the form for you.
			 * 
			 * The options argument for ajaxForm works exactly as it does for
			 * ajaxSubmit. ajaxForm merely passes the options argument along
			 * after properly binding events for submit elements and the form
			 * itself.
			 */
			$.fn.ajaxForm = function(options) {
				options = options || {};
				options.delegation = options.delegation
						&& $.isFunction($.fn.on);

				// in jQuery 1.3+ we can fix mistakes with the ready state
				if (!options.delegation && this.length === 0) {
					var o = {
						s : this.selector,
						c : this.context
					};
					if (!$.isReady && o.s) {
						log('DOM not ready, queuing ajaxForm');
						$(function() {
									$(o.s, o.c).ajaxForm(options);
								});
						return this;
					}
					// is your DOM ready?
					// http://docs.jquery.com/Tutorials:Introducing_$(document).ready()
					log('terminating; zero elements found by selector'
							+ ($.isReady ? '' : ' (DOM not ready)'));
					return this;
				}

				if (options.delegation) {
					$(document).off('submit.form-plugin', this.selector,
							doAjaxSubmit).off('click.form-plugin',
							this.selector, captureSubmittingElement).on(
							'submit.form-plugin', this.selector, options,
							doAjaxSubmit).on('click.form-plugin',
							this.selector, options, captureSubmittingElement);
					return this;
				}

				return this.ajaxFormUnbind().bind('submit.form-plugin',
						options, doAjaxSubmit).bind('click.form-plugin',
						options, captureSubmittingElement);
			};

			// private event handlers
			function doAjaxSubmit(e) {
				/* jshint validthis:true */
				var options = e.data;
				if (!e.isDefaultPrevented()) { // if event has been canceled,
												// don't proceed
					e.preventDefault();
					$(e.target).ajaxSubmit(options); // #365
				}
			}

			function captureSubmittingElement(e) {
				/* jshint validthis:true */
				var target = e.target;
				var $el = $(target);
				if (!($el.is("[type=submit],[type=image]"))) {
					// is this a child element of the submit el? (ex: a span
					// within a button)
					var t = $el.closest('[type=submit]');
					if (t.length === 0) {
						return;
					}
					target = t[0];
				}
				var form = this;
				form.clk = target;
				if (target.type == 'image') {
					if (e.offsetX !== undefined) {
						form.clk_x = e.offsetX;
						form.clk_y = e.offsetY;
					} else if (typeof $.fn.offset == 'function') {
						var offset = $el.offset();
						form.clk_x = e.pageX - offset.left;
						form.clk_y = e.pageY - offset.top;
					} else {
						form.clk_x = e.pageX - target.offsetLeft;
						form.clk_y = e.pageY - target.offsetTop;
					}
				}
				// clear form vars
				setTimeout(function() {
							form.clk = form.clk_x = form.clk_y = null;
						}, 100);
			}

			// ajaxFormUnbind unbinds the event handlers that were bound by
			// ajaxForm
			$.fn.ajaxFormUnbind = function() {
				return this.unbind('submit.form-plugin click.form-plugin');
			};

			/**
			 * formToArray() gathers form element data into an array of objects
			 * that can be passed to any of the following ajax functions: $.get,
			 * $.post, or load. Each object in the array has both a 'name' and
			 * 'value' property. An example of an array for a simple login form
			 * might be:
			 *  [ { name: 'username', value: 'jresig' }, { name: 'password',
			 * value: 'secret' } ]
			 * 
			 * It is this array that is passed to pre-submit callback functions
			 * provided to the ajaxSubmit() and ajaxForm() methods.
			 */
			$.fn.formToArray = function(semantic, elements) {
				var a = [];
				if (this.length === 0) {
					return a;
				}

				var form = this[0];
				var formId = this.attr('id');
				var els = semantic
						? form.getElementsByTagName('*')
						: form.elements;
				var els2;

				if (els && !/MSIE [678]/.test(navigator.userAgent)) { // #390
					els = $(els).get(); // convert to standard array
				}

				// #386; account for inputs outside the form which use the
				// 'form' attribute
				if (formId) {
					els2 = $(':input[form=' + formId + ']').get();
					if (els2.length) {
						els = (els || []).concat(els2);
					}
				}

				if (!els || !els.length) {
					return a;
				}

				var i, j, n, v, el, max, jmax;
				for (i = 0, max = els.length; i < max; i++) {
					el = els[i];
					n = el.name;
					if (!n || el.disabled) {
						continue;
					}

					if (semantic && form.clk && el.type == "image") {
						// handle image inputs on the fly when semantic == true
						if (form.clk == el) {
							a.push({
										name : n,
										value : $(el).val(),
										type : el.type
									});
							a.push({
										name : n + '.x',
										value : form.clk_x
									}, {
										name : n + '.y',
										value : form.clk_y
									});
						}
						continue;
					}

					v = $.fieldValue(el, true);
					if (v && v.constructor == Array) {
						if (elements) {
							elements.push(el);
						}
						for (j = 0, jmax = v.length; j < jmax; j++) {
							a.push({
										name : n,
										value : v[j]
									});
						}
					} else if (feature.fileapi && el.type == 'file') {
						if (elements) {
							elements.push(el);
						}
						var files = el.files;
						if (files.length) {
							for (j = 0; j < files.length; j++) {
								a.push({
											name : n,
											value : files[j],
											type : el.type
										});
							}
						} else {
							// #180
							a.push({
										name : n,
										value : '',
										type : el.type
									});
						}
					} else if (v !== null && typeof v != 'undefined') {
						if (elements) {
							elements.push(el);
						}
						a.push({
									name : n,
									value : v,
									type : el.type,
									required : el.required
								});
					}
				}

				if (!semantic && form.clk) {
					// input type=='image' are not found in elements array!
					// handle it here
					var $input = $(form.clk), input = $input[0];
					n = input.name;
					if (n && !input.disabled && input.type == 'image') {
						a.push({
									name : n,
									value : $input.val()
								});
						a.push({
									name : n + '.x',
									value : form.clk_x
								}, {
									name : n + '.y',
									value : form.clk_y
								});
					}
				}
				return a;
			};

			/**
			 * Serializes form data into a 'submittable' string. This method
			 * will return a string in the format: name1=value1&name2=value2
			 */
			$.fn.formSerialize = function(semantic) {
				// hand off to jQuery.param for proper encoding
				return $.param(this.formToArray(semantic));
			};

			/**
			 * Serializes all field elements in the jQuery object into a query
			 * string. This method will return a string in the format:
			 * name1=value1&name2=value2
			 */
			$.fn.fieldSerialize = function(successful) {
				var a = [];
				this.each(function() {
							var n = this.name;
							if (!n) {
								return;
							}
							var v = $.fieldValue(this, successful);
							if (v && v.constructor == Array) {
								for (var i = 0, max = v.length; i < max; i++) {
									a.push({
												name : n,
												value : v[i]
											});
								}
							} else if (v !== null && typeof v != 'undefined') {
								a.push({
											name : this.name,
											value : v
										});
							}
						});
				// hand off to jQuery.param for proper encoding
				return $.param(a);
			};

			/**
			 * Returns the value(s) of the element in the matched set. For
			 * example, consider the following form:
			 * 
			 * <form><fieldset> <input name="A" type="text" /> <input name="A"
			 * type="text" /> <input name="B" type="checkbox" value="B1" />
			 * <input name="B" type="checkbox" value="B2"/> <input name="C"
			 * type="radio" value="C1" /> <input name="C" type="radio"
			 * value="C2" /> </fieldset></form>
			 * 
			 * var v = $('input[type=text]').fieldValue(); // if no values are
			 * entered into the text inputs v == ['',''] // if values entered
			 * into the text inputs are 'foo' and 'bar' v == ['foo','bar']
			 * 
			 * var v = $('input[type=checkbox]').fieldValue(); // if neither
			 * checkbox is checked v === undefined // if both checkboxes are
			 * checked v == ['B1', 'B2']
			 * 
			 * var v = $('input[type=radio]').fieldValue(); // if neither radio
			 * is checked v === undefined // if first radio is checked v ==
			 * ['C1']
			 * 
			 * The successful argument controls whether or not the field element
			 * must be 'successful' (per
			 * http://www.w3.org/TR/html4/interact/forms.html#successful-controls).
			 * The default value of the successful argument is true. If this
			 * value is false the value(s) for each element is returned.
			 * 
			 * Note: This method *always* returns an array. If no valid value
			 * can be determined the array will be empty, otherwise it will
			 * contain one or more values.
			 */
			$.fn.fieldValue = function(successful) {
				for (var val = [], i = 0, max = this.length; i < max; i++) {
					var el = this[i];
					var v = $.fieldValue(el, successful);
					if (v === null || typeof v == 'undefined'
							|| (v.constructor == Array && !v.length)) {
						continue;
					}
					if (v.constructor == Array) {
						$.merge(val, v);
					} else {
						val.push(v);
					}
				}
				return val;
			};

			/**
			 * Returns the value of the field element.
			 */
			$.fieldValue = function(el, successful) {
				var n = el.name, t = el.type, tag = el.tagName.toLowerCase();
				if (successful === undefined) {
					successful = true;
				}

				if (successful
						&& (!n || el.disabled || t == 'reset' || t == 'button'
								|| (t == 'checkbox' || t == 'radio')
								&& !el.checked
								|| (t == 'submit' || t == 'image') && el.form
								&& el.form.clk != el || tag == 'select'
								&& el.selectedIndex == -1)) {
					return null;
				}

				if (tag == 'select') {
					var index = el.selectedIndex;
					if (index < 0) {
						return null;
					}
					var a = [], ops = el.options;
					var one = (t == 'select-one');
					var max = (one ? index + 1 : ops.length);
					for (var i = (one ? index : 0); i < max; i++) {
						var op = ops[i];
						if (op.selected) {
							var v = op.value;
							if (!v) { // extra pain for IE...
								v = (op.attributes && op.attributes.value && !(op.attributes.value.specified))
										? op.text
										: op.value;
							}
							if (one) {
								return v;
							}
							a.push(v);
						}
					}
					return a;
				}
				return $(el).val();
			};

			/**
			 * Clears the form data. Takes the following actions on the form's
			 * input fields: - input text fields will have their 'value'
			 * property set to the empty string - select elements will have
			 * their 'selectedIndex' property set to -1 - checkbox and radio
			 * inputs will have their 'checked' property set to false - inputs
			 * of type submit, button, reset, and hidden will *not* be effected -
			 * button elements will *not* be effected
			 */
			$.fn.clearForm = function(includeHidden) {
				return this.each(function() {
					$('input,select,textarea', this).clearFields(includeHidden);
				});
			};

			/**
			 * Clears the selected form elements.
			 */
			$.fn.clearFields = $.fn.clearInputs = function(includeHidden) {
				var re = /^(?:color|date|datetime|email|month|number|password|range|search|tel|text|time|url|week)$/i; // 'hidden'
																														// is
																														// not
																														// in
																														// this
																														// list
				return this.each(function() {
							var t = this.type, tag = this.tagName.toLowerCase();
							if (re.test(t) || tag == 'textarea') {
								this.value = '';
							} else if (t == 'checkbox' || t == 'radio') {
								this.checked = false;
							} else if (tag == 'select') {
								this.selectedIndex = -1;
							} else if (t == "file") {
								if (/MSIE/.test(navigator.userAgent)) {
									$(this).replaceWith($(this).clone(true));
								} else {
									$(this).val('');
								}
							} else if (includeHidden) {
								// includeHidden can be the value true, or it
								// can be a selector string
								// indicating a special test; for example:
								// $('#myForm').clearForm('.special:hidden')
								// the above would clean hidden inputs that have
								// the class of 'special'
								if ((includeHidden === true && /hidden/.test(t))
										|| (typeof includeHidden == 'string' && $(this)
												.is(includeHidden))) {
									this.value = '';
								}
							}
						});
			};

			/**
			 * Resets the form data. Causes all form elements to be reset to
			 * their original value.
			 */
			$.fn.resetForm = function() {
				return this.each(function() {
					// guard against an input with the name of 'reset'
					// note that IE reports the reset function as an 'object'
					if (typeof this.reset == 'function'
							|| (typeof this.reset == 'object' && !this.reset.nodeType)) {
						this.reset();
					}
				});
			};

			/**
			 * Enables or disables any matching elements.
			 */
			$.fn.enable = function(b) {
				if (b === undefined) {
					b = true;
				}
				return this.each(function() {
							this.disabled = !b;
						});
			};

			/**
			 * Checks/unchecks any matching checkboxes or radio buttons and
			 * selects/deselects and matching option elements.
			 */
			$.fn.selected = function(select) {
				if (select === undefined) {
					select = true;
				}
				return this.each(function() {
							var t = this.type;
							if (t == 'checkbox' || t == 'radio') {
								this.checked = select;
							} else if (this.tagName.toLowerCase() == 'option') {
								var $sel = $(this).parent('select');
								if (select && $sel[0]
										&& $sel[0].type == 'select-one') {
									// deselect all other options
									$sel.find('option').selected(false);
								}
								this.selected = select;
							}
						});
			};

			// expose debug var
			$.fn.ajaxSubmit.debug = false;

			// helper fn for console logging
			function log() {
				if (!$.fn.ajaxSubmit.debug) {
					return;
				}
				var msg = '[jquery.form] '
						+ Array.prototype.join.call(arguments, '');
				if (window.console && window.console.log) {
					window.console.log(msg);
				} else if (window.opera && window.opera.postError) {
					window.opera.postError(msg);
				}
			}

		}));
		return jQuery;
	};
	exports('jquery_form', jquery_form);
});