What is the best way to re-render a view after an event takes place (eg. submitting a note). In the below code, I want to re-render the view to show the note that was just added.
var NotesView = SectionBaseView.extend({
model: app.models.Note,
events: {
'submit': 'Notes'
},
Notes: function (e) {
e.preventDefault();
var details = new this.model($('form').serializeObject());
details.url = '/email/Notes';
details
.save()
.done(function () {
app.notifySuccess('Note added successfully.');
});
views['#Notes'].render();
}
});
Notes view is initialized in the document.ready function as follows:
views['#Notes'] = new NotesView({ el: '#Notes', template: app.templates.Notes });
I tried using views['#Notes'].render(); but this doesn't seem to be working.
The default implementation of render is a no-op. Override this function with your code that renders the view template from model data, and updates this.el with the new HTML. A good convention is to return this at the end of render to enable chained calls. Docs
var NotesView = SectionBaseView.extend({
model: app.models.Note,
events: {
'submit': 'Notes'
},
render : function(){
//your code
return this;
},
Notes: function (e) {
var that = this;
e.preventDefault();
var details = new this.model($('form').serializeObject());
details.url = '/email/Notes';
details
.save()
.done(function () {
app.notifySuccess('Note added successfully.');
});
that.render();
}
});
on document.ready
views['#Notes'] = new NotesView({ el: '#Notes', template: app.templates.Notes });
views['#Notes'].render();
Related
I'm using Parse-SDK-JS, Handlebars.js and hash routing to create a dynamic webpage. When a user clicks on any link, I call a template using a URL in the following way: http://www.website.com/#/admin.
Router
BlogApp.Router = Parse.Router.extend({
start: function () {
Parse.history.start({root: '/beta/'});
},
routes: {
'': 'index',
'blog/:url': 'blog',
'category/:url': 'category',
'admin': 'admin',
'login': 'login',
'reset': 'reset',
'logout': 'logout',
'add': 'add',
'register': 'register',
'editprofile': 'editprofile',
'changeprofilepic': 'changeprofilepic',
':username': 'userprofile'
},
index: function () {
BlogApp.fn.setPageType('blog');
$blogs = [];
if (!currentUser) {
Parse.history.navigate('#/register', {trigger: true});
console.log("There is no logged in user.");
} else {
var groupId = currentUser.get('groupId');
var designsQuery = new Parse.Query(BlogApp.Models.Blog).equalTo('groupId', groupId).include('author').descending('lastReplyUpdatedAt').limit(50);
designsQuery.find({success: function (blogs) {
for (var i in blogs) {
var des = blogs[i].toJSON();
des.author = blogs[i].get('author').toJSON();
$blogs.push(des);
}
// console.log(blogs);
BlogApp.fn.renderView({
View: BlogApp.Views.Blogs,
data: {blogs: $blogs}
});
}, error: function (blogs, e) {
console.log(JSON.stringify(e));
}});
}
},
});
View
BlogApp.Views.Blogs = Parse.View.extend({
template: Handlebars.compile($('#blogs-tpl').html()),
className: 'blog-post',
render: function () {
var collection = {blog: []};
collection = {blog: this.options.blogs};
this.$el.html(this.template(collection));
},
});
My problem is that upon loading a new template, the user is not sent to the top of the page, i.e. to the following div:
<div id="main-nav"></div>
The users' scroll position on the page doesn't change if the new page is longer than the current page. The user just ends up somewhere down the middle of the page because the new template is loaded but they are not anchoring anywhere new.
Normally in HTML I would open a new page to a particular anchor with something like this: http://www.website.com/page#container if I wanted to, but with the way I set up my hash routing the anchor is the template call itself, so I can't do something like this: http://www.website.com/#/admin#container.
I hope this makes sense.
How can I always send the user to the div "container" upon loading a new template into my view?
I solved this by scrolling into an element after the View was generated.
cookies: function () {
BlogApp.fn.setPageType('cookies');
BlogApp.fn.renderView({
View: BlogApp.Views.Cookies
});
document.getElementById('main-nav').scrollIntoView();
},
Better... by adding the scrollIntoView() function after data is rendered into the View object, so that this works for all links in the router without so much copy pasta.
BlogApp.fn.renderView = function (options) {
var View = options.View, // type of View
data = options.data || null, // data obj to render in the view
$container = options.$container || BlogApp.$container, // container to put the view
notInsert = options.notInsert, // put the el in the container or return el as HTML
view = new View(data);
view.render();
if (notInsert) {
return view.el.outerHTML;
} else {
$container.html(view.el);
document.getElementById('main-nav').scrollIntoView();
}
};
I've created a search bar, but when the data is gathered from the user, it displays the default data over again rather then the users new search criteria.
I'm resetting the collection and giving it a new URL when the user searches, but it doesn't seem to update correctly, and I'm having trouble figuring out where my problem(s) are.
(function(){
'use strict';
var red = red || {};
//model////////////////////////////////////////////////
red.RedditModel = Backbone.Model.extend({
defaults: {
urlTarget: $('#textBox').val(),
urlStart: 'https://www.reddit.com/r/',
urlEnd: '.json'
},
initialize: function() {
this.on('change:urlTarget', function() {
console.log('The Url Target has changed to ' + this.get("urlTarget"));
});
this.on('change:concatURL', function() {
console.log('The model Url has changed to ' + this.get("concatURL"));
});
this.on('change:url', function() {
console.log('The collection url has changed to: ' + this.get('url'));
});
}
});
var redditModel = new red.RedditModel();
var fullURL = new red.RedditModel({
concatURL: redditModel.attributes.urlStart + redditModel.attributes.urlTarget + redditModel.attributes.urlEnd
});
var listElmement,
$list = $('.list');
//collections//////////////////////////////////////////
red.redditCollection = Backbone.Collection.extend({
model: red.RedditModel,
url: fullURL.attributes.concatURL,
parse: function(response) {
var redditData = response.data.children;
return redditData;
}
});
//view////////////////////////////////////
red.RedditView = Backbone.View.extend({
model: fullURL,
collection: redditCollection,
el: '.searchBar',
events: {
'click .searchButton': function(e) {
this.updateModel(e);
this.updateCollection(e);
},
'change #textBox': 'initialize'
},
updateModel: function() {
this.$urlTarget = $('#textBox').val()
this.model.set('urlTarget', this.$urlTarget);
this.model.set('concatURL', redditModel.attributes.urlStart + this.$urlTarget + redditModel.attributes.urlEnd);
},
updateCollection: function() {
this.collection.reset();
this.$urlTarget = $('#textBox').val();
var newUrl = redditModel.attributes.urlStart + this.$urlTarget + redditModel.attributes.urlEnd;
this.collection.add({ urlTarget: this.$urlTarget });
this.collection.add({ url: newUrl });
console.log(newUrl);
},
tagName: 'li',
className: 'listItems',
initialize: function() {
$list.html('');
this.collection.fetch({
success: function(redditData) {
redditData.each(function(redditData) {
redditData = redditData.attributes.data.title
listElmement = $('<li></li>').text(redditData);
$list.append(listElmement);
})
}
});
},
render: function() {
}
});
var redditCollection = new red.redditCollection({
redditModel,
fullURL
});
var myRedditView = new red.RedditView({
model: redditModel,
collection: redditCollection
});
$('.page').html(myRedditView.render());;
})();
Parse within the model, and use it for its intended purpose. No need to store the reddit url and other search related info in a model.
red.RedditModel = Backbone.Model.extend({
parse: function(data) {
return data.data;
},
})
Since you already take care of the reddit url here. Don't be afraid to make yourself some utility functions and getters/setters in your Backbone extended objects (views, model, collection, etc).
red.RedditCollection = Backbone.Collection.extend({
url: function() {
return 'https://www.reddit.com/r/' + this.target + this.extension;
},
initialize: function(models, options) {
this.extension = '.json'; // default extension
},
setExtension: function(ext) {
this.extension = ext;
},
setTarget: function(target) {
this.target = target;
},
parse: function(response) {
return response.data.children;
}
});
Don't be afraid to have a lot of views, Backbone views should be used to wrap small component logic.
So here's the item:
red.RedditItem = Backbone.View.extend({
tagName: 'li',
className: 'listItems',
render: function() {
this.$el.text(this.model.get('title'));
return this;
}
});
Which is used by the list:
red.RedditList = Backbone.View.extend({
tagName: 'ul',
initialize: function() {
this.listenTo(this.collection, 'sync', this.render);
},
render: function() {
this.$el.empty();
this.collection.each(this.renderItem, this);
return this;
},
renderItem: function(model) {
var view = new red.RedditItem({ model: model });
this.$el.append(view.render().el);
}
});
And the list is just a sub-component (sub-view) of our root view.
red.RedditView = Backbone.View.extend({
el: '.searchBar',
events: {
'click .searchButton': 'onSearchClick',
},
initialize: function() {
// cache the jQuery element for the textbox
this.$target = $('#textBox');
this.collection = new red.RedditCollection();
this.list = new red.RedditList({
collection: this.collection,
// assuming '.list' is within '.searchBar', and it should
el: this.$('.list'),
});
},
render: function() {
this.list.render();
return this;
},
onSearchClick: function(e) {
this.collection.setTarget(this.$target.val());
console.log(this.collection.url());
this.collection.fetch({ reset: true });
},
});
Then, you only need the following to use it:
var myRedditView = new red.RedditView();
myRedditView.render();
Notice the almost non-existent use of the global jQuery selector. If you're using Backbone and everywhere you're using $('#my-element'), you're defeating the purpose of Backbone which is, in part, to apply MVC concepts on top of jQuery.
Some notes on the code posted
Take time to understand what's going on. There are several lines of code in your question that doesn't do anything, or just don't work at all.
Though it's been removed in your answer, the following doesn't make sense because the collection constructor is Backbone.Collection([models], [options]) and what you have here translates to passing an options object (using ES6 shorthand property names { a, b, c}) to the models parameter.
var redditCollection = new red.redditCollection({
redditModel,
fullURL
});
This line does nothing, because .render() doesn't do anything and doesn't return anything.
$('.page').html(myRedditView.render());
Here, you're creating a new element manually using jQuery while you have Backbone which does this for you.
$('<li></li>').text(redditData);
Don't use the attributes directly, always use .get('attributeKey') unless you have a good reason not to.
redditModel.attributes.urlStart
Favor local variables whenever you can. The listElement var here is defined at the "app" level without a need for it.
listElmement = $('<li></li>').text(redditData);
$list.append(listElmement);
A Backbone collection is automatically filled with the new instances of models on success. You do not need to re-parse that in the success callback (in addition to the ambiguity with redditData).
this.collection.fetch({
success: function(redditData) {
redditData.each(function(redditData) {
redditData = redditData.attributes.data.title;
I don't mean to be rude and I took the time to write that long answer to try to help, you, and any future reader that comes by.
I have several Backbone Models rendered in a Collection View, and also I have a route that should render a view of that model. So, here come the views
resume.js
// this renders a single model for a collection view
var ResumeView = Backbone.View.extend({
model: new Resume(),
initialize: function () {
this.template = _.template($('#resume').html());
},
render: function () {
this.$el.html(this.template(this.model.toJSON));
return this;
}
});
#resume template
<section id="resume">
<h1><%= profession %></h1>
<!-- !!!!! The link for a router which should navigate to ShowResume view -->
View Details
</section>
Collection view:
var ResumeList = Backbone.View.extend({
initialize: function (options) {
this.collection = options.collection;
this.collection.on('add', this.render, this);
// Getting the data from JSON-server
this.collection.fetch({
success: function (res) {
_.each(res.toJSON(), function (item) {
console.log("GET a model with " + item.id);
});
},
error: function () {
console.log("Failed to GET");
}
});
},
render: function () {
var self = this;
this.$el.html('');
_.each(this.collection.toArray(), function (cv) {
self.$el.append((new ResumeView({model: cv})).render().$el);
});
return this;
}
});
The code above works perfectly and does exactly what I need -- an array of models is fetched from my local JSON-server and each model is displayed within a collection view. However, the trouble starts when I try to navigate through my link in the template above. Here comes the router:
var AppRouter = Backbone.Router.extend({
routes: {
'': home,
'resumes/:id': 'showResume'
},
initialize: function (options) {
// layout is set in main.js
this.layout = options.layout
},
home: function () {
this.layout.render(new ResumeList({collection: resumes}));
},
showResume: function (cv) {
this.layout.render(new ShowResume({model: cv}));
}
});
and finally the ShowResume view:
var ShowResume = Backbone.View.extend({
initialize: function (options) {
this.model = options.model;
this.template = _.template($('#full-resume').html());
},
render: function () {
this.$el.html(this.template(this.model.toJSON()));
}
});
I didn't provide the template for this view because it is quite large, but the error is following: whenever I try to navigate to a link, a view tries to render, but returns me the following error: Uncaught TypeError: this.model.toJSON is not a function. I suspect that my showResume method in router is invalid, but I can't actually get how to make it work in right way.
You are passing the string id of the url 'resumes/:id' as the model of the view.
This should solve it.
showResume: function (id) {
this.layout.render(new ShowResume({
model: new Backbone.Model({
id: id,
profession: "teacher" // you can pass data like this
})
}));
}
But you should fetch the data in the controller and react accordingly in the view.
var AppRouter = Backbone.Router.extend({
routes: {
'*otherwise': 'home', // notice the catch all
'resumes/:id': 'showResume'
},
initialize: function(options) {
// layout is set in main.js
this.layout = options.layout
},
home: function() {
this.layout.render(new ResumeList({ collection: resumes }));
},
showResume: function(id) {
// lazily create the view and keep it
if (!this.showResume) {
this.showResume = new ShowResume({ model: new Backbone.Model() });
}
// use the view's model and fetch
this.showResume.model.set('id', id).fetch({
context: this,
success: function(){
this.layout.render(this.showResume);
}
})
}
});
Also, this.model = options.model; is unnecessary as Backbone automatically picks up model, collection, el, id, className, tagName, attributes and events, extending the view with them.
I have a problem with backbone. I have two models: A and B
I need to listen to events in one model (for instance A), and then after the event has happened make changes in the view of model B and its view.
Does anyone have a fairly simple example how such functionality can be implemented in backbone?
var Model_A_View= Backbone.View.extend({
events: {
// some events;
"click" : "ok",
},
initialize: function () {
this.Model_A = new Model_A({ // });
}
var Model_B_View= Backbone.View.extend({
events: {
// some events;
},
initialize: function () {
this.Model_B = new Model_B({ // });
this.listenTo( model_A , "change: ok", this.dosomethingFunction());
}
dosomethingFunction: function () {
//dosomething
}
Your code hard to read, no code style, and some synax error.
However, you'd better create model outside of you view's initialize function, and pass the model as a para when new a view:
var modelA = new Model_A();
var viewA = new Model_A_View({
model: modelA
});
var modelB = new Model_B();
var viewB = new Model_B_View({
model: modelB
});
viewB.listenTo(modelA, "change:ok", viewB.dosomethingFunction);
As variant you can use The Backbone Events. Few simple steps, how you can do this.
1.Specify events global object:
window._vent = _.extend({}, Backbone.Events);
2.Specify event triggers in your view/model/collection. For example for your view:
var Model_A_View = Backbone.View.extend({
events: {
// some events;
"click" : "ok",
},
initialize: function() {
this.Model_A = new Model_A({});
},
ok: function() {
vent.trigger('model_A:update', this.model); //you can also send your model
}
});
3.Specify event listening in your Model_B:
var Model_B_View = Backbone.View.extend({
initialize: function() {
this.Model_B = new Model_B({});
vent.on('model_A:update', this.updateModel, this);
},
updateModel: function() {
//this function will be call after `model_A:update` event triggered
//do something with Model_B
}
});
I have a web application using BackboneJS. In this application, I have a LayoutView.js file in which there is a Backbone View (called LayoutView). LayoutView has other functions (methods) that call other views. I am fetching some data in the initialize function of LayoutView, and I need to get this same data (model) in another view and work (update/delete) on it. Below is how I am passing data from LayoutView to myView:
var LayoutView = Backbone.View.extend({
el: $("#mi-body"),
initialize: function () {
var that = this;
this.ConfigData = new Configurations(); //Configurations is a collection
this.ConfigData.fetch({
success: function () {
alert("success");
},
error: function () {
alert("error");
}
});
this.render();
Session.on('change:auth', function (session) {
var self = that;
that.render();
});
},
render: function () {
// other code
},
events: {
'click #logout': 'logout',
'click #divheadernav .nav li a': 'highlightSelected'
},
myView: function () {
if (Session.get('auth')) {
this.$el.find('#mi-content').html('');
this.options.navigate('Myview');
return new MyLayout(this.ConfigData);
}
}
});
Still, I do not know how to "get"/access this data as my current data/model/collection (I am not sure which term is correct) in myView and work on it using Backbone's "model.save(), model.destroy()" methods. Also, whenever an edit/delete happens, the data of ConfigData should be modified and the update should reflect in the html displayed to the user.
Below is the code from MyView:
var MyView = Backbone.View.extend({
tagName: 'div',
id: "divConfigurationLayout",
initialize: function (attrs) {
this.render();
},
render: function () {
var that = this;
},
events: {
"click #Update": "update",
"click #delete": "delete"
},
update: function(){
//code for updating the data like model.save...
},
delete: function(){
//code for deleting the data like model.destroy...
}
});
Now the data I passed is in attrs in the initialize function. How to get this done..?
The syntax for instantiating a Backbone view is new View(options) where options is an Object with key-value pairs.
To pass a collection to your view, you'd instantiate it like so:
new MyLayout({
collection : this.configData
});
Within your view, this.collection would refer to your configData collection.