说干就干,花了一天时间进行研究、实现代码,Yii的组件架构和OOP的特性,让我比较方便的就实现了这个目标:兼容RPC批量调用和正常的MVC页面流程。
具体实现了几个类:
- ExtApplication,是CApplication的继承类,覆盖了ProcessRequest方法,区分普通页面action和RPC调用action等。
- Controller,是CController的继承类,覆盖若干方法以调用ExtAction
- ExtAction,是CAction的继承类,用于执行RPC方法
- ApiAction,也是CAction的继承类,用于暴露服务器端方法
- ExtDirect_API和ExtDirect_CacheProvider这两个类是direct自带的类,稍作修改。
将以上类文件放入项目的protected/components下即可,然后修改入口文件index.php为:
run();
然后修改 protected/views/layouts/main.php,加入extjs引用和代码:
这段代码“”就是调用了接口页面,得到了服务器端暴露的方法列表,如:
Ext.ns('Ext.app'); Ext.app.REMOTING_API = {"url":"\/index.php","type":"remoting","actions":{"Site":[{"name":"DoLogin","len":1,"formHandler":true},{"name":"Logout","len":0},{"name":"updateBasicInfo","len":1,"formHandler":true},{"name":"getBasicInfo","len":2},{"name":"GetPhoneInfo","len":0},{"name":"getLocationInfo","len":1}],"Test":[{"name":"Index","len":0}]}};
然后这句:“Ext.Direct.addProvider(Ext.app.REMOTING_API);“就是向extjs注册了rpc的api。
这样准备工作就完成了。
接着添加apiAction的定义到 SiteController.php中:
/** * Declares class-based actions. */ public function actions() { return array( 'api'=>array( 'class'=>'ApiAction', ), ); }
然后在Controllers中定义普通action和rpc方法:
public function actionLogin() { $this->render('login'); } /** * @remotable * @formHandler */ public function actionDoLogin($formPacket) { $model=new LoginForm; // collect user input data if(isset($formPacket['username'])) { $model->username = $formPacket['username']; $model->password = $formPacket['password']; // validate user input and redirect to the previous page if valid if($model->validate() && $model->login()) $output['success'] = true; else { $output['success'] = false; $output['errors'] = $model->errors; } return $output; } }
注意上面的 “
/**
* @remotable
* @formHandler
*/”
注释,表示DoLogin这个方法是可以远程调用的,且是表单处理方法,这里的备注可以在反射的时候用到。而且可以发现普通页面action和RPC的action的写法上的区别:一个是没有参数的,render页面或者直接输出内容的;一个是有参数的,然后有返回值的。
actionLogin对应的视图文件中就可以调用DoLogin方法来登录用户了:
var indexUrl = 'createUrl('index') ?>'; Ext.onReady(function(){ function login(){ if(form.getForm().isValid()){ form.getForm().submit({ waitMsg: '正在登录,请稍候...', waitTitle:'登录中', success: function(form,action){ loginwin.hide(); window.location = indexUrl; } }); } } var form = new Ext.form.FormPanel({ baseCls: 'x-plain', labelWidth: 55, defaultType: 'textfield', title: '会员管理系统', api: {submit: Site.DoLogin }, items: [{ fieldLabel: '用户名', name: 'username', allowBlank: false, blankText: "请输入您的用户名", anchor:'92%', listeners: { specialkey : function(field, e) { if (e.getKey() == Ext.EventObject.ENTER) { login(); } } } },{ inputType: "password", fieldLabel: '密 码', allowBlank: false, blankText: "请输入您的密码", name: 'password', anchor: '92%' , listeners: { specialkey : function(field, e) { if (e.getKey() == Ext.EventObject.ENTER) { login(); } } } }], buttons: [{ text: '登录', handler: login }, {text: '重设', handler: function(){form.getForm().reset();}} ], buttonAlign:'center' }); var loginwin = new Ext.Window({ title: "管理登录", width: 300, height: 180, modal: true, draggable: false, closable: false, tools: [{id: "help", handler: function(){Ext.Msg.alert('帮助','输入您的用户名和密码登录系统。')}}], layout: 'fit', plain:true, bodyStyle:'padding:5px;', items: form, resizable: false }); loginwin.show(); });
注意FormPanel中的 “api: {submit: Site.DoLogin },”,这样表单的处理请求就自动被发送到了SiteController的actionDoLogin方法,其处理结果也自动返回给客户端了,是不是很方便?
以上涉及到的完整代码已附上,需要的朋友拿去吧,有问题别忘了反馈,一起改进完善。
效果截图:
以上截图表现的是如何处理表单,下面说说如何调用服务器端其他方法,如我们在SiteController中定义:
/** * @remotable * put your comment there... * This method configured with len=2, so 2 arguments will be sent * in the order according to the client side specified paramOrder * @param Number $userId * @param String $foo * @return Array response packet */ function actiongetBasicInfo($userId, $foo){ return array( 'success'=>true, 'data'=>array( 'foo'=>$foo, 'name'=>'Aaron Conran', 'company'=>'Ext JS, LLC', 'email'=>'[email protected]' ) ); } /** * Handler for client side form sumbit * @formHandler * @remotable * * @param Array $formPacket Collection of form items along with direct data * @return Array response packet * */ function actionUpdateBasicInfo($formPacket){ $response = array(); $email = $formPacket['email']; if ($email == '[email protected]') { $success = false; $response['errors'] = array( 'email'=>'already taken' ); } else { $success = true; } $response['success'] = $success; // return form packet for demonstration/testing purposes $response['debug_formPacket'] = $formPacket; return $response; }
接下来就可以在客户端js中这么调用:
var basicInfo = new Ext.form.FormPanel({ // configs for FormPanel title: 'Basic Information', border: false, padding: 10, buttons:[{ text: 'Submit', handler: function(){ basicInfo.getForm().submit({ params: { foo: 'bar', uid: 34 } }); } }], // configs apply to child items defaults: {anchor: '-20'}, // provide some room on right for validation errors defaultType: 'textfield', items: [{ fieldLabel: 'Name', name: 'name' },{ fieldLabel: 'Email', msgTarget: 'side', name: 'email' },{ fieldLabel: 'Company', name: 'company' }], // configs for BasicForm api: { // The server-side method to call for load() requests load: Site.getBasicInfo, // The server-side must mark the submit handler as a 'formHandler' submit: Site.UpdateBasicInfo }, // specify the order for the passed params paramOrder: ['uid', 'foo'] });
这样一来,这个表单的初始数据加载和提交,就都是RPC自动调用了,是不是很赞?呵呵