Backbonejs - View custom HTML attributes? - javascript

I want to add a custom HTML attribute to a view so that it shows when I render a View. This is my code:
var Song = Backbone.Model.extend({
defaults: {
title: 'Red',
artist: 'Taylor Swift',
filename: 'audio1.mp3',
playlist: 0
}
});
var Player = Backbone.View.extend({
tagName: "audio",
id: "player",
//Can I add a custom HTML attr here? eg. onended="view.next()"
newTemplate: _.template('<source src="<%= filename %>" >'),
initialize: function(){
this.render();
},
render: function(){
var track = this.model.currentTrack
var currentModel = _.find(this.model.models, function(arr){return arr.attributes.playlist == track});
this.$el.html(this.newTemplate(currentModel.toJSON()));
$(document.body).html(this.el);
},
next: function(){
this.model.currentTrack++;
console.log('currentTrack: ', this.model.currentTrack);
this.render();
}
});
var Playlist = Backbone.Collection.extend({
model: Song,
initialize: function(models,options) {
_.extend(this,_.pick(options, 'currentTrack'));
}
});
var playlist = new Playlist([
{
title: 'Red',
artist: 'Taylor Swift',
filename: 'audio1.mp3',
playlist: 1
},
{
title: 'Style',
artist: 'Taylor Swift',
filename: 'audio2.mp3',
playlist: 2
}
],
{
currentTrack: 1
});
So, basically I want to add an HTML listener to the rendered View (<audio onended="view.next()">)so when the audio is finished playing I can trigger the next method of the view. How can I do this?
I would prefer to use Backbone events to do this but according to this answer I have to trigger events from the HTML element.

So it turns out I was going totally wrong about this and the answer was quite simple.
I just have to treat it like any other event...
var Player = Backbone.View.extend({
tagName: "audio",
id: "player",
//I add an events attribute with the event and the method to trigger.
events: {
'ended': 'next'
},
newTemplate: _.template('<source src="<%= filename %>" >'),
initialize: function(){
this.render();
},
Thanks to mu is too short for the heads up. He also mentioned to check out delegate events

Related

Display collection length in a view in Marionette.js

I'm learning Marionette.js and have a scenario, where my app has:
app.addRegions({
main: '#omen',
newItem: '#addnewitem',
counter: '#counter'
});
These regions. I have these Model/Collections:
var Item = Backbone.Model.extend(),
Items = Backbone.Collection.extend({
model: Item,
url: 'api/items'
}),
I have an Item view and Items view:
ItemView = Mn.ItemView.extend({
tagName: 'tr',
template: '#itemView',
events: {
'click #btnDeleteBook' : 'deleteItem'
},
deleteItem: function() {
app.trigger('item:delete', this.model);
}
}),
ItemsView = Mn.CollectionView.extend({
tagName: 'table',
childView: ItemView,
onShow: function(view) {
TweenMax.staggerFrom($(view).find('td'), 1, {
x: 100
}, 2);
}
}),
I have an initializer function, that listens for events above and does stuff through app.ItemController. It all works fine.
But now I want to add a region (counter region), that displays the total number of items in my collection. I need this to be a separate view ideally, because I will be displaying it in different places.
So I do this:
DisplayCounter = Mn.ItemView.extend({
template: _.template('Total: '+ app.Items.length),
}),
app.Items is an instance of Collection declared above. But even before instantiation of DisplayCounter, I get error:
Uncaught TypeError: Cannot read property 'length' of undefined.
Please help... :(
------------------------- E D I T ----------------------
I've achieved it, but it seems to be so complicated to do such a tiny thing.
Changed my collection like so:
Items = Backbone.Collection.extend({
model: Item,
url: 'api/items',
initialize: function() {
this.listenTo(this, 'add', function() {
app.trigger('collection:updated', this);
});
}
}),
and changed my DisplayCounter like this:
DisplayCounter = Mn.ItemView.extend({
template: _.template('Total: <%=length%>'),
templateHelpers: function() {
return {
length: this.lengthVariable || 0
}
},
initialize: function() {
app.on('collection:updated', function(params){
this.lengthVariable = params.length;
this.render();
}.bind(this));
}
}),
I can't believe there's no easier way to do this.. :/
The code that sets up DisplayCounter is being run before the code that puts an instance of Items into app.Items.
Even if you avoided this problem by assigning app.Items first, you'd still have a problem - the template property is only set once so you'd only ever see the length of app.Items at the time that you define DisplayCounter.
Rather than hard-coding the value directly into the template string, you should supply a value at render time. Mn.View.serializeData allows you to customise the data that is passed into the template function at render time:
DisplayCounter = Mn.ItemView.extend({
template: _.template('Total:: <%= itemCount %>),
serializeData: function() {
return { itemCount: app.Items.length }
}
}),
app.Items is not being defined.
In Marionette you can define which collection or model are your views going to use.
ItemsView = Mn.CollectionView.extend({
tagName: 'table',
childView: ItemView,
collection: myItems // An instance of your collection
onShow: function(view) {
TweenMax.staggerFrom($(view).find('td'), 1, {
x: 100
}, 2);
}
}),
So marionette is going to render one itemView per element in your collection. Then inside of your collection view this.collection is going to refer to the collection instance. So this.collection.length will have what you need.
And in your ItemView you can get the corresponding model by using this.model

Backbone.js: Using Embeded Javascript inside the View

I'm trying to make the View dynamic as when someone touches the item, but using the '<%= myClassName %>' inside the View doesn't work. I can't use this technique inside the HTML file as it would draw another element and that's not the idea. Also I have set a template but it has nothing on it. I just did that to relate jQuery Mobile into a data-role="content" and render the view inside the content. Any ideas?
Here is what I have:
app.js
var TodoItem = Backbone.Model.extend({
toggleStatus: function(){
if(this.get('status') === 'incomplete'){
this.set({'status': 'complete'});
} else {
this.set({'status': 'incomplete'});
}
this.save();
// PUT /TODOS/1
}
});
var TodoItems = Backbone.Collection.extend({
model: TodoItem,
localStorage: new Backbone.LocalStorage("button"),
initialize: function () {
this.on('remove', this.hideModel, this);
},
hideModel: function (model) {
model.trigger('hide');
}
});
var TodosView = Backbone.View.extend({
initialize: function () {
this.collection.on('add', this.addOne, this);
},
addOne: function (todoItem) {
var todoView = new TodoView({ model: todoItem });
this.$el.append(todoView.render().el);
},
addAll: function () {
this.collection.forEach(this.addOne, this);
},
render: function() {
this.collection.forEach(this.addOne, this);
this.addAll;
return this;
}
});
var TodoView = Backbone.View.extend({
tagName: 'span',
// THIS IS THE MAIN PROBLEM
className: '<%= status %>',
// END COMMENT
template: _.template( $('#personTemplate').html() ),
events: {
"touchstart": "toggleStatus",
"touchend": "toggleStatus"
},
toggleStatus: function () {
this.model.toggleStatus();
},
remove: function(){
this.$el.remove();
},
initialize: function(){
this.render();
this.model.on('change', this.render, this);
this.model.on('destroy', this.remove, this);
this.model.on('hide', this.remove, this);
},
render: function () {
var attributes = this.model.toJSON();
this.$el.html(this.template(attributes));
return this;
}
});
var todoItems = new TodoItems([
{
description: 'Jeffrey Way',
status: "incomplete",
id: 1
},
{
description: 'John Doe',
status: "incomplete",
id: 2
},
{
description: 'Sally Doe',
status: "incomplete",
id: 3
}
]);
var todosView = new TodosView({
el: $('#elem'),
collection: todoItems
});
todosView.render().el
You can do
this.$el.addClass(this.model.get('status'))
inside the view's render method.
Trying to use a template value in the view code doesn't make any sense; those properties are set when the object is parsed, so how would it know which object to fetch the status from?
The best thing todo, if i understand correctly, is to listen to the change:status in your view, and adding/removing a class to your view according to the value of status.
in your initialize of TodoView:
this.listenTo(this.model, 'change:status', this.changeStatus);
in your TodoView declare:
changeStatus : function(model, value, options)
{
// add a class or toggle it or change something in the view..
}
Solution 1
var TodoView = Backbone.View.extend({
tagName: 'span',
...})
var x=new TodoView ({className:'sample'});
Solution 2
Use a template!
var TodoView = Backbone.View.extend({
template="<span class=<%-className%>>Some Stuff Goes Here</span>",
...
render:function(){
var $ele=$(_.template(this.template,{className:'sample'}));
this.$el.replaceWith($ele);
this.$el=$ele;
this.delegateEvents(); //inbuilt-method, to re-bind all event handlers
});

Unable to get the Backbone View working for a simple example

I am trying to write a simple example using Backbone.js for study. Some how nothing gets printed in the browser. Need a little help here. The code is given below.
Html:
<div id="container">
<ul id="person-list">
</ul>
</div>
Models
var Person = Backbone.Model.extend({
defaults: {
id: 0,
name: ''
}
});
var PersonStore = Backbone.Collection.extend({
model: Person,
url: 'api/person', //currently not using
initialize: function () {
console.log("Store initialize");
}
});
Views
var PersonView = Backbone.View.extend({
tagName: 'li',
initialize: function () {
_.bindAll(this, "render");
},
render: function () {
$(this.el).append(this.model.name) //model.name shows undefined here
return this;
}
});
var PersonListView = Backbone.View.extend({
el: $('#person-list'),
tagName:'ul',
initialize: function () {
_.bindAll(this, "render");
this.render();
},
render: function () {
self = this;
this.collection.each(function (person) { //name property undefined here on person
var personView = new PersonView({ model: person });
$(self.el).append(personView.render().el);
});
}
});
Sample Run
var persons = new PersonStore([
new Person({id:1, name: "Person 1"}),
new Person({ id: 2, name: "Person 2" }),
]);
new PersonListView({ collection: persons });
The above setup prints nothing(blank) on screen. I have struggled now for some time and need a little help here as to why the two Person's name does not get displayed in the browser.
To make your code work you have to replace
this.$el.append(this.model.name)
with
this.$el.append(this.model.get('name'))
Always use method .get() to access model properties.
Also i highly recommend you use templates for rendering views. This approach let you write .render() implementation once and will be no need to change it if you need visual changes, you can make in template

Adding new models to a backbone collection, not replace

I am trying to add new models to a collection (i'm not saving to the server this time, just doing this in memory). I have the code below:
$(function () {
//Model
var Story = Backbone.Model.extend({
defaults: {
'title': 'This is the title',
'description': 'Description',
'points': 0
},
url: '/'
});
//Collection
var Stories = Backbone.Collection.extend({
model: Story,
url: '/'
});
//View
var BaseView = Backbone.View.extend({
el: $('#container'),
events: {
'click .inner': 'onClickInner'
},
onClickInner: function() {
this.options.x++;
this.story.set({
'title': 'This is my collection test ' + this.options.x,
'description' : 'this is the description'
});
this.stories.add(this.story);
this.render();
},
initialize: function () {
this.stories = new Stories();
this.story = new Story();
},
render: function(){
console.log(this.stories);
}
});
//Initialize App
var app = new BaseView({
'x' : 0
});
});
My question is this, for each time 'onClickInner' runs, I want to add a new model to the collection. However, in my code it replaces the existing model in the collection. How do I add new models and not replace?
Thanks for your time.
It happens because you update current model instead of adding new new one. To fix it you have to just execute add method on your collection. This method adds passed data as a new model to your collection:
this.stories.add({
'title': 'This is my collection test ' + this.options.x,
'description' : 'this is the description'
});

Uncaught TypeError: Object [object Window] has no method 'each' function

Hey guys here is my js file and I am taking error message about each function at line:24 and I do not know why I couldnt find whats wrong. I am just trying to see the list of items on the console.log panel but it does not even give me list on the html page.
(function() {
window.App = {
Models: {},
Collections: {},
Views: {}
};
window.template = function(id){
return _.template( $('#' + id).html() );
};
App.Models.Task = Backbone.Model.extend({});
App.Collections.Task = Backbone.Collection.extend({
model: App.Models.Task
});
App.Views.Tasks = Backbone.View.extend({
tagName: 'ul',
render: function(){
this.collection.each( this.addOne, this);
return this;
},
addOne: function(task){
//creating new child view
var taskView = new App.Views.Task({ model: task });
//append to the root element
this.$el.append(taskView.render().el);
}
});
App.Views.Task = Backbone.View.extend({
tagName: 'li',
template: template('taskTemplate'),
events: {
'click .edit': 'editTask'
},
editTask: function(){
alert('you are editing the tas.');
},
render: function(){
var template = this.template( this.model.toJSON() );
this.$el.html(template);
return this;
}
});
var tasksCollection = new App.Views.Task([
{
title: 'Go to the store',
priority: 4
},
{
title: 'Go to the mall',
priority: 3
},
{
title: 'get to work',
priority: 5
}
]);
var tasksView = new App.Views.Tasks({ collection: tasksCollection });
$('.tasks').html(tasksView.render().el);
})();
You're creating a view instance as though it was a class:
App.Views.Tasks = Backbone.View.extend({ /* ... */ });
var tasksCollection = new App.Views.Task([
{
title: 'Go to the store',
priority: 4
},
//...
and then you create another instance of that view and hand it tasksCollection as though it really was a collection:
var tasksView = new App.Views.Tasks({ collection: tasksCollection });
But views and collections are different things and only collection's have an each method (unless you add an each to your view of course).
You want to create tasksCollection as an App.Collections.Task:
var tasksCollection = new App.Collections.Task([
{
title: 'Go to the store',
priority: 4
},
//...
Hi this is happening cus your each method not able to find the collection. As well the singular Task to Tasks
At this line:
Change this
var tasksCollection = new App.Views.Task([
TO, this:
var tasksCollection = new App.Collections.Tasks([

Categories