Quantcast
Channel: SalesLogix – Customer FX
Viewing all 169 articles
Browse latest View live

Required Fields Validation and Save Buttons in Infor CRM (Saleslogix) Web

$
0
0

The “Performs Update” Property in the Button Click Actions

Validation of required fields in Infor CRM (Saleslogix) Web is something that is built in. However, how you set up the save button on your form does make a difference on whether or not it works. When you add a Save button to a form, by default it will set the Save button’s “Performs Update” property to true.

PerformsUpdateProperty

What this “Performs Update” property does is causes the save button to be registered as a save button with the ClientBindingManagerService. A button that is registered as a save button has it’s onClick event connected to the Sage.Utility.Validate.onWorkSpaceSave function. This function takes the top DOM node of the workspace and iterates through any dijit based controls that are located within the hierarchy below the top DOM node and invoke’s it’s validation. If any control fails validation, it’s isValid flag is set to false and the control is in an error state (indicated by the red error state of the control)

picklist_error

Problems with Required Field Validation on Save

Here’s where things can go wrong. If your save button does not have it’s “Performs Update” property set to true, any required fields will no longer be required. If you click the save button in Chrome & Firefox it will just save the form, even if required fields are missing. However in IE, the behavior is a bit different. If the form has it’s dirty data message showing, clicking the save button will do nothing at all and you will NOT see the red error state of required fields – just nothing happens with no indication as to why (in IE if there is no dirty data flag, it will save normally).

This is an important thing to remember. If you add a Save button to your form in AA and then change the action – for example, from the Save business rule to a C# Snippet Action instead, AA will automatically change the “Performs Update” from true to false. Then your save button will no longer be registered as a save button.

Registering Save Buttons on Custom SmartParts

If you’re working with a custom SmartPart, you’ll still need to register your Save buttons as save buttons in order to take advantage of the standard required field validation. To do this, add the following code to the OnFormBound event of your SmartPart:

// ClientBindingMgr is built-in from EntityBoundSmartPart
// Otherwise it can be obtained via:
// var ClientBindingMgr = PageWorkItem.Services.Get<ClientBindingManagerService>();

ClientBindingMgr.RegisterSaveButton(cmdSave);

Now, when your Save button (cmdSave) is clicked it will automatically trigger the onWorkspaceSave function which will cause validation to run on all dijit based controls.


Passing Values Between Forms in Infor CRM (Saleslogix) Web

$
0
0

There are a few different ways to pass values or objects between forms in the Infor CRM (Saleslogix) Web client. Since Infor CRM is an ASP.NET application, you have all the things available in ASP.NET for persisting values such as the Session (See Kris Halsrud’s article on using the Session in Infor CRM). There is also a built in way to persist values as well using the State collection, which is available from the PageWorkItem.

Using the Page State Collection

The State collection works in a similar way to the ASP.NET Session (see differences at end of this article). Using it is identical to how to use the session. Here’s a sample:

// add myvalue
this.PageWorkItem.State["MySavedValue"] = myvalue;

// then later to retrieve
string myvalue = this.PageWorkItem.State["MySavedValue"].ToString();

// to remove
this.PageWorkItem.State.Remove("MySavedValue");

You can also use it to pass objects and Infor CRM entities around as well. Let’s say you have a form where you want to allow the user to select products to add to something, but you don’t want to save them right away until the user does some action. You can persist the selected products in the page state until you’re ready to save them. Something like this:

var prod = this.BindingSource.Current as Sage.Entity.Interfaces.IProduct;
//prod is my already populated object that was bound to a form in a dialog.
//instead of calling it's Save method, will store it in the State to call this later.
 
List<Sage.Entity.Interfaces.IProduct> list;

// first check the page state to see if you have the collection of products saved
// if it doesn't exist, create an empty list, otherwise get the collection from 
// the page state and add the product to it 
if (this.PageWorkItem.State["ProductsList"] == null)
    list = new List<Sage.Entity.Interfaces.IProduct>();
else
    list = (List<Sage.Entity.Interfaces.IProduct>)this.PageWorkItem.State["ProductsList"];
 
list.Add(prod);

Now, later when you want to actually save these products you could do this:

var myEntity = this.BindingSource.Current as Sage.Entity.Interfaces.IMyEntity;
 
List<Sage.Entity.Interfaces.IProduct> list;

// get the list of products from the page state
if (this.PageWorkItem.State["ProductsList"] != null)
    list = (List<Sage.Entity.Interfaces.IProduct>this.PageWorkItem.State["ProductsList"];
 
if (list != null)
{
    foreach (Sage.Entity.Interfaces.IProduct prod in list)
    {
        // add to the entity (or whatever you need to do with them)
        myEntity.Products.Add(prod);
        // etc
    }
}

myEntity.Save();

Of course, that is just an example, but I wanted to point out how easy it is to pass objects around using the page state.

One more note, if you don’t have a built-in PageWorkItem in wherever you’re using this code, you can get it via the locator service. Note: PageWorkItem is already built-in to all quickforms and any SmartParts that inherit from EntityBoundSmartPart.

IPageWorkItemLocator locatorService = Sage.Platform.Application.ApplicationContext.Current.Services.Get<IPageWorkItemLocator>();

Differences Between Using Session and Page State

There are some differences between using the ASP.NET Session and the built-in Page State collection. One way is not right and the other wrong; instead, it depends on how you need to use it. Each have different purposes.

The ASP.NET Session object is maintained for each user that requests a page or document from the Infor CRM ASP.NET application. Variables stored in the Session object are not discarded when the user moves from page to page in the application; instead, these variables persist as long as the user is accessing pages in your application. So, to be clear, when you set something in the Session, it stays there until the user logs out our closes the browser (or something else happens to cause the session to clear or renew). This means, if you put something in the session on PageA, then the user leaves this page and does other stuff and later returns to PageA, whatever you put into the session will still be there. You might not want this behavior. Instead, it sometimes makes sense to start fresh when the user comes to PageA and not have whatever was saved in the session from their last visit to PageA.

The built-in Page State works in the same way as the ASP.NET session, except it only persists as long as the user is on that page. So, if the user visits PageA and you put something into the page state, then they leave and do other things and later return to PageA, you’re starting fresh, without the value you put into the state the last time. One thing to keep in mind, dialogs are a part of the page. So, if you put something in the page state in a dialog, that will be available on other forms on that page. So any SmartPart or QuickForm added to the page can access values stored in the state for that page.

Troubleshooting Mobile Customizations Not Loading in Infor CRM (Saleslogix)

$
0
0

This article will outline tips to consider when working with mobile customizations in Infor CRM (Saleslogix) and your customizations are not showing up. The first, and most important thing to know, is whether your customizations are even getting loading by the browser.

Checking If Your Customizations Are Being Loaded

As I mentioned, knowing whether your customizations are even being loaded by the browser is the first thing to check. To check this, open the mobile site (just need it to load, you don’t even need to log in) in Chrome and open the developer tools by accessing the Chrome menu Chrome menu at the top-right of your browser window, then select Tools > Developer Tools. With the developer tools open, click the Sources tab on the top, then expand SlxMobile\content\javascript. You should see your customization file listed there (keep in mind that it is minified into a single file named “argos-” followed by your customization file name (this file name is determined by your build file: See Bundling and Deploying Mobile Customizations for more info on the build file).

mobile-customizations-loading

If you see your customization file there, that tells you that it is properly added to the index.html/index.aspx and being accessed. This tells you that the mobile client “sees” your customization file, which is what you want.

Checking the Code to See If Your Customizations Are Present

While you have the developer tools open and your file selected from the previous step, you’ll see the code looks compact and all pushed together into a single line. If you’d like to view the code there in a more readable format (so you can verify the code that is in the file and make sure it is up to date), copy the big long single line of code and go to jsbeautifier.org and paste in the code. It will get it all formatted nicely again. Keep in mind that if your customization consists of several different files it will all be in that single file now after being deployed. Now you can easily scan through the code to ensure it is up to date with all of the code changes.

Checking For Errors From Your Customizations

With the developer tools open, click on the Console tab at the top. Any errors from your javascript (assuming it is loading as outlined in the previous steps) will show here in the console (in red). You’ll likely see other items listed there as well, and the far right side will show the file that is generating the error (which could show up as dojo.js, but hopefully will show up as your file name). If there are errors listed there you can go and address those, deploy, and reload to see your changes.

Clearing the Cache to Get The Current Version of Your Customizations

If the previous steps are showing an older version of your customizations, you can cause the cache to clear by touching the index.aspx file. Simply, open the file, make a small change like adding a space somewhere, and then save. Now when you go to the mobile site it will reload all of the current files from the file system into cache and you should be seeing the newest version of your customizations. Keep in mind that deploying does this automatically. However, touching the file can be a quick trick if you need it.

Displaying Actual Dates Instead of Relative Dates in Infor CRM (Saleslogix) Mobile

$
0
0

In the Infor CRM (Saleslogix) Mobile client, dates in group lists are displayed as relative dates. You’ll see dates in the group list like “10 days ago”, “3 months ago”, or “a year ago”, and so on. This applies to future dates as well, seeing values like “in a month”.

Example:

Notice the relative dates in the screenshot above. However, if you’d like to see actual dates instead of relative dates there is a simple solution.

Displaying Actual Dates in Group Lists

To display actual dates instead of relative dates in all group lists (keep in mind this change applies to all dates in all group lists), simply add the following to your ApplicationModule. Note: the reference to GroupUtility in the AMD references.

define('Mobile/CustomApp/ApplicationModule', [
    'dojo/_base/declare',
	'Mobile/SalesLogix/GroupUtility'
], function (
    declare,
	groupUtility
) {
    return declare('Mobile.CustomApp.ApplicationModule', Sage.Platform.Mobile.ApplicationModule, {
        loadCustomizations: function () {
            
            // Do not use relative dates in group lists
            groupUtility.groupFormatters.forEach(function(formatter) {
                if (formatter.name == 'DateTime') {
                    formatter.options.useRelative = false;
                }
            });
			
        }
    });
});

With that code in place you’ll see the actual dates.

Retrieving Picklist and Picklist Items via Infor CRM (Saleslogix) SData using C#

$
0
0

Accessing system level data, such as picklists, groups, etc, is an easy task with the SData system endpoint. In this post we will look at how to retrieve picklists and picklist items using SData, C#, and the DotNetSDataClient library.

The DotNetSDataClient Library

As I mentioned in an earlier post outlining SData resources, the DotNetSDataClient Library is a newer, officially supported client library for SData and .NET. This library replaces the older SDataCSharpClientLib library and is the recommended library for SData when using .NET. It is available via NuGet as DotNetSdataClient or on github, and has excellent documentation.

Retrieving Picklists and Picklist Items via SData

The way the DotNetSDataClient Library generally works is you create POCO (Plain Old CLR Object) classes for the entities and the library retrieves the data and fills the objects. If you’re familiar with Sublogix, it works the exact same way. For retrieving Picklists, the first thing we need to do is create the objects. We only need to create properties for the SData entity properties we’re interested in keeping. We could add more properties for Picklist entity properties and they will be filled as well (such as Id etc).

[SDataPath("pickLists")]
public class PickList
{
    public string Name { get; set; }
    public IList<PickListItem> Items { get; set; }
}

public class PickListItem
{
    public string Text { get; set; }
    public string Code { get; set; }
}

Now that we have those objects we can have the library fill them with our Picklist data.

// create the SData client
var client = new SDataClient("http://localhost/sdata/slx/system/-")
{
    UserName = "admin",
    NamingScheme = NamingScheme.CamelCase
};

// use the client to get the Picklist with ID kSYST0000001 and include the items 
var pickList = client.Get<PickList>("kSYST0000001", null, new SDataPayloadOptions {Include = "items"}); 

// display the picklist name on the console
Console.WriteLine(pickList.Name); 

// display the items on the console
foreach (var item in pickList.Items) 
{
    Console.WriteLine("- ({0}) {1}", item.Code, item.Text); 
}

That’s it. View more about retrieving picklists in the documentation.

Overriding Field Level Security Control Disabling in Infor CRM (Saleslogix) Web

$
0
0

Binding controls in Infor CRM (Saleslogix) Web forms will also automatically disable controls based on field level security for the current user. But what if you want to override that behavior? You might want to still allow a user to add data into those controls on an insert screen even though they would normally be disabled based on FLS for the user.

The control bindings include the ability to override this behavior. You simply loop through all bindings on the form and tell it to ignore the FLS rules and not disable the control.

foreach (Sage.Platform.EntityBinding.IEntityBinding eb in BindingSource.Bindings)
{ 
    WebEntityBinding wb = eb as WebEntityBinding;
    wb.IgnoreFLSDisabling = true;
}

You would put that code into a LoadAction on the form (make sure it’s RepaintEvent property is set to true)

Infor CRM (formerly Saleslogix) 2015 Infor NEXT Conference: The Inside Scoop

$
0
0

Join us Wednesday, November 18th at 2pm CST.

Infor Conference

The Infor CRM 2015 annual conference just wrapped up, and while it was great to see and meet so many customers and vendors, we know that many of you were unable to attend. We don’t want anyone to miss out on the information provided, so we thought we would give you the inside scoop on some of the most important takeaways from this years conference. Join us as we share information related to:

• A road map for the future of Infor CRM (formally Saleslogix).
• Updates to the user interface and technologies.
• The vision and positioning of Infor CRM.
• New features planned for End Users, Administrators, and Developers in 2015/2016.
• And more!

Register now!

Improving Performance of the Initial Load Time of Infor CRM (Saleslogix) Web

$
0
0

We received a tip from a customer (Rob Seiwert at Video Corporation of America) last week that is worth passing along. This tip will improve the performance of your Infor CRM (Saleslogix) Web and reduce the initial load time after periods of inactivity, after a server reboot, or after a new deployment has been performed.

Pre-Compiling and IIS Application Pools

In the Application Architect there is an option to pre-compile when doing a deployment. While pre-compiling does help for worker processes, it doesn’t solve the startup issue. By default, an IIS application pool starts “on demand”. Meaning, that when the website is accessed it spins itself up, an IIS worker process is created and started, the appropriate assemblies are loaded into memory, threads are created a queued, and the code is executed. In short, the application pool isn’t started until it’s used, then the user waits for all of that initialization to happen.

I’ve seen many attempts at solving this over the years, such as applications that access each URL in a website each morning to cause things to load into memory, pre-initializing things before users start using things.

The IIS Application Initialization Module

To solve the issue stated earlier, the IIS team at Microsoft has created a module for IIS (supporting IIS 7 and higher) called the Application Initialization Module.

You can use the Web Platform Installer in IIS to install this module. Once installed, you can change the application pool’s start mode from “on demand” to “always running”. By setting the startMode to AlwaysRunning, when you recycle the process, perform a reboot, or redeploy CRM, the application pool’s IIS w3wp.exe process is started up automatically, rather than waiting for a user to come access CRM. In addition, you can enable “preload” for the website which causes a similar effect to a user accessing the website and causing pages to be background compiled by ASP.NET.

Read more about installing and configuring the IIS Application Initialization Module

One thing to keep in mind, that you’ll see the IIS processes in memory, basically all the time from that point on. Not a huge concern however, since this is what happens when a user hits CRM anyway. All in all, what you’re doing with this module, is simulating a user going to the website every so often to keep the worker processes running and loaded. This way, when real users access CRM, it’s ready to go and will perform better. Obviously, this isn’t the sort of thing you’d want to enable on a development deployment.

According to the customer we received this tip from, they are noticing a huge difference in the initial load time of their Infor CRM deployment. Our customers are completely great. It was great to be the receivers of such a helpful tip. Thanks Rob!


Programmatically Setting Custom Conditions for the Lookup Results Group in Infor CRM (Saleslogix) Web

$
0
0

The Lookup Results Group in Infor CRM displays a list of records matching a particular set of conditions. Typically, the user performs a search and the search conditions set the results of the Lookup Results group. However, it is possible to programmatically set these conditions at runtime.

Kris Halsrud previously wrote about setting the Lookup Results based on a custom condition. This article is going to expound on that idea and look at all the options available.

See Kris Halsrud’s article:
Programmatically Creating a Temporary Lookup Group for a specific entity in the SalesLogix web client

Let’s look at the base code (in this sample, we are on the opportunity entity and setting the condition to display all opportunities for a particular account):

// get group context service
Sage.SalesLogix.Client.GroupBuilder.IGroupContextService groupContextService = Sage.Platform.Application.ApplicationContext.Current.Services.Get<Sage.SalesLogix.Client.GroupBuilder.IGroupContextService>() as Sage.SalesLogix.Client.GroupBuilder.GroupContextService;
if (groupContextService != null)
{
    // set base table
    groupContextService.CurrentTable = "OPPORTUNITY";
        
    // set group info
    Sage.SalesLogix.Client.GroupBuilder.EntityGroupInfo currentGroupInfo = groupContextService.GetGroupContext().CurrentGroupInfo;
    currentGroupInfo.LookupTempGroup.ClearConditions();
    
    // this is the important part, this is where you set the 
    // conditions you want to display in the lookup results.
    // add conditions in form of data path "TABLE:FIELD" and value.
    // other overloads allow operation and conjunction for multiple conditions
    // in this example we will set the lookup results for all opportunities for a particular account
    currentGroupInfo.LookupTempGroup.AddLookupCondition("ACCOUNT:ACCOUNTID", "AGHEA0002669");
        
    // add conditions to group XML & set as lookupresults group
    currentGroupInfo.LookupTempGroup.GroupXML = Sage.SalesLogix.Client.GroupBuilder.GroupInfo.RebuildGroupXML(currentGroupInfo.LookupTempGroup.GroupXML);
    groupContextService.CurrentGroupID = "LOOKUPRESULTS";
}

In the code above, the important part is this line:

currentGroupInfo.LookupTempGroup.AddLookupCondition("ACCOUNT:ACCOUNTID", "AGHEA0002669");

There are some other overloads for AddLookupCondition that allow you to specify the operator (such as equals, starts with, etc) and the conjunction (and, or, etc). One overload allows you to include the operator only (for setting a single condition):

currentGroupInfo.LookupTempGroup.AddLookupCondition("ACCOUNT:ACCOUNTID", "=", "AGHEA0002669");

If no operator is specified, the default is ” STARTS WITH ” (See operator choices below). The other overload that allows you to specify both the operator and the conjunction:

// set the lookup results for all opportunities for two different accounts
currentGroupInfo.LookupTempGroup.AddLookupCondition("ACCOUNT:ACCOUNTID", "=", "AGHEA0002669", "OR");
currentGroupInfo.LookupTempGroup.AddLookupCondition("ACCOUNT:ACCOUNTID", "=", "AA2EK0013031", "OR");

This gives you the ability to add multiple conditions. If you wanted to see all opportunities for a particular account that has a status of open you could add the conditions like this:

// set the lookup results for all opportunities for two different accounts
currentGroupInfo.LookupTempGroup.AddLookupCondition("ACCOUNT:ACCOUNTID", "=", "AGHEA0002669");
currentGroupInfo.LookupTempGroup.AddLookupCondition("OPPORTUNITY:STATUS", "=", "Open", "AND");

So, what are the choices for the operator and conjunction?

Valid operator choices are:

  • ” STARTS WITH “
  • ” LIKE “
  • ” IN “
  • ” IS ” (works great with value ” NULL ” for does not contain data)
  • ” IS NOT ” (works great with value ” NULL ” for does contain data)
  • “=”
  • “>=”
  • “>”
  • “<=”
  • “<“
  • “<>”
  • etc

For the group, this is turning into a SQL query, so basically operators that are valid in SQL should work here (obviously, the “STARTS WITH” here changes to a LIKE with a wildcard after the value)

Valid conjunction/disjunction choices are:

  • AND
  • OR
  • etc

Again, think SQL query for the choices.

If you set the Lookup Result Group on the initial load of a page, the lookup results group will display with the conditions you’ve set. The detail page for the record you navigated to will still display normally, but the lookup results group will be the selected group so the list in the left task pane will show the record matching your conditions. If the page has already loaded and you’re setting the lookup results on a post back, you’ll need to reload the page by redirecting back to itself.

Opening External Websites from Navigation or Menus in Infor CRM (Saleslogix)

$
0
0

It’s an easy task to open an external website from the navigation (left nav area) or menus in Infor CRM (Saleslogix). One thing that is often overlooked it the ability to add Javascript as the URL property for navigation/menu items.

If you want to open an external website, simply add some Javascript like the following to the URL property of the navigation or menu item:

javascript: var win = window.open('http://google.com');

This will open google.com in a new browser window/tab (just like a hyperlink with target=”_blank”) when the navigation or menu item is clicked.

Obviously, you can do more than just open external websites in new browser windows. Since you can add javascript to the URL property of the menu items, you can execute any kind of javascript you’d like.

Opening External Websites from Server-Side C# Code Snippets in Infor CRM (Saleslogix) Web

$
0
0

If you need to open a new browser window or tab to an external website from a C# Code Snippet in Infor CRM (Saleslogix) Web, which is server-side code, you can easily create Javascript and add it to the page so it executes when the post back that fired the C# Snippet completes.

In the C# Snippet, add code like the following:

var script = "var win = window.open('http://google.com');";
ScriptManager.RegisterClientScriptBlock(this, GetType(), "OpenExternalWebsite", script, true);

This registers the javascript to open google.com in a new browser window/tab on the page. Now when the postback completes, the javascript will run and open the website. This sort of thing can especially come in handy when you need to do some server-side code and conditionally open the website based on some conditions.

Sorting a SlxGridView web control using Linq

$
0
0

In a previous post, I described  how you can sort a grid in the 7.5x level of Infor CRM (formerly Saleslogix).  Apparently in 8.1x that method doesn’t work but you can also do the same using Linq.

Lets say I am on the Account page and want to sort the contact grid by CreateDate in descending order.  I can use the following code to do so:

Sage.Entity.Interfaces.IAccount acc = this.BindingSource.Current as Sage.Entity.Interfaces.IAccount; 
var orderedList = acc.Contacts.OrderByDescending(x => x.CreateDate); 
grdContacts.DataSource = orderedList; 

To sort ascending you would just use this instead:

...
var orderedList = acc.Contacts.OrderBy(x => x.CreateDate); 
...

Extending the Infor CRM (formerly Saleslogix) Web Client Email Control for Better Validation

$
0
0

By default the email control in the Infor CRM web client has very basic, I would argue, insufficient validation of the email that a user inputs.  The standard validation simply checks for a string like *@*, where the * can be anything except a < or space.  That leaves a lot of room for bad data.

 

Lets take a look at how to fix this.  We are going to be extending the control.  The nice thing about doing this is that it will automatically take affect for any email control in the web client, regardless of what screen the field exists on.  There are a couple of steps to doing this.  I am going to skip some of the details, but this should give you a good step by step for those who know their way around the Application Architect.

Step 1

Add a new folder within the jscript folder.  This is to contain the javascript files we will use to extend the control.  I would recommend naming it something like your company name.  I will call mine CFX.

Step 2

Inside this folder we need to create a new main.js javascript file. in here we wire up the main initialization of our custom control extension. here is the code for that:

define([
    'CFX/CustomControlsModule',
],
    function (CustomControlsModule) {
    var customControlsModule = new CustomControlsModule();
    customControlsModule.initailize();
});

Make note you need to be consistent with the prefix you are adding in these js files. In mine I am prefixing as CFX.

Step 3

Inside the same folder, add another js file called CustomControlsModule.js. This is where we are actually going to extend the base email control. We need to pass in the Email control. Remember again to consistently prefix your stuff, as I have done with CFX. Here is the code:

define('CFX/CustomControlsModule', [
        'dojo/_base/declare',
        'dojo/ready',
        'dojo/aspect',
        'dojo/_base/lang',
        'Sage/UI/Controls/Email'
],
function (declare,
            ready,
            aspect,
            lang,
            email
        ) {
    var customControlsModule = declare('CFX.CustomControlsModule', null, {
        initailize: function () {
            lang.extend(Sage.UI.Controls.Email, {                
                regExpGen: function () {                                                          
                    var validationString = "^([\\w\\.\\-_']+)?\\w+@[\\w-_]+(\\.\\w+){1,}$";
                    return validationString;
                }
            });
        }
    });
    return customControlsModule;
});

The standard email control has a regExpGen property that we are replacing with our version when the custom extension is initialized. You can see the standard unminified version of the Email control’s js file in Jscript/Sage/UI/Controls/Email.js. you cant modify this directly, hence our extension of it. The property contains a function that returns a Regex expression.
The standard looks like this:

var validationString = "[^< ]+@[^< ]+\\.+.[^< ]*?";

Ours looks like

validationString = "^([\\w\\.\\-_']+)?\\w+@[\\w-_]+(\\.\\w+){1,}$";

One thing to note is that the escape character within the normal regex string needs to be escaped again (i.e.. \\).

Step 4
The last step is to wire up the web client to actually use our new spiffy javascript. We do this by modifying the base.master file. This master file is used by all the pages in the web client and is where all of the javascript files actually get wired up. We need to look for 2 spots to update.

Search for “var dojoConfig = {“. You will see this contains a paths property that will look like:

paths: { 'Sage': '../../../jscript/Sage'},

We need to change this to also add the path to our new folder (in my case CFX):

paths: { 'Sage': '../../../jscript/Sage', 'CFX': '../../../jscript/CFX'},

Lastly we need to invoke our main.js which is the entry point to our custom stuff. Again, search for an area in the code that looks like:

<script type="text/javascript">
        require([
            "dojo/_base/html",

There are a whole bunch of items in this list. At the very end we need to add one more, ours. Type this after the last require statement:

,"CFX/main"

That is all there is to it. Redeploy the web client and take a look. Your email validations should be much more robust now.

Extending the v8.x Infor CRM (formerly Saleslogix) Entity Model to Allow for Extended Entity Audit Logging

$
0
0

One of the great things about the entity model in the Infor CRM web is the ability to easily set up auditing on changes to fields in an entity. All you need to do is set up a history table and then simply check the entity properties you wish to have audited.

One shortcoming that has always existed is the ability to audit extended entities off the main entity. These extended entities represent one-to-one tables. In the Application Architect you can define the table to record History based on the parent table by default. However if you choose to do that and then attempt to make a change in the web client to one of the extended entities property you get a casting error. For instance if you have an extended entity off ITicket called ITicketExt you would get an error when you try to record a history change to a property in the ITicketExt entity saying unable to convert ITicketExt to ITicket. This is because the Ticket history table expects a parent of type ITicket, not ITicketExt. If we look at how a entity is implemented (which is where the auditing is wired up) we can see it contains a method called NewAuditEntry. This is where the casting error happens. Here is that code:

        public override object NewAuditEntry()
        {
            IAuditTable at = (IAuditTable)EntityFactory.Create(typeof(ITicketHistory));                      
            at.SetEntity(this);            
            return at;
        }

We can see that is is attempting to set the audit table objects parent to this. This works great if this id the Ticket but if you are trying to use the extended entity of TicketExt you will get the cast error on this line. It would be sweet if we could just change the line

at.SetEntity(this);

to

at.SetEntity(this.Ticket);

Well lets do it!

These implementations are generated dynamically when you do a web platform build. The implementations use a code template file that generates the actual implementation which is then compiled into the Sage.Saleslogix.Entities.dll. Knowing that a template is in use we can modify the core template to handle extended entities.

WARNING! MODIFY THIS ONLY IF YOU KNOW WHAT YOU ARE DOING. INCORRECT MODIFICATION CAN PREVENT YOU FROM BUILDING YOUR ENTITY MODEL. MAKE A BACKUP FIRST BEFORE MAKING THESE CHANGES SO YOU CAN REVERT TO NORMAL IF YOU BREAK SOMETHING!

Here is step by step run down.

  • Open the Application Architect and the Virtual File System Explorer.
  • In the Model/Entity Model/Code Templates/Entity folder you will see a file “Default-Class-Saleslogix.Class.codetemplate.xml
  • BACK THIS FILE UP
  • Open the file.
  • Search for “NewAuditEntry”
  • You should see a method that looks like this:
        public override object NewAuditEntry()
        {
<#  if (isHistoryTable) { #>
            IHistory h = (IHistory)EntityFactory.Create(typeof(<#= historyTypeName #>));
<#   if (entity.Name == "Address") { #>
            SetAddressHistoryKeys(h);
<#   } else { #>
<#    var aco = metadata.FindACOParent; #>
<#    if (aco != null) { #>
            h.<#= aco.Name #>Id = Id;
<#     if (aco.Name != "Account" && entity.Name != aco.Name) { #>
            h.AccountId = (string)<#= aco.Name #>.Account.Id;
            h.AccountName = <#= aco.Name #>.Account.AccountName;
<#     } #>
<#    } #>
<#   } #>
            return h;
<#  } else { #>
            IAuditTable at = (IAuditTable)EntityFactory.Create(typeof(<#= historyTypeName #>));           
            at.SetEntity(this);
            return at;
<#  } #>
        }

At the end we can see the 3 lines of code shown earlier with the at.SetEntity() line. There are parts of this code enclosed in

<# #>

tags. This is the code which runs during the construct of the web platform. The lines without these tags are what actually gets rendered into the final implementations. Knowing this we can add an if statement checking if this is an extension entity, and if so changing our SetEntity input parameter accordingly, like so:

<#if (entity.IsExtension && IsEntityIncluded(entity.ExtendedEntity)) { #>      
            at.SetEntity(this.<#= entity.ExtendedEntity.Name #>);
<#     } #>
<#   else { #>           
            at.SetEntity(this);
<#     } #>      

The final complete method looks like:

        public override object NewAuditEntry()
        {
<#  if (isHistoryTable) { #>
            IHistory h = (IHistory)EntityFactory.Create(typeof(<#= historyTypeName #>));
<#   if (entity.Name == "Address") { #>
            SetAddressHistoryKeys(h);
<#   } else { #>
<#    var aco = metadata.FindACOParent; #>
<#    if (aco != null) { #>
            h.<#= aco.Name #>Id = Id;
<#     if (aco.Name != "Account" && entity.Name != aco.Name) { #>
            h.AccountId = (string)<#= aco.Name #>.Account.Id;
            h.AccountName = <#= aco.Name #>.Account.AccountName;
<#     } #>
<#    } #>
<#   } #>
            return h;
<#  } else { #>
            IAuditTable at = (IAuditTable)EntityFactory.Create(typeof(<#= historyTypeName #>));           
<#if (entity.IsExtension && IsEntityIncluded(entity.ExtendedEntity)) { #>      
            at.SetEntity(this.<#= entity.ExtendedEntity.Name #>);
<#     } #>
<#   else { #>           
            at.SetEntity(this);
<#     } #>            
            return at;
<#  } #>
        }

After making this change, save your template file. Close the Application Architect and re-open (these templates are chached on opening). Do a build and now the implementation on an extension entity will have its method like this:

        public override object NewAuditEntry()
        {
            IAuditTable at = (IAuditTable)EntityFactory.Create(typeof(ITicketHistory));                      
            at.SetEntity(this.Ticket);            
            return at;
        }

TA-DA!

SpeedSearch Wildcard Searches in Infor CRM (formerly Saleslogix)

$
0
0

One of my favorite tools built into Infor CRM (formerly Saleslogix) is SpeedSearch.  Rather then navigating away from the screen you are on and then doing a lookup to find a record, save yourself some time and clicks by typing part of a word, such as the first few letters of a contact’s name, then an asterisk in the SpeedSearch box.

Here are some special characters that work in the SpeedSearch box:

  • Use a question mark (?) to replace a single unknown character.
  • Use an asterisk (*) to replace an unknown number of characters in a keyword.
  • Use an equal sign (=) to replace a single unknown number.

The SpeedSearch process is quick and the results are ranked according to the number of Hits, just like an Internet search engine.  The results screen has previews and hyperlinks for one-click navigation to the records.


How to change the Welcome Widget in Infor CRM (formerly Saleslogix)

$
0
0

So you stare at the Welcome dashboard widget every time you log into Infor CRM and you get an advertisement for the software you already own.  Would you like to turn this into something useful instead? Read on to see just how easy it is.

Welcome Screen Standard

The Welcome dashboard widget in constructed of code and several code references, however, changing the image and the text can be done using the following steps.

First, let’s replace the Infor CRM screen shot with a new one, such as:

crm_centerbanner0412

To do this, download or create an image file and replace the one that the widget code is calling for.  It is critical that you name the new file exactly the same as the stock one which is shown in this screen shot.CRM_Center Banner

Your new image file must be copied into the directory shown in this screen shot.  Rename or overwrite the stock image file.  The image size should be something similar to the stock one which is 344×170 pixels.  I had to play around with the size to get it to look right, and the extra white space around the image itself is unnecessary.

Welcome screen with new logo

Now we will work on the text.  The Header text “Welcome to Infor CRM” and the Body text are called in a reference statement in the Widget code but we are going to take the easy route instead. Open the Application Architect and select Tools from the top menu and then click on Manage Widgets. The below screen will come up, now select the Welcome Widget at the bottom of the list.

Widget Explorer

Line 21 defines the Header text.  You are going to delete resources.welcomeH2 and replace that with your new Header text.

Here is a screen shot with my new Header text on line 21. The text string must start and end with a single quote.

Welcome widget with new text

Line 22 is the text for the body of the widget and scrolls off to the right but ends with a single quote and comma just like line 21 does.  Keep the comma at the end of both lines as shown. Now click the Save button, then the Release button, select Everyone, and then OK.

Welcome screen with new text

This is what your new Welcome Widget will look like.  How cool is that?  The changes are live and no build or deploy is needed.  The changes will show up right away for your users once they leave the dashboard area, refresh the page, or when they first log in. (I notice a delay in my changes showing up some of the time.)

The Welcome screen is now a useful tool for communicating updates and announcements to your team.

Infor CRM Plugin Type

$
0
0

Plugin Types have not changed but a few have been added since Ryan’s Post in 2005. Here is the most current list.

Infor CRM Plugin Types

0   – Process
1   – Sales Process
2   – Scripts, Basic (Legacy)
3   – Views, Sales (Legacy)
4   – Profiles
5   – Scripts, SQL
6   – System Reports
7   – Templates, SalesLogix
8   – Groups, Sales
9   – Report Profiles
10 – Reports, SalesLogix
11 – SalesLogix WP templates
12 – Templates, Word (2.x, 3.x, 4.x)
13 – Macros
14 – Strips, Menu
15 – Strips, Toolbar
16 – Nav Bars
17 – Reports, IQ
18 – Bitmaps
19 – Reports, Crystal
20 – Context Menus
21 – SupportLogix Reports
22 – Listviews, Support (Legacy)
23 – Groups, Support
24 – Detail Views, Support
25 – Templates, Word (5.x)
26 – XML Database Schema
27 – VBScript
28 – Active Forms
29 – ActiveX Controls
30 – MainViews
31 – Global Scripts (Handlers)
35- Dashboard Widget
36- Dashboard Page

Web Client Lookups with Multiple Conditions in Infor CRM (Saleslogix)

$
0
0

Back in the LAN client, there was a way to add multiple conditions to a lookup control, as long as you knew how to manipulate the Lookup properties to your advantage.

The web client’s lookup control is of course a different animal that now uses an SData feed to perform the lookup based on conditions. There is a pre filter property of the lookup control but you are very limited there in picking single conditions.

There is a way of doing this though by manipulating the lookup control itself. You can do so on a load action of a quick form using a C# snippet. Lets look at how you could add multiple conditions. Again by using the various attributes of the lookup control we can construct a valid SData query that will allow multiple conditions.

Let’s say we are in an Opportunity and want to show a lookup (called lookupProduct) to all Opportunity Products that are active and in the current opportunity, or some other criteria. We can use the following code to accomplish this:

    Sage.Entity.Interfaces.IOpportunity opp = this.GetParentEntity() as Sage.Entity.Interfaces.IOpportunity;
    if (opp != null)
    {
        string prods = string.Empty;       
        foreach(Sage.Entity.Interfaces.IOpportunityProduct op in opp.Products)
        {
            prods += string.Format("\"{0}\",", op.Product.Id);
        }        
        lookupProduct.SeedProperty = "(Status";
        lookupProduct.SeedValue = "Active\" and Id in (" + prods + ")) or \"A\" eq \"B";             
        lookupProduct.OverrideSeedOnSearch = false;
    }

We look through and construct a quote enclosed string of the Opportunity Products in the Opportunity.
Then we set the SeedProperty of the control to “(Status”. We add the ( in front of the property to handle the or clause logic later.
Then we set the SeedValue to the rest of our conditions. We include a quote after our first value and before our last value, because when the SeedProperty and SeedValue are appended together an opening quote and closing quote are added automatically.

With these 2 attributes, when the lookup is rendered and used, a query condition is constructed that looks like:

(Status eq "Active" and Id in ("123","456","etc.")) or "A" eq "B"

Remember this is an SData query so this is the where clause in the SData query language.

Creating a Multi-Select List in the Infor CRM (Saleslogix) Web Client

$
0
0

I’ve posted previously about how you can change the Dijit control type of the ComboBox to give it different functionality or behavior.

See Creating a Searchable & Filterable ComboBox in Infor CRM

In that post, I showed how to change the ComboBox’s data-dojo-type attribute to change it from a normal ComboBox to a filterable & searchable ComboBox. In this post, we’ll be doing the same to change the ComboBox into a multi-select ListBox.

First of all, in this example, we’ll be populating a ComboBox with teams. The code to load the ComboBox will look like this:

// get all teams
var teams = Sage.Platform.EntityFactory.GetRepository<Sage.Entity.Interfaces.ITeam>().FindAll();
// load into combobox
foreach (var team in teams)
{
    comboTeams.Items.Add(new System.Web.UI.WebControls.ListItem(team.SeccodeDesc, team.Id.ToString()));
}

Up to this point, we have a normal ComboBox where the user can select a single team. It looks like this:

team-combo

Just a normal, single select ComboBox. Now, let’s change that to allow multiple selections. First, we need to change the control’s data-dojo-type to a different type of Dijit control called “dijit.form.MultiSelect“. We’ll also need to adjust the height of the control since we’ll be showing multiple items at once and we need more to be visible. Lastly, we’ll need to tell ASP.NET that our ComboBox does in fact allow multiple selections. In the form’s LoadAction (likely where we are already populating the teams into our ComboBox) we’ll add the following code:

comboTeams.Attributes["data-dojo-type"] = "dijit.form.MultiSelect";
comboTeams.Attributes["style"] = "height:200px;";
comboTeams.SelectionMode = System.Web.UI.WebControls.ListSelectionMode.Multiple;

Now what our form looks like is this:

team-combo-multiselect

That’s a nice change for just a small amount of code. The only thing left is how we retrieve the multi-selected items. We can do that by looping through the items like this:

foreach (System.Web.UI.WebControls.ListItem item in comboTeams.Items)
{
    if (item.Selected)
    {
        var teamName = item.Text;
        var teamId = item.Value;

        // now do whatever you need with the team
    }
}

That’s it, you now have a working multi-select list in Infor CRM without the need for resorting to a custom SmartPart.

Adding a Photo from your Phone as an Attachment in Infor CRM (Saleslogix)

$
0
0

I think this is a really neat feature of the Infor CRM (formerly Saleslogix) Mobile client.  We’re all carrying smartphones and tablets with cameras, so why not put it to work when you need to add a photo to an Infor CRM record.

iPhone Attachment screen shot

In the Mobile client, navigate to an Account, Contact, Opportunity or Ticket and then select the Attachments area.  Now click the + sign and this screen pops up. When you touch the “Click or Tap here to add a file” you have several options for either taking a new photo and/or adding an existing file from other sources.  Upload the file after you selected it and the picture is now a new attachment for that record.

Viewing all 169 articles
Browse latest View live