Most of the examples of using dialogs in SharePoint refer to calling other pages, effectively creating a popup iframe. This approach is both useful and reasonably well documented (Charlie Holland’s blog has the best examples I’ve seen to date).
<script> function ShowDialog() { var options = { url: 'http://www.google.com', autoSize:true, allowMaximize:true, title: 'Test dialog', showClose: true, }; var dialog = SP.UI.ModalDialog.showModalDialog(options); } </script> <a href="javascript:ShowDialog()">Boo</a>
What isn’t well documented is using SharePoint modal dialogs to render dynamically generated html. When you call SP.UI.ModalDialog.ShowModalDialog you need to pass in a SP.UI.DialogOptions object. Charlie kindly posted the list onto MSDN, but with regards to passing in html it’s incorrect. It seems to indicate that raw html can be passed in.
<script> function ShowDialog() { var htmlString = 'hello world'; var options = { html: htmlString, autoSize:true, allowMaximize:true, title: 'Test dialog', showClose: true, }; var dialog = SP.UI.ModalDialog.showModalDialog(options); } </script> <a href="javascript:ShowDialog()">Boo</a>
However if you try this you’ll just get an error like this:
Message: Object doesn't support this property or method Line: 2 Char: 18225 Code: 0 URI: http:///_layouts/sp.ui.dialog.js?rev=IuXtJ2CrScK6oX4zOTTy%2BA%3D%3D
This is because the string that gets passed in is evaluated for nodeType (if you want to check for yourself, see around line 764 in SP.UI.Dialog.debug.js, there is a function that the html string is passed into with a definition that looks like $13_0: function($p0).
What this means is that you have to pass in a DOM element.
<script> function ShowDialog() { var htmlElement = document.createElement('p'); var helloWorldNode = document.createTextNode('Hello world!'); htmlElement.appendChild(helloWorldNode); var options = { html: htmlElement, autoSize:true, allowMaximize:true, title: 'Test dialog', showClose: true, }; var dialog = SP.UI.ModalDialog.showModalDialog(options); } </script> <a href="javascript:ShowDialog()">Boo</a>
Do that, and you’ll get your dynamic-html dialog!
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:
- 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).
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:
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.
Last articles