zoukankan      html  css  js  c++  java
  • 【番外篇】ASP.NET MVC快速入门之免费jQuery控件库(MVC5+EF6)

    目录

    【第一篇】ASP.NET MVC快速入门之数据库操作(MVC5+EF6)

    【第二篇】ASP.NET MVC快速入门之数据注解(MVC5+EF6)

    【第三篇】ASP.NET MVC快速入门之安全策略(MVC5+EF6)

    【第四篇】ASP.NET MVC快速入门之完整示例(MVC5+EF6)

    【番外篇】ASP.NET MVC快速入门之免费jQuery控件库(MVC5+EF6)

    FineUIMvc简介

    FineUIMvc 是基于 jQuery 的专业 ASP.NET MVC 控件库,其前身是基于 WebForms 的开源控件库 FineUI(历时9120多个版本)。FineUIMvc(基础版)包含开源版的全部功能,支持 30 种内置主题和 FontAwesome 图标,支持消息对话框和单元格编辑表格,功能强大,最重要的是完全免费

     

    以下引自FineUI官网的介绍:

    FineUIMvc(基础版)作为三石奉献给社区的一个礼物,功能绝对强大到让你心动:

    1.       拥有 FineUI(开源版)的全部功能。

    2.       拥有 FineUI(专业版)相同的 jQuery 前端库,体积小,速度快。

    3.       拥有 FineUIMvc(企业版)强大的通知对话框和单元格编辑表格。

    4.       内置 30 Metro jQueryUI 主题(以及自定义主题Bootstrap Pure)。

    5.       内置 500 多个 FontAwesome 图标字体,控件原生支持。

    6.       9 年技术积累,1300 多位捐赠会员,100 多家企业客户,稳定可信赖。

    7.       重要的事情说三遍:完全免费!完全免费!完全免费!

     

    官网首页:http://fineui.com/mvc/
    在线示例:http://fineui.com/demo_mvc/

     

    这篇文章,我们将使用FineUIMvc(基础版)来实现本示例。

     

    FineUIMvc空项目

    登录FineUI的论坛,下载FineUIMvc空项目,这样不仅省去了配置的麻烦,而且还有默认的左右框架的实现:

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

     

    双击FineUIMvc.EmptyProject.sln,打开项目:

     

    这个目录结构和之前的很类似,多了一个res目录,这个是FineUIMvc中的约定,用来放置静态资源,比如CSSJS、图片以及一套Icon图标。

     

    Ctrl+F5直接运行项目:

    修改Web.config

    空项目已经配置好了Web.config文件,主要是两个地方的改动:

    <configSections>
    
           <section name="FineUIMvc" type="FineUIMvc.ConfigSection, FineUIMvc"
    
    requirePermission="false" />
    
    </configSections>
    
    <FineUIMvc DebugMode="true" Theme="Cupertino" />

     

    这里设置FineUIMvc的全局参数:

    l  Theme: 样式主题,内置 30 种主题(其中 6 Metro 主题,24 jQueryUI 官方主题,默认值:Default

    n  Metro 主题:Default, Metro_Blue, Metro_Dark_Blue, Metro_Gray, Metro_Green, Metro_Orange

    n  jQueryUI 主题: Black_Tie, Blitzer, Cupertino, Dark_Hive, Dot_Luv, Eggplant, Excite_Bike, Flick, Hot_Sneaks, Humanity, Le_Frog, Mint_Choc, Overcast, Pepper_Grinder, Redmond, Smoothness, South_Street, Start, Sunny, Swanky_Purse, Trontastic, UI_Darkness, UI_Lightness, Vader

    l  CustomTheme: 自定义样式主题(custom_default/bootstrap_pure

    l  Language: 控件语言(en/zh_CN/zh_TW,默认值:zh_CN

    l  FormMessageTarget: 表单字段错误提示信息的显示位置(Title/Side/Qtip,默认值:Side

    l  FormLabelWidth: 表单字段标签的宽度(默认值:100px

    l  FormLabelAlign: 表单字段标签的位置(Left/Right/Top,默认值:Left

    l  FormRedStarPosition: 表单字段红色星号的位置(AfterText/BeforeText/AfterSeparator,默认值:AfterText

    l  FormLabelSeparator: 表单字段标签与内容的分隔符(默认值:""

    l  EnableAjax: 是否启用AJAX(默认值:true

    l  AjaxTimeout: Ajax超时时间(单位:秒,默认值:120s

    l  DebugMode: 是否开发模式,启用时格式化页面输出的JavaScript代码,便于调试(默认值:false

    l  EnableAjaxLoading: 是否启用Ajax提示(默认值:true

    l  AjaxLoadingType: Ajax提示类型,默认在页面顶部显示黄色提示框(Default/Mask,默认值:Default

    l  EnableShim: 是否启用遮罩层,防止ActiveXFlash等对象覆盖弹出窗体(默认值:false

    l  EnableCompactMode: 是否启用紧凑模式(默认值:false

    l  EnableLargeMode: 是否启用大字体模式(默认值:false

    l  MobileAdaption: 是否启用移动浏览器自适应(默认值:false

    l  EnableAnimation: 是否启用动画(仅Webkit浏览器支持动画效果)(默认值:false

    l  LoadingImageNumber: 页面加载动画图片的序号(从 1 30,默认值:1

     

    更详细的介绍参考FineUIMvc在线示例站点:

    http://mvc.fineui.com/#/Config/ModifyWebConfig

     

    另外一处配置HTTP处理器:

    <system.web>
    
           <httpModules>
    
             <add name="FineUIMvcScriptModule" type="FineUIMvc.ScriptModule, FineUIMvc"/>
    
           </httpModules>
    
           <httpHandlers>
    
             <add verb="GET" path="res.axd" type="FineUIMvc.ResourceHandler, FineUIMvc"/>
    
           </httpHandlers>
    
    </system.web>

     

    如果你之前用过FineUI(开源版),相信对这个配置很熟悉。

    添加全局模型绑定器

    Global.asax中,添加全部模型绑定器:

    protected void Application_Start()
    {
           AreaRegistration.RegisterAllAreas();
           FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
           RouteConfig.RegisterRoutes(RouteTable.Routes);
    
           ModelBinders.Binders.Add(typeof(JArray), new JArrayModelBinder());
           ModelBinders.Binders.Add(typeof(JObject), new JObjectModelBinder());
    }

     

    这个设置用于模型绑定,可以将客户端传入的JSON数组自动转化为JArray对象,类似如下代码:

     

    当然这个设置不是必须的,如果去掉这个设置,则上面的代码就需要这样写了:

    public ActionResult Grid1_PageIndexChanged(string Grid1_fields, int Grid1_pageIndex)
    {
           JArray fields = JArray.Parse(Grid1_fields);
    
           // .....
    }

     

    布局视图

    布局视图类似于WebForms的母版页,位于Views/Home/Shared/_Layout.cshtml,我们先看下其中的代码:

    @{
        var F = Html.F();
    }
     
    <!DOCTYPE html>
    <html>
    <head>
        <title>@ViewBag.Title - FineUIMvc 空项目</title>
     
        @F.RenderCss()
        <link href="~/res/css/common.css" rel="stylesheet" type="text/css" />
        @RenderSection("head", false)
     
    </head>
    <body>
        @Html.AntiForgeryToken()
     
        @F.PageManager
     
        @RenderSection("body", true)
     
        @F.RenderScript()
        @RenderSection("script", false)
     
    </body>
    </html>

     

    整体架构非常简单,其中和FineUIMvc密切相关的代码:

    1.     var F = Html.F():实例化FineUIMvcHTML辅助方法,视图中所有的FineUIMvc控件都要通过F变量来初始化。

    2.     F.RenderCss():放置FineUIMvc内置的CSS文件。

    3.     F.PageManagerFineUIMvc的页面管理器,每个页面都需要,所以放到布局视图中。

    4.     F.RenderScript():放置FineUIMvc内置的JavaScript文件。

     

    除此之外,我们还通过MVC内置的RenderSection函数定义了几个段落,主要用于放置自定义CSS文件,页面主体HTML以及页面自定义JavaScript脚本文件。

     

    Html.AntiForgeryToken()用于防止跨站请求伪造攻击,需要配合控制器中方法的[ValidateAntiForgeryToken]特性使用,本系列的第三篇文章曾做过详细的介绍。

     

    注意:和用VS自动生成的布局视图有一个很大的不同,这里使用RenderSection("body", true)来方式主体HTML,第二个参数true表明这是一个必选项。所以在具体的视图页面,必须存在名为body的节,否则就会报错。

     

    首页视图

    首页视图的代码相对有点复杂,从截图上可以看出,整个页面被分为三部分,上半部分放置网址标题,操作按钮等控件,左侧部分是一个树控件,右侧部分是一个选项卡控件,我们先来看这部分的代码:

    @(F.RegionPanel()
           .ID("RegionPanel1")
           .ShowBorder(false)
           .IsViewPort(true)
           .Regions(
                  F.Region()
                         .ID("Region1")
                         .ShowBorder(false)
                         .ShowHeader(false)
                         .RegionPosition(Position.Top)
                         .Layout(LayoutType.Fit)
                         .ContentEl("#header"),
                  F.Region()
                         .ID("Region2")
                         .RegionSplit(true)
                         .Width(200)
                         .ShowHeader(true)
                         .Title("菜单")
                         .EnableCollapse(true)
                         .Layout(LayoutType.Fit)
                         .RegionPosition(Position.Left)
                         .Items(
                                F.Tree()
                                       .ShowBorder(false)
                                       .ShowHeader(false)
                                       .ID("treeMenu")
                                       .Nodes(
                                              F.TreeNode()
                                                     .Text("默认分类")
                                                     .Expanded(true)
                                                     .Nodes(
                                                            F.TreeNode()
                                                                   .Text("开始页面")
                                                                   .NavigateUrl("~/Home/Hello"),
                                                            F.TreeNode()
                                                                   .Text("登录页面")
                                                                   .NavigateUrl("~/Home/Login")
                                                     )
                                       )
                         ),
                  F.Region()
                         .ID("mainRegion")
                         .ShowHeader(false)
                         .Layout(LayoutType.Fit)
                         .RegionPosition(Position.Center)
                         .Items(
                                F.TabStrip()
                                       .ID("mainTabStrip")
                                       .EnableTabCloseMenu(true)
                                       .ShowBorder(false)
                                       .Tabs(
                                              F.Tab()
                                                     .ID("Tab1")
                                                     .Title("首页")
                                                     .BodyPadding(10)
                                                     .Layout(LayoutType.Fit)
                                                     .Icon(Icon.House)
                                                     .ContentEl("#maincontent")
                                       )
                         )
           )
    )

    最外层是一个RegionPanel控件,并设置了填充整个浏览器窗口(IsViewPort=true),上半部分的Region控件通过ContentEl属性来指定一个id=headerHTML片段;左侧Region里面放置了一个硬编码的Tree控件,通过指定RegionLayout=Fit来让Tree控件填充整个左侧区域;右侧Region里面放置了TabStrip控件,TabStrip放置了一个初始选项卡Tab,并通过ContentEl来指向一个id=maincontentHTML片段。

     

    这个分析过程也很容易理解,如果你之前使用过FineUI(开源版),对比应该并不陌生,只不过把之前的ASPX语法换成了Rezor语法而已。

     

    左侧树控件和右侧选项卡控件的交互是由JavaScript代码控制的,这个常见交互FineUIMvc进行了封装:

    @section script {
        <script>
            // 页面控件初始化完毕后,会调用用户自定义的onReady函数
            F.ready(function () {
     
                // 初始化主框架中的树和选项卡互动,以及地址栏的更新
                // treeMenu: 主框架中的树控件实例
                // mainTabStrip: 选项卡实例
                // updateHash: 切换Tab时,是否更新地址栏Hash值(默认值:true)
                // refreshWhenExist: 添加选项卡时,如果选项卡已经存在,是否刷新内部IFrame(默认值:false)
                // refreshWhenTabChange: 切换选项卡时,是否刷新内部IFrame(默认值:false)
                // maxTabCount: 最大允许打开的选项卡数量
                // maxTabMessage: 超过最大允许打开选项卡数量时的提示信息
                F.initTreeTabStrip(F.ui.treeMenu, F.ui.mainTabStrip, {
                    maxTabCount: 10,
                    maxTabMessage: '请先关闭一些选项卡(最多允许打开 10 个)!'
                });
     
            });
        </script>
    }

     

    对这里出现JavaScript代码的简单描述:

    1.     F.ready:在控件初始化完毕后执行,类似于jQuery$.ready,只不过F.ready是在DomReady之后执行的。

    2.     F.initTreeTabStrip:设置树控件和选项卡控件的交互,点击树控件节点新建一个启用IFrame的选项卡控件。里面有很多参数可以控制交互行为,在上面的代码中都有注释,就不一一解释了。

    3.     F.ui.treeMenu:引用左侧树控件实例。FineUIMvc会在F.ui命名控件下存储所有页面上初始化的FineUIMvc控件,因此可以方便的通过控件的名称来引用。

     

    主题选择框

    这个页面还有一个需要JavaScript脚本的交互过程,那就是页面右上角的[主体仓库]按钮,点击此按钮时会弹出一个启用IFrameWindow控件,默认这个Window控件是隐藏的:

    @(F.Window()
           .Hidden(true)
           .EnableResize(true)
           .EnableMaximize(true)
           .EnableClose(true)
           .Height(600)
           .Width(1020)
           .IsModal(true)
           .ClearIFrameAfterClose(false)
           .IFrameUrl(Url.Content("~/Home/Themes"))
           .EnableIFrame(true)
           .Title("主题仓库")
           .ID("windowThemeRoller")
    )

     

    按钮的代码放置在id=headerHTML片段中:

    @(F.Button()
           .EnableDefaultCorner(false)
           .EnableDefaultState(false)
           .IconFont(IconFont.Bank)
           .IconAlign(IconAlign.Top)
           .Text("主题仓库")
           .ID("btnThemeSelect")
           .CssClass("icontopaction themes")
           .Listener("click", "onThemeSelectClick")
    )

     

    通过ButtonListener属性来指定点击按钮时需要执行的JavaScript脚本:

    // 点击主题仓库
    function onThemeSelectClick(event) {
           F.ui.windowThemeRoller.show();
    }

     

     

    选择某个主题后的逻辑也很简单,把用户选择的值保存到Cookie中然后刷新页面,这个逻辑不难,请自行查看源代码。

     

    页面刷新后,如何从Cookie中读取值并设置所需的主题呢?这个逻辑其实是在布局视图中完成的,下面来看下完整的布局视图:

    @{
        var F = Html.F();
    }
     
    <!DOCTYPE html>
    <html>
    <head>
        <title>@ViewBag.Title - FineUIMvc 空项目</title>
     
        @F.RenderCss()
        <link href="~/res/css/common.css" rel="stylesheet" type="text/css" />
        @RenderSection("head", false)
     
    </head>
    <body>
        @Html.AntiForgeryToken()
     
        @{
            var pm = F.PageManager;
     
            // 主题
            HttpCookie themeCookie = Request.Cookies["Theme_Mvc"];
            if (themeCookie != null)
            {
                string themeValue = themeCookie.Value;
                Theme theme;
                if (Enum.TryParse<Theme>(themeValue, true, out theme))
                {
                                pm.CustomTheme(String.Empty);
                    pm.Theme(theme);
                }
                else
                {
                    pm.CustomTheme(themeValue);
                }
            }
        }
        @F.PageManager
     
        @RenderSection("body", true)
     
        @F.RenderScript()
        @RenderSection("script", false)
     
    </body>
    </html>

     

    这个逻辑不难,首先从请求的HTTP参数中读取需要的Cookie值,然后设置PageManagerTheme属性,这里之所以有个Enum.TryParse的逻辑,是因为Cookie中保存的也可能是用户自定义主题,两者的处理不同。

     

    登录页面

    1.     访问学生列表页面,会要求用户先登录,如果直接在浏览器地址栏输入:

    http://localhost:64475/Students

    2.     页面会被重定向到:http://localhost:64475/Login?ReturnUrl=%2fStudents

     

    3.     登录成功后,页面会重定向到首页:

     

    4.     此时点击用户头像的下拉菜单项[安全退出],就会重定向到登录页面。

     

     

    这一系列过程是通过表单身份验证完整的,先来看下登录页面的视图代码:

    @{
        ViewBag.Title = "Login";
        var F = @Html.F();
    }
     
    @section body {
        @(F.Window()
            .Width(350)
            .WindowPosition(WindowPosition.GoldenSection)
            .EnableClose(false)
            .IsModal(false)
            .Title("登录表单")
            .ID("Window1")
            .Items(
                F.SimpleForm()
                    .ShowHeader(false)
                    .LabelWidth(80)
                    .BodyPadding(10)
                    .ShowBorder(false)
                    .ID("SimpleForm1")
                    .Items(
                        F.TextBox()
                            .ShowRedStar(true)
                            .Required(true)
                            .Label("用户名")
                            .ID("tbxUserName"),
                        F.TextBox()
                            .ShowRedStar(true)
                            .Required(true)
                            .TextMode(TextMode.Password)
                            .Label("密码")
                            .ID("tbxPassword")
                    )
            )
            .Toolbars(
                F.Toolbar()
                    .Position(ToolbarPosition.Bottom)
                    .ToolbarAlign(ToolbarAlign.Right)
                    .ID("Toolbar1")
                    .Items(
                        F.Button()
                            .OnClick(Url.Action("btnLogin_Click"), "SimpleForm1")
                            .ValidateTarget(Target.Top)
                            .ValidateForms("SimpleForm1")
                            .Type(ButtonType.Submit)
                            .Text("登录")
                            .ID("btnLogin"),
                        F.Button()
                            .Type(ButtonType.Reset)
                            .Text("重置")
                            .ID("btnReset")
                    )
            )
        )
    }

    这个页面是由几个FineUIMvc控件组成的:

    1.     Window控件:默认弹出的Window控件位于页面的中央位置。

    2.     Toolbar控件:Window控件的底部工具栏。

    3.     Button控件:工具栏中的[登录][重置]按钮。

    4.     TextBox控件:[用户名][密码]文本输入框。

     

    点击[登录]按钮,发起一个HTTP POST请求,这个请求对应于控制器LoginbtnLogin_Click方法,第二个参数SimpleForm1用于指定用于传入控制器方法的表单参数。

     

    这个过程对于WebForms开发者来说应该很面熟,如果用WebForms的术语我可以这么来说:点击[登录]按钮,回发当前页面,触发后台的btnLogin_Click事件。这里我特地保留了WebForms中后台事件的命名方法,其实换汤不换药,百变不离其宗,了解HTTP协议的大概工作原理,就不难理解。

     

    后台的btnLogin_Click方法:

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult btnLogin_Click(string tbxUserName, string tbxPassword)
    {
           if (tbxUserName == "admin" && tbxPassword == "admin")
           {
                  FormsAuthentication.RedirectFromLoginPage(tbxUserName, false);
           }
           else
           {
                  ShowNotify("用户名或密码错误!", MessageBoxIcon.Error);
           }
     
           return UIHelper.Result();
    }

     

    退出操作:

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult onSignOut_Click()
    {
           FormsAuthentication.SignOut();
           return RedirectToAction("Index", "Login");
    }

     

    学生列表页面(CRUD

    FineUIMvc中所有去往服务器的POST请求都是AJAX,所以我们可以毫不费力的制作单页应用程序。配合FineUIMvc内置的IFrame支持,可以将一些逻辑独立成一个页面,不仅有助于代码的解耦,而且页面效果也比较统一。

     

    学生列表首页:

     

    在这个页面中,我们可以进行如下操作:

    1.     新增用户:弹出一个启用IFrameWindow控件,在新页面中进行新增用户操作,操作结束后,需要重新绑定表格数据(因为数据已经改变了)。

    2.     编辑用户:和新增是类似的逻辑。

    3.     删除单个用户:在表格行中集成了删除按钮,删除之前会有确认提示框。

    4.     删除多个用户:点击表格工具栏中的[删除选中记录],可以一次删除多条记录,同样删除之前会有确认提示框。

     

    表格页面

    我们先来看下表格的视图代码:

    @(F.Grid()
           .IsViewPort(true)
           .ShowHeader(false)
           .ShowBorder(false)
           .ID("Grid1")
           .DataIDField("ID")
           .DataTextField("Name")
           .EnableCheckBoxSelect(true)
           .Toolbars(
                  F.Toolbar()
                         .Items(
                                F.Button()
                                       .ID("btnDeleteSelected")
                                       .Icon(Icon.Delete)
                                       .Text("删除选中记录")
                                       .Listener("click", "onDeleteSelectedClick"),
                                F.ToolbarFill(),
                                F.Button()
                                       .ID("btnCreate")
                                       .Icon(Icon.Add)
                                       .Text("新增用户")
                                       .Listener("click", "onCreateClick")
                         )
           )
           .Columns(
                  F.RowNumberField(),
                  F.RenderField()
                         .HeaderText("姓名")
                         .DataField("Name")
                         .Width(100),
                  F.RenderField()
                         .HeaderText("性别")
                         .DataField("Gender")
                         .FieldType(FieldType.Int)
                         .RendererFunction("renderGender")
                         .Width(80),
                  F.RenderField()
                         .HeaderText("所学专业")
                         .DataField("Major")
                         .ExpandUnusedSpace(true),
                  F.RenderField()
                         .HeaderText("入学日期")
                         .DataField("EntranceDate")
                         .FieldType(FieldType.Date)
                         .Renderer(Renderer.Date)
                         .RendererArgument("yyyy-MM-dd")
                         .Width(100),
                  F.RenderField()
                         .HeaderText("")
                         .Width(60)
                         .RendererFunction("renderEditField")
                         .TextAlign(TextAlign.Center)
                         .EnableHeaderMenu(false),
                  F.RenderField()
                         .HeaderText("")
                         .Width(60)
                         .RendererFunction("renderDeleteField")
                         .TextAlign(TextAlign.Center)
                         .EnableHeaderMenu(false)
           )
           .DataSource(Model)
    )

     

    这里除了对表格列的定义之外,就是表格顶部工具栏的定义,里面包含图示的两个操作按钮。

     

    表格数据是通过DataSource方法指定的,传入的Model参数类型在页面头部有定义:

    @model IEnumerable<FineUIMvc.QuickStart.Models.Student>

     

    行渲染函数RendererFunction

    同时注意性别列和表格最后的两个操作列,都定义了RendererFunction方法,它用来指定列的客户端渲染脚本。对于性别列,我们知道其数据类型为int,所以需要通过客户端渲染函数来转换为字符串:

    function renderGender(value, params) {
           return value == 1 ? '男' : '女';
    }

     

    而两个操作列,则需要返回包含编辑和删除图标的HTML片段:

    function renderDeleteField(value, params) {
           return '<a href="javascript:;" class="deletefield">
    <img class="f-grid-cell-icon" src="@Url.Content("~/res/icon/delete.png")"></a>';
    }
     
    function renderEditField(value, params) {
           return '<a href="javascript:;" class="editfield">
    <img class="f-grid-cell-icon" src="@Url.Content("~/res/icon/pencil.png")"></a>';
    }

     

    行中删除图标的点击事件

    下面看下如何在脚本中处理行中的编辑图标和删除图标的点击事件:

    F.ready(function () {
     
           var grid1 = F.ui.Grid1;
     
           grid1.el.on('click', 'a.deletefield', function (event) {
                  var rowEl = $(this).closest('.f-grid-row');
                  var rowData = grid1.getRowData(rowEl);
     
                  F.confirm({
                         message: '你确定要删除选中的行数据吗?',
                         target: '_top',
                         ok: function () {
                                deleteSelectedRows([rowData.id]);
                         }
                  });
           });
    });
     

    首先通过F.ui.Grid1来获取页面上表格的实例,而F.ui.Grid1.el则是一个标准的jQuery对象,表示此表格在页面上的DOM元素。然后通过jQueryon函数来注册编辑图标和删除图标的点击事件。

     

    在删除事件中,通过jQueryclosest函数获取编辑图标所在的表格行,然后调用表格的getRowData方法获取行数据,删除时需要知道本行的行ID。然后调用F.confirm弹出确认对话框,在用户点击确认对话框的确认按钮时,执行删除操作(deleteSelectedRows函数)。

     

    之所以将删除逻辑放到deleteSelectedRows中,是因为在批量删除时也需要用到,所以提取为公共方法:

    function deleteSelectedRows(selectedRows) {
           // 触发后台事件
           F.doPostBack('@Url.Action("Grid1_Delete")', {
                  'selectedRows': selectedRows,
                  'Grid1_fields': F.ui.Grid1.fields
           });
    }

     

    这里调用了FineUIMvc封装好的AJAX POST方法F.doPostBack(类似于WebForms中的__doPostBack的命名),第一个参数指定了请求的URL,第二个参数指定请求中附加的表单参数。

     

    行中编辑图标的点击事件

    由于需要在Window控件中弹出新增用户页面和编辑用户页面,所以我们还需要一个隐藏的Window控件:

    @(F.Window()
           .ID("Window1")
           .Width(600)
           .Height(300)
           .IsModal(true)
           .Hidden(true)
           .Target(Target.Top)
           .EnableResize(true)
           .EnableMaximize(true)
           .EnableIFrame(true)
           .IFrameUrl(Url.Content("about:blank"))
           .OnClose(Url.Action("Window1_Close"), "Grid1")
    )

     

    注意:我们为Window控制设置Target=Top属性,表明在顶层页面中弹出这个窗体,而不是局限在当前页面内,这个是FineUIMvc内置的特性,并且仅对于启用IFrameWindow窗体有效(EnableIFrame)。

     

     

    F.ready函数中注册编辑图标的点击事件:

    grid1.el.on('click', 'a.editfield', function (event) {
           var rowEl = $(this).closest('.f-grid-row');
           var rowData = grid1.getRowData(rowEl);
     
           F.ui.Window1.show('@Url.Content("~/Students/Edit/")?studentId=' + rowData.id, '编辑用户');
    });

     

    在编辑事件中,同样先取得当前行数据,然后调用F.ui.Window1.show来在Window控件中显示编辑页面,第二个参数[编辑用户]指定Window控件的标题栏文本。

     

     

    编辑窗体中的[保存后关闭]按钮逻辑

    先来看下Edit视图代码:

    @{
        ViewBag.Title = "Edit";
        var F = @Html.F();
    }
     
    @model FineUIMvc.QuickStart.Models.Student
     
    @section body {
        @(F.Panel()
            .ID("Panel1")
            .ShowBorder(false)
            .ShowHeader(false)
            .BodyPadding(10)
            .AutoScroll(true)
            .IsViewPort(true)
            .Toolbars(
                F.Toolbar()
                    .Items(
                        F.Button()
                            .Icon(Icon.SystemClose)
                            .Text("关闭")
                            .Listener("click", "F.activeWindow.hide();"),
                        F.ToolbarSeparator(),
                        F.Button()
                            .ValidateForms("SimpleForm1")
                            .Icon(Icon.SystemSaveClose)
                            .OnClick(Url.Action("btnEdit_Click"), "SimpleForm1")
                            .Text("保存后关闭")
                    )
            )
            .Items(
                F.SimpleForm()
                    .ID("SimpleForm1")
                    .ShowBorder(false)
                    .ShowHeader(false)
                    .Items(
                        F.HiddenFieldFor(m => m.ID),
                        F.TextBoxFor(m => m.Name),
                        F.RadioButtonListFor(m => m.Gender)
                            .Items(
                                F.RadioItem()
                                    .Text("")
                                    .Value("1"),
                                F.RadioItem()
                                    .Text("")
                                    .Value("0")
                            ),
                        F.TextBoxFor(m => m.Major),
                        F.DatePickerFor(m => m.EntranceDate)
                            .EnableEdit(false)
                    )
            )
        )
    }
     

    让我们把关注点转移到两个操作按钮:

    1.     [关闭]按钮:Listener("click", "F.activeWindow.hide();"),通过这种方式注册一段JavaScript脚本,用来关闭当前IFrame所在的活动窗体。

    2.     [保存后关闭]按钮:这个逻辑要在服务器端处理,因为我们需要先在服务器端保存数据到数据库,然后才能关闭当前活动窗体。

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult btnEdit_Click([Bind(Include = "ID,Name,Gender,Major,EntranceDate")] Student student)
    {
           if (ModelState.IsValid)
           {
                  db.Entry(student).State = EntityState.Modified;
                  db.SaveChanges();
     
                  // 关闭本窗体(触发窗体的关闭事件)
    PageContext.RegisterStartupScript(ActiveWindow.GetHidePostBackReference());
           }
     
           return UIHelper.Result();
    }

     

    关闭活动窗体的逻辑是通过PageContext.RegisterStartupScript函数来注册一段JavaScript脚本来完成的,如果你之前使用过FineUI(开源版),详细对此函数一定非常熟悉:

    1.     ActiveWindow代表的是当前活动的Window控件,而当前脚本是在Window控件的IFrame页面内执行的。

    2.     GetHidePostBackReference用于获取一段脚本,首先关闭当前活动的Window控件,然后触发Window控件的Close事件。这里的命名和开源版中的命名一模一样,相信你不会陌生。

     

    删除多行记录

    表格工具栏中有一个[删除选中记录]按钮,用户可以通过Ctrl或者Shift选择多行,然后点击删除:

     

    这个弹出确认框的逻辑是在客户端完成的,如果默认没有选中值,则会弹出另一个提示框:

     

    首先在视图中通过Listener属性来注册按钮的客户端脚本处理函数:

    F.Button()
           .ID("btnDeleteSelected")
           .Icon(Icon.Delete)
           .Text("删除选中记录")
           .Listener("click", "onDeleteSelectedClick")

     

    下面看下这个脚本处理函数:

    function onDeleteSelectedClick(event) {
           var grid1 = F.ui.Grid1;
     
           if (!grid1.hasSelection()) {
                  F.alert('请至少选择一项!');
                  return;
           }
     
           var selectedRows = grid1.getSelectedRows();
           F.confirm({
                  message: '你确定要删除选中的&nbsp;<b>' + selectedRows.length + '</b>&nbsp;行数据吗?',
                  target: '_top',
                  ok: function () {
                         deleteSelectedRows(selectedRows);
                  }
           });
    }

     

    这里有对FineUIMvc的客户端API接口的调用:

    1.     hasSelection:返回表格是否有选中行。

    2.     F.alert:用来弹出一个提示框。

    3.     getSelectedRows:返回表格选中的行数组,数组元素是行的ID(在表格定义中通过DataIDField标识),这个函数还有个重载,如果传入true参数,则返回的表格元素为行数据。

    4.     F.confirm:用来弹出一个确认框,target用来指定的弹出的位置,由于当前页面位于iframe中,所以我们希望在顶层窗口中弹出确认框。

     

    表单检索与数据库分页

    限于篇幅原因,表单检索和数据库分页就不讲解了,请自行查看源代码。

     

    小结

    本示例使用FineUIMvc(基础版)来实现相同的示例功能,但是页面效果更加专业,功能更加完善,所有到服务器的POST请求都是AJAX,而IFrame的内置支持让整个页面交互轻松自然,不用跳来跳去,同时又能保持业务逻辑代码的独立,方便维护更新。

    下载示例源代码

     

  • 相关阅读:
    android之APN
    Simple XML
    Retrofit – Java(Android) 的REST 接口封装类库
    Android 删除短信
    解决android:background背景图片被拉伸问题
    人分三等,你是哪一等?
    将android中的sample例子到eclipse中
    linux内存管理
    Android 使用android-support-multidex解决Dex超出方法数的限制问题,让你的应用不再爆棚(转)
    使用maven创建web项目
  • 原文地址:https://www.cnblogs.com/sanshi/p/6223164.html
Copyright © 2011-2022 走看看