Nov 212011
 

Everybody loves to use InfoPath forms. And let’s face it, we all wish we could just take them and put them into CRM. What I’ve written is a simple way to do that without the hassle of figuring out how to bind the InfoPath form schema to a custom WCF service. It still doesn’t do everything I want it to, but it works, so I figured I’d go ahead and put it out there for everyone to look at, play with, and even use.

If you are interested in this solution, you can download the following:

  • Managed Solution (this is the only thing you need)
  • Unmanaged Solution (in case you want to modify mine, like add grid support, it is on my to-do list)
  • Source Code (this is in case you would prefer to use a WCF service or if you just want to play around with my code)

Once you’ve installed the managed solution, you’ll need to do the following to create / setup an InfoPath form:

  1. For this particular InfoPath integration, you need to create InfoPath Forms that mirror a CRM Entity.
    Note: You can always create a new entity for the form data.

  2. Rename the default form Group to the name of the entity (ex: Contact)

  3. Inside CRM create the InfoPath form (it is an entity included in the solution), select the entity and save the form.

  4. Once it has been saved, you can copy the ID using the “Copy ID” link in the header.

    Note: If you do not select the Allow Any CRM Field checkbox, you will need to add the individual Fields, which can be found in the left navigation.

  5. In the InfoPath Form Designer, add a field named SubmissionCode with the Default Value as the ID you just copied.

    Note: Using the submission code allows us to check and see if the form is valid, active, and allowed.

  6. Add a field for each of the fields in CRM that you want to be available, selecting the appropriate data type.

    Note: Unfortunately I have not had the time to test the fields and have been using the checkbox on the infopath form record to enable any/all CRM fields. However, I did not want to delay releasing the code and information as it currently is. If someone identifies a problem with this or any other section of the code, I’ll be happy to look into it and work on resolving the issue.

  7. Layout the fields on the form, then click File > Submit Options > To Email

  8. Enter the Queue email or where you want new forms to be submitted.

  9. Click Next.

  10. Click Finish.

  11. Save and close the form designer.
  12. Open the form and test submission.

Once the email comes into CRM, the process of reading the form is easy; you just open it up and click the Import InfoPath button on the ribbon.

Usually I would post the source-code and comment on how it works in great detail, but this time, it really depends on which component. There is a plugin and WCF service written in C# as well as two Jscript files, one for the buttons and one for the InfoPath form inside CRM that creates a drop-down of the entities in CRM. I would suggest downloading the unmanaged solution and source-code if you want to delve into those items.

Now, for complex situations or form conversions, binding the InfoPath schema is the way to go. Using that method (the one described in detail on Philo’s Weblog) you can import the InfoPath form schema into Visual Studio:

  1. Create an InfoPath form and lay it out the way you want it.
  2. Extract the form files (File | Extract Form Files) to a location you can find later.
  3. Close the InfoPath form designer.
  4. Open the Visual Studio command prompt.
  5. Change to the directory where you extracted the files
    cd c:\myinfopathfiles
  6. Run xsd against the myschema.xsd in that directory, and specify the namespace you would like to use.
    Note: My WCF namespace is CrmInfoPathService and my plugin namespace is InfoPathPlugin.
    xsd.exe myschema.xsd /classes /l:cs /n:InfoPathPlugin
  7. Then you can deserialize the xml into the object and store it in CRM or any other system using the logic embedded in your code.

The key point to my solution was not to do something that everyone does on occasion, but to make something so that I can leverage it in the future and not have to create a new integration every time I wanted to leverage a new form. Now, on that note, here are the list of things that if you delve into, I’d like to know, a.k.a. my to-do list for this project:

  • Add a button onto the grid for activities
  • Create a way to handle child entities.
  • Modify the plugin/WCF code to link the created entities to the form email using connections.
  • Change the button on the form to monitor for the results (success/failure) and report back to the user.
  • Any great ideas my readers think should be added!
Oct 042011
 

Here is another web-resource html control to expand your CRM forms. It is a slider control for CRM with step control.

Below is an example of the slider control in action, and you can click here to download a solution to import the web-resources into your CRM deployment (it is a managed solution with no entities, just like the star rating control). If you want the unmanaged solution you can download it here.

Ok, once you have the web-resource loaded into your CRM deployment, you’ll need to create a new field to store the slider value and put it on the form, but uncheck the visible box.

Add the Web-Resource to the form, check the box to display the label on the form, and then enter your parameters. I like using a pipe separator. The available parameters are:

  • min: minimum value
  • max: maximum value
  • field: the CRM field to store the value in
  • step: the step increase
  • stepoverride: don’t use the step when a value is manually entered

Make sure to click on the formatting tab and set the number of rows to 1, scrolling to Never, and uncheck the box to display the border.

Then move the hidden field down and out the way. If you have a lot of fields you are accessing through form scripts you could just drop them into a hidden section.

Here is the code for the slider control HTML web resource.

<html>
<head>
  <title>Slider Control</title>
  <script type="text/javascript">
var Settings = {};
Settings.Min = 0;
Settings.Max = 100;
Settings.Step = 5;
Settings.Value = 50;
Settings.Field = null;
Settings.AllowStepOverride = false;

var Slider = {};
Slider.Drag = false;

Slider.Initialize = function Slider(id,min,max,step,value,onchange) {
  Settings.Min = min;
  Settings.Max = max;
  Settings.Step = step;
  Slider.Value = value;
  
  var ihtml = "";
  ihtml += "<table width='100%' cellspacing='0' cellpadding='0'><tr>";
  ihtml += "<td class='left-bracket'>&nbsp;</td><td class='value'>";
  ihtml += "<input class='value' id='value' type='text' onchange='Slider.SetValue();' />";
  ihtml += "</td><td class='right-bracket'>&nbsp;</td>";
  ihtml += "<td class='slider-left' /><td class='slider-area' id='"+id+"-area' >";
  ihtml += "<div id='"+id+"-selector' class='slider-selector' /></td>"
  ihtml += "<td class='slider-right' /></tr></td></table>";
  var p = document.getElementById(id);
  p.innerHTML = ihtml;

  Slider.Element = document.getElementById(id+"-area");
  Slider.Element.attachEvent("onmousedown",Slider.OnMouseClick);
 
  Slider.Id = id;
  Slider.Selector = document.getElementById(id+"-selector");
  Slider.Selector.attachEvent("onmousedown",Slider.OnMouseDown);
  Slider.Selector.attachEvent("onmousemove",Slider.OnMouseMove);
  Slider.Selector.attachEvent("onmouseup",Slider.OnMouseUp);
  Slider.Selector.attachEvent("onmouseleave",Slider.OnMouseUp);
 
  Slider.UpdatePosition(); 
}

Slider.SliderRatio = function() {
  var num =  Settings.Max - Settings.Min;
  var den = parseFloat(Slider.Element.offsetWidth)-parseFloat(Slider.Selector.offsetWidth);
  return den/num;
};

Slider.UpdatePosition = function () {
  Slider.Position = Math.round(Slider.Value*Slider.SliderRatio());
  Slider.SetPosition();
};

Slider.OnMouseClick = function(e) {
  Slider.Position = e.clientX - (8+Slider.Selector.offsetLeft-parseInt(Slider.Selector.style.left.replace("px","")));
  Slider.SetPosition();
  Slider.OnChange(Slider.Value);
}

Slider.SetPosition = function() {
  if (Slider.Position<0) Slider.Position = 0;
  var max = Slider.Element.offsetWidth-Slider.Selector.offsetWidth;
  if (Slider.Position>max) Slider.Position = max;
  
  Slider.Selector.style.left = Slider.Position + "px";

  var i = Math.round(Slider.Position/Slider.SliderRatio()) + Settings.Min;
  i = (Math.floor(i/Settings.Step))*Settings.Step;

  Slider.Value = i;
  
  var val = document.getElementById('value');
  if (val===null) return;
  val.value = i;
}

Slider.SetValue = function() {
  var val = document.getElementById('value');
  if (Settings.AllowStepOverride) {
    Slider.Value = val.value;
  } else {
    Slider.Value = Math.round(val.value/Settings.Step)*Settings.Step;
  }
  if (Slider.Value<Settings.Min) Slider.Value = Settings.Min;
  if (Slider.Value>Settings.Max) Slider.Value = Settings.Max;
  Slider.UpdatePosition(); 
}

Slider.OnMouseDown = function(e)  {
  Slider.Drag = true;
}

Slider.OnMouseMove = function(e) {
  if (Slider.Drag) {
    Slider.Position = e.clientX - (8+Slider.Selector.offsetLeft-parseInt(Slider.Selector.style.left.replace("px","")));
    Slider.SetPosition();
  }
};

Slider.OnMouseUp = function(e) {
  if (Slider.Drag) {
    Slider.Drag = false;
    Slider.OnChange(Slider.Value);
  }
};

Slider.OnChange = function(val) {
  parent.Xrm.Page.data.entity.attributes.get(Settings.Field).setValue(val);
};

var InitializeSlider = function() {
  var data = WebResource.GetDataParams();
  var sliderBox = document.getElementById("sliderBox");
  
  for (var i in data)
  {
    switch (data[i][0].toLowerCase())
	{
	  case 'min': Settings.Min = parseInt(data[i][1],10); break;
	  case 'max': Settings.Max = parseInt(data[i][1],10); break;
	  case 'step': Settings.Step = parseInt(data[i][1],10); break;
	  case 'value': Settings.Value = parseInt(data[i][1],10); break;
	  case 'field': Settings.Field = data[i][1]; break;
	  case 'stepoverride': Settings.AllowStepOverride = (data[i][1]=='true'); break;
	  default:break;
	}
  }
  
  Slider.Initialize("slider",Settings.Min,Settings.Max,Settings.Step,Settings.Value,null)
  
  if (Settings.Field!=null) {
    var val = parent.Xrm.Page.data.entity.attributes.get(Settings.Field).getValue();
	if (val!==null) {
	  var num = parseInt(val);
	  Slider.Value = num;
	  Slider.UpdatePosition();
	}
  }
};

var WebResource = {};

WebResource.GetDataParams = function()
{ //modified version of: http://technet.microsoft.com/en-us/library/gg327945.aspx
  //Get the any query string parameters and load them
  //into the vals array

  var vals = new Array();
  if (location.search !== "")
  {
    vals = location.search.substr(1).split("&");
    for (var i in vals)
    {
      vals[i] = vals[i].replace(/\+/g, " ").split("=");
    }

    //look for the parameter named 'data'
    var found = false;
        var datavals;
    for (var j in vals)
    {
      if (vals[j][0].toLowerCase() == "data")
      {
        found = true;
        datavals = decodeURIComponent(vals[j][1]).split("|");
        for (var k in datavals)
        {
          datavals[k] = datavals[k].replace(/\+/g, " ").split("=");
        }
        break;
      }
    }
    if (found) { return datavals; }
  }
  return null;
};
  </script>
  <style type="text/css">
html, body {
  padding: 0px;
  margin: 0px;
  border: 0px;
  background-color: rgb(246, 248, 250);
  overflow:hidden;
}
.slider-left, .slider-area, .slider-right {
  background-image:url(img/bar.png);
  background-repeat:repeat-x;
  height:17px;
  padding:0px;
  margin:0px;
  cursor:hand;
}
.slider-left, .slider-right {
  width: 7px;
}
div.slider-selector {
  background-image:url(img/selector.png);
  background-repeat:no-repeat;
  width:15px;
  height:17px;
  position: relative;
  cursor:hand;
}
td.left-bracket, td.right-bracket {
  background-repeat:no-repeat;
  height:17px;
  width:6px;
  display:inline;
}
td.left-bracket {
  background-image:url(img/left_bracket.png);
  
}
td.right-bracket {
  background-image:url(img/right_bracket.png);
}
td.value {
  background-image:url(img/val_bk.png);
  background-repeat:repeat-x;
  height:17px;
  width:30px;
}
input.value {
  border: none;
  font:11px segoe ui,tahoma,arial;
  height:17px;
  width:30px;
  background-color:transparent;
  vertical-align:top;
  padding-top:1px;
  text-align:center;
}
  </style>
</head>
<body onload="InitializeSlider();">
<div id="slider"></div>
</body>
</html>

Thanks to Manish Mistry comment on my star control for refreshing the iframe when you change the value, you could do the same on this control. However, you could also just set the value and update the position. The only reason I get the control’s object and it’s id is because I’m not 100% sure the id will always match, even though all my tests indicate it does.

var control = Xrm.Page.ui.controls.get('WebResource_Score');
var id      = control.getObject().id;
var frame   = document.frames[id];
frame.Slider.Value = 50;
frame.Slider.UpdatePosition();

One of my TODO’s is to join my web resources HTML form controls into a single library once I have enough of them so that people can leverage them in an easier manner. If anyone has any ideas for custom controls for Dynamics CRM, let me know and I’ll see if I can build it.

Sep 202011
 

Update:Here is the most up to date version for UR12. It fixes some style-sheet issues that caused the stars not to display.

To expand on the technique used for my previous post on making the state field a drop down here is a star rating field control so that you can get a nice visual star rating control that is configurable.

Below is an example of the star rating control in action, and you can click here to download a solution to import the web-resources into your CRM deployment (it is a managed solution with no entities).

Ok, once you have the web-resource loaded into your CRM deployment, you’ll need to create a new field to store the rating and put it on the form, but uncheck the visible box.

Add the Web-Resource to the form, and check the box to display the label on the form.

Then enter your parameters (I like using a pipe as a separator). The available parameters are:

  • min: minimum star rating – really should always be 1
  • max: maximum star rating – maybe 5 or 10
  • field: the CRM field to store the value in
  • showvalue: whether or not to display the numerical value

Make sure to click on the formatting tab and set the number of rows to 1, scrolling to Never, and check the box to display the border.

Then move the hidden field down and out the way. If you have a lot of fields you are accessing through form scripts you could just drop them into a hidden section.

Now that you know how to set it up, let’s go ahead and look at the code.

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>Rating Control</title>
  <link href="/_common/styles/fonts.css.aspx?lcid=1033" rel="stylesheet" type="text/css" />
  <link href="/_common/styles/global.css.aspx?lcid=1033" rel="stylesheet" type="text/css" />
  <link href="/_common/styles/select.css.aspx?lcid=1033" rel="stylesheet" type="text/css" />
  <script type="text/javascript">
var Rating = {};

// Default Values
Rating.ShowValue = false;
Rating.Max = 5;
Rating.Min = 1;
Rating.Field = '';
Rating.Value = null;

Rating.Highlight = function(index)
{
  var val = document.getElementById('rating_value');
  if (index===null) { 
    index = -1; 
    val.innerHTML = '[Not Set]';
  } else {
    val.innerHTML = '['+index+'/'+Rating.Max+']';
  }
  for(var i=Rating.Min; i<=Rating.Max;i++)
  {
    if (i<=index) {
      document.getElementById("rating_"+i).className = "staron";
    } else {
      document.getElementById("rating_"+i).className = "staroff";
    }
  }
};

Rating.HolderRollover = function() {
  if (Rating.Value !== null) {
    var r = document.getElementById('rating_remove');
    r.className = 'stardel';
  }
};

Rating.HolderRollout = function() {
  var r = document.getElementById('rating_remove');
  r.className = 'stardel2';
};

Rating.Rollover = function(index) {
  Rating.Highlight(index);
};

Rating.Rollout = function()
  {
  Rating.Highlight(Rating.Value);
};

Rating.Set = function(index) {
  Rating.Value = index;
  if (index<0) {
    parent.Xrm.Page.data.entity.attributes.get(Rating.Field).setValue(null);
  } else {
    parent.Xrm.Page.data.entity.attributes.get(Rating.Field).setValue(Rating.Value);
  }
  Rating.Highlight(index);
};

Rating.Initialize = function() {
  var data = WebResource.GetDataParams();
  for (var i in data)
  {
    switch (data[i][0].toLowerCase())
        {
          case 'min': Rating.Min = parseInt(data[i][1],10); break;
          case 'max': Rating.Max = parseInt(data[i][1],10); break;
          case 'field': Rating.Field = data[i][1]; break;
          case 'showvalue': Rating.ShowValue = (data[i][1] == 'true'); break;
          default:break;
        }
  }
  var d = document.getElementById('starholder');
  for (var j=Rating.Min;j<=Rating.Max;j++)
  {
    var el = "<span id='rating_"+j+"' " +
             "onmouseover='Rating.Rollover("+j+");' " +
             "onmouseout='Rating.Rollout();' " +
             "onclick='Rating.Set("+j+");' />";
    d.innerHTML += el;
  }

  var rel = "<span id='rating_remove' class='stardel2' " + 
            "onclick='Rating.Set(null);' onmouseover='Rating.Rollover(null);' />";
  d.innerHTML += rel;

  var val = "<span id='rating_value' class='value' " + 
            (Rating.ShowValue ? "" : "style='display:none' ") + 
            "/>";
  d.innerHTML += val; 
   
  Rating.Value = parent.Xrm.Page.data.entity.attributes.get(Rating.Field).getValue();
  Rating.Highlight(Rating.Value);
};

var WebResource = {};

WebResource.GetDataParams = function()
{ //modified version of: http://technet.microsoft.com/en-us/library/gg327945.aspx
  //Get the any query string parameters and load them
  //into the vals array

  var vals = new Array();
  if (location.search !== "")
  {
    vals = location.search.substr(1).split("&");
    for (var i in vals)
    {
      vals[i] = vals[i].replace(/\+/g, " ").split("=");
    }
    
    //look for the parameter named 'data'
    var found = false;
        var datavals;
    for (var j in vals)
    {
      if (vals[j][0].toLowerCase() == "data")
      {
        found = true; 
        datavals = decodeURIComponent(vals[j][1]).split("|");
        for (var k in datavals)
        {
          datavals[k] = datavals[k].replace(/\+/g, " ").split("=");
        }
        break;
      }
    }
    if (found) { return datavals; }
  }
  return null;
};
  </script>
  <style type="text/css">
.staroff,.staron,.stardel,.stardel2 {
	height:15px;
	margin:1px 1px 1px 2px;
	width:15px;
	display:inline-block;
}

staroff,.staron,.stardel {
	background:no-repeat
}

.starholder {
	background:#FFF;
	padding:1px
}

.stardel {
	background:url(remove.png)
}

.staron {
	background:url(staron.png)
}

.staron,.staroff {
	margin-right:5px
}

.staroff {
	background:url(staroff.png)
}

.value {
	font:11px segoe ui,tahoma,arial;
	color:#000;
	height:17px;
	vertical-align:middle
}
  </style>
</head>

<body onload="Rating.Initialize();">
  <table cellspacing="0"
         cellpadding="0"
         width="100%"
         summary="star table">
    <tr>
      <td id="cell_dd">
        <div id="starholder"
             style="width:100%;"
             onmouseover="Rating.HolderRollover();"
             onmouseout="Rating.HolderRollout();"></div>
      </td>
    </tr>
  </table>
</body>
</html>
These are the image files used: (they are hidden in the managed solution)