509 lines
17 KiB
JavaScript
509 lines
17 KiB
JavaScript
/*global _, window, document, YAHOO */
|
|
|
|
(function () {
|
|
function clone(o) {
|
|
function F() {}
|
|
F.prototype = o;
|
|
return new F();
|
|
}
|
|
|
|
function formatCurrency(n) {
|
|
return parseFloat(n.toString()).toFixed(2);
|
|
}
|
|
|
|
function validAddress(a) {
|
|
return a.label &&
|
|
a.firstName &&
|
|
a.lastName &&
|
|
a.address1 &&
|
|
a.city &&
|
|
a.state &&
|
|
a.code &&
|
|
a.country;
|
|
}
|
|
|
|
function addressIdCounts(id) {
|
|
return id &&
|
|
id !== 'new_address' &&
|
|
id !== 'update_address';
|
|
}
|
|
|
|
function fillIn(dom, a) {
|
|
_.each(a, function (v, k) {
|
|
if (dom[k]) {
|
|
dom[k].value = v || '';
|
|
}
|
|
});
|
|
}
|
|
|
|
var Cart = {
|
|
attachAddressBlurHandlers: function (name) {
|
|
var els = this.elements[name],
|
|
label = els.label,
|
|
addr = els.address1,
|
|
fields = _.values(els),
|
|
handler = this.createAddressBlurHandler(name);
|
|
this.event.on(fields, 'focusout', handler);
|
|
this.event.on(addr, 'focusout', function () {
|
|
if (!label.value) {
|
|
label.value = addr.value;
|
|
label.blur();
|
|
}
|
|
});
|
|
},
|
|
|
|
attachAddressSelectHandler: function (name) {
|
|
var e = this.elements.dropdowns[name],
|
|
handler = this.createAddressFiller(name);
|
|
|
|
this.event.on(e, 'change', handler);
|
|
},
|
|
|
|
attachPerItemShippingChangeHandler: function (select) {
|
|
this.event.on(select, 'change',
|
|
_.bind(this.setCartItemShippingId, this, select));
|
|
},
|
|
|
|
// Updates the total fields based on information already contained in
|
|
// this.prices
|
|
calculateSummary: function () {
|
|
var e = this.elements,
|
|
prices = this.prices,
|
|
shipper = e.shipper,
|
|
shipping = shipper && prices.shipping[shipper.value],
|
|
shipPrice = (shipping ?
|
|
(shipping.hasPrice ?
|
|
parseFloat(shipping.price) :
|
|
0)
|
|
: 0),
|
|
tax = parseFloat(prices.tax),
|
|
subtotal = parseFloat(prices.subtotal),
|
|
beforeCredit = tax + subtotal + shipPrice,
|
|
creditAvail = parseFloat(e.credit.available.innerHTML),
|
|
creditUsed = Math.min(beforeCredit, creditAvail),
|
|
afterCredit = beforeCredit - creditUsed;
|
|
|
|
e.credit.used.innerHTML = formatCurrency(creditUsed);
|
|
e.total.innerHTML = formatCurrency(afterCredit);
|
|
},
|
|
|
|
computePerItemShippingOptions: function () {
|
|
var self = this,
|
|
shipping = this.elements.dropdowns.shipping,
|
|
selectedMain = shipping.value,
|
|
validOptions = _.select(shipping.options, function (o) {
|
|
var v = o.value;
|
|
return addressIdCounts(v) &&
|
|
v !== selectedMain;
|
|
});
|
|
|
|
_.each(this.getPerItemShippingDropdowns(), function (d) {
|
|
var selected = d.value;
|
|
|
|
_(d.options).chain().filter(function (o) {
|
|
return o.value;
|
|
}).each(_.bind(d.removeChild, d));
|
|
|
|
_.each(validOptions, function (o) {
|
|
d.appendChild(o.cloneNode(true));
|
|
});
|
|
|
|
// The idea here is to reselect the option that was selected,
|
|
// if it's still valid. If not, we have to tell the backend
|
|
// as well.
|
|
d.value = selected;
|
|
if (d.value !== selected) {
|
|
d.value = '';
|
|
self.setCartItemShippingId(d);
|
|
}
|
|
});
|
|
},
|
|
|
|
connect: YAHOO.util.Connect,
|
|
|
|
copyBilling: function () {
|
|
var self = this,
|
|
e = this.elements,
|
|
d = e.dropdowns;
|
|
d.shipping.value = d.billing.value;
|
|
this.getSelectAddress(d.billing, function (address) {
|
|
fillIn(e.shipping, address);
|
|
self.computePerItemShippingOptions();
|
|
self.updateSummary();
|
|
});
|
|
},
|
|
|
|
create: function (args) {
|
|
var self = clone(this);
|
|
self.init(args);
|
|
},
|
|
|
|
createAddressBlurHandler: function (name) {
|
|
var self = this,
|
|
other = name === 'billing' ? 'shipping' : 'billing',
|
|
e = this.elements[name],
|
|
o = this.elements[other],
|
|
c = this.addressCache;
|
|
|
|
return function () {
|
|
var address = {},
|
|
label = e.label.value,
|
|
copy = o.label && o.label.value === label,
|
|
cache = c[label],
|
|
dirty;
|
|
|
|
if (!cache) {
|
|
cache = c[label] = {};
|
|
}
|
|
|
|
_.each(e, function (v, k) {
|
|
v = v.value || '';
|
|
address[k] = v;
|
|
if (cache[k] !== v) {
|
|
dirty = true;
|
|
cache[k] = v;
|
|
}
|
|
if (copy) {
|
|
o[k].value = v;
|
|
}
|
|
});
|
|
|
|
if (dirty && validAddress(address)) {
|
|
self.saveAddress(address, name);
|
|
}
|
|
};
|
|
},
|
|
|
|
createAddressFiller: function (name) {
|
|
var self = this,
|
|
e = this.elements[name],
|
|
select = this.elements.dropdowns[name];
|
|
|
|
return _.bind(this.getSelectAddress, this, select, function (a) {
|
|
fillIn(e, a);
|
|
if (name === 'billing' && self.sameShipping()) {
|
|
self.copyBilling();
|
|
}
|
|
self.updateSummary();
|
|
});
|
|
},
|
|
|
|
dom: YAHOO.util.Dom,
|
|
|
|
formatAddress: function (a) {
|
|
var et = _.template('<a href="mailto:<%= email %>"><%= email %>'),
|
|
csz = _.template('<%= city %>, <%= state %> <%= code %>');
|
|
|
|
return _.compact([
|
|
' ',
|
|
[a.firstName, a.middleName, a.lastName].join(' '),
|
|
a.address1, a.address2, a.address3,
|
|
csz(a),
|
|
a.country,
|
|
a.phone,
|
|
a.email && et(a)
|
|
]).join('<br />');
|
|
},
|
|
|
|
getPerItemShippingDropdowns: function () {
|
|
return this.dom.getElementsByClassName('itemAddressMenu', 'select');
|
|
},
|
|
|
|
getSelectAddress: function (select, callback) {
|
|
var self = this,
|
|
id = select.value,
|
|
label = select.options[select.selectedIndex].text,
|
|
c = this.addressCache,
|
|
cache = c[label];
|
|
|
|
if (cache) {
|
|
callback(cache);
|
|
}
|
|
else {
|
|
this.request('GET', {
|
|
shop : 'address',
|
|
method : 'ajaxGetAddress',
|
|
addressId : id
|
|
},
|
|
function (o) {
|
|
var address = self.json.parse(o.responseText);
|
|
c[address.label] = address;
|
|
callback(address);
|
|
});
|
|
}
|
|
},
|
|
|
|
event: YAHOO.util.Event,
|
|
|
|
init: function (args) {
|
|
// this.elements is our cache of dom objects. We're passed in an
|
|
// object with ids, and we want to replace those ids with actual
|
|
// dom references.
|
|
function getElements(o) {
|
|
_.each(o, function (v, k) {
|
|
if (typeof v === 'object') {
|
|
getElements(v);
|
|
}
|
|
else {
|
|
o[k] = document.getElementById(v);
|
|
}
|
|
});
|
|
}
|
|
|
|
var self = this,
|
|
e = args.elements,
|
|
f = document.getElementById('wgCartId'),
|
|
checks = f.sameShippingAsBilling,
|
|
sameChange;
|
|
|
|
getElements(e);
|
|
this.elements = e;
|
|
|
|
this.prices = null;
|
|
this.addressCache = {};
|
|
this.baseUrl = args.baseUrl;
|
|
this.attachAddressBlurHandlers('billing');
|
|
this.attachAddressSelectHandler('billing');
|
|
|
|
// if checks is false, we don't have the shipping address form on
|
|
// this page (because none of the items in the cart require
|
|
// shipping)
|
|
if (checks) {
|
|
e.same = checks[0];
|
|
sameChange = _.bind(this.useSameShippingAddressChange, this);
|
|
this.event.on(checks, 'change', sameChange);
|
|
sameChange();
|
|
this.attachAddressBlurHandlers('shipping');
|
|
this.attachAddressSelectHandler('shipping');
|
|
_.each(
|
|
this.getPerItemShippingDropdowns(),
|
|
_.bind(self.attachPerItemShippingChangeHandler, self)
|
|
);
|
|
this.event.on(e.dropdowns.shipping, 'change', function () {
|
|
self.computePerItemShippingOptions();
|
|
});
|
|
self.computePerItemShippingOptions();
|
|
}
|
|
else {
|
|
delete e.shipping;
|
|
}
|
|
|
|
this.event.on(e.shipper, 'change',
|
|
this.calculateSummary, null, this);
|
|
|
|
this.updateSummary();
|
|
},
|
|
|
|
json: YAHOO.lang.JSON,
|
|
|
|
saveAddress: function (address, name) {
|
|
var self = this,
|
|
label = address.label;
|
|
|
|
this.request('POST', {
|
|
shop : 'address',
|
|
method : 'ajaxSave',
|
|
address : this.json.stringify(address)
|
|
},
|
|
function (o) {
|
|
var id = o.responseText,
|
|
d = self.elements.dropdowns;
|
|
|
|
if (!id.match(/^[A-Za-z0-9_-]{22}$/)) {
|
|
alert('Error: bad response trying to save address.');
|
|
return;
|
|
}
|
|
|
|
function updateOne(dropdown) {
|
|
var opt = _.detect(dropdown.options, function (o) {
|
|
return o.text === label;
|
|
});
|
|
|
|
if (!opt) {
|
|
opt = document.createElement('option');
|
|
opt.innerHTML = label;
|
|
dropdown.appendChild(opt);
|
|
}
|
|
|
|
opt.value = id;
|
|
}
|
|
|
|
updateOne(d.billing);
|
|
if (d.shipping) {
|
|
updateOne(d.shipping);
|
|
if (name === 'billing' && self.sameShipping()) {
|
|
self.copyBilling();
|
|
}
|
|
else {
|
|
self.computePerItemShippingOptions();
|
|
}
|
|
}
|
|
d[name].value = id;
|
|
self.updateSummary();
|
|
});
|
|
},
|
|
|
|
// Like calling calculateSummary, except that it will first fetch
|
|
// price information from the server. This should be called when the
|
|
// address information has changed, and at least once on page load (so
|
|
// we have an initial this.prices to work with)
|
|
updateSummary: function () {
|
|
var self = this,
|
|
e = this.elements,
|
|
tax = e.tax,
|
|
shipper = e.shipper,
|
|
d = e.dropdowns,
|
|
billing = d.billing.value,
|
|
selected, shipping,
|
|
params = {
|
|
shop: 'cart',
|
|
method: 'ajaxPrices'
|
|
};
|
|
|
|
if (addressIdCounts(billing)) {
|
|
params.billingId = billing;
|
|
}
|
|
|
|
if (shipper) {
|
|
selected = shipper.value; // save so we can restore later
|
|
shipping = d.shipping.value;
|
|
if (this.sameShipping()) {
|
|
params.shippingId = params.billingId;
|
|
} else if (addressIdCounts(shipping)) {
|
|
params.shippingId = shipping;
|
|
}
|
|
}
|
|
|
|
this.request('GET', params, function (o) {
|
|
var response = self.json.parse(o.responseText);
|
|
|
|
if (response.error) {
|
|
return;
|
|
}
|
|
if (shipper) {
|
|
// We need to repopulate the shipper options dropdown
|
|
// (but only if we have a shipper at all)
|
|
_(shipper.options).chain().select(function (o) {
|
|
return o.value;
|
|
}).each(function (o) {
|
|
o.parentNode.removeChild(o);
|
|
});
|
|
|
|
_.each(response.shipping, function (o, id) {
|
|
var opt = document.createElement('option'),
|
|
label = o.label;
|
|
|
|
if (o.hasPrice) {
|
|
label += ' (' + formatCurrency(o.price) + ')';
|
|
}
|
|
|
|
opt.innerHTML = label;
|
|
opt.value = id;
|
|
shipper.appendChild(opt);
|
|
});
|
|
|
|
shipper.value = selected;
|
|
}
|
|
self.prices = response;
|
|
tax.innerHTML = formatCurrency(response.tax);
|
|
self.calculateSummary();
|
|
});
|
|
},
|
|
|
|
// This is a very thin layer on top of YAHOO.util.Connect.asyncRequest.
|
|
request: function (method, params, success) {
|
|
var url = encodeURI(this.baseUrl),
|
|
cb = { success: success },
|
|
query = _(params).map(function (v, k) {
|
|
return [k, escape(v)].join('=');
|
|
}).join('&');
|
|
|
|
if (method === 'GET') {
|
|
this.connect.asyncRequest(method, url + '?' + query, cb);
|
|
}
|
|
else {
|
|
this.connect.asyncRequest(method, url, cb, query);
|
|
}
|
|
},
|
|
|
|
sameShipping: function () {
|
|
var same = this.elements.same;
|
|
return same && same.checked;
|
|
},
|
|
|
|
setCartItemShippingId: function (select) {
|
|
var self = this, parent = select.parentNode;
|
|
|
|
function setText(t) {
|
|
parent.innerHTML = t;
|
|
parent.insertBefore(select, parent.firstChild);
|
|
}
|
|
|
|
this.request('POST', {
|
|
shop : 'cart',
|
|
method : 'ajaxSetCartItemShippingId',
|
|
itemId : select.id.match(/itemAddress_(.*)_formId/)[1],
|
|
addressId : select.value
|
|
}, function () {
|
|
self.updateSummary();
|
|
if (select.value) {
|
|
self.getSelectAddress(select, function (address) {
|
|
setText(self.formatAddress(address));
|
|
});
|
|
}
|
|
else {
|
|
setText('');
|
|
}
|
|
});
|
|
},
|
|
|
|
useSameShippingAddressChange: function () {
|
|
var e = this.elements,
|
|
disable = this.sameShipping(),
|
|
drops = e.dropdowns;
|
|
|
|
_.each(e.shipping, function (v, k) {
|
|
v.disabled = disable;
|
|
});
|
|
drops.shipping.disabled = disable;
|
|
if (disable && addressIdCounts(drops.billing.value)) {
|
|
this.copyBilling();
|
|
}
|
|
}
|
|
},
|
|
addressParts = [
|
|
'label', 'firstName', 'lastName', 'organization',
|
|
'address1', 'address2', 'address3', 'city', 'state',
|
|
'code', 'country', 'phoneNumber', 'email'
|
|
],
|
|
elements = {
|
|
shipper : 'shipperId_formId',
|
|
tax : 'taxWrap',
|
|
total : 'totalPriceWrap',
|
|
credit : {
|
|
available : 'inShopCreditAvailableWrap',
|
|
used : 'inShopCreditDeductionWrap'
|
|
},
|
|
dropdowns : {
|
|
billing : 'billingAddressId_formId',
|
|
shipping: 'shippingAddressId_formId'
|
|
}
|
|
};
|
|
|
|
function addAddressKind(name) {
|
|
var obj = elements[name] = {};
|
|
_.each(addressParts, function (key) {
|
|
obj[key] = name + '_' + key + '_formId';
|
|
});
|
|
}
|
|
|
|
addAddressKind('billing');
|
|
addAddressKind('shipping');
|
|
|
|
Cart.event.onDOMReady(function () {
|
|
Cart.create({
|
|
baseUrl : window.location.pathname,
|
|
elements : elements
|
|
});
|
|
});
|
|
}());
|