《Agile Web Development with Rails》抄书笔记(14):购物车靠边
《Agile Web Development with Rails》抄书笔记系列
“《Agile Web Development with Rails》抄书笔记系列”目录
上一节提到,客户希望我们的网店使用Ajax来实现。那么神马是Ajax呢?
在过去(2005年以前),浏览器被我们当作一个聋哑人。当我们开发一个基于浏览器的应用时,我们会发送一个页面到浏览器,然后忽略这次会话。曾几何时,用户填写一些表单信息,然后点击链接,接着应用被进来的请求唤醒,开始处理请求。最后,应用返回一个完整页面给用户,冗长的处理过程还要再度开始。在此之前,我们的Depot应用也是如此。
但是,事实上浏览器并非是一个聋哑人。浏览器也可以运行代码,几乎所有的浏览器都可以运行JavaScript。另外,经试验证明,JavaScript可以在幕后和服务器进行交互,更新页面。 Jesse James Garrett将这种方式命名为Ajax (Asynchronous JavaScript and XML的缩写,意指可以使浏览器传输的如此少。)
将我们的购物车Ajax化,我们不需要一个单独的购物车页面,可以将当前的购物车放到商品目录的侧栏。然后,添加Ajax代码来实现在不刷新整个页面的情况下更新购物车信息。
当我们开始使用Ajax时,最好先实现一个没有Ajax的版本。然后再逐步引入Ajax特性。下一步,我们就来完成这个。第一步,让我们先将购物车移到侧栏。
D呱呱
关于这节内容的代码:
购物车靠边
现在,我们的购物车被CartController的show方法(action)调用相应的 .html.erb模板渲染而来。这以为着,不能在我们页面显示。我们需要在layout中渲染整个商品目录。使用局部模板就可以很容易来实现。
局部模板(Partial Templates)
编程语言可以让我们定义方法,一个方法就是一块被命名的代码,可以通过方法名来调用方法,来使得相应的代码块运行。当然,你可以给一个方法传递参数,可以实现一段在很多情况下适用的代码。
你可以将Rails的局部模板视为一种针对特定方法的视图。一个局部模板是一段在单独文件中的视图代码。你可以从其他模板或者Controller中调用局部模板,局部模板可以自我渲染并且返回渲染结果。同时,在局部模板对应的方法中,你可以向局部模板传递参数以使局部模板渲染出不同的结果。
我们将在这个迭代中使用两次局部模板。首先,让我们先看看这个购物车的视图代码(%depot%/app/views/carts/show.html.erb):
<% if notice %> <p id="notice"><%= notice %></p> <% end %> <div class="cart_title">Your Cart</div> <table> <% @cart.line_items.each do |item| %> <tr> <td><%= item.quantity %>×</td> <td><%= item.product.title %></td> <td class="item_price"><%= number_to_currency(item.total_price) %></td> </tr> <% end %> <tr class="total_line"> <td colspan="2">Total</td> <td class="total_cell"><%= number_to_currency(@cart.total_price) %></td> </tr> </table> <%= button_to 'Empty cart', @cart, method: :delete, data:{ confirm: 'Are you sure?' } %>
它负责创建一个表格列表,一列对应购物车中的一种商品。无论何时,当你发现有如此的迭代代码时,你就应该停下来反思一下,是否在模板中包含了过多的逻辑代码?实际上,我们可以使用局部模板将这个循环抽象出来。(正如我们所见,稍后这里将是展示Ajax效果的舞台。)你可以向方法中传递一个集合,然后渲染这个模板,方法对集合中的每个元素都会自动调用局部模板。来让我们重写这个购物车视图来使用这中特性:(%depot%/app/views/carts/show.html.erb)
<% if notice %> <p id="notice"><%= notice %></p> <% end %> <div class="cart_title">Your Cart</div> <table> <%= render(@cart.line_items) %> <tr class="total_line"> <td colspan="2">Total</td> <td class="total_cell"><%= number_to_currency(@cart.total_price) %></td> </tr> </table> <%= button_to 'Empty cart', @cart, method: :delete, data:{ confirm: 'Are you sure?' } %>
这很简单。render()方法将会迭代传给它的任何集合。局部模板是另外一个简单模板文件(默认情况下,在被渲染的对象的同一个目录下,同时文件名和表名相同)。为了和其他模板区分开,Rails会以下划线开头命名这些文件,查找时亦是如此。这意味着,我们需要这样命名我们的局部模板文件,_line_item.html.erb,同时将其放到 %depot%app/views/line_items/目录下:(%depot%/app/views/line_items/_line_item.html.erb)
<tr> <td><%= line_item.quantity %>×</td> <td><%= line_item.product.title %></td> <td class="item_price"><%= number_to_currency(line_item.total_price) %></td> </tr>
D呱呱
根据D瓜哥的个人使用,如果Ruby代码片段的结尾少了(后面的%>落了或者特意删除),不会报错。还原封不动的把模板内容显示出来;Ruby代码段中出差,比方法后的括号少了结尾,则会报错。
这里有个小细节需要注意!在局部模板内容,我们通过变量名引用了当前对象。在这种情况下,局部模板被命名为line_item,所以在局部模板内部,我们也希望有个叫line_item的变量。
现在,我们已经整理过购物车商品的展示了,但是还没有被移动到侧栏。为了实现这个功能,让我们重新查看一下我们的布局文件。如果我们有一个可以展示购物车的局部模板,我们就可以很简单地潜入到侧栏中。代码如下:
render("cart")
但是,局部模板如何知道去哪里去查找这个cart对象呢?一种方式就是为其作为一种假设。在布局文件中,我可以获取由Controller设定的实例变量@cart。事实上,这在导入的局部模板中依然可以使用。虽然,这有点像方法调用以及通过全局变量传值,而且还可以正常运行,但是这样编码有坏味道,并且它会增加耦合性,进而使你的程序脆弱、难以维护。
现在,我们有一个为 line item准备的局面模板。首先,我们先创建模板文件_cart.html.erb。这是carts/show.html.erb的一部分,不过我们这里使用cart代替@cart,同时也不需要提示(注意:局部模板可以调用局部模板。):(%depot%/app/views/carts/_cart.html.erb)
<div class="cart_title">Your Cart</div> <table> <%= render(cart.line_items) %> <tr class="total_line"> <td colspan="2">Total</td> <td class="total_cell"><%= number_to_currency(cart.total_price) %></td> </tr> </table> <%= button_to 'Empty cart', cart, method: :delete, data:{ confirm: 'Are you sure?' } %>
正如热赞Rails的颂歌所言,不要重复!(Don’t Repeat Yourselves。简称DRY),但是这里我们却违反了这个原则。同一时间,有两个文件需要同步,看起来貌似没有什么问题,但是如果有一组Ajax调用逻辑和另外一组处理JavaScript不能使用的逻辑时,这是问题就来了。所以,我们应该尽量避免出现这样的问题,这里可以移除原来的目标,是用局部模板代替。代码修改如下:(%depot%/app/views/carts/show.html.erb)
<% if notice %> <p id="notice"><%= notice %></p> <% end %> <%= render(@cart) %>
现在,我们修改应用的布局,在侧栏中引入新的局部模板:(%depot%/app/views/layouts/application.html.erb)
<!DOCTYPE html> <html> <head> <title>Depot</title> <%= stylesheet_link_tag "application", :media => "all" %> <%= javascript_include_tag "application" %> <%= csrf_meta_tags %> </head> <body class="<%= controller.controller_name %>"> <div id="banner"> <%= image_tag("logo.png") %> <%= @page_title || "Pragmatic Bookshelf" %> </div> <div id="columns"> <div id="side"> <div id="cart"> <%= render(@cart) %> </div> <ul> <li><a href="http://127.0.0.1:3000/">Home</a></li> <li><a href="http://127.0.0.1:3000/">Questions</a></li> <li><a href="http://127.0.0.1:3000/">News</a></li> <li><a href="http://127.0.0.1:3000/">Contact</a></li> </ul> </div> <div id="main"> <%= yield %> </div> </div> </body>
下一步,我们必须在StoreController中做一些小小的改变。修改index方法(Action)中,在其中定义并初始化@cart。
# %depot%/app/controllers/store_controller.rb def index @products = Product.order(:title) @cart = current_cart end
最后,我们修改样式定义,将目前只适用CartController是输出,稍经修改也适用侧栏的输出。SCSS可以使我们只需要修改一个地方,其余嵌套地方可以自动修改。代码如下:(%depot%/app/assets/stylesheets/carts.css.scss)
// Place all the styles related to the carts controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ .carts, #side #cart { .cart_title { font: 120% bold; } .item_price, .total_line { text-align: right; } .total_line .total_cell { font-weight: bold; border-top: 1pxsolid #595; }
这是,对于购物车中的数据,无须关心被放置到哪里,也无需关心数据如存储。但是上,黑色字体加绿色背景是很难阅读。所以,我们要对在侧栏的购物车表格增加一个特色的规则。如下:(%depot%/app/assets/stylesheets/application.css.scss)
#side { float: left; padding: 1em 2em; width: 13em; background: #141; form, div { display: inline; } input { font-size: small; } #cart { font-size: smaller; color: white; table { border-top: 1px dotted #595; border-bottom: 1px dotted #595; margin-bottom: 10px; } } ul { padding: 0; li { list-style: none; a { color: #bfb; font-size: small; } } } }
D呱呱
我在自己写代码的过程中,反了一个错误,将这里的
padding: 1em 2em;错写成了
padding: 1em2em;造成页面布局混乱。特此更正一下!
在想购物车增加一些东西后,购物车页面应该如下图20所示,购物车在侧栏。相信我们的程序可以得到Webby奖提名。哈哈
更新页面流程
现在,我们把购物车展示在侧栏,下面我们应该修改AddtoCart按钮的工作方式。点击”AddtoCart”按钮,我们只需要刷新主页就行了,而不需要单独展示一个购物车页面。
这个改变很简单,只需要在create方法的最后,重定向到主页即可。代码如下:
# %depot%/app/controllers/line_items_controller.rb # POST /line_items # POST /line_items.json def create @cart = current_cart product = Product.find(params[:product_id]) @line_item = @cart.add_product(product.id) respond_to do |format| if @line_item.save format.html { redirect_to store_url } format.json { render json: @line_item, status: :created, location: @line_item } else format.html { render action: "new" } format.json { render json: @line_item.errors, status: :unprocessable_entity } end end end
到现在为止,我们有一个在侧栏显示购物车的网店了。当点击添加商品按钮时,页面重新显示以更新购物车。如果我们的商品目录很大,这个重新展示也许会消耗很长世间,并且还浪费带宽,消耗服务器资源。幸运的时,我们可以使用Ajax来更好实现这个功能。
原文链接:https://wordpress.diguage.com/archives/25.html
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。