I have the following router:
define([
'backbone.marionette',
'app',
'views/products/list',
'views/products/browsing_filter',
'views/products/detail',
'views/dashboard/index',
'views/layout'
],
function(Marionette, App, ProductListView, BrowsingFilterView, ProductDetailView, LayoutView){
var AppRouter = Backbone.Marionette.AppRouter.extend({
routes: {
'product/:id': 'showProduct',
'products/:id': 'showProduct',
'products': 'listProducts',
'*path': 'showDashboard',
},
listProducts: function(path) {
App.contentRegion.show(new ProductListView());
product_filter_view = new BrowsingFilterView();
},
showProduct: function(id) {
App.contentRegion.show(new ProductDetailView({id: id}));
},
showDashboard: function() {
return require(['views/dashboard/index', 'collections/newsfeed_items','models/newsfeed_item'], function(DashboardView, NewsfeedItemCollection, NewsfeedItem) {
App.contentRegion.show(new DashboardView({
collection: new NewsfeedItemCollection(),
model: new NewsfeedItem()
}));
});
}
});
return AppRouter;
});
When a route is called it works fine. However, when the next route is called the container for the region App.contentRegion is emptied and no new content is rendered.
When the new route is called, the AJAX requests are done as they should, the view simply seems to either become detached or not rendered at all.
What is wrong?
Edit:
ProductDetailView:
define([
'jquery',
'backbone',
'models/product',
'models/product_property_value',
'models/product_property',
'hbs!template/product_detail/detail',
'hbs!template/product_detail/edit_string',
'collections/product_property_values',
'collections/newsfeed_items',
'hbs!template/newsfeed/feed'
],
function($, Backbone, ProductModel, ProductPropertyValueModel, ProductPropertyModel, ProductDetailTemplate, StringEditTemplate, ProductPropertyValueCollection, NewsfeedItemCollection, FeedTemplate){
ProductDetailView = Backbone.View.extend({
el: '#product_detail',
product_id: null,
events: {
'click a.show_edit': 'triggerEdit',
// 'click div.edit_container a.save': 'saveChanges',
'submit form.edit_property_value': 'saveChanges',
'click a.cancel_edit': 'cancelEdit'
},
initialize: function(param){
this.product_id = param.id;
this.product = new ProductModel({'id': this.product_id});
this.product.fetch();
this.newsfeeditems = new NewsfeedItemCollection({'product': {'id': this.product_id}});
this.listenTo(this.newsfeeditems, 'change', this.renderFeed);
this.listenTo(this.newsfeeditems, 'fetch', this.renderFeed);
this.listenTo(this.newsfeeditems, 'sync', this.renderFeed);
this.newsfeeditems.setProductId(this.product_id);
this.newsfeeditems.fetch({reset:true});
this.listenTo(this.product, 'change', this.render);
this.listenTo(this.product, 'fetch', this.render);
this.listenTo(this.product, 'sync', this.render);
},
renderFeed: function(r) {
context = this.newsfeeditems.toJSON();
this.$el.find('#product_newsfeed').html(FeedTemplate({items:context}));
},
edit_container: null,
product_property_model: null,
triggerEdit: function(r) {
r.preventDefault();
this.cancelEdit();
editable_container = $(r.target).parents('.editable').first();
product_property_value_ids = editable_container.data('property-value-id');
edit_container = $(editable_container).find('div.edit_container');
if(edit_container.length === 0) {
console.log(edit_container);
editable_container.append('<div class="edit_container"></div>');
edit_container = $(editable_container).find('div.edit_container');
}
this.edit_container = edit_container;
value_init = [];
for(var i = 0; i < product_property_value_ids.length; i++) {
value_init = {'id': product_property_value_ids[i]};
}
if(product_property_value_ids.length > 1) {
throw new Exception('Not supported');
}
this.edit_value = new ProductPropertyValueModel({'id': product_property_value_ids[0]});
this.listenTo(this.edit_value, 'change', this.renderEditField);
this.listenTo(this.edit_value, 'reset', this.renderEditField);
this.listenTo(this.edit_value, 'fetch', this.renderEditField);
this.edit_value.fetch({'reset': true});
return false;
},
cancelEdit: function() {
this.$el.find('.edit_container').remove();
},
renderEditField: function() {
edit_container.html(StringEditTemplate(this.edit_value.toJSON()));
},
saveChanges: function(r) {
r.preventDefault();
console.log('save changes');
ev = this.edit_value;
_.each($(r.target).serializeArray(), function(value, key, list) {
ev.set(value, key);
});
ev.save();
},
render: function(r) {
context = this.product.toJSON();
this.$el.html(ProductDetailTemplate(context));
$(document).foundation();
return this;
}
});
return ProductDetailView;
});
In our app we use appRoutes instead of routes as the key. I think that is the way you should do it when using Marionette.
Next you should make sure you are starting Backbone.history by using Backbone.history.start().
Related
I have this bb app that I'm trying to search and return the results of the search, then when cleared, so all results again. I was able to get everything to show before adding the search feature, but now nothing showing up. I think the collection isn't available at the time it's trying to populate, but can't seem to get it to wait. I've tried moving the fetch around to no avail. Any help would be greatly appreciate. For the sake of ease, I've put everything in a fiddle that can be found here...
//Campaign Model w defaults
app.model.Campaign = Backbone.Model.extend({
default: {
title: '',
img: '',
id: '',
slug: '',
image_types: 'small',
tagline: ''
}
});
//Campaign Collection from model
app.collection.Campaign = Backbone.Collection.extend({
//our URL we're fetching from
url: 'https://api.indiegogo.com/1/campaigns.json?api_token=e377270bf1e9121da34cb6dff0e8af52a03296766a8e955c19f62f593651b346',
parse: function(response) {
console.log('parsing...');
return response.response; //get array from obj to add to collection based on model
},
currentStatus: function(status){
return _(this.filter(function(data){
console.log('currentStats', status);
return data.get('_pending') == status;
}));
},
search: function(searchVal) {
console.log('search...');
if (searchVal == '') {
return this;
}
var pattern = new RegExp(searchVal, 'gi');
return _(this.filter(function(data) {
return pattern.test(data.get('title'));
}));
}
});
app.collection.campaigns = new app.collection.Campaign();
app.collection.campaigns.fetch({
success: function(){
console.log('Success...');
var sHeight = window.screen.availHeight - 200 + 'px';
$('#container ul').css('height', sHeight);
},
error: function() {
console.log('error ',arguments);
}
});
//List view for all the campaigns
app.view.CampaignList = Backbone.View.extend({
events: {
'keyup #searchBox': 'search'
},
render: function(data) {
console.log('campaignList',$(this.el).html(this.template));
$(this.el).html(this.template);
return this;
},
renderAll: function(campaigns) {
console.log('campList renderAll', campaigns, $('#campaignList'));
$('#campaignList').html('');
campaigns.each(function(campaign){
var view = new app.view.CampaignItem({
model: campaign,
collection: this.collection
});
console.log(view);
$('#campaignList').append(view.render().el);
});
return this;
},
initialize: function() {
console.log('init campList',app);
this.template = _.template($('#campaignList-tmp').html());
this.collection.bind('reset', this.render, this);
},
search: function(e) {
console.log('listView search');
var searchVal = $('#searchBox').val();
this.renderAll(this.collection.search(searchVal));
},
sorts: function() {
var status = $('#campSorting').find('option:selected').val();
if(status == '') {
status = false;
};
this.renderAll(this.collection.currentStatus(status));
}
});
//Item view for single campaign
app.view.CampaignItem = Backbone.View.extend({
events: {},
render: function(data){
console.log('campItem render...', data);
this.$el.html(this.template(this.model.toJSON()));
return this;
},
initialize: function(){
console.log('campItem init');
this.template = _.template( $('#campaignItem-tmp').html());
}
});
//Router
app.router.Campaign = Backbone.Router.extend({
routes: {
'': 'campaigns'
},
campaigns: function(){
this.campListView = new app.view.CampaignList({
collection: app.collection.campaigns
});
$('#container').append(this.campListView.render().el);
this.campListView.sorts();
}
});
app.router.campaigns = new app.router.Campaign();
Backbone.history.start();
http://jsfiddle.net/skipzero/xqvrpyx8/
In my Backbone application, I'm trying to figure out why when I click the back button on a page, the URL changes appropriately but the actual browser display does not.
Here's one flow:
Go to application: elections/
Starting page: elections/#profile/manage/:id/
Page clicked to: elections/#profile/manage/:id/personalInfo/
Back button clicked. Should end up displaying elections/#profile/manage/:id/
However when the back button is clicked, the page display doesn't change, just the URL.
I'm not sure what's causing this nor how to get around this. I've read some things about options to the Backbone.history.start() command, but whenever I add anything to it, nothing displays.
Not really sure how to go about fixing this problem. Can someone point me in the right direction? (I can expand the code samples if need be, I just thought this might be easier to read)
elections/app.js - called from elections/index.html
require.config({
baseUrl: 'js/lib',
paths: {
app: '../app',
tpl: '../tpl',
bootstrap: 'bootstrap/js/',
},
shim: {
'backbone': {
deps: ['underscore', 'jquery'],
exports: 'Backbone'
},
'underscore': {
exports: '_'
},
}
});
require([
'jquery',
'backbone',
'app/router',
], function ($, Backbone, Router) {
var router = new Router();
Backbone.history.start({pushState:true});
});
elections/js/router.js - instantiated in app.js
define(function (require) {
"use strict";
var $ = require('jquery'),
Backbone = require('backbone'),
WindowView = require('app/views/Window'),
breadcrumbs = {"Home": ""},
$body = "",
$content = "",
return Backbone.Router.extend({
routes: {
'' : 'home',
'profile/login(/)' : 'candidateProfileLogin',
'profile/manage(/)' : 'candidateProfileLogin',
'profile/manage/:id(/)' : 'candidateProfileHome',
'profile/manage/:id/:section(/)' : 'candidateProfileSection',
},
initialize: function () {
require([], function () {
$body = $('body');
windowView = new WindowView ({el: $body}).render();
$content = $("#content", windowView .el);
});
},
candidateProfileHome: function (id) {
require(["app/views/Candidate", "app/models/candidate"], function (CandidateView, models) {
var candidate = new models.Candidate({id: id});
console.log('router: candidateProfileHome');
candidate.fetch({
success: function (data, response) {
var view = new CandidateView({model: data, el: $content});
view.render();
},
error: function (data, response) {
var view = new CandidateView({model: data, el: $content});
view.render();
}
});
breadcrumbs['Profile Home'] = Backbone.history.fragment;
windowView.addBreadcrumbs(breadcrumbs);
windowView.selectMenuItem('candidate-menu');
});
},
candidateProfileSection: function (id, section) {
require(["app/views/Candidate", "app/models/candidate"], function (CandidateView, models) {
var sectionNames = {
'questionnaire': 'Questionnaire',
'endorsements' : 'Endorsements',
'photo' : 'Photo',
};
var sectionName = sectionNames[section];
var candidate = new models.Candidate({id: id});
candidate.fetch({
success: function (data, response) {
var view = new CandidateView({model: data, el: $content});
view.render(section);
},
error: function (data, response) {
//Output the data to the console. Let the template take care of the error pages
console.log(data);
var view = new CandidateView({model: data, el: $content});
view.render(section);
}
});
breadcrumbs['Profile Home'] = "profile/manage/" + id + "/";
breadcrumbs[sectionName] = Backbone.history.fragment;
windowView.addBreadcrumbs(breadcrumbs);
windowView.selectMenuItem('candidate-menu');
});
},
candidateProfileQuestionnaire: function (id, page) {
require(["app/views/Candidate", "app/models/candidate"], function (CandidateView, models) {
var pageNames = {
'personalInfo': 'Personal Information',
'essay' : 'Biography & Essay',
'survey' : 'Survey Questions',
'endorsements': 'Endorsements',
'photo' : 'Photo'
};
var pageName = "Questionnaire: " + pageNames[page];
var candidate = new models.Candidate({password: id});
candidate.fetch({
success: function (data, response) {
console.log('success');
var view = new CandidateView({model: data, el: $content});
view.render(page);
},
error: function (data, response) {
//Output the data to the console. Let the template take care of the error pages
console.log('error');
console.log(data);
var view = new CandidateView({model: data, el: $content});
view.render(page);
}
});
breadcrumbs['Profile Home'] = "profile/manage/" + id + "/";
breadcrumbs[pageName] = Backbone.history.fragment;
windowView.addBreadcrumbs(breadcrumbs);
windowView.selectMenuItem('candidate-menu');
});
},
});
});
You shouldn't need to specifically configure Backbone's router to handle browsers history state or events, it's built in. The issue lies somewhere else...in your case it was the view, rendering the template.
we have two solutions
Backbone.Router.navigate("url", {trigger: true});
or
Backbone.history.back();
I'm trying to improve the navigation of my little backbone application. Right now I just have some simple navigation using html links that use to #path/to/page in the href element.
What I'm running into is when I click on one of these and then click the back button, the page doesn't refresh properly, and the HTML content doesn't change. So I'm trying to incorporate the navigate functionality into my code.
The issue I'm running into is that I can't find an example that matches the code layout I'm currently using, and I don't understand how backbone works enough to adapt the things I find into something useful.
Here's what I've got:
app.js - called from the index.html file
require.config({
baseUrl: 'js/lib',
paths: {
app: '../app',
tpl: '../tpl',
bootstrap: 'bootstrap/js/',
},
shim: {
'backbone': {
deps: ['underscore', 'jquery'],
exports: 'Backbone'
},
'underscore': {
exports: '_'
}
}
});
require([
'jquery',
'backbone',
'app/router',
], function ($, Backbone, Router) {
var router = new Router();
Backbone.history.start();
});
app/router.js - instantiated in app.js
define(function (require) {
"use strict";
var $ = require('jquery'),
Backbone = require('backbone'),
WindowView = require('app/views/Window'),
breadcrumbs = {"Home": ""},
$body = "",
$content = "",
windowView = "";
return Backbone.Router.extend({
initialize: function () {
require([], function () {
$body = $('body');
windowView = new WindowView({el: $body}).render();
$content = $("#content", windowView.el);
});
},
routes: {
'' : 'home',
'profile/login(/)' : 'candidateProfileLogin',
'profile/manage(/)' : 'candidateProfileLogin',
'profile/manage/:id(/)' : 'candidateProfileHome',
'profile/manage/:id/questionnaire/:page(/)' : 'candidateProfileQuestionnaire',
'profile/manage/:id/:section(/)' : 'candidateProfileSection',
},
home: function (){
},
candidateProfileLogin: function () {
require(['app/views/CandidateLogin'], function (CandidateLoginView) {
console.log(Backbone.history.fragment);
var view = new CandidateLoginView({el: $content});
view.render();
});
},
candidateProfileHome: function (id) {
require(["app/views/Candidate", "app/models/candidate"], function (CandidateView, models) {
var candidate = new models.Candidate({id: id});
candidate.fetch({
success: function (data) {
var view = new CandidateView({model: data, el: $content});
view.render();
},
error: function (data) {
var view = new CandidateView({model: data, el: $content});
view.render();
}
});
});
},
candidateProfileSection: function (id, section) {
require(["app/views/Candidate", "app/models/candidate"], function (CandidateView, models) {
var candidate = new models.Candidate({id: id});
candidate.fetch({
success: function (data) {
var view = new CandidateView({model: data, el: $content});
view.render(section);
},
error: function (data) {
//Output the data to the console. Let the template take care of the error pages
console.log(data);
var view = new CandidateView({model: data, el: $content});
view.render();
}
});
});
},
candidateProfileQuestionnaire: function (id, page) {
require(["app/views/Candidate", "app/models/candidate"], function (CandidateView, models) {
var candidate = new models.Candidate({id: id});
candidate.fetch({
success: function (data) {
var view = new CandidateView({model: data, el: $content});
view.render(page);
},
error: function (data) {
//Output the data to the console. Let the template take care of the error pages
console.log(data);
var view = new CandidateView({model: data, el: $content});
view.render();
}
});
});
},
});
});
app/views/Candidate.js - My view I'm trying to process the clicks
define(function (require) {
"use strict";
var $ = require('jquery'),
_ = require('underscore'),
Backbone = require('backbone'),
tpl = require('text!tpl/Candidate.html'),
template = _.template(tpl),
CandidateErrorView = require('app/views/CandidateError'),
errtpl = require('text!tpl/CandidateError.html'),
errTemplate = _.template(errtpl);
return Backbone.View.extend({
events: {
'submit #voters-guide-personalInfo': 'savePersonalInfo',
'submit #voters-guide-essay' : 'saveEssay',
'submit #voters-guide-survey' : 'saveSurvey',
'submit #voters-guide-endorsements': 'saveEndorsements',
'submit #voters-guide-photo' : 'savePhoto',
'click #table-of-contents a' : 'navTOC',
},
savePersonalInfo: function (event) {
console.log(event);
},
saveEssay: function (event) {
console.log(event);
},
saveSurvey: function (event) {
console.log(event);
},
saveEndorsements: function (event) {
console.log(event);
},
savePhoto: function(event) {
console.log(event);
},
navTOC: function (event) {
console.log(event.target);
var id = $(event.target).data('candidate-id');
var path = $(event.target).data('path');
//router.navigate("profile/manage/" + id + "/" + path, {trigger: true});
},
render: function (page) {
//Check to see if we have any errors
if (!this.model.get('error')) {
var dataToSend = {candidate: this.model.attributes};
switch(page) {
case 'personalInfo':
template = _.template(require('text!tpl/Candidate-personalInfo.html'));
break;
case 'essay':
template = _.template(require('text!tpl/Candidate-essay.html'));
break;
case 'survey':
template = _.template(require('text!tpl/Candidate-survey.html'));
break;
case 'endorsements':
template = _.template(require('text!tpl/Candidate-endorsements.html'));
break;
case 'photo':
template = _.template(require('text!tpl/Candidate-photo.html'));
break;
default:
break;
}
this.$el.html(template(dataToSend));
return this;
} else {
this.$el.html(errTemplate({candidate: this.model.attributes}));
return this;
}
}
});
});
Now, in an attempt to stop the 'the page content doesn't reload when I hit the back button' issue, I've been looking into the navigate function that backbone has available (this: router.navigate(fragment, [options]);). There are lots of examples of how this is used, but none of them seem to have anything similar to the file setup that I'm using, so I'm not exactly sure how best to access this functionality from my view. If I include the router file in the view and instantiate a new version of it, the page breaks b/c it tries to run the initialize function again.
I'm just really at a loss on how this is supposed to work.
Can someone point me in the right direction?
Thanks!
--Lisa
P.S. If someone has any better ideas, I am all ears!
You should have access to the Backbone object, which within it, has access to navigate around using the history.navigate function. If you call that passing in trigger: true you'll invoke the route. For instance:
Backbone.history.navigate("profile/manage", { trigger: true });
I have Backbone Model that collect data from server:
Job.Models.Response = Backbone.Model.extend({
defaults: {
'authStatus': false,
'id': '1',
'name': 'name',
},
urlRoot: '/static/js/public/json/'
});
I have button with data-id = "id from /static/js/public/json/".
Job.Views.Response = Backbone.View.extend({
el: '.ra-response-button',
events: {
"click": "load"
},
load: function () {
var info = this.$el.data();
this.model.set({ id: info.id});
this.model.fetch();
if (this.model.attributes.authStatus === false) {
console.log('Register')
}
else {
console.log('Unregister')
}
}
});
If i console.log my model after fetch, its dont update, but data fetch success.
What kind of problem can be here?
Here i init our plugin:
var responseModel = new Job.Models.Response;
var response = new Job.Views.Response({ model: responseModel });
I resolve my problem. Finally View.
Job.Views.Response = Backbone.View.extend({
el: '.ra-response-button',
events: {
"click": "load"
},
load: function () {
var that = this;
var info = that.$el.data();
that.model.set({ id: info.id});
that.model.fetch({
success: function() {
if (that.model.attributes.authStatus === true) {
new Job.Views.ResponseForm({ model: that.model })
}
else {
new Job.Views.ResponseAuth({ model : that.model })
}
},
error: function() {
alert('Error, repeat please.')
}
});
}
});
Unfortunately, I have written a sub-view for my project. While it had previously rendered perfectly well and exactly how I wanted it to, I learned that my app was not structured properly. I am in the process of restructuring, and everything else works like a charm, but with my new setup it doesn't seem to render in its el. The el renders perfectly fine, but nothing is inserted. I put a console.log() in that printed the template that was supposed to render, and it is flawless. However, the that.$.el.html() line doesn't render the template at all. I am not sure what is going on. The file is below. My noobish-ness is killing me!
contactlist.js (View):
define([
'jquery',
'underscore',
'backbone',
'text!templates/contactlist.html',
'collections/contacts'
], function($, _, Backbone, contactListTemplate, Contacts){
var ContactListView = Backbone.View.extend({
initialize: function () {
var that = this;
this.options = {
idclicked: null,
query: null,
scrolled: 0
};
this.options.get = function (property) {
return that.options[property];
};
this.options.set = function (properties) {
for (property in properties) {
that.options[property] = properties[property];
}
};
},
el: '.contact-list',
render: function() {
var that = this;
var contacts = new Contacts();
contacts.fetch({
success: function(contacts) {
var results = contacts.models;
if (that.options.query || that.options.query === '') {
var query = that.options.query.toUpperCase();
var results = contacts.toArray();
results = contacts.filter(function(contact){
return _.any(['firstname', 'lastname', 'email', 'phonenumber'], function(attr){
return contact.get(attr).toString().toUpperCase().indexOf(query) !== -1;
});
});
that.options.set({idclicked: null});
}
if (!that.options.idclicked && results[0]) {
that.options.idclicked = results[0].get('id');
Backbone.history.navigate('/contacts/' + results[0].get('id'), {trigger: true});
}
var template = _.template(contactListTemplate, {contacts: results, idclicked: that.options.idclicked});
that.$el.html(template);
$(document).ready(function() {
$('.contact-list').scrollTop(that.options.scrolled);
});
}
});
},
events: {
'click .contact': 'clickContact'
},
clickContact: function (ev) {
$('.contact-clicked').removeClass('contact-clicked').addClass('contact');
$(ev.currentTarget).addClass('contact-clicked').removeClass('contact');
window.location.replace($(ev.currentTarget).children('a').attr('href'));
}
});
return ContactListView;
});
UPDATE:
tried one thing that worked, but not sure about whether it is a good practice or not. The code below is the parent view:
allcontacts.js (View):
define([
'jquery',
'underscore',
'backbone',
'text!templates/allcontacts.html',
'views/contactlist',
'collections/contacts'
], function($, _, Backbone, allContactsTemplate, ContactListView, Contacts) {
var AllContactsView = Backbone.View.extend({
initialize: function () {
var that = this;
this.options = {
idclicked: null,
pageloaded: false,
renderlist: true,
scrolled: null
};
this.options.get = function (property) {
return that.options[property];
};
this.options.set = function (properties) {
for (property in properties) {
that.options[property] = properties[property];
}
};
that.contactListView = new ContactListView();
},
el: '.allcontacts',
render: function() {
var that = this;
if (!that.options.pageloaded) {
var template = _.template(allContactsTemplate, {});
that.$el.html(template)
}
if (that.options.renderlist) {
this.contactListView.options.set({idclicked: that.options.idclicked, scrolled: that.options.scrolled});
this.contactListView.setElement('.contact-list').render();
}
},
events: {
'keyup .search': 'search',
'submit .search-contacts-form': 'cancelSubmit'
},
search: function (ev) {
this.contactListView.options.set({idclicked: this.options.idclicked, query: $(ev.currentTarget).val(), scrolled: this.options.scrolled});
this.contactListView.setElement('.contact-list').render();
},
cancelSubmit: function (ev) {
return false;
}
});
return AllContactsView;
});
Check if your el element is available when you bind it. Seems like it's not rendered yet.