Posted on 27. November 2017

Ribbon Dependencies in Version 9 – isNaN is no more!

I recently blogged about the introduction of the script dependancies dialog in Version 9 where you can define the scripts that are needed by another. Although it does not solve the asynchronous loading issue for forms, it makes it simpler to add scripts to form since the dependencies will automatically be added for us.

Up until now, there has been a common pattern when adding script to Ribbon Commands where the dependancies were added with a function of 'isNaN'. It didn't have to be isNaN, but that is the most popular 'no operation' function call.

With the introduction of the script dependencies, you only need to include the reference to ClientCommands.js and the ClientCommon.js will be loaded automatically for you first before the command is called.

Awesome – we no longer need the isNaN approach that always felt like a 'hack'.

Posted on 25. November 2017

Script Load Dependencies in Version 9

A long time ago, Dynamics CRM introduced the concept of asynchronous loading of form web resources – this created a challenge when scripts depend on other scripts to be loaded first (e.g. inheritance or using a common type system library during script loading).

Version 9 has introduced an interesting feature where you can define the dependencies that a specific script has on other scripts.

Imagine you had 3 scripts

  • C.js requires B.js to load
  • B.js requires A.js to load

You can now define these dependencies in the web resources dialog:

I was hoping that by defining this dependency graph, the runtime would load them in the correct order like a module loader would – but having run some test the execution order still depending on the download speed and size of the script.

Script load execution order C - B - A

Script load execution order A - B - C

Conclusion

The Web resource dependency feature is awesome when you have many resources that are required during form events at runtime (e.g. onload, onchange etc.) You can simply add the single script into the form and the other dependencies will be loaded for you.

At this time, it's not a solution for where you need those dependencies during script load execution.

Posted on 11. August 2017

Solution Packager and global optionset enum support in spkl Task Runner

I’ve published version 1.0.9 of spkl to NuGet - this adds the following new features:

  1. Global optionset enum generation for early bound classes.
  2. Solution Packager support

Global Optionset enum generation

This was a tricky one due to the CrmSvcUtil not making it easy to prevent multiple enums being output where a global optionset is used, but you can now add the following to your spkl.json early bound section to generate global optionset enums.

{
  "earlyboundtypes": [
    {
      ...
      "generateOptionsetEnums": true,
      ...
    }
  ]
}

In a future update, I’ll add the ability to filter out the enums to only those used.

Solution Packager Support

The solution packager allows you to manage your Dynamics metadata inside a Visual Studio project by extracting the solution into separate xml files. When you need to combine multiple updates from code comments, you can then use the packager to re-combine and import into Dynamics. To configure the solution packager task you can add the following to your spkl.json

 /*
  The solutions section defines a solution that can be extracted to individual xml files to make
  versioning of Dynamics metadata (entities, attributes etc) easier
  */
  "solutions": [
    {
      "profile": "default,debug",
      /*
      The unique name of the solution to extract, unpack, pack and import
      */
      "solution_uniquename": "spkltestsolution",
      /*
      The relative folder path to store the extracted solution metadata xml files
      */
      "packagepath": "package",
      /*
      Set to 'true' to increment the minor version number before importing from the xml files
      */
      "increment_on_import": false
    }
  ]

There are two .bat files provided that will call:

spkl unpack

This will extract the solution specifed in the spkl.json into the packagepath as multiple xml files

spkl import

This will re-pack the xml files and import into Dynamics - optionally increasing the version number of the solution to account for the new build.

Posted on 15. May 2017

Continuous Integration using spkl Task Runner

This is the third video in a series showing you how to quickly setup VSTS Continuous Integration with spkl.

Watch in youtube

1. Learn more about the spkl task runner

2. Learn how to deploy plugins with the spkl task runnner

3. Learn how to deploy webresources with the spkl task runnner

Posted on 15. May 2017

Deploying Webresources using spkl Task Runner

This is the second video in a series showing you how get up and running with spkl with no fuss!

Watch in youtube

1. Learn more about the spkl task runner

2. Learn how to deploy plugins with the spkl task runnner

Posted on 21. November 2015

Option-Set, Lookup or Autocomplete

In the constant struggle to improve data quality it is common to avoid using free-text fields in favour of select fields. This approach has the advantage of ensuring that data is entered consistently such that it can easily be searched and reported upon.

There are a number of choices of approaches to implementing select fields in Dynamics CRM. This post aims to provide all the information you need to make an informed choice of which to use on a field by field basis. The options are:

  • Option-Set Field - stored in the database as an integer value which is rendered in the user interface as a dropdown list. The labels can be translated into multiple languages depending on the user's language selection.
  • Lookup Field - a search field that is stored in the database as a GUID ID reference to another entity record. The Lookup field can only search a single entity type.
  • Auto Complete Field - a 'free text' field that has an auto complete registered using JavaScript to ensure that text is entered in a consistent form. The term 'autocomplete' might be a bit misleading since the field is not automatically completed but instead you must select the suggested item from the list. This is a new feature in CRM 2016 that you can read more about in the preview SDK.
The following table provides an overview of the aspects that this post discusses for each option:
  • Number of items in list – The larger the list and the likelihood that it will grow, the more this becomes important.
  • Filtering based on user's business unit - This is especially important where you have different values that apply to different parts of the business and so the list of options must be trimmed to suit.
  • Adding new Items - Ease of adding values frequently by business users.
  • Removing values – Ease of removing values without affecting existing records that are linked to those values.
  • Multi-Language – Having options translated to the user's selected language
  • Dependant/Filtered options - This is important where you have one select field that is used to filter another such as country/city pairs.
  • Additional information stored against each option - This is important if you have information that you need to store about the selected item such as the ISO code of a country.
  • Mapping between entities - Is the option on multiple entity types? Often the same list of items is added as a field in multiple places and multiple entities. This can be important when using workflows/attribute maps to copy values between different entity types.
  • Number of select fields - The more select fields you have across all your entities, the more important this becomes.
  • Filters, Reports and Advanced Find - When creating advanced finds and views, a user should be able to select from a list of values rather than type free text.
  • Configure once, deploy everywhere – One key design goal should be that once a field is configured, it should be easily used across the web, outlook, tablet and phone clients.

Option-Set Fields

Option-Sets are the default starting point for select fields.

Number of items in list (Option-sets)

No more than ~100 for performance reasons. All items are downloaded into the user interface which will cause performance problems for large lists – especially where there are lots of option-sets on the same form.

Filtering based on user's business unit (Option-sets)

Requires JavaScript to filter items dynamically based on the user's role/business unit.

Ease of adding values frequently by business users (Option-sets)

Option-Sets require a metadata configuration change and a re-publish that would usually be done out of hours by an administrator. It is best practice to do this on a development environment and then import a solution into production. Adding new values to the list isn't something that can be done by business users.

Removing values over time (Option-sets)

Removing items causes data loss in old records. Items can be removed using JavaScript to preserve old records, but Advanced Find will still show the values.

Multi-Language Options (Option-sets)

Each option-set item can be translated into multiple languages.

If you need to have the select field multi-language then an option-set is probably your best choice unless it is going to be a long list, in which case you'll need to make a compromise.

Dependant/Filtered options (Option-sets)

Requires JavaScript to filter options.

Additional information stored against each option (Option-sets)

It is not possible to store additional data other than the label and integer value of the option-set. You would need to store somewhere else in a lookup table format.

Mapping between entities (Option-sets)

Use a global option-set that can be defined once and used by multiple option-set fields.

Number of select fields (Option-sets)

You can have as many select fields as your entity forms will allow. The more fields you have the slower the form will load and save. 

Search/Filtering (Option-sets)

Option-sets are always presented as a drop down in advanced fine and view filters.

Configure once, deploy everywhere (Option-sets)

Works across all clients including phone and tablet native apps.

Option-sets are the most 'native' choice for select fields and will work in all deployment types without much consideration.

 

Lookup Fields with Custom Entity

Lookup fields allow selecting a single reference to a custom entity using a free text search.

Number of items in list (Lookup)

Unlimited list items subject to database size. Since all list items are not downloaded to the user interface (unlike option-sets) the user can search quickly for the item they need.

Filtering based on user's business unit (Lookup)

Security Roles can be used in combination with a user owned lookup entity so that lookup records are visible to subset of users.

Ease of adding values frequently by business users (Lookup)

New records can easily be added by users using the 'Add New' link. Control over who can add new items can be done using Security Roles.

If you need business users to add values regularly then a Lookup field is a good choice. The Configuration Migration tool in the SDK can be used to easily move values between environments.

 

Removing values over time (Lookup)

Old items can be easily deactivated and will no longer show in lookup fields (including in advanced finds) however existing records will retain their selected value (unlike when option-set items are removed).

If you need to make changes constantly to the list and remove items without affecting previous records then a lookup field is most likely your best choice.

 

Multi-Language Options (Lookup)

Not possible without complex plugin server side code to dynamically return the name in the current user's language.

Dependant/Filtered options (Lookup)

Lookup filtering options can be added in the form field properties or can be added via JavaScript for more complex scenarios.

Lookups are the easiest and quickest to setup dependant lists without writing code. This filtering will also work on tablet/mobile clients without further consideration.

Additional information stored against each option (Lookup)

Additional information can be stored as attributes on the lookup entity records. Lookup views can show up to 2 additional attributes within the inline lookup control.

If you are integrating with another system that requires a special foreign key to be provided, lookup entities are good way of storing this key.

 

Mapping between entities (Lookup)

Lookups can easily be mapped between records using attribute maps/workflows or calculated fields.

Number of select fields (Lookup)

CRM Online is limited to 300 custom entities.

This is an important consideration and it's unlikely to be a good idea to use Lookup entities for all of your select fields.
If you are using CRM online you'll likely have to always use a combination of lookups and option-sets due to the limit of 300 custom entities. Don't take the decision to make all your select fields as lookups.

 

Search/Filtering (Lookup)

Lookups are presented as search fields in Advanced Find and Filters.

Configure once, deploy everywhere (Lookup)

Works across all clients including phone and tablet native apps. If working offline however, all lookup values may not be available.

Text Field Auto Completes (CRM 2016)

Autocompletes are a free text field with an on key press event added to show an autocomplete. The great thing about autocompletes is that they can show icons and additional action links.See below for an example of how to use autocompletes in Javascript.

Number of items in list (Autocomplete)

An autocomplete field can only show as many items as you return at a time but you'll want to put a limit for performance reasons.

If you need the select field to be more like a combo-box where users can type their own values or select from predefined items then autocomplete text fields are a good choice.

 

Filtering based on user's business unit (Autocomplete)

You can add any search filtering you need using JavaScript.

 

Ease of adding values frequently by business users (Autocomplete)

If the autocomplete is using a lookup entity to store the data displayed then the same considerations would apply as for Lookup Entities. If the values are hard coded into the JavaScript then this would be more akin to the Option-Set solution import.

Removing values over time (Autocomplete)

Since the actual field is stored as a text field there is no issue with removing values. Existing data will still be preserved.

Multi-Language Options (Autocomplete)

You can detect the user interface language and return a specific language field to the user via JavaScript however it will be stored in the textbox and other users will not see it in their language (unlike an option-set). One solution to this would be to use the autocomplete for data entry and then use a web resource to present the field value in the local user's language.

Dependant/Filtered options (Autocomplete)

You can apply whatever filtering you need using JavaScript.

Additional information stored against each option (Autocomplete)

If you use the autocomplete to search a custom entity you can store additional data as additional attributes. The autocomplete flyout can display multiple values for each result row.

Autocomplete fields have the advantage that they can show an icon that is specific to the record (e.g. The flag of the country). If you need this feature, then Auto completes are a good choice.

 

Search/Filtering (Autocomplete)

If you use a free text autocomplete it's advisable to additionally populate a backing lookup field to facilitate searching/filtering. This would also allow you to ensure that 'unresolved' values cannot be saved by using an OnSave event to check that the text field matches a hidden lookup field that is populated in the OnChange event.

Configure once, deploy everywhere (Autocomplete)

Autocomplete does not work on phone/tablet native apps yet.

Show me the Code!

I have added support for the Auto Complete SDK extensions in CRM2016 to SparkleXRM. To show a country autocomplete lookup, you'd add onload code similar to:

public static void OnLoad()
{
    Control control = Page.GetControl("dev1_countryautocomplete");
    control.AddOnKeyPress(OnCountrySearch);
}

public static void OnCountrySearch(ExecutionContext context)
{
    string searchTerm = Page.GetControl("dev1_countryautocomplete").GetValue<string>();
    string fetchXml = String.Format(@"<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>
                              <entity name='dev1_country'>
                                <attribute name='dev1_countryid' />
                                <attribute name='createdon' />
                                <attribute name='dev1_name' />
                                <attribute name='dev1_longname' />
                                <attribute name='dev1_iso' />
                                <attribute name='entityimage_url' />
                                <order attribute='dev1_name' descending='false' />
                                <filter type='and'>
                                  <condition attribute='dev1_name' operator='like' value='{0}%' />
                                </filter>
                              </entity>
                            </fetch>", searchTerm);

    OrganizationServiceProxy.BeginRetrieveMultiple(fetchXml, delegate(object state)
    {
        try
        {
            // We use an aysnc call so that the user interface isn't blocked whilst we are searching for results
            EntityCollection countries = OrganizationServiceProxy.EndRetrieveMultiple(state, typeof(Entity));
            AutocompleteResultSet results = new AutocompleteResultSet();

            // The Autocomplete can have an action button in the footer of the results flyout
            AutocompleteAction addNewAction = new AutocompleteAction();
            addNewAction.Id = "add_new";
            addNewAction.Icon = @"/_imgs/add_10.png";
            addNewAction.Label = "New";
            addNewAction.Action = delegate()
            {
                OpenEntityFormOptions windowOptions = new OpenEntityFormOptions();
                windowOptions.OpenInNewWindow = true;
                Utility.OpenEntityForm2("dev1_country", null,null, windowOptions);
            };
            results.Commands = addNewAction;
            results.Results = new List<AutocompleteResult>();
            
            // Add the results to the autocomplete parameters object
            foreach (Entity country in countries.Entities)
            {
                AutocompleteResult result = new AutocompleteResult();
                result.Id = country.Id;
                result.Icon = country.GetAttributeValueString("entityimage_url");
                result.Fields = new string[] { country.GetAttributeValueString("dev1_name"),                           
                    country.GetAttributeValueString("dev1_iso"),
                     country.GetAttributeValueString("dev1_longname")
                };
                ArrayEx.Add(results.Results, result);                    
            }
            if (results.Results.Count > 0)
            {
                // Only show the autocomplete if there are results
                context.GetEventSource().ShowAutoComplete(results);
            }
            else
            {
                // There are no results so hide the autocomplete
                context.GetEventSource().HideAutoComplete();
            }
        }
        catch(Exception ex)
        {
            Utility.AlertDialog("Could not load countries: " + ex.Message, null);
        }   
    });
}

This would result in JavaScript:

ClientHooks.Autocomplete = function ClientHooks_Autocomplete() {
}
ClientHooks.Autocomplete.onLoad = function ClientHooks_Autocomplete$onLoad() {
    var control = Xrm.Page.getControl('dev1_countryautocomplete');
    control.addOnKeyPress(ClientHooks.Autocomplete.onCountrySearch);
}
ClientHooks.Autocomplete.onCountrySearch = function ClientHooks_Autocomplete$onCountrySearch(context) {
    var searchTerm = Xrm.Page.getControl('dev1_countryautocomplete').getValue();
    var fetchXml = String.format("<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>\r\n                                      <entity name='dev1_country'>\r\n                                        <attribute name='dev1_countryid' />\r\n                                        <attribute name='createdon' />\r\n                                        <attribute name='dev1_name' />\r\n                                        <attribute name='dev1_longname' />\r\n                                        <attribute name='dev1_iso' />\r\n                                        <attribute name='entityimage_url' />\r\n                                        <order attribute='dev1_name' descending='false' />\r\n                                        <filter type='and'>\r\n                                          <condition attribute='dev1_name' operator='like' value='{0}%' />\r\n                                        </filter>\r\n                                      </entity>\r\n                                    </fetch>", searchTerm);
    Xrm.Sdk.OrganizationServiceProxy.beginRetrieveMultiple(fetchXml, function(state) {
        try {
            var countries = Xrm.Sdk.OrganizationServiceProxy.endRetrieveMultiple(state, Xrm.Sdk.Entity);
            var results = {};
            var addNewAction = {};
            addNewAction.id = 'add_new';
            addNewAction.icon = '/_imgs/add_10.png';
            addNewAction.label = 'New';
            addNewAction.action = function() {
                var windowOptions = {};
                windowOptions.openInNewWindow = true;
                Xrm.Utility.openEntityForm('dev1_country', null, null, windowOptions);
            };
            results.commands = addNewAction;
            results.results = [];
            var $enum1 = ss.IEnumerator.getEnumerator(countries.get_entities());
            while ($enum1.moveNext()) {
                var country = $enum1.current;
                var result = {};
                result.id = country.id;
                result.icon = country.getAttributeValueString('entityimage_url');
                result.fields = [ country.getAttributeValueString('dev1_name'), country.getAttributeValueString('dev1_iso'), country.getAttributeValueString('dev1_longname') ];
                Xrm.ArrayEx.add(results.results, result);
            }
            if (results.results.length > 0) {
                context.getEventSource().showAutoComplete(results);
            }
            else {
                context.getEventSource().hideAutoComplete();
            }
        }
        catch (ex) {
            Xrm.Utility.alertDialog('Could not load countries: ' + ex.message, null);
        }
    });
}

ClientHooks.Autocomplete.registerClass('ClientHooks.Autocomplete');
});

 

 

 

Posted on 24. May 2015

Turbo Forms: Get your JavaScript ready for CRM2015 Update 1

With the introduction of 'Turbo Forms' in CRM2015 Update 1 I thought I'd give you a heads up on what you'll need to address in your JavaScript to support this new form rendering engine. The Dynamics CRM Team Blog has a very good article on the changes but there have been some misunderstandings of the statement 'we have parallelized as many operations as possible'. In actual fact the script loading of custom web resource scripts has not really changed since CRM2013 - It remains the same as I describe in my CRM2013 Script Loading Deep Dive. The operations that are parallelized with turbo form rendering are the internal form load operations rather than custom ones. Custom JavaScript loading has always been parallelized since CRM2013.

Turbo Form Page Differences

Before Turbo Forms both global scripts and your custom web resources would have been loaded into a content IFRAME within the main window each time you navigate between records.

The biggest change with Turbo Forms is that the content IFRAME is kept in memory and re-used for performance reasons. This parent content IFRAME is even kept in memory between navigation between different entity types. Any custom JavaScript is then loaded into a child IFRAME (via the ClientApiWrapper.aspx page) so that when you navigate between records they are re-loaded.

SparkleXRM solutions already have a load order mechanism that ensure that your custom scripts are loaded in order that they are needed.

Impact on unsupported code

If your JavaScript is using only supported SDK calls then there will be no impact from this new loading mechanism. Sometimes it is necessary to use unsupported functions or referencing parameters of the Content IFRAME (such as the query string). Since your custom JavaScript is now running in the context of the ClientApiWrapper (rather than the content IFRAME) any reference to window methods such as window.openStdWin or window.location.href will fail. In order to access these objects you will need to reference parent.window.

The ribbon workbench 'how-to' article that shows starting a dialog from a ribbon button does infact use openStdWin to ensure consistency with the out of the box dialog experience. I have updated the code to use the parent window when required.

Footnotes

There are a couple of other notable aspects of Turbo Forms that I thought I'd point out:

IFRAMES that are collapsed by default are not sized correctly.

If you have an IFRAME or HTML Webresource inside a Tab that is collaposed by default you will find that they are not sized correctly when the tab is expanded. This will be fixed in an upcoming update but until then you will need to show the tab by default and collapse when the form is loaded.

entityType vs typename

Turbo Forms have dropped the typename attribute of Lookup values.

In the past, the following code would return the same:

Xrm.Page.getAttribute("parentcustomerid").getValue()[0].typename
Xrm.Page.getAttribute("parentcustomerid").getValue()[0].entityType

With Turbo Forms only the documented entityType is available. The typename attribute was left over from the CRM4 days and just had not been removed until now!

@ScottDurow

 

Posted on 20. January 2015

The cream cracker conundrum (or customising the sub grid command bar)

I still find the streamlined user experience offered by the Command Bar a welcome change from the CRM2011 Ribbon. The sub-grid command bar is the only possible exception with the loss of the ability to add custom sub-grid buttons. There are only at most two buttons on a sub grid – 'Add' and 'Open Associated Sub-Grid'

The user must click on the 'Open associated sub-grid' button to show the full associated view and the full command bar with custom buttons. I say 'possible exception' because in fact there are still the same number of clicks involved (users had to click on the sub grid to show the contextual ribbon before) but it does feel as though we should have the ability to add buttons to the sub-grid command bar. I can think of some good reasons why this design decision may have been made (performance for one!) – but this post details what you CAN do to the sub-grid command bar.

Because the 'Open associated sub-grid' button is a bit of a mouthful, I'll refer to it from now on as the 'cream cracker' because it kind of looks like one and is equally a bit of a mouth full! (Thanks goes to my friends at the British Red Cross who first named it this way!)

Hiding buttons

We have established that you cannot add buttons to the form sub grid, but both the 'Add New' and 'Cream cracker' buttons are customisable in terms of their command and visibility (but you cannot change the image or the tool tip text).

To hide the sub grid buttons you have the following options:

  1. Hiding based on a security role (if the user does not have access to append a record to the parent or create new records of the sub grid type, the 'add new' button will be invisible
  2. Hiding all of the time using a 'Hide Action'
  3. Hiding conditionally using a security role
  4. Hiding conditionally using a custom JavaScript rule.

A common requirement is to hide the Add New button on a sub-gird until a specific criteria is met on the parent form. The standard ValueRule cannot be used because this rule will only work when the command button is added to a form command bar. So to achieve the conditional show/hide we must use a Custom JavaScript Rule.

The first hurdle is to determine which button needs to be customised. The sub grid 'Add New' button calls a different command depending on the nature of the relationship.

If you have a foreign-key attribute on your child entity that is marked as Field Requirement = 'Optional' then the Add New button will be the AddExistingStandard since it allows you to search for an existing record first. If the Field Requirement = 'Business required' then the button will be AddNewStandard

 

 

Once you've identified the right button, you can then open the ribbon workbench and click Customize Command and add the Value Rule as described by my user voice article.

Changing the command actions

Although we cannot add new buttons (did I mention that?!) we can change the command actions that either of those two buttons call. Since we can't customise the button, the only option here is to customise the command and change its behaviour in a very similar way to adding custom enable rules.

  1. Right click the button in the Ribbon Workbench and select Customise Command
  2. Expand the command in the Commands node in the Solution Elements panel and select the command that has been created for you to customise.
  3. Right click Edit Actions and you can simply delete the standard action and add your own custom ones.
  4. Remember to mark all the enable and display rules that you don't want to customise as IsCore=True.

Once such use of this technique is to replace the standard add new button with a custom dialog.

Refreshing the sub grid Command Bar

You will find that when the form is loaded and the sub grid is refreshed for the first time the EnableRules are evaluated. If however the conditions for the EnableRules change (e.g. a value changes on the parent form) the sub grid command bar will not automatically refresh to reflect the new state. Upon adding or deleting rows in the sub grid the command bar is refreshed – but this isn't much use in this case.

The main form command bar can be refreshed using Xrm.Page.ui.refreshRibbon() however this will not refresh sub grid command bars. Instead, we can add an onchange event to the fields that are used in our ValueRule and call:

Xrm.Page.data.save();

This will refresh the sub grids and re-evaluate any of the EnableRules however it will also save any other dirty attributes and so should be used with caution if you do not have auto-save enabled.

Responding to new/deleted rows in the sub grid

Since the sub grid command bar is refreshed when new rows are added or deleted we can use the fact that the EnableRules will be re-evaluated to call custom JavaScript when the sub grid changes. This simulates a sub-gird onchange event and was first described by James Wood's blog post for CRM2011. He states on his blog that this technique doesn't work for CRM2013 – however if we add the custom EnableRule to the existing command (rather than use a new button as James describes) then this technique works well in CRM2013 and CRM2015. So we can customise the command for the Add New or cream cracker and add a Custom JavaScript Enable Rule that always returns true in just the same way that you might use the EnableRule to dynamically show/hide the button but rather we just run our onchange code.

Perhaps in the future there will be more possibilities but for now that about sums up the possibilities for customising the sub grid command bar.

@ScottDurow

Posted on 2. January 2015

SparkleXRM for CRM2015 with process API support

I've just committed an update to SparkleXRM with CRM2015 support and the process client API. One of the design decisions I made early on with SparkleXRM was to stick with a CRM2011 solution format to allow installation on both CRM2011 and CRM2013. Now that CRM2015 does not support installing CRM2011 solutions I've had to branch and make both CRM2011 and CRM2015 versions available. The code base still remains the same but they are distributed through two separate solution files depending on your target version. You can download the new CRM2015 SparkleXRM solution from github.

The new client side process control API is such a welcome addition. The latest version of SparkleXRM contains support for this so that you can:

Write code to run when the stage is changed or the user selects a process tab (such as hiding/showing sections on the form.

// Add On Process Stage change
Page.Data.Process.AddOnStageChange(delegate(ExecutionContext context){
    // Stage Stepped forwards backwards
});

// Add On Process Stage change
Page.Data.Process.AddOnStageSelected(delegate(ExecutionContext context)
{
    // Stage Tab Selected
});

Write code to get the current process and stage so that onload functions can show/hide sections on the form.

// Get Current Process
Process process = Page.Data.Process.GetActiveProcess();

Stage stage = Page.Data.Process.GetActiveStage();
Script.Alert("Process = " + process.GetName() + " Stage = " + stage.GetName());

Write code to get the stages and steps of the current process and find the attributes that are referenced – I've not found a use for this yet!

// Get Stages
ClientCollection stages = process.GetStages();

if (stages.GetLength() > 0)
{
    // Get Steps
    Stage stage0 = stages.Get(0);
    ClientCollection steps = stage0.GetSteps();
    steps.ForEach(delegate(Step step, int index)
    {
        Script.Alert(step.GetName() + " " + step.GetAttribute());
        return true;
    });
}

Write code to show/hide or collapse/expand the process ribbon:

// Show/Hide Process
Page.Ui.Process.SetVisible(true);

// Expand/collapse
Page.Ui.Process.SetDisplayState(ProcessExpanded.Collapsed);

Write to advance/step back the stage or change the process/stage completely:

// Change stage
Page.Data.Process.MoveNext(delegate(MoveStepResult result)
{
    Script.Alert(result.ToString());

});

// Change process
Stage currentStage = stages.Get(0);
Page.Data.Process.SetActiveStage(currentStage.GetId(), delegate(SetActiveStageResult result)
{
    Script.Alert(result.ToString());
});

// Change process to the first available process that the user has access to. 
// If the same as the current process, this does nothing.
Page.Data.Process.GetEnabledProcesses(delegate(Dictionary processes)
{
    Page.Data.Process.SetActiveProcess(processes.Keys[0], delegate(SetActiveProcessResult result)
    {
        Script.Alert(result.ToString());
    });
});

Along with the server side branching support for processes – I think this really finishes off this feature nicely. The business process flow feature is now by far my favourite in terms of innovation, business usefulness and developer API. First it was gold in CRM203 RTM, then green in SP1 - now with CRM2015 I especially like the calming cool blue that the process ribbon is now rendered with!

Cheers,

@ScottDurow

 

Posted on 28. January 2014

Chrome Dynamics CRM Developer Tools

Chrome already provides a fantastic set of Developer tools for HTML/Javascript, but now thanks to Blake Scarlavai at Sonoma Partners we have the Chrome CRM Developer Tools.

This fantastic Chome add in provides lots of gems to make debugging forms and testing fetchXml really easy:

Form Information
- Displays the current form’s back-end information
- Entity Name
- Entity Id
- Entity Type Code
- Form Type
- Is Dirty
- Ability to show the current form’s attributes’ schema names
- Ability to refresh the current form
- Ability to enable disabled attributes on the current form (System Administrators only)
- Ability to show hidden attributes on the current form (System Administrators only)


Current User Information
- Domain Name
- User Id
- Business Unit Id


Find
- Ability to open advanced find
- Set focus to a field on the current form
- Display a specific User and navigate to the record (by Id)
- Display a specific Privilege (by Id)


Test
- Ability to update attributes from the current form (System Administrators only)
- This is helpful when you need to update values for testing but the fields don’t exist on the form


Fetch
- Execute any Fetch XML statement and view the results


Check it out in the chrome web store - https://chrome.google.com/webstore/detail/sonoma-partners-dynamics/eokikgaenlfgcpoifejlhaalmpeihfom