首页 > Web开发, 动态语言, 挨踢(IT) > 《Agile Web Development with Rails》抄书笔记(14):购物车靠边

《Agile Web Development with Rails》抄书笔记(14):购物车靠边

2013年6月27日 发表评论 阅读评论 543 人阅读    

《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呱呱

  关于这节内容的代码:

  1. 这节完成之前:https://github.com/diguage/depot/tree/v-10.3
  2. 这节开始之后:https://github.com/diguage/depot/tree/v-11.1

购物车靠边

  现在,我们的购物车被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 %>&times;</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 %>&times;</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奖提名。哈哈

Figure 20—The cart is in the sidebar

更新页面流程

  现在,我们把购物车展示在侧栏,下面我们应该修改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来更好实现这个功能。



作 者: D瓜哥,https://www.diguage.com/
原文链接:https://wordpress.diguage.com/archives/25.html
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。

  1. 本文目前尚无任何评论.
  1. 本文目前尚无任何 trackbacks 和 pingbacks.