您的位置:首页 > 编程语言 > PHP开发

Yii中CGridView单元格组件delete之Ajax特性

2014-07-10 18:03 417 查看
Yii中用Gii生成的admin视图是用CGridView这个Zii组件实现的,它的添加、更新操作仍然是转换到create、update视图来完成的,而删除操作是Ajax方式,从而可以实现连续删除(因为admin视图“不动”)。

当然,实际控制器代码中为不支持Ajax的情况(虽然现在禁用javascript的情况较少出现了)留了后路,即非ajax方式,删除后跳转到预设的返回URL或admin:

if (!isset($_POST['ajax']))
$this->redirect(isset($_POST['returnUrl']) ? $_POST['returnUrl'] : array('admin'));


还是来看看admin视图中“调用”CGridView的情况:

$this->widget('zii.widgets.grid.CGridView', array(
'id' => 'post-grid',
'dataProvider' => $model->search(),
'filter' => $model,
'columns' => array(
……………………
array('class' => 'CButtonColumn', ),
),
));


单元格组件的最后一列(按钮列)是通过类CButtonColumn来实现的——Yii中定义数据值的方式常常有3种,即直接定义值、方法定义值、类定义值

CButtonColumn类的使用方法,http://blog.sina.com.cn/s/blog_795914e701014sav.html 有较详细的论述,这里对其进行进一步理解

默认CButtonColumn类呈现3个按钮,从直观上理解,每个按钮都有对应的图片、鼠标移上去显示的提示文字、状态栏可见的对应URL,所以,每个按钮包含了XxxButtonImageUrl 、XxxButtonLabel、XxxButtonUrl 属性,还有XxxButtonOptions属性 (按钮的HTML选项,像其他插件的htmlOptions 属性一样)。删除按钮略特殊,可以使用deleteConfirmation属性(删除前的提示信息)和类事件属性afterDelete。XxxButtonUrl
属性中可以使用$row、$data、$this等变量来定制目标url。

CButtonColumn的灵活性在于可以通过模板实现按钮定制,定制的基本思路是:

array
(
'class'=>'CButtonColumn',
'template'=>'{id1}{id2}{delete}',
'buttons'=>array
(
'id1' => array
(
'label'=>'功能1',
'imageUrl'=>Yii::app()->request->baseUrl.'/images/id1.png',
'url'=>'Yii::app()->createUrl("users/id1", array("id"=>$data->id))',
),
'id2' => array
(
'label'=>'功能2',
'url'=>'"#"',
'visible'=>'$data->score > 0',
'click'=>'function(){alert("Going down!");}',
),
),
),

即:指明类CButtonColumn,然后用template参数指明所有按钮id(update、view、delete等我默认类可识别的id),用buttons数组指明每个按钮对应的有关属性(默认的按钮可以不进行属性设定,使用它本身默认的功能,如上面的delete)。

一些细节用法:

*指定按钮列头部的显示文字:用'header' => '操作', 进行属性设定即可

*隐藏某个按钮:类似'updateButtonOptions'=>array('style'=>'display:none'),

*每个按钮可设定的有关属性

'buttonID' => array

(

'label'=>'...', //按钮的文本标签.

'url'=>'...', //使用 PHP 表达式得出按钮的 URL.

'imageUrl'=>'...', //按钮的图片路径.

'options'=>array(), //按钮的 HTML 选项.

'click'=>'...', //当点击按钮时调用的 javascript 函数

'visible'=>'...', //确定按钮是否显示的 PHP 表达式

)

*删除的确定信息中包含和该行实例相关的信息:参考以下用法

array

(

'class'=>'CButtonColumn',

'deleteConfirmation'=>"js:'Record with ID '+$(this).parent().parent().children(':first-child').text()+' will be deleted! Continue?'",

),



array

(

'class'=>'CButtonColumn',

'deleteConfirmation'=>"js:'Do you really want to delete record with ID '+$(this).parent().parent().children(':nth-child(2)').text()+'?'",

),

由于Gii生成的是默认按钮,仅仅指定了类名,要理解默认的删除的动作,还是需要了解该类源码。

用一定的IDE,可以从源码清晰看到,CButtonColumn类从CGridColumn继承,有很多(19个)自己的变量,但只有少量(5个)自己的函数:init()、initDefaultButtons()、registerClientScript()、renderButton、renderDataCellContent

init()用于初始化列(为按钮列注册必要的客户端Javascript脚本):先调用initDefaultButtons初始化默认按钮,再设定每个按钮对应的click事件处理代码,最后用registerClientScript注册Javascript脚本。

initDefaultButtons()用于初始化默认的三个按钮:设定默认按钮的Label、ImageUrl等,构建buttons数组。其中对delete按钮的设定明显较复杂,其中对delete的click事件处理代码进行了设置,如下:

if(!isset($this->buttons['delete']['click']))

{

if(is_string($this->deleteConfirmation))

$confirmation="if(!confirm(".CJavaScript::encode($this->deleteConfirmation).")) return false;";

…………………………………………

if($this->afterDelete===null)

$this->afterDelete='function(){}';

$this->buttons['delete']['click']=<<<EOD
function() {
$confirmation
var th = this,
afterDelete = $this->afterDelete;
jQuery('#{$this->grid->id}').yiiGridView('update', {
type: 'POST',
url: jQuery(this).attr('href'),$csrf
success: function(data) {
jQuery('#{$this->grid->id}').yiiGridView('update');
afterDelete(th, true, data);
},
error: function(XHR) {
return afterDelete(th, false, XHR);
}
});
return false;
}
EOD;


上面的$confirmation其实是一小段Javascript,当confirm对话返回false(选择取消)时,return false得到执行,事件处理就结束了,什么也不会被删除。反之,当confirm返回true时,事件处理过程就不会被“拦截”,继续向下执行。

afterDelete变量获取用户设定的处理过程(如果用户未设定,就是个空函数)。用jQuery选择符选择该单元格并调用其yiiGridView函数——从结构来看yiiGridView应该是一个Ajax的POST方式的update过程,最后返回false结束事件处理。显然,这部分是要在删除按钮点击后发送删除请求,在成功后刷新单元格显示。这里的关键是yiiGridView函数,并且它使用了两次。

registerClientScript就是注册客户端脚本,这个查阅权威指南可以了解客户端脚本的分布和注册的。

renderButton是根据参数如何渲染按钮,它在renderDataCellContent中被调用,最终渲染出整列。

我们从哪里来了解用于单元格的Javascript函数yiiGridView呢?

从框架的目录zii/widgets/assets/gridview下可以发现用于单元格组件的js脚本jquery.yiigridview.js文件,它是作为jQuery的插件形式设计的。

因为该插件的代码是针对整个单元格组件的功能来说的,我们只挑选对删除按钮相关的部分来了解:

$.fn.yiiGridView = function (method) {
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || !method) {
return methods.init.apply(this, arguments);
} else {
$.error('Method ' + method + ' does not exist on jQuery.yiiGridView');
return false;
}
};
yiiGridView函数是一个方法“调用器”,它的第一个参数method是被调用的方法(如果在方法列表中)或对象(不在方法列表中)。methods属性很长,我们挑键为update的部分来看:

update: function (options) {
var customError;
if (options && options.error !== undefined) {
customError = options.error;
delete options.error;
}

return this.each(function () {
var $form,
$grid = $(this),
id = $grid.attr('id'),
settings = gridSettings[id];

options = $.extend({
type: settings.ajaxType,
url: $grid.yiiGridView('getUrl'),
success: function (data) {
var $data = $('<div>' + data + '</div>');
$.each(settings.ajaxUpdate, function (i, el) {
var updateId = '#' + el;
$(updateId).replaceWith($(updateId, $data));
});
if (settings.afterAjaxUpdate !== undefined) {
settings.afterAjaxUpdate(id, data);
}
if (settings.selectableRows > 0) {
selectCheckedRows(id);
}
},
complete: function () {
yiiXHR[id] = null;
$grid.removeClass(settings.loadingClass);
},
error: function (XHR, textStatus, errorThrown) {
var ret, err;
if (XHR.readyState === 0 || XHR.status === 0) {
return;
}
if (customError !== undefined) {
ret = customError(XHR);
if (ret !== undefined && !ret) {
return;
}
}
switch (textStatus) {
case 'timeout':
err = 'The request timed out!';
break;
case 'parsererror':
err = 'Parser error!';
break;
case 'error':
if (XHR.status && !/^\s*$/.test(XHR.status)) {
err = 'Error ' + XHR.status;
} else {
err = 'Error';
}
if (XHR.responseText && !/^\s*$/.test(XHR.responseText)) {
err = err + ': ' + XHR.responseText;
}
break;
}

if (settings.ajaxUpdateError !== undefined) {
settings.ajaxUpdateError(XHR, textStatus, errorThrown, err);
} else if (err) {
alert(err);
}
}
}, options || {});
if (options.type === 'GET') {
if (options.data !== undefined) {
options.url = $.param.querystring(options.url, options.data);
options.data = {};
}
} else {
if (options.data === undefined) {
options.data = $(settings.filterSelector).serialize();
}
}
if(yiiXHR[id] != null){
yiiXHR[id].abort();
}
//class must be added after yiiXHR.abort otherwise ajax.error will remove it
$grid.addClass(settings.loadingClass);

if (settings.ajaxUpdate !== false) {
if(settings.ajaxVar) {
options.url = $.param.querystring(options.url, settings.ajaxVar + '=' + id);
}
if (settings.beforeAjaxUpdate !== undefined) {
settings.beforeAjaxUpdate(id, options);
}
yiiXHR[id] = $.ajax(options);
} else {  // non-ajax mode
if (options.type === 'GET') {
window.location.href = options.url;
} else {  // POST mode
$form = $('<form action="' + options.url + '" method="post"></form>').appendTo('body');
if (options.data === undefined) {
options.data = {};
}

if (options.data.returnUrl === undefined) {
options.data.returnUrl = window.location.href;
}

$.each(options.data, function (name, value) {
$form.append($('<input type="hidden" name="t" value="" />').attr('name', name).val(value));
});
$form.submit();
}
}
});
},
我们知道索引为update的值是一个function,它的参数options对应jQuery的AJAX请求中的选项,所以我们前面的用法中看到和jQuery Ajax请求相似的格式。

update的作用是执行基于Ajax的单元格内容的更新,默认情况,请求的URL是产生当前单元格视图对应的那个URL,返回值为jQuery对象。

对于使用者来说,再细究细节没有太大意义。我们来了解$.ajax的有关选项。

type参数指定请求方式,默认是GET,上面的删除中使用POST方式。

url参数是发送请求的目的地址,默认当前页地址。注意,前面的代码中,url第一次是:

url: jQuery(this).attr('href'),$csrf 它是指向类似href="/blog/index.php?r=post/delete&id=1"的URL的,所以,其实是执行actionDelete($id)。url第二次是:

jQuery('#{$this->grid->id}').yiiGridView('update'); 它没有指定url,会使用url: $grid.yiiGridView('getUrl'), 即由方法getUrl确定:

getUrl: function () {

var sUrl = gridSettings[this.attr('id')].url;

return sUrl || this.children('.keys').attr('title');

},

应该就是admin视图

如果要处理 $.ajax() 得到的数据,则需要使用回调函数:beforeSend、error、dataFilter、success、complete。上面用到了success和error 。success的时候进行了上面第二个url的调用,即刷新过程。

admin视图中另一个Ajax操作是高级搜索表单:

<?php echo CHtml::link('高级搜索','#',array('class'=>'search-button')); ?>

<div class="search-form" style="display:none">

<?php $this->renderPartial('_search',array(

'model'=>$model,

)); ?>

</div><!-- search-form -->

因为表单初始是不显示的,所以提供了链接按钮“高级搜索”来显示/隐藏切换。对应注册的客户端Javascript脚本包含了显示/隐藏切换代码和Ajax方式提交表单的代码:

Yii::app()->clientScript->registerScript('search', "

$('.search-button').click(function(){

$('.search-form').toggle();

return false;

});

$('.search-form form').submit(function(){

$('#user-grid').yiiGridView('update', {

data: $(this).serialize()

});

return false;

});

");

在表单提交中,我们再次见到了方法调用器yiiGridView和被调用的函数update,指定了选项data为对表单数据的序列化打包。

分析了上面的特点,我们可以制作Ajax方式的增加和修改了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: