Why the refresh on the model does not work? - sapui5

Maybe I don't really understand the this.getView().getModel().refresh(true) or updateBindings.. Somehow it doesn't refresh the model, or my main idea is wrong. I mean; I can do a workaround to call a function that reads the odata service again, but this is not really beautiful. So, I read the Model in the onInit
onInit: function () {
var that = this;
var oViewModel = new sap.ui.model.json.JSONModel({});
this.getView().setModel(oViewModel, "detailView");
sap.ui.getCore().setModel(oViewModel,"detailView");
var oFilter = [];
var zAppFilter = new sap.ui.model.Filter("XXX", sap.ui.model.FilterOperator.EQ, "XXXX");
oFilter.push(zAppFilter);
var oModel = that.getView().getModel();
oModel.setDefaultBindingMode("TwoWay");
oModel.read("/XXXXSet", {
filters: oFilter,
success: function (oData) {
that.getView().getModel("detailView").setData(oData.results);
},
// ...
});
},
I use this "detailView"-JSONModel model in my view for bindings. This works.. Now, the add or delete function for example:
onDelete: function (oEvent) {
var that = this;
var oModel = this.getOwnerComponent().getModel();
var oSelectedItem = oEvent.getSource().getParent();
var oSourceID = oSelectedItem.getBindingContext("detailView").getObject().Zid;
oModel.remove("/XXX(XXX='XXX',XXXX='" + XXXX+ "')", {
method: "DELETE",
success: function(data) {
that.getView().getModel("detailView").refresh(true);
sap.ui.getCore().getModel("detailView").refresh(true);
},
// ...
});
},
That does not work.. but why? I mean also when I do updateBindings or something else. Am I understanding or doing something wrong?

Your JSONModel is not connected to anything. It's just a bunch of JSON data. So if you tell it to refresh, how should it know where to get the new data?
What refresh does not do is getting new data.
What refresh actually does (in a JSONModel) is telling the bindings that it has new data. One of these bindings can be the items of a sap.m.List for example. The list then knows that it needs to rerender to show the new data.
If you don't fetch new data and call refresh nothing will happen. The actual data is still the same.
i can do a workaround to call a function that reads agean the odata service but this is not really beautyfull
Well using an additional JSONModel when you already have a perfectly fine ODataModel isn't beautiful in the first place. If you just dropped your JSONModel and bound your view to your ODataModel then the view would automatically update after calling remove.
To bind the view to your ODataModel you can start with
<Table id="table0" items="{/XXXXSet}">
Don't forget to remove detailView from your cells.

You're mixing a client-side model (JSONModel) with a server-side model (ODataModel), expecting them to synchronize.
Client-side models and server-side models are two separate models serving two different purposes.
Client-side models
The main purpose of the client-side models is to provide and to sync data that are only available during the runtime of the application. If the app is gone, the data are gone. Some of the prominent use cases of client side models are:
Device model via JSONModel which provides information about user's device and its states.
ResourceModel which provides client side translatable UI texts for i18n purposes.
Synchronizing states from UI or application
The models here are not aware of any server-side data, and they shouldn't since it's not their purpose.
When dealing with a remote data provider that complies with a certain specification (e.g. OData or FHIR), the appropriate server-side model should be used instead.
Server-side models
Server-side models, such as ODataModel, have the advantage that they're server aware.
They know how to fetch, delete, update, create data, and even call functions from the backend system. They can be used to share states between the client and the server efficiently.
How? Simply use the server-side model in the binding definition directly. With OData as the default model for example:
<List items="{
path: '/MyEntitySet',
filters: [
{
path: 'ThatProperty',
operator: 'EQ',
value1: 'something'
}
]
}"> <!-- given "MyEntitySet", "ThatProperty", "EntityTitle", and "EntityDesc" are defined in $metadata -->
<StandardListItem title="{EntityTitle}" description="{EntityDesc}" />
</List>
This creates an ODataListBinding instance which will send a request to the service with the following URL:
https://....svc/MyEntitySet?$filter=ThatProperty eq 'something'
When the request succeeds, the list will show the entities accordingly. Afterwards, when calling myODataModel.remove(...);, the corresponding list will be refreshed automatically.
TL;DR
Am I understanding or doing something wrong?
Yes. Having an intermediate JSONModel in such cases is a common anti-pattern creating high maintenance costs. Try using the ODataModel only. The framework will do the work for you.

Related

Does JSONModel have $top and $skip like OData?

In my app I'm reading data from a JSON file and creating a model from it like this
var myModel = new sap.ui.model.JSONMOdel("pathToJson");
I have 300 values but I only want to read 50, is there a way to do that. I know I can use $top and $skip to select a specific set of values using OData. The API provides the function myModel.loadData() which contains a parameter oParameters but I don't know what I can pass in. Does anyone know if this possible?
The JSON model is a client-side model. This means that all the data is loaded at once with a single request. In the standard implementation, it has no methods for reading paged JSON contents (with top / skip or any other name you might give them).
You have said that you have a JSON file that you are loading. So such a paging does not even make sense from a technical point of view. This is because you cannot (easily) load a portion of a static file with client-only code (especially JSON, which is not valid if you are reading a fragment of it).
If you actually just want to store a segment of the file in the model, you can simply read the whole file with jQuery.ajax and then slice the array.
If you actually have a RESTful web service, then the paging mechanism should be part of this service (e..g it should have some path or query parameters for specifying the paging parameters). This service should return a valid JSON document for each call. On the client side, you can use such a service with the help of some functions (e.g. in the controller):
onInit: function () {
this.setModel(new JSONModel([])); // initially an emty array
},
//call this method when you want to read a page
onReadDataPage: function (iTop, iSkip) {
// use jQuery.ajax or jQuery.get to read a "page" of data; e.g.
jQuery.ajax({
url: "your service path",
data: {
top: iTop,
skip: iSkip || 0
},
success: this.onDataReceived.bind(this)
});
},
onDataReceived: function (aData) {
var oModel = this.getModel();
oModel.setData(oModel.getData().concat(aData);
}
If you want to use this in combination with a List with the growing feature, then you will need to create a new type of model - which is not trivial.

UI5 - OData (Response) Object Normalization

I'm looking for a method in the library to convert an OData response object into a normalized js object that can be sent into another ODataModel.create function. An OData "object" would be an OData REST response containing 'd', the '__meta', the 'results', etc.
I have a working solution with custom methods in a Utilities module but I'm not too sure about its resilience to all possible OData rules.
It isn't necessary to have the d, __meta and results section in a valid OData body:
The d potion is optional. You may also just form your body as a regular object such as:
{
Id: 12345,
Name: 'This is a valid OData body'
}
The __metadata section is optional as well and may be left out.
The results section is something you will receive when you query an Entity Set. If you query a single Entity (by providing its key), this section is left out as there will only be one result. You can't use the results section when you want to submit entities to the OData service, as every submission is supposed to happen in a separate operations.
With that, your normalisation/processing code could be very simple and look like:
jQuery.each(odata.results, function(idx, value) {
var body = value.d ? value.d : value;
delete body.__metadata;
// Do something with the body, e.g.:
ODataModel.create("/AwesomeEntity", body);
});
I hope this makes life easier? Full OData v2 JSON specs are available here:
http://www.odata.org/documentation/odata-version-2-0/json-format/

BackboneJS: REST DELETE does not trigger on destroy()

I am learning BackboneJS. With a REST backend I am trying to issue a HTTP DELETE in line: this.at(0).destroy(); in the code below:
var Task = Backbone.Model.extend({
defaults: {
name: 'Testing Just',
},
url: 'http://localhost:8080/todos/',
});
var Tasks = Backbone.Collection.extend({
model: Task,
url: 'http://localhost:8080/todos/'
});
var tasks = new Tasks();
tasks.fetch({
context: tasks
}).done(function() {
console.log("Tasks:" + this.length)
console.log(this.at(0).get('name'));
this.at(0).destroy();
console.log("Tasks:" + this.length);
console.log(this.at(0).get('name'));
});
The model is deleted from the collection but no REST DELETE occurs to the backend. The deletion on the REST backend works with 'localhost:8080/todos/0'.
Please advise what I am missing.
The deletion on the REST backend works with 'localhost:8080/todos/0'
You probably thought that backbone issues requests based on the index of model in collection, which is not the case. The param which is appended to collection's url is the id of the model, and id is what backbone uses to check whether a model is persisted or not.
Your models probably doesn't have an id attribute, or idAttribute set, so backbone thinks that model is not yet saved in persistence layer and there is no need to issue a DELETE request.

How do I customise the Ember store path for a specific model?

I have an API endpoint at /movies/:movie_id/actors.
I'm trying to use Ember Data to fetch this endpoint. I'm not interested in modelling movies at this point, just actors. My route looks like this:
this.route('actors', { path: '/movies/:movie_id/actors' });
My actor model is plain:
DS.Model.extend({
name: DS.attr("name")
})
In my actors route, I have:
model: function(params) {
// params contains movie_id
return this.store.findAll('actor')
}
This will cause Ember to send a request for /actors. How can I tell Ember to send a request to /movies/:movie_id/actors instead?
My JSON is being returned in the format { "movies": [ { … } ] } and I'm using the DS.ActiveModelAdapter, if that's at all relevant. I'm using Ember 2.0.
DS.Store doesn't work around "path" concept. It's more of a data bucket, which - when supplemented - can take burden of working with provider (fetch/update/create/cache etc.) off developer. In your case it looks similar to this:
ActiveModelAdapter, which you're using right now is using specific convention for accessing and isn't compatible with your data provider. So, what options do you have?
Customize ActiveModelAdapter by overriding pathForType or buildURL methods (note - links are for RESTAdapter, since ActiveModelAdapter subclasses it)
Choose more compatible adapter or even write your own
Don't use adapter - fetch the data through AJAX and feed it to store directly using push()/pushPayload()

Sail.js - routing to methods, custom policies & PATCH method

I have a few questions that I couldn't find answers anywhere online.
Does sails.js framework support HTTP PATCH method? If not - does anyone know if there is a planned feature in the future?
By default if I create method in a controller it is accessible with GET request is it the routes.js file where I need to specify that method is accessible only via POST or other type of methods?
How would you create a policy that would allow to change protected fields on entity only for specific rights having users. I.e: user that created entity can change "name", "description" fields but would not be able to change "comments" array unless user is ADMIN?
How would you add a custom header to "find" method which specifies how many items there are in database? I.e.: I have /api/posts/ and I do query for finding specific items {skip: 20; limit: 20} I would like to get response with those items and total count of items that would match query without SKIP and LIMIT modifiers. One thing that comes to my mind is that a policy that adds that that custom header would be a good choice but maybe there is a better one.
Is there any way to write a middle-ware that would be executed just before sending response to the client. I.e.: I just want to filter output JSON not to containt some values or add my own without touching the controller method.
Thank you in advance
I can help with 2 and 5. In my own experience, here is what I have done:
2) I usually just check req.method in the controller. If it's not a method I want to support, I respond with a 404 page. For example:
module.exports = {
myAction: function(req, res){
if (req.method != 'POST')
return res.notFound();
// Desired controller action logic here
}
}
5) I create services in api/services when I want to do this. You define functions in a service that accept callbacks as arguments so that you can then send your response from the controller after the service function finishes executing. You can access any service by the name of the file. For example, if I had MyService.js in api/services, and I needed it to work with the request body, I would add a function to it like this:
exports.myServiceFunction = function(requestBody, callback){
// Work with the request body and data access here to create
// data to give back to the controller
callback(data);
};
Then, I can use this service from the controller like so:
module.exports = {
myAction: function(req, res){
MyService.myServiceFunction(req.body, function(data){
res.json(data);
});
}
}
In your case, the data that the service sends back to the controller through the callback would be the filtered JSON.
I'm sorry I can't answer your other questions, but I hope this helps a bit. I'm still new to Sails.js and am constantly learning new things, so others might have better suggestions. Still, I hope I have answered two of your questions.