An all too common situation us web developers face is the dreaded “double submit” issue.

Users, for some reason, love to double-click on every submit button you might create in your web application.  It’s like an impulse.  They MUST submit the record into the database twice.

Poor souls.  They just can’t help themselves.

There are a handful of ways to address this issue using javascript, SQL, and server-side code.

I wanted to use javascript because I felt it was the most “lightweight” way to handle this issue.

Plus, my submit button was nested inside of a FormView, that was nested inside of a ContentPanel, that was nested inside of a MasterPage.

Fun.

In any event, I created a javascript function to fire on the OnClientClick property of the submit button:

<asp:Button ID=”btnSubmit” runat=”server” Text=”Submit Assessment Form” ToolTip=”Submit the Assessment Form”

onclick=”btnSubmit_Click” OnClientClick=”DisableSubmit(this);” />

I then created a javascript function on the Masterpage:

<script type=”text/javascript”>

function DisableSubmit(buttonElement) {

document.getElementById(buttonElement.id).disabled = true;

}

</script>

Hmmmm, when I would click the button, nothing would happen.

So I put in an alert and discovered that the javascript would fire, but the button would not enter the “disabled” state.

Frustrating!

Long story longer, I discovered that this would not work if I was using the Telerik RadFormDecorator.

That’s because the RadFormDecorator renames the client id of each control that it skins.

Therefore, the javascript function DisableSubmit was receiving “ctl00_ContentPlaceHolder1_frmAssessment_btnSubmit”, but the actual id was “Skinnedctl00_ContentPlaceHolder1_frmAssessment_btnSubmit”.

The RadFormDecorator was placing “Skinned” at the front of the id.

So I amended the javascript function:

<script type=”text/javascript”>

function DisableSubmit(buttonElement) {

document.getElementById(‘Skinned’ + buttonElement.id).disabled = true;

}

</script>

Now, the button disables appropriately.  Yay me!

First, lets be clear.

I am, by far, the WORST test taker.  I have paralyzing anxiety when it comes to taking tests.  I have found, in my years in college, that the way I pass exams is by being prepared.  There really is no “short-cut” for me.

I am a bit embarrassed that after 3 months of intensive study for this exam, I received a passing score of 700.

That’s right.  I got just enough questions right to pass.

To be honest, I was a little dissapointed.  I believed that with the effort I put into my studies, I should’ve performed better.  But I digress.

Here are some things that DID help me prepare.

1.  Its a good idea to fully understand what you are studying for.  This page gives a pretty extensive overview of what to expect on the exam.

2. I purchased the MCTS Self-Paced Training Kit book by Tony Northrup, Shawn Wildermuth, and Bill Ryan.  This book has its flaws (which I’m sure you’ve seen in other forums).  Overall, its a comprehensive collection of what’s needed to pass.  I found the MeasureUp test, included with the book, to be absolutely useless.  The questions are poorly written and really just tell you what you didn’t memorize from the book.  It doesn’t focus on CONCEPTS.  Furthermore, if you select the wrong answer, MesureUp does not thoroughly explain why that is not the right answer.

3.  I purchased a Transcender practice exam.  There are different schools of thought on using “practice” exams.  I was a bit leary in using it, but Transcender did an AMAZING job of putting together practice questions.

I would recommend that you start with the book.  Read through each chapter and take notes.  I used Microsoft One Note to collect all of my notes.  This made it handy when I was finished.  I could print my entire notebook for review.  Plus, I could copy and paste elements from the ebook (diagrams, tables, etc.) that I wanted to remember.  The only problem I had was copying and pasting code from VS to One Note.  I had to paste it into Word first to keep the coloring and formatting.  C’mon Microsoft, can we fix this please!?

After completing the book, I went through and completed each lab in the book.  I think it was a better strategy to do it this way because, in a sense, it allows you to go through the book twice.

When I was finished with the book and the labs, I purchased the Transcender.  Each question that I got wrong, I printed out.  Then I would highlight the “keywords” or concepts that I was missing from that question.  Again, Transcender does such a GREAT job of really honing in on why the wrong answer is wrong.  I feel like I learned more about what I didn’t know from taking the Transcender and, upon taking the test, I found very few questions that were similar.  So, it didn’t feel like “cheating” to me.

Rinse and Repeat.

You can complete the exam in C#, Visual Basic, and (I think) C++?

I can write in both VB and C#, but I chose VB for readability.

I would say that if you are new to programming in general or haven’t developed a competincy for reading/writing code, this would be an ambitious starting point.

If you have any other questions, feel free to leave me a comment and I will do the best I can to answer.

My wife and I sporting SWEET T-Shirts from Telerik (Thanks Emily!)
My wife and I sporting SWEET T-Shirts from Telerik (Thanks Emily!)

My wife and family are happy.  That’s because I’m home in time for dinner.

Thanks to the hard working folks at Telerik, I can get things DONE.

Most importantly, I don’t have to sacrifice my design or UI strategies!

Let’s face it.  A lot of us developers (especially in health care) work on projects by ourselves.

We may be on a team of developers, but with so many projects coming in, we take one under our wing and run with it.

It becomes our baby.  We feed it, love it, and one day we let it leave our protection; hoping it does what we told it to do.

Having to work under these conditions means that we have to be the developer, tester, AND designer.

And it seems to me, the cool design elements always come last.  After we’ve ran out of time.

“Gee, I would really like to have this form AJAX-ifed, but I just don’t have the time.”

“It would be really cool to bring some color and design elements to this GridView, but alas, it’s due next week.”

“I spent all my time getting this INFERNAL thing to work; I don’t have time to make it pretty!”

Well, I am more than happy to suggest a FANTASTIC set of ASP.NET tools from Telerik.  I can’t tell you how much I love these tools.

Having these controls is like having a team of developers enhancing every aspect of your development.

For instance, the RadFormDecorator will allow you (with one line of code) to format all of your buttons, radio buttons, text boxes, list boxes, drop down lists, etc. to a skin of your choice.  Allowing you to instantly change the look and feel of your pages.

The RadEditor, with spell check, gives you a robust text editor that will make your boss think you SLAVED over the design.

The RadMenu, RadTabStrip, and RadPanel give you intuitive navigation with very little coding.

And the RadGrid?  RadGrid, alone, is worth the money spent.

I could go on and on.

Do not hesitate.  Download the trial today!

First, some background (in case you might be interested):

Here at Kaweah Delta, I have been challenged with providing a solution for a common health care problem.  Like most hospitals, we are constantly looking for effective ways to put pertinent information into the hands of our physicians.

How can we communicate lab results to a physician when he/she is not within the hospital?  If a patient is ready to go home, is it possible for the physician to discharge her without making her wait until his rounds?  Is there a secure, discreet, way for physicians to communicate with each other, regarding a patient?  If so, can this communication be secured and retained for future use?

We have identified that being able to communicate this type of data to physicians, when they are outside our hospital, is becoming more and more of a demand.  We believe that advances in mobile computing can answer that demand.

Therefore, I am currently in the process of developing a suite of mobile applications that will, hopefully, provide solutions to these types of challenges.

We are calling it Verity and, currently, it is built on the Windows Mobile 6.0 platform.

The first, most fundamental, challenge that I encountered was instituting a communication model that would be best for critical information without creating a high resource demand on the device.

More specifically, I have created a series of web services that retrieve and send information to the device, however, having a device check these services periodically (every 5 minutes) proved to be too much demand on the battery.

The question became: “How can we send messages to our mobile application as they arrive?”

Enter Microsoft Direct Push.  Those of us that have access to this technology, understand how great it is.

The next question became: “Is there some way to leverage this existing technology for our mobile application?”.

The answer is yes.

Enter the Windows Communication Foundation available in the Windows Mobile 6.0 SDK.

This framework provides an ability to create a communication channel between a server application and a custom mobile application.  The important factor is that it relies on Exchange 2007 and Direct Push to send this communication.

With these tools, I can create system messages that get sent through our Exchange server, to a mobile device’s inbox.  My mobile application listens on a specified channel, retrieves that message on processing, performs the event, and removes the e-mail from the inbox.  Essentially, Direct Push for my application!

My primary source for establishing this was this example by Andrew Arnott in the September 2008 issue of MSDN Magazine.  The code download can be found here.

Although this example demonstrates an IM-like messaging application, the fundamentals are demonstrated.  I can create any type of server application I want, as long as it can effectively send a message to the device.

So let’s evaluate how this might work for my suite of mobile applications:

1. A message or event is triggered in one of our clinical systems.  I have designed a server application that is looking for those events.

2. The server application recognizes that the event occurred, looks up the appropriate physician’s e-mail address, and sends a serialized XML message to the Exchange server.

3. Exchange then pushes it to the device using Direct Push.  My mobile application is listening for messages hitting the device’s inbox with a particular channel name.

4. Because the message has the right channel name, the mobile application retrieves the message from the inbox (effectively removing it) and performs a call to the web service.

5. The web service supplies the clinical system event that occurred.

I did, however, run into a snag when accessing the web services on our Exchange server.  We found that they were not enabled.  D’uoh!  That will be a week of my life I’ll never get back.

Anyway, our Exchange guru was able to figure out how to enable those services.

If connecting to your Exchange server requires SSL, here is an additional resource.

I have currently been tasked with the duty of providing a web part that displays patient satisfaction scores for the hospital. However, there was one stipulation:

“Can you make the web part reflect the unit that the nurse is working on?”.

In other words, could I create a web part that would observe the user’s login and display appropriate information based on where, in the hospital, that employee worked. In this case, the requester (ahem, my boss) wanted the scores to display for the nurse’s unit (think of a unit as a “wing” or “floor” in a hospital).

Provocative.

Right off the bat, I figured the best way to access the nurse’s unit was by pulling the “Department” property from our Active Directory schema.

But then I thought: “Why not build a function that would return any property I might want from Active Directory?” That way, from here on out, as I am building my web parts I could make decisions of the data to display based on a user’s department, job title, director, etc.

Essentially, creating web parts that are “user-aware”.

Good idea self!

Below is a namespace (ActiveDirectoryInfo) , class (UserInfo), and function (GetPropertyValue) that I created to do just this:

using Microsoft.Office.Server.Audience;

using Microsoft.SharePoint;

using Microsoft.SharePoint.WebControls;

using System;

using System.Collections.Generic;

using System.DirectoryServices;

using System.Text;

using System.Web;

namespace ActiveDirectoryInfo

{

class UserInfo

{

public String GetPropertyValue(string PropKey)

{

//Get user’s Department from Active Directory

SPWeb spw = SPControl.GetContextWeb(HttpContext.Current); //Establishes the current SharePoint site.

SPUser spu = spw.CurrentUser; //Grabs user information from the logged in state of the user.

DirectoryEntry dentry = new DirectoryEntry(@”LDAP://DC=<hidden>,DC=<hidden>”, “<domain_user>”, “<domain_user_password>”);

DirectorySearcher dsearch = new DirectorySearcher(dentry);

String retUserName= “”;

dsearch.Filter = “(objectSid=” + spu.Sid + “)”; //Filters the search result based on the user’s SID.

//dsearch.Filter = “(sAMAccountName=” + spu.LoginName.Replace(@”Domain\”, “”) + “)”; //Filters the search result based on the user’s login (without Domain\).

dsearch.PropertiesToLoad.Add(PropKey); //Loads the specific property to be viewed.

SearchResult result = dsearch.FindOne(); //Peforms the search for one matching record.

if (null == result)

{

//Nothing

}

else

{

retUserName = result.Properties[PropKey][0].ToString();

}

return retUserName;

}

}

}

So here is how we might call upon this function within a web part:

protected override void Render(System.Web.UI.HtmlTextWriter writer)

{

UserInfo ui = new ActiveDirectoryInfo.UserInfo();

writer.Write(ui.GetPropertyValue(“cn”)); //Returns user’s full name

writer.Write(ui.GetPropertyValue(“mail”)); //Returns user’s e-mail

writer.Write(ui.GetPropertyValue(“Department”)); //Returns user’s Department

}

I found myself in a tricky situation the other day. In the hospital we have patient units. Think of them as floors or “wings” in a hospital. We also have sections in each one of the units. A section might be food, nurses, a room, or the discharge process. As a hospital, we survey our patients and have them rate the quality of a section in the unit they stayed in. Got me so far? Okay.

Well, I needed to create a report to reflect the managers that are responsible for a particular section in a particular unit. Rather than have each manager and the section they are accountable for listed in rows, I wanted to have one field in a column that represented a list of managers (separated by commas).

For example, something like this:

Unit

Section

Managers

North Wing

Meals

Martha Stewart, Steve Jobs, Paul Allen

In SQL Management Studio, I created a Scalar_valued Function and put the following code inside:

– Add the parameters for the function here

@unit nvarchar(50),

@section int

)

RETURNS nvarchar(500)

AS

BEGIN

– Declare the return variable here

DECLARE @Individuals CURSOR

DECLARE @Ac_Individuals nvarchar(50)

DECLARE @RV nvarchar(500)

– Add the T-SQL statements to compute the return value here

SET @Individuals = CURSOR FOR

SELECT Fname + ‘ ‘ + Lname AS Individuals

FROM PG_Individuals A INNER JOIN

PG_Accountable B ON A.IID = B.IID

WHERE A.Active = 1 AND UID = @unit AND SID = @section

OPEN @Individuals

WHILE @@FETCH_STATUS = 0

BEGIN

IF @RV IS NULL

BEGIN

SELECT @RV = @Ac_Individuals

END

ELSE

SELECT @RV = @Ac_Individuals + ‘, ‘ + @RV

FETCH NEXT FROM @Individuals INTO @Ac_Individuals

END

CLOSE @Individuals

DEALLOCATE @Individuals

–SELECT @Ac_Individuals = @unit

– Return the result of the function

RETURN @RV

@unit and @section are the parameters that are passed into the function.

@Individuals is a CURSOR that loops through a SELECT query that selects all the managers and joins them to the accountable table, thus pairing them up with their respective accountabilities. This query is filtered using the @unit and @section parameters.

I then begin to FETCH each individual record and store it into the @Ac_Individuals variable. Cycling through I add each entry from @Ac_Individuals into the @RV variable appending a comma.

Finally, I return the @RV variable that is representative of the comma separated list.

Now, I can use this function in the following way:

SELECT dbo.<name_of_my_function>(A.UID, A.SID) AS Accountability

Cisco Phone Photo Who cares that I don’t have a face? It’s still cool!

Below is the code I used to tap into the CIPIMAGE API and convert a .jpg image from our PeopleSoft database into a valid .cip image at runtime. Specifically it uses the “ImageProcessor” object to convert the image from a jpeg into XML data to be parsed by the phone.

This code will also require my Cisco Phone Object Class. The cipimage.dll is included in this post below. Be sure to register cipimage.dll on your web server!

 

 

Here’s the code:

Imports CiscoIPPhoneServices

Imports System.Data

Imports System.Data.SqlClient

Imports CIPIMAGE

Partial Class physdir_show_details

Inherits System.Web.UI.Page

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

Response.ContentType = “text/xml”

Dim ImgProc As New ImageProcessor

‘Establish connection with database

Dim SQLConnect As New SqlConnection(ConfigurationManager.AppSettings(“ConnectionStringName”))

Dim SQLCommand_Info As New SqlCommand(“stored_procedure_to_receive_info_from_db”, SQLConnect)

SQLCommand_Info.CommandType = CommandType.StoredProcedure

SQLCommand_Info.Parameters.AddWithValue(“@empid”, Request.QueryString(“empid”).ToString)

‘Prepare the table

Dim Adapter As New SqlDataAdapter(SQLCommand_Info)

Dim DT As New DataTable

Adapter.Fill(DT)

Dim EmpPhoto As String = “”

Dim EmpName As String = DT.Rows(0).Item(“EmployeeName”)

Dim pWidth As Integer = 52

If DT.Rows(0).IsNull(“EmpPhoto”) = False Then

Dim bEmpPhoto As Byte() = DT.Rows(0).Item(“EmpPhoto”)

ImgProc.LoadJPGFromBuffer(bEmpPhoto)

ImgProc.SetLocation(-1, -1)

ImgProc.Resize(52, 65)

ImgProc.ColorToGray()

ImgProc.ReducePaletteColors(4)

EmpPhoto = ImgProc.SaveCIPDataToBuffer()

EmpPhoto = Left(EmpPhoto, EmpPhoto.Length - 6)

Else

‘No photo Hex ASCII string

EmpPhoto = “<Insert a valid ASCII string for a picture that will display when the employee does not have a photo>”

EmpName = “No Photo Available”

pWidth = 87

End If

Dim CIPPD As New CiscoIPPhoneImage(“”, EmpName, -1, -1, pWidth, 65, 2, EmpPhoto, “Back”, “SoftKey:Back”, 1)

Response.Write(CIPPD)

End Sub

End Class

Download - CipImage.dll

Okay, that might be a bit of an exaggeration. Unfortunately, setting up this feature wasn’t as easy as the documentation suggested. Then again, is it ever that way!?

Incoming e-mail is a feature which allows users to e-mail an address and have the content of that e-mail appear in a document library, discussion board, or any other list supported under SharePoint.

In my opinion, this is an extremely valuable piece to the SharePoint deployment puzzle. I would go so far to say that it is a crucial piece to every SharePoint configuration.

Let’s face it. Most people communicate widely via e-mail. To leave that source of knowledge and documentation nested in a neat folder within your inbox, is downright neglegent! Plus, most end-users have been trained, and have become comfortable, with navigating your e-mail system. Outlook (or any other mail client) is the perfect front end to encourage users to post their information into their respective SharePoint site.

But, of course, you know all this.

Why then, was this crucial piece of configuration such a BEAR to setup? Two reasons:

1. Microsoft needed to make this configuration path a little more clear, consistent, and stable.

2. I need to learn just what the heck I’m doing!

So in order to enrich the community (and help me remember how I did all this), I give you my steps for setting up incoming e-mail on a discussion board list:

NOTE: If you are not the demi-god of your IT dept and do not have access to everything, know that you are going to have to get a couple people involved. Namely, the person that supports your Active Directory structure and the person that supports your DNS server (probably the same person [maybe it's you!]). You will also have to have access to configure the web front-end server of your SharePoint configuration running Central Administration. Hopefully you at least have access to that. If you don’t, I shed a tear for you. I know you cringe at the thought of having to ask your network administrator for help, but just bite the bullet and walk over to his/her desk and beg mercilessly for his/her help.

1. The first thing I did (ahem my network admin. did) was create an Organizational Unit (OU) called “SharePoint Distribution” in the local domain of our Active Directory Schema. He placed this OU under the OU called “Groups” (don’t ask me why, that’s his job!)

2. He then right-clicked on the SharePoint Distribution OU and selected “Delegate Control…”. This opened the Delegation of Control Wizard. He specified the user name of the account that is running the application pool for our Central Administration.

NOTE: If you don’t know what your’s is, go to the front-end web server that is running your CA and under IIS > (your server) > Application Pools, find your the Application Pool that is running your CA. Right-click > Properties… and click on the Identity tab. The user name specified here will be the account that you are going to want to use. Ours is “sp_farm”.

Back to the Delegate Control wizard.

He added “sp_farm” as the account, clicked Next and specified “Create a custom task to delegate”, then selected Next.

Under the Active Directory Object Type screen, he selected: “This folder, existing objects in this folder, and creation of new objects in this folder”, then selected Next.

Under the Permissions screen, he selected “General”, “Property-specific”, and gave the sp_farm account “Write” access.

Now the sp_farm account has the ability to create contacts in the SharePoint Distribution OU.

3. Now we needed to configure our DNS server. I had him create a dns record entry for sharepoint.<our_internal_domain>.org and had him point it to the IP Address of our web front-end server running CA.

4. I then logged on to the web front-end server, stopped the Default SMTP Virtual Server, then completed the following steps:

A. Right-click on Default SMTP Virtual Server > New > Virtual Server…

B. Name: sharepoint.<our_internal_domain>.com > Next

C. All Unassigned > Next > Answer “Yes” to the prompt that comes up

D. Home Directory: C:\inetpub\mailroot

E. Domain: sharepoint.<our_internal_domain>.com > Finish

5. I then had to log into Central Administration. Under Operations > Topology and Services > Incoming e-mail settings I specified the following parameters:

NOTE: Under “Active Directory container where new distribution groups and contacts will be created:” I specified:

“OU=SharePoint Distribution,OU=Groups,DC=<servername>,DC=local”

6. I’m finally ready to set this thing up! So I went to my SharePoint site and navigated to my discussion board list. In this case, I am working with a Discussion Board for our Database Team. I then selected Settings > Discussion Board Settings.

I then clicked on the link “Incoming e-mail settings” under the communications heading. I configured the following parameters:

In this case, I have configured an e-mail address of dbaod@sharepoint.<our_internal_domain>.org to be used for this discussion board list.

Upon, selecting OK, it then….

BAM! (Melodramatic music playing)

I got a blanket “Error in application” response from SharePoint. Hmmmm, yes…very descriptive.

“There must be some sort of permission issue!” I thought. After hours and hours (did I say HOURS?) of frustrating blowing away and re-configuring, all it took was a simple restart of my web front-end server. You know the one that is running CA? Anyway, that is what worked for me.

Conclusion:

Now that it is working. When I click OK to save the “incoming e-mail settings” on my discussion board settings page. The contact “Database AOD DBA AOD Discussion Board” gets created under the “SharePoint Distribution” OU. It takes some time but, eventually, the e-mail address of “dbaod@sharepoint.<our_internal_domain>.org” get specified in the properties of this contact as well.

Now when an e-mail is sent to this address, an .eml file gets generated and sent to the drop folder on our Web front end server (you know, the one running CA). An SPTimer service grabs that file and posts it up on the appropriate SharePoint list. This process takes about 60 seconds for us.

By the way, Todd Klindt’s Blog has some useful information on all of this. Also, the white paper from Combined Knowledge is very helpful.

January 10th, 2008Yahoo! Weather Web Part

Weather Web Part ImageYeah, I know. You’re thinking this is frivolous; “What’s the point?”, you say. Well this is my first web part and I decided to go with something a little less “high profile”. However, I am still happy with it and think its got enough of that cool factor to warrant a post here.

It accesses the RSS feed from Yahoo! Weather and parses it for use within the web part. The code I provided is designed to retrieve the weather information for our location (Visalia, CA 93277) but could be easily modified for yours. Or, if you wanted, you could add a TextBox and have the user submit their own zip code!

There is A LOT of styling involved with making it look this way. I will include the styles as well as the plate graphics (which turns to gray after sunset). I would suggest, if you plan to use my styles, that you read my previous post on “Customizing the order of your stylesheets“. It’s a bit more work, but I like to have all the styles for the webparts I’ve developed in an independent stylesheet and not have those styles overwritten by the INFAMOUS core.css

I didn’t build this in a deployable package (still learning that), so this is just the C# code. You will have to build it into a .dll and deploy it to the GAC or place it into the bin folder. You will also have to set the web part namespace to be trusted in your web.config. Google will be your friend on all of that.

If all of this is just too daunting then I suggest trying Dustin Miller’s approach using SharePoint Designer (wuuuhhh!?) . The only problem I found with this, is that the datalist web part created in SharePoint Designer could not be shared easily between sites and users would not be able to add it to their own content pages (without my intervention).

Here are the source files: weather_webpart.zip

Anyone who has tried to brand their SharePoint sites has probably encountered the 5 stages of grief when learning about the core.css:

  1. Disbelief - “They couldn’t have possibly designed it this way; I must be missing something!”
  2. Anger – “This is ridiculous! What a waste of time and money!”
  3. Bargaining – “If I can get this to work the way I want, I promise I will love SharePoint forever.”
  4. Depression – “Maybe I’m not cut out for this SharePoint stuff. I should do something else. I wonder if there are any openings at Best Buy.”
  5. Acceptance – “Okay, I just won’t brand my site the way I wanted to do it.”

Essentially, Microsoft (in it’s infinite wisdom) has most of the styles, for any particular template, stored in the core.css.

There has been plenty of discussion on how one might modify those styles. Heather Solomon is certainly the resident expert on it. When diving in you might start to read about things like “ghosted” and “unghosted” or “customized” and “uncustomized”.

The reality is that you may only want to make one minor change to the core.css and not have the whole thing be sent into the file system in a “customized” state. Or maybe you’re a purist (read: anal-retentive) and you just don’t like the idea of your core.css being stored on the file system.


Understanding that a value in a stylesheet takes precedence in the order it was applied (hence the name “cascading”) you might start to think:

“Gee, if I create a new style in a new stylesheet with the same name of the one stored in the core.css, it should overwrite that style and all will be wonderful again!”

A-hem…not exactly.

By it’s very nature, core.css is designed to load after any other specified stylesheet. Therefore, no style can be overwritten because core.css is ALWAYS applied last.

CURSE YOU MICROSOFT!

So to change this behavior, we must hijack the rendering of the CSSLink server control in SharePoint.

CSSLink is used in order to load a specified stylesheet (using the DefaultUrl property). This will return all of the system stylesheets along with the path of the stylesheet you specified. All of this will be in order (yep, you guessed it) with the core.css sheet specified last.

The class that I built is rooted in the work of Michael Hofer and CleverWorkarounds.

This class creates a server control that is to be used in replace of CSSLink and is called EnhancedCssLink. I would read Michael Hofer’s blog on how you might add this class to your SharePoint site (build a .dll, mark it as safe in your web.config, deploy it to your SharePoint GAC or bin folder, etc.) If you can’t find out how to do it there, Google it man!

Michael Hofer’s class puts the core.css last in the order, which can be problematic. CleverWorkarounds improved on this idea by switching the core.css with a specified .css sheet. Ultimately, giving the specified stylesheet the highest priority. Cool!

I expanded on these two ideas by allowing the user to specify multiple stylesheets (separated by a comma). The DefaultCss property is now named “CSSPath”. Here’s an example on how to use it:

<PublishingEnhancements:EnhancedCssLink runat=”server” CSSPath=“../../_styles/master.css, ../../_styles/webparts.css”/>

This will now return the system stylesheets in the proper order with the specified stylesheets loaded last.

Finally, here is my code for the EnhancedCssLink class:

 

//Publishing Enhancements

namespace PublishingEnhancements

{

public class EnhancedCssLink : CssLink

{

public EnhancedCssLink() : base() { }

private string CSS;

public virtual string CSSPath

{

get

{

return CSS;

}

set

{

CSS = value;

}

}

protected override void Render(System.Web.UI.HtmlTextWriter output)

{

// Let base render the stylesheets

StringWriter sw = new StringWriter();

base.Render(new HtmlTextWriter(sw));

string renderedOutput = sw.ToString();

if (this.CSS == null) {

output.Write(renderedOutput);

}

else

{

// Split the styleSheets into an array

string[] styleSheets = renderedOutput.Split(new char[] { ‘\n’ }, StringSplitOptions.RemoveEmptyEntries);

if (styleSheets.Length == 0)

{

output.Write(renderedOutput);

}

else

// Render the system stylesheets, then render any specified stylesheets after that

{

string[] customStyleSheets = this.CSS.Split(new char[] { ‘,’ }, StringSplitOptions.RemoveEmptyEntries);

output.Write(string.Concat(styleSheets));

for (int i = 0; i < customStyleSheets.Length; i++)

{

output.Write(“<link rel=’stylesheet’ type=’text/css’ href=’” + customStyleSheets[i].ToString() + “‘ />”);

}

}

}

}

}

}

 


© 2007 travislowdermilk.com | iKon Wordpress Theme by TextNData | Powered by Wordpress | rakCha web directory