2009/04/30

Coding CRM Lookup Fields

Coding CRM Lookup Fields
A Lookup is a reference or a pointer to another record (GUID) in crm. As opposed to other crm types, lookups are handled differently in many aspects. One “interesting” aspect which I relate to here is how a lookup DataValue is assigned when used in JavaScript.

The lookup DataValue is actually an array which holds a “lookup item” object or objects. Each lookup item exposes a set of properties we need to address in code for crm to acknowledge
its value. In reality an entity with a lookup is comprised of 3 table columns, so for example the primary contact lookup field on the account entity is actually a combination of primarycontactid (guid “{1234…}”) , primarycontactidname (name “Adi Katz”) and primarycontactiddesc columns in the database.

Now, crm only cares about GUIDs so until we start calling our selves by GUID (and not by NAME) we need to bridge that gap. Ms does relate to this gap, but only from the UI / End user perspective (auto complete feature). Unfortunately this ability is not exposed through code in any supported way and we need to build that bridge (automate the process) our selves using custom Ajax calls.

In the simplest scenario the lookup DataValue requires only a GUID (id) and entity name(typename). For example:
view plaincopy to clipboardprint?


1. var LookupItem = new Object();
2. LookupItem.name = “Crm only cares about GUIDs (pls Save...)”;
3. LookupItem.id = “{D04D44AD-36D0-DC11-AA32-0003FF33509E}”;
4. LookupItem.typename = “contact”;
5. crmForm.all..DataValue = [LookupItem];


The problem is that we usually don’t know the entity id (GUID) only its name e.g.
view plaincopy to clipboardprint?


1. var LookupItem = new Object();
2. LookupItem.name = “Adi Katz”;
3. LookupItem.id = “”;
4. LookupItem.typename = “contact”;
5. crmForm.all..DataValue = [LookupItem];


And of course this won’t work unless we lookup “Adi Katz" GUID by name and assign
it to the LookupItem.id.

The following “LookupHelper” class facilitates / automates the process by providing
the following features:

If “Adi Katz” does not exist then a clean lookup dialog is opened.
If “Adi Katz” exists more then once a lookup dialog is opened with the name “Adi Katz” inside the search box.
If one “Adi Katz” exists then the lookup is assigned a valid DataValue

The Lookup Helper works with all lookup types e.g. (single entity, customer, regarding and party lookup).

In all lookups, setting the entity name will set the lookup defaulttype property so when you open the lookup dialog the entity being searched is set in advance.

The OnCrmPageLoad bellow describes the How-to and possibilities you might encounter
in your daily “lookup programming”.
view plaincopy to clipboardprint?
1. var curLookup;
2. function OnCrmPageLoad()
3. {
4. /*
5. - Both GUID (id) and name are known
6. - entity name is taken from the lookup
7. */
8. curLookup = new LookupHelper( "transactioncurrencyid" );
9. curLookup.SetValue( "US Dollar" , "{944038FC-65C1-DC11-B67A-0003FFBB057D}" );
10.
11. /*
12. - Search Mode
13. - Open the lookup dialog window
14. */
15. curLookup = new LookupHelper( "transactioncurrencyid" );
16. curLookup.SetValue("");
17. /*
18. - only GUID (id) is known (usually not the case)
19. - name is a temporary text until you save the record
20. */
21. curLookup = new LookupHelper( "transactioncurrencyid" );
22. curLookup.SetValue( "" , "{944038FC-65C1-DC11-B67A-0003FFBB057D}");
23. /*
24. - only name is known, the id is fetched from crm
25. - must provide PrimaryField of the lookup entity in this case currencyname
26. */
27. curLookup = new LookupHelper( "transactioncurrencyid" , "currencyname" );
28. curLookup.SetValue( "US Dollar" );
29. //OR
30. curLookup = new LookupHelper( "transactioncurrencyid" );
31. curLookup.PrimaryField = "currencyname";
32. curLookup.SetValue( "US Dollar" );
33. /*
34. - Customer Lookup
35. - Change from account to contact entity
36. - the id is fetched form crm
37. */
38. curLookup = new LookupHelper( "customerid" );
39. curLookup.SetEntity( "contact" , "fullname" , "Adi Katz" );
40. //is the same as
41. curLookup = new LookupHelper( "customerid" );
42. curLookup.SetEntity( "contact" );
43. curLookup.PrimaryField = "fullname";
44. curLookup.SetValue( "Adi Katz" );
45. /*
46. - getting the actual lookup field
47. */
48. curLookup = new LookupHelper( "customerid" );
49. alert(curLookup.Lookup.DataValue[0].name); //assuming datavalue exists.
50. }
51.
52. /*
53. Parameters:
54.
55. lookupId - Mandatory, The lookup schema name.
56. primaryField - Optional, Required for fetch. can be set here or later
57. entity - Optional, Taken from the lookup control
58. */
59. function LookupHelper( lookupId , primaryField , entity )
60. {
61. //Lookup Reference
62. this.Lookup = document.getElementById( lookupId );
63. //Checks if the lookupId is valid
64. if(isUndefined(this.Lookup))
65. return alert('lookup ' + lookupId + ' is undefined');
66.
67.
68. /* Private Fields */
69.
70. var Instance = this;
71. var data = this.Lookup.lookuptypenames.split(',')[0].split(':');
72. var Entity = isUndefined(entity)? data[0]:entity;
73. var PK = Entity + "id";
74.
75. /* Public Fields */
76.
77. //Holds the lookup text e.g. Adi Katz
78. this.Text = "Value Selected (Save...)";
79. //Holds the default operator for the fetchxml condition e.g. 'fullname eq "Adi Katz"'
80. this.Operator = "eq";
81. //Holds the lookup entity primary field e.g. contact = fullname , account = name
82. this.PrimaryField = primaryField;
83.
84. /*public Functions
85.
86. Descrition:
87.
88. Sets the Entity and PrimaryKey - Required For Fetch
89. Sets the Primary Field - Required For Fetch
90. Sets the Default Lookup Type - Feature for lookup popup window
91. If the text is supplied then the id is Fetched from crm
92.
93. Parameters:
94.
95. entity - Mandatory, Required For Fetch
96. primaryField - Mandatory (unless supplied in the ctor),Required For Fetch
97. text - Optional
98. */
99.
100. this.SetEntity = function( entity , primaryField , text )
101. {
102. Entity = entity;
103. PK = entity + "id";
104.
105. if(!isUndefined(primaryField))
106. Instance.PrimaryField = primaryField;
107.
108. var regDefType = new RegExp(entity + ":(\\d+)","gi");
109. regDefType.exec(Instance.Lookup.lookuptypenames);
110. Instance.Lookup.defaulttype = RegExp.$1;
111.
112. if(!isUndefined(text))
113. Instance.SetValue(text);
114. }
115.
116. /*
117. Description:
118. Sets The lookup DataValue
119.
120. if text is undefined and guid is supplied then
121. use default text e.g. 'Value Selected (Save...)'
122. else
123. use supplied text
124.
125. if guid is supplied then
126. set the lookup datavalue
127. else if text is undefined then
128. open clean lookup dialog
129. else
130. fetch for lookup item id
131. if 1 record is returned then set datavalue
132. else
133. open lookup dialog with specified search condition
134.
135. Parameters:
136. guid - record id - global unique identifier
137. text - name displayed inside the lookup
138. */
139. this.SetValue = function( text , guid )
140. {
141. if(!isUndefined(text))
142. Instance.Text = text;
143.
144. if(!isUndefined(guid))
145. {
146. var LookupItem = new Object();
147. LookupItem.name = text;
148. LookupItem.id = guid;
149. LookupItem.typename = Entity;
150. Instance.Lookup.DataValue = [LookupItem];
151. }
152. else if(isUndefined(text)) Search( "" );
153. else LookupValue( text );
154. }
155.
156. /* Clear Lookup Value - DataVale = null */
157. this.Clear = function()
158. {
159. Instance.Lookup.DataValue = null;
160. }
161.
162. /* Private Functions
163.
164. builds a valid Fetchxml for the current lookup type
165. fetch value and ( set datavalue or open lookup dialog )
166. */
167. function LookupValue( text )
168. {
169. var xmlHttp = CreateXmlHttp();
170. var xml = "
";
174.
175. xml += GenerateAuthenticationHeader();
176. xml += "
";
177. xml += "";
179. xml += "";
180.
181. var fetchxml = "";
182.
183. fetchxml += "";
184. fetchxml += "";
185. fetchxml += "";
186. fetchxml += "";
189. fetchxml += "
";
190. fetchxml += "
";
191. fetchxml += "
";
192.
193. xml += _HtmlEncode(fetchxml);
194. xml += "
";
195. xml += "
";
196. xml += "
";
197. xml += "
";
198.
199. xmlHttp.open("POST", "/mscrmservices/2007/crmservice.asmx", false );
200. xmlHttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
201. xmlHttp.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/crm/2007/WebServices/Fetch");
202. xmlHttp.send(xml);
203.
204. var resultDoc = loadXmlDocument(xmlHttp.responseXML.text);
205. var resultRecords = resultDoc.selectNodes("//" + PK );
206.
207. if( resultRecords.length == 1 )
208. {
209. var guid = resultRecords[0].text;
210. Instance.SetValue( Instance.Text , guid );
211. }
212. else
213. {
214. Search( Instance.Text );
215. }
216. }
217.
218. /* Fill search
219. condtion and open lookup dialog */
220.
221. function Search( text ){
222. Instance.Lookup.AddParam("search" , text );
223. Instance.Lookup.click();
224. }
225.
226. /* Checks object definition */
227. function isUndefined(obj){
228. return obj == null typeof(obj) == "undefined" obj == "";
229. }
230. }
231.
232. OnCrmPageLoad();