Hacking Customer Lifecycle Management (CLM)

Today I will show you how I made simple modifications to Infor Customer LifeCycle Management (CLM), the CRM product for Infor M3. With CLM standard out-of-the-box we only have the ability to show/hide fields, for example choosing whether or not we want the name, address, and phone number columns in the list or the fields in the details view. CLM is a great product, and by design it is intended to be simple to use. In my case I wanted something more: I needed to add a call button next to the phone number. That is not officially possible by default so I had to do some hacking.

I’m working on a project for a customer to integrate Cisco IP phones with CLM, such that when a customer service representative on the phone receives an incoming phone call we automatically pop-up the corresponding customer data on the screen, and conversely, such that they can click a phone number in CLM and make that outgoing phone call. I had already done some work in the past integrating Skype with Smart Office, and integrating ShoreTel phones with Smart Office. This time it’s Cisco IP phones. I cannot show you the entire source code as it’s propriety of the customer and my employer, but I will show you interesting bits and pieces, and the writing helps me clean-up my code too.

I will show you:

  • How to get the list of open CLM windows
  • How to find the phone number field
  • How to add a button to CLM and use the Design System Icons
  • How to make the outgoing phone call

About CLM

To tell if you have CLM, go to Smart Office > Help > About Infor Smart Office > View features, and you will see CLM Application:
1

Then, go to the Navigator widget, you will see the menu Customer Lifecycle Management, expand it and launch My Accounts > All:
2

It will open the list of accounts:
3_

Double-click one of the rows to open the account details:
4_

How to get the list of open CLM windows

Now we have two CLM windows open: the list of accounts, and the details of an account. To programmatically get that list, I use the DashboardTaskService.FindRunningTaskByUri method, to discriminate by Uri lclm://
0


var list /*System.Collections.Generic.List<Mango.UI.Services.FindTaskResult>*/ = DashboardTaskService.Manager.FindRunningTaskByUri("lclm://", TaskMatch.StartsWith);
for (var result : FindTaskResult in list) {
    var runner : IRunner = result.Runner;
    var task : ITask = runner.Task;
    debug.WriteLine(task.Uri);
}

That will return two tasks:
lclm://filter/?ActionType=View&MainTableID=…&FilterGroupID=…&SubFilterID=…
lclm://details/?ActionType=View&MainTableID=…&PrimaryKey=…

Now we need to tell apart the Accounts windows from the other potential CLM windows such as Activities or Contacts:

var host : IInstanceHost = runner.Host;
if (host.HostTitle.StartsWith("Account")) {
    // ...
}

This code will only work for English. Ideally we would use an official CLM API that returns the correct Tasks, but I haven’t found one. Let me know if you find one.

How to find the phone number field

Now that we have the correct window, we can get its contents and find the phone number field. First, I use WPF Inspector to visually introspect the window and find the phone number field in the visual tree:
6

The fields are layed out in one of the ancestor Grids:
5

More specifically the phone number field is itself a Grid of one row and three columns:
6_

That’s where I’ll inject my button. To get there programmatically, I use the VisualTreeHelper, and I do a pre-order depth first search:

function ... {
    //...
    var content : FrameworkElement = host.HostContent;
    if (content.GetType().ToString() == "lclmControls.Custom.TabularDetailsView") {
        var o: DependencyObject = FindPhoneTextBox(content);
        if (o != null) {
            var txtbox: SingleLineTextBox = o;
        }
    }
}
function FindPhoneTextBox(o : DependencyObject): DependencyObject {
    // visit node
    if (o != null) {
        if (o.GetType().ToString().EndsWith("SingleLineTextBox")) {
            var txtbox: SingleLineTextBox = o;
            if (txtbox.Name == "Phone") {
                return o;
            }
        }
    }
    // visit children
    for (var i = 0; i < VisualTreeHelper.GetChildrenCount(o); i++) {
        var child = VisualTreeHelper.GetChild(o, i);
        var result: DependencyObject = FindPhoneTextBox(child);
        if (result != null) {
            return result;
        }
    }
    // not found
    return null;
}

How to add a button to CLM and use the Design System Icons

Now that we found the phone number field and the Grid, we can add a button. I will use the IconButtons of the Design System as illustrated by norpe:

var btn: IconButton = new IconButton();
btn.IconName = "Phone";
btn.ToolTip = "Call this phone number."
btn.HorizontalAlignment = HorizontalAlignment.Left;
btn.Margin = new Thickness(5, 0, 0, 0);
btn.Tag = txtbox; // remember the textbox
Grid.SetRow(btn, 0);
Grid.SetColumn(btn, 2);
Grid.SetColumnSpan(btn, 2);
var grid: Grid = VisualTreeHelper.GetParent(txtbox);
grid.Children.Add(btn);
btn.add_Click(OnCall);

This is the result, with the IconButton hover and ToolTip:
7_

Note: to get the Grid I used the textboxe’s parent. This assumption is true in CLM version 1.0.0.99, but could be false in a future version of CLM in which case this code would break. Ideally we would have an official CLM API for this.

How to make the outgoing phone call

Now you can do whatever with the phone number, for example use the default operating system’s URI handler for the tel scheme which is Skype in my case.

function OnCall(sender: Object, e: RoutedEventArgs) {
    try {
        var btn: Button = sender;
        var txtbox: SingleLineTextBox = btn.Tag;
        var phoneNumber: String = txtbox.Text;
        var uri: Uri = new Uri("tel:" + phoneNumber); // RFC 3966
        ScriptUtil.Launch(uri);
    } catch (ex : Exception) {
        debug.WriteLine(ex);
    }
}

And here’s the result:
8_

The method ScriptUtil.Launch will instruct the operating system to execute the specified command. That’s the equivalent of typing start command at the DOS prompt. In our case it’s:

start tel:+14156247033

9

That means any special characters of the command must be escaped, such as white spaces and ampersands. To escape white spaces in DOS that means enclosing the entire string in double-quotes. I tried enclosing the URI in double quotes, and it didn’t work. I also tried other escaping and encoding techniques like using backslash, plus sign, and %20, and they didn’t work either. So let’s simply strip it out:

phoneNumber = phoneNumber.Replace(' ', '');

Also, in my example I used a phone number that’s already correctly formatted in international E.123 notation which Skype understands. To validate the phone number, we can use a regular expression. A simple one is to strip all characters and keep only the plus sign and the digits, but that’s probably not fully compliant with the E.123 specification so we need to work more on this in the future:

var regex = /[^\+^\d]/g;
phoneNumber =  phoneNumber.replace(regex, "");

Future work

Future work includes:

  • Use an event handler to listen for new CLM windows to automatically add the Call button as the user opens the Account views. I couldn’t find an event, and Karin confirmed it’s not currently supported. I tried MForms.MainController, DashboardTaskService, DashboardTaskBar, lclmControls.EventNotifier, WindowManager, etc. I found an event handler for M3 Forms, an event handler for the Quick Start CTRL+R, and private event handlers that would have worked had they been public. Nothing I could use. I ended up using a worker that’s polling Tasks every second in a background thread (yikes).
  • Remember we added the button so we don’t add it again next time.
  • Add the Call button on all phone number fields: fax, mobile phone, home phone, etc.
  • Validate the phone number with a regular expression that complies with the specifications.
  • Make the outgoing call thru the Cisco IP phone instead of using Skype.
  • Listen for incoming phone calls.
  • Move the script to a widget using the Smart Office SDK.

That’s it! If you like this post, subscribe to this blog. And if you rock, become an author to share your ideas.

Published by

thibaudatwork

M3 Technical Consultant

10 thoughts on “Hacking Customer Lifecycle Management (CLM)”

  1. UPDATE: The Infor sales demo team has informed me they made a proof-of-concept that illustrates how to integrate Microsoft Lync with Infor Smart Office to receive phone calls. Great. Let’s add this to the list of known computer telephony integration solutions for Infor Smart Office: Skype, Twilio, ShoreTel, Cisco, Microsoft Lync.

    Like

  2. Hi Thibaudatwork 🙂

    Great work, I actually was looking for anyway to be able to add buttons on CLM screens to allow Scripts functionality, you have made a huge difference to me :-).

    But I have one question, this is the first time I know that CLM has APIs, so where can I check the API list? also where can I find the port to connect to it on the server!!??

    Appreciate if you have any Docs on this.

    Like

    1. Hello Mohamed, I’m glad you like the blog post. Yes, CLM and any application in Smart Office has APIs, some private, some public, some documented, some undocumented. The results I wrote on this post were by way of hacking into CLM. By hacking I mean just dis-assembling the .NET DLLs of the Smart Office folder, analyzing the packages, namespaces, Classes, Constructors, Properties, Methods, and Events, and learning results by trial and error. Start by reading my post on the tools I use for Smart Office: https://thibaudatwork.wordpress.com/2011/09/29/tools-to-develop-smart-office-scripts/ . I use Red Gate’s .NET Reflector. There are other dis-assembler tools like JetBrains dotPeek.

      Like

      1. Thanks thibaudatwork for the reply and the clarification, I will start digging using the methodology you mentioned, and will get back to you later if I got any new ideas to discuss it with you 🙂

        Like

  3. UPDATE: I just found a super short path to find the phone number field, I’ll let you break it up: ((SingleLineTextBox)((TabularDetailsView)task.Parameter).DetailsView.BaseDialog.DataGroups[0].GetSections()[0].GetField(“Phone”).DataEditor)

    Like

  4. By coincidence, while talking about another subject on the phone, I just met the great team behind CLM for Smart Office and mobile: Cherie, Sony, and Mark. I re-iterated my want for these API in CLM: get an event for incoming calls, validate phone numbers as we are typing, store phone numbers in parsed form, and add a button to call the phone number. Salamat!

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s