I am building html pages with a lot of checkboxes. I want to manage a single cookie containing selected items. Cookie looks like 1234^9876^3456^ where ^ is the separator.
When the user checks or unchecks the box, the cookie shows the added or removed id number.
Here are the functions I am using. All but the 2 last are from know third-party developers. Some problems are:
Each checkbox has an onclick event AddRemoveOrdinal2(...);
The user sees okay checked and unchecked summary data, even refreshing the page but several cookies are stored in browser, same name, path, different content;
The last function RemoveOrdinal is used to remove an item from the summary, it deletes the cookie and does not replace the new one.
Maybe it would be better to start again with a new idea/procedure
function setCookie(c_name,value,expiredays)
{
var exdate=new Date();
exdate.setDate(exdate.getDate()+expiredays);
document.cookie=c_name+ "=" +escape(value)+
((expiredays==null) ? "" : ";expires="+exdate.toUTCString());
}
function getCookie(c_name)
{
var i,x,y,ARRcookies=document.cookie.split(";");
for (i=0;i<ARRcookies.length;i++)
{
x=ARRcookies[i].substr(0,ARRcookies[i].indexOf("="));
y=ARRcookies[i].substr(ARRcookies[i].indexOf("=")+1);
x=x.replace(/^\s+|\s+$/g,"");
if (x==c_name)
{
return unescape(y);
}
}
}
function del_cookie(name)
{
if (dbug) alert('del_cookie');
document.cookie = name + '=' + '; expires=Thu, 01-Jan-70 00:00:01 GMT; path=/; '
}
function get_cookie(name) {
if (dbug) alert('get_cookie');
var dcookie = document.cookie;
var cname = name + "=";
var clen = dcookie.length;
var cbegin = 0;
while (cbegin < clen) {
var vbegin = cbegin + cname.length;
if (dcookie.substring(cbegin, vbegin) == cname) {
var vend = dcookie.indexOf (";", vbegin);
if (vend == -1) vend = clen;
return unescape(dcookie.substring(vbegin, vend));
}
cbegin = dcookie.indexOf(" ", cbegin) + 1;
if (cbegin == 0) break;
} return null;
}
function set_array(name, ary, expires) {
if (dbug) alert('set_array');
var value = '';
for (var i = 1; ary[i]; i++) {
value += ary[i] + '^';
}
set_cookie(name, value, expires);
}
function AddRemoveOrdinal2(id, ordinal, id_checkbox){
var foo = document.getElementById(id_checkbox).checked;
if (foo == false) {
get_array(cookieName, myarray);
var myarray2 = init_array();
for (var i=0; i<next_entry(myarray); i++) {
if(myarray[i] != ordinal){
myarray2.push(myarray[i]);
}
}
del_cookie(cookieName);
set_array(cookieName, myarray2, expires);
myarray = myarray2;
// Code here to display:none/block
}
else {
get_array(cookieName, myarray);
myarray.push(ordinal);
set_array(cookieName, myarray, expires);
}
get_array(cookieName, myarray);
if(myarray.length > 1){
// Code here to display:none/block elements
}
else {
// Code here to display:none/block elements
}
function RemoveOrdinal(id, ordinal, id_checkbox){
get_array(cookieName, myarray);
var myarray2 = init_array();
for (var i=0; i<next_entry(myarray); i++) {
if(myarray[i] != ordinal){
myarray2.push(myarray[i]);
}
}
del_cookie(cookieName);
set_array(cookieName, myarray2, expires);
myarray = myarray2;
esconder(id);
document.getElementById(id_checkbox).checked=false;
get_array(cookieName, myarray);
if(myarray.length > 1){
// Code here to display:none/block elements
else {
// Code here to display:none/block elements
}
}
On load
var cookieName = 'ordinals';
var myarray = init_array();
var timeToKeep = 60000*60*24*7;
var expires = new Date();
expires.setTime(expires.getTime() + timeToKeep);
var x = get_cookie(cookieName);
if ( !x || x == null) {
set_array(cookieName, myarray, expires);
}
Related
I have a MultiSelectDropDown, that is, several RadComboBox controls are used in a combined way. For example, I can have a dropdown for regions, another for depots and another for user. The idea is to change the content of lower levels dynamically whenever items are selected or unselected on a higher level. The problem is that in the case when many items are selected, this becomes brutally slow due to some Telerik functions, but I do not understand why. This is a chunk from the client-side of the MultiSelectDropDown prototype:
changeLowerLevels: function (valueIndex, values, value) {
if (!this.canChange) return;
//Get selected values from combobox
var combo = $find(this.ddlIDs[valueIndex - 1]);
var cbItems = combo.get_checkedItems();
var selectedItems = [];
var change = null;
var counter = 0;
if (cbItems.length) this.filterString = "";
for (var i = 0; i < cbItems.length; i++) {
counter++;
if (this.filterString == "") this.filterString = cbItems[i].get_text();
selectedItems.push(cbItems[i].get_value());
}
if (counter > 1) this.filterString += " with " + (counter - 1) + " other" + ((counter > 2) ? "s" : "");
if (JSON.stringify(selectedItems) === JSON.stringify(this.selectedItems[valueIndex - 1]) || selectedItems == [])
return;
this.selectedItems[valueIndex - 1] = selectedItems;
var controlObject = this;
var combo = $find(this.ddlIDs[valueIndex]);
var comboItems = combo.get_items();
if(!this.disabled) combo.enable();
combo.clearItems();
if (valueIndex == 1) this.twoLevelCache = values;
var val = values;
//break if all items are found
var nrOfSelectedItems = this.selectedItems[valueIndex - 1].length;
var nrOfFoundItems = 0;
var index = 0;
var indexes = [];
var found = false;
while (nrOfFoundItems < nrOfSelectedItems && val[index] !== undefined) {
found = (this.selectedItems[valueIndex - 1].indexOf(val[index].Value) != -1);
if (!(found))
index++;
else {
indexes.push(index)
nrOfFoundItems++;
index++;
}
}
//separators from valuesIndex - 1 level
var controlObject = this;
for (var i = 0; i < indexes.length; i++) {
var separator = new Telerik.Web.UI.RadComboBoxItem();
separator.set_text("<span><a class=\"checkAll tt-multi-uncheck-icon\" index=\"" + index + "\">U</a>" + $find(this.ddlIDs[valueIndex - 1]).findItemByValue(val[indexes[i]].Value).get_text() + "</span>");
separator.set_value("");
separator.set_isSeparator(true);
comboItems.add(separator);
this.twoLevelCache.push(val[indexes[i]].Levels);
//valuesIndex level
var valuesArray = val;
var comboItem = new Telerik.Web.UI.RadComboBoxItem();
for (var depot in valuesArray[indexes[i]].Levels) {
comboItem = new Telerik.Web.UI.RadComboBoxItem();
comboItem.set_text(valuesArray[indexes[i]].Levels[depot].Name);
comboItem.set_value(valuesArray[indexes[i]].Levels[depot].Value);
comboItems.add(comboItem);
comboItem = null;
}
$('#' + this.ddlIDs[valueIndex] + '_DropDown a.checkAll').unbind().on("click", function () {
checkAllLowerItems(this, controlObject.ddlIDs[valueIndex]);
});
}
combo.set_emptyMessage(this.allText);
//$("#" + this.ddlIDs[valueIndex]).html(returnValue);
if (this.ddlIDs.length > valueIndex + 1) {
var paramToPass = (((val == undefined) || (val[index] === undefined)) ? ("") : (val[index]));
if (this.allText.length > 0)
this.changeLowerLevels(valueIndex + 1, paramToPass, "");
else {
if (paramToPass !== "")
paramToPass = paramToPass.Levels;
if ((val[index] == undefined) || (val[index].Levels[0] === undefined) || (val[index].Levels[0].Value === "")) {
this.changeLowerLevels(valueIndex + 1, paramToPass, "");
}
else {
this.changeLowerLevels(valueIndex + 1, paramToPass, val[index].Levels[0].Value);
}
}
}
else {
if (this.allText.length > 0)
this.selectedItems[valueIndex] = "";
else
if ((val[index] == undefined) || (val[index].Levels[0] === undefined) || (val[index].Levels[0].Value === "")) {
this.selectedItems[valueIndex] = "";
}
else {
this.selectedItems[valueIndex] = val[index].Levels[0].Value;
}
}
this.setText();
}
combo.clearItems() is extremeley slow. I have take a look on how it is implemented:
function (){var f=this._parent._getControl();?if(f._checkBoxes){f._checkedIndicesJson="[]";?f._checkedIndices=[];?var g=f.get_items();?for(var d=0,e=g.get_count();?d<e;?d++){var c=f.get_items().getItem(d);?c.set_checked(false);?}f.updateClientState();?}a.RadComboBoxItemCollection.callBaseMethod(this,"clear");?}
How can I make sure that this Javascript function speeds up?
I have finally solved the problem by rewriting Telerik client-side functionalities. It was a long and difficult debugging, but it yielded a large performance boost in the most difficult circumstances. From ~30 000 milliseconds, to ~300. Let's see the parts of the optimization:
The actual rewrite
/* Overriding Telerik functions Start */
var overridenTelerikControls = false;
function overrideTelerikFunctionalities() {
if (!overridenTelerikControls) {
overridenTelerikControls = true;
Telerik.Web.UI.RadComboBox.prototype.clearItems = function (isMultiSelectDropDown) {
this.get_items().clear(isMultiSelectDropDown);
this._itemData = null;
};
Telerik.Web.UI.RadComboBoxItemCollection.prototype.clear = function (isMultiSelectDropDown){
var f=this._parent._getControl();
if(f._checkBoxes){
f._checkedIndicesJson="[]";
f._checkedIndices=[];
var g = f.get_items();
for(var d=0,e=g.get_count();d<e;d++){
var c=f.get_items().getItem(d);
c.set_checked(false, isMultiSelectDropDown);
}
if (isMultiSelectDropDown) {
f._updateComboBoxText();
if (f._checkAllCheckBoxElement != null) {
f._updateCheckAllState();
}
}
f.updateClientState();
}
Telerik.Web.UI.RadComboBoxItemCollection.callBaseMethod(this, "clear");
};
Telerik.Web.UI.RadComboBoxItem.prototype.set_checked = function (d, isMultiSelectDropDown){
if(!this.get_enabled()){
return;
}
this._setChecked(d);
var c=this.get_comboBox();
if(c){
if(d){
c._registerCheckedIndex(this.get_index());
}else{
c._unregisterCheckedIndex(this.get_index());
}
if (!isMultiSelectDropDown) {
c._updateComboBoxText();
}
if((!isMultiSelectDropDown) && (c._checkAllCheckBoxElement!=null)){
c._updateCheckAllState();
}
}
};
}
}
/* Overriding Telerik functions End*/
My approach was to keep the old way of their working by default, but if an isMultiSelectDropDown parameter is passed, then work in the optimized manners. So we have a switch materialized as a parameter and we can turn it on/off. The main difference was that the old way was to change the label text showing the selected elements each time a checkbox is checked/unchecked. The main improvement was to do this change after all the checkboxes were checked/unchecked. This extremely simple idea was the driving force behind the boost of performance.
Actual usage
overrideTelerikFunctionalities();
combo.clearItems(true);
This was the functionalities were overriden if they were not already and the parameter was true, therefore the new approach was chosen.
Test, test, test
I'm having this weird error while accessing the key evenimente of my object. Any ideas why?
It clearly is available but I cannot access it via .(dot)
EDIT: Sorry, here is the code
var isPresent = function(entry) {
var evenimente = entry.evenimente;
var elem = $("#" + entry.nume + "-" + entry.prenume);
for(var i = 0; i < evenimente.length; i++) {
var eveniment = evenimente[i];
if(eveniment.eveniment_id == eventId && eveniment.user_id == entry.id) {
elem.prop('checked', true);
}
}
elem.prop('checked', false);
}
I get entry via ajax from the server
Looks like your entry is an array (see the [ and ] in your console).
So, you should be able to access the evenimente field this way:
var ev = entry[0].evenimente
The updated code would look like this:
var isPresent = function (entry) {
var evenimente = entry[0].evenimente;
var elem = $("#" + entry[0].nume + "-" + entry[0].prenume);
for (var i = 0; i < evenimente.length; i++) {
var eveniment = evenimente[i];
if (eveniment.eveniment_id == eventId && eveniment.user_id == entry[0].id) {
elem.prop('checked', true);
}
}
elem.prop('checked', false);
}
Also, for convenience, you could just override the entry value like below:
var isPresent = function (entry) {
entry = entry[0];
if (!entry) {
throw new Error("No available entry.");
}
var evenimente = entry.evenimente;
var elem = $("#" + entry.nume + "-" + entry.prenume);
for (var i = 0; i < evenimente.length; i++) {
var eveniment = evenimente[i];
if (eveniment.eveniment_id == eventId && eveniment.user_id == entry.id) {
elem.prop('checked', true);
}
}
elem.prop('checked', false);
}
I want to hide my website content exception made when ?token_code=12345678 is used in URL. This is the code that's not working correctly, it hides website but never shows it:
I'm calling script by www.example.com/?authtoken=12345678
So when that parameter is included in URL it should show website. But it's not displaying it. It's only hiding it.
PS. I'm using cookies to remember "token" :)
HTML:
<body data-token="12345678"> </body>
JS:
//setCookie and readCookie
function SetCookie(e, t, n) {
var r = new Date;
var i = new Date;
if (n == null || n == 0) n = 1;
i.setTime(r.getTime() + 36e5 * 24 * n);
document.cookie = e + "=" + escape(t) + ";expires=" + i.toGMTString()
}
function ReadCookie(e) {
var t = " " + document.cookie;
var n = t.indexOf(" " + e + "=");
if (n == -1) n = t.indexOf(";" + e + "=");
if (n == -1 || e == "") return "";
var r = t.indexOf(";", n + 1);
if (r == -1) r = t.length;
return unescape(t.substring(n + e.length + 2, r))
}
function DeleteCookie(name) {
document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT;';
}
//capitalzies string
function capitalize(str) {
var first = str.charAt(0).toUpperCase();
str = str.replace(/^.{1}/, first);
return str;
}
// get's the GET paramters like so --> $_GET('var1');
function getVar(variable) {
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split("=");
if (pair[0] == variable) {
return pair[1];
}
}
return (false);
}
// Checks for one of TWO access short codes
// includeOnly && excludeOnly
// If includeOnly is not NULL, then ONLY include
// categories mentioned in that varaible.
// Also, cookie the data, that it's saved.
// Of course, if anyone re-visits the site, and
// re-writes the GET paramter, it'd delete all
// previous data in the cookie.
var token_code = ["authtoken", "excludeOnly"];
var asc = ""; //this is used to select the CURRENT access short code
var tokenValues = [];
//first check if there are ANY get params.
if (getVar(token_code[0]) != false) {
//before writing the inlcude only, delete EXCLUDE only
DeleteCookie(token_code[1]);
SetCookie(token_code[0], getVar(token_code[0]));
}
if (getVar(token_code[1]) != false) {
//before writing the EXCLUDE only, delete include only
DeleteCookie(token_code[0]);
SetCookie(token_code[1], getVar(token_code[1]));
}
//Try and reaad the cookie (there should be a cookie named "includeOnly" or "excludeOnly -- both from token_code)
//includeOnly is present?
if (ReadCookie(token_code[0]).toString().length > 0) {
//defines what the user wants to do. Exlcude or include? when token_code[0] it's include!
asc = token_code[0];
var tokens = ReadCookie(asc).toString();
tokenValues = decodeURIComponent(tokens).split(',');
//loop through each category.
//hide every category and it's children
$("[data-token]").hide();
$.each(tokenValues, function (index, value) {
//show every category, and it's childen, for the values
$("[data-token='" + value + "']").show();
});
}
//excludeOnly is present?
if (ReadCookie(token_code[1]).toString().length > 0) {
//defines what the user wants to do. Exlcude or include? when token_code[0] it's include!
asc = token_code[1];
var tokens = ReadCookie(asc).toString();
tokenValues = decodeURIComponent(tokens).split(',');
//loop through each category.
//hide every category and it's children
$("[data-token]").show();
$.each(tokenValues, function (index, value) {
//show every category, and it's childen, for the values
$("[data-token='" + value + "']").hide();
});
}
is there an easier way to do this?
In the bottom of your code, were the comment says to show, it runs .hide().
Could that be a problem?
//show every category, and it's childen, for the values
$("[data-token='" + value + "']").hide();
So I have this javascript game that works on my local server but once I put it online, one of the functions stops working. It may have something to do with permissions of the cookies I am storing for the high score. Can someone please point out what may be wrong.
//Save Highscore if score is greater
function saveScore() {
loadScore();
if (score > highscore) {
var date = new Date();
date.setMonth(date.getMonth() + 5);
var expires = "; expires=" + date.toGMTString();
document.cookie = "score=" + score + expires + "; path=/";
}
}
//Load High Score
function loadScore() {
var cookiearray = document.cookie.split(';');
for (var i = 0; i < cookiearray.length; i++) {
var name = cookiearray[i].split('=')[0];
var value = cookiearray[i].split('=')[1];
if (name == "score") {
//alert("Score Found!");
highscore = value;
}
}
return highscore;
}
//Lost game function
function Lost() {
saveScore();
loadScore();
var lost = document.getElementById("lost");
var hs = document.getElementById("hs");
lost.style.visibility = "visible";
postscore.innerHTML = score;
hs.innerHTML = highscore;
lost.addEventListener('click', function (event) {
window.location.reload();
});
}
Based on your comments, I'm guessing that you've modified your code over time but have always had a saved cookie since it became necessary to have one. In your code sample, if the cookie doesn't exist, highscore is never going to be assigned before it's used. You should have some function that runs on page load that looks for the cookie and if it doesn't exist, initializes the beginning state to known values.
Moreover, I would suggest that an object-oriented approach might be more robust. In particularly, I'd create a dedicated object for cookie-handling - which properly handles the case where the cookie doesn't exist - and a separate object or objects for the game.
From MDN - an example of a cookie handler
var docCookies = {
getItem: function (sKey) {
return decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null;
},
setItem: function (sKey, sValue, vEnd, sPath, sDomain, bSecure) {
if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) { return false; }
var sExpires = "";
if (vEnd) {
switch (vEnd.constructor) {
case Number:
sExpires = vEnd === Infinity ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT" : "; max-age=" + vEnd;
break;
case String:
sExpires = "; expires=" + vEnd;
break;
case Date:
sExpires = "; expires=" + vEnd.toUTCString();
break;
}
}
document.cookie = encodeURIComponent(sKey) + "=" + encodeURIComponent(sValue) + sExpires + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "") + (bSecure ? "; secure" : "");
return true;
},
removeItem: function (sKey, sPath, sDomain) {
if (!sKey || !this.hasItem(sKey)) { return false; }
document.cookie = encodeURIComponent(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT" + ( sDomain ? "; domain=" + sDomain : "") + ( sPath ? "; path=" + sPath : "");
return true;
},
hasItem: function (sKey) {
return (new RegExp("(?:^|;\\s*)" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=")).test(document.cookie);
},
keys: /* optional method: you can safely remove it! */ function () {
var aKeys = document.cookie.replace(/((?:^|\s*;)[^\=]+)(?=;|$)|^\s*|\s*(?:\=[^;]*)?(?:\1|$)/g, "").split(/\s*(?:\=[^;]*)?;\s*/);
for (var nIdx = 0; nIdx < aKeys.length; nIdx++) { aKeys[nIdx] = decodeURIComponent(aKeys[nIdx]); }
return aKeys;
}
};
One potential implementation of the elements of your game as an object, loaded in an immediately invoked function that uses the cookie handler above.
(function(cookieHandler) {
// Game constructor
var Game = function(cookieHandler) {
this.init(cookieHandler);
};
// initializes the game variables, sets up and binds the event handlers
Game.prototype.init = function(cookieHandler) {
this.scoreCookie = 'score';
this.score = 0;
this.highScore = this.loadScore();
this.cookieHandler = cookieHandler;
this.lostElem = document.getElementById("lost");
this.postScoreElem = null; // don't see this defined anywhere
this.highScoreElem = document.getElementById("hs");
this.setUpHandlers()
.bind();
};
// create handlers for the events
Game.prototype.setupHandlers = function() {
this.lostHandler = this.lost.bind(this);
return this;
};
// bind the handlers to events
Game.prototype.bind = function() {
// there should probably be a binding for some event
// to invoke the lost handler (lostHandler)
this.lostElem.addEventListener('click', function (event) {
window.location.reload();
});
};
// save the current score, if higher than the current high score, to
// the game cookie
Game.prototype.saveScore = function() {
if (this.score > this.highScore) {
var date = new Date();
date.setMonth(date.getMonth() + 5);
this.cookieHandler.setItem(this.scoreCookie, score, date, '/');
}
};
// retrieves the high game score from the cookie, initializes it to zero
// if there is no cookie.
Game.prototype.loadScore = function () {
this.highScore = 0;
if (this.cookieHandler.hasItem(this.scoreCookie)) {
this.highScore = parseInt(this.cookieHandler.getItem(this.scoreCookie,10);
}
};
// when the game is lost, update the high and save the current score
// if it is a new high score. Signal that the game was lost.
Game.prototype.lost = function() {
this.loadScore();
this.saveScore();
this.lostElem.style.visibility = "visible"
// this.postScoreElem.innerHTML = score;
this.highScoreElem.innerHTML = highscore;
};
var game = new Game(docCookies);
)(docCookies);
i want to make a cokkie which can save my selected region on netsoltech.com whenever user select the region on map it can save the cookie and when the next time user come on domain the automatic go to 1st time click region page i make this code
but the problem is when the user come on next time then 1st region selector page come then it can refresh and go to the region page.. not the automatic...
<script>
/*
Cookie script - Scott Andrew
Popup script, Copyright 2005, Sandeep Gangadharan
*/
function newCookie(name,value,days) {
var days = 10; // the number at the left reflects the number of days for the cookie to last
// modify it according to your needs
if (days) {
var date = new Date();
date.setTime(date.getTime()+(days*24*60*60*1000));
var expires = "; expires="+date.toGMTString(); }
else var expires = "";
document.cookie = name+"="+value+expires+"; path=/"; }
function readCookie(name) {
var nameSG = name + "=";
var nuller = '';
if (document.cookie.indexOf(nameSG) == -1)
return nuller;
var ca = document.cookie.split(';');
for(var i=0; i<ca.length; i++) {
var c = ca[i];
while (c.charAt(0)==' ') c = c.substring(1,c.length);
if (c.indexOf(nameSG) == 0) return c.substring(nameSG.length,c.length); }
return null; }
function eraseCookie(name) {
newCookie(name,"",1); }
function toMem(region) {
newCookie('region', region);
window.location= "http://www.netsoltech.com/"+region+"/index";
}
function remCookie() {
window.location= "http://www.netsoltech.com/"+readCookie("region")+"/index";
}
function addLoadEvent(func) {
var oldonload = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function() {
if (oldonload) {
oldonload();
}
func();
}
}
}
addLoadEvent(function() {
remCookie();
});
</script>
Try this
function setCookie(c_name, value, exdays) {
var exdate = new Date();
exdate.setDate(exdate.getDate() + exdays);
var c_value = escape(value) + ((exdays == null) ? "" : "; expires=" + exdate.toUTCString());
document.cookie = c_name + "=" + c_value;
//redirect here
}
function getCookie(c_name) {
var i, x, y, ARRcookies = document.cookie.split(";");
for (i = 0; i < ARRcookies.length; i++) {
x = ARRcookies[i].substr(0, ARRcookies[i].indexOf("="));
y = ARRcookies[i].substr(ARRcookies[i].indexOf("=") + 1);
x = x.replace(/^\s+|\s+$/g, "");
if (x == c_name) {
return unescape(y);
}
}
}
function checkCookie() {
var region = getCookie("region");
if (region != null && region != "") {
alert("redirect to " + region); //replace this with redirect
}
}
checkCookie(); //check this on page load
and the HTML
europe
JSFiddle
http://jsfiddle.net/YtF2B/6/