"Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the universe trying to build bigger and better idiots. So far, the universe is winning."

Tuesday, August 18, 2009

Sticky Notes On Your Desktop! Tray Buddy v2.0 Released.

Tray Buddy is a simple FREE Desktop Notes application developed for the Microsoft Windows (Windows 2000 and above) platform under the GNU GPL.

NOTE17 Click here to get it!

Tray Buddy lets users keep sticky notes on the desktop. See illustration below.

Enjoy!

Monday, June 8, 2009

Fix the Fox : 3 Hacks to Reduce Firefox Memory Consumption

Being a web developer my daily work routine involves heavy browser usage. Since I also need to test web pages being developed for cross browser compatibility, I use different browsers too. i.e. Microsoft Internet Explorer, Opera, Safari, Google Chrome and ofcourse my favourite - Mozilla Firefox.

Having used these browsers side by side for a long time I noticed that firefox tends to use up a lot of memory when you use it continuously for a long period of time. I was aware of the memory leaks that plagued firefox from the start hence I continued to ignore this small issue of high memory consumption and kept closing tabs regularly to free up memory thinking it would make a difference. Well.... I was wrong. I was so startled that I almost fell off my chair one day when I saw in the MS Task Manager that FF was using more than 100 MB of memory. For a user like myself on a PC which had a total of only just 512 MB of memory, this was an utterly ridiculous predicament to be in. On top of that FF wasn't using many add-ons at the time and it had not loaded any flash movies or anything like that either. Hence it was a "scratch my head" moment :D

As always I turned to Google for help. After about 10 minutes of googling I managed to come across 3 easy steps to make FF run like the wind. And you know what? They really did the trick for me :D So here's what you do,

1. browser.sessionhistory.max_entries

Firefox saves the last 50 visited websites of a single session in memory which means that it could add up quickly if you visit lots of content filled websites. The reason behind this is that it is faster to access an already visited website if it still resides in memory instead of loading it from disk cache or from the server. But 50 is a little too much don't you think? Hence if you are a user like me with little memory but browses a lot of different websites during a browsing session, yeah it sucks to be you... unless you do the following,

  • Type about:config in the Firefox address bar and press Enter
  • Search for the term browser.sessionhistory.max_entries
  • Right-click the entry, select modify from the list and change it to a lower value. (5 works for me)

2. config.trim_on_minimize

Wouldn't it be nice if FF released unused memory when you minimize it? Makes sense doesn't it. Well by default FF does not do it. So you've got to tell FF to do so as follows,

  • Type about:config in the Firefox address bar and press Enter
  • Right click on the tab and go to New > Boolean.
  • Type config.trim_on_minimize and set it to true.

Now whenever you minimize firefox, it’ll reduce the amount of ram it takes up.

3. browser.cache.memory.capacity

When images are loaded, they can be cached so they don't need to be decoded or uncompressed to be redisplayed. This preference controls the maximum amount of memory to use for caching decoded images and chrome (application user interface elements). Firefox dynamically assigns memory cache usage. To reduce memory consumption, you could specify a lower value for the memory cache capacity yourself as follows,

  • Type about:config in the Firefox address bar and press Enter
  • Right click on the tab and go to New > Integer.
  • Type browser.cache.memory.capacity and set it to a value mentioned below. These values depend on the amount of your total RAM. Yes you can experiment with lower than default values too.
Physical RAM Memory Cache (in KB)
32 MB 2048
64 MB  4096
128 MB  6144
256 MB 10240
512 MB 14336
1 GB 18432
2 GB  24576
4 GB  30720
8 GB and up  32768

Also Note:

    • The browser.cache.memory.enable value must be true for this preference to take effect. (This is true by default)
    • Lowering this value causes less memory to be used but also increases the load time of previously visited pages and dialogs. Raising it has the opposite effect.
    • To view current memory cache usage, put about:cache?device=memory in the Location Bar and press Enter.
    • This preference does not exist by default in Mozilla applications other than Thunderbird.

Now restart your fox and let it run like the wind :D

Hope this helps.

Monday, March 9, 2009

ASP.NET AJAX : Update Panels and Register Script Woes

Recently I was working on a composite control which required me to register some javascript snippets via the normal method in ASP.NET i.e. by calling the following method.

Control.Page.ClientScript.RegisterStartupScript()

The objective of the javascript snippet was to hide the composite control when a certain link button was pressed. All worked fine until I placed the control inside an UpdatePanel in which case the javascript didn't get fired at all. After a little bit of googling I found out that when using ASP.NET AJAX controls, a totally different approach was necessary. I quote Rick Strahl

"The problem is that Microosft decided in MS AJAX Beta to go with a completely separate script generation engine which is driven through the ScriptManager control. The MS Ajax ScriptManager mimics many of the ClientScript object’s methods, but provides them as static methods (thankfully! without that we’d be really screwed)."

Hence registering client scripts via Page.ClientScript.RegisterStartupScript() or Page.ClientScript.RegisterClientScriptBlock() won't work. Instead use the following,

ScriptManager.RegisterStartupScript() or ScriptManager.RegisterClientScriptBlock()

to register the client javascripts when using ASP.NET AJAX UpdatePanels. The major drawback to this method is that a ScriptManager actually has to be present on the page for it to work. So what's the big deal with that you may ask...

Well for one thing, this decision taken by Micro$oft to adopt a new script engine totally breaks code in legacy controls which were developed prior to ASP.Net AJAX and prevent them from being used inside UpdatePanels. It gets even worse... How? Just imagine.... What if we have to use the same composite control with and without the UpdatePanel in the same application? Bummer! You dont want to end up using a ScriptManager on all pages now do you?

Thank god for workarounds :D Here's what Rick Strahl did to work around it by developing a wrapper class to probe if a ScriptManager was present or not and then register the script via the appropriate method.

 

Hope this helps.

Tuesday, November 18, 2008

Search Engine Optimization : The Dos and the Donts

Search Engine Optimization is not an exact science. However the following methods can be utilized to increase the possibility of your site's pages to be listed among the topmost items in a search engine such as google or msn.

If it is important, it should be in text
Search engines only index text. So important content should be textually present in your pages. Placing such information in images and flash will not get them indexed. Although there maybe some exceptions to that rule as SEs are getting more intelligent and extending indexing capabilities towards other file types such as pdfs, it would not be wise to depend on that fact.
If a page doesn't contain at least 250 words, some search engines won't bother to index it. It's preferable to have 500 words or more if possible, on a page. You'll get better results from the search engines if you have more text.
Text should be as close to the beginning of the page as possible, because there is a limit to how far down into a page a search engine spider will go to try to find text. It is generally agreed that this is 3kb. That's only about 80-100 lines of code! So make sure your text comes before some really long JavaScript menu in the code for the page, or else the search engine won't make it far enough into the page to index the text.

Optimize more than one page
Just optimizing the site root page is not enough. Other pages should be optimized accordingly too.

A good domain name is half the battle
Pick a domain name which would include at least part of what the users will be searching for. i.e. if your site is renting cars in a city called acme then the domain name should be something like www.acmecarrental.com.

Pick a good web hosting company
Free website hosting is usually bad for search engine rankings. There are several reasons for this. One would be that you are often not in control of the domain name you are given. Free hosts often place your site inside its own frame or place adds or annoying popups on your site. For more details click here.
Another thing to consider is whether your web hosting company host spammers or pornographic sites which have a great potential to be banned by SEs. If this is the case then your site may also be banned for good by the SEs due to "association".

Static IP
The website should have its own dedicated static ip address. Big hosting companies typically use "dynamically assigned" IP addresses. This means that when someone types in your URL into his browser, the HTTP request is presented to your hosting company's server, which quickly assigns an IP address to your website files on its server and connects the visitor to your files. For some search engines this is a total turnoff.

Keywords
Figure out the right keywords for each page to be optimized and place them inside the <title> and <meta> tags within the <head> tag. Ofcourse the title of the page should make sense as it is displayed but you can use the meta tags to specify more key words. However the keyword meta tag has become obsolete now since major SEs like google completely ignores it. Yet it doesnt hurt to have them inside the pages all the same.

i.e. <meta name="keywords" content="acme,cars,rent">

The <meta> description tag is a description of the page. This tag should describe the specific page it is on, not the whole website. This is the description of the page that shows up at the search engine when someone is lucky enough to find this page in his search.

i.e. <meta name="description" content="Toyota Prius rental package,car features,fee">

CAUTION : Don't repeat individual key words more than twice in any one meta tag because that can get a site banned from search engines
The <title> tag must contain the main keyword phrase for which you are optimizing that page. Google in particular places heavy emphasis on what is in your page's <title> tag. So does MSN. IMHO the <title> tag is the most important tag which can be used to get the pages indexed so get it right.
In addition, the same keywords should also be present in the body (content) of the document. It also seems to help to put the key word phrase near the end of the text, too. Search engines use a factor called "key word density" to determine how relevant that key word you're using is to the page. If it shows up a few times in the text, it is more "dense". Key word density is a good thing, up to a certain point.

Page Headings
Put keywords inside headings using the <h1>, <h2>, <h3>, etc. tags. Search engines will look for and index headings when they index pages. Some people detest using headings because they tend to be ugly. You can easily bypass this using a simple inline style command, like this:
<H1 style="margin-bottom: 0px; font-size: 12px;">

Images and Alt tags
Images on a web page should contain a little text description that only shows up when you move your mouse over the picture. That little description is called an "ALT tag". These ALT tags are necessary since some SEs index them. Hence be sure to include keywords in them. This will also help SEs index the images in your website.

Anchor tags
Hyperlinks in your site will utilize anchor tags. You can include keywords inside these tags too. i.e. <a href="prius.html" title="Toyota Prius Rental Package"> Toyota Prius </a> These "titles" for the anchor tags get indexed by the search engines.

HTML validation
Validate the page html to ensure that your html code conforms to standards as much as possible. The more the conformance the easier it is for the SE crawlers to go through your page. You may use online tools such as the one found here to do this.

Manual SE registration
The site should be submitted to (registered with) the search engines (like Google and MSN) and directories (like Yahoo! and the ODP). Most of these are free.

Links to your site
Go out and find as many other websites as possible that might have an interest in linking from their site to yours, contact them and convince them to set up such links. The more links there are to your site, the more relevance it will get in most SEs.
i.e. Google Page Rank
Page rank is Google's way of measuring your rank in terms of links to your site from other sites, based on both quantity and quality. Google page rank varies from 0 (terrible) to 10 (ideal). If you have lots of links from sites with low page ranks, they will mean very little to your page rank, whereas even a few links from other sites with good page ranks can make a difference. That doesn't mean you should cancel existing links. They still have value.


I'm sure there are many more means by which you can do SEO. Please take some time to comment on them too.

Hope this helps.

Wednesday, October 15, 2008

Postgre SQL : How to List All Databases on a Postgre Server

In a Postgre SQL Server, the available database list is kept in a table called 'pg_database'. This table can be found in all databases in the server and is shared among all databases in a cluster.


The pg_database table contains the following columns,

NameTypeReferencesDescription
datnamename Database name
datdbaint4pg_shadow.usesysidOwner of the database, initially who created it
encodingint4 Character/multibyte encoding for this database
datistemplatebool If true then this database can be used in the "TEMPLATE" clause of CREATE DATABASE to create the new database as a clone of this one.
datallowconnbool If false then no one can connect to this database. This is used to protect the template0 database from being altered.
datlastsysoidoid Last system OID in the database; useful particularly to pg_dump
datvacuumxidxid All tuples inserted or deleted by transaction IDs before this one have been marked as known committed or known aborted in this database. This is used to determine when commit-log space can be recycled.
datfrozenxidxid All tuples inserted by transaction IDs before this one have been relabeled with a permanent ("frozen") transaction ID in this database. This is useful to check whether a database must be vacuumed soon to avoid transaction ID wraparound problems.
datpathtext If the database is stored at an alternative location then this records the location. It's either an environment variable name or an absolute path, depending how it was entered.

Hence to list the database names, use the following query

SELECT datname FROM pg_database
Simple enough! So what's the problem you may ask. :)

When you are trying to get a list of available databases, chances are you do not know the name of any database in that server to begin with. (Otherwise why would you run a query like that dah!). So now ask yourself against which database do you execute the query? Get the point?

That is where the default database comes in. From Postgre 8.1 onwards the server automatically creates a default database called 'postgres' hence the query can be executed against this.

In prior releases, the database 'template1' was used both as a default connection for utilities like createuser, and as a template for new databases. This caused CREATE DATABASE to sometimes fail, because a new database cannot be created if anyone else is in the template database. From v8.1, the default connection database is postgres, meaning it is much less likely someone will be using template1 during CREATE DATABASE.

Hence the template1 database can also be used to run the query if you need compatibility for all versions.

Hope this helps.

Thursday, October 2, 2008

Crystal Reports: How to Share Data between Main Report and Sub Reports

To share data between a Report and its sub reports in Crystal, you have to use shared variables.

1. Create a formula field within each sub report which will contain the shared variable declaration and how it is used. The shared variable declaration (name and type) should be the same in each field. i.e.

Shared numberVar totalPoints;
totalPoints := (leadsCountPerMonth/64) * 800;
2. Now Create a formula field which will also contain the same declaration of the shared variable in its definition. Then return the value of the shared variable as follows i.e.
Shared numberVar totalPoints;
totalPoints;
The most important thing to remember when using shared variables is that Crystal Reports must first evaluate the formula where the value is stored (in this case sub reports) before evaluating the formula that retrieves the shared variable (main report).

i.e. Passing a grand total from the sub report to do a calculation in the main report will have you place the @MainReportFormula in a main report section that is beneath the section containing the subreport (i.e. Main Report Footer)


Hope this helps.

Friday, September 26, 2008

MS SQL Sever: How to SELECT TOP @RowCount

MS SQL Server is sooo picky about what it lets developers use with statements such as select, insert, update, etc. I came across this issue when trying to select the top X number of rows from a table using TSQL. The X here is a variable value. Hence the first solution to popup in to your mind would be to

SELECT TOP @RowCount
FROM SomeTable
Well I'm here to tell you all that it doesn't work hehe. :p Apparently MS SQL Server doesn't allow variables to be used in that way along side statements such as select. The same goes for a scenario where you'd want to have a variable WHERE clause.
SELECT [Col1], [Col2]
FROM SomeTable
WHERE @VariableWhereClause
One solution for this is to make the whole query a string and use the EXEC statement to execute the it as follows,

SOLUTION 1
DECLARE @QueryString VARCHAR(MAX)

SET @QueryString = 'SELECT TOP ' + @RowCount + ' FROM SomeTable'

EXEC @QueryString
If @RowCount is not a VARCHAR variable (which it probably won't be) you may need to cast it into varchar before concatenation.

Yeah! I know it looks dirty. So this is what I prefered to do instead,

SOLUTION 2
SET ROWCOUNT @RowCount
SELECT * FROM SomeTable
SET ROWCOUNT 0
SET ROWCOUNT tells the server to stop processing after the number of rows specified by the @RowCount is met. Hence returning only that number of rows. But Remember to SET ROWCOUNT 0 immediately afterwards so that this action will be reset.

Hope this helps.

Monday, September 8, 2008

Crystal Reports : The Mystery of the Invisible Chart

Crystal reports has a habit of giving developers headaches. But when you don't have anything better out there, you tend to stick to what you've got. My latest problem with Crystal Reports arose when I inserted a Chart into a report. Since it was a web based project (ASP.Net 2.0) I was kind of expecting for things to go wrong with it.

The problem was that the chart was not being rendered in the browser at all, but when I exported it to PDF, presto! there it was. So I went over the page source using the browser and found out that the url to the chart image was a little something like,
"http://localhost:49573/CrystalImageHandler.aspx?dynamicimage=imagename.png"

So what or where was this mysterious CrystalImageHandler.aspx. Enter Google as always :)
It seemed that I was missing the following line in the 'httpHandlers' section of my web.config file.

add path="CrystalImageHandler.aspx" verb="GET" type="CrystalDecisions.Web.CrystalImageHandler, CrystalDecisions.Web, Version=10.2.3600.0, Culture=neutral, PublicKeyToken=692fbea5521e1304"

This line is added automatically when you use the visual studio designer to drag and drop the report viewer. My project was using a Composite Control which contained the CrystalReportViewer in it and to add it, as I didn't use the designer, the line did not get added automatically.

After adding the line, the chart got rendered perfectly. So remember to add the CrystalImageHandler line manually if you do not use the VS designer.

Hope this helps.

Thursday, September 4, 2008

Displaying a Modal Browser Window via JavaScript

A number of browsers (for example Netscape 4 or any version of Opera up to and including version 9) do not allow you to make a popup window modal at all. There is simply no command that you can use that will make a window modal in those browsers.

There are two groups of browsers that do allow you to create modal windows and each uses a different method to do so.

Internet Explorer adds an entirely new method to the window object to open a modal window - window.showModalDialog(). If you use that to try to open a modal window then if your visitor is running Internet Explorer then a modal window will be opened. If they are running some other browser then all they will get is a Javascript error.

The Mozilla based browsers (Netscape 6+, Firefox, etc) use a completely different method to declare that the window that is opened should stay in front . They use the normal window.open and just add an extra attribute modal=yes to the list of atributes that define the appearance of the window.

Hence a best of both worlds method would have to be adopted as follows,
function modalWin()
{
if (window.showModalDialog)
{
window.showModalDialog("
modalPage.html","name", "dialogWidth:255px;dialogHeight:250px");
}
else
{

window.open('modalPage.html','name', 'height=255,width=250,toolbar=no,directories=no,status=no, menubar=no,scrollbars=no,resizable=no ,modal=yes');
}
}
Next you may call the modalWin function using a hyperlink like so.

a href="modalPage.htm" target="name" onclick="modalWin(); return false;"...........


Hope this helps.

TSQL : Understanding @@identity, scope_identity(), and ident_current()

The need often arises to retrieve the value contained within an identity column of a newly inserted row just after insertion. Consider the following scenario.

In a MSSQL database there are 2 tables, namely 'InvoiceHeader' and 'InvoiceDetails'.
InvoiceHeader(InvNo(PK/Identity), InvDate, Discount)
InvoiceDetails(InvNo, ItemNo, UnitPrice, Qty)

Assume that the primary key column of the InvoiceHeader table 'InvNo' which is an identity column acts as a foreign key in the table 'InvoiceDetails'. Assume its a 1:M relationship. Hence when a new invoice is issued we need to insert a single record to the 'InvoiceHeader' and possibly multiple records to 'InvoiceDetails'.

Inserting a new row to 'InvoiceHeader' would not be a problem and would look something like this,

INSERT INTO InvoiceHeader (InvDate, Discount)
VALUES ('1-Jan-2008', 100)

Note that 'InvNo' is not specified since its an identity column.

Next we need to add the item details of the invoice to InvoiceDetails table. To do that we need to know the 'InvNo' value of the newly inserted 'InvoiceHeader' row. To retrieve the newly created identity value in such a situation use the SCOPE_IDENTITY() function as follows.

DECLARE @NewInvNo BIGINT

INSERT INTO InvoiceHeader (InvDate, Discount)
VALUES ('1-Jan-2008', 100)

SET @NewInvNo = SCOPE_IDENTITY()

INSERT INTO InvoiceDetails (InvNo, ItemNo, UnitPrice, Qty)
VALUES (@NewInvNo, 1, 99.90, 2)
INSERT INTO InvoiceDetails (InvNo, ItemNo, UnitPrice, Qty)
VALUES (@NewInvNo, 10, 150.00, 4)
Add the above tsql code in a stored procedure and you are good to go.

Although SCOPE_IDENTITY() maybe used most of the time there are two other confusingly similar options in TSQL which are @@identity and ident_current().

Before diving into these two, we need to understand some keywords first.
  • Session - This means the current connection that's executing the command.
  • Scope - This means the immediate context of a command. Every stored procedure call executes in its own scope, and nested calls execute in a nested scope within the calling procedure's scope.
Here are the differences between the three identity retrieval methods,
  • @@identity returns the last identity value generated in this session but any scope
  • scope_identity() returns the last identity value generated in this session and this scope
  • ident_current() returns the last identity value generated for a particular table in any session and any scope
Hope this helps.