I have a user model, each user has an artist and every artist has several albums. I'm trying to render a view to show a user profile. When I try to render the view I get the following error:
Uncaught ReferenceError: albums is not defined
(anonymous function) b.template.c underscore-min.js:30
Backbone.View.extend.render profile.js:13
Backbone.Router.extend.profile router.js:62
...
It seems to be I'm not passing an album object to the template but I'm not using any album variable in that template, nor in the view. Here is the code for both:
View:
headerT = require('text!templates/user/profile_header.html');
profileT = require('text!templates/user/profile.html');
var profilesView = Backbone.View.extend({
el: $('#main-container'),
initialize: function(){
this.template = _.template(profileT);
this.artist = this.model.get('artist');
},
render: function(){
$(this.el).html(this.template({
current_user: app.current_user,
user: this.model.toJSON(),
artist: this.artist.toJSON(),
}));
return this;
},
});
Template:
<div class="row">
<div class="grid_3">
<img src="<%=user.pict%>" class="frame" alt="">
<span class="title">Username – <strong><%=user.username%></strong></span>
<%if(current_user!=undefined && (current_user.get('is_admin') == true || current_user.get('id') == user.id)){%>
<span class="title"><span class="icon user"></span> Edit Profile</span>
<%}%>
<%if(artist.active==true){%>
<div>Go to Artist Profile</div>
<%}%>
<div class="separator"></div>
</div>
<div class="clear"></div>
</div>
I changed the name of the variable that was holding the template and it worked. I had another variable with the same name, holding a different template (that had albums in it) in another file and it was loading that one instead. I thought the scope was different because it was in another file, but it didn't.
Related
My project currently is made with VueJS. Now, we need to process a certain template with the input data, then store the result and use it to send an email (for example).
Can i render a template with the user data and save it? how?
I don't want to use another library for this purpose, unless We can't do with VueJS
I have read about SSR. But i don't want to use a server-side rendering. The idea is only render certain messages. Following a behavior like this:
save: function(){
userNote.user = ...
userNote.message = document.getElementById('message').innerHtml;
saveToServer(userNote);
}
The Message template:
<div id="message"> Dear {{user.name}}, please confirm that {{notes}} before {{date}}</div>
I hope i made me understand.
Thanks in advance.
Assuming your template is stored in a component, you could add an export method :
var templateComponent = Vue.component("template-component", {
template: "<p>Hello {{name}}</p>",
props: ["name"],
methods: {
exportHTML: function() {
return this.$el.outerHTML;
}
}
});
var app = new Vue({
el: '#app',
data: {
name: "David",
html: undefined
},
methods: {
getHTML: function() {
this.html = this.$refs.template.exportHTML();
}
}
});
<script src="https://unpkg.com/vue#2.4.2/dist/vue.min.js"></script>
<div id="app">
<div style="display: none">
<template-component :name="name" ref="template"></template-component>
</div>
<label>Name
<input v-model="name">
</label>
<button #click="getHTML">Get html</button>
<pre>{{ html }}</pre>
</div>
Then you just have to call the exportHTML method on the template component to retrieve the HTML.
I have a Backbone application where I trigger a View through an event-click and the Views HTML gets rendered with HandlebarsJS and displays data from a collection. So far it works except that my HTML gets repeated for each entry
My HTML looks like this:
<header>
<span class="blackdot" aria-hidden="true" />
<h1>Overview</h1>
</header>
<div>
<ul>
<li>
{{brand}}
<p>{{country}}</p>
</li>
</ul>
</div>
Right now, it duplicates the whole HTML code-block, including <header> and <h1>-tag for each entry but what I want to achieve is that my HTML would look like this:
<header>
<span class="blackdot" aria-hidden="true" />
<h1>Overview</h1>
</header>
<div>
<ul>
<li>
Audi
<p>Germany</p>
</li>
<li>
Hyundai
<p>South Korea</p>
</li>
<li>
Fiat
<p>Italy</p>
</li>
</ul>
</div>
My backbone View looks like this:
define(['backbone','handlebars', 'text!templates/Cars.html'],
function(Backbone,Handlebars, Template) {
'use strict';
var CarsView = Backbone.View.extend({
template: Handlebars.compile(Template),
events: {
},
initialize: function () {
_.bindAll(this, 'render');
},
render: function() {
var self = this;
self.collection.each(function(model){
self.$el.append(self.template({
brand:model.get('brand'),
country:model.get('country')
})
);
});
return this;
}
});
return CarsView;
}
);
Of course i could define the most of the HTML on the index page, wrap it in a DIV and do display:none and only write the <li>-tag in the Handlebars HTML template, but I want to avoid that and since the data what is returned are strings, I cant do the {{#each}}-thing... so, is there any solution for this?
To add onto what guzmonne said, the reason you are seeing this is because you are looping over the entire template. What you should be doing is taking the <li>'s and making a new template specifically for them. I've modified your existing code to show how something like what you are trying to do can be accomplished.
The CarsView Handlebars template:
<header>
<span class="blackdot" aria-hidden="true" />
<h1>Overview</h1>
</header>
<div>
<ul id="cars_list" />
</div>
The CarsView Backbone View:
var CarsView = Backbone.View.extend({
template: Handlebars.compile(Template),
events: {},
initialize: function () {
_.bindAll(this, 'render');
},
render: function() {
this.$el.html(this.template()); // this now only renders once
this.addAll();
return this;
},
addOne: function(car) {
var view = new CarView({model: car});
this.$("#cars_list").append(view.render().el);
},
addAll: function() {
this.collection.each(this.addOne, this);
}
});
The main differences between what I have done and your original CarsView is that firstly, the template itself doesn't contain any <li>'s. Instead, there is a placeholder ID which I have titled "cars_list". This container will give us an entry point to loop through the cars collection and add each item. Secondly, we aren't looping through the collection and re-rendering the CarsView. Instead, we take what CarsView dropped into the DOM and manually apppend to it from there.
Normally when dealing with collections, you can leverage Backbone's this.listenTo() function, which can take an event such as "reset" or "add" and tie it to a function. Since it would appear that the collection has already been fetched, we simply do this.addAll() after the CarsView template has been rendered. It is in here that the collection is looped and added.
To accomplish this, you will need another Backbone view, and another template...
The CarView Handlebars template:
{{brand}}
<p>{{country}}</p>
The CarView Backbone View:
var CarView = Backbone.View.extend({
tagName: "li",
template: Handlebars.compile(Template),
events: {},
initialize: function () {
// model event listeners (in case these list items can be edited/removed)
this.listenTo(this.model, 'change', this.render);
this.listenTo(this.model, 'destroy', this.remove);
},
render: function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
The CarView view is short and simple, but takes care of a lot for you. As you can see, this view will generate an <li> tag and take the contents of the model and send it to the handlebars template. No more having to deal with manually fetching model attributes, which is an added bonus.
I'm trying to render a search box on my page. I'm trying to keep it in a seperate view. The page without this boxview is working correctly but as soon as i initialize my BoxView i get the error related to underscore.
Uncaught TypeError: Object [object Object] has no method 'replace' underscore-min.js:29
Here is my View
/* Views */
var BoxView = Backbone.View.extend({
el: '.head',
initialize: function() {
this.render();
},
render : function(){
var that = this;
var template = _.template($('#search-box').html());
that.$el.html(template);
}
});
var boxview = new BoxView();
Template
<script type="text/template" id="search-box">
<form class="navbar-search pull-right" id="search">
<input class="search-query" name="searchText" type="text" id="searchText" placeholder="Search books"/>
<button type="submit" class="btn">Submit</button>
</form>
</script>
EDIT :
Removed the typo error
It seems to me it's just a typo:
var template = _.template($('#search-box')).html();
should be:
var template = _.template($('#search-box').html());
Edit:
I'd normally assume this is the kind of typo made while writing the question, but your error suggests the problem comes from an underscore call (you only have one), and that it's trying to use replace on an object. _.template surely uses replace and you give it an object ($('#search-box')). So it makes sense.
I have a text input which I use to collect a keyword from the user, and then send an ajax request (which is getting successful results). I would like to append the <ul> with <li> items, each representing a result.
Here is the which includes the search input and should get the appended <li> results.
<div class="search_group pull-right">
<h5>Find Other Groups<span class="caret"></span></h5>
<ul>
<li class="search-input">
<input type="text" class="search" name="search">
</li>
</ul>
</div>
I have the following underscore template which would ideally represent each result in the collection.
<script type="text/template" class="template" id="template-search-result">
<% _.each(results,function(result){ %>
<li class="result">
<a href="/surnames/<%=URL%>">
<%=result.name%>
<span><%=result.members%> – Members</span>
<span><%=result.ancestors%> – Ancestors</span>
</a>
</li>
<% }); %>
</script>
And now, my Backbone code.
I fetch my Results collection after the user enters something in the input field, and when that collection returns results from my server (which I can see successfully in the console), I would expect the ResultView to append the ul with additional li results.
window.Results = Backbone.Collection.extend({
url: function() {
return '/ajax/groups/search?q=' + query;
}
});
var results = new Results();
window.SearchInput = Backbone.View.extend({
el: ".search-input input",
events: {
"change": "getResults",
"keyup": "getResults",
"keydown": "getResults",
"paste": "getResults"
},
getResults: function() {
query = $('input').val();
results.fetch();
}
});
var searchInput = new SearchInput({ collection: results });
window.ResultView = Backbone.View.extend({
el: ".search-group ul",
initialize: function() {
_.bindAll(this, 'render');
this.collection.bind('change', this.render);
this.template = _.template($("#template-search-result").html());
},
render: function() {
this.$el.append( this.template( {results: this.collection.toJSON()} ));
return this;
}
});
var resultView = new ResultView({ collection: results });
You're either looking for the add or reset events, depending how you're loading your collection.
If you're doing a fetch, you'll want to listen to reset. If you are adding models to the collection manually, or calling fetch({update:true}), you'll get the add events.
The collection change event only fires when the one of the collection's models fires a change of its own.
Here's a list of all the built-in Backbone events for reference.
Instead of defining the "el" in my ResultView as:
el: ".search-group ul",
I changed it to an id and modified my markup accordingly:
el: "#result-list",
and:
<ul id="result-list">
<li class="search-input">
<input type="text" class="search" name="search">
</li>
</ul>
I am using backbone.js,
I have a model that is formatted like this:
{
username:"name",
id:"1",
picture:"image.jpg"
}
I want to have a view like this:
<div id="1">
<span class="name">name<span>
<img src="image.jpg" />
</div>
from this template:
<script type="text/template" id="users-template">
<span class="name"><%= username %></span>
<img src="<%= image %>" />
</script>
but I get stuck when it comes to putting the users id into the views id attribute.
UserView = Backbone.View.extend({
id: this.model.id, //this is what I have tried but it doesnt work
initialize: function(){
this.template = _.template($('#users-template').html());
},
render: function(){
...
}
})
does anyone know how put the current models id into the id attribute?
I use that
id : function () {
return this.model.get("id");
}
U can get the model's data only using ".get()"
Properties like tagName, id, className, el, and events may also be defined as a function, if you want to wait to define them until runtime. #from Backbonejs.org
First of all, your model has picture but your template uses image, they should be the same or you'll get a "missing variable" error from your template.
The id attribute on a view is supposed to be the DOM id of an existing element; Backbone won't add it to the el, Backbone uses it to find the el. Besides, this won't be a view when id: this.model.id is executed (this will probably be window or some other useless thing) and the model property isn't set until the view is instantiated anyway.
The view's el is:
created from the view's tagName, className, id and attributes properties, if specified. If not, el is an empty div.
So drop the id from your view and use the default <div> for the el. Then, in your render method, you can set the id attribute on this.el; also, using a numeric id attribute causes trouble with some browsers so you should usually prefix it the id with something non-numeric:
render: function() {
this.el.id = 'v' + this.model.get('id');
this.$el.append(this.template(this.model.toJSON()));
return this;
}
Demo: http://jsfiddle.net/ambiguous/ZBE5z/
Open your console when running the demo and you'll be able to see the HTML that ends up in your view's this.el.
This happens because the model isn't set when the JavaScript initially gets executed. Try this:
initialize: function(){
this.id = this.model.id;
this.template = _.template($('#users-template').html());
},