SAPUI5/OpenUI5: Model attachRequestCompleted in onInit Function - sapui5

I've got two models defined in my Component.js. One is a list of all contacts and the other is only the logged in contact. Now i want to check in my controller if the logged in contact is already existing in the list of all contacts. I compare the registrationToken from the list agains the token from the logged in contact. But when i loop through the list the length is 0 because of asynchronous communication.
I saw the attachRequestCompleted function but now i got another problem... the onInit-function is already finished when my attach-function is fill my view-Model..
onInit : function(){
var gLocalContact = sap.ui.getCore().getModel("gLocalContact");
var gRemoteContacts = sap.ui.getCore().getModel("gRemoteContacts");
gRemoteContacts.attachRequestCompleted( function() {
... if ... setProperty to gLocalContact.getProperty("/registrationToken")...
console.log("I should be the first log to get the data in view");
});
console.log("I should be the second log!");
this.getView().setModel(gLocalContact, "localContact");
}
The first log in the attach-function should be first because there i define some data to gLocalContact which i need in my view. Another problem is that i have no access to my gLocalContact variable....

This is a little bit ugly because SAPUI5 does not support promises. So inside your view you don't know if the requestCompleted event will be fired or if the data has already been loaded. There are some solutions comming to my mind:
Attach the requestCompleted eventhandler in your component before you call loadData(). Then you can be shure that you will get the event.
You would have to build your view to handle an empty gLocalContact model though. But as soon as the model is populated with data the bindings will update the view.
Put the remaining stuff of your onInit() into your eventhander. To be sure to get the event do a check if there is already data in your model, and if so call your eventhandler manually to have it run at least once.
Use jQuerys Promises to synchronize. This allows you to wait for the second model too:
onInit : function(){
var gLocalContact = sap.ui.getCore().getModel("gLocalContact");
var gRemoteContacts = sap.ui.getCore().getModel("gRemoteContacts");
console.log("Wait some seconds for the data...");
var localContactPromise = this.getPromise(gLocalContact, "/origin");
localContactPromise.done(function() {
//same code as before but this time you can be shure its called.
//... if ... setProperty to
//gLocalContact.getProperty("/registrationToken")...
console.log("I should be the first log to get the data in view");
});
var remoteContactsPromise = this.getPromise(gRemoteContacts,"/origin"); //Wait for the other model to
$.when(localContactPromise, remoteContactsPromise).done(function(){
//When both models are loaded do this
console.log("I should be the second log!");
this.getView().setModel(gLocalContact, "localContact");
this.byId("label").setText("all loaded");
}.bind(this));
},
getPromise:function(oModel, pathToTestForData){
var deferred = $.Deferred();
if (oModel.getProperty(pathToTestForData))
deferred.resolve(); //Data already loaded
else
oModel.attachRequestCompleted(deferred.resolve); //Waiting for the event
return deferred.promise();
}
Full example on JSBin
A Promise is a object that has a done event. A Deferred is an object that has a Promise and a resolve() method that will raise the done event on that Promise. If you first call resolve() on the Deferred and then register a handler for the done the handler is immediately called. So you won't miss the event even if you were slower than the asynchronous load request.
But: If your model could not even been set on the component/core when your view initializes you have a severe problem as there is no such thing as a modelChanged event. I would recommend to create a empty model and assign it to the component in the components init-method and then use loadData() on that model.

Related

Event parameter overwritten after another event?

In view:
<List selectionChange=".onSelectionChange">
In controller:
onSelectionChange: function (oEvent) {
console.log(oEvent.sId); // log 1, output "selectionChange"
MessageBox.warning(Utils.i18n("CHANGE_CONFIRM"), {
actions: [ Utils.i18n("LEAVE_BTN"), MessageBox.Action.CANCEL ],
onClose: function (sAction) {
console.log(oEvent.sId); // log 2, output "closed"
if (sAction === Utils.i18n("LEAVE_BTN")) {
this._showDetail(oEvent.getParameter("listItem") || oEvent.getSource(), oEvent.getSource().data("target"));
}
}.bind(this)
});
}
Hi, may I ask why oEvent changed when onClose is triggered? Why can't I store oEvent in my scope?
Event is a module that implements Poolable, meaning that Event has to implement init and reset which will then be leveraged by its corresponding ObjectPool instance ("oEventPool" internally) to reuse the existing Event instance for the next event.
The "next event", in our case, is the "close" event which was fired by the dialog. As you could already observe, oEvent suddenly doesn't have the ID "selectionChange" but "close". This is because the Event instance was reset and reused again. And since oEvent is just a reference (not a copy), and because JS applies Call by Object-Sharing, it's "changed".
The API Reference of ObjectPool explains what it's doing with the Event instance:
(ObjectPool) maintains a list of free objects of the given type. If sap.ui.base.ObjectPool.prototype.borrowObject is called, an existing free object is taken from the pool and the init method is called on this object.
When no longer needed, any borrowed object should be returned to the pool by calling #returnObject. At that point in time, the reset method is called on the object and the object is added to the list of free objects.
Currently, the (oEvent) object is considered "no longer needed" when its handler is called. So the object is already reset right after onSelectionChange, and initialized again right before onClose is triggered.
UI5 does this so that it doesn't have to create and destroy multiple Event instances to improve performance. This is a practice borrowed from the Object Pool Design Pattern (which is also often used in game development).
So, what does it mean for us as application developers? Just don't rely on the event object that is inside a closure. Instead, assign primitive values from the object to separate variables so that we can use them later. E.g.:
onSelectionChange: function(oEvent) {
const eventId = oEvent.getId(); // === "selectionChange"
MessageBox.warning(/*...*/, {
onClose: function() {
/* oEvent.getId() === suddenly "close" but
eventId === still "selectionChange" 👍 */
},
});
},

Get Model in onAfterRendering Method in SAPUI5

I have created a json model in Manifest.json file. I want to access this model in onAfterRendering method. I tried below mentioned ways to get the data.
var oModel1 = this.getOwnerComponent().getModel("shipData");
var oModel2 = sap.ui.getCore().getModel("shipData") ;
var oModel3 = this.getView().getModel("shipData");
And results are oModel1 and oModel3 is created but there is no data when I am trying to get with getData method.
oModel2 is undefined.
How can I get the data from my model in onAfterRendering method?
Regards,
Mayank
You may not always get data from your JSONModel in the onAfterRendering method. This is very likely due to the fact that the JSON service request defined in your manifest.json is not yet complete. Due to the asynchronous nature of the request you will have to wait until the request is complete. One way would be to attach a request completion event handler on the model.
var oModel1 = this.getOwnerComponent().getModel("shipData");
oModel1.attachRequestCompleted(function(event){
//Run code when request is complete
var oModel = event.getSource();
var data = oModel.getData();
});

Get newly created id of a record before redirecting page

I would like to retrieve the id of a newly created record using javascript when I click on save button and just before redirecting page.
Do you have any idea please ?
Thank you !
One way to do this in Sugar 7 would be by overriding the CreateView.
Here an example of a CustomCreateView that outputs the new id in an alert-message after a new Account was successfully created, but before Sugar gets to react to the created record.
custom/modules/Accounts/clients/base/views/create/create.js:
({
extendsFrom: 'CreateView',
// This initialize function override does nothing except log to console,
// so that you can see that your custom view has been loaded.
// You can remove this function entirely. Sugar will default to CreateView's initialize then.
initialize: function(options) {
this._super('initialize', [options]);
console.log('Custom create view initialized.');
},
// saveModel is the function used to save the new record, let's override it.
// Parameters 'success' and 'error' are functions/callbacks.
// (based on clients/base/views/create/create.js)
saveModel: function(success, error) {
// Let's inject our own code into the success callback.
var custom_success = function() {
// Execute our custom code and forward all callback arguments, in case you want to use them.
this.customCodeOnCreate(arguments)
// Execute the original callback (which will show the message and redirect etc.)
success(arguments);
};
// Make sure that the "this" variable will be set to _this_ view when our custom function is called via callback.
custom_success = _.bind(custom_success , this);
// Let's call the original saveModel with our custom callback.
this._super('saveModel', [custom_success, error]);
},
// our custom code
customCodeOnCreate: function() {
console.log('customCodeOnCreate() called with these arguments:', arguments);
// Retrieve the id of the model.
var new_id = this.model.get('id');
// do something with id
if (!_.isEmpty(new_id)) {
alert('new id: ' + new_id);
}
}
})
I tested this with the Accounts module of Sugar 7.7.2.1, but it should be possible to implement this for all other sidecar modules within Sugar.
However, this will not work for modules in backward-compatibility mode (those with #bwc in their URL).
Note: If the module in question already has its own Base<ModuleName>CreateView, you probably should extend from <ModuleName>CreateView (no Base) instead of from the default CreateView.
Be aware that this code has a small chance of breaking during Sugar upgrades, e.g. if the default CreateView code receives changes in the saveModel function definition.
Also, if you want to do some further reading on extending views, there is an SugarCRM dev blog post about this topic: https://developer.sugarcrm.com/2014/05/28/extending-view-javascript-in-sugarcrm-7/
I resolved this by using logic hook (after save), for your information, I am using Sugar 6.5 no matter the version of suitecrm.
Thank you !

Restangular extendModel on new object

Restangular offers a feature, extendModel, which lets you add functionality onto objects returned from the server. Is there any way to get these methods added to an empty / new model, that hasn't yet been saved to the server?
I wanted to do the same thing but didn't find an example. Here's how I ended up doing it:
models.factory('User', function(Restangular) {
var route = 'users';
var init = {a:1, b:2}; // custom User properties
Restangular.extendModel(route, function(model) {
// User functions
model.myfunc = function() {...}
return model;
});
var User = Restangular.all(route);
User.create = function(obj) {
// init provides default values which will be overridden by obj
return Restangular.restangularizeElement(null, _.merge({}, init, obj), route);
}
return User;
}
Some things to be aware of:
Use a function like _.merge() instead of angular.extend() because it clones the init variable rather than simply assigning its properties.
There is a known issue with Restangular 1.x that causes the Element's bound data to not be updated when you modify its properties (see #367 and related). The workaround is to call restangularizeElement() again before calling save(). However this call will always set fromServer to false which causes a POST to be sent so I wrote a wrapper function that checks if id is non-null and sets fromServer to true.

Destroy server model does not update id to null

after I change to Backbone-Relational my model stopped to work when I call destroy().. I have to ensure that when it removes success on server there will be no more id at client side, so then when I try to save it again my model wont request PUT (update) - throws Record Not Found on server side.
Coffeescript side
save: ->
if isBlank #model.get("text")
#model.destroy() # after success it still with same attributes including id!!
else
#model.save()
Rails side
def destroy
#note = Note.find(params[:id])
#note.destroy
respond_with #note # callback is empty
end
Bug from Backbone-Relational perhaps? Does Backbone.js update id after destroy?
Backbone does not look like it modifies the Model on destruction in any way. Except removing it from its collection if any.
Check the code
What it does is triggering the event destroy so you easily can listen this event and do whatever you think is the proper behavior in destruction:
// code simplified and no tested
var MyModel = Backbone.Model.extend({
initialize: function(){
this.on( "destroy", this.afterDestroy, this );
},
afterDestroy: function(){
this.set( "id", null );
}
});