Backbone js: Accessing View variables from the template - ruby-on-rails

I am new to Backbone js and i have written a piece of code like this;
Skymama.Views.UsersIndex = Backbone.View.extend({
template: JST['users/index'],
render: function() {
var allUsers = new Skymama.Collections.Users();
allUsers.fetch();
this.$el.html( this.template({users: allUsers }) );
return this;
},
});
How can i access the values of allUsers in the template under something like this;
<% _.each(users, function(user){ %>
<% }); %>

fetch() is an asynchronous method, you should call render after fetch() is finished.
usually you wanna initialize your collection in your view's initialize
initialize: function () {
this.collection = new Skymama.Collections.Users([]);
this.collection.fetch({reset: true});
this.listenTo(this.collection, 'reset', this.render);
},
render: function () {
this.$el.html( this.template({users: this.collection }) );
return this;
}
'reset' event will be fired on the collection when fetch() is successful.
you can also do this by attaching this.render to fetch as its callback
this.collection.fetch().done(this.render);
but you will want to bind render's context to the view if you prefer doing it this way
initialize: function () {
_.bindAll(this, 'render');
//...
}

Related

React / Rails : Append dynamically element to DOM

Currently following facebook tutorial on React (react_tuto).
I don't understand how 2 components can communicate so that on "submit a comment button" it appends dynamically the "comment list".
currently, comment are created on server but appears on page only when page refreshed
how can the comment appear on submit button?
This i my AddComment component
var AddComment = React.createClass({
getInitialState: function(){
return {
content: this.props.content,
adrien: "before"
}
},
handleKeyUp: function(e) {
this.setState({
content: this.refs.addComment.getDOMNode().value,
})
},
handleValidation: function() {
var that = this
$.ajax({
type: "POST",
data: {comment: { content: that.state.content } },
url: Routes.create_comment_path({format: 'json'}),
success: function(data) {
that.setState({
content: "",
adrien: "after"
})
}
})
},
render: function(){
return (
<div>
<textarea onKeyUp={this.handleKeyUp} value={this.state.value} ref="addComment"></textarea>
<button onClick={this.handleValidation}>submit</button>
</div>
)
}
})
This is my CommentList component:
var CommentList = React.createClass({
render: function() {
return (
<div>
{this.props.comments.map(function(comment){
return <CommentListElement key={comment.id} comment={comment} />;
})}
</div>
);
}
});
You need a common parent component for communication between different components.
I have updated you example a bit to include common parent component CommentSystem
Note: I have removed ajax call to just show the communication between component.
Check below link.
https://jsfiddle.net/j4yk3pzc/15/
Extra Info:
In react we store states on parent component and pass them down to children. Along with state we also pass actions to manipulate data down to the children. When child component want's to update data passed to it from parent, then it fires the action passed from the parent. This is called Data down action up approach. Data is passed from parent to child to grandchild. While actions are propagated from grandchild to child to parent.
If you don't want to create the parent component then you can use some Publish / Subscribe or EventEmitter based system to communicate between children having no common parent.
Reference:
http://ctheu.com/2015/02/12/how-to-communicate-between-react-components/
Code:
var CommentSystem = React.createClass({
getInitialState: function() {
return {
comments: []
}
},
addComments: function(comment) {
var comments = this.state.comments;
comments.push(comment);
this.setState({comments: comments})
},
render: function() {
return (
<div>
<AddComment addComments={this.addComments}/>
<CommentList comments={this.state.comments}/>
</div>
)
}
})
var AddComment = React.createClass({
getInitialState: function(){
return {
content: this.props.content,
adrien: "before"
}
},
handleKeyUp: function(e) {
this.setState({
content: this.refs.addComment.getDOMNode().value,
})
},
handleValidation: function() {
var that = this;
this.props.addComments(this.state.content);
},
render: function(){
return (
<div>
<textarea onKeyUp={this.handleKeyUp} value={this.state.value} ref="addComment"></textarea>
<button onClick={this.handleValidation}>submit</button>
</div>
)
}
})
var CommentList = React.createClass({
render: function() {
return (
<div>
{this.props.comments.map(function(comment){
return <CommentListElement key={comment.id} comment={comment} />;
})}
</div>
);
}
});
var CommentListElement = React.createClass({
render: function() {
return (
<div>{this.props.comment}</div>
)
}
})
React.render(<CommentSystem/>, document.getElementById('container'));
Hope this helps.

Backbone route not invoking an action with URL

This is the code to a simple ToDo app using ASP.NET MVC:
var Appointment = Backbone.Model.extend({
defaults: function () {
return {
'date': new Date(),
'cancelled': false,
'title': 'Checkup'
}
}
});
var AppointmentList = Backbone.Collection.extend({
model: Appointment,
url:"/Home/Appointments"
});
var AppointmentView = Backbone.View.extend({
template: _.template('<span class="<% if(cancelled) print("cancelled") %>"><%= title %></span>x'),
initialize: function () {
this.model.on('change', this.render, this);
},
render: function () {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
var AppointmentListView = Backbone.View.extend({
initialize: function () {
this.collection.on('add', this.addOne, this);
this.collection.on('reset', this.render, this);
},
render: function () {
this.collection.forEach(this.addOne, this);
},
addOne: function (model) {
var appointmentView = new AppointmentView({ model: model });
appointmentView.render();
this.$el.append(appointmentView.el);
}
});
var AppRouter = new (Backbone.Router.extend({
routes: { "Home/Appointment/:id": "show", "": "index" },
initialize: function (options) {
this.appointmentList = new AppointmentList();
},
start: function () {
Backbone.history.start({ pushState: true });
},
index: function () {
var appointmentsView = new AppointmentListView({ collection: this.appointmentList });
appointmentsView.render();
$('body').html(appointmentsView.el);
this.appointmentList.fetch();
},
show: function (id) {
debugger;
var appointment = new Appointment({ id: id });
var appointmentView = new AppointmentView({ model: appointment });
appointmentView.render();
$('body').html(appointmentView.el);
appointment.fetch();
}
}));
$(function () { AppRouter.start() });
Please do not be overwhelmed by the code. If you look closely it is very simple as I am a noob.
My problem is that show action is not being hit. But index can works and can populate the page with view. I tried with all kinds of url to hit the show function but unsuccessful. Can someone please tell me what I need to do to make this right?

Backbone Uncaught TypeError: Cannot call method 'toJSON' of undefined Unable to get the model

I am trying to build an application in rails with Backbone. I am facing a problem where I get an undefined on the model where i try to read it.
Here is my code.
// Collection
Quizzer.Collections.Posts = Backbone.Collection.extend({
model: Quizzer.Models.Post,
url: "/posts"
});
// Model
Quizzer.Models.Post = Backbone.Model.extend({
});
//PostIndex View
Quizzer.Views.PostsIndex = Backbone.View.extend({
template: JST['posts/index'],
el: '#posts',
render: function(){
$(this.el).html(this.template);
$(this.projectsCallView());
return this;
},
projectsCallView: function(){
var pp = new Quizzer.Views.Posts({ collection : new Quizzer.Collections.Posts });
this.$("ul").append(pp.render().el)
}
});
//Posts View
Quizzer.Views.Posts = Backbone.View.extend({
el: '#container',
template: JST['posts/posts'],
initialize: function(){
this.listenTo(this.collection, 'reset', this.render);
this.collection.fetch({ reset:true });
},
render:function(){
$(this.el).html(this.template());
_.each(this.collection,this.addOne);
return this;
},
addOne: function(model){
console.log(model);
var vv = new Quizzer.Views.PostW({ model: model })
$("ul").append(vv.render().el)
}
});
//PostW View
Quizzer.Views.PostW = Backbone.View.extend({
template: JST['posts/postsw'],
render: function() {
console.log(this.model)
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
Can you tell where my problem is?
Instead of
_.each(this.collection,this.addOne);
do
this.collection.each(this.addOne);

events not firing when creating single instance of a view, but works when mutliple instance are created

I'm a complete newbie to Backbone and am trying to get my head round few things. Im trying to build something using jQuery mobile and Backbone. Please find my code below
var WelcomePage = Backbone.View.extend({
initialize:function () {
this.template = _.template($("#welcome_template").html());
},
render:function (eventName) {
$(this.el).html(this.template());
return this;
},
events:{
"click .btn_continue" : function(){
appRouter.navigate('login',{trigger: true});
}
}
});
var Login = Backbone.View.extend({
initialize:function () {
this.template = _.template($("#login_template").html());
},
render:function (eventName) {
$(this.el).html(this.template());
return this;
},
events:{
"click .btn_login" : function(){
appRouter.navigate('dashboard',{trigger: true});
}
}
});
var Dashboard = Backbone.View.extend({
initialize:function () {
this.template = _.template($("#dashboard_template").html());
},
render:function (eventName) {
$(this.el).html(this.template());
return this;
},
events:{
"click .btn_loadImages" : function(){
console.log('load Images');
}
}
});
var Router = Backbone.Router.extend({
routes:{
"":"welcome",
"login":"login",
"dashboard":"dashboard",
},
initialize:function () {
},
welcome:function () {
this.changePage(new WelcomePage());
},
login:function () {
this.changePage(new Login());
},
dashboard:function(){
this.changePage(new Dashboard());
},
changePage:function (page) {
$(page.el).attr('data-role', 'page');
page.render();
$('body').append($(page.el));
$.mobile.changePage($(page.el), {changeHash:false, transition: 'slide'});
}
});
var appRouter = new Router();
Backbone.history.start();
Now while I go back and forth through the screens using the BACK key the events fire OK using the code above. Then I tried replacing the code for the Router with the code below
var Router = Backbone.Router.extend({
routes:{
"":"welcome",
"login":"login",
"dashboard":"dashboard",
},
initialize:function () {
},
welcome:function () {
this.changePage(v_WelcomePage);
},
login:function () {
this.changePage(v_Login);
},
dashboard:function(){
this.changePage(v_Dashboard);
},
changePage:function (page) {
$(page.el).attr('data-role', 'page');
page.render();
$('body').append($(page.el));
$.mobile.changePage($(page.el), {changeHash:false, transition: 'slide'});
}
});
var v_WelcomePage = new WelcomePage();
var v_Login = new Login();
var v_Dashboard = new Dashboard();
var appRouter = new Router();
Backbone.history.start();
I noticed when I go back to the previous screens the events stop firing. Instead of creating the instance of the view in the action of the router I have created it outside and call it each time.I hope im making some sense.
Any advice much appreciated.
Events are hooked up using jQuery when the view is instantiated, not rendered (in the Backbone View constructor function). jQuery disconnects those events when the html is removed from the page (probably in $.mobile.changePage).
So, the second time you render the page, the events will not be hooked back up. You could try calling page.delegateEvents() to manually hook up the events again, or you could re-create the view each time.

jQuery UI animation Callback executing before animation starts

I'm trying to get some animation to work during a Backbone View's render operation that is called when I have refreshed the data of the underlying model to a new record.
SiteView = Backbone.View.extend({
initialize: function () {
this.model.bind('change', this.render, this);
},
render: function () {
if (this.model.get('name')) {
var callback = function (view) {
view.$("#activesite_name").empty().append(view.model.get('name'));
view.$("#activesite_desc").empty().append(view.model.get('description'));
$(view.el).show('drop', { direction: 'down' }, 'slow').removeClass('hidden');
};
$(this.el).filter(':visible').fadeOut(500, callback(this));
}
}
});
However, the jQuery UI callback function is being executed before the show operation is, resulting in the UI updating and then disappearing when refreshing from one model selection to another.
How can I get the callback to be called only once the element is properly hidden?
Try this:
render: function () {
if (this.model.get('name')) {
var view = this;
var callback = function () {
view.$("#activesite_name").empty().append(view.model.get('name'));
view.$("#activesite_desc").empty().append(view.model.get('description'));
view.$el.show('drop', { direction: 'down' }, 'slow').removeClass('hidden');
};
$(this.el).filter(':visible').fadeOut(500, callback);
}
}

Resources