How to pass variables from xs javascript to ABAP function - javascript

I have been give the task of passing ABAP to xs Javascript.
This will use and ODATA service where a POST request with body.data will have an object with headers and an array of objects that I will be able to use in the following format:
{
"IvCompCode":"1710",
"IvDocType":"NB",
"IvPurchOrg":"1710",
"IvPurGroup":"002",
"IvVendor":"17386001",
"ItemsSet":[
{
"Ebelp":"00010",
"Matnr":"MZ-RM-M500-09",
"Werks":"1710",
"Lgort":"171S",
"Menge":"5.000",
"Netpr":"100.000"
},
{
"Ebelp":"00020",
"Matnr":"MZ-RM-M500-07",
"Werks":"1710",
"Lgort":"171S",
"Menge":"4.000",
"Netpr":"105.000"
}
]
}
I can call the ABAP function inside the javascript function, however, I have no idea how to pass the variables from the ODATA service and then simply put them inside the ABAP function, while also keeping them in the javascript function so they can be used in another logic. Here's what I have right now:
<script language="JavaScript">
function callABAPMethod()
{
<%
DATA: po_header LIKE bapimepoheader,
poheaderx LIKE bapimepoheaderx,
tab_poitem TYPE STANDARD TABLE OF bapimepoitem,
tab_poitemx TYPE STANDARD TABLE OF bapimepoitemx,
tab_poitem_struct LIKE bapimepoitem,
tab_poitemx_struct LIKE bapimepoitemx,
ebelp_num(5) TYPE n,
ebelp_char(5) TYPE c.
po_header-comp_code = iv_comp_code.
po_header-doc_type = iv_doc_type.
po_header-vendor = iv_vendor.
po_header-purch_org = iv_purch_org.
po_header-pur_group = iv_pur_group.
poheaderx-comp_code = 'X'.
poheaderx-doc_type = 'X'.
poheaderx-vendor = 'X'.
poheaderx-purch_org = 'X'.
poheaderx-pur_group = 'X'.
LOOP AT zpoitems INTO DATA(item).
CLEAR: tab_poitem_struct, tab_poitemx_struct.
tab_poitem_struct-po_item = item-ebelp.
tab_poitem_struct-material = item-matnr.
tab_poitem_struct-plant = item-werks.
tab_poitem_struct-stge_loc = item-lgort.
tab_poitem_struct-quantity = item-menge.
tab_poitem_struct-net_price = item-netpr.
tab_poitemx_struct-po_item = item-ebelp.
tab_poitemx_struct-po_itemx = 'X'.
tab_poitemx_struct-material = 'X'.
tab_poitemx_struct-plant = 'X'.
tab_poitemx_struct-stge_loc = 'X'.
tab_poitemx_struct-quantity = 'X'.
tab_poitemx_struct-net_price = 'X'.
APPEND tab_poitem_struct TO tab_poitem.
APPEND tab_poitemx_struct TO tab_poitemx.
ENDLOOP.
IF iv_update EQ 'X'.
IF iv_po_number IS NOT INITIAL.
CALL FUNCTION 'BAPI_PO_CHANGE'
EXPORTING
purchaseorder = iv_po_number
poheader = po_header
poheaderx = poheaderx
TABLES
return = return
poitem = tab_poitem
poitemx = tab_poitemx.
IF sy-subrc = 0.
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT' .
ENDIF.
ENDIF.
ELSE.
CALL FUNCTION 'BAPI_PO_CREATE1'
EXPORTING
poheader = po_header
poheaderx = poheaderx
IMPORTING
exppurchaseorder = po_numer
TABLES
return = et_return
poitem = tab_poitem
poitemx = tab_poitemx.
IF sy-subrc = 0.
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'.
ENDIF.
ENDIF.
%>
}
</script>

You can create custom oData service for cathing ABAP codes in backend which are sending from js. Use SEGW t-code for creating custom service and implement your own ABAP code for catching request. Some of sample blog here.
On backend side you can create dynamic program in run time on ABAP side but generally not approved by system admins. Your request may be catched by code scan tools. It has risk because of, someone manipulate your code and change your codes to access system without privileges.
Check the GENERATE SUBROUTINE POOL statement for running dynamic ABAP code in runtime.

Related

addPreSearch filter not applying

I am trying to use the addPreSearch function to add a custom filter to a lookup field, but the function does not seem to execute fully before the results of the lookup are displayed. The code for this looks something like this:
function onFieldChange(executionContext) {
var formContext = executionContext.getFormContext();
formContext.getControl("test_code").removePreSearch(testFunctionFilter);
formContext.getControl("test_code").addPreSearch(testFunctionFilter);
}
function testFunctionFilter(executionContext) {
var formContext = executionContext.getFormContext();
var record1 = formContext.getAttribute("test_record1_link").getValue(); //get linked record
var record1FullId, record1Id, stringRecordId, idLength, record1Guid = "0";
if (record1 != null) {
record1Id = record1[0].id;
record1Id = record1FullId.slice(1, -1);
stringRecordId = record1FullId.toString();
idLength = stringRecordId.length;
//Guid when retrieved from tablet does not have parenthesis on each end
if (idLength == 36) {
record1Guid = record1FullId;
} else {
record1Guid = recordId;
}
}
var fieldValue;
Xrm.WebApi.retrieveRecord("test_record1", record1Guid, "?$select=test_field1")
.then(function(result1) {
fieldValue = result1.test_field;
var options = generateOptions(executionContext, fieldValue); //creates option string using retrieved fieldValue
Xrm.WebApi.retrieveMultipleRecords("test_record2", options)
.then(function(result) {
var codes = getCodes(result2, fieldValue);
filter = generateFilter(codes, record1Guid); //creates custom filter using provided parameters
console.log(filter); //displays filter correctly
formContext.getControl("test_codelookup").addCustomFilter(filter, "test_coderecord"); //not working?
});
});
}
The filter is generated correctly using the functions used above whose definitions aren't shown. That isn't the issue. I've tried creating a separate test function where I hard coded one of the filters that the function above generated, and the lookup displayed the correct results. The testFunctionFilter should run to completion before the results of the lookup are displayed, correct? Because the filter is logged to the console after the results of the lookup appear. Are the nested asynchronous Xrm.WebApi calls somehow causing the issue? I'm not quite sure what is wrong. Please advise.
You are right. Xrm.WebApi calls are always Asynchronous, which is unusable in this case of adding dynamic filter using addCustomFilter.
You have to use XMLHttpRequest and make that call as Synchronous by setting third parameter as false like below:
var req = new XMLHttpRequest();
req.open("GET", Xrm.Utility.getGlobalContext().getClientUrl() +
"/api/data/v9.0/test_record1?$select=test_field1", false);
In order to work around the async delay, I think you're going to have to reorganise your code:
Add a form OnLoad event and execute the query to retrieve test_field1 and cache the results in a parameter
In the OnChange event, remove the presearch filter, re-execute the query to retrieve test_field1 and update the same parameter (from onload)
In testFunctionFilter use the cached results rather than building the presearch filter from scratch

Access Firebase database

I have a project with Firebase. I want to have access to the attribute "text" of my database with a cloud function as soon as there is a recent branch added to my database.
I am not get used to using their database.
My code in NodeJs is bellow :
exports.messageAnswer = functions.database.ref('/messages')
.onWrite(event => {
const snapshot = event.data;
const val = snapshot.val();
var textMsg = val.text;
var regex = "(bot)";
//var database = firebase.database();
if(!textMsg.match(regex) && textMsg.length > 0){
var id = new Date().getTime().toString();
var object = {
text : "I am the Cloud bot #" + id,
};
admin.database().ref('/messages').push(object);
}
return 0;
});
My problem is the fact that there is a "PUSH ID" : I don't know how to contourne this unique string to get the value of the attribute "text".
You can use wildcards to get the text attribute some something like this:
exports.messageAnswer = functions.database.ref('/messages/{pushid}')
.onWrite(event => {
for more info check this: https://firebase.google.com/docs/database/extend-with-functions
You can specify a path component as a wildcard by surrounding it with curly brackets; ref('foo/{bar}') matches any child of /foo. The values of these wildcard path components are available within the event.params object of your function. In this example, the value is available as event.params.bar
Your current code is being triggered whenever any data under /messages changes. From your description it seems you only want to be triggered when a new message is added. You do that by using onCreate instead of onWrite. In addition you'll want to listen for changes to a specific message, instead of to the entire /messages node:
exports.messageAnswer = functions.database.ref('/messages/{messageId}')
.onCreate(event => {
Also see Doug's blog post on these triggers: https://firebase.googleblog.com/2017/07/cloud-functions-realtime-database.html

JS & SAPUI 5 : wait for 2 asynchronous functions WITHOUT setTimeout

Here is the code I use in SAPUI5 to get data from an odata service :
var sServiceUrl = "http://localhost:8080/PlanningV0_2/proxy/sap/opu/odata/sap/ZPM_OM_WORKMANAGER_SRV";
var oOdataModel = new sap.ui.model.odata.v2.ODataModel(sServiceUrl);
var oView = this.getView();
var oEmployeeModel = new JSONModel();
var oOperationModel = new JSONModel();
var aEmployees = [];
var aOperations = [];
//set odata models
var readEmp = oOdataModel.read("/EmployeeSet",{
success : function(oData,response){
oEmployeeModel.setData(oData);
oView.setModel(oEmployeeModel,"EmployeeModel");
aEmployees = oEmployeeModel.getData().results;
},
error : function(oError){
console.log(oError);
}
});
var aFilters = [new Filter("PersNo",FilterOperator.NE,"00000000")];
var readOp = oOdataModel.read("/OrderOperationSet",{
filters : aFilters,
success : function(oData,response){
oOperationModel.setData(oData);
oView.setModel(oOperationModel,"OperationModel");
aOperations = oOperationModel.getData().results;
},
error : function(oError){
console.log(oError);
}
});
After those read functions I want to reuse the arrays aEmployees and aOperations to build a custom model and bind it to my view. The problem is : these functions are asynchrone. If I put a console.log(aEmployees) right after the last line, it displays an empty array.
How can I wait for the end of the two async functions to use the arrays ?
I looked for something like sleep() or wait() to wait for the end of async functions, but it seems it doesn't exist for JavaScript.
EDIT : PRECISION I ask because I prefer to avoid setTimeout, because I don't want to put the treatment code into a callback, I find it awfuly readable, I want to access to 'this' easily if needed, and I don't want to use a hardcoded waiting time.
EDIT : ANSWER I used the attachBatchRequestCompleted, according to #Qualiture advises, to build the arrays :
oOdataModel.attachBatchRequestCompleted(function(){
var aEmp = oView.getModel("EmployeeModel").getData().results;
var aOp = oView.getModel("OperationModel").getData().results;
});
It doesn't matter how many requests are bound to the odata model, this event handler will catch them all in the end. Obviously, it is a callback, but I figured out that it is impossible to deal without. The point is that I don't have to hardcode a waiting time.
Use the ODataModel's attachRequestCompleted event handler:
yourModel.attachRequestCompleted(function(oEvent){
var oModel = oEvent.getSource();
//etc
});
Do this for both models, and once both events are catched, you can do your magic building your custom model structure

Local storage saving multiples of the same items

I'm quite new to using storage settings in HTML/JavaScript. I'm building a hybrid app which is a not taking app on mobile using Phonegap. I want the user to type in a note name, then the note itself, and be able to save both by placing them into a jquery mobile list and putting them back on the home screen. My problem is that I can only save one note at a time. If I try to save another one, it just overwrites the previous one. How would I go about fixing it? Also, when I try refresh the browser the note disappears. Is this normal?
Please and thank you.
Here is the saving function I used:
function storeData() {
var i;
for (i=0; i<999; i++) {
var fname = document.getElementById('fname').value;
var wtf = document.getElementById('wtf').value;
localStorage.setItem('fname', fname);
localStorage.setItem('wtf', wtf);
}
var newEl = "<li><a href='#' id='savedNote'onclick='loadData'></a></li>"
document.getElementById("savedNote").innerHTML = localStorage.getItem("fname");
//try to create a new list element in main menu for this item being stored in
// and add an onclick load function for that
};
function loadData() {
var x;
for (x=0; x<999; x++) {
document.getElementById("fname").innerHTML = localStorage.getItem('fname', fnamei);
document.getElementById("wtf").innerHTML = localStorage.getItem('wtf', wtfi);
}
};
I'm not sure why you're using a loop for your functions. The storeData function looks 999 times for the value of #fname and #wtf, and save this 999x times in localStorage.fname and localStorage.wtf
This makes absolut no sense. Same Problem with your loadData function.
A nice way to save more then one string to the localStorage, is to create a javascript object, stringify it and then save it to the localStorage.
You only need to load the data from the localStorage, if you (re)load the page. But you need to save it to the localStorage, every time something changed, to be sure that the data in the localStorage is always up to date.
For display and manipulation on the page, you use the javascript object. in my example "myData". If you change something, you update your javascript object and then save it to the localStorage.
a side note. to be sure that the user don't overwrite something with a
identical name, you should use unique ids. like i did with the timestamp.
var postID = new Date().getTime();
Here a little example to show you a possible way. It's hard to code something functionally without your html code.
// Creating a object for all Data
var myData = {};
// Fill the Object with data if there is something at the LocalStorage
if (localStorage.myData){
loadDataFromLocalStorage();
}
function createNewPost(){
// Create a ID for the Post
var postID = new Date().getTime();
// Create a Object inside the main object, for the new Post
myData[postID] = {};
// Fill the Object with the data
myData[postID].fname = document.getElementById('fname').value;
myData[postID].wtf = document.getElementById('wtf').value;
// Save it to the LocalStorage
saveDataToLocalStorage();
// Display the Listitem. with the right postID
}
function loadPost (postID){
var singlePost = myData[postID];
// Display it
}
// A Helper Function that turns the myData Object into a String and save it to the Localstorage
function saveDataToLocalStorage(){
localStorage.myData = JSON.stringify(myData);
}
// A Helper Function that turns the string from the LocalStorage into a javascript object
function loadDataFromLocalStorage(){
myData = JSON.parse(localStorage.myData);
}
// Creating a object for all Data
var myData = {};
// Fill the Object with data if there is something at the LocalStorage
if (localStorage.myData){
loadDataFromLocalStorage();
}
function createNewPost(){
// Create a ID for the Post
var postID = new Date().getTime();
// Create a Object inside the main object, for the new Post
myData[postID] = {};
// Fill the Object with the data
myData[postID].fname = document.getElementById('fname').value;
myData[postID].wtf = document.getElementById('wtf').value;
// Save it to the LocalStorage
saveDataToLocalStorage();
// Display the Listitem. with the right postID
}
function loadPost (postID){
var singlePost = myData[postID];
// Display it
}
// A Helper Function that turns the myData Object into a String and save it to the Localstorage
function saveDataToLocalStorage(){
localStorage.myData = JSON.stringify(myData);
}
// A Helper Function that turns the string from the LocalStorage into a javascript object
function loadDataFromLocalStorage(){
myData = JSON.parse(localStorage.myData);
}
Store an array.
var arrayX = [];
arrayX.push(valueY);
localStorage.setItem('localSaveArray', arrayX);

How to return data from CRM 2011 javascript odata function?

This seems silly that I haven't been able to accomplish this. I'm using the common code found on the web to do an odata query. The problem is the results stay in getFieldData(retrieveReq) routine. I don't want to immediately set a field on the current form. How can I get my values out of it so the data can be used in other javascript functions? Global variable would be good but nothing I've tried has worked. The below code displays "x".
var var1 = "x"; odataquery(); alert(var1);
The example given here has two alerts that display the data. How can Id and Name get outside of that function to be useful?
Edit1: Below is the main part of the routine that calls getFieldData(this). I want to use OwnerBUID and OwnerBUName in other javascript functions.
var retrieveReq = new XMLHttpRequest();
retrieveReq.open("GET", odataSelect, false);
retrieveReq.setRequestHeader("Accept", "application/json");
retrieveReq.setRequestHeader("Content-Type", "application/json; charset=utf-8");
retrieveReq.onreadystatechange = function () {
getFieldData(this);
};
retrieveReq.send();
function getFieldData(retrieveReq) {
if (retrieveReq.readyState == 4 && retrieveReq.status == 200) {
// 4=request complete, 200=OK
var retrieved = this.parent.JSON.parse(retrieveReq.responseText).d;
var retrievedValue = retrieved.results[0].BusinessUnitId;
OwnerBUID = retrievedValue.Id;
OwnerBUName = retrievedValue.Name;
}
}
I guess you want to put the data as the return value of a javascript function. You could do this:
var returnedData = function getFieldData(retrieveReq)
{
...
return data;
}
BTW, you could consider to use JayData, Breeze and Datajs sources code packages in your client application. They implement the low level APIs for consuming an odata service using javascript.

Categories