您的位置:首页 > 产品设计 > UI/UE

FineUI大版本升级,外置ExtJS库、去AXD化、表格合计行、表格可编辑单元格的增删改、顶部菜单框架

2013-06-17 08:17 393 查看
FineUIv3.3.0更新的内容非常多,所以一下子从v3.2.6连跳3个小版本,直接来到了v3.3.0。详细的更新记录请参考这里:http://fineui.com/version

主要的更新有如下几个方面:

外置ExtJS库

去AXD化

表格合计行

表格可编辑单元格的增删改

顶部菜单框架

下面就来详细说明这些更新。

1.外置ExtJS库

FineUI最初使用的是GPLv2授权协议,不过这和FineUI所倡导的开源免费的原则相抵触,因为如果某个企业使用了FineUI库,即使已经购买了ExtJS的商业授权,还是需要公开源代码的,因为受到FineUI的GPLv2协议限制。基于这个原因,FineUI从v3.1.0开始拥抱ApacheLicense2.0,从而真正做到了免费开源!

上面这个转变过程,我曾经写过一篇博客记录:

不仅开源,而且对企业应用完全免费!ExtAspNet弃用GPLv2,拥抱ApacheLicense2.0

然而,在详细阅读了ExtJS的授权协议后,我发现FineUI并没有完全遵守ExtJS所指定的规则,先来看看ExtJS的对开源工具的制定的规则:



ExtJSOpenSourceLicense
Senchaisanavidsupporterofopensourcesoftware.OuropensourcelicenseistheappropriateoptionifyouarecreatinganopensourceapplicationunderalicensecompatiblewiththeGNUGPLlicensev3.AlthoughtheGPLv3hasmanyterms,themostimportantisthatyoumustprovidethesourcecodeofyourapplicationtoyouruserssotheycanbefreetomodifyyourapplicationfortheirownneeds.

IfyouwouldliketousetheGPLv3versionofExtJSwithyournon-GPLv3opensourceproject,thefollowingFLOSS(Free,LibreandOpenSource)exceptionsareavailable:
OpenSourceLicenseExceptionforDevelopment



虽然FineUI使用的ApacheLicense2.0是和GPLv3兼容的协议,不过ExtJS还制定了更加严格的规则:不能包含ExtJS的源代码,而是要告诉用户怎么获取ExtJS的源代码!

FineUI作为知名的开源软件,会无条件遵守开源社区的游戏规则,因此在本次v3.3.0中做出重大调整:

FineUI的ApacheLicensev2.0授权协议与ExtJS的GPLv3兼容;

FineUI公开全部源代码,没有任何保留;

FineUI不包含ExtJS的任何源代码;

FineUI不将ExtJS作为整体发布,而是提供获取ExtJS的方法;

FineUI公开说明使用了ExtJS库,并指出ExtJS库是采用GPLv3授权协议的;

FineUI是为了将ExtJS引入ASP.NET领域,而非独立存在的库。

如果获取适用于FineUI的ExtJS库呢?

首先下载ExtJS库:http://www.sencha.com/products/extjs3/download/

将ExtJS库的全部内容拷贝到官方示例目录:FineUI.Examples\extjs_builder\extjs_source_all

运行build.bat,即可生成目录:FineUI.Examples\extjs

将生成的extjs拷贝到你网站的根目录下即可(老项目不需要修改任何代码和配置文件!)。

注:由于ExtJS库比较大(20M),我们在官方论坛提供了生成好的extjs目录方便大家使用:http://fineui.com/bbs/forum.php?mod=viewthread&tid=3218

2.去AXD化

外置ExtJS库带来了另一个好处,再也不用使用散落在网站各处的res.axd路径了(为了保证老项目的正常运行,之前res.axd的方式仍然有效)!

AXD是ASP.NET内置的一种获取程序集内部资源的方式,但是在实际部署中会出现各种问题,在官方论坛AXD+404的总结帖子就有好几个:





其中最典型的错误是在IIS中没有设置正确的AXD扩展:





更离谱的是,错误的服务器时间也会导致AXD出现404错误,具体原因不明:

http://fineui.com/bbs/forum.php?mod=viewthread&tid=1271

/article/5010014.html

从FineUIv3.3.0开始,只要你不手工调用res.axd路径,就再也不会出现上述问题了。

3.表格合计行

论坛用户对表格合计行的呼声特别高,实际项目中可能需要对当前分页数据合计,也可能对全部数据合计。

这次更新,我们特别制作了几个示例,由于需要手写CSS和JavaScript,所以对程序员的要求比较高,不过没关系大家只需照例子写就行了。

3.1服务器全部合计





实现上述效果,需要分三步走:

1.在后台代码中生成合计数据

protectedvoidPage_Load(objectsender,EventArgse){

[code]if(!IsPostBack){
BindGrid();


OutputSummaryData();

}

}



privatevoidOutputSummaryData(){

DataTablesource=GetDataTable2();


floatdonateTotal=0.0f;

floatfeeTotal=0.0f;

foreach(DataRowrowinsource.Rows){

donateTotal+=Convert.ToInt32(row["Donate"]);

feeTotal+=Convert.ToInt32(row["Fee"]);

}


JObjectjo=newJObject();

jo.Add("donateTotal",donateTotal);

jo.Add("feeTotal",feeTotal);


hfGrid1Summary.Text=jo.ToString(Newtonsoft.Json.Formatting.None);


}

[/code]

由于合计数据在不改变数据源的情况下是不变的,因此我们只要在第一次页面加载(!IsPostBack)时生成合计数据即可。

然后将全部合计数据以JSON字符串的形式保存到隐藏字段(HiddenField)中,供前台JavaScript代码调用。





2.使用前台代码显示合计数据

<script>

[code]vargridClientID='<%=Grid1.ClientID%>';
vargridSummaryID='<%=hfGrid1Summary.ClientID%>';


functioncalcGridSummary(grid){

vardonateTotal=0,

store=grid.getStore(),

view=grid.getView(),

storeCount=store.getCount();


//防止重复添加了合计行

if(Ext.get(view.getRow(storeCount-1)).hasClass('mygrid-row-summary')){

return;

}


//从隐藏字段获取全部数据的汇总

varsummaryJSON=JSON.parse(X(gridSummaryID).getValue());



store.add(newExt.data.Record({

'major':'全部合计:',

'donate':summaryJSON['donateTotal'].toFixed(2),

'fee':summaryJSON['feeTotal'].toFixed(2)

}));




//为合计行添加自定义样式(隐藏序号列、复选框列,取消hover和selected效果)

Ext.get(view.getRow(storeCount)).addClass('mygrid-row-summary');


}


//页面第一个加载完毕后执行的函数


functiononReady(){

vargrid=X(gridClientID);

grid.addListener('viewready',function(){

calcGridSummary(grid);

});


}


//页面AJAX回发后执行的函数


functiononAjaxReady(){

vargrid=X(gridClientID);

calcGridSummary(grid);

}

</script>

[/code]

上面代码首先定义了一个向表格中添加合计行的函数(calcGridSummary),并分别在页面第一次加载时(onReady)和AJAX结束时(onAjaxReady)调用此函数。

在calcGridSummary函数内部,通过JSON.parse函数解析保存在隐藏字段中的合计数据,然后调用表格的grid.getStore().add来添加合计行,最后给这个合计行添加CSS样式(mygrid-row-summary)。

上面的代码不大完善,新增加的合计行属于表格数据的一部分,因此用户可以选中这个合计行,这是我们所不希望的,怎么办?


functiononReady(){

[code]vargrid=X(gridClientID);
grid.addListener('viewready',function(){

calcGridSummary(grid);

});


//防止选中合计行

grid.getSelectionModel().addListener('beforerowselect',function(sm,rowIndex,keepExisting,record){

if(Ext.get(grid.getView().getRow(rowIndex)).hasClass('mygrid-row-summary')){

returnfalse;

}

returntrue;

});

}

[/code]
我们还需要在页面初始化时,加入防止合计行被选中的事件处理,其中用到了刚刚添加到合计行的CSS定义(mygrid-row-summary)。

3.使用CSS调整合计行样式

最后,我们还需要通过CSS来简单调整合计行的样式:

<style>

[code].mygrid-row-summary.x-grid3-row{
background-color:#efefef!important;

background-image:none!important;

border-color:#fff#ededed#ededed!important;

}

.mygrid-row-summary.x-grid3-row.x-grid3-td-numberer,.mygrid-row-summary.x-grid3-row.x-grid3-td-checker{

background-image:none!important;

}

.mygrid-row-summary.x-grid3-row.x-grid3-td-numberer.x-grid3-col-numberer,.mygrid-row-summary.x-grid3-row.x-grid3-td-checker.x-grid3-col-checker{

display:none;

}

.mygrid-row-summary.x-grid3-rowtd{

font-size:14px;

line-height:16px;

font-weight:bold;

color:red;

}

</style>

[/code]

3.2服务器分页合计

服务器分页合计和服务器全部合计的前台代码完全相同,所不同的时分页合计时每次表格数据绑定都需要计算本页的合计数据,如下所示:


privatevoidOutputPageSummaryData(DataTablesource){

[code]floatdonateTotal=0.0f;
floatfeeTotal=0.0f;

foreach(DataRowrowinsource.Rows){

donateTotal+=Convert.ToInt32(row["Donate"]);

feeTotal+=Convert.ToInt32(row["Fee"]);

}


JObjectjo=newJObject();

jo.Add("donateTotal",donateTotal);

jo.Add("feeTotal",feeTotal);


hfGrid1Summary.Text=jo.ToString(Newtonsoft.Json.Formatting.None);


}


privatevoidBindGrid(){

//1.设置总项数(特别注意:数据库分页一定要设置总记录数RecordCount)

Grid1.RecordCount=GetTotalCount();


//2.获取当前分页数据

DataTabletable=GetPagedDataTable(Grid1.PageIndex,Grid1.PageSize);


//3.绑定到Grid

Grid1.DataSource=table;

Grid1.DataBind();


//输出分页合计结果

OutputPageSummaryData(table);

}

[/code]

页面效果如下:





3.3服务器全部合计(绝对定位合计行)

实际项目的一个常见需求是将合计行绝对定位到表格底部,如下图所示:





该如何实现这个功能?

这个时候我们只好在前台下工夫了,总的思路如下:

1.和服务器全部合计一模一样的前台代码;

2.将生成的合计行拷贝一份,然后将拷贝的合计行插入表格容器节点中并绝对定位。

一定要注意:在这个过程中,是要拷贝一个合计行(而不是删除合计行),这样才不至于在滚动条滚动时把最后一行表格数据遮挡住。此时页面上其实是有两个一模一样的合计行,只不过所在位置不同,并且原始的合计行要设置CSS属性visibility:hidden;(让原始的合计行占位,但不显示出来,这个主意是不是很妙

)。

看看下图就明白了:





关键JavaScript代码:


functioncalcGridSummary(grid){

[code]vardonateTotal=0,
store=grid.getStore(),

view=grid.getView(),

storeCount=store.getCount();


//防止重复添加了合计行

if(Ext.get(view.getRow(storeCount-1)).hasClass('mygrid-row-summary')){

return;

}


//从隐藏字段获取全部数据的汇总

varsummaryJSON=JSON.parse(X(gridSummaryID).getValue());



store.add(newExt.data.Record({

'major':'全部合计:',

'donate':summaryJSON['donateTotal'].toFixed(2),

'fee':summaryJSON['feeTotal'].toFixed(2)

}));



//为合计行添加自定义样式(隐藏序号列、复选框列,取消hover和selected效果)

varsummaryNode=Ext.get(view.getRow(storeCount)).addClass('mygrid-row-summary');


//找到合计行的外部容器节点

varviewportNode=summaryNode.parent('.x-grid3-viewport');

//删除容器节点下直接子节点为mygrid-row-summary的节点

viewportNode.select('>.mygrid-row-summary').remove();


//创建合计行的副本

varcloneSummaryNode=summaryNode.dom.cloneNode(true);

//修改合计行的副本的样式,绝对定位,距离底部0px,显示副本(默认是占位隐藏visibility:hidden;)

Ext.get(cloneSummaryNode).setStyle({

position:'absolute',

bottom:0,

visibility:'visible'

});


//向容器节点添加合计行的副本

viewportNode.appendChild(cloneSummaryNode);


}

[/code]

更加详细的代码,请直接去看官方示例:http://fineui.com/demo/#/demo/grid/grid_summary_absolute.aspx

4.表格可编辑单元格的增删改

论坛用户对ExtJS可编辑功能的呼声也很高,虽然FineUI的模板列能够实现一定的编辑功能(http://fineui.com/demo/#/demo/grid/grid_edit.aspx),但毕竟不是ExtJS的原生方式。

上个版本简单实现了可编辑表格的“改”,这个版本对此进行了修正和改进,下面就来一一描述。

4.1可编辑表格的“改”





首先来看下ASPX文件的结构定义:


<x:GridID="Grid1"ShowBorder="true"ShowHeader="true"Title="表格"Width="850px"Height="350px"

[code]runat="server"DataKeyNames="Id,Name"AllowCellEditing="true"ClicksToEdit="1">
<Columns>

<x:TemplateFieldWidth="60px">

<ItemTemplate>

<asp:LabelID="Label1"runat="server"Text='<%#Container.DataItemIndex+1%>'></asp:Label>

</ItemTemplate>

</x:TemplateField>

<x:RenderFieldWidth="100px"ColumnID="Name"DataField="Name"FieldType="String"

HeaderText="姓名">

<Editor>

<x:TextBoxID="tbxEditorName"Required="true"runat="server">

</x:TextBox>

</Editor>

</x:RenderField>

<x:RenderFieldWidth="100px"ColumnID="Gender"DataField="Gender"FieldType="Int"

RendererFunction="renderGender"HeaderText="性别">

<Editor>

<x:DropDownListID="ddlGender"Required="true"runat="server">

<x:ListItemText="男"Value="1"/>

<x:ListItemText="女"Value="0"/>

</x:DropDownList>

</Editor>

</x:RenderField>

<x:RenderFieldWidth="100px"ColumnID="EntranceYear"DataField="EntranceYear"FieldType="Int"

HeaderText="入学年份">

<Editor>

<x:NumberBoxID="tbxEditorEntranceYear"NoDecimal="true"NoNegative="true"MinValue="2000"

MaxValue="2010"runat="server">

</x:NumberBox>

</Editor>

</x:RenderField>

<x:RenderFieldWidth="100px"ColumnID="EntranceDate"DataField="EntranceDate"FieldType="Date"

Renderer="Date"RendererArgument="yyyy-MM-dd"HeaderText="入学日期">

<Editor>

<x:DatePickerID="DatePicker1"Required="true"runat="server">

</x:DatePicker>

</Editor>

</x:RenderField>

<x:RenderCheckFieldWidth="100px"ColumnID="AtSchool"DataField="AtSchool"HeaderText="是否在校"/>

<x:RenderFieldWidth="100px"ColumnID="Major"DataField="Major"FieldType="String"

ExpandUnusedSpace="true"HeaderText="所学专业">

<Editor>

<x:TextBoxID="tbxEditorMajor"Required="true"runat="server">

</x:TextBox>

</Editor>

</x:RenderField>

</Columns>

</x:Grid>

[/code]
RenderField是专门用于可编辑表格的,我们可以在RenderField内部定义Editor,一个Editor也就是一个表单字段。

常用做Editor有TextBox、NumberBox、DropDownList、DatePicker等。

还有一个特殊的列类型是RenderCheckField,专门用来生成可编辑的复选框,要特别注意RenderCheckField和CheckBoxField的区别。

为什么用于可编辑表格的列类型都是Render开头的呢?

其实这里的Render可以理解为客户端渲染,服务器端会把数据准备好,而不会在服务器端生成每个单元格的HTML(这一点可以和之前的列类型做对比),而是在客户端根据服务器端提供的原始数据渲染成所需要的HTML。

比如这个例子中的Gender列定义了RendererFunction="renderGender",这里的renderGender就是一个JavaScript函数:

<script>

[code]functionrenderGender(value,metadata,record,rowIndex,colIndex){
returnvalue==1?'男':'女';

}

</script>

[/code]
这里返回的“男”或者“女”就是本列处于非编辑状态下显示的内容,当然我们可以用两个图标分别代表,比如用下面这个函数来替代上面的函数:

<script>

[code]functionrenderGender(value,metadata,record,rowIndex,colIndex){
returnvalue==1?'<imgsrc="../extjs/res/images/boy.png"/>':'<imgsrc="../extjs/res/images/girl.png"/>';

}

</script>

[/code]

另一个需要注意的地方,我们为每一列都定义了ColumnID,这一点很重要。在后台代码中获取用户修改后的数据时,需要用到这个属性。

下面来看下后台如何获取用户的修改值,并保存到持久化设备。

作为示例,我们没有使用数据库,而是在内存中模拟了持久化存储(当然不是真的持久化,也不要在实际项目中这样用):


privatestaticreadonlystringKEY_FOR_DATASOURCE_SESSION="datatable_for_grid_editor_cell";

[code]
//模拟在服务器端保存数据

//特别注意:在真实的开发环境中,不要在Session放置大量数据,否则会严重影响服务器性能

privateDataTableGetSourceData(){

if(Session[KEY_FOR_DATASOURCE_SESSION]==null){

Session[KEY_FOR_DATASOURCE_SESSION]=GetDataTable();

}

return(DataTable)Session[KEY_FOR_DATASOURCE_SESSION];

}

[/code]

在用户点击“保存数据”按钮时,后台处理代码:


protectedvoidButton2_Click(objectsender,EventArgse){

[code]Dictionary<int,Dictionary<string,string>>modifiedDict=Grid1.GetModifiedDict();

for(inti=0,count=Grid1.Rows.Count;i<count;i++){

if(modifiedDict.ContainsKey(i)){

Dictionary<string,string>rowDict=modifiedDict[i];


//更新数据源

DataTabletable=GetSourceData();


DataRowrowData=table.Rows[i];


//姓名

if(rowDict.ContainsKey("Name")){

rowData["Name"]=rowDict["Name"];

}

//性别

if(rowDict.ContainsKey("Gender")){

rowData["Gender"]=Convert.ToInt32(rowDict["Gender"]);

}

//入学年份

if(rowDict.ContainsKey("EntranceYear")){

rowData["EntranceYear"]=rowDict["EntranceYear"];

}

//入学日期

if(rowDict.ContainsKey("EntranceDate")){

rowData["EntranceDate"]=DateTime.Parse(rowDict["EntranceDate"]).ToString("yyyy-MM-dd");

}

//是否在校

if(rowDict.ContainsKey("AtSchool")){

rowData["AtSchool"]=Convert.ToBoolean(rowDict["AtSchool"]);

}

//所学专业

if(rowDict.ContainsKey("Major")){

rowData["Major"]=rowDict["Major"];

}


}

}


labResult.Text="用户修改的数据:"+Grid1.GetModifiedData().ToString(Newtonsoft.Json.Formatting.None);


BindGrid();


Alert.Show("数据保存成功!(表格数据已重新绑定)");

}

[/code]

这里的GetModifiedDict函数返回用户在客户端所有的修改数据,它的数据类型是Dictionary<int,Dictionary<string,string>>,第一个int表示行索引,第一个string表示列标识(ColumnID),第二个string表示用户在客户端修改的值。

理解了这一点,上面的代码就清晰明了了:

1.首先遍历表格的所有数据行

2.查看当前数据行是否在客户端修改了?

3.如果修改了,则查找本行数据中哪些列在客户端修改了,并更新数据源。

是不是对GetModifiedData函数感兴趣?

这个函数是服务器接收到的客户端回发的原始数据,是用JSON表示的,来看这个例子的结果:

[

[code][2,{
"Name":"董婷婷2",

"Gender":"1",

"EntranceYear":2009,

"AtSchool":false,

"EntranceDate":"2008-09-02T00:00:00"

}],
[4,{

"EntranceDate":"2008-09-09T00:00:00",

"EntranceYear":2000

}]

]

[/code]

4.2可编辑表格的“删”





由于只能选中一个单元格,而不是一行数据,所以我们可以通过选中某行单元格来删除本行数据。


protectedvoidbtnDelete_Click(objectsender,EventArgse)

[code]{
StringBuildersb=newStringBuilder();

if(Grid1.SelectedCell!=null){

introwIndex=Grid1.SelectedCell[0];


GetSourceData().Rows.RemoveAt(rowIndex);


BindGrid();


Alert.ShowInTop("删除数据成功!(表格数据已重新绑定)");

}else{

Alert.ShowInTop("没有选中任何单元格!");

}


}

[/code]

这个过程比较简单,首先获取用户选中的单元格(SelectedCell),这个数组的第一个元素就是行索引,接下来从数据源中删除本行数据,并重新绑定表格即可。

4.3可编辑表格的“增”





首先来看下如何为“新增数据”按钮绑定客户端脚本:


protectedvoidPage_Load(objectsender,EventArgse)

[code]{
if(!IsPostBack){

JObjectdefaultObj=newJObject();

defaultObj.Add("Name","用户名");

defaultObj.Add("Gender",1);

defaultObj.Add("EntranceYear","2015");

defaultObj.Add("EntranceDate","2015-09-01");

defaultObj.Add("AtSchool",false);

defaultObj.Add("Major","化学系");


//第一行新增一条数据

btnNew.OnClientClick=Grid1.GetAddNewRecordReference(defaultObj,false);


btnReset.OnClientClick=Grid1.GetRejectChangesReference();


BindGrid();

}

}

[/code]

GetAddNewRecordReference函数接受的第一个参数类型是JObject,用来指定新增数据每一列的默认值,第二个参数指定是否将新增行添加到当前数据的末尾。

如果看下页面源代码,可以发现生成的JavaScript如下所示:

X('Grid1').x_addNewRecord({

[code]"Name":"用户名",
"Gender":1,

"EntranceYear":"2015",

"EntranceDate":"2015-09-01",

"AtSchool":false,

"Major":"化学系"

},false);

[/code]

再来看看保存数据的代码,由于有两部分数据需要保存,一部分是新增的,另一部分是修改现有的数据,所以提取了一个共有函数:


privatestaticvoidUpdateSourceDataRow(Dictionary<string,string>rowDict,DataRowrowData){

[code]//姓名
if(rowDict.ContainsKey("Name")){

rowData["Name"]=rowDict["Name"];

}

//性别

if(rowDict.ContainsKey("Gender")){

rowData["Gender"]=Convert.ToInt32(rowDict["Gender"]);

}

//入学年份

if(rowDict.ContainsKey("EntranceYear")){

rowData["EntranceYear"]=rowDict["EntranceYear"];

}

//入学日期

if(rowDict.ContainsKey("EntranceDate")){

rowData["EntranceDate"]=DateTime.Parse(rowDict["EntranceDate"]).ToString("yyyy-MM-dd");

}

//是否在校

if(rowDict.ContainsKey("AtSchool")){

rowData["AtSchool"]=Convert.ToBoolean(rowDict["AtSchool"]);

}

//所学专业

if(rowDict.ContainsKey("Major")){

rowData["Major"]=rowDict["Major"];

}

}

[/code]

保存数据的代码则清晰明了:


protectedvoidButton2_Click(objectsender,EventArgse){

[code]
//1.先修改的现有数据

Dictionary<int,Dictionary<string,string>>modifiedDict=Grid1.GetModifiedDict();

for(inti=0,count=Grid1.Rows.Count;i<count;i++){

if(modifiedDict.ContainsKey(i)){

Dictionary<string,string>rowDict=modifiedDict[i];


//更新数据源

DataTabletable=GetSourceData();


DataRowrowData=table.Rows[i];


UpdateSourceDataRow(rowDict,rowData);


}

}



//2.再新增数据

List<Dictionary<string,string>>newAddedList=Grid1.GetNewAddedList();

for(inti=newAddedList.Count-1;i>=0;i--){

DataTabletable=GetSourceData();


DataRowrowData=table.NewRow();


UpdateSourceDataRow(newAddedList[i],rowData);


table.Rows.InsertAt(rowData,0);

}



labResult.Text="用户修改的数据:"+Grid1.GetModifiedData().ToString(Newtonsoft.Json.Formatting.None);


BindGrid();


Alert.Show("数据保存成功!(表格数据已重新绑定)");

}

[/code]

我们可以看到,修改现有数据的代码和之前的一模一样,都是先使用GetModifiedDict获取用户在客户端修改的值。

保存新增数据行的代码更加简单:

1.使用GetNewAddedList方法返回新增的数据行列表;

2.遍历每一行,将新增数据添加到数据源中。

需要注意:

1.一定要修改现有数据,然后再处理新增数据

2.处理完后一定要重新绑定数据,因为此时前段显示和后端的数据已经不一致了。

5.顶部菜单框架

这个需求也是来源于论坛用户。官网示例给出的是左侧菜单结构的框架,那么如何实现顶部菜单结构的框架呢?





如图所示,点击顶部菜单来更新左侧树结构,实现起来倒不难,不过需要一点JavaScript知识。

首先来看下顶部菜单的定义:

<ul>

[code]<liclass="selectedmenu-mail">
<asp:LinkButtonID="lbtnMail"runat="server"OnClick="lbtnMail_Click">

<span>邮件收发</span></asp:LinkButton>

</li>

<liclass="menu-sms">

<asp:LinkButtonID="lbtnSMS"runat="server"OnClick="lbtnSMS_Click">

<span>短信收发</span></asp:LinkButton>

</li>

<liclass="menu-sys">

<asp:LinkButtonID="lbtnSYS"runat="server"OnClick="lbtnSYS_Click">

<span>系统管理</span></asp:LinkButton>

</li>

</ul>

[/code]

后台代码中,分别处理三个顶部菜单的点击事件,更新左侧树控件即可:


privatevoidBindLeftTree(stringmenuType){

[code]if(menuType=="mail"){
XmlDataSource1.DataFile="./data/menuMail.xml";

PageContext.RegisterStartupScript("selectMenu('menu-mail');");

}elseif(menuType=="sys"){

XmlDataSource1.DataFile="./data/menuSYS.xml";

PageContext.RegisterStartupScript("selectMenu('menu-sys');");

}elseif(menuType=="sms"){

XmlDataSource1.DataFile="./data/menusms.xml";

PageContext.RegisterStartupScript("selectMenu('menu-sms');");

}


BindLeftTree();

}


privatevoidBindLeftTree(){

leftTree.DataSource=XmlDataSource1;

leftTree.DataBind();

}


protectedvoidlbtnMail_Click(objectsender,EventArgse){

BindLeftTree("mail");

}

protectedvoidlbtnSYS_Click(objectsender,EventArgse){

BindLeftTree("sys");


}

protectedvoidlbtnSMS_Click(objectsender,EventArgse){

BindLeftTree("sms");

}

[/code]

但是不要忘了在切换顶部菜单时,更新选中菜单的样式,同时要选中树控件的第一个节点,并在主区域内加载此节点所指向的页面:

<script>

[code]varleftTreeID='<%=leftTree.ClientID%>';

functionselectMenu(menuClassName){

//选中当前菜单

Ext.select('.menuulli').removeClass('selected');

Ext.select('.menuulli.'+menuClassName).addClass('selected');


//展开树的第一个节点,并选中第一个节点下的第一个子节点(在右侧IFrame中打开)

vartree=X(leftTreeID);

vartreeFirstChild=tree.getRootNode().firstChild;

//展开第一个节点(如果想要展开全部节点,调用tree.expandAll();)

treeFirstChild.expand();



//选中第一个链接节点,并在右侧IFrame中打开此链接

vartreeFirstLink=treeFirstChild.firstChild;

treeFirstLink.select();

window.frames['mainframe'].location.href=treeFirstLink.attributes['href'];


}


functiononReady(){

selectMenu('menu-mail');

}

</script>

[/code]

虽然写了一点JavaScript代码,但最终还是实现了我们需要的结果。

不过想要实现如下界面,就不那么容易了:





你可能会想,不就是把树控件换成手风琴控件么,不和上例一样的么?

如果你真的这么想,那你就需要先了解下ASP.NET下动态创建控件的游戏了,先看这篇文章:/article/4676751.html

原因是树控件是一个控件,可以通过更新数据源来重新加载;而手风琴控件是由多个控件组合而成的,无法在页面回发时重新创建另一个手风琴控件!

怎么办呢?

办法总会有的,我们可以把左侧的区域也做成IFrame,这样每次点击顶部菜单时,就重新加载左侧IFrame(动态创建手风琴控件)就行了(是不是很妙

)!

这里只提供一个思路,具体的例子请查看:http://fineui.com/demo/#/demo/iframe/topmenu3/default.aspx

下载FineUIv3.3.0和官方示例

下载地址:http://fineui.codeplex.com/releases/

FineUI严格遵守ExtJS关于开源软件的规则,不再内置ExtJS库。

获取适用于FineUI的ExtJS库:http://fineui.com/bbs/forum.php?mod=viewthread&tid=3218

基于FineUI的空项目(Net2.0和Net4.0两个版本):http://fineui.com/bbs/forum.php?mod=viewthread&tid=2123

如果你喜欢FineUI,别忘了点击页面右下角的“推荐”按钮哦!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
章节导航