I am just referring the tutorials from knockout.js:
http://learn.knockoutjs.com/#/?tutorial=webmail
In the UI the markup is:
<!-- Folders -->
<ul class="folders" data-bind="foreach: folders">
<li data-bind="text: $data,
css: { selected: $data == $root.chosenFolderId() },
click: $root.goToFolder"></li>
</ul>
and it's ViewModel is:
function WebmailViewModel() {
// Data
var self = this;
self.folders = ['Inbox', 'Archive', 'Sent', 'Spam'];
self.chosenFolderId = ko.observable();
// Behaviours
self.goToFolder = function(folder) { self.chosenFolderId(folder); };
};
ko.applyBindings(new WebmailViewModel());
Can anybody tell me what is is $root and why is it required? If I remove it, it doesn't work.
$root refers to the top model in KnockoutJS hierarchy (the one you use in .applyBindings). In your case WebmailViewModel object is the $root.
It is required, because when you use foreach then inside the loop the context changes. Everything you want to fire here is associated to an element within a loop. Thus you need $root to use functions/fields defined outside of that context (in your case chosenFolderId is a method of WebmailViewModel object).
You'll need to check out the binding contexts page.
$root
This is the main view model object in the root context, i.e., the
topmost parent context. It is equivalent to $parents[$parents.length
- 1].
The $root context always refers to the top-level ViewModel, regardless of loops or other changes in scope. This allow us to access top-level methods for manipulating the ViewModel.
In your example $data represent to folder array values like 'Inbox', 'Archive','Sent' and 'spam'. But $root represent root functions of ViewModel like chosenFolderId and goToFolder .
See http://www.dotnet-tricks.com/Tutorial/knockout/bSKG240313-Understanding-Knockout-Binding-Context-Variable.html
I think this link examples will help you more.
Related
I have a context problem / design problem for my Backbone view.
Goal
The user selects a user from a list / user collection in a separate view.
The mentioned view passes an global event that the editUserView receives ("edit-contact").
The editUserView should receive this event and extract the (user) model.id attribute. By using this model.id I want to update the view with the corresponding object retrieved from the existing view model Tsms.Collection.Users.
Problem
The context passed to the updateView function is wrong, and thus I do not have access to the parent views .render() function. The debugger states "render() is not a function".
Since the context is not that of the parent view I am also unable to set the this.current variable.
How would I go about solving this problem?
View code
Tsms.Views.editUserView = Backbone.View.extend({
model: Tsms.Collections.Users,
initialize: function(options) {
Tsms.require_template('edituser')
this.template = _.template($('#template_edituser').html());
this.current = -1;
Tsms.vent.on('edit-contact', this.updateView)
},
updateView: function(model) {
this.current = model.id;
this.render();
},
render: function() {
this.$el.html(this.template(this.model.get(this.current).attributes));
return this;
}
});
Backbone's on actually takes three arguments:
on object.on(event, callback, [context])
[...]
To supply a context value for this when the callback is invoked, pass the optional last argument: model.on('change', this.render, this) or model.on({change: this.render}, this).
The easiest and (currently) most idiomatic way to solve your problem would be to use the third context argument:
Tsms.vent.on('edit-contact', this.updateView, this);
While mu is too short is right, you should use Backbone's listenTo to avoid memory leaks (zombie views).
this.listenTo(Tsms.vent, 'edit-contact', this.updateView);
The context is automatically set to this, the calling view.
When remove is called on the view, stopListening is called and any references kept for events are removed.
Another reason to avoid on is that it should be the view that is responsible for the events it wants to handle, the event bus shouldn't have to know.
I'm trying to target an element using jQuery which is embedded in one of my knockout templates:
<script type="text/html" id="video-file-template">
<div class="video" data-bind="attr: { 'data-index': $index }">
</div>
</script>
Yet, when I attempt to select $('.video') using jQuery, wrapped in a document ready function, I get an object with a length of 0 returned:
$(document).ready(function() {
console.log($('.video')); // Returns an object with a length of 0
});
Why is this? Is it because the element is not part of the DOM when my jQuery script is evaluated? If so, how can I target the element when it is loaded into the DOM via Knockout.js?
It's true that the document is ready before ko.applyBindings finishes, so that's why you're not seeing the element. However, you should not be using jQuery to violate the boundary between your view model and the DOM like that. In knockout, the way to accomplish what you need is with custom bindings.
Basically, you define a new knockout binding (like text, value, foreach, etc) and you have access to an init function, which fires when the element is first rendered, and an update function, which fires when the value you pass to the binding is updated. In your case, you would only need to define init:
ko.bindingHandlers.customVideo = {
init: function (element) {
console.log(element, $(element)); // notice you can use jquery here
}
};
And then you use the binding like this:
<div data-bind="customVideo"></div>
Perhaps it's better to add the "video" class and do other initialization right in the init callback:
ko.bindingHandlers.customVideo = {
init: function (element) {
$(element).addClass('video');
}
};
If this feels a little wonky at first, remember there's a very good reason for the indirection. It keeps your view model separate from the DOM it applies to. So you can change the DOM more freely and you can test the view model more independently. If you waited for ko.applyBindings to finish and called some jQuery stuff after that, you'd have a harder time testing that code. Notice that knockout custom bindings are not "special" in any way, and you can see that the built in bindings are defined exactly the same: https://github.com/knockout/knockout/tree/master/src/binding/defaultBindings
As the previous comments have suggested, it's because your $(document).ready fires before your knockout templates have been rendered.
Whenever I need to do this sort of thing I tend to have an 'init' (or whatever) function on my ko view model that I call after applyBindings has completed;
So:
var ViewModel = function(){
var self=this;
//blah
self.init = function(){
//jquery targeting template elements
}
}
var vm = new ViewModel();
ko.applyBindings(vm);
vm.init();
I'm really just starting into knockout and so far it is pretty impressive. With that, there is also an issue I am running into.
I'm trying to take a list of binded objects and when on is clicked, I want it take that model and bind it to another view for further editing, no issues there. The problem I run into is that I get an error saying I cannot bind multiple times to the same element. I have read the ko.cleanNode([domElement]) can be used to circumvent it, but that is really meant to be used internally. I have done some searching and people mention you should use with but I don't think that applies to what I want to do.
Is there a way to apply the bindings but then just update the underlying model context?
Simplified jsfiddle example. My js code starts on like 119, above it the ko mapping plugin.
First, use the external resources to add external resources to your fiddle rather than pasting them into your fiddle.
Second, on your problem, the easiest way to do something like this is to have a SelectedItem property in your view model and bind the child view to it. Something like this:
var MyViewModel = function() {
var self = this;
self.MyItems = ko.observableArray();
self.SelectedItem = ko.observable();
}
var MyChildViewModel = function() {
var self = this;
self.SomeProperty = ko.observable();
}
Now populate MyItems with instances of MyChildViewModel and set SelectedItem to whichever item in MyItems you want to have as your child and bind the child view to SelectedItem:
<div id="theChildView" data-bind="with: SelectedItem">
<span data-bind="text: SomeProperty"></span>
</div>
In a video tutorial on the Knockout.js homepage, the presenter sets up a simple example in which the ViewModel contains an observableArray property containing instances of a custom object (a "friend" object, in this case):
function friend(name) {
return {
name: ko.observable(name),
remove: function () {
viewModel.friends.remove(this);
}
};
}
var viewModel = {
friends: ko.observableArray()
}
ko.applyBindings(viewModel);
This is then rendered using a KO template:
<script id="friendsTemplate" type="text/html">
<li>
<input data-bind="value: name" />
<button data-bind="click: remove">Remove</button>
</li>
</script>
It seems odd to me that the logic for removing a friend object (the Model?) from the ViewModel's friends collection is implemented in the friend object itself. Doesn't this create an undesirable coupling between the friend object and the ViewModel instance? Is there a more consistent design approach that would allow the removeFriend functionality to be defined in the ViewModel rather than in the friend object?
A more common pattern would be to put a removeFriend function one level higher and the bind to it using:
<button data-bind="click: $parent.removeFriend">Remove</button>
When Knockout calls the handler from the click or event binding it will pass the current data item as the first argument. It will also set the context (this) equal to the current data as well. Depending on your structure, you may have to bind the function to ensure that it is called with the appropriate context or use a strategy like var self = this;.
Since, viewModel is an object literal, in this case removeFriend could look like:
friends: ko.observableArray(),
removeFriend: function(friend) {
viewModel.friends.remove(friend);
}
Issue:
I'm still learning knockoutJS, please guide me if my approach is wrong.
Here is my fiddle: http://jsfiddle.net/amitava82/wMH8J/25/
While onclick of edit, I receive the json model which is represented in the view and I want to remove certain items (child array) or actions (parent array) from the model (I removed add UI to add more Actions from the fiddle for simplicity) and then finally pass the model back to server.
Now, deleting from root level is easy. I'm stuck with deleting individual item which is ActionParamaters in ActionItems array.
Question:
How do I remove an item from a child array?
You can pass the clicked actionItem and the containing action array to deleteActionItem function as follows:
<!-- /ko -->
remove item
In your model you need to make every actionItem array observable using ko.mapping plugin (see edit function)
var viewModel = function() {
var self = this;
self.data = ko.observable();
self.edit = function() {
self.data ( ko.mapping.fromJS(editData) );
}
self.log = function() {
console.log(self.data())
}
self.deleteAction = function(data) {
//delete root node
self.data().remove(data)
}
self.deleteActionItem = function(data,actionItem) {
//delete items
data.ActionItems.remove(actionItem);
}
}
Then you will be able to remove the item from array in the deleteActionItem function and since the array is observable now, the result will reflect to binded dom element.
Sam, your fiddle data was too complicated. When asking questions, you will improve your chance of getting help if you distill your fiddle down to the relevant elements. I have cooked up a simple fiddle that illustrates nested arrays, and removal.
Here is the HTML, note that the remove function is inside the context of the array, so it calls a function on $parent instead of $root. This lets us target the context directly above, instead of the root.
<ul data-bind="foreach: editData">
<li>
<span data-bind="text: name"></span>
<button data-bind="click: $parent.removeParent">Remove Parent</button>
...
<!-- This line is on the child context -->
<button data-bind="click: $parent.removeChild">Remove Child</button>
</ul>
Here is the parent model. Note the removal function here is for removing children. When the removeChild function is called, it is from the child context asking for $parent, which will call this remove.
var Parent = function(name, children) {
var self = this;
self.name = ko.observable(name);
self.children = ko.observableArray(children);
self.removeChild = function(child) {
self.children.remove(child);
};
};
Your fiddle also makes no use of models, which are an important aspect of MVVM development. You should consider going through the tutorials on the knockout site to get a better understanding of how to structure knockout applications. It will help you deal with problems like this much easier.