




已阅读5页,还剩79页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
9.2 迭代D2:创建基于Ajax的购物车Iteration D2:Creating an Ajax-Based CartAjax允许我们通过编写在浏览器中运行的代码,来与服务器端的应用程序交互。在这里,我们希望让Add to Cart按钮在后台调用服务器端的add_to_card方法,随后服务器把关于购物车的HTML发回浏览器,我们只要把服务器更新的HTML片段替换到边框里就行了。要实现这种效果,通常的做法是首先编写在浏览器中运行的JavaScript代码,然后编写服务器端代码与JavaScript交互(可能是通过JSON之类的技术)。好消息是,只要使用Rails,这些东西都将被隐藏起来:我们使用Ruby(再借助一些Rails辅助方法)就可以完成所有功能。向应用程序中引入Ajax的技巧在于“小步前进”,所以我们先从最基本的开始:修改货品列表页,让它向服务器端应用程序发起Ajax请求;应用程序则应答一段HTML代码,其中展示了最新的购物车。在索引页上,目前我们是用button_to()来创建“Add to Cart”链接的。揭开神秘的面纱,button_to()其实就生成了HTML的标记。下列辅助方法 :add_to_cart, :id = product %会生成类似这样的HTML代码 这是一个标准的HTML表单,所以,当用户点击提交按钮时,就会生成一个POST请求。我们不希望这样,而是希望它发送一个Ajax请求。为此,必须更直接地编写表单代码可以使用form_remote_tag这个Rails辅助方法。“form_._tag”这样的名字代表它会生成HTML表单,“remote”则说明它会发起Ajax远程调用。现在,打开app/views/store目录下的index.html.erb文件,将button_to()调用替换为下列代码:depot_l/app/views/store/index.html.erb :action = add_to_cart, :id = product do % 用:url参数,你就可以告诉form_remote_tag()应该如何调用服务器端的应用程序。该参数接收一个hash,其中的值就跟传递给button_to()方法的参数一样。在表单内部(也就是do和end之间的代码块中),我们编写了一个简单的提交按钮。在用户看来,这个页面就跟以前一模一样。虽然现在处理的是视图,但我们还需要对应用程序做些调整,让它把Rails需要用到的JavaScript库发送到用户的浏览器上。在第24章“Web 2.0”(第521页)中,我们还会详细讨论这个话题;现在,我们只需在store布局的部分里调用javascript_include_tag方法即可。depot_l/app/views/layouts/store.html.erb Pragprog Books Online Store all % 到目前为止,浏览器已经能够向我们的应用程序发送Ajax请求,下一步就是让应用程序做出应答。我们打算创建一段HTML代码来代表购物车,然后让浏览器把这段HTM插入当前页面的DOM 文档对象模型(Document Object Model)。这是文档结构和内容在浏览器中的内部表示,我们据此来改变显示给用户的东西。,替换掉当前显示的购物车。为此,我们要做的第一个修改就是不再让add_to_cart重定向到首页。(我们知道,我们刚刚才加上这个功能,现在又要把它拿掉了我们很敏捷,对吧?) 我们调用respond_to()方法并告诉它我们要响应的是.js格式文件 这条语句乍看起来有些奇怪,其实就是一个使用代码块作为参数的方法调用。代码块在第A.9节“代码块与迭代器”中有介绍。在第12.1节“分别应答”中有更详细的描述。 。Download depot_l/app/controllers/store_controller.rbdef add_to_cart product = Product.find(params:id) cart = find_cart cart.add_product(product) respond_to do |format| format.js endrescue ActiveRecord:RecordNotFound logger.error(Attempt to access invalid product #params:id ) redirect_to_index(Invalid product )end修改的结果是,当add_to_cart完成对Ajax请求的处理之后,Rails就会查照add_to_cart这个模板来执行渲染。在第118页,我们已经删掉了旧的.html.erb模板,看起来现在又需要把它弄回来了。不过,还是让我们换另一种方式来做这件事吧。Rails支持RJS模板的概念“JS”指的是JavaScript。.js.rjs模板可以将JavaScript发送到浏览器,而你需要写的只是服务器端的Ruby代码。下面我们就来编写第一个.rjs模板:add_to_cart.js.rjs,它和别的模板一样,也位于app/views/store目录下。depot_l/app/views/store/add_to_cart.js.rjspage.replace_html(cart , :partial = cart , :object = cart)我们来分析一下这个模板。page这个变量是JavaScript生成器的实例这是Rails提供的一个类,它知道如何在服务器端创建JavaScript,并使其在浏览器上运行。在这里,我们希望它找到当前页面上id为cart的元素,然后将其中的内容替换成某些东西。传递给replace_html的参数看上去很眼熟,因为它们就跟store布局中渲染局部模板时传入的参数完全一样。这个简单的RJS模板就会渲染出用于显示购物车的HTML代码,随后就会告诉浏览器将id=cart的中的内容替换成这段HTML代码。这有效吗?在书里很难演示,不过它确实有效。首先刷新首页,以确保form_remote_tag调用和JavaScript库被加载到浏览器。然后,点击Add to Cart按钮,就应该能看到边框里的购物车信息被更新了,同时浏览器却不会有任何刷新页面的迹象。你已经创建了一个Ajax应用程序。排疑解难Troubleshooting虽然Rails极大地简化了Ajax,却没办法让它变得易如反掌。而且,由于涉及到几种技术的松散整合,一旦Ajax出现故障,可能会很难跟踪调试。这也是应该始终小步前进、逐渐增加Ajax功能的原因。如果你的Depot应用没有显露出任何Ajax魔法,这里有几个提示:l 你删除旧的add_to_cart.html.erb文件了吗?l 你记得用javascript_include_tag将必要的JavaScript库包含到store布局中了吗?l 你的浏览器是否有什么特殊的设置,强迫它每次都重新加载整个页面?有时候浏览器会在本地缓存页面内容,这也会给测试增加困难。也许你应该首先刷新整个页面,然后再进行测试。l 是否收到了任何错误报告?请察看logs目录中的development.log文件。l 还是日志文件,你是否看到了针对add_to_cart这个action的请求?如果没有,就表示你的浏览器并没有发起Ajax请求。如果JavaScript库已经加载到浏览器中(用 “查看源文件”功能可以看到HTML源代码),是否你的浏览器禁用了JavaScript?l 有些读者告诉我们,当他们重新启动应用程序之后,基于Ajax的购物车就一切正常了。l 如果你使用Internet Explorer浏览器,它有可能正运行在所谓quirks模式下这是为了兼容旧版本IE而设计的一种运行模式,它在处理Ajax时有很多问题。如果在页面上设置了适当的DOCTYPE头信息,IE就会转入standards模式,这种模式能够更好地处理Ajax内容。我们的布局模板中使用了下列头信息:客户永不满足The Customer Is Never Satisfied我们对自己的表现感到相当满意:只修改了几行代码,原来那个乏昧的Web1.0应用就走上了Web 2.0和Ajax的康庄大道。屏住呼吸,把客户叫到身旁。什么都不用说,我们自豪地按下了Add to Cart按钮,然后扭头看着她,等待着她的赞扬。可是,没有赞扬,她看上去很惊讶。“你们把我叫过来就是为了给我看一个bug?”她问道,“你点了按钮,却什么都没发生。”我们耐心地解释说,其实背后已经发生好多事情了。看看边框里的购物车,看见了吗?我们添加货品的时候,购物车里的货品数量就从4变成了5。“噢,”她这样说,“我还真没注意到。”如果她注意不到页面更新的话,估计顾客也注意不到。看来我们还需要对用户界面做点改进。9.3 迭代D3:高亮显示变化Iteration D3: Highlighting Changes前面就已经提到过,javascript_include_tag辅助方法会把几个JavaScript库加载到浏览器中,其中之一的effects.js可以给页面装饰以各种有趣的可视化效果 effect.js是script.aculo.us库的一部分。在/madrobby/scriptaculous/wikis这里列出了一些可视化效果,你可以看看用这个库都能做些什么。在这些可视化效果中就包括如今声名显赫的“黄渐变技巧”(Yellow fade Technique)这是一种高亮显示页面元素的技巧,通常会首先把背景变成黄色,然后再渐变到白色。图9.2展示了将黄渐变技巧应用到我们的购物车上的效果:最后面的那张图片是最初的购物车;用户点击Add to Cart按钮,购物车中货品的数量变成“2”,同时货品名称所在的那行文字背景变亮;随后,背景颜色又会逐渐变回原来的样子。我们来把这样的高亮效果加到购物车上:每当购物车被更新时(不管是添加了新的货品,还是改变了现有货品的数量),就把这部分的背景变亮。这样,用户就可以更清楚地看到“有东西发生了变化”,虽然整个页面并没有刷新。我们面临的第一个问题是:如何知道购物车中的哪种货品是最近更新的?现在,每种货品在显示时都只是一个简单的元素,我们需要找到一个办法来标记出其中最近更新的一个。为此,首先需要对Cart模型类做些调整:让add_product()方法返回与新加货品对应的CartItem对象。图9.2加上黄渐变效果的购物车depot_m/app/models/cart.rbdef add_product(product) current_item = items.find |item| duct = product if current_item current_item.increment_quantity else current_item = CartItem.new(product) items current_item end current_itemend在store_controller.rb中,我们可以取出这项信息,并将其赋入一个实例变量,以便传递给视图模板。Download depot_m/app/controllers/store_controller.rbdef add_to_cart product = Product.find(params:id) cart = find_cart current_item = cart.add_product(product) respond_to do |format| format.js endrescue ActiveRecord:RecordNotFound logger.error(Attempt to access invalid product #params:id ) redirect_to_index(Invalid product )end在_cart_item.html.erb局部模板中,我们会检查当前要渲染的这种货品是不是刚刚发生了改变。如果是,就给它做个标记:将它的id属性设为current_item。depot_m/app/views/store/_cart_item.html.erb × 经过这三个小修改之后,最近发生变化的货品所对应的元素就会被打上id=current_item的标记。现在,我们只要告诉JavaScript对这些元素施加高亮效果就行了:在add_to_cart.js.rjs模板中调用visual_effect方法。depot_m/app/views/store/add_to_cart.js.rjspage.replace_html(cart , :partial = cart , :object = cart)page:current_item.visual_effect :highlight, :startcolor = #88ff88 , :endcolor = #114411请注意我们是如何指定“想要施加可视化效果的元素”的:只要把:current_item传递给page就行了。然后我们对这些元素施加了高亮显示的可视化效果,并将默认的“黄-白”色改成了更适合我们设计风格的色调。现在点击按钮往购物车里添加一件货品,你就会看到新添加的货品会显示为亮绿色,然后逐渐变淡,最后完全融入背景。9.4 迭代D4:隐藏空购物车Iteration D4:Hiding an Empty Cart来自客户的最后一个请求:即便购物车中没有任何东西,现在我们还是把它显示在边框里;能不能只在其中有东西的时候才显示?当然可以!实际上,可选的办法有好几种。最简单的做法大概是:只有当购物车中有东西时才包含显示它的那段HTML代码。这个方案只需要修改_cart一个局部模板就可以实现。 Your Cart cart_item , :collection = cart.items) % Total :empty_cart %虽然这也是可行的方案,但却让用户界面显得有点生硬:当购物车由空转为不空时,整个边框都需要重绘。所以,我们不要这样做,还是把它做更圆滑一点吧。script.aculo.us提供了几个可视化效果,可以漂亮地让页面元素出现在浏览器上。我们现在来试试blind_down,它会让购物车平滑地出现,并让边框上其他的部分依次向下移动。毫不意外地,我们会在现有的.js.rjs模板中调用这个效果。因为add_to_cart模板只有在用户向购物车中添加货品时才会被调用,所以我们知道:如果购物车中有了一件货品,那么就应该在边框中显示购物车了(因为这就意味着此前购物车是空的,因此也是隐藏起来的)。此外,我们需要先把购物车显示出来,然后才能使用背景高亮的可视化效果,所以“显示购物车”的代码应该在“触发高亮效果”的代码之前。现在,这个模板大概就是这样:depot_n/app/views/store/add_to_cart.js.rjspage.replace_html(cart , :partial = cart , :object = cart)page:cart.visual_effect :blind_down if cart.total_items = 1page:current_item.visual_effect :highlight, :startcolor = #88ff88 , :endcolor = #114411它暂时还不能工作,因为Cart模型类中还没有total_items()这个方法。depot_n/app/models/cart.rbdef total_items items.sum |item| item.quantity end我们还需要在购物车为空的时候将其隐藏起来。有两种基本的办法可以做这件事。第一,就像本节开始处展示的,根本不生成任何HTML就好了。遗憾的是,如果我们这样做,当用户朝空的购物车中放入第一件货品时,浏览器上就会出现一阵闪烁购物车被显示出来,然后又隐藏起来,然后再被blind_down效果逐渐显示出来。所以更好的解决办法是创建“显示购物车”的HTML,但对它的CSS样式进行设置,使其不被显示出来如果购物车为空,就设置为display:none。为此,我们需要修改app/views/layouts目录下的store.html.erb布局文件。首先想到的修改方法大致如下:div id=cart style=display: none cart , :object = cart) %当购物车为空时,这段代码就会给标记加上style=display: none这段CSS属性。这确实有效,不过真的、真的很丑陋。下面挂着一个孤孤单单的“”字符,看起来就像放错了地方一样(虽然确实没放错);而且更糟糕的是,逻辑被放到了HTML标记的中间,这正是给模板语言带来恶名的行为。不要让这么丑陋的代码出现,我们还是创建一层抽象编写一个辅助方法把它隐藏起来吧。辅助方法Helper Methods每当需要将某些处理逻辑从视图(不管是哪种视图)中抽象出来时,我们就应该编写一个辅助方法。进入app目录,你会看到下列子目录:depot ls p appcontrollers/ helpers/ models/ views/毫不意外地,我们的辅助方法应该放在helpers目录下。进入这个目录,你会看到其中已经有几个文件存在了。depot ls p app/helpersapplication_helper.rb products_helper.rb store_helper.rbRails代码生成器会自动为每个控制器(在我们这里就是products和store)创建一个辅助方法文件;Rails命令本身(也就是一开始创建整个应用程序的命令)则生成了application_helper.rb文件。如果你愿意,可以把方法放到某个控制器对应的辅助文件中,但实际上视图可以使用所有的辅助方法。目前还只有在store控制器的视图中需要使用它,所以就先把它放在store_helper.rb文件中吧。我们先来看看store_helper.rb这个文件,它就位于helpers目录下:module StoreHelperend我们来编写一个名叫hidden_div_if()的辅助方法,它接收三个参数:一个条件判断, 一组可选的属性以及一个代码块。该方法将代码块产生的结果用标记包装起来,如果条件判断为true,就给它加上display:none样式。在布局文件中,我们会这样使用它:depot_n/app/views/layouts/store.html.erb cart ) do % cart , :object = cart) %我们把这个辅助方法放在app/helpers目录下的store_helper.rb文件中。depot_n/app/helpers/store_helper.rbmodule StoreHelper def hidden_div_if(condition, attributes = , &block) if condition attributesstyle = display: none end content_tag(div , attributes, &block) endend这段代码使用了Rails的一个标准的辅助方法content_tag(),它把传递给它的代码块的输出用一个标记包装起来。通过&block标志,我们可以将代码块经过hidden_div_if()方法传递给content_tag()方法。最后,我们要停用那条曾经用来指示购物车已经清空的flash消息我们已经不再需要它了,因为当货品列表页刷新时,购物车直接就消失了。除此之外,还有另一个原因要去掉它:我们已经用Ajax来向购物车中添加货品,用户在购物时主页面根本就不会刷新;也就是说,只要用户清空了一次购物车,那条flash消息就会一直显示在页面上,告诉他“购物车已经被清空”,即便边框里显示的购物车又被放进了新的货品。depot_n/app/controllers/store_controller.rbdef empty_cart session:cart = nil redirect_to_indexend看起来步骤不少,其实不然:要隐藏和显示购物车,我们所需要做的只是根据其中货品的数量设置CSS的显示样式,另外,当第一件货品被放进购物车时,通过RJS模板来调用blind_down效果,仅此而已。看到这个漂亮的新界面,每个人都很兴奋。由于我们的电脑连接在办公室网络上,好多同事干脆打开浏览器,亲身体验这个测试程序。当他们看到购物车如何出现在屏幕上、如何更新其中的内容时,惊讶的口哨声不断地响了起来。每个人都爱死了它,每个人除了Bruce以外。Bruce从来不信任浏览器上运行的JavaScript,他把JavaScript给禁用了。这样一来,所有精彩的Ajax效果也都不起作用了。当Bruce往购物车里添加货品时,他只看到一些奇怪的东西:$(cart ).update(Your Cartnnn n nn 3 × Pragmatic ProjectAutomationnnn n ruby script/generate scaffold order name:string address:text email:string pay_type:string.depot ruby script/generate scaffold line_item product_id:integer order_id:integer quantity:integer total_price:decimal.然后,修改创建orders表的迁移任务,先给pay_type字段加上长度限定:Download depot_p/db/migrate/20080601000005_create_orders.rbclass CreateOrders 10 t.timestamps end end def self.down drop_table :orders endend接着修改创建line_items表的迁移任务:Download depot_p/db/migrate/20080601000006_create_line_items.rbclass CreateLineItems false, :options = CONSTRAINT fk_line_item_products REFERENCES products(id) eger :order_id, :null = false, :options = CONSTRAINT fk_line_item_orders REFERENCES orders(id) eger :quantity, :null = false t.decimal :total_price, :null = false, :precision = 8, :scale = 2 t.timestamps end end def self.down drop_table :line_items endendJoe 问信用卡处理在哪儿到现在,我们这个教学用的应用程序与真实世界出现了一点差异。在真实世界里,我们很可能需要应用程序处理整个支付业务,包括信用卡处理(可能使用Payment模块*或是Tobia Ltke的ActiveMerchant类*)。但是后端支付流程的整合涉及大量的规约资料,会将读者的注意力从Rails这里移开,所以我们决定不考虑这方面的细节。* http//projects/payment* http//am请注意,这张表包含两个外键。这是因为line_items表中的每条记录都需要同时与“订单”和“货品”关联。遗憾的是,这里有几个问题,首先, Rails迁移任务并未提供一种独立于数据库的方式指定外键约束,所以我们只好通过附加数据库(在这里就是SQLite3)原生的DDL语句来建立外键;其次,就像这篇文章所说的那样,SQLite3 v3.4.0仅仅会对语句做语法分析,而不会真正执行外键约束;最后,这些自定义的约束不会被储存在db/schema.rb文件中,因此也不会被复制到测试数据库。 很多Rails开发者压根儿就不去费心指定数据库层面的约束(例如外键),完全靠应用代码来保证正确。这可能也是Rails迁移任务不支持约束的原因之一。不过,如果数据完整性很重要的话,很多人(包括Dave和Sam)仍然认为:花一点小功夫多做一次检查,可以节省彻夜在生产环境下调试的大把时间。如果你对此有兴趣,第286页还有相关内容可以参考。我们已经写好了两个迁移任务,现在就可以实施它们了。depot rake db:migrate= 20080601000005 CreateOrders: migrating =- create_table(:orders) - 0.0066s= 20080601000005 CreateOrders: migrated (0.0096s) = 20080601000006 CreateLineItems: migrating =- create_table(:line_items) - 0.0072s= 20080601000006 CreateLineItems: migrated (0.0500s) =由于schema_migrations表中没有这两个迁移任务的记录,所以运行db:migrate任务会实施这两个最新的迁移任务。当然我们也可以逐一创建迁移任务,然后再逐一实施它们。模型之间的关系Relationships between Models现在,数据库已经知道订单、订单项与货品之间的关系了,但Rails应用还不知道。所以,我们还需要在模型类中增加一些声明,指定它们之间的关系。首先,打开新近创建的order.rb文件(位于app/models目录下),在其中调用has_many()方法。class Order ActiveRecord:Base has_many :line_itemsendhas_many指令看来一目了然:一个订单(可能)有多个订单项与之关联。这些订单项之所以被关联到这个订单,是因为它们引用了该订单的id。此外,还应该在Product模型类中加上has_many声明:如果有很多订单的话,每种货品都可能有多个订单项与之关联。class Product ActiveRecord:Base has_many :line_items #.end下面我们就要指定反向的关联:从订单项到订单和货品的关联。为此,我们需要在line_item.rb文件中两次使用belongs_to()声明。class LineItem ActiveRecord:Base belongs_to :order belongs_to :productendbelongs_to声明告诉Rails:line_items表中存放的是orders表和products表中记录的子记录;如果没有对应的订单和货品存在,则订单项不能独立存在。有一种简单的办法可以帮你记住应该在哪里做belongs_to声明:如果一张表包含外键,那么它对应的模型类就应该针对每个外键做belongs_to声明。这些声明到底管什么用?简单说来,它们会给模型对象加上彼此导航的能力。由于在LineItem模型中添加了belongs_to声明,现在我们就可以直接得到与之对应的Order对象了。li = LineItem.find(.)puts This line item was bought by #另一方面,由于Order类有指向LineItem的has_many声明,我们也可以从Order对象直接引用与之关联的LineItem对象它们都在一个集合中。order = Order.find(.)puts This order has #order.line_items.size line items在第324页,我们还会详细介绍模型对象之间的关联。创建表单搜集订单信息Creating the Order Capture Form现在数据库表和模型类都已经到位,我们可以开始处理付账的流程了。首先,我们需要在购物车里加上一个Checkout按钮,并使其连接到store控制器的checkout方法。depot_p/app/views/store/_cart.html.erbYour Cart cart_item , :collection = cart.items) % Total checkout % :empty_cart %我们希望checkout这个action能向用户呈现
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
评论
0/150
提交评论