Skip to main content
Code Review

Return to Question

deleted 650 characters in body; edited title
Source Link
Jamal
  • 35.2k
  • 13
  • 134
  • 238

Javascript based JavaScript-based product customization app

http://duc.pagodabox.com/product.php

I'm developing a JS based appJS-based app that allows you to customize a product. I still consider myself a super newbie to javascript and especially to object oriented programming. So far, so good, the app does what I want it to do, so no trouble there.

My background is mostly in jQuery, so my main concern is how I'm sharing and re-using variables between functions, and how I can make this code prettier and more maintainable through optimizing those functions and vars.

Any tips and how to make this more semantic, more efficient, and cleaner code, I would really appreciate!

If you want to really put some time into giving feedback, I can trade my own hours in exchange for a little education!

thanks!My background is mostly in jQuery, so my main concern is how I'm sharing and reusing variables between functions, and how I can make this code prettier and more maintainable through optimizing those functions and vars.

A little more info. The point of the app is:


update if you've already looked at this code: I just ran it through jshint.com and corrected some of the minor bracket and semicolon issues. Thanks!


UPDATE! I've made alot of positive progress and am again reposting new code. I broke up what was once one big file into 3 modules. And am loading them using LABjs. If anyone has any comments on loading efficiency / module loading, etc. I'd really appreciate that as well!

I broke up what was once one big file into 3 modules and am loading them using LABjs:

First file PRODUCT.JS

Second file PRODUCT-COLOR.JS

Third file PRODUCT-EVENTS.JS

Javascript based product customization app

http://duc.pagodabox.com/product.php

I'm developing a JS based app that allows you to customize a product. I still consider myself a super newbie to javascript and especially to object oriented programming. So far, so good, the app does what I want it to do, so no trouble there.

My background is mostly in jQuery, so my main concern is how I'm sharing and re-using variables between functions, and how I can make this code prettier and more maintainable through optimizing those functions and vars.

Any tips and how to make this more semantic, more efficient, and cleaner code, I would really appreciate!

If you want to really put some time into giving feedback, I can trade my own hours in exchange for a little education!

thanks!

A little more info. The point of the app is:


update if you've already looked at this code: I just ran it through jshint.com and corrected some of the minor bracket and semicolon issues. Thanks!


UPDATE! I've made alot of positive progress and am again reposting new code. I broke up what was once one big file into 3 modules. And am loading them using LABjs. If anyone has any comments on loading efficiency / module loading, etc. I'd really appreciate that as well!

First file PRODUCT.JS

Second file PRODUCT-COLOR.JS

Third file PRODUCT-EVENTS.JS

JavaScript-based product customization app

I'm developing a JS-based app that allows you to customize a product. I still consider myself a super newbie to javascript and especially to object oriented programming. So far, so good, the app does what I want it to do, so no trouble there.

My background is mostly in jQuery, so my main concern is how I'm sharing and reusing variables between functions, and how I can make this code prettier and more maintainable through optimizing those functions and vars.

The point of the app is:

I broke up what was once one big file into 3 modules and am loading them using LABjs:

PRODUCT.JS

PRODUCT-COLOR.JS

PRODUCT-EVENTS.JS

Notice removed Canonical answer required by Community Bot
Bounty Ended with Larry Battle's answer chosen by Community Bot
Notice added Canonical answer required by HandiworkNYC.com
Bounty Started worth 50 reputation by HandiworkNYC.com
broke up script into 3 modules; Post Made Community Wiki
Source Link

UPDATE! I've made alot of positive progress and am again reposting new code. I broke up what was once one big file into 3 modules. And am loading them using LABjs. If anyone has any comments on loading efficiency / module loading, etc. I'd really appreciate that as well!

$LAB
 .script(homeUrl + "/assets/js/product/product.js").wait()
 .script(homeUrl + "/assets/js/product/product-color.js")
 .script(homeUrl + "/assets/js/product/product-events.js")

First file PRODUCT.JS

(function (window, document, $) {
 "use strict";
 
 var productName = $('#product').attr('data-product-name'),
 // Create a new product object with the name of the currently viewed product
 product = new Product(productName),
 // This is set to true when the slide animation has completed, to avoid multiple fast clicks
 animReady = true,
 $nodes,
 productPrice,
 totalPrice,
 productOptions,
 addCost,
 inAnim = {},
 outAnim = {opacity: 0},
 outCss = {opacity: 1};
 
 // Set some general element variables used throughout this script
 var $nodes = {
 list: $('.cust-list'),
 steps: $('.steps'),
 step: $('.cust-step'),
 stepsSlide: $('.steps-slide'),
 subtotal: $('.customizer .total .price'),
 allTypes: $('.finish-type a'),
 allColors: $('.finish-color a'),
 options: $('.cust-option'),
 checks: $('.cust-option-checklist a')
 };
 
 productPrice = $nodes.subtotal.attr('data-subtotal');
 
 function Product (name) {
 this.name = name;
 this.options = [];
 }

 // Loops through the options slide divs
 function optionsLoop($options, callback) {
 for(var i = 0; i < $options.length; i++) {
 callback(i, $options[i]);
 }
 }
 
 // Loops through the array of product options within the product object
 function productOptionsLoop(productOptions, callback) {
 for(var i = 0; i < productOptions.length; i++) {
 callback(i, productOptions[i]);
 }
 }
 
 // Populate the product object with an array of options
 function getProductOptions($nodes, product) {
 optionsLoop($nodes.options, function(index,value) {
 var $me = $(value),
 name = $me.attr('data-option'),
 type = $me.attr('data-option-type'),
 option = {
 option: name,
 type: type
 };
 
 product.options.push(option);
 });
 }
 
 getProductOptions();
 productOptions = product.options;

 // Change the cost according to the added options / variations
 function updateCost(addCost, productOptions, totalPrice, $subtotal, productPrice, $nodes) {
 var currentSubtotal = $subtotal.attr('data-subtotal');
 addCost = 0;
 
 // Go through all the product options, if an additional cost has been set, add them up
 productOptionsLoop(productOptions, function(index,value){
 var $me = value,
 cost = $me.cost;
 
 if(cost) {
 addCost += cost;
 }
 });
 
 productPrice = +productPrice;
 totalPrice = productPrice + addCost;
 
 animateNumber($nodes.subtotal, currentSubtotal, totalPrice);
 
 // Update the data attribute on the subtotal to reflect the user's choices
 $nodes.subtotal.attr('data-subtotal',totalPrice);
 
 // animating number produces rounding errors, so shortly after we animate, update the text with the exact total
 setTimeout(function(){
 $nodes.subtotal.text(totalPrice).digits();
 },325);
 }
 
 function updateOptions(productOptions, myOption, myName, myCost, myType) {
 // Go through the array of options and add the selected color and cost to this option
 productOptionsLoop(productOptions, function(index,value) {
 var $this = value;
 if($this.option === myOption){
 $this.name = myName;
 $this.cost = Math.floor(myCost);
 if(myType) {
 $this.type = myType;
 }
 return false;
 }
 });
 }
 
  $.extend(window, {
 '$nodes': $nodes,
 'Product': Product,
 'optionsLoop': optionsLoop,
 'productOptionsLoop': productOptionsLoop,
 'getProductOptions': getProductOptions,
 'updateCost': updateCost,
 'updateOptions': updateOptions
  });
}(window, document, jQuery));

Second file PRODUCT-COLOR.JS

$(document).ready(function(){
 var productName = $('#product').attr('data-product-name'),
 // Create a new product object with the name of the currently viewed product
 product = new Product(productName),
 // This is set to true when the slide animation has completed, to avoid multiple fast clicks
 animReady = true,
 productPrice,
 totalPrice,
 productOptions,
 addCost,
 inAnim = {},
 outAnim = {opacity: 0},
 outCss = {opacity: 1};
 getProductOptions($nodes, product);
 productOptions = product.options;
 
 productPrice = $nodes.subtotal.attr('data-subtotal');
 
 // Color selecting
 $nodes.checks.add($nodes.allColors).add($nodes.allTypes).on('click',function(){
 if($(this).hasClass('current')) {
 return false;
 }

 var $me = $(this),
 $parent = $me.parent(),
 $granpa = $me.parents('.cust-step'),
 granpaEq = $granpa.index() - 1,
 myOption = $granpa.attr('data-option'),
 $myCheck = $('.option-list li:eq(' + granpaEq + ')'),
 
 myCost = $me.attr('data-option-cost'),
 myName = $me.attr('data-option-name'),
 myType = null,
 
 $optTypes,
 $optColors,
 $curColor,
 $curType,
 $myParentType,
 $myColors,
 
 className,
 $add,
 $remove,
 
 isCheck = $me.is('.cust-option-checklist a'),
 isColor = $me.is('.finish-color a'),
 isType = $me.is('.finish-type a');
 
 if(isCheck) {
 var $curCheck = $granpa.find('a.selected');
 
 className = 'selected';
 $add = $me;
 $remove = $curCheck;
 }
 
 if(isColor || isType) {
 if(isColor) {
 // If we're clicking a color, select the <a> links
 myType = $parent.attr('data-finish-type');
 $optColors = $granpa.find('.finish-color a');
 } else {
 // If we're clicking a color, select the divs containing each color <a>
 myType = $me.attr('data-finish-type');
 $optColors = $granpa.find('.finish-color');
 }
 
 // All types and colors for the current option
 $optTypes = $granpa.find('.finish-type a');
 
 $curColor = $optColors.filter('.current');
 $curType = $optTypes.filter('.current');

 if(isColor) {
 var myBg = $me.css('backgroundColor'),
 myBgImg = $me.css('backgroundImage'),
 bg = myBgImg;
 
 $myParentType = $optTypes.filter('[data-finish-type=' + myType + ']');
 $remove = $curColor.add($optTypes);
 $add = $me.add($myParentType);
 className = 'current ic-check selected';
 } else {
 $myColors = $optColors.filter('[data-finish-type=' + myType + ']');
 
 className = 'current';
 $remove = $curColor.add($curType);
 $add = $me.add($myColors);
 }
 }

 // Add selected class to chosen finish + type
 setCurrent($add,$remove,className);
 
 if(isColor) { 
 $curType = $optTypes.filter('.current');

 // Set the background image for parent type to reflect chosen color
 if(myBgImg === 'none') {
 bg = myBg;
 }
 
 $curType.css({'background' : bg});
 }
 
 // If you select a color or a checkbox, mark the list item as selected
 if( (isColor || isCheck) && !$myCheck.hasClass('.selected') ) {
 $myCheck.add($granpa).addClass('selected');
 }

 updateOptions(productOptions, myOption, myName, myCost, myType);
 updateCost(addCost, productOptions, totalPrice, $nodes.subtotal, productPrice, $nodes);
 
 // Remove existing price indicator
 $myCheck.find('.price').remove();
 
 // Add price indicator to checklist if there is an extra cost, or else
 if(myCost > 0) {
 $myCheck.addClass('extra').find('a').append('<span class="f9 price">$' + myCost + '</span>');
 } else {
 $myCheck.removeClass('extra');
 }
 });
 
 // Navigation
 $('.cust-btn:not(.go-back)').on('click',function(){
 var $me = $(this),
 $curStep = $nodes.step.filter('.cust-step-cur'),
 $nextStep = $curStep.next(),
 $prevStep = $curStep.prev(),
 isPrev = $me.hasClass('prev'),
 $tar = $nextStep,
 curIndex,
 offset,
 speed = 350;
 
 if(isPrev) {
 $tar = $prevStep;
 if($tar.length === 0) {
 $tar = $nodes.step.filter(':last');
 }
 } else {
 if($tar.length === 0) {
 $tar = $nodes.step.filter(':first');
 speed = 0;
 }
 }
 
 setCurrent($tar, $curStep, 'cust-step-cur');
 
 $curStep = $nodes.step.filter('.cust-step-cur');
 curIndex = $curStep.index('.cust-step');
 offset = curIndex * 160;
 
 $nodes.stepsSlide.animate({
 right: -offset
 },speed);
 });
 
 // Checklist Click
 $('.option-list a').on('click',function(){
 var $me = $(this),
 myIndex = ($me.parent().index()) + 1,
 $mySlide = $nodes.step.eq(myIndex),
 offset = myIndex * 160;

 setCurrent($mySlide, $nodes.list, 'cust-step-cur');
 
 $nodes.stepsSlide.animate({
 right: -offset
 }, 0);
 });
 
 $('.cust-btn.go-back').on('click',function(){
 var $curStep = $nodes.step.filter('.cust-step-cur');
 setCurrent($nodes.list, $curStep, 'cust-step-cur');

 $nodes.stepsSlide.animate({
 right: 0
 }, 0); 
 });
});

Third file PRODUCT-EVENTS.JS

$(document).ready(function(){
 var $productImg = $('.customizer-wrap .image'),
 productImgUrl = $productImg.find('img').attr('src'),
 spinnner,
 $stepsSlide = $nodes.stepsSlide,
 slideCount = $stepsSlide.find('.cust-step').length,
 slidesLength = slideCount * 160;
 
 $(document)$stepsSlide.ready(functioncss(spinner){width: slidesLength});
 var $productImg// =Initialize $('.customizer-wraploading .image'),graphic
 if(!productImgUrl) {
 productImgUrl =return $productImg.find('img').attr('src'),false;
 }
 spinnner,
 spinner = startSpinner('product-image', 10, 4, 15, false);
 $stepsSlide = $nodes.stepsSlide,
 // Preload the big image, when loaded, fade slideCountin
 = $stepsSlide $.findimgpreload('.cust-step').lengthproductImgUrl, function(){
 $productImg
 slidesLength = slideCount * 160; .zoom();
 
 $stepsSlide.css({width: slidesLength});
 
 // Initialize loading graphic
 if(!productImgUrl) {
 return false;
 }
 
 spinner = startSpinner('product-image', 10, 4, 15, false);
 
 // Preload the big image, when loaded, fade in
 $.imgpreload(productImgUrl, function(){
 $('.spinner').fadeOut(2000, function(){
 spinner.stop();
 $('html').removeClass('img-loading');
 });
 });
 });
}(jQuery));
(function ($) {
 "use strict";
 
 var productName = $('#product').attr('data-product-name'),
 // Create a new product object with the name of the currently viewed product
 product = new Product(productName),
 // This is set to true when the slide animation has completed, to avoid multiple fast clicks
 animReady = true,
 $nodes,
 productPrice,
 totalPrice,
 productOptions,
 addCost,
 inAnim = {},
 outAnim = {opacity: 0},
 outCss = {opacity: 1};
 
 // Set some general element variables used throughout this script
 $nodes = {
 list: $('.cust-list'),
 steps: $('.steps'),
 step: $('.cust-step'),
 stepsSlide: $('.steps-slide'),
 subtotal: $('.customizer .total .price'),
 allTypes: $('.finish-type a'),
 allColors: $('.finish-color a'),
 options: $('.cust-option'),
 checks: $('.cust-option-checklist a')
 };
 
 productPrice = $nodes.subtotal.attr('data-subtotal');
 
 function Product (name) {
 this.name = name;
 this.options = [];
 }

 // Loops through the options slide divs
 function optionsLoop($options, callback) {
 for(var i = 0; i < $options.length; i++) {
 callback(i, $options[i]);
 }
 }
 
 // Loops through the array of product options within the product object
 function productOptionsLoop(productOptions, callback) {
 for(var i = 0; i < productOptions.length; i++) {
 callback(i, productOptions[i]);
 }
 }
 
 // Populate the product object with an array of options
 function getProductOptions() {
 optionsLoop($nodes.options, function(index,value) {
 var $me = $(value),
 name = $me.attr('data-option'),
 type = $me.attr('data-option-type'),
 option = {
 option: name,
 type: type
 };
 
 product.options.push(option);
 });
 }
 
 getProductOptions();
 productOptions = product.options;

 // Change the cost according to the added options / variations
 function updateCost(addCost, productOptions, totalPrice, $subtotal, productPrice) {
 var currentSubtotal = $subtotal.attr('data-subtotal');
 addCost = 0;
 
 // Go through all the product options, if an additional cost has been set, add them up
 productOptionsLoop(productOptions, function(index,value){
 var $me = value,
 cost = $me.cost;
 
 if(cost) {
 addCost += cost;
 }
 });
 
 productPrice = +productPrice;
 totalPrice = productPrice + addCost;
 
 animateNumber($nodes.subtotal, currentSubtotal, totalPrice);
 
 // Update the data attribute on the subtotal to reflect the user's choices
 $nodes.subtotal.attr('data-subtotal',totalPrice);
 
 // animating number produces rounding errors, so shortly after we animate, update the text with the exact total
 setTimeout(function(){
 $nodes.subtotal.text(totalPrice).digits();
 },325);
 }
 
 function updateOptions(productOptions, myOption, myName, myCost, myType) {
 // Go through the array of options and add the selected color and cost to this option
 productOptionsLoop(productOptions, function(index,value) {
 var $this = value;
 if($this.option === myOption){
 $this.name = myName;
 $this.cost = Math.floor(myCost);
 if(myType) {
 $this.type = myType;
 }
 return false;
 }
 });
 }
 
 $nodes.checks.add($nodes.allColors).add($nodes.allTypes).on('click',function(){
 if($(this).hasClass('current')) {
 return false;
 }
 var $me = $(this),
 $parent = $me.parent(),
 $granpa = $me.parents('.cust-step'),
 granpaEq = $granpa.index() - 1,
 myOption = $granpa.attr('data-option'),
 $myCheck = $('.option-list li:eq(' + granpaEq + ')'),
 
 myCost = $me.attr('data-option-cost'),
 myName = $me.attr('data-option-name'),
 myType = null,
 
 $optTypes,
 $optColors,
 $curColor,
 $curType,
 $myParentType,
 $myColors,
 
 className,
 $add,
 $remove,
 
 isCheck = $me.is('.cust-option-checklist a'),
 isColor = $me.is('.finish-color a'),
 isType = $me.is('.finish-type a');
 
 if(isCheck) {
 var $curCheck = $granpa.find('a.selected');
 
 className = 'selected';
 $add = $me;
 $remove = $curCheck;
 }
 
 if(isColor || isType) {
 if(isColor) {
 myType = $parent.attr('data-finish-type');
 $optColors = $granpa.find('.finish-color a');
 } else {
 myType = $me.attr('data-finish-type');
 $optColors = $granpa.find('.finish-color');
 }
 
 // All types and colors for the current option
 $optTypes = $granpa.find('.finish-type a');
 
 $curColor = $optColors.filter('.current');
 $curType = $optTypes.filter('.current');
 if(isColor) {
 var myBg = $me.css('backgroundColor'),
 myBgImg = $me.css('backgroundImage'),
 bg = myBgImg;
 
 $myParentType = $optTypes.filter('[data-finish-type=' + myType + ']');
 $remove = $curColor.add($optTypes);
 $add = $me.add($myParentType);
 className = 'current ic-check selected';
 } else {
 $myColors = $optColors.filter('[data-finish-type=' + myType + ']');
 
 className = 'current';
 $remove = $curColor.add($curType);
 $add = $me.add($myColors);
 }
 }
 // Add selected class to chosen finish + type
 setCurrent($add,$remove,className);
 
 if(isColor) { 
 $curType = $optTypes.filter('.current');
 // Set the background image for parent type to reflect chosen color
 if(myBgImg === 'none') {
 bg = myBg;
 }
 
 $curType.css({'background' : bg});
 }
 
 // If you select a color or a checkbox, mark the list item as selected
 if( (isColor || isCheck) && !$myCheck.hasClass('.selected') ) {
 $myCheck.add($granpa).addClass('selected');
 }
 updateOptions(productOptions, myOption, myName, myCost, myType);
 updateCost(addCost, productOptions, totalPrice, $nodes.subtotal, productPrice);
 
 // Remove existing price indicator
 $myCheck.find('.price').remove();
 
 // Add price indicator to checklist if there is an extra cost, or else
 if(myCost > 0) {
 $myCheck.addClass('extra').find('a').append('<span class="f9 price">$' + myCost + '</span>');
 } else {
 $myCheck.removeClass('extra');
 }
 });
 
 // Navigation
 $('.cust-btn:not(.go-back)').on('click',function(){
 var $me = $(this),
 $curStep = $nodes.step.filter('.cust-step-cur'),
 $nextStep = $curStep.next(),
 $prevStep = $curStep.prev(),
 isPrev = $me.hasClass('prev'),
 $tar = $nextStep,
 curIndex,
 offset,
 speed = 350;
 
 if(isPrev) {
 $tar = $prevStep;
 if($tar.length === 0) {
 $tar = $nodes.step.filter(':last');
 }
 } else {
 if($tar.length === 0) {
 $tar = $nodes.step.filter(':first');
 speed = 0;
 }
 }
 
 setCurrent($tar, $curStep, 'cust-step-cur');
 
 $curStep = $nodes.step.filter('.cust-step-cur');
 curIndex = $curStep.index('.cust-step');
 offset = curIndex * 160;
 
 $nodes.stepsSlide.animate({
 right: -offset
 },speed);
 });
 
 // Checklist Click
 $('.option-list a').on('click',function(){
 var $me = $(this),
 myIndex = ($me.parent().index()) + 1,
 $mySlide = $nodes.step.eq(myIndex),
 offset = myIndex * 160;
 setCurrent($mySlide, $nodes.list, 'cust-step-cur');
 
 $nodes.stepsSlide.animate({
 right: -offset
 }, 0);
 });
 
 $('.cust-btn.go-back').on('click',function(){
 var $curStep = $nodes.step.filter('.cust-step-cur');
 setCurrent($nodes.list, $curStep, 'cust-step-cur');
 $nodes.stepsSlide.animate({
 right: 0
 }, 0); 
 });
 
 $(document).ready(function(spinner){ var $productImg = $('.customizer-wrap .image'),
 productImgUrl = $productImg.find('img').attr('src'),
 spinnner,
 $stepsSlide = $nodes.stepsSlide,
 slideCount = $stepsSlide.find('.cust-step').length,
 slidesLength = slideCount * 160;
 
 $stepsSlide.css({width: slidesLength});
 
 // Initialize loading graphic
 if(!productImgUrl) {
 return false;
 }
 
 spinner = startSpinner('product-image', 10, 4, 15, false);
 
 // Preload the big image, when loaded, fade in
 $.imgpreload(productImgUrl, function(){
 $('.spinner').fadeOut(2000, function(){
 spinner.stop();
 $('html').removeClass('img-loading');
 });
 });
 });
}(jQuery));

UPDATE! I've made alot of positive progress and am again reposting new code. I broke up what was once one big file into 3 modules. And am loading them using LABjs. If anyone has any comments on loading efficiency / module loading, etc. I'd really appreciate that as well!

$LAB
 .script(homeUrl + "/assets/js/product/product.js").wait()
 .script(homeUrl + "/assets/js/product/product-color.js")
 .script(homeUrl + "/assets/js/product/product-events.js")

First file PRODUCT.JS

(function (window, document, $) {
 "use strict";
 // Set some general element variables used throughout this script
 var $nodes = {
 list: $('.cust-list'),
 steps: $('.steps'),
 step: $('.cust-step'),
 stepsSlide: $('.steps-slide'),
 subtotal: $('.customizer .total .price'),
 allTypes: $('.finish-type a'),
 allColors: $('.finish-color a'),
 options: $('.cust-option'),
 checks: $('.cust-option-checklist a')
 };
 
 function Product (name) {
 this.name = name;
 this.options = [];
 }
 // Loops through the options slide divs
 function optionsLoop($options, callback) {
 for(var i = 0; i < $options.length; i++) {
 callback(i, $options[i]);
 }
 }
 
 // Loops through the array of product options within the product object
 function productOptionsLoop(productOptions, callback) {
 for(var i = 0; i < productOptions.length; i++) {
 callback(i, productOptions[i]);
 }
 }
 
 // Populate the product object with an array of options
 function getProductOptions($nodes, product) {
 optionsLoop($nodes.options, function(index,value) {
 var $me = $(value),
 name = $me.attr('data-option'),
 type = $me.attr('data-option-type'),
 option = {
 option: name,
 type: type
 };
 
 product.options.push(option);
 });
 }
 // Change the cost according to the added options / variations
 function updateCost(addCost, productOptions, totalPrice, $subtotal, productPrice, $nodes) {
 var currentSubtotal = $subtotal.attr('data-subtotal');
 addCost = 0;
 
 // Go through all the product options, if an additional cost has been set, add them up
 productOptionsLoop(productOptions, function(index,value){
 var $me = value,
 cost = $me.cost;
 
 if(cost) {
 addCost += cost;
 }
 });
 
 productPrice = +productPrice;
 totalPrice = productPrice + addCost;
 
 animateNumber($nodes.subtotal, currentSubtotal, totalPrice);
 
 // Update the data attribute on the subtotal to reflect the user's choices
 $nodes.subtotal.attr('data-subtotal',totalPrice);
 
 // animating number produces rounding errors, so shortly after we animate, update the text with the exact total
 setTimeout(function(){
 $nodes.subtotal.text(totalPrice).digits();
 },325);
 }
 
 function updateOptions(productOptions, myOption, myName, myCost, myType) {
 // Go through the array of options and add the selected color and cost to this option
 productOptionsLoop(productOptions, function(index,value) {
 var $this = value;
 if($this.option === myOption){
 $this.name = myName;
 $this.cost = Math.floor(myCost);
 if(myType) {
 $this.type = myType;
 }
 return false;
 }
 });
 }
 
  $.extend(window, {
 '$nodes': $nodes,
 'Product': Product,
 'optionsLoop': optionsLoop,
 'productOptionsLoop': productOptionsLoop,
 'getProductOptions': getProductOptions,
 'updateCost': updateCost,
 'updateOptions': updateOptions
  });
}(window, document, jQuery));

Second file PRODUCT-COLOR.JS

$(document).ready(function(){
 var productName = $('#product').attr('data-product-name'),
 // Create a new product object with the name of the currently viewed product
 product = new Product(productName),
 // This is set to true when the slide animation has completed, to avoid multiple fast clicks
 animReady = true,
 productPrice,
 totalPrice,
 productOptions,
 addCost,
 inAnim = {},
 outAnim = {opacity: 0},
 outCss = {opacity: 1};
 getProductOptions($nodes, product);
 productOptions = product.options;
 
 productPrice = $nodes.subtotal.attr('data-subtotal');
 
 // Color selecting
 $nodes.checks.add($nodes.allColors).add($nodes.allTypes).on('click',function(){
 if($(this).hasClass('current')) {
 return false;
 }

 var $me = $(this),
 $parent = $me.parent(),
 $granpa = $me.parents('.cust-step'),
 granpaEq = $granpa.index() - 1,
 myOption = $granpa.attr('data-option'),
 $myCheck = $('.option-list li:eq(' + granpaEq + ')'),
 
 myCost = $me.attr('data-option-cost'),
 myName = $me.attr('data-option-name'),
 myType = null,
 
 $optTypes,
 $optColors,
 $curColor,
 $curType,
 $myParentType,
 $myColors,
 
 className,
 $add,
 $remove,
 
 isCheck = $me.is('.cust-option-checklist a'),
 isColor = $me.is('.finish-color a'),
 isType = $me.is('.finish-type a');
 
 if(isCheck) {
 var $curCheck = $granpa.find('a.selected');
 
 className = 'selected';
 $add = $me;
 $remove = $curCheck;
 }
 
 if(isColor || isType) {
 if(isColor) {
 // If we're clicking a color, select the <a> links
 myType = $parent.attr('data-finish-type');
 $optColors = $granpa.find('.finish-color a');
 } else {
 // If we're clicking a color, select the divs containing each color <a>
 myType = $me.attr('data-finish-type');
 $optColors = $granpa.find('.finish-color');
 }
 
 // All types and colors for the current option
 $optTypes = $granpa.find('.finish-type a');
 
 $curColor = $optColors.filter('.current');
 $curType = $optTypes.filter('.current');

 if(isColor) {
 var myBg = $me.css('backgroundColor'),
 myBgImg = $me.css('backgroundImage'),
 bg = myBgImg;
 
 $myParentType = $optTypes.filter('[data-finish-type=' + myType + ']');
 $remove = $curColor.add($optTypes);
 $add = $me.add($myParentType);
 className = 'current ic-check selected';
 } else {
 $myColors = $optColors.filter('[data-finish-type=' + myType + ']');
 
 className = 'current';
 $remove = $curColor.add($curType);
 $add = $me.add($myColors);
 }
 }

 // Add selected class to chosen finish + type
 setCurrent($add,$remove,className);
 
 if(isColor) { 
 $curType = $optTypes.filter('.current');

 // Set the background image for parent type to reflect chosen color
 if(myBgImg === 'none') {
 bg = myBg;
 }
 
 $curType.css({'background' : bg});
 }
 
 // If you select a color or a checkbox, mark the list item as selected
 if( (isColor || isCheck) && !$myCheck.hasClass('.selected') ) {
 $myCheck.add($granpa).addClass('selected');
 }

 updateOptions(productOptions, myOption, myName, myCost, myType);
 updateCost(addCost, productOptions, totalPrice, $nodes.subtotal, productPrice, $nodes);
 
 // Remove existing price indicator
 $myCheck.find('.price').remove();
 
 // Add price indicator to checklist if there is an extra cost, or else
 if(myCost > 0) {
 $myCheck.addClass('extra').find('a').append('<span class="f9 price">$' + myCost + '</span>');
 } else {
 $myCheck.removeClass('extra');
 }
 });
 
 // Navigation
 $('.cust-btn:not(.go-back)').on('click',function(){
 var $me = $(this),
 $curStep = $nodes.step.filter('.cust-step-cur'),
 $nextStep = $curStep.next(),
 $prevStep = $curStep.prev(),
 isPrev = $me.hasClass('prev'),
 $tar = $nextStep,
 curIndex,
 offset,
 speed = 350;
 
 if(isPrev) {
 $tar = $prevStep;
 if($tar.length === 0) {
 $tar = $nodes.step.filter(':last');
 }
 } else {
 if($tar.length === 0) {
 $tar = $nodes.step.filter(':first');
 speed = 0;
 }
 }
 
 setCurrent($tar, $curStep, 'cust-step-cur');
 
 $curStep = $nodes.step.filter('.cust-step-cur');
 curIndex = $curStep.index('.cust-step');
 offset = curIndex * 160;
 
 $nodes.stepsSlide.animate({
 right: -offset
 },speed);
 });
 
 // Checklist Click
 $('.option-list a').on('click',function(){
 var $me = $(this),
 myIndex = ($me.parent().index()) + 1,
 $mySlide = $nodes.step.eq(myIndex),
 offset = myIndex * 160;

 setCurrent($mySlide, $nodes.list, 'cust-step-cur');
 
 $nodes.stepsSlide.animate({
 right: -offset
 }, 0);
 });
 
 $('.cust-btn.go-back').on('click',function(){
 var $curStep = $nodes.step.filter('.cust-step-cur');
 setCurrent($nodes.list, $curStep, 'cust-step-cur');

 $nodes.stepsSlide.animate({
 right: 0
 }, 0); 
 });
});

Third file PRODUCT-EVENTS.JS

$(document).ready(function(){
 var $productImg = $('.customizer-wrap .image'),
 productImgUrl = $productImg.find('img').attr('src'),
 spinnner,
 $stepsSlide = $nodes.stepsSlide,
 slideCount = $stepsSlide.find('.cust-step').length,
 slidesLength = slideCount * 160;
 
 $stepsSlide.css({width: slidesLength});
 // Initialize loading graphic
 if(!productImgUrl) {
 return false;
 }
 
 spinner = startSpinner('product-image', 10, 4, 15, false);
 
 // Preload the big image, when loaded, fade in
  $.imgpreload(productImgUrl, function(){
 $productImg
  .zoom();
 
 $('.spinner').fadeOut(2000, function(){
 spinner.stop();
 $('html').removeClass('img-loading');
 });
 });
});
deleted 842 characters in body
Source Link
(function ($) {
 "use strict";
 
 var productName = $('#product').attr('data-product-name'),
 // Create a new product object with the name of the currently viewed product
 product = new Product(productName),
 // This is set to true when the slide animation has completed, to avoid multiple fast clicks
 animReady = true,
 $nodes,
 productPrice,
 totalPrice,
 productOptions,
 addCost;addCost,
 inAnim = {},
 outAnim = {opacity: 0},
 outCss = {opacity: 1};
 
 // Set some general element variables used throughout this script
 $nodes = {
 list: $('.cust-list'),
 steps: $('.steps'),
 step: $('.cust-step'),
 stepsSlide: $('.steps-slide'),
 subtotal: $('.customizer .total .price'),
 allTypes: $('.finish-type a'),
 allColors: $('.finish-color a'),
 options: $('.cust-option'),
 checks: $('.cust-option-checklist a')
 };
 
 productPrice = $nodes.subtotal.attr('data-subtotal');
 
 function Product (name) {
 this.name = name;
 this.options = [];
 }
 
 
 // Loops through the options slide divs
 function optionsLoop($options, callback) {
 for(var i = 0; i < $options.length; i++) {
 callback(i, $options[i]);
 }
 }
 
 // Loops through the array of product options within the product object
 function productOptionsLoop(productOptions, callback) {
 for(var i = 0; i < productOptions.length; i++) {
 callback(i, productOptions[i]);
 }
 }
 
 // Populate the product object with an array of options
 function getProductOptions() {
 optionsLoop($nodes.options, function(index,value) {
 var $me = $(value),
 name = $me.attr('data-option'),
 type = $me.attr('data-option-type'),
 option = {
 option: name,
 type: type
 };
 
 product.options.push(option);
 });
 }
 
 getProductOptions();
 productOptions = product.options;
 // Change the cost according to the added options / variations
 function updateCost(addCost, productOptions, totalPrice, $subtotal, productPrice) {
 var currentSubtotal = $subtotal.attr('data-subtotal');
 addCost = 0;
 
 // Go through all the product options, if an additional cost has been set, add them up
 productOptionsLoop(productOptions, function(index,value){
 var $me = value,
 cost = $me.cost;
 
 if(cost) {
 addCost += cost;
 }
 });
 
 productPrice = +productPrice;
 totalPrice = productPrice + addCost;
 
 animateNumber($nodes.subtotal, currentSubtotal, totalPrice);
 
 // Update the data attribute on the subtotal to reflect the user's choices
 $nodes.subtotal.attr('data-subtotal',totalPrice);
 
 // animating number produces rounding errors, so shortly after we animate, update the text with the exact total
 setTimeout(function(){
 $nodes.subtotal.text(totalPrice).digits();
 },325);
 }
 
 function updateOptions(productOptions, myOptionNamemyOption, myName, myCost, myType) {
 // Go through the array of options and add the selected color and cost to this option
 productOptionsLoop(productOptions, function(index,value) {
 var $this = value;
 if($this.option === myOptionNamemyOption){
 $this.name = myName;
 $this.cost = Math.floor(myCost);
 if(myType) {
 $this.type = myType;
 }
 return false;
 }
 });
 }
 
 // Animate current step
 function newStepIn($el, $list, $btnCheck) {
 $el.animate({
 left: 0
 }, 500, function(){
 $(this).addClass('cust-step-cur');
 
 // Check if the current step is the checklist, if so, disabled the checklist button
 if($list) { 
 if($list.hasClass('cust-step-cur')) {
 $btnCheck.addClass('disabled');
 } else {
 $btnCheck.removeClass('disabled');
 }
 }
 
 animReady = true;
 });
 }
 
 function currentStepOut(myVars) {
 myVars.curStep.animate({
 left: '-160px',
 opacity: 0
 }, 250, function(){
 $(this).css({left: '160px', opacity: 1});
 });
 
 myVars.curStep.removeClass('cust-step-cur');
 }
 
 // Finish type click
 $nodes.allTypes.on('click',function(){
 if($(this).hasClass('current')) {
 return false;
 }
 
 var $me = $(this),
 $granpa = $me.parents('.cust-step'),
 
 myType = $me.attr('data-finish-type'),
 
 // All colors and types for the current option
 $optColors = $granpa.find('.finish-color'),
 $optTypes = $granpa.find('.finish-type a'),
 
 // All colors for the current type
 $myColors = $optColors.filter('[data-finish-type=' + myType + ']'),
 
 $curColor = $optColors.filter('.current'),
 $curType = $optTypes.filter('.current'),
 
 $add,
 $remove;
 
 if($me.hasClass('current')) {
 return false;
 }
 
 $remove = $curColorchecks.add($curType$nodes.allColors);
 $add = $me.add($myColors);
 
 setCurrent($add,$remove,'current');
 });
 
 // Finish color click
 $nodes.allColorsallTypes).on('click',function(){
 if($(this).hasClass('current')) {
 return false;
 }

 var $me = $(this),
 $parent = $me.parent(),
 $granpa = $parent$me.parents('.cust-step'),
 granpaEq = $granpa.index() - 1,
 // My check refers to 'MymyOption Checklist',= i.e$granpa. the menu item that triggered this particular option screenattr('data-option'),
 $myCheck = $('.option-list li:eq(' + granpaEq + ')'),
 
 // All types and colors for the current option
 $optTypes = $granpa.find('.finish-type a'),
 $optColors = $granpa.find('.finish-color a'),
 
 myOptionName = $granpa.attr('data-option'),
 
 myType = $parent.attr('data-finish-type'),
 myColor = $me.attr('data-option-name'),
 myCost = $me.attr('data-option-cost'),
 
 myBg = $me.css('backgroundColor'),
 myBgImgmyName = $me.cssattr('backgroundImage''data-option-name'),
 bgmyType = myBgImgnull,
 
 $optTypes,
 $optColors,
 $curColor,
 $curType,
 $myParentType,
 = $optTypes.filter('[data-finish-type=' + myType + ']') $myColors,
 
 $curColor = $optColors.filter('.current')className,
 $curType$add,
 = $optTypes.filter('.current') $remove,
 
 $removeisCheck = $curColor$me.addis($optTypes'.cust-option-checklist a'),
 $addisColor = $me.addis($myParentType);
 
 // Add selected class to chosen '.finish + type
 setCurrent($add,$remove,'current ic-checkcolor selected'a');,
 $curTypeisType = $optTypes$me.filteris('.current'finish-type a');
 
 //if(isCheck) Set{
 the background image for parent type to reflect chosen color var $curCheck = $granpa.find('a.selected');
 if(myBgImg === 'none') {
 bgclassName = myBg;'selected';
 $add = $me;
 $remove = $curCheck;
 }
 
 $curType.css({'background' : bg});

 $myCheck.addClass('selected');
 
 updateOptionsif(productOptions,isColor myOptionName,|| myColor,isType) myCost,{
 if(isColor) {
  myType = $parent.attr('data-finish-type');
 updateCost $optColors = $granpa.find(addCost,'.finish-color productOptions,a');
 totalPrice } else {
 myType = $me.attr('data-finish-type');
 $optColors = $granpa.find('.finish-color');
 }
 
 // All types and colors for the current option
 $optTypes = $granpa.find('.finish-type a');
 
 $curColor = $optColors.filter('.current');
 $curType = $optTypes.filter('.current');
 if(isColor) {
 var myBg = $me.css('backgroundColor'), $nodes myBgImg = $me.subtotalcss('backgroundImage'), productPrice bg = myBgImg;
 
 $myParentType = $optTypes.filter('[data-finish-type=' + myType + ']');
 $remove = $curColor.add($optTypes);
 $add = $me.add($myParentType);
 className = 'current ic-check selected';
 } else {
 $myColors = $optColors.filter('[data-finish-type=' + myType + ']');
 
 // Option checklist click
 $nodes.checks.on('click',function(){
 className = 'current';
 var $me = $(this),
 $granpa$remove = $me$curColor.parentsadd('.cust-step'$curType),;
 granpaEq $add = $granpa$me.indexadd($myColors) - 1,;
 myOptionName = $granpa.attr('data-option'),}
 }
 $myCheck = $('.option-list li:eq(' + granpaEq// +Add ')'),
selected class to chosen finish + type
 $curCheck = $granpa.findsetCurrent('a.selected')$add,$remove,className);
 
 if(isColor) { myCost $curType = $me$optTypes.attrfilter('data-option-cost''.current'),;

 myName// =Set $me.attrthe background image for parent type to reflect chosen color
 if('data-option-name'myBgImg === 'none'), {
 myType bg = null;myBg;
 }
 
 $curType.css({'background' : bg});
 }
 
 setCurrent($me,$curCheck// If you select a color or a checkbox, mark the list item as selected
 if( (isColor || isCheck) && !$myCheck.hasClass('.selected') ) {
 $myCheck.add($granpa).addClass('selected');
 }
 updateOptions(productOptions, myOptionNamemyOption, myName, myCost, myType);
 updateCost(addCost, productOptions, totalPrice, $nodes.subtotal, productPrice);
 
 if($myCheck.hasClass('.selected')) {return false;}
 // Remove existing price indicator
 $myCheck.addClassfind('selected''.price').remove();
 
 console// Add price indicator to checklist if there is an extra cost, or else
 if(myCost > 0) {
 $myCheck.logaddClass(productOptions'extra').find('a').append('<span class="f9 price">$' + myCost + '</span>');
 } else {
 $myCheck.removeClass('extra');
 }
 });

 function navVars($me) {
 var $curStep = $('.cust-step-cur'),
 $nextStep = $curStep.next('.cust-step'),
 nextStepLen = $nextStep.length,
 $list = $('.cust-list'),
 $btnCheck = $('.cust-btn.checklist'),
 hasChecklist = $me.hasClass('checklist');
 
 
 return {
 curStep: $curStep,
 nextStep: $nextStep,
 nextStepLen: nextStepLen,
 list: $list,
 btnCheck: $btnCheck,
 hasChecklist: hasChecklist
 };
 }
 
 // Navigation
 $('.cust-btn'btn:not(.go-back)').on('click',function(){
 if(animReady === false) {
 return false;
 }
 
 var $me = $(this),
 myVars$curStep = navVars$nodes.step.filter('.cust-step-cur'),
 $nextStep = $curStep.next(),
 $prevStep = $curStep.prev(),
 isPrev = $me.hasClass('prev');,
 $tar = $nextStep,
 curIndex,
 offset,
 speed = 350;
 
 //if(isPrev) Disable{
 the checklist button if we're already on the checklist slide $tar = $prevStep;
 if(myVars$tar.hasChecklistlength &&=== myVars.list.hasClass('cust-step-cur')0) {
 return false; $tar = $nodes.step.filter(':last');
 }
 } else {
 currentStepOut if(myVars$tar.length === 0); {
 $tar = $nodes.step.filter(':first');
 if(myVars.nextStepLen === 0 || $me.hasClass('checklist')) {
 speed = 0;
 myVars.nextStep = myVars.list; }
 }
 
 animReadysetCurrent($tar, =$curStep, false;'cust-step-cur');
 
 newStepIn$curStep = $nodes.step.filter(myVars'.nextStep,cust-step-cur');
 myVars.list, myVars curIndex = $curStep.btnCheckindex('.cust-step');
 offset = curIndex * 160;
 
 $nodes.stepsSlide.animate({
 right: -offset
 },speed);
 });
 
 // Checklist Click
 $('.option-list a').on('click',function(){
 var $me = $(this),
 myVarsmyIndex = navVars($me),
 $myPar = $me.parent(),
 myEq = $myPar.index()) + 1,
 $myStep$mySlide = $('$nodes.cust-step')step.eq(myEqmyIndex);,
 offset = myIndex * 160;

 currentStepOutsetCurrent(myVars$mySlide, $nodes.list, 'cust-step-cur');
 
 newStepIn$nodes.stepsSlide.animate($myStep,{
 myVars.list right: -offset
 }, myVars.btnCheck0);
 });
 
 $('.cust-btn.go-back').on('click',function(){
 var $curStep = $nodes.step.filter('.cust-step-cur');
 setCurrent($nodes.list, $curStep, 'cust-step-cur');
 $nodes.stepsSlide.animate({
 right: 0
 }, 0); 
 });
 
 $(document).ready(function(spinner){
 var $productImg = $('.customizer-wrap .image'),
 productImgUrl = $productImg.find('img').attr('src'),
 spinnner;spinnner,
 $stepsSlide = $nodes.stepsSlide,
 slideCount = $stepsSlide.find('.cust-step').length,
 slidesLength = slideCount * 160;
  
 $stepsSlide.css({width: slidesLength});
 
 // Initialize loading graphic
 if(!productImgUrl) {
 return false;
 }
 
 spinner = startSpinner('product-image', 10, 4, 15, false);
 
 // Preload the big zoom image, when loaded, fade in and fire the zoom function
 $.imgpreload(productImgUrl, function(){
 $('.spinner').fadeOut(2000, function(){
 spinner.stop();
 $('html').removeClass('img-loading');
 $productImg.zoom({url: productImgUrl});
 });
 });
 });
}(jQuery));
(function ($) {
 "use strict";
 
 var productName = $('#product').attr('data-product-name'),
 // Create a new product object with the name of the currently viewed product
 product = new Product(productName),
 // This is set to true when the slide animation has completed, to avoid multiple fast clicks
 animReady = true,
 $nodes,
 productPrice,
 totalPrice,
 productOptions,
 addCost;
 
 // Set some general element variables used throughout this script
 $nodes = {
 subtotal: $('.customizer .total .price'),
 allTypes: $('.finish-type a'),
 allColors: $('.finish-color a'),
 options: $('.cust-option'),
 checks: $('.cust-option-checklist a')
 };
 
 productPrice = $nodes.subtotal.attr('data-subtotal');
 
 function Product (name) {
 this.name = name;
 this.options = [];
 }
 
 
 // Loops through the options slide divs
 function optionsLoop($options, callback) {
 for(var i = 0; i < $options.length; i++) {
 callback(i, $options[i]);
 }
 }
 
 // Loops through the array of product options within the product object
 function productOptionsLoop(productOptions, callback) {
 for(var i = 0; i < productOptions.length; i++) {
 callback(i, productOptions[i]);
 }
 }
 
 // Populate the product object with an array of options
 function getProductOptions() {
 optionsLoop($nodes.options, function(index,value) {
 var $me = $(value),
 name = $me.attr('data-option'),
 type = $me.attr('data-option-type'),
 option = {
 option: name,
 type: type
 };
 
 product.options.push(option);
 });
 }
 
 getProductOptions();
 productOptions = product.options;
 // Change the cost according to the added options / variations
 function updateCost(addCost, productOptions, totalPrice, $subtotal, productPrice) {
 var currentSubtotal = $subtotal.attr('data-subtotal');
 addCost = 0;
 
 // Go through all the product options, if an additional cost has been set, add them up
 productOptionsLoop(productOptions, function(index,value){
 var $me = value,
 cost = $me.cost;
 
 if(cost) {
 addCost += cost;
 }
 });
 
 productPrice = +productPrice;
 totalPrice = productPrice + addCost;
 
 animateNumber($nodes.subtotal, currentSubtotal, totalPrice);
 
 // Update the data attribute on the subtotal to reflect the user's choices
 $nodes.subtotal.attr('data-subtotal',totalPrice);
 
 // animating number produces rounding errors, so shortly after we animate, update the text with the exact total
 setTimeout(function(){
 $nodes.subtotal.text(totalPrice).digits();
 },325);
 }
 
 function updateOptions(productOptions, myOptionName, myName, myCost, myType) {
 // Go through the array of options and add the selected color and cost to this option
 productOptionsLoop(productOptions, function(index,value) {
 var $this = value;
 if($this.option === myOptionName){
 $this.name = myName;
 $this.cost = Math.floor(myCost);
 if(myType) {
 $this.type = myType;
 }
 return false;
 }
 });
 }
 
 // Animate current step
 function newStepIn($el, $list, $btnCheck) {
 $el.animate({
 left: 0
 }, 500, function(){
 $(this).addClass('cust-step-cur');
 
 // Check if the current step is the checklist, if so, disabled the checklist button
 if($list) { 
 if($list.hasClass('cust-step-cur')) {
 $btnCheck.addClass('disabled');
 } else {
 $btnCheck.removeClass('disabled');
 }
 }
 
 animReady = true;
 });
 }
 
 function currentStepOut(myVars) {
 myVars.curStep.animate({
 left: '-160px',
 opacity: 0
 }, 250, function(){
 $(this).css({left: '160px', opacity: 1});
 });
 
 myVars.curStep.removeClass('cust-step-cur');
 }
 
 // Finish type click
 $nodes.allTypes.on('click',function(){
 if($(this).hasClass('current')) {
 return false;
 }
 
 var $me = $(this),
 $granpa = $me.parents('.cust-step'),
 
 myType = $me.attr('data-finish-type'),
 
 // All colors and types for the current option
 $optColors = $granpa.find('.finish-color'),
 $optTypes = $granpa.find('.finish-type a'),
 
 // All colors for the current type
 $myColors = $optColors.filter('[data-finish-type=' + myType + ']'),
 
 $curColor = $optColors.filter('.current'),
 $curType = $optTypes.filter('.current'),
 
 $add,
 $remove;
 
 if($me.hasClass('current')) {
 return false;
 }
 
 $remove = $curColor.add($curType);
 $add = $me.add($myColors);
 
 setCurrent($add,$remove,'current');
 });
 
 // Finish color click
 $nodes.allColors.on('click',function(){
 if($(this).hasClass('current')) {
 return false;
 }

 var $me = $(this),
 $parent = $me.parent(),
 $granpa = $parent.parents('.cust-step'),
 granpaEq = $granpa.index() - 1,
 // My check refers to 'My Checklist', i.e. the menu item that triggered this particular option screen
 $myCheck = $('.option-list li:eq(' + granpaEq + ')'),
 
 // All types and colors for the current option
 $optTypes = $granpa.find('.finish-type a'),
 $optColors = $granpa.find('.finish-color a'),
 
 myOptionName = $granpa.attr('data-option'),
 
 myType = $parent.attr('data-finish-type'),
 myColor = $me.attr('data-option-name'),
 myCost = $me.attr('data-option-cost'),
 
 myBg = $me.css('backgroundColor'),
 myBgImg = $me.css('backgroundImage'),
 bg = myBgImg,
 
 $myParentType = $optTypes.filter('[data-finish-type=' + myType + ']'),
 
 $curColor = $optColors.filter('.current'),
 $curType = $optTypes.filter('.current'),
 
 $remove = $curColor.add($optTypes),
 $add = $me.add($myParentType);
 
 // Add selected class to chosen finish + type
 setCurrent($add,$remove,'current ic-check selected');
 $curType = $optTypes.filter('.current');
 
 // Set the background image for parent type to reflect chosen color
 if(myBgImg === 'none') {
 bg = myBg;
 }
 
 $curType.css({'background' : bg});

 $myCheck.addClass('selected');
 
 updateOptions(productOptions, myOptionName, myColor, myCost, myType);
 updateCost(addCost, productOptions, totalPrice, $nodes.subtotal, productPrice);
 });
 
 // Option checklist click
 $nodes.checks.on('click',function(){
 var $me = $(this),
 $granpa = $me.parents('.cust-step'),
 granpaEq = $granpa.index() - 1,
 myOptionName = $granpa.attr('data-option'),
 $myCheck = $('.option-list li:eq(' + granpaEq + ')'),
 $curCheck = $granpa.find('a.selected'),
 
 myCost = $me.attr('data-option-cost'),
 myName = $me.attr('data-option-name'),
 myType = null;
 
 setCurrent($me,$curCheck,'selected');
 updateOptions(productOptions, myOptionName, myName, myCost, myType);
 updateCost(addCost, productOptions, totalPrice, $nodes.subtotal, productPrice);
 
 if($myCheck.hasClass('.selected')) {return false;}
  
 $myCheck.addClass('selected');
 
 console.log(productOptions);
 });

 function navVars($me) {
 var $curStep = $('.cust-step-cur'),
 $nextStep = $curStep.next('.cust-step'),
 nextStepLen = $nextStep.length,
 $list = $('.cust-list'),
 $btnCheck = $('.cust-btn.checklist'),
 hasChecklist = $me.hasClass('checklist');
 
 
 return {
 curStep: $curStep,
 nextStep: $nextStep,
 nextStepLen: nextStepLen,
 list: $list,
 btnCheck: $btnCheck,
 hasChecklist: hasChecklist
 };
 }
 
 // Navigation
 $('.cust-btn').on('click',function(){
 if(animReady === false) {
 return false;
 }
 
 var $me = $(this),
 myVars = navVars($me);
 
 // Disable the checklist button if we're already on the checklist slide
 if(myVars.hasChecklist && myVars.list.hasClass('cust-step-cur')) {
 return false;
 }
 currentStepOut(myVars);
 
 if(myVars.nextStepLen === 0 || $me.hasClass('checklist')) {
 myVars.nextStep = myVars.list;
 }
 
 animReady = false;
 
 newStepIn(myVars.nextStep, myVars.list, myVars.btnCheck);
 
 });
 
 // Checklist Click
 $('.option-list a').on('click',function(){
 var $me = $(this),
 myVars = navVars($me),
 $myPar = $me.parent(),
 myEq = $myPar.index() + 1,
 $myStep = $('.cust-step').eq(myEq); 
 currentStepOut(myVars);
 
 newStepIn($myStep, myVars.list, myVars.btnCheck);
 });
 
 $(document).ready(function(spinner){
 var $productImg = $('.customizer-wrap .image'),
 productImgUrl = $productImg.find('img').attr('src'),
 spinnner;
 
 // Initialize loading graphic
 if(!productImgUrl) {
 return false;
 }
 
 spinner = startSpinner('product-image', 10, 4, 15, false);
 
 // Preload the big zoom image, when loaded, fade in and fire the zoom function
 $.imgpreload(productImgUrl, function(){
 $('.spinner').fadeOut(2000, function(){
 spinner.stop();
 $('html').removeClass('img-loading');
 $productImg.zoom({url: productImgUrl});
 });
 });
 });
}(jQuery));
(function ($) {
 "use strict";
 
 var productName = $('#product').attr('data-product-name'),
 // Create a new product object with the name of the currently viewed product
 product = new Product(productName),
 // This is set to true when the slide animation has completed, to avoid multiple fast clicks
 animReady = true,
 $nodes,
 productPrice,
 totalPrice,
 productOptions,
 addCost,
 inAnim = {},
 outAnim = {opacity: 0},
 outCss = {opacity: 1};
 
 // Set some general element variables used throughout this script
 $nodes = {
 list: $('.cust-list'),
 steps: $('.steps'),
 step: $('.cust-step'),
 stepsSlide: $('.steps-slide'),
 subtotal: $('.customizer .total .price'),
 allTypes: $('.finish-type a'),
 allColors: $('.finish-color a'),
 options: $('.cust-option'),
 checks: $('.cust-option-checklist a')
 };
 
 productPrice = $nodes.subtotal.attr('data-subtotal');
 
 function Product (name) {
 this.name = name;
 this.options = [];
 }
 
 
 // Loops through the options slide divs
 function optionsLoop($options, callback) {
 for(var i = 0; i < $options.length; i++) {
 callback(i, $options[i]);
 }
 }
 
 // Loops through the array of product options within the product object
 function productOptionsLoop(productOptions, callback) {
 for(var i = 0; i < productOptions.length; i++) {
 callback(i, productOptions[i]);
 }
 }
 
 // Populate the product object with an array of options
 function getProductOptions() {
 optionsLoop($nodes.options, function(index,value) {
 var $me = $(value),
 name = $me.attr('data-option'),
 type = $me.attr('data-option-type'),
 option = {
 option: name,
 type: type
 };
 
 product.options.push(option);
 });
 }
 
 getProductOptions();
 productOptions = product.options;
 // Change the cost according to the added options / variations
 function updateCost(addCost, productOptions, totalPrice, $subtotal, productPrice) {
 var currentSubtotal = $subtotal.attr('data-subtotal');
 addCost = 0;
 
 // Go through all the product options, if an additional cost has been set, add them up
 productOptionsLoop(productOptions, function(index,value){
 var $me = value,
 cost = $me.cost;
 
 if(cost) {
 addCost += cost;
 }
 });
 
 productPrice = +productPrice;
 totalPrice = productPrice + addCost;
 
 animateNumber($nodes.subtotal, currentSubtotal, totalPrice);
 
 // Update the data attribute on the subtotal to reflect the user's choices
 $nodes.subtotal.attr('data-subtotal',totalPrice);
 
 // animating number produces rounding errors, so shortly after we animate, update the text with the exact total
 setTimeout(function(){
 $nodes.subtotal.text(totalPrice).digits();
 },325);
 }
 
 function updateOptions(productOptions, myOption, myName, myCost, myType) {
 // Go through the array of options and add the selected color and cost to this option
 productOptionsLoop(productOptions, function(index,value) {
 var $this = value;
 if($this.option === myOption){
 $this.name = myName;
 $this.cost = Math.floor(myCost);
 if(myType) {
 $this.type = myType;
 }
 return false;
 }
 });
 }
 
 $nodes.checks.add($nodes.allColors).add($nodes.allTypes).on('click',function(){
 if($(this).hasClass('current')) {
 return false;
 }
 var $me = $(this),
 $parent = $me.parent(),
 $granpa = $me.parents('.cust-step'),
 granpaEq = $granpa.index() - 1,
 myOption = $granpa.attr('data-option'),
 $myCheck = $('.option-list li:eq(' + granpaEq + ')'),
 
 myCost = $me.attr('data-option-cost'),
 myName = $me.attr('data-option-name'),
 myType = null,
 
 $optTypes,
 $optColors,
 $curColor,
 $curType,
 $myParentType,
  $myColors,
 
 className,
 $add,
  $remove,
 
 isCheck = $me.is('.cust-option-checklist a'),
 isColor = $me.is('.finish-color a'),
 isType = $me.is('.finish-type a');
 
 if(isCheck) {
  var $curCheck = $granpa.find('a.selected');
 
 className = 'selected';
 $add = $me;
 $remove = $curCheck;
 }
 
 
 if(isColor || isType) {
 if(isColor) {
  myType = $parent.attr('data-finish-type');
  $optColors = $granpa.find('.finish-color a');
  } else {
 myType = $me.attr('data-finish-type');
 $optColors = $granpa.find('.finish-color');
 }
 
 // All types and colors for the current option
 $optTypes = $granpa.find('.finish-type a');
 
 $curColor = $optColors.filter('.current');
 $curType = $optTypes.filter('.current');
 if(isColor) {
 var myBg = $me.css('backgroundColor'),  myBgImg = $me.css('backgroundImage'),  bg = myBgImg;
 
 $myParentType = $optTypes.filter('[data-finish-type=' + myType + ']');
 $remove = $curColor.add($optTypes);
 $add = $me.add($myParentType);
 className = 'current ic-check selected';
 } else {
 $myColors = $optColors.filter('[data-finish-type=' + myType + ']');
 
 className = 'current';
 $remove = $curColor.add($curType);
 $add = $me.add($myColors);
 }
 }
 // Add selected class to chosen finish + type
 setCurrent($add,$remove,className);
 
 if(isColor) { $curType = $optTypes.filter('.current');

 // Set the background image for parent type to reflect chosen color
 if(myBgImg === 'none') {
  bg = myBg;
 }
 
 $curType.css({'background' : bg});
 }
 
 // If you select a color or a checkbox, mark the list item as selected
 if( (isColor || isCheck) && !$myCheck.hasClass('.selected') ) {
 $myCheck.add($granpa).addClass('selected');
 }
 updateOptions(productOptions, myOption, myName, myCost, myType);
 updateCost(addCost, productOptions, totalPrice, $nodes.subtotal, productPrice);
 
 // Remove existing price indicator
 $myCheck.find('.price').remove();
 
 // Add price indicator to checklist if there is an extra cost, or else
 if(myCost > 0) {
 $myCheck.addClass('extra').find('a').append('<span class="f9 price">$' + myCost + '</span>');
 } else {
 $myCheck.removeClass('extra');
 }
 });
 
 // Navigation
 $('.cust-btn:not(.go-back)').on('click',function(){
 var $me = $(this),
 $curStep = $nodes.step.filter('.cust-step-cur'),
 $nextStep = $curStep.next(),
 $prevStep = $curStep.prev(),
 isPrev = $me.hasClass('prev'),
 $tar = $nextStep,
 curIndex,
 offset,
 speed = 350;
 
 if(isPrev) {
  $tar = $prevStep;
 if($tar.length === 0) {
  $tar = $nodes.step.filter(':last');
 }
 } else {
  if($tar.length === 0) {
 $tar = $nodes.step.filter(':first');
 speed = 0;
  }
 }
 
 setCurrent($tar, $curStep, 'cust-step-cur');
 
 $curStep = $nodes.step.filter('.cust-step-cur');
  curIndex = $curStep.index('.cust-step');
 offset = curIndex * 160;
 
 $nodes.stepsSlide.animate({
 right: -offset
 },speed);
 });
 
 // Checklist Click
 $('.option-list a').on('click',function(){
 var $me = $(this),
 myIndex = ($me.parent().index()) + 1,
 $mySlide = $nodes.step.eq(myIndex),
 offset = myIndex * 160;

 setCurrent($mySlide, $nodes.list, 'cust-step-cur');
 
 $nodes.stepsSlide.animate({
  right: -offset
 }, 0);
 });
 
 $('.cust-btn.go-back').on('click',function(){
 var $curStep = $nodes.step.filter('.cust-step-cur');
 setCurrent($nodes.list, $curStep, 'cust-step-cur');
 $nodes.stepsSlide.animate({
 right: 0
 }, 0); 
 });
 
 $(document).ready(function(spinner){
 var $productImg = $('.customizer-wrap .image'),
 productImgUrl = $productImg.find('img').attr('src'),
 spinnner,
 $stepsSlide = $nodes.stepsSlide,
 slideCount = $stepsSlide.find('.cust-step').length,
 slidesLength = slideCount * 160;
  
 $stepsSlide.css({width: slidesLength});
 
 // Initialize loading graphic
 if(!productImgUrl) {
 return false;
 }
 
 spinner = startSpinner('product-image', 10, 4, 15, false);
 
 // Preload the big image, when loaded, fade in
 $.imgpreload(productImgUrl, function(){
 $('.spinner').fadeOut(2000, function(){
 spinner.stop();
 $('html').removeClass('img-loading');
 });
 });
 });
}(jQuery));
added 1228 characters in body
Source Link
Loading
added 16 characters in body
Source Link
Loading
added 116 characters in body
Source Link
Loading
added 343 characters in body
Source Link
Loading
deleted 37 characters in body
Source Link
Loading
added 261 characters in body
Source Link
Loading
added 54 characters in body
Source Link
Loading
deleted 37 characters in body
Source Link
Loading
Tweeted twitter.com/#!/StackCodeReview/status/239829828621172737
added 273 characters in body
Source Link
Loading
Source Link
Loading
default

AltStyle によって変換されたページ (->オリジナル) /