会话存储(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项。
null。当我们在session storage中移除了一个key后,再去获取它的值,将返回null。现在,我们刚才设置的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:总价。
- storage:
sessionStorage对象。 - 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()方法运行以下的测试:
- 字符串是小数格式?如果是,它使用parseFloat()方法。
- 字符串是整数格式?如果是,它使用parseInt()方法。
- 如果字符串不能转换为数字,它使用Number()构造函数。
- 如果结果是一个数字(使用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 |
到这里,一个客户端的购物车系统就基本完成了。下面还有一些相关的阅读资料,建议大家阅读一下:
- Mozilla Developer Network-DOM Storage Guide
- Nicholas C. Zakas-Introduction to Session Storage
- Mozilla Developer Network-Using data-* Attributes