您的位置:首页 > Web前端 > JQuery

layui tree treegrid javascript js treetable 前端 jquery form

2017-09-29 14:49 615 查看
最近接了个活,被狠狠的坑了一把,也没有什么收获。接近尾声,把唯一的一点成果发出来。

新项目,没有完整的前端框架,选择了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
= 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
)
&& s.extraData

.hasOwnProperty('name')
&& s.extraData

.hasOwnProperty('value')) {
extraInputs
.push( $('<input type="hidden" name="'
+ s.extraData
.name
+ '">')
.val(s.extraData
.value)
.appendTo(form)[0]);
} else {
extraInputs
.push( $('<input type="hidden" name="'
+ n + '">')
.val(s.extraData
)
.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);
});
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息