原文:Realtime Web Apps
协议:CC BY-NC-SA 4.0
零、简介
几年前,我参加了一个名为“保持实时性”的会议这是一群深入实时世界的演示者,他们解决的问题是世界上大多数人从未听说过的。
这项技术的力量是惊人的,它已经被使用的地方的数量是相当惊人的。我想知道更多,开始使用它然后。我该如何在我自己的应用中使用这个奇妙的、神奇的新想法呢?
我坐在观众席上,参加了一个实践环节,并立即迷失了方向。一个留着胡子的害羞的小家伙拿着笔记本电脑站在讲台上,对着麦克风喃喃自语,用 Vim 以令人难以置信的速度编写代码。当我发现他正在初始化 socket.io 时,他已经完成了应用的一半。
我的情绪低落了,我开始怀疑这种令人敬畏的技术是否只保留给秘密忍者开发者的精英影子集团。如果我跟不上一个正在教这些东西的人,我怎么能靠自己建造任何东西呢?
如果你曾经问过一个真正聪明的开发人员如何做某事,你可能知道这种感觉:当某人达到一定的聪明水平时,他们有时会忘记如何与我们这些以前没有使用过这种技术的人交谈。这将我们置于一种境地,我们要么挖掘大量复杂的代码、规范和粗糙的文档,要么就放弃。
这本书旨在帮助揭开实时编码的神秘面纱,并使任何具有中等 PHP 和 JavaScript 水平的开发人员都可以使用它。如果你现在就想在真实的项目中使用这些东西,并且不需要知道如何构建 Flash polyfill 或维护 Node.js,这本书很适合你。
我们相信,虽然理论是有趣和必要的,但开发的真正令人兴奋的部分是将它投入使用并看到它变成现实。为此,本书中使用的技术易于设置,不需要您学习新的编程语言或框架;这本书基于当今一些最流行的应用、网站和内容管理系统中使用的相同网络技术。
实时应该属于含咖啡因的大众,所以拿起你的咖啡(或茶),让我们开始吧。在天气变冷之前,您将启动并运行 realtime。
一、什么是实时?
如果你在过去的一两年里一直关注着网络发展的趋势,毫无疑问你已经看到了术语实时被抛来抛去。但是什么是实时呢?它与当前的网络技术有什么不同,为什么我们要费心去使用它?
*为了更好地理解实时意味着什么,以及它如何改变我们所知的互联网,让我们看看它试图解决的问题的历史:我们如何在客户端影响我们的 web 应用的状态,而不需要用户采取任何行动?
媒体的演变
实话实说:说到信息,我们有一种想先听到消息的欲望。这种渴望可以归因于天生的求知欲,第一个知道的人可能会给我们带来的机会,或者仅仅是因为这意味着我们可以成为所有八卦的一方。在某些情况下,比起关心新闻内容,我们甚至更重视第一个得到新闻。(巧合的是,这也是潮人存在的全部原因。)我们首先想知道,这意味着我们想在这个信息可用的时候知道。
图 1-1。随着某些类型的信息变得司空见惯,其感知价值往往会降低
这种对保持现状的不懈追求让我们走到了今天:我们不满足于洞穴壁画或手写的大部头作品;印刷机给了我们书和传单,但我们还想要更多;报纸和其他期刊每天早上都给我们提供最新消息,但这些事情都发生在昨天。收音机和电视只能在几小时内给我们提供信息,或者——在天气好的时候——几分钟。
互联网让我们有能力与全球观众分享信息。但是这些信息仍然需要很长时间才能被发现,我们依靠电子邮件和论坛来传播信息。谷歌改变了这一切,让数据更容易被发现。即便如此,其页面索引的速度意味着我们仍然需要等待我们的数据通过搜索被发现。“实时博客”的发明意味着,如果我们知道去哪里找,我们就可以收到频繁的更新,而那些目的地通常都是知名的媒体品牌。
社交媒体加大了赌注,创造了一个全球网络,任何人都可以随时分享新闻。在 2011 年埃及革命等事件中,Twitter 等服务是我们的主要信息来源。然而,第一个实时网络游戏规则的改变者是,有史以来第一次,新信息发布的瞬间也可以通过搜索被发现。这开始证明在互联网上即时获取新信息的价值,增加了用户对“实时内容”的期望,甚至导致知名技术评论员罗伯特·斯考伯(Robert Scoble)质疑“实时网络是否是对谷歌的威胁。” 2
社交媒体平台正在转变为实时交流平台。你一发布状态更新,就会收到一个或多个用户的回复。这种快速、交互式的反馈对我们大多数人来说是非常新鲜的,除了我们这些玩基于 Flash 的游戏的人,他们习惯于只提供相对静态的单用户体验的互联网应用。这一新的多用户交互功能带来了更具吸引力和吸引力的用户体验。
媒体已经从提供延迟和静态的内容发展到具有更丰富、实时和互动的潜力。用户看到了这些体验,他们现在对互联网应用的期望大大提高了。
尽管互联网和社交媒体展示了所有这些即时的满足感,但许多来源仍然没有将我们的新闻作为直播内容提供给我们,或者为我们提供互动和迷人的体验。为什么不呢?
网站,而不是网络应用
互联网传统上用于共享静态内容。一个网站仅仅是属于一个集合的静态实体的结构。一个网站的主要焦点是展示它的内容,而“内容为王” 3 的理念并没有改变多少。即使当我们提出创建“动态内容”的技术时,我们实际上的意思是,我们的服务器现在可以基于一组不同但定义好的参数和值动态地生成静态内容。
我们用来查看互联网上的实体的应用,即 Web 浏览器,自然专注于确保它满足日常需求:下载和呈现 HTML 和图像,并了解如何跟踪链接——这在最初就足够了。
以同样的方式,媒体的形式被推动着发展,我们的网站也是如此。我们希望我们的网站看起来更好,所以我们引入了 CSS。我们希望他们对用户的输入反应更快(你能相信你曾经可以对 DHTML 库收费吗?例如,下拉菜单),于是 JavaScript 出现了(让我们忘记 VBScript 曾经存在过)。这些技术增强了网络浏览器的功能,但主要是让我们增强网站上的页面。
一些先驱超越了静态网站,开始考虑动态 web 应用。对于 web 应用,焦点从服务器转移到了客户端。客户必须做更多的工作;它动态地检索和加载内容,根据用户反馈改变用户界面(UI ),并且 UI 以我们传统上与桌面应用相关联的方式呈现。很少关注页面重载和页面的一般概念。内容也变得不那么基于文本,我们开始在 web 应用中实现更具视觉吸引力和交互性的数据表示。
http hack
随着越来越多的人(我们开发人员是先锋)开始构建 web 应用,对 web 浏览器的需求也在增加。性能成了问题;不仅仅是 web 浏览器应用,还有运行浏览器的机器。那些真正推动 web 技术和 web 应用边界的人也遇到了一个巨大的绊脚石:HTTP。4
HTTP 是一种协议,在这种协议中,客户端发出数据请求并接收响应。然而,一些 web 应用开始要求信息从服务器发送到客户机。所以我们不得不开始入侵!黑客攻击会导致非标准化和复杂的解决方案。将跨 web 浏览器的特性支持状态抛入其中,您可以想象这个问题的一些解决方案的复杂性(我们将在后面讨论其中的一些)。
它采用了 Twitter 和脸书等广受欢迎的解决方案,来证明实时网络技术带来的好处和体验需求。在需求的驱动下,这导致了实时网络技术的巨大进步和可用性。
但是首先:“实时”实际上意味着什么?
术语实时指的是事件发生和我们意识到它之间的及时性。一个事件发生和交付之间的时间测量确实取决于该事件。如果事件是把你的脚放在汽车刹车上,那么你的脚放下和刹车之间的时间必须绝对最小。然而,如果事件是在足球论坛中发送聊天消息,并显示给其他用户,几秒钟不太可能有很大的不同。最终,事件需要在足够短的时间内交付,以便该事件仍然相关;在上下文中仍然有意义。想象一下被扇了一巴掌:在巴掌的冲击和疼痛的记录之间没有延迟。这是实时的。如果有延误,那将会非常混乱。
然而,添加任何实时体验的能力最初都不是那么容易的。但是开发人员并不容易被打败,他们已经想出了聪明的变通办法和“窍门”来解决服务器和客户机之间的通信故障。
注意这里已经省略了一些最早的与服务器建立双向通信的方法,因为它们不常被使用。
创建交互式、快速动态网页应用的网页开发技术
随着 JavaScript 开始变得更加流行,开发人员开始利用 XMLHttpRequest 对象 5 异步发送 HTTP 请求*,或者不需要重新加载当前页面。这叫做 AJAX ,或者异步 JavaScript 和 XML 。*
*这种方法非常适合向 web 应用添加用户触发的功能,因此通常仍然依赖于浏览器中的事件,例如单击,因此在保持内容最新的过程中并没有真正解决任何问题。
投票
在 AJAX 站稳脚跟之后,尝试将浏览器事件从等式中剔除并自动获取新信息的过程是一个短暂的跳跃。开发人员使用类似 JavaScript setInterval()
函数的东西设置一个刷新间隔,每隔 n 秒检查一次更新。
图 1-2。轮询经常发送 HTTP 请求来检查新信息
为了更好地理解这有多浪费,您可以将这种通信想象成客户端和服务器之间的对话:
CLIENT: Hi! Can I have some data?
SERVER: Sure. Here you go!
[time passes]
CLIENT: Do you have any new data for me?
SERVER: No.
[time passes]
CLIENT: Do you have any new data for me?
SERVER: No.
[time passes]
CLIENT: Do you have any new data for me?
SERVER: No.
[time passes]
CLIENT: Do you have any new data for me?
SERVER: I do! Here you go!
就像现实生活一样,客户机和服务器之间这样的对话既烦人又没什么成效。
尽管这个轮询解决方案绝对是一个开始,但它也有缺点。最值得注意的是,它创建了许多空请求,这对一个应用造成了许多不必要的开销。这种开销可能会妨碍应用的良好扩展:如果一个应用每秒轮询一次新数据,并且 10 万用户同时使用该应用,则每分钟有 600 万个请求。
如果考虑到每个 HTTP 请求的开销——在彼得·吕贝尔斯的测试中,每个请求/响应总计 871 字节6——来回发送大量额外信息只是为了发现服务器上没有发生任何新的事情。
HTTP 长轮询
实时进化链的下一步是 HTTP 长轮询 ,这是在设定的时间段内打开一个 HTTP 请求来监听服务器响应的实践。如果有新数据,服务器会发送并关闭请求;否则,在达到间隔限制后,请求将被关闭,并将打开一个新的请求。
图 1-3。HTTP 长轮询使 HTTP 请求在一段时间内保持打开状态,以检查更新
与标准轮询相比,这要高效得多。它节省了开销,减少了应用发送的请求数量。客户端和服务器的对话如下所示:
CLIENT: Hi! Can I have some data?
SERVER: Sure. Here you go!
CLIENT: Thanks! I'm ready for more, if it comes in.
[time passes]
SERVER: I have new data for you! Here you go!
CLIENT: Thanks! I'm ready for more, if it comes in.
好多了。这种方法提供了一种机制,通过这种机制,服务器可以提醒客户端有新数据*,而不需要客户端*方面的任何动作。
如果需要客户机/服务器双向通信,就可以看出 HTTP 长轮询的一个主要问题。一旦长轮询 HTTP 连接打开,客户机与服务器通信的唯一方法就是发出另一个 HTTP 请求。这可能导致使用双倍的资源:一个用于服务器到客户端的消息,另一个用于客户端到服务器的消息。这种情况的确切影响实际上取决于双向交流的数量;客户机和服务器之间的对话越多,资源消耗就越大。
这种方法的另一个问题是,在长轮询请求之间有一小段时间,客户机上的数据可能与服务器上的数据不同步。只有当连接重新建立后,客户端才能检查是否有新的数据可用。这其中的负面影响确实要看数据,但如果数据是高度时效性的,那肯定不是好事。
HTTP 流
HTTP streaming 非常类似于 HTTP 长轮询,除了当新数据可用时或者在给定的时间间隔连接不会关闭。相反,新数据通过保持打开的现有连接推送。
客户端和服务器之间的对话现在变成如下所示:
CLIENT: Hi! Can I have some data? And please let me know whenever any new data comes along.
SERVER: Sure. Here you go!
[time passes]
SERVER: I have new data for you! Here you go!
[time passes]
SERVER: I have more new data for you! Here you go!
这种解决方案的好处是,客户端和服务器之间的连接是持久的,因此一旦有新数据可用,就可以将其发送到客户端,之后的任何新数据也通过同一连接发送。这确保了服务器和客户端保持同步。
HTTP 流仍然不能提供双向通信,因此存在潜在的资源问题,需要使用第二个连接进行客户端到服务器的通信。
HTTP 流方法的一个大问题是它在不同的浏览器中实现方式的不一致性。在基于 Gecko 的浏览器中,可以使用多部分替换头,指示浏览器用更新的内容替换上次接收的旧内容。在其他浏览器中,这是不可能的,因此响应缓冲区会不断增长,直到没有其他选择,只能关闭并重新打开到服务器的连接。
Web 浏览器中基于 HTTP 的解决方案的其他问题
使用多个连接进行双向通信的要求和跨浏览器实现的差异并不是基于 HTTP 的解决方案的唯一问题。浏览器还限制了来自网页的 HTTP 请求的目的地以及可以建立的连接数。
在网页中运行的 JavaScript 向服务器发出请求的能力长期以来被限制为只允许向同一个域发出请求。 7 例如,如果网页是www.example.com/index.html
,JavaScript 只能向www.example.com
上的资源发出请求,或者通过操纵 JavaScript 中document.domain
的值,可以向任何 example.com 子域发出请求,比如 sub.example.com。这种限制是由浏览器供应商出于安全原因设置的,但与许多安全限制一样,它阻止了向其他域发出请求的合法用例。跨来源资源共享(CORS)解决了提出这些请求的需求。 8 CORS 有很好的浏览器支持, 9 但是有明显的老浏览器考虑。
对可以建立的连接数的限制是针对每个域实施的,例如对www.example.com的请求。在早期的浏览器中,这意味着同一个域只能有两个连接。对于基于 HTTP 的解决方案,这意味着您只能打开使用 HTTP 长轮询或流的 web 应用或网站的一个页面。如果您尝试打开第二个页面,连接将会失败。解决这个问题的方法是将许多子域映射回同一个服务器。连接限制在现代浏览器中仍然是强制的,但是现在允许的连接数要合理得多。 10
术语注释
有许多不同的术语被用来描述基于 HTTP 的实时 web 解决方案。其中大部分都是总括性的术语,涵盖了开发人员用来通过 HTTP 实现服务器到客户端通信的各种方法。
这些术语包括 Comet 、HTTP Server Push 和 AJAX Push 等等。问题是,尽管其中一些术语有非常具体的定义和技术——尤其是 Comet——它们对不同的人有不同的含义。
本书的观点是, Comet 是一个术语,用来定义应用结构中的范例:即模拟使用两个 HTTP 连接的服务器和客户机之间的双向通信。 11
图 1-4。Comet 范例意味着客户机和服务器之间的双向通信 11
Comet 应用可以在任何时候向客户端传送数据,而不仅仅是响应用户输入。数据通过之前打开的单个连接传递。
—亚历克斯·罗素
甚至有人认为 HTML5 WebSockets 等新技术是 Comet 范式的一部分,而不是它的替代品。然而,亚历克斯·罗素(他创造了这个术语)现在已经证实,我们应该把 Comet 看作是旧的基于 HTTP 的黑客的总称,并且用一种叫做 WebSockets 的新技术展望未来。 12
Web Sockets 是彗星的一种形式吗?或者彗星只是 HTTP 黑客?我倾向于后一种定义。这句话和那些黑客也许应该一起消失在夕阳下。就我而言,欢迎我们的非 HTTP 实时霸主。在某种程度上,我们可以忘记旧的浏览器,我们都可以使用“网络插座”,不再需要任何特定的保护伞。
—亚历克斯·罗素
解决方案:WebSockets
毫无疑问,你已经听到人们谈论 HTML5 和它所有的新功能。其中两个新特性直接应用于实时 web 技术和客户机服务器通信——这是一个极好的结果,表明 web 标准组织和浏览器供应商确实听取了我们的反馈。
服务器发送的事件和 event source API13是 HTTP 流解决方案的形式化,但还有一个更令人兴奋的解决方案。
你可能听过一两次 WebSockets 这个术语。如果你以前从未真正研究过实时,WebSockets 可能不会出现在你的雷达上,除非它是谈论 HTML5 所有伟大新特性的文章中的一个流行词。WebSockets 如此令人兴奋的原因是,它们提供了一种标准化的方式来实现我们多年来一直试图通过 Comet hacks 实现的目标。这意味着我们现在可以通过单个连接实现客户端服务器双向实时通信。它还内置了对跨域通信的支持*。*
图 1-5。Websockets 打开一个全双工连接,允许双向客户端服务器通信
WebSocket 规范是 HTML5 的一部分,这意味着 web 开发者可以在现代浏览器中使用 WebSocket 协议。14
根据 WHATWG, 15 的说法,WebSocket 协议定义了一种在 web 应用中添加实时通信的标准化方式:
WebSocket 协议支持在受控环境中运行不受信任的代码的用户代理与选择加入来自该代码的通信的远程主机之间的双向通信。为此使用的安全模型是 Web 浏览器通常使用的基于原点的安全模型。该协议由初始握手和随后的基本消息成帧组成,分层于 TCP 之上。该技术的目标是为基于浏览器的应用提供一种机制,这种机制需要与服务器进行双向通信,而不依赖于打开多个 HTTP 连接(例如,使用 XMLHttpRequest 或 s 和长轮询)。 16
广泛的 WebSocket 支持带来的最有益的影响之一是可伸缩性:因为 web socket 使用单个 TCP 连接在服务器和客户端之间进行通信,而不是多个单独的 HTTP 请求,所以开销大大减少了。
WebSocket 协议
因为使用 HTTP 无法实现全双工通信,所以 WebSocket 实际上定义了一个全新的协议,或者从客户端连接到服务器的方法。
这是通过打开一个 HTTP 请求,然后请求服务器通过发送以下报头将连接“升级”到 WebSocket 协议来实现的: 17
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin:http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
如果请求成功,服务器将返回如下所示的标头:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
这个交换被称为握手,它是建立 WebSocket 连接所必需的。一旦服务器和客户端之间成功握手,就建立了双向通信通道,客户端和服务器都可以独立地向对方发送数据。
握手后发送的数据被封装在帧中,这些帧本质上是信息块。每个帧以一个0x00
字节开始,以一个0xFF
字节结束,这意味着除了消息的大小之外,发送的每个消息只有两个字节的开销。
因此,我们已经明确表示,这对 web 开发人员来说是个好消息。但不幸的是,并非所有的独角兽和冰淇淋甜筒都是如此:和以往一样,我们将等待少数用户和公司升级到现代浏览器。我们还将等待互联网基础设施的某些部分赶上来。例如,一些代理和防火墙会阻止合法的 WebSocket 连接。然而,这并不意味着我们不能在我们的应用中使用它们。
为什么要学习实时网络技术呢?
你可能想知道为什么值得学习这些;这项技术最初可能看起来很复杂,很难支持,很难学习,而且它太新了,不重要。
事实是,实时技术已经改变了我们与网络互动的方式:如前所述,像脸书这样的社交网络正在使用实时组件;Spike TV 与 Loyalize 公司合作,允许《最致命的战士》第四季大结局的观众参与一些现场投票,从而改变了电视节目的进程; 18 谷歌已经在其几个项目中加入了实时功能,包括谷歌文档和谷歌分析。
图 1-6。谷歌分析使用实时技术来显示分析数据
如果我们希望作为 web 开发人员跟上时代,我们需要尽早接受实时技术。对我们来说幸运的是,有很多公司致力于从无聊的旧拉动式网络向全新的 holymolyawesome 实时驱动网络转变。我们所要做的就是想出一些很酷的东西用它来建造。
现在在你的应用中使用实时网络技术
虽然你可能无法开始完全依赖 WebSocket 技术来开发你的新网络应用,但是现在有越来越多的公司和项目致力于让你获得实时网络功能。他们的方法不同,从使用(喘气!)Flash 19 ,它实际上已经有了多年的套接字支持,当 WebSockets 本身不能专注于我们前面提到的基于 HTTP 的解决方案时,它可以作为一个后备。
一些选项包括 Socket.io、 20 Faye、 21 SignalR、 22 PubNub、23Realtime.co、 24 和 Pusher 25 (有关更全面的解决方案列表,请参见实时 Web 技术指南)。 26
在本书中,我们将重点介绍 Pusher 的使用。
摘要
实时就是现在正在发生的事情。使用这一功能,我们可以让客户知道新数据可用,而不会产生大量开销,这使我们可以创建应用,在更新信息可用时(而不是在用户请求更新后)为用户提供实时内容体验。更重要的是,它让我们能够构建交互式功能,为我们的应用用户提供更具吸引力的体验。这使得他们会回来买更多。
既然您已经了解了实时的来源、含义、工作方式以及它提供的好处,那么您可以开始选择工具来构建您的第一个实时 web 应用了。在下一章,我们将讨论你将用来构建应用的所有组件技术和编程语言。
1
2
3
4
5
6
7
8
9
10
11 图及引用来源:http://infrequently.org/2006/03/comet-low-latency-data-for-the-browser/
12
13
14
15
16
17 这些例句头都是借用了http://tools.ietf.org/html/rfc6455
18
19
20
21
22
23
24
25
26**
二、工具
在这一章中,你将对你将要构建的应用有一个大致的了解,以便学习如何使用实时网络技术。您将使用这个粗略的想法来确定构建应用所需的工具,并快速浏览每个工具的角色和功能。
在本章结束时,你应该对你已经知道的技术有所更新,并准备开始学习新的知识。
我们在建造什么?
在我们做任何其他事情之前,看一看我们正在试图构建的东西可能是一个好主意。这应该给我们一个应用需要做什么的粗略轮廓,这允许我们创建一个工具列表,我们将需要使这一切发生。
我们在本书中的目标是创建一个问答应用。该应用将允许演示者创建一个“房间”,与会者可以加入。
与会者将能够提出一个问题,该问题将立即显示在演示者的设备上,任何带有浏览器的设备,如笔记本电脑、平板电脑或智能手机,都将得到回答。如果另一个与会者已经问了这个问题,与会者将能够投票选择答案,以向演示者表明哪些问题是最紧迫的。
演示者可以将问题标记为已回答,还可以在演示结束时关闭房间。这些行动的结果将立即显示给所有与会者。
从发展角度来看,这意味着什么?
现在我们知道了应用的基本功能,我们需要将它分成不同的层,这也有助于我们将应用分成不同的技术。
首先,我们需要一个用户界面,这样用户就可以用简单的方式与我们的应用交互。没有出色的用户界面,我们的应用再酷、再有用也没用;如果它很难或令人困惑,它就不会被使用。
其次,我们需要处理用户请求,并处理他们在与应用交互过程中执行的各种操作。为了让应用有用,它需要做一些事情。
第三,我们需要存储用户提供的应用数据,以便房间可以存档,设置可以存储,并且各种其他数据可以在整个应用中保持。
第四,更新需要是即时的。如果用户不得不不断刷新以获取数据,那么问题重叠和错过信息的可能性会高得多。这个应用的有用性几乎完全取决于信息传递的实时性。
最后,我们需要确保认证并开始使用该网站是简单而不费力的。用户可能会在演示开始时第一次使用这个应用,所以他们不会有很多时间来填写个人信息或查看确认电子邮件;我们需要让他们尽可能快地启动并运行应用。
选择我们的工具
现在我们对应用的各个部分有了一个大致的概念,我们可以选择技术来满足每个部分的需求。让我们看看您将用来构建该应用的各种技术,并深入了解每种技术将扮演的角色。
HTML5
HTML5 是在开发界引起巨大轰动的技术之一,因此它的意义已经被营销人员、博客和一般极客媒体严重淡化了。
虽然 HTML5 的发布意味着我们开发网站的方式发生了很多变化和改进,但我们专注于几个关键部分,这些部分将帮助您实现这款应用。
我们为什么需要它?
HTML5 将提供一些我们需要的东西,让我们的应用以我们想要的方式工作,即这些:
- **标记创建我们的应用的用户界面:**如果没有标记语言,就很难以一种易于理解的方式向用户呈现应用数据。几乎互联网上的每个网站和 web 应用都使用某种形式的 HTML 来呈现数据,我们的也不例外。
- **WebSockets 允许演示者和出席者之间的实时交互:**我们将在本章的后面更详细地讨论这一点。
- 比以前的 HTML 规范 : 1 更干净、更简单的语法 HTML5 中的新元素——比如
<header>
和<section>
——使得标记更容易扫描和调试,这减少了维护方面的麻烦,加快了我们的初始开发。 - ****数据**属性,它允许我们轻松地包含额外的数据:**它本身并不是特别有用,但是当我们将它与 jQuery 结合使用时,它提供了一个非常简单有效的语法来处理特殊效果和事件。稍后,当您开始使用 jQuery 时,您将了解到更多这方面的内容。
- **更健壮的表单元素来改善用户界面:**最初,HTML 只支持少得可怜的几种输入类型,这意味着开发人员不得不将大多数数据硬塞进带有
type="text"
的<input>
中,并依赖客户端和服务器端的验证脚本来确保提供正确的信息。虽然 HTML5 还没有完全解决验证的问题,但它已经为我们提供了许多更有用的输入类型——包括e-mail
、number
和URL
——这改善了一些现代浏览器的用户体验。
起到什么作用?
在我们的应用中,HTML5 将扮演应用骨架的角色。它将为数据和效果提供一个合适的结构。
它是如何工作的?
HTML5 由浏览器解释,浏览器读取 HTML 标签并分配表示样式。这些可以用 CSS 来修改,我们将在接下来讨论。
注意因为本书假设了 HTML 的工作知识,所以只解释本书中将要用到的 HTML5 的新特性。如果你需要更多关于 HTML 或 HTML5 的信息,请查阅克里斯托弗·墨菲、理查德·克拉克、奥利·斯图德霍尔姆和迪维娅·马年的《HTML5 的开始》和《?? 的 CSS3》、《??》、《??》和《??》、《??》。
练习 2-1:使用 HTML5 标签创建一个 HTML 文件
下面是一个使用一些新标签的 HTML5 标记的基本例子:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Realtime Web Apps – Exercise 02-01</title>
</head>
<body>
<header>
<h1><em>Realtime Web Apps</em> – Exercise 02-01</h1>
<p>
Published on
<time datetime="2012-05-28T20:26:00-07:00">May 28, 2012</time>.
</p>
</header>
<section>
<p>
This is an example HTML file to demonstrate really basic
HTML5 markup.
</p>
<p>
We're using several of the new HTML5 elements, including
the <code><section></code> and
<code><time></code> elements.
</p>
</section>
<footer>
<p>All content © 2012 Jason Lengstorf & Phil Leggetter</p>
</footer>
</body>
</html>
该代码在浏览器中加载时,将呈现类似于图 2-1 的效果。
图 2-1。我们的 HTML5 标记生成的浏览器输出
CSS3
和 HTML5 类似,CSS3 被过度炒作,被淡化了。就其核心而言,CSS3 规范的采用是朝着消除我们对黑客技术和大量图像的依赖以在我们的网站上创建酷效果迈出的一步。它引入了对视觉效果的支持——阴影、圆形边缘、渐变等等——并为开发人员提供了一种使用非标准字体而无需 Flash 或 JavaScript 破解的方式。它提供了选择元素的新方法,并为我们提供了一种无需使用 Flash 或 JavaScript 就能在页面上制作元素动画的方法。
我们为什么需要它?
CSS3 给了我们一些工具来做一些非常酷的效果,这将会改善我们的应用。其中包括:
- 让用户界面看起来更好的视觉效果:我们是视觉动物,我们倾向于被视觉上吸引人的东西所吸引。您将使用 CSS3,通过使用阴影和渐变等东西,为应用增添一些特色。
- **改善用户体验的非必要动画:**由于还不完全支持,我们不能依赖 CSS3 过渡来制作必要的动画,但我们肯定可以使用它们为现代浏览器上的用户增加一些额外的活力。
- **告诉浏览器如何显示标记的样式规则:**为了给我们的应用一个基本的外观,我们需要创建一个带有规则的样式表,告诉浏览器页面上的每个元素是什么样子。这是 CSS 的首要目的。
起到什么作用?
出于我们的目的,CSS3 将充当视觉层。它将赋予应用“皮肤”,提供我们所追求的精致美感,并创建小的、非必要的效果,以增强那些使用支持它们的浏览器的用户的体验。
它是如何工作的?
CSS 通过文档的head.
中的link
标签链接到 HTML 文档中,然后由浏览器解析,将样式规则应用于标记中包含的元素。这本书假设了 CSS 的基本知识,所以我们将只讨论你将在应用中使用的 CSS3 的新特性。
练习 2-2:将 CSS 添加到页面
让我们继续练习 2-1 中的代码,并在页面中添加一些 CSS。首先在 HTML 文件所在的文件夹中创建一个名为styles
的新文件夹,然后在styles
中创建一个名为02.css
的新文件。
接下来,在我们在练习 02-01 的<head>
部分创建的 HTML 文件中添加一个<link>
标签。这将加载我们的 CSS 文件:
<head>
<meta charset="utf-8" />
<title>Realtime Web Apps – Exercise 02-01</title>
<link rel="stylesheet" href="styles/02.css" />
</head>
The CSS file is empty right now, so let's add a few rules to give our HTML some style. We'll include some CSS3 for extra flair as well:
/*
* Exercise 02-02, Realtime Web Apps
*
* @author Jason Lengstorf <jason@copterlabs.com>
* @author Phil Leggetter <phil@leggetter.co.uk>
*/
html { background: #efefdc; }
body {
width: 660px;
margin: 40px auto;
background: #def;
border: 2px solid #779;
/* Creates two shadow effects: outer and inner */
-webkit-box-shadow: 0 1px 6px #88a, inset 0 -1px 10px white;
-moz-box-shadow: 0 1px 6px #88a, inset 0 -1px 10px white;
-o-box-shadow: 0 1px 6px #88a, inset 0 -1px 10px white;
-ms-box-shadow: 0 1px 6px #88a, inset 0 -1px 10px white;
box-shadow: 0 1px 6px #88a, inset 0 -1px 10px white;
}
section {
margin: 20px 30px 10px;
padding: 20px 20px 10px;
overflow: hidden;
background: white;
border: 1px solid #dfdfef;
/* Creates two shadow effects: outer and inner */
-webkit-box-shadow: inset 0 1px 4px #88a, 0 1px 10px white;
-moz-box-shadow: inset 0 1px 4px #88a, 0 1px 10px white;
-o-box-shadow: inset 0 1px 4px #88a, 0 1px 10px white;
-ms-box-shadow: inset 0 1px 4px #88a, 0 1px 10px white;
box-shadow: inset 0 1px 4px #88a, 0 1px 10px white;
}
body,section {
/* Sets a border radius for every element that needs it */
-webkit-border-radius: 15px;
-moz-border-radius: 15px;
-o-border-radius: 15px;
-ms-border-radius: 15px;
border-radius: 15px;
}
footer { margin: 0 0 10px; }
h1 {
margin: 20px 30px 10px;
color: #446;
font: bold 30px/40px georgia, serif;
}
p {
margin: 0 0 10px;
font: 15px/20px sans-serif;
color: #557;
}
h1,p { text-shadow: 1px 1px 1px #88a; }
header p {
margin: 0;
padding: 2px 40px;
border-top: 1px solid #779;
border-bottom: 1px solid #779;
color: white;
font-size: 12px;
font-style: italic;
line-height: 20px;
text-shadow: 1px 1px 1px #779;
/* Adds a gradient fade */
background: #889;
background-image: -webkit-linear-gradient(top, #aac 0%, #88a 100%);
background-image: -moz-linear-gradient(top, #aac 0%, #88a 100%);
background-image: -o-linear-gradient(top, #aac 0%, #88a 100%);
background-image: -ms-linear-gradient(top, #aac 0%, #88a 100%);
background-image: linear-gradient(top, #aac 0%, #88a 100%);
background-image: -webkit-gradient(
linear,
left top,
left bottom,
color-stop(0, #aac),
color-stop(1, #88a)
);
}
footer p {
margin: 0;
color: #889;
font: italic 12px/1.67em sans-serif;
text-align: center;
text-shadow: 1px 1px 1px white;
}
将这些规则保存在02.css
中;然后保存并刷新 HTML 文件以查看规则生效(参见图 2-2 )。为了获得最佳效果,请使用支持 CSS3 效果的最新浏览器,但是样式表可以在任何浏览器中工作,因为 CSS3 只添加了非必要的效果。
图 2-2。应用了 CSS 样式表的(略微更新的)HTML 文件
注意您可能已经注意到,有几个规则用一个供应商前缀声明了多次(-webkit-
、-moz-
等等),这可能看起来有点混乱。因为 CSS3 还没有 100%完成,每个浏览器处理新规则的方式都略有不同。
如果没有办法在浏览器之间进行补偿,这可能会给开发人员带来问题,因此添加了供应商前缀,以允许不同的规则应用于不同的浏览器。希望不久的某一天,每一个新的 CSS3 规则都有一个统一的语法,但是事情仍然在变化,这是不可避免的。
JavaScript 和 jQuery
JavaScript 是一种客户端脚本语言,这意味着它在用户的计算机上执行。这使得它非常适合于一些任务,比如动画页面上的元素、进行动态计算,以及其他各种如果需要页面刷新就会非常不方便的操作。
JavaScript 还允许通过异步调用脚本来执行某些服务器端操作。这项技术通常被称为 AJAX:异步 JavaScript 和 XML。这个术语的 XML 部分的出现是因为请求通常会返回 XML 数据。虽然这不再是常见的用例,但是这个名字已经被记住了。本质上,AJAX 允许加载不同的页面,并使用 JavaScript 将其内容返回到当前页面。加载的页面可以接收来自 AJAX 请求的数据、处理数据、存储数据、检索新数据,并将数据返回给请求数据的脚本。(我们之前在第一章中讨论过这个问题)。
然而,尽管 JavaScript 拥有强大的功能,但它一直是个麻烦的动物,因为它的文档不够优秀,有时语法混乱,并且在其他浏览器上的实现不一致。因此,JavaScript 的学习曲线非常陡峭,任何希望在项目中使用它的开发人员都需要投入大量的时间。
为了应对这种挫折,一些团体和个人开始着手简化 JavaScript,让每个人都能使用它。他们创建了处理常见任务的框架,克服了跨浏览器的麻烦,并为新开发人员提供了良好的文档和支持社区。
起初有几十个这样的框架,包括 MooTools、YUI、Dojo、Prototype 和 jQuery。两者各有优缺点,但是 jQuery 似乎获得了最多的社区支持,这主要是因为它有很好的文档和非常简单的语法。
我们为什么需要它?
改进的 JavaScript 文档、对该技术使用的普遍接受以及语言标准化为使用该语言的开发人员带来了更好的开发体验。然而,在某些情况下,库还是非常有用的。jQuery 将处理任何未解决的跨浏览器不一致问题,并为我们提供执行以下任务所需的工具:
- **创建动画来显示应用中正在发生的事情:**通过动画显示各种动作来显示用户正在发生的事情是增强用户界面的一种很好的方式,它增加了我们正在努力实现的整体完美性。
- **处理用户事件:**当用户与应用交互时——无论是点击、轻击还是滑动——浏览器都会触发一个事件,jQuery 可以检测到该事件并根据用户的动作执行任务。
- **显示实时事件的结果:**当用户执行某些操作时,应用需要向当前与之交互的所有用户显示操作的结果。您将使用 WebSocket 技术和 Pusher 来处理数据发送——我们将很快介绍这一点——但是通过 WebSocket 连接接收到的信息将触发一个事件,就像单击或任何其他用户交互一样。我们将使用 jQuery 来处理这些事件,并根据应用中发生的事情来执行任务。
起到什么作用?
jQuery 将扮演这款应用一半大脑的角色。它会根据用户交互或实时事件注意到应用中的任何变化,并适当地处理这些变化,或者通过制作动画,或者在其他地方(如另一个用户的设备上)发生变化时更新用户。
它是如何工作的?
使用一个由浏览器解析的<script>
标签将 JavaScript 加载到 HTML 标记中。本书假设读者具备 jQuery 的基础知识,因此为了简洁起见,很多基础知识将被跳过。
注意如果你想学习 jQuery 的基础知识,可以去拿一本 Jason Lengstorf 写的 Pro PHP 和 jQuery 3 。
练习 2-3:添加一个简单的 JQUERY 效果
为了试验 jQuery,让我们向 HTML 文件添加一个小脚本,它将执行以下操作:
- 绑定到所有
<code>
元素上的悬停事件。 - 当鼠标悬停时,它将从用户鼠标悬停的标签中获取文本,并使用该元素的文本内容来标识页面上的其他元素;例如,
<code><time></code>
标识其他<time>
元素。 - 其他元素的背景将被设置为黄色。
第一步是在 HTML 文件所在的目录下创建一个名为scripts
的新文件夹,并在其中创建一个名为03.js
的新文件。
接下来,使用<script>
标签将 jQuery 和03.js
加载到 HTML 文件中,将它们插入到结束标签的上方
</body> tag:</footer>
<script src="[`code.jquery.com/jquery-1.7.2.min.js"></script`](http://code.jquery.com/jquery-1.7.2.min.js"></script)>
<script src="scripts/03.js"></script>
</body>
现在我们需要将代码添加到03.js
中。关于代码的更多细节,它将遵循的步骤如下:
- 为每个
<code>
标签的hover
事件绑定两个函数:一个用于鼠标进入悬停状态,一个用于鼠标退出悬停状态。 - 使用 jQuery 的
.text()
方法检测<code>
元素中的标记名;使用简单的正则表达式删除任何非字母数字的字符(删除左括号和右括号);并将匹配的字符串转换为一个String
来防止错误。 - 悬停时,找到匹配的元素,使用
.data()
存储每个元素的原始背景色;然后使用.css()
将背景颜色更改为黄色。 - 悬停结束后,用
.data()
取回原来的背景色;然后用.css()
恢复。
/*
* Exercise 02-03, Realtime Web Apps
*
* @author Jason Lengstorf <jason@copterlabs.com>
* @author Phil Leggetter <phil@leggetter.co.uk>
*/
(function($) {
// Highlights the element contained in the <code> tag
$('code').hover(
function() {
var elem = $(getElementName(this)),
bg = elem.css("background");
elem.data('bg-orig', bg).css({ "background": "yellow" });
},
function() {
var elem = $(getElementName(this)),
bg = elem.data('bg-orig');
$(elem).css({ "background": bg });
}
);
/**
* Retrieves the element name contained within a code tag
*/
function getElementName(element) {
return String($(element).text().match(/\w+/));
}
})(jQuery);
保存您的代码;然后重新加载 HTML 并将鼠标放在其中一个<code>
标签上以查看结果(如图 2-3 中的所示)。
图 2-3。一个简单的 jQuery 效果在 HTML5 标签的名字被悬停时高亮显示。当用户将鼠标悬停在<时间>文本上时,会高亮显示<时间>元素
PHP
PHP 是一种服务器端脚本语言,为处理数据提供了强大的工具。它为开发人员提供了一种在 HTML 标记中构建动态内容的方法,并且已经发展成为互联网上使用最广泛的服务器端脚本语言之一。
PHP 不是我们可以使用的唯一语言,因为有许多语言可以让我们构建 web 应用。许多语言也让我们使用实时网络技术,但有些语言做得比其他语言好。Node.js 与实时 web 技术紧密相关,主要是因为它的事件性质和 socket.io, 4 ,这可能是最著名的实时 web 框架;Ruby 有很多解决方案,最流行的是 FAYE??。NET 有一个相当新的微软支持的解决方案,叫做 SignalR 6 Python 有很多基于 Tornado 框架的解决方案; 7 以此类推。
有趣的是,PHP 应用通常运行在 Apache 上,并不太适合实时 web 技术,因为它们是在考虑 HTTP 和请求响应范例的情况下构建的。它们不是为处理维护大量持久连接甚至高容量轮询而构建的。这实际上给了我们一个使用 Pusher 的很好的理由,作为一种托管服务,Pusher 消除了维护我们实时 web 技术基础设施的潜在痛苦。
我们为什么需要它?
您的应用将利用 PHP 的几个特性来为应用添加功能,例如:
- **动态生成输出以定制应用的显示:**像用户名、当前“房间”的名称和演示者的姓名这样的东西需要动态插入到 HTML 标记中。
- **连接到 Pusher API 以实现实时通信:**我们将在本章的后面讨论这一点,并在第三章中详细讨论。
起到什么作用?
如果 JavaScript/jQuery 是我们应用的半脑,PHP 将扮演另一半的角色。它将处理用户发送到应用的数据,并发送回各种用户请求的处理后的响应。
它是如何工作的?
PHP 是一个预处理器 ,这意味着它在页面呈现之前进行计算和数据操作。这意味着 PHP 代码被嵌入到标记中,服务器读取 PHP 并在将标记传递给浏览器之前用适当的输出替换它。
注意这本书假设了 PHP 的工作知识,所以基本概念和语法将不被涵盖。如果你想提高你的 PHP 水平,请阅读 Jason Lengstorf 为绝对初学者编写的PHP、 8 。
练习 2-4:使用 PHP 插入动态内容
让我们通过将当前日期动态插入标记来试验 PHP。为此,我们需要将 HTML 的一个新副本保存为 PHP 文件,我们将其命名为04.php
。
接下来,让我们在文件的最顶端插入一些 PHP 代码,就在<!doctype>
声明的上面:
<?php
// Set the timezone and generate two formatted date strings
date_default_timezone_set('US/Pacific');
$datetime = date('c');
$date_fmt = date('F d, Y');?>
<!doctype html>
如注释所示,这段代码将时区设置为太平洋时区,然后在变量中存储两个格式化的日期字符串。
现在我们有了变量,让我们通过在标记中插入以下内容将它们插入到<time>
标记中:
<header>
<h1><em>Realtime Web Apps</em> – Exercise 02-04</h1>
<p>
Published on
<time datetime="
<?php echo $datetime; ?>">
<?php echo $date_fmt; ?>
</time>.
</p>
</header>
保存此文件;然后将其加载到浏览器中,查看显示的当前日期(参见图 2-4 )。
图 2-4。使用 PHP,在标记中输出当前日期
MySQLt1 版
MySQL 是一个数据库管理系统——准确地说是一个关系数据库管理系统(RDBMS)——是这个星球上使用最广泛的。它提供了一种易于阅读的语法来存储和检索表中的数据,并允许开发人员创建可以存储用户设置等数据的应用,在我们的应用中,还可以存储房间名称和问题。
我们为什么需要它?
某些数据(如房间名称和提出的问题)需要存储在数据库中,以便日后检索:
- **会议室的详细信息:**会议室名称和会议信息等详细信息,以便演示者日后返回会议室参考。
- **在每个房间问的问题:**这允许新与会者迟到,不会错过任何东西。
起到什么作用?
MySQL 将扮演我们应用的内存角色。它会记住各种房间和问题的详细信息,并在将来需要时做好准备。
它是如何工作的?
MySQL 安装在服务器上,通过创建数据库、数据库中的表和表中的行来工作。
每一行都是一段特定的数据。例如,如果表名为 rooms ,那么每一行都将包含一个房间的信息,比如它的名称、惟一标识符和其他相关数据。
注意 MySQL 也涵盖在前面提到的 PHP 绝对初学者中。
练习 MYSQL 的乐趣
因为将 MySQL 与我们的 HTML 标记集成在一起需要太多的设置,所以让我们变得古怪一点,在命令行上使用 MySQL。当然,您需要在您的系统上安装和配置 MySQL。打开终端并连接到 MySQL,用您的 MySQL 用户名替换您的用户名:
mysql -uyour_username -p
应该会提示您输入密码,然后您将连接到 MySQL 服务器。你也可以使用桌面客户端或者 phpMyAdmin 来做这个练习——在 Mac 上我们推荐 SequelPro, 9 ,在 Windows 上我们听说过 Navicat 的优点。 10
一旦您连接到服务器,创建一个数据库来玩,并确保事情按预期工作。姑且称之为awesome_test_db
:
CREATE DATABASE awesome_test_db;
这将为您提供以下输出:
Query OK, 1 row affected (0.00 sec)
现在,让我们选择将对其执行查询的数据库:
USE awesome_test_db;
这应该告诉您数据库已经更改,这意味着我们现在可以创建一个表:
CREATE TABLE awesome_things (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(64),
percent TINYINT
);
准备好表格后,我们可以插入几行;每一行都有一个事物的名称和一个百分比来表示它有多棒。请记住,这只是为了好玩,并证明 MySQL 正在工作:
INSERT INTO awesome_things (name, percent)
VALUES
('Wooden sunglasses', 72),
('Pabst Blue Ribbon', 85),
('Bands no one has heard of', 100),
('Vintage clothing', 67);
这将为您提供以下输出:
Query OK, 4 rows affected (0.00 sec)
Records: 4 Duplicates: 0 Warnings: 0
既然我们知道了什么是令人敬畏的,让我们确保我们知道所有超过 75%令人敬畏的事情:
SELECT CONCAT(name, ': ', percent, '% awesome, which means I'm onboard.')
FROM awesome_things
WHERE percent>75
ORDER BY percent DESC;
使用CONCAT()
允许我们将输出组合成一个句子,而不仅仅是查看原始数据。使用WHERE
子句,我们可以过滤结果,以便我们只看到超过 75%的精彩行,因为我们想首先看到最精彩的内容,所以我们按照降序中的percent
排序,或者从高到低排序。执行时,您将看到以下内容:
+----------------------------------------------------------------------+
| CONCAT(name, ': ', percent, '% awesome, which means I'm onboard.') |
+----------------------------------------------------------------------+
| Bands no one has heard of: 100% awesome, which means I'm onboard. |
| Pabst Blue Ribbon: 85% awesome, which means I'm onboard. |
+----------------------------------------------------------------------+
2 rows in set (0.00 sec)
现在您知道什么是最棒的了,您可能想要通过完全删除数据库来销毁证据:
DROP DATABASE awesome_test_db;
这将完全删除数据库,这样您的 MySQL 服务器就不会塞满测试数据。
HTML5 WebSocket 技术和推送器
我们已经谈了一点 WebSocket 和 realtime,但是让我们回顾一下:HTML5 WebSocket 允许应用将数据推送到客户端,而不是要求客户端不断请求新数据。
练习 2-6:试用 WEBSOCKET API
让我们看一下本地 WebSocket API,以了解如何使用它。创建一个包含以下内容的 HTML 文件。该文件包含连接到 WebSocket echo 测试服务的 JavaScript。这意味着您可以测试连接、发送和接收消息。
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Trying out the WebSocket API 02-06</title>
</head>
<body>
<script>
var ws = new WebSocket( 'ws://echo.websocket.org' );
ws.onopen = function() {
console.log( 'connected' );
console.log( '> hello' );
ws.send( 'hello' );
};
ws.onmessage = function( ev ) { console.log( '< ' + ev.data ); };
ws.onclose = function() { console.log( 'closed' ); };
ws.onerror = function() { console.log( 'error' ); };
</script>
</body>
</html>
如果您在支持 WebSocket 的浏览器中打开此页面,并打开浏览器的 JavaScript 控制台,您将看到以下内容:
connected
> hello
< hello
当 WebSocket 已经连接到服务器,并且已经调用了onopen
函数处理程序时,显示connected
消息。代码随后记录> hello
以表明它将通过 WebSocket 连接使用 WebSocket send
函数发送hello
到服务器。最后,当服务器回显消息时,调用onmessage
函数处理程序,并将< hello
记录到控制台。
这演示了如何使用 WebSocket API,并向您展示了它的用处。但是,正如我们在第一章中所提到的,WebSocket API 还没有被所有的浏览器完全支持,我们需要一个后备机制。因此,如果我们必须自己处理浏览器兼容性问题,实现实时应用可能会很麻烦、棘手,而且非常耗时。
幸运的是,对于我们其他人来说,有许多服务已经克服了这些障碍,并创建了从检查 WebSocket 支持开始的 APIs 然后回归检查下一个最好的解决方案,直到他们找到一个工作。结果是强大的实时功能,而没有向后兼容的麻烦。
在这些提供实时服务的公司中,Pusher 以其极其简单的实现方式脱颖而出,为那些没有庞大用户群、优秀文档和有帮助的支持人员的服务提供免费账户。
Pusher 提供了一个 JavaScript 库 11 ,它不仅可以处理旧浏览器的回退,还可以通过其 API 提供自动重新连接和发布/订阅 12 消息传递抽象等功能,这比我们使用原生 WebSocket API 时简单处理一般消息要容易得多。
最后,因为 Pusher 是一个托管服务,它将负责维护数据传输的持久连接,并且可以处理扩展以满足我们的需求。虽然后一点对于我们的示例应用来说可能不是什么大问题,但是在构建生产应用时,这是一个值得考虑的问题。
出于这些原因,我们将在本书中使用 Pusher 来构建我们的实时系统。
我们为什么需要它?
Pusher 将允许您向应用添加实时通知和更新,包括以下内容:
- **添加新问题时更新所有用户:**这意味着当用户添加新问题时,该房间中当前使用该应用的所有用户都会立即收到新问题。
- **当演示者将问题标记为“已回答”时更新与会者:**当演示者回答问题时,将其标记为“已回答”将会立即更新所有与会者的设备,以防止混淆。
- **当多个与会者希望回答同一个问题时更新演示者:**如果多个用户对某个问题的答案感兴趣,他们可以投票支持该问题。演示者将收到一个视觉提示,让他们知道问题很紧迫。
- **会议室关闭时更新所有与会者:**当演示者关闭会议室时,需要更新与会者,以便他们知道不要问任何不会得到回答的问题。
起到什么作用?
Pusher 将扮演应用神经系统的角色:当发生变化时,它会收到通知,并将信息传递给应用的大脑,以便它们可以处理信息。
它是如何工作的?
简而言之,Pusher 提供了一种机制,让客户端“监听”应用的变化。当事情发生时,Pusher 会向所有正在监听的客户发送通知,以便他们能够做出适当的反应。这就是我们前面提到的发布/订阅范例。
第三章专注于更详细的细节,因此我们将跳过这一部分的练习。
oath
与目前讨论的技术不同,OAuth 是一种协议,而不是一种真正的编程语言。这是 2007 年起草的一个概念,旨在解决提供重叠服务的网站带来的问题;想想社交网络如何访问你的地址簿来寻找朋友,或者照片共享网站如何连接到 Twitter,让你的关注者知道你何时发布了新照片。
问题是这样的:当这些服务开始协同工作时,它们要求用户提供用户名和密码来访问服务,这是一个潜在的巨大风险。怎样才能阻止一个可疑的服务出于自己的目的使用那个密码,包括改变你的密码并把你锁在外面?
这是一个大问题。OAuth 基于对许多其他解决问题的尝试的研究,设计了一个解决方案,使用了它认为每个方案的最佳部分。
套用 OAuth 网站上一个很好的比喻: 13
OAuth 就像给某人一辆豪华汽车的代客钥匙。一把代客钥匙只能让汽车行驶几英里;它不允许进入后备箱;它阻止使用汽车车载电脑中的任何存储数据,如地址簿。OAuth 类似于您的在线服务的代客钥匙:您不需要提供密码,并且您可以只允许帐户的某些特权,而不会暴露您的所有信息。
例如,脸书使用 OAuth 对第三方服务进行用户认证。如果您已经登录到脸书,您会看到一个对话框(在脸书的域上),告诉您需要哪些权限,并允许您接受或拒绝请求。特权是分门别类的——例如,阅读某人的时间表不同于查看他们的好友列表——以确保第三方服务只获得他们需要的特权。
这保证了用户的安全,并减少了网络应用的责任。它还为开发人员提供了一个极好的好处:我们可以允许用户使用他们的脸书、Twitter 或其他使用简单 API 的凭证登录我们的应用。
我们为什么需要它?
我们在构建的应用中不需要它,但它会是一个很好的功能,所以如果你想知道如何包含它,我们将它包含在附录 A 中。简而言之,我们将使用 OAuth 来消除构建用户管理系统的需要。这也将极大地减少注册账户所需的时间,而不会减少应用运行所需的信息。
让我们面对现实吧:大多数人在互联网上拥有的账号比他们能记住的还要多。有人使用我们的应用和不使用我们的应用之间的区别可能就像他必须点击多少个按钮才能开始一样简单。
OAuth 提供了一种很好的方式来获得我们需要的一切:
- **验证这个人确实是真实的:**我们可以合理地假设,任何登录到有效的脸书或推特账户的人都是真实的。
- **收集关于用户的必要数据:**对于这个应用,我们只需要一个名字和电子邮件。
- **降低准入门槛:**通过消除创建账户的所有常规步骤,我们可以让用户只需点击两下就能在几秒钟内进入我们的应用。
起到什么作用?
OAuth 将是我们应用的看门人。它将使用第三方服务来验证用户的真实性,并收集应用运行所需的必要信息。
它是如何工作的?
你可以在附录 A 中找到关于 OAuth 细节的更多细节,但是在它的核心,OAuth 联系服务,我们希望通过它来验证我们的用户,并发送一个标识我们应用的令牌。如果用户尚未登录第三方服务,系统会提示用户登录,然后允许或拒绝我们的应用请求的权限。如果用户允许我们的应用访问所请求的数据,服务会发回一个令牌,我们可以使用它来检索必要的数据,并认为用户“登录”了我们的应用。
摘要
此时,我们已经成功地为我们的应用定义了一个粗略的功能和需求列表。我们还利用这些信息充实了一个工具列表,我们将使用这些工具来使这款应用变得更加生动。
在下一章中,您将熟悉 Pusher 及其底层技术,并将构建您的第一个实时应用。
1 这是 100%作者的意见。
2
3
4
6
5
7
8
9
10
11
12
13
三、Pusher
现在,我们已经为应用奠定了一些基础,并回顾了我们将用来构建它的一些编程语言,您可以开始熟悉将要发挥作用的新技术了。
首先,让我们熟悉一下 Pusher,它将负责我们站点上的实时交互。在这一章中,我们将探索 Pusher 的起源,底层技术,他们提供的一些帮助开发的工具,并通过构建一个简单的实时活动跟踪应用来实践。
推杆简史
Pusher 是互联网上一个相对较新的趋势的一部分,这个趋势被称为软件即服务(SAAS) 。这些公司收费向其他开发者提供有用的工具、实用程序、服务或其他价值。这使我们能够使用极其强大的新技术,而无需花费数天或数周时间来解决可扩展性和跨浏览器支持等问题。
2010 年初,联合创始人 Max Williams 和 Damien Tanner 经营着英国最成功的 Ruby On Rails 商店之一。当他们发现需要在团队成员之间同步数据时,他们构建了一个小工具来利用新的 HTML5 WebSocket API。
一旦他们意识到使用他们的基础设施创建实时应用是多么容易,他们就看到了超越内部管理工具的机会。
从那时起,Pusher 已经发展成为实时 SaaS 市场的主导力量,拥有令人印象深刻的客户名单,包括 Groupon、MailChimp 和 SlideShare。
为什么要用 Pusher?
使用像 Pusher 这样的托管服务的一个关键原因是,它通过使以前复杂的目标更容易实现来加速开发过程。其中一个关键部分是达到目标的速度。但是还有其他人。因为我们使用的是 Pusher 托管服务,所以有必要强调一下这样做的一些好处。
可量测性
基于云的 SaaS 首先提供的是可伸缩性的承诺,Pusher 也不例外。它提供并扩展实时基础设施,这样我们就可以专注于为我们的应用添加实时交互功能。
WebSocket、回退支持和自动重新连接
在第一章中,我们展示了 WebSocket 技术有多棒,但是仍然有一个不幸的需求,那就是为旧的浏览器或者复杂的网络提供支持。因此,Pusher 也能处理老浏览器的回退问题,这应该是一种解脱。其 JavaScript 库根据浏览器运行时间和网络条件选择最合适的连接方式。该库还会检测断开的连接,并自动为您重新连接。
该库在用户的浏览器和推送服务之间建立了一个连接,这样一旦有了新数据,就可以发布并推送给用户。您也可以直接从客户端发布信息。在本章的学习过程中,我们将更深入地研究可用的功能。
其他客户端库
尽管 WebSocket 规范现在属于 HTML5 的范畴,但重要的是要记住它是一个协议的规范。这意味着任何可以建立 TCP 连接的技术也可以建立 WebSocket 连接。Pusher 利用了这一点,还提供了许多其他技术的客户端库,包括用于 iOS 开发的 Objective-C、用于 Android 和桌面开发的 Java、用于 Flash 的 ActionScript 和用于 general 的 C#。NET 和 Silverlight 运行时。
应用接口
没有 REST API 的托管服务是不完整的,Pusher 也不例外。1REST API 主要用于发布数据,但也提供在 Pusher 中查询应用状态的功能。提供 REST API 意味着任何可以发出 HTTP 请求的技术都可以使用这个 API。
服务器库
因为 REST API 需要认证,并且有一些潜在的复杂需求,所以开发了许多服务器库来使执行对 API 的请求变得更容易。因为调用 REST API 所需要做的只是发出一个 HTTP 请求,所以可用库的数量非常多也就不足为奇了。 2 最常用的库包括 PHP、Ruby、。NET、Python、Node.js 和 Java。
开发人员工具
托管服务的另一个越来越有用的特性是某种形式的开发人员工具,它增加了开发的便利性。这是通过公开服务可用的内部工作和日志记录信息来实现的。在 Pusher 的例子中,这意味着公开与您正在构建的应用相关的信息,比如连接和数据流。我们将在构建应用时使用这些工具。
文件
文档对于任何技术来说都是必不可少的,对于托管服务来说更是如此,这可以被视为一个黑盒——这不像你可以在托管服务的所有组件的源代码中四处挖掘以了解发生了什么——即使他们开源了其中的一些组件或使用现有的开源组件。
因此,包含大量代码示例的良好文档可以决定是否使用某项服务。Pusher 的文档 3 是用户指南和参考资料的结合,重点在于探索什么是可能的,并让您快速找到实现某个目标的方法。
Pusher 术语
在我们开始使用 Pusher 之前,您应该熟悉一些术语。
一个连接代表一个客户端(在我们的例子中是一个 web 浏览器)和推送服务之间的持久连接。消息通过 Pusher 在此连接上接收,我们也可以通过 Pusher 在此连接上向其他用户发送消息。
Pusher 使用发布-订阅消息模式 4 ,因此使用通道的概念来识别客户端应用感兴趣的内容(例如,“体育新闻”或“新闻推文”)。一个频道简单地用一个名字来表示,你可以通过订阅来注册一个频道。
有些人把实时网络称为“事件网络”这是因为实时 web 技术经常被用于传达某种事件已经发生,并且将与该事件相关联的数据传递给有兴趣知道该事件已经发生的用户或系统。例如,当某人发推文时,系统可能会通知其所有关注者该推文事件,并提供推文数据(推文用户、文本、推文时间、是否是对另一条推文的回复等)。)对他们来说。
因此,Pusher 也使用事件的概念也就不足为奇了。事件与渠道结合使用;当您订阅一个频道时,您可以绑定到该频道上的事件。例如,您可以订阅“新闻-推文”频道,并绑定到“新闻 _ 推文”、“推文 _ 删除”、“推文 _ 收藏”和“转发”事件。事件与 CRUD(创建、读取、更新、销毁)功能的创建、更新和销毁部分配合得非常好;以及反映事件结果的用户界面变化。
为了接收数据,必须发布。数据在通道上发布并与事件相关联。为了维持事件网络的概念,在一个频道上发布数据的行为被称为触发事件。因此,触发和发布可以互换使用。
在我们开发应用时,您将看到所有这些概念都在使用,这将演示您如何在新的或现有的应用中轻松使用相同的概念。
Pusher 入门
在我们开始构建示例应用之前,您需要做的第一件事是注册一个 Pusher 沙盒帐户。这是一个免费帐户,它限制了您可以从客户端同时打开的连接数和每天可以发送的邮件数。这个免费帐户对于本书中介绍的所有应用来说都绰绰有余。
首先,前往http://www.pusher.com
的推手网站并注册。
注册后,你将被带到你的账户主页,那里有你对你的账户进行快速概念验证所需的所有信息(见图 3-1 )。)
图 3-1。Pusher 欢迎屏幕
暂时就这样。说真的。不过,请保持此页打开,因为您将在练习 3-1 中使用它。
练习 3-1:一个简单的推杆测试
为了验证您的帐户是活跃的,并展示使用 Pusher 是多么容易,让我们创建一个极其简单的 HTML 文档并向它发送一条消息。
首先,创建一个简单的 HTML 文件。在内部,添加以下标记:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Realtime Web Apps – Exercise 3-2</title>
</head>
<body>
<h1>Testing Pusher</h1>
<p>
This is a simple demo of how easy it is to integrate Pusher
into an application.
</p>
</body>
</html>
接下来,通过在结束的</body>
标签上方插入这个脚本标签,在页面上包含 Pusher JavaScript 库:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Realtime Web Apps – Exercise 3-2</title>
</head>
<body>
<h1>Testing Pusher</h1>
<p>
This is a simple demo of how easy it is to integrate Pusher
into an application.
</p>
<script src="[`js.pusher.com/1.12/pusher.min.js"></script`](http://js.pusher.com/1.12/pusher.min.js"></script)>
</body>
</html>
接下来要做的是连接到推进器。在下面的代码中,用 Pusher 应用凭证中列出的key
替换appKey
变量值。
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Realtime Web Apps – Exercise 3-2</title>
</head>
<body>
<h1>Testing Pusher</h1>
<p>
This is a simple demo of how easy it is to integrate Pusher
into an application.
</p>
<script src="http://js.pusher.com/1.12/pusher.min.js"></script>
<script type="text/javascript">
var appKey =' 079be339124bac43c45c';
var pusher = new Pusher(appKey);
</script>
</body>
</html>
当您创建新的 Pusher 实例时,将建立到 Pusher 服务的新连接。
接下来要做的是检查你是否连接。您可以使用 Pusher 调试控制台手动完成这项工作,这是我们前面提到的开发工具之一。为此,请转到您的应用的 Pusher 仪表板,并单击 Debug Console 链接。现在,在不同的浏览器窗口中,打开新的 HTML 文档。如果你看一下 Pusher 调试控制台,你会看到一个新的连接已经被列出,如图 3-2 中的所示。
图 3-2。推杆调试控制台
您还可以通过绑定到pusher.connection
对象上的事件来检查您的代码中是否已经建立了连接。 5 如果你想给用户关于连接状态的反馈,或者如果你需要应用在连接不可用时做出不同的反应——如果用户当前离线的话,这可能是有用的。如前所述,Pusher 使用了事件的概念,在监控连接状态时也是如此。在下面的代码中,我们将绑定到state_change
事件,并在页面中显示当前状态。实现这一点所需的代码更改以粗体显示:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Realtime Web Apps – Exercise 3-2</title>
</head>
<body>
<h1>Testing Pusher</h1>
<div id="connection_state">‐</div>
<p>
This is a simple demo of how easy it is to integrate Pusher
into an application.
</p>
<script src="http://js.pusher.com/1.12/pusher.min.js"></script>
<script>
var pusher = new Pusher( '079be339124bac43c45c' );
pusher.connection.bind( 'state_change', function( change ) {
document.getElementById( 'connection_state' ).innerHTML = change.current;
} );
</script>
</body>
</html>
对于每个连接状态变化,调用作为第二个参数传递给bind
函数的函数。然后我们可以更新页面来显示当前的连接状态。
现在我们知道了如何检测我们的连接状态,我们可以看看订阅一个通道。如前所述,通道完全由名称来标识:a string
。您不需要做任何事情来创建通道或在 Pusher 中提供它。只要订阅它,它就存在了。您甚至不需要担心使用太多的通道,因为它们实际上只是一种将数据从发布者(我们尚未涉及)路由到订阅者的方式。因此,让我们订阅一个频道并绑定到该频道上的一个事件:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Realtime Web Apps – Exercise 3-2</title>
</head>
<body>
<h1>Testing Pusher</h1>
<div id="connection_state">‐</div>
<p>
This is a simple demo of how easy it is to integrate Pusher
into an application.
</p>
<script src="http://js.pusher.com/1.12/pusher.min.js"></script>
<script>
var pusher = new Pusher( '079be339124bac43c45c' );
pusher.connection.bind( 'state_change', function( change ) {
document.getElementById( 'connection_state' ).innerHTML = change.current;
} );
var channel = pusher.subscribe( 'test_channel' );
channel.bind( 'my_event', function( data ) {
alert( data );
} );
</script>
</body>
</html>
与处理连接事件一样,事件处理程序非常简单:当事件被触发时,正在发送的消息显示在一个警告框中。
在一个浏览器窗口中导航到您的 HTML 文件;在另一个窗口中,转到事件创建者,可以在您的应用的 Pusher 仪表盘中找到。您将看到事件创建者表单。输入与我们刚刚编写的 JavaScript 代码相对应的详细信息;通道名应该是 test_channel,事件名应该是 my_event。在事件数据文本区输入一些文本并点击发送事件按钮(见图 3-3 )。
图 3-3。Pusher 事件创建者
注意 Pusher 建议你发送 JSON 作为事件数据,他们的库会帮助你做到这一点。出于测试目的,为了让警报显示一些人类可读的信息,我们将只发送文本。
当按下 Send Event 按钮时,您应该从您的测试脚本(如果您仍然打开 Pusher 页面,则为两个)中收到一个类似于图 3-4 中的警告。
图 3-4。当事件创建者发送测试事件时,会出现一个警告框
在其最简单的形式中,这是一个工作 Pusher 应用。在下一个练习中,您将看到一个更有用的例子。
使用 Pusher 发送事件
练习 3-1 演示了使用 Pusher 接收事件是多么容易,但是发送事件又如何呢?
得益于它的各种 API 库,Pusher 使得发送事件就像接收事件一样简单。我们将使用 PHP API 库,它位于 GitHub 上的https://github.com/pusher/pusher-php-server
。
练习 3-2:使用 PUSHER 发布和订阅
为了熟悉 Pusher 的服务器端功能,让我们构建一个简单的消息传递系统。
对于基础,为了节省时间,我们将尽可能多地重用我们在第二章的练习中编写的 HTML 和 CSS。创建一个新的 HTML 文件,并输入以下代码:
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Realtime Web Apps – Exercise 3-2</title>
<link rel="stylesheet" href="styles/layout.css" />
</head>
<body>
<header>
<h1>Send a Message with Pusher!</h1>
</header>
<section>
<form method="post" action="post.php">
<label>
Your Name
<input type="text" name="name"
placeholder="i.e. John" />
</label>
<label>
Your Message
<input type="text" name="message"
id="message" value="Hello world!" />
</label>
<input type="submit" class="input-submit" value="Send" />
</form>
</section>
<aside>
<h2>Received Messages</h2>
<ul id="messages">
<li class="no-messages">No messages yet...</li>
</ul>
</aside>
<footer>
<p>
All content © 2013 Jason Lengstorf & Phil Leggetter
</p>
</footer>
<script src="http://js.pusher.com/1.12/pusher.min.js"></script>
<script
src="http://code.jquery.com/jquery-1.7.2.min.js"></script>
<script
src="scripts/init.js"></script>
</body>
</html>
这段代码创建了一个简单的表单,接受一个名称和一条消息,以及一个无序列表来显示接收到的任何消息。
接下来,我们来添加一些 CSS。创建一个名为 styles 的文件夹,并向其中添加一个名为 layout.css 的新文件。该文件已经链接到我们之前创建的 HTML。将以下代码添加到新的 CSS 文件中:
/*
* Exercise 3-2, Realtime Web Apps
*
* @author Jason Lengstorf <jason@copterlabs.com>
* @author Phil Leggetter <phil@leggetter.co.uk>
*/
html { background: #efefdc; }
body {
width: 800px;
margin: 40px auto;
overflow: hidden;
background: #def;
border: 2px solid #779;
/* Creates two shadow effects: outer and inner */
-webkit-box-shadow: 0 1px 6px #88a, inset 0 -1px 10px white;
-moz-box-shadow: 0 1px 6px #88a, inset 0 -1px 10px white;
-o-box-shadow: 0 1px 6px #88a, inset 0 -1px 10px white;
-ms-box-shadow: 0 1px 6px #88a, inset 0 -1px 10px white;
box-shadow: 0 1px 6px #88a, inset 0 -1px 10px white;
}
section,aside {
float: left;
margin: 20px 30px 10px;
padding: 20px 20px 10px;
overflow: hidden;
background: white;
border: 1px solid #dfdfef;
/* Creates two shadow effects: outer and inner */
-webkit-box-shadow: inset 0 1px 4px #88a, 0 1px 10px white;
-moz-box-shadow: inset 0 1px 4px #88a, 0 1px 10px white;
-o-box-shadow: inset 0 1px 4px #88a, 0 1px 10px white;
-ms-box-shadow: inset 0 1px 4px #88a, 0 1px 10px white;
box-shadow: inset 0 1px 4px #88a, 0 1px 10px white;
}
section {
width: 400px;
}
aside {
width: 226px;
margin-left: 0;
}
body,section,aside,input {
/* Sets a border radius for every element that needs it */
-webkit-border-radius: 15px;
-moz-border-radius: 15px;
-o-border-radius: 15px;
-ms-border-radius: 15px;
border-radius: 15px;
}
footer { margin: 0 0 10px; }
h1,h2 {
margin: 20px 30px 10px;
color: #446;
font-weight: bold;
font-family: georgia, serif;
}
h1 {
font-size: 30px;
line-height: 40px;
}
h2 {
font-size: 18px;
}
label,li {
display: block;
margin: 0 0 10px;
font: 15px/20px sans-serif;
color: #557;
}
h1,label,input,li { text-shadow: 1px 1px 1px #88a; }
label input {
display: block;
width: 378px;
border: 1px solid #dfdfef;
padding: 4px 10px;
font-size: 18px;
line-height: 20px;
/* Creates an inner shadow */
-webkit-box-shadow: inset 0 1px 4px #88a;
-moz-box-shadow: inset 0 1px 4px #88a;
-o-box-shadow: inset 0 1px 4px #88a;
-ms-box-shadow: inset 0 1px 4px #88a;
box-shadow: inset 0 1px 4px #88a;
}
/* These MUST be separate rules to work */
input::-webkit-input-placeholder { color: #aac; text-shadow: none; }
input:-moz-placeholder { color: #aac; text-shadow: none; }
input:-ms-input-placeholder { color: #aac; text-shadow: none; }
input.input-submit {
padding: 4px 30px 5px;
background: #446;
border: 1px solid #88a;
color: #dfdfef;
font: bold 18px/20px georgia,serif;
text-transform: uppercase;
/* Creates two shadow effects: outer and inner */
-webkit-box-shadow: 0 1px 6px #88a, inset 0 -1px 10px white;
-moz-box-shadow: 0 1px 6px #88a, inset 0 -1px 10px white;
-o-box-shadow: 0 1px 6px #88a, inset 0 -1px 10px white;
-ms-box-shadow: 0 1px 6px #88a, inset 0 -1px 10px white;
box-shadow: 0 1px 6px #88a, inset 0 -1px 10px white;
}
aside h2,aside ul {
margin: 0 0 10px;
}
aside ul {
padding: 10px 0 0;
border-top: 1px solid #dfdfef;
}
aside li {
padding: 0 5px 10px;
border-bottom: 1px solid #dfdfef;
}
footer {
clear: both;
}
footer p {
margin: 0;
color: #889;
font: italic 12px/1.67em sans-serif;
text-align: center;
text-shadow: 1px 1px 1px white;
}
在浏览器中加载你的 HTML 文件,你会看到你的样式标记(见图 3-5 )。
图 3-5。将使用 Pusher 发送和接收消息的样式化页面
现在您已经有了一个页面,它的 UI 区域被指定用于发送和接收消息,我们可以开始通过发布和订阅来添加实时功能。查看 HTML,我们知道表单将条目提交到一个名为post.php
的文件中;让我们从创建该文件并包含 Pusher PHP 库开始。
从https://github.com/pusher/pusher-php-server
下载 Pusher PHP 库,并将lib
目录复制到保存 HTML 文件的同一个文件夹。
保存后,我们可以创建一个新的 Pusher 对象,并开始用几行简短的代码向post.php
发送数据:
<?php
ini_set('display_errors', 1);
error_reporting(E_ALL);
require_once 'lib/Pusher.php';
// Make sure you grab your own Pusher app credentials!
$key = '1507a86011e47d3d00ad';
$secret = 'badd14bcd1905e47b370';
$app_id = '22052';
$pusher = new Pusher($key, $secret, $app_id);
if (isset($_POST['name']) && isset($_POST['message']))
{
$data = array(
'name' => htmlentities(strip_tags($_POST['name'])),
'msg' => htmlentities(strip_tags($_POST['message'])),
);
$pusher->trigger('exercise-3-2', 'send-message', $data);
}
前两行打开错误报告以使调试更容易(您可以在生产站点中关闭它)。接下来,我们包括 Pusher PHP 库,定义应用凭证(不要忘记插入您自己的凭证)并实例化一个新的Pusher
对象,存储在$pusher
中。
接下来,脚本检查两个必需的表单值,name
和message
,如果它们确实存在,就将它们存储在一个数组中。然后使用trigger
方法将数组传递给 Pusher,这将触发我们命名为exercise-3-2
的通道上的send-message
事件。传递给trigger
方法的事件数据将作为 JSON 发送,由库为我们处理。
此时,提交表单将导致 Pusher 发送一个事件,但是在我们看到应用中的效果之前,我们需要使用 JavaScript 添加一个事件处理程序。但首先,我们至少可以使用 Pusher 调试控制台手动检查事件是否被触发(参见图 3-6 )。
图 3-6。触发事件作为 API 消息出现在 Pusher 调试控制台中
为了在我们的应用中可视化这个消息,我们需要编写 JavaScript 来连接 Pusher、订阅一个通道并绑定到一个事件。
我们的 HTML 文件包含一个名为init.js
的脚本。因此,让我们在一个名为scripts.
的目录中创建该文件,我们将添加两个代码块:一个将侦听由我们的服务器端脚本触发的事件,另一个将捕获表单提交并将它们发送到post.php
而不刷新页面。首先,让我们为自定义推送器事件添加一个事件监听器:
(function($){
// Handles receiving messages
var pusher = new Pusher('1507a86011e47d3d00ad'),
channel = pusher.subscribe('exercise-3-2);
// Adds an event listener for the custom event triggered by Pusher
channel
.bind(
'send-message',
function(data) {
var cont = $('#messages');
// Removes the placeholder LI if it's present
cont.find('.no-messages').remove();
// Adds the new message to the page
$('<li>')
.html('<strong>'+data.name+':</strong> '+data.msg)
.appendTo(cont);
}
);
// TODO: Handle form submission
})(jQuery);
这使用我们的应用 API 键创建了一个新的Pusher
对象—(同样,不要忘记使用您自己的键),然后订阅我们在服务器端使用的Exercise 3-2)
通道。
接下来,我们将一个事件处理程序绑定到通道来捕获send-message
事件。当它被触发时,我们获取无序的消息显示列表,确保移除占位符消息(如果它存在的话),然后将新消息追加到列表的底部。
为了防止页面重新加载,这将从我们的应用中删除 JavaScript 从我们的浏览器添加的任何消息,我们需要添加第二个代码块来捕获表单提交,并通过 AJAX 而不是使用页面刷新来发布它们。通过插入粗体代码来添加:
(function($){
// Handles receiving messages
var pusher = new Pusher('1507a86011e47d3d00ad'),
channel = pusher.subscribe('exercise-3-2);
// Adds an event listener for the custom event triggered by Pusher
channel
.bind(
'send-message',
function(data) {
var cont = $('#messages');
// Removes the placeholder LI if it's present
cont.find('.no-messages').remove();
// Adds the new message to the page
$('<li>')
.html('<strong>'+data.name+':</strong> '+data.msg)
.appendTo(cont);
}
);
// Handles form submission
$('form').submit(function(){
// Posts the form data so it can be sent to other browsers
$.post('post.php', $(this).serialize());
// Empties the input
$('#message').val('').focus();
// Prevents the default form submission
return false;
});
})(jQuery);
这段代码捕获submit
事件,将序列化的表单数据发送到post.php
,并清空消息输入。它保持姓名输入不变,使重复的信息更容易发送;为此,它还将焦点放回到消息输入上。
通过返回false
,默认表单提交被阻止,这阻止了页面的重新加载。
现在您已经准备好测试这个应用了。在浏览器中加载您的 HTML 文件并发送一两条消息;消息如预期的那样显示在右侧的列表中。但这还不是最激动人心的部分。
要了解实时的威力,请在两个不同的浏览器(或同一浏览器的两个窗口)中加载这个测试,并开始发送一些消息。没有轮询和页面刷新,你会看到一个窗口中的事件影响了另一个窗口的显示(见图 3-7 )。这是实时的。
图 3-7。在一个浏览器中发布的消息会实时显示在另一个浏览器中
调试您的 Pusher 应用
作为一名开发人员,你会知道事情并不总是按照计划进行。当这种情况发生时,您需要开始调试您的应用,尝试并找出为什么您期望发生的事情没有发生!
如果你发现自己处于这种情况,开发者工具是你最好的朋友。幸运的是,所有主流浏览器供应商现在都为您提供了一套很好的工具,让您可以访问 JavaScript 控制台、动态执行代码、调试运行中的代码、检查网页中的元素等等。托管服务也提供了良好的开发工具,让您可以看到服务内部发生了什么,并执行一些手动测试。我们在前面的 Pusher 调试控制台和事件创建器中已经看到了这一点。
无论你是使用 Pusher 还是其他实时网络技术,浏览器开发工具都是你的开发者宝库中必不可少的一部分。使用我们已经讨论过的功能,比如使用console.log
来跟踪代码的哪些部分被调用,检查变量值是简单的第一步,使用浏览器开发工具断点或debugger
语句来中断执行代码也非常方便。
如果您正在使用第三方库,就像我们在这里使用的 Pusher JavaScript 库一样,库必须公开一种跟踪它正在做什么的方法。Pusher 库通过将一个Pusher.log
属性作为其 API 的一部分来实现这一点。作为最后一个练习,让我们看看如何使用它——在构建应用时,它可能会派上用场。
练习 3-3:推进式测井
与大多数 Pusher 功能一样,在 Pusher JavaScript 库中公开日志非常容易。您所需要做的就是给Pusher.log
属性分配一个函数。将下面的代码添加到init.js
文件中:
Pusher.log = function( msg ) {
if( console && console.log ) {
console.log( msg );
}
};
如果您现在在浏览器中导航到 HTML 文件并打开您的 JavaScript 控制台,您将看到 Pusher JavaScript 库记录信息,这在开发过程中非常有用(参见图 3-8 )。与大多数开发日志记录一样,在将应用投入生产之前,应该考虑删除这种日志记录功能。
图 3-8。记录 Pusher JavaScript 库的内部工作
摘要
在大约 60 行完整的注释代码中,您已经在 web 应用中实现了一个基本的实时层。当你使用 Pusher 时,真的很简单。
在下一章中,我们将更详细地计划我们将要构建的应用,从我们为什么选择 web 而不是 native 开始。
REST 纯粹主义者可能会认为 Pusher 的 REST API 不是严格 RESTful 的。我们可以将它列为 Web 或 HTTP API。
2
3
4
5
四、选择网络应用而不是本地应用
对于任何梦想创建新应用的开发人员来说,首先必须考虑的一个问题是平台的选择:这个应用应该基于网络并考虑移动应用还是只支持 iPhone?是否有受众支持黑莓版本的应用,或者大多数人只会在笔记本电脑上使用这款应用?
本章旨在涵盖你在回答“web 应用还是原生应用?”时应该考虑的一些因素问题。然后,我们将在问答应用的上下文中回答这个问题,该应用将在本书的其余部分中构建。
为什么重要?
乍一看,这似乎是一个微不足道的决定,不值得思考。然而,这个决定将会影响一切,从你的应用如何编码(很明显),到你如何赚钱(不太明显),到如何维护。
在接下来的几页中,我们将探讨 web 和原生应用的优缺点,然后在我们将要构建的应用的上下文中应用两者的优缺点,这将帮助我们做出明智的决定,即哪种方法最适合我们的情况。
注意每个 app 都不一样,大多数想法都有不止一种有效的执行方法。本章中的决定和观点是作者积累的经验、偏见和偏好的结果,虽然它们可能不完全符合您自己的想法,但它们是创建简单有效的应用的一种方式。
要考虑的因素
根据您正在构建的内容,在决定您的应用应该使用本地技术还是 web 技术时,您需要考虑许多因素。然而,有些因素可以普遍适用于所有应用。我们将在这里讨论其中的一些。
了解你的用户
如果你正在开发一个应用,你真的需要确保你在开发的时候考虑到了你的目标用户。知道谁是你的用户会让你更好地理解你的应用需要运行的平台,平台可以公开的特性类型,以及你有潜力提供给你的用户的功能。用户的类型也可能设置功能期望的级别,但是我们在这里不包括人物角色 1 。
例如,如果您的用户极有可能拥有一部新的智能手机,您可以开始考虑使用更新的技术,并使用设备上的加速计或摄像头等高级功能。这也意味着你有潜力构建运行在现代移动浏览器上的丰富的交互式网络应用,以及本地的 iOS 和 Android 应用。
反过来,了解你的用户也将帮助你识别你真的不应该关注的事情;如果你知道你所有的用户都有一个黑莓手机,那么为 iPhone 开发一个应用就没有意义了(这真的不言而喻,但我们这么说只是为了说明这一点)。
同样值得考虑的是用户可能会在哪里使用您的应用。你可能有一个创新的新控制系统的计划,在这个系统中,用户通过设备摄像头监控的一系列星形跳跃与应用进行交互。但如果你的目标用户可能想在空间有限的火车上使用该应用,这不太可能是可行的。至少,他们会收到其他乘客很多奇怪的目光。
类似地,如果用户在一个连接受限的地方,应用就不应该依赖于从托管的 API 中检索数据。
营销
如果没有人会使用它,那么构建一个功能丰富的应用是没有意义的。因此,让潜在客户了解你的应用对它的成功至关重要。
使用 marketplace 代表着一个确保你的应用——你的产品——至少是可被发现的绝佳机会。在 iOS 设备的 App Store 和 Android 设备的 Google Play 上有超过 50 万个移动应用,这清楚地证明了市场是发现你的应用的好地方。微软紧随其后推出了 Windows 商店。但是,你当然可以争辩说,竞争如此激烈,实际上由于应用的大量选择,找到你的应用非常困难。
注苹果重新定义了应用的含义:从简单地代表一个在某些设备上运行的程序,到一个拥有蓬勃发展的生态系统的整体概念,包括小额支付,当他们创造了应用商店的概念。产品营销、销售和分销的市场:应用。这是一个惊人的营销成就。
除了可以被发现之外,出现在市场中意味着你的应用有机会从市场内审查系统中受益。理论是,如果你开发了一个很棒的应用,你会收到很好的评论,并从应用排行榜的飙升中受益。
Marketplaces 最初是为本地应用创建的,事实证明非常成功。这个想法被复制到网络应用中,但成功的几率要小得多。有人在努力改变这一点,比如谷歌 Chrome 网络商店、2Firefox market place、 3 脸书应用中心、 4 甚至是苹果的“网络应用”目录、 5 但这种吸收比他们的原生应用要慢得多。
然而,建立和控制的市场也有不利的一面。苹果因其对 App Store 中什么可以什么不可以的奇怪且有时完全独裁的控制而臭名昭著,这对应用开发者来说可能是一个巨大的路障。苹果的审批流程给应用开发者带来问题的最著名案例之一是谷歌语音,它在向应用商店提交其 iOS 应用时被拒绝 6 。
对谷歌来说幸运的是,网络应用可以绕过苹果的审批程序,允许用户从任何装有 HTML5 浏览器(iPhone 有)的设备上访问它们。谷歌把它的语音服务变成了一个网络应用,而苹果无法阻止它向用户提供这项服务7。
注意苹果已经让步,现在允许谷歌语音在应用商店发布其应用。 8
长期以来,产品成败的关键因素之一是强大的在线存在。由于网络应用存在于网上,它们自然会存在,并且可以通过自然搜索从搜索引擎的发现中受益。相比之下,本地应用必须创建一个全新的营销网站,或者简单地依赖 marketplace web 列表作为在线表示和自然发现的来源。
销售
一旦你的营销发挥了它的魔力,潜在客户即将转化为真正的用户,你就需要一个销售机制。即使你正在放弃你的申请,仍然有一个销售方面;用户需要承诺以某种方式使用你的应用(例如,注册或下载)。
这里首先要考虑的是信任。如果你已经创建了一个本地应用,并通过一个可信的市场进行销售——由一个大品牌如苹果、谷歌或微软支持——用户已经放心了,该应用已经作为市场提交过程的一部分被审查。此外,他们为应用进行的任何支付都是通过市场进行的,而不是直接通过应用或其他第三方支付系统。
web 应用不会受益于这种继承的信任。它需要通过看起来专业来建立信任;驻留在加密(SSL)连接上;并通过可信、可靠和专业的机制提供支付。消费者对在手机上输入信用卡信息犹豫不决,这种犹豫不是没有根据的:在触摸屏上填写表格并不容易,更难的是在填写时掏出信用卡。再加上有人可能在公共场合使用手机,输入财务信息就更没吸引力了。
因此,处理交易和安全存储支付细节的第三方支付提供商(如 PayPal、Square、Dwolla、GoCardless 等)发现他们的服务需求越来越大也就不足为奇了。尽管与这样的支付提供商集成变得越来越容易,但仍然需要额外的开发人员努力。
然而,本地应用通过各自的市场拥有内置的商业平台,允许预先定价购买应用、应用内购买、付费升级等。
这并不是说没有很多其他的方法来赚钱,无论是本地的还是非本地的,但这种讨论超出了本书的范围。
分配
付费或免费注册完成后,客户现在需要访问您的应用才能实际使用它。
原生应用市场提供了一个屡试不爽的下载机制,这也增加了实际购买东西的感觉。应用下载、安装、启动和运行速度非常快。
但不可否认的是,互联网正变得越来越适用于所有设备,而且这一趋势没有停止的迹象。因此,市场上的设备越来越多(从 iPhones 和 iPads 到亚马逊 Kindle Fire 运行 Android、BlackBerry、Windows Phone 等的智能手机),这些智能手机的现代网络浏览器能够完成桌面浏览器所能完成的大部分工作。这为开发人员提供了一个独特的机会,否则他们需要创建许多本地应用来提供无处不在的支持,只需为所有设备创建一个 web 应用,通过导航到 web 浏览器中的 URL 即可立即使用。
在应用更新时,Web 应用提供了一个明显的优势。你不用等着用户花时间把你的应用更新到最新版本,你只需要在你的服务器上更新 web 应用,用户下次在线加载你的应用时就会得到新的版本。这是一个巨大的好处,特别是对于感受用户如何与一个新的应用交互;随着新功能的推出和旧错误的消除,最新和最棒的应用版本可以立即提供给用户群。
从用户的角度来看,通过支付升级费用,或者通过手动或自动升级,通过市场机制,可以相对容易地应用对原生应用的更新。然而,开发者必须向市场提交更新,并等待验证过程完成。这意味着简单或紧急的应用升级可能需要一段时间才能分发。
外观、感觉和性能
当开发一个本地应用时,该平台的外观和感觉经常被理解和很好地定义。虽然有 web 应用用户界面(UI)框架可用,但它们不会完全符合运行它们的平台的原生感觉。此外,每个平台的 UI 可能不同,这意味着在 web 应用中检测到这一点,并为每个平台维护不同的皮肤。即便如此,你也无法从一个 web 应用中控制浏览器 chrome,因此它永远不会与原生应用的外观完全一致。
但是,重要的是要记住,要试着去拥抱网络这个平台,并利用网络的优势。
这是一个笼统的说法,当然也有例外,但因为本地应用通常是基于设备的本地语言构建的,所以它们通常会感觉更流畅一些。
从启动本地应用的过程开始:在本地应用中,你找到应用的图标,点击它,它就启动了。对于 web 来说,这是不同的,有点复杂;首先打开浏览器应用,然后导航到一个 URL 或打开一个书签。如果你正在使用多个网络应用,那么你也需要在标签之间切换应用。这导致用户使用 web 应用时的体验不如使用本地应用时那么流畅。
因为 web 应用不是本地运行的,所以跳转、停顿、停顿和其他故障的风险更高,这些故障往往会降低用户体验的质量。这并不是说您不会在原生应用中实现这一点,但是当在浏览器应用(它本身运行在操作系统上)中运行 web 应用时,有一个额外的层有可能导致问题。
由于这个原因,web 应用在不降低性能的情况下可以添加的华丽的附加功能的数量上更加有限。
浏览器供应商、HTML5 框架、 9 和初创公司 10 已经取得了一些重大进展,以应对这种性能冲击,情况只会有所改善。但是,在某些情况下,如密集型游戏,原生仍将是最好的解决方案。
发展
尽管我们已经触及了一些影响开发人员的因素,但是我们应该更多地关注那些能够影响开发的因素。因此,当考虑“web v native”时,让我们更深入地挖掘一些真正影响开发应用的因素
语言和平台
HTML5 正在成为跨平台开发技术的首选。尽管不是严格意义上的语言,HTML5 包括用于视图标记的 HTML、用于视图样式和动画的 CSS 以及用于应用逻辑的 JavaScript。如果你使用其中的一种,你很可能也会使用其他的。
如前所述,web 应用可以在任何装有支持该应用所用技术的 web 浏览器的设备上使用。今天的大多数智能手机都有支持 HTML5、CSS3 和 JavaScript 的 web 浏览器。最重要的是,市场上“傻瓜手机”的比例正在迅速下降(此外,傻瓜手机也不倾向于提供特别有趣的本地应用,所以它们是一个有争议的问题)。
当开发移动或平板电脑的 web 应用时,而不仅仅是桌面,唯一增加的负担是考虑移动设备呈现的新屏幕尺寸。大多数网页设计的常见技术,如 HTML5 和 CSS3,都受到大多数移动设备的支持,这意味着在旧版本的 Internet Explorer 上运行应用的所有功能可能比在移动浏览器上运行更困难。
软件开发中一个非常强有力的原则由首字母缩略词 DRY 代表,它是“不要重复自己”的缩写。通过专注于构建一个单一的响应式网络应用将所有支持的平台整合到一个集中的代码库,这是向干式应用迈出的一大步。
有 PhoneGap 11 和 Trigger.io 12 等平台旨在将一个 web app 做成各种平台专用的 app。诸如此类的工具的存在证明了本地应用的市场和学习如何为不同平台编码的困难。微软甚至创建了一个名为 WinRT, 13 的应用架构,允许应用在 HTML5 中开发,使原生 Windows 8 应用和 web 应用使用相同的代码库成为可能。
如果你决定专注于构建本地应用,你很可能会专注于 iOS 设备的 Objective-C,Android 的 Java,以及 Windows 设备的 WinRT 支持语言。学习一门新的编程语言是一件很棒的事情,但是对于一个不知道为给定平台选择哪种语言的开发人员来说,这确实会增加几天甚至几周的开发时间。
如果您的原生应用是为一个被另一个平台取代的平台开发的,您别无选择,只能为新系统重新构建应用。比如,任何为 Symbian 开发的应用都在迅速变得无关紧要,任何 Windows Phone 7 应用都无法升级到 Windows Phone 8;它们必须被重建。
这带来了风险,因为原生应用受其构建平台的影响很大。如果由于一些反常的事情发生,苹果突然关闭了应用商店,开发者将没有追索权或快速修复;他们将不得不重新开始,为另一个平台开发他们的应用,或者干脆放弃,去做别的事情。
维护
维护一个本机应用可能与维护一个 web 应用差不多(甚至更简单,取决于平台)。当面对多平台应用时,维护问题就出现了:如果一个应用需要在 iOS、Android、黑莓和 Windows Phone 上运行,它就会变成四个。突然之间,大多数维护都乘以 4,这甚至没有考虑基于网络的配套工具或应用的 web 应用版本的强烈可能性。
最重要的是,更新必须由用户下载,所以除了市场提示、你的鼓励和支持文档中的注释,没有什么可以保证用户能够获得最新版本的应用。
对于 web 应用,无需对该应用的 Android、iOS、BlackBerry 和 Windows Phone 版本进行修复,只需更新一个版本的代码(假设该应用是为响应而构建的)即可清理该应用设计的任何设备上的问题。
测试
当创建一个 web 应用时,努力创建一个“一刀切”的应用。因此,它需要对所有需要支持的设备进行认真的测试。理想情况下,它甚至应该跨每个平台的不同版本进行测试。
原生应用提出了与测试网站跨浏览器支持类似的问题:iPhone 的修复可能会在 Android 上引入新的错误,一个版本的 Android 的变化可能会在另一个版本的 Android 上产生意想不到的副作用,并且总有可能某个功能不能在每个设备上以相同的方式工作。
功能支持
如果你的应用需要访问设备的加速度计或摄像头,那么争论就结束了,除非你确切知道你的用户将在他们的手机上使用什么浏览器。例如,要从浏览器访问设备上的网络摄像头,你需要使用getUserMedia
API,即使是主流浏览器的最新版本也不支持它; 14 一如既往,Chrome、Firefox、Opera 一路领先。
如果你不知道你的 web 应用的所有用户是否都支持它需要的所有特性,你最好的选择是创建一个本地应用。
操作系统向本机应用公开对大多数硬件功能的访问。浏览器正在慢慢地公开对这些特性的访问,但是它们仍然不能跨浏览器使用。一个这样的例子是方向控制。在某些应用中,您可能总是希望用户以横向模式查看,例如,一个赛车游戏,但是尽管有检测方向初始状态和变化的方法,但目前在 web 浏览器中没有 API 来控制这一点,所以唯一的解决方案是有效的黑客攻击。 十五
现代移动 web 浏览器在不断发展,提供 API 来访问硬件和其他操作系统功能的浏览器的数量也在不断增加。但是现在,本机应用具有优势,因为它提供了对应用可以访问的硬件和操作系统功能的完全控制。
连通性
我们生活在一个联系越来越紧密的世界,所以很多应用依赖于连接也就不足为奇了。
为了最初获取我们的本地或 web 应用,我们需要一个互联网连接。应用提供的许多功能也需要连接,例如任何利用其他互联网服务或 API 的功能(例如:公共交通应用,在待办事项或笔记应用(如 EverNote)等设备之间同步信息的功能,或地图应用,这些应用绝不能存储用户可能请求的所有信息)。
其中一些应用,如 EverNote,在离线时仍然可以提供有用的功能。但其他公司,如 Skype,没有连接就无法提供任何核心功能。他们还不如拒绝开门。
一旦本机应用下载到您的设备上,所有资源(至少在大多数情况下)现在都存储在设备上,这意味着它应该可以离线使用。但是许多应用假定连接性会导致非常令人沮丧的用户体验。
这种情况没有真正的原因,web 应用也是如此。HTML5 有定义良好的离线支持, 16 包括应用文件缓存和本地存储,所以在开发人员的关心下,有一个工作的离线 web 应用是可能的,就像本地应用应该离线工作一样。
根据需求选择
鉴于本地应用和网络应用各有利弊,很明显,在为应用选择平台时,没有明确的正确答案。有太多的因素影响了这个或那个仅仅是“更好”的决定。
相反,应用开发人员应该花合理的时间考虑他们的应用,它将如何使用,它将做什么,以及他们打算如何赚钱。在他们弄清楚需求后,他们可以通过前面列出的利弊来运行他们的应用,以及针对他们的情况的任何其他考虑,并选择最有利于他们的应用的方法。
选择网络应用而非本地应用
对于您的应用项目,让我们根据之前的利弊来权衡应用的拟议功能,以针对这种情况做出最佳决定。
这个应用是做什么的?
虽然我们将在下一章中更深入地了解这一点,但这个应用提供了一个简单的界面,让观众可以向演示者提问,演示者也可以实时接收这些问题。
这个 App 是怎么赚钱的?
出于本书的目的,该应用不赚钱,但如果它将被货币化,不太可能有人会愿意支付 0.99 美元来问一个他们可以简单举手的问题。
向演示者收费来创建一个房间可能更有意义,因为该应用可以帮助他们控制房间,并确保所有问题都得到回答。主持人可能会在布置房间之前购买房间,这在桌面浏览器上最容易做到,因此通过网络界面付款可能不会成为一个障碍。
人们将如何使用这个应用?
据推测,用户将在演示者举办活动的物理空间中。这可能是一所大学的教室,一个会议的会议室,或者一个大公司的主题演讲的会议室。
因为与会者很可能不在办公桌前,所以可以安全地假设她可能会使用笔记本电脑、平板电脑或手机与发言者进行互动,因此该应用应该支持尽可能多的设备。
这个应用需要访问任何设备硬件吗?
该应用不需要访问用户的任何手机硬件,如相机,所以这不是你需要为这个项目担心的事情。
最后的决定是:让我们构建一个 Web 应用
鉴于这款应用不需要访问设备硬件或收取访问应用的费用,鉴于用户群不可预测地使用任何特定设备,并且观众与演示者之间的通信绝对需要互联网连接,因此将我们的应用构建为 web 应用是最有意义的。
摘要
在这一章中,你学习了构建本地应用和 web 应用的优点和缺点。因为没有正确或错误的答案,应用开发人员需要单独考虑每个项目,以确定哪种方法对他们最有利。
在下一章,我们将进入应用的细节,以确保在我们开始编码之前,我们所有的鸭子都在一条线上。
1
2
3
4
5
6
7
8
9
10
11
12https://trigger.io/
13http://en.wikipedia.org/wiki/Windows_Runtime
14
15
16
五、确定应用的功能和结构
现在我们知道我们正在构建一个 web 应用,并且我们已经准备好了工具,我们可以开始真正规划应用的功能了…在本章中,您将:
- 定义应用的功能
- 确定用户将如何与之交互(以及基于用户角色的不同之处)
- 为网站的后端代码和数据库制定一个计划
- 想出一个前端的结构
- 为应用创建一个简单的线框
在这一章结束的时候,你应该对应用的构建有一个非常清晰的想法;这将消除浪费的开发时间,并有助于保持应用正常运行。
这个应用是做什么的?
在我们计划任何事情之前,你需要一个非常清晰简明的特性列表。这将检查我们的特性列表,并在您开始设计和开发时帮助避免任何范围蔓延。
给应用一个使命宣言
让我们从应用的高层次概述开始,这将指导您做出关于应用功能的所有决策。
用一小段话来说,让我们想想如何描述这个应用:
这是一个简单的 Q &一个网络应用,允许特定事件的参与者向演讲者提问,并确认另一个参与者的问题也与他们相关,所有这些都是实时的。此外,演示者应该有一个问题列表和每个问题的提问人数,她可以在向小组给出答案时从中标记出已回答的问题。
App 不做什么?
与应用能做什么相比,同样重要的是它不能做什么。这将有助于决定某个功能是否应该包含在应用中。
该应用旨在成为现场活动的演示者和参与者的工具,最有可能在与其他参与者相同的物理空间中使用。所以 app 应该而不是:
-
尝试取代现场讨论。
-
这意味着应用本身不允许评论或回答;它只是提出问题,而不是回答或澄清问题。允许在应用中讨论可能会分散对实际演示的注意力。
-
提供有关会话的任何附加信息。
-
该应用不会告诉与会者会议的内容,也不会提供任何资源。焦点仍然在演示者身上,因为这个应用并不参与演示,只是问答。
-
为演示者提供任何组织工具。
-
演示者的仪表板只是一个问题列表,按受欢迎程度排序,可以标记为“已回答”这不是帮助演示者更好地进行演示的工具;它可以帮助他们回答房间里的问题。
用户会扮演什么角色?
既然我们知道了应用做什么——提问和回答问题——我们就可以定义与它交互的不同类型的用户。
因为这是一个非常简单的应用,所以只有两个用户角色:演示者和参与者。
提出者
演示者将在应用的主视图上创建一个房间,然后使用创建房间时生成的链接与与会者共享该房间。将向他提供一个结束会话的按钮,关闭该按钮以回答进一步的问题。
当提问时,它们会实时出现在演示者的屏幕上,并带有一个 UI 元素,允许他将问题标记为已回答。
出席者
与会者将通过单击演示者提供的链接或输入房间 ID 来加入房间,房间 ID 将是 URL 中不是域名的部分(即http://example.com/1234
,其中1234
是房间 ID)。
进入会议室后,与会者将看到之前在此会议中提出的问题(如果有),并可以选择对该问题进行投票表决。任何新问题都会实时出现在屏幕上。
还将有一个表格,与会者可以通过它提出自己的问题,这些问题将被添加到她已经投票表决的问题列表中。
最后,如果一个问题没有得到满意的回答,或者这个问题不适合整个小组,将会有一个链接直接给演示者发电子邮件。
前端规划
你的下一步是开始充实你将采用的应用前端的方法,或者用户将与之交互的应用的可视部分。
在本节中,您将为所有前端技术组织您的开发方法。
我们在使用什么技术?
我们已经讨论了我们将用于此应用的工具,但概括一下,您将使用以下内容构建网站的前端:
- HTML5
- CSS3
- Java Script 语言
- 框架
- Pusher JavaScript 库
您将使用每种技术的多个方面来为应用制作一个好看、易用、简单的前端。
使用 HTML5
和几乎所有的 web 应用一样,前端将使用 HTML 标记构建。对于这个应用,您将利用 HTML5 中引入的一些新元素和属性。
您可能已经见过或使用过任何新元素,比如<article>
和<footer>
元素。如果您没有,它们在功能上与<div>
元素相同,但是从语义的角度来看它们更好,因为它们是自描述的。
你还将利用新的<input>
类型——即email
类型——来鼓励触摸屏设备定制键盘,以适应输入的数据。
最后,您将使用新的数据属性将信息传递给级联样式表(CSS)和 jQuery,用于各种目的。我们将在下一章更深入地探讨如何和为什么这样做。
用 HTML 实现极简主义
小心以下是作者的边界咆哮。
好的 HTML 标记是一个相对的术语。怎么好?有功能就好吗?格式良好?有效吗?
像编程中的几乎所有东西一样,HTML 标记“好”的大部分原因完全是主观的。没有全球公认的“正确方式”来编写标记,这可能是一件好事,因为它允许开发人员创造性地使用 HTML 元素来创建真正聪明的布局。
然而,有种绝对错误的方式来编写标记。比如对每个应用的 CSS 类使用不同的嵌套<div>
是错误的;这只是草率的编程。使用完全不透明的类名是错误的。把适当的缩进和嵌套抛到九霄云外是错误的。
对于这个应用,我们将 HTML 视为一个舞池:它需要清除任何碎片和障碍,以便呈现的数据不会被绊倒。
让我们回顾一些最常见和最有害的标记实践,并计划而不是使用它们中的任何一个。
滥用
元素
HTML 标记最明显的滥用是为每个要应用的新样式应用新的<div>
元素。这不仅给文档对象模型(DOM)增加了更多的元素,使 jQuery 遍历这样的事情变得更加复杂和低效,而且使以后的维护成为一场噩梦,尤其是当其他人不得不处理您的代码时。
使用完全不透明的类名
另一个令人沮丧的标记失误是使用了神秘的类名和 id。在 99.999%的情况下,通过将类名float-right
缩短为fl
来节省几个字节的好处完全被令人头痛的事情抵消了,因为这使人们不得不在以后试图理解和维护代码。
完全忽略缩进
缩进在 HTML 中是可选的,因此它在很大程度上被许多开发人员忽略了。这不一定会损害他们的工作质量,但会使处理他们的加价变得比必要的更痛苦。
考虑到至少对标记进行排序所需的额外时间可以忽略不计,特别是考虑到大多数现代集成开发环境(IDEs)默认情况下会正确缩进代码——不花一点时间来确保以后必须处理标记的人可以通过添加一些空白来阅读它,这简直是懒惰。
好的标记与坏的标记的例子
为了展示关注标记所能带来的巨大差异,让我们来看一个非常小的例子,这个例子比较了一个糟糕的标记片段和一个完成相同任务的重写的片段。
错误标记的示例
首先,我们来看看不好的标记。在不到 3 秒钟的时间内,您能找出这是什么,以及如何解决额外的保证金问题吗?
<td><div class="sb"><div class="im csh"><a
href="http://www.example.com/inventory/1234/"
title="Example Product"><img width="150" height="150"
src="http://www.example.com/img/1234.jpg" class="at"
alt="Example Product" title="Example Product" /></a></div></div></td>
这段代码完全有效,但是太糟糕了。这些<div>
都是从哪里来的?sb
班到底是干什么的?那么csh
、im
或者at
呢?要解决这个假设的边距问题,您必须检查这个代码片段中引用的每个类,然后才能确定有问题的样式。
最重要的是,由于完全没有格式化,如果不花额外的时间真正查看,很难说出这个片段是什么。这将会减慢将来的维护,尤其是如果有其他人在做的话。
同样的例子,但是有更好的标记
将前面的标记与下面的代码片段进行对比:
<td class="show-badge">
<a href="http://www.example.com/inventory/1234"
class="image cushioned"
title="Example Product">
<img src="http://www.example.com/img/1234.jpg"
class="product-thumbnail"
alt="Example Product" />
</a>
</td>
你花了多长时间才发现这是一个产品的图片缩略图?不长,尤其是与原始片段相比。
多余的<div>
已经被删除,因为所有这些样式都可以应用于<td>
或<a>
元素。类名已经扩展到人类可读的程度。(据猜测,我们假设的保证金问题可能来自于cushioned
类。)元素已被格式化,以便于扫描。
对你的标记做这些小小的调整将会多花几秒钟,但是会节省小时的沮丧。所以你应该去做。
CSS3,媒体查询 ,以及它如何影响设计和 HTML
为了设计应用,你将使用 CSS,包括 CSS3 的一些新特性。使用像box-shadow
和border-radius
这样的功能将允许你给你的应用一层设计润色,而不必处理大量的图像。
您将实现的 CSS3 最令人兴奋的新特性可能是使用媒体查询的自适应布局。这将允许您基于浏览器的宽度应用样式,消除浏览器嗅探、应用的多个版本或仅在特定设备子集上看起来不错的布局的需要。
然而,使用媒体查询带来了一些问题,应该在应用的设计和标记阶段解决。因为布局会改变以最适合它所显示的屏幕,所以从一开始就需要考虑布局的一定程度的灵活性。
一列和两列布局
当查看实现自适应布局的网站时,最常用的技术之一是在较小的屏幕上查看网站时,从多列变为单列。
乍一看,这似乎没什么大不了的,但是如果内容需要以除了第一列、第二列等等之外的任何方式流动(反之亦然),这可能会给布局带来麻烦。(例如,如果广告突然被推离所有最新内容下方的屏幕,侧边栏中有广告的网站可能会与广告商陷入困境。)
练习 5-1:使用响应式布局
为了展示 responsive layout 可能带来的内容回流挑战,我们来看一个示例站点。
在这个练习中,你负责将图 5-1 中的设计转换成一个响应式布局。
图 5-1。需要转换为响应布局的示例网站
这是一个非常标准的布局:一个页眉、一个页脚、一个包含博客条目的左侧栏和一个包含标准“侧边栏”内容(广告和时事通讯截图)的右侧栏。
在小屏幕上重排内容
从逻辑上讲,当站点在手持设备上呈现时,并排显示两列是没有意义的;内容会过于混乱和狭窄。自然的解决方案是切换到单列布局,这允许内容以更美观的方式显示。
然而,这个网站的广告商不会容忍广告被埋在博客条目下面——随着更多内容的积累,可能会有多达八个条目显示在左栏中——所以简单地将左栏堆叠在右栏之上不会有所减少。
要解决这个问题,您需要以某种方式将左栏的内容与右栏的内容交错。这需要对标记进行一些调整,以允许主要内容区域的每个部分独立流动。
通常,像这样的设计可能会被标记为如下形式(简化):
<header>...</header>
<section id="main-content">
<article>...</article>
<article>...</article>
</section>
<aside id="sidebar-content">
<div id="ads">...</div>
<div id="newsletter">...</div>
</aside>
<footer>...</footer>
然而,这会将列放入不允许您重排内容的“框”中。要解决这个问题,您需要让每一部分内容都有自己的盒子:
<header>...</header>
<article>...</article>
<aside id="ads">...</aside>
<article>...</article>
<aside id="newsletter">...</aside>
<footer>...</footer>
这种标记类似于单列布局的外观:以一个条目开头,然后显示广告,然后显示另一个条目,最后显示新闻稿注册。将来,更多的条目将出现在时事通讯的下方。
现在,当屏幕足够宽时,您可以使用媒体查询将内容排列成两列,但当屏幕不够宽时,可以按可接受的顺序堆叠内容。
通过媒体查询进行内容重排
要让内容正确显示,请从最小的屏幕尺寸开始。标记没什么特别的;只是一些基本的风格:
article {
position: relative;
margin: 0 0 2em;
overflow: hidden;
}
aside {
margin: 1.5em 0;
padding: 1.5em 0;
border-top: 1px dashed #955240;
border-bottom: 1px dashed #955240;
}
然后,当屏幕足够宽时,应用附加样式将内容分成两列。对于本例,应用这些规则的媒体查询被设置为匹配任何小于 768 像素的屏幕,这是一种常见的平板电脑分辨率。
这种风格并不疯狂:博客条目向左浮动,侧边栏内容向右浮动,将内容分为两栏:
@media screen and (min-width: 768px) {
article {
position: relative;
float: left;
width: 55%;
margin: 0 3% 2em 0;
}
aside {
float: right;
width: 42%;
margin: 0 0 2%;
padding: 0;
border: none;
}
}
注意百分比的使用允许列根据屏幕大小增长或收缩。在桌面浏览器中查看此布局时,您可以调整窗口大小来查看内容重排,以利用可用空间。
一旦布局完成,窄屏幕将看到一个单列布局,广告可接受地位于第一个条目的下方(图 5-2 )。
图 5-2。从左到右显示回流内容的三个屏幕
注要查看实际布局并查看上述练习的完整源代码,请访问https://github.com/jlengstorf/responsive-design
。
可点击区域和胖手指
设计将在移动设备上使用的应用的另一个不同之处是当点击(或点击)时精确度的损失。如果你曾经试图在手机上使用一个专为全尺寸浏览器设计的网站,你可能会经历这样的挫折:试图点击列表中显示的链接,但由于手指太大而无法准确选择,所以意外地选择了错误的链接。
由于这个原因,你的用户界面应该有大按钮,并确保链接周围有足够的空间,以确保用户即使用胖手指也能轻松点击它们。
效果和动画
为了让应用感觉像是在响应用户动作,您还需要实现效果和动画,包括指示用户选择了哪个输入或控件(包括键盘控制)的代码,以及表示请求的动作已经执行的简单动画。
投票表决一个问题
当与会者点击某个问题的向上投票按钮时,您会希望添加一种效果来提供视觉反馈。在这种情况下,只需突出显示控件并增加投票计数就足够了。
回答问题
当演讲者回答问题时,它应该停留在屏幕上,但移到不太突出的地方。您需要实现一些效果来对问题列表进行重新排序,将已回答的问题移到未回答的问题下面,以及降低其不透明度来保持对未回答问题的关注。
来自 UI 元素的反馈
用户可以在应用中的各种 UI 元素上悬停或切换,所以你要确保添加效果,让他们知道什么是可点击的,或者哪个表单元素有焦点。这可以用 CSS 来完成。
其他影响
每当其他用户执行某些操作时,网站的后端将通过 Pusher 用新数据更新应用,因此必须有适当的效果来处理 DOM 操作以显示更新。我们将在后面的章节中更深入地讨论这些影响。
后端规划
网站的后端是所有用户操作需要被处理和存储的地方。为了使应用易于维护和快速开发,让我们来看看脚本和数据库应该如何组织。
模型视图控制器
web 应用软件设计的行业标准是模型-视图-控制器 (MVC) 模式。有几十种可用的 PHP 框架,其中大多数都是基于 MVC 模式的。
因为它的广泛使用以及它被普遍认为是 web 应用的最佳方法这一事实,您将使用 MVC 模式来构建这个应用。
MVC 编程模式简介
MVC 听起来比实际更复杂。MVC 的核心概念是将任何表示元素(视图,通常是 HTML 标记)与数据(模型,通常是存储在数据库中的信息)和逻辑(控制器,可能是 PHP 代码)分离开来。
使用 MVC 模式的应用有三个非常明显的区别:
- 控制器处理数据和解释用户输入的类和代码。这就是解释用户指令(比如一个页面请求),向模型请求所需的数据,操纵它,并将其发送到视图进行输出。
- 建模读写数据的类。就这样。这些类不对数据进行任何操作,也不生成任何显示给最终用户的输出。
- 这是用来向用户显示信息的代码。这里没有任何形式的逻辑;在 web 应用环境中,视图应该尽可能接近普通的 HTML。
对基于 MVC 的应用的典型请求应该与这些非常相似(见图 5-3 )。
图 5-3。显示典型 MVC 请求的操作顺序的图表
让我们带着我们正在构建的应用,走一遍图 5-3 所示的步骤。
- 用户通过统一资源指示器(URI) 传递命令,与客户端应用(控制器)进行交互。这可能是一个演示与会者填写并提交问题表,或投票表决一个问题。或者可能是演示者将问题标记为已回答。这导致客户端应用向服务器发出请求。
- 控制器处理该请求,基于请求参数确定动作。然后,它根据这些输入与适当的模型进行交互。
- MVC 中的模型也可以表示或包含数据访问层。在我们的应用中,就是这种情况。因此,当用户提交一个问题与模型进行交互时,它会将问题存储在数据库中。如果是问题投票表决的结果,问题已经被投票表决的事实也将被保留。在模型处理了交互之后,它将适当的数据返回给控制器。
- 控制器处理来自模型的响应,并将其传递给视图。
- 视图根据控制器的响应进行更新。正在提交的问题可能会导致视图发生变化,向演示者显示所有问题,同时突出显示新添加的问题。或者在向上投票的情况下,问题可以改变视图中的位置。
用一个愚蠢的类比来解释 MVC
为了以最简单的形式理解 MVC,考虑订购一份比萨饼。你打电话给披萨店,和收银员说,收银员会帮你点菜。当你点完菜后,收银员告诉厨师点了什么,厨师就开始做披萨。披萨出炉后,厨师将披萨交给收银员,收银员将披萨打包后交给送货司机,随后送货司机会将披萨送到你家。
在这个例子中,收银员是控制者。你通过电话与她互动——URI(??)——给她你的命令——?? 的指示(??)——她会翻译。
厨师是型。收银员向他要你要的披萨——数据——他给了收银员。厨师接受请求,制作比萨饼,在烤箱中烹制,然后交给收银员。
然后,收银员准备好要交付的比萨饼— 逻辑—并把它交给司机,司机是从交互中产生的视图。收银员给她指示后,司机把披萨送到你面前——展示——供你享用。司机除了给你送披萨,从来不会以任何方式碰披萨;如果你的披萨外卖司机染指了你食物的原材料,那就太不可思议了。
注 MVC 可以追溯到 1979 年 2 从它的概念提出到现在的 30 多年里,已经被数十种语言的程序员解释、再解释、再解释。实现 MVC 没有绝对正确的方法。这是一个指导发展的哲学原则,而且,和所有的哲学一样,它在不同的阵营中会有不同的实践。不要为此感到紧张。
确定数据库结构
为了存储关于房间和问题的信息,你需要一个数据库。在本节中,您将列出需要存储的数据列表,并确定这些数据的结构。
需要存储哪些数据?
首先,让我们列出应用正常运行需要存储的所有内容。这包括房间和问题信息:
- 房间的名称
- 房间的唯一标识符
- 房间是否处于活动状态
- 演示者的姓名
- 演示者的唯一标识符
- 演示者的电子邮件
- 这个问题
- 问题的唯一标识符
- 不管这个问题是否被回答
- 这个问题的投票数
数据库规范化
为了保持数据库整洁并消除重复信息,需要对数据库进行规范化。 3
为什么数据库规范化很重要
说明数据库规范化为什么重要的最简单的方法是看一个简单的例子。
想象一下,一所大学有一个数据库,需要跟踪它的教授以及每个教授目前在教什么课。它需要存储教授的姓名、电子邮件地址、聘用日期和他们所教班级的 ID。
如果数据库是而不是规范化的,它可能看起来像表 5-1 。
表 5-1 。尚未规范化的表
乍一看,这可能没有错,但是当你开始处理数据时,事情就开始变得有点不可靠了。
例如,当一个新教授被雇用,但还没有上课,会发生什么?该条目将有一个空列,这可能会导致读取该数据的任何应用出现问题。
反过来,一个老师教两个班或者两个班以上怎么办?除了一列之外,所有列都有重复数据(见表 5-2 )。
表 5-2 。一个教授教两个班的非规格化表
还有删除信息的问题:如果汤姆·琼斯停止教 MATH-315,那一行从数据库中删除,他的其他信息也会丢失,这可能不是想要的结果。
规范化数据库
为了使这个数据库更易于管理,应该将其规范化,这将把数据分成两个表:一个用于教授数据(见表 5-3 ),另一个用于将教授映射到他们所教的班级(见表 5-4 )。
表 5-3 。没有班级信息的教授表
名字 | 电子邮件 | 雇佣日期 |
---|---|---|
简·多伊 | jane.doe@example.org | 2004-08-09 |
汤姆·琼斯 | tom.jones@example.org | 2007-01-15 |
表 5-4 。新表格将教授与他们所教的班级对应起来
教授 | 班级 |
---|---|
简·多伊 | ECON-101 |
简·多伊 | ECON-201 |
汤姆·琼斯 | 数学-315 |
通过将数据拆分到两个表中,现在可以在不丢失个人数据的情况下取消汤姆·琼斯作为数学 315 教师的职务,并且也可以让无名氏教两个班而不复制她的所有其他数据。
然而,这个表仍然不理想,因为它在更新教授的姓名时存在困难。例如,如果雇佣了另一个叫 Jane Doe 的老师,如何在教授和所教班级的映射表中唯一地识别出哪个老师是被引用的?可以进一步规范化为教授使用数字 ID 而不是他们的名字,这解决了这个问题(见表 5-5 和表 5-6 )。
表 5-5 。添加了 ID 字段的 Professors 表
表 5-6 。班级信息表现在使用教授的 id 而不是他们的名字
教授 _ID | 班级 |
---|---|
one | ECON-101 |
one | ECON-201 |
Two | 数学-315 |
在这一点上,这个数据库是充分规范化的,在将来添加、修改或删除数据时不会出现任何问题。
确定表格和结构
考虑到标准化,让我们为您的应用定义表。概括地说,您需要存储的数据如下:
- 房间的名称
- 房间的唯一标识符
- 房间是否处于活动状态
- 演示者的姓名
- 演示者的唯一标识符
- 演示者的电子邮件
- 这个问题
- 问题的唯一标识符
- 不管这个问题是否被回答
- 这个问题的投票数
如果您正常地对这些数据进行分组,您应该得到四个表:
- 提出者
- 空间
- 问题
- 投票
演示者表格
第一个表presenters
,存储演示者的姓名、电子邮件和唯一的 ID(见表 5-7 )。
表 5-7 。presenters
表
编号 | 名字 | 电子邮件 |
---|---|---|
房间表
rooms
表将存储房间的唯一 ID、名称以及是否处于活动状态(参见表 5-8 )。
表 5-8 。rooms
表
编号 | 名字 | 不活动的 |
---|---|---|
问题表
questions
表将存储问题的唯一 ID、问题所属的房间、问题文本以及问题是否已被回答(参见表 5-9 )。
表 5-9 。问题表
编号 | 室友 | 问题 | 已回答 |
---|---|---|---|
问题投票表
question
_ votes
表将存储问题的 ID 和当前的票数(见表 5-10 )。
表 5-10 。question_votes
表
问题 id | 投票计数 |
---|---|
房间所有者表
Room_owners
表将存储房间和主持人的 id(参见表 5-11 )。
表 5-11 。room_owners
表
室友 | 演示者 id |
---|---|
将所有东西放在线框图中
此时,我们已经完成了大部分计划;剩下的就是快速绘制应用的线框,以获得布局的大致想法。
组织主页
主页需要服务于两个目的,以满足我们两个用户角色的需求。
首先,它需要为演示者提供创建新房间的方法。这将需要一个要求提供必要信息的表格。
其次,如果与会者没有与会话的直接链接,他们需要能够加入房间。这还需要一个接受房间 ID 的表单。
除了这两个表单之外,还应该有一个用于标识应用的页眉和一个用于提供附加信息(如版权)的页脚。
更宽屏幕的线框
在更宽的屏幕上,并排设置这两种形式是有意义的,因为它们并不比另一个更重要或更不重要。当我们把这些都放到一个基本的线框中,它看起来像图 5-4 。
图 5-4。更宽屏幕的主页线框
较窄屏幕的线框
在较窄的屏幕上,两栏布局是不可行的,所以我们必须将内容重排为一栏。与会者加入会议室的表格应该放在顶部,原因有二:它将比演示者表格短得多,而且有理由认为使用该应用参加会议的人将多于演示者。
内容重排后,看起来会像图 5-5 。
图 5-5。窄屏幕的主页线框
为与会者组织问答页面
与会者加入房间后,应用需要显示房间的名称和演示者是谁,并向她显示提问表格和现有问题列表。
页眉和页脚将在整个应用中保持一致,因此只有主要内容区域需要改变。
更宽屏幕的线框
同样,在更宽的屏幕上,内容可以分成两列,但在这种情况下,其中一列应该比另一列宽,因为侧边栏中不会显示太多内容。
提出新问题的表格应该放在主栏的顶部,下面是问题。
在右侧,应该显示房间的名称和演示者的信息。
插入这些信息后,线框看起来像图 5-6 。
图 5-6。为与会者设计的问答页面被框在更宽的屏幕上
较窄屏幕的线框
对于单列布局,我们实际上要把包含房间信息的侧栏放在顶部。这样做的原因是内容很重要——没有它,与会者就不知道他们参加了哪个会议——而且非常短,所以它不会把其余的内容压得太低。
重排内容后,你会看到类似于图 5-7 的东西。
图 5-7。为与会者设计的问答页面适合更窄的屏幕
为演示者组织问答页面
面向与会者的问答页面和面向演示者的问答页面之间只有一些小的区别。也就是说,删除了“提问”表单,并为演示者添加了一些控件。
更宽屏幕的线框
将“提问”表单换成演示者控件后,两栏视图中的布局没有太大变化。然而,控件并没有放在问题列表的顶部,而是放在房间信息的下面,以便将注意力更多地放在回答问题上(参见图 5-8 )。
图 5-8。Q&为演示者设计的页面被框在更宽的屏幕上
较窄屏幕的线框
与与会者的布局类似,房间信息和控件将移动到单列视图中问题列表的顶部(参见图 5-9 )。
图 5-9。Q&专为窄屏幕的演示者设计的页面
摘要
此时,您已经熟悉了所有需要的技术,您已经为站点的前端和后端开发制定了一个攻击计划,并且您已经有了一个线框来通知您的设计。
在下一章中,你将根据本章的计划从头开始设计和构建应用。
1 关于适应性布局的图库,参见http://mediaqueri.es
。
2
3