Introduction

The Internet is a tremendously important resource professionally and for private use, yet organizing access to frequently visited sites is left to the Favorites menu. For some reason Favorites doesn't work for me. It's great for filing away and organizing bookmarks to pages of interest but, for me, its not visible enough. The Links toolbar is good for a few items, but like many users, I visit a core set of about 30 sites and this is too many for the Links toolbar. So to try and address my own requirements, I've come up with my own home page that might also work for you. The page uses a web control that serves as an illustration of an ASP.NET control in action and that you might find a use for elsewhere.

As you can see from the image, the control is included in an ASP.NET (.aspx) page and comprises one or more group boxes. In this case the web part is one that Microsoft previously used on their MSDN site for presenting current information, grouped by topic - just as I want to have happen. The group boxes are expandable, they remember their collapsed/expanded state (even between successive IE sessions), so that the information on view can be restricted, but always show the header, so I know what's available and can be moved around. So far so good, but creating a web part using HTML is, while not not challenging, tedious because the tables have to be laid out just-so and use specific style classes recognized by the web part. Also, having the web part implemented in HTML means that changes are not straight forward as there is no separation of code and content. Sounds like an ideal candidate for being rendered by a web control.

The web page

Allowing the web parts to be generated automatically simplifies the process of creating a page and allows the source of the data to be a database or XML file. In my case I've chosen to use an XML file but more about that in a moment. Here is the HTML for the web page shown above:

Not much to it. The key line is the one containing the <Links:WebPartsControl> tag. The tag is registered as required and the tag reference (<Links:WebPartsControl ... >) initializes the WebPartControl class that, in turn, presents the required group boxes. Note that the page is inherited directly from System.Web.UI.Page and does not use a code-behind implementation. This is just to remove all obfuscation from the example. The disadvantage of this page as presented is that it cannot be used in the designer.

The web part sits within a table comprising 3 columns. The positioning of the web part is controlled exclusively by this "outer" HTML. In this case, it sits within the left hand column. The middle column is used to provide a gap between it and the default page that is displayed on the right. The right hand column contains a frame, the source for which is a default page. You could just type in the name of a page to use, but the WebPartsControl provides a default to use. The default is initialized within the control to http://www.google.com (that, of course, you can change). But you can override this default by specifying one in the tag definition (see the HTML) or by using a parameter on the page reference (see below).

The XML file

The number of web parts and the links to be hosted by any given web part is defined by an associated xml file. Here is an example:

There are two type of links: "frame" and "popup". A frame link will cause the referenced page to be displayed in a named frame (which defaults to _Parent), while a popup will always display the page in the parents frame. The example use of the web control given above, specifies that the frame named LinkPage is to be used. There are two other significant differences between frame and popup link types: there can be only one FrameLink entry; popup links can have groups. The XML sample shown illustrates all tags and attributes recognized by the web control, so a moments review will provide almost all the information needed. The ID attribute is worth an additional comment. The web part will store its expanded/collapsed state and order so that it can be retrieved then next time IE is used. However it can only restore order if the parts being used have unique IDs. I've used GUIDs but any other unique ID will do. For example "A", "B", "C"...

The web part

The web part control is based on a web part implementation like the one that used to feature on the MSDN site. A web part is a component comprising some HTML and Java Script. Unlike a component in .NET, the presentation (HTML) is not in the same place as the code. But like a .NET component, the web part code encapsulates the functionality of the component. The link between HTML and code is a style class. In IE, a style class can have its behavior attribute set to refer to a file containing the code and a <component> tag. The following diagram attempts to depict these relationships:

The code for the web part component used in this control can be found in the webparts.htc file in the webparts folder. The styles used by the web part, which includes the one that links the web part HTML to web part code, can be found in the file ie.css again in the webparts folder.

The HTML used for the web parts can be found in both the Render() method of the web control and within files in the HTML folder. This folder contains four files. Each contains a snippet of the HTML used to show the collapsible group boxes. The HTML is broken up into snippets so that they can be reused easily by the web control. They are held in files so that they can, if needed, be modified without the need to recompile the web control.

The first file to review is webpart.txt the contents of which are reproduced here:

This HTML defines the table that controls the layout of a group box, the position of the button and the grab handle, its title and where the content will be located. This snippet contains the identifiers %WebPartTitle and %Elements. The web control will substitute these identifiers with HTML (based on HTML snippets from other files) to create a complete group box that is returned to the user's browser.

You will see that the main table of this snippet is assigned a style class. As well as providing format styling, the use of this class allows the associated component code, residing in the .htc file, to identify the HTML elements that are part of the component. In this case the class is called clsPart.

The WebParts control

The entire web part is a collection of any number of the group boxes. This list of group boxes are, in turn, held within a containing table. The HTML for this table can be found in the Render() method of the web control:

XMLFile

This defines the name of the XML file used to contain the link information. The default is links.xml. Like the DefaultPage, this property can be modified by including a value when the control is defined on the containing web page:

Things that could improve the control

Add ability for setting width property (this is difficult because some of the width determinants are in style classes and within the .htc file)

Add a colour property (this far from impossible but yet more difficult as the colour is intrinsic to the images used)

Limitations

Beside the limitations implied in the "things to do" list, the HTML generated will only work when display in IE 4.01 browsers or higher. This is because the web part uses behaviors, an IE specific feature.

Updates

2003-06-14 - Fixed problem with drag window not disappearing

2003-07-10 - Added support for multiple controls on a page The condition of having two controls on a page was never tested. As it turns out this didn't work. Now as many controls as are required can exist within any page, and each control stores is data independently.

2003-07-10 - Added support for the use of the control on a page that does not have an HTML or Webparts folder The sub-folder containing the HTML snippets used by the control can be referenced using a relative path so that only one copy of the HTML folder is required (rather than one per page). An attribute called HTMLSubFolder can be set on the control to specify the relative location of the HTML folder and corresponding web parts folder.

2003-07-14 - Included .cmd file to compile the source (for those without VB.NET)

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

I can't be sure of the error you are seeing. However the only error that's been reported is when the links.aspx pages (and the the other supporting files) are not available from a virtual directory root. An error occurs in this circumstance because the ZIP file contains a webconfig file and ASPX expects the webconfig file to exist ONLY in the root folder. If you are using a sub-folder, then delete the webconfig file from the sub-folder (though one must exist in the virtual directory root folder).

Take a look at some of the earlier messages posted here and you will find further information that may be useful. If you have unzipped the files into a virtual directory root folder then post a message with the error message you are seeing.

I think I might have found a problem (or two) with the function fnReOrder() in webparts.htc.

The line

if( i != 0 ) oLastPart = element.parts[i-1];

appears to be looking at the original order of parts rather than the "new" order. Should it not be something more like

if( i != 0 ) oLastPart = fnGetPartById( oStateArray[i-1][0] );

?

This same function (fnReOrder) appears to break if the restore-state refers to a part ID that's no longer present in the configuration. A quick check of the oPart variable before trying to insert the refered-to object into the document object model takes care of the problem.

I think that the code is correct. At the risk of overstating the obvious, it maybe worth saying that this is a simple re-order rather than a sort. The re-order will start with the page displaying the groups in the order that are when in the source XML fle.

The code loops through the saved state and retrieves the part corresponding to the position associated with the iteration(from the gradually changing list). When i=0, no attempt will be made to retrieve the "last" part (-1) item as it will not exist. In this condition, element.rows(0).cells(0).insertAdjacentElement( "afterBegin" , oPart ) is called to insert oPart in the zeroth position.

You referred to the list held in "element" as the "original" list. I think that using the word original give a clue to your misunderstanding. The list only has an original order and this is grandually re-ordered by the function.

On the next iteration, (1) the group now at position i-1 is retrieved and the group referenced by oPart is inserted directly after oLastPart. So as each iteration completes, we've a display that is becoming gradually more re-ordered.

All the above is included just help understanding of the function. But the truth is that while the line is not wrong, it is redundant. Comment it out and the function works just fine. Moreover, in verifying that I understand my code, I do think I've seen an error: sometimes when moving groups, the position of the moved group can be off by one place (but only when it is move the first time, move it again and it sticks). I'm pretty sure this has nothing to do with the re-order function. Rather, the problem will be in fnDrag, fnRelease or fnSetPosition.

I updated my test platform with the latest code, so we're looking at the same sheet of music.

Here's what I'm seeing...

If you edit links.xml (for easier visibility) so the titles of the parts are

3 Code Pages
1 Links
2 On-line Stores

run the page, and manually drag&drop sort the parts so they're 1,2,3

close the page

when I restart it, they come back up ordered 1,3,2 instead of the last-saved 1,2,3. This is because, in fnReOrder(), the object assigned to the variable oLastPart is sometimes the wrong "last part". (You -might- have to clear out your browser cache to see it, interestingly. )

If you also change the GUIDs of the parts to simpler IDs, such as A, B, & C, it's easier to see that the first time fnReOrder is called during initialization of the component, element.parts is in "natural order", (that is, the order they appear in links.xml), not the order described by oStateArray. element.parts[i-1] doesn't necessarily refer to the "last part" as described by oStateArray. (This is how I discovered the issue about oPart turning up null. You'll have to clear your browser cache/cookies or code around the issues, there.)

I also see, as you note, that there are two redunant assignments to oLastPart, but the latter assignment is also not quite right because of it's location in the code. Move it outside the next curly brace, (so it's non-conditional within the for-loop), remove/remark-out the "first" assignment, and fnReOrder should be good to go (I think).

in your example there is only one FrameLink,and two PopUpLink ,when I add another FrameLink in The XML file,the web page display the two FrameLink together in the same block! I want to display order by "title" block!How to realize it??Thank a lot!!

Thanks for the clarification. That there are two link is as a result of some experimentation on my part. Yes, only one framelink can be defined. Just a rule I was playing with. If you want more than one framelink type, I'm afraid you will need to modify the code.

The change is not too hard and involves making the function FramePart() look more like LinksPart() (except without the loop for groups). Any revision of FramePart() will still need to call FrameLink() as it is in this function that the target for of the link is set to be the name of a frame of the displayed HTML.

I'm trying to develop an own webpart based on your Links webpart. How and where do you store the state information of the webpart (collapsed, expanded and position)? I tried to figure it out but I somehow can't find it.

Take a look the response to the Curiousity!!! thread here. If it doesn't provide you with what you are looking for let me know. It's also worth taking a look at the DHTML stuff on the msdn site as background to the technique used in this control is covered. Take a look at:

Gregor, I have no need of a C# version. The code sample comes out of a larger project written using VB.NET (a port of an earlier ASP application). The DLL can be called by an ASPX page that uses C# or J# as it scripting language or by any other .NET application. What's motivating your request?

This sounds like a great opportunity for you to learn a little bit about VB ==> C# translation. I would bet that if you put a little effort into it, you'll find that the translation is pretty darn simple.

LOL. I agree! My post was for 'gsuttie', not for you. From your code, I already expected that you have no trouble with the translation as you seem confortable in both languages. I was simply suggestion that 'gsuttie' take the opportunity to learn a bit about VB.Net by doing the translation him/herself.