I'm writing a sample app using backbone.js.
On update of my model I re-render my view in this fashion
$('.target').html("");
$('.target').append(this.$el.html(this.template(model)))
Once the view is re-rendered after model update [on change event], events attached to the el's children gets lost [doesn't seem to be like jQuery live]. Is this a known issue or am I missing something? Should I try to replace html instead of append? fiddle
Once the view is in DOM, you don't need to keep removing and appending it. I think the simplest way to manage this is to remove the DOM insertion from the view altogether, and let the caller of view.render to take care of it.
View:
render: function() {
this.$el.html(this.template(model));
return this;
}
Caller (on first render):
var view = new SomeView();
$('.target').append(view.render().el);
On subsequent renders:
view.render();
After the view has been rendered into DOM it can keep happily re-rendering itself without having to know anything about parent views. The event bindings should also stay intact between renders.
Related
I have a Backbone view and I'm using Handlebars to load templates in my view.
I have a list #list in the template that have to be updated in the view and after that it needs to be re-rendered.
I want to re-render only the part that has changed so only #list without reload all my DOM (and so all the Handlebars helpers that I have).
I tried this:
template: Handlebars.compile(templateHtml),
render: function() {
this.$el.html(this.template(this.model.toJSON());
},
renderList: function() {
var html = this.template(this.model.toJSON());
var selector = "#list";
this.$el.find(selector).replaceWith($(selector, html));
}
but this is very slow because I think it re-renders all DOM before changing the #list content as I need.
Is there any better way to do that?
Is it a good idea to put the content of #list in another template and load it as a subView?
In this case how can I trigger an update of the list from my first view?
Thanks
It is better to update just the part of your rendered view by creating a dedicated render method, something I explained here.
In your situation it looks like you have to build whole html view from template, just to update the part of it. Which is neat, if you want to keep the template as one template.
If there really a lot of going on in the template beyond the list, you could have two templates in your view, one will be the general one, and the other one just for the list, which you will mount into the first one on global render, with the benefit of being able to update just the list.
Further, you might not even need a second template if the #list is simply a container for a list. I can imagine that might be an <ul> tag which you will mount at the right place during the global render, and then update it with other custom render methods.
Also, I wrote an example here, which might be potentially helpful in your case:
Backbone.js fetch() JSON to model get() returnes undefined - in the example there's a template, that has a container for a list, and the list is managed separately by it's own collection.
Just some thoughts.
I'm working on a control panel application right now, where each tool loads its own Javascript file, most of which contain some Knockout bindings. Knockout itself is being loaded in the document head, but tools are loaded asynchronous into a #body div, so my concern is that elements will continue to be bound, even after a different tool is loaded. I assume this would result in memory leaks and probably some glitches, if the same element is bound multiple times. Is it possible to clear all Knockout bindings at once, before I load a new tool?
The general pattern that I would recommend is something like:
//obviously doesn't have to be an object literal
var viewModel = {
currentTool: ko.observable()
};
ko.applyBindings(viewModel);
Then, bind your page like:
<div data-bind="with: currentTool">
...content here
</div>
Now, when the page is initially bound, the area will not be rendered as currentTool is undefined, but KO will copy off the children to use as a "template".
When you populate the currentTool observable, it will render a copy of the elements and bind the content.
When you change currentTool, then KO will clean up the existing bindings and elements, and render/bind a new copy of the elements.
So, you only call ko.applyBindings once and continue to update currentTool based on what you want to display.
This is killing me, being reading the examples on this site but can't figure out why it works like this.
I want to pass back values to my view, which has buttons that you can use to change the values.
If I use the following
this.$el.empty().html(view.el)
View.el contains the correct html, but those not render on the screen. If I use the following
$("#handicap").html( view.el);
The values get displayed on screen but the events no longer get picked up eventhough if I put an onclick function in the html code it kicks off.
Ideally I would like to get this.$el.empty().html(view.el) working. It has to do with context but can't see why.
I have created a jsbin here http://jsbin.com/iritex/1/edit
If I have to use $("#handicap").html( view.el), do I need to do something special to unbind events. I have tried undelegate everything but that didn't do the trick either.
thanks
A Backbone View's el property will always contain a reference to a valid DOM object. However, that DOM object may or may not be in your display tree. It's up to you to make sure it's in the display tree when you need it to be. This functionality lets Backbone maintain the state of it's View element without it being rendered to the screen. You can add and remove a view from the screen efficiently, for example.
There are a few ways to get your View's element into the display tree.
1) Associate the view with an existing DOM element on the page by passing in a jquery selector to the initializer as the "el" property.
var view = new MyView({el: '#MyElementSelector'});
2) Associate the view with an existing DOM element on the page by hardcoding the jQuery selector it into the view's "el" property.
var MyView = Backbone.View.extend({
el: '#MyElementSelector'
});
3) Render it to the page from within another view
var view = new MyView();
view.render();
this.$el.empty().html(view.el);
If you're interested, I show examples in a Backbone Demo I put together.
You need to put both views into the DOM. Wherever you create the view that above is this needs to be inserted into the DOM. If you do that, then the first line will work fine this.$el.empty().html(view.el).
This question demonstrates that overriding an Ember.View instance's didInsertElement allows you to execute some code after the view's element is in the DOM.
http://jsfiddle.net/gvUux/2/
Naturally, overriding didInsertElement on the child view class you add to an Ember.CollectionView will run the hook after each child view is rendered and inserted.
http://jsfiddle.net/BFUvK/1/
Two collection-oriented hooks on Ember.CollectionView, arrayDidChange and contentDidChange, execute after the underlying content has changed, but they execute before any rendering takes place. arrayDidChange is executed for every element added to the array, and contentDidChange wraps the content binding.
I would like to be able to hook around the rendering pipeline, something like willInsertCollection and didInsertCollection, to manipulate the DOM before and after all child elements are rendered - essentially, before and after filters around contentBinding.
Any ideas? I'm stumped.
If you want to want to do something before and/or after a view has been rendered you should use willInsertElement and/or didInsertElement respectively. In this case, since you want "to manipulate the DOM before and after all child elements are rendered" you should call those on your CollectionView.
I'm not quite sure what you mean by "before and after filters around contentBinding", so if this doesn't answer your question if you could clarify I'd be happy to help.
jsFiddle if needed
I wanted to apply a scroll animation to slide a list up after pushing new objects. The list was rendered using an ArrayController and the #each helper. Simply triggering an event on the controller which the view subscribed to after pushing objects was causing the animation to execute before the changes to the content were actually rendered. The following technique worked perfectly for me.
//excerpt from my loadMore method on the ArrayController
var self = this;
self.content.pushObjects(moreItems);
Ember.run.scheduleOnce('afterRender', this, function()
{
self.trigger('loadMoreComplete');
});
when I inject any new elements into the DOM after ko.applyBindings(); was called, then knockout won't recognize these new elements.
I can understand why this is happening - they are just not indexed by knockout.
So, at first I thought this would be solved by just calling ko.applyBindings() again, after adding my new elements, BUT then I realized that for every ko.applyBindings() call you make, the according events get fired multiple times. So after applying five times, a click: binding will be fired five times, so this is not a desireable solution ;)
Is there anything like ko.updateBindings() or something else, to tell knockout to, well... update the element bindings?
greetings,
Chris
Each time you invoke ko.applyBindings the entire DOM is inspected for bindings. As a result you will get multiple bindings for each element if you do this more than once. If you just want to bind a new DOM element you can pass this element as a parameter to the applyBindings function:
ko.applyBindings(viewModelA, document.getElementById("newElement"));
See this related question:
Can you call ko.applyBindings to bind a partial view?
Without knowing what you're up to exactly, it seems like you're going the wrong way about this. Your view should be driven by your view model. So you shouldn't be directly adding DOM elements you then need to apply knockout bindings to.
Instead you should be updating your view model to reflect the change in the view, which then causes your new element to appear.
So for example, for your $('body').append('Click me!');, rather than adding the DOM element when the button should be visible, control the button visibility using the view model.
So your view model includes
var viewModel = { clickMeAvailable: ko.observable(false) }
And your HTML includes
Click me!
When the application state changes so the click me button is available, you then just viewModel.clickMeAvailable(true).
The point of doing this, and a big part of knockout, is to separate business logic from presentation. So the code that makes click me available doesn't care that click me involves a button. All it does is update viewModel.clickMeAvailable when click me is available.
For example, say click me is a save button that should be available when a form is filled in validly. You'd tie the save button visibility to a formValid view model observable.
But then you decide to change things so after the form is valid, a legal agreement appears which has to be consented to before saving. The logic of your form doesn't change - it still sets formValid when the form is valid. You would just change what occurs when formValid changes.
As lassombra points out in the comments on this answer, there are cases when direct DOM manipulation may be your best approach - for example a complex dynamic page where you only want to hydrate parts of the view as they are needed. But you are giving up some of the separation of concerns Knockout provides by doing this. Be mindful if you are considering making this trade-off.
I just stumbled upon a similar problem. I tried to add new elements to container and give those a onclick function.
At first tried the things you did, and even tried the approach ColinE recommended. This wasn't a practical solution for me so I tried SamStephens approach and came up with that, which works perfectly for me:
HTML:
<div id="workspace" data-bind="foreach:nodeArr, click:addNode">
<div class="node" data-bind="attr:{id:nodeID},style:{left:nodeX,top:nodeY},text:nodeID, click:$parent.changeColor"></div>
</div>
JavaScript:
<script>
function ViewModel() {
var self = this;
var id = 0;
self.nodeArr = ko.observableArray();
self.addNode = function (data, event) {
self.nodeArr.push({
'nodeID': 'node' + id,
'nodeX' : (event.offsetX - 25) + 'px',
'nodeY' : (event.offsetY - 10) + 'px'
})
id++;
}
self.changeColor = function(data, event){
event.stopPropagation();
event.target.style.color = 'green';
event.target.style.backgroundColor = 'white';
}
}
ko.applyBindings(new ViewModel());
</script>
You can play with it in the JS Fiddle I made.