Waiting for sap.ui.table.Table rendered after bound model has changed - sapui5

I have sap.ui.table.Table which rows are bound to an JSONModel.
var oListModel = new sap.ui.model.json.JSONModel();
//oTable created here (sap.ui.table.Table)
oTable.setModel(oListModel);
oTable.bindRows("/");
When the table is rendered, i.e. the DOM is created for this table, i need to reference to the DOM to pass the DOM elements (table rows) to a library which will make them draggable.
My problem is: How do i know when the DOM for the table is created after the model has been changed and the table is rerendered? I didnt find any listener. The view controller's listener onAfterRendering() didn't help me.
The model is filled with data after a XMLHTTPRequest is successful. When i set the model in the success handler of the xht request and try to access the DOM elments directly afterwards they don't exist yet.
Thank you for your help!

You can add an event delegate
var oMyTable = new sap.ui.table.Table();
oMyTable.addEventDelegate({
onAfterRendering: function() {
$table = this.getDomRef() ....
}
});
a better way is to extend the control see Using addDelegate to extend a control to use third party functionality
UPDATE
the example in that blog doesn't work anymore fixed here

I had a similar issue recently where i had to access an element after a smartform (where the element is present) rendered onto the view. I posted my query here as well as my final solution (based on the accepted answer of #Dopedev). I am using XML views - including nested XML views - here however and the onAfterRendering didn't help for me as well.
I guess you could look at this solution - like it's mentioned in the accepted answer, it may not be an optimal solution, but it sure works. In my case there is not much of a performance issue with the binding of the DOMNodeInserted since it is being called in the onAfterRendering of the nested view that consists of only the smartform with an immediate unbinding upon finding.
The condition if (id === "yourtableid") { } should be enough to identify and pass on. Since you have a table and thus several child nodes, unbinding is imperative at this point.
Mutation Observer is the preferred method but i guess you may need to check the browser compatibility table at the end of the page to see if it matches your requirements. There is an example here. I have used Mutation Observer (outside of a SAPUI5/openUI5 environment) earlier and found it very convenient(and performant) to listen to DOM insert events. In fact the sap.ui.dt package consists of MutationObserver.js

Related

How to make a custom ListView page in SuiteCRM

I need to make a page in SuiteCRM (v7.9 -- based loosely on Sugar 6.5 CE) that has a list of objects (of a custom module), with checkboxes in front of each one. So far, so good: that's a standard ListView.
The catch is that only some records should be in the list (filtering on whether there is an associated row in a related custom module/object).
This page needs to be distinct from the "regular" list for this module, which should indeed list all records.
It seems to me it makes sense to use a custom "action" to access this page view, and I can get my custom action code to fire with the right URL.
But I don't see how to hook in the filtering. At first, it looked like the process_record logic hook might be helpful here, but it just gives the bean for every record to be displayed. Unless there's a flag "display this record" that I'm not seeing, that's not so helpful.
Ideally, of course, I'd like to be able to inject a different WHERE clause in my custom controller action before calling
parent::action_listview();
to display the page, but I'm not seeing doc to indicate how that might work. I would include source code, but so far, the line above is everything (but boilerplate) that's in the controller.php file.
Create a copy of listview in custom folder and then override the listview's listViewProcess() method and insert your query there:
function listViewProcess() // generating listview
{
$this->processSearchForm();
if($this->where==''){
$this->where.="leads.status='Converted'";
}
$this->lv->searchColumns = $this->searchForm->searchColumns;
if(!$this->headers)
return;
$this->lv->setup($this->seed, 'custom/modules/Leads/ListView/ListViewGeneric.tpl', $this->where, $this->params);
echo $this->lv->display();
}
More info: http://wiki-crm-forum.com/forum/viewtopic.php?f=2&t=9420&p=32674&hilit=listViewProcess&sid=21907ecd28734a726f61f7017a7e9a24#p32674
Another tested working example can be found here:
How to hard code the where condition in list view ,basic search,advance search in sugar CE
P.S: I'm not so sure about "v7.9 -- based loosely on Sugar 6.5 CE" I'd say it's 95% identical apart from API stuff
for custom modules in SuiteCRM.
You may change in function create_new_list_query.

Best practice to lazy load data on tab click

Within the onBeforeRendering() function of a view how should I determine if a specific node is present in the model and modify the model to include the node if not? This question is about lazy loading of data to the model for data that will be infrequently accessed by users but would have a performance penalty if loaded with initial model data.
My use case has a list page leading to a detail page for whatever item in the list the use clicks. On the detail page is a set of tabs which expose sub-details related to the selected detail. Specifically a long text description of a the brief for a task.
The tab control leads via manifest.json style routing to display a view in the tabs content area.
This is my current working code which is within the onBeforeRendering event of the view controller:
onBeforeRendering: function(oEvent){
var sPath = this.getView().getBindingContext("Projects").getPath(); // something like /task/6
console.log('Path='+sPath)
var oModel = this.getView().getModel("Projects");
var oTask = oModel.getProperty(sPath + "/brief");
if (oTask) { // looks like /brief exists so must already have loaded the brief
// nothing to do
console.log('Use existing data')
}
else { // /brief not yet present so we need to load it up
console.log('Load new data')
oModel.setProperty(sPath + "/brief", "This is the brief...") // replace with loaddata() from server, use attachRequestCompleted to call function to modify model.
}}
Question - is this the correct approach?
Edit: Based on discussion in this question I modified my code to use an event that fires per display of the view. onBeforeRendering turned out to run without much apparent predictability - which I am sure it has but in any case I wanted a one-per-display event. Also, I fleshed out the code further but retained the basic structure and it appears to do what I wanted.
This is a valid approach. But you should think aboute following use case: What happens if the data you loaded have been changed at the backend? The JSONModel does not give you any support here as it acts dumb data store only.

Lightswitch HTML Client, Set Default Value of List Picker

Lightswitch renders my navigation property as a list picker, but I can't figure out how to set a default value on it. There must be a simple way to do it. I've scoured the net, and all the suggestions look hopelessly kludgy. Is there a good comprehensive tutorial out there for Lightswitch? The most common tasks like setting the default value on a dropdown are ridiculously hard to figure out.
Here's how I did it.
myapp.Address.created = function (entity) {
myapp.activeDataWorkspace.MailListData.CountryRegion_LUs.filter("CountryRegionCode eq 'US'").execute().then(function (result) {
entity.CountryRegion_LU = result.results[0];
});
}
You want to write a handler for the created Javascript event for the parent entity. I'm binding CountryRegion_LUs, which are in the Address entity, so I am binding to the created event of the Address entity, not the CountryRegion_LUs entity. "MailListData" is the name of my database. I'm using an OData query to pull out the CountryRegion_LU that has a CountryRegionCode of "US." I could have simply used "load()" instead of .filter("CountryRegionCode eq 'US'").execute() to load all the records, and then picked the member of the result-set I wanted, say results.result[221], but then I'd be bringing all the data over and filtering client-side.
refer to this post of which I asked myself, there is a detailed answer on the problem you have above: it explains how to set your modal picker/details picker with a default value
Lightswitch HTML Client - set modal picker value when screen created
any questions feel free to ask

Use Template on Invisible Table for inserting into existing bound sap.m.Table

Excuse the confusing language but hopefully this makes sense: (see code for more clear explanation)
I have a requirement to display a list of "spare parts" in an sap.m.Table but there is the ability if one of these "spare parts" has a related "spare part" (e.g. A heavy duty version, a light version, etc) , that you can click a button on the row and display these related "spare parts" by inserting them immediately below the "spare part" in question.
While I can get the sap.m.Table doing what I want to do, I would like to take advantage of templates and binding to create a temporary sap.m.Table; bind it to the relationship that returns these alternate spare parts; and reuse the template for a row to give me an array of ColumnListItems which I can insert into the Table at the right place.
Unfortunately, doing this, a sap.m.Table has a feature that if it is not displayed, it doesn't actually make the Odata call and leverage the template function.
To explain possibly much clearer, refer to this jsbin:
http://jsbin.com/sihofu/4/edit?html,js,output
Any better ideas on how to generate template output for a binding without using a sap.m.Table; or alternative, getting the sap.m.Table to make the call without placing it on the screen visible (temporarily)?
The specific code to look at is as follows:
var oTable2 = new sap.m.Table();
oTable2.attachUpdateFinished(function() {
console.log("But this one doesn't");
// What I'm trying to do here is insert these entries below Key 1
});
oTable2.bindAggregation("items", {
path: "/ExampleSecondaryValues",
template: oTemplate,
});
Thanks,
Matt
Back from Holidays now and solved this problem with a bit of brute force by simply enhancing/extending the sap.m.table control slightly.
The problem was if the control was invisible, nothing was rendered, and some optimisation within UI5 core means that in the case nothing is rendered, the AfterRender event is not called on the control and this event is what fires the UpdateFinished event.
I won't debate whether that optimisation is appropriate or not, but to fix this I simply extended the table control with a new control which looks like as follows:
sap.m.Table.extend("my.InvisibleTable", {
renderer: function(oRm, oControl) {
oRm.write("<span");
oRm.writeControlData(oControl);
oRm.write("></span>");
}
});
e.g. Simply always rendering something in the render function, causes the AfterRender event to be called; which in turns allows the sap.m.Table to fire the UpdateFinished event which allows me to then safely get the rendered template items to insert in my visible table.
Would love to know a much better way of doing this (possibly using the template control or similar), but this works okay to solve the problem.
Cheers,
Matt

ExtJS 4 - How to load grid store with its params carrying latest values from a form?

I have a window with a search form at the top and grid at the bottom.
User can enter values in the search form and click button - Get Records.
At the click of this button, I load the store of the grid by passing the values in form fields as parameters in following way:
store.load({
params:{
key1:Ext.getCmp('field1').getValue();
}
});
I tried giving parameters in the store proxy itself, but it unfortunately always takes up initial values (values when the form is rendered) and not the latest one entered by the users in the form fields. Following is the method I used for assigning values to params while creating the store:
extraParams:{
key1:Ext.getCmp('field1').getValue();
}
I wanted to seek guidance at two things:
a. While defining a store, can I ensure that store takes latest/current values from the form fields before querying server, so that I don't have to provide these values while calling load function?
This becomes more necessary as I have a paging toolbar at the bottom which carries a refresh button (along with next, last, previous, first icons for navigation).
Now, whenever user clicks at refresh (or any navigation icon), the store gets loaded without the query parameters.
Thus the second thing is:
b. If the answer of 'a' is that - Pass the latest values to parameters manually when calling load function - then how can I write the handler for 'refresh' button and navigation icons (that is, next, last, previous and first) in the paging toolbar, so that I can pass the latest form values to load function.
Thanks for any help in advance.
PS: I am using ExtJS 4.
yourStore.on('beforeload',function(store, operation,eOpts){
operation.params={
status:cmbStatus.getValue(),
value:txtBuscarPor.getValue(),
empresa:'saasd',
app:'dsads'
};
},this);
Related to your question (b) (and because you especially asked for this in the comments section):
I see only one standard way to hook into the PagingToolbar button handlers which is very limited.
Ext.toolbar.Paging fires a 'beforechange' event before it actually changes the current page. See API docs. A listener that returns false will stop the page change.
All other methods require extending Ext classes which wouldn't be a problem if the ComboBox would make it easier to use your own implementation of BoundList (which is the class that renders the dropdown) or pass through config parameters to BoundList resp. the paging toolbar.
I tried to bring this lack of flexibility up on the Ext message board once but was pretty much ignored.
A possible solution for this is to use 'beforeload' event of the store and provide the list of parameters in it. This way, whenever the store is loaded, then its beforeload event is fired and the values picked up are always the latest. Hope this helps someone looking for something similar.