This question already has answers here:
Angular 2 http get not getting
(4 answers)
Closed 3 years ago.
I have the following auto-generated code (NSwag Studio) - the only amendment is withCredentials which I've added for Windows Auth (intranet app).
deleteObjective(id: number): Observable<ObjectiveDTO> {
let url_ = this.baseUrl + "/api/Objectives/{id}";
if (id === undefined || id === null)
throw new Error("The parameter 'id' must be defined.");
url_ = url_.replace("{id}", encodeURIComponent("" + id));
url_ = url_.replace(/[?&]$/, "");
let options_ : any = {
observe: "response",
responseType: "blob",
withCredentials: true,
headers: new HttpHeaders({
"Accept": "application/json"
})
};
return this.http.request("delete", url_, options_).pipe(_observableMergeMap((response_ : any) => {
return this.processDeleteObjective(response_);
})).pipe(_observableCatch((response_: any) => {
if (response_ instanceof HttpResponseBase) {
try {
return this.processDeleteObjective(<any>response_);
} catch (e) {
return <Observable<ObjectiveDTO>><any>_observableThrow(e);
}
} else
return <Observable<ObjectiveDTO>><any>_observableThrow(response_);
}));
}
everything else in the generated service works fine (this is the only delete) but this is literally not sending any traffic - in the Chrome Network pullout (F12) there is no call to the API and the API does not receive anything.
I am calling it from my component like this:
deleteObjective(): void {
if (confirm('Are you sure you want to delete this objective? This cannot be un-done.')) {
if (this.objective.id !== 0)
this.service.deleteObjective(this.objective.id);
for (var i = 0; i < this.objectives.length; i++) {
if (this.objectives[i] === this.objective) {
this.objectives.splice(i, 1);
}
}
}
}
and the splice is definitely working. If I put debugger before the http request it calls it. There are no errors in the console.
Any ideas? I am new to angular but old to programming.
When you are calling the function from the service, make sure you subscribe() to it.
The API will be hit only after you subscribe
Try like this:
deleteObjective(): void {
if (confirm('Are you sure you want to delete this objective? This cannot be un-done.')) {
if (this.objective.id !== 0)
this.service.deleteObjective(this.objective.id).subscribe(res => {
for (var i = 0; i < this.objectives.length; i++) {
if (this.objectives[i] === this.objective) {
this.objectives.splice(i, 1);
}
}
})
}
}
Related
While developing a custom app for my organization, I am trying to request the name and the avatar of the individual accessing the card. I am able to get the name of the individual without any problems, but when requesting the avatar image I get the following console error:
Uncaught (in promise) Error: Invalid JSON response at XMLHttpRequest.d.onload (domo.ts:309:18)
I have looked into the domo.js code, and after making some limited sense of things, I found that it tries to JSON.parse the .png that is returned.
When checking the network dev tools tab I can see the correct image getting returned, but it doesn't get passed to the app.
Here is the function that returns the error:
d.onload = function() {
var e;
if( u(d.status) ) {
!["csv","excel"].includes(r.format) && d.response || i(d.response), "blob" === r.responseType && i(new Blob([d.response], { type:d.getResponseHeader("content-type") }));
var t = d.response;
try{
e = JSON.parse(t)
}
catch(e){
return void c(Error("Invalid JSON response"))
}i(e)
}else c(Error(d.statusText))
}
As far as I can tell, e refers to the Domo environment, although I am not 100% sure of that.
Note: I am turning to stackoverflow because my organization still has open support tickets with Domo that are more than 2 years old with no response, so I have little faith in getting a timely response from Domo regarding this issue.
UPDATE: Here is the full function that is called-
function i(e,t,r,n,a) {
return r = r || {}, new Promise((function(i,c) {
var d = new XMLHttpRequest;
if (n?d.open(e,t,n):d.open(e,t), p(d,t,r), function(e,t) {
t.contentType ?
"multipart" !== t.contentType && e.setRequestHeader("Content-Type", t.contentType)
: e.setRequestHeader("Content-Type", o.DataFormats.JSON)
} (d,r), function(e) {
s && e.setRequestHeader("X-DOMO-Ryuu-Token", s)
} (d), function(e,t) {
void 0 !== t.responseType && (e.responseType = t.responseType)
} (d,r),
d.onload = function() {
var e;
if( u(d.status) ) {
!["csv","excel"].includes(r.format) && d.response || i(d.response), "blob" === r.responseType && i(new Blob([d.response], { type:d.getResponseHeader("content-type") }));
var t = d.response;
try{
e = JSON.parse(t)
}
catch(e){
return void c(Error("Invalid JSON response"))
}i(e)
}else c(Error(d.statusText))
},
d.onerror = function() {
c(Error("Network Error"))
}, a)
if (r.contentType && r.contentType !== o.DataFormats.JSON) d.send(a);
else {
var f = JSON.stringify(a);
d.send(f)
}
else d.send()
}))
Here is the domo.js method that is being called to get the image:
e.get = function(e, t) {
return i(o.RequestMethods.GET, e, t)
},
#Skousini you can get the avatar for a user by providing this URL directly to the src property of the <img> tag (obviously replacing the query params with the relevant information):
<img src="/domo/avatars/v2/USER/846578099?size=300&defaultForeground=fff&defaultBackground=000&defaultText=D" />
This documentation is available on developer.domo.com: https://developer.domo.com/docs/dev-studio-references/user-api#User%20Avatar
If you want to pull down data from endpoints, you don't have to use domo.js. You could use axios or any other HTTP tool. domo.js is trying to make HTTP requests simpler by automatically parsing json (since most requests are json based). There are a few other options for what data format that domo.get can support provided in this documentation: https://developer.domo.com/docs/dev-studio-tools/domo-js#domo.get
I have a pretty simple method/function that I'm calling in Vue upon button click, which changes state (also toggles text on the button).
pauseTask: function() {
this.isOpen = !this.isOpen;
this.pauseButton.text = this.isOpen ? 'Pause' : 'Resume';
},
It works perfectly, but I need to make an axios call each time based on the state so if this.isOpen then I want to call:
axios.post('/item/status/pause',data)
.then((response) => {
// handle success
console.log(response.data);
if (response.data.success == false) {
this.errors = [];
const errorLog = Object.entries(response.data.errors);
for (var i = errorLog.length - 1; i >= 0; i--) {
console.log(errorLog[i][1][0]);
this.errors.push(errorLog[i][1][0]);
}
}
});
and if !this.isOpen then:
axios.post('/item/status/resume',data)
.then((response) => {
// handle success
console.log(response.data);
if (response.data.success == false) {
this.errors = [];
const errorLog = Object.entries(response.data.errors);
for (var i = errorLog.length - 1; i >= 0; i--) {
console.log(errorLog[i][1][0]);
this.errors.push(errorLog[i][1][0]);
}
}
});
How exactly can I achieve this with the current structure?
Id make a seperate js ( or ts ) file for talking to you API and put those calls into it, then import it as a 'itemService' or something
So from your "pauseTask" you could then do
if(this.isOpen) { itemService.pause(item) } else {itemService.resume(item)}
or you can trigger it on a watch for isOpen
watch: {
isOpen: function (val) {
if(val) { itemService.pause(item) } else {itemService.resume(item)}
}
the advantage of the watch is that anything that manipulates isOpen will trigger the calls.
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Closed 5 years ago.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Improve this question
So I have this object, Looks like this:
I am trying to access the when_is_meeting property. I dont have any issue accessing any other property in this object. But if I try to access when_is_meeting I get an undefined as seen above next to "WHEN IS IT"
This is what the code looks like....
var listOfObjects = [];
for (var modelData in responseData) {
listOfObjects.push(service.create(name, responseData[modelData]));
console.log('RESP', response.data.content[modelData]);
console.log('WHEN IS IT!!!',response.data.content[modelData].when_is_meeting);
}
Does anyone know what the heck is going on here? Did I spell something wrong? I've been over it 50 times. Must be something simple im overlooking.
----------------EDIT------HERE IS THE WHOLE SERVICE----------------------
service.fetch = function (name, id, options) {
options = options || {};
const ModelClass = service.models[name];
const baseUrl = ModelClass.getUrl(service.getBaseUrl(ModelClass), ModelClass.getModelName());
let url;
let paged = false;
let pageList = false;
if (id) {
if (id instanceof models.BaseModel) {
const BaseModelModelName = id.getModelName();
let baseModelWithId = ModelClass.getUrl(service.getBaseUrl(), BaseModelModelName);
url = [baseModelWithId, id.getId(), ModelClass.getModelName(), ''].join('/');
pageList = true;
} else {
url = [baseUrl, id].join('/');
}
} else {
pageList = true;
url = [baseUrl, ''].join('/');
}
if (options.path) {
url = url + options.path;
delete options.path;
}
if (typeof options.paged === 'object') {
let currentPage = options.paged.page;
let pageSize = options.paged.page_size || 20;
paged = options.paged;
options.page = currentPage;
options.size = pageSize;
if (options.paged.sort) {
let sortParam = options.paged.sort;
options.sort = sortParam.param+','+sortParam.order;
}
delete options.paged;
}
return AuthRequestsService.load.then(function () {
return $http({
method: 'GET',
url: url,
headers: {
'Authorization': AuthRequestsService.getAuthorizationHeader(),
},
params: options,
json: true
});
}).then(function (response) {
console.log('RESPONSE',response);
for (let i = 0; i < response.data.content.length; i++){
if (response.data.content[i].activity_history_type == 3){
let builder = JSON.parse(response.data.content[i].relations);
let secondaryUrl = AppSettings.apiUrl + ['/advisor/meetings', builder.meeting].join('/');
$http({
url: secondaryUrl,
headers: {
'Authorization': AuthRequestsService.getAuthorizationHeader(),
},
method:'GET',
json:true,
}).then(function(res){
response.data.content[i].when_is_meeting = res.data.meeting_date;
console.log('WHEN IS',response.data.content[i].when_is_meeting); // <-- this works
})
}
}
if (!pageList) {
return service.create(name, response.data);
} else {
let responseData = response.data;
if (paged) {
responseData = response.data.content;
}
var listOfObjects = [];
for (var modelData in responseData) {
listOfObjects.push(service.create(name, responseData[modelData]));
console.log('RESP', response.data.content[modelData]);
listOfObjects[modelData].when_is_meeting = response.data.content[modelData].when_is_meeting;
listOfObjects[modelData].whatever = 44;
console.log('response.data.content[modelData].when_is_meeting',response.data.content[modelData].when_is_meeting);
console.log('listOfObjects[modelData].when_is_meeting', listOfObjects[modelData].when_is_meeting);
console.log('listOfObjects[modelData].whatever', listOfObjects[modelData].whatever);
console.log('Keys', Object.keys(response.data.content[modelData]));
// console.log('PRE IF', response.data.content[modelData].when_is_meeting);
// listOfObjects[modelData].when_is_meeting = response.data.content[modelData].when_is_meeting;
// console.log('IFFFFFFFFFFFFFFFFFFFFF', listOfObjects[modelData].when_is_meeting);
// console.log('IN FOR LOOP RESP', response.data.content[modelData].when_is_meeting);
// console.log('listOfObjects[modelData] PART 2', listOfObjects[modelData]);
function testForKey() {
if (typeof response.data.content[modelData].when_is_meeting !== "undefined") {
// when_is_meeting now exists! Do stuff with it here.
console.log("We now have the key:", response.data.content[modelData].when_is_meeting)
}
else {
// when_is_meeting is still missing. Try again in 200ms.
window.setTimeout(testForKey, 200);
console.log('TTTIIMMMEEEEOOOUUUTTT');
}
}
testForKey();
}
if (paged) {
console.log('#########################', listOfObjects);
return {
objects: listOfObjects,
totalPages: response.data.totalPages,
currentPage: response.data.number,
isMore: (!response.data.last),
totalElements: response.data.totalElements
};
} else {
return listOfObjects;
}
}
});
};
This problem occurs because when you expand the toggle to view an object the console show what it has now, not what it had at the time it was logged.
This is explained well here: http://felix-kling.de/blog/2011/08/18/inspecting-variables-in-javascript-consoles/
This scenario is not uncommon when you are working with Ajax. You are trying to access the result before everything has completed; ie, something else in your code is working on your response.data and adding in the missing key after your console.log statements.
This is confirmed by the logging statement suggested in the comments above by #Steven. Object.keys(response.data.content[modelData])) will show the keys available at the time the log statement is made and hence does not have the same problem as just logging the object itself to the console. The results from that confirm when_is_meeting is indeed missing at log-time.
To fix this properly, we need to see more of your code so we can work out why you are trying to access your result before other things finish using it.
Or you can do a crude workaround using timeouts to test the response.data.content[modelData] for the availability of when_is_meeting and only access it when it exists, like the example below. But this is not really recommended - much better to figure out where the problem lies in your response handling.
var listOfObjects = [];
for (var modelData in responseData) {
listOfObjects.push(service.create(name, responseData[modelData]));
// testForKey looks for the required key 'when_is_meeting' and if it does not
// exist it starts a timeout to test for it again in the future.
function testForKey() {
if (typeof response.data.content[modelData].when_is_meeting !== "undefined") {
// when_is_meeting now exists! Do stuff with it here.
console.log("We now have the key:", response.data.content[modelData].when_is_meeting)
}
else {
// when_is_meeting is still missing. Try again in 200ms.
window.setTimeout(testForKey, 200);
}
}
testForKey();
}
(I've not tested this code - it might have syntax errors)
Update - a fix for your supplied code
Your supplied code shows that as predicted the when_is_meeting key is being added to your result in a separate Ajax call. This call complete asynchronously and as such its result is not available to your log statements below it. This fragment shows one approach for fixing it. As before, I have not syntax-checked this code.
return AuthRequestsService.load.then(function () {
return $http({
method: 'GET',
url: url,
headers: {
'Authorization': AuthRequestsService.getAuthorizationHeader(),
},
params: options,
json: true
});
}).then(function (response) {
console.log('RESPONSE',response);
for (let i = 0; i < response.data.content.length; i++){
if (response.data.content[i].activity_history_type == 3){
let builder = JSON.parse(response.data.content[i].relations);
let secondaryUrl = AppSettings.apiUrl + ['/advisor/meetings', builder.meeting].join('/');
// Store the result of this $http call as a promise.
response.data.content[i].when_is_meeting_promise = $http({
url: secondaryUrl,
headers: {
'Authorization': AuthRequestsService.getAuthorizationHeader(),
},
method:'GET',
json:true,
})
// Remove your .then handler from here. It will be dealt with below.
//.then(function(res){
// response.data.content[i].when_is_meeting = res.data.meeting_date;
// console.log('WHEN IS',response.data.content[i].when_is_meeting); // <-- this works
//})
}
}
if (!pageList) {
return service.create(name, response.data);
} else {
let responseData = response.data;
if (paged) {
responseData = response.data.content;
}
var listOfObjects = [];
for (var modelData in responseData) {
// OK, now access the promise you stored above. This means you'll be sure you'll
// have the when_is_meeting key.
responseData[modelData].when_is_meeting_promise.then(function(when_is_meeting_result) {
// Now you can copy the neeting date into your responseData object.
responseData[modelData].when_is_meeting = when_is_meeting_result.data.meeting_date;
// Carry on....
listOfObjects.push(service.create(name, responseData[modelData]));
console.log('RESP', response.data.content[modelData]);
// This should now work...
console.log('response.data.content[modelData].when_is_meeting',response.data.content[modelData].when_is_meeting);
});
}
}
});
In my add-in I am making an HTTP request and receiving an output. I want to place that output into a binding and have it expand the binding if necessary because the user won't necessarily know how many rows x columns the output will be. How would I go about doing this? Currently I am binding to a range, but if that range does not match the size of the [[]] that I am providing, then the data is not displayed in the sheet. So, this ends up requiring the user to know the size of the output.
What I'm doing currently using Angular is as follows (the problem with this being that the output isn't always the same size as the Office.BindingType.Matrix that the user selected in the spreadsheet):
I create the binding to where the output should be placed as follows:
inputBindFromPrompt(parameterId: number): Promise<IOfficeResult> {
let bindType: Office.BindingType;
if(this.inputBindings[parameterId].type != 'data.frame' && this.inputBindings[parameterId].type != 'vector') {
bindType = Office.BindingType.Text;
} else {
bindType = Office.BindingType.Matrix;
}
return new Promise((resolve, reject) => {
this.workbook.bindings.addFromPromptAsync(bindType, { id: this.inputBindings[parameterId].name },
(addBindingResult: Office.AsyncResult) => {
if(addBindingResult.status === Office.AsyncResultStatus.Failed) {
reject({
error: 'Unable to bind to workbook. Error: ' + addBindingResult.error.message
});
} else {
this.inputBindings[parameterId].binding = addBindingResult.value;
resolve({
success: 'Created binding ' + addBindingResult.value.type + ' on ' + addBindingResult.value.id
});
}
})
})
}
Then when the user submits via a button, the inputs are passed to a HTTP request service which then receives an output that I process into an array of arrays so that it can go into an Office.BindingType.Matrix:
this.isBusy = true;
this.feedback = 'submitted';
// Grab the values from the form
// Send as a POST and receive an output
// Put the output in the Excel sheet
this.webServicesService.postWebServices(this.service, this.inputParameters)
.subscribe(
(data: any) => {
// Correctly received data
// Access the data by name while looping through output parameters
this.error = false;
this.feedback = 'received data';
let i = 0;
this.outputParameters.forEach(element => {
// temporary name to identify the parameter
let name = element.name;
// Set the data value in the parameter
if(element.type == 'data.frame') {
let parameter = data[name];
this.feedback = parameter;
let excelData = [];
for(var key in parameter) {
if(parameter.hasOwnProperty(key)) {
var val = parameter[key];
excelData.push(val);
}
}
element.value = excelData;
}
else {
element.value = data[name];
}
// Set value in the form
let param = (<FormArray>this.serviceForm.controls['outputParameters']).at(i);
param.patchValue({
value: element.value
});
// Set value in the spreadsheet
this.excelService.outputSetText(i, element.value)
.then((result: IOfficeResult) => {
this.onResult(result);
i++;
});
}, (result: IOfficeResult) => {
this.onResult(result);
});
},
(error) => {
if(error.status == 400 || error.status == 401) {
// Return user to authentication page
this.authService.logout();
this.router.navigate(['/']);
} else {
// Tell user to try again
this.error = true;
}
}
);
The line above that is setting the value to the Office.Matrix.Binding is this.excelService.outputSetText(i, element.value), which calls this method in the Excel Service:
outputSetText(parameterId: number, data: any): Promise<IOfficeResult> {
return new Promise((resolve, reject) => {
if(this.outputBindings[parameterId].binding) {
this.outputBindings[parameterId].binding.setDataAsync(data, function (result: Office.AsyncResult) {
if(result.status == Office.AsyncResultStatus.Failed) {
reject({ error: 'Failed to set value. Error: ' + result.error.message });
} else {
let test: Office.Binding;
resolve({
success: 'successfully set value'
});
}
})
} else {
reject({
error: 'binding has not been created. bindFromPrompt must be called'
});
}
})
}
It's essentially using addFromPromptAsync() to set an output spot for the HTTP request. Then the user submits which sends the request, receives the data back and processes it into an array of arrays [[]] so that it can be the correct data format for Office.BindingType.Matrix. However, unless this is the same number of rows and columns as the binding originally selected, it won't display in the sheet. So, is there a binding type that will dynamically grow based on the data I give it? Or would I just need to release the current binding and make a new binding according to the size of the HTTP response data?
So long as you're using the "shared" (Office 2013) APIs, you will have this issue.
However, in the host-specific (2016+) APIs, you can easily solve the problem by resizing the range to suit your needs. Or more precisely, getting the binding, then asking for its range, then getting just the first (top-left) cell, and then resizing it:
await Excel.run(async (context) => {
let values = [
["", "Price"],
["Apple", 0.99],
["Orange", 1.59],
];
let firstCell = context.workbook.bindings.getItem("TestBinding").getRange().getCell(0, 0);
let fullRange = firstCell.getResizedRange(
values.length - 1, values[0].length - 1);
fullRange.values = values;
await context.sync();
});
You can try this snippet live in literally five clicks in the new Script Lab (https://aka.ms/getscriptlab). Simply install the Script Lab add-in (free), then choose "Import" in the navigation menu, and use the following GIST URL: https://gist.github.com/Zlatkovsky/5a2fc743bc9c8556d3eb3234e287d7f3. See more info about importing snippets to Script Lab.
Requirements when clicking the Qualify button in the Lead entity form:
Do not create an Opportunity
Retain original CRM qualify-lead JavaScript
Detect duplicates and show duplicate detection form for leads
Redirect to contact, either merged or created version, when done
The easiest approach is to create a plugin running on Pre-Validation for message "QualifyLead". In this plugin you simply have to set CreateOpportunity input property to false. So it would look like:
public void Execute(IServiceProvider serviceProvider)
{
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
context.InputParameters["CreateOpportunity"] = false;
}
Or you can go with more fancy way:
public void Execute(IServiceProvider serviceProvider)
{
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
var qualifyRequest = new QualifyLeadRequest();
qualifyRequest.Parameters = context.InputParameters;
qualifyRequest.CreateOpportunity = false;
}
Remember that it should be Pre-Validation to work correctly. Doing it like that allows you to remain with existing "Qualify" button, without any JavaScript modifications.
So Pawel Gradecki already posted how to prevent CRM from creating an Opportunity when a Lead is qualified. The tricky part is to make the UI/client refresh or redirect to the contact, as CRM does nothing if no Opportunity is created.
Before we begin, Pawel pointed out that
some code is not supported, so be careful during upgrades
I don't have experience with any other versions than CRM 2015, but he writes that there are better ways to do this in CRM 2016, so upgrade if you can. This is a fix that's easy to implement now and easy to remove after you've upgraded.
Add a JavaScript-resource and register it in the Lead form's OnSave event. The code below is in TypeScript. TypeScript-output (js-version) is at the end of this answer.
function OnSave(executionContext: ExecutionContext | undefined) {
let eventArgs = executionContext && executionContext.getEventArgs()
if (!eventArgs || eventArgs.isDefaultPrevented() || eventArgs.getSaveMode() !== Xrm.SaveMode.qualify)
return
// Override the callback that's executed when the duplicate detection form is closed after selecting which contact to merge with.
// This callback is not executed if the form is cancelled.
let originalCallback = Mscrm.LeadCommandActions.performActionAfterHandleLeadDuplication
Mscrm.LeadCommandActions.performActionAfterHandleLeadDuplication = (returnValue) => {
originalCallback(returnValue)
RedirectToContact()
}
// Because Opportunities isn't created, and CRM only redirects if an opportunity is created upon lead qualification,
// we have to write custom code to redirect to the contact instead
RedirectToContact()
}
// CRM doesn't tell us when the contact is created, since its qualifyLead callback does nothing unless it finds an opportunity to redirect to.
// This function tries to redirect whenever the contact is created
function RedirectToContact(retryCount = 0) {
if (retryCount === 10)
return Xrm.Utility.alertDialog("Could not redirect you to the contact. Perhaps something went wrong while CRM tried to create it. Please try again or contact the nerds in the IT department.")
setTimeout(() => {
if ($("iframe[src*=dup_warning]", parent.document).length)
return // Return if the duplicate detection form is visible. This function is called again when it's closed
let leadId = Xrm.Page.data.entity.getId()
$.getJSON(Xrm.Page.context.getClientUrl() + `/XRMServices/2011/OrganizationData.svc/LeadSet(guid'${leadId}')?$select=ParentContactId`)
.then(r => {
if (!r.d.ParentContactId.Id)
return RedirectToContact(retryCount + 1)
Xrm.Utility.openEntityForm("contact", r.d.ParentContactId.Id)
})
.fail((_, __, err) => Xrm.Utility.alertDialog(`Something went wrong. Please try again or contact the IT-department.\n\nGuru meditation:\n${err}`))
}, 1000)
}
TypeScript definitions:
declare var Mscrm: Mscrm
interface Mscrm {
LeadCommandActions: LeadCommandActions
}
interface LeadCommandActions {
performActionAfterHandleLeadDuplication: { (returnValue: any): void }
}
declare var Xrm: Xrm
interface Xrm {
Page: Page
SaveMode: typeof SaveModeEnum
Utility: Utility
}
interface Utility {
alertDialog(message: string): void
openEntityForm(name: string, id?: string): Object
}
interface ExecutionContext {
getEventArgs(): SaveEventArgs
}
interface SaveEventArgs {
getSaveMode(): SaveModeEnum
isDefaultPrevented(): boolean
}
interface Page {
context: Context
data: Data
}
interface Context {
getClientUrl(): string
}
interface Data {
entity: Entity
}
interface Entity {
getId(): string
}
declare enum SaveModeEnum {
qualify
}
TypeScript-output:
function OnSave(executionContext) {
var eventArgs = executionContext && executionContext.getEventArgs();
if (!eventArgs || eventArgs.isDefaultPrevented() || eventArgs.getSaveMode() !== Xrm.SaveMode.qualify)
return;
var originalCallback = Mscrm.LeadCommandActions.performActionAfterHandleLeadDuplication;
Mscrm.LeadCommandActions.performActionAfterHandleLeadDuplication = function (returnValue) {
originalCallback(returnValue);
RedirectToContact();
};
RedirectToContact();
}
function RedirectToContact(retryCount) {
if (retryCount === void 0) { retryCount = 0; }
if (retryCount === 10)
return Xrm.Utility.alertDialog("Could not redirect you to the contact. Perhaps something went wrong while CRM tried to create it. Please try again or contact the nerds in the IT department.");
setTimeout(function () {
if ($("iframe[src*=dup_warning]", parent.document).length)
return;
var leadId = Xrm.Page.data.entity.getId();
$.getJSON(Xrm.Page.context.getClientUrl() + ("/XRMServices/2011/OrganizationData.svc/LeadSet(guid'" + leadId + "')?$select=ParentContactId"))
.then(function (r) {
if (!r.d.ParentContactId.Id)
return RedirectToContact(retryCount + 1);
Xrm.Utility.openEntityForm("contact", r.d.ParentContactId.Id);
})
.fail(function (_, __, err) { return Xrm.Utility.alertDialog("Something went wrong. Please try again or contact the IT-department.\n\nGuru meditation:\n" + err); });
}, 1000);
}
There is a fully functional and supported solution posted over at our Thrives blog: https://www.thrives.be/dynamics-crm/functional/lead-qualification-well-skip-that-opportunity.
Basically we combine the plugin modification as mentioned by Pawel with a Client Side redirect (using only supported JavaScript) afterwards:
function RefreshOnQualify(eventContext) {
if (eventContext != null && eventContext.getEventArgs() != null) {
if (eventContext.getEventArgs().getSaveMode() == 16) {
setTimeout(function () {
Xrm.Page.data.refresh(false).then(function () {
var contactId = Xrm.Page.getAttribute("parentcontactid").getValue();
if (contactId != null && contactId.length > 0) {
Xrm.Utility.openEntityForm(contactId[0].entityType, contactId[0].id)
}
}, function (error) { console.log(error) });;
}, 1500);
}
}
}