Programming, technology, and CRM – from a Belgian programmer exiled to Missouri
  • rss
  • Home
  • Contact Me
  • Welcome

A multi-select picklist for MS CRM

Nicolas Galler | August 22, 2008

One of the difference between the MSCRM and the Saleslogix editors is the richness of the controls included with Saleslogix.  For example, the picklist available by default with MSCRM does not support multiple selection.  Fortunately it is easy to remedy using a little bit of Javascript in the form.

The key requisites were:

  • should be able to manage the values for the picklist using the same interface as regular picklists
  • should not have to customize the code (just paste the base code and add one line to initialize the picklist)
  • does not disrupt tab navigation (I did not 100% succeed on that one because you can only tab forward, not backward)

image

To make it work I simply created 2 attributes: "HobbiesList" points to the picklist, and "Hobbies" saves the actual values (this wastes a field in the contact table but we only lose 4 bytes).  Both attributes are present on the form but at runtime the HobbiesList is hidden and converted to a multiple selection list to be displayed only when you tab into the Hobbies textbox.

image

This is the javascript I used in the "Load" event on the form.  This part is generic and does not have to be customized – I just paste it at the beginning of the code.  I struggled a bit to get the positioning right, the rest is pretty classic stuff:

// make the textbox act as a multi select picklist including the elements of the 
// given select control
function makeMultiSelectPicklist(txt, pkl){
    txt.associatedPicklist = pkl;
    pkl.associatedTextbox = txt;
    txt.readonly = true;

    // hide containing row
    for(var p = pkl; p ; p = p.parentNode){
    if(p.tagName == "TR"){
        // hide the entire row (the picklist will be moved out)
        p.style.display = "none";
        break;
    }
    }

    // setup position for the multi select picklist
    var container = document.createElement("span");
    container.style.position = "relative";
    container.style.top = "0px";
    container.style.left = "0px";
    container.style.width = "0px";
    container.style.height = "0px";
    txt.parentNode.insertBefore(container, txt);
    container.insertBefore(pkl, null);
    pkl.style.display = "none";
    pkl.style.position = "absolute";
    pkl.style.top = "0px";
    pkl.style.left = "0px";
    pkl.multiple = "true";

    txt.onfocus = function(e) {
    var txt = this;
    var pkl = txt.associatedPicklist;
    var values = "|" + txt.value.replace(/; /g, "|") + "|";
    pkl.style.display = "block";
    pkl.style.width = txt.offsetWidth + "px";
    for(var i=0; i < pkl.options.length; i++){
        pkl.options[i].selected = new RegExp("\\|" + pkl.options[i].text + "\\|").test(values);
    }
    pkl.focus();
    }
    pkl.onblur = function(e) {
    var pkl = this;
    var txt = pkl.associatedTextbox;

    var s = [];
    for(var i=0; i < pkl.options.length; i++){
        if(pkl.options[i].text && pkl.options[i].selected){
        s.push(pkl.options[i].text);
        }
    }
    txt.value = s.join("; ");
    pkl.style.display = "none";
    }

    // break reference to avoid leak on IE6
    txt = null;
    pkl = null;
}

And finally I add this line at the end of the Load event to initialize the picklist:

makeMultiSelectPicklist(document.all.new_hobbies, document.all.new_hobbieslist);

A little bit more work than in Saleslogix but not too terrible!  And, it works somewhat better for tab navigation.  One thing worth noting – the Javascript editor on the CRM designer is so terrible, it is a lot easier to do the development in a separate file then paste it in.

Comments
1 Comment »
Categories
MSCRM
Tags
Javascript, MSCRM
Comments rss Comments rss
Trackback Trackback

Automatic Assembly Version Number

Nicolas Galler | August 6, 2008

Under Properties\AssemblyInfo.cs you can set the version number – this shows up under the file’s details in Windows:

image

There is a nifty msbuild task that was distributed by Microsoft a while back called "AssemblyInfoTask" (I think it came with one of the Team Foundation Server releases) that can automatically set it to the current date which is very handy to quickly find out which version of a particular DLL is at a customer site.  However there are a few issues with the strategy:

  • because the version number is a 16-bit number it has some issues with assemblies built after 2007 (there is a work around for it – as you can see I only have month and day)
  • it updates AssemblyInfo.cs (no easy way to customize that) which is usually under version control… so this causes a lot of unnecessary commits and conflicts (trivial to resolve, but still annoying)
  • since it updates AssemblyInfo.cs every time it runs and not just when the version number needs to change it will cause the assembly to be rebuilt completely every time which makes the build process slower.

Alternatively there is a task in the "MSBuild Community Task" that can be used to generate a VersionInfo.cs file – just as good as AssemblyInfo but because this only contains the version it does not need to be checked into source control alleviating the 2nd issue above (at this point you comment out the AssemblyVersion attribute from the AssemblyInfo.cs file).

Another one from MSBuild Community Task is "SvnInfo" – it can be used to retrieve the Subversion revision number (there are other, equivalent tasks for other version control system).  Using this is just as good if not better than the date so we avoid the 1st issue (though this will come back when my SVN number gets above 65536!  But I have a long way to go for that).

There was still one issue which is the file would always be generated thus preventing the incremental build from shortening the build process as it is supposed to (in C or Java we can somewhat get away with that since the objects are built on a file-by-file basis but in C# they are always built into assemblies).

Therefore I created a very very simple task (don’t laugh, it is my first msbuild custom task!) that generates the file but replaces it only if the number is updated:

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Build.Utilities;
using System.IO;

namespace SSSWorld.MsBuild
{
    /// <summary>
    /// Task used to generate a VersionInfo file with the AssemblyVersion and AssemblyFileVersion
    /// attributes.
    /// Contains logic to avoid overwriting the file if it exists and the content is identical.
    /// </summary>
    public class GenerateVersionInfo : Task
    {
        public UInt16 MajorNumber { get; set; }
        public UInt16 MinorNumber { get; set; }
        public UInt16 RevisionNumber { get; set; }
        public UInt16 BuildNumber { get; set; }
        public String OutputFileName { get; set; }

        public GenerateVersionInfo()
        {
            MajorNumber = 0;
            MinorNumber = 0;
            RevisionNumber = 0;
            BuildNumber = 0;
            OutputFileName = null;
        }

        public override bool Execute()
        {
            String code = GenerateCode();
            String existingCode = null;
            if (File.Exists(OutputFileName))
                existingCode = File.ReadAllText(OutputFileName);
            if (!code.Equals(existingCode))
            {
                File.WriteAllText(OutputFileName, code);
                Log.LogMessage("Generated VersionInfo file {0}", OutputFileName);
            }
            else
            {
                Log.LogMessage("VersionInfo file {0} is up to date", OutputFileName);
            }

            return true;
        }

        private String GenerateCode()
        {
            StringBuilder buf = new StringBuilder();

            buf.AppendLine("// Code generated automatically by SSSWorld.MsBuild.GenerateVersionInfo")
                .Append("[assembly: System.Reflection.AssemblyVersion(\"")
                .Append(MajorNumber).Append(".")
                .Append(MinorNumber).Append(".")
                .Append(RevisionNumber).Append(".")
                .Append(BuildNumber == 0 ? "*" : BuildNumber.ToString())
                .AppendLine("\")]")
                .Append("[assembly: System.Reflection.AssemblyFileVersion(\"")
                .Append(MajorNumber).Append(".")
                .Append(MinorNumber).Append(".")
                .Append(RevisionNumber).Append(".")
                .Append(BuildNumber)
                .AppendLine("\")]");
            return buf.ToString();
        }
    }
}

And I adapted the AssemblyInfoTask target file to call on this task:

<?xml version="1.0" encoding="utf-8"?>
<!-- This targets file includes all the necessary information to automatically increment build numbers as part of
     a regular build process. To use it simply include it in your project file after any other includes. The typical
     include line looks like this:
     
     <Import Project="...\SSSWorld\msbuild\AssemblyVersion.target"/>
     
     and make sure you have a Properties\VersionInfo.cs file in your project (it will be overwritten by this task
     and should NOT be checked in version control)
  -->
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <UsingTask AssemblyFile="SSSWorld.MsBuild.dll" TaskName="SSSWorld.MsBuild.GenerateVersionInfo"/>

  <!-- These properties can be overridden to specify the major/minor version number -->
  <PropertyGroup>
    <MajorNumber Condition="'$(MajorNumber)'==''">1</MajorNumber>
    <MinorNumber Condition="'$(MinorNumber)'==''">0</MinorNumber>
    <!-- Default will be to get the SVN revision number as revision number -->
    <RevisionNumber  Condition="'$(RevisionNumber)'==''">0</RevisionNumber>
  </PropertyGroup>

  <!-- Re-define CoreCompileDependsOn to ensure the assemblyinfo files are updated before compilation. -->
  <PropertyGroup>
    <CoreCompileDependsOn>
      $(CoreCompileDependsOn);
      GenerateVersionInfoFile
    </CoreCompileDependsOn>
  </PropertyGroup>

  <Import Project="$(MSBuildExtensionsPath)\MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>    

  <Target Name="GenerateVersionInfoFile">
    <SvnInfo LocalPath="." Condition="$(RevisionNumber) == 0">
      <Output TaskParameter="Revision" PropertyName="RevisionNumber"/>
    </SvnInfo>
    <GenerateVersionInfo OutputFileName="Properties\VersionInfo.cs"
      MajorNumber="$(MajorNumber)"
      MinorNumber="$(MinorNumber)"
      RevisionNumber="$(RevisionNumber)"/>
  </Target>
</Project>

What then needs to be done to get the version number into any project is to:

  • Remove AssemblyVersion and AssemblyFileVersion attribute from AssemblyInfo.cs
  • Add a VersionInfo.cs under the Properties folder of the project (does not have to contain anything as it will be overwritten automatically – and make sure it is NOT checked into source control)
  • Manually edit the project file and add the import line… Make sure the DLL is in the same directoy as the target file.
  • If desired, the Major and Minor version numbers can be specified in the project file by adding a “MajorNumber” or “MinorNumber” property under the <PropertyGroup> element (typically near the very top of the project file)

Optionally the following command can also be run to prevent the "unsafe project" warning dialog (replace with actual path of target file):

reg add HKLM\Software\Microsoft\VisualStudio\9.0\MSBuild\SafeImports /v AssemblyVersion /t REG_SZ /d "E:\Projects\SSSWorld\msbuild\AssemblyVersion.target"

Note that the key name is HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\9.0\MSBuild\SafeImports on Windows64.

Instead of using SvnInfo to get the revision number it can be obtained using the <Time> task, also part of the MSBuild Community Task.  Just make sure that the number is below 65536 as it needs to fit in 16 bits.

It is very simple but I have uploaded it to http://code.msdn.microsoft.com/AssemblyVersion in case it is useful to anybody else.  It was interesting at least to see how easy it was to create and use an msbuild custom task – I was relieved to find that it was not necessary to sign the assembly and install it to the GAC, and that you can use it right away.

Comments
2 Comments »
Categories
Programming
Comments rss Comments rss
Trackback Trackback

Categories

  • Experiments (4)
  • Interesting (1)
  • MSCRM (1)
  • Programming (60)
  • Rant (3)
  • Saleslogix (34)
  • Tricks (8)
  • Uncategorized (24)

Post History

  • 2010
    • January (3)
    • March (1)
  • 2009
    • March (2)
    • April (1)
    • May (3)
    • June (3)
    • July (1)
    • September (3)
    • October (2)
    • December (5)
  • 2008
    • January (9)
    • February (4)
    • March (9)
    • April (1)
    • May (5)
    • June (8)
    • July (1)
    • August (2)
    • September (1)
    • November (1)
    • December (3)
  • 2007
    • January (3)
    • February (7)
    • March (1)
    • April (3)
    • May (6)
    • June (2)
    • July (1)
    • August (2)
    • September (5)
    • October (3)
    • November (5)
    • December (4)
  • 2006
    • January (2)
    • September (1)
    • November (3)
    • December (4)
  • 2005
    • April (1)

Meta

  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org
rss Comments rss valid xhtml 1.1 design by jide powered by Wordpress get firefox