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
- 483
- 1
- 6
- 16
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');
});
});
});
(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));