Archive for the ‘User Interface’ Category

Update on the GreaseMonkey Custom homepage script

Wednesday, December 31st, 2008

As mentioned at the end of my last post, i continued exploring the idea of having a custom homepage with content aggregated from different sites on the client side. I tried using the about:blank page of Firefox as the starting point on which to build content, but could not succeed. After googling on this issue i found that doing this is not a good idea, since many sites use the about:blank page to create an empty iframe and then set its source. I also read that doing this could make your site prone to XSS attacks. Maybe thats why Firefox blocked access to it (?)

Then i thought about using Google as my start page. I decided to add a 10-day weather forecast at the top of the screen. Had to do some screenscraping to get this content. Next came news. I decided to use the RSS feeds for this purpose. I came across a site->www.webrss.com that will parse the xml content of the RSS feed and provide it via a javascript call. It also gives you the option of converting the rss feed to html, php or asp call. I added feeds from Google News, Times of India, CNN and BBC. Here is how it looks now (click on the image to get a bigger view):

Custom homepage using GreaseMonkey scripts

I decided to hold off on adding mail since that requires authentication.

Here’s the code so far:

// ==UserScript==
// @name           Homepage
// @namespace      http://amarphadke.com/userscripts/customhomepage
// @include        http://www.google.co.in
// @include        http://www.google.com
// ==/UserScript==
 
GM_xmlhttpRequest({
    method: 'GET',
    url: 'http://www.weather.com/outlook/travel/businesstraveler/tenday/44130?from=36hr_topnav_business',
    headers: {
        'User-agent': 'Mozilla/4.0 (compatible) Greasemonkey',
        'Accept': 'application/xml,text/xml',
    },
    onload: function(responseDetails) {
        var startIndex = responseDetails.responseText.indexOf('<div id="tenDayWrap">');
        var endIndex = responseDetails.responseText.indexOf('<div id="TFbuttonB">', startIndex);
        var reqdString = responseDetails.responseText.substring(startIndex,endIndex);
        reqdString = reqdString.replace(/&deg;/g, "==deg==");
        reqdString = reqdString.replace(/&nbsp;/g, " ");
        reqdString = reqdString.replace(/<br>/g, "<br/>");
        reqdString = reqdString.replace(/\"\" width/g, "\" width");
        reqdString = closeImgTags(reqdString);
 
	var parser = new DOMParser();
	var dom = parser.parseFromString(reqdString,"application/xml");
	var tdWraps = getArrayOf(dom,"tdWrap");
 
	var weatherTableHTML = "<table border='0' cellspacing='0' cellpadding='0' width='100%' style='font-size:11px;background-image:url(http://i.imwx.com/web/common/backgrounds/tenday_bkgd.jpg)'><tr>";
 
	for(var i=0;i<tdWraps.length;i++){
		var divs = tdWraps[i].getElementsByTagName("div");
		var dayOfWeek, date,imgSrc, imgText, high, low ;
		for(var j=0;j<divs.length;j++){
			if(divs[j].getAttribute("class")=="tdDate"){
				dayOfWeek = getDayOfWeek(divs[j]);
				date = getDate(divs[j]);
			}
			if(divs[j].getAttribute("class")=="tdForecast"){
				imgSrc = getImageSrc(divs[j]);
				imgText = getImageText(divs[j]);
			}
			if(divs[j].getAttribute("class")=="tdTemps"){
				high = getHighTemp(divs[j]);
				low = getLowTemp(divs[j]);
			}
		}
 
		weatherTableHTML+=renderCol(dayOfWeek, date,imgSrc, imgText, high, low);
	}
 
	weatherTableHTML+="</tr></table>";
 
 
	document.body.innerHTML = "";
	document.title="It's my custom homepage";
	document.body.setAttribute("style","margin:0px;");
 
	var weatherDiv = document.createElement('div');
	weatherDiv.innerHTML = "<div style='text-align:center;font-weight:bold;font-weight:14px;font-family:Verdana;background-image:url(http://i.imwx.com/web/common/backgrounds/tenday_bkgd.jpg);'>Weather</div>";
 
	var newElement = document.createElement('div');
	newElement.setAttribute("style","text-align:center;vertical-align:center;font-weight:bold;font-family:Verdana;");
	newElement.innerHTML = weatherTableHTML;
 
	weatherDiv.appendChild(newElement);
	document.body.appendChild(weatherDiv);
 
	var newsDiv = document.createElement('div');
	newsDiv.innerHTML = "<div style='text-align:center;font-weight:bold;font-weight:14px;font-family:Verdana;background-image:url(http://i.imwx.com/web/common/backgrounds/tenday_bkgd.jpg);'>News</div>";
	addWebRSSFeed(newsDiv, 7885);
	addWebRSSFeed(newsDiv, 7887);
	addWebRSSFeed(newsDiv, 7888);
	addWebRSSFeed(newsDiv, 7889);
 
	document.body.appendChild(newsDiv);
    }
});
 
function addWebRSSFeed(parentDiv, feedId){
	var feed = document.createElement('iframe');
    	feed.setAttribute("src", "http://www.webrss.com/get_mysite.php?mysiteId="+feedId);
    	feed.setAttribute("width","300px");
    	feed.setAttribute("height","400px");
    	feed.setAttribute("frameborder","0");
    	parentDiv.appendChild(feed);
}
 
function getArrayOf(parentNode, tdClassName){
	var tdWraps = new Array(0);
	var divArrays = parentNode.getElementsByTagName("div");
	for(var i=0;i<divArrays.length;i++){
		if(divArrays[i].getAttribute("class")==tdClassName){
			tdWraps[tdWraps.length] = divArrays[i];
		}
	}
	return tdWraps;
}
 
function closeImgTags(string){
	var updatedString = "";
	var lastIndexOfImg=0;
	var endBracketIndex = 0;
	while((lastIndexOfImg=string.indexOf("<img ", lastIndexOfImg+1)) != -1){
		updatedString+= string.substring(endBracketIndex, lastIndexOfImg);
		var endBracketIndex = string.indexOf(">", lastIndexOfImg);
		updatedString+= string.substring(lastIndexOfImg, endBracketIndex)+"/";
	}
	updatedString+= string.substring(endBracketIndex);
	return updatedString;
}
 
function getDayOfWeek(tdDateDiv){
	return tdDateDiv.getElementsByTagName("a")[0].firstChild.textContent;
}
 
function getDate(tdDateDiv){
	return tdDateDiv.getElementsByTagName("p")[0].lastChild.textContent;
}
 
function getImageSrc(tdForecast){
	return tdForecast.getElementsByTagName("img")[0].getAttribute("src");
}
 
function getImageText(tdForecast){
	return tdForecast.getElementsByTagName("p")[0].lastChild.textContent;
}
 
function getHighTemp(tdTemps){
        return tdTemps.getElementsByTagName("strong")[0].textContent.replace(/==deg==/g,"&deg; F");
}
 
function getLowTemp(tdTemps){
        return tdTemps.getElementsByTagName("p")[0].lastChild.textContent.replace(/==deg==/g,"&deg; F");
}
 
 
function renderCol(dayOfWeek, date,imgSrc, imgText, high, low){
        return "<td width='10%' align='center'>"+dayOfWeek+","+date+"<div style='background-color:white;width:81px;height:75px; border: 1px solid #F0EBD5;'><br/><img src='"+imgSrc+"' border='0'/><br/></div>"+imgText+"<br/>"+high+", "+low+"</td>";
}

After reading this post, you may ask why build this when there are sites that offer this kind of feature for free (Eg., Google, Yahoo, BBC, etc.,). My answer to that would be ->This customization is being done on the client side. You have more flexibility in terms of what content to display and from what source. Can you imagine iGoogle giving you a widget to read your Yahoo emails? or BBC presenting a news widget on its portal the content of which comes from Times of India? or how easily can you convince Google to build a 10-day weather forecast widget for you on iGoogle?

Playing around with Greasemonkey

Friday, December 26th, 2008

Greasemonkey is an extension for firefox which allows users to write custom scripts that can change the UI and behavior of web-pages.

I tried writing a small script that would change the layout of an RSS-feed aggregator site->www.waywework.it. Here is a screen shot of how it looks after applying the custom Greasemonkey script.
Modified layout of www.waywework.it

And here is the script:

 
// ==UserScript==
// @name           WayWeWork.it
// @namespace      http://amarphadke.com/userscripts/waywework.it
// @include        http://waywework.it/
// ==/UserScript==
 
var blogsAndPosts = new Array();
var contentDivCollection = document.evaluate(
    "//div[@class='left left_column']",
    document,
    null,
    XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
    null);
if(contentDivCollection.snapshotLength==1){
	document.write("<html><head>");
	document.write("</head><body bgcolor='#F1F4E2'>");
 
	document.write('<div align="center"><img src="http://waywework.it/images/logo.png?1227062722"/></div>n');
 
	var contentDiv = contentDivCollection.snapshotItem(0);
	var currentPost = '';
	for(var i=0;i<contentDiv.childNodes.length;i++){
		if(contentDiv.childNodes[i].nodeName == "H1" ){
			currentPost = contentDiv.childNodes[i].innerHTML;
		}
 
		if(contentDiv.childNodes[i].nodeName == "H2" ){
			if(contentDiv.childNodes[i].childNodes.length==2){
				var currentBlog = contentDiv.childNodes[i].childNodes[1].innerHTML;
				var currentBlogURL = contentDiv.childNodes[i].childNodes[1].href;
				currentPost+= " ("+stripLastChar(contentDiv.childNodes[i].childNodes[0].textContent)+")";
 
				currentBlog = (currentBlog+"").replace(/>s*</a>/g,'>Blank</a>');
				currentPost = (currentPost+"").replace(/>s*</a>/g,'>Blank</a>');
 
				addToArray(currentBlog, currentBlogURL, currentPost);
				currentPost = '';
			}
 
		}
	}
 
	blogsAndPosts = blogsAndPosts.sort();
	document.write("<div style='padding-left:150px;'><ul>n");
	for(var i=0;i<blogsAndPosts.length;i++){
		var blogsToPost = blogsAndPosts[i];
		if(i>0){
			document.write("<br/>");
		}
		document.write("<li><a style='font-weight:bold;color:black;font-family:Verdana,Tahoma;text-decoration:none;font-size:14px' href='"+blogsToPost[1]+"'>"+blogsToPost[0]+"</a>");
		document.write("<ul>");
		for(var j=2;j<blogsToPost.length;j++){
			document.write("<li><div style='font-family:Tahoma,Verdana;font-size:12px;text-decoration:none;'>"+blogsToPost[j]+"</div></li>");
		}
		document.write("</ul>");
		document.write("</li>");
	}
	document.write("</ul></div>");
 
	document.write("</body></html>");
	document.close();
}
 
function stripLastChar(string){
	return string.substr(0, string.length-3);
}
 
 
function addToArray(blog, blogURL,post){
	if(blogsAndPosts.length==0){
		addBlog(blogsAndPosts, blog, blogURL, post);
	}
	else{
		var found = false;
		for(var i=0;i<blogsAndPosts.length;i++){
			var blogsToPost = blogsAndPosts[i];
			if(blogsToPost[0] == blog){
				blogsToPost[blogsToPost.length] = post;
				found = true;
				break;
			}
		}
		if(!found){
			addBlog(blogsAndPosts, blog, blogURL, post);
		}
	}
}
 
function addBlog(blogsArray, blog, blogURL, post){
	if(blog == '' || post == ''){
		return;
	}
	var blogsToPost = new Array();
	blogsToPost[0] = blog;
	blogsToPost[1] = blogURL;
	blogsToPost[2] = post;
	blogsArray[blogsArray.length] = blogsToPost;
}

Next step, try and see if we can build a custom browser homepage(about:blank) with a Google Searchbar, Yahoo mail and weather.com weather.

Modal dialog on a web page

Thursday, May 8th, 2008

You are developing a web application that requires the user to take an action before doing anything else on the web page. Eg.,

  1. Click to keep session from being timed out.
  2. Confirm changes on the web page.
  3. Rate a product displayed on the web page.
  4. Accept clauses that pertain to certain actions on the web page.
  5. Forced reading of administrative messages.

What do you do? The only modal dialog options supported by the html standard today are alert, confirm and prompt. An alert dialog box displays a message with an “OK” button, Confirm displays a message/question with “OK” and “Cancel” buttons. Prompt displays a message with an input box for taking user input. The look and feel of these built-in dialog boxes is controlled by the browser. You cannot embed all the html tags/styles within these dialog boxes to make it look a part of your application.

There are a few workarounds available that are not part of the standard today. One of the popular ones is using a set of div tags containing a semi-transparent png image file that covers the entire web page. The modal dialog is then presented on top of this div. Just do a google search for “modal dialog box” . One of the sites that i found useful was http://javascript.about.com/library/blmodald1.htm . Another one exists here http://www.eggheadcafe.com/articles/javascript_modal_dialog.asp

If you are developing an application only for the Internet Explorer web browser (does anyone do that anymore?), you could make use of window.showModalDialog(url) which is IE-specific. It displays the content of a web page as a new modal window on top of the current web page.

In my personal opinion, its high time that the html standard came up with a consistent way to display modal dialog boxes so that the poor web developer does not have to come up with workarounds. This will save a lot of time and redundant scripting.

UI Design and Web developer

Sunday, April 27th, 2008

Many Web Projects today have a UI designer to make websites more appealing and easy to navigate. The way these UI designers typically work is by creating mock-ups of web pages. These mock-ups are typically created using a WYSIWYG tool which can add a lot of redundant html, js and css content.

What happens next is that the web developers merge these mock-ups with the actual script. They either lay this mockup on top of the raw page that is being scripted or script on top of the mockup. In either case, the UI content is left untouched. While this may look simple and uncomplicated there could be a few issues.

The first and foremost issue is with cross browser compatibility. Not all UI designers check their mock-ups with different browsers. This to an extent is also dependent on the systems requirement for browser compatibility. The poor Web developer then has to google around for making the given design cross browser compatible.

A lot of times, due to the redundant html, js and css content, the UI script becomes very difficult to understand. Web developers start guessing about where they should make a change so that it appears correctly on the page. What follows is a series of trial and error cases. Each case taking up valuable time. This could be especially painful where the data comes from a backend system thats not running locally.

At times, it so happens that the mock up takes the liberty of knowing the static data in advance. A typical example of this would be 2 separate tables with each row(in those 2 tables) containing partial information about the same record. This sort of situations become difficult to handle and generally introduce performance issues. They could also drive the developer crazy while trying to get the same look and feel as the mockup.

In my opinion, to effectively handle these issues, the developer should start taking ownership of the html, js and css content once its designed. It should be treated similar to code and refactored whenever it becomes redundant or complex.The idea should be to get the UI to look the same. Not to maintain the same html, js or css files as when the mock-ups were designed.This may seem like taking more time but in reality it does not.