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"; 

debugger;
//Build Status Reason Option Set values
var options = "";
var req = new XMLHttpRequest();
req.open("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>";
        }
    }
};
req.send();
 
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("#srdiv").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;req.open('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)Xrm.Page.data.refresh(!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;

Alert.show(title, message, buttons, icon, width, height, baseUrl, preventCancel, padding);
}

Filed under: Blog

1 Comment

  1. Very interesting implementation! I may be biased but I prefer the 3.0 version 😛


Add a Comment

Your email address will not be published. Required fields are marked *

Comment *
Name *
Email *
Website

This site uses Akismet to reduce spam. Learn how your comment data is processed.