I've recently started using NVD3's great angular directives for constructing D3 charts. Indeed they are slick. However I'm having a lot of difficulties with callbacks. Callbacks work well enough when I add them using nv.addGraph(), like in Alex's answer and in the examples page. I've also had varying success with other suggestions in these SO answers. But to make it easier for other junior programmers in my company, I would like to use an HTML directive like those shown on the examples on github. Something like this:
<nvd3-multi-bar-chart
data="monthData"
id="monthDataChart"
... other properties ...
callback="monthCallback">
<svg></svg>
</nvd3-multi-bar-chart>
The function in my scope called monthCallback attempts to attach attributes, such as titles, and events, such as click, to each .nv-bar in the chart. The problem is that the chart starts to render before the data returns from the ajax request, and so monthCallback is fired before there are any .nv-bar on the page. (Note: it doesn't seem to make a difference whether or not the callback is declared with parentheses, i.e. callback="monthCallback" vs. callback="monthCallback()")
I considered using the workaround by liptga, or DavidSouther's answer, but linking the callback to the transition seemed the wrong way to address this problem. Any other suggestions for getting the callback to fire at the right time, using the HTML directive?
You can also try angular-nvd3 directive. It completely operates with charts via json, and you can also access to the full nvd3 core api.
In your case, you need to somehow refresh the chart.
1). One can use an api attribute of this directive like:
//in html
<nvd3 options="options" data="data" api="api"></nvd3>
and then in controller you can completely refresh directive anywhere using:
//javascript
$scope.api.refresh();
2). Another approach is to just make your chart hidden/visible, using config attribute and varying visible option like:
<nvd3 options="options" data="data" config="{ visible: false }"></nvd3>
For example, if there is no data yet, set visible: false. While data is returned, set visible: true. See live example below.
3). And the simplest way is to just change your data, and directive automatically will be refreshed with new data:
//javascript
$scope.data = newData;
$scope.$apply(); //sometimes you need to refresh the scope
As for your case with ajax it can look something like:
//ajax request; in the live example below I use timeout
$http.get("/some_url/")
.success(function(data){
$scope.data = data;
$scope.$apply();
//chart will render after the data returns
})
Callback function is defined as any other options:
//javascript, in controller
$scope.options = {
..., //any other options
callback: function(){
d3.selectAll(".nv-bar").on('click', function(){
alert("Hi, I'm callback!");
});
}
}
So it will be fired after chart renders, and after data is returned.
See live example. (updated with callback)
Not sure if that much related to the question, but ended here by searching callback firing too soon. I had similar problem with the Angular directive and Callback was firing too fast, I just added a simple if statement to see if the item I am trying to access is ready. Like this:
callback(chart) {
if (chart && chart.interactiveLayer) {
// do something
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.
A week ago I ran into a problem with emberjs and DataTables.
I was using ember-data to get data from the asp codebehind using webmethods based on the route parameters. Then I would use that data to create a table with datatables. However, when I changed the route, which changed the data and therefore changed the html, datatables would add the rows, but it wouldn't remove the old rows. In addition none of the functionality would work on the new rows and whenever I would sort, it would remove the new data.
Please let me know if anyone has a better answer than the one I posted.
I looked and found a lot of questions on this topic, or similar topics; However all of the solutions were hacky or costly performance-wise. So I found my own.
It isn't perfect; I would love for ember to implement an event based on this.
I added a controller initially for navigating in my application view. The change event looks mostly like this:
paramsChanged: function () {
if (this.type && this.filingType.value && this.year && this.period) {
this.transitionToRoute('application');
Ember.run.next(this, function () {
this.transitionToRoute(this.type.value, this.year, this.period);
});
//console.log('persist');
}
}.observes('type', 'year', 'period')
This is changing the route to application(basically removing the sub view) then moving to whichever route I need next.
The performance cost, although untested, should be negligible. I need to run the code for creating the view anyway, and I'm already in the application. I'm destroying a little bit extra by transitioning to the index, then I'm recreating the sub-view on the next run loop causing the initialization code contained in didInsertElement to be run.
I am using marionette in my application. I am showing ItemView through regions like in the following.
var productInfoViewObj=new productInfoView.ProductInfoView({model:tagInformationModel.tagInformationModelObj});
exports.MyApp.bodyContainer.show(productInfoViewObj);
This is the code, I written inside view.
exports.ProductInfoView=Backbone.Marionette.ItemView.extend({
domInfo:{
mainTemplateId:"tagProductListTpl",
tableTemplateId:"taginfoViewTpl",
tableContentDiv:"taginfoViewDiv",
//tad Info
tagInfoTabId:"tagInfoBtn",
productInfoTabId:"productInfoBtn"
},
template:function(){
return commonFunctions.templateCompilation("tagProductListTpl","");
},
onRender:function(){
console.log(document.getElementById("productInfoBtn"));
}
});
I am passing templateId and data as arguments to commonFunctions.templateCompilation. It will compile and return compiled string. That compiled result passing to template.
As per my assumption, after completion of template, onRender function will trigger. What I mean before onRender, dom will available whatever we are templating using template.
But I am getting null inside onRender function.
I want a callback, it should trigger after template available in dom. so I can access elements whatever I templated using template.
I can do one thing, whatever I written inside onRender, I can setup time like in the following way.
onRender:function(){
setTimeout(function(){console.log(document.getElementById("productInfoBtn"));},1000);
}
If I set time, working fine but it's not correct way to implement.
can anyone help me.
Thanks.
It's resolved, I have to use onShow instead of onRender function. Now it's working fine.
I would like to know how I can force a knockout binding to refresh it's value. Normally we use an observable and that way the binding can happen automatically when the observable changes. But in my case I have created a custom binding:
if (!ko.bindingHandlers.asyncHtml) {
ko.bindingHandlers.asyncHtml = {
init: function (element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
var parameters = value.params.concat([function (data) {
$(element).html(data);
} ]);
parameters.concat([function (data) {
$(element).html('Unable to retrieve html.');
} ]);
value.source.apply(null, parameters);
}
}
}
This is so that a function which performs an asynchronous JSON call can update the respective element (with the returned HTML) once the call completes. The element, a DIV in this case, looks like this:
<div id="myDiv" data-bind="asyncHtml: {source: getHtml, params: [myId()]}">
My problem is that, another feature on this page can change database values that require myDiv to be updated as a result. I can probably find a complicated way to correct this problem but I was wondering if there was a simpler way where I can just force the binding to reapply?
NOTE: getHtml is a function on my viewmodel which performs the JSON call to retrieve the HTML.
Thanks
I hope I understood what you are trying to accomplish correctly, but I am not sure, so let me explain how I understand your objective.
You have a div (#myDiv) which will retrieve it's initial HTML from the server.
You have an ajax function (getHtml) which retrieves this html and onSuccess updates #myDiv, possibly with this:
$('#myDiv').html(serverResponseHTMLContent);
You then have another function which may produce different HTML that should take the place of the server generated html.
If this is all correct then I would suggest you use knockout's html binding.
Your div would look like so.
<div id="myDiv" data-bind="html: myDivInnerHtml">
myDivInnerHtml would be part of your viewModel and should be an observable as you say you usually do.
Before the initial bind, call getHtml and have it set the value for myDivInnerHtml instead of actually setting the html for myDiv.
myDivInnerHtml = ko.observable(serverHtmlString);
Then when you apply the binding, myDiv's inner Html will be set by knockout.
To update the html, your client side function can change the value of myDivInnerHtml.
myDivInnerHtml(clientSideFunctionHtmlString);
If my assumptions are wrong and you have recreate the same html with different value, then you should use a template if possible and the server should not be sending the html, but instead the values to bind to the html.
Also, if the client side function is not creating html, but instead values to be bound to the html, then this will also not work.
You could look at the valueHasMutated() function which notifies subscribers that they should re-evaluate the observable.
See How to force a view refresh without having it trigger automatically from an observable? for a bit more explanation.
I'm having this odd issue when I update my viewmodel...basically with every update, there appears to be a random chance that each observable will contain this data:
function observable() {
if (arguments.length > 0) {
// Write
// Ignore writes if the value hasn't changed
if ((!observable['equalityComparer']) || !observable['equalityComparer'](_latestValue, arguments[0])) {
observable.valueWillMutate();
_latestValue = arguments[0];
observable.valueHasMutated();
}
return this; // Permits chained assignments
} else {
// Read
ko.dependencyDetection.registerDependency(observable); // The caller only needs to be notified of changes if they did a "read" operation
return _latestValue;
}
}
I've been using KnockoutJS for a while, and I've never seen anything like this. My guess is that it has something to do with my template binding, but I'm really not sure. I'm going to dig into it, but I figured I'd post it here in case anyone else is having this issue, or has a solution. Like I said, it doesn't happen consistently, only on occasion.
//// More Information ////
So Matt below referenced this (http://stackoverflow.com/questions/9763211/option-text-becomes-a-function-string-after-updated-with-fromjs), which is roughly the same issue. The only difference is that I'm using the native template binding in a style like this:
<div data-bind="template: {name: 'issueTemplate', data: incidents}"></div>
<script id="dashboardIssueTemplate" type="text/html">
<!--ko foreach: $data-->
<div data-bind="text: title"></div>
</script>
It was my assumption that KnockoutJS handled the unwrapping by itself when you pass the observableArray into the template binder. I know I can't say "title()" in this example, because that doesn't exist. Am I supposed to be binding with a command like $root.title()?
//// Even More Information ////
It appears that this problem occurs as a result of having two "applyBindings" on one page. My application contains an external widget which adds it's DOM to the host page DOM at runtime. That widget is using the ko.applyBindings(vm, ROOTNODE) syntax which should allow for the host page to run it's own ko.applyBindings(hostVm).
In fact, it does, and it works correctly every refresh. The problem however is when the host page does a viewModel update with no refresh. Somehow, the UI rendering spits out this internal function on EVERY data-bound node. I've debugged through KnockoutJS and actually confirmed that the viewModel and rootNode are correct...something outside of the actual binding is taking over.
This has something to do with the "()" appended onto the data object in the template. What I've found is that during the first render (page load) writing the template like this:
<div data-bind="template: {name: 'issueTemplate', data: incidents}"></div>
<script id="dashboardIssueTemplate" type="text/html">
<div data-bind="text: title"></div>
</script>
works just fine. However, once you run the update on the observableArray my "title" object becomes that function. If I write the template using this style:
<div data-bind="text: title()"></div>
It seems to work on every update.
I am not certain why this is the solution. From the looks of it, the data object being passed to the Knockout binder is the exact same on both page load and update. I'll post this as an answer, but I'm not marking it as an answer until I understand why this is happening.