I'm trying to build a basic web application with Backbone.JS and already encounter understanding issues in the very beginning.
I was thinking about the following HTML structure:
<script type="text/template" class="t_show">FOO</script>
static foobar
<script type="text/template" class="t_show">BAR</script>
where static foobar always gets rendered - .t_show however only, when the router matches #show.
That's my current backbone code:
var v_show = Backbone.View.extend({
el: $(".client"),
template: _.template( $( '.t_show' ).html() ),
render: function() {
this.$el.html(this.template(this.model.attributes));
return this;
},
that kind of already works, however only renders the very first template-partial (FOO) and the static code (static code), but no (BAR).
Ling story short: How to realise template snippets belonging to the same view in Backbone?
jquery's .html() function will only return the html of the first matched element, so calling _.template($('.t_show').html()) will only pass FOO to the template function.
There's a couple ways you could resolve this:
If you want FOO and BAR to render right next to each other (styling aside), you can combine them into a single template. I'm guessing this doesn't work for your use case or you wouldn't have asked about supporting multiple templates, but included it for completeness.
Iterate through each .t_show and add the html() bits together, and pass the result to _.template(). This also will show FOO and BAR next to each other.
Assign each template to its own variable and then execute and render the templates separately.
That last option has the most flexibility as you can place the resulting html in different parts of your view if you wish, or you could re-render them individually which is handy if they're fairly isolated or either is kind of heavy to render. That might look something like this:
fooTemplate: _.template($('.foo.t_show').html()),
barTemplate: _.template($('.bar.t_show').html()),
render: function() {
this.$el.html(this.fooTemplate(this.model.attributes));
this.$el.append(this.barTemplate(this.model.attributes));
return this;
},
...
Related
Question about MVVM and data binding in Kendo Mobile:
account.js:
define([], function () {
return {
userPhone: 111
};
});
index.html:
<p>Phone: <span id="test-span" data-bind="html: userPhone"></span>.</p>
home-view.js:
define(["kendo", "app/account"], function (kendo, account) {
var viewModel = kendo.observable({
userPhone: account.userPhone
});
return {
show: function() {
viewModel.set("userPhone", account.userPhone); // LINE A
account.userPhone = "222"; // LINE B
},
viewModel: viewModel
}
});
Without LINE A and LINE B, #test-span displays (null)
With only LINE A, #test-span displays "111"
With only LINE B, #test-span displays (null)
I understand why #2 behaves the way it does. I just doesn't understand why #1 and #3 behave as they do. I thought the whole point of MVVM and data-bindings is that I could update account.userPhone and have it update views globally without having to do viewModel.set.
Assuming I have home-view2.js, home-view3.js, etc, how can I update all viewModels will changing just the account property?
Line B would work or not depending on the framework used, in this case KendoUI is not dirty-checking based. This means setting account.userName directly will not work, the updates need to be done by calling special setters in model classes such as in line A.
For example AngularJs is based on dirty checking, so line B would work if put on a controller or called inside $apply, and there is no need for code like line A.
The way angular dirty checking works is by taking a snapshot of a plain javascript object, and then at appropriate moments (on event callbacks, ajax callback and setTimeouts) take another another snapshot.
If the two snapshots differ, all the components observing account.userName are updated, for example DOM elements - and this is how angular bidirectional binding with plain javascript objects works.
Have a look at angular KendoUI for an Angular library based on the Kendo widgets.
If you are interested in dirty checking and how it works, have a look at this podcast by the Angular authors, and this answer from them, where a comparison with framework like Knockout or Backbone is made.
I'm trying to render two instances of an ItemView into different regions. When I do this, only one region appears with the working javascript. Both instances have their html elements.
I have two files that are both Layouts. One calls
var monthView = new CalendarView({ parentDivName: "full_screen" });
App.mainRegion.show(monthView);
The other calls:
var monthView2 = new CalendarView({ parentDivName: "full_screen22" });
App.datePickerRegion.show(monthView2);
Does anyone have a fiddle they can share where two instances of an ItemView are rendered on the same page where the javascript within the ItemView can be visible? Or, am I going about this all wrong? Is there a better way to create multiple instances of the same ItemView?
I imagine the problem lies elsewhere in your code as this is both valid code and a valid approach to rendering multiple instances of an ItemView. Without more code, it will be hard to figure out what exactly is the problem. One thing to check is that the layout has been rendered before trying to render something in one of its regions. Here's a simple jsfiddle that demos what I think you're going for: http://jsfiddle.net/craigjennings11/jxu9j/
var Layout = Backbone.Marionette.Layout.extend({
el: '.layout',
template: _.template('<div class="view1"></div><div class="view2"></div>'),
regions: {
view1: '.view1',
view2: '.view2'
}
});
ContainerView.pushObject() does not automatically wire-up dynamically added views with a Container object.
The lack of an auto-wired container causes a render failure when a view renders a template that contains a handlebars render helper.
SIMPLE CASE THAT WORKS (KIND OF)
View:
App.DynamicView = Em.View.extend({
templateName: 'dynamic',
didInsertElement: function() {
var control = this.get('controller');
control.send( 'view_inserted', this.templateName, control._debugContainerKey);
control.send('callDynamicController');
}
});
Template:
<script type="text/x-handlebars" data-template-name="dynamic">
dynamic
</script>
Controller (only used when manually assigned):
App.DynamicController = Em.ObjectController.extend({
className: 'App.DynamicWithRenderController',
callDynamicController: function() {
console.log('DynamicController.callDynamicController()');
}
});
Index Controller:
App.IndexController = Em.ObjectController.extend({
view_inserted: function(aview, acontroller) {
console.log('view inserted!', aview, acontroller);
}
})
Instantiation code:
var acontainer = App.DynamicController.create({});
var aview = App.DynamicView.create({ controller: acontroller })
acontainerView.pushObject(aview);
These classes render & behave as expected, but if you interogate them, lack some of the Ember-wiring (e.g. no _debugContainerKey & container properties IIRC):
MORE ADVANCED CASE THAT BREAKS
If we introduce a handlebars template that uses rendering helpers, it breaks rendering. The dynamically added view currently lacks some properties the rendering helper assumes
<script type="text/x-handlebars" data-template-name="dynamic-with-render">
dynamic w/render:
{{render knob}}
</script>
and make knob look like this:
<script type="text/x-handlebars" data-template-name="knob">
{{render knob}}
</script>
The (failing) dynamic view instantiation code:
var acontainer = App.DynamicController.create({});
var aview = App.DynamicView.create({
controller: acontroller,
template:'dynamic-with-render' })
acontainerView.pushObject(aview);
CODE EXAMPLE
A fuller example with some notes can be seen here:
http://jsfiddle.net/AshCoolman/KyJ2U/6/embedded/result/
NOTE: My tests include a custom handlebars helper based of the control helper called controlWithVars
THE PROBLEM
It looks like I need to write something that does the Ember-wiring, in either:
the more native ContainerView (getting into the Ember guts), OR
a more de-coupled new render helper possibly inelegant)
I'm not sure how to proceed. It would be great if someone has already come up with an elegant solution, or at least could give me some helpful tips.
EDIT: So it looks like creating and assigning a container, which includes the views dependencies might be a solution. Thoughts anyone?
HELPFUL READING
https://github.com/emberjs/ember.js/issues/2108
What is the purpose of the Ember.Container
http://mcdowall.info/posts/ember-application-initializers/
The raison d'ĂȘtre of Ember.ContainerView is for dynamically adding and removing views, so I'm pretty confident you can do all the things you want to with it.
One thing I noticed in your examples is that you are creating your child views with View.create(attrs). It is important to use containerView.createChildView(viewClassName, attrs) to create views that get the container, parent view hierarchy, and more. See the implementation more details:
https://github.com/emberjs/ember.js/blob/master/packages/ember-views/lib/views/view.js#L2072
I'm building functionality onto a webpage which the user can perform multiple times. Through the user's action, an object/model is created and applied to HTML using ko.applyBindings().
The data-bound HTML is created through jQuery templates.
So far so good.
When I repeat this step by creating a second object/model and call ko.applyBindings() I encounter two problems:
The markup shows the previous object/model as well as the new object/model.
A javascript error occurs relating to one of the properties in the object/model, although it's still rendered in the markup.
To get around this problem, after the first pass I call jQuery's .empty() to remove the templated HTML which contains all the data-bind attributes, so that it's no longer in the DOM. When the user starts the process for the second pass the data-bound HTML is re-added to the DOM.
But like I said, when the HTML is re-added to the DOM and re-bound to the new object/model, it still includes data from the the first object/model, and I still get the JS error which doesn't occur during the first pass.
The conclusion appears to be that Knockout is holding on to these bound properties, even though the markup is removed from the DOM.
So what I'm looking for is a means of removing these bound properties from Knockout; telling knockout that there is no longer an observable model. Is there a way to do this?
EDIT
The basic process is that the user uploads a file; the server then responds with a JSON object, the data-bound HTML is added to the DOM, then the JSON object model is bound to this HTML using
mn.AccountCreationModel = new AccountViewModel(jsonData.Account);
ko.applyBindings(mn.AccountCreationModel);
Once the user has made some selections on the model, the same object is posted back to the server, the data-bound HTML is removed from then DOM, and I then have the following JS
mn.AccountCreationModel = null;
When the user wishes to do this once more, all these steps are repeated.
I'm afraid the code is too 'involved' to do a jsFiddle demo.
Have you tried calling knockout's clean node method on your DOM element to dispose of the in memory bound objects?
var element = $('#elementId')[0];
ko.cleanNode(element);
Then applying the knockout bindings again on just that element with your new view models would update your view binding.
For a project I'm working on, I wrote a simple ko.unapplyBindings function that accepts a jQuery node and the remove boolean. It first unbinds all jQuery events as ko.cleanNode method doesn't take care of that. I've tested for memory leaks, and it appears to work just fine.
ko.unapplyBindings = function ($node, remove) {
// unbind events
$node.find("*").each(function () {
$(this).unbind();
});
// Remove KO subscriptions and references
if (remove) {
ko.removeNode($node[0]);
} else {
ko.cleanNode($node[0]);
}
};
You could try using the with binding that knockout offers:
http://knockoutjs.com/documentation/with-binding.html
The idea is to use apply bindings once, and whenever your data changes, just update your model.
Lets say you have a top level view model storeViewModel, your cart represented by cartViewModel,
and a list of items in that cart - say cartItemsViewModel.
You would bind the top level model - the storeViewModel to the whole page. Then, you could separate the parts of your page that are responsible for cart or cart items.
Lets assume that the cartItemsViewModel has the following structure:
var actualCartItemsModel = { CartItems: [
{ ItemName: "FirstItem", Price: 12 },
{ ItemName: "SecondItem", Price: 10 }
] }
The cartItemsViewModel can be empty at the beginning.
The steps would look like this:
Define bindings in html. Separate the cartItemsViewModel binding.
<div data-bind="with: cartItemsViewModel">
<div data-bind="foreach: CartItems">
<span data-bind="text: ItemName"></span>
<span data-bind="text: Price"></span>
</div>
</div>
The store model comes from your server (or is created in any other way).
var storeViewModel = ko.mapping.fromJS(modelFromServer)
Define empty models on your top level view model. Then a structure of that model can be updated with
actual data.
storeViewModel.cartItemsViewModel = ko.observable();
storeViewModel.cartViewModel = ko.observable();
Bind the top level view model.
ko.applyBindings(storeViewModel);
When the cartItemsViewModel object is available then assign it to the previously defined placeholder.
storeViewModel.cartItemsViewModel(actualCartItemsModel);
If you would like to clear the cart items:
storeViewModel.cartItemsViewModel(null);
Knockout will take care of html - i.e. it will appear when model is not empty and the contents of div (the one with the "with binding") will disappear.
I have to call ko.applyBinding each time search button click, and filtered data is return from server, and in this case following work for me without using ko.cleanNode.
I experienced, if we replace foreach with template then it should work fine in case of collections/observableArray.
You may find this scenario useful.
<ul data-bind="template: { name: 'template', foreach: Events }"></ul>
<script id="template" type="text/html">
<li><span data-bind="text: Name"></span></li>
</script>
Instead of using KO's internal functions and dealing with JQuery's blanket event handler removal, a much better idea is using with or template bindings. When you do this, ko re-creates that part of DOM and so it automatically gets cleaned. This is also recommended way, see here: https://stackoverflow.com/a/15069509/207661.
I think it might be better to keep the binding the entire time, and simply update the data associated with it. I ran into this issue, and found that just calling using the .resetAll() method on the array in which I was keeping my data was the most effective way to do this.
Basically you can start with some global var which contains data to be rendered via the ViewModel:
var myLiveData = ko.observableArray();
It took me a while to realize I couldn't just make myLiveData a normal array -- the ko.oberservableArray part was important.
Then you can go ahead and do whatever you want to myLiveData. For instance, make a $.getJSON call:
$.getJSON("http://foo.bar/data.json?callback=?", function(data) {
myLiveData.removeAll();
/* parse the JSON data however you want, get it into myLiveData, as below */
myLiveData.push(data[0].foo);
myLiveData.push(data[4].bar);
});
Once you've done this, you can go ahead and apply bindings using your ViewModel as usual:
function MyViewModel() {
var self = this;
self.myData = myLiveData;
};
ko.applyBindings(new MyViewModel());
Then in the HTML just use myData as you normally would.
This way, you can just muck with myLiveData from whichever function. For instance, if you want to update every few seconds, just wrap that $.getJSON line in a function and call setInterval on it. You'll never need to remove the binding as long as you remember to keep the myLiveData.removeAll(); line in.
Unless your data is really huge, user's won't even be able to notice the time in between resetting the array and then adding the most-current data back in.
I had a memory leak problem recently and ko.cleanNode(element); wouldn't do it for me -ko.removeNode(element); did. Javascript + Knockout.js memory leak - How to make sure object is being destroyed?
Have you thought about this:
try {
ko.applyBindings(PersonListViewModel);
}
catch (err) {
console.log(err.message);
}
I came up with this because in Knockout, i found this code
var alreadyBound = ko.utils.domData.get(node, boundElementDomDataKey);
if (!sourceBindings) {
if (alreadyBound) {
throw Error("You cannot apply bindings multiple times to the same element.");
}
ko.utils.domData.set(node, boundElementDomDataKey, true);
}
So to me its not really an issue that its already bound, its that the error was not caught and dealt with...
I have found that if the view model contains many div bindings the best way to clear the ko.applyBindings(new someModelView); is to use: ko.cleanNode($("body")[0]); This allows you to call a new ko.applyBindings(new someModelView2); dynamically without the worry of the previous view model still being binded.
<div id="books">
<ul data-bind="foreach: booksImReading">
<li data-bind="text: name"></li>
</ul>
</div>
var bookModel = {
booksImReading: [
{ name: "Effective Akka" },
{ name: "Node.js the Right Way" }]
};
ko.applyBindings(bookModel, el);
var bookModel2 = {
booksImReading: [
{ name: "SQL Performance Explained" },
{ name: "Code Connected" }]
};
ko.cleanNode(books);
ko.applyBindings(bookModel2, books);
I have three different ways to initialize and render a view and its subviews, and each one of them has different problems. I'm curious to know if there is a better way that solves all of the problems:
Scenario One:
Initialize the children in the parent's initialize function. This way, not everything gets stuck in render so that there is less blocking on rendering.
initialize : function () {
//parent init stuff
this.child = new Child();
},
render : function () {
this.$el.html(this.template());
this.child.render().appendTo(this.$('.container-placeholder');
}
The problems:
The biggest problem is that calling render on the parent for a second time will remove all of the childs event bindings. (This is because of how jQuery's $.html() works.) This could be mitigated by calling this.child.delegateEvents().render().appendTo(this.$el); instead, but then the first, and the most often case, you're doing more work unnecessarily.
By appending the children, you force the render function to have knowledge of the parents DOM structure so that you get the ordering you want. Which means changing a template might require updating a view's render function.
Scenario Two:
Initialize the children in the parent's initialize() still, but instead of appending, use setElement().delegateEvents() to set the child to an element in the parents template.
initialize : function () {
//parent init stuff
this.child = new Child();
},
render : function () {
this.$el.html(this.template());
this.child.setElement(this.$('.placeholder-element')).delegateEvents().render();
}
Problems:
This makes the delegateEvents() necessary now, which is a slight negative over it only being necessary on subsequent calls in the first scenario.
Scenario Three:
Initialize the children in the parent's render() method instead.
initialize : function () {
//parent init stuff
},
render : function () {
this.$el.html(this.template());
this.child = new Child();
this.child.appendTo($.('.container-placeholder').render();
}
Problems:
This means that the render function now has to be tied down with all of the initialization logic as well.
If I edit the state of one of the child views, and then call render on the parent, a completely new child will be made and all of its current state will be lost. Which also seems like it could get dicey for memory leaks.
Really curious to get your guys' take on this. Which scenario would you use? or is there a fourth magical one that solves all of these problems?
Have you ever kept track of a rendered state for a View? Say a renderedBefore flag? Seems really janky.
This is a great question. Backbone is great because of the lack of assumptions it makes, but it does mean you have to (decide how to) implement things like this yourself. After looking through my own stuff, I find that I (kind of) use a mix of scenario 1 and scenario 2. I don't think a 4th magical scenario exists because, simply enough, everything you do in scenario 1 & 2 must be done.
I think it'd be easiest to explain how I like to handle it with an example. Say I have this simple page broken into the specified views:
Say the HTML is, after being rendered, something like this:
<div id="parent">
<div id="name">Person: Kevin Peel</div>
<div id="info">
First name: <span class="first_name">Kevin</span><br />
Last name: <span class="last_name">Peel</span><br />
</div>
<div>Phone Numbers:</div>
<div id="phone_numbers">
<div>#1: 123-456-7890</div>
<div>#2: 456-789-0123</div>
</div>
</div>
Hopefully it's pretty obvious how the HTML matches up with the diagram.
The ParentView holds 2 child views, InfoView and PhoneListView as well as a few extra divs, one of which, #name, needs to be set at some point. PhoneListView holds child views of its own, an array of PhoneView entries.
So on to your actual question. I handle initialization and rendering differently based on the view type. I break my views into two types, Parent views and Child views.
The difference between them is simple, Parent views hold child views while Child views do not. So in my example, ParentView and PhoneListView are Parent views, while InfoView and the PhoneView entries are Child views.
Like I mentioned before, the biggest difference between these two categories is when they're allowed to render. In a perfect world, I want Parent views to only ever render once. It is up to their child views to handle any re-rendering when the model(s) change. Child views, on the other hand, I allow to re-render anytime they need since they don't have any other views relying upon them.
In a little more detail, for Parent views I like my initialize functions to do a few things:
Initialize my own view
Render my own view
Create and initialize any child views.
Assign each child view an element within my view (e.g. the InfoView would be assigned #info).
Step 1 is pretty self explanatory.
Step 2, the rendering, is done so that any elements the child views rely on already exist before I try to assign them. By doing this, I know all child events will be correctly set, and I can re-render their blocks as many times as I want without worrying about having to re-delegate anything. I do not actually render any child views here, I allow them to do that within their own initialization.
Steps 3 and 4 are actually handled at the same time as I pass el in while creating the child view. I like to pass an element in here as I feel the parent should determine where in its own view the child is allowed to put its content.
For rendering, I try to keep it pretty simple for Parent views. I want the render function to do nothing more than render the parent view. No event delegation, no rendering of child views, nothing. Just a simple render.
Sometimes this doesn't always work though. For instance in my example above, the #name element will need to be updated any time the name within the model changes. However, this block is part of the ParentView template and not handled by a dedicated Child view, so I work around that. I will create some sort of subRender function that only replaces the content of the #name element, and not have to trash the whole #parent element. This may seem like a hack, but I've really found it works better than having to worry about re-rendering the whole DOM and reattaching elements and such. If I really wanted to make it clean, I'd create a new Child view (similar to the InfoView) that would handle the #name block.
Now for Child views, the initialization is pretty similar to Parent views, just without the creation of any further Child views. So:
Initialize my view
Setup binds listening for any changes to the model I care about
Render my view
Child view rendering is also very simple, just render and set the content of my el. Again, no messing with delegation or anything like that.
Here is some example code of what my ParentView may look like:
var ParentView = Backbone.View.extend({
el: "#parent",
initialize: function() {
// Step 1, (init) I want to know anytime the name changes
this.model.bind("change:first_name", this.subRender, this);
this.model.bind("change:last_name", this.subRender, this);
// Step 2, render my own view
this.render();
// Step 3/4, create the children and assign elements
this.infoView = new InfoView({el: "#info", model: this.model});
this.phoneListView = new PhoneListView({el: "#phone_numbers", model: this.model});
},
render: function() {
// Render my template
this.$el.html(this.template());
// Render the name
this.subRender();
},
subRender: function() {
// Set our name block and only our name block
$("#name").html("Person: " + this.model.first_name + " " + this.model.last_name);
}
});
You can see my implementation of subRender here. By having changes bound to subRender instead of render, I don't have to worry about blasting away and rebuilding the whole block.
Here's example code for the InfoView block:
var InfoView = Backbone.View.extend({
initialize: function() {
// I want to re-render on changes
this.model.bind("change", this.render, this);
// Render
this.render();
},
render: function() {
// Just render my template
this.$el.html(this.template());
}
});
The binds are the important part here. By binding to my model, I never have to worry about manually calling render myself. If the model changes, this block will re-render itself without affecting any other views.
The PhoneListView will be similar to the ParentView, you'll just need a little more logic in both your initialization and render functions to handle collections. How you handle the collection is really up to you, but you'll at least need to be listening to the collection events and deciding how you want to render (append/remove, or just re-render the whole block). I personally like to append new views and remove old ones, not re-render the whole view.
The PhoneView will be almost identical to the InfoView, only listening to the model changes it cares about.
Hopefully this has helped a little, please let me know if anything is confusing or not detailed enough.
I'm not sure if this directly answers your question, but I think it's relevant:
http://lostechies.com/derickbailey/2011/10/11/backbone-js-getting-the-model-for-a-clicked-element/
The context in which I set up this article is different, of course, but I think the two solutions I offer, along with the pros and cons of each, should get you moving in the right direction.
To me it does not seem like the worst idea in the world to differentiate between the intital setup and subsequent setups of your views via some sort of flag. To make this clean and easy the flag should be added to your very own View which should extend the Backbone (Base) View.
Same as Derick I am not completely sure if this directly answers your question but I think it might be at least worth mentioning in this context.
Also see: Use of an Eventbus in Backbone
Kevin Peel gives a great answer - here's my tl;dr version:
initialize : function () {
//parent init stuff
this.render(); //ANSWER: RENDER THE PARENT BEFORE INITIALIZING THE CHILD!!
this.child = new Child();
},
I'm trying to avoid coupling between views like these. There are two ways I usually do:
Use a router
Basically, you let your router function initialize parent and child view. So the view has no knowledge of each other, but the router handles it all.
Passing the same el to both views
this.parent = new Parent({el: $('.container-placeholder')});
this.child = new Child({el: $('.container-placeholder')});
Both have knowledge of the same DOM, and you can order them anyway you want.
What I do is giving each children an identity (which Backbone has already done that for you: cid)
When Container does the rendering, using the 'cid' and 'tagName' generate a placeholder for every child, so in template the children has no idea about where it will be put by the Container.
<tagName id='cid'></tagName>
than you can using
Container.render()
Child.render();
this.$('#'+cid).replaceWith(child.$el);
// the rapalceWith in jquery will detach the element
// from the dom first, so we need re-delegateEvents here
child.delegateEvents();
no specified placeholder is needed, and Container only generate the placeholder rather than the children's DOM structure. Cotainer and Children are still generating own DOM elements and only once.
Here is a light weight mixin for creating and rendering subviews, which I think addresses all the issues in this thread:
https://github.com/rotundasoftware/backbone.subviews
The approach taken by this plug is create and render subviews after the first time the parent view is rendered. Then, on subsequent renders of the parent view, $.detach the subview elements, re-render the parent, then insert the subview elements in the appropriate places and re-render them. This way subviews objects are reused on subsequent renders, and there is no need to re-delegate events.
Note that the case of a collection view (where each model in the collection is represented with one subview) is quite different and merits its own discussion / solution I think. Best general solution I am aware of to that case is the CollectionView in Marionette.
EDIT: For the collection view case, you may also want to check out this more UI focused implementation, if you need selection of models based on clicks and / or dragging and dropping for reordering.