65.9K
CodeProject 正在变化。 阅读更多。
Home

MVC - 自适应渲染设备视图

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.59/5 (14投票s)

2014年3月12日

CPOL

7分钟阅读

viewsIcon

45544

downloadIcon

537

我将提供一个端到端的教程,演示如何设计一个 MVC 项目,该项目能够支持多种设备,但只有一个相应的控制器和 JavaScript 文件。

引言

我将提供一个端到端的教程,演示如何设计一个 MVC 项目,该项目能够支持多种设备,但只有一个相应的控制器和 JavaScript 文件。视图很可能需要一个配套的 CSS 文件。

我想分享一些技巧和窍门,介绍如何识别连接到您网站的设备,以及如何为该设备渲染合适的视图。所有这些操作都将遵循 MVC 设计模式。

场景

您的经理要求您的团队为当地一所学院开发一个学生仪表板,向教务处管理员提供关于学位课程的统计数据和指标。当您的经理走开时,他回头说,我们的敏捷故事中,支持平板电脑和移动设备是理所当然的!

最终应用程序

我截取了桌面、平板电脑和移动浏览器同时显示在显示器上的截图。从右下角开始是移动视图,中间是平板电脑视图,桌面视图在平板电脑后面。您会注意到,桌面有更多的空间可以利用,并且比平板电脑和移动设备托管更多的控件——因此您必须以不同的方式设计您的视图——但诀窍是使用相同的控制器,尽可能地为用户提供相同的用户体验。

技术
  1. MVC 设计模式
  2. C# 4.5
  3. Visual Studio 2013 (express)
  4. JQuery
  5. JQuery UI
  6. JQuery Mobile
  7. JPlot 图表
  8. JQuery Blocking-UI

测试工具

  1. Opera Mobile Emulator
  2. Safari (发布时可更改 User Agent)
  3. FireFox (UserAgent Switcher)

项目结构

下面,您可以看到该项目是一个普通的 MVC 应用程序。我唯一需要提醒您的是,HomeShared 文件夹有多个视图和布局文件。您必须记住,对于每个桌面视图,您至少需要创建两个额外的视图,一个用于移动设备,一个用于平板电脑(例如,也可以是电视)。我为每种视图类型(移动或平板电脑)都有一个单独的布局文件。

代码

捆绑包

BundleConfig.cs 文件中,我添加了几个新的 bundle。以反映我们正在渲染的新设备。例如,JQuery UI(JavaScript 或 CSS)库在移动环境中实际上并没有用,您会使用其移动等效项。因此,我为桌面、平板电脑和移动设备创建了一个 bundle——您会注意到 bundle 之间有一些共同的脚本或 CSS 文件,一个更聪明的人会将这些共同的文件分离到一个单独的 bundle 中,并在视图中引用它们。

// mobile bundles
bundles.Add(new ScriptBundle("~/bundles/mobileJs").Include(
                      "~/Scripts/jquery.mobile-1.4.0.min.js",
                      "~/Scripts/jquery.jqplot.min.js",                   "~/Scripts/jquery.jqplot.1.0.8r1250/dist/plugins/jqplot.pieRenderer.js",                      "~/Scripts/jquery.jqplot.1.0.8r1250/dist/plugins/jqplot.barRenderer.js",                      "~/Scripts/jquery.jqplot.1.0.8r1250/dist/plugins/jqplot.categoryAxisRenderer.min.js",                      "~/Scripts/jquery.jqplot.1.0.8r1250/dist/plugins/jqplot.pointLabels.min.js",
                      "~/Scripts/jquery.validate.js",
                      "~/Scripts/FooTable-2/dist/footable.all.min.js",
                      "~/Scripts/FooTable-2/dist/footable.filter.min.js",
                      "~/Scripts/FooTable-2/dist/footable.min.js",
                      "~/Scripts/FooTable-2/dist/footable.paginate.min.js",
                      "~/Scripts/FooTable-2/dist/footable.sort.min.js",
                      "~/Scripts/FooTable-2/demos/js/bootstrap-tab.js",
                      "~/Scripts/FooTable-2/demos/js/bootstrapSwitch.js"               
                      ));
 
            bundles.Add(new StyleBundle("~/Content/css/mobile").Include(
                     "~/Content/jquery.mobile-1.4.0.min.css",
                     "~/Content/jquery.mobile.icons-1.4.0.min.css",
                     "~/Content/jquery.mobile.theme-1.4.0.min.css",
                     "~/Content/jquery.mobile.structure-1.4.0.min.css",
                     "~/Content/jquery.mobile.inline-png-1.4.0.css",
                     "~/Content/jquery.mobile.external-png-1.4.0.min.css",
                     "~/Content/jquery.mobile.inline-svg-1.4.0.min.css",
                     "~/Scripts/FooTable-2/css/footable.core.min.css",
                     "~/Scripts/FooTable-2/css/footable.metro.min.css",
                     "~/Scripts/FooTable-2/css/footable.standalone.min.css",
                     "~/Scripts/jquery.jqplot.min.css"                  
                     ));
            // end mobile

 
            // desktop bundles
            bundles.Add(new ScriptBundle("~/bundles/desktopJs").Include(
                      "~/Scripts/jquery.jqplot.min.js",
                      "~/Scripts/jquery.jqGrid.min.js",
                      "~/Scripts/jquery.blockUI.min.js",                      "~/Scripts/jquery.jqplot.1.0.8r1250/dist/plugins/jqplot.pieRenderer.js",                      "~/Scripts/jquery.jqplot.1.0.8r1250/dist/plugins/jqplot.barRenderer.js",                      "~/Scripts/jquery.jqplot.1.0.8r1250/dist/plugins/jqplot.categoryAxisRenderer.min.js",                      "~/Scripts/jquery.jqplot.1.0.8r1250/dist/plugins/jqplot.pointLabels.min.js",
                      "~/Scripts/jquery.validate.js"
                      ));
 
            bundles.Add(new StyleBundle("~/Content/css/desktop").Include(
                     "~/Scripts/jquery.jqplot.min.css", 
                     "~/Scripts/jquery.jqplot.min.css",                    
                     "~/Content/jquery.jqGrid/ui.jqgrid.css",
                     "~/Content/themes/base/jquery.ui.all.css",
                     "~/Scripts/validateForm/validationEngine.jquery.css"                        
                     ));
            // end desktop

            // tablet bundles
            bundles.Add(new ScriptBundle("~/bundles/tabletJs").Include(
                      "~/Scripts/jquery.jqplot.min.js",
                      "~/Scripts/jquery.jqGrid.min.js",
                      "~/Scripts/jquery.blockUI.min.js",                      "~/Scripts/jquery.jqplot.1.0.8r1250/dist/plugins/jqplot.pieRenderer.js",                      "~/Scripts/jquery.jqplot.1.0.8r1250/dist/plugins/jqplot.barRenderer.js",                      "~/Scripts/jquery.jqplot.1.0.8r1250/dist/plugins/jqplot.categoryAxisRenderer.min.js",                      "~/Scripts/jquery.jqplot.1.0.8r1250/dist/plugins/jqplot.pointLabels.min.js",
                      "~/Scripts/jquery.validate.js",
                      "~/Scripts/jquery-ui-1.10.4.min.js"                    
                      ));
 
            bundles.Add(new StyleBundle("~/Content/css/tablet").Include(
                     "~/Scripts/jquery.jqplot.min.css",
                     "~/Content/jquery.mobile-1.4.0.min.css",
                     "~/Content/jquery.mobile.icons-1.4.0.min.css",
                     "~/Content/jquery.mobile.theme-1.4.0.min.css",
                     "~/Content/jquery.jqGrid/ui.jqgrid.css",
                     "~/Scripts/validateForm/validationEngine.jquery.css",
                     "~/Content/themes/base/jquery-ui.css"
                     ));
            // end tablet

引用不同布局的视图

移动视图片段

下面是我移动视图顶部的代码片段,它将引用移动布局。

@{
    Layout = "~/Views/Shared/_LayoutMobile.cshtml";   
}

平板视图片段

同样,平板视图也有自己的布局。布局文件看起来非常相似,这是因为我只为其中一个(Home)视图创建了自适应渲染。随着您扩展应用程序并添加更多视图,编辑相应的布局文件并知道您的更改不会影响其他设备布局的布局将变得更容易。

@{
    Layout = "~/Views/Shared/_LayoutTablet.cshtml";      
}

确定连接到您网站的设备

您网站的主要入口点是通过 Global.asax 文件中的 Application Start 方法。Start 方法将解析 UserAgent 头并确定设备类型。有一些插件可以帮助您进行此部分的开发,即

  1. 51Degress (http://51degrees.codeplex.com/) 或 (http://51degrees.com/)
  2. WURFL

提示: 自行实现的方法是检查连接到您网站的设备,如果您无法确定它——则渲染桌面视图并记录设备详细信息——发送电子邮件给您的支持团队。有了这些信息,他们就可以更新您的设备存储库,以便当同一设备连接时,您将渲染合适的视图。

下面的代码片段基本上是在 DisplayModeProvider 集合中为移动设备插入一个新条目。因此,当设备连接到您的网站并且它是一个移动设备时,它将获得以下页面渲染 - Index.mobile.cshtml,用于索引页面。

DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("mobile")
{
ContextCondition = (context => GetDeviceType(context.GetOverriddenUserAgent()) == "mobile")
});

helper 方法 GetDeviceType 将尽可能准确地确定设备类型——但没有什么比最新的设备数据存储库查询更好了。但是,此方法肯定会覆盖 90% 的设备,如果您为未知设备采用支持策略,随着时间的推移,您可以轻松地将此百分比提高到您的公司所需。

 protected void Application_Start()
        {         
            DisplayModeProvider.Instance.Modes.Insert(0, new DefaultDisplayMode("mobile")
            {
                ContextCondition = (context => GetDeviceType(context.GetOverriddenUserAgent()) == "mobile")
            }); 
 
            DisplayModeProvider.Instance.Modes.Insert(1, new DefaultDisplayMode("tablet")
            {
                ContextCondition = (context => GetDeviceType(context.GetOverriddenUserAgent()) == "tablet")
            });
 
            DisplayModeProvider.Instance.Modes.Insert(2, new DefaultDisplayMode("tv")
            {
                ContextCondition = (context => GetDeviceType(context.GetOverriddenUserAgent()) == "tv")
            });                       
 
            // otherwise it’s a desktop browser and defaults to normal view.

            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
        }
 
        public string GetDeviceType(string ua)
        {
            string ret = "";
            // Check if user agent is a smart TV - http://goo.gl/FocDk
            if (Regex.IsMatch(ua, @"GoogleTV|SmartTV|Internet.TV|NetCast|NETTV|AppleTV|boxee|Kylo|Roku|DLNADOC|CE\-HTML", RegexOptions.IgnoreCase))
            {
                ret = "tv";
            }
            // Check if user agent is a TV Based Gaming Console
            else if (Regex.IsMatch(ua, "Xbox|PLAYSTATION.3|Wii", RegexOptions.IgnoreCase))
            {
                ret = "tv";
            }
            // Check if user agent is a Tablet
            else if ((Regex.IsMatch(ua, "iP(a|ro)d", RegexOptions.IgnoreCase) || (Regex.IsMatch(ua, "tablet", RegexOptions.IgnoreCase)) && (!Regex.IsMatch(ua, "RX-34", RegexOptions.IgnoreCase)) || (Regex.IsMatch(ua, "FOLIO", RegexOptions.IgnoreCase))))
            {
                ret = "tablet";
            }
            // Check if user agent is an Android Tablet
            else if ((Regex.IsMatch(ua, "Linux", RegexOptions.IgnoreCase)) && (Regex.IsMatch(ua, "Android", RegexOptions.IgnoreCase)) && (!Regex.IsMatch(ua, "Fennec|mobi|HTC.Magic|HTCX06HT|Nexus.One|SC-02B|fone.945", RegexOptions.IgnoreCase)))
            {
                ret = "tablet";
            }
            // Check if user agent is a Kindle or Kindle Fire
            else if ((Regex.IsMatch(ua, "Kindle", RegexOptions.IgnoreCase)) || (Regex.IsMatch(ua, "Mac.OS", RegexOptions.IgnoreCase)) && (Regex.IsMatch(ua, "Silk", RegexOptions.IgnoreCase)))
            {
                ret = "tablet";
            }
            // Check if user agent is a pre Android 3.0 Tablet
            else if ((Regex.IsMatch(ua, @"GT-P10|SC-01C|SHW-M180S|SGH-T849|SCH-I800|SHW-M180L|SPH-P100|SGH-I987|zt180|HTC(.Flyer|\\_Flyer)|Sprint.ATP51|ViewPad7|pandigital(sprnova|nova)|Ideos.S7|Dell.Streak.7|Advent.Vega|A101IT|A70BHT|MID7015|Next2|nook", RegexOptions.IgnoreCase)) || (Regex.IsMatch(ua, "MB511", RegexOptions.IgnoreCase)) && (Regex.IsMatch(ua, "RUTEM", RegexOptions.IgnoreCase)))
            {
                ret = "tablet";
            }
            // Check if user agent is unique Mobile User Agent
            else if ((Regex.IsMatch(ua, "BOLT|Fennec|Iris|Maemo|Minimo|Mobi|mowser|NetFront|Novarra|Prism|RX-34|Skyfire|Tear|XV6875|XV6975|Google.Wireless.Transcoder", RegexOptions.IgnoreCase)))
            {
                ret = "mobile";
            }
            // Check if user agent is an odd Opera User Agent - http://goo.gl/nK90K
            else if ((Regex.IsMatch(ua, "Opera", RegexOptions.IgnoreCase)) && (Regex.IsMatch(ua, "Windows.NT.5", RegexOptions.IgnoreCase)) && (Regex.IsMatch(ua, @"HTC|Xda|Mini|Vario|SAMSUNG\-GT\-i8000|SAMSUNG\-SGH\-i9", RegexOptions.IgnoreCase)))
            {
                ret = "mobile";
            }
            // Check if user agent is Windows Desktop
            else if ((Regex.IsMatch(ua, "Windows.(NT|XP|ME|9)")) && (!Regex.IsMatch(ua, "Phone", RegexOptions.IgnoreCase)) || (Regex.IsMatch(ua, "Win(9|.9|NT)", RegexOptions.IgnoreCase)))
            {
                ret = "desktop";
            }
            // Check if agent is Mac Desktop
            else if ((Regex.IsMatch(ua, "Macintosh|PowerPC", RegexOptions.IgnoreCase)) && (!Regex.IsMatch(ua, "Silk", RegexOptions.IgnoreCase)))
            {
                ret = "desktop";
            }
            // Check if user agent is a Linux Desktop
            else if ((Regex.IsMatch(ua, "Linux", RegexOptions.IgnoreCase)) && (Regex.IsMatch(ua, "X11", RegexOptions.IgnoreCase)))
            {
                ret = "desktop";
            }
            // Check if user agent is a Solaris, SunOS, BSD Desktop
            else if ((Regex.IsMatch(ua, "Solaris|SunOS|BSD", RegexOptions.IgnoreCase)))
            {
                ret = "desktop";
            }
            // Check if user agent is a Desktop BOT/Crawler/Spider
            else if ((Regex.IsMatch(ua, "Bot|Crawler|Spider|Yahoo|ia_archiver|Covario-IDS|findlinks|DataparkSearch|larbin|Mediapartners-Google|NG-Search|Snappy|Teoma|Jeeves|TinEye", RegexOptions.IgnoreCase)) && (!Regex.IsMatch(ua, "Mobile", RegexOptions.IgnoreCase)))
            {
                ret = "desktop";
            }
            // Otherwise assume it is a Mobile Device
            else
            {
                ret = "mobile";
            }
            HttpRuntime.Cache.Insert("DeviceType", ret);
                        
            return ret;
        }

一个控制器

下面是我用于多个设备视图的单个控制器。每个视图都调用相同的控制器方法(使用 Ajax),返回的数据被略微处理不同——但本质上一个 JavaScript 也会处理并绑定返回的数据。

using DeskTabMobile.EntFramework;
using DeskTabMobile.Models;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
 
namespace DeskTabMobile.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {         
            return View();
        }       
 
        public JsonResult GetPieGraphData()
        {
            // perform an EF action to retrieve data...dummy data below!!!
            List<PieChartData> dataArray = new List<PieChartData>();
            dataArray.Add(new PieChartData() { Name = "Physics", Value = 15 });
            dataArray.Add(new PieChartData() { Name = "Maths", Value = 10 });
            dataArray.Add(new PieChartData() { Name = "English Lt", Value = 25 });
            dataArray.Add(new PieChartData() { Name = "ICT", Value = 20 });
            dataArray.Add(new PieChartData() { Name = "History", Value = 30 });
            return this.Json(dataArray, JsonRequestBehavior.AllowGet);            
        }
 
        public JsonResult GetCourseResultsData()
        {
            // perform an EF action to retrieve data...dummy data below!!!
            BarChartData dataArray = new BarChartData();
            dataArray.ICTScores = new List<int>() { 200, 600, 700, 1000, 460, 820, 460, -210, 690, 820 };
            dataArray.MathsScores = new List<int>() { 460, -210, 690, 820, 200, 600, 700, -210, 690, 820 };
            dataArray.PhysicsScores = new List<int>() { -260, -440, 320, 200, 200, 600, 700, 1000, 460, 820 };
            dataArray.Months = new List<string>() { "Jan", "Feb", "Mar", "Apr", "May", "June", "Sept", "Oct", "Nov", "Dec"};
            
            return this.Json(dataArray, JsonRequestBehavior.AllowGet);                    
        }
 
        public JsonResult GetStudentsOnCourseData()
        {
            // perform an EF action to retrieve data...dummy data below!!!
            List<GridData> gridData = new List<GridData>();
            gridData.Add(new GridData() { StudentID = "JC200300001", Name = "Jessica Cohen", Course = "Applied Arts and Sciences", Year = 1 });
            gridData.Add(new GridData() { StudentID = "SF200800002", Name= "Shane Foster", Course= "Business Administration", Year= 2});
            gridData.Add(new GridData() {StudentID= "KK200800003", Name= "Kellan Kher", Course= "Social Sciences", Year= 4});
            gridData.Add(new GridData() { StudentID= "PS200100004", Name= "Patrick Shirazi", Course= "Education", Year= 1});
            gridData.Add(new GridData() { StudentID= "CB200400005", Name= "Constance Barry", Course= "Social Sciences", Year= 3});
            gridData.Add(new GridData() { StudentID= "DW200400006", Name= "Dustin Wallace", Course= "Philosophy", Year= 1  });
            gridData.Add(new GridData() { StudentID= "JC200300011", Name= "Jessica Cohen", Course= "Applied Arts and Sciences", Year= 1});
            gridData.Add(new GridData() { StudentID= "SF200800012", Name= "Shane Foster", Course= "Business Administration", Year= 2 });
            gridData.Add(new GridData() { StudentID= "KK200800013", Name= "Kellan Kher", Course= "Social Sciences", Year= 4 });
            gridData.Add(new GridData() {StudentID="PS200100014", Name= "Patrick Shirazi", Course= "Education", Year= 1 });
            gridData.Add(new GridData() { StudentID= "DW200400016", Name= "Dustin Wallace", Course= "Philosophy", Year= 1 });
            gridData.Add(new GridData() { StudentID= "JC200300101", Name= "Jessica Cohen", Course= "Applied Arts and Sciences", Year= 1});
            gridData.Add(new GridData() { StudentID= "SF200800102", Name= "Shane Foster", Course= "Business Administration", Year= 2});
            gridData.Add(new GridData() { StudentID= "KK200800103", Name= "Kellan Kher", Course= "Social Sciences", Year= 4});
            gridData.Add(new GridData() { StudentID= "PS200100104", Name= "Patrick Shirazi", Course= "Education", Year= 1 });
            gridData.Add(new GridData() { StudentID= "CB200400105", Name= "Constance Barry", Course= "Social Sciences", Year= 3 });
            gridData.Add(new GridData() { StudentID= "DW200400106", Name= "Dustin Wallace", Course= "Philosophy", Year= 1 });            
 
            return this.Json(gridData, JsonRequestBehavior.AllowGet);            
           
        }
 
        public ActionResult GetDeviceType()
        {           
            string deviceType = HttpRuntime.Cache["DeviceType"].ToString();
            return Json(new { deviceType }, JsonRequestBehavior.AllowGet);
        }
    }
}

一个 JavaScript

此外,我想为每个视图设备创建一个 JavaScript 文件,因为为每个设备创建每个视图的 JavaScript 文件很快就会导致管理问题!

关键是要在客户端确定设备类型;这可以通过同步调用控制器方法来返回设备类型来轻松实现。然后,当我调用一个 JavaScript 方法时,我就可以根据设备确定运行哪个代码片段。

以下代码片段将获取设备类型供客户端保存以备后用

$.getJSON('/home/GetDeviceType', function (result) {
        deviceType = result.deviceType;       
    });

JavaScript 文件可能有点大,但您必须记住,您基本上是在一个应用程序中设计三个应用程序——这个大小是可以管理的——JavaScript 的复杂程度取决于您自己——也许转向 TypeScript 可能是另一种方法!

最后,在 JavaScript 代码中,我会在运行代码之前确定设备是什么,使用类似于以下的片段

if (deviceType == 'mobile') {...}

JavaScript代码

$(document).ready(function () {
    var rowID; 
    var tableRowSelected;
    var isLoaded = false;
    var isGridLoaded = new Boolean(false);
    var deviceType;
 
    $.ajaxSetup({
        async: false
    });
 
    $.getJSON('/home/GetDeviceType', function (result) {
        deviceType = result.deviceType;       
    });
 
    $.ajaxSetup({
        async: true
    });
 
    if (deviceType == 'tablet') { $("#tabs").tabs(); }
 
    if (deviceType == 'mobile') {
        $(document).on("mobileinit", function () {
            $.mobile.loader.prototype.options.text = "loading";
            $.mobile.loader.prototype.options.textVisible = false;
            $.mobile.loader.prototype.options.theme = "a";
            $.mobile.loader.prototype.options.html = "";
        });
    }
 
    $('#btnUpdate').prop('disabled', true);
    $('#btnClear').prop('disabled', true);
 
    // load data into controls
    if (deviceType == 'desktop') {
      
        LoadBarChartChart();
        LoadGridData();
    }
 
    // all devices load pie chat & data
    LoadPieChartData();   
 
    function LoadBarChartChart()
    {
        if (deviceType == 'desktop') {
            $('#chartCtrl').block({
                message: '<h1>Loading...</h1>',
                css: {
                    border: 'none',
                    padding: '15px',
                    backgroundColor: '#000',
                    '-webkit-border-radius': '10px',
                    '-moz-border-radius': '10px',
                    opacity: .5,
                    color: '#fff'
                }
            });
        }
 
        if (deviceType == 'mobile') {
            $.mobile.loading("show", { text: "Loading......", textVisible: true });
 
            $('#one').hide();
            $('#three').hide();
            $('#four').hide();
            $('#two').show();
        }
 
        $.getJSON("/Home/GetCourseResultsData", null, function (data) {
            var s1 = data.MathsScores;
            var s2 = data.ICTScores;
            var s3 = data.PhysicsScores;
            var ticks = data.Months;
 
            var plot1 = $.jqplot('chartCtrl', [s1, s2, s3], {
                seriesDefaults: {
                    renderer: $.jqplot.BarRenderer,
                    rendererOptions: { fillToZero: true }
                },
                series: [
                    { label: 'Maths' },
                    { label: 'ICT' },
                    { label: 'Physics' }
                ],
                legend: {
                    show: true,
                    placement: 'outsideGrid'
                },
                axes: {
                    xaxis: {
                        renderer: $.jqplot.CategoryAxisRenderer,
                        ticks: ticks
                    },
                    yaxis: {
                        pad: 1.05,
                        tickOptions: { formatString: '#%d' }
                    }
                }
            });
 
            if (deviceType == 'mobile') {
                $.mobile.loading("hide");
            }
            else {
                $('#chartCtrl').unblock();
            }
 
           
 
        });
    }
 
    function LoadPieChartData() {
        if (deviceType == 'mobile') {
            $.mobile.loading("show", { text: "Loading......", textVisible: true });
        }
 
        // percentage of degree cources   
        
        if (deviceType == 'desktop') {
            if (deviceType == 'desktop') {
                $('#pieCtrl').block({
                    message: '<h1>Loading...</h1>',
                    css: {
                        border: 'none',
                        padding: '15px',
                        backgroundColor: '#000',
                        '-webkit-border-radius': '10px',
                        '-moz-border-radius': '10px',
                        opacity: .5,
                        color: '#fff'
                    }
                });
            }
        }
 
        $.getJSON("/Home/GetPieGraphData", null, function (data) {
            var pieChartData = [];
            for (var prop_name in data) {
                pieChartData.push([data[prop_name].Name, data[prop_name].Value])
            }
 
            var plot1 = $.jqplot('pieCtrl', [pieChartData], {
                gridPadding: { top: 0, bottom: 38, left: 0, right: 0 },
                seriesDefaults: {
                    renderer: $.jqplot.PieRenderer,
                    trendline: { show: false },
                    rendererOptions: { padding: 8, showDataLabels: true }
                },
                legend: {
                    show: true,
                    placement: 'inside',                   
                    rendererOptions: {
                        numberRows: 1
                    },
                    location: 's',
                    marginTop: '15px'
                }
            });
 
            if (deviceType == 'mobile') {
                $.mobile.loading("hide");
            }
            else {
                $('#pieCtrl').unblock();
            }
 
        });
    }
 
    function LoadGridData() {        
        if (deviceType == 'mobile') {
            $.mobile.loading("show", { text: "Loading......", textVisible: true });
 
            $('#one').hide();
            $('#two').hide();
            $('#four').hide();
            $('#three').show();
        }
 
        if (deviceType == 'desktop') {
            // make ajax call to controller
            if (deviceType == 'desktop') {
                $('#student-grid-array').block({
                    message: '<h1>Loading...</h1>',
                    css: {
                        border: 'none',
                        padding: '15px',
                        backgroundColor: '#000',
                        '-webkit-border-radius': '10px',
                        '-moz-border-radius': '10px',
                        opacity: .5,
                        color: '#fff'
                    }
                });
            }
        }
 
        $.getJSON("/Home/GetStudentsOnCourseData", null, function (data) {           
            if (deviceType == 'mobile') {
                $('#myTable tbody').html('<tr> <td></td><td>');
                var content = '';
                $.each(data, function () {
                    content += "<tr> <td>" + this['StudentID'] + "</td><td>" + this['Name'] + "</td><td>" + this['Course'] + "</td><td>" + this['Year'] + "</td> <td>Active</td></tr>";
                });
                $('#myTable tbody').html(content);
 
                $('#myTable').footable();
                $('#myTable').footable().bind({
                    'footable_row_expanded': function (e) {
                        tableRowSelected = e.row;
 
                        var elTableCells = e.row.getElementsByTagName("td"); // get the row cells

                        $('#txtID').val(elTableCells[0].innerText);
                        $('#txtName').val(elTableCells[1].innerText);
                        $('#txtCourse').val(elTableCells[2].innerText);
                        $('#txtYear').val(elTableCells[3].innerText);
                        $('#btnUpdate').prop('disabled', false);
                    }
                });
            }
            else {
                var pieChartData = [];
                for (var prop_name in data) {
                    pieChartData.push([data[prop_name].Name, data[prop_name].Value])
                }
 
                // studenst on courses
                $("#student-grid-array").jqGrid({
                    datatype: "local",
                    autowidth: true,
                    // height: $("#gridContainor").height()-55, // allow for scrollbar buttons etc.
                    colNames: ["Student ID", "Name", "Course", "Year"],
                    colModel: [
                                { name: "StudentID", index: "StudentID" },
                                { name: "Name", index: "Name" },
                                { name: "Course", index: "Course" },
                                { name: "Year", index: "Year", sorttype: "int" }
                    ],
                    multiselect: false,
                    shrinkToFit: true,
                    caption: "Student Degree List by Course",
                    onSelectRow: function (id) {
                        rowID = id;
                        var name = $('#student-grid-array').jqGrid('getCell', id, 'Name');
                        var course = $('#student-grid-array').jqGrid('getCell', id, 'Course');
                        var year = $('#student-grid-array').jqGrid('getCell', id, 'Year');
                        var studentID = $('#student-grid-array').jqGrid('getCell', id, 'StudentID');
 
                        $('#txtID').val(studentID);
                        $('#txtName').val(name);
                        $('#txtCourse').val(course);
                        $('#txtYear').val(year);
 
                        $('#btnUpdate').prop('disabled', false);
                        $('#btnClear').prop('disabled', false);
 
                    }
                });
 
                for (var x = 0; x <= data.length; x++) {
                    $("#student-grid-array").addRowData(x, data[x]);
                }
            }
 
            if (deviceType == 'mobile') {
                $.mobile.loading("hide");              
            }
            else {
                $('#student-grid-array').unblock();
            }
        });
    } 
 
    $('#btnUpdate').click(function () {
 
        if (deviceType == 'mobile') {
            $.mobile.loading("show", { text: "Loading......", textVisible: true });
 
            var elTableCells = tableRowSelected.getElementsByTagName("td"); // get the row cells
            elTableCells[0].innerText = $('#txtID').val(); // update ID
            elTableCells[1].innerText = $('#txtName').val(); // update name
            elTableCells[2].innerText = $('#txtCourse').val(); // update course
            elTableCells[3].innerText = $('#txtYear').val(); // update year

            $('#txtID').val('');
            $('#txtName').val('');
            $('#txtCourse').val('');
            $('#txtYear').val('');
            $('#btnUpdate').prop('disabled', true);
            $('#btnInsert').prop('disabled', true);
 
            $('#one').hide();
            $('#two').hide();
            $('#three').show();
            $('#four').hide();
 
            // make tab (appear) active
            $('#lnkStudentsOnCourse').addClass('ui-btn-active');
            $('#lnkUpdateStudent').removeClass('ui-btn-active');
 
            // refresh table
            $('#myTable').trigger('footable_redraw');
            $(".footable").trigger("footable_resize");
 
            $.mobile.loading("hide");
        }
        else {
 
 
            // update grid data (no validation)!!!
            var rowData = $('#student-grid-array').jqGrid('getRowData', rowID);
            rowData.Name = $('#txtName').val();
            rowData.Course = $('#txtCourse').val();
            rowData.Year = $('#txtYear').val();
            $('#student-grid-array').jqGrid('setRowData', rowID, rowData);
 
            $('#txtID').val('');
            $('#txtName').val('');
            $('#txtCourse').val('');
            $('#txtYear').val('');
            $('#btnUpdate').prop('disabled', true);
            $('#btnInsert').prop('disabled', true);
 
            if (deviceType == 'tablet') { $("#tabs").tabs("option", "active", 2); }
        }
 
    });
 
    $('#btnClear').click(function () {
        $('#txtID').val('');
        $('#txtName').val('');
        $('#txtCourse').val('');
        $('#txtYear').val('');
        $('#btnUpdate').prop('disabled', true);
        $('#btnClear').prop('disabled', true);
    });
 
    /*
    Mobile & Tablet tab methods
    */
 
    $("#lnkAverageMarks").click(function () {
 
        if (deviceType == 'mobile') {
            $.mobile.loading("show", { text: "Loading......", textVisible: true });
 
            $('#one').hide();
            $('#three').hide();
            $('#four').hide();
            $('#two').show();
        }
 
        LoadBarChartChart();
    });
 
    $("#lnkStudentsOnCourse").click(function () {
 
        if (deviceType == 'mobile') {
            $.mobile.loading("show", { text: "Loading......", textVisible: true });
 
            $('#one').hide();
            $('#two').hide();
            $('#four').hide();
            $('#three').show();            
        }
 
        if (isGridLoaded == false) {
            isGridLoaded = true;
            LoadGridData();
        }
 
        $.mobile.loading("hide");
    });
 
    $("#lnkUpdateStudent").click(function () {
        // fields will have been populated
        $('#one').hide();
        $('#two').hide();
        $('#three').hide();
        $('#four').show();
    });
 
    $("#lnkCourseAttend").click(function () {
        // data already loaded
        $('#four').hide();
        $('#two').hide();
        $('#three').hide();
        $('#one').show();
        $('#pieCtrl').unblock();
    });
});

视图 CSS

因为每个视图都不同,所以将应用不同的 CSS 到相应的视图。为了创建一种易于管理的样式文件结构,会在 Contents 文件夹中创建一个表示视图的文件夹结构。我只创建了一个 custom 文件夹,这在语法上是不正确的,因为您最好为每个视图创建一个文件夹(例如,Home),然后为每个设备创建三个子文件夹。因此,因为我的应用程序只有一个页面——我只创建了一个 custom 文件夹——但实际上一个 Web 应用程序将有多个页面,因此为每个视图创建一个文件夹,其中包含三个子文件夹(用于三个设备)。

运行应用程序

如果您从 Visual Studio 运行应用程序,您应该会看到桌面版本

桌面

平板电脑

请注意,我使用了标签页方法来显示各种控件。在点击相应的标签页标题时按需加载数据。

要测试该应用程序作为平板电脑,请启动 Opera Emulator,选择一个平板电脑设备,然后输入桌面浏览器中的 URL——按回车键,您应该开始看到该应用程序在平板电脑设备上的外观。我使用了Samsung Galaxy Tab 设备。

移动

与其他设备相比,视图再次发生了变化。一些客户端逻辑也必须改变,这就是为什么我必须在 JavaScript 文件中确定我正在使用的设备。同样,只有在我点击标签页按钮时才加载数据。我正在使用 JQuery Mobile 库和 JQM CSS 来获得外观和感觉。我再次使用 Opera Emulator 将页面视为移动设备——选择LG Intuition 设备。

结论

您可以看到,使用 MVC 设计模式为多种设备进行设计是可能的。我承认 HTML5 在性能和与设备功能的交互方面落后于原生设备代码(Android 或 IOS)。但是,如果您追求的是面向数据/Web 的应用程序,那么这种方法是非常可行的!

© . All rights reserved.