I'm trying to understand Knockout JS. What bothers me is that the data I bind gets blanked out. It's annoying because I want to show the data I retrieve from the server.
An example:
Name: <span class="name"><?php echo $data->name; ?></span>
The result is: Name: John B
Now if I data-bind this with Knockout to this:
Name: <span data-bind="text: name" class="name"><?php echo $data->name; ?></span>
The result is: Name:
The JS will be something like:
var viewModel = {
name: ko.observable() // Initially blank <--- this is the culprit
};
ko.applyBindings(viewModel);
So what do I need to do in order to show / keep the data?
Can't Knockout somehow keep the data like Angular JS does?
Note
I'm using Yii MVC framework to handle a lot of server side stuff. Using that to load data on page load, saves me from writing a lot of JS Ajax code. I wanted to use Knockout to reduce the amount of jQuery code I have, not add to it :)
Another alternative is to construct your viewmodel supplying your value from the server directly into the observable.
<span data-bind="text: name"></span>
var viewModel = {
name: ko.observable(<?php echo $data->name; ?>);
};
ko.applyBindings();
Trying to initialise an observable's value from the HTML is dangerous as there are many ways that the observable's value will be reset, specifically if an element is contained within a parent binding that uses a explicit/implicit template, like an if
binding.
<div data-bind="if: someFlag">
<span data-bind="text: name">John Doe</span>
<div>
In the above example, everytime the someFlag
value changes from false to true, the text binding on the span element will be re-initialised.
Here are two workaround you can try to avoid declaring your data twice (note that they do not directly provide a way to keep your data in your html file) :
Solution 1: Put your default model in a separate js/php file
Create a php file with:
var defaultVm = {
name: '<?php echo $data->name; ?>',
anyVariable: '<?php echo $data->anyVariable; ?>'
}
Then you include this php file as a js file and init your viewmodel with it:
var viewModel = {
name: ko.observable(defaultVm.name);
anyVariable: ko.observable(defaultVm.anyVariable);
};
ko.applyBindings(viewModel);
Solution 2: Use the ko.mapping plugin
Create a php file with:
{
"name": "<?php echo $data->name; ?>",
"anyVariable": "<?php echo $data->anyVariable; ?>"
}
Then in your js, you can use the ko.mapping plugin:
var viewModel = function (data) {
ko.mapping.fromJS(data, {}, this);
};
ko.applyBindings(new viewModel(getYourPhpFileTheWayYoulike));
This allows you to get your data asynchronously (if you want) with something like:
$.getJSON("yourphpmodelurl", function (data) {
ViewModelInstance = new viewModel(data);
ko.applyBindings(ViewModelInstance);
}
You can create a custom binding handler to do this:
ko.bindingHandlers.textInitialized= {
init: function (element, valueAccessor) {
valueAccessor()(element.innerHTML); // get the current value and update the observable
},
update: function (element, valueAccessor) {
var value = valueAccessor();
element.innerHTML = ko.utils.unwrapObservable(value);
}
};
And then you can bind to your viewmodel like this:
Name: <span data-bind="textInitialized: name" class="name">John B</span>
EDIT after jsFiddle:
You have created a completely different thing in the jsFiddle.
A few things:
- Inside your autocomplethandler, you use the code I provided, but: you want to set the "value"-attribute, not the innerHTML, so you should change that to
element.value
instead of element.innerHTML
.
- You have both an autocomplete-binding and a value-binding. Both of them work on the value-attribute (if you follow my first comment). The second one will come in after the first and undo that change.
I have modified the fiddle with these changes: http://jsfiddle.net/P8N77/24/