handling per-item shipping dropdowns in js
This commit is contained in:
parent
ec2d6a85e5
commit
e26723f526
2 changed files with 469 additions and 237 deletions
|
|
@ -1,157 +1,16 @@
|
|||
/*global _, window, document, YAHOO */
|
||||
|
||||
(function () {
|
||||
var $event = YAHOO.util.Event,
|
||||
$connect = YAHOO.util.Connect,
|
||||
$json = YAHOO.lang.JSON,
|
||||
prices = null,
|
||||
addressCache = {},
|
||||
elements = {
|
||||
shipper : 'shipperId_formId',
|
||||
tax : 'taxWrap',
|
||||
total : 'totalPriceWrap',
|
||||
credit : {
|
||||
available : 'inShopCreditAvailableWrap',
|
||||
used : 'inShopCreditDeductionWrap'
|
||||
},
|
||||
dropdowns : {
|
||||
billing : 'billingAddressId_formId',
|
||||
shipping: 'shippingAddressId_formId'
|
||||
}
|
||||
},
|
||||
addressParts = [
|
||||
'label', 'firstName', 'lastName', 'organization', 'address1',
|
||||
'address2', 'address3', 'city', 'state', 'code', 'country',
|
||||
'phoneNumber', 'email'
|
||||
];
|
||||
function clone(o) {
|
||||
function F() {}
|
||||
F.prototype = o;
|
||||
return new F();
|
||||
}
|
||||
|
||||
function formatCurrency(n) {
|
||||
return parseFloat(n.toString()).toFixed(2);
|
||||
}
|
||||
|
||||
function addAddressKind(name) {
|
||||
var obj = elements[name] = {};
|
||||
_.each(addressParts, function (key) {
|
||||
obj[key] = name + '_' + key + '_formId';
|
||||
});
|
||||
}
|
||||
|
||||
function getDomElements(o) {
|
||||
_.each(o, function (v, k) {
|
||||
if (typeof v === 'object') {
|
||||
getDomElements(v);
|
||||
}
|
||||
else {
|
||||
o[k] = document.getElementById(v);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function sameChange() {
|
||||
var d = elements.same.checked;
|
||||
_.each(elements.shipping, function (v, k) {
|
||||
v.disabled = d;
|
||||
});
|
||||
elements.dropdowns.shipping.disabled = d;
|
||||
}
|
||||
|
||||
function calculateSummary() {
|
||||
var shipping = prices.shipping[elements.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(elements.credit.available.innerHTML),
|
||||
creditUsed = Math.min(beforeCredit, creditAvail),
|
||||
afterCredit = beforeCredit - creditUsed;
|
||||
|
||||
elements.credit.used.innerHTML = formatCurrency(creditUsed);
|
||||
elements.total.innerHTML = formatCurrency(afterCredit);
|
||||
}
|
||||
|
||||
function updatePrices() {
|
||||
var selectedShipper = elements.shipper.value,
|
||||
shipping = elements.dropdowns.shipping.value,
|
||||
billing = elements.dropdowns.billing.value,
|
||||
shipper = elements.shipper,
|
||||
url = window.location.pathname +
|
||||
'?shop=cart;method=ajaxPrices;' +
|
||||
( shipping === 'new_address' ?
|
||||
'' : 'shippingId=' + shipping) +
|
||||
( billing === 'new_address' ?
|
||||
'' : 'billingId=' + billing);
|
||||
cb = {
|
||||
success: function (o) {
|
||||
var response = $json.parse(o.responseText);
|
||||
if (response.error) {
|
||||
return;
|
||||
}
|
||||
prices = response;
|
||||
elements.tax.innerHTML = formatCurrency(response.tax);
|
||||
_(shipper.options)
|
||||
.chain()
|
||||
.map(_.identity)
|
||||
.each(function (o) {
|
||||
if (o.value) {
|
||||
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 = selectedShipper;
|
||||
calculateSummary();
|
||||
}
|
||||
};
|
||||
$connect.asyncRequest('GET', url, cb);
|
||||
}
|
||||
|
||||
function updateAddressDropdowns(o) {
|
||||
var label = o.argument.address.label,
|
||||
id = o.responseText;
|
||||
|
||||
function updateOne(dropdown) {
|
||||
var opt = _.detect(dropdown.options, function (o) {
|
||||
return o.text === label;
|
||||
});
|
||||
|
||||
if (!opt) {
|
||||
opt = document.createElement('option');
|
||||
opt.text = label;
|
||||
dropdown.appendChild(opt);
|
||||
}
|
||||
opt.value = id;
|
||||
}
|
||||
|
||||
updateOne(elements.dropdowns.billing);
|
||||
updateOne(elements.dropdowns.shipping);
|
||||
elements.dropdowns[o.argument.name].value = id;
|
||||
updatePrices();
|
||||
}
|
||||
|
||||
function saveAddress(a, name) {
|
||||
var cb = {
|
||||
success: updateAddressDropdowns,
|
||||
argument: { address: a, name: name }
|
||||
},
|
||||
post = 'shop=address;method=ajaxSave;address=' +
|
||||
$json.stringify(a),
|
||||
url = window.location.pathname;
|
||||
|
||||
$connect.asyncRequest('POST', url, cb, post);
|
||||
}
|
||||
|
||||
function validAddress(a) {
|
||||
return a.label &&
|
||||
a.firstName &&
|
||||
|
|
@ -163,108 +22,462 @@
|
|||
a.country;
|
||||
}
|
||||
|
||||
function addressChange(name) {
|
||||
var other = name === 'billing' ? 'shipping' : 'billing';
|
||||
return function () {
|
||||
var address = {},
|
||||
els = elements[name],
|
||||
label = els.label.value,
|
||||
oels = elements[other],
|
||||
copy = oels && oels.label.value === label,
|
||||
cached = addressCache[label],
|
||||
dirty;
|
||||
|
||||
if (!cached) {
|
||||
cached = addressCache[label] = {};
|
||||
}
|
||||
|
||||
_.each(elements[name], function (v, k) {
|
||||
v = v.value;
|
||||
address[k] = v;
|
||||
if (cached[k] !== v) {
|
||||
dirty = true;
|
||||
cached[k] = v;
|
||||
}
|
||||
if (copy) {
|
||||
oels[k].value = v;
|
||||
}
|
||||
});
|
||||
if (dirty && validAddress(address)) {
|
||||
saveAddress(address, name);
|
||||
}
|
||||
};
|
||||
function addressIdCounts(id) {
|
||||
return id &&
|
||||
id !== 'new_address' &&
|
||||
id !== 'update_address';
|
||||
}
|
||||
|
||||
function addressUpdater(name) {
|
||||
var elems = elements[name];
|
||||
function update(address) {
|
||||
_.each(address, function (v, k) {
|
||||
var dom = elems[k];
|
||||
if (dom) {
|
||||
dom.value = v;
|
||||
function fillIn(dom, a) {
|
||||
_.each(a, function (v, k) {
|
||||
if (dom[k]) {
|
||||
dom[k].value = v;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var Cart = {
|
||||
attachAddressBlurHandlers: function (name) {
|
||||
var fields = _.values(this.elements[name]),
|
||||
handler = this.createAddressBlurHandler(name);
|
||||
this.event.on(fields, 'focusout', handler);
|
||||
},
|
||||
|
||||
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,
|
||||
shipping = prices.shipping[e.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);
|
||||
}
|
||||
});
|
||||
updatePrices();
|
||||
}
|
||||
return function () {
|
||||
var id = this.value,
|
||||
label = this.options[this.selectedIndex].text,
|
||||
cached = addressCache[label],
|
||||
url, cb;
|
||||
},
|
||||
|
||||
if (cached) {
|
||||
return update(cached);
|
||||
}
|
||||
connect: YAHOO.util.Connect,
|
||||
|
||||
url = window.location.pathname +
|
||||
'?shop=address;method=ajaxGetAddress;addressId=' +
|
||||
id;
|
||||
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();
|
||||
});
|
||||
},
|
||||
|
||||
cb = {
|
||||
success: function (o) {
|
||||
var address = $json.parse(o.responseText);
|
||||
addressCache[address.label] = address;
|
||||
update(address);
|
||||
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 && 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);
|
||||
}
|
||||
};
|
||||
$connect.asyncRequest('GET', url, cb);
|
||||
},
|
||||
|
||||
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.forms[0],
|
||||
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;
|
||||
|
||||
function updateOne(dropdown) {
|
||||
var opt = _.detect(dropdown.options, function (o) {
|
||||
return o.text === label;
|
||||
});
|
||||
|
||||
if (!opt) {
|
||||
opt = document.createElement('option');
|
||||
opt.text = label;
|
||||
dropdown.appendChild(opt);
|
||||
}
|
||||
|
||||
opt.value = id;
|
||||
}
|
||||
|
||||
updateOne(d.billing);
|
||||
updateOne(d.shipping);
|
||||
d[name].value = id;
|
||||
if (name === 'billing' && self.sameShipping()) {
|
||||
self.copyBilling();
|
||||
}
|
||||
else {
|
||||
self.computePerItemShippingOptions();
|
||||
}
|
||||
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,
|
||||
selected = shipper.value,
|
||||
d = e.dropdowns,
|
||||
shipping = d.shipping.value,
|
||||
billing = d.billing.value,
|
||||
params = {
|
||||
shop: 'cart',
|
||||
method: 'ajaxPrices'
|
||||
};
|
||||
|
||||
if (addressIdCounts(shipping)) {
|
||||
params.shippingId = shipping;
|
||||
}
|
||||
|
||||
if (addressIdCounts(billing)) {
|
||||
params.billingId = billing;
|
||||
}
|
||||
|
||||
this.request('GET', params, function (o) {
|
||||
var response = self.json.parse(o.responseText);
|
||||
|
||||
if (response.error) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.prices = response;
|
||||
tax.innerHTML = formatCurrency(response.tax);
|
||||
|
||||
_(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.calculateSummary();
|
||||
});
|
||||
},
|
||||
|
||||
// This is a very thin layer on top of YAHOO.util.Connect.asyncRequest.
|
||||
request: function (method, params, success) {
|
||||
var url = this.baseUrl,
|
||||
cb = { success: success },
|
||||
query = _(params).map(function (v, k) {
|
||||
return [k, v].join('=');
|
||||
}).join('&');
|
||||
|
||||
if (method === 'GET') {
|
||||
this.connect.asyncRequest(method, url + '?' + query, cb);
|
||||
}
|
||||
else {
|
||||
this.connect.asyncRequest(method, url, cb, query);
|
||||
}
|
||||
},
|
||||
|
||||
sameShipping: function () {
|
||||
return this.elements.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';
|
||||
});
|
||||
}
|
||||
|
||||
function handleBlur(name) {
|
||||
$event.on(_.values(elements[name]), 'focusout', addressChange(name));
|
||||
}
|
||||
addAddressKind('billing');
|
||||
addAddressKind('shipping');
|
||||
|
||||
function handleDropdown(name) {
|
||||
$event.on(elements.dropdowns[name], 'change', addressUpdater(name));
|
||||
}
|
||||
|
||||
function main() {
|
||||
var checks;
|
||||
addAddressKind('billing');
|
||||
addAddressKind('shipping');
|
||||
getDomElements(elements);
|
||||
|
||||
elements.form = document.forms[0];
|
||||
|
||||
handleBlur('billing');
|
||||
handleDropdown('billing');
|
||||
|
||||
checks = elements.form.sameShippingAsBilling;
|
||||
if (checks) {
|
||||
elements.same = checks[0];
|
||||
$event.on(checks, 'change', sameChange);
|
||||
sameChange();
|
||||
handleBlur('shipping');
|
||||
handleDropdown('shipping');
|
||||
}
|
||||
else {
|
||||
delete elements.shipping;
|
||||
}
|
||||
|
||||
$event.on(elements.shipper, 'change', calculateSummary);
|
||||
updatePrices();
|
||||
}
|
||||
|
||||
$event.onDOMReady(main);
|
||||
Cart.event.onDOMReady(function () {
|
||||
Cart.create({
|
||||
baseUrl : window.location.pathname,
|
||||
elements : elements
|
||||
});
|
||||
});
|
||||
}());
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue