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" 👍 */
},
});
},
Related
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 !
I've got event listeners defined as <AgGridReact> properties and then also just added this to my onGridReady callback:
grid.api.addEventListener('sortChanged',
evt => {
console.log(evt);
debugger;
});
Either way, the evt object is empty (an empty object, not undefined). Is this expected behavior or am I doing something wrong? The documentation (https://www.ag-grid.com/javascript-grid-events/) doesn't say anything about what to expect.
What you have is fine, but all 3 of the "sort" events:
sortChanged
beforeSortChanged
afterSortChanged
are notification only events - no event object are passed into the event itself
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.
Basically, I want to add a breakpoint every time a given closure variable is changed. Is there any way to do this?
I don't think there's currently a way to directly watch variables, but if you can put the closure variable in an object, then you can use Object.observe() to observe that object for changes. (Object.observe can only observe objects)
This requires you to have Experimental Javascript enabled - chrome://flags/#enable-javascript-harmony.
(function(){
var holder = {
watchedVariable: "something"
};
Object.observe(holder, function (changes) {
// returns an array of objects(changes)
if ( changes[0].name === "watchedVariable" ) {
debugger;
}
});
})()
Here's my beef: I'm observing the document for a keyup and then calling a handler. The handler is calling a function of a class and is bound to the class's context. When the function is called and my conditionals are met, it's supposed to stop observing the handler. But its not.
Created a class, this is the function I'm calling on page:
look_for: function(key_combo) {
this.keys_array = key_combo.split("+");
this.match_key_handler = this.match_keys.bind(this);
document.observe('keyup', this.match_key_handler);
},
This calls the document to observe keyup and calls this.match_keys(). As you can see, it's assigned to a handler because the function needs to be bound. Below is the match_keys functions:
match_keys: function() {
// matching the keys here, blah blah
if(this.keys_matched) {
document.stopObserving('keyup',this.match_key_handler);
}
}
However, when the conditional is met, it doesn't stop observing! If I stopObserving all keyups on the document, it will unregister this function but I want it to only unregister the match_key_handler.
StackOverflow, help me out! Below is another post thats similar. Followed what was on the page but still no dice.
Stop Observing Events with JS Prototype not working with .bind(this)