how to use ractive as view component in backbone.js [closed] - backbone.js

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
I am new to ractive.js and want to integrate it with backbone.js. I found a backbone adapter for ractive howevere didn't found a sample which will show to use it effectively.
Need a sample that explain how to use ractive.js as a view component in backbone.js

Since the official demo seems to be down, I was able to put together a simple demo based on the rest of documentation.
Basically include ractive and ractive adapter for backbone after backbone and dependencies, then in your view's render method initialize a new ractive view passing it the backbone view's element, template and model data as el, template and data respectively as shown in below example.
For one way bindings, use the model getter method like {{model.get('prop')}}
and for two way bindings directly refer the property like {{model.attributes.name}}
Also, to avoid possibility of memory leak, override backbone view's remove method and have it destroy it's ractive view before removing itself.
Hope the comment explains the process:
// Nothing special, create a model instance with data
var model = new Backbone.Model({
name: "John"
});
// Backbone view constructor
var View = Backbone.View.extend({
template: $('#ractiveTest').html(),
initialize: function() {
this.render();
},
events: {
'click button': 'remove'
},
render: function() {
//initialize ractive view as part of rendering
this.ractive = new Ractive({
el: this.el, // pass the view's element to ractive
template: this.template, // pass the view's template to ractive
data: {
user: this.model // pass view's model data to ractive
}
});
},
//override view's remove method to destroy ractive instance as well
remove: function() {
// Just for added safety
this.ractive.teardown();
Backbone.View.prototype.remove.call(this);
}
});
// initialize the view and pass in the model.
var view = new View({
model: model
});
// append the view to DOM
view.$el.appendTo(document.body);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.3.1/backbone.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ractive/0.7.3/ractive.js"></script>
<script src="//cdn.jsdelivr.net/ractive.adaptors-backbone/latest/ractive-adaptors-backbone.min.js"></script>
<script type="text/template" id="ractiveTest">
<label>
Enter your name:
<input value="{{user.attributes.name}}">
</label>
<p>Hello, {{user.get('name')}}!</p>
<button>remove</button>
</script>
I find it a bit strange that we have to do user.get('name') in the template for one way binding and user.attributes.name for two way binding.
It could've been abstracted away in the Backbone adapter likeuser.name likes rivets does.

Related

Backbone.js: undelegateEvents not removing events

In the following code:
HTML
<div id="myView">
<button id="test_button">
Test Button
</button>
<ul id="output"></ul>
</div>
JavaScript
var myView = Backbone.View.extend({
initialize: function() {
// why doesn't this remove the previously delegated events?
this.undelegateEvents();
this.delegateEvents({
'click #test_button': 'buttonClicked'
});
},
// this event fires twice for one button click
buttonClicked: function() {
$("#output").append('<li>Button was clicked</li>');
}
});
$(document).ready(function(){
new myView({el: "#myView"});
// instantiate view again
new myView({el: "#myView"});
});
why does
this.undelegateEvents();
in the initialize() method of the Backbone View not remove the previously delegated events from the previous instantiation of the View?
JSFiddle example of above code: https://jsfiddle.net/billb123/o43zruea/28/
I'll try not to shout but please stop trying to bind views to existing elements. Let the view create and own its own el, then call view.remove() to kill it off before replacing it. This simple change solves so many problems with view events that you should always think twice (and twice more) if you don't do it this way.
In your case, you'd have HTML like this:
<script id="t" type="text/x-underscore">
<div id="myView">
<button id="test_button">
Test Button
</button>
</div>
</script>
<div id="container">
</div>
<ul id="output"> <!-- This is outside the container because we're going to empty and refill it -->
</ul>
And your JavaScript would look like this:
var myView = Backbone.View.extend({
events: {
'click #test_button': 'buttonClicked'
},
render: function() {
this.$el.html($('#t').html());
return this;
},
buttonClicked: function() {
$("#output").append('<li>Button was clicked</li>');
}
});
$(document).ready(function(){
var v = new myView();
$('#container').append(v.render().el);
v.remove(); // <----------------- Clean things up before adding a new one
v = new myView();
$('#container').append(v.render().el);
});
Points of interest:
Create the view then render it then put it on the page.
Call remove on the view when you're done with it.
The view goes inside the container. The caller owns the container, the view owns its el.
There are no delegateEvents or undelegateEvents calls anywhere. The presence of those almost always point to structural problems in your application IMO.
Each view is self contained: the outside world doesn't play with anything inside the view and the view keeps its hands to itself.
Updated fiddle: https://jsfiddle.net/bp8fqdgm/
But why didn't your attempted undelegateEvents do anything? undelegateEvents looks like this:
undelegateEvents: function() {
if (this.$el) this.$el.off('.delegateEvents' + this.cid);
return this;
},
The cid is unique per view instance so each view instance uses its own unique namespace for events that delegateEvents binds. That means that this:
this.undelegateEvents();
this.delegateEvents();
is saying:
Remove the events that this instance of the view has bound. These events will be found in the the '.delegateEvents' + this.cid namespace where cid is unique for each view instance.
Bind the events that this instance of the view defines (or the events in the delegateEvents call). These events will be attached using the '.delegateEvents' + this.cid namespace.
So your undelegateEvents call is removing events but not all of them, only the specific event bindings that that view instance adds are removed.
Your this.undelegateEvents() call doesn't actually accomplish anything because it is in the wrong place and called at the wrong time. If the new View caller did the undelegateEvents call:
var v = new myView({el: "#myView"});
v.undelegateEvents();
new myView({el: "#myView"});
then it would happen in the right place and at the right time. Of course this means that your router needs to keep track of the current view so that it can currentView.undelegateEvents() at the right time; but if you're doing that then you'd be better off (IMO) taking the approach I outlined at the top of the answer.

When I define a backbone view does the view element have to be loaded into the DOM?

I have some code which defines a backbone view being loaded as soon as a web page is loaded. The JavaScript is possibly executed before the DOM is fully loaded. If the dom element which becomes $el is not available when the code that defines the view is run is this a problem?
Programmatically I have something like this:
var view = Backbone.View.extend({
el: jQuery("#test")
events: {
}
render: function() {
this.$el.html();
}
//other view code
});
return view;
//some time passes
//with the view rendered above I now call :
view.render()
the problem is that when the render method is called this.$el is undefined. This is because when the first block of code was executed #test had not been loaded into the DOM. So; when the function Backbone.View.extend is called the 'el' element has to be loaded?
The context of this is a backbone application loaded via AMD. The first block of code is in a module. The module is 'required' before the DOM is loaded. Is this a common problem? How is it normally dealt with?
Thanks
el should not be a jQuery object but only a selector, $el will be the jQuery object for that selector. Also the id you specify as the el has to be found at initialization. So yes, the element has to be in the dom before you make it the el for the view.
If you're not creating / populating the view before making it a Backbone view you could do something like this to have backbone create the html tags etc. for you:
var view = Backbone.View.extend({
tagName: 'div',
id: 'test',
events: {
},
render: function() {
this.$el.html();
},
//other view code
});
You could then use templates to populate the view in your render function.
Short answer: Yes, the view element has to be loaded into the DOM when you initialize the view.

How to update data after model fetch in Handlebars template with Backbone and Marionette?

Trying to have a data field in a Handlebars template update after the model that is assigned to the Marionette CompositeView is fetched, but the HTML in the page is not getting updated.
My code looks like this:
Model:
B.Page.Model = Backbone.Model.extend({
url: function () {
return 'my/resource/';
},
});
View:
B.Page.CompositeView = Backbone.Marionette.CompositeView.extend({
template: Handlebars.compile(templates.find('#my-template').html()),
initialize: function(options) {
_.bindAll(this);
this.model.fetch();
},
)};
Template:
<script id="my-template" type="text/x-handlebars-template">
Date: <span id="my-data-field">{{data}}</span>
</script>
I have checked the resource and it does return proper JSON with the data field set. Also, the model is getting passed in to the view.
I suspect that this is due to the render function not getting called after the data is retrieved; however, I would like to get feedback on how it should be done.
What is a good way to do this?
Thanks!
EDIT: This CompositeView does have a Collection that is associated with it (which renders just fine when I trigger the appropriate event). I purposefully left out that part of the code to avoid muddying up the problem.
Your are right, since a CompositeView extends from CollectionView, it only re-renders on collection events by default. To make it re-render on changes on your model, you could do something like this in your CompositeView:
initialize: function(){
this.listenTo(this.model, "change", this.render);
}
All Marionette views have a modelEvents object that is bound to the passed in model. So you could clean the accepted answer up slightly by doing:
template: Handlebars.compile(templates.find('#my-template').html()),
modelEvents: {
'change': 'render'
}
rather than binding manually in initialize.

Using Marionette.ItemView for views without models?

Is it conventional to use Marionette.ItemView for view classes that do not have a specific model property associated with them?
As Marionette.View is not meant to be used directly, it seems like an ItemView makes sense as a view class with convenient defaults and bindings.
Or, should one just resort to using Backbone.View? If so, is there a way to hook Backbone.View into Marionette's evented and garbage-collected architecture?
Thank you for clarification!
ItemView can be used without a model. I do this quite regularly.
If you need to specify data for an ItemView, but not have that data in a Backbone.Model, you need to override the serializeData method:
MyView = Marionette.ItemView.extend({
serializeData: function(){
return {
my: "custom data"
};
}
});
the base Marionette.View isnt' meant to be used directly because it doesn't provide a render function on it's own. That doesn't mean you can't use it to create your own base view types, though. You could, for example, build a view type for your application that deals with rendering google maps or a third party widget or something else that doesn't need the general Backbone.Model based rendering that ItemView has in it.
I just found out you can use a templateHelper for this - just chuck this in your ItemView declaration:
templateHelpers: function() {
return {
message: this.message,
cssClass: this.cssClass
}
}
And then in your template:
<script type="text/html" id="notice-template">
<span class="<%= cssClass %>"><%= message %></span>
</script>
And then when you initialise the view:
var noticeView = new App.Views.Notice();
noticeView.message = "HELLO";
App.noticeRegion.show(noticeView);
I would be interested in your thoughts on this Derick?

Rendering problem

From the backbone documentation:
All views have a DOM element at all times (the el property), whether they've already been inserted into the page or not.
I have following very simple javascript file:
CBBItem = Backbone.Model.extend(
{
});
CBBTrackItem = Backbone.View.extend(
{
template: _.template("<span><%= title %></span>"),
initialize: function()
{
_.bindAll(this, "render");
},
render: function()
{
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
And a html page like this:
<script type="text/javascript">
$(function()
{
var itm1 = new CBBItem({ title: 'track 1'});
var itmUI1 = new CBBTrackItem({ model: itm1, id: "kzl" });
itmUI1.render();
});
</script>
<body>
<div id="kzl"></div>
</body>
My view item doesn't want to render although there is a created div on the page. I can trick the situation in many ways. For example doing something like this
var itm1 = new CBBItem({ title: 'track 1'});
var itmUI1 = new CBBTrackItem({ model: itm1, id: "big_kzl" });
$(itmUI1.render().el).appendTo("#kzl");
But, why is the main case not working?
Here's one possibility: you aren't setting the el for the view, so it doesn't know what to do with your template. Could you modify your view-calling code to look like this?
var itmUI1 = new CBBTrackItem({
model: itm1,
id: "big_kz1",
el: "#kz1"
});
itmUT1.render();
Alternatively, you could set the el value within the initialize of the view if the value never varies. The advantage to doing so is that callers of the view don't have to know this information and thus the view is more self-contained.
If the document already has the element you want to use as el for a particular view, you have to manually set that dom element as the el attribute when the view is initialized. Backbone provides you no shortcut for doing that.
I've experienced problems when passing values like ID and events in during construction as opposed to defining them during extension. You may want to check and see if that's the difference you're looking for.

Resources