request.object.id not returning in afterSave() in Cloud Code - parse-platform

Parse.Cloud.afterSave(function(request) {
var type = request.object.get("type");
switch (type) {
case 'inspiration':
var query = new Parse.Query("Inspiration");
break;
case 'event':
var query = new Parse.Query("Event");
break;
case 'idea':
var query = new Parse.Query("Idea");
break;
case 'comment':
break;
default:
return;
}
if (query) {
query.equalTo("shares", request.object.id);
query.first({
success: function(result) {
result.increment("sharesCount");
result.save();
},
error: function(error) {
throw "Could not save share count: " + error.message;
}
});
}
});
For some reason request.object.id is not returning the object id from the newly created record. I've tested this code out throughly and have isolated it down to the request.object.id variable. I've even successfully ran it with using a pre-existing object ID and it worked fine. Am I using the wrong variable for the object ID?
Thanks in advanced for any help!

Had this exact problem a few weeks ago.
It turned out to be a bug in Parse's newest Javascript SDK. Please have a look at your CloudCode folder - it should contain a global.json file where you can specify the JavaScript SDK version. By default, it states "latest", change it to "1.4.2" and upload your CloudCode folder again.
In case the global.json file is missing in your cloud code folder, please have a look at this thread, where I described how to create it manually.

Thanks for the reply. I found out another work around for this for version 1.6.5. I should probably also mention that my use case for this code is to increment a count column (comments count) when a new relation has been added to a particular record (post).
Instead of implementing an afterSave method on my relation class (comment), I instead implemented a beforeSave method on my class (Post) and used request.object.dirtyKeys() to get my modified columns. From there I check to see if my dirty key was comments and if it is I increment my count column. It works pretty well actually.

Related

How to get query sys_id of current.sys_id Service Portal (ServiceNow)

I have a question regarding a small issue that I'm having. I've created a widget that will live on the Service Portal to allow an admin to Accept or Reject requests.
The data for the widget is pulling from the Approvals (approval_approver) table. Under my GlideRecord, I have a query that checks for the state as requested. (Ex. addQuery('state', 'requested'))
To narrow down the search, I tried entering addQuery('sys_id', current.sys_id). When I use this query, my script breaks and I get an error on the Service Portal end.
Here's a sample of the GlideRecord script I've written to Accept.
[//Accept Request
if(input && input.action=="acceptApproval") {
var inRec1 = new GlideRecord('sysapproval_approver');
inRec1.addQuery('state', 'requested');
//inRec1.get('sys_id', current.sys_id);
inRec1.query();
if(inRec1.next()) {
inRec1.setValue('state', 'Approved');
inRec1.setValue('approver', gs.getUserID());
gs.addInfoMessage("Accept Approval Processed");
inRec1.update();
}
}][1]
I've research the web, tried using $sp.getParameter() as a work-around and no change.
I would really appreciate any help or insight on what I can do different to get script to work and filter the right records.
If I understand your question correctly, you are asking how to get the sysId of the sysapproval_approver record from the client-side in a widget.
Unless you have defined current elsewhere in your server script, current is undefined. Secondly, $sp.getParameter() is used to retrieve URL parameters. So unless you've included the sysId as a URL parameter, that will not get you what you are looking for.
One pattern that I've used is to pass an object to the client after the initial query that gets the list of requests.
When you're ready to send input to the server from the client, you can add relevant information to the input object. See the simplified example below. For the sake of brevity, the code below does not include error handling.
// Client-side function
approveRequest = function(sysId) {
$scope.server.get({
action: "requestApproval",
sysId: sysId
})
.then(function(response) {
console.log("Request approved");
});
};
// Server-side
var requestGr = new GlideRecord();
requestGr.addQuery("SOME_QUERY");
requestGr.query(); // Retrieve initial list of requests to display in the template
data.requests = []; // Add array of requests to data object to be passed to the client via the controller
while(requestsGr.next()) {
data.requests.push({
"number": requestsGr.getValue("number");
"state" : requestsGr.getValue("state");
"sysId" : requestsGr.getValue("sys_id");
});
}
if(input && input.action=="acceptApproval") {
var sysapprovalGr = new GlideRecord('sysapproval_approver');
if(sysapprovalGr.get(input.sysId)) {
sysapprovalGr.setValue('state', 'Approved');
sysapprovalGr.setValue('approver', gs.getUserID());
sysapprovalGr.update();
gs.addInfoMessage("Accept Approval Processed");
}
...

Parse Query using Geoquery

I'm running queries on some objects with linked geoquery data.
However, none of the queries work and return the following error
location object expected, location array not in correct format
Console logs show that
gp is definitely an object, is definitely a Parse Geopoint and has correct coordinates.
I'm stumped, any advice appreciated
let gp = null;
if (search.hasOwnProperty("geo")) {
console.log(`Geodata ${JSON.stringify(search.geo)}`);
gp = new Parse.GeoPoint({
latitude: search.geo.lat,
longitude: search.geo.long
});
console.log(`GP type ${typeof gp}, GP ${JSON.stringify(gp)}`);
let query = new Parse.Query("Place");
query.near("location", gp);
query.find({
success: (e) => console.log(`Geo near query ${JSON.stringify(e)}`),
error: (e) => console.log(`Geo near error ${JSON.stringify(e)}`)
});
};
I created something on my side that work with the following code:
let gp = new Parse.GeoPoint({
latitude: 37.337845,
longitude: -122.037687
});
// 1. first run this in order to save some location to your DB
var LocationTest = Parse.Object.extend("LocationTest");
var locationObject = new LocationTest();
locationObject.set("location",gp);
locationObject.set("name","location name test");
locationObject.save().then(function(result){
},function(error){
});
// 2. Uncomment and run this query after you save the object above
/*
var query = new Parse.Query("LocationTest");
query.near("location",gp);
query.find().then(function(results){
},function(error){
}); */
Please first run the first piece of code in order to save a location to your DB and then run the second part for query what you have saved.
The reason that you are getting this error can be because your location is not saved as an object but as an array on your database or maybe because your location field is not of type PFGeoPoint
what i suggest you is maybe to first run my code above and check if it works and then maybe try to change maybe the class name or the field name in your DB and save and query from a different field.
This code works for me on parse-server 2.2.17 (the latest version) so please also check that your parse-server is up to date by running npm install on your app
With some experimentation, found that geoqueries fail if any of the objects being queried don't have geodata associated with it.
Bit more severe than just not having geodata, even if it has a geodata property, and that property is empty, it will crash.
Solution is to check that latitude and longitude exist before querying that object

Getting Parse Objects via pointers

I am trying to get a Reservation object which contains a pointer to Restaurant.
In Parse Cloud code, i am able to get the restaurants objects associated with Reservations via query.include('Restaurant') in log just before response.success. However, the Restaurants reverted back to pointer when i receive the response on client app.
I tried reverted JSSDK version to 1.4.2 & 1.6.7 as suggested in some answers, but it doesn't work for me.
Parse.Cloud.define('getreservationsforuser', function(request, response) {
var user = request.user
console.log(user)
var query = new Parse.Query('Reservations')
query.equalTo('User', user)
query.include('Restaurant')
query.find({
success : function(results) {
console.log(JSON.stringify(results))
response.success(results)
},
error : function (error) {
response.error(error)
}
})
})
response :
..."restaurant":{"__type":"Pointer",
"className":"Restaurants",
"objectId":"kIIYe7Z0tD"},...
You can't directly send the pointer objects back from cloud code even though you have included it. You need to manually copy the content of that pointer object to a javascript object. Like below:
var restaurant = {}
restaurant["id"] = YOUR_POINTER_OBJECT.id;
restaurant["createdAt"] = YOUR_POINTER_OBJECT.createdAt;
restaurant["custom_field"] = YOUR_POINTER_OBJECT.get("custom_field");
ps: in your code you seem do nothing else other than directly send the response back. I think parse REST api might be a better choice in that case.
It turned out that my code implementation was correct.

Kendo grid batch editing - making a single call to save

With Kendo grid batch editing turned on, I know that you can hook into the create, update and destroy commands where Kendo will send 3 separate commands to the server when you click on Save Changes.
I was wondering if there was any way to send all three sets of updates as a single call to the server -like a transaction. Or even send each in a specified order, with a check for success before sending the next .
The only way I could come up with was to have a custom Save Changes implementation which ,when invoked, would lookup the grid datasource to find out all rows that have been added (isNew() for added rows), deleted (_destroyed for deleted rows), updated (isDirty for updated rows) and then craft my own call to a server endpoint using ajax using the identified datasets.
Telerik posted a work-around in their code library recently: http://www.kendoui.com/code-library/mvc/grid/save-all-changes-with-one-request.aspx. Unfortunately the work-around is rather bare-bones. It gives a good example of how to capture destroyed, dirty, and new records but finishes with some hand waving to handle any errors in the response and synchronizing the data source on success. Also note that there is no check to ensure there are destroyed, dirty, or new records before making the ajax request.
Here is the relevant code. Download the full example from the link above to see how the grid is setup and to ensure you have the latest version.
function sendData() {
var grid = $("#Grid").data("kendoGrid"),
parameterMap = grid.dataSource.transport.parameterMap;
//get the new and the updated records
var currentData = grid.dataSource.data();
var updatedRecords = [];
var newRecords = [];
for (var i = 0; i < currentData.length; i++) {
if (currentData[i].isNew()) {
//this record is new
newRecords.push(currentData[i].toJSON());
} else if(currentData[i].dirty) {
updatedRecords.push(currentData[i].toJSON());
}
}
//this records are deleted
var deletedRecords = [];
for (var i = 0; i < grid.dataSource._destroyed.length; i++) {
deletedRecords.push(grid.dataSource._destroyed[i].toJSON());
}
var data = {};
$.extend(data, parameterMap({ updated: updatedRecords }), parameterMap({ deleted: deletedRecords }), parameterMap({ new: newRecords }));
$.ajax({
url: "/Home/UpdateCreateDelete",
data: data,
type: "POST",
error: function () {
//Handle the server errors using the approach from the previous example
},
success: function () {
alert("update on server is completed");
grid.dataSource._destroyed = [];
//refresh the grid - optional
grid.dataSource.read();
}
})
}
Maybe you can enable the batch property of the Datasource
batch Boolean(default: false)
If set to true the data source will batch CRUD operation requests. For example updating two data items would cause one HTTP request instead of two. By default the data source makes a HTTP request for every CRUD operation.
Source : Datasource API
After six years we have an answer, check submit function to execute single request to save all changes: https://docs.telerik.com/kendo-ui/api/javascript/data/datasource/configuration/transport.submit

How to save a record and immediately use its GUID

I'm executing some javascript from a ribbon button and what I want to do is save the record that I am creating and then immediately use its GUID for some code a bit further on. Each time I try it the GUID is coming back null even though I'm requesting it after the record has been saved. If I try the button again after I've saved it then it works, but not as I'm saving it.
Is there a way to do this?
function RibbonButton_AddProduct()
{
//Save the Record
Xrm.Page.data.entity.save();
LoadProductCreate();
}
function LoadProductCreate()
{
var serverUrl;
var errorMessage = "Context to retrieve the Server URL is not available.";
if (typeof GetGlobalContext != "undefined"){
serverUrl = GetGlobalContext().getServerUrl();
} else {
if (typeof Xrm != "undefined"){
serverUrl = Xrm.Page.context.getServerUrl();
} else {
alert(errorMessage);
return;
}
}
if (serverUrl.match(/\/$/)){
serverUrl = serverUrl.substring(0, serverUrl.length - 1);
}
var recordId = Xrm.Page.data.entity.getId();
alert(recordId);
var url = serverUrl + "/main.aspx?etc=10030&extraqs=%3f_CreateFromId%3d%"+recordId
+"%257d%26_CreateFromType%3d10029%26etc%3d10030%26"
+"pagemode%3diframe%26preloadcache%3d1345465354543&pagetype=entityrecord";
window.open(url);
}
Here’s a different approach to solving this problem.
What you are trying to do is ‘working against the system’ - you are effectively making two save buttons. In the rest of Crm when the Id is required for a ribbon button the record must first be saved. E.g. you can’t use the dialog or workflow buttons on an unsaved record, you also can’t 'add new/existing' to an unsaved record.
So my solution would be to disable the button on unsaved forms, force the user to save the record manually and then allow them to use your button - this is the way Crm is meant to be used, and is the way the rest of Crm will work.
You should not work against the system, you should work with it, you have a product to customise and extend – not change.
If this doesn’t meet your requirement I would suggest uses Greg’s suggestion (1) of having flags, though it sounds a bit messy - but then this is a requirement that inherently is.
You could try one of two things:
Add a hidden boolean attribute to your form(e.g. "new_launchProductCreate"), set it in code prior to save and then read it onLoad.
Instead of setting the value prior to create (and therefore potentially commiting it to the database), you could create a plugin registered against the "Create" step of your record that injects a boolean value into the Entity.Attributes collection as the record is returned to the user. This would prevent the value persisting into the database and running every time your form loads.
You can instead use AJAX to reset the value as you launch your onLoad code so that it doesn't trigger on every form load
Assign the record guid manually, use AJAX to save your record, pop your new window using th enew guid and then reload your original form (so that the form is no longer in an "unsaved" state).
At the risk of being proven wrong as I cannot verify this right away... you will need to save and then reload the page.
The value stored in Xrm.Page.data.entity.getId() is set when the page is loaded/initialised and hence won't be updated when you access it after you have called Save().
It is also why it does work when you reload the page.
Perhaps you could call save and then reload the window adding a querystring variable of your own, to indicate that this event has just occurred?
e.g.
function DoSomething() {
//do your stuff
Xrm.Page.data.entity.save();
//something like - sure someone can do better!
window.location = window.location.href + '&foo=bar';
}
and then register something like this onFormLoad
function OnLoad() {
var queryStringParms = Xrm.Page.context.getQueryStringParameters();
//test to see if your query string param exists here
for (var i in queryStringParams) {
//if you find query string, do extra processing here
}
}

Resources