AJAX Script Services on SlxWeb
Posted By: nicocrm on March 12th, 2009 in Uncategorized
No Gravatar

From my user perspective one of the most dreadful aspect of the "QuickForms" is how slow they are.  If you want to associate a piece of custom logic to a control, typically you will have to set it to auto-postback.  At this point whenever the user changes it Saleslogix will have to post the entire form and (since it does not know what may have changed) pull the entire form back over the network and render it in the browser.  This is noticeable even on a local network connection but can easily take 1 to 3 seconds over a WAN, which really breaks the flow of the form.

In a custom smart part there are a couple ways to get around that.  One of them and often the easiest is to put smaller updatepanels around the controls that you know are going to be updated.  It is not the most efficient, and it requires a basic understanding of ASP.NET Ajax (because there are a few workarounds that need to be used to disable the default handling from the top-level SalesLogix container page), but it does not usually affect the page lifecycle so the code-behind does not need to be touched.  This is not the subject here though.

As an alternative to the Microsoft ASP.NET Ajax controls it is perfectly possible to use 100% custom, client-side Javascript.  For example it is easy to perform basic calculations in the page and display the results.  If they are saved to a bound control they can be saved to the database when the form is actually posted.  This is very fast because it is done entirely on the client but it does not let us access the database or any of the business rules defined in C# on the server side.

A middle-ground option is to have the client-side Javascript call a custom web service and display the results in the form controls.  This can be quite a bit faster than using an UpdatePanel, especially if a lot of controls need to be updated, and it also offers greater control over the flow of the code.  Sometimes the UpdatePanel’s behavior can become rather annoying and the custom Javascript actually be easier to use.  This is the method that I want to write about here.

On the client side, traditionally this approach would have required coding of the Javascript XmlHttpRequest and possibly parsing of the resulting XML (unless JSON was used).  However with ASP.NET it is a lot simpler, you can have a Javascript proxy generated automatically by using code like this:

<asp:ScriptManagerProxy runat="server" ID="scriptManagerProxy">
    <Services>
        <asp:ServiceReference Path="~/SmartParts/QuickTicket/InsertQuickTicketScriptService.asmx" />
    </Services>
</asp:ScriptManagerProxy>

By the way the ScriptManagerProxy means that it will use the ScriptManager on the main page, which is created by the Saleslogix framework.  You can’t just add a ScriptManager on your custom smart part as it will conflict with Saleslogix’.  In the Javascript code the methods from the web service will appear as method under an "InsertQuickTicketScriptService" object:

InsertQuickTicketScriptService.GetAccountInfo(accId, function(accInfo){
  alert(accInfo.AccountName);
})

Note they are called in an asynchronous fashion with a callback function.

Assuming the GetAccountInfo method returns an object containing a list of contacts the following code can be used to populate a list of contacts for the selected account:

InsertQuickTicketScriptService.GetAccountInfo(accId, function(accInfo){
        var cbo = $get("<%= cboContacts.ClientID %>");
        cbo.options.length = 0;
        for (var i = 0; i < accInfo.Contacts.length; i++) {
            cbo.options[cbo.options.length] =
                    new Option(accInfo.Contacts[i].ContactName,
                        accInfo.Contacts[i].Id);
        }
        if (cbo.options.length > 0)
            cbo.options[0].selected = true;
})

On the server side things are pretty easy thanks to the flexibility of the Saleslogix Entity service… In this case I used something similar to this:

[WebMethod(EnableSession = true)]
public AccountInfo GetAccountInfo(string accId)
{
    IAccount account = Sage.Platform.EntityFactory.GetById<IAccount>(accId);
    if (account == null)
        throw new InvalidOperationException("Account id not found " + accId);

    return new AccountInfo
    {
        Contacts = (from c in account.Contacts
                    select new ContactInfo
                    {
                        ContactId = (String)c.Id,
                        FullName = c.FullName
                    }).OrderBy(c => c.FullName).ToArray()
    };
}

Don’t forget the EnableSession=true… Saleslogix uses sessions to store the current user’s connection data and by default won’t be able to access it from an asmx.  By the way if you need to access it from an ashx you need to look for the similar option.  AccountInfo and ContactInfo are just locally defined classes, with the minimum properties that need to be passed down to the client script.

If you are writing a custom smart part ASP.NET truly makes it easy to call the web service from your Javascript.


My SalesLogix Web Development Setup
Posted By: nicocrm on March 12th, 2009 in Uncategorized
No Gravatar

I have experimented with a few setups for development on the web client but this is the one that seemed to work best for me the last few times.  Thought I would write it down to get it organized and maybe see how others worked.

Despite what I wrote before about using version control with SlxWeb we do not systematically check the projects into the central repository anymore.  The reason is that SlxWeb doesn’t jive too well with Subversion.  The files for each project are saved to a central, shared drive.  Each project is actually a git branch, but is also available as the full project backup on the shared drives for devs who prefer not to use git (in which case they have to manually copy the files to their hard drive and back – sounds rather annoying to me but that’s their choice).

As for myself I work with a single git repository on my workstation (or rather, on each of the vm’s that I work from).  I start a project by switching to the "virgin" Slx7.5.1 branch, then creating a branch for the new feature/project:

git checkout SLX7.5.1
git checkout -b QuickTicket

This folder now contains the base "Model" project for SLX 7.5.  Typically this would be under ProjectsSlxWeb.git for me.

I then create a web site called "SmartParts" in Visual Studio and save it under a subfolder off the repository folder… for example ProjectsSlxWeb.gitQuickTicketSmartParts.  This will be used to contain my custom smart parts.  I could create them directly under the "SupportFiles" under ModelPortalSlxClientSupportFiles, and this would be a bit more convenient for deployment, the problem is it would make it hard for me to tell where the custom parts are.

Now if needed I will also create the class library project that will contain my custom business rules (this would be ProjectsSlxWeb.gitQuickTicketsQuickTickets.BL) and my unit tests (QuickTickets.BL.UnitTest).  I add all the Saleslogix references to all these projects (I also add a few shared projects to the solution containing some helper methods).

If the project is large enough and is going to contain some custom entities I add the package for them in the Application Architect’s entity model, and add the corresponding assembly to the hibernate.xml file.  It is not perfect isolation of course because we’ll still have to define the relationships from the Saleslogix entities to our custom entities within the Saleslogix stock packages, but it is as good as we can get it with the current system.

At this point I am ready to start work.  If I want to make a new custom smart part I create a folder for the corresponding "Page" (using the same name as in the Saleslogix portal config) in my "SmartParts" web site project, and add all the relevant files to it (ascx, asmx, resx, images, etc).  I have to manually add them as links in the Saleslogix portal config which is a bit tedious but I think the extra isolation is worth this little pain.

Once I am done I need to commit my changes and push the branch onto the main repository:

git add -A .
git commit -m "Whatever"
git push
cd /p/QuickTicket/SlxWeb   (wherever the shared folder is...)
git merge QuickTicket    (here we merge the change onto the shared files)

The last step with the merge is the one that eluded me for a while.  Normally with git the "central" repository (if there is one) would not be used to store files, but in our case we want to make them available for devs who prefer not to use git.  So, the repository on the shared drive is set to track the "master" branch onto which I merge my changes.

I tend to do little work with bundles as I find them tedious to work with – if I need to apply a feature from a project to another I can simply pull and merge the git branch.  But this development setup still makes it possible to create bundles if needed.

That’s about it.  Makes for a rather dull posts, but helps me put things together.