Programming, technology, and CRM – from a Belgian programmer exiled to Missouri
  • rss
  • Home
  • Contact Me
  • Welcome

Validation of SalesLogix Lookup Controls

Nicolas Galler | March 8, 2010

OK so I am really, really loving JQuery lately. This is a neat use of how to add a custom validator for a lookup control in SalesLogix. The lookups have this “Required” property which you can turn on to make them required, but this has 2 major flaws:

  • Can’t specify the validation group, in case you want the lookup to only be validated in certain cases
  • More importantly, can’t specify an error message, so you are left with the default tiny-ish red asterisk. I don’t know about you but I like my error message to be big, bold, and explicit.

So we have this RequiredFieldValidator, part of the standard ASP.NET controls. This is so handy for validating textboxes but if you try to have it validate a lookup you will get this beautiful error message:

Control 'luePurchContact' referenced by the ControlToValidate property of '' cannot be validated.

There is a customvalidator that lets you specify your own validation function, and you can do that. This would look something like:

 <asp:CustomValidator ID="CustomValidator2" runat="server" ValidationGroup="SubmitCredit" Text="*" OnServerValidate="luePurchContact_Validate"
                ErrorMessage="Please select Purchasing Contact"/>

and then in the code behind:

    void luePurchContact_Validate(object source, ServerValidateEventArgs args)
    {
        args.IsValid = (luePurchContact.LookupResultValue != null);
    }

The big problem here is that this requires a postback. Postbacks are slow. You want to avoid them as much as possible. They waste bandwidth, server CPU, client CPU, they look ugly, they cause the control focus to be lost, and they mess up Javascript customizations. They suck. Furthermore, if some controls require a postback to validate, and some don’t, the users will only get a partial validation at a time, which is annoying.

Anyway, Microsoft thought the same thing of postbacks, and they provided an extension to these customvalidators that let you do the validation in Javascript. So the only difficulty is, how do we retrieve the lookup’s value in Javascript? It’s very easy if you know the client id, but ASP.NET has this nasty tendency to mangle those. Not a big deal though, as JQuery is still able to find controls based on the last characters of the id (assuming there is no other control with that same id in the page). This lets me write the following for validation:

<asp:CustomValidator runat="server" ValidationGroup="SubmitCredit" Text="*"
 ErrorMessage="Please select Purchasing Contact"
 ClientValidationFunction="(function(s, e) { args.IsValid = !!$('input[id$=luePurchContact_LookupResult]').val(); })" />

If you are suspicious about another lookup with the same id on another part of the page, use the following instead:

<div id="frmAccCredit">
<!-- 
  "frmAccCredit" would be a unique identifier for the form.
  You can use anything that is going to be unique, and put it at top level of the form.
  This is also handy for designing css rules that should not affect other
  parts of the page.
-->

...
more stuff
...

<asp:CustomValidator runat="server" ValidationGroup="SubmitCredit" Text="*"
 ErrorMessage="Please select Purchasing Contact"
 ClientValidationFunction="(function(s, e) { args.IsValid = !!$('#frmAccCredit input[id$=luePurchContact_LookupResult]').val(); })" />
Comments
No Comments »
Categories
Uncategorized
Comments rss Comments rss
Trackback Trackback

Step by Step Guide to Custom Form Development with Visual Studio

Nicolas Galler | December 15, 2009

I think I just made the longest post ever on the SalesLogix Journal. It is a fairly complete guide on how to get started writing custom smart parts for SalesLogix in Visual Studio. I think it is a bit scary to get started with those but it is a must to provide rich functionality (for better or worse – but I think it is a good thing that Sage is not trying to cram every possible functionality into the QuickForms – their job is not to create a Visual Studio replacement!!)

Anyway, I might start writing most of the SalesLogix content through that channel so this blog does not look so much like a SalesLogix reference guide :)

Comments
No Comments »
Categories
Uncategorized
Comments rss Comments rss
Trackback Trackback

Simple Picklist – Enabling Picklist manager options for the web client

Nicolas Galler | October 5, 2009

One missing feature of the SLX web client some of our users have been very vocal about is the ability to manage picklist options (multi-select, alphabetical sort, etc) from the good old picklist manager.  What makes it even more confusing is that there is no indication in the picklist manager that the options are not actually going to take effect… but in fact in the web client these options are only taken from the picklist control itself (i.e., set via the App Architect or in the page’s code). 

Fortunately it is a reasonably simple problem to resolve since those picklist options are readily accessible from the database.  All we have to do really is wrap the creation of the picklist control with something that will know how to pull that info and set it on the control.  The simplest way to do that would probably be to subclass the control, but I decided to go with a decorator pattern instead (the idea was that this would let me change a bit more of the behavior, for example, replace the stock picklist with a regular dropdown).  So instead of using:

<SalesLogix:PickListControl runat="server" ID="pklType" PickListName="Account Type" AutoPostBack="true"  AlphaSort="true" MustExistInList="true" AllowMultiple="true"  />

I use:

<SSS:SimplePickList runat="server" ID="pklType" PickListName="Account Type" AutoPostBack="true"   />

Behind the scene the SimplePicklist controls reads the attributes from the database and create a Saleslogix picklist control with the proper settings, something like this:

_picklist = new PickListControl();
if ((_storageMode & PicklistStorageMode.Id) != 0)
    _picklist.StorageMode = Sage.Platform.Controls.StorageModeEnum.ID;
else if ((_storageMode & PicklistStorageMode.Code) != 0)
    _picklist.StorageMode = Sage.Platform.Controls.StorageModeEnum.Code;
_picklist.PickListName = _picklistName;
_picklist.AllowMultiples = _attr.AllowMultiples;
_picklist.AlphaSort = _attr.AlphaSorted;
_picklist.NoneEditable = _attr.NoneEditable;
_picklist.Required = _attr.Required;
_picklist.MustExistInList = _attr.ValueMustExist;
_picklist.PickListValueChanged += delegate
{
    if (TextChanged != null)
        TextChanged(this, EventArgs.Empty);
};
parentControl.Controls.Add(_picklist);

This works fine for custom smart parts, how about for QuickForms?  Well, it is possible to change the template there too, but as it is global (will affect nearly every picklist in the client) it is a bit scarier :)

In the Model\QuickForms\Web\QFSLXPickList.WebControlRenderingTemplate.vm I changed the control to use my “SimplePickList” instead of a PickListControl

## Simple Picklist alternative (to automatically determine the sort etc)
    <SSS:SimplePickList Compatible="true" runat="server" ID="${qfcontrol.ControlId}" #if($qfcontrol.IsReadOnly)ReadOnly="true" #end
#if(!$qfcontrol.Enabled)Enabled="false" #end
#if($qfcontrol.ToolTip != "") ToolTip="<%$ resources: ${qfcontrol.ControlId}.ToolTip %>" #end
#if($qfcontrol.HotKey != "")AccessKey="$qfcontrol.HotKey" #end
#if($qfcontrol.PickListName != "")PickListName="$qfcontrol.PickListName" #end
#if($qfcontrol.HasActionCode || $qfcontrol.AutoPostBack)AutoPostBack="true" #end
#if($qfcontrol.Required)Required="true" #end
#if($qfcontrol.MaxLength > 0)MaxLength="$qfcontrol.MaxLength" #end
#if($qfcontrol.TabIndex > 0)TabIndex="$qfcontrol.TabIndex" #end
#if($qfcontrol.StorageMode != "Text")StorageMode="$qfcontrol.StorageMode" #end
#if($qfcontrol.StyleScheme != "")CssClass="$qfcontrol.StyleScheme" #end
#if(!$qfcontrol.Visible)Visible="false" #end  />

The “Compatible=true” setting is something I added to avoid bad surprises – it forces the list to display as a standard Saleslogix picklist, instead of e.g. a dropdown.

Now because it is a custom control, it requires a change to web.config too, something like this (under system.web/pages/controls):

<add tagPrefix="SSS" namespace="SSSWorld.Slx75.Web.Controls" assembly="SSSWorld.Slx75"/>

I think it works great, but haven’t tried on a production system yet – I am going to save it for a next big project when I can get a resource to QA the entire app!  I do already use the “small” version in production in my custom smart parts, though.  I saved the code under here, if interested feel free to rip it up (though it probably has some extra dependencies so it will require a bit of cleanup).

So although I think the PickListControl should support this out of the box it is nice to see that it is not too hard to add the functionality ourselves.

Comments
2 Comments »
Categories
Uncategorized
Comments rss Comments rss
Trackback Trackback

Display user lookup as a dropdown (legacy-web style)

Nicolas Galler | September 1, 2009

Our users upgraded from 7.0 to 7.5 complained about the user lookup dialog – on 7.0 it used to be a dropdown, whereas on 7.5 it’s a popup similar to the LAN client.

image

Sorry for the chopped image, I had to hide the name and was too lazy to re-run on the eval db.

The problem is the popup takes a couple seconds to come up on IE7, sometimes more on slow machines or slow connections – it’s a bit irritating.  After a brief phase of denial (how dare they refuse the New and Improved way, etc) I set to work.  Had to struggle a bit with the combination of available properties but finally got it working.  Configuring lookup is definitely a big pain on the web client!  This is the winning combo:

  • Display Mode = DropDownList
  • Display Property = UserInfo.UserName
  • Lookup Entity Name = User
  • Lookup Binding Mode = Object
  • In the Lookup Pre Filters, add a filter on “Enabled” (make sure the property is populated as System.Boolean) and filter value of “true”

The DropDown style lookups are a bit more picky on the parameters than the regular lookup, because they actually use NHibernate parameters instead of inline HQL.  In general I would say it is a good thing to use parameters, though in this case it makes it a bit harder for us to hack around the limitation of the control.

Data binding works fine, the only big problem I have found is that it sorts by first name instead of last name for some reason (which you can’t really see in the screen shot as I removed the last name but you can imagine).

Comments
1 Comment »
Categories
Uncategorized
Comments rss Comments rss
Trackback Trackback

Troubleshooting Reporting Server

Nicolas Galler | September 1, 2009

This post is an example of the tricks you can accomplish with Reflector as well as a reminder for me on how to do this.

If you have tried to set up a report server on the web client and run into any kind of trouble you may have noticed that there was very little diagnostic.  Yesterday I upgraded one of our customers from 7.2.2 to 7.5.1.  Most of it went smoothly (apart from some error in the downloaded installation files) but the reporting just would not work – Firefox just printed an obscure message “XML Parsing Error: no element found”, while Internet Explorer showed an empty page. 

image

Well actually they were both right – in lieu of a diagnostic message the report server page (SLXWebReportingServer.ashx) just did not return anything at all.  The first thing I did was make sure logging was enabled, and I edited log4net.config to make the name a bit more obvious (by default it just logs messages as “Service” – I changed it to “Saleslogix Web Reporting”).  Yet it still showed no message.  I had to get the report handler to print more diagnostic.  As it is a compiled assembly this cannot be done directly – however we can extract it using Reflector:

image

Paste the code in a file called SLXWebReportingServer.ashx in the slxwebrpt folder and add the handler directive at the top:

<% WebHandler language="C#" class="SLXWebReportingServer" %>

Then comment out the httpHandler directive in web.config and re-run the report (or you can just F5 the reporting window).  This will give a compilation error, of course, since we have not added any “using” directive.  Turn off customErrors in web.config (add a <customErrors mode=”off”/> under <system.web>) and add the missing using statements until you get the page to compile with the same error as the original one (there might be a way to get Reflector to generate the using statements, if so, i have not found it).  This is what I had to add for this one:

using System;
using System.Web;
using System.Web.SessionState;
using Interop.SLXWR;
using System.Runtime.InteropServices;
using log4net;
using Sage.SalesLogix.Reporting.Server;
using System.Data.OleDb;
using System.Data;
using System.Xml;
using System.IO;
using Sage.Platform;
using Sage.SalesLogix;

Create a logger object

public class SLXWebReportingServer: IHttpHandler, IRequiresSessionState
{
    private static readonly ILog LOG =
        LogManager.GetLogger(typeof(SLXWebReportingServer));

And verify that it works:

    public void ProcessRequest(HttpContext context)
    {
        try
        {
LOG.Warn("Boo");

OK, now we are ready to work.  The first thing I should have done at this point was add a log statement in the top-level catch block, at the very end of the file:

        catch (Exception exception2)
        {
LOG.Warn("Error in top level try/catch", exception2);
            context.Response.StatusCode = 500;
            context.Response.StatusDescription = exception2.Message;
        }

It’s a good idea to first scan for trapped, unlogged exceptions and make sure they are reported.  However, I smartly dove in and started peppering the code with LOG statements to get an idea of where it was hung:

    private static bool GetConnectionString(string userName, string password, string timeZoneKey, out string connectionString, out string dataSource, out string errorMsg)
    {
LOG.Info("GetConnectionString - 1");
        object obj2;
        connectionString = null;
        dataSource = null;
        errorMsg = null;
        string physicalApplicationPath = HttpContext.Current.Request.PhysicalApplicationPath;
        if (string.IsNullOrEmpty(physicalApplicationPath))
        {
            errorMsg = "ERR_APPLICATIONPATH";
            return false;
        }
LOG.Info("GetConnectionString - 2");
        string path = Path.Combine(physicalApplicationPath, "connection.config");
        if (!File.Exists(path))
        {
            errorMsg = "ERR_CONNECTIONCONFIG_NOTFOUND";
            return false;
        }
LOG.Info("GetConnectionString - 3");
        XmlDocument document = new XmlDocument();
        try
        {
            document.Load(path);
        }
        catch (Exception exception)
        {
            errorMsg = string.Format("ERR_CONNECTIONCONFIG_LOAD", exception.Message);
            return false;
        }
LOG.Info("GetConnectionString - 4");

You get the idea.  Eventually I found the GetRWPassword call was not returning and that’s when I realized there was a blanket catch statement.  From there the error was obvious (I had not installed the SQL native client on the report server) and of course the quick way to find out would have been to install the SalesLogix client on the box – but it was an interesting exercise nonetheless, and could prove useful in other cases.

To recap:

  1. First step should be to install the Sales client and make sure you can log in
  2. Then, edit the logging options to make sure you are looking for the correct messages
  3. If all fails, you can always replace the default handler to add some diagnostic
  4. Don’t forget to put the default handler back when done! :)

This is the ashx file I used (for version 7.5.1 – on other versions you’d have to make sure you get the appropriate code using Reflector).

Comments
1 Comment »
Categories
Uncategorized
Comments rss Comments rss
Trackback Trackback

Global Search / Replace (with Powershell)

Nicolas Galler | June 17, 2009

Had to rename an assembly and was faced with the daunting task of updating a few dozen business rules… I knew I had to do it on the backend as having to click my way through that in App Architect would take a day or more. This is slow but does the trick:

ls -recurse -filter *.xml |% { $text = (get-content $_.FullName) -replace "SourceAssemblyName", "NewAssemblyName"; $text | out-file -encoding utf8 $_.FullName }

Kinda like it better than the old find / grep / sed combo.

There is an alternative that checks if the text was actually modified (otherwise it can append newline to files which sometimes messes things up):

ls -recurse -filter *.xml |% {
$oldtext = [string]::join([environment]::newline, (get-content $_.FullName));
$text = $oldtext -replace "SourceAssemblyName", "NewAssemblyName";
if($text -ne $oldtext) { $text | out-file -encoding utf8 $_.FullName }
}

Finally I stuck this in my powershell profile (do notepad $profile to bring it up). This lets me use the “ReplText” command without having to retype the whole thing out. It takes the list of files from the input pipeline to be flexible:

# Replace text in every file listed on the input.
# $input: list of file objects (from pipeline)
# $pattern: source pattern (regular expression)
# $replacement: replacement (can have backreferences)
#
# Usage: dir -recur -filter *.xml | ReplText SourcePattern DestPattern
function ReplText($pattern, $replacement) {
  process {
    $oldtext = [string]::join([environment]::newline, (get-content $_.FullName))
    $newtext = $oldtext -replace $pattern, $replacement
    if($newtext -ne $oldtext) { $newtext | out-file -encoding utf8 $_.FullName }
  }
}
Comments
No Comments »
Categories
Uncategorized
Comments rss Comments rss
Trackback Trackback

Things to try with Windows 7 / Win 2008 R2

Nicolas Galler | June 5, 2009

I installed Windows 7 RC1 last week-end and so far it has much exceeded my expectations. While Vista had a “oh shiny… but nothing works?” feel to it, Windows 7 seems to be finally fulfilling all those promises (or some of them, anyway!). Lots of neat stuff coming out from MS lately, hope they can keep that up. I am almost considering replacing my Linux box with a Windows server (gasp) to get the document libraries to work correctly (Samba is OK for basic use but lacks the more advanced search features).

Beside a much polished UI and a few very nice usability improvements, here are a few things that I find interesting or will need to try on the more technical side:

  • Updated Virtual PC, now with USB support
  • Managed Services Account will allow password-less service accounts
  • VHD boot
  • Powershell 2.0
  • Search connectors
  • Multi touch? Maybe.

I still have not loaded it on my work machine, mind you – am not sure Saleslogix will like it too much.

Comments
No Comments »
Categories
Uncategorized
Comments rss Comments rss
Trackback Trackback

Easy Business Rules with Extension Methods

Nicolas Galler | June 1, 2009

I just realized that I could use C# extension methods to avoid the whole “open architect, expand the little tree to find my entity, add the business rule definition, select the assembly, select the method from the assembly, save, copy comment from the C# project, save again, build, deploy, pray that the deployment actually worked” process.

I can declare my business rule like this:

public static void CalculateProcessTask(this ITicketActivity ticketActivity)

save as TicketRules.dll, and add this to the code to use it:

using TicketRules;

Another huge benefit is I can now use F12 to navigate to the definition. One less reason to open the Application Architect for – YAY! Sadly there is no way to do something like that for events, or for code snippet properties. But if you add the namespace used for the extension rules to the web.config/system.web/pages/namespaces element (see http://www.west-wind.com/WebLog/posts/2287.aspx for an example) you can use it in snippet actions.

It also opens the door to something that is a bit scary… it looks like those rules could be defined in App_Code (the folder that contains dynamically compiled code for ASP.NET). This would let us edit them without having to recompile… or restart the IIS app… mucho más rápido! But this prevents the code from being unit-tested, and makes it harder to manage, so I am not very likely to use it (business rules typically don’t need to be adjusted very often anyway, once they are unit-tested)

Comments
No Comments »
Categories
Uncategorized
Comments rss Comments rss
Trackback Trackback

SlxGridHelper – Make the SlxDataGrid more convenient in our custom smart parts

Nicolas Galler | May 22, 2009

There are a few things that I like to set on all my datagrids (double-click edit, single-click select, delete/add button). In an earlier post I had described how to tweak the quickform template so that every quickform containing a grid would receive these changes.  However there were a number of drawbacks to that approach, starting with the fact that it was rather tedious to implement and affected “stock” grids negatively.  I also found out that the limit of quickforms were very easy to hit and therefore ended up doing custom smartparts in most cases.  Therefore I decided to group this functionality in an external control that would be included on the custom smart part and modify the grid’s behavior using its public API.

The general principle is the same as the one used in the previous approach, but with the increased flexibility I wanted to implement the following features:

  • Ability to specify Add/Delete buttons, using control ids pointing to other objects in the form
  • Ability to have a grid’s title bar generated, optionally including Add/Delete buttons (useful when there are several grids on a form, or a grid and some other controls)
  • Optionally specify the databinding and parent/child relationship, so as to avoid having to put the datasource code on the smartpart itself (the code to bind a Saleslogix datagrid is rather noisy and not too pretty to look at)

The code for a typical grid… with an automatically generated toolbar:

<SSS:SlxGridHelper ID="grdSalesEntitiesHelper" runat="server" GridId="grdSalesEntities"
    GridTitle="Sales Entities" ShowAddButton="true" ShowDeleteButton="true" AutoBind="true">
    <DialogSpecs ActiveSmartPartID="SEOppSalesCreditForm" CenterDialog="true"
     DialogHeight="300" DialogWidth="600" Title="Edit Sales Entity">
        <MyChildInsertInfo ChildEntityTypeName="Sage.Entity.Interfaces.ISEOppSalesCredit, Sage.Entity.Interfaces"
             ParentEntityTypeName="Sage.Entity.Interfaces.IOpportunity, Sage.Entity.Interfaces"
             ParentReferencePropertyName="Opportunity"
             ParentsCollectionPropertyName="SEOppSalesCredits" />
    </DialogSpecs>
</SSS:SlxGridHelper>
<SalesLogix:SlxGridView runat="server" ID="grdSalesEntities" GridLines="None"
AutoGenerateColumns="false" CellPadding="4" CssClass="datagrid" PagerStyle-CssClass="gridPager"
AlternatingRowStyle-CssClass="rowdk" RowStyle-CssClass="rowlt" SelectedRowStyle-CssClass="rowSelected" ShowEmptyTable="true" EnableViewState="false"
 ExpandableRows="True" ResizableColumns="True"  >
<Columns>
  <asp:ButtonField CommandName="Edit" DataTextField="SalesmanName" HeaderText="Salesman Name" />
  <asp:BoundField DataField="Adder" HeaderText="Adder"/>
  <asp:TemplateField HeaderText="Percentage">
  <ItemTemplate>
    <asp:Label runat="server" Text='<%# Bind("Percent", "{0:##%}") %>' ID="lblPercent" />
  </ItemTemplate>
  </asp:TemplateField>
  <asp:BoundField DataField="Title" HeaderText="Title" />
 </Columns>
</SalesLogix:SlxGridView>

And for a grid that is included on a tab (using the built-in toolbar – notice the “DeleteButtonId” and “AddButtonId”):

<SSS:SlxGridHelper ID="grdHelper" runat="server" GridId="grdLDC"
    AddButtonId="btnAdd" DeleteButtonId="btnDelete"
    GridTitle="" ShowAddButton="false" ShowDeleteButton="false" AutoBind="true">
    <DialogSpecs ActiveSmartPartID="SEOppLdcForm" CenterDialog="true"
     DialogHeight="300" DialogWidth="600" Title="Edit LDC Account">
        <MyChildInsertInfo ChildEntityTypeName="Sage.Entity.Interfaces.ISEOppLDCAccount, Sage.Entity.Interfaces"
             ParentEntityTypeName="Sage.Entity.Interfaces.IOpportunity, Sage.Entity.Interfaces"
             ParentReferencePropertyName="Opportunity"
             ParentsCollectionPropertyName="SEOppLDCAccounts" />
    </DialogSpecs>
</SSS:SlxGridHelper>
<SalesLogix:SlxGridView runat="server" ID="grdLDC" GridLines="None" AllowSorting="true" AllowPaging="true"
AutoGenerateColumns="false" CellPadding="4" CssClass="datagrid" PagerStyle-CssClass="gridPager"
AlternatingRowStyle-CssClass="rowdk" RowStyle-CssClass="rowlt" SelectedRowStyle-CssClass="rowSelected" ShowEmptyTable="true" EnableViewState="false"
 ExpandableRows="True" ResizableColumns="True"  >
 <Columns>
    <asp:ButtonField CommandName="Edit" HeaderText="LDC Name" DataTextField="ProspectName" ButtonType="Link" SortExpression="ProspectName" />
    <asp:BoundField HeaderText="City" DataField="City" SortExpression="City" />
    <asp:BoundField HeaderText="State" DataField="State" SortExpression="State" />
    <asp:BoundField HeaderText="Zip" DataField="Postalcode" SortExpression="Postalcode" />
    <asp:BoundField HeaderText="Zone" DataField="Zone" SortExpression="Zone" />
 </Columns>
</SalesLogix:SlxGridView>
<SalesLogix:SmartPartToolsContainer runat="server" ID="toolbar" ToolbarLocation="right">
    <asp:ImageButton runat="server" ID="btnAdd" ImageUrl="ImageResource.axd?scope=global&type=Global_Images&key=plus_16x16"
        AlternateText="Add LDC Account Number" ToolTip="Add LDC Account Number" />
    <asp:ImageButton runat="server" ID="btnImport" ImageUrl="ImageResource.axd?scope=global&type=Global_Images&key=Import_History_16x16"
        AlternateText="Import from Spreadsheet" ToolTip="Import from Spreadsheet" />
    <asp:ImageButton runat="server" ID="btnDelete" ImageUrl="ImageResource.axd?scope=global&type=Global_Images&key=Delete_16x16"
        AlternateText="Remove LDC Account Number" ToolTip="Remove LDC Account Number"
        OnClientClick="return confirm('Are you sure you wish to delete this LDC account number?');" />
</SalesLogix:SmartPartToolsContainer>

 

There is no associated code-behind in either case (of course, if I wanted to customize the databinding, I would need to do that in the code behind).

The result looks like this (this is a grid with a “generated” toolbar):

AddlDetails

The code is simple – similar to what I used before in the quickform version.  I saved a copy here if interested feel free to rummage through it and keep what you want.

Comments
2 Comments »
Categories
Uncategorized
Comments rss Comments rss
Trackback Trackback

Using IChangedState to track before/after values of an entity

Nicolas Galler | May 11, 2009

This is a very nifty feature of the new Saleslogix platform that addresses the very common scenario of detecting when the user edited a field.  In the legacy client we often had to either run a SQL or manually save the old value to a global on the form – this was a rather tedious and dirty if you had to check a lot of properties.  In this new platform the change tracking is built-in – there are a few steps to it so it does not necessarily save in terms of line of code but it is less messy.

The basics are documented in the Saleslogix Web Client FAQ:

private static String GetOldMainPhoneValue(IAccount account)
{
    IChangedState accountState = account as IChangedState;
    if (accountState != null)
    {
        ChangeSet changes = accountState.GetChangedState();
        PropertyChange mainPhoneChange = changes.FindPropertyChange("MainPhone");

        if (mainPhoneChange != null)
        {
            return (string) mainPhoneChange.OldValue;
        }
    }
    return null;
}

However there are a few pitfalls.

First of all and not really related, but I found out your business rules would not execute if you specified it as a “Pre-Execute” or “Post-Execute” target but did not have a primary step on the rule.

Secondly, you have to know when this update information is available.  As far as I can tell this is available on the form handlers, as well as on the “OnBeforeUpdate” entity events, but not once the entity has been saved (e.g. on the OnAfterUpdate event).

Another point is that there is a slightly different syntax depending on the type of change to be tracked.  If it is a simple property change the above code (FindPropertyChange) works.  However if the change is on a related entity… for example opportunity.AccountManager… you have to use this syntax:

ChangeSet changes = changeState.GetChangedState();
EntityPropertyChange change = changes.FindMemberChange<EntityPropertyChange>("AccountManager");
string newUserId = (String) change.NewEntity.EntityId;
string oldUserId = (String)change.OldEntity.EntityId;

Unfortunately this does not work with simple properties… so you have to know which property you are dealing with in order to know how to examine the changes.

Comments
No Comments »
Categories
Uncategorized
Comments rss Comments rss
Trackback Trackback

« Previous Entries

Categories

  • Experiments (4)
  • Interesting (1)
  • MSCRM (1)
  • Programming (60)
  • Rant (3)
  • Saleslogix (34)
  • Tricks (8)
  • Uncategorized (24)

Post History

  • 2010
    • January (3)
    • March (1)
  • 2009
    • March (2)
    • April (1)
    • May (3)
    • June (3)
    • July (1)
    • September (3)
    • October (2)
    • December (5)
  • 2008
    • January (9)
    • February (4)
    • March (9)
    • April (1)
    • May (5)
    • June (8)
    • July (1)
    • August (2)
    • September (1)
    • November (1)
    • December (3)
  • 2007
    • January (3)
    • February (7)
    • March (1)
    • April (3)
    • May (6)
    • June (2)
    • July (1)
    • August (2)
    • September (5)
    • October (3)
    • November (5)
    • December (4)
  • 2006
    • January (2)
    • September (1)
    • November (3)
    • December (4)
  • 2005
    • April (1)

Meta

  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org
rss Comments rss valid xhtml 1.1 design by jide powered by Wordpress get firefox