很久没有用过ExtJS,正好一个新的在线系统需要搭建一个运营平台,没有交互,没有页面设计,没有原型,我那点可怜的美感只能借助Extjs来搭建了。
1.通用的架子典型的Border布局+tree菜单+TabPanel的内容区的框架,模块清晰简单,菜单可配置。
Ext.BLANK_IMAGE_URL="ext/resources/images/default/s.gif";
Ext.QuickTips.init();
/*菜单面板*/
MenuPanel=function(config){
config=config||{};
var config_=Ext.applyIf({
layout:'accordion',
region:'west',
split:true,
width:200,
collapsible:true,
border:false,
animate: true
},config);
MenuPanel.superclass.constructor.call(this,config_);
};
Ext.extend(MenuPanel,Ext.Panel,{});
/*菜单模块面板*/
ModulePanel=function(config){
config=config||{};
var nodes_loader=new Ext.tree.TreeLoader({dataUrl:config.dataUrl});
ModulePanel.superclass.constructor.call(this,{
animate: true,
title:config.title,
rootVisible:false,
iconCls:'tab_icon',
region:'west',
split:true,
width:200,
collapsible:true,
root:{
},
loader:nodes_loader
});
};
Ext.extend(ModulePanel,Ext.tree.TreePanel,{
setTargetOpen:function(contentPanel){
this.on("click",function(node){
if(!node.attributes.leaf){
return true;
}
var tabid="module_tab_"+node.attributes.id;
var exist_panel=contentPanel.getComponent(tabid);
if(exist_panel){
contentPanel.setActiveTab(exist_panel);
}else{
var iframe_in_tab="iframe_"+tabid;
var iframe_html="<iframe width=100% height=100% id='"+iframe_in_tab+"'/>"
var panel=new Ext.Panel({
title:node.attributes.text,
id:tabid,
closable:true,
iconCls:'tab_icon_2',
html:iframe_html
});
contentPanel.add(panel);
contentPanel.setActiveTab(panel);
Ext.get(iframe_in_tab).set({
src:node.attributes.url
});
}
});
}
});
/*主面板*/
MainPanel=function(){
MainPanel.superclass.constructor.call(this,{
region:'center',
margins:'0 5 5 0',
resizeTabs: true,
minTabWidth: 135,
tabWidth: 135,
enableTabScroll: true,
activeTab: 0,
items:[{
title:'公告面板',
closable:true,
html:'管理平台欢迎您,如有问题,请联系coreycui,timwen,liubangchen'
}],
tbar:new Ext.Toolbar({
items:[
{xtype:'displayfield',value:'内容页面导航: ',style:'color:RED'},
{xtype:'button',iconCls:'prev_icon',tooltip:'后退',handler:function(){
var activePanel=this.findParentByType("toolbar",false).ownerCt.getActiveTab();
var activePanelId=activePanel.getItemId();
var iframe_in_tab=Ext.get("iframe_"+activePanelId);
Ext.getDom("iframe_"+activePanelId).contentWindow.history.back();
}},
{xtype:'button',iconCls:'next_icon',tooltip:'前进',handler:function(){
var activePanel=this.findParentByType("toolbar",false).ownerCt.getActiveTab();
var activePanelId=activePanel.getItemId();
var iframe_in_tab=Ext.get("iframe_"+activePanelId);
Ext.getDom("iframe_"+activePanelId).contentWindow.history.forward();
}},
{xtype:'button',iconCls:'refresh_icon',tooltip:'刷新',handler:function(){
var activePanel=this.findParentByType("toolbar",false).ownerCt.getActiveTab();
var activePanelId=activePanel.getItemId();
var iframe_in_tab=Ext.get("iframe_"+activePanelId);
Ext.getDom("iframe_"+activePanelId).contentWindow.document.location.reload();
}}
]
})
});
}
Ext.extend(MainPanel,Ext.TabPanel,{});
Ext.onReady(function(){
var contentPanel=new MainPanel();
var menu_panel=new ModulePanel({
title:'功能导航',
dataUrl:'menu_node.jsp'
});
menu_panel.setTargetOpen(contentPanel);
var viewport=new Ext.Viewport({
layout:'border',
items:[
{region:'north',border:false,contentEl:'header',split:true},
menu_panel,
contentPanel
]
});
});
菜单控制基于另外的一个json页面,可方便的接入权限控制,配置如下:
[
{
text:'直通车',children:[
{text:'a1',leaf:true,url:'http://com/g/s?aid=index&g_ut=1',id:'a1'},
{text:'a2',leaf:true,url:'http://com/g/s?aid=index&g_ut=2',id:'a2'},
{text:'a3',leaf:true,url:'http://.com',id:'a3'},
{text:'a4',leaf:true,url:'http://com',id:'a4'},
{text:'a5',leaf:true,url:'http://0',id:'a5'},
{text:'a6',leaf:true,url:'http://a1p',id:'a6'}
]
},
{
text:'XX管理',children:[
{text:'XX查询',leaf:true,url:'<%=request.getContextPath()%>/xxmanage/querycp.jsp',id:'xx_query'},
{text:'XX审核',leaf:true,url:'<%=request.getContextPath()%>/xxmanage/querycp4manage.jsp',id:'xx_manage'}
]
}
]
为了防止内存泄漏和全局变量泛滥,采用继承和组合的方式来组织整个页面的UI结构。继承的模式大致如下:
MenuPanel=function(config){
config=config||{};
var config_=Ext.applyIf({
layout:'accordion',
region:'west',
split:true,
width:200,
collapsible:true,
border:false,
animate: true
},config);
MenuPanel.superclass.constructor.call(this,config_);
};
Ext.extend(MenuPanel,Ext.Panel,{});
MenuPanel.superclass.constructor.call(this,config_);
主要用来在子类的构造函数里面显示的调用父类的构造函数。
var config_=Ext.applyIf();
主要用来指定一些固定的初始化option
2.Grid模块
var columnModel=new Ext.grid.ColumnModel([
{header:'日期',dataIndex:'stat_date'}
/*............*/
]);
var grid=new Ext.grid.GridPanel({
cm:columnModel,
store:store,
renderTo:'grid',
autoHeight:true,
viewConfig:{
forceFit:true
},
loadMask:{
msg:'不要着急,休息一下,休息一下 :)..'
},
bbar:new Ext.PagingToolbar({
pageSize:10,
store:store,
stripeRows:true,
displayInfo:true,
displayMsg:'显示第{0}条到第{1}条,一共{2}条',
emptyMsg:'无记录'
})
});
var form=new Ext.form.FormPanel({
renderTo:'data_setting_div',
labelAlign:'right',
labelWidth:60,
items:[
{
xtype: 'compositefield',
fieldLabel: '日期范围',
defaults:{
width:150
},
items:[
{xtype:'datefield',id:'startdate',name:'startdate'},
{xtype:'displayfield',value:'--',width:10},
{xtype:'datefield',name:'enddate',id:'enddate'},
{xtype:'button',text:'查询',listeners:{
click:function(){
startDate=form.getForm().findField("startdate").getValue().format('Y-m-d')
endDate=form.getForm().findField("enddate").getValue().format('Y-m-d');
store.reload();
}
}}
]
}
]
});
store.load({params:{start:0, limit:10,fromButton:'refresh'}});
强制单元格填充表格:
viewConfig:{
forceFit:true
}
第一次渲染grid的时候必须显示load store,并且需要指定start和limit这两个分页参数,store不会去自动取在grid中配置的。
store.load({params:{start:0, limit:10,fromButton:'refresh'}});
如果需要查询条件:
{xtype:'button',text:'查询',listeners:{
click:function(){
startDate=form.getForm().findField("startdate").getValue().format('Y-m-d')
endDate=form.getForm().findField("enddate").getValue().format('Y-m-d');
store.reload();
}
}}
还必须添加以下事件:
var store=new Ext.data.Store({
proxy:new Ext.data.HttpProxy({
url:gridDataUrl
}),
reader:new Ext.data.JsonReader({
totalProperty: 'totalCount',
root:'list'
},[
{name:'stat_date'}
/*{}*/
]),
listeners:{
beforeload:function(thiz,options){
Ext.apply
(
thiz.lastOptions.params,
{
startDate : startDate,
endDate : endDate
}
);
}
}
});
thiz.lastOptions.params
代表最后提交的参数。
3.扩展Extjs HTMLEditor,图片文件上传并插入。
Extjs HTML Editor是比较坑爹的,自己扩展了一个图片插入的组件。使用方式如下:
new Ext.form.HtmlEditor({
fieldLabel:'正文',
id:'news_details',
name:'news_details',
plugins:new FileInsertPlugin({url:FileUpload.url,MVC_BUS:'FileManager',MVC_ACTION:'upload'})
}),
FileInsertPlugin:
/*coreycui*/
FileInsertPlugin = function(config) {
config=config||{};
var editor;
var win;
/*创建图片*/
var createImage = function() {
var imageWidth = win.getImageWidth();
var imageHeight = win.getImageHeight();
var element = document.createElement("img");
element.src = win.getImageSrc();
element.alt = win.getImageAlt();
if (imageWidth == null || imageWidth == '') {
} else {
element.style.width = imageWidth + "px"
}
if (imageHeight == null || imageHeight == '') {
} else {
element.style.height = imageHeight + "px"
}
return element;
}
// 把图片插入editor中
var insertImageByBrowser = function() {
if (Ext.isIE) {
return function() {
var selection = editor.doc.selection;
var range = selection.createRange();
range.pasteHTML(createImage().outerHTML);
};
} else {
return function() {
var selection = editor.win.getSelection();
if (!selection.isCollapsed) {
selection.deleteFromDocument();
}
selection.getRangeAt(0).insertNode(createImage());
};
}
}();
/*插入图片*/
var insertImage = function() {
editor.win.focus();
insertImageByBrowser();
editor.updateToolbar();
editor.deferFocus();
};
/*图片成功回调*/
var whenImgUploadSuccess=function(result){
insertImage();
win.close();
};
/*图片失败回调*/
var whenImgUploadFailure=function(result){
Msg.MessageBox.alert("图片上传失败",result.msg);
win.close();
}
/*打开图片上传窗口*/
var openImageWindow = function() {
win=new FileUploadWindow(config);
win.setSuccessCallback(whenImgUploadSuccess);
win.setFailureCallback(whenImgUploadFailure);
win.show(this);
}
var onRender=function(){
editor.tb.add({
itemId : 'image',
cls : 'x-btn-icon x-edit-image',
handler : openImageWindow,
tooltip : {
title : '图片',
text : '插入图片',
cls : 'x-html-editor-tip'
}
});
}
return {
init : function(htmlEditor) {
editor = htmlEditor;
editor.on('render',onRender);
}
}
}
插件的形式主要是在init中做文章,宿主会render后init这个插件:
init : function(htmlEditor) {
editor = htmlEditor;
editor.on('render',onRender);
}
主要是将生成的HTML代码插入在编辑器中:
var selection = editor.doc.selection;
var range = selection.createRange();
range.pasteHTML(createImage().outerHTML);
文件上传的窗口如下:
/*文件上传窗口*/
FileUploadWindow=function(config){
config=config||{};
/*成功的回调函数*/
var successCallback;
var failureCallback;
var fileUrl;
var fileUploadForm=new Ext.form.FormPanel({
fileUpload:true,
url:config.url,
labelAlign:'right',
labelWidth:60,
defaults:{
anchor:'-20'
},
items:[
{fieldLabel:'本地文件',xtype:'textfield',name:'file',id:'file',inputType:'file'},
{fieldLabel:'文件描述',xtype:'textfield',name:'filedesc',id:'filedesc',width:190},
{fieldLabel:'高度',xtype:'textfield',name:'fileheight',id:'fileheight',width:190},
{fieldLabel:'宽度',xtype:'textfield',name:'filewidth',id:'filewidth',width:190}
],
buttons:[
{text:'提交',handler:function(){
fileUploadForm.getForm().submit({
params:config,
failure:function(form,action){
if(failureCallback){
failureCallback(action.result);
}
},
success:function(form,action){
fileUrl=action.result.fileUrl;
if(successCallback){
successCallback(action.result);
}
}
});
}}
]
});
Ext.apply(config,{
width:325,
title:'图片插入',
autoHeight:true,
items:[fileUploadForm]
});
FileUploadWindow.superclass.constructor.call(this,config);
/*获取表单*/
this.getForm=function(){
return fileUploadForm;
}
/*获取基础表单*/
this.getBasicForm=function(){
return fileUploadForm.getForm();
}
/*设置成功回调函数*/
this.setSuccessCallback=function(callbackfn){
successCallback=callbackfn;
}
/*设置失败回调函数*/
this.setFailureCallback=function(callbackfn){
failureCallback=callbackfn;
}
this.getImageSrc=function(){
return fileUrl;
}
this.getImageAlt=function(){
return this.getBasicForm().findField('filedesc').getValue();
}
this.getImageWidth=function(){
return this.getBasicForm().findField('filewidth').getValue();
}
this.getImageHeight=function(){
return this.getBasicForm().findField('fileheight').getValue();
}
}
Ext.extend(FileUploadWindow,Ext.Window,{});
事实上就是一个文件上传的扩展窗口,可以在初始化参数中指定文件上传的URL路径,将上传成功和失败的回调钩子的实现留给了调用者。向外提供了获取图片上传后的图片的属性。
4.一个增删改查的窗口抽取。
一个grid配上了一个用于增改的window。将window和grid的控制逻辑抽取出来,将window中的表单,记录新增的URL,记录修改的URL,记录内容获取的URL路径暴露出来。在grid的toolbar中新增如下功能按钮以及相对的数据:
tbar:new Ext.Toolbar({
items:[
{xtype:'button',text:'新增新闻',iconCls:'add',handler:function(thiz,option){
var newsForm=new NewsForm(mvcUrl);
var newsAddWin=new AddOrEditWindow({autoScroll:true,width:800,height:500,title:'新增新闻',mvcUrl:mvcUrl},newsForm)
newsAddWin.show();
newsAddWin.setParams({
MVC_BUS:'News',
MVC_ACTION:'add'
});
newsAddWin.setSuccessCallback(function(){
store.reload();
});
}},
{xtype:'button',text:'编辑新闻',iconCls:'icon_window',handler:function(thiz,option){
var selections=sm.getSelections();
if(selections.length!=1){
Ext.example.msg("警告","请选择一条记录!");
}else{
var id=selections[0].get("id");
var newsForm=new NewsForm(mvcUrl);
var newsAddWin=new AddOrEditWindow({autoScroll:true,width:800,height:500,title:'编辑新闻',mvcUrl:mvcUrl},newsForm)
newsAddWin.show();
newsAddWin.initFormData(config.getDataUrl,{id:id});
newsAddWin.setParams({
MVC_BUS:'News',
MVC_ACTION:'edit'
});
newsAddWin.setSuccessCallback(function(){
store.reload();
});
}
}}
]
})
新增和修改的主要是玩转一个AddOrEditWindow,我们将自己构造的表单作为参数传入该window:
var newsAddWin=new AddOrEditWindow({autoScroll:true,width:800,height:500,title:'编辑新闻',mvcUrl:mvcUrl},newsForm)
当window用来编辑的时候,这个form还必须实现一个接口,obj是从记录获取URL中得到的数据对象,如下:
this.setFields=function(obj){
this.getForm().findField("news_title").setValue(obj.result.summary.news_title);
this.getForm().findField("summary").setValue(obj.result.summary.summary);
this.getForm().findField("status_combo").setValue(obj.result.summary.status);
this.getForm().findField("news_order").setValue(obj.result.summary.news_order);
editor.setSource(obj.result.details.news_details);
if(obj.result.types){
for(var i=0;i<obj.result.types.length;i++){
var typeCheckboxId="news_type_"+obj.result.types[i];
var checkBoxType=Ext.getCmp(typeCheckboxId);
checkBoxType.setValue(true);
}
}
}
调用
newsAddWin.initFormData(config.getDataUrl,{id:id});
系统会从第一个参数URL中用第二个param map去远程服务器获取一个obj数据对象。调用form的setFields进行填充,并且将id属性关联在该修改表单上。
AddOrEditWindow的实现如下:
AddOrEditWindow = function(config, form) {
config = config || {};
var thiz = this;
var params = {};
var successCallback;
var failureCallback;
var formPanel = form;
var sumitForm = function() {
formPanel.getForm().submit({
params : params,
success : function(form, action) {
Ext.example.msg("提示", "操作成功");
if (successCallback) {
successCallback();
}
thiz.close();
},
failure : function(form, action) {
Ext.example.msg("提示", "操作失败");
if (failureCallback) {
failureCallback();
}
thiz.close();
}
});
}
Ext.apply(config, {
iconCls : 'icon_window',
items : [ formPanel ],
buttons : [ {
xtype : 'button',
text : '提交',
iconCls : 'save',
handler : function() {
sumitForm();
}
}, {
xtype : 'button',
text : '关闭',
iconCls : 'delete',
handler : function() {
thiz.close();
}
} ]
});
AddOrEditWindow.superclass.constructor.call(this, config);
this.setParams = function(ps) {
Ext.apply(params, ps);
}
this.initFormData = function(url, ps) {
Ext.Ajax.request({
url : url,
params : ps,
success : function(resp, options) {
var respText = Ext.util.JSON.decode(resp.responseText);
params.id = respText.result.id;
formPanel.setFields(respText);
},
failure : function(resp, options) {
thiz.close();
}
});
}
this.setSuccessCallback = function(fn) {
successCallback = fn;
}
this.setFailureCallback = function(fn) {
failureCallback = fn;
}
};
Ext.extend(AddOrEditWindow, Ext.Window, {});
5.一些小技巧
给表格新增一个操作的列,如删除等等之类的。
{xtype:'actioncolumn',width:70,items:[{
icon: '../ext/selfimg/picture_delete.png',
tooltip:'删除封面',
handler:function(grid, rowIndex, colIndex){
var rec = store.getAt(rowIndex);
var id=rec.get("id");
Ext.MessageBox.confirm("删除确认","确认是否删除?",function(btn){
}
});
}
}]}
type为:
actioncolumn
6.与Jquery成为一对好基友
有的时候,需要和jquery一起使用,只需引入jquery的适配器。
<script type="text/javascript" src="../ext/jquery-1.4.2.min.js"></script>
<script type="text/javascript" src="../ext/ext-jquery-adapter.js"></script>
<script type="text/javascript" src="../ext/xheditor-1.1.9-zh-cn.min.js"></script>
<script type="text/javascript" src="../ext/ext-all.js"></script>
<script type="text/javascript" src="../ext/ext-lang-zh_CN.js"></script>
如使用jquery的插件XKEditor编辑器。
{xtype:'textarea',fieldLabel:'正文',id:'news_details_proxy',name:'news_details_proxy',height:400,anchor: '-20'}
editor=$('#news_details_proxy').xheditor({html5Upload:false,upLinkUrl:fileUploadUrl,upLinkExt:"zip,rar,txt",upImgUrl:fileUploadUrl,upImgExt:"jpg,jpeg,gif,png",upFlashUrl:fileUploadUrl,upFlashExt:"swf",upMediaUrl:fileUploadUrl,upMediaExt:"avi"});
发现总是无法成功,textarea还是老样子。
原来是因为在表单render动态的新增了组件,嗲用了doLayout,
var renderNewsTypes=function(){
Ext.Ajax.request({
url : '<%=request.getContextPath()%>/mvc?MVC_BUS=NewsTypes&MVC_ACTION=list',
success : function(resp, options) {
var respText = Ext.util.JSON.decode(resp.responseText);
var types=respText.list;
typesInited=true;
for(var i=0;i<types.length;i++){
typesCheckBoxes.push({xtype:'checkbox',boxLabel:types[i].type_name,inputValue:types[i].id,name:'news_type_'+types[i].id,id:'news_type_'+types[i].id});
}
thiz.get(0).insert(1,{fieldLabel:'新闻类型',xtype:"checkboxgroup",columns: 4,items:typesCheckBoxes,name:'news_type_group',id:'news_type_group'});
thiz.get(0).remove(thiz.get(0).findById("news_types_proxy"));
thiz.doLayout();
},failure:function(){
Ext.MessageBox.alert("警告","新闻内容拉取失败");
}
});
}
所以必须在每次layout后,重新将编辑器渲染一下:
thiz.doLayout();
editor=$('#news_details_proxy').xheditor({html5Upload:false,upLinkUrl:fileUploadUrl,upLinkExt:"zip,rar,txt",upImgUrl:fileUploadUrl,upImgExt:"jpg,jpeg,gif,png",upFlashUrl:fileUploadUrl,upFlashExt:"swf",upMediaUrl:fileUploadUrl,upMediaExt:"avi"});
7.一些不解
当文件AJAX上传的时候,服务器返回数据必须显示指定;
response.setContentType()"text/html";否则判断为失败。