";
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 |
Item
23 |
Qty
24 |
Price
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | Shipping:
35 |
36 |
37 |
38 | Total:
39 |
40 |
41 |
42 |
43 |
Your Data
44 |
45 |
46 |
47 |
48 |
49 |
50 |
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 |
--------------------------------------------------------------------------------