I've tried to prepare data from an OData source to show it in a bar graph in my fiori app. For this, I setup the OData model in the manifest.json. A test with a list, simply using
items="{path : 'modelname>/dataset'}
works fine and shows the content.
To prepare data for a diagram (VizFrame), I used the onInit() function in the controller of the view (mvc:XMLView). The data preparation is similar to the one discussed in question.
At first I obtain the ODataModel:
var oODataModel = this.getOwnerComponent().getModel("modelname");
Next I do the binding:
var oBindings = oODataModel.bindList("/dataset");
Unfortunately, the oBindings().getContexts() array is always empty, and also oBindings.getLength() is zero. As a consequence, the VizFrame shows only "No Data".
May it be that the data model is not fully loaded during the onInit() function, or do I misunderstand the way to access data?
Thanks in advance
Update
I temporary solved the problem by using the automatically created bind from the view displaying the data as list. I grep the "dataReceived" event from the binding getView().byId("myList").getBindings("items") and do my calculation there. The model for the diagram (since it is used in a different view) is created in the Component.js, and registered in the Core sap.ui.getCore().setModel("graphModel").
I think this solution is dirty, because the graph data depends on the list data from a different view, which causes problems, e.g. when you use a growing list (because the data in the binding gets updated and a different range is selected from the odata model).
Any suggestions, how I can get the odata model entries without depending on a different list?
The following image outlines the lifecycle of your UI5 application.
Important are the steps which are highlighted with a red circle. Basically, in your onInit you don't have full access to your model via this.getView().getModel().
That's probably why you tried using this.getOwnerComponent().getModel(). This gives you access to the model, but it's not bound to the view yet so you don't get any contexts.
Similarly metadataLoaded() returns a Promise that is fullfilled a little too early: Right after the metadata has been loaded, which might be before any view binding has been done.
What I usually do is
use onBeforeRendering
This is the lifecycle hook that gets called right after onInit. The view and its models exist, but they are not yet shown to the user. Good possibility to do stuff with your model.
use onRouteMatched
This is not really a lifecycle hook but an event handler which can be bound to the router object of your app. Since you define the event handler in your onInit it will be called later (but not too late) and you can then do your desired stuff. This obviously works only if you've set up routing.
You'll have to wait until the models metadata has been loaded. Try this:
onInit: function() {
var oBindings;
var oODataModel = this.getComponent().getModel("modelname");
oODataModel.metadataLoaded().then(function() {
oBindings = oODataModel.bindList("/dataset");
}.bind(this));
},
May it be that the data model is not fully loaded during the onInit()
function, or do I misunderstand the way to access data?
You could test if your model is fully loaded by console log it before you do the list binding
console.log(oODataModel);
var oBindings = oODataModel.bindList("/dataset");
If your model contains no data, then that's the problem.
My basic misunderstanding was to force the use of the bindings. This seems to work only with UI elements, which organize the data handling. I switched to
oODataModel.read("/dataset", {success: function(oEvent) {
// do all my calculations on the oEvent.results array
// write result into graphModel
}
});
This whole calculation is in a function attached to the requestSent event of the graphModel, which is set as model for the VizFrame in the onBeforeRendering part of the view/controller.
Related
I'm using SAPUI5 to make an application and I'm having an issue with an update to the model not being reflected immediately in the view.
A slightly simplified scenario: I created a text field that is taking its text from the model:
currentText = new sap.m.Text({
width: '100%',
text: '{/currentTrip/perHour}'
});
Then I create a select and attach a change handler:
sourceSelect.attachChange(oController.changeTripSource);
Inside that controller function I'm updating the view model:
var model = this.getView().getModel().getData();
model.currentTrip.perHour = 5;
All the things I do with this number flowing from this change event work (like updating a connected graph, which uses this number directly). The view itself however will not reflect the change to the model until I take another action like push a button or something. I'm not understanding why that is, or what triggers UI5 to check the changed values to the model and update the view. Does anyone have a fix for this?
Why don't you call setProperty() on the model directly? This saves you superfluous calls to update your bindings as the model will do it internally.
model.setProperty("/currentTrip/perHour", 5);
By the way, I don't see why updateBindings() shall work while refresh does not. Both methods call the checkUpdate() behind the curtains.
Always good practice to call updateBindings() whenever data is updating. Try:
model.updateBindings()
I am using knockout in my project. I have multiple viewmodel, each viewmodel have its own save function implemented in it. Now whenever user clicks on save button the viewmodel data post to the server, i want to block the save button until the response came back from server.
Currently i am handling this by creating an extra observable property saving in each viewmodel. So when user click over the save button i am setting saving observable to true and in callback i am setting it to false. And i have bind this saving property with the button using knockout disable binding.
But i feel that this approach is not good and it contains the following big drawbacks:
For this i have to add an extra property in each viewmodel.
I have to add multiple line of code like setting it to true and again set it to false.
The approach is not centralize, the code for this approach is scattered.
So i want to know is there any other better way to handle this, a plugin or some standard way ??
Edit
Just to clarify, my question has nothing to do with asp.net postback, the question is how i can handle efficiently the ajax, like block the save button, displaying the response message etc
??
This is generally what makes a viewmodel a viewmodel. In a pattern like MVC, your controller shouldn't really have any idea what your view looks like, what controls it has, or what it's state is, and your model only contains data for the view to model. In an MVVM pattern, as knockout is, your viewModel actually does have knowledge of the current states of controls on the view. This isn't to say your viewmodel should directly update the view, but it usually will contain properties that are bound to states of the view. Things like SaveButtonEnabled or IsSavingData or even things like StatusLabelColor are accepted in a viewmodel.
Perhaps use $.ajaxSetup(). You call this in your document ready function.
$.ajaxSetup({
beforeSend: function(jqXHR)
{
//this will be called before every
//ajax call in your program
//so perhaps, increment an observable viewmodel variable
//representing the number of outstanding requests
//if this variable is > 0 then disable
//your button
},
complete: function(jqXHR)
{
//this will be called after every
//call returns
//decrement your variable here
//if variable is zero, then enable button
}
});
I'd recommend you take a look at http://durandaljs.com/, a framework using Knockout and has some great data patterns, even if you don't use it directly.
ya'll I have a bit of a structural/procedural question for ya.
So I have a pretty simple ember app, trying to use ember-data and I'm just not sure if I'm 'doing it right'. So the user hits my index template, I grab their location coordinates and encode a hash of it (that part works). Then on my server I have a db that stores 'tiles' named after there hash'd coords (if i hit my #/tiles/H1A2S3H4E5D route I get back properly formatted JSON).
What I would like to happen next, if to display each of the returned tiles to the user on the bottom of the first page (like in a partial maybe? if handlebars does that).
I have a DS.Model for the tiles, if I hard code the Hash'd cords into a App.find(H1A2S3H4E5D); I can see my server properly responding to the query. However, I cannot seem to be able to figure out how to access the returned JSON object, or how to display it to the user.
I did watch a few tutorial videos but they all seem to be outdated with the old router.
Mainly I would like to know:
1. Where does the information returned by App.find(); live & how to access it?
2. what is the 'correct' way to structure my templates/views to handle this?
3. how should I pass that id (the hash'd coords) to App.find? as a global variable? or is there a better way?
the biggest problem(to me) seems to be that the id I search by doesn't exist until the user hit the page tho first time. (since its dynamically generated) so I can't just grab it when the page loads.
I can post a fiddle if required, but I'm looking for more of a conceptual/instructional answer rather then some one to just write my code for me
I'm still learning a lot with Ember as well, but this is my understanding. When you follow the guides and the tutorials out there, you'll have something like this:
App.TileController = Ember.ObjectController.extend();
App.TileRoute = Ember.Route.extend({
setupController: function(controller) {
controller.set('content', App.Tile.find(MYHASH));
}
});
What it does is set the special content object to the result. So since we're declaring an object controller, and calling find with a parameter, it knows that a single result is expected. So a view & template that follow the naming convention of Tile will be loaded. And in there you can access properties on the Tile object:
<p>{{lat}}</p><p>{{lng}}</p>
I have to admit that this feels a bit mystical at times. The core to it is all in the naming convention. You need to be pretty specific in how you name all your various controllers, routes, etc. Once that's nailed down, it's a matter of binding what data you want to the controller's content.
1) Aside from the generic answer of "in memory", the .find() calls live where ever you return it to. Generally speaking, this is meant to be set on a 'content' property of a controller.
2) I more or less answered this, but generally speaking you take the name of your route, and base it off that. So for a route TileRoute, you have:
TileController = Ember.ObjectController.extend
Tile = DS.Model.extend
TileView = Ember.View.extend
tile.handlebars
I generally store all my handlebars files in a templates/ folder. If you nest them deeper, just specify the path in your view object:
App.TileView = Ember.View.extend({
templateName: "tiles/show"
});
3) This really depends on your app. Generally speaking its better for the id to be either obtained from the URL, or constructed locally in a function. Since you are encoding a hash, i imagine you're doing this in a function, and then calling find. I do something a bit similar for an Array controller.
I don't know at what point you are generating a hash, so let's say it's onload. You should be able to generate the hash just in the setupController function.
App.TileRoute = Ember.Route.extend({
generateHashBasedOnCoords: function() {
// ...
},
setupController: function(controller) {
var MYHASH = this.generateHashBasedOnCoords();
controller.set('content', App.Tile.find(MYHASH));
}
});
I hope that helps.
I believe that you can make use of the data binding in ember and basically have an array controller for tiles and set the content initially to an empty array. Then we you get back your response do a App.find() and set the content of the tiles controller with the data that is returned. This should update the view through the data binding. (Very high level response)
The data itself is stored in a store that is setup with ember data. You access it with the same method you are using the model methods App.Tile.find() ect. It checks to see if the data that is needed is in the store if so it returns the data otherwise it makes a call to the api to get the data.
I am using Ember.js in my application, but there is a point where I update a property of the view's context(controller) but right after the update there is a parser(MathJax) that looks at the dom for the updated fields to parse it into math. However even though the update is taking place, it happens after mathjax looks for the update. What I need to do is force ember to update the view or wait for ember to update before I tell mathjax to parse the html. Is there a way to achieve this?
This is a fairly common use-case. To specify code that should execute after a property's changes have propogated, use an observer. Ember triggers observers after it successfully propagates the change. For example:
App.MathView = Ember.View.extend({
controller: null,
template: Ember.Handlebars.compile("<div>{{myProperty}}</div>"),
myPropertyDidChange: function() {
// From here the controller value is changed but DOM has not been updated
Ember.run.next(this, function() {
// This code will run when after changes have propagated to the DOM
// Call MathJax parser here
// If needed you can access the view's DOM element via this.$()
alert("New property value is in dom: "+ this.$().text());
});
}.observes('controller.myProperty')
});
See the Ember Managing-Asynchrony Guide and API docs for Ember.run:
http://emberjs.com/guides/understanding-ember/managing-asynchrony/
http://emberjs.com/api/classes/Ember.run.html#method_next
I have a big problem with using Knockout JS. In my view model I have a field, called Method, that is actually an other view model.
This view model can be one of three different things (it is mapped to a polymorphic object in the domain model). To solve this I use templates that checks which type of Method that is selected withing the domain model and then shows the template that binds data for that type.
The function that checks the type of method looks like:
this.getTemplate = function (data) {
var method = data.original.get_Method();
if (method instanceof MyProj.MethodA)
return "methodA";
else if (method instanceof MyProj.MethodB)
return "methodB";
else if (method instanceof MyProj.MethodC)
return "methodC";
}
The markup where I bind the template looks like:
<div data-bind="template: {name: getTemplate($data), data: $data.Method}"></div>
This actually works very nice and when I change the type of method via an dropdown in the UI the domain model updates and the right template is shown. However here comes my problem. Each template contains a number of different fields that are specific for each method type. Whenever I change one of the values in the view model displayed by one of the templates the UI flashes and I think that happens because the template get selected again. This is quite irritating and looks extremly bad.
Any ideas on how to solve this problem? Any help would be greatly appreciated!
Thanks in advance
/Björn
Did you use any observable inside the getTemplate function. Updating the value of that observable makes the template rerender and you get your flash effect.
Checkout this link Part : "Note 5: Dynamically choosing which template is used".