Skip to content

Commit 9bb8aee

Browse files
Simplified access to formatted and lookup data values
1 parent 336ae91 commit 9bb8aee

File tree

8 files changed

+175
-43
lines changed

8 files changed

+175
-43
lines changed

DynamicsWebApi.njsproj

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
<EnableTypeScript>true</EnableTypeScript>
99
<TypeScriptSourceMap>true</TypeScriptSourceMap>
1010
<TypeScriptModuleKind>CommonJS</TypeScriptModuleKind>
11+
<TypeScriptToolsVersion>2.3</TypeScriptToolsVersion>
12+
<NodeExePath>
13+
</NodeExePath>
1114
</PropertyGroup>
1215
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
1316
<PropertyGroup>
@@ -23,12 +26,25 @@
2326
<OutputPath>.</OutputPath>
2427
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
2528
<ProjectTypeGuids>{3AF33F2E-1136-4D97-BBB7-1795711AC8B8};{349c5851-65df-11da-9384-00065b846f21};{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}</ProjectTypeGuids>
26-
<ProjectView>ShowAllFiles</ProjectView>
29+
<ProjectView>ProjectFiles</ProjectView>
2730
<NodejsPort>1337</NodejsPort>
28-
<StartWebBrowser>true</StartWebBrowser>
31+
<StartWebBrowser>True</StartWebBrowser>
2932
</PropertyGroup>
3033
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
3134
<DebugSymbols>true</DebugSymbols>
35+
<TypeScriptTarget>ES6</TypeScriptTarget>
36+
<TypeScriptJSXEmit>None</TypeScriptJSXEmit>
37+
<TypeScriptModuleKind>CommonJS</TypeScriptModuleKind>
38+
<TypeScriptCompileOnSaveEnabled>True</TypeScriptCompileOnSaveEnabled>
39+
<TypeScriptNoImplicitAny>False</TypeScriptNoImplicitAny>
40+
<TypeScriptRemoveComments>False</TypeScriptRemoveComments>
41+
<TypeScriptOutFile />
42+
<TypeScriptOutDir />
43+
<TypeScriptGeneratesDeclarations>False</TypeScriptGeneratesDeclarations>
44+
<TypeScriptNoEmitOnError>True</TypeScriptNoEmitOnError>
45+
<TypeScriptSourceMap>True</TypeScriptSourceMap>
46+
<TypeScriptMapRoot />
47+
<TypeScriptSourceRoot />
3248
</PropertyGroup>
3349
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
3450
<DebugSymbols>true</DebugSymbols>

README.md

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ Any suggestions are welcome!
3737
* [Fetch All records](#fetch-all-records)
3838
* [Execute Web API functions](#execute-web-api-functions)
3939
* [Execute Web API actions](#execute-web-api-actions)
40+
* [Formatted Values and Lookup Properties](#formatted-values-and-lookup-properties)
4041
* [JavaScript Promises](#javascript-promises)
4142
* [JavaScript Callbacks](#javascript-callbacks)
4243

@@ -862,17 +863,54 @@ dynamicsWebApi.executeUnboundAction("WinOpportunity", actionRequest).then(functi
862863
});
863864
```
864865

866+
## Formatted Values and Lookup Properties
867+
868+
Starting from version 1.3.0 it became easier to access formatted values for properties and lookup data in response objects.
869+
DynamicsWebApi automatically creates aliases for each property that contains a formatted value or lookup data.
870+
For example:
871+
872+
```js
873+
//before v.1.3.0 a formatted value for account.donotpostalmail field could be accessed as following:
874+
var doNotPostEmailFormatted = response['[email protected]'];
875+
876+
//starting with v.1.3.0 it can be simplified
877+
doNotPostEmailFormatted = response.donotpostalmail_Formatted;
878+
879+
//same for lookup data
880+
//before v.1.3.0
881+
var customerName = response['[email protected]'];
882+
var customerEntityLogicalName = response['[email protected]'];
883+
var customerNavigationProperty = response['_customerid_value@Microsoft.Dynamics.CRM.associatednavigationproperty'];
884+
885+
//starting with v.1.3.0
886+
customerName = response._customerid_value_Formatted;
887+
customerEntityLogicalName = response._customerid_value_LogicalName;
888+
customerNavigationProperty = response._customerid_value_NavigationProperty;
889+
```
890+
891+
If you still want to use old properties you can do so, they are not removed from the response, so it does not break your existing functionality.
892+
893+
As you have already noticed formatted and lookup data values are accesed by adding a particular suffix to a property name,
894+
the following table summarizes it.
895+
896+
OData Annotation | Property Suffix
897+
------------ | -------------
898+
`@OData.Community.Display.V1.FormattedValue` | `_Formatted`
899+
`@Microsoft.Dynamics.CRM.lookuplogicalname` | `_LogicalName`
900+
`@Microsoft.Dynamics.CRM.associatednavigationproperty` | `_NavigationProperty`
901+
865902
### In Progress
866903

867904
- [X] Overloaded functions with rich request options for all Web API operations.
868-
- [X] Get all pages requests, such as: countAll, retrieveMultipleAll, fetchXmlAll and etc. Implemented in v.1.2.5.
905+
- [X] Get all pages requests, such as: countAll, retrieveMultipleAll, fetchXmlAll and etc. `Implemented in v.1.2.5`
869906
- [X] Web API requests that have long URL (more than 2000 characters) should be automatically converted to batch requests.
870-
Feature is very convenient for big Fetch XMLs. Implemented in v.1.2.8.
871-
- [ ] Web API Authentication for On-Premise instances.
872-
- [ ] Intellisense for request objects.
873-
- [ ] "Formatted" values in responses. For instance: Web API splits information about lookup fields into separate properties,
907+
Feature is very convenient for big Fetch XMLs. `Implemented in v.1.2.8`
908+
- [X] "Formatted" values in responses. For instance: Web API splits information about lookup fields into separate properties,
874909
the config option "formatted" will enable developers to retrieve all information about such fields in a single requests and access it through DynamicsWebApi custom response objects.
910+
- [X] Simplified names for "Formatted" properties. `Implemeted in v.1.3.0`
875911
- [ ] Batch requests.
912+
- [ ] Web API Authentication for On-Premise instances.
913+
- [ ] Intellisense for request objects.
876914
- [ ] Use entity names instead of collection names. I have not done an investigation about it but if you, by any chance, know how to do that,
877915
I will be very grateful for an advice! Quick guess, does it work like in English language?
878916

lib/dynamics-web-api-callbacks.js

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -697,16 +697,8 @@ function DynamicsWebApi(config) {
697697
var toCount = request.count;
698698

699699
var onSuccess = function (response) {
700-
if (response.data['@odata.nextLink'] != null) {
701-
response.data.oDataNextLink = response.data['@odata.nextLink'];
702-
}
703700
if (toCount) {
704-
response.data.oDataCount = response.data['@odata.count'] != null
705-
? parseInt(response.data['@odata.count'])
706-
: 0;
707-
}
708-
if (response.data['@odata.context'] != null) {
709-
response.data.oDataContext = response.data['@odata.context'];
701+
response.data.oDataCount = response.data.oDataCount || 0;
710702
}
711703

712704
successCallback(response.data);
@@ -799,12 +791,8 @@ function DynamicsWebApi(config) {
799791
var encodedFetchXml = encodeURIComponent(fetchXml);
800792

801793
var onSuccess = function (response) {
802-
if (response.data['@Microsoft.Dynamics.CRM.fetchxmlpagingcookie'] != null) {
803-
response.data.PagingInfo = Utility.getFetchXmlPagingCookie(response.data['@Microsoft.Dynamics.CRM.fetchxmlpagingcookie'], pageNumber);
804-
}
805-
806-
if (response.data['@odata.context'] != null) {
807-
response.data.oDataContext = response.data['@odata.context'];
794+
if (response.data['@' + DWA.Prefer.Annotations.FetchXmlPagingCookie] != null) {
795+
response.data.PagingInfo = Utility.getFetchXmlPagingCookie(response.data['@' + DWA.Prefer.Annotations.FetchXmlPagingCookie], pageNumber);
808796
}
809797

810798
successCallback(response.data);

lib/dynamics-web-api.js

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -548,16 +548,9 @@ function DynamicsWebApi(config) {
548548

549549
return _sendRequest("GET", result.url, null, result.headers)
550550
.then(function (response) {
551-
if (response.data['@odata.nextLink'] != null) {
552-
response.data.oDataNextLink = response.data['@odata.nextLink'];
553-
}
551+
554552
if (toCount) {
555-
response.data.oDataCount = response.data['@odata.count'] != null
556-
? parseInt(response.data['@odata.count'])
557-
: 0;
558-
}
559-
if (response.data['@odata.context'] != null) {
560-
response.data.oDataContext = response.data['@odata.context'];
553+
response.data.oDataCount = response.data.oDataCount || 0;
561554
}
562555

563556
return response.data;
@@ -725,12 +718,8 @@ function DynamicsWebApi(config) {
725718
return _sendRequest("GET", result.url + "?fetchXml=" + encodedFetchXml, null, result.headers)
726719
.then(function (response) {
727720

728-
if (response.data['@Microsoft.Dynamics.CRM.fetchxmlpagingcookie'] != null) {
729-
response.data.PagingInfo = Utility.getFetchXmlPagingCookie(response.data['@Microsoft.Dynamics.CRM.fetchxmlpagingcookie'], pageNumber);
730-
}
731-
732-
if (response.data['@odata.context'] != null) {
733-
response.data.oDataContext = response.data['@odata.context'];
721+
if (response.data['@' + DWA.Prefer.Annotations.FetchXmlPagingCookie] != null) {
722+
response.data.PagingInfo = Utility.getFetchXmlPagingCookie(response.data['@' + DWA.Prefer.Annotations.FetchXmlPagingCookie], pageNumber);
734723
}
735724

736725
return response.data;

lib/requests/helpers/parseResponse.js

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
var dateReviver = require('./dateReviver');
1+
var DWA = require('../../dwa');
2+
var dateReviver = require('./dateReviver');
3+
4+
//string es6 polyfill
5+
if (!String.prototype.endsWith || !String.prototype.startsWith) {
6+
require("../../polyfills/string-es6");
7+
}
28

39
//https://github.com/emiltholin/google-api-batch-utils
410
function parseBatchResponse(response) {
@@ -20,16 +26,65 @@ function parseBatchResponse(response) {
2026
return result;
2127
}
2228

29+
function populateFormattedValues(object) {
30+
var keys = Object.keys(object);
31+
32+
for (var i = 0; i < keys.length; i++) {
33+
if (object[keys[i]].constructor === Array) {
34+
for (var j = 0; j < object[keys[i]].length; j++) {
35+
object[keys[i]][j] = populateFormattedValues(object[keys[i]][j]);
36+
}
37+
}
38+
39+
if (keys[i].indexOf('@') == -1)
40+
continue;
41+
42+
var format = keys[i].split('@');
43+
var newKey = null;
44+
switch (format[1]) {
45+
case 'odata.context':
46+
newKey = 'oDataContext';
47+
break;
48+
case 'odata.count':
49+
newKey = 'oDataCount';
50+
object[keys[i]] = object[keys[i]] != null
51+
? parseInt(object[keys[i]])
52+
: 0;
53+
break;
54+
case 'odata.nextLink':
55+
newKey = 'oDataNextLink';
56+
break;
57+
case DWA.Prefer.Annotations.FormattedValue:
58+
newKey = format[0] + '_Formatted';
59+
break;
60+
case DWA.Prefer.Annotations.AssociatedNavigationProperty:
61+
newKey = format[0] + '_NavigationProperty';
62+
break;
63+
case DWA.Prefer.Annotations.LookupLogicalName:
64+
newKey = format[0] + '_LogicalName';
65+
break;
66+
}
67+
68+
if (newKey) {
69+
object[newKey] = object[keys[i]];
70+
}
71+
}
72+
73+
return object;
74+
}
75+
2376
/**
2477
*
2578
* @param {string} response
2679
*/
2780
module.exports = function parseResponse(response) {
2881
var responseData = null;
2982
if (response.length) {
30-
responseData = response.indexOf('--batchresponse_') > -1
83+
responseData = response.indexOf('--batchresponse_') > -1
3184
? responseData = parseBatchResponse(response)[0]
3285
: responseData = JSON.parse(response, dateReviver);
86+
87+
responseData = populateFormattedValues(responseData);
3388
}
3489

3590
return responseData;

lib/requests/sendRequest.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@ module.exports = function sendRequest(method, uri, config, data, additionalHeade
6363
return value;
6464
});
6565

66-
stringifiedData = stringifiedData.replace(/[\u007F-\uFFFF]/g, function(chr) {
66+
stringifiedData = stringifiedData.replace(/[\u007F-\uFFFF]/g, function (chr) {
6767
return "\\u" + ("0000" + chr.charCodeAt(0).toString(16)).substr(-4)
68-
})
68+
});
6969
}
7070

7171
//if the URL contains more characters than max possible limit, convert the request to a batch request

tests/common-tests.js

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ var ErrorHelper = require('../lib/helpers/ErrorHelper');
1111
var mocks = require("./stubs");
1212
var dateReviver = require('../lib/requests/helpers/dateReviver');
1313
var sendRequest = require('../lib/requests/sendRequest');
14+
var parseResponse = require('../lib/requests/helpers/parseResponse');
1415

1516
describe("Utility.buildFunctionParameters - ", function () {
1617
it("no parameters", function () {
@@ -1296,7 +1297,6 @@ describe("sendRequest", function () {
12961297
checkBody += rBodys[i];
12971298
}
12981299

1299-
console.error(rBody + '!');
13001300
before(function () {
13011301
var response = mocks.responses.batch;
13021302
scope = nock(mocks.webApiUrl + '$batch')
@@ -1321,7 +1321,7 @@ describe("sendRequest", function () {
13211321
it("returns a correct response", function (done) {
13221322
sendRequest('GET', url, { webApiUrl: mocks.webApiUrl }, null, null, function (object) {
13231323
var multiple = mocks.responses.multiple();
1324-
delete multiple.oDataContext;
1324+
//delete multiple.oDataContext;
13251325
var expectedO = {
13261326
status: 200,
13271327
headers: {},
@@ -1339,4 +1339,16 @@ describe("sendRequest", function () {
13391339
expect(scope.isDone()).to.be.true;
13401340
});
13411341
});
1342+
});
1343+
1344+
describe("parseResponse", function () {
1345+
it("parses formatted values", function () {
1346+
var response = parseResponse(mocks.responses.responseFormatted200.responseText);
1347+
expect(response).to.be.deep.equal(mocks.responses.responseFormattedEntity());
1348+
});
1349+
1350+
it("parses formatted values - array", function () {
1351+
var response = parseResponse(mocks.responses.multipleFormattedResponse.responseText);
1352+
expect(response).to.be.deep.equal(mocks.responses.multipleFormatted());
1353+
});
13421354
});

tests/stubs.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ var dataStubs = {
2323
name: "record",
2424
subject: "test"
2525
},
26+
testEntityFormatted: {
27+
name: "record",
28+
subject: "test",
29+
option: "value",
30+
"@odata.context": "context",
31+
"[email protected]": "formatted"
32+
},
2633
updatedEntity: {
2734
fullname: "test record"
2835
},
@@ -45,6 +52,13 @@ var dataStubs = {
4552
{ name: "name2", subject: "subject2" }
4653
]
4754
},
55+
multipleFormatted: {
56+
"@odata.context": "context",
57+
value: [
58+
{ name: "name1", subject: "subject1", option: "value", "[email protected]": "formatted" },
59+
{ name: "name2", subject: "subject2" }
60+
]
61+
},
4862
multipleWithCount: {
4963
"@odata.context": "context",
5064
"@odata.count": 2,
@@ -199,6 +213,10 @@ var responseStubs = {
199213
status: 200,
200214
responseText: JSON.stringify(dataStubs.testEntity)
201215
},
216+
responseFormatted200: {
217+
status: 200,
218+
responseText: JSON.stringify(dataStubs.testEntityFormatted)
219+
},
202220
retrieveReferenceResponse: {
203221
status: 200,
204222
responseText: JSON.stringify(dataStubs.referenceResponse)
@@ -215,6 +233,10 @@ var responseStubs = {
215233
status: 200,
216234
responseText: JSON.stringify(dataStubs.multiple)
217235
},
236+
multipleFormattedResponse: {
237+
status: 200,
238+
responseText: JSON.stringify(dataStubs.multipleFormatted)
239+
},
218240
multipleWithCountResponse: {
219241
status: 200,
220242
responseText: JSON.stringify(dataStubs.multipleWithCount)
@@ -280,6 +302,12 @@ var responseStubs = {
280302
error: { message: "message" }
281303
})
282304
},
305+
responseFormattedEntity: function () {
306+
var stub = dataStubs.testEntityFormatted;
307+
stub.oDataContext = stub["@odata.context"];
308+
stub.option_Formatted = stub["[email protected]"];
309+
return stub;
310+
},
283311
multipleWithLink: function () {
284312
var stub = dataStubs.multipleWithLink;
285313
stub.oDataContext = stub["@odata.context"];
@@ -292,6 +320,12 @@ var responseStubs = {
292320
return stub;
293321

294322
},
323+
multipleFormatted: function () {
324+
var stub = dataStubs.multipleFormatted;
325+
stub.oDataContext = stub["@odata.context"];
326+
stub.value[0].option_Formatted = stub.value[0]["[email protected]"];
327+
return stub;
328+
},
295329
multipleWithCount: function () {
296330
var stub = dataStubs.multipleWithCount;
297331
stub.oDataContext = stub["@odata.context"];

0 commit comments

Comments
 (0)