Sunday, July 12, 2009

Adding SyntaxtHighlighter javascript with Blogger

Ever heard of "Syntax Highlighter"? This is the javascript framework that converts all the plain text code in my blog (and many other blogs, like Scott Hanselman's) into a neat and highlighted code segments with line number etc - almost like looking at it in an IDE.

You can review, download, and get the main instructions here.

This post is about how to use it with Blogger, Google's blogging engine. Which includes steps in modifying the blogger template of your choosing.

  1. After logging in to you blogger account, go to "Layout" and "Edit HTML" tab. You should see a screen like this:

  2. Copy the url of the hosted files from http://alexgorbatchev.com/pub/sh/current/ for both styles and scripts for ones you want to use and link them as CSS and javascript include in your template, right below the "HEAD" tag. Like this (or you can copy these lines below if you want):

    <link href='http://alexgorbatchev.com/pub/sh/current/styles/shCore.css' rel='stylesheet' type='text/css'/>
    <link href='http://alexgorbatchev.com/pub/sh/current/styles/shThemeDefault.css' rel='stylesheet' type='text/css'/>
    <script src='http://alexgorbatchev.com/pub/sh/current/scripts/shCore.js' type='text/javascript'/>

    <script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushCpp.js' type='text/javascript'/>
    <script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushCSharp.js' type='text/javascript'/>
    <script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushCss.js' type='text/javascript'/>
    <script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushJava.js' type='text/javascript'/>
    <script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushJScript.js' type='text/javascript'/>
    <script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushSql.js' type='text/javascript'/>
    <script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushXml.js' type='text/javascript'/>
    <script src='http://alexgorbatchev.com/pub/sh/current/scripts/shLegacy.js' type='text/javascript'/>


  3. Then scroll all the way down to the bottom of your template, until you see the comment marking for "end-outer-wrapper" and before the closing "body" tag. Here you will need to initialize the Syntax Highlighter by executing several javascript lines. If this is the first time you've used Syntax Highlighter, you probably won't need the last line ("dp.SyntaxHighlighter ..."), since it is really code to enforce backward compatibility with previous version of Syntax Highlighter marking.

    <script language='javascript'>
    SyntaxHighlighter.config.bloggerMode = true;
    SyntaxHighlighter.all();
    dp.SyntaxHighlighter.HighlightAll('code');
    </script>


So now, how do you use it? It's pretty easy. Read here for for the usage/user's guide. In essence, you will just need to surround your "code" with a "pre" tag and depending on what coding language your code is, you will need to assign certain values to the "pre" tag's attribute. Here are some examples:

// for HTML/XML
<pre class="brush:html">
<UL id=sortable>
<li>one</li>
<li>two</li>
<li>three</li>
<li>four</li>
</ul>
</pre>

// for javascript
<pre class="brush:js">
alert("Hello world");
</pre>

// for C#
<pre class="brush:c#">void dfShipper_DeletingItem(object sender, System.ComponentModel.CancelEventArgs e)
{
northwindDSContext.DeleteObject(dfShipper.CurrentItem);
northwindDSContext.BeginSaveChanges((asyncResult) =>
{ northwindDSContext.EndSaveChanges(asyncResult); }, null);
}
</pre>

-- read more and comment ...

Wednesday, June 17, 2009

Twitter via Mobile Devices

You're on Twitter and use a mobile device? Here is a list of mobile device applications (obviously not exhaustive) that will hook you up to Twitter:

Windows Mobile:
Twikini
ceTwit
TwitToday

Blackberry:

Twitterberry
Blackbird
Twibble

iPhone (get from iTunes):
TweetDeck for iPhone
Tweetie
Twitterific
Twinkle

You can follow me on Twitter: http://www.twitter.com/setiabud -- read more and comment ...

Monday, June 8, 2009

jQuery UI Sortable with TABLE

Drag and drop sorting on a web application? This thought is so far fetched several years ago - and now everybody is or can do that easily with jQuery. Looking at the tutorials and documentation in jQuery's website, it lays out a simple method to call to make our list to become sortable.

     $(function() {
        $("#sortable").sortable();
        $("#sortable").disableSelection();
    });
With our corresponding HTML:
     <ul id=sortable>
        <li>one</li>
        <li>two</li>
        <li>three</li>
        <li>four</li>
    </ul>
Here is a demo on how that works.
Now what if we are using TABLE instead of UL or OL??Easy - Use TBODY tag. So using the example above, let's convert the list into a table:
     <table id=anothersortable>
        <tbody class=content> 
            <tr><td>one</td></tr>
            <tr><td>two</td></tr>
            <tr><td>three</td></tr>
            <tr><td>four</td></tr>
        </tbody>
    </table>
Then our jQuery to be as such:
     $(function() {
        $("#anothersortable tbody.content").sortable();
        $("#anothersortable tbody.content").disableSelection();
    });
Click for demo for the simple table.You can even make this having sub-sort - or with children sorting. Like this:
     <table id=subsortsortable>
        <tbody class=content>
            <tr><td>one</td></tr>
            <tr><td>two</td></tr>
            <tr><td>
                <table><tbody class=subcontent>
                    <tr><td>three.one</td></tr>
                    <tr><td>three.two</td></tr>
                </tbody></table>
            </td></tr>
            <tr><td>four</td></tr>
        </tbody>
    </table>
Adjust our jQuery to be as such:
     $(function() {
        $("#subsortsortable tbody.content").sortable();
        $("#subsortsortable tbody.content").disableSelection();
        $("tbody.subcontent").sortable();
        $("tbody.subcontent").disableSelection();
    });
Click for demo of this one. UPDATE: Viewer Coolboy in his comment below pointed out that viewing in IE7 may sometimes introduce unexpected behavior in the y axis. I can replicate the problem and have found a solution or a work around for it by using "handler". Using handler, you basically designating a "dragging" point, instead of using the whole row as to drag. So in using handler, our code changes a little bit in both HTML and javascript. Here is the updated HTML:
     <table id=subsortsortable>
        <tbody class=content>
            <tr><td><label class="levelonehandle">X</label></td><td>one</td></tr>
            <tr><td><label class="levelonehandle">X</label></td><td>two</td></tr>
            <tr><td><label class="levelonehandle">X</label></td><td>
                <table><tbody class=subcontent>
                    <tr><td><label class="leveltwohandle">X</label></td><td>three.one</td></tr>
                    <tr><td><label class="leveltwohandle">X</label></td><td>three.two</td></tr>
                </tbody></table>
            </td></tr>
            <tr><td><label class="levelonehandle">X</label></td><td>four</td></tr>        </tbody>
    </table>
Then our javascript as such:
     $(function() {
        $("#subsortsortable tbody.content").sortable({
                handle: ".levelonehandle"
            });
        $("#subsortsortable tbody.content").disableSelection();
        $("tbody.subcontent").sortable({
                handle: ".leveltwohandle"
            });
        $("tbody.subcontent").disableSelection();
    });
-- read more and comment ...

Wednesday, June 3, 2009

How to Do Sharpening in Photoshop

Why do we need "sharpening"? Well, you actually don't. But, given 2 identical photos, you will see that the one that is sharpened will be able to identify that it seems to be better focused and "sharp". This, a lot of times enhance the photographic viewing experience and create a more detailed perception of the picture.

Let's look at an example:
Original photo (cropped)


Here is the same image after sharpening (and color correction)
Pay attention to the pointy tip of the flower and the creases - and you can see a lot more if you view large (by clicking on the images).

The sharpening technique I used is called "edge sharpening". There are a lot of variety and settings that people use for this kind of sharpening (click here for Google search result for "edge sharpening"). There are other types of sharpening, lab-sharpening or just plainly using the unsharp mask. I am not going to discuss those methods in this post, but only going to focus on edge-sharpening.

Here is the step-by-step process that I usually take (you can make this into an action if you want) - I am using Photoshop CS and I am assuming that it will be similar in the newer versions:


  1. Open you image, crop, adjust levels, curve, etc etc.
  2. Create a new channel: Go to channel palette, click the new channel icon on the bottom. Put a name on your channel, call it "Sharp Channel" (or whatever you want). In PS CS, it will create a new channel and focus on this new channel (black screen/image). Still on the channel palette, click on the RGB channel to focus back on your image.
  3. Select the whole image (Ctrl-A or do SELECT - ALL), and then copy the whole image (EDIT - COPY or EDIT - COPY MERGE)
  4. Paste it on the new channel "Sharp Channel": select the "Sharp Channel" and then paste (EDIT - PASTE). It will display as black and white, which is OK.
  5. Find edges: Make sure the "Sharp Channel" is focused/selected, and then click FILTER - STYLIZE - FIND EDGES
  6. Eventually we are going to make this as a mask. So that means that the black part will affect your image and the white area won't. If you are pretty satisfied with the mask (it is hard to say what is a "good" one at this point - you will just have to experiment), you can skip step 7 and go to step 9 directly. I usually (9 out of 10) always do step 7 & 8 ...
  7. Click IMAGE - ADJUSTMENTS - LEVELS. Try to adjust the levels by moving the triangular slide below the histogram. You basically want to make black to be very black and white to be white. Keep in mind that once we convert this channel into a mask, the black area are the one that will be sharpened. Play with this (unfortunately with trial and error) until you get the desired effect.
  8. If there are details, lots of lines, etc, you may want to introduce a nice smoot transition from the sharpened area to the non-sharpened area. Use Gaussian Blur to do this: FILTER - BLUR - GAUSSIAN BLUR (enter between 1-5 px).
  9. Now click SELECT - LOAD SELECTION. A dialog box will show up and make sure the selected channel is correct and "INVERT" checkbox is checked. Hit OK.
  10. Click "Layers" (you were on "Channels"). The mask from our edge will be super-imposed on our image. You can hide this "blinking selection" by doing this: click VIEW - SHOW - and uncheck SHOW SELECTION.
  11. Now we are ready for sharpening. It is usually helpful to view the image in large view/zoomed in. I would go for 100% zoom/view before start sharpening - so I can see/preview the affect of the sharpening. Click FILTER - SHARPEN - UNSHARP MASK. Try out combination of values in the USM window until you get the desired result. I recommend to keep the threshold to 0 and the radius to be less than 1.5.
There you go - good luck!
-- read more and comment ...

Friday, May 29, 2009

URLRewritter & .NET 3.5 SP1

In one of our project, we encountered this error when we pushed our code to the server:

Validation of viewstate MAC failed. If this application is hosted by a Web Farm or cluster, ensure that configuration specifies the same validationKey and validation algorithm. AutoGenerate cannot be used in a cluster.

Our web server is a stand alone server, not a cluster/web-farm. So after digging around, there are multiple issues that related to our problem that can ultimately cause this error to show up:
1. From here: WebForm starts paying attention to the "action" attribute of the form tag in .NET 3.5 SP 1. So, if they are unmatched, this error will be generated.
2. We use URLRewritter, in which means that our URL in the location bar is different (modified) than the one in the action attribute.
3. In IIS7 or VS 2008 emulator, this problem cannot be replicated

There are also other issues that can cause this error (but they are not related to my project - and you can read about them here and here.

So, how did I solve my problem? I can just simply upgrade my web server to IIS7, but that was not an option when I was working on this. Here are the things that I did:
1. Add event handler to blank out the form action from global.asax.vb file for each request (read here)
2. Change form tag into the one supplied by URL Rewritter (read here)

Those 2 steps provide me with a URL that is identical in my action attribute on the form as well a consistent URL - which mean ViewState is valid!
-- read more and comment ...

Thursday, May 28, 2009

Cascading Drop Down with ASP.NET MVC, jQuery with JSON

Cascading drop down is webform is pretty easy by using postback - or UpdatePanel. In MVC, how would it be done? jQuery comes to the rescue for this problem. Since ASP.NET MVC is able to return a JSON format result, what we need to do then is just calling that Controller's Action via jQuery and then display the drop down. Let's use an example:Say I want to list available counties per state. So the the first drop down will be the state drop down, and based on the selection of the state, the second drop down will display available counties for the selected state.

Here is a snippet of the HTML for our two drop downs, for both states and counties.
 
<%= Html.DropDownList("StateID", Model.StateList) %>
<select name="CountyID" id="CountyID"></select>
So since we need to (re)populate the county drop down for each selection/change of the StateID drop down, we use the .change method in jQuery.
 
$(document).ready(function() {
    $("#StateID").change(function() {    });
});
First, we need to get the selected State and also make sure that the default State selection will also execute and display the list of Counties appropriately.
 
$(document).ready(function() {
    $("#StateID").change(function() {
        var strStateID = "";
        strStateID = $(this)[0].value; // get the selected state id
    })
    .change(); // making sure the event runs on initialization for default value
});
Once we have the state id, we can then call our controller's action to get a list of counties back via AJAX.
 
$(document).ready(function() {
    $("#StateID").change(function() {
        var strStateID = "";
        strStateID = $(this)[0].value; // get the selected state id
        var url = "/MyController/Counties/" + strStateID;
        // call controller's action
        $.getJSON(url, null, function(data) {
            // do something once the data is retrieved
        });
    })
    .change(); // making sure the event runs on initialization for default value
});
With the data in hand, we can (re)build the County drop down with the values returned from the controller.
 
$(document).ready(function() {
    $("#StateID").change(function() {
        var strStateID = "";
        strStateID = $(this)[0].value; // get the selected state id
        var url = "/MyController/Counties/" + strStateID;
        // call controller's action
        $.getJSON(url, null, function(data) {
            // do something once the data is retrieved
            $("#CountyID").empty();
            $.each(data, function(index, optionData) {
                $("#CountyID").append("<option value='"
                     + optionData.CountyID
                     + "'>" + optionData.CountyName
                     + "</option>");
            });
        });
    })
    .change(); // making sure the event runs on initialization for default value});
-- read more and comment ...

Wednesday, May 6, 2009

VMWare Server 2.0 Troubleshooting

Yesterday, my VMWare Server stops working. It did not give me the login screen - so I thought maybe I just need to restart the host agent service under Services. I wrote a post about this here.

So I went to Services, found that the host agent is stopped and I restarted it. Then I got this error:



Aaaarrrrgggh!!! So anyway - I went around, googled it, read the logs, etc etc, and resolved it.

In my case the error was cause by a malformed XML file for VMWare configuration. The XML in my Win XP host is located at "C:\Documents and Settings\All Users\Application Data\VMware\VMware Server\hostd".

I am guessing that when I installed VMWare, I probably chose "allow all users to use it" - and if you picked "allow only me", the xml files probably will reside under your username instead of under "all users".

So what I did was just opening the XML file one by one with an XML editor (I used Visual Studio) until I found the culprit XML file - which in my case was the datastores.xml - then fixed the tags and save.

Then via Services, I restarted the host agent service.


Voila! It works!
-- read more and comment ...

Saturday, May 2, 2009

Investing on Cameras or Lenses?

Every year, there are several DSLRs coming out with new features and functionality that are eclipsing the past years DSLRs. If your have a DSLR and it was from several years ago, you may feel that it is quite obsolete compared to the new ones coming out this year. They are cheaper too!

To illustrate the point, let's say you have a entry level DSLR from Nikon, the Nikon D40 - which is release on December 2006. So a two and a half year old camera. It has big bright 2.5 inches screen, small, 3 focus points, 2 frames per seconds, built in flash, info view, scene modes, etc - for $549. It was the best selling DSLR at some point, the biggest bang for the buck.

So what would you do? Get a new DSLR every year (or every other year)? Or maybe lenses?

I recommend getting several flashes and some light stand and learn how to light via Strobist! Financially, it saves you LOTS of money (spend hundreds instead of thousands of dollars) AND exponentially improve your photographic skill - even with your current camera and lenses!

Now why is that? Or how does it work? Strobist provides tons of "how-to" etc (take lighting 101) and lighting resources (where to buy, reviews, etc). Secondly, most people usually want to get newer lenses because they need "faster" lenses. Faster lens means larger opening to capture more light. The standard kit lens is usually a "slow" lens, where it does not have the ability to capture light as fast as the faster lens. So, lighting/flashes compensate that by adding more light! So if you learn how to light - it will make your equipment to be more useful and all the more, you can do a LOT of things that really not possible without flashes.

Consider also this cost analysis:
Getting a new fast lens probably cost you $1,500. Getting an SB-800 and 2 more SB-600 (so 3 flashes total) will cost you $~700. Throw in a pair or light stand, 2 umbrellas, some gels - ~$200 - brings up the total to $900 - that using Nikon brand, and some nicer brands of stands and umbrellas, etc. So, learn how to light: WORTH IT!!

Now, if the choice is between camera and lenses, then I recommend in investing on lenses instead of camera. Several reasons:

  • All technique aside, it is the lens that contributes more into the sharpness of your picture
  • Beautiful bokeh (rendering of out of focus area) is caused by lens characteristics, not camera
  • Lens holds its value really well, unlike camera
  • Lens controls the aperture - which allow you to produce DoF, bokeh, and shutter speed - and of course the camera control the lens
  • Less need for upgrade for lenses
  • Studying the correct usage of lenses and its characteristics can improve your photographic skills better than camera
So got an older camera and itching for an upgrade? I say hold it. Check out your lens arsenal, and improve them first. In most cases for amateur or serious amateur, what your camera can do is more than enough for our needs. So as long as your camera is compatible with the lenses, stick with it and get a NICE lens instead. Does this mean you should throw away your kit lens? Nah, not really. You can keep it around for most occasions - parties, trips, vacations, etc - where you just want to enjoy the moment instead of being hassled by taking photos and their settings.

So, what do you currently have in your lens line up? I wrote a blog post about this in the past.

I am still using my Nikon D80 and a Nikon film camera - and I am not anticipating for a camera upgrade anytime soon. They are both still do their jobs very very well. But since then, I have upgraded my lens line up to be as such:
  • AF-S 17-35 f/2.8
  • AF-S 24-70 f/2.8
  • AF-S 70-200 f/2.8
BUT, again - my best investment so far are:
  • 2 Bogen Light Stands
  • 2 SB 600s
  • 1 SB 800
  • 1 SB 80DX
  • 2 White Umbrellas
  • 1 Silver Umbrella
  • 1 Multicolor reflector
  • 1 Set of gels
With some of those lighting gear, I made this picture in this post.
-- read more and comment ...

Wednesday, April 8, 2009

EQATEC .NET App Profiler

Someone posted a link to this .NET Profiler tool on Twitter (Twitter FTW!) - so I went and downloaded it and tried it. The tool is called EQATEC Profiler (you can download it here). So what is it?

The way it works it that it rebuild your dlls/exes so when you run it, the tool will be able to profile your .NET method calls and give you a report. Based on the report, you can see which method is the slowest, which one is being called the most number of times, etc. The hope is that based on that data then you can optimize your application to maximize the speed/performance.

When you downloaded the app, it comes with a sample WinForm app. You can use it to see how it works. When it finds the exe/Main() method, it will use it as a starting point when you run it.

My application is an ASP.NET MVC application - so it does not have a Main() method. How do I then profile it?

When you open the profiler, this the screen you will see. Click browse on the top right - and browse to your ASP.NET application bin directory. You can select a dll OR not select any file and just the folder (which will select all dlls in the bin folder). Hit "OK" then make sure all the dlls that you want to profile is checked. Hit the "Build" button on the lower right. Once it's done, you will see that there is a message in the message box saying that it does not find a Main() method. This is OK. Click "Open output folder" on the lower left, and copy all dlls in there back into your original bin folder. Then run your web application (Do NOT run it by hitting F5 - which will overwrite all the dlls from EQATEC. But run it by opening a browser and type "http://localhost:<port>/<appname>". At this point the EQATEC is already attached to your ASP.NET web app process and ready to profile it. So run through your web app (like doing a comprehensive walk-through) and before you are done, click "Run" tab on the EQATEC profiler then hit the "Take snapshot" button. If you are done at this point, you can close your browser. You can take a snapshot anytime you want and they are accumulative - so if you took 5 snapshots, the latest one will have the most comprehensive data out of the 5.

Now highlight the top most (or latest) snapshot you have taken, then click "View" button on the lower right of the "Run" tab. It displays the break-down of all of your method calls during the walk-through. The telemetry includes:

  • "Calls" - which is the number of times the method is being called during the walk-through session
  • "Avg (self)" - which is the average amount of time spent in that method alone in milliseconds.
  • "Total (self)" is the total amount of time spent in the method during the whole time spent in the walk-through.
  • "Total (full)" - the total amount of time spent in the method and all the other methods called by it.
  • "Avg (full)" is the average - "Total (full) / Calls".

From those data, we should be able to see what method takes the longest to execute and optimize it if necessary - OR - why is a particular method is being called hundred of times and accumulate cost over time and maybe we can put caching to reduce it, etc etc.

Overall, I like this tool as it provides me a way to pin-point the bottlenecks in my application and take action accordingly, instead of just guessing or doing a manual measurement - which is painful and tedious.
-- read more and comment ...

Monday, April 6, 2009

Silverlight 3.0, Astoria, LINQ to SQL (3): DataForm CRUD

In last few posts:here and here, I wrote about how to build/prepare the solution, projects, and some ground work in LINQ to SQL and connecting it to ADO.NET Data Service as well as reading data through a Silverlight application using Silverlight 3.0 DataForm.

In this post, I will continue in implementing the Update/Edit, Create/New, and Delete to complete our CRUD.

To do EDIT, it is actually very simple. First you will need to add event handlers. We will need two handlers, but both of them will be reusable for ADD. To add event handlers, all we need to do is start typing our DataForm name and select the event that we want to implement via intellisense and hit tab key twice. It will complete the line for you and create a stub for the event hendler method.

By the way, I changed our binding, so instead of going against Customer, it is against Shipper (both from Northwind db and should exist in the LINQ mapping from the auto-gen code).

static Uri baseUri = new Uri("http://localhost:10403/NorthwindDataService.svc", UriKind.Absolute);
NorthwindDBDataContext northwindDSContext = new NorthwindDBDataContext(baseUri);

public MainPage()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainPage_Loaded);

// new code
dfShipper.ItemEditEnding += new EventHandler<DataFormItemEditEndingEventArgs>(dfShipper_ItemEditEnding);
dfShipper.ItemEditEnded += new EventHandler<DataFormItemEditEndedEventArgs>(dfShipper_ItemEditEnded);
}

void dfShipper_ItemEditEnding(object sender, DataFormItemEditEndingEventArgs e)
{
northwindDSContext.UpdateObject(dfShipper.CurrentItem);
}

void dfShipper_ItemEditEnded(object sender, DataFormItemEditEndedEventArgs e)
{
northwindDSContext.BeginSaveChanges((asyncResult) =>
{ northwindDSContext.EndSaveChanges(asyncResult); }, null);
}

The "BeginSaveChanges" and "EndSaveChanges" are there because our data connection is asynchronous.

For ADD, we need to change "dfShipper_ItemEditEnding" into this:
void dfShipper_ItemEditEnding(object sender, DataFormItemEditEndingEventArgs e)
{
if (dfShipper.IsAddingNew)
northwindDSContext.AddObject("Shippers", dfShipper.CurrentItem);
else
northwindDSContext.UpdateObject(dfShipper.CurrentItem);
}

For DELETE, we add this handler:
void dfShipper_DeletingItem(object sender, System.ComponentModel.CancelEventArgs e)
{
northwindDSContext.DeleteObject(dfShipper.CurrentItem);
northwindDSContext.BeginSaveChanges((asyncResult) =>
{ northwindDSContext.EndSaveChanges(asyncResult); }, null);
}

So our complete code should look like this:
public partial class MainPage : UserControl
{
static Uri baseUri = new Uri("http://localhost:10403/NorthwindDataService.svc", UriKind.Absolute);
NorthwindDBDataContext northwindDSContext = new NorthwindDBDataContext(baseUri);

public MainPage()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainPage_Loaded);
dfShipper.ItemEditEnding += new EventHandler<DataFormItemEditEndingEventArgs>(dfShipper_ItemEditEnding);
dfShipper.ItemEditEnded += new EventHandler<DataFormItemEditEndedEventArgs>(dfShipper_ItemEditEnded);
dfShipper.DeletingItem += new EventHandler<System.ComponentModel.CancelEventArgs>(dfShipper_DeletingItem);
}

void dfShipper_DeletingItem(object sender, System.ComponentModel.CancelEventArgs e)
{
northwindDSContext.DeleteObject(dfShipper.CurrentItem);
northwindDSContext.BeginSaveChanges((asyncResult) =>
{ northwindDSContext.EndSaveChanges(asyncResult); }, null);
}

void dfShipper_ItemEditEnding(object sender, DataFormItemEditEndingEventArgs e)
{
if (dfShipper.IsAddingNew)
northwindDSContext.AddObject("Shippers", dfShipper.CurrentItem);
else
northwindDSContext.UpdateObject(dfShipper.CurrentItem);
}

void dfShipper_ItemEditEnded(object sender, DataFormItemEditEndedEventArgs e)
{
northwindDSContext.BeginSaveChanges((asyncResult) =>
{ northwindDSContext.EndSaveChanges(asyncResult); }, null);
}

void MainPage_Loaded(object sender, RoutedEventArgs e)
{
var query = (DataServiceQuery<Shipper>)
from c in northwindDSContext.Shippers
orderby c.CompanyName
select c;

query.BeginExecute((asyncResult) =>
{
try
{
IEnumerable<Shipper> result = query.EndExecute(asyncResult);
Dispatcher.BeginInvoke(() =>
{
ObservableCollection<Shipper> data = new ObservableCollection<Shipper>();
foreach (Shipper p in result)
{
data.Add(p);
}
dfShipper.DataContext = data;
dfShipper.ItemsSource = data;
});
}
catch (Exception ex)
{
throw ex;
}
}, null);
}
}

-- read more and comment ...

Wednesday, April 1, 2009

Silverlight 3.0, Astoria, LINQ to SQL (2): DataForm CRUD

In the last post, I wrote about how to build/prepare the solution, projects, and some ground work in LINQ to SQL and connecting it to ADO.NET Data Service. Now in this post, I am going to continue in building the Silverlight application using Silverlight 3.0 DataForm.

We will first add the service reference to our ADO.NET Service that we built from our Silverlight application. Then how to hook it up to our DataForm so it will display the first record by default.

1. Add a service reference to the ADO.NET Data Service ("NorthwindDataService.svc"). Run the solution with "NorthwindDataService.svc" as the Start page. Once it loads, copy the URL in the location bar and close the browser.

On the "Northwind.Silverlight" project, right click on "Service References" and click "Add Service Reference". Paste the URL you copy to clipboard into the "Address" field and click "Go". It should then populate the "Services" left pane. Hit OK.


2. Create the XAML. We need to add a DataForm to our XAML. Now, I do not have Expression Blend, so I did mine using Visual Studio. First of all, you need to add a reference to System.Windows.Controls.Data.DataForm. Your XAML should look like this:


<UserControl x:Class="Northwind.Silverlight.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:df="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="450">
<Grid x:Name="LayoutRoot" Background="White">
<df:DataForm x:Name="dfCustomer"></df:DataForm>
</Grid>
</UserControl>

3. Bind the data to the DataForm control. We need to bind the data to our dataform. There are multiple ways on how to go about doing this. In this post I bind the data as soon as the Silverlight application is loading. Or, you can create a button (or whatever event) to initiate the bind. Open "MainPage.xaml.cs" and create a handler method for the "Loaded" event. In this method, basically I will query the data using our data service reference and then bind the result to our dataform.

public partial class MainPage : UserControl
{
Uri baseUri = new Uri("http://localhost:10403/NorthwindDataService.svc", UriKind.Absolute);
public MainPage()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainPage_Loaded);
}

void MainPage_Loaded(object sender, RoutedEventArgs e)
{
NorthwindDBDataContext northwindDSContext = new NorthwindDBDataContext(baseUri);

var query = (DataServiceQuery<Customer>)
from c in northwindDSContext.Customers
orderby c.ContactName
select c;

query.BeginExecute((asyncResult) =>
{
try
{
IEnumerable<Customer> result = query.EndExecute(asyncResult);
Dispatcher.BeginInvoke(() =>
{
ObservableCollection<Customer> data = new ObservableCollection<Customer>();
foreach (Customer p in result)
{
data.Add(p);
}
dfCustomer.DataContext = data;
dfCustomer.ItemsSource = data;
});
}
catch (Exception ex)
{
throw ex;
}
}, null);
}
}
Now, we do we need to do all that? It looks overly complicated for just doing a data binding. Why not just do a LINQ query and then bind the result directly? What's "BeginExecute" for? The answer to all that really boils down to because the call to the data service is being down asynchronously. Therefore, the callback is needed. If you look into the method signature of "BeginExecute", you will see that the first parameter is a callback function.

So let's break it down ... see the line where it does this?

query.BeginExecute((asyncResult) => { ... }, null);
That line basically is executing the query and then upon completion of the query and getting the result back (stuffed into the param "asyncResult"), run this generic method (stuff inside the curly braces).

So let's take a look the callback function (stuff inside the curly braces).

IEnumerable<Customer> result = query.EndExecute(asyncResult);
Dispatcher.BeginInvoke(() =>
{
ObservableCollection<Customer> data = new ObservableCollection<Customer>();
foreach (Customer p in result)
{
data.Add(p);
}
dfCustomer.DataContext = data;
dfCustomer.ItemsSource = data;
});
The first line is the "closer" for the async call and it will return a result as an IEnumerable. Next we then using Dispatch.BeginInvoke (because we are transferring data over web service) get the data one by one and put them into an ObservableCollection. Then after that we bind our DataForm to the ObservableCollection.

So far so good? In the next post: Add, Edit, Delete!
-- read more and comment ...

Tuesday, March 31, 2009

Silverlight 3.0, Astoria, LINQ to SQL (1): Setting Up Astoria & LINQ to SQL

Silverlight 3.0 that just came out during MIX09 has a new control called DataForm. In essence, this control binds to an object/collection and from it you can view, edit, add, delete, page, etc. It looks like having a MS Access display your records through its Form.

Mike Taulty (one of the developers in the Silverlight team) made an excellent video showcasing the features and functionality of the DataForm control in Silverlight. You can watch the video here. There is a good blog post recap by Vikram Pendse.

I am not going to repeat what Mike has shown in the video. But what I am going to show through this post is how to build a DataForm that does CRUD (create, read, update, delete) to a LINQ to SQL data layer via ADO.NET Data Service. This first post in the series is to show how to build/prepare the solution, projects, and some ground work in LINQ to SQL and connecting it to ADO.NET Data Service.

What do you need to follow along? Several things:


For the database, I am going to use Northwind. If you do not have Northwind, you can get it from here.

OK, let's begin.
1. Create a Class Library project for our LINQ to SQL project. Why not just create the LINQ to SQL inside a web project for our data service? Because making it in its own project allow us to reuse it. For instance if I want to build a web Dynamic Data project connecting to the same DB, I can just reference this project in my Dynamic Data project. I called project as "Northwind.DalLinq". Add references to "System.Data.Services" and "System.Data.Services.Client".

2. Create a Silverlight project. I named mine as "Northwind.Silverlight".

When you do this, it will then ask you whether you want to host the Silverlight application inside a new website. This website will host not only our Silverlight application, but also the ADO.NET Data Service that talks to our LINQ to SQL. So, I selected "Web Application Project" and hit OK. You can choose "Web Site" or "MVC Web Project" if you want to - it does not really matter.

Add references to "System.ComponentModel", "System.ComponentModel.DataAnnotation", "System.Windows.Controls.Data", and "System.Windows.Controls.Data.DataForm" in the "Northwind.Silverlight" project. Add references to "Northwind.DalLinq" to the "Northwind.Silverlight.Web" project.

So at this point, your solution should look like this:

3. Create a LINQ to SQL class in "Northwind.DalLinq" project and create all table mappings into the dbml, as well as the stored proc. We are going to use them all in this post, but for simplicity, let's just add all. If you feel you know what you're doing, feel free to be more selective.

4. Create an ADO.NET Data Service item in the "Northwind.Silverlight.Web". Right click on the "Northwind.Silverlight.Web" project, "Add New Item", select "ADO.NET Data Service". I name mine as "NorthwindDataService".

Open up "NorthwindDataService.cs" (the codebehind for the ADO.NET Data Service item we just added) and enable it to bind to our Northwind data context. We do this by filling the generic for the DataService type in the class declaration line.
    public class NorthwindDataService : DataService<Northwind.DalLinq.NorthwindDBDataContext>
Then we also need to indicate which table mapping are going to be available to be accessed. We do this by specifying it in this line:
config.SetEntitySetAccessRule("*", EntitySetRights.All);
Right now that will allow ALL to be accessible. This is OK for our exercise, but in reality we want to allow with caution. So your code should look like this:
namespace Northwind.Silverlight.Web
{
public class NorthwindDataService : DataService<Northwind.DalLinq.NorthwindDBDataContext>
{
// This method is called only once to initialize service-wide policies.
public static void InitializeService(IDataServiceConfiguration config)
{
// TODO: set rules to indicate which entity sets and service operations are visible, updatable, etc.
// Examples:
config.SetEntitySetAccessRule("*", EntitySetRights.All);
// config.SetServiceOperationAccessRule("MyServiceOperation", ServiceOperationRights.All);
}
}
}

5. Make sure that your web application is running on a static port, so our Service Reference will be able to find it later. To do this, right click on the "Northwind.Silverlight.Web" project and click "Properties", go to "Web" tab. In here, we want to make sure our port is static by setting it to "Specific Port" instead of the default "Auto-assign Port". You can use any port number that is available.

Alright, right now we already setup our basic LINQ to SQL mapping from our DB, setting up our Silverlight project along with the web application to host it, and lastly we created a new ADO.NET Data Service item in our web application project.

So let's try it! Set "Northwind.Silverlight.Web" project as the StartUp and set "NorthwindDataService.svc" as the Start Page and hit F5!

Most likely you will get this error:

WHAAATTT???

6. Well, no need to panic ... you can set the error to be verbose and it will give you more information. The error is basically caused by ADO.NET confusion about what is the field that holds the primary key of the table mappings that we did in the LINQ to SQL. If you use Entity Framework, it will do it out of the box, but not LINQ to SQL apparently, so we need to decorate the classes with DataServiceKey attribute. Since the dbml generated by LINQ to SQL contains partial classes of the mappings, we can just simply add our own partial class that match with the generated part.

Add a class to our "Northwind.DalLinq" project and call it "NorthwindDB.Custom.cs". So your solution should look like this:

In that class, we declare our partial classes from our DB mappings decorated with the "DataServiceKey" attribute. Here are several examples on how to do them:
namespace Northwind.DalLinq
{
[DataServiceKey("CategoryID")]
public partial class Category { }

[DataServiceKey("TerritoryID")]
public partial class Territory { }

[DataServiceKey(new string[] { "CustomerID", "CustomerTypeID" })]
public partial class CustomerCustomerDemo { }

// TODO: do the rest of the classes
}
Now, if we try hitting F5 again, we then should get a different result:

So now we have our ADO.NET Data Service ready and in my next post I will show you how to hook it up to the DataForm control to display our data.
-- read more and comment ...

Friday, March 20, 2009

Calculating Business Days

A common problem in making custom scheduler application is figuring out how to schedule future items or to predict the next due date. For an example, let's say you are the programmer in UPS (the shipping company wearing everything in brown) - and let say you want to give an ability for the customers to view that if a shipment is shipped on a certain date, it would take 5 business days after the shipment date to get to their door - let's call this a delivery date (an existing functionality). Simple enough, right? If we are just doing this in our head, it sounds pretty easy. But we need to remember that we must skip holidays and weekends - since UPS is only open and operating on BUSINESS DAYS.

There are actually an easy solution to this (which we will see) and I also made a more optimized solution and compared the performance.

We are going to assume that there is a list (List _holidays) that holds the holiday schedule. So our method will be given a start date and an interval - and it should figure out the next date. Here is the code for the easiest solution.


private DateTime ProjectDateByLoop(DateTime startDate, int interval)
{
DateTime endDate = startDate;
int i = 0;
while (i < interval)
{
endDate = endDate.AddDays(1.0);
if (endDate.DayOfWeek != DayOfWeek.Saturday && endDate.DayOfWeek != DayOfWeek.Sunday && (!_holidays.Contains(endDate)))
i++;
}
return endDate;
}
A very simple code, it basically increment the day one by one and check whether it is a holiday or not or whether it falls on a weekend.

As we see above, it is very very simple and it works accurately. In my example above about UPS, this is probably is the best solution for the problem: simple code, easy to maintain, and works! But, as I look into it, I started to wonder what if the interval is in the range of thousands, or millions? It is very unlikely and may never be used ever - but I felt compelled (with the encouragement of certain people) to find a better way of doing this.

So, with that in mind, I produced this code below.

private static int CountDaysInDates(DayOfWeek queriedDay, DateTime startDate, DateTime endDate)
{
TimeSpan ts = endDate - startDate;
// get number of full weeks within dates
int count = (int)Math.Floor(ts.TotalDays / 7);

// get remainder and adjust
int remainder = (int)(ts.TotalDays % 7);
int numOfDaySinceLastQueriedDay = (int)(endDate.DayOfWeek - queriedDay);
if (numOfDaySinceLastQueriedDay < 0) numOfDaySinceLastQueriedDay += 7;
if (remainder >= numOfDaySinceLastQueriedDay) count++;
return count;
}

private DateTime ProjectDate(DateTime startDate, int interval)
{
DateTime endDate = startDate;

if (interval == 0)
endDate = startDate;
else
{
endDate = startDate.AddDays(interval);
int numOfSaturday = CountDaysInDates(DayOfWeek.Saturday, startDate.DayOfWeek == DayOfWeek.Saturday ? startDate.AddDays(1) : startDate, endDate);
int numOfSunday = CountDaysInDates(DayOfWeek.Sunday, startDate.DayOfWeek == DayOfWeek.Sunday ? startDate.AddDays(1) : startDate, endDate);

int numOfHoliday = _holidays.Count(h => h >= startDate && h <= endDate && (h.DayOfWeek != DayOfWeek.Sunday && h.DayOfWeek != DayOfWeek.Saturday));
int offSet = numOfSaturday + numOfSunday + numOfHoliday;
endDate = ProjectDate(endDate, offSet);
}
return endDate;
}
At first glance, the second code should be much faster, since it does not have any loop at all. But, it is not quite as simple as the first code (even though it is still simple). The method CountDaysInDates is basically for counting how many times a day (like "Monday") occurs within 2 given dates. I use this method to calculate how many Saturdays and Sundays (weekends) to skipped. Why not just count the number of weekends? Because there is a likelihood that the start date is on a Sunday which needs to be skipped and counted only as "half" of a weekend. Secondly, I want the code to be flexible enough that if the algorithm is used to skip only Sundays, it can be modified easily.

The method ProjectDate is a recursive method. In it basically I checked the number of Saturdays and Sundays plus holidays and add them together - and then call ProjectDate again with the new start date (after interval was added) with the new interval (Sundays + Saturdays + holidays).

So ... how are the performance of these two algorithms? Amazingly enough, when they are used with small intervals, the performance difference is negligible in real use. So, I created an iteration for 100,000 times to run both and measure the execution time.


private void TargetDateHarness()
{
int totalPasses = 100000;
int interval = 720;
DateTime targetDate;

System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
sw.Start();
for (int i = 0; i <= totalPasses; i++)
targetDate = ProjectDate(DateTime.Today, interval);
sw.Stop();
TimeSpan ts = sw.Elapsed;
testContextInstance.WriteLine("Non-Loop time: " + ts.TotalMilliseconds + " ms");

sw.Reset();
sw.Start();
for (int i = 0; i <= totalPasses; i++)
targetDate = ProjectDateByLoop(DateTime.Today, interval);
sw.Stop();
ts = sw.Elapsed;
testContextInstance.WriteLine("Loop time: " + ts.TotalMilliseconds + " ms");
}
The result:

Non-Loop time: 2018.7745 ms (2 seconds)
Loop time: 53362.9731 ms (53 seconds)

-- read more and comment ...

Wednesday, March 18, 2009

ASP.NET Lightweight Automation Framework - Add, Edit, Delete (3/3)

In the last post, we discussed about LAF & how to test a page with list on it. We did paging, sort, and simple display. Now in this post I will show you how to do a test for Add/Edit and Delete.

Here is the code for testing Add Customer functionality from the UI. The essence of the code is that after navigating to the Customer List page, we click on the "New Customer" link with an expectation that the new customer form will show (so we verify it). Then after the form shows, fill it in with information and click "Save" button. This then should close the form and redisplay the list with the new Customer in it - so we verify that it is in there. At this point, our Add is already working. I included at the same time, a test for common edit & deletion.


[WebTestMethod]
public void AddCustomer()
{
HtmlPage page = new HtmlPage("");

// navigate to Customer
page.Navigate("/Customer/");

// Verify search result
HtmlTableElement gridView = (HtmlTableElement)page.Elements.Find("CustomerListingTable");
Assert.IsNotNull(gridView);

// click new Customer
page.Elements.Find("a", "New Customer", 0).Click(WaitFor.None);
System.Threading.Thread.Sleep(1000);

// verify new form exists
Assert.IsTrue(page.Elements.Find("CustomerDetail").IsVisible());
Assert.IsTrue(page.Elements.Exists("Cancel"));

// enter information
page.Elements.Find("FirstName").SetText("AFIRST");
page.Elements.Find("LastName").SetText("ALAST");
page.Elements.Find("MiddleInitial").SetText("A");
page.Elements.Find("DoB").SetText("1/1/1999");
page.Elements.Find("TitleId").SetSelectedIndex(0);

// click submit
page.Elements.Find("Save").Click(WaitFor.None);
System.Threading.Thread.Sleep(1000);

// verify entry in list
Assert.IsFalse(page.Elements.Find("CustomerDetail").IsVisible());
Assert.IsTrue(gridView.Rows[1].Cells[(int)ColumnNames.LastName].GetInnerText() == "ALAST");

// click detail
gridView.Rows[1].Cells[0].ChildElements[0].Click(WaitFor.None);
System.Threading.Thread.Sleep(1000);

// click edit
page.Elements.Find("a", "Edit", 0).Click(WaitFor.None);
System.Threading.Thread.Sleep(1000);

// edit information
page.Elements.Find("FirstName").SetText("AFIRSTUPDATED");
page.Elements.Find("LastName").SetText("ALASTUPDATED");
page.Elements.Find("MiddleInitial").SetText("B");
page.Elements.Find("DoB").SetText("2/2/2000");
page.Elements.Find("TitleId").SetSelectedIndex(1);

// click submit
page.Elements.Find("Update").Click(WaitFor.None);
System.Threading.Thread.Sleep(1000);

// verify entry in list
Assert.IsFalse(page.Elements.Find("IdentityDetail").IsVisible());
Assert.IsTrue(gridView.Rows[1].Cells[(int)ColumnNames.LastName].GetInnerText() == "AFIRSTUPDATED");

// click detail
gridView.Rows[1].Cells[0].ChildElements[0].Click(WaitFor.None);
System.Threading.Thread.Sleep(1000);

// click delete with confirm
page.Elements.Find("a", "Delete", 0).Click(new CommandParameters(WaitFor.None, PopupAction.ConfirmOK));
System.Threading.Thread.Sleep(1000);

// verify
Assert.IsTrue(gridView.Rows[1].Cells[(int)ColumnNames.LastName].GetInnerText() != "ALAST");
}

This is a very rudimentary test only testing best case scenarios where add, edit, and delete are successful. In my project, I also build tests for if there are any error during saving, or business rules violations, when somebody hit cancel, etc. But, the simple example above should provide enough basis to be built on top of for those other tests.

- Part 2: List, Paging, Sorting
- Part 3: Add, Edit, Delete
-- read more and comment ...

Tuesday, March 17, 2009

Using jQuery to Create Hot-Keys for Web Widgets

I recently created a "dashboard" for one of my project and with jQuery, it was actually pretty easy and fun. Here is a simplified model of what I did.

First of all, you will need to download and include jQuery CORE, jQuery UI, and a hot-key plug-in in your web application. You can get the instruction on how to do that from here.

Here is the basic style I am using to create the boxes:

    .content { width: 450px; }
.dashboard { border: solid 1px #ccc; }
.wrapper { height:100%;}
.displayrestore { clear:both; margin: 0 2px 0 2px; height:100px; overflow:auto; }
.displaymaximize { clear: both; display:none;}
div.maximize { clear:both; width:450px; height: 250px; font-size:2em; background-color: #888;}
div.restore { width:200px; clear:none; float:left; margin-right:8px;background-color: #ddd;}
div.minimize { width:200px; clear:none; float:left; margin-right:8px;}

Here is the script to initialize my hot-keys and setting all the widget boxes to "restore" mode. As you can see that each div is bound to a hot-key ("ctrl-1", etc) and they are all bound to "ctrl-m" to minimize all and to "ctrl-r" to restore all.
        var speed = "normal";
var MainArea = "Main";
var DashboardArea = "Dashboard";

$(document).ready(function() {
for (var i = 0; i < $("#" + DashboardArea).children().length; i++) {
displayRestore($("#" + DashboardArea).children()[i].id.substring(DashboardArea.length));
}
$.hotkeys.add('Ctrl+r', function() {
for (var i = 0; i < $("#" + DashboardArea).children().length; i++) {
displayRestore($("#" + DashboardArea).children()[i].id.substring(DashboardArea.length));
}
});
$.hotkeys.add('Ctrl+m', function() {
for (var i = 0; i < $("#" + DashboardArea).children().length; i++) {
displayMinimize($("#" + DashboardArea).children()[i].id.substring(DashboardArea.length));
}
});
$.hotkeys.add('Ctrl+1', function() { displayCascadeMaximize('ItemOne'); });
$.hotkeys.add('Ctrl+2', function() { displayCascadeMaximize('ItemTwo'); });
$.hotkeys.add('Ctrl+3', function() { displayCascadeMaximize('ItemThree'); });
$.hotkeys.add('Ctrl+4', function() { displayCascadeMaximize('ItemFour'); });
$.hotkeys.add('Ctrl+5', function() { displayCascadeMaximize('ItemFive'); });
$.hotkeys.add('Ctrl+6', function() { displayCascadeMaximize('ItemSix'); });
});

Below is the function to handle maximize event to the widget. It checks first whether there is any widget in maximize mode inside the "main" container and if there is any, restore it then maximize the one intended to be maximized.

function displayCascadeMaximize(source) {
// restore what is currently in max mode
if ($("#" + MainArea).children().length > 0)
displayRestore($("#" + MainArea).children()[0].id);

// display selected widget in main area with maximize view
displayMaximize(source);
}

function displayMaximize(source) {
// create a clone of selected widget and put in main area
$("#" + MainArea).html($("#" + source));

// display maximize mode
$("#" + source + "Maximize").show(speed);
$("#" + source + "Restore").hide(speed);

// adjust css
$("#" + source).removeClass("restore");
$("#" + source).removeClass("minimize");
$("#" + source).addClass("maximize");
}

The handler for restore is pretty straight forward. It checks whether the "source" intended to be restored is in the "main" container or not and based on that execute the correct restore process.

function displayRestore(source) {
if ($("#" + source).parent()[0].id == MainArea) {
// if source is in main area then remove it from main area and show in Dashboard area
$("#" + DashboardArea + source).html($("#" + MainArea).html());
$("#" + MainArea).html("");
$("#" + DashboardArea + " #" + source).show(speed);

if ($("#" + DashboardArea + " #" + source).hasClass("restore")) { }
else {
$("#" + DashboardArea + " #" + source + "Maximize").hide(speed);
$("#" + DashboardArea + " #" + source + "Restore").show(speed);

$("#" + DashboardArea + " #" + source).addClass("restore");
$("#" + DashboardArea + " #" + source).removeClass("minimize");
$("#" + DashboardArea + " #" + source).removeClass("maximize");
}
}
else {
// if source is in Dashboard area
$("#" + source + "Maximize").hide(speed);
$("#" + source + "Restore").show(speed);

$("#" + source).addClass("restore");
$("#" + source).removeClass("minimize");
$("#" + source).removeClass("maximize");
}
}

The handler for restore is pretty much the same as restore - except it executes minimize instead of restore.

function displayMinimize(source) {
if ($("#" + source).parent()[0].id == MainArea) {
// if source is in main area then remove it from main area and show in Dashboard area
$("#" + DashboardArea + source).html($("#" + MainArea).html());
$("#" + MainArea).html("");
$("#" + DashboardArea + " #" + source).show(speed);

if ($("#" + DashboardArea + " #" + source).hasClass("minimize")) { }
else {
$("#" + DashboardArea + " #" + source + "Maximize").hide(speed);
$("#" + DashboardArea + " #" + source + "Restore").hide(speed);

$("#" + DashboardArea + " #" + source).addClass("minimize");
$("#" + DashboardArea + " #" + source).removeClass("restore");
$("#" + DashboardArea + " #" + source).removeClass("maximize");
}
}
else {
// if source is in Dashboard area
$("#" + source + "Maximize").hide(speed);
$("#" + source + "Restore").hide(speed);

$("#" + source).addClass("minimize");
$("#" + source).removeClass("restore");
$("#" + source).removeClass("maximize");
}
}

The HTML:
    








Item One in Restore Mode


Item One in Maximize Mode

 






Item Two in Restore Mode


Item Two in Maximize Mode

 






Item Three in Restore Mode


Item Three in Maximize Mode

 






Item Four in Restore Mode


Item Four in Maximize Mode

 






Item Five in Restore Mode


Item Five in Maximize Mode

 






Item Six in Restore Mode


Item Six in Maximize Mode

 






-- read more and comment ...