At Trinicom, the place I work, we are creating a new customer experience product. We lean heavily on good engineering practices and use Scrum as our development process.
For the application we defined cornerstones, or fundamentals: usability being one of them.
Because our application needs to be zero footprint web based, we use JavaScript a lot, mostly JQuery actually. We structure our JavaScript code using module and jquery widget patterns. We are thinking about implementing an mvc framework for Javascript, or use something like knockout.js, because we depend on real-time data.
Our application needs real-time info and because we develop for the future we are leveraging Websockets to make the real-time updates a reality.
As I mentioned we lean on good engineering practices, TDD is important for our C# code, so because we develop more and more JavaScript code, we are using TDD for our client-side code also.

QUnit
We use QUnit for our JavaScript unit tests. We use Continuous Integration and run our unit tests at every check-in, on TFS 2010. We wanted that for our JavaScript unit tests also.
So we started searching for information, there was very little. It seems that most ASP.NET devs do not integrate JavaScript unit tests into their builds, or do not unit test their JavaScript at all.

Web Client Developer Guidance
The Web Client Developer Guidance from Patterns & Practices on Codeplex provides some information on how to integrate QUnit unit tests into a TFS 2008 build. The problem was, that the guidance is incomplete and unclear. The P&P group created the QUnitExtensions.js that provides the familiar Asserts C# developers know and love, isTrue, isFalse, areEqual, IsNull, isNotNull, isUndefined, isNullOrUndefined, isNotNullNorUndefined.
The guidance exist of a ASP.NET MVC  app that takes JSON and serializes that to some xml format. The xml format is than persisted on disk. The JSON is provided by the QUnit testrunner that is extended and uses the url to a Controller of ASP.NET MVC app that takes JSON. The extended testrunner serializes the testresults into a hidden formfield and submits the contents of the field to the MVC app’s controller.

The format that was persisted to disk is unfamiliar and does not seem to map to any TFS format. The guidance says all you have to do is write the xml file to disk and tell the buildserver where it is by using an msbuild script and the result is written to the buildlog etc. That did not do the trick for us.

Workflow Activity
We created a Workflow activity that we use in our build workflow.

[RequiredArgument]
public InArgument ResultFile { get; set; }

[RequiredArgument]
public InArgument BuildDetail { get; set; }

protected override void Execute(CodeActivityContext context)
{
	IBuildDetail buildDetail = context.GetValue(this.BuildDetail);
	string file = context.GetValue(this.ResultFile);

	TestRun testRun = DeserializeTestRun(file);
	string outcome = GetOutcome(testRun.TestsFailed);

	IActivityTracking currentTracking = context.GetExtension().GetActivityTracking(context);
	IBuildInformationNode childNode = currentTracking.Node.Children.CreateNode();
	childNode.Type = currentTracking.Node.Type;

	IBuildStep  buildStep = childNode.Children.AddBuildStep("JS Unittest Build Step", string.Format("The Javascript Unittests for build: {0} have run", testRun.Name));
	if(testRun.TestsFailed > 0)
	{
	   IBuildError buildError = childNode.Children.AddBuildError(string.Format("The Javascript UnittestRun {0} ", outcome), DateTime.Now);
		buildError.Save();
	}

	WriteNewBuildInformationNode(currentTracking, string.Format("The Javascript UnittestRun for build: {0} {1}",  testRun.Name,outcome));
	WriteNewBuildInformationNode(currentTracking, string.Format("{0} test(s) run", testRun.TestsTotal));
	WriteNewBuildInformationNode(currentTracking, string.Format("{0} test(s) succesfull", testRun.TestsPassed));
	WriteNewBuildInformationNode(currentTracking, string.Format("{0} test(s) failed", testRun.TestsFailed));
	WriteTestCaseInformation(testRun.TestCases, currentTracking, buildDetail);

	SetBuildStatus(testRun.TestsFailed > 0, buildStep, buildDetail);

	if (testRun.TestsFailed == 0)
	{
		WriteToBuildSummary(buildDetail, string.Format("The Javascript UnittestRun for build: {0} {1}", testRun.Name, outcome));
	}

	buildStep.FinishTime = DateTime.Now;
	buildStep.Save();
	buildDetail.Information.Save();
}	

Listing 1

The activity takes a file and deserializes the xml into objects, it than uses the object graph to report into the buildlog if the unit test where succesfull, or not.

TestRun Domain objects

post_jsut_01_TestRun_classdiagram

Img 1: The TestRun domain

Image 1 shows the domain objects of the TestRun. A TestRun has one or more TestCases and a TestCase can have  an Output that is of the ErrorInfo type.
A TestCase has an OutCome and a StatusPassed, if an Exception was thrown, the details of that Exception are found in the Output property.
The TestRun has three properties that hold the result for the run, how many Tests where run (TestTotal), how many Test failed (TestFailed) and how many tests passed (TestsPassed).
The Activity ofcourse uses the three properties of the TestRun to decide wether the Run was succesfull or not. When the TestRun is not successful, the TestCases that failed are written to the buildlog and the build will fail.

ITestResultSerializer interface
The Web Client Developer Guidance contains an MVC application that uses an interface named: ITestResultSerializer. I wrote our own implementation that is simpler than the implementations provided by Patterns & Practices.
They use XmlTextWriter to write out the xml, I just serialize the objectgraph to xml.

ITestResultSerializer Tfs implementation

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Xml;
using System.IO;
using System.Xml.Serialization;
using QUnitTestResult.Contract;
using QUnitTestResult.Models.Tfs;

namespace QUnitTestResult.Models
{
    public class TfsTestResultSerializer : ITestResultSerializer
    {

        public void Serialize(TestRun testRun, string resultsFilePath, string fileName, string buildName)
        {
            string filename = Path.Combine(resultsFilePath, fileName);
            if (!Directory.Exists(resultsFilePath))
            {
                Directory.CreateDirectory(resultsFilePath);
            }

            using (var textWriter = new XmlTextWriter(filename, Encoding.UTF8))
            {
                var xmlSerializer = new XmlSerializer(typeof(TestRun));
                xmlSerializer.Serialize(textWriter, testRun);
            }
        }

    }
}

Listing 2: TfsTestResultSerializer

The TfsTestResultSerializer writes the xml to a location on disk. The build workflow knows this location and picks the xml up and the Activity publishes the results to the buildlog of our TFS 2010 TeamBuild.

Workflow – BuildTemplate

To make it possible to run JavaScript unittests in an TFS 2010 Teambuild, I made some changes to the workflow of the BuildTemplate.

Arguments
In the workflow I defined a few Arguments, that can be set in the builddefinition. The arguments are:

  • RunJavascriptUnittests (datatype boolean - default value = false)
  • JavascriptUnittestBrowserFiles (datatype List<string> – default value = new String() { “c:\program files\apple\safari.exe” })
  • TestresultAcceptorWebsite (datatype string – default value = “localhost:8081”)
  • JavascriptTestRunnerFile (datatype string – default value = “scripts/unittests/testrunner.htm”)

 

post_jsut_buildtemplate_args

Img 2: Buildtemplate arguments

 

In the default BuildTemplate there is an Activity that is called: “Run Tests”. You can find it if you follow the default BuildTemplate and click your way through the following path in the workflow:

Process > Sequence > Run On Agent > Compile, Test, and Associate Changesets and Work Items > Sequence > Compile, Test, and Associate Changesets and Work Items > Try Compile and Test > Compile and Test > For Each Configuration in BuildSettings.PlatformConfigurations > Compile and Test for Configuration > If Not DisableTests > Run Tests

Below the “If Then” Activity: “If Not TestSpec Is Nothing” I add my own “If Then Activity” and call it: “If RunJavascriptUnittests is True”.

post_jsut__02_RunTests_SequenceImg 3: Run Tests Sequence Build Workflow

In the If I check a boolean Argument I added to the builddefinition (as stated in the Arguments section above), that is used to specify if we want to run JaveScript Unittests in the build.

post_jsut__03_If_jsutests_need_torunImg 4: If RunJavaScriptUnitTests is set to true

If this argument is set to true, the “Then” branche in the If then Activity is folllowed. In the “Then" part I add a Sequence called: “Run Javascript Unittests”.

 post_jsut__04_ForeachbrowserexeinImg 5: Run JS UnitTests

The sequence holds a “For Each” Activity: “ForEach Browser exe in JavascriptUnittestBrowserFiles”.

post_jsut__05_ForeachtestrunnerinImg 6: ForEach browser exe in Argument

In the “ForEach” Activity all strings in the “JavascriptUnittestBrowserFiles” argument are iterated over, these strings hold the path and name of the executable of a browser on the buildserver (for example “C:\users\username\AppData\Local\Google\Chrome\Application\chrome.exe”).
Inside the Body another “ForEach” activity “Foreach testRunner in JavascriptTestRunnerFiles” exists.

Sequence

post_jsut_foreach_testrunner_in_jstestrunnerFiles

Img 7: For each testrunner in argument

This “ForEach” Activity will iterate over all strings in the Argument: “JavascriptTestRunnerFile”, this is so that we can have more than one html file containing Javascript unittests. Inside the body I put another Sequence activity (img 8).

post_jsut_sequence_actual_workImg 8: Sequence that does the work 

WriteBuildMessage
The “WriteBuildMessage” only writes to the buildlog for debugging purposes, also it comes in handy when someting goes wrong in a build sometimes.

Start testrunner js unittests (InvokeProcess)
Next the “Start testrunner js unittests” InvokeProces Activity will be run. The properties are shown in img  9.

post_jsut__10_StartTestRunnerJsUT

Img 9: InvokeProcess calls browser exe with html file as parameter

The “FileName” property is filled with the “browserFile” variable from img 6 and holds the exe of the browser.

The ‘”Arguments” property  is the string that concatenates all variables to a string that holds the important pieces of the puzzle.

post_jsut__11_StartTestRunnerJsUTArguments
Img 10: Arguments property The string looks like listing 4:

"""file:///" + BuildDirectory + "\Binaries\_PublishedWebsites\" + JavascriptTestRunnerFile + "?submitResults=" + TestresultAcceptorWebsite + "/qunit/acceptor&resultfilePath=" + TestResultsDirectory + "&build=" + BuildDetail.BuildNumber + "&platform=" + platformConfiguration.Platform + "&flavor=" + platformConfiguration.Configuration + "&teamproject=" + BuildDetail.BuildDefinition.TeamProject + """"

Listing 4

Listing 4 shows how the Arguments are built up: It tells the browser to open the “JavascriptTestRunnerFile” which holds the html filename in our project as a file on the filesystem, we use the “"BuildDirectory” variable and the kwoledge where the websites are published in this directory to point to the right file.
Next we add querystring variables and values so the testrunner javascript file knows where to send the results of the tests and which build we run, what solutionconfiguration we are building etc .

 

browserFile.Substring(0, browserFile.LastIndexOf("\"))

Listing 5 Working Directory

Listing 5 shows the value for the Working directory property.

FindMatchingFiles
The next step is to check if the results are written to disk as expected, with the “FindMatchingFiles” activity using the MatchPattern shown in Listing 6:

TestResultsDirectory + String.Format("\QUnitResult_{0}.xml", BuildDetail.BuildNumber)

Listing 6 MatchPattern

 

When the file is found the variable JSUnitTestMatchingFileResult is filled with the number of results.

If Js UnitTestResultFile is found
The “If Then” activity “If Js UnitTestResultFile is found” is run.

post_jsut__14_IfFileFoundImg 11: “If Then” activity

If the results Count() is equal to 1 the Workflow Activity mentioned in the beginning of the post is called (“JsUnitTestResultReader”), if not a message is written to the buildlog.

post_jsut__15_ReadJSResultsActivityImg 12: Properties JsUnitTestResultReader 

We pass the BuildDetail to the Activity, so the context is known.
We also need the name and path of the resultfile, to read the results and write them to the buildlog.

post_jsut__16_ReadJSResultsActivityResultFileImg 13: ResultFile property

TestResultsDirectory + String.Format("\QUnitResult_{0}.xml", BuildDetail.BuildNumber)

Listing 7 ResultFile

Now the circle is full, and the results can be written to the buildlog.

post_jsut_resultbuildlog_posImg 14: Passed unittests in log details

Of course when a test fails the build fails partially (like normal unittests) and the result shows in the buildlog summary, the results like shown in img 14 are only visible in the details (diagnostics) part of the buildlog.

Henry Cordes
My thoughts exactly…


Ever since ASP.NET MVC Framework 2.0 has released I did not have a change to work with the new validation logic.
The DataAnnotations that are the only place where you have to keep logic related to validation, no more having to write the same logic in several places.

Today I wanted to give it a try, since I can delete lots of code from my views, if it works as promised.
I want to use jquery.validate.js (ships out of the box with Visual Studio 2010 and ASP.NET MVC Framework 2.0. But to my surprise only files that support the MicrosoftMvcValidation.js are available with the project template for a ASP.NET MVC 2 Web Application.

MicrosoftMvcJQueryValidation.js
With the ASP.NET MVC 2 RTM release a file named MicrosoftMvcJQueryValidation.js was shipped. I downloaded the RTM once again and got the MicrosoftMvcJQueryValidation.js file.

In my view I now add the following script tags:



Listing 1: Script tags

Enable validation on a view clientside
Than below script tags, I put the following call to enable validation on the client.

<% Html.EnableClientValidation(); %>

Listing 2: Html.EnableClientValidation

This call (listing 2) bubbles up the validation rules as Json data, the MicrosoftMvcJQueryValidation.js leverages jQuery to do the client side validation.

I added the code from listing 1 and 2 onto the  logon.aspx from a vanilla ‘file > new project > ASP.NET MVC 2 Web Application’ and it worked as I expected.

Contents MicrosoftMvcJQueryValidation.js
Because it took me some effort to get the MicrosoftMvcJQueryValidation.js I will put the code here:

/// 
/// 

// register custom jQuery methods

jQuery.validator.addMethod("regex", function(value, element, params) {
    if (this.optional(element)) {
        return true;
    }

    var match = new RegExp(params).exec(value);
    return (match && (match.index == 0) && (match[0].length == value.length));
});

// glue

function __MVC_ApplyValidator_Range(object, min, max) {
    object["range"] = [min, max];
}

function __MVC_ApplyValidator_RegularExpression(object, pattern) {
    object["regex"] = pattern;
}

function __MVC_ApplyValidator_Required(object) {
    object["required"] = true;
}

function __MVC_ApplyValidator_StringLength(object, maxLength) {
    object["maxlength"] = maxLength;
}

function __MVC_ApplyValidator_Unknown(object, validationType, validationParameters) {
    object[validationType] = validationParameters;
}

function __MVC_CreateFieldToValidationMessageMapping(validationFields) {
    var mapping = {};

    for (var i = 0; i < validationFields.length; i++) {
        var thisField = validationFields[i];
        mapping[thisField.FieldName] = "#" + thisField.ValidationMessageId;
    }

    return mapping;
}

function __MVC_CreateErrorMessagesObject(validationFields) {
    var messagesObj = {};

    for (var i = 0; i < validationFields.length; i++) {
        var thisField = validationFields[i];
        var thisFieldMessages = {};
        messagesObj[thisField.FieldName] = thisFieldMessages;
        var validationRules = thisField.ValidationRules;

        for (var j = 0; j < validationRules.length; j++) {
            var thisRule = validationRules[j];
            if (thisRule.ErrorMessage) {
                var jQueryValidationType = thisRule.ValidationType;
                switch (thisRule.ValidationType) {
                    case "regularExpression":
                        jQueryValidationType = "regex";
                        break;

                    case "stringLength":
                        jQueryValidationType = "maxlength";
                        break;
                }

                thisFieldMessages[jQueryValidationType] = thisRule.ErrorMessage;
            }
        }
    }

    return messagesObj;
}

function __MVC_CreateRulesForField(validationField) {
    var validationRules = validationField.ValidationRules;

    // hook each rule into jquery
    var rulesObj = {};
    for (var i = 0; i < validationRules.length; i++) {
        var thisRule = validationRules[i];
        switch (thisRule.ValidationType) {
            case "range":
                __MVC_ApplyValidator_Range(rulesObj,
                    thisRule.ValidationParameters["minimum"], thisRule.ValidationParameters["maximum"]);
                break;

            case "regularExpression":
                __MVC_ApplyValidator_RegularExpression(rulesObj,
                    thisRule.ValidationParameters["pattern"]);
                break;

            case "required":
                __MVC_ApplyValidator_Required(rulesObj);
                break;

            case "stringLength":
                __MVC_ApplyValidator_StringLength(rulesObj,
                    thisRule.ValidationParameters["maximumLength"]);
                break;

            default:
                __MVC_ApplyValidator_Unknown(rulesObj,
                    thisRule.ValidationType, thisRule.ValidationParameters);
                break;
        }
    }

    return rulesObj;
}

function __MVC_CreateValidationOptions(validationFields) {
    var rulesObj = {};
    for (var i = 0; i < validationFields.length; i++) {
        var validationField = validationFields[i];
        var fieldName = validationField.FieldName;
        rulesObj[fieldName] = __MVC_CreateRulesForField(validationField);
    }

    return rulesObj;
}

function __MVC_EnableClientValidation(validationContext) {
    // this represents the form containing elements to be validated
    var theForm = $("#" + validationContext.FormId);

    var fields = validationContext.Fields;
    var rulesObj = __MVC_CreateValidationOptions(fields);
    var fieldToMessageMappings = __MVC_CreateFieldToValidationMessageMapping(fields);
    var errorMessagesObj = __MVC_CreateErrorMessagesObject(fields);

    var options = {
        errorClass: "input-validation-error",
        errorElement: "span",
        errorPlacement: function(error, element) {
            var messageSpan = fieldToMessageMappings[element.attr("name")];
            $(messageSpan).empty();
            $(messageSpan).removeClass("field-validation-valid");
            $(messageSpan).addClass("field-validation-error");
            error.removeClass("input-validation-error");
            error.attr("_for_validation_message", messageSpan);
            error.appendTo(messageSpan);
        },
        messages: errorMessagesObj,
        rules: rulesObj,
        success: function(label) {
            var messageSpan = $(label.attr("_for_validation_message"));
            $(messageSpan).empty();
            $(messageSpan).addClass("field-validation-valid");
            $(messageSpan).removeClass("field-validation-error");
        }
    };

    // register callbacks with our AJAX system
    var formElement = document.getElementById(validationContext.FormId);
    var registeredValidatorCallbacks = formElement.validationCallbacks;
    if (!registeredValidatorCallbacks) {
        registeredValidatorCallbacks = [];
        formElement.validationCallbacks = registeredValidatorCallbacks;
    }
    registeredValidatorCallbacks.push(function() {
        theForm.validate();
        return theForm.valid();
    });

    theForm.validate(options);
}

// need to wait for the document to signal that it is ready
$(document).ready(function() {
    var allFormOptions = window.mvcClientValidationMetadata;
    if (allFormOptions) {
        while (allFormOptions.length > 0) {
            var thisFormOptions = allFormOptions.pop();
            __MVC_EnableClientValidation(thisFormOptions);
        }
    }
});

Listing 3: Contents MicrosoftMvcJQueryValidation.js

Henry Cordes
My thoughts exactly…


I used to like Syntaxhighlighter, but when I started using blogengine.net I did not implemented it. In blogengine.net a plugin is available from manoli.net.
The bad part is, that I have to go to the manoli.net website and generate html from my C#, SQL, Javascript and other code, every time I want to use code in my posts.

So I started to look into Syntaxhighlighter again. It matured quite a bit, but still lacks type support (manoli also does not support this, by the way!). Still, I like it a lot, I suppose it can save me lots of time.

Beauty of Code jQuery plugin

Lars Corneliussen wrote a jQuery plugin for SyntaxHighlighter and it makes the process of incorporating it on a html page a lot smoother. I used this with my first tryout to use the plugin in blogengine.net. I think it is an elegant way to use syntax highlighter, so i want to share, how it works. Ofcourse you have to reference jQuery in the head element of your page. In addition to that we add a reference to the jquery.beautyofcode.js (the code written by Lars).

	
	

Listing: 1

To initialize the highlight functionality, you have to add the following javascript code to a page:

$.beautyOfCode.init({
	brushes: ['Plain', 'CSharp', 'Xml', 'JScript', 'Css', 'Sql']}
);

Listing: 2

All I have to do now is to start using it, as followes:

    
	<code class="JScript">
		alert('Hi, I love highlighted syntax!');   
	</code>  

Listing: 3

The point is that my blog does not accept the pre tags with a nested code tag that has a class attribute. TinyMCE is used and somehow it strips the entire class attribute, thus the highlighting never takes place…

I am now using the default way, without jQuery. This works niceley, with a caveat: XML tags (html) are transformed to uppercase, when saving a post through the TinyMCE standard blogengine.net manner of creating and editing posts, but also when using Windows LiveWriter. I will try to find out more about this, because I really do not like uppercase tagnames!

The default syntax highlighter configuration

First you need to reference the scripts and stylesheets syntax highlighter needs to do the job, so in the head of the page you need to highlight code on, you add the following js and css references:













Listing: 4

Of course you only need to add the the brushes that you really use.

Next you need to instantiate the javascript object, you need to add the following javascript in a script block (also in the head of the page):

SyntaxHighlighter.config.clipboardSwf = 'scripts/clipboard.swf';
SyntaxHighlighter.all();

Listing: 5

The clipboard.swf (Flash file) is used to make it possible for the user that visits your site, to copy the code to his or her clipboard, without an alert from the Operating System. The .all() method initializes takes care of the actual highlighting.

Now I can use it, by letting syntax highlighter know what brush to use, like this:

    
	alert('Hi, I love highlighted syntax!');    

Listing: 6

The next few posts, I will try it out some more, but the first impression is a positive one.

Henry Cordes
My thoughts exactly…


Using jQuery with MS CRM 4.0

Published 11/21/2008 by Henry in CRM | Javascript | jQuery

Doing a MS CRM 4.0 project, where the requirement is to create a toolbar button on a detailform.
This button needs to open a window (prefferably a modal window). In this window users can make some selections and when submitting, some processing will be done using these selections, this is not important for this post.

When thinking about this requirement, I really wanted to use something like the ASP.NET AJAX Control ToolKit's Modal Popup. Also I am really impressed by jQuery, a JavaScript library, that is an elegant and easy to use abstraction over JavaScript. It simplifies document traversing, event handling, animation and Ajax interactions. I knew about jqModal a jQuery plugin that simplifies display of dialogs and modal windows.

NOT FULLY SUPPORTED
If you are a developer
working with MS CRM, you know that it is higly customizable, but within it's own boundries. An important matter on this project particularly is that customizations need to be supported by Microsoft. What I am about to explain is NOT FULLY SUPPORTED, because I will use external.js files, using a link element to import it in the page. But I want to explain also, that it is possible to do this without using external files, you could paste the contents of the 'jquery-1.2.6.min.js' file into the onload function of the detailform's property window.

I wanted to give it a try, because, well just think about the possibilities for a while. If you can use jQuery in CRM development, a lot of possibilities open up.

FIRST STEPS
So the first thing to do is to put the 'jquery-1.2.6.min.js' file in a '_customscript' folder in the CRMWEB root folder. Next I need a way to reference the file, so it's contents can be called from the page. Like I already posted in my blogpost External js file and CRM, I like to do this using the Msxml2.XMLHTTP object. The function reads the contents of a file and imports all functions found into the current namespace, so they are accessible.

   1:  function load_script (url) 
   2:  { 
   3:      var x = new ActiveXObject("Msxml2.XMLHTTP"); 
   4:      x.open('GET', url, false); x.send(''); 
   5:      eval(x.responseText); 
   6:      var s = x.responseText.split(/\n/); 
   7:      var r = /^function\s*([a-z_]+)/i; 
   8:      for (var i = 0; i < s.length; i++) 
   9:      { 
  10:          var m = r.exec(s[i]); 
  11:          if (m != null) 
  12:              window[m[1]] = eval(m[1]); 
  13:      } 
  14:  } 
  15:   
  16:  load_script("/_customscript/jquery-1.2.6.min.js"); 
  17:  load_script("/_customscript/jqModal.js"); 
  18:  load_script("/_customscript/jqDnR.js"); 
  19:  load_script("/_customscript/customscript.js"); 

Listing 1

As you can read in listing 1, the 'jqModal.js' file is also referenced, this holds the jqModal library, I also load 'jqDnR.js' a jQuery library that supports dragging and resizing. I also load 'customscript.js', this is a custom javascript file that will hold the function that is called when the button on the details window is clicked.

CSS STYLESHEET
Because we do not want to mess with the stylesheets used by CRM we also need to add a stylesheet containig the modal window's styles to the filesystem. I made a css folder below the _customscript folder, the folder where I put the javascript libraries. You could add these styles through Dom manipulation in JavaScript also, to keep the customization supported by Microsoft!
I use the following code, you can do this in the OnLoad function of the details form, or in a separate function, that you call from the Onload function. I use the function that also checks if the reference exists already, but for simplicity put this in the On Load after listing 1.

   1:  var styleSheet = '/_customscript/css/modalWindow.css';
   2:  var head = document.getElementsByTagName("head")[0];
   3:  var linkElement = document.createElement('<link rel="stylesheet" type="text/css" href="'+ styleSheet +'" />');
   4:  head.appendChild(linkElement);

Listing 2

In listing 2 I create a link element with a reference to a style sheet and append this element to the head element of the html page (CRM's detail form).

Listing 3 will display the contents of the css style sheet:

   1:  .jqmWindow  
   2:  { 
   3:  	display: none; 
   4:  	position: fixed; 
   5:  	top: 17%; 
   6:  	left: 50%; 
   7:  	margin-left: -300px; 
   8:  	background-color: #E4EEF9; 
   9:  	border-width: 1px; 
  10:  	border-style: solid; 
  11:  	border-color: #0a6cce; 
  12:  	padding: 0px; 
  13:  	width: 374px; 
  12:  	height: 264px; 
  13:  } 
  14:  
  15:  .jqmOverlay { filter: alpha(opacity=70); opacity: 0.7; }
  16:  
  17:  .jqmTitle 
  18:  { 
  19:  	cursor: move; 
  20:  	background-color: #6793CC; 
  21:  	border: solid 1px #6793CC; 
  22:  	color: #FFFFFF; 
  23:  	font-weight: bold; 
  24:  	width: 100%; 
  25:  } 
  26:  
  27:  .jqmTitleText { float: left; position: relative; }
  28:  
  29:   /* Fixed posistioning emulation for IE6  
  30:      Star selector used to hide definition from browsers other than IE6  
  31:      For valid CSS, use a conditional include instead */  
  32:  * html .jqmWindow 
  33:  { 
  34:    position: absolute; 
  35:    top: expression((document.documentElement.scrollTop || document.body.scrollTop) + 
  36:         Math.round(17 * (document.documentElement.offsetHeight || document.body.clientHeight) / 100) + 'px'); 
  37:  } 
  38:  
  39:  .jqmIFrame { width: 100%; height: 100%; }
  40:  
  41:  .jqmClose 
  42:  {  
  43:    width: 20px; 
  44:  	font-weight: bold; 
  45:    float: right; 
  46:  	cursor: pointer; 
  47:    color: #FFFFFF; 
  48:  	background: #6793CC; 
  49:    border: 1px solid #6793CC; 
  50:  }  

Listing 3

CREATE DIV (MODAL WINDOW)
Next we need to add the div to CRM's details page. I wanted to this using the DOM, but for some reason it would not work. If anybody knows the reason why, please let me know!
Instead i used  the following approach, again: you can do this in the OnLoad function of the details form, or in a separate function, that you call from the Onload function.
In the real world project I use the function, but for simplicity in this example, I just put this in the On Load after listing 2.

   1:  // Not using dom to add new elements, because for some reason that does not work!
   2:  var body = document.getElementsByTagName("body")[0];
   3:  var modalDiv = document.createElement('<div id="dialog" class="jqmWindow" ></div>');
   4:  var titleDiv = document.createElement('<div id="jqmTitle" class="jqmTitle"></div>');
   5:  var modalTitle = document.createElement('<span id="jqmTitleText" class="jqmTitleText"></span>');
   6:  modalTitle.innerText = 'Documentenuitvoer';
   7:      
   8:  var modalButton = document.createElement('<button class="jqmClose" ></button>');
   9:  modalButton.innerText = 'X';
  10:      
  11:  var modalIFrame = document.createElement('<iframe id="jqmContent" class="jqmIFrame" frameborder="0" scrolling="no"></iframe>');
  12:  titleDiv.appendChild(modalButton);
  13:  titleDiv.appendChild(modalTitle);
  14:      
  15:  modalDiv.appendChild(titleDiv);
  16:  modalDiv.appendChild(modalIFrame);  
  17:      
  18:  body.appendChild(modalDiv);

Listing 4

As you can read in listing 4 first I get the body element and assign it to the 'body' variable. Than I create two div elements, the reason I do not create a div element and use setAttribute to set the attributes, but create the element in one line using a string containing the whole tag is that I could not get it to work, again let me know if you do! The first div will be our modal window. The second with id 'jqmTitle' is the header. Next I Create a span that will contain the text for the header, a (close) button that will be placed in the header on the right side.
And an iframe, in this iframe any (custom) page can be loaded.

INITIALIZE MODAL WINDOW USING jQUERY
To initialize the modal window, we have to add listing 5 to the OnLoad function of the details form in CRM after listing 4.

   1:  $().ready(function() {
   2:    $('#dialog').jqm();
   3:    $('#dialog').jqm().jqDrag('#jqmTitle'); 
   4:  });

Listing 5

In Listing 5 we use jQuery syntax. We attach the code to the OnReadyState eventhandler of the document. when the document is fully loaded the element with id 'dialog' will be registered as a jqModal window by line 2. Line 3 registers the dragging functionality of the element with id 'jqmTitle' inside the modal window (our header div).

CUSTOMIZE ISV.CONFIG XML
Now we only have to do two more things, the first is that we have to change isv.config.xml to create the button on our detail form, I use the contact and need the modal window functionality on the contact entity's form. I made the following change to the isv.config.xml:

   1:  <!-- Microsoft Customer Relationship Management Entities (Objects) -->
   2:  <Entities>
   3:      <Entity name="contact">
   4:        <MenuBar>
   5:          <CustomMenus>
   6:            <Menu>
   7:              <Titles>
   8:                <Title LCID="1043" Text="Documenten" />
   9:              </Titles>
  10:              <MenuItem JavaScript="ShowModalWindow();">
  11:                <Titles>
  12:                  <Title LCID="1043" Text="Plaats document in DocumentUitvoer" />
  13:                </Titles>
  14:              </MenuItem>
  15:            </Menu>
  16:          </CustomMenus>
  17:        </MenuBar>
  18:      </Entity>
  19:  </Entities>

Listing 6

Listing 6 displays part of the xml, the IsvConfig element holds an element configuration , which holds the Entities element. I need the contact Entity. The MenuBar element holds CustomMenus, which holds Menu. I added a Menu element with Title "Documenten" (LCID="1043" which means dutch). The Menu element holds MenuItem elements. I added a MenuItem element With a dutch Title and a JavaScript attribute that has the value 'ShowModalWindow()'. When we import the isv.config.xml and publish the customization changes, we see the menuitem is added.

CREATE JAVASCRIPT FUNCTION THAT SHOWS MODAL WINDOW 
In listing 1 on line 19 we saw that the external javascript file 'customscript.js' is loaded in the OnLoad function of the details form. 'customscript.js' is a custom  javascript file that holds the 'ShowModalWindow()' function.
As I mentiod earlier we had to do 2 more things, the first was the customization of the isv.config.xml and the second is to create this function: 

   1:  var customPagesVirtualDir = 'Custompages';
   2:   
   3:  function ShowModalWindow()
   4:  {
   5:      var contactId = crmForm.ObjectId;
   6:      var popUpUrl = '/' + customPagesVirtualDir + '/Popup.aspx?contactid=' + contactId;
   7:      $('#jqmContent').html('').attr('src', popUpUrl);
   8:      $('#dialog').jqmShow({modal: true});
   9:  }

Listing 7

Addition:  

I added line 1 after Imane pointed out the variable customPagesVirtualDir was not declared, thanks for telling me!

Listing 7 displays the 'ShowModalWindow()' function, the crmForm.ObjectId is assigned to a variable contactId. A variable popupUrl is filled with the url to a custom aspx page. Line 7 is jQuery syntax and assigns the url to the src attribute of the iframe inside the modal window. Line 8 opens the modal window and assigns true to the modal property, So the behaviour of the dialog will be like a modal dialog.

EXAMPLE

CRM's detail form contact entity
Picture 1: Contact details form

In the menu a button is added:


Picture 2: Menu item

 When a user clicks on the menu-item, the function ShowModalDialog is called in the external js file that is referenced in the OnLoad function of the page where the jQuery plugin loads the modal window (client-side!) and the users sees picture 3:

jQuery and jqModal in action on a contact details form
Picture 3: jQuery and jqModal in action on a contact details form

CONCLUSION
Personally I think this is awsome, jQuery is real easy to use and complex matters seem simple. It is a joy to use jQuery with CRM. This combination opens up a world of possibilities. Ofcourse CRM is a web application and customizable, so you could expect external libraries targeted to web applications to work with it. But for me the experience to open a modal dialog like this on a CRM details window without hacking CRM code...
Well I will stop rambling, you can judge yourself.

Henry Cordes
My thoughts exactly...


I am really proud that my first .NET Magazine article is publiced.
I wrote this article with my co-worker Dennis van de Laar. The topic is AJAX and Javascript.


Dutch .NET Magazine #22


 Ajax is niet meer weg te denken

It is a small download, it''s PDF and it is Dutch 

Henry Cordes
My thougts exactly...