十四、使用 jQuery Mobile 改善用户体验

在前一章中,我们学习了如何创建移动设备特有的视图。除了内容更改之外,这些视图还使用了响应设计和自适应渲染技术,以使我们的应用在用户浏览器中看起来和性能更好。话虽如此,它看起来和感觉仍然像一个网络应用。我们真正想做的是为我们的用户提供一种体验,让他们忘记他们正在看一个网络应用。我们可以使用 jQuery Mobile 提供这种体验。

jQuery Mobile 是一个针对移动浏览器的用户界面框架。它旨在通过一次写入的方式为移动网络应用提供设备原生的外观。

jQuery Mobile 能够同时针对多个浏览器,因为它采用了渐进式增强方法。简而言之,渐进式增强是一种设计方法,允许基线内容和布局面向各种浏览器。当向用户显示内容时,通过 CSS 和 JavaScript 对内容进行了查看浏览器能力范围内的增强。

可以说,jQuery Mobile 最吸引人的特性是语义标记的使用。通过使用自定义数据属性,即我们在第 12 章为移动设备设计您的应用中讨论 HTML5 时了解到的data-*属性,我们可以将 HTML 标签标记为 jQuery Mobile 小部件。然后,通过渐进式增强,只有能够向用户显示的小部件才能通过移动外观得到增强。

如果您决定不使用自定义数据属性在标记中提供表示提示,您也可以直接实例化 jQuery Mobile 小部件,但是您将错过框架的一个非常强大的功能。

要了解 jQuery Mobile 使用起来有多简单,最好的方法就是实际使用它,所以让我们开始吧。

安装 jQuery 手机

如果我们使用移动应用模板启动我们的应用,我们会立即获得 jQuery 移动库。由于我们使用互联网应用模板启动了我们的应用,我们需要将库添加到我们的项目中。我们将使用软件包管理器控制台从 NuGet 软件包安装它。

jQuery Mobile NuGet 包包含一个名为_Layout.Mobile.cshtml的文件,与我们在上一章创建的手机布局同名。为了正确安装软件包,我们首先需要从现有项目中删除_Layout.Mobile.cshtml文件。它将被替换为包中的那个。右键单击我们项目中的_Layout.Mobile.cshtml文件并将其删除。删除布局时,继续删除 Nexus 7 布局。我们希望所有移动设备都以 jQuery Mobile 布局为目标。

现在我们可以通过从工具菜单中选择库包管理器来打开包管理器控制台,并通过在控制台中执行以下操作来安装 jQuery Mobile MVC 包:

Install-Package jQuery.Mobile.MVC

这个包的安装修改了我们的项目,在_Layout.Mobile.cshtml之外增加了几个新的文件。简而言之,我们应该在我们的项目中找到以下文件:

  • 移动 jQuery
  • 移动 CSS 文件和支持图像
  • 框架
  • _ViewSwitcher.cshtml,部分视图,提供在桌面和移动视图之间切换的能力
  • 包含用于支持部分视图切换器的控制器的ViewSwitcherController.cs文件
  • A new jQuery Mobile bundle definition in BundleMobileConfig.cs

    这些文件代表了从包版本 1.0.0 开始的 jQuery Mobile MVC NuGet 包的内容。

启用 jQuery 移动捆绑包

jQuery 移动包包括 CSS 和 jQuery 移动库的捆绑包。这些包在我们项目的App_Start文件夹中添加的BundleMobileConfig.cs文件中定义。

安装软件包后,我们会看到一个包含以下文本的readme.txt文件:

要启用默认移动视图,您必须在Global.asax.cs中添加以下行作为Application_Start方法的最后一行:

BundleMobileConfig.RegisterBundles(BundleTable.Bundles);

让我们打开Global.asax.cs文件,按照readme.txt文件指示注册我们的移动捆绑包:

ServiceLocatorConfig.RegisterTypes();
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
BundleMobileConfig.RegisterBundles(BundleTable.Bundles);
AuthConfig.RegisterAuth();
DisplayModeConfig.RegisterDisplayModes();

现在,我们的应用已经在 jQuery Mobile 环境下为移动设备启用了,所以让我们看看有什么新功能。

查看结果

在我们可以启动 app 之前,我们需要对新的_Layout.Mobile.cshtml文件做一个小的改动。我们的视图正在布局中寻找名为Scripts的部分。jQuery Mobile 包提供的移动布局中不存在此部分。这个很简单就能搞定;只需在结束正文标签前将以下代码添加到_Layout.Mobile.cshtml文件中:

@RenderSection("Scripts", false)

现在,如果我们构建并启动我们的 BrewHow 应用,在 Chrome 中查看该应用时不会有任何差异,但在 Opera Mobile 模拟器中查看该应用时,差异会相当明显:

Viewing the results

除了内容之外,该网站的移动版本与我们之前的移动版本几乎没有相似之处。让我们检查一下我们的移动应用的新布局文件,了解 jQuery Mobile 是如何被用来创建这种新的外观和感觉的。

jQuery 手机的布局

新的 jQuery Mobile 布局相当简单。事实上,它比我们在上一章中构建的初始移动布局小得多:

<!DOCTYPE html> 
<html> 
  <head> 
    <meta charset="utf-8" />
    <title>@ViewBag.Title</title> 
    <meta name="viewport" content="width=device-width" />
    @Styles.Render("~/Content/Mobile/css", "~/Content/jquerymobile/css")
    @Scripts.Render("~/bundles/jquery", "~/bundles/jquerymobile")
    <script>
      $(document).ready(function () {
        $.mobile.ajaxEnabled = false; 
      });
    </script>
  </head> 
  <body> 

    <div data-role="page" data-theme="a">
      @Html.Partial("_ViewSwitcher")

      <div data-role="header">
        <h1>@ViewBag.Title</h1>
      </div>

      <div data-role="content">
        @RenderSection("featured", false)
        @RenderBody()       
      </div>

    </div>
    @RenderSection("Scripts", false)
  </body>
</html>

我们新的布局包括熟悉的元素,如视口meta标签。还要注意,有些元素虽然相似,但也有变化。例如,样式和脚本包现在包括在BundleMobileConfig.cs文件中标识的 jQuery Mobile 样式和脚本。

我们手机应用的其余变化都是通过 jQuery Mobile 的魔力完成的。一些变化是由标记提示触发的,例如data-*属性。其他更改,如触摸友好控件,由框架自动提供。让我们再深入一点,好吗?

数据-角色和数据属性

当我们在第 12 章为移动设备设计你的应用中了解到的数据属性(data-*)时,我们了解到它们真的是句法糖。它们可以用于存储关于 HTML 元素的附加信息,但对该元素的呈现或显示没有影响。这个功能是 jQuery 逐步增强的关键。它使用这个语义标记将标准的 HTML 元素转换成 jQuery Mobile 小部件。具体来说,jQuery Mobile 查找具有data-role属性的 HTML 元素,并尝试将它们转换为属性值中标识的 jQuery Mobile 小部件。这种转换包括控件主题的应用和小部件特定的行为。

在我们的新布局中,我们使用data-role属性将三个标准 HTML 元素标识为 jQuery Mobile 小部件:pageheadercontent。其他可用的小部件类型包括按钮、列表视图、滑块、复选框、单选按钮、站点导航等。该列表非常详尽,但在 jQuery Mobile 网站上有完整的详细信息。

我们将很快看到,每个特定的小部件也支持一组data-*数据属性,这些属性定义了data-role中标识的小部件的行为。例如,我们可以将数据属性data-fixed应用于headerdata-rolediv。该属性的应用将防止标题与页面内容的其余部分一起滚动。

是的,虽然您可以在 JavaScript 中动态声明小部件,但是 jQuery Mobile 真正的强大之处在于语义标记。

形态元素

除了到使用语义标记识别小部件,jQuery Mobile 还免费给了我们几样东西。开箱即用,它将把我们的标准 HTML 表单元素转换成触摸友好的小部件。为此,jQuery Mobile 库解析我们应用的 DOM,并将 jQuery Mobile CSS 包中定义的新样式应用到它找到的表单元素中。我们不需要以data-roles的形式提供任何提示,我们只需要将元素封装在form标签中,并确保每个输入字段都有相关的标签。

如果处理得当,外观的变化会非常显著,如我们的登录屏幕所示:

Form elements

我们的登录页面在我们的输入上有漂亮的圆角。用于记住我们的用户登录的复选框现在是触摸友好的,并且横跨屏幕宽度。使用 BrewHow 凭据登录或使用谷歌登录的按钮也进行了修改,以便在移动设备上更容易按下。所有这些都是免费的,只要我们维护有效的 HTML5 标记。

主题

如果你曾经使用过 jQuery 用户界面,你可能对它提供的广泛主题化功能很熟悉。在这方面,jQuery Mobile 步其更老的兄弟的后尘。它对主题化有广泛的支持,总共支持 26 个主题。每个主题都被认为是字母表中的一个字母。

要应用特定主题,只需将data-theme数据属性添加或更改到用于识别主题的字母表的字母中。除了页眉和页脚之外,每个小部件都继承其父部件的主题,除非明确分配了不同的主题。页眉和页脚默认为主题a,除非明确配置为使用不同的主题。

移动模板的默认布局使用页面的数据角色在元素上设置主题:

<div data-role="page" data-theme="a">

以下是应用了主题“e”的登录页面:

Themes

这是通过简单地将data-theme属性的值从a更改为e来完成的。

$。可动的

除了改变应用的外观之外,jQuery Mobile 还将使用其他渐进式增强技术来尝试让移动应用尽可能地具有设备原生的感觉。为了实现这一点,jQuery Mobile 将尝试使用 AJAX 技术异步处理所有内容请求。一旦检索到内容,该框架就采用诸如滑动内容等过渡来更好地模仿原生应用。

虽然这种行为确实为用户提供了更好的体验,但它改变了人们可能习惯于看到的由 jQuery 或其他库触发的事件的顺序和存在。如果您熟悉这种行为并且正在开发新的应用,那么这种更改可能不会有问题。然而,如果你正在将现有的应用转换为更加移动友好的应用,正在利用第三方库来挂钩某些页面级别的事件,如document.ready,或者两者的结合,这种变化是显著的。如果你没有意识到这种行为,它可能是无法调试的。

jQuery Mobile 库的作者知道对一个典型的应用事件生命周期的更改会非常不和谐,所以他们为我们提供了$.mobile对象,我们可以用它来覆盖一些全局行为。检查我们的新布局页面显示 NuGet jQuery Mobile 包的包已经关闭了这些转换,知道它们可能会给我们带来问题,直到我们熟悉库的复杂性:

<script>
  $(document).ready(function () {
    $.mobile.ajaxEnabled = false; 
  });
</script>

jQuery Mobile 还支持mobileinit等新事件。虽然我们的应用将通过在document.ready处理程序中应用这种特定的覆盖行为来运行良好,但是应该注意的是,jQuery Mobile 将在加载后立即开始应用标记增强,这可能早在document.ready事件触发之前。当 jQuery Mobile 本身启动时mobileinit事件将会触发,并且对全局行为的任何覆盖都应该真正应用于响应此事件通知。

视图切换器

虽然不是 jQuery Mobile 的部分,但 jQuery Mobile 布局附带了一个非常有用的部分控件,可用于在我们应用的桌面和移动版本之间切换。视图切换器分为用于嵌入视图的部分视图(_ViewSwitcher.chstml)和用于处理在应用版本之间切换的请求的控制器(ViewSwitcherController):

@Html.Partial("_ViewSwitcher")

局部控制本身相当简单;它将两个值传递给ViewSwitcherController。第一个值表示是否使用网站的移动版本。第二个值是用户在处理完请求后应该返回的网址。为了完成覆盖,控制器利用了 ASP.NET MVC 4 中一种新的扩展方法,命名为SetOverriddenBrowser:

public RedirectResult SwitchView(bool mobile, string returnUrl) {
  if (Request.Browser.IsMobileDevice == mobile)
    HttpContext.ClearOverriddenBrowser();
  else
    HttpContext.SetOverriddenBrowser(mobile ? BrowserOverride.Mobile : BrowserOverride.Desktop);

  return Redirect(returnUrl);
}

在内部,SetOverriddenBrowser方法在浏览器中设置一个 cookie。运行时使用该 cookie 来评估应该返回网站的哪个版本。如果存在,则使用 cookie 中的值。如果 cookie 不存在,则使用发出请求的用户代理的默认显示模式,无论是.Mobile还是我们在上一章中创建的自定义版本。

现在了解了所有这些部分是如何协同工作的,让我们将一些 jQuery Mobile 功能应用于 BrewHow 的其余部分,使其成为真正独特的移动体验。

动员酿酒

通过安装 jQuery Mobile NuGet 包,并使用新的布局模板,我们可以宣布成功动员我们的网站。它确实提供了很多现成的功能,对于大部分网站来说已经足够了。然而,我们正在为我们的用户寻找一种更加原生的体验。为了提供这一点,我们需要从日常使用的移动应用中提取一些用户界面队列,并将这些知识与我们在移动网络开发方面学到的知识结合到我们的应用中。

调整表头

我们日常使用的大多数移动应用都会为我们提供一个标题,提供一些基本功能,或者至少是应用的标题。我们的应用应该没什么不同。

在我们的标题中,我们应该包括导航,以返回到我们的应用的起始页:食谱列表。我们还想让用户知道他们正在运行 BrewHow 移动应用,所以我们可能应该包括一个简单的标题。鉴于用户实际上是一个用户,应该为他们提供某种机制来登录和注销我们的网站。最后,我们希望我们的标题包括导航的基本站点。

打开_Layout.Mobile.cshtml文件,用以下代码替换header标签的内容:

<header data-role="header">
  <a href="~/" 
    data-icon="home" 
    data-iconpos="notext">Home
  </a>
  <h1>BrewHow</h1>
  @Html.Partial("_LoginPartial")
  @if (Request.IsAuthenticated)
  {
    <nav data-role="navbar">
      <ul>
        <li>@Html.ActionLink(
          "Recipes", 
          "Index", 
          "Recipe", 
          new { area = "" }, 
          null)
        </li> 
        <li>@Html.ActionLink(
          "My Library", 
          "Index", 
          "Library", 
          new { area = "" }, 
          null)
        </li>
      </ul>
    </nav>
  }
</header>

我们需要为此功能修改的另一段代码是登录的部分视图:_LoginPartial.cshtml。我们需要在用户移动设备对我们施加的限制范围内运作。向未经身份验证的用户显示注册和登录链接,或者向当前经过身份验证的用户显示整个问候语,在大多数移动设备上是不合适的。

为此,我们需要为我们的应用创建一个新的部分登录视图。复制Shared视图文件夹中的_LoginPartial.cshtml文件,并重命名复制的版本_LoginPartial.Mobile.cshtml。用以下内容替换文件内容:

@if (Request.IsAuthenticated) {
  <a href="javascript:document.getElementById('logoutForm')
    .submit()" 
    data-role="button" 
    class="ui-btn-right">
    Log off
  </a>
  using (Html.BeginForm(
    "LogOff", 
    "Account", 
    FormMethod.Post, 
    new { id = "logoutForm" })) 
  {
    @Html.AntiForgeryToken()
  }
}
else
{
  @Html.ActionLink(
    "Log in", 
    "Login", 
    "Account", 
    new { area = "" }, 
    new { data_role="button", @class="ui-btn-right"})
}

新的登录控件将仅用于移动设备,并根据用户的认证状态显示登录和注销的单个按钮。

对于匿名用户,我们的应用现在呈现一个类似如下的标题:

Adjusting the header

对于经过身份验证的用户,导航将出现,看起来应该类似于以下内容:

Adjusting the header

让我们检查一下现在在我们的移动布局中出现的每个元素。

主页按钮

位于我们应用标题左上角的主页按钮是以下标记的渲染输出:

<a href="~/" 
  data-icon="home" 
  data-iconpos="notext">Home
</a>

这段代码只是一个锚,它有两个相关的data-*属性。jQuery Mobile 支持包附带的 CSS 文件中的几个图标。要指示 jQuery 将图标作为小部件的一部分呈现,我们只需使用data-icon数据属性,并将该值设置为支持的名称之一。

仅选择元素支持data-icon属性。虽然元素列表是有限的,但是支持数据图标属性的任何元素都支持整个图标列表。

类似地,data-iconpos数据属性指示 jQuery Mobile 框架将图标相对于控件中的任何文本放置在何处。

在撰写本文时,data-icondata-iconpos的完整值列表可在http://jquerymobile . com/demos/1 . 1 . 2/docs/button/button-icons . html上找到。

登录用户

我们认证状态的 app 外观在移动版中有了很大的简化。我们的移动应用只提供了一个登录或注销按钮,而不是向未经身份验证的用户显示登录和注册链接以及向经过身份验证的用户显示问候和注销链接。

出于几个原因,我们简化了这个过程。首先,app 的主要功能是让用户找到菜谱。虽然真正经过认证的用户可能希望从他们的库中提取配方,但他们也可以简单地打开应用从配方列表中提取配方,而不需要向网站进行认证。

简化提示的第二个原因是由于移动设备上的空间限制。根本没有空间向未经身份验证的用户显示注册和登录链接。同样,我们不能在标题中显示问候语。对于移动版本,我们完全牺牲了问候,并提供了从我们的应用登录页面进入注册过程的途径。在当今的互联世界中,大多数用户可以假设他们将被允许从登录页面创建帐户,我们在设计应用时就考虑到了这一假设。

站点导航

关于导航首先要指出的是,它只对已经登录 app 的用户可用。遵循我们早期学习的移动应用开发原则,我们只需要呈现与用户想要完成的任务相关的信息。因为只有经过身份验证的用户才能拥有可用的菜谱库,所以未经身份验证的用户在导航栏中只有一个元素。考虑到导航栏占据了用户屏幕相当大的一部分,用一个功能占用这个空间似乎相当浪费,所以当用户匿名使用我们的应用时,我们会移除导航栏。

为了将 jQuery Mobile 样式应用于布局的nav元素中包含的导航元素,我们简单地添加了一个值为navbardata-role属性。这指示 jQuery 将元素呈现为navbar小部件:

<nav data-role="navbar">

创建页脚

在 BrewHow 应用中,我们不需要在页脚中放置太多信息。我们应用的桌面版本只是显示版权声明,虽然这可能很有用,但我们的移动网站肯定不需要它,因为空间可以最好地用于其他方面。

对于 BrewHow,我们应该将视图切换器从页眉移到页脚。通过将它放在屏幕底部,我们可以将更多相关内容放在应用可见窗口的更高处。要创建 jQuery 移动页脚,我们只需将footerdata-role添加到移动布局模板的footer元素中:

<footer data-role="footer" data-position="fixed">
  @Html.Partial("_ViewSwitcher")
</footer>

您会注意到我们的footer元素中还有一个data-position数据属性。data-position属性的值为fixed。该属性和值对锁定屏幕底部任何可滚动内容之前的页脚:

Creating a footer

桌面页脚

我们应用的桌面页脚也需要调整。如果移动设备上的用户选择切换到我们应用的桌面版本,他们可能需要返回到移动视图的能力。做出这种改变非常简单。我们只需将视图切换器控件添加到桌面页脚:

<footer>
  <div class="content-wrapper">
    <div class="float-left">
      <p>&copy; @DateTime.Now.Year - My ASP.NET MVC Application</p>
    </div>
  </div>
 @Html.Partial("_ViewSwitcher")
</footer>

可以看到视图切换器出现在桌面页脚,如下图所示:

Desktop footer

在对我们的应用外观进行了返工后,让我们将重点转移到调整内容上。

配置内容

我们 app 的首要目的是收集和分享酿酒食谱。这些食谱是我们应用的内容驱动因素。因此,我们需要让他们在我们的移动网络应用中完全可访问。为此,我们需要对内容的布局方式进行一些调整。

食谱列表

配方列表仍显示为表格数据。这样做的问题是,表格显示通常与本地移动应用无关。此外,当我们以垂直方向在屏幕上显示多列时,大部分数据将被包装,或者在支持 jQuery Mobile 的应用中,被相邻的列遮挡。我们需要将表格转换为更适合移动设备显示的内容。

jQuery 移动列表视图

jQuery Mobile 提供了一个 listview 小部件,我们可以用它来显示我们应用中包含的食谱。正如你现在可能已经猜到的,它是通过使用data-role数据属性定义的。当应用listviewdata-role值时,我们可以将标准的 HTML 列表转换为 jQuery Mobile listview 小部件。以下为RecipeController修改后的手机Index视图标注:

@model BrewHow
  .ViewModels
  .ITypedPagedResult
  <BrewHow.ViewModels.RecipeDisplayViewModel>
@{
  ViewBag.Title = ViewBag.Title ?? "Recipes";
}
<ul data-role="listview">
@foreach (var item in Model)
{
  <li>
    @Html.ActionLink(item.Name, "Details", new 
    { 
      id=item.RecipeId, 
      slug=item.Slug 
    })
  </li>
}
</ul>
<p>
  @{ Html.RenderPartial("PagingPartial", Model); }
</p>
<p>
  @if (Request.IsAuthenticated) {
    @Html.ActionLink("Create Recipe", "Create")
  }
</p>

这当然是一个改进,但是页面上显示的信息仍然紧密地组合在一起,即使在为移除样式和向库中添加食谱的能力做出调整之后:

The jQuery Mobile listview

因为我们正在开发一个移动应用,我们需要考虑用户的输入法:他们的手指。为了更好地适应触摸输入,我们需要将内容进一步分离,以便为用户提供更好的体验。

我们可以从开始,通过插入列表,将其与导航和创建链接分开来改善体验。要插入列表,我们需要将另一个数据属性(称为data-inset数据属性)应用于ul元素,并为该属性提供一个值true:

<ul data-role="listview" data-inset="true">

插入列表会导致我们的外观和感觉发生以下变化:

The jQuery Mobile listview

不幸的是,我们的内容现在看起来有点稀疏,所以让我们来解决这个问题。

扩展列表视图内容

当我们将显示从表格转换为 jQuery Mobile listview 小部件时,我们从配方列表中删除了配方样式。我们实际上有能力在不严重影响显示的情况下将信息添加回列表。

在 jQuery Mobile listview 小部件中,listview 第一个锚点中包含的所有内容都被设计为 listview 项目。如果我们调整列表项的内容以包含具有多个内容项的单个锚点,我们可以成功地将配方样式重新插入到内容中:

@foreach (var item in Model)
{
  <li>
    <a href="@Url.Action("Details", new 
    { 
      id = item.RecipeId, 
      slug=item.Slug 
    })">
      <h3>@Html.DisplayFor(modelItem => item.Name)</h3>
      <p>
        @Html.DisplayFor(modelItem => item.Style)
      </p>
    </a>
  </li>
}

类型

补充列表视图内容

还有其他方法可以将附加内容放入 listview。jQuery Mobile 附带了几个额外的样式,其中一些可以用来控制信息在列表视图中的显示方式。如果我们希望显示右对齐的内容,我们可以将ui-li-aside样式应用于包含在列表项目的锚点内的内容。如果我们希望计数气泡出现在列表项名称的右侧,我们也可以应用ui-li-count样式。

我们已经从稀疏变成相当冗长了。增加更多的内容是可以的,只要它是相关的和有意义的。不过,我们确实需要为用户提供过滤的能力。

列表视图过滤器

当显示大量数据时,可能需要为用户提供过滤内容的机制。listview 小部件通过使用data-filter数据属性来提供这一功能。要启用它,我们只需要将属性添加到 listview 小部件中,并将其值设置为 true。我们甚至可以调整占位符文本,即使用数据属性给用户的提示:

<ul 
  data-role="listview" 
  data-inset="true" 
 data-filter="true" 
 data-filter-placeholder="Search for a recipe...">

此功能仅搜索列表中当前显示的内容。我们将在这本书的最后一章讨论搜索所有的食谱。至于创造食谱的能力,我们需要在那里做一个小调整。

按钮

我们需要对配方列表页面进行的最后一个调整是将创建新配方的链接转换成更友好的内容。jQuery Mobile 可以通过将链接转换为触摸友好的按钮小部件,你猜对了,data-role数据属性:

@Html.ActionLink(
  "Create Recipe", 
  "Create", 
  null, 
 new { data_role = "button" }
)

当向包含破折号的动作链接提供 HTML 属性时,我们必须使用下划线将它们提供给动作链接 HTML 帮助器。运行时足够智能,当它呈现到输出流的链接时,可以将下划线转换为破折号。

我们的食谱列表视图是完整的,但是如果用户不能确定他们实际上在看哪个屏幕,它就相当没有意义了。

导航提示

在查看食谱列表时,不清楚我们是在查看全球食谱列表,还是在查看我们的库中的食谱。我们需要提供一个导航提示来向用户阐明这一点。要在移动布局中突出显示我们的navbar小部件中的一个按钮,我们需要将ui-btn-active样式应用于活动导航元素:

<li>@Html.ActionLink(
  "Recipes", 
  "Index", 
  "Recipe", 
  new { area = "" }, 
 new { @class="ui-btn-active" })
</li>

我们所有工作的输出如下图所示:

Navigation hints

食谱列表现在看起来、感觉和功能都像一个原生的移动应用。是时候关注细节页面了。

配方详情

当在 BrewHow 手机应用中显示菜谱的详细信息时,我们需要维护几个功能:返回上一页的能力、添加和查看菜谱评论的能力以及编辑菜谱的能力。我们希望以 jQuery Mobile 支持的方式完成所有这些工作。我们当前对RecipeControllerDetail视图充满了样式和布局元素,它们是特定于我们旧的移动模板附带的 CSS 的。我们需要对RecipeControllerDetail视图做一些实质性的清理工作。

我们将用于手机应用的新视图的相关代码显示如下:

<article id="recipe-detail">
  <header>
    <h2>
      <a href="#" 
        data-role="button" 
        data-icon="back" 
        data-iconpos="notext" 
        data-inline="true"
        onclick="javascript:history.go(-1)">
      Back
      </a>
      @Model.Name
    </h2>
  </header>
  <section>
    <header>
      <strong>
        @Html.DisplayNameFor(model =>model.PercentAlcoholByVolume)
      </strong>
    </header>
    @Html.DisplayFor(model => model.PercentAlcoholByVolume)
  </section>
  <br>
  <!-- Other section fields omitted -->
</article>
<div>
  @if (Request.IsAuthenticated) 
  { 
    @* Buttons to add to library and edit recipe *@
  }
  @Html.ActionLink("View Reviews", 
    "Index", 
    new 
    { 
    area = "Review", 
    id = Model.RecipeId 
    }, 
    new { data_role = "button" })
  @if (Request.IsAuthenticated) 
  { 
    @Html.ActionLink(
    "Add Review", 
    "Create", 
    new 
    { 
    area = "Review", 
    id = Model.RecipeId 
    }, 
    new { data_role = "button" }); 
  }
</div>

让我们研究一下这个新视图如何满足我们在本节开始时确定的用户需求。

后退按钮

创建后退按钮使用的技术与我们在标题中创建主页按钮时使用的技术相同。唯一值得注意的重大变化是data-icon数据属性的值和我们附加到锚点的内联 JavaScript:

<a href="#" 
  data-role="button" 
  data-icon="back" 
  data-iconpos="notext" 
  data-inline="true" 
  onclick="javascript:history.go(-1)">Back</a>

我们可以使用 jQuery 来提供后退按钮功能,而不是内联 JavaScript,如果我们正在编写生产就绪代码,我们可能应该这样做。如果您选择在自己的生产移动应用中使用部分内容,请进行相应调整。

动作按钮

用户在查看配方时可能采取的操作已转换为按钮。这些操作包括向库中添加配方、编辑配方、查看配方或查看配方的所有评论的能力。我们还将用户界面中的库、配方编辑和审查功能的访问权限限制为只有经过身份验证的用户。这些限制也在我们的控制人员中实施:

@if (Request.IsAuthenticated) 
{ 
  @Html.ActionLink("Edit Recipe", "Edit", new 
  { 
    id = Model.RecipeId, 
    slug = Model.Slug 
  }, 
  new { data_role = "button" }); 
}
@Html.ActionLink("View Reviews", "Index", new 
{ 
  area = "Review", 
  id = Model.RecipeId 
}, 
new { data_role = "button" })
@if (Request.IsAuthenticated) 
{ 
  @Html.ActionLink("Add Review", "Create", new 
  { 
    area = "Review", 
    id = Model.RecipeId 
  }, 
  new { data_role = "button" }); 
}

所有这些变化导致了新的和改进的配方细节屏幕,如下图所示:

 buttons

下一站:从移动设备编辑食谱的能力。

配方编辑

我们的手机应用目前没有编辑食谱的手机专用视图。虽然我们的大多数用户确实会使用移动应用来快速检索信息,但也会有用户希望通过他们的移动设备为酿酒做出贡献。因此,我们应该让编辑体验尽可能愉快。

让我们从开始,在Views文件夹中为我们的配方控制器制作一份Edit.cshtml文件的副本,并将新副本重命名为Edit.Mobile.cshtml。重命名完成后,打开新的移动编辑视图。在大多数情况下,我们的移动编辑视图在大多数移动设备上都是可用的。不过,我们可以做得更好。

与详细视图一样,我们需要支持用户返回上一页的愿望。添加后退按钮几乎与详图中定义的练习完全相同:

<a href="#" 
  data-role="button" 
  data-icon="back" 
  data-iconpos="notext" 
  data-inline="true"
  onclick="javascript:history.go(-1)">Back</a>

我们需要做的更大的改变是从我们的视野中移除fieldsetlegend元素。它们将被fieldcontain风格所取代。

现场容器

fieldcontain样式是在 jQuery Mobile CSS 中定义的样式。当布局输入表单时,它用于支持响应设计。这个 CSS 类应用于封装标签和输入元素的容器。它试图以最适合设备的方式显示标签和输入。

当处理异常低的分辨率时,fieldcontain样式将强制浏览器在输入上方布局标签。但是,如果呈现页面的设备有足够的水平分辨率来显示输入旁边的标签,那么它就会这样做。

要将fieldcontain样式应用到我们的移动编辑视图中,我们需要将相关的标签和输入移动到一个容器中。在我们看来,容器是一个 div。然后,我们需要对每个 div 应用fieldcontain样式,包装一个标签和字段对。

举例来说:

<div class="editor-label">
  @Html.LabelFor(model => model.Instructions)
</div>
<div class="editor-field">
  @Html.EditorFor(model => model.Instructions)
  @Html.ValidationMessageFor(model => model.Instructions)
</div>

前面的代码如下:

<div class="fieldcontain">
  @Html.LabelFor(model => model.Instructions)
  @Html.EditorFor(model => model.Instructions)
  @Html.ValidationMessageFor(model => model.Instructions)
</div>

新的编辑视图如下图所示:

Fieldcontain

评论

对我们新手机应用流量影响最大的是评论的进入和查看。在我们应用的桌面版和响应设计版本中,评论会显示配方的详细信息。在移动领域,这可能不是一个好主意。

如上所述,当我们为移动设备编写应用时,我们需要确保只向用户返回请求的信息。附加信息的返回意味着用户将不得不等待更长的时间,以使其显示在他们的设备上,并且将不得不过滤呈现给他们的所有信息,以便找到他们实际上正在寻找的信息。

我们仍然应该向我们的用户提供对评论的访问,但是我们应该只在用户明确要求时才返回评论。这就要求我们把评论移到新的一页。当我们在本章的食谱详细视图中添加查看评论添加评论按钮时,我们做了这个假设。如果我们去点击按钮查看评论,你会看到有些东西有点不对劲:

Reviews

首先,也是最明显的,我们的评论视图没有与之相关的样式。它缺少了 jQuery Mobile 风格和与我们的桌面视图相关的风格。

或许不太明显的是viewport meta标签的缺失。没有这个标签,浏览器就试图将一个“桌面”网站缩放到移动设备的分辨率。

缺少任何样式和viewport meta标签都提示没有任何布局应用于视图。这确实是正在发生的事情。为了理解如何操作,我们必须在Review区域的配方控制器中检查我们的Index操作方法的返回值,该操作负责返回特定配方的评论。此操作方法具有以下返回值:

return PartialView(ToListModel(reviews));

当我们第一次写这个方法的时候,我们似乎做了一个假设,我们只会将这个视图嵌入到另一个视图或布局中。因为视图会嵌入到另一个视图中,所以我们只需要将它作为局部视图返回。当然,这只会返回视图的内容,不会试图将视图嵌入到布局文件中,如_Layout.cshtml_Layout.Mobile.cshtml

因为该视图由桌面版本的应用使用,所以我们不想阻止操作返回部分视图。我们也不想仅仅为了给移动浏览器返回一个完整的视图而创建一个新的动作方法。然后,我们的视图需要知道调用哪个动作以及何时调用它。我们需要做的是让控制器负责根据请求者决定使用哪个视图。幸运的是,ASP.NET MVC 4 框架为我们提供了这样一种机制。

动产副

要返回移动站点的完整视图,我们只需确定是否有移动设备发出请求。我们可以使用当前RequestBrowser.IsMobileDevice属性来实现:

if (Request.Browser.IsMobileDevice)
{
  return View(reviewList);
}

该操作现在将返回移动设备的完整视图,以及模拟桌面浏览器的桌面设备或移动浏览器的部分视图。但是即使返回完整视图也只能解决我们的部分问题。即使我们的工作是返回一个完整的视图,响应看起来和以前没有什么不同。运行时没有与我们的视图相关联的布局。

_ViewStart.cshtml文件从我们项目的Views文件夹的根目录复制到项目的Areas/Review/Views文件夹。这将通知运行时对此区域使用与我们应用其他区域相同的布局。现在,如果我们在评论区有一些实际的移动视图。

移动视图

我们仍然需要创建特定于移动设备的视图用于查看列表和编辑,但是我们现在可以利用视图命名约定(.Mobile)而不是依赖于视图中的复杂逻辑。在Review区域的Recipe视图文件夹中,复制Index.cshtml并将复制的文件重命名为Index.Mobile.cshtml。以下是我们新的手机评论列表视图的代码:

<h2>
  <a href="#" 
    data-role="button" 
    data-icon="back" 
    data-iconpos="notext" 
    data-inline="true" 
    onclick="javascript:history.go(-1)">
    Back</a>
@ViewBag.RecipeName
</h2>

<ul data-role="listview" data-inset="true">
  @foreach (var item in Model)
  {
    <li>
      <p style="white-space: normal;">
        @Html.DisplayFor(modelItem => item.Comment)
      </p>
      <p class="ui-li-count">@item.Rating</p>
    </li>
  }
</ul>
<p>
  @if (Request.IsAuthenticated) {
    @Html.ActionLink("Add Review", "Create", new {area = "Review", id = ViewBag.RecipeId, name=ViewBag.RecipeName }, new { data_role = "button" })
  }
</p>

我们的评级为ui-li-count级。这将显示为计数气泡,我们将很快看到。

我们目前正在返回我们的视图需要的一切,除了食谱的名称。我们的观点是在ViewBag.RecipeName中寻找这个值。我们需要向我们的用户提供这个密钥,因为没有它,我们的用户将没有阅读评论的上下文。也就是说,他们将无法辨别审查属于哪个食谱。这不是桌面版本的审查列表的问题,因为审查与配方详细信息显示在同一页面上。鉴于我们的移动应用并非如此,我们需要解决这个问题。

这样做的第一步是向ViewBag添加一个RecipeName属性。我们可以在为配方审查列表调用的Index操作方法中很容易地做到这一点。填充它会带来问题。

我们不希望审查控制器加载配方,如果他们没有真正的需要这样做,并且,考虑到这种情况,这是没有保证的。如果下游不使用信息,我们也不想开始缓存信息。在这一点上,我们最好的选择似乎是在必要时向评审控制者的行动方法提供配方的名称。我们需要修改Index操作方法,将配方的名称作为可选参数(它是一个字符串,因此默认情况下是可空的,因此是可选的):

public ActionResult Index(int id, string name)
{
  var reviews = this
    ._reviewRepository
    .GetReviewsForRecipe(id);

  this.ViewBag.RecipeId = id;
  this.ViewBag.RecipeName = name;

我们新的操作现在支持通过ViewBag.RecipeName属性检索名称并将名称传递给下游视图。我们现在需要将名称传递给操作方法。这也相当简单。我们只需在构建行动链接时将其添加到路由数据中,如下所示:

@Html.ActionLink(
  "View Reviews", 
  "Index", 
  new 
  { 
    area = "Review", 
    id = Model.RecipeId, 
 name=Model.Name 
  }, 
  new 
  { 
    data_role = "button" 
  })

当选择路由时,添加到操作链接的路由集合中的任何不匹配的数据都将作为查询字符串参数追加到构造的链接中。我们可以通过构建我们的应用并点击配方详细信息页面上的查看评论链接来验证这一工作。如果一切顺利,我们应该看到以下情况:

Mobile views

你知道些什么?成功了。

总结

本章向您介绍了 jQuery Mobile 及其在生成网络应用方面的用途,这些应用的功能就像是设备的原生应用一样。通过渐进式增强,jQuery Mobile 可以抓取任何页面,并将其转化为真正的移动用户体验。

这一章只是触及了 jQuery Mobile 的表面,不可否认的是,它在很少的几页中提供了很多信息。对于一些真正有趣的想法、工具、技术和演示,包括对浏览器定位和滑动事件的支持,我建议您访问位于jquerymobile.com的 jQuery Mobile 网站。

说到只抓表面,决定在这本书里一章一章地放什么信息,是我做过的最困难的事情之一。我想涵盖的其他几个项目根本不适合这种尺寸的书。在下一章中,我们将看看一些没有成功的更有趣的项目,我将为您提供自己实现它们的起点。