Rune Grønkjærs Blog
Abonnér på mit feed

Tea Commerce JavaScript / xslt examples

UPDATE

The examples might not all be updated according to the newest Tea Commerce API

E-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

  1. Universal Events
  2. Add product to the Tea Commerce cart
  3. Remove order lines from the Tea Commerce cart
  4. Change currency
  5. Delivery and Payment
  6. Extra order information
  7. Delivery countries
  8. Payment providers

Other Tea Commerce articles

Downloads

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

Tea Commerce links

This post was written in Ajax, css, e-commerce, html, Javascript, jQuery, Tea Commerce, Umbraco. Ad permalink to favorites. Follow all comments with RSS feed for this posts. Drop a comment or a trackback: Trackback URL. | Læs denne side på dansk dansk version

7 comments

  1. Written November 25, 2010 at 9:59 am | Permalink

    Thanks Rune.

    Sidenode: Please consider a smaller font size :-)

    Best,
    Jesper

    (it’s very very small isnt it :-)

  2. Rune Grønkjær
    Written November 25, 2010 at 10:09 am | Permalink

    Remember to set zoom level to 100% In your browser. ;)

    /Rune

  3. Written November 25, 2010 at 10:22 am | Permalink

    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

  4. Written November 25, 2010 at 10:40 am | Permalink

    Then my posts would be several kilometers high :D

  5. Written November 25, 2010 at 10:47 am | Permalink

    That’s true. It is a rather lengthy post. But it would make it much more readable. ;)

  6. Written November 25, 2010 at 11:15 pm | Permalink

    There… Happy now ;)

  7. Written November 26, 2010 at 1:33 am | Permalink

    Thank you, sir. I’m sure that you’ve made a bunch of ppl’s lives easier.

    Best
    Jesper

Drop a comment

Your email is never published nor shared. Required fields are marked *

*
*

You can use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Dansk version