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()
Related
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.
In the project I am working on I have a lot of serial true/false data that needs to be displayed in different ways. I have been working on a directive that will allow me to pass in the model, the table header, and heres the tricky part, number of columns.
<serial-checkbox-table title="Title" columns="2" ng-model="items"></serial-checkbox-table>
I cant seem to get the columns parameter to be respected. I built this thing at one point where the html was generated in the link phase but I was having a hard time updating the model that was passed into the directive.
heres a jsfiddle of the current state of the directive.
http://jsfiddle.net/peledies/2tVAe/
Anyone have any ideas?
What you need is a $scope.$watch on the model variable.
Here is your old cold:
var model = $scope.$eval($attrs.ngModel);
...
// compile and write to dom
Needs to updated to
$scope.$watch($attrs.ngModel,function(newVal,oldVal) {
//var model = $scope.$eval($attrs.ngModel);
// can just take model value from watch function result
var model = newVal;
...
// compile and write to dom
});
And here is your jsfiddle updated with this logic: http://jsfiddle.net/callado4/2tVAe/7/
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.
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'm wrapping up a Javascript widget in a Wicket component. I want to let the JS side talk to the component. What I've got so far:
Component in question goes like
talker = new GridAjaxBehavior();
this.add(talker);
in constructor
and then, later on, puts something like
"var MyGridTalker = new talker(" + this.talker.getCallbackUrl() + ");";
into the JS.
where GridAjaxBehavior extends AbstractDefaultAjaxBehavior. I want GridAjaxBehavior to spit back some XML when the JS calls it.
Am I doing this the right way? What should GridAjaxBehaviour do to spit back the XML?
Thanks
Spit back some XML for what? Presumably to update the model or the view, yes?
The strength of Wicket is that you don't have to worry about the rendered HTML. In Model-View-Controller terms, you set up the Controller to correctly modify the Model, and Wicket takes care of the View.
The separation is not entirely clear: in fact you can show/hide view components, or change then, and that can be seen as altering the View.
But what you generally don't have to do is directly manage the browser or javascript. Wicket takes care of that, if you take care of making your changes in the Java code.
In Wicket, the Ajax will call a method on your AjaxBehavior with an AjaxRequestTarget target.
In that method (or in methods called from it), you do whatever you need to do, updating models or views, and then you add to the target any view component that that has changed. Wicket takes care of updating the browser.
Here's an example. It's taken from some code I did, but heavily altered just to make explication clearer. The idea is simple: "chained" dropdown choices, where the options in the child change when the select option in the parent changes, as in the series of [State] [County] [District].
(In the actual class, the Model change is passed to the child, which decides for itself if it has changed, and adds itself to the target if it has, then passes the target to its child. I've removed most of that to make a clearer example.)
Here's the ctor, which just adds to itself an anonymous subclass of an AjaxBehavior:
public AjaxChildNotifyingDropDownChoice(...code elided for clarity...) {
this.child = child;
// Ajax won't work without this:
setOutputMarkupId(true);
//
add( new OnChangeAjaxBehavior() {
#Override
public void onUpdate(final AjaxRequestTarget target) {
// tell child to update its list
// based on newly selected value
// when the Ajax is called,
// my owning component's model
// is already updated
// note we could just type getModel()
// I'm making explicit that we're calling it
// on the enclosing class
// (which a non-static inner class has a hidden ref to)
child.setNewModelBasedOnSelectionOf(
AjaxChildNotifyingDropDownChoice.this.getModel());
// now add the child to the target
// Wicket javascript will receive the new
// options and re-render the child dropdown
target.add(child);
}
});
}
We could also have hidden or un-hidden components, or added behaviors like CSS styles, or even swapped one Panel for another. As long as for each changed component we:
1) called setOutputMarkupId(true); so that the javascript can find it, and
2) added it to the AjaxRequestTarget
Note that different types (subclases) of Ajax Behavior have different callback functions, so be sure you're overriding the right one (add an #Override annotation so the compiler can complain if you got the name wrong).
But again, the basic wicket idea is that instead of sending raw data for the client to parse and act on, you update your model and view, and tell Wicket to re-render what you've changed, by adding the chnaged components to the target.
The only reason I can think of to send straight XML would to be to feed it to non-Wicket javascript. Let me know if that's your aim, and I completely missed the point. ;)
I don't really know what Wicket is or what it does, but there is a minor bug in your code (as it appears).
This:
"var MyGridTalker = new talker(" + this.talker.getCallbackUrl();
You seem to be missing your end parens:
"var MyGridTalker = new talker(" + this.talker.getCallbackUrl() + ")";
Anyway, not a big deal, but didn't know if it was intentional.