UPDATE
The examples might not all be updated according to the newest Tea Commerce APIE-Commerce in umbraco
Our focus, for an Umbraco E-commerce , has been towards the frontend developer . Existing E-commerce solutions, also for Umbraco, is based on the old post back approach where the page reloads every time, something happens. Frontend developers and the designers has been more or less locked to certain ways of doing things. Tea Commerce for Umbraco turns everything upsides down and are based on the front end. Via Tea Commerce’s JavaScript API information is sent and retrieved between the server and the client with ajax. This approach provides some opportunities to make interactive user interfaces for an Umbraco E-commerce, which usually takes a long time to write.The Tea Commerce demo webshop for Umbraco
In this blog post I will show how easy it is to create simple effects with jQuery and Tea Commerce . All examples will be from our demo e-commerce shop , which is made with a clean Umbraco 4.5.2 installation and Tea Commerce . I’ve packed the full Umbraco web shop with database, files and everything. The Demo webshop to Umbraco can be downloaded here.Contents of this blog post
- Universal Events
- Add product to the Tea Commerce cart
- Remove order lines from the Tea Commerce cart
- Change currency
- Delivery and Payment
- Extra order information
- Delivery countries
- Payment providers
Other Tea Commerce articles
- A complete reference of all the Tea Commerce JavaScript API public methods.
- General information about the Tea Commerce JavaScript API
- Review of the Tea Commerce xslt extensions
- What to do when you have installed Tea Commerce
- Tea Commerce on our.umbraco.org where you can download everything
Downloads
- All xslt files used in the Tea Commerce demo webshop
- All JavaScripts from the Tea Commerce demo webshop
1. Universal Events
The most basic of Tea Commerce is the event model on the client side. With this you are always guaranteed the opportunity to update the user interface when the user changes the cart’s content. You can sign up on three different events which the Tea Commerce JavaScript API fires, when necessary.OnCartUpdating
Before the JavaScript API requests a change of the cart to the server, the onCartUpdating event is fired. The idea is to show the user that something is happening. In the example below, I add some classes on my “carts”, which changes the UI to show the user that the cart is being updated.A timer (submitTimer) and a counter (update counter) must keep track on when the first of a series of server calls are started and how many have been made. They will later be used to remove the added classes after a desired period. More on that below onCartUpdated .
JavaScript
var updateCounter = 0,
submitTimer = null;
/*
We hook on to the onCartUpdating event.
*/
TeaCommerce.subscribeToOnCartUpdating(function () {
/*
Set the start time of the submitTimer if it has not yet been set.
This way we only set it on the first call in a series off calls
*/
if (submitTimer === null) {
submitTimer = new Date();
}
//Set the cart to updating mode
jQuery('#TopCart').addClass('updating');
jQuery('#cart').addClass('updating');
//Increment the updatecounter
updateCounter++;
});
OnCartUpdateError
This one we hope won’t get fired. If it does, the server has thrown an error and then something is wrong. Often it will be semo settings that are incorrect. But it is also a posibility that you have discovered a bug in the system.In my example I have chosen to alert the error messages to the user. This might not be very smart on a live option, but good for debugging.
Notice that I update the counter variable, since there has been response from the server. You should probably handle a lot more stuff here, in a live situation.
JavaScript
var updateCounter = 0;
/*
We hook on to the onCartUpdateError event.
*/
TeaCommerce.subscribeToOnCartUpdateError(function (error) {
/*
The errortext is thrown right at the user in the
ugliest possible way.
It is only called when something went very wrong
and the server through an exception. This should
never happen as the system is perfect
*/
alert("Cart Error: \n" + error.responseText);
//The update counter is decremented as this call has returned.
updateCounter--;
});
OnCartUpdated
This event is the one with the most action. It is fired when there has been a response from the server, and the cart has been changed. When this happens, the UI must be updated, as changes may have happened to the cart.In the example below, I update the UI after the last call has come back from the server. At least half a second must have passed, so the user will see that something has happened. This I regard as a necessity because the server, in most cases will respond very quickly because our use of the caching in Tea Commerce .
The first thing we will do is to check that the return data contains an order. An order in Tea Commerce is the cart, and the contains all information about the carts state. If there is an order, we can now begin to update the UI. The first thing we change is the small cart at the top of the page. We change the total price and quantity and we check to see if it is empty, as this must affect its appearance.
To update the advanced cart we use TeaCommerce.invokeXSLT. It is created to retrieve the content that a given xslt on the server generates. With TeaCommerce.invokeXSLT , we can use the same HTML, to recreate the cart, which was used when the page was loaded. In this case we let cartStep01.xslt recreate the carts contents and let the new html replace the old. This is smart since we can make the carts HTML in one place and use it where and when we need it.
Finally I remove the “updating” class from my elements tomake them look normal again. The user is thus made aware that the system has finished its task.
I make sure to update both the updateCounter and the submitTimer to be ready for the next call.
JavaScript
var updateCounter = 0,
submitTimer = null;
/*
We hook on to the onCartUpdated event.
This way we can change the UI everytime the Cart/order has been changed
*/
TeaCommerce.subscribeToOnCartUpdated(function (data) {
/*
We use a timer to secure that the user will see our update graphics.
The caching on the server makes the server calls very fast and
the user might just se the page blink and will not realise that
something changed.
It's important for me to say that i have not yet tested this on
a live shop. It does though work very well on our test setup.
*/
submitTimerEnd = (new Date()).getTime() - submitTimer.getTime();
//Check the timeout to see if it took at least half a second
if (submitTimerEnd < 500) {
//If the return call was too fast we delay the call for
window.setTimeout(function () {
updateCarts(data);
}, submitTimerEnd);
return false;
}
/*
We keep track of the updates made with a counter.
This way we only need to update the carts when all
changes have taken place.
*/
updateCounter--;
if (updateCounter < 1) {
//All calls have returned. Now we can start the update of the UI
var topCart = jQuery('#TopCart'), cart = jQuery('#cart.stepProgress01')
/*
The data object holds the return data from the server.
In these general events the return data will allways be a javascript object.
*/
//If the data holds an order object, we can update the top cart
if (data.Order) {
//Information in the order object is used to update the UI
topCart.find(".orderTotal").html(data.Order.TotalPriceFormatted);
topCart.find(".itemsInCart").html(data.Order.TotalQuantity);
//If the Total quantity has changed we may need to change the class of the top cart
if (data.Order.TotalQuantity < 1) {
topCart.addClass('isEmpty');
} else {
topCart.removeClass('isEmpty');
}
//If there is a cart on the page we can update that
if (cart[0]) {
//Fetch the new cart html with invokeXSLT and update the UI
var cartJqHtml = jQuery(TeaCommerce.invokeXSLT('cartStep01.xslt', _nodeId, false, null, null));
cart.after(cartJqHtml);
cart.remove();
}
}
//All updates have been made and we can now remove the "updating" class on the top cart.
topCart.removeClass("updating");
//the timer is reset and is now ready for the next run
submitTimer = null;
}
});
2. Add product to Tea Commerce's cart
If you need to create, overwrite or count products in the cart up and down, this is what to read. The only thing you need is an ID on the node / product you want to put in the cart, and the quantity to be or added to the cart. How to obtain the two things is up to each developer / webshop. Here I give you a few examples of how to do it.addOrderLine and addOrderLineBulk
These two methods is used to add products to the cart. They can also be used to count existing order lines up or down. The difference of the two methods is that addOrderLine , adds or changes a single order line. The second, addOrderLineBulk , adds or changes many orderlines in a single server call. See how to use addOrderLineBulk here .I have two examples of how to use addOrderLine. Both use jQuery's live event , to wait for a user to click on certain buttons.
The first example enables functionality on the products' "add to cart" links. A few lines of code handles the various stages of a button. When a product is put in the cart its button must have the "updating" class. When the server responds to the request, I will show it will have the "Updated" class for a few seconds.
I fetch the product node id from a custom attribute on the button itself, or from the variant dropdown box, if it exists. Quantity comes from an ordinary input field. After getting the node id and the number in place, I call TeaCommerce.addOrderLine. I make the server call asynchronously, so the user can continue browsing the web shop. I know I will be notified of the server's response in my universal onCartUpdating event, but I would also like to know when this particular buttons answer comes back. Therefore I attach a method that will be called when the server response comes back. This means that I can update the button's state, but only this buttons state.
JavaScript eksempel 1
/*
When an add to cart button is clicked
*/
jQuery(".productAddToCart").live("click", function () {
var jQEle = jQuery(this);
/*
If the product allready have a pending add to cart running
we do not want to make another one.
in principle you could easily make the call again. This
is just for demo purposes. You can make as many calls to
the server as you like.
*/
if (!jQEle.hasClass('updating')) {
jQEle.addClass('updating');
//Get the various elements and variables
var product = jQEle.closest('.product'),
quantityInput = product.find('input.productQuantity'),
quantity = quantityInput[0] ? quantityInput.val() : 1,
variantSelect = product.find('select.chooseVariant'),
nodeId = variantSelect[0] ? variantSelect.val() : jQEle.attr('nodeid');
//If no node id has been selected we return and alert the user
if (nodeId === '0') {
alert('Please select a variant');
jQEle.removeClass('updated');
jQEle.removeClass('updating')
return;
}
/*
Add orderline is called using the Tea Commerce javascript API
We subscribe to the return events to be able to update the
css class of the button.
*/
TeaCommerce.addOrderLine(nodeId, quantity, true, function () {
//A timeout is set to show the user that the product has been added
jQEle.removeClass('updating').addClass('updated');
window.setTimeout(function () {
jQEle.removeClass('updated');
}, 1500);
}, function () {
//Something went wrong. We should handle it here
jQEle.removeClass('updating');
});
}
return false;
});
My second example is to implement the functionality of the plus and minus buttons
in the advanced cart. When the plus or minus buttons is clicked, I fetch the node
id from the order line, which has a custom attribute attached. I find out if there
is to be added or subtracted from the quantity, and I updates the input field with
the new quantity. If I do not do the latter, the user will not be made aware that
something has happened. Finally I call the server, with
TeaCommerce.addOrderLine .
JavaScript eksempel 2
/*
The increment and decrement funktionality of the main cart
*/
jQuery('td#leftColumn a.minus, td#leftColumn a.plus').live('click', function () {
var jQEle = jQuery(this),
orderLine = jQEle.closest('.item'),
quantityInput = orderLine.find('input.productQuantity'),
nodeId = orderLine.attr('nodeid'),
quantity = 1;
//If the clicked button is minus the quantity is likewise
if (jQEle.hasClass('minus')) {
quantity = -1;
}
/*
We increment/decrement the input field to let the
user know somthing has happened
*/
quantityInput.val(parseInt(quantityInput.val(), 10) + quantity);
/*
Again we use the versatile addOrderLine method to
change the quantity.
This also means that if the user adds a product
to the cart several times the quantity will be
added to the orderline.
*/
TeaCommerce.addOrderLine(nodeId, quantity);
return false;
});
overwriteQuantity and overwriteQuantityBulk
Different but still almost identical to the addOrderLine methods. OverwriteQuantity and overwriteQuantityBulk overwrites the quantity where addOrderLine and addOrderLineBulk adds and subtracts. Besides of that one difference the two types of methods are exactly the same.I have chosen to make an example, in which we will let the user write the new quantity in an input field. When the user is done, we overwrite the quantity of the order line. What we do is to register a method to the input field's onblur event. It will be run when the user removes the cursor from the input field. I do it with jQuery's live event handler, because I know the number of order lines could be changed with ajax. The live event ensures that both new and old elements can fire the event, if only they meet my requirements.
The node id is found on the order line's custom attribute and the number is drawn from the input field itself. Now we just call OverwriteQuantity and the server overwrites the number on the order line in the cart.
JavaScript
/*
When the quantity is changed in the cart
*/
jQuery('#cart input.productQuantity').live("blur", function () {
var jQEle = jQuery(this),
nodeId = jQEle.closest(".item").attr('nodeId'),
quantity = jQEle.val();
//The quantity is overwritten
TeaCommerce.overwriteQuantity(nodeId, quantity);
});
Additional information on the order line
One frequent question is - How to add additional information on each order line? - And - is the security in order around the same?Tea Commerce lets the server add additional information to the order line. In the general settings of the Tea Commerce backend, you can configure which fields you wish to add to the order lines when they are added to the cart. Tea Commerce will find the product node and pull the necessary information. If it gets hold of a variant and / or the desired property simply does not exist, Tea Commerce will search up the node tree in an attempt to find the requested node property.
This way the user has by no means any control over the information stored on each order line, and therefore sequrity is intact.
3. Removing order lines from Tea Commerce cart
To delete one or all order lines can be done very simply.If you only want to remove one order line you would use TeaCommerce.removeOrderLine and send a product node id with it. If you want all orderlines removed, call TeaCommerce.removeAllOrderLines.
In both examples we trigger the call when a certain button is clicked.
JavaScript
/*
The remove item event
*/
jQuery('a.remove').live('click', function () {
var jQEle = jQuery(this),
orderLine = jQEle.closest('.item'),
nodeId = orderLine.attr('nodeid');
//Remove a specific orderline
TeaCommerce.removeOrderLine(nodeId);
return false;
});
/*
The empty cart event
*/
jQuery('a#emptyCart').live('click', function () {
//call the removeAllOrderLines which does just that
TeaCommerce.removeAllOrderLines();
return false;
});
4. Change of currency
Tea Commerce makes it possible for the developer, to change the active currency. To do this, you will need the new currency ID. It can be obtained via Tea Commerce's xslt extensions or by fethching them from the server with TeaCommerce.getCurrencies . The present currency, can be loaded with TeaCommerce.getCurrentCurrency .setCurrentCurrency
In the example below we use TeaCommerce.setCurrentCurrency to change the selected currency. A dropdown list allows the user to choose among the different currencies and we sign on the drop downs change event.The first thing we do is to withdraw the new currency ID from the dropdown value. Then we use TeaCommerce.setCurrentCurrency to send the new id to the server, which modifies the cart currency. We choose to sign up for the return call event, since a lot of the UI need to be changed when a new currency is chosen. In the return event, we ensures to handle delivery and payment, which may have changed. Also all products on the page is reloaded. The carts we don't need to update here, since the universal event is fired when we are done here, and does it for us.
For all the changes we use TeaCommerce.invokeXSLT because it is the lazy, quick way. It is of course also the safe way, because we know that the HTML is the same, just with new data. If you want to minimize traffic back and forth between server and client, there are other ways to update your UI.
Each developer's imagination can flourish in those situations.
JavaScript
//Subscribe to jQuery's document ready
jQuery(function () {
/*
The currency drop down shoul control the current country of the order.
OnChange we will change the orders current country.
*/
jQuery('select#currency').change(function () {
var jQEle = jQuery(this), currencyId = jQEle.val();
//Send the new currency Id to the server
TeaCommerce.setCurrentCurrency(currencyId, true, function () {
/*
Allmost the whole page must be updated now as all prices has
changed to the new currency. We could reload the page... But why
not do it the nice way.
The cart will be automatically updated when the OnCartUpdated
event is fired just after this one!
*/
//Shipping and payment info is updated
var shippingAndPayment = jQuery("#shippingAndPayment");
//Check to see that we need to do the update
if (shippingAndPayment[0]) {
//Use invokeXSLT to get the new html from the database.
shippingAndPayment.after(TeaCommerce.invokeXSLT('paymentAndDelivery.xslt', _nodeId, false, null, null));
shippingAndPayment.remove();
Cufon.replace('#shippingAndPayment .cufonReplace');
}
//Our product list is updated
var productList = jQuery("#productList"), featuredProduct = jQuery('#featuredProduct');
//Check to see that we have a featured product
if (featuredProduct[0]) {
//Use invokeXSLT to get the new html from the database.
featuredProduct.after(TeaCommerce.invokeXSLT('featuredProduct.xslt', _nodeId, false, null, null));
featuredProduct.remove();
}
//Check to see that we have a product list
if (productList[0]) {
//Use invokeXSLT to get the new html from the database.
productList.html(TeaCommerce.invokeXSLT('productList.xslt', _nodeId, false, null, null));
}
});
});
});
5. Delivery and Payment
Delivery and payment, in Tea Commerce, works almost the same. It will be natural to describe them at the same time. Delivery and payment methods may be associated with a fee which shall be reckoned with in the order. When you change one of them something might have to be changed in the UI. Often, as in this example, it will be enough to handle these changes in the universal event.In the following two examples we use live click events on two radio button lists with the delivery and payment methods. When selecting a new shipping or payment method we send the new id to the server, which modifies the order.
JavaScript
/*
The change shipping method event
*/
jQuery('#shippingMethods input').live("click", function () {
var shippingMethodId = jQuery(this).val();
//We call the server and sends the new shipping id
TeaCommerce.setShippingMethod(shippingMethodId);
});
/*
The change payment method event
*/
jQuery('#paymentMethods input').live("click", function () {
var paymentMethodId = jQuery(this).val();
//We call the server and sends the new payment id
TeaCommerce.setPaymentMethod(paymentMethodId);
});
6. Additional order information
In all e-commerce online shops have a need to be able to associate extra information to an order. Whether it is customer information, information about gift wrap or other special requests, Tea Commerce handles it the same. From our JavaScript it is possible to attach all kinds of information to an order, as long as it's text. All extra information is stored in an index, where you can look them up again on their alias / key.In our example, we send all information in the same server call, but with updateOrderProperty you can change / add one at a time instead. In step two in the cart, we let the user enter a lot of personal information in a variety of input fields. When he clicks on the "next" button, we make sure to save the information on the order and send the user to step 3. This is done by first collecting all the users entered data. We combine them into a JavaScript object that eventually can be sent directly to the server with updateOrderProperties. Before we send the information, we create a simple validation of the entered data. Where the call is made to the server, we choose to do it synchronously. This means that the JavaScript will wait for a response from the server before it can continue execution. When the answer comes back, the user is sent to the next step.
The example does not handle the possibility of a server error. If that should happen, you would have to handle that situation as well.
/*
The next step event on step 2 of the cart
*/
jQuery('#cart.stepProgress02 a#next').live("click", function () {
var personalInformation = jQuery("#personalInformation"), differentShippingAddress = jQuery("#differentShippingAddress")[0].checked;
/*
We fetch the information from the form and creates
an object that can be sent to the server as a form
POST submit
*/
var formObj = {
"firstName": personalInformation.find("#firstName").val(),
"lastName": personalInformation.find("#lastName").val(),
"city": personalInformation.find("#city").val(),
"zipCode": personalInformation.find("#zipCode").val(),
"streetAddress": personalInformation.find("#street").val(),
"country": personalInformation.find("#country option:selected").text(),
"phone": personalInformation.find("#phone").val(),
"email": personalInformation.find("#email").val(),
"differentShippingAddress": jQuery("#differentShippingAddress")[0].checked,
"comments": jQuery("#comments").val()
};
if (differentShippingAddress) {
var shippingInformation = jQuery("#shippingInformation");
formObj["shipping_companyName"] = differentShippingAddress ? shippingInformation.find("#shipping_companyName").val() : "";
formObj["shipping_firstName"] = shippingInformation.find("#shipping_firstName").val();
formObj["shipping_lastName"] = shippingInformation.find("#shipping_lastName").val();
formObj["shipping_city"] = shippingInformation.find("#shipping_city").val();
formObj["shipping_zip"] = shippingInformation.find("#shipping_zip").val();
formObj["shipping_streetAddress"] = shippingInformation.find("#shipping_street").val();
formObj["shipping_country"] = shippingInformation.find("#shipping_country option:selected").text();
}
//Validation
var pageValidateText = '';
if (formObj.firstName == '') {
pageValidateText += '\nFirst name';
}
if (formObj.lastName == '') {
pageValidateText += '\nLast name';
}
if (formObj.email == '') {
pageValidateText += '\nE-mail';
}
if (!jQuery('[name=shippingMethod]:checked')[0]) {
pageValidateText += '\nShipping method';
}
if (!jQuery('[name=paymentMethod]:checked')[0]) {
pageValidateText += '\nPayment method';
}
if (pageValidateText != '') {
pageValidateText = 'Remember to fill out' + pageValidateText;
alert(pageValidateText);
return false;
}
/*
The properties is sent to the server with a syncronous call
This way we lock the UI and can redirect the user.
*/
TeaCommerce.updateOrderProperties(formObj, false);
window.location.href = "/en/cart/accept-step-3.aspx";
return false;
});
7. Delivery Countries
When the user selects a new delivery country we may, depending on the settings in the Tea Commerce backend, need to change the UI. The change can be in terms of the delivery and / or payment method, since these are linked to one or more delivery countries. In the demo web shop the delivery and payment methods differ between the United States and Denmark, so here we would have to change the options when a new delivery country is selected. Because it's all done on the same page, step 2, we do it on the fly.In my example below we take into account that the delivery country may be different from the billing country. In all cases it is the delivery country that desides what options the user has. Therefore I make some checks to see, if it is the first or the second select box that reflects the delivery country. In the following example, if the user has checked the second delivery address checkbox nothing will happen if the user selects a new billing address.
Besides that, the example is relatively simple. We fethces the new delivery country Id and sends it, with setCurrentCountry, to the server, which modifies the cart. We know that the universal event will handle the updating of the cart at the top of the page, so everything we need to do is to update the delivery and payment methods. This is done with TeaCommerce.invokeXSLT which is used in the return call from the server.
/*
Cart step 2 - Subscribe to country dropdowns onchange events
and update the current country on the server
*/
jQuery('select#country, select#shipping_country').change(function () {
//Get the chosen country id
var jQEle = jQuery(this), countryId = jQEle.val();
/*
If shipping address is chosen only the shipping country dropdown can
change the current country
*/
if (jQEle.attr("id") === "shipping_country" || (jQEle.attr("id") === "country" && !jQuery("#differentShippingAddress")[0].checked)) {
/*
Use the Tea Commerce javascript API to send the new country id to the server
We subscribe to the success event of the server call
*/
TeaCommerce.setCurrentCountry(countryId, true, function () {
//On success
/*
If the triggering dropdown is the normal country select
we change the shipping country dropdown as well to keep them in sync
*/
if (jQEle.attr("id") === "country") {
jQuery("select#shipping_country").val(countryId);
}
/*
If the page contains shipping and payment information these have to be updated.
The reason for this is that shipping and payment are hooked up on the countries
and may change when we change the current country
*/
var shippingAndPayment = jQuery("#shippingAndPayment");
if (shippingAndPayment[0]) {
/*
TeaCommerce.invokeXSLT is used to fetch the paymentAndDelivery.xslt html from the server.
The new html will be based on the new current country.
The html replaces the old.
*/
shippingAndPayment.after(TeaCommerce.invokeXSLT('paymentAndDelivery.xslt', _nodeId, false, null, null));
shippingAndPayment.remove();
}
});
}
});
8. Payment providers
Tea Commerce has a very simple and effective approach to sending the user to a selected payment provider. in the example the user selected a payment method at a previous step. The task now is to get the user redirected to the payment providers website.Basically we have two scenarios.
The simple scenario is that there is no payment provider involved. This could be the case if the user has chosen account transfer, or if he is to fetch and pay his product in the store. In this scenario, Tea Commerce automatically approve the order and send the user to the confirmation page.
In the second scenario, the user must pay with a credit card. Here Tea Commerce sends the user to the chosen payment provider (eg. QuickPay, PayPal).
Both scenarios are handled similarly by Tea Commerce, by allowing the developer to call goToPayment. Via the server goToPayment sends the user to the chosen destination.
Technically speaking the server generates a form that goToPayment simply submits.
/*
The next step event on step 3 of the cart
*/
jQuery("#cart.stepProgress03 #next").live("click", function () {
/*
TeaCommerce.GoToPayment fetches the payment method from the server
and redirects the user to the payment provider.
If the provider is a creditcard-thingy a form is returned from the
server and the
Tea Commerce javascript API submits the form.
*/
TeaCommerce.goToPayment();
});
Rounding off the demo e-commerce webshop
Thus my review of the JavaScript in our demo e-commerce webshop in Umbraco. If you want to read more or download the demo site xslt and JavaScript files, I have written my links below.If you have questions or comments you are always welcome to write a comment here or in the Tea Commerce Forum .
Downloads
- All xslt files used in the Tea Commerce demo webshop
- All JavaScripts from the Tea Commerce demo webshop
dansk version
Twitter
LinkedIn
7 comments
Thanks Rune.
Sidenode: Please consider a smaller font size
Best,
Jesper
(it’s very very small isnt it
Remember to set zoom level to 100% In your browser.
/Rune
Believe me it is. But you’re using font-size: 11px? That’s not fair
Paragraph text should be at least 13 to be readable Check the rest of the web – haha.
Jesus – you’re forcing me to buy glasses.
/j
Then my posts would be several kilometers high
That’s true. It is a rather lengthy post. But it would make it much more readable.
There… Happy now
Thank you, sir. I’m sure that you’ve made a bunch of ppl’s lives easier.
Best
Jesper