Programming, technology, and CRM – from a Belgian programmer exiled to Missouri
  • rss
  • Home
  • Soft Gallery
    • autosvnbackup.sh
    • VBScript Snippets
  • Contact Me
  • Welcome

Adding keyboard shortcuts in SalesLogix

Nicolas Galler | October 12, 2010

For data entry screens keyboard shortcuts are an incredibly useful way to make the form faster and more user friendly.  Users who bang at the screen all day will learn the shortcuts surprisingly fast and thank you for saving their wrists.  Of course this should be used together with other techniques such as proper keyboard focus management and ensuring there is no auto-postback causing an undue delay.

With the jQuery library included with SalesLogix it is pretty easy to set up, here is an example of how I am using it to trigger certain buttons when Ctrl+N, Ctrl+R etc are pressed:

$(document).keydown(function (e) {
    if (e.which == 'N'.charCodeAt(0) && e.ctrlKey) {
        if ($("#<%# btnNextFax.ClientID %>").is(":visible"))
        // this check is to prevent them from slamming repeatedly on Ctrl+N
            $("#<%# btnNextFax.ClientID %>").click();
        return false;
    }
    if (e.which == 'R'.charCodeAt(0) && e.ctrlKey) {
        if ($("#<%# btnRefresh.ClientID %>").is(":visible"))
            $("#<%# btnRefresh.ClientID %>").click();
        return false;
    }
    if (e.which == 'D'.charCodeAt(0) && e.ctrlKey && $("#<%# btnDeleteSelected.ClientID %>").is(":visible")) {
        // short delay so that the handler is over by the time the prompt shows up
        setTimeout(function () { $("#<%# btnDeleteSelected.ClientID %>").click(); }, 100);
        return false;
    }
    if (e.which == 'A'.charCodeAt(0) && e.ctrlKey && $("#<%# btnSelectAll.ClientID %>").is(":visible")) {
        $("#<%# btnSelectAll.ClientID %>").click();
        return false;
    }
});

It’s important to remember to only register the handler once… and if you register in a dialog smartpart you need to make sure to undo it (otherwise it will try to trigger the button again even once the dialog is hidden)!  This can be done calling $(document).unbind(‘keydown’, myfunction)  (in that case you have to make sure you keep a reference to your handler). 

Comments
No Comments »
Categories
Javascript, Saleslogix
Comments rss Comments rss
Trackback Trackback

Using DevExpress controls on a SalesLogix tab

Nicolas Galler | October 7, 2010

For the past 2 year or so we have been using a 3rd party control library provided by DevExpress.  I have come to really, really like them – they allow for some really cool effects with minimal work, usually have fairly decent performance, are easy to work with and not too buggy.

For example this is the DevExpress date picker:

image

I think it’s better looking and easier to use than the SalesLogix one.  It also does not suffer from the bug that the stock SalesLogix DateTimePicker control has with dates that are stored in local time.

This is a neat cascading grid effect:

image

It also support grouping, exporting to Excel, customization of the layout by end user, all with very little effort.

If you use the controls on a mainview, or in a frame, then it is very straightforward – just drop them on the page.

If you use the controls in a tab or a dialog there is a little trick to consider – used like that, the controls will not be able to automatically load the supporting javascript, so you may get some errors.  It can also happen if the control is initially hidden on your view and shown conditionally on a postback.

To get around it there are 3 options:

  • If you have control on the mainview of the page, you can add a call to this page to load the DevExpress scripts.  Since that will be called on the initial load it will be able to load the scripts at that time and they will remain available when the DevExpress control finally appears, on the dialog or whatnot.  The scripts will be cached after the first time so the performance hit should be minimal.
  • Otherwise, you can use a module to accomplish the same effect.
  • Finally you can load the control in an Iframe tag.  This also offers the advantage of better performance for “callback” operation (expanding the grid rows, for example), but requires a bit of effort to make sure the frame is sized correctly.

For the second approach, this is the code I used in the module:

 

public class DevExpressScriptLoader : IModule
{
    /// <summary>
    /// Have the browser load the DevExpress scripts automatically.
    /// Otherwise, if they are on a tab, and the tab is not initially loaded, the DevExpress contols will fail.
    /// They will be cached anyway so not a big performance hit.
    /// </summary>
    private void LoadDevExpressScripts()
    {
        if (HttpContext.Current != null &&
            HttpContext.Current.Handler is Page)
        {
            DevExpress.Web.ASPxClasses.ASPxWebControl.RegisterBaseScript((Page)HttpContext.Current.Handler);
        }
    }

    public void Load()
    {
        LoadDevExpressScripts();
    }
}

The main part is the call to “ASPxWebControl.RegisterBaseScript”, and if you choose to go with the first approach it is the only line needed.

Building it as a module lets you include it easily for all pages of the site – under the Portal / Modules section of the Application Architect.

For a framed approach I found the following function useful – it provides a “sizeParent” function which can then be called from the contained frame:

_frame.Attributes["onload"] = "var frm = document.getElementById('" + _frame.ClientID + "');frm.contentWindow._sizeParent = function(h) { $('#" + _frame.ClientID + "').height(h); };";            
Comments
No Comments »
Categories
Programming, Saleslogix
Comments rss Comments rss
Trackback Trackback

Change lookup seedvalue at runtime

Nicolas Galler | October 5, 2010

You can change the “SeedValue” of a lookup (used for additional query filter) by simply assigning it from the code behind, during a postback or during the initial page load.

If you want to assign it from client script, you need a few extra steps. First of all, the current seed value can be set simply by assigning it:


luProj._currentSeedValue = newAccId;
luProj._seedValue = newAccId;

I have no idea why there are 2 values. It looks like just setting “currentSeedValue” works right now but I just set them both to be safe.

This works fine if there is no initial seed value on the lookup. However, if the value was set from the code behind, it will actually get hard-coded in the lookup object. The most effective way I have found to get rid of it is overriding the “show” method to remove the optional parameter it accepts:


if(!luProj._oldshow){
luProj._oldshow = luProj.show;
luProj.show = function() { return this._oldshow(); }
}

Don’t forget that if there is a postback, the value you set from the client side will be lost. So either register your script to get called again, or add logic on the server to add the seedvalue on the postback (as usual, the motivation for not having the server do it in the first place is to avoid the postback delay, loss of keyboard focus, etc)

Comments
No Comments »
Categories
Saleslogix
Comments rss Comments rss
Trackback Trackback

SalesLogix Bundle Sort

Nicolas Galler | August 15, 2010

Published a little utility for sorting items within a SalesLogix bundle (don’t you hate it when the Architect decides to “shuffle” the items you took 20 minutes to arrange?). I don’t work that much with the SalesLogix LAN client anymore but this was irritating enough that I had to do something about it.

It is on the MSDN code gallery.

Comments
No Comments »
Categories
Saleslogix
Comments rss Comments rss
Trackback Trackback

Microsoft Visual Studio is waiting for an operation to complete

Nicolas Galler | August 7, 2010

This is not an uncommon message… but I hope this could help someone who is confronted with the same issue I was.

I found I was getting the error every time I started Visual Studio in the morning, with Visual Studio hanging for a looong time… but not in the afternoon… crazy uh? The difference is in the morning I connect to my workstation via remote desktop… and usually in the afternoon I am physically at the keyboard. It turns out the delay was caused by the remote desktop clipboard helper… an application called rdpclip.exe. Killing the application solved the problem (though it does prevent pasting via remote desktop, obviously, but I can live without that)

Comments
5 Comments »
Categories
Uncategorized
Comments rss Comments rss
Trackback Trackback

SLX Generic “Disable Form” function

Nicolas Galler | April 14, 2010

This is a generic function to enable / disable all controls on a form from server-side, except for buttons. Call as LockForm(this, true) or LockForm(this, false). Watch out as values will be persisted between postbacks (e.g. when browsing through SalesLogix entities). Also, it won’t go through a main view tab.

/// <summary>
/// Enable / disable all controls on the form
/// </summary>
/// <param name="islocked"></param>
private void LockForm(Control parent, bool islocked)
{
    foreach (Control c in parent.Controls)
    {
        if (c is IButtonControl || c is SmartPartToolsContainer)
            continue;
        if (c.Controls.Count > 0)
            LockForm(c, islocked);

        PropertyInfo pr = c.GetType().GetProperty("ReadOnly");
        if (pr != null)
        {
            pr.SetValue(c, islocked, null);
        }
        else
        {
            pr = c.GetType().GetProperty("Enabled");
            if (pr != null)
            {
                pr.SetValue(c, !islocked, null);
            }
        }
    }
}
Comments
No Comments »
Categories
Uncategorized
Comments rss Comments rss
Trackback Trackback

Sorted SLX Lookup

Nicolas Galler | April 8, 2010

One little “nicety” of the lookups in the web client is we no longer have to add a fake condition to force them to populate when they are open – one can simply add an “InitializeLookup=true” parameter to obtain that effect.

Unfortunately there is a serious usability drawback in the current version – the records are not sorted by default. So if there are more than a handful of them the user still has to click on the search button to get them to sort, which defeats the purpose of getting the lookup to pre-populate.

You can fix the problem in several ways. The first is to add a little piece of javascript in your code behind to force the sort… this is rather unobtrusive and easy to test so if you are only fixing one form it’s probably the way to go:

ScriptManager.RegisterStartupScript(this, GetType(), Guid.NewGuid().ToString(),
            "$(document).ready(function() {" +
            lueLinkOpp.ClientID + @"_luobj.initGrid = function (seedValue, reload) {
                LookupControl.prototype.initGrid.apply(this, [seedValue, reload]);
                this.getGrid().getNativeGrid().getStore().setDefaultSort('CreateDate');
            }
            });
            ", true);

It looks pretty yucky, but hey, it works.

If there are more than a handful of lookups, or if you want the changes to apply also on QuickForms, you’ll probably want to package them into a user control, something like this:

public class FixSlxLookup : LookupControl
{
    /// <summary>
    /// Add the sorting hack.
    /// </summary>
    /// <param name="e"></param>
    protected override void OnPreRender(EventArgs e)
    {
        base.OnPreRender(e);
        if (InitializeLookup && LookupProperties.Count > 0)
        {
            ScriptManager.RegisterStartupScript(this, GetType(), Guid.NewGuid().ToString(),
                @"$(document).ready(function() { setTimeout(function() {
                    " + this.ClientID + @"_luobj.initGrid = function(seedValue, reload) {
                        LookupControl.prototype.initGrid.apply(this, [seedValue, reload]);
                        this.getGrid().getNativeGrid().getStore().setDefaultSort('" + this.LookupProperties[0].PropertyName + @"');
                    };
                }, 500) });", true);
        }
    }
}

Once you place the control in your own assembly, one tiny problem remains – by default the lookup control will try to locate the image for the button in the containing assembly. So you need to adjust that in order to make it look in the original SalesLogix assembly:

/// <summary>
/// Initialize the lookup image if necessary
/// </summary>
/// <param name="e"></param>
protected override void OnInit(EventArgs e)
{
    base.OnInit(e);
    // Fix the image - by default the SLX controls tries to load it from the current type's assembly.
    // since this is a subclass of LookupControl it is not in the "correct" assembly anymore.
    // this fix ensures that the image is loaded from the original assembly
    if (this.ViewState["LookupImageURL"] == null)
        LookupImageURL = this.Page.ClientScript.GetWebResourceUrl(typeof(LookupControl), "Sage.SalesLogix.Web.Controls.Resources.Find_16x16.gif");
}

If you want to have it used in QuickForms you’ll have to adjust the QuickForm template – there is an example of how to do that in a previous article.

The fixed lookup is available as part of OpenSlx, but it just has the 2 above functions so you can simply rip those instead.

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

OpenSlx Library

Nicolas Galler | March 22, 2010

Just wanted to do a quick post to introduce the OpenSlx library I mentioned in the previous post about SimpleLookup. The library is available from GitHub at http://github.com/nicocrm/OpenSlx. It is free to use in your own customizations, or you can rip code out of it and put it into your own stuff, though as it is placed under the GPL license there are a few limitations:

  • you can take the OpenSlx library, modify it, and redistribute it as part of a customization, without having to publish it (though you would have to make the source code available to the customer, at their request, which is the usual business model anyway). The same apply to ripping out code – you can rip the code and put it into your own customizations, without having to publish those (other than to the user of the code, i.e. the customer – this does not include the actual end users, either SalesLogix users, or their own customers accessing SalesLogix via customer portal)
  • you can take the OpenSlx library, unmodified, and include it as part of a proprietary component – you would have to make available any modification you make to OpenSlx, or any derived work (e.g. if you were to create a subclass), but other parts of your package would not be affected
  • you can’t take the OpenSlx library, add some controls to it (or fix some of the existing ones), and resell it as a proprietary component – this is the kind of change that I hope would be redistributed
  • you can’t rip some code from the OpenSlx library, and include it into your own, proprietary component which you resell as a software package – you have to leave the code separated. However you can rip some code, and include it into your own open source components – they will have to be released under the GPL as well, though

2010-06-27: I changed the license to Apache, so most of the restrictions above do not hold anymore, even though I still hope that you would contribute improvements back to OpenSlx. Without getting into details, this was the only practical way for me to be able to keep contributing to the code.

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

Simple Lookup – for Slx Web

Nicolas Galler | March 15, 2010

This is going to be another quickie because I am in a hurry!

One of the most boring tasks when creating custom smart parts is setting up the lookups. There is next to no help from intellisense, so it takes a while to get right in the first place, but worse than that, if the user wants to add a column to a lookup you have to go through ALL the forms that have that lookup and add it by hand (this is actually a problem with QuickForms too, not just custom smart part). What a huge usability setback from the LAN client where they can just design their lookups in the Administrator.

In order to (partly) remedy that I created a component called “SimpleLookup” that runs through the Lookup table meta-data and populates the LookupProperties collection accordingly. So basically you create the lookup as:

<OpenSlx:SimpleLookup LookupName="ACCOUNT:ACCOUNT"
   LookupEntityName="Account" ID="lueAssignDistributor"
   LookupBindingMode="String" AutoPostBack="true"
   LookupEntityTypeName="Sage.SalesLogix.Entities.Account, Sage.SalesLogix.Entities"
   runat="server" />

The LookupName part is the important part – you can either put the Lookup Name (as defined in the lookup manager) or the search field (similar to how we used to reference lookups in the LAN client). Now when you want to add a column you can add it in the familiar lookup manager (though it is cached for efficiency so it may still not show up for a few minutes or until the application pool is recycled).

This SimpleLookup is part of a growing collection of components that I am able to make available as open source – it is available on GitHub at http://github.com/nicocrm/OpenSlx, along with a few other pieces like the SimplePicklist I blogged about before. Note that it won’t handle the more exotic lookup definitions (in particular any link used in the lookup must also be defined as a relationship in the Application Architect) so be sure to test the particular configuration.

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

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) { e.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) { e.IsValid = !!$('#frmAccCredit input[id$=luePurchContact_LookupResult]').val(); })" />
Comments
No Comments »
Categories
Uncategorized
Comments rss Comments rss
Trackback Trackback

« Previous Entries Next Entries »

Categories

  • Dojo (1)
  • Experiments (4)
  • Force.com (2)
  • Interesting (1)
  • Javascript (3)
  • MSCRM (1)
  • Programming (63)
  • Rant (3)
  • Saleslogix (41)
  • Tricks (8)
  • Uncategorized (32)

Post History

  • 2011
    • January (3)
    • February (2)
    • March (1)
  • 2010
    • January (3)
    • March (3)
    • April (2)
    • August (2)
    • October (4)
    • November (1)
    • December (2)
  • 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