├── cart.html ├── checkout.html ├── css └── style.css ├── images ├── logo.png ├── paypal-shopping-cart.jpg ├── wine1.jpg ├── wine2.jpg └── wine3.jpg ├── index.html ├── js └── jquery.shop.js ├── order.html └── readme.md /cart.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Winery: Your Shopping Cart 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |

Winery Wines for web developers since 1999

16 |
17 |
18 |

Your Shopping Cart

19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
ItemQtyPrice
32 |

33 | Sub Total: 34 |

35 | 49 |
50 |
51 | 52 | 53 | 54 |
55 | 56 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /checkout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Winery: Checkout 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |

Winery Wines for web developers since 1999

16 |
17 |
18 |

Checkout

19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
ItemQtyPrice
31 |
32 | 33 |

34 | Shipping: 35 |

36 | 37 |

38 | Total: 39 |

40 |
41 | 42 |
43 |

Your Details

44 | 45 |
46 | Billing 47 |
48 | 49 | 50 |
51 |
52 | 53 | 54 |
55 |
56 | 57 | 58 |
59 |
60 | 61 | 62 |
63 |
64 | 65 | 66 |
67 |
68 | 69 | 74 |
75 |
76 | 77 |
Same as Billing
78 | 79 |
80 | 81 | Shipping 82 | 83 |
84 | 85 | 86 |
87 |
88 | 89 | 90 |
91 |
92 | 93 | 94 |
95 |
96 | 97 | 98 |
99 |
100 | 101 | 102 |
103 |
104 | 105 | 110 |
111 |
112 | 113 |

114 | 115 |
116 |
117 | 118 | 119 | 120 |
121 | 122 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | @import url(http://fonts.googleapis.com/css?family=PT+Serif:400,700,400italic); 2 | 3 | 4 | /*! normalize.css v2.1.2 | MIT License | git.io/normalize */article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,video{display:inline-block}audio:not([controls]){display:none;height:0}[hidden],template{display:none}script{display:none!important}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}a{background:0 0}a:focus{outline:thin dotted}a:active,a:hover{outline:0}h1{font-size:2em;margin:.67em 0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}mark{background:#ff0;color:#000}code,kbd,pre,samp{font-family:monospace,serif;font-size:1em}pre{white-space:pre-wrap}q{quotes:"\201C" "\201D" "\2018" "\2019"}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:0}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}button,input{line-height:normal}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}textarea{overflow:auto;vertical-align:top}table{border-collapse:collapse;border-spacing:0} 5 | 6 | 7 | /*= Demo */ 8 | 9 | html, body { 10 | height: 100%; 11 | min-height: 100%; 12 | } 13 | 14 | body { 15 | font: 100%/1 'PT Serif', serif; 16 | background: #fff; 17 | color: #333; 18 | border-top: 0.5em solid #cc8500; 19 | } 20 | 21 | :active, :focus { 22 | outline-style: none; 23 | } 24 | 25 | a:link { 26 | color: #cc8500; 27 | text-decoration: none; 28 | } 29 | 30 | a:hover { 31 | color: #cc1400; 32 | text-decoration: underline; 33 | } 34 | 35 | h1, h2, h3, h4, h5, h6 { 36 | font-weight: normal; 37 | font-size: 100%; 38 | } 39 | 40 | #site { 41 | max-width: 960px; 42 | margin: 0 auto; 43 | padding: 0 1em; 44 | } 45 | 46 | #masthead { 47 | margin-top: 3em; 48 | padding: 2em 0 0.5em 0; 49 | border-bottom: 1px solid #ddd; 50 | } 51 | 52 | #masthead h1 { 53 | margin: 0; 54 | font-size: 3.5em; 55 | overflow: hidden; 56 | padding-left: 85px; 57 | min-height: 82px; 58 | line-height: 2.3; 59 | background: url(../images/logo.png) no-repeat 0 50%; 60 | color: #900; 61 | 62 | } 63 | 64 | .tagline { 65 | float: right; 66 | color: #666; 67 | font-size: 14px; 68 | padding-top: 3.5em; 69 | } 70 | 71 | #products ul { 72 | margin: 1.5em 0; 73 | padding: 0; 74 | list-style: none; 75 | overflow: hidden; 76 | } 77 | 78 | #products li { 79 | float: left; 80 | width: 31%; 81 | margin: 0 1%; 82 | display: block; 83 | } 84 | 85 | .product-image { 86 | margin: 0 auto 1em auto; 87 | background: #000; 88 | width: 150px; 89 | height: 150px; 90 | overflow: hidden; 91 | border-radius: 50%; 92 | 93 | } 94 | 95 | .product-description { 96 | padding: 1em; 97 | background: #000; 98 | color: #fff; 99 | border-radius: 5px; 100 | } 101 | 102 | .product-name { 103 | text-align: center; 104 | color: #fc0; 105 | margin: 0; 106 | font-size: 1.4em; 107 | padding-bottom: 0.2em; 108 | border-bottom: 1px dotted #666; 109 | text-transform: uppercase; 110 | letter-spacing: 0.1em; 111 | } 112 | 113 | .product-price { 114 | width: 4em; 115 | height: 4em; 116 | font-size: 1.2em; 117 | text-align: center; 118 | margin: 1em auto; 119 | background: #fff; 120 | color: #800; 121 | line-height: 4; 122 | border-radius: 50%; 123 | } 124 | 125 | form.add-to-cart div, 126 | form.add-to-cart p { 127 | text-align: center; 128 | } 129 | 130 | form input.qty { 131 | width: 40px; 132 | border: 1px solid #eee; 133 | font: 1em 'PT Serif', serif; 134 | background: #f9f9f9; 135 | color: #000; 136 | border-radius: 3px; 137 | margin-left: 0.4em; 138 | } 139 | 140 | .btn, a.btn { 141 | display: inline-block; 142 | background: #cc1400; 143 | color: #fff; 144 | font: 1em 'PT Serif', serif; 145 | padding: 0.3em 1em; 146 | text-align: center; 147 | border-radius: 4px; 148 | border: 1px solid #a00; 149 | } 150 | 151 | 152 | #site-info { 153 | height: 3em; 154 | width: 100%; 155 | line-height: 3; 156 | text-align: center; 157 | background: #cc8500; 158 | position: absolute; 159 | color: #fff; 160 | left: 0; 161 | bottom: 0; 162 | } 163 | 164 | body#checkout-page #site-info { 165 | position: static; 166 | } 167 | 168 | #shopping-cart { 169 | margin: 1.5em 0; 170 | } 171 | 172 | .shopping-cart { 173 | border: 1px solid #ddd; 174 | border-collapse: collapse; 175 | border-spacing: 0; 176 | width: 100%; 177 | table-layout: fixed; 178 | } 179 | 180 | .shopping-cart th { 181 | font-size: 1.3em; 182 | padding: 0.3em; 183 | width: 33.3%; 184 | border: 1px solid #ddd; 185 | text-transform: uppercase; 186 | } 187 | 188 | .shopping-cart td { 189 | padding: 0.3em; 190 | width: 33.3%; 191 | border: 1px solid #ddd; 192 | } 193 | 194 | .shopping-cart tr:nth-child(even) { 195 | background: #fafafa; 196 | }, 197 | 198 | .shopping-cart td.pdelete { 199 | text-align: center; 200 | } 201 | 202 | .pdelete a, 203 | .pdelete a:hover { 204 | color: #c00; 205 | text-decoration: none; 206 | font-size: 2.5em; 207 | display: block; 208 | text-align: center; 209 | } 210 | 211 | #shopping-cart-actions { 212 | margin: 1.5em 0; 213 | padding: 0; 214 | list-style: none; 215 | text-align: center; 216 | } 217 | 218 | #shopping-cart-actions li { 219 | display: inline-block; 220 | margin-right: 1em; 221 | } 222 | 223 | #pricing { 224 | padding: 0.5em; 225 | margin: 1em 0; 226 | background: #fafafa; 227 | } 228 | 229 | #sub-total, #shipping { 230 | margin: 1.5em 0; 231 | text-align: right; 232 | } 233 | 234 | #sub-total span, 235 | #shipping span { 236 | margin-left: 1em; 237 | } 238 | 239 | #content > h1, 240 | #checkout-order-form h2 { 241 | font-size: 2em; 242 | text-align: center; 243 | } 244 | 245 | #pricing #sub-total, 246 | #pricing #shipping { 247 | margin: 1em 0; 248 | } 249 | 250 | #checkout-order-form { 251 | margin: 1.5em 0; 252 | } 253 | 254 | #checkout-order-form fieldset { 255 | border: 1px solid #ddd; 256 | border-radius: 6px; 257 | padding: 1em; 258 | margin-bottom: 1.3em; 259 | } 260 | 261 | #checkout-order-form legend { 262 | padding: 0.3em; 263 | background: #fafafa; 264 | font-weight: bold; 265 | } 266 | 267 | #checkout-order-form div { 268 | margin-bottom: 1em; 269 | } 270 | 271 | #checkout-order-form label { 272 | display: block; 273 | font-weight: bold; 274 | margin-bottom: 0.3em; 275 | text-align: left; 276 | } 277 | 278 | #checkout-order-form input[type="text"] { 279 | width: 200px; 280 | display: block; 281 | background: #fff; 282 | border: 1px solid #ddd; 283 | border-radius: 4px; 284 | padding: 0.3em; 285 | } 286 | 287 | #checkout-order-form select { 288 | width: 200px; 289 | display: block; 290 | } 291 | 292 | .message { 293 | display: block; 294 | margin: 0.5em 0; 295 | color: red; 296 | } 297 | 298 | #user-details { 299 | margin: 1.5em 0; 300 | } 301 | 302 | #user-details > h2 { 303 | text-align: center; 304 | font-size: 2em; 305 | } 306 | 307 | #user-details-content { 308 | margin: 1.5em 0; 309 | padding: 1em; 310 | border: 1px solid #ddd; 311 | border-radius: 6px; 312 | overflow: hidden; 313 | } 314 | 315 | #user-details-content .detail { 316 | float: left; 317 | width: 46%; 318 | } 319 | 320 | #user-details-content .detail.right { 321 | float: right; 322 | } 323 | 324 | #user-details-content .detail > h2 { 325 | text-align: center; 326 | font-size: 1.6em; 327 | margin-bottom: 0.5em; 328 | } 329 | 330 | #user-details-content ul { 331 | margin: 0 0 1em 0; 332 | padding: 0; 333 | list-style: none; 334 | } 335 | 336 | #user-details-content li { 337 | display: block; 338 | margin-bottom: 0.5em; 339 | padding-bottom: 0.3em; 340 | border-bottom: 1px solid #ddd; 341 | } 342 | 343 | #paypal-form { 344 | margin: 1.5em 0; 345 | } 346 | 347 | -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrieleromanato/jQuery-sessionStorage-shopping-cart/05e5dd7408ba13afb71fca3b973fdd6df322f0c4/images/logo.png -------------------------------------------------------------------------------- /images/paypal-shopping-cart.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrieleromanato/jQuery-sessionStorage-shopping-cart/05e5dd7408ba13afb71fca3b973fdd6df322f0c4/images/paypal-shopping-cart.jpg -------------------------------------------------------------------------------- /images/wine1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrieleromanato/jQuery-sessionStorage-shopping-cart/05e5dd7408ba13afb71fca3b973fdd6df322f0c4/images/wine1.jpg -------------------------------------------------------------------------------- /images/wine2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrieleromanato/jQuery-sessionStorage-shopping-cart/05e5dd7408ba13afb71fca3b973fdd6df322f0c4/images/wine2.jpg -------------------------------------------------------------------------------- /images/wine3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabrieleromanato/jQuery-sessionStorage-shopping-cart/05e5dd7408ba13afb71fca3b973fdd6df322f0c4/images/wine3.jpg -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Winery 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |

Winery Wines for web developers since 1999

16 |
17 |
18 |
19 |
    20 |
  • 21 |
    22 | 23 |
    24 |
    25 |

    Wine #1

    26 |

    € 5

    27 |
    28 |
    29 | 30 | 31 |
    32 |

    33 |
    34 |
    35 |
  • 36 | 37 |
  • 38 |
    39 | 40 |
    41 |
    42 |

    Wine #2

    43 |

    € 8

    44 |
    45 |
    46 | 47 | 48 |
    49 |

    50 |
    51 |
    52 |
  • 53 | 54 |
  • 55 |
    56 | 57 |
    58 |
    59 |

    Wine #3

    60 |

    € 11

    61 |
    62 |
    63 | 64 | 65 |
    66 |

    67 |
    68 |
    69 |
  • 70 | 71 | 72 | 73 |
74 |
75 |
76 | 77 | 78 | 79 |
80 | 81 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /js/jquery.shop.js: -------------------------------------------------------------------------------- 1 | (function( $ ) { 2 | $.Shop = function( element ) { 3 | this.$element = $( element ); 4 | this.init(); 5 | }; 6 | 7 | $.Shop.prototype = { 8 | init: function() { 9 | 10 | // Properties 11 | 12 | this.cartPrefix = "winery-"; // Prefix string to be prepended to the cart's name in the session storage 13 | this.cartName = this.cartPrefix + "cart"; // Cart name in the session storage 14 | this.shippingRates = this.cartPrefix + "shipping-rates"; // Shipping rates key in the session storage 15 | this.total = this.cartPrefix + "total"; // Total key in the session storage 16 | this.storage = sessionStorage; // shortcut to the sessionStorage object 17 | 18 | 19 | this.$formAddToCart = this.$element.find( "form.add-to-cart" ); // Forms for adding items to the cart 20 | this.$formCart = this.$element.find( "#shopping-cart" ); // Shopping cart form 21 | this.$checkoutCart = this.$element.find( "#checkout-cart" ); // Checkout form cart 22 | this.$checkoutOrderForm = this.$element.find( "#checkout-order-form" ); // Checkout user details form 23 | this.$shipping = this.$element.find( "#sshipping" ); // Element that displays the shipping rates 24 | this.$subTotal = this.$element.find( "#stotal" ); // Element that displays the subtotal charges 25 | this.$shoppingCartActions = this.$element.find( "#shopping-cart-actions" ); // Cart actions links 26 | this.$updateCartBtn = this.$shoppingCartActions.find( "#update-cart" ); // Update cart button 27 | this.$emptyCartBtn = this.$shoppingCartActions.find( "#empty-cart" ); // Empty cart button 28 | this.$userDetails = this.$element.find( "#user-details-content" ); // Element that displays the user information 29 | this.$paypalForm = this.$element.find( "#paypal-form" ); // PayPal form 30 | 31 | 32 | this.currency = "€"; // HTML entity of the currency to be displayed in the layout 33 | this.currencyString = "€"; // Currency symbol as textual string 34 | this.paypalCurrency = "EUR"; // PayPal's currency code 35 | this.paypalBusinessEmail = "yourbusiness@email.com"; // Your Business PayPal's account email address 36 | this.paypalURL = "https://www.sandbox.paypal.com/cgi-bin/webscr"; // The URL of the PayPal's form 37 | 38 | // Object containing patterns for form validation 39 | this.requiredFields = { 40 | expression: { 41 | value: /^([\w-\.]+)@((?:[\w]+\.)+)([a-z]){2,4}$/ 42 | }, 43 | 44 | str: { 45 | value: "" 46 | } 47 | 48 | }; 49 | 50 | // Method invocation 51 | 52 | this.createCart(); 53 | this.handleAddToCartForm(); 54 | this.handleCheckoutOrderForm(); 55 | this.emptyCart(); 56 | this.updateCart(); 57 | this.displayCart(); 58 | this.deleteProduct(); 59 | this.displayUserDetails(); 60 | this.populatePayPalForm(); 61 | 62 | 63 | }, 64 | 65 | // Public methods 66 | 67 | // Creates the cart keys in the session storage 68 | 69 | createCart: function() { 70 | if( this.storage.getItem( this.cartName ) == null ) { 71 | 72 | var cart = {}; 73 | cart.items = []; 74 | 75 | this.storage.setItem( this.cartName, this._toJSONString( cart ) ); 76 | this.storage.setItem( this.shippingRates, "0" ); 77 | this.storage.setItem( this.total, "0" ); 78 | } 79 | }, 80 | 81 | // Appends the required hidden values to the PayPal's form before submitting 82 | 83 | populatePayPalForm: function() { 84 | var self = this; 85 | if( self.$paypalForm.length ) { 86 | var $form = self.$paypalForm; 87 | var cart = self._toJSONObject( self.storage.getItem( self.cartName ) ); 88 | var shipping = self.storage.getItem( self.shippingRates ); 89 | var numShipping = self._convertString( shipping ); 90 | var cartItems = cart.items; 91 | var singShipping = Math.floor( numShipping / cartItems.length ); 92 | 93 | $form.attr( "action", self.paypalURL ); 94 | $form.find( "input[name='business']" ).val( self.paypalBusinessEmail ); 95 | $form.find( "input[name='currency_code']" ).val( self.paypalCurrency ); 96 | 97 | for( var i = 0; i < cartItems.length; ++i ) { 98 | var cartItem = cartItems[i]; 99 | var n = i + 1; 100 | var name = cartItem.product; 101 | var price = cartItem.price; 102 | var qty = cartItem.qty; 103 | 104 | $( "
" ).html( "" ). 105 | insertBefore( "#paypal-btn" ); 106 | $( "
" ).html( "" ). 107 | insertBefore( "#paypal-btn" ); 108 | $( "
" ).html( "" ). 109 | insertBefore( "#paypal-btn" ); 110 | $( "
" ).html( "" ). 111 | insertBefore( "#paypal-btn" ); 112 | $( "
" ).html( "" ). 113 | insertBefore( "#paypal-btn" ); 114 | 115 | } 116 | 117 | 118 | 119 | } 120 | }, 121 | 122 | // Displays the user's information 123 | 124 | displayUserDetails: function() { 125 | if( this.$userDetails.length ) { 126 | if( this.storage.getItem( "shipping-name" ) == null ) { 127 | var name = this.storage.getItem( "billing-name" ); 128 | var email = this.storage.getItem( "billing-email" ); 129 | var city = this.storage.getItem( "billing-city" ); 130 | var address = this.storage.getItem( "billing-address" ); 131 | var zip = this.storage.getItem( "billing-zip" ); 132 | var country = this.storage.getItem( "billing-country" ); 133 | 134 | var html = "
"; 135 | html += "

Billing and Shipping

"; 136 | html += "
    "; 137 | html += "
  • " + name + "
  • "; 138 | html += "
  • " + email + "
  • "; 139 | html += "
  • " + city + "
  • "; 140 | html += "
  • " + address + "
  • "; 141 | html += "
  • " + zip + "
  • "; 142 | html += "
  • " + country + "
  • "; 143 | html += "
"; 144 | 145 | this.$userDetails[0].innerHTML = html; 146 | } else { 147 | var name = this.storage.getItem( "billing-name" ); 148 | var email = this.storage.getItem( "billing-email" ); 149 | var city = this.storage.getItem( "billing-city" ); 150 | var address = this.storage.getItem( "billing-address" ); 151 | var zip = this.storage.getItem( "billing-zip" ); 152 | var country = this.storage.getItem( "billing-country" ); 153 | 154 | var sName = this.storage.getItem( "shipping-name" ); 155 | var sEmail = this.storage.getItem( "shipping-email" ); 156 | var sCity = this.storage.getItem( "shipping-city" ); 157 | var sAddress = this.storage.getItem( "shipping-address" ); 158 | var sZip = this.storage.getItem( "shipping-zip" ); 159 | var sCountry = this.storage.getItem( "shipping-country" ); 160 | 161 | var html = "
"; 162 | html += "

Billing

"; 163 | html += "
    "; 164 | html += "
  • " + name + "
  • "; 165 | html += "
  • " + email + "
  • "; 166 | html += "
  • " + city + "
  • "; 167 | html += "
  • " + address + "
  • "; 168 | html += "
  • " + zip + "
  • "; 169 | html += "
  • " + country + "
  • "; 170 | html += "
"; 171 | 172 | html += "
"; 173 | html += "

Shipping

"; 174 | html += "
    "; 175 | html += "
  • " + sName + "
  • "; 176 | html += "
  • " + sEmail + "
  • "; 177 | html += "
  • " + sCity + "
  • "; 178 | html += "
  • " + sAddress + "
  • "; 179 | html += "
  • " + sZip + "
  • "; 180 | html += "
  • " + sCountry + "
  • "; 181 | html += "
"; 182 | 183 | this.$userDetails[0].innerHTML = html; 184 | 185 | } 186 | } 187 | }, 188 | 189 | // Delete a product from the shopping cart 190 | 191 | deleteProduct: function() { 192 | var self = this; 193 | if( self.$formCart.length ) { 194 | var cart = this._toJSONObject( this.storage.getItem( this.cartName ) ); 195 | var items = cart.items; 196 | 197 | $( document ).on( "click", ".pdelete a", function( e ) { 198 | e.preventDefault(); 199 | var productName = $( this ).data( "product" ); 200 | var newItems = []; 201 | for( var i = 0; i < items.length; ++i ) { 202 | var item = items[i]; 203 | var product = item.product; 204 | if( product == productName ) { 205 | items.splice( i, 1 ); 206 | } 207 | } 208 | newItems = items; 209 | var updatedCart = {}; 210 | updatedCart.items = newItems; 211 | 212 | var updatedTotal = 0; 213 | var totalQty = 0; 214 | if( newItems.length == 0 ) { 215 | updatedTotal = 0; 216 | totalQty = 0; 217 | } else { 218 | for( var j = 0; j < newItems.length; ++j ) { 219 | var prod = newItems[j]; 220 | var sub = prod.price * prod.qty; 221 | updatedTotal += sub; 222 | totalQty += prod.qty; 223 | } 224 | } 225 | 226 | self.storage.setItem( self.total, self._convertNumber( updatedTotal ) ); 227 | self.storage.setItem( self.shippingRates, self._convertNumber( self._calculateShipping( totalQty ) ) ); 228 | 229 | self.storage.setItem( self.cartName, self._toJSONString( updatedCart ) ); 230 | $( this ).parents( "tr" ).remove(); 231 | self.$subTotal[0].innerHTML = self.currency + " " + self.storage.getItem( self.total ); 232 | }); 233 | } 234 | }, 235 | 236 | // Displays the shopping cart 237 | 238 | displayCart: function() { 239 | if( this.$formCart.length ) { 240 | var cart = this._toJSONObject( this.storage.getItem( this.cartName ) ); 241 | var items = cart.items; 242 | var $tableCart = this.$formCart.find( ".shopping-cart" ); 243 | var $tableCartBody = $tableCart.find( "tbody" ); 244 | 245 | if( items.length == 0 ) { 246 | $tableCartBody.html( "" ); 247 | } else { 248 | 249 | 250 | for( var i = 0; i < items.length; ++i ) { 251 | var item = items[i]; 252 | var product = item.product; 253 | var price = this.currency + " " + item.price; 254 | var qty = item.qty; 255 | var html = "" + product + "" + ""; 256 | html += "" + price + "×"; 257 | 258 | $tableCartBody.html( $tableCartBody.html() + html ); 259 | } 260 | 261 | } 262 | 263 | if( items.length == 0 ) { 264 | this.$subTotal[0].innerHTML = this.currency + " " + 0.00; 265 | } else { 266 | 267 | var total = this.storage.getItem( this.total ); 268 | this.$subTotal[0].innerHTML = this.currency + " " + total; 269 | } 270 | } else if( this.$checkoutCart.length ) { 271 | var checkoutCart = this._toJSONObject( this.storage.getItem( this.cartName ) ); 272 | var cartItems = checkoutCart.items; 273 | var $cartBody = this.$checkoutCart.find( "tbody" ); 274 | 275 | if( cartItems.length > 0 ) { 276 | 277 | for( var j = 0; j < cartItems.length; ++j ) { 278 | var cartItem = cartItems[j]; 279 | var cartProduct = cartItem.product; 280 | var cartPrice = this.currency + " " + cartItem.price; 281 | var cartQty = cartItem.qty; 282 | var cartHTML = "" + cartProduct + "" + "" + cartQty + "" + "" + cartPrice + ""; 283 | 284 | $cartBody.html( $cartBody.html() + cartHTML ); 285 | } 286 | } else { 287 | $cartBody.html( "" ); 288 | } 289 | 290 | if( cartItems.length > 0 ) { 291 | 292 | var cartTotal = this.storage.getItem( this.total ); 293 | var cartShipping = this.storage.getItem( this.shippingRates ); 294 | var subTot = this._convertString( cartTotal ) + this._convertString( cartShipping ); 295 | 296 | this.$subTotal[0].innerHTML = this.currency + " " + this._convertNumber( subTot ); 297 | this.$shipping[0].innerHTML = this.currency + " " + cartShipping; 298 | } else { 299 | this.$subTotal[0].innerHTML = this.currency + " " + 0.00; 300 | this.$shipping[0].innerHTML = this.currency + " " + 0.00; 301 | } 302 | 303 | } 304 | }, 305 | 306 | // Empties the cart by calling the _emptyCart() method 307 | // @see $.Shop._emptyCart() 308 | 309 | emptyCart: function() { 310 | var self = this; 311 | if( self.$emptyCartBtn.length ) { 312 | self.$emptyCartBtn.on( "click", function() { 313 | self._emptyCart(); 314 | }); 315 | } 316 | }, 317 | 318 | // Updates the cart 319 | 320 | updateCart: function() { 321 | var self = this; 322 | if( self.$updateCartBtn.length ) { 323 | self.$updateCartBtn.on( "click", function() { 324 | var $rows = self.$formCart.find( "tbody tr" ); 325 | var cart = self.storage.getItem( self.cartName ); 326 | var shippingRates = self.storage.getItem( self.shippingRates ); 327 | var total = self.storage.getItem( self.total ); 328 | 329 | var updatedTotal = 0; 330 | var totalQty = 0; 331 | var updatedCart = {}; 332 | updatedCart.items = []; 333 | 334 | $rows.each(function() { 335 | var $row = $( this ); 336 | var pname = $.trim( $row.find( ".pname" ).text() ); 337 | var pqty = self._convertString( $row.find( ".pqty > .qty" ).val() ); 338 | var pprice = self._convertString( self._extractPrice( $row.find( ".pprice" ) ) ); 339 | 340 | var cartObj = { 341 | product: pname, 342 | price: pprice, 343 | qty: pqty 344 | }; 345 | 346 | updatedCart.items.push( cartObj ); 347 | 348 | var subTotal = pqty * pprice; 349 | updatedTotal += subTotal; 350 | totalQty += pqty; 351 | }); 352 | 353 | self.storage.setItem( self.total, self._convertNumber( updatedTotal ) ); 354 | self.storage.setItem( self.shippingRates, self._convertNumber( self._calculateShipping( totalQty ) ) ); 355 | self.storage.setItem( self.cartName, self._toJSONString( updatedCart ) ); 356 | 357 | }); 358 | } 359 | }, 360 | 361 | // Adds items to the shopping cart 362 | 363 | handleAddToCartForm: function() { 364 | var self = this; 365 | self.$formAddToCart.each(function() { 366 | var $form = $( this ); 367 | var $product = $form.parent(); 368 | var price = self._convertString( $product.data( "price" ) ); 369 | var name = $product.data( "name" ); 370 | 371 | $form.on( "submit", function() { 372 | var qty = self._convertString( $form.find( ".qty" ).val() ); 373 | var subTotal = qty * price; 374 | var total = self._convertString( self.storage.getItem( self.total ) ); 375 | var sTotal = total + subTotal; 376 | self.storage.setItem( self.total, sTotal ); 377 | self._addToCart({ 378 | product: name, 379 | price: price, 380 | qty: qty 381 | }); 382 | var shipping = self._convertString( self.storage.getItem( self.shippingRates ) ); 383 | var shippingRates = self._calculateShipping( qty ); 384 | var totalShipping = shipping + shippingRates; 385 | 386 | self.storage.setItem( self.shippingRates, totalShipping ); 387 | }); 388 | }); 389 | }, 390 | 391 | // Handles the checkout form by adding a validation routine and saving user's info into the session storage 392 | 393 | handleCheckoutOrderForm: function() { 394 | var self = this; 395 | if( self.$checkoutOrderForm.length ) { 396 | var $sameAsBilling = $( "#same-as-billing" ); 397 | $sameAsBilling.on( "change", function() { 398 | var $check = $( this ); 399 | if( $check.prop( "checked" ) ) { 400 | $( "#fieldset-shipping" ).slideUp( "normal" ); 401 | } else { 402 | $( "#fieldset-shipping" ).slideDown( "normal" ); 403 | } 404 | }); 405 | 406 | self.$checkoutOrderForm.on( "submit", function() { 407 | var $form = $( this ); 408 | var valid = self._validateForm( $form ); 409 | 410 | if( !valid ) { 411 | return valid; 412 | } else { 413 | self._saveFormData( $form ); 414 | } 415 | }); 416 | } 417 | }, 418 | 419 | // Private methods 420 | 421 | 422 | // Empties the session storage 423 | 424 | _emptyCart: function() { 425 | this.storage.clear(); 426 | }, 427 | 428 | /* Format a number by decimal places 429 | * @param num Number the number to be formatted 430 | * @param places Number the decimal places 431 | * @returns n Number the formatted number 432 | */ 433 | 434 | 435 | 436 | _formatNumber: function( num, places ) { 437 | var n = num.toFixed( places ); 438 | return n; 439 | }, 440 | 441 | /* Extract the numeric portion from a string 442 | * @param element Object the jQuery element that contains the relevant string 443 | * @returns price String the numeric string 444 | */ 445 | 446 | 447 | _extractPrice: function( element ) { 448 | var self = this; 449 | var text = element.text(); 450 | var price = text.replace( self.currencyString, "" ).replace( " ", "" ); 451 | return price; 452 | }, 453 | 454 | /* Converts a numeric string into a number 455 | * @param numStr String the numeric string to be converted 456 | * @returns num Number the number 457 | */ 458 | 459 | _convertString: function( numStr ) { 460 | var num; 461 | if( /^[-+]?[0-9]+\.[0-9]+$/.test( numStr ) ) { 462 | num = parseFloat( numStr ); 463 | } else if( /^\d+$/.test( numStr ) ) { 464 | num = parseInt( numStr, 10 ); 465 | } else { 466 | num = Number( numStr ); 467 | } 468 | 469 | if( !isNaN( num ) ) { 470 | return num; 471 | } else { 472 | console.warn( numStr + " cannot be converted into a number" ); 473 | return false; 474 | } 475 | }, 476 | 477 | /* Converts a number to a string 478 | * @param n Number the number to be converted 479 | * @returns str String the string returned 480 | */ 481 | 482 | _convertNumber: function( n ) { 483 | var str = n.toString(); 484 | return str; 485 | }, 486 | 487 | /* Converts a JSON string to a JavaScript object 488 | * @param str String the JSON string 489 | * @returns obj Object the JavaScript object 490 | */ 491 | 492 | _toJSONObject: function( str ) { 493 | var obj = JSON.parse( str ); 494 | return obj; 495 | }, 496 | 497 | /* Converts a JavaScript object to a JSON string 498 | * @param obj Object the JavaScript object 499 | * @returns str String the JSON string 500 | */ 501 | 502 | 503 | _toJSONString: function( obj ) { 504 | var str = JSON.stringify( obj ); 505 | return str; 506 | }, 507 | 508 | 509 | /* Add an object to the cart as a JSON string 510 | * @param values Object the object to be added to the cart 511 | * @returns void 512 | */ 513 | 514 | 515 | _addToCart: function( values ) { 516 | var cart = this.storage.getItem( this.cartName ); 517 | 518 | var cartObject = this._toJSONObject( cart ); 519 | var cartCopy = cartObject; 520 | var items = cartCopy.items; 521 | items.push( values ); 522 | 523 | this.storage.setItem( this.cartName, this._toJSONString( cartCopy ) ); 524 | }, 525 | 526 | /* Custom shipping rates calculation based on the total quantity of items in the cart 527 | * @param qty Number the total quantity of items 528 | * @returns shipping Number the shipping rates 529 | */ 530 | 531 | _calculateShipping: function( qty ) { 532 | var shipping = 0; 533 | if( qty >= 6 ) { 534 | shipping = 10; 535 | } 536 | if( qty >= 12 && qty <= 30 ) { 537 | shipping = 20; 538 | } 539 | 540 | if( qty >= 30 && qty <= 60 ) { 541 | shipping = 30; 542 | } 543 | 544 | if( qty > 60 ) { 545 | shipping = 0; 546 | } 547 | 548 | return shipping; 549 | 550 | }, 551 | 552 | /* Validates the checkout form 553 | * @param form Object the jQuery element of the checkout form 554 | * @returns valid Boolean true for success, false for failure 555 | */ 556 | 557 | 558 | 559 | _validateForm: function( form ) { 560 | var self = this; 561 | var fields = self.requiredFields; 562 | var $visibleSet = form.find( "fieldset:visible" ); 563 | var valid = true; 564 | 565 | form.find( ".message" ).remove(); 566 | 567 | $visibleSet.each(function() { 568 | 569 | $( this ).find( ":input" ).each(function() { 570 | var $input = $( this ); 571 | var type = $input.data( "type" ); 572 | var msg = $input.data( "message" ); 573 | 574 | if( type == "string" ) { 575 | if( $input.val() == fields.str.value ) { 576 | $( "" ).text( msg ). 577 | insertBefore( $input ); 578 | 579 | valid = false; 580 | } 581 | } else { 582 | if( !fields.expression.value.test( $input.val() ) ) { 583 | $( "" ).text( msg ). 584 | insertBefore( $input ); 585 | 586 | valid = false; 587 | } 588 | } 589 | 590 | }); 591 | }); 592 | 593 | return valid; 594 | 595 | }, 596 | 597 | /* Save the data entered by the user in the ckeckout form 598 | * @param form Object the jQuery element of the checkout form 599 | * @returns void 600 | */ 601 | 602 | 603 | _saveFormData: function( form ) { 604 | var self = this; 605 | var $visibleSet = form.find( "fieldset:visible" ); 606 | 607 | $visibleSet.each(function() { 608 | var $set = $( this ); 609 | if( $set.is( "#fieldset-billing" ) ) { 610 | var name = $( "#name", $set ).val(); 611 | var email = $( "#email", $set ).val(); 612 | var city = $( "#city", $set ).val(); 613 | var address = $( "#address", $set ).val(); 614 | var zip = $( "#zip", $set ).val(); 615 | var country = $( "#country", $set ).val(); 616 | 617 | self.storage.setItem( "billing-name", name ); 618 | self.storage.setItem( "billing-email", email ); 619 | self.storage.setItem( "billing-city", city ); 620 | self.storage.setItem( "billing-address", address ); 621 | self.storage.setItem( "billing-zip", zip ); 622 | self.storage.setItem( "billing-country", country ); 623 | } else { 624 | var sName = $( "#sname", $set ).val(); 625 | var sEmail = $( "#semail", $set ).val(); 626 | var sCity = $( "#scity", $set ).val(); 627 | var sAddress = $( "#saddress", $set ).val(); 628 | var sZip = $( "#szip", $set ).val(); 629 | var sCountry = $( "#scountry", $set ).val(); 630 | 631 | self.storage.setItem( "shipping-name", sName ); 632 | self.storage.setItem( "shipping-email", sEmail ); 633 | self.storage.setItem( "shipping-city", sCity ); 634 | self.storage.setItem( "shipping-address", sAddress ); 635 | self.storage.setItem( "shipping-zip", sZip ); 636 | self.storage.setItem( "shipping-country", sCountry ); 637 | 638 | } 639 | }); 640 | } 641 | }; 642 | 643 | $(function() { 644 | var shop = new $.Shop( "#site" ); 645 | }); 646 | 647 | })( jQuery ); -------------------------------------------------------------------------------- /order.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Winery: Your Order 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |

Winery Wines for web developers since 1999

16 |
17 |
18 |

Your Order

19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |
ItemQtyPrice
31 |
32 | 33 |

34 | Shipping: 35 |

36 | 37 |

38 | Total: 39 |

40 |
41 | 42 |
43 |

Your Data

44 |
45 |
46 | 47 | 48 | 49 | 50 |
51 | 52 | 53 | 54 | 55 | 56 | 57 |
58 | 59 | 60 |
61 | 62 | 63 | 64 |
65 | 66 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # jQuery: a shopping cart with sessionStorage 2 | 3 | I've built this shopping cart after completing a web project where I had to completely rewrite an e-commerce cart. This project also includes PayPal payments. 4 | 5 | This is a proof-of-concept and is not intended to replace any existing server-side techniques. 6 | 7 | Bear in mind that there are a few things you need to change on the main `jquery.shop.js` file, namely: 8 | 9 | * The type of PayPal's cart. 10 | * Your business email address. 11 | * The URL of the PayPal's form. 12 | * The way shipping charges are calculated. 13 | 14 | Hope you find this useful. 15 | 16 | ## Demo 17 | [https://jquerycart.gabrieleromanato.dev/](https://jquerycart.gabrieleromanato.dev/) 18 | --------------------------------------------------------------------------------