How do I force knockout to rebind a custom binding? - javascript

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.

Related

How do I pass an object data as a value (not as reference) using angularJS $broadcast

I'm trying to pass some data and action within two controllers by using AngularJS $broadcast event. But I'd problems with passing the data as a value (not reference).
What I've done so far is first I created a function that broadcast sendCartPreview event with an object of shoppingCart inside shopping-cart.controller.js
//function inside shopping-cart.controller.js
function sendCartPreview() {
var shoppingCart = $scope.shoppingCart;
$rootScope.$broadcast('sendCartPreview', shoppingCart);
}
Then I add a listener of the event on another controller which retrieve the shoppingCart data and pass the data value to sendCartPreview function inside the controller
//function inside chat.controller.js
$scope.$on("sendCartPreview", function(event, message){
sendCartPreview(message);
})
Basically the sendCartPreview function received the object data and added it to an array of message.
function sendCartPreview(shopping_cart) {
//some logic here and push the data to an array
vm.arrayOfMessage.push(shopping_cart);
}
The problem that I'm facing is whenever $scope.shoppingCart value changes, the value inside vm.arrayOfMessage also change according to the respective changes. What I want to achieve in the mean time is passing the data as a value (not by reference) so that everytime $scope.shoppingCart value changed, it will not affect the data inside vm.arrayOfMessage. How do I achieve that? Kindly need your help in this, any kind of help would be appreciated, thanks!
Make a deep copy call like sendCartPreview(angular.copy(message));
Thats pretty easy. I assume you have jquery also.
var cart = jQuery.extend(true, {}, shoppingCart);
& broadcast cart.
You can use angular.copy, for more info: https://docs.angularjs.org/api/ng/function/angular.copy

controller for knockout.js custom element

Sometimes a component/custom element has some UI logic which requires some UI code, it's something which can't be done by binding to the component's view-model.
For example, let's say the component needs to change the way it looks based on available space, and this requires manipulating elements by JavaScript code.
What I need is a controller for the UI.
For example, imagine we have a component called myGadget for which I have myGadget.html, myGadgetViewModel.js and I also want to have myGadgetView.js
Within the myGadgetView.js I want to have something like this:
function myGadgetView(element)
{
// element is the custom element's node
}
What is the best way to do this in Knockout?
Should I combine component with custom binding?
With a custom binding I could get access to the element, so the HTML of the component would look like this:
<script id="myBar-template">
<div data-bind="myGadget : ...">
</div>
</script>
and I need to put somewhere this:
ko.bindingHandlers.myGadget = {
init: function (element, valueAccessor)
{
// I have access to element node
var myGadgetView = new myGadgetView(element);
},
update: function (element, valueAccessor)
{
// I have access to element node
}
}
I'm not sure about using custom binding for this, I wonder if there's a better approach.
For example, I'm looking to the custom component loading, but I don't have a clear idea yet.
When defining a component, you can specify a createViewModel function. This function will be passed the element the component will be bound to. According the Knockout documentation, it's still preferable to use custom bindings to manipulate the view.
Any manipulation of the view should be done in binding handlers, but that doesn't mean you can't make something like a jQuery plug-in, which your myGadgetView.js would be, and use that in the binding handler. You just wouldn't want your plug-in to be aware of your viewmodel, nor your viewmodel to be aware of the plug-in. The binding handler would mediate, mapping viewmodel elements to plug-in parameters.

Bindings on self-written HandlebarsHelper?

i am trying add internationalization-abilities to my website.
I have written my own I18n.js which uses translation-objects out of the DS.store instead of its own (so there is a translation model and Ember preloads it on Application-start).
To get my translations into the Templates i have written this handlebars-helper
Ember.Handlebars.registerHelper('i18n', function(key) {
return Application.I18n.t(key);
});
so i could easily use it like:
{{i18n example_key}}
So far, everything works just perfect.
But the translations visible on screen are not bind to its translation-models.
If i change a translation in the administration-page which is places there too, i have to reload the page.
is it possible to add bindings between the helper and the translation model the helper have to display?
Thanks
Use registerBoundHelper instead of registerHelper. I don't even think registerHelper is part of the public API.
What does Application.I18n look like exactly? In order to make the {{i18n}} helper refresh its content when something changes, it needs to observe something that is observable.
Ember.Handlebars.helper is useful if you pass an object with observable properties (see http://emberjs.com/guides/templates/writing-helpers/#toc_dependencies). But if you only pass a key as a string, you'll have to set up the binding yourself in some way.

NVD3 Angular Directive callback firing too soon

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

How to handle ajax in knockout viewmodels?

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.

Categories