I am having a little trouble rendering my player.
Im trying to create a structure using backbonejs that will
Add a new player view when a new player joins
Updates a player score
In updatePlayerData I update the player score if the player model has already been created. If not I create a new one.
The problem is in playerScore. I am creating a span everytime render is called. This is causing many player classes to be creating under the one player view
I need to be able to loop through my current views and update the player of a certain id. I need to then re render that player view so the new content is displayed.
For reference here is my code so far:
var Player = Backbone.Model.extend({});
var PlayerView = Backbone.View.extend({
className: 'player',
initialize: function () {
_.bindAll(this, 'render');
},
render: function () {
this.playerScore();
return this;
},
playerScore: function(){
$('<span/>', {
class: 'playerScore'
}).appendTo($(this.el)).html(this.model.get('score'));
}
});
var Team = Backbone.Collection.extend({
model: Player
});
var TeamView = Backbone.View.extend({
el: '#users',
initialize: function () {
_.bindAll(this, 'updatePlayerData', 'addPlayer');
this.collection.bind('add', this.addPlayer);
this._subviews = [];
},
updatePlayerData: function (playerEntity) {
var view = _(this._subviews).find(function(v){
return playerEntity.id == v.model.get('id');
});
if(view){
view.model.set({
score: playerEntity.score
});
view.render();
return;
}
var playerModel = new Player();
playerModel.set({
score: playerEntity.score
});
this.collection.add(playerModel);
},
addPlayer: function (player) {
var playerView = new PlayerView({
model: player
});
var playerHtml = playerView.render().el;
$(this.el).html(playerHtml);
this._subviews.push(playerView);
}
});
Try modifying your playerScore method to something like:
playerScore: function () {
if (!$(this.el).find('span.playerScore').length) {
$('<span/>', {
class: 'playerScore'
}).appendTo($(this.el)).html(this.model.get('score'));
} else {
$(this.el).find('span.playerScore').html(this.model.get('score'));
}
}
Related
I'm relatively new to programming and this is my first time posting on here, so sorry in advance if this isn't presented correctly.
I'm building a health tracker app with Backbone JS. I am retrieving data from the Nutritionix API and populating a view on the left side of the screen with that data. I want the user to be able to drag an item from the populated view and drop it in a view to the right in which case a calorie counter will increase. I also need that data to persist so that when the user closes and reopens the app their selected data in the view will remain the same.
I've implemented the drag and drop feature just fine. I am now trying to make it so that when the user drops an item from the left view into the right view a collection is created that only contains the data in the right view. I believe that I need to do this so I can persist the data. Right now, I am having trouble transferring the API data along into the right view. Here are the relevant code snippets:
appView:
var app = app || {};
var ENTER_KEY = 13;
app.AppView = Backbone.View.extend({
el: '#health-app',
urlRoot: 'https://api.nutritionix.com/v1_1/search/',
events: {
'keypress': 'search',
'click #clearBtn': 'clearFoodList',
'drop .drop-area': 'addSelectedFood',
// 'drop #selected-food-template': 'addSelectedFood'
},
initialize: function() {
this.listenTo(app.ResultCollection, 'add', this.addFoodResult);
// this.listenTo(app.SelectedCollection, 'add', this.addSelectedFood);
app.ResultCollection.fetch();
app.SelectedCollection.fetch();
this.clearFoodList()
},
addFoodResult: function(resultFood) {
var foodResult = new SearchResultView({
model: resultFood
});
$('#list').append(foodResult.render().el);
},
// addSelectedFood: function(selectedFood) {
// // var selectedFoodCollection = app.SelectedCollection.add(selectedFood)
// console.log(app.SelectedCollection.add(selectedFood))
// },
clearFoodList: function() {
_.invoke(app.ResultCollection.toArray(), 'destroy');
$('#list').empty();
return false;
},
search: function(e) {
var food;
//When the user searches, clear the list
if($('#search').val() === '') {
_.invoke(app.ResultCollection.toArray(), 'destroy');
$('#list').empty();
}
//Get the nutrition information, and add to app.FoodModel
if (e.which === ENTER_KEY) {
this.query = $('#search').val() + '?fields=item_name%2Citem_id%2Cbrand_name%2Cnf_calories%2Cnf_total_fat&appId=be7425dc&appKey=c7abd4497e5d3c8a1358fb6da9ec1afe';
this.newUrl = this.urlRoot + this.query;
var getFood = $.get(this.newUrl, function(data) {
var hits = data.hits;
var name, brand_name, calories, id;
_.each(hits, function(hit){
name = hit.fields.item_name;
brand_name = hit.fields.brand_name;
calories = hit.fields.nf_calories;
id = hit.fields.item_id;
food = new app.FoodModel({
name: name,
brand_name: brand_name,
calories: calories,
id: id
});
//If the food isn't in the ResultCollection, add it.
if (!app.ResultCollection.contains(food)) {
app.ResultCollection.add(food);
}
});
food.save();
});
}
}
});
breakfastView:
var app = app || {};
app.BreakfastView = Backbone.View.extend({
el: '#breakfast',
attributes: {
'ondrop': 'ev.dataTransfer.getData("text")',
'ondragover': 'allowDrop(event)'
},
events: {
'dragenter': 'dragEnter',
'dragover': 'dragOver',
'drop': 'dropped'
},
initialize: function() {
this.listenTo(app.SelectedCollection, 'change', this.addSelectedFood);
},
render: function() {},
addSelectedFood: function(selectedFood) {
// var selectedFoodCollection = app.SelectedCollection.add(selectedFood)
console.log(app.SelectedCollection.add(selectedFood))
},
dragEnter: function (e) {
e.preventDefault();
},
dragOver: function(e) {
e.preventDefault();
},
dropped: function(ev) {
var data = ev.originalEvent.dataTransfer.getData("text/plain");
ev.target.appendChild(document.getElementById(data));
// console.log(app.SelectedCollection)
this.addSelectedFood(data);
},
});
new app.BreakfastView
SelectedCollection:
var app = app || {};
app.SelectedCollection = Backbone.Collection.extend({
model: app.FoodModel,
localStorage: new Backbone.LocalStorage('selected-food'),
})
app.SelectedCollection = new app.SelectedCollection();
Here's my repo also, just in case: https://github.com/jawaka72/health-tracker-app/tree/master/js
Thank you very much for any help!
I have tried to remove a view that contains makers that are rendered in map. When I click a button, the event click .ver is being activated, but nothing happens and I receive the following error: Uncaught TypeError: undefined is not a function. I don't know what I'm doing wrong.
My code:
ev.views.Home_Eventos = Backbone.View.extend({
initialize: function(map){
this.template = _.template(ev.templateLoader.get('home_list_eventos'));
this.map = map;
//console.log(this.map);
this.render(this.map);
},
events: {
'click .ver' : 'teste'
},
teste: function(){
ev.views.Markers.remove();
},
render: function(map){
var that = this;
that.map = map;
var imagens = new ev.models.ImagemCollection();
imagens.fetch({
success: function(){
that.$el.html(that.template({imagens: imagens.models}));
var marcadores = imagens.models;
setTimeout(function() {
that.local(that.map);
var marcadores = new ev.views.Markers(that.map);
}, 0);
return that;
}
});
}
});
ev.views.Markers = Backbone.View.extend({
initialize: function(map){
this.map = map;
this.render(this.map);
},
render: function(map){
var that = this;
var imagens = new ev.models.ImagemCollection();
imagens.fetch({
success: function(){
var marcadores = imagens.models;
setTimeout(function() {
_.each(marcadores, function(marcador){
var myLatlng = new google.maps.LatLng(marcador.get('latitude'),marcador.get('longitude'));
var marker = new google.maps.Marker({
position: myLatlng,
map: that.map,
});
});
}, 0);
return that;
}
});
}
});
You need to keep a reference to the views you want to remove. For example, you have:
var marcadores = new ev.views.Markers(that.map);
you could save a reference on your view by doing this instead:
that.marcadores = new ev.views.Markers(that.map);
and then in your event handler,
teste: function(){
this.marcadores.remove();
},
This will destroy the Marker view that you've created but may not clean up the Google Maps markers managed by that view.
When you write the following:
ev.views.Markers = Backbone.View.extend({ ... });
You are creating a new class. In order for ev.views.Markers.remove(); to work correctly ev.views.Markers needs to refer to an instance of a class rather than the class itself.
You're getting the error Uncaught TypeError: undefined is not a function because ev.views.Markers.remove is not a function, ev.views.Markers.prototype.remove is.
I have a backbone application which works fine but was getting a bit heavy to be in one file so I have started to separate it into different files I now have:
backbone-view.js
backbone-router.js
...
I am using my backbone router to instantiate views when the URL changes like so:
var Router = Backbone.Router.extend({
routes: {
'our-approach.php': 'instantiateOurApproach',
'our-work.php': 'instantiateOurWork',
'who-we-are.php': 'instantiateWhoWeAre',
'social-stream.php': 'instantiateSocialStream',
'contact.php': 'instantiateContact'
},
instantiateOurApproach: function() {
if(window.our_approach_view == null) {
window.our_approach_view = new OurApproachView();
}
},
instantiateOurWork: function() {
if(window.our_work_view == null) {
window.our_work_view = new OurWorkView();
}
},
instantiateWhoWeAre: function() {
if(window.who_we_are_view == null) {
window.who_we_are_view = new WhoWeAreView();
}
},
instantiateSocialStream: function() {
if(window.social_stream_view == null) {
window.social_stream_view = new SocialStreamView();
}
},
instantiateContact: function() {
if(window.contact_view == null) {
window.contact_view = new ContactView();
}
}
});
The problem I am now having is that I cannot access the views as they are declared in a separate file so the following are all undefined:
OurApproachView()
OurWorkView()
WhoWeAreView()
SocialStreamView()
ContactView()
I have tried doing:
window.OurApproachView()
But this doesn't work.
I am not sure what my next move is so if anyone can help that would be awesome.
Thanks!
EDIT
OK so it seems doing:
window.OurApproachView()
does actually work, my apologies there, but does anyone have a better suggestion?
You can take this approach:
// sample-view.js
var app = app || {};
$(function() {
app.SampleView = Backbone.View.extend({
el: '#sample-element',
template : // your template
events: {
// your events
},
initialize: function() {
// do stuff on initialize
},
render: function() {
// do stuff on render
}
});
});
Similarly, all your js files (models, collections, routers) can be setup like this. You would then be able to access any view from the router by doing:
var view = new app.SampleView();
This works:
window.our_work_view = new window.OurApproachView();
But I don't like it as a solution.
Anyone suggest anything better?
I think I might have a problem with zombie views in my Backbone Marionette app.
How can I check for unclosed views and memory leaks? I'm using the illuminations-for-developers.com add-on for Firefox and as I move around my application I see over 1000 views piling up in the 'widgets' illuminations tab - and when I inspect the HTML for them the majority are not in the DOM. Are these zombied views?
Have added the code I'm using below to get peoples opinion on if I'm attacking the problem the right way.
I'm trying to build a UI similar to the Facebook multiple friend selector dialog (see pic).
I have a layout with two collection views, one populated with a list of users, and an empty one in which the selected users are added to.
I want to use this layout in multiple areas of my app. So I have built a controller object that handles initializing it and loading the data for the collections, and then I initialize it and show it in another region whenever it is required.
Would appreciate tips on how to go about this, thanks.
Codez:
MyApp.UserFilterController
MyApp.UserFilterController = (function(){
var UserFilterController = {};
var selectedUsersCol;
var userFilterColView;
var selectedUsersColView;
var usersCol;
UserFilterController.initialize = function ( callback, excludeUsers ) {
// make a query...
// exclude the users...
var usersQ = new Parse.Query(Parse.User);
// just users with email addresses
usersQ.exists('email');
usersQ.exists('name');
usersQ.limit(1000);
usersQ.ascending('name');
usersQ.notContainedIn('objectId',excludeUsers);
usersCol = usersQ.collection();
// tell it where to render... append to the body give it an element?
userFilterColView = new MyApp.UserFilterUserCollectionView({
collection:usersCol
});
usersCol.fetch({
success:function (col) {
console.log("users collection fetched", col.length);
},
error:function () {
console.log("error fetching users collection");
}
});
$('#subpage-header').text("Users Selection");
// empty collection to hold the selected users
selectedUsersCol = new MyApp.Users();
// view to show the selected users
selectedUsersColView = new MyApp.SelectedUserCollectionView({
collection:selectedUsersCol
});
_.extend(selectedUsersCol, newBackboneAddMethod());
MyApp.userFilterLayout = new MyApp.UserFilterLayout();
MyApp.slideUp.content.show(MyApp.userFilterLayout);
MyApp.userFilterLayout.selectedusers.show(selectedUsersColView);
MyApp.userFilterLayout.allusers.show(userFilterColView);
//When user clicks on user in all users then its added to selected users
userFilterColView.on("itemview:clicked", function(childView, model){
console.log(model);
selectedUsersCol.add(model);
});
userFilterColView.on("collection:rendered", function(childView, model){
console.log('its rendered');
});
//When user clicks on selected user then it is removed from the collection
selectedUsersColView.on("itemview:clicked", function(childView, model){
console.log(model);
console.log(model.id);
selectedUsersCol.remove(model);
});
MyApp.App.vent.bind("slideUp:send",function(){
console.log("send button has been clicked. attempting call back")
callback(selectedUsersCol);
});
//unbinds the trigger above when view is being closed
userFilterColView.on('collection:before:close', function (){
MyApp.App.vent.unbind("slideUp:send");
console.log('colView before:close')
});
};
UserFilterController.removeUser = function ( user ) {
//console.log("you asked to remove", usersArray.length, 'users');
selectedUsersCol.remove(user);
usersCol.remove(user);
};
UserFilterController.generateListview = function ( user ) {
userFilterColView.$el.listview();
};
UserFilterController.resetSelected = function (user) {
selectedUsersCol.reset();
};
UserFilterController.cleanup = function () {
console.log("its closing");
//selectedUsersColView.unbindAll();
// selectedUsersColView.close();
userFilterColView.close();
// userFilterLayout.unbindAll();
// MyApp.userFilterLayout.close();
// MyApp.slideUp.content.close();
// MyApp.slideUp.close();
};
return UserFilterController;
}());
MyApp.EventDisplayLayout
MyApp.EventDisplayLayout = Backbone.Marionette.Layout.extend({
template: '#event-display-layout',
id: "EventDisplayLayout",
events: {
'click #invite': 'showUserFilter'
},
// User clicked on 'invite' button
showUserFilter: function () {
$.mobile.changePage($('#subpage'), {changeHash: false,transition: 'slideup'});
MyApp.UserFilterController.generateListview();
}
}
MyApp.showEventDisplay
MyApp.showEventDisplay = function (event) {
var eventDisplayLayout = new MyApp.EventDisplayLayout({});
MyApp.App.mainRegion.show(eventDisplayLayout);
var Invitees = event.get("invitees");
var excludeIds = [];
_.each(Invitees,function(invitee){
excludeIds.push(invitee.id);
});
MyApp.UserFilterController.initialize(function (selectUsersCol){
console.log("In call back: ",selectUsersCol);
},excludeIds);
};
MyApp.SlideUpPageLayout
// The generic layout used for modal panel sliding up from bottom of page.
MyApp.SlideUpPageLayout = Backbone.Marionette.Layout.extend({
el: '#subpage',
//template: '#homepage-temp',
regions: {
header: '.header',
content: '.content'
},
events:{
'click .send':'slideUpSend',
'click .cancel':'slideUpCancel'
},
onShow: function () {
console.log("SlideUpPage onShow");
this.$el.trigger('create');
},
initialize: function () {
// make user collection...
},
slideUpSend: function () {
console.log("send button has been pressed");
MyApp.App.vent.trigger('slideUp:send');
$.mobile.changePage($('.type-home'),{transition: 'slideup',reverse:true});
},
slideUpCancel: function () {
// MyApp.App.vent.trigger('slideUp:cancel');
$.mobile.changePage($('.type-home'),{transition: 'slideup',reverse:true});
}
});
MyApp.UserFilterLayout
// The layout used for the user filter panel sliding up from bottom of page.
MyApp.UserFilterLayout = Backbone.Marionette.Layout.extend({
template: '#userfilterlayout',
//template: '#homepage-temp',
regions: {
selectedusers: '.selectedusers',
allusers: '.allusers'
},
onShow: function () {
console.log("userfilterlayout onShow");
this.$el.trigger('create');
}
});
I have to remove current view being in the same view...
if i am in the parent view i can do
parentView.remove(childView);
but being on the child view i am not having parentView so how can i pop childView to get parentView on top, as it happens on pressing the back button in iOS??
please help
Here is my childView file..
function DetailView(){
var self = Ti.UI.createView({
backgroundColor:'#fff'
});
// Create a Button.
var aButton = Ti.UI.createButton({
title : 'aButton',
height : '50',
width : '100',
top : '10',
left : '20'
});
// Listen for click events.
aButton.addEventListener('click', function() {
alert('\'aButton\' was clicked!');
I have to navigate back on press of aButton, what should i Put here to do that
});
// Add to the parent view.
self.add(aButton);
return(self);
}
module.exports = DetailView;
Here is my parent view:
//FirstView Component Constructor
var self = Ti.UI.createView();
function FirstView() {
//create object instance, a parasitic subclass of Observable
var DetailView = require('ui/common/DetailView');
var data = [{title:"Row 1"},{title:"Row 2"}];
var table = Titanium.UI.createTableView({
data:data
});
table.addEventListener('click', rowSelected);
self.add(table);
return self;
}
function rowSelected()
{
var DetailView = require('ui/common/DetailView');
//construct UI
var detailView = new DetailView();
self.add(detailView);
}
module.exports = FirstView;
You can pass your parentView to the constructor of child view at this point:
//construct UI
var detailView = new DetailView(parentView);
self.add(detailView);
and in click event
aButton.addEventListener('click', function() {
if ( parentView != null ) {
parentView.remove(childView);
}
});