sessionStorage 购物车

会话存储(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项。

注意:当在session storage中一个键不存在是,它的值总是 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)中明确规定:

“Every community organization, corporation, business, or government agency relies on an outward-facing website to provide information about themselves, announce an event, or sell a product or service. Consequently, public-facing websites are often the most targeted attack vectors for malicious activity.”

即使是在浏览器正常关闭的情况下终止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

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

KeyValue
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
});                           

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

KeyValue
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会被添加类似下面的数据:

KeyValue
billing-nameJohn Doe
billing-emailjdoe@localhost
billing-cityNew York
billing-addressStreet 1
billing-zip1234
billing-countryUSA
公共方法

公共方法在初始化方法(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类似下面的样子:

KeyValue
winery-cart{“items”:[]}
winery-shipping-rates0
winery-total0

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

// 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的数据类似下面的样子。

KeyValue
winery-cart{“items”:[{“product”:”Wine #1″,”price”:5,”qty”:5}]}
winery-shipping-rates0
winery-total25

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

KeyValue
winery-cart{“items”:[{“product”:”Wine #1″,”price”:5,”qty”:5},{“product”:”Wine #2″,”price”:8,”qty”:2}]}
winery-shipping-rates0
winery-total25

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

// 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:

KeyValue
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-rates20
winery-total139

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

// 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中,例如:

KeyValue
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-rates20
winery-total139
billing-nameJohn Doe
billing-emailjdoe@localhost
billing-cityNew York
billing-addressStreet 1
billing-zip1234
billing-countryUSA

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

原文链接:http://www.htmleaf.com/ziliaoku/qianduanjiaocheng/201503201552.html
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值