Using jquery.autosuggest.js with Asp.Net

Simple Auto-Suggest with Asp.Net and jQuery

After getting fed up with the Ajax Control Toolkit's AutoCompleteExtender and it's inability to deal with strings that are numbers, I decided to look to my new friend jQuery. Apparently they decided for you if you are feeding it an array of number strings (i.e. { "001","002", etc.}) that the auto-suggest will strip all leading zeroes... BUT I NEEDED THOSE ZEROES!

The jQuery solution is pretty simple, much easier than I thought thanks to this awesome plugin: jquery.autocomplete.js. All you really need to do is make a simple aspx page that spits out the data you want based on a QueryString, use a little jquery to tie it up with a TextBox and you are all set. Here is how its done:


Make your data retrieval page

This plugin consumes it's data in one form: text, with one item per line. This makes it very easy to use and very versatile. You could simply point it at a static .txt file, feel it a javascript variable, or, what we are going to do, use Asp.Net to get you some filtered data.

This tutorial is going to use two examples, one pulling from a list of numbers 1-1000, and another pulling form an xml file.

First, the code-behind for number.aspx:
protected void Page_Load(object sender, EventArgs e)
{
  if (!string.IsNullOrEmpty(Request.QueryString["q"]))
  {
    int q = 0;
    if (Int32.TryParse(Request.QueryString["q"], out q))
    {
      for (int i = q; i < 1001; i++)
        if (i.ToString().Contains(q.ToString())) Response.Write(i + Environment.NewLine);
    }
    else
      Response.Write("Not a number fool!");
  }
}

Doesn't get much easier than that... simply writes out any numbers that contain the number (not mathematically, though we could do that) passed in the 'q' QueryString, so if you were to pass the url: number.aspx?q=100 you will get the results:
100
1000

simple.

Now we do the same thing, but dig into an xml file with Linq:
protected void Page_Load(object sender, EventArgs e)
{
  string q = Request.QueryString["q"] ?? string.Empty;
  IEnumerable<string> things = from p in XDocumentLoad(path).Descendants("thing")
        where p.Value.Contains(q) select p.Value;
  foreach(string s in things) Response.Write(s + Environment.NewLine);
}

That was actually the hardest part. Notice that on the Response.Write() I used Environment.NewLine - that will make a newline in the proper format for the jquery to digest, a <br /> or a \n will not work with this plugin. What was made is a psuedo-web-service (it's not really becuase it does not output xml).

Tie it to some TextBoxes

Now just make a couple TextBoxes that will use them:
<asp:textbox ID="txtNumbers" runat="server" />
<asp:textbox ID="txtThings" runat="server" />

Now use jQuery to attach the pages we made to the proper TextBox:

$().ready(function() {
  $("#txtNumbers").autocomplete("number.aspx");
  $("#txtThings").autocomplete("thingXml.aspx");
});

And that's it, you have working auto-suggesters; pretty simple. One thing to notice that can give you some headaches in Asp.Net is that I used the IDs for selection in the code, which is fine if you are not using User Controls or Masterpages, but if you are you get those funky '...ctl100_...' ids that are made at runtime, so you might want to select them with a different method, like making a dummy class and using that as a selector in jQuery

That's about it - but don't forget to style your auto-suggest box or it will just be transparent; there is some basic css showing the classes produced by the jquery in the example. This was just enough to get you started; there is a bunch of documentation and other variables, settings, formatting tricks that you can use (this is a really powerful plugin) - you can read up on them here: http://docs.jquery.com/Plugins/Autocomplete.

IMPORTANT: Remember to delete *everything* on your handling .aspx page other than the '@' declaration so you don't have any extra html in there (I think I left it in there on the download...).


Setting up Subversion (SVN) with Visual Studio walkthrough/tutorial

From no source control to full Subversion support in 10 minutes - for free

UPDATE Somehow I didn't realize that VisualSVN was only a trial version (thanks for pointing that out guys!) for free :P - but, there is a great free VS plugin (ANKHSVN) available here http://ankhsvn.open.collab.net/

Now if you don't already know, SVN is a great program for keeping track of all your changes in code... and it's what all the cool kids are doing (Google, CodePlex, etc.). Your code is all kept in the repository, if you or someone wants to use it, they check it out Not to mention it is incredibly useful for teams and will save you hours/days if your coding takes a wrong turn and you need to move backwards. Bottomline is: if you are not using it, start!

There are now a bunch of free SVN providers out there, and all you really need is a client installed on your machine and you are good to go. There are a lot of great clients out there, I prefer using VisualSVN to tie together the functionality of TortoiseSVN with the UI of Visual studio (though Tortoise is great on its own as well). As for host, I actually prefer OpenSVN as ghetto as it may appear... is a quite humble site, but it is incredibly useful and FREE! Don't be fooled by its 1990 styling and clunky interface... it is there to help! Most important it is simple and to the point. Here is a walkthrough on how to set up your OpenSVN account, and get it integrated with Visual Studio 2008.

Set up your OpenSVN account

First thing is first, go to OpenSVN and sign up for your account. All you need is a project name, a couple email addresses and click Go!.

Next you will have to check your email and retrieve your password; now go back to OpenSVN, click on 'Manage Your Project' and enter your username/password.

Now that you are in take notice of the project location urls, we will need those later, also, I would recommend changing your password to something you will remember. Then click on 'Access Control' link on the top and add at least one new user.

Now you are all done with OpenSVN and you really never have to go back unless you are adding new users.

Install the Required Programs on your Machine

There are a few options here, but I prefer to use the tag-team of VisualSVN and TortoiseSVN in combination with Visual Studio, it works flawlessly!

TortoiseSVN is a standalone client that integrates directly with windows and does not neet Visual Studio, you can use this by itself if you want, but I prefer to also add VisualSVN as it ties Tortoise and VS together.

Install Tortoise first (this will require a reboot) then install VisualSVN. Now that is all done... simple.

Putting it all together

Ok, now the last part which is the hardest, and it is still incredibly simple.
  • Open an existing website in Visual Studio and you will see a new menu: 'VisualSVN' click on it and go down to 'Add Solution to Subversion'
  • A popup will come up, check 'I will set working copt root manually' and click 'Next'
  • This is the important part: Make sure that you select the correct folder that your project is in! Then click 'Next'
  • Choose 'Exisiting Repository' and click 'Next'
  • In the 'Destination URL:' box, type in the project location url from above (I prefer to use the https one)
  • It will prompt you for username/password, this is the user that you made up above, then click 'OK'
  • After verification, it will ask you if you want to import your new files (since the repository is empty) click 'Import' - this may take a bit
  • And... your done, everything should be working correctly now - you can use either Visual Studio or Tortoise to keep your project up to date


Finished


And that's it, you are set up to use SVN. There are simple buttons in VS that allow you to commit and update. Commit will push your changes to the repository, while update will pull the most recent copies in the repository to your local directory. Also, you will notice that you will be able to see the file/folder status of the items in your solution in the Solution Explorer (green=current, yellow=needs commit, etc.) -- great integration with VS. Keep in mind that you can also do commits, updates and all other operations in Windows with the Tortoise interface which is built in - just right click on a file or folder and look in the 'TortoiseSVN' menu.

Now that you have SVN working, there is a great free online book all about it: http://svnbook.red-bean.com/en/1.5/svn-book.html.

Other Free SVN Host Providers

Oddly enough, OpenSVN does not dazzle everyone, there are more out there, I just prefer the simplicity it brings. These are all available for free, though you have to pay for upgraded usage:

Shout it kick it on DotNetKicks.com

C-Sharpener.com - Another Project

Learn to Program Asp.Net/C# is Just Days - Guaranteed!

If you have been here before you have likely noticed the ad banner over to the right of the page. That is one of my newer projects that you can check out at C-Sharpener.com.

It is a video series provided with code and throrough explanations of how to get started in the web application and web programming world. The videos cover everything from setting up your environment (with all free tools) and arrays to things such as AJAX and Linq-to-SQL. If it doesn't work, I give you your money back - it's that simple.



Getting started with Linq-To-Entities tutorial

The transition from Linq-to-SQL to .Net's Entities framework is incredibly simple

In my humble opinion, Linq is easily the greatest thing .Net has come out with in the past few years, and along with it, Linq-to-SQL (L2S) was a godsend. Being quick to hop on the L2S bandwagon, I quickly became a huge fan of the framework, it is amazing easy and useful. That being as it is, I was quite disappointed when I heard that Linq-to-SQL was no longer going to be advanced at all. Now, it is surely not dead, it is still very usable and effective at what it does, but I like to stay with frameworks that will be actively advanced and fbug-fixed. So I decided to make the jump to Linq-to-Entities (L2E), and it was suprisingly simple.

This guide should be a good starting point for anyone whether or not they are familiar with L2S, and a quick 'jumping guide' to L2S developers. Here's how to get started:

Make your ADO.NET Entity Data Model (.edmx file)

This is comparable to the .dbml file that you made with L2S.
  • Right click on your App_Code folder and select New Item
  • Select ADO.NET Entity Data Model and name it (I left the default Model.edmx for the example)
  • Choose Generate from database and click Next
  • Choose your database from the dropdown and choose the name to save the ConnectionString as and click Next
  • Choose all the things you want included, I just chose tables, but you may want to include views and SPs if you have them
  • *Be sure to remember the Model Namespace you choose for your .edmx (I will use DatabaseModel for the example) - click Finish

Now it will take a moment to run through and produce your .edmx; when it is done you will see a nice little representation of your tables reminiscent of the .dbml display in L2S:

Access your .edmx

Now we simply access the .edmx much like we did with L2S. You must remember to add Using DatabaseModel (or whatever your namespace was) to the code where you are using it. Now we will make a DatabaseModel object and use it to add a 'product' to the database. First, you need to make your Entity Access Object:
DatabaseEntities db = new DatabaseEntities();

Then you can use it to enter an object:
products p = new products();
p.product_name = "Word Processing Software";
p.price = 99;
p.stock = 100;
db.AddToproducts(p);
db.SaveChanges();

Once again, if you are familiar with L2S, this is almost the same exact thing! If you are new, this is very straight-forward and should be easy to understand:
  • Make an object of a type inside your database
  • Fill the object up with data
  • Put it into the database
  • Save the changes

Now L2E one-ups L2S and makes data entry even easier, this will accomplish the same as above:
db.AddToproducts(products.Createproducts(0, "Accounting Software", 300, 125));
db.SaveChanges();

Notice that the 'product_id' is entered as '0', that is because it is auto-incrementing, so it doesn't matter what I put there, it will be ignored. Now I don't know about you, but that little bit there will save me hundreds of lines of code! I am starting to like L2E already!

Display your data

Now that we have put a few objects in to the database, we can go and check it out. Later we will dig in with code, but first we will use a LinqDatasource/GridView:
  • Drag a LinqDataSource (LDS) on to the page and click Configure Data Source from the little square on the upper right of the LDS
  • Choose your Object Model and click Next
  • Choose your table and click Finished
  • Drag a GridView (GV) on to your page
  • In the GV option box, choose your datasource from the dropdown

now just view your page:

Modify Data

Now that we have seen how to put in data, we will modify some; once again, L2E makes it trivially simple:
products edit = (from p in db.products where p.product_id == 2 select p).First();
edit.product_name = "Account Software v2";
db.SaveChanges();

The Linq statement is EXACTLY like it would be in L2S, and just like in L2S, you can shorten up this with some Lambda integration; this will accomplish the same thing:
db.products.First(p => p.product_id == 2).product_name = "Accounting Software v2.1";
db.SaveChanges();

Deleting Items
Now that we have seen Insert and Update, the next logical step is Delete, and it is just as easy. Let us delete the 'Word Processing Software' frorm the table:
products del = (from p in db.products where p.product_id == 1 select p).First();
db.DeleteObject(del);
db.SaveChanges();

And the same exact thing shorthand with Lambdas:
db.DeleteObject(db.products.First(p => p.product_id == 1));
db.SaveChanges();

Once again, I have to say I like the approach that L2E takes of that of L2S, as it is not necessary to specify which table to delete from, as an object can only be deleted frorm the table that it is in.

Using your database relations

As in L2S, L2E takes great advantage of well designed databases and relations. If you noticed up above, the two tables in my database have a 1 to many relation frorm product to reviews. Each element in the 'reviews' table is required to relate to an element in the 'products' table (column 'product_id'). Let's fill the DB with a few more products and some reviews; a lot is going to go on here:
products p1 = db.products.First(p => p.product_id == 2);
products p2 = products.Createproducts(0, "Strategy Game", 49, 99);
products p3 = products.Createproducts(0, "Sports Game", 39, 99);
db.AddToproducts(p2);
db.AddToproducts(p3);

reviews r1_1 = reviews.Createreviews(0, "Much Improved", "this is a much better version", "Bill Brasky");
r1_1.productsReference.Value = p1;
reviews r2_1 = reviews.Createreviews(0, "Terrible", "worthless", "Dirk Digler");
r2_1.productsReference.Value = p2;
reviews r3_1 = reviews.Createreviews(0, "Great Game", "very tough AI", "Wonderboy");
r3_1.productsReference.Value = p3;
reviews r3_2 = reviews.Createreviews(0, "Very Fun", "the Bears rule", "Mike Ditka");
r3_2.productsReference.Value = p3;

db.AddToreviews(r1_1);
db.AddToreviews(r2_1);
db.AddToreviews(r3_1);
db.AddToreviews(r3_2);

db.SaveChanges();

Now to explain what just happened:
  • The first line gets an object of type products where product_id == 2 (this is 'Accounting Software v2.1')
  • The next two lines create new products
  • The next two submit those into the database

Now we have a total of 3 objects in the 'products' table and none in the 'review' table, that is where the next 8 lines come in. If you break up those 8 lines into pairs of two, you can see they are all the same thing really:
  • Make a new object of type reviews
  • Assign its foreign key reference to a products object

Since the database requires a relation between these two tables, you must be sure to set the reference. The last lines just submit everything and commit them to the database.

Now that that is in there, you can use the real power of relations in L2E. Once again, it is almost the same as L2S:
foreach (products p in (from _p in db.products select _p))
{
  Response.Write("<h3>" + p.product_name + " - $" + p.price + "</h3>");
  Response.Write("<b>Reviews:</b><div style='border:1px solid navy;padding:5px;'>");
  p.reviews.Load(); // this loads all the reviews that relate to the product p
  foreach (reviews r in p.reviews)
    Response.Write("<b>" + r.title + " - " + r.reviewer + "</b><br />" + r.review + "<br />");
  Response.Write("</div><br />");
}

And this is what you get:
Yes, I know I used the ugly 'Response.Write', and the output is hideous... but this is just a demo people! As you can see, it is incredibly easy to iterate through all of the reviews of a products, this big difference being that you need to call the .Load() method or they will not be populated. Now this is both a blessing and a curse; this is very efficient as it only loads the related objects if need be, but... it is soemthing that is easy to forget and may leave you scratching your head. I think I like it.

Now relations also work the other way as well. Say you have a reviews object, and you want to know the price of the products it is related to. No need to actually look up the products object, you just call the relation:
reviews r = db.reviews.First(_r => _r.review_id == 3);
r.productsReference.Load();
Response.Write("Price: $" + r.productsReference.Value.price.ToString());

Once again, notice that you have to Load() the reference, or you will get nothing.

Now that should get you started. Like I said, if you are familiar with L2S, this transition should be no problem, and if you are new to this whole Linq arena, this should be simple to pick up. This new ADO is getting more and more impressive the more MS works on it. I can't even imagine goging back to the old methods...

Extend an existing or custom class

Writing a extension for an existing class such as string.DoSomethingHere()

I usually run my database logic through Linq and a dbi.cs class for my database interaction. The dbml file will produce all of my custom classes which is great, but, I wanted to add some custom extensions. Looking at that code created by the dbml is overwhelming and I would rather not go poking around in there, so, I decided to make my own extensions, it's not a difficult process.


I had a class named ticket and I wanted to add an extension IEnumerable<ticket>.open() which would list all open tickets. Now, to get in to my calss structure and all of that would be a waste of time, so I will show how to do this with something that is a bit more familiar and likely useful, I am going to extend the string class with string.capitalizeFirstLetter() which would turn:

a senTENCE like this ONE hErE

into one like this

A Sentence Like This One Here

And yes, I know there is the CSS text-transform, but that isn't the point, it is the method I am showing.


First thing is first, make a new class called MyCustomExtensions.cs or something like that. Next, make sure to declare the class as a public static class. Now make a public static string method. normally in a method you would take a string input like this:

methodName(string someString)

For extensions, you do them slightly different like this:

methodName(this string someString)

Notice that this is now ahead of string, that will tell you program to 'look' for this extension whever a single string is used; it will also populate your intellisense in Visual Studio.


That is really the only big difference, now you just treat it like any other method. This is my full example class:

using System.Collections.Generic;
using System.Text;

/// <summary>
/// Custom Class Extensions
/// </summary>

public static class custom_extensions
{
  public static string CapitalizeFirstLetters(this string s)
  {
    string[] splitString = s.Split(new char[] { ' ' });
    List capitalized = new List();
    foreach (string word in splitString)
    {
      string add;
      if (word.Length == 1) add = word.ToUpper();
      else if (word.Length == 0) add = string.Empty;
      else add = word.Substring(0, 1).ToUpper() + word.Substring(1, word.Length - 1);
      capitalized.Add(add);
    }
    StringBuilder sb = new StringBuilder();
    foreach (string word in capitalized) sb.Append(word + " ");
    sb.Remove(sb.Length - 1, 1); return sb.ToString();
  }
}

Packaging and Deploying a SharePoint 2007 Web Part

You know how to make a web part, but do you know how to get it to your production environment in the proper way?

In a past post, I did a WSS 3.0/MOSS 2007 Web Part Tutorial that showed how to make a web part.  That's all great and grand, but I did not cover how to actually get it to your production environment (truth is I didn't know how at the time).  Technically, you could install VS 2008 and the extension package on your production farm, but that isn't necessarily a good idea or 'best practice' by a long shot.  What you want is one nice neat file that you can take to your server and upload as a feature, that is what this tutorial is all about (most of this info was deciphered from the SharePoint 2007 SDK.)

 

We are going to assume that you already made your web part, and I am going to use the one from my past tutorial as the example for simplicity's sake.

 

Setup your Solution Structure

In order for this to compile correctly, your web part and cab projects must be inside the same solution.

 

  • Here we go: Open Visual Studio and Choose File > New Project, once in that dialogue, click on Other Project Types > Visual Studio Solutions and choose Blank Solution. Name it something, I am going to use TestSolution for mine.
  • This is a tricky part, but it is to keep the solution folder open and not go to a smaller project when we import one: In the Solution Explorer, right click on the solution and choose Add > New Solution Folder and name it dummy -- we will delete this later, but we need it for a moment.
  • In the Solution Explorer, right click on the solution and choose Add > Existing Project now navigate to your existing csproj (in my case, WPs.csproj) and click OK.
  • Now, in the Solution Explorer, right click on the solution and choose Add > New Project click on Other Project Types > Setup and Deployment and choose CAB Project name it and click OK (I named mine TestCab).
  • Now go back and delete the dummy folder.

 

Now you have the proper setup and structure to your folder.  Only a couple more steps and we will have a nice portable web part.  Your Solution Explorer should now look like this:

 

Prepare your Manifast and dwp files

These are required in order for your cab to be properly set up and used by SharePoint.

 

  • Right click on the Web Part Project inside the Solution and add an XML File, name it Manifest.xml, insert the following text into the file (notice the noly changes you might need to make are to the Namespace, which is used in your .cs file of the webpart, and FileName attributes):

[code:xml]

<?xml version="1.0"?>
<!-- You need only one manifest per CAB project for Web Part Deployment.-->
<!-- This manifest file can have multiple assembly nodes.-->
<WebPartManifest xmlns="http://schemas.microsoft.com/WebPart/v2/Manifest">
    <Assemblies>
        <Assembly FileName="WPs.dll">
            <!-- Use the <ClassResource> tag to specify resources like image files or Microsoft JScript files that your Web Parts use. -->
            <!-- Note that you must use relative paths when specifying resource files. -->

            <ClassResources></ClassResources>

            <SafeControls>
                <SafeControl
                  Namespace="WPs"
                  TypeName="*"
                />
            </SafeControls>
        </Assembly>
    </Assemblies>
    <DwpFiles>
        <DwpFile FileName="WPs.dwp"/>
    </DwpFiles>
</WebPartManifest>

[/code]

 

  •  Now right click on the sameProject and add another xml file, name this one your_web_part_name.dwp, for my example I am using WPs.dwp.  Notice that it is the same as specified in the FileName attribute in the xml file above -- add this text to your dwp file:

[code:xml]

<?xml version="1.0" encoding="utf-8"?>
<WebPart xmlns="http://schemas.microsoft.com/WebPart/v2" >
    <Title>Title goes here</Title>
    <Description>Put your description here.</Description>
    <Assembly>WPs, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9f4da00116c38ec5</Assembly>
    <TypeName>WPs.Test</TypeName>
    <!-- Specify initial values for any additional base class or custom properties here. -->
</WebPart>

[/code]

 

The only part that may be a snag for some people is populating the <Assembly>'s PublicKeyToken and  <TypeName> tags in the above XML file.  Let me try and explain what and how to get these values:

  • To get the <Assembly>'s PublicKeyToken you need to go to your test machine, where the code was compiled and get the PublicKeyToken - that is the value you enter there.
  • As for the <TypeName> that is simply the Namespace.WebPartName of the web part itself.
  • Now right-click on your CAB Project and choose Add > Project Output once in that dialogue, choose Primary Output and Content Files, Click OK.
  • Right-click again on the CAB Project and Choose Add > File then browse you your dwp file you just made (mine is WPs.dwp), click Open and save your Solution.

 

You should now have everything set up correctly, your Solution Explorer should look like this:

 

Build and Deploy

All the hard work is done, now just build and deploy.

 

  • Right click on the solution and choose Build Solution - this makes your portable web part, just look in your solution folder, in my example it is located at TestSolution\TestCab\Debug\TestCab.CAB - if you will notice, that is just: Solution_Name\Cab_Project_Name\Debug\CabFile.CAB.
  • Now that you have your file, copy it to your production environment to the folder: C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\bin\
  • Then run the command (from that directory):

     

    stsadm -o addwppack -filename Time_Zones.CAB -url http://your_url_here -globalinstall -force

     

  • And run an iisreset.


You are all done.  You should have your web part available to all of your users on the farm now.  And you can pass out the .CAB file to whoever wants it without any messy code to worry about!

Adding Multiple Persistent Controls is Asp.Net

It's easy to add one control in asp.net during runtime, but it's not so straight-forward to add a bunch dynamically

You would think to add more and more controls, you could simply do something like this on a button click event (I will be using LiteralControls for ease of explanation, but this works the same for all controls, even custom ones):

 

[code:c#]

somePanel.Controls.Add(new LiteralControl("<div>I AM A NEW CONTROL</div>"));

[/code]

 

Which does work perfectly, and does exactly what you think it would do.  But, if you click it again, guess what?  It just re-makes the same control, replacing the old one.  Doing this over and over.  This is because what is produced is not kept in any sort of persistent state.  You need to 'tell' your program what you want it to keep.  For this,  I like to use Session variables... and if you don't like that, then too bad!

 

With that said, we will need some sort of way to keep all the controls in an easily accessable structure.  My choice here is a Generic List as they are so easy to work with and offer so much that you can do do/with them.  To start with, I will be working with a basic List<LiteralControl> but I will get on later to a list with multiple controls contained within it as it is likely that you will want to add multiple controls at each click.

 

Now we just need to show how this is going to be persistant.  First, we need to declare a list in the class.  Then, each time a control is added, it must be pushed into the list as well as the page as to keep a 'copy' of it.  But that still will not survice a postback.  We also need to put the List into a Session variable.  And now that we have that list of controls in the session, we need to check every Page_Load to see if there is any controls we have stowed away, and if so, push them to the page.  Here is the most basic way to show this with functioning code:

 

aspx 

<asp:Button ID="btn" runat="server" Text="Add Control" onclick="btn_Click" />
<asp:Panel ID="pnl" runat="server" />

 

code-behind (c#)

[code:c#]

List<LiteralControl> persistControls = new List<LiteralControl>();
protected void Page_Load(object sender, EventArgs e)
{
    // if you already have some controls populated
    if (Session["persistControls"] != null)
    {
        // pull them out of the session
        persistControls = (List<LiteralControl>)Session["persistControls"];
        foreach (LiteralControl lc in persistControls)
            pnl.Controls.Add(lc); // and push them back into the page
    }
}
protected void btn_Click(object sender, EventArgs e)
{
    // basic control for demo
    LiteralControl lc = new LiteralControl("<div style=\"border:solid 2px navy;padding:3px;margin:3px\">NEW CONTROL</div>");
    pnl.Controls.Add(lc);// add it to your page
    persistControls.Add(lc);// add it to the list
    Session["persistControls"] = persistControls; // put it in the session
}

[/code]

 

Now that is just a basic example to get the point across that this is actually a pretty easy concept.  Now on to a bit more functionality.  There are two basic things I would think an average use of this would require:

 

  • Ability to delete each 'set' of controls
  • Ability to add a bunch of controls each time and persist all of them

 

Turns out both of these are also quite easy given the structure we are using. 

 

Looking at the first one: Ability to delete each 'set' of controls should be no problem as we are using a generic List. What does a List have that makes this so simple?  List.Remove(List Item)  In fact, using a List makes this ridiculously easy.  Simply add a btnDelete_Click method to our code-behind, Now every time a control is added, a Button must also be added with a corresponding CommandArgument that relates to it's position in the List and add a Button.Click += new EventHandler(btnDelete_Click) so the new delete method will be called.  Since the List itself will automatically adjust it's size and move it's items dynamically we can simply use an integer to count up from 0 each time we push a control to the page.  And when a new Control is added, all that is needed is to add the List.Count to the CommandArgument.

 

Now on to: Ability to add a bunch of controls each time and persist all of them which will be even easier.  Instead of passing just one Control like above, we can pass a collection of Controls inside another control.  For this I chose Panels for the easy to work with <div> that they produce.  So now, every time you add a Control, you simply add all your Controls to a Panel, then push that Panel to the List (which is now a List<Panel> by the way).  And really that is all you need to do.  

 

With those two additions, the example code now looks like this:

 

aspx 

<asp:Button ID="btn" runat="server" Text="Add Control" onclick="btn_Click" />
<asp:Button ID="btnClear" runat="server" Text="Reset" onclick="btnClear_Click" />
<br /><br />
<asp:PlaceHolder ID="ph" runat="server" />

 

code-behind (c#) 

[code:c#]

List<Panel> persistControls = new List<Panel>();
Random rand = new Random(); // for display so we can get a simple difference in controls
protected void Page_Load(object sender, EventArgs e)
{
    // if you already have some controls populated
    if (Session["persistControls"] != null)
    {
        persistControls = (List<Panel>)Session["persistControls"]; // pull them out of the session
        int count = 0;
        foreach (Panel lc in persistControls)
        {
            lc.CssClass = "smallPanel";
            Button btn = new Button();
            btn.Click += new EventHandler(btnDelete_Click);
            btn.Text = "Delete";
            btn.CommandArgument = count.ToString();
            ph.Controls.Add(lc); // and push them back into the page
            ph.Controls.Add(btn);
            ph.Controls.Add(new LiteralControl("<br /><br />")); // for formatting
            count++;
        }
    }
}
protected void btn_Click(object sender, EventArgs e)
{
    LiteralControl lc1 = new LiteralControl("<span style=\"border:solid 2px navy;margin:3px\"> NEW CONTROL [ "+ rand.Next(1000,9999).ToString() + "] </span>");
    LiteralControl lc2 = new LiteralControl("<span style=\"border:solid 2px navy;margin:3px\"> NEW CONTROL [ " + rand.Next(1000, 9999).ToString() + "] </span>");
    Panel pnl = new Panel();

    pnl.Controls.Add(lc1);
    pnl.Controls.Add(lc2);
    Button btn = new Button();
    btn.Click += new EventHandler(btnDelete_Click);
    btn.Text = "Delete";
    btn.CommandArgument = persistControls.Count.ToString();
    ph.Controls.Add(pnl); // and push them back into the page
    persistControls.Add(pnl);// add it to the list
    ph.Controls.Add(btn);
    ph.Controls.Add(new LiteralControl("<br /><br />")); // for formatting
    Session["persistControls"] = persistControls; // put it in the session
}
protected void btnClear_Click(object sender, EventArgs e)
{
    Session["persistControls"] = null;
    Response.Redirect(Request.Url.ToString());
}
protected void btnDelete_Click(object sender, EventArgs e)
{
    int deleteThisOne = int.Parse(((Button)sender).CommandArgument);
    persistControls.Remove(persistControls[deleteThisOne]);
    Session["persistControls"] = persistControls; // put it in the session
    Response.Redirect(Request.Url.ToString());
}

[/code]

 

And that is all you need to do.  This could be cleaned up a bit and redundant code could be consolidated, but you get the idea.  This will work with any group of controls be it TextBoxes, DropDownLists or even custom Controls that you make.  I throw mine inside an UpdatePanel to make it function smoother.  Remember though, since this is using Session, high traffic or large control collection can make this a bit of a memory hog, so be careful. 

 

Here is an example and the code:

 



code:


WSS 3.0/MOSS 2007 Web Part Tutorial

So you want to build your own web part eh?

First thing is first, I am going to cut through the crap so you know whether or not you should keep reading right now: for this tutorial to make any sort of sense, you are going to need to have WSS 3.0 and VS/VWD 2008 installed on your Server 2003+ machine; if you do not have one available, make a virtual server (Actually, this is the ideal setup anyway).  You will then need to install WSS 3.0 Extensions for Visual Studio 2008 on your machine (I know I previously posted how to install WSS 3.0 Extensions on your desktop, but that will not work for a full build).  Yes it's a pain, and a lot of requirements, but SharePoint can be a bastard child sometimes.  This really is the hardest part about building new web parts.

Once all of that is out of the way, simply start a new project in VS and choose SharePoint->Web Part.  Give it a name and a location and hit OK.

 

Your solution explorer should now be populated with a bunch of files and references like this:



Now really there are only a few files you need to concern yourself with.  The most important one being the <WebPartName>/<WebPartName>.cs file, in my case it is Test/Test.cs; I will refer to this file as the main file from now on.  I say it is the most important as that is the one that does everything from presentation to code-behind.  The others are the <WebPartName>.webpart file and possibly the AssemblyInfo.cs file, but we will focus on the first right now.


Open up the main file and you will see something like this:



Where you see “TODO: …” is where you are going to put your rendering code.  They even provide you with the always-friendly “Hello World” program if you just un-comment it.  The Guid is automatically populated when you make your project, so don’t mess with it.  The big thing here is that 100% of your styling and layout has to be taken care of back here.  There is no aspx page to your aspx.cs like a normal .net web page.  Once you have a handle on that, it can be treated just like any other asp.net website you have ever made.  There are some other great interactive tools and features you can include, but that is for another day.  Right now, just use the “Hello World” code, use my supplied code or fill it up with your own.

 

NOTE: Often times it is easier to get your programming logic done in a regular Visual Studio Project as there is no way to easily test in a SharePoint Web Part Project.

 

Now that you have your code finished, we can change a few other things.  Open the <WebPartName>.webpart file and you can see some properties you can change.  Customize the Title and Description if you like.



That will change the default settings for this web part.  That is all I would mess with for right now.  So in theory, you now have a 100% working web part.  Now is the easy part.  Just right click on your project and click ‘Deploy’.

 


Assuming everything worked, you know have published your web part to your portal.  If it didn’t work, go fix your bugs and come back :PGo to your Portal->Site Actions->Site Settings->Modify All Site Settings and click on Web Parts under Galleries and you should see your Web Part with a happy little ‘!NEW’ by it.



Now just go to a Web Part Page and put it in there somewhere then exit edit mode.  You should now see your web part in action:


 

Here is my example web part you can take a look at.  It shows a couple different methods of how you can make a web part interact with itself.  As I mentioned above, there is a lot you can do with web parts beyond this, and I will hopefully be able to post some more in the near future.

 

UPDATE

I wrote a tutorial on how to package and deploy a web part once you make it.


Source Code:



Text Input Watermarks using Javascript (IE Compatible)

Simple way to add watermarks to your textboxes for a little flair in your input forms

I have always used the TextBoxWatermark control that is supplied with the AjaxControlToolkit which is very easy to use and I highly recommend it.  But recently I was asked to figure out a way without using 3rd party tools, hence in JavaScript.  At first I figured it was going to be simple, just clear the text when you click on the box and maybe change the background color.  Simple, that works.   Thats when I ran into my next problem.

 

I wanted a textbox that was forr passwords, it would start out and say 'password' but then when you clicked on it, that would disappear and it would become a type="password" box (display text as: ******).  It worked flawlessly! ...in non-crappy browsers.  IE7 dumped all over my dreams yet again and left me with a Javascript error.  Turns out you can't change the type property in IE7 (apparently it works in IE6???) which is completely stupid.

 

So, I did some good old googling which brought me here to find out that in order to change type, you now have to make an entire new object and swap it out with the old one.  So after that, it works, but my focus is all screwed up: I click on the blank, the password text disappears, but the cursor is not in the textbox I just clicked?!  That brought me here which showed me a crazy way to reset focus on a lost object:

window.tempObject = newObject;
   setTimeout("tempObject.hasFocus=true;tempObject.focus();",1);

Now everything seems to work just fine.  BUT, they aren't.  For one thing, I had had an onblur statement in my textbox originally (not in the example, but for discussion purposes) and since the new object did not have an onblur, I had to make sure to re-add that back in.  Also, the same loss goes for CSS class, but that might want to change, so I added a new field into the function to take into account a new CSS class.  If it is left blank, it will inherit the old CSS class if there is one, or simply have no class like Britney Spears. 

 

Ok, so I *thought* that would be easy, but IE comes back again to spite me.  For most browsers, you can set Object.setAttribute("onblur", "JSmethod") but IE decides that isn't good enough, so you have to to this Object.onblur = function(){js_function(god, damnit, ie);}; (from this thread).  Ok, now that we have that fixed, we can look at the CSS class.  It's mostly the same, but this is yet another IE specific problem.  You need to include two different declarations: Object.setAttribute("class", "css_class"); for most browsers and Object.setAttribute("className", "css_class"); for IE.  This time the IE fix/hack will not work for cross-browser compatibility.  So here is the finished JS:

 

[code:js]

 function makePassword(oldObject, newClass)
{
    //newclass is the new css class to pass to the textbox
    //otherwise leave it blank ('') to inherit it's old class
    //or if there isn't one, have no class
    var newObject = document.createElement('input');
    //only switched to password if the original text was password
    newObject.type = (oldObject.value == 'password')?'password':'text';
    if(oldObject.id) newObject.id = oldObject.id;
    /// this decided whether to keep the old class or get a new one
    var cssClass = (oldObject.className && newClass.length < 1)?oldObject.className:newClass;
    newObject.setAttribute('className', cssClass);// for IE
    newObject.setAttribute('class', cssClass);// for others (these are both needed)
    //the following is hard coded to add onblur functionality to the control
    //newObject.setAttribute('onblur', 'blurred(this)');// this would work if it wasn't for IE
    newObject.onblur = function(){blurred(this,newClass);}; // because of IE
    oldObject.parentNode.replaceChild(newObject,oldObject);//this is necessary because of IE
    //this is needed since a focus() will only work the second time since it happens too 'soon'
    window.tempObject = newObject;
    setTimeout("tempObject.hasFocus=true;tempObject.focus();",1);
}

[/code]

 

Remember though that you will have to validate that the original values are not in the textboxes when the user submits, as they will not be empty initially.  Here is a fully working example to mess with, it shows how to use it both for a simple watermark/mask and also with a password field change.  It also includes how to add javascript function calls and deal with changing (or persistent) css:


 

Getting the row or sql value/index of the gridview row you just clicked

Getting the row you just clicked is not as obvious as it seems first hand

There are generally two cases that I need information from the row I clicked in a GridView (or listview, etc.).  The first being simply to get the index in the gridview, whether or not it was the first, fifth or 50th row.  That is pretty easy, but the second case, getting the index (or any) value of the SQL entry of the row just clicked is a little less obvious, though still pretty easy.  Here is a better explanation of that second one if it isn't clear: Say you have a GridView bound to a SqlDataSource (or Linq, etc.) and you have an column in that table that is Product_ID.  Your user is going to click on a button in one of those rows, say it is row 15, but the item Product_ID is 547.  You want to get 547 with no other information than the _Click event.

 

This is how you do it:

 

[code:c#] 

protected void btn_Click(object sender, EventArgs e)
{
   Button btnClicked = (Button)sender;
   // This will get you your GridViewRow
   GridViewRow row = (GridViewRow)btnClicked.NamingContainer;
   // This will get you the actual Index out of your SQL table
   string getIndex = this.GridView1.DataKeys[row.RowIndex].Value.ToString();
   // And this will just print it out to show it works
   Response.Write("Row Index: " + row.RowIndex.ToString() + "<br />");
   Response.Write("Sql Index: " + getIndex);
}

[/code] 

***IN ORDER FOR THIS TO WORK, you *have to* remember to add DataKeyNames="Product_ID" to the GridView tag, otherwise you will throw an OutOfBounds exception because your program won't know what the hell you are calling with DataKeys. 

Keep in mind this trick will work for any column in your table. I use this in my own app to populate a modal popup depending on what row was clicked, works very slick.