CRM 2011: Change disabled button tooltip text

Guys, this is a nice one, just finished it! And I am quite happy with it.
When a button is disabled, your users will – and with reason – sometimes wonder why. The most logical place to display the reasons is in the tooltip, at least I think so.

So, how to do that?

1. In the function enabling/disabling the button, prepare a string with the reason(s)

2. Find the button id, using chrome or firefox web developer tools

3. More difficult, find the existing tooltip box id. Not so easy as it disappears as soon as the mouse leaves the button… Simple anyway: with Chrome dev tools open in a separate window, bring that window just under your disabled button, even covering it a little. So that moving the mouse on the button shows the tooltip, and that you can then switch to the dev window with the tooltip still open. In the elements tab, scroll down to the bottom, and watch a nice <span> element show, that looks like below. Copy the id and replace TOOLTIPID with it in the javascript code.

 

<span unselectable=”on” class=”ms-cui-tooltip” id=”new_entity|NoRelationship|Form|ipm.Form.new_entity.MyButton_ToolTip” role=”tooltip” aria-hidden=”false” style=”visibility: visible; position: absolute; top: 135px; left: 286px; min-width: 210px;”>
<div unselectable=”on” class=”ms-cui-tooltip-body”>
<div unselectable=”on” class=”ms-cui-tooltip-glow”>
<h1 unselectable=”on”>Temporary Suspension</h1>
<div unselectable=”on” class=”ms-cui-tooltip-description”>Temporary Suspension Description</div>
<div unselectable=”on” class=”ms-cui-tooltip-clear”></div>
<hr unselectable=”on”>
<div unselectable=”on” class=”ms-cui-tooltip-footer”>
<span unselectable=”on” class=” ms-cui-img-16by16 ms-cui-img-cont-float” style=”vertical-align: top;”></span>
<div unselectable=”on”>This button is currently disabled.</div>
</div>
<div unselectable=”on” class=”ms-cui-tooltip-description” style=”width: 90%;”>You may not have selected the item that works with this feature. If you do not have permissions to use this feature, contact your system administrator.</div>
</div>
</div>
</span>

 

4. Before returning true or false, if false, call this function, with the message. Replace BUTTONID with the appropriate value. It will be something long and with pipes (|) in it

function showDisabledReason(msg) {

// Get button
var link = window.parent.document.getElementById(‘BUTTONID’);

// Add custom tooltip
$(link, window.parent.document).hover(
function() { tryShowReason(msg); },
function() { }
);
return;

}

 

function tryShowReason(msg) {

var tooltip = manageToolTip(msg);
if (!tooltip) {
setTimeout(function () {
tryShowReason(msg);
}, 200);
}

}
function manageToolTip(msg) {
var tip = window.parent.document.getElementById(‘TOOLTIPID’);
if (tip) replaceToolTip(tip, msg);
return tip;
}
function replaceToolTip(tooltip, msg) {
$(tooltip).find(‘.ms-cui-tooltip-description:nth(1)’).html(msg);
}

 

And you’re done!!!

CRM 2011: Filter the customerid lookup field on quote form

And here we go for another highly unsupported customization.

This one has the following assumptions:

  • You know customerid is used only for showing contacts
  • There is another field on the form to show accounts, named new_AccountId
  • You want customerid to show only contacts from the selected account
  • You have jquery, otherwise the code will be slighly different and longer, but it’s of course possible

Here’s the code to run in the onLoad of the quote form:

 

$(‘#customerid’).attr(‘relationshipid’, ‘contact_customer_accounts’);
$(‘#customerid’).attr(‘dependantattributename’, ‘quote.new_AccountId’);
$(‘#customerid’).attr(‘dependantattributetype’, ’1′);

 

And your lookup is filtered!

CRM 2011 Dialog: how to filter a lookup field

From what I’ve discovered while playing with dialogs, when using a lookup field, the filter criteria that you put on the field are simply ignored by the dialog. By default, the dialog will set the lookup default view to the concerned entity’s lookup view. That can be a problem sometimes. To solve this, here is another highly unsupported customization that I’ve come up with today.

 

1. Find the guid of your dialog (just open it and look for the id parameter in the url)
2. Create the view you want to use to filter the lookup, and get its guid (same method)
3. Open this file: “C:\Program Files\Microsoft Dynamics CRM\CRMWeb\CS\dialog\rundialog.aspx”
4. Open the dialog, and using Chrome for example, find the lookup img’s id. It will look like this: “InteractionStep7″
5. At the end of the script tag, add the following function:

function setId(dialogGuid, viewGuid) {
if (window.location.href.indexOf(dialogGuid) > -1) {
var lookupImg = document.getElementById(‘InteractionStep7′);
if(!lookupImg) {
setTimeout(setId, 100);
} else {
lookupImg.setAttribute(‘defaultviewid’, viewGuid);
}
}
}
6. Then in the  same file, make the body tag like this, with the appropriate guids of course: <body onload=”setId(’123124435′, ’123124345′)”>
Explanations
Finding the guids are obivous.
Then editing the rundialog file, please note that this is highly unsupported of course.
Looking at the dialog page’s dom elements, you will see that the lookup image has an id. But that id is not set when onload is called. Probably some javascript executed there to get the dialog’s data.
This is why I use a timeout, that just waits for that id to be set. When the element is found, then I update the default view id, ie the view that will be use in the lookup window url.
And that’s it!

In the contact entity, modify the related orders default view

That one is extremely tricky. My customer had this sweet, short request: change the default view of related orders, in the contact entity.

RelatedOrders

By default, the defaule view is set to “Active”, and the customer wanted it to be “All”. How to do that? Well, here’s the answer below. It took me 5 hours to come up with this solution, but it works.
Highly unsupported if course…

Here’s the code below:

function setDefaultView() {
var grid = document.getElementById(“crmGrid_order_customer_contacts”);
    if (grid) {
        XMLHttpRequest.prototype.reallysend = XMLHttpRequest.prototype.send;
        XMLHttpRequest.prototype.send = function (body) {
            var that = this;
            if (body) {
                body = body.replace(‘<statecode>0</statecode>’, ‘<statecode>All</statecode>’);
                body = body.replace(‘<statecode>1</statecode>’, ‘<statecode>All</statecode>’);
                body = body.replace(‘<statecode>2</statecode>’, ‘<statecode>All</statecode>’);
                body = body.replace(‘<statecode>3</statecode>’, ‘<statecode>All</statecode>’);
                body = body.replace(‘<statecode>4</statecode>’, ‘<statecode>All</statecode>’);
            }
            this.onreadystatechange = function () {
                if (this.responseXML && XMLHttpRequest.prototype.reallysend) {
                    XMLHttpRequest.prototype.send = XMLHttpRequest.prototype.reallysend;
                    XMLHttpRequest.prototype.reallysend = null;
                }
            }
            return this.reallysend(body);
        }
        $(‘#crmGrid_order_customer_contacts_statecode’).find(‘option[value="All"]‘).prop(‘selected’, ‘selected’);
        grid.control.refresh();
}
This deserves a few explanations and configurations:
  1. I use jQuery to change the selected option in the picklist. However, and of course, this doesn’t change the displayed data. And, a call to refresh after changing this value, does not get the right data. It still retrieves only the active records. So to access jquery in this code, as we are in the context of an associated view, I use the ribbon trick (could also have deleted the js web resource directly and evaled it).
  2. I use a global variable, ouch. So I name it carefully.
  3. I replace the send function of the XMLHttpRequest object… That becomes sweet… :) The point is to replace, in the message sent to the server, the statecode 0, by All, so that all records are retrieved, instead of the active ones.
  4. After the first pass, I reset the original send function, otherwise the replace will be applied to all subsequent queries, always retrieving all the records…

And here you go, a nice related view, with the default filter being set on ‘All’ !

That one is my pride. :)

Messing around with the CRM Database

Today I encountered an interesting bug in CRM: impossible to delete an invoice, coming along with the nice “Generic SQL Error”.
Digging a little, I quickly found that the SQL error was related to the foreign key constraint between InvoiceBase and InvoiceExtensionBase, namely.

The delete statement conflicted with the REFERENCE constraint “FK_InvoiceExtensionBase_InvoiceBase”. The conflict occurred in the database…, table…, column ‘InvoiceId’.

Counting records of both these tables  in the DB, I found different results… Hmmm

It just so happened that for performance reasons, my customer recently decided to transpose some plugin code directly in SQL code, and that included the creation of invoices. Quite crazy you might say, ok let’s not start this debate, as we are all fully aware of the risks (especially with CRM 2013 where the extension tables simply disappear…). To achieve that task, he run the SQL profiler and executed all the tasks he wanted to have in SQL. He then reorganized the logged SQL commands in to a few stored procedures. Except that he went probably a bit too far, as he inserted the invoiceid into the table InvoiceExtensionBase, as well.

Si what I had was records in the table InvoiceExtensionBase, starting from the day the plugin was replaced by the stored proc. And the consequence was that it was then impossible to delete an invoice. Interesting conclusion then: since the invoice entity had not been customized, the CRM platform simply did not insert anything into the extension table. So deleting all the records from InvoiceExtensionBase solved the problem.

Create a new order from contact through a custom button

Here’s an exemple of the potential use of the new_pluginlauncher entity evoked in my previous post.

In this code, I have put a “Create Order” button on the contact form.

function newOrder() {
    var url = ‘_controls/lookup/lookupinfo.aspx?AllowFilterOff=0&DefaultType=1084&DefaultViewId=%7b30969771-F303-4B8E-AE91-1E4F5A7FEEFB%7d&DisableQuickFind=0&DisableViewPicker=0&LookupStyle=single&ShowNewButton=1&ShowPropButton=1&browse=false&objecttypes=1084′;
    var params = ‘toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=600,height=600′;
    var lookupWindow = window.open(url, ‘entity’, params);
    lookupWindow.onbeforeunload = function (test) {
        var result = lookupWindow.getDialogReturnValue();
        if (result && typeof(result) != ‘undefined’) {
            var quoteId = result.items[0].id;
            var entityType = 923620004; /* Contact */
            var actionType = 923620006; /* Create order */
            var recordGuid = Xrm.Page.data.entity.getId();
            var recordName = Xrm.Page.getAttribute(‘fullname’).getValue();
            createPluginLauncher(entityType, actionType, recordGuid, recordName, quoteId);
        }
    }
}
Clicking this button thus displays a lookup form, allowing the user to choose an offer. From there, I retrieve the selected offer id using  “lookupWindow.getDialogReturnValue();”. This is unsupported of course.
I can then pass that Id to a record of the new_pluginlauncher, allowing me to call ConvertToSalesOrder in a plugin, to return the new order’s guid to js, and to popup a form displaying this order. How to do that? The plugin on new_pluginlauncher is on precreate: create the order, and set the guid in the record before it’s created.
Here’s the create plugin launcher function:
/* Create plugin launcher */
function createPluginLauncher(entityType, actionType, recordGuid, recordName, quoteId) {
    var newPluginLauncher = {
        new_EntityType: { Value: entityType },
        new_ActionType: { Value: actionType },
        new_RecordGuid: recordGuid,
        new_QuoteId: quoteId,
        new_name: recordName
    };
    SDK.JQuery.createRecord(
        newPluginLauncher,
        ‘new_pluginlauncher’,
        function (newPluginLauncher) {
            Xrm.Utility.openEntityForm(“salesorder”, newPluginLauncher.new_OrderId);
        },
        function (error) {
            alert(error.message);
        }
    );
}

CRM 2011: trigger a plugin from a stored procedure

No, it’s not possible, you’re dreaming. Or is it? Well, sort of. What’s possible is the following:

  1. I have created an entity called “new_pluginlauncher”. I have made a plugin firef upon the creation of a record of this entity, from which  I can do anything, depending on the parameters passed when creating the record. I can do things such as win or reopen a quote, create an order, close a case, well whatever really. As long as the correct guids and parameters are set upon record creation.
  2. I have created an SSIS package, based on Kingswaysoft,  that does that: create a record of that new_pluginlauncher entity
  3. I have deployed the SSIS package appropriately in the SQL Server instance
  4. I have created a stored procedure that calls that SSIS package

Bingo! From now on, I can access plugin code, on demand, from a SQL Server stored procedure.

And the best thing is: this is completely supported.

 

Here’s the code of the stored procedure. Pay attention to allowing xp_cmdshell.

ALTER Proc [dbo].[proc_CreatePluginLauncher]
@entitytype varchar(20),
@actiontype varchar(20),
@recordid varchar(50)
as
begin
declare @command varchar(8000),
@packagelocation varchar(1000),
@packagename varchar(200),
@param varchar(2000)
set @packagelocation = ‘\SSISDB\CRM\CreateCRMPluginLauncher\’
set @packagename = ‘CreatePluginLauncher.dtsx’
set @param = ‘select ‘ +
@entitytype + ‘ as EntityType, ‘ +
@actiontype + ‘ as ActionType, ‘ +
@recordid + ‘ as RecordGuid’
set @command =
‘dtexec /ISSERVER ‘ + @packagelocation + @packagename + ‘ ‘ +
‘/set \package.variables[User::SQLCommand].Value;”\”‘ + @param + ‘\”" ‘
—-now execute dynamic SQL by using EXEC.
DECLARE @returncode int
EXEC @returncode = xp_cmdshell @command
select @returncode
end

CRM 2011: Add javascript library to ribbon button

Say you want to use CrmRestKit or any other library from a ribbon button click, within an associated view. You can add the library to the parent entity form or to the associated entity, it won’t work.

As I found here, what you need to do is, in the command of the button click, using ribbon workbench for example (or directly in the xml), as follows.
The main thing here is to add the required libraries, and assign them isNaN as function name.

 

<Actions>

<JavaScriptFunction Library="$webresource:new_json2.js" FunctionName="isNaN" />
<JavaScriptFunction Library="$webresource:new_sdk.rest.js" FunctionName="isNaN" />
<JavaScriptFunction Library="$webresource:new_task_formscripts.js" FunctionName="Task_Update">
<CrmParameter Value="FirstSelectedItemId"/>
</JavaScriptFunction>
</Actions>

Windows is dying

Everyone can see that now. Windows 8 is a disaster in terms of sales. Windows 8.1 has a very limited installed base. Microsoft is in trouble in that regard. Why is that?

Some say it’s the “best Windows version ever“. Well, let me totally disagree on that. It’s a failure. Here’s why:

  • Frustrating for tablet users: I own a Surface 1. It’s a nice tablet for surfing the web.
    • Except when you have hover menus (fixed in 8.1): you could simply not access the menu. It took them a whole year to fix that. Imagine the frustration: tap the menu, see it display for a split second, then disappear. And you just can’t surf to your page. And you need to get the laptop, wait, etc. A whole year to fix that!
    • Except when you need to change some settings that are available only in the desktop. Small menus, not at all touch oriented. How do you call that a tablet OS then?
    • Except when you want to stream music to a bluetooth device. Good luck to find the exact menu. Why not ask the user to type it in command line while they’re at it?
    • Except when you want to stream video to your xbox: good luck with that one as well.
    • Except when you want to stream IE to your tv: good luck with that one. I love to see that happen so easily with an ipad.
    • Except when you are looking for your home banking apps in the store, and for any truely useful app in that regard
    • Etc…
  • Frustrating for the desktop user:
    • Forced to go through a touch oriented UI as main UI. Why ?????

The “everything at once” concept can probably work, but the realization is terrible. My advice for now, and I like Microsoft and own some of their devices, is don’t get one. A Surface is just too hard to love. The hardware is so beautiful, but the software is a failure. No wonder that the whole Windows 8 management team is basically out. It’s actually a relief to see that someone, up there, actually has some common sense and acknowledges that Windows 8 is nothing but non sense.

I am also sick and tired of reading blogs about the next Windows’ new “features” that are always limited to: “The back button will be back!” or  “Floating windows in metro mode!”. What the f… Can we talk about true features here? Can we talk about innovation? Not just some ui gimmick going away and then back with every new release?

I want to the next Windows to be “just great”. To be smart, no non sense, useful and usable. Come on guys, talk to people before designing non sense stuff. What do you think? That putting colored squares is about to get everyone excited? Come on now…

And, this is your last chance to deliver something good. Windows 7 was ok, but not good, sorry. Nothing wow in there. But, it just works, and that’s a start. Competition is moving fast. Chromebooks are a joke and will not go beyond a certain level, there is no potential for this. But Android laptops are a different story. You guys want to sell Windows when Android is free? Fine, but then help oem’s get something for their money, something amazing, with no flaw.

Windows must keep on dominating the pc market, even if that market is decreasing. And windows must make a dent in the mobile market. Or it will die. So stop frustrating your users, finish the product, and make it good.

CK Editor and Rollup 15: problem

Apparently CRM 2011 UR15 has an impact on the way CRM fetches web resources from the server.
Since I installed it this morning, I get a JS error on my ckeditor enabled form, and a white area instead of my nice wysiwyg…
The console shows 404 errors, and CRM trying to get js and css files from the userdefined url, instead of webresources…

So what I did is :

  1. Delete all web resources related to ckeditor, as they are not necessary anymore
  2. Create a folder named by the organization in C:\Program Files\Microsoft Dynamics CRM\CRMWeb
    1. If your org is “SalesForce”, then the folder is: C:\Program Files\Microsoft Dynamics CRM\CRMWeb\SalesForce
  3. In there, create another folder called “userdefined”
  4. In there, copy all the ckeditor files (styles.css, config.js, all folders, everything)

Bingo, it works!

And the webresources are suddenly much less cluttered…