Close Opportunity in D365 CE with Alert.js 2.1

There is an article on Magnetism about how to use Alert.js 3.0 to mimic an opportunity close dialog. This is an example of how to use the open-source Alert.js 2.0 to mimic the opportunity close function. Alert.js 3.0 is $1200 and I definitely recommend it if you have the budget, but if not, you can still use 2.0 to do achieve similar functionality

So without further chit chat, here is what you do.

  1. Download and install Alert.js 2.1 on your CRM instance.
  2. Put the Opportunity entity in a solution without any sub-components.
  3. Create a JavaScript web resource and call it OpportunityClose.js or whatever you want to call it.
  4. Put the code listed below in the OpportunityClose.js
  5. Download and open Ribbon Workbench in XrmToolBox or your CRM instance.
  6. Add a button as seen in Image 1.
  7. Add a command as seen in Image 2.
  8. Create an Enable Rule and add it to the command as seen in Image 3.
Image 1: Adding the button. Note that the command will be empty until you create it and then add it in the button.
Image 2: Adding the command
Image 3: Adding a Display Rule to the command so that it only displays when the opportunity is active.
// Called from ribbon button, passing through first primary record ID
function showCloseAsWon(firstPrimaryItemId, primaryControl) {  
var context = primaryControl.context;
var pid = firstPrimaryItemId.replace( /[{}]/g, '' );

var title = "Close Opportunity"; 

//Build Status Reason Option Set values
var options = "";
var req = new XMLHttpRequest();"GET", context.getClientUrl() + "/api/data/v9.1/stringmaps?$select=attributevalue,value&$filter=attributename eq 'statuscode' and  objecttypecode eq 'opportunity'", false);
req.setRequestHeader("OData-MaxVersion", "4.0");
req.setRequestHeader("OData-Version", "4.0");
req.setRequestHeader("Accept", "application/json");
req.setRequestHeader("Content-Type", "application/json; charset=utf-8");
req.setRequestHeader("Prefer", "odata.include-annotations=\"*\"");
req.onreadystatechange = function() {
    if (this.readyState === 4) {
        req.onreadystatechange = null;
        if (this.status === 200) {
            var results = JSON.parse(this.response);
            for (var i = 0; i < results.value.length; i++) {
                var attributevalue = results.value[i]["attributevalue"];
                var value = results.value[i]["value"];
                var opt = "<option "+"id='" + value +"' value='"+ attributevalue.toString() +"'>"+ value +"</option>";
                options = options.concat(opt);
        } else {
           options = "<option value='5'>Lost</option>";
var style = "<style>" 
            + ".header { padding-top: 10px;}"
            + "p { font-size: 14px; }"
            + ".alert-label     { line-height: 22px; margin-top: 8px; } "
            + ".alert-textarea  { width: 76%; height: 160px; border:1px solid #AAA; padding: 2%; } "
            + ".alert-select    { padding: 4px 8px; width: 60%; } "
            + ".alert-button    { padding: 10px 20px; font-size: 14px; background-color: white; border: 1px solid #AAA; margin: 10px 10px 0 0; min-width: 100px; } "
            + ".alert-button:hover    { background-color: #EEE; } "
            + ".alert-button:focus    { background-color: #DDD; } "
            + "</style>";

var subtitle =  "<div class='header'>"
                + "<p><strong>Provide the following information about why this opportunity is being closed.</strong></p>"
                + "</div>";
var status =  "<div class='container'>"
            + "<div id='stdiv'><p class='alert-label'>Status</p>"
            + "<div><select class='alert-select' name='status' id='st' onchange='javascript: var s=1==this.value?document.querySelector(&quot;#srdiv&quot;).style.display=\"none\":document.querySelector(\"#srdiv\").style.display=\"inherit\";' onfocusout='javascript: statusValue = this.value;'>"
            + "<option id='Lost' value='2'>Lost</option>"
            + "<option id='Won' value='1'>Won</option>"
            + "</select></div></div>"
            + "<input type='hidden' value=" + pid + " id='op'></input>";
var reason1 = "<div id='srdiv'><p class='alert-label'>Status Reason</p>"
              + "<div><select class='alert-select' name='selLoseStatus' id='sr' onfocus='javascript: reasonValue = this.value;' onfocusout='javascript: reasonValue = this.value;'>";

var reason2 = "</select></div></div>";
var description =   "<div id='dsdiv'><p class='alert-label'>Description</p>"
                    + "<div><textarea class='alert-textarea' id='ds' onfocus='javascript: descValue = this.value;' onfocusout='javascript: descValue = this.value;' placeholder='Enter description here'></textarea></div>"
                    + "</div>"
                    + "<div id='alert-message'></div></div>";
var close1 = "<button onclick=\"javascript: "; 
var clop =  "var context,st=document.querySelector('#st').value,sr=1==st?-1:document.querySelector('#sr').value,ds=document.querySelector('#ds').value,id=document.querySelector('#op').value,op=1==st?'Win':'Lose',opportunityclose={'opportunityid@odata.bind':'/opportunities('+id+')',description:ds},parameters={OpportunityClose:opportunityclose,Status:sr};context='function'==typeof GetGlobalContext?GetGlobalContext():Xrm.Page.context;var req=new XMLHttpRequest;'POST',context.getClientUrl()+'/api/data/v9.1/'+op+'Opportunity',!1),req.setRequestHeader('OData-MaxVersion','4.0'),req.setRequestHeader('OData-Version','4.0'),req.setRequestHeader('Accept','application/json'),req.setRequestHeader('Content-Type','application/json; charset=utf-8'),req.onreadystatechange=function(){if(4===this.readyState)if(req.onreadystatechange=null,204===this.status)!0);else{var e=this.responseText;console.log(e)}},req.send(JSON.stringify(parameters));"
            + "Alert.hide();";

var close2 = "\" value='Close' class='alert-button' name='Close Opportunity' aria-label='Close Opportunity'>Close Opportunity</button>";
var notnow = "<button onclick='javascript: Alert.hide()' class='alert-button' name='Not Now' aria-label='Not Now'>Not Now</button>";                    
var message = style + subtitle + status + reason1 + options + reason2 + description + notnow + close1 + clop + close2;

var buttons = [

var width = 480;
var height = 540;
var baseUrl = context.getClientUrl();
var preventCancel = false;
var icon = "INFO";
var padding = 30;, message, buttons, icon, width, height, baseUrl, preventCancel, padding);

Filed under: Blog

Icon packs to match D365 CE UCI entities

Icon packs to match D365 CE UCI entities on

There are ton more on which you can pick from. Modify the SVG to be a shade of #999999 to get a good match on color, and pick from lineal designs aren’t too concentrated.

Filed under: Blog, Dynamics 365/CRM

Send data from WordPress (and other PHP sites) to Dynamics CRM/CE

On of the easiest ways to capture data from WordPress and other PHP based sites and send it to Dynamics CRM/365CE has to be the PHP Toolkit created by AlexaCRM. They have two tools available for use for free.

  1. PHP Toolkit
  2. WordPress Plugin

    WordPress Plugin for Dynamics CRM

    If you have a WordPress site, connecting to Dynamics CRM/D365CE is a 10-minute exercise. Install the WordPress plugin on your site, create a form in Dynamics with a distinct name, add a page in WordPress and put the following shortcode snippet in the code editor.

    In the code above, replace the “Contact Card” with the name of your form in CRM. That’s it!

    Other PHP based sites

    If you have any other PHP based site like Joomla or Drupal, you can use the PHP Toolkit to connect Dynamics CRM/365CE to your site. Put the toolkit libraries into a libraries folder and you can call them from any page since it’s PHP. AlexaCRM has made examples available on their GitHub page.

    Filed under: Blog, Dynamics 365/CRM, Tools