会话存储(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 ) {
$(
"<span class="
message
">"
).text( msg ).
insertBefore( $input );
valid =
false
;
}
}
else
{
if
( !fields.expression.value.test( $input.val() ) ) {
$(
"<span class="
message
">"
).text( msg ).
insertBefore( $input );
valid =
false
;
}
}
});
});
return
valid;
}
</span></span>
|
校验会发生在通过检查是否当前字段需要一个简单的字符串比较(或者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