cancel
Showing results for 
Search instead for 
Did you mean: 

SAP Fiori: How to bind async loaded model to UI?

mario_fliegner
Explorer
0 Kudos

How can I use a model that was loaded by an async AJAX request in the UI?
What is the approach to load the model and even where?

I've tried to load the model in the Component.js (based on the QuickStartApplication template). Doesn't work. Because the request grabs data from another server it has to be async anyway. The AJAX call returns the data but I can't wait for it in neither in the component.js nor in the controller nor in the model.js.

My model.js including the async call looks like:

sap.ui.define([
	"sap/ui/model/json/JSONModel"
], function(JSONModel) {
	"use strict";
		function ajaxCallToCMISRepository(callbackResult) {
			jQuery.sap.log.info("Entering ajaxCallToCMISRepository");
			var resultObjects = "({})";
			var repoUser = "admin";
			var repoPass = "admin";
	    	var repoURL = "https://myserver:8443/cmis/root";
	    	jQuery.sap.log.info("Load JSON from Repository " + repoURL);
			var method = "GET";
			jQuery.sap.log.info("AJAX call to: " + repoURL);
			var dfd = new $.Deferred();
			$.ajax({
				type: method,
				url: repoURL,
				async: false,
				data: "{}",
				crossDomain: true,
				dataType: "jsonp",
				beforeSend: function (xhr){ 
					xhr.withCredentials = true;
				    xhr.setRequestHeader("Authorization", "Basic " + btoa(repoUser + ":" + repoPass)); 
				},
				success:function(data) {
            		callbackResult(data);
				},
				error : function (xhr, status, error) {
					jQuery.sap.log.info("ERROR xhr: " + xhr);
					jQuery.sap.log.info("ERROR status: " + status);
					jQuery.sap.log.info("ERROR error: " + error);
					var keys = $.map(xhr, function(item, key) {
						jQuery.sap.log.info("!!!!!!! " + key +  " ==> " + item);
					});
				}
			});
			//jQuery.sap.log.info("Return resultObjects: " + resultObjects);
			//return resultObjects;
		}


	return {
		loadRepositoryModel: function() {
			jQuery.sap.log.info("Entering loadRepositoryModel in model.js)");
			var resultJSON = "";
			ajaxCallToCMISRepository(function(data) {
    			resultJSON = data;
    			//resultJSON = getFolderNamesFromRepository(resultJSON["objects"]);
		    	var oModel = new JSONModel(resultJSON);
		    	oModel.setData(resultJSON);
				oModel.setDefaultBindingMode(sap.ui.model.BindingMode.OneWay);
				jQuery.sap.log.info("RETURN from loadRepositoryModel()!" + resultJSON.getJSON());
				return oModel;
			});
			//return resultJSON;
		}
	};
});

The related part in the Component.js is:

		init: function() {
			// call the base component's init function
			UIComponent.prototype.init.apply(this, arguments);
			var cmisModel = new JSONModel(models.loadRepositoryModel());
			this.setModel(cmisModel, "repoModel");
                        jQuery.sap.log.info("cmisModel -> " + cmisModel.getJSON());

But while the AJAX returns the model, meaning the log of resultJSON.getJSON() in the model.js returns the result, the cmisModel.getJSON() in the component.js is always empty because it does not wait.

What is the best way to load and finally to display async data in the UI?

Accepted Solutions (0)

Answers (5)

Answers (5)

former_member321811
Participant

Hi,

This should not be complicated, use data binding and set the data to the model when you get it from the server. Here's an example: http://plnkr.co/edit/A5NUMbGa1fOz03dZ8qIJ?p=preview

Cheers,

Pierre

mario_fliegner
Explorer
0 Kudos

Hi,

thanks, but it's still not working... tried it now with a simpler model but still dont know, how to bin the model within JavaScript to an ObjectListItem?

my component.js

init: function() {
	UIComponent.prototype.init.apply(this, arguments);
	sap.ui.getCore().setModel(new JSONModel({}), "activeUser");
	models.setCurrentUserModel();
}

my model.js

return {
	setCurrentUserModel: function() {
	var repoUser = "myuser";
	var repoPass = "mypass";
	var repoURL = "https://our.company.com:8443/api/user";
	$.ajax({
		type: "GET",
		url: repoURL,
		async: true,
		crossDomain: true,
		beforeSend: function (xhr){ 
		   xhr.withCredentials = true;
		   xhr.setRequestHeader("Authorization", "Basic " + btoa(repoUser + ":" + repoPass)); 
		},
		success:function(data) {
	 		var oModel = new JSONModel();
			oModel.setData(data);
			oModel.setDefaultBindingMode(sap.ui.model.BindingMode.TwoWay);
			sap.ui.getCore().setModel(oModel, "activeUser");
		},
		error : function (xhr, status, error) {
			jQuery.sap.log.info("ERROR xhr: " + xhr);
			jQuery.sap.log.info("ERROR status: " + status);
			jQuery.sap.log.info("ERROR error: " + error);
		}
	});
	}
};

The JSON that is returned has the following format, its just one entry:

{"id":1001,"firstName":"Henry","lastName":"Myers","email":"h.myers@company.com","externalId":null, "authorization: [ {...}, {...}]}

All I want to display in the sap.m.ObjectListItem is the "firstName" and the "lastName" as title information. So, I tried to read it in the init() method of the controller view:

return Controller.extend("QuickstartApplication1.controller.View1", {
	onInit : function () {
        	var userObjectListItemTile = this.byId("tileUserAccount"); //this does exists and is of type ObjectListItem, like: <ObjectListItem title="User Account" intro="User Account" icon="sap-icon://account" id="tileUserAccount" activeIcon="sap-icon://account"/>
	        userObjectListItemTile.bindProperty("title", "acitivUser>/firstName");
        }
});	

Nothing happens, even the model is loaded? Any thought what goes wrong?

Thanks!

former_member321811
Participant
0 Kudos

2 things:

- you have to bind the items aggregation if it's not done in the view xml

- there's a typo in your code (acitivUser):

userObjectListItemTile.bindProperty("title", "acitivUser>/firstName");

Check this and if it still doesn't work then please copy/paste your view xml.

mario_fliegner
Explorer
0 Kudos

Thanks, for the hint regarding the typo. I have not yet bound the items aggregation in the view xml. How can I achieve this in the code?

former_member321811
Participant
0 Kudos

Well maybe you should start with the tutorial if you don't know how to use data binding: https://sapui5.hana.ondemand.com/#docs/guide/bf71375454654b44af01379a3c3a6273.html

junwu
Active Contributor
0 Kudos

binding only need model, not the data.

mario_fliegner
Explorer
0 Kudos

Tried the $.Deferred(); approach along with the attachRequestCompleted event. Looks like this works only fine for models loaded with .loadData(...). But I have a async request (from another domain) and I'm using the function setData(ajaxResultJSON) to fill the model. With that the attachRequestCompleted event is not fired.
This is an example that I found, it does exactly what I need but it uses the .loadData() function: https://jsbin.com/miripajozu/2/edit?html,js,console,output

The issue is, that the model is not available at this time, because it is still loading. So, having the $.Deferred(); approach in my controller, the model is undefined at this time and therefore the .attachRequestCompleted() fails... and the wait does not work at all.
Next try was to instantiate the model in the controller before (according to the example above), setting an empty model, like:

sap.ui.getCore().setModel(new JSONModel(),"cmisModel");

Having that, the $.Deferred(); approach does not work either, because attachRequestCompleted() is always true for the promise. It is not fired again when the async requests updates the data with setData().

This is the code I tried in my controller.js:

var cmisModel = sap.ui.getCore().getModel("cmisModel");
var remoteCheck = this.getPromise(cmisModel,"/objects");
$.when(remoteCheck).done(function(){ 
	jQuery.sap.log.info("Model loaded!");
	this.byId("myRepoTable").setModel(cmisModel, "cmisModel");
}.bind(this));

The getPromise function:

getPromise : function (oModel, pathToTestForData){
	jQuery.sap.log.info("IN: getPromise, " + oModel + ", " + pathToTestForData);
	var deferred = $.Deferred();
	if (oModel.getProperty(pathToTestForData)){
		jQuery.sap.log.info("ok");
		deferred.resolve(); //Data already loaded
	} else {
		jQuery.sap.log.info("wait");
		oModel.attachRequestCompleted(deferred.resolve()); //Waiting for the event
	}	
	jQuery.sap.log.info("OUT: getPromise!");
	return deferred.promise();
}

Any suggestions how I can solve it?

mario_fliegner
Explorer
0 Kudos

Thank you for the answer. But even if I try to set the model in the success function, because of the ASYNC call the rest of the model including the UI has been already loaded before the AJAX call has finished. So I still facing the same issue.

success:function(data) {
       sap.ui.getCore().setModel(data, "cmisModel");

Maybe, I have to refresh the table after loading? But within the success I don't have the controller available. What is the best way to do that?

former_member365727
Active Contributor
0 Kudos

normally in this case we can pass component instance to model.js so that model can be set on Component

Use below code in Component.js

init: function(){
      UIComponent.prototype.init.apply(this, arguments);
      models.loadRepositoryModel(this);      //pass component instance to method in models
} //Below code for success method in loadRepositoryModel loadRepositoryModel: function(Component) { //Component reference passed to method success:function(data) { Component.setModel(data, "cmisModel"); } }
former_member365727
Active Contributor
0 Kudos

When using AJAX calls or ODATA aggregation bindings its better to use promises to notify that data load is completed.

In your case 'model.js' contains the function 'success' indicating that data is loaded from AJAX call, in this promise function you can set the model 'cisModel'

or

Raise an event from the promise function and catch the same in 'Component.js', in the event handler set the model 'cisModel'