Getting server side stuff client side (for Sage CRM v7.1sp2 and earlier)

Hints, Tips and Tricks

Technical Hints Tips and Tricks that cover customization and development using Sage CRM. API usage and coding are covered.

Getting server side stuff client side (for Sage CRM v7.1sp2 and earlier)

  • Comments 3
  • Likes

Note:  This article discusses techniques relevant for Sage CRM v7.1sp2 and earlier.  If you are using Sage CRM 7.2 please refer to the articles that discuss the new Client Side API.

In this post I will be exploring the possibilities of combining the CRM.AddContent method and some javascript data transmission techniques. 

Previously I've blogged about using AJAX to access server-side objects in client-side scripts.  AJAX is great for sending information back and forth from the client at times when you don't want to reload the whole page, but if all you want to do is get some reference information to the client then AJAX is not the correct weapon of choice. 

Using CRM.AddContent in a Create Script

When a screen is being generated the Create Script for each field is executed server-side.  This gives us an opportunity to capture information usually only available to server-side objects and bring them with us into the client-side world.

We can do this by utilising the CRM object's AddContent method.  Any string we pass to AddContent will be included in the HTML when the page is rendered.  Note this is an especially useful trick for getting client-side code into a workflow screen. 

So to pass something that's only available server-side, for example say the current user's default e-mail address setting, you can do this with the following in a Create Script:

CRM.AddContent('<' + 'script>var defaultemailaddress=\'' + CRM.UserOption('defaultemailaddress') + '\'<' + '/script>');

Note: In 6.2 d and below there is an ugly bug that causes the UI to break if you have HTML tags in your scripts (this has been logged as fault reference 0-98333).  To work around this problem break up the tags like I have done above.  If you forget and break CRM see KB article 416-13469 for steps to fix it.

So aside from a troublesome bug this is pretty simple stuff.  If you want to send more complicated data you may want to learn about JSON.  JSON, short for Javascript Object Notation, is a lightweight data interchange format.  What's great about JSON is it's very easy to construct and consume.  With very little code on either end you can send complex data structures.

Getting Translations Client-side

I recently had a situation where I needed to populate a CRM date field with a calculated date based on fields the user was populating.  It was important to do this client side to give the user an opportunity to verify or change the calculated date before it was submitted.  I wrote a couple of functions for converting a javascript date object into the CRM date format based on the current user's date and time preferences.  There was just one small problem, this customisation had to work in a number of locales.  In the US, UK and France AM and PM translate as AM and PM but in Germany it is Vormittag(s) and Nachmittag(s).  I could have hard coded these values into my script but that would have been messy.  Instead I wrote this code for passing translations to a client side script from a Create Script:

 

 



  // converts a javascript string into a javascript string literal sequence

  function StringToLiteral(str) { if (!StringToLiteral.allowedChars) StringToLiteral.allowedChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ _!"£$%^&*()/[]{}#,.@:;';

  if (!StringToLiteral.escapeChars) StringToLiteral.escapeChars = '\\\n\t\O\r\v\'';

  if (!StringToLiteral.escapeCodes) StringToLiteral.escapeCodes = ['\\\\', '\\n', '\\t', '\\O', '\\r', '\\v', '\\\''];

  var result = '',

  ch;

  for (var i = 0; i < str.length; i++) {

  ch = str.charAt(i);

  if (StringToLiteral.allowedChars.indexOf(ch) == -1) {

  var code = StringToLiteral.escapeChars.indexOf(ch);

  if (code != -1) result += StringToLiteral.escapeCodes[code];

  else result += '\\u' + ('000' + str.charCodeAt(i).toString(16)).slice(-4);

  } else result += ch;

  }

  return '\'' + result + '\'';

  }var Translations = new function () {

  var _MakeSafe = function (str) {

  if (str != escape(str).replace(/%20/g, ' ')) return StringToLiteral(str);

  return '\'' + str + '\'';

  }

  this.cache = {};

  this.Add = function (capt_family, capt_code) {

  var codes = [];

  if (!capt_code) {

  var captRec = CRM.CreateQueryObj('SELECT capt_code FROM custom_captions WHERE capt_family = \'' + capt_family + '\'');

  captRec.SelectSql();

  while (!captRec.eof) {

  codes.push(captRec('capt_code'));

  captRec.NextRecord();

  }

  } else codes.push(capt_code);

  if (!this.cache[capt_family]) this.cache[capt_family] = {};

  for (var i in codes)

  this.cache[capt_family][codes[i]] = CRM.GetTrans(capt_family, codes[i])

  }

  this.PushToClient = function () {

  var families = [];

  for (var family in this.cache) {

  var codes = [];

  for (var code in this.cache[family])

  codes.push('\'' + code.toLowerCase() + '\':' + _MakeSafe(this.cache[family][code]));

  families.push('\'' + family.toLowerCase() + '\':{' + codes.join(',') + '}');

  }

  CRM.AddContent('<' + 'script>Translations={' + families.join(',') + '};<' + '/script>');

  }

  }

 

 

To use this code you need to first load the Translations object with the translations you want.  This is done by calling the Translations.Add method.  The first parameter is the family and the second optional parameter is the code.

So if you wanted to get the entire AmPm family you would use:

Translations.Add('AmPm');

Or if you want just a specific code in the family you could use:

Translations.Add('Blocks', 'InProgressCasesinMyTeamDesc');

Next send the Translations object to the client with:

Translations.PushToClient();

This will put a script like the following in the page:

<script>Translations={'ampm':{'am':'AM','pm':'PM'},'blocks':{'inprogresscasesinmyteamdesc':'InProgressCasesinMyTeamDesc'}};</script> 

Now client side you can access the translations using this syntax:

//Syntax Translations['FamilyName', 'CodeName']
Translations['ampm']['am'] // Returns Vormittag(s) for German users!

Note that the family and code names must be in lowercase.

Getting CreateQueryObj Results Client-Side

Here is a function that allows you to send the results of any SELECT query to the client:

 

 



// converts a javascript string into a javascript string literal sequence

function StringToLiteral(str) {

 if (!StringToLiteral.allowedChars) StringToLiteral.allowedChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ _!"£$%^&*()/[]{}#,.@:;';

 if (!StringToLiteral.escapeChars) StringToLiteral.escapeChars = '\\\n\t\O\r\v\'';

 if (!StringToLiteral.escapeCodes) StringToLiteral.escapeCodes = ['\\\\', '\\n', '\\t', '\\O', '\\r', '\\v', '\\\''];

 var result = '',

 ch;

 for (var i = 0; i < str.length; i++) {

 ch = str.charAt(i);

 if (StringToLiteral.allowedChars.indexOf(ch) == -1) {

 var code = StringToLiteral.escapeChars.indexOf(ch);

 if (code != -1) result += StringToLiteral.escapeCodes[code];

 else result += '\\u' + ('000' + str.charCodeAt(i).toString(16)).slice(-4);

 } else result += ch;

 }

 return '\'' + result + '\'';

}

function JSLiteralFromQuery(query) {

 var cache = {}, fields = [],

 value, escaped;

 // make an array with all the field names in it.

 for (f = new Enumerator(query); !f.atEnd(); f.moveNext()) fields.push(new String(f.item()));

 cache = new Array(fields.length);

 for (i in fields) cache[i] = [];

 while (!query.eof) {

 for (i in fields) {

 value = query(fields[i]);

 if (value == null) {

 cache[i].push('null');

 } else if ((typeof value).toString() == 'date') {

 value = new Date(value);

 value = value.getUTCFullYear() + ',' + value.getUTCMonth() + ',' + value.getUTCDate() + ',' + value.getUTCHours() + ',' + value.getUTCMinutes() + ',' + value.getUTCSeconds() + ',' + value.getUTCMilliseconds();

 if (value == '1899,11,30,0,0,0,0') cache[i].push('null')

 else cache[i].push('new Date(Date.UTC(' + value + '))');

 } else {

 value = new String(value);

 if (value != escape(value).replace(/%20/g, ' ')) value = StringToLiteral(value);

 else value = '\'' + value + '\'';

 cache[i].push(value);

 }

 }

 query.NextRecord();

 }

 var fieldAry = [];

 for (i in fields) fieldAry.push(StringToLiteral(fields[i].toLowerCase()) + ':[(' + cache[i].join(',') + ']');

 return '{' + fieldAry.join(',') + '}';

}

The JSLiteralFromQuery function returns a string that contains a javascript literal representation of the result set. To use it you need to pass in an IeWareQuery object.


var query = CRM.CreateQueryObj('SELECT curr_currencyid, curr_symbol, curr_rate FROM Currency');

query.SelectSql();

var clientScript = '<' + 'script>';

clientScript += 'Currency=' + JSLiteralFromQuery(query) + ';';

clientScript += '<' + '/script>';

CRM.AddContent(clientScript);

 

This will put a script like the following in the page:

<script>Currency={'curr_currencyid':['1','2','3','4'],'curr_symbol':['$','\u20ac','£','\u00a5'],'curr_rate':['0.9865','1','0.6309','121.94']};</script>

Now from the client side we can access the data using this syntax:

 




// Syntax: Currency['Field Name'][0 Based row number]


Currency['curr_symbol'][2]; // Returns the curr_symbol from the third record

 

Remember, don't send too large a data set and avoid using SELECT * queries.  Only select the data you absolutely need to avoid sending junk to the client and slowing down the customer experience.

As always the possibilities are endless! 

I hope you find these techniques useful and that they give you ideas about even more ways to customise CRM. 

PS - I'll share my CRM javascript date functions with you in a future blog.

Comments
  • When i copy paste the code from between "Getting CreateQueryObj Results Client-Side

    Here is a function that allows you to send the results of any SELECT query to the client:" to "The JSLiteralFromQuery function returns a string that contains a javascript literal representation of the result set.  To use it you need to pass in an IeWareQuery object." and the code from between "The JSLiteralFromQuery function returns a string that contains a javascript literal representation of the result set.  To use it you need to pass in an IeWareQuery object." to "This will put a script like the following in the page:" i get a jscript error tells me that in line 3 char 25 the object doesn't support that method or property... what could it be :-(?!

  • Ok it works now... thank you.

  • I added a new field to quotes screen "Currency Rate" and i would like to fetch and populate the related currency rate from currency table when a currency is chosen or changed. I appreciate your assistance.