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

Ad-hoc REST services on the force.com platform

Nicolas Galler | December 5, 2010

Note – as of Summer ’11 there is a much better solution in the form of Javascript Remoting. I am leaving the information below for reference but do not recommend using it.


Force.com is coming up with a REST style api to access the data from 3rd party applications, widgets etc. While this is great news as this type of web services is a lot easier to access than traditional SOAP calls from many programming languages (including obviously the important Javascript), it should not be forgotten that it has been possible for a while already to create your own, custom REST-style services – very useful when trying to make a page more responsive by coding certain areas in Javascript with AJAX calls. These don’t benefit from the OAuth authentication so they are more useful for pages that are accessed by already authenticated users. One advantage is that as they are very specialized you can make the payload as lean as necessary. One drawback is that as the facilities of the native Force.com programming environment are a bit rudimentary there is a certain amount of boiler-plate code involved.

The controller side looks like a usual controller, typically using the query-string or form parameters in the constructor to build a result which is then available to the view. The view can loop over that result and output it in JSON format using the built-in JsEncode function. For example here is a simple contact search service:

public with sharing class SearchServiceController {
	/**
	 * Array containing the results.
	 */
	public Contact[] Matches { get; private set; }

        /*
         * grab the parameter from the page's and run the search.
         */
        public void executeSearch() {
		String name = ApexPages.currentPage().getParameters().get('name');
		executeSearch(name);
        }

	/**
	 * Run the search with the specified parameters, populating the Matches variable.
	 */
	public void executeSearch(String name){
		Matches = [select Id, Name, MailingStreet, MailingCity, Mailing State from Contact where LastName=:name];
        }
}

Then the view could look something like this… most modern programming languages have constructs for automatically serializing to JSON nowadays, but not Apex – still, it’s not that hard to get the same result on the view:

<apex:page contentType="text/javascript" controller="ProximitySearchServiceController" action="{!executeSearch}" >
<apex:variable var="rowIndex" value="{!0}"/>
{"result":[
    <apex:repeat value="{!Matches}" var="contact" >
        <apex:outputText rendered="{!rowIndex != 0}" value=","/>
        {"name":"{!JsEncode(contact.Name)}",
         "address1":"{!JsEncode(contact.MailingStreet)}",
         "city":"{!JsEncode(contact.MailingCity)}",
         "state":"{!JsEncode(contact.MailingState)}" }
         <apex:variable var="rowIndex" value="{!rowIndex+1}"/>
    </apex:repeat>
]}
</apex:page>

Not exactly rocket science, but that’s kind of the point: the beauty of REST is that it simply leverages the existing HTTP protocol without the added baggage (both in payload and complexity) of an RPC protocol. Perhaps the only tricky part in the force.com version is the output of the comma in the right position (remember a trailing comma in a list works fine in FireFox but not in IE). There is no out of the box facility for getting the row index in the repeater. Also, don’t forget to set the contentType so that the default page headers don’t get included. And don’t forget to disable the development mode on the page… other, the HTML for the page editor will still get sent and mess with the JSON output!

Lastly a little security note… such services are not wide open because they require the user to be logged into Salesforce. However, it is possible for an attacker who controls a web site that is visited by a Salesforce user to submit a request on their behalf (including their authentication cookies). They won’t be able to retrieve the results, but if the service causes an update or an insert that could happen. If it’s a concern (e.g. for an ordering system) you can check the HTTP referer using ApexPages.currentPage().getHeaders().get(‘Referer’), and make sure it comes from an expected site / page. Some good notes about that on the force.com blog.

Comments
No Comments »
Categories
Force.com
Comments rss Comments rss
Trackback Trackback

(mis)adventures with the Dojo build system

Nicolas Galler | December 5, 2010

Dojo has this amazing tool as part of their framework which is kind of a all-in-one shop for all your Javascript and CSS optimization needs. It’s called the Dojo Build System. It uses the dojo.require statements that are part of the module definition to figure out the combination of Javascript files that will include everything needed, but not more, to minimize both the total size of data transferred and the number of connections necessary to do so.

Overall it works great and there are a few good examples that show how to use it as part of their documentation. I did run into a few pitfalls mostly due to the assumption it makes about the directory layout which I thought I should document here for the benefit of Google and Nick-from-the-future.

  1. Although not mentioned explicitly, if you specify a CSS optimization level, the tool will optimize all CSS files it finds in the folder. I spent a while looking for a switch that would let me specify which CSS files to pick up before realizing that.
  2. A little catch: the output of the Javascript optimization is to be found under the “release/dojo” folder, not “release/yourmodule” as you might expect.
  3. It is quite sensitive to syntax errors and the error messages are really not very good… so make sure the Javascript in the files is all good… especially when you also take into account the fact that the tool takes a while to run because it re-processes the whole Dojo distribution every time (I wish there was a way to do an incremental build but I have not figured out how).
  4. The tool must be run from within the correct folder (util/buildscripts in the dojo source distro). Most of the examples in the documentation have you place your module folder (or create a symlink to it) within the dojo source folder… this is pretty messy and confusing, and makes the upgrade of dojo harder because if you pick up a new release you have to remember to create the appropriate symlinks. There is however an example directory layout that works well and keeps things separated on DojoCampus. They also provide a sample shell script there that makes invoking the tool a lot easier. It is also good to show the various important parameters for the build.
  5. Along the same lines: be careful with symlink, as the tool does a lot of “..” to get to the various files… so if the folder is a symlink the tool might walk itself out of the directory structure.
  6. Even once the Javascript file is flattened, one thing to remember is the localization messages are still loaded separately (the assumption is that the system won’t know what language to load until runtime). If desired (if only one language is needed) it can be concatenated to the output file, and then the locale can be fixed in the djConfig parameter.
  7. Finally, an error that I spent quite a while to debug: if the “releaseDir” contains a “..” in its path, then the build errors out with:
    js: "./jslib/i18nUtil.js#61(eval)", line 1: uncaught JavaScript runtime exception: SyntaxError: missing name after . operator
    
    js: dijit..var.www.nicocrm.test.spx....spxRelease.release.dijit._editor
    

    Oh well, now I know. Apparently there is a similar bug misfeature if you include a backslash on the path name (if you do the build on Windows). I can’t speak for that one for sure as I only tried it on Linux so far.

Once I got past those problems all was good – inline CSS and Javascript, inline templates, all comments stripped, and the Javascript is compressed. You should only need 2 scripts included per page: the “base” dojo.js which is normally included on every page thus will be cached, and the “layer” script which is created based on the modules that are needed on that specific page. For CSS you might have 3 files: the base dojo.css, the theme, and the layer file (I suppose you might combine the theme and base dojo stylesheets, for an even more optimized build). Unfortunately however the tool does not appear to do CSS sprite optimization yet.

Comments
2 Comments »
Categories
Dojo
Comments rss Comments rss
Trackback Trackback

Free Amazon EC2 instance

Nicolas Galler | November 21, 2010

Throwing it out there as I did not know about it – Amazon is offering a free tier for its Elastic Cloud services. It’s available at http://aws.amazon.com/free. Only for the Linux “micro” instance, and only for a year, but it’s still very nice for me (mainly so I can avoid problems like waiting for 2 weeks for a new fan). The one thing that I found was rather dumb is I had to create a new account for it as it was reserved to new users and I had previously used Amazon S3 on my main account (why ever do people make these “new customers only” offers – are they really trying to alienate the existing customer base?). Also be careful not to make the same mistake I made – this pricing only applies to the “basic” Linux install, not the Suse or CentOS builds.

Also not sure when they added the Windows “Micro” instance – not part of this free tier but at $.03/hr that would be very economic way to run a small site if you need a bit more control than with an ASP.NET host (no, I very much doubt you can run SalesLogix on it, hah). It comes off quite a bit cheaper than MS Azure and I am not too sure what Azure buys you for the extra cost. Will be an interesting competition to watch in the coming months.

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

Check Your Relations

Nicolas Galler | October 17, 2010

A familiar problem with upgrades from SalesLogix LAN to Web is many of the foreign keys may have some invalid values.  The legacy LAN client allowed one to put basically anything in there without much validation since we were playing with raw SQL, but the NHibernate platform used in the new client is not as forgiving.  Therefore you may find yourself with this type of errors:

System.Web.HttpUnhandledException: Exception of type ‘System.Web.HttpUnhandledException’ was thrown. —> System.Reflection.TargetInvocationException: Property accessor ‘Ticket’ on object ‘Sage.SalesLogix.Entities.TLXProjectExpense’ threw the following exception:’No row with the given identifier exists[Sage.SalesLogix.Entities.Ticket#            ]‘ —> NHibernate.ObjectNotFoundException: No row with the given identifier exists[Sage.SalesLogix.Entities.Ticket#            ]

What happened here is the original developer chose an empty string instead of a NULL to indicate the absence of a value – it works fine on the LAN client but the web client tries to actually load a ticket with an id of “            “.  The solution recommended so far is to clean out those values as you find them… obviously not very practical in an upgrade where you could have dozens of foreign key relationships to check one by one.

Thankfully all the information we need to get around the problem is already stored by SalesLogix and collected in the entity model – so all we have to do in theory is crawl through the relationships defined in the App Architect and run an update for each one of them.  It is actually quite easy as Sage prepared some neat (albeit undocumented) API to examine the model metadata.  For example this is the code I used to load the project model and iterate through the relationships:

IProject project = ProjectUtility.InitProject(ModelPath);
OrmModel model = project.Models.Get<OrmModel>();
foreach (OrmRelationship relationship in model.Relationships)
{
  // ...
}

It took me a few hours to write the app, mostly because I had set out to learn a few more things about WPF at the same time – the logic for the check and update itself was pretty easy. The one caveat is there are a few relationships that are a bit “odd” and can’t get fixed right (for example the M:1 relationship from targetresponse to MKTGSVC_RECIPIENT is on TargetResponseId which we can’t null out)

For now it is usable as a standalone app… Eventually I would like to make it as an App Architect extension and add a few features (most importantly the ability to drill down to see the actual data for the invalid records)

image

For the source code I made it available as part of the OpenSlx project on GitHub, but the binaries can be downloaded here if you want to give it a try (obviously be sure to backup the DB beforehand etc – the “no warranty” disclaimer is there for a reason!) One big caveat there before I forget – if you have problems with invalid Seccodeid in the Secrights table, etc, you may have to do some cleanup before and/or after running the script as it will null them out which will crash the client.

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

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

« Previous 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