OpenUI5: Where is the data gone, loaded by sap.ui.model.odata.v2.ODataModel()? - odata

I have been googling and testing around for quite a few days with no success. If someone could give me a hint to point me into the right direction, then I would greatly appreciate that.
What is my target: to get some data from an OData service and show it in an UI5 oTable. Later on, I will have to do the CRUD-operations, but for now the aim is just to show the data successfully.
What I have got so far: I have an MVC project. I have created an OpenUI5Controller.cs, whose purpose is to represent an ODataService:
public class OpenUI5Controller : Controller
{
private MyEntities myEntities = new MyEntities();
public JsonResult GetAllContragents()
{
try
{
IQueryable<Contragent> contragents = myEntities.Contragent.OrderBy(x => x.Code);
// for now, take the first 100 only
contragents = contragents.Take(100);
var jsonData = new
{
rows = contragents.ToList()
//rows = "123"
};
var res = Json(jsonData, JsonRequestBehavior.AllowGet);
return res;
}
catch (Exception e)
{
LoggingService.WriteLog("Error in OpenUI5Controller.GetAllContragents()", e);
return null;
}
}
}
After that, I try to consume the data like this:
var oModel = new sap.ui.model.odata.v2.ODataModel('/OpenUI5/GetAllContragents', {
//maxDataServiceVersion: '2.0',
json: true,
skipMetadataAnnotationParsing: true,
// none of those callbacks is ever triggered, even the requestFailed...!?
requestSent: function (a, b, c) {
debugger;
var trgrwg = oModel.getData();
var vfewgvfewgv = oModel.getProperty('/');
var vfewgvfewgv2 = oModel.getProperty('/rows');
var vfewgvfewgv3 = oModel.getProperty('rows');
},
requestFailed: function (a, b, c) {
debugger;
var trgrwg = oModel.getData();
var vfewgvfewgv = oModel.getProperty('/');
var vfewgvfewgv2 = oModel.getProperty('/rows');
var vfewgvfewgv3 = oModel.getProperty('rows');
},
requestCompleted: function (a, b, c) {
debugger;
var trgrwg = oModel.getData();
var vfewgvfewgv = oModel.getProperty('/');
var vfewgvfewgv2 = oModel.getProperty('/rows');
var vfewgvfewgv3 = oModel.getProperty('rows');
},
batchRequestCompleted: function (a, b, c) {
debugger;
var trgrwg = oModel.getData();
var vfewgvfewgv = oModel.getProperty('/');
var vfewgvfewgv2 = oModel.getProperty('/rows');
var vfewgvfewgv3 = oModel.getProperty('rows');
},
useBatch : false
});
var trgrwg = oModel.getData();
var vfewgvfewgv = oModel.getProperty('/');
var vfewgvfewgv2 = oModel.getProperty('/rows');
var vfewgvfewgv3 = oModel.getProperty('rows');
var vfewgvfewgv4 = oModel.getProperty('contragents');
var vfewgvfewgv5 = oModel.getProperty('/contragents');
var vfewgvfewgv6 = oModel.getProperty('Contragents');
var vfewgvfewgv7 = oModel.getProperty('/Contragents');
debugger;
When stopping at this breakpoint, I see this in the Chrome Developer Tools:
Moving further, I came accross this and tried to read() those contragents:
oModel.read('/Contragents', {
success: function (event) {
debugger;
// event.root I guess should be the received data as jsonString
// As I guess, I will have to get it and give it to oModel.oData manually, or am I wrong? I guess this would cause update(), delete()... calls to fail later on. Anyways...
sap.ui.getCore().setModel(oModel); // https://archive.sap.com/discussions/thread/3746588 - seems not to work like this
if (withJsonModel !== true) { https://help.sap.com/saphelp_uiaddon20/helpdata/en/12/32241b99d7437ba3614698d53dfa4b/content.htm
oTable.setModel(oModel);
oTable.bindRows("/");
//oTable.bindRows("/rows");
oTable.placeAt('tblContragents', "only");
}
},
error: function (event) {
debugger;
}
});
Here, neither success, nor error callback gets ever triggered.
After this read() attempt, I have again:
var trgrwg = oModel.getData();
var vfewgvfewgv = oModel.getProperty('/');
var vfewgvfewgv2 = oModel.getProperty('/rows');
var vfewgvfewgv3 = oModel.getProperty('rows');
var vfewgvfewgv4 = oModel.getProperty('contragents');
var vfewgvfewgv5 = oModel.getProperty('/contragents');
var vfewgvfewgv6 = oModel.getProperty('Contragents');
var vfewgvfewgv7 = oModel.getProperty('/Contragents');
and I see this:
After that, I have oTable = new sap.ui.table.Table(...);
Finally, I try this:
sap.ui.getCore().setModel(oModel);
//oTable.setModel(oModel); // or maybe should be this one directly?
oTable.bindRows("/Contragents");
oTable.placeAt('tblContragents', "only");
As a result, I get an empty table.
I suppose I should tell you what I see in Fiddler as well: I see this request: 1 200 HTTP localhost:55714 /OpenUI5/GetAllContragents/$metadata
As a result in the TextView of the response section, I see the data as json, that I need to show in the table.
How to make it work and show the data in the table?
edit from 02.02.2017: I have 1% of progress. After some more and more testing and googling, I decided to try it like this.
First of all, some time ago (can't really remember when), after seeing a good example for OdataServices, I decided to create a dedicated controller like this:
public class ContragentsController : ODataController
{
private MyEntities db = new MyEntities();
//public System.Web.Mvc.JsonResult GetContragents()
public string GetContragents()
{
try
{
IQueryable<Contragent> contragents = db.Contragent.OrderBy(x => x.Code);
// for now, give me first 100
contragents = contragents.Take(100);
var jsSerializer = new System.Web.Script.Serialization.JavaScriptSerializer();
var rows = jsSerializer.Serialize(contragents.ToList());
//var jsonData = new
//{
// rows = model
//};
//var res = new System.Web.Mvc.JsonResult();
//res.Data = jsonData;
//res.JsonRequestBehavior = System.Web.Mvc.JsonRequestBehavior.AllowGet;
return rows;
}
catch (Exception e)
{
//...some errorLogging
return null;
}
}
After that, I changed the call to ODataModel() like this:
var oModel = new sap.ui.model.odata.v2.ODataModel(BASE_HREF + 'odata/', {
//maxDataServiceVersion: '2.0',
json: true,
skipMetadataAnnotationParsing: true,
// those callbacks still do not get called at all
requestSent: function (a, b, c) {
debugger;
var trgrwg = oModel.getData();
var vfewgvfewgv = oModel.getProperty('/');
var vfewgvfewgv2 = oModel.getProperty('/rows');
var vfewgvfewgv3 = oModel.getProperty('rows');
},
requestFailed: function (a, b, c) {
debugger;
var trgrwg = oModel.getData();
var vfewgvfewgv = oModel.getProperty('/');
var vfewgvfewgv2 = oModel.getProperty('/rows');
var vfewgvfewgv3 = oModel.getProperty('rows');
},
requestCompleted: function (a, b, c) {
debugger;
var trgrwg = oModel.getData();
var vfewgvfewgv = oModel.getProperty('/');
var vfewgvfewgv2 = oModel.getProperty('/rows');
var vfewgvfewgv3 = oModel.getProperty('rows');
},
batchRequestCompleted: function (a, b, c) {
debugger;
var trgrwg = oModel.getData();
var vfewgvfewgv = oModel.getProperty('/');
var vfewgvfewgv2 = oModel.getProperty('/rows');
var vfewgvfewgv3 = oModel.getProperty('rows');
},
useBatch : false
});
The whole drama is that SapUI5 runs a request for $metadata, before making the actual request to odata/Contragents and so on. As expected in this case, I see in Fiddler, that the server 404-s to that request: http://localhost:55714/odata/Contragents/$metadata and it doesn't reach the moment to make the request to Contragents. I know, that I will have to change the source of the library somewhere in order to prevent this $metadata request from running, which I don't want to do. So in Fiddler's Composer, I tried the following: localhost:55714/odata/$metadata and it returned some xml with the desired metadata (maybe indeed it is important, but I haven't understood yet why). Next step was to modify the call to sap.ui.model.odata.v2.ODataModel(); as shown above. The great thing after all that is the fact, that oModel.read('/Contragents', {...}); showed signs of life! Now, the success callback gets called and in event.root I get those contragents as json string. And then what? I tried oModel.setData(JSON.parse(event.root)); but it gave me this error:
Also, if I manage to get it working this way, I don't quite understand, will the CRUD methods of oModel work? oModel.setData()-ing is something familliar to me from the time when I was playing around with JSONModel():
oModel = new sap.ui.model.json.JSONModel();
oModel.setData(dataForGrid); // this works like a charm
But using JSONModel() means, that I cannot use the insert(), update()... methods of sap.ui.model.odata.v2.ODataModel(), because JSONModel(); does not provide us with them, which is normal, as long as I get dataForGrid via a standard $.ajax() call.
edit2: While debugging, I noticed, that the method ContragentsController.GetContragents(); gets called twice and in the console, I see this error message:
edit3: Following the good example for ODataService, I added a "selection" method to the controller like this:
// GET: odata/Contragents(5)
[EnableQuery]
public SingleResult<Contragent> GetContragents([FromODataUri] string key)
{
return SingleResult.Create(db.Contragent.Where(contragent => contragent.Code == key));
}
In Fiddler, it 404-s and does not get called at all no matter if i Compose it like this: http://localhost:55714/Contragents(2) or like this: http://localhost:55714/odata/Contragents(2)
Couple of minutes later, I've got couple of % progress. I looked even closer in the tutorial and I noticed something, which made me change the "selection" method to this:
// GET: odata/Contragents(5)
[EnableQuery]
public SingleResult<Contragent> Get([FromODataUri] string key)
{
return SingleResult.Create(db.Contragent.Where(contragent => contragent.Code == key));
}
Now, when I Compose this request: http://localhost:55714/odata/Contragents(2), it now does not 404, but does 406 Not Acceptable. I came across this error code some time ago and I didn't understand well what is the reason for it and how is it related to ODataServices.
I just reminded myself what does 406 mean and it turns out, that every method on the controller must return a json result as string. Because, SapUI5 sets Accept-Type to "application/json".

Related

indexeddb on IOS devices

I have a problem with an indexeddb query with index when running on IOS devices.
$.indexedDB(dbName).objectStore(tablename).index("INDICE").each(function(itemLocal) {
itemLocal.delete();
}, [VALORINDICE]).then(function() {
callback();
}, function() {
console.log("error");
});
The problem is if there is more than one record that matches the index, it does not eliminate them, it eliminates the first one and leaves. But if for example I put console.log (itemLocal) instead of itemLocal.delete() if it shows all those that match the index. Any suggestions of something that may be leaking?
I have tried with this code and I get the same error(code without api jquery)
var request = indexedDB.open(DATABASE_NAME);
request.onsuccess = function(event) {
var db = request.result;
var transaction = db.transaction(["TABLE"], "readwrite");
var table = transaction.objectStore("TABLE");
var index = table.index("INDEX");
var req = index.openCursor();
req.onsuccess = function() {
var cursor = req.result;
if (cursor) {
console.info(cursor.value);
cursor["delete"]();
cursor["continue"]();
}
};
req.onerror = function(e) {
console.error(e, req);
};
};
request.onerror = function(e) {
console.error(e, request);
};

How to import entities after save changes with breeze across two entity managers

I've implemented repository pattern with two entity managers,
mainManager is for read only and delete, and updateManager is used for edit and add new entities. I use createEmptyCopy() to create updateManager.
Before i update an entity i export the entity from mainManager and import into the updateManager, after the change i call to updateManager.saveChanges() method.
I've noticed that i get back the updated entities in the promise response. i wonder what is the best practice to import those entities back into the mainManager?
here is my code:
function ($q, $http, entityManagerFactory) {
var self = this;
self.mainManager = entityManagerFactory.newManager();
self.updateManager = entityManagerFactory.newManager();
self.saveChanges = function () {
return self.updateManager.saveChanges();
};
self.rejectChanges = function() {
self.updateManager.rejectChanges();
};
self.getDomains = function () {
self.mainManager.clear();
var query = new breeze.EntityQuery()
.from('Domains')
.orderBy('name');
return self.mainManager.executeQuery(query);
};
self.createEmptyDomain = function () {
var domain = self.updateManager.createEntity('Domain');
return domain;
};
self.editDomain = function(domain) {
var exported = self.mainManager.exportEntities([domain]);
return self.updateManager.importEntities(exported).entities[0];
}
self.addDomain = function (domain) {
self.updateManager.addEntity(domain);
return self.updateManager.saveChanges();
};
self.deleteDomain = function (domain) {
domain.entityAspect.setDeleted();
var deferred = $q.defer();
self.mainManager.saveChanges().then(
function(data) {
deferred.resolve(data);
},
function (reason) {
console.log(reason);
self.mainManager.rejectChanges();
deferred.reject(reason);
});
return deferred.promise;
};
}
Right now i'm calling mainManager.clear() and get the data again from the server as you can see above in getDomains function.
But i think this is too expansive, why call the server if i already have the updated entities from the saveChanges promise?
i've also tried to import those entities back to mainManager using:
mainManager.importEntities(data.entities, { mergeStrategy: breeze.MergeStrategy.OverwriteChanges });
but i get an internal null breeze exception:
TypeError: Cannot read property 'forEach' of undefined
at EntityManager.proto.importEntities (breeze.debug.js:13081)
at self.importEntities (domain-list.service.js:22)
at domain-list.controller.js:70
at processQueue (angular.js:13170)
at angular.js:13186
at Scope.promises.$get.Scope.$eval (angular.js:14383)
at Scope.promises.$get.Scope.$digest (angular.js:14199)
at Scope.promises.$get.Scope.$apply (angular.js:14488)
at done (angular.js:9646)
at completeRequest (angular.js:9836)
the error is from this line breeze.debug.js:13081
13080: var tempKeyMap = {};
13081: json.tempKeys.forEach(function (k) {
13082: var oldKey = EntityKey.fromJSON(k, that.metadataStore);
13083: // try to use oldKey if not already used in this keyGenerator. 13084: tempKeyMap[oldKey.toString()] = new EntityKey(oldKey.entityType,
13085: that.keyGenerator.generateTempKeyValue(oldKey.entityType, oldKey.values[0]));
13086: });
var exportData = updateManager.exportEntities(data.entities, false);
mainManager.importEntities(exportData,
{ mergeStrategy: breeze.MergeStrategy.OverwriteChanges });

Can't access to event posted values

i'm writing a little google apps script which allow to select several names among a list of developpers. But in my doPost(e) method, i can't access to the array of posted values (it's undefinded), event if i check all the checkboxes...
function onOpen() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
// add the new menu
var menuEntries = [];
menuEntries.push({name: "Edit page", functionName: "start"})
ss.addMenu("My tools", menuEntries);
}
function start() {
var app = UiApp.createApplication();
app.setTitle("Edit page");
var formPanel = app.createFormPanel();
var mainPanel = app.createVerticalPanel();
var gridPanel = app.createGrid(1, 2);
// developpers
var developperPanel = app.createVerticalPanel()
var developpers = [];
developpers.push( "John", "Peter", "Max", "Johnny" );
for (var i = 1; i < developpers.length; ++i) {
var checkbox = app.createCheckBox(developpers[i]).setName("dev"+i);
developperPanel.add(checkbox);
}
gridPanel.setWidget(0,0,app.createLabel("Developpers : "));
gridPanel.setWidget(0,1,developperPanel);
// submit button
mainPanel.add(gridPanel);
mainPanel.add(app.createSubmitButton().setText("OK"));
formPanel.add(mainPanel);
app.add(formPanel);
var ss = SpreadsheetApp.getActive();
ss.show(app);
}
function doPost(e) {
var app = UiApp.getActiveApplication();
app.add(app.createLabel(e.values[0])); // Here is the error.
return app;
}
In the exemple, the list is fixed but in my real script, i create the list thanks to the Spreadsheet.
Thanks
Max
The correct way to see the checkboxes values is using e.parameter[name], like you said yourself on a comment. Here is some code example:
function start() {
//...
var developpers = ["John", "Peter", "Max", "Johnny"];
//you were skiping the first developer
for (var i = 0; i < developpers.length; ++i) { //array starts on zero, not one
var checkbox = app.createCheckBox(developpers[i]).setName("dev"+i);
developperPanel.add(checkbox);
}
//...
}
function doPost(e) {
var app = UiApp.getActiveApplication();
var developpers = ["John", "Peter", "Max", "Johnny"]; //repeat it here or make it global, if it's static like this
var checked = [];
for( var i in developpers )
if( e.parameter['dev'+i] == 'true' )
checked.push(developpers[i]);
app.add(app.createLabel(checked));
return app;
}
You should use e.parameter.yourCheckBoxName
for example:
function doPost(e) {
var app = UiApp.getActiveApplication();
app.add(app.createLabel(e.parameter.dev1); // Access checkbox by its name
return app;
}
This would show status of check box "Peter", when it is checked. You can modify based on your need.

The document.body.innerHTML.replace() replaces the url in the address bar

I am trying to make an extension as part of which i want certain words in the web pages to be highlighted. The document.body.innerHTML.replace() replaces the url in the address bar as well. So the moment this code gets exwecuted the page doesnt get loaded properly..
Is there a way around this problem?
onPageLoad: function(aEvent) {
var doc = aEvent.originalTarget;
var str="the";
var regex;
var regex = new RegExp(str, "g");
doc.body.innerHTML = doc.body.innerHTML.replace(regex,'<b>'+str+'</b>');
}
The listener is registered as follows in a browser.xul overlay:
window.addEventListener("load", function() {
myExtension.init();
}, false);
var myExtension = {
init: function() {
var appcontent = document.getElementById("appcontent"); // browser
if(appcontent)
appcontent.addEventListener("DOMContentLoaded", myExtension.onPageLoad, false);
},
onPageLoad: function(aEvent) {
if(aEvent.originalTarget.nodeName=="#document"){
var doc = aEvent.originalTarget;
var str="the";
var regex;
var regex = new RegExp(str, "g");
doc.body.innerHTML = doc.body.innerHTML.replace(regex,'<b>'+str+'</b>');
}
}
The if condition in the function happens to do the trick..
:)

An observer for page loads in a custom xul:browser

In my firefox extension I'm creating a xul:browser element. I want to have an observer that intercepts any url changes within the embedded browser and opens the url in a new browser tab (in the main browser). I'd also like new windows spawned by the xul:browser window to open in a tab instead of a new browser window.
I've created an observer which works, but I don't yet know how to apply that observer only to the xul:browser element.
function myFunction(){
var container = jQuery("#container")[0];
var new_browser_element = document.createElement('browser');
container.appendChild(new_browser_element);
var observerService = Components.classes["#mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
observerService.addObserver(myObserver, "http-on-modify-request", false);
}
var myObserver = {
observe: function(aSubject, aTopic, aData){
if (aTopic != 'http-on-modify-request'){
aSubject.QueryInterface(Components.interfaces.nsIHttpChannel);
// alert(aSubject.URI.spec);
// Now open url in new tab
}
},
QueryInterface: function(iid){
if (!iid.equals(Components.interfaces.nsISupports) &&
!iid.equals(Components.interfaces.nsIObserver))
throw Components.results.NS_ERROR_NO_INTERFACE;
return this;
}
};
You could try:
var myObserver = {
observe: function(aSubject, aTopic, aData){
if (aTopic == 'http-on-modify-request')
{
aSubject.QueryInterface(Components.interfaces.nsIHttpChannel);
var url = aSubject.URI.spec;
var postData ;
if (aSubject.requestMethod.toLowerCase() == "post")
{
var postText = this.readPostTextFromRequest(request);
if (postText)
{
var dataString = parseQuery(postText);
postData = postDataFromString(dataString);
}
}
var oHttp = aSubject.QueryInterface(Components.interfaces.nsIHttpChannel);
var interfaceRequestor = oHttp.notificationCallbacks.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
var DOMWindow = interfaceRequestor.getInterface(Components.interfaces.nsIDOMWindow);
//check if it is one of your mini browser windows
if (jQuery(DOMWindow).hasClass('mini_browser'))
{
openInTab(url, postData);
var request = aSubject.QueryInterface(Components.interfaces.nsIRequest);
request.cancel(Components.results.NS_BINDING_ABORTED);
}
}
},
QueryInterface: function(iid){
if (!iid.equals(Components.interfaces.nsISupports) &&
!iid.equals(Components.interfaces.nsIObserver))
throw Components.results.NS_ERROR_NO_INTERFACE;
return this;
},
readPostTextFromRequest : function(request) {
var is = request.QueryInterface(Components.interfaces.nsIUploadChannel).uploadStream;
if (is)
{
var ss = is.QueryInterface(Components.interfaces.nsISeekableStream);
var prevOffset;
if (ss)
{
prevOffset = ss.tell();
ss.seek(Components.interfaces.nsISeekableStream.NS_SEEK_SET, 0);
}
// Read data from the stream..
var charset = "UTF-8";
var text = this.readFromStream(is, charset, true);
// Seek locks the file so, seek to the beginning only if necko hasn't read it yet,
// since necko doesn't seek to 0 before reading (at lest not till 459384 is fixed).
if (ss && prevOffset == 0)
ss.seek(Components.interfaces.nsISeekableStream.NS_SEEK_SET, 0);
return text;
}
else {
dump("Failed to Query Interface for upload stream.\n");
}
}
return null;
},
readFromStream : function(stream, charset, noClose)
{
var sis = Components.classes["#mozilla.org/binaryinputstream;1"]
.getService(Components.interfaces.nsIBinaryInputStream);
sis.setInputStream(stream);
var segments = [];
for (var count = stream.available(); count; count = stream.available())
segments.push(sis.readBytes(count));
if (!noClose)
sis.close();
var text = segments.join("");
return text;
}
};
function openInTab(url, postData)
{
var wm = Components.classes["#mozilla.org/appshell/window-mediator;1"]
.getService(Components.interfaces.nsIWindowMediator);
var recentWindow = wm.getMostRecentWindow("navigator:browser");
if (recentWindow)
{
// Use an existing browser window, open tab and "select" it
recentWindow.gBrowser.selectedTab = recentWindow.gBrowser.addTab(url, null, null, postData);
}
}
function parseQuery() {
var qry = this;
var rex = /[?&]?([^=]+)(?:=([^&#]*))?/g;
var qmatch, key;
var paramValues = {};
// parse querystring storing key/values in the ParamValues associative array
while (qmatch = rex.exec(qry)) {
key = decodeURIComponent(qmatch[1]);// get decoded key
val = decodeURIComponent(qmatch[2]);// get decoded value
paramValues[key] = val;
}
return paramValues;
}
function postDataFromString(dataString)
{
// POST method requests must wrap the encoded text in a MIME
// stream
var stringStream = Components.classes["#mozilla.org/io/string-input-stream;1"]
.createInstance(Components.interfaces.nsIStringInputStream);
if ("data" in stringStream) // Gecko 1.9 or newer
stringStream.data = dataString;
else // 1.8 or older
stringStream.setData(dataString, dataString.length);
var postData = Components.classes["#mozilla.org/network/mime-input-stream;1"].
createInstance(Components.interfaces.nsIMIMEInputStream);
postData.addHeader("Content-Type", "application/x-www-form-urlencoded");
postData.addContentLength = true;
postData.setData(stringStream);
return postData;
}
I'll update this to fill in the blanks in a bit.
edit: see http://forums.mozillazine.org/viewtopic.php?p=2772951#p2772951 for how to get the source window of a request.
Request cancellation code from http://zenit.senecac.on.ca/wiki/index.php/Support_For_OpenID.
see http://mxr.mozilla.org/mozilla-central/source/netwerk/base/public/nsIRequest.idl for details on nsIRequest.
See http://forums.mozillazine.org/viewtopic.php?p=2404533#p2404533 and https://developer.mozilla.org/en/XUL/Method/addTab for the definition of addTab.
parseQuery comes from http://blog.strictly-software.com/2008/10/using-javascript-to-parse-querystring.html.
See https://developer.mozilla.org/en/Code_snippets/Post_data_to_window#Preprocessing_POST_data for how to process post data in a form suitable for addTab.
ReadPostFromText and ReadTextFromStream both come from firebug (though slightly modified)

Resources