I’m working with backbone, this is the static data I have:
var emailfields = new EmailFields([
new EmailField({ id: "data-email", name: "Email" }),
new EmailField({ id: "data-first-name", name: "First Name" }),
new EmailField({ id: "data-last-name", name: "Last Name" }),
]);
I want to create n (n > 1) drop-down lists populated with the same data (emailfields). If any of the values is selected I want to notify the user that he cannot select the same field again.
This is my view:
EmailFieldSelectView = Backbone.View.extend({
initialize: function() {
_.bindAll(this, "addSingleEmailField", "add");
this.add();
},
addSingleEmailField: function(emailfield) {
$("select").each(function() {
$(this).append(new EmailFieldView({ model: emailfield}).render().el);
});
},
add: function() {
this.collection.each(this.addSingleEmailField);
},
});
This is my initialization:
window.emailview = new EmailFieldSelectView({
collection: emailfields
});
In order to populate each select with the same data I’m using $("select").
Is there a better way to do that (I feel this looks like a hack)?
Thanks.
What you've got seems like a reasonable way to approach things. You could use a template to reduce the javascript and need for a view to create select list options.
<script type="text/template" id="exampleTemplate">
<select id="list1">
<% _(emailFields).each(function(emailField) { %>
<option value="<%= emailField.id %>"><%= emailField.name %></option>
<% }); %>
</select>
<select id="list2">
<% _(emailFields).each(function(emailField) { %>
<option value="<%= emailField.id %>"><%= emailField.name %></option>
<% }); %>
</select>
etc.
</script>
Then the view becomes -
EmailFieldSelectView = Backbone.View.extend({
template: _template($('#exampleTemplate').html());
initialize: function() {
_.bindAll(this, "addSingleEmailField", "add");
this.render();
},
render: function() {
this.$el.html(this.template({ emailFields: this.collection.toJSON() }));
return this;
}
});
Related
I'm trying to learn Backbone and can't seem to match data from the fetch function into my Underscore template. How can can I get the children array in my JSON and match it to the template?
The Backbone.View looks like this:
var Projects = Backbone.Collection.extend({
url: '/tree/projects'
});
var Portfolio = Backbone.View.extend({
el: '.page',
render: function () {
var that = this;
var projects = new Projects();
projects.fetch({
success: function (projects) {
var template = _.template($('#projects-template').html());
that.$el.html(template({projects: projects.models}));
}
})
}
});
At the url: http://localhost:3000/portfolio/api/tree/projects
The JSON returned looks like this:
{
id:"projects",
url:"http://localhost:8888/portfolio/projects",
uid:"projects",
title:"Projects",
text:"",
files:[
],
children:[
{
id:"projects/example-1",
url:"http://localhost:8888/portfolio/projects/example-1",
uid:"example-1",
title:"Example 1",
images:"",
year:"2017",
tags:"Website",
files:[
],
children:[
]
},
{
id:"projects/example-2",
url:"http://localhost:8888/portfolio/projects/example-2",
uid:"example-2",
title:"Example #"2
text:"Example 2's text",
year:"2016",
tags:"Website",
files:[
{
url:"http://localhost:8888/portfolio/content/1-projects/4-example-2/example_ss.png",
name:"example_ss",
extension:"png",
size:244845,
niceSize:"239.11 kB",
mime:"image/png",
type:"image"
}
],
children:[
]
},
]
}
My Underscore file looks like this:
<script type="text/template" id="projects-template">
<h4>tester</h4>
<div>
<% _.each(projects.children, function (project) { %>
<div>
<div><%= project.get('year') %></div>
<div><%= project.get('title') %></div>
<div><%= project.get('tags') %></div>
</div>
<% }); %>
</div>
</script>
You can define a parse method on the collection:
var Projects = Backbone.Collection.extend({
url: '/tree/projects',
parse: function(response){
/* save other data from response directly to collection if needed.
for eg this.title = response.title; */
return response.children; // now models will be populated from children array
}
});
Do not use parse
While I usually agree with TJ, using parse on the collection is more like a hack than a definite solution. It would work only to get the children projects of a project and nothing more.
The parse function shouldn't have side-effects on the collection and with this approach, changing and saving fields on the parent project wouldn't be easily possible.
It also doesn't deal with the fact that it's a nested structure, it's not just a wrapped array.
This function works best when receiving wrapped data:
{
data: [{ /*...*/ }, { /*...*/ }]
}
Models and collections
What you have here are projects that have nested projects. A project should be a model. You also have files, so you should have a File model.
Take each resource and make a model and collection classes with it. But first, get the shared data out of the way.
var API_ROOT = 'http://localhost:8888/';
File
var FileModel = Backbone.Model.extend({
defaults: {
name: "",
extension: "png",
size: 0,
niceSize: "0 kB",
mime: "image/png",
type: "image"
}
});
var FileCollection = Backbone.Collection.extend({
model: FileModel
});
Project
var ProjectModel = Backbone.Model.extend({
defaults: function() {
return {
title: "",
text: "",
files: [],
children: []
};
},
getProjects: function() {
return this.get('children');
},
setProjects: function(projectArray, options) {
return this.set('children', projectArray, options);
},
getFiles: function() {
return this.get('files');
},
getSubProjectUrl: function() {
return this.get('url');
}
});
var ProjectCollection = Backbone.Collection.extend({
model: ProjectModel,
url: API_ROOT + '/tree/projects'
});
Project view
Then, make a view for a project. This is a simple example, see the additional information for tips on optimizing the rendering.
var ProjectView = Backbone.View.extend({
template: _.template($('#projects-template').html()),
initialize: function(options) {
this.options = _.extend({
depth: 0, // default option
}, options);
// Make a new collection instance with the array when necessary
this.collection = new ProjectCollection(this.model.getProjects(), {
url: this.model.getSubProjectUrl()
});
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
this.$projectList = this.$('.list');
// use the depth option to avoid rendering too much projects
if (this.depth > 0) this.collection.each(this.renderProject, this);
return this;
}
renderProject: function(model) {
this.$projectList.append(new ProjectView({
model: model,
depth: depth - 1
}).render().el);
}
});
With a template like this:
<script type="text/template" id="projects-template">
<h4><%= title %></h4>
<span><%= year %></span><span><%= tags %></span>
<p><%= text %></p>
<div class="list"></div>
</script>
Using the view:
var model = new ProjectModel({ id: "project" });
model.fetch({
success: function() {
var project = new ProjectView({
model: model,
depth: 2
});
}
});
Additional info
Nested models and collections
Efficiently rendering a list
I have model user
window.User = Backbone.Model.extend({
defaults: {
"id": null,
"login": "",
"password": "",
"role": ""
}
});
window.UserCollection = Backbone.Collection.extend({
model: User
});
json response:
{
"id":1,
"login":"log1",
"password":"psw1",
"role":
{
"id":1,
"name":"user"
}
},
I have json response with all roles:
[{"id":1,"name":"user"},{"id":2,"name":"admin"}]
How create dropdown form with all roles and after set it to model user when i create a new user
To create a drop down at render time and select the correct option, you could define a template and then using when the view is rendered.
Template:
<script type="text/html" id="rolesSelectTemplate">
<select id="selectRole" name="userRole">
<% _(allRoles).each(function(aRole) { %>
<% if (aRole.get('IsSelected') == "selected") { %>
<option value="<%= aRole.get('id') %>" id="<%- aRole.get('id') %>" selected="selected"><%- aRole.get('name') %></option>
<% } %>
<% else { %>
<option value="<%- aRole.get('id') %>" id="<%- aRole.get('id') %>"><%- aRole.get('name') %></option>
<% } %>
<% }); %>
</select>
</script>
Function:
render: function(){
// do something first
// Assuming you are able to init the users and roles collection with JSON
var selectedRole = this.model.get('role');
allRoles.each(function (aRole) {
aRole.set('IsSelected', "");
if (selectedRole && selectedRole.id == aRole.get('id') /* if the nested role attribute is a simple object or else use selectedRole.get('id')*/) {
aRole.set('IsSelected', "selected");
}
});
var allRolesDropDown = _.template($("#rolesSelectTemplate").html())({
allRoles: allRoles.models,
});
$("#allRolesDropDownElement").append(allRolesDropDown);
// do something else
return this;
}
I'm fetching some data from my MySQL database with a php file and now I want to display this data in my View by passing it through a Model with the json_encode method. So far I created a Router, a Model, a Collection (is it necessary?) and a View. When i console.log the collection in my View, I can see that the data is actually there but my View shows nothing. When i console.log the Model I get the "undefined" message. So it seems that the Model is not instantiated, but I dont really know how to solve it. I use RequireJS and the HandlebarsJS for HTML templating purpose.
So here is my Router.
define(['backbone',
'views/firstpage',
'views/secondpage',
'views/thirdpage',
'collections/BandCollection']),
function( Backbone,FirstpageView, SecondpageView, ThirdpageView,BandCollection ) {
var Router = Backbone.Router.extend({
routes: {
'': 'index',
'firstpage' : 'firstpage',
'secondpage' : 'secondpage',
'thirdpage' : 'thirdpage'
},
initialize: function () {
this.bandCollection = new BandCollection();
this.bandCollection.fetch({
error: function () {
console.log("error!!");
},
success: function (collection) {
console.log("no error");
}
});
},
thirdpage: function() {
var thirdpageView = new ThirdpageView({ el:'#topContent', collection:this.bandCollection}).render();
},
});
return Router;
}
);
My Model looks like this:
define([
"jquery",
"backbone"
],
function($, Backbone) {
var BandModel = Backbone.Model.extend({
url: "../metal/db/bands.php",
defaults: {
"id": '',
"band": '',
"label": ''
}
});
return BandModel;
});
My Collection:
define([
"backbone",
"models/BandModel"
],
function(Backbone, BandModel) {
var BandCollection = Backbone.Collection.extend({
model: BandModel,
url: "../metal/db/bands.php"
});
return BandCollection;
});
My HTML template:
<div>
<p><%= id %></p>
<p><%= band %></p>
<p><%= label %></p>
</div>
And My View looks like this:
define(['backbone','handlebars', 'text!templates/Thirdpage.html'],
function(Backbone,Handlebars, Template) {
'use strict';
var ThirdpageView = Backbone.View.extend({
template: Handlebars.compile(Template),
initialize: function () {
_.bindAll(this, 'render');
this.render();
},
render: function() {
console.log(this.collection);
this.$el.html(this.template(this.collection.toJSON()));
return this;
}
});
return ThirdpageView;
}
);
As said before, the console.log(this.collection) tells me that the data is available..
{length: 6, models: Array[6], _byId: Object, constructor: function, model: function…}
but console.log(this.model) gives me "undefined" - and the View actually displays the HTML mentioned before and not the data, meaning it actually shows
<div>
<p><%= id %></p>
<p><%= band %></p>
<p><%= label %></p>
</div>
So, can anyone help me out? I'm out of ideas...
Change your render() method in your view like this:
render: function() {
var self = this;
console.log(this.collection);
self.collection.each(function(model){
console.log(this.model); // can view all models here
self.$el.append(self.template({id:model.get('id'),band:model.get('band'),label:model.get('label')}));
});
return this;
}
Change your Template like this:
<div>
<p>{{id}}</p>
<p>{{band}}</p>
<p>{{label}}></p>
</div>
template code :
<script type="text/template" id="baseTemplate">
<% collection.each(function() { %>
<div>
<%= collection %>
</div>
<% }); %>
</script>
<div id="baseContainer"></div>
and other code is :
//model
var PageModel = Backbone.Model.extend({
defaults: {
"id":null,
"title":"",
"content":""
},
});
//collection
var PageCollection = Backbone.Collection.extend({
defaults: {
model: PageModel
},
model: PageModel,
url: 'api/get_page/?id=6'
});
//view
var PageView = Backbone.View.extend({
el: $('#baseContainer'),
initialize: function () {
this.collection = new PageCollection();
this.collection.bind("reset", this.render, this);
this.collection.bind("change", this.render, this);
this.collection.fetch();
},
render: function () {
var html = _.template($("#baseTemplate").html(), { collection: this.collection });
this.$el.html(html);
console.log(html);
return this;
}
});
var page = new PageView();
problem is that its return and object how can i get values from object? api link is http://furqankhanzada.com/backbonejs/api/get_page/?id=6 and here you can see object in browser console http://furqankhanzada.com/backbonejs/
i need to get title, content , attachments -> images -> Gallery Large -> url (attachments using each() ).
Not sure whether this is proper solution or not, but you can give it a try.
An alternative can be like,
var html = _.template($("#baseTemplate").html(), { models: this.collection.models });
Pass models instead of directly passing collection. And in the template you can do something like this,
<script type="text/template" id="baseTemplate">
<% _.each(models, function(mdl) { %>
<div>
<%= mdl.get('title') %>
<%= mdl.get('content') %>
<% _.each(mdl.get('page').attachments, function(attachment) { %>
<%= attachment.images["Gallery Large"].url %>
<% }) %>
</div>
<% }); %>
</script>
<div id="baseContainer"></div>
Please modify the markup as per your needs. But this solution is too specific to the problem :( :(
window.User = Backbone.Model.extend({
defaults: {
name: 'Jane',
friends: []
},
urlRoot: "users",
initialize: function(){
this.fetch();
}
});
var HomeView = Backbone.View.extend({
el: '#container',
template: _.template($("#home-template").html()),
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});
home: function() {
var user = new User({id: 1});
this.homeView = new HomeView({
model: user
});
this.homeView.render();
},
The model data is being queried and the root level attributes work fine, but the attribute that contains an array of other objects doesn't seem to show up.
Template:
<script id="home-template" type="text/template">
<div id="id">
<div class="name"><%= name %></div>
<br />
<h3> Single Friends</h3>
<br />
<ul data-role="listview" data-inset="true", data-filter="true">
<% _.each(friends, function(friend) { %>
<li>
<a href="/profile?id=<%= friend.id %>", data-ajax="false">
<div class="picture"><img src="http://graph.facebook.com/<%= friend.fb_user_id %>/picture"></div>
<div class="name"><%= friend.name %></div>
</a>
</li>
<% }); %>
</ul>
</div>
</script>
Return JSON:
{"name":"John Smith","friends":[{"id":"1234","name":"Joe Thompson","fb_user_id":"4564"},{"id":"1235","name":"Jane Doe","fb_user_id":"4564"}]}
It almost seems like it's not seeing the .friends attribute at all because it's taking the defaults of the model ([]).
Any suggestions?
You are calling render() before fetch() has returned the data from the server.
Try this?
window.User = Backbone.Model.extend({
defaults: {
name: 'Jane',
friends: []
},
urlRoot: "users"
});
var HomeView = Backbone.View.extend({
el: '#container',
template: _.template($("#home-template").html()),
initialize: function() {
this.model.fetch();
this.model.bind('change', this.render, this);
}
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
return this;
}
});