So I'm building a mobile website and I have a directory called 'api' with various php files hat echo JSON formatted data from a remote API. I did this to avoid the cross-domain issue.
But one of the php files needs a GET parameter (i.e. id) so that I can echo the JSON data for a specific object based on it's id.
My collection will need to do this (assuming this will work):
define([
'backbone',
'models/tournaments/single'
], function(Backbone, singleModel) {
var TournamentCollection = Backbone.Collection.extend({
model: singleModel,
url: '/api/tournament.php?id=' + id,
parse: function(response) {
return response;
}
});
return TournamentCollection;
});
I have this in my router, but how do I pass the 'id' value to the view or collection:
define([
'jquery',
'underscore',
'backbone',
'views/home',
'views/tournament'
], function($, _, Backbone, HomeView, TournamentView) {
var AppRouter = Backbone.Router.extend({
routes: {
'': 'home',
'tournament/:id': 'tournament'
}
});
var initialize = function() {
var app_router = new AppRouter;
app_router.on('route:home', function() {
var homeView = new HomeView();
});
app_router.on('route:tournament', function(id) {
var tournamentView = new TournamentView({id: id});
});
Backbone.history.start();
};
return {
initialize: initialize
};
});
Couple of things:
1) Your definition of the url property of the collection will not work as id is likely not defined when defining the TournamentCollection class. You can use a function rather than a property. TournamentCollection will become something like this:
define([
'backbone',
'models/tournaments/single'
], function(Backbone, singleModel) {
var TournamentCollection = Backbone.Collection.extend({
model: singleModel,
initialize: function (options) {
this.id = options.id;
},
url: function () {
return '/api/tournament.php?id=' + this.id
},
parse: function(response) {
return response;
}
});
return TournamentCollection;
});
This way you can initialize the object with an id, and later, when the url is fetched it will include the correct id.
2) I would probably initialize and fetch the collection from the router. Then from the initialize of the view, listen for that fetch to complete and ultimately re-render the view. Something like this:
define([
'jquery',
'underscore',
'backbone',
'views/home',
'views/tournament'
], function($, _, Backbone, HomeView, TournamentView) {
var AppRouter = Backbone.Router.extend({
routes: {
'': 'home',
'tournament/:id': 'tournament'
}
});
var initialize = function() {
var app_router = new AppRouter;
app_router.on('route:home', function() {
var homeView = new HomeView();
});
app_router.on('route:tournament', function(id) {
var tournaments = new TournamentCollection({ id: id });
tournaments.fetch();
var tournamentView = new TournamentView({ collection: tournaments });
});
Backbone.history.start();
};
return {
initialize: initialize
};
});
// Tournament View define stuff
var TournamentView = Backbone.View.extend({
initialize: function () {
this.listenTo(this.collection, 'sync', this.render);
},
render: function () {
//...
}
});
return TournamentView
hope that helps. :)
Related
I just started to lean Backbone, and underscore template, not sure if the structure suitable for it.
The question is, when I reload a template, how to re-bind event from Backbone which is re-run the events function.
The example is simply load an index page, insert main_option template into the page, and jump between main_option, and role_view template.
Here is the app.js which I put router in there:
define(['jquery', 'underscore', 'backbone', 'views/role_view', 'views/main_options'], function ($, _, Backbone, rolePage, mainOptions) {
var appRouter = Backbone.Router.extend({
$el: $('.container'),
initialize: function () {
this.mainOptionPage = mainOptions;
this.loginView = rolePage;
},
routes: {
"": "mainOption",
"views/role_view": "login"
},
mainOption: function () {
this.$el.html(this.mainOptionPage.render().$el);
},
login: function () {
this.$el.html(this.loginView.render().$el);
}
});
var router = new appRouter();
Backbone.history.start();
});
Here is the main_option.js
define(['jquery', 'underscore', 'backbone'], function($, _, Backbone){
var Person = Backbone.Model.extend({
defaults: {
name: 'Guest Worker',
age: 23,
occupation: 'worker'
}
});
var testView = Backbone.View.extend({
$el: $('#indexPage'),
initialize: function () {
var self = this;
$.get('/test/templates/mainOptions.html').success(function (data) {
self.template_loaded(data);
template = _.template(data, {name: "Test"});
}, 'html');
},
events: {
'click .signButton': 'pageToSign'
},
pageToSign: function (e) {
e.preventDefault();
Backbone.history.navigate("views/role_view", {trigger: true});
},
template_loaded: function (html) {
var template = _.template(html, {name: "Test"});
this.$el.html(template);
return this;
}
});
var person = new Person;
return new testView({model: person});
});
and final page is role_view.js
define(['jquery', 'underscore', 'backbone'], function($, _, Backbone){
var role = Backbone.View.extend({
initialize: function(){
var self = this;
$.get('/test/templates/chooseRole.html').success(function(html){
self.template_loaded(html);
});
},
events: {
'click .parentButton': 'parentClick'
},
template_loaded: function(html) {
var template = _.template(html, {name: "Test"});
this.$el.html(template);
return this;
},
parentClick: function(e) {
e.preventDefault();
Backbone.history.navigate("", {trigger: true});
}
});
return new role();
});
Thanks.
You real problem is that you're reusing views rather than destroying and creating them as needed. In your router, you have this:
mainOption: function () {
this.$el.html(this.mainOptionPage.render().$el);
},
login: function () {
this.$el.html(this.loginView.render().$el);
}
You call this.$el.html the first time, the view goes up, and everything seems to be okay. Then you switch views by calling this.$el.html and everything still seems to be okay. But the next time you switch views, your events are gone. This happens because of the way jQuery's html function works; from the fine manual:
When .html() is used to set an element's content, any content that was in that element is completely replaced by the new content. Additionally, jQuery removes other constructs such as data and event handlers from child elements before replacing those elements with the new content.
Emphasis mine. Calling this.$el.html will destroy the event bindings on the previous content (such as this.mainOptionsPage.el or this.loginView.el).
If you create and destroy views as needed:
define(['jquery', 'underscore', 'backbone'], function($, _, Backbone){
// Your Person model goes in its own file or possibly in the router file for now...
var TestView = Backbone.View.extend({
//...
});
return TestView; // Return the view "class", not an instance.
});
define(['jquery', 'underscore', 'backbone'], function($, _, Backbone){
var Role = Backbone.View.extend({
//...
});
return Role;
});
define(['jquery', 'underscore', 'backbone', 'views/role_view', 'views/main_options', 'models/person'], function ($, _, Backbone, Role, TestView, Person) {
var person = new Person; // The person model is managed here.
var appRouter = Backbone.Router.extend({
//...
initialize: function () {
// Don't need anything in here anymore.
},
//...
mainOption: function () {
// Create a new view when we need it.
this.switchTo(new TestView({ model: person }));
},
login: function() {
// Create a new view when we need it.
this.switchTo(new Role);
},
switchTo: function(view) {
// Destroy the old view since we don't need it anymore.
if(this.currentView)
this.currentView.remove();
// Keep track of the new current view so that we can
// kill it alter and avoid leaks.
this.currentView = view;
this.$el.html(this.currentView.render().el);
}
});
//...
});
Navigation is not working as I expected, when I trigger goToTournament (see below) the current view just re-renders and I get a jQuery 404 not found error on the console. The URL is changing appropriately and the correct route method is being triggered as well.
// js/views/home.js
define([
'jquery',
'jquerym',
'underscore',
'backbone',
'models/tournaments/featured',
'collections/home',
'text!/templates/home.html'
], function($, JQM, _, Backbone, FeaturedModel, HomeCollection, homeTemplate) {
var HomeView = Backbone.View.extend({
el: $('#site-main'),
events: {
'click .tournament': 'goToTournament'
},
initialize: function() {
this.render();
},
render: function() {
var homeCollection = new HomeCollection();
homeCollection.fetch({
success: function() {
var data = {tournaments: homeCollection.toJSON()};
var compiledTemplate = _.template(homeTemplate, data);
$('#site-main').html(compiledTemplate);
$('.main-content').fadeTo(500, 1);
return this;
}
});
},
goToTournament: function(e) {
this;
var t_id = $(e.currentTarget).data('id');
var router = new Backbone.Router();
router.navigate('tournament/' + t_id, {trigger: true})
}
});
return HomeView;
});
// js/router.js
define([
'jquery',
'underscore',
'backbone',
'views/home',
'views/tournament',
'collections/tournament'
], function($, _, Backbone, HomeView, TournamentView, TournamentCollection) {
var AppRouter = Backbone.Router.extend({
routes: {
'': 'home',
'tournament/:id': 'tournament'
}
});
var initialize = function() {
var app_router = new AppRouter;
app_router.on('route:home', function() {
var homeView = new HomeView();
});
app_router.on('route:tournament', function(id) {
var tournament = new TournamentCollection({ id: id });
tournament.fetch({
success: function() {
var tournamentView = new TournamentView({collection: tournament});
}
});
});
Backbone.history.start();
};
return {
initialize: initialize
};
});
I got it working by completely disabling jquery mobile's loading method. I made a jqm-config.js file and made sure it was caled before jquery mobile itself.
I want to restart all JQuery events in Backbone. The problem is this: when you go to the particular view I have the following event:
el: $('#content'),
events : {
'click #stops-drop-down' : 'stopDropDown',
},
stopDropDown: function(ui){
console.log("Event");
$("#stops-drop-down").toggleClass("focus-box");
$("#stops-list").slideToggle();
}
When you return to the previous view from which I've come to current and again go back to current and use the event, it is already running 2 times, if you do the same exercise another way it 3 times and it begins to grow. How can I deal with this problem and open the reset each time events?
Also to mention that use Requirejs. Here's what it looks like my router .js
define([
'jquery',
'underscore',
'backbone',
'views/home/home',
'views/line/line',
], function($, _, Backbone, HomeView, LineView){
var AppRouter = Backbone.Router.extend({
routes: {
'': 'homePage',
'line/:line/:type_transport': 'lineDetails',
'*action': 'errPage'
}
});
var initialize = function(){
var self = this;
var app_router = new AppRouter;
app_router.on('route:homePage', function() {
var homeView = new HomeView();
});
app_router.on('route:lineDetails', function(line, typeTransport) {
var lineDetailsView = new LineView();
lineDetailsView.render(line, typeTransport);
})
app_router.on('route:errPage', function() {
alert("Err Page");
});
Backbone.history.start();
};
return {
initialize: initialize
};
});
SOLUTION:
I decided my problem as follows: I created the close method, which has the following content:
close: function(){
this.undelegateEvents();
$(this).empty;
this.unbind();
},
Also, here's how it seems my router.js now:
define([
'jquery',
'underscore',
'backbone',
'views/home/home',
'views/line/line',
], function($, _, Backbone, HomeView, LineView){
var AppRouter = Backbone.Router.extend({
routes: {
'': 'homePage',
'line/:line/:type_transport': 'lineDetails',
'*action': 'errPage'
}
});
var initialize = function(){
var self = this;
var app_router = new AppRouter;
var lineDetailsView;
var homeView ;
app_router.on('route:homePage', function() {
if(homeView) {
homeView.close();
}
homeView = new HomeView();
});
app_router.on('route:lineDetails', function(line, typeTransport) {
if(lineDetailsView){
lineDetailsView.close();
}
lineDetailsView = new LineView();
lineDetailsView.render(line, typeTransport);
})
app_router.on('route:errPage', function() {
alert("Err Page");
});
Backbone.history.start();
};
return {
initialize: initialize
};
});
var YourView = Backbone.View.extend({
el: $('#content'),
events : {
'click #stops-drop-down' : 'stopDropDown',
},
close : function(){
this.remove(); // removes view from dom
this.unbind(); // unbinds all the events associated with the view
},
stopDropDown: function(ui){
console.log("Event");
$("#stops-drop-down").toggleClass("focus-box");
$("#stops-list").slideToggle();
}
});
// check if view already exists before creating new instance
// if exists call close on that view -- and then create your new view
if(yourView)
yourView.close();
yourview = new YourView();
check this article , there may be other reasons for the view to still exist
EDIT
this is almost the same way i have done in my application make sure you add close function as property in all views
define([
'jquery',
'underscore',
'backbone',
'views/home/home',
'views/line/line',
], function($, _, Backbone, HomeView, LineView){
var AppRouter = Backbone.Router.extend({
routes: {
'': 'homePage',
'line/:line/:type_transport': 'lineDetails',
'*action': 'errPage'
}
});
var initialize = function(){
var self = this;
var app_router = new AppRouter;
app_router.on('route:homePage', function() {
if(homeView)
homeView.close();
var homeView = new HomeView();
});
app_router.on('route:lineDetails', function(line, typeTransport) {
if(lineDetailsView)
lineDetailsView.close();
var lineDetailsView = new LineView();
lineDetailsView.render(line, typeTransport);
})
app_router.on('route:errPage', function() {
alert("Err Page");
});
Backbone.history.start();
};
return {
initialize: initialize
};
});
I am using jquery, backbonejs, underscorejs and bootstrap 3 for my project (enter link description here). This is my source code https://github.com/datomnurdin/izify-template.
My question is, how to combine 3 models into 1 collections in OrderCollections.
CategoryModel.js
define([
'underscore',
'backbone',
], function(_, Backbone) {
var CategoryModel = Backbone.Model.extend({});
return CategoryModel;
});
MerchantModel.js
define([
'underscore',
'backbone',
], function(_, Backbone) {
var MerchantModel = Backbone.Model.extend({});
return MerchantModel;
});
ProductModel.js
define([
'underscore',
'backbone',
], function(_, Backbone) {
var ProductModel = Backbone.Model.extend({});
return ProductModel;
});
OrderCollection.js
define([
'underscore',
'backbone',
'models/category/CategoryModel',
'models/merchant/MerchantModel',
'models/product/ProductModel'
], function(_, Backbone, CategoryModel, MerchantModel, ProductModel){
//stuck here
var OrderCollection = Backbone.Collection.extend({
model: CategoryModel,
url:"https://izify.com/api/izify-api/user/get_all_categories.php",
parse: function(data) {
return data.tbl_categories;
},
});
return OrderCollection;
});
And then, how to retrieve all data into one html page?
OrderView.js
define(['jquery', 'underscore', 'backbone', 'models/global/GlobalModel', 'collections/order/OrderCollection', 'views/header/HeaderView', 'views/sidebar/SidebarView', 'views/footer/FooterView', 'text!templates/category/orderTemplate.html'], function($, _, Backbone, GlobalModel, OrderCollection,HeaderView,SidebarView,FooterView, orderTemplate) {
var OrderView = Backbone.View.extend({
el: $("#page"),
initialize: function() {
this.$el.off();
},
render: function(productId) {
var that = this;
var global = new GlobalModel();
this.collection = new OrderCollection();
var formValues = {
merchantId: global.merchantId,
productId: productId
};
this.collection.fetch({
data: formValues,
success: function(collection, response) {
var template = _.template(orderTemplate, {
orders: that.collection.models
});
that.$el.html(template);
var sidebarView = new SidebarView();
sidebarView.render();
},
error: function(collection, response) {
console.log("error");
}
});
},
});
return OrderView;
});
Demo: http://staging.revivalx.com/izify-template/
Resource: https://github.com/datomnurdin/izify-template
Thanks a lot in advance.
The magic of creating a collection of different models is by implementing a model function.
here is the examples from http://backbonejs.org/#Collection-model
var Library = Backbone.Collection.extend({
model: function(attrs, options) {
if (condition) {
return new PublicDocument(attrs, options);
} else {
return new PrivateDocument(attrs, options);
}
}
});
I'm picking up backbone for the first time and I'm having some trouble getting my view to render my collection.
main.js
/*global require*/
'use strict';
require.config({
shim: {
underscore: {
exports: '_'
},
backbone: {
deps: [
'underscore',
'jquery'
],
exports: 'Backbone'
},
},
paths: {
app: 'app',
jquery: '../components/jquery/jquery',
backbone: '../components/backbone-amd/backbone',
underscore: '../components/underscore-amd/underscore',
competitions: 'collections/competition-collection',
competitionModel: 'models/Competition-model',
templates: 'templates'
}
});
require([
'backbone',
'app',
'competitions',
'competitionModel',
'views/competition-view',
'templates'
], function (
Backbone,
App,
Competitions,
CompetitionModel,
CompetitionsView
) {
window._app = new App(
Competitions,
CompetitionModel,
CompetitionsView
);
window._app.demoData();
window._app.start();
});
app.js
define([], function() {
var App = function(Competitions,CompetitionModel,CompetitionsView) {
// Our models will be instantiated later as needed later.
this.Models.CompetitionModel = CompetitionModel;
this.Collections.Competitions = Competitions;
this.Collections.competitions = new Competitions();
this.Views.competitionsView = new CompetitionsView();
//console.log(this.Views.competitionsView)
};
App.prototype = {
Views: {},
Models: {},
Collections: {},
start: function() {
this.Views.competitionsView.render();
Backbone.history.start();
},
// TODO: We'll get rid of this or move later ... just "spiking" ;)
demoData: function() {
var me = new this.Collections.Competitions(
[
{
name: 'Some Name',
},
{
name: 'Other Name',
}
]
);
console.log("***** Demo Competitions Created *****");
}
};
return App;
});
Competition-model.js
define([
'underscore',
'backbone',
], function (_, Backbone) {
'use strict';
var CompetitionModel = Backbone.Model.extend({
defaults: {
},
initialize: function(){
console.log(this.attributes);
}
});
this.listenTo(Competitions, 'add', function(){
console.log("bla")
});
return CompetitionModel;
});
competition-collection.js
define([
'underscore',
'backbone',
'models/competition-model'
], function (_, Backbone, CompetitionModel) {
'use strict';
var CompetitionCollection = Backbone.Collection.extend({
model: CompetitionModel
});
return CompetitionCollection;
});
competition-view.js
define([
'jquery',
'underscore',
'backbone',
'templates',
'competitions',
], function ($, _, Backbone, JST, Competitions) {
'use strict';
var CompetitionView = Backbone.View.extend({
template: JST['app/scripts/templates/competition.ejs'],
render: function() {
console.log(this.model);
}
});
console.log("yo")
return CompetitionView;
});
I know that the models are loaded correctly but I can't seem to figure out how to pass the model collection to the view and render all the objects.
Can anyone help?
Thanks
You have created the view, collection and models properly but haven't created a link between collection and view. You need to pass the collection to the view and use that collection in the view to render all models.
In your app.js replace:
this.Views.competitionsView = new CompetitionsView();
With:
this.Views.competitionsView = new CompetitionsView({collection: this.Collections.competitions});
Now you have a reference of collections object in your view. Now inside competition-view.js replace:
console.log(this.model);
With:
this.collection.each(function (model) {
console.log(model);
});
Also in your app.js, inside start function, you are calling Backbone.history.start() without creating a Backbone router, which is also giving a console error.