ASP.NET

AJAX-Enabled Web-Parts: solving the problem of changing Client IDs

AJAX-Enabled Web-Parts: solving the problem of changing Client IDs One of the most challenging aspects of building SharePoint-enabled web parts is using JavaScript to interact with specific controls. As a consequence of how SharePoint renders controls with id’s in Sharepoint WebParts, it’s near impossible to write clean JavaScript. From a maintenance perspective I HATE using server-side code to render JavaScript. It’s inelegant and reeks of a lack of forethought. Yuck yuck yuck. It’s like sweeping dust under the rug. Wherever possible, webpart-specific javascript should be held within a single .js file (stored in the 12Hive). The problem is that Sharepoint alters a control’s ID during the page rendering process. So, a label with the ID “label1” might become “ctl00_label1”. The naming process is entirely unpredictable. This makes interacting with a specific DIV, INPUT or any other html element very difficult because you can’t predict the rendered IDs during development. This is especially problematic if you’re looking to host several instances of a webpart on a single page. One really terrible way of dealing with this is to render the html yourself by adding literal controls ala

LiteralControl l = new LiteralControl();
l.text = '
blah
'; this.Controls.Add(l);

This is a terrible approach which is severely limiting and unnecessary. To tackle the problem I’ve devised the following solution which revolves around creating a Javascript class to accompany the web part:

  • create properties to store the dynamically generated IDs of every html component to wish to interact with
  • create pointers to every html component you wish to interact with
  • create an init function. As input you pass the clientID of the webpart AND the id’s of every component you wish to interact with.
  • this function will store the id’s using the properties
  • this function will also attach the pointers to their respective objects
  • this function should also attach any eventhandlers
  • create any supporting functions (i.e. webservice calls, webservice receivers)

This approach embraces the absence of reliable identification info. It is also sympathetic to having multiple copies of a webpart on the same page. Here’s what the javascript might look like

function UpdatingLabelWebPart() 
{ 

	var me = this;	//required to reference the object at runtime  
	this.clientID = null;	//ids 
	this.labelID = null; 
	this.buttonID = null;
	this.label = null;	//controls 
	this.button = null;
	this.register = function (clientID, labelID, buttonID) 
	{ 
		me.clientID = clientID; //init the id 
		me.labelID = labelID;
		me.buttonID = buttonID;
		me.attachToButton();
	}  
	
	this.attachToButton = function() 
	{ 
		//if the controls haven't loaded yet 
		if (document.getElementById(me.buttonID) == null || document.getElementById(me.labelID) == null) 
		{ 
			setTimeout(me.attachToButton, 50);
		} 
		else 
		{ 
			//register the controls 
			me.label = document.getElementById(me.labelID);
			me.button = document.getElementById(me.buttonID);
			//attach any events button.AttachEvent("click", me.buttonClicked);
		} 
	}  
	
	this.buttonClicked = function(sender, args) 
	{ 
		me.label.innerHTML = "Hello World";
	} 
}   
 

This approach lets you have multiple instances of the same AJAX-enabled webpart and saves you the hassle of having to write annoying javascript (having to write me.label is hardly a chore).

By admin on September 16, 2008 | Ajax, ASP.NET, c#, Mac, Sharepoint | A comment?

‘Webservice’ is undefined effor with ASP.NET AJAX

I came across an annoying problem when playing with the ASP.NET Ajax extensions earlier today.  I was trying to call a script-enabled web-service but kept getting a javascript error telling me my service was undefined.  It’s been a month or so since I’ve used MS Ajax and needed a refresher.

Some background:

  • My project namespace is ajaxTest
  • My webservice class is helloTest
  • My webservice function is helloWorld

I enabled the web service using the [System.Web.Script.Services.ScriptService()] attribute

I put the script manager code block in place and included a reference to the service.

<asp:ScriptManager ID=”ScriptManager1″ runat=”server”>
<Services>
<asp:ServiceReference Path=”helloTest.asmx” />
</Services>
</asp:ScriptManager>

I then created some javascript to call the web service

function testScriptService()
{
var tmp = helloTest.helloWorld(onResult, onTimeout, onError);
}

It was at this point I received the “helloTest is undefined” message.

A quick search on google revealed two websites which helped me.  The first had the right answer (in my situation).  Strangely, the second website had a slightly different solution which seems to have helped a number of people, but I was unable to figure out why (I didn’t work for me).

So, the solution that worked:

You need to fully qualify the javascript with the namespace of your webservice.

Namespace.Class.Function >> ajaxTest.helloTest.helloWorld

The solution which didn’t work for me but has worked for others is to use the following structure:

Namespace.Services.Class.Function >> ajaxTest.Services.helloTest.helloWorld

No idea why the difference, but hey.

Credit where credit’s due, thanks to Omen’s blog and Ryan at Solutek.

By admin on August 2, 2008 | Ajax, ASP.NET, c# | 8 comments

Dataview web parts in your master page cause errors on subsites

If you’ve had this problem, then two things to make you feel better:

  1. The solution is very easy
  2. The cause of the problem is stupid stupid stupid. You can bang your head against the keyboard, there’s no way you could have known.

This post, meanders a bit, so if you want the solution, jump to the bottom.

Still here? Sucker.

So, what are the prerequisites to causing this problem:

  • you have a dataview web part inserted into a master page;
  • the dataview refers to data in a list; and
  • the list is scoped at the root web of the site collection.

I think that’s it.

Symptoms

The web part will display correctly anywhere in your root web, but nowhere else. I’m calling it a “web” here because it maps to how Sharepoint’s API. If it helps for you to think about it as a single “Site” in WSS2.0 fashion, go nuts. When you visit subsites, you see the following error:

“Unable to display this Web Part. To troubleshoot the problem, open this Web page in a Windows SharePoint Services-compatible HTML editor such as Microsoft Office SharePoint Designer. If the problem persists, contact your Web server administrator.”

How helpful, especially if you’re an administrator or developer. As if they’d have a magic wand to fix the problem. The error might as well say:

“You’re screwed. Unless you’re a clever developer, give up, swear at Sharepoint and call tech support. Not many people at your company are going to be able to help you.”

Which is silly, because once we come to the cause, the error is completely within the realms of detection. To the developer who wrote that line of code… 10 minutes on the naughty step.

The problem is seemingly unresolveable. If you try to add the dataview directly to a page in a subsite, it works fine, but master page? Sharepoint tells you to go whistle.

Cause

The cause? Sharepoint doesn’t know which “web” the list comes from.

The Dataview web part can take parameters, including, parameters which tell pages where to find a list. In Sharepoint Designer, when you edit a master page, it fails to insert a parameter telling pages the list lives in the root.

Bad Sharepoint Designer! Bad!

The problem compounded because taking a dataview created on a subsite and dumping the code into the masterpage doesn’t work either (which makes it very tricky to solve).

To figure this out, I took the following steps in Sharepoint Designer:

  • Created a dataview in the master page (worked at top level and nowhere else)
  • Created the dataview with the exact same settings on a page in a subsite
  • Copied the code snippets into a text-editor and compared them side-by-side

A side-by-side comparison revealed several things…

Web part parameters in the master

Web part parameters in a page

First, the datasource control (a child of the dataview web part) is referred to with a different namespace in the master page than in the page itself. This is why you can’t copy dataview code from a regular page into a master page. Maybe there’s a good reason for having done this, but I think the masterpage programmer forgot to speak to the regular page programmer. However, that isn’t the root cause, but something to be aware of.

Second, THERE’S ANOTHER PARAMETER. OMG!!! Froth, swear, stomp, swear, slam your keyboard. Feel better?

Knowing a parameter exists and knowing the syntax are two different things. Before finding the actual code, I was pretty confident the problem was down to subsites not knowing where the list was.

The worst thing is that the url of the web is actually part of the dataview web part, down at the very bottom of the code. Why doesn’t this flow through to the datasource? Naughty step!

Web part parameters

Solution

It’s simple enough to copy the parameter from the subsite dataview into the masterpage dataview.

<WebPartPages:DataFormParameter Name=”WebURL” ParameterKey=”WebURL” PropertyName=”ParameterValues” DefaultValue=”/”/>

I don’t believe the approach is necessary if you’re referring to a list in a subsite (the parameter should be inserted automatically).

Hope that helps!

Relative URLs in Sharepoint Web Parts

If you’re using STSDev for web part development, it becomes very simple to deploy files into the 12Hive (which in some circumstances is preferable to using embedded resources). As a consequence, it becomes tricky to create hyperlinks to resource files because:

  • you can’t guarantee the web part is sitting in root of the site collection (which would make for simple hyperlinks ala “_layouts/styles/myPart/myCSS.css”)
  • you can’t know that your site collection is located at the root of the web server (which would make for simply hyperlinks ala “_layouts/styles/myPart/myCSS.css”

So, to create simple relative urls from code (as opposed to using the <% $SPURL:~sitecollection/_layouts/styles/myPart/myCss.css %> approach) I used something I found on Ari Bakker’s website.

Deep underneath the SPContext object is a property called ServerRelativeUrl which is attached to the Site(SPWeb) and Site collection(SPSite) objects.

These can be used by the web part to refer to resource files deployed along with it. So, something like the following should work anywhere inside a site collection:

string cssLocation = SPContext.Current.Site.ServerRelativeUrl + “_layouts/styles/myPart/myCSS.css”;

By admin on April 21, 2008 | ASP.NET, c#, Code, Development, Sharepoint | A comment?