当前位置主页 > 资料库 > 前端教程 > 使用jQuery和Session storage构建客户端购物车系统

使用jQuery和Session storage构建客户端购物车系统

03-20

下载地址

会话存储(Session storage)技术是由W3C的Web Storage提供的一项新特性。这个新特性可以被Explorer 8+、Firefox、Chrome、Safari 和 Opera的桌面浏览器支持,完整的浏览器支持列表可以查看Can I Use。在这篇文章中,我们将使用session storage会话存储技术的sessionStorage对象和jQuery来实现一个完整的客户端电子商务购物车。

Session Storage

我们可以使用sessions来存储数据,并在多个页面中使用这些数据。通常,在某个用户选择了某样产品之后,我们需要保存他选择的这样产品的名称、数量和价格。然后,用户需要填写一些个人信息,这些也需要保存在session中,典型的页面时支付页面和对支付网关后续重定向的页面。

一个购物车是如何被创建的呢?例如使用PHP,可以使用一个关联数组来实现一个购物车的基本结构。PHP开发人员可以使用关联数组来保存会话数据的结构和组织。

JavaScript的会话方式则截然不同。一般情况下,一个session的生命周期是用户关闭浏览器时它就会过期(注意:关闭浏览器的概念在移动mobile浏览器上不适用)。当一个session过期后,浏览器上保存在会话存储(session storage)中的session数据会被全部清除。我们不需要明确的初始化一个session,因为在JavaScript中,可以从全局的sessionStorage对象中获取session。

session数据有键值对组成,每一个key的名称的一个唯一的字符串。要写入数据,我们可以使用sessionStorage.setItem( name, value )方法:

sessionStorage.setItem( "total", 120 );                              
                            

在上面的例子中,key的名称是“total”,对应的值是120。我们现在已经通过setItem方法在session中放入了一个值,这个值在session被销毁前是一直有效的。我们可以使用sessionStorage.removeItem( "total" )方法来指定销毁某一个项,或使用sessionStorage.clear()来销毁所有的session项。

现在,我们刚才设置的key值将一直有效,无论我们导航到网站的那个页面,直到浏览器关闭,session被销毁为止。要获取这个key的值,可以简单的使用下面的方法:

var total = sessionStorage.getItem( "total" );
console.log( total ); // '120', a string                              
                            

我们还可以使用sessionStorage.setItem()方法来更新key对于的值:

var total = parseInt( sessionStorage.getItem( "total" ) );
var quantity = 2;
var updatedTotal = total * quantity;
sessionStorage.setItem( "total", updatedTotal ); // '240', a string                              
                            

上面的语句将名称为"total"的key的值修改为240,。为什么要调用parseInt()函数呢?不用多说,它可以将字符串解析为一个整数。记住,在session storage中所有的值都是字符串,我们要计算必须进行转换。

对于一个对象,它可以转换为一个JSON字符串(通过JSON.stringify()方法)再存储于session storage中。然后可以通过javascript对象来读取它(通过JSON.parse())。

var cart = {
  item: "Product 1",
  price: 35.50,
  qty: 2
};
var jsonStr = JSON.stringify( cart );
sessionStorage.setItem( "cart", jsonStr );
// now the cart is {"item":"Product 1","price":35.50,"qty":2}
var cartValue = sessionStorage.getItem( "cart" );
var cartObj = JSON.parse( cartValue );
// original object                              
                            

要更新一个对象,我们可以简单的继承它,再按照上面的步骤操作一次即可。

安全注意事项

对于购物系统,安全是非常重要的。我们可以阅读W3C的规范security notes,来了解客户端存储方面的安全知识。

美国计算机应急响应小组(The US Computer Emergency Readiness Team)在 technical paper on website security(PDF)中明确规定:

即使是在浏览器正常关闭的情况下终止session,恶意的攻击行为仍可能会发生,特别是浏览器存在一些严重漏洞的时候。此外,一些木马程序通常可以通过浏览器来传播和入侵。

因此,在使用浏览器存储session数据技术之前,请确保您的网站是安全的。

项目演示

下面我们要制作一个简单的项目演示-一个在线出售酒类产品的购物车项目。这是一个简单的电子商务页面,它可以完成购买商品的更换和价格结算。

用户可以从购物车中添加、移除商品,也可以更新商品的数量和清空所有的商品。

HTML结构

整个项目有以下几个页面:

  • index.html-用户购物界面。
  • cart.html-购物车界面。
  • checkout.html-结算界面。

index.html页面的HTML结构如下:

<div class="product-description" data-name="Wine #1" data-price="5">
  <h3 class="product-name">Wine #1</h3>
  <p class="product-price">€ 5</p>
  <form class="add-to-cart" action="cart.html" method="post">
    <div>
      <label for="qty-1">Quantity</label>
      <input type="text" name="qty-1" id="qty-1" class="qty" value="1" />
    </div>
    <p><input type="submit" value="Add to cart" class="btn" /></p>
  </form>
</div>                             
                            

这里使用的data属性用于存储商品的名称和价格,可以通过jQuery的.data()$.data()方法来接收。

cart.html页面的HTML结构如下:

购物车界面由三部分组成:一个产品信息表格,一个显示总价的元素和一组执行操作的按钮。

<form id="shopping-cart" action="cart.html" method="post">
  <table class="shopping-cart">
    <thead>
      <tr>
        <th scope="col">Item</th>
        <th scope="col">Qty</th>
        <th scope="col">Price</th>
      </tr>
    </thead>
    <tbody></tbody>
  </table>
  <p id="sub-total">
    <strong>Sub Total</strong>: <span id="stotal"></span>
  </p>
  <ul id="shopping-cart-actions">
    <li>
      <input type="submit" name="update" id="update-cart" class="btn" value="Update Cart" />
    </li>
    <li>
      <input type="submit" name="delete" id="empty-cart" class="btn" value="Empty Cart" />
    </li>
    <li>
      <a href="index.html" class="btn">Continue Shopping</a>
    </li>
    <li>
      <a href="checkout.html" class="btn">Go To Checkout</a>
    </li>
  </ul>
</form>                             
                            

表格的数据开始时空的,我们要通过javascript来相应的填写数据。更新和清空购物车按钮将通过javascrip来执行相应操作,其它两个按钮只是一个链接。

CHECKOUT.html页面的HTML结构如下:

<table id="checkout-cart" class="shopping-cart">
  <thead>
    <tr>
      <th scope="col">Item</th>
      <th scope="col">Qty</th>
      <th scope="col">Price</th>
    </tr>
  </thead>
  <tbody>

  </tbody>
</table>

<div id="pricing">
  <p id="shipping">
    <strong>Shipping</strong>: <span id="sshipping"></span>
  </p>

  <p id="sub-total">
    <strong>Total</strong>: <span id="stotal"></span>
  </p>
</div>

<form action="order.html" method="post" id="checkout-order-form">
  <h2>Your Details</h2>
  <fieldset id="fieldset-billing">
    <legend>Billing</legend>
      <!-- Name, Email, City, Address, ZIP Code, Country (select box) -->
    <div>
      <label for="name">Name</label>
      <input type="text" name="name" id="name" data-type="string" data-message="This field may not be empty" />
    </div>

    <div>
      <label for="email">Email</label>
      <input type="text" name="email" id="email" data-type="expression" data-message="Not a valid email address" />
    </div>

    <div>
      <label for="city">City</label>
      <input type="text" name="city" id="city" data-type="string" data-message="This field may not be empty" />
    </div>

    <div>
      <label for="address">Address</label>
        <input type="text" name="address" id="address" data-type="string" data-message="This field may not be empty" />
    </div>

    <div>
      <label for="zip">ZIP Code</label>
      <input type="text" name="zip" id="zip" data-type="string" data-message="This field may not be empty" />
    </div>

    <div>
      <label for="country">Country</label>
        <select name="country" id="country" data-type="string" data-message="This field may not be empty">
          <option value="">Select</option>
          <option value="US">USA</option>
          <option value="IT">Italy</option>
        </select>
    </div>
    </fieldset>

    <div id="shipping-same">Same as Billing <input type="checkbox" id="same-as-billing" value=""/></div>

    <fieldset id="fieldset-shipping">
    <legend>Shipping</legend>
      <!-- Same fields as billing -->
    </fieldset>

  <p><input type="submit" id="submit-order" value="Submit" class="btn" /></p>

</form>                              
                            

data属性用于验证。data-type属性指定我们校验的数据的类型,data-message是用于显示的错误信息。

JAVASCRIPT

我们将使用面向对象的方法来构建js代码。

OBJECT STRUCTURE

我们使用的对象有一个非常简单的结构。构造函数中将初始化包含整个DOM元素的顶级元素,并执行初始化方法。

(function( $ ) {
  $.Shop = function( element ) {
    this.$element = $( element ); // top-level element
    this.init();
  };

  $.Shop.prototype = {
    init: function() {
      // initializes properties and methods
    }
  };

  $(function() {
    var shop = new $.Shop( "#site" ); // object's instance
  });

})( jQuery );                              
                            

对象实例在页面初始化时被创建。可以使用下面的代码来测试对象是否正常工厂:

$(function() {
  var shop = new $.Shop( "#site" );
  console.log( shop.$element );
});                              
                            

上面的代码会输出:

x.fn.x.init[1]
0: div#site
context: document
length: 1
selector: "#site"                              
                            

现在,对象被正常创建,我们可以开始定义它的属性。

OBJECT PROPERTIES

我们的对象的属性分为两类:第一是处理计算、表单和校验的属性,第二类是HTML元素的引用。

$.Shop.prototype = {
  init: function() {
    // Properties
      this.cartPrefix = "winery-"; // prefix string to be prepended to the cart's name in session storage
      this.cartName = this.cartPrefix + "cart"; // cart's name in session storage
      this.shippingRates = this.cartPrefix + "shipping-rates"; // shipping rates key in session storage
      this.total = this.cartPrefix + "total"; // total key in the session storage
      this.storage = sessionStorage; // shortcut to sessionStorage object

      this.$formAddToCart = this.$element.find( "form.add-to-cart" ); // forms for adding items to the cart
      this.$formCart = this.$element.find( "#shopping-cart" ); // Shopping cart form
      this.$checkoutCart = this.$element.find( "#checkout-cart" ); // checkout form cart
      this.$checkoutOrderForm = this.$element.find( "#checkout-order-form" ); // checkout user details form
      this.$shipping = this.$element.find( "#sshipping" ); // element that displays the shipping rates
      this.$subTotal = this.$element.find( "#stotal" ); // element that displays the subtotal charges
      this.$shoppingCartActions = this.$element.find( "#shopping-cart-actions" ); // cart actions links
      this.$updateCartBtn = this.$shoppingCartActions.find( "#update-cart" ); // update cart button
      this.$emptyCartBtn = this.$shoppingCartActions.find( "#empty-cart" ); // empty cart button
      this.$userDetails = this.$element.find( "#user-details-content" ); // element that displays the user's information
      this.$paypalForm = this.$element.find( "#paypal-form" ); // PayPal form

      this.currency = "€"; // HTML entity of the currency to be displayed in layout
      this.currencyString = "€"; // currency symbol as text string
      this.paypalCurrency = "EUR"; // PayPal's currency code
      this.paypalBusinessEmail = "yourbusiness@email.com"; // your PayPal Business account email address
      this.paypalURL = "https://www.sandbox.paypal.com/cgi-bin/webscr"; // URL of the PayPal form

      // object containing patterns for form validation
      this.requiredFields = {
        expression: {
          value: /^([w-.]+)@((?:[w]+.)+)([a-z]){2,4}$/
        },
        str: {
          value: ""
        }
      };

      // public methods invocation
  }
};                              
                            

接下来看一看上面的每一个属性的意义。

Storage and other properties:
  • cartPrefix:session storage中key名称的前缀。
  • cartName:session storage中key的名称(和cartPrefix结合组成car的名称字符串)。
  • shippingRates:运费。
  • total:总价。
  • storagesessionStorage对象。
  • currency:显示当前货币的HTML布局。
  • currencyString:当前货币的符号。
  • paypalCurrency:PayPal的货币code。
  • paypalBusinessEmail:你的PayPal的email账号。
  • paypalURL:PayPal表单的URL地址。
  • requiredFields:表单中的必填字段。
References to elements:
  • $formAddToCart:添加商品的表单。
  • $formCart:购物车表单。
  • $checkoutCart:结账表单。
  • $checkoutOrderForm:结账表单中的客户填写信息。
  • $shipping:显示运费的元素。
  • $subTotal:显示总价的元素。
  • $shoppingCartActions:购物车操作相关的元素。
  • $updateCartBtn:更新购物车的按钮。
  • $emptyCartBtn:清空购物车的按钮。
  • $userDetails:显示用户信息的元素。
  • $paypalForm:PayPal的表单。

所有这些属性都添加了$前缀,表示它们是一个jQuery对象,这些属性不是在所有页面上都有效的。要查看一个jQuery元素是否存在,可以简单的使用length属性来测试它:

if( $element.length ) {
  // the element exists
}                              
                            
对象方法

在项目时候使用的方法分为私有方法和公共方法。使用方法用于后台操作,辅助公共方法完成任务。私有方法都使用下划线作为前缀,并且不能直接调用。

公共方法用于直接操作页面上的元素和数据,它们没有下划线前缀。

私有方法

第一个私有方法是_emptyCart(),用于情况当前session storage的数据。

$.Shop.prototype = {
  // empties session storage

  _emptyCart: function() {
    this.storage.clear();
  }
};                              
                            

_formatNumber()方法用于格式化数字。

/* Format a number by decimal places
 * @param num Number the number to be formatted
 * @param places Number the decimal places
 * @returns n Number the formatted number
*/
_formatNumber: function( num, places ) {
  var n = num.toFixed( places );
  return n;
}                              
                            

这个方法使用Number对象的javascript toFixed()方法,项目中使用它来格式化商品价格。

因为不是所有的价格数据都保存在data属性中,我们需要知道一个方法来提取商品的价格。这个方法是_extractPrice()

/* Extract the numeric portion from a string
 * @param element Object the jQuery element that contains the relevant string
 * @returns price String the numeric string
 */
_extractPrice: function( element ) {
  var self = this;
  var text = element.text();
  var price = text.replace( self.currencyString, "" ).replace( " ", "" );
  return price;
}                              
                            

上面的self$.Shop对象的引用。我们每次需要接受一个属性或方法时都需要用到它。

为了程序更加健壮,可以删除文本前后的空白:

var text = $.trim( element.text() );                              
                            

需要注意的是,jQuery的$.trim()方法会删除字符串前面和后面的所有的新行、空格和Tabs。

接下来,我们需要两个方法来进行字符串和数字之间的相互转换。

/* Converts a numeric string into a number
 * @param numStr String the numeric string to be converted
 * @returns num Number the number, or false if the string cannot be converted
 */
_convertString: function( numStr ) {
  var num;
  if( /^[-+]?[0-9]+.[0-9]+$/.test( numStr ) ) {
    num = parseFloat( numStr );
  } else if( /^d+$/.test( numStr ) ) {
    num = parseInt( numStr );
  } else {
    num = Number( numStr );
  }

  if( !isNaN( num ) ) {
    return num;
  } else {
    console.warn( numStr + " cannot be converted into a number" );
    return false;
  }
},

/* Converts a number to a string
 * @param n Number the number to be converted
 * @returns str String the string returned
 */
_convertNumber: function( n ) {
  var str = n.toString();
  return str;
}                              
                            

_convertString()方法运行以下的测试:

  1. 字符串是小数格式?如果是,它使用parseFloat()方法。
  2. 字符串是整数格式?如果是,它使用parseInt()方法。
  3. 如果字符串不能转换为数字,它使用Number()构造函数。
  4. 如果结果是一个数字(使用isNaN()来测试),它返回数字。否则,它会在控制台输出警告信息并返回false

_convertNumber()方法只是简单的调用toString()方法来将数字转换为字符串。

再接下来,要定义两个方法将一个对象转换为JSON字符串和将JSON字符串转换为对象。

/* Converts a JSON string to a JavaScript object
 * @param str String the JSON string
 * @returns obj Object the JavaScript object
 */

_toJSONObject: function( str ) {
  var obj = JSON.parse( str );
  return obj;
},

/* Converts a JavaScript object to a JSON string
 * @param obj Object the JavaScript object
 * @returns str String the JSON string
 */


_toJSONString: function( obj ) {
  var str = JSON.stringify( obj );
  return str;
}                              
                            

第一个方法使用JSON.parse()方法,第二个方法调用JSON.stringify()方法。可以参考Mozilla Developer Network的文章Using Native JSON

为什么我们需要这些方法呢?因为我们的购物车使用下面的数据结构来存储相关的商品信息:

Key Value
winery-cart { "items": [ { "product": "Wine #1", "qty": 5, "price": 5 } ] }

key winery-cart包含一个代表数组对象的JSON字符串,该数组包含了商品的名称、数量和价格等信息。

现在,我们需要一个方法来在session storage值为指定的key添加items。

/* Add an object to the cart as a JSON string
 * @param values Object the object to be added to the cart
 * @returns void
 */
_addToCart: function( values ) {
  var cart = this.storage.getItem( this.cartName );
  var cartObject = this._toJSONObject( cart );
  var cartCopy = cartObject;
  var items = cartCopy.items;
  items.push( values );

  this.storage.setItem( this.cartName, this._toJSONString( cartCopy ) );
}                            
                          

这个方法从会话存储(session storage)中获取key,然后将它转换为一个javascript对象,并且使用JSON数据添加一个新的对象到购物车数组中。新添加的对象的数据结构如下:

this._addToCart({
  product: "Test",
  qty: 1,
  price: 2
});                            
                          

添加后的购物车键值对如下:

Key Value
winery-cart { "items": [ { "product": "Wine #1", "qty": 5, "price": 5 }, { "product": "Test", "qty": 1, "price": 2 } ] }

运费是按产品的数量添加到购物车,而不是每个产品的数量。

/* Custom shipping rates calculated based on total quantity of items in cart
 * @param qty Number the total quantity of items
 * @returns shipping Number the shipping rates
 */
_calculateShipping: function( qty ) {
  var shipping = 0;
  if( qty >= 6 ) {
    shipping = 10;
  }
  if( qty >= 12 && qty <= 30 ) {
    shipping = 20;
  }

  if( qty >= 30 && qty <= 60 ) {
    shipping = 30;
  }

  if( qty > 60 ) {
    shipping = 0;
  }

  return shipping;

}                            
                          

这个方法你可以自己按需要更换。

我们还需要一个方法来校验结账表单用户填写的个人信息。

/* Validates the checkout form
 * @param form Object the jQuery element of the checkout form
 * @returns valid Boolean true for success, false for failure
 */
_validateForm: function( form ) {
    var self = this;
    var fields = self.requiredFields;
    var $visibleSet = form.find( "fieldset:visible" );
    var valid = true;

    form.find( ".message" ).remove();

  $visibleSet.each(function() {

    $( this ).find( ":input" ).each(function() {
    var $input = $( this );
    var type = $input.data( "type" );
    var msg = $input.data( "message" );

    if( type == "string" ) {
      if( $input.val() == fields.str.value ) {
        $( "" ).text( msg ).
        insertBefore( $input );

        valid = false;
      }
    } else {
      if( !fields.expression.value.test( $input.val() ) ) {
        $( "" ).text( msg ).
        insertBefore( $input );

        valid = false;
      }
    }

  });
  });
  return valid;
}                            
                          

校验会发生在通过检查是否当前字段需要一个简单的字符串比较(或者data-type="string")或者一个正则表达式(data-type="expression")。所有的测试都基于requiredFields属性,如果有错误,将通过data-message来显示错误信息。

注意,上面的字段校验只是用于简单的颜色目的,并不是十分完美。实际项目中建议使用一些jQuery插件,如jQuery Validation

最后一个私有方法是注册用户在结算表单中填写的信息:

/* Save the data entered by the user in the checkout form
 * @param form Object the jQuery element of the checkout form
 * @returns void
 */
_saveFormData: function( form ) {
  var self = this;
  var $visibleSet = form.find( "fieldset:visible" );

  $visibleSet.each(function() {
    var $set = $( this );
    if( $set.is( "#fieldset-billing" ) ) {
      var name = $( "#name", $set ).val();
      var email = $( "#email", $set ).val();
      var city = $( "#city", $set ).val();
      var address = $( "#address", $set ).val();
      var zip = $( "#zip", $set ).val();
      var country = $( "#country", $set ).val();

      self.storage.setItem( "billing-name", name );
      self.storage.setItem( "billing-email", email );
      self.storage.setItem( "billing-city", city );
      self.storage.setItem( "billing-address", address );
      self.storage.setItem( "billing-zip", zip );
      self.storage.setItem( "billing-country", country );
    } else {
      var sName = $( "#sname", $set ).val();
      var sEmail = $( "#semail", $set ).val();
      var sCity = $( "#scity", $set ).val();
      var sAddress = $( "#saddress", $set ).val();
      var sZip = $( "#szip", $set ).val();
      var sCountry = $( "#scountry", $set ).val();

      self.storage.setItem( "shipping-name", sName );
      self.storage.setItem( "shipping-email", sEmail );
      self.storage.setItem( "shipping-city", sCity );
      self.storage.setItem( "shipping-address", sAddress );
      self.storage.setItem( "shipping-zip", sZip );
      self.storage.setItem( "shipping-country", sCountry );

    }
  });
}                            
                          

当用户提交结算页面表单后,session storage会被添加类似下面的数据:

Key Value
billing-name John Doe
billing-email jdoe@localhost
billing-city New York
billing-address Street 1
billing-zip 1234
billing-country USA
公共方法

公共方法在初始化方法(init())中被调用。第一件事是要在会话存储(session storage)中初始化键值对数据。

// Creates the cart keys in session storage
createCart: function() {
  if( this.storage.getItem( this.cartName ) == null ) {
    var cart = {};
    cart.items = [];

    this.storage.setItem( this.cartName, this._toJSONString( cart ) );
    this.storage.setItem( this.shippingRates, "0" );
    this.storage.setItem( this.total, "0" );
  }
}                            
                          

第一个if测试是检测数据是否已经被添加到 session storage 中,因为这个方法可以在页面加载完成后再次被调用,如果已经存在数据,保证它们不会被覆盖。

现在session storage类似下面的样子:

Key Value
winery-cart {“items”:[]}
winery-shipping-rates 0
winery-total 0

现在,我们需要处理用户添加商品到购物车的事件。

// Adds items to shopping cart
handleAddToCartForm: function() {
  var self = this;
  self.$formAddToCart.each(function() {
    var $form = $( this );
    var $product = $form.parent();
    var price = self._convertString( $product.data( "price" ) );
    var name =  $product.data( "name" );
    $form.on( "submit", function() {
      var qty = self._convertString( $form.find( ".qty" ).val() );
      var subTotal = qty * price;
      var total = self._convertString( self.storage.getItem( self.total ) );
      var sTotal = total + subTotal;
      self.storage.setItem( self.total, sTotal );
      self._addToCart({
        product: name,
        price: price,
        qty: qty
      });
      var shipping = self._convertString( self.storage.getItem( self.shippingRates ) );
      var shippingRates = self._calculateShipping( qty );
      var totalShipping = shipping + shippingRates;
      self.storage.setItem( self.shippingRates, totalShipping );
    });
  });
}                            
                          

每次用户提交这个表单,都需要获取用户添加的商品数量,并用这个数量来计算商品的价格。然后,读取session storage中的商品总价并将它更新。完成上面的步骤后,调用_addToCart()方法将商品的明细存储在session storage中。

如果某个用户选择了一个商品Wine #1,价格为5.00,购买数量为5。那么他提交表单后,session storage的数据类似下面的样子。

Key Value
winery-cart {“items”:[{“product”:”Wine #1″,”price”:5,”qty”:5}]}
winery-shipping-rates 0
winery-total 25

加入用户这时又返回去购买了Wine #2,商品价格为8.00,数量为2:

Key Value
winery-cart {“items”:[{“product”:”Wine #1″,”price”:5,”qty”:5},{“product”:”Wine #2″,”price”:8,”qty”:2}]}
winery-shipping-rates 0
winery-total 25

当用户到购物车页面或结账页面的时候,我们需要准确的显示购物车的明细:

// Displays the shopping cart
displayCart: function() {
  if( this.$formCart.length ) {
    var cart = this._toJSONObject( this.storage.getItem( this.cartName ) );
    var items = cart.items;
    var $tableCart = this.$formCart.find( ".shopping-cart" );
    var $tableCartBody = $tableCart.find( "tbody" );

    for( var i = 0; i < items.length; ++i ) {
      var item = items[i];
      var product = item.product;
      var price = this.currency + " " + item.price;
      var qty = item.qty;
      var html = "<tr><td class='pname'>" + product + "</td>" + "<td class='pqty'><input type='text' value='" + qty + "' class='qty'/></td>" + "<td class='pprice'>" + price + "</td></tr>";

      $tableCartBody.html( $tableCartBody.html() + html );
    }
    var total = this.storage.getItem( this.total );
    this.$subTotal[0].innerHTML = this.currency + " " + total;
  } else if( this.$checkoutCart.length ) {
    var checkoutCart = this._toJSONObject( this.storage.getItem( this.cartName ) );
    var cartItems = checkoutCart.items;
    var $cartBody = this.$checkoutCart.find( "tbody" );

    for( var j = 0; j < cartItems.length; ++j ) {
      var cartItem = cartItems[j];
      var cartProduct = cartItem.product;
      var cartPrice = this.currency + " " + cartItem.price;
      var cartQty = cartItem.qty;
      var cartHTML = "<tr><td class='pname'>" + cartProduct + "</td>" + "<td class='pqty'>" + cartQty + "</td>" + "<td class='pprice'>" + cartPrice + "</td></tr>";

      $cartBody.html( $cartBody.html() + cartHTML );
    }
    var cartTotal = this.storage.getItem( this.total );
    var cartShipping = this.storage.getItem( this.shippingRates );
    var subTot = this._convertString( cartTotal ) + this._convertString( cartShipping );
    this.$subTotal[0].innerHTML = this.currency + " " + this._convertNumber( subTot );
    this.$shipping[0].innerHTML = this.currency + " " + cartShipping;
  }
}                            
                          

如果是在购物车界面,该方法会遍历包含winery-cart键的数组对象,并找到相关的数据来填充购物车表格。在例子中没有添加删除商品的功能。

然后我们需要一个方法来更新购物车中商品的数量:

// Updates the cart
updateCart: function() {
    var self = this;
  if( self.$updateCartBtn.length ) {
    self.$updateCartBtn.on( "click", function() {
      var $rows = self.$formCart.find( "tbody tr" );
      var cart = self.storage.getItem( self.cartName );
      var shippingRates = self.storage.getItem( self.shippingRates );
      var total = self.storage.getItem( self.total );

      var updatedTotal = 0;
      var totalQty = 0;
      var updatedCart = {};
      updatedCart.items = [];

      $rows.each(function() {
        var $row = $( this );
        var pname = $.trim( $row.find( ".pname" ).text() );
        var pqty = self._convertString( $row.find( ".pqty > .qty" ).val() );
        var pprice = self._convertString( self._extractPrice( $row.find( ".pprice" ) ) );

        var cartObj = {
          product: pname,
          price: pprice,
          qty: pqty
        };

        updatedCart.items.push( cartObj );

        var subTotal = pqty * pprice;
        updatedTotal += subTotal;
        totalQty += pqty;
      });

      self.storage.setItem( self.total, self._convertNumber( updatedTotal ) );
      self.storage.setItem( self.shippingRates, self._convertNumber( self._calculateShipping( totalQty ) ) );
      self.storage.setItem( self.cartName, self._toJSONString( updatedCart ) );

    });
  }
}                            
                          

这个方法遍历相关的购物车表格,并在winery-cart键中插入一个新的对象,同时它会重新计算商品的价格和运费。

加入会员将Wine #2的数量从2变为6:

Key Value
winery-cart {“items”:[{“product”:”Wine #1″,”price”:5,”qty”:5},{“product”:”Wine #2″,”price”:8,”qty”:6},{“product”:”Wine #3″,”price”:11,”qty”:6}]}
winery-shipping-rates 20
winery-total 139

如果用户想清空购物车,使用的是下面的方法:

// Empties the cart by calling the _emptyCart() method
// @see $.Shop._emptyCart()
emptyCart: function() {
  var self = this;
  if( self.$emptyCartBtn.length ) {
    self.$emptyCartBtn.on( "click", function() {
      self._emptyCart();
    });
  }
}                            
                          

现在,用户清空购物车并可以重新开始购物。当用户购物完毕,我们需要在他们填写个人信息表单后处理结账表单:

// Handles the checkout form by adding a validation routine and saving user’s info in session storage
handleCheckoutOrderForm: function() {
  var self = this;
  if( self.$checkoutOrderForm.length ) {
    var $sameAsBilling = $( "#same-as-billing" );
    $sameAsBilling.on( "change", function() {
      var $check = $( this );
      if( $check.prop( "checked" ) ) {
        $( "#fieldset-shipping" ).slideUp( "normal" );
      } else {
        $( "#fieldset-shipping" ).slideDown( "normal" );
      }
    });

    self.$checkoutOrderForm.on( "submit", function() {
      var $form = $( this );
      var valid = self._validateForm( $form );

      if( !valid ) {
        return valid;
      } else {
        self._saveFormData( $form );
      }
    });
  }
}                            
                          

首先,如果用户指定计费信息和运费信息相同的话,要隐藏运费字段。可以使用change事件,结合jQuery的.prop()方法来实现。

然后,我们需要验证表单的各个字段,如果有错误的话返回false,以阻止表单的提交。如果字段全部验证成功,将用户的信息保存到session storage中,例如:

Key Value
winery-cart {“items”:[{“product”:”Wine #1″,”price”:5,”qty”:5},{“product”:”Wine #2″,”price”:8,”qty”:6},{“product”:”Wine #3″,”price”:11,”qty”:6}]}
winery-shipping-rates 20
winery-total 139
billing-name John Doe
billing-email jdoe@localhost
billing-city New York
billing-address Street 1
billing-zip 1234
billing-country USA

到这里,一个客户端的购物车系统就基本完成了。下面还有一些相关的阅读资料,建议大家阅读一下:

下载地址

Previous:
上一篇:使用CSS制作Apple Watch刻度盘动画效果
Next:
下一篇:使用CSS3制作小球弹跳的动画效果
返回顶部