Catching exceptions thrown by Swagger - exception

I'm new at fumbling with Swagger, so I might be asking a silly question. Is it in any way possible to prevent the site from crashing whenever it is "unable to read from api"?
My site is working most of the time, but if there for some reason is an api that is unreadable (or just unreachable) swagger just stop working. It still displays the api's it managed to reach, but all functionality is completely gone its not even able to expand a row.
To summarize:
How do I prevent swagger from crashing, when one or more API's is unreadable and returns something like this:
Unable to read api 'XXXX' from path
http://example.com/swagger/api-docs/XXXX (server
returned undefined)
Below is my initialization of Swagger:
function loadSwagger() {
window.swaggerUi = new SwaggerUi({
url: "/frameworks/swagger/v1/api.json",
dom_id: "swagger-ui-container",
supportedSubmitMethods: ['get', 'post', 'put', 'delete'],
onComplete: function (swaggerApi, swaggerUi) {
log("Loaded SwaggerUI");
if (typeof initOAuth == "function") {
initOAuth({
clientId: "your-client-id",
realm: "your-realms",
appName: "your-app-name"
});
}
$('pre code').each(function (i, e) {
hljs.highlightBlock(e);
});
},
onFailure: function (data) {
log("Unable to Load SwaggerUI");
},
docExpansion: "none",
sorter: "alpha"
});
$('#input_apiKey').change(function () {
var key = $('#input_apiKey')[0].value;
log("key: " + key);
if (key && key.trim() != "") {
log("added key " + key);
window.authorizations.add("api_key", new ApiKeyAuthorization('api_key', key, 'header'));
}
});
$('#apiVersionSelectID').change(function () {
var sel = $('#apiVersionSelectID').val();
window.swaggerUi.url = sel;
$('#input_baseUrl').val(sel);
$('#explore').click();
});
window.swaggerUi.load();
};

I was searching for a solution to this problem too but could not find one. Here is a quick hack i did to solve the problem. Hope it can be of help to someone who is having the same trouble.
In swagger-client.js Find the function error: function (response) {
I replaced the return api_fail with addApiDeclaration to make it draw the api with some limited information even when it fails. I send in a dummy api json object with the path set to "/unable to load ' + _this.url. I send in an extra parameter that can be true or false, where true indicates that this is a failed api.
Old code:
enter cerror: function (response) {
_this.api.resourceCount += 1;
return _this.api.fail('Unable to read api \'' +
_this.name + '\' from path ' + _this.url + ' (server returned ' +response.statusText + ')');
}
New code
error: function (response) {
_this.api.resourceCount += 1;
return _this.addApiDeclaration(JSON.parse('{"apis":[{"path":"/unable to load ' + _this.url + '","operations":[{"nickname":"A","method":" "}]}],"models":{}}'), true);
}
I modified the addApiDeclaration function in the same file to display a different message for a failed api by first adding a secondary parameter to it called failed and then an if statement that check if failed is true and then change the name of the api to "FAILED TO LOAD RESOURCE " + this.name. This adds the FAILED TO LOAD RESOURCE text before the failed api.
Old code
SwaggerResource.prototype.addApiDeclaration = function (response) {
if (typeof response.produces === 'string')
this.produces = response.produces;
if (typeof response.consumes === 'string')
this.consumes = response.consumes;
if ((typeof response.basePath === 'string') && response.basePath.replace(/\s/g, '').length > 0)
this.basePath = response.basePath.indexOf('http') === -1 ? this.getAbsoluteBasePath(response.basePath) : response.basePath;
this.resourcePath = response.resourcePath;
this.addModels(response.models);
if (response.apis) {
for (var i = 0 ; i < response.apis.length; i++) {
var endpoint = response.apis[i];
this.addOperations(endpoint.path, endpoint.operations, response.consumes, response.produces);
}
}
this.api[this.name] = this;
this.ready = true;
if(this.api.resourceCount === this.api.expectedResourceCount)
this.api.finish();
return this;
};
New code
SwaggerResource.prototype.addApiDeclaration = function (response, failed) {
if (typeof response.produces === 'string')
this.produces = response.produces;
if (typeof response.consumes === 'string')
this.consumes = response.consumes;
if ((typeof response.basePath === 'string') && response.basePath.replace(/\s/g, '').length > 0)
this.basePath = response.basePath.indexOf('http') === -1 ? this.getAbsoluteBasePath(response.basePath) : response.basePath;
this.resourcePath = response.resourcePath;
this.addModels(response.models);
if (response.apis) {
for (var i = 0 ; i < response.apis.length; i++) {
var endpoint = response.apis[i];
this.addOperations(endpoint.path, endpoint.operations, response.consumes, response.produces);
}
}
if (failed == true) {
this.name = "FAILED TO LOAD RESOURCE - " + this.name;
}
this.api[this.name] = this;
this.ready = true;
if(this.api.resourceCount === this.api.expectedResourceCount)
this.api.finish();
return this;
};

Related

AngularJS Selects Empty Option Even Valid Option is Avaliable

I'm using AngularJS ver. 1.2.15 on my project. And, I have a select element on one of my views as per below:
<select class="select-white form-control form-select" id="cat2_{{feed.id}}" ng-model="feed.operationstatusid" ng-change="updateCategoryAndStatus(feed, true)"></select>
And, I'm feeding this element like this:
function SetCategory2(cat1Id, feed) {
var feedId = feed.id;
var fromRuleOpStatusId = -1;
$('#cat2_' + feedId).find('option').remove();
if (cat1Id > -1) {
$('#cat2_' + feedId).append($('<option></option>').text(lang.SelectSubCategory).val(0));
$.each($scope.category2, function (index, cat2Item) {
$('#cat2_' + feedId).append($('<option></option>').text(cat2Item.statusdescription).val(cat2Item.id));
});
var isselected = false;
$.each($scope.category2, function (index, cat2Item) {
if (feed.operationstatusid == cat2Item.id) {
$('#cat2_' + feedId).val(cat2Item.id);
fromRuleOpStatusId = -1;
isselected = true;
}
else {
var feedStr = "";
if (feed.title != undefined && feed.title != null) {
feedStr = feed.title.toLowerCase();
}
if ($scope.catTitleRulesTwo) {
$.each($scope.catTitleRulesTwo, function (r_index, r_item) {
if (cat2Item.id == r_item.titleCode && !isselected) {
if (feedStr != undefined && feedStr != null && r_item != undefined && r_item != null) {
String.prototype.contains = function (str) { return this.toLowerCase().indexOf(str) !== -1; };
var text = feedStr;
if (eval(r_item.ruleexpression)) {
$('#cat2_' + feedId).val(cat2Item.id);
fromRuleOpStatusId = cat2Item.id;
isselected = true;
}
}
}
});
}
}
});
if (fromRuleOpStatusId != -1) {
feed.operationstatusid = fromRuleOpStatusId;
}
}
else {
$('#cat2_' + feedId).append($('<option></option>').text(lang.SelectSubCategory).val(0));
}
}
I am aware of the facts about eval function, but the project I'm working on is quite old, so does the code. Anyway, this is about business logic and quite irrelevant with the thing I'm going to ask (or so I was thinking).
As you can see I'm appending all the options before I set the value of the selectbox with using .val(...). I have also checked that values do match along with the data types. But, when I observe this function step by step, I saw that selected value does show up without flaw. After the code finish with my above mentioned function (SetCategory2), code goes through on of the function located on AngularJS file, named xhr.onreadystatechange. It's not a long function, so I'm sharing it also on below.
xhr.onreadystatechange = function() {
if (xhr && xhr.readyState == 4) {
var responseHeaders = null,
response = null;
if(status !== ABORTED) {
responseHeaders = xhr.getAllResponseHeaders();
response = ('response' in xhr) ? xhr.response : xhr.responseText;
}
completeRequest(callback,
status || xhr.status,
response,
responseHeaders);
}
};
After the code released from this function, respective selectbox's value is pointed at the empty option.
I have run into topics which talks about this behaviour might due to invalid option-value match, but as I described above, I append all my options before deciding the value. So, I can't figure out what I'm missing.
Thank you in advance.

How change display value/color td based on JSON

I'm working on an app where I get a json via an ajax call. This json contains objects where you get a certain status code per extension (1 = online, 2, is ringing, 3 = busy)
How can I ensure that the value that I get back is converted to the text (preferably with a different color of the )
So when I get a 1 back I want it to show Online, and with a 2 Ring etc
$.ajax({
type:'GET',
url: url,
dataType: 'json',
error: function(jqXHR, exception) {ajax_error_handler(jqXHR, exception);},
success: function(data){
// console.log(JSON.parse(data.responseText));
// console.log(JSON.parse(data.responseJSON));
console.log(data['entry']);
var event_data = '';
$.each(data.entry, function(index, value){
/* console.log(data['entry']);*/
event_data += '<tr>';
event_data += '<td>'+value.extension+'</td>';
event_data += '<td>'+value.status+'</td>';
<!--event_data += '<td>'+value.registration+'</td>';-->
event_data += '</tr>';
});
$("#list_table_json").append(event_data);
},
error: function(d){
/*console.log("error");*/
alert("404. Please wait until the File is Loaded.");
}
});
Thanks in advance!
I have change the code
function get_blf() {
$.ajax({
type:'GET',
url: url,
dataType: 'json',
error: function(jqXHR, exception) {ajax_error_handler(jqXHR, exception);},
success: function(data){
$.each(data.entry, (index, value) => {
const tableRow = document.createElement('tr');
const tdExtension = document.createElement('td');
extension.textContent = value.status;
const tdStatus = document.createElement('td');
if (value.status == 3) status.textContent = 'Busy';
if (value.status == 2) status.textContent = 'Ringing';
if (value.status == 1) status.textContent = 'Online';
tdStatus.classList.add(`status-${value.status}`);
tableRow.appendChild(tdExtension);
tableRow.appendChild(tdStatus);
$('#list_table_json').append(tableRow);
}
});
}
}
and add the css, but now i can't get any values back. but now i can't get any values back. (sorry I'm fairly new to javascript)
Please use the DOM API
One way of getting colors would be to use CSS classes for the status:
// js
...
$.each(data.entry, (index, value) => {
const tableRow = document.createElement('tr');
const tdExtension = document.createElement('td');
extension.textContent = value.extension;
const tdStatus = document.createElement('td');
if (value.status == 3) status.textContent = 'Busy';
if (value.status == 2) status.textContent = 'Ringing';
if (value.status == 1) status.textContent = 'Online';
tdStatus.classList.add(`status-${value.status}`);
tableRow.appendChild(tdExtension);
tableRow.appendChild(tdStatus);
$('#list_table_json').append(tableRow);
});
...
// css
.status-1 {
color: green;
}
.status-2 {
color: red;
}
.status-3 {
color: orange;
}
I finally got the script working. I am now trying to build in a polling, however I see that the ajax call is executed again and the array is fetched. However, the table is not refreshed but a new table is added, does anyone know a solution for this?
code I'm using now for the repoll is
function repoll(poll_request, poll_interval, param=null) {
if (poll_interval != 0) {
if (window.timeoutPool) {
window.timeoutPool.push(setTimeout(function() { poll_request(param); }, poll_interval));
}
else {
setTimeout(function() { poll_request(param); }, poll_interval);
}
}
else {
log_msg('Poll cancelled.');
}
}
tableRow.appendChild(tdExtension);
tableRow.appendChild(tdNr);
tableRow.appendChild(tdStatus);
$('#list_table_json').append(tableRow);
});
repoll(get_blf, poll_interval_blf);

How do I format my output to be on top of each other instead of side to side?

I am using Googles NLP in Apps Scripts and the data is pulling through. However my output is displaying horizontally instead of on-top of each other. Probably a simple change but I'm not able to figure it out. In the screenshot I shared I would like the number 0.3 to be under the metric 2.10 (in yellow). Any advice would be helpful.
function SentimentAnalysis(text)
{
if (text == undefined || text == null || text == "")
{
throw "No text was specified for performing sentiment analysis."
}
var URL_PREFIX = "https://language.googleapis.com/v1/documents:analyzeSentiment?fields=documentSentiment&key=";
// retrieve api key;
var apiKey = PropertiesService.getScriptProperties().getProperty("GOOGLE_CLOUD_API_KEY");
if (apiKey == null || apiKey == "REPLACEME" || apiKey == "") {
PropertiesService.getScriptProperties().setProperty("GOOGLE_CLOUD_API_KEY", "REPLACEME");
throw "Specify your GOOGLE_CLOUD_API_KEY via User Properties (File->Project Properties, Script Properties)";
}
var url = URL_PREFIX + apiKey;
// define the request
var data = {
"document": {
"content": text,
"type": "PLAIN_TEXT"
},
"encodingType": "UTF8"
};
var options = {
"method" : "POST",
"contentType" : "application/json",
"payload" : JSON.stringify(data)
};
// make the request
var response = UrlFetchApp.fetch(url, options);
// get the response
if (response.getResponseCode() != 200) {
throw "Unexpected response code from Google.";
}
var responseText = response.getContentText();
if (responseText == null || responseText == "") {
throw "Empty response from Facebook.";
}
// parse the response
var magnitude = 0, score = 0;
try
{
var sentimentResponse = JSON.parse(responseText, false);
magnitude = parseFloat(sentimentResponse.documentSentiment.magnitude);
score = parseFloat(sentimentResponse.documentSentiment.score);
}
catch (e)
{
throw "Unreadable response from Google: " + e;
}
return [[magnitude, score]];
}
I believe your goal as follows.
You want to put the values of magnitude and score to the vertical direction.
You are using the function of SentimentAnalysis as the custom function.
magnitude and score are the correct values you expect.
In this case, how about the following modification?
From:
return [[magnitude, score]];
To:
return [magnitude, score];
or
return [[magnitude], [score]];

How do you go into production with polymer project?

Is it common sense to use polymer build and then deploy the Application on your Web Server used for production?
Or does it make sense to actutally use polymer serve / polyserve as the Web Server?
The problem with polymer serve is that if it falls over it doesn't restart, leaving you with no web site. Its real use is in development because it maps directories for you when you are developing a single element.
Also, how will you be handling ajax calls?
IN the past I have previously run my code (a bespoke node web server) in PM2. These days I run using docker, and in particular docker-compose which also restarts the application if it fails.
EDIT The following is how I transpile on the fly code is copied (and then altered by me) from Google Polymer Teams "Polymer Server" and is therefore subject to the licence conditions given in that project.
* Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
* This code may only be used under the BSD style license found at
* http://polymer.github.io/LICENSE.txt
* The complete set of authors may be found at
* http://polymer.github.io/AUTHORS.txt
* The complete set of contributors may be found at
* http://polymer.github.io/CONTRIBUTORS.txt
* Code distributed by Google as part of the polymer project is also
* subject to an additional IP rights grant found at
* http://polymer.github.io/PATENTS.txt
The code consists of some supporting functions like these
const parse5 = require('parse5');
const dom5 = require('dom5');
const LRU = require('lru-cache');
const babelCore = require('babel-core');
const transformLog = require('debug')('web:transform');
const babelTransformers = [
'babel-plugin-transform-es2015-arrow-functions',
'babel-plugin-transform-es2015-block-scoped-functions',
'babel-plugin-transform-es2015-block-scoping',
'babel-plugin-transform-es2015-classes',
'babel-plugin-transform-es2015-computed-properties',
'babel-plugin-transform-es2015-destructuring',
'babel-plugin-transform-es2015-duplicate-keys',
'babel-plugin-transform-es2015-for-of',
'babel-plugin-transform-es2015-function-name',
'babel-plugin-transform-es2015-literals',
'babel-plugin-transform-es2015-object-super',
'babel-plugin-transform-es2015-parameters',
'babel-plugin-transform-es2015-shorthand-properties',
'babel-plugin-transform-es2015-spread',
'babel-plugin-transform-es2015-sticky-regex',
'babel-plugin-transform-es2015-template-literals',
'babel-plugin-transform-es2015-typeof-symbol',
'babel-plugin-transform-es2015-unicode-regex',
'babel-plugin-transform-regenerator',
].map((name) => require(name));
const isInlineJavaScript = dom5.predicates.AND(
dom5.predicates.hasTagName('script'),
dom5.predicates.NOT(dom5.predicates.hasAttr('src')));
const babelCompileCache = LRU({
length: (n, key) => n.length + key.length
});
function compileHtml(source, location) {
const document = parse5.parse(source);
const scriptTags = dom5.queryAll(document, isInlineJavaScript);
for (const scriptTag of scriptTags) {
try {
const script = dom5.getTextContent(scriptTag);
const compiledScriptResult = compileScript(script);
dom5.setTextContent(scriptTag, compiledScriptResult);
} catch (e) {
// By not setting textContent we keep the original script, which
// might work. We may want to fail the request so a better error
// shows up in the network panel of dev tools. If this is the main
// page we could also render a message in the browser.
//eslint-disable-next-line no-console
console.warn(`Error compiling script in ${location}: ${e.message}`);
}
}
return parse5.serialize(document);
}
function compileScript(script) {
return babelCore
.transform(script, {
plugins: babelTransformers,
}).code;
}
function transform(request, body, isHtml) {
const source = body;
const cached = babelCompileCache.get(source);
if (cached !== undefined) {
transformLog('using the cache');
return cached;
}
if (isHtml) {
transformLog('compiling html');
body = compileHtml(source, request.path);
} else {
transformLog('compiling js');
body = compileScript(source);
}
babelCompileCache.set(source, body);
return body;
}
The meat though is the middleware which effectively inserts itself in the outgoing stream captures all the chunks of outgoing html and js files and transforms them if necessary.
function transformResponse(transformNeeded) {
return (req, res, next) => {
let ended = false;
let _shouldTransform = null;
let isHtml = true;
// Note: this function memorizes its result.
function shouldTransform() {
if (_shouldTransform == null) {
const successful = res.statusCode >= 200 && res.statusCode < 300;
if (successful) {
const result = transformNeeded(req);
isHtml = result.isHtml;
_shouldTransform = !!result.transform;
} else {
_shouldTransform = false;
}
}
return _shouldTransform;
}
const chunks = [];
const _write = res.write;
res.write = function( chunk, enc, cb) {
if (ended) {
_write.call(this, chunk, enc, cb);
return false;
}
if (shouldTransform()) {
const buffer = (typeof chunk === 'string') ? new Buffer(chunk,enc) : chunk;
chunks.push(buffer);
return true;
}
return _write.call(this, chunk, enc, cb);
}.bind(res);
const _end = res.end;
res.end = function (chunk, enc, cb) {
if (ended)
return false;
ended = true;
if (shouldTransform()) {
if (chunk) {
const buffer = (typeof chunk === 'string') ? new Buffer(chunk,enc) : chunk;
chunks.push(buffer);
}
const body = Buffer.concat(chunks).toString('utf8');
let newBody = body;
try {
newBody = transform(req, body, isHtml);
} catch (e) {
//eslint-disable-next-line no-console
console.warn('Error', e);
}
// TODO(justinfagnani): re-enable setting of content-length when we know
// why it was causing truncated files. Could be multi-byte characters.
// Assumes single-byte code points!
// res.setHeader('Content-Length', `${newBody.length}`);
this.removeHeader('Content-Length');
return _end.call(this, newBody);
}
return _end.call(this,chunk, enc, cb);
}.bind(res);
next();
};
}
This routine called transformNeeded which is as follows (this is the bit that detects the brower)
function transformNeeded(req) {
const pathname = url.parse(req.url).pathname;
const isHtml = pathname === '/' || pathname.slice(-5) === '.html';
if (isHtml || pathname.slice(-3) === '.js') {
//see if we need to compile as we have a .html or .js file
const splitPathName = pathname.split('/');
const isPolyfill = splitPathName.includes('webcomponentsjs') ||
splitPathName.includes('promise-polyfill');
if (!isPolyfill) {
const browser = new UAParser(req.headers['user-agent']).getBrowser();
const versionSplit = (browser.version || '').split('.');
const [majorVersion, minorVersion] = versionSplit.map((v) => v ? parseInt(v, 10) : -1);
const supportsES2015 = (browser.name === 'Chrome' && majorVersion >= 49) ||
(browser.name === 'Chromium' && majorVersion >= 49) ||
(browser.name === 'OPR' && majorVersion >= 36) ||
(browser.name === 'Mobile Safari' && majorVersion >= 10) ||
(browser.name === 'Safari' && majorVersion >= 10) ||
// Note: The Edge user agent uses the EdgeHTML version, not the main
// release version (e.g. EdgeHTML 15 corresponds to Edge 40). See
// https://en.wikipedia.org/wiki/Microsoft_Edge#Release_history.
//
// Versions before 15.15063 may contain a JIT bug affecting ES6
// constructors (see #161).
(browser.name === 'Edge' &&
(majorVersion > 15 || (majorVersion === 15 && minorVersion >= 15063))) ||
(browser.name === 'Firefox' && majorVersion >= 51);
requestLog(
'Browser is %s version %d,%d - supports ES2015? ',
browser.name,
majorVersion,
minorVersion,
supportsES2015
);
return {transform: !supportsES2015, isHtml: isHtml};
}
}
return {transform: false, isHtml: isHtml};
}
Finally, I have to set up the routes before I establish the web server and then tell the web server to use the routes I have set up.
const Router = require('router');
//sets up my API routes
manager.setRoutes(router);
router.use('/', transformResponse(this.transformNeeded));
router.use('/', staticFiles(clientPath));
this._start(router);

CRM 2011: Getting entity with Javascript

I am working on some CRM 2011 Online customisations and I need to get an entity using javascript.
The entity I need will be based on the ID value of another field (a Contact entity) - this Contact ID I can get fine.
The entity I want is a custom entity. There may be multiple matches based on the Contact ID so I just want to get the first one in the list (order not important)
So far I have looked into a few ways to do this...
OData - I couldn't find enough examples on this as to what query expressions I can create, also I don't know if/how to make this work for custom entities
FetchXML - I can create a nice FetchXML query using the built-in "advanced find" too and would be happy to call this from javascript if anyone can help? I found one promising answer here but I could not see how the "results" return data was being set (Service.Fetch function)
SOAP Request - First thing I tried is a similar method as I could have done in CRM 4 but this does not seem to work. Although the request executes, my result data just seems to be empty. This is all I have code for so if any one can spot a problem with the code below then that would be great.
EDIT: I have spotted some redundant query data (I had removed link opening tags but left closing tags) - since removing this I now get XML result data... however, the where clause does not seem to apply (just get list of all entities)
var xml = "<?xml version='1.0' encoding='utf-8'?>" +
"<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">" +
GenerateAuthenticationHeader() +
"<soap:Body>" +
"<RetrieveMultiple xmlns=\"http://schemas.microsoft.com/crm/2007/WebServices\">" +
"<query xmlns:q1=\"http://schemas.microsoft.com/crm/2006/Query\" xsi:type=\"q1:QueryExpression\">" +
"<q1:EntityName>new_vehicle</q1:EntityName>" +
"<q1:ColumnSet xsi:type='q1:ColumnSet'>" +
"<q1:Attributes>" +
"<q1:Attribute>new_vehicleid</q1:Attribute>" +
"<q1:Attribute>new_primarydriver</q1:Attribute>" +
"<q1:Attribute>statuscode</q1:Attribute>" +
"<q1:Attribute>new_registration</q1:Attribute>" +
"</q1:Attributes>" +
"</q1:ColumnSet>" +
"<q1:Distinct>false</q1:Distinct>" +
"<q1:Conditions>" +
"<q1:Condition>" +
"<q1:AttributeName>new_primarydriver</q1:AttributeName>" +
"<q1:Operator>Equal</q1:Operator>" +
"<q1:Values>" +
"<q1:Value xmlns:q2='http://microsoft.com/wsdl/types/' xsi:type='q2:guid'>" +
customerID +
"</q1:Value></q1:Values></q1:Condition>" +
"</q1:Conditions>" +
"</query></RetrieveMultiple>" +
"</soap:Body></soap:Envelope>";
var xmlHttpRequest = new ActiveXObject("Msxml2.XMLHTTP");
xmlHttpRequest.Open("POST", "/mscrmservices/2007/CrmService.asmx", false);
xmlHttpRequest.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/crm/2007/WebServices/RetrieveMultiple");
xmlHttpRequest.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
xmlHttpRequest.setRequestHeader("Content-Length", xml.length);
xmlHttpRequest.send(xml);
var result = xmlHttpRequest.responseXML.xml;
var doc = new ActiveXObject("MSXML2.DOMDocument");
doc.async = false;
doc.loadXML(result);
var id = doc.selectSingleNode("//new_vehicleid");
var registration = doc.selectSingleNode("//new_registration");
if(id == null)
return null;
var vehicle = new Array();
value[0] = new Object();
value[0].id = id;
value[0].name = registration;
value[0].entityType = "new_vehicle";
return vehicle;
Sorry about the big code post but hopefully somebody who has a better understanding can help
Firstly, thanks to GlennFerrieLive for his answer post. The samples I found with the Dynamics CRM 2011 SDK (well just one in particular) really helped and the JSON parser included was perfect for the job!
I am posting this answer to give a full example of how I did it with some important comments to pay attention to which may not be so obvious from the SDK examples.
Get selected ID value from lookup field
The aim of my task was to use javascript to get set a lookup field, based on the selected data of another lookup entity. The entity to set is "new_vehicle" and the entity to query on is "customer".
First job is to get the ID value of the contact lookup field...
var customerItem = Xrm.Page.getAttribute("customerid").getValue();
var customerID = customerItem[0].id;
Querying an entity using an ID
Next is the part where I used the customerID value to find the vehicle that is currently assigned to them (the entity I want to use to set a lookup field).
First problem I found was that when querying with OData, the ID value does not seem to work with curly brackets {} - so these need to be removed...
customerID = customerID.replace('{', '').replace('}', '');
Next we get the oDataPath...
var oDataPath = Xrm.Page.context.getServerUrl() + "/xrmservices/2011/organizationdata.svc";
Then we can construct the OData query...
var filter = "/new_vehicleSet?" +
"$select=new_vehicleId,new_Registration" +
"&$filter=new_PrimaryDriver/Id eq (guid'" + customerID + "')" +
"&$orderby=new_LastAllocationDate desc" +
"&$top=1";
NOTE: There are a couple of important things to note here...
When using a guid value you must explicitly say it is a guid using (guid'xxx')
When filtering by a lookup entity (e.g. new_PrimaryDriver) you must append the value to query (e.g. Id) - this results in new_PrimaryDriver/Id
Once we have the query setup we can request the data as follows...
var retrieveRecordsReq = new XMLHttpRequest();
retrieveRecordsReq.open("GET", oDataPath + filter, true);
retrieveRecordsReq.setRequestHeader("Accept", "application/json");
retrieveRecordsReq.setRequestHeader("Content-Type", "application/json; charset=utf-8");
retrieveRecordsReq.onreadystatechange = function () {
if (this.readyState == 4) {
if (this.status == 200) {
var retrievedRecords = JSON.parse(retrieveRecordsReq.responseText).d;
if(retrievedRecords.results.length > 0)
{
var vehicle = retrievedRecords.results[0];
SetLookup("new_replacedvehicle", vehicle.new_vehicleId, vehicle.new_Registration, "new_vehicle");
}
}
}
};
retrieveRecordsReq.send();
Note that this is an asynchronous call and the onreadystatechange function will be processed upon completion, in this function we do a couple of checks to see if it was a success and the we parse the resulting JSON data - the JSON.Parse function has been included at the bottom of this post (but is available from the SDK)
Setting a lookup field using the entity queried above
The other function to make note of here is SetLookup which is just a simple helper function I added to set a lookup field. This is as follows...
function SetLookup(fieldName, idValue, textValue, typeValue)
{
var value = new Array();
value[0] = new Object();
value[0].id = idValue;
value[0].name = textValue;
value[0].typename = typeValue;
Xrm.Page.getAttribute(fieldName).setValue(value);
}
JSON parse function
This is the JSON helper function that was used in the above code (JSON.parse), pasted as it was found in the SDK...
if (!this.JSON) { this.JSON = {}; } (function () { function f(n) { return n < 10 ? '0' + n : n; } if (typeof Date.prototype.toJSON !== 'function') { Date.prototype.toJSON = function (key) { return isFinite(this.valueOf()) ? this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + f(this.getUTCDate()) + 'T' + f(this.getUTCHours()) + ':' + f(this.getUTCMinutes()) + ':' + f(this.getUTCSeconds()) + 'Z' : null; }; String.prototype.toJSON = Number.prototype.toJSON = Boolean.prototype.toJSON = function (key) { return this.valueOf(); }; } var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, gap, indent, meta = { '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"': '\\"', '\\': '\\\\' }, rep; function quote(string) { escapable.lastIndex = 0; return escapable.test(string) ? '"' + string.replace(escapable, function (a) { var c = meta[a]; return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }) + '"' : '"' + string + '"'; } function str(key, holder) { var i, k, v, length, mind = gap, partial, value = holder[key]; if (value && typeof value === 'object' && typeof value.toJSON === 'function') { value = value.toJSON(key); } if (typeof rep === 'function') { value = rep.call(holder, key, value); } switch (typeof value) { case 'string': return quote(value); case 'number': return isFinite(value) ? String(value) : 'null'; case 'boolean': case 'null': return String(value); case 'object': if (!value) { return 'null'; } gap += indent; partial = []; if (Object.prototype.toString.apply(value) === '[object Array]') { length = value.length; for (i = 0; i < length; i += 1) { partial[i] = str(i, value) || 'null'; } v = partial.length === 0 ? '[]' : gap ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : '[' + partial.join(',') + ']'; gap = mind; return v; } if (rep && typeof rep === 'object') { length = rep.length; for (i = 0; i < length; i += 1) { k = rep[i]; if (typeof k === 'string') { v = str(k, value); if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v); } } } } else { for (k in value) { if (Object.hasOwnProperty.call(value, k)) { v = str(k, value); if (v) { partial.push(quote(k) + (gap ? ': ' : ':') + v); } } } } v = partial.length === 0 ? '{}' : gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : '{' + partial.join(',') + '}'; gap = mind; return v; } } if (typeof JSON.stringify !== 'function') { JSON.stringify = function (value, replacer, space) { var i; gap = ''; indent = ''; if (typeof space === 'number') { for (i = 0; i < space; i += 1) { indent += ' '; } } else if (typeof space === 'string') { indent = space; } rep = replacer; if (replacer && typeof replacer !== 'function' && (typeof replacer !== 'object' || typeof replacer.length !== 'number')) { throw new Error('JSON.stringify'); } return str('', { '': value }); }; } if (typeof JSON.parse !== 'function') { JSON.parse = function (text, reviver) { var j; function walk(holder, key) { var k, v, value = holder[key]; if (value && typeof value === 'object') { for (k in value) { if (Object.hasOwnProperty.call(value, k)) { v = walk(value, k); if (v !== undefined) { value[k] = v; } else { delete value[k]; } } } } return reviver.call(holder, key, value); } text = String(text); cx.lastIndex = 0; if (cx.test(text)) { text = text.replace(cx, function (a) { return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }); } if (/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '#').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { j = eval('(' + text + ')'); return typeof reviver === 'function' ? walk({ '': j }, '') : j; } throw new SyntaxError('JSON.parse'); }; } } ());